SCDraw score of timed drawing using Pen
SCDraw is intended for the non-realtime realization of procedural animation. It draws and writes out an
image sequence with user-designed drawing functions. It is partially modelled on Score. A "score" is
an array of events like the following:
[
[ circle, 0.0, 2.0, \x, 100.0, \y, 100, \r, 100.0 ],
[ square, 2.0, 2.0, \x, 100.0, \y, 100, \size, 100.0 ],
[ pict, 3.0, 2.0, \x, 100.0, \y, 100, \size, Env([ 100.0, 0.0 ], [1.0]) ]
]
The first element in each event is the name of a function that you create. The next two numbers are the
starttime and duration of an event. These three are required. Optional arguments follow in symbol, value
pairs. They can be named whatever you like. Because SCDraw does everything on the lang side, the values
can be anything, like an Env or a Tendency. The arguments are gathered together in a Dictionary and passed
to the drawing function. This dictionary also includes the startTime and duration of the event. The keys for
those are \start and \duration. The score needs to be ordered by startTime. If it isn't not, out of sequence events
will not happen.
The drawing functions can use any or all Pen methods. Drawing methods specific to SCImage can also be
used. The functions are always passed a float that represents the elapsed percentage of an event's duration.
Over the course of an event, this value goes from 0.0 to 1.0 (of course). The function will also be passed a
dictionary that has all the arguments from the relevant event in the score, including the start and duration values.
There is a built-in "instrument" called \noRefresh which while it is active inhibits the refreshing of the drawing
every frame. Use the symbol identifier in the event to use it. See the example below.
SCDraw does have a realtime preview to allow for a faster workflow.
Class Methods
*new(list, start, end, rate, sort, frameMode, onCompletion) - returns a SCDraw object with supplied list processed for the given rate, which
defaults to 25.0. sort defaults to true. start and end define a subsection of the score, default is the entire score. If frameMode
is true, then starttimes and durations are assumed to be expressed in frames, i.e., 30 frames instread of 1 second, if rate is 30.
onCompletion is a function without arguments that will be evaluated when either preview or render is complete.
*preview(list, start, end, rate, width, height, color, sort, frameMode, onCompletion) - runs the score in realtime. width and height default to 500.
color defaults to Color.black. sort defaults to true. start and end define the section to be previewed, normally the
entire score. onCompletion is a function without arguments that will be evaluated when the preview is complete.
*render(path, list, start, end, rate, width, height, color, ext, sort, frameMode, prependZeroes, onCompletion) - render the image sequence. path should
include base filename which will then have a frame number appended with ext appended after that. For
example, "~/folder/mySeq" will yield "~/folder/mySeq_1.png" and so on. ext is the file extension and defaults
to "png". The available choices are "tiff", "png", "gif", "jpeg", and "bmp". The dot before the extension
does not have to included; that is taken care of. The other arguments default the same as *preview.
start and end define the section to be rendered, normally the entire score. onCompletion is a function
without arguments that will be evaluated when the render is complete. prependZeroes specifies whether leading
zeroes will be added to the file names; e.g., file_0001.png vs file_1.png.
Instance Methods
preview(width, height, color) - same as the class method.
render(path, width, height, color, ext) - same as the class method.
Examples
(
var score, circle, square, bkgrnd, image, pict;
image = SCImage.open("/Library/Desktop Pictures/Ripples Blue.jpg");
circle = { arg perc, dict;
var x, y, r;
x = dict.at(\x);
if(x.class == Env, { x = x.at(perc); });
y = dict.at(\y);
if(y.class == Env, { y = y.at(perc); });
r = dict.at(\r);
if(r.class == Env, { r = r.at(perc); });
Pen.fillColor = Color.blue;
Pen.strokeColor = Color.green;
Pen.addArc(x@y, r, 0.0, 2pi);
Pen.fillStroke;
};
square = { arg perc, dict;
var x, y, r;
x = dict.at(\x);
if(x.class == Env, { x = x.at(perc); });
y = dict.at(\y);
if(y.class == Env, { y = y.at(perc); });
r = dict.at(\r);
if(r.class == Env, { r = r.at(perc); });
Pen.fillColor = Color.blue;
Pen.strokeColor = Color.green;
Pen.addRect(Rect(x-r+rrand(-50.0, 50.0), y-r+rrand(-50.0, 50.0), 2*r, 2*r));
Pen.stroke;
};
bkgrnd = { arg perc, dict;
image.drawInRect(Rect(0, 0, 500, 500), image.bounds, fraction: 1.0);
};
pict = { arg perc, dict;
var x, y, rx, ry;
x = dict.at(\x);
if(x.class == Env, { x = x.at(perc); });
y = dict.at(\y);
if(y.class == Env, { y = y.at(perc); });
rx = dict.at(\rx);
if(rx.class == Env, { rx = rx.at(perc); });
ry = dict.at(\ry);
if(ry.class == Env, { ry = ry.at(perc); });
image.drawInRect(Rect(x-rx, y-ry, 2*rx, 2*ry), image.bounds);
};
score = [ [ bkgrnd, 0.0, 3.0],
[ circle, 0.1, 2.9, \x, 100.0, \y, 100.0, \r, Env([ 150.0, 25 ], [1.0]) ],
[ circle, 0.2, 2.8, \x, 25.0, \y, Env([ 50.0, 250], [1.0]), \r, 10.0 ],
[ circle, 0.3, 2.7, \x, 150.0, \y, Env([ 250.0, 50], [1.0]), \r, Env([ 150.0, 25 ], [1.0]) ],
[ circle, 0.3, 2.7, \x, Env([ 50.0, 250], [1.0]), \y, 150.0, \r, 34.0 ],
[ circle, 0.4, 2.6, \x, Env([ 50.0, 250], [1.0]), \y, Env([ 50.0, 250], [1.0]), \r, 30.0 ],
[ pict, 3.0, 3.0, \x, Env([ 50.0, 450.0, 50.0 ], [ 0.5, 0.5 ]), \y, Env([ 50.0, 450.0, 50.0 ], [ 0.5, 0.5 ]),
\rx, Env([ 250.0, 25 ], [1.0]), \ry, Env([ 250.0, 25 ], [1.0]) ],
[ pict, 6.0, 3.0, \x, 250.0, \y, 250.0, \rx, Env([ 250.0, 25 ], [1.0]), \ry, Env([ 25.0, 250.0 ], [1.0]) ],
[ \noRefresh, 9.0, 2.0 ],
[ square, 9.0, 4.0, \x, 250.0, \y, 250.0, \r, Env([ 250.0, 25.0, 250.0 ], [ 0.5, 0.5]) ],
[ square, 13.0, 4.0, \x, 250.0, \y, 250.0, \r, Env([ 250.0, 25.0, 250.0 ], [ 0.5, 0.5]) ],
[ \noRefresh, 15.0, 2.0 ]
];
SCDraw.preview(score);
//SCDraw.render("~/Desktop/sequence/image".standardizePath, score);
)
A little study to illustrate using both sound and images.
Don't forget to boot the local server.
(
var server, cdraw, csynth, circle, makeEvents, maxdur;
server = Server.local;
SynthDef("pluck", { arg dur, amp, freq, pan;
var sig, env;
env = EnvGen.kr(Env([ 1, 0 ], [ dur ], 'linear'), 1, amp, 0, 1, 2);
sig = Pluck.ar(WhiteNoise.ar(1.0), Impulse.kr(0), freq.reciprocal, freq.reciprocal, dur, 0.25);
sig = Pan2.ar(sig, pan);
Out.ar(0, sig*env);
}).load(server);
circle = { arg perc, dict;
var x, y, r;
x = dict.at(\x);
if(x.class == Env, { x = x.at(perc); });
y = dict.at(\y);
if(y.class == Env, { y = y.at(perc); });
r = dict.at(\r);
if(r.class == Env, { r = r.at(perc); });
Pen.fillColor = Color.white;
Pen.strokeColor = Color.white;
Pen.addArc(x@y, r, 0.0, 2pi);
Pen.fill;
r = dict.at(\r);
if(r.class == Env, { r = r.at(perc*perc); });
Pen.addArc(x@y, r, 0.0, 2pi);
Pen.stroke;
};
csynth = [];
cdraw = [];
maxdur = 0.0;
makeEvents = { arg start, duration, dur, rhythm, hori, vert, front;
var x, y, z, rhy, perc, ndur;
var now = 0.0;
while { now < duration } {
perc = now/duration;
if(hori.isNumber, { x = hori; }, { x = hori.at(perc); });
if(vert.isNumber, { y = vert; }, { y = vert.at(perc); });
if(front.isNumber, { z = front; }, { z = front.at(perc); });
if(rhythm.isNumber, { rhy = rhythm; }, { rhy = rhythm.at(perc); });
if(dur.isNumber, { ndur = dur; }, { ndur = dur.at(perc); });
csynth = csynth.add([ now+start, [\s_new, \pluck, -1, 0, 1, \dur, ndur, \amp, z, \freq, (y*24+60).midicps, \pan, x ] ]);
cdraw = cdraw.add([ circle, now+start, ndur, \x, x*200.0+250.0, \y, y*200.0.neg+250.0, \r, Env([ z*50.0, 0.0 ], [1.0], -10) ]);
maxdur = maxdur.max(now+start+ndur);
now = (now + rhy).round(0.04);
}
};
makeEvents.value(1.0, 9.0, Tendency(2.0, 1.0), Tendency(0.25, 0.1), Tendency(Env([0.1, 1.0],[1]), Env([-0.1, -1.0],[1])),
Tendency(0.01, -0.01), Env([0.1, 1.0],[1]));
makeEvents.value(10.0, 10.0, Tendency(2.0, 1.0), Tendency(0.25, 0.1), Tendency(0.01, -0.01), Tendency(Env([0.1, 1.0],[1]), Env([-0.1, -1.0],[1])),
Env([1.0, 0.1],[1]));
makeEvents.value(21.0, 6.0, Env([ 2.0, 1.0 ],[ 1.0 ]), Tendency(Env([0.3, 0.1],[1]), Env([0.2, 0.05],[1])), Env([-1, 1], [1]),
Tendency(0.01, -0.01), Tendency(0.2, 0.1));
makeEvents.value(27.0, 6.0, Env([ 1.0, 5.0 ],[ 1.0 ]), Tendency(0.1, 0.05), Env([1, -1], [1]), Tendency(Env([0.01, 1.0],[1]),
Env([-0.01, -1.0],[1])), Tendency(Env([0.2, 0.75 ], [1]), 0.1));
makeEvents.value(33.0, 10.0, 7.0, Tendency(0.2, 0.05), Tendency(1.0, -1.0), Tendency(-0.48, -0.52), Tendency(Env([0.02, 0.1, 0.02],[0.5, 0.5]), 0.01));
makeEvents.value(37.0, 7.0, 7.0, Tendency(0.2, 0.05), Tendency(0.52, 0.48), Tendency(1.0, -1.0), Tendency(Env([0.02, 0.1, 0.02],[0.5, 0.5]), 0.01));
makeEvents.value(40.0, 5.0, 7.0, Tendency(0.2, 0.05), Tendency(0.48, -1.0), Tendency(0.52, 0.48), Tendency(Env([0.02, 0.1, 0.02],[0.5, 0.5]), 0.01));
makeEvents.value(45.0, 10.0, Env([ 1.0, 5.0 ],[ 1.0 ], 5), Tendency(Env([ 0.08, 1.0 ], [ 1.0 ], 5), Env([ 0.05, 0.96 ], [ 1.0 ], 5)),
Tendency(Env([0.05, 1.0],[1]), Env([-0.05, -1.0],[1])), Tendency(Env([0.05, 1.0],[1]), Env([-0.05, -1.0],[1])),
Tendency(Env([0.1, 1.0 ],[1]), Env([0.05, 0.7],[1])));
csynth = csynth.add([ maxdur, [\c_set, 0, 0] ]);
cdraw = cdraw.add([ nil, maxdur, 0 ]); //optional
Score.play(csynth, server);
//check your paths!
//Score.recordNRT(csynth.sort({ arg a, b; a[0] < b[0]; }), "help-oscFile", "~/Desktop/scdraw/study.aiff".standardizePath,
// options: ServerOptions.new.numOutputBusChannels_(2));
SCDraw.preview(cdraw);
//SCDraw.render("~/Desktop/scdraw/sequence/study".standardizePath, cdraw);
)
Same as first example except with frameMode = true.
(
var score, circle, square, bkgrnd, image, pict;
image = SCImage.open("/Library/Desktop Pictures/Ripples Blue.jpg");
circle = { arg perc, dict;
var x, y, r;
x = dict.at(\x);
if(x.class == Env, { x = x.at(perc); });
y = dict.at(\y);
if(y.class == Env, { y = y.at(perc); });
r = dict.at(\r);
if(r.class == Env, { r = r.at(perc); });
Pen.fillColor = Color.blue;
Pen.strokeColor = Color.green;
Pen.addArc(x@y, r, 0.0, 2pi);
Pen.fillStroke;
};
square = { arg perc, dict;
var x, y, r;
x = dict.at(\x);
if(x.class == Env, { x = x.at(perc); });
y = dict.at(\y);
if(y.class == Env, { y = y.at(perc); });
r = dict.at(\r);
if(r.class == Env, { r = r.at(perc); });
Pen.fillColor = Color.blue;
Pen.strokeColor = Color.green;
Pen.addRect(Rect(x-r+rrand(-50.0, 50.0), y-r+rrand(-50.0, 50.0), 2*r, 2*r));
Pen.stroke;
};
bkgrnd = { arg perc, dict;
image.drawInRect(Rect(0, 0, 500, 500), image.bounds, fraction: 1.0);
};
pict = { arg perc, dict;
var x, y, rx, ry;
x = dict.at(\x);
if(x.class == Env, { x = x.at(perc); });
y = dict.at(\y);
if(y.class == Env, { y = y.at(perc); });
rx = dict.at(\rx);
if(rx.class == Env, { rx = rx.at(perc); });
ry = dict.at(\ry);
if(ry.class == Env, { ry = ry.at(perc); });
image.drawInRect(Rect(x-rx, y-ry, 2*rx, 2*ry), image.bounds);
};
score = [ [ bkgrnd, 0, 75],
[ circle, 1, 74, \x, 100.0, \y, 100.0, \r, Env([ 150.0, 25 ], [1.0]) ],
[ circle, 2, 73, \x, 25.0, \y, Env([ 50.0, 250], [1.0]), \r, 10.0 ],
[ circle, 3, 72, \x, 150.0, \y, Env([ 250.0, 50], [1.0]), \r, Env([ 150.0, 25 ], [1.0]) ],
[ circle, 4, 71, \x, Env([ 50.0, 250], [1.0]), \y, 150.0, \r, 34.0 ],
[ circle, 5, 70, \x, Env([ 50.0, 250], [1.0]), \y, Env([ 50.0, 250], [1.0]), \r, 30.0 ],
[ pict, 75, 75, \x, Env([ 50.0, 450.0, 50.0 ], [ 0.5, 0.5 ]), \y, Env([ 50.0, 450.0, 50.0 ], [ 0.5, 0.5 ]),
\rx, Env([ 250.0, 25 ], [1.0]), \ry, Env([ 250.0, 25 ], [1.0]) ],
[ pict, 150, 75, \x, 250.0, \y, 250.0, \rx, Env([ 250.0, 25 ], [1.0]), \ry, Env([ 25.0, 250.0 ], [1.0]) ],
[ \noRefresh, 225, 50 ],
[ square, 225, 100, \x, 250.0, \y, 250.0, \r, Env([ 250.0, 25.0, 250.0 ], [ 0.5, 0.5]) ],
[ square, 325, 100, \x, 250.0, \y, 250.0, \r, Env([ 250.0, 25.0, 250.0 ], [ 0.5, 0.5]) ],
[ \noRefresh, 375, 50 ]
];
SCDraw.preview(score, frameMode: true);
//SCDraw.render("~/Desktop/sequence/image".standardizePath, score, frameMode: true);
)