I’ve been working for a while on this and didn’t realise it was already a thing. Oops. Thanks for sharing!
I made something similar (using WaveTerrain.ar), which is probably nowhere near as efficient… here’s mine:
var genTerrain = {
arg formula = \sin,
directory = "~/Music/SuperCollider/anti alias/rendered",
samples = 16384,
buffers = 128,
bufferFundamentalFirst = 0,
bufferFundamentalLast = 127,
cutoffStartHz = 17500,
cutoffEndHz = 20750;
var cutoffStart = cutoffStartHz.cpsmidi;
var cutoffEnd = cutoffEndHz.cpsmidi;
var harmonics = trunc((cutoffEndHz) / (bufferFundamentalFirst.midicps));
var bufferRange = bufferFundamentalLast - bufferFundamentalFirst;
var cutoffRange = cutoffEnd - cutoffStart;
var path = format("%/%_%x%_%-%_%Hz-%Hz.wav", directory, formula, samples, buffers, bufferFundamentalFirst, bufferFundamentalLast, cutoffStartHz, cutoffEndHz).standardizePath;
var currentHarmonic = Array.fill(samples, 0);
var highestBufferFilledPrevious = buffers - 1;
var highestBufferFillHypothetical = buffers - 1;
var highestBufferFillCurrent = buffers - 1;
var highestBufferTouch = buffers - 1;
var terrain = Array.fill(buffers, {Array.fill(samples, 0)});
var bufferArray = Array.fill(buffers * samples, {0});
format("starting % wave render", formula).postln;
for(1, harmonics, { arg harmonic;
format("adding harmonic % of %" , harmonic, harmonics).postln;
highestBufferFilledPrevious = highestBufferFillCurrent;
highestBufferFillHypothetical = trunc((((cutoffStartHz / harmonic).cpsmidi) - bufferFundamentalFirst) * (buffers - 1) / bufferRange);
highestBufferFillCurrent = if(highestBufferFillHypothetical < buffers,
if(highestBufferFillHypothetical < 0, 0, highestBufferFillHypothetical), buffers - 1);
highestBufferTouch = trunc((((cutoffEndHz / harmonic).cpsmidi) - bufferFundamentalFirst) * (buffers - 1) / bufferRange);
forBy(highestBufferFilledPrevious - 1, highestBufferFillCurrent, -1, {
arg space; terrain.put(space, terrain[highestBufferFilledPrevious]);
});
currentHarmonic = Array.fill(samples, {
arg sample, phase = 0, mul = 1;
switch(formula,
\sin, {
phase = 0;
mul = if(harmonic == 1, 1, 0);
((harmonic * sample * 2 * pi / samples) - phase).sin * mul;
},
\tri, {
phase = ((harmonic % 4) - 1) * pi / 2;
mul = if(harmonic % 2 == 0, 0, 8 / (pi ** 2) / (harmonic ** 2));
((harmonic * sample * 2 * pi / samples) - phase).sin * mul;
},
\triNoFund, {
phase = ((harmonic % 4) - 1) * pi / 2;
mul = if(harmonic == 1, 0, if(harmonic % 2 == 0, 0, 8 / (pi ** 2) / (harmonic ** 2)));
((harmonic * sample * 2 * pi / samples) - phase).sin * mul;
},
\saw, {
phase = 0;
mul = 2 / pi / harmonic;
((harmonic * sample * 2 * pi / samples) - phase).sin * mul;
},
\sawNoFund, {
phase = 0;
mul = if(harmonic == 1, 0, 2 / pi / harmonic);
((harmonic * sample * 2 * pi / samples) - phase).sin * mul;
},
\sqr, {
phase = 0;
mul = if(harmonic % 2 == 0, 0, 4 / pi / harmonic);
((harmonic * sample * 2 * pi / samples) - phase).sin * mul;
},
\sqrNoFund, {
phase = 0;
mul = if(harmonic == 1, 0, if(harmonic % 2 == 0, 0, 4 / pi / harmonic));
((harmonic * sample * 2 * pi / samples) - phase).sin * mul;
},
\primes, {
phase = 0;
mul = if(harmonic.isPrime, 1, 0) / (harmonic ** 2);
((harmonic * sample * 2 * pi / samples) - phase).sin * mul;
},
);
});
forBy(highestBufferFillHypothetical + 1, highestBufferTouch, 1, {
arg touchedBuffer;
if(touchedBuffer >= 0, {if(touchedBuffer < buffers, {
terrain.put(touchedBuffer, terrain[touchedBuffer] + (currentHarmonic *
(((((((touchedBuffer * bufferRange / (buffers - 1)) + bufferFundamentalFirst)
+ (12 * log2(harmonic))) - cutoffStart) * pi / cutoffRange).cos) + 1) / 2));
})});
});
if(highestBufferFillHypothetical >= 0, {
terrain.put(highestBufferFillCurrent, terrain[highestBufferFillCurrent] + currentHarmonic);
});
for(0, buffers - 1, {arg buffer; for(0, samples - 1, {arg sample;
bufferArray.put((buffer * samples) + sample, terrain[buffer][sample]);
})});
});
"added all harmonics; loading plot".postln;
bufferArray.plot;
format("loaded plot; writing buffer to %", path).postln;
Buffer.loadCollection(s, bufferArray).write(path, headerFormat: "WAV", sampleFormat: "float");
};
genTerrain.value(
formula: \sqr,
directory: "~",
samples: 16384,
buffers: 128,
bufferFundamentalFirst: 0,
bufferFundamentalLast: 127,
cutoffStartHz: 17500,
cutoffEndHz: 20750
);
That code should just run if you evaluate file. It calculates one harmonic at a time, and adds them to 128 “buffers” (rows in a 2D sound file), one for each MIDI note. Apologies for the lack of commenting and readability.
I’ll try to steal from yours when I next revisit the problem :)