From 11003147ac4ea947ec5017921019d668cb4953d1 Mon Sep 17 00:00:00 2001 From: Aleksey Lim Date: Tue, 28 Jun 2011 09:23:54 +0000 Subject: Switch to the singular sources tree and releasing scheme --- (limited to 'Jam') diff --git a/Jam/Block.py b/Jam/Block.py new file mode 100644 index 0000000..e6a7bf4 --- /dev/null +++ b/Jam/Block.py @@ -0,0 +1,898 @@ + +import pygtk +pygtk.require( '2.0' ) +import gtk + +import random + +import common.Util.InstrumentDB as InstrumentDB +import common.Config as Config + +from common.Util.NoteDB import PARAMETER + +#::: NOTE: +# All the graphics resources are loaded in Desktop and referenced here as necessary +#::: + +class Block: + + WIDTH = 100 + HEIGHT = 100 + + SNAP = 15 + + PAD = 4 + + KEYSIZE = 26 + KEYMASK_START = 309 + + def __init__( self, owner, data ): + self.owner = owner + self.gc = owner.gc + + self.data = {} + for key in data.keys(): + self.data[key] = data[key] + + self.type = Block + + self.width = Block.WIDTH + self.height = Block.HEIGHT + + self.parent = None + self.canChild = False + self.child = None + self.canParent = False + + self.canSubstitute = False + + self.parentOffest = 0 + + self.dragging = False # is currently dragging + self.placed = False # has been placed on the desktop at least once + + self.firstLoc = True + self.x = -1 + self.y = -1 + + self.active = False + + def dumpToStream( self, ostream, child = False ): + ostream.block_add( ClassToStr[ self.type ], self.active, self.x + self.width//2, self.y + self.height//2, child, self.data ) + if self.child: + self.child.dumpToStream( ostream, True ) + + def destroy( self ): + if self.child: + self.child.destroy() + self.child = None + self.invalidate_rect( not self.dragging ) + + def isPlaced( self ): + return self.placed + + def setPlaced( self, placed ): + self.placed = placed + + def getLoc( self ): + return ( self.x, self.y ) + + def setLoc( self, x, y ): + if x == self.x and y == self.y: return + + if self.firstLoc: + self.firstLoc = False + else: + self.invalidate_rect( not self.dragging ) + + self.x = int(x) + self.y = int(y) + self.endX = self.x + self.width + self.endY = self.y + self.height + + self.invalidate_rect( not self.dragging ) + + if self.child: + self.child.snapToParentLoc( self.getChildAnchor() ) + + def resetLoc( self ): + if self.oldParent != None: + self.oldParent.addChild( self ) + return False + else: + self.setLoc( self.oldLoc[0], self.oldLoc[1] ) + return True + + def getParentAnchor( self ): + return ( self.x + self.parentOffset, self.y ) + + def getChildAnchor( self ): + return ( self.endX, self.y ) + + def snapToParentLoc( self, loc ): + self.setLoc( loc[0] - self.parentOffset, loc[1] ) + + def substitute( self, block ): + pass # override in subclasses + + def testSubstitute( self, block ): + if self.child: + return self.child.testSubstitute( block ) + + def testChild( self, loc ): + + if not self.canParent: + return False + + if self.child: + return self.child.testChild( loc ) + elif abs( self.endX - loc[0] ) < Block.SNAP and abs( self.y - loc[1] ) < Block.SNAP: + return self + + return False + + def addChild( self, child ): + c = self.child + if self.child: + self.removeChild() + + self.child = child + child._addParent( self ) + child.snapToParentLoc( self.getChildAnchor() ) + + if c: + child.addChild( c ) + + def removeChild( self ): + self.child._removeParent() + self.child = None + + def _addParent( self, parent ): + self.parent = parent + + def _removeParent( self ): + self.parent = None + + def getRoot( self ): + if self.parent: return self.parent.getRoot() + return self + + def isActive( self ): + return self.active + + def setActive( self, state ): + self.active = state + self.invalidate_rect( not self.dragging ) + + def getData( self, key ): + return self.data[ key ] + + def setData( self, key, value ): + self.data[ key ] = value + + def testMouseOver( self, event ): + if self.child: + ret = self.child.testMouseOver( event ) + if ret: return ret + + x = event.x - self.x + y = event.y - self.y + + if 0 <= x <= self.width and 0 <= y <= self.height: + return -1 + + return False + + def button_press( self, event ): + + if event.y < self.y or event.y > self.endY: + return False + + return self._button_pressB( event ) + + def _button_pressB( self, event ): + + if event.x < self.x: + return False + + if event.x > self.endX: + if self.child: + return self.child._button_pressB( event ) + else: + return False + + self.oldParent = self.parent + self.oldLoc = ( self.x, self.y ) + self.dragOffset = ( event.x - self.x, event.y - self.y ) + + self._doButtonPress( event ) + + return self + + def _doButtonPress( self, event ): + pass # override in subclasses + + def button_release( self, event ): + if self.dragging: + self.dragging = False + self.placed = True + self.invalidateBranch() + + def motion_notify( self, event ): + + removeFromBlocks = not self.dragging and not self.parent + + if not self.dragging: + self.dragging = True + self.invalidate_rect() + + if self.parent: + self.parent.removeChild() + + self.setLoc( event.x - self.dragOffset[0], event.y - self.dragOffset[1] ) + + return removeFromBlocks + + def _beginDrag( self ): + self.dragging = True + self.dragOffset = ( self.width//2, self.height//2 ) + + def invalidateBranch( self, base = True ): + self.invalidate_rect( base ) + if self.child: + self.child.invalidateBranch( base ) + + def invalidate_rect( self, base = True ): + self.owner.invalidate_rect( self.x, self.y, self.width, self.height, base ) + + def draw( self, startX, startY, stopX, stopY, pixmap ): + if stopY <= self.y or startY >= self.endY: + return False + + self._drawB( startX, startY, stopX, stopY, pixmap ) + + def _drawB( self, startX, startY, stopX, stopY, pixmap ): + + if stopX <= self.x: + return False + + if self.child: + self.child._drawB( startX, startY, stopX, stopY, pixmap ) + + if startX >= self.endX: + return False + + self._doDraw( startX, startY, stopX, stopY, pixmap ) + + return True + + def _doDraw( self, startX, startY, stopX, stopY, pixmap ): + pass # override in subclasses + + def drawHighlight( self, startX, startY, stopX, stopY, pixmap ): + pass # override in subclasses + +class Instrument(Block): + + MASK_START = 0 + + #::: data format: + # { "name": name, "id": instrumentId [, "volume": 0-1, "pan": 0-1, "reverb": 0-1 ] } + #::: + def __init__( self, owner, data ): + Block.__init__( self, owner, data ) + + self.type = Instrument + + self.canParent = True + self.canSubstitute = True + + if not "volume" in self.data.keys(): + self.data["volume"] = 0.5 + if not "pan" in self.data.keys(): + self.data["pan"] = 0.5 + if not "reverb" in self.data.keys(): + self.data["reverb"] = 0 + + self.img = [ self.owner.getInstrumentImage( self.data["id"], False ), + self.owner.getInstrumentImage( self.data["id"], True ) ] + + def setData( self, key, value ): + 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"] + self.img = [ self.owner.getInstrumentImage( self.data["id"], False ), + self.owner.getInstrumentImage( self.data["id"], True ) ] + self.invalidate_rect( True ) + + if self.child and self.child.active: + self.owner.updateLoop( self.child ) + + def testSubstitute( self, block ): + ret = Block.testSubstitute( self, block ) + if ret: + return ret + + if block.type == Loop: + return False + + if abs( self.x - block.x ) < Block.SNAP and abs( self.y - block.y ) < Block.SNAP: + return self + + return False + + def _doButtonPress( self, event ): # we were hit with a button press + pass + + def button_release( self, event ): + if not self.dragging: + self.owner.activateInstrument( self ) + Block.button_release( self, event ) + + def _doDraw( self, startX, startY, stopX, stopY, pixmap ): + x = max( startX, self.x ) + y = max( startY, self.y ) + endX = min( stopX, self.endX ) + endY = min( stopY, self.endY ) + width = endX - x + height = endY - y + + # draw border + if self.active: self.gc.foreground = self.owner.colors["Border_Active"] + else: self.gc.foreground = self.owner.colors["Border_Inactive"] + self.gc.set_clip_origin( self.x-Instrument.MASK_START, self.y ) + pixmap.draw_rectangle( self.gc, True, x, y, width, height ) + + # draw block + self.gc.set_clip_origin( self.x-Instrument.MASK_START, self.y-self.height ) + pixmap.draw_drawable( self.gc, self.img[self.active], 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"] + self.gc.set_clip_origin( self.x-Instrument.MASK_START, self.y ) + pixmap.draw_rectangle( self.gc, True, self.x, self.y, self.width, self.height ) + +class Drum(Block): + + MASK_START = 100 + + KEYRECT = [ Block.PAD - 1, Block.HEIGHT + 1 - Block.PAD - Block.KEYSIZE, Block.KEYSIZE, Block.KEYSIZE ] + KEYRECT += [ KEYRECT[0]+KEYRECT[2], KEYRECT[1]+KEYRECT[3] ] + + #::: data format: + # { "name": name, "id": instrumentId [ , "page": pageId, "volume": 0-1, "reverb": 0-1, "beats": 2-12, "regularity": 0-1, "key": shortcut ] } + #::: + def __init__( self, owner, data ): + Block.__init__( self, owner, data ) + self.instrumentDB = InstrumentDB.getRef() + self.type = Drum + + 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.0 + if not "beats" in self.data.keys(): + self.data["beats"] = 4 #random.randint(2, 12) + if not "regularity" in self.data.keys(): + self.data["regularity"] = 0.8 #random.random() + if "key" not in self.data.keys(): + self.data["key"] = None + + self.owner.mapKey( self.data["key"], self ) + self.keyImage = [ self.owner.getKeyImage( self.data["key"], False ), + self.owner.getKeyImage( self.data["key"], True ) ] + + + 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.mapKey( None, self, self.data["key"] ) + self.owner.noteDB.deletePages( [ self.data["page"] ] ) + Block.destroy( self ) + + def setData( self, key, value ): + if key == "beats": + self.data["beats"] = value + self.owner.noteDB.updatePage( self.data["page"], PARAMETER.PAGE_BEATS, value ) + + elif key == "key": + oldKey = self.data["key"] + self.data["key"] = value + self.keyImage = [ self.owner.getKeyImage( value, False ), + self.owner.getKeyImage( value, True ) ] + self.invalidate_rect() + self.owner.mapKey( value, self, oldKey ) + + else: + self.data[key] = value + + if self.active: + self.owner.updateDrum( self ) + + def substitute( self, block ): + self.setData( "name", block.data["name"] ) + self.setData( "id", block.data["id"] ) + + self.img = [ self.owner.getInstrumentImage( self.data["id"], False ), + self.owner.getInstrumentImage( self.data["id"], True ) ] + + self.invalidate_rect( True ) + + if self.active: + self.owner.updateDrum( self ) + + def testSubstitute( self, block ): + ret = Block.testSubstitute( self, block ) + if ret: + return ret + + if block.type == Loop: + return False + + if self.instrumentDB.instId[block.data["id"]].kit == None: + return False + + if abs( self.x - block.x ) < Block.SNAP and abs( self.y - block.y ) < Block.SNAP: + return self + + return False + + def testMouseOver( self, event ): + ret = self.testWithinKey( event ) + if ret: return ret + + x = event.x - self.x + y = event.y - self.y + + if 0 <= x <= self.width and 0 <= y <= self.height: + return -1 + + return False + + def testWithinKey( self, event ): + x = event.x - self.x + y = event.y - self.y + + if Drum.KEYRECT[0] <= x <= Drum.KEYRECT[4] and Drum.KEYRECT[1] <= y <= Drum.KEYRECT[5]: + return self + + return False + + def _doButtonPress( self, event ): # we were hit with a button press + pass + + def button_release( self, event ): + if not self.dragging: + if self.active: + self.owner.deactivateDrum( self ) + else: + self.owner.activateDrum( self ) + Block.button_release( self, event ) + + def _doDraw( self, startX, startY, stopX, stopY, pixmap ): + x = max( startX, self.x ) + y = max( startY, self.y ) + endX = min( stopX, self.endX ) + endY = min( stopY, self.endY ) + width = endX - x + height = endY - y + + # draw border + if self.active: self.gc.foreground = self.owner.colors["Border_Active"] + else: self.gc.foreground = self.owner.colors["Border_Inactive"] + self.gc.set_clip_origin( self.x-Drum.MASK_START, self.y ) + pixmap.draw_rectangle( self.gc, True, x, y, width, height ) + + # draw block + self.gc.set_clip_origin( self.x-Drum.MASK_START, self.y-self.height ) + pixmap.draw_drawable( self.gc, self.img[self.active], x-self.x, y-self.y, x, y, width, height ) + + # draw key + self.gc.set_clip_origin( self.x+Drum.KEYRECT[0]-Block.KEYMASK_START, self.y+Drum.KEYRECT[1] ) + pixmap.draw_drawable( self.gc, self.keyImage[ self.active ], 0, 0, self.x+Drum.KEYRECT[0], self.y+Drum.KEYRECT[1], Block.KEYSIZE, Block.KEYSIZE ) + + + def drawHighlight( self, startX, startY, stopX, stopY, pixmap ): + self.gc.foreground = self.owner.colors["Border_Highlight"] + 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 drawKeyHighlight( self, pixmap ): + self.gc.foreground = self.owner.colors["Border_Highlight"] + self.gc.set_clip_origin( self.x+Drum.KEYRECT[0]-Block.KEYMASK_START, self.y+Drum.KEYRECT[1]-Block.KEYSIZE ) + pixmap.draw_rectangle( self.gc, True, self.x+Drum.KEYRECT[0], self.y+Drum.KEYRECT[1], Block.KEYSIZE, Block.KEYSIZE ) + + 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): + + HEAD = 13 + BEAT = 23 + TAIL = BEAT + Block.PAD + + WIDTH = [ HEAD + BEAT*(n-1) + TAIL for n in range(Config.MAXIMUM_BEATS+1) ] + + BEAT_MUL3 = BEAT*3 + + MASK_START = 200 + MASK_BEAT = MASK_START + HEAD + MASK_TAIL = MASK_START + HEAD + BEAT*3 + + KEYRECT = [ HEAD + Block.PAD, Block.HEIGHT - 2*Block.PAD - Block.KEYSIZE, Block.KEYSIZE, Block.KEYSIZE ] + KEYRECT += [ KEYRECT[0]+KEYRECT[2], KEYRECT[1]+KEYRECT[3] ] + + #::: data format: + # { "name": name, "id": pageId [, "beats": 2-12, "regularity": 0-1, "key": shortcut ] } + #::: + def __init__( self, owner, data ): + Block.__init__( self, owner, data ) + + self.type = Loop + + self.canParent = True + self.canChild = True + self.canSubstitute = True + + self.parentOffset = Loop.HEAD - 4 + + 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"] = 0.8 #random.random() + if "key" not in self.data.keys(): + self.data["key"] = None + + self.keyActive = False + self.keyImage = [ self.owner.getKeyImage( self.data["key"], False ), + self.owner.getKeyImage( self.data["key"], True ) ] + + self.img = [ self.owner.getLoopImage( self.data["id"], False ), + self.owner.getLoopImage( self.data["id"], True ) ] + + def destroy( self ): + if self.active: + self.owner.deactivateLoop( self ) + if self.keyActive: + self.owner.mapKey( None, self, self.data["key"] ) + self.owner.noteDB.deletePages( [ self.data["id"] ] ) + Block.destroy( self ) + + def _updateWidth( self ): + self.invalidateBranch( True ) + + oldWidth = self.width + + 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 ) ] + + def setData( self, key, value ): + + if key == "beats": + self.data["beats"] = value + self.owner.noteDB.updatePage( self.data["id"], PARAMETER.PAGE_BEATS, value ) + self._updateWidth() + self.updateLoop() + + elif key == "key": + oldKey = self.data["key"] + self.data["key"] = value + self.keyImage = [ self.owner.getKeyImage( value, False ), + self.owner.getKeyImage( value, True ) ] + self.invalidate_rect() + if self.keyActive: + self.owner.mapKey( value, self, oldKey ) + + else: + self.data[key] = value + + 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: + c = block.child + after = self + while c: + data = {} + for key in c.data.keys(): + data[key] = c.data[key] + + newid = self.owner.noteDB.duplicatePages( [ data["id"] ] )[data["id"]] + self.owner.updateLoopImage( newid ) + data["id"] = newid + + copy = Loop( self.owner, self.gc, data ) + after.addChild( copy ) + after = copy + c = c.child + elif self.child: + self.child.snapToParentLoc( self.getChildAnchor() ) + + if self.active: + self.owner.updateLoop( self.getRoot().child ) + + def testSubstitute( self, block ): + ret = Block.testSubstitute( self, block ) + if ret: + return ret + + if block.type != Loop: + return False + + if abs( self.x - block.x ) < Block.SNAP and abs( self.y - block.y ) < Block.SNAP: + return self + + return False + + def setActive( self, state ): + Block.setActive( self, state ) + + if self.child: + self.child.setActive( state ) + + def addChild( self, child ): + Block.addChild( self, child ) + if self.active: + child.setActive( True ) + self.owner.updateLoop( self.getRoot().child ) + + def _addParent( self, parent ): + Block._addParent( self, parent ) + + if self.parent.type == Instrument: + self.keyActive = True + self.owner.mapKey( self.data["key"], self ) + else: + root = self.getRoot() + if root.type == Instrument: + root = root.child + if root.getData("key") == None: + root.setData( "key", self.data["key"] ) + self.setData( "key", None ) + + def _removeParent( self ): + if self.active: + loopRoot = self.getRoot().child + parent = self.parent + else: + loopRoot = None + + self.keyActive = False + self.owner.mapKey( None, self, self.data["key"] ) + + Block._removeParent( self ) + + if loopRoot == self: + self.owner.deactivateLoop( loopRoot ) + elif loopRoot != None: + self.setActive( False ) + parent.child = None # disconnect us before updating + self.owner.updateLoop( loopRoot ) + + def testMouseOver( self, event ): + ret = self.testWithinKey( event ) + if ret: return ret + + x = event.x - self.x + y = event.y - self.y + + if 0 <= x <= self.width and 0 <= y <= self.height: + return -1 + + return False + + def testWithinKey( self, event ): + if not self.keyActive: + return False + + x = event.x - self.x + y = event.y - self.y + + if Loop.KEYRECT[0] <= x <= Loop.KEYRECT[4] and Loop.KEYRECT[1] <= y <= Loop.KEYRECT[5]: + return self + + return False + + def _doButtonPress( self, event ): # we were hit with a button press + pass + + def button_release( self, event ): + if not self.dragging: + if self.active: + root = self.getRoot() + self.owner.deactivateLoop( root.child ) + else: + root = self.getRoot() + if root.type == Instrument: # must be attached to an instrument + self.owner.activateLoop( root.child ) + Block.button_release( self, event ) + + def _doDraw( self, startX, startY, stopX, stopY, pixmap ): + y = max( startY, self.y ) + endY = min( stopY, self.endY ) + height = endY - y + + loop = self.img[ self.active ] + if self.active: self.gc.foreground = self.owner.colors["Border_Active"] + else: self.gc.foreground = self.owner.colors["Border_Inactive"] + + #-- draw head ----------------------------------------- + + if self.x + Loop.HEAD > startX: + x = max( startX, self.x ) + endX = min( stopX, self.x + Loop.HEAD ) + width = endX - x + + # draw border + self.gc.set_clip_origin( self.x-Loop.MASK_START, self.y ) + pixmap.draw_rectangle( self.gc, True, x, y, width, height ) + + # draw block + self.gc.set_clip_origin( self.x-Loop.MASK_START, self.y-self.height ) + pixmap.draw_drawable( self.gc, loop, x-self.x, y-self.y, x, y, width, height ) + + #-- draw beats ---------------------------------------- + + beats = self.owner.noteDB.getPage(self.data["id"]).beats - 1 # last beat is drawn with the tail + curx = self.x + Loop.HEAD + while beats > 3: + if curx >= stopX: + break + elif curx + Loop.BEAT_MUL3 > startX: + x = max( startX, curx ) + endX = min( stopX, curx + Loop.BEAT_MUL3 ) + width = endX - x + + # draw border + self.gc.set_clip_origin( curx-Loop.MASK_BEAT, self.y ) + pixmap.draw_rectangle( self.gc, True, x, y, width, height ) + + # draw block + self.gc.set_clip_origin( curx-Loop.MASK_BEAT, self.y-self.height ) + pixmap.draw_drawable( self.gc, loop, x-self.x, y-self.y, x, y, width, height ) + + curx += Loop.BEAT_MUL3 + beats -= 3 + if beats and curx < stopX: + endX = curx + Loop.BEAT*beats + if endX > startX: + x = max( startX, curx ) + endX = min( stopX, endX ) + width = endX - x + + # draw border + self.gc.set_clip_origin( curx-Loop.MASK_BEAT, self.y ) + pixmap.draw_rectangle( self.gc, True, x, y, width, height ) + + # draw block + self.gc.set_clip_origin( curx-Loop.MASK_BEAT, self.y-self.height ) + pixmap.draw_drawable( self.gc, loop, x-self.x, y-self.y, x, y, width, height ) + + curx += Loop.BEAT*beats + + + #-- draw tail ----------------------------------------- + + if curx < stopX: + x = max( startX, curx ) + endX = min( stopX, self.endX ) + width = endX - x + + # draw border + self.gc.set_clip_origin( curx-Loop.MASK_TAIL, self.y ) + pixmap.draw_rectangle( self.gc, True, x, y, width, height ) + + # draw block + self.gc.set_clip_origin( curx-Loop.MASK_TAIL, self.y-self.height ) + pixmap.draw_drawable( self.gc, loop, x-self.x, y-self.y, x, y, width, height ) + + #-- draw key ------------------------------------------ + if self.keyActive: + self.gc.set_clip_origin( self.x+Loop.KEYRECT[0]-Block.KEYMASK_START, self.y+Loop.KEYRECT[1] ) + pixmap.draw_drawable( self.gc, self.keyImage[ self.active ], 0, 0, self.x+Loop.KEYRECT[0], self.y+Loop.KEYRECT[1], Block.KEYSIZE, Block.KEYSIZE ) + + def drawHighlight( self, startX, startY, stopX, stopY, pixmap ): + self.gc.foreground = self.owner.colors["Border_Highlight"] + + #-- draw head ----------------------------------------- + + self.gc.set_clip_origin( self.x-Loop.MASK_START, self.y ) + pixmap.draw_rectangle( self.gc, True, self.x, self.y, Loop.HEAD, self.height ) + + #-- draw beats ---------------------------------------- + + beats = self.owner.noteDB.getPage(self.data["id"]).beats - 1 # last beat is drawn with the tail + x = self.x + Loop.HEAD + while beats > 3: + self.gc.set_clip_origin( x-Loop.MASK_BEAT, self.y ) + pixmap.draw_rectangle( self.gc, True, x, self.y, Loop.BEAT_MUL3, self.height ) + + x += Loop.BEAT_MUL3 + beats -= 3 + if beats: + width = Loop.BEAT*beats + + self.gc.set_clip_origin( x-Loop.MASK_BEAT, self.y ) + pixmap.draw_rectangle( self.gc, True, x, self.y, width, self.height ) + + x += width + + #-- draw tail ----------------------------------------- + + 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 drawKeyHighlight( self, pixmap ): + self.gc.foreground = self.owner.colors["Border_Highlight"] + self.gc.set_clip_origin( self.x+Loop.KEYRECT[0]-Block.KEYMASK_START, self.y+Loop.KEYRECT[1]-Block.KEYSIZE ) + pixmap.draw_rectangle( self.gc, True, self.x+Loop.KEYRECT[0], self.y+Loop.KEYRECT[1], Block.KEYSIZE, Block.KEYSIZE ) + + 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, + "Loop": Loop + } + +ClassToStr = { + Instrument: "Instrument", + Drum: "Drum", + Loop: "Loop" + } diff --git a/Jam/Desktop.py b/Jam/Desktop.py new file mode 100644 index 0000000..0698e88 --- /dev/null +++ b/Jam/Desktop.py @@ -0,0 +1,467 @@ + +import pygtk +pygtk.require( '2.0' ) +import gtk + +import common.Config as Config + +from gettext import gettext as _ + +import common.Util.InstrumentDB as InstrumentDB +from Jam import Block +from Jam import Popup + +class Desktop( gtk.EventBox ): + + def __init__( self, owner ): + gtk.EventBox.__init__( self ) + + self.instrumentDB = InstrumentDB.getRef() + self.owner = owner + + self.drawingArea = gtk.DrawingArea() + self.add( self.drawingArea ) + + # take drawing setup from owner + self.gc = owner.gc + self.colors = owner.colors + self.blockMask = owner.blockMask + + self.noteDB = owner.noteDB + + self.dirtyRectToAdd = gtk.gdk.Rectangle() # used by the invalidate_rect function + self.screenBuf = None + self.screenBufDirty = False + self.screenBufDirtyRect = gtk.gdk.Rectangle() + + self.blocks = [] # items on the desktop + self.activeInstrument = 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) + + self.connect( "size-allocate", self.on_size_allocate ) + self.connect( "button-press-event", self.on_button_press ) + self.connect( "button-release-event", self.on_button_release ) + self.connect( "motion-notify-event", self.on_motion_notify ) + self.drawingArea.connect( "expose-event", self.on_expose ) + + self.clickedBlock = None + self.possibleParent = None + self.possibleSubstitute = None + self.dragging = False + self.possibleDelete = False + self.overKey = False + + #-- Popups -------------------------------------------- + self.rightClicked = False + + 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 ): + for b in self.blocks: + b.dumpToStream( ostream ) + + def on_size_allocate( self, widget, allocation ): + if self.screenBuf == None or self.alloc.width != allocation.width or self.alloc.height != allocation.height: + win = gtk.gdk.get_default_root_window() + self.screenBuf = gtk.gdk.Pixmap( win, allocation.width, allocation.height ) + self.invalidate_rect( 0, 0, allocation.width, allocation.height ) + self.alloc = allocation + self.absoluteLoc = [0,0] + parent = self.get_parent() + while parent: + alloc = parent.get_allocation() + self.absoluteLoc[0] += alloc.x + self.absoluteLoc[1] += alloc.y + parent = parent.get_parent() + return False + + def getLoopIds( self ): + return [ self.loops[loop] for loop in self.loops ] + + #========================================================== + # Blocks + + def addBlock( self, blockClass, blockData, loc = (-1,-1), drag = False ): + + block = blockClass( self, blockData ) + + if loc[0] != -1: x = loc[0] + else: x = self.alloc.width//2 + if loc[1] != -1: y = loc[1] + elif drag: y = self.alloc.height - block.height//2 + else: y = self.alloc.height//2 + + if drag: + win = gtk.gdk.get_default_root_window() + display = win.get_display() + screen = display.get_default_screen() + display.warp_pointer( screen, int(self.absoluteLoc[0] + x), int(self.absoluteLoc[1] + y) ) + self._beginDrag( block ) + block.setLoc( x - block.width//2, y - block.height//2 ) + else: + self.blocks.append( block ) + block.setLoc( x - block.width//2, y - block.height//2 ) + + if blockClass == Block.Instrument: + pass + elif blockClass == Block.Drum: + pass + elif blockClass == Block.Loop: + pass + + return block + + def deleteBlock( self, block ): + if block.type == Block.Instrument: + if block == self.activeInstrument: + self.activeInstrument = None + for i in range(len(self.blocks)-1, -1, -1): + if self.blocks[i].type == Block.Instrument: + self.activateInstrument( self.blocks[i] ) + break + + elif block.type == Block.Drum: + if block.isActive(): + self.deactivateDrum( block ) + + elif block.type == Block.Loop: + if block.isActive(): + self.deactivateLoop( block ) + + if block in self.blocks: + block.invalidate_rect( True ) + self.blocks.remove( block ) + + block.destroy() + + def _clearDesktop( self ): + for i in range( len(self.blocks)-1, -1, -1 ): + self.deleteBlock( self.blocks[i] ) + + def mapKey( self, key, block, oldKey = None ): + self.owner.mapKey( key, block, oldKey ) + + def getInstrumentImage( self, id, active = False ): + return self.owner.getInstrumentImage( id, active ) + + def getKeyImage( self, key, active = False ): + return self.owner.getKeyImage( key, active ) + + def getLoopImage( self, id, active = False ): + return self.owner.getLoopImage( id, active ) + + def updateLoopImage( self, id ): + self.owner.updateLoopImage( id ) + + #========================================================== + # State + + def activateInstrument( self, block ): + if self.activeInstrument: + self.activeInstrument.setActive( False ) + + block.setActive( True ) + self.activeInstrument = block + + self.updateInstrument( block ) + + def updateInstrument( self, block ): + data = block.data + self.owner._updateInstrument( data["id"], data["volume"], data["pan"], data["reverb"] ) + + def activateDrum( self, block ): + for drum in self.drums: + self.deactivateDrum( drum ) + + block.setActive( True ) + self.drums.append( block ) + + self.updateDrum( block, True ) + + def deactivateDrum( self, block ): + self.owner._stopDrum( self.loops[block] ) + del self.loops[block] + + block.setActive( False ) + self.drums.remove( block ) + + def updateDrum( self, block, firstTime = False ): + data = block.data + + if firstTime: + loopId = None + else: + loopId = self.loops[block] + + self.loops[block] = self.owner._playDrum( data["id"], data["page"], data["volume"], data["reverb"], data["beats"], data["regularity"], loopId ) + + def activateLoop( self, block ): + block.setActive( True ) + + self.updateLoop( block, True ) + + def deactivateLoop( self, block ): + block.setActive( False ) + + self.owner._stopLoop( self.loops[block] ) + del self.loops[block] + + def updateLoop( self, block, firstTime = False ): + inst = block.parent.data + + tune = [] + itr = block + while itr != None: + tune.append( itr.data["id"] ) + itr = itr.child + + if firstTime: + loopId = None + else: + loopId = self.loops[block] + + self.loops[block] = self.owner._playLoop( inst["id"], inst["volume"], inst["reverb"], tune, loopId ) + + #========================================================== + # Mouse + + def on_button_press( self, widget, event ): + + if event.button == 3: + self.rightClicked = True + + hit = False + for i in range(len(self.blocks)-1, -1, -1): + hit = self.blocks[i].button_press( event ) + if hit: + self.clickedBlock = hit + break + + def on_button_release( self, widget, event ): + + if event.button == 3: # Right Click + if self.clickedBlock: + if self.clickedBlock.type == Block.Instrument: + self.popup[Popup.Instrument].setBlock( self.clickedBlock ) + elif self.clickedBlock.type == Block.Drum: + self.popup[Popup.Drum].setBlock( self.clickedBlock ) + elif self.clickedBlock.type == Block.Loop: + self.popup[Popup.Loop].setBlock( self.clickedBlock ) + + self.clickedBlock = None + self.rightClicked = False + self.overKey = False # just in case + return + + if self.overKey: + self.popup[Popup.Shortcut].setBlock( self.clickedBlock ) + self.overKey.invalidate_rect( False ) + self.overKey = False + self.clickedBlock = None + return + + if self.possibleDelete: + self.possibleDelete = False + self.deleteBlock( self.clickedBlock ) + self.clickedBlock = None + self.possibleParent = None + self.possibleSubstitute = None + self.dragging = False + + if self.dragging: + self.dragging = False + + if self.possibleParent: + self.possibleParent.addChild( self.clickedBlock ) + root = self.possibleParent.getRoot() + self.blocks.remove(root) + self.blocks.append(root) + root.invalidateBranch( True ) + self.possibleParent = None + elif self.possibleSubstitute: + self.possibleSubstitute.substitute( self.clickedBlock ) + if self.clickedBlock.isPlaced(): + if self.clickedBlock.resetLoc(): + self.blocks.append( self.clickedBlock ) + else: + self.deleteBlock( self.clickedBlock ) + self.clickedBlock = None + if self.possibleSubstitute.type == Block.Instrument: + self.activateInstrument( self.possibleSubstitute ) + self.possibleSubstitute = None + else: + self.blocks.append( self.clickedBlock ) + + if self.clickedBlock: + self.clickedBlock.button_release( event ) + self.clickedBlock = None + + + def on_motion_notify( self, widget, event ): + + if self.rightClicked: + return + + if self.clickedBlock and self.overKey: + return + + if event.is_hint or widget != self: + x, y, state = self.window.get_pointer() + event.x = float(x) + event.y = float(y) + event.state = state + + blockCount = len(self.blocks) + + if not self.clickedBlock: + if self.popup[Popup.Shortcut].is_up(): + return + for i in range(blockCount-1, -1, -1): + over = self.blocks[i].testMouseOver( event ) + if over == -1: + break # over block but no hotspot + if over: + if self.overKey != self.blocks[i]: + if self.overKey: + self.overKey.invalidate_rect( False ) + self.overKey = over + self.overKey.invalidate_rect( False ) + return + if self.overKey: + self.overKey.invalidate_rect( False ) + self.overKey = False + return + + self.dragging = True + + if self.clickedBlock.motion_notify( event ): # first drag of root block, remove from self.blocks + self.blocks.remove( self.clickedBlock ) + blockCount = len(self.blocks) + + if event.y < 0 or event.y > self.alloc.height: + self.possibleDelete = True + return + else: + self.possibleDelete = False + + if self.clickedBlock.canChild and blockCount: + for i in range(blockCount-1, -1, -1): + handled = self.blocks[i].testChild( self.clickedBlock.getParentAnchor() ) + if handled: + if self.possibleParent != handled: + if self.possibleParent: + self.possibleParent.invalidate_rect( False ) + self.possibleParent = handled + self.possibleParent.invalidate_rect( False ) + break + if not handled and self.possibleParent: + self.possibleParent.invalidate_rect( False ) + self.possibleParent = None + + if self.clickedBlock.canSubstitute and blockCount: + for i in range(blockCount-1, -1, -1): + handled = self.blocks[i].testSubstitute( self.clickedBlock ) + if handled: + if self.possibleSubstitute != handled: + if self.possibleSubstitute: + self.possibleSubstitute.invalidate_rect( False ) + self.possibleSubstitute = handled + self.possibleSubstitute.invalidate_rect( False ) + break + if not handled and self.possibleSubstitute: + self.possibleSubstitute.invalidate_rect( False ) + self.possibleSubstitute = None + + def _beginDrag( self, block ): + block._beginDrag() + self.clickedBlock = block + self.dragging = True + + #========================================================== + # Drawing + + def draw( self ): + + startX = self.screenBufDirtyRect.x + startY = self.screenBufDirtyRect.y + stopX = startX + self.screenBufDirtyRect.width + stopY = startY + self.screenBufDirtyRect.height + + self.gc.set_clip_rectangle( self.screenBufDirtyRect ) + + # draw background + self.gc.foreground = self.colors["bg"] + self.screenBuf.draw_rectangle( self.gc, True, startX, startY, self.screenBufDirtyRect.width, self.screenBufDirtyRect.height ) + + # draw blocks + self.gc.set_clip_mask( self.blockMask ) + for block in self.blocks: + block.draw( startX, startY, stopX, stopY, self.screenBuf ) + + self.screenBufDirty = False + + def on_expose( self, DA, event ): + + if self.screenBufDirty: + self.draw() + + self.drawingAreaDirty = False + + startX = event.area.x + startY = event.area.y + stopX = event.area.x + event.area.width + stopY = event.area.y + event.area.height + + self.gc.set_clip_rectangle( event.area ) + + # draw base + DA.window.draw_drawable( self.gc, self.screenBuf, startX, startY, startX, startY, event.area.width, event.area.height ) + + if self.possibleDelete: + return + + self.gc.set_clip_mask( self.blockMask ) + + # draw possible parent + if self.possibleParent: + self.possibleParent.drawHighlight( startX, startY, stopX, stopY, DA.window ) + + # draw dragged objects + if self.dragging: + self.clickedBlock.draw( startX, startY, stopX, stopY, DA.window ) + + # draw possible substitute + if self.possibleSubstitute: + self.possibleSubstitute.drawHighlight( startX, startY, stopX, stopY, DA.window ) + + # draw key highlight + if self.overKey: + self.overKey.drawKeyHighlight( DA.window ) + + def invalidate_rect( self, x, y, width, height, base = True ): + 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 base: # the base image has been dirtied + if not self.screenBufDirty: + self.screenBufDirtyRect.x = x + self.screenBufDirtyRect.y = y + self.screenBufDirtyRect.width = width + self.screenBufDirtyRect.height = height + else: + self.screenBufDirtyRect = self.screenBufDirtyRect.union( self.dirtyRectToAdd ) + self.screenBufDirty = True + if self.drawingArea.window != None: + self.drawingArea.window.invalidate_rect( self.dirtyRectToAdd, True ) + self.drawingAreaDirty = True diff --git a/Jam/Fillin.py b/Jam/Fillin.py new file mode 100644 index 0000000..8588f6d --- /dev/null +++ b/Jam/Fillin.py @@ -0,0 +1,116 @@ +import pygtk +pygtk.require( '2.0' ) +import gtk +import gobject + +from RythmGenerator import * +from common.Util.CSoundClient import new_csound_client +from common.Util.NoteDB import Note +import common.Config as Config + +class Fillin: + def __init__( self, nbeats, tempo, instrument, reverb, volume ): + self.notesList = [] + self.barCount = 0 + self.gate = 0 + self.nbeats = nbeats + self.tempo = tempo + self.instrument = instrument + self.reverb = reverb + self.volume = volume + self.onsets = [] + self.pitchs = [] + self.playBackTimeout = None + self.loopId = 0 + self.csnd = new_csound_client() + + def reset( self ): + self.barCount = 0 + self.gate = 0 + + def setLoopId( self, id ): + self.loopId = id + + def setProperties( self, tempo, instrument, volume, beats, reverb ): + self.setTempo( tempo ) + self.setInstrument( instrument ) + self.setVolume( volume ) + self.setBeats( beats ) + self.setReverb( reverb ) + + def setInstrument( self, instrument ): + self.instrument = instrument + + def setBeats( self, nbeats ): + if self.playBackTimeout != None: + gobject.source_remove( self.playBackTimeout ) + + self.nbeats = nbeats + self.clear() + self.reset() + + def setTempo( self, tempo ): + self.tempo = tempo + if self.playBackTimeout != None: + gobject.source_remove( self.playBackTimeout ) + self.play() + + def setReverb( self, reverb ): + self.reverb = reverb + + def setVolume( self, volume ): + self.volume = volume + + def play( self ): + if self.playBackTimeout == None: + self.playBackTimeout = gobject.timeout_add( int(60000/self.tempo/8), self.handleClock ) + self.handleClock() + + def stop( self ): + if self.playBackTimeout != None: + gobject.source_remove( self.playBackTimeout ) + self.clear() + + def clear( self ): + if self.notesList: + for n in self.notesList: + self.csnd.loopDelete(n, self.loopId) + self.notesList = [] + + def handleClock( self ): + tick = self.csnd.loopGetTick( self.loopId ) + if tick < ( Config.TICKS_PER_BEAT / 2 + 1 ): + if self.gate == 0: + self.gate = 1 + self.barCount += 1 + self.barCount %= 4 + if self.barCount == 1: + self.clear() + + if tick > ( ( Config.TICKS_PER_BEAT * self.nbeats ) - ( Config.TICKS_PER_BEAT / 2 ) - 1 ): + if self.gate == 1: + self.gate = 0 + if self.barCount == 3: + self.regenerate() + return True + + def unavailable( self, onsets, pitchs ): + self.onsets = onsets + self.pitchs = pitchs + + def regenerate(self): + def flatten(ll): + rval = [] + for l in ll: + rval += l + return rval + i = 500 + self.notesList= [] + for x in flatten( generator(self.instrument, self.nbeats, 0.4, 0.1, self.reverb) ): + if x.onset not in self.onsets or x.pitch not in self.pitchs: + x.amplitude = x.amplitude*self.volume + n = Note(0, x.trackId, i, x) + self.notesList.append(n) + i += 1 + self.csnd.loopPlay(n,1, loopId = self.loopId ) #add as active + diff --git a/Jam/GenRythm.py b/Jam/GenRythm.py new file mode 100644 index 0000000..b9e6d48 --- /dev/null +++ b/Jam/GenRythm.py @@ -0,0 +1,84 @@ +import random +import common.Config as Config +import common.Util.InstrumentDB as InstrumentDB + +from common.Generation.GenerationConstants import GenerationConstants +from common.Generation.Utils import * + +class GenRythm: + def __init__(self): + self.instrumentDB = InstrumentDB.getRef() + + def drumRythmSequence(self, instrumentName, nbeats, density, regularity ): + rythmSequence = [] + binSelection = [] + downBeats = [] + upBeats = [] + beats = [] + countDown = 0 + onsetTime = None + + if self.instrumentDB.instNamed[instrumentName].instrumentRegister == Config.PUNCH: + registerDensity = 0.5 + downBeatRecurence = 4 + downBeats = [x for x in GenerationConstants.DRUM_PUNCH_ACCENTS[ nbeats ]] + for downBeat in downBeats: + upBeats.append( downBeat + Config.TICKS_PER_BEAT / 2 ) + + if self.instrumentDB.instNamed[instrumentName].instrumentRegister == Config.LOW: + registerDensity =1 + downBeatRecurence = 4 + downBeats = [x for x in GenerationConstants.DRUM_LOW_ACCENTS[ nbeats ]] + for downBeat in downBeats: + upBeats.append( downBeat + Config.TICKS_PER_BEAT / 2 ) + + if self.instrumentDB.instNamed[instrumentName].instrumentRegister == Config.MID: + registerDensity = .75 + downBeatRecurence = 1 + downBeats = [x for x in GenerationConstants.DRUM_MID_ACCENTS[ nbeats ]] + for downBeat in downBeats: + upBeats.append( downBeat + Config.TICKS_PER_BEAT / 4 ) + + if self.instrumentDB.instNamed[instrumentName].instrumentRegister == Config.HIGH: + registerDensity = 1.5 + downBeatRecurence = 1 + downBeats = [x for x in GenerationConstants.DRUM_HIGH_ACCENTS[ nbeats ]] + for downBeat in downBeats: + upBeats.append( downBeat + Config.TICKS_PER_BEAT / 4 ) + + realDensity = density * registerDensity + if realDensity > 1.: + realDensity = 1. + + list = range( int( realDensity * len( downBeats ) ) ) + for i in list: + if random.random() < ( regularity * downBeatRecurence ) and binSelection.count( 1 ) < len( downBeats ): + binSelection.append( 1 ) + else: + if binSelection.count( 0 ) < len( downBeats ): + binSelection.append( 0 ) + else: + binSelection.append( 1 ) + + countDown = binSelection.count( 1 ) + + length = len(downBeats) - 1 + for i in range( countDown ): + ran1 = random.randint(0, length) + ran2 = random.randint(0, length) + randMin = min(ran1, ran2) + onsetTime = downBeats.pop(randMin) + rythmSequence.append( onsetTime ) + length -= 1 + + length = len(upBeats) - 1 + for i in range( len( binSelection ) - countDown ): + ran1 = random.randint(0, length) + ran2 = random.randint(0, length) + randMin = min(ran1, ran2) + onsetTime = upBeats.pop(randMin) + rythmSequence.append( onsetTime ) + length -= 1 + + rythmSequence.sort() + return rythmSequence diff --git a/Jam/JamMain.py b/Jam/JamMain.py new file mode 100644 index 0000000..2798d4c --- /dev/null +++ b/Jam/JamMain.py @@ -0,0 +1,1206 @@ + +import pygtk +pygtk.require( '2.0' ) +import gtk +import pango + +import os, sys, shutil, commands +import random + +import common.Util.Instruments +import common.Config as Config +from gettext import gettext as _ +import sugar.graphics.style as style + +from Jam.Desktop import Desktop +import Jam.Picker as Picker +import Jam.Block as Block +from Jam.Toolbars import JamToolbar, PlaybackToolbar, DesktopToolbar, RecordToolbar + + +from common.Util.CSoundNote import CSoundNote +from common.Util.CSoundClient import new_csound_client +import common.Util.InstrumentDB as InstrumentDB +from common.Util import NoteDB + +from Fillin import Fillin +from RythmGenerator import generator +from common.Generation.GenerationConstants import GenerationConstants +from common.Util.NoteDB import Note, Page + +from common.Util import ControlStream +from common.Util import OS +from common.Tooltips import Tooltips + +import xdrlib +import time +import gobject +import common.Util.Network as Net +from sugar.presence import presenceservice +from sugar.graphics.xocolor import XoColor + +from math import sqrt + +HEARTBEAT_BUFFER = 100 # increase the length of heartbeat loop to remove problems with wrapping during sync correction + +class JamMain(gtk.EventBox): + + def __init__(self, activity): + gtk.EventBox.__init__(self) + + self.activity = activity + + self.instrumentDB = InstrumentDB.getRef() + self.noteDB = NoteDB.NoteDB() + + #-- initial settings ---------------------------------- + self.tempo = Config.PLAYER_TEMPO + self.beatDuration = 60.0/self.tempo + self.ticksPerSecond = Config.TICKS_PER_BEAT*self.tempo/60.0 + self.volume = 0.5 + + self.csnd = new_csound_client() + for i in range(0,9): + self.csnd.setTrackVolume( 100, i ) + self.csnd.setMasterVolume( self.volume*100 ) # csnd expects a range 0-100 for now + self.csnd.setTempo( self.tempo ) + + self.muted = False + + presenceService = presenceservice.get_instance() + self.xoOwner = presenceService.get_owner() + + #-- 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 } + r = int( 0.7*(16*hexToDec[hex[1]] + hexToDec[hex[2]]) ) + g = int( 0.7*(16*hexToDec[hex[3]] + hexToDec[hex[4]]) ) + b = int( 0.7*(16*hexToDec[hex[5]] + hexToDec[hex[6]]) ) + return colormap.alloc_color( r*256, g*256, b*256 ) + def lighten( 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 } + r = 255 - int( 0.7*(255-(16*hexToDec[hex[1]] + hexToDec[hex[2]])) ) + g = 255 - int( 0.7*(255-(16*hexToDec[hex[3]] + hexToDec[hex[4]])) ) + b = 255 - int( 0.7*(255-(16*hexToDec[hex[5]] + hexToDec[hex[6]])) ) + return colormap.alloc_color( r*256, g*256, b*256 ) + + xoColorKey = self.xoOwner.props.color + if not xoColorKey: + xoColorKey = ( "#8D8D8D,#FFDDEA" ) + xoColor = XoColor( xoColorKey ) + + win = gtk.gdk.get_default_root_window() + self.gc = gtk.gdk.GC( win ) + colormap = gtk.gdk.colormap_get_system() + self.colors = { "bg": colormap.alloc_color( Config.PANEL_BCK_COLOR ), + "black": colormap.alloc_color( style.COLOR_BLACK.get_html() ), + #"Picker_Bg": colormap.alloc_color( "#404040" ), + #"Picker_Bg_Inactive": colormap.alloc_color( "#808080" ), + "Picker_Bg": colormap.alloc_color( style.COLOR_TOOLBAR_GREY.get_html() ), + "Picker_Bg_Inactive": colormap.alloc_color( style.COLOR_BUTTON_GREY.get_html() ), + "Picker_Fg": colormap.alloc_color( style.COLOR_WHITE.get_html() ), + "Border_Active": colormap.alloc_color( xoColor.get_stroke_color() ), #colormap.alloc_color( "#590000" ), + "Border_Inactive": colormap.alloc_color( "#8D8D8D" ), + "Border_Highlight": colormap.alloc_color( "#FFFFFF" ), + "Bg_Active": colormap.alloc_color( xoColor.get_fill_color() ), #colormap.alloc_color( "#FFDDEA" ), + "Bg_Inactive": colormap.alloc_color( "#DBDBDB" ), + "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"] + + + if True: # load block clipmask + pix = gtk.gdk.pixbuf_new_from_file(Config.IMAGE_ROOT+'jam-blockMask.png') + pixels = pix.get_pixels() + stride = pix.get_rowstride() + channels = pix.get_n_channels() + bitmap = "" + byte = 0 + shift = 0 + for j in range(pix.get_height()): + offset = stride*j + for i in range(pix.get_width()): + r = pixels[i*channels+offset] + if r != "\0": byte += 1 << shift + shift += 1 + if shift > 7: + bitmap += "%c" % byte + byte = 0 + shift = 0 + if shift > 0: + bitmap += "%c" % byte + byte = 0 + shift = 0 + self.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') + pixels = pix.get_pixels() + stride = pix.get_rowstride() + channels = pix.get_n_channels() + bitmap = "" + byte = 0 + shift = 0 + for j in range(pix.get_height()): + offset = stride*j + for i in range(pix.get_width()): + r = pixels[i*channels+offset] + if r != "\0": byte += 1 << shift + shift += 1 + if shift > 7: + bitmap += "%c" % byte + byte = 0 + shift = 0 + if shift > 0: + bitmap += "%c" % byte + byte = 0 + shift = 0 + self.sampleNoteMask = gtk.gdk.bitmap_create_from_data( None, bitmap, pix.get_width(), pix.get_height() ) + self.sampleNoteMask.endOffset = pix.get_width()-3 + + self.loopPitchOffset = 4 + self.loopTickOffset = 13 + self.pitchPerPixel = float(Config.NUMBER_OF_POSSIBLE_PITCHES-1) / (Block.Loop.HEIGHT - 2*self.loopPitchOffset - self.sampleNoteHeight) + self.pixelsPerPitch = float(Block.Loop.HEIGHT - 2*self.loopPitchOffset - self.sampleNoteHeight)/(Config.MAXIMUM_PITCH - Config.MINIMUM_PITCH) + self.pixelsPerTick = Block.Loop.BEAT/float(Config.TICKS_PER_BEAT) + self.ticksPerPixel = 1.0/self.pixelsPerTick + + #-- Instruments --------------------------------------- + self.instrumentImage = {} + self.instrumentImageActive = {} + for inst in self.instrumentDB.getSet( "All" ): + if not inst.kitStage: + self.prepareInstrumentImage( inst.instrumentId, inst.img ) + self.csnd.load_instrument(inst.name) + + #-- Loop Images --------------------------------------- + self.loopImage = {} # get filled in through updateLoopImage + self.loopImageActive = {} # + + #-- Key Images ---------------------------------------- + self.keyImage = {} + self.keyImageActive = {} + # use hardware key codes to work on any keyboard layout (hopefully) + self.valid_shortcuts = { 18:"9", 19:"0", 20:"-", 21:"=", + 32:"O", 33:"P", 34:"[", 35:"]", + 47:";", 48:"'", 51:"\\", + 60:".", 61:"/", + None:" " } + for key in self.valid_shortcuts.keys(): + self.prepareKeyImage( key ) + + #-- Toolbars ------------------------------------------ + self.jamToolbar = JamToolbar( self ) + self.activity.toolbox.add_toolbar( _("Jam"), self.jamToolbar ) + self.playbackToolbar = PlaybackToolbar( self ) + self.activity.toolbox.add_toolbar( _("Playback"), self.playbackToolbar ) + self.desktopToolbar = DesktopToolbar( self ) + self.activity.toolbox.add_toolbar( _("Desktop"), self.desktopToolbar ) + if Config.FEATURES_MIC or Config.FEATURES_NEWSOUNDS: + self.recordToolbar = RecordToolbar( self ) + self.activity.toolbox.add_toolbar( _("Record"), self.recordToolbar ) + + #-- GUI ----------------------------------------------- + if True: # GUI + self.modify_bg( gtk.STATE_NORMAL, self.colors["bg"] ) # window bg + + self.GUI = {} + self.GUI["mainVBox"] = gtk.VBox() + self.add( self.GUI["mainVBox"] ) + + #-- Desktop ------------------------------------------- + self.desktop = self.GUI["desktop"] = Desktop( self ) + self.GUI["mainVBox"].pack_start( self.GUI["desktop"] ) + + #-- Bank ---------------------------------------------- + separator = gtk.Label( " " ) + separator.set_size_request( -1, style.TOOLBOX_SEPARATOR_HEIGHT ) + self.GUI["mainVBox"].pack_start( separator, False ) + self.GUI["notebook"] = gtk.Notebook() + self.GUI["notebook"].set_scrollable( True ) + self.GUI["notebook"].modify_bg( gtk.STATE_NORMAL, self.colors["Picker_Bg"] ) # active tab + self.GUI["notebook"].modify_bg( gtk.STATE_ACTIVE, self.colors["Picker_Bg_Inactive"] ) # inactive tab + self.GUI["notebook"].props.tab_vborder = style.TOOLBOX_TAB_VBORDER + self.GUI["notebook"].props.tab_hborder = style.TOOLBOX_TAB_HBORDER + self.GUI["notebook"].set_size_request( -1, 160 ) + self.GUI["notebook"].connect( "switch-page", self.setPicker ) + self.GUI["mainVBox"].pack_start( self.GUI["notebook"], False, False ) + self.pickers = {} + self.pickerScroll = {} + for type in [ Picker.Instrument, Picker.Drum, Picker.Loop ]: + self.pickers[type] = type( self ) + + def prepareLabel( name ): + label = gtk.Label(Tooltips.categories.get(name) or name) + label.set_alignment( 0.0, 0.5 ) + label.modify_fg( gtk.STATE_NORMAL, self.colors["Picker_Fg"] ) + label.modify_fg( gtk.STATE_ACTIVE, self.colors["Picker_Fg"] ) + return label + + self.GUI["notebook"].append_page( self.pickers[Picker.Drum], prepareLabel(_("Drum Kits")) ) + self.GUI["notebook"].append_page( self.pickers[Picker.Loop], prepareLabel(_("Loops")) ) + + sets = self.instrumentDB.getLabels()[:] + sets.sort() + for set in sets: + page = gtk.HBox() + page.set = set + self.GUI["notebook"].append_page( page, prepareLabel( set ) ) + + self.show_all() + + self.GUI["notebook"].set_current_page( 0 ) + + #-- Keyboard ------------------------------------------ + self.key_dict = {} + self.nextTrack = 2 + self.keyboardListener = None + self.recordingNote = None + + self.keyMap = {} + + # default instrument + self._updateInstrument( self.instrumentDB.instNamed["kalimba"].instrumentId, 0.5 ) + self.instrumentStack = [] + + # metronome + page = NoteDB.Page( 1, local = False ) + self.metronomePage = self.noteDB.addPage( -1, page ) + self.metronome = False + + #-- Drums --------------------------------------------- + self.drumLoopId = None + # use dummy values for now + self.drumFillin = Fillin( 2, 100, self.instrumentDB.instNamed["drum1kit"].instrumentId, 0, 1 ) + + #-- Desktops ------------------------------------------ + self.curDesktop = None + # copy preset desktops + path = Config.FILES_DIR+"/Desktops/" + filelist = os.listdir( path ) + for file in filelist: + shutil.copyfile( path+file, Config.TMP_DIR + '/' + file ) + + #-- Network ------------------------------------------- + self.network = Net.Network() + self.network.addWatcher( self.networkStatusWatcher ) + self.network.connectMessage( Net.HT_SYNC_REPLY, self.processHT_SYNC_REPLY ) + self.network.connectMessage( Net.HT_TEMPO_UPDATE, self.processHT_TEMPO_UPDATE ) + self.network.connectMessage( Net.PR_SYNC_QUERY, self.processPR_SYNC_QUERY ) + self.network.connectMessage( Net.PR_TEMPO_QUERY, self.processPR_TEMPO_QUERY ) + self.network.connectMessage( Net.PR_REQUEST_TEMPO_CHANGE, self.processPR_REQUEST_TEMPO_CHANGE ) + + # sync + self.syncQueryStart = {} + self.syncTimeout = None + self.heartbeatLoop = self.csnd.loopCreate() + self.syncBeats = 4 + self.syncTicks = self.syncBeats*Config.TICKS_PER_BEAT + self.offsetTicks = 0 # offset from the true heartbeat + self.csnd.loopSetNumTicks( self.syncTicks*HEARTBEAT_BUFFER, self.heartbeatLoop ) + self.heartbeatStart = time.time() + self.csnd.loopStart( self.heartbeatLoop ) + self.curBeat = 0 + self.beatWheelTimeout = gobject.timeout_add( 100, self.updateBeatWheel ) + + # data packing classes + self.packer = xdrlib.Packer() + self.unpacker = xdrlib.Unpacker("") + + # handle forced networking + if self.network.isHost(): + self.updateSync() + self.syncTimeout = gobject.timeout_add( 1000, self.updateSync ) + elif self.network.isPeer(): + self.sendTempoQuery() + self.syncTimeout = gobject.timeout_add( 1000, self.updateSync ) + + self.activity.connect( "shared", self.shared ) + + if self.activity._shared_activity: # PEER + self.activity._shared_activity.connect( "buddy-joined", self.buddy_joined ) + self.activity._shared_activity.connect( "buddy-left", self.buddy_left ) + self.activity.connect( "joined", self.joined ) + self.network.setMode( Net.MD_WAIT ) + + #-- Final Set Up -------------------------------------- + self.setVolume( self.volume ) + self.setTempo( self.tempo ) + self.activity.toolbox.set_current_toolbar(1) # JamToolbar + self.setDesktop( 0, True ) + + + #========================================================== + + def onActivate( self, arg ): + pass + def onDeactivate( self ): + pass + + def onDestroy( self ): + self.network.shutdown() + + #clear up scratch folder + path = Config.TMP_DIR + filelist = os.listdir( path ) + for file in filelist: + os.remove( path + '/' + file ) + + + #========================================================== + # Playback + + def onKeyPress( self, widget, event ): + key = event.hardware_keycode + + if key in self.keyMap.keys(): + activate = True + for block in self.keyMap[key]: + if block.isActive(): + activate = False + break + if activate: + for block in self.keyMap[key]: + if not block.isActive(): + if block.type == Block.Drum: self.desktop.activateDrum( block ) + elif block.type == Block.Loop: self.desktop.activateLoop( block ) + else: + for block in self.keyMap[key]: + if block.isActive(): + if block.type == Block.Drum: self.desktop.deactivateDrum( block ) + elif block.type == Block.Loop: self.desktop.deactivateLoop( block ) + return + + if self.key_dict.has_key( key ): # repeated press + return + + if Config.KEY_MAP_PIANO.has_key( key ): + pitch = Config.KEY_MAP_PIANO[key] + inst = self.instrumentDB.instId[self.instrument["id"]] + + if inst.kit: # drum kit + if pitch in GenerationConstants.DRUMPITCH: + pitch = GenerationConstants.DRUMPITCH[pitch] + csnote = self._playNote( key, + 36, + self.instrument["amplitude"]*0.5, # trackVol*noteVol + self.instrument["pan"], + 100, + self.instrumentDB.instNamed[inst.kit[pitch]].instrumentId, + self.instrument["reverb"] ) + else: + if event.state == gtk.gdk.MOD1_MASK: + pitch += 5 + + if inst.csoundInstrumentId == Config.INST_PERC: #Percussions resonance + duration = 60 + else: + duration = -1 + + 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 + + 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, + amplitude, + pan, + duration, + self.nextTrack, + instrumentId, + reverbSend = reverb, + tied = True, + mode = 'mini' ) + self.nextTrack += 1 + if self.nextTrack > 8: + self.nextTrack = 2 + self.csnd.play(self.key_dict[key], 0.3) + + return self.key_dict[key] + + def _stopNote( self, key ): + csnote = self.key_dict[key] + if self.instrumentDB.instId[ csnote.instrumentId ].csoundInstrumentId == Config.INST_TIED: + csnote.duration = .5 + csnote.decay = 0.7 + csnote.tied = False + self.csnd.play(csnote, 0.3) + del self.key_dict[key] + + def _updateInstrument( self, id, volume, pan = 0, reverb = 0 ): + self.instrument = { "id": id, + "amplitude": volume, + "pan": pan, + "reverb": reverb } + + + def pushInstrument( self, instrument ): + self.instrumentStack.append( self.instrument ) + self.instrument = instrument + + def popInstrument( self ): + self.instrument = self.instrumentStack.pop() + + def _playDrum( self, id, pageId, volume, reverb, beats, regularity, loopId = None, sync = True ): + + oldId = loopId + loopId = self.csnd.loopCreate() + + noteOnsets = [] + notePitchs = [] + for n in self.noteDB.getNotesByTrack( pageId, 0 ): + n.pushState() + noteOnsets.append( n.cs.onset ) + notePitchs.append( n.cs.pitch ) + n.cs.instrumentId = id + n.cs.amplitude = volume * n.cs.amplitude + n.cs.reverbSend = reverb + self.csnd.loopPlay( n, 1, loopId = loopId ) #add as active + n.popState() + + ticks = self.noteDB.getPage( pageId ).ticks + + self.csnd.loopSetNumTicks( ticks, loopId ) + + self.drumFillin.setLoopId( loopId ) + self.drumFillin.setProperties( self.tempo, self.instrumentDB.instId[id].name, volume, beats, reverb ) + self.drumFillin.unavailable( noteOnsets, notePitchs ) + + self.drumFillin.play() + + if oldId == None: + if sync: startTick = self.csnd.loopGetTick( self.heartbeatLoop ) % self.syncTicks + else: startTick = 0 + else: + if sync: startTick = self.csnd.loopGetTick( oldId ) # TODO is this really safe? could potentially add several milliseconds of delay everytime a loop is updated + else: startTick = 0 + + while startTick > ticks: + startTick -= ticks + + self.csnd.loopSetTick( startTick, loopId ) + self.csnd.loopStart( loopId ) + + if oldId != None: + self.csnd.loopDestroy( oldId ) + + return loopId + + def _stopDrum( self, loopId ): + self.drumFillin.stop() + self.csnd.loopDestroy( loopId ) + + def _playLoop( self, id, volume, reverb, tune, loopId = None, force = False, sync = True ): + + oldId = loopId + loopId = self.csnd.loopCreate() + + inst = self.instrumentDB.instId[id] + + ticks = 0 + for page in tune: + for n in self.noteDB.getNotesByTrack( page, 0 ): + n.pushState() + n.cs.instrumentId = id + n.cs.amplitude = volume * n.cs.amplitude + n.cs.reverbSend = reverb + if inst.kit: # drum kit + if n.cs.pitch in GenerationConstants.DRUMPITCH: + n.cs.pitch = GenerationConstants.DRUMPITCH[n.cs.pitch] + n.cs.onset += ticks + self.csnd.loopPlay( n, 1, loopId = loopId ) + n.popState() + for n in self.noteDB.getNotesByTrack( page, 1 ): # metronome track + self.csnd.loopPlay( n, 1, loopId = loopId ) + for n in self.noteDB.getNotesByTrack( page, 2 ): # record scratch track + self.csnd.loopPlay( n, 1, loopId = loopId ) + ticks += self.noteDB.getPage(page).ticks + + self.csnd.loopSetNumTicks( ticks, loopId ) + + if oldId == None: + if sync: startTick = self.csnd.loopGetTick( self.heartbeatLoop ) % self.syncTicks + else: startTick = 0 + else: + if sync: startTick = self.csnd.loopGetTick( oldId ) # TODO is this really safe? could potentially add several milliseconds of delay everytime a loop is updated + else: startTick = 0 + + while startTick > ticks: + startTick -= ticks + + self.csnd.loopSetTick( startTick, loopId ) + self.csnd.loopStart( loopId ) + + if oldId != None: + self.csnd.loopDestroy( oldId ) + + return loopId + + def _stopLoop( self, loopId ): + self.csnd.loopDestroy( loopId ) + + def addMetronome( self, page, period ): + self.noteDB.deleteNotesByTrack( [ page ], [ 1 ] ) + + baseCS = CSoundNote( 0, # onset + 36, # pitch + 0.2, # amplitude + 0.5, # pan + 100, # duration + 1, # track + self.instrumentDB.instNamed["drum1hatpedal"].instrumentId, + reverbSend = 0.5, + tied = True, + mode = 'mini' ) + + stream = [] + offset = 0 + + for b in range( self.noteDB.getPage( page ).beats ): + cs = baseCS.clone() + cs.instrumentId = self.instrumentDB.instNamed["drum1hatshoulder"].instrumentId + cs.amplitude = 0.5 + cs.onset += offset + + stream.append( cs ) + + onset = period + while onset < Config.TICKS_PER_BEAT: + cs = baseCS.clone() + cs.onset = onset + offset + stream.append( cs ) + onset += period + + offset += Config.TICKS_PER_BEAT + + self.noteDB.addNotes( [ page, 1, len(stream) ] + stream + [ -1 ] ) + + def removeMetronome( self, page ): + self.noteDB.deleteNotesByTrack( [ page ], [ 1 ] ) + + def setMuted( self, muted ): + self.playbackToolbar.setMuted( muted ) + + def _setMuted( self, muted ): + if self.muted == muted: + return False + + if self.muted: # unmute + self.muted = False + self.csnd.setTrackVolume( 100, 0 ) + else: # mute + self.muted = True + self.csnd.setTrackVolume( 0, 0 ) + + return True + + def setStopped( self ): + for drum in list(self.desktop.drums): + self.desktop.deactivateDrum(drum) + + for loop in list(self.desktop.loops): # we copy the list using the list() method + self.desktop.deactivateLoop(loop) + + + + #========================================================== + # Generate + + def _generateDrumLoop( self, instrumentId, beats, regularity, reverb, pageId = -1 ): + def flatten(ll): + rval = [] + for l in ll: + rval += l + return rval + + notes = flatten( generator( self.instrumentDB.instId[instrumentId].name, beats, 0.8, regularity, reverb) ) + + if pageId == -1: + page = Page( beats ) + pageId = self.noteDB.addPage( -1, page ) + else: + self.noteDB.deleteNotesByTrack( [ pageId ], [ 0 ] ) + + if len(notes): + self.noteDB.addNotes( [ pageId, 0, len(notes) ] + notes + [-1] ) + + return pageId + + def _generateTrack( self, instrumentId, page, track, parameters, algorithm ): + dict = { track: { page: self.noteDB.getCSNotesByTrack( page, track ) } } + instruments = { page: [ self.instrumentDB.instId[instrumentId].name for i in range(Config.NUMBER_OF_TRACKS) ] } + beatsOfPages = { page: self.noteDB.getPage(page).beats } + + algorithm( parameters, + [ 0.5 for i in range(Config.NUMBER_OF_TRACKS) ], + instruments, + self.tempo, + beatsOfPages, + [ track ], + [ page ], + dict, + 4) + + # filter & fix input ...WTF!? + for track in dict: + for page in dict[track]: + for note in dict[track][page]: + intdur = int(note.duration) + note.duration = intdur + note.pageId = page + note.trackId = track + + # prepare the new notes + newnotes = [] + for tid in dict: + for pid in dict[tid]: + newnotes += dict[tid][pid] + + # delete the notes and add the new + self.noteDB.deleteNotesByTrack( [ page ], [ track ] ) + + self.noteDB.addNotes( + [ page, track, len(dict[track][page]) ] + + dict[track][page] + + [ -1 ] ) + + + #========================================================== + # Mic recording + def micRec(self, widget, mic): + self.csnd.inputMessage("i5600 0 4") + OS.arecord(4, "crop.csd", mic) + self.csnd.load_mic_instrument(mic) + + + #========================================================== + # Loop Settings + def loopSettingsChannel(self, channel, value): + self.csnd.setChannel(channel, value) + + def loopSettingsPlayStop(self, state, loop): + if not state: + if loop: + self.loopSettingsPlaying = True + self.csnd.inputMessage(Config.CSOUND_PLAY_LS_NOTE % 5022) + else: + self.csnd.inputMessage(Config.CSOUND_PLAY_LS_NOTE % 5023) + else: + if loop: + self.loopSettingsPlaying = False + self.csnd.inputMessage(Config.CSOUND_STOP_LS_NOTE) + + def load_ls_instrument(self, soundName): + self.csnd.load_ls_instrument(soundName) + + #========================================================== + # Get/Set + + def getVolume( self ): + return self.volume + + def setVolume( self, volume ): + self.jamToolbar.volumeSlider.set_value( volume ) + + def _setVolume( self, volume ): + if self.muted: + self.setMuted( False ) + self.volume = volume + self.csnd.setMasterVolume( self.volume*100 ) # csnd expects a range 0-100 for now + + def getTempo( self ): + return self.tempo + + def setTempo( self, tempo, quiet = False ): + self.jamToolbar.setTempo( tempo, quiet ) + + def _setTempo( self, tempo, propagate = True ): + if self.network.isHost() or self.network.isOffline(): + t = time.time() + elapsedTicks = (t - self.heartbeatStart)*self.ticksPerSecond + + self.tempo = tempo + self.beatDuration = 60.0/self.tempo + self.ticksPerSecond = Config.TICKS_PER_BEAT*self.tempo/60.0 + self.csnd.setTempo( self.tempo ) + + if self.network.isHost() or self.network.isOffline(): + self.heatbeatStart = t - elapsedTicks*self.beatDuration + self.updateSync() + self.sendTempoUpdate() + + def getInstrument( self ): + return self.instrument + + def getDesktop( self ): + return self.desktop + + def _clearDesktop( self, save = True ): + if self.curDesktop == None: + return + + if save: + self._saveDesktop() + + self.desktop._clearDesktop() + + self.curDesktop = None + + def setDesktop( self, desktop, force = False ): + radiobtn = self.desktopToolbar.getDesktopButton( desktop ) + if force and radiobtn.get_active(): + self._setDesktop( desktop ) + else: + radiobtn.set_active( True ) + + def _setDesktop( self, desktop ): + self._clearDesktop() + + self.curDesktop = desktop + + TTTable = ControlStream.TamTamTable( self.noteDB, jam = self ) + + filename = self.getDesktopScratchFile( self.curDesktop ) + try: + stream = open( filename, "r" ) + TTTable.parseFile( stream ) + stream.close() + except IOError, (errno, strerror): + if Config.DEBUG > 3: print "IOError:: _setDesktop:", errno, strerror + + def getInstrumentImage( self, id, active = False ): + if active: return self.instrumentImageActive[id] + else: return self.instrumentImage[id] + + def getKeyImage( self, key, active = False ): + if active: return self.keyImageActive[key] + else: return self.keyImage[key] + + def getLoopImage( self, id, active = False ): + if active: return self.loopImageActive[id] + else: return self.loopImage[id] + + def setPicker( self, widget, pagePointer, page_num ): + page = self.GUI["notebook"].get_nth_page( page_num ) + if page == self.pickers[Picker.Drum]: + pass + elif page == self.pickers[Picker.Loop]: + pass + else: + self.pickers[Picker.Instrument].setFilter( ( page.set ) ) + parent = self.pickers[Picker.Instrument].get_parent() + if parent != page: + if parent != None: + parent.remove( self.pickers[Picker.Instrument] ) + page.add( self.pickers[Picker.Instrument] ) + + def setKeyboardListener( self, listener ): + self.keyboardListener = listener + + def mapKey( self, key, block, oldKey = None ): + if oldKey != None and block in self.keyMap[oldKey]: + self.keyMap[oldKey].remove( block ) + + if key == None: + return + + if key not in self.keyMap.keys(): + self.keyMap[key] = [] + + if block not in self.keyMap[key]: + self.keyMap[key].append( block ) + + #========================================================== + # Pixmaps + + def prepareInstrumentImage( self, id, img_path ): + win = gtk.gdk.get_default_root_window() + try: + pix = gtk.gdk.pixbuf_new_from_file( img_path ) + except: + if Config.DEBUG >= 5: print "JamMain:: file does not exist: " + img_path + pix = gtk.gdk.pixbuf_new_from_file( Config.IMAGE_ROOT + "generic.png" ) + x = (Block.Block.WIDTH-pix.get_width())//2 + y = (Block.Block.HEIGHT-pix.get_height())//2 + img = gtk.gdk.Pixmap( win, Block.Block.WIDTH, Block.Block.HEIGHT ) + self.gc.foreground = self.colors["Bg_Inactive"] + img.draw_rectangle( self.gc, True, 0, 0, Block.Block.WIDTH, Block.Block.HEIGHT ) + img.draw_pixbuf( self.gc, pix, 0, 0, x, y, pix.get_width(), pix.get_height(), gtk.gdk.RGB_DITHER_NONE ) + self.instrumentImage[id] = img + img = gtk.gdk.Pixmap( win, Block.Block.WIDTH, Block.Block.HEIGHT ) + self.gc.foreground = self.colors["Bg_Active"] + img.draw_rectangle( self.gc, True, 0, 0, Block.Block.WIDTH, Block.Block.HEIGHT ) + img.draw_pixbuf( self.gc, pix, 0, 0, x, y, pix.get_width(), pix.get_height(), gtk.gdk.RGB_DITHER_NONE ) + self.instrumentImageActive[id] = img + + def _drawNotes( self, pixmap, beats, notes, active ): + self.gc.set_clip_mask( self.sampleNoteMask ) + for note in notes: # draw N notes + x = self.ticksToPixels( note.cs.onset ) + endX = self.ticksToPixels( note.cs.onset + note.cs.duration ) - 3 # include end cap offset + width = endX - x + if width < 5: + width = 5 + endX = x + width + y = self.pitchToPixels( note.cs.pitch ) + # draw fill + if active: self.gc.foreground = self.colors["Note_Fill_Active"] + else: self.gc.foreground = self.colors["Note_Fill_Inactive"] + self.gc.set_clip_origin( x, y-self.sampleNoteHeight ) + pixmap.draw_rectangle( self.gc, True, x+1, y+1, width+1, self.sampleNoteHeight-2 ) + # draw border + if active: self.gc.foreground = self.colors["Note_Border_Active"] + else: self.gc.foreground = self.colors["Note_Border_Inactive"] + self.gc.set_clip_origin( x, y ) + pixmap.draw_rectangle( self.gc, True, x, y, width, self.sampleNoteHeight ) + self.gc.set_clip_origin( endX-self.sampleNoteMask.endOffset, y ) + pixmap.draw_rectangle( self.gc, True, endX, y, 3, self.sampleNoteHeight ) + + def prepareKeyImage( self, key ): + win = gtk.gdk.get_default_root_window() + pangolayout = self.create_pango_layout( _(self.valid_shortcuts[key]) ) + fontDesc = pango.FontDescription( "bold" ) + pangolayout.set_font_description( fontDesc ) + extents = pangolayout.get_pixel_extents() + x = ( Block.Block.KEYSIZE - extents[1][2] ) // 2 + y = ( Block.Block.KEYSIZE - extents[1][3] ) // 2 + + pixmap = gtk.gdk.Pixmap( win, Block.Block.KEYSIZE, Block.Block.KEYSIZE ) + self.gc.foreground = self.colors["Border_Inactive"] + pixmap.draw_rectangle( self.gc, True, 0, 0, Block.Block.KEYSIZE, Block.Block.KEYSIZE ) + self.gc.foreground = self.colors["Bg_Inactive"] + pixmap.draw_layout( self.gc, x, y, pangolayout ) + self.keyImage[key] = pixmap + + pixmap = gtk.gdk.Pixmap( win, Block.Block.KEYSIZE, Block.Block.KEYSIZE ) + self.gc.foreground = self.colors["Border_Active"] + pixmap.draw_rectangle( self.gc, True, 0, 0, Block.Block.KEYSIZE, Block.Block.KEYSIZE ) + self.gc.foreground = self.colors["Bg_Active"] + pixmap.draw_layout( self.gc, x, y, pangolayout ) + self.keyImageActive[key] = pixmap + + def updateLoopImage( self, id ): + page = self.noteDB.getPage( id ) + + win = gtk.gdk.get_default_root_window() + width = Block.Loop.WIDTH[page.beats] + height = Block.Loop.HEIGHT + + self.gc.set_clip_rectangle( gtk.gdk.Rectangle( 0, 0, width, height ) ) + + pixmap = gtk.gdk.Pixmap( win, width, height ) + self.gc.foreground = self.colors["Bg_Inactive"] + pixmap.draw_rectangle( self.gc, True, 0, 0, width, height ) + self._drawNotes( pixmap, page.beats, self.noteDB.getNotesByTrack( id, 0 ), False ) + self.loopImage[id] = pixmap + + self.gc.set_clip_rectangle( gtk.gdk.Rectangle( 0, 0, width, height ) ) + + pixmap = gtk.gdk.Pixmap( win, width, height ) + self.gc.foreground = self.colors["Bg_Active"] + pixmap.draw_rectangle( self.gc, True, 0, 0, width, height ) + self._drawNotes( pixmap, page.beats, self.noteDB.getNotesByTrack( id, 0 ), True ) + self.loopImageActive[id] = pixmap + + def ticksToPixels( self, ticks ): + return self.loopTickOffset + int(round( ticks * self.pixelsPerTick )) + def pitchToPixels( self, pitch ): + return self.loopPitchOffset + int(round( ( Config.MAXIMUM_PITCH - pitch ) * self.pixelsPerPitch )) + + #========================================================== + # Load/Save + + def _saveDesktop( self ): + if self.curDesktop == None: + return + + filename = self.getDesktopScratchFile( self.curDesktop ) + if os.path.isfile( filename ): + os.remove( filename ) + + try: + scratch = open( filename, "w" ) + stream = ControlStream.TamTamOStream(scratch) + + self.noteDB.dumpToStream( stream, True ) + self.desktop.dumpToStream( stream ) + stream.sync_beats( self.syncBeats ) + + scratch.close() + except IOError, (errno, strerror): + if Config.DEBUG > 3: print "IOError:: _saveDesktop:", errno, strerror + + def getDesktopScratchFile( self, i ): + return Config.TMP_DIR+"/desktop%d" % i + + def handleJournalLoad( self, filepath ): + + self._clearDesktop( False ) + + TTTable = ControlStream.TamTamTable( self.noteDB, jam = self ) + + try: + stream = open( filepath, "r" ) + TTTable.parseFile( stream ) + stream.close() + + self.setVolume( TTTable.masterVolume ) + self.setTempo( TTTable.tempo ) + + except IOError, (errno, strerror): + if Config.DEBUG > 3: print "IOError:: handleJournalLoad:", errno, strerror + + def handleJournalSave( self, filepath ): + + self._saveDesktop() + + try: + streamF = open( filepath, "w" ) + stream = ControlStream.TamTamOStream( streamF ) + + for i in range(10): + desktop_file = self.getDesktopScratchFile( i ) + stream.desktop_store( desktop_file, i ) + + stream.desktop_set( self.curDesktop ) + + stream.master_vol( self.volume ) + stream.tempo( self.tempo ) + + streamF.close() + + except IOError, (errno, strerror): + if Config.DEBUG > 3: print "IOError:: handleJournalSave:", errno, strerror + + #========================================================== + # Network + + #-- Activity ---------------------------------------------- + + def shared( self, activity ): + if Config.DEBUG: print "TamTamJam:: successfully shared, start host mode" + self.activity._shared_activity.connect( "buddy-joined", self.buddy_joined ) + self.activity._shared_activity.connect( "buddy-left", self.buddy_left ) + self.network.setMode( Net.MD_HOST ) + self.updateSync() + self.syncTimeout = gobject.timeout_add( 1000, self.updateSync ) + + def joined( self, activity ): + if Config.DEBUG: + print "TamTamJam:: joined activity!!" + for buddy in self.activity._shared_activity.get_joined_buddies(): + print buddy.props.ip4_address + + def buddy_joined( self, activity, buddy ): + if Config.DEBUG: + print "buddy joined " + str(buddy) + try: + print buddy.props.ip4_address + except: + print "bad ip4_address" + if self.network.isHost(): + if buddy == self.xoOwner: + return + if buddy.props.ip4_address: + self.network.introducePeer( buddy.props.ip4_address ) + else: + print "TamTamJam:: new buddy does not have an ip4_address!!" + + def buddy_left( self, activity, buddy): + if Config.DEBUG: print "buddy left" + + #def joined( self, activity ): + # if Config.DEBUG: print "miniTamTam:: successfully joined, wait for host" + # self.net.waitForHost() + + #-- Senders ----------------------------------------------- + + def sendSyncQuery( self ): + self.packer.pack_float(random.random()) + hash = self.packer.get_buffer() + self.packer.reset() + self.syncQueryStart[hash] = time.time() + self.network.send( Net.PR_SYNC_QUERY, hash) + + def sendTempoUpdate( self ): + self.packer.pack_int(self.tempo) + self.network.sendAll( Net.HT_TEMPO_UPDATE, self.packer.get_buffer() ) + self.packer.reset() + + def sendTempoQuery( self ): + self.network.send( Net.PR_TEMPO_QUERY ) + + def requestTempoChange( self, val ): + self.packer.pack_int(val) + self.network.send( Net.PR_REQUEST_TEMPO_CHANGE, self.packer.get_buffer() ) + self.packer.reset() + + #-- Handlers ---------------------------------------------- + + def networkStatusWatcher( self, mode ): + if mode == Net.MD_OFFLINE: + if self.syncTimeout: + gobject.source_remove( self.syncTimeout ) + self.syncTimeout = None + if mode == Net.MD_PEER: + self.updateSync() + if not self.syncTimeout: + self.syncTimeout = gobject.timeout_add( 1000, self.updateSync ) + self.sendTempoQuery() + + def processHT_SYNC_REPLY( self, sock, message, data ): + t = time.time() + hash = data[0:4] + latency = t - self.syncQueryStart[hash] + self.unpacker.reset(data[4:8]) + elapsed = self.unpacker.unpack_float() + #print "mini:: got sync: next beat in %f, latency %d" % (nextBeat, latency*1000) + self.heartbeatStart = t - elapsed - latency/2 + self.correctSync() + self.syncQueryStart.pop(hash) + + def processHT_TEMPO_UPDATE( self, sock, message, data ): + self.unpacker.reset(data) + val = self.unpacker.unpack_int() + self.setTempo( val, True ) + self.sendSyncQuery() + + def processPR_SYNC_QUERY( self, sock, message, data ): + self.packer.pack_float(time.time() - self.heartbeatStart) + self.network.send( Net.HT_SYNC_REPLY, data + self.packer.get_buffer(), sock ) + self.packer.reset() + + def processPR_TEMPO_QUERY( self, sock, message, data ): + self.packer.pack_int(self.tempo) + self.network.send( Net.HT_TEMPO_UPDATE, self.packer.get_buffer(), to = sock ) + self.packer.reset() + + def processPR_REQUEST_TEMPO_CHANGE( self, sock, message, data ): + if self.jamToolbar.tempoSliderActive: + return + self.unpacker.reset(data) + val = self.unpacker.unpack_int() + self.setTempo( val ) + + #========================================================== + # Sync + + def setSyncBeats( self, beats ): + self.playbackToolbar.setSyncBeats( beats ) + + def _setSyncBeats( self, beats ): + if beats == self.syncBeats: + return + + elapsedTicks = (time.time() - self.heartbeatStart)*self.ticksPerSecond + self.offsetTicks + elapsedBeats = int(elapsedTicks) // Config.TICKS_PER_BEAT + + targBeat = (elapsedBeats % self.syncBeats) % beats + curBeat = elapsedBeats % beats + offset = (targBeat - curBeat) * Config.TICKS_PER_BEAT + + self.syncBeats = beats + self.syncTicks = beats*Config.TICKS_PER_BEAT + + self.offsetTicks = (offset + self.offsetTicks) % (self.syncTicks*HEARTBEAT_BUFFER) + elapsedTicks += offset + + newTick = elapsedTicks % (self.syncTicks*HEARTBEAT_BUFFER) + + self.csnd.loopSetTick( newTick, self.heartbeatLoop ) + self.csnd.loopSetNumTicks( self.syncTicks*HEARTBEAT_BUFFER, self.heartbeatLoop ) + + self.updateSync() + + def _setBeat( self, beat ): + curTick = self.csnd.loopGetTick( self.heartbeatLoop ) % self.syncTicks + curBeat = int(curTick) // Config.TICKS_PER_BEAT + offset = (beat - curBeat) * Config.TICKS_PER_BEAT + if offset > self.syncTicks//2: + offset -= self.syncTicks + elif offset < -self.syncTicks//2: + offset += self.syncTicks + + self.offsetTicks = (offset + self.offsetTicks) % (self.syncTicks*HEARTBEAT_BUFFER) + + for id in self.desktop.getLoopIds() + [ self.heartbeatLoop ]: + tick = self.csnd.loopGetTick( id ) + maxTick = self.csnd.loopGetNumTicks( id ) + newTick = (tick + offset) % maxTick + self.csnd.loopSetTick( newTick, id ) + + def updateBeatWheel( self ): + curTick = self.csnd.loopGetTick( self.heartbeatLoop ) % self.syncTicks + self.curBeat = int( curTick ) // Config.TICKS_PER_BEAT + self.playbackToolbar.updateBeatWheel( self.curBeat ) + return True + + def correctedHeartbeat( self ): + elapsedTicks = (time.time() - self.heartbeatStart)*self.ticksPerSecond + return (elapsedTicks + self.offsetTicks) % (self.syncTicks*HEARTBEAT_BUFFER) + + def updateSync( self ): + if Config.DEBUG: + # help the log print out on time + sys.stdout.flush() + + if self.network.isOffline(): + return False + elif self.network.isWaiting(): + return True + elif self.network.isHost(): + self.correctSync() + else: + self.sendSyncQuery() + return True + + def correctSync( self ): + curTick = self.csnd.loopGetTick( self.heartbeatLoop ) + corTick = self.correctedHeartbeat() + err = corTick - curTick + maxTick = self.syncTicks * HEARTBEAT_BUFFER + if err < -maxTick//2: # these should never happen becasue of HEARTBEAT_BUFFER, but hey + err += maxTick + elif err > maxTick//2: + err -= maxTick + + #print "correctSync", curTick, corTick, err, maxTick, self.offsetTicks + + if abs(err) > 4*Config.TICKS_PER_BEAT: # we're way off + for id in self.desktop.getLoopIds() + [ self.heartbeatLoop ]: + tick = self.csnd.loopGetTick( id ) + maxTick = self.csnd.loopGetNumTicks( id ) + newTick = (tick + err) % maxTick + self.csnd.loopSetTick( newTick, id ) + elif abs(err) > 0.25: # soft correction + self.csnd.adjustTick( err/3 ) diff --git a/Jam/Parasite.py b/Jam/Parasite.py new file mode 100644 index 0000000..74b9e0d --- /dev/null +++ b/Jam/Parasite.py @@ -0,0 +1,349 @@ +import pygtk +pygtk.require( '2.0' ) +import gtk + +import common.Config as Config + +from common.Util.NoteDB import PARAMETER +from common.Util.CSoundClient import new_csound_client + +class 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 new file mode 100644 index 0000000..f1030bf --- /dev/null +++ b/Jam/Picker.py @@ -0,0 +1,398 @@ + +import pygtk +pygtk.require( '2.0' ) +import gtk + +import os + +import random #TEMP +import sets + +from common.Util.CSoundClient import new_csound_client +from common.port.scrolledbox import HScrolledBox +import common.Config as Config +from gettext import gettext as _ + +from sugar.graphics.palette import Palette, WidgetInvoker + +from common.Util import ControlStream +from common.Util import InstrumentDB + +from Jam import Block + +class Picker(HScrolledBox): + + def __init__( self, owner, filter = None ): + HScrolledBox.__init__(self) + + self.owner = owner + + # take drawing setup from owner + self.gc = owner.gc + self.colors = owner.colors + self.blockMask = owner.blockMask + + self.filter = filter + + self.desktop = owner.getDesktop() + + self.pickerBox = gtk.HBox() + self.set_viewport(self.pickerBox) + self.modify_bg( gtk.STATE_NORMAL, self.colors["Picker_Bg"] ) + + # spacers + self.pickerBox.pack_start( gtk.Label(" "), True, True ) + self.pickerBox.pack_end( gtk.Label(" "), True, True ) + + self.show_all() + self.scroll = {} + self.scroll[filter] = 0 + + self.blocks = [] + + def addBlock( self, data, name, block ): + # tooltip + invoker = WidgetInvoker(block) + invoker._position_hint = WidgetInvoker.AT_CURSOR + invoker.set_palette(Palette(name)) + + block.add_events( gtk.gdk.BUTTON_PRESS_MASK + | gtk.gdk.BUTTON_RELEASE_MASK + | gtk.gdk.ENTER_NOTIFY_MASK + | gtk.gdk.LEAVE_NOTIFY_MASK + | gtk.gdk.POINTER_MOTION_MASK + | gtk.gdk.POINTER_MOTION_HINT_MASK ) + block.connect( "button-press-event", self.on_button_press ) + block.connect( "button-release-event", self.on_button_release ) + block.connect( "motion-notify-event", self.on_motion_notify ) + block.data = data + + self.blocks.append( block ) + + if self._testAgainstFilter( block ): + self.pickerBox.pack_start( block, False, False, 3 ) + + block.show_all() + + return block + + def getFilter( self ): + return filter + + def setFilter( self, filter ): + if filter == self.filter: + return + + self.scroll[self.filter] = self.get_adjustment().get_value() + + self.filter = filter + + for block in self.pickerBox.get_children()[1:-1]: # outside children are spacers + self.pickerBox.remove( block ) + + for block in self.blocks: + if self._testAgainstFilter( block ): + self.pickerBox.pack_start( block, False, False, 3 ) + + if self.scroll.has_key( filter ): + self.get_adjustment().set_value( self.scroll[filter] ) + else: + self.get_adjustment().set_value( 0 ) + self.scroll[filter] = 0 + + def _testAgainstFilter( self, block ): + return True + + def on_button_press( self, widget, event ): + pass + + def on_button_release( self, widget, event ): + self.desktop.on_button_release( widget, event ) + + def on_motion_notify( self, widget, event ): + self.desktop.on_motion_notify( widget, event ) + + +class Instrument( Picker ): + + def __init__( self, owner, filter = ( "All" ) ): + Picker.__init__( self, owner, filter ) + + self.type = Instrument + + self.instrumentDB = InstrumentDB.getRef() + + all = [] + lab = [] + mic = [] + + for i in self.instrumentDB.getSet( "All" ): + if i.name.startswith('lab'): + lab.append(i) + elif i.name.startswith('mic'): + mic.append(i) + elif not i.kitStage and not i.kit: + all.append(i) + + all += sorted(lab, key=lambda i: i.name) + all += sorted(mic, key=lambda i: i.name) + + for inst in all: + self.addBlock( inst.instrumentId ) + + def addBlock( self, id ): + # match data structure of Block.Instrument + data = { "name": self.instrumentDB.instId[id].nameTooltip, + "id": id } + + win = gtk.gdk.get_default_root_window() + width = Block.Instrument.WIDTH + height = Block.Instrument.HEIGHT + pixmap = gtk.gdk.Pixmap( win, width, height ) + + self.gc.set_clip_rectangle( gtk.gdk.Rectangle( 0, 0, width, height ) ) + + # draw bg + self.gc.foreground = self.colors["Picker_Bg"] + pixmap.draw_rectangle( self.gc, True, 0, 0, width, height ) + + self.gc.set_clip_mask( self.blockMask ) + + # draw border + self.gc.foreground = self.colors["Border_Inactive"] + self.gc.set_clip_origin( -Block.Instrument.MASK_START, 0 ) + pixmap.draw_rectangle( self.gc, True, 0, 0, width, height ) + + # draw block + inst = self.owner.getInstrumentImage( data["id"] ) + self.gc.set_clip_origin( -Block.Instrument.MASK_START, -height ) + pixmap.draw_drawable( self.gc, inst, 0, 0, 0, 0, width, height ) + + image = gtk.Image() + image.set_from_pixmap( pixmap, None ) + + block = gtk.EventBox() + block.modify_bg( gtk.STATE_NORMAL, self.colors["Picker_Bg"] ) + block.add( image ) + + Picker.addBlock( self, data, data["name"], block ) + + def _testAgainstFilter( self, block ): + if "All" in self.filter: + return True + + if self.instrumentDB.getInstrument( block.data["id"] ).category in self.filter: + return True + + return False + + def on_button_press( self, widget, event ): + walloc = widget.get_allocation() + valloc = self.get_viewport_allocation() + loc = ( valloc.x + walloc.x + event.x, -1 ) + + block = self.desktop.addBlock( Block.Instrument, widget.data, loc, True ) + self.desktop.activateInstrument( block ) + + +class Drum( Picker ): + + def __init__( self, owner, filter = None ): + Picker.__init__( self, owner, filter ) + + self.type = Drum + + self.instrumentDB = InstrumentDB.getRef() + + for inst in self.instrumentDB.getSet( "percussions" ): + if self.instrumentDB.instNamed[inst.name].kit: + self.addBlock( inst.instrumentId ) + + def addBlock( self, id ): + # match data structure of Block.Drum + data = { "name": self.instrumentDB.instId[id].nameTooltip, + "id": id } + + win = gtk.gdk.get_default_root_window() + width = Block.Drum.WIDTH + height = Block.Drum.HEIGHT + pixmap = gtk.gdk.Pixmap( win, width, height ) + + self.gc.set_clip_rectangle( gtk.gdk.Rectangle( 0, 0, width, height ) ) + + # draw bg + self.gc.foreground = self.colors["Picker_Bg"] + pixmap.draw_rectangle( self.gc, True, 0, 0, width, height ) + + self.gc.set_clip_mask( self.blockMask ) + + # draw border + self.gc.foreground = self.colors["Border_Inactive"] + self.gc.set_clip_origin( -Block.Drum.MASK_START, 0 ) + pixmap.draw_rectangle( self.gc, True, 0, 0, width, height ) + + # draw block + inst = self.owner.getInstrumentImage( data["id"] ) + self.gc.set_clip_origin( -Block.Drum.MASK_START, -height ) + pixmap.draw_drawable( self.gc, inst, 0, 0, 0, 0, width, height ) + + image = gtk.Image() + image.set_from_pixmap( pixmap, None ) + + block = gtk.EventBox() + block.modify_bg( gtk.STATE_NORMAL, self.colors["Picker_Bg"] ) + block.add( image ) + + Picker.addBlock( self, data, data["name"], block ) + + def on_button_press( self, widget, event ): + walloc = widget.get_allocation() + valloc = self.get_viewport_allocation() + loc = ( valloc.x + walloc.x + event.x, -1 ) + self.desktop.addBlock( Block.Drum, widget.data, loc, True ) + + +class Loop( Picker ): + + def __init__( self, owner, filter = None ): + Picker.__init__( self, owner, filter ) + + self.type = Loop + + self.presetLoops = self._scanDirectory( Config.FILES_DIR+"/Loops" ) + + def _loadFile( self, fullpath, filename ): + if filename[-4:] != ".ttl": + if Config.DEBUG >= 3: print "WARNING: incorrect extension on loop file: " + filename + return -1 + try: + oldPages = sets.Set( self.owner.noteDB.getTune() ) + + ifile = open( fullpath, 'r' ) + ttt = ControlStream.TamTamTable ( self.owner.noteDB ) + ttt.parseFile( ifile ) + ifile.close() + + curPages = sets.Set( self.owner.noteDB.getTune() ) + newPages = curPages.difference( oldPages ) + + if len(newPages) != 1: + print "ERROR: bad loop file, contains more than one page (or none)" + return -1 + + id = newPages.pop() # new pageId + + self.owner.noteDB.getPage( id ).setLocal( False ) # flag as a global page + + self.addBlock( id, filename[:-4] ) + + return id + + except OSError,e: + print 'ERROR: failed to open file %s for reading\n' % ofilename + return -1 + + def _scanDirectory( self, path ): + dirlist = os.listdir( path ) + ids = [] + for fpath in dirlist: + id = self._loadFile( path+"/"+fpath, fpath ) + if id != -1: ids.append(id) + return ids + + def addBlock( self, id, name ): + # match data structure of Block.Loop + data = { "name": _('Loop'), + "id": id } + + self.owner.updateLoopImage( data["id"] ) + loop = self.owner.getLoopImage( data["id"] ) + + page = self.owner.noteDB.getPage( id ) + + win = gtk.gdk.get_default_root_window() + width = Block.Loop.WIDTH[page.beats] + height = Block.Loop.HEIGHT + pixmap = gtk.gdk.Pixmap( win, width, height ) + + self.gc.set_clip_rectangle( gtk.gdk.Rectangle( 0, 0, width, height ) ) + + # draw bg + self.gc.foreground = self.colors["Picker_Bg"] + pixmap.draw_rectangle( self.gc, True, 0, 0, width, height ) + + self.gc.set_clip_mask( self.blockMask ) + self.gc.foreground = self.owner.colors["Border_Inactive"] + + #-- draw head ----------------------------------------- + + # draw border + self.gc.set_clip_origin( -Block.Loop.MASK_START, 0 ) + pixmap.draw_rectangle( self.gc, True, 0, 0, Block.Loop.HEAD, height ) + + # draw block + self.gc.set_clip_origin( -Block.Loop.MASK_START, -height ) + pixmap.draw_drawable( self.gc, loop, 0, 0, 0, 0, Block.Loop.HEAD, height ) + + #-- draw beats ---------------------------------------- + + beats = page.beats - 1 # last beat is drawn with the tail + curx = Block.Loop.HEAD + while beats > 3: + # draw border + self.gc.set_clip_origin( curx-Block.Loop.MASK_BEAT, 0 ) + pixmap.draw_rectangle( self.gc, True, curx, 0, Block.Loop.BEAT_MUL3, height ) + + # draw block + self.gc.set_clip_origin( curx-Block.Loop.MASK_BEAT, -height ) + pixmap.draw_drawable( self.gc, loop, curx, 0, curx, 0, Block.Loop.BEAT_MUL3, height ) + + curx += Block.Loop.BEAT_MUL3 + beats -= 3 + + if beats: + w = Block.Loop.BEAT*beats + + # draw border + self.gc.set_clip_origin( curx-Block.Loop.MASK_BEAT, 0 ) + pixmap.draw_rectangle( self.gc, True, curx, 0, w, height ) + + # draw block + self.gc.set_clip_origin( curx-Block.Loop.MASK_BEAT, -height ) + pixmap.draw_drawable( self.gc, loop, curx, 0, curx, 0, w, height ) + + curx += w + + #-- draw tail ----------------------------------------- + + # draw border + self.gc.set_clip_origin( curx-Block.Loop.MASK_TAIL, 0 ) + pixmap.draw_rectangle( self.gc, True, curx, 0, Block.Loop.TAIL, height ) + + # draw block + self.gc.set_clip_origin( curx-Block.Loop.MASK_TAIL, -height ) + pixmap.draw_drawable( self.gc, loop, curx, 0, curx, 0, Block.Loop.TAIL, height ) + + image = gtk.Image() + image.set_from_pixmap( pixmap, None ) + + block = gtk.EventBox() + block.modify_bg( gtk.STATE_NORMAL, self.colors["Picker_Bg"] ) + block.add( image ) + + Picker.addBlock( self, data, data["name"], block ) + + def on_button_press( self, widget, event ): + walloc = widget.get_allocation() + valloc = self.get_viewport_allocation() + loc = ( valloc.x + walloc.x + event.x, -1 ) + + data = {} + for key in widget.data.keys(): + data[key] = widget.data[key] + + newid = self.owner.noteDB.duplicatePages( [ data["id"] ] )[data["id"]] + self.owner.updateLoopImage( newid ) + data["id"] = newid + + block = self.desktop.addBlock( Block.Loop, data, loc, True ) diff --git a/Jam/Popup.py b/Jam/Popup.py new file mode 100644 index 0000000..8ff1888 --- /dev/null +++ b/Jam/Popup.py @@ -0,0 +1,1466 @@ + +import pygtk +pygtk.require( '2.0' ) +import gtk + +import common.Config as Config + +from gettext import gettext as _ +from sugar.graphics import style +from sugar.graphics.palette import Palette, Invoker +import gobject + +from Jam import Block +from common.Util.NoteDB import PARAMETER +from common.Util.CSoundNote import CSoundNote +from common.Util.CSoundClient import new_csound_client +from Jam.Parasite import LoopParasite + +from common.Generation.Generator import generator1, GenerationParameters +from common.Generation.GenerationConstants import GenerationConstants + +class SELECTNOTES: + ALL = -1 + NONE = 0 + ADD = 1 + REMOVE = 2 + FLIP = 3 + EXCLUSIVE = 4 + +class NoneInvoker( Invoker ): + + def __init__( self ): + Invoker.__init__( self ) + self._position_hint = Invoker.AT_CURSOR + + def get_rect( self ): + return gtk.gdk.Rectangle( 0, 0, 0, 0 ) + + def get_toplevel( self ): + return None + +class Popup( Palette ): + + def __init__( self, label, owner ): + Palette.__init__( self, label ) + + self.owner = owner + + self.block = None + + self.props.invoker = NoneInvoker() + self.set_group_id( "TamTamPopup" ) + + self.connect( "key-press-event", self.on_key_press ) + self.connect( "key-release-event", self.on_key_release ) + + self.connect( "focus_out_event", self.closePopup ) + + def destroy( self ): + pass + + def _leave_notify_event_cb( self, widget, event ): + return # don't popdown() + + def _show( self ): + Palette._show( self ) + + if self._palette_popup_sid != None: + self._palette_popup_sid = None + + def popup( self, immediate = False ): + if hasattr(self, '_set_state'): + self._set_state(self.SECONDARY) + Palette.popup( self, immediate) + else: + Palette.popup( self, immediate, state = Palette.SECONDARY ) + + def popdown( self, immediate = False ): + self.block = None + + Palette.popdown( self, immediate ) + + def updatePosition( self ): + self.props.invoker._cursor_x = -1 + self.props.invoker._cursor_y = -1 + self._update_position() + + def closePopup( self, widget, event ): + self.popdown( True ) + + def on_key_press( self, widget, event ): + self.owner.onKeyPress( widget, event ) + + def on_key_release( self, widget, event ): + self.owner.onKeyRelease( widget, event ) + + def setBlock( self, block ): + if self.is_up(): + self.updatePosition() + else: + self.popup( True ) + + +class Instrument( 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( 100, -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.01, 0.01, 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( True ) + self.GUI["volumeSlider"].set_digits( 2 ) + self.GUI["volumeSlider"].set_value_pos( gtk.POS_RIGHT ) + 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 ) + + #-- Pan ----------------------------------------------- + self.GUI["panBox"] = gtk.HBox() + self.GUI["mainBox"].pack_start( self.GUI["panBox"], padding = style.DEFAULT_PADDING ) + self.GUI["panLabel"] = gtk.Label( _("Pan:") ) + self.GUI["panLabel"].set_size_request( 100, -1 ) + self.GUI["panLabel"].set_alignment( 0.0, 0.5 ) + self.GUI["panBox"].pack_start( self.GUI["panLabel"], False, padding = style.DEFAULT_PADDING ) + self.GUI["panAdjustment"] = gtk.Adjustment( 0.5, 0, 1.0, 0.01, 0.01, 0 ) + self.GUI["panAdjustment"].connect( 'value-changed', self.handlePan ) + self.GUI["panSlider"] = gtk.HScale( adjustment = self.GUI["panAdjustment"] ) + self.GUI["panSlider"].set_size_request( 250, -1 ) + self.GUI["panSlider"].set_draw_value( True ) + self.GUI["panSlider"].set_digits( 2 ) + self.GUI["panSlider"].set_value_pos( gtk.POS_RIGHT ) + self.GUI["panBox"].pack_start( self.GUI["panSlider"], False, padding = style.DEFAULT_PADDING ) + self.GUI["panImage"] = gtk.Image() + self.GUI["panBox"].pack_start( self.GUI["panImage"], 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( 100, -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.01, 0.01, 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( True ) + self.GUI["reverbSlider"].set_digits( 2 ) + self.GUI["reverbSlider"].set_value_pos(gtk.POS_RIGHT) + 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 ) + + 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 ) + + 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.settingBlock = False + + Popup.setBlock( self, block ) + + def handleVolume( self, widget ): + if not self.settingBlock: + self.block.setData( "volume", widget.get_value() ) + + def handlePan( self, widget ): + if not self.settingBlock: + self.block.setData( "pan", widget.get_value() ) + + def handleReverb( self, widget ): + if not self.settingBlock: + self.block.setData( "reverb", widget.get_value() ) + + +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.01, 0.01, 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( True ) + self.GUI["volumeSlider"].set_digits( 2 ) + self.GUI["volumeSlider"].set_value_pos(gtk.POS_RIGHT) + 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.01, 0.01, 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( True ) + self.GUI["reverbSlider"].set_digits( 2 ) + self.GUI["reverbSlider"].set_value_pos( gtk.POS_RIGHT ) + 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( True ) + self.GUI["beatsSlider"].set_value_pos( gtk.POS_RIGHT ) + self.GUI["beatsSlider"].set_digits(0) + 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.8, 0.0, 1.0, 0.01, 0.01, 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( True ) + self.GUI["regularitySlider"].set_digits( 2 ) + self.GUI["regularitySlider"].set_value_pos( gtk.POS_RIGHT ) + 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 + + Popup.setBlock( self, block ) + + 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 ): + # snap to 0 decimal places + val = widget.get_value() + if round( val ) != val: + widget.set_value( round( val ) ) + return + + 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( True ) + self.GUI["beatsSlider"].set_value_pos( gtk.POS_RIGHT ) + self.GUI["beatsSlider"].set_digits( 0 ) + 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.8, 0.0, 1.0, 0.01, 0.01, 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( True ) + self.GUI["regularitySlider"].set_digits( 2 ) + self.GUI["regularitySlider"].set_value_pos( gtk.POS_RIGHT ) + 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.grid = Config.DEFAULT_GRID + + self.activeTrack = 0 # which track notes are being edited/displayed on + + 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 ) + + if self.block != None: + self.applyNoteSelection( SELECTNOTES.NONE, 0, [], self.curPage ) + + 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 ) + + Popup.setBlock( self, block ) + + 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 ): + # snap to 0 decimal places + val = widget.get_value() + if round( val ) != val: + widget.set_value( round( val ) ) + return + + 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(), self.activeTrack, self ): + n.updateTransform( True ) + self.invalidatePreview( 0, 0, self.previewDA.width, self.previewDA.height ) + + if self.recordLoop: + self.owner.removeMetronome( self.curPage ) + self.owner.addMetronome( self.curPage, self.grid ) + self.recordLoop = self.owner._playLoop( self.instrument["id"], self.instrument["amplitude"], self.instrument["reverb"], [ self.curPage ], self.recordLoop, force = True, sync = False ) + + def handleRegularity( self, widget ): + if not self.settingBlock: + self.block.setData( "regularity", widget.get_value() ) + + def handleRegenerate( self, widget ): + parameters = GenerationParameters( + density = [0.9 for x in range(4)], + rythmRegularity = [self.block.getData( "regularity" ) for x in range(4)], + step = [0.5 for x in range(4)], + pitchRegularity = [0. for x in range(4)], + articule = [1. for x in range(4)], + silence = [0.1 for x in range(4)], + pattern = [3 for x in range(4)], + scale = GenerationConstants.NATURAL_MINOR) + + self.owner._generateTrack( self.instrument["id"], self.curPage, self.activeTrack, 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, sync = False ) + + def handleClear( self, widget ): + if self.recording: + self.noteDB.deleteNotesByTrack( [ self.curPage ], [ 2 ] ) + else: + 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, sync = False ) + + 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, self.activeTrack, 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, + self.activeTrack, + instrumentId = self.instrument["id"] ) + cs.pageId = page + id = self.noteDB.addNote( -1, page, self.activeTrack, cs ) + n = self.noteDB.getNote( page, self.activeTrack, id, self ) + self.selectNotes( { self.activeTrack:[n] }, True ) + n.playSampleNote( False ) + + noteS = self.noteDB.getNotesByTrack( page, self.activeTrack ) + for note in noteS: + if note.cs.onset < onset and (note.cs.onset + note.cs.duration) > onset: + self.noteDB.updateNote(self.curPage, self.activeTrack, 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, self.activeTrack, [], 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, self.activeTrack, len( self.selectedNotes[0] ) ] + + [ n.note.id for n in self.selectedNotes[0] ] + + [ -1 ] ) + 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, sync = False ) + + 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, self.activeTrack, 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.changedMute = self.owner._setMuted( True ) + self.owner.pushInstrument( self.instrument ) + self.owner.setKeyboardListener( self ) + + self.owner.addMetronome( self.curPage, self.grid ) + + # record to scratch track + self.owner.noteDB.tracksToClipboard( [ self.curPage ], [ 0 ] ) + self.owner.noteDB.pasteClipboard( [ self.curPage ], 0, { 2:0 }, { 2:self.instrument["id"] } ) + self.activeTrack = 2 + + self.recordLoop = self.owner._playLoop( self.instrument["id"], self.instrument["amplitude"], self.instrument["reverb"], [ self.curPage ], force = True, sync = False ) + self.updatePlayhead() + self.recordTimeout = gobject.timeout_add( 20, self._record_timeout ) + self.recording = True + + def stopRecording( self ): + if not self.recording: + return + + gobject.source_remove( self.recordTimeout ) + self.recording = False + + if self.recordingNote: + self.finishNote() + + self.owner.removeMetronome( self.curPage ) + + # copy scratch track back to default track + self.noteDB.deleteNotesByTrack( [ self.curPage ], [ 0 ] ) + self.owner.noteDB.tracksToClipboard( [ self.curPage ], [ 2 ] ) + self.owner.noteDB.pasteClipboard( [ self.curPage ], 0, { 0:2 } ) + self.noteDB.deleteNotesByTrack( [ self.curPage ], [ 2 ] ) + self.activeTrack = 0 + self.block.updateLoop() + + self.owner._stopLoop( self.recordLoop ) + self.recordLoop = None + self.clearPlayhead() + + self.owner.popInstrument() + self.owner.setKeyboardListener( None ) + if self.changedMute: + self.owner._setMuted( False ) + self.changedMute = False + + def recordNote( self, pitch ): + page = self.block.getData("id") + ticks = self.owner.noteDB.getPage(page).ticks + + onset = self.csnd.loopGetTick( self.recordLoop ) + onset = self.grid * int( onset/self.grid + 0.5 ) + if onset < 0: onset += ticks + elif onset >= ticks: onset -= ticks + self.recordingNotePassed = False + + cs = CSoundNote( onset, + pitch, + 0.75, + 0.5, + self.grid, + 2, + instrumentId = self.instrument["id"] ) + cs.pageId = self.curPage + + for n in self.noteDB.getNotesByTrack( self.curPage, 2 )[:]: + if onset < n.cs.onset: + break + if onset >= n.cs.onset + n.cs.duration: + continue + if n.cs.onset < onset and n.cs.duration > self.grid: + self.noteDB.updateNote( n.page, n.track, n.id, PARAMETER.DURATION, onset - n.cs.onset ) + else: + self.noteDB.deleteNote( n.page, n.track, n.id ) + + self.recordingNote = self.noteDB.addNote( -1, self.curPage, 2, cs ) + + self.recordLoop = self.owner._playLoop( self.instrument["id"], self.instrument["amplitude"], self.instrument["reverb"], [ self.curPage ], self.recordLoop, force = True, sync = False ) + + def finishNote( self ): + self.recordingNote = None + + 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, sync = False ) + + def _updateNote( self ): + page = self.block.getData("id") + ticks = self.owner.noteDB.getPage(page).ticks + + tick = self.csnd.loopGetTick( self.recordLoop ) + tick = self.grid * int( tick/self.grid + 0.5 ) + if tick < 0: tick += ticks + elif tick >= ticks: tick -= ticks + + + note = self.noteDB.getNote( self.curPage, self.activeTrack, self.recordingNote ) + + if tick > note.cs.onset: + self.recordingNotePassed = True + + if self.recordingNotePassed and 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, self.activeTrack ): + if n.cs.onset <= note.cs.onset: + continue + if 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, self.activeTrack ): + if n.cs.onset <= note.cs.onset: + continue + if n.cs.onset < note.cs.onset + note.cs.duration: + self.noteDB.deleteNote( n.page, n.track, n.id ) + else: + break + self.recordLoop = self.owner._playLoop( self.instrument["id"], self.instrument["amplitude"], self.instrument["reverb"], [ self.curPage ], self.recordLoop, force = True, sync = False ) + + 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() + if self.recordLoop: + self.recordLoop = self.owner._playLoop( self.instrument["id"], self.instrument["amplitude"], self.instrument["reverb"], [ self.curPage ], self.recordLoop, force = True, sync = False ) + + 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(), self.activeTrack, 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 ): + + def __init__( self, label, owner ): + Popup.__init__( self, label, owner ) + + self.gc = self.owner.gc + + self.GUI = {} + + self.GUI["mainBox"] = gtk.VBox() + self.set_content( self.GUI["mainBox"] ) + + #-- Keys ---------------------------------------------- + # match keycodes from JamMain.valid_shortcuts + layout = [ [ 0.0, [ 18, 19, 20, 21 ] ], + [ 0.3, [ 32, 33, 34, 35 ] ], + [ 1.7, [ 47, 48, 51 ] ], + [ 1.1, [ 60, 61 ] ] ] + + self.GUI["keyBox"] = gtk.VBox() + self.GUI["mainBox"].pack_start( self.GUI["keyBox"], padding = style.DEFAULT_PADDING - 2 ) + + for row in layout: + offset = row[0] + hbox = gtk.HBox() + self.GUI["keyBox"].pack_start( hbox, padding = 2 ) + separator = gtk.Label("") + separator.set_size_request( int(Block.Block.KEYSIZE*row[0]) + style.DEFAULT_PADDING, -1 ) + hbox.pack_start( separator, False ) + separator = gtk.Label("") + separator.set_size_request( style.DEFAULT_PADDING, -1 ) + hbox.pack_end( separator, False ) + for key in row[1]: + self.GUI[key] = gtk.ToggleButton() + self.GUI[key].connect( "expose-event", self.keyExpose ) + self.GUI[key].connect( "toggled", self.keyToggled ) + self.GUI[key].set_size_request( Block.Block.KEYSIZE, Block.Block.KEYSIZE ) + self.GUI[key].key = key + self.GUI[key].image = [ self.owner.getKeyImage( key, False ), + self.owner.getKeyImage( key, True ) ] + hbox.pack_start( self.GUI[key], False, padding = 2 ) + + #-- None ---------------------------------------------- + self.GUI["noneBox"] = gtk.HBox() + self.GUI["mainBox"].pack_start( self.GUI["noneBox"], padding = style.DEFAULT_PADDING ) + self.GUI["noneButton"] = gtk.Button( _("None") ) + self.GUI["noneButton"].connect( "clicked", self.handleNone ) + self.GUI["noneBox"].pack_start( self.GUI["noneButton"], True, False, padding = style.DEFAULT_PADDING ) + + self.GUI["mainBox"].show_all() + + self.key = None + + def setBlock( self, block ): + self.ignoreToggle = True + + self.block = block + self.key = self.block.getData( "key" ) + + if self.key != None: + self.GUI[self.key].set_active( True ) + + self.ignoreToggle = False + + Popup.setBlock( self, block ) + + def on_key_press( self, widget, event ): + key = event.hardware_keycode + if key in self.owner.valid_shortcuts.keys(): + self.block.setData( "key", key ) + if self.key != None: # clear old key + self.ignoreToggle = True + self.GUI[self.key].set_active( False ) + self.key = None + self.ignoreToggle = False + self.popdown( True ) + else: + self.owner.onKeyPress( widget, event ) + + def keyExpose( self, widget, event ): + self.gc.set_clip_mask( self.owner.blockMask ) + self.gc.set_clip_origin( event.area.x - Block.Block.KEYMASK_START, event.area.y ) + widget.window.draw_drawable( self.gc, widget.image[widget.get_active()], 0, 0, event.area.x, event.area.y, event.area.width, event.area.height ) + return True + + def keyToggled( self, widget ): + if self.ignoreToggle: + return + + if widget.get_active(): + self.block.setData( "key", widget.key ) + + self.ignoreToggle = True + + if self.key != None: # clear old key + self.GUI[self.key].set_active( False ) + self.key = None + + widget.set_active( False ) + + self.ignoreToggle = False + + self.popdown( True ) + + def handleNone( self, widget ): + if self.key != None: + self.ignoreToggle = True + self.GUI[self.key].set_active( False ) + self.key = None + self.ignoreToggle = False + + self.block.setData( "key", None ) + + self.popdown( True ) diff --git a/Jam/RythmGenerator.py b/Jam/RythmGenerator.py new file mode 100644 index 0000000..70e734c --- /dev/null +++ b/Jam/RythmGenerator.py @@ -0,0 +1,80 @@ +import random + +import common.Util.InstrumentDB as InstrumentDB +import common.Config as Config +from common.Util.CSoundNote import CSoundNote +from common.Generation.GenerationConstants import GenerationConstants +from GenRythm import GenRythm + +instrumentDB = InstrumentDB.getRef() + +def generator( instrument, nbeats, density, regularity, reverbSend ): + + makeRythm = GenRythm() + + noteDuration = GenerationConstants.DOUBLE_TICK_DUR / 2 + trackId = 0 + pan = 0.5 + attack = 0.005 + decay = 0.095 + filterType = 0 + filterCutoff = 1000 + tied = False + mode = 'mini' + + def makePitchSequence(length, drumPitch): + pitchSequence = [] + append = pitchSequence.append + list = range(length) + max = len(drumPitch) - 1 + for i in list: + append(drumPitch[ random.randint( 0, max ) ] ) + return pitchSequence + + def makeGainSequence( onsetList ): + gainSequence = [] + append = gainSequence.append + for onset in onsetList: + if onset == 0: + gain = random.uniform(GenerationConstants.GAIN_MID_MAX_BOUNDARY, GenerationConstants.GAIN_MAX_BOUNDARY) + elif ( onset % Config.TICKS_PER_BEAT) == 0: + gain = random.uniform(GenerationConstants.GAIN_MID_MIN_BOUNDARY, GenerationConstants.GAIN_MID_MAX_BOUNDARY) + else: + gain = random.uniform(GenerationConstants.GAIN_MIN_BOUNDARY, GenerationConstants.GAIN_MID_MIN_BOUNDARY) + append(gain) + return gainSequence + + def pageGenerate( regularity, drumPitch ): + barLength = Config.TICKS_PER_BEAT * nbeats + + #print 'pageGenerate drumPitch[0] ', drumPitch[0] + currentInstrument = instrumentDB.instNamed[ instrument ].kit[ drumPitch[0] ] + + rythmSequence = makeRythm.drumRythmSequence(currentInstrument, nbeats, density, regularity) + pitchSequence = makePitchSequence(len(rythmSequence), drumPitch ) + gainSequence = makeGainSequence(rythmSequence) + + trackNotes = [] + list = range(len(rythmSequence)) + for i in list: + trackNotes.append( CSoundNote( rythmSequence[i], pitchSequence[i], gainSequence[i], + pan, noteDuration, trackId, + instrumentDB.instNamed[instrument].instrumentId, attack, + decay, reverbSend, filterType, filterCutoff, tied, mode)) + return trackNotes + +################################################################################## + # begin generate() + if regularity > 0.75: + streamOfPitch = GenerationConstants.DRUM_COMPLEXITY1 + elif regularity > 0.5: + streamOfPitch = GenerationConstants.DRUM_COMPLEXITY2 + elif regularity > 0.25: + streamOfPitch = GenerationConstants.DRUM_COMPLEXITY3 + else: + streamOfPitch = GenerationConstants.DRUM_COMPLEXITY4 + + trackNotes = [] + for drumPitch in streamOfPitch: + trackNotes.append(pageGenerate( regularity, drumPitch )) + return trackNotes diff --git a/Jam/Toolbars.py b/Jam/Toolbars.py new file mode 100644 index 0000000..9069060 --- /dev/null +++ b/Jam/Toolbars.py @@ -0,0 +1,600 @@ + +import pygtk +pygtk.require( '2.0' ) +import gtk +import gobject + +import os, commands + +from gettext import gettext as _ + +from sugar.graphics.palette import Palette, WidgetInvoker +from sugar.graphics.radiotoolbutton import RadioToolButton +from sugar.graphics.toolbutton import ToolButton +from sugar.graphics.toggletoolbutton import ToggleToolButton +from sugar.graphics.combobox import ComboBox +from sugar.graphics.toolcombobox import ToolComboBox + +from common.Util.ThemeWidgets import * + +import common.Config as Config +from common.Util import OS + + +class JamToolbar( gtk.Toolbar ): + + def __init__( self, owner ): + gtk.Toolbar.__init__( self ) + + self.owner = owner + + self.toolItem = {} + + self.volumeImg = gtk.Image() + + self._insert_separator( True ) + + self.volumeAdjustment = gtk.Adjustment( 0.0, 0, 1.0, 0.1, 0.1, 0 ) + self.volumeAdjustment.connect( 'value-changed', self.handleVolume ) + self.volumeSlider = gtk.HScale( adjustment = self.volumeAdjustment ) + self.volumeSlider.set_size_request( 260, -1 ) + self.volumeSlider.set_draw_value( False ) + self._add_tooltip( self.volumeSlider, _("Master volume") ) + self._insert_widget( self.volumeSlider, -1 ) + self._insert_widget( self.volumeImg, -1 ) + + self._insert_separator( True ) + + self.tempoImg = gtk.Image() + + self.delayedTempo = 0 # used to store tempo updates while the slider is active + self.tempoSliderActive = False + + self.tempoAdjustment = gtk.Adjustment( Config.PLAYER_TEMPO_LOWER, Config.PLAYER_TEMPO_LOWER, Config.PLAYER_TEMPO_UPPER+1, 10, 10, 0 ) + self.tempoAdjustmentHandler = self.tempoAdjustment.connect( 'value-changed', self.handleTempo ) + self.tempoSlider = gtk.HScale( adjustment = self.tempoAdjustment ) + self.tempoSlider.set_size_request( 260, -1 ) + self.tempoSlider.set_draw_value( False ) + self.tempoSlider.connect("button-press-event", self.handleTempoSliderPress) + self.tempoSlider.connect("button-release-event", self.handleTempoSliderRelease) + self._add_tooltip( self.tempoSlider, _("Tempo") ) + self._insert_widget( self.tempoSlider, -1 ) + self._insert_widget( self.tempoImg, -1 ) + + self._insert_separator( True ) + + self.show_all() + + #def _add_palette( self, widget, palette, position = Palette.DEFAULT ): + def _add_palette( self, widget, palette ): + widget._palette = palette + widget._palette.props.invoker = WidgetInvoker( widget ) + #widget._palette.set_property( "position", position ) + + def _add_tooltip( self, widget, tooltip ): + #self._add_palette( widget, Palette( tooltip ), Palette.DEFAULT ) + self._add_palette( widget, Palette( tooltip ) ) + + def _insert_widget( self, widget, pos ): + self.toolItem[ widget ] = gtk.ToolItem() + self.toolItem[ widget ].add( widget ) + self.insert( self.toolItem[ widget ], pos ) + + def _insert_separator( self, expand = False ): + separator = gtk.SeparatorToolItem() + separator.set_draw( False ) + separator.set_expand( expand ) + self.insert( separator, -1 ) + + def mapRange( self, value, ilower, iupper, olower, oupper ): + if value == iupper: + return oupper + return olower + int( (oupper-olower+1)*(value-ilower)/float(iupper-ilower) ) + + def handleVolume( self, widget ): + self.owner._setVolume( widget.get_value() ) + + img = self.mapRange( widget.value, widget.lower, widget.upper, 0, 3 ) + self.volumeImg.set_from_file(Config.TAM_TAM_ROOT + '/icons/volume' + str(img) + '.svg') + + def handleTempo( self, widget ): + if self.owner.network.isPeer(): + self.owner.requestTempoChange(int(widget.get_value())) + else: + self._updateTempo( widget.get_value() ) + + def setTempo( self, tempo, quiet = False ): + if self.tempoSliderActive: + self.delayedTempo = tempo + elif quiet: + self.tempoAdjustment.handler_block( self.tempoAdjustmentHandler ) + self.tempoAdjustment.set_value( tempo ) + self._updateTempo( tempo ) + self.tempoAdjustment.handler_unblock( self.tempoAdjustmentHandler ) + else: + self.tempoAdjustment.set_value( tempo ) + + def _updateTempo( self, tempo ): + self.owner._setTempo( tempo ) + + img = self.mapRange( tempo, self.tempoAdjustment.lower, self.tempoAdjustment.upper, 1, 8 ) + self.tempoImg.set_from_file(Config.TAM_TAM_ROOT + '/icons/tempo' + str(img) + '.svg') + + def handleTempoSliderPress(self, widget, event): + self.tempoSliderActive = True + + def handleTempoSliderRelease(self, widget, event): + self.tempoSliderActive = False + if self.owner.network.isPeer() and self.delayedTempo != 0: + if self.owner.getTempo() != self.delayedTempo: + self.setTempo( self.delayedTempo, True ) + self.delayedTempo = 0 + self.owner.sendSyncQuery() + +class PlaybackToolbar( gtk.Toolbar ): + + def __init__( self, owner ): + gtk.Toolbar.__init__( self ) + + self.owner = owner + + self.toolItem = {} + + self.stopButton = ToolButton('media-playback-stop') + self.stopButton.connect('clicked',self.handleStopButton) + self.insert(self.stopButton, -1) + self.stopButton.show() + self.stopButton.set_tooltip(_('Stop Loops')) + + self.muteButton = ToggleToolButton('mute') + self.muteButton.connect('clicked',self.handleMuteButton) + self.insert(self.muteButton, -1) + self.muteButton.show() + self.muteButton.set_tooltip(_('Mute Loops')) + + self._insert_separator( True ) + + self.blockBeat = False + self.beatWheel = [] + + btn = RadioToolButton(group = None ) + btn.set_named_icon('beats') + btn.connect( 'toggled', self.setBeat, 0 ) + btn.set_tooltip( _('Jump To Beat') ) + self.insert( btn, -1 ) + self.beatWheel.append( btn ) + + for i in range(1,12): + btn = RadioToolButton(group = self.beatWheel[0] ) + btn.set_named_icon('beats') + btn.connect( 'toggled', self.setBeat, i ) + btn.set_tooltip( _('Jump To Beat') ) + self.insert( btn, -1 ) + self.beatWheel.append( btn ) + + + label = gtk.Label( _("Synch to:") ) + self.syncLabel = gtk.ToolItem() + self.syncLabel.add( label ) + self.insert( self.syncLabel, -1 ) + + self.comboBox = ComboBox() + self.comboBox.append_item( 1, _("1 Beat") ) + self.comboBox.append_item( 2, _("2 Beats") ) + self.comboBox.append_item( 3, _("3 Beats") ) + self.comboBox.append_item( 4, _("4 Beats") ) + self.comboBox.append_item( 5, _("5 Beats") ) + self.comboBox.append_item( 6, _("6 Beats") ) + self.comboBox.append_item( 7, _("7 Beats") ) + self.comboBox.append_item( 8, _("8 Beats") ) + self.comboBox.append_item( 9, _("9 Beats") ) + self.comboBox.append_item( 10, _("10 Beats") ) + self.comboBox.append_item( 11, _("11 Beats") ) + self.comboBox.append_item( 12, _("12 Beats") ) + self.comboBox.set_active( 4 - 1 ) # default 4 beats + self.comboBox.connect( "changed", self.changeSync ) + self.syncBox = ToolComboBox( self.comboBox ) + self.insert( self.syncBox, -1 ) + + self.show_all() + + #def _add_palette( self, widget, palette, position = Palette.DEFAULT ): + def _add_palette( self, widget, palette ): + widget._palette = palette + widget._palette.props.invoker = WidgetInvoker( widget ) + #widget._palette.set_property( "position", position ) + + def _add_tooltip( self, widget, tooltip ): + #self._add_palette( widget, Palette( tooltip ), Palette.DEFAULT ) + self._add_palette( widget, Palette( tooltip ) ) + + def _insert_widget( self, widget, pos ): + self.toolItem[ widget ] = gtk.ToolItem() + self.toolItem[ widget ].add( widget ) + self.insert( self.toolItem[ widget ], pos ) + + def _insert_separator( self, expand = False ): + separator = gtk.SeparatorToolItem() + separator.set_draw( False ) + separator.set_expand( expand ) + self.insert( separator, -1 ) + + def setBeat( self, widget, beat ): + if not self.blockBeat and widget.get_active(): + self.owner._setBeat( beat ) + + def updateBeatWheel( self, beat ): + self.blockBeat = True + self.beatWheel[ beat ].set_active( True ) + self.blockBeat = False + + def setSyncBeats( self, beats ): + self.comboBox.set_active( beats - 1 ) + + def changeSync( self, widget ): + beats = widget.get_active() + 1 + for i in range(beats): + self.beatWheel[i].show() + for i in range(beats,12): + self.beatWheel[i].hide() + + self.owner._setSyncBeats( beats ) + + def handleStopButton( self, widget ): + self.owner.setStopped() + + def setMuted( self, muted ): + if self.muteButton.get_active() == muted: + return + + self.muteButton.set_active( muted ) + + def handleMuteButton( self, widget ): + if widget.get_active(): + self.owner._setMuted( True ) + else: + self.owner._setMuted( False ) + + +class DesktopToolbar( gtk.Toolbar ): + + def __init__( self, owner ): + gtk.Toolbar.__init__( self ) + + self.owner = owner + + self._insert_separator( True ) + + self.desktop = [] + + btn = RadioToolButton(group = None ) + btn.set_named_icon('preset1') + btn.connect( 'toggled', self.setDesktop, 0 ) + btn.set_tooltip( _('Desktop 1') ) + self.insert( btn, -1 ) + self.desktop.append( btn ) + + for i in range(2,11): + btn = RadioToolButton( group = self.desktop[0] ) + btn.set_named_icon('preset%d'%i) + btn.connect( 'toggled', self.setDesktop, i-1 ) + btn.set_tooltip( _('Desktop %d'%i) ) + self.insert( btn, -1 ) + self.desktop.append( btn ) + + self._insert_separator( True ) + + self.show_all() + + def _insert_separator( self, expand = False ): + separator = gtk.SeparatorToolItem() + separator.set_draw( False ) + separator.set_expand( expand ) + self.insert( separator, -1 ) + + def getDesktopButton( self, which ): + return self.desktop[which] + + def setDesktop( self, widget, which ): + if widget.get_active(): + self.owner._setDesktop( which ) + +class RecordToolbar(gtk.Toolbar): + def __init__(self, jam): + gtk.Toolbar.__init__(self) + + def _insertSeparator(x = 1): + for i in range(x): + self.separator = gtk.SeparatorToolItem() + self.separator.set_draw(True) + self.insert(self.separator,-1) + self.separator.show() + + #self.toolbox = toolbox + self.jam = jam + + if Config.FEATURES_MIC: + self.micRec1Button = ToolButton('rec1') + self.micRec1Button.connect('clicked',self.jam.micRec,'mic1') + self.insert(self.micRec1Button, -1) + self.micRec1Button.show() + self.micRec1Button.set_tooltip(_('Record microphone into slot 1')) + + self.micRec2Button = ToolButton('rec2') + self.micRec2Button.connect('clicked',self.jam.micRec,'mic2') + self.insert(self.micRec2Button, -1) + self.micRec2Button.show() + self.micRec2Button.set_tooltip(_('Record microphone into slot 2')) + + self.micRec3Button = ToolButton('rec3') + self.micRec3Button.connect('clicked',self.jam.micRec,'mic3') + self.insert(self.micRec3Button, -1) + self.micRec3Button.show() + self.micRec3Button.set_tooltip(_('Record microphone into slot 3')) + + self.micRec4Button = ToolButton('rec4') + self.micRec4Button.connect('clicked',self.jam.micRec,'mic4') + self.insert(self.micRec4Button, -1) + self.micRec4Button.show() + self.micRec4Button.set_tooltip(('Record microphone into slot 4')) + + _insertSeparator() + + if Config.FEATURES_NEWSOUNDS: + self._loopSettingsPalette = LoopSettingsPalette(_('Add new Sound'), self.jam) + self.loopSetButton = ToggleToolButton('loop') + self.loopSetButton.set_palette(self._loopSettingsPalette) + self.insert(self.loopSetButton, -1) + self.loopSetButton.show() + + self.show_all() + +class LoopSettingsPalette( Palette ): + def __init__( self, label, jam ): + Palette.__init__( self, label ) + self.connect('popup', self.handlePopup) + self.connect('popdown', self.handlePopdown) + + self.jam = jam + + self.tooltips = gtk.Tooltips() + self.loopedSound = False + self.soundLength = 1.00 + self.start = 0 + self.end = 1.00 + self.dur = 0.01 + self.volume = 1 + self.register = 0 + self.ok = True + + self.mainBox = gtk.VBox() + + self.controlsBox = gtk.HBox() + + self.GUI = {} + + self.soundBox = gtk.HBox() + self.soundLabel = gtk.Label(_('Sound: ')) + self.soundMenuBox = BigComboBox() + self.sounds = [snd for snd in os.listdir(Config.DATA_DIR) if snd != 'snds_info'] + for sound in self.sounds: + self.soundMenuBox.append_item(self.sounds.index(sound), sound) + self.soundMenuBox.connect('changed', self.handleSound) + self.soundBox.pack_start(self.soundLabel, False, False, padding=10) + self.soundBox.pack_start(self.soundMenuBox, False, False, padding=10) + + self.mainBox.pack_start(self.soundBox, False, False, 10) + + nameBox = gtk.VBox() + self.nameEntry = gtk.Entry() + entrycolor = gtk.gdk.Color() + self.nameEntry.modify_text(gtk.STATE_NORMAL, entrycolor) + self.nameEntry.set_text("name_of_the_sound") + nameBox.pack_start(self.nameEntry) + self.mainBox.pack_start(nameBox, False, False, 10) + + registerBox = gtk.HBox() + self.registerBoxLabel = gtk.Label(_('Register: ')) + self.registerMenuBox = BigComboBox() + self.registers = ['LOW', 'MID', 'HIGH', 'PUNCH'] + for reg in self.registers: + self.registerMenuBox.append_item(self.registers.index(reg), reg) + self.registerMenuBox.connect('changed', self.handleRegister) + registerBox.pack_start(self.registerBoxLabel, False, False, padding=10) + registerBox.pack_end(self.registerMenuBox, False, False, padding=10) + self.mainBox.pack_start(registerBox, False, False, 10) + + loopedBox = gtk.HBox() + loopedLabel = gtk.Label("Looped sound: ") + loopedToggle = ImageToggleButton(Config.IMAGE_ROOT+"checkOff.svg",Config.IMAGE_ROOT+"checkOn.svg") + loopedToggle.connect('button-press-event', self.handleLooped ) + loopedBox.pack_start(loopedLabel, False, False, padding=10) + loopedBox.pack_end(loopedToggle, False, False, padding=10) + self.mainBox.pack_start(loopedBox, False, False, 10) + + startBox = gtk.VBox() + self.startAdjust = gtk.Adjustment( 0.01, 0, 1., .001, .001, 0) + self.GUI['startSlider'] = gtk.VScale( adjustment = self.startAdjust ) + self.startAdjust.connect("value-changed", self.handleStart) + self.GUI['startSlider'].set_inverted(True) + self.GUI['startSlider'].set_size_request(50, 200) + self.GUI['startSlider'].set_digits(3) + # tooltip closes loop settings palette!!! + #self._add_tooltip( self.GUI['startSlider'], _("Loop start position") ) + self.handleStart( self.startAdjust ) + startBox.pack_start(self.GUI['startSlider'], True, True, 5) + self.controlsBox.pack_start(startBox) + + endBox = gtk.VBox() + self.endAdjust = gtk.Adjustment( 0.9, 0, 1, .001, .001, 0) + self.GUI['endSlider'] = gtk.VScale( adjustment = self.endAdjust ) + self.endAdjust.connect("value-changed", self.handleEnd) + self.GUI['endSlider'].set_inverted(True) + self.GUI['endSlider'].set_size_request(50, 200) + self.GUI['endSlider'].set_digits(3) + self.handleEnd( self.endAdjust ) + endBox.pack_start(self.GUI['endSlider'], True, True, 5) + self.controlsBox.pack_start(endBox) + + durBox = gtk.VBox() + self.durAdjust = gtk.Adjustment( 0.01, 0, 0.2, .001, .001, 0) + self.GUI['durSlider'] = gtk.VScale( adjustment = self.durAdjust ) + self.durAdjust.connect("value-changed", self.handleDur) + self.GUI['durSlider'].set_inverted(True) + self.GUI['durSlider'].set_size_request(50, 200) + self.GUI['durSlider'].set_digits(3) + self.handleDur( self.durAdjust ) + durBox.pack_start(self.GUI['durSlider'], True, True, 5) + self.controlsBox.pack_start(durBox) + + volBox = gtk.VBox() + self.volAdjust = gtk.Adjustment( 1, 0, 2, .01, .01, 0) + self.GUI['volSlider'] = gtk.VScale( adjustment = self.volAdjust ) + self.volAdjust.connect("value-changed", self.handleVol) + self.GUI['volSlider'].set_inverted(True) + self.GUI['volSlider'].set_size_request(50, 200) + self.GUI['volSlider'].set_digits(3) + self.handleVol( self.volAdjust ) + volBox.pack_start(self.GUI['volSlider'], True, True, 5) + self.controlsBox.pack_start(volBox) + + self.mainBox.pack_start(self.controlsBox, False, False, 10) + + previewBox = gtk.VBox() + self.playStopButton = ImageToggleButton(Config.IMAGE_ROOT + 'miniplay.png', Config.IMAGE_ROOT + 'stop.png') + self.playStopButton.connect('button-press-event' , self.handlePlayButton) + previewBox.pack_start(self.playStopButton) + self.mainBox.pack_start(previewBox, False, False, 10) + + checkBox = gtk.VBox() + checkButton = ImageButton(Config.TAM_TAM_ROOT + '/icons/accept.svg') + checkButton.connect('clicked' , self.handleCheck) + checkBox.pack_start(checkButton) + self.mainBox.pack_start(checkBox, False, False, 10) + + self.mainBox.show_all() + self.set_content(self.mainBox) + + def _add_palette( self, widget, palette ): + widget._palette = palette + widget._palette.props.invoker = WidgetInvoker( widget ) + #widget._palette.set_property( "position", position ) + + def _add_tooltip( self, widget, tooltip ): + #self._add_palette( widget, Palette( tooltip ), Palette.DEFAULT ) + self._add_palette( widget, Palette( tooltip ) ) + + def handlePopup(self, widget, data=None): + self.setButtonState() + self.soundMenuBox.remove_all() + self.sounds = [snd for snd in os.listdir(Config.DATA_DIR) if snd != 'snds_info'] + for sound in self.sounds: + self.soundMenuBox.append_item(self.sounds.index(sound), sound) + self.nameEntry.set_text("name_of_the_sound") + + def handlePopdown(self, widget, data=None): + if self.playStopButton.get_active() == True: + self.jam.loopSettingsPlayStop(True, self.loopedSound) + + def handleSound(self, widget, data=None): + self.sndname = self.sounds[widget.props.value] + fullname = Config.DATA_DIR + '/' + self.sndname + results = OS.system("du -b %s" % fullname) + if results[0] == 0: + list = results[1].split() + soundLength = float(list[0]) / 2 / 16000. + self.nameEntry.set_text(self.sndname) + self.set_values(soundLength) + self.startAdjust.set_all( 0.01, 0, soundLength, .001, .001, 0) + self.endAdjust.set_all( soundLength-0.01, 0, soundLength, .001, .001, 0) + self.timeoutLoad = gobject.timeout_add(2000, self.loopSettingsDelay) + + def loopSettingsDelay(self): + self.jam.load_ls_instrument(self.sndname) + gobject.source_remove( self.timeoutLoad ) + + def handleCheck(self, widget): + if self.nameEntry.get_text() != self.sndname: + oldName = self.sndname + self.sndname = self.nameEntry.get_text() + copy = True + else: + copy = False + + ofile = open(Config.SNDS_INFO_DIR + '/' + self.sndname, 'w') + if self.loopedSound: + tied = str(Config.INST_TIED) + else: + tied = str(Config.INST_SIMP) + register = str(self.register) + category = 'mysounds' + start = str(self.start) + end = str(self.end) + dur = str(self.dur) + vol = str(self.volume) + + ofile.write('TamTam idf v1\n') + ofile.write(self.sndname + '\n') + ofile.write(tied + '\n') + ofile.write(register + '\n') + ofile.write(start + '\n') + ofile.write(end + '\n') + ofile.write(dur + '\n') + ofile.write(vol + '\n') + ofile.write(self.sndname + '\n') + ofile.write(Config.IMAGE_ROOT+"/"+self.sndname+".png\n") + ofile.write(category) + ofile.close() + if copy: + OS.system('cp ' + Config.DATA_DIR + '/' + oldName + ' ' + Config.DATA_DIR + '/' + self.sndname) + + def set_values(self, soundLength): + self.soundLength = soundLength + self.handleStart(self.GUI['startSlider']) + self.handleEnd(self.GUI['endSlider']) + + def handleLooped(self, widget, data=None): + if widget.get_active() == True: + self.loopedSound = False + else: + self.loopedSound = True + + def handleRegister(self, widget, data=None): + self.register = self.registers[widget.props.value] + + def handleStart(self, widget, data=None): + self.start = self.startAdjust.value + if self.start > self.end: + self.start = self.end + self.jam.loopSettingsChannel('lstart', self.start) + + def handleEnd(self, widget, data=None): + self.end = self.endAdjust.value + if self.end < self.start: + self.end = self.start + self.jam.loopSettingsChannel('lend', self.end) + + def handleDur(self, widget, data=None): + self.dur = self.durAdjust.value + self.jam.loopSettingsChannel('ldur', self.dur) + + def handleVol(self, widget, data=None): + self.volume = self.volAdjust.value + self.jam.loopSettingsChannel('lvol', self.volume) + + def handlePlayButton(self, widget, data=None): + if self.ok: + self.jam.loopSettingsPlayStop(widget.get_active(), self.loopedSound) + if self.loopedSound == False and widget.get_active() == False: + self.timeoutStop = gobject.timeout_add(int(self.soundLength * 1000)+500, self.playButtonState) + + def setButtonState(self): + self.ok = False + self.playStopButton.set_active(False) + self.ok = True + + def playButtonState(self): + self.ok = False + self.playStopButton.set_active(False) + gobject.source_remove(self.timeoutStop) + self.ok = True diff --git a/Jam/__init__.py b/Jam/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Jam/__init__.py -- cgit v0.9.1