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" ) prepareDrawable( "trackBGDrum" ) prepareDrawable( "trackBGDrumSelected" ) 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)