updated MT tutorials

nix
Pierre Alexandre Tremblay 6 years ago
parent 6d1c149b66
commit 90dc29bc00

@ -4,17 +4,17 @@ CATEGORIES:: Libraries>FluidDecomposition
RELATED:: Guides/FluCoMa, Guides/FluidDecomposition, Guides/FluidBufMultiThreading
DESCRIPTION::
This class implements a simple tutorial object to illustrate and experiment with the behaviour of the FluidBuf* objects. It simply starts a process that will return a single value after a delay, during which it will just wait and do nothing. It is part of the Fluid Decomposition Toolkit of the FluCoMa project.footnote::This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899).::
This class implements a simple tutorial object to illustrate and experiment with the behaviour of the FluidBuf* objects. It is part of the Fluid Decomposition Toolkit of the FluCoMa project.footnote::This was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899).:: It simply starts a process that will, upon completion of its task, write a single value to the destination buffer. This is the general behaviour of much of the CPU consuming FluidBuf* objects. In this case, as a toy example, the task is simply just wait and do nothing, to simulate a task that would actually take that long in the background.
The process will return its delay lenght as the first index of the specified destination buffer.
The process will, after waiting for STRONG::time:: millisecond, return its delay lenght as a Float writen at index [0] of the specified destination buffer.
CLASSMETHODS::
METHOD:: process
This is the method that calls for the delay to be .
This is the method that calls for the job to be done. In this case, simply waiting STRONG::time:: millisecond before writing a value in the destination buffer.
ARGUMENT:: server
(describe argument here)
The server on which the destination buffer is declared.
ARGUMENT:: result
The destination buffer, where the value should be written at the end of the process.
@ -26,11 +26,11 @@ ARGUMENT:: action
A function that will be executed upon completion. It is passed the destination buffer as argument.
RETURNS::
Nothing, as the destination buffer is specified in the call.
The instance of FluidNRTProcess which can be used to cancel the job.
METHOD:: kr
This is the UGEN that is holding the link with the background thread. It is called by the 'process' method.
This is the UGen that is holding the link with the background thread. It is called by the 'process' method and can be used directly to monitor the progress of the job. For examples of such usage, please refer to the tutorial on link::Guides/FluidBufMultiThreading::.
ARGUMENT:: result
The destination buffer, where the value should be written at the end of the process.
@ -45,15 +45,6 @@ RETURNS::
It report the approximate job progress, from 0 to 1.
INSTANCEMETHODS::
METHOD:: cancel
This allows to cancel the process on the background thread safely.
RETURNS::
Nothing.
EXAMPLES::
For a thorough explanation, please refer to the tutorial on link::Guides/FluidBufMultiThreading::.
@ -65,10 +56,10 @@ b=Buffer.alloc(s,1);
// a simple call, where we query the destination buffer upon completion with the action message.
FluidBufThreadDemo.process(s, b, 1000, {|x|x.get(0,{|y|y.postln});});
// as the 'process' returns its location, we can cancel the process easily
// as the 'process' returns its parent UGen, we can cancel the process easily
c = FluidBufThreadDemo.process(s, b, 100000, {|x|x.get(0,{|y|y.postln});});
c.cancel
// if a simple call to the UGen is used, the progress can be monitored
{c = FluidBufThreadDemo.kr(b,10000, Done.freeSelf);}.scope;
{c = FluidBufThreadDemo.kr(b,10000, Done.freeSelf); Poll.kr(Impulse.kr(2),c);}.scope;
::

@ -6,13 +6,13 @@ RELATED:: Guides/FluCoMa, Guides/FluidDecomposition
DESCRIPTION::
The Fluid Decomposition toolbox footnote::This toolbox was made possible thanks to the FluCoMa project ( http://www.flucoma.org/ ) funded by the European Research Council ( https://erc.europa.eu/ ) under the European Unions Horizon 2020 research and innovation programme (grant agreement No 725899).:: provides an open-ended, loosely coupled set of objects to break up and analyse sound in terms of slices (segments in time), layers (superositions in time and frequency) and objects (configurable or discoverable patterns in sound). Almost all objects have audio-rate and buffer-based versions.
These latter buffer-based processes can be very CPU intensive, and therefore require a careful consideration of the underlying architecture. Luckily, the FluidBuf* have different entry points, from transparent usage to more advanced control, to allow the creative coder to care as much as they need too.
These latter buffer-based processes can be very CPU intensive, and therefore require a careful consideration of the underlying architecture. Luckily, the FluidBuf* have different entry points, from transparent usage to more advanced control, to allow the creative coder to care as much as they need too. The overarching principle is to send the CPU intensive tasks to their own background thread, to avoid blocking the Server and its Non-Real Time thread, whilst providing ways to cancel the tasks and monitor their progress.
subsection:: Basic Usage
In SuperCollider, the server will delegate to a second, slow, non-real-time thread, tasks that are potentially too long for the real-time server, for instance, loading a soundfile to a buffer. This process is explained HERE and HERE, and for the inquisitive mind, in chapter XX of the SuperCollider book.
In SuperCollider, the server will delegate to a second, non-real-time thread, tasks that are potentially too long for the real-time server, for instance, loading a soundfile to a buffer. This process is explained HERE and HERE, and for the inquisitive mind, in chapter XX of the SuperCollider book.
The problem with the FluidBuf* tasks is that they are very, very much longer than any of these native tasks, so we have to send them in their own thread in order to leave both real-time and non-real-time native server threads alone and responsive.
The problem with the FluidBuf* tasks is that they are much longer than any of these native tasks, so we have to send them in their own thread in order to leave both real-time and non-real-time native server threads alone and responsive.
The first approach, the simplest, is therefore to call the 'process' method on the FluidBuf* objects. For this tutorial, we will use a dummy class, LINK::Classes/FluidBufThreadDemo::, which in effects does nothing but to wait on that new thread before sending back one value in a buffer.
@ -28,7 +28,7 @@ This will print 1000 in the Post window. But actually, this is what is happening
NUMBEREDLIST::
## The class will check the arguments' validity
## It will send the job to a new thread (in this case, doing nothing but waiting for 1000 ms, then writing that number to the first index of a destination buffer)
## It will send the job to a new thread (in this case, doing nothing but waiting for 1000 ms, then writing that number to index [0] of a destination buffer)
## It will received an acknoledgment of the job being done
## It will call the user-defined function with the destination buffer as argument. In this case, we send it to a function get which prints the value of index 0.
::
@ -36,7 +36,7 @@ Actually, what is really happening is going to be discussed below, but this shou
subsection:: Cancelling
The 'process' method returns a pointer to the task running in background, which allows to cancel the job. To allow that, one must capture the returned task ID.
The 'process' method returns an instance of FluidNRTProcess, which is a pointer to a Synth running in the background. This allows us to cancel the job.
CODE::
//start a long process, capturing the instance of the process
@ -46,57 +46,56 @@ c = FluidBufThreadDemo.process(s, b, 100000, {|x|x.get(0,{|y|y.postln});});
c.cancel;
//////////////////////////////
////// FOR GERARD AND OWEN: we are still getting a call to the done function, which would be good to avoid
////// FOR GERARD AND OWEN: we are still getting a call to the done function, which would be good to avoid. We can comment on this here when it is sorted.
//////////////////////////////
::
subsection:: KR Usage
If we look at the class definition, we will see that the 'process' method is actually calling a temporary Synth which connects to the process on the new thread. We can also call this method directly, in order to get a feedback on how the process is going.
If we look at the class definition, we will see that the 'process' method is actually calling a temporary Synth which spawns a thread with the job defined by the specific UGen. We can also call this specify UGen directly, in order to get a feedback on how the process is going.
CODE::
CODE::
// if a simple call to the UGen is used, the progress can be monitored
{FluidBufThreadDemo.kr(b,10000, Done.freeSelf);}.scope;
//or polled within a synth
{FluidBufThreadDemo.kr(b,3000).poll;SinOsc.ar(110,0,0.1)}.play;
//////////////////////////////
////// FOR GERARD AND OWEN: here the KR out is never 1 nor never stops... let's see
//////////////////////////////
{FluidBufThreadDemo.kr(b,3000).poll}.play;
//or its value used to control other processes, here changing the pitch, whilst being polled to the window twice per second
{SinOsc.ar(Poll.kr(Impulse.kr(2),FluidBufThreadDemo.kr(b,3000)).exprange(110,220),0,0.1)}.play;
::
STRONG::FOR GERARD AND OWEN:::
To cancel the job that way, we just free the synth, and the background thread will be killed.
To cancel, we now have to do some complicated magic like explained here:
CODE::
// load a buffer, declare a destination, and make a control bus to monitor the work
(
b = Buffer.read(s,File.realpath(FluidBufNMF.class.filenameSymbol).dirname.withTrailingSlash ++ "../AudioFiles/Tremblay-AaS-SynthTwoVoices-M.wav");
c = Buffer.new(s);
d = Bus.control(s,1);
)
CODE::
a = {c = FluidBufThreadDemo.kr(b,50000,doneAction:Done.freeSelf)}.scope
c.server = Server.default;
c.synth = a;
c.cancel
::
// start a very long job
e = {Out.kr(d,FluidBufNMF.kr(b, destination:c, components:50, iterations:1000, windowSize:8192, hopSize:256))}.play
it would be better to do something more transparent like managing to inherit c.synth and c.server from the call and do just:
// make a dummy synth to look at the progress
f = {In.kr(d).poll}.play
CODE::
{c = FluidBufThreadDemo.kr(b,10000,doneAction:Done.freeSelf)}.scope
c.cancel
::
// stop the monitoring
f.free
Owen said:
EMPHASIS::Synth definitely stores its server as a property, by dint of inheriting from Node. Would suggest that, barring any cleverer ideas, cancel() can take the synth as an argument; if its nil, the instance will try and fallback on its own stored synth variable and if thats nil, an error happens. :: but what does Gerard the Wise think?
//make a slighly more useful use of the progress
f = {SinOsc.ar(In.kr(d).poll.exprange(110,880),0,0.1)}.play
NEXT IN LINE for PA to write:
show the interruption again, but in a still working synth, mapping the KR out to a pitch gliss for instance.
//kill the process
e.free
//kill the synth
f.free
subsection:: A Musical Example: FluidBufNMF
//to appreciate the multithreading, use your favourite CPU monitoring application. scsynth will be very, very high, despite the peakCPU and avgCPU being very low.
::
just for shits and giggles, I'll port something here.
subsection: Further Reading

Loading…
Cancel
Save