def __init__(self, harmonic_chord: Any = None, octave: int = None, key: Union[MajorKey, MinorKey] = None, mn: MakeNoteConfig = None): validate_types(('harmonic_chord', harmonic_chord, HarmonicChord), ('octave', octave, int), ('mn', mn, MakeNoteConfig)) validate_type_choice('key', key, (MajorKey, MinorKey)) self.matched_key_type = Chord.get_key_type(key, harmonic_chord) # Assign attrs before completing validation because it's more convenient to check for required # attrs by using getattr(attr_name) after they have been assigned self.harmonic_chord = harmonic_chord self.octave = octave self.pitch_for_key = mn.pitch_for_key self.num_attributes = mn.num_attributes # Get the list of keys in the chord as string names from mingus self.key = key self.mingus_chord = Chord.get_mingus_chord_for_harmonic_chord(key, harmonic_chord) # Construct the sequence of notes for the chord in the NoteSequence base class super(Chord, self).__init__(num_notes=len(self.mingus_chord), mn=mn) # Convert to Notes for this chord's note_type with pitch assigned for the key in the chord self._mingus_key_to_key_enum_mapping = Scale.get_mingus_key_to_key_enum_mapping(self.matched_key_type) set_notes_pitches_to_mingus_keys(self.mingus_chord, self._mingus_key_to_key_enum_mapping, self, self.pitch_for_key, self.octave, validate=False)
def add_notes_on_start(self, to_add: NoteSequence) -> 'Measure': """Uses note as a template and makes copies of it to fill the measure. Each new note's start time is set to that of the previous notes start + duration. Validates that all the durations fit in the total duration of the measure. NOTE: This *replaces* all notes in the Measure with this sequence of notes on the beat """ validate_types(('to_add', to_add, NoteSequence)) # TODO DO THIS IN NUMPY NATIVE WAY sum_of_durations = sum( self._get_duration_for_tempo(note) for note in to_add) if self.next_note_start + sum_of_durations > self.meter.measure_dur_secs: raise ValueError( (f'measure.next_note_start {self.next_note_start} + ' f'sum of note.durations {sum_of_durations} > ' f'measure.max_duration {self.max_duration}')) for note in to_add: note.duration = self._get_duration_for_tempo(note) note.start = self.next_note_start self.next_note_start += note.duration super(Measure, self).append(note) self._sort_notes_by_start_time() return self
def __init__(self, name: Optional[str] = None, num_measures: int = None, meter: Optional[Meter] = None, swing: Optional[Swing] = None, player: Optional[Union[Player, Writer]] = None, arpeggiator_chord: Optional[Chord] = None, mn: MakeNoteConfig = None): validate_types(('num_measures', num_measures, int), ('mn', mn, MakeNoteConfig)) validate_optional_types(('name', name, str), ('swing', swing, Swing), ('arpeggiator_chord', arpeggiator_chord, Chord)) validate_optional_type_choice('player', player, (Player, Writer)) # Sequencer wraps song but starts with no Tracks. It provides an alternate API for generating and adding Tracks. to_add = [] meter = meter or Sequencer.DEFAULT_METER super(Sequencer, self).__init__(to_add, name=name, meter=meter, swing=swing) self.player = player if self.player: self.player.song = self self.arpeggiator_chord = arpeggiator_chord or Sequencer.DEFAULT_ARPEGGIATOR_CHORD self.mn = mn self.num_measures = num_measures or Sequencer.DEFAULT_NUM_MEASURES self.default_note_duration: float = self.meter.beat_note_dur.value self.num_tracks = 0 # Internal index to the next track to create when add_track() or add_pattern_as_track() are called self._next_track = 0 self._track_name_idx_map = {} self._track_name_player_map = {}
def set_track_pattern(self, track_name: str = None, pattern: str = None, instrument: Optional[Union[float, int]] = None, swing: Optional[Swing] = None, arpeggiate: bool = False, arpeggiator_chord: Optional[HarmonicChord] = None): """ - Sets the pattern, a section of measures in the track named `track_name`. - If the track already has a pattern, it is replaced. If the track is empty, its pattern is set to `pattern`. - If `swing` is arg is supplied, then the track will have swing applied using it - If the `instrument` arg is supplied, this instrument will be bound to the track, replacing whatever instrument it previously was bound to - If the `apply_swing` arg is True and the class has `self.swing` then the class swing object will be used to apply swing """ validate_types(('track_name', track_name, str), ('pattern', pattern, str)) validate_optional_types(('arpeggiate', arpeggiate, bool), ('arpeggiator_chord', arpeggiator_chord, HarmonicChord), ('swing', swing, Swing)) validate_optional_type_choice('instrument', instrument, (float, int)) # Will raise if track_name is not valid track = self.track_list[self._track_name_idx_map[track_name]] if track.measure_list: track.remove((0, self.num_measures)) instrument = instrument or track.instrument section = self._parse_pattern_to_section(pattern=pattern, instrument=instrument, arpeggiate=arpeggiate, arpeggiator_chord=arpeggiator_chord) if len(section) < self.num_measures: self._fill_section_to_track_length(section) track.extend(to_add=section) swing = swing or self.swing if swing and swing.is_swing_on(): track.apply_swing()
def __setitem__(self, index: int, note_sequence: NoteSequence) -> None: validate_types(('index', index, int), ('note_sequence', note_sequence, NoteSequence)) if abs(index) >= len(self.note_seq_seq): raise IndexError( f'`index` out of range index: {index} len(note_seq_seq): {len(self.note_seq_seq)}' ) self.note_seq_seq[index] = NoteSequence.copy(note_sequence)
def __init__(self, note: Any, measure: Measure, event_type: MidiEventType): validate_types(('measure', measure, Measure), ('event_type', event_type, MidiEventType)) self.note = note self.measure = measure self.event_type = event_type self.event_time = abs(self._event_time()) self.tick = self._tick() self.tick_delta = 0
def get_chord_pitches(mingus_keys: Sequence[str], mingus_key_to_key_enum_mapping: Mapping, pitch_for_key: Any, octave: int) -> Sequence[Any]: validate_types(('mingus_key_to_key_enum_mapping', mingus_key_to_key_enum_mapping, Mapping), ('octave', octave, int)) validate_sequence_of_type('mingus_keys', mingus_keys, str) return [ pitch_for_key(mingus_key_to_key_enum_mapping[mingus_key.upper()], octave=octave) for mingus_key in mingus_keys ]
def set_note_pitch_to_mingus_key(mingus_key: str, mingus_key_to_key_enum_mapping: Mapping, note: Any, pitch_for_key: Any, octave: int, validate=True): if validate: validate_types(('mingus_key', mingus_key, str), ('mingus_key_to_key_enum_mapping', mingus_key_to_key_enum_mapping, Mapping), ('octave', octave, int)) key = mingus_key_to_key_enum_mapping[mingus_key.upper()] note.pitch = pitch_for_key(key, octave=octave)
def add_notes_on_beat(self, to_add: NoteSequence) -> 'Measure': """Uses note as a template and makes copies of it to fill the measure. Each new note's start time is set to that beat start time. NOTE: This *replaces* all notes in the Measure with this sequence of notes on the beat """ validate_types(('to_add', to_add, NoteSequence)) if len(to_add) > self.meter.beats_per_measure: raise ValueError( f'Sequence `to_add` must have a number of notes <= to the number of beats per measure' ) # Now iterate the beats per measure and assign each note in note_list to the next start time on the beat for i, beat_start_time in enumerate(self.meter.beat_start_times_secs): # There might be fewer notes being added than beats per measure if i == len(to_add): break to_add[i].start = beat_start_time self.extend(to_add) return self
def __init__(self, key: Union[MajorKey, MinorKey] = None, octave: int = None, harmonic_scale: HarmonicScale = None, mn: MakeNoteConfig = None): validate_types(('octave', octave, int), ('harmonic_scale', harmonic_scale, HarmonicScale), ('mn', mn, MakeNoteConfig)) # Use return value to detect which type of enum `key` is. Use this to determine which KEY_MAPPING # to use to convert the mingus key value (a string) to the enum key value (a member of MajorKey or MinorKey) _, matched_key_type = validate_type_reference_choice('key', key, (MajorKey, MinorKey)) self.is_major_key = matched_key_type is MajorKey self.is_minor_key = matched_key_type is MinorKey self.key = key self.octave = octave self.harmonic_scale = harmonic_scale # Get the mingus keys (pitches) for the musical scale (`scale_type`) with its root at `key` str_key_dict = MAJOR_KEY_DICT if self.is_major_key else MINOR_KEY_DICT mingus_keys = harmonic_scale.value(list(str_key_dict.keys())[0]).ascending() # Trim the last element because mingus returns the first note in the next octave along with all the # notes in the scale of the octave requested. This behavior is observed and not exhaustively tested # so check and only remove if the first and last note returned are the same. if mingus_keys[0] == mingus_keys[-1]: mingus_keys = mingus_keys[:-1] mingus_key_to_key_enum_mapping = Scale.KEY_MAPS[matched_key_type.__name__] self.keys = [mingus_key_to_key_enum_mapping[mingus_key.upper()] for mingus_key in mingus_keys] # Construct the sequence of notes for the chord in the NoteSequence base class super(Scale, self).__init__(num_notes=len(mingus_keys), mn=mn) set_notes_pitches_to_mingus_keys(mingus_keys, mingus_key_to_key_enum_mapping, self, mn.pitch_for_key, self.octave, validate=False)
def __init__(self, num_notes: int = None, child_sequences: Sequence['NoteSequence'] = None, mn: MakeNoteConfig = None): validate_types(('num_notes', num_notes, int), ('num_attributes', mn.num_attributes, int), ('attr_name_idx_map', mn.attr_name_idx_map, dict)) validate_optional_type('attr_val_default_map', mn.attr_val_default_map, dict) validate_sequence_of_type('attr_name_idx_map', mn.attr_name_idx_map.keys(), str) validate_sequence_of_type('attr_name_idx_map', mn.attr_name_idx_map.values(), int) if mn.attr_val_default_map: validate_sequence_of_type('attr_vals_map', list(mn.attr_val_default_map.keys()), str) validate_sequence_of_type_choice('attr_vals_map', list(mn.attr_val_default_map.values()), (float, int)) validate_optional_type_choice('child_sequences', child_sequences, (list, set)) validate_optional_sequence_of_type('child_sequences', child_sequences, NoteSequence) self.mn = mn # Construct empty 2D numpy array of the specified dimensions. Each row stores a Note's values. rows = [[0.0] * self.mn.num_attributes for _ in range(num_notes)] self.note_attr_vals = np_array(rows) if num_notes > 0: # THIS MUST NOT BE ALTERED self._num_attributes = self.note_attr_vals.shape[1] if self.mn.attr_val_default_map: assert set(self.mn.attr_val_default_map.keys()) <= set(self.mn.attr_name_idx_map.keys()) for note_attr in self.note_attr_vals: for attr_name, attr_val in self.mn.attr_val_default_map.items(): note_attr[self.mn.attr_name_idx_map[attr_name]] = attr_val self.child_sequences = child_sequences or [] # Absolute index position over all sequences, that is self.note_attr_vals and the note_attr_vals of each # child_sequence, and, recursively, any of its child sequences. # So if this sequence has 10 notes and it has one child sequence with 11 notes then self.index # will move from 0 to 20 and then reset to 0. self.index = 0 self.range_map = {0: self}
def __init__(self, song: Song = None, out_file_path: Path = None, score_file_path: Path = None, orchestra_file_path: Path = None, csound_path: Path = None, verbose: bool = False): validate_types(('song', song, Song), ('out_file_path', out_file_path, Path), ('score_file_path', score_file_path, Path), ('orchestra_file_path', orchestra_file_path, Path), ('verbose', verbose, bool)) validate_optional_type('csound_path', csound_path, Path) super(CSoundWriter, self).__init__() self._song = song self.out_file_path = out_file_path self.score_file_path = score_file_path self.orchestra_file_path = orchestra_file_path self._score_file_lines = [] self.csound_path = csound_path or CSoundWriter.CSOUND_OSX_PATH self.verbose = verbose self._include_file_names = []
def set_notes_pitches_to_mingus_keys(mingus_keys: Sequence[str], mingus_key_to_key_enum_mapping: Mapping, notes: NoteSequence, pitch_for_key: Any, octave: int, validate=True): if validate: validate_sequence_of_type('mingus_key_list', mingus_keys, str) validate_types(('mingus_key_to_key_enum_mapping', mingus_key_to_key_enum_mapping, Dict), ('notes', notes, NoteSequence), ('octave', octave, int)) if len(mingus_keys) != len(notes): raise ValueError(( 'mingus_keys and notes must have same length. ' f'len(mingus_keys): {len(mingus_keys)} len(notes): {len(notes)}' )) for i, mingus_key in enumerate(mingus_keys): set_note_pitch_to_mingus_key(mingus_key, mingus_key_to_key_enum_mapping, notes[i], pitch_for_key, octave, validate=False)
def insert(self, index: int, to_add: NoteSequence) -> 'NoteSequenceSequence': validate_types(('index', index, int), ('to_add', to_add, NoteSequence)) self.note_seq_seq.insert(index, to_add) return self
def add_player_for_track(self, track_name: str = None, player: Player = None): """Adds a Player specific to a track, which overrides self.player if present""" validate_types(('track_name', track_name, str), ('player', player, Player)) self._track_name_player_map[track_name] = player
def set_tempo_for_track(self, track_name: str = None, tempo: int = None): validate_types(('track_name', track_name, str), ('tempo', tempo, int)) # Make a new meter object set to the new tempo and assign it to the sequencer track = self.track_list[self._track_name_idx_map[track_name]] track.tempo = tempo