class Loop(Block): HEAD = scale(13) BEAT = scale(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 = scale(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)
class Block: WIDTH = scale(100) HEIGHT = scale(100) SNAP = scale(15) PAD = scale(4) KEYSIZE = scale(26) KEYMASK_START = scale(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 Drum(Block): MASK_START = scale(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])
def __init__(self, activity): GObject.GObject.__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) # csnd expects a range 0-100 for now self.csnd.setMasterVolume(self.volume * 100) self.csnd.setTempo(self.tempo) self.muted = False #-- Drawing ------------------------------------------- def darken(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 r * 256, g * 256, b * 256 def lighten(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 r * 256, g * 256, b * 256 xoColor = profile.get_color() if not xoColor: xoColorKey = ("#8D8D8D,#FFDDEA") xoColor = XoColor(xoColorKey) # colors in Config and in XoColor are strings, # the colors in style are style.Color, transform all to Gdk.Color self.colors = {"bg": CairoUtil.get_gdk_color(Config.PANEL_BCK_COLOR), "black": style.COLOR_BLACK.get_gdk_color(), #"Picker_Bg": colormap.alloc_color("#404040"), #"Picker_Bg_Inactive": colormap.alloc_color("#808080"), "Picker_Bg": style.COLOR_TOOLBAR_GREY.get_gdk_color(), "Picker_Bg_Inactive": style.COLOR_BUTTON_GREY.get_gdk_color(), "Picker_Fg": style.COLOR_WHITE.get_gdk_color(), "Border_Active": \ CairoUtil.get_gdk_color(xoColor.get_stroke_color()), "Border_Inactive": CairoUtil.get_gdk_color("#8D8D8D"), "Border_Highlight": CairoUtil.get_gdk_color("#FFFFFF"), "Bg_Active": CairoUtil.get_gdk_color(xoColor.get_fill_color()), "Bg_Inactive": CairoUtil.get_gdk_color("#DBDBDB"), "Preview_Note_Fill": CairoUtil.get_gdk_color(Config.BG_COLOR), "Preview_Note_Border": CairoUtil.get_gdk_color(Config.FG_COLOR), "Preview_Note_Selected": style.COLOR_WHITE.get_gdk_color(), # TODO: lighten here can be removed, check if is used in other # places "Note_Fill_Active": Gdk.Color(*lighten("#590000")), # base "Border_Active" "Note_Fill_Inactive": Gdk.Color(*lighten("#8D8D8D")), # base "Border_Inactive" "Beat_Line": CairoUtil.get_gdk_color("#959595")} self.colors["Note_Border_Active"] = self.colors["Border_Active"] self.colors["Note_Border_Inactive"] = self.colors["Border_Inactive"] self.sampleNoteHeight = 7 self.sampleBg = cairo.ImageSurface.create_from_png( imagefile('sampleBG.png')) 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) jam_toolbar_button = ToolbarButton(label=_('Jam'), page=self.jamToolbar, icon_name='voltemp') self.jamToolbar.show() jam_toolbar_button.show() self.activity.toolbar_box.toolbar.insert(jam_toolbar_button, -1) self.beatToolbar = BeatToolbar(self) beat_toolbar_button = ToolbarButton(label=_('Beat'), page=self.beatToolbar, icon_name='heart') self.beatToolbar.show() beat_toolbar_button.show() self.activity.toolbar_box.toolbar.insert(beat_toolbar_button, -1) self.desktopToolbar = DesktopToolbar(self) desktop_toolbar_button = ToolbarButton(label=_('Desktop'), page=self.desktopToolbar, icon_name='jam-presets-list') self.desktopToolbar.show() desktop_toolbar_button.show() self.activity.toolbar_box.toolbar.insert(desktop_toolbar_button, -1) if Config.FEATURES_MIC or Config.FEATURES_NEWSOUNDS: self.recordToolbar = RecordToolbar(self) record_toolbar_button = ToolbarButton(label=_('Record'), page=self.recordToolbar, icon_name='microphone') self.recordToolbar.show() record_toolbar_button.show() self.activity.toolbar_box.toolbar.insert(record_toolbar_button, -1) separator = Gtk.SeparatorToolItem() separator.props.draw = True separator.set_expand(False) self.activity.toolbar_box.toolbar.insert(separator, -1) separator.show() common_playback_buttons(self.activity.toolbar_box.toolbar, self) #-- GUI ----------------------------------------------- if True: # GUI self.modify_bg(Gtk.StateType.NORMAL, self.colors["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"], True, True, 0) #-- Bank ---------------------------------------------- separator = Gtk.Label(label=" ") separator.set_size_request(-1, style.TOOLBOX_SEPARATOR_HEIGHT) self.GUI["mainVBox"].pack_start(separator, False, True, 0) self.GUI["notebook"] = Gtk.Notebook() self.GUI["notebook"].set_scrollable(True) self.GUI["notebook"].modify_bg(Gtk.StateType.NORMAL, self.colors["Picker_Bg"]) self.GUI["notebook"].modify_bg(Gtk.StateType.ACTIVE, self.colors["Picker_Bg_Inactive"]) # TODO gtk3 no available anymore? #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, scale(160)) self.GUI["notebook"].connect("switch-page", self.setPicker) self.GUI["mainVBox"].pack_start(self.GUI["notebook"], False, False, 0) self.pickers = {} self.pickerScroll = {} for type in [Picker.Instrument, Picker.Drum, Picker.Loop]: self.pickers[type] = type(self) def prepareLabel(name): label = Gtk.Label(label=Tooltips.categories.get(name) or name) label.set_alignment(0.0, 0.5) label.modify_fg(Gtk.StateType.NORMAL, self.colors["Picker_Fg"]) label.modify_fg(Gtk.StateType.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.toolbar_box.set_current_toolbar(1) # JamToolbar self.setDesktop(0, True)
class Loop(Block): HEAD = scale(13) BEAT = scale(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 = scale(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 noteDB = self.owner.noteDB newid = noteDB.duplicatePages([block.data["id"]])[block.data["id"]] self.data["id"] = newid self.data["beats"] = 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 = 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") is 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 is not 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() # must be attached to an instrument if root.type == Instrument: self.owner.activateLoop(root.child) Block.button_release(self, event) def _doDraw(self, startX, startY, stopX, stopY, ctx, highlight=False, key_highlight=False): x = max(startX, self.x) y = max(startY, self.y) loop = self.img[self.active] width = loop.get_width() height = loop.get_height() ctx.save() CairoUtil.draw_loop_mask(ctx, x, y, width, height) ctx.set_line_width(3) if self.active: ctx.set_source_rgb( *CairoUtil.gdk_color_to_cairo(self.owner.colors["Bg_Active"])) else: ctx.set_source_rgb(*CairoUtil.gdk_color_to_cairo( self.owner.colors["Bg_Inactive"])) ctx.fill_preserve() if self.active: ctx.set_source_rgb(*CairoUtil.gdk_color_to_cairo( self.owner.colors["Border_Active"])) else: ctx.set_source_rgb(*CairoUtil.gdk_color_to_cairo( self.owner.colors["Border_Inactive"])) if highlight: ctx.set_source_rgb(*CairoUtil.gdk_color_to_cairo( self.owner.colors["Border_Highlight"])) ctx.stroke() ctx.save() # draw block ctx.translate(x, y) ctx.set_source_surface(loop) ctx.paint() ctx.restore() #draw key if self.keyActive: ctx.save() ctx.translate(x + Loop.KEYRECT[0], y + Loop.KEYRECT[1]) ctx.set_source_surface(self.keyImage[self.active]) ctx.paint() ctx.restore() if key_highlight: ctx.save() ctx.set_source_rgb(*CairoUtil.gdk_color_to_cairo( self.owner.colors["Border_Highlight"])) ctx.translate(x + Loop.KEYRECT[0] - 1, y + Loop.KEYRECT[1] - 1) CairoUtil.draw_round_rect(ctx, 0, 0, Block.KEYSIZE + 2, Block.KEYSIZE + 2, radio=5) ctx.stroke() ctx.restore() ctx.restore() def drawHighlight(self, startX, startY, stopX, stopY, ctx): self._doDraw(startX, startY, stopX, stopY, ctx, highlight=True) def drawKeyHighlight(self, ctx): self._doDraw(self.x, self.y, self.x, self.y, ctx, key_highlight=True) 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)
class Drum(Block): MASK_START = scale(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 is 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, ctx, highlight=False, key_highlight=False): 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 ctx.save() # draw border CairoUtil.draw_drum_mask(ctx, x, y, width) ctx.set_line_width(3) if self.active: ctx.set_source_rgb( *CairoUtil.gdk_color_to_cairo(self.owner.colors["Bg_Active"])) else: ctx.set_source_rgb(*CairoUtil.gdk_color_to_cairo( self.owner.colors["Bg_Inactive"])) ctx.fill_preserve() if self.active: ctx.set_source_rgb(*CairoUtil.gdk_color_to_cairo( self.owner.colors["Border_Active"])) else: ctx.set_source_rgb(*CairoUtil.gdk_color_to_cairo( self.owner.colors["Border_Inactive"])) if highlight: ctx.set_source_rgb(*CairoUtil.gdk_color_to_cairo( self.owner.colors["Border_Highlight"])) ctx.stroke() # draw block ctx.save() ctx.translate(x, y) ctx.set_source_surface(self.img[self.active]) ctx.paint() ctx.restore() # draw key ctx.save() ctx.translate(self.x + Drum.KEYRECT[0], self.y + Drum.KEYRECT[1]) ctx.set_source_surface(self.keyImage[self.active]) ctx.paint() ctx.restore() ctx.restore() if key_highlight: ctx.save() ctx.set_source_rgb(*CairoUtil.gdk_color_to_cairo( self.owner.colors["Border_Highlight"])) ctx.translate(x + Drum.KEYRECT[0] - 1, y + Drum.KEYRECT[1] - 1) CairoUtil.draw_round_rect(ctx, 0, 0, Block.KEYSIZE + 2, Block.KEYSIZE + 2, radio=5) ctx.stroke() ctx.restore() def drawHighlight(self, startX, startY, stopX, stopY, ctx): self._doDraw(startX, startY, stopX, stopY, ctx, highlight=True) def drawKeyHighlight(self, ctx): self._doDraw(self.x, self.y, self.x, self.y, ctx, key_highlight=True) 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 SynthLabConstants: PIC_SIZE = scale(80) HALF_SIZE = PIC_SIZE // 2 PIC_SIZE_HIGHLIGHT = PIC_SIZE + 4 HALF_SIZE_HIGHLIGHT = PIC_SIZE + 2 GT_CONTROL_OUTPUT = 0 GT_CONTROL_INPUT = 1 GT_SOUND_OUTPUT = 2 GT_SOUND_INPUT = 3 # GATE_POINT[objecttype][gatetype][gatenum] = (x,y) # relative to object center GATE_POINT = [ [ [(scale(-1), scale(33))], ], [ [], [(scale(-24), scale(-34)), (scale(-9), scale(-34)), (scale(8), scale(-34)), (scale(24), scale(-34))], [(scale(-1), scale(33))], ], [ [], [(scale(31), scale(-20)), (scale(31), scale(-6)), (scale(31), scale(6)), (scale(31), scale(19))], [(scale(-3), scale(33))], [(scale(-3), scale(-34))], ], [ [], [], [], [(scale(2), scale(-35))], ], ] # GATE_MAP[objecttype][gatetype][gatenum] = [sx, sy, ex, ey, (wireX,wireY)] # gate locations relative to object center GATE_MAP = [ [ [[scale(-7), scale(26), scale(4), scale(39)]], ], [ [], [[scale(-30), scale(-40), scale(-19), scale(-28)], [scale(-15), scale(-40), scale(-3), scale(-28)], [scale(3), scale(-40), scale(14), scale(-28)], [scale(19), scale(-40), scale(28), scale(-28)], ], [[scale(-6), scale(28), scale(5), scale(40)]], ], [ [], [[scale(25), scale(-25), scale(37), scale(-14)], [scale(25), scale(-12), scale(37), scale(-1)], [scale(25), scale(1), scale(37), scale(12)], [scale(25), scale(13), scale(37), scale(25)], ], [[scale(-8), scale(27), scale(3), scale(40)]], [[scale(-8), scale(-40), scale(3), scale(-27)]], ], [ [], [], [], [[scale(-4), scale(-40), scale(7), scale(-29)]], ], ] # insert wire locations into map GATE_OFFSET = scale(15) for oT in GATE_MAP: for gT in oT: for m in gT: x = (m[2]+m[0])//2 y = (m[3]+m[1])//2 # snap to edges if x < -HALF_SIZE+GATE_OFFSET: x = m[0] elif x > HALF_SIZE-GATE_OFFSET: x = m[2] if y < -HALF_SIZE+GATE_OFFSET: y = m[1] elif y > HALF_SIZE-GATE_OFFSET: y = m[3] m.append( ( x, y ) ) OBJ_Y_LOC = scale(710) INIT_LOCATIONS = [ [scale(450), OBJ_Y_LOC], [scale(450), OBJ_Y_LOC], [scale(450), OBJ_Y_LOC], [scale(450), OBJ_Y_LOC], [scale(225), OBJ_Y_LOC], [scale(225), OBJ_Y_LOC], [scale(225), OBJ_Y_LOC], [scale(225), OBJ_Y_LOC], [scale(675), OBJ_Y_LOC], [scale(675) ,OBJ_Y_LOC], [scale(675), OBJ_Y_LOC], [scale(675), OBJ_Y_LOC], [scale(450), scale(625)], ] FLOAT1 = [.1, 1] FLOAT = [.01, 2] INTEGER = [1, 0] # s1 s2 s3 s4 s1min s1max s2min s2max s3min s3max s4min s4max [s1step s1digits] [s2step s2digits] [s3step s3digits] TYPES = { 'lfo': [.1, 5, 0, 1, 0, 1, 0, 50, 0, 5, 0, 1, FLOAT, FLOAT, INTEGER], 'rand': [.5, 1.5, 2, 0, 0, 2, 0, 2, 0, 50, 0, 1, FLOAT, FLOAT, FLOAT], 'adsr': [.02, .05, .8, .1, 0, 1, 0, 1, 0, 1, 0, 1, FLOAT, FLOAT, FLOAT], 'trackpadX': [0, 1, 0, 0, -1, 1, 0, 2, 0, 1, 0, 1, FLOAT, FLOAT, INTEGER], 'trackpadY': [0, 1, 0, 0, -1, 1, 0, 2, 0, 1, 0, 1, FLOAT, FLOAT, INTEGER], 'fm': [1, .5, 5, 1, 0, 2, 0, 2, 0, 10, 0, 2, FLOAT, FLOAT, FLOAT], 'buzz': [1, 30, .85, 1, 0, 2, 0, 40, 0, 1, 0, 2, FLOAT, INTEGER, FLOAT], 'vco': [1, 1, .2, 1, 0, 2, 0, 2, 0, .5, 0, 2, FLOAT, INTEGER, FLOAT], 'pluck': [1, 5000, 0, 1, 0, 2, 100, 8000, 0, 8, 0, 2, FLOAT, INTEGER, FLOAT], 'noise': [0, 3000, 4000, 1, 0, 2, 0, 8000, 0, 6000, 0, 2, INTEGER, INTEGER,INTEGER], 'sample': [1, 15, 5000, 1, 0, 2, 0, 99, 100, 8000, 0, 2, FLOAT, INTEGER, INTEGER], 'voice': [1, 3, 5, 1, 0, 2, 0, 8, 1, 10, 0, 2, FLOAT, INTEGER, FLOAT], 'grain': [1, 15, 1, 1, 0, 2, 0, 99, 0, 1, 0, 2, FLOAT, INTEGER, FLOAT], 'addSynth': [1, .005, 5, 1, 0, 2, 0, 20, 0, 9, 0, 2, FLOAT, FLOAT, INTEGER], 'mic': [5, 1, 1, 1, 1, 10, 1, 4, 0, 4, 0, 2, FLOAT, INTEGER, FLOAT], 'wguide': [100, 3000, .8, 1, 0, 200, 100, 5000, 0, 1, 0, 2, FLOAT1, INTEGER, FLOAT], 'distort': [800, .7, .7, 1, 0, 1000, 0, 1, 0, 1, 0, 2, INTEGER, FLOAT, FLOAT], 'filter': [1000, .6, 0, 1, 200, 5000, 0, 1, 0, 2, 0, 2, INTEGER, FLOAT, INTEGER], 'ring': [500, 1, 0, 1, 0, 1000, 0, 1, 0, 5, 0, 2, INTEGER, FLOAT, INTEGER], 'reverb': [1.5, 3000, .5, 1, 0, 4, 100, 7000, 0, 1, 0, 2, FLOAT, INTEGER, FLOAT], 'harmon': [1.25, .04, .5, 1, 0, 2, 0, 1, 0, 1, 0, 2, FLOAT, FLOAT, FLOAT], 'eq4band': [1., 1., 1., 1., 0, 2, 0, 2, 0, 2, 0, 2, FLOAT, FLOAT, FLOAT], 'chorus': [.5, 1., 5., .5, 0, 3, 0, 10, 0, 30, 0, 1, FLOAT, FLOAT, FLOAT]} CONTROL_TYPES = ['lfo', 'rand', 'adsr', 'trackpadX', 'trackpadY'] CONTROL_TYPES_PLUS = [type + '+' for type in CONTROL_TYPES] SOURCE_TYPES = ['fm', 'buzz', 'vco', 'pluck', 'noise', 'sample', 'voice', 'grain', 'addSynth'] if Config.FEATURES_MIC: SOURCE_TYPES += ['mic'] SOURCE_TYPES_PLUS = [type + '+' for type in SOURCE_TYPES] FX_TYPES = ['wguide', 'distort','filter', 'ring', 'reverb', 'harmon', 'eq4band', 'chorus'] FX_TYPES_PLUS = [type + '+' for type in FX_TYPES] OUTPUT_TYPE = ['adsr'] OUTPUT_TYPE_SEL = ['adsrsel'] CHOOSE_TYPE = [CONTROL_TYPES, SOURCE_TYPES, FX_TYPES, OUTPUT_TYPE] CHOOSE_TYPE_PLUS = [CONTROL_TYPES_PLUS, SOURCE_TYPES_PLUS, FX_TYPES_PLUS] # SynthLab Tooltips SOURCE = _('Source') EFFECT = _('Effect') CONTROL = _('Control') SOUNDOUT = _('Sound Output') #Controls # TRANS: http://en.wikipedia.org/wiki/Low_frequency_oscillator LFO = _('LFO') AMP = _('Amplitude') FREQ = _('Frequency') WAVEFORM = _('Waveform') LFO_WAVEFORMS = [_('Sine'), _('Triangle'), _('Bi-Square'), _('Uni-Square'), _('Sawtooth'), _('Sawtooth-down')] OFFSET = _('Offset') LFO_INFO = _('A low frequency oscillation (LFO) is an inaudible, pulsing wave used to change another sound.') LFO_PARA1 = _('The volume of the LFO wave. More volume means more effect.') LFO_PARA2 = _('The speed of the wave.') LFO_PARA3 = _('The type of wave that will be used for the LFO.') LFO_PARA4 = _('The value added to the amplitude of the LFO.') RANDOM = _('Random') MIN = _('Minimum') MAX = _('Maximum') FREQ = FREQ SEED = _('Seed') RANDOM_INFO = _('A sequence of numbers without repetition chosen by the computer.') RANDOM_PARA1 = _('The smallest number allowed') RANDOM_PARA2 = _('The biggest number allowed.') RANDOM_PARA3 = _('The speed of the sequence.') RANDOM_PARA4 = _('The number to initialize the number generator') ADSR = _('Envelope') ATTACK = _('Attack') DECAY = _('Decay') SUSTAIN = _('Sustain') RELEASE = _('Release') ADSR_INFO = _("An ADSR envelope is the shape of the sound's volume over time.") ADSR_PARA1 = _('(A) how quickly the sound reaches full volume.') ADSR_PARA2 = _('(D) how quickly the sound drops after the attack.') ADSR_PARA3= _('(S) the volume of the sound until the note is released.') ADSR_PARA4 = _('(R) how quickly the sound goes away.') TRACKPADX = _('Trackpad X') MIN = MIN MAX = MAX SCALING = _('Scaling') SCALING_TYPES = [_('Linear'), _('Logarithmic')] POLL = _('Poll time') TRACKPADX_INFO = _('The trackpad can be used to modify the sound. This is from left to right.') TRACKPADX_PARA1 = _('The minimum value the trackpad will send.') TRACKPADX_PARA2 = _('The maximum value the trackpad will send.') TRACKPADX_PARA3 = _('The shape of the value repartition. In a straight line (linear) or a curved line (logarithmic).') TRACKPADX_PARA4= _('The time interval between each event coming from the trackpad.') TRACKPADY = _('Trackpad Y') MIN = MIN MAX = MAX SCALING = SCALING SCALING_TYPES = SCALING_TYPES POLL = POLL TRACKPADY_INFO = _('The trackpad can be used to modify the sound. This is from top to bottom.') TRACKPADY_PARA1 = _('The minimum value the trackpad will send.') TRACKPADY_PARA2 = _('The maximum value the trackpad will send.') TRACKPADY_PARA3 = _('The shape of the value repartition. In a straight line (linear) or a curved line (logarithmic).') TRACKPADY_PARA4 = _('The time interval between each event coming from the trackpad.') #Source # TRANS: http://en.wikipedia.org/wiki/Frequency_modulation_synthesis FM = _('FM') # TRANS: http://en.wikipedia.org/wiki/Carrier_wave CAR = _('Carrier Frequency') MOD = _('Modulator Frequency') INDEX = _('Index') GAIN = _('Gain') FM_INFO = _('Frequency modulation synthesis (FM) creates an electronic sound by combining the frequency of two waves (the carrier and the modulator).') FM_PARA1 = _('The main wave frequency.') FM_PARA2 = _('The frequency of the wave that will modify the Carrier wave.') FM_PARA3 = _('The variation in frequency of the Carrier wave.') FM_PARA4 = _('The volume of the sound.') BUZZ = _('Buzz') FREQ = FREQ NHARM = _('Number of harmonics') FSLOPE = _('Filter Slope') GAIN = GAIN BUZZ_INFO = _('A buzz is a very bright sound with many harmonics.') BUZZ_PARA1 = _('The pitch of the buzz sound.') BUZZ_PARA2 = _('The harmonic thickness of the sound.') BUZZ_PARA3 = _('The brightness of the sound.') BUZZ_PARA4 = _('The volume of the sound.') # TRANS: http://en.wikipedia.org/wiki/Voltage-controlled_oscillator VCO = _('VCO') FREQ = FREQ WAVEFORM = WAVEFORM VCO_WAVEFORMS = [_('Sawtooth'), _('Square'), _('Triangle')] FSLOPE = FSLOPE GAIN = GAIN VCO_INFO = _('A voltage-controlled oscillator (VCO) creates an electronic sound by combining the shape of two waves.') VCO_PARA1 = _('The wave that will be modified by the VCO.') VCO_PARA2 = _("The shape of the VCO's wave.") VCO_PARA3 = _('The brightness of the sound.') VCO_PARA4 = _('The volume of the sound.') PLUCK = _('Pluck') FREQ = FREQ LFILTER = Tooltips.PROP['filterTypeLowButton'] # TRANS: http://en.wikipedia.org/wiki/Vibrato VIBRATO = _('Vibrato') GAIN = GAIN PLUCK_INFO = _('An electronic string instrument (like a guitar).') PLUCK_PARA1 = _('The pitch of the instrument.') PLUCK_PARA2 = _('The brightness of the sound.') PLUCK_PARA3 = _('The speed of the wave.') PLUCK_PARA4 = _('The volume of the sound.') NOISE = _('Noise') NOISETYPE = _('Type') NOISE_TYPES = [_('White'), _('Pink'), _('Gauss')] FREQ = FREQ BANDWITH = _('Bandwidth') GAIN = GAIN NOISE_INFO = _('Noise is a sound with energy on every frequency.') NOISE_PARA1 = _('The shape of noise to be used (white = bright, pink = dark, gauss = colored).') NOISE_PARA2 = _('The brightness of the sound.') NOISE_PARA3 = _('The thickness of the sound.') NOISE_PARA4 = _('The volume of the sound.') SAMPLE = _('Sound Sample') FREQ = FREQ SAMPLEN = _('Sample Number') SAMPLE_NAMES = _('Sample name') LFILTER = LFILTER GAIN = GAIN SAMPLE_INFO = _("A sample is a real sound that has been recorded and that can be played back.") SAMPLE_PARA1 = _('The pitch of the sample.') SAMPLE_PARA2 = _('The sample to be used.') SAMPLE_PARA3 = _('The brightness of the sound.') SAMPLE_PARA4 = _('The volume of the sound.') VOICE = _('Voice') FREQ = FREQ VOWEL = _('Vowel') VOWEL_TYPES = ['i', 'e', 'ee', 'a', 'u', 'o1', 'o2', 'oa', 'oe'] VIBRATO = VIBRATO GAIN = GAIN VOICE_INFO = _('An electronic voice.') VOICE_PARA1 = _('The pitch of the sound.') VOICE_PARA2 = _('The shape of the sound based on vowels.') VOICE_PARA3 = _('The speed of the wave.') VOICE_PARA4 = _('The volume of the sound.') GRAIN = _('Grain') FREQ = FREQ SAMPLEN = SAMPLEN INDEX = _('Index') GAIN = GAIN GRAIN_INFO = _('The grain effect splits the sound in tiny bits which can be rearranged differently.') GRAIN_PARA1 = _('The pitch of grains.') GRAIN_PARA2 = _('The sample to be used.') GRAIN_PARA3 = _('The variation in pitch of grains.') GRAIN_PARA4 = _('The volume of the sound.') ADDSYNTH = _('Additive Synthesis') FREQ = FREQ SPREAD = _('Spread') WAVE = _('Waveform') GAIN = GAIN ADDSYNTH_INFO = _('Additive synthesis creates musical timbre by combining different waves.') ADDSYNTH_PARA1 = _('The pitch of the sound.') ADDSYNTH_PARA2 = _('The separation between the different waves.') ADDSYNTH_PARA3 = _('The shape of the wave.') ADDSYNTH_PARA4 = _('The volume of the sound.') MIC = _('Microphone input') DURATION = _('Length of the memory') BIN = _('memory number') SPEED = _('Playback speed') GAIN = GAIN MIC_INFO = _('Microphone input is record into a buffer for playback (right-click on the object to record sound)') MIC_PARA1 = _('Length of the memory') MIC_PARA2 = _('This parameter can not be modified') MIC_PARA3 = _('Speed playback changes duration and pitch of the sound') MIC_PARA4 = _('The volume of the sound.') #Effects DELAY = _('Delay') FREQ = FREQ LFILTER = LFILTER FEEDBACK = _('Feedback') GAIN = GAIN DELAY_INFO = _('Delay is an audio effect that repeats the sound over and over.') DELAY_PARA1 = _('The speed of the delay.') DELAY_PARA2 = _('The brightness of the sound.') DELAY_PARA3 = _('The time it takes for the sound to go away.') DELAY_PARA4 = _('The volume of the sound.') DIST = _('Distortion') FREQ = FREQ RESON = _('Resonance') DISTL = _('Distortion Level') GAIN = GAIN DIST_INFO = _("Distortion is the deformation of a wave which creates harsh sounds.") DIST_PARA1 = _('The pitch of the distorted sound.') DIST_PARA2 = _('The amount of vibration the instrument has against itself.') DIST_PARA3 = _('The volume of the distorted sound.') DIST_PARA4 = _('The volume of the sound.') FILTER = _('Filter') FREQ = FREQ FSLOPE = FSLOPE FTYPE = _('Type') FILTER_TYPES = [_('Lowpass'), _('Highpass'), _('Bandpass')] GAIN = GAIN FILTER_INFO = _('An audio filter is designed to brighten, darken or color a sound.') FILTER_PARA1 = _('The point in the sound to be filtered.') FILTER_PARA2 = _('The size of the region affected by the filter.') FILTER_PARA3 = _('The type of filter used: lowpass = dark, highpass = bright, bandpass = colored.') FILTER_PARA4 = _('The volume of the sound.') # TRANS: http://en.wikipedia.org/wiki/Ring_modulation RINGMOD = _('Ring Modulator') FREQ = FREQ MIX = _('Mix') WAVEFORM = WAVEFORM LFO_WAVEFORMS = LFO_WAVEFORMS GAIN = GAIN RINGMOD_INFO = _('Ring modulation is an audio effect that creates metallic sounds.') RINGMOD_PARA1 = _('The pitch of the ring modulator.') RINGMOD_PARA2 = _('The volume of the modulated sound.') RINGMOD_PARA3 = _('The shape of the wave used for modulation.') RINGMOD_PARA4 = _('The volume of the sound.') REVERB = _('Reverb') REVERBD = _('Length') REVERBF = Tooltips.PROP['filterTypeLowButton'] REVERBL = _('Reverb Level') GAIN = GAIN REVERB_INFO = _('Reverberation is the length a sound stays in a room.') REVERB_PARA1 = _('The size of the room.') REVERB_PARA2 = _('The brightness of the reverberated sound.') REVERB_PARA3 = _('The amount of reverb to be applied.') REVERB_PARA4 = _('The volume of the sound.') HARMON = _('Harmonizer') FREQ = FREQ DRYDELAY = _('Dry delay') MIX = MIX GAIN = GAIN HARMON_INFO = _('A harmonizer doubles the sound musically.') HARMON_PARA1 = _('The pitch of the doubled sound.') HARMON_PARA2 = _('The start time of the original sound.') HARMON_PARA3 = _('The balance between the original and the doubled sound.') HARMON_PARA4 = _('The volume of the sound.') EQ4BAND = _('Equalizer 4 bands') FREQ1 = _('Band one gain') FREQ2 = _('Band two gain') FREQ3 = _('Band three gain') FREQ4 = _('Band four gain') EQ4BAND_INFO = _('A 4 band equalizer chooses slices (bands) in the sound and makes them louder or softer.') EQ4BAND_PARA1 = _('The volume of band 1 (low).') EQ4BAND_PARA2 = _('The volume of band 2 (mid-low).') EQ4BAND_PARA3 = _('The volume of band 3 (mid-high).') EQ4BAND_PARA4 = _('The volume of band 4 (high).') # TRANS: http://en.wikipedia.org/wiki/Chorus_effect CHORUS = _('Chorus') LFODEPTH = _('LFO Depth') LFOFREQ = _('LFO Frequency') DELAY = _('Delay') FEEDBACK = FEEDBACK CHORUS_INFO = _('The chorus effect plays copies of the same sound with a slight variation.') CHORUS_PARA1 = _('The volume of the LFO signal.') CHORUS_PARA2 = _('The pitch of the LFO signal.') CHORUS_PARA3 = _('The amount of delay between the two signals.') CHORUS_PARA4 = _('The volume of the sound.') SYNTHTYPES = [[LFO, RANDOM, ADSR, TRACKPADX, TRACKPADY], [FM, BUZZ, VCO, PLUCK, NOISE, SAMPLE, VOICE, GRAIN, ADDSYNTH, MIC], [DELAY, DIST, FILTER, RINGMOD, REVERB, HARMON, EQ4BAND, CHORUS], [ADSR]] SYNTHPARA = { 'lfo': [AMP, FREQ, WAVEFORM, OFFSET, LFO_INFO, LFO_PARA1, LFO_PARA2, LFO_PARA3, LFO_PARA4], 'rand': [MIN, MAX, FREQ, SEED, RANDOM_INFO, RANDOM_PARA1, RANDOM_PARA2, RANDOM_PARA3, RANDOM_PARA4], 'adsr': [ATTACK, DECAY, SUSTAIN, RELEASE, ADSR_INFO, ADSR_PARA1, ADSR_PARA2, ADSR_PARA3, ADSR_PARA4], 'trackpadX': [MIN, MAX, SCALING, POLL, TRACKPADX_INFO, TRACKPADX_PARA1, TRACKPADX_PARA2, TRACKPADX_PARA3, TRACKPADX_PARA4], 'trackpadY': [MIN, MAX, SCALING, POLL, TRACKPADY_INFO, TRACKPADY_PARA1, TRACKPADY_PARA2, TRACKPADY_PARA3, TRACKPADY_PARA4], 'fm': [CAR, MOD, INDEX, GAIN, FM_INFO, FM_PARA1, FM_PARA2, FM_PARA3, FM_PARA4], 'buzz': [FREQ, NHARM, FSLOPE, GAIN, BUZZ_INFO, BUZZ_PARA1, BUZZ_PARA2, BUZZ_PARA3, BUZZ_PARA4], 'vco': [FREQ, WAVEFORM, FSLOPE, GAIN, VCO_INFO, VCO_PARA1, VCO_PARA2, VCO_PARA3, VCO_PARA4], 'pluck': [FREQ, LFILTER, VIBRATO, GAIN, PLUCK_INFO, PLUCK_PARA1, PLUCK_PARA2, PLUCK_PARA3, PLUCK_PARA4], 'noise': [NOISETYPE, FREQ, BANDWITH, GAIN, NOISE_INFO, NOISE_PARA1, NOISE_PARA2, NOISE_PARA3, NOISE_PARA4], 'sample': [FREQ, SAMPLEN, LFILTER, GAIN, SAMPLE_INFO, SAMPLE_PARA1, SAMPLE_PARA2, SAMPLE_PARA3, SAMPLE_PARA4], 'voice': [FREQ, VOWEL, VIBRATO, GAIN, VOICE_INFO, VOICE_PARA1, VOICE_PARA2, VOICE_PARA3, VOICE_PARA4], 'grain': [FREQ, SAMPLEN, INDEX, GAIN, GRAIN_INFO, GRAIN_PARA1, GRAIN_PARA2, GRAIN_PARA3, GRAIN_PARA4], 'addSynth': [FREQ, SPREAD, WAVE, GAIN, ADDSYNTH_INFO, ADDSYNTH_PARA1, ADDSYNTH_PARA2, ADDSYNTH_PARA3, ADDSYNTH_PARA4], 'mic': [DURATION, BIN, SPEED, GAIN, MIC_INFO, MIC_PARA1, MIC_PARA2, MIC_PARA3, MIC_PARA4], 'wguide': [FREQ, LFILTER, FEEDBACK, GAIN, DELAY_INFO, DELAY_PARA1, DELAY_PARA2, DELAY_PARA3, DELAY_PARA4], 'distort': [FREQ, RESON, DISTL, GAIN, DIST_INFO, DIST_PARA1, DIST_PARA2, DIST_PARA3, DIST_PARA4], 'filter': [FREQ, FSLOPE, FTYPE, GAIN, FILTER_INFO, FILTER_PARA1, FILTER_PARA2, FILTER_PARA3, FILTER_PARA4], 'ring': [FREQ, MIX, WAVEFORM, GAIN, RINGMOD_INFO, RINGMOD_PARA1, RINGMOD_PARA2, RINGMOD_PARA3, RINGMOD_PARA4], 'reverb': [REVERBD, REVERBF, REVERBL, GAIN, REVERB_INFO, REVERB_PARA1, REVERB_PARA2, REVERB_PARA3, REVERB_PARA4], 'harmon': [FREQ, DRYDELAY, MIX, GAIN, HARMON_INFO, HARMON_PARA1, HARMON_PARA2, HARMON_PARA3, HARMON_PARA4], 'eq4band': [FREQ1, FREQ2, FREQ3, FREQ4, EQ4BAND_INFO, EQ4BAND_PARA1, EQ4BAND_PARA2, EQ4BAND_PARA3, EQ4BAND_PARA4], 'chorus': [LFODEPTH, LFOFREQ, DELAY, FEEDBACK, CHORUS_INFO, CHORUS_PARA1, CHORUS_PARA2, CHORUS_PARA3, CHORUS_PARA4]}
def __init__(self, activity): GObject.GObject.__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) # csnd expects a range 0-100 for now self.csnd.setMasterVolume(self.volume * 100) self.csnd.setTempo(self.tempo) self.muted = False #-- Drawing ------------------------------------------- def darken(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 r * 256, g * 256, b * 256 def lighten(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 r * 256, g * 256, b * 256 xoColor = profile.get_color() if not xoColor: xoColorKey = ("#8D8D8D,#FFDDEA") xoColor = XoColor(xoColorKey) # colors in Config and in XoColor are strings, # the colors in style are style.Color, transform all to Gdk.Color self.colors = {"bg": CairoUtil.get_gdk_color(Config.PANEL_BCK_COLOR), "black": style.COLOR_BLACK.get_gdk_color(), #"Picker_Bg": colormap.alloc_color("#404040"), #"Picker_Bg_Inactive": colormap.alloc_color("#808080"), "Picker_Bg": style.COLOR_TOOLBAR_GREY.get_gdk_color(), "Picker_Bg_Inactive": style.COLOR_BUTTON_GREY.get_gdk_color(), "Picker_Fg": style.COLOR_WHITE.get_gdk_color(), "Border_Active": \ CairoUtil.get_gdk_color(xoColor.get_stroke_color()), "Border_Inactive": CairoUtil.get_gdk_color("#8D8D8D"), "Border_Highlight": CairoUtil.get_gdk_color("#FFFFFF"), "Bg_Active": CairoUtil.get_gdk_color(xoColor.get_fill_color()), "Bg_Inactive": CairoUtil.get_gdk_color("#DBDBDB"), "Preview_Note_Fill": CairoUtil.get_gdk_color(Config.BG_COLOR), "Preview_Note_Border": CairoUtil.get_gdk_color(Config.FG_COLOR), "Preview_Note_Selected": style.COLOR_WHITE.get_gdk_color(), # TODO: lighten here can be removed, check if is used in other # places "Note_Fill_Active": Gdk.Color(*lighten("#590000")), # base "Border_Active" "Note_Fill_Inactive": Gdk.Color(*lighten("#8D8D8D")), # base "Border_Inactive" "Beat_Line": CairoUtil.get_gdk_color("#959595")} self.colors["Note_Border_Active"] = self.colors["Border_Active"] self.colors["Note_Border_Inactive"] = self.colors["Border_Inactive"] self.sampleNoteHeight = 7 self.sampleBg = cairo.ImageSurface.create_from_png( imagefile('sampleBG.png')) 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) jam_toolbar_button = ToolbarButton(label=_('Jam'), page=self.jamToolbar, icon_name='voltemp') self.jamToolbar.show() jam_toolbar_button.show() self.activity.toolbar_box.toolbar.insert(jam_toolbar_button, -1) self.beatToolbar = BeatToolbar(self) beat_toolbar_button = ToolbarButton(label=_('Beat'), page=self.beatToolbar, icon_name='heart') self.beatToolbar.show() beat_toolbar_button.show() self.activity.toolbar_box.toolbar.insert(beat_toolbar_button, -1) self.desktopToolbar = DesktopToolbar(self) desktop_toolbar_button = ToolbarButton(label=_('Desktop'), page=self.desktopToolbar, icon_name='jam-presets-list') self.desktopToolbar.show() desktop_toolbar_button.show() self.activity.toolbar_box.toolbar.insert(desktop_toolbar_button, -1) if Config.FEATURES_MIC or Config.FEATURES_NEWSOUNDS: self.recordToolbar = RecordToolbar(self) record_toolbar_button = ToolbarButton(label=_('Record'), page=self.recordToolbar, icon_name='microphone') self.recordToolbar.show() record_toolbar_button.show() self.activity.toolbar_box.toolbar.insert(record_toolbar_button, -1) separator = Gtk.SeparatorToolItem() separator.props.draw = True separator.set_expand(False) self.activity.toolbar_box.toolbar.insert(separator, -1) separator.show() common_playback_buttons(self.activity.toolbar_box.toolbar, self) #-- GUI ----------------------------------------------- if True: # GUI self.modify_bg(Gtk.StateType.NORMAL, self.colors["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"], True, True, 0) #-- Bank ---------------------------------------------- separator = Gtk.Label(label=" ") separator.set_size_request(-1, style.TOOLBOX_SEPARATOR_HEIGHT) self.GUI["mainVBox"].pack_start(separator, False, True, 0) self.GUI["notebook"] = Gtk.Notebook() self.GUI["notebook"].set_scrollable(True) self.GUI["notebook"].modify_bg(Gtk.StateType.NORMAL, self.colors["Picker_Bg"]) self.GUI["notebook"].modify_bg(Gtk.StateType.ACTIVE, self.colors["Picker_Bg_Inactive"]) # TODO gtk3 no available anymore? #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, scale(160)) self.GUI["notebook"].connect("switch-page", self.setPicker) self.GUI["mainVBox"].pack_start(self.GUI["notebook"], False, False, 0) self.pickers = {} self.pickerScroll = {} for type in [Picker.Instrument, Picker.Drum, Picker.Loop]: self.pickers[type] = type(self) def prepareLabel(name): label = Gtk.Label(label=Tooltips.categories.get(name) or name) label.set_alignment(0.0, 0.5) label.modify_fg(Gtk.StateType.NORMAL, self.colors["Picker_Fg"]) label.modify_fg(Gtk.StateType.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.toolbar_box.set_current_toolbar(1) # JamToolbar self.setDesktop(0, True)