11. Mark Trayle's pins & splits
26 Jan 2018Mark Trayle
Mark Trayle (January 17, 1955 – February 18, 2015), my mentor during Grad School, was an incredibly inspiring musician, composer, and technologist. His work frequently used repurposed consumer products (Wacom Tablets, Credit Card Readers, etc.) to creative ends, and he was a big proponent of designing "computer-native" approaches to improvisation and experimental music. Mark was a pioneer of network-based live electronic music, a technology-driven approach to ensemble improvisation he researched and developed with his colleagues in The Hub as well as in his personal work.
pins & splits was written in 2014 and performed extensively around that time. After Mark passed we continued performing the piece. It is an incredibly strong example of everything I love about Mark's work. It is also a excellent introduction to thinking through approaches to interaction over the network. To begin our discussion of network-based communication for sound ecologies we will spend the rest of the day experimenting with this piece.
pins & splits
What follows is what Mark distributed to performers as the Score to pins&splits (note: I have redacted some things that were instructions to instrumentalists who performed with the laptops). Though he referred to this writing as a "control spec" one can also think of it as a set of instructions for how the piece works as well as what sounds should be designed.
////// PINS & SPLITS!!!!
Mark Trayle, 2014
In pins&splits players alternate between a single 'background' sound and a set of one or more 'foreground' sounds. A player's foreground sound can be triggered by another player at any time, causing the background sound to stop. After a relatively short period of time, say, in the 1 - 10 second range, the foreground sound stops, and the background sound starts again.
Players have no control over their own foreground/background switching, that's done by other players in the group.
Players do have control over certain parameters of the sounds they make.
My intention is to create quickly changing instrumental groupings and sonic textures through a limitation of sonic material and the interruption of the performers' musical flow.
Messaging and Musical Flow
This section is primarily intended for the laptop players. It’s rather technical.
Start by making the background sound. When you receive an OpenSoundControl
message of the form…
/<yourname> /<sender> /switch /density <float 0…1> /dur <float 1…10>
Check to make sure that <yourname>
identifies you, and if it does, start the foreground sound and apply the /density
value to it in a way you deem appropriate. After /dur
expires the foreground sound should stop and you should return to the background sound. Send /switch
messages to anyone in the group at any time you wish. You should have direct control of the timing of the messages and the value of the /density
and /dur
parameters, i.e., a performer interface rather than an algorithm.
Notes and a Variation (for all)
- Background sounds should be relatively quiet (p-pp) and static.
- Foreground sounds should stand out from the background.
- You could decide to pick a different range of foreground duration, as the occasion sees fit.
- You can react to
/switch
messages with as many different sounds, sound-events, sound-aggregates, etc., as you like.
Specifics for Laptopists
You’re free to use any sounds you like. However, keep in mind that this should be a very active piece, I suggest avoiding sounds that take a long time to develop, anything too “drone-y”, for your foreground palette. Drones are fine for background sounds. Samples are okay, maybe even encouraged as the rest of the concert contains 0% sampled material. Shorter samples would be better, and they can always be stretched out. And samples would be better in the foreground palette.
Code
Note: from a sound design perspective this is definitely the most boring version of this piece possible
/*
pins and splits, Mark Trayle, 2014
code by Casey Anderson
density range = 0.01..1.0
trayle duration range = 1.0..10.0
bitpanic duration range = 5.0..25.0
*/
s.boot;
(
// setup database of IPs
~ips = Dictionary.newFrom(
List[
"partner", "",
"me", ""
]
);
~names = ~ips.keys;
~listofnames = ~names.as(List);
~states = ~listofnames.collect { |name|
[name, Color.white, Color.grey]
};
~addresses = ~listofnames.collect { |name|
NetAddr(~ips[name], 57120)
};
// control bus
~density = Bus.control(s, 1).set(0.25);
~duration = Bus.control(s, 1).set(5.0);
~backAmp = Bus.control(s, 1).set(0.0);
~foreAmp = Bus.control(s, 1).set(0.0);
)
///////// SYNTHS /////////
(
/*background sound
(this is too boring for use in this piece
but enough to demonstrate the structure)
*/
SynthDef( \background, {| amp = 0.0, out = 0, trig = 0 |
var env, sig;
env = EnvGen.kr( Env.asr( 0.1, 0.9, 0.1 ), trig, doneAction: 0 );
sig = PinkNoise.ar( amp ) * env;
Out.ar( out, Pan2.ar( sig ) );
}).add;
/*foreground sound
(also too boring for use in this piece
but different enough to be a noticeable
change upon receiving a switch message)
*/
SynthDef( \foreground, { | amp = 0.0, dur = 4, freq = 300, out = 0, trig = 0 |
var env, sig;
env = EnvGen.kr( Env.linen( 0.01, dur, 0.01 ), trig, doneAction: 2 );
sig = SinOsc.ar( freq, 0.0, amp ) * env;
Out.ar( out, Pan2.ar( sig ));
}).add;
// switcher function
~switcher = { | msg, tm, addr |
var dens = msg[1], dur = msg[2], rPos, bufNumber;
postln( "pins and splits "++msg );
~back.set(\trig, 0 ); // turns background off
// foreground goes here
Synth(\foreground, [\amp, ~foreAmp.asMap, \dur, dur, \trig, 1, \freq, { rrand(200.0, 500.0).asFloat }.value ]);
SystemClock.sched(dur, {
// turn the background synth back on after dur
~back.set( \trig, 1 );
});
};
)
// GUI
(
~window = Window.new("pins&splits", Rect(0,0,470,280))
.onClose_({
"CLOSING".postln;
OSCdef.freeAll;
~back.free;
});
// background sound controls
~button = Button(~window, Rect( 10, 10, 50, 50))
.states_([
["OFF", Color.white, Color.black],
["ON", Color.white, Color.red]
])
.action_({ arg butt;
if( butt.value == 1,
{
"START THE SWITCHER".postln;
OSCdef(\whatever, ~switcher, \switch );
"RUN BACKGROUND".postln;
~back = Synth( \background, [ \trig, 1, \amp, ~backAmp.asMap ]);
},
{
"STOPPED BACKGROUND".postln;
~back.set(\trig, 0);
~back.free;
"KILLED SWITCHER".postln;
OSCdef(\whatever).free;
}
);
});
~backAmpSlider = Slider.new(~window, Rect(65, 10, 50, 200))
.action_({ |slider|
var val = slider.value;
~backAmp.set(val);
~backAmpVal.value_(val); // show slider value in number box
});
~backAmpVal = NumberBox(~window, Rect(65, 215, 50, 25));
~backAmpVal.align = \center;
~backAmpVal.value = 0.0;
~backAmpLabel = StaticText(~window, Rect( 65, 245, 50, 25));
~backAmpLabel.align = \center;
~backAmpLabel.string = "back";
~backAmpLabel.background = Color.white;
~foreAmpSlider = Slider.new(~window, Rect(120, 10, 50, 200))
.action_({ |slider|
var val = slider.value;
~foreAmp.set(val);
~foreAmpVal.value_(val); // show slider value in number box
});
~foreAmpVal = NumberBox(~window, Rect(120, 215, 50, 25));
~foreAmpVal.align = \center;
~foreAmpVal.value = 0.0;
~foreAmpLabel = StaticText(~window, Rect( 120, 245, 50, 25));
~foreAmpLabel.align = \center;
~foreAmpLabel.string = "fore";
~foreAmpLabel.background = Color.white;
// network control
//select person to send to
~sendto = Knob.new(~window, Rect(185, 10, 50, 50))
.action_({ |knobval|
var maxval, val;
maxval = (~listofnames.size) - 1;
val = [0, maxval, \lin, 1].asSpec.map(knobval.value);
{ ~whom.value_(val) }.defer;
});
// display sendto's hostname
~whom = Button(~window, Rect( 245, 10, 100, 50))
.states_(~states);
// send the message!
~sendNow = Button(~window, Rect( 185, 65, 160, 50))
.states_([
["SEND", Color.white, Color.grey],
["", Color.red, Color.red]
])
.action_({ |val|
var num = val.value;
if( num == 1, {
// send here
var addr = ~addresses[~whom.value];
("//send to " ++ ~listofnames[~whom.value] ++ "!!").postln;
~duration.get({ |durValue|
var dens, dur = durValue, ip;
~density.get({ |densValue|
dens = densValue;
addr.sendMsg(\switch, dens, dur);
SystemClock.sched( 0.25, {
{ ~sendNow.value_(0) }.defer; // resets sendNow
});
});
});
});
});
// density
~densSlider = Slider.new(~window, Rect(355, 10, 50, 200))
.action_({ |slider|
var val = [0.01, 1.0, \lin, 0.01].asSpec.map(slider.value);
//set bus
~density.set(val);
~densNumber.value_(val); // show slider value in number box
});
~densNumber = NumberBox(~window, Rect(355, 215, 50, 25));
~densNumber.align = \center;
~densNumber.value = 0.0;
~densLabel = StaticText(~window, Rect( 355, 245, 50, 25));
~densLabel.align = \center;
~densLabel.string = "dens";
~densLabel.background = Color.white;
// duration
~durSlider = Slider.new(~window, Rect(410, 10, 50, 200))
.action_({ |slider|
var val = [5.0, 25.0, \lin, 0.01].asSpec.map(slider.value);
//set bus
~duration.set(val);
~durNumber.value_(val); // show slider value in number box
});
~durNumber = NumberBox(~window, Rect(410, 215, 50, 25));
~durNumber.align = \center;
~durNumber.value = 0.0;
~durLabel = StaticText(~window, Rect( 410, 245, 50, 25));
~durLabel.align = \center;
~durLabel.string = "dur";
~durLabel.background = Color.white;
~window.front;
)