From c1f7cc02fd65249b67e3f06779762bdb9309de73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Forment?= Date: Sun, 12 Oct 2025 23:39:14 +0200 Subject: [PATCH] new engine --- src/lib/audio/engines/PhaseDistortionFM.ts | 101 +++++++++++++++++---- 1 file changed, 83 insertions(+), 18 deletions(-) diff --git a/src/lib/audio/engines/PhaseDistortionFM.ts b/src/lib/audio/engines/PhaseDistortionFM.ts index 69c7edf..5614382 100644 --- a/src/lib/audio/engines/PhaseDistortionFM.ts +++ b/src/lib/audio/engines/PhaseDistortionFM.ts @@ -14,6 +14,10 @@ enum PDAlgorithm { Dual, // Two oscillators, different waveforms Detune, // Two slightly detuned oscillators Octave, // Oscillator + octave up + Fifth, // Base + fifth up (1.5x frequency) + Sub, // Base + octave down + Stack, // Base + fifth + octave + Wide, // Heavily detuned oscillators } enum LFOWaveform { @@ -30,6 +34,7 @@ interface PDOscillatorParams { level: number; detune: number; // in cents dcw: number; // Digitally Controlled Waveshaping (0-1, controls brightness) + pulseWidth: number; // 0-1, controls pulse wave width attack: number; decay: number; sustain: number; @@ -227,48 +232,104 @@ export class PhaseDistortionFM implements SynthEngine { switch (algorithm) { case PDAlgorithm.Single: { // Single oscillator - const outL = this.generatePDWaveform(phasesL[0], oscillators[0].waveform, dcws[0]) + const outL = this.generatePDWaveform(phasesL[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth) * envelopes[0] * oscillators[0].level; - const outR = this.generatePDWaveform(phasesR[0], oscillators[0].waveform, dcws[0]) + const outR = this.generatePDWaveform(phasesR[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth) * envelopes[0] * oscillators[0].level; return [outL, outR]; } case PDAlgorithm.Dual: { // Two oscillators with different waveforms - const osc1L = this.generatePDWaveform(phasesL[0], oscillators[0].waveform, dcws[0]) + const osc1L = this.generatePDWaveform(phasesL[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth) * envelopes[0] * oscillators[0].level; - const osc1R = this.generatePDWaveform(phasesR[0], oscillators[0].waveform, dcws[0]) + const osc1R = this.generatePDWaveform(phasesR[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth) * envelopes[0] * oscillators[0].level; - const osc2L = this.generatePDWaveform(phasesL[1], oscillators[1].waveform, dcws[1]) + const osc2L = this.generatePDWaveform(phasesL[1], oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth) * envelopes[1] * oscillators[1].level; - const osc2R = this.generatePDWaveform(phasesR[1], oscillators[1].waveform, dcws[1]) + const osc2R = this.generatePDWaveform(phasesR[1], oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth) * envelopes[1] * oscillators[1].level; return [(osc1L + osc2L) * 0.707, (osc1R + osc2R) * 0.707]; } case PDAlgorithm.Detune: { // Two slightly detuned oscillators (chorus effect) - const osc1L = this.generatePDWaveform(phasesL[0], oscillators[0].waveform, dcws[0]) + const osc1L = this.generatePDWaveform(phasesL[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth) * envelopes[0] * oscillators[0].level; - const osc1R = this.generatePDWaveform(phasesR[0], oscillators[0].waveform, dcws[0]) + const osc1R = this.generatePDWaveform(phasesR[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth) * envelopes[0] * oscillators[0].level; - const osc2L = this.generatePDWaveform(phasesL[1], oscillators[0].waveform, dcws[0]) + const osc2L = this.generatePDWaveform(phasesL[1], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth) * envelopes[0] * oscillators[1].level; - const osc2R = this.generatePDWaveform(phasesR[1], oscillators[0].waveform, dcws[0]) + const osc2R = this.generatePDWaveform(phasesR[1], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth) * envelopes[0] * oscillators[1].level; return [(osc1L + osc2L) * 0.707, (osc1R + osc2R) * 0.707]; } case PDAlgorithm.Octave: { // Oscillator + octave up - const osc1L = this.generatePDWaveform(phasesL[0], oscillators[0].waveform, dcws[0]) + const osc1L = this.generatePDWaveform(phasesL[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth) * envelopes[0] * oscillators[0].level; - const osc1R = this.generatePDWaveform(phasesR[0], oscillators[0].waveform, dcws[0]) + const osc1R = this.generatePDWaveform(phasesR[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth) * envelopes[0] * oscillators[0].level; - const osc2L = this.generatePDWaveform(phasesL[1] * 2, oscillators[1].waveform, dcws[1]) + const osc2L = this.generatePDWaveform(phasesL[1] * 2, oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth) * envelopes[1] * oscillators[1].level; - const osc2R = this.generatePDWaveform(phasesR[1] * 2, oscillators[1].waveform, dcws[1]) + const osc2R = this.generatePDWaveform(phasesR[1] * 2, oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth) + * envelopes[1] * oscillators[1].level; + return [(osc1L + osc2L) * 0.707, (osc1R + osc2R) * 0.707]; + } + + case PDAlgorithm.Fifth: { + // Base + fifth up (1.5x frequency) + const osc1L = this.generatePDWaveform(phasesL[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth) + * envelopes[0] * oscillators[0].level; + const osc1R = this.generatePDWaveform(phasesR[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth) + * envelopes[0] * oscillators[0].level; + const osc2L = this.generatePDWaveform(phasesL[1] * 1.5, oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth) + * envelopes[1] * oscillators[1].level; + const osc2R = this.generatePDWaveform(phasesR[1] * 1.5, oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth) + * envelopes[1] * oscillators[1].level; + return [(osc1L + osc2L) * 0.707, (osc1R + osc2R) * 0.707]; + } + + case PDAlgorithm.Sub: { + // Base + octave down + const osc1L = this.generatePDWaveform(phasesL[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth) + * envelopes[0] * oscillators[0].level; + const osc1R = this.generatePDWaveform(phasesR[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth) + * envelopes[0] * oscillators[0].level; + const osc2L = this.generatePDWaveform(phasesL[1] * 0.5, oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth) + * envelopes[1] * oscillators[1].level; + const osc2R = this.generatePDWaveform(phasesR[1] * 0.5, oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth) + * envelopes[1] * oscillators[1].level; + return [(osc1L + osc2L) * 0.707, (osc1R + osc2R) * 0.707]; + } + + case PDAlgorithm.Stack: { + // Base + fifth + octave - three oscillators! + const osc1L = this.generatePDWaveform(phasesL[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth) + * envelopes[0] * oscillators[0].level; + const osc1R = this.generatePDWaveform(phasesR[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth) + * envelopes[0] * oscillators[0].level; + const osc2L = this.generatePDWaveform(phasesL[1] * 1.5, oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth) + * envelopes[1] * oscillators[1].level; + const osc2R = this.generatePDWaveform(phasesR[1] * 1.5, oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth) + * envelopes[1] * oscillators[1].level; + const osc3L = this.generatePDWaveform(phasesL[1] * 2, oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth) + * envelopes[1] * oscillators[1].level * 0.7; + const osc3R = this.generatePDWaveform(phasesR[1] * 2, oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth) + * envelopes[1] * oscillators[1].level * 0.7; + return [(osc1L + osc2L + osc3L) * 0.577, (osc1R + osc2R + osc3R) * 0.577]; + } + + case PDAlgorithm.Wide: { + // Heavily detuned oscillators for super wide chorus + const osc1L = this.generatePDWaveform(phasesL[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth) + * envelopes[0] * oscillators[0].level; + const osc1R = this.generatePDWaveform(phasesR[0], oscillators[0].waveform, dcws[0], oscillators[0].pulseWidth) + * envelopes[0] * oscillators[0].level; + const osc2L = this.generatePDWaveform(phasesL[1], oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth) + * envelopes[1] * oscillators[1].level; + const osc2R = this.generatePDWaveform(phasesR[1], oscillators[1].waveform, dcws[1], oscillators[1].pulseWidth) * envelopes[1] * oscillators[1].level; return [(osc1L + osc2L) * 0.707, (osc1R + osc2R) * 0.707]; } @@ -278,7 +339,7 @@ export class PhaseDistortionFM implements SynthEngine { } } - private generatePDWaveform(phase: number, waveform: PDWaveform, dcw: number): number { + private generatePDWaveform(phase: number, waveform: PDWaveform, dcw: number, pulseWidth: number): number { const TAU = Math.PI * 2; let normalizedPhase = phase / TAU; normalizedPhase = normalizedPhase - Math.floor(normalizedPhase); @@ -297,7 +358,7 @@ export class PhaseDistortionFM implements SynthEngine { return 1 - distortedPhase * 2; case PDWaveform.Pulse: - return distortedPhase < 0.5 ? 1 : -1; + return distortedPhase < pulseWidth ? 1 : -1; case PDWaveform.DoubleSine: return Math.sin(distortedPhase * TAU * 2); @@ -417,7 +478,7 @@ export class PhaseDistortionFM implements SynthEngine { } randomParams(pitchLock?: PitchLock): PhaseDistortionFMParams { - const algorithm = this.randomInt(0, 3) as PDAlgorithm; + const algorithm = this.randomInt(0, 7) as PDAlgorithm; let baseFreq: number; if (pitchLock?.enabled) { @@ -450,6 +511,8 @@ export class PhaseDistortionFM implements SynthEngine { let detune = 0; if (algorithm === PDAlgorithm.Detune) { detune = this.randomRange(-10, 10); + } else if (algorithm === PDAlgorithm.Wide) { + detune = this.randomRange(-30, 30); } return { @@ -457,6 +520,7 @@ export class PhaseDistortionFM implements SynthEngine { level: this.randomRange(0.5, 0.9), detune, dcw: this.randomRange(0.2, 0.8), + pulseWidth: this.randomRange(0.2, 0.8), attack: this.randomRange(0.001, 0.1), decay: this.randomRange(0.02, 0.2), sustain: this.randomRange(0.3, 0.8), @@ -473,7 +537,7 @@ export class PhaseDistortionFM implements SynthEngine { return { baseFreq, - algorithm: Math.random() < 0.1 ? this.randomInt(0, 3) as PDAlgorithm : params.algorithm, + algorithm: Math.random() < 0.1 ? this.randomInt(0, 7) as PDAlgorithm : params.algorithm, oscillators: params.oscillators.map(osc => this.mutateOscillator(osc, mutationAmount) ) as [PDOscillatorParams, PDOscillatorParams], @@ -493,6 +557,7 @@ export class PhaseDistortionFM implements SynthEngine { level: this.mutateValue(osc.level, amount, 0.3, 1.0), detune: this.mutateValue(osc.detune, amount, -20, 20), dcw: this.mutateValue(osc.dcw, amount, 0, 1), + pulseWidth: this.mutateValue(osc.pulseWidth, amount, 0.1, 0.9), attack: this.mutateValue(osc.attack, amount, 0.001, 0.2), decay: this.mutateValue(osc.decay, amount, 0.01, 0.3), sustain: this.mutateValue(osc.sustain, amount, 0.1, 0.95),