def __init__(self, beats_per_measure: int = None, beat_note_dur: NoteDur = None, tempo: int = None, quantizing: bool = True): validate_optional_types(('beats_per_measure', beats_per_measure, int), ('beat_dur', beat_note_dur, NoteDur), ('tempo', tempo, int), ('quantizing', quantizing, bool)) self.quantizing = quantizing # Meter notation # Numerator of meter self.beats_per_measure = beats_per_measure # Inverse of denominator of meter, e.g. 4/4 is quarter note is 1 beat self.beat_note_dur = beat_note_dur # Meter in musical notation as a tuple, e.g. (4, 4) # noinspection PyTypeChecker self.meter_notation = (self.beats_per_measure, int(1 / self.beat_note_dur.value)) # Each note is some fraction of a quarter note. So for N / 4 meters, this ratio is 1. # For N / 8 meters, e.g. 6 / 8, this ration os 0.5. This ratio multiplied by the actual time duration # of a quarter note, derived from the tempo in qpm, is the duration of a note # noinspection PyTypeChecker self.quarter_notes_per_beat_note = int(self.beat_note_dur.value / Meter.QUARTER_NOTE_DUR) # Actual note duration # Map note durations from meter, which are unitless, to time, using tempo, which is a ratio of # quarter-note beats to time. qpm == quarter notes per minute self._set_tempo_attributes(tempo)
def __init__(self, instruments: Sequence[str] = None, sampling_rate: int = 44100, ksmps: int = 100, num_channels: int = 1, zed_dbfs: int = 1): validate_sequence_of_type('instruments', instruments, str) validate_optional_types( ('sampling_rate', sampling_rate, int), ('ksmps', ksmps, int), ('num_channels', num_channels, int), ('zed_dbfs', zed_dbfs, int)) # These keys should always be defined. Their values are sensible CSound defaults that can be set # to other values if you know what you are doing. self.global_vars = { # Output sampling rate 'sr': sampling_rate, # Ratio of output sampling rate to control rate (actual samples per control period) 'ksmps': ksmps, # Number output channels (mono, stereo, quadraphonic) 'nchnls': num_channels, # Value of 0 decibels, 1 means don't alert amp of output and is most compatible with plugins # Must be written as '0dbfs' in CSound output, but Py vars can't start with a number '0dbfs': zed_dbfs } self.instruments = instruments
def __init__(self, meter: Meter = None, swing: Swing = None, num_notes: int = None, mn: MakeNoteConfig = None, performance_attrs: PerformanceAttrs = None): validate_optional_types( ('meter', meter, Meter), ('swing', swing, Swing), ('performance_attrs', performance_attrs, PerformanceAttrs)) super(Measure, self).__init__(num_notes=num_notes, mn=mn) # TODO Enforce duration of meter bpm and tempo and add unit test coverage, currently the onus is on # the caller to put correct duration in note_config, as that is what is used to create notes, ignoring tempo # Maintain the invariant that notes are sorted ascending by start self._sort_notes_by_start_time() self.meter = meter or copy(Measure.DEFAULT_METER) self.swing = swing self.num_notes = num_notes or 0 self.performance_attrs = performance_attrs # Support adding notes based on Meter self.beat = 0 # Support adding notes offset from end of previous note self.next_note_start = 0.0 self.max_duration = self.meter.beats_per_measure * self.meter.beat_note_dur_secs
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 __init__(self, measure_list: List[Measure], meter: Optional[Meter] = None, swing: Optional[Swing] = None, name: str = None, performance_attrs: Optional[PerformanceAttrs] = None): validate_optional_types(('measure_list', measure_list, List), ('performance_attrs', performance_attrs, PerformanceAttrs), ('meter', meter, Meter), ('swing', swing, Swing), ('name', name, str)) validate_optional_sequence_of_type('measure_list', measure_list, Measure) measure_list = measure_list or [] super(Section, self).__init__(measure_list) # TODO REFACTOR NAME TO 'measures' self.measure_list = self.note_seq_seq self.name = name self._performance_attrs = performance_attrs self._meter = meter if meter: for measure in self.measure_list: measure.meter = meter self._swing = swing if swing: for measure in self.measure_list: measure.swing = swing self.index = 0 if self._performance_attrs: for measure in self.measure_list: measure.performance_attrs = self._performance_attrs
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)
def __init__(self, csound_orchestra: Optional[CSoundOrchestra] = None, csound_score: Optional[CSoundScore] = None, song: Optional[Song] = None): validate_optional_types( ('csound_orchestra', csound_orchestra, CSoundOrchestra), ('csound_score', csound_score, CSoundScore), ('song', song, Song)) super(CSoundCSDPlayer, self).__init__() self.orchestra = csound_orchestra if csound_orchestra and csound_score: self._csd = CSD(csound_orchestra, csound_score) self._song = song
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
def __init__(self, to_add: Optional[Union[List[Measure], Section]] = None, meter: Optional[Meter] = None, swing: Optional[Swing] = None, name: str = None, instrument: Optional[Union[float, int]] = None, performance_attrs: Optional[PerformanceAttrs] = None): validate_optional_types(('meter', meter, Meter), ('swing', swing, Swing), ('performance_attrs', performance_attrs, PerformanceAttrs)) validate_optional_type_choice('instrument', instrument, (float, int)) # Get the measure_list from either List[Measure] or Section self._section_map: Dict[str, Section] = {} measure_list = [] if to_add: try: validate_optional_sequence_of_type('to_add', to_add, Measure) measure_list = to_add except ValueError: pass if not measure_list: validate_optional_type('to_add', to_add, Section) measure_list = to_add.measure_list if to_add.name: self._section_map[to_add.name] = to_add super(Track, self).__init__(measure_list=measure_list, meter=meter, swing=swing, name=name, performance_attrs=performance_attrs) self.name = name self._instrument = instrument self.index = 0 # Set the instrument stored at the Track level. Also if an `instrument` was passed in, # modify all Measures, which will in turn modify all of their Notes if instrument: for measure in measure_list: measure.set_attr('instrument', instrument) self.instrument = instrument else: self.instrument = Track.DEFAULT_INSTRUMENT
def calculate_swing_adjust(self, swing_direction: SwingDirection = None, swing_jitter_type: SwingJitterType = None): validate_optional_types( ('swing_direction', swing_direction, Swing.SwingDirection), ('swing_jitter_type', swing_jitter_type, Swing.SwingJitterType)) swing_direction = swing_direction or self.swing_direction swing_jitter_type = swing_jitter_type or self.swing_jitter_type swing_adjust = self.swing_range if swing_jitter_type == Swing.SwingJitterType.Random: swing_adjust *= random() if swing_direction == Swing.SwingDirection.Forward: return swing_adjust elif swing_direction == Swing.SwingDirection.Reverse: return -swing_adjust elif swing_direction == Swing.SwingDirection.Both: return sign() * swing_adjust
def __init__(self, swing_on: bool = None, swing_range: float = None, swing_direction: SwingDirection = None, swing_jitter_type: SwingJitterType = None): validate_optional_types( ('swing_on', swing_on, bool), ('swing_range', swing_range, float), ('swing_direction', swing_direction, Swing.SwingDirection), ('swing_jitter_type', swing_jitter_type, Swing.SwingJitterType)) if swing_on is None: swing_on = Swing.DEFAULT_SWING_ON self.swing_on = swing_on if swing_range is None: self.swing_range = Swing.DEFAULT_SWING_RANGE else: self.swing_range = swing_range self.swing_direction = swing_direction or Swing.DEFAULT_SWING_DIRECTION self.swing_jitter_type = swing_jitter_type or Swing.DEFAULT_SWING_JITTER_TYPE
def __init__(self, to_add: Optional[Union[List[Track], Track]] = None, name: str = None, meter: Optional[Meter] = None, swing: Optional[Swing] = None, performance_attrs: Optional[PerformanceAttrs] = None): validate_optional_types( ('meter', meter, Meter), ('swing', swing, Swing), ('performance_attrs', performance_attrs, PerformanceAttrs)) self.name = name self.track_map = {} self.index = 0 track_list = [] if to_add: try: validate_optional_sequence_of_type('to_add', to_add, Track) track_list = to_add except ValueError: pass if not track_list: validate_optional_type('to_add', to_add, Track) track_list = [to_add] self.track_list = track_list for track in self.track_list: if track.name: self.track_map[track.name] = track self._meter = meter if meter: for track in self.track_list: track._meter = meter self._swing = swing if swing: for track in self.track_list: track._swing = swing self._performance_attrs = performance_attrs if performance_attrs: for track in self.track_list: track._performance_attrs = performance_attrs