4.2 Graphical User Interfaces
4.2 Graphical User Interfaces
Server.default=s=Server.local;
Graphical user interface capabilities in SC let us build frontends for our computer
music projects. They are a convenient way to create custom virtual synths, and
package up novel programs for ourselves and other users.
GUI classes include various forms of slider, buttons, dials, drop down lists, drag
and drop facilities and many more custom views.
This file will use code which should work on all platforms without any worry about
the underlying implementation (SC 3.3.1 on). There may be slight differences
between versions of SuperCollider on the available GUI capabilities.
[side note: On the Mac, for SC3.5 and earlier, you can press shift+cmd+N
immediately to see a selection of the available GUI widgets.]
Because GUIs tend to be quite operating system specific, under the surface, there
are different main GUI implementations available.
Historically, there are also OS X ('cocoa') specific classes (usually with prefix
SC before the class names used here) and SwingOSC ('swing') Java cross platform
classes (usually with prefix JSC).
You can call the standard cross-platform GUI class names, like Slider, Window,
View, without worrying about which of qt, Cocoa or SwingOSC is operative.
Both qt and SwingOSC act like servers, sending and receiving messages from the
language app. On OS X, a native Cocoa implementation is built into the standard
language environment for SC3.5 and earlier.
//make sure SwingOSC server is running if you are using that, before you run any
GUI code:
SwingOSC.default.boot
To make a window
//The Rect(angle) takes the initial screen position and the window size
//as screenx, screeny, windowwidth, windowheight, where screeny is 0 at the bottom
(
var w;
Note that we count on the y axis from screen origin at bottom left, to the bottom
left corner of the window.
We add controls to our window, defining any parameters of their use. We pass in the
window we wish the control to appear in and use a Rect again to specify where in
the window the control will appear and how large it is. However, this time the co-
ordinates are no longer relative to the screen, but relative to the top left corner
of the window, and x and y positions indicate distance from left and from top
respectively.
(
var w, slid;
w.front;
)
1.0.rand*50 //create a random number from 0.0 to 1.0, and multiply it by 50 to get
a new range from 0.0 to 50.0
1.0.rand*50+14.7 //create a random number from 0.0 to 1.0, multiply it by 50, then
add 14.7, to get a new range from 14.7 to 64.7
(
var w, slid, cs;
slid=Slider(w,Rect(10,10,180,40));
w.front;
)
Given the action function for a GUI component, we can plug through to sound
synthesis. Here we use the set command to modulate the control arguments of a
running synth.
(
SynthDef(\filterme,{arg freq=1000, rq=0.5; //make sure there are control
arguments to affect!
Out.ar(0,Pan2.ar(
BPF.ar(Impulse.ar(LFNoise0.kr(15,500,1000),0.1,
WhiteNoise.ar(2)),freq,rq)
))
}).add;
)
(
var w, slid2d, syn;
slid2d.action_({
[slid2d.x, slid2d.y].postln;
syn.set(\freq,100+(10000*slid2d.y),\rq,0.01+(0.09*slid2d.x)); //I'm doing my
own linear mapping here rather than use a ControlSpec
});
w.front;
w.onClose={syn.free;}; //action which stops running synth when the window close
button is pressed
)
If you want to arrange a bank of dials, for instance, you might use a helper class
(a 'decorator') for arranging views on screen:
Note:
10@10 //is the Point (10,10), an (x,y) co-ordinate position
(
w= Window("decoration",Rect(200,200,400,500));
//set up decorator. FlowLayout needs to know the size of the parent window, the
outer borders (10 pixels in on horizontal and vertical here) and the standard gap
to space GUI views (20 in x, 20 in y)
w.view.decorator= FlowLayout(w.view.bounds, 10@10, 20@20);
//now, when GUI views are added to the main view, they are automatically arranged,
and you only need to say how big each view is
k= Array.fill(10,{Knob(w.view,100@100).background_(Color.rand)});
//they were stored in an array, held in global variable k, so we can access them
all easily via one variable
k[3].background_(Color.rand)
However, maximum precision will come from specifying positions yourself. Make use
of SuperCollider as a programming language to do this:
(
w= Window("programming it directly ourselves",Rect(200,200,400,400));
//now, when GUI views are added to the main view, they are automatically arranged,
and you only need to say how big each view is
k= Array.fill(16,{|i| Knob(w,Rect((i
%4)*100+10,i.div(4)*100+10,80,80)).background_(Color.rand)});
//if worried by the use of % for modulo and .div for integer division, try the code
in isolation:
//i.e., try out 5%4, and 5.div(4) as opposed to 5/4. How does this give the
different grid positions as
//argument i goes from 0 to 15?
w=Window();
w.view.children //slider should be in the list, even though we didn't store any
reference to the slider object in a global variable (like w) ourselves
w.refresh; //refresh updates the appearance of the window and the slider disappears
For further explorations:
For demos of Drag and Drop and other UI facilities see the examples/GUI examples
folder
e.g.,
Document.open("examples/GUI examples/GUI_examples1.scd"); //on a Mac with SC3.5 or
earlier this should open it straight away
Document.open("examples/GUI examples/GUI_examples2.scd");
Warning: some examples may not use the more recent implementation free formulation,
and involve different calls.
n = NumberBox(w, Rect(0,0,80,24));
n.value = 123;
n = NumberBox(w, Rect(0,0,80,24));
n.value = 456;
n = DragBoth(w, Rect(0,0,80,24));
n.object = 789;
f.nextLine;
s = Slider(w, Rect(0,0,240,24));
w.front;
)
There are also interesting help files for many other GUI objects. Note that there
may be differences between 3.5 and 3.6 in what is available.
(credit: many of the GUI objects were introduced into SuperCollider by Jan
Trutzschler. for OS X originally)
[EnvelopeView]
(
w = Window.new("soundfile test", Rect(200, 200, 800, 400));
a = SoundFileView(w, Rect(20,20, 700, 60));
f = SoundFile.new;
if(Main.versionAtLeast(3,5),{
f.openRead(Platform.resourceDir +/+ "sounds/a11wlk01.wav");
},{
f.openRead("sounds/a11wlk01.wav");
});
a.soundfile_(f);
a.read(0, f.numFrames);
a.gridOn_(false);
w.front;
)
(
var w, h = 400, v = 400, seed = Date.seed, run = true;
w = Window("subdiv", Rect(40, 40, h, v));
w.view.background = Color.rand;
w.onClose = { run = false };
w.front;
//for SC3.4 or earlier, use drawHook
w.drawFunc = { var done, nextx, nexty, yellowness, penwidth;
nextx=0;
nexty=0;
yellowness=rrand(0.0,1.0);
penwidth=rrand(0.5,1.5);
//done=0;
Pen.use {
200.do({arg i;
var lastx,lasty;
lastx=nextx;
lasty=nexty;
nextx=nextx+rrand(1,20);
nexty=nexty+rrand(1,40);
if(nextx>=h, {nextx=nextx%h});
if(nexty>=v, {nexty=nexty%v});
penwidth=(penwidth+(0.2.rand2))%8.0;
Pen.width= penwidth;
yellowness= (yellowness+(0.1.rand2))%2.0;
Color.yellow(yellowness).set;
Pen.beginPath;
Pen.line(Point(lastx,lasty),Point(nextx,nexty));
Pen.rotate(rand(i%40));
Pen.line(Point(lastx,lasty),Point(nextx,nexty));
Pen.rotate(rand(i%40));
Pen.line(Point(lastx,lasty),Point(nextx,nexty));
Pen.rotate(rand(i%40));
Pen.line(Point(lastx,lasty),Point(nextx,nexty));
Pen.stroke;
//Pen.fillRect(Rect(h.rand,v.rand,rrand(1,50),rrand(1,50)))
});
};
};
(
var linetext, drawletter;
var w, h = 800, v = 60, seed = Date.seed, run = true;
var time, name, sourcestring;
var yellowness, penwidth;
//name=[\s,\u,\p,\e,\r,\c,\o,\l,\l,\i,\d,\e,\r];
name=Array.fill(sourcestring.size,{arg i; sourcestring[i].asSymbol});
time=0;
linetext= (
'a':[[[0,1],[0.5,0]],[[0.5,0],[1,1]],[[0.25,0.5],[0.75,0.5]]],
'b':[[[0,1],[0,0]],[[0,1],[1,1]],[[0,0],[1,0]],[[0,0.5],[0.75,0.5]],[[0.75,0.5],
[1,0.75]],[[0.75,0.5],[1,0.25]],[[1,0.75],[1,1]],[[1,0.25],[1,0]]],
'c':[[[0,1],[0,0]],[[0,0],[1,0]],[[0,1],[1,1]]],
'd':[[[0,1],[0,0]],[[0,0],[0.75,0]],[[0,1],[0.75,1]],[[0.75,1],[1,0.75]],[[0.75,0],
[1,0.25]],[[1,0.25],[1,0.75]]],
'e':[[[0,0],[0,1]],[[0,0],[1,0]],[[0,1],[1,1]],[[0,0.5],[1,0.5]]],
'f':[[[0,0],[0,1]],[[0,0],[1,0]],[[0,0.5],[1,0.5]]],
'g':[[[0,1],[0,0]],[[0,0],[1,0]],[[0,1],[1,1]],[[1,1],[1,0.5]],[[0.5,0.5],
[1,0.5]]],
'h':[[[0,1],[0,0]],[[0,0.5],[1,0.5]],[[1,1],[1,0]]],
'i':[[[0,0],[1,0]],[[0.5,0],[0.5,1]],[[0,1],[1,1]]],
'j':[[[0,0],[1,0]],[[0.5,0],[0.5,1]],[[0,1],[0.5,1]]],
'k':[[[0,1],[0,0]],[[0,0.5],[1,1]],[[0,0.5],[1,0]]],
'l':[[[0,1],[0,0]],[[0,1],[1,1]]],
'm':[[[0,1],[0,0]],[[0,0],[0.5,0.5]],[[0.5,0.5],[1,0]],[[1,0],[1,1]]],
'n':[[[0,1],[0,0]],[[0,0],[1,1]],[[1,1],[1,0]]],
'o':[[[0,1],[0,0]],[[0,0],[1,0]],[[0,1],[1,1]],[[1,0],[1,1]]],
'p':[[[0,0],[0,1]],[[0,0],[1,0]],[[0,0.5],[1,0.5]],[[1,0],[1,0.5]]],
'q':[[[0,0],[0,0.75]],[[0,0],[0.75,0]],[[0,0.75],[0.75,0.75]],[[0.75,0],
[0.75,0.75]],[[0.5,0.5],[1,1]]],
'r':[[[0,0],[0,1]],[[0,0],[1,0]],[[0,0.5],[1,0.5]],[[1,0],[1,0.5]],[[0,0.5],
[1,1]]],
's':[[[0,0],[0,0.5]],[[0,0],[1,0]],[[0,1],[1,1]],[[0,0.5],[1,0.5]],[[1,0.5],
[1,1]]],
't':[[[0,0],[1,0]],[[0.5,0],[0.5,1]]],
'u':[[[0,1],[0,0]],[[0,1],[1,1]],[[1,0],[1,1]]],
'v':[[[0,0],[0.5,1]],[[0.5,1],[1,0]]],
'w':[[[0,0],[0.25,1]],[[0.25,1],[0.5,0.5]],[[0.5,0.5],[0.75,1]],[[0.75,1],[1,0]]],
'x':[[[0,0],[1,1]],[[0,1],[1,0]]],
'y':[[[0,0],[0.5,0.5]],[[0.5,0.5],[1,0]],[[0.5,0.5],[0.5,1]]],
'z':[[[0,1],[1,0]],[[0,0],[1,0]],[[0,1],[1,1]]],
(" ".asSymbol):[[[0,1],[1,1]],[[0,0.8],[0,1]],[[1,0.8],[1,1]]]
);
data= linetext[which];
prop=(round((data.size)*prop).asInteger).max(1);
prop.do({arg i;
var val=data[i];
Pen.beginPath;
Pen.line(Point(startx+(xscale*val[0][0]),starty+(yscale*val[0][1])),Point(startx+
(xscale*val[1][0]),starty+(yscale*val[1][1])));
Pen.stroke;
});
};
yellowness=rrand(0.7,0.9);
penwidth=rrand(2,3);
w.drawFunc = {
usedtime=time.min(1.0);
todraw=(round((name.size)*usedtime).asInteger).max(1);
todraw.do({arg j;
xoscil= sin(2*pi*time+(j*pi*0.13))*140/(1+(10*time));
yoscil= sin(2*pi*time+(j*pi*0.03))*200/(1+(200*time));
xsizoscil= time*5+5;
drawletter.value(name[j],50+(25*j)+
(xoscil),10+yoscil,xsizoscil,xsizoscil,usedtime);
});
};
};
{ while { time<2.0 } {
w.refresh;
time=(time+0.025); //%2.0;
0.05.wait; } }.fork(AppClock)
If you're on OS X before SC3.6, also see SCImage for many image processing
capabilities, such as placing an image into the background of your GUI window, or
adding a logo.