I love a lot those specific single cycle waveforms (2048 frames) and I used them a lot, in various pulsar or wavetable applications. They come from a pack and they are called “iterative sine”.
I would love to be able to learn to design them from scratch (using Signal or so), in order to be able to produce variations around ; but it’s well beyond my basic math capacities
Is someone would be able to replicate those two examples using math operations ? The second one is “smoothed”.
would be cool to have either a stateless waveshaping function or PM with control via an index for more modulation capabilites instead of loading static single cycles to a buffer, i think.
Many single cycle waveform packs like the Adventure Kid one comprise mostly sampled waveforms. Don’t be afraid to just sample it.
Parametric, formula-based design of waveforms that sound as good as sampled ones are an interesting topic, but there really isn’t a universal solution or technique, it’s just a combination of math chops and throwing things at the wall until they stick. Give me a few minutes in Desmos…
It’s better to do this by ear than by eye but it’s still fun. A lot of waveforms have a particular fractal dimension (which is closely related to spectral tilt if you were to take the FFT of it) so you want to try to carefully mix together duller and brighter waveforms. I’m using mostly FM/PM and some waveshaping here.
To match the original waveform really closely, there are some more sophisticated algorithms using optimization methods, maybe matching pursuit would be worth looking into.
Signal.sineFill might be useful.
This code adds small amounts of random harmonics to create a Signal and plays it.
Each time you run it, the posted array shows the random harmonic levels in case you hear one you like.
(
var signalSize = 2048;
var harmonicTotal = 31;
var harmonicProb = 0.25;
var minLevel = 0.05;
var maxLevel = 0.13;
a = [1] ++ harmonicTotal.collect({arg i;
harmonicProb.coin.binaryValue * minLevel.rrand(maxLevel);
});
b = Signal.sineFill(signalSize, a);
// plot, post & play signal
[a,b].plot;
a.post;
b.play(true, 0.2);
)
BTW, the ddwWavetableSynth quark includes a utility to smooth wavetables by removing upper partials (so that, at higher frequencies, it isn’t exceeding Nyquist), and a player for wavetables that have been processed this way.
(
var spectrum = Array.fill(250, { 1.0.rand });
(0..249).scramble.keep(120).do { |i| spectrum[i] = 0 };
x = Signal.sineFill(2048, spectrum);
w = WavetablePrep.new.readStream(SoundFileStream(x));
)
// see it (optional)
y = WavetablePrepGui(w).front;
// pop it over to the server
z = SoundFileStream.new;
w.writeStream(z);
s.boot;
b = Buffer.sendCollection(s, z.collection, 1);
// hear it
a = { (MultiWtOsc.ar(MouseX.kr(100, 1000, 1), bufnum: b) * 0.1).dup }.play;
a.release;
In that example, it’s up to you to provide x (the Signal containing the full-spectrum wavetable). The quark just gives you an easier way to use it.
(Note: I just pushed a couple of minor bug fixes, so if you installed this quark before, better update.)
Buffer methods sine1/sine2/sine3 might also be useful.
Here’s the previous code rewritten using sine1 with Osc.ar to play the buffer:
// prepare buffer
s = Server.local;
b = Buffer.alloc(s, 2048, 1);
)
(
// generate buffer and play
var playFreq = 220.0;
var outLevel = 0.2;
var harmonicTotal = 31;
var harmonicProb = 0.25;
var minLevel = 0.05;
var maxLevel = 0.13;
a = [1] ++ harmonicTotal.collect({arg i;
harmonicProb.coin.binaryValue * minLevel.rrand(maxLevel);
});
b.sine1(a);
b.updateInfo;
// plot, post & play
// a.plot;
b.plot;
a.post; // post the harmonic levels
{Osc.ar(b, freq: playFreq, mul: outLevel)}.play;
)
sine2 and sine3 are more flexible allowing you to set the individual frequencies and phases of the added sine waves going beyond ordinary harmonics.
Beefing up the number of harmonics while reducing aliasing:
// prepare buffer
s = Server.local;
b = Buffer.allocConsecutive(8, s, 2048, 1);
(
// generate buffer and play
var playFreq = 220.0;
var harmonicTotal = 200;
var harmonicProb = 0.25;
var minLevel = 0.05;
var maxLevel = 0.13;
a = [1] ++ harmonicTotal.collect({arg i;
harmonicProb.coin.binaryValue * minLevel.rrand(maxLevel);
});
b.do { |buf, i|
var spectrum = a.keep(a.size div: (1 << i));
buf.sine1(spectrum);
};
a.post; // post the harmonic levels
)
(
{ |amp = 0.1|
var freq = MouseX.kr(50, 2000, 1);
// considering 100 to be a baseline freq
// where the 200 partials in the first wavetable
// are OK
var index = (freq / 100).log2.clip(0, b.size - 1.001);
VOsc.ar(b[0].bufnum + index, freq: freq, mul: amp).dup
}.play;
)