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);

)