Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/Util/Sound.py
diff options
context:
space:
mode:
authorJames Bergstra <james@mackie.(none)>2007-01-15 03:34:01 (GMT)
committer James Bergstra <james@mackie.(none)>2007-01-15 03:34:01 (GMT)
commit158d399d97c52554659f1725425700724eafc514 (patch)
tree124bc91394f90517a030eb09272bef39e98ecbd1 /Util/Sound.py
parentd94a1afec9661b7d52032efced14bf560260533b (diff)
writing util/sound
Diffstat (limited to 'Util/Sound.py')
-rw-r--r--Util/Sound.py294
1 files changed, 235 insertions, 59 deletions
diff --git a/Util/Sound.py b/Util/Sound.py
index 333adb5..be00cb5 100644
--- a/Util/Sound.py
+++ b/Util/Sound.py
@@ -5,16 +5,147 @@ import select
import sys
import threading
import time
-from sugar import env
+import bisect
+from sugar import env
import Config
-class CSoundClientBase:
- def __init__(self, orc):
+from Util.CSoundNote import CSoundNote #maybe not actually used, but dependence is there. All notes are assumed to be CSoundNotes
+from Generation.GenerationConstants import GenerationConstants
+
+class Sound:
+ #PRIVATE
+ DRIFT = 0.01 #careful about changing this... coordinate with instrument 5777
+ def loop_work(self, sleeptime):
+ def next( ) :
+ time_time = time.time()
+ #tickhorizon is tick where we'll be after range_sec
+ tickhorizon = self.getTick( self.range_sec + time_time, False )
+ time0_time = self.time0 - self.time_start + self.DRIFT
+
+ if tickhorizon < 0 : return []
+ if len(self.notes) == 0 : return []
+
+ def cache_cmd(secs_per_tick, amplitude, pitch, inst, trackId, duration, tied, fullDuration, overlap, attack, decay, reverbSend, filterType, filterCutoff, pan ):
+ if inst[0:4] == 'drum':
+ if pitch in GenerationConstants.DRUMPITCH:
+ key = GenerationConstants.DRUMPITCH[ pitch ]
+ else:
+ key = pitch
+
+ if inst == 'drum1kit':
+ inst = Config.DRUM1INSTRUMENTS[ key ]
+ if inst == 'drum2kit':
+ inst = Config.DRUM2INSTRUMENTS[ key ]
+ if inst == 'drum3kit':
+ inst = Config.DRUM3INSTRUMENTS[ key ]
+ pitch = 1
+
+ else:
+ pitch = GenerationConstants.TRANSPOSE[ pitch - 24 ]
+
+ # condition for tied notes
+ if Config.INSTRUMENTS[ inst ].csoundInstrumentId == 101 and tied and fullDuration:
+ duration= -1.0
+ # condition for overlaped notes
+ if Config.INSTRUMENTS[ inst ].csoundInstrumentId == 102 and overlap:
+ duration += 1.0
+
+ attack = max( 0.002, duration * attack)
+ decay = max( 0.002, duration * decay)
+
+ rval = Config.PLAY_NOTE_COMMAND_MINUS_DELAY % \
+ ( Config.INSTRUMENTS[ inst ].csoundInstrumentId,
+ trackId,
+ '%f', #delay,
+ duration,
+ pitch,
+ reverbSend,
+ amplitude,
+ pan,
+ Config.INSTRUMENT_TABLE_OFFSET + Config.INSTRUMENTS[ inst ].instrumentId,
+ attack,
+ decay,
+ filterType, filterCutoff )
+ return rval
+
+ def getText(i, secs_per_tick, time_offset):
+ (onset,note,cache,z) = self.notes[i]
+ if cache == '' or note.nchanges != z :
+ self.notes[i] = \
+ (
+ onset,
+ note,
+ cache_cmd(
+ secs_per_tick,
+ note.amplitude, # * track-level mixer rate
+ note.pitch,
+ note.instrumentFlag,
+ note.trackId,
+ note.duration * self.secs_per_tick,
+ note.tied,
+ note.fullDuration,
+ note.overlap,
+ note.attack,
+ note.decay,
+ note.reverbSend,
+ note.filterType,
+ note.filterCutoff,
+ note.pan),
+ note.nchanges
+ )
+ rval = self.notes[i][2] % float(onset * self.secs_per_tick + time_offset)
+ return rval
+
+ prev_secs = (self.loops * self.duration) * self.secs_per_tick
+ rval = []
+ while self.notes[self.hIdx][0] + self.loops * self.duration < tickhorizon:
+ rval.append ( getText(self.hIdx, self.secs_per_tick, prev_secs + time0_time ) )
+ self.hIdx += 1
+ if self.hIdx == len(self.notes):
+ self.hIdx = 0
+ self.loops += 1
+ prev_secs += self.duration * self.secs_per_tick
+
+ return rval
+
+ #thread.start_new_thread( testtimer, (0,) )
+ m = 0.0
+ while self.thread_continue:
+ t0 = time.time()
+ time.sleep(sleeptime)
+ t1 = time.time()
+ if t1 - t0 > 2.0 * sleeptime :
+ print 'critical lagginess: ', t1 - t0
+ if m < t1 - t0:
+ m = t1 - t0
+ print t1, ' timer max = ', m
+ cmds = self.next()
+ for c in cmds:
+ self.perf.InputMessage( '' )
+
+ def __init__(self, orc, range_sec, ticks_per_sec ):
self.orc = orc
- self.on = False
+ self.up = False
self.csound = csnd.Csound()
+ self.ticks_per_sec = ticks_per_sec # ticks last this long
+ self.secs_per_tick = 1.0 / ticks_per_sec # precomputed inverse
+ self.range_sec = range_sec # notes are checked-for, this many seconds in advance
+
+ self.duration = 0 # number of ticks in playback loop
+ self.loops = 0 # number of elapsed loops
+ self.notes = [] # sorted list of (onset, noteptr, cache)
+
+ self.time0 = time.time() + 1000000 # the real time at which tick == 0 (sometimes retro-active)
+ self.thread_continue = 1
+ self.thread = thread.start_new_thread( loop_work, (self,0.040) )
+
+ def uninit(self):
+ self.thread_continue = 0
+ self.thread.join()
+ if self.up : self.lower()
+
def micRecording( self, table ):
mess = Config.MIC_RECORDING_COMMAND % table
self.sendText( mess )
@@ -26,6 +157,17 @@ class CSoundClientBase:
mess = Config.LOAD_INSTRUMENT_COMMAND % ( instrumentId, fileName )
self.sendText( mess )
+ def startTime(self):
+ if not self.up :
+ debug_print (1, "ERROR: Sound::startTime, performance thread isn't up yet.")
+ return
+ self.perf.InputMessage('i 5999 0.0 60000000')
+ self.time_start = time.time()
+ # if a note event is sent to csound before or simultaneous to this one, then it will not play correctly.
+ # thus we sleep right here, to (ideally) let csound pick up the message.
+ # NB: match this to the constant in the instrument 5777 of the csound orcestra
+ time.sleep(0.1)
+
def load_instruments( self ):
home_path = env.get_profile_path() + Config.PREF_DIR
for instrumentSoundFile in Config.INSTRUMENTS.keys():
@@ -37,64 +179,98 @@ class CSoundClientBase:
mess = Config.LOAD_INSTRUMENT_COMMAND % ( instrumentId, fileName )
self.sendText( mess )
- def startTime(self):
- self.sendText("perf.InputMessage('i 5999 0.0 60000000')")
- # if any other message arrives to csound at the same time as this one,
- # then the global variables will not be set up right in the orcestra
- #
- # NB: match this to the constant in the instrument 5777 of the csound orcestra
- time.sleep(0.1)
+ def raise( self ):
+ if self.up :
+ debug_print(3, 'Sound::raise() already up.')
+ return
+ self.up = True
+ self.perf = csnd.CsoundPerformanceThread(self.csound)
+ self.csound.Compile( self.orc )
+ self.perf.Play()
+ self.load_instruments()
+ debug_print(5, 'Sound::raise succeeded')
- def initialize( self, init = True ):
- if init:
- if self.on : return
- self.on = True
- self.perf = csnd.CsoundPerformanceThread(self.csound)
- self.csound.Compile( self.orc )
- self.perf.Play()
- self.load_instruments()
- print 'CSoundClient = True'
- else:
- if not self.on : return
- self.on = False
- #self.csound.SetChannel('udprecv.0.on', 0)
- #print Config.UNLOAD_TABLES_COMMAND
- self.sendText( Config.UNLOAD_TABLES_COMMAND )
- #print 'PERF STOP'
- self.perf.Stop()
- #print 'SLEEP'
- #time.sleep(1)
- #print 'JOIN'
- #time.sleep(1)
- rval = self.perf.Join()
- #print 'Join returned ', rval
- #del self.perf
- #time.sleep(1)
- #print 'STOP'
- #self.csound.Stop()
- #print 'RESET'
- self.csound.Reset()
- print 'CSoundClient = False'
- #careful how much cleaning up we do... don't cause a segault!
- # better to leave a segfault for the automatic cleanning at the end of the prog
-
- #self.csound.Cleanup()
- #print 'STOPPED'
- #time.sleep(1)
- #del self.csound
- #print 'DELETED'
- #time.sleep(1)
+ def lower(self):
+ if not self.up :
+ debug_print(3, 'Sound::lower() already down.')
+ return
+ self.up = False
+ self.sendText( Config.UNLOAD_TABLES_COMMAND )
+ self.perf.Stop()
+ rval = self.perf.Join()
+ self.csound.Reset()
+ debug_print(5, 'Sound::lower() succeeded')
def setMasterVolume(self, volume):
self.csound.SetChannel('masterVolume',volume )
- def sendText(self, txt):
- #print txt
- perf = self.perf
- csound = self.csound
- if 'csound' in txt:
- print txt
- import sys
- sys.exit(0)
- exec txt
+ def inputMessage(self, txt):
+ self.perf.InputMessage(txt)
+
+ def loop_setTick( self, tick ):
+ time_time = time.time()
+ self.time0 = time_time - tick * self.secs_per_tick
+ self.loops = tick // self.duration
+ self.hIdx = bisect.bisect_left(self.notes, tick - self.duration * self.loops )
+
+ def loop_setRate( self, ticks_per_sec):
+ if ticks_per_sec != self.ticks_per_sec:
+ secs_per_tick = 1.0 / ticks_per_sec
+
+ time_time = time.time()
+ curtick = self.getTick( time_time, False )
+ curticktime = curtick * self.secs_per_tick + self.time0
+
+ self.ticks_per_sec = ticks_per_sec
+ self.secs_per_tick = secs_per_tick
+ self.time0 = curticktime - curtick * secs_per_tick
+ self.notes = [ (o,n,'',z) for (o,n,c,z) in self.notes ] #clear cache
+ self.loops = 0
+
+ def loop_setDuration( self, duration ):
+ self.time0 += self.loops * self.duration * self.secs_per_tick
+ self.loops = 0
+ self.duration = duration
+
+ def loop_getTick(self, t, domod): #t is for time
+ if domod :
+ return ( int( ( t - self.time0 ) * self.ticks_per_sec ) ) % self.duration
+ else :
+ return ( int( ( t - self.time0 ) * self.ticks_per_sec ) )
+
+ def loop_insert( self, notes):
+ def insertMany():
+ self.notes += [ ( notes[i][0], notes[i][1], '', 0 ) for i in xrange(len(notes)) ]
+ self.notes.sort()
+ def insertFew():
+ for i in xrange(len(notes)):
+ t = (notes[i][0], notes[i][1],'',0)
+ l = bisect.bisect_left(self.notes, t )
+ self.notes.insert(l, t)
+
+ if len(notes) >= 1:
+ insertMany()
+ else:
+ insertFew()
+ self.hIdx = bisect.bisect_left(self.notes, self.getTick(self.range_sec + time.time(), True))
+
+ def loop_remove(self, note):
+ def removeFew():
+ i = 0
+ while i < len(self.notes):
+ if self.notes[i][1] in note:
+ del self.notes[i]
+ else:
+ i += 1
+
+ def removeMany():
+ self.notes = [t for t in self.notes if t[1] not in note]
+
+ if len(idset) >= 0: #just guessing here, should do some timing tests to see if this is good or no
+ removeMany()
+ else:
+ removeFew()
+ self.hIdx = bisect.bisect_left(self.notes, self.getTick(self.range_sec + time.time(), True))
+ def loop_clear(self):
+ self.notes = []