Conductor

 

Superclass: Environment

related classes:  ConductorPlayer, ConductorGUI, 

  CV, SV, CVPreset, CVInterpolator, 


Overview

Conductor provides a framework for defining interactive programs in terms of a collection of related components. The Conductor is a kind of  Dictionary and its components are stored and accessed as dictionary entries (i.e., key/value pairs).  This allows the Conductor to be configured either when it is first defined or in separate code.

Class Methods

*new


*make (function)

The first argument of the function is set to the Conductor being constructed.

Subsequent arguments are initalized as CVs; arguments with default values are 

set to instances of the class the default value specifies.

Below, the first line defines a Conductor with four CV's assigned to the arguments a,b,c,d.

The second line displays that Conductor.

(

c = Conductor.make { | conductor, a, b, c, d |  };    

c.show;

)

 

( // here the CV d is initialized to an array of values.

c = Conductor.make { | conductor, a, b, c, d | d.value_(1/(1..128)) };    

c.show;

)

*specs

Control.specs is a dictionary of ControlSpecs.  When an argument in the make function is 

initialized to a CV, its identifier is looked up in this dictionary.   If that does not provide a ControlSpec, 

the same identifier stripped of all of its numeric characters is used once again look-up.

The specs dictionary is provided with the following defaults:

amp, audiobus, beats, bipolar, boostcut, controlbus, db, dbamp, 

delay, detune, dur, fadeTime, fin, freq, i_out, in, lofreq, 

longdelay, midfreq, midi, midinote, midivelocity, out, pan, 

phase, rate, ratio, rq, unipolar, widefreq

( // define a conductor using the default controlspecs

Conductor.specs[\spT] = ControlSpec(-60, 700, 'linear', 0, 33);

a = Conductor.make{ | con, freq1, db, pan, dur, spT3, s3pT, sp3T|

con.name_("example 1");

con.pattern_(Pbind(*[freq: freq1, db: db, pan: pan, dur: dur]) );

};

a.show;

)

(

a.play;

a[\freq].value = 700;

)

Instance Variables

gui - an instance of [ConductorGUI] that defines the Conductor's GUI presentation


player - an instance of [ConductorPlayer], which provides unified stop/play/pause/resume 

control for Patterns, Tasks, and, on the server, synths, groups, buses, and buffers.

(These objects use a variant of Server-sendBundle to guarantee correct order execution on 

the server.)


preset - an instance of [CVPreset] or [CVInterpolator], this provides preset values for a user 

specified collection of CV's and Conductor's. The CVInterpolator allows interpolations between 

presets to be used for values

path - stores the pathname of the file that saves the Conductor's settings and attempts to load 

those settings


valueKeys an array of keys that determine the Conductor's response to value.  

(Typically includes both individual CV's and the CVPreset or CVInterpolator used by the Conductor.)


Instance Methods

Player related methods:

stop

play

pause

resume

action_ (playFunc, stopFunc, pauseFunc, resumeFunc)

Adds an ActionPlayer which responds to play, stop, pause, and resume by evaluating 

the corresponding function with the Conductor as currentEnvironment.

(

// action_can control any kind of user program, 

c = Conductor.make { |conductor, freq, db, dur | 

freq .spec_(\freq);

db .spec_(\db, -10);

dur .sp(0.2, 0.05, 1, 0, 'exp');

// add a pattern using actions,

// notice the use of ~player, an environment variable

// within the Conductor

conductor.action_(

{ ~pat = Pbind(*[freq: freq * 2, db: db, dur: dur/2])

  .play(quant: 0);

},

{ ~pat.stop },

{ ~pat.pause},

{ ~pat.resume}

);

conductor.name_("test");

};    

c.show;

)

task_ (function, clock, quant)

Adds a TaskPlayer which plays the function within a task scheduled by the specified clock 

and quantization. (On stop, tasks that block on a message port are also be deleted.)

pattern_ (pattern, clock, event, quant)

Adds a PatternPlayer which plays the pattern with the specified event, clock and quantization.

(

// but convenience methods such as pattern_  are more concise

c = Conductor.make { |conductor, freq, db, dur | 

freq .spec_(\freq);

db .spec_(\db, -10);

dur .sp(0.2, 0.05, 1, 0, 'exp');


conductor.pattern_( 

Pbind(*[freq: freq * 2, db: db, dur: dur/2]), 

quant: 0

)

};    

c.show;

)

nodeProxy_(nodeProxy, args, bus, numChannels, group, multi)

Adds a NodeProxyPlayer, which uses the following bus, numChannels, and group if specified, 

otherwise uses default values.  See NodeProxy for details.

(

a = Conductor.make({ | con, freq1, freq2, widefreq3, db|

~np = NodeProxy.audio(Server.default, 2);

con.nodeProxy_(~np, 

[freq1: freq1, freq2: freq2, widefreq3: widefreq3, db: db]

);

~np[0] = { | freq1, freq2, widefreq3, db| 

Mix(SinOsc.ar([freq1, freq2, widefreq3], 0, db.dbamp))

};

});

a.show

)

synth_ ( event, args)

(

// Controlling a synth

Conductor.make({ arg conductor, freq, volIndB;

freq .spec_(\freq);

volIndB .sp(-20,-100,20);

conductor.synth_( 

( instrument: \default, // this Event is explicitly specifying all the default values

addAction: 1, 

group: 1, 

server: Server.default

), 

[freq: freq, amp: [volIndB, volIndB.dbamp], pan: -1 ]);

}).show

)

group_ ( event, args)

CV's assigned to a group affect all of the synths within the group. In the following example

the CV only alters playing synths, new synths use the default value:

(

SynthDef("pm1", { | out, freq, amp, pan, gate = 1,

ratio1 = 1, index1 = 1, a1 = 0.01, d1 = 0.2, s1 = 0.5, r1 = 1, 

ratio2 = 1, index2 = 1, a2 = 0.01, d2 = 0.2, s2 = 0.5, r2 = 1

|

var audio, env1, env2;

env1 = EnvGen.kr(Env.adsr(a1,d1,s1,r1), gate, doneAction: 2) * index1;

env2 = EnvGen.kr(Env.adsr(a2,d2,s2,r2), gate) * index2;

audio = PMOsc.ar(freq * ratio1, freq * ratio2, env2, 0, env1);

Out.ar(out, Pan2.ar(audio, pan, amp))

}).store;

Conductor.make{ | con, index2, ratio1, ratio2 |

index2 .sp(0,0,20);

// create a whole bunch of groups in sequence

con.group_(

(id:[2,3,4,5,6,7,8,9], addAction: 1), [index2: index2, ratio1: ratio1, ratio2: ratio2] 

);

// play a pattern in one

con.pattern_(Pbind(*[

instrument: \pm1,degree: Pwhite(0, 10), dur: 0.2, sustain: 2, group: 6,

ratio1: ratio1, ratio2: ratio2, index2: index2

]))

}.show

)

The argument args is an interleaved array of keys and CVs (or value). 

CVs can also be altered  before being sent to the server and combinations of CVs can

determine the value to be sent:

value [freq: 440  ]

CV [freq: aCV  ]

altered CV [freq: [ aCV, aCV.midicps ] ]

combination [freq: [ [aCV, bCV], aCV.midicps + bCV] ]

function [freq: [ aCV, { aCV.midicps.value + 33.rand }]


The events use the same keys as note events in patterns. The keys server, group, and 

addAction and, for synths, instrument determine the group or synth. As in patterns, the

default values for these keys are 

server: Server.default, 

group: 1,

addAction: 0, 

instrument: 'default' 


Usually the node ID of the group or synth is dynamically allocated, but the key id can be set 

to set the id directly.  For group events, a new group  or collection of groups is created with the

specified id(s).  For synth events, no synths are created, but the control values determined by 

the event are sent to the specified id(s).

controlBus_ ( event, cvs)

The event can specify

server: aServer (defaults to Server.default)

index: (optional)

CVs is an array of CVs that are used to determine the value of consecutive buses

buffer_ ( event)

This event is designed primarily for small waveform buffers, it specifies:

server: aServer (defaults to Server.default)

cv: a CV that determines the values in the buffer

msg: A symbol that determines how the values are used to fill the buffer.

Is is one of: \sine1, \cheby, \wave, or \signal

display: anotherCV

An optional CV used to display the contents of the buffer (as received from the server)

size: integer (defaults to 512 and should not exceed 1024).

Value related methods:

value

Returns an array of the values of all components identified by valueKeys

value_ (valueArray)

Iterates over the the array assigning the value to be the value of the corresponding component 

identified by valueKeys

Settings, Preset and Interpolator related methods


A Conductor can load a set of initial settings for its contents from a file.

noSettings no file controls are displayed

useSettings allow a single set of settings to be saved to file and restored from file

path_ (filePath)

Load the settings stored in the file identified by filePath.


Presets: A CVPreset saves 'presets' for an array of CVs or other objects that respond to input and input_.

presetKeys_ (keys, preset)

The objects at the keys will have their settings saved and restored by preset, which

defaults to the object in the preset instance variable.

usePresets creates a CVPreset, gives it valueKeys as its default presetKeys


Interpolator: A CVInterpolator will set a specified set of CVs to values that derived from interpolating 

between two presets

interpKeys_ (keys, preset)

The objects at the keys (which must be a subset of the valueKeys of the preset) can 

have their settings interpolated between preset values.

useInterpolator creates a CVInterpolator, sets valueKeys to be both presetKeys and interpKeys

Here is an example:

( a = Conductor.make { | con, cv1, cv2, cv3 | 

cv1 .sp(440, 20, 20000, 0, 'exp'); // set the value range of cv1

cv2 .sp(0.1, 0.01, 1, 0, 'exp');

// set the value range of cv1

con.useInterpolator;

// we want to use an interpolator

con.presetKeys_(#[cv3]);

// we  want to save  cv3 only

con.interpKeys = #[cv3];

// we only want to interpolate cv3

cv3.value = 1/(1..128); 

// save some presets

con.preset.addPreset;

cv3.value = 1/(1..128).reverse; 

con.preset.addPreset;

con.preset.presetCV.value = 1;

// select a preset

con.task_({ 

// this task is added to the player object

loop {

// once the player has something to play, 

// its start button will appear in the GUI

con.preset.targetCV.value_(0);

100.do {|  i | con.preset.interpCV.value_(i/100); cv2.value.wait }; 

con.preset.targetCV.value_(1);

100.do {|  i | con.preset.interpCV.value_(i/100); (cv2.value * 2).wait } ;

}

});

}.show

)


adding new CVs

addCV(key) creates a CV inserts it at key in the Conductor and appends it to valueItems and, if it exists, presetItems


MIDI related methods:

useMIDI (keys)  

this will add a set of controls to enable linking individual CVs to MIDI continuous controllers

midiKBD_(function, MIDIchannel | nil)

The function receives a key number and velocity value and returns an object that responds to release (typically a Synth). If  MIDIchannel is nil, it responds to MIDI key commands from all channels in "omni" mode.


GUI related methods:


show (argName, x, y, w, h)

Draw the Conductor within a window named argName at x,y with size w,h.

draw (window, name, conductor)

Draw the Conductor within the specified window


See ConductorGUI for more details


Conductor Internals

The basic components of a Conductor are:

1. ConductorPlayer, which can play, stop, pause, and resume an arbitrary collection of Tasks, Synths, 

Patterns, and NodeProxys as well as allocate and deallocate resources such s Buffers and Buses.  

By default a Conductor has a single player, but it can be readily configured to have several.

2. CV, which defines a control value or array of control values constrained to a specific range of values 

by a ControlSpec.  The value of a CV can be set directly or as an input ranging from 0 to 1 rescaled to the

CV's range.  CVs provide methods to connect to GUI elements, server nodes, buses, and buffers and may 

be used directly in Pattern definitions. SV (symbolic value), EV (envelope value), and TV (text value) are 

classes derived from CV which provide similar interfaces.

Typically, a Conductor has multiple CV's.

3. ConductorGUI, which defines the GUI representation of the Conductor

4. CVPreset, which saves the values of a user specified collection of components into selectable presets.

CVInterpolator is a preset that also provides the ability to interpolate between presets.

5. Conductor, one Conductor can be a component of another, providing hierarchical control. 

Conductor has the instance variables player, gui, and preset which respectively default to a 

ConductorPlayer, a ConductorGUI, and a CVPreset.

The messages spec_(specName, default) and sp(default, lo, hi, step, warp) can be used to set

the range of values a CV can assume (see [CV] for details).  In this example, the CV assigned to

d is given an array as a default value.

( a = Conductor.new; // create the conductor

a.addCV(\cv1); // add some CVs

a.addCV(\cv2);

a.addCV(\cv3);

w = a.show; defer( { Document.current.front}, 0.05); // display it

)

( a[\cv1] .sp(440, 20, 20000, 0, 'exp'); // set the range of cv1

a[\cv2] .sp(0.1, 0.01, 1, 0, 'exp'); // now cv2

)

( w.close; // close the display window

defer({

a[\cv3].value = 1/(1..128); // and change cv3 to represent an array

w = a.show

}, 0.1); // and show it now

)

( w.close;

defer({

a.useInterpolator; // add the use of an interpolator

a[\cv3].value = 1/(1..128); a.preset.addPreset; // save some presets

a[\cv3].value = 1/(1..128).reverse; a.preset.addPreset;

a.preset.presetCV.value_(0);

w = a.show;   

Document.current.front;

}, 0.1);

)

(

Task { // interpolate between the presets

loop {

a.preset.targetCV.value_(1);

100.do {|  i | a.preset.interpCV.value_(i/100); 0.01.wait };

a.preset.targetCV.value_(0);

100.do {|  i | a.preset.interpCV.value_(i/100); 0.02.wait }; 

}

}.play(AppClock);

)

w.close;

(

// Changing CV ranges

c = Conductor.make { |conductor, a, b, c, d | 

a .spec_(\freq);

b .spec_(\freq, 880);

c .sp(1, 0, 15, 1);

d .spec_(\unipolar,1/(1..128));

};    

c.show;

)



(

// Controlling a Pattern and a Group

//  'vol' is assigned to both the Pattern and the Pattern's group

// this provides continuous control of the pattern's notes as they are sounding

b = Conductor.make({ arg conductor, freq, vol;

freq .spec_(\freq);

vol .sp(-20,-100,20);

conductor.name_( "a group used to control a pattern's synths");

conductor.group_( (id: [22], group: 0, addAction: 1), [amp: [vol, vol.dbamp], freq: freq  ]);

conductor.pattern_(Pbind

(\freq, Prand([1, 3/2, 5/4, 7/4], inf) * freq + Ptuple([Pwhite(-1.0,1),Pwhite(-1.0,1)]),

\db, vol, 

\dur, Prand([0.2, 1.2],inf),

\group, 22, 

\legato, 10), 

quant:0 );


} );

 

b.show;

)

(

// Using a Buffer and interpolation

c = Conductor.make({ arg conductor,  freq, vol, overtones, waveform;

var buf;

freq .spec_(\freq);

vol .sp(-20,-100,20);

overtones .sp(1/(1..64),0, 1);

waveform .spec_(\bipolar, Array.fill(512,0));

SynthDef ("osc", { |out = 0, freq = 200, amp = 0.1, bufnum | 

Out.ar(out, Osc.ar(bufnum, freq, 0, amp) ) 

}).store;

conductor.name_("synth");

conductor.buffer_(buf = (msg: \sine1, cv: overtones, display: waveform) );

b = buf;

conductor.synth_(a = (instrument: \osc), [

freq: [freq, freq * 3/2], amp: [vol, vol.dbamp], bufnum:  { buf.bufnum } ]) 

});

c.valueKeys_(#[overtones]);

c.useInterpolator;

// c.usePresets;

d = c.show;

)