It's really been bugging me that there MUST be some good reason why CA decided to cripple the poly mechanism by limiting the index to [0, numVoices - 1] rather than [0, 15].
It bugged me so much that I didn't get much sleep last night as I worked through the following ideas while lying in bed.
This is going to get a bit technical and I may well have made a real blunder somewhere in my reasoning so just skip this post if you aren't interested in the low-level detail of how VM may or may not work. And as always I very much welcome anyone showing where I have made any error in my thinking.
The following is bare bones pseudocode, I'm using my own terms (so socket rather than jack, etc), using imaginary classes and ignoring various details in order to focus on why CA might have crippled poly in order to improve performance.
Here's the mechanism I would use to implement VM's core functionality...
The main thread would look like this...
Code: Select all
mainThread
{
isEven = true
while running
while buffer not full
for each module m in patch
m.processSample()
isEven = ! isEven
buffer.put( leftOutputSocket.getValue(), rightOutputSocket.getValue() )
tell audio output thread that buffer is ready
sleep until audio output thread has made a copy of the buffer
set buffer to empty
}
In this scheme all inter-module communication is driven from inside processSample() by calls to getValue() and setValue() methods of MonoSocket and PolySocket objects.
Code: Select all
class monoSocket
{
double getValue()
{
result = 0
if isInput
for each monoCable c that is connected
result += c.getSource().getValue()
else
if isEven
result = valueB
else
result = valueA
return result
}
setValue( double value )
{
if isEven
valueA = value
else
valueB = value
}
}
Code: Select all
class polySocket
{
double getValue( int index )
{
result = 0
if isInput
for each polyCable c that is connected
result += c.getSource().getValue( index )
else
if isEven
result = valueBArray[ index ]
else
result = valueAArray[ index ]
return result
}
setValue( int index, double value )
{
if isEven
valueAArray[ index ] = value
else
valueBArray[ index ] = value
}
}
The reason why there are A and B values inside the socket (VoltageJack like) classes is that we need the sample update to maintain atomicity. So output sockets have two sets of values, one from the previous sample period that forms a stable input for calculations made in the current one and then a more dynamic and messy set of values that are steadily being calculated as we work through the modules in an arbitary order.
I think this code works but I've only run it in my head.
Now here's how I suspect CA's code works.
The main thread would look like this...
Code: Select all
mainThread
{
while running
while buffer not full
for each module m in patch
m.processSample()
for each output monoSocket ms in patch
ms.oldValue = ms.newValue
for each output polySocket ps in patch
if ps.isConnected
for index = 0 to index = numVoices - 1
ps.oldValueArray[ index ] = ps.newValueArray[ index ]
buffer.put( leftOutputSocket.getValue(), rightOutputSocket.getValue() )
tell audio output thread that buffer is ready
sleep until audio output thread has made a copy of the buffer
set buffer to empty
}
As before everything is driven from inside processSample() by calls to getValue() and setValue() methods of MonoSocket and PolySocket objects.
But this time a brute-force algorithm is used to attain atomicity.
This simplifies the get/setValue() methods as shown below...
Code: Select all
class monoSocket
{
double getValue()
{
result = 0
if isInput
for each monoCable c that is connected
result += c.getSource().getValue()
else
result = oldValue
return result
}
setValue( double value )
{
newValue = value
}
}
Code: Select all
class polySocket
{
double getValue( int index )
{
result = 0
if isInput
for each polyCable c that is connected
result += c.getSource().getValue( index )
else
result = oldValueArray[ index ]
return result
}
setValue( int index, double value )
{
newValueArray[ index ] = value
}
}
So now instead of A and B values we have old and new values. This is easier to understand but now instead of just inverting the isEven boolean the main thread has to traverse all output sockets and copy the new values to the old values every sample period.
This is an expensive operation especially for polySockets so the code is optimized to only handle polySockets that are connected and also limit the data copying to the range [0, numVoices - 1].
Testing monoSocket's isConnected state would be unlikely to result in performance improvement as the test and branch would probably cost more than always doing the simple assignment.
It could be further optimized either by manipulating the socket lists as cables are connected and disconnected (but that might be pretty fiddly) or by traversing the cable lists for each module instead (but that involves more looping and dereferencing).
So in summary the first "demand-driven" algorithm which is how I imagined CA would have coded things would not incur a penalty for allowing poly indices to be full-range but in practice they probably implemented the second "brute-force" algorithm so decided it was worth crippling the system to make it more efficient.