Beispiel #1
0
def make_note(note_attr_vals: ndarray,
              attr_name_idx_map: Mapping[str, int],
              attr_val_cast_map: Mapping[str, Any] = None):
    validate_type('note_attr_vals', note_attr_vals, ndarray)
    validate_type('attr_name_idx_map', attr_name_idx_map, Mapping)
    validate_sequence_of_type('attr_name_idx_map', attr_name_idx_map.keys(),
                              str)
    validate_optional_type('attr_val_cast_map', attr_val_cast_map, Mapping)
    if attr_val_cast_map:
        validate_optional_sequence_of_type('attr_val_cast_map',
                                           attr_val_cast_map.keys(), str)

    cls = _make_cls(attr_name_idx_map)
    note = cls()

    # Assign core attributes
    note.note_attr_vals = note_attr_vals
    note.attr_name_idx_map = attr_name_idx_map

    # Set mapping of attribute names to functions that cast return type of get() calls, e.g. cast instrument to int
    note.attr_val_cast_map = attr_val_cast_map or {}
    for attr_name in note.attr_name_idx_map:
        if attr_name not in note.attr_val_cast_map:
            note.attr_val_cast_map[attr_name] = lambda x: x
    # These are always returned as an int
    note.attr_val_cast_map['instrument'] = int
    note.attr_val_cast_map['velocity'] = int
    note.attr_val_cast_map['amplitude'] = int
    note.attr_val_cast_map['pitch'] = int
    note.attr_val_cast_map['channel'] = int

    return note
Beispiel #2
0
 def __getitem__(self, index: int) -> Track:
     validate_type('index', index, int)
     if abs(index) >= len(self.track_list):
         raise IndexError(
             f'`index` out of range index: {index} len(track_list): {len(self.track_list)}'
         )
     return self.track_list[index]
Beispiel #3
0
    def insert(self, index: int, to_add: Any) -> 'NoteSequence':
        validate_type('index', index, int)

        new_notes = to_add.note_attr_vals
        if len(new_notes.shape) == 1:
            new_notes_num_attributes = new_notes.shape[0]
        else:
            new_notes_num_attributes = new_notes.shape[1]
        if len(self.note_attr_vals):
            num_attributes = self.note_attr_vals.shape[1]
        else:
            num_attributes = 0
        if num_attributes and num_attributes != new_notes_num_attributes:
            raise NoteSequenceInvalidAppendException(
                    'NoteSequence inserted into a NoteSequence must have the same number of attributes')

        if len(self.note_attr_vals):
            self.note_attr_vals = np_insert(self.note_attr_vals, index, new_notes, axis=0)
        else:
            # Must copy the list of the underlying note array to initialize storage for a NoteSequence
            # because NoteSequence arrays are 2D
            if len(new_notes.shape) == 1:
                new_notes = [new_notes]
            self.note_attr_vals = np_copy(new_notes)

        self.update_range_map()
        return self
Beispiel #4
0
 def append_child_sequence(self, child_sequence: 'NoteSequence') -> 'NoteSequence':
     if id(self) == id(child_sequence):
         raise ValueError('Cycle detected! Attempt to append a NoteSequence to itself as a child sequence.')
     validate_type('child_sequence', child_sequence, NoteSequence)
     self.child_sequences.append(child_sequence)
     self.update_range_map()
     return self
 def __getitem__(self, index: int) -> NoteSequence:
     validate_type('index', index, int)
     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)}'
         )
     return self.note_seq_seq[index]
Beispiel #6
0
 def _get_note_for_index(self, index: int) -> Any:
     """Factory method to construct a Note over a stored Note value at an index in the underlying array"""
     validate_type('index', index, int)
     if index >= len(self):
         raise IndexError(f'`index` out of range index: {index} max_index: {len(self)}')
     # Simple case, index is in the range of self.attrs
     if index < len(self.note_attr_vals):
         return self.mn.make_note(self.note_attr_vals[index],
                                  self.mn.attr_name_idx_map,
                                  attr_val_cast_map=self.mn.attr_val_cast_map)
     # Index is above the range of self.note_attr_vals, so either it is in the range of one of the recursive
     # flattened sequence of child_sequences, or it's invalid
     else:
         index_range_sum = len(self.note_attr_vals)
         for index_range in self.range_map.keys():
             # Dict keys are in order they were written, so ascending order, so each one is the max index
             # for that range. So the first entry it is less than is the entry it is in range of.
             if index < index_range:
                 # Get the note attrs for the note_sequence for the range this index is in
                 note_attrs = self.range_map[index_range]
                 # Adjust index to access the note_attr_vals with offset of 0. The index entry from range_map
                 # is the running sum of all the previous indexes so we need to subtract that from index
                 adjusted_index = index - index_range_sum
                 return self.mn.make_note(note_attrs[adjusted_index],
                                          self.mn.attr_name_idx_map,
                                          attr_val_cast_map=self.mn.attr_val_cast_map)
             index_range_sum += index_range
Beispiel #7
0
 def _setter(self, attr_val) -> None:
     if attr_name in self.attr_name_idx_map:
         validate_type('attr_name', attr_name, str)
         validate_type_choice('attr_val', attr_val, (float, int))
         self.note_attr_vals[self.attr_name_idx_map[attr_name]] = attr_val
     else:
         setattr(self, attr_name, attr_val)
Beispiel #8
0
def pitch_for_key(key: Union[MajorKey, MinorKey], octave: int) -> float:
    validate_type_choice('key', key, (MajorKey, MinorKey))
    validate_type('octave', octave, int)
    if not (MIN_OCTAVE < octave < MAX_OCTAVE):
        raise ValueError((f'Arg `octave` must be in range '
                          f'{MIN_OCTAVE} <= octave <= {MAX_OCTAVE}'))
    return PITCH_MAP[key] + (float(octave) - 1.0)
Beispiel #9
0
 def copy(source: 'NoteSequence') -> 'NoteSequence':
     validate_type('source', source, NoteSequence)
     copy = NoteSequence(num_notes=len(source),
                         child_sequences=source.child_sequences,
                         mn=source.mn)
     # Copy the underlying np array from source note sequence to target
     copy.note_attr_vals = np_copy(source.note_attr_vals)
     return copy
Beispiel #10
0
def transpose(self, interval: int):
    """Midi pitches are ints in the range MIN_PITCH..MAX_PITCH"""
    validate_type('interval', interval, int)
    new_pitch = self.pitch + interval
    if new_pitch < MIN_PITCH or new_pitch > MAX_PITCH:
        raise ValueError(
            f'Arg `interval` creates invalid pitch value: {new_pitch}')
    self.pitch = new_pitch
Beispiel #11
0
 def __init__(self,
              event_type: CSoundEventType = None,
              event_data: Sequence[Union[float, int]] = None):
     validate_type('event_type', event_type, CSoundEventType)
     validate_sequence_of_type_choice('event_data', event_data,
                                      (float, int))
     self.event_type = event_type
     self.event_data = event_data
Beispiel #12
0
 def __init__(self,
              append_mode: MidiPlayerAppendMode = None,
              port_name: Optional[str] = None):
     validate_type('append_mode', append_mode, MidiPlayerAppendMode)
     validate_optional_type('port_name', port_name, str)
     super(MidiInteractiveMultitrackPlayer, self).__init__()
     self.append_mode = append_mode
     self.midi_track_tick_relative = self.append_mode == MidiPlayerAppendMode.AppendAfterPreviousNote
     self.port_name = port_name or f'{self.__class__.__name__}_port'
Beispiel #13
0
 def tempo(self, tempo: int) -> None:
     """
     Sets the meter.tempo_qpm to the tempo value provided. Also recursively traverses down to every measure
     in every section in every track, rebuilding the measures notes to adjust their start times to use the
     new meter with the new tempo.
     """
     validate_type('tempo', tempo, int)
     self.meter.tempo = tempo
     for track in self.track_list:
         track.tempo = tempo
Beispiel #14
0
 def add_end_score_event(self, beats_to_wait: int = 0):
     validate_type('beats_to_wait', beats_to_wait, int)
     if beats_to_wait:
         self._cs.scoreEvent(
             CSoundScoreEvent.EVENT_TYPE_CODES[
                 CSoundEventType.EndScore.name], (0, beats_to_wait))
     else:
         self._cs.scoreEvent(
             CSoundScoreEvent.EVENT_TYPE_CODES[
                 CSoundEventType.EndScore.name], ())
Beispiel #15
0
 async def play_track(self, track_name: str = None):
     """Plays a track with a track-mapped Player, if present, otherwise plays using self.Player"""
     validate_type('track_name', track_name, str)
     track = self._track_name_idx_map[track_name]
     # Create an anonymous song with just this track to pass to the Player, since Players play Songs
     song = Song(to_add=track, meter=self.meter, swing=self.swing)
     player = self._track_name_player_map.get(track_name) or self.player
     if not player:
         raise InvalidPlayerException(f'No track player or self.player found to play track {track_name}')
     player.song = song
     await player.play()
Beispiel #16
0
 def add_attr(self,
              attr_name: str = None,
              val: Any = None,
              attr_type: Any = None):
     validate_type('attr_name', attr_name, str)
     validate_not_none('attr_type', attr_type)
     if self.frozen:
         raise PerformanceAttrsFrozenException(
             (f'Attempt to set attribute: {attr_name} '
              f'on frozen PerformanceConfigFactory: {self.name}'))
     self.attr_type_map[attr_name] = attr_type
     setattr(self, attr_name, val)
Beispiel #17
0
 def _set_csd_for_track(self,
                        track: Track,
                        score_header_lines: Optional[Sequence[str]] = None):
     validate_type('track', track, Track)
     validate_optional_sequence_of_type('score_header_lines',
                                        score_header_lines, str)
     note_lines = []
     for measure in track.measure_list:
         for note in measure:
             note_lines.append(f'{str (note)}')
     score = CSoundScore(header_lines=score_header_lines or [''],
                         note_lines=note_lines)
     self._csd = CSD(self.orchestra, score)
Beispiel #18
0
 def __init__(self,
              song: Optional[Song] = None,
              append_mode: MidiPlayerAppendMode = None,
              midi_file_path: Path = None):
     validate_type('append_mode', append_mode, MidiPlayerAppendMode)
     validate_optional_types(('song', song, Song),
                             ('midi_file_path', midi_file_path, Path))
     self._song = song
     self.midi_file_path = midi_file_path
     # Type 1 - multiple synchronous tracks, all starting at the same time
     # https://mido.readthedocs.io/en/latest/midi_files.html
     self.midi_file = MidiFile(type=1)
     super(MidiWriter, self).__init__(song=song)
Beispiel #19
0
 def extend(self, note_sequence: 'NoteSequence') -> 'NoteSequence':
     validate_type('note_sequence', note_sequence, NoteSequence)
     if len(self.note_attr_vals) and self.note_attr_vals[0].shape != note_sequence.note_attr_vals[0].shape:
         raise NoteSequenceInvalidAppendException(
             'NoteSequence extended to a NoteSequence must have the same number of attributes')
     # Either this is the first note in the sequence, or it's not
     # If it is, make this sequence the note_attr_vals of this sequence. If it is not, append these notes
     # to the existing sequence -- we have already confirmed the shapes conform if existing sequence is not empty.
     if len(self.note_attr_vals):
         self.note_attr_vals = np_concatenate((self.note_attr_vals, note_sequence.note_attr_vals))
     else:
         self.note_attr_vals = np_copy(note_sequence.note_attr_vals)
     self.update_range_map()
     return self
Beispiel #20
0
 def apply_swing(self,
                 note_sequence: NoteSequence,
                 swing_direction: SwingDirection = None,
                 swing_jitter_type: SwingJitterType = None):
     """Applies swing to all notes in note_sequence, using current object settings, unless swing_direction
        is provided. In that case the swing_direction arg overrides self.swing_direction and is applied.
     """
     validate_type('note_sequence', note_sequence, NoteSequence)
     if self.swing_on:
         for note in note_sequence:
             note.start += self.calculate_swing_adjust(
                 swing_direction, swing_jitter_type)
             if note.start < 0.0:
                 note.start = 0.0
Beispiel #21
0
    def add_track(self,
                  track_name: str = None,
                  instrument: Union[float, int] = None) -> Track:
        validate_type('track_name', track_name, str)
        validate_type_choice('instrument', instrument, (float, int))

        track_name = track_name or str(self._next_track)
        track = Track(meter=self.meter,
                      swing=self.swing, name=track_name,
                      instrument=instrument)
        self.append(track)
        self.num_tracks += 1
        self._track_name_idx_map[track_name] = self._next_track
        self._next_track += 1
        return track
Beispiel #22
0
    def add_pattern_as_new_track(self,
                                 track_name: Optional[str] = None,
                                 pattern: str = None,
                                 instrument: Union[float, int] = None,
                                 swing: Optional[Swing] = None,
                                 track_type: Optional[Any] = Track,
                                 arpeggiate: bool = False,
                                 arpeggiator_chord: Optional[HarmonicChord] = None) -> Track:
        """
        - Sets the pattern, a section of measures in a new track named `track_name` or if no name is provided
          in a track with a default name of its track number.
        - Track is bound to `instrument`
        - If `track_name` is not supplied, a default name of `Track N`, where N is the index of the track, is assigned
        - If `swing` is arg is supplied and `is_swing_on()` is `True`, then the track will have swing applied using it
        - If `self.swing` `is_swing_on()` is `True` then the track will have swing applied using it
        - If `track_type` is provided, this method constructs a subclass of Track. This allows callers to
          construct for example a MidiTrack, which derives from Track and adds attributes specific to MIDI.
        """
        validate_type('pattern', pattern, str)
        validate_optional_types(('track_name', track_name, str), ('swing', swing, Swing),
                                ('arpeggiate', arpeggiate, bool),
                                ('arpeggiator_chord', arpeggiator_chord, HarmonicChord))
        validate_type_choice('instrument', instrument, (float, int))
        validate_type_reference('track_type', track_type, Track)

        # Set the measures in the section to add to the track
        section = self._parse_pattern_to_section(pattern=pattern, instrument=instrument,
                                                 arpeggiate=arpeggiate, arpeggiator_chord=arpeggiator_chord)
        # If the section is shorter than num_measures, the length of all tracks, repeat it to fill the track
        if len(section) < self.num_measures:
            self._fill_section_to_track_length(section)

        # Create the track, add the section to it, apply quantize and swing according to the logic in the docstring
        track_name = track_name or str(self._next_track)
        self._track_name_idx_map[track_name] = self._next_track
        swing = swing or self.swing
        track = track_type(name=track_name, instrument=instrument, meter=self.meter, swing=swing)
        track.extend(to_add=section)
        if swing and swing.is_swing_on():
            track.apply_swing()

        # Add the track to the sequencer, update bookkeeping
        self.append(track)
        self.num_tracks += 1
        self._next_track += 1

        return track
Beispiel #23
0
    def add_note_on_beat(self, note: Any, increment_beat=False) -> 'Measure':
        """Modifies the note_sequence in place by setting its start_time to the value of measure.beat.
        If increment_beat == True the measure_beat is also incremented, after the insertion. So this method
        is a convenience method for inserting multiple notes in sequence on the beat.
        """
        validate_type('increment_beat', increment_beat, bool)
        if len(self) + 1 > self.meter.beats_per_measure:
            raise ValueError(
                f'Attempt to add a note to a measure greater than the the number of beats per measure'
            )

        note.start = self.meter.beat_start_times_secs[self.beat]
        self.append(note)
        # Increment beat position if flag set and beat is not on last beat of the measure already
        if increment_beat:
            self.increment_beat()

        return self
Beispiel #24
0
    def __init__(self,
                 csound_orchestra: CSoundOrchestra = None,
                 song: Optional[Song] = None):
        validate_type('csound_orchestra', csound_orchestra, CSoundOrchestra)
        validate_optional_type('song', song, Song)

        super(CSoundInteractivePlayer, self).__init__()
        self._orchestra = csound_orchestra
        self._song = song
        self._cs = ctcsound.Csound()
        self._cs.setOption('-d')
        self._cs.setOption('-odac')
        self._cs.setOption('-m0')
        if self._cs.compileOrc(str(
                self._orchestra)) != ctcsound.CSOUND_SUCCESS:
            raise InvalidOrchestraError(
                'ctcsound.compileOrc() failed for {}'.format(self._orchestra))
        self._played = False
Beispiel #25
0
    def quantize_to_beat(self, note_sequence: NoteSequence):
        # sourcery skip: assign-if-exp
        """Adjusts each note start_time to the closest beat time, so that each note will start on a beat.
        """
        validate_type('note_sequence', note_sequence, NoteSequence)

        if self.quantizing:
            # First quantize() to make sure notes in NoteSequence are scaled to duration of Measure
            self.quantize(note_sequence)
            # Then adjust note start times to closest beat
            # Algorithm:
            #  - self.beat_start_times is sorted, so use binary search strategy to find closest note in O(logN) time
            #  - call bisect to find insertion point for note in the sequence of start times, the cases are:
            #  -   insertion point i == 0, we are done, the note quantizes to the first beat
            #  -   insertion point i > 0, then note.start >= beat_start_times[i - 1] <= note.start < beat_start_times[i]
            #  -     in this case test distance of each beat_start_time to note.start and pick the closest one

            # Append measure end time to beat_start_times as a sentinel value for bisect()
            beat_start_times = self.beat_start_times_secs + [
                self.measure_dur_secs
            ]
            for note in note_sequence:
                i = bisect_left(beat_start_times, note.start)
                # Note maps to 0th beat
                if i == 0:
                    note.start = 0.0
                    continue
                # Note starts after last beat, so maps to last beat
                elif i == len(beat_start_times):
                    note.start = self.beat_start_times_secs[-1]
                    continue
                # Else note.start is between two beats in the range 1..len(beat_start_times) - 1
                # The note is either closest to beat_start_times[i - 1] or beat_start_times[i]
                prev_start = beat_start_times[i - 1]
                next_start = beat_start_times[i]
                prev_gap = note.start - prev_start
                next_gap = next_start - note.start
                if prev_gap <= next_gap:
                    note.start = prev_start
                else:
                    note.start = next_start
Beispiel #26
0
def make_note(note_attr_vals: np.array,
              attr_name_idx_map: Mapping[str, int],
              attr_val_cast_map: Mapping[str, Any] = None) -> Any:
    # TODO THIS VALIDATION BREAKS BECAUSE numpy.array IS A FUNCTION NOT A TYPE
    # validate_type('note_attr_vals', note_attr_vals, np.array)
    validate_type('attr_name_idx_map', attr_name_idx_map, Mapping)
    validate_sequence_of_type('attr_name_idx_map', attr_name_idx_map.keys(),
                              str)
    validate_optional_type('attr_val_cast_map', attr_val_cast_map, Mapping)
    if attr_val_cast_map:
        validate_optional_sequence_of_type('attr_val_cast_map',
                                           attr_val_cast_map.keys(), str)

    cls = _make_cls(attr_name_idx_map)
    note = cls()

    # Assign core attributes
    note.note_attr_vals = note_attr_vals
    note.attr_name_idx_map = attr_name_idx_map

    # Set string formatters for note attributes, this is specific to CSound per the comments
    note.set_attr_str_formatter('instrument', lambda x: str(x))
    note.set_attr_str_formatter('start', lambda x: f'{x:.5f}')
    note.set_attr_str_formatter('duration', lambda x: f'{x:.5f}')
    note.set_attr_str_formatter('amplitude', lambda x: str(x))
    # Handle case that pitch is a float and will have rounding but that sometimes we want
    # to use it to represent fixed pitches in Western scale, e.g. 4.01 == Middle C, and other times
    # we want to use to represent arbitrary floats in Hz. The former case requires .2f precision,
    # and for the latter case we default to .5f precision but allow any precision.
    # This is DEFAULT_PITCH_PRECISION to start with. User can call setter to update the value.
    note.set_attr_str_formatter('pitch', pitch_to_str(note.pitch_precision))

    # Set mapping of attribute names to functions that cast return type of get() calls, e.g. cast instrument to int
    note.attr_val_cast_map = attr_val_cast_map or {}
    for attr_name in note.attr_name_idx_map:
        if attr_name not in note.attr_val_cast_map:
            note.attr_val_cast_map[attr_name] = lambda x: x
    # Instrument is always returned as an int
    note.attr_val_cast_map['instrument'] = int

    return note
Beispiel #27
0
    def insert(self, index: int, to_add: Union[List[Track], Track]) -> 'Song':
        validate_type('index', index, int)

        try:
            validate_type('to_add', to_add, Track)
            self.track_list.insert(index, to_add)
            if to_add.name:
                self.track_map[to_add.name] = to_add
            return self
        except ValueError:
            pass

        validate_sequence_of_type('to_add', to_add, Track)
        for track in to_add:
            self.track_list.insert(index, track)
            # noinspection PyUnresolvedReferences
            if track.name:
                # noinspection PyUnresolvedReferences
                self.track_map[track.name] = track
            index += 1
        return self
Beispiel #28
0
def transpose(self, interval: int):
    """NOTE: This is only valid to call with pitches in the CSound octave.western_scale style, e.g. 4.01 for C4.

    Algorithm:
    There are 11 notes in each octave, so project each note in octave.pitch notation into 0-based vector space with
    11 slots per octave. e.g. C4 == 4.01 = 44.

    The formula to convert a note into this space is:
      (octave * 11) + (pitch - 1), e.g. 4.01 = (4 * 11) + (1 - 1) == 44
    The formula to convert a note from this space back SCALE PITCH PRECISION is the complement:
       (value % 11) + (remainder + 1)

    Examples:
        5.01 + interval 1 = 55 + 1 = 56, converted 56 % 11 + 1 + 1 = 5.02
        5.01 + interval 11 = 54 + 11 = 65, converted 65 % 11 + 10 + 1 = 5.11
        5.01 + interval 12 == 54 + 12 = 66, converted 66 % 11 + 0 + 1 = 6.01
        5.10 + interval 23 == 54 + 23 = 77, converted 77 % 11 + 0 + 1 = 7.01
        5.01 - interval 1 = 55 - 1 - 54, converted 54 % 11 + 10 + 1 = 4.11
        5.01 - interval 11 = 55 - 11 = 44, converted 44 % 11 + 0 + 1 = 4.01
        5.01 - interval 12 = 55 - 12 = 43, converted 43 % 11 + 10 + 1 = 3.11
        5.01 - interval 23 = 55 - 23 = 32, converted 32 % 11 + 10 + 1 = 2.11
    """
    if self.pitch_precision != SCALE_PITCH_PRECISION:
        raise CSoundInvalidTransposeError((
            'CSound pitch_precision must be SCALE_PITCH_PRECISION, '
            'which is `octave.pitch` notation like 4.01 for C4, to transpose'))
    validate_type('interval', interval, int)

    # Get current pitch as an integer in the range 1..11, == 1..NUM_NOTES_IN_OCTAVE
    cur_octave, cur_pitch = str(round(self.pitch, 2)).split('.')
    cur_octave = int(cur_octave)
    cur_pitch = int(cur_pitch)
    # -1 to adjust for 1-based values in CSound scale notation
    int_scale_pitch = (cur_octave * NUM_NOTES_IN_OCTAVE) + (cur_pitch - 1)
    int_scale_pitch += interval
    new_octave, new_pitch = divmod(int_scale_pitch, NUM_NOTES_IN_OCTAVE)
    # +1 to adjust for 1-based values in CSound scale notation
    new_pitch += 1
    self.pitch = round(new_octave + (new_pitch / 100.0), 2)
Beispiel #29
0
def pitch_for_key(key: Union[MajorKey, MinorKey], octave: int) -> int:
    """MIDI pitches sequence from 21 A0 to 127, the 3 highest notes below C1 to the last note of C7.
       The algorithm is that we store the values for C1-12 as ints in the PITCH_MAP
       and thus increment by + 12 for every octave > 1, handle the special case for the 3 notes < C1 and
       validate that the (key, octave) combination is a valid MIDI pitch.
    """
    validate_type_choice('key', key, (MajorKey, MinorKey))
    validate_type('octave', octave, int)
    if not (MIN_OCTAVE < octave < MAX_OCTAVE):
        raise ValueError(
            f'Arg `octave` must be in range {MIN_OCTAVE} <= octave <= {MAX_OCTAVE}'
        )

    if octave == MIN_OCTAVE:
        # Handle edge case of only 3 notes being valid when `octave` == 0
        if key not in KEYS_IN_MIN_OCTAVE:
            raise ValueError(('If arg `octave` == 0 then `key` must be in '
                              f'{KEYS_IN_MIN_OCTAVE}'))
        return PITCH_MAP[key] - NUM_INTERVALS_IN_OCTAVE
    else:
        interval_offset = (octave - 1) * NUM_INTERVALS_IN_OCTAVE
        return PITCH_MAP[key] + interval_offset
Beispiel #30
0
    def insert(self, index: int, to_add: Union[Measure, Section]) -> 'Track':
        validate_type('index', index, int)

        try:
            validate_type('to_add', to_add, Measure)
            self.measure_list.insert(index, to_add)
            return self
        except ValueError:
            pass

        validate_type('to_add', to_add, Section)
        for measure in to_add.measure_list:
            self.measure_list.insert(index, measure)
            index += 1
        self._section_map[to_add.name] = to_add
        return self