MixerChannel 


Creates a single channel for a MixingBoard. Allows any number of channels in and out. Provides full level and pan automation, and support for pre- and post-fader sends.


23 Jan 2004: Upon pressing cmd-., all existing MixerChannels will attempt to recreate the server infrastructure so that playing may continue. Synths and effects playing through the MixerChannel will not be rebuilt, but faders and sends will continue to function.


25 July 2004: The MixingBoard now allows drag-and-drop to assign MixerChannels to GUI slots. MIDI controls for mixers levels can also be assigned to the GUI objects, rather than directly to the mixer, so that you can have a consistent mapping between your MIDI interface and the visual representation, while the mapping to the "physical" mixer is dynamic.


17 November 2004: MixerChannels handle node execution order in a much more robust way. See detailed help below, "Order of Execution."


New feature (16 July 2005): The control structure for MixerChannels is now customizable, to allow support for all spatialization techniques. See the MixerChannelDef and MixerGUIDef help files for more details.


Architecture 


Each MixerChannel resides in three groups on the server, which should contain all the synths playing on that channel. The order of execution should be: 


fadergroup (contains the other 2)

synthgroup (contains synths)

effectgroup (contains effects)

fader synth


MixerChannel uses an audio bus to handle all signal processing. You may use only part of the bus at either end, allowing a MixerChannel to expand a mono signal to stereo, or reduce a stereo or quad signal to mono (quad support is not currently provided, but you can add it easily using MixerChannelDef).


Synthdef conventions 


Note: You can avoid many of these details by using the crucial library Instr's and Patches. See Instr and Patch.


Your synthdefs must be able to play on any bus to work with MixerChannel. You must not hardwire the output bus. If MixerChannel can't tell the synthdef what bus to use, the signal will never reach the MixerChannel. 


s = Server.internal; s.boot;


SynthDef.new("pinkfilt", {

arg outbus, // outbus is the standard name used by MixerChannel

freq, rq;

Out.ar(outbus, RLPF.ar(PinkNoise.ar, freq, rq));

}).send(s);


m = MixerChannel.new("pinkfilt", s);  // defaults to mono-to-stereo


a = m.play("pinkfilt", [\freq, 2000, \rq, 0.02]);


m.free; // all synths disappear


Note that if you use MixerChannel.play, you don't have to specify the bus number as long as your synthdef uses outbus as the output bus argument. Otherwise, do this:


a = m.play("pinkfilt", [\freq, 2000, \rq, 0.02, \out, m.inbus.index]);


Any effect synthdefs you write should read the input signal from outbus and use ReplaceOut.ar(outbus...). If you want the input signal to be included in the effect's output, it's advisable to include an argument for wet/dry level rather than simply using Out to add the effect to the bus, because you have more control: 


SynthDef.new("fx/chorus1x1", {

arg outbus, delay = 0.01, freq = 0.2, depth = 0.05, wet = 0.5;

var in, out;

in = In.ar(outbus, 1);

out = DelayN.ar(in, 1, SinOsc.ar(freq, 0, depth, delay));

ReplaceOut.ar(outbus, in*(1-wet).sqrt + out*wet.sqrt);  // equal power

}).send(s);


m.playfx("fx/chorus1x1"); // so it goes to effectgroup


Methods 


Creation and destruction


*new(name, server, inChannels = 1, outChannels = 2, level = 0.75, pan = 0, inbus, outbus, completionFunc) 


Creates a new instance. You must specify a server. Name is optional, although if you're planning to use MixingBoard, you'll need the name to know which channel is which. Inbus and outbus will be created for you. Outbus defaults to zero (main hardware out), but can be overridden to point to a bus or another MixerChannel, allowing for a MixerChannel to serve as a submix.


*newFromDef(name, defname, server, initValues, inbus, outbus, completionFunc)


Creates an instance using a specific MixerChannelDef. For instance, the following statements will generate functionally equivalent MixerChannels.


m = MixerChannel.new(\new, s, 1, 2, level: 0.25, pan: -0.5);

n = MixerChannel.newFromDef(\newFromDef, \mix1x2, s, (level: 0.25, pan: -0.5));


The initValues should be a dictionary with control names as keys. (key: value, key: value) is a shortcut syntax. Not all controls need be specified--values not assigned here will take the default from the MixerChannelDef.


free(updateGUI = true) 


Frees all the server objects associated with this mixer, buses created by the MixerChannel, and removes the channel from its mixing board (if it has one). UpdateGUI should usually not be specified by the user; it's there to prevent infinitely recursive calls between MixerChannel-free and MixingBoard-free.


m.free; // watch the ugens on the server drop!


Playing synths 


play(thing, args) 


Thing may be:


a string representing the name of the synthdef to play

an Instr (which will be placed in a Patch, and the Patch returned:

myPatch = myMixer.play(myInstr, args)

a Patch (or any subclass of AbstractPlayer)

an Event Pattern (any pattern that can be .play'ed on its own)

Args are optional, and must be written in an array as in the usual syntax. The synth object will be placed at the tail of synthgroup.


Returns the synth object (or Patch, if Instr is used).


a = m.play("pinkfilt", [\freq, 2000, \rq, 0.02]);

a.free;


Playing a pattern (without MixerChannel) takes the arguments clock, protoEvent and quant. With MixerChannel, place these parameters into a dictionary (Event) and supply it to the argument.


p = m.play(Pbind(

\degree, Pn(Pseries(0, 1, 8), inf),

\dur, 0.125

), (quant: 4.0));


p.stop;


playfx(name, args) 


Differs from play in that the synth object is placed in the effect group.


Level and pan setting 


getControl(name)

setControl(name, value, updateGUI = true, stopAutomation = true, resync = true)


Get or set the value of a control. The additional flags for setControl are used internally; generally you should not override the default.


m.setControl(\level, 0.25);


Getters and setters are provided for level and pan for backward-compatibility.


level

level_(lev) 

pan

pan_(p)


The level range is 0..1; use .dbamp to convert decibels (negative!) to this range. Pan ranges from -1..+1.


m.level = 0.25; // sufficient in nearly every case 


Control automation 


Two techniques are used to automate level and panning: a control rate synthdef for LFOs, and a routine for linear (finite) changes. 


automate(name, synthdef, args)

levelAuto(synthdef, args)

panAuto(synthdef, args) 


Places a control rate synth on the server, mapped to the desired parameter.


SynthDef.new("mxaut/SinLFO", { // sinewave lfo

arg outbus, freq = 1, phase = 0, mul = 1, add = 0;

ReplaceOut.kr(outbus, SinOsc.kr(freq, phase, mul, add));

}).load(s);


m.panAuto("mxaut/SinLFO", [\freq, 0.2]);

m.stopPanAuto;


You may also use any type of object that may be played on the mixerchannel (synthdef name with args, function, Instr or Patch), except that it must output a kr signal.


m.panAuto({ SinOsc.kr(0.1) });  // same as a DAW's autopanner *grin*


To view the automation in a mixingboard:


m.watch(controlName);

m.stopWatching(controlName);


m.watch without arguments will watch all controls -- however, this can be CPU expensive. Use with caution.


controlLine(name, start, end, dur, warp)

levelLine(start, end, dur, warp)

panLine(start, end, dur, warp)


controlLineTo(name, end, dur, warp)

levelTo(end, dur, warp)

panTo(end, dur, warp) 


Makes a linear transition from start to end, taking dur seconds. Warp can be any of the options supported by ControlSpec. Default values for start and end will be taken from the control's spec. The spec is defined in the MixerChannelDef.


These methods use a routine to make the changes and update the MixingBoard. The time between the iterations of the routine is controlled by the instance variable guiUpdateTime, which may be set by the user. The default is 0.25.


controlLineTo, levelTo and panTo start from the current control value, so the starting parameter is not specified. 


(

m.levelLine(0, 0.75, 10); // a 10-second fade in

{ m.levelTo(0, 15); nil }.defer(7); // interrupt the fade in and begin fadeout

) 


stopAuto(name)   [If no name is given, all control automation will be stopped.]

stopLevelAuto

stopPanAuto 


Cancel automation and leave the desired parameter wherever the automation left it. Any time you set the level or pan or start a new automation process, current automation is stopped using one of these methods. Stopping level automation does not stop pan automation, and vice versa.


Scope


scope(layout, bounds)


Displays an oscilloscope view of the output of this mixer channel. This uses the crucial GUI framework, so the scope view can be embedded in FlowViews, MultiPageLayouts, etc.


Sends 


See MixerSend for details.


newPreSend(dest, level)

newPostSend(dest, level)


Create a pre- or post-fader send on the MixerChannel. 


myMC.newPreSend(dest, level) is the same as MixerPreSend.new(myMC, dest, level).


Order of Execution


MixerChannels use a system of dependencies to determine which mixers should appear first in the mixer chain. The algorithm should work under almost every possible circumstance. It also holds mixer execution order after pressing cmd-.


It is not permitted to create a circular feedback loop among MixerChannels. This is nonsensical usage anyway, because the server-side implementation does not support feedback. An error is thrown in this case.


a = MixerChannel(\src1, s, 2, 2);

b = MixerChannel(\src2, s, 2, 2, outbus:a);

a.outbus = b; // this will produce an error


ERROR: MixerChannel(src1, localhost, 2, 2) -> MixerChannel(src2, localhost, 1, 2) will produce an infinite loop.

RECEIVER:

Instance of MixerChannel {    (0FFCF620, gc=54, fmt=00, flg=00, set=05)

...


NOTE: The same error will occur if you try to create a MixerChannel with the same inbus and outbus. MixerChannel is not designed for that use. Instead, use MasterMixerChannel.


Setting a mixer's outbus to another MixerChannel, or creating a send whose destination is a MixerChannel, automatically creates the required dependencies and fixes the node order. There may be cases where you need to route the signal from a MixerChannel into another MixerChannel without using sends or direct routing. The following methods are provided to assist:


sendsSignalTo(mc)

stopsSendingTo(mc)


The source mixer is the message receiver, and the destination is the argument.


source.sendsSignalTo(dest)


To end the relationship, use source.stopsSendingTo(dest).


receivesSignalFrom(mc)

stopsReceivingFrom(mc)


The argument MC will be added as a dependent of the receiver: dest.receivesSignalFrom(source)


Recording


Recording functions can be activated from the MixingBoard GUI. The record button switches between "unpauseRecord" (which also starts recording) and "pauseRecord."


startRecord(path, headerformat = "aiff", sampleformat = "int16")


Begins recording the output of this mixerchannel. All arguments are optional. If path is not specified, the name of the mixerchannel will be used, plus a number based on the length of time SuperCollider has been open--meaning that you can record several files from the same mixerchannel in succession without overwriting.


stopRecord


Stops recording and closes the file. stopRecord is called when the mixerchannel is freed.


pauseRecord

unpauseRecord


If you call unpauseRecord and the channel is not already recording, recording will be started.


prepareRecord(path, headerformat = "aiff", sampleformat = "int16")


Gets the mixerchannel ready to record but does not begin recording.