Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/TamTamJam.activity
diff options
context:
space:
mode:
authoramartin <olpc@xo-05-28-21.localdomain>2008-02-14 08:18:41 (GMT)
committer amartin <olpc@xo-05-28-21.localdomain>2008-02-14 08:18:41 (GMT)
commit89501dcf2cc75a2cb5ad291d8cd8fe96b916e17e (patch)
tree479816af650a1c0bb11ddd1fb49b89663553c27e /TamTamJam.activity
parent985d7564501f9eadea4548c860b36879301ac520 (diff)
new heartbeat
Diffstat (limited to 'TamTamJam.activity')
-rw-r--r--TamTamJam.activity/Jam/JamMain.py187
1 files changed, 92 insertions, 95 deletions
diff --git a/TamTamJam.activity/Jam/JamMain.py b/TamTamJam.activity/Jam/JamMain.py
index a4d760e..4b544f3 100644
--- a/TamTamJam.activity/Jam/JamMain.py
+++ b/TamTamJam.activity/Jam/JamMain.py
@@ -39,6 +39,8 @@ from sugar.graphics.xocolor import XoColor
from math import sqrt
+HEARTBEAT_BUFFER = 100 # increase the length of heartbeat loop to remove problems with wrapping during sync correction
+
class JamMain(gtk.EventBox):
def __init__(self, activity):
@@ -298,7 +300,9 @@ class JamMain(gtk.EventBox):
self.syncTimeout = None
self.heartbeatLoop = self.csnd.loopCreate()
self.syncBeats = 4
- self.csnd.loopSetNumTicks( self.syncBeats*Config.TICKS_PER_BEAT, self.heartbeatLoop )
+ 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
@@ -464,13 +468,7 @@ class JamMain(gtk.EventBox):
def _playDrum( self, id, pageId, volume, reverb, beats, regularity, loopId = None, sync = True ):
- if loopId == None: # create new loop
- if sync: startTick = self.csnd.loopGetTick( self.heartbeatLoop )
- else: startTick = 0
- else: # update loop
- startTick = self.csnd.loopGetTick( loopId )
- self.csnd.loopDestroy( loopId )
-
+ oldId = loopId
loopId = self.csnd.loopCreate()
noteOnsets = []
@@ -495,33 +493,22 @@ class JamMain(gtk.EventBox):
self.drumFillin.play()
+ if oldId == None:
+ if sync: startTick = self.csnd.loopGetTick( self.heartbeatLoop ) % self.syncTicks
+ else: startTick = 0
+ else:
+ if sync: startTick = self.csnd.loopGetTick( oldId ) # TODO is this really safe? could potentially add several milliseconds of delay everytime a loop is updated
+ else: startTick = 0
+
while startTick > ticks:
startTick -= ticks
- # sync to heartbeat
- if sync:
- beatTick = int(startTick) % Config.TICKS_PER_BEAT
- syncTick = self.csnd.loopGetTick( self.heartbeatLoop ) % Config.TICKS_PER_BEAT
- if beatTick > syncTick:
- if beatTick - syncTick < syncTick + Config.TICKS_PER_BEAT - beatTick:
- startTick = (int(startTick)//Config.TICKS_PER_BEAT)*Config.TICKS_PER_BEAT + syncTick
- else:
- startTick = (1 + int(startTick)//Config.TICKS_PER_BEAT)*Config.TICKS_PER_BEAT + syncTick
- else:
- if syncTick - beatTick < beatTick + Config.TICKS_PER_BEAT - syncTick:
- startTick = (int(startTick)//Config.TICKS_PER_BEAT)*Config.TICKS_PER_BEAT + syncTick
- else:
- startTick = (-1 + int(startTick)//Config.TICKS_PER_BEAT)*Config.TICKS_PER_BEAT + syncTick
-
- if startTick >= ticks:
- startTick -= ticks
- elif startTick < 0:
- startTick += ticks
-
self.csnd.loopSetTick( startTick, loopId )
-
self.csnd.loopStart( loopId )
+ if oldId != None:
+ self.csnd.loopDestroy( oldId )
+
return loopId
def _stopDrum( self, loopId ):
@@ -529,18 +516,13 @@ class JamMain(gtk.EventBox):
self.csnd.loopDestroy( loopId )
def _playLoop( self, id, volume, reverb, tune, loopId = None, force = False, sync = True ):
- if loopId == None: # create new loop
- if sync: startTick = self.csnd.loopGetTick( self.heartbeatLoop )
- else: startTick = 0
- else: # update loop
- startTick = self.csnd.loopGetTick( loopId )
- self.csnd.loopDestroy( loopId )
+ oldId = loopId
loopId = self.csnd.loopCreate()
inst = self.instrumentDB.instId[id]
- offset = 0
+ ticks = 0
for page in tune:
for n in self.noteDB.getNotesByTrack( page, 0 ):
n.pushState()
@@ -550,44 +532,33 @@ class JamMain(gtk.EventBox):
if inst.kit: # drum kit
if n.cs.pitch in GenerationConstants.DRUMPITCH:
n.cs.pitch = GenerationConstants.DRUMPITCH[n.cs.pitch]
- n.cs.onset += offset
+ n.cs.onset += ticks
self.csnd.loopPlay( n, 1, loopId = loopId )
n.popState()
for n in self.noteDB.getNotesByTrack( page, 1 ): # metronome track
self.csnd.loopPlay( n, 1, loopId = loopId )
for n in self.noteDB.getNotesByTrack( page, 2 ): # record scratch track
self.csnd.loopPlay( n, 1, loopId = loopId )
- offset += self.noteDB.getPage(page).ticks
-
- self.csnd.loopSetNumTicks( offset, loopId )
+ ticks += self.noteDB.getPage(page).ticks
- while startTick > offset:
- startTick -= offset
+ self.csnd.loopSetNumTicks( ticks, loopId )
- # sync to heartbeat
- if sync:
- beatTick = startTick % Config.TICKS_PER_BEAT
- syncTick = self.csnd.loopGetTick( self.heartbeatLoop ) % Config.TICKS_PER_BEAT
- if beatTick > syncTick:
- if beatTick - syncTick < syncTick + Config.TICKS_PER_BEAT - beatTick:
- startTick = (int(startTick)//Config.TICKS_PER_BEAT)*Config.TICKS_PER_BEAT + syncTick
- else:
- startTick = (1 + int(startTick)//Config.TICKS_PER_BEAT)*Config.TICKS_PER_BEAT + syncTick
- else:
- if syncTick - beatTick < beatTick + Config.TICKS_PER_BEAT - syncTick:
- startTick = (int(startTick)//Config.TICKS_PER_BEAT)*Config.TICKS_PER_BEAT + syncTick
- else:
- startTick = (-1 + int(startTick)//Config.TICKS_PER_BEAT)*Config.TICKS_PER_BEAT + syncTick
+ if oldId == None:
+ if sync: startTick = self.csnd.loopGetTick( self.heartbeatLoop ) % self.syncTicks
+ else: startTick = 0
+ else:
+ if sync: startTick = self.csnd.loopGetTick( oldId ) # TODO is this really safe? could potentially add several milliseconds of delay everytime a loop is updated
+ else: startTick = 0
- if startTick >= offset:
- startTick -= offset
- elif startTick < 0:
- startTick += offset
+ while startTick > ticks:
+ startTick -= ticks
self.csnd.loopSetTick( startTick, loopId )
-
self.csnd.loopStart( loopId )
+ if oldId != None:
+ self.csnd.loopDestroy( oldId )
+
return loopId
def _stopLoop( self, loopId ):
@@ -773,6 +744,8 @@ class JamMain(gtk.EventBox):
self.jamToolbar.setTempo( tempo, quiet )
def _setTempo( self, tempo, propagate = True ):
+ # TODO update to new heardbeat format
+ return
if self.network.isHost() or self.network.isOffline():
t = time.time()
percent = self.heartbeatElapsed() / self.beatDuration
@@ -1111,9 +1084,9 @@ class JamMain(gtk.EventBox):
hash = data[0:4]
latency = t - self.syncQueryStart[hash]
self.unpacker.reset(data[4:8])
- nextBeat = self.unpacker.unpack_float()
+ elapsed = self.unpacker.unpack_float()
#print "mini:: got sync: next beat in %f, latency %d" % (nextBeat, latency*1000)
- self.heartbeatStart = t + nextBeat - self.beatDuration - latency/2
+ self.heartbeatStart = t - elapsed - latency/2
self.correctSync()
self.syncQueryStart.pop(hash)
@@ -1124,7 +1097,7 @@ class JamMain(gtk.EventBox):
self.sendSyncQuery()
def processPR_SYNC_QUERY( self, sock, message, data ):
- self.packer.pack_float(self.nextHeartbeat())
+ self.packer.pack_float(time.time() - self.heartbeatStart)
self.network.send( Net.HT_SYNC_REPLY, data + self.packer.get_buffer(), sock )
self.packer.reset()
@@ -1150,68 +1123,70 @@ class JamMain(gtk.EventBox):
if beats == self.syncBeats:
return
- self.syncBeats = beats
+ elapsedTicks = (time.time() - self.heartbeatStart)*self.ticksPerSecond + self.offsetTicks
+ elapsedBeats = int(elapsedTicks) // Config.TICKS_PER_BEAT
- ticks = beats * Config.TICKS_PER_BEAT
+ targBeat = (elapsedBeats % self.syncBeats) % beats
+ curBeat = elapsedBeats % beats
+ offset = (targBeat - curBeat) * Config.TICKS_PER_BEAT
- curTick = self.csnd.loopGetTick( self.heartbeatLoop )
+ self.syncBeats = beats
+ self.syncTicks = beats*Config.TICKS_PER_BEAT
+
+ self.offsetTicks = (offset + self.offsetTicks) % (self.syncTicks*HEARTBEAT_BUFFER)
+ elapsedTicks += offset
- self.csnd.loopSetNumTicks( ticks, self.heartbeatLoop )
- while curTick > ticks:
- curTick -= ticks
+ newTick = elapsedTicks % (self.syncTicks*HEARTBEAT_BUFFER)
- self.csnd.loopSetTick( curTick, self.heartbeatLoop )
+ self.csnd.loopSetTick( newTick, self.heartbeatLoop )
+ self.csnd.loopSetNumTicks( self.syncTicks*HEARTBEAT_BUFFER, self.heartbeatLoop )
self.updateSync()
def _setBeat( self, beat ):
- curTick = self.csnd.loopGetTick( self.heartbeatLoop )
- beatTick = curTick % Config.TICKS_PER_BEAT
-
- newTick = beat*Config.TICKS_PER_BEAT + beatTick
- maxTick = self.syncBeats*Config.TICKS_PER_BEAT
- while newTick >= maxTick:
- newTick -= maxTick
- while newTick < 0:
- newTick += maxTick
- self.csnd.loopSetTick( newTick, self.heartbeatLoop )
- offset = newTick - curTick
+ curTick = self.csnd.loopGetTick( self.heartbeatLoop ) % self.syncTicks
+ curBeat = int(curTick) // Config.TICKS_PER_BEAT
+ offset = (beat - curBeat) * Config.TICKS_PER_BEAT
+ if offset > self.syncTicks//2:
+ offset -= self.syncTicks
+ elif offset < -self.syncTicks//2:
+ offset += self.syncTicks
- for id in self.desktop.getLoopIds():
+ self.offsetTicks = (offset + self.offsetTicks) % (self.syncTicks*HEARTBEAT_BUFFER)
+
+ for id in self.desktop.getLoopIds() + [ self.heartbeatLoop ]:
tick = self.csnd.loopGetTick( id )
- newTick = tick + offset
maxTick = self.csnd.loopGetNumTicks( id )
- while newTick >= maxTick:
- newTick -= maxTick
- while newTick < 0:
- newTick += maxTick
+ newTick = (tick + offset) % maxTick
self.csnd.loopSetTick( newTick, id )
- #self.csnd.adjustTick( newTick - curTick )
-
def updateBeatWheel( self ):
- curTick = self.csnd.loopGetTick( self.heartbeatLoop )
+ curTick = self.csnd.loopGetTick( self.heartbeatLoop ) % self.syncTicks
self.curBeat = int( curTick ) // Config.TICKS_PER_BEAT
self.playbackToolbar.updateBeatWheel( self.curBeat )
return True
- def nextHeartbeat( self ):
+ def anextHeartbeat( self ):
delta = time.time() - self.heartbeatStart
return self.beatDuration - (delta % self.beatDuration)
- def nextHeartbeatInTicks( self ):
+ def anextHeartbeatInTicks( self ):
delta = time.time() - self.heartbeatStart
next = self.beatDuration - (delta % self.beatDuration)
return self.ticksPerSecond*next
- def heartbeatElapsed( self ):
+ def aheartbeatElapsed( self ):
delta = time.time() - self.heartbeatStart
return delta % self.beatDuration
- def heartbeatElapsedTicks( self ):
+ def aheartbeatElapsedTicks( self ):
delta = time.time() - self.heartbeatStart
return self.ticksPerSecond*(delta % self.beatDuration)
+
+ def correctedHeartbeat( self ):
+ elapsedTicks = (time.time() - self.heartbeatStart)*self.ticksPerSecond
+ return (elapsedTicks + self.offsetTicks) % (self.syncTicks*HEARTBEAT_BUFFER)
def updateSync( self ):
if Config.DEBUG:
@@ -1229,6 +1204,28 @@ class JamMain(gtk.EventBox):
return True
def correctSync( self ):
+ curTick = self.csnd.loopGetTick( self.heartbeatLoop )
+ corTick = self.correctedHeartbeat()
+ err = corTick - curTick
+ maxTick = self.syncTicks * HEARTBEAT_BUFFER
+ if err < -maxTick//2: # these should never happen becasue of HEARTBEAT_BUFFER, but hey
+ err += maxTick
+ elif err > maxTick//2:
+ err -= maxTick
+
+ #print "correctSync", curTick, corTick, err, maxTick, self.offsetTicks
+
+ if abs(err) > 10*Config.TICKS_PER_BEAT: # we're way off
+ for id in self.desktop.getLoopIds() + [ self.heartbeatLoop ]:
+ tick = self.csnd.loopGetTick( id )
+ maxTick = self.csnd.loopGetNumTicks( id )
+ newTick = (tick + err) % maxTick
+ self.csnd.loopSetTick( newTick, id )
+ elif abs(err) > 0.25: # soft correction
+ self.csnd.adjustTick( err/3 )
+
+ return
+ # old code, TODO delete me
curTick = self.csnd.loopGetTick( self.heartbeatLoop ) % Config.TICKS_PER_BEAT
curTicksIn = curTick % Config.TICKS_PER_BEAT
ticksIn = self.heartbeatElapsedTicks()