import Config class PARAMETER: ONSET = 0 PITCH = 1 AMPLITUDE = 2 DURATION = 3 INSTRUMENT = 4 class Note: def __init__( self, page, track, id, cs ): self.page = page self.track = track self.id = id self.cs = cs class Page: def __init__( self, beats ): self.beats = beats self.ticks = beats*Config.TICKS_PER_BEAT self.nextNoteId = 0 # first note will be 1 def genId( self ): self.nextNoteId += 1 if self.nextNoteId == 65536: # short print "note id overflow!" # TODO think of how to handle this!? return self.nextNoteId class PageListener: def notifyPageAdd( self, id, at ): pass def notifyPageDelete( self, which, safe ): pass def notifyPageDuplicate( self, new, at ): pass def notifyPageMove( self, which, low, high ): pass class NoteListener: def notifyNoteAdd( self, page, track, id ): pass def notifyNoteDelete( self, page, track, id ): pass def notifyNoteUpdate( self, page, track, id, parameter, value ): pass class NoteDB: def __init__( self ): self.noteD = {} # bins containing all the notes by page, track, and id # structure self.noteD[pageId][trackIndex][noteId] self.noteS = {} # bins containing all the notes by page and track # first sorted by onset then by pitch (for drum hits) # structure self.noteS[pageId][trackIndex][noteIndex] self.pages = {} # dict of Pages indexed by pageId self.tune = [] # list of pageIds ordered by tune self.listeners = [] # complete list of listeners self.pageListeners = [] # list of listeners who want page notifications self.noteListeners = [] # list of listeners who want note notifications self.parasiteList = {} # dict of parasites indexed by listener self.parasiteD = {} # bin of parasites indexed by listener self.parasiteS = {} # parasites sorted as in self.noteS, self.nextId = 0 # base id, first page will be 1 self.clipboard = [] # stores copied cs notes self.clipboardArea = [] # stores the limits and tracks for each page in the clipboard #-- private -------------------------------------------- def _genId( self ): self.nextId += 1 if self.nextId == 65536: # short print "page id overflow!" # TODO think of how to handle this!? return self.nextId #======================================================= # Page Functions def addPage( self, beats, after = False ): id = self._newPage( beats ) at = self._insertPage( id, after ) for l in self.pageListeners: l.notifyPageAdd( id, at ) return id def deletePages( self, which ): beats = self.pages[self.tune[0]].beats low = 999999 ind = -1 for id in which: ind = self.tune.index(id) if ind < low: low = ind for t in range(Config.NUMBER_OF_TRACKS): for n in self.noteD[id][t].keys(): self.deleteNote( id, t, n ) del self.noteD[id] del self.noteS[id] del self.parasiteD[id] del self.parasiteS[id] del self.pages[id] at = self.tune.index( id ) self.tune.pop(at) if not len(self.tune): self.addPage( beats ) # always have at least one page safe = self.tune[0] else: safe = self.tune[max(ind-1,0)] for l in self.pageListeners: l.notifyPageDelete( which, safe ) def duplicatePages( self, which, after = False ): sorted = [] if after: first = self.tune.index(after)+1 else: first = 0 i = j = 0 while i < len(self.tune) and j < len(which): if self.tune[i] in which: sorted.append(self.tune[i]) j += 1 i += 1 new = {} for cp in sorted: id = self._newPage( self.pages[cp].beats ) self._insertPage( id, after ) after = id new[cp] = id for l in self.pageListeners: l.notifyPageDuplicate( new, first ) for cp in sorted: for t in range(Config.NUMBER_OF_TRACKS): for n in self.noteD[cp][t].keys(): self.duplicateNote( cp, t, n, new[cp], t, 0 ) return new def movePages( self, which, after = False ): sorted = [] if after: at = self.tune.index(after)+1 else: at = 0 low = high = at i = j = 0 while i < len(self.tune): if self.tune[i] in which: sorted.append(self.tune[i]) self.tune.pop(i) if i < low: low = i if i > high: high = i if i < at: at -= 1 j += 1 else: i += 1 self.tune = self.tune[:at] + sorted + self.tune[at:] for l in self.pageListeners: l.notifyPageMove( sorted, low, high ) #-- private -------------------------------------------- def _newPage( self, beats ): id = self._genId() self.pages[id] = Page( beats ) self.noteD[id] = [ {} for i in range(Config.NUMBER_OF_TRACKS) ] self.noteS[id] = [ [] for i in range(Config.NUMBER_OF_TRACKS) ] self.parasiteD[id] = [ {} for i in range(Config.NUMBER_OF_TRACKS) ] self.parasiteS[id] = [ {} for i in range(Config.NUMBER_OF_TRACKS) ] for i in range(Config.NUMBER_OF_TRACKS): for par in self.parasiteList.keys(): self.parasiteD[id][i][par] = {} self.parasiteS[id][i][par] = [] return id def _insertPage( self, id, after ): if not after: at = 0 else: at = self.tune.index(after)+1 self.tune.insert( at, id ) return at #======================================================= # Track Functions #def deleteTracks( self, pages, which ): #def duplicateTracks( self, pages, which, insert ): #======================================================= # Note Functions def addNote( self, page, track, cs, hint = False ): id = self.pages[page].genId() n = self.noteD[page][track][id] = Note( page, track, id, cs ) if not hint: at = 0 else: at = hint[0] while at > 0: onset = self.noteS[page][track][at-1].cs.onset if onset <= cs.onset: if onset <= cs.onset: break elif self.noteS[page][track][at-1].cs.pitch <= cs.pitch: break at -= 1 last = len(self.noteS[page][track]) while at < last: onset = self.noteS[page][track][at].cs.onset if onset >= cs.onset: if onset > cs.onset: break elif self.noteS[page][track][at].cs.pitch > cs.pitch: break at += 1 self.noteS[page][track].insert( at, n ) for par in self.parasiteList.keys(): parasite = self.parasiteList[par]( self, par, n ) self.parasiteD[page][track][par][id] = parasite.attach() # give parasites the option of return something other than themselves self.parasiteS[page][track][par].insert( at, parasite.attach() ) if hint: hint[0] = at + 1 # assume the next note will fall after this one for l in self.noteListeners: l.notifyNoteAdd( page, track, id ) return id # stream format: # page id # track index # number of following notes (N) # cs pointer # ... up to N # page id or -1 to exit def addNotes( self, stream ): new = {} i = [-1] p = self._readstream(stream,i) while p != -1: if p not in new: new[p] = [ [] for x in range(Config.NUMBER_OF_TRACKS) ] t = self._readstream(stream,i) N = self._readstream(stream,i) hint = [0] for j in range(N): new[p][t].append( self.addNote( p, t, self._readstream(stream,i), hint ) ) p = self._readstream(stream,i) return new def deleteNote( self, page, track, id ): ind = self.noteS[page][track].index( self.noteD[page][track][id] ) for par in self.parasiteList.keys(): self.parasiteD[page][track][par][id].destroy() self.parasiteS[page][track][par].pop(ind) del self.parasiteD[page][track][par][id] self.noteS[page][track].pop(ind) del self.noteD[page][track][id] for l in self.noteListeners: l.notifyNoteDelete( page, track, id ) # stream format: # page id # track index # number of following notes (N) # note id # ... up to N # page id or -1 to exit def deleteNotes( self, stream ): i = [-1] p = self._readstream(stream,i) while p != -1: t = self._readstream(stream,i) N = self._readstream(stream,i) for j in range(N): self.deleteNote( p, t, self._readstream(stream,i) ) p = self._readstream(stream,i) def deleteNotesByTrack( self, pages, tracks ): for p in pages: for t in tracks: notes = self.noteS[p][t][:] for n in notes: self.deleteNote( p, t, n.id ) def duplicateNote( self, page, track, id, toPage, toTrack, offset ): cs = self.noteD[page][track][id].cs.clone() cs.trackId = toTrack cs.pageId = toPage cs.onset += offset ticks = self.pages[toPage].ticks if cs.onset >= ticks: return False # off the end of the page if cs.onset + cs.duration > ticks: cs.duration = ticks - cs.onset return self.addNote( toPage, toTrack, cs ) # stream format: # page id # track index # toPage id # toTrack index # offset # number of following notes (N) # note id # ... up to N # page id or -1 to exit def duplicateNotes( self, stream ): i = [-1] p = self._readstream(stream,i) while p != -1: t = self._readstream(stream,i) toP = self._readstream(stream,i) toT = self._readstream(stream,i) offset = self._readstream(stream,i) N = self._readstream(stream,i) for j in range(N): self.duplicateNote( p, t, self._readstream(stream,i), toP, toT, offset ) p = self._readstream(stream,i) def updateNote( self, page, track, id, parameter, value ): if parameter == PARAMETER.ONSET: self.noteD[page][track][id].cs.onset = value self._resortNote( page, track, id ) elif parameter == PARAMETER.PITCH: self.noteD[page][track][id].cs.pitch= value self._resortNote( page, track, id ) elif parameter == PARAMETER.AMPLITUDE: self.noteD[page][track][id].cs.amplitude = value elif parameter == PARAMETER.DURATION: self.noteD[page][track][id].cs.duration = value elif parameter == PARAMETER.INSTRUMENT: self.noteD[page][track][id].cs.instrumentId = value for par in self.parasiteList.keys(): self.parasiteD[page][track][par][id].updateParameter( parameter, value ) for l in self.noteListeners: l.notifyNoteUpdate( page, track, id, parameter, value ) # stream format: # page id # track index # parameter id # number of following notes (N) # note id # value # ... up to N # page id or -1 to exit def updateNotes( self, stream ): i = [-1] p = self._readstream(stream,i) while p != -1: t = self._readstream(stream,i) param = self._readstream(stream,i) N = self._readstream(stream,i) for j in range(N): self.updateNote( p, t, self._readstream(stream,i), param, self._readstream(stream,i) ) p = self._readstream(stream,i) #-- private -------------------------------------------- def _readstream( self, stream, i ): i[0] += 1 return stream[i[0]] def _resortNote( self, page, track, id ): ins = out = self.noteS[page][track].index(self.noteD[page][track][id]) cs = self.noteD[page][track][id].cs while ins > 0: # check backward onset = self.noteS[page][track][ins-1].cs.onset if onset <= cs.onset: if onset <= cs.onset: break elif self.noteS[page][track][ins-1].cs.pitch <= cs.pitch: break ins -= 1 if ins == out: # check forward ins += 1 last = len(self.noteS[page][track]) while ins < last: onset = self.noteS[page][track][ins].cs.onset if onset >= cs.onset: if onset > cs.onset: break elif self.noteS[page][track][ins].cs.pitch > cs.pitch: break ins += 1 if ins != out: # resort if ins > out: ins -= 1 n = self.noteS[page][track].pop( out ) self.noteS[page][track].insert( ins, n ) for par in self.parasiteList.keys(): p = self.parasiteS[page][track][par].pop( out ) self.parasiteS[page][track][par].insert( ins, p ) #======================================================= # Clipboard Functions # stream format: # page id # track index # number of following notes (N) # note id # ... up to N # page id or -1 to exit def notesToClipboard( self, stream ): self.clipboard = [] self.clipboardArea = [] i = [-1] pages = [] p = self._readstream(stream,i) while p != -1: if p not in pages: page = [ [] for x in range(Config.NUMBER_OF_TRACKS) ] pageArea = { "limit": [ 99999, 0 ], "tracks": [ 0 for x in range(Config.NUMBER_OF_TRACKS) ] } pages.append(p) self.clipboard.append(page) self.clipboardArea.append(pageArea) else: ind = pages.index(p) page = self.clipboard[ind] pageArea = self.clipboardArea[ind] t = self._readstream(stream,i) pageArea["tracks"][t] = 1 N = self._readstream(stream,i) for j in range(N): cs = self.noteD[p][t][self._readstream(stream,i)].cs.clone() if cs.onset < pageArea["limit"][0]: pageArea["limit"][0] = cs.onset if cs.onset + cs.duration > pageArea["limit"][1]: pageArea["limit"][1] = cs.onset + cs.duration page[t].append( cs ) p = self._readstream(stream,i) return self.clipboardArea def tracksToClipboard( self, pages, tracks ): self.clipboard = [] self.clipboardOrigin = [ 0, 0 ] self.clipboardArea = [] for p in pages: page = [ [] for x in range(Config.NUMBER_OF_TRACKS) ] pageArea = { "limit": [ 0, 99999 ], "tracks": [ 0 for x in range(Config.NUMBER_OF_TRACKS) ] } self.clipboard.append(page) self.clipboardArea.append(pageArea) for t in tracks: pageArea["tracks"][t] = 1 for id in self.noteD[p][t]: cs = self.noteD[p][t][id].cs.clone() page[t].append( cs ) return self.clipboardArea # trackMap = { X: Y, W: Z, ... }; X,W are track indices, Y,Z are clipboard indices # instrumentMap = { X: Y, W: Z, ... }; X,W are track indices, Y,Z are instrument ids def pasteClipboard( self, pages, offset, trackMap, instrumentMap = {} ): if not len(self.clipboard): return deleteStream = [] updateStream = [] addStream = [] pp = 0 ppMax = len(self.clipboard) for p in pages: ticks = self.pages[p].ticks area = self.clipboardArea[pp] area["limit"][0] += offset area["limit"][1] += offset for t in trackMap.keys(): if not area["tracks"][trackMap[t]]: continue if instrumentMap.has_key(t): updateInstrument = True instrumentId = instrumentMap[t] else: updateInstrument = False tdeleteStream = [] tupdateOStream = [] tupdateDStream = [] taddStream = [] # clear area for n in self.noteS[p][t]: start = n.cs.onset end = start + n.cs.duration if area["limit"][0] <= start < area["limit"][1]: start = area["limit"][1] if area["limit"][0] < end <= area["limit"][1]: end = area["limit"][0] if start < area["limit"][0] and end > area["limit"][1]: end = area["limit"][0] if end <= start: tdeleteStream.append( n.id ) elif start != n.cs.onset: tupdateDStream += [ n.id, end - start ] tupdateOStream += [ n.id, start ] elif end != start + n.cs.duration: tupdateDStream += [ n.id, end - start ] if len(tdeleteStream): deleteStream += [ p, t, len(tdeleteStream) ] + tdeleteStream if len(tupdateOStream): updateStream += [ p, t, PARAMETER.ONSET, len(tupdateOStream)//2 ] + tupdateOStream if len(tupdateDStream): updateStream += [ p, t, PARAMETER.DURATION, len(tupdateDStream)//2 ] + tupdateDStream # paste notes for cs in self.clipboard[pp][trackMap[t]]: newcs = cs.clone() newcs.onset += offset if newcs.onset >= ticks: continue if newcs.onset + newcs.duration > ticks: newcs.duration = ticks - newcs.onset newcs.pageId = p newcs.trackId = t if updateInstrument: newcs.instrumentId = instrumentId # TODO update any other parameters? taddStream.append( newcs ) if len(taddStream): addStream += [ p, t, len(taddStream) ] + taddStream pp += 1 if pp == ppMax: pp -= ppMax if len(deleteStream): self.deleteNotes( deleteStream + [-1] ) if len(updateStream): self.updateNotes( updateStream + [-1] ) if len(addStream): return self.addNotes( addStream + [-1] ) return None def getClipboardArea( self, ind ): N = len(self.clipboardArea) while ind >= N: ind -= N return self.clipboardArea[ind] #======================================================= # Listener Functions def addListener( self, listener, parasite = None, page = False, note = False ): if listener in self.listeners: return # only one listener per object if parasite: self.parasiteList[listener] = parasite self._addParasite( listener, parasite ) if page: self.pageListeners.append( listener ) if note: self.noteListeners.append( listener ) self.listeners.append( listener ) def deleteListener( self, listener ): self.listeners.remove( listener ) if listener in self.pageListeners: self.pageListeners.remove( listener ) if listener in self.noteListeners: self.noteListeners.remove( listener ) if self.parasites.has_key( listener ): self._deleteParasite( listener ) del self.parasiteList[listener] #-- private -------------------------------------------- def _addParasite( self, listener, parasite ): for p in self.tune: for t in range(Config.NUMBER_OF_TRACKS): self.parasiteD[p][t][listener] = {} self.parasiteS[p][t][listener] = [] for n in self.noteD[p][t].keys(): parasite( self, listener, self.noteD[p][t][n] ) self.parasiteD[p][t][listener][n] = parasite.attach() # give parasites the option of returning something other than themselves self.parasiteS[p][t][listener].insert( self.noteS[p][t].index( self.noteD[p][t][n]), parasite.attach() ) def _deleteParasite( self, listener ): for p in self.tune: for t in range(Config.NUMBER_OF_TRACKS): for n in self.notes[p][t].keys(): self.parasiteD[p][t][listener][n].destroy() del self.parasiteD[p][t][listener] del self.parasiteS[p][t][listener] #======================================================= # Get Functions def getPageCount( self ): return len(self.pages) def getTune( self ): return self.tune def getPage( self, page ): return self.pages[page] def getPageByIndex( self, ind ): return self.tune[ind] def getPageIndex( self, page ): return self.tune.index(page) def getNote( self, page, track, id, listener = None ): if listener: return self.parasiteD[page][track][listener][id] return self.noteD[page][track][id] def getNotesByPage( self, page, listener = None ): notes = [] if listener: for i in range(Config.NUMBER_OF_TRACKS): notes.extend( self.parasiteS[page][i][listener] ) else: for i in range(Config.NUMBER_OF_TRACKS): notes.extend( self.noteS[page][i] ) return notes def getNotesByTrack( self, page, track, listener = None ): if listener: return self.parasiteS[page][track][listener] else: return self.noteS[page][track] def getNotes(self, listener = None ): notes = [] for p in self.pages: notes.extend( self.getNotesByPage(p, listener ) ) return notes def getCSNotesByPage( self, page ): return map( lambda n: n.cs, self.getNotesByPage( page ) ) def getCSNotesByTrack( self, page, track ): return map( lambda n: n.cs, self.getNotesByTrack( page, track ) )