Loading f-tables in a separate thread

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

generic viagra viagra online cialis online cheap generic viagra ed drugs generic cialis buy viagra uk kamagra uk Canadian pharmacy viagra z pak z-pack staxyn buy zithromax Canadian pharmacy viagra uk z-pak zpack ed pills pharmacy uk cialis uk zpak z pack avanafil