Working version of partials and phases
This commit is contained in:
parent
0ecacae71e
commit
cc4452ce58
6 changed files with 79 additions and 22 deletions
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue