From 7e682ce7620f1f8a9fb9b4ffc4f4039dbfdffc56 Mon Sep 17 00:00:00 2001 From: amartin Date: Mon, 10 Sep 2007 05:03:34 +0000 Subject: Jam Popups --- (limited to 'Jam') diff --git a/Jam/Block.py b/Jam/Block.py index 9857715..39d3170 100644 --- a/Jam/Block.py +++ b/Jam/Block.py @@ -7,6 +7,8 @@ import random import Config +from Util.NoteDB import PARAMETER + #::: NOTE: # All the graphics resources are loaded in Desktop and referenced here as necessary #::: @@ -274,6 +276,8 @@ class Instrument(Block): self.data[ key ] = value if self.active: self.owner.updateInstrument( self ) + if self.child and self.child.active: + self.owner.updateLoop( self.child ) def substitute( self, block ): self.data["id"] = block.data["id"] @@ -334,7 +338,7 @@ class Drum(Block): MASK_START = 100 #::: data format: - # { "name": name, "id": instrumentId [, "volume": 0-1, "reverb": 0-1, "beats": 2-12, "regularity": 0-1, "seed": 0-1 ] } + # { "name": name, "id": instrumentId [ , "page": pageId, "volume": 0-1, "reverb": 0-1, "beats": 2-12, "regularity": 0-1 ] } #::: def __init__( self, owner, data ): Block.__init__( self, owner, data ) @@ -343,20 +347,33 @@ class Drum(Block): self.canSubstitute = True + if not "page" in self.data.keys(): + self.data["page"] = -1 if not "volume" in self.data.keys(): self.data["volume"] = 0.5 if not "reverb" in self.data.keys(): - self.data["reverb"] = 0.5 + self.data["reverb"] = 0.0 if not "beats" in self.data.keys(): self.data["beats"] = random.randint(2, 12) if not "regularity" in self.data.keys(): self.data["regularity"] = random.random() - if not "seed" in self.data.keys(): - self.data["seed"] = random.random() self.img = [ self.owner.getInstrumentImage( self.data["id"], False ), self.owner.getInstrumentImage( self.data["id"], True ) ] + if self.data["page"] == -1: + self.regenerate() + + def destroy( self ): + self.owner.noteDB.deletePages( [ self.data["page"] ] ) + Block.destroy( self ) + + def setData( self, key, value ): + self.data[key] = value + if key == "beats": + self.owner.noteDB.updatePage( self.data["page"], PARAMETER.PAGE_BEATS, value ) + if self.active: + self.owner.updateDrum( self ) def substitute( self, block ): self.data["name"] = block.data["name"] @@ -392,7 +409,7 @@ class Drum(Block): def button_release( self, event ): if not self.dragging: if self.active: - self.owner.deactivateDrum() + self.owner.deactivateDrum( self ) else: self.owner.activateDrum( self ) Block.button_release( self, event ) @@ -421,6 +438,13 @@ class Drum(Block): self.gc.set_clip_origin( self.x-Drum.MASK_START, self.y ) pixmap.draw_rectangle( self.gc, True, self.x, self.y, self.width, self.height ) + def regenerate( self ): + self.data["page"] = self.owner.owner._generateDrumLoop( self.data["id"], self.data["beats"], self.data["regularity"], self.data["reverb"], self.data["page"] ) + if self.active: + self.owner.updateDrum( self ) + + def clear( self ): + self.owner.noteDB.deleteNotesByTrack( [ self.data["page"] ], [ 0 ] ) class Loop(Block): @@ -437,7 +461,7 @@ class Loop(Block): MASK_TAIL = MASK_START + HEAD + BEAT*3 #::: data format: - # { "name": name, "id": pageId } + # { "name": name, "id": pageId [, "beats": 2-12, "regularity": 0-1 ] } #::: def __init__( self, owner, data ): Block.__init__( self, owner, data ) @@ -450,8 +474,11 @@ class Loop(Block): self.parentOffset = Loop.HEAD - 4 - beats = self.owner.noteDB.getPage(self.data["id"]).beats - self.width = Loop.WIDTH[beats] + self.data["beats"] = self.owner.noteDB.getPage(self.data["id"]).beats + self.width = Loop.WIDTH[ self.data["beats"] ] + + if "regularity" not in self.data.keys(): + self.data["regularity"] = random.random() self.img = [ self.owner.getLoopImage( self.data["id"], False ), self.owner.getLoopImage( self.data["id"], True ) ] @@ -460,21 +487,51 @@ class Loop(Block): self.owner.noteDB.deletePages( [ self.data["id"] ] ) Block.destroy( self ) - def substitute( self, block ): + def _updateWidth( self ): self.invalidateBranch( True ) oldWidth = self.width - newid = self.owner.noteDB.duplicatePages( [ block.data["id"] ] )[block.data["id"]] - self.owner.updateLoopImage( newid ) - self.data["id"] = newid + self.width = Loop.WIDTH[self.data["beats"]] + self.endX = self.x + self.width + + if self.child: + self.child.snapToParentLoc( self.getChildAnchor() ) + + if oldWidth < self.width: # or block.child: + self.invalidateBranch( True ) + + def updateLoop( self ): + self.updateImage() + self.invalidate_rect() + + if self.active: + self.owner.updateLoop( self.getRoot().child ) + def updateImage( self ): + self.owner.updateLoopImage( self.data["id"] ) self.img = [ self.owner.getLoopImage( self.data["id"], False ), self.owner.getLoopImage( self.data["id"], True ) ] - beats = self.owner.noteDB.getPage(self.data["id"]).beats - self.width = Loop.WIDTH[beats] - self.endX = self.x + self.width + def setData( self, key, value ): + self.data[key] = value + + if key == "beats": + self.owner.noteDB.updatePage( self.data["id"], PARAMETER.PAGE_BEATS, value ) + self._updateWidth() + self.updateLoop() + + def substitute( self, block ): + self.invalidateBranch( True ) + + oldWidth = self.width + + newid = self.owner.noteDB.duplicatePages( [ block.data["id"] ] )[block.data["id"]] + self.data["id"] = newid + self.data["beats"] = self.owner.noteDB.getPage(self.data["id"]).beats + + self.updateImage() + self._updateWidth() if False: # don't substitute children if block.child: @@ -496,12 +553,6 @@ class Loop(Block): elif self.child: self.child.snapToParentLoc( self.getChildAnchor() ) - if self.child: - self.child.snapToParentLoc( self.getChildAnchor() ) - - if oldWidth < self.width: # or block.child: - self.invalidateBranch( True ) - if self.active: self.owner.updateLoop( self.getRoot().child ) @@ -643,7 +694,6 @@ class Loop(Block): pixmap.draw_drawable( self.gc, loop, x-self.x, y-self.y, x, y, width, height ) def drawHighlight( self, startX, startY, stopX, stopY, pixmap ): - self.gc.foreground = self.owner.colors["Border_Highlight"] #-- draw head ----------------------------------------- @@ -674,6 +724,17 @@ class Loop(Block): self.gc.set_clip_origin( x-Loop.MASK_TAIL, self.y ) pixmap.draw_rectangle( self.gc, True, x, self.y, Loop.TAIL, self.height ) + def clear( self ): + self.owner.noteDB.deleteNotesByTrack( [ self.data["id"] ], [ 0 ] ) + + self.updateImage() + + self.invalidate_rect() + + if self.active: + self.owner.updateLoop( self.getRoot().child ) + + StrToClass = { "Instrument": Instrument, "Drum": Drum, diff --git a/Jam/Desktop.py b/Jam/Desktop.py index 7380385..a814132 100644 --- a/Jam/Desktop.py +++ b/Jam/Desktop.py @@ -37,6 +37,7 @@ class Desktop( gtk.EventBox ): self.activeDrum = None self.loops = {} # dict of playing loops by loop root + self.drums = [] # list of active drums self.add_events(gtk.gdk.POINTER_MOTION_MASK|gtk.gdk.POINTER_MOTION_HINT_MASK) @@ -58,6 +59,7 @@ class Desktop( gtk.EventBox ): self.popup = {} self.popup[Popup.Instrument] = Popup.Instrument( _("Instrument Properties"), self.owner ) self.popup[Popup.Drum] = Popup.Drum( _("Drum Kit Properties"), self.owner ) + self.popup[Popup.Loop] = Popup.Loop( _("Loop Properties"), self.owner ) self.popup[Popup.Shortcut] = Popup.Shortcut( _("Assign Key"), self.owner ) def dumpToStream( self, ostream ): @@ -79,6 +81,9 @@ class Desktop( gtk.EventBox ): parent = parent.get_parent() return False + def getLoopIds( self ): + return [ self.loops[loop] for loop in self.loops ] + #========================================================== # Blocks @@ -164,39 +169,35 @@ class Desktop( gtk.EventBox ): self.owner._updateInstrument( data["id"], data["volume"], data["pan"], data["reverb"] ) def activateDrum( self, block ): - if self.activeDrum: - self.activeDrum.setActive( False ) - self.owner._stopDrum() + for drum in self.drums: + self.deactivateDrum( drum ) block.setActive( True ) - self.activeDrum = block + self.drums.append( block ) - self.updateDrum() + self.updateDrum( block, True ) - def deactivateDrum( self ): - if not self.activeDrum: - return + def deactivateDrum( self, block ): + self.owner._stopDrum( self.loops[block] ) + del self.loops[block] - self.activeDrum.setActive( False ) - self.activeDrum = None - self.owner._stopDrum() + block.setActive( False ) + self.drums.remove( block ) - def updateDrum( self ): - data = self.activeDrum.data - self.owner._playDrum( data["id"], data["volume"], data["reverb"], data["beats"], data["regularity"], data["seed"] ) + def updateDrum( self, block, firstTime = False ): + data = block.data - def activateLoop( self, block ): - block.setActive( True ) + if firstTime: + loopId = None + else: + loopId = self.loops[block] - inst = block.parent.data + self.loops[block] = self.owner._playDrum( data["id"], data["page"], data["volume"], data["reverb"], data["beats"], data["regularity"], loopId ) - tune = [] - itr = block - while itr != None: - tune.append( itr.data["id"] ) - itr = itr.child + def activateLoop( self, block ): + block.setActive( True ) - self.loops[block] = self.owner._playLoop( inst["id"], inst["volume"], tune ) + self.updateLoop( block, True ) def deactivateLoop( self, block ): block.setActive( False ) @@ -204,7 +205,7 @@ class Desktop( gtk.EventBox ): self.owner._stopLoop( self.loops[block] ) del self.loops[block] - def updateLoop( self, block ): + def updateLoop( self, block, firstTime = False ): inst = block.parent.data tune = [] @@ -213,7 +214,12 @@ class Desktop( gtk.EventBox ): tune.append( itr.data["id"] ) itr = itr.child - self.loops[block] = self.owner._playLoop( inst["id"], inst["volume"], tune, self.loops[block] ) + if firstTime: + loopId = None + else: + loopId = self.loops[block] + + self.loops[block] = self.owner._playLoop( inst["id"], inst["volume"], inst["reverb"], tune, loopId ) #========================================================== # Mouse @@ -242,12 +248,19 @@ class Desktop( gtk.EventBox ): self.popup[Popup.Instrument].popup( True ) elif self.clickedBlock.type == Block.Drum: - #self.popup[Popup.Drum].setBlock( self.clickedBlock ) + self.popup[Popup.Drum].setBlock( self.clickedBlock ) if self.popup[Popup.Drum].is_up(): self.popup[Popup.Drum].updatePosition() else: self.popup[Popup.Drum].popup( True ) + elif self.clickedBlock.type == Block.Loop: + self.popup[Popup.Loop].setBlock( self.clickedBlock ) + if self.popup[Popup.Loop].is_up(): + self.popup[Popup.Loop].updatePosition() + else: + self.popup[Popup.Loop].popup( True ) + self.clickedBlock = None self.rightClicked = False return diff --git a/Jam/JamMain.py b/Jam/JamMain.py index dd21be8..06e7dad 100644 --- a/Jam/JamMain.py +++ b/Jam/JamMain.py @@ -25,7 +25,7 @@ from Util import NoteDB from Fillin import Fillin from RythmGenerator import generator from Generation.GenerationConstants import GenerationConstants -from Util.NoteDB import Note +from Util.NoteDB import Note, Page from Util import ControlStream @@ -51,6 +51,8 @@ class JamMain(SubActivity): self.csnd.setMasterVolume( self.volume*100 ) # csnd expects a range 0-100 for now self.csnd.setTempo( self.tempo ) + self.paused = False + #-- Drawing ------------------------------------------- def darken( colormap, hex ): hexToDec = { "0":0, "1":1, "2":2, "3":3, "4":4, "5":5, "6":6, "7":7, "8":8, "9":9, "A":10, "B":11, "C":12, "D":13, "E":14, "F":15, "a":10, "b":11, "c":12, "d":13, "e":14, "f":15 } @@ -80,9 +82,13 @@ class JamMain(SubActivity): "Border_Highlight": colormap.alloc_color( "#FFFFFF" ), "Bg_Active": colormap.alloc_color( "#FFDDEA" ), "Bg_Inactive": colormap.alloc_color( "#DBDBDB" ), - "Note_Fill_Active": lighten( colormap, "#590000" ), # base "Border_Active" - "Note_Fill_Inactive": lighten( colormap, "#8D8D8D" ) } # base "Border_Inactive" - self.colors[ "Note_Border_Active"] = self.colors["Border_Active"] + "Preview_Note_Fill": colormap.alloc_color( Config.BG_COLOR ), + "Preview_Note_Border": colormap.alloc_color( Config.FG_COLOR ), + "Preview_Note_Selected": colormap.alloc_color( style.COLOR_WHITE.get_html() ), + "Note_Fill_Active": lighten( colormap, "#590000" ), # base "Border_Active" + "Note_Fill_Inactive": lighten( colormap, "#8D8D8D" ), # base "Border_Inactive" + "Beat_Line": colormap.alloc_color( "#959595" ) } + self.colors[ "Note_Border_Active"] = self.colors["Border_Active"] self.colors[ "Note_Border_Inactive"] = self.colors["Border_Inactive"] @@ -110,6 +116,10 @@ class JamMain(SubActivity): shift = 0 self.blockMask = gtk.gdk.bitmap_create_from_data( None, bitmap, pix.get_width(), pix.get_height() ) + pix = gtk.gdk.pixbuf_new_from_file( Config.IMAGE_ROOT+"sampleBG.png" ) + self.sampleBg = gtk.gdk.Pixmap( win, pix.get_width(), pix.get_height() ) + self.sampleBg.draw_pixbuf( self.gc, pix, 0, 0, 0, 0, pix.get_width(), pix.get_height(), gtk.gdk.RGB_DITHER_NONE ) + self.sampleBg.endOffset = pix.get_width()-5 self.sampleNoteHeight = 7 if True: # load sample note clipmask pix = gtk.gdk.pixbuf_new_from_file(Config.IMAGE_ROOT+'sampleNoteMask.png') @@ -216,9 +226,12 @@ class JamMain(SubActivity): #-- Keyboard ------------------------------------------ self.key_dict = {} self.nextTrack = 1 + self.keyboardListener = None + self.recordingNote = None # default instrument self._updateInstrument( Config.INSTRUMENTS["kalimba"].instrumentId, 0.5 ) + self.instrumentStack = [] #-- Drums --------------------------------------------- self.drumLoopId = None @@ -275,14 +288,13 @@ class JamMain(SubActivity): if inst.kit: # drum kit if pitch in GenerationConstants.DRUMPITCH: pitch = GenerationConstants.DRUMPITCH[pitch] - print inst.kit - self._playNote( key, - 36, - self.instrument["amplitude"], - self.instrument["pan"], - 100, - inst.kit[pitch].instrumentId, - self.instrument["reverb"] ) + csnote = self._playNote( key, + 36, + self.instrument["amplitude"]*0.5, # trackVol*noteVol + self.instrument["pan"], + 100, + inst.kit[pitch].instrumentId, + self.instrument["reverb"] ) else: if event.state == gtk.gdk.MOD1_MASK: pitch += 5 @@ -292,13 +304,17 @@ class JamMain(SubActivity): else: duration = -1 - self._playNote( key, - pitch, - self.instrument["amplitude"], - self.instrument["pan"], - duration, - self.instrument["id"], - self.instrument["reverb"] ) + csnote = self._playNote( key, + pitch, + self.instrument["amplitude"]*0.5, # trackVol*noteVol + self.instrument["pan"], + duration, + self.instrument["id"], + self.instrument["reverb"] ) + + if self.keyboardListener: + self.keyboardListener.recordNote( csnote.pitch ) + self.recordingNote = True def onKeyRelease( self, widget, event ): key = event.hardware_keycode @@ -306,6 +322,11 @@ class JamMain(SubActivity): if self.key_dict.has_key( key ): self._stopNote( key ) + if self.recordingNote: + if self.keyboardListener: + self.keyboardListener.finishNote() + self.recordingNote = False + def _playNote( self, key, pitch, amplitude, pan, duration, instrumentId, reverb ): self.key_dict[key] = CSoundNote( 0, # onset pitch, @@ -322,6 +343,8 @@ class JamMain(SubActivity): self.nextTrack = 1 self.csnd.play(self.key_dict[key], 0.3) + return self.key_dict[key] + def _stopNote( self, key ): csnote = self.key_dict[key] if Config.INSTRUMENTSID[ csnote.instrumentId ].csoundInstrumentId == Config.INST_TIED: @@ -337,52 +360,73 @@ class JamMain(SubActivity): "pan": pan, "reverb": reverb } - def _playDrum( self, id, volume, reverb, beats, regularity, seed ): - def flatten(ll): - rval = [] - for l in ll: - rval += l - return rval + def pushInstrument( self, instrument ): + self.instrumentStack.append( self.instrument ) + self.instrument = instrument - if self.drumLoopId != None: - self._stopDrum() + def popInstrument( self ): + self.instrument = self.instrumentStack.pop() - self.drumLoopId = self.csnd.loopCreate() + def _playDrum( self, id, pageId, volume, reverb, beats, regularity, loopId = None ): + + if loopId == None: # create new loop + startTick = 0 + else: # update loop + startTick = self.csnd.loopGetTick( loopId ) + self.csnd.loopDestroy( loopId ) + + loopId = self.csnd.loopCreate() + + # TODO update track volume noteOnsets = [] notePitchs = [] - i = 0 - for x in flatten( generator( Config.INSTRUMENTSID[id].name, beats, 0.8, regularity, reverb) ): - x.amplitude = x.amplitude * volume - noteOnsets.append(x.onset) - notePitchs.append(x.pitch) - n = Note(0, x.trackId, i, x) - i = i + 1 - self.csnd.loopPlay( n, 1, loopId = self.drumLoopId ) #add as active - self.csnd.loopSetNumTicks( beats * Config.TICKS_PER_BEAT, self.drumLoopId ) - - self.drumFillin.setLoopId( self.drumLoopId ) + for n in self.noteDB.getNotesByTrack( pageId, 0 ): + n.pushState() + noteOnsets.append( n.cs.onset ) + notePitchs.append( n.cs.pitch ) + n.cs.amplitude = volume * n.cs.amplitude # TODO remove me once track volume is working + n.cs.reverbSend = reverb + self.csnd.loopPlay( n, 1, loopId = loopId ) #add as active + n.popState() + + ticks = self.noteDB.getPage( pageId ).ticks + + self.csnd.loopSetNumTicks( ticks, loopId ) + + self.drumFillin.setLoopId( loopId ) self.drumFillin.setProperties( self.tempo, Config.INSTRUMENTSID[id].name, volume, beats, reverb ) self.drumFillin.unavailable( noteOnsets, notePitchs ) self.drumFillin.play() - #self.csnd.loopSetTick( 0 ) - self.csnd.loopStart( self.drumLoopId ) - - def _stopDrum( self ): + + while startTick > ticks: # align with last beat + startTick -= Config.TICKS_PER_BEAT + + self.csnd.loopSetTick( startTick, loopId ) + + # TODO update for beat syncing + + if not self.paused: + self.csnd.loopStart( loopId ) + + return loopId + + def _stopDrum( self, loopId ): self.drumFillin.stop() - self.csnd.loopDestroy( self.drumLoopId ) - self.drumLoopId = None + self.csnd.loopDestroy( loopId ) - def _playLoop( self, id, volume, tune, loopId = None ): + def _playLoop( self, id, volume, reverb, tune, loopId = None, force = False ): if loopId == None: # create new loop - loopId = self.csnd.loopCreate() startTick = 0 else: # update loop startTick = self.csnd.loopGetTick( loopId ) self.csnd.loopDestroy( loopId ) - loopId = self.csnd.loopCreate() + loopId = self.csnd.loopCreate() + + # TODO update track volume + inst = Config.INSTRUMENTSID[id] offset = 0 @@ -390,6 +434,8 @@ class JamMain(SubActivity): for n in self.noteDB.getNotesByTrack( page, 0 ): n.pushState() n.cs.instrumentId = id + n.cs.amplitude = volume * n.cs.amplitude # TODO remove me once track volume is working + n.cs.reverbSend = reverb if inst.kit: # drum kit if n.cs.pitch in GenerationConstants.DRUMPITCH: n.cs.pitch = GenerationConstants.DRUMPITCH[n.cs.pitch] @@ -408,13 +454,91 @@ class JamMain(SubActivity): # TODO update for beat syncing - self.csnd.loopStart( loopId ) + if not self.paused or force: + self.csnd.loopStart( loopId ) return loopId def _stopLoop( self, loopId ): self.csnd.loopDestroy( loopId ) + def setPaused( self, paused ): + if self.paused == paused: + return + + loops = self.desktop.getLoopIds() + + if self.paused: # unpause + self.paused = False + for loop in loops: + self.csnd.loopStart( loop ) + else: # pause + self.paused = True + for loop in loops: + self.csnd.loopPause( loop ) + + #========================================================== + # Generate + + def _generateDrumLoop( self, instrumentId, beats, regularity, reverb, pageId = -1 ): + def flatten(ll): + rval = [] + for l in ll: + rval += l + return rval + + notes = flatten( generator( Config.INSTRUMENTSID[instrumentId].name, beats, 0.8, regularity, reverb) ) + + if pageId == -1: + page = Page( beats ) + pageId = self.noteDB.addPage( -1, page ) + else: + self.noteDB.deleteNotesByTrack( [ pageId ], [ 0 ] ) + + if len(notes): + self.noteDB.addNotes( [ pageId, 0, len(notes) ] + notes + [-1] ) + + return pageId + + def _generateTrack( self, instrumentId, page, track, parameters, algorithm ): + dict = { track: { page: self.noteDB.getCSNotesByTrack( page, track ) } } + instruments = { page: [ Config.INSTRUMENTSID[instrumentId].name for i in range(Config.NUMBER_OF_TRACKS) ] } + beatsOfPages = { page: self.noteDB.getPage(page).beats } + + algorithm( parameters, + [ 0.5 for i in range(Config.NUMBER_OF_TRACKS) ], + instruments, + self.tempo, + beatsOfPages, + [ track ], + [ page ], + dict, + 4) + + # filter & fix input ...WTF!? + for track in dict: + for page in dict[track]: + for note in dict[track][page]: + intdur = int(note.duration) + note.duration = intdur + note.pageId = page + note.trackId = track + + # prepare the new notes + newnotes = [] + for tid in dict: + for pid in dict[tid]: + newnotes += dict[tid][pid] + + # delete the notes and add the new + self.noteDB.deleteNotesByTrack( [ page ], [ track ] ) + + self.noteDB.addNotes( + [ page, track, len(dict[track][page]) ] + + dict[track][page] + + [ -1 ] ) + + #========================================================== # Get/Set @@ -438,6 +562,9 @@ class JamMain(SubActivity): self.tempo = tempo self.csnd.setTempo( self.tempo ) + def getInstrument( self ): + return self.instrument + def getDesktop( self ): return self.desktop @@ -496,6 +623,9 @@ class JamMain(SubActivity): parent.remove( self.pickers[Picker.Instrument] ) page.add( self.pickers[Picker.Instrument] ) + def setKeyboardListener( self, listener ): + self.keyboardListener = listener + #========================================================== # Pixmaps @@ -596,8 +726,8 @@ class JamMain(SubActivity): self.desktop.dumpToStream( stream ) scratch.close() - except: - print "ERROR:: _clearDesktop: unable to open file: " + filename + except IOError, (errno, strerror): + if Config.DEBUG > 3: print "IOError:: _saveDesktop:", errno, strerror def getDesktopScratchFile( self, i ): return Config.SCRATCH_DIR+"desktop%d" % i diff --git a/Jam/Parasite.py b/Jam/Parasite.py new file mode 100644 index 0000000..084d092 --- /dev/null +++ b/Jam/Parasite.py @@ -0,0 +1,349 @@ +import pygtk +pygtk.require( '2.0' ) +import gtk + +import Config + +from Util.NoteDB import PARAMETER +from Util.CSoundClient import new_csound_client + +class LoopParasite: + + def __init__( self, noteDB, owner, note ): + self.noteDB = noteDB + self.owner = owner + self.note = note + + self.firstTransform = True + self.x = 0 + self.y = 0 + self.width = 1 + self.height = Config.NOTE_HEIGHT + + self.selected = False + self.potentialDeselect = False + + self.oldOnset = -1 + self.oldEnd = -1 + self.oldPitch = -1 + self.oldAmplitude = -1 + self.oldBeats = -1 + self.lastDragO = 0 + self.lastDragP = 0 + self.lastDragD = 0 + + self.gc = self.owner.gc + self.colors = self.owner.colors + + self.updateParameter( None, None ) + + def attach( self ): + return self + + def destroy( self ): + if self.selected: + self.owner.deselectNotes( { self.note.track: [self] } ) + else: # if we were deselected above the rect has already been invalidated + self.owner.invalidatePreview( self.x, self.y, self.width, self.height, self.note.page, True ) + + def updateParameter( self, parameter, value ): + self.end = self.note.cs.onset + self.note.cs.duration + + self.updateTransform() + + def getId( self ): + return self.note.id + + def getStartTick( self ): + return self.note.cs.onset + + def getEndTick( self ): + return self.end + + def testOnset( self, start, stop ): + return self.note.cs.onset >= start and self.note.cs.onset < stop + + def getPitch( self ): + return self.note.cs.pitch + + def updateTransform( self, force = False ): + if self.note.page == self.owner.getPage(): + if not self.firstTransform: + oldX = self.x + oldY = self.y + oldEndX = self.x + self.width + dirty = True + else: + dirty = False + + beats = self.noteDB.getPage( self.note.page ).beats + if force or self.note.cs.onset != self.oldOnset or beats != self.oldBeats: + self.x = self.owner.ticksToPixels( beats, self.note.cs.onset ) + self.oldOnset = self.note.cs.onset + if force or self.end != self.oldEnd or self.note.cs.onset != self.oldOnset or beats != self.oldBeats: + self.width = self.owner.ticksToPixels( beats, self.end ) - self.x + self.oldEnd = self.end + if force or self.note.cs.pitch != self.oldPitch: + self.y = self.owner.pitchToPixels( self.note.cs.pitch ) + self.oldPitch = self.note.cs.pitch + self.oldBeats = beats + + if dirty: + if self.firstTransform: + self.owner.invalidatePreview( self.x, self.y, self.width, self.height, self.note.page, True ) + else: + x = min( self.x, oldX ) + y = min( self.y, oldY ) + endx = max( self.x + self.width, oldEndX ) + endy = max( self.y, oldY ) + self.height + self.owner.invalidatePreview( x, y, endx-x, endy-y, self.note.page, True ) + + self.firstTransform = False + + def updateDragLimits( self, dragLimits, leftBound, rightBound, widthBound, maxRightBound ): + left = leftBound - self.note.cs.onset + right = rightBound - self.note.cs.duration - self.note.cs.onset + up = Config.MAXIMUM_PITCH - self.note.cs.pitch + down = Config.MINIMUM_PITCH - self.note.cs.pitch + short = Config.MINIMUM_NOTE_DURATION - self.note.cs.duration + long = widthBound - self.note.cs.duration - self.note.cs.onset + + if dragLimits[0][0] < left: dragLimits[0][0] = left + if dragLimits[0][1] > right: dragLimits[0][1] = right + if dragLimits[1][0] < down: dragLimits[1][0] = down + if dragLimits[1][1] > up: dragLimits[1][1] = up + if dragLimits[2][0] < short: dragLimits[2][0] = short + if dragLimits[2][1] > long: dragLimits[2][1] = long + + # store the current loc as a reference point + self.baseOnset = self.note.cs.onset + self.basePitch = self.note.cs.pitch + self.baseDuration = self.note.cs.duration + + def playSampleNote( self, full=True ): + secs_per_tick = 0.025 + csnd = new_csound_client() + + if full: + onset = self.note.cs.onset + instrumentId = self.note.cs.instrumentId + self.note.cs.onset = 0 + self.note.cs.instrumentId = self.owner.instrument["id"] + csnd.play( self.note.cs, 0.024) + self.note.cs.onset = onset + self.note.cs.instrumentId = instrumentId + else: + onset = self.note.cs.onset + duration = self.note.cs.duration + instrumentId = self.note.cs.instrumentId + self.note.cs.onset = 0 + self.note.cs.duration = 10 + self.note.cs.instrumentId = self.owner.instrument["id"] + csnd.play( self.note.cs, 0.024) + self.note.cs.onset = onset + self.note.cs.duration = duration + self.note.cs.instrumentId = instrumentId + + #======================================================= + # Events + + # handleButtonPress returns: + # -2, not a hit but there was X overlap + # -1, event occurs before us so don't bother checking any later notes + # 0, event didn't hit + # 1, event was handled + def handleButtonPress( self, emitter, event ): + eX = event.x - self.x + if eX < 0: + return -1 # event occurs before us, no point in checking further + if eX > self.width: + return 0 # no X overlap + + eY = event.y - self.y + if eY < 0 or eY > self.height: + return -2 # not a hit, but it was in our X range + + if event.button == 3: + print "Show some note parameters!?!" + #self.noteParameters = NoteParametersWindow( self.note, self.getNoteParameters ) + return 1 # handled + + playSample = False + + if event.type == gtk.gdk._2BUTTON_PRESS: # select bar + self.potentialDeselect = False + start = 0 + check = self.note.cs.onset - Config.TICKS_PER_BEAT + while start <= check: start += Config.TICKS_PER_BEAT + stop = start + Config.TICKS_PER_BEAT + check += self.note.cs.duration + while stop < check: stop += Config.TICKS_PER_BEAT + emitter.selectNotesByBar( self.note.track, start, stop ) + elif event.type == gtk.gdk._3BUTTON_PRESS: # select track + self.potentialDeselect = False + emitter.selectNotesByTrack( self.note.track ) + else: + if self.selected: # we already selected, might want to delected + self.potentialDeselect = True + else: + emitter.selectNotes( { self.note.track: [ self ] } ) + playSample = True + + percent = eX/self.width + if percent < 0.3: emitter.setCurrentAction( "note-drag-onset", self ) + elif percent > 0.7: emitter.setCurrentAction( "note-drag-duration", self ) + else: + emitter.setCurrentAction( "note-drag-pitch", self ) + if playSample: self.playSampleNote() + + return 1 + + def handleButtonRelease( self, emitter, event, buttonPressCount ): + + if self.potentialDeselect: + self.potentialDeselect = False + emitter.deselectNotes( { self.note.track: [ self ] } ) + + emitter.doneCurrentAction() + + return True + + def noteDragOnset( self, do, stream ): + self.potentialDeselect = False + if do != self.lastDragO: + self.lastDragO = do + stream += [ self.note.id, self.baseOnset + do ] + + def noteDragPitch( self, dp, stream ): + self.potentialDeselect = False + if dp != self.lastDragP: + self.lastDragP = dp + stream += [ self.note.id, self.basePitch + dp ] + + def noteDragDuration( self, dd, stream ): + self.potentialDeselect = False + if dd != self.lastDragD: + self.lastDragD = dd + stream += [ self.note.id, self.baseDuration + dd ] + + def doneNoteDrag( self, emitter ): + self.baseOnset = self.note.cs.onset + self.basePitch = self.note.cs.pitch + self.baseDuration = self.note.cs.duration + + self.lastDragO = 0 + self.lastDragP = 0 + self.lastDragD = 0 + + def noteDecOnset( self, step, leftBound, stream ): + if self.selected: + if leftBound < self.note.cs.onset: + onset = max( self.note.cs.onset+step, leftBound ) + stream += [ self.note.id, onset ] + return onset + self.note.cs.duration + return self.end + + def noteIncOnset( self, step, rightBound, stream ): + if self.selected: + if rightBound > self.end: + onset = min( self.end+step, rightBound ) - self.note.cs.duration + stream += [ self.note.id, onset ] + return onset + return self.note.cs.onset + + def noteDecPitch( self, step, stream ): + if self.note.cs.pitch > Config.MINIMUM_PITCH: + stream += [ self.note.id, max( self.note.cs.pitch+step, Config.MINIMUM_PITCH ) ] + + def noteIncPitch( self, step, stream ): + if self.note.cs.pitch < Config.MAXIMUM_PITCH: + stream += [ self.note.id, min( self.note.cs.pitch+step, Config.MAXIMUM_PITCH ) ] + + def noteDecDuration( self, step, stream ): + if self.note.cs.duration > Config.MINIMUM_NOTE_DURATION: + stream += [ self.note.id, max( self.note.cs.duration+step, Config.MINIMUM_NOTE_DURATION ) ] + + def noteIncDuration( self, step, rightBound, stream ): + if self.selected: + if self.end < rightBound: + stream += [ self.note.id, min( self.end+step, rightBound ) - self.note.cs.onset ] + + def noteDecVolume( self, step, stream ): + if self.note.cs.amplitude > 0: + stream += [ self.note.id, max( self.note.cs.amplitude+step, 0 ) ] + + def noteIncVolume( self, step, stream ): + if self.note.cs.amplitude < 1: + stream += [ self.note.id, min( self.note.cs.amplitude+step, 1 ) ] + + def handleMarqueeSelect( self, emitter, start, stop ): + intersectionY = [ max(start[1],self.y), min(stop[1],self.y+self.height) ] + if intersectionY[0] > intersectionY[1]: + return False + + intersectionX = [ max(start[0],self.x), min(stop[0],self.x+self.width) ] + if intersectionX[0] > intersectionX[1]: + return False + + return True + + # updateTooltip returns: + # -2, not a hit but there was X overlap + # -1, event occurs before us so don't bother checking any later notes + # 0, event didn't hit + # 1, event was handled + def updateTooltip( self, emitter, event ): + eX = event.x - self.x + if eX < 0: + return -1 # event occurs before us, no point in checking further + if eX > self.width: + return 0 # no X overlap + + eY = event.y - self.y + if eY < 0 or eY > self.height: + return -2 # not a hit, but it was in our X range + + percent = eX/self.width + if percent < 0.3: emitter.setCursor("drag-onset") + elif percent > 0.7: emitter.setCursor("drag-duration") + else: emitter.setCursor("drag-pitch") + + return 1 # we handled it + + #======================================================= + # Selection + + def setSelected( self, state ): + if self.selected != state: + self.selected = state + self.owner.invalidatePreview( self.x, self.y, self.width, self.height, self.note.page ) + return True # state changed + return False # state is the same + + def getSelected( self ): + return self.selected + + #======================================================= + # Draw + + def draw( self, win, gc, startX, stopX ): + if stopX < self.x: return False # we don't need to draw and no one after us will draw + if startX > self.x + self.width: return True # we don't need to draw, but maybe a later note does + + # draw fill + self.gc.foreground = self.colors["Preview_Note_Fill"] + self.gc.set_clip_origin( self.x, self.y-self.owner.sampleNoteHeight ) + self.owner.previewBuffer.draw_rectangle( self.gc, True, self.x+1, self.y+1, self.width-2, self.owner.sampleNoteHeight-2 ) + # draw border + if self.selected: + self.gc.foreground = self.colors["Preview_Note_Selected"] + else: + self.gc.foreground = self.colors["Preview_Note_Border"] + self.gc.set_clip_origin( self.x, self.y ) + endX = self.x + self.width - 3 + self.owner.previewBuffer.draw_rectangle( self.gc, True, self.x, self.y, self.width-3, self.owner.sampleNoteHeight ) + self.gc.set_clip_origin( endX-self.owner.sampleNoteMask.endOffset, self.y ) + self.owner.previewBuffer.draw_rectangle( self.gc, True, endX, self.y, 3, self.owner.sampleNoteHeight ) + + return True # we drew something + diff --git a/Jam/Picker.py b/Jam/Picker.py index ad136da..e7f8b82 100644 --- a/Jam/Picker.py +++ b/Jam/Picker.py @@ -304,7 +304,7 @@ class Loop( Picker ): id = newPages.pop() # new pageId - self.owner.noteDB.getPage( id ).local = False # flag as a global page + self.owner.noteDB.getPage( id ).setLocal( False ) # flag as a global page self.addBlock( id, filename[:-4] ) diff --git a/Jam/Popup.py b/Jam/Popup.py index 587a34d..2326e3a 100644 --- a/Jam/Popup.py +++ b/Jam/Popup.py @@ -8,7 +8,23 @@ import Config from gettext import gettext as _ from sugar.graphics import style from sugar.graphics.palette import Palette, Invoker, _palette_observer +import gobject +import Jam.Block as Block +from Util.NoteDB import PARAMETER +from Util.CSoundNote import CSoundNote +from Util.CSoundClient import new_csound_client +from Jam.Parasite import LoopParasite + +from Generation.Generator import generator1, GenerationParameters + +class SELECTNOTES: + ALL = -1 + NONE = 0 + ADD = 1 + REMOVE = 2 + FLIP = 3 + EXCLUSIVE = 4 class NoneInvoker( Invoker ): @@ -31,17 +47,22 @@ class Popup( Palette ): self.owner = owner + self.block = None + self.props.invoker = NoneInvoker() self.set_property( "position", Palette.AT_CURSOR ) self.set_group_id( "TamTamPopup" ) self._set_state( Palette.SECONDARY ) # skip to fully exposed - self.connect( "key-press-event", self.owner.onKeyPress ) - self.connect( "key-release-event", self.owner.onKeyRelease ) + self.connect( "key-press-event", self.on_key_press ) + self.connect( "key-release-event", self.on_key_release ) self.connect( "focus_out_event", self.closePopup ) + def destroy( self ): + pass + def _leave_notify_event_cb( self, widget, event ): return # don't popdown() @@ -59,6 +80,7 @@ class Popup( Palette ): Palette.popup( self, immediate ) def popdown( self, immediate = False ): + self.block = None Palette.popdown( self, immediate ) @@ -72,12 +94,20 @@ class Popup( Palette ): def closePopup( self, widget, event ): self.popdown( True ) + def on_key_press( self, widget, event ): + self.owner.onKeyPress( widget, event ) + + def on_key_release( self, widget, event ): + self.owner.onKeyRelease( widget, event ) + class Instrument( Popup ): def __init__( self, label, owner ): Popup.__init__( self, label, owner ) + self.settingBlock = False + self.GUI = {} self.GUI["mainBox"] = gtk.VBox() @@ -131,36 +161,44 @@ class Instrument( Popup ): self.GUI["reverbImage"] = gtk.Image() self.GUI["reverbBox"].pack_start( self.GUI["reverbImage"], False, padding = style.DEFAULT_PADDING ) - self.GUI["separator"] = gtk.HSeparator() - self.GUI["mainBox"].pack_start( self.GUI["separator"], padding = style.DEFAULT_PADDING ) + if False: # TEMP quote out + self.GUI["separator"] = gtk.HSeparator() + self.GUI["mainBox"].pack_start( self.GUI["separator"], padding = style.DEFAULT_PADDING ) - #-- Export -------------------------------------------- - self.GUI["exportBox"] = gtk.HBox() - self.GUI["mainBox"].pack_start( self.GUI["exportBox"], padding = style.DEFAULT_PADDING ) - self.GUI["exportEntry"] = gtk.Entry() - self.GUI["exportEntry"].modify_fg( gtk.STATE_NORMAL, self.owner.colors["black"] ) - self.GUI["exportEntry"].modify_fg( gtk.STATE_ACTIVE, self.owner.colors["black"] ) - self.GUI["exportBox"].pack_start( self.GUI["exportEntry"], padding = style.DEFAULT_PADDING ) - self.GUI["exportButton"] = gtk.Button( "Export" ) - self.GUI["exportBox"].pack_start( self.GUI["exportButton"], False, padding = style.DEFAULT_PADDING ) + #-- Export -------------------------------------------- + self.GUI["exportBox"] = gtk.HBox() + self.GUI["mainBox"].pack_start( self.GUI["exportBox"], padding = style.DEFAULT_PADDING ) + self.GUI["exportEntry"] = gtk.Entry() + self.GUI["exportEntry"].modify_fg( gtk.STATE_NORMAL, self.owner.colors["black"] ) + self.GUI["exportEntry"].modify_fg( gtk.STATE_ACTIVE, self.owner.colors["black"] ) + self.GUI["exportBox"].pack_start( self.GUI["exportEntry"], padding = style.DEFAULT_PADDING ) + self.GUI["exportButton"] = gtk.Button( "Export" ) + self.GUI["exportBox"].pack_start( self.GUI["exportButton"], False, padding = style.DEFAULT_PADDING ) self.GUI["mainBox"].show_all() def setBlock( self, block ): + self.settingBlock = True + self.block = block self.GUI["volumeAdjustment"].set_value( block.getData( "volume" ) ) self.GUI["panAdjustment"].set_value( block.getData( "pan" ) ) self.GUI["reverbAdjustment"].set_value( block.getData( "reverb" ) ) - self.GUI["exportEntry"].set_text( block.getData( "name" ) ) + #self.GUI["exportEntry"].set_text( block.getData( "name" ) ) + + self.settingBlock = False def handleVolume( self, widget ): - self.block.setData( "volume", widget.get_value() ) + if not self.settingBlock: + self.block.setData( "volume", widget.get_value() ) def handlePan( self, widget ): - self.block.setData( "pan", widget.get_value() ) + if not self.settingBlock: + self.block.setData( "pan", widget.get_value() ) def handleReverb( self, widget ): - self.block.setData( "reverb", widget.get_value() ) + if not self.settingBlock: + self.block.setData( "reverb", widget.get_value() ) class Drum( Popup ): @@ -168,13 +206,1058 @@ class Drum( Popup ): def __init__( self, label, owner ): Popup.__init__( self, label, owner ) + self.settingBlock = False + + self.GUI = {} + + self.GUI["mainBox"] = gtk.VBox() + self.set_content( self.GUI["mainBox"] ) + + #-- Volume -------------------------------------------- + self.GUI["volumeBox"] = gtk.HBox() + self.GUI["mainBox"].pack_start( self.GUI["volumeBox"], padding = style.DEFAULT_PADDING ) + self.GUI["volumeLabel"] = gtk.Label( _("Volume:") ) + self.GUI["volumeLabel"].set_size_request( 130, -1 ) + self.GUI["volumeLabel"].set_alignment( 0.0, 0.5 ) + self.GUI["volumeBox"].pack_start( self.GUI["volumeLabel"], False, padding = style.DEFAULT_PADDING ) + self.GUI["volumeAdjustment"] = gtk.Adjustment( 0.5, 0.0, 1.0, 0.1, 0.1, 0 ) + self.GUI["volumeAdjustment"].connect( 'value-changed', self.handleVolume ) + self.GUI["volumeSlider"] = gtk.HScale( adjustment = self.GUI["volumeAdjustment"] ) + self.GUI["volumeSlider"].set_size_request( 250, -1 ) + self.GUI["volumeSlider"].set_draw_value( False ) + self.GUI["volumeBox"].pack_start( self.GUI["volumeSlider"], False, padding = style.DEFAULT_PADDING ) + self.GUI["volumeImage"] = gtk.Image() + self.GUI["volumeBox"].pack_start( self.GUI["volumeImage"], False, padding = style.DEFAULT_PADDING ) + + #-- Reverb -------------------------------------------- + self.GUI["reverbBox"] = gtk.HBox() + self.GUI["mainBox"].pack_start( self.GUI["reverbBox"], padding = style.DEFAULT_PADDING ) + self.GUI["reverbLabel"] = gtk.Label( _("Reverb:") ) + self.GUI["reverbLabel"].set_size_request( 130, -1 ) + self.GUI["reverbLabel"].set_alignment( 0.0, 0.5 ) + self.GUI["reverbBox"].pack_start( self.GUI["reverbLabel"], False, padding = style.DEFAULT_PADDING ) + self.GUI["reverbAdjustment"] = gtk.Adjustment( 0.5, 0, 1.0, 0.1, 0.1, 0 ) + self.GUI["reverbAdjustment"].connect( 'value-changed', self.handleReverb ) + self.GUI["reverbSlider"] = gtk.HScale( adjustment = self.GUI["reverbAdjustment"] ) + self.GUI["reverbSlider"].set_size_request( 250, -1 ) + self.GUI["reverbSlider"].set_draw_value( False ) + self.GUI["reverbBox"].pack_start( self.GUI["reverbSlider"], False, padding = style.DEFAULT_PADDING ) + self.GUI["reverbImage"] = gtk.Image() + self.GUI["reverbBox"].pack_start( self.GUI["reverbImage"], False, padding = style.DEFAULT_PADDING ) + + self.GUI["generationSeparator"] = gtk.HSeparator() + self.GUI["mainBox"].pack_start( self.GUI["generationSeparator"], padding = style.DEFAULT_PADDING ) + + #-- Beats --------------------------------------------- + self.GUI["beatsBox"] = gtk.HBox() + self.GUI["mainBox"].pack_start( self.GUI["beatsBox"], padding = style.DEFAULT_PADDING ) + self.GUI["beatsLabel"] = gtk.Label( _("Beats:") ) + self.GUI["beatsLabel"].set_size_request( 130, -1 ) + self.GUI["beatsLabel"].set_alignment( 0.0, 0.5 ) + self.GUI["beatsBox"].pack_start( self.GUI["beatsLabel"], False, padding = style.DEFAULT_PADDING ) + self.GUI["beatsAdjustment"] = gtk.Adjustment( 4, 2, Config.MAXIMUM_BEATS, 1, 1, 0 ) + self.GUI["beatsAdjustment"].connect( 'value-changed', self.handleBeats ) + self.GUI["beatsSlider"] = gtk.HScale( adjustment = self.GUI["beatsAdjustment"] ) + self.GUI["beatsSlider"].set_size_request( 250, -1 ) + self.GUI["beatsSlider"].set_draw_value( False ) + self.GUI["beatsBox"].pack_start( self.GUI["beatsSlider"], False, padding = style.DEFAULT_PADDING ) + self.GUI["beatsImage"] = gtk.Image() + self.GUI["beatsBox"].pack_start( self.GUI["beatsImage"], False, padding = style.DEFAULT_PADDING ) + + #-- Regularity ---------------------------------------- + self.GUI["regularityBox"] = gtk.HBox() + self.GUI["mainBox"].pack_start( self.GUI["regularityBox"], padding = style.DEFAULT_PADDING ) + self.GUI["regularityLabel"] = gtk.Label( _("Regularity:") ) + self.GUI["regularityLabel"].set_size_request( 130, -1 ) + self.GUI["regularityLabel"].set_alignment( 0.0, 0.5 ) + self.GUI["regularityBox"].pack_start( self.GUI["regularityLabel"], False, padding = style.DEFAULT_PADDING ) + self.GUI["regularityAdjustment"] = gtk.Adjustment( 0.5, 0.0, 1.0, 0.1, 0.1, 0 ) + self.GUI["regularityAdjustment"].connect( 'value-changed', self.handleRegularity ) + self.GUI["regularitySlider"] = gtk.HScale( adjustment = self.GUI["regularityAdjustment"] ) + self.GUI["regularitySlider"].set_size_request( 250, -1 ) + self.GUI["regularitySlider"].set_draw_value( False ) + self.GUI["regularityBox"].pack_start( self.GUI["regularitySlider"], False, padding = style.DEFAULT_PADDING ) + self.GUI["regularityImage"] = gtk.Image() + self.GUI["regularityBox"].pack_start( self.GUI["regularityImage"], False, padding = style.DEFAULT_PADDING ) + + #-- Generate ------------------------------------------ + self.GUI["generateBox"] = gtk.HBox() + self.GUI["mainBox"].pack_start( self.GUI["generateBox"], padding = style.DEFAULT_PADDING ) + self.GUI["regenerateButton"] = gtk.Button( "Regenerate" ) + self.GUI["regenerateButton"].connect( "clicked", self.handleRegenerate ) + self.GUI["generateBox"].pack_start( self.GUI["regenerateButton"], True, False, padding = style.DEFAULT_PADDING ) + self.GUI["clearButton"] = gtk.Button( "Clear" ) + self.GUI["clearButton"].connect( "clicked", self.handleClear ) + self.GUI["generateBox"].pack_start( self.GUI["clearButton"], True, False, padding = style.DEFAULT_PADDING ) + + self.GUI["mainBox"].show_all() + + def setBlock( self, block ): + self.settingBlock = True + + self.block = block + self.GUI["volumeAdjustment"].set_value( block.getData( "volume" ) ) + self.GUI["reverbAdjustment"].set_value( block.getData( "reverb" ) ) + self.GUI["beatsAdjustment"].set_value( block.getData( "beats" ) ) + self.GUI["regularityAdjustment"].set_value( block.getData( "regularity" ) ) + + self.settingBlock = False + + def handleVolume( self, widget ): + if not self.settingBlock: + self.block.setData( "volume", widget.get_value() ) + + def handleReverb( self, widget ): + if not self.settingBlock: + self.block.setData( "reverb", widget.get_value() ) + + def handleBeats( self, widget ): + if not self.settingBlock: + self.block.setData( "beats", int(round( widget.get_value() )) ) + + def handleRegularity( self, widget ): + if not self.settingBlock: + self.block.setData( "regularity", widget.get_value() ) + + def handleRegenerate( self, widget ): + self.block.regenerate() + + def handleClear( self, widget ): + self.block.clear() + +class Loop( Popup ): + + def __init__( self, label, owner ): + Popup.__init__( self, label, owner ) + + self.settingBlock = False + + self.gc = self.owner.gc + self.colors = self.owner.colors + self.sampleNoteMask = self.owner.sampleNoteMask + + self.noteDB = self.owner.noteDB + self.csnd = new_csound_client() + self.GUI = {} self.GUI["mainBox"] = gtk.VBox() self.set_content( self.GUI["mainBox"] ) + #-- Beats --------------------------------------------- + self.GUI["beatsBox"] = gtk.HBox() + self.GUI["mainBox"].pack_start( self.GUI["beatsBox"], padding = style.DEFAULT_PADDING ) + self.GUI["beatsLabel"] = gtk.Label( _("Beats:") ) + self.GUI["beatsLabel"].set_size_request( 130, -1 ) + self.GUI["beatsLabel"].set_alignment( 0.0, 0.5 ) + self.GUI["beatsBox"].pack_start( self.GUI["beatsLabel"], False, padding = style.DEFAULT_PADDING ) + self.GUI["beatsAdjustment"] = gtk.Adjustment( 4, 2, Config.MAXIMUM_BEATS, 1, 1, 0 ) + self.GUI["beatsAdjustment"].connect( 'value-changed', self.handleBeats ) + self.GUI["beatsSlider"] = gtk.HScale( adjustment = self.GUI["beatsAdjustment"] ) + self.GUI["beatsSlider"].set_size_request( 250, -1 ) + self.GUI["beatsSlider"].set_draw_value( False ) + self.GUI["beatsBox"].pack_start( self.GUI["beatsSlider"], False, padding = style.DEFAULT_PADDING ) + self.GUI["beatsImage"] = gtk.Image() + self.GUI["beatsBox"].pack_start( self.GUI["beatsImage"], False, padding = style.DEFAULT_PADDING ) + + #-- Regularity ---------------------------------------- + self.GUI["regularityBox"] = gtk.HBox() + self.GUI["mainBox"].pack_start( self.GUI["regularityBox"], padding = style.DEFAULT_PADDING ) + self.GUI["regularityLabel"] = gtk.Label( _("Regularity:") ) + self.GUI["regularityLabel"].set_size_request( 130, -1 ) + self.GUI["regularityLabel"].set_alignment( 0.0, 0.5 ) + self.GUI["regularityBox"].pack_start( self.GUI["regularityLabel"], False, padding = style.DEFAULT_PADDING ) + self.GUI["regularityAdjustment"] = gtk.Adjustment( 0.5, 0.0, 1.0, 0.1, 0.1, 0 ) + self.GUI["regularityAdjustment"].connect( 'value-changed', self.handleRegularity ) + self.GUI["regularitySlider"] = gtk.HScale( adjustment = self.GUI["regularityAdjustment"] ) + self.GUI["regularitySlider"].set_size_request( 250, -1 ) + self.GUI["regularitySlider"].set_draw_value( False ) + self.GUI["regularityBox"].pack_start( self.GUI["regularitySlider"], False, padding = style.DEFAULT_PADDING ) + self.GUI["regularityImage"] = gtk.Image() + self.GUI["regularityBox"].pack_start( self.GUI["regularityImage"], False, padding = style.DEFAULT_PADDING ) + + #-- Generate ------------------------------------------ + self.GUI["generateBox"] = gtk.HBox() + self.GUI["mainBox"].pack_start( self.GUI["generateBox"], padding = style.DEFAULT_PADDING ) + self.GUI["regenerateButton"] = gtk.Button( "Regenerate" ) + self.GUI["regenerateButton"].connect( "clicked", self.handleRegenerate ) + self.GUI["generateBox"].pack_start( self.GUI["regenerateButton"], True, False, padding = style.DEFAULT_PADDING ) + self.GUI["clearButton"] = gtk.Button( "Clear" ) + self.GUI["clearButton"].connect( "clicked", self.handleClear ) + self.GUI["generateBox"].pack_start( self.GUI["clearButton"], True, False, padding = style.DEFAULT_PADDING ) + self.GUI["recordButton"] = gtk.ToggleButton( "Record" ) + self.GUI["recordButton"].connect( "toggled", self.handleRecord ) + self.GUI["generateBox"].pack_start( self.GUI["recordButton"], True, False, padding = style.DEFAULT_PADDING ) + + #-- Preview ------------------------------------------- + self.GUI["previewBox"] = gtk.HBox() + self.GUI["mainBox"].pack_start( self.GUI["previewBox"], padding = style.DEFAULT_PADDING ) + self.GUI["previewEventBox"] = gtk.EventBox() + self.GUI["previewEventBox"].add_events(gtk.gdk.POINTER_MOTION_MASK|gtk.gdk.POINTER_MOTION_HINT_MASK) + self.GUI["previewEventBox"].connect( "button-press-event", self.handlePreviewPress ) + self.GUI["previewEventBox"].connect( "button-release-event", self.handlePreviewRelease ) + self.GUI["previewEventBox"].connect( "motion-notify-event", self.handlePreviewMotion ) + self.GUI["previewEventBox"].connect( "leave-notify-event", self.handlePreviewLeave ) + self.GUI["previewBox"].pack_start( self.GUI["previewEventBox"], True, padding = style.DEFAULT_PADDING ) + self.previewDA = self.GUI["previewDA"] = gtk.DrawingArea() + self.GUI["previewDA"].connect( "size-allocate", self.handlePreviewAlloc ) + self.GUI["previewDA"].connect( "expose-event", self.handlePreviewExpose ) + self.GUI["previewEventBox"].add( self.GUI["previewDA"] ) + self.GUI["mainBox"].show_all() + self.previewDA.alloced = False + self.previewDirty = False + self.previewDirtyRect = gtk.gdk.Rectangle( 0, 0, 0, 0 ) + self.dirtyRectToAdd = gtk.gdk.Rectangle( 0, 0, 0, 0 ) + + self.sampleBg = self.owner.sampleBg + self.GUI["previewDA"].set_size_request( -1, self.sampleBg.get_size()[1] ) + self.sampleNoteHeight = self.owner.sampleNoteHeight + self.sampleNoteMask = self.owner.sampleNoteMask + + self.pitchPerPixel = float(Config.NUMBER_OF_POSSIBLE_PITCHES-1) / (self.sampleBg.get_size()[1] - self.sampleNoteHeight) + self.pixelsPerPitch = float(self.sampleBg.get_size()[1] - self.sampleNoteHeight)/(Config.MAXIMUM_PITCH - Config.MINIMUM_PITCH) + # Temporary Initialization + self.pixelsPerTick = [0] + [ 1 for i in range(1,Config.MAXIMUM_BEATS+1) ] + self.ticksPerPixel = [0] + [ 1 for i in range(1,Config.MAXIMUM_BEATS+1) ] + + 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.recording = False + self.recordLoop = None + self.recordingNote = None + + self.owner.noteDB.addListener( self, LoopParasite ) + + def destroy( self ): + self.owner.noteDB.deleteListener( self ) + + Popup.destroy() + + def setBlock( self, block ): + self.settingBlock = True + + if self.GUI["recordButton"].get_active(): + self.GUI["recordButton"].set_active( False ) + + self.block = block + self.GUI["beatsAdjustment"].set_value( block.getData( "beats" ) ) + self.GUI["regularityAdjustment"].set_value( block.getData( "regularity" ) ) + + root = block.getRoot() + if root.type == Block.Instrument: + self.instrument = { "id": root.getData( "id" ), + "amplitude": root.getData( "volume" ), + "pan": root.getData( "pan" ), + "reverb": root.getData( "reverb" ) } + else: + self.instrument = self.owner.getInstrument() + + self.curPage = block.getData("id") + self.curBeats = block.getData("beats") + + 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.playheadT = 0 + self.playheadX = Config.TRACK_SPACING_DIV2 + + self.settingBlock = False + + if self.previewDA.alloced: + self.invalidatePreview( 0, 0, self.previewDA.width, self.previewDA.height, -1, True ) + + def popdown( self, immediate = False ): + self.applyNoteSelection( SELECTNOTES.NONE, 0, [], self.curPage ) + + if self.GUI["recordButton"].get_active(): + self.GUI["recordButton"].set_active( False ) + + Popup.popdown( self, immediate ) + + def getPage( self ): + if self.block != None: + return self.block.getData("id") + else: + return -1 + + #======================================================= + # Handelers + + def handleBeats( self, widget ): + if not self.settingBlock: + self.curBeats = int(round( widget.get_value() )) + self.block.setData( "beats", self.curBeats ) + for n in self.owner.noteDB.getNotesByTrack( self.getPage(), 0, self ): + n.updateTransform( True ) + self.invalidatePreview( 0, 0, self.previewDA.width, self.previewDA.height ) + + if self.recordLoop: + self.recordLoop = self.owner._playLoop( self.instrument["id"], self.instrument["amplitude"], self.instrument["reverb"], [ self.curPage ], self.recordLoop, force = True ) + + def handleRegularity( self, widget ): + if not self.settingBlock: + self.block.setData( "regularity", widget.get_value() ) + + def handleRegenerate( self, widget ): + parameters = GenerationParameters( + rythmRegularity = self.block.getData( "regularity" ), + pitchRegularity = self.block.getData( "regularity" ) ) + + self.owner._generateTrack( self.instrument["id"], self.curPage, 0, parameters, generator1 ) + + self.block.updateLoop() + + if self.recordLoop: + self.recordLoop = self.owner._playLoop( self.instrument["id"], self.instrument["amplitude"], self.instrument["reverb"], [ self.curPage ], self.recordLoop, force = True ) + + def handleClear( self, widget ): + self.block.clear() + + if self.recordLoop: + self.recordLoop = self.owner._playLoop( self.instrument["id"], self.instrument["amplitude"], self.instrument["reverb"], [ self.curPage ], self.recordLoop, force = True ) + + def handleRecord( self, widget ): + if widget.get_active(): + self.startRecording() + else: + self.stopRecording() + + def handlePreviewPress( self, widget, event ): + if event.button != 1: + return + + 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) ] + + page = self.block.getData("id") + beats = self.block.getData("beats") + + notes = self.noteDB.getNotesByTrack( page, 0, self ) + last = len(notes)-1 + handled = 0 + for n in range(last+1): + 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 + return + else: # all other options mean we can stop looking + break + + if not handled or handled == -1: # event didn't overlap any notes, so we can draw + pitch = min( self.pixelsToPitchFloor( self.clickLoc[1] - self.previewDA.height + self.sampleNoteHeight//2 ), Config.NUMBER_OF_POSSIBLE_PITCHES-1) + Config.MINIMUM_PITCH + onset = self.pixelsToTicksFloor( beats, self.clickLoc[0] ) + cs = CSoundNote( onset, + pitch, + 0.75, + 0.5, + 1, + 0, + instrumentId = self.instrument["id"] ) + cs.pageId = page + id = self.noteDB.addNote( -1, page, 0, cs ) + n = self.noteDB.getNote( page, 0, id, self ) + self.selectNotes( { 0:[n] }, True ) + n.playSampleNote( False ) + + noteS = self.noteDB.getNotesByTrack( page, 0 ) + for note in noteS: + if note.cs.onset < onset and (note.cs.onset + note.cs.duration) > onset: + self.noteDB.updateNote(self.curPage, 0, note.id, PARAMETER.DURATION, onset - note.cs.onset) + + self.updateDragLimits() + self.clickLoc[0] += self.ticksToPixels( beats, 1 ) + self.setCurrentAction( "note-drag-duration", n ) + self.setCursor("drag-duration") + + def handlePreviewRelease( 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 + + if event.button != 1: + return + + if not self.curAction: + self.applyNoteSelection( SELECTNOTES.NONE, 0, [], self.curPage ) + return + + if not self.curActionObject: # there was no real action to carry out + self.curAction = False + 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 ) + self.updateTooltip( event ) + + def handlePreviewMotion( self, widget, event ): + if event.is_hint: + x, y, state = self.previewDA.window.get_pointer() + event.x = float(x) + event.y = float(y) + event.state = state + + if not self.clickButton: # we recieved this event but were never clicked! (probably a popup window was open) + self.updateTooltip( event ) + return + + if event.state & gtk.gdk.BUTTON1_MASK: + 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 ) + else: + self.updateTooltip( event ) + + def handlePreviewLeave( self, widget, event ): + self.setCursor("default") + + def handlePreviewAlloc( self, widget, allocation ): + self.previewDA.alloced = True + win = gtk.gdk.get_default_root_window() + self.previewDA.width = allocation.width + self.previewDA.height = allocation.height + self.previewBuffer = gtk.gdk.Pixmap( win, allocation.width, allocation.height ) + self.clearClipMask = gtk.gdk.Rectangle( 0, 0, allocation.width, allocation.height ) + + self.pixelsPerTick = [0] + [ self.previewDA.width/float(i*Config.TICKS_PER_BEAT) for i in range(1,Config.MAXIMUM_BEATS+1) ] + self.ticksPerPixel = [0] + [ 1.0/self.pixelsPerTick[i] for i in range(1,Config.MAXIMUM_BEATS+1) ] + + self.beatSpacing = [[0]] + for i in range(1,Config.MAXIMUM_BEATS+1): + self.beatSpacing.append( [ self.ticksToPixels( i, Config.TICKS_PER_BEAT*j ) for j in range(i) ] ) + + for n in self.owner.noteDB.getNotes( self ): + n.updateTransform( True ) + + self.invalidatePreview( 0, 0, allocation.width, allocation.height, -1, True ) + + def on_key_press( self, widget, event ): + keyval = event.keyval + + # backspace and del keys + if keyval == gtk.keysyms.Delete or keyval == gtk.keysyms.BackSpace: + if len( self.selectedNotes[0] ): + self.owner.noteDB.deleteNotes( + [ self.curPage, 0, len( self.selectedNotes[0] ) ] + + [ n.note.id for n in self.selectedNotes[0] ] + + [ -1 ] ) + self.block.updateLoop() + else: + self.owner.onKeyPress( widget, event ) + + #======================================================= + # Drawing + + def previewDraw( self ): + startX = self.previewDirtyRect.x + startY = self.previewDirtyRect.y + stopX = self.previewDirtyRect.x + self.previewDirtyRect.width + stopY = self.previewDirtyRect.y + self.previewDirtyRect.height + + page = self.block.getData("id") + beats = self.owner.noteDB.getPage(page).beats + + self.gc.set_clip_rectangle( self.previewDirtyRect ) + + # draw background + self.previewBuffer.draw_drawable( self.gc, self.sampleBg, 0, 0, 0, 0, self.previewDA.width-5, self.previewDA.height ) + self.previewBuffer.draw_drawable( self.gc, self.sampleBg, self.sampleBg.endOffset, 0, self.previewDA.width-5, 0, 5, self.previewDA.height ) + + # draw beat lines + self.gc.set_line_attributes( Config.BEAT_LINE_SIZE, gtk.gdk.LINE_ON_OFF_DASH, gtk.gdk.CAP_BUTT, gtk.gdk.JOIN_MITER ) + self.gc.foreground = self.colors["Beat_Line"] + for i in range(1,beats): + x = self.beatSpacing[beats][i] + self.previewBuffer.draw_line( self.gc, x, 1, x, self.previewDA.height-1 ) + + # draw notes + self.gc.set_clip_mask( self.sampleNoteMask ) + notes = self.owner.noteDB.getNotesByTrack( page, 0, self ) + for n in notes: + if not n.draw( self.previewBuffer, self.gc, startX, stopX ): break + + self.previewDirty = False + + def handlePreviewExpose( self, widget, event ): + if self.previewDirty: + self.previewDraw() + + self.gc.set_clip_rectangle( event.area ) + + # draw base + widget.window.draw_drawable( self.gc, self.previewBuffer, event.area.x, event.area.y, event.area.x, event.area.y, event.area.width, event.area.height ) + + 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.colors["Preview_Note_Selected"] + widget.window.draw_rectangle( self.gc, False, self.marqueeRect[0][0], self.marqueeRect[0][1], self.marqueeRect[1][0], self.marqueeRect[1][1] ) + + if self.recording: # 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.colors["black"] + widget.window.draw_line( self.gc, self.playheadX, event.area.y, self.playheadX, event.area.y + event.area.height ) + + def invalidatePreview( self, x, y, width, height, page = -1, base = True ): + if page != -1 and page != self.getPage(): + return + + self.dirtyRectToAdd.x = x + self.dirtyRectToAdd.y = y + self.dirtyRectToAdd.width = width + self.dirtyRectToAdd.height = height + + if base: # the base image has been dirtied + if not self.previewDirty: + self.previewDirtyRect.x = x + self.previewDirtyRect.y = y + self.previewDirtyRect.width = width + self.previewDirtyRect.height = height + else: + self.previewDirtyRect = self.previewDirtyRect.union( self.dirtyRectToAdd ) + self.previewDirty = True + + if self.previewDA.window != None: + self.previewDA.window.invalidate_rect( self.dirtyRectToAdd, True ) + + #======================================================= + # Recording + + def startRecording( self ): + if self.recording: + return + + self.owner.setPaused( True ) + self.owner.pushInstrument( self.instrument ) + self.owner.setKeyboardListener( self ) + + self.recordLoop = self.owner._playLoop( self.instrument["id"], self.instrument["amplitude"], self.instrument["reverb"], [ self.curPage ], force = True ) + self.updatePlayhead() + self.recordTimeout = gobject.timeout_add( 20, self._record_timeout ) + self.recording = True + + def stopRecording( self ): + if not self.recording: + return + + self.owner.setPaused( False ) + self.owner.popInstrument() + self.owner.setKeyboardListener( None ) + + gobject.source_remove( self.recordTimeout ) + self.recording = False + + if self.recordingNote: + self.finishNote() + + self.owner._stopLoop( self.recordLoop ) + self.recordLoop = None + self.clearPlayhead() + + def recordNote( self, pitch ): + onset = self.csnd.loopGetTick( self.recordLoop ) + #onset = Config.DEFAULT_GRID * int(onset / Config.DEFAULT_GRID + 0.5) + + cs = CSoundNote( onset, + pitch, + 0.75, + 0.5, + Config.DEFAULT_GRID, + 0, + instrumentId = self.instrument["id"] ) + cs.pageId = self.curPage + + for n in self.noteDB.getNotesByTrack( self.curPage, 0 ): + if onset < n.cs.onset: + break + if onset >= n.cs.onset + n.cs.duration: + continue + if onset < n.cs.onset + n.cs.duration - 2: + self.noteDB.deleteNote( n.page, n.track, n.id ) + elif onset - n.cs.onset < 1: + self.noteDB.deleteNote( n.page, n.track, n.id ) + else: + self.noteDB.updateNote( n.page, n.track, n.id, PARAMETER.DURATION, onset - n.cs.onset ) + break + + self.recordingNote = self.noteDB.addNote( -1, self.curPage, 0, cs ) + + self.recordLoop = self.owner._playLoop( self.instrument["id"], self.instrument["amplitude"], self.instrument["reverb"], [ self.curPage ], self.recordLoop, force = True ) + + def finishNote( self ): + self.recordingNote = None + + self.block.updateLoop() + + def _updateNote( self ): + tick = self.csnd.loopGetTick( self.recordLoop ) + #tick = Config.DEFAULT_GRID * int(tick / Config.DEFAULT_GRID + 0.5) + + note = self.noteDB.getNote( self.curPage, 0, self.recordingNote ) + + if tick < note.cs.onset: + tick = self.noteDB.getPage( self.curPage ).ticks + self.noteDB.updateNote( note.page, note.track, note.id, PARAMETER.DURATION, tick - note.cs.onset ) + for n in self.noteDB.getNotesByTrack( self.curPage, 0 ): + if n.cs.onset <= note.cs.onset: + continue + if n.cs.onset > note.cs.onset and n.cs.onset < note.cs.onset + note.cs.duration: + self.noteDB.deleteNote( n.page, n.track, n.id ) + else: + break + self.finishNote() + elif tick > note.cs.onset + note.cs.duration: + self.noteDB.updateNote( note.page, note.track, note.id, PARAMETER.DURATION, tick - note.cs.onset ) + for n in self.noteDB.getNotesByTrack( self.curPage, 0 ): + if n.cs.onset <= note.cs.onset: + continue + if n.cs.onset > note.cs.onset and n.cs.onset < note.cs.onset + note.cs.duration: + self.noteDB.deleteNote( n.page, n.track, n.id ) + else: + break + + def _record_timeout( self ): + self.updatePlayhead() + if self.recordingNote: + self._updateNote() + return True + + def updatePlayhead( self ): + ticks = self.csnd.loopGetTick( self.recordLoop ) + if self.playheadT != ticks: + self.invalidatePreview( self.playheadX-Config.PLAYHEAD_SIZE/2, 0, Config.PLAYHEAD_SIZE, self.previewDA.height, self.curPage, False ) + self.playheadX = self.ticksToPixels( self.curBeats, ticks ) + self.invalidatePreview( self.playheadX-Config.PLAYHEAD_SIZE/2, 0, Config.PLAYHEAD_SIZE, self.previewDA.height, self.curPage, False ) + self.playheadT = ticks + + return True + + def clearPlayhead( self ): + self.invalidatePreview( self.playheadX-Config.PLAYHEAD_SIZE/2, 0, Config.PLAYHEAD_SIZE, self.previewDA.height, self.curPage, False ) + + #======================================================= + # 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() + + 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 ) + + 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() + + 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 ) ) + + 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] ) + + self.curActionObject.playSampleNote( True ) + + 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 ) + + self.block.updateLoop() + + 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.previewDA.width: self.marqueeLoc[0] = self.previewDA.width + if self.marqueeLoc[1] < 0: self.marqueeLoc[1] = 0 + elif self.marqueeLoc[1] > self.previewDA.height: self.marqueeLoc[1] = self.previewDA.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.invalidatePreview( 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 = {} + + intersectionY = [ self.marqueeRect[0][1], stop[1] ] + + notes = [] + track = self.noteDB.getNotesByTrack( self.getPage(), 0, 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[0] = notes + + self.selectNotes( select ) + + self.marqueeLoc = False + self.doneCurrentAction() + + self.invalidatePreview( self.marqueeRect[0][0]-1, self.marqueeRect[0][1]-1, self.marqueeRect[1][0]+2, self.marqueeRect[1][1]+2, self.getPage(), False ) + + def updateTooltip( self, event ): + + notes = self.noteDB.getNotesByTrack( self.getPage(), 0, 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 + + if handled == -2: # event X overlapped with a note + self.setCursor("default") + return + + self.setCursor("pencil") + + def setCursor( self, cursor ): + self.window.set_cursor(self.cursor[cursor]) + + 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 pitchToPixels( self, pitch ): + return int(round( ( Config.MAXIMUM_PITCH - pitch ) * self.pixelsPerPitch )) + def ticksToPixelsFloor( self, beats, ticks ): + return int( ticks * self.pixelsPerTick[beats] ) + def pixelsToTicksFloor( self, beats, pixels ): + return int( pixels * self.ticksPerPixel[beats] ) + 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) + class Shortcut( Popup ): diff --git a/Jam/Toolbars.py b/Jam/Toolbars.py index 29d034b..08ce06f 100644 --- a/Jam/Toolbars.py +++ b/Jam/Toolbars.py @@ -63,6 +63,7 @@ class JamToolbar( gtk.Toolbar ): def _insert_separator( self, expand = False ): separator = gtk.SeparatorToolItem() + separator.set_draw( False ) separator.set_expand( expand ) self.insert( separator, -1 ) @@ -115,6 +116,7 @@ class DesktopToolbar( gtk.Toolbar ): def _insert_separator( self, expand = False ): separator = gtk.SeparatorToolItem() + separator.set_draw( False ) separator.set_expand( expand ) self.insert( separator, -1 ) -- cgit v0.9.1