Working version of partials and phases

This commit is contained in:
Aria 2025-10-04 20:33:25 -05:00
parent 0ecacae71e
commit cc4452ce58
6 changed files with 79 additions and 22 deletions

View file

@ -14,9 +14,10 @@ import {
noises,
webAudioTimeout,
} from './helpers.mjs';
import { logger } from './logger.mjs';
import { getNoiseMix, getNoiseOscillator } from './noise.mjs';
const waveforms = ['triangle', 'square', 'sawtooth', 'sine'];
const waveforms = ['triangle', 'square', 'sawtooth', 'sine', 'user'];
const waveformAliases = [
['tri', 'triangle'],
['sqr', 'square'],
@ -413,9 +414,13 @@ export function registerSynthSounds() {
waveformAliases.forEach(([alias, actual]) => soundMap.set({ ...soundMap.get(), [alias]: soundMap.get()[actual] }));
}
export function waveformN(partials, type) {
const real = new Float32Array(partials + 1);
const imag = new Float32Array(partials + 1);
const PI2 = 2 * Math.PI;
export function waveformN(partials, phases, type) {
const isList = typeof partials === 'object';
partials = isList ? partials : new Float32Array(partials).fill(1);
const len = partials.length;
const real = new Float32Array(len + 1);
const imag = new Float32Array(len + 1);
const ac = getAudioContext();
const osc = ac.createOscillator();
@ -423,20 +428,29 @@ export function waveformN(partials, type) {
sawtooth: (n) => [0, -1 / n],
square: (n) => [0, n % 2 === 0 ? 0 : 1 / n],
triangle: (n) => [n % 2 === 0 ? 0 : 1 / (n * n), 0],
user: (_n) => [0, 1],
};
if (!terms[type]) {
throw new Error(`unknown wave type ${type}`);
}
real[0] = 0; // dc offset
imag[0] = 0;
let n = 1;
while (n <= partials) {
const [r, i] = terms[type](n);
real[n] = r;
imag[n] = i;
n++;
for (let n = 0; n < len; n++) {
const mag = partials[n];
const [r, i] = terms[type](n + 1); // we skip n === 0 as this is dc offset
const phase = phases?.[n] ?? 0;
// Scale by `partials`
let R = r * mag;
let I = i * mag;
// Apply rotation by the phase
if (phase !== 0) {
const c = Math.cos(PI2 * phase);
const s = Math.sin(PI2 * phase);
R = c * R - s * I;
I = s * R + c * I;
}
real[n + 1] = R;
imag[n + 1] = I;
}
const wave = ac.createPeriodicWave(real, imag);
@ -446,16 +460,24 @@ export function waveformN(partials, type) {
// expects one of waveforms as s
export function getOscillator(s, t, value) {
let { n: partials, duration, noise = 0 } = value;
const { duration, noise = 0 } = value;
const partials = value.partials ?? value.n;
let o;
if (s === 'user' && !partials) {
logger(
`[superdough] Synth 'user' was selected, but partials not specified. Defaulting to triangle. Use pat.partials to setup custom waveform`,
);
s = 'triangle';
}
s = s === 'user' && !partials ? 'triangle' : s;
// If no partials are given, use stock waveforms
if (!partials || s === 'sine') {
if (!partials || !partials?.length || s === 'sine') {
o = getAudioContext().createOscillator();
o.type = s || 'triangle';
}
// generate custom waveform if partials are given
else {
o = waveformN(partials, s);
o = waveformN(partials, value.phases, s);
}
// set frequency
o.frequency.value = getFrequencyFromValue(value);