I'm sure many will agree that realtime sample playback is rather difficult to do in Csound (especially from a beginner's perspective). At the very least, I believe it's unnecessarily complex.
In the past, this is one way I solved the problem.
Csound Orchestra:
instr 4 ; play table
iDur = p3
iTable = p4
iSR = p5
iLen = ftlen(iTable)
iRate = 1.0 / (iLen / iSR)
aPhase phasor iRate
aPhase = aPhase * iLen
aOut tab aPhase, iTable
aOut = aOut * ampdbfs(gkVol4)
outs aOut, aOut
endin
instr 110 ; load instrument
kCounter init 0
Sfname strget p4
Spathback sprintf "%d/len", p5
Spathbackchan sprintf "%d/channels", p5
Spathbacksr sprintf "%d/sr", p5
ichannels filenchnls Sfname
isr filesr Sfname
iftno ftgen p5, 0, 0, 1, Sfname, 0, 0, 1
if kCounter >= 0 kgoto done
outvalue Spathback, ftlen(iftno)
outvalue Spathbackchan, ichannels
outvalue Spathbacksr, isr
done:
endin
Python Code:
class AudioFile:
def __init__(self, csound, filename, fTable):
self._filename = filename
self._fTableIndex = fTable
self.csound = csound
self._tableLength = 0
self._channels = 0
self._sampleRate = 0
self.isLoaded = False
def filename(self):
return self._filename
def fTableIndex(self):
return self._fTableIndex
def tableLength(self):
return self._tableLength
def numChannels(self):
return self._channels
def sampleRate(self):
return self._sampleRate
def loadedCallback(self, message):
"""
When the instrument finishes loading the f-table,
this method should be called twice. Once as /len
and once as /channels ... once both values have been
acquired, we can test them and decide if the table
has been successfully loaded and then decide to play the sample
if it was requested
argument(0) will represent the length and number of channels respectively
"""
#message.display("callback:")
#print
if message.currentPath() == '/len':
self._tableLength = message.argument(0)
elif message.currentPath() == '/channels':
self._channels = message.argument(0)
elif message.currentPath() == '/sr':
self._sampleRate = message.argument(0)
if self._tableLength > 0 and self._channels > 0 and self._sampleRate > 0:
self.isLoaded = True
def duration(self):
if self._sampleRate > 0:
return self._tableLength / self._sampleRate
else:
return 0
def load(self):
"""
Load the audio file as an f-table
The instrument will notify us when the table has loaded by calling
self.loadedCallback
"""
self.loadCalled = True
self.csound.registerChannel(str(self._fTableIndex), self.loadedCallback)
fEvent = 'i110 0 1 "'+self._filename+'" '+str(self._fTableIndex)
self.csound.playEventAsString(fEvent)
I'm all for simplifying the above if possible. But for now, its not a huge concern. The concern is the _way_ files are loaded.
One potential solution I've been tossing around is the idea of loading f-tables in a separate thread. On the XO, if you're playing audio - you can't expect to load an audio file without it messing up playback. Now, of course you _could_ load all samples at startup, but many applications will want to play from the large collection of samples Dr. B has collected. There isn't enough RAM and it would take too long to load every possibility.
Warning: The following is not necessarily complete and there may be a better way.
So we might need the following 'thread opcodes'
Create and Destroy Threads
thread_id thread_create level (optional)
thread_destroy thread_id
Load the tables on the thread (add them to a queue to be processed)
table_id thread_ftgen thread_id, [other ftgen params]
thread_table_notifications table_id, instrument_callback_number, parameter_to_check
OR to make things clearer, separate it to return 2 params
table_id, load_id thread_ftgen thread_id, [other ftgen params]
thread_table_notifications load_id, instrument_callback_number, [callback params]
The instrument_callback_number represents the number of the instrument that an event will be sent to when the event occurs.
The following notifications are needed:
1 - when the table is finished loading
2 - if an error occurs
etc...
Also, we'd want to be able to query how much has loaded.
Here's a sketch of what it might look like on the Csound side:
iPriority = 1 ; low-priority
githreadID thread_create iPriority
instr 100 ; notify when table loads
iTable = p4
iLoaded = p5 ; 1 if success, 0 if failed
Spathback sprintf "%d/len", iTable
outvalue Spathback, iLoaded
endin
instr 110 ; load instrument
kCounter init 0
; p4 = filename
; p5 = f-table id
Sfname strget p4
Spathback sprintf "%d/len", p5
Spathbackchan sprintf "%d/channels", p5
Spathbacksr sprintf "%d/sr", p5
ichannels filenchnls Sfname
isr filesr Sfname
iftno thread_ftgen githreadID, p5, 0, 0, 1, Sfname, 0, 0, 1
thread_table_notifications iftno, 100, 1
if kCounter >= 0 kgoto done
outvalue Spathback, ftlen(iftno)
outvalue Spathbackchan, ichannels
outvalue Spathbacksr, isr
done:
endin
Thoughts... comments... other ideas?
greg



Another thought
If I were to develop a custom set of python bindings I could possibly support this dynamic loading through the bridge between python and the Csound API as opposed to implementing this directly in Csound.
why the instrument?
I am not sure I understand the reason for the
instrument to load ftables. You could just call
csoundInputMessage() to load a table at the time
you wish, from Python. Perhaps I am missing something.
The major issue with Csound events is that they
are processed by PerformKsmps() and will be called
in the same thread as the audio processing.
A (roundabout) way to solve this would be to
load a file using csoundGetTable() etc into
a table (ignoring GENs)in an auxiliary Class,
which could be wrapped in Python. This class
can be made to do this things in a separate thread.
Perhaps we could also look into a new API
function to invoke a GEN directly to fill a
table.
Victor
Re: why the instrument?
Why was I loading f-tables in the instrument?
So as to be notified of the size and number of channels when it was finished loading and ready to use. If there is an alternative, I'd be happy to know (especially if it makes things easier).
I agree, there should be some way to load and transfer data to and from tables in the API on a separate thread. That would certainly work in this case. Of course it would need to support notifications (ie, when it finishes loading).
greg