You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

166 lines
6.3 KiB
Plaintext

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// https://nathan.ho.name/posts/pulsar-synthesis/
(
{
var snd, freq, formantFreq, pulsaretPhase;
freq = 100;
formantFreq = 400;
pulsaretPhase = LFSaw.ar(freq, iphase: 1).linlin(-1, 1, 0, 1) * formantFreq / freq;
pulsaretPhase.dup;
}.play;
)
pulsaretPhase is then used in two ways. First, if its greater than or equal to 1, the pulsaret has stopped playing and silence is produced. Second, the pulsaretPhase indexes into the pulsaret signal. Heres a sine wave pulsaret with proper gating:
(
{
var snd, freq, formantFreq, pulsaretPhase;
freq = 100;
formantFreq = 400;
pulsaretPhase = LFSaw.ar(freq, iphase: 1).linlin(-1, 1, 0, 1) * formantFreq / freq;
snd = sin(pulsaretPhase * 2pi) * (pulsaretPhase < 1);
snd.dup;
}.play;
)
We can generalize to multiple concatenated sine waves by multiplying the term inside the sin(...) by a value well call sineCycles:
(
{
var snd, freq, formantFreq, sineCycles, pulsaretType, pulsaretPhase;
freq = 100;
formantFreq = 400;
sineCycles = 4;
pulsaretPhase = LFSaw.ar(freq, iphase: 1).linlin(-1, 1, 0, 1) * formantFreq / freq;
snd = sin(pulsaretPhase * 2pi * sineCycles.floor) * (pulsaretPhase < 1);
snd.dup;
}.play;
)
We can apply an approximately exponentially decaying window to the pulsaret, as opposed to the rectangular one weve been using so far:
(
{
var snd, freq, formantFreq, sineCycles, pulsaretPhase, window;
freq = 100;
formantFreq = 200;
sineCycles = 4;
pulsaretPhase = LFSaw.ar(freq, iphase: 1).linlin(-1, 1, 0, 1) * formantFreq / freq;
window = pulsaretPhase.lincurve(0, 1, 1, 0, -4);
snd = sin(pulsaretPhase * 2pi * sineCycles.floor) * window * (pulsaretPhase < 1);
snd.dup;
}.play;
)
Thats enough plotting. Lets hear it with random modulation of all the parameters we discussed, including amplitude and pan position:
(
{
var snd, freq, formantFreq, sineCycles, pulsaretPhase, window, randomLFO;
randomLFO = { LFNoise2.kr(5) };
freq = randomLFO.().linexp(-1, 1, 1, 1000);
formantFreq = randomLFO.linexp(-1, 1, 2, 8000);
sineCycles = randomLFO.().linlin(-1, 1, 1, 4);
pulsaretPhase = LFSaw.ar(freq, iphase: 1).linlin(-1, 1, 0, 1) * formantFreq / freq;
window = pulsaretPhase.lincurve(0, 1, 1, 0, -4);
snd = sin(pulsaretPhase * 2pi * sineCycles.floor) * window * (pulsaretPhase < 1);
snd = snd * randomLFO.().linlin(-1, 1, 0, 1);
snd = Pan2.ar(snd, randomLFO.() * 0.4);
snd = snd * -5.dbamp;
snd ! 2;
}.play(fadeTime: 0);
)
As discussed in Microsound, lets use three separate formants and pan them around the stereo field separately:
(
{
var snd, freq, formantCount, formantFreq, sineCycles, pulsaretPhase, window, randomLFO, randomLFOs;
formantCount = 3;
randomLFO = { LFNoise2.kr(5) };
randomLFOs = { { randomLFO.() } ! formantCount };
freq = randomLFO.().linexp(-1, 1, 1, 1000);
formantFreq = randomLFOs.().linexp(-1, 1, 2, 8000);
sineCycles = randomLFOs.().linlin(-1, 1, 1, 4);
pulsaretPhase = LFSaw.ar(freq, iphase: 1).linlin(-1, 1, 0, 1) * formantFreq / freq;
window = pulsaretPhase.lincurve(0, 1, 1, 0, -4);
snd = sin(pulsaretPhase * 2pi * sineCycles.floor) * window * (pulsaretPhase < 1);
snd = snd * randomLFOs.().linlin(-1, 1, 0, 1);
snd = Pan2.ar(snd, randomLFOs.() * 0.4);
snd = snd.flop.sum;
snd = snd * -2.dbamp;
snd;
}.play(fadeTime: 0);
)
This gets old fast, and the shapes of the random LFOs are the culprit. They are all constantly moving smoothly and theres a lack of space to make movement sound special, leading to monotony. Recalling the switching method that I talked about in my last blog post, lets have each random LFO produce a random static value every now and then. Also, lets modulate the rate of all LFOs with another global slow LFO that determines their overall rate:
(
{
var snd, freq, formantCount, formantFreq, sineCycles, pulsaretPhase, window, randomLFO, randomLFOs, lfoRate;
formantCount = 3;
lfoRate = LFDNoise1.kr(0.3).linexp(-1, 1, 0.1, 16);
randomLFO = {
var trigger;
trigger = Dust.kr(lfoRate);
Select.kr(ToggleFF.kr(trigger), [
LFNoise2.kr(lfoRate),
TRand.kr(-1, 1, trigger)
]);
};
randomLFOs = { { randomLFO.() } ! formantCount };
freq = randomLFO.().linexp(-1, 1, 1, 1000);
formantFreq = randomLFOs.().linexp(-1, 1, 2, 8000);
sineCycles = randomLFOs.().linlin(-1, 1, 1, 4);
pulsaretPhase = LFSaw.ar(freq, iphase: 1).linlin(-1, 1, 0, 1) * formantFreq / freq;
window = pulsaretPhase.lincurve(0, 1, 1, 0, -4);
snd = sin(pulsaretPhase * 2pi * sineCycles.floor) * window * (pulsaretPhase < 1);
snd = snd * randomLFOs.().linlin(-1, 1, 0, 1);
snd = Pan2.ar(snd, randomLFOs.() * 0.4);
snd = snd.flop.sum;
snd = snd * -2.dbamp;
snd;
}.play(fadeTime: 0);
)
As always, its not just the synthesis recipe, its also how you modulate it.
To bring this patch into the 21st century, we can stack on a few effects. Here are three pitch shifters (not just any pitch shifter, but the weird and metallic PitchShift) and three frequency shifters, all modulated randomly:
(
{
var snd, freq, formantCount, formantFreq, sineCycles, pulsaretPhase, window, randomLFO, randomLFOs, lfoRate;
formantCount = 3;
lfoRate = LFDNoise1.kr(0.3).linexp(-1, 1, 0.1, 16);
randomLFO = {
var trigger;
trigger = Dust.kr(lfoRate);
Select.kr(ToggleFF.kr(trigger), [
LFNoise2.kr(lfoRate),
TRand.kr(-1, 1, trigger)
]);
};
randomLFOs = { { randomLFO.() } ! formantCount };
freq = randomLFO.().linexp(-1, 1, 1, 1000);
formantFreq = randomLFOs.().linexp(-1, 1, 2, 8000);
sineCycles = randomLFOs.().linlin(-1, 1, 1, 4);
pulsaretPhase = LFSaw.ar(freq, iphase: 1).linlin(-1, 1, 0, 1) * formantFreq / freq;
window = pulsaretPhase.lincurve(0, 1, 1, 0, -4);
snd = sin(pulsaretPhase * 2pi * sineCycles.floor) * window * (pulsaretPhase < 1);
snd = snd * randomLFOs.().linlin(-1, 1, 0, 1);
snd = Pan2.ar(snd, randomLFOs.() * 0.4);
snd = snd.flop.sum;
[0.2, 0.1, 0.05].do { |windowSize|
snd = PitchShift.ar(snd, windowSize, randomLFO.().linexp(-1, 1, 0.5, 2)) * 6.dbamp;
snd = FreqShift.ar(snd, randomLFO.() * 100);
};
snd = Limiter.ar(snd);
snd = snd * -2.dbamp;
snd;
}.play(fadeTime: 0);
)