@@ -609,7 +609,13 @@ earlier in the pipeline (data sources, for example) will be cheaper than the cos
higher level nodes, such as some kind of final post-processing endpoint. If you need to do mass
detachments, detach starting from the lowest level nodes and work your way towards the final
endpoint node (but don't try detaching the node graph's endpoint). If the audio thread is not
running, detachment will be fast and detachment in any order will be the same.
running, detachment will be fast and detachment in any order will be the same. The reason nodes
need to wait for their input attachments to complete is due to the potential for desyncs between
data sources. If the node was to terminate processing mid way through processing it's inputs,
there's a chance that some of the underlying data sources will have been read, but then others not.
That will then result in a potential desynchronization when detaching and reattaching higher-level
nodes. A possible solution to this is to have an option when detaching to terminate processing
before processing all input attachments which should be fairly simple.
Another compromise, albeit less significant, is locking when attaching and detaching nodes. This
locking is achieved by means of a spinlock in order to reduce memory overhead. A lock is present
...
...
@@ -641,15 +647,12 @@ only be happening in a forward direction which means the "previous" pointer won'
used. The same general process applies to detachment. See `ma_node_attach_to_output/input_node()`
and `ma_node_detach_output_bus()` for the implementation of this mechanism.
One outstanding problem exists regarding attaching and detaching. It is possible for an output bus
to be detached while the audio thread is in the middle of processing it. This by itself is not a
problem because the node is still valid. The problem is that of reattaching the output bus to a new
input bus while a read is still happening on the audio thread. What *could* happen is that the node
is reattached to a new input bus which hasn't yet been iterated in the current call to
`ma_node_graph_read_pcm_frames()` thereby resulting in the node getting processed twice. This would
flow through the base data source and result in a desync because it's read from it twice in the
same call to `ma_node_graph_read_pcm_frames()`. This is an unusual scenario and would most likely
go unnoticed by the majority of people, but it's still an issue to consider.
Loop detection is achieved through the use of a counter. At the ma_node_graph level there is a
counter which is updated after each read. There is also a counter for each node which is set to the
counter of the node graph plus 1 after each time it processes data. Before anything is processed, a
check is performed that the node's counter is lower or equal to the node graph. If so, it's fine to
proceed with processing. If not, MA_LOOP is returned and nothing is output. This represents a sort
of termination point.
*/
...
...
@@ -786,6 +789,7 @@ struct ma_node_base
ma_uint16consumedFrameCountIn;
/* These variables are read and written between different threads. */
volatilema_uint32readCounter;/* For loop prevention. Compared with the current read count of the node graph. If larger, means a loop was encountered and reading is aborted and no samples read. */
ma_node_output_bus_set_has_read(&pNodeBase->outputBuses[iOutputBus],MA_FALSE);/* <-- This is what tells the next calls to this function for other output buses for this time period to read from cache instead of pulling in more data. */
ma_node_output_bus_set_has_read(&pNodeBase->outputBuses[iOutputBus],MA_FALSE);/* <-- This is what tells the next calls to this function for other output buses for this time period to read from cache instead of pulling in more data. */
/* Once we've determined out destination pointer we can read. Note that we must inspect the number of frames read and fill any leftovers with silence for safety. */
/* We failed to read any data. To prevent any kind of corrupted audio from being output to the speakers we'll abort with the number of frames read being explicitly set to 0. */
*pFramesRead=0;
break;
}
/* We only need to read from input buses if there isn't already some data in the cache. */
if(pNodeBase->cachedFrameCountIn==0){
/* Here is where we pull in data from the input buses. This is what will trigger an advance in time. */
/* Once we've determined out destination pointer we can read. Note that we must inspect the number of frames read and fill any leftovers with silence for safety. */
/* We failed to read any data. To prevent any kind of corrupted audio from being output to the speakers we'll abort with the number of frames read being explicitly set to 0. */
*pFramesRead=0;
break;
/* Any leftover frames need to silenced for safety. */
At this point we have our input data so now we need to do some processing. Sneaky little
optimization here - we can set the pointer to the output buffer for this output bus so
that the final copy into the output buffer is done directly by onProcess().
*/
if(pFramesOut!=NULL){
ppFramesOut[outputBusIndex]=pFramesOut;
}
}
/*
At this point we have our input data so now we need to do some processing. Sneaky little
optimization here - we can set the pointer to the output buffer for this output bus so
that the final copy into the output buffer is done directly by onProcess().
*/
if(pFramesOut!=NULL){
ppFramesOut[outputBusIndex]=pFramesOut;
frameCountIn=pNodeBase->cachedFrameCountIn;/* Give the processing function as much input data as we've got. */
frameCountOut=framesToRead;/* Give the processing function the entire capacity of the output buffer. */
ma_node_process_pcm_frames_ex(pNode,ppFramesOut,&frameCountOut,(constfloat**)ppFramesIn,&frameCountIn);/* From GCC: expected 'const float **' but argument is of type 'float **'. Shouldn't this be implicit? Excplicit cast to silence the warning. */
/*
Thanks to our sneaky optimization above we don't need to do any data copying directly into
the output buffer - the onProcess() callback just did that for us. We do, however, need to
apply the number of input and output frames that were processed.
/* Getting here means the loop counter of the node is ahead of the graph which means we've hit a loop. */
result=MA_LOOP;/* So the caller knows to stop attempting to read more data. */
}
frameCountIn=pNodeBase->cachedFrameCountIn;/* Give the processing function as much input data as we've got. */
frameCountOut=framesToRead;/* Give the processing function the entire capacity of the output buffer. */
ma_node_process_pcm_frames_ex(pNode,ppFramesOut,&frameCountOut,(constfloat**)ppFramesIn,&frameCountIn);/* From GCC: expected 'const float **' but argument is of type 'float **'. Shouldn't this be implicit? Excplicit cast to silence the warning. */
/*
Thanks to our sneaky optimization above we don't need to do any data copying directly into
the output buffer - the onProcess() callback just did that for us. We do, however, need to
apply the number of input and output frames that were processed.
/* Adjust the volume of the splitter node's endpoints. We'll just do it 50/50 so that both of them combine to reproduce the original signal at the endpoint. */
@@ -108,11 +132,22 @@ int main(int argc, char** argv)
/* Adjust the volume of the splitter node's endpoints. We'll just do it 50/50 so that both of them combine to reproduce the original signal at the endpoint. */