import Config class PARAMETER: ONSET = 0 PITCH = 1 AMPLITUDE = 2 DURATION = 3 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 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 #-- 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 ) for t in range(Config.NUMBER_OF_TRACKS): for n in self.noteD[cp][t].keys(): self.duplicateNote( cp, t, n, id, t, 0 ) self._insertPage( id, after ) after = id new[cp] = id for l in self.pageListeners: l.notifyPageDuplicate( new, first ) 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( 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 ): i = [-1] p = self._readstream(stream,i) while p != -1: t = self._readstream(stream,i) N = self._readstream(stream,i) hint = [0] for j in range(N): self.addNote( p, t, self._readstream(stream,i), hint ) p = self._readstream(stream,i) 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( 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, page, track ): notes = self.noteS[page][track][:] for n in notes: self.deleteNote( page, track, n.id ) def duplicateNote( self, page, track, id, toPage, toTrack, offset ): cs = self.noteD[page][track][id].cs.clone() cs.track = toTrack 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 for par in self.parasiteList.keys(): self.parasiteD[page][track][par][id].updateParameter( parameter, value ) for l in self.noteListeners: l.notifyNoteUpdate( 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, stream[inc(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 ) #======================================================= # 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 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 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 ) )