001 Granular Piano
Published on: 2019-12-13
Description
Short samples from Arvo Pärt's Für Alina are granulated and triggered with movement sensors attached to the wrists and ankles of the performer. The movements are based on a feedback loop between full-body listening to and creation of the sounds.
Performed by Külli Roosna, created on 13 December 2019 at Dansearena nord in Hammerfest, Norway.
Source Code
Requires MiniBeeUtils and movement sensors. We use MiniBees, but with a bit of rewriting the MiniBeeUtils should work with any sensor data. See the full gitlab repo for more details on implementation.
To use: run the setup code first, then the sketch. If you run several sketches in a row you should only run setup once.
(
// define global variables
~numSpeakers = 2;
~masterBus = 0;
~root = "/path/to/your/project/root";
~sketchdir = ~root+/+"sketches";
~mb = (9..16); // ids of minibees
~play = Dictionary.new();
~free = Dictionary.new();
~stop = Dictionary.new();
// load in buffers, one dictionary entry per directory in the audio
// directory. These can be found in the gitlab repo.
~buf = Dictionary.new;
PathName((~root+/+"audio").standardizePath).folders.do({|folder|
s.makeBundle(nil, {
~buf.add(
folder.folderName.asSymbol -> folder.entries.collect({|file|
Buffer.readChannel(s, file.fullPath, channels: 0);
})
);
});
});
// set up MIDI, in this case a Launchpad controller
MIDIClient.init;
MIDIClient.sources.do{|device, idx|
if(device.name.contains("Launchpad MIDI 1")){
MIDIIn.connect(idx, device);
"% connected\n".postf(device.name);
~launchPadOut = MIDIOut.newByName(
"Launchpad",
"Launchpad MIDI 1"
);
~hasLaunchPad = true;
~launchPadOut.control(176, 0, 127);
~launchPadOut.latency = 0;
}
};
// some basic colors for the Launchpad
~launchPadColor = (
\off: 12,
\redLo: 13,
\redHi: 15,
\amberLo: 29,
\amberHi: 63,
\yellow: 62,
\greenLo: 28,
\greenHi: 60
);
// Evaluate the necessary SynthDefs:
SynthDef(\route, {
var sig, env;
env = EnvGen.kr(
Env.asr(
\attack.kr(0.1), 1.0,
\release.kr(0.1)
),
gate: \gate.kr(1),
doneAction: \da.kr(2)
);
sig = In.ar(\in.kr(~numSpeakers), ~numSpeakers);
sig = sig * env * \amp.kr(1, 0.1);
Out.ar(\out.kr(0), sig);
}).add;
SynthDef(\pm, {
var sig, env, freq, modenv, mod;
freq = \freq.kr(440);
modenv = EnvGen.kr(
Env.asr(
\modattack.kr(0.1), 1.0,
\modrelease.kr(0.1)
),
gate: \gate.kr(1)
);
mod = modenv * \pmindex.kr(0);
env = EnvGen.kr(
Env.asr(
\attack.kr(0.1), 1.0,
\release.kr(0.1)
),
gate: \gate.kr(1),
doneAction: \da.kr(2)
);
sig = PMOsc.ar(freq, \modfreq.kr(440), mod);
sig = sig * AmpCompA.kr(freq);
sig = sig * env * \amp.kr(0.3, 0.1);
sig = PanAz.ar(~numSpeakers, sig, \pan.kr(0), width: \width.kr(2));
Out.ar(\out.kr(0), sig);
}).add;
SynthDef(\flanger, {
var in, sig, env, maxdelay, maxrate, dsig, mixed, mix, local;
env = EnvGen.kr(
Env.asr(
\attack.kr(0.01), 1,
\release.kr(1)
),
gate: \gate.kr(1),
doneAction: \da.kr(2)
);
mix = \mix.kr(1);
maxdelay = 0.013;
maxrate = 10.0;
in = In.ar(\in.kr((~numSpeakers + ~numSubs), ~numSpeakers));
local = LocalIn.ar(~numSpeakers);
dsig = AllpassL.ar(
in + (local * \feedback.kr(0.0)),
maxdelay * 2,
LFPar.kr(
\rate.kr(0.06) * maxrate,
0,
\depth.kr(0.08) * maxdelay,
\delay.kr(0.1) * maxdelay
),
\decay.kr(0.0)
);
mixed = in + dsig;
LocalOut.ar(mixed);
sig = (1 - mix) * in + (mixed * mix);
sig = sig * env * \amp.kr(1);
Out.ar(\out.kr(0), sig);
}).add;
SynthDef(\jpverb, {
var source, sig, env, mix;
mix = \mix.kr(1);
env = EnvGen.kr(
Env.asr(
\attack.kr(0.01), 1,
\release.kr(1)
),
gate: \gate.kr(1),
doneAction: \da.kr(2)
);
source = In.ar(\in.kr(~numSpeakers), ~numSpeakers);
source = source.sum * env;
sig = JPverb.ar(
source,
\revtime.kr(1),
\damp.kr(0),
\size.kr(1),
\early.kr(0.707)
);
sig = (1 - mix) * source + (sig * mix);
sig = SplayAz.ar(~numSpeakers, sig, spread: 0.5, width:~numSpeakers);
Out.ar(\out.kr(0), sig);
}).add;
SynthDef(\eq, {
arg in, attack=0.1, release=0.1, amp=1, out=0, gate=1, da=2,
locut=20, loshelffreq=200, loshelfdb=0,
hishelffreq=1500, hishelfdb=0, hicut=20000,
peakfreq=600, peakrq=1, peakdb=0;
var sig, env;
env = EnvGen.kr(
Env.asr(attack, 1, release),
gate: gate,
doneAction: da
);
sig = In.ar(in, ~numSpeakers);
sig = BLowShelf.ar(sig, loshelffreq, db: loshelfdb);
sig = BHiShelf.ar(sig, hishelffreq, db: hishelfdb);
sig = BHiPass.ar(sig, locut);
sig = BLowPass.ar(sig, hicut);
sig = BPeakEQ.ar(sig, peakfreq, peakrq, peakdb);
sig = sig * amp * env;
Out.ar(out, sig);
}).add;
SynthDef(\grbufphasor, {
var sig, env, pos, posDev, buf, duration, trig, rate,
rateDev, startPos, endPos, pan, panDev, playbackRate;
buf = \buf.kr(0);
playbackRate = \playbackRate.kr(1);
startPos = \startPos.kr(0);
endPos = \endPos.kr(1);
duration = (endPos - startPos).abs * (
BufFrames.kr(buf) / SampleRate.ir()
);
env = EnvGen.kr(
Env.asr(
\attack.kr(0.01), 1,
\release.kr(1)
),
gate: \gate.kr(1),
doneAction: \da.kr(2)
);
trig = Impulse.kr(\grainfreq.kr(10));
posDev = \posDev.kr(0);
pos = Line.kr(
startPos,
endPos, (duration * playbackRate.reciprocal)
);
pos = (pos + TRand.kr(0.0, posDev, trig)).mod(endPos);
rate = \rate.kr(1);
rateDev = \rateDev.kr(0);
rate = rate + TRand.kr(0.0, rateDev, trig);
pan = \pan.kr(0);
panDev = \panDev.kr(0);
pan = pan + TRand.kr(0.0, panDev, trig);
pan = pan.mod(1.0);
sig = GrainBuf.ar(
~numSpeakers,
trig,
\grainsize.kr(0.2),
buf,
rate,
pos,
pan: pan
);
sig = sig * env * \amp.kr(0.3, 0.1);
Out.ar(\out.kr(0), sig);
}).add;
)
(
// create a globally accessible function that can be
// triggered when required
~arvopbind = {|fadeInTime=10, level=0.2,
fadeOutTime=10, mb, threshold=0.03|
var bufs, mbTrig;
var piano;
var eq, eqBus;
var rev, revBus, revTime=3;
var out, outBus;
outBus = Bus.audio(s, ~numSpeakers);
out = Synth(\route, [
\in, outBus,
\out, ~masterBus,
\attack, fadeInTime,
\release, fadeOutTime,
\amp, level,
]);
eqBus = Bus.audio(s, ~numSpeakers);
eq = Synth(\eq, [
\in, eqBus,
\out, outBus,
\locut, 120,
\hishelffreq, 800,
\hishelfdb, -6
]);
revBus = Bus.audio(s, ~numSpeakers);
rev = Synth(\jpverb, [
\in, revBus,
\out, eqBus,
\revtime, revTime,
\mix, 0.2,
]);
mbTrig = mb.collect{|id, idx|
MBDeltaTrig.new(
speedlim: 0.5,
threshold: threshold,
minibeeID: id,
minAmp: -20,
maxAmp: -6,
function: {|dt, minAmp, maxAmp|
// the minibee triggers a pattern. Make sure that
// this pattern terminates by including at least
// one key that stops by itself!
Pbind(
\instrument, \grbufphasor,
\buf, Prand(~buf[\arvo]),
\dur, dt.linlin(0.0, 1.0, 0.5, 2),
\attack, Pkey(\dur) * 0.2,
\release, Pkey(\dur) * Pwhite(1.0, 2.0),
\rate, Pwrand(
[-1, -0.5, 0.25, 0.25, 1],
[0.75, 0.5, 0.25, 0.1, 0.1].normalizeSum,
inf
),
\rateDev, Pwhite(0, 0.001),
\posDev, Pwhite(0, 0.01),
\playbackRate, Pwrand(
[-1, -0.5, 0.25, 0.25, 1],
[0.5, 0.5, 0.25, 0.3, 0.3].normalizeSum,
inf
),
\grainsize, Pwhite(0.01, 0.8),
\grainfreq, Pwhite(4, 20),
\db, dt.linlin(0.0, 1.0, minAmp, maxAmp),
\pan, Pwhite(-1.0, 1.0),
\panDev, Pwhite(0.0, 1.0),
\out, revBus,
).play;
};
).play;
};
~play[\arvopbind] = {|i|
if(i.isInteger){
mbTrig[~mb.indexOf(i)].play;
};
if(i.isCollection){
i.do{|item|
mbTrig[~mb.indexOf(item)].play;
}
};
};
~stop[\arvopbind] = {|i|
if(i.isInteger){
mbTrig[~mb.indexOf(i)].stop;
};
if(i.isCollection){
i.do{|item|
mbTrig[~mb.indexOf(item)].stop;
}
};
};
~free[\arvopbind] = {
out.set(\gate, 0);
fadeOutTime.wait;
mbTrig.do(_.free);
[revBus, eqBus].do(_.free);
};
}
)
// 001-granular-piano.scd
( // create a globally accessible function that can be
// triggered when required
~arvopbind = {|fadeInTime=10, level=0.2,
fadeOutTime=10, mb, threshold=0.03|
var bufs, mbTrig;
var piano;
var eq, eqBus;
var rev, revBus, revTime=3;
var out, outBus;
outBus = Bus.audio(s, ~numSpeakers);
out = Synth(\route, [
\in, outBus,
\out, ~masterBus,
\attack, fadeInTime,
\release, fadeOutTime,
\amp, level,
]);
eqBus = Bus.audio(s, ~numSpeakers);
eq = Synth(\eq, [
\in, eqBus,
\out, outBus,
\locut, 120,
\hishelffreq, 800,
\hishelfdb, -6
]);
revBus = Bus.audio(s, ~numSpeakers);
rev = Synth(\jpverb, [
\in, revBus,
\out, eqBus,
\revtime, revTime,
\mix, 0.2,
]);
mbTrig = mb.collect{|id, idx|
MBDeltaTrig.new(
speedlim: 0.5,
threshold: threshold,
minibeeID: id,
minAmp: -20,
maxAmp: -6,
function: {|dt, minAmp, maxAmp|
// the minibee triggers a pattern. Make sure that
// this pattern terminates by including at least
// one key that stops by itself!
Pbind(
\instrument, \grbufphasor,
\buf, Prand(~buf[\arvo]),
\dur, dt.linlin(0.0, 1.0, 0.5, 2),
\attack, Pkey(\dur) * 0.2,
\release, Pkey(\dur) * Pwhite(1.0, 2.0),
\rate, Pwrand(
[-1, -0.5, 0.25, 0.25, 1],
[0.75, 0.5, 0.25, 0.1, 0.1].normalizeSum,
inf
),
\rateDev, Pwhite(0, 0.001),
\posDev, Pwhite(0, 0.01),
\playbackRate, Pwrand(
[-1, -0.5, 0.25, 0.25, 1],
[0.5, 0.5, 0.25, 0.3, 0.3].normalizeSum,
inf
),
\grainsize, Pwhite(0.01, 0.8),
\grainfreq, Pwhite(4, 20),
\db, dt.linlin(0.0, 1.0, minAmp, maxAmp),
\pan, Pwhite(-1.0, 1.0),
\panDev, Pwhite(0.0, 1.0),
\out, revBus,
).play;
};
).play;
};
~play[\arvopbind] = {|i|
if(i.isInteger){
mbTrig[~mb.indexOf(i)].play;
};
if(i.isCollection){
i.do{|item|
mbTrig[~mb.indexOf(item)].play;
}
};
};
~stop[\arvopbind] = {|i|
if(i.isInteger){
mbTrig[~mb.indexOf(i)].stop;
};
if(i.isCollection){
i.do{|item|
mbTrig[~mb.indexOf(item)].stop;
}
};
};
~free[\arvopbind] = {
out.set(\gate, 0);
fadeOutTime.wait;
mbTrig.do(_.free);
[revBus, eqBus].do(_.free);
};
}
)