From 5cbe30095ba35a9530b4e1ad1a451ace89c7e2bd Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Thu, 24 Aug 2023 12:21:29 +0200 Subject: [PATCH] control osc partial count with n --- packages/superdough/helpers.mjs | 11 ------ packages/superdough/synth.mjs | 60 +++++++++++++++++++++++++++++---- 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/packages/superdough/helpers.mjs b/packages/superdough/helpers.mjs index 7cc54c8d..651fc85c 100644 --- a/packages/superdough/helpers.mjs +++ b/packages/superdough/helpers.mjs @@ -6,17 +6,6 @@ export function gainNode(value) { return node; } -export const getOscillator = ({ s, freq, t }) => { - // make oscillator - const o = getAudioContext().createOscillator(); - o.type = s || 'triangle'; - o.frequency.value = Number(freq); - o.start(t); - //o.stop(t + duration + release); - const stop = (time) => o.stop(time); - return { node: o, stop }; -}; - // alternative to getADSR returning the gain node and a stop handle to trigger the release anytime in the future export const getEnvelope = (attack, decay, sustain, release, velocity, begin) => { const gainNode = getAudioContext().createGain(); diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 57317133..9ad347e3 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -1,6 +1,6 @@ import { midiToFreq, noteToMidi } from './util.mjs'; import { registerSound, getAudioContext } from './superdough.mjs'; -import { getOscillator, gainNode, getEnvelope } from './helpers.mjs'; +import { gainNode, getEnvelope } from './helpers.mjs'; const mod = (freq, range = 1, type = 'sine') => { const ctx = getAudioContext(); @@ -36,17 +36,17 @@ export function registerSynthSounds() { } = value; let { n, note, freq } = value; // with synths, n and note are the same thing - n = note || n || 36; - if (typeof n === 'string') { - n = noteToMidi(n); // e.g. c3 => 48 + note = note || 36; + if (typeof note === 'string') { + note = noteToMidi(note); // e.g. c3 => 48 } // get frequency - if (!freq && typeof n === 'number') { - freq = midiToFreq(n); // + 48); + if (!freq && typeof note === 'number') { + freq = midiToFreq(note); // + 48); } // maybe pull out the above frequency resolution?? (there is also getFrequency but it has no default) // make oscillator - const { node: o, stop } = getOscillator({ t, s: wave, freq }); + const { node: o, stop } = getOscillator({ t, s: wave, freq, partials: n }); let stopFm; if (fmModulationIndex) { @@ -76,3 +76,49 @@ export function registerSynthSounds() { ); }); } + +export function waveformN(partials, type) { + const real = new Float32Array(partials + 1); + const imag = new Float32Array(partials + 1); + const ac = getAudioContext(); + const osc = ac.createOscillator(); + + const amplitudes = { + sawtooth: (n) => 1 / n, + square: (n) => (n % 2 === 0 ? 0 : 1 / n), + triangle: (n) => (n % 2 === 0 ? 0 : 1 / (n * n)), + }; + + if (!amplitudes[type]) { + throw new Error(`unknown wave type ${type}`); + } + + real[0] = 0; // dc offset + imag[0] = 0; + let n = 1; + while (n <= partials) { + real[n] = amplitudes[type](n); + imag[n] = 0; + n++; + } + + const wave = ac.createPeriodicWave(real, imag); + osc.setPeriodicWave(wave); + return osc; +} + +export function getOscillator({ s, freq, t, partials }) { + // make oscillator + let o; + if (!partials || s === 'sine') { + o = getAudioContext().createOscillator(); + o.type = s || 'triangle'; + } else { + o = waveformN(partials, s); + } + o.frequency.value = Number(freq); + o.start(t); + //o.stop(t + duration + release); + const stop = (time) => o.stop(time); + return { node: o, stop }; +}