Skip to content

Instantly share code, notes, and snippets.

@LFSaw
Last active November 28, 2024 15:40
Show Gist options
  • Save LFSaw/e0f8a2230882a2b6fa43afd53724b3c7 to your computer and use it in GitHub Desktop.
Save LFSaw/e0f8a2230882a2b6fa43afd53724b3c7 to your computer and use it in GitHub Desktop.
less than one minutes generative soundscsape
// wait and hear, denmark
// accompanying 4-channel soundscape for the wait and hear presentation at Sound Art Lab in 2024
// 2024, Till Bovermann
(
q = q ? ();
q.numChans = 4; // needs to be an even number
q.playbackDurationRange = ControlSpec(30, 50); // seconds
q.freqRange = ControlSpec(10, 20000, \exp); // Hz
// s.options.memSize_(600 * 1024 * 1024);
s.options.numOutputBusChannels = q.numChans;
// s.options.device = "Scarlett 6i6 USB";
s.options.device = "BlackHole 64ch";
s.reboot;
s.meter;
s.makeWindow;
/////////// load buffers
q.dirname = thisProcess.nowExecutingPath.dirname;
q.fileNames = (q.dirname +/+ "_renders/*.wav").pathMatch;
q.loopFileNames = (q.dirname +/+ "_renders/loops/*.wav").pathMatch;
// assume all soundfiles to have the same length, samplerate and number of channels
s.waitForBoot{
q.soundfile = SoundFile.openRead(q.fileNames[0]);
q.soundfile.close;
// cue all the files at random positions
q.allBuffers = q.fileNames.collect({ |path|
Buffer.cueSoundFile(s, path, rrand(0, q.playbackDurationRange.map(1)), q.soundfile.numChannels)
});
q.loopBuffers = q.loopFileNames.collect({ |path|
Buffer.read(s, path)
});
}
)
////////////////// synthesis definitions
(
// a Synthdef that plays back a buffer for a given duration using DiskIn
SynthDef(\playback, {|out = 0, buf = 0, dur = 10, attack = 0.5, decay = 0.5, pos = 0, amp = 0.1, spread= 0.5, width = 2|
var snd, env;
var lpFreq = \lpFreq.kr(2000);
var lpRq = \lpRq.kr(1);
var hpFreq = \hpFreq.kr(200);
var hpRq = \hpRq.kr(1);
// create an envelope with attack and decay
env = Env.linen(attack, dur - (attack + decay), decay).ar(doneAction: 2);
// buffer playback
snd = DiskIn.ar(q.soundfile.numChannels, buf);
// simple musical filter
// snd = RLPF.ar(snd, lpFreq, lpRq);
// snd = RHPF.ar(snd, hpFreq, hpRq);
// panning
snd = SplayAz.ar(
numChans: q.numChans,
inArray: snd * env,
spread: spread,
center: pos,
level: amp,
width: width,
orientation: 0.5
);
Out.ar(out, snd.tanh);
}).add;
// some sines
SynthDef(\sine, {|out = 0, freq = 100, dur = 10, attack = 0.5, decay = 0.5, pos = 0, amp = 0.1, spread= 0.5, width = 2|
var snd, env, rev, revEnv;
var lpFreq = \lpFreq.kr(2000);
var lpRq = \lpRq.kr(1);
var hpFreq = \hpFreq.kr(200);
var hpRq = \hpRq.kr(1);
// create an envelope with attack and decay
env = Env.linen(attack, dur - (attack + decay), decay).ar(doneAction: 0);
revEnv = Env.linen(attack, dur - (attack + decay), decay + 20).ar(doneAction: 2);
freq = (freq + [0, \freqSpread.kr(10)]) * [LFNoise2.kr(Rand(0.5, 2)).range(0.95, 1.0), LFNoise2.kr(Rand(0.5, 2)).range(0.95, 1.0)];
freq = freq * AmpCompA.kr(freq, 25);
// sinewave
snd = SinOscFB.ar(freq, \fb.kr(0.5));
// simple musical filter
// snd = RLPF.ar(snd, lpFreq, lpRq);
// snd = RHPF.ar(snd, hpFreq, hpRq);
// panning
snd = SplayAz.ar(
numChans: q.numChans,
inArray: snd * env,
spread: spread,
center: pos,
level: amp,
width: width,
orientation: 0.5
);
rev = AdCVerb.ar(snd, 10, nOuts: q.numChans) * revEnv;
Out.ar(out, (snd + (0.25 * rev)).tanh * 0.5);
}).add;
// some noise
SynthDef(\noise, {|out = 0, dur = 10, attack = 0.5, decay = 0.5, pos = 0, amp = 0.1, spread= 0.5, width = 2|
var snd, env;
var lpFreq = \lpFreq.kr(2000);
var lpRq = \lpRq.kr(1);
var hpFreq = \hpFreq.kr(200);
var hpRq = \hpRq.kr(1);
// create an envelope with attack and decay
env = Env.linen(attack, dur - (attack + decay), decay).ar(doneAction: 2);
// noise
snd = {PinkNoise.ar}!2;
// simple musical filter
snd = LPF.ar(snd, lpFreq);
snd = HPF.ar(snd, hpFreq);
// panning
snd = SplayAz.ar(
numChans: q.numChans,
inArray: snd * env,
spread: spread,
center: pos,
level: amp,
width: width,
orientation: 0.5
);
Out.ar(out, snd.tanh);
}).add;
)
//////////////// playback routine for oneshots
(
q.playingBuffers = Set[];
q.allIndices = Array.iota(q.allBuffers.size).asSet;
q.cueAndPlay = {
var idx, buf, bufFilename, dur, pos, lpFreqStart, lpFreq, hpFreq;
idx = (q.allIndices - q.playingBuffers).choose;
idx.isNil.if({ "all buffers currently playing".postln; }, {
buf = q.allBuffers[idx];
bufFilename = q.fileNames[idx];
dur = q.playbackDurationRange.map(1.0.rand);
pos = rrand(0, 2.0);
lpFreqStart = 1.0.sum3rand + 1 /2; // randomize the filter frequency
// where to cut lows
hpFreq = q.freqRange.map(lpFreqStart);
// where to cut highs
lpFreq = q.freqRange.map(rrand(lpFreqStart, 1.0));
r {
q.playingBuffers.add(idx);
"playing buffer % % for % seconds at %".format(idx, bufFilename.basename, dur, pos).postln;
// buf.cueSoundFile(bufFilename, (q.soundfile.duration - dur).rand * q.soundfile.sampleRate, q.soundfile.numChannels);
buf.cueSoundFile(bufFilename, 0, q.soundfile.numChannels);
1.wait; // wait for the buffer to be ready
Synth(\playback, [
\buf, buf,
\dur, dur,
\attack, 10,
\decay, 15,
\pos, pos.round(0.25),
// \amp, ((1.0.sum3rand + 1 /2) * 0.5 + 0.5).postln,
\amp, rrand(0.7, 1),
\spread, 0.75,
\width, 2,
// \lpFreq, lpFreq,
// \hpFreq, hpFreq,
// \lpRq, [0.1, 2].asSpec.map(1.0.sum3rand + 1 /2),
// \hpRq, [0.1, 2].asSpec.map(1.0.sum3rand + 1 /2),
]);
// wait for a longer period to make sure the sound will show up again only after a longer period of time
(dur * 5).wait;
q.playingBuffers.remove(idx);
}.play;
});
};
)
///////////////////////// playback loops
(
// all one-shots randomly
Tdef(\playback, {
loop{
rrand(15, 25).wait;
q.cueAndPlay
}
}).play;
)
Tdef(\playback).stop
// play back a random buffer for a random duration
// Synth(\playback, [\buf, q.allBuffers.choose, \dur, q.playbackDurationRange.map(1.0.rand).postln]);
(
// a low sine
Tdef(\sineplayerLow, {
loop{
var freq = exprand(25, 100.0);
"Sine Low".postln;
Synth(\sine, [\dur, q.playbackDurationRange.map(1.0.rand), \pos, 2.0.rand, \freq, freq, \amp, rrand(0.01, 0.06), \spread, 1, \attack, 20, \decay, 20, \fb, rrand(0.2, 0.7), \freqSpread, rrand(1.0, 7)].postln);
rrand(60, 120.0).wait;
}
}).play
)
Tdef(\sineplayerLow).stop
(
// a high sine
Tdef(\sineplayerHigh, {
loop{
var freq = exprand(1000, 1500.0);
"Sine High".postln;
Synth(\sine, [\dur, q.playbackDurationRange.map(1.0.rand), \pos, 2.0.rand, \freq, freq, \amp, rrand(0.005, 0.008) * 0.5, \spread, 1, \attack, 20, \decay, 20, \fb, rrand(0.2, 0.7), \freqSpread, rrand(1.0, 7)].postln);
rrand(120, 160.0).wait;
}
}).play
)
Tdef(\sineplayerHigh).stop
// q.playingBuffers = q.allIndices.copy;
(
// some noise
Tdef(\noiseplayer, {
loop{
rrand(60, 120.0).wait;
"Noise".postln;
Synth(\noise, [\dur, q.playbackDurationRange.map(1.0.rand), \pos, 2.0.rand, \amp, rrand(0.01, 0.08), \spread, 1, \attack, 20, \decay, 20, \fb, rrand(0.2, 0.7), \freqSpread, rrand(1.0, 7), \hpFreq, exprand(20, 600.0), \lpFreq, exprand(500, 5000.0)].postln);
}
}).play
)
Tdef(\noiseplayer).play
Tdef(\noiseplayer).stop
(
// a rumble loop
Ndef(\looper).fadeTime = 10;
Ndef(\looper, {
var trigMute = Impulse.ar(1/LFNoise1.kr(0.1).range(220, 400));
(
{PlayBuf.ar(2, q.loopBuffers.first, 1, loop: 1, startPos: 0.125 * q.loopBuffers.first.numFrames)}!(q.numChans/2)).flat
* ({LFNoise2.kr(LFNoise1.kr(0.1).range(0.1, 0.03)).range(0.5, 1.0)}!q.numChans
) * Env([1, 0, 0, 1], [20, 60, 60]).ar(gate: trigMute.poll(trigMute))
});
Ndef(\looper).play;
Ndef(\looper).vol = 0.25;
)
// s.makeWindow;
Ndef(\looper).edit

Till Bovermann, 2024 https://tai-studio.org/projects/1-min-wait-and-hear/

A quadrophonic generative soundscape made from one-minute recordings during the Wait and Hear residency at Sound Art Lab, Struer 2024. Originally, it accompanied a listening and discursive session at the Sound Art Lab on 2024-10-08.

Field recordings and a downmix of the soundscape are available at archive.org.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment