492 lines
11 KiB
Racket
492 lines
11 KiB
Racket
#lang scribble/manual
|
|
@(require (for-label racket))
|
|
|
|
@title{Routine}
|
|
@section{categories}
|
|
Core>Kernel
|
|
Functions that can return in the middle and then resume where they left off@section{related}
|
|
Classes/Stream
|
|
|
|
@section{description}
|
|
|
|
A Routine runs a link::Classes/Function:: and allows it to be suspended in the middle
|
|
and be resumed again where it left off. This functionality is supported by the Routine's
|
|
superclass link::Classes/Thread::. Effectively, Routines can be used to implement
|
|
co-routines as found in Scheme and some other languages.
|
|
|
|
A Routine is strong::started:: the first time link::#-next:: is called, which will run
|
|
the Function from the beginning. It is strong::suspended:: when it "yields"
|
|
(using link::Classes/Object#-yield:: within the Function), and then strong::resumed::
|
|
using link::#-next:: again. When the Function returns, the Routine is considered
|
|
strong::stopped::, and calling link::#-next:: will have no effect - unless the Routine is
|
|
strong::reset:: using link::#-reset::, which will rewind the Function to the beginning.
|
|
You can stop a Routine before its Function returns using link::#-stop::.
|
|
|
|
When a Routine is strong::scheduled:: on a link::Classes/Clock:: (e.g. using
|
|
link::#-play::), it will be started or resumed at the scheduled time. The value yielded
|
|
by the Routine will be used as the time difference for rescheduling the Routine. (See
|
|
link::#-awake::).
|
|
|
|
Since Routine inherits from link::Classes/Thread::, it has its own associated
|
|
link::Classes/Thread#-beats#logical time::, etc. When a Routine is started or
|
|
resumed, it becomes the link::Classes/Thread#.thisThread#current thread::.
|
|
|
|
Routine also inherits from link::Classes/Stream::, and thus shares its ability to be
|
|
combined using math operations and "filtered".
|
|
|
|
|
|
@section{classMethods}
|
|
|
|
|
|
@section{method}
|
|
new
|
|
|
|
Creates an instance of Routine, passing it the Function with code to run.
|
|
|
|
@section{argument}
|
|
func
|
|
A Function with code for the Thread to run.
|
|
|
|
@section{argument}
|
|
stackSize
|
|
Call stack size (an Integer).
|
|
|
|
@section{discussion}
|
|
|
|
|
|
@racketblock[
|
|
a = Routine.new({ 1.yield; 2.yield; });
|
|
a.next.postln;
|
|
a.next.postln;
|
|
a.next.postln;
|
|
::
|
|
|
|
]
|
|
@section{instanceMethods}
|
|
|
|
|
|
@section{method}
|
|
next
|
|
|
|
This method performs differently according to the Routine's state:
|
|
@section{list}
|
|
|
|
## Starts the Routine, if it has not been started yet or it has been
|
|
link::#-reset#reset::; i.e runs its Function from the beginning, passing on the
|
|
|
|
@racketblock[inval:: argument.
|
|
## Resumes the Routine, if it has been suspended (it has yielded); i.e. resumes its
|
|
Function from the point where link::Classes/Object#-yield#yield:: was called on an Object,
|
|
passing the ]
|
|
|
|
@racketblock[inval:: argument as the return value of ]
|
|
|
|
@racketblock[yield::.
|
|
## Does nothing if the Routine has stopped (because its Function has returned, or
|
|
link::#-stop:: has been called).
|
|
::
|
|
|
|
Since Routine inherits from link::Classes/Thread::, it will become the
|
|
emphasis::current thread:: when it is started or resumed; i.e.
|
|
link::Classes/Thread#.thisThread#thisThread:: used in the Routine Function will return
|
|
the Routine. It will inherit the parent thread's logical time and clock
|
|
(see link::Classes/Thread#-parent::).
|
|
|
|
Synonyms for ]
|
|
|
|
@racketblock[next:: are link::#-value:: and link::#-resume::.
|
|
|
|
]
|
|
@section{returns}
|
|
|
|
@section{list}
|
|
|
|
## Either the value that the Routine yields (the Object on which
|
|
link::Classes/Object#-yield#yield:: is called within the Routine Function),
|
|
## ...or
|
|
@racketblock[nil::, if the Routine has stopped.
|
|
::
|
|
|
|
]
|
|
@section{discussion}
|
|
|
|
|
|
When a Routine is started by a call to this method (or one of its synonyms), the method's
|
|
argument is passed on as the argument to the Routine Function:
|
|
|
|
|
|
@racketblock[
|
|
Routine { arg inval;
|
|
inval.postln;
|
|
}.value("hello routine");
|
|
::
|
|
|
|
After the Routine has yielded (it has been suspended at the point in its Function where
|
|
]
|
|
|
|
@racketblock[yield:: is called on an Object), a call to this method (or its synonyms) resumes
|
|
executing the Function and the argument to this method becomes the return value of
|
|
]
|
|
|
|
@racketblock[yield::. To access that value within the Function, you have to assign it to a
|
|
variable - typically, the argument of the Function is reused:
|
|
|
|
]
|
|
|
|
@racketblock[
|
|
(
|
|
r = Routine { arg inval;
|
|
inval.postln;
|
|
inval = 123.yield;
|
|
inval.postln;
|
|
}
|
|
)
|
|
|
|
r.value("hello routine");
|
|
r.value("goodbye routine");
|
|
::
|
|
|
|
Typically, a Routine yields multiple times, and each time the result of the yield is
|
|
reassigning to the argument of its Function.
|
|
|
|
]
|
|
|
|
@racketblock[
|
|
(
|
|
r = Routine { arg inval;
|
|
inval.postln; // Post the value passed in when started.
|
|
5.do { arg i;
|
|
inval = (i + 10).yield;
|
|
inval.postln; // Post the value passed in when resumed.
|
|
}
|
|
}
|
|
)
|
|
(
|
|
5.do {
|
|
r.value("hello routine").postln; // Post the value that the Routine yields.
|
|
}
|
|
)
|
|
::
|
|
|
|
]
|
|
@section{method}
|
|
value
|
|
|
|
Equivalent to link::#-next::.
|
|
|
|
@section{method}
|
|
resume
|
|
|
|
Equivalent to link::#-next::.
|
|
|
|
@section{method}
|
|
stop
|
|
|
|
Equivalent to the Routine Function reaching its end or returning: after this, the Routine
|
|
will never run again (the link::#-next:: method has no effect and returns
|
|
@racketblock[nil::),
|
|
unless link::#-reset:: is called.
|
|
|
|
]
|
|
@section{method}
|
|
reset
|
|
|
|
Causes the Routine to start from the beginning next time link::#-next:: is called.
|
|
|
|
@section{discussion}
|
|
|
|
|
|
If a Routine is stopped (its Function has returned or link::#-stop:: has been called), it
|
|
will never run again (the link::#-next:: method has no effect and returns
|
|
@racketblock[nil::),
|
|
unless this method is called.
|
|
|
|
A Routine cannot reset itself, except by calling link::Classes/Object#-yieldAndReset::.
|
|
|
|
See also: link::Classes/Object#-yield::, link::Classes/Object#-alwaysYield::
|
|
|
|
|
|
]
|
|
@section{method}
|
|
play
|
|
|
|
In the SuperCollider application, a Routine can be played using a link::Classes/Clock::, as can any link::Classes/Stream::.
|
|
every time the Routine yields, it should do so with a float, the clock will interpret that, usually
|
|
pausing for that many seconds, and then resume the routine, passing it the clock's current time.
|
|
|
|
@section{argument}
|
|
clock
|
|
a Clock, TempoClock by default
|
|
|
|
@section{argument}
|
|
quant
|
|
see the link::Classes/Quant:: helpfile
|
|
|
|
@section{discussion}
|
|
|
|
using link::Classes/Object#idle#Object:idle:: within a routine, return values until this time is over. Time is measured relative to the thread's clock.
|
|
|
|
@racketblock[
|
|
// for 6 seconds, return 200, then continue
|
|
(
|
|
r = Routine {
|
|
199.yield;
|
|
189.yield;
|
|
200.idle(6);
|
|
199.yield;
|
|
189.yield;
|
|
};
|
|
|
|
fork {
|
|
loop {
|
|
r.value.postln;
|
|
1.wait;
|
|
}
|
|
}
|
|
);
|
|
|
|
// the value can also be a stream or a function
|
|
(
|
|
r = Routine {
|
|
199.yield;
|
|
189.yield;
|
|
Routine { 100.do { |i| i.yield } }.idle(6);
|
|
199.yield;
|
|
189.yield;
|
|
};
|
|
|
|
fork {
|
|
loop {
|
|
r.value.postln;
|
|
1.wait;
|
|
}
|
|
}
|
|
);
|
|
::
|
|
|
|
]
|
|
@section{method}
|
|
awake
|
|
|
|
This method is called by a link::Classes/Clock:: on which the Routine was scheduled
|
|
when its scheduling time is up. It calls link::#-next::, passing on the scheduling
|
|
time in beats as an argument. The value returned by
|
|
@racketblock[next:: (the value yielded
|
|
by the Routine) will in turn be returned by this method, thus determining the time
|
|
which the Routine will be rescheduled for.
|
|
|
|
]
|
|
@section{argument}
|
|
inBeats
|
|
The scheduling time in beats. This is equal to the current logical time
|
|
(link::Classes/Thread#-beats::).
|
|
|
|
@section{argument}
|
|
inSeconds
|
|
The scheduling time in seconds. This is equal to the current logical time
|
|
(link::Classes/Thread#-seconds::).
|
|
|
|
@section{argument}
|
|
inClock
|
|
The clock which awoke the Routine.
|
|
|
|
|
|
|
|
@section{subsection}
|
|
Accessible instance variables
|
|
|
|
Routine inherits from link::Classes/Thread::, which allows access to some of its state:
|
|
|
|
|
|
@racketblock[
|
|
(
|
|
r = Routine { arg inval;
|
|
loop {
|
|
// thisThread refers to the routine.
|
|
postf("beats: % seconds: % time: % \n",
|
|
thisThread.beats, thisThread.seconds, Main.elapsedTime
|
|
);
|
|
1.0.yield;
|
|
|
|
}
|
|
}.play;
|
|
)
|
|
|
|
r.stop;
|
|
r.beats;
|
|
r.seconds;
|
|
r.clock;
|
|
::
|
|
|
|
]
|
|
@section{method}
|
|
beats
|
|
|
|
@section{returns}
|
|
The elapsed beats (logical time) of the routine. The beats do not proceed when the routine is not playing.
|
|
|
|
@section{method}
|
|
seconds
|
|
|
|
@section{returns}
|
|
The elapsed seconds (logical time) of the routine. The seconds do not proceed when the routine is not playing, it is the converted beat value.
|
|
|
|
@section{method}
|
|
clock
|
|
|
|
@section{returns}
|
|
The thread's clock. If it has not played, it is the SystemClock.
|
|
|
|
@section{examples}
|
|
|
|
|
|
|
|
@racketblock[
|
|
(
|
|
var r, outval;
|
|
r = Routine.new({ arg inval;
|
|
("->inval was " ++ inval).postln;
|
|
inval = 1.yield;
|
|
("->inval was " ++ inval).postln;
|
|
inval = 2.yield;
|
|
("->inval was " ++ inval).postln;
|
|
inval = 99.yield;
|
|
});
|
|
|
|
outval = r.next('a');
|
|
("<-outval was " ++ outval).postln;
|
|
outval = r.next('b');
|
|
("<-outval was " ++ outval).postln;
|
|
r.reset; "reset".postln;
|
|
outval = r.next('c');
|
|
("<-outval was " ++ outval).postln;
|
|
outval = r.next('d');
|
|
("<-outval was " ++ outval).postln;
|
|
outval = r.next('e');
|
|
("<-outval was " ++ outval).postln;
|
|
outval = r.next('f');
|
|
("<-outval was " ++ outval).postln;
|
|
)
|
|
::
|
|
|
|
]
|
|
|
|
@racketblock[
|
|
// wait
|
|
|
|
(
|
|
var r;
|
|
r = Routine {
|
|
10.do({ arg a;
|
|
a.postln;
|
|
// Often you might see Wait being used to pause a routine
|
|
// This waits for one second between each number
|
|
1.wait;
|
|
});
|
|
// Wait half second before saying we're done
|
|
0.5.wait;
|
|
"done".postln;
|
|
}.play;
|
|
)
|
|
::
|
|
|
|
]
|
|
|
|
@racketblock[
|
|
// waitUntil
|
|
|
|
(
|
|
var r;
|
|
r = Routine {
|
|
var times = { rrand(1.0, 10.0) }.dup(10) + thisThread.beats;
|
|
times = times.sort;
|
|
times.do({ arg a;
|
|
waitUntil(a);
|
|
a.postln;
|
|
});
|
|
// Wait half second before saying we're done
|
|
0.5.wait;
|
|
"done".postln;
|
|
}.play;
|
|
)
|
|
::
|
|
|
|
]
|
|
|
|
@racketblock[
|
|
// Using Routine to set button states on the fly.
|
|
(
|
|
var update, w, b;
|
|
w = SCWindow.new("State Window", Rect(150,SCWindow.screenBounds.height-140,380,60));
|
|
|
|
// a convenient way to set the button label
|
|
update = {
|
|
|but, string| but.states = [[string.asString, Color.black, Color.red]];
|
|
but.refresh;
|
|
};
|
|
|
|
b = SCButton(w, Rect(10,10,360,40));
|
|
b.font_(Font("Impact", 24));
|
|
|
|
update.value(b, "there is only one state");
|
|
|
|
// if an action should do something different each time it is called, a routine is the
|
|
// right thing to use. This is better than creating variables outside and setting them
|
|
// from the action function to keep state from one action to the next
|
|
|
|
b.action_(Routine { |butt|
|
|
rrand(15, 45).do { |i|
|
|
update.value(butt, "%. there is still only 1 state".format(i + 2));
|
|
0.yield; // stop here
|
|
};
|
|
w.close;
|
|
});
|
|
|
|
w.front;
|
|
)
|
|
::
|
|
|
|
]
|
|
|
|
@racketblock[
|
|
// drawing in a window dynamically with Pen
|
|
(
|
|
var w, much = 0.02, string, synth;
|
|
|
|
w = Window.new("swing", Rect(100, 100, 300, 500)).front;
|
|
w.view.background_(Color.new255(153, 255, 102).vary);
|
|
|
|
string = "swing ".dup(24).join;
|
|
|
|
w.drawFunc = Routine {
|
|
var i = 0;
|
|
var size = 40;
|
|
var func = { |i, j| sin(i * 0.07 + (j * 0.0023) + 1.5pi) * much + 1 };
|
|
var scale;
|
|
Pen.font = Font("Helvetica-Bold", 40);
|
|
loop {
|
|
i = i + 1;
|
|
string.do { |char, j|
|
|
|
|
scale = func.value(i, j).dup(6);
|
|
|
|
Pen.fillColor = Color.new255(0, 120, 120).vary;
|
|
Pen.matrix = scale * #[1, 0, 0, 1, 1, 0];
|
|
Pen.stringAtPoint(char.asString,
|
|
((size * (j % 9)) - 10) @ (size * (j div: 9))
|
|
);
|
|
};
|
|
0.yield // stop here, return something unimportant
|
|
}
|
|
};
|
|
|
|
fork { while { w.isClosed.not } { defer { w.refresh }; 0.04.wait; } };
|
|
|
|
w.front;
|
|
|
|
)
|
|
::
|
|
|
|
]
|
|
|
|
|