Ejemplo n.º 1
0
 def new_note(mn: MakeNoteConfig = None) -> Any:
     """Factory method to construct a single note with underlying storage so it can be appended to another
     NoteSequence like a Measure. Returns a NoteSequence of length 1 and a reference to the Note in that
     sequence, so that there is reference to the underlying NoteSequence with the storage to the note
     in the calling scope. If we didn't do that the Note reference would be invalid."""
     seq = NoteSequence(num_notes=1,
                        mn=MakeNoteConfig.copy(mn))
     return seq.note(0)
Ejemplo n.º 2
0
    def copy(source: 'Measure') -> 'Measure':
        new_measure = Measure(meter=source.meter,
                              swing=source.swing,
                              num_notes=source.num_notes,
                              mn=MakeNoteConfig.copy(source.mn),
                              performance_attrs=source.performance_attrs)

        # Copy the underlying np array from source note before constructing a Measure (and parent class NoteSequence)
        #  from the source. This is because both of those __init__()s construct new storage and notes from the
        #  measure's MakeNoteConfig. If that has attr_vals_default_map set it will use that to construct the notes.
        #  But we want copy ctor semantics, not ctor semantics. So we have to repeat the same logic as is found
        #  in NoteSequence.copy() and copy the underlying note storage from source to target.
        new_measure.note_attr_vals = np_copy(source.note_attr_vals)

        new_measure.beat = source.beat
        new_measure.next_note_start = source.next_note_start
        return new_measure
Ejemplo n.º 3
0
 def copy(source: 'Chord') -> 'Chord':
     return Chord(harmonic_chord=source.harmonic_chord,
                  octave=source.octave,
                  key=source.key,
                  mn=MakeNoteConfig.copy(source.mn))
Ejemplo n.º 4
0
    def _parse_pattern_to_section(self,
                                  pattern: str = None,
                                  instrument: Union[float, int] = None,
                                  swing: Swing = None,
                                  arpeggiate: bool = False,
                                  arpeggiator_chord: Optional[HarmonicChord] = None) -> Section:
        section = Section([])
        swing = swing or self.swing

        def _make_note_vals(_instrument, _start, _duration, _amplitude, _pitch):
            _note_vals = NoteValues(self.mn.attr_name_idx_map.keys())
            _note_vals.instrument = _instrument
            _note_vals.start = _start
            _note_vals.duration = _duration
            _note_vals.amplitude = _amplitude
            _note_vals.pitch = _pitch
            return _note_vals

        measure_tokens = [t.strip() for t in pattern.split(Sequencer.MEASURE_TOKEN_DELIMITER)]
        for measure_token in measure_tokens:
            note_tokens = [t.strip() for t in measure_token.split()]
            next_start = 0.0
            duration = self.default_note_duration
            # Sum up the duration of all note positions to validate that the notes fit in the measure. We look at
            # "note positions" because for chords we only count the duration of all the notes in the chord once,
            # because they sound simultaneously so that duration only contributes to the total duration once.
            measure_duration = 0.0
            note_vals_lst = []
            for i, note_token in enumerate(note_tokens):
                start = self.mn.attr_val_cast_map['start'](next_start)

                # It's a rest note
                if note_token == Sequencer.REST_TOKEN:
                    # Dummy values
                    amplitude = self.mn.attr_val_cast_map['amplitude'](0)
                    pitch = self.mn.attr_val_cast_map['pitch'](1)
                    note_vals = _make_note_vals(instrument, start, duration, amplitude, pitch)
                    note_vals_lst.append(note_vals)
                    measure_duration += duration
                # It's a sounding note or chord, parse the pattern and collect the note/chord parameters
                else:
                    key, octave, chord, amplitude, duration = note_token.split(Sequencer.NOTE_TOKEN_DELIMITER)
                    # Only major or minor notes supported
                    key = MAJOR_KEY_DICT.get(key) or MINOR_KEY_DICT.get(key)
                    if not key:
                        raise InvalidPatternException(f'Pattern \'{pattern}\' has invalid key {key} token')
                    octave = int(octave)
                    amplitude = self.mn.attr_val_cast_map['amplitude'](amplitude)
                    # If no duration provided we already assigned default note duration (quarter note)
                    if duration:
                        duration = float(duration)

                    # It's a chord. `arpeggiate=True` is ignored.
                    if chord:
                        # Chord can be empty, but if there is a token it must be valid
                        harmonic_chord = HARMONIC_CHORD_DICT.get(chord)
                        if not harmonic_chord:
                            raise InvalidPatternException(f'Pattern \'{pattern}\' has invalid chord {chord} token')
                        chord_sequence = Chord(harmonic_chord=harmonic_chord, octave=octave, key=key, mn=self.mn)
                        for note in chord_sequence:
                            note_vals_lst.append(_make_note_vals(instrument, start, duration, amplitude, note.pitch))
                        # Only count duration of the chord once in the total for the measure
                        measure_duration += duration
                    # It's a single sounding note. Converted into arpeggiated chords if `arpeggiate=True`.
                    else:
                        pitch = self.mn.pitch_for_key(key, octave)

                        if not arpeggiate:
                            note_vals = _make_note_vals(instrument, start, duration, amplitude, pitch)
                            note_vals_lst.append(note_vals)
                        else:
                            harmonic_chord = arpeggiator_chord or self.arpeggiator_chord
                            chord_sequence = Chord(harmonic_chord=harmonic_chord, octave=octave, key=key, mn=self.mn)
                            arpeggiation_offset = duration / len(chord_sequence)
                            chord_sequence.mod_ostinato(init_start_time=start, start_time_interval=arpeggiation_offset)
                            # Duration of arpeggiated notes are fit into the duration of the notated note
                            arpeggiated_note_duration = duration / len(chord_sequence)
                            for note in chord_sequence:
                                note_vals_lst.append(_make_note_vals(
                                        instrument, note.start, arpeggiated_note_duration, amplitude, note.pitch))

                        measure_duration += duration
                next_start += duration

            measure = Measure(num_notes=len(note_vals_lst),
                              meter=self.meter,
                              swing=swing,
                              mn=MakeNoteConfig.copy(self.mn))

            # TODO BETTER RULE THAN THIS FOR ARPEGGIATION
            # TODO WE SHOULD NOT NEED THIS ANYMORE BUT WE STILL DO OR TESTS FAIL ON MEASURE DURATION
            # Don't validate measure duration if we are arpeggiating, because arpeggiating on the last note will
            #  push offset start times beyond the end of the measure
            if not arpeggiate and measure_duration != \
                    pytest.approx(self.meter.beats_per_measure * self.meter.beat_note_dur.value):
                raise InvalidPatternException((f'Measure duration {measure_duration} != '
                                               f'self.meter.beats_per_measure {self.meter.beats_per_measure} * '
                                               f'self.meter_beat_note_dur {self.meter.beat_note_dur.value}'))
            for i, note_vals in enumerate(note_vals_lst):
                set_attr_vals_from_note_values(measure.note(i), note_vals)

            section.append(measure)

        return section
Ejemplo n.º 5
0
        name='chords',
        instrument=MidiInstrument.Acoustic_Grand_Piano.value,
        channel=2)

    # Ostinato
    swing_factor = 0.008
    swing = Swing(swing_on=True,
                  swing_range=swing_factor,
                  swing_direction=Swing.SwingDirection.Both)

    dur = NoteDur.THIRTYSECOND
    # noinspection PyTypeChecker
    dur_val: float = dur.value
    notes_per_measure = int((1 / dur_val) * (BEATS_PER_MEASURE * BEAT_DUR_VAL))
    for _ in range(NUM_MEASURES):
        note_config = MakeNoteConfig.copy(NOTE_CONFIG)
        ostinato_measure = Measure(num_notes=notes_per_measure,
                                   meter=METER,
                                   swing=swing,
                                   mn=note_config)

        for i in range(notes_per_measure):
            note_values = NoteValues(ATTR_NAMES)
            note_values.time = i * dur_val
            note_values.duration = dur_val
            note_values.velocity = int(BASE_VELOCITY - (
                (i % notes_per_measure) / VELOCITY_FACTOR))
            note_values.pitch = SCALE[i % NUM_NOTES_IN_SCALE].pitch
            note_config.attr_val_default_map = note_values.as_dict()
            note = NoteSequence.new_note(note_config)
            ostinato_measure.append(note)