Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
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)
Ejemplo n.º 6
0
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