class SimplePianoActivity(activity.Activity): """SimplePianoActivity class as specified in activity.info""" def __init__(self, handle): activity.Activity.__init__(self, handle) GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.close) Gst.init(None) self._what_list = [] self.play_recording_thread = None self.playing_recording = False self.firstTime = False self.playing = False self.regularity = 0.7 self._drums_store = [] self.recording = False self.recorded_keys = [] self.is_valid_recording = False # we do not have collaboration features # make the share option insensitive self.max_participants = 1 self.csnd = new_csound_client() self.rythmInstrument = 'drum1kick' # toolbar with the new toolbar redesign toolbar_box = ToolbarBox() activity_button = ActivityToolbarButton(self) toolbar_box.toolbar.insert(activity_button, 0) toolbar_box.toolbar.set_style(Gtk.ToolbarStyle.BOTH_HORIZ) self.play_index = 0 self.play_recording_button = ToolButton( icon_name='media-playback-start') self.play_recording_button.set_property('can-default', True) self.play_recording_button.show() self.record_button = ToggleToolButton(icon_name='media-record') self.record_button.set_property('can-default', True) self.record_button.show() self.play_recording_button.set_sensitive(False) self.record_button.connect('clicked', self.__record_button_click_cb) self.play_recording_button.connect('clicked', self.handlePlayRecordingButton) toolbar_box.toolbar.set_style(Gtk.ToolbarStyle.BOTH_HORIZ) # TODO: disabe until is implemented with csnd6 # self.createPercussionToolbar(toolbar_box) toolbar_box.toolbar.insert(Gtk.SeparatorToolItem(), -1) keybord_labels = RadioToolButton() keybord_labels.props.icon_name = 'q_key' keybord_labels.props.group = keybord_labels keybord_labels.connect('clicked', self.set_keyboard_labels_cb) toolbar_box.toolbar.insert(keybord_labels, -1) notes_labels = RadioToolButton() notes_labels.props.icon_name = 'do_key' notes_labels.props.group = keybord_labels notes_labels.connect('clicked', self.set_notes_labels_cb) toolbar_box.toolbar.insert(notes_labels, -1) ti_notes_labels = RadioToolButton() ti_notes_labels.props.icon_name = 'ti_key' ti_notes_labels.props.group = keybord_labels ti_notes_labels.connect('clicked', self.set_ti_notes_labels_cb) toolbar_box.toolbar.insert(ti_notes_labels, -1) german_labels = RadioToolButton() german_labels.props.icon_name = 'c_key' german_labels.props.group = keybord_labels german_labels.connect('clicked', self.set_german_labels_cb) toolbar_box.toolbar.insert(german_labels, -1) no_labels = RadioToolButton() no_labels.props.icon_name = 'edit-clear' no_labels.props.group = keybord_labels no_labels.connect('clicked', self.set_keyboard_no_labels_cb) toolbar_box.toolbar.insert(no_labels, -1) self._what_widget = Gtk.ToolItem() self._what_search_button = FilterToolItem(_('Select Instrument'), 'view-type', _('Piano'), self._what_widget) self._what_widget.show() toolbar_box.toolbar.insert(Gtk.SeparatorToolItem(), -1) toolbar_box.toolbar.insert(self._what_search_button, -1) self._what_search_button.show() self._what_search_button.set_is_important(True) self._what_widget_contents = None self._what_drum_widget_contents = None separator = Gtk.SeparatorToolItem() toolbar_box.toolbar.insert(separator, -1) toolbar_box.toolbar.insert(self.record_button, -1) toolbar_box.toolbar.insert(self.play_recording_button, -1) separator = Gtk.SeparatorToolItem() separator.props.draw = False separator.set_expand(True) toolbar_box.toolbar.insert(separator, -1) stop_button = StopButton(self) toolbar_box.toolbar.insert(stop_button, -1) stop_button.show() self._save_as_audio_bt = ToolButton(icon_name='save-as-audio') self._save_as_audio_bt.props.tooltip = _('Save as audio') self._save_as_audio_bt.connect('clicked', self._save_ogg_cb) self._save_as_audio_bt.show() self._save_as_audio_bt.set_sensitive(False) activity_button.page.insert(self._save_as_audio_bt, -1) self.set_toolbar_box(toolbar_box) toolbar_box.show_all() self.keyboard_letters = ['ZSXDCVGBHNJM', 'Q2W3ER5T6Y7U', 'I'] notes = [ 'DO', ['DO#', 'REb'], 'RE', ['RE#', 'MIb'], 'MI', 'FA', ['FA#', 'SOLb'], 'SOL', ['SOL#', 'LAb'], 'LA', ['LA#', 'SIb'], 'SI' ] self.notes_labels = [notes, notes, ['DO']] # some countries use TI instead of SI ti_notes = [ 'DO', ['DO#', 'REb'], 'RE', ['RE#', 'MIb'], 'MI', 'FA', ['FA#', 'SOLb'], 'SOL', ['SOL#', 'LAb'], 'LA', ['LA#', 'TIb'], 'TI' ] self.ti_notes_labels = [ti_notes, ti_notes, ['DO']] german_notes = [ 'C', ['C#', 'Db'], 'D', ['D#', 'Eb'], 'E', 'F', ['F#', 'Gb'], 'G', ['G#', 'Ab'], 'A', ['A#', 'Bb'], 'B' ] self.german_labels = [german_notes, german_notes, ['C']] self.piano = PianoKeyboard(octaves=2, add_c=True, labels=self.keyboard_letters) # init csound self.instrumentDB = InstrumentDB.getRef() self.timeout_ms = 50 self.instVolume = 50 self.drumVolume = 0.5 self.instrument = 'piano' self.beat = 4 self.reverb = 0.1 self.tempo = PLAYER_TEMPO self.beatDuration = 60.0 / self.tempo self.ticksPerSecond = Config.TICKS_PER_BEAT * self.tempo / 60.0 self.sequencer = MiniSequencer(self.recordStateButton, self.recordOverSensitivity) self.loop = Loop(self.beat, math.sqrt(self.instVolume * 0.01)) self.drumFillin = Fillin(self.beat, self.tempo, self.rythmInstrument, self.reverb, self.drumVolume) self.muteInst = False self.csnd.setTempo(self.tempo) self.noteList = [] for i in range(21): self.csnd.setTrackVolume(100, i) # TODO commented because apparently are not used in the activity # for i in range(10): # self.csnd.load_instrument('guidice' + str(i + 1)) self.volume = 100 self.csnd.setMasterVolume(self.volume) self.enableKeyboard() self.setInstrument(self.instrument) self.connect('key-press-event', self.onKeyPress) self.connect('key-release-event', self.onKeyRelease) self.piano.connect('key_pressed', self.__key_pressed_cb) self.piano.connect('key_released', self.__key_released_cb) vbox = Gtk.VBox() vbox.set_homogeneous(False) self.load_instruments() self._event_box = Gtk.EventBox() self._event_box.modify_bg(Gtk.StateType.NORMAL, style.COLOR_WHITE.get_gdk_color()) vbox.pack_start(self._event_box, False, False, 0) vbox.pack_end(self.piano, True, True, 0) vbox.show_all() self.set_canvas(vbox) piano_height = Gdk.Screen.width() / 2 self._event_box.set_size_request( -1, Gdk.Screen.height() - piano_height - style.GRID_CELL_SIZE) self.connect('size-allocate', self.__allocate_cb) # TODO: disabe until is implemented with csnd6 # GLib.idle_add(self.initializePercussion) def createPercussionToolbar(self, toolbar_box): self.beats_pm_button = IntensitySelector(range(2, 13), 4, imagefile('beat3.svg')) self.tempo_button = \ IntensitySelector(range(PLAYER_TEMPO_LOWER, PLAYER_TEMPO_UPPER + 1, PLAYER_TEMPO_STEP), PLAYER_TEMPO, imagefile('tempo5.png')) self.complexity_button = IntensitySelector(xfrange(0, 1, 0.1), self.regularity, imagefile('complex6.svg')) self._play_percussion_btn = ToolButton( icon_name='media-playback-start') self._play_percussion_btn.set_property('can-default', True) self._play_percussion_btn.show() self._play_percussion_btn.connect('clicked', self.handlePlayButton) beats_toolbar = ToolbarBox() beats_toolbar.toolbar.insert(self._play_percussion_btn, -1) self._what_drum_widget = Gtk.ToolItem() self._what_drum_search_button = FilterToolItem(_('Select Drum'), 'view-type', _('Jazz / Rock Kit'), self._what_drum_widget) self._what_drum_search_button.set_widget_icon( file_name=imagefile("drum1kit.svg")) self._what_drum_widget.show() beats_toolbar.toolbar.insert(self._what_drum_search_button, -1) self._what_drum_search_button.show() self._what_drum_search_button.set_is_important(True) beats_toolbar.toolbar.insert(Gtk.SeparatorToolItem(), -1) beats_toolbar.toolbar.insert(self.complexity_button, -1) beats_toolbar.toolbar.insert(self.beats_pm_button, -1) beats_toolbar.toolbar.insert(self.tempo_button, -1) beats_toolbar_button = ToolbarButton(icon_name='toolbar-drums', page=beats_toolbar) beats_toolbar_button.show() toolbar_box.toolbar.insert(beats_toolbar_button, 1) self.beats_pm_button.set_tooltip(_("Beats per bar")) self.beats_pm_button.show() self.beats_pm_button.connect('changed', self.beatSliderChange, True) self.tempo_button.connect('changed', self.tempoSliderChange, True) self.complexity_button.connect('changed', self.handleComplexityChange, True) self.complexity_button.set_tooltip(_("Beat complexity")) self.tempo_button.show() self.tempo_button.set_tooltip(_('Tempo')) self.complexity_button.show() def initializePercussion(self): self.rythmInstrument = 'drum1kit' self.csnd.load_drumkit(self.rythmInstrument) self.csnd.setTempo(self.tempo) self.beatPickup = False def flatten(ll): rval = [] for l in ll: rval += l return rval noteOnsets = [] notePitchs = [] i = 0 self.noteList = [] self.csnd.loopClear() for x in flatten( generator(self.rythmInstrument, self.beat, 0.8, self.regularity, self.reverb)): x.amplitude = x.amplitude * self.drumVolume noteOnsets.append(x.onset) notePitchs.append(x.pitch) n = Note(0, x.trackId, i, x) self.noteList.append((x.onset, n)) i = i + 1 self.csnd.loopPlay(n, 1) # add as active self.csnd.loopSetNumTicks(self.beat * Config.TICKS_PER_BEAT) self.drumFillin.unavailable(noteOnsets, notePitchs) if self.playing: self.csnd.loopStart() def __allocate_cb(self, widget, rect): GLib.idle_add(self.resize, rect.width, rect.height) return False def resize(self, width, height): logging.debug('activity.py resize......') piano_height = width / 2 self._event_box.set_size_request( -1, Gdk.Screen.height() - piano_height - style.GRID_CELL_SIZE) return False def load_instruments(self): self._instruments_store = [] # load the images images_path = os.path.join(activity.get_bundle_path(), 'instruments') logging.debug('Loading instrument images from %s', images_path) for file_name in os.listdir(images_path): image_file_name = os.path.join(images_path, file_name) pxb = GdkPixbuf.Pixbuf.new_from_file_at_size( image_file_name, 75, 75) # instrument_name = image_file_name[image_file_name.rfind('/'):] instrument_name = image_file_name[image_file_name.rfind('/') + 1:] instrument_name = instrument_name[:instrument_name.find('.')] instrument_desc = \ self.instrumentDB.instNamed[instrument_name].nameTooltip file_path = os.path.join(images_path, file_name) # set the default icon if (instrument_name == 'piano'): self._what_search_button.set_widget_icon(file_name=file_path) self._instruments_store.append({ "instrument_name": instrument_name, "pxb": pxb, "instrument_desc": instrument_desc, "file_name": file_path, "callback": self.__instrument_iconview_activated_cb }) self._what_widget_contents = set_palette_list(self._instruments_store) self._what_widget.add(self._what_widget_contents) self._what_widget_contents.show() # TODO: disabe until is implemented with csnd6 """ for drum_number in range(0, DRUMCOUNT): drum_name = 'drum%dkit' % (drum_number + 1) self._drums_store.append({ "instrument_name": drum_name, "file_name": imagefile(drum_name + '.svg'), "instrument_desc": self.instrumentDB.instNamed[drum_name].nameTooltip, "callback": self.__drum_iconview_activated_cb }) self._what_drum_widget_contents = set_palette_list(self._drums_store) self._what_drum_widget.add(self._what_drum_widget_contents) self._what_drum_widget_contents.show() """ def __drum_iconview_activated_cb(self, widget, event, item): data = item['instrument_name'] self.rythmInstrument = data self.csnd.load_drumkit(data) instrumentId = self.instrumentDB.instNamed[data].instrumentId for (o, n) in self.noteList: self.csnd.loopUpdate(n, NoteDB.PARAMETER.INSTRUMENT, instrumentId, -1) self.drumFillin.setInstrument(self.rythmInstrument) self._what_drum_search_button.set_widget_label( label=item['instrument_desc']) self._what_drum_search_button.set_widget_icon( file_name=item['file_name']) def __instrument_iconview_activated_cb(self, widget, event, item): self.setInstrument(item['instrument_name']) self._what_search_button.set_widget_icon(file_name=item['file_name']) self._what_search_button.set_widget_label( label=item['instrument_desc']) def set_notes_labels_cb(self, widget): self.piano.font_size = 16 self.piano.set_labels(self.notes_labels) def set_ti_notes_labels_cb(self, widget): self.piano.font_size = 16 self.piano.set_labels(self.ti_notes_labels) def set_keyboard_labels_cb(self, widget): self.piano.font_size = 25 self.piano.set_labels(self.keyboard_letters) def set_german_labels_cb(self, widget): self.piano.font_size = 25 self.piano.set_labels(self.german_labels) def beatSliderChange(self, widget, event): self.beat = int(self.beats_pm_button.get_value()) self.sequencer.beat = self.beat self.loop.beat = self.beat self.drumFillin.setBeats(self.beat) img = int(self.scale(self.beat, 2, 12, 1, 11)) self.beats_pm_button.set_image(imagefile('beat' + str(img) + '.svg')) self.beatPickup = False self.regenerate() self.beatPickup = True def regenerate(self): def flatten(ll): rval = [] for l in ll: rval += l return rval noteOnsets = [] notePitchs = [] i = 0 self.noteList = [] self.csnd.loopClear() for x in flatten( generator(self.rythmInstrument, self.beat, 0.8, self.regularity, self.reverb)): x.amplitude = x.amplitude * self.drumVolume noteOnsets.append(x.onset) notePitchs.append(x.pitch) n = Note(0, x.trackId, i, x) self.noteList.append((x.onset, n)) i = i + 1 self.csnd.loopPlay(n, 1) # add as active self.csnd.loopSetNumTicks(self.beat * Config.TICKS_PER_BEAT) self.drumFillin.unavailable(noteOnsets, notePitchs) self.recordOverSensitivity(False) if self.playing: self.csnd.loopStart() def handlePlayRecordingButton(self, val): if not self.playing_recording: self.playing_recording = True self.play_recording_button.props.icon_name = 'media-playback-stop' self.play_recording_thread = \ GLib.timeout_add(100, self._play_recorded_keys) else: self.playing_recording = False self.play_recording_button.props.icon_name = 'media-playback-start' def _save_ogg_cb(self, widget): self._wav_tempfile = tempfile.NamedTemporaryFile(mode='w+b', suffix='.wav', dir='/tmp/') self.csnd.inputMessage(Config.CSOUND_RECORD_PERF % self._wav_tempfile.name) self.playing_recording = True self.play_recording_thread = \ GLib.timeout_add(100, self._play_recorded_keys, self._save_ogg_end_cb) def _save_ogg_end_cb(self): self.csnd.inputMessage(Config.CSOUND_STOP_RECORD_PERF % self._wav_tempfile.name) self._ogg_tempfile = tempfile.NamedTemporaryFile(mode='w+b', suffix='.ogg', dir='/tmp/') line = 'filesrc location=%s ! ' \ 'wavparse ! audioconvert ! vorbisenc ! oggmux ! ' \ 'filesink location=%s' % (self._wav_tempfile.name, self._ogg_tempfile.name) pipe = Gst.parse_launch(line) pipe.get_bus().add_signal_watch() pipe.get_bus().connect('message::eos', self._save_ogg_eos_cb, pipe) pipe.set_state(Gst.State.PLAYING) def _save_ogg_eos_cb(self, bus, message, pipe): bus.remove_signal_watch() pipe.set_state(Gst.State.NULL) title = '%s saved as audio' % self.metadata['title'] jobject = datastore.create() jobject.metadata['title'] = title jobject.metadata['keep'] = '0' jobject.metadata['mime_type'] = 'audio/ogg' jobject.file_path = self._ogg_tempfile.name datastore.write(jobject) self._wav_tempfile.close() self._ogg_tempfile.close() alert = NotifyAlert(10) alert.props.title = _('Audio recorded') alert.props.msg = _('The audio file was saved in the Journal') alert.connect('response', self.__alert_response_cb) self.add_alert(alert) return False def __alert_response_cb(self, alert, result): self.remove_alert(alert) def __record_button_click_cb(self, button): if not self.recording: self.play_recording_button.set_sensitive(False) self._save_as_audio_bt.set_sensitive(False) self.recorded_keys = [] self.recording = True icon = Icon(icon_name='media-record', fill_color='#ff0000') icon.show() self.record_button.set_icon_widget(icon) else: self.recording = False icon = Icon(icon_name='media-record', fill_color='#ffffff') icon.show() self.record_button.set_icon_widget(icon) if len(self.recorded_keys) != 0: self.play_recording_button.set_sensitive(True) self._save_as_audio_bt.set_sensitive(True) def tempoSliderChange(self, widget, event): self._updateTempo(self.tempo_button.get_value()) img = int( self.scale(self.tempo, PLAYER_TEMPO_LOWER, PLAYER_TEMPO_UPPER, 1, 9)) self.tempo_button.set_image(imagefile('tempo' + str(img) + '.png')) def _updateTempo(self, val): self.tempo = val self.beatDuration = 60.0 / self.tempo self.ticksPerSecond = Config.TICKS_PER_BEAT * self.tempo / 60.0 self.csnd.setTempo(self.tempo) self.sequencer.tempo = self.tempo self.drumFillin.setTempo(self.tempo) def handlePlayButton(self, val): if not self.playing: if not self.firstTime: self.regenerate() self.firstTime = True self.drumFillin.play() self.csnd.loopSetTick(0) self.csnd.loopStart() self.playing = True self._play_percussion_btn.props.icon_name = 'media-playback-stop' else: self.drumFillin.stop() self.sequencer.stopPlayback() self.csnd.loopPause() self.playing = False self._play_percussion_btn.props.icon_name = 'media-playback-start' def scale(self, input, input_min, input_max, output_min, output_max): range_input = input_max - input_min range_output = output_max - output_min result = (input - input_min) * range_output / range_input + output_min if (input_min > input_max and output_min > output_max) or \ (output_min > output_max and input_min < input_max): if result > output_min: return output_min elif result < output_max: return output_max else: return result if (input_min < input_max and output_min < output_max) or \ (output_min < output_max and input_min > input_max): if result > output_max: return output_max elif result < output_min: return output_min else: return result def handleComplexityChange(self, widget, event): self.regularity = self.complexity_button.get_value() img = int(self.complexity_button.get_value() * 7) + 1 self.complexity_button.set_image( imagefile('complex' + str(img) + '.svg')) self.beatPickup = False self.regenerate() self.beatPickup = True """ def handleBalanceSlider(self, adj): self.instVolume = int(adj.get_value()) self.drumVolume = sqrt( (100-self.instVolume)*0.01 ) self.adjustDrumVolume() self.drumFillin.setVolume(self.drumVolume) instrumentVolume = sqrt( self.instVolume*0.01 ) self.loop.adjustLoopVolume(instrumentVolume) self.sequencer.adjustSequencerVolume(instrumentVolume) img = int(self.scale(self.instVolume,100,0,0,4.9)) self._playToolbar.balanceSliderImgLeft.set_from_file( imagefile('dru' + str(img) + '.png')) img2 = int(self.scale(self.instVolume,0,100,0,4.9)) self._playToolbar.balanceSliderImgRight.set_from_file( imagefile('instr' + str(img2) + '.png')) def handleReverbSlider(self, adj): self.reverb = adj.get_value() self.drumFillin.setReverb( self.reverb ) img = int(self.scale(self.reverb,0,1,0,4)) self._playToolbar.reverbSliderImgRight.set_from_file( imagefile('reverb' + str(img) + '.png')) self.keyboardStandAlone.setReverb(self.reverb) """ def set_keyboard_no_labels_cb(self, widget): self.piano.font_size = 25 self.piano.set_labels(None) def enableKeyboard(self): self.keyboardStandAlone = KeyboardStandAlone( self.sequencer.recording, self.sequencer.adjustDuration, self.csnd.loopGetTick, self.sequencer.getPlayState, self.loop) self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) def setInstrument(self, instrument): logging.debug("Set Instrument: %s" % instrument) self.instrument = instrument self.keyboardStandAlone.setInstrument(instrument) self.csnd.load_instrument(instrument) def recordStateButton(self, button, state): pass # if button == 1: # self._recordToolbar.keyboardRecButton.set_active( state ) # else: # self._recordToolbar.keyboardRecOverButton.set_active( state ) def recordOverSensitivity(self, state): pass # self._recordToolbar.keyboardRecOverButton.set_sensitive( state ) def _play_recorded_keys(self, end_cb=None): GLib.source_remove(self.play_recording_thread) letter = self.recorded_keys[self.play_index] time_difference = 0 if self.play_index == len(self.recorded_keys) - 1: time_difference = \ self.recorded_keys[self.play_index][0] - \ self.recorded_keys[self.play_index - 1][0] else: next_time = self.recorded_keys[self.play_index + 1][0] time_difference = next_time - letter[0] if not self.playing_recording: self.play_recording_button.props.icon_name = 'media-playback-start' return if letter[-1] == 1: self.keyboardStandAlone.do_key_release( LETTERS_TO_KEY_CODES[letter[3]]) GLib.idle_add(self.piano.physical_key_changed, LETTERS_TO_KEY_CODES[letter[3]], False) else: self.keyboardStandAlone.do_key_press( LETTERS_TO_KEY_CODES[letter[3]], None, math.sqrt(self.instVolume * 0.01)) GLib.idle_add(self.piano.physical_key_changed, LETTERS_TO_KEY_CODES[letter[3]], True) if self.play_index == len(self.recorded_keys) - 1: self.play_index = 0 self.play_recording_button.props.icon_name = 'media-playback-start' self.playing_recording = False if end_cb is not None: end_cb() else: self.play_index += 1 self.play_recording_thread = \ GLib.timeout_add(int((time_difference) * 1000), self._play_recorded_keys, end_cb) def __key_pressed_cb(self, widget, octave_clicked, key_clicked, letter, physicallKey): logging.debug('Pressed Octave: %d Key: %d Letter: %s' % (octave_clicked, key_clicked, letter)) if letter in LETTERS_TO_KEY_CODES.keys(): if self.recording: self.recorded_keys.append( [time.time(), octave_clicked, key_clicked, letter]) if not physicallKey: self.keyboardStandAlone.do_key_press( LETTERS_TO_KEY_CODES[letter], None, math.sqrt(self.instVolume * 0.01)) def __key_released_cb(self, widget, octave_clicked, key_clicked, letter, physicallKey): if self.recording: self.recorded_keys.append( [time.time(), octave_clicked, key_clicked, letter, 1]) if not physicallKey: if letter in LETTERS_TO_KEY_CODES.keys(): self.keyboardStandAlone.do_key_release( LETTERS_TO_KEY_CODES[letter]) def onKeyPress(self, widget, event): if event.state & Gdk.ModifierType.CONTROL_MASK: return if event.hardware_keycode == 37: if self.muteInst: self.muteInst = False else: self.muteInst = True self.piano.physical_key_changed(event.hardware_keycode, True) self.keyboardStandAlone.onKeyPress(widget, event, math.sqrt(self.instVolume * 0.01)) def onKeyRelease(self, widget, event): self.keyboardStandAlone.onKeyRelease(widget, event) self.piano.physical_key_changed(event.hardware_keycode, False) def write_file(self, file_path): f = open(file_path, 'w') # substract the initial time to all the saved values if len(self.recorded_keys) > 0: initial_time = self.recorded_keys[0][0] for key in self.recorded_keys: key[0] = key[0] - initial_time f.write(json.dumps(self.recorded_keys)) f.close() def read_file(self, file_path): f = open(file_path, 'r') contents = f.read().strip() self.recorded_keys = json.loads(contents) if len(self.recorded_keys) != 0: self.play_recording_button.set_sensitive(True) self._save_as_audio_bt.set_sensitive(True) f.close() def close(self, skip_save=False): self.csnd.stop() # without which Csound will segfault activity.Activity.close(self, skip_save=skip_save)
class JamMain(Gtk.EventBox): def __init__(self, activity): GObject.GObject.__init__(self) self.activity = activity self.instrumentDB = InstrumentDB.getRef() self.noteDB = NoteDB.NoteDB() #-- initial settings ---------------------------------- self.tempo = Config.PLAYER_TEMPO self.beatDuration = 60.0 / self.tempo self.ticksPerSecond = Config.TICKS_PER_BEAT * self.tempo / 60.0 self.volume = 0.5 self.csnd = new_csound_client() for i in range(0, 9): self.csnd.setTrackVolume(100, i) # csnd expects a range 0-100 for now self.csnd.setMasterVolume(self.volume * 100) self.csnd.setTempo(self.tempo) self.muted = False #-- Drawing ------------------------------------------- def darken(hex): hexToDec = {"0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "A": 10, "B": 11, "C": 12, "D": 13, "E": 14, "F": 15, "a": 10, "b": 11, "c": 12, "d": 13, "e": 14, "f": 15} r = int(0.7 * (16 * hexToDec[hex[1]] + hexToDec[hex[2]])) g = int(0.7 * (16 * hexToDec[hex[3]] + hexToDec[hex[4]])) b = int(0.7 * (16 * hexToDec[hex[5]] + hexToDec[hex[6]])) return r * 256, g * 256, b * 256 def lighten(hex): hexToDec = {"0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "A": 10, "B": 11, "C": 12, "D": 13, "E": 14, "F": 15, "a": 10, "b": 11, "c": 12, "d": 13, "e": 14, "f": 15} r = 255 - int(0.7 * (255 - ( 16 * hexToDec[hex[1]] + hexToDec[hex[2]]))) g = 255 - int(0.7 * (255 - ( 16 * hexToDec[hex[3]] + hexToDec[hex[4]]))) b = 255 - int(0.7 * (255 - ( 16 * hexToDec[hex[5]] + hexToDec[hex[6]]))) return r * 256, g * 256, b * 256 xoColor = profile.get_color() if not xoColor: xoColorKey = ("#8D8D8D,#FFDDEA") xoColor = XoColor(xoColorKey) # colors in Config and in XoColor are strings, # the colors in style are style.Color, transform all to Gdk.Color self.colors = {"bg": CairoUtil.get_gdk_color(Config.PANEL_BCK_COLOR), "black": style.COLOR_BLACK.get_gdk_color(), #"Picker_Bg": colormap.alloc_color("#404040"), #"Picker_Bg_Inactive": colormap.alloc_color("#808080"), "Picker_Bg": style.COLOR_TOOLBAR_GREY.get_gdk_color(), "Picker_Bg_Inactive": style.COLOR_BUTTON_GREY.get_gdk_color(), "Picker_Fg": style.COLOR_WHITE.get_gdk_color(), "Border_Active": \ CairoUtil.get_gdk_color(xoColor.get_stroke_color()), "Border_Inactive": CairoUtil.get_gdk_color("#8D8D8D"), "Border_Highlight": CairoUtil.get_gdk_color("#FFFFFF"), "Bg_Active": CairoUtil.get_gdk_color(xoColor.get_fill_color()), "Bg_Inactive": CairoUtil.get_gdk_color("#DBDBDB"), "Preview_Note_Fill": CairoUtil.get_gdk_color(Config.BG_COLOR), "Preview_Note_Border": CairoUtil.get_gdk_color(Config.FG_COLOR), "Preview_Note_Selected": style.COLOR_WHITE.get_gdk_color(), # TODO: lighten here can be removed, check if is used in other # places "Note_Fill_Active": Gdk.Color(*lighten("#590000")), # base "Border_Active" "Note_Fill_Inactive": Gdk.Color(*lighten("#8D8D8D")), # base "Border_Inactive" "Beat_Line": CairoUtil.get_gdk_color("#959595")} self.colors["Note_Border_Active"] = self.colors["Border_Active"] self.colors["Note_Border_Inactive"] = self.colors["Border_Inactive"] self.sampleNoteHeight = 7 self.sampleBg = cairo.ImageSurface.create_from_png( imagefile('sampleBG.png')) self.loopPitchOffset = 4 self.loopTickOffset = 13 self.pitchPerPixel = float(Config.NUMBER_OF_POSSIBLE_PITCHES - 1) / \ (Block.Loop.HEIGHT - 2 * self.loopPitchOffset - \ self.sampleNoteHeight) self.pixelsPerPitch = float(Block.Loop.HEIGHT - \ 2 * self.loopPitchOffset - self.sampleNoteHeight) / \ (Config.MAXIMUM_PITCH - Config.MINIMUM_PITCH) self.pixelsPerTick = Block.Loop.BEAT / float(Config.TICKS_PER_BEAT) self.ticksPerPixel = 1.0 / self.pixelsPerTick #-- Instruments --------------------------------------- self.instrumentImage = {} self.instrumentImageActive = {} for inst in self.instrumentDB.getSet("All"): if not inst.kitStage: self.prepareInstrumentImage(inst.instrumentId, inst.img) self.csnd.load_instrument(inst.name) #-- Loop Images --------------------------------------- self.loopImage = {} # get filled in through updateLoopImage self.loopImageActive = {} #-- Key Images ---------------------------------------- self.keyImage = {} self.keyImageActive = {} # use hardware key codes to work on any keyboard layout (hopefully) self.valid_shortcuts = {18: "9", 19: "0", 20: "-", 21: "=", 32: "O", 33: "P", 34: "[", 35: "]", 47: ";", 48: "'", 51: "\\", 60: ".", 61: "/", None: " "} for key in self.valid_shortcuts.keys(): self.prepareKeyImage(key) #-- Toolbars ------------------------------------------ self.jamToolbar = JamToolbar(self) jam_toolbar_button = ToolbarButton(label=_('Jam'), page=self.jamToolbar, icon_name='voltemp') self.jamToolbar.show() jam_toolbar_button.show() self.activity.toolbar_box.toolbar.insert(jam_toolbar_button, -1) self.beatToolbar = BeatToolbar(self) beat_toolbar_button = ToolbarButton(label=_('Beat'), page=self.beatToolbar, icon_name='heart') self.beatToolbar.show() beat_toolbar_button.show() self.activity.toolbar_box.toolbar.insert(beat_toolbar_button, -1) self.desktopToolbar = DesktopToolbar(self) desktop_toolbar_button = ToolbarButton(label=_('Desktop'), page=self.desktopToolbar, icon_name='jam-presets-list') self.desktopToolbar.show() desktop_toolbar_button.show() self.activity.toolbar_box.toolbar.insert(desktop_toolbar_button, -1) if Config.FEATURES_MIC or Config.FEATURES_NEWSOUNDS: self.recordToolbar = RecordToolbar(self) record_toolbar_button = ToolbarButton(label=_('Record'), page=self.recordToolbar, icon_name='microphone') self.recordToolbar.show() record_toolbar_button.show() self.activity.toolbar_box.toolbar.insert(record_toolbar_button, -1) separator = Gtk.SeparatorToolItem() separator.props.draw = True separator.set_expand(False) self.activity.toolbar_box.toolbar.insert(separator, -1) separator.show() common_playback_buttons(self.activity.toolbar_box.toolbar, self) #-- GUI ----------------------------------------------- if True: # GUI self.modify_bg(Gtk.StateType.NORMAL, self.colors["bg"]) self.GUI = {} self.GUI["mainVBox"] = Gtk.VBox() self.add(self.GUI["mainVBox"]) #-- Desktop ------------------------------------------- self.desktop = self.GUI["desktop"] = Desktop(self) self.GUI["mainVBox"].pack_start(self.GUI["desktop"], True, True, 0) #-- Bank ---------------------------------------------- separator = Gtk.Label(label=" ") separator.set_size_request(-1, style.TOOLBOX_SEPARATOR_HEIGHT) self.GUI["mainVBox"].pack_start(separator, False, True, 0) self.GUI["notebook"] = Gtk.Notebook() self.GUI["notebook"].set_scrollable(True) self.GUI["notebook"].modify_bg(Gtk.StateType.NORMAL, self.colors["Picker_Bg"]) self.GUI["notebook"].modify_bg(Gtk.StateType.ACTIVE, self.colors["Picker_Bg_Inactive"]) # TODO gtk3 no available anymore? #self.GUI["notebook"].props.tab_vborder = style.TOOLBOX_TAB_VBORDER #self.GUI["notebook"].props.tab_hborder = style.TOOLBOX_TAB_HBORDER self.GUI["notebook"].set_size_request(-1, scale(160)) self.GUI["notebook"].connect("switch-page", self.setPicker) self.GUI["mainVBox"].pack_start(self.GUI["notebook"], False, False, 0) self.pickers = {} self.pickerScroll = {} for type in [Picker.Instrument, Picker.Drum, Picker.Loop]: self.pickers[type] = type(self) def prepareLabel(name): label = Gtk.Label(label=Tooltips.categories.get(name) or name) label.set_alignment(0.0, 0.5) label.modify_fg(Gtk.StateType.NORMAL, self.colors["Picker_Fg"]) label.modify_fg(Gtk.StateType.ACTIVE, self.colors["Picker_Fg"]) return label self.GUI["notebook"].append_page(self.pickers[Picker.Drum], prepareLabel(_("Drum Kits"))) self.GUI["notebook"].append_page(self.pickers[Picker.Loop], prepareLabel(_("Loops"))) sets = self.instrumentDB.getLabels()[:] sets.sort() for set in sets: page = Gtk.HBox() page.set = set self.GUI["notebook"].append_page(page, prepareLabel(set)) self.show_all() self.GUI["notebook"].set_current_page(0) #-- Keyboard ------------------------------------------ self.key_dict = {} self.nextTrack = 2 self.keyboardListener = None self.recordingNote = None self.keyMap = {} # default instrument self._updateInstrument( self.instrumentDB.instNamed["kalimba"].instrumentId, 0.5) self.instrumentStack = [] # metronome page = NoteDB.Page(1, local=False) self.metronomePage = self.noteDB.addPage(-1, page) self.metronome = False #-- Drums --------------------------------------------- self.drumLoopId = None # use dummy values for now self.drumFillin = Fillin( 2, 100, self.instrumentDB.instNamed["drum1kit"].instrumentId, 0, 1) #-- Desktops ------------------------------------------ self.curDesktop = None # copy preset desktops path = Config.FILES_DIR + "/Desktops/" filelist = os.listdir(path) for file in filelist: shutil.copyfile(path + file, Config.TMP_DIR + '/' + file) #-- Network ------------------------------------------- self.network = Net.Network() self.network.addWatcher(self.networkStatusWatcher) self.network.connectMessage(Net.HT_SYNC_REPLY, self.processHT_SYNC_REPLY) self.network.connectMessage(Net.HT_TEMPO_UPDATE, self.processHT_TEMPO_UPDATE) self.network.connectMessage(Net.PR_SYNC_QUERY, self.processPR_SYNC_QUERY) self.network.connectMessage(Net.PR_TEMPO_QUERY, self.processPR_TEMPO_QUERY) self.network.connectMessage(Net.PR_REQUEST_TEMPO_CHANGE, self.processPR_REQUEST_TEMPO_CHANGE) # sync self.syncQueryStart = {} self.syncTimeout = None self.heartbeatLoop = self.csnd.loopCreate() self.syncBeats = 4 self.syncTicks = self.syncBeats * Config.TICKS_PER_BEAT self.offsetTicks = 0 # offset from the true heartbeat self.csnd.loopSetNumTicks(self.syncTicks * HEARTBEAT_BUFFER, self.heartbeatLoop) self.heartbeatStart = time.time() self.csnd.loopStart(self.heartbeatLoop) self.curBeat = 0 self.beatWheelTimeout = GObject.timeout_add(100, self.updateBeatWheel) # data packing classes self.packer = xdrlib.Packer() self.unpacker = xdrlib.Unpacker("") # handle forced networking if self.network.isHost(): self.updateSync() self.syncTimeout = GObject.timeout_add(1000, self.updateSync) elif self.network.isPeer(): self.sendTempoQuery() self.syncTimeout = GObject.timeout_add(1000, self.updateSync) self.activity.connect("shared", self.shared) if self.activity.shared_activity: # PEER self.activity.shared_activity.connect("buddy-joined", self.buddy_joined) self.activity.shared_activity.connect("buddy-left", self.buddy_left) self.activity.connect("joined", self.joined) self.network.setMode(Net.MD_WAIT) #-- Final Set Up -------------------------------------- self.setVolume(self.volume) self.setTempo(self.tempo) #self.activity.toolbar_box.set_current_toolbar(1) # JamToolbar self.setDesktop(0, True) #========================================================== def onActivate(self, arg): pass def onDeactivate(self): pass def onDestroy(self): self.network.shutdown() #clear up scratch folder path = Config.TMP_DIR filelist = os.listdir(path) for file in filelist: os.remove(path + '/' + file) #========================================================== # Playback def onKeyPress(self, widget, event): key = event.hardware_keycode if key in self.keyMap.keys(): activate = True for block in self.keyMap[key]: if block.isActive(): activate = False break if activate: for block in self.keyMap[key]: if not block.isActive(): if block.type == Block.Drum: self.desktop.activateDrum(block) elif block.type == Block.Loop: self.desktop.activateLoop(block) else: for block in self.keyMap[key]: if block.isActive(): if block.type == Block.Drum: self.desktop.deactivateDrum(block) elif block.type == Block.Loop: self.desktop.deactivateLoop(block) return if key in self.key_dict: # repeated press return if key in Config.KEY_MAP_PIANO: pitch = Config.KEY_MAP_PIANO[key] inst = self.instrumentDB.instId[self.instrument["id"]] if inst.kit: # drum kit if pitch in GenerationConstants.DRUMPITCH: pitch = GenerationConstants.DRUMPITCH[pitch] csnote = self._playNote( # trackVol * noteVol key, 36, self.instrument["amplitude"] * 0.5, self.instrument["pan"], 100, self.instrumentDB.instNamed[inst.kit[pitch]].instrumentId, self.instrument["reverb"]) else: if event.get_state() == Gdk.ModifierType.MOD1_MASK: pitch += 5 # Percussions resonance if inst.csoundInstrumentId == Config.INST_PERC: duration = 60 else: duration = -1 csnote = self._playNote( # trackVol * noteVol key, pitch, self.instrument["amplitude"] * 0.5, self.instrument["pan"], duration, self.instrument["id"], self.instrument["reverb"]) if self.keyboardListener: self.keyboardListener.recordNote(csnote.pitch) self.recordingNote = True def onKeyRelease(self, widget, event): key = event.hardware_keycode if key in self.key_dict: self._stopNote(key) if self.recordingNote: if self.keyboardListener: self.keyboardListener.finishNote() self.recordingNote = False def _playNote(self, key, pitch, amplitude, pan, duration, instrumentId, reverb): self.key_dict[key] = CSoundNote( # onset 0, pitch, amplitude, pan, duration, self.nextTrack, instrumentId, reverbSend=reverb, tied=True, mode='mini') self.nextTrack += 1 if self.nextTrack > 8: self.nextTrack = 2 self.csnd.play(self.key_dict[key], 0.3) return self.key_dict[key] def _stopNote(self, key): csnote = self.key_dict[key] if self.instrumentDB.instId[csnote.instrumentId].csoundInstrumentId \ == Config.INST_TIED: csnote.duration = .5 csnote.decay = 0.7 csnote.tied = False self.csnd.play(csnote, 0.3) del self.key_dict[key] def _updateInstrument(self, id, volume, pan=0, reverb=0): self.instrument = {"id": id, "amplitude": volume, "pan": pan, "reverb": reverb} def pushInstrument(self, instrument): self.instrumentStack.append(self.instrument) self.instrument = instrument def popInstrument(self): self.instrument = self.instrumentStack.pop() def _playDrum(self, id, pageId, volume, reverb, beats, regularity, loopId=None, sync=True): oldId = loopId loopId = self.csnd.loopCreate() noteOnsets = [] notePitchs = [] for n in self.noteDB.getNotesByTrack(pageId, 0): n.pushState() noteOnsets.append(n.cs.onset) notePitchs.append(n.cs.pitch) n.cs.instrumentId = id n.cs.amplitude = volume * n.cs.amplitude n.cs.reverbSend = reverb self.csnd.loopPlay(n, 1, loopId=loopId) # add as active n.popState() ticks = self.noteDB.getPage(pageId).ticks self.csnd.loopSetNumTicks(ticks, loopId) self.drumFillin.setLoopId(loopId) self.drumFillin.setProperties( self.tempo, self.instrumentDB.instId[id].name, volume, beats, reverb) self.drumFillin.unavailable(noteOnsets, notePitchs) self.drumFillin.play() if oldId == None: if sync: startTick = self.csnd.loopGetTick(self.heartbeatLoop) \ % self.syncTicks else: startTick = 0 else: # TODO is this really safe? could potentially add several # milliseconds of delay everytime a loop is updated if sync: startTick = self.csnd.loopGetTick(oldId) else: startTick = 0 while startTick > ticks: startTick -= ticks self.csnd.loopSetTick(startTick, loopId) self.csnd.loopStart(loopId) if oldId != None: self.csnd.loopDestroy(oldId) return loopId def _stopDrum(self, loopId): self.drumFillin.stop() self.csnd.loopDestroy(loopId) def _playLoop(self, id, volume, reverb, tune, loopId=None, force=False, sync=True): oldId = loopId loopId = self.csnd.loopCreate() inst = self.instrumentDB.instId[id] ticks = 0 for page in tune: for n in self.noteDB.getNotesByTrack(page, 0): n.pushState() n.cs.instrumentId = id n.cs.amplitude = volume * n.cs.amplitude n.cs.reverbSend = reverb if inst.kit: # drum kit if n.cs.pitch in GenerationConstants.DRUMPITCH: n.cs.pitch = GenerationConstants.DRUMPITCH[n.cs.pitch] n.cs.onset += ticks self.csnd.loopPlay(n, 1, loopId=loopId) n.popState() # metronome track for n in self.noteDB.getNotesByTrack(page, 1): self.csnd.loopPlay(n, 1, loopId=loopId) # record scratch track for n in self.noteDB.getNotesByTrack(page, 2): self.csnd.loopPlay(n, 1, loopId=loopId) ticks += self.noteDB.getPage(page).ticks self.csnd.loopSetNumTicks(ticks, loopId) if oldId == None: if sync: startTick = self.csnd.loopGetTick(self.heartbeatLoop) \ % self.syncTicks else: startTick = 0 else: # TODO is this really safe? could potentially add several # milliseconds of delay everytime a loop is updated if sync: startTick = self.csnd.loopGetTick(oldId) else: startTick = 0 while startTick > ticks: startTick -= ticks self.csnd.loopSetTick(startTick, loopId) self.csnd.loopStart(loopId) if oldId != None: self.csnd.loopDestroy(oldId) return loopId def _stopLoop(self, loopId): self.csnd.loopDestroy(loopId) def addMetronome(self, page, period): self.noteDB.deleteNotesByTrack([page], [1]) baseCS = CSoundNote( 0, # onset 36, # pitch 0.2, # amplitude 0.5, # pan 100, # duration 1, # track self.instrumentDB.instNamed["drum1hatpedal"].instrumentId, reverbSend=0.5, tied=True, mode='mini') stream = [] offset = 0 for b in range(self.noteDB.getPage(page).beats): cs = baseCS.clone() cs.instrumentId = \ self.instrumentDB.instNamed["drum1hatshoulder"].instrumentId cs.amplitude = 0.5 cs.onset += offset stream.append(cs) onset = period while onset < Config.TICKS_PER_BEAT: cs = baseCS.clone() cs.onset = onset + offset stream.append(cs) onset += period offset += Config.TICKS_PER_BEAT self.noteDB.addNotes([page, 1, len(stream)] + stream + [-1]) def removeMetronome(self, page): self.noteDB.deleteNotesByTrack([page], [1]) def handleStopButton(self, widget): self.setStopped() def handleMuteButton(self, widget): if widget.get_active(): self._setMuted(True) else: self._setMuted(False) def setMuted(self, muted): if Config.HAVE_TOOLBOX: toolbar = self.activity.toolbox.toolbar else: toolbar = self.playbackToolbar if toolbar.muteButton.get_active() == muted: return toolbar.muteButton.set_active(muted) toolbar.playbackToolbar.setMuted(muted) def _setMuted(self, muted): if self.muted == muted: return False if self.muted: # unmute self.muted = False self.csnd.setTrackVolume(100, 0) else: # mute self.muted = True self.csnd.setTrackVolume(0, 0) return True def setStopped(self): for drum in list(self.desktop.drums): self.desktop.deactivateDrum(drum) # we copy the list using the list() method for loop in list(self.desktop.loops): self.desktop.deactivateLoop(loop) #========================================================== # Generate def _generateDrumLoop(self, instrumentId, beats, regularity, reverb, pageId=-1): def flatten(ll): rval = [] for l in ll: rval += l return rval notes = flatten(generator( self.instrumentDB.instId[instrumentId].name, beats, 0.8, regularity, reverb)) if pageId == -1: page = Page(beats) pageId = self.noteDB.addPage(-1, page) else: self.noteDB.deleteNotesByTrack([pageId], [0]) if len(notes): self.noteDB.addNotes([pageId, 0, len(notes)] + notes + [-1]) return pageId def _generateTrack(self, instrumentId, page, track, parameters, algorithm): dict = {track: {page: self.noteDB.getCSNotesByTrack(page, track)}} instruments = {page: [self.instrumentDB.instId[instrumentId].name \ for i in range(Config.NUMBER_OF_TRACKS)]} beatsOfPages = {page: self.noteDB.getPage(page).beats} algorithm(parameters, [0.5 for i in range(Config.NUMBER_OF_TRACKS)], instruments, self.tempo, beatsOfPages, [track], [page], dict, 4) # filter & fix input ...WTF!? for track in dict: for page in dict[track]: for note in dict[track][page]: intdur = int(note.duration) note.duration = intdur note.pageId = page note.trackId = track # prepare the new notes newnotes = [] for tid in dict: for pid in dict[tid]: newnotes += dict[tid][pid] # delete the notes and add the new self.noteDB.deleteNotesByTrack([page], [track]) self.noteDB.addNotes([page, track, len(dict[track][page])] \ + dict[track][page] + [-1]) #========================================================== # Mic recording def micRec(self, widget, mic): self.csnd.inputMessage("i5600 0 4") OS.arecord(4, "crop.csd", mic) self.csnd.load_mic_instrument(mic) #========================================================== # Loop Settings def loopSettingsChannel(self, channel, value): self.csnd.setChannel(channel, value) def loopSettingsPlayStop(self, state, loop): if not state: if loop: self.loopSettingsPlaying = True self.csnd.inputMessage(Config.CSOUND_PLAY_LS_NOTE % 5022) else: self.csnd.inputMessage(Config.CSOUND_PLAY_LS_NOTE % 5023) else: if loop: self.loopSettingsPlaying = False self.csnd.inputMessage(Config.CSOUND_STOP_LS_NOTE) def load_ls_instrument(self, soundName): self.csnd.load_ls_instrument(soundName) #========================================================== # Get/Set def getVolume(self): return self.volume def setVolume(self, volume): self.jamToolbar.volumeSlider.set_value(volume) def _setVolume(self, volume): if self.muted: self.setMuted(False) self.volume = volume # csnd expects a range 0-100 for now self.csnd.setMasterVolume(self.volume * 100) def getTempo(self): return self.tempo def setTempo(self, tempo, quiet=False): self.jamToolbar.setTempo(tempo, quiet) def _setTempo(self, tempo, propagate=True): if self.network.isHost() or self.network.isOffline(): t = time.time() elapsedTicks = (t - self.heartbeatStart) * self.ticksPerSecond self.tempo = tempo self.beatDuration = 60.0 / self.tempo self.ticksPerSecond = Config.TICKS_PER_BEAT * self.tempo / 60.0 self.csnd.setTempo(self.tempo) if self.network.isHost() or self.network.isOffline(): self.heatbeatStart = t - elapsedTicks * self.beatDuration self.updateSync() self.sendTempoUpdate() def getInstrument(self): return self.instrument def getDesktop(self): return self.desktop def _clearDesktop(self, save=True): if self.curDesktop == None: return if save: self._saveDesktop() self.desktop._clearDesktop() self.curDesktop = None def setDesktop(self, desktop, force=False): radiobtn = self.desktopToolbar.getDesktopButton(desktop) if force and radiobtn.get_active(): self._setDesktop(desktop) else: radiobtn.set_active(True) def _setDesktop(self, desktop): self._clearDesktop() self.curDesktop = desktop TTTable = ControlStream.TamTamTable(self.noteDB, jam=self) filename = self.getDesktopScratchFile(self.curDesktop) try: stream = open(filename, "r") TTTable.parseFile(stream) stream.close() except IOError, (errno, strerror): if Config.DEBUG > 3: print "IOError:: _setDesktop:", errno, strerror
class miniTamTamMain(Gtk.HBox): def __init__(self, activity): Gtk.HBox.__init__(self) self.instrumentPanel = None self.activity = activity self.instrumentDB = InstrumentDB.getRef() self.firstTime = False self.playing = False self.csnd = new_csound_client() self.timeout_ms = 50 self.instVolume = 50 self.drumVolume = 0.5 self.instrument = 'sarangi' self.regularity = 0.75 self.beat = 4 self.reverb = 0.1 self.tempo = Config.PLAYER_TEMPO self.beatDuration = 60.0/self.tempo self.ticksPerSecond = Config.TICKS_PER_BEAT*self.tempo/60.0 self.rythmInstrument = 'drum1kit' self.csnd.load_drumkit(self.rythmInstrument) self.muteInst = False self.drumFillin = Fillin( self.beat, self.tempo, self.rythmInstrument, self.reverb, self.drumVolume ) self.sequencer= MiniSequencer(self.recordStateButton, self.recordOverSensitivity) self.loop = Loop(self.beat, sqrt( self.instVolume*0.01 )) self.csnd.setTempo(self.tempo) self.noteList = [] time.sleep(0.001) # why? self.trackpad = Trackpad( self ) for i in range(21): self.csnd.setTrackVolume( 100, i ) for i in range(10): r = str(i+1) self.csnd.load_instrument('guidice' + r) self.volume = 100 self.csnd.setMasterVolume(self.volume) self.sequencer.beat = self.beat self.loop.beat = self.beat self.leftBox = Gtk.VBox() self.rightBox = Gtk.VBox() # TODO: right is at left, and left is at right? self.pack_start(self.rightBox, False, False, 0) self.pack_start(self.leftBox, False, False, 0) self.enableKeyboard() self.setInstrument(self.instrument) self.connect('key-press-event', self.onKeyPress) self.connect('key-release-event', self.onKeyRelease) self.drawGeneration() self.show_all() if 'a good idea' == True: self.playStartupSound() self.beatPickup = True #self.regenerate() self.heartbeatStart = time.time() self.syncQueryStart = {} self.syncTimeout = None self.network = Net.Network() self.network.addWatcher( self.networkStatusWatcher ) self.network.connectMessage( Net.HT_SYNC_REPLY, self.processHT_SYNC_REPLY ) self.network.connectMessage( Net.HT_TEMPO_UPDATE, self.processHT_TEMPO_UPDATE ) self.network.connectMessage( Net.PR_SYNC_QUERY, self.processPR_SYNC_QUERY ) self.network.connectMessage( Net.PR_TEMPO_QUERY, self.processPR_TEMPO_QUERY ) self.network.connectMessage( Net.PR_REQUEST_TEMPO_CHANGE, self.processPR_REQUEST_TEMPO_CHANGE ) # data packing classes self.packer = xdrlib.Packer() self.unpacker = xdrlib.Unpacker("") #-- handle forced networking --------------------------------------- if self.network.isHost(): self.updateSync() self.syncTimeout = GObject.timeout_add( 1000, self.updateSync ) elif self.network.isPeer(): self.sendTempoQuery() self.syncTimeout = GObject.timeout_add( 1000, self.updateSync ) #------------------------------------------------------------------- # Toolbar if Config.HAVE_TOOLBOX: from sugar3.graphics.toolbarbox import ToolbarButton # no sharing # self.max_participants = 1 self._playToolbar = playToolbar(self) ## Uncomment to show play and record tabs ## ''' play_toolbar_button = ToolbarButton(label=_('Play'), page=self._playToolbar, # Fixme: need an icon icon_name='activity-start') self._playToolbar.show() play_toolbar_button.show() self.activity.toolbox.toolbar.insert(play_toolbar_button, -1) self._recordToolbar = recordToolbar(self) record_toolbar_button = ToolbarButton(label=_('Record'), page=self._recordToolbar, # Fixme: need an icon icon_name='media-record') self._recordToolbar.show() record_toolbar_button.show() self.activity.toolbox.toolbar.insert(record_toolbar_button, -1) ''' else: self._playToolbar = playToolbar(self) ## set to 1 to show play and record tabs ## if 0: self._recordToolbar = recordToolbar(self) self.activity.toolbox.add_toolbar(_('Play'), self._playToolbar) self.activity.toolbox.add_toolbar(_('Record'), self._recordToolbar) self.activity.toolbox.set_current_toolbar(1) self._playToolbar.show() self._recordToolbar.show() if not Config.HAVE_TOOLBOX: self.activity.connect( "shared", self.shared ) if os.path.isfile("FORCE_SHARE"): # HOST #print "::::: Sharing as TTDBG%f :::::" % r #self.activity.set_title(_("TTDBG%f" % r)) print "::::: Sharing as TamTam :::::" self.activity.set_title(_("TamTam")) self.activity.share() elif self.activity.shared_activity: # PEER self.activity.shared_activity.connect( "buddy-joined", self.buddy_joined ) self.activity.shared_activity.connect( "buddy-left", self.buddy_left ) self.activity.connect( "joined", self.joined ) self.network.setMode( Net.MD_WAIT ) #self.activity.activity_toolbar.share.hide() def drawGeneration( self ): slidersBox = Gtk.VBox() geneSliderBox = Gtk.VBox() self.geneSliderBoxImgTop = Gtk.Image() self.geneSliderBoxImgTop.set_from_file(imagefile('complex6.png')) self.geneAdjustment = Gtk.Adjustment(value=self.regularity, lower=0, upper=1, step_incr=0.01, page_incr=0, page_size=0) self.geneSlider = ImageVScale('sliderbutbleu.png', self.geneAdjustment, 5) self.geneSlider.set_inverted(False) self.geneAdjustment.connect("value_changed" , self.handleGenerationSlider) self.geneSlider.connect("button-release-event", self.handleGenerationSliderRelease) geneSliderBox.pack_start(self.geneSliderBoxImgTop, False, True, padding=10) geneSliderBox.pack_start(self.geneSlider, True, True, 20) self.geneSlider.set_tooltip_text(Tooltips.COMPL) beatSliderBox = Gtk.VBox() self.beatSliderBoxImgTop = Gtk.Image() self.beatSliderBoxImgTop.set_from_file(imagefile('beat3.png')) self.beatAdjustment = Gtk.Adjustment(value=self.beat, lower=2, upper=12, step_incr=1, page_incr=0, page_size=0) self.beatSlider = ImageVScale('sliderbutjaune.png', self.beatAdjustment, 5, snap=1) self.beatSlider.set_inverted(True) self.beatAdjustment.connect("value_changed" , self.handleBeatSlider) self.beatSlider.connect("button-release-event", self.handleBeatSliderRelease) beatSliderBox.pack_start(self.beatSliderBoxImgTop, False, True, padding=10) beatSliderBox.pack_start(self.beatSlider, True, True, 20) self.beatSlider.set_tooltip_text(Tooltips.BEAT) self.delayedTempo = 0 # used to store tempo updates while the slider is active self.tempoSliderActive = False tempoSliderBox = Gtk.VBox() self.tempoSliderBoxImgTop = Gtk.Image() self.tempoSliderBoxImgTop.set_from_file(imagefile('tempo5.png')) self.tempoAdjustment = Gtk.Adjustment(value=self.tempo, lower=Config.PLAYER_TEMPO_LOWER, upper=Config.PLAYER_TEMPO_UPPER, step_incr=1, page_incr=1, page_size=1) tempoSlider = ImageVScale('sliderbutvert.png', self.tempoAdjustment, 5) tempoSlider.set_inverted(True) self.tempoAdjustmentHandler = self.tempoAdjustment.connect("value_changed" , self.handleTempoSliderChange) tempoSlider.connect("button-press-event", self.handleTempoSliderPress) tempoSlider.connect("button-release-event", self.handleTempoSliderRelease) tempoSliderBox.pack_start(self.tempoSliderBoxImgTop, False, True, padding=10) tempoSliderBox.pack_start(tempoSlider, True, True, 0) tempoSlider.set_tooltip_text(Tooltips.TEMPO) volumeSliderBox = Gtk.VBox() self.volumeSliderBoxImgTop = Gtk.Image() self.volumeSliderBoxImgTop.set_from_file(imagefile('volume2.png')) self.volumeAdjustment = Gtk.Adjustment(value=self.volume, lower=0, upper=200, step_incr=1, page_incr=1, page_size=1) volumeSlider = ImageVScale('sliderbutbleu.png', self.volumeAdjustment, 5) volumeSlider.set_inverted(True) self.volumeAdjustment.connect("value_changed" , self.handleVolumeSlider) #volumeSlider.connect("button-release-event", self.handleVolumeSliderRelease) volumeSliderBox.pack_start(self.volumeSliderBoxImgTop, False, True, padding=10) volumeSliderBox.pack_start(volumeSlider, True, True, 0) volumeSlider.set_tooltip_text(Tooltips.VOL) slidersBoxSub = Gtk.HBox() slidersBoxSub.pack_start(beatSliderBox, True, True, 0) slidersBoxSub.pack_start(geneSliderBox, True, True, 0) slidersBoxSub.pack_start(tempoSliderBox, True, True, 0) slidersBoxSub.pack_start(volumeSliderBox, True, True, 0) slidersBox.pack_start(slidersBoxSub, True, True, 0) generateBtnSub = Gtk.HBox() self.playButton = ImageToggleButton('miniplay.png', 'stop.png') self.playButton.connect('clicked',self.handlePlayButton) generateBtnSub.pack_start(self.playButton, True, True, 0) self.playButton.set_tooltip_text(_('Play / Stop')) generateBtn = ImageButton('dice.png', clickImg_path='diceblur.png') generateBtn.connect('button-press-event', self.handleGenerateBtn) generateBtnSub.pack_start(generateBtn, True, True, 0) generateBtn.set_tooltip_text(Tooltips.GEN) # drums drum_box = Gtk.VBox() drum_scroll = VScrolledBox(scroll_policy=Gtk.PolicyType.NEVER) drum_scroll.set_viewport(drum_box) drum_scroll.modify_bg(Gtk.StateType.NORMAL, style.Color(Config.PANEL_BCK_COLOR).get_gdk_color()) drum_i = 0 drum_group = None for row in range(DRUMCOUNT/2 + DRUMCOUNT%2): row_box = Gtk.HBox() drum_box.pack_start(row_box, False, True, 0) for col in range(2): if drum_i >= DRUMCOUNT: break drum = ImageRadioButton( group=drum_group, mainImg_path='drum%dkit.png' % (drum_i + 1), altImg_path='drum%dkitselgen.png' % (drum_i +1)) drum.connect('clicked', self.handleGenerationDrumBtn, 'drum%dkit' % (drum_i+1)) row_box.pack_start(drum, True, True, 0) drum_name = 'drum%dkit' % (drum_i + 1) hint = self.instrumentDB.instNamed[drum_name].nameTooltip drum.set_tooltip_text(hint) if not drum_group: drum_group = drum drum_i += 1 self.rightBox.pack_start(slidersBox, True, True, 0) self.rightBox.pack_start(generateBtnSub, True, True, 0) self.rightBox.pack_start(drum_scroll, False, True, 0) drum_size = drum_group.get_size_request() slidersBox.set_size_request(-1, int(drum_size[1] * 2.3)) self.rightBox.set_size_request(int(drum_size[0] * 2.05), -1) def loopSettingsChannel(self, channel, value): self.csnd.setChannel(channel, value) def loopSettingsPlayStop(self, state, loop): if not state: if loop: self.loopSettingsPlaying = True self.csnd.inputMessage(Config.CSOUND_PLAY_LS_NOTE % 5022) else: self.csnd.inputMessage(Config.CSOUND_PLAY_LS_NOTE % 5023) else: if loop: self.loopSettingsPlaying = False self.csnd.inputMessage(Config.CSOUND_STOP_LS_NOTE) def load_ls_instrument(self, soundName): self.csnd.load_ls_instrument(soundName) def updateInstrumentPanel(self): if self.instrumentPanel is None: self.instrumentPanel = InstrumentPanel() width = Gdk.Screen.width() - self.rightBox.get_size_request()[0] self.instrumentPanel.configure(self.setInstrument, self.playInstrumentNote, False, self.micRec, width=width) self.instrumentPanel.load() self.leftBox.pack_start(self.instrumentPanel, True, True, 0) def micRec(self, widget, mic): self.csnd.inputMessage("i5600 0 4") OS.arecord(4, "crop.csd", mic) self.micTimeout = GObject.timeout_add(200, self.loadMicInstrument, mic) self.instrumentPanel.set_activeInstrument(mic,True) self.setInstrument(mic) def recordStateButton( self, button, state ): if button == 1: self._recordToolbar.keyboardRecButton.set_active( state ) else: self._recordToolbar.keyboardRecOverButton.set_active( state ) def recordOverSensitivity( self, state ): pass #self._recordToolbar.keyboardRecOverButton.set_sensitive( state ) def loadMicInstrument( self, data ): self.csnd.load_mic_instrument( data ) def regenerate(self): def flatten(ll): rval = [] for l in ll: rval += l return rval if self.beatPickup: self.pickupNewBeat() noteOnsets = [] notePitchs = [] i = 0 self.noteList= [] self.csnd.loopClear() for x in flatten( generator(self.rythmInstrument, self.beat, 0.8, self.regularity, self.reverb) ): x.amplitude = x.amplitude * self.drumVolume noteOnsets.append(x.onset) notePitchs.append(x.pitch) n = Note(0, x.trackId, i, x) self.noteList.append( (x.onset, n) ) i = i + 1 self.csnd.loopPlay(n,1) #add as active self.csnd.loopSetNumTicks( self.beat * Config.TICKS_PER_BEAT) self.drumFillin.unavailable( noteOnsets, notePitchs ) self.recordOverSensitivity( False ) if self.playing: self.csnd.loopStart() def adjustDrumVolume(self): for n in self.noteList: self.csnd.loopUpdate(n[1], PARAMETER.AMPLITUDE, n[1].cs.amplitude*self.drumVolume, 1) def handleClose(self,widget): if self.playStopButton.get_active() == True: self.playStopButton.set_active(False) self.sequencer.clearSequencer() self.csnd.loopClear() self.activity.close() def handleGenerationSlider(self, adj): img = int(adj.get_value() * 7)+1 self.geneSliderBoxImgTop.set_from_file( imagefile('complex' + str(img) + '.png')) def handleGenerationSliderRelease(self, widget, event): self.regularity = widget.get_adjustment().get_value() self.beatPickup = False self.regenerate() self.beatPickup = True def pickupNewBeat(self): self.beat = random.randint(2, 12) img = self.scale(self.beat,2,12,1,11) self.beatSliderBoxImgTop.set_from_file( imagefile('beat' + str(img) + '.png')) self.beatAdjustment.set_value(self.beat) self.regularity = random.randint(50, 100) * 0.01 img = int(self.regularity * 7)+1 self.geneSliderBoxImgTop.set_from_file( imagefile('complex' + str(img) + '.png')) self.geneAdjustment.set_value(self.regularity) self.sequencer.beat = self.beat self.loop.beat = self.beat self.drumFillin.setBeats( self.beat ) def handleBeatSlider(self, adj): img = self.scale(int(adj.get_value()),2,12,1,11) self.beatSliderBoxImgTop.set_from_file( imagefile('beat' + str(img) + '.png')) self.sequencer.beat = self.beat self.loop.beat = self.beat self.drumFillin.setBeats( self.beat ) def handleBeatSliderRelease(self, widget, event): self.beat = int(widget.get_adjustment().get_value()) self.sequencer.beat = self.beat self.loop.beat = self.beat self.drumFillin.setBeats( self.beat ) self.beatPickup = False self.regenerate() self.beatPickup = True def handleTempoSliderPress(self, widget, event): self.tempoSliderActive = True def handleTempoSliderRelease(self, widget, event): self.tempoSliderActive = False if self.network.isPeer() and self.delayedTempo != 0: if self.tempo != self.delayedTempo: self.tempoAdjustment.handler_block( self.tempoAdjustmentHandler ) self.tempoAdjustment.set_value( self.delayedTempo ) self._updateTempo( self.delayedTempo ) self.tempoAdjustment.handler_unblock( self.tempoAdjustmentHandler ) self.delayedTempo = 0 self.sendSyncQuery() def handleTempoSliderChange(self,adj): if self.network.isPeer(): self.requestTempoChange(int(adj.get_value())) else: self._updateTempo(int(adj.get_value())) def _updateTempo( self, val ): if self.network.isHost(): t = time.time() percent = self.heartbeatElapsed() / self.beatDuration self.tempo = val self.beatDuration = 60.0/self.tempo self.ticksPerSecond = Config.TICKS_PER_BEAT*self.tempo/60.0 self.csnd.setTempo(self.tempo) self.sequencer.tempo = self.tempo self.drumFillin.setTempo(self.tempo) if self.network.isHost(): self.heatbeatStart = t - percent*self.beatDuration self.updateSync() self.sendTempoUpdate() img = int(self.scale( self.tempo, Config.PLAYER_TEMPO_LOWER,Config.PLAYER_TEMPO_UPPER, 1,9)) self.tempoSliderBoxImgTop.set_from_file( imagefile('tempo' + str(img) + '.png')) def handleBalanceSlider(self, adj): self.instVolume = int(adj.get_value()) self.drumVolume = sqrt( (100-self.instVolume)*0.01 ) self.adjustDrumVolume() self.drumFillin.setVolume(self.drumVolume) instrumentVolume = sqrt( self.instVolume*0.01 ) self.loop.adjustLoopVolume(instrumentVolume) self.sequencer.adjustSequencerVolume(instrumentVolume) img = int(self.scale(self.instVolume,100,0,0,4.9)) self._playToolbar.balanceSliderImgLeft.set_from_file( imagefile('dru' + str(img) + '.png')) img2 = int(self.scale(self.instVolume,0,100,0,4.9)) self._playToolbar.balanceSliderImgRight.set_from_file( imagefile('instr' + str(img2) + '.png')) def handleReverbSlider(self, adj): self.reverb = adj.get_value() self.drumFillin.setReverb( self.reverb ) img = int(self.scale(self.reverb,0,1,0,4)) self._playToolbar.reverbSliderImgRight.set_from_file( imagefile('reverb' + str(img) + '.png')) self.keyboardStandAlone.setReverb(self.reverb) def handleVolumeSlider(self, adj): self.volume = adj.get_value() self.csnd.setMasterVolume(self.volume) img = int(self.scale(self.volume,0,200,0,3.9)) self.volumeSliderBoxImgTop.set_from_file( imagefile('volume' + str(img) + '.png')) def handlePlayButton(self, widget, data = None): # use widget.get_active() == False when calling this on 'clicked' # use widget.get_active() == True when calling this on button-press-event if widget.get_active() == False: self.drumFillin.stop() self.sequencer.stopPlayback() self.csnd.loopPause() self.playing = False else: if not self.firstTime: self.regenerate() self.firstTime = True self.drumFillin.play() #self.csnd.loopSetTick(0) nextInTicks = self.nextHeartbeatInTicks() #print "play:: next beat in %f ticks. bpb == %d. setting ticks to %d" % (nextInTicks, self.beat, Config.TICKS_PER_BEAT*self.beat - int(round(nextInTicks))) self.csnd.loopSetTick( Config.TICKS_PER_BEAT*self.beat - int(round(nextInTicks)) ) self.csnd.loopStart() self.playing = True def handleGenerationDrumBtn(self , widget , data): #data is drum1kit, drum2kit, or drum3kit #print 'HANDLE: Generate Button' self.rythmInstrument = data self.csnd.load_drumkit(data) instrumentId = self.instrumentDB.instNamed[data].instrumentId for (o,n) in self.noteList : self.csnd.loopUpdate(n, NoteDB.PARAMETER.INSTRUMENT, instrumentId, -1) self.drumFillin.setInstrument( self.rythmInstrument ) def handleGenerateBtn(self , widget , data=None): self.regenerate() if not self.playButton.get_active(): self.playButton.set_active(True) #this calls sends a 'clicked' event, #which might be connected to handlePlayButton self.playStartupSound() def enableKeyboard( self ): self.keyboardStandAlone = KeyboardStandAlone( self.sequencer.recording, self.sequencer.adjustDuration, self.csnd.loopGetTick, self.sequencer.getPlayState, self.loop ) self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) def setInstrument( self , instrument ): self.instrument = instrument self.keyboardStandAlone.setInstrument(instrument) self.csnd.load_instrument(instrument) def playInstrumentNote(self , instrument, secs_per_tick = 0.025): if not self.muteInst: self.csnd.play( CSoundNote( onset = 0, pitch = 36, amplitude = 1, pan = 0.5, duration = 20, trackId = 1, instrumentId = self.instrumentDB.instNamed[instrument].instrumentId, reverbSend = self.reverb, tied = False, mode = 'mini'), secs_per_tick) def onKeyPress(self, widget, event): if event.hardware_keycode == 219: #'/*' button to reset drum loop if self.playStopButton.get_active() == True: self.handlePlayButton(self.playStopButton) self.playStopButton.set_active(False) self.handlePlayButton(self.playStopButton) self.playStopButton.set_active(True) if event.hardware_keycode == 37: if self.muteInst: self.muteInst = False else: self.muteInst = True if event.hardware_keycode == 65: #what key is this? what feature is this? pass #if self.playStopButton.get_active(): #self.playStopButton.set_active(False) #else: #self.playStopButton.set_active(True) self.keyboardStandAlone.onKeyPress(widget, event, sqrt( self.instVolume*0.01 )) def onKeyRelease(self, widget, event): self.keyboardStandAlone.onKeyRelease(widget, event) def playStartupSound(self): r = str(random.randrange(1,11)) self.playInstrumentNote('guidice' + r) def onActivate( self, arg ): self.csnd.loopPause() self.csnd.loopClear() def onDeactivate( self ): SubActivity.onDeactivate( self ) self.csnd.loopPause() self.csnd.loopClear() def onDestroy( self ): self.network.shutdown() def scale(self, input,input_min,input_max,output_min,output_max): range_input = input_max - input_min range_output = output_max - output_min result = (input - input_min) * range_output / range_input + output_min if (input_min > input_max and output_min > output_max) or (output_min > output_max and input_min < input_max): if result > output_min: return output_min elif result < output_max: return output_max else: return result if (input_min < input_max and output_min < output_max) or (output_min < output_max and input_min > input_max): if result > output_max: return output_max elif result < output_min: return output_min else: return result #----------------------------------------------------------------------- # Network #-- Activity ----------------------------------------------------------- def shared( self, activity ): if Config.DEBUG: print "miniTamTam:: successfully shared, start host mode" self.activity.shared_activity.connect( "buddy-joined", self.buddy_joined ) self.activity.shared_activity.connect( "buddy-left", self.buddy_left ) self.network.setMode( Net.MD_HOST ) self.updateSync() self.syncTimeout = GObject.timeout_add( 1000, self.updateSync ) def joined( self, activity ): print "miniTamTam:: joined activity!!" for buddy in self.activity.shared_activity.get_joined_buddies(): print buddy.props.ip4_address def buddy_joined( self, activity, buddy ): print "buddy joined " + str(buddy) try: print buddy.props.ip4_address except: print "bad ip4_address" if self.network.isHost(): # TODO how do I figure out if this buddy is me? if buddy.props.ip4_address: self.network.introducePeer( buddy.props.ip4_address ) else: print "miniTamTam:: new buddy does not have an ip4_address!!" def buddy_left( self, activity, buddy): print "buddy left" #def joined( self, activity ): # if Config.DEBUG: print "miniTamTam:: successfully joined, wait for host" # self.net.waitForHost() #-- Senders ------------------------------------------------------------ def sendSyncQuery( self ): self.packer.pack_float(random.random()) hash = self.packer.get_buffer() self.packer.reset() self.syncQueryStart[hash] = time.time() self.network.send( Net.PR_SYNC_QUERY, hash) def sendTempoUpdate( self ): self.packer.pack_int(self.tempo) self.network.sendAll( Net.HT_TEMPO_UPDATE, self.packer.get_buffer() ) self.packer.reset() def sendTempoQuery( self ): self.network.send( Net.PR_TEMPO_QUERY ) def requestTempoChange( self, val ): self.packer.pack_int(val) self.network.send( Net.PR_REQUEST_TEMPO_CHANGE, self.packer.get_buffer() ) self.packer.reset() #-- Handlers ----------------------------------------------------------- def networkStatusWatcher( self, mode ): if mode == Net.MD_OFFLINE: if self.syncTimeout: GObject.source_remove( self.syncTimeout ) self.syncTimeout = None if mode == Net.MD_PEER: self.updateSync() if not self.syncTimeout: self.syncTimeout = GObject.timeout_add( 1000, self.updateSync ) self.sendTempoQuery() def processHT_SYNC_REPLY( self, sock, message, data ): t = time.time() hash = data[0:4] latency = t - self.syncQueryStart[hash] self.unpacker.reset(data[4:8]) nextBeat = self.unpacker.unpack_float() #print "mini:: got sync: next beat in %f, latency %d" % (nextBeat, latency*1000) self.heartbeatStart = t + nextBeat - self.beatDuration - latency/2 self.correctSync() self.syncQueryStart.pop(hash) def processHT_TEMPO_UPDATE( self, sock, message, data ): self.unpacker.reset(data) val = self.unpacker.unpack_int() if self.tempoSliderActive: self.delayedTempo = val return self.tempoAdjustment.handler_block( self.tempoAdjustmentHandler ) self.tempoAdjustment.set_value( val ) self._updateTempo( val ) self.tempoAdjustment.handler_unblock( self.tempoAdjustmentHandler ) self.sendSyncQuery() def processPR_SYNC_QUERY( self, sock, message, data ): self.packer.pack_float(self.nextHeartbeat()) self.network.send( Net.HT_SYNC_REPLY, data + self.packer.get_buffer(), sock ) self.packer.reset() def processPR_TEMPO_QUERY( self, sock, message, data ): self.packer.pack_int(self.tempo) self.network.send( Net.HT_TEMPO_UPDATE, self.packer.get_buffer(), to = sock ) self.packer.reset() def processPR_REQUEST_TEMPO_CHANGE( self, sock, message, data ): if self.tempoSliderActive: return self.unpacker.reset(data) val = self.unpacker.unpack_int() self.tempoAdjustment.set_value( val ) #----------------------------------------------------------------------- # Sync def nextHeartbeat( self ): delta = time.time() - self.heartbeatStart return self.beatDuration - (delta % self.beatDuration) def nextHeartbeatInTicks( self ): delta = time.time() - self.heartbeatStart next = self.beatDuration - (delta % self.beatDuration) return self.ticksPerSecond*next def heartbeatElapsed( self ): delta = time.time() - self.heartbeatStart return delta % self.beatDuration def heartbeatElapsedTicks( self ): delta = time.time() - self.heartbeatStart return self.ticksPerSecond*(delta % self.beatDuration) def updateSync( self ): if self.network.isOffline(): return False elif self.network.isWaiting(): return True elif self.network.isHost(): self.correctSync() else: self.sendSyncQuery() return True def correctSync( self ): curTick = self.csnd.loopGetTick() curTicksIn = curTick % Config.TICKS_PER_BEAT ticksIn = self.heartbeatElapsedTicks() err = curTicksIn - ticksIn if err > Config.TICKS_PER_BEAT_DIV2: err -= Config.TICKS_PER_BEAT elif err < -Config.TICKS_PER_BEAT_DIV2: err += Config.TICKS_PER_BEAT correct = curTick - err ticksPerLoop = Config.TICKS_PER_BEAT*self.beat if correct > ticksPerLoop: correct -= ticksPerLoop elif correct < 0: correct += ticksPerLoop #print "correct:: %f ticks, %f ticks in, %f expected, %f err, correct %f" % (curTick, curTicksIn, ticksIn, err, correct) if abs(err) > 0.25: self.csnd.adjustTick(-err)
class SimplePianoActivity(activity.Activity): """SimplePianoActivity class as specified in activity.info""" def __init__(self, handle): activity.Activity.__init__(self, handle) GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.close) Gst.init(None) self._what_list = [] self.play_recording_thread = None self.playing_recording = False self.firstTime = False self.playing = False self.regularity = 0.7 self._drums_store = [] self.recording = False self.recorded_keys = [] self.is_valid_recording = False # we do not have collaboration features # make the share option insensitive self.max_participants = 1 self.csnd = new_csound_client() self.rythmInstrument = 'drum1kick' # toolbar with the new toolbar redesign toolbar_box = ToolbarBox() activity_button = ActivityToolbarButton(self) toolbar_box.toolbar.insert(activity_button, 0) toolbar_box.toolbar.set_style(Gtk.ToolbarStyle.BOTH_HORIZ) self.play_index = 0 self.play_recording_button = ToolButton( icon_name='media-playback-start') self.play_recording_button.set_property('can-default', True) self.play_recording_button.show() self.record_button = ToggleToolButton(icon_name='media-record') self.record_button.set_property('can-default', True) self.record_button.show() self.play_recording_button.set_sensitive(False) self.record_button.connect('clicked', self.__record_button_click_cb) self.play_recording_button.connect('clicked', self.handlePlayRecordingButton) toolbar_box.toolbar.set_style(Gtk.ToolbarStyle.BOTH_HORIZ) # TODO: disabe until is implemented with csnd6 # self.createPercussionToolbar(toolbar_box) toolbar_box.toolbar.insert(Gtk.SeparatorToolItem(), -1) keybord_labels = RadioToolButton() keybord_labels.props.icon_name = 'q_key' keybord_labels.props.group = keybord_labels keybord_labels.connect('clicked', self.set_keyboard_labels_cb) toolbar_box.toolbar.insert(keybord_labels, -1) notes_labels = RadioToolButton() notes_labels.props.icon_name = 'do_key' notes_labels.props.group = keybord_labels notes_labels.connect('clicked', self.set_notes_labels_cb) toolbar_box.toolbar.insert(notes_labels, -1) ti_notes_labels = RadioToolButton() ti_notes_labels.props.icon_name = 'ti_key' ti_notes_labels.props.group = keybord_labels ti_notes_labels.connect('clicked', self.set_ti_notes_labels_cb) toolbar_box.toolbar.insert(ti_notes_labels, -1) german_labels = RadioToolButton() german_labels.props.icon_name = 'c_key' german_labels.props.group = keybord_labels german_labels.connect('clicked', self.set_german_labels_cb) toolbar_box.toolbar.insert(german_labels, -1) no_labels = RadioToolButton() no_labels.props.icon_name = 'edit-clear' no_labels.props.group = keybord_labels no_labels.connect('clicked', self.set_keyboard_no_labels_cb) toolbar_box.toolbar.insert(no_labels, -1) self._what_widget = Gtk.ToolItem() self._what_search_button = FilterToolItem(_('Select Instrument'), 'view-type', _('Piano'), self._what_widget) self._what_widget.show() toolbar_box.toolbar.insert(Gtk.SeparatorToolItem(), -1) toolbar_box.toolbar.insert(self._what_search_button, -1) self._what_search_button.show() self._what_search_button.set_is_important(True) self._what_widget_contents = None self._what_drum_widget_contents = None separator = Gtk.SeparatorToolItem() toolbar_box.toolbar.insert(separator, -1) toolbar_box.toolbar.insert(self.record_button, -1) toolbar_box.toolbar.insert(self.play_recording_button, -1) separator = Gtk.SeparatorToolItem() separator.props.draw = False separator.set_expand(True) toolbar_box.toolbar.insert(separator, -1) stop_button = StopButton(self) toolbar_box.toolbar.insert(stop_button, -1) stop_button.show() self._save_as_audio_bt = ToolButton(icon_name='save-as-audio') self._save_as_audio_bt.props.tooltip = _('Save as audio') self._save_as_audio_bt.connect('clicked', self._save_ogg_cb) self._save_as_audio_bt.show() self._save_as_audio_bt.set_sensitive(False) activity_button.page.insert(self._save_as_audio_bt, -1) self.set_toolbar_box(toolbar_box) toolbar_box.show_all() self.keyboard_letters = ['ZSXDCVGBHNJM', 'Q2W3ER5T6Y7U', 'I'] notes = ['DO', ['DO#', 'REb'], 'RE', ['RE#', 'MIb'], 'MI', 'FA', ['FA#', 'SOLb'], 'SOL', ['SOL#', 'LAb'], 'LA', ['LA#', 'SIb'], 'SI'] self.notes_labels = [notes, notes, ['DO']] # some countries use TI instead of SI ti_notes = ['DO', ['DO#', 'REb'], 'RE', ['RE#', 'MIb'], 'MI', 'FA', ['FA#', 'SOLb'], 'SOL', ['SOL#', 'LAb'], 'LA', ['LA#', 'TIb'], 'TI'] self.ti_notes_labels = [ti_notes, ti_notes, ['DO']] german_notes = ['C', ['C#', 'Db'], 'D', ['D#', 'Eb'], 'E', 'F', ['F#', 'Gb'], 'G', ['G#', 'Ab'], 'A', ['A#', 'Bb'], 'B'] self.german_labels = [german_notes, german_notes, ['C']] self.piano = PianoKeyboard(octaves=2, add_c=True, labels=self.keyboard_letters) # init csound self.instrumentDB = InstrumentDB.getRef() self.timeout_ms = 50 self.instVolume = 50 self.drumVolume = 0.5 self.instrument = 'piano' self.beat = 4 self.reverb = 0.1 self.tempo = PLAYER_TEMPO self.beatDuration = 60.0 / self.tempo self.ticksPerSecond = Config.TICKS_PER_BEAT * self.tempo / 60.0 self.sequencer = MiniSequencer(self.recordStateButton, self.recordOverSensitivity) self.loop = Loop(self.beat, math.sqrt(self.instVolume * 0.01)) self.drumFillin = Fillin(self.beat, self.tempo, self.rythmInstrument, self.reverb, self.drumVolume) self.muteInst = False self.csnd.setTempo(self.tempo) self.noteList = [] for i in range(21): self.csnd.setTrackVolume(100, i) # TODO commented because apparently are not used in the activity # for i in range(10): # self.csnd.load_instrument('guidice' + str(i + 1)) self.volume = 100 self.csnd.setMasterVolume(self.volume) self.enableKeyboard() self.setInstrument(self.instrument) self.connect('key-press-event', self.onKeyPress) self.connect('key-release-event', self.onKeyRelease) self.piano.connect('key_pressed', self.__key_pressed_cb) self.piano.connect('key_released', self.__key_released_cb) vbox = Gtk.VBox() vbox.set_homogeneous(False) self.load_instruments() self._event_box = Gtk.EventBox() self._event_box.modify_bg( Gtk.StateType.NORMAL, style.COLOR_WHITE.get_gdk_color()) vbox.pack_start(self._event_box, False, False, 0) vbox.pack_end(self.piano, True, True, 0) vbox.show_all() self.set_canvas(vbox) piano_height = Gdk.Screen.width() / 2 self._event_box.set_size_request( -1, Gdk.Screen.height() - piano_height - style.GRID_CELL_SIZE) self.connect('size-allocate', self.__allocate_cb) # TODO: disabe until is implemented with csnd6 # GLib.idle_add(self.initializePercussion) def createPercussionToolbar(self, toolbar_box): self.beats_pm_button = IntensitySelector(range(2, 13), 4, imagefile('beat3.svg')) self.tempo_button = \ IntensitySelector(range(PLAYER_TEMPO_LOWER, PLAYER_TEMPO_UPPER + 1, PLAYER_TEMPO_STEP), PLAYER_TEMPO, imagefile('tempo5.png')) self.complexity_button = IntensitySelector(xfrange(0, 1, 0.1), self.regularity, imagefile('complex6.svg')) self._play_percussion_btn = ToolButton( icon_name='media-playback-start') self._play_percussion_btn.set_property('can-default', True) self._play_percussion_btn.show() self._play_percussion_btn.connect('clicked', self.handlePlayButton) beats_toolbar = ToolbarBox() beats_toolbar.toolbar.insert(self._play_percussion_btn, -1) self._what_drum_widget = Gtk.ToolItem() self._what_drum_search_button = FilterToolItem( _('Select Drum'), 'view-type', _('Jazz / Rock Kit'), self._what_drum_widget) self._what_drum_search_button.set_widget_icon( file_name=imagefile("drum1kit.svg")) self._what_drum_widget.show() beats_toolbar.toolbar.insert(self._what_drum_search_button, -1) self._what_drum_search_button.show() self._what_drum_search_button.set_is_important(True) beats_toolbar.toolbar.insert(Gtk.SeparatorToolItem(), -1) beats_toolbar.toolbar.insert(self.complexity_button, -1) beats_toolbar.toolbar.insert(self.beats_pm_button, -1) beats_toolbar.toolbar.insert(self.tempo_button, -1) beats_toolbar_button = ToolbarButton(icon_name='toolbar-drums', page=beats_toolbar) beats_toolbar_button.show() toolbar_box.toolbar.insert(beats_toolbar_button, 1) self.beats_pm_button.set_tooltip(_("Beats per bar")) self.beats_pm_button.show() self.beats_pm_button.connect('changed', self.beatSliderChange, True) self.tempo_button.connect('changed', self.tempoSliderChange, True) self.complexity_button.connect('changed', self.handleComplexityChange, True) self.complexity_button.set_tooltip(_("Beat complexity")) self.tempo_button.show() self.tempo_button.set_tooltip(_('Tempo')) self.complexity_button.show() def initializePercussion(self): self.rythmInstrument = 'drum1kit' self.csnd.load_drumkit(self.rythmInstrument) self.csnd.setTempo(self.tempo) self.beatPickup = False def flatten(ll): rval = [] for l in ll: rval += l return rval noteOnsets = [] notePitchs = [] i = 0 self.noteList = [] self.csnd.loopClear() for x in flatten( generator(self.rythmInstrument, self.beat, 0.8, self.regularity, self.reverb)): x.amplitude = x.amplitude * self.drumVolume noteOnsets.append(x.onset) notePitchs.append(x.pitch) n = Note(0, x.trackId, i, x) self.noteList.append((x.onset, n)) i = i + 1 self.csnd.loopPlay(n, 1) # add as active self.csnd.loopSetNumTicks(self.beat * Config.TICKS_PER_BEAT) self.drumFillin.unavailable(noteOnsets, notePitchs) if self.playing: self.csnd.loopStart() def __allocate_cb(self, widget, rect): GLib.idle_add(self.resize, rect.width, rect.height) return False def resize(self, width, height): logging.debug('activity.py resize......') piano_height = width / 2 self._event_box.set_size_request( -1, Gdk.Screen.height() - piano_height - style.GRID_CELL_SIZE) return False def load_instruments(self): self._instruments_store = [] # load the images images_path = os.path.join(activity.get_bundle_path(), 'instruments') logging.debug('Loading instrument images from %s', images_path) for file_name in os.listdir(images_path): image_file_name = os.path.join(images_path, file_name) pxb = GdkPixbuf.Pixbuf.new_from_file_at_size( image_file_name, 75, 75) # instrument_name = image_file_name[image_file_name.rfind('/'):] instrument_name = image_file_name[image_file_name.rfind('/') + 1:] instrument_name = instrument_name[:instrument_name.find('.')] instrument_desc = \ self.instrumentDB.instNamed[instrument_name].nameTooltip file_path = os.path.join(images_path, file_name) # set the default icon if (instrument_name == 'piano'): self._what_search_button.set_widget_icon( file_name=file_path) self._instruments_store.append( {"instrument_name": instrument_name, "pxb": pxb, "instrument_desc": instrument_desc, "file_name": file_path, "callback": self.__instrument_iconview_activated_cb}) self._what_widget_contents = set_palette_list(self._instruments_store) self._what_widget.add(self._what_widget_contents) self._what_widget_contents.show() # TODO: disabe until is implemented with csnd6 """ for drum_number in range(0, DRUMCOUNT): drum_name = 'drum%dkit' % (drum_number + 1) self._drums_store.append({ "instrument_name": drum_name, "file_name": imagefile(drum_name + '.svg'), "instrument_desc": self.instrumentDB.instNamed[drum_name].nameTooltip, "callback": self.__drum_iconview_activated_cb }) self._what_drum_widget_contents = set_palette_list(self._drums_store) self._what_drum_widget.add(self._what_drum_widget_contents) self._what_drum_widget_contents.show() """ def __drum_iconview_activated_cb(self, widget, event, item): data = item['instrument_name'] self.rythmInstrument = data self.csnd.load_drumkit(data) instrumentId = self.instrumentDB.instNamed[data].instrumentId for (o, n) in self.noteList: self.csnd.loopUpdate(n, NoteDB.PARAMETER.INSTRUMENT, instrumentId, -1) self.drumFillin.setInstrument(self.rythmInstrument) self._what_drum_search_button.set_widget_label( label=item['instrument_desc']) self._what_drum_search_button.set_widget_icon( file_name=item['file_name']) def __instrument_iconview_activated_cb(self, widget, event, item): self.setInstrument(item['instrument_name']) self._what_search_button.set_widget_icon(file_name=item['file_name']) self._what_search_button.set_widget_label( label=item['instrument_desc']) def set_notes_labels_cb(self, widget): self.piano.font_size = 16 self.piano.set_labels(self.notes_labels) def set_ti_notes_labels_cb(self, widget): self.piano.font_size = 16 self.piano.set_labels(self.ti_notes_labels) def set_keyboard_labels_cb(self, widget): self.piano.font_size = 25 self.piano.set_labels(self.keyboard_letters) def set_german_labels_cb(self, widget): self.piano.font_size = 25 self.piano.set_labels(self.german_labels) def beatSliderChange(self, widget, event): self.beat = int(self.beats_pm_button.get_value()) self.sequencer.beat = self.beat self.loop.beat = self.beat self.drumFillin.setBeats(self.beat) img = int(self.scale(self.beat, 2, 12, 1, 11)) self.beats_pm_button.set_image(imagefile('beat' + str(img) + '.svg')) self.beatPickup = False self.regenerate() self.beatPickup = True def regenerate(self): def flatten(ll): rval = [] for l in ll: rval += l return rval noteOnsets = [] notePitchs = [] i = 0 self.noteList = [] self.csnd.loopClear() for x in flatten( generator(self.rythmInstrument, self.beat, 0.8, self.regularity, self.reverb)): x.amplitude = x.amplitude * self.drumVolume noteOnsets.append(x.onset) notePitchs.append(x.pitch) n = Note(0, x.trackId, i, x) self.noteList.append((x.onset, n)) i = i + 1 self.csnd.loopPlay(n, 1) # add as active self.csnd.loopSetNumTicks(self.beat * Config.TICKS_PER_BEAT) self.drumFillin.unavailable(noteOnsets, notePitchs) self.recordOverSensitivity(False) if self.playing: self.csnd.loopStart() def handlePlayRecordingButton(self, val): if not self.playing_recording: self.playing_recording = True self.play_recording_button.props.icon_name = 'media-playback-stop' self.play_recording_thread = \ GLib.timeout_add(100, self._play_recorded_keys) else: self.playing_recording = False self.play_recording_button.props.icon_name = 'media-playback-start' def _save_ogg_cb(self, widget): self._wav_tempfile = tempfile.NamedTemporaryFile( mode='w+b', suffix='.wav', dir='/tmp/') self.csnd.inputMessage(Config.CSOUND_RECORD_PERF % self._wav_tempfile.name) self.playing_recording = True self.play_recording_thread = \ GLib.timeout_add(100, self._play_recorded_keys, self._save_ogg_end_cb) def _save_ogg_end_cb(self): self.csnd.inputMessage(Config.CSOUND_STOP_RECORD_PERF % self._wav_tempfile.name) self._ogg_tempfile = tempfile.NamedTemporaryFile( mode='w+b', suffix='.ogg', dir='/tmp/') line = 'filesrc location=%s ! ' \ 'wavparse ! audioconvert ! vorbisenc ! oggmux ! ' \ 'filesink location=%s' % (self._wav_tempfile.name, self._ogg_tempfile.name) pipe = Gst.parse_launch(line) pipe.get_bus().add_signal_watch() pipe.get_bus().connect('message::eos', self._save_ogg_eos_cb, pipe) pipe.set_state(Gst.State.PLAYING) def _save_ogg_eos_cb(self, bus, message, pipe): bus.remove_signal_watch() pipe.set_state(Gst.State.NULL) title = '%s saved as audio' % self.metadata['title'] jobject = datastore.create() jobject.metadata['title'] = title jobject.metadata['keep'] = '0' jobject.metadata['mime_type'] = 'audio/ogg' jobject.file_path = self._ogg_tempfile.name datastore.write(jobject) self._wav_tempfile.close() self._ogg_tempfile.close() alert = NotifyAlert(10) alert.props.title = _('Audio recorded') alert.props.msg = _('The audio file was saved in the Journal') alert.connect('response', self.__alert_response_cb) self.add_alert(alert) return False def __alert_response_cb(self, alert, result): self.remove_alert(alert) def __record_button_click_cb(self, button): if not self.recording: self.play_recording_button.set_sensitive(False) self._save_as_audio_bt.set_sensitive(False) self.recorded_keys = [] self.recording = True icon = Icon(icon_name='media-record', fill_color='#ff0000') icon.show() self.record_button.set_icon_widget(icon) else: self.recording = False icon = Icon(icon_name='media-record', fill_color='#ffffff') icon.show() self.record_button.set_icon_widget(icon) if len(self.recorded_keys) != 0: self.play_recording_button.set_sensitive(True) self._save_as_audio_bt.set_sensitive(True) def tempoSliderChange(self, widget, event): self._updateTempo(self.tempo_button.get_value()) img = int(self.scale(self.tempo, PLAYER_TEMPO_LOWER, PLAYER_TEMPO_UPPER, 1, 9)) self.tempo_button.set_image(imagefile('tempo' + str(img) + '.png')) def _updateTempo(self, val): self.tempo = val self.beatDuration = 60.0 / self.tempo self.ticksPerSecond = Config.TICKS_PER_BEAT * self.tempo / 60.0 self.csnd.setTempo(self.tempo) self.sequencer.tempo = self.tempo self.drumFillin.setTempo(self.tempo) def handlePlayButton(self, val): if not self.playing: if not self.firstTime: self.regenerate() self.firstTime = True self.drumFillin.play() self.csnd.loopSetTick(0) self.csnd.loopStart() self.playing = True self._play_percussion_btn.props.icon_name = 'media-playback-stop' else: self.drumFillin.stop() self.sequencer.stopPlayback() self.csnd.loopPause() self.playing = False self._play_percussion_btn.props.icon_name = 'media-playback-start' def scale(self, input, input_min, input_max, output_min, output_max): range_input = input_max - input_min range_output = output_max - output_min result = (input - input_min) * range_output / range_input + output_min if (input_min > input_max and output_min > output_max) or \ (output_min > output_max and input_min < input_max): if result > output_min: return output_min elif result < output_max: return output_max else: return result if (input_min < input_max and output_min < output_max) or \ (output_min < output_max and input_min > input_max): if result > output_max: return output_max elif result < output_min: return output_min else: return result def handleComplexityChange(self, widget, event): self.regularity = self.complexity_button.get_value() img = int(self.complexity_button.get_value() * 7) + 1 self.complexity_button.set_image( imagefile('complex' + str(img) + '.svg')) self.beatPickup = False self.regenerate() self.beatPickup = True """ def handleBalanceSlider(self, adj): self.instVolume = int(adj.get_value()) self.drumVolume = sqrt( (100-self.instVolume)*0.01 ) self.adjustDrumVolume() self.drumFillin.setVolume(self.drumVolume) instrumentVolume = sqrt( self.instVolume*0.01 ) self.loop.adjustLoopVolume(instrumentVolume) self.sequencer.adjustSequencerVolume(instrumentVolume) img = int(self.scale(self.instVolume,100,0,0,4.9)) self._playToolbar.balanceSliderImgLeft.set_from_file( imagefile('dru' + str(img) + '.png')) img2 = int(self.scale(self.instVolume,0,100,0,4.9)) self._playToolbar.balanceSliderImgRight.set_from_file( imagefile('instr' + str(img2) + '.png')) def handleReverbSlider(self, adj): self.reverb = adj.get_value() self.drumFillin.setReverb( self.reverb ) img = int(self.scale(self.reverb,0,1,0,4)) self._playToolbar.reverbSliderImgRight.set_from_file( imagefile('reverb' + str(img) + '.png')) self.keyboardStandAlone.setReverb(self.reverb) """ def set_keyboard_no_labels_cb(self, widget): self.piano.font_size = 25 self.piano.set_labels(None) def enableKeyboard(self): self.keyboardStandAlone = KeyboardStandAlone( self.sequencer.recording, self.sequencer.adjustDuration, self.csnd.loopGetTick, self.sequencer.getPlayState, self.loop) self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) def setInstrument(self, instrument): logging.debug("Set Instrument: %s" % instrument) self.instrument = instrument self.keyboardStandAlone.setInstrument(instrument) self.csnd.load_instrument(instrument) def recordStateButton(self, button, state): pass # if button == 1: # self._recordToolbar.keyboardRecButton.set_active( state ) # else: # self._recordToolbar.keyboardRecOverButton.set_active( state ) def recordOverSensitivity(self, state): pass # self._recordToolbar.keyboardRecOverButton.set_sensitive( state ) def _play_recorded_keys(self, end_cb=None): GLib.source_remove(self.play_recording_thread) letter = self.recorded_keys[self.play_index] time_difference = 0 if self.play_index == len(self.recorded_keys) - 1: time_difference = \ self.recorded_keys[self.play_index][0] - \ self.recorded_keys[self.play_index - 1][0] else: next_time = self.recorded_keys[self.play_index + 1][0] time_difference = next_time - letter[0] if not self.playing_recording: self.play_recording_button.props.icon_name = 'media-playback-start' return if letter[-1] == 1: self.keyboardStandAlone.do_key_release( LETTERS_TO_KEY_CODES[letter[3]]) GLib.idle_add(self.piano.physical_key_changed, LETTERS_TO_KEY_CODES[letter[3]], False) else: self.keyboardStandAlone.do_key_press( LETTERS_TO_KEY_CODES[letter[3]], None, math.sqrt(self.instVolume * 0.01)) GLib.idle_add(self.piano.physical_key_changed, LETTERS_TO_KEY_CODES[letter[3]], True) if self.play_index == len(self.recorded_keys) - 1: self.play_index = 0 self.play_recording_button.props.icon_name = 'media-playback-start' self.playing_recording = False if end_cb is not None: end_cb() else: self.play_index += 1 self.play_recording_thread = \ GLib.timeout_add(int((time_difference) * 1000), self._play_recorded_keys, end_cb) def __key_pressed_cb(self, widget, octave_clicked, key_clicked, letter, physicallKey): logging.debug( 'Pressed Octave: %d Key: %d Letter: %s' % (octave_clicked, key_clicked, letter)) if letter in LETTERS_TO_KEY_CODES.keys(): if self.recording: self.recorded_keys.append( [time.time(), octave_clicked, key_clicked, letter]) if not physicallKey: self.keyboardStandAlone.do_key_press( LETTERS_TO_KEY_CODES[letter], None, math.sqrt(self.instVolume * 0.01)) def __key_released_cb(self, widget, octave_clicked, key_clicked, letter, physicallKey): if self.recording: self.recorded_keys.append( [time.time(), octave_clicked, key_clicked, letter, 1]) if not physicallKey: if letter in LETTERS_TO_KEY_CODES.keys(): self.keyboardStandAlone.do_key_release( LETTERS_TO_KEY_CODES[letter]) def onKeyPress(self, widget, event): if event.state & Gdk.ModifierType.CONTROL_MASK: return if event.hardware_keycode == 37: if self.muteInst: self.muteInst = False else: self.muteInst = True self.piano.physical_key_changed(event.hardware_keycode, True) self.keyboardStandAlone.onKeyPress( widget, event, math.sqrt(self.instVolume * 0.01)) def onKeyRelease(self, widget, event): self.keyboardStandAlone.onKeyRelease(widget, event) self.piano.physical_key_changed(event.hardware_keycode, False) def write_file(self, file_path): f = open(file_path, 'w') # substract the initial time to all the saved values if len(self.recorded_keys) > 0: initial_time = self.recorded_keys[0][0] for key in self.recorded_keys: key[0] = key[0] - initial_time f.write(json.dumps(self.recorded_keys)) f.close() def read_file(self, file_path): f = open(file_path, 'r') contents = f.read().strip() self.recorded_keys = json.loads(contents) if len(self.recorded_keys) != 0: self.play_recording_button.set_sensitive(True) self._save_as_audio_bt.set_sensitive(True) f.close() def close(self, skip_save=False): self.csnd.stop() # without which Csound will segfault activity.Activity.close(self, skip_save=skip_save)
class miniTamTamMain(gtk.EventBox): def __init__(self, activity): gtk.EventBox.__init__(self) self.instrumentPanel = None self.activity = activity #self.set_border_width(Config.MAIN_WINDOW_PADDING) #self.set_border_width(0) self.instrumentDB = InstrumentDB.getRef() self.firstTime = False self.playing = False self.csnd = new_csound_client() self.timeout_ms = 50 self.instVolume = 50 self.drumVolume = 0.5 self.instrument = 'sarangi' self.regularity = 0.75 self.beat = 4 self.reverb = 0.1 self.tempo = Config.PLAYER_TEMPO self.beatDuration = 60.0 / self.tempo self.ticksPerSecond = Config.TICKS_PER_BEAT * self.tempo / 60.0 self.rythmInstrument = 'drum1kit' self.csnd.load_drumkit(self.rythmInstrument) self.muteInst = False self.drumFillin = Fillin(self.beat, self.tempo, self.rythmInstrument, self.reverb, self.drumVolume) self.sequencer = MiniSequencer(self.recordStateButton, self.recordOverSensitivity) self.loop = Loop(self.beat, sqrt(self.instVolume * 0.01)) self.csnd.setTempo(self.tempo) self.noteList = [] time.sleep(0.001) # why? self.trackpad = Trackpad(self) for i in range(21): self.csnd.setTrackVolume(100, i) for i in range(10): r = str(i + 1) self.csnd.load_instrument('guidice' + r) self.volume = 100 self.csnd.setMasterVolume(self.volume) self.sequencer.beat = self.beat self.loop.beat = self.beat self.tooltips = gtk.Tooltips() self.mainWindowBox = gtk.HBox() self.leftBox = gtk.VBox() self.rightBox = gtk.VBox() self.mainWindowBox.pack_start(self.rightBox, False, True) self.mainWindowBox.pack_start(self.leftBox, True, True) self.add(self.mainWindowBox) self.enableKeyboard() self.setInstrument(self.instrument) self.connect('key-press-event', self.onKeyPress) self.connect('key-release-event', self.onKeyRelease) self.drawGeneration() self.show_all() if 'a good idea' == True: self.playStartupSound() self.beatPickup = True #self.regenerate() self.heartbeatStart = time.time() self.syncQueryStart = {} self.syncTimeout = None self.network = Net.Network() self.network.addWatcher(self.networkStatusWatcher) self.network.connectMessage(Net.HT_SYNC_REPLY, self.processHT_SYNC_REPLY) self.network.connectMessage(Net.HT_TEMPO_UPDATE, self.processHT_TEMPO_UPDATE) self.network.connectMessage(Net.PR_SYNC_QUERY, self.processPR_SYNC_QUERY) self.network.connectMessage(Net.PR_TEMPO_QUERY, self.processPR_TEMPO_QUERY) self.network.connectMessage(Net.PR_REQUEST_TEMPO_CHANGE, self.processPR_REQUEST_TEMPO_CHANGE) # data packing classes self.packer = xdrlib.Packer() self.unpacker = xdrlib.Unpacker("") #-- handle forced networking --------------------------------------- if self.network.isHost(): self.updateSync() self.syncTimeout = gobject.timeout_add(1000, self.updateSync) elif self.network.isPeer(): self.sendTempoQuery() self.syncTimeout = gobject.timeout_add(1000, self.updateSync) #------------------------------------------------------------------- # Toolbar if Config.HAVE_TOOLBOX: from sugar.graphics.toolbarbox import ToolbarButton # no sharing # self.max_participants = 1 self._playToolbar = playToolbar(self) ## Uncomment to show play and record tabs ## ''' play_toolbar_button = ToolbarButton(label=_('Play'), page=self._playToolbar, # Fixme: need an icon icon_name='activity-start') self._playToolbar.show() play_toolbar_button.show() self.activity.toolbox.toolbar.insert(play_toolbar_button, -1) self._recordToolbar = recordToolbar(self) record_toolbar_button = ToolbarButton(label=_('Record'), page=self._recordToolbar, # Fixme: need an icon icon_name='media-record') self._recordToolbar.show() record_toolbar_button.show() self.activity.toolbox.toolbar.insert(record_toolbar_button, -1) ''' else: self._playToolbar = playToolbar(self) ## set to 1 to show play and record tabs ## if 0: self._recordToolbar = recordToolbar(self) self.activity.toolbox.add_toolbar(_('Play'), self._playToolbar) self.activity.toolbox.add_toolbar(_('Record'), self._recordToolbar) self.activity.toolbox.set_current_toolbar(1) self._playToolbar.show() self._recordToolbar.show() if not Config.HAVE_TOOLBOX: self.activity.connect("shared", self.shared) if os.path.isfile("FORCE_SHARE"): # HOST r = random.random() #print "::::: Sharing as TTDBG%f :::::" % r #self.activity.set_title(_("TTDBG%f" % r)) print "::::: Sharing as TamTam :::::" self.activity.set_title(_("TamTam")) self.activity.share() elif self.activity._shared_activity: # PEER self.activity._shared_activity.connect("buddy-joined", self.buddy_joined) self.activity._shared_activity.connect("buddy-left", self.buddy_left) self.activity.connect("joined", self.joined) self.network.setMode(Net.MD_WAIT) #self.activity.activity_toolbar.share.hide() def drawGeneration(self): slidersBox = RoundVBox(fillcolor=Config.PANEL_COLOR, bordercolor=Config.PANEL_BCK_COLOR, radius=Config.PANEL_RADIUS) slidersBox.set_border_width(Config.PANEL_SPACING) geneSliderBox = gtk.VBox() self.geneSliderBoxImgTop = gtk.Image() self.geneSliderBoxImgTop.set_from_file(imagefile('complex6.png')) self.geneAdjustment = gtk.Adjustment(value=self.regularity, lower=0, upper=1, step_incr=0.01, page_incr=0, page_size=0) self.geneSlider = ImageVScale('sliderbutbleu.png', self.geneAdjustment, 5) self.geneSlider.set_inverted(False) self.geneAdjustment.connect("value_changed", self.handleGenerationSlider) self.geneSlider.connect("button-release-event", self.handleGenerationSliderRelease) geneSliderBox.pack_start(self.geneSliderBoxImgTop, False, padding=10) geneSliderBox.pack_start(self.geneSlider, True, 20) self.tooltips.set_tip(self.geneSlider, Tooltips.COMPL) beatSliderBox = gtk.VBox() self.beatSliderBoxImgTop = gtk.Image() self.beatSliderBoxImgTop.set_from_file(imagefile('beat3.png')) self.beatAdjustment = gtk.Adjustment(value=self.beat, lower=2, upper=12, step_incr=1, page_incr=0, page_size=0) self.beatSlider = ImageVScale('sliderbutjaune.png', self.beatAdjustment, 5, snap=1) self.beatSlider.set_inverted(True) self.beatAdjustment.connect("value_changed", self.handleBeatSlider) self.beatSlider.connect("button-release-event", self.handleBeatSliderRelease) beatSliderBox.pack_start(self.beatSliderBoxImgTop, False, padding=10) beatSliderBox.pack_start(self.beatSlider, True, 20) self.tooltips.set_tip(self.beatSlider, Tooltips.BEAT) self.delayedTempo = 0 # used to store tempo updates while the slider is active self.tempoSliderActive = False tempoSliderBox = gtk.VBox() self.tempoSliderBoxImgTop = gtk.Image() self.tempoSliderBoxImgTop.set_from_file(imagefile('tempo5.png')) self.tempoAdjustment = gtk.Adjustment(value=self.tempo, lower=Config.PLAYER_TEMPO_LOWER, upper=Config.PLAYER_TEMPO_UPPER, step_incr=1, page_incr=1, page_size=1) tempoSlider = ImageVScale('sliderbutvert.png', self.tempoAdjustment, 5) tempoSlider.set_inverted(True) self.tempoAdjustmentHandler = self.tempoAdjustment.connect( "value_changed", self.handleTempoSliderChange) tempoSlider.connect("button-press-event", self.handleTempoSliderPress) tempoSlider.connect("button-release-event", self.handleTempoSliderRelease) tempoSliderBox.pack_start(self.tempoSliderBoxImgTop, False, padding=10) tempoSliderBox.pack_start(tempoSlider, True) self.tooltips.set_tip(tempoSlider, Tooltips.TEMPO) volumeSliderBox = gtk.VBox() self.volumeSliderBoxImgTop = gtk.Image() self.volumeSliderBoxImgTop.set_from_file(imagefile('volume2.png')) self.volumeAdjustment = gtk.Adjustment(value=self.volume, lower=0, upper=200, step_incr=1, page_incr=1, page_size=1) volumeSlider = ImageVScale('sliderbutbleu.png', self.volumeAdjustment, 5) volumeSlider.set_inverted(True) self.volumeAdjustment.connect("value_changed", self.handleVolumeSlider) #volumeSlider.connect("button-release-event", self.handleVolumeSliderRelease) volumeSliderBox.pack_start(self.volumeSliderBoxImgTop, False, padding=10) volumeSliderBox.pack_start(volumeSlider, True) self.tooltips.set_tip(volumeSlider, Tooltips.VOL) slidersBoxSub = gtk.HBox() slidersBoxSub.pack_start(beatSliderBox) slidersBoxSub.pack_start(geneSliderBox) slidersBoxSub.pack_start(tempoSliderBox) slidersBoxSub.pack_start(volumeSliderBox) slidersBox.pack_start(slidersBoxSub) generateBtnSub = RoundHBox(fillcolor=Config.PANEL_COLOR, bordercolor=Config.PANEL_BCK_COLOR, radius=Config.PANEL_RADIUS) generateBtnSub.set_border_width(Config.PANEL_SPACING) #playImg = gtk.Image() #playImg.set_from_icon_name('media-playback-start', gtk.ICON_SIZE_LARGE_TOOLBAR) self.playButton = ImageToggleButton('miniplay.png', 'stop.png') #self.playButton.set_relief(gtk.RELIEF_NONE) #self.playButton.set_image(playImg) self.playButton.connect('clicked', self.handlePlayButton) generateBtnSub.pack_start(self.playButton) #self.playButton.set_tooltip(_('Play / Stop')) generateBtn = ImageButton('dice.png', clickImg_path='diceblur.png') generateBtn.connect('button-press-event', self.handleGenerateBtn) generateBtnSub.pack_start(generateBtn) self.tooltips.set_tip(generateBtn, Tooltips.GEN) # drums drum_box = RoundVBox(fillcolor=Config.PANEL_COLOR, bordercolor=Config.PANEL_BCK_COLOR, radius=Config.PANEL_RADIUS) drum_scroll = VScrolledBox(scroll_policy=gtk.POLICY_NEVER) drum_scroll.set_viewport(drum_box) drum_scroll.modify_bg( gtk.STATE_NORMAL, style.Color(Config.PANEL_BCK_COLOR).get_gdk_color()) drum_i = 0 drum_group = None for row in range(DRUMCOUNT / 2 + DRUMCOUNT % 2): row_box = gtk.HBox() drum_box.pack_start(row_box, False) for col in range(2): if drum_i >= DRUMCOUNT: break drum = ImageRadioButton( group=drum_group, mainImg_path='drum%dkit.png' % (drum_i + 1), altImg_path='drum%dkitselgen.png' % (drum_i + 1)) drum.connect('clicked', self.handleGenerationDrumBtn, 'drum%dkit' % (drum_i + 1)) row_box.pack_start(drum) drum_name = 'drum%dkit' % (drum_i + 1) hint = self.instrumentDB.instNamed[drum_name].nameTooltip self.tooltips.set_tip(drum, hint) if not drum_group: drum_group = drum drum_i += 1 self.rightBox.pack_start(slidersBox, False) self.rightBox.pack_start(generateBtnSub, False) self.rightBox.pack_start(drum_scroll) drum_size = drum_group.get_size_request() slidersBox.set_size_request(-1, int(drum_size[1] * 2.3)) self.rightBox.set_size_request(int(drum_size[0] * 2.05), -1) def loopSettingsChannel(self, channel, value): self.csnd.setChannel(channel, value) def loopSettingsPlayStop(self, state, loop): if not state: if loop: self.loopSettingsPlaying = True self.csnd.inputMessage(Config.CSOUND_PLAY_LS_NOTE % 5022) else: self.csnd.inputMessage(Config.CSOUND_PLAY_LS_NOTE % 5023) else: if loop: self.loopSettingsPlaying = False self.csnd.inputMessage(Config.CSOUND_STOP_LS_NOTE) def load_ls_instrument(self, soundName): self.csnd.load_ls_instrument(soundName) def updateInstrumentPanel(self): if self.instrumentPanel is None: self.instrumentPanel = InstrumentPanel() self.leftBox.pack_start(self.instrumentPanel) width = gtk.gdk.screen_width() - self.rightBox.get_size_request()[0] self.instrumentPanel.configure(self.setInstrument, self.playInstrumentNote, False, self.micRec, width=width) self.instrumentPanel.load() def micRec(self, widget, mic): self.csnd.inputMessage("i5600 0 4") OS.arecord(4, "crop.csd", mic) self.micTimeout = gobject.timeout_add(200, self.loadMicInstrument, mic) self.instrumentPanel.set_activeInstrument(mic, True) self.setInstrument(mic) def recordStateButton(self, button, state): if button == 1: self._recordToolbar.keyboardRecButton.set_active(state) else: self._recordToolbar.keyboardRecOverButton.set_active(state) def recordOverSensitivity(self, state): pass #self._recordToolbar.keyboardRecOverButton.set_sensitive( state ) def loadMicInstrument(self, data): self.csnd.load_mic_instrument(data) def regenerate(self): def flatten(ll): rval = [] for l in ll: rval += l return rval if self.beatPickup: self.pickupNewBeat() noteOnsets = [] notePitchs = [] i = 0 self.noteList = [] self.csnd.loopClear() for x in flatten( generator(self.rythmInstrument, self.beat, 0.8, self.regularity, self.reverb)): x.amplitude = x.amplitude * self.drumVolume noteOnsets.append(x.onset) notePitchs.append(x.pitch) n = Note(0, x.trackId, i, x) self.noteList.append((x.onset, n)) i = i + 1 self.csnd.loopPlay(n, 1) #add as active self.csnd.loopSetNumTicks(self.beat * Config.TICKS_PER_BEAT) self.drumFillin.unavailable(noteOnsets, notePitchs) self.recordOverSensitivity(False) if self.playing: self.csnd.loopStart() def adjustDrumVolume(self): for n in self.noteList: self.csnd.loopUpdate(n[1], PARAMETER.AMPLITUDE, n[1].cs.amplitude * self.drumVolume, 1) def handleClose(self, widget): if self.playStopButton.get_active() == True: self.playStopButton.set_active(False) self.sequencer.clearSequencer() self.csnd.loopClear() self.activity.close() def handleGenerationSlider(self, adj): img = int(adj.value * 7) + 1 self.geneSliderBoxImgTop.set_from_file( imagefile('complex' + str(img) + '.png')) def handleGenerationSliderRelease(self, widget, event): self.regularity = widget.get_adjustment().value self.beatPickup = False self.regenerate() self.beatPickup = True def pickupNewBeat(self): self.beat = random.randint(2, 12) img = self.scale(self.beat, 2, 12, 1, 11) self.beatSliderBoxImgTop.set_from_file( imagefile('beat' + str(img) + '.png')) self.beatAdjustment.set_value(self.beat) self.regularity = random.randint(50, 100) * 0.01 img = int(self.regularity * 7) + 1 self.geneSliderBoxImgTop.set_from_file( imagefile('complex' + str(img) + '.png')) self.geneAdjustment.set_value(self.regularity) self.sequencer.beat = self.beat self.loop.beat = self.beat self.drumFillin.setBeats(self.beat) def handleBeatSlider(self, adj): img = self.scale(int(adj.value), 2, 12, 1, 11) self.beatSliderBoxImgTop.set_from_file( imagefile('beat' + str(img) + '.png')) self.sequencer.beat = self.beat self.loop.beat = self.beat self.drumFillin.setBeats(self.beat) def handleBeatSliderRelease(self, widget, event): self.beat = int(widget.get_adjustment().value) self.sequencer.beat = self.beat self.loop.beat = self.beat self.drumFillin.setBeats(self.beat) self.beatPickup = False self.regenerate() self.beatPickup = True def handleTempoSliderPress(self, widget, event): self.tempoSliderActive = True def handleTempoSliderRelease(self, widget, event): self.tempoSliderActive = False if self.network.isPeer() and self.delayedTempo != 0: if self.tempo != self.delayedTempo: self.tempoAdjustment.handler_block(self.tempoAdjustmentHandler) self.tempoAdjustment.set_value(self.delayedTempo) self._updateTempo(self.delayedTempo) self.tempoAdjustment.handler_unblock( self.tempoAdjustmentHandler) self.delayedTempo = 0 self.sendSyncQuery() def handleTempoSliderChange(self, adj): if self.network.isPeer(): self.requestTempoChange(int(adj.value)) else: self._updateTempo(int(adj.value)) def _updateTempo(self, val): if self.network.isHost(): t = time.time() percent = self.heartbeatElapsed() / self.beatDuration self.tempo = val self.beatDuration = 60.0 / self.tempo self.ticksPerSecond = Config.TICKS_PER_BEAT * self.tempo / 60.0 self.csnd.setTempo(self.tempo) self.sequencer.tempo = self.tempo self.drumFillin.setTempo(self.tempo) if self.network.isHost(): self.heatbeatStart = t - percent * self.beatDuration self.updateSync() self.sendTempoUpdate() img = int( self.scale(self.tempo, Config.PLAYER_TEMPO_LOWER, Config.PLAYER_TEMPO_UPPER, 1, 9)) self.tempoSliderBoxImgTop.set_from_file( imagefile('tempo' + str(img) + '.png')) def handleBalanceSlider(self, adj): self.instVolume = int(adj.value) self.drumVolume = sqrt((100 - self.instVolume) * 0.01) self.adjustDrumVolume() self.drumFillin.setVolume(self.drumVolume) instrumentVolume = sqrt(self.instVolume * 0.01) self.loop.adjustLoopVolume(instrumentVolume) self.sequencer.adjustSequencerVolume(instrumentVolume) img = int(self.scale(self.instVolume, 100, 0, 0, 4.9)) self._playToolbar.balanceSliderImgLeft.set_from_file( imagefile('dru' + str(img) + '.png')) img2 = int(self.scale(self.instVolume, 0, 100, 0, 4.9)) self._playToolbar.balanceSliderImgRight.set_from_file( imagefile('instr' + str(img2) + '.png')) def handleReverbSlider(self, adj): self.reverb = adj.value self.drumFillin.setReverb(self.reverb) img = int(self.scale(self.reverb, 0, 1, 0, 4)) self._playToolbar.reverbSliderImgRight.set_from_file( imagefile('reverb' + str(img) + '.png')) self.keyboardStandAlone.setReverb(self.reverb) def handleVolumeSlider(self, adj): self.volume = adj.value self.csnd.setMasterVolume(self.volume) img = int(self.scale(self.volume, 0, 200, 0, 3.9)) self.volumeSliderBoxImgTop.set_from_file( imagefile('volume' + str(img) + '.png')) def handlePlayButton(self, widget, data=None): # use widget.get_active() == False when calling this on 'clicked' # use widget.get_active() == True when calling this on button-press-event if widget.get_active() == False: self.drumFillin.stop() self.sequencer.stopPlayback() self.csnd.loopPause() self.playing = False else: if not self.firstTime: self.regenerate() self.firstTime = True self.drumFillin.play() #self.csnd.loopSetTick(0) nextInTicks = self.nextHeartbeatInTicks() #print "play:: next beat in %f ticks. bpb == %d. setting ticks to %d" % (nextInTicks, self.beat, Config.TICKS_PER_BEAT*self.beat - int(round(nextInTicks))) self.csnd.loopSetTick(Config.TICKS_PER_BEAT * self.beat - int(round(nextInTicks))) self.csnd.loopStart() self.playing = True def handleGenerationDrumBtn(self, widget, data): #data is drum1kit, drum2kit, or drum3kit #print 'HANDLE: Generate Button' self.rythmInstrument = data self.csnd.load_drumkit(data) instrumentId = self.instrumentDB.instNamed[data].instrumentId for (o, n) in self.noteList: self.csnd.loopUpdate(n, NoteDB.PARAMETER.INSTRUMENT, instrumentId, -1) self.drumFillin.setInstrument(self.rythmInstrument) def handleGenerateBtn(self, widget, data=None): self.regenerate() if not self.playButton.get_active(): self.playButton.set_active(True) #this calls sends a 'clicked' event, #which might be connected to handlePlayButton self.playStartupSound() def enableKeyboard(self): self.keyboardStandAlone = KeyboardStandAlone( self.sequencer.recording, self.sequencer.adjustDuration, self.csnd.loopGetTick, self.sequencer.getPlayState, self.loop) self.add_events(gtk.gdk.BUTTON_PRESS_MASK) def setInstrument(self, instrument): self.instrument = instrument self.keyboardStandAlone.setInstrument(instrument) self.csnd.load_instrument(instrument) def playInstrumentNote(self, instrument, secs_per_tick=0.025): if not self.muteInst: self.csnd.play( CSoundNote(onset=0, pitch=36, amplitude=1, pan=0.5, duration=20, trackId=1, instrumentId=self.instrumentDB. instNamed[instrument].instrumentId, reverbSend=self.reverb, tied=False, mode='mini'), secs_per_tick) def onKeyPress(self, widget, event): if event.hardware_keycode == 219: #'/*' button to reset drum loop if self.playStopButton.get_active() == True: self.handlePlayButton(self.playStopButton) self.playStopButton.set_active(False) self.handlePlayButton(self.playStopButton) self.playStopButton.set_active(True) if event.hardware_keycode == 37: if self.muteInst: self.muteInst = False else: self.muteInst = True if event.hardware_keycode == 65: #what key is this? what feature is this? pass #if self.playStopButton.get_active(): #self.playStopButton.set_active(False) #else: #self.playStopButton.set_active(True) self.keyboardStandAlone.onKeyPress(widget, event, sqrt(self.instVolume * 0.01)) def onKeyRelease(self, widget, event): self.keyboardStandAlone.onKeyRelease(widget, event) def playStartupSound(self): r = str(random.randrange(1, 11)) self.playInstrumentNote('guidice' + r) def onActivate(self, arg): self.csnd.loopPause() self.csnd.loopClear() def onDeactivate(self): SubActivity.onDeactivate(self) self.csnd.loopPause() self.csnd.loopClear() def onDestroy(self): self.network.shutdown() def scale(self, input, input_min, input_max, output_min, output_max): range_input = input_max - input_min range_output = output_max - output_min result = (input - input_min) * range_output / range_input + output_min if (input_min > input_max and output_min > output_max) or (output_min > output_max and input_min < input_max): if result > output_min: return output_min elif result < output_max: return output_max else: return result if (input_min < input_max and output_min < output_max) or (output_min < output_max and input_min > input_max): if result > output_max: return output_max elif result < output_min: return output_min else: return result #----------------------------------------------------------------------- # Network #-- Activity ----------------------------------------------------------- def shared(self, activity): if Config.DEBUG: print "miniTamTam:: successfully shared, start host mode" self.activity._shared_activity.connect("buddy-joined", self.buddy_joined) self.activity._shared_activity.connect("buddy-left", self.buddy_left) self.network.setMode(Net.MD_HOST) self.updateSync() self.syncTimeout = gobject.timeout_add(1000, self.updateSync) def joined(self, activity): print "miniTamTam:: joined activity!!" for buddy in self.activity._shared_activity.get_joined_buddies(): print buddy.props.ip4_address def buddy_joined(self, activity, buddy): print "buddy joined " + str(buddy) try: print buddy.props.ip4_address except: print "bad ip4_address" if self.network.isHost(): # TODO how do I figure out if this buddy is me? if buddy.props.ip4_address: self.network.introducePeer(buddy.props.ip4_address) else: print "miniTamTam:: new buddy does not have an ip4_address!!" def buddy_left(self, activity, buddy): print "buddy left" #def joined( self, activity ): # if Config.DEBUG: print "miniTamTam:: successfully joined, wait for host" # self.net.waitForHost() #-- Senders ------------------------------------------------------------ def sendSyncQuery(self): self.packer.pack_float(random.random()) hash = self.packer.get_buffer() self.packer.reset() self.syncQueryStart[hash] = time.time() self.network.send(Net.PR_SYNC_QUERY, hash) def sendTempoUpdate(self): self.packer.pack_int(self.tempo) self.network.sendAll(Net.HT_TEMPO_UPDATE, self.packer.get_buffer()) self.packer.reset() def sendTempoQuery(self): self.network.send(Net.PR_TEMPO_QUERY) def requestTempoChange(self, val): self.packer.pack_int(val) self.network.send(Net.PR_REQUEST_TEMPO_CHANGE, self.packer.get_buffer()) self.packer.reset() #-- Handlers ----------------------------------------------------------- def networkStatusWatcher(self, mode): if mode == Net.MD_OFFLINE: if self.syncTimeout: gobject.source_remove(self.syncTimeout) self.syncTimeout = None if mode == Net.MD_PEER: self.updateSync() if not self.syncTimeout: self.syncTimeout = gobject.timeout_add(1000, self.updateSync) self.sendTempoQuery() def processHT_SYNC_REPLY(self, sock, message, data): t = time.time() hash = data[0:4] latency = t - self.syncQueryStart[hash] self.unpacker.reset(data[4:8]) nextBeat = self.unpacker.unpack_float() #print "mini:: got sync: next beat in %f, latency %d" % (nextBeat, latency*1000) self.heartbeatStart = t + nextBeat - self.beatDuration - latency / 2 self.correctSync() self.syncQueryStart.pop(hash) def processHT_TEMPO_UPDATE(self, sock, message, data): self.unpacker.reset(data) val = self.unpacker.unpack_int() if self.tempoSliderActive: self.delayedTempo = val return self.tempoAdjustment.handler_block(self.tempoAdjustmentHandler) self.tempoAdjustment.set_value(val) self._updateTempo(val) self.tempoAdjustment.handler_unblock(self.tempoAdjustmentHandler) self.sendSyncQuery() def processPR_SYNC_QUERY(self, sock, message, data): self.packer.pack_float(self.nextHeartbeat()) self.network.send(Net.HT_SYNC_REPLY, data + self.packer.get_buffer(), sock) self.packer.reset() def processPR_TEMPO_QUERY(self, sock, message, data): self.packer.pack_int(self.tempo) self.network.send(Net.HT_TEMPO_UPDATE, self.packer.get_buffer(), to=sock) self.packer.reset() def processPR_REQUEST_TEMPO_CHANGE(self, sock, message, data): if self.tempoSliderActive: return self.unpacker.reset(data) val = self.unpacker.unpack_int() self.tempoAdjustment.set_value(val) #----------------------------------------------------------------------- # Sync def nextHeartbeat(self): delta = time.time() - self.heartbeatStart return self.beatDuration - (delta % self.beatDuration) def nextHeartbeatInTicks(self): delta = time.time() - self.heartbeatStart next = self.beatDuration - (delta % self.beatDuration) return self.ticksPerSecond * next def heartbeatElapsed(self): delta = time.time() - self.heartbeatStart return delta % self.beatDuration def heartbeatElapsedTicks(self): delta = time.time() - self.heartbeatStart return self.ticksPerSecond * (delta % self.beatDuration) def updateSync(self): if self.network.isOffline(): return False elif self.network.isWaiting(): return True elif self.network.isHost(): self.correctSync() else: self.sendSyncQuery() return True def correctSync(self): curTick = self.csnd.loopGetTick() curTicksIn = curTick % Config.TICKS_PER_BEAT ticksIn = self.heartbeatElapsedTicks() err = curTicksIn - ticksIn if err > Config.TICKS_PER_BEAT_DIV2: err -= Config.TICKS_PER_BEAT elif err < -Config.TICKS_PER_BEAT_DIV2: err += Config.TICKS_PER_BEAT correct = curTick - err ticksPerLoop = Config.TICKS_PER_BEAT * self.beat if correct > ticksPerLoop: correct -= ticksPerLoop elif correct < 0: correct += ticksPerLoop #print "correct:: %f ticks, %f ticks in, %f expected, %f err, correct %f" % (curTick, curTicksIn, ticksIn, err, correct) if abs(err) > 0.25: self.csnd.adjustTick(-err)
class JamMain(Gtk.EventBox): def __init__(self, activity): GObject.GObject.__init__(self) self.activity = activity self.instrumentDB = InstrumentDB.getRef() self.noteDB = NoteDB.NoteDB() #-- initial settings ---------------------------------- self.tempo = Config.PLAYER_TEMPO self.beatDuration = 60.0 / self.tempo self.ticksPerSecond = Config.TICKS_PER_BEAT * self.tempo / 60.0 self.volume = 0.5 self.csnd = new_csound_client() for i in range(0, 9): self.csnd.setTrackVolume(100, i) # csnd expects a range 0-100 for now self.csnd.setMasterVolume(self.volume * 100) self.csnd.setTempo(self.tempo) self.muted = False #-- Drawing ------------------------------------------- def darken(hex): hexToDec = { "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "A": 10, "B": 11, "C": 12, "D": 13, "E": 14, "F": 15, "a": 10, "b": 11, "c": 12, "d": 13, "e": 14, "f": 15 } r = int(0.7 * (16 * hexToDec[hex[1]] + hexToDec[hex[2]])) g = int(0.7 * (16 * hexToDec[hex[3]] + hexToDec[hex[4]])) b = int(0.7 * (16 * hexToDec[hex[5]] + hexToDec[hex[6]])) return r * 256, g * 256, b * 256 def lighten(hex): hexToDec = { "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "A": 10, "B": 11, "C": 12, "D": 13, "E": 14, "F": 15, "a": 10, "b": 11, "c": 12, "d": 13, "e": 14, "f": 15 } r = 255 - int(0.7 * (255 - (16 * hexToDec[hex[1]] + hexToDec[hex[2]]))) g = 255 - int(0.7 * (255 - (16 * hexToDec[hex[3]] + hexToDec[hex[4]]))) b = 255 - int(0.7 * (255 - (16 * hexToDec[hex[5]] + hexToDec[hex[6]]))) return r * 256, g * 256, b * 256 xoColor = profile.get_color() if not xoColor: xoColorKey = ("#8D8D8D,#FFDDEA") xoColor = XoColor(xoColorKey) # colors in Config and in XoColor are strings, # the colors in style are style.Color, transform all to Gdk.Color self.colors = {"bg": CairoUtil.get_gdk_color(Config.PANEL_BCK_COLOR), "black": style.COLOR_BLACK.get_gdk_color(), #"Picker_Bg": colormap.alloc_color("#404040"), #"Picker_Bg_Inactive": colormap.alloc_color("#808080"), "Picker_Bg": style.COLOR_TOOLBAR_GREY.get_gdk_color(), "Picker_Bg_Inactive": style.COLOR_BUTTON_GREY.get_gdk_color(), "Picker_Fg": style.COLOR_WHITE.get_gdk_color(), "Border_Active": \ CairoUtil.get_gdk_color(xoColor.get_stroke_color()), "Border_Inactive": CairoUtil.get_gdk_color("#8D8D8D"), "Border_Highlight": CairoUtil.get_gdk_color("#FFFFFF"), "Bg_Active": CairoUtil.get_gdk_color(xoColor.get_fill_color()), "Bg_Inactive": CairoUtil.get_gdk_color("#DBDBDB"), "Preview_Note_Fill": CairoUtil.get_gdk_color(Config.BG_COLOR), "Preview_Note_Border": CairoUtil.get_gdk_color(Config.FG_COLOR), "Preview_Note_Selected": style.COLOR_WHITE.get_gdk_color(), # TODO: lighten here can be removed, check if is used in other # places "Note_Fill_Active": Gdk.Color(*lighten("#590000")), # base "Border_Active" "Note_Fill_Inactive": Gdk.Color(*lighten("#8D8D8D")), # base "Border_Inactive" "Beat_Line": CairoUtil.get_gdk_color("#959595")} self.colors["Note_Border_Active"] = self.colors["Border_Active"] self.colors["Note_Border_Inactive"] = self.colors["Border_Inactive"] self.sampleNoteHeight = 7 self.sampleBg = cairo.ImageSurface.create_from_png( imagefile('sampleBG.png')) self.loopPitchOffset = 4 self.loopTickOffset = 13 self.pitchPerPixel = float(Config.NUMBER_OF_POSSIBLE_PITCHES - 1) / \ (Block.Loop.HEIGHT - 2 * self.loopPitchOffset - \ self.sampleNoteHeight) self.pixelsPerPitch = float(Block.Loop.HEIGHT - \ 2 * self.loopPitchOffset - self.sampleNoteHeight) / \ (Config.MAXIMUM_PITCH - Config.MINIMUM_PITCH) self.pixelsPerTick = Block.Loop.BEAT / float(Config.TICKS_PER_BEAT) self.ticksPerPixel = 1.0 / self.pixelsPerTick #-- Instruments --------------------------------------- self.instrumentImage = {} self.instrumentImageActive = {} for inst in self.instrumentDB.getSet("All"): if not inst.kitStage: self.prepareInstrumentImage(inst.instrumentId, inst.img) self.csnd.load_instrument(inst.name) #-- Loop Images --------------------------------------- self.loopImage = {} # get filled in through updateLoopImage self.loopImageActive = {} #-- Key Images ---------------------------------------- self.keyImage = {} self.keyImageActive = {} # use hardware key codes to work on any keyboard layout (hopefully) self.valid_shortcuts = { 18: "9", 19: "0", 20: "-", 21: "=", 32: "O", 33: "P", 34: "[", 35: "]", 47: ";", 48: "'", 51: "\\", 60: ".", 61: "/", None: " " } for key in self.valid_shortcuts.keys(): self.prepareKeyImage(key) #-- Toolbars ------------------------------------------ self.jamToolbar = JamToolbar(self) jam_toolbar_button = ToolbarButton(label=_('Jam'), page=self.jamToolbar, icon_name='voltemp') self.jamToolbar.show() jam_toolbar_button.show() self.activity.toolbar_box.toolbar.insert(jam_toolbar_button, -1) self.beatToolbar = BeatToolbar(self) beat_toolbar_button = ToolbarButton(label=_('Beat'), page=self.beatToolbar, icon_name='heart') self.beatToolbar.show() beat_toolbar_button.show() self.activity.toolbar_box.toolbar.insert(beat_toolbar_button, -1) self.desktopToolbar = DesktopToolbar(self) desktop_toolbar_button = ToolbarButton(label=_('Desktop'), page=self.desktopToolbar, icon_name='jam-presets-list') self.desktopToolbar.show() desktop_toolbar_button.show() self.activity.toolbar_box.toolbar.insert(desktop_toolbar_button, -1) if Config.FEATURES_MIC or Config.FEATURES_NEWSOUNDS: self.recordToolbar = RecordToolbar(self) record_toolbar_button = ToolbarButton(label=_('Record'), page=self.recordToolbar, icon_name='microphone') self.recordToolbar.show() record_toolbar_button.show() self.activity.toolbar_box.toolbar.insert(record_toolbar_button, -1) separator = Gtk.SeparatorToolItem() separator.props.draw = True separator.set_expand(False) self.activity.toolbar_box.toolbar.insert(separator, -1) separator.show() common_playback_buttons(self.activity.toolbar_box.toolbar, self) #-- GUI ----------------------------------------------- if True: # GUI self.modify_bg(Gtk.StateType.NORMAL, self.colors["bg"]) self.GUI = {} self.GUI["mainVBox"] = Gtk.VBox() self.add(self.GUI["mainVBox"]) #-- Desktop ------------------------------------------- self.desktop = self.GUI["desktop"] = Desktop(self) self.GUI["mainVBox"].pack_start(self.GUI["desktop"], True, True, 0) #-- Bank ---------------------------------------------- separator = Gtk.Label(label=" ") separator.set_size_request(-1, style.TOOLBOX_SEPARATOR_HEIGHT) self.GUI["mainVBox"].pack_start(separator, False, True, 0) self.GUI["notebook"] = Gtk.Notebook() self.GUI["notebook"].set_scrollable(True) self.GUI["notebook"].modify_bg(Gtk.StateType.NORMAL, self.colors["Picker_Bg"]) self.GUI["notebook"].modify_bg(Gtk.StateType.ACTIVE, self.colors["Picker_Bg_Inactive"]) # TODO gtk3 no available anymore? #self.GUI["notebook"].props.tab_vborder = style.TOOLBOX_TAB_VBORDER #self.GUI["notebook"].props.tab_hborder = style.TOOLBOX_TAB_HBORDER self.GUI["notebook"].set_size_request(-1, scale(160)) self.GUI["notebook"].connect("switch-page", self.setPicker) self.GUI["mainVBox"].pack_start(self.GUI["notebook"], False, False, 0) self.pickers = {} self.pickerScroll = {} for type in [Picker.Instrument, Picker.Drum, Picker.Loop]: self.pickers[type] = type(self) def prepareLabel(name): label = Gtk.Label(label=Tooltips.categories.get(name) or name) label.set_alignment(0.0, 0.5) label.modify_fg(Gtk.StateType.NORMAL, self.colors["Picker_Fg"]) label.modify_fg(Gtk.StateType.ACTIVE, self.colors["Picker_Fg"]) return label self.GUI["notebook"].append_page(self.pickers[Picker.Drum], prepareLabel(_("Drum Kits"))) self.GUI["notebook"].append_page(self.pickers[Picker.Loop], prepareLabel(_("Loops"))) sets = self.instrumentDB.getLabels()[:] sets.sort() for set in sets: page = Gtk.HBox() page.set = set self.GUI["notebook"].append_page(page, prepareLabel(set)) self.show_all() self.GUI["notebook"].set_current_page(0) #-- Keyboard ------------------------------------------ self.key_dict = {} self.nextTrack = 2 self.keyboardListener = None self.recordingNote = None self.keyMap = {} # default instrument self._updateInstrument( self.instrumentDB.instNamed["kalimba"].instrumentId, 0.5) self.instrumentStack = [] # metronome page = NoteDB.Page(1, local=False) self.metronomePage = self.noteDB.addPage(-1, page) self.metronome = False #-- Drums --------------------------------------------- self.drumLoopId = None # use dummy values for now self.drumFillin = Fillin( 2, 100, self.instrumentDB.instNamed["drum1kit"].instrumentId, 0, 1) #-- Desktops ------------------------------------------ self.curDesktop = None # copy preset desktops path = Config.FILES_DIR + "/Desktops/" filelist = os.listdir(path) for file in filelist: shutil.copyfile(path + file, Config.TMP_DIR + '/' + file) #-- Network ------------------------------------------- self.network = Net.Network() self.network.addWatcher(self.networkStatusWatcher) self.network.connectMessage(Net.HT_SYNC_REPLY, self.processHT_SYNC_REPLY) self.network.connectMessage(Net.HT_TEMPO_UPDATE, self.processHT_TEMPO_UPDATE) self.network.connectMessage(Net.PR_SYNC_QUERY, self.processPR_SYNC_QUERY) self.network.connectMessage(Net.PR_TEMPO_QUERY, self.processPR_TEMPO_QUERY) self.network.connectMessage(Net.PR_REQUEST_TEMPO_CHANGE, self.processPR_REQUEST_TEMPO_CHANGE) # sync self.syncQueryStart = {} self.syncTimeout = None self.heartbeatLoop = self.csnd.loopCreate() self.syncBeats = 4 self.syncTicks = self.syncBeats * Config.TICKS_PER_BEAT self.offsetTicks = 0 # offset from the true heartbeat self.csnd.loopSetNumTicks(self.syncTicks * HEARTBEAT_BUFFER, self.heartbeatLoop) self.heartbeatStart = time.time() self.csnd.loopStart(self.heartbeatLoop) self.curBeat = 0 self.beatWheelTimeout = GObject.timeout_add(100, self.updateBeatWheel) # data packing classes self.packer = xdrlib.Packer() self.unpacker = xdrlib.Unpacker("") # handle forced networking if self.network.isHost(): self.updateSync() self.syncTimeout = GObject.timeout_add(1000, self.updateSync) elif self.network.isPeer(): self.sendTempoQuery() self.syncTimeout = GObject.timeout_add(1000, self.updateSync) self.activity.connect("shared", self.shared) if self.activity.shared_activity: # PEER self.activity.shared_activity.connect("buddy-joined", self.buddy_joined) self.activity.shared_activity.connect("buddy-left", self.buddy_left) self.activity.connect("joined", self.joined) self.network.setMode(Net.MD_WAIT) #-- Final Set Up -------------------------------------- self.setVolume(self.volume) self.setTempo(self.tempo) #self.activity.toolbar_box.set_current_toolbar(1) # JamToolbar self.setDesktop(0, True) #========================================================== def onActivate(self, arg): pass def onDeactivate(self): pass def onDestroy(self): self.network.shutdown() #clear up scratch folder path = Config.TMP_DIR filelist = os.listdir(path) for file in filelist: os.remove(path + '/' + file) #========================================================== # Playback def onKeyPress(self, widget, event): key = event.hardware_keycode if key in self.keyMap.keys(): activate = True for block in self.keyMap[key]: if block.isActive(): activate = False break if activate: for block in self.keyMap[key]: if not block.isActive(): if block.type == Block.Drum: self.desktop.activateDrum(block) elif block.type == Block.Loop: self.desktop.activateLoop(block) else: for block in self.keyMap[key]: if block.isActive(): if block.type == Block.Drum: self.desktop.deactivateDrum(block) elif block.type == Block.Loop: self.desktop.deactivateLoop(block) return if key in self.key_dict: # repeated press return if key in Config.KEY_MAP_PIANO: pitch = Config.KEY_MAP_PIANO[key] inst = self.instrumentDB.instId[self.instrument["id"]] if inst.kit: # drum kit if pitch in GenerationConstants.DRUMPITCH: pitch = GenerationConstants.DRUMPITCH[pitch] csnote = self._playNote( # trackVol * noteVol key, 36, self.instrument["amplitude"] * 0.5, self.instrument["pan"], 100, self.instrumentDB.instNamed[inst.kit[pitch]].instrumentId, self.instrument["reverb"]) else: if event.get_state() == Gdk.ModifierType.MOD1_MASK: pitch += 5 # Percussions resonance if inst.csoundInstrumentId == Config.INST_PERC: duration = 60 else: duration = -1 csnote = self._playNote( # trackVol * noteVol key, pitch, self.instrument["amplitude"] * 0.5, self.instrument["pan"], duration, self.instrument["id"], self.instrument["reverb"]) if self.keyboardListener: self.keyboardListener.recordNote(csnote.pitch) self.recordingNote = True def onKeyRelease(self, widget, event): key = event.hardware_keycode if key in self.key_dict: self._stopNote(key) if self.recordingNote: if self.keyboardListener: self.keyboardListener.finishNote() self.recordingNote = False def _playNote(self, key, pitch, amplitude, pan, duration, instrumentId, reverb): self.key_dict[key] = CSoundNote( # onset 0, pitch, amplitude, pan, duration, self.nextTrack, instrumentId, reverbSend=reverb, tied=True, mode='mini') self.nextTrack += 1 if self.nextTrack > 8: self.nextTrack = 2 self.csnd.play(self.key_dict[key], 0.3) return self.key_dict[key] def _stopNote(self, key): csnote = self.key_dict[key] if self.instrumentDB.instId[csnote.instrumentId].csoundInstrumentId \ == Config.INST_TIED: csnote.duration = .5 csnote.decay = 0.7 csnote.tied = False self.csnd.play(csnote, 0.3) del self.key_dict[key] def _updateInstrument(self, id, volume, pan=0, reverb=0): self.instrument = { "id": id, "amplitude": volume, "pan": pan, "reverb": reverb } def pushInstrument(self, instrument): self.instrumentStack.append(self.instrument) self.instrument = instrument def popInstrument(self): self.instrument = self.instrumentStack.pop() def _playDrum(self, id, pageId, volume, reverb, beats, regularity, loopId=None, sync=True): oldId = loopId loopId = self.csnd.loopCreate() noteOnsets = [] notePitchs = [] for n in self.noteDB.getNotesByTrack(pageId, 0): n.pushState() noteOnsets.append(n.cs.onset) notePitchs.append(n.cs.pitch) n.cs.instrumentId = id n.cs.amplitude = volume * n.cs.amplitude n.cs.reverbSend = reverb self.csnd.loopPlay(n, 1, loopId=loopId) # add as active n.popState() ticks = self.noteDB.getPage(pageId).ticks self.csnd.loopSetNumTicks(ticks, loopId) self.drumFillin.setLoopId(loopId) self.drumFillin.setProperties(self.tempo, self.instrumentDB.instId[id].name, volume, beats, reverb) self.drumFillin.unavailable(noteOnsets, notePitchs) self.drumFillin.play() if oldId == None: if sync: startTick = self.csnd.loopGetTick(self.heartbeatLoop) \ % self.syncTicks else: startTick = 0 else: # TODO is this really safe? could potentially add several # milliseconds of delay everytime a loop is updated if sync: startTick = self.csnd.loopGetTick(oldId) else: startTick = 0 while startTick > ticks: startTick -= ticks self.csnd.loopSetTick(startTick, loopId) self.csnd.loopStart(loopId) if oldId != None: self.csnd.loopDestroy(oldId) return loopId def _stopDrum(self, loopId): self.drumFillin.stop() self.csnd.loopDestroy(loopId) def _playLoop(self, id, volume, reverb, tune, loopId=None, force=False, sync=True): oldId = loopId loopId = self.csnd.loopCreate() inst = self.instrumentDB.instId[id] ticks = 0 for page in tune: for n in self.noteDB.getNotesByTrack(page, 0): n.pushState() n.cs.instrumentId = id n.cs.amplitude = volume * n.cs.amplitude n.cs.reverbSend = reverb if inst.kit: # drum kit if n.cs.pitch in GenerationConstants.DRUMPITCH: n.cs.pitch = GenerationConstants.DRUMPITCH[n.cs.pitch] n.cs.onset += ticks self.csnd.loopPlay(n, 1, loopId=loopId) n.popState() # metronome track for n in self.noteDB.getNotesByTrack(page, 1): self.csnd.loopPlay(n, 1, loopId=loopId) # record scratch track for n in self.noteDB.getNotesByTrack(page, 2): self.csnd.loopPlay(n, 1, loopId=loopId) ticks += self.noteDB.getPage(page).ticks self.csnd.loopSetNumTicks(ticks, loopId) if oldId == None: if sync: startTick = self.csnd.loopGetTick(self.heartbeatLoop) \ % self.syncTicks else: startTick = 0 else: # TODO is this really safe? could potentially add several # milliseconds of delay everytime a loop is updated if sync: startTick = self.csnd.loopGetTick(oldId) else: startTick = 0 while startTick > ticks: startTick -= ticks self.csnd.loopSetTick(startTick, loopId) self.csnd.loopStart(loopId) if oldId != None: self.csnd.loopDestroy(oldId) return loopId def _stopLoop(self, loopId): self.csnd.loopDestroy(loopId) def addMetronome(self, page, period): self.noteDB.deleteNotesByTrack([page], [1]) baseCS = CSoundNote( 0, # onset 36, # pitch 0.2, # amplitude 0.5, # pan 100, # duration 1, # track self.instrumentDB.instNamed["drum1hatpedal"].instrumentId, reverbSend=0.5, tied=True, mode='mini') stream = [] offset = 0 for b in range(self.noteDB.getPage(page).beats): cs = baseCS.clone() cs.instrumentId = \ self.instrumentDB.instNamed["drum1hatshoulder"].instrumentId cs.amplitude = 0.5 cs.onset += offset stream.append(cs) onset = period while onset < Config.TICKS_PER_BEAT: cs = baseCS.clone() cs.onset = onset + offset stream.append(cs) onset += period offset += Config.TICKS_PER_BEAT self.noteDB.addNotes([page, 1, len(stream)] + stream + [-1]) def removeMetronome(self, page): self.noteDB.deleteNotesByTrack([page], [1]) def handleStopButton(self, widget): self.setStopped() def handleMuteButton(self, widget): if widget.get_active(): self._setMuted(True) else: self._setMuted(False) def setMuted(self, muted): if Config.HAVE_TOOLBOX: toolbar = self.activity.toolbox.toolbar else: toolbar = self.playbackToolbar if toolbar.muteButton.get_active() == muted: return toolbar.muteButton.set_active(muted) toolbar.playbackToolbar.setMuted(muted) def _setMuted(self, muted): if self.muted == muted: return False if self.muted: # unmute self.muted = False self.csnd.setTrackVolume(100, 0) else: # mute self.muted = True self.csnd.setTrackVolume(0, 0) return True def setStopped(self): for drum in list(self.desktop.drums): self.desktop.deactivateDrum(drum) # we copy the list using the list() method for loop in list(self.desktop.loops): self.desktop.deactivateLoop(loop) #========================================================== # Generate def _generateDrumLoop(self, instrumentId, beats, regularity, reverb, pageId=-1): def flatten(ll): rval = [] for l in ll: rval += l return rval notes = flatten( generator(self.instrumentDB.instId[instrumentId].name, beats, 0.8, regularity, reverb)) if pageId == -1: page = Page(beats) pageId = self.noteDB.addPage(-1, page) else: self.noteDB.deleteNotesByTrack([pageId], [0]) if len(notes): self.noteDB.addNotes([pageId, 0, len(notes)] + notes + [-1]) return pageId def _generateTrack(self, instrumentId, page, track, parameters, algorithm): dict = {track: {page: self.noteDB.getCSNotesByTrack(page, track)}} instruments = {page: [self.instrumentDB.instId[instrumentId].name \ for i in range(Config.NUMBER_OF_TRACKS)]} beatsOfPages = {page: self.noteDB.getPage(page).beats} algorithm(parameters, [0.5 for i in range(Config.NUMBER_OF_TRACKS)], instruments, self.tempo, beatsOfPages, [track], [page], dict, 4) # filter & fix input ...WTF!? for track in dict: for page in dict[track]: for note in dict[track][page]: intdur = int(note.duration) note.duration = intdur note.pageId = page note.trackId = track # prepare the new notes newnotes = [] for tid in dict: for pid in dict[tid]: newnotes += dict[tid][pid] # delete the notes and add the new self.noteDB.deleteNotesByTrack([page], [track]) self.noteDB.addNotes([page, track, len(dict[track][page])] \ + dict[track][page] + [-1]) #========================================================== # Mic recording def micRec(self, widget, mic): self.csnd.inputMessage("i5600 0 4") OS.arecord(4, "crop.csd", mic) self.csnd.load_mic_instrument(mic) #========================================================== # Loop Settings def loopSettingsChannel(self, channel, value): self.csnd.setChannel(channel, value) def loopSettingsPlayStop(self, state, loop): if not state: if loop: self.loopSettingsPlaying = True self.csnd.inputMessage(Config.CSOUND_PLAY_LS_NOTE % 5022) else: self.csnd.inputMessage(Config.CSOUND_PLAY_LS_NOTE % 5023) else: if loop: self.loopSettingsPlaying = False self.csnd.inputMessage(Config.CSOUND_STOP_LS_NOTE) def load_ls_instrument(self, soundName): self.csnd.load_ls_instrument(soundName) #========================================================== # Get/Set def getVolume(self): return self.volume def setVolume(self, volume): self.jamToolbar.volumeSlider.set_value(volume) def _setVolume(self, volume): if self.muted: self.setMuted(False) self.volume = volume # csnd expects a range 0-100 for now self.csnd.setMasterVolume(self.volume * 100) def getTempo(self): return self.tempo def setTempo(self, tempo, quiet=False): self.jamToolbar.setTempo(tempo, quiet) def _setTempo(self, tempo, propagate=True): if self.network.isHost() or self.network.isOffline(): t = time.time() elapsedTicks = (t - self.heartbeatStart) * self.ticksPerSecond self.tempo = tempo self.beatDuration = 60.0 / self.tempo self.ticksPerSecond = Config.TICKS_PER_BEAT * self.tempo / 60.0 self.csnd.setTempo(self.tempo) if self.network.isHost() or self.network.isOffline(): self.heatbeatStart = t - elapsedTicks * self.beatDuration self.updateSync() self.sendTempoUpdate() def getInstrument(self): return self.instrument def getDesktop(self): return self.desktop def _clearDesktop(self, save=True): if self.curDesktop == None: return if save: self._saveDesktop() self.desktop._clearDesktop() self.curDesktop = None def setDesktop(self, desktop, force=False): radiobtn = self.desktopToolbar.getDesktopButton(desktop) if force and radiobtn.get_active(): self._setDesktop(desktop) else: radiobtn.set_active(True) def _setDesktop(self, desktop): self._clearDesktop() self.curDesktop = desktop TTTable = ControlStream.TamTamTable(self.noteDB, jam=self) filename = self.getDesktopScratchFile(self.curDesktop) try: stream = open(filename, "r") TTTable.parseFile(stream) stream.close() except IOError, (errno, strerror): if Config.DEBUG > 3: print "IOError:: _setDesktop:", errno, strerror