def copy(source_track: 'Track') -> 'Track': measure_list = None if source_track.measure_list: # noinspection PyTypeChecker measure_list = [Measure.copy(measure) for measure in source_track.measure_list] return Track(to_add=measure_list, name=source_track.name, instrument=source_track.instrument, meter=source_track._meter, swing=source_track._swing, performance_attrs=source_track._performance_attrs)
def _measure(mn=None, meter=None, swing=None, num_notes=None, attr_val_default_map=None): num_notes = num_notes or NUM_NOTES mn.attr_val_default_map = attr_val_default_map or ATTR_VAL_DEFAULT_MAP measure = Measure(meter=meter, swing=swing, num_notes=num_notes, mn=mn) if len(measure) == 4: measure[1].start += DUR measure[2].start += (DUR * 2) measure[3].start += (DUR * 3) return measure
def _generate_tracks_and_layout(num_tracks, measures_per_track, meter): note_config, scale = _get_note_config_and_scale(meter) for track_idx in range(num_tracks): track = MidiTrack(meter=meter, channel=track_idx, instrument=INSTRUMENT) TRACKS.append(track) CHANNELS.append([]) LAYOUT.append([]) layout_measures_row = [] mingus_keys = Chord.get_mingus_chord_for_harmonic_chord(key=KEY_PITCH, harmonic_chord=HarmonicChord.MajorTriad) mingus_key_to_enum_mapping = Scale.get_mingus_key_to_key_enum_mapping(KEY) chord_label = \ f'{KEY_PITCH.name}.{HARMONIC_CHORD.name}:.{"-".join(mingus_key_to_enum_mapping[mingus_key].name for mingus_key in mingus_keys)}' chord_pitches = '_'.join(str(pitch) for pitch in get_chord_pitches(mingus_keys=mingus_keys, mingus_key_to_key_enum_mapping=mingus_key_to_enum_mapping, pitch_for_key=pitch_for_key, octave=OCTAVE)) for measure_idx in range(measures_per_track): measure = Measure(meter=meter, num_notes=meter.beats_per_measure, mn=note_config) track.append(measure) layout_notes = [] for k in range(meter.beats_per_measure): # Initialize each note to the params of the root note in the Scale set_attr_vals_from_dict(measure[k], as_dict(scale[0])) # PySimpleGUI refers to UI objects by "key" and returns this key when events are trapped on the UI. # Prepend key with track num, this is the channel for the queue of messages from the UI to this track. # Key each button to it's index in the flattened Messages list, key * 2 because the # index into Messages is even indexes, because Messages are note_on/note_off pairs. # NOTE: 'key' is overloaded and here means unique id referring to PySimpleGUI object. # key must be unique. For note on_off the value is unique on each iteration so don't need key event_id = f'{track_idx}_{2 * ((measure_idx * meter.beats_per_measure) + k)}' start_key = f'note_on_off|{event_id}|{event_id}' chord_key = f'chord|{event_id}|{str(chord_pitches)}' note_checkbox = sg.Checkbox(str (k + 1), default=False, enable_events=True, key=start_key) layout_notes.append(note_checkbox) NOTE_ELEMENTS[track_idx].append(note_checkbox) # chord_key needs a unique key id and a value, because values (i.e. which chord a note is) can repeat layout_notes.append(sg.DropDown(values=str(chord_label), key=chord_key, enable_events=True, size=(15, 15))) layout_measures_row.append(sg.Frame(title=f'Measure {measure_idx + 1}', layout=[layout_notes])) LAYOUT[track_idx].append(sg.Frame(title=f'Track {track_idx + 1}', layout=[layout_measures_row]))
def test_getitem_insert_remove(make_note_config, section, measure, meter, swing): expected_len = len(_measure_list(make_note_config, meter, swing)) assert len(section) == expected_len old_first_measure = section[0] old_first_note = old_first_measure[0] old_first_note_amplitude = old_first_note.amplitude insert_measure = Measure.copy(measure) expected_new_first_note_amplitude = old_first_note_amplitude + 1 insert_measure[0].amplitude = expected_new_first_note_amplitude section.insert(0, insert_measure) assert len(section) == expected_len + 1 new_first_measure = section[0] new_first_note = new_first_measure[0] assert new_first_note.amplitude == expected_new_first_note_amplitude assert new_first_note.amplitude != old_first_note_amplitude section.remove((0, 1)) assert len(section) == expected_len old_first_measure = section[0] old_first_note = old_first_measure[0] assert old_first_note.amplitude == old_first_note_amplitude
; - str1, str2, str3 ... where str is a fixed harmonic partial ; - the value of str# is the relative strength of the partial in the final mixed timbre ; - partials to be skipped are given value 0 ; ; Func # Loadtm TblSize GEN Parameters ... ; First partial variations f 1 0 8193 10 1''' SCORE_HEADER_LINES = [SCORE_HEADER] if __name__ == '__main__': meter = Meter(beats_per_measure=BEATS_PER_MEASURE, beat_note_dur=BEAT_DUR, tempo=TEMPO_QPM) swing = Swing(swing_range=SWING_RANGE) measure = Measure(num_notes=NUM_NOTES, meter=meter, swing=swing, mn=DEFAULT_NOTE_CONFIG()) for i in range(NUM_NOTES): measure[i].instrument = INSTRUMENT_1_ID measure[i].start = (i % NUM_NOTES) * DUR measure[i].duration = DUR measure[i].amplitude = BASE_AMP measure[i].pitch = PITCH measure.apply_swing() track = Track(to_add=[measure], name='ostinato', instrument=INSTRUMENT_1_ID) song = Song(to_add=[track], name=SONG_NAME) orchestra = CSoundOrchestra(instruments=INSTRUMENTS, sampling_rate=SR,
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
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) ostinato_measure.apply_swing() ostinato_track.append(ostinato_measure)
def copy(source: 'Section') -> 'Section': measure_list = None if source.measure_list: measure_list = [Measure.copy(measure) for measure in source.measure_list] return Section(measure_list=measure_list, performance_attrs=source._performance_attrs)