From ee0904a24c1b7d6c2f66578607d5a4e7a8e81ab8 Mon Sep 17 00:00:00 2001 From: James Date: Sat, 17 Feb 2007 03:45:23 +0000 Subject: merging --- diff --git a/Config.py b/Config.py index c35c59f..8c1ca45 100644 --- a/Config.py +++ b/Config.py @@ -389,11 +389,16 @@ HIT_IMAGE_PADDING = 6 HIT_IMAGE_PADDING_MUL2 = HIT_IMAGE_PADDING*2 TRACK_SPACING = 4 TRACK_SPACING_DIV2 = TRACK_SPACING//2 -TRACK_COLORS = [ ( "#00591B", "#00E847" ), \ - ( "#6F1200", "#E72500" ), \ - ( "#004682", "#0090EA" ), \ - ( "#716D00", "#F9EF00" ), \ - ( "#37187B", "#4A00ED" ) ] +TRACK_COLORS = [ ( "#00290B", "#00E847" ), \ + ( "#3F0200", "#E72500" ), \ + ( "#002642", "#0090EA" ), \ + ( "#313D00", "#F9EF00" ), \ + ( "#17083B", "#4A00ED" ) ] +#TRACK_COLORS = [ ( "#00591B", "#00E847" ), \ +# ( "#6F1200", "#E72500" ), \ +# ( "#004682", "#0090EA" ), \ +# ( "#716D00", "#F9EF00" ), \ +# ( "#37187B", "#4A00ED" ) ] BEAT_COLOR = "#999999" BEAT_LINE_SIZE = 2 PLAYHEAD_COLOR = "#666666" @@ -472,12 +477,12 @@ PLAYER_TEMPO_UPPER = 200 DEFAULT_VOLUME = 80 #NUMERICAL CONSTANTS -NUMBER_OF_POSSIBLE_PITCHES = 25.0 -MINIMUM_PITCH = 24.0 +NUMBER_OF_POSSIBLE_PITCHES = 25 +MINIMUM_PITCH = 24 MAXIMUM_PITCH = MINIMUM_PITCH + NUMBER_OF_POSSIBLE_PITCHES - 1 -NUMBER_OF_POSSIBLE_PITCHES_DRUM = 13.0 +NUMBER_OF_POSSIBLE_PITCHES_DRUM = 13 PITCH_STEP_DRUM = 2 -MINIMUM_PITCH_DRUM = 24.0 +MINIMUM_PITCH_DRUM = 24 MAXIMUM_PITCH_DRUM = MINIMUM_PITCH_DRUM + PITCH_STEP_DRUM*(NUMBER_OF_POSSIBLE_PITCHES_DRUM - 1) MINIMUM_NOTE_DURATION = 1 # ticks MS_PER_MINUTE = 60000.0 diff --git a/Edit/HitInterface.py b/Edit/HitInterface.py index 1301a33..1b12b1f 100644 --- a/Edit/HitInterface.py +++ b/Edit/HitInterface.py @@ -18,10 +18,14 @@ class HitInterface( NoteInterface ): self.updateTransform() def updateTransform( self ): - if self.note.page == self.owner.curPage and not self.firstTransform: - oldX = self.imgX - oldY = self.imgY - oldEndX = self.imgX + self.imgWidth + 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 if self.note.cs.onset != self.oldOnset: self.x = self.owner.ticksToPixels( self.noteDB.getPage(self.note.page).beats, self.note.cs.onset ) @@ -33,7 +37,7 @@ class HitInterface( NoteInterface ): self.imgY = self.y - Config.NOTE_IMAGE_PADDING self.oldPitch = self.note.cs.pitch - if self.note.page == self.owner.curPage: + if dirty: if self.firstTransform: self.owner.invalidate_rect( self.imgX, self.imgY, self.imgWidth, self.imgHeight, self.note.page ) self.firstTransform = False @@ -107,19 +111,42 @@ class HitInterface( NoteInterface ): return 1 - def noteDrag( self, emitter, do, dp, dd ): + def noteDragPitch( self, dp, stream ): self.potentialDeselect = False - - if do != self.lastDragO: - self.lastDragO = do - self.noteDB.updateNote( self.note.page, self.note.track, self.note.id, PARAMETER.ONSET, self.baseOnset + do ) - self.end = self.note.cs.onset + self.note.cs.duration - if dp != self.lastDragP and not dp%2: self.lastDragP = dp - newPitch = self.basePitch + dp - self.noteDB.updateNote( self.note.page, self.note.track, self.note.id, PARAMETER.PITCH, newPitch ) - self.updateSampleNote( newPitch ) + 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 diff --git a/Edit/MainWindow.py b/Edit/MainWindow.py index 2a8465b..4514f3e 100644 --- a/Edit/MainWindow.py +++ b/Edit/MainWindow.py @@ -207,17 +207,17 @@ class MainWindow( gtk.EventBox ): self.GUI["2toolPanel"].set_size_request( -1, 75 ) # + + tool box self.GUI["2toolBox"] = formatRoundBox( RoundHBox(), "#6C9790" ) - self.GUI["2toolBox"].set_size_request( 146, -1 ) + self.GUI["2toolBox"].set_size_request( 154, -1 ) self.GUI["2toolPointerButton"] = ImageRadioButton( None, Config.IMAGE_ROOT+"pointer.png", Config.IMAGE_ROOT+"pointerDown.png", backgroundFill = "#6C9790" ) - self.GUI["2toolPointerButton"].connect( "clicked", self.handleToolClick , "Default" ) + self.GUI["2toolPointerButton"].connect( "clicked", self.handleToolClick , "default" ) self.GUI["2toolBox"].pack_start( self.GUI["2toolPointerButton"] ) self.GUI["2toolPencilButton"] = ImageRadioButton( self.GUI["2toolPointerButton"], Config.IMAGE_ROOT+"pencil.png", Config.IMAGE_ROOT+"pencilDown.png", backgroundFill = "#6C9790" ) - self.GUI["2toolPencilButton"].connect( "clicked", self.handleToolClick , "Draw" ) + self.GUI["2toolPencilButton"].connect( "clicked", self.handleToolClick , "draw" ) self.GUI["2toolBox"].pack_start( self.GUI["2toolPencilButton"] ) self.GUI["2toolPanel"].pack_start( self.GUI["2toolBox"], False, False ) self.GUI["2rightPanel"].pack_start( self.GUI["2toolPanel"], False ) # + + context box (for context sensitive buttons, nothing to do with CAIRO) - contextWidth = 592 + contextWidth = 674 self.GUI["2contextBox"] = formatRoundBox( RoundFixed(), "#6C9790" ) self.GUI["2contextBox"].set_size_request( contextWidth, -1 ) self.GUI["2contextPrevButton"] = gtk.Button("<") @@ -229,48 +229,92 @@ class MainWindow( gtk.EventBox ): # + + + page box self.GUI["2pageBox"] = gtk.HBox() self.GUI["2pageBox"].set_size_request( contextWidth-50, -1 ) + self.GUI["2pageGenerateButton"] = gtk.Button("Gen") + self.GUI["2pageGenerateButton"].connect( "clicked", lambda a1:self.pageGenerate() ) + self.GUI["2pageBox"].pack_start( self.GUI["2pageGenerateButton"] ) + self.GUI["2pagePropertiesButton"] = gtk.Button("Prop") + self.GUI["2pagePropertiesButton"].connect( "clicked", lambda a1:self.pageProperties() ) + self.GUI["2pageBox"].pack_start( self.GUI["2pagePropertiesButton"] ) self.GUI["2pageDeleteButton"] = gtk.Button("Delete") - self.GUI["2pageDeleteButton"].connect( "clicked", lambda a1:self.removePages() ) + self.GUI["2pageDeleteButton"].connect( "clicked", lambda a1:self.pageDelete() ) self.GUI["2pageBox"].pack_start( self.GUI["2pageDeleteButton"] ) - self.GUI["2pageNewButton"] = gtk.Button("New") - self.GUI["2pageNewButton"].connect( "clicked", lambda a1:self.addPage() ) - self.GUI["2pageBox"].pack_start( self.GUI["2pageNewButton"] ) self.GUI["2pageDuplicateButton"] = gtk.Button("Duplicate") - self.GUI["2pageDuplicateButton"].connect( "clicked", lambda a1:self.duplicatePages() ) + self.GUI["2pageDuplicateButton"].connect( "clicked", lambda a1:self.pageDuplicate() ) self.GUI["2pageBox"].pack_start( self.GUI["2pageDuplicateButton"] ) + self.GUI["2pageNewButton"] = gtk.Button("New") + self.GUI["2pageNewButton"].connect( "clicked", lambda a1:self.pageAdd() ) + self.GUI["2pageBox"].pack_start( self.GUI["2pageNewButton"] ) + self.GUI["2pageBeatsButton"] = gtk.Button("Beats") + self.GUI["2pageBeatsButton"].connect( "clicked", lambda a1:self.pageBeats() ) + self.GUI["2pageBox"].pack_start( self.GUI["2pageBeatsButton"] ) self.GUI["2contextBox"].put( self.GUI["2pageBox"], 25, 0 ) # + + + track box self.GUI["2trackBox"] = gtk.HBox() self.GUI["2trackBox"].set_size_request( contextWidth-50, -1 ) + self.GUI["2trackGenerateButton"] = gtk.Button("tGen") + self.GUI["2trackGenerateButton"].connect( "clicked", lambda a1:self.trackGenerate() ) + self.GUI["2trackBox"].pack_start( self.GUI["2trackGenerateButton"] ) + self.GUI["2trackPropertiesButton"] = gtk.Button("tProp") + self.GUI["2trackPropertiesButton"].connect( "clicked", lambda a1:self.trackProperties() ) + self.GUI["2trackBox"].pack_start( self.GUI["2trackPropertiesButton"] ) self.GUI["2trackDeleteButton"] = gtk.Button("tDelete") - self.GUI["2trackDeleteButton"].connect( "clicked", lambda a1:self.removePages() ) + self.GUI["2trackDeleteButton"].connect( "clicked", lambda a1:self.trackDelete() ) self.GUI["2trackBox"].pack_start( self.GUI["2trackDeleteButton"] ) - self.GUI["2trackNewButton"] = gtk.Button("tNew") - self.GUI["2trackNewButton"].connect( "clicked", lambda a1:self.addPage() ) - self.GUI["2trackBox"].pack_start( self.GUI["2trackNewButton"] ) - self.GUI["2trackDuplicateButton"] = gtk.Button("tDuplicate") - self.GUI["2trackDuplicateButton"].connect( "clicked", lambda a1:self.duplicatePages() ) + self.GUI["2trackDuplicateButton"] = gtk.ToggleButton("tDuplicate") + self.GUI["2trackDuplicateButton"].connect( "toggled", lambda a1:self.trackDuplicate() ) self.GUI["2trackBox"].pack_start( self.GUI["2trackDuplicateButton"] ) self.GUI["2contextBox"].put( self.GUI["2trackBox"], 25, 0 ) # + + + note box self.GUI["2noteBox"] = gtk.HBox() self.GUI["2noteBox"].set_size_request( contextWidth-50, -1 ) + self.GUI["2noteGenerateButton"] = gtk.Button("nGen") + self.GUI["2noteGenerateButton"].connect( "clicked", lambda a1:self.noteGenerate() ) + self.GUI["2noteBox"].pack_start( self.GUI["2noteGenerateButton"] ) + self.GUI["2notePropertiesButton"] = gtk.Button("nProp") + self.GUI["2notePropertiesButton"].connect( "clicked", lambda a1:self.noteProperties() ) + self.GUI["2noteBox"].pack_start( self.GUI["2notePropertiesButton"] ) self.GUI["2noteDeleteButton"] = gtk.Button("nDelete") - self.GUI["2noteDeleteButton"].connect( "clicked", lambda a1:self.removePages() ) + self.GUI["2noteDeleteButton"].connect( "clicked", lambda a1:self.noteDelete() ) self.GUI["2noteBox"].pack_start( self.GUI["2noteDeleteButton"] ) - self.GUI["2noteNewButton"] = gtk.Button("nNew") - self.GUI["2noteNewButton"].connect( "clicked", lambda a1:self.addPage() ) - self.GUI["2noteBox"].pack_start( self.GUI["2noteNewButton"] ) - self.GUI["2noteDuplicateButton"] = gtk.Button("nDuplicate") - self.GUI["2noteDuplicateButton"].connect( "clicked", lambda a1:self.duplicatePages() ) + self.GUI["2noteDuplicateButton"] = gtk.ToggleButton("nDuplicate") + self.GUI["2noteDuplicateButton"].connect( "toggled", self.noteDuplicateWidget ) self.GUI["2noteBox"].pack_start( self.GUI["2noteDuplicateButton"] ) + self.GUI["2noteOnsetBox"] = gtk.HBox() + self.GUI["2noteOnsetMinusButton"] = gtk.Button("<") + self.GUI["2noteOnsetMinusButton"].connect( "clicked", lambda a1:self.trackInterface.noteStepOnset(-1) ) + self.GUI["2noteOnsetBox"].pack_start( self.GUI["2noteOnsetMinusButton"] ) + self.GUI["2noteOnsetPlusButton"] = gtk.Button(">") + self.GUI["2noteOnsetPlusButton"].connect( "clicked", lambda a1:self.trackInterface.noteStepOnset(1) ) + self.GUI["2noteOnsetBox"].pack_start( self.GUI["2noteOnsetPlusButton"] ) + self.GUI["2noteBox"].pack_start( self.GUI["2noteOnsetBox"] ) + self.GUI["2notePitchBox"] = gtk.VBox() + self.GUI["2notePitchPlusButton"] = gtk.Button("^") + self.GUI["2notePitchPlusButton"].connect( "clicked", lambda a1:self.trackInterface.noteStepPitch(1) ) + self.GUI["2notePitchBox"].pack_start( self.GUI["2notePitchPlusButton"] ) + self.GUI["2notePitchMinusButton"] = gtk.Button("v") + self.GUI["2notePitchMinusButton"].connect( "clicked", lambda a1:self.trackInterface.noteStepPitch(-1) ) + self.GUI["2notePitchBox"].pack_start( self.GUI["2notePitchMinusButton"] ) + self.GUI["2noteBox"].pack_start( self.GUI["2notePitchBox"] ) + self.GUI["2noteDurationBox"] = gtk.HBox() + self.GUI["2noteDurationMinusButton"] = gtk.Button("<") + self.GUI["2noteDurationMinusButton"].connect( "clicked", lambda a1:self.trackInterface.noteStepDuration(-1) ) + self.GUI["2noteDurationBox"].pack_start( self.GUI["2noteDurationMinusButton"] ) + self.GUI["2noteDurationPlusButton"] = gtk.Button(">") + self.GUI["2noteDurationPlusButton"].connect( "clicked", lambda a1:self.trackInterface.noteStepDuration(1) ) + self.GUI["2noteDurationBox"].pack_start( self.GUI["2noteDurationPlusButton"] ) + self.GUI["2noteBox"].pack_start( self.GUI["2noteDurationBox"] ) + self.GUI["2noteVolumeBox"] = gtk.VBox() + self.GUI["2noteVolumePlusButton"] = gtk.Button("^") + self.GUI["2noteVolumePlusButton"].connect( "clicked", lambda a1:self.trackInterface.noteStepVolume(0.05) ) + self.GUI["2noteVolumeBox"].pack_start( self.GUI["2noteVolumePlusButton"] ) + self.GUI["2noteVolumeMinusButton"] = gtk.Button("v") + self.GUI["2noteVolumeMinusButton"].connect( "clicked", lambda a1:self.trackInterface.noteStepVolume(-0.05) ) + self.GUI["2noteVolumeBox"].pack_start( self.GUI["2noteVolumeMinusButton"] ) + self.GUI["2noteBox"].pack_start( self.GUI["2noteVolumeBox"] ) self.GUI["2contextBox"].put( self.GUI["2noteBox"], 25, 0 ) self.GUI["2toolPanel"].pack_start( self.GUI["2contextBox"], False ) # + + transport box self.GUI["2transportBox"] = formatRoundBox( RoundHBox(), "#6C9790" ) - self.GUI["2generateButton"] = gtk.Button("G") - self.GUI["2generateButton"].connect( "clicked", self.handleGenerate, None ) - self.GUI["2transportBox"].pack_start( self.GUI["2generateButton"] ) self.GUI["2recordButton"] = gtk.ToggleButton("R") self.GUI["2transportBox"].pack_start( self.GUI["2recordButton"] ) self.GUI["2playButton"] = gtk.ToggleButton("P") @@ -311,6 +355,10 @@ class MainWindow( gtk.EventBox ): self.currentpageId = 0 self.playingTuneIdx = 0 + # timers + self.predrawTimeout = False + self.playbackTimeout = False + # FPS stuff self.fpsTotalTime = 0 self.fpsFrameCount = 0 @@ -323,7 +371,7 @@ class MainWindow( gtk.EventBox ): init_data() #above init_GUI() #above - + self.csnd.setMasterVolume( self.getVolume() ) for tid in range(Config.NUMBER_OF_TRACKS): @@ -376,7 +424,10 @@ class MainWindow( gtk.EventBox ): notes += self.noteDB.getCSNotesByTrack( page, track ) self.playing = True - self.playbackTimeout = gobject.timeout_add( 100, self.onTimeout ) + if self.predrawTimeout: + gobject.source_remove( self.predrawTimeout ) + self.predrawTimeout = False + self.playbackTimeout = gobject.timeout_add( 50, self.onTimeout ) if len(self.pages_playing) > 1: self.displayPage( self.pages_playing[0], self.pages_playing[1] ) @@ -407,6 +458,7 @@ class MainWindow( gtk.EventBox ): else: #stop gobject.source_remove( self.playbackTimeout ) + self.playbackTimeout = False if False: #This is causing csound to stop working... # reimplement this with real CSoundNotes and it should be ok. @@ -426,10 +478,22 @@ class MainWindow( gtk.EventBox ): curtick = self.csnd.loopGetTick() curIdx = curtick / ( 4 * Config.TICKS_PER_BEAT) #TODO handle each pages_playing length - if curIdx + 1 < len(self.pages_playing): predraw = self.pages_playing[curIdx+1] - else: predraw = self.pages_playing[0] - self.displayPage( self.pages_playing[curIdx], predraw ) + # TODO update playhead + + if self.pages_playing[curIdx] != self.displayedPage: + if curIdx + 1 < len(self.pages_playing): predraw = self.pages_playing[curIdx+1] + else: predraw = self.pages_playing[0] + self.displayPage( self.pages_playing[curIdx], predraw ) + else: + self.trackInterface.predrawPage( time.time() + 0.020 ) # 20 ms time limit + + return True + + def onPredrawTimeout( self ): + if self.trackInterface.predrawPage( time.time() + 0.020 ): # 20 ms time limit + self.predrawTimeout = False + return False return True def onMuteTrack( self, widget, trackId ): @@ -478,6 +542,10 @@ class MainWindow( gtk.EventBox ): 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 onKeyboardButton( self, widget, data ): self.kb_active = widget.get_active() @@ -488,11 +556,6 @@ class MainWindow( gtk.EventBox ): #----------------------------------- # generation functions #----------------------------------- - def handleGenerate( self, widget, data ): - #if widget.get_active(): - self.generationParametersWindow.show_all() - #else: - # self.handleCloseGeneratonParametersWindow() def handleCloseGenerationParametersWindow( self, widget = None, data = None ): self.generationParametersWindow.hide_all() @@ -507,11 +570,19 @@ class MainWindow( gtk.EventBox ): for p in range(Config.NUMBER_OF_PAGES): dict[t][p] = [] - if self.trackSelected == [ 0 for i in range(Config.NUMBER_OF_TRACKS) ]: + if self.generateMode == "note": + # unsupported!? + self.handleCloseGenerationParametersWindow( None, None ) + return + elif 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)) - else: - newtracks = set( [ i for i in range(Config.NUMBER_OF_TRACKS) if self.trackSelected[i] ] ) - newpages = self.tuneInterface.getSelectedIds() + newpages = self.tuneInterface.getSelectedIds() algo( params, @@ -532,17 +603,21 @@ class MainWindow( gtk.EventBox ): note.pageId = page note.trackId = track - # add the new notes + # 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 + if self.generateMode == "note": + print "TODO generateMode == note" + else: # track or page mode + self.noteDB.deleteNotesByTrack( newpages, newtracks ) + stream = [] for page in newpages: for track in newtracks: - self.noteDB.deleteNotesByTrack( page, track ) stream += [ page, track, len(dict[track][page]) ] stream += dict[track][page] stream += [-1] @@ -557,14 +632,96 @@ class MainWindow( gtk.EventBox ): self.recompose( variate, params) #======================================================= + # 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() + return self.noteDB.pasteClipboard( pages, offset, trackMap ) + + def cleanupClipboard( self ): + if self.GUI["2noteDuplicateButton"].get_active(): + self.GUI["2noteDuplicateButton"].set_active(False) + if self.GUI["2trackDuplicateButton"].get_active(): + self.GUI["2trackDuplicateButton"].set_active(False) + self.trackInterface.donePaste() + + + #======================================================= + # Note Functions + + def noteGenerate( self ): + self.generateMode = "note" + self.generationParametersWindow.show_all() + return + + def noteProperties( self ): + # TODO + return + + 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): + if self.GUI["2trackDuplicateButton"].get_active(): + self.GUI["2trackDuplicateButton"].set_active( False ) + 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 + self.trackInterface.setInterfaceMode("tool") + widget.set_active(False) + + 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): - self.trackSelected[i] = False + if self.trackSelected[i]: + self.trackSelected[i] = False + self.trackInterface.trackToggled( i ) self.trackSelected[trackN] = True - self.trackInterface.trackToggled() # invalidate whole page + self.trackInterface.trackToggled( trackN ) self.setContextState( CONTEXT.TRACK, True ) self.setContext( CONTEXT.TRACK ) else: @@ -577,27 +734,60 @@ class MainWindow( gtk.EventBox ): return 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.setContextState( CONTEXT.TRACK, False ) + def getTrackSelected( self, trackN ): return self.trackSelected[trackN] - #======================================================= - # NoteDB notifications + def trackGenerate( self ): + self.generateMode = "track" + self.generationParametersWindow.show_all() - def notifyPageAdd( self, id, at ): - self.displayPage( id ) + def trackProperties( self, trackIds = -1 ): + # TODO + return - def notifyPageDelete( self, which, safe ): - if self.displayedPage in which: - self.displayPage( safe ) + def trackDelete( self, pageIds = -1, trackIds = -1 ): - def notifyPageDuplicate( self, new, at ): - self.displayPage( new[self.displayedPage] ) + if pageIds == -1: pageIds = self.tuneInterface.getSelectedIds() + if trackIds == -1: trackIds = [ i for i in range(Config.NUMBER_OF_TRACKS) if self.trackSelected[i] ] - def notifyPageMove( self, which, low, high ): - return + 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): + if self.GUI["2noteDuplicateButton"].get_active(): + self.GUI["2noteDuplicateButton"].set_active( False ) + 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 + self.trackInterface.setInterfaceMode("tool") + widget.set_active(False) #----------------------------------- - # tune functions + # tune/page functions #----------------------------------- def scrollTune( self, scroll ): @@ -614,25 +804,63 @@ class MainWindow( gtk.EventBox ): self.trackInterface.displayPage( pageId, nextId ) - def addPage( self, after = -1, beats = False ): + 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 + if not self.predrawTimeout: + self.predrawTimeout = gobject.timeout_add( 50, self.onPredrawTimeout ) - if after == -1: after = self.tuneInterface.getLastSelected() - if not beats: beats = self.noteDB.getPage( self.displayedPage ).beats + def pageGenerate( self ): + self.generateMode = "page" + self.generationParametersWindow.show_all() - self.noteDB.addPage( beats, after ) + def pageProperties( self, pageIds = -1 ): - def duplicatePages( self, after = -1, pageIds = False ): + if pageIds == -1: pageIds = self.tuneInterface.getSelectedIds() + + # TODO show properties or something + + def pageDelete( self, pageIds = -1 ): + + if pageIds == -1: pageIds = self.tuneInterface.getSelectedIds() + + self.noteDB.deletePages( pageIds ) + + def pageDuplicate( self, after = -1, pageIds = False ): if after == -1: after = self.tuneInterface.getLastSelected() if not pageIds: pageIds = self.tuneInterface.getSelectedIds() self.noteDB.duplicatePages( pageIds, after ) - def removePages( self, pageIds = -1 ): + def pageAdd( self, after = -1, beats = False ): + + if after == -1: after = self.tuneInterface.getLastSelected() + if not beats: beats = self.noteDB.getPage( self.displayedPage ).beats + + self.noteDB.addPage( beats, after ) + + def pageBeats( self, pageIds = -1 ): if pageIds == -1: pageIds = self.tuneInterface.getSelectedIds() - self.noteDB.deletePages( pageIds ) + # TODO change the beats + + #======================================================= + # NoteDB notifications + + def notifyPageAdd( self, id, at ): + self.displayPage( id ) + + def notifyPageDelete( self, which, safe ): + if self.displayedPage in which: + self.displayPage( safe ) + + def notifyPageDuplicate( self, new, at ): + self.displayPage( new[self.displayedPage] ) + + def notifyPageMove( self, which, low, high ): + return #----------------------------------- # load and save functions diff --git a/Edit/NoteInterface.py b/Edit/NoteInterface.py index 9dc9e43..25a160d 100644 --- a/Edit/NoteInterface.py +++ b/Edit/NoteInterface.py @@ -40,7 +40,11 @@ class NoteInterface: self.updateParameter( None, None ) def destroy( self ): - self.owner.invalidate_rect( self.imgX, self.imgY, self.imgWidth, self.imgHeight, self.note.page, True ) + if self.selected: + print "destroy", self.note.id + 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 @@ -70,10 +74,14 @@ class NoteInterface: return self.note.cs.pitch def updateTransform( self ): - if self.note.page == self.owner.curPage and not self.firstTransform: - oldX = self.imgX - oldY = self.imgY - oldEndX = self.imgX + self.imgWidth + 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 if self.note.cs.onset != self.oldOnset: self.x = self.owner.ticksToPixels( self.noteDB.getPage( self.note.page).beats, self.note.cs.onset ) @@ -89,15 +97,16 @@ class NoteInterface: self.imgY = self.y - Config.NOTE_IMAGE_PADDING self.oldPitch = self.note.cs.pitch - if self.firstTransform: - self.owner.invalidate_rect( self.imgX, self.imgY, self.imgWidth, self.imgHeight, self.note.page, True ) - 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, True ) + if dirty: + if self.firstTransform: + self.owner.invalidate_rect( self.imgX, self.imgY, self.imgWidth, self.imgHeight, self.note.page, True ) + 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, True ) def updateDragLimits( self, dragLimits, leftBound, rightBound, widthBound, maxRightBound ): left = leftBound - self.note.cs.onset @@ -187,24 +196,23 @@ class NoteInterface: return True - def noteDrag( self, emitter, do, dp, dd ): - self.potentialDeselect = False - + def noteDragOnset( self, do, stream ): + self. potentialDeselect = False if do != self.lastDragO: self.lastDragO = do - self.noteDB.updateNote( self.note.page, self.note.track, self.note.id, PARAMETER.ONSET, self.baseOnset + do ) - self.end = self.note.cs.onset + self.note.cs.duration + stream += [ self.note.id, self.baseOnset + do ] + def noteDragPitch( self, dp, stream ): + self.potentialDeselect = False if dp != self.lastDragP: self.lastDragP = dp - newPitch = self.basePitch + dp - self.noteDB.updateNote( self.note.page, self.note.track, self.note.id, PARAMETER.PITCH, newPitch ) - self.updateSampleNote( newPitch ) + stream += [ self.note.id, self.basePitch + dp ] + def noteDragDuration( self, dd, stream ): + self.potentialDeselect = False if dd != self.lastDragD: self.lastDragD = dd - self.noteDB.updateNote( self.note.page, self.note.track, self.note.id, PARAMETER.DURATION, self.baseDuration + dd ) - self.end = self.note.cs.onset + self.note.cs.duration + stream += [ self.note.id, self.baseDuration + dd ] def doneNoteDrag( self, emitter ): self.baseOnset = self.note.cs.onset @@ -217,6 +225,47 @@ class NoteInterface: self.clearSampleNote() + 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]: diff --git a/Edit/TrackInterface.py b/Edit/TrackInterface.py index a3b3986..a5b7593 100644 --- a/Edit/TrackInterface.py +++ b/Edit/TrackInterface.py @@ -3,12 +3,15 @@ pygtk.require( '2.0' ) import gtk from math import floor +import time import Config from Edit.NoteInterface import NoteInterface from Edit.HitInterface import HitInterface from Edit.MainWindow import CONTEXT +from Util.NoteDB import PARAMETER + from Util.Profiler import TP class SELECTNOTES: @@ -22,7 +25,8 @@ class SELECTNOTES: class INTERFACEMODE: DEFAULT = 0 DRAW = 1 - PASTE = 2 + PASTE_NOTES = 2 + PASTE_TRACKS = 3 class TrackInterfaceParasite: def __init__( self, noteDB, owner, note ): @@ -66,15 +70,20 @@ class TrackInterface( gtk.EventBox ): 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.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.SB_V_DOUBLE_ARROW), \ - "drag-duration": gtk.gdk.Cursor(gtk.gdk.SB_H_DOUBLE_ARROW), \ - "drag-playhead": gtk.gdk.Cursor(gtk.gdk.LEFT_SIDE), \ + "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) @@ -164,6 +173,16 @@ class TrackInterface( gtk.EventBox ): self.curScreen = 0 self.preScreen = 1 + #-- 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] + #======================================================= # Module Interface @@ -173,18 +192,32 @@ class TrackInterface( gtk.EventBox ): else: return ( self.image["note"], self.image["noteSelected"], self.drawingArea.get_colormap(), self.trackColors[track] ) - def predrawPage( self, page ): + 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, timeout ): + return self.draw( self.preScreen, False, timeout ) def displayPage( self, page, predraw = -1 ): - if page == self.curPage: return + 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 @@ -202,23 +235,43 @@ class TrackInterface( gtk.EventBox ): self.screenBufPage[self.preScreen] = predraw self.screenBufBeats[self.preScreen] = self.noteDB.getPage(predraw).beats self.invalidate_rect( 0, 0, self.width, self.height, predraw ) + elif self.screenBufPage[self.preScreen] == -1: # make sure predraw is assigned to a valid page at least + self.screenBufPage[self.preScreen] = self.screenBufPage[self.curScreen] if clearNotes: # clear the notes now that we've sorted out the screen buffers - self.clearSelectedNotes() + self.clearSelectedNotes( oldPage ) + + if self.curAction == "paste": + self._updateClipboardArea() def setPlayhead( self, ticks ): self.invalidate_rect( self.playheadX-Config.PLAYHEAD_SIZE/2, 0, Config.PLAYHEAD_SIZE, self.height, self.curPage, False ) - self.playheadX = self.ticksToPixels( ticks, self.screenBufBeats[self.curScreen] ) + Config.TRACK_SPACING_DIV2 + 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 ) def setInterfaceMode( self, mode ): - if mode == "Draw": + self.doneCurrentAction() + + if mode == "tool": + mode = self.owner.getTool() + + if mode == "draw": self.interfaceMode = INTERFACEMODE.DRAW - elif mode == "Paste": - self.interfaceMode = INTERFACEMODE.PASTE + 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 @@ -226,7 +279,7 @@ class TrackInterface( gtk.EventBox ): self.alloc = allocation width = allocation.width height = allocation.height - + self.drawingArea.set_size_request( width, height ) if self.window != None: @@ -242,6 +295,13 @@ class TrackInterface( gtk.EventBox ): 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 ) @@ -289,8 +349,9 @@ class TrackInterface( gtk.EventBox ): 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 ) - else: self.owner.toggleTrack( i, True ) + 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" ) @@ -317,7 +378,6 @@ class TrackInterface( gtk.EventBox ): 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) @@ -326,7 +386,17 @@ class TrackInterface( gtk.EventBox ): TP.ProfileEnd( "TI::handleMotion::Common" ) - if event.state & gtk.gdk.BUTTON1_MASK: + 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.pixelsToTicks( 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 @@ -361,9 +431,9 @@ class TrackInterface( gtk.EventBox ): #======================================================= # Actions - def setCurrentAction( self, action, obj ): + def setCurrentAction( self, action, obj = None ): if self.curAction: - print "BackgroundView - Action already in progress!" + self.doneCurrentAction() self.curAction = action self.curActionObject = obj @@ -372,19 +442,25 @@ class TrackInterface( gtk.EventBox ): 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 self.curAction == "note-drag-onset": self.doneNoteDrag() - elif self.curAction == "note-drag-duration": self.doneNoteDrag() - elif self.curAction == "note-drag-pitch": self.doneNoteDrag() - elif self.curAction == "note-drag-pitch-drum": self.doneNoteDrag() - + if not self.curAction: return + action = self.curAction self.curAction = False - self.curActionObject = False + + if action == "note-drag-onset": self.doneNoteDrag() + elif action == "note-drag-duration": self.doneNoteDrag() + elif action == "note-drag-pitch": self.doneNoteDrag() + elif action == "note-drag-pitch-drum": self.doneNoteDrag() + elif action == "paste": + self.owner.cleanupClipboard() def trackToggled( self, trackN = -1 ): - if trackN == -1: self.invalidate_rect( 0, 0, self.width, self.height, self.curPage ) - else: self.invalidate_rect( 0, self.trackLimits[trackN][0], self.width, self.trackLimits[trackN][1]-self.trackLimits[trackN][0], self.curPage ) + 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() @@ -398,14 +474,15 @@ class TrackInterface( gtk.EventBox ): return self.owner.setContextState( CONTEXT.NOTE, False ) - def applyNoteSelection( self, mode, trackN, which ): + def applyNoteSelection( self, mode, trackN, which, page = -1 ): + if page == -1: page = self.curPage if mode == SELECTNOTES.ALL: - track = self.noteDB.getNotesByTrack( self.curPage, trackN, self ) + 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.noteDB.getNotesByTrack( self.curPage, trackN, self ) + track = self.noteDB.getNotesByTrack( page, trackN, self ) map( lambda note:note.setSelected( False ), track ) self.selectedNotes[trackN] = [] elif mode == SELECTNOTES.ADD: @@ -425,7 +502,7 @@ class TrackInterface( gtk.EventBox ): note.setSelected( True ) self.selectedNotes[trackN].append( note ) elif mode == SELECTNOTES.EXCLUSIVE: - notes = self.noteDB.getNotesByTrack( self.curPage, trackN, self ) + notes = self.noteDB.getNotesByTrack( page, trackN, self ) for n in range(len(notes)): if notes[n] in which: if notes[n].setSelected( True ): @@ -434,51 +511,51 @@ class TrackInterface( gtk.EventBox ): if notes[n].setSelected( False ): self.selectedNotes[trackN].remove( notes[n] ) - def selectNotesByBar( self, trackN, start, stop ): + 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 ) - else: self.applyNoteSelection( SELECTNOTES.ADD, trackN, notes ) + 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, [] ) + if not Config.ModKeys.ctrlDown: self.applyNoteSelection( SELECTNOTES.NONE, i, [], page ) self.selectionChanged() - def selectNotesByTrack( self, trackN ): + def selectNotesByTrack( self, trackN, page = -1 ): if Config.ModKeys.ctrlDown: - self.applyNoteSelection( SELECTNOTES.ALL, trackN, [] ) + self.applyNoteSelection( SELECTNOTES.ALL, trackN, [], page ) else: for i in range(Config.NUMBER_OF_TRACKS): - if i == trackN: self.applyNoteSelection( SELECTNOTES.ALL, trackN, [] ) - else: self.applyNoteSelection( SELECTNOTES.NONE, i, [] ) + if i == trackN: self.applyNoteSelection( SELECTNOTES.ALL, trackN, [], page ) + else: self.applyNoteSelection( SELECTNOTES.NONE, i, [], page ) self.selectionChanged() - def selectNotes( self, noteDic ): - if Config.ModKeys.ctrlDown: + 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] ) + 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] ) - else: self.applyNoteSelection( SELECTNOTES.NONE, i, [] ) + 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 ): + def deselectNotes( self, noteDic, page = -1 ): for i in noteDic: - self.applyNoteSelection( SELECTNOTES.REMOVE, i, noteDic[i] ) + self.applyNoteSelection( SELECTNOTES.REMOVE, i, noteDic[i], page ) self.selectionChanged() - def clearSelectedNotes( self ): + def clearSelectedNotes( self, page = -1 ): for i in range(Config.NUMBER_OF_TRACKS): - self.applyNoteSelection( SELECTNOTES.NONE, i, [] ) + 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.curBeats * Config.TICKS_PER_BEAT + 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 @@ -510,39 +587,133 @@ class TrackInterface( gtk.EventBox ): 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 ) ) - dp = 0 - dd = 0 + stream = [] for i in range(Config.NUMBER_OF_TRACKS): + tstream = [] for note in self.selectedNotes[i]: - note.noteDrag(self, do, dp, dd) + 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 ): - do = 0 - dp = 0 dd = self.pixelsToTicks( self.curBeats, event.x - self.clickLoc[0] ) dd = min( self.dragLimits[2][1], max( self.dragLimits[2][0], dd ) ) + stream = [] for i in range(Config.NUMBER_OF_TRACKS): + tstream = [] for note in self.selectedNotes[i]: - note.noteDrag(self, do, dp, dd) + 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 ): - do = 0 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 ) ) - dd = 0 + stream = [] for i in range(Config.NUMBER_OF_TRACKS): + tstream = [] for note in self.selectedNotes[i]: - note.noteDrag(self, do, dp, dd) + 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] ) def doneNoteDrag( self ): 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] @@ -616,6 +787,79 @@ class TrackInterface( gtk.EventBox ): 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 @@ -654,8 +898,10 @@ class TrackInterface( gtk.EventBox ): #======================================================= # Drawing - def predraw( self, buf, noescape = True ): - TP.ProfileBegin( "TrackInterface::predraw" ) + 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 @@ -675,7 +921,7 @@ class TrackInterface( gtk.EventBox ): 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[0] == 0: + if resume[1] == 0: if startY > self.trackLimits[i][1]: continue if stopY < self.trackLimits[i][0]: break @@ -691,28 +937,30 @@ class TrackInterface( gtk.EventBox ): x = beatStart + j*beatSpacing 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.curPage, i, self ) - for n in range( resume[1], len(notes) ): + notes = self.noteDB.getNotesByTrack( self.screenBufPage[buf], i, self ) + for n in range( resume[2], len(notes) ): # check escape - if 0: + if not noescape and time.time() > timeout: resume[0] = i - resume[1] = n - TP.ProfilePause( "TrackInterface::predraw" ) + 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[0] = 0 resume[1] = 0 + resume[2] = 0 # drum track if stopY > self.trackLimits[self.drumIndex][0]: - if resume[0] == 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 ) @@ -725,27 +973,29 @@ class TrackInterface( gtk.EventBox ): x = beatStart + j*beatSpacing 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.curPage, self.drumIndex, self ) - for n in range( resume[1], len(notes) ): + notes = self.noteDB.getNotesByTrack( self.screenBufPage[buf], self.drumIndex, self ) + for n in range( resume[2], len(notes) ): # check escape - if 0: + if not noescape and time.time() > timeout: resume[0] = i - resume[1] = n - TP.ProfilePause( "TrackInterface::predraw" ) + 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::predraw" ) + TP.ProfileEnd( "TrackInterface::draw" ) return True def expose( self, DA, event ): if self.screenBufDirty[self.curScreen]: - self.predraw( self.curScreen ) + self.draw( self.curScreen ) TP.ProfileBegin( "TrackInterface::expose" ) @@ -771,11 +1021,23 @@ class TrackInterface( gtk.EventBox ): 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, base = True ): + 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 @@ -783,7 +1045,7 @@ class TrackInterface( gtk.EventBox ): self.dirtyRectToAdd.height = height #print "dirty %d %d %d %d %d %d" % (x, y, width, height, x+width, y+height) - if page == self.curPage: + 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 @@ -792,13 +1054,13 @@ class TrackInterface( gtk.EventBox ): self.screenBufDirtyRect[self.curScreen].height = height else: self.screenBufDirtyRect[self.curScreen] = self.screenBufDirtyRect[self.curScreen].union( self.dirtyRectToAdd ) - self.screenBufResume[self.curScreen] = [0,0] + 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 - elif page == self.screenBufPage[self.preScreen]: + 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 @@ -806,7 +1068,7 @@ class TrackInterface( gtk.EventBox ): self.screenBufDirtyRect[self.preScreen].height = height else: self.screenBufDirtyRect[self.preScreen] = self.screenBufDirtyRect[self.preScreen].union( self.dirtyRectToAdd ) - self.screenBufResume[self.preScreen] = [0,0] + self.screenBufResume[self.preScreen] = [0,0,0] self.screenBufDirty[self.preScreen] = True #self.queue_draw() diff --git a/Edit/TuneInterface.py b/Edit/TuneInterface.py index e6790b9..d1d5481 100644 --- a/Edit/TuneInterface.py +++ b/Edit/TuneInterface.py @@ -58,6 +58,8 @@ class TuneInterface( gtk.EventBox ): self.dragMode = None self.dropAt = -1 + 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 ) @@ -90,15 +92,23 @@ class TuneInterface( gtk.EventBox ): def handleButtonPress( self, widget, event ): ind = int(event.x-self.pageOffset)//self.pageSpacing if ind >= self.noteDB.getPageCount(): - self.dragMode = self.DRAG_BLOCK + 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._2BUTTON_PRESS: # double click -> exclusive select + + if event.state & gtk.gdk.BUTTON2_MASK: + # bring up properties or something + return + + if event.type == gtk.gdk._3BUTTON_PRESS: # triple click -> select all + self.selectAll() + self.owner.displayPage( id ) + elif event.type == gtk.gdk._2BUTTON_PRESS: # double click -> exclusive select self.selectPage( id ) self.owner.displayPage( id ) else: @@ -120,38 +130,54 @@ class TuneInterface( gtk.EventBox ): self.owner.setContext( CONTEXT.PAGE ) def handleButtonRelease( self, widget, event ): - - if self.dragMode == self.DRAG_MOVE: + if self.dragMode == self.DRAG_MOVE \ + and event.button == 1: self.invalidate_rect( 0, 0, self.width, 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 + self.dragMode = None def handleMotion( self, widget, event ): - if Config.ModKeys.ctrlDown and (self.dragMode == None or self.dragMode == self.DRAG_MOVE): - self.dropAt = -1 - self.dragMode = self.DRAG_SELECT - - if self.dragMode == self.DRAG_SELECT: # select on drag - ind = int(event.x-self.pageOffset)//self.pageSpacing - if ind < self.noteDB.getPageCount(): self.selectPage( self.noteDB.getPageByIndex(ind), False ) - elif self.dragMode == self.DRAG_DESELECT: # deselect on drag + if event.is_hint: + x, y, state = self.window.get_pointer() + event.x = float(x) + event.y = float(y) + event.state = state + + if event.state & gtk.gdk.BUTTON1_MASK: # clicking + if Config.ModKeys.ctrlDown and (self.dragMode == None or self.dragMode == self.DRAG_MOVE): + self.dropAt = -1 + self.dragMode = self.DRAG_SELECT + + if self.dragMode == self.DRAG_SELECT: # select on drag + ind = int(event.x-self.pageOffset)//self.pageSpacing + if ind < self.noteDB.getPageCount(): self.selectPage( self.noteDB.getPageByIndex(ind), False ) + elif self.dragMode == self.DRAG_DESELECT: # deselect on drag + ind = int(event.x-self.pageOffset)//self.pageSpacing + if ind < self.noteDB.getPageCount(): self.deselectPage( self.noteDB.getPageByIndex(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: + self.dropAt = int(event.x-self.pageOffset+Config.PAGE_THUMBNAIL_WIDTH_DIV2)//self.pageSpacing + c = self.noteDB.getPageCount() + if self.dropAt > c: self.dropAt = c + self.invalidate_rect( 0, 0, self.width, self.height ) + + else: # hovering ind = int(event.x-self.pageOffset)//self.pageSpacing - if ind < self.noteDB.getPageCount(): self.deselectPage( self.noteDB.getPageByIndex(ind) ) - elif self.dragMode == None and abs(self.clickX-event.x) > 20: # drag and drop - self.dragMode = self.DRAG_MOVE + if 0 <= ind < self.noteDB.getPageCount(): + id = self.noteDB.getPageByIndex(ind) + if id != self.displayedPage: + self.owner.predrawPage( id ) - if self.dragMode == self.DRAG_MOVE: - self.dropAt = int(event.x-self.pageOffset+Config.PAGE_THUMBNAIL_WIDTH_DIV2)//self.pageSpacing - c = self.noteDB.getPageCount() - if self.dropAt > c: self.dropAt = c - self.invalidate_rect( 0, 0, self.width, self.height ) def displayPage( self, id, scroll ): if self.displayedPage == id: return -1 @@ -160,12 +186,12 @@ class TuneInterface( gtk.EventBox ): if id not in self.selectedIds: self.selectPage( id ) - + ind = self.noteDB.getPageIndex( id ) - + startX = self.pageOffset + ind*self.pageSpacing stopX = startX + self.pageSpacing - + self.invalidate_rect( 0, 0, self.width, self.height ) if scroll > startX: scroll = startX @@ -176,7 +202,7 @@ class TuneInterface( gtk.EventBox ): return -1 else: scroll = stopX - self.baseWidth - + return scroll def selectPage( self, id, exclusive = True ): @@ -213,11 +239,14 @@ class TuneInterface( gtk.EventBox ): return True # page removed from the selection + def selectAll( self ): + self.selectedIds = self.noteDB.getTune()[:] + self.invalidate_rect( 0, 0, self.width, self.height ) + def clearSelection( self ): self.selectedIds = [] - self.invalidate_rect( 0, 0, self.width, self.height ) - + def getSelectedIds( self ): return self.selectedIds @@ -249,7 +278,7 @@ class TuneInterface( gtk.EventBox ): # Drawing def draw( self, drawingArea, event ): - + startX = event.area.x startY = event.area.y stopX = event.area.x + event.area.width diff --git a/Player/KeyboardStandAlone.py b/Player/KeyboardStandAlone.py index 1517b5a..c3d53cf 100644 --- a/Player/KeyboardStandAlone.py +++ b/Player/KeyboardStandAlone.py @@ -98,7 +98,7 @@ class KeyboardStandAlone: if KEY_MAP_PIANO.has_key(key): if Config.INSTRUMENTS[ self.key_dict[key].instrument].csoundInstrumentId == Config.INST_TIED: - self.key_dict[key].duration = 1 + self.key_dict[key].duration = .5 self.key_dict[key].decay = 0.7 self.key_dict[key].amplitude = 1 self.key_dict[key].playNow(0.3) diff --git a/Player/StandalonePlayer.py b/Player/StandalonePlayer.py index e26c38a..9566de0 100644 --- a/Player/StandalonePlayer.py +++ b/Player/StandalonePlayer.py @@ -18,6 +18,7 @@ from Player.KeyboardStandAlone import KeyboardStandAlone from Player.RythmPlayer import RythmPlayer from Player.RythmGenerator import * from SynthLab.SynthLabWindow import SynthLabWindow +from Player.Trackpad import Trackpad Tooltips = Config.Tooltips @@ -43,6 +44,7 @@ class StandAlonePlayer( gtk.EventBox ): self.notesList = [] time.sleep(0.001) self.playbackTimeout = None + self.trackpad = Trackpad( self, self.csnd ) loopPointsTable = [] sample_names = [name for i in range( len( Config.INSTRUMENTS ) ) for name in Config.INSTRUMENTS.keys() if Config.INSTRUMENTS[ name ].instrumentId == i ] diff --git a/Player/Trackpad.py b/Player/Trackpad.py new file mode 100644 index 0000000..0c1d53b --- /dev/null +++ b/Player/Trackpad.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python +import pygtk +pygtk.require( '2.0' ) +import gtk +import gobject + +import Config + +KEY_MAP_PIANO = Config.KEY_MAP_PIANO + +class Trackpad: + def __init__(self, win, client): + self.win = win + self.csnd = client + win.add_events(gtk.gdk.POINTER_MOTION_MASK) + win.add_events(gtk.gdk.BUTTON_PRESS_MASK) + win.add_events(gtk.gdk.BUTTON_RELEASE_MASK) + win.connect('motion-notify-event',self.handle_motion) + win.connect('key-press-event',self.handle_keyPress) + win.connect('key-release-event',self.handle_keyRelease) + + self.first_x = None + self.current_x = None + self.final_x = None + self.first_y = None + self.current_y = None + self.final_y = None + + self.buttonPressed = False + + self.create_invisible_cursor() + + + def create_invisible_cursor(self): + pix_data = """/* XPM */ + static char * invisible_xpm[] = { + "1 1 1 1", + " c None", + " "};""" + color = gtk.gdk.Color() + pix = gtk.gdk.pixmap_create_from_data(None, pix_data, 1, 1, 1, color, color) + self.invisible_cursor = gtk.gdk.Cursor(pix,pix,color,color,0,0) + + def handle_motion(self,widget,event): + if event.x < 0: + X = 0 + elif event.x > 1200: + X = 1200 + else: + X = event.x + + if event.y < 0: + Y = 0 + elif event.y > 900: + Y = 900 + else: + Y = event.y + + self.current_x = X + self.current_y = Y + if self.buttonPressed: + self.final_x = X - self.first_x + self.final_y = Y - self.first_y + self.csnd.setTrackpadX(self.final_x) + self.csnd.setTrackpadY(self.final_y) + + def handle_keyPress(self,widget,event): + if KEY_MAP_PIANO.has_key(event.hardware_keycode) and self.buttonPressed == False: + gtk.gdk.pointer_grab(self.win.window, event_mask = gtk.gdk.POINTER_MOTION_MASK, cursor = self.invisible_cursor ) + self.buttonPressed = True + self.first_x = self.current_x + self.first_y = self.current_y + + def handle_keyRelease(self,widget,event): + if KEY_MAP_PIANO.has_key(event.hardware_keycode): + gtk.gdk.pointer_ungrab(time = 0L) + self.buttonPressed = False + self.restoreDelay = gobject.timeout_add(120, self.restore) + + def restore( self ): + self.csnd.setTrackpadX(0) + self.csnd.setTrackpadY(0) + gobject.source_remove( self.restoreDelay ) + diff --git a/Resources/univorc.csd b/Resources/univorc.csd index 2063556..b2ee4a7 100644 --- a/Resources/univorc.csd +++ b/Resources/univorc.csd @@ -400,6 +400,15 @@ Reverb + master out instr 200 gktime timek + +kTrackpadX chnget "trackpadX" +gkTrackpadX = kTrackpadX / 2400. +gkTrackpadX limit gkTrackpadX, -1, 1 + +kTrackpadY chnget "trackpadY" +gkTrackpadY = kTrackpadY / 500. +gkTrackpadY limit -gkTrackpadY, -1, 1 + koutGain chnget "masterVolume" koutGain = koutGain * 0.01 gkduck init 1 @@ -646,7 +655,10 @@ kls portk p13, igliss, p13 kle portk p14, igliss, p14 kcd portk p15, igliss, p15 -a1 flooper2 1, kpitch, kls, kle, kcd, p8, 0, 0, 0, iskip +kpitchBend port gkTrackpadX, .03 +kampBend port gkTrackpadY, .03 + +a1 flooper2 1*(1+kampBend), kpitch*(1+kpitchBend), kls, kle, kcd, p8, 0, 0, 0, iskip if (p11-1) != -1 then acomp = a1 diff --git a/SynthLab/Parameter.py b/SynthLab/Parameter.py index bf158b0..add1d21 100644 --- a/SynthLab/Parameter.py +++ b/SynthLab/Parameter.py @@ -15,7 +15,6 @@ class Parameter( gtk.Window ): self.modify_bg(gtk.STATE_NORMAL, color) self.move(15, 660) self.set_size_request(450, 40) -# self.move(500, 50) self.set_decorated(False) mainBox = RoundHBox(fillcolor=Config.INST_BCK_COLOR, bordercolor=Config.INST_BCK_COLOR) mainBox.set_border_width(4) @@ -27,3 +26,4 @@ class Parameter( gtk.Window ): def update( self, string ): self.text.set_text(string) + diff --git a/SynthLab/SynthLabParametersWindow.py b/SynthLab/SynthLabParametersWindow.py index b1d15c9..bfdb351 100644 --- a/SynthLab/SynthLabParametersWindow.py +++ b/SynthLab/SynthLabParametersWindow.py @@ -138,6 +138,7 @@ class SynthLabParametersWindow( gtk.Window ): def onKeyPress(self,widget,event): key = event.hardware_keycode + print 'from slider window: %ld' % key if key not in Config.KEY_MAP: return midiPitch = Config.KEY_MAP[key] @@ -195,7 +196,7 @@ class SynthLabParametersWindow( gtk.Window ): def hideParameter( self, widget, data=None ): if self.parameterOpen and not self.clockStart: - self.windowCloseDelay = gobject.timeout_add(500, self.closeParameterWindow) + self.windowCloseDelay = gobject.timeout_add(300, self.closeParameterWindow) self.clockStart = 1 self.tooltipsUpdate() if self.instanceID != 12: diff --git a/SynthLab/SynthLabWindow.py b/SynthLab/SynthLabWindow.py index 3e9085a..4686915 100644 --- a/SynthLab/SynthLabWindow.py +++ b/SynthLab/SynthLabWindow.py @@ -381,14 +381,6 @@ class SynthLabWindow( gtk.Window ): gate = self.testGates( i, event.x-self.locations[i][0], event.y-self.locations[i][1] ) if gate: self.highlightGate( i, gate ) - choosen = SynthLabConstants.CHOOSE_TYPE[i/4][self.typesTable[i]] - str = Tooltips.SYNTHTYPES[i/4][self.typesTable[i]] + ': ' + Tooltips.SYNTHPARA[choosen][gate[1]] - if gate[0] == 1: - if self.parameterOpen: - self.parameterUpdate( str ) - else: - self.parameter = Parameter( str ) - self.parameterOpen = 1 else: self.highlightGate( None ) if self.parameterOpen: @@ -501,6 +493,15 @@ class SynthLabWindow( gtk.Window ): y = self.locations[self.overGateObj][1] + self.overGate[3][1] - self.overGateSizeDIV2 self.overGateLoc = ( x, y ) self.invalidate_rect( self.overGateLoc[0], self.overGateLoc[1], self.overGateSize, self.overGateSize ) + if obj != 12: + choosen = SynthLabConstants.CHOOSE_TYPE[obj/4][self.typesTable[obj]] + str = Tooltips.SYNTHTYPES[obj/4][self.typesTable[obj]] + ': ' + Tooltips.SYNTHPARA[choosen][gate[1]] + if gate[0] == 1: + if self.parameterOpen: + self.parameterUpdate( str ) + else: + self.parameter = Parameter( str ) + self.parameterOpen = 1 def startDragObject( self, i ): self.dragObject = i @@ -795,13 +796,13 @@ class SynthLabWindow( gtk.Window ): def writeTables( self, typesTable, controlParametersTable, sourceParametersTable, fxParametersTable ): mess = 'f5200 0 16 -2 ' + " ".join([str(n) for n in controlParametersTable]) self.csnd.inputMessage( mess ) - time.sleep(0.01) + time.sleep(0.005) mess = "f5201 0 16 -2 " + " " .join([str(n) for n in sourceParametersTable]) self.csnd.inputMessage( mess ) - time.sleep(.01) + time.sleep(.005) mess = "f5202 0 16 -2 " + " " .join([str(n) for n in fxParametersTable]) self.csnd.inputMessage( mess ) - time.sleep(.01) + time.sleep(.005) self.typesTable = typesTable lastTable = [0]*12 for i in range(12): @@ -809,7 +810,7 @@ class SynthLabWindow( gtk.Window ): lastTable[i] = (typesTable[i]+1) mess = "f5203 0 16 -2 " + " " .join([str(n) for n in lastTable]) + " 0 0 0 0" self.csnd.inputMessage( mess ) - time.sleep(.01) + time.sleep(.005) if lastTable[4] == 8: snd = Config.SOUNDS_DIR + '/' + self.sample_names[int(sourceParametersTable[1])] mess = "f5501 0 32768 -1 " + "\"%s\" 0 0 0" % snd @@ -826,7 +827,7 @@ class SynthLabWindow( gtk.Window ): snd = Config.SOUNDS_DIR + '/' + self.sample_names[int(sourceParametersTable[13])] mess = "f5504 0 32768 -1 " + "\"%s\" 0 0 0" % snd self.csnd.inputMessage( mess ) - time.sleep(.01) + time.sleep(.005) self.loadPixmaps(typesTable) self.invalidate_rect( 0, 0, self.drawingAreaWidth, self.drawingAreaHeight ) diff --git a/Util/CSoundClient.py b/Util/CSoundClient.py index 0862282..5b3802c 100644 --- a/Util/CSoundClient.py +++ b/Util/CSoundClient.py @@ -171,6 +171,14 @@ class CSoundClientPlugin( CSoundClientBase ): if self.on: sc_setMasterVolume(volume) + def setTrackpadX( self, value ): + trackpadX = value + sc_setTrackpadX(trackpadX) + + def setTrackpadY( self, value ): + trackpadY = value + sc_setTrackpadY(trackpadY) + def micRecording( self, table ): sc_inputMessage( Config.CSOUND_MIC_RECORD % table ) diff --git a/Util/Clooper/Makefile b/Util/Clooper/Makefile index c853a6d..87caf59 100644 --- a/Util/Clooper/Makefile +++ b/Util/Clooper/Makefile @@ -16,7 +16,7 @@ all : _SClient.so cmd_csound $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ clean : - rm -f *.o _ttest.so + rm -f *.o _ttest.so _SClient.so cmd_csound: cmd_csound.cpp SoundClient.o g++ -o $@ $^ -lcsound diff --git a/Util/Clooper/SClient.py b/Util/Clooper/SClient.py index 5171569..d27472b 100644 --- a/Util/Clooper/SClient.py +++ b/Util/Clooper/SClient.py @@ -53,6 +53,8 @@ sc_initialize = _SClient.sc_initialize sc_start = _SClient.sc_start sc_stop = _SClient.sc_stop sc_setMasterVolume = _SClient.sc_setMasterVolume +sc_setTrackpadX = _SClient.sc_setTrackpadX +sc_setTrackpadY = _SClient.sc_setTrackpadY sc_inputMessage = _SClient.sc_inputMessage sc_scoreEvent4 = _SClient.sc_scoreEvent4 sc_scoreEvent15 = _SClient.sc_scoreEvent15 diff --git a/Util/Clooper/SoundClient.cpp b/Util/Clooper/SoundClient.cpp index e527c02..6bc92eb 100644 --- a/Util/Clooper/SoundClient.cpp +++ b/Util/Clooper/SoundClient.cpp @@ -363,6 +363,35 @@ struct TamTamSound } } + void setTrackpadX(MYFLT value) + { + if (!csound) { + fprintf(stderr, "skipping %s, csound==NULL\n", __FUNCTION__); + return ; + } + MYFLT *p; + if (!(csoundGetChannelPtr(csound, &p, "trackpadX", CSOUND_CONTROL_CHANNEL | CSOUND_INPUT_CHANNEL))) + *p = (MYFLT) value; + else + { + fprintf(_debug, "ERROR: failed to set trackpad X value\n"); + } + } + + void setTrackpadY(MYFLT value) + { + if (!csound) { + fprintf(stderr, "skipping %s, csound==NULL\n", __FUNCTION__); + return ; + } + MYFLT *p; + if (!(csoundGetChannelPtr(csound, &p, "trackpadY", CSOUND_CONTROL_CHANNEL | CSOUND_INPUT_CHANNEL))) + *p = (MYFLT) value; + else + { + fprintf(_debug, "ERROR: failed to set trackpad Y value\n"); + } + } }; TamTamSound * sc_tt = NULL; @@ -400,6 +429,16 @@ void sc_setMasterVolume(MYFLT v) sc_tt->setMasterVolume(v); } +void sc_setTrackpadX(MYFLT v) +{ + sc_tt->setTrackpadX(v); +} + +void sc_setTrackpadY(MYFLT v) +{ + sc_tt->setTrackpadY(v); +} + void sc_inputMessage(const char *msg) { sc_tt->inputMessage(msg); diff --git a/Util/Clooper/SoundClient.h b/Util/Clooper/SoundClient.h index a946ced..561529b 100644 --- a/Util/Clooper/SoundClient.h +++ b/Util/Clooper/SoundClient.h @@ -13,6 +13,8 @@ extern "C" int sc_start(); int sc_stop(); void sc_setMasterVolume(float); + void sc_setTrackpadX(float); + void sc_setTrackpadY(float); void sc_inputMessage(const char *msg); void sc_scoreEvent4(char type, MYFLT p0, MYFLT p1, MYFLT p2, MYFLT p3); void sc_scoreEvent15(char type, MYFLT p1, MYFLT p2, MYFLT p3, MYFLT p4, MYFLT p5, MYFLT p6, MYFLT p7, MYFLT p8, MYFLT p9, MYFLT p10, MYFLT p11, MYFLT p12, MYFLT p13, MYFLT p14, MYFLT p15); diff --git a/Util/Clooper/_SClient.so b/Util/Clooper/_SClient.so index adbf427..66074a6 100755 --- a/Util/Clooper/_SClient.so +++ b/Util/Clooper/_SClient.so Binary files differ diff --git a/Util/Clooper/_ttest.so b/Util/Clooper/_ttest.so deleted file mode 100755 index 2019799..0000000 --- a/Util/Clooper/_ttest.so +++ /dev/null Binary files differ diff --git a/Util/NoteDB.py b/Util/NoteDB.py index ff6f2d4..0ed1c2d 100644 --- a/Util/NoteDB.py +++ b/Util/NoteDB.py @@ -50,6 +50,9 @@ class NoteDB: self.nextId = 0 # base id, first page will be 1 + self.clipboard = [] # stores copied cs notes + self.clipboardArea = [] # stores the limits and tracks for each page in the clipboard + #-- private -------------------------------------------- def _genId( self ): self.nextId += 1 @@ -224,16 +227,21 @@ class NoteDB: # ... up to N # page id or -1 to exit def addNotes( self, stream ): + new = {} i = [-1] p = self._readstream(stream,i) while p != -1: + if p not in new: + new[p] = [ [] for x in range(Config.NUMBER_OF_TRACKS) ] t = self._readstream(stream,i) N = self._readstream(stream,i) hint = [0] for j in range(N): - self.addNote( p, t, self._readstream(stream,i), hint ) + new[p][t].append( self.addNote( p, t, self._readstream(stream,i), hint ) ) p = self._readstream(stream,i) + return new + def deleteNote( self, page, track, id ): ind = self.noteS[page][track].index( self.noteD[page][track][id] ) @@ -265,14 +273,17 @@ class NoteDB: self.deleteNote( p, t, self._readstream(stream,i) ) p = self._readstream(stream,i) - def deleteNotesByTrack( self, page, track ): - notes = self.noteS[page][track][:] - for n in notes: - self.deleteNote( page, track, n.id ) + def deleteNotesByTrack( self, pages, tracks ): + for p in pages: + for t in tracks: + notes = self.noteS[p][t][:] + for n in notes: + self.deleteNote( p, t, n.id ) def duplicateNote( self, page, track, id, toPage, toTrack, offset ): cs = self.noteD[page][track][id].cs.clone() - cs.track = toTrack + cs.trackId = toTrack + cs.pageId = toPage cs.onset += offset ticks = self.pages[toPage].ticks if cs.onset >= ticks: return False # off the end of the page @@ -340,7 +351,7 @@ class NoteDB: param = self._readstream(stream,i) N = self._readstream(stream,i) for j in range(N): - self.updateNote( p, t, self._readstream(stream,i), param, stream[inc(i)] ) + self.updateNote( p, t, self._readstream(stream,i), param, self._readstream(stream,i) ) p = self._readstream(stream,i) #-- private -------------------------------------------- @@ -354,7 +365,7 @@ class NoteDB: while ins > 0: # check backward onset = self.noteS[page][track][ins-1].cs.onset if onset <= cs.onset: - if onset < cs.onset: break + if onset <= cs.onset: break elif self.noteS[page][track][ins-1].cs.pitch <= cs.pitch: break ins -= 1 if ins == out: # check forward @@ -364,7 +375,7 @@ class NoteDB: onset = self.noteS[page][track][ins].cs.onset if onset >= cs.onset: if onset > cs.onset: break - elif self.noteS[page][track][ins].cs.pitch >= cs.pitch: break + elif self.noteS[page][track][ins].cs.pitch > cs.pitch: break ins += 1 if ins != out: # resort @@ -377,6 +388,136 @@ class NoteDB: #======================================================= + # Clipboard Functions + + # stream format: + # page id + # track index + # number of following notes (N) + # note id + # ... up to N + # page id or -1 to exit + def notesToClipboard( self, stream ): + self.clipboard = [] + self.clipboardArea = [] + i = [-1] + pages = [] + p = self._readstream(stream,i) + while p != -1: + if p not in pages: + page = [ [] for x in range(Config.NUMBER_OF_TRACKS) ] + pageArea = { "limit": [ 99999, 0 ], + "tracks": [ 0 for x in range(Config.NUMBER_OF_TRACKS) ] } + pages.append(p) + self.clipboard.append(page) + self.clipboardArea.append(pageArea) + else: + ind = pages.index(p) + page = self.clipboard[ind] + pageArea = self.clipboardArea[ind] + t = self._readstream(stream,i) + pageArea["tracks"][t] = 1 + N = self._readstream(stream,i) + for j in range(N): + cs = self.noteD[p][t][self._readstream(stream,i)].cs.clone() + if cs.onset < pageArea["limit"][0]: pageArea["limit"][0] = cs.onset + if cs.onset + cs.duration > pageArea["limit"][1]: pageArea["limit"][1] = cs.onset + cs.duration + page[t].append( cs ) + p = self._readstream(stream,i) + + return self.clipboardArea + + def tracksToClipboard( self, pages, tracks ): + self.clipboard = [] + self.clipboardOrigin = [ 0, 0 ] + self.clipboardArea = [] + for p in pages: + page = [ [] for x in range(Config.NUMBER_OF_TRACKS) ] + pageArea = { "limit": [ 0, 99999 ], + "tracks": [ 0 for x in range(Config.NUMBER_OF_TRACKS) ] } + self.clipboard.append(page) + self.clipboardArea.append(pageArea) + for t in tracks: + pageArea["tracks"][t] = 1 + for id in self.noteD[p][t]: + cs = self.noteD[p][t][id].cs.clone() + page[t].append( cs ) + + return self.clipboardArea + + # trackMap = { X: Y, W: Z, ... }; X,W are track indices, Y,Z are clipboard indices + def pasteClipboard( self, pages, offset, trackMap ): + if not len(self.clipboard): return + + deleteStream = [] + updateStream = [] + addStream = [] + + pp = 0 + ppMax = len(self.clipboard) + for p in pages: + ticks = self.pages[p].ticks + area = self.clipboardArea[pp] + area["limit"][0] += offset + area["limit"][1] += offset + for t in trackMap.keys(): + if not area["tracks"][trackMap[t]]: continue + tdeleteStream = [] + tupdateOStream = [] + tupdateDStream = [] + taddStream = [] + # clear area + for n in self.noteS[p][t]: + start = n.cs.onset + end = start + n.cs.duration + if area["limit"][0] <= start < area["limit"][1]: start = area["limit"][1] + if area["limit"][0] < end <= area["limit"][1]: end = area["limit"][0] + if start < area["limit"][0] and end > area["limit"][1]: end = area["limit"][0] + if end <= start: + tdeleteStream.append( n.id ) + elif start != n.cs.onset: + tupdateDStream += [ n.id, end - start ] + tupdateOStream += [ n.id, start ] + elif end != start + n.cs.duration: + tupdateDStream += [ n.id, end - start ] + if len(tdeleteStream): + deleteStream += [ p, t, len(tdeleteStream) ] + tdeleteStream + if len(tupdateOStream): + updateStream += [ p, t, PARAMETER.ONSET, len(tupdateOStream)//2 ] + tupdateOStream + if len(tupdateDStream): + updateStream += [ p, t, PARAMETER.DURATION, len(tupdateDStream)//2 ] + tupdateDStream + # paste notes + for cs in self.clipboard[pp][trackMap[t]]: + newcs = cs.clone() + newcs.onset += offset + if newcs.onset >= ticks: continue + if newcs.onset + newcs.duration > ticks: + newcs.duration = ticks - newcs.onset + newcs.pageId = p + newcs.trackId = t + # TODO update the cs.instrument or any other parameters? + taddStream.append( newcs ) + if len(taddStream): + addStream += [ p, t, len(taddStream) ] + taddStream + + pp += 1 + if pp == ppMax: pp -= ppMax + + if len(deleteStream): + self.deleteNotes( deleteStream + [-1] ) + if len(updateStream): + self.updateNotes( updateStream + [-1] ) + if len(addStream): + return self.addNotes( addStream + [-1] ) + + return None + + def getClipboardArea( self, ind ): + N = len(self.clipboardArea) + while ind >= N: ind -= N + return self.clipboardArea[ind] + + #======================================================= # Listener Functions def addListener( self, listener, parasite = None, page = False, note = False ): @@ -438,6 +579,11 @@ class NoteDB: def getPageIndex( self, page ): return self.tune.index(page) + def getNote( self, page, track, id, listener = None ): + if listener: + return self.parasiteD[page][track][listener][id] + return self.noteD[page][track][id] + def getNotesByPage( self, page, listener = None ): notes = [] if listener: diff --git a/scripts/clean_trailing_whitespace.py b/scripts/clean_trailing_whitespace.py index 13532b2..57e793c 100644 --- a/scripts/clean_trailing_whitespace.py +++ b/scripts/clean_trailing_whitespace.py @@ -15,7 +15,7 @@ r = open(bakName) w = open(fName, "w" ) for line in r: line = line[:-1] - while len(line) and (line[-1] == " " or line[-1] == " "): + while len(line) and (line[-1] == " " or line[-1] == " "): line = line[:-1] w.write(line+"\n") -- cgit v0.9.1