From 11003147ac4ea947ec5017921019d668cb4953d1 Mon Sep 17 00:00:00 2001 From: Aleksey Lim Date: Tue, 28 Jun 2011 09:23:54 +0000 Subject: Switch to the singular sources tree and releasing scheme --- (limited to 'Edit') diff --git a/Edit/EditToolbars.py b/Edit/EditToolbars.py new file mode 100644 index 0000000..beb9556 --- /dev/null +++ b/Edit/EditToolbars.py @@ -0,0 +1,1495 @@ +import gtk + +import common.Config as Config +from sugar.graphics.toolbutton import ToolButton +from sugar.graphics.toggletoolbutton import ToggleToolButton +from sugar.graphics.radiotoolbutton import RadioToolButton +from sugar.graphics.palette import Palette +from sugar.graphics.icon import Icon +from common.Util.ThemeWidgets import * +from gettext import gettext as _ + +#Generation palette +import gobject +from common.Generation.Generator import GenerationParameters +#Generation palette and Properties palette +from common.Generation.GenerationConstants import GenerationConstants +from common.Generation.GenerationRythm import GenerationRythm +from common.Generation.GenerationPitch import GenerationPitch + +#Properties palette +from common.Util.NoteDB import PARAMETER +from common.Generation.Drunk import * +import common.Generation.Utils as Utils +from types import * +from math import sqrt +from random import * + +class mainToolbar(gtk.Toolbar): + def __init__(self,toolbox, edit): + gtk.Toolbar.__init__(self) + + def _insertSeparator(x = 1): + for i in range(x): + self.separator = gtk.SeparatorToolItem() + self.separator.set_expand(True) + self.separator.set_draw(True) + self.insert(self.separator,-1) + self.separator.show() + + self.toolbox = toolbox + self.edit = edit + + self.tooltips = gtk.Tooltips() + + #Play button + self.playButton = ToggleToolButton('media-playback-start') + self.playButtonHandler = self.playButton.connect('toggled', self.handlePlayPause) + self.insert(self.playButton, -1) + self.playButton.show() + self.playButton.set_tooltip(_('Play / Pause')) + + #Stop button + self.stopButton = ToolButton('media-playback-stop') + self.stopButton.connect('clicked', self.handleStop) + self.insert(self.stopButton, -1) + self.stopButton.show() + self.stopButton.set_tooltip(_('Stop')) + + #Play button Image + self.playButtonImg = gtk.Image() + self.playButtonImg.set_from_icon_name('media-playback-start', gtk.ICON_SIZE_LARGE_TOOLBAR) + self.playButtonImg.show() + + #Pause button Image + self.pauseButtonImg = gtk.Image() + self.pauseButtonImg.set_from_icon_name('media-playback-pause', gtk.ICON_SIZE_LARGE_TOOLBAR) + self.pauseButtonImg.show() + + #Record button + self.recordButton = ToggleToolButton('recordK') + self.recordButton.connect('clicked', self.edit.handleKeyboardRecordButton) + self.insert(self.recordButton, -1) + self.recordButton.show() + self.recordButton.set_tooltip(_('Record keyboard')) + + if Config.FEATURES_OGG: + #RecordOgg button + self.recordOggButton = ToggleToolButton('recordO') + self.recordOggButton.connect('clicked', self.handleRecord) + self.insert(self.recordOggButton, -1) + self.recordOggButton.show() + self.recordOggButton.set_tooltip(_('Record to ogg')) + + _insertSeparator(1) + + #Pointer button + self._pointerPalette = pointerPalette(_('Select tool'), self.edit) + self.pointerButton = RadioToolButton(group = None) + self.pointerButton.set_named_icon('edit-pointer') + self.pointerButton.set_palette(self._pointerPalette) + self.pointerButton.connect('toggled', self.edit.handleToolClick, 'default') + self.insert(self.pointerButton, -1) + self.pointerButton.show() + + #Draw button + self._drawPalette = drawPalette(_('Draw Tool'), self.edit) + self.drawButton = RadioToolButton(group = self.pointerButton) + self.drawButton.set_named_icon('edit-pencil') + self.drawButton.set_palette(self._drawPalette) + self.drawButton.connect('toggled', self.edit.handleToolClick, 'draw') + self.insert(self.drawButton, -1) + self.drawButton.show() + + #Paint button + self._paintPalette = paintPalette(_('Paint Tool'), self.edit) + self.paintButton = RadioToolButton(group = self.pointerButton) + self.paintButton.set_named_icon('edit-brush') + self.paintButton.set_palette(self._paintPalette) + self.paintButton.connect('toggled', self.edit.handleToolClick, 'paint') + self.insert(self.paintButton, -1) + self.paintButton.show() + + _insertSeparator(1) + + #Duplicate button + self.duplicateButton = ToggleToolButton('duplicate') + self.duplicateButton.connect('toggled', self.handleDuplicate) + self.insert(self.duplicateButton, -1) + self.duplicateButton.show() + self.duplicateButton.set_tooltip(_('Duplicate')) + + #Volume / Tempo button + self._volumeTempoPalette = volumeTempoPalette(_('Volume / Tempo'), self.edit) + self.volumeTempoButton = ToggleToolButton('voltemp') + self.volumeTempoButton.set_palette(self._volumeTempoPalette) + self.insert(self.volumeTempoButton, -1) + self.volumeTempoButton.show() + + def handleRecord(self, widget, data = None): + if widget.get_active(): + self.playButton.set_active(False) + self.edit.handleAudioRecord(widget, data) + if widget.get_active(): + gobject.timeout_add( 500, self._startAudioRecord ) + + def _startAudioRecord( self ): + self.playButton.set_active(True) + return False + + def handlePlayPause(self, widget, data = None): + if widget.get_active(): + self.edit.handlePlay(widget) + self.edit._generateToolbar.handler_block(self.edit._generateToolbar.playButtonHandler) + self.edit._generateToolbar.playButton.set_active(True) + self.edit._generateToolbar.handler_unblock(self.edit._generateToolbar.playButtonHandler) + widget.set_icon_widget(self.pauseButtonImg) + self.edit._generateToolbar.playButton.set_icon_widget(self.edit._generateToolbar.pauseButtonImg) + else: + self.edit.handleStop(widget, False) + self.edit._generateToolbar.handler_block(self.edit._generateToolbar.playButtonHandler) + self.edit._generateToolbar.playButton.set_active(False) + self.edit._generateToolbar.handler_unblock(self.edit._generateToolbar.playButtonHandler) + widget.set_icon_widget(self.playButtonImg) + self.edit._generateToolbar.playButton.set_icon_widget(self.edit._generateToolbar.playButtonImg) + + def handleStop(self, widget, data = None): + self.playButton.set_active(False) + self.edit.handleStop(widget, True) + if self.recordButton.get_active(): + self.recordButton.set_active(False) + + def handleDuplicate(self, widget): + if widget.get_active(): + if self.edit.getContext() == 0: #Page + self.edit.pageDuplicate() + elif self.edit.getContext() == 1: #Track + self.edit.trackDuplicateWidget(widget) + elif self.edit.getContext() == 2: #Note + self.edit.noteDuplicateWidget(widget) + widget.set_active(False) + +class generateToolbar(gtk.Toolbar): + def __init__(self,toolbox, edit): + gtk.Toolbar.__init__(self) + + def _insertSeparator(x = 1): + for i in range(x): + self.separator = gtk.SeparatorToolItem() + self.separator.set_expand(True) + self.separator.set_draw(False) + self.insert(self.separator,-1) + self.separator.show() + + self.toolbox = toolbox + self.edit = edit + + self.tooltips = gtk.Tooltips() + + #Play button + self.playButton = ToggleToolButton('media-playback-start') + self.playButtonHandler = self.playButton.connect('toggled', self.handlePlayPause) + self.insert(self.playButton, -1) + self.playButton.show() + self.playButton.set_tooltip(_('Play / Pause')) + + #Stop button + self.stopButton = ToolButton('media-playback-stop') + self.stopButton.connect('clicked', self.handleStop) + self.insert(self.stopButton, -1) + self.stopButton.show() + self.stopButton.set_tooltip(_('Stop')) + + #Play button Image + self.playButtonImg = gtk.Image() + self.playButtonImg.set_from_icon_name('media-playback-start', gtk.ICON_SIZE_LARGE_TOOLBAR) + self.playButtonImg.show() + + #Pause button Image + self.pauseButtonImg = gtk.Image() + self.pauseButtonImg.set_from_icon_name('media-playback-pause', gtk.ICON_SIZE_LARGE_TOOLBAR) + self.pauseButtonImg.show() + + _insertSeparator(1) + + #BigGeneration button + self.bigGenerationButton = ToolButton('diceB') + self.bigGenerationButton.connect('clicked', self.edit.createNewTune) + self.insert(self.bigGenerationButton, -1) + self.bigGenerationButton.show() + self.bigGenerationButton.set_tooltip(_('Generate Tune')) + + #Generation button + self._generationPalette = generationPalette(_('Generation'), self.edit) + self.generationButton = ToggleToolButton('dice') + #self.generationButton.connect(None) + self.generationButton.set_palette(self._generationPalette) + self.insert(self.generationButton, -1) + self.generationButton.show() + + #Properties button + self._propertiesPalette = propertiesPalette(_('Properties'), self.edit) + self.propsButton = ToggleToolButton('props') + self.propsButton.set_palette(self._propertiesPalette) + self.insert(self.propsButton, -1) + self.propsButton.show() + + def handlePlayPause(self, widget, data = None): + if widget.get_active(): + self.edit.handlePlay(widget) + self.edit._mainToolbar.handler_block(self.edit._mainToolbar.playButtonHandler) + self.edit._mainToolbar.playButton.set_active(True) + self.edit._mainToolbar.handler_unblock(self.edit._mainToolbar.playButtonHandler) + widget.set_icon_widget(self.pauseButtonImg) + self.edit._mainToolbar.playButton.set_icon_widget(self.edit._mainToolbar.pauseButtonImg) + else: + self.edit.handleStop(widget, False) + self.edit._mainToolbar.handler_block(self.edit._mainToolbar.playButtonHandler) + self.edit._mainToolbar.playButton.set_active(False) + self.edit._mainToolbar.handler_unblock(self.edit._mainToolbar.playButtonHandler) + widget.set_icon_widget(self.playButtonImg) + self.edit._mainToolbar.playButton.set_icon_widget(self.edit._mainToolbar.playButtonImg) + + def handleStop(self, widget, data = None): + self.edit.handleStop(widget, True) + self.playButton.set_active(False) + if self.edit._mainToolbar.recordButton.get_active(): + self.edit._mainToolbar.recordButton.set_active(False) + +class pointerPalette(Palette): + def __init__(self, label, edit): + Palette.__init__(self, label) + + self.edit = edit + + self.pointerBox = gtk.VBox() + + self.snapGridHBox = gtk.HBox() + self.snapGridImage = gtk.Image() + self.snapGridImage.set_from_file(Config.TAM_TAM_ROOT + '/icons/grid.svg') + self.snapGridBox = BigComboBox() + self.snapGridBox.connect('changed', self.handleSnapGrid) + self.gridDurs = [1, 2, 3, 4, 6, 12, 24] + durs = [_('1/12'), _('1/6'), _('1/4'), _('1/3'), _('1/2'), _('1'), _('2') ] + for dur in durs: + self.snapGridBox.append_item(durs.index(dur),dur) + self.snapGridBox.set_active(0) + self.snapGridHBox.pack_start(self.snapGridImage, False, False, padding = 5) + self.snapGridHBox.pack_start(self.snapGridBox, False, False, padding = 5) + + self.pointerBox.pack_start(self.snapGridHBox, False, False, padding = 5) + self.pointerBox.show_all() + + self.set_content(self.pointerBox) + + pass + #self.noteDur = widget.props.value + + def handleSnapGrid(self, widget): + data = widget.props.value + grid = int(self.gridDurs[data]) + self.edit.trackInterface.setPointerGrid(grid) + +class drawPalette(Palette): + def __init__(self, label, edit): + Palette.__init__(self, label) + + self.edit = edit + + self.drawBox = gtk.VBox() + + self.snapGridHBox = gtk.HBox() + self.snapGridImage = gtk.Image() + self.snapGridImage.set_from_file(Config.TAM_TAM_ROOT + '/icons/grid.svg') + self.snapGridBox = BigComboBox() + self.snapGridBox.connect('changed', self.handleSnapGrid) + self.gridDurs = [1, 2, 3, 4, 6, 12, 24] + durs = [_('1/12'), _('1/6'), _('1/4'), _('1/3'), _('1/2'), _('1'), _('2') ] + for dur in durs: + self.snapGridBox.append_item(durs.index(dur),dur) + self.snapGridBox.set_active(0) + self.snapGridHBox.pack_start(self.snapGridImage, False, False, padding = 5) + self.snapGridHBox.pack_start(self.snapGridBox, False, False, padding = 5) + + self.drawBox.pack_start(self.snapGridHBox, False, False, padding = 5) + self.drawBox.show_all() + + self.set_content(self.drawBox) + + pass + #self.noteDur = widget.props.value + + def handleSnapGrid(self, widget): + data = widget.props.value + grid = int(self.gridDurs[data]) + self.edit.trackInterface.setDrawGrid(grid) + +class paintPalette(Palette): + def __init__(self, label, edit): + Palette.__init__(self, label) + + self.edit = edit + + self.paintBox = gtk.VBox() + + self.noteDurHBox = gtk.HBox() + self.noteDurImage = gtk.Image() + self.noteDurImage.set_from_file(Config.TAM_TAM_ROOT + '/icons/notedur.svg') + self.noteDurBox = BigComboBox() + self.noteDurBox.connect('changed', self.handleNoteDur) + self.noteDurs = [1, 2, 3, 4, 6, 12, 24] + self.durs = [_('1/12'), _('1/6'), _('1/4'), _('1/3'), _('1/2'), _('1'), _('2') ] + for dur in self.durs: + self.noteDurBox.append_item(self.durs.index(dur),dur) + self.noteDurBox.set_active(2) + self.noteDurHBox.pack_start(self.noteDurImage, False, False, padding = 5) + self.noteDurHBox.pack_start(self.noteDurBox, False, False, padding = 5) + + self.snapGridHBox = gtk.HBox() + self.snapGridImage = gtk.Image() + self.snapGridImage.set_from_file(Config.TAM_TAM_ROOT + '/icons/grid.svg') + self.snapGridBox = BigComboBox() + self.snapGridBox.connect('changed', self.handleSnapGrid) + self.gridDurs = [1, 2, 3, 4, 6, 12, 24] + durs = [_('1/12'), _('1/6'), _('1/4'), _('1/3'), _('1/2'), _('1'), _('2')] + for dur in durs: + self.snapGridBox.append_item(durs.index(dur),dur) + self.snapGridBox.set_active(2) + self.snapGridHBox.pack_start(self.snapGridImage, False, False, padding = 5) + self.snapGridHBox.pack_start(self.snapGridBox, False, False, padding = 5) + + self.paintBox.pack_start(self.noteDurHBox, False, False, padding = 5) + self.paintBox.pack_start(self.snapGridHBox, False, False, padding = 5) + self.paintBox.show_all() + + self.set_content(self.paintBox) + + def resizeNoteDur(self): + oldActive = self.noteDurBox.get_active() + len = self.snapGridBox.get_active() + self.noteDurBox.remove_all() + for dur in self.durs[0:len+1]: + self.noteDurBox.append_item(self.durs.index(dur), dur) + if oldActive <= len: + self.noteDurBox.set_active(oldActive) + else: + self.noteDurBox.set_active(len) + + def handleNoteDur(self, widget): + data = widget.props.value + noteDur = int(self.noteDurs[data]) + self.edit.trackInterface.setPaintNoteDur(noteDur) + + def handleSnapGrid(self, widget): + data = widget.props.value + grid = int(self.gridDurs[data]) + self.edit.trackInterface.setPaintGrid(grid) + self.resizeNoteDur() + +class volumeTempoPalette(Palette): + def __init__(self, label, edit): + Palette.__init__(self, label) + + self.edit = edit + + self.volumeTempoBox = gtk.VBox() + + self.volumeSliderBox = gtk.HBox() + self.volumeSliderLabel = gtk.Label(_('Volume')) + self.volumeSliderAdj = gtk.Adjustment(Config.DEFAULT_VOLUME, 0, 100, 1, 1, 0) + self.volumeSliderAdj.connect('value-changed', self.edit.handleVolume) + self.volumeSlider = gtk.HScale(adjustment = self.volumeSliderAdj) + self.volumeSlider.set_size_request(250,-1) + self.volumeSlider.set_inverted(False) + self.volumeSlider.set_draw_value(False) + self.volumeSliderBox.pack_start(self.volumeSliderLabel, False, False, padding = 5) + self.volumeSliderBox.pack_end(self.volumeSlider, False, False, padding = 5) + + self.tempoSliderBox = gtk.HBox() + self.tempoSliderLabel = gtk.Label(_('Tempo')) + self.tempoSliderAdj = gtk.Adjustment(Config.PLAYER_TEMPO, 40, 240, 1, 1, 0) + self.tempoSliderAdj.connect('value-changed', self.edit.handleTempo) + self.tempoSlider = gtk.HScale(adjustment = self.tempoSliderAdj) + self.tempoSlider.set_size_request(250,-1) + self.tempoSlider.set_inverted(False) + self.tempoSlider.set_draw_value(False) + self.tempoSliderBox.pack_start(self.tempoSliderLabel, False, False, padding = 5) + self.tempoSliderBox.pack_end(self.tempoSlider, False, False, padding = 5) + + self.volumeTempoBox.pack_start(self.volumeSliderBox, padding = 5) + self.volumeTempoBox.pack_start(self.tempoSliderBox, padding = 5) + self.volumeTempoBox.show_all() + + self.set_content(self.volumeTempoBox) + +class generationPalette(Palette): + def __init__(self, label, edit): + Palette.__init__(self, label) + + self.edit = edit + + self.rythmDensity = GenerationConstants.DEFAULT_DENSITY + self.rythmRegularity = GenerationConstants.DEFAULT_RYTHM_REGULARITY + self.pitchRegularity = GenerationConstants.DEFAULT_PITCH_REGULARITY + self.pitchStep = GenerationConstants.DEFAULT_STEP + self.duration = GenerationConstants.DEFAULT_DURATION + self.silence = GenerationConstants.DEFAULT_SILENCE + self.rythmMethod = GenerationConstants.DEFAULT_RYTHM_METHOD + self.pitchMethod = GenerationConstants.DEFAULT_PITCH_METHOD + self.pattern = GenerationConstants.DEFAULT_PATTERN + self.scale = GenerationConstants.DEFAULT_SCALE + + self.mainBox = gtk.VBox() + self.slidersBox = gtk.HBox() + self.scaleModeBox = gtk.VBox() + self.decisionBox = gtk.HBox() + + self.XYSlider1MainBox = gtk.VBox() + self.XYSlider1TopLabel = gtk.Label(_('Rhythm')) + self.XSlider1BottomLabelBox = gtk.HBox() + self.XSlider1Img = gtk.Image() + self.XSlider1Img.set_from_file(Config.TAM_TAM_ROOT + '/icons/sideR.svg') + self.XSlider1BottomLabel = gtk.Label(_('Density')) + self.YSlider1BottomLabelBox = gtk.HBox() + self.YSlider1Img = gtk.Image() + self.YSlider1Img.set_from_file(Config.TAM_TAM_ROOT + '/icons/updownR.svg') + self.YSlider1BottomLabel = gtk.Label(_('Regularity')) + self.XYSliderBox1 = RoundFixed(fillcolor = '#CCCCCC', bordercolor = '#000000') + self.XYSliderBox1.set_size_request(200,200) + self.XYButton1 = ImageToggleButton( Config.TAM_TAM_ROOT + '/icons/XYBut.svg', Config.TAM_TAM_ROOT + '/icons/XYButDown.svg') + self.XAdjustment1 = gtk.Adjustment(self.rythmDensity[0] * 100, 0, 100, 1, 1, 1) + self.XAdjustment1.connect("value-changed", self.handleXAdjustment1) + self.YAdjustment1 = gtk.Adjustment(self.rythmRegularity[0] * 100, 0, 100, 1, 1, 1) + self.YAdjustment1.connect("value-changed", self.handleYAdjustment1) + self.XYSlider1 = XYSlider( self.XYSliderBox1, self.XYButton1, self.XAdjustment1, self.YAdjustment1, False, True ) + self.XSlider1BottomLabelBox.pack_start(self.XSlider1Img, False, False, padding = 5) + self.XSlider1BottomLabelBox.pack_start(self.XSlider1BottomLabel, False, False, padding = 5) + self.YSlider1BottomLabelBox.pack_start(self.YSlider1Img, False, False, padding = 5) + self.YSlider1BottomLabelBox.pack_start(self.YSlider1BottomLabel, False, False, padding = 5) + self.XYSlider1MainBox.pack_start(self.XYSlider1TopLabel, False, False, padding = 5) + self.XYSlider1MainBox.pack_start(self.XYSlider1, False, False, padding = 2) + self.XYSlider1MainBox.pack_start(self.XSlider1BottomLabelBox, False, False, padding = 2) + self.XYSlider1MainBox.pack_start(self.YSlider1BottomLabelBox, False, False, padding = 2) + + self.XYSlider2MainBox = gtk.VBox() + self.XYSlider2TopLabel = gtk.Label(_('Pitch')) + self.XSlider2BottomLabelBox = gtk.HBox() + self.XSlider2Img = gtk.Image() + self.XSlider2Img.set_from_file(Config.TAM_TAM_ROOT + '/icons/sideR.svg') + self.XSlider2BottomLabel = gtk.Label(_('Regularity')) + self.YSlider2BottomLabelBox = gtk.HBox() + self.YSlider2Img = gtk.Image() + self.YSlider2Img.set_from_file(Config.TAM_TAM_ROOT + '/icons/updownR.svg') + self.YSlider2BottomLabel = gtk.Label(_('Maximum step')) + self.XYSliderBox2 = RoundFixed(fillcolor = '#CCCCCC', bordercolor = '#000000') + self.XYSliderBox2.set_size_request(200,200) + self.XYButton2 = ImageToggleButton( Config.TAM_TAM_ROOT + '/icons/XYBut.svg', Config.TAM_TAM_ROOT + '/icons/XYButDown.svg') + self.XAdjustment2 = gtk.Adjustment(self.pitchRegularity[0] * 100, 0, 100, 1, 1, 1) + self.XAdjustment2.connect("value-changed", self.handleXAdjustment2) + self.YAdjustment2 = gtk.Adjustment(self.pitchStep[0] * 100, 0, 100, 1, 1, 1) + self.YAdjustment2.connect("value-changed", self.handleYAdjustment2) + self.XYSlider2 = XYSlider( self.XYSliderBox2, self.XYButton2, self.XAdjustment2, self.YAdjustment2, False, True ) + self.XSlider2BottomLabelBox.pack_start(self.XSlider2Img, False, False, padding = 5) + self.XSlider2BottomLabelBox.pack_start(self.XSlider2BottomLabel, False, False, padding = 5) + self.YSlider2BottomLabelBox.pack_start(self.YSlider2Img, False, False, padding = 5) + self.YSlider2BottomLabelBox.pack_start(self.YSlider2BottomLabel, False, False, padding = 5) + self.XYSlider2MainBox.pack_start(self.XYSlider2TopLabel, False, False, padding = 5) + self.XYSlider2MainBox.pack_start(self.XYSlider2, False, False, padding = 2) + self.XYSlider2MainBox.pack_start(self.XSlider2BottomLabelBox, False, False, padding = 2) + self.XYSlider2MainBox.pack_start(self.YSlider2BottomLabelBox, False, False, padding = 2) + + self.XYSlider3MainBox = gtk.VBox() + self.XYSlider3TopLabel = gtk.Label(_('Duration')) + self.XSlider3BottomLabelBox = gtk.HBox() + self.XSlider3Img = gtk.Image() + self.XSlider3Img.set_from_file(Config.TAM_TAM_ROOT + '/icons/sideR.svg') + self.XSlider3BottomLabel = gtk.Label(_('Note duration')) + self.YSlider3BottomLabelBox = gtk.HBox() + self.YSlider3Img = gtk.Image() + self.YSlider3Img.set_from_file(Config.TAM_TAM_ROOT + '/icons/updownR.svg') + self.YSlider3BottomLabel = gtk.Label(_('Silence density')) + self.XYSliderBox3 = RoundFixed(fillcolor = '#CCCCCC', bordercolor = '#000000') + self.XYSliderBox3.set_size_request(200,200) + self.XYButton3 = ImageToggleButton( Config.TAM_TAM_ROOT + '/icons/XYBut.svg', Config.TAM_TAM_ROOT + '/icons/XYButDown.svg') + self.XAdjustment3 = gtk.Adjustment(self.duration[0] * 100, 0, 100, 1, 1, 1) + self.XAdjustment3.connect("value-changed", self.handleXAdjustment3) + self.YAdjustment3 = gtk.Adjustment(self.silence[0] * 100, 0, 100, 1, 1, 1) + self.YAdjustment3.connect("value-changed", self.handleYAdjustment3) + self.XYSlider3 = XYSlider( self.XYSliderBox3, self.XYButton3, self.XAdjustment3, self.YAdjustment3, False, True ) + self.XSlider3BottomLabelBox.pack_start(self.XSlider3Img, False, False, padding = 5) + self.XSlider3BottomLabelBox.pack_start(self.XSlider3BottomLabel, False, False, padding = 5) + self.YSlider3BottomLabelBox.pack_start(self.YSlider3Img, False, False, padding = 5) + self.YSlider3BottomLabelBox.pack_start(self.YSlider3BottomLabel, False, False, padding = 5) + self.XYSlider3MainBox.pack_start(self.XYSlider3TopLabel, False, False, padding = 5) + self.XYSlider3MainBox.pack_start(self.XYSlider3, False, False, padding = 2) + self.XYSlider3MainBox.pack_start(self.XSlider3BottomLabelBox, False, False, padding = 2) + self.XYSlider3MainBox.pack_start(self.YSlider3BottomLabelBox, False, False, padding = 2) + + self.slidersBox.pack_start(self.XYSlider1MainBox, False, False, padding = 5) + self.slidersBox.pack_start(self.XYSlider2MainBox, False, False, padding = 5) + self.slidersBox.pack_start(self.XYSlider3MainBox, False, False, padding = 5) + + self.previewBox = gtk.HBox() + self.previewDA = gtk.DrawingArea() + self.previewDA.set_size_request( -1, 100 ) + self.previewDA.connect( "size-allocate", self.handlePreviewAlloc ) + self.previewDA.connect( "expose-event", self.handlePreviewExpose ) + self.previewBox.pack_start( self.previewDA, True, True, padding = 5 ) + + self.scaleBoxHBox = gtk.HBox() + self.scaleBoxLabel = gtk.Label(_('Scale: ')) + self.scaleBox = BigComboBox() + scales = [_('Major scale'), _('Harmonic minor scale'), _('Natural minor scale'), _('Phrygian scale'), _('Dorian scale'), _('Lydian scale'), _('Mixolydian scale')] + for scale in scales: + self.scaleBox.append_item(scales.index(scale), scale) + self.scaleBox.connect('changed', self.handleScale) + + self.modeBoxHBox = gtk.HBox() + self.modeBoxLabel = gtk.Label(_('Mode: ')) + self.modeBox = BigComboBox() + modes = [_('Drunk'), _('Drone and Jump'), _('Repeater'), _('Loop segments')] + for mode in modes: + self.modeBox.append_item(modes.index(mode), mode) + self.modeBox.connect('changed', self.handleMode) + + self.scaleBoxHBox.pack_start(self.scaleBoxLabel, False, False, padding = 10) + self.scaleBoxHBox.pack_start(self.scaleBox, False, False, padding = 10) + self.modeBoxHBox.pack_start(self.modeBoxLabel, False, False, padding = 10) + self.modeBoxHBox.pack_start(self.modeBox, False, False, padding = 10) + self.scaleModeBox.pack_start(self.scaleBoxHBox, False, False, padding = 5) + self.scaleModeBox.pack_start(self.modeBoxHBox, False, False, padding = 5) + + self.acceptButton = ImageButton(Config.TAM_TAM_ROOT + '/icons/accept.svg') + self.acceptButton.connect('clicked',self.generate) + self.cancelButton = ImageButton(Config.TAM_TAM_ROOT + '/icons/cancel.svg') + self.cancelButton.connect('clicked',self.cancel) + self.decisionBox.pack_start(self.cancelButton, False, False, padding = 5) + self.decisionBox.pack_start(self.acceptButton, False, False, padding = 5) + + self.mainBox.pack_start(self.slidersBox, False, False, padding = 5) + self.mainBox.pack_start( self.previewBox, False, False, padding = 5 ) + self.mainBox.pack_start(self.scaleModeBox, False, False, padding = 5) + self.mainBox.pack_start(self.decisionBox, False, False, padding = 5) + self.mainBox.show_all() + + self.set_content(self.mainBox) + + #-- preview drawing ----------------------------------- + win = gtk.gdk.get_default_root_window() + self.gc = gtk.gdk.GC( win ) + self.parametersDirty = False + self.drawingPreview = False + self.predrawTarget = 0 + self.predrawIdleAbort = False + self.predrawBuffer = False + # self.predrawBuffer is initialized in handlePreviewAlloc + pix = gtk.gdk.pixbuf_new_from_file( Config.IMAGE_ROOT+"sampleBG.png" ) + self.sampleBg = gtk.gdk.Pixmap( win, pix.get_width(), pix.get_height() ) + self.sampleBg.draw_pixbuf( self.gc, pix, 0, 0, 0, 0, pix.get_width(), pix.get_height(), gtk.gdk.RGB_DITHER_NONE ) + self.sampleBg.endOffset = pix.get_width()-5 + self.sampleNoteHeight = 7 + if True: # load clipmask + pix = gtk.gdk.pixbuf_new_from_file(Config.IMAGE_ROOT+'sampleNoteMask.png') + pixels = pix.get_pixels() + stride = pix.get_rowstride() + channels = pix.get_n_channels() + bitmap = "" + byte = 0 + shift = 0 + for j in range(pix.get_height()): + offset = stride*j + for i in range(pix.get_width()): + r = pixels[i*channels+offset] + if r != "\0": byte += 1 << shift + shift += 1 + if shift > 7: + bitmap += "%c" % byte + byte = 0 + shift = 0 + if shift > 0: + bitmap += "%c" % byte + byte = 0 + shift = 0 + self.sampleNoteMask = gtk.gdk.bitmap_create_from_data( None, bitmap, pix.get_width(), pix.get_height() ) + self.sampleNoteMask.endOffset = pix.get_width()-3 + + colormap = self.previewDA.get_colormap() + self.colors = { "Beat_Line": colormap.alloc_color( "#959595", True, True ), + "Note_Border": colormap.alloc_color( Config.BG_COLOR, True, True ), + "Note_Fill": colormap.alloc_color( Config.FG_COLOR, True, True ) } + + self.scaleBox.set_active(0) + self.modeBox.set_active(0) + + + def handleXAdjustment1( self, data ): + self.rythmDensity = [self.XAdjustment1.value * .01 for x in range(4)] + + self.parametersChanged() + + def handleYAdjustment1( self, data ): + self.rythmRegularity = [self.YAdjustment1.value * .01 for x in range(4)] + self.parametersChanged() + + def handleXAdjustment2( self, data ): + self.pitchRegularity = [self.XAdjustment2.value * .01 for x in range(4)] + self.parametersChanged() + + def handleYAdjustment2( self, data ): + self.pitchStep = [self.YAdjustment2.value * .01 for x in range(4)] + self.parametersChanged() + + def handleXAdjustment3( self, data ): + self.duration = [self.XAdjustment3.value * .01 for x in range(4)] + self.parametersChanged() + + def handleYAdjustment3( self, data ): + self.silence = [self.YAdjustment3.value * .01 for x in range(4)] + self.parametersChanged() + + def handleScale(self, widget, data = None): + self.scale = widget.props.value + self.edit.scale = self.scale + self.parametersChanged() + + def handleMode( self, widget, data = None ): + self.pattern = [widget.props.value for x in range(4)] + self.parametersChanged() + + def getGenerationParameters( self ): + return GenerationParameters( self.rythmDensity, + self.rythmRegularity, + self.pitchStep, + self.pitchRegularity, + self.duration, + self.silence, + self.rythmMethod, + self.pitchMethod, + self.pattern, + self.scale ) + + def cancel(self, widget, data = None): + self.popdown(True) + + def generate(self, widget, data=None): + context = self.edit.getContext() + if context == 0: # Page + mode = 'page' + elif context == 1: # Track + mode = 'track' + elif context == 2: # Note + self.popdown(True) + return + self.edit.setPageGenerateMode(mode) + self.edit.generate(self.getGenerationParameters()) + print self.rythmDensity, self.rythmRegularity,self.pitchRegularity,self.pitchStep, self.duration, self.silence,self.pattern + self.popdown(True) + + ############ generate a preview melody ##############s + def previewGenerator(self, parameters): + makeRythm = GenerationRythm() + makePitch = GenerationPitch() + table_duration = Utils.scale(parameters.articule[0], GenerationConstants.ARTICULATION_SCALE_MIN_MAPPING, GenerationConstants.ARTICULATION_SCALE_MAX_MAPPING, GenerationConstants.ARTICULATION_SCALE_STEPS) + table_pitch = GenerationConstants.SCALES[parameters.scale] + beat = self.edit.noteDB.pages[self.edit.tuneInterface.getSelectedIds()[0]].beats + barLength = Config.TICKS_PER_BEAT * beat + trackNotes = [] + + rythmSequence = makeRythm.celluleRythmSequence(parameters, barLength, 0) + pitchSequence = makePitch.drunkPitchSequence(len(rythmSequence),parameters, table_pitch, 0) + gainSequence = self.makeGainSequence(rythmSequence) + durationSequence = self.makeDurationSequence(rythmSequence, parameters, table_duration, barLength) + + for i in range(len(rythmSequence)): + if random() > parameters.silence[0]: + trackNotes.append([rythmSequence[i], pitchSequence[i], gainSequence[i], durationSequence[i]]) + #print "-------------------------------------------------------",trackNotes + return ( trackNotes, beat ) + + def makeGainSequence( self, onsetList ): + gainSequence = [] + max = GenerationConstants.GAIN_MAX_BOUNDARY + midMax = GenerationConstants.GAIN_MID_MAX_BOUNDARY + midMin = GenerationConstants.GAIN_MID_MIN_BOUNDARY + min = GenerationConstants.GAIN_MIN_BOUNDARY + for onset in onsetList: + if onset == 0: + gainSequence.append(uniform(midMax, max)) + elif ( onset % Config.TICKS_PER_BEAT) == 0: + gainSequence.append(uniform(midMin, midMax)) + else: + gainSequence.append(uniform(min, midMin)) + return gainSequence + + def makeDurationSequence( self, onsetList, parameters, table_duration, barLength ): + durationSequence = [] + if len( onsetList ) > 1: + for i in range(len(onsetList) - 1): + durationSequence.append((onsetList[i+1] - onsetList[i]) * Utils.prob2( table_duration )) + durationSequence.append(( barLength - onsetList[-1]) * Utils.prob2( table_duration )) + elif len( onsetList ) == 1: + durationSequence.append( ( barLength - onsetList[0] ) * Utils.prob2( table_duration )) + return durationSequence + + def parametersChanged( self ): + if not self.drawingPreview: + self.drawPreview() + else: + self.parametersDirty = True + + def drawPreview( self, force = False ): + if not self.predrawBuffer: + return # not alloc'ed yet + + if self.drawingPreview and not force: + return # should never happen + + notes, beats = self.previewGenerator( self.getGenerationParameters() ) + self.parametersDirty = False + + if force: + if self.drawingPreview: + self.predrawIdleAbort = True + self._idleDraw( notes, beats, True, True ) + else: + self.drawingPreview = True + gobject.idle_add( self._idleDraw, notes, beats, True, False ) + + def _idleDraw( self, notes, beats, fresh, force ): + if self.predrawIdleAbort and not force: + self.predrawIdleAbort = False + return False + + pixmap = self.predrawBuffer[self.predrawTarget] + + if fresh: + # draw bg + pixmap.draw_drawable( self.gc, self.sampleBg, 0, 0, 0, 0, self.previewDA.width-5, self.previewDA.height ) + pixmap.draw_drawable( self.gc, self.sampleBg, self.sampleBg.endOffset, 0, self.previewDA.width-5, 0, 5, self.previewDA.height ) + # draw beat lines + self.gc.set_line_attributes( Config.BEAT_LINE_SIZE, gtk.gdk.LINE_ON_OFF_DASH, gtk.gdk.CAP_BUTT, gtk.gdk.JOIN_MITER ) + self.gc.foreground = self.colors["Beat_Line"] + for i in range(1,beats): + x = self.beatSpacing[beats][i] + pixmap.draw_line( self.gc, x, 1, x, self.previewDA.height-1 ) + + if not force: + gobject.idle_add( self._idleDraw, notes, beats, False, False ) + return False + + if force: N = len(notes) + else: N = min( 3, len( notes ) ) # adjust this value to get a reasonable response + + self.gc.set_clip_mask( self.sampleNoteMask ) + for i in range( N ): # draw N notes + note = notes.pop() + x = self.ticksToPixels( beats, note[0] ) + endX = self.ticksToPixels( beats, note[0] + note[3] ) - 3 # include end cap offset + width = endX - x + y = self.pitchToPixels( note[1] ) + # draw fill + self.gc.foreground = self.colors["Note_Fill"] + self.gc.set_clip_origin( x, y-self.sampleNoteHeight ) + pixmap.draw_rectangle( self.gc, True, x+1, y+1, width+1, self.sampleNoteHeight-2 ) + # draw border + self.gc.foreground = self.colors["Note_Border"] + self.gc.set_clip_origin( x, y ) + pixmap.draw_rectangle( self.gc, True, x, y, width, self.sampleNoteHeight ) + self.gc.set_clip_origin( endX-self.sampleNoteMask.endOffset, y ) + pixmap.draw_rectangle( self.gc, True, endX, y, 3, self.sampleNoteHeight ) + self.gc.set_clip_rectangle( self.clearClipMask ) + + if not len(notes): + self.predrawTarget = not self.predrawTarget + self.previewDA.queue_draw() + + self.drawingPreview = False + + if self.parametersDirty: + self.drawPreview() + + return False + + return True + + def handlePreviewAlloc( self, widget, allocation ): + win = gtk.gdk.get_default_root_window() + self.previewDA.width = allocation.width + self.previewDA.height = allocation.height + self.predrawBuffer = [ gtk.gdk.Pixmap( win, allocation.width, allocation.height ), + gtk.gdk.Pixmap( win, allocation.width, allocation.height ) ] + self.clearClipMask = gtk.gdk.Rectangle( 0, 0, allocation.width, allocation.height ) + + self.pitchPerPixel = float(Config.NUMBER_OF_POSSIBLE_PITCHES-1) / (self.previewDA.height - self.sampleNoteHeight) + self.pixelsPerPitch = float(self.previewDA.height - self.sampleNoteHeight)/(Config.MAXIMUM_PITCH - Config.MINIMUM_PITCH) + self.pixelsPerTick = [0] + [ self.previewDA.width/float(i*Config.TICKS_PER_BEAT) for i in range(1,Config.MAXIMUM_BEATS+1) ] + self.ticksPerPixel = [0] + [ 1.0/self.pixelsPerTick[i] for i in range(1,Config.MAXIMUM_BEATS+1) ] + + self.beatSpacing = [[0]] + for i in range(1,Config.MAXIMUM_BEATS+1): + self.beatSpacing.append( [ self.ticksToPixels( i, Config.TICKS_PER_BEAT*j ) for j in range(i) ] ) + + self.drawPreview( True ) + + def handlePreviewExpose( self, widget, event ): + widget.window.draw_drawable( self.gc, self.predrawBuffer[not self.predrawTarget], event.area.x, event.area.y, event.area.x, event.area.y, event.area.width, event.area.height ) + + def ticksToPixels( self, beats, ticks ): + return int(round( ticks * self.pixelsPerTick[beats] )) + def pitchToPixels( self, pitch ): + return int(round( ( Config.MAXIMUM_PITCH - pitch ) * self.pixelsPerPitch )) + + +class propertiesPalette(Palette): + def __init__(self, label, edit): + Palette.__init__(self, label) + self.connect('popup', self.handlePopup) + self.connect('popdown', self.handlePopdown) + + self.edit = edit + + self.filterTypes = [_('None'), _('Lowpass'), _('Bandpass'), _('Highpass')] + self.geneTypes = [_('Line'),_('Drunk'),_('Drone and Jump'),_('Repeater'),_('Loop segments')] + self.colors = [_('Purple'), _('Green'), _('Blue'), _('Yellow')] + self.currentFilterType = self.filterTypes[0] + + self.line = Line(0, 100) + self.drunk = Drunk(0, 100) + self.droneAndJump = DroneAndJump(0, 100) + self.repeter = Repeter(0, 100) + self.loopseg = Loopseg(0, 100) + self.algoTypes = [self.line, self.drunk, self.droneAndJump, self.repeter, self.loopseg] + self.algorithm = self.algoTypes[0] + self.geneMinimum = 0 + self.geneMaximum = 100 + self.geneRandom = 20 + + self.setup = False + self.hidden = False + self.geneCheckButtonDic = {} + + self.pageIds = [] + self.context = "page" + + self.mainBox = gtk.VBox() + + self.gridDivisionBox = gtk.HBox() + self.gridDivisionLabel = gtk.Label(_('Grid division: ')) + self.gridDivisionSliderAdj = gtk.Adjustment(4, 2, 12, 1, 1, 0) + self.gridDivisionSlider = gtk.HScale(adjustment = self.gridDivisionSliderAdj) + self.gridDivisionSlider.set_digits(0) + self.gridDivisionSlider.connect('button-release-event', self.handleBeat) + self.gridDivisionSlider.set_size_request(200,-1) + self.gridDivisionSlider.set_value_pos(gtk.POS_RIGHT) + self.gridDivisionBox.pack_start(self.gridDivisionLabel, False, False, padding = 5) + self.gridDivisionBox.pack_end(self.gridDivisionSlider, False, False, padding = 52) + + self.pageColorBox = gtk.HBox() + self.pageColorLabel = gtk.Label(_('Page color: ')) + self.pageColorComboBox = BigComboBox() + for color in (0,1,2,3): + self.pageColorComboBox.append_item(color, text = None, icon_name = Config.IMAGE_ROOT + 'pageThumbnailBG' + str(color) + '.png', size = (30,40)) + self.pageColorComboBox.set_active(0) + self.pageColorComboBox.connect('changed', self.handleColor) + self.pageColorBox.pack_start(self.pageColorLabel, False, False, padding = 5) + self.pageColorBox.pack_end(self.pageColorComboBox, False, False, padding = 55) + + self.pageSeparator = gtk.HSeparator() + self.pageSeparator.set_size_request(20, -1) + + self.transposeBox = gtk.HBox() + self.transposeLabel = gtk.Label(_('Transposition: ')) + self.transposeDownButton = ImageButton(Config.TAM_TAM_ROOT + '/icons/arrow-down.svg') + self.transposeDownButton.connect('clicked', self.stepPitch, -1) + self.transposeUpButton = ImageButton(Config.TAM_TAM_ROOT + '/icons/arrow-up.svg') + self.transposeUpButton.connect('clicked', self.stepPitch, 1) + self.transposeCheckButton = gtk.CheckButton() + self.transposeCheckButton.connect('toggled', self.handleGeneCheckButton) + self.geneCheckButtonDic['transpose'] = self.transposeCheckButton + self.transposeBox.pack_start(self.transposeLabel, False, False, padding = 5) + self.transposeBox.pack_end(self.transposeCheckButton, False, False, padding = 5) + self.transposeBox.pack_end(self.transposeUpButton, False, False, padding = 50) + self.transposeBox.pack_end(self.transposeDownButton, False, False, padding = 5) + + self.volumeBox = gtk.HBox() + self.volumeLabel = gtk.Label(_('Volume') + ': ') + self.volumeDownButton = ImageButton(Config.TAM_TAM_ROOT + '/icons/arrow-down.svg') + self.volumeDownButton.connect('clicked', self.stepVolume, -0.1) + self.volumeUpButton = ImageButton(Config.TAM_TAM_ROOT + '/icons/arrow-up.svg') + self.volumeUpButton.connect('clicked', self.stepVolume, 0.1) + self.volumeCheckButton = gtk.CheckButton() + self.volumeCheckButton.connect('toggled', self.handleGeneCheckButton) + self.geneCheckButtonDic['volume'] = self.volumeCheckButton + self.volumeBox.pack_start(self.volumeLabel, False, False, padding = 5) + self.volumeBox.pack_end(self.volumeCheckButton, False, False, padding = 5) + self.volumeBox.pack_end(self.volumeUpButton, False, False, padding = 50) + self.volumeBox.pack_end(self.volumeDownButton, False, False, padding = 5) + + self.panBox = gtk.HBox() + self.panLabel = gtk.Label(_('Pan: ')) + self.panSliderAdj = gtk.Adjustment(0.5, 0, 1, .1, .1, 0) + self.panSliderAdj.connect('value-changed', self.handlePan) + self.panSlider = gtk.HScale(adjustment = self.panSliderAdj) + self.panSlider.set_size_request(200,-1) + self.panSlider.set_value_pos(gtk.POS_RIGHT) + self.panSlider.set_digits(2) + self.panSlider.set_update_policy(gtk.UPDATE_DISCONTINUOUS) + self.panCheckButton = gtk.CheckButton() + self.panCheckButton.connect('toggled', self.handleGeneCheckButton) + self.geneCheckButtonDic['pan'] = self.panCheckButton + self.panBox.pack_start(self.panLabel, False, False, padding = 5) + self.panBox.pack_end(self.panCheckButton, False, False, padding = 5) + self.panBox.pack_end(self.panSlider, False, False, padding = 5) + + self.reverbBox = gtk.HBox() + self.reverbLabel = gtk.Label(_('Reverb') + ': ') + self.reverbSliderAdj = gtk.Adjustment(0.1, 0, 1, 0.1, 0.1, 0) + self.reverbSliderAdj.connect("value-changed", self.handleReverb) + self.reverbSlider = gtk.HScale(adjustment = self.reverbSliderAdj) + self.reverbSlider.set_size_request(200,-1) + self.reverbSlider.set_value_pos(gtk.POS_RIGHT) + self.reverbSlider.set_digits(2) + self.reverbSlider.set_update_policy(gtk.UPDATE_DISCONTINUOUS) + self.reverbCheckButton = gtk.CheckButton() + self.reverbCheckButton.connect('toggled', self.handleGeneCheckButton) + self.geneCheckButtonDic['reverb'] = self.reverbCheckButton + self.reverbBox.pack_start(self.reverbLabel, False, False, padding = 5) + self.reverbBox.pack_end(self.reverbCheckButton, False, False, padding = 5) + self.reverbBox.pack_end(self.reverbSlider, False, False, padding = 5) + + self.attackDurBox = gtk.HBox() + self.attackDurLabel = gtk.Label(_('Attack duration') + ': ') + self.attackDurSliderAdj = gtk.Adjustment(0.04, 0.03, 1, .01, .01, 0) + self.attackDurSliderAdj.connect('value-changed', self.handleAttack) + self.attackDurSlider = gtk.HScale(adjustment = self.attackDurSliderAdj) + self.attackDurSlider.set_size_request(200,-1) + self.attackDurSlider.set_value_pos(gtk.POS_RIGHT) + self.attackDurSlider.set_digits(2) + self.attackDurSlider.set_update_policy(gtk.UPDATE_DISCONTINUOUS) + self.attackDurCheckButton = gtk.CheckButton() + self.attackDurCheckButton.connect('toggled', self.handleGeneCheckButton) + self.geneCheckButtonDic['attack'] = self.attackDurCheckButton + self.attackDurBox.pack_start(self.attackDurLabel, False, False, padding = 5) + self.attackDurBox.pack_end(self.attackDurCheckButton, False, False, padding = 5) + self.attackDurBox.pack_end(self.attackDurSlider, False, False, padding = 5) + + self.decayDurBox = gtk.HBox() + self.decayDurLabel = gtk.Label(_('Decay duration') + ': ') + self.decayDurSliderAdj = gtk.Adjustment(0.31, 0.03, 1, .01, .01, 0) + self.decayDurSliderAdj.connect('value-changed', self.handleDecay) + self.decayDurSlider = gtk.HScale(adjustment = self.decayDurSliderAdj) + self.decayDurSlider.set_size_request(200,-1) + self.decayDurSlider.set_value_pos(gtk.POS_RIGHT) + self.decayDurSlider.set_digits(2) + self.decayDurSlider.set_update_policy(gtk.UPDATE_DISCONTINUOUS) + self.decayDurCheckButton = gtk.CheckButton() + self.decayDurCheckButton.connect('toggled', self.handleGeneCheckButton) + self.geneCheckButtonDic['decay'] = self.decayDurCheckButton + self.decayDurBox.pack_start(self.decayDurLabel, False, False, padding = 5) + self.decayDurBox.pack_end(self.decayDurCheckButton, False, False, padding = 5) + self.decayDurBox.pack_end(self.decayDurSlider, False, False, padding = 5) + + self.filterTypeBox = gtk.HBox() + self.filterTypeLabel = gtk.Label(_('Filter Type: ')) + self.filterTypeComboBox = BigComboBox() + for filtertype in self.filterTypes: + self.filterTypeComboBox.append_item(self.filterTypes.index(filtertype), filtertype) + self.filterTypeComboBox.connect('changed', self.handleFilterTypes) + self.filterTypeBox.pack_start(self.filterTypeLabel, False, False, padding = 5) + self.filterTypeBox.pack_end(self.filterTypeComboBox, False, False, padding = 55) + + self.filterCutoffBox = gtk.HBox() + self.filterCutoffLabel = gtk.Label(_('Filter cutoff') + ': ') + self.filterCutoffSliderAdj = gtk.Adjustment(1000, 100, 7000, 100, 100, 0) + self.filterCutoffSliderAdj.connect('value-changed', self.handleFilter) + self.filterCutoffSlider = gtk.HScale(adjustment = self.filterCutoffSliderAdj) + self.filterCutoffSlider.set_size_request(200,-1) + self.filterCutoffSlider.set_value_pos(gtk.POS_RIGHT) + self.filterCutoffSlider.set_digits(0) + self.filterCutoffSlider.set_update_policy(gtk.UPDATE_DISCONTINUOUS) + self.filterCutoffCheckButton = gtk.CheckButton() + self.filterCutoffCheckButton.connect('toggled', self.handleGeneCheckButton) + self.geneCheckButtonDic['filter'] = self.filterCutoffCheckButton + self.filterCutoffBox.pack_start(self.filterCutoffLabel, False, False, padding = 5) + self.filterCutoffBox.pack_end(self.filterCutoffCheckButton, False, False, padding = 5) + self.filterCutoffBox.pack_end(self.filterCutoffSlider, False, False, padding = 5) + + self.generationMainBox = gtk.VBox() + self.generationSeparator = gtk.HSeparator() + self.generationLabel = gtk.Label(_('Generation')) + + self.generationTypeBox = gtk.HBox() + self.generationTypeLabel = gtk.Label(_('Type') + ': ') + self.generationTypeComboBox = BigComboBox() + for genetype in self.geneTypes: + self.generationTypeComboBox.append_item(self.geneTypes.index(genetype), genetype) + self.generationTypeComboBox.connect('changed', self.handleGeneTypes) + self.generationTypeComboBox.set_active(0) + self.generationTypeBox.pack_start(self.generationTypeLabel, False, False, padding = 5) + self.generationTypeBox.pack_end(self.generationTypeComboBox, False, False, padding = 55) + + self.minimumBox = gtk.HBox() + self.minimumLabel = gtk.Label(_('Minimum') + ': ') + self.minimumSliderAdj = gtk.Adjustment(0, 0, 100, 1, 1, 0) + self.minimumSliderAdj.connect('value-changed', self.handleMinimum) + self.minimumSlider = gtk.HScale(adjustment = self.minimumSliderAdj) + self.minimumSlider.set_size_request(200,-1) + self.minimumSlider.set_update_policy(gtk.UPDATE_DISCONTINUOUS) + self.minimumSlider.set_value_pos(gtk.POS_RIGHT) + self.minimumSlider.set_digits(0) + self.minimumBox.pack_start(self.minimumLabel, False, False, padding = 5) + self.minimumBox.pack_end(self.minimumSlider, False, False, padding = 52) + + self.maximumBox = gtk.HBox() + self.maximumLabel = gtk.Label(_('Maximum') + ': ') + self.maximumSliderAdj = gtk.Adjustment(100, 0, 100, 1, 1, 0) + self.maximumSliderAdj.connect('value-changed', self.handleMaximum) + self.maximumSlider = gtk.HScale(adjustment = self.maximumSliderAdj) + self.maximumSlider.set_size_request(200,-1) + self.maximumSlider.set_update_policy(gtk.UPDATE_DISCONTINUOUS) + self.maximumSlider.set_value_pos(gtk.POS_RIGHT) + self.maximumSlider.set_digits(0) + self.maximumBox.pack_start(self.maximumLabel, False, False, padding = 5) + self.maximumBox.pack_end(self.maximumSlider, False, False, padding = 52) + + self.randomBox = gtk.HBox() + self.randomLabel = gtk.Label(_('Random') + ': ') + self.randomSliderAdj = gtk.Adjustment(20, 0, 100, 1, 1, 0) + self.randomSliderAdj.connect('value-changed', self.handleRandom) + self.randomSlider = gtk.HScale(adjustment = self.randomSliderAdj) + self.randomSlider.set_size_request(200,-1) + self.randomSlider.set_update_policy(gtk.UPDATE_DISCONTINUOUS) + self.randomSlider.set_value_pos(gtk.POS_RIGHT) + self.randomSlider.set_digits(0) + self.randomBox.pack_start(self.randomLabel, False, False, padding = 5) + self.randomBox.pack_end(self.randomSlider, False, False, padding = 52) + + self.decisionBox = gtk.HBox() + self.acceptButton = ImageButton(Config.TAM_TAM_ROOT + '/icons/accept.svg') + self.acceptButton.connect('clicked', self.acceptGeneration) + self.cancelButton = ImageButton(Config.TAM_TAM_ROOT + '/icons/cancel.svg') + self.cancelButton.connect('clicked', self.resetGeneCheckButton) + self.decisionBox.pack_start(self.cancelButton, False, False, padding = 5) + self.decisionBox.pack_start(self.acceptButton, False, False, padding = 5) + + self.mainBox.pack_start(self.gridDivisionBox, padding = 3) + self.mainBox.pack_start(self.pageColorBox, padding = 3) + self.mainBox.pack_start(self.pageSeparator, padding = 10) + self.mainBox.pack_start(self.transposeBox, padding = 3) + self.mainBox.pack_start(self.volumeBox, padding = 3) + self.mainBox.pack_start(self.panBox, padding = 3) + self.mainBox.pack_start(self.reverbBox, padding = 3) + self.mainBox.pack_start(self.attackDurBox, padding = 3) + self.mainBox.pack_start(self.decayDurBox, padding = 3) + self.mainBox.pack_start(self.filterTypeBox, padding = 3) + self.mainBox.pack_start(self.filterCutoffBox, padding = 3) + self.generationMainBox.pack_start(self.generationSeparator, padding = 5) + self.generationMainBox.pack_start(self.generationLabel, padding = 10) + self.generationMainBox.pack_start(self.generationTypeBox, padding = 3) + self.generationMainBox.pack_start(self.minimumBox, padding = 3) + self.generationMainBox.pack_start(self.maximumBox, padding = 3) + self.generationMainBox.pack_start(self.randomBox, padding = 3) + self.generationMainBox.pack_start(self.decisionBox, padding = 3) + self.mainBox.pack_start(self.generationMainBox, padding = 3) + self.mainBox.show_all() + + self.generationMainBox.hide() + + self.set_content(self.mainBox) + + def handlePopup(self, widget, data = None): + if self.edit.getContext() == 0: #Page + self.setContext('page', self.edit._generateToolbar._generationPalette.scale, self.edit.tuneInterface.getSelectedIds()) + elif self.edit.getContext() == 1: #Track + self.setContext('track', self.edit._generateToolbar._generationPalette.scale, self.edit.tuneInterface.getSelectedIds(), [ i for i in range(Config.NUMBER_OF_TRACKS) if self.edit.trackSelected[i] ]) + elif self.edit.getContext() == 2: #Note + ids = self.edit.trackInterface.getSelectedNotes() + notes = { self.edit.displayedPage: {} } + for t in range(Config.NUMBER_OF_TRACKS): + if len(ids[t]): + notes[self.edit.displayedPage][t] = [ self.edit.noteDB.getNote( self.edit.displayedPage, t, id ) for id in ids[t] ] + self.setContext('note', self.edit._generateToolbar._generationPalette.scale, notes = notes) + + def handlePopdown(self, widget, data = None): + self.resetGeneCheckButton(self.cancelButton) + + def setContext( self, context, scale, pageIds = None, trackIds = None, notes = {} ): + self.context = context + self.scale = GenerationConstants.SCALES[scale] + self.notes = {} + self.pageIds = pageIds + self.trackIds = trackIds + + if context == "page": + self.trackIds = [0,1,2,3,4] + for p in pageIds: + self.notes[p] = {} + for t in range(Config.NUMBER_OF_TRACKS): + self.notes[p][t] = self.edit.noteDB.getNotesByTrack( p, t ) + page = self.edit.noteDB.getPage(pageIds[0]) + self.gridDivisionSliderAdj.set_value(page.beats) + elif context == "track": + for p in pageIds: + self.notes[p] = {} + for t in trackIds: + self.notes[p][t] = self.edit.noteDB.getNotesByTrack( p, t ) + else: + self.notes = notes + self.pageIds = self.notes.keys() + self.trackIds = self.notes[self.pageIds[0]].keys() + + for p in self.notes: + for t in self.notes[p]: + if len(self.notes[p][t]): + # initialize values from first note + self.setup = True + n = self.notes[p][t][0] + self.panSliderAdj.set_value( n.cs.pan ) + self.reverbSliderAdj.set_value( n.cs.reverbSend ) + self.attackDurSliderAdj.set_value( n.cs.attack ) + self.decayDurSliderAdj.set_value( n.cs.decay ) + self.filterTypeComboBox.set_active(n.cs.filterType) + self.currentFilterType = n.cs.filterType + self.filterCutoffSliderAdj.set_value( n.cs.filterCutoff ) + self.setup = False + + def acceptGeneration( self, widget ): + valList = [self.geneMinimum, self.geneMaximum, self.geneRandom] + if self.geneCheckButtonDic['transpose'].get_active(): self.algoPitch(valList, self.algorithm) + if self.geneCheckButtonDic['volume'].get_active(): self.algoVolume(valList, self.algorithm) + if self.geneCheckButtonDic['pan'].get_active(): self.algoPan(valList, self.algorithm) + if self.geneCheckButtonDic['reverb'].get_active(): self.algoReverb(valList, self.algorithm) + if self.geneCheckButtonDic['attack'].get_active(): self.algoAttack(valList, self.algorithm) + if self.geneCheckButtonDic['decay'].get_active(): self.algoDecay(valList, self.algorithm) + if self.geneCheckButtonDic['filter'].get_active(): self.algoCutoff(valList, self.algorithm) + + def resetGeneCheckButton(self, widget): + if self.hidden: + self.generationMainBox.hide() + + for key in self.geneCheckButtonDic: + self.geneCheckButtonDic[key].set_active(False) + + def handleGeneCheckButton(self, widget, data = None): + self.hidden = True + if widget.get_active(): + self.generationMainBox.show() + else: + for key in self.geneCheckButtonDic: + if self.geneCheckButtonDic[key].get_active(): + self.hidden = False + if self.hidden: + self.generationMainBox.hide() + + + def handleBeat(self, widget, signal_id): + beats = int(widget.get_adjustment().value) + stream = [] + for page in self.pageIds: + stream += [ page, beats ] + if len(stream): + self.edit.noteDB.updatePages( [ PARAMETER.PAGE_BEATS, len(stream)//2 ] + stream ) + + def handleColor(self, widget): + index = widget.props.value + stream = [] + for page in self.pageIds: + stream += [ page, index ] + if len(stream): + self.edit.noteDB.updatePages( [ PARAMETER.PAGE_COLOR, len(stream)//2 ] + stream ) + + def stepPitch(self, widget, step): + stream = [] + for p in self.notes: + for t in self.notes[p]: + substream = [] + if step > 0: + if t != Config.NUMBER_OF_TRACKS-1: # regular note + for n in self.notes[p][t]: + if n.cs.pitch != Config.MAXIMUM_PITCH: + substream += [ n.id, min( Config.MAXIMUM_PITCH, n.cs.pitch + step ) ] + else: # drum note + for n in self.notes[p][t]: + if n.cs.pitch != Config.MAXIMUM_PITCH_DRUM: + substream += [ n.id, min( Config.MAXIMUM_PITCH_DRUM, n.cs.pitch + step*Config.PITCH_STEP_DRUM ) ] + else: + if t != Config.NUMBER_OF_TRACKS-1: # regular note + for n in self.notes[p][t]: + if n.cs.pitch != Config.MINIMUM_PITCH: + substream += [ n.id, max( Config.MINIMUM_PITCH, n.cs.pitch + step ) ] + else: # drum note + for n in self.notes[p][t]: + if n.cs.pitch != Config.MINIMUM_PITCH_DRUM: + substream += [ n.id, max( Config.MINIMUM_PITCH_DRUM, n.cs.pitch + step*Config.PITCH_STEP_DRUM ) ] + if len(substream): + stream += [ p, t, PARAMETER.PITCH, len(substream)//2 ] + substream + if len(stream): + self.edit.noteDB.updateNotes( stream + [-1] ) + + def algoPitch( self, list, algorithm ): + maxValue = max(list[0], list[1]) + scaleLength = len(self.scale)-1 + stream = [] + for t in range(len(self.trackIds)): + trackLength = 0 + for p in range(len(self.pageIds)): + trackLength += len(self.notes[self.pageIds[p]][self.trackIds[t]]) + algorithm.__init__(list[0], list[1], trackLength) + for p in range(len(self.pageIds)): + substream = [] + if self.trackIds[t] != Config.NUMBER_OF_TRACKS-1: + for n in self.notes[self.pageIds[p]][self.trackIds[t]]: + val = algorithm.getNextValue(list[2], maxValue) + substream += [ n.id, self.scale[int(val*0.01*scaleLength)]+36 ] + if len(substream): + stream += [ self.pageIds[p], self.trackIds[t], PARAMETER.PITCH, len(substream)//2 ] + substream + else: + for n in self.notes[self.pageIds[p]][self.trackIds[t]]: + val = algorithm.getNextValue(list[2], maxValue) + val = int((val*0.12)*2+24) + if val in GenerationConstants.DRUMPITCH.keys(): + val = GenerationConstants.DRUMPITCH[val] + substream += [ n.id, val ] + if len(substream): + stream += [ self.pageIds[p], self.trackIds[t], PARAMETER.PITCH, len(substream)//2 ] + substream + if len(stream): + self.edit.noteDB.updateNotes( stream + [-1] ) + + def stepVolume(self, widget, step): + stream = [] + for p in self.notes: + for t in self.notes[p]: + substream = [] + if step > 0: + for n in self.notes[p][t]: + if n.cs.amplitude != Config.MAXIMUM_AMPLITUDE: + substream += [ n.id, min( Config.MAXIMUM_AMPLITUDE, n.cs.amplitude + step ) ] + else: + for n in self.notes[p][t]: + if n.cs.amplitude != Config.MINIMUM_AMPLITUDE: + substream += [ n.id, max( Config.MINIMUM_AMPLITUDE, n.cs.amplitude + step ) ] + if len(substream): + stream += [ p, t, PARAMETER.AMPLITUDE, len(substream)//2 ] + substream + if len(stream): + self.edit.noteDB.updateNotes( stream + [-1] ) + + + def algoVolume( self, list, algorithm ): + maxValue = max(list[0], list[1]) + stream = [] + for t in range(len(self.trackIds)): + trackLength = 0 + for p in range(len(self.pageIds)): + trackLength += len(self.notes[self.pageIds[p]][self.trackIds[t]]) + algorithm.__init__(list[0], list[1], trackLength) + for p in range(len(self.pageIds)): + substream = [] + for n in self.notes[self.pageIds[p]][self.trackIds[t]]: + val = algorithm.getNextValue(list[2], maxValue) + substream += [ n.id, min( Config.MAXIMUM_AMPLITUDE, val*0.01 ) ] + if len(substream): + stream += [ self.pageIds[p], self.trackIds[t], PARAMETER.AMPLITUDE, len(substream)//2 ] + substream + if len(stream): + self.edit.noteDB.updateNotes( stream + [-1] ) + + def handlePan(self, adjust): + if not self.setup: + stream = [] + for p in self.notes: + for t in self.notes[p]: + if len(self.notes[p][t]): + stream += [ p, t, PARAMETER.PAN, len(self.notes[p][t]) ] + for n in self.notes[p][t]: + stream += [ n.id, adjust.value ] + if len(stream): + self.edit.noteDB.updateNotes( stream + [-1] ) + + def algoPan( self, list, algorithm ): + maxValue = max(list[0], list[1]) + stream = [] + for t in range(len(self.trackIds)): + trackLength = 0 + for p in range(len(self.pageIds)): + trackLength += len(self.notes[self.pageIds[p]][self.trackIds[t]]) + algorithm.__init__(list[0], list[1], trackLength) + for p in range(len(self.pageIds)): + substream = [] + for n in self.notes[self.pageIds[p]][self.trackIds[t]]: + val = algorithm.getNextValue(list[2], maxValue) + substream += [ n.id, val*0.01 ] + if len(substream): + stream += [ self.pageIds[p], self.trackIds[t], PARAMETER.PAN, len(substream)//2 ] + substream + if len(stream): + self.edit.noteDB.updateNotes( stream + [-1] ) + + def handleReverb(self, adjust): + if not self.setup: + stream = [] + for p in self.notes: + for t in self.notes[p]: + if len(self.notes[p][t]): + stream += [ p, t, PARAMETER.REVERB, len(self.notes[p][t]) ] + for n in self.notes[p][t]: + stream += [ n.id, adjust.value ] + if len(stream): + self.edit.noteDB.updateNotes( stream + [-1] ) + + def algoReverb( self, list, algorithm ): + maxValue = max(list[0], list[1]) + stream = [] + for t in range(len(self.trackIds)): + trackLength = 0 + for p in range(len(self.pageIds)): + trackLength += len(self.notes[self.pageIds[p]][self.trackIds[t]]) + algorithm.__init__(list[0], list[1], trackLength) + for p in range(len(self.pageIds)): + substream = [] + for n in self.notes[self.pageIds[p]][self.trackIds[t]]: + val = algorithm.getNextValue(list[2], maxValue) + substream += [ n.id, val*0.02 ] + if len(substream): + stream += [ self.pageIds[p], self.trackIds[t], PARAMETER.REVERB, len(substream)//2 ] + substream + if len(stream): + self.edit.noteDB.updateNotes( stream + [-1] ) + + def handleAttack(self, adjust): + if not self.setup: + stream = [] + for p in self.notes: + for t in self.notes[p]: + if len(self.notes[p][t]): + stream += [ p, t, PARAMETER.ATTACK, len(self.notes[p][t]) ] + for n in self.notes[p][t]: + stream += [ n.id, adjust.value ] + if len(stream): + self.edit.noteDB.updateNotes( stream + [-1] ) + + def algoAttack( self, list, algorithm ): + maxValue = max(list[0], list[1]) + stream = [] + for t in range(len(self.trackIds)): + trackLength = 0 + for p in range(len(self.pageIds)): + trackLength += len(self.notes[self.pageIds[p]][self.trackIds[t]]) + algorithm.__init__(list[0], list[1], trackLength) + for p in range(len(self.pageIds)): + substream = [] + for n in self.notes[self.pageIds[p]][self.trackIds[t]]: + val = algorithm.getNextValue(list[2], maxValue) + substream += [ n.id, val*0.01 ] + if len(substream): + stream += [ self.pageIds[p], self.trackIds[t], PARAMETER.ATTACK, len(substream)//2 ] + substream + if len(stream): + self.edit.noteDB.updateNotes( stream + [-1] ) + + def handleDecay(self, adjust): + if not self.setup: + stream = [] + for p in self.notes: + for t in self.notes[p]: + if len(self.notes[p][t]): + stream += [ p, t, PARAMETER.DECAY, len(self.notes[p][t]) ] + for n in self.notes[p][t]: + stream += [ n.id, adjust.value ] + if len(stream): + self.edit.noteDB.updateNotes( stream + [-1] ) + + + def algoDecay( self, list, algorithm ): + maxValue = max(list[0], list[1]) + stream = [] + for t in range(len(self.trackIds)): + trackLength = 0 + for p in range(len(self.pageIds)): + trackLength += len(self.notes[self.pageIds[p]][self.trackIds[t]]) + algorithm.__init__(list[0], list[1], trackLength) + for p in range(len(self.pageIds)): + substream = [] + for n in self.notes[self.pageIds[p]][self.trackIds[t]]: + val = algorithm.getNextValue(list[2], maxValue) + substream += [ n.id, val*0.01 ] + if len(substream): + stream += [ self.pageIds[p], self.trackIds[t], PARAMETER.DECAY, len(substream)//2 ] + substream + if len(stream): + self.edit.noteDB.updateNotes( stream + [-1] ) + + def handleFilterTypes(self, widget): + self.currentFilterType = widget.props.value + + if not self.currentFilterType: + self.filterCutoffSlider.set_sensitive(False) + else: + self.filterCutoffSlider.set_sensitive(True) + + if not self.setup: + if self.currentFilterType: + typestream = [] + cutoffstream = [] + cutoff = self.filterCutoffSliderAdj.value + for p in self.notes: + for t in self.notes[p]: + if len(self.notes[p][t]): + substream = [] + typestream += [ p, t, PARAMETER.FILTERTYPE, len(self.notes[p][t]) ] + for n in self.notes[p][t]: + typestream += [ n.id, self.currentFilterType ] + if n.cs.filterCutoff != cutoff: + substream += [ n.id, cutoff ] + if len(substream): + cutoffstream += [ p, t, PARAMETER.FILTERCUTOFF, len(substream)//2 ] + substream + if len(typestream): + self.edit.noteDB.updateNotes( typestream + [-1] ) + if len(cutoffstream): + self.edit.noteDB.updateNotes( cutoffstream + [-1] ) + else: + self.currentFilterType = 0 + typestream = [] + for p in self.notes: + for t in self.notes[p]: + if len(self.notes[p][t]): + typestream += [ p, t, PARAMETER.FILTERTYPE, len(self.notes[p][t]) ] + for n in self.notes[p][t]: + typestream += [ n.id, 0 ] + if len(typestream): + self.edit.noteDB.updateNotes( typestream + [-1] ) + + def handleFilter(self, adjust): + stream = [] + for p in self.notes: + for t in self.notes[p]: + if len(self.notes[p][t]): + stream += [ p, t, PARAMETER.FILTERCUTOFF, len(self.notes[p][t]) ] + for n in self.notes[p][t]: + stream += [ n.id, adjust.value ] + if len(stream): + self.edit.noteDB.updateNotes( stream + [-1] ) + + def algoCutoff( self, list, algorithm ): + maxValue = max(list[0], list[1]) + stream = [] + for t in range(len(self.trackIds)): + trackLength = 0 + for p in range(len(self.pageIds)): + trackLength += len(self.notes[self.pageIds[p]][self.trackIds[t]]) + algorithm.__init__(list[0], list[1], trackLength) + for p in range(len(self.pageIds)): + substream = [] + for n in self.notes[self.pageIds[p]][self.trackIds[t]]: + val = algorithm.getNextValue(list[2], maxValue) + substream += [ n.id, val*70+100 ] + if len(substream): + stream += [ self.pageIds[p], self.trackIds[t], PARAMETER.FILTERCUTOFF, len(substream)//2 ] + substream + if len(stream): + self.edit.noteDB.updateNotes( stream + [-1] ) + + def handleGeneTypes(self, widget): + self.algorithm = self.algoTypes[widget.props.value] + + def handleMinimum(self, adjust): + self.geneMinimum = int(adjust.value) + + def handleMaximum(self, adjust): + self.geneMaximum = int(adjust.value) + + def handleRandom(self, adjust): + self.geneRandom = int(adjust.value) diff --git a/Edit/HitInterface.py b/Edit/HitInterface.py new file mode 100644 index 0000000..049c2b0 --- /dev/null +++ b/Edit/HitInterface.py @@ -0,0 +1,195 @@ +import pygtk +pygtk.require( '2.0' ) +import gtk + +from common.Util.NoteDB import PARAMETER +from Edit.NoteInterface import NoteInterface +import common.Config as Config + +class HitInterface( NoteInterface ): + + def __init__( self, noteDB, owner, note ): + NoteInterface.__init__( self, noteDB, owner, note ) + + self.width = self.height = Config.HIT_HEIGHT + self.imgWidth = self.imgHeight = Config.HIT_HEIGHT + Config.HIT_IMAGE_PADDING_MUL2 + + self.firstTransform = True + self.updateTransform() + + def updateTransform( self ): + if self.note.page in self.owner.getActivePages(): + if not self.firstTransform: + oldX = self.imgX + oldY = self.imgY + oldEndX = self.imgX + self.imgWidth + dirty = True + else: + dirty = False + + beats = self.noteDB.getPage( self.note.page ).beats + if self.note.cs.onset != self.oldOnset or beats != self.oldBeats: + self.x = self.owner.ticksToPixels( beats, self.note.cs.onset ) + self.x += self.origin[0] + self.imgX = self.x - Config.NOTE_IMAGE_PADDING + self.oldOnset = self.note.cs.onset + self.oldBeats = beats + if self.note.cs.pitch != self.oldPitch: + self.y = self.owner.pitchToPixelsDrum( self.note.cs.pitch ) + self.origin[1] + self.imgY = self.y - Config.NOTE_IMAGE_PADDING + self.oldPitch = self.note.cs.pitch + + if dirty: + if self.firstTransform: + self.owner.invalidate_rect( self.imgX, self.imgY, self.imgWidth, self.imgHeight, self.note.page ) + self.firstTransform = False + else: + x = min( self.imgX, oldX ) + y = min( self.imgY, oldY ) + endx = max( self.imgX + self.imgWidth, oldEndX ) + endy = max( self.imgY, oldY ) + self.imgHeight + self.owner.invalidate_rect( x, y, endx-x, endy-y, self.note.page ) + + self.firstTransform = False + + def updateDragLimits( self, dragLimits, leftBound, rightBound, widthBound, maxRightBound ): + left = 0 - self.note.cs.onset + right = maxRightBound - self.note.cs.duration - self.note.cs.onset + up = Config.MAXIMUM_PITCH_DRUM - self.note.cs.pitch + down = Config.MINIMUM_PITCH_DRUM - self.note.cs.pitch + + if dragLimits[0][0] < left: dragLimits[0][0] = left + if dragLimits[0][1] > right: dragLimits[0][1] = right + if dragLimits[1][0] < down: dragLimits[1][0] = down + if dragLimits[1][1] > up: dragLimits[1][1] = up + + # store the current loc as a reference point + self.baseOnset = self.note.cs.onset + self.basePitch = self.note.cs.pitch + + #======================================================= + # Events + + # handleButtonPress returns: + # -1, event occurs before us so don't bother checking any later notes + # 0, event didn't hit + # 1, event was handled + def handleButtonPress( self, emitter, event ): + eX = event.x - self.x + if eX < 0: + return -1 # event occurs before us, no point in checking further + if eX > self.width: + return 0 # no X overlap + + eY = event.y - self.y + if eY < 0 or eY > self.height: + return 0 # not a hit + + if event.button == 3: + print "Show some note parameters!?!" + #self.noteParameters = NoteParametersWindow( self.note, self.getNoteParameters ) + return 1 # handled + + playSample = False + + if event.type == gtk.gdk._2BUTTON_PRESS: # select bar + self.potentialDeselect = False + start = 0 + check = self.note.cs.onset - Config.TICKS_PER_BEAT + while start <= check: start += Config.TICKS_PER_BEAT + stop = start + Config.TICKS_PER_BEAT + check += 1 + while stop < check: stop += Config.TICKS_PER_BEAT + emitter.selectNotesByBar( self.note.track, start, stop ) + elif event.type == gtk.gdk._3BUTTON_PRESS: # select track + self.potentialDeselect = False + emitter.selectNotesByTrack( self.note.track ) + else: + if self.getSelected(): # we already selected, might want to delected + self.potentialDeselect = True + else: + emitter.selectNotes( { self.note.track: [ self ] } ) + playSample = True + + percent = eX/self.width + if percent < 0.5: emitter.setCurrentAction( "note-drag-onset", self ) + else: + emitter.setCurrentAction( "note-drag-pitch-drum", self ) + if playSample: self.playSampleNote() + + return 1 + + def noteDragPitch( self, dp, stream ): + self.potentialDeselect = False + if dp != self.lastDragP and not dp%2: + self.lastDragP = dp + stream += [ self.note.id, self.basePitch + dp ] + + def noteDragDuration( self, dd, stream ): + return + + def noteDecOnset( self, step, leftBound, stream ): + if self.selected: + if leftBound < self.note.cs.onset: + onset = max( self.note.cs.onset+step, leftBound ) + stream += [ self.note.id, onset ] + return leftBound + + def noteIncOnset( self, step, rightBound, stream ): + if self.selected: + if rightBound > self.end: + onset = min( self.end+step, rightBound ) - self.note.cs.duration + stream += [ self.note.id, onset ] + return rightBound + + def noteDecPitch( self, step, stream ): + if self.note.cs.pitch > Config.MINIMUM_PITCH_DRUM: + stream += [ self.note.id, max( self.note.cs.pitch+2*step, Config.MINIMUM_PITCH_DRUM ) ] + + def noteIncPitch( self, step, stream ): + if self.note.cs.pitch < Config.MAXIMUM_PITCH_DRUM: + stream += [ self.note.id, min( self.note.cs.pitch+2*step, Config.MAXIMUM_PITCH_DRUM ) ] + + def noteDecDuration( self, step, stream ): + return + + def noteIncDuration( self, step, rightBound, stream ): + return + + # updateTooltip returns: + # -1, event occurs before us so don't bother checking any later notes + # 0, event didn't hit + # 1, event was handled + def updateTooltip( self, emitter, event ): + eX = event.x - self.x + if eX < 0: + return -1 # event occurs before us, no point in checking further + if eX > self.width: + return 0 # no X overlap + + eY = event.y - self.y + if eY < 0 or eY > self.height: + return 0 # not a hit + + percent = eX/self.width + if percent < 0.5: emitter.setCursor("drag-onset") + else: emitter.setCursor("drag-pitch") + + return 1 # we handled it + + #======================================================= + # Draw + + def draw( self, win, gc, startX, stopX ): + if stopX < self.imgX: return False # we don't need to draw and no one after us will draw + if startX > self.imgX + self.imgWidth: return True # we don't need to draw, but maybe a later note does + + gc.foreground = self.color + win.draw_rectangle( gc, True, self.x+2, self.y+2, self.width-4, self.height-4 ) + + if self.selected: img = self.imageSelected + else: img = self.image + win.draw_pixbuf( gc, img, 0, 0, self.imgX, self.imgY, self.imgWidth, self.imgHeight, gtk.gdk.RGB_DITHER_NONE ) + + return True # we drew something + diff --git a/Edit/MainWindow.py b/Edit/MainWindow.py new file mode 100644 index 0000000..0a2c44f --- /dev/null +++ b/Edit/MainWindow.py @@ -0,0 +1,2281 @@ +import pygtk +pygtk.require( '2.0' ) +import gtk + +import gobject + +import common.Util.Instruments +import common.Util.InstrumentDB as InstrumentDB +from common.Util.ThemeWidgets import * +from common.Util.Profiler import TP +from common.Util import NoteDB +from common.Util.NoteDB import PARAMETER +from common.Util import ControlStream +from common.Util.CSoundClient import new_csound_client +from common.Util.CSoundNote import CSoundNote +from EditToolbars import mainToolbar +from EditToolbars import generateToolbar +from gettext import gettext as _ +from subprocess import Popen +from sugar.graphics.palette import Palette, Invoker +from sugar.datastore import datastore +import time +import os +import commands +import random +from common.Util import OS +from common.port.scrolledbox import HScrolledBox +from sugar.graphics import style + +class CONTEXT: + PAGE = 0 + TRACK = 1 + NOTE = 2 + +import common.Config as Config + +from common.Generation.GenerationConstants import GenerationConstants +from Edit.Properties import Properties +from Edit.TrackInterface import TrackInterface, TrackInterfaceParasite +from Edit.TuneInterface import TuneInterface, TuneInterfaceParasite + +from common.Generation.Generator import generator1, GenerationParameters + +Tooltips = Config.Tooltips() +KEY_MAP_PIANO = Config.KEY_MAP_PIANO + +#----------------------------------- +# The main TamTam window +#----------------------------------- +class MainWindow( gtk.EventBox ): + + def __init__( self, activity ): + gtk.EventBox.__init__(self) + self.instrumentDB = InstrumentDB.getRef() + self.csnd = new_csound_client() + self.tooltips = gtk.Tooltips() + self.activity = activity + + for i in [6,7,8,9,10]: + self.csnd.setTrackVolume(100, i) + self.trackCount = 6 + + self.scale = GenerationConstants.DEFAULT_SCALE + + # META ALGO: [section, variation or not, nPages] A B A C + self.tuneForm = [[0, False, 2], [1, False, 4], [0, True, 2], [2, False, 2]] + + def init_data( ): + TP.ProfileBegin("init_data") + self._data = {} + + #[ volume, ... ] + self._data['track_volume'] = [ Config.DEFAULT_VOLUME ] * Config.NUMBER_OF_TRACKS + self._data['track_mute'] = [ 1.0 ] * Config.NUMBER_OF_TRACKS + + #[ instrument index, ... ] + self.trackInstrumentDefault = [ + self.instrumentDB.instNamed["kalimba"], + self.instrumentDB.instNamed["kalimba"], + self.instrumentDB.instNamed["kalimba"], + self.instrumentDB.instNamed["kalimba"], + self.instrumentDB.instNamed["drum2kit"] ] + self.trackInstrument = self.trackInstrumentDefault[:] + + for i in self.trackInstrument: + if i.kit == None: + self.csnd.load_instrument(i.name) + else: + self.csnd.load_drumkit(i.name) + + if len(self.trackInstrument) != Config.NUMBER_OF_TRACKS: raise 'error' + self.drumIndex = Config.NUMBER_OF_TRACKS - 1 + + self.last_clicked_instTrackID = 0 + self.last_clicked_instPrimary = 'kalimba' + + #second instrument for melodic tracks + self.trackInstrument2Default = [ None, None, None, None] + self.trackInstrument2 = self.trackInstrument2Default[:] + + self._data['volume'] = Config.DEFAULT_VOLUME + self._data['tempo'] = Config.PLAYER_TEMPO + + self.playScope = "Selection" + self.displayedPage = -1 + self.trackSelected = [ 0 for i in range(Config.NUMBER_OF_TRACKS) ] + self.trackActive = [ 1 for i in range(Config.NUMBER_OF_TRACKS) ] + + self.pages_playing = [] + self.journalCalled = False + + self.noteDB = NoteDB.NoteDB() + TP.ProfileEnd("init_data") + + def formatRoundBox( box, fillcolor ): + box.set_radius( 7 ) + box.set_border_width( 1 ) + box.set_fill_color( fillcolor ) + box.set_border_color( Config.PANEL_BCK_COLOR ) + return box + + def init_GUI(): + + self.GUI = {} + self.GUI["2main"] = gtk.VBox() + self.GUI["2instrumentPalette"] = instrumentPalette(_('Track 1 Volume'), self) + + def draw_inst_icons(): + instruments = [ k for k in self.instrumentDB.inst if not k.kitStage ] + self.GUI["2instrumentIcons"] = {} + for i in instruments: + try: + self.GUI["2instrumentIcons"][i.name] = gtk.gdk.pixbuf_new_from_file(i.img) + except: + self.GUI["2instrumentIcons"][i.name] = gtk.gdk.pixbuf_new_from_file(Config.IMAGE_ROOT + 'generic.png') + TP.ProfileBegin("init_GUI::instrument icons") + draw_inst_icons() + TP.ProfileEnd("init_GUI::instrument icons") + + + #------------------------------------------------------------------------ + # page + self.GUI["2page"] = gtk.HBox() + self.scrollWin = gtk.ScrolledWindow() + self.scrollWin.set_policy(gtk.POLICY_NEVER,gtk.POLICY_AUTOMATIC) + self.scrollWin.add_with_viewport(self.GUI["2page"]) + self.GUI["2main"].pack_start( self.scrollWin, True ) + + if 1: # + instrument panel + self.GUI["2instrumentPanel"] = gtk.VBox() + self.GUI["2page"].pack_start( self.GUI["2instrumentPanel"], True ) + # + + instrument 1 box + self.GUI["2instrument1Box"] = formatRoundBox( RoundHBox(), Config.BG_COLOR ) + self.GUI["2instrument1Box"].set_size_request( -1, 132 ) + self.GUI["2instrument1volBox"] = gtk.VBox() + #self.GUI["2instrument1volumeAdjustment"] = gtk.Adjustment( self._data["track_volume"][1], 0, 100, 1, 1, 0 ) + #self.GUI["2instrument1volumeAdjustment"].connect( "value_changed", self.onTrackVolumeChanged, 0 ) + #self.GUI["2instrument1volumeSlider"] = gtk.VScale(self.GUI["2instrument1volumeAdjustment"]) + #self.GUI["2instrument1volumeSlider"].set_draw_value(False) + #self.GUI["2instrument1volumeSlider"].set_inverted(True) + #self.GUI["2instrument1volumeSlider"].set_size_request( 30, -1 ) + #self.GUI["2instrument1volumeAdjustment"].connect( "value-changed", self.handleTrackVolume, 0 ) + self.GUI["2instrument1muteButton"] = ImageToggleButton(Config.IMAGE_ROOT+"checkOff.svg",Config.IMAGE_ROOT+"checkOn.svg") + self.GUI["2instrument1muteButton"].connect("toggled",self.handlemuteButton,0) + self.GUI["2instrument1muteButton"].connect("button-press-event",self.handlemuteButtonRightClick,0) + self.GUI["2instrument1muteButton"].set_active(True) + #self.GUI["2instrument1volBox"].pack_start( self.GUI["2instrument1volumeSlider"], True, True, 0 ) + #self.GUI["2instrument1volBox"].pack_start( self.GUI["2instrument1muteButton"], False, False, 5 ) + self.GUI["2instrument1Box"].pack_start( self.GUI["2instrument1volBox"], False, False, 0 ) + self.GUI["2instrument1Button"] = InstrumentButton( self, 0, Config.BG_COLOR ) + self.GUI["2instrument1Button"].connect('button-release-event',self.GUI["2instrumentPalette"].setBlock, 0) + self.GUI["2instrument1Button"].setPrimary( self.GUI["2instrumentIcons"][self.trackInstrument[0].name] ) + self.GUI["2instrument1Box"].pack_start( self.GUI["2instrument1Button"], padding = 3 ) + self.GUI["2instrumentPanel"].pack_start( self.GUI["2instrument1Box"] ) + # + + instrument 2 box + self.GUI["2instrument2Box"] = formatRoundBox( RoundHBox(), Config.BG_COLOR ) + self.GUI["2instrument2Box"].set_size_request( -1, 132 ) + self.GUI["2instrument2volBox"] = gtk.VBox() + #self.GUI["2instrument2volumeAdjustment"] = gtk.Adjustment( self._data["track_volume"][1], 0, 100, 1, 1, 0 ) + #self.GUI["2instrument2volumeAdjustment"].connect( "value_changed", self.onTrackVolumeChanged, 1 ) + #self.GUI["2instrument2volumeSlider"] = gtk.VScale(self.GUI["2instrument2volumeAdjustment"]) + #self.GUI["2instrument2volumeSlider"].set_draw_value(False) + #self.GUI["2instrument2volumeSlider"].set_inverted(True) + #self.GUI["2instrument2volumeSlider"].set_size_request( 30, -1 ) + #self.GUI["2instrument2volumeAdjustment"].connect( "value-changed", self.handleTrackVolume, 1 ) + self.GUI["2instrument2muteButton"] = ImageToggleButton(Config.IMAGE_ROOT+"checkOff.svg",Config.IMAGE_ROOT+"checkOn.svg") + self.GUI["2instrument2muteButton"].connect("toggled",self.handlemuteButton,1) + self.GUI["2instrument2muteButton"].connect("button-press-event",self.handlemuteButtonRightClick,1) + self.GUI["2instrument2muteButton"].set_active(True) + #self.GUI["2instrument2volBox"].pack_start( self.GUI["2instrument2volumeSlider"], True, True, 0 ) + #self.GUI["2instrument2volBox"].pack_start( self.GUI["2instrument2muteButton"], False, False, 5 ) + self.GUI["2instrument2Box"].pack_start( self.GUI["2instrument2volBox"], False, False, 0 ) + self.GUI["2instrument2Button"] = InstrumentButton( self, 1, Config.BG_COLOR ) + self.GUI["2instrument2Button"].connect('button-release-event',self.GUI["2instrumentPalette"].setBlock, 1) + self.GUI["2instrument2Button"].setPrimary( self.GUI["2instrumentIcons"][self.trackInstrument[1].name] ) + self.GUI["2instrument2Box"].pack_start( self.GUI["2instrument2Button"], padding = 3 ) + self.GUI["2instrumentPanel"].pack_start( self.GUI["2instrument2Box"] ) + # + + instrument 3 box + self.GUI["2instrument3Box"] = formatRoundBox( RoundHBox(), Config.BG_COLOR ) + self.GUI["2instrument3Box"].set_size_request( -1, 132 ) + self.GUI["2instrument3volBox"] = gtk.VBox() + #self.GUI["2instrument3volumeAdjustment"] = gtk.Adjustment( self._data["track_volume"][2], 0, 100, 1, 1, 0 ) + #self.GUI["2instrument3volumeAdjustment"].connect( "value_changed", self.onTrackVolumeChanged, 2 ) + #self.GUI["2instrument3volumeSlider"] = gtk.VScale(self.GUI["2instrument3volumeAdjustment"]) + #self.GUI["2instrument3volumeSlider"].set_draw_value(False) + #self.GUI["2instrument3volumeSlider"].set_inverted(True) + #elf.GUI["2instrument3volumeSlider"].set_size_request( 30, -1 ) + #self.GUI["2instrument3volumeAdjustment"].connect( "value-changed", self.handleTrackVolume, 2 ) + self.GUI["2instrument3muteButton"] = ImageToggleButton(Config.IMAGE_ROOT+"checkOff.svg",Config.IMAGE_ROOT+"checkOn.svg") + self.GUI["2instrument3muteButton"].connect("toggled",self.handlemuteButton,2) + self.GUI["2instrument3muteButton"].connect("button-press-event",self.handlemuteButtonRightClick,2) + self.GUI["2instrument3muteButton"].set_active(True) + #self.GUI["2instrument3volBox"].pack_start( self.GUI["2instrument3volumeSlider"], True, True, 0 ) + #self.GUI["2instrument3volBox"].pack_start( self.GUI["2instrument3muteButton"], False, False, 5 ) + self.GUI["2instrument3Box"].pack_start( self.GUI["2instrument3volBox"], False, False, 0 ) + self.GUI["2instrument3Button"] = InstrumentButton( self, 2, Config.BG_COLOR ) + self.GUI["2instrument3Button"].connect('button-release-event',self.GUI["2instrumentPalette"].setBlock, 2) + self.GUI["2instrument3Button"].setPrimary( self.GUI["2instrumentIcons"][self.trackInstrument[2].name] ) + self.GUI["2instrument3Box"].pack_start( self.GUI["2instrument3Button"], padding = 3 ) + self.GUI["2instrumentPanel"].pack_start( self.GUI["2instrument3Box"] ) + # + + instrument 4 box + self.GUI["2instrument4Box"] = formatRoundBox( RoundHBox(), Config.BG_COLOR ) + self.GUI["2instrument4Box"].set_size_request( -1, 132 ) + self.GUI["2instrument4volBox"] = gtk.VBox() + #self.GUI["2instrument4volumeAdjustment"] = gtk.Adjustment( self._data["track_volume"][3], 0, 100, 1, 1, 0 ) + #self.GUI["2instrument4volumeAdjustment"].connect( "value_changed", self.onTrackVolumeChanged, 3 ) + #self.GUI["2instrument4volumeSlider"] = gtk.VScale(self.GUI["2instrument4volumeAdjustment"]) + #self.GUI["2instrument4volumeSlider"].set_draw_value(False) + #self.GUI["2instrument4volumeSlider"].set_inverted(True) + #self.GUI["2instrument4volumeSlider"].set_size_request( 30, -1 ) + #self.GUI["2instrument4volumeAdjustment"].connect( "value-changed", self.handleTrackVolume, 3 ) + self.GUI["2instrument4muteButton"] = ImageToggleButton(Config.IMAGE_ROOT+"checkOff.svg",Config.IMAGE_ROOT+"checkOn.svg") + self.GUI["2instrument4muteButton"].connect("toggled",self.handlemuteButton,3) + self.GUI["2instrument4muteButton"].connect("button-press-event",self.handlemuteButtonRightClick,3) + self.GUI["2instrument4muteButton"].set_active(True) + #self.GUI["2instrument4volBox"].pack_start( self.GUI["2instrument4volumeSlider"], True, True, 0 ) + #self.GUI["2instrument4volBox"].pack_start( self.GUI["2instrument4muteButton"], False, False, 5 ) + self.GUI["2instrument4Box"].pack_start( self.GUI["2instrument4volBox"], False, False, 0 ) + self.GUI["2instrument4Button"] = InstrumentButton( self, 3, Config.BG_COLOR ) + self.GUI["2instrument4Button"].connect('button-release-event',self.GUI["2instrumentPalette"].setBlock, 3) + self.GUI["2instrument4Button"].setPrimary( self.GUI["2instrumentIcons"][self.trackInstrument[3].name] ) + self.GUI["2instrument4Box"].pack_start( self.GUI["2instrument4Button"], padding = 3 ) + self.GUI["2instrumentPanel"].pack_start( self.GUI["2instrument4Box"] ) + # + + drum box + self.GUI["2drumBox"] = formatRoundBox( RoundHBox(), Config.BG_COLOR ) + self.GUI["2drumBox"].set_size_request( -1, 165 ) + self.GUI["2drumVolBox"] = gtk.VBox() + self.GUI["2drumvolumeAdjustment"] = gtk.Adjustment( self._data["track_volume"][4], 0, 100, 1, 1, 0 ) + self.GUI["2drumvolumeAdjustment"].connect( "value_changed", self.onTrackVolumeChanged, 4 ) + #self.GUI["2drumvolumeSlider"] = gtk.VScale(self.GUI["2drumvolumeAdjustment"]) + #self.GUI["2drumvolumeSlider"].set_draw_value(False) + #self.GUI["2drumvolumeSlider"].set_inverted(True) + #self.GUI["2drumvolumeSlider"].set_size_request( 30, -1 ) + self.GUI["2drumvolumeAdjustment"].connect( "value-changed", self.handleTrackVolume, 4 ) + self.GUI["2drumMuteButton"] = ImageToggleButton(Config.IMAGE_ROOT+"checkOff.svg",Config.IMAGE_ROOT+"checkOn.svg") + self.GUI["2drumMuteButton"].connect("toggled",self.handlemuteButton,4) + self.GUI["2drumMuteButton"].connect("button-press-event",self.handlemuteButtonRightClick,4) + self.GUI["2drumMuteButton"].set_active(True) + #self.GUI["2drumVolBox"].pack_start( self.GUI["2drumvolumeSlider"], True, True, 0 ) + #self.GUI["2drumVolBox"].pack_start( self.GUI["2drumMuteButton"], False, False, 5 ) + self.GUI["2drumBox"].pack_start( self.GUI["2drumVolBox"], False, False, 0 ) + self.GUI["2drumButton"] = ImageToggleButton(Config.IMAGE_ROOT + self.trackInstrument[4].name + '.png', Config.IMAGE_ROOT + self.trackInstrument[4].name + '.png') + self.GUI["2drumPalette"] = drumPalette(_('Track 5 Properties'), self, 4) + self.GUI["2drumButton"].connect("toggled", self.pickDrum) + self.GUI["2drumButton"].connect('button-release-event',self.GUI["2drumPalette"].setBlock) + self.GUI["2drumBox"].pack_start( self.GUI["2drumButton"] ) + self.GUI["2instrumentPanel"].pack_start( self.GUI["2drumBox"] ) + self.GUI["2page"].pack_start( self.GUI["2instrumentPanel"], True ) + # + track interface + tracks_width = gtk.gdk.screen_width() - 140 + self.trackInterface = TrackInterface( self.noteDB, self, self.getScale, tracks_width ) + self.noteDB.addListener( self.trackInterface, TrackInterfaceParasite, True ) + self.trackInterface.set_size_request( tracks_width, -1 ) + self.GUI["2page"].pack_start( self.trackInterface, False ) + + #------------------------------------------------------------------------ + # tune interface + if 1: # + tune interface + self.GUI["2tuneScrolledWindow"] = HScrolledBox() + self.tuneInterface = TuneInterface( self.noteDB, self, self.GUI["2tuneScrolledWindow"].get_adjustment() ) + self.noteDB.addListener( self.tuneInterface, TuneInterfaceParasite, True ) + self.GUI["2tuneScrolledWindow"].set_viewport( self.tuneInterface ) + self.tuneInterface.get_parent().set_shadow_type( gtk.SHADOW_NONE ) + self.GUI["2tuneScrolledWindow"].set_size_request(-1, 100) + self.GUI["2tuneScrolledWindow"].modify_bg(gtk.STATE_NORMAL, + style.Color(Config.TOOLBAR_BCK_COLOR).get_gdk_color()) + self.GUI["2main"].pack_start( self.GUI["2tuneScrolledWindow"], False, True ) + + # set tooltips + for key in self.GUI: + if Tooltips.Edit.has_key(key): + self.tooltips.set_tip(self.GUI[key],Tooltips.Edit[key]) + + self.add( self.GUI["2main"] ) + + self.skipCleanup = "" # used when jumping between duplicate note/track + + + # Popups + TP.ProfileBegin("init_GUI::popups") + # + generation window + #TP.ProfileBegin("init_GUI::generationPanel") + #self.generationPanel = GenerationParametersWindow( self.generate, self.doneGenerationPopup ) + #TP.ProfileEnd("init_GUI::generationPanel") + #self.GUI["9generationPopup"] = gtk.Window(gtk.WINDOW_POPUP) + #self.GUI["9generationPopup"].set_modal(True) + #self.GUI["9generationPopup"].add_events( gtk.gdk.BUTTON_PRESS_MASK ) + #self.GUI["9generationPopup"].connect("button-release-event", lambda w,e:self.doneGenerationPopup() ) + #self.GUI["9generationPopup"].add( self.generationPanel ) + # + properties window + #self.GUI["9propertiesPopup"] = gtk.Window(gtk.WINDOW_POPUP) + #self.GUI["9propertiesPopup"].set_modal(True) + #self.GUI["9propertiesPopup"].add_events( gtk.gdk.BUTTON_PRESS_MASK ) + #self.GUI["9propertiesPopup"].connect("button-release-event", lambda w,e:self.donePropertiesPopup() ) + #TP.ProfileBegin("init_GUI::propertiesPanel") + #self.propertiesPanel = Properties( self.noteDB, self.donePropertiesPopup, self.GUI["9propertiesPopup"] ) + #TP.ProfileEnd("init_GUI::propertiesPanel") + #self.GUI["9propertiesPopup"].add( self.propertiesPanel ) + # + playback scope + self.GUI["9loopPopup"] = gtk.Window(gtk.WINDOW_POPUP) + self.GUI["9loopPopup"].move( 100, 100 ) + self.GUI["9loopPopup"].resize( 300, 100 ) + self.GUI["9loopPopup"].set_modal(True) + self.GUI["9loopPopup"].add_events( gtk.gdk.BUTTON_PRESS_MASK ) + self.GUI["9loopPopup"].connect("button-release-event", lambda w,e:self.GUI["2loopButton"].set_active(False) ) + self.GUI["9loopBox"] = formatRoundBox( RoundHBox(), Config.BG_COLOR ) + self.GUI["9loopAllOnce"] = gtk.Button("AO") + self.GUI["9loopBox"].pack_start( self.GUI["9loopAllOnce"] ) + self.GUI["9loopAllRepeat"] = gtk.Button("AR") + self.GUI["9loopBox"].pack_start( self.GUI["9loopAllRepeat"] ) + self.GUI["9loopSelectedOnce"] = gtk.Button("SO") + self.GUI["9loopBox"].pack_start( self.GUI["9loopSelectedOnce"] ) + self.GUI["9loopSelectedRepeat"] = gtk.Button("SR") + self.GUI["9loopBox"].pack_start( self.GUI["9loopSelectedRepeat"] ) + self.GUI["9loopPopup"].add(self.GUI["9loopBox"]) + TP.ProfileEnd("init_GUI::popups") + + #=================================================== + # begin initialization + + # keyboard variables + self.kb_record = False + self.kb_keydict = {} + + # playback params + self.playing = False + self.playSource = 'Page' + self.currentpageId = 0 + self.playingTuneIdx = 0 + + # timers + self.playbackTimeout = False + + # FPS stuff + self.fpsTotalTime = 0 + self.fpsFrameCount = 0 + self.fpsN = 100 # how many frames to average FPS over + self.fpsLastTime = time.time() # fps will be borked for the first few frames but who cares? + + self.context = -1 # invalidate + self.contextTrackActive = False + self.contextNoteActive = False + + init_data() #above + init_GUI() #above + + # register for notification AFTER track and tune interfaces + self.noteDB.addListener( self, page=True, note=True ) + + self.csnd.setMasterVolume( self.getVolume() ) + self.initTrackVolume() + + for tid in range(Config.NUMBER_OF_TRACKS): + self.handleInstrumentChanged( ( tid, self.trackInstrument[tid] ) ) + + instrumentsIds = [] + for inst in self.trackInstrument: + instrumentsIds.append(inst.instrumentId) + + first = self.noteDB.addPage( -1, NoteDB.Page(4, instruments = instrumentsIds) ) + self.displayPage( first ) + + if not self.journalCalled: + self.createNewTune( None ) + + # Toolbar + self._mainToolbar = mainToolbar(self.activity.toolbox, self) + self._generateToolbar = generateToolbar(self.activity.toolbox, self) + self.activity.toolbox.add_toolbar(_('Compose'), self._mainToolbar) + self.activity.toolbox.add_toolbar(_('Generate'), self._generateToolbar) + self.activity.toolbox.set_current_toolbar(1) + self._mainToolbar.show() + self._generateToolbar.show() + + self.show_all() #gtk command + + self.setContext( CONTEXT.PAGE ) + + self.audioRecordWidget = None + + def createNewTune( self, widget, data=None ): + self.createNewTune3() + + def createNewTune3( self ): + + if self.playing == True: + self.handleStop() + + self.tuneInterface.selectPages( self.noteDB.getTune() ) + + beats = random.randint(3,6) + stream = [] + for page in self.noteDB.getTune(): + stream += [ page, beats ] + if len(stream): + self.noteDB.updatePages( [ PARAMETER.PAGE_BEATS, len(stream)//2 ] + stream ) + + orch = self.newOrchestra() + + for i in orch: + if i.kit == None: + self.csnd.load_instrument(i.name) + else: + self.csnd.load_drumkit(i.name) + + instrumentsIds = [] + for inst in orch: + instrumentsIds.append(inst.instrumentId) + + self.pageDelete( -1, instruments = instrumentsIds ) + + initTempo = random.randint(60, 132) + self._data['tempo'] = initTempo + + formsUsed = [] + for section in self.tuneForm: + if section[0] not in formsUsed: + param = self.chooseGenParams() + self.tuneInterface.selectPages( self.noteDB.getTune() ) + if not formsUsed: + for i in range(section[2]-1): + self.pageAdd(instruments = instrumentsIds) + else: + for i in range(section[2]): + self.pageAdd(instruments = instrumentsIds) + formsUsed.append(section[0]) + + self.tuneInterface.selectPages( self.noteDB.getTune()[-section[2]:] ) + self.generateMode = 'page' + self.generate( GenerationParameters( density = param[0], rythmRegularity = param[1], step = param[2], pitchRegularity = param[3], articule = param[4], silence = param[5], pattern = param[6], scale = param[7]), section[2] ) + else: + pageOffset = 0 + pageIds = [] + firstPos = [i[0] for i in self.tuneForm].index(section[0]) + if firstPos == 0: + pageOffset = 0 + else: + for i in range(firstPos): + pageOffset += self.tuneForm[i][2] + for i in range(section[2]): + pageIds.append(self.noteDB.getTune()[pageOffset + i]) + after = self.noteDB.getTune()[-1] + self.displayPage( self.noteDB.getTune()[pageOffset] ) + self.tuneInterface.selectPages(self.noteDB.getTune()) + self.pageDuplicate(-1, pageIds) + + self.tuneInterface.selectPages( self.noteDB.getTune() ) + self.displayPage( self.noteDB.getTune()[0] ) + + + def newOrchestra(self): + stringsPickup = [] + windsPickup = [] + keyboardPickup = [] + fxPickup = [] + drumsPickup = ["drum1kit", "drum2kit", "drum3kit", "drum4kit", "drum5kit"] + for name in self.instrumentDB.instNamed.keys(): + if self.instrumentDB.instNamed[name].category == 'strings' and self.instrumentDB.instNamed[name].name != 'violin': + stringsPickup.append(name) + elif self.instrumentDB.instNamed[name].category == 'winds' and self.instrumentDB.instNamed[name].name != 'didjeridu': + windsPickup.append(name) + elif self.instrumentDB.instNamed[name].category == 'keyboard' or self.instrumentDB.instNamed[name].category == 'percussions' and not name.startswith('drum'): + if self.instrumentDB.instNamed[name].name != 'zap' and self.instrumentDB.instNamed[name].name != 'cling': + keyboardPickup.append(name) + return [ + self.instrumentDB.instNamed[random.choice(stringsPickup)], + self.instrumentDB.instNamed[random.choice(stringsPickup)], + self.instrumentDB.instNamed[random.choice(windsPickup)], + self.instrumentDB.instNamed[random.choice(keyboardPickup)], + self.instrumentDB.instNamed[random.choice(drumsPickup)] ] + + def chooseGenParams(self): + choose = [random.randint(0,16) for x in range(4)] + density = [GenerationConstants.RYTHM_DENSITY_BANK[i] for i in choose] + rytReg = [GenerationConstants.RYTHM_REGU_BANK[i] for i in choose] + step = [GenerationConstants.PITCH_STEP_BANK[i] for i in choose] + pitReg = [GenerationConstants.PITCH_REGU_BANK[i] for i in choose] + dur = [GenerationConstants.DURATION_BANK[i] for i in choose] + silence = [GenerationConstants.SILENCE_BANK[i] for i in choose] + pattern = [GenerationConstants.PATTERN_BANK[i] for i in choose] + scale = random.randint(0,6) + return [density, rytReg, step, pitReg, dur, silence, pattern, scale] + + def onActivate( self, arg ): + # whatever needs to be done on initialization + self.csnd.loopPause() + self.csnd.loopClear() + for n in self.noteDB.getNotes( ): + self.csnd.loopPlay(n, 0) #adds all notes to c client in inactive state + + + def onDeactivate( self ): + # clean up things like popups etc + self.csnd.loopPause() + self.csnd.loopClear() + + + def updateFPS( self ): + t = time.time() + dt = t - self.fpsLastTime + self.fpsLastTime = t + self.fpsTotalTime += dt + self.fpsFrameCount += 1 + if self.fpsFrameCount == self.fpsN: + fps = self.fpsN/self.fpsTotalTime + avgMS = 1000/fps + fps = "FPS %d ms %.2f" % (fps, avgMS) + #self.fpsText.set_text(fps ) + if (Config.DEBUG > 2): print fps + self.fpsTotalTime = 0 + self.fpsFrameCount = 0 + + #========================================================= + # Popup Windows + + def doneGenerationPopup( self ): + if self.GUI["2pageGenerateButton"].get_active(): + self.GUI["2pageGenerateButton"].set_active( False ) + if self.GUI["2trackGenerateButton"].get_active(): + self.GUI["2trackGenerateButton"].set_active( False ) + + def donePropertiesPopup( self ): + if self.GUI["2pagePropertiesButton"].get_active(): + self.GUI["2pagePropertiesButton"].set_active( False ) + if self.GUI["2trackPropertiesButton"].get_active(): + self.GUI["2trackPropertiesButton"].set_active( False ) + if self.GUI["2notePropertiesButton"].get_active(): + self.GUI["2notePropertiesButton"].set_active( False ) + + def cancelPopup( self, w, event, popup ): + popup.hide() + + + def handleLoopButton( self, w ): + if w.get_active(): self.GUI["9loopPopup"].show_all() + else: self.GUI["9loopPopup"].hide() + + #----------------------------------- + # playback functions + #----------------------------------- + + def updatePageSelection( self, selectedIds ): + if not self.playing: + return + + if self.playScope == "All": + return + + if self.displayedPage in selectedIds: + startPage = self.displayedPage + else: + startPage = selectedIds[0] + + self._playPages( selectedIds, startPage, self.trackInterface.getPlayhead() ) + + def updatePagesPlaying( self ): + if not self.playing: + return + + curTick = self.csnd.loopGetTick() + + pageTick = self.page_onset[self.displayedPage] + if curTick < pageTick: + pageTick = 0 + startPage = self.pages_playing[0] + else: + startPage = self.displayedPage + + localTick = curTick - pageTick + + self._playPages( self.tuneInterface.getSelectedIds(), startPage, localTick ) + + def handleAudioRecord( self, widget, data=None ): + if widget.get_active() == True: + self.audioRecordWidget = widget + self.audioRecordTick = -1 + else: + self.audioRecordWidget = None + + def handlePlay( self, widget = None ): + if widget: + widget.event( gtk.gdk.Event( gtk.gdk.LEAVE_NOTIFY ) ) # fake the leave event + + if self.audioRecordWidget: + filename = Config.TMP_DIR + "/perf.wav" + self.csnd.inputMessage( Config.CSOUND_RECORD_PERF % filename) + time.sleep( 0.01 ) + + if self.playScope == "All": + toPlay = self.noteDB.getTune() + else: + toPlay = self.tuneInterface.getSelectedIds() + + self._playPages( toPlay, self.displayedPage, self.trackInterface.getPlayhead() ) + + self.playing = True + + def _playPages( self, pages, startPage, startTick ): + + self.pages_playing = pages[:] + + trackset = set( [ i for i in range(Config.NUMBER_OF_TRACKS) if self.trackActive[i] ] ) + + numticks = 0 + self.page_onset = {} + for pid in self.pages_playing: + self.page_onset[pid] = numticks + numticks += self.noteDB.getPage(pid).ticks + + # check for a second instrument on melodic tracks + stream = [] + for page in self.pages_playing: + for track in trackset: + if track != self.drumIndex: + if self.trackInstrument2[track] != None: + if len(self.noteDB.getNotesByTrack(page, track)): + stream += [ page, track, NoteDB.PARAMETER.INSTRUMENT2, len(self.noteDB.getNotesByTrack(page, track)) ] + for n in self.noteDB.getNotesByTrack(page, track): + stream += [ n.id, self.trackInstrument2[track].instrumentId ] + if len(stream): + self.noteDB.updateNotes( stream + [-1] ) + + self.csnd.loopClear() + for page in self.pages_playing: + for track in trackset: + for n in self.noteDB.getNotesByTrack( page, track ): + self.csnd.loopPlay(n, 1) + self.csnd.loopUpdate(n, NoteDB.PARAMETER.ONSET, n.cs.onset + self.page_onset[n.page] , 1) + + self.csnd.loopSetNumTicks( numticks ) + + self.csnd.loopSetTick( self.page_onset[startPage] + startTick ) + self.csnd.setTempo(self._data['tempo']) + if (Config.DEBUG > 3): print "starting from tick", startTick, 'at tempo', self._data['tempo'] + self.csnd.loopStart() + + if not self.playbackTimeout: + self.playbackTimeout = gobject.timeout_add( 50, self.onTimeout ) + + + + def handleStop( self, widget = None, rewind = True ): + + if widget: + widget.event( gtk.gdk.Event( gtk.gdk.LEAVE_NOTIFY ) ) # fake the leave event + + if self.audioRecordWidget: + filename = Config.TMP_DIR + "/perf.wav" + self.csnd.inputMessage( Config.CSOUND_STOP_RECORD_PERF % filename) + time.sleep( 0.01 ) + + if self.playbackTimeout: + gobject.source_remove( self.playbackTimeout ) + self.playbackTimeout = False + + self.csnd.loopPause() + self.csnd.loopDeactivate() + + if self.audioRecordWidget: + time.sleep(4) + self.csnd.__del__() + time.sleep(0.5) + tmp_ogg = os.path.join(Config.TMP_DIR, "perf.ogg") + + command = "gst-launch-0.10 filesrc location=" + Config.TMP_DIR + "/perf.wav ! wavparse ! audioconvert ! vorbisenc ! oggmux ! filesink location=" + tmp_ogg + command2 = "rm " + Config.TMP_DIR + "/perf.wav" + OS.system(command) + OS.system(command2) + + from datetime import datetime + title = '%s %s.ogg' % (self.activity.get_title(), + datetime.now().isoformat(' ')) + + jobject = datastore.create() + jobject.metadata['title'] = title + jobject.metadata['keep'] = '1' + jobject.metadata['mime_type'] = 'audio/ogg' + jobject.file_path = tmp_ogg + datastore.write(jobject) + + os.remove(tmp_ogg) + self.audioRecordWidget.set_active(False) + self.audioRecordWidget = None + + self.csnd.__init__() + time.sleep(0.1) + self.csnd.connect(True) + time.sleep(0.1) + self.waitToSet() + self.csnd.load_instruments() + self.GUI["2recordButton"].set_active(False) + self.playing = False + + if rewind: self.handleRewind() + + def handleRewind( self, widget = None ): + if self.playScope == "All": id = self.noteDB.getPageByIndex(0) + else: id = self.tuneInterface.getFirstSelected() + self.trackInterface.setPlayhead( 0 ) + self.displayPage( id ) + + def handleClose(self,widget): + self.activity.close() + + def onTimeout(self): + self.updateFPS() + + curTick = self.csnd.loopGetTick() + + pageTick = self.page_onset[self.displayedPage] + if curTick < pageTick: + pageTick = 0 + ind = 0 + else: + ind = self.pages_playing.index(self.displayedPage) + + localTick = curTick - pageTick + pageLength = self.noteDB.getPage(self.pages_playing[ind]).ticks + max = len(self.pages_playing) + while localTick > pageLength: + ind += 1 + if ind == max: ind = 0 + localTick -= pageLength + pageLength = self.noteDB.getPage(self.pages_playing[ind]).ticks + + self.trackInterface.setPlayhead( localTick ) + + if self.pages_playing[ind] != self.displayedPage: + if ind + 1 < max: predraw = self.pages_playing[ind+1] + else: predraw = self.pages_playing[0] + self._displayPage( self.pages_playing[ind], predraw ) + else: + self.trackInterface.predrawPage() + + if self.audioRecordWidget: + if self.audioRecordTick > curTick: # we've looped around + self.handleStop() + else: + self.audioRecordTick = curTick + + + return True + + def onMuteTrack( self, widget, trackId ): + self._data['track_mute'][trackId] = not self._data['track_mute'][trackId] + #if self._data['track_mute'][trackId]: + #self.noteLooper.setMute( trackId, 0.0 ) + #else: + #self.noteLooper.setMute( trackId, 1.0 ) + + def onTrackVolumeChanged( self, widget, trackId ): + v = widget.get_value() / 100.0 + self._data['track_volume'][trackId] = v + #self.noteLooper.setVolume( trackId, v ) + + def clearInstrument( self, id, primary = True ): + btn = self.GUI["2instrument%dButton" % (id+1)] + if primary: + if self.trackInstrument2[id] == None: + return + self.handleInstrumentChanged( ( id, self.trackInstrument2[id] ), True ) + self.handleInstrumentChanged( ( id, None ), False ) + btn.setPrimary( self.GUI["2instrumentIcons"][self.trackInstrument[id].name] ) + btn.setSecondary( None ) + else: + self.handleInstrumentChanged( ( id, None ), False ) + btn.setSecondary( None ) + pages = self.tuneInterface.getSelectedIds() + self.noteDB.setInstrument2( pages, id, -1 ) + + # data is tuple ( trackId, instrumentName ) + def handleInstrumentChanged( self, data, primary = True ): + (id, instrument) = data + if primary: + self.trackInstrument[id] = instrument + else: + self.trackInstrument2[id] = instrument + + if instrument: + if instrument.kit == None: + self.csnd.load_instrument(instrument.name) + else: + self.csnd.load_drumkit(instrument.name) + + if primary: # TODO handle secondary instruments properly + if (Config.DEBUG > 3): print "handleInstrumentChanged", id, instrument.name, primary + + pages = self.tuneInterface.getSelectedIds() + self.noteDB.setInstrument( pages, id, instrument.instrumentId ) + + def getScale(self): + return self.scale + + def handleVolume( self, widget ): + self._data["volume"] = round( widget.get_value() ) + self.csnd.setMasterVolume(self._data["volume"]) + img = min(3,int(4*self._data["volume"]/100)) # volume 0-3 + #self.GUI["2volumeImage"].set_from_file( Config.IMAGE_ROOT+"volume"+str(img)+".png" ) + + def initTrackVolume( self ): + for i in range(Config.NUMBER_OF_TRACKS): + self.csnd.setTrackVolume(self._data["track_volume"][i], i) + + def handleTrackVolume( self, widget = None, track = None ): + self._data["track_volume"][track] = round( widget.get_value() ) + self.csnd.setTrackVolume(self._data["track_volume"][track], track) + + def getTrackInstrument( self, track ): + return self.trackInstrument[track] + + def getTrackVolume( self, track ): + return self._data["track_volume"][track] + + def handleTempo( self, widget ): + self._data['tempo'] = round( widget.get_value() ) + img = min(7,int(8*(self._data["tempo"]-widget.lower)/(widget.upper-widget.lower)))+1# tempo 1-8 + #self.GUI["2tempoImage"].set_from_file( Config.IMAGE_ROOT+"tempo"+str(img)+".png" ) + if self.playing: + self.csnd.setTempo(self._data['tempo']) + + def handleToolClick( self, widget, mode ): + if widget.get_active(): self.trackInterface.setInterfaceMode( mode ) + + def getTool( self ): + if self.GUI["2toolPointerButton"].get_active(): return "default" + else: return "draw" + + def handleKeyboardRecordButton( self, widget, data=None ): + self.kb_record = widget.get_active() + + def pickInstrument( self, widget, num, primary = True ): + self.last_clicked_instTrackID = num + self.last_clicked_instPrimary = primary + + if primary or self.trackInstrument2[num] == None: + instrument = self.trackInstrument[num] + else: + instrument = self.trackInstrument2[num] + + self.GUI["2instrumentPalette"].setInstrument(instrument) + + def cancelInstrumentSelection( self ): + self.GUI["9instrumentPopup"].hide() + + def donePickInstrument( self, instrumentName ): + self.handleInstrumentChanged( (self.last_clicked_instTrackID, self.instrumentDB.instNamed[instrumentName]), self.last_clicked_instPrimary ) + btn = self.GUI["2instrument%dButton" % (self.last_clicked_instTrackID+1)] + if self.last_clicked_instPrimary: + btn.setPrimary( self.GUI["2instrumentIcons"][instrumentName] ) + else: + btn.setSecondary( self.GUI["2instrumentIcons"][instrumentName] ) + #self.GUI["9instrumentPopup"].hide() + + + def pickDrum( self, widget , data = None ): + if widget.get_active(): + self.GUI['2drumPalette'].setDrum(self.trackInstrument[Config.NUMBER_OF_TRACKS-1].name) + + def cancelDrumSelection( self ): + self.GUI["2drumButton"].set_active( False ) + + def donePickDrum( self, drumName ): + self.handleInstrumentChanged( ( self.drumIndex, self.instrumentDB.instNamed[drumName] ) ) + self.GUI["2drumButton"].setImage( "main", self.GUI["2instrumentIcons"][drumName] ) + self.GUI["2drumButton"].setImage( "alt", self.GUI["2instrumentIcons"][drumName] ) + self.GUI["2drumButton"].set_active( False ) + + def playInstrumentNote( self, instrumentName, secs_per_tick = 0.025): + self.csnd.play( + CSoundNote( onset = 0, + pitch = 36, + amplitude = 1, + pan = 0.5, + duration = 20, + trackId = 1, + instrumentId = self.instrumentDB.instNamed[instrumentName].instrumentId, + reverbSend = 0), + secs_per_tick) + + def handlemuteButton(self,widget,track): + if widget.get_active(): + self.trackActive[track] = True + else: + self.trackActive[track] = False + self.updatePagesPlaying() + + def handlemuteButtonRightClick(self,widget,event,track): + if event.button == 3: + widget.set_active(True) + #if the other tracks are inactive + if self.trackActive.count(False) == Config.NUMBER_OF_TRACKS - 1: + for i in range(Config.NUMBER_OF_TRACKS): + if i == 4: + #self.GUI["2drumMuteButton"].set_active(True) + self.GUI["2drumPalette"].muteButton.set_active(True) + else: + #self.GUI["2instrument" + str(i+1) + "muteButton"].set_active(True) + self.GUI["2instrument" + str(i+1) + "Palette"].muteButton.set_active(True) + else: + for i in range(Config.NUMBER_OF_TRACKS): + if i != track: + if i == 4: + #self.GUI["2drumMuteButton"].set_active(False) + self.GUI["2drumPalette"].muteButton.set_active(False) + else: + #self.GUI["2instrument" + str(i+1) + "muteButton"].set_active(False) + self.GUI["2instrument" + str(i+1) + "Palette"].muteButton.set_active(False) + self.updatePagesPlaying() + + #----------------------------------- + # generation functions + #----------------------------------- + + def recompose( self, algo, params, nPagesCycle = 4): + if self.generateMode == "track": + if self.trackSelected == [ 0 for i in range(Config.NUMBER_OF_TRACKS) ]: + newtracks = set(range(Config.NUMBER_OF_TRACKS)) + else: + newtracks = set( [ i for i in range(Config.NUMBER_OF_TRACKS) if self.trackSelected[i] ] ) + newpages = self.tuneInterface.getSelectedIds() + else: # page mode + newtracks = set(range(Config.NUMBER_OF_TRACKS)) + newpages = self.tuneInterface.getSelectedIds() + + dict = {} + for t in newtracks: + dict[t] = {} + for p in newpages: + dict[t][p] = self.noteDB.getCSNotesByTrack( p, t ) + + beatsOfPages = {} + for pageId in newpages: + beatsOfPages[pageId] = self.noteDB.pages[pageId].beats + + instruments = self.noteDB.getInstruments(newpages) + + #[ i.name for i in self.trackInstrument ], + algo( + params, + self._data['track_volume'][:], + instruments, + self._data['tempo'], + beatsOfPages, + newtracks, + newpages, + dict, nPagesCycle) + + # 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( newpages, newtracks ) + + stream = [] + for page in newpages: + for track in newtracks: + stream += [ page, track, len(dict[track][page]) ] + stream += dict[track][page] + stream += [-1] + self.noteDB.addNotes( stream ) + + def generate( self, params, nPagesCycle = 4 ): + self.recompose( generator1, params, nPagesCycle) + + #======================================================= + # Clipboard Functions + + def getClipboardArea( self, page = -1 ): + if page == -1: page = self.displayedPage + ids = self.tuneInterface.getSelectedIds() + return self.noteDB.getClipboardArea( ids.index(page) ) + + def pasteClipboard( self, offset, trackMap ): + pages = self.tuneInterface.getSelectedIds() + instrumentMap = {} + for t in trackMap: + if t != trackMap[t]: instrumentMap[t] = self.trackInstrument[t].instrumentId + return self.noteDB.pasteClipboard( pages, offset, trackMap, instrumentMap ) + + def cleanupClipboard( self ): + self.trackInterface.donePaste() + + + #======================================================= + # Note Functions + + def noteProperties( self, widget ): + if widget.get_active(): + ids = self.trackInterface.getSelectedNotes() + notes = { self.displayedPage: {} } + for t in range(Config.NUMBER_OF_TRACKS): + if len(ids[t]): + notes[self.displayedPage][t] = [ self.noteDB.getNote( self.displayedPage, t, id ) for id in ids[t] ] + + self.propertiesPanel.setContext("note", self.generationPanel.scale, notes = notes ) + winLoc = self.parent.window.get_position() + balloc = self.GUI["2contextBox"].get_allocation() + walloc = self.GUI["9propertiesPopup"].get_allocation() + if walloc.height != 1: # hack to deal with showing the window before first allocation T_T + self.GUI["9propertiesPopup"].move( balloc.x + winLoc[0] - 30, balloc.y - walloc.height + winLoc[1] ) + else: + self.GUI["9propertiesPopup"].move(0, 2048) # off the screen + self.GUI["9propertiesPopup"].show() + if walloc.height == 1: + walloc = self.GUI["9propertiesPopup"].get_allocation() + self.GUI["9propertiesPopup"].move( balloc.x + winLoc[0] - 30, balloc.y - walloc.height + winLoc[1] ) + else: + self.GUI["9propertiesPopup"].hide() + + def noteDelete( self ): + ids = self.trackInterface.getSelectedNotes() + stream = [] + for t in range(Config.NUMBER_OF_TRACKS): + N = len(ids[t]) + if not N: continue + stream += [ self.displayedPage, t, N ] + ids[t] + if len(stream): + self.noteDB.deleteNotes( stream + [-1] ) + + def noteDuplicate( self ): + ids = self.trackInterface.getSelectedNotes() + stream = [] + for t in range(Config.NUMBER_OF_TRACKS): + N = len(ids[t]) + if not N: continue + stream += [ self.displayedPage, t, N ] + ids[t] + if len(stream): + self.skipCleanup = "note" + self.skipCleanup = "" + self.noteDB.notesToClipboard( stream + [-1] ) + self.trackInterface.setInterfaceMode("paste_notes") + return True + return False + + def noteDuplicateWidget( self, widget ): + if widget.get_active(): + if self.noteDuplicate(): # duplicate succeeded + return + # cancel duplicate + widget.set_active(False) + self.trackInterface.setInterfaceMode("tool") + else: + self.trackInterface.setInterfaceMode("tool") + + def noteOnset( self, step ): + self.trackInterface.noteStepOnset( step ) + + def notePitch( self, step ): + # TODO + return + + def noteDuration( self, step ): + # TODO + return + + def noteVolume( self, step ): + # TODO + return + + #======================================================= + # Track Functions + + def toggleTrack( self, trackN, exclusive ): + if exclusive: + for i in range(Config.NUMBER_OF_TRACKS): + if self.trackSelected[i]: + self.trackSelected[i] = False + self.trackInterface.trackToggled( i ) + self.tuneInterface.trackToggled( i ) + self.trackSelected[trackN] = True + self.trackInterface.trackToggled( trackN ) + self.tuneInterface.trackToggled( trackN ) + self.setContextState( CONTEXT.TRACK, True ) + self.setContext( CONTEXT.TRACK ) + else: + self.trackSelected[trackN] = not self.trackSelected[trackN] + self.trackInterface.trackToggled( trackN ) + self.tuneInterface.trackToggled( trackN ) + trackSelected = False + for i in range(Config.NUMBER_OF_TRACKS): + if self.trackSelected[i]: + self.setContextState( CONTEXT.TRACK, True ) + self.setContext( CONTEXT.TRACK ) + trackSelected = True + break + if not trackSelected: + self.setContextState( CONTEXT.TRACK, False ) + + def setTrack( self, trackN, state ): + if self.trackSelected[trackN] != state: + self.trackSelected[trackN] = state + self.trackInterface.trackToggled( trackN ) + + def clearTracks( self ): + for i in range(Config.NUMBER_OF_TRACKS): + if self.trackSelected[i]: + self.trackSelected[i]= False + self.trackInterface.trackToggled( i ) + self.tuneInterface.trackToggled( i ) + + self.setContextState( CONTEXT.TRACK, False ) + + def getTrackSelected( self, trackN ): + return self.trackSelected[trackN] + + def trackGenerate( self, widget ): + if widget.get_active(): + self.generateMode = "track" + winLoc = self.parent.window.get_position() + balloc = self.GUI["2contextBox"].get_allocation() + walloc = self.GUI["9generationPopup"].get_allocation() + if walloc.height != 1: # hack to make deal with showing the window before first allocation T_T + self.GUI["9generationPopup"].move( balloc.x + winLoc[0], balloc.y - walloc.height + winLoc[1] ) + else: + self.GUI["9generationPopup"].move(0, 2048) # off the screen + self.GUI["9generationPopup"].show() + if walloc.height == 1: + walloc = self.GUI["9generationPopup"].get_allocation() + self.GUI["9generationPopup"].move( balloc.x + winLoc[0], balloc.y - walloc.height + winLoc[1] ) + else: + self.GUI["9generationPopup"].hide() + + + def trackProperties( self, widget ): + if widget.get_active(): + self.propertiesPanel.setContext( "track", self.generationPanel.scale, self.tuneInterface.getSelectedIds(), [ i for i in range(Config.NUMBER_OF_TRACKS) if self.trackSelected[i] ] ) + winLoc = self.parent.window.get_position() + balloc = self.GUI["2contextBox"].get_allocation() + walloc = self.GUI["9propertiesPopup"].get_allocation() + if walloc.height != 1: # hack to make deal with showing the window before first allocation T_T + self.GUI["9propertiesPopup"].move( balloc.x + winLoc[0] - 30, balloc.y - walloc.height + winLoc[1] ) + else: + self.GUI["9propertiesPopup"].move(0, 2048) # off the screen + self.GUI["9propertiesPopup"].show() + if walloc.height == 1: + walloc = self.GUI["9propertiesPopup"].get_allocation() + self.GUI["9propertiesPopup"].move( balloc.x + winLoc[0] - 30, balloc.y - walloc.height + winLoc[1] ) + else: + self.GUI["9propertiesPopup"].hide() + + def trackDelete( self, pageIds = -1, trackIds = -1 ): + + if pageIds == -1: pageIds = self.tuneInterface.getSelectedIds() + if trackIds == -1: trackIds = [ i for i in range(Config.NUMBER_OF_TRACKS) if self.trackSelected[i] ] + + self.noteDB.deleteNotesByTrack( pageIds, trackIds ) + + def trackDuplicate( self, pageIds = -1, trackIds = -1 ): + + if pageIds == -1: pageIds = self.tuneInterface.getSelectedIds() + if trackIds == -1: trackIds = [ i for i in range(Config.NUMBER_OF_TRACKS) if self.trackSelected[i] ] + + if len(trackIds): + self.skipCleanup = "track" + self.skipCleanup = "" + self.noteDB.tracksToClipboard( pageIds, trackIds ) + self.trackInterface.setInterfaceMode("paste_tracks") + return True + return False + + def trackDuplicateWidget( self, widget ): + if widget.get_active(): + if self.trackDuplicate(): # duplicate succeeded + return + # cancel duplicate + widget.set_active(False) + self.trackInterface.setInterfaceMode("tool") + else: + self.trackInterface.setInterfaceMode("tool") + + #----------------------------------- + # tune/page functions + #----------------------------------- + + def displayPage( self, pageId, nextId = -1 ): + if self.playing: + if self.displayedPage != pageId and pageId in self.pages_playing: + self.csnd.loopSetTick( self.page_onset[pageId] ) + + self._displayPage( pageId, nextId ) + + + # only called locally! + def _displayPage( self, pageId, nextId = -1 ): + + self.displayedPage = pageId + + page = self.noteDB.getPage(pageId) + for i in range(Config.NUMBER_OF_TRACKS): + if self.trackInstrument[i].instrumentId != page.instruments[i]: + self.trackInstrument[i] = self.instrumentDB.instId[page.instruments[i]] + if i == Config.NUMBER_OF_TRACKS-1: + btn = self.GUI["2drumButton"] + btn.setImage( "main", self.GUI["2instrumentIcons"][self.trackInstrument[i].name] ) + btn.setImage( "alt", self.GUI["2instrumentIcons"][self.trackInstrument[i].name] ) + else: + btn = self.GUI["2instrument%dButton"%(i+1)] + btn.setPrimary( self.GUI["2instrumentIcons"][self.trackInstrument[i].name] ) + if self.trackInstrument2[i] != None: + btn.setSecondary( self.GUI["2instrumentIcons"][self.trackInstrument2[i].name] ) + else: + btn.setSecondary( None ) + self.tuneInterface.displayPage( pageId ) + self.trackInterface.displayPage( pageId, nextId ) + + def predrawPage( self, pageId ): + if self.playbackTimeout: return # we're playing, predrawing is already handled + if self.trackInterface.setPredrawPage( pageId ): # page needs to be drawn + self.trackInterface.predrawPage() + + def abortPredrawPage( self ): + self.trackInterface.abortPredrawPage() + + def pageGenerate( self, widget ): + if widget.get_active(): + self.generateMode = "page" + winLoc = self.parent.window.get_position() + balloc = self.GUI["2contextBox"].get_allocation() + walloc = self.GUI["9generationPopup"].get_allocation() + if walloc.height != 1: # hack to make deal with showing the window before first allocation T_T + self.GUI["9generationPopup"].move( balloc.x + winLoc[0], balloc.y - walloc.height + winLoc[1] ) + else: + self.GUI["9generationPopup"].move(0, 2048) # off the screen + self.GUI["9generationPopup"].show() + if walloc.height == 1: + walloc = self.GUI["9generationPopup"].get_allocation() + self.GUI["9generationPopup"].move( balloc.x + winLoc[0], balloc.y - walloc.height + winLoc[1] ) + else: + self.GUI["9generationPopup"].hide() + + def setPageGenerateMode(self, mode): + self.generateMode = mode + + def pageProperties( self, widget ): + if widget.get_active(): + self.propertiesPanel.setContext( "page", self.generationPanel.scale, self.tuneInterface.getSelectedIds() ) + winLoc = self.parent.window.get_position() + balloc = self.GUI["2contextBox"].get_allocation() + walloc = self.GUI["9propertiesPopup"].get_allocation() + if walloc.height != 1: # hack to make deal with showing the window before first allocation T_T + self.GUI["9propertiesPopup"].move( balloc.x + winLoc[0] - 100, balloc.y - walloc.height + winLoc[1] ) + else: + self.GUI["9propertiesPopup"].move(0, 2048) # off the screen + self.GUI["9propertiesPopup"].show() + if walloc.height == 1: + walloc = self.GUI["9propertiesPopup"].get_allocation() + self.GUI["9propertiesPopup"].move( balloc.x + winLoc[0] - 100, balloc.y - walloc.height + winLoc[1] ) + else: + self.GUI["9propertiesPopup"].hide() + + def pageDelete( self, pageIds = -1, instruments = False ): + + if pageIds == -1: + pageIds = self.tuneInterface.getSelectedIds() + + if instruments == False: + instruments = [] + for inst in self.trackInstrument: + instruments.append(inst.instrumentId) + + self.noteDB.deletePages( pageIds[:], instruments ) + + def pageDuplicate( self, after = -1, pageIds = False ): + + if after == -1: after = self.tuneInterface.getLastSelected() + if not pageIds: pageIds = self.tuneInterface.getSelectedIds() + + new = self.noteDB.duplicatePages( pageIds[:], after ) + self.displayPage( new[self.displayedPage] ) + self.tuneInterface.selectPages( new.values() ) + + def pageAdd( self, after = -1, beats = False, color = False, instruments = False ): + + if after == -1: after = self.tuneInterface.getLastSelected() + page = self.noteDB.getPage( self.displayedPage ) + if not beats: beats = page.beats + if not color: color = page.color + if not instruments: instruments = page.instruments + + # TODO think about network mode here... + self.displayPage( self.noteDB.addPage( -1, NoteDB.Page(beats,color,instruments), after ) ) + + def pageBeats( self, pageIds = -1 ): + + if pageIds == -1: pageIds = self.tuneInterface.getSelectedIds() + + # TODO change the beats + + #======================================================= + # NoteDB notifications + + def notifyPageAdd( self, id, at ): + return + + def notifyPageDelete( self, which, safe ): + if self.displayedPage in which: + self.displayPage( safe ) + + def notifyPageDuplicate( self, new, at ): + return + + def notifyPageMove( self, which, low, high ): + return + + def notifyPageUpdate( self, page, parameter, value ): + pass + + def notifyNoteAdd( self, page, track, id ): + if (Config.DEBUG > 3) : print 'INFO: adding note to loop', page, track, id + n = self.noteDB.getNote(page, track, id) + self.csnd.loopPlay(n,0) + if self.playing and (n.page in self.page_onset ): + onset = n.cs.onset + self.page_onset[n.page] + self.csnd.loopUpdate(n, NoteDB.PARAMETER.ONSET, onset, 1) #set onset + activate + + def notifyNoteDelete( self, page, track, id ): + if (Config.DEBUG > 3) : print 'INFO: deleting note from loop', page, track, id + self.csnd.loopDelete1(page,id) + def notifyNoteUpdate( self, page, track, id, parameter, value ): + if (Config.DEBUG > 3) : print 'INFO: updating note ', page, id, parameter, value + note = self.noteDB.getNote(page, track, id) + self.csnd.loopUpdate(note, parameter, value, -1) + + #----------------------------------- + # load and save functions + #----------------------------------- + + def waitToSet(self): + self.csnd.setMasterVolume(self._data['volume']) + self.csnd.setTempo(self._data['tempo']) + self.initTrackVolume() + + def handleSave(self, widget = None): + + chooser = gtk.FileChooserDialog( + title='Save Tune', + action=gtk.FILE_CHOOSER_ACTION_SAVE, + buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_SAVE,gtk.RESPONSE_OK)) + filter = gtk.FileFilter() + filter.add_pattern('*.tam') + chooser.set_filter(filter) + chooser.set_current_folder(Config.DATA_DIR) + + for f in chooser.list_shortcut_folder_uris(): + chooser.remove_shortcut_folder_uri(f) + + if chooser.run() == gtk.RESPONSE_OK: + ofilename = chooser.get_filename() + if ofilename[-4:] != '.tam': + ofilename += '.tam' + try: + ofile = open(ofilename, 'w') + ofilestream = ControlStream.TamTamOStream (ofile) + self.noteDB.dumpToStream(ofilestream) + ofilestream.track_vol(self._data['track_volume']) + ofilestream.master_vol(self._data['volume']) + ofilestream.tempo(self._data['tempo']) + ofile.close() + except OSError,e: + print 'ERROR: failed to open file %s for writing\n' % ofilename + chooser.destroy() + + def handleLoopSave(self): + date = str(time.localtime()[3]) + '-' + str(time.localtime()[4]) + '-' + str(time.localtime()[5]) + ofilename = Config.DATA_DIR + '/' + date + '.ttl' + ofile = open(ofilename, 'w') + ofilestream = ControlStream.TamTamOStream (ofile) + self.noteDB.dumpToStream(ofilestream) + ofilestream.track_vol(self._data['track_volume']) + ofilestream.master_vol(self._data['volume']) + ofilestream.tempo(self._data['tempo']) + ofile.close() + + def handleJournalSave(self, file_path): + ofile = open(file_path, 'w') + ofilestream = ControlStream.TamTamOStream (ofile) + self.noteDB.dumpToStream(ofilestream) + ofilestream.track_vol(self._data['track_volume']) + ofilestream.master_vol(self._data['volume']) + ofilestream.tempo(self._data['tempo']) + ofile.close() + + def _loadFile( self, path ): + try: + oldPages = self.noteDB.getTune()[:] + + ifile = open(path, 'r') + ttt = ControlStream.TamTamTable ( self.noteDB ) + ttt.parseFile(ifile) + self.trackInstrument = self.trackInstrumentDefault[:] # these will get set correctly in displayPage + self._data['track_volume'] = ttt.tracks_volume + self._data['volume'] = float(ttt.masterVolume) + self._data['tempo'] = float(ttt.tempo) + #self.GUI["2volumeAdjustment"].set_value(self._data['volume']) + #self.GUI["2tempoAdjustment"].set_value(self._data['tempo']) + ifile.close() + + self.noteDB.deletePages( oldPages ) + + self.tuneInterface.selectPages( self.noteDB.getTune() ) + except OSError,e: + print 'ERROR: failed to open file %s for reading\n' % ofilename + + def handleLoad(self, widget): + chooser = gtk.FileChooserDialog( + title='Load Tune', + action=gtk.FILE_CHOOSER_ACTION_OPEN, + buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) + + filter = gtk.FileFilter() + filter.add_pattern('*.tam') + chooser.set_filter(filter) + chooser.set_current_folder(Config.DATA_DIR) + + for f in chooser.list_shortcut_folder_uris(): + chooser.remove_shortcut_folder_uri(f) + + if chooser.run() == gtk.RESPONSE_OK: + print 'DEBUG: loading file: ', chooser.get_filename() + self._loadFile( chooser.get_filename() ) + + chooser.destroy() + self.delay = gobject.timeout_add(1000, self.waitToSet) + + def handleJournalLoad(self,file_path): + self.journalCalled = True + self._loadFile( file_path ) + + #----------------------------------- + # Record functions + #----------------------------------- + def handleMicRecord( self, widget, data ): + self.csnd.micRecording( data ) + def handleCloseMicRecordWindow( self, widget = None, data = None ): + self.micRecordWindow.destroy() + self.micRecordButton.set_active( False ) + + #----------------------------------- + # callback functions + #----------------------------------- + def handleKeyboardShortcuts(self,event): + keyval = event.keyval + + if self.activity.activity_toolbar.title.is_focus(): + return + + # backspace and del keys + if keyval == gtk.keysyms.Delete or keyval == gtk.keysyms.BackSpace: + if self.context == CONTEXT.PAGE: self.pageDelete() + if self.context == CONTEXT.TRACK: self.trackDelete() + if self.context == CONTEXT.NOTE: self.noteDelete() + # plus key + if keyval == gtk.keysyms.equal: + self.pageAdd() + # duplicate ctrl-c + if event.state == gtk.gdk.CONTROL_MASK and keyval == gtk.keysyms.c: + if self.context == CONTEXT.PAGE: self.pageDuplicate() + if self.context == CONTEXT.TRACK: self.trackDuplicate() + if self.context == CONTEXT.NOTE: self.noteDuplicate() + #Arrows + if event.state == gtk.gdk.SHIFT_MASK: + # up/down arrows volume + if keyval == gtk.keysyms.Up: self.trackInterface.noteStepVolume(0.1) + if keyval == gtk.keysyms.Down: self.trackInterface.noteStepVolume(-0.1) + # left/right arrows onset + if keyval == gtk.keysyms.Left: self.trackInterface.noteStepDuration(-1) + if keyval == gtk.keysyms.Right: self.trackInterface.noteStepDuration(1) + else: + # up/down arrows pitch + if keyval == gtk.keysyms.Up: self.trackInterface.noteStepPitch(1) + if keyval == gtk.keysyms.Down: self.trackInterface.noteStepPitch(-1) + # left/right arrows duration + if keyval == gtk.keysyms.Left: self.trackInterface.noteStepOnset(-1) + if keyval == gtk.keysyms.Right: self.trackInterface.noteStepOnset(1) + #Save Loop + if event.state == gtk.gdk.CONTROL_MASK and keyval == gtk.keysyms.s: + self.handleLoopSave() + + + def onKeyPress(self,widget,event): + + self.handleKeyboardShortcuts(event) + Config.ModKeys.keyPress( event.hardware_keycode ) + key = event.hardware_keycode + + # If the key is already in the dictionnary, exit function (to avoir key repeats) + if self.kb_keydict.has_key(key): + return + + # Assign on which track the note will be created according to the number of keys pressed + if self.trackCount >= 9: + self.trackCount = 6 + fakeTrack = self.trackCount + self.trackCount += 1 + + # If the pressed key is in the keymap + if KEY_MAP_PIANO.has_key(key): + pitch = KEY_MAP_PIANO[key] + duration = -1 + + # get instrument from top selected track if a track is selected + if True in self.trackSelected: + index = self.trackSelected.index(True) + instrument = self.getTrackInstrument(index).name + else: + return + + tid = index + + # pitch, inst and duration for drum recording + if tid == Config.NUMBER_OF_TRACKS-1: + if GenerationConstants.DRUMPITCH.has_key( pitch ): + pitch = GenerationConstants.DRUMPITCH[pitch] + if self.instrumentDB.instNamed[instrument].kit != None: + instrument = self.instrumentDB.instNamed[instrument].kit[pitch].name + duration = 100 + + # Create and play the note + self.kb_keydict[key] = CSoundNote(onset = 0, + pitch = pitch, + amplitude = 1, + pan = 0.5, + duration = duration, + trackId = fakeTrack, + instrumentId = self.instrumentDB.instNamed[instrument].instrumentId, + tied = False, + mode = 'edit') + self.csnd.play(self.kb_keydict[key], 0.3) + + # doesn't keep track of keys for drum recording + if tid == Config.NUMBER_OF_TRACKS-1: + del self.kb_keydict[key] + + # remove previosly holded key from dictionary + if len(self.kb_keydict) > 1: + for k in self.kb_keydict.keys(): + if k != key: + gobject.source_remove( self.durUpdate ) + self.durUpdate = False + self.kb_keydict[k].duration = 0.5 + self.kb_keydict[k].amplitude = 0 + self.kb_keydict[k].decay = 0.7 + self.kb_keydict[k].tied = False + self.csnd.play(self.kb_keydict[k], 0.3) + if not self.kb_record: + del self.kb_keydict[k] + return + self.removeRecNote(self.csId) + + if not self.kb_record: + return + + #record the note on track + pageList = self.tuneInterface.getSelectedIds() + pid = self.displayedPage + minOnset = self.page_onset[pid] + onsetQuantized = Config.DEFAULT_GRID * int((self.csnd.loopGetTick() - minOnset) / Config.DEFAULT_GRID + 0.5) + + maxOnset = self.noteDB.getPage(pid).ticks + if onsetQuantized >= maxOnset: + if pid == pageList[-1]: + pid = pageList[0] + else: + if len(pageList) > 1: + pidPos = pageList.index(pid) + pid = pageList[pidPos+1] + onsetQuantized = 0 + + if tid < Config.NUMBER_OF_TRACKS-1: + for n in self.noteDB.getNotesByTrack( pid, tid ): + if onsetQuantized < n.cs.onset: + break + if onsetQuantized >= n.cs.onset + n.cs.duration: + continue + if onsetQuantized < n.cs.onset + n.cs.duration - 2: + self.noteDB.deleteNote(n.page, n.track, n.id) + elif onsetQuantized - n.cs.onset < 1: + self.noteDB.deleteNote(n.page, n.track, n.id) + else: + self.noteDB.updateNote( n.page, n.track, n.id, PARAMETER.DURATION, onsetQuantized - n.cs.onset ) + break + else: + for n in self.noteDB.getNotesByTrack( pid, tid ): + if onsetQuantized < n.cs.onset: + break + if onsetQuantized == n.cs.onset: + if pitch < n.cs.pitch: + break + if pitch == n.cs.pitch: + return # don't bother with a new note + + csnote = CSoundNote(onset = 0, + pitch = pitch, + amplitude = 1, + pan = 0.5, + duration = duration, + trackId = index, + instrumentId = self.instrumentDB.instNamed[instrument].instrumentId, + tied = False, + mode = 'edit') + + csnote.onset = onsetQuantized + csnote.duration = 1 + csnote.pageId = pid + id = self.noteDB.addNote(-1, pid, tid, csnote) + # csId: PageId, TrackId, Onset, Key, DurationSetOnce + self.csId = [pid, tid, id, csnote.onset, key, False ] + if tid < Config.NUMBER_OF_TRACKS-1: + self.durUpdate = gobject.timeout_add( 25, self.durationUpdate ) + + def onKeyRelease(self,widget,event): + + Config.ModKeys.keyRelease( event.hardware_keycode ) + key = event.hardware_keycode + + if True in self.trackSelected: + index = self.trackSelected.index(True) + if index == Config.NUMBER_OF_TRACKS-1: + return + else: + return + + if KEY_MAP_PIANO.has_key(key) and self.kb_keydict.has_key(key): + if self.kb_record and self.durUpdate: + gobject.source_remove( self.durUpdate ) + self.durUpdate = False + + if self.instrumentDB.instId[ self.kb_keydict[key].instrumentId ].csoundInstrumentId == Config.INST_TIED: + self.kb_keydict[key].duration = 0.5 + self.kb_keydict[key].amplitude = 0 + self.kb_keydict[key].decay = 0.5 + self.kb_keydict[key].tied = False + self.csnd.play(self.kb_keydict[key], 0.3) + if not self.kb_record: + del self.kb_keydict[key] + return + + self.removeRecNote(self.csId) + + def removeRecNote(self, csId): + newDuration = (int(self.csnd.loopGetTick()) - self.page_onset[csId[0]]) - csId[3] + maxTick = self.noteDB.getPage(csId[0]).ticks + + if not csId[5]: # handle notes that were created right at the end of a page + if newDuration > maxTick//2: + newDuration = 1 + else: + csId[5] = True + + if newDuration < -Config.DEFAULT_GRID_DIV2: # we looped around + newDuration = maxTick - self.csId[3] + elif newDuration < 1: + newDuration = 1 + + if (csId[3] + newDuration) > maxTick: + newDuration = maxTick - csId[3] + + for n in self.noteDB.getNotesByTrack( csId[0], csId[1] ): + if n.id == csId[2]: + continue + if csId[3] + newDuration <= n.cs.onset: + break + if csId[3] >= n.cs.onset + n.cs.duration: + continue + self.noteDB.deleteNote(n.page, n.track, n.id) + break + + self.noteDB.updateNote( csId[0], csId[1], csId[2], PARAMETER.DURATION, newDuration) + + del self.kb_keydict[csId[4]] + + def durationUpdate(self): + newDuration = (int(self.csnd.loopGetTick()) - self.page_onset[self.csId[0]]) - self.csId[3] + + maxTick = self.noteDB.getPage(self.csId[0]).ticks + stop = False + + if not self.csId[5]: # handle notes that were created right at the end of a page + if newDuration > maxTick//2: + newDuration = 1 + else: + self.csId[5] = True + + if newDuration < -Config.DEFAULT_GRID_DIV2: # we looped around + newDuration = maxTick - self.csId[3] + stop = True + elif newDuration < 1: + newDuration = 1 + + if (self.csId[3] + newDuration) > maxTick: + stop = True + newDuration = maxTick - self.csId[3] + + for n in self.noteDB.getNotesByTrack( self.csId[0], self.csId[1] ): + if n.id == self.csId[2]: + continue + if self.csId[3] + newDuration <= n.cs.onset: + break + if self.csId[3] >= n.cs.onset + n.cs.duration: + continue + self.noteDB.deleteNote(n.page, n.track, n.id) + break + + self.noteDB.updateNote( self.csId[0], self.csId[1], self.csId[2], PARAMETER.DURATION, newDuration) + + if stop: + key = self.csId[4] + if self.instrumentDB.instId[ self.kb_keydict[key].instrumentId ].csoundInstrumentId == Config.INST_TIED: + self.kb_keydict[key].duration = 0.5 + self.kb_keydict[key].amplitude = 0 + self.kb_keydict[key].decay = 0.5 + self.kb_keydict[key].tied = False + self.csnd.play(self.kb_keydict[key], 0.3) + + del self.kb_keydict[key] + return False + return True + + def delete_event( self, widget, event, data = None ): + return False + + def onDestroy( self ): + + if (Config.DEBUG > 1): print TP.PrintAll() + + def setContextState( self, context, state ): + if context == CONTEXT.TRACK: + self.contextTrackActive = state + if not state: + if self.context == CONTEXT.TRACK: + if self.contextNoteActive: + self.setContext( CONTEXT.NOTE ) + else: + self.setContext( CONTEXT.PAGE ) + else: + self.contextNoteActive = state + if not state: + if self.context == CONTEXT.NOTE: + self.prevContext() + + def setContext( self, context, force = False ): + + if self.context == context and not force: return + + self.context = context + + if self.context == CONTEXT.NOTE: + self._generateToolbar.generationButton.set_sensitive(False) + else: + self._generateToolbar.generationButton.set_sensitive(True) + + def getContext(self): + return self.context + + def prevContext( self ): + if self.context == CONTEXT.TRACK: + self.setContext( CONTEXT.PAGE ) + elif self.contextTrackActive: + self.setContext( CONTEXT.TRACK ) + else: + self.setContext( CONTEXT.PAGE ) + + def nextContext( self ): + if self.context == CONTEXT.TRACK: + self.setContext( CONTEXT.NOTE ) + elif self.contextTrackActive: + self.setContext( CONTEXT.TRACK ) + else: + self.setContext( CONTEXT.NOTE ) + + #----------------------------------- + # access functions (not sure if this is the best way to go about doing this) + #----------------------------------- + def getVolume( self ): + return self._data["volume"] + + def getTempo( self ): + return self._data["tempo"] + #return round( self.tempoAdjustment.value, 0 ) + + def getBeatsPerPage( self ): + return int(round( self.beatsPerPageAdjustment.value, 0 )) + + def getWindowTitle( self ): + return "Tam-Tam [Volume %i, Tempo %i, Beats/Page %i]" % ( self.getVolume(), self.getTempo(), self.getBeatsPerPage() ) + + +class InstrumentButton( gtk.DrawingArea ): + + def __init__( self, owner, index, backgroundFill ): + gtk.DrawingArea.__init__( self ) + + self.index = index + self.owner = owner + + self.win = gtk.gdk.get_default_root_window() + self.gc = gtk.gdk.GC( self.win ) + + colormap = self.get_colormap() + self.color = { "background": colormap.alloc_color( backgroundFill, True, True ), + "divider": colormap.alloc_color( "#000", True, True ), + "+/-": colormap.alloc_color( Config.FG_COLOR, True, True ), + "+/-Highlight": colormap.alloc_color( "#FFF", True, True ) } + + self.pixmap = None + self.primary = None + self.primaryWidth = self.primaryHeight = 1 + self.secondary = None + self.secondaryWidth = self.secondaryHeight = 1 + + self.clicked = None + self.hover = None + + self.add_events( gtk.gdk.BUTTON_PRESS_MASK + | gtk.gdk.BUTTON_RELEASE_MASK + | gtk.gdk.POINTER_MOTION_MASK + | gtk.gdk.POINTER_MOTION_HINT_MASK + | gtk.gdk.LEAVE_NOTIFY_MASK + | gtk.gdk.ENTER_NOTIFY_MASK ) + self.connect( "size-allocate", self.size_allocate ) + self.connect( "button-press-event", self.button_press ) + self.connect( "button-release-event", self.button_release ) + self.connect( "motion-notify-event", self.motion_notify ) + self.connect( "leave-notify-event", self.leave_notify ) + self.connect( "expose-event", self.expose ) + + def size_allocate( self, widget, allocation ): + self.alloc = allocation + self.pixmap = gtk.gdk.Pixmap( self.win, allocation.width, allocation.height ) + self.primaryX = (self.alloc.width - self.primaryWidth) // 2 + self.primaryY = (self.alloc.height - self.primaryHeight) // 2 + self.secondaryX = (self.alloc.width - self.secondaryWidth) // 2 + self.secondaryY = self.alloc.height//2 + + self.hotspots = [ [ self.alloc.width-24, self.alloc.height-29, self.alloc.width-8, self.alloc.height-13 ], + [ self.alloc.width-24, self.alloc.height//2-23, self.alloc.width-8, self.alloc.height//2-7 ] ] + + self.hotspots[0] += [ (self.hotspots[0][0]+self.hotspots[0][2])//2, (self.hotspots[0][1]+self.hotspots[0][3])//2 ] + self.hotspots[1] += [ (self.hotspots[1][0]+self.hotspots[1][2])//2, (self.hotspots[1][1]+self.hotspots[1][3])//2 ] + + self._updatePixmap() + + def button_press( self, widget, event ): + + self.clicked = "PRIMARY" + self.hover = None + + if event.x >= self.hotspots[0][0] and event.x <= self.hotspots[0][2] \ + and event.y >= self.hotspots[0][1] and event.y <= self.hotspots[0][3]: + self.clicked = "HOTSPOT_0" + + elif self.secondary != None: + + if event.x >= self.hotspots[1][0] and event.x <= self.hotspots[1][2] \ + and event.y >= self.hotspots[1][1] and event.y <= self.hotspots[1][3]: + self.clicked = "HOTSPOT_1" + + elif event.y > self.alloc.height//2: + self.clicked = "SECONDARY" + + def button_release( self, widget, event ): + if self.clicked == "PRIMARY": + self.owner.pickInstrument( self, self.index, True ) + elif self.clicked == "SECONDARY": + self.owner.pickInstrument( self, self.index, False ) + elif self.clicked == "HOTSPOT_0": + if self.secondary != None: # remove secondary + self.owner.clearInstrument( self.index, False ) + else: # add secondary + self.owner.pickInstrument( self, self.index, False ) + else: # HOTSPOT_1, remove primary + self.owner.clearInstrument( self.index, True ) + + self.clicked = None + + def motion_notify( self, widget, event ): + + if self.clicked != None: + return + + if event.is_hint: + x, y, state = widget.window.get_pointer() + event.x = float(x) + event.y = float(y) + event.state = state + + if event.x >= self.hotspots[0][0] and event.x <= self.hotspots[0][2] \ + and event.y >= self.hotspots[0][1] and event.y <= self.hotspots[0][3]: + if self.hover != "HOTSPOT_0": + self.hover = "HOTSPOT_0" + self.queue_draw() + + + elif self.secondary != None \ + and event.x >= self.hotspots[1][0] and event.x <= self.hotspots[1][2] \ + and event.y >= self.hotspots[1][1] and event.y <= self.hotspots[1][3]: + if self.hover != "HOTSPOT_1": + self.hover = "HOTSPOT_1" + self.queue_draw() + else: + if self.hover != None: + self.hover = None + self.queue_draw() + + def leave_notify( self, widget, event ): + if event.mode != gtk.gdk.CROSSING_NORMAL: + return + if self.hover != None: + self.hover = None + if self.clicked == None: + self.queue_draw() + + + def setPrimary( self, img ): + self.primary = img + self.primaryWidth = img.get_width() + self.primaryHeight = img.get_height() + if self.pixmap: + self.primaryX = (self.alloc.width - self.primaryWidth) // 2 + self.primaryY = (self.alloc.height - self.primaryHeight) // 2 + self._updatePixmap() + + def setSecondary( self, img ): + self.secondary = img + if img != None: + self.secondaryWidth = img.get_width() + self.secondaryHeight = img.get_height() + self.secondaryOffset = self.secondaryHeight//2 + if self.pixmap: + self.secondaryX = (self.alloc.width - self.secondaryWidth) // 2 + self.secondaryY = self.alloc.height//2 + if self.pixmap: + self._updatePixmap() + + def _updatePixmap( self ): + self.gc.foreground = self.color["background"] + self.pixmap.draw_rectangle( self.gc, True, 0, 0, self.alloc.width, self.alloc.height ) + if self.secondary != None: + self.pixmap.draw_pixbuf( self.gc, self.primary, 0, 0, self.primaryX, self.primaryY, self.primaryWidth, self.primaryHeight//2, gtk.gdk.RGB_DITHER_NONE ) + self.pixmap.draw_pixbuf( self.gc, self.secondary, 0, self.secondaryOffset, self.secondaryX, self.secondaryY, self.secondaryWidth, self.secondaryHeight//2, gtk.gdk.RGB_DITHER_NONE ) + self.gc.foreground = self.color["divider"] + self.gc.set_line_attributes( 2, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, gtk.gdk.JOIN_MITER ) + self.pixmap.draw_line( self.gc, 2, self.alloc.height//2, self.alloc.width-4, self.alloc.height//2 ) + else: + self.pixmap.draw_pixbuf( self.gc, self.primary, 0, 0, self.primaryX, self.primaryY, self.primaryWidth, self.primaryHeight, gtk.gdk.RGB_DITHER_NONE ) + self.queue_draw() + + def expose( self, widget, event ): + self.window.draw_drawable( self.gc, self.pixmap, 0, 0, 0, 0, self.alloc.width, self.alloc.height ) + self.gc.set_line_attributes( 4, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_ROUND, gtk.gdk.JOIN_MITER ) + if self.secondary != None: + if self.clicked == "HOTSPOT_0" or (self.clicked == None and self.hover == "HOTSPOT_0" ): + self.gc.foreground = self.color["+/-Highlight"] + else: + self.gc.foreground = self.color["+/-"] + self.window.draw_line( self.gc, self.hotspots[0][0], self.hotspots[0][5], self.hotspots[0][2], self.hotspots[0][5] ) + if self.clicked == "HOTSPOT_1" or (self.clicked == None and self.hover == "HOTSPOT_1" ): + self.gc.foreground = self.color["+/-Highlight"] + else: + self.gc.foreground = self.color["+/-"] + self.window.draw_line( self.gc, self.hotspots[1][0], self.hotspots[1][5], self.hotspots[1][2], self.hotspots[1][5] ) + else: + if self.clicked == "HOTSPOT_0" or self.hover == "HOTSPOT_0": + self.gc.foreground = self.color["+/-Highlight"] + else: + self.gc.foreground = self.color["+/-"] + self.window.draw_line( self.gc, self.hotspots[0][0], self.hotspots[0][5], self.hotspots[0][2], self.hotspots[0][5] ) + self.window.draw_line( self.gc, self.hotspots[0][4], self.hotspots[0][1], self.hotspots[0][4], self.hotspots[0][3] ) + + def set_palette(self, palette): + pass + + +class NoneInvoker( Invoker ): + + def __init__( self ): + Invoker.__init__( self ) + self._position_hint = Invoker.AT_CURSOR + + def get_rect( self ): + return gtk.gdk.Rectangle( 0, 0, 0, 0 ) + + def get_toplevel( self ): + return None + +class Popup( Palette ): + + def __init__( self, label, owner ): + Palette.__init__( self, label ) + + self.owner = owner + + self.block = None + + self.props.invoker = NoneInvoker() + self.set_group_id( "TamTamPopup" ) + + self.connect( "key-press-event", self.on_key_press ) + self.connect( "key-release-event", self.on_key_release ) + + #self.connect( "focus_out_event", self.closePopup ) + + def destroy( self ): + pass + + def _leave_notify_event_cb( self, widget, event ): + return # don't popdown() + + def _show( self ): + Palette._show( self ) + + if self._palette_popup_sid != None: + self._palette_popup_sid = None + + def popup( self, immediate = False ): + if hasattr(self, '_set_state'): + self._set_state(self.SECONDARY) + Palette.popup( self, immediate) + else: + Palette.popup( self, immediate, state = Palette.SECONDARY ) + + def popdown( self, immediate = False ): + self.block = None + + Palette.popdown( self, immediate ) + + def updatePosition( self ): + self.props.invoker._cursor_x = -1 + self.props.invoker._cursor_y = -1 + self._update_position() + + def closePopup( self, widget, event ): + self.popdown( True ) + + def on_key_press( self, widget, event ): + self.owner.onKeyPress( widget, event ) + + def on_key_release( self, widget, event ): + self.owner.onKeyRelease( widget, event ) + +class instrumentPalette( Popup ): + ICON_SIZE = (70,70) + def __init__(self, label, edit): + Popup.__init__(self, label, edit) + + self.instrumentDB = InstrumentDB.getRef() + self.edit = edit + + self.skip = False + self.skipVolAdj = False + self.lastClickedTrack = None + + self.tooltips = gtk.Tooltips() + + self.mainBox = gtk.VBox() + self.volumeBox = gtk.HBox() + self.instrumentMainBox = gtk.HBox() + + + self.muteButtonLabel = gtk.Label(_('M')) + self.muteButton = gtk.CheckButton() + self.muteButton.connect("toggled",self.handlemuteButton) + self.muteButton.set_active(True) + self.tooltips.set_tip(self.muteButton, _('Mute track')) + + self.soloButtonLabel = gtk.Label(_('S')) + self.soloButton = gtk.CheckButton() + self.soloButton.connect("toggled",self.handlesoloButton) + self.soloButton.set_active(True) + self.tooltips.set_tip(self.soloButton, _('Solo track')) + + self.volumeSliderAdj = gtk.Adjustment( self.edit._data["track_volume"][0], 0, 100, 1, 1, 0 ) + self.volumeSliderAdj.connect( "value-changed", self.handleTrackVolume) + self.volumeSlider = gtk.HScale(adjustment = self.volumeSliderAdj) + self.volumeSlider.set_size_request(250, -1) + self.volumeSlider.set_inverted(False) + self.volumeSlider.set_draw_value(False) + + self.categories = Config.CATEGORIES + self.categoryBox = BigComboBox() + for category in self.categories: + image = Config.IMAGE_ROOT + category.lower() + '.png' + if not os.path.isfile(image): + image = Config.IMAGE_ROOT + 'generic.png' + self.categoryBox.append_item(category, category.capitalize(), + icon_name = image, size = instrumentPalette.ICON_SIZE) + self.categoryBox.connect('changed', self.handleCategoryChange) + + self.icons = [] + + for i in self.instrumentDB.inst: + if not i.kit and not i.kitStage: + self.icons.append([i, gtk.gdk.pixbuf_new_from_file_at_size( + i.img, instrumentPalette.ICON_SIZE[0], + instrumentPalette.ICON_SIZE[1])]) + + self.instruments = [] + self.instrumentBox1 = BigComboBox() + self.instrumentBox1.connect('changed', self.handleInstrumentChange) + + self.volumeBox.pack_start(self.muteButtonLabel, padding = 5) + self.volumeBox.pack_start(self.muteButton, padding = 5) + #self.volumeBox.pack_start(self.soloButtonLabel, padding = 5) + #self.volumeBox.pack_start(self.soloButton, padding = 5) + self.volumeBox.pack_start(self.volumeSlider, padding = 5) + self.mainBox.pack_start(self.volumeBox, padding = 5) + self.instrumentMainBox.pack_start(self.categoryBox, padding = 5) + self.instrumentMainBox.pack_start(self.instrumentBox1, padding = 5) + self.mainBox.pack_start(self.instrumentMainBox, padding = 5) + self.mainBox.show_all() + + self.set_content(self.mainBox) + + def handleTrackVolume(self, widget): + if not self.skipVolAdj: + if self.lastClickedTrack != None: + self.edit.handleTrackVolume(widget = widget, track = self.lastClickedTrack) + + def handlemuteButton(self, widget): + if not self.skipVolAdj: + if self.lastClickedTrack != None: + self.edit.handlemuteButton(widget, self.lastClickedTrack) + + def handlesoloButton(self, widget, event = None): + pass + + def handleInstrumentChange(self, widget): + if not self.skip and self.instrumentBox1.get_active() != -1: + instrument = widget.props.value + self.edit.donePickInstrument(instrument) + time.sleep(0.05) + self.edit.playInstrumentNote(instrument) + self.popdown(True) + + def handleCategoryChange(self, widget): + category = widget.props.value.lower() + + self.instrumentBox1.set_active(-1) + self.instrumentBox1.remove_all() + self.instruments = [] + + for i in self.icons: + if category == 'all' or i[0].category == category: + self.instrumentBox1.append_item(i[0].name, None, pixbuf = i[1]) + self.instruments.append(i[0].name) + + if not self.skip: + self.instrumentBox1.popup() + + def setInstrument(self, instrument): + self.skip = True + self.categoryBox.set_active(self.categories.index(instrument.category)) + self.instrumentBox1.set_active(self.instruments.index(instrument.name)) + self.skip = False + + def setBlock( self, widget = None, event = None, block = None ): + if self.is_up(): + self.popdown(True) + else: + self.set_primary_text(_('Track %s Properties' % str(block+1))) + self.skipVolAdj = True + self.volumeSliderAdj.set_value(self.edit._data["track_volume"][block]) + if self.edit.trackActive[block]: + self.muteButton.set_active(True) + else: + self.muteButton.set_active(False) + self.skipVolAdj = False + self.lastClickedTrack = block + self.popup( True ) + +class drumPalette( Popup ): + ICON_SIZE = (70,70) + def __init__(self, label, edit, trackID): + Popup.__init__(self, label, edit) + + self.instrumentDB = InstrumentDB.getRef() + self.trackID = trackID + self.edit = edit + + self.skip = False + + self.tooltips = gtk.Tooltips() + + self.mainBox = gtk.VBox() + self.volumeBox = gtk.HBox() + self.instrumentMainBox = gtk.HBox() + + + self.muteButton = gtk.CheckButton() + self.muteButton.connect("toggled",self.edit.handlemuteButton, self.trackID) + self.muteButton.connect("button-press-event",self.edit.handlemuteButtonRightClick, self.trackID) + self.muteButton.set_active(True) + self.tooltips.set_tip(self.muteButton, _('Left click to mute, right click to solo')) + + if self.trackID < 4: + exec "self.volumeSliderAdj = self.edit.GUI['2instrument%svolumeAdjustment']" % str(self.trackID+1) + else: + self.volumeSliderAdj = self.edit.GUI["2drumvolumeAdjustment"] + self.volumeSliderAdj.connect( "value-changed", self.edit.handleTrackVolume, self.trackID) + self.volumeSlider = gtk.HScale(adjustment = self.volumeSliderAdj) + self.volumeSlider.set_size_request(250, -1) + self.volumeSlider.set_inverted(False) + self.volumeSlider.set_draw_value(False) + + self.drums = self.getDrums() + + self.drumBox = BigComboBox() + self.loadDrumMenu(self.getDrums()) + self.drumBox.connect('changed', self.handleInstrumentChange) + + self.volumeBox.pack_start(self.muteButton, padding = 5) + self.volumeBox.pack_start(self.volumeSlider, padding = 5) + self.mainBox.pack_start(self.volumeBox, padding = 5) + self.instrumentMainBox.pack_start(self.drumBox, False, False, padding = 5) + self.mainBox.pack_start(self.instrumentMainBox, padding = 5) + self.mainBox.show_all() + + self.set_content(self.mainBox) + + def handleInstrumentChange(self, widget): + if not self.skip: + drum = widget.props.value + self.edit.donePickDrum(drum) + time.sleep(0.05) + self.edit.playInstrumentNote(drum) + self.popdown(True) + + + def setDrum(self, Drum): + self.skip = True + self.drumBox.set_active(self.drums.index(Drum)) + self.skip = False + + def loadDrumMenu(self, instruments): + self.drumBox.remove_all() + for instrument in instruments: + image = Config.IMAGE_ROOT + instrument + '.png' + if not os.path.isfile(image): + image = Config.IMAGE_ROOT + 'generic.png' + self.drumBox.append_item(instrument, text = None, icon_name = image, size = instrumentPalette.ICON_SIZE) + + def getDrums(self): + return sorted([instrument for instrument in self.instrumentDB.instNamed.keys() if self.instrumentDB.instNamed[instrument].kit]) + + def setBlock( self, widget = None, event = None, block = None ): + if self.is_up(): + self.popdown(True) + else: + self.popup( True ) diff --git a/Edit/NoteInterface.py b/Edit/NoteInterface.py new file mode 100644 index 0000000..9dc4247 --- /dev/null +++ b/Edit/NoteInterface.py @@ -0,0 +1,345 @@ +import pygtk +pygtk.require( '2.0' ) +import gtk + +import common.Config as Config + +from common.Util.NoteDB import PARAMETER +from common.Util.CSoundClient import new_csound_client + +class NoteInterface: + + def __init__( self, noteDB, owner, note ): + self.noteDB = noteDB + self.owner = owner + self.note = note + + self.origin = self.owner.getTrackOrigin( note.track ) + self.firstTransform = True + self.x = 0 + self.y = 0 + self.width = 1 + self.height = Config.NOTE_HEIGHT + self.imgX = 0 + self.imgY = 0 + self.imgWidth = 1 + self.imgHeight = self.height + Config.NOTE_IMAGE_PADDING_MUL2 + + self.selected = False + self.potentialDeselect = False + + self.oldOnset = -1 + self.oldEnd = -1 + self.oldPitch = -1 + self.oldAmplitude = -1 + self.oldBeats = -1 + self.lastDragO = 0 + self.lastDragP = 0 + self.lastDragD = 0 + + self.image, self.imageSelected, self.colormap, self.baseColors = self.owner.getDrawingPackage( note.track ) + + self.updateParameter( None, None ) + + def destroy( self ): + if self.selected: + self.owner.deselectNotes( { self.note.track: [self] } ) + else: # if we were deselected above the rect has already been invalidated + self.owner.invalidate_rect( self.imgX, self.imgY, self.imgWidth, self.imgHeight, self.note.page, True ) + + def updateParameter( self, parameter, value ): + self.end = self.note.cs.onset + self.note.cs.duration + + if self.oldAmplitude != self.note.cs.amplitude: + r = self.baseColors[0][0] + int(self.baseColors[1][0]*self.note.cs.amplitude) + g = self.baseColors[0][1] + int(self.baseColors[1][1]*self.note.cs.amplitude) + b = self.baseColors[0][2] + int(self.baseColors[1][2]*self.note.cs.amplitude) + self.color = self.colormap.alloc_color( r, g, b, True, True ) # TODO potential memory leak? + self.oldAmplitude = self.note.cs.amplitude + + self.updateTransform() + + def getId( self ): + return self.note.id + + def getStartTick( self ): + return self.note.cs.onset + + def getEndTick( self ): + return self.end + + def testOnset( self, start, stop ): + return self.note.cs.onset >= start and self.note.cs.onset < stop + + def getPitch( self ): + return self.note.cs.pitch + + def updateTransform( self ): + if self.note.page in self.owner.getActivePages(): + if not self.firstTransform: + oldX = self.imgX + oldY = self.imgY + oldEndX = self.imgX + self.imgWidth + dirty = True + else: + dirty = False + + beats = self.noteDB.getPage( self.note.page ).beats + if self.note.cs.onset != self.oldOnset or beats != self.oldBeats: + self.x = self.owner.ticksToPixels( beats, self.note.cs.onset ) + self.x += self.origin[0] + self.imgX = self.x - Config.NOTE_IMAGE_PADDING + self.oldOnset = self.note.cs.onset + if self.end != self.oldEnd or self.note.cs.onset != self.oldOnset or beats != self.oldBeats: + self.width = self.owner.ticksToPixels( beats, self.end ) - self.x + self.origin[0] + self.imgWidth = self.width + Config.NOTE_IMAGE_PADDING_MUL2 + self.oldEnd = self.end + if self.note.cs.pitch != self.oldPitch: + self.y = self.owner.pitchToPixels( self.note.cs.pitch ) + self.origin[1] + self.imgY = self.y - Config.NOTE_IMAGE_PADDING + self.oldPitch = self.note.cs.pitch + self.oldBeats = beats + + if dirty: + if self.firstTransform: + self.owner.invalidate_rect( self.imgX, self.imgY, self.imgWidth, self.imgHeight, self.note.page, True ) + else: + x = min( self.imgX, oldX ) + y = min( self.imgY, oldY ) + endx = max( self.imgX + self.imgWidth, oldEndX ) + endy = max( self.imgY, oldY ) + self.imgHeight + self.owner.invalidate_rect( x, y, endx-x, endy-y, self.note.page, True ) + + self.firstTransform = False + + def updateDragLimits( self, dragLimits, leftBound, rightBound, widthBound, maxRightBound ): + left = leftBound - self.note.cs.onset + right = rightBound - self.note.cs.duration - self.note.cs.onset + up = Config.MAXIMUM_PITCH - self.note.cs.pitch + down = Config.MINIMUM_PITCH - self.note.cs.pitch + short = Config.MINIMUM_NOTE_DURATION - self.note.cs.duration + long = widthBound - self.note.cs.duration - self.note.cs.onset + + if dragLimits[0][0] < left: dragLimits[0][0] = left + if dragLimits[0][1] > right: dragLimits[0][1] = right + if dragLimits[1][0] < down: dragLimits[1][0] = down + if dragLimits[1][1] > up: dragLimits[1][1] = up + if dragLimits[2][0] < short: dragLimits[2][0] = short + if dragLimits[2][1] > long: dragLimits[2][1] = long + + # store the current loc as a reference point + self.baseOnset = self.note.cs.onset + self.basePitch = self.note.cs.pitch + self.baseDuration = self.note.cs.duration + + def playSampleNote( self, full=True ): + secs_per_tick = 0.025 + csnd = new_csound_client() + + if full: + onset = self.note.cs.onset + self.note.cs.onset = 0 + csnd.play( self.note.cs, 0.024) + self.note.cs.onset = onset + else: + (onset,duration) = ( self.note.cs.onset, self.note.cs.duration) + ( self.note.cs.onset, self.note.cs.duration) = (0, 10) + csnd.play( self.note.cs, 0.024) + ( self.note.cs.onset, self.note.cs.duration) = (onset,duration) + + #======================================================= + # Events + + # handleButtonPress returns: + # -2, not a hit but there was X overlap + # -1, event occurs before us so don't bother checking any later notes + # 0, event didn't hit + # 1, event was handled + def handleButtonPress( self, emitter, event ): + eX = event.x - self.x + if eX < 0: + return -1 # event occurs before us, no point in checking further + if eX > self.width: + return 0 # no X overlap + + eY = event.y - self.y + if eY < 0 or eY > self.height: + return -2 # not a hit, but it was in our X range + + if event.button == 3: + print "Show some note parameters!?!" + #self.noteParameters = NoteParametersWindow( self.note, self.getNoteParameters ) + return 1 # handled + + playSample = False + + if event.type == gtk.gdk._2BUTTON_PRESS: # select bar + self.potentialDeselect = False + start = 0 + check = self.note.cs.onset - Config.TICKS_PER_BEAT + while start <= check: start += Config.TICKS_PER_BEAT + stop = start + Config.TICKS_PER_BEAT + check += self.note.cs.duration + while stop < check: stop += Config.TICKS_PER_BEAT + emitter.selectNotesByBar( self.note.track, start, stop ) + elif event.type == gtk.gdk._3BUTTON_PRESS: # select track + self.potentialDeselect = False + emitter.selectNotesByTrack( self.note.track ) + else: + if self.selected: # we already selected, might want to delected + self.potentialDeselect = True + else: + emitter.selectNotes( { self.note.track: [ self ] } ) + playSample = True + + percent = eX/self.width + if percent < 0.3: emitter.setCurrentAction( "note-drag-onset", self ) + elif percent > 0.7: emitter.setCurrentAction( "note-drag-duration", self ) + else: + emitter.setCurrentAction( "note-drag-pitch", self ) + if playSample: self.playSampleNote() + + return 1 + + def handleButtonRelease( self, emitter, event, buttonPressCount ): + + if self.potentialDeselect: + self.potentialDeselect = False + emitter.deselectNotes( { self.note.track: [ self ] } ) + + emitter.doneCurrentAction() + + return True + + def noteDragOnset( self, do, stream ): + self.potentialDeselect = False + if do != self.lastDragO: + self.lastDragO = do + stream += [ self.note.id, self.baseOnset + do ] + + def noteDragPitch( self, dp, stream ): + self.potentialDeselect = False + if dp != self.lastDragP: + self.lastDragP = dp + stream += [ self.note.id, self.basePitch + dp ] + + def noteDragDuration( self, dd, stream ): + self.potentialDeselect = False + if dd != self.lastDragD: + self.lastDragD = dd + stream += [ self.note.id, self.baseDuration + dd ] + + def doneNoteDrag( self, emitter ): + self.baseOnset = self.note.cs.onset + self.basePitch = self.note.cs.pitch + self.baseDuration = self.note.cs.duration + + self.lastDragO = 0 + self.lastDragP = 0 + self.lastDragD = 0 + + def noteDecOnset( self, step, leftBound, stream ): + if self.selected: + if leftBound < self.note.cs.onset: + onset = max( self.note.cs.onset+step, leftBound ) + stream += [ self.note.id, onset ] + return onset + self.note.cs.duration + return self.end + + def noteIncOnset( self, step, rightBound, stream ): + if self.selected: + if rightBound > self.end: + onset = min( self.end+step, rightBound ) - self.note.cs.duration + stream += [ self.note.id, onset ] + return onset + return self.note.cs.onset + + def noteDecPitch( self, step, stream ): + if self.note.cs.pitch > Config.MINIMUM_PITCH: + stream += [ self.note.id, max( self.note.cs.pitch+step, Config.MINIMUM_PITCH ) ] + + def noteIncPitch( self, step, stream ): + if self.note.cs.pitch < Config.MAXIMUM_PITCH: + stream += [ self.note.id, min( self.note.cs.pitch+step, Config.MAXIMUM_PITCH ) ] + + def noteDecDuration( self, step, stream ): + if self.note.cs.duration > Config.MINIMUM_NOTE_DURATION: + stream += [ self.note.id, max( self.note.cs.duration+step, Config.MINIMUM_NOTE_DURATION ) ] + + def noteIncDuration( self, step, rightBound, stream ): + if self.selected: + if self.end < rightBound: + stream += [ self.note.id, min( self.end+step, rightBound ) - self.note.cs.onset ] + + def noteDecVolume( self, step, stream ): + if self.note.cs.amplitude > 0: + stream += [ self.note.id, max( self.note.cs.amplitude+step, 0 ) ] + + def noteIncVolume( self, step, stream ): + if self.note.cs.amplitude < 1: + stream += [ self.note.id, min( self.note.cs.amplitude+step, 1 ) ] + + def handleMarqueeSelect( self, emitter, start, stop ): + intersectionY = [ max(start[1],self.y), min(stop[1],self.y+self.height) ] + if intersectionY[0] > intersectionY[1]: + return False + + intersectionX = [ max(start[0],self.x), min(stop[0],self.x+self.width) ] + if intersectionX[0] > intersectionX[1]: + return False + + return True + + # updateTooltip returns: + # -2, not a hit but there was X overlap + # -1, event occurs before us so don't bother checking any later notes + # 0, event didn't hit + # 1, event was handled + def updateTooltip( self, emitter, event ): + eX = event.x - self.x + if eX < 0: + return -1 # event occurs before us, no point in checking further + if eX > self.width: + return 0 # no X overlap + + eY = event.y - self.y + if eY < 0 or eY > self.height: + return -2 # not a hit, but it was in our X range + + percent = eX/self.width + if percent < 0.3: emitter.setCursor("drag-onset") + elif percent > 0.7: emitter.setCursor("drag-duration") + else: emitter.setCursor("drag-pitch") + + return 1 # we handled it + + #======================================================= + # Selection + + def setSelected( self, state ): + if self.selected != state: + self.selected = state + self.owner.invalidate_rect( self.imgX, self.imgY, self.imgWidth, self.imgHeight, self.note.page ) + return True # state changed + return False # state is the same + + def getSelected( self ): + return self.selected + + #======================================================= + # Draw + + def draw( self, win, gc, startX, stopX ): + if stopX < self.imgX: return False # we don't need to draw and no one after us will draw + if startX > self.imgX + self.imgWidth: return True # we don't need to draw, but maybe a later note does + + gc.foreground = self.color + win.draw_rectangle( gc, True, self.x+1, self.y+1, self.width-2, self.height-2 ) + + if self.selected: img = self.imageSelected + else: img = self.image + win.draw_pixbuf( gc, img, 0, 0, self.imgX, self.imgY, self.imgWidth-Config.NOTE_IMAGE_ENDLENGTH, self.imgHeight, gtk.gdk.RGB_DITHER_NONE ) + win.draw_pixbuf( gc, img, Config.NOTE_IMAGE_TAIL, 0, self.imgX+self.imgWidth-Config.NOTE_IMAGE_ENDLENGTH, self.imgY, Config.NOTE_IMAGE_ENDLENGTH, self.imgHeight, gtk.gdk.RGB_DITHER_NONE ) + + return True # we drew something + diff --git a/Edit/Properties.py b/Edit/Properties.py new file mode 100644 index 0000000..2dc6d84 --- /dev/null +++ b/Edit/Properties.py @@ -0,0 +1,808 @@ +import pygtk +pygtk.require('2.0') +import gtk +from types import * +from math import sqrt +from random import * +from common.Generation.Drunk import * +from common.Generation.GenerationConstants import GenerationConstants +from common.Util.ThemeWidgets import * +from common.Util.NoteDB import PARAMETER +import common.Config as Config +Tooltips = Config.Tooltips() + +class Properties( gtk.VBox ): + def __init__( self, noteDB, doneHandler, popup ): + gtk.VBox.__init__( self ) + self.tooltips = gtk.Tooltips() + self.noteDB = noteDB + #self.doneHandler = doneHandler + self.popup = popup + self.popup.resize( 545, 378 ) + + self.context = "page" + self.notes = {} # notes indexed by page and track + self.setup = False # flag to block note updates durning setup + + self.line = Line(0, 100) + self.drunk = Drunk(0, 100) + self.droneAndJump = DroneAndJump(0, 100) + self.repeter = Repeter(0, 100) + self.loopseg = Loopseg(0, 100) + self.algoTypes = [self.line, self.drunk, self.droneAndJump, self.repeter, self.loopseg] + self.algorithm = self.algoTypes[0] + + #self.set_size_request( 300, 200 ) + + self.filterType = 0 + self.minValue = 0. + self.maxValue = 100. + self.paraValue = 20. + + self.activeWidget = None + + self.pageIds = [] + + self.GUI = {} + self.parametersBox = RoundHBox(fillcolor=Config.INST_BCK_COLOR, bordercolor=Config.PANEL_BCK_COLOR) + #self.parametersBox.set_border_width(1) + self.parametersBox.set_radius(10) + self.pack_start(self.parametersBox) + self.fixed = gtk.Fixed() + self.parametersBox.pack_start( self.fixed ) + + self.controlsBox = gtk.HBox() + + #-- Page Properties ------------------------------------------------ + self.pageBox = RoundHBox(fillcolor=Config.PANEL_COLOR, bordercolor=Config.INST_BCK_COLOR) + self.pageBox.set_size_request( 125, -1 ) + self.pageBox.set_border_width(3) + self.pageBox.set_radius(10) + beatBox = gtk.VBox() + self.beatAdjust = gtk.Adjustment( 4, 2, 12, 1, 1, 0) + self.GUI['beatSlider'] = ImageVScale( Config.IMAGE_ROOT + "/sliderEditVolume.png", self.beatAdjust, 7 ) + self.GUI['beatSlider'].connect("button-release-event", self.handleBeat) + self.GUI['beatSlider'].set_snap( 1 ) + self.GUI['beatSlider'].set_inverted(True) + self.GUI['beatSlider'].set_size_request(50, 200) + beatBox.pack_start( self.GUI['beatSlider'] ) + self.beatLabel = gtk.Image() + self.beatLabel.set_from_file(Config.IMAGE_ROOT + 'volume3.png') + self.beatAdjust.connect("value-changed", self.updateBeatLabel) + self.updateBeatLabel( self.beatAdjust ) + beatBox.pack_start( self.beatLabel ) + self.pageBox.pack_start( beatBox ) + colorBox = gtk.VBox() + self.GUI["color0Button"] = ImageRadioButton( None, Config.IMAGE_ROOT+"pageThumbnailBut0.png", Config.IMAGE_ROOT+"pageThumbnailBut0Down.png", backgroundFill = Config.PANEL_COLOR ) + self.GUI["color0Button"].set_size_request( 80, -1 ) + self.GUI["color0Button"].connect( "clicked", self.handleColor, 0 ) + colorBox.pack_start( self.GUI["color0Button"] ) + self.GUI["color1Button"] = ImageRadioButton( self.GUI["color0Button"], Config.IMAGE_ROOT+"pageThumbnailBut1.png", Config.IMAGE_ROOT+"pageThumbnailBut1Down.png", backgroundFill = Config.PANEL_COLOR ) + self.GUI["color1Button"].connect( "clicked", self.handleColor, 1 ) + colorBox.pack_start( self.GUI["color1Button"] ) + self.GUI["color2Button"] = ImageRadioButton( self.GUI["color0Button"], Config.IMAGE_ROOT+"pageThumbnailBut2.png", Config.IMAGE_ROOT+"pageThumbnailBut2Down.png", backgroundFill = Config.PANEL_COLOR ) + self.GUI["color2Button"].connect( "clicked", self.handleColor, 2 ) + colorBox.pack_start( self.GUI["color2Button"] ) + self.GUI["color3Button"] = ImageRadioButton( self.GUI["color0Button"], Config.IMAGE_ROOT+"pageThumbnailBut3.png", Config.IMAGE_ROOT+"pageThumbnailBut3Down.png", backgroundFill = Config.PANEL_COLOR ) + self.GUI["color3Button"].connect( "clicked", self.handleColor, 3 ) + colorBox.pack_start( self.GUI["color3Button"] ) + self.pageBox.pack_start( colorBox ) + self.pageBox.show_all() + #self.controlsBox.pack_start(self.pageBox) + + #-- Note Properties ------------------------------------------------ + pitchBox = RoundVBox(fillcolor=Config.PANEL_COLOR, bordercolor=Config.INST_BCK_COLOR) + pitchBox.set_border_width(3) + pitchBox.set_radius(10) + self.GUI['pitchUp'] = ImageButton( Config.IMAGE_ROOT+"arrowEditUp.png", Config.IMAGE_ROOT+"arrowEditUpDown.png", Config.IMAGE_ROOT+"arrowEditUpOver.png", backgroundFill = Config.PANEL_COLOR ) + self.GUI['pitchUp'].connect( "clicked", lambda w:self.stepPitch( 1 ) ) + self.GUI['pitchGen'] = ImageToggleButton( Config.IMAGE_ROOT+"diceProp.png", Config.IMAGE_ROOT+"dicePropSel.png", Config.IMAGE_ROOT+"dicePropSel.png", backgroundFill = Config.PANEL_COLOR ) + self.GUI['pitchGen'].connect( "clicked", self.openAlgoBox, 'pitch' ) + pitchBox.pack_start( self.GUI['pitchGen'], False, False, 5 ) + pitchBox.pack_start( self.GUI['pitchUp'] ) + self.pitchIcon = gtk.Image() + self.pitchIcon.set_from_file(Config.IMAGE_ROOT + 'propPitch2.png') + pitchBox.pack_start(self.pitchIcon) + self.GUI['pitchDown'] = ImageButton( Config.IMAGE_ROOT+"arrowEditDown.png", Config.IMAGE_ROOT+"arrowEditDownDown.png", Config.IMAGE_ROOT+"arrowEditDownOver.png", backgroundFill = Config.PANEL_COLOR ) + self.GUI['pitchDown'].connect( "clicked", lambda w:self.stepPitch( -1 ) ) + pitchBox.pack_start( self.GUI['pitchDown'] ) + self.controlsBox.pack_start(pitchBox) + + volumeBox = RoundVBox(fillcolor=Config.PANEL_COLOR, bordercolor=Config.INST_BCK_COLOR) + volumeBox.set_border_width(3) + volumeBox.set_radius(10) + self.GUI['volumeUp'] = ImageButton( Config.IMAGE_ROOT+"arrowEditUp.png", Config.IMAGE_ROOT+"arrowEditUpDown.png", Config.IMAGE_ROOT+"arrowEditUpOver.png", backgroundFill = Config.PANEL_COLOR ) + self.GUI['volumeUp'].connect( "clicked", lambda w:self.stepVolume( 0.1 ) ) + self.GUI['volumeGen'] = ImageToggleButton( Config.IMAGE_ROOT+"diceProp.png", Config.IMAGE_ROOT+"dicePropSel.png", Config.IMAGE_ROOT+"dicePropSel.png", backgroundFill = Config.PANEL_COLOR ) + self.GUI['volumeGen'].connect( "clicked", self.openAlgoBox, 'volume' ) + volumeBox.pack_start( self.GUI['volumeGen'], False, False, 5 ) + volumeBox.pack_start( self.GUI['volumeUp'] ) + self.volumeIcon = gtk.Image() + self.volumeIcon.set_from_file(Config.IMAGE_ROOT + 'volume3.png') + volumeBox.pack_start(self.volumeIcon) + self.GUI['volumeDown'] = ImageButton( Config.IMAGE_ROOT+"arrowEditDown.png", Config.IMAGE_ROOT+"arrowEditDownDown.png", Config.IMAGE_ROOT+"arrowEditDownOver.png", backgroundFill = Config.PANEL_COLOR ) + self.GUI['volumeDown'].connect( "clicked", lambda w:self.stepVolume( -0.1 ) ) + volumeBox.pack_start( self.GUI['volumeDown'] ) + self.controlsBox.pack_start(volumeBox) + + panBox = RoundVBox(fillcolor=Config.PANEL_COLOR, bordercolor=Config.INST_BCK_COLOR) + panBox.set_border_width(3) + panBox.set_radius(10) + self.panAdjust = gtk.Adjustment( 0.5, 0, 1, .1, .1, 0) + self.GUI['panSlider'] = ImageVScale( Config.IMAGE_ROOT + "/sliderEditVolume.png", self.panAdjust, 7 ) + self.panAdjust.connect("value-changed", self.handlePan) + self.GUI['panSlider'].set_snap( 0.1 ) + self.GUI['panSlider'].set_inverted(True) + self.GUI['panSlider'].set_size_request(50, 200) + self.panLabel = gtk.Image() + self.handlePan( self.panAdjust ) + self.GUI['panGen'] = ImageToggleButton( Config.IMAGE_ROOT+"diceProp.png", Config.IMAGE_ROOT+"dicePropSel.png", Config.IMAGE_ROOT+"dicePropSel.png", backgroundFill = Config.PANEL_COLOR ) + self.GUI['panGen'].connect( "clicked", self.openAlgoBox, 'pan' ) + panBox.pack_start(self.GUI['panGen'], True, True, 5) + panBox.pack_start(self.GUI['panSlider'], True, True, 5) + panBox.pack_start(self.panLabel, False, padding=10) + self.controlsBox.pack_start(panBox) + + reverbBox = RoundVBox(fillcolor=Config.PANEL_COLOR, bordercolor=Config.INST_BCK_COLOR) + reverbBox.set_border_width(3) + reverbBox.set_radius(10) + self.reverbAdjust = gtk.Adjustment(0.1, 0, 1, 0.1, 0.1, 0) + self.GUI['reverbSlider'] = ImageVScale( Config.IMAGE_ROOT + "/sliderEditVolume.png", self.reverbAdjust, 7 ) + self.reverbAdjust.connect("value-changed", self.handleReverb) + self.GUI['reverbSlider'].set_snap( 0.1 ) + self.GUI['reverbSlider'].set_inverted(True) + self.GUI['reverbSlider'].set_size_request(50, 200) + self.reverbLabel = gtk.Image() + self.handleReverb( self.reverbAdjust ) + self.GUI['reverbGen'] = ImageToggleButton( Config.IMAGE_ROOT+"diceProp.png", Config.IMAGE_ROOT+"dicePropSel.png", Config.IMAGE_ROOT+"dicePropSel.png", backgroundFill = Config.PANEL_COLOR ) + self.GUI['reverbGen'].connect( "clicked", self.openAlgoBox, 'reverb' ) + reverbBox.pack_start(self.GUI['reverbGen'], True, True, 5) + reverbBox.pack_start(self.GUI['reverbSlider'], True, True, 5) + reverbBox.pack_start(self.reverbLabel, False, padding=10) + self.controlsBox.pack_start(reverbBox) + + attackBox = RoundVBox(fillcolor=Config.PANEL_COLOR, bordercolor=Config.INST_BCK_COLOR) + attackBox.set_border_width(3) + attackBox.set_radius(10) + self.attackAdjust = gtk.Adjustment(0.04, 0.03, 1, .01, .01, 0) + self.GUI['attackSlider'] = ImageVScale( Config.IMAGE_ROOT + "/sliderEditVolume.png", self.attackAdjust, 7 ) + self.attackAdjust.connect("value-changed", self.handleAttack) + self.GUI['attackSlider'].set_snap( 0.01 ) + self.GUI['attackSlider'].set_inverted(True) + self.GUI['attackSlider'].set_size_request(50, 200) + self.attackLabel = gtk.Image() + self.handleAttack( self.attackAdjust ) + self.GUI['attackGen'] = ImageToggleButton( Config.IMAGE_ROOT+"diceProp.png", Config.IMAGE_ROOT+"dicePropSel.png", Config.IMAGE_ROOT+"dicePropSel.png", backgroundFill = Config.PANEL_COLOR ) + self.GUI['attackGen'].connect( "clicked", self.openAlgoBox, 'attack' ) + attackBox.pack_start(self.GUI['attackGen'], True, True, 5) + attackBox.pack_start(self.GUI['attackSlider'], True, True, 5) + attackBox.pack_start(self.attackLabel, False, padding=10) + self.controlsBox.pack_start(attackBox) + + decayBox = RoundVBox(fillcolor=Config.PANEL_COLOR, bordercolor=Config.INST_BCK_COLOR) + decayBox.set_border_width(3) + decayBox.set_radius(10) + self.decayAdjust = gtk.Adjustment(0.31, 0.03, 1, .01, .01, 0) + self.GUI['decaySlider'] = ImageVScale( Config.IMAGE_ROOT + "/sliderEditVolume.png", self.decayAdjust, 7 ) + self.decayAdjust.connect("value-changed", self.handleDecay) + self.GUI['decaySlider'].set_snap( 0.01 ) + self.GUI['decaySlider'].set_inverted(True) + self.GUI['decaySlider'].set_size_request(50, 200) + self.decayLabel = gtk.Image() + self.handleDecay( self.decayAdjust ) + self.GUI['decayGen'] = ImageToggleButton( Config.IMAGE_ROOT+"diceProp.png", Config.IMAGE_ROOT+"dicePropSel.png", Config.IMAGE_ROOT+"dicePropSel.png", backgroundFill = Config.PANEL_COLOR ) + self.GUI['decayGen'].connect( "clicked", self.openAlgoBox, 'decay' ) + decayBox.pack_start(self.GUI['decayGen'], True, True, 5) + decayBox.pack_start(self.GUI['decaySlider'], True, True, 5) + decayBox.pack_start(self.decayLabel, False, padding=10) + self.controlsBox.pack_start(decayBox) + + filterBox = RoundHBox(fillcolor=Config.PANEL_COLOR, bordercolor=Config.INST_BCK_COLOR) + filterBox.set_border_width(3) + filterBox.set_radius(10) + + filterTypeBox = gtk.VBox() + self.GUI['filterTypeLowButton'] = ImageToggleButton(Config.IMAGE_ROOT + 'propLow3.png', Config.IMAGE_ROOT + 'propLow3Sel.png', Config.IMAGE_ROOT + 'propLow3Over.png') + self.GUI['filterTypeLowButton'].connect( "toggled", self.handleFilterType, 1 ) + filterTypeBox.pack_start( self.GUI['filterTypeLowButton'] ) + self.GUI['filterTypeHighButton'] = ImageToggleButton(Config.IMAGE_ROOT + 'propHi3.png', Config.IMAGE_ROOT + 'propHi3Sel.png', Config.IMAGE_ROOT + 'propHi3Over.png') + self.GUI['filterTypeHighButton'].connect( "toggled", self.handleFilterType, 2 ) + filterTypeBox.pack_start( self.GUI['filterTypeHighButton'] ) + self.GUI['filterTypeBandButton'] = gtk.ToggleButton( "B" ) + self.GUI['filterTypeBandButton'] = ImageToggleButton(Config.IMAGE_ROOT + 'propBand3.png', Config.IMAGE_ROOT + 'propBand3Sel.png', Config.IMAGE_ROOT + 'propBand3Over.png') + self.GUI['filterTypeBandButton'].connect( "toggled", self.handleFilterType, 3 ) + filterTypeBox.pack_start( self.GUI['filterTypeBandButton'] ) + filterBox.pack_start( filterTypeBox ) + + self.filterSliderBox = gtk.VBox() + self.filterSliderBox.set_size_request(50, -1) + self.cutoffAdjust = gtk.Adjustment(1000, 100, 7000, 100, 100, 0) + self.GUI['cutoffSlider'] = ImageVScale( Config.IMAGE_ROOT + "/sliderEditVolume.png", self.cutoffAdjust, 7 ) + self.GUI['cutoffSlider'].set_snap(100) + self.cutoffAdjust.connect("value-changed", self.handleFilter) + self.GUI['cutoffSlider'].set_inverted(True) + self.GUI['cutoffSlider'].set_size_request(50, 200) + self.GUI['cutoffGen'] = ImageToggleButton( Config.IMAGE_ROOT+"diceProp.png", Config.IMAGE_ROOT+"dicePropSel.png", Config.IMAGE_ROOT+"dicePropSel.png", backgroundFill = Config.PANEL_COLOR ) + self.GUI['cutoffGen'].connect( "clicked", self.openAlgoBox, 'cutoff' ) + self.filterSliderBox.pack_start(self.GUI['cutoffGen'], True, True, 5) + self.filterSliderBox.pack_start(self.GUI['cutoffSlider'], True, True, 5) + self.filterLabel = gtk.Image() + self.filterLabel.set_from_file(Config.IMAGE_ROOT + 'propFilter1.png') + self.filterSliderBox.pack_start(self.filterLabel, False, padding=10) + + filterBox.pack_start(self.filterSliderBox) + + self.controlsBox.pack_start(filterBox) + + self.algoBox = RoundVBox(fillcolor=Config.PANEL_COLOR, bordercolor=Config.INST_BCK_COLOR) + self.algoBox.set_size_request( -1, 378 ) + self.algoBox.set_border_width(3) + self.algoBox.set_radius(10) + #self.algoBox = gtk.VBox() + + algoUpperBox = gtk.HBox() + + algoRadioButtonBox = gtk.VBox() + algoRadioButtonBox.set_size_request(100, 150) + #algoRadioButtonBox = RoundHBox(fillcolor=Config.PANEL_COLOR, bordercolor=Config.INST_BCK_COLOR) + #algoRadioButtonBox.set_border_width(3) + #algoRadioButtonBox.set_radius(10) + + self.GUI['line'] = ImageRadioButton( None, Config.IMAGE_ROOT + 'propLine.png', Config.IMAGE_ROOT + 'propLineDown.png', Config.IMAGE_ROOT + 'propLineOver.png' ) + self.GUI['line'].connect( "toggled", self.handleAlgo, 0 ) + algoRadioButtonBox.pack_start( self.GUI['line'], False, False, 1 ) + self.GUI['drunk'] = ImageRadioButton( self.GUI['line'], Config.IMAGE_ROOT + 'propDrunk.png', Config.IMAGE_ROOT + 'propDrunkDown.png', Config.IMAGE_ROOT + 'propDrunkOver.png' ) + self.GUI['drunk'].connect( "toggled", self.handleAlgo, 1 ) + algoRadioButtonBox.pack_start( self.GUI['drunk'], False, False, 1 ) + self.GUI['droneJump'] = ImageRadioButton( self.GUI['line'], Config.IMAGE_ROOT + 'propDroneJump.png', Config.IMAGE_ROOT + 'propDroneJumpDown.png', Config.IMAGE_ROOT + 'propDroneJumpOver.png' ) + self.GUI['droneJump'].connect( "toggled", self.handleAlgo, 2 ) + algoRadioButtonBox.pack_start( self.GUI['droneJump'], False, False, 1 ) + self.GUI['repeater'] = ImageRadioButton( self.GUI['line'], Config.IMAGE_ROOT + 'propRepeater.png', Config.IMAGE_ROOT + 'propRepeaterDown.png', Config.IMAGE_ROOT + 'propRepeaterOver.png' ) + self.GUI['repeater'].connect( "toggled", self.handleAlgo, 3 ) + algoRadioButtonBox.pack_start( self.GUI['repeater'], False, False, 1 ) + self.GUI['loopseg'] = ImageRadioButton( self.GUI['line'], Config.IMAGE_ROOT + 'propLoopseg.png', Config.IMAGE_ROOT + 'propLoopsegDown.png', Config.IMAGE_ROOT + 'propLoopsegOver.png' ) + self.GUI['loopseg'].connect( "toggled", self.handleAlgo, 4 ) + algoRadioButtonBox.pack_start( self.GUI['loopseg'], False, False, 1 ) + + algoUpperBox.pack_start(algoRadioButtonBox) + + algoSlidersBox = gtk.HBox() + algoSlidersBox.set_size_request(150, 320) + #algoSlidersBox = RoundHBox(fillcolor=Config.PANEL_COLOR, bordercolor=Config.INST_BCK_COLOR) + #algoSlidersBox.set_border_width(3) + #algoSlidersBox.set_radius(10) + minBox = gtk.VBox() + self.minAdjust = gtk.Adjustment(0, 0, 100, 1, 1, 0) + self.GUI['minSlider'] = ImageVScale( Config.IMAGE_ROOT + "/sliderEditVolume.png", self.minAdjust, 7 ) + self.GUI['minSlider'].set_snap(1) + self.minAdjust.connect("value-changed", self.handleMin) + self.GUI['minSlider'].set_inverted(True) + self.GUI['minSlider'].set_size_request(50, 200) + minBox.pack_start(self.GUI['minSlider'], True, True, 5) + algoSlidersBox.pack_start(minBox) + + maxBox = gtk.VBox() + self.maxAdjust = gtk.Adjustment(100, 0, 100, 1, 1, 0) + self.GUI['maxSlider'] = ImageVScale( Config.IMAGE_ROOT + "/sliderEditVolume.png", self.maxAdjust, 7 ) + self.GUI['maxSlider'].set_snap(1) + self.maxAdjust.connect("value-changed", self.handleMax) + self.GUI['maxSlider'].set_inverted(True) + self.GUI['maxSlider'].set_size_request(50, 200) + maxBox.pack_start(self.GUI['maxSlider'], True, True, 5) + algoSlidersBox.pack_start(maxBox) + + paraBox = gtk.VBox() + self.paraAdjust = gtk.Adjustment(20, 0, 100, 1, 1, 0) + self.GUI['paraSlider'] = ImageVScale( Config.IMAGE_ROOT + "/sliderEditVolume.png", self.paraAdjust, 7 ) + self.GUI['paraSlider'].set_snap(1) + self.paraAdjust.connect("value-changed", self.handlePara) + self.GUI['paraSlider'].set_inverted(True) + self.GUI['paraSlider'].set_size_request(50, 200) + paraBox.pack_start(self.GUI['paraSlider'], True, True, 5) + algoSlidersBox.pack_start(paraBox) + + algoUpperBox.pack_start(algoSlidersBox) + + self.algoBox.pack_start(algoUpperBox) + + #transButtonBox = RoundHBox(fillcolor=Config.PANEL_COLOR, bordercolor=Config.INST_BCK_COLOR) + #transButtonBox.set_border_width(3) + #transButtonBox.set_radius(10) + transButtonBox = gtk.HBox() + transButtonBox.set_size_request(150, 50) + + # create cancel/check button + self.GUI["checkButton"] = ImageButton(Config.IMAGE_ROOT + 'check.png', backgroundFill=Config.PANEL_COLOR ) + self.GUI["checkButton"].connect("clicked", self.apply) + + self.GUI["cancelButton"] = ImageButton(Config.IMAGE_ROOT + 'closeA.png', backgroundFill=Config.PANEL_COLOR ) + self.GUI["cancelButton"].connect("clicked", self.cancel) + + transButtonBox.pack_end(self.GUI["checkButton"], False, False, 10) + transButtonBox.pack_end(self.GUI["cancelButton"], False, False) + self.algoBox.pack_start(transButtonBox) + + self.fixed.put( self.controlsBox, 0, 0 ) + self.algoBox.show_all() + + # set tooltips + for key in self.GUI: + if Tooltips.PROP.has_key(key): + self.tooltips.set_tip(self.GUI[key],Tooltips.PROP[key]) + self.tooltips.set_tip(self.GUI['paraSlider'], 'Random') + + + self.show_all() + + def openAlgoBox( self, widget, data=None ): + if widget.get_active() == True: + self.property = data + if self.activeWidget: + self.activeWidget.set_active(False) + self.activeWidget = widget + if self.context == "page": + if self.algoBox.parent == None: self.fixed.put( self.algoBox, 671, 0 ) + else: self.fixed.move( self.algoBox, 671, 0 ) + self.popup.resize( 927, 378 ) + else: + self.popup.resize( 801, 378 ) + if self.algoBox.parent == None: self.fixed.put( self.algoBox, 545, 0 ) + else: self.fixed.move( self.algoBox, 545, 0 ) + else: + self.property = None + self.activeWidget = None + if self.algoBox.parent != None: + if self.context == "page": self.popup.resize( 671, 378 ) + else: self.popup.resize( 545, 378 ) + self.fixed.remove( self.algoBox ) + + def setContext( self, context, scale, pageIds = None, trackIds = None, notes = {} ): + self.context = context + self.scale = GenerationConstants.SCALES[scale] + self.notes = {} + self.pageIds = pageIds + self.trackIds = trackIds + + try: + self.activeWidget.set_active(False) + self.activeWidget = None + except: + self.activeWidget = None + + if context == "page": + if self.pageBox.parent == None: + self.controlsBox.pack_start( self.pageBox ) + self.controlsBox.reorder_child( self.pageBox, 0 ) + self.controlsBox.set_size_request( 671, 378 ) + self.popup.resize( 671, 378 ) + self.trackIds = [0,1,2,3,4] + for p in pageIds: + self.notes[p] = {} + for t in range(Config.NUMBER_OF_TRACKS): + self.notes[p][t] = self.noteDB.getNotesByTrack( p, t ) + page = self.noteDB.getPage(pageIds[0]) + self.beatAdjust.set_value(page.beats) + btn = "color%dButton" % page.color + self.GUI[btn].set_active(True) + elif context == "track": + if self.pageBox.parent != None: + self.controlsBox.remove( self.pageBox ) + self.controlsBox.set_size_request( 545, 378 ) + self.popup.resize( 545, 378 ) + for p in pageIds: + self.notes[p] = {} + for t in trackIds: + self.notes[p][t] = self.noteDB.getNotesByTrack( p, t ) + else: + if self.pageBox.parent != None: + self.controlsBox.remove( self.pageBox ) + self.controlsBox.set_size_request( 545, 378 ) + self.popup.resize( 545, 378 ) + self.notes = notes + self.pageIds = self.notes.keys() + self.trackIds = self.notes[self.pageIds[0]].keys() + + for p in self.notes: + for t in self.notes[p]: + if len(self.notes[p][t]): + # initialize values from first note + self.setup = True + n = self.notes[p][t][0] + self.panAdjust.set_value( n.cs.pan ) + self.reverbAdjust.set_value( n.cs.reverbSend ) + self.attackAdjust.set_value( n.cs.attack ) + self.decayAdjust.set_value( n.cs.decay ) + if n.cs.filterType == 0: + self.GUI['filterTypeLowButton'].set_active(False) + self.GUI['filterTypeHighButton'].set_active(False) + self.GUI['filterTypeBandButton'].set_active(False) + self.filterLabel.hide() + self.GUI['cutoffSlider'].hide() + self.GUI['cutoffGen'].hide() + else: + if n.cs.filterType == 1: + self.GUI['filterTypeLowButton'].set_active(True) + if n.cs.filterType == 2: + self.GUI['filterTypeHighButton'].set_active(True) + if n.cs.filterType == 3: + self.GUI['filterTypeBandButton'].set_active(True) + self.filterLabel.show() + self.GUI['cutoffSlider'].show() + self.GUI['cutoffGen'].show() + self.filterType = n.cs.filterType + self.cutoffAdjust.set_value( n.cs.filterCutoff ) + self.setup = False + return + + def handleColor( self, widget, index ): + stream = [] + for page in self.pageIds: + stream += [ page, index ] + if len(stream): + self.noteDB.updatePages( [ PARAMETER.PAGE_COLOR, len(stream)//2 ] + stream ) + + def updateBeatLabel( self, adjust ): + beats = int(adjust.value) + self.beatLabel.set_from_file(Config.IMAGE_ROOT + 'propBeats' + str(beats) + '.png') + + def handleBeat( self, widget, signal_id ): + beats = int(widget.get_adjustment().value) + stream = [] + for page in self.pageIds: + stream += [ page, beats ] + if len(stream): + self.noteDB.updatePages( [ PARAMETER.PAGE_BEATS, len(stream)//2 ] + stream ) + + + def stepPitch( self, step ): + stream = [] + for p in self.notes: + for t in self.notes[p]: + substream = [] + if step > 0: + if t != Config.NUMBER_OF_TRACKS-1: # regular note + for n in self.notes[p][t]: + if n.cs.pitch != Config.MAXIMUM_PITCH: + substream += [ n.id, min( Config.MAXIMUM_PITCH, n.cs.pitch + step ) ] + else: # drum note + for n in self.notes[p][t]: + if n.cs.pitch != Config.MAXIMUM_PITCH_DRUM: + substream += [ n.id, min( Config.MAXIMUM_PITCH_DRUM, n.cs.pitch + step*Config.PITCH_STEP_DRUM ) ] + else: + if t != Config.NUMBER_OF_TRACKS-1: # regular note + for n in self.notes[p][t]: + if n.cs.pitch != Config.MINIMUM_PITCH: + substream += [ n.id, max( Config.MINIMUM_PITCH, n.cs.pitch + step ) ] + else: # drum note + for n in self.notes[p][t]: + if n.cs.pitch != Config.MINIMUM_PITCH_DRUM: + substream += [ n.id, max( Config.MINIMUM_PITCH_DRUM, n.cs.pitch + step*Config.PITCH_STEP_DRUM ) ] + if len(substream): + stream += [ p, t, PARAMETER.PITCH, len(substream)//2 ] + substream + if len(stream): + self.noteDB.updateNotes( stream + [-1] ) + + def algoPitch( self, list, algorithm ): + maxValue = max(list[0], list[1]) + scaleLength = len(self.scale)-1 + stream = [] + for t in range(len(self.trackIds)): + trackLength = 0 + for p in range(len(self.pageIds)): + trackLength += len(self.notes[self.pageIds[p]][self.trackIds[t]]) + algorithm.__init__(list[0], list[1], trackLength) + for p in range(len(self.pageIds)): + substream = [] + if self.trackIds[t] != Config.NUMBER_OF_TRACKS-1: + for n in self.notes[self.pageIds[p]][self.trackIds[t]]: + val = algorithm.getNextValue(list[2], maxValue) + substream += [ n.id, self.scale[int(val*0.01*scaleLength)]+36 ] + if len(substream): + stream += [ self.pageIds[p], self.trackIds[t], PARAMETER.PITCH, len(substream)//2 ] + substream + else: + for n in self.notes[self.pageIds[p]][self.trackIds[t]]: + val = algorithm.getNextValue(list[2], maxValue) + val = int((val*0.12)*2+24) + if val in GenerationConstants.DRUMPITCH.keys(): + val = GenerationConstants.DRUMPITCH[val] + substream += [ n.id, val ] + if len(substream): + stream += [ self.pageIds[p], self.trackIds[t], PARAMETER.PITCH, len(substream)//2 ] + substream + if len(stream): + self.noteDB.updateNotes( stream + [-1] ) + + def stepVolume( self, step ): + stream = [] + for p in self.notes: + for t in self.notes[p]: + substream = [] + if step > 0: + for n in self.notes[p][t]: + if n.cs.amplitude != Config.MAXIMUM_AMPLITUDE: + substream += [ n.id, min( Config.MAXIMUM_AMPLITUDE, n.cs.amplitude + step ) ] + else: + for n in self.notes[p][t]: + if n.cs.amplitude != Config.MINIMUM_AMPLITUDE: + substream += [ n.id, max( Config.MINIMUM_AMPLITUDE, n.cs.amplitude + step ) ] + if len(substream): + stream += [ p, t, PARAMETER.AMPLITUDE, len(substream)//2 ] + substream + if len(stream): + self.noteDB.updateNotes( stream + [-1] ) + + def algoVolume( self, list, algorithm ): + maxValue = max(list[0], list[1]) + stream = [] + for t in range(len(self.trackIds)): + trackLength = 0 + for p in range(len(self.pageIds)): + trackLength += len(self.notes[self.pageIds[p]][self.trackIds[t]]) + algorithm.__init__(list[0], list[1], trackLength) + for p in range(len(self.pageIds)): + substream = [] + for n in self.notes[self.pageIds[p]][self.trackIds[t]]: + val = algorithm.getNextValue(list[2], maxValue) + substream += [ n.id, min( Config.MAXIMUM_AMPLITUDE, val*0.01 ) ] + if len(substream): + stream += [ self.pageIds[p], self.trackIds[t], PARAMETER.AMPLITUDE, len(substream)//2 ] + substream + if len(stream): + self.noteDB.updateNotes( stream + [-1] ) + + def handlePan( self, adjust ): + img = min( 4, int(adjust.value * 5) ) + self.panLabel.set_from_file(Config.IMAGE_ROOT + 'propPan' + str(img) + '.png') + if not self.setup: + stream = [] + for p in self.notes: + for t in self.notes[p]: + if len(self.notes[p][t]): + stream += [ p, t, PARAMETER.PAN, len(self.notes[p][t]) ] + for n in self.notes[p][t]: + stream += [ n.id, adjust.value ] + if len(stream): + self.noteDB.updateNotes( stream + [-1] ) + + def algoPan( self, list, algorithm ): + maxValue = max(list[0], list[1]) + stream = [] + for t in range(len(self.trackIds)): + trackLength = 0 + for p in range(len(self.pageIds)): + trackLength += len(self.notes[self.pageIds[p]][self.trackIds[t]]) + algorithm.__init__(list[0], list[1], trackLength) + for p in range(len(self.pageIds)): + substream = [] + for n in self.notes[self.pageIds[p]][self.trackIds[t]]: + val = algorithm.getNextValue(list[2], maxValue) + substream += [ n.id, val*0.01 ] + if len(substream): + stream += [ self.pageIds[p], self.trackIds[t], PARAMETER.PAN, len(substream)//2 ] + substream + if len(stream): + self.noteDB.updateNotes( stream + [-1] ) + + def handleReverb( self, adjust ): + img = min( 5, int(adjust.value * 6) ) + self.reverbLabel.set_from_file(Config.IMAGE_ROOT + 'propReverb' + str(img) + '.png') + if not self.setup: + stream = [] + for p in self.notes: + for t in self.notes[p]: + if len(self.notes[p][t]): + stream += [ p, t, PARAMETER.REVERB, len(self.notes[p][t]) ] + for n in self.notes[p][t]: + stream += [ n.id, adjust.value ] + if len(stream): + self.noteDB.updateNotes( stream + [-1] ) + + def algoReverb( self, list, algorithm ): + maxValue = max(list[0], list[1]) + stream = [] + for t in range(len(self.trackIds)): + trackLength = 0 + for p in range(len(self.pageIds)): + trackLength += len(self.notes[self.pageIds[p]][self.trackIds[t]]) + algorithm.__init__(list[0], list[1], trackLength) + for p in range(len(self.pageIds)): + substream = [] + for n in self.notes[self.pageIds[p]][self.trackIds[t]]: + val = algorithm.getNextValue(list[2], maxValue) + substream += [ n.id, val*0.02 ] + if len(substream): + stream += [ self.pageIds[p], self.trackIds[t], PARAMETER.REVERB, len(substream)//2 ] + substream + if len(stream): + self.noteDB.updateNotes( stream + [-1] ) + + def handleAttack( self, adjust ): + val = adjust.value #*adjust.value + img = min( 4, int(val * 4) ) + self.attackLabel.set_from_file(Config.IMAGE_ROOT + 'propAtt' + str(img) + '.png') + if not self.setup: + stream = [] + for p in self.notes: + for t in self.notes[p]: + if len(self.notes[p][t]): + stream += [ p, t, PARAMETER.ATTACK, len(self.notes[p][t]) ] + for n in self.notes[p][t]: + stream += [ n.id, adjust.value ] + if len(stream): + self.noteDB.updateNotes( stream + [-1] ) + + def algoAttack( self, list, algorithm ): + maxValue = max(list[0], list[1]) + stream = [] + for t in range(len(self.trackIds)): + trackLength = 0 + for p in range(len(self.pageIds)): + trackLength += len(self.notes[self.pageIds[p]][self.trackIds[t]]) + algorithm.__init__(list[0], list[1], trackLength) + for p in range(len(self.pageIds)): + substream = [] + for n in self.notes[self.pageIds[p]][self.trackIds[t]]: + val = algorithm.getNextValue(list[2], maxValue) + substream += [ n.id, val*0.01 ] + if len(substream): + stream += [ self.pageIds[p], self.trackIds[t], PARAMETER.ATTACK, len(substream)//2 ] + substream + if len(stream): + self.noteDB.updateNotes( stream + [-1] ) + + def handleDecay( self, adjust ): + val = adjust.value #*adjust.value + img = min( 4, int(val * 4) ) + self.decayLabel.set_from_file(Config.IMAGE_ROOT + 'propDec' + str(img) + '.png') + if not self.setup: + stream = [] + for p in self.notes: + for t in self.notes[p]: + if len(self.notes[p][t]): + stream += [ p, t, PARAMETER.DECAY, len(self.notes[p][t]) ] + for n in self.notes[p][t]: + stream += [ n.id, adjust.value ] + if len(stream): + self.noteDB.updateNotes( stream + [-1] ) + + def algoDecay( self, list, algorithm ): + maxValue = max(list[0], list[1]) + stream = [] + for t in range(len(self.trackIds)): + trackLength = 0 + for p in range(len(self.pageIds)): + trackLength += len(self.notes[self.pageIds[p]][self.trackIds[t]]) + algorithm.__init__(list[0], list[1], trackLength) + for p in range(len(self.pageIds)): + substream = [] + for n in self.notes[self.pageIds[p]][self.trackIds[t]]: + val = algorithm.getNextValue(list[2], maxValue) + substream += [ n.id, val*0.01 ] + if len(substream): + stream += [ self.pageIds[p], self.trackIds[t], PARAMETER.DECAY, len(substream)//2 ] + substream + if len(stream): + self.noteDB.updateNotes( stream + [-1] ) + + def handleFilterType( self, widget, type ): + + if widget.get_active(): + if self.filterType == 0: + self.filterLabel.show() + self.GUI['cutoffSlider'].show() + self.GUI['cutoffGen'].show() + + self.filterType = type + self.updateFilterLabel() + + if widget != self.GUI['filterTypeLowButton'] and self.GUI['filterTypeLowButton'].get_active(): + self.GUI['filterTypeLowButton'].set_active( False ) + if widget != self.GUI['filterTypeBandButton'] and self.GUI['filterTypeBandButton'].get_active(): + self.GUI['filterTypeBandButton'].set_active( False ) + if widget != self.GUI['filterTypeHighButton'] and self.GUI['filterTypeHighButton'].get_active(): + self.GUI['filterTypeHighButton'].set_active( False ) + if not self.setup: + typestream = [] + cutoffstream = [] + cutoff = self.cutoffAdjust.value + for p in self.notes: + for t in self.notes[p]: + if len(self.notes[p][t]): + substream = [] + typestream += [ p, t, PARAMETER.FILTERTYPE, len(self.notes[p][t]) ] + for n in self.notes[p][t]: + typestream += [ n.id, type ] + if n.cs.filterCutoff != cutoff: + substream += [ n.id, cutoff ] + if len(substream): + cutoffstream += [ p, t, PARAMETER.FILTERCUTOFF, len(substream)//2 ] + substream + if len(typestream): + self.noteDB.updateNotes( typestream + [-1] ) + if len(cutoffstream): + self.noteDB.updateNotes( cutoffstream + [-1] ) + + elif type == self.filterType: + self.filterType = 0 + self.filterLabel.hide() + self.GUI['cutoffSlider'].hide() + self.GUI['cutoffGen'].hide() + if not self.setup: + typestream = [] + for p in self.notes: + for t in self.notes[p]: + if len(self.notes[p][t]): + typestream += [ p, t, PARAMETER.FILTERTYPE, len(self.notes[p][t]) ] + for n in self.notes[p][t]: + typestream += [ n.id, 0 ] + if len(typestream): + self.noteDB.updateNotes( typestream + [-1] ) + + def handleFilter( self, adjust ): + stream = [] + for p in self.notes: + for t in self.notes[p]: + if len(self.notes[p][t]): + stream += [ p, t, PARAMETER.FILTERCUTOFF, len(self.notes[p][t]) ] + for n in self.notes[p][t]: + stream += [ n.id, adjust.value ] + if len(stream): + self.noteDB.updateNotes( stream + [-1] ) + + def algoCutoff( self, list, algorithm ): + maxValue = max(list[0], list[1]) + stream = [] + for t in range(len(self.trackIds)): + trackLength = 0 + for p in range(len(self.pageIds)): + trackLength += len(self.notes[self.pageIds[p]][self.trackIds[t]]) + algorithm.__init__(list[0], list[1], trackLength) + for p in range(len(self.pageIds)): + substream = [] + for n in self.notes[self.pageIds[p]][self.trackIds[t]]: + val = algorithm.getNextValue(list[2], maxValue) + substream += [ n.id, val*70+100 ] + if len(substream): + stream += [ self.pageIds[p], self.trackIds[t], PARAMETER.FILTERCUTOFF, len(substream)//2 ] + substream + if len(stream): + self.noteDB.updateNotes( stream + [-1] ) + + self.updateFilterLabel() + + def handleAlgo( self, widget, data ): + self.algorithm = self.algoTypes[data] + paraTooltips = ['Random', 'Maximum step', 'Maximum step', 'Maximum step', 'Maximum step'] + self.tooltips.set_tip(self.GUI['paraSlider'], paraTooltips[data]) + + def handleMin( self, adjust ): + self.minValue = adjust.value + + def handleMax( self, adjust ): + self.maxValue = adjust.value + + def handlePara( self, adjust ): + self.paraValue = adjust.value + + def apply( self, widget, data=None ): + valList = [self.minValue, self.maxValue, self.paraValue] + if self.property == 'pitch': + self.algoPitch(valList, self.algorithm) + elif self.property == 'volume': + self.algoVolume(valList, self.algorithm) + elif self.property == 'pan': + self.algoPan(valList, self.algorithm) + elif self.property == 'reverb': + self.algoReverb(valList, self.algorithm) + elif self.property == 'attack': + self.algoAttack(valList, self.algorithm) + elif self.property == 'decay': + self.algoDecay(valList, self.algorithm) + elif self.property == 'cutoff': + self.algoCutoff(valList, self.algorithm) + self.cancel(self.activeWidget) + + def cancel( self, widget, data=None ): + self.activeWidget.set_active(False) + + def updateFilterLabel( self ): + val = (self.cutoffAdjust.value-self.cutoffAdjust.lower)/(self.cutoffAdjust.upper-self.cutoffAdjust.lower) + img = min( 5, int(val * 6) ) + self.filterLabel.set_from_file(Config.IMAGE_ROOT + 'propFilter%d.%d' % (self.filterType, img) + '.png') + + + + + + diff --git a/Edit/TrackInterface.py b/Edit/TrackInterface.py new file mode 100644 index 0000000..ad740ee --- /dev/null +++ b/Edit/TrackInterface.py @@ -0,0 +1,1374 @@ +import pygtk +pygtk.require( '2.0' ) +import gtk + +import gobject + +from math import floor +import time + +import common.Config as Config +from Edit.NoteInterface import NoteInterface +from Edit.HitInterface import HitInterface +from Edit.MainWindow import CONTEXT + +from common.Util.NoteDB import PARAMETER +from common.Util.CSoundNote import CSoundNote +from common.Generation.GenerationConstants import GenerationConstants +from common.Util.Profiler import TP + +class SELECTNOTES: + ALL = -1 + NONE = 0 + ADD = 1 + REMOVE = 2 + FLIP = 3 + EXCLUSIVE = 4 + +class INTERFACEMODE: + DEFAULT = 0 + DRAW = 1 + PASTE_NOTES = 2 + PASTE_TRACKS = 3 + PAINT = 4 + +class TrackInterfaceParasite: + def __init__( self, noteDB, owner, note ): + if note.track == Config.NUMBER_OF_TRACKS-1: # drum track + self.parasite = HitInterface( noteDB, owner, note ) + else: + self.parasite = NoteInterface( noteDB, owner, note ) + + def attach( self ): + return self.parasite + +class TrackInterface( gtk.EventBox ): + + def __init__( self, noteDB, owner, getScaleFunction, width ): + gtk.EventBox.__init__( self ) + + self.noteDB = noteDB + self.owner = owner + self.getScale = getScaleFunction + + self.drawingArea = gtk.DrawingArea() + self.drawingAreaDirty = False # are we waiting to draw? + self.add( self.drawingArea ) + self.dirtyRectToAdd = gtk.gdk.Rectangle() # used by the invalidate_rect function + + self.fullWidth = 1 # store the maximum allowed width + self.width = 1 + self.height = 1 + + self.interfaceMode = INTERFACEMODE.DEFAULT + + self.curPage = -1 # this isn't a real page at all! + self.curBeats = 4 + self.painting = False + self.pointerGrid = 1 + self.drawGrid = Config.DEFAULT_GRID + self.paintGrid = Config.DEFAULT_GRID + self.paintNoteDur = Config.DEFAULT_GRID + + self.selectedNotes = [ [] for i in range(Config.NUMBER_OF_TRACKS) ] + + self.curAction = False # stores the current mouse action + self.curActionObject = False # stores the object that in handling the action + + self.lastDO = self.lastDP = self.lastDrumDP = self.lastDD = None + + self.clickButton = 0 # used in release and motion events to make sure we where actually the widget originally clicked. (hack for popup windows) + self.buttonPressCount = 1 # used on release events to indicate double/triple releases + self.clickLoc = [0,0] # location of the last click + self.marqueeLoc = False # current drag location of the marquee + self.marqueeRect = [[0,0],[0,0]] + + self.pasteTick = -1 + self.pasteTrack = -1 + self.pasteRect = False + + self.playheadT = 0 + self.playheadX = Config.TRACK_SPACING_DIV2 + + self.cursor = { \ + "default": None, \ + "drag-onset": gtk.gdk.Cursor(gtk.gdk.SB_RIGHT_ARROW), \ + "drag-pitch": gtk.gdk.Cursor(gtk.gdk.BOTTOM_SIDE), \ + "drag-duration": gtk.gdk.Cursor(gtk.gdk.RIGHT_SIDE), \ + "drag-playhead": gtk.gdk.Cursor(gtk.gdk.SB_H_DOUBLE_ARROW), \ + "pencil": gtk.gdk.Cursor(gtk.gdk.PENCIL), \ + "paste": gtk.gdk.Cursor(gtk.gdk.CENTER_PTR), \ + "error": None } + + self.add_events(gtk.gdk.POINTER_MOTION_MASK|gtk.gdk.POINTER_MOTION_HINT_MASK) + + self.connect( "size-allocate", self.size_allocate ) + + self.drawingArea.connect( "expose-event", self.expose ) + self.connect( "button-press-event", self.handleButtonPress ) + self.connect( "button-release-event", self.handleButtonRelease ) + self.connect( "motion-notify-event", self.handleMotion ) + + # prepare drawing stuff + hexToInt = { "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 } + self.trackColors = [] + for i in Config.TRACK_COLORS: + low = ( 256*(hexToInt[i[0][1]]*16+hexToInt[i[0][2]]), 256*(hexToInt[i[0][3]]*16+hexToInt[i[0][4]]), 256*(hexToInt[i[0][5]]*16+hexToInt[i[0][6]]) ) + high = ( 256*(hexToInt[i[1][1]]*16+hexToInt[i[1][2]]), 256*(hexToInt[i[1][3]]*16+hexToInt[i[1][4]]), 256*(hexToInt[i[1][5]]*16+hexToInt[i[1][6]]) ) + delta = ( high[0]-low[0], high[1]-low[1], high[2]-low[2] ) + self.trackColors.append( (low, delta) ) + + colormap = self.drawingArea.get_colormap() + self.beatColor = colormap.alloc_color( Config.BEAT_COLOR, True, True ) + self.playheadColor = colormap.alloc_color( Config.PLAYHEAD_COLOR, True, True ) + self.marqueeColor = colormap.alloc_color( Config.MARQUEE_COLOR, True, True ) + + self.image = {} + win = gtk.gdk.get_default_root_window() + self.gc = gtk.gdk.GC( win ) + + def prepareDrawable( name, width = -1 ): + pix = gtk.gdk.pixbuf_new_from_file( Config.IMAGE_ROOT+name+".png" ) + if width != -1: + pix = pix.scale_simple(width, pix.get_height(), gtk.gdk.INTERP_BILINEAR) + self.image[name] = gtk.gdk.Pixmap( win, pix.get_width(), pix.get_height() ) + self.image[name].draw_pixbuf( self.gc, pix, 0, 0, 0, 0, pix.get_width(), pix.get_height(), gtk.gdk.RGB_DITHER_NONE ) + def preparePixbuf( name ): + self.image[name] = gtk.gdk.pixbuf_new_from_file( Config.IMAGE_ROOT+name+".png" ) + + prepareDrawable( "trackBG", width ) + prepareDrawable( "trackBGSelected", width ) + prepareDrawable( "trackBGDrum", width ) + prepareDrawable( "trackBGDrumSelected", width ) + preparePixbuf( "note" ) + preparePixbuf( "noteSelected" ) + preparePixbuf( "hit" ) + preparePixbuf( "hitSelected" ) + + # define dimensions + self.width = self.trackFullWidth = width + self.trackWidth = self.width - Config.TRACK_SPACING + self.trackFullHeight = self.image["trackBG"].get_size()[1] + self.trackHeight = self.trackFullHeight - Config.TRACK_SPACING + self.trackFullHeightDrum = self.image["trackBGDrum"].get_size()[1] + self.trackHeightDrum = self.trackFullHeightDrum - Config.TRACK_SPACING + self.height = self.trackHeight*(Config.NUMBER_OF_TRACKS-1) + self.trackHeightDrum + Config.TRACK_SPACING*Config.NUMBER_OF_TRACKS + self.trackLimits = [] + self.trackRect = [] + self.drumIndex = Config.NUMBER_OF_TRACKS-1 + for i in range(self.drumIndex): + start = i*(self.trackFullHeight) + self.trackLimits.append( (start,start+self.trackFullHeight) ) + self.trackRect.append( gtk.gdk.Rectangle(Config.TRACK_SPACING_DIV2,start+Config.TRACK_SPACING_DIV2, self.trackWidth, self.trackHeight ) ) + self.trackLimits.append( ( self.height - self.trackFullHeightDrum, self.height ) ) + self.trackRect.append( gtk.gdk.Rectangle( Config.TRACK_SPACING_DIV2, self.height - self.trackFullHeightDrum + Config.TRACK_SPACING_DIV2, self.trackWidth, self.trackHeightDrum ) ) + + self.pitchPerPixel = float(Config.NUMBER_OF_POSSIBLE_PITCHES-1) / (self.trackHeight - Config.NOTE_HEIGHT) + self.pixelsPerPitch = float(self.trackHeight-Config.NOTE_HEIGHT)/(Config.MAXIMUM_PITCH - Config.MINIMUM_PITCH) + self.pitchPerPixelDrum = float(Config.NUMBER_OF_POSSIBLE_PITCHES_DRUM-1)*Config.PITCH_STEP_DRUM / (self.trackHeightDrum - Config.HIT_HEIGHT) + self.pixelsPerPitchDrum = float(self.trackHeightDrum-Config.HIT_HEIGHT)/(Config.MAXIMUM_PITCH_DRUM - Config.MINIMUM_PITCH_DRUM ) + + self.pixelsPerTick = [0] + [ self.trackWidth/float(i*Config.TICKS_PER_BEAT) for i in range(1,Config.MAXIMUM_BEATS+1) ] + + self.ticksPerPixel = [0] + [ 1.0/self.pixelsPerTick[i] for i in range(1,Config.MAXIMUM_BEATS+1) ] + + self.beatSpacing = [[0]] + for i in range(1,Config.MAXIMUM_BEATS+1): + self.beatSpacing.append( [ self.ticksToPixels( i, Config.TICKS_PER_BEAT*j ) for j in range(i) ] ) + + # screen buffers + self.screenBuf = [ gtk.gdk.Pixmap( win, self.width, self.height ), \ + gtk.gdk.Pixmap( win, self.width, self.height ) ] + self.screenBufPage = [ -1, -1 ] + self.screenBufBeats = [ -1, -1 ] + self.screenBufDirtyRect = [ gtk.gdk.Rectangle(), gtk.gdk.Rectangle() ] + self.screenBufDirty = [ False, False ] + self.screenBufResume = [ [0,0], [0,0] ] # allows for stopping and restarting in the middle of a draw + self.curScreen = 0 + self.preScreen = 1 + self.predrawTimeout = False + + #-- private -------------------------------------------- + + def _updateClipboardArea( self ): + self.clipboardArea = self.owner.getClipboardArea( self.curPage ) + self.clipboardTrackTop = 0 + for t in range(self.drumIndex): + if self.clipboardArea["tracks"][t]: break + self.clipboardTrackTop += 1 + self.clipboardDrumTrack = self.clipboardArea["tracks"][self.drumIndex] + + #======================================================= + # NoteDB notifications + + def notifyPageAdd( self, id, at ): + return + + def notifyPageDelete( self, which, safe ): + if self.screenBufPage[self.preScreen] in which: + self.screenBufPage[self.preScreen] = -1 + + def notifyPageDuplicate( self, new, at ): + return + + def notifyPageMove( self, which, low, high ): + return + + def notifyPageUpdate( self, page, parameter, value ): + if parameter == PARAMETER.PAGE_BEATS: + notes = self.noteDB.getNotesByPage( page, self ) + for note in notes: + note.updateTransform() + + if page == self.screenBufPage[self.curScreen]: + self.screenBufBeats[self.curScreen] = value + self.curBeats = value + if self.playheadT >= value*Config.TICKS_PER_BEAT: + self.playheadT = value*Config.TICKS_PER_BEAT - 1 + self.playheadX = self.ticksToPixels( self.curBeats, self.playheadT ) + Config.TRACK_SPACING_DIV2 + self.invalidate_rect( 0, 0, self.width, self.height, page ) + if page == self.screenBufPage[self.preScreen]: + self.screenBufBeats[self.preScreen] = value + self.invalidate_rect( 0, 0, self.width, self.height, page ) + self.predrawPage() + + + #======================================================= + # Module Interface + + def getDrawingPackage( self, track ): + if track == self.drumIndex: + return ( self.image["hit"], self.image["hitSelected"], self.drawingArea.get_colormap(), self.trackColors[track] ) + else: + return ( self.image["note"], self.image["noteSelected"], self.drawingArea.get_colormap(), self.trackColors[track] ) + + def getActivePages( self ): + return self.screenBufPage + + def setPredrawPage( self, page ): + if self.screenBufPage[self.preScreen] != page: + self.screenBufPage[self.preScreen] = page + self.screenBufBeats[self.preScreen] = self.noteDB.getPage(page).beats + self.invalidate_rect( 0, 0, self.width, self.height, page ) + return True + return False + + def predrawPage( self ): + if self.screenBufPage[self.preScreen] == -1: return True # no page to predraw + if not self.predrawTimeout: + self.predrawTimeout = gobject.timeout_add( 50, self._predrawTimeout ) + + def abortPredrawPage( self ): + if self.predrawTimeout: + gobject.source_remove( self.predrawTimeout ) + self.predrawTimeout = False + + def _predrawTimeout( self ): + if self.preScreen == -1: return False # no page to predraw + if self.draw( self.preScreen, False, time.time() + 0.020 ): # 20 ms time limit + self.predrawTimeout = False + return False + return True + + + + def displayPage( self, page, predraw = -1 ): + if page == self.curPage: + if predraw >= 0 and self.screenBufPage[self.preScreen] != predraw: + self.screenBufPage[self.preScreen] = predraw + self.screenBufBeats[self.preScreen] = self.noteDB.getPage(predraw).beats + self.invalidate_rect( 0, 0, self.width, self.height, predraw ) + return + + if self.curPage >= 0 and self.curPage != page: clearNotes = True + else: clearNotes = False + + oldPage = self.curPage + self.curPage = page + self.curBeats = self.noteDB.getPage(page).beats + + if self.screenBufPage[self.preScreen] == self.curPage: # we predrew this page, so smart! + t = self.preScreen + self.preScreen = self.curScreen + self.curScreen = t + self.invalidate_rect( 0, 0, self.width, self.height, self.curPage, False ) + else: # we need to draw this page from scratch + self.screenBufPage[self.curScreen] = self.curPage + self.screenBufBeats[self.curScreen] = self.curBeats + self.invalidate_rect( 0, 0, self.width, self.height, self.curPage ) + + if predraw >= 0 and self.screenBufPage[self.preScreen] != predraw: + self.screenBufPage[self.preScreen] = predraw + self.screenBufBeats[self.preScreen] = self.noteDB.getPage(predraw).beats + self.invalidate_rect( 0, 0, self.width, self.height, predraw ) + + if clearNotes: # clear the notes now that we've sorted out the screen buffers + self.clearSelectedNotes( oldPage ) + + if self.curAction == "paste": + self._updateClipboardArea() + + def getPlayhead( self ): + return self.playheadT + + def setPlayhead( self, ticks ): + if self.playheadT != ticks: + self.invalidate_rect( self.playheadX-Config.PLAYHEAD_SIZE/2, 0, Config.PLAYHEAD_SIZE, self.height, self.curPage, False ) + self.playheadX = self.ticksToPixels( self.curBeats, ticks ) + Config.TRACK_SPACING_DIV2 + self.invalidate_rect( self.playheadX-Config.PLAYHEAD_SIZE/2, 0, Config.PLAYHEAD_SIZE, self.height, self.curPage, False ) + self.playheadT = ticks + + def setInterfaceMode( self, mode ): + self.doneCurrentAction() + + if mode == "tool": + mode = self.owner.getTool() + + if mode == "draw": + self.interfaceMode = INTERFACEMODE.DRAW + elif mode == "paint": + self.interfaceMode = INTERFACEMODE.PAINT + elif mode == "paste_notes": + self.interfaceMode = INTERFACEMODE.PASTE_NOTES + self.setCurrentAction("paste", self) + elif mode == "paste_tracks": + self.interfaceMode = INTERFACEMODE.PASTE_TRACKS + self.setCurrentAction("paste", self ) + else: + self.interfaceMode = INTERFACEMODE.DEFAULT + + def getSelectedNotes( self ): + ids = [] + for t in range(Config.NUMBER_OF_TRACKS): + ids.append( [ n.note.id for n in self.selectedNotes[t] ] ) + return ids + + #======================================================= + # Event Callbacks + + def size_allocate( self, widget, allocation ): + self.alloc = allocation + width = allocation.width + height = allocation.height + + self.drawingArea.set_size_request( width, height ) + + if self.window != None: + self.invalidate_rect( 0, 0, width, height, self.curPage, False ) + + def setPointerGrid(self, value): + self.pointerGrid = value + + def setDrawGrid(self, value): + self.drawGrid = value + + def setPaintGrid(self, value): + self.paintGrid = value + + def setPaintNoteDur(self, value): + self.paintNoteDur = value + + def handleButtonPress( self, widget, event ): + + TP.ProfileBegin( "TI::handleButtonPress" ) + + self.clickButton = event.button + + if event.type == gtk.gdk._2BUTTON_PRESS: self.buttonPressCount = 2 + elif event.type == gtk.gdk._3BUTTON_PRESS: self.buttonPressCount = 3 + else: self.buttonPressCount = 1 + + self.clickLoc = [ int(event.x), int(event.y) ] + + + if self.curAction == "paste": + self.doPaste() + self.setCurrentAction("block-track-select") + TP.ProfileEnd( "TI::handleButtonPress" ) + return + + + # check if we clicked on the playhead + if event.x >= self.playheadX and event.x <= self.playheadX + Config.PLAYHEAD_SIZE: + self.setCurrentAction( "playhead-drag", self ) + TP.ProfileEnd( "TI::handleButtonPress" ) + return + + if event.x < Config.TRACK_SPACING_DIV2 or event.x > self.trackWidth + Config.TRACK_SPACING_DIV2: + TP.ProfileEnd( "TI::handleButtonPress" ) + return + + for i in range(Config.NUMBER_OF_TRACKS): + if self.trackLimits[i][0] > event.y: break + if self.trackLimits[i][1] < event.y: continue + + handled = 0 + notes = self.noteDB.getNotesByTrack( self.curPage, i, self ) + last = len(notes)-1 + for n in range(last+1): + if i == self.drumIndex and n < last: # check to see if the next hit overlaps this one + if notes[n].getStartTick() == notes[n+1].getStartTick() and notes[n].getPitch() == notes[n+1].getPitch(): + continue + handled = notes[n].handleButtonPress( self, event ) + if handled == 0: + continue + elif handled == 1: + if not self.curAction: self.curAction = True # it was handled but no action was declared, set curAction to True anyway + TP.ProfileEnd( "TI::handleButtonPress" ) + return + else: # all other options mean we can stop looking + break + + if self.interfaceMode == INTERFACEMODE.DRAW: + if not handled or handled == -1: # event didn't overlap any notes, so we can draw + if i == self.drumIndex: pitch = min( self.pixelsToPitchDrumFloor( self.clickLoc[1] - self.trackLimits[i][1] + Config.HIT_HEIGHT//2 )//Config.PITCH_STEP_DRUM, Config.NUMBER_OF_POSSIBLE_PITCHES_DRUM-1)*Config.PITCH_STEP_DRUM + Config.MINIMUM_PITCH_DRUM + else: pitch = min( self.pixelsToPitchFloor( self.clickLoc[1] - self.trackLimits[i][1] + Config.NOTE_HEIGHT//2 ), Config.NUMBER_OF_POSSIBLE_PITCHES-1) + Config.MINIMUM_PITCH + onset = self.pixelsToTicksFloor( self.curBeats, self.clickLoc[0] - self.trackRect[i].x) + snapOnset = self.drawGrid * int(onset / float(self.drawGrid) + 0.5) + cs = CSoundNote( snapOnset, + pitch, + 0.75, + 0.5, + 1, + i, + instrumentId = self.owner.getTrackInstrument(i).instrumentId ) + cs.pageId = self.curPage + id = self.noteDB.addNote( -1, self.curPage, i, cs ) + n = self.noteDB.getNote( self.curPage, i, id, self ) + self.selectNotes( { i:[n] }, True ) + n.playSampleNote( False ) + + noteS = self.noteDB.getNotesByTrack(self.curPage, i) + for note in noteS: + if note.cs.onset < snapOnset and (note.cs.onset + note.cs.duration) > snapOnset: + self.noteDB.updateNote(self.curPage, i, note.id, PARAMETER.DURATION, snapOnset - note.cs.onset) + + if i != self.drumIndex: # switch to drag duration + self.updateDragLimits() + self.clickLoc[0] += self.ticksToPixels( self.curBeats, 1 ) + self.setCurrentAction( "note-drag-duration", n ) + self.setCursor("drag-duration") + else: + self.curAction = True # we handled this, but there's no real action + + TP.ProfileEnd( "TI::handleButtonPress" ) + return + elif self.interfaceMode == INTERFACEMODE.PAINT: + self.scale = self.getScale() + self.painting = True + self.paintTrack = i + if i == self.drumIndex: + pitch = min( self.pixelsToPitchDrumFloor( self.clickLoc[1] - self.trackLimits[i][1] + Config.HIT_HEIGHT//2 )//Config.PITCH_STEP_DRUM, Config.NUMBER_OF_POSSIBLE_PITCHES_DRUM-1)*Config.PITCH_STEP_DRUM + Config.MINIMUM_PITCH_DRUM + if pitch < 24: + pitch = 24 + elif pitch > 48: + pitch = 48 + else: + pitch = pitch + else: + pitch = min( self.pixelsToPitchFloor( self.clickLoc[1] - self.trackLimits[i][1] + Config.NOTE_HEIGHT//2 ), Config.NUMBER_OF_POSSIBLE_PITCHES-1) + Config.MINIMUM_PITCH + if pitch < 24: + pitch = 24 + elif pitch > 48: + pitch = 48 + else: + pitch = pitch + + minDiff = 100 + for pit in GenerationConstants.SCALES[self.scale]: + diff = abs(pitch-(pit+36)) + if diff < minDiff: + minDiff = diff + nearestPit = pit + pitch = nearestPit+36 + + onset = self.pixelsToTicksFloor( self.curBeats, self.clickLoc[0] - self.trackRect[i].x ) + onset = self.paintGrid * int(onset / self.paintGrid + 0.5) + self.pLastPos = onset + if i != self.drumIndex: + noteS = self.noteDB.getNotesByTrack(self.curPage, i) + ids = [] + stream = [] + for n in noteS: + if n.cs.onset >= onset and n.cs.onset < (onset + self.paintNoteDur): + ids.append(n.id) + if onset > n.cs.onset and onset < (n.cs.onset + n.cs.duration): + ids.append(n.id) + if len(ids): + stream += [self.curPage, i, len(ids)] + ids + self.noteDB.deleteNotes( stream + [-1] ) + + cs = CSoundNote( int(onset), + pitch, + 0.75, + 0.5, + 1, + i, + instrumentId = self.owner.getTrackInstrument(i).instrumentId ) + cs.pageId = self.curPage + id = self.noteDB.addNote( -1, self.curPage, i, cs ) + self.noteDB.updateNote(self.curPage, i, id, PARAMETER.DURATION, self.paintNoteDur) + n = self.noteDB.getNote( self.curPage, i, id, self ) + self.selectNotes( { i:[n] }, True ) + n.playSampleNote( False ) + self.curAction = True + + TP.ProfileEnd( "TI::handleButtonPress" ) + + + def handleButtonRelease( self, widget, event ): + if not self.clickButton: return # we recieved this event but were never clicked! (probably a popup window was open) + self.clickButton = 0 + self.painting = False + + TP.ProfileBegin( "TI::handleButtonRelease" ) + + if event.button != 1: + TP.ProfileEnd( "TI::handleButtonRelease" ) + return + + if not self.curAction: #do track selection stuff here so that we can also handle marquee selection + for i in range(Config.NUMBER_OF_TRACKS): + if self.trackLimits[i][0] > event.y: break + if self.trackLimits[i][1] < event.y: continue + if event.button == 1: + if self.buttonPressCount == 1: self.owner.toggleTrack( i, False ) + elif self.buttonPressCount == 2: self.owner.toggleTrack( i, True ) + else: self.owner.clearTracks() + break + + TP.ProfileEnd( "TI::handleButtonRelease" ) + return + + if not self.curActionObject: # there was no real action to carry out + self.curAction = False + TP.ProfileEnd( "TI::handleButtonRelease" ) + return + + if self.curActionObject != self: + self.curActionObject.handleButtonRelease( self, event, self.buttonPressCount ) + self.updateTooltip( event ) + else: + # we're doing the action ourselves + if self.curAction == "marquee": self.doneMarquee( event ) + elif self.curAction == "playhead-drag": self.donePlayhead( event ) + self.updateTooltip( event ) + + + TP.ProfileEnd( "TI::handleButtonRelease" ) + return + + def handleMotion( self, widget, event ): + TP.ProfileBegin( "TI::handleMotion::Common" ) + + if event.is_hint: + x, y, state = self.window.get_pointer() + event.x = float(x) + event.y = float(y) + event.state = state + + if self.painting: + i = self.paintTrack + curPos = self.pixelsToTicksFloor(self.curBeats, event.x - self.trackRect[i].x) + gridPos = self.paintGrid * int(curPos / self.paintGrid + 0.5) + if gridPos >= self.curBeats * Config.TICKS_PER_BEAT: + return + if gridPos != self.pLastPos: + self.pLastPos = gridPos + if i == self.drumIndex: + pitch = min( self.pixelsToPitchDrumFloor( int(event.y) - self.trackLimits[i][1] + Config.HIT_HEIGHT//2 )//Config.PITCH_STEP_DRUM, Config.NUMBER_OF_POSSIBLE_PITCHES_DRUM-1)*Config.PITCH_STEP_DRUM + Config.MINIMUM_PITCH_DRUM + if pitch < 24: + pitch = 24 + elif pitch > 48: + pitch = 48 + else: + pitch = pitch + else: + pitch = min( self.pixelsToPitchFloor( int(event.y) - self.trackLimits[i][1] + Config.NOTE_HEIGHT//2 ), Config.NUMBER_OF_POSSIBLE_PITCHES-1) + Config.MINIMUM_PITCH + if pitch < 24: + pitch = 24 + elif pitch > 48: + pitch = 48 + else: + pitch = pitch + minDiff = 100 + for pit in GenerationConstants.SCALES[self.scale]: + diff = abs(pitch-(pit+36)) + if diff < minDiff: + minDiff = diff + nearestPit = pit + pitch = nearestPit+36 + + onset = gridPos + if i != self.drumIndex: + noteS = self.noteDB.getNotesByTrack(self.curPage, i) + ids = [] + stream = [] + for n in noteS: + if n.cs.onset >= onset and n.cs.onset < (onset + self.paintNoteDur): + ids.append(n.id) + if onset > n.cs.onset and onset < (n.cs.onset + n.cs.duration): + ids.append(n.id) + if len(ids): + stream += [self.curPage, i, len(ids)] + ids + self.noteDB.deleteNotes( stream + [-1] ) + + cs = CSoundNote( int(onset), + pitch, + 0.75, + 0.5, + 1, + i, + instrumentId = self.owner.getTrackInstrument(i).instrumentId ) + cs.pageId = self.curPage + id = self.noteDB.addNote( -1, self.curPage, i, cs ) + self.noteDB.updateNote(self.curPage, i, id, PARAMETER.DURATION, self.paintNoteDur) + n = self.noteDB.getNote( self.curPage, i, id, self ) + self.selectNotes( { i:[n] }, True ) + n.playSampleNote( False ) + self.curAction = True + + + TP.ProfileEnd( "TI::handleMotion::Common" ) + + if not self.clickButton and self.curAction != "paste": # we recieved this event but were never clicked! (probably a popup window was open) + TP.ProfileBegin( "TI::handleMotion::Hover" ) + self.updateTooltip( event ) + TP.ProfileEnd( "TI::handleMotion::Hover" ) + return + + if self.curAction == "paste": + TP.ProfileBegin( "TI::handleMotion::Paste" ) + top = Config.NUMBER_OF_TRACKS + for i in range(Config.NUMBER_OF_TRACKS): + if self.trackLimits[i][0] > event.y: break + if self.trackLimits[i][1] < event.y: continue + top = i + break + self.updatePaste( self.pixelsToTicksFloor( self.curBeats, event.x ), top ) + TP.ProfileEnd( "TI::handleMotion::Paste" ) + elif event.state & gtk.gdk.BUTTON1_MASK: + TP.ProfileBegin( "TI::handleMotion::Drag" ) + + if not self.curAction: # no action is in progress yet we're dragging, start a marquee + self.setCurrentAction( "marquee", self ) + + if self.curAction == "note-drag-onset": + self.noteDragOnset( event ) + + elif self.curAction == "note-drag-duration": + self.noteDragDuration( event ) + + elif self.curAction == "note-drag-pitch": + self.noteDragPitch( event ) + + elif self.curAction == "note-drag-pitch-drum": + self.noteDragPitch( event, True ) + + elif self.curAction == "marquee": + self.updateMarquee( event ) + + elif self.curAction == "playhead-drag": + self.updatePlayhead( event ) + + TP.ProfileEnd( "TI::handleMotion::Drag" ) + else: + TP.ProfileBegin( "TI::handleMotion::Hover" ) + self.updateTooltip( event ) + TP.ProfileEnd( "TI::handleMotion::Hover" ) + + return + + #======================================================= + # Actions + + def setCurrentAction( self, action, obj = None ): + if self.curAction: + self.doneCurrentAction() + + self.curAction = action + self.curActionObject = obj + + if action == "note-drag-onset": self.updateDragLimits() + elif action == "note-drag-duration": self.updateDragLimits() + elif action == "note-drag-pitch": self.updateDragLimits() + elif action == "note-drag-pitch-drum": self.updateDragLimits() + elif action == "paste": + self._updateClipboardArea() + self.setCursor("paste") + + def doneCurrentAction( self ): + if not self.curAction: return + action = self.curAction + self.curAction = False + + if action == "note-drag-onset": self.doneNoteDrag( action ) + elif action == "note-drag-duration": self.doneNoteDrag( action ) + elif action == "note-drag-pitch": self.doneNoteDrag( action ) + elif action == "note-drag-pitch-drum": self.doneNoteDrag( action ) + elif action == "paste": + self.owner.cleanupClipboard() + + def trackToggled( self, trackN = -1 ): + if trackN == -1: self.invalidate_rect( 0, 0, self.width, self.height ) + else: self.invalidate_rect( 0, self.trackLimits[trackN][0], self.width, self.trackLimits[trackN][1]-self.trackLimits[trackN][0] ) + + def selectionChanged( self ): + if self.curAction == "note-drag-onset": self.updateDragLimits() + elif self.curAction == "note-drag-duration": self.updateDragLimits() + elif self.curAction == "note-drag-pitch": self.updateDragLimits() + elif self.curAction == "note-drag-pitch-drum": self.updateDragLimits() + for i in range(Config.NUMBER_OF_TRACKS): + if len(self.selectedNotes[i]): + self.owner.setContextState( CONTEXT.NOTE, True ) + self.owner.setContext( CONTEXT.NOTE ) + return + self.owner.setContextState( CONTEXT.NOTE, False ) + + def applyNoteSelection( self, mode, trackN, which, page = -1 ): + if page == -1: page = self.curPage + if mode == SELECTNOTES.ALL: + track = self.noteDB.getNotesByTrack( page, trackN, self ) + map( lambda note:note.setSelected( True ), track ) + self.selectedNotes[trackN] = [] + map( lambda note:self.selectedNotes[trackN].append(note), track ) + elif mode == SELECTNOTES.NONE: + track = self.selectedNotes[trackN] #self.noteDB.getNotesByTrack( page, trackN, self ) + map( lambda note:note.setSelected( False ), track ) + self.selectedNotes[trackN] = [] + elif mode == SELECTNOTES.ADD: + for note in which: + if note.setSelected( True ): + self.selectedNotes[trackN].append( note ) + elif mode == SELECTNOTES.REMOVE: + for note in which: + if note.setSelected( False ): + self.selectedNotes[trackN].remove( note ) + elif mode == SELECTNOTES.FLIP: + for note in which: + if note.getSelected(): + note.setSelected( False ) + self.selectedNotes[trackN].remove( note ) + else: + note.setSelected( True ) + self.selectedNotes[trackN].append( note ) + elif mode == SELECTNOTES.EXCLUSIVE: + notes = self.noteDB.getNotesByTrack( page, trackN, self ) + for n in range(len(notes)): + if notes[n] in which: + if notes[n].setSelected( True ): + self.selectedNotes[trackN].append( notes[n] ) + else: + if notes[n].setSelected( False ): + self.selectedNotes[trackN].remove( notes[n] ) + + def selectNotesByBar( self, trackN, start, stop, page = -1 ): + for i in range(Config.NUMBER_OF_TRACKS): + if i == trackN: + notes = [] + track = self.noteDB.getNotesByTrack( self.curPage, trackN, self ) + for n in range(len(track)): + if track[n].testOnset( start, stop ): notes.append(track[n]) + if not Config.ModKeys.ctrlDown: self.applyNoteSelection( SELECTNOTES.EXCLUSIVE, trackN, notes, page ) + else: self.applyNoteSelection( SELECTNOTES.ADD, trackN, notes, page ) + else: + if not Config.ModKeys.ctrlDown: self.applyNoteSelection( SELECTNOTES.NONE, i, [], page ) + self.selectionChanged() + + def selectNotesByTrack( self, trackN, page = -1 ): + if Config.ModKeys.ctrlDown: + self.applyNoteSelection( SELECTNOTES.ALL, trackN, [], page ) + else: + for i in range(Config.NUMBER_OF_TRACKS): + if i == trackN: self.applyNoteSelection( SELECTNOTES.ALL, trackN, [], page ) + else: self.applyNoteSelection( SELECTNOTES.NONE, i, [], page ) + self.selectionChanged() + + def selectNotes( self, noteDic, ignoreCtrl = False, page = -1 ): + if Config.ModKeys.ctrlDown and not ignoreCtrl: + for i in noteDic: + self.applyNoteSelection( SELECTNOTES.FLIP, i, noteDic[i], page ) + else: + for i in range(Config.NUMBER_OF_TRACKS): + if i in noteDic: self.applyNoteSelection( SELECTNOTES.EXCLUSIVE, i, noteDic[i], page ) + else: self.applyNoteSelection( SELECTNOTES.NONE, i, [], page ) + self.selectionChanged() + + def deselectNotes( self, noteDic, page = -1 ): + for i in noteDic: + self.applyNoteSelection( SELECTNOTES.REMOVE, i, noteDic[i], page ) + self.selectionChanged() + + def clearSelectedNotes( self, page = -1 ): + for i in range(Config.NUMBER_OF_TRACKS): + self.applyNoteSelection( SELECTNOTES.NONE, i, [], page ) + self.selectionChanged() + + def updateDragLimits( self ): + self.dragLimits = [ [-9999,9999], [-9999,9999], [-9999,9999] ] # initialize to big numbers! + maxRightBound = self.noteDB.getPage(self.curPage).ticks + + for i in range(Config.NUMBER_OF_TRACKS): + if not len(self.selectedNotes[i]): continue # no selected notes here + + track = self.noteDB.getNotesByTrack( self.curPage, i, self ) + leftBound = 0 + skip = True # skip the first note + for n in range(len(track)): + if skip: + skip = False + thisNote = track[n] + continue + nextNote = track[n] + if not thisNote.getSelected(): + leftBound = thisNote.getEndTick() + else: + if not nextNote.getSelected(): + rightBound = min( nextNote.getStartTick(), maxRightBound ) + widthBound = rightBound + else: + rightBound = maxRightBound + widthBound = min( nextNote.getStartTick(), maxRightBound ) + thisNote.updateDragLimits( self.dragLimits, leftBound, rightBound, widthBound, maxRightBound ) + thisNote = nextNote + # do the last note + if thisNote.getSelected(): + thisNote.updateDragLimits( self.dragLimits, leftBound, maxRightBound, maxRightBound, maxRightBound ) + + def noteDragOnset( self, event ): + do = self.pixelsToTicks( self.curBeats, event.x - self.clickLoc[0] ) + do = min( self.dragLimits[0][1], max( self.dragLimits[0][0], do ) ) + do = self.pointerGrid * int(do / self.pointerGrid) + + if do != self.lastDO: + self.lastDO = do + stream = [] + for i in range(Config.NUMBER_OF_TRACKS): + tstream = [] + for note in self.selectedNotes[i]: + note.noteDragOnset( do, tstream ) + if len(tstream): + stream += [ self.curPage, i, PARAMETER.ONSET, len(tstream)//2 ] + tstream + if len(stream): + self.noteDB.updateNotes( stream + [-1] ) + + def noteDragDuration( self, event ): + dd = self.pixelsToTicks( self.curBeats, event.x - self.clickLoc[0] ) + dd = min( self.dragLimits[2][1], max( self.dragLimits[2][0], dd ) ) + + if dd != self.lastDD: + self.lastDD = dd + stream = [] + for i in range(Config.NUMBER_OF_TRACKS): + tstream = [] + for note in self.selectedNotes[i]: + note.noteDragDuration( dd, tstream ) + if len(tstream): + stream += [ self.curPage, i, PARAMETER.DURATION, len(tstream)//2 ] + tstream + if len(stream): + self.noteDB.updateNotes( stream + [-1] ) + + def noteDragPitch( self, event, drum = False ): + if not drum: dp = self.pixelsToPitch( event.y - self.clickLoc[1] ) + else: dp = self.pixelsToPitchDrum( event.y - self.clickLoc[1] ) + dp = min( self.dragLimits[1][1], max( self.dragLimits[1][0], dp ) ) + + if dp != self.lastDP: + self.lastDP = dp + stream = [] + for i in range(Config.NUMBER_OF_TRACKS): + tstream = [] + for note in self.selectedNotes[i]: + note.noteDragPitch( dp, tstream ) + if len(tstream): + stream += [ self.curPage, i, PARAMETER.PITCH, len(tstream)//2 ] + tstream + if len(stream): + self.noteDB.updateNotes( stream + [-1] ) + + if self.curActionObject.note.track != self.drumIndex: + self.curActionObject.playSampleNote( True ) + elif dp != self.lastDrumDP and not dp%2: # only play of "full" drum pitches + self.lastDrumDP = dp + self.curActionObject.playSampleNote( False ) + + def doneNoteDrag( self, action ): + # if action == "note-drag-pitch" or action == "note-drag-pitch-drum": + # self.curActionObject.playSampleNote() + + self.lastDO = self.lastDP = self.lastDrumDP = self.lastDD = None + + for i in range(Config.NUMBER_OF_TRACKS): + for note in self.selectedNotes[i]: + note.doneNoteDrag( self ) + + def noteStepOnset( self, step ): + stream = [] + for i in range(Config.NUMBER_OF_TRACKS): + if not len(self.selectedNotes[i]): continue # no selected notes here + + tstream = [] + track = self.noteDB.getNotesByTrack( self.curPage, i, self ) + if step < 0: # moving to the left, iterate forwards + leftBound = 0 + for n in range(len(track)): + leftBound = track[n].noteDecOnset( step, leftBound, tstream ) + else: # moving to the right, iterate backwards + rightBound = self.noteDB.getPage(self.curPage).ticks + for n in range(len(track)-1, -1, -1 ): + rightBound = track[n].noteIncOnset( step, rightBound, tstream ) + + if len(tstream): + stream += [ self.curPage, i, PARAMETER.ONSET, len(tstream)//2 ] + tstream + + if len(stream): + self.noteDB.updateNotes( stream + [-1] ) + + def noteStepPitch( self, step ): + stream = [] + for i in range(Config.NUMBER_OF_TRACKS): + if not len(self.selectedNotes[i]): continue # no selected notes here + + tstream = [] + if step < 0: + for n in self.selectedNotes[i]: + n.noteDecPitch( step, tstream ) + else: + for n in self.selectedNotes[i]: + n.noteIncPitch( step, tstream ) + + if len(tstream): + stream += [ self.curPage, i, PARAMETER.PITCH, len(tstream)//2 ] + tstream + + if len(stream): + self.noteDB.updateNotes( stream + [-1] ) + + def noteStepDuration( self, step ): + stream = [] + for i in range(Config.NUMBER_OF_TRACKS): + if not len(self.selectedNotes[i]): continue # no selected notes here + + tstream = [] + if step < 0: + for n in self.selectedNotes[i]: + n.noteDecDuration( step, tstream ) + else: + track = self.noteDB.getNotesByTrack( self.curPage, i, self ) + for j in range(len(track)-1): + track[j].noteIncDuration( step, track[j+1].getStartTick(), tstream ) + track[len(track)-1].noteIncDuration( step, self.noteDB.getPage(self.curPage).ticks, tstream ) + + if len(tstream): + stream += [ self.curPage, i, PARAMETER.DURATION, len(tstream)//2 ] + tstream + + if len(stream): + self.noteDB.updateNotes( stream + [-1] ) + + def noteStepVolume( self, step ): + stream = [] + for i in range(Config.NUMBER_OF_TRACKS): + if not len(self.selectedNotes[i]): continue # no selected notes here + + tstream = [] + if step < 0: + for n in self.selectedNotes[i]: + n.noteDecVolume( step, tstream ) + else: + for n in self.selectedNotes[i]: + n.noteIncVolume( step, tstream ) + + if len(tstream): + stream += [ self.curPage, i, PARAMETER.AMPLITUDE, len(tstream)//2 ] + tstream + + if len(stream): + self.noteDB.updateNotes( stream + [-1] ) + + + def updateMarquee( self, event ): + if self.marqueeLoc: + oldX = self.marqueeRect[0][0] + oldEndX = self.marqueeRect[0][0] + self.marqueeRect[1][0] + oldY = self.marqueeRect[0][1] + oldEndY = self.marqueeRect[0][1] + self.marqueeRect[1][1] + else: + oldX = oldEndX = self.clickLoc[0] + oldY = oldEndY = self.clickLoc[1] + + self.marqueeLoc = [ int(event.x), int(event.y) ] + if self.marqueeLoc[0] < 0: self.marqueeLoc[0] = 0 + elif self.marqueeLoc[0] > self.width: self.marqueeLoc[0] = self.width + if self.marqueeLoc[1] < 0: self.marqueeLoc[1] = 0 + elif self.marqueeLoc[1] > self.height: self.marqueeLoc[1] = self.height + + if self.marqueeLoc[0] > self.clickLoc[0]: + self.marqueeRect[0][0] = self.clickLoc[0] + self.marqueeRect[1][0] = self.marqueeLoc[0] - self.clickLoc[0] + else: + self.marqueeRect[0][0] = self.marqueeLoc[0] + self.marqueeRect[1][0] = self.clickLoc[0] - self.marqueeLoc[0] + if self.marqueeLoc[1] > self.clickLoc[1]: + self.marqueeRect[0][1] = self.clickLoc[1] + self.marqueeRect[1][1] = self.marqueeLoc[1] - self.clickLoc[1] + else: + self.marqueeRect[0][1] = self.marqueeLoc[1] + self.marqueeRect[1][1] = self.clickLoc[1] - self.marqueeLoc[1] + + x = min( self.marqueeRect[0][0], oldX ) + width = max( self.marqueeRect[0][0] + self.marqueeRect[1][0], oldEndX ) - x + y = min( self.marqueeRect[0][1], oldY ) + height = max( self.marqueeRect[0][1] + self.marqueeRect[1][1], oldEndY ) - y + self.invalidate_rect( x-1, y-1, width+2, height+2, self.curPage, False ) + + def doneMarquee( self, event ): + if self.marqueeLoc: + stop = [ self.marqueeRect[0][0] + self.marqueeRect[1][0], self.marqueeRect[0][1] + self.marqueeRect[1][1] ] + + select = {} + + for i in range(Config.NUMBER_OF_TRACKS): + intersectionY = [ max(self.marqueeRect[0][1],self.trackLimits[i][0]), min(stop[1],self.trackLimits[i][1]) ] + if intersectionY[0] > intersectionY[1]: + continue + + notes = [] + track = self.noteDB.getNotesByTrack( self.curPage, i, self ) + for n in range(len(track)): + hit = track[n].handleMarqueeSelect( self, + [ self.marqueeRect[0][0], intersectionY[0] ], \ + [ stop[0], intersectionY[1] ] ) + if hit: notes.append(track[n]) + + if len(notes): select[i] = notes + + self.selectNotes( select ) + + self.marqueeLoc = False + self.doneCurrentAction() + + self.invalidate_rect( self.marqueeRect[0][0]-1, self.marqueeRect[0][1]-1, self.marqueeRect[1][0]+2, self.marqueeRect[1][1]+2, self.curPage, False ) + + def updatePlayhead( self, event ): + x = min( self.trackWidth - self.pixelsPerTick[self.curBeats], max( Config.TRACK_SPACING_DIV2, event.x ) ) + self.setPlayhead( self.pixelsToTicks( self.curBeats, x ) ) + + def donePlayhead( self, event ): + x = min( self.trackWidth - self.pixelsPerTick[self.curBeats], max( Config.TRACK_SPACING_DIV2, event.x ) ) + ticks = self.pixelsToTicks( self.curBeats, x ) + print "set playhead to %d ticks" % (ticks) + self.doneCurrentAction() + + def updatePaste( self, tick, track ): + if self.interfaceMode == INTERFACEMODE.PASTE_TRACKS: tick = 0 + if self.pasteTick == tick and self.pasteTrack == track: return + if self.noteDB.getPage(self.curPage).ticks < tick < 0 \ + or track > self.drumIndex \ + or ( track == self.drumIndex and not self.clipboardDrumTrack ): + if self.pasteRect: + self.invalidate_rect( self.pasteRect[0][0], self.pasteRect[0][1], self.pasteRect[1][0], self.pasteRect[1][1], self.curPage, False ) + self.pasteTick = self.pasteTrack = -1 + self.pasteRect = False + return + if self.pasteRect: + self.invalidate_rect( self.pasteRect[0][0], self.pasteRect[0][1], self.pasteRect[1][0], self.pasteRect[1][1], self.curPage, False ) + if self.clipboardDrumTrack: + bottom = self.drumIndex + else: + bottom = self.drumIndex - 1 + for t in range(self.drumIndex-1,self.clipboardTrackTop-1,-1): + if self.clipboardArea["tracks"][t]: break + bottom -= 1 + end = -tick + min( self.noteDB.getPage(self.curPage).ticks, tick + self.clipboardArea["limit"][1]-self.clipboardArea["limit"][0] ) + self.pasteTick = tick + self.pasteTrack = track + self.pasteRect = [ [ self.ticksToPixels( self.curBeats, tick ), \ + self.trackLimits[track][0] ], \ + [ self.ticksToPixels( self.curBeats, end), \ + self.trackLimits[bottom][1] ] ] + self.invalidate_rect( self.pasteRect[0][0], self.pasteRect[0][1], self.pasteRect[1][0], self.pasteRect[1][1], self.curPage, False ) + + def doPaste( self ): + if self.pasteTrack == -1: + self.doneCurrentAction() + return + + trackMap = {} + for t in range(self.pasteTrack,self.drumIndex): + ind = t+self.clipboardTrackTop-self.pasteTrack + if ind >= self.drumIndex: break + if not self.clipboardArea["tracks"][ind]: + continue + trackMap[t] = ind + if self.clipboardDrumTrack: + trackMap[self.drumIndex] = self.drumIndex + new = self.owner.pasteClipboard( self.pasteTick - self.clipboardArea["limit"][0], trackMap ) + if self.interfaceMode == INTERFACEMODE.PASTE_NOTES and self.curPage in new: + noteDic = {} + for t in range(Config.NUMBER_OF_TRACKS): + if len(new[self.curPage][t]): + noteDic[t] = [ self.noteDB.getNote( self.curPage, t, n, self ) for n in new[self.curPage][t] ] + self.selectNotes(noteDic) + elif self.interfaceMode == INTERFACEMODE.PASTE_TRACKS: + for t in range(self.drumIndex): + ind = t + self.clipboardTrackTop - self.pasteTrack + if ind >= self.drumIndex or ind < 0: self.owner.setTrack( t, False ) + else: self.owner.setTrack( t, self.clipboardArea["tracks"][ind] ) + self.owner.setTrack( self.drumIndex, self.clipboardDrumTrack ) + + self.doneCurrentAction() + + def donePaste( self ): + if self.pasteRect: + self.invalidate_rect( self.pasteRect[0][0], self.pasteRect[0][1], self.pasteRect[1][0], self.pasteRect[1][1], self.curPage, False ) + self.pasteTick = self.pasteTrack = -1 + self.pasteRect = False + self.setInterfaceMode("tool") + # make a fake event for updateTooltip + event = gtk.gdk.Event(gtk.gdk.MOTION_NOTIFY) + x, y, state = self.window.get_pointer() + event.x = float(x) + event.y = float(y) + event.state = state + self.updateTooltip( event ) + + def updateTooltip( self, event ): + + # check clicked the playhead + if event.x >= self.playheadX and event.x <= self.playheadX + Config.PLAYHEAD_SIZE: + self.setCursor("drag-playhead") + return + + if event.x < Config.TRACK_SPACING_DIV2 or event.x > self.trackWidth + Config.TRACK_SPACING_DIV2: + self.setCursor("default") + return + + for i in range(Config.NUMBER_OF_TRACKS): + if self.trackLimits[i][0] > event.y: break + if self.trackLimits[i][1] < event.y: continue + + notes = self.noteDB.getNotesByTrack( self.curPage, i, self ) + handled = 0 + for n in range(len(notes)): + handled = notes[n].updateTooltip( self, event ) + if handled == 0: continue + elif handled == 1: return # event was handled + else: break + + # note wasn't handled, could potentially draw a note + if self.interfaceMode == INTERFACEMODE.DRAW: + if handled == -2: # event X overlapped with a note + self.setCursor("default") + return + + self.setCursor("pencil") + return + + break + + self.setCursor("default") + + def setCursor( self, cursor ): + self.window.set_cursor(self.cursor[cursor]) + + #======================================================= + # Drawing + + def draw( self, buf, noescape = True, timeout = 0 ): + if not self.screenBufDirty[buf]: return True + + TP.ProfileBegin( "TrackInterface::draw" ) + + startX = self.screenBufDirtyRect[buf].x + startY = self.screenBufDirtyRect[buf].y + stopX = self.screenBufDirtyRect[buf].x + self.screenBufDirtyRect[buf].width + stopY = self.screenBufDirtyRect[buf].y + self.screenBufDirtyRect[buf].height + + beatStart = Config.TRACK_SPACING_DIV2 + beats = self.screenBufBeats[buf] + + pixmap = self.screenBuf[buf] + + resume = self.screenBufResume[buf] + + self.gc.set_clip_rectangle( self.screenBufDirtyRect[buf] ) + + self.gc.set_line_attributes( Config.BEAT_LINE_SIZE, gtk.gdk.LINE_ON_OFF_DASH, gtk.gdk.CAP_BUTT, gtk.gdk.JOIN_MITER ) + # regular tracks + for i in range( resume[0], self.drumIndex ): + if resume[1] == 0: + if startY > self.trackLimits[i][1]: continue + if stopY < self.trackLimits[i][0]: break + + # draw background + if self.owner.getTrackSelected( i ): + pixmap.draw_drawable( self.gc, self.image["trackBGSelected"], 0, 0, 0, self.trackLimits[i][0], self.trackFullWidth, self.trackFullHeight ) + else: + pixmap.draw_drawable( self.gc, self.image["trackBG"], 0, 0, 0, self.trackLimits[i][0], self.trackFullWidth, self.trackFullHeight ) + + # draw beat lines + self.gc.foreground = self.beatColor + for j in range(1,self.screenBufBeats[buf]): + x = beatStart + self.beatSpacing[beats][j] + pixmap.draw_line( self.gc, x, self.trackRect[i].y, x, self.trackRect[i].y+self.trackRect[i].height ) + + resume[1] = 1 # background drawn + + # draw notes + TP.ProfileBegin("TI::draw notes") + notes = self.noteDB.getNotesByTrack( self.screenBufPage[buf], i, self ) + for n in range( resume[2], len(notes) ): + # check escape + if not noescape and time.time() > timeout: + resume[0] = i + resume[2] = n + TP.ProfilePause( "TrackInterface::draw" ) + return False + + if not notes[n].draw( pixmap, self.gc, startX, stopX ): break + TP.ProfileEnd("TI::draw notes") + + # finished a track, reset the resume values for the next one + resume[1] = 0 + resume[2] = 0 + + # drum track + if stopY > self.trackLimits[self.drumIndex][0]: + + if resume[1] == 0: + # draw background + if self.owner.getTrackSelected( self.drumIndex ): + pixmap.draw_drawable( self.gc, self.image["trackBGDrumSelected"], 0, 0, 0, self.trackLimits[self.drumIndex][0], self.trackFullWidth, self.trackFullHeightDrum ) + else: + pixmap.draw_drawable( self.gc, self.image["trackBGDrum"], 0, 0, 0, self.trackLimits[self.drumIndex][0], self.trackFullWidth, self.trackFullHeightDrum ) + + # draw beat lines + self.gc.foreground = self.beatColor + for j in range(1,self.screenBufBeats[buf]): + x = beatStart + self.beatSpacing[beats][j] + pixmap.draw_line( self.gc, x, self.trackRect[self.drumIndex].y, x, self.trackRect[self.drumIndex].y+self.trackRect[self.drumIndex].height ) + + resume[1] = 1 # background drawn + + # draw notes + notes = self.noteDB.getNotesByTrack( self.screenBufPage[buf], self.drumIndex, self ) + for n in range( resume[2], len(notes) ): + # check escape + if not noescape and time.time() > timeout: + resume[0] = i + resume[2] = n + TP.ProfilePause( "TrackInterface::draw" ) + return False + if not notes[n].draw( pixmap, self.gc, startX, stopX ): break + + self.screenBufDirty[buf] = False + + TP.ProfileEnd( "TrackInterface::draw" ) + + return True + + def expose( self, DA, event ): + + if self.screenBufDirty[self.curScreen]: + self.draw( self.curScreen ) + + TP.ProfileBegin( "TrackInterface::expose" ) + + startX = event.area.x + startY = event.area.y + stopX = event.area.x + event.area.width + stopY = event.area.y + event.area.height + + #print "%d %d %d %d" % (startX,startY,stopX,stopY) + + self.gc.set_clip_rectangle( event.area ) + + # draw base + DA.window.draw_drawable( self.gc, self.screenBuf[self.curScreen], startX, startY, startX, startY, event.area.width, event.area.height ) + + # draw playhead + self.gc.set_line_attributes( Config.PLAYHEAD_SIZE, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, gtk.gdk.JOIN_MITER ) + self.gc.foreground = self.playheadColor + DA.window.draw_line( self.gc, self.playheadX, startY, self.playheadX, stopY ) + + if self.marqueeLoc: # draw the selection rect + self.gc.set_line_attributes( Config.MARQUEE_SIZE, gtk.gdk.LINE_ON_OFF_DASH, gtk.gdk.CAP_BUTT, gtk.gdk.JOIN_MITER ) + self.gc.foreground = self.marqueeColor + DA.window.draw_rectangle( self.gc, False, self.marqueeRect[0][0], self.marqueeRect[0][1], self.marqueeRect[1][0], self.marqueeRect[1][1] ) + + if self.pasteRect: # draw the paste highlight + self.gc.set_function( gtk.gdk.INVERT ) + for t in range(self.pasteTrack,self.drumIndex): + ind = t+self.clipboardTrackTop-self.pasteTrack + if ind >= self.drumIndex: break + if not self.clipboardArea["tracks"][ind]: + continue + DA.window.draw_rectangle( self.gc, True, self.pasteRect[0][0], self.trackLimits[t][0] + Config.TRACK_SPACING_DIV2, self.pasteRect[1][0], self.trackHeight ) + if self.clipboardDrumTrack: + DA.window.draw_rectangle( self.gc, True, self.pasteRect[0][0], self.trackLimits[self.drumIndex][0] + Config.TRACK_SPACING_DIV2, self.pasteRect[1][0], self.trackHeightDrum ) + self.gc.set_function( gtk.gdk.COPY ) + + self.drawingAreaDirty = False + + TP.ProfileEnd( "TrackInterface::expose" ) + + def invalidate_rect( self, x, y, width, height, page = -1, base = True ): + #print "%d %d %d %d Page %d CurPage %d" % (x,y,width,height,page,self.curPage) + self.dirtyRectToAdd.x = x + self.dirtyRectToAdd.y = y + self.dirtyRectToAdd.width = width + self.dirtyRectToAdd.height = height + + #print "dirty %d %d %d %d %d %d" % (x, y, width, height, x+width, y+height) + if page == self.curPage or page == -1: + if base: # the base image has been dirtied + if not self.screenBufDirty[self.curScreen]: + self.screenBufDirtyRect[self.curScreen].x = x + self.screenBufDirtyRect[self.curScreen].y = y + self.screenBufDirtyRect[self.curScreen].width = width + self.screenBufDirtyRect[self.curScreen].height = height + else: + self.screenBufDirtyRect[self.curScreen] = self.screenBufDirtyRect[self.curScreen].union( self.dirtyRectToAdd ) + self.screenBufResume[self.curScreen] = [0,0,0] + self.screenBufDirty[self.curScreen] = True + if self.drawingArea.window != None: + self.drawingArea.window.invalidate_rect( self.dirtyRectToAdd, True ) + self.drawingAreaDirty = True + + if page == self.screenBufPage[self.preScreen] or page == -1: + if not self.screenBufDirty[self.preScreen]: + self.screenBufDirtyRect[self.preScreen].x = x + self.screenBufDirtyRect[self.preScreen].y = y + self.screenBufDirtyRect[self.preScreen].width = width + self.screenBufDirtyRect[self.preScreen].height = height + else: + self.screenBufDirtyRect[self.preScreen] = self.screenBufDirtyRect[self.preScreen].union( self.dirtyRectToAdd ) + self.screenBufResume[self.preScreen] = [0,0,0] + self.screenBufDirty[self.preScreen] = True + + #self.queue_draw() + + def getTrackOrigin( self, track ): + return ( self.trackRect[track].x, self.trackRect[track].y ) + + def ticksToPixels( self, beats, ticks ): + return int(round( ticks * self.pixelsPerTick[beats] )) + def pixelsToTicks( self, beats, pixels ): + return int(round( pixels * self.ticksPerPixel[beats] )) + def ticksToPixelsFloor( self, beats, ticks ): + return int( ticks * self.pixelsPerTick[beats] ) + def pixelsToTicksFloor( self, beats, pixels ): + return int( pixels * self.ticksPerPixel[beats] ) + def pitchToPixels( self, pitch ): + return int(round( ( Config.MAXIMUM_PITCH - pitch ) * self.pixelsPerPitch )) + def pixelsToPitch( self, pixels ): + return int(round(-pixels*self.pitchPerPixel)) + def pitchToPixelsFloor( self, pitch ): + return int(( Config.MAXIMUM_PITCH - pitch ) * self.pixelsPerPitch ) + def pixelsToPitchFloor( self, pixels ): + return int(-pixels*self.pitchPerPixel) + def pitchToPixelsDrum( self, pitch ): + return int(round( ( Config.MAXIMUM_PITCH_DRUM - pitch ) * self.pixelsPerPitchDrum )) + def pixelsToPitchDrum( self, pixels ): + return int(round(-pixels*self.pitchPerPixelDrum)) + def pitchToPixelsDrumFloor( self, pitch ): + return int( ( Config.MAXIMUM_PITCH_DRUM - pitch ) * self.pixelsPerPitchDrum ) + def pixelsToPitchDrumFloor( self, pixels ): + return int(-pixels*self.pitchPerPixelDrum) diff --git a/Edit/TuneInterface.py b/Edit/TuneInterface.py new file mode 100644 index 0000000..f8bc4e7 --- /dev/null +++ b/Edit/TuneInterface.py @@ -0,0 +1,646 @@ +import pygtk +pygtk.require( '2.0' ) +import gtk + +import common.Config as Config + +from common.Util.Profiler import TP +from Edit.MainWindow import CONTEXT + +from common.Util.NoteDB import PARAMETER + +class TuneInterfaceParasite: + + def __init__( self, noteDB, owner, note ): + self.noteDB = noteDB + self.owner = owner + self.note = note + + self.x = self.y = self.width = -1 + + def attach( self ): + self.updateParameter( None, None ) + return self + + def destroy( self ): + self.owner.invalidate_thumbnail( self.note.page, self.x, self.y, self.width, 1 ) + + def updateParameter( self, parameter, value ): + if parameter == PARAMETER.AMPLITUDE: return + x = 2 + Config.THUMBNAIL_TRACK_RECT[self.note.track][0] + self.owner.ticksToPixels( self.noteDB.getPage( self.note.page).beats, self.note.cs.onset ) + if self.note.track == Config.NUMBER_OF_TRACKS-1: # drum track + y = Config.THUMBNAIL_TRACK_RECT[self.note.track][1] + self.owner.pitchToPixelsDrum( self.note.cs.pitch ) + if x != self.x or y != self.y: + if parameter != None: # not the first update + xx = min( self.x, x ) + yy = min( self.y, y ) + endxx = max( self.endx, x + 1 ) + endyy = max( self.y, y ) + 1 + self.x = x + self.endx = x + 1 + self.y = y + self.owner.invalidate_thumbnail( self.note.page, xx, yy, endxx-xx, endyy-yy ) + else: + self.x = x + self.endx = x + 1 + self.y = y + self.owner.invalidate_thumbnail( self.note.page, x, y, 1, 1 ) + else: + y = Config.THUMBNAIL_TRACK_RECT[self.note.track][1] + self.owner.pitchToPixels( self.note.cs.pitch ) + width = max( 1, self.owner.ticksToPixels( self.noteDB.getPage( self.note.page).beats, self.note.cs.duration ) ) + if x != self.x or y != self.y or width != self.width: + if parameter != None: # not the first update + xx = min( self.x, x ) + yy = min( self.y, y ) + endxx = max( self.endx, x + width ) + endyy = max( self.y, y ) + 1 + self.x = x + self.endx = x + width + self.y = y + self.width = width + self.owner.invalidate_thumbnail( self.note.page, xx, yy, endxx-xx, endyy-yy ) + else: + self.x = x + self.endx = x + width + self.y = y + self.width = width + self.owner.invalidate_thumbnail( self.note.page, x, y, width, 1 ) + + def draw( self, win, gc, startX, stopX ): + if stopX < self.x: return False # we don't need to draw and no one after us will draw + if startX > self.endx: return True # we don't need to draw, but maybe a later note does + + win.draw_line( gc, self.x, self.y, self.endx, self.y ) + + return True # we drew something + + +class TuneInterface( gtk.EventBox ): + + DRAG_BLOCK = -1 # block other drag events + DRAG_SELECT = 1 + DRAG_DESELECT = 2 + DRAG_MOVE = 3 + + def __init__( self, noteDB, owner, adjustment ): + gtk.EventBox.__init__( self ) + + self.noteDB = noteDB + self.owner = owner + self.adjustment = adjustment + #adjustment.connect( "changed", self.adjustmentChanged ) + adjustment.connect( "value-changed", self.adjustmentValue ) + + self.drawingArea = gtk.DrawingArea() + self.drawingAreaDirty = False # is the drawingArea waiting to draw? + self.add( self.drawingArea ) + self.dirtyRectToAdd = gtk.gdk.Rectangle() # used by the invalidate_rect function + + self.selectedIds = [] + self.displayedPage = -1 + + self.drumIndex = Config.NUMBER_OF_TRACKS-1 + + self.trackRect = Config.THUMBNAIL_TRACK_RECT + self.thumbnail = {} + self.thumbnailDirty = {} + self.thumbnailDirtyRect = {} + self.defaultwin = gtk.gdk.get_default_root_window() # used when creating pixmaps + self.gc = gtk.gdk.GC( self.defaultwin ) + colormap = self.drawingArea.get_colormap() + self.bgColor = colormap.alloc_color( Config.TOOLBAR_BCK_COLOR, True, True ) + self.lineColor = colormap.alloc_color( Config.THUMBNAIL_DRAG_COLOR, True, True ) + self.displayedColor = colormap.alloc_color( Config.THUMBNAIL_DISPLAYED_COLOR, True, True ) + self.selectedColor = colormap.alloc_color( Config.THUMBNAIL_SELECTED_COLOR, True, True ) + + # prepare thumbnail + self.thumbnailBG = [] + self.gc.foreground = self.bgColor + for i in range(4): + pix = gtk.gdk.pixbuf_new_from_file( Config.IMAGE_ROOT+"pageThumbnailBG%d.png"%i ) + self.thumbnailBG.append( gtk.gdk.Pixmap( self.defaultwin, Config.PAGE_THUMBNAIL_WIDTH, Config.PAGE_THUMBNAIL_HEIGHT ) ) + self.thumbnailBG[i].draw_rectangle( self.gc, True, 0, 0, Config.PAGE_THUMBNAIL_WIDTH, Config.PAGE_THUMBNAIL_HEIGHT ) + self.thumbnailBG[i].draw_pixbuf( self.gc, pix, 0, 0, 0, 0, Config.PAGE_THUMBNAIL_WIDTH, Config.PAGE_THUMBNAIL_HEIGHT, gtk.gdk.RGB_DITHER_NONE ) + + # load clipmask + pix = gtk.gdk.pixbuf_new_from_file(Config.IMAGE_ROOT+'pageThumbnailMask.png') + pixels = pix.get_pixels() + stride = pix.get_rowstride() + channels = pix.get_n_channels() + bitmap = "" + byte = 0 + shift = 0 + for j in range(pix.get_height()): + offset = stride*j + for i in range(pix.get_width()): + if pixels[i*channels+offset] != "\0": + byte += 1 << shift + shift += 1 + if shift > 7: + bitmap += "%c" % byte + byte = 0 + shift = 0 + if shift: + bitmap += "%c" % byte + byte = 0 + shift = 0 + self.clipMask = gtk.gdk.bitmap_create_from_data( None, bitmap, pix.get_width(), pix.get_height() ) + self.clearMask = gtk.gdk.Rectangle( 0, 0, 1200, 800 ) + + self.pageOffset = 5 # offset the first page by this + self.dropWidth = 5 # line thickness of the drop head + self.dropWidthDIV2 = self.dropWidth//2 + + self.pixelsPerPitch = float(self.trackRect[0][3]-1)/(Config.MAXIMUM_PITCH - Config.MINIMUM_PITCH) + self.pixelsPerPitchDrum = float(self.trackRect[self.drumIndex][3]-1)/(Config.MAXIMUM_PITCH_DRUM - Config.MINIMUM_PITCH_DRUM ) + self.pixelsPerTick = [0] + [ float(self.trackRect[0][2]-4)/(i*Config.TICKS_PER_BEAT) for i in range(1,Config.MAXIMUM_BEATS+1) ] + + self.alloced = False + self.width = self.baseWidth = self.height = -1 + self.waitingForAlloc = True + self.scrollTo = None + self.clickX = -1 + + self.set_size_request( self.width, self.height ) + + self.button1Down = False + self.dragMode = None + self.dropAt = -1 + self.dropAtX = 0 + + self.visibleX = 0 + self.visibleEndX = 0 + + self.add_events(gtk.gdk.POINTER_MOTION_MASK|gtk.gdk.POINTER_MOTION_HINT_MASK) + + self.connect( "size-allocate", self.size_allocated ) + self.drawingArea.connect( "expose-event", self.draw ) + self.connect( "button-press-event", self.handleButtonPress ) + self.connect( "button-release-event", self.handleButtonRelease ) + self.connect( "motion-notify-event", self.handleMotion ) + + def size_allocated( self, widget, allocation ): + if not self.alloced: + self.baseWidth = allocation.width + self.visibleEndX = self.baseWidth + self.baseHeight = allocation.height + self.alloced = True + self.updateSize() + self.width = allocation.width + self.height = allocation.height + self.drawingArea.set_size_request( self.width, self.height ) + self.clearMask.height = self.height + self.clearMask.width = self.width + + self.pageY = 2 + (self.height-Config.PAGE_THUMBNAIL_HEIGHT)//2 + + if self.scrollTo != None: + if self.scrollTo >= 0: self.adjustment.set_value( self.scrollTo ) + else: self.adjustment.set_value( self.width - self.baseWidth ) + self.scrollTo = None + + self.waitingForAlloc = False + + def adjustmentValue( self, adj ): + self.visibleX = int(adj.value) + self.visibleEndX = self.visibleX + self.baseWidth + + def updateSize( self ): + width = self.noteDB.getPageCount()*Config.PAGE_THUMBNAIL_WIDTH + 5 # add extra 5 for the first page + self.waitingForAlloc = True + if width < self.baseWidth: + self.pageOffset = ( self.baseWidth - width ) // 2 + 5 + else: + self.pageOffset = 5 + + if self.alloced: + self.set_size_request( max( self.baseWidth, width), -1 ) + self.invalidate_rect( self.visibleX, 0, self.baseWidth, self.height ) + + def handleButtonPress( self, widget, event ): + if event.button != 1: + # bring up properties or something + return + + self.button1Down = True + + self.owner.abortPredrawPage() + + ind = int(event.x-self.pageOffset)//Config.PAGE_THUMBNAIL_WIDTH + if ind >= self.noteDB.getPageCount(): + if self.dragMode != self.DRAG_MOVE: + self.dragMode = self.DRAG_BLOCK + return + if ind < 0: ind = 0 + + self.clickX = event.x + + id = self.noteDB.getPageByIndex( ind ) + + if event.type == gtk.gdk._3BUTTON_PRESS: # triple click -> select all + self.owner.displayPage( id ) + self.selectAll() + elif event.type == gtk.gdk._2BUTTON_PRESS: # double click -> exclusive select + self.owner.displayPage( id ) + self.selectPage( id ) + else: + if Config.ModKeys.ctrlDown: + if id in self.selectedIds: # ctrl click, selected page -> remove page from selection + if self.deselectPage( id ): + self.dragMode = self.DRAG_DESELECT + self.dragLastInd = ind + else: + self.dragMode = self.DRAG_SELECT # special case, they clicked on the last selected page and it wasn't deselected + self.dragLastInd = ind + else: # ctrl click, unselected page -> add page to selection (but don't display it) + self.selectPage( id, False ) + self.dragMode = self.DRAG_SELECT + self.dragLastInd = ind + elif id in self.selectedIds: # click, selected page -> display this page but don't change the selection + self.owner.displayPage( id ) + else: # click, unselected page -> exclusive select + self.owner.displayPage( id ) + self.selectPage( id ) + + + self.owner.setContext( CONTEXT.PAGE ) + + def handleButtonRelease( self, widget, event ): + if event.button != 1: + return + + self.button1Down = False + + if self.dragMode == self.DRAG_MOVE: + self.invalidate_rect( self.dropAtX - self.dropWidthDIV2, 0, self.dropWidth, self.height ) # drop head + + if self.dropAt > 0: after = self.noteDB.getPageByIndex( self.dropAt-1 ) + else: after = False + + self.noteDB.movePages( self.selectedIds, after ) + + self.dropAt = -1 + + self.dragMode = None + + def handleMotion( self, widget, event ): + + if event.is_hint: + x, y, state = self.window.get_pointer() + event.x = float(x) + event.y = float(y) + event.state = state + + if self.button1Down: # clicking + if Config.ModKeys.ctrlDown and (self.dragMode == None or self.dragMode == self.DRAG_MOVE): + self.dropAt = -1 + self.dragMode = self.DRAG_SELECT + if event.x >= self.pageOffset: ind = int(event.x-self.pageOffset)//Config.PAGE_THUMBNAIL_WIDTH + else: ind = 0 + self.dragLastInd = ind + + if self.dragMode == self.DRAG_SELECT: # select on drag + if event.x > self.pageOffset: ind = int(event.x-self.pageOffset)//Config.PAGE_THUMBNAIL_WIDTH + else: ind = 0 + pageCount = self.noteDB.getPageCount() + if ind >= pageCount: ind = pageCount-1 + for i in range( min(ind,self.dragLastInd), max(ind,self.dragLastInd)+1): + self.selectPage( self.noteDB.getPageByIndex(i), False ) + self.dragLastInd = ind + elif self.dragMode == self.DRAG_DESELECT: # deselect on drag + if event.x > self.pageOffset: ind = int(event.x-self.pageOffset)//Config.PAGE_THUMBNAIL_WIDTH + else: ind = 0 + pageCount = self.noteDB.getPageCount() + if ind >= pageCount: ind = pageCount-1 + for i in range( min(ind,self.dragLastInd), max(ind,self.dragLastInd)+1): + self.deselectPage( self.noteDB.getPageByIndex(i) ) + self.dragLastInd = ind + elif self.dragMode == None and abs(self.clickX-event.x) > 20: # drag and drop + self.dragMode = self.DRAG_MOVE + + if self.dragMode == self.DRAG_MOVE: + if self.dropAt >= 0: lastX = self.dropAtX + else: lastX = -1 + if event.x > self.pageOffset: self.dropAt = int(event.x-self.pageOffset+Config.PAGE_THUMBNAIL_WIDTH_DIV2)//Config.PAGE_THUMBNAIL_WIDTH + else: self.dropAt = 0 + c = self.noteDB.getPageCount() + if self.dropAt > c: self.dropAt = c + self.dropAtX = self.pageOffset + self.dropAt*Config.PAGE_THUMBNAIL_WIDTH - self.dropWidthDIV2 - 1 + if lastX >= 0 and lastX != self.dropAtX: + if lastX < self.dropAtX: + x = lastX - self.dropWidthDIV2 + w = self.dropAtX - lastX + self.dropWidth + else: + x = self.dropAtX - self.dropWidthDIV2 + w = lastX - self.dropAtX + self.dropWidth + self.invalidate_rect( x, 0, w, self.height ) + elif lastX == -1: + self.invalidate_rect( self.dropAtX-self.dropWidthDIV2, 0, self.dropWidth, self.height ) + + else: # hovering + ind = int(event.x-self.pageOffset)//Config.PAGE_THUMBNAIL_WIDTH + if ind != self.lastPredrawInd and 0 <= ind < self.noteDB.getPageCount(): + id = self.noteDB.getPageByIndex(ind) + if id != self.displayedPage: + self.owner.predrawPage( id ) + self.lastPredrawInd = ind + + + def trackToggled( self, i ): + self.invalidate_rect( self.visibleX, 0, self.baseWidth, self.height ) + + def displayPage( self, id ): + if self.displayedPage == id: return -1 + + self.lastPredrawInd = -1 + + if self.displayedPage != -1: + ind = self.noteDB.getPageIndex( self.displayedPage ) + self.invalidate_rect( self.pageOffset + ind*Config.PAGE_THUMBNAIL_WIDTH, 0, Config.PAGE_THUMBNAIL_WIDTH, self.height ) + + if not self.thumbnail.has_key( id ): + # premptive add + self.thumbnail[id] = gtk.gdk.Pixmap( self.defaultwin, Config.PAGE_THUMBNAIL_WIDTH, Config.PAGE_THUMBNAIL_HEIGHT ) + self.thumbnailDirtyRect[id] = gtk.gdk.Rectangle( 0, 0, Config.PAGE_THUMBNAIL_WIDTH, Config.PAGE_THUMBNAIL_HEIGHT ) + self.thumbnailDirty[id] = True + self.selectPage( id ) + self.updateSize() + + self.displayedPage = id + + if id not in self.selectedIds: + self.selectPage( id ) + + ind = self.noteDB.getPageIndex( id ) + + startX = self.pageOffset + ind*Config.PAGE_THUMBNAIL_WIDTH + stopX = startX + Config.PAGE_THUMBNAIL_WIDTH + + if self.adjustment.value > startX: + scroll = startX + Config.PAGE_THUMBNAIL_WIDTH + Config.PAGE_THUMBNAIL_WIDTH_DIV2 - self.baseWidth + if scroll < 0: scroll = 0 + self.adjustment.set_value( scroll ) + elif self.adjustment.value + self.baseWidth < stopX: + scroll = startX - Config.PAGE_THUMBNAIL_WIDTH_DIV2 + if scroll + self.baseWidth > self.width: + if self.waitingForAlloc: + self.scrollTo = -1 + else: + self.adjustment.set_value( self.width - self.baseWidth ) + else: + if self.waitingForAlloc: + self.scrollTo = scroll + else: + self.adjustment.set_value( scroll ) + + self.invalidate_rect( startX, 0, Config.PAGE_THUMBNAIL_WIDTH, self.height ) + + def selectPage( self, id, exclusive = True ): + if exclusive: + self._clearSelection() + + if id in self.selectedIds: return False # no change + + ind = self.noteDB.getPageIndex( id ) + l = len(self.selectedIds) + i = 0 # in case len(self.selectedIds) == 0 + while i < l: + if self.noteDB.getPageIndex( self.selectedIds[i] ) > ind: break + i += 1 + + self.selectedIds.insert( i, id ) + + self.invalidate_rect( self.pageOffset + ind*Config.PAGE_THUMBNAIL_WIDTH, 0, Config.PAGE_THUMBNAIL_WIDTH, self.height ) + + self.owner.updatePageSelection( self.selectedIds ) + + return True # page added to selection + + def deselectPage( self, id, force = False, skip_redraw = False, noUpdate = False ): + if not id in self.selectedIds: return False # page isn't selected + + if not force: + if len(self.selectedIds) <= 1: return False # don't deselect the last page + + if self.displayedPage == id: + i = self.selectedIds.index(id) + if i == 0: self.owner.displayPage( self.selectedIds[1] ) + else: self.owner.displayPage( self.selectedIds[i-1] ) + + self.selectedIds.remove( id ) + if not skip_redraw: + ind = self.noteDB.getPageIndex( id ) + self.invalidate_rect( self.pageOffset + ind*Config.PAGE_THUMBNAIL_WIDTH, 0, Config.PAGE_THUMBNAIL_WIDTH, self.height ) + + if not noUpdate: + self.owner.updatePageSelection( self.selectedIds ) + + return True # page removed from the selection + + def selectPages( self, which ): + self._clearSelection() + self.selectedIds += which + + self.owner.updatePageSelection( self.selectedIds ) + + def selectAll( self ): + self.selectedIds = self.noteDB.getTune()[:] + self.invalidate_rect( self.visibleX, 0, self.baseWidth, self.height ) + + self.owner.updatePageSelection( self.selectedIds ) + + def _clearSelection( self ): + self.selectedIds = [] + self.invalidate_rect( self.visibleX, 0, self.baseWidth, self.height ) + + def getSelectedIds( self ): + return self.selectedIds + + def getDisplayedIndex( self ): + return self.selectedIds.index( self.displayedPage ) + + def getFirstSelected( self ): + return self.selectedIds[0] + + def getLastSelected( self ): + return self.selectedIds[-1] + + #======================================================= + # NoteDB notifications + + def notifyPageAdd( self, id, at ): + if not self.thumbnail.has_key(id): + self.thumbnail[id] = gtk.gdk.Pixmap( self.defaultwin, Config.PAGE_THUMBNAIL_WIDTH, Config.PAGE_THUMBNAIL_HEIGHT ) + self.thumbnailDirtyRect[id] = gtk.gdk.Rectangle( 0, 0, Config.PAGE_THUMBNAIL_WIDTH, Config.PAGE_THUMBNAIL_HEIGHT ) + self.thumbnailDirty[id] = True + self.selectPage( id ) + self.updateSize() + + def notifyPageDelete( self, which, safe ): + if self.displayedPage in which: + noUpdate = True + else: + noUpdate = False + for id in self.selectedIds: + if id in which: + self.deselectPage( id, True, True, noUpdate ) + for id in which: + del self.thumbnail[id] + del self.thumbnailDirtyRect[id] + del self.thumbnailDirty[id] + if self.displayedPage in which: + self.displayedPage = -1 + self.updateSize() + + def notifyPageDuplicate( self, new, at ): + for id in new: + self.thumbnail[new[id]] = gtk.gdk.Pixmap( self.defaultwin, Config.PAGE_THUMBNAIL_WIDTH, Config.PAGE_THUMBNAIL_HEIGHT ) + self.thumbnailDirtyRect[new[id]] = gtk.gdk.Rectangle( 0, 0, Config.PAGE_THUMBNAIL_WIDTH, Config.PAGE_THUMBNAIL_HEIGHT ) + self.thumbnailDirty[new[id]] = True + self.updateSize() + + def notifyPageMove( self, which, low, high ): + self.invalidate_rect( self.visibleX, 0, self.baseWidth, self.height ) + + def notifyPageUpdate( self, page, parameter, value ): + if parameter == PARAMETER.PAGE_BEATS: + notes = self.noteDB.getNotesByPage( page, self ) + for note in notes: + note.updateParameter( -1, -1 ) # force update transform + + elif parameter == PARAMETER.PAGE_COLOR: + self.invalidate_thumbnail( page, 0, 0, Config.PAGE_THUMBNAIL_WIDTH, Config.PAGE_THUMBNAIL_HEIGHT ) + + #======================================================= + # Drawing + + def drawThumbnail( self, id, pixmap, rect ): + startX = rect.x + startY = rect.y + stopX = rect.x + rect.width + stopY = rect.y + rect.height + + # draw background + pixmap.draw_drawable( self.gc, self.thumbnailBG[self.noteDB.getPage(id).color], startX, startY, startX, startY, rect.width, rect.height+1 ) + + # draw regular tracks + self.gc.foreground = self.lineColor + self.gc.set_line_attributes( 1, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_BUTT, gtk.gdk.JOIN_MITER ) + for i in range(self.drumIndex): + if startY >= self.trackRect[i+1][1]: continue + if stopY < self.trackRect[i][1]: break + + # draw notes + notes = self.noteDB.getNotesByTrack( id, i, self ) + for n in range( len(notes) ): + if not notes[n].draw( pixmap, self.gc, startX, stopX ): break + # drum track + if stopY > self.trackRect[self.drumIndex][0]: + # draw notes + notes = self.noteDB.getNotesByTrack( id, self.drumIndex, self ) + for n in range( len(notes) ): + if not notes[n].draw( pixmap, self.gc, startX, stopX ): break + + self.thumbnailDirty[id] = False + + + def draw( self, drawingArea, event ): + + startX = event.area.x + startY = event.area.y + stopX = event.area.x + event.area.width + stopY = event.area.y + event.area.height + + self.gc.set_clip_rectangle( self.clearMask ) + + # draw background + self.gc.foreground = self.bgColor + drawingArea.window.draw_rectangle( self.gc, True, startX, startY, event.area.width, event.area.height ) + + tracks = [ self.owner.getTrackSelected(i) for i in range(Config.NUMBER_OF_TRACKS) ] + + # draw pages + self.gc.set_clip_mask( self.clipMask ) + + x = self.pageOffset + endx = x + Config.PAGE_THUMBNAIL_WIDTH + for pageId in self.noteDB.getTune(): + if endx < startX: + x = endx + endx += Config.PAGE_THUMBNAIL_WIDTH + continue + if x > stopX: break + + # draw thumbnail + if self.thumbnailDirty[pageId]: + self.gc.set_clip_origin( 0, 0 ) + self.drawThumbnail( pageId, self.thumbnail[pageId], self.thumbnailDirtyRect[pageId] ) + self.gc.set_clip_origin( x, self.pageY ) + drawingArea.window.draw_drawable( self.gc, self.thumbnail[pageId], 0, 0, x, self.pageY, Config.PAGE_THUMBNAIL_WIDTH, Config.PAGE_THUMBNAIL_HEIGHT ) + + # draw border if necessary + if pageId == self.displayedPage: # displayed page border + self.gc.set_function( gtk.gdk.INVERT ) + for i in range(Config.NUMBER_OF_TRACKS): + if tracks[i]: + drawingArea.window.draw_rectangle( self.gc, True, x + self.trackRect[i][0], self.pageY + self.trackRect[i][1], self.trackRect[i][2], self.trackRect[i][3] ) + self.gc.set_function( gtk.gdk.COPY ) + self.gc.foreground = self.displayedColor + self.gc.set_clip_origin( x - Config.PAGE_THUMBNAIL_WIDTH, self.pageY ) + drawingArea.window.draw_rectangle( self.gc, True, x, self.pageY, Config.PAGE_THUMBNAIL_WIDTH, Config.PAGE_THUMBNAIL_HEIGHT ) + elif pageId in self.selectedIds: # selected page border + self.gc.set_function( gtk.gdk.INVERT ) + for i in range(Config.NUMBER_OF_TRACKS): + if tracks[i]: + drawingArea.window.draw_rectangle( self.gc, True, x + self.trackRect[i][0], self.pageY + self.trackRect[i][1], self.trackRect[i][2], self.trackRect[i][3] ) + self.gc.set_function( gtk.gdk.COPY ) + self.gc.foreground = self.selectedColor + self.gc.set_clip_origin( x - Config.PAGE_THUMBNAIL_WIDTH, self.pageY ) + drawingArea.window.draw_rectangle( self.gc, True, x, self.pageY, Config.PAGE_THUMBNAIL_WIDTH, Config.PAGE_THUMBNAIL_HEIGHT ) + + x += Config.PAGE_THUMBNAIL_WIDTH + + # draw drop marker + if self.dropAt >= 0: + self.gc.set_clip_rectangle( self.clearMask ) + self.gc.set_line_attributes( self.dropWidth, gtk.gdk.LINE_SOLID, gtk.gdk.CAP_ROUND, gtk.gdk.JOIN_MITER ) + self.gc.foreground = self.lineColor + drawingArea.window.draw_line( self.gc, self.dropAtX, self.pageY+2, self.dropAtX, self.pageY+Config.PAGE_THUMBNAIL_HEIGHT-4 ) + + def invalidate_rect( self, x, y, width, height ): + if self.alloced == False: return + if x < self.visibleX: x = self.visibleX + if x + width > self.visibleEndX: width = self.visibleEndX - x + if width <= 0: return + + self.dirtyRectToAdd.x = x + self.dirtyRectToAdd.y = y + self.dirtyRectToAdd.width = width + self.dirtyRectToAdd.height = height + if self.drawingArea.window: + self.drawingArea.window.invalidate_rect( self.dirtyRectToAdd, True ) + self.drawingAreaDirty = True + + def invalidate_thumbnail( self, id, x, y, width, height ): + if not self.thumbnailDirty[id]: + self.thumbnailDirtyRect[id].x = x + self.thumbnailDirtyRect[id].y = y + self.thumbnailDirtyRect[id].width = width + self.thumbnailDirtyRect[id].height = height + self.thumbnailDirty[id] = True + else: + self.dirtyRectToAdd.x = x + self.dirtyRectToAdd.y = y + self.dirtyRectToAdd.width = width + self.dirtyRectToAdd.height = height + self.thumbnailDirtyRect[id] = self.thumbnailDirtyRect[id].union( self.dirtyRectToAdd ) + + ind = self.noteDB.getPageIndex( id ) + self.invalidate_rect( self.pageOffset + ind*Config.PAGE_THUMBNAIL_WIDTH, 0, Config.PAGE_THUMBNAIL_WIDTH, self.height ) + + def ticksToPixels( self, beats, ticks ): + return int(round( ticks * self.pixelsPerTick[beats] )) + def pitchToPixels( self, pitch ): + return int(round( ( Config.MAXIMUM_PITCH - pitch ) * self.pixelsPerPitch )) + def pitchToPixelsDrum( self, pitch ): + return int(round( ( Config.MAXIMUM_PITCH_DRUM - pitch ) * self.pixelsPerPitchDrum )) diff --git a/Edit/__init__.py b/Edit/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Edit/__init__.py diff --git a/Edit/rm/BackgroundView.py b/Edit/rm/BackgroundView.py new file mode 100644 index 0000000..ff6e75f --- /dev/null +++ b/Edit/rm/BackgroundView.py @@ -0,0 +1,501 @@ +import pygtk +pygtk.require( '2.0' ) +import gtk + +from math import floor + +from Framework.Constants import Constants +from GUI.GUIConstants import GUIConstants +from GUI.Core.NoteParametersWindow import NoteParametersWindow + +from Framework.Core.Profiler import TP + +class SELECTNOTES: + ALL = -1 + NONE = 0 + ADD = 1 + REMOVE = 2 + EXCLUSIVE = 3 + +#------------------------------------------------------------- +# This is a TEMPORARY implementaion of the BackgroundView, +# it was written quickly to get track selections working +#------------------------------------------------------------- + +# TODO: Do I really have to subclass gtk.EventBox to get the button-press-event? +# (I wasn't getting it subclassing directly from DrawingArea) +class BackgroundView( gtk.EventBox ): + #----------------------------------- + # initialization + #----------------------------------- + def __init__( self, trackIDs, selectedTrackIDs, selectionChangedCallback, mutedTrackIDs, beatsPerPageAdjustment, trackDictionary, selectedPageIDs, updatePageCallback ): + gtk.EventBox.__init__( self ) + + self.drawingArea = gtk.DrawingArea() + self.add( self.drawingArea ) + + self.sizeInitialized = False + + self.trackViews = {} + self.trackIDs = trackIDs + self.selectedTrackIDs = selectedTrackIDs + self.selectionChangedCallback = selectionChangedCallback + self.mutedTrackIDs = mutedTrackIDs + self.beatsPerPageAdjustment = beatsPerPageAdjustment + self.trackDictionary = trackDictionary + self.selectedPageIDs = selectedPageIDs + self.updatePageCallback = updatePageCallback + + self.curAction = False # stores the current mouse action + self.curActionObject = False # stores the object that in handling the action + + self.buttonPressCount = 1 # used on release events to indicate double/triple releases + self.clickLoc = [0,0] # location of the last click + self.marqueeLoc = False # current drag location of the marquee + + self.drawingArea.connect( "expose-event", self.draw ) + self.connect( "button-press-event", self.handleButtonPress ) + self.connect( "button-release-event", self.handleButtonRelease ) + self.connect( "motion-notify-event", self.handleMotion ) + + #----------------------------------- + # access methods + #----------------------------------- + def getTrackRect( self, trackID ): + return gtk.gdk.Rectangle( GUIConstants.BORDER_SIZE, + self.getTrackYLocation( trackID ), + self.getTrackWidth(), + self.getTrackHeight() ) + + def getTrackWidth( self ): + return self.get_allocation().width - 2 * ( GUIConstants.BORDER_SIZE + 2 ) + + def getFullHeight( self ): + return int( floor( self.get_allocation().height / len( self.trackIDs ) ) ) + + def getTrackHeight( self ): + return int( self.getFullHeight() - 2 * self.getTrackSpacing() ) + + #TODO-> trackIDs should probably be ordered! + # we're just using trackID as an index here (this will only work until you can remove tracks) + def getTrackYLocation( self, trackID ): + if self.getTrackHeight() < 0: + return 0 + else: + trackIndex = trackID + + trackHeight = int( floor( self.get_allocation().height / len( self.trackIDs ) ) ) + trackBackgroundYValue = trackHeight * trackIndex + return trackBackgroundYValue + GUIConstants.BORDER_SIZE + + def getTrackSpacing( self ): + return GUIConstants.TRACK_SPACING + + #----------------------------------- + # callback methods + #----------------------------------- + def set_size_request( self, width, height ): + self.sizeInitialized = True + self.drawingArea.set_size_request( width, height ) + self.height = height + self.width = width + + numTracks = len(self.trackIDs) + trackSpacing = self.getTrackSpacing() + if numTracks: self.trackHeight = int( floor( (height - trackSpacing*(numTracks-1)) / numTracks ) ) + else: self.trackHeight = 1 + self.trackWidth = width + + trackCount = 0 + for trackID in self.trackIDs: + self.trackViews[trackID].set_size_request( self.trackWidth, self.trackHeight ) + self.trackViews[trackID].setPositionOffset( (0, trackCount*(self.trackHeight+trackSpacing)) ) + trackCount += 1 + + def setCurrentTracks( self, trackViews ): + + oldLen = len(self.trackViews) + + if oldLen and trackViews != self.trackViews: self.clearSelectedNotes() # clear all the currently selected notes + + self.trackViews = trackViews + + numTracks = len(self.trackViews) + if oldLen != numTracks and self.sizeInitialized: + trackSpacing = self.getTrackSpacing() + if numTracks: self.trackHeight = int( floor( (self.height - trackSpacing*(numTracks-1)) / numTracks ) ) + else: self.trackHeight = 1 + trackCount = 0 + for trackID in self.trackIDs: + self.trackViews[trackID].set_size_request( self.trackWidth, self.trackHeight ) + self.trackViews[trackID].setPositionOffset( (0, trackCount*(self.trackHeight+trackSpacing)) ) + trackCount += 1 + + self.dirty() + + + def getNoteParameters( self ): + for trackID in self.selectedTrackIDs: + for pageID in self.selectedPageIDs: + for note in self.trackDictionary[ trackID ][ pageID ]: + newPitch = note.pitch + self.noteParameters.pitchAdjust.value + newAmplitude = note.amplitude * self.noteParameters.amplitudeAdjust.value + newPan = self.noteParameters.panAdjust.value + newReverbSend = note.reverbSend * self.noteParameters.reverbSendAdjust.value + newAttack = self.noteParameters.attackAdjust.value + newDecay = self.noteParameters.decayAdjust.value + newFilterType = self.noteParameters.filterType + newFilterCutoff = self.noteParameters.filterCutoff + newTied = self.noteParameters.tied + newOverlap = self.noteParameters.overlap + + note.pitch = self.noteParametersBoundaries( newPitch, note.pitch, Constants.MINIMUM_PITCH, Constants.MAXIMUM_PITCH ) + note.amplitude = self.noteParametersBoundaries( newAmplitude, note.amplitude, Constants.MINIMUM_AMPLITUDE, Constants.MAXIMUM_AMPLITUDE ) + note.reverbSend = self.noteParametersBoundaries( newReverbSend, note.reverbSend, Constants.MINIMUM_AMPLITUDE, + Constants.MAXIMUM_AMPLITUDE ) + if newPan != note.pan: + note.pan = newPan + + if newAttack != note.attack: + note.attack = newAttack + + if newDecay != note.decay: + note.decay = newDecay + + if newFilterType != note.filterType: + note.filterType = newFilterType + + if newFilterCutoff != note.filterCutoff: + note.filterCutoff = newFilterCutoff + + if newTied != note.tied: + note.tied = newTied + + if newOverlap != note.overlap: + note.overlap = newOverlap + + self.updatePageCallback() + + def noteParametersBoundaries( self, newValue, noteValue, minBoundary, maxBoundary ): + if newValue != noteValue: + if newValue >= minBoundary and newValue <= maxBoundary: + return newValue + elif newValue < minBoundary: + return minBoundary + elif newValue > maxBoundary: + return maxBoundary + else: + return noteValue + + #----------------------------------- + # action and event methods + #----------------------------------- + def setCurrentAction( self, action, obj ): + if self.curAction: + print "BackgroundView - Action already in progress!" + + self.curAction = action + self.curActionObject = obj + + if action == "note-drag-onset": self.updateDragLimits() + elif action == "note-drag-duration": self.updateDragLimits() + elif action == "note-drag-pitch": self.updateDragLimits() + + def doneCurrentAction( self ): + if self.curAction == "note-drag-onset": self.doneNoteDrag() + elif self.curAction == "note-drag-duration": self.doneNoteDrag() + elif self.curAction == "note-drag-pitch": self.doneNoteDrag() + + self.curAction = False + self.curActionObject = False + + def toggleTrack( self, trackID, exclusive ): + if exclusive: + self.selectedTrackIDs.clear() + self.selectedTrackIDs.add( trackID ) + else: + if trackID in self.selectedTrackIDs: + self.selectedTrackIDs.discard( trackID ) + else: + self.selectedTrackIDs.add( trackID ) + + def selectionChanged( self ): + if self.curAction == "note-drag-onset": self.updateDragLimits() + elif self.curAction == "note-drag-duration": self.updateDragLimits() + elif self.curAction == "note-drag-pitch": self.updateDragLimits() + self.dirty() + + def selectNotesByBar( self, selID, startX, stopX ): + beatCount = int(round( self.beatsPerPageAdjustment.value, 0 )) + for trackID in self.trackIDs: + if trackID == selID: + notes = self.trackViews[trackID].getNotesByBar( beatCount, startX, stopX ) + self.trackViews[trackID].selectNotes( SELECTNOTES.EXCLUSIVE, notes ) + else: + self.trackViews[trackID].selectNotes( SELECTNOTES.NONE, [] ) + self.selectionChanged() + + def selectNotesByTrack( self, selID ): + for trackID in self.trackIDs: + if trackID == selID: self.trackViews[trackID].selectNotes( SELECTNOTES.ALL, [] ) + else: self.trackViews[trackID].selectNotes( SELECTNOTES.NONE, [] ) + self.selectionChanged() + + def selectNotes( self, noteDic ): + for trackID in self.trackIDs: + if trackID in noteDic: self.trackViews[trackID].selectNotes( SELECTNOTES.EXCLUSIVE, noteDic[trackID] ) + else: self.trackViews[trackID].selectNotes( SELECTNOTES.NONE, [] ) + self.selectionChanged() + + def addNotesToSelection( self, noteDic ): + for trackID in self.trackIDs: + if trackID in noteDic: self.trackViews[trackID].selectNotes( SELECTNOTES.ADD, noteDic[trackID] ) + self.selectionChanged() + + def deselectNotes( self, noteDic ): + for trackID in self.trackIDs: + if trackID in noteDic: self.trackViews[trackID].selectNotes( SELECTNOTES.REMOVE, noteDic[trackID] ) + self.selectionChanged() + + def clearSelectedNotes( self ): + for trackID in self.trackIDs: + self.trackViews[trackID].selectNotes( SELECTNOTES.NONE, [] ) + self.selectionChanged() + + def updateDragLimits( self ): + self.dragLimits = [ [-9999,9999], [-9999,9999], [-9999,9999] ] # initialize to big numbers! + for trackID in self.trackIDs: + self.trackViews[trackID].updateDragLimits( self.dragLimits ) + + def noteDragOnset( self, event ): + dx = event.x - self.clickLoc[0] + dx = min( self.dragLimits[0][1], max( self.dragLimits[0][0], dx ) ) + dy = 0 + dw = 0 + + for trackID in self.trackIDs: + self.trackViews[trackID].noteDrag( self, dx, dy, dw ) + self.dirty() + + def noteDragDuration( self, event ): + dx = 0 + dy = 0 + dw = event.x - self.clickLoc[0] + dw = min( self.dragLimits[2][1], max( self.dragLimits[2][0], dw ) ) + + for trackID in self.trackIDs: + self.trackViews[trackID].noteDrag( self, dx, dy, dw ) + self.dirty() + + def noteDragPitch( self, event ): + dx = 0 + dy = event.y - self.clickLoc[1] + dy = min( self.dragLimits[1][1], max( self.dragLimits[1][0], dy ) ) + dw = 0 + + for trackID in self.trackIDs: + self.trackViews[trackID].noteDrag( self, dx, dy, dw ) + self.dirty() + + def doneNoteDrag( self ): + for trackID in self.trackIDs: + self.trackViews[trackID].doneNoteDrag( self ) + + def updateMarquee( self, event ): + self.marqueeLoc = [ event.x, event.y ] + parentRect = self.get_allocation() + if self.marqueeLoc[0] < 0: self.marqueeLoc[0] = 0 + elif self.marqueeLoc[0] > parentRect.width: self.marqueeLoc[0] = parentRect.width + if self.marqueeLoc[1] < 0: self.marqueeLoc[1] = 0 + elif self.marqueeLoc[1] > parentRect.height: self.marqueeLoc[1] = parentRect.height + + self.dirty() + + def doneMarquee( self, event ): + if self.marqueeLoc: + start = [ min(self.clickLoc[0],self.marqueeLoc[0]), \ + min(self.clickLoc[1],self.marqueeLoc[1]) ] + stop = [ max(self.clickLoc[0],self.marqueeLoc[0]), \ + max(self.clickLoc[1],self.marqueeLoc[1]) ] + + select = {} + + trackSpacing = self.getTrackSpacing() + trackTop = 0 + for trackID in self.trackIDs: + notes = self.trackViews[trackID].handleMarqueeSelect( self, start, stop ) + if notes: select[trackID] = notes + trackTop += self.trackHeight + trackSpacing + if trackTop > stop[1]: break + + self.selectNotes( select ) + + self.marqueeLoc = False + self.doneCurrentAction() + + self.dirty() + + def handleButtonPress( self, drawingArea, event ): + + TP.ProfileBegin( "BV::handleButtonPress" ) + + if event.type == gtk.gdk._2BUTTON_PRESS: self.buttonPressCount = 2 + elif event.type == gtk.gdk._3BUTTON_PRESS: self.buttonPressCount = 3 + else: self.buttonPressCount = 1 + + self.clickLoc = [ event.x, event.y ] + + trackSpacing = self.getTrackSpacing() + trackTop = 0 + for trackID in self.trackIDs: + handled = self.trackViews[trackID].handleButtonPress( self, event ) + trackTop += self.trackHeight + trackSpacing + if handled or trackTop > event.y: break + + if handled: + if not self.curAction: self.curAction = True # it was handled maybe no action was declared, set curAction to True anyway + TP.ProfileEnd( "BV::handleButtonPress" ) + return + + if event.button == 3: + self.noteParameters = NoteParametersWindow( self.trackDictionary, self.getNoteParameters ) + self.setCurrentAction( "noteParameters", False ) + + TP.ProfileEnd( "BV::handleButtonPress" ) + + + def handleButtonRelease( self, drawingArea, event ): + TP.ProfileBegin( "BV::handleButtonRelease" ) + + if not self.curAction: #do track selection stuff here so that we can also handle marquee selection + trackSpacing = self.getTrackSpacing() + trackTop = 0 + for trackID in self.trackIDs: + handled = self.trackViews[trackID].handleButtonRelease( self, event, self.buttonPressCount ) + trackTop += self.trackHeight + trackSpacing + if handled or trackTop > event.y: break + + if handled: self.dirty() + + TP.ProfileEnd( "BV::handleButtonRelease" ) + return + + if not self.curActionObject: # there was no real action to carry out + self.curAction = False + TP.ProfileEnd( "BV::handleButtonRelease" ) + return + + if self.curActionObject != self: + if self.curActionObject.handleButtonRelease( self, event, self.buttonPressCount ): + self.dirty() + TP.ProfileEnd( "BV::handleButtonRelease" ) + return + + + # we're doing the action ourselves + + if self.curAction == "marquee": self.doneMarquee( event ) + + TP.ProfileEnd( "BV::handleButtonRelease" ) + return + + def handleMotion( self, drawingArea, event ): + TP.ProfileBegin( "BV::handleMotion" ) + + if not self.curAction: # no action is in progress yet we're dragging, start a marquee + self.setCurrentAction( "marquee", self ) + + if self.curAction == "note-drag-onset": + self.noteDragOnset( event ) + TP.ProfileEnd( "BV::handleMotion" ) + return + + if self.curAction == "note-drag-duration": + self.noteDragDuration( event ) + TP.ProfileEnd( "BV::handleMotion" ) + return + + if self.curAction == "note-drag-pitch": + self.noteDragPitch( event ) + TP.ProfileEnd( "BV::handleMotion" ) + return + + # we're doing the action ourselves + + if self.curAction == "marquee": self.updateMarquee( event ) + + TP.ProfileEnd( "BV::handleMotion" ) + return + + def TEMPOLDSTUFF(self): + + #TODO change this to accomodate the space between tracks + trackHeight = ( drawingArea.get_allocation().height - 1 ) / len( self.trackIDs ) + trackID = int( floor( event.y / trackHeight ) ) + + if event.type == gtk.gdk.BUTTON_PRESS: + #single click toggles track selection + if trackID in self.selectedTrackIDs: + self.selectedTrackIDs.discard( trackID ) + else: + self.selectedTrackIDs.add( trackID ) + elif event.type == gtk.gdk._2BUTTON_PRESS: + #double click selects a single track + self.selectedTrackIDs.clear() + self.selectedTrackIDs.add( trackID ) + + self.drawingArea.queue_draw() + self.selectionChangedCallback() + if event.button == 3: + self.noteParameters = NoteParametersWindow( self.trackDictionary, self.getNoteParameters ) + + #----------------------------------- + # drawing methods + #----------------------------------- + def draw( self, drawingArea, event ): + TP.ProfileBegin( "BackgroundView::draw" ) + + context = drawingArea.window.cairo_create() + context.set_antialias(0) # I don't know what to set this to to turn it off, and it doesn't seem to work anyway!? + + #parentRect = self.get_allocation() + + beatCount = int(round( self.beatsPerPageAdjustment.value, 0 )) + + for trackID in self.trackIDs: + self.trackViews[trackID].draw( context, + beatCount, + trackID in self.selectedTrackIDs ) + + # if self.curAction == "note-drag": # draw a cross at clickLoc + # lineW = 1 + # context.set_line_width( lineW ) + # lineWDIV2 = lineW/2.0 + # context.set_source_rgb( 1, 1, 1 ) + + # context.move_to( self.clickLoc[0] + lineWDIV2 - 3, self.clickLoc[1] + lineWDIV2 ) + # context.rel_line_to( 6, 0 ) + # context.stroke() + # context.move_to( self.clickLoc[0] + lineWDIV2, self.clickLoc[1] + lineWDIV2 - 3) + # context.rel_line_to( 0, 6 ) + # context.stroke() + + if self.marqueeLoc: # draw the selection rect + lineW = 1 + context.set_line_width( lineW ) + lineWDIV2 = lineW/2.0 + + context.move_to( self.clickLoc[0] + lineWDIV2, self.clickLoc[1] + lineWDIV2 ) + context.line_to( self.marqueeLoc[0] + lineWDIV2, self.clickLoc[1] + lineWDIV2 ) + context.line_to( self.marqueeLoc[0] + lineWDIV2, self.marqueeLoc[1] + lineWDIV2 ) + context.line_to( self.clickLoc[0] + lineWDIV2, self.marqueeLoc[1] + lineWDIV2 ) + context.close_path() + context.set_source_rgb( 1, 1, 1 ) + context.stroke() + + TP.ProfileEnd( "BackgroundView::draw" ) + + def dirty( self ): + self.queue_draw() + + diff --git a/Edit/rm/KeyboardInput.py b/Edit/rm/KeyboardInput.py new file mode 100644 index 0000000..d1a0d83 --- /dev/null +++ b/Edit/rm/KeyboardInput.py @@ -0,0 +1,97 @@ +import pygtk +pygtk.require( '2.0' ) +import gtk + +from Framework import Note +from Framework.CSound.CSoundConstants import CSoundConstants +from Framework.Generation.GenerationConstants import GenerationConstants +from GUI.Core.KeyMapping import KEY_MAP + + +class KeyboardInput: + def __init__( self , getCurrentTick , getTrackInstruments , getTrackDictionary , getSelectedTrackIDs , mainWindowUpdateCallback , pagePlayerUpdateCallback , getCurrentPageIDCallback ): + self.active = False + self.record = False + self.monophonic = False + self.key_dict = dict() + + self.getCurrentTick = getCurrentTick + self.getTrackInstruments = getTrackInstruments + self.getTrackDictionary = getTrackDictionary + self.getSelectedTrackIDs = getSelectedTrackIDs + self.mainWindowUpdateCallback = mainWindowUpdateCallback + self.pagePlayerUpdateCallback = pagePlayerUpdateCallback + self.getCurrentPageIDCallback = getCurrentPageIDCallback + + def onKeyPress(self,widget,event): + if not self.active: + return + if self.record: + self.monophonic = False + + key = event.hardware_keycode + # If the key is already in the dictionnary, exit function (to avoir key repeats) + if self.key_dict.has_key(key): + return + # Assign on which track the note will be created according to the number of keys pressed + track = len(self.key_dict)+10 + if self.monophonic: + track = 10 + # If the pressed key is in the keymap + if KEY_MAP.has_key(key): + # CsoundNote parameters + onset = self.getCurrentTick() + pitch = KEY_MAP[key] + duration = -1 + instrument = self.getTrackInstruments()[0] + # get instrument from top selected track if a track is selected + if self.getSelectedTrackIDs(): + instrument = self.getTrackInstruments()[min(self.getSelectedTrackIDs())] + + if instrument == 'drum1kit': + if GenerationConstants.DRUMPITCH.has_key( pitch ): + instrument = CSoundConstants.DRUM1INSTRUMENTS[ GenerationConstants.DRUMPITCH[ pitch ] ] + else: + instrument = CSoundConstants.DRUM1INSTRUMENTS[ pitch ] + pitch = 36 + duration = 100 + + if CSoundConstants.INSTRUMENTS[instrument].csoundInstrumentID == 102: + duration = 100 + + # Create and play the note + self.key_dict[key] = Note.note_new(onset = 0, + pitch = pitch, + amplitude = 1, + pan = 0.5, + duration = duration, + trackID = track, + fullDuration = False, + instrument = instrument, + instrumentFlag = instrument) + Note.note_play(self.key_dict[key]) + + def onKeyRelease(self,widget,event): + if not self.active: + return + key = event.hardware_keycode + + if KEY_MAP.has_key(key): + self.key_dict[key]['duration'] = 0 + self.key_dict[key]['amplitude'] = 0 + self.key_dict[key]['dirty'] = True + Note.note_play(self.key_dict[key]) + self.key_dict[key]['duration'] = self.getCurrentTick() - self.key_dict[key]['onset'] + #print "onset",self.key_dict[key].onset + #print "dur",self.key_dict[key].duration + if self.record and len( self.getSelectedTrackIDs() ) != 0: + self.key_dict[key]['amplitude'] = 1 + self.getTrackDictionary()[min(self.getSelectedTrackIDs())][self.getCurrentPageIDCallback()].append(self.key_dict[key]) + self.mainWindowUpdateCallback() + self.pagePlayerUpdateCallback() + del self.key_dict[key] + + + def onButtonPress(self,widget,event): + pass + diff --git a/Edit/rm/NoteView.py b/Edit/rm/NoteView.py new file mode 100644 index 0000000..ac139a1 --- /dev/null +++ b/Edit/rm/NoteView.py @@ -0,0 +1,253 @@ +import pygtk +pygtk.require( '2.0' ) +import gtk + +from Framework.Constants import Constants +from Framework.CSound.CSoundConstants import CSoundConstants +from GUI.GUIConstants import GUIConstants +from GUI.Core.NoteParametersWindow import NoteParametersWindow + +from BackgroundView import SELECTNOTES + +#---------------------------------------------------------------------- +# TODO: currently we are only using CSoundNotes, +# - this should updated to handle generic Note objects +#---------------------------------------------------------------------- + +#---------------------------------------------------------------------- +# A view for the (CSound)Note class +#---------------------------------------------------------------------- +class NoteView: + #----------------------------------- + # initialization + # TODO: not sure if passing in beatsPerPageAdjustment is the best way to go about it + #----------------------------------- + def __init__( self, note, track, beatsPerPageAdjustment ): + self.note = note + + self.track = track + self.beatsPerPageAdjustment = beatsPerPageAdjustment + self.posOffset = (0,0) + + self.baseOnset = self.basePitch = self.baseDuration = self.lastDragX = self.lastDragY = self.lastDragW = 0 # note dragging properties + + self.sampleNote = None + + self.parentSize = False + + self.selected = False + self.potentialDeselect = False + + + def getNoteParameters( self ): + self.note.pitch = self.noteParameters.pitchAdjust.value + self.note.amplitude = self.noteParameters.amplitudeAdjust.value + self.note.pan = self.noteParameters.panAdjust.value + self.note.attack = self.noteParameters.attackAdjust.value + self.note.decay = self.noteParameters.decayAdjust.value + self.note.reverbSend = self.noteParameters.reverbSendAdjust.value + self.note.filterType = self.noteParameters.filterType + self.note.filterCutoff = self.noteParameters.filterCutoff + self.note.tied = self.noteParameters.tied + self.note.overlap = self.noteParameters.overlap + + def handleButtonPress( self, emitter, event ): + eX = event.x - self.x + eY = event.y - self.y + + if eX < 0 or eX > self.width \ + or eY < 0 or eY > self.height: + return False + + if event.button == 3: + self.noteParameters = NoteParametersWindow( self.note, self.getNoteParameters ) + return True + + if event.type == gtk.gdk._2BUTTON_PRESS: # select bar + self.potentialDeselect = False + emitter.selectNotesByBar( self.track.getID(), self.x, self.x+self.width ) + elif event.type == gtk.gdk._3BUTTON_PRESS: # select track + self.potentialDeselect = False + emitter.selectNotesByTrack( self.track.getID() ) + else: + if self.getSelected(): # we already selected, might want to delected + self.potentialDeselect = True + else: + emitter.selectNotes( { self.track.getID(): [ self ] } ) + self.updateSampleNote( self.note.pitch ) + + percent = eX/self.width + if percent < 0.3: emitter.setCurrentAction( "note-drag-onset", self ) + elif percent > 0.7: emitter.setCurrentAction( "note-drag-duration", self ) + else: emitter.setCurrentAction( "note-drag-pitch", self ) + + + emitter.dirty() + + return True + + def handleButtonRelease( self, emitter, event, buttonPressCount ): + + if self.potentialDeselect: + self.potentialDeselect = False + emitter.deselectNotes( { self.track.getID(): [ self ] } ) + + self.clearSampleNote() + + emitter.doneCurrentAction() + + return True + + def noteDrag( self, emitter, dx, dy, dw ): + self.potentialDeselect = False + + ticksPerPixel = (round( self.beatsPerPageAdjustment.value, 0 ) * Constants.TICKS_PER_BEAT) / self.parentSize[0] + stepPerPixel = (Constants.NUMBER_OF_POSSIBLE_PITCHES-1) / (self.parentSize[1] - self.height) + pitchPerStep = (Constants.MAXIMUM_PITCH-Constants.MINIMUM_PITCH)/(Constants.NUMBER_OF_POSSIBLE_PITCHES-1) + + if dx != self.lastDragX: + self.lastDragX = dx + self.note.onset = self.baseOnset + int(dx*ticksPerPixel) + + if dy != self.lastDragY: + self.lastDragY = dy + newPitch = self.basePitch + round(-dy*stepPerPixel)*pitchPerStep + self.note.pitch = newPitch + self.updateSampleNote( newPitch ) + + if dw != self.lastDragW: + self.lastDragW = dw + self.note.duration = self.baseDuration + int(dw*ticksPerPixel) + + self.updateTransform( False ) + + def doneNoteDrag( self, emitter ): + self.baseOnset = self.note.onset + self.basePitch = self.note.pitch + self.baseDuration = self.note.duration + + self.lastDragX = 0 + self.lastDragY = 0 + self.lastDragW = 0 + + self.clearSampleNote() + + + def handleMarqueeSelect( self, emitter, start, stop ): + intersectionY = [ max(start[1],self.y), min(stop[1],self.y+self.height) ] + if intersectionY[0] > intersectionY[1]: + return False + + intersectionX = [ max(start[0],self.x), min(stop[0],self.x+self.width) ] + if intersectionX[0] > intersectionX[1]: + return False + + return True + + #----------------------------------- + # draw + #----------------------------------- + + def setPositionOffset( self, offset ): + self.posOffset = offset + if self.parentSize: self.updateTransform( False ) + + def draw( self, context ): + lineW = GUIConstants.NOTE_BORDER_SIZE + lineWDIV2 = lineW/2.0 + context.set_line_width( lineW ) + + context.move_to( self.x + lineWDIV2, self.y + lineWDIV2 ) + context.rel_line_to( self.width - lineW, 0 ) + context.rel_line_to( 0, self.height - lineW ) + context.rel_line_to( -self.width + lineW, 0 ) + context.close_path() + + #background + colour = 1 - ( ( self.note.amplitude * 0.7 ) + 0.3 ) + context.set_source_rgb( colour, colour, colour ) + context.fill_preserve() + + #border + if self.selected: context.set_source_rgb( 1, 1, 1 ) + else: context.set_source_rgb( 0, 0, 0 ) + context.stroke() + + #----------------------------------- + # update + #----------------------------------- + + def updateTransform( self, parentSize ): + if parentSize: self.parentSize = parentSize + self.width = int( self.parentSize[0] * self.note.duration / (round( self.beatsPerPageAdjustment.value, 0 ) * Constants.TICKS_PER_BEAT) ) + self.height = round( max( GUIConstants.MINIMUM_NOTE_HEIGHT, self.parentSize[1] / (Constants.NUMBER_OF_POSSIBLE_PITCHES-1) ) ) + self.x = int( self.parentSize[0] * self.note.onset / (round( self.beatsPerPageAdjustment.value, 0 ) * Constants.TICKS_PER_BEAT) ) \ + + self.posOffset[0] + self.y = round( (self.parentSize[1]-self.height) * ( Constants.MAXIMUM_PITCH - self.note.pitch ) / (Constants.NUMBER_OF_POSSIBLE_PITCHES-1) ) \ + + self.posOffset[1] + + def checkX( self, startx, stopx ): + if self.x >= startx and self.x < stopx: return True + else: return False + + def getStartTick( self ): + return self.note.onset + + def getEndTick( self ): + return self.note.onset + self.note.duration + + def updateDragLimits( self, dragLimits, leftBound, rightBound, widthBound ): + pixelsPerTick = self.parentSize[0] / (round( self.beatsPerPageAdjustment.value, 0 ) * Constants.TICKS_PER_BEAT) + pixelsPerPitch = (self.parentSize[1] - self.height) / (Constants.MAXIMUM_PITCH - Constants.MINIMUM_PITCH) + left = (leftBound - self.note.onset) * pixelsPerTick + right = (rightBound - self.note.duration - self.note.onset) * pixelsPerTick + up = (self.note.pitch - Constants.MAXIMUM_PITCH) * pixelsPerPitch + down = (self.note.pitch - Constants.MINIMUM_PITCH) * pixelsPerPitch + short = (Constants.MINIMUM_NOTE_DURATION - self.note.duration) * pixelsPerTick + long = (widthBound - self.note.duration - self.note.onset) * pixelsPerTick + + if dragLimits[0][0] < left: dragLimits[0][0] = left + if dragLimits[0][1] > right: dragLimits[0][1] = right + if dragLimits[1][0] < up: dragLimits[1][0] = up + if dragLimits[1][1] > down: dragLimits[1][1] = down + if dragLimits[2][0] < short: dragLimits[2][0] = short + if dragLimits[2][1] > long: dragLimits[2][1] = long + + # store the current loc as a reference point + self.baseOnset = self.note.onset + self.basePitch = self.note.pitch + self.baseDuration = self.note.duration + + def updateSampleNote( self, pitch ): + if self.sampleNote == None: + self.sampleNote = self.note.clone() + #TODO clean this up: + if CSoundConstants.INSTRUMENTS[ self.sampleNote.instrumentFlag ].csoundInstrumentID == 103: + self.sampleNote.duration = 100 + else: + self.sampleNote.duration = -1 + self.sampleNote.play() + + elif self.sampleNote.pitch != pitch: + self.sampleNote.pitch = pitch + self.sampleNote.play() + + def clearSampleNote( self ): + if self.sampleNote != None: + self.sampleNote.duration = 0 + self.sampleNote.play() + del self.sampleNote + self.sampleNote = None + + #----------------------------------- + # Selection + #----------------------------------- + + def setSelected( self, state ): + if self.selected != state: + self.selected = state + return True # state changed + return False # state is the same + + def getSelected( self ): + return self.selected diff --git a/Edit/rm/PageBankView.py b/Edit/rm/PageBankView.py new file mode 100644 index 0000000..fedadef --- /dev/null +++ b/Edit/rm/PageBankView.py @@ -0,0 +1,85 @@ +import pygtk +pygtk.require( '2.0' ) +import gtk + +from GUI.GUIConstants import GUIConstants +from GUI.Core.PageView import PageView + +class PageBankView( gtk.Frame ): + + NO_PAGE = -1 + + def __init__( self, selectPageCallback, pageDropCallback ): + gtk.Frame.__init__( self ) + self.table = gtk.Table( 1, GUIConstants.NUMBER_OF_PAGE_BANK_COLUMNS ) + self.add( self.table ) + self.drag_dest_set( gtk.DEST_DEFAULT_ALL, [ ( "tune page", gtk.TARGET_SAME_APP, 11 )], gtk.gdk.ACTION_COPY|gtk.gdk.ACTION_MOVE ) + self.connect( "drag_data_received", self.dragDataReceived ) + self.selectPageCallback = selectPageCallback + self.pageDropCallback = pageDropCallback + self.selectedPageIds = set([]) + self.pageIndexDictionary = {} + self.pageViews = {} + + def dragDataReceived( self, widget, context, x, y, selectionData, info, time): + self.pageDropCallback( selectionData.data ) + + def addPage( self, pageId, invokeCallback = True ): + pageIndex = len( self.pageViews.keys() ) + self.pageIndexDictionary[ pageIndex ] = pageId + + #TODO: resize table to suit number of pages? + #if pageIndex > ( self.table.n-rows * self.table.n_columns ): + # self.table.resize( self.table.n_rows + 1, self.table.n_columns ) + + pageView = PageView( pageIndex, self.selectPage, True ) + self.pageViews[ pageIndex ] = pageView + + columnIndex = pageIndex % GUIConstants.NUMBER_OF_PAGE_BANK_COLUMNS + rowIndex = int( pageIndex / GUIConstants.NUMBER_OF_PAGE_BANK_COLUMNS ) + self.table.attach( pageView, columnIndex, columnIndex + 1, rowIndex, rowIndex + 1, gtk.SHRINK, gtk.SHRINK ) + + self.updateSize( pageView ) + + pageView.drag_source_set( gtk.gdk.BUTTON1_MASK, + [ ( "bank page", gtk.TARGET_SAME_APP, 10 ) ], + gtk.gdk.ACTION_COPY ) + + self.selectPage( pageId, True, invokeCallback ) + + pageView.show() + + def set_size_request( self, width, height ): + gtk.Frame.set_size_request( self, width, height ) + self.table.set_size_request( width, height ) + for pageId in self.pageViews.keys(): + self.updateSize( self.pageViews[ pageId ] ) + + def updateSize( self, pageView ): + pageView.set_size_request( self.get_allocation().width / GUIConstants.NUMBER_OF_PAGE_BANK_COLUMNS, + GUIConstants.PAGE_HEIGHT - 1 ) + + def selectPage( self, selectedPageId, invokeCallback = True, deselectOthers = True ): + if deselectOthers: + for pageId in self.pageViews.keys(): + self.pageViews[ pageId ].setSelected( pageId == selectedPageId ) + if pageId != selectedPageId: + self.selectedPageIds.discard( pageId ) + else: + self.selectedPageIds.add( pageId ) + #nb: pageId might be NO_PAGE, and selectedPageIds can be empty here + + else: + self.pageViews[ selectedPageId ].toggleSelected() + if self.pageViews[ selectedPageId ].selected: + self.selectedPageIds.add( selectedPageId ) + else: + self.selectedPageIds.discard( selectedPageId ) + + if invokeCallback: + self.selectPageCallback( selectedPageId ) + + def getSelectedPageIds( self ): + rval = filter( lambda id: self.pageViews[id].selected == True, self.pageViews.keys()) + return rval + diff --git a/Edit/rm/PageView.py b/Edit/rm/PageView.py new file mode 100644 index 0000000..00b650b --- /dev/null +++ b/Edit/rm/PageView.py @@ -0,0 +1,65 @@ +import pygtk +pygtk.require( '2.0' ) +import gtk + +import pango + +from GUI.GUIConstants import GUIConstants + +class PageView( gtk.DrawingArea ): + def __init__( self, pageID, selectPageCallback, selected = False ): + gtk.DrawingArea.__init__( self ) + + self.pageID = pageID + self.selectPageCallback = selectPageCallback + self.selected = selected + + self.add_events( gtk.gdk.BUTTON_PRESS_MASK ) + + self.connect( "expose-event", self.handleExposeEvent ) + self.connect( "button-press-event", self.handleButtonPress ) + self.connect( "drag_data_get", self.getData ) + + def handleButtonPress( self, widget, event ): + if event.button == 1 or event.button == 3: + self.selectPageCallback( self.pageID, event.button == 1 ) + + def getData( self, widget, context, selection, targetType, eventTime ): + return selection.set( gtk.gdk.SELECTION_PRIMARY, 32, "p %d" % self.pageID ) + + def toggleSelected( self ): + self.selected = not self.selected + self.queue_draw() + + def setSelected( self, selected ): + if self.selected != selected: + self.selected = selected + self.queue_draw() + + # TODO: this is temporary: replace with a visual representation of the page + def handleExposeEvent( self, drawingArea, event ): + size = self.get_allocation() + context = self.window.cairo_create() + + if self.selected: + context.set_line_width( GUIConstants.PAGE_SELECTED_BORDER_SIZE ) + else: + context.set_line_width( GUIConstants.PAGE_BORDER_SIZE ) + context.move_to( 0, 0 ) + context.rel_line_to( size.width, 0 ) + context.rel_line_to( 0, size.height ) + context.rel_line_to( -size.width, 0 ) + context.close_path() + + #blue background + context.set_source_rgb( 0.75, 0.75, 0.75 ) + context.fill_preserve() + + #black border + context.set_source_rgb( 0, 0, 0 ) + context.stroke() + + #text + layout = self.create_pango_layout( "%d" % ( self.pageID + 1 ) ) + layout.set_font_description( pango.FontDescription( 'Sans 10' ) ) + self.window.draw_layout( self.window.new_gc(), 5, 5, layout ) diff --git a/Edit/rm/PositionIndicator.py b/Edit/rm/PositionIndicator.py new file mode 100644 index 0000000..aadc4f4 --- /dev/null +++ b/Edit/rm/PositionIndicator.py @@ -0,0 +1,47 @@ +import pygtk +pygtk.require( '2.0' ) +import gtk + +#---------------------------------------------------------------------- +# A verical bar used to show the current point in time on a page +# TODO: modify this class to change the current point in time +# on click and drag +#---------------------------------------------------------------------- +class PositionIndicator( gtk.DrawingArea ): + #----------------------------------- + # initialization + #----------------------------------- + def __init__( self, trackIDs, selectedTrackIDs, mutedTrackIDs ): + gtk.DrawingArea.__init__( self ) + + self.trackIDs = trackIDs + self.selectedTrackIDs = selectedTrackIDs + self.mutedTrackIDs = mutedTrackIDs + + self.connect( "expose-event", self.draw ) + + def draw( self, drawingArea, event ): + indicatorSize = self.get_allocation() + trackHeight = indicatorSize.height / len( self.trackIDs ) + + context = drawingArea.window.cairo_create() + + trackIndex = 0 + for trackID in self.trackIDs: + height = trackIndex * trackHeight + + context.move_to( 0, height ) + context.rel_line_to( indicatorSize.width, 0 ) + context.rel_line_to( 0, height + trackHeight ) + context.rel_line_to( -indicatorSize.width, 0 ) + context.close_path() + + if trackID not in self.mutedTrackIDs: + context.set_source_rgb( 0, 0, 0 ) #black + else: + context.set_source_rgb( 0.6, 0.6, 0.6 ) #grey + + context.fill_preserve() + context.stroke() + + trackIndex += 1 \ No newline at end of file diff --git a/Edit/rm/TrackView.py b/Edit/rm/TrackView.py new file mode 100644 index 0000000..0b66abd --- /dev/null +++ b/Edit/rm/TrackView.py @@ -0,0 +1,263 @@ +import pygtk +pygtk.require( '2.0' ) +import gtk + +from Framework.Constants import Constants +from GUI.GUIConstants import GUIConstants + +from BackgroundView import SELECTNOTES +from NoteView import NoteView + + +#---------------------------------------------------------------------- +# This view class is used to show the contents of a NoteTrack +# i.e. a Collection of Note objects +#---------------------------------------------------------------------- +class TrackView: + #----------------------------------- + # initialization functions + #----------------------------------- + def __init__( self, trackID, beatsPerPageAdjustment ): + self.trackID = trackID + self.beatsPerPageAdjustment = beatsPerPageAdjustment + self.noteViews = [] + self.posOffset = (0,0) + self.selectedNotes = [] + + def getID( self ): + return self.trackID + + #----------------------------------- + # modification methods + #----------------------------------- + def setNotes( self, notes ): + self.clearNotes() + + lineW = self.getBorderWidth() + + for note in notes: + noteView = NoteView( note, self, self.beatsPerPageAdjustment ) + self.noteViews.append( noteView ) + noteView.setPositionOffset( (self.posOffset[0]+lineW, self.posOffset[1]+lineW ) ) + + self.updateNoteTransforms() + + def clearNotes( self ): + del self.noteViews + self.noteViews = [] + self.selectedNotes = [] + + def selectNotes( self, mode, which ): + if mode == SELECTNOTES.ALL: + for note in self.noteViews: note.setSelected( True ) + self.selectedNotes = self.noteViews[:] + elif mode == SELECTNOTES.NONE: + for note in self.noteViews: note.setSelected( False ) + self.selectedNotes = [] + elif mode == SELECTNOTES.ADD: + for note in which: + if note.setSelected( True ): + self.selectedNotes.insert( 0, note ) + elif mode == SELECTNOTES.REMOVE: + for note in which: + if note.setSelected( False ): + self.selectedNotes.remove( note ) + elif mode == SELECTNOTES.EXCLUSIVE: + for note in self.noteViews: + if note in which: + if note.setSelected( True ): + self.selectedNotes.insert( 0, note ) + else: + if note.setSelected( False ): + self.selectedNotes.remove( note ) + + def updateDragLimits( self, dragLimits ): + if not len(self.selectedNotes): return # no selected notes here + + leftBound = 0 + maxRightBound = round( self.beatsPerPageAdjustment.value, 0 ) * Constants.TICKS_PER_BEAT + last = len(self.noteViews)-1 + for i in range(0,last): + if not self.noteViews[i].getSelected(): + leftBound = self.noteViews[i].getEndTick() + else: + if not self.noteViews[i+1].getSelected(): + rightBound = min( self.noteViews[i+1].getStartTick(), maxRightBound ) + widthBound = rightBound + else: + rightBound = maxRightBound + widthBound = min( self.noteViews[i+1].getStartTick(), maxRightBound ) + self.noteViews[i].updateDragLimits( dragLimits, leftBound, rightBound, widthBound ) + if self.noteViews[last].getSelected(): + self.noteViews[last].updateDragLimits( dragLimits, leftBound, maxRightBound, maxRightBound ) + + def getNotesByBar( self, beatCount, startX, stopX ): + beatWidth = self.getBeatLineSpacing( beatCount ) + beatStart = self.getBeatLineStart() + while beatStart+beatWidth <= startX: + beatStart += beatWidth + beatStop = beatStart + beatWidth + while beatStop+beatWidth < stopX: + beatStop += beatWidth + + notes = [] + for note in self.noteViews: + if note.checkX( beatStart, beatStop ): + notes.insert(0,note) + return notes + + #----------------------------------- + # event methods + #----------------------------------- + + def handleButtonPress( self, emitter, event ): + eX = event.x - self.posOffset[0] + eY = event.y - self.posOffset[1] + if eX < 0 or eX > self.width or eY < 0 or eY > self.height: + return False + + for note in self.noteViews: + handled = note.handleButtonPress( emitter, event ) + if handled: return handled + + return False + + def handleButtonRelease( self, emitter, event, buttonPressCount ): + eX = event.x - self.posOffset[0] + eY = event.y - self.posOffset[1] + + if eX < 0 or eX > self.width or eY < 0 or eY > self.height: + return False + + if event.button == 1: + if buttonPressCount == 1: emitter.toggleTrack( self.trackID, False ) + else: emitter.toggleTrack( self.trackID, True ) + + return True + + def handleMarqueeSelect( self, emitter, start, stop ): + intersectionY = [ max(start[1],self.posOffset[1]), min(stop[1],self.posOffset[1]+self.height) ] + if intersectionY[0] > intersectionY[1]: + return False + + intersectionX = [ max(start[0],self.posOffset[0]), min(stop[0],self.posOffset[0]+self.width) ] + if intersectionX[0] > intersectionX[1]: + return False + + + hits = [] + for note in self.noteViews: + hit = note.handleMarqueeSelect( emitter, + [ intersectionX[0], intersectionY[0] ], \ + [ intersectionX[1], intersectionY[1] ] ) + if hit: hits.insert(0,note) + + if len(hits): return hits + + return False + + def noteDrag( self, emitter, dx, dy, dw ): + for note in self.selectedNotes: + note.noteDrag( emitter, dx, dy, dw ) + + def doneNoteDrag( self, emitter ): + for note in self.selectedNotes: + note.doneNoteDrag( emitter ) + + #----------------------------------- + # drawing methods + #----------------------------------- + + def getBorderWidth( self ): #should return a constant value, otherwise we have to recalculate sizing and positioning everyframe! + return GUIConstants.BORDER_SIZE + + def getBeatLineWidth( self ): + return GUIConstants.BEAT_LINE_SIZE #should return a constant value, otherwise we have to recalculate sizing and positioning everyframe! + + def getBeatLineSpacing( self, beatCount ): + return (self.width - 2*self.getBorderWidth() + self.getBeatLineWidth())/beatCount + + def getBeatLineStart( self ): + return self.posOffset[0] + self.getBorderWidth() - self.getBeatLineWidth()/2.0 + + def setPositionOffset( self, offset ): + self.posOffset = offset + + lineW = self.getBorderWidth() + for note in self.noteViews: + note.setPositionOffset( ( self.posOffset[0]+lineW, self.posOffset[1]+lineW ) ) + + def draw( self, context, beatCount, selected ): + #if selected: lineW = GUIConstants.SELECTED_BORDER_SIZE + #else: lineW = GUIConstants.BORDER_SIZE + lineW = self.getBorderWidth() + context.set_line_width( lineW ) + lineWDIV2 = lineW/2.0 + + context.move_to( self.posOffset[0] + lineWDIV2, self.posOffset[1] + lineWDIV2 ) + context.rel_line_to( self.width - lineW, 0 ) + context.rel_line_to( 0, self.height - lineW ) + context.rel_line_to( -self.width + lineW, 0 ) + context.close_path() + + #draw the background + context.set_source_rgb( 0.75, 0.75, 0.75 ) + context.fill_preserve() + + #draw the border + if selected: context.set_source_rgb( 1, 1, 1 ) + else: context.set_source_rgb( 0, 0, 0 ) + context.stroke() + + #draw the beat lines + beatLineWidth = self.getBeatLineWidth() + context.set_line_width( beatLineWidth ) + beatWidth = self.getBeatLineSpacing( beatCount ) + beatStart = self.getBeatLineStart() + context.set_source_rgb( 0, 0, 0 ) + for i in range(1,beatCount): + context.move_to( beatStart + i*beatWidth, self.posOffset[1] + lineW ) + context.rel_line_to( 0, self.height - 2*lineW ) + context.stroke() + + #draw the notes + for note in self.noteViews: + note.draw( context ) + + #----------------------------------- + # sizing methods + #----------------------------------- + + def updateNoteTransforms( self ): + width = self.width - 2*self.getBorderWidth() + height = self.height - 2*self.getBorderWidth() # adjust for actual note drawing area + for noteView in self.noteViews: + noteView.updateTransform( (width, height) ) + + def set_size_request( self, width, height ): + self.width = width + self.height = height + self.updateNoteTransforms() + + +#unused for now... +class NoteViewPool: + def __init__( self, parentContainer, beatsPerPageAdjustment ): + self.parentContainer = parentContainer + self.beatsPerPageAdjustment = beatsPerPageAdjustment + self.pool = [] + + def addNoteView( self, noteView ): + #noteView.hide() + self.pool.append( noteView ) + + def addNoteViews( self, noteViews ): + for noteView in noteViews: + self.addNoteView( noteView ) + + def getNoteView( self ): + poolSize = len( pool ) + if poolSize != 0: + return pool.pop( poolSize ) + + return NoteView( None, self.parentContainer, self.beatsPerPageAdjustment ) diff --git a/Edit/rm/TunePageView.py b/Edit/rm/TunePageView.py new file mode 100644 index 0000000..501504f --- /dev/null +++ b/Edit/rm/TunePageView.py @@ -0,0 +1,17 @@ +import pygtk +pygtk.require( '2.0' ) +import gtk + +from PageView import PageView + +class TunePageView( PageView ): + def __init__( self, pageID, tuneIndex, selectPageCallback, selected = False ): + PageView.__init__( self, pageID, selectPageCallback, selected ) + + self.pageIndex = tuneIndex + + def handleButtonPress( self, widget, data ): + self.selectPageCallback( self.tuneIndex ) + + def getData( self, widget, context, selection, targetType, eventTime ): + return selection.set( gtk.gdk.SELECTION_PRIMARY, 32, "t %d %d" % (self.pageID,self.pageIndex) ) diff --git a/Edit/rm/TuneView.py b/Edit/rm/TuneView.py new file mode 100644 index 0000000..63cf468 --- /dev/null +++ b/Edit/rm/TuneView.py @@ -0,0 +1,123 @@ +import pygtk +pygtk.require( '2.0' ) +import gtk + +from GUI.GUIConstants import GUIConstants +from GUI.Core.TunePageView import TunePageView + +def swap(l,i,j): + e = l[i] + l[i] = l[j] + l[j] = e + +class TuneView( gtk.ScrolledWindow ): + + NO_PAGE = -1 + + def _page_width(self): + return self.pageContainer.get_allocation().width / GUIConstants.NUMBER_OF_PAGE_BANK_COLUMNS + + def __init__( self, selectPageCallback ): + gtk.ScrolledWindow.__init__( self ) + + #selectPageCallback(): currently connected to pagePlayer.setPlayTune, which skips to a given page of the tune. + self.selectPageCallback = selectPageCallback + self.selectedPageIndex = self.NO_PAGE + + self.set_policy( gtk.POLICY_ALWAYS, gtk.POLICY_AUTOMATIC ) + self.set_placement( gtk.CORNER_TOP_LEFT ) + + #self.pageViews: list of our custom PageView widgets + self.pageViews = [] + self.pageContainer = gtk.HBox( False ) + self.add_with_viewport( self.pageContainer ) + + #the old part + self.pageContainer.drag_dest_set( gtk.DEST_DEFAULT_ALL, + [ ( "bank page", gtk.TARGET_SAME_APP, 10 ), + ( "tune page", gtk.TARGET_SAME_APP, 11 )], + gtk.gdk.ACTION_COPY|gtk.gdk.ACTION_MOVE ) + + self.pageContainer.connect( "drag_data_received", self.dragDataReceived ) + + #private method: called by gtk when pages get dragged onto the tune-view + def dragDataReceived( self, widget, context, x, y, selectionData, info, time ): + print 'dragDataReceived: ', selectionData.data, info, selectionData.data + recv = selectionData.data.split() + if recv[0] == 'p': + pageId = int( recv[1] ) + self.addPage( pageId, min( x / self._page_width(), len( self.pageViews )) ) + elif recv[0] == 't': + self.moveSelectedPage( min( x / self._page_width(), len( self.pageViews ) -1)) + else: + raise 'ERROR' + + #public method: called by MainWindow on file load + def syncFromPagePlayer(self): + raise 'never call this' + map( lambda pv:pv.destroy(), self.pageViews ) + self.pageViews = [] + tunePages = self.tunePagesCallback() + for i in range( len(tunePages)): + self.addPage( tunePages[i], i, False) + + + def addPage( self, pageID, position ): + #create a new widget + pageView = TunePageView( pageID, position, self.selectPage ) + self.pageViews.insert( position, pageView ) + self.pageContainer.pack_start( pageView, False ) + self.pageContainer.reorder_child( pageView, position ) + + pageView.set_size_request( self.pageContainer.get_allocation().width / GUIConstants.NUMBER_OF_PAGE_BANK_COLUMNS, + GUIConstants.PAGE_HEIGHT ) + pageView.show() + + for i in range( len(self.pageViews)) : + self.pageViews[i].tuneIndex = i + self.pageViews[i].setSelected( i == position) + self.selectPageCallback( pageID, position ) + pageView.drag_source_set( + gtk.gdk.BUTTON1_MASK, + [ ( "tune page", gtk.TARGET_SAME_APP, 11 ) ], + gtk.gdk.ACTION_COPY|gtk.gdk.ACTION_MOVE ) + + def moveSelectedPage( self, position): + self.pageContainer.reorder_child( self.pageViews[self.selectedPageIndex], position ) + swap( self.pageViews, self.selectedPageIndex, position ) + self.selectedPageIndex = position + for i in range( len(self.pageViews) ) : + self.pageViews[i].tuneIndex = i + self.pageViews[i].setSelected( i == position) + + def removePage( self, position ): + pv = self.pageViews[position] + self.pageViews[position:position+1] = [] + if self.selectedPageIndex >= position : self.selectedPageIndex -= 1 + for i in range( len(self.pageViews)) : + self.pageViews[i].tuneIndex = i + self.pageViews[i].setSelected( i == position) + self.pageContainer.remove(pv) + del pv + + def selectPage( self, selectedPageIndex, invokeCallback = True ): + if selectedPageIndex >= len( self.pageViews ): selectedPageIndex = self.NO_PAGE + self.selectedPageIndex = selectedPageIndex + if selectedPageIndex == self.NO_PAGE: + for pv in self.pageViews: pv.setSelected(False) + if invokeCallback: self.selectPageCallback( -1, -1 ) + else: + if not self.pageViews[ selectedPageIndex ].selected: + map( lambda pv: pv.setSelected( pv.tuneIndex == selectedPageIndex), self.pageViews) + if invokeCallback: self.selectPageCallback( self.pageViews[selectedPageIndex].pageID, selectedPageIndex ) + + def set_size_request( self, width, height ): + gtk.ScrolledWindow.set_size_request( self, width, height ) + map( lambda pv: pv.set_size_request( width / GUIConstants.NUMBER_OF_PAGE_BANK_COLUMNS, GUIConstants.PAGE_HEIGHT ), self.pageViews) + + def getPageId( self, idx): + return self.pageViews[idx].pageID + + def getTune( self ): + return [ p.pageID for p in self.pageViews ] + -- cgit v0.9.1