Skip to content

Instantly share code, notes, and snippets.

@Skoddiethecat
Created February 9, 2020 21:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Skoddiethecat/6e656dd6f81ac08fc8b38905ed1878f9 to your computer and use it in GitHub Desktop.
Save Skoddiethecat/6e656dd6f81ac08fc8b38905ed1878f9 to your computer and use it in GitHub Desktop.
Server.killAll;
s.boot;
w = Wavesets.from("sounds/a11wlk01.wav");//You can delete the code inside the parentheses, then drag and drop another file to replace it. Shift+return to evaluate. (Note: Wavesets only uses MONO files.)
//Some useful data:
w.numXings; // = the total number of wavesets in the file. When writing patterns for your Wavesets, make sure to never ask your synth to play a waveset index higher than (numXings)-1; otherwise much unpleasantmess may ensue.
w.lengths;
// lengths of all wavesets. We'll convert this data to an Array, which will make it available for someinteresting transformations later:
~wlengths = w.lengths.asArray;
w.amps; // peak amplitude of every waveset. We'll create another Array of this data.
~wamps = w.amps.asArray;
// Get data for a single waveset: frameIndex (i.e., the index of the first sample in the waveset), length (in frames/samples), duration (in seconds). Note that index valules are (usuallly) fractional; if the is no sample that equals exactly zero, the zero crossing is between samples.
w.frameFor(0, 1);
//See the waveset:
w.plot(0, 1);
(
// A wavesets loads the file into a buffer by default.
b = w.buffer;
//When we evaluate the following line of code...
Wavesets.prepareSynthDefs;
//SuperCollider loads this SynthDef under the hood for use to use. (NEWBIES: click on any class (Blue letters, first letter capitalized) or method (lowercase letters preceded by a dot) and press cmd+d to see its help file.)
SynthDef(\wvst0, { arg out = 0, buf = 0, start = 0, length = 441, playRate = 1, sustain = 1, amp=0.2, pan;
var phasor = Phasor.ar(0, BufRateScale.ir(buf) * playRate, 0, length) + start;
var env = EnvGen.ar(Env([amp, amp, 0], [sustain, 0]), doneAction: 2);
var snd = BufRd.ar(1, buf, phasor) * env;
OffsetOut.ar(out, Pan2.ar(snd, pan));
}, \ir.dup(8)).add;
)
//And now, a pattern with which to play our wavesets:
(
Pbindef(\ws1,
\instrument, \wvst0,
\startWs, Pn(Pseries(0, 1, w.numXings-1), 1),
\numWs, 1,
\playRate, 1,
\bufnum, b.bufnum,
\repeats, 1,
\amp, 0.4,
[\start, \length, \sustain], Pfunc({ |ev|
var start, length, wsDur;
#start, length, wsDur = w.frameFor(ev[\startWs], ev[\numWs]);
[start, length, wsDur * ev[\repeats] / ev[\playRate].abs]
}),
\dur, Pkey(\sustain)
).play;
)
//Before we begin tweaking our files, a word of caution: Waveset synthesis involves working with events of extremely short duration. Depending upon the operations being used, the process can become very computationally expensive. SuperCollider's openness and flexibility make it easy not just to create ammazing sounds, but also to crash your server. Take care when writing your code not to do things like create events with durations less than 0, or an amplitude of 80.
//Some of Trevor Wishart's transformations:
// waveset transposition: every second waveset, half speed
Pbindef(\ws1, \playRate, 0.5, \startWs, Pn(Pseries(0, 2, 1718), 1)).play;
// reverse every single waveset
Pbindef(\ws1, \playRate, -1, \startWs, Pn(Pseries(0, 1, 3435), 1)).play;
// reverse every 2 wavesets
Pbindef(\ws1, \numWs, 2, \playRate, -1, \startWs, Pn(Pseries(0, 2, 1718), 1)).play;
// reverse every 20 wavesets
Pbindef(\ws1, \numWs, 20, \playRate, -1, \startWs, Pn(Pseries(0, 20, 171), 1)).play;
// restore
Pbindef(\ws1, \numWs, 1, \playRate, 1, \startWs, Pn(Pseries(0, 1, 3435), 1)).play;
// time stretching
Pbindef(\ws1, \playRate, 1, \repeats, 2).play;
Pbindef(\ws1, \playRate, 1, \repeats, 4).play;
Pbindef(\ws1, \playRate, 1, \repeats, 6).play;
Pbindef(\ws1, \repeats, 1).play; // restore
// waveset omission: drop every second
Pbindef(\ws1, \numWs, 1, \amp, Pseq([0.4, 0], inf)).play;
Pbindef(\ws1, \numWs, 1, \amp, Pseq([0.4, 0.4, 0, 0], inf)).play;
Pbindef(\ws1, \numWs, 1, \amp, Pwrand([0.4, 0], [0.75, 0.25], inf)).play; // drop randomly. Play with the weights [0.75, 0.25] to hear different textures. (Weights must add up to 1.0.)
Pbindef(\ws1, \numWs, 1, \amp, 0.4, \startWs, Pn(Pseries(0, 1, 3435), 1) ).play; // restore
// waveset shuffling (randomize waveset order +- 5, 25, 125)
Pbindef(\ws1, \startWs, Pn(Pseries(0, 1, 3435), 1) + Pfunc({ 5.rand2 })).play;
Pbindef(\ws1, \startWs, Pn(Pseries(0, 1, 3435), 1) + Pfunc({ 25.rand2 })).play;
Pbindef(\ws1, \startWs, Pn(Pseries(0, 1, 3435), 1) + Pfunc({ 125.rand2 })).play;
//dynamic compression/expansion: Here, we draw from our ~wamps Array that we created earlier and exponentiate the values in order to narrow or expand the range. Pindex links the index of the startWs to the corresponding value in ~wamps. "**" means "to the power of...".
Pbindef(\ws1, \amp, Pindex(~wamps, Pkey(\startWs))**0.01).play;//compression
Pbindef(\ws1, \amp, Pindex(~wamps, Pkey(\startWs))**5).play;//expansion
Pbindef(\ws1, \amp, 0.4).play;//restore
//substitution: replace the with another waveform, while keeping frequncy and amplitude of the original soundfile. The code in the [\start, \length, \sustain] section of this pattern is pretty involved. For beginners, it's probably most expedient not to worry about the details in that section and to focus on how to use this code block to insert different waveforms.
//First, create the waveform to substitute:
c = Buffer.alloc(s, 512); //an empty Buffer
c.sendCollection(Signal.sineFill(512, [1]));//fill it with a sine wave
c.plot;//check it out
(
Pbindef(\ws1).clear;
Pbindef(\ws1,
\instrument, \wvst0,
\startWs, Pn(Pseries(0, 1, w.numXings-1), 1),
\numWs, 1,
\playRate, 1,
\buf, c.bufnum, // sine wave
\repeats, 1,
\amp, 0.4,
[\start, \length, \sustain], Pfunc({ |ev|
var origRate, start, length, wsDur;
origRate = ev[\playRate];
// get orig waveset specs
#start, length, wsDur = w.frameFor(ev[\startWs], ev[\numWs]);
// adjust playrate for different length of substituted wave
ev[\playRate] = origRate * (512 / length);
// get amplitude from waveset, to scale full volume sine wave
ev[\amp] = w.ampFor(ev[\startWs], ev[\numWs]);
[0, 512, wsDur * ev[\repeats] / origRate.abs]
}),
\dur, Pkey(\sustain)
).play;
)
// clearer sinewave-ish segments
Pbindef(\ws1, \playRate, 1, \repeats, 2).play;
Pbindef(\ws1, \playRate, 1, \repeats, 6).play;
Pbindef(\ws1).stop;
// Now, try some different waveforms with the patterns above:
c.sendCollection(Signal.sineFill(512, 1/(1..4).squared.scramble));//adding some harmonics to our sine wave. You can play around with this one by removing the .squared &/or .scramble methods; you can change the number of harmonics by changing the 4 to another number. Note that .scramble yields a different waveform each time it is executed.
c.plot;
c.sendCollection(Signal.rand(512, -1.0, 1.0));//random/noise
c.plot;
c.sendCollection(Signal.chebyFill(512, [1]));//sawtooth wave
c.plot;
c.sendCollection(Signal.sineFill(512, [1]));//restore to original sine wave
//Now, let's restore our Pbindef to its original settings before moving on...
(
Pbindef(\ws1).clear;
Pbindef(\ws1,
\instrument, \wvst0,
\startWs, Pn(Pseries(0, 1, w.numXings-1), 1),
\numWs, 1,
\playRate, 1,
\bufnum, b.bufnum,
\repeats, 1,
\amp, 0.4,
[\start, \length, \sustain], Pfunc({ |ev|
var start, length, wsDur;
#start, length, wsDur = w.frameFor(ev[\startWs], ev[\numWs]);
[start, length, wsDur * ev[\repeats] / ev[\playRate].abs]
}),
\dur, Pkey(\sustain)
);
)
Pbindef(\ws1).play;//just to confirm that everything is back in order.
Pbindef(\ws1).stop;
//harmonic distortion: add double- and triple-speed wavesets.
//This function creates and plays 3 Pbindefs: \ws1, \ws2, & \ws3.
(
Pbindef(\ws1).clear;
3.do({|i| Pbindef(("ws"++(i+1)).asSymbol,
\instrument, \wvst0,
\startWs, Pn(Pseries(0, 1, 3435), 1),
\numWs, 1,
\playRate, i+1, //yields 1, 2, & 3, respectively
\bufnum, b.bufnum,
\repeats, i+1, //repeats matches playRates so that all 3 patterns play in sync
\amp, 0.13,//amp is 1/3 previous setting because we're playing 3 patterns at once
[\start, \length, \sustain], Pfunc({ |ev|
var start, length, wsDur;
#start, length, wsDur = w.frameFor(ev[\startWs], ev[\numWs]);
[start, length, wsDur * ev[\repeats] / ev[\playRate].abs]
}),
\dur, Pkey(\sustain)
).play;
});
)
//try different harmonic patterns:
3.do({|i| Pbindef(("ws"++(i+1)).asSymbol, \playRate, (i+1)*0.8, \repeats, Pkey(\playRate)).play});
//play with the playRate multiplier (0.8). Pkey(\playRate) matches the # of repeats to the playRate. Note that Wavesets allows for fractional repeats.
//averaging: Find the average duration of all wavesets
Pbindef(\ws1, \dur, w.avgLength/44100).play;
//Note: if working with a file with a different sample rate (e.g. 48000 or 96000), change the divisor to match the sample rate.
//Now, we'll adjust the playRate of each waveset to match the average dur:
Pbindef(\ws1, \playRate, Pindex(~wlengths, Pkey(\startWs))/w.avgLength, \dur, w.avgLength/44100).play;
Pbindef(\ws1, \playRate, 1, \dur, Pkey(\sustain)).play; //restore to normal
//enveloping/amplitude modulation:
Pbindef(\ws1, \amp, Pseg([0, 1, 0], [0.05, 0.05], \sin, inf)).play;//sine wave, 10Hz (0.05+0.05 = 0.1).
Pbindef(\ws1, \amp, Pseg([0, 1], [0.1], \lin, inf)).play;//sawtooth
Pbindef(\ws1, \amp, Pseg([0, 1], [0.01], \lin, inf)).play;//sawtooth, 100Hz.
Pbindef(\ws1, \amp, Pseg([0, 1], [0.1], \sqr, inf)).play;//square
//The preceding patterns modify the amplitude at a constant frequency. If you want to modify the amplitude every n wave cycles (rather than every n seconds), use a UGen pattern like PSinOsc. (cmd+d on PSinOsc, then click the UGenPatterns link to learn more about them).
Pbindef(\ws1, \amp, PSinOsc(160).abs).play;
Pbindef(\ws1, \amp, PSinOsc(40).abs).play;
//Note that the frequency argument (40) for the PSinOsc refers to the number of events it takes to complete a cycle, not the length of time. Lower numbers are therefore faster.
Pbindef(\ws1, \amp, 0.4).play;//restore
//waveset transfer: combine timing from one source with waveforms from another.
//First, we'll need to create a second Wavesets object from another file.
x = Wavesets.from();//drag and drop a mono file between the parentheses
d = x.buffer;//assign a variable to x's Buffer.
(
Pbindef(\ws1, \buf, b.bufnum, [\start, \length, \sustain], Pfunc({ |ev|
var start, length, wsDur;
#start, length, wsDur = x.frameFor(ev[\startWs], ev[\numWs]);
[start, length, wsDur * ev[\repeats] / ev[\playRate].abs]
})
).play;
)
//In the above code w.frameFor has been replaced with x.frameFor, which grafts x's timing onto w's waveform. Depending on the two files being used, the results may be magical or total garbage.
//To switch them up, i.e. use w's timing with d's waveform, run the following:
(
Pbindef(\ws1, \buf, d.bufnum, [\start, \length, \sustain], Pfunc({ |ev|
var start, length, wsDur;
#start, length, wsDur = w.frameFor(ev[\startWs], ev[\numWs]);
[start, length, wsDur * ev[\repeats] / ev[\playRate].abs]
})
).play;
)
//Try dropping in new files:
y = Wavesets.from();//replace w.frameFor with y.frameFor in the pattern above.
//OR...
e = y.buffer;//replace d.bufnum with e.bufnum
//Mix and match b, c, d, or e, and w, x, or y.
//restore our original pattern:
(
Pbindef(\ws1, \buf, b.bufnum, [\start, \length, \sustain], Pfunc({ |ev|
var start, length, wsDur;
#start, length, wsDur = w.frameFor(ev[\startWs], ev[\numWs]);
[start, length, wsDur * ev[\repeats] / ev[\playRate].abs]
})
).play;
)
//interleaving: alternate different buffers:
Pbindef(\ws1, \buf, Pstutter(40, Pseq([b.bufnum, d.bufnum], inf))).play;
//change the 40 to modify the change rate (smaller numbers are faster)
Pbindef(\ws1, \buf, b.bufnum).play;//back to normal
//alternate timings:
(
Pdefn(\wsus, Pfuncn({ |ev| var start, length, wsDur; #start, length, wsDur = w.frameFor(ev[\startWs], ev[\numWs]); [start, length, wsDur * ev[\repeats] / ev[\playRate].abs]}, 40));
Pdefn(\xsus, Pfuncn({ |ev| var start, length, wsDur; #start, length, wsDur = x.frameFor(ev[\startWs], ev[\numWs]); [start, length, wsDur * ev[\repeats] / ev[\playRate].abs]}, 40));
Pbindef(\ws1, [\start, \length, \sustain], Pseq([Pdefn(\wsus), Pdefn(\xsus)], inf)).play;
)
//Mix and match again, subsituting different sources for bufnum and frameFor.
//to restore our original pattern:
Pbindef(\ws1, \buf, b.bufnum, [\start, \length, \sustain], Pfunc({ |ev| var start, length, wsDur; #start, length, wsDur = w.frameFor(ev[\startWs], ev[\numWs]); [start, length, wsDur * ev[\repeats] / ev[\playRate].abs]})).play;
//interpolation: time-stretching method morphing from one waveform to the next. This is a non-realtime method, which can take several minutes to process for longer files or extreme interpolation values. Wait for the message "Ready!" in the post window.
(
Routine.run{
~ws1array = Array.fill((w.numXings)-1, {|i| w.signal.copyRange(w.xings[i], w.xings[i+1]).as(Array)});
s.sync;
~ws1int = ~ws1array.interpolate(4).flat;
s.sync;
~ws1intbuf = Buffer.loadCollection(s, ~ws1int);
s.sync;
"Ready!".postln;
};
)
//When ready:
{PlayBuf.ar(1, ~ws1intbuf.bufnum)!2}.play;
//To play with different files, substitute x or any other variable assigned to a Wavesets object for all of the w's in the ~ws1array line.
//To try different time-stretching scales, change the 4 in the ~ws1int line. The number entered equals the factor by which the file will be stretched.
//Now, try combining methods in one pattern!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment