Wavesets analyse soundfiles into short fragments for granular synthesis
Inherits from: Object
Wavesets analyses soundfiles into short fragments called wavesets, and keeps these waveset data.
It can support a variety of waveset based synthesis instruments.
By Trevor Wishart's definition, a waveset is a segment of an audio signal between
one non-positive to positive zero crossing and the next. [ see T. Wishart (1994): Audible Design. ]
Note that this definition only applies to mono signals.
Creation / Class Methods
*from (path, name, toBuffer)
Read a soundfile, analyse the signal, read the file to a buffer
path - the path to the soundfile
name - a name by which to keep the new wavesets in a global dictionary
toBuffer - a boolean whether to load the file to a buffer immediately.
// a first little example
Server.default = s = Server.internal; s.boot;
// make a wavesets from a soundfile
w = Wavesets.from("sounds/a11wlk01.wav");
w.dump; // contains mainly analysis data
w.plot(200, 1); // plot a single waveset
w.signal.copyRange(w.xings[600], w.xings[601]).plot;
w.plot(600, 1); // a single
w.plot(600, 5); // a group of five contiguous wavesets
w.buffer;
w.buffer.play;
Wavesets.prepareSynthDefs;
// startWs
w.eventFor(startWs: 600, length: 5, repeats: 2).postln.play;
w.eventFor(startWs: 600, length: 2, playRate: 1, repeats: 5).postln.play;
w.eventFor(startWs: 600, length: 2, playRate: 0.5, repeats: 5).postln.play;
w.eventFor(700, 20, 5, 1).play;
(
fork {
666.do { |i|
var ev = w.eventFor(i * 5, 2, 5, exprand(0.5, 1.0));
ev.put(\pan, 1.0.rand2).play;
ev.sustain.wait;
}
};
)
*new (name, sig, sampleRate)
Make a Wavesets from a signal directly - not typical usage, but may be needed sometimes.
name - a name by which to keep the new wavesets in a global dictionary
sig - the signal to be analysed Explanation of sig. Default value is nil. Other information.
sampleRate - Explanation of sampleRate. Default value is nil. Other information.
all A dictionary where all the wavesets are kept by name.
Existing wavesets in .all are not re-analysed.
Wavesets.all;
*at (key) Access a Wavesets by its name.
key - The name to look up
g = Wavesets.at('a11wlk01.wav');
*clear Clear the global dictionary
Wavesets.clear;
*minLength set the shortest length (in frames) that will still be treated as a waveset. Shorter
wavesets are merged with their neighbors.
*prepareSynthDefs
prepare some Synthdefs to be used with wavesets.
Accessing Instance and Class Variables
signal the audio signal that was analysed.
name the wavesets name in the global dictionary
buffer a buffer on the server that was created from the same soundfile
numFrames the number of frames
sampleRate the sample rate of the signal, default is Server.default.sampleRate
The following variables are analysis result lists
xings all integer indices of the zero crossings found
numXings total number of zero crossings found
lengths lengths of all wavesets
amps peak amplitude of every waveset
maxima indices of positive maximum value in every waveset
minima indices of negative minimum value in every waveset
fracXings the calculated fractional zerocrossing points.
this allows for more precise pitch information
and waveset transitions, resulting in smoother sound.
fracLengths fractional lengths - in effect, waveset 'pitch'.
These are overall statistics of the entire wavesets
minSet shorted waveset
maxSet longest waveset
avgLength average length of all wavesets
sqrAvgLength weighted average length - so bigger wavesets have a larger impact
minAmp softest waveset amplitude
maxAmp loudest waveset amplitude
avgAmp average amplitude of the entire waveset
sqrAvgAmp weighted average of (squared) amplitude of the entire waveset
Some utility instance methods:
toBuffer(server) load the analysed soundfile to the buffer.
frameFor(startWs, numWs, useFrac)
calculate startFrame, length in frames, and duration
for a waveset or group. useFrac = true means use fractional crossings.
true by default.
ampFor(startWs, length) maximum amp that occurs in a waveset or group.
eventFor(startWs, numWs, repeats, playRate, useFrac)
generate an event for a given combination of start waveset index,
number of wavesets, repeats, playback rate, and use of fractional crossings.
plot (startWs, length) plot a waveset or group for a given waveset index and length.
Examples
The simplest usage is to ask the waveset to prepare an event for you.
w = Wavesets.from("sounds/a11wlk01.wav");
(
fork {
666.do { |i|
var ev = w.eventFor((i * 5).postln, 2, 5, exprand(0.5, 1.0));
ev.put(\pan, 1.0.rand2).play;
ev.sustain.wait;
}
};
)
// play a single waveset or waveset group by hand
(
{ var startFr, endFr, dur;
startFr = w.xings[800];
endFr = w.xings[820];
dur = endFr - startFr / w.buffer.sampleRate;
dur.postln;
BufRd.ar(1, w.buffer, Line.ar(startFr, endFr, dur, doneAction: 2))
}.play;
)
loop buffer segments with a Phasor:
(
x = { arg start = 0, end = 128, playRate = 1;
BufRd.ar(1, w.buffer,
Phasor.ar(0, playRate * BufRateScale.kr(w.buffer), start, end)
)
}.scope(zoom: 8);
)
x.set(\start, 0, \end, 1024); // just some random length, may buzz
x.set(\start, w.xings.at(100), \end, w.xings.at(101));
x.set(\start, w.xings.at(101), \end, w.xings.at(102));
x.set(\start, w.xings.at(100), \end, w.xings.at(200));
x.set(\start, w.xings.at(780), \end, w.xings.at(800));
x.set(\playRate, 0.25);
x.set(\playRate, 1);
x.release;
Doing Some Task (optional)
Examples
To play a waveset (or group) for a precise number of repetitions,
one can use a SynthDef with a hard cutoff envelope, as below.
Note that adding an offset outside the phasor works better;
Phasor.ar(0, playRate, start, end) is sometimes off by a few samples.
This is likely a 32bit float precision problem.
( // the simplest synthdef as implented in *prepareSynthDefs:
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)).memStore;
)
Synth("wvst0", [\bufnum, b, \start, 0, \length, 5000, \sustain, 0.1]);
// do the math by hand to understand it:
(
var startWs = 100, length = 6, rep = 10, playRate = 0.5;
var startframe, endframe, sustain;
startframe = w.xings[startWs];
endframe = w.xings[startWs + length];
sustain = (endframe - startframe) * rep / playRate / w.sampleRate;
Synth("wvst0", [
\bufnum, w.buffer,
\start, startframe,
\length, endframe - startframe,
\playRate, playRate,
\sustain, sustain,
\amp, 1
]);
)
// the same done with eventFor:
w.eventFor(100, 6, repeats: 10, playRate: 0.5).put(\amp, 1).play;
(
Task({
300.do({ arg i;
var ev = w.eventFor(100 + i, 2, 10, 1);
ev.putPairs([\pan, [-1, 1].choose, \amp, 0.5]);
ev.play;
(ev.sustain).wait;
});
}).play;
)
x = a11wlk01-44_1.aiff;
// compare fractional and integer xings:
// especially for high frequency signals,
// fractional does slightly cleaner looping and finer pitch gradations.
// - better test below with ordering wavesets by length -
(
Task({
[true, false].do { |usefrac|
(1250..1500).do { arg i;
var ev = w.eventFor(i, 1, 100, 1, useFrac: usefrac);
ev.putPairs([/*\pan, 0 [-1, 1].choose,*/ \amp, 0.5]);
ev.play;
(ev.sustain).wait;
};
1.wait;
};
}).play;
)
// some variants waveset timestretch
(
Task({
// segments of 10 wavesets, step by 1 => 10x longer
(300, 301 .. 900).do { arg start;
var ev = w.eventFor(start, numWs: 10, repeats: 1);
ev.putPairs([\amp, 0.5]);
ev.play;
(ev.sustain).wait;
};
1.wait;
// 1 waveset at a time, loop 10 times - much more 'pitch beads'-like
(300, 301 .. 900).do { arg start;
var ev = w.eventFor(start, numWs: 1, repeats: 10);
ev.putPairs([\amp, 0.5]);
ev.play;
(ev.sustain).wait;
};
}).play;
)
// play them sorted by (integer) waveset length:
w.lengths.plot; // lengths are very irregular
o = w.lengths.order;
(
Task({
var start, end, startFr, endFr, dur, repeats;
var order;
order = w.lengths.order;
[\false, \true].do { |useFrac|
if (useFrac) { "fractional crossings - better pitch resolution" } {
"integer waveset borders - pitches quantized to integer sample lengths"
}.postln;
order.do({ arg start;
var ev = w.eventFor(start, numWs: 1, repeats: 5);
ev.putPairs([\amp, 0.5]);
ev.play;
(ev.sustain).wait;
});
}).play;
)