lowlevel audio synthesis subsystem. modular analog synthesizer in software
: design :
here are all design ideas/philosophy considered during the design of
- audio data flow between modules must be continuous. data cannot be
thrown out or ignored. this means that modules must wait for
input to produce output. otherwise the integrety of the resulting
audio data could become corrupt.
To enable a level of detail sort of optimization, we should consider
using a flag that signals when not to wait.
the done state
this "done" state means "dont wait for
me". This allows modules to continue processing data when a source
module predicts there will be no change in its output data for a
i.e. when a .wav file is done playing, or when a knob is not turned.
how is the done state signaled?
probably doesn't matter too much since these are all basically the
- in the queue
- in the connection
- in the terminal
data flow between modules
general data movement
- direct write or direct read from one module to the next.
This data flow model is not good for asyncronous execution of
- buffered read or buffered write.
This method would need a queue (fixed size, or variable size) to support
buffered reading or writting of audio data, so that we don't lose any
of it (see section on data integrity). The queue container will
ensure data is not discarded until it is ok.
3 ideas about queuing:
- Queue implemented with 2 buffers (like OpenGL front/back buffers)
of a fixed size (i.e. 20000 samples each)
one buffer is available for writing while the other can be read from.
- Queue implemented with 2 buffers of a variable size.
Size changes based on demand.
i.e. if openal needs more, then the module requests more data from
its input terminal. Next iteration, the source module must provide
a larger block of samples.
- N sized Queue with variable sized buffers. This
version would be similar to the Producer Consumer pattern found in
distributed computing literature. (current implementation)
2 ideas about buffers:
- buffer can be "filled" and "drained". this allows you to come back
to it and tell where you left off at. (current implementation)
- fill and drain is an idea that can be implemented in the Buffer
class, or externally in an iterator class. If external,
this could be much more flexible, since it allows many people
all to gain an iterator over the buffer, and do their fills and drains
individually without affecting the others..
issues: how to signal when all iterators are done, so we
know when to tell the queue the buffer has been drained?
this topic has to do with a module, its terminals, and how the terminals get
connected, and who owns the queue used in that connection.
2 ideas so far:
- current impl is we have a Terminal class, then separate input and output
terminal classes. the input class owns the queue, and that is the only
different. input and output classes directly connect to each other. this
is type safe (i.e. input and input cannot connect to each other.)
- next idea is to refactor the current impl into one Terminal class. and
there are two ways i've thought of that i can go with it:
- have a Terminal and a Connection class (no input or output specialized
classes) Terminal serves as place holders so you can query a module
for what its terminals are. Then to connect two Terminals, you
bind them with a 3rd Connection class that also contains the actual
Queue. The Queue is created upon a connection of two terminals, and
destroyed upon disconnect.
- just have a Terminal class and autonegotiate which one keeps the Queue
in some way. this could be saved after disconnect so the memory doesn't
thrash when connect/disconnect happens frequently...
or it could be let go upon disconnect, in which case you'd have the
thrashing when connect/disconnect often...
this topic has to do with converting data between modules, and best ways to
implement audio data conversion (such as format, channels, samp rate).
2 ideas so far:
- ensure performance for the user.
never convert data between modules, enforce this with coding practices
such as asserts, or don't allow access to bad performace code.
- allow it for flexibility, but if the user wants to get maximum
performance, then they will want to anylize their configuration for any
bad performance due to excessive conversion between modules.
NOTE: float32 is probably a good choice for audio data in the sound network.
this allows flexible algorithms, and is accelerated on most modern computers.
TODO: do tests on int and float and measure performance between the two.
software architechture overview
- what does SUBSYNTH look like?
- audio network
- network of modules connected by terminals
- a module can be
- a generator of sound
- a filter for sound
- a receiver of sound
- each module has any number of input and output terminals
- data is propagated to and from the terminals using a generic data signal
- generic data signal
- represented as an array of samples
- facilitates non-audio data as well (controllers)
- amplitude controler
- dc offset
- real time aspects, task managment
- task managment
- scheduler (plugable)
software architechture details
no description available yet.,..
UML view of the currently implemented system...
intro | documentation | design| requirements| implementation.notes| lit.search| publications