def __init__(self, music_player, wavetype=LEAD, volume=.7, reverb_mix=.1, notes=None): """ Create new WaveInstrument Arguments music_player: Parent MusicPlayer of this instrument """ Instrument.__init__(self, music_player, volume, reverb_mix, notes) self.names = ["C", "B", "A", "G", "F", "E", "D", "C"] self.wavetype = wavetype if (self.wavetype == self.BASS): table = SquareTable() self.frequencies = [(freq / 4) for freq in self.C_FREQUENCIES] elif (self.wavetype == self.LEAD): table = CosTable() self.frequencies = self.C_FREQUENCIES self.generator_cutoff = True # Generate oscillators for every pitch, feed into mixer self.oscillators = [] for i in range(0, music_player.NUM_ROWS): oscillator = Osc(table=table, freq=self.frequencies[i]) oscillator.stop() self.row_generators.append(oscillator) self.mixer.addInput(i, oscillator) self.mixer.setAmp(i, 0, (1.0 / self.LIMITING_FACTOR)) pre_effect = self.mixer[0] if (self.wavetype == self.BASS): distorted = Disto(pre_effect) volume_reducer = Mixer(outs=1) volume_reducer.addInput(0, distorted) volume_reducer.setAmp(0, 0, 0.3) post_effect = volume_reducer[0] elif (self.wavetype == self.LEAD): #chorus = Chorus(pre_effect) #post_effect = chorus post_effect = pre_effect else: post_effect = pre_effect # Apply reverb to omixer self.reverb_out = WGVerb(post_effect, feedback=0.8, cutoff=3500, bal=self.reverb_mix) #use generator.setBal(x) to modify reverb self.generator = self.reverb_out
def mixer_setup(self): # Combine all tracks in mixer self.track_mixer = Mixer(outs=1) for inst_index in range(0, len(self.instruments)): instrument = self.instruments[inst_index] generator = instrument.get_generator() self.track_mixer.addInput(inst_index, generator) self.track_mixer.setAmp(inst_index, 0, instrument.get_volume()) # Prepare master output self.master_out = Mixer(outs=1, chnls=2) self.master_out.addInput(0, self.track_mixer[0]) self.master_out.setAmp(0, 0, self.global_volume) self.master_out.out()
def __init__(self, music_player, volume, reverb_mix, notes): """ Create generic Instrument Arguments: music_player: Parent MusicPlayer of this instrument """ self.music_player = music_player self.reverb_mix = reverb_mix # This field is just for storage. Volume is taken care of in # the MusicPlayer's mixer self.volume = volume # Initialize notes array to all zeroes if no notes input as argument if notes == None: self.notes = [] for beat_index in range(0, music_player.NUM_BEATS): beat_column = [] for row_index in range(0, music_player.NUM_ROWS): beat_column.append(0) self.notes.append(beat_column) else: self.notes = notes # For generating sound self.row_generators = [] self.reverb_out = None self.mixer = Mixer(outs=1) # For GUI purposes, override this self.names = []
def add_modules_to_mixer(self, mixer: pyo.Mixer) -> None: for module in self.modules: for ( physical_output, mixer_channel, ) in settings.MODULE_MIXER2CHANNEL_MAPPING.items(): track_mixer_number = ( settings.TRACK2MIXER_NUMBER_MAPPING["{}_{}".format( module.name, physical_output.split("_")[1])], ) mixer.addInput( track_mixer_number, module.mixer[mixer_channel][0], ) mixer.setAmp( track_mixer_number, settings.PHYSICAL_OUTPUT2CHANNEL_MAPPING[physical_output], 1, )
class Instrument: """This class holds the sound generator and playback parameters for a track""" SECONDS_PER_MIN = 60 def __init__(self, music_player, volume, reverb_mix, notes): """ Create generic Instrument Arguments: music_player: Parent MusicPlayer of this instrument """ self.music_player = music_player self.reverb_mix = reverb_mix # This field is just for storage. Volume is taken care of in # the MusicPlayer's mixer self.volume = volume # Initialize notes array to all zeroes if no notes input as argument if notes == None: self.notes = [] for beat_index in range(0, music_player.NUM_BEATS): beat_column = [] for row_index in range(0, music_player.NUM_ROWS): beat_column.append(0) self.notes.append(beat_column) else: self.notes = notes # For generating sound self.row_generators = [] self.reverb_out = None self.mixer = Mixer(outs=1) # For GUI purposes, override this self.names = [] def __del__(self): pass def get_generator(self): return self.generator def get_names(self): return self.names def pause(self): self.mixer.stop() def set_note(self, note): beat_index = note.page_index * self.music_player.NUM_COLS +\ note.column beat_col = self.notes[beat_index] if note.turn_on == True: beat_col[note.row] = 1 elif note.turn_on == False: beat_col[note.row] = 0 def get_volume(self): return self.volume def set_volume(self, new_volume): self.volume = new_volume def get_reverb(self): return self.reverb_mix def set_reverb(self, new_reverb): self.reverb_mix = new_reverb self.reverb_out.setBal(new_reverb) def get_page(self, page_index): """ Get a page of notes with <row, col> indexing """ start_column = page_index * self.music_player.NUM_COLS end_column = start_column + (self.music_player.NUM_COLS - 1) page_notes = [] for row_index in range(0, self.music_player.NUM_ROWS): row_notes = [] for col_index in range(start_column, end_column + 1): row_notes.append(self.notes[col_index][row_index]) page_notes.append(row_notes) return page_notes """Abstract methods: override these for your instrument""" def play_step(self): pass def get_data(self): pass
class MusicPlayer: """Playback engine for sequencer samples and sounds""" NUM_PAGES = 8 NUM_ROWS = 8 NUM_COLS = 8 NUM_TRACKS = 3 NUM_BEATS = NUM_PAGES * NUM_COLS SECONDS_PER_MIN = 60.0 # Parameters for GUI to build sliders MIN_TEMPO = 40.0 MAX_TEMPO = 240.0 MIN_VOLUME = 0.0 MAX_VOLUME = 1.0 MIN_REVERB = 0.0 MAX_REVERB = 1.0 # Instrument descriptive constants WAVETABLE_A = 0 WAVETABLE_B = 1 DRUM_KIT = 2 def __init__(self): """Constructor for music_player""" """Make sure to call add_gui once initialized""" self.instruments = [] #instrument/track volume is here self.tempo = 120.0 #BPM (for now) self.global_volume = 0.75 #between 0 and 1 self.page_index = 0 #1st page self.play_all = False self.playhead_index = 0 self.beat_index = 0 self.server = Server(duplex=0) """Set proper output device for latency-free playback on Windows""" """Source: https://groups.google.com/d/msg/pyo-discuss/9fvFiGbch3c/tzJTfbpLUY8J""" if platform.system() == "Windows": out_devices = pa_get_output_devices() od_index = 0 for od in out_devices[0]: if "Primary Sound Driver" in od: pai = int(out_devices[1][od_index]) self.server.setOutputDevice(pai) break od_index += 1 self.server.boot() self.server.start() metronome_time = self.SECONDS_PER_MIN / self.tempo self.metronome = Metro(time=metronome_time) self.metronome_callback = TrigFunc(self.metronome, function=self.step) # Create instruments wavetable_a = WaveInstrument(self, WaveInstrument.BASS) wavetable_b = WaveInstrument(self, WaveInstrument.LEAD) drums = DrumInstrument(self) self.instruments.append(wavetable_a) self.instruments.append(wavetable_b) self.instruments.append(drums) self.mixer_setup() def mixer_setup(self): # Combine all tracks in mixer self.track_mixer = Mixer(outs=1) for inst_index in range(0, len(self.instruments)): instrument = self.instruments[inst_index] generator = instrument.get_generator() self.track_mixer.addInput(inst_index, generator) self.track_mixer.setAmp(inst_index, 0, instrument.get_volume()) # Prepare master output self.master_out = Mixer(outs=1, chnls=2) self.master_out.addInput(0, self.track_mixer[0]) self.master_out.setAmp(0, 0, self.global_volume) self.master_out.out() def add_gui(self, gui): """ Sets the GUI that this music player must instruct to update playhead. Must be called right after constructor before operation. Arguments: gui: GUI object monitoring this player """ self.gui = gui def terminate(self): """Terminate MusicPlayer server in preparation for shutdown""" self.server.stop() self.server.shutdown() def step(self): """ Step the music player through next beat """ # Set GUI to reflect current beat self.gui.update_playhead() # Play step for instruments for instrument in self.instruments: instrument.play_step() # For next iteration, increment playhead and beat indices self.playhead_index = (self.playhead_index + 1) % self.NUM_COLS if (self.play_all == True): self.beat_index = (self.beat_index + 1) % self.NUM_BEATS elif (self.play_all == False): self.beat_index = (self.page_index * self.NUM_COLS) +\ self.playhead_index def add_network_handler(self, network_handler): self.network_handler = network_handler """playback methods""" def play(self): self.metronome.play() def pause(self): for instrument in self.instruments: instrument.pause() self.metronome.stop() def set_session(self, session): """used to load a session into the music player""" # Reload pertinent MusicPlayer variables self.set_tempo(session.tempo) # Reconstruct each instrument from session data self.instruments = [] instrument_data = session.instrument_data for data in instrument_data: volume = data.volume reverb_mix = data.reverb_mix notes = data.notes if isinstance(data, WaveInstrumentData): wavetype = data.wavetype wave_instrument = WaveInstrument(self, wavetype, volume, reverb_mix, notes) self.instruments.append(wave_instrument) elif isinstance(data, DrumInstrumentData): drum_instrument = DrumInstrument(self, volume, reverb_mix, notes) self.instruments.append(drum_instrument) # Reload mixer to reflect new instruments self.mixer_setup() """ Modifiers """ def set_note(self, note): instrument = self.instruments[note.track_id] instrument.set_note(note) def set_global_volume(self, volume): self.global_volume = volume self.master_out.setAmp(0, 0, volume) def set_volume(self, track_id, volume): self.instruments[track_id].set_volume(volume) self.track_mixer.setAmp(track_id, 0, volume) def set_reverb(self, track_id, reverb): self.instruments[track_id].set_reverb(reverb) def set_tempo(self, new_tempo): new_time = self.SECONDS_PER_MIN / new_tempo self.metronome.setTime(new_time) self.tempo = new_tempo """getter methods""" def get_session(self): """Get descriptive MusicPlayer session to restore later""" session = Session(self, self.instruments) return session """getter methods for GUI""" def get_names(self, track_id): return self.instruments[track_id].get_names() def get_reverb(self, track_id): return self.instruments[track_id].get_reverb() def get_volume(self, track_id): return self.instruments[track_id].get_volume() def get_global_volume(self): return self.global_volume def get_tempo(self): return self.tempo def get_note(self, track_id, page_index, position, pitch): pass def get_current_page(self, track_id): instrument = self.instruments[track_id] notes = instrument.get_page(self.page_index) return notes