# Generate those single cycle waveforms on SC

Hello,

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”.

Thanks a lot

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.

Exactly ! How is it possible ?

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…

``````import numpy as np
import matplotlib.pyplot as plt

def main():
t = np.linspace(0, 1, 10_000, endpoint=False)
x = np.sin(2 * np.pi * t + 1.5 * np.sin(2 * np.pi * t))
x = x + (
np.tanh(np.sin(3 * 2 * np.pi * t + np.tanh(np.sin(1 * 2 * np.pi * t) * 10) * 0.5) * 2) * 0.7
* np.tanh(np.sin(5 * 2 * np.pi * t + np.tanh(np.sin(31 * 2 * np.pi * t) * 30) * 0.03) * 3)
* np.tanh(np.sin(1 * 2 * np.pi * t + np.tanh(np.sin(41 * 2 * np.pi * t) * 3) * 0.01) * 3.6)
)
x = np.tanh(x * 1.1)
x = x + (
np.tanh(np.sin(3 * 2 * np.pi * t + np.tanh(np.sin(13 * 2 * np.pi * t) * 3) * 0.5) * 2) * 0.6
* np.tanh(np.sin(7 * 2 * np.pi * t + np.tanh(np.sin(71 * 2 * np.pi * t) * 7) * 0.03) * 3)
* np.tanh(np.sin(9 * 2 * np.pi * t + np.tanh(np.sin(33 * 2 * np.pi * t) * 8) * 0.05) * 3.6)
* 0.3
)
x = x + np.tanh(x * 1.0)
plt.plot(t, x)
plt.show()

if __name__ == "__main__":
main()

``````

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.

2 Likes

Wow, thanks a lot Nathan. But would it be too much to translate that into SC to fill a Buffer ? I don’t know what to do from that.

This is what I can come up with before my wife yells at me.

``````
(

{

a = LFSaw.ar(-200, 1, 1)+

SinOsc.ar(-400, 1pi, 0.5);

a = LPF.ar(a.fold(-1,1).fold(-0.75, 0.75),3000);

}.plot

)

(

{

a = LFSaw.ar(-200, 1, 1)+

SinOsc.ar(-400, 1pi, 0.5);

a = LPF.ar(a.fold(-1,1).fold(-0.75, 0.75),3000);

a+LFNoise1.ar(10000, 0.1)

}.plot

)

``````

Sam

LOL. Thanks a lot Sam.

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);
)
``````
1 Like

Superb and clear little piece of code. That is exactly what I was looking for, thanks a lot.

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);

)

// 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.)

hjh

1 Like

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.

1 Like

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;
)
``````

hjh

2 Likes

That’s great, thanks a lot James, Paul and Sam.