diff --git a/projects/web-synth-9x1a/JOURNAL.md b/projects/web-synth-9x1a/JOURNAL.md index f0ec8a08..507e09dd 100644 --- a/projects/web-synth-9x1a/JOURNAL.md +++ b/projects/web-synth-9x1a/JOURNAL.md @@ -170,3 +170,16 @@ - [x] Add "Randomize Parameters" button to Chorus Node - [x] Add "Randomize Parameters" button to Tremolo Node - [x] Add Bypass switch to Tremolo Node + + +## Expansion Phase 13 (New Features) +- [x] Add Glide (Portamento) control to Oscillator Node +- [ ] Add Symmetry/Pulse Width control to Oscillator Node (for Square/Triangle waves) +- [x] Add Sub-Oscillator toggle (mix in an octave down) to Oscillator Node +- [x] Add Fine Tuning (Cents) control to Oscillator Node +- [ ] Implement Bypass switch for Pitch Shift Node (if it existed, or add Pitch Shift node) +- [ ] Add Dry/Wet control to Bitcrusher Node +- [ ] Add Drive control to Gain Node to act as a preamp +- [ ] Add LFO Destination selection in LFO Node (pitch, filter, amp) +- [x] Add 'Randomize Parameters' button to Bitcrusher Node +- [ ] Add a visual meter (VU meter style) to Gain Node diff --git a/projects/web-synth-9x1a/src/audio/nodes/sources.ts b/projects/web-synth-9x1a/src/audio/nodes/sources.ts index 33c7b58c..59d3512c 100644 --- a/projects/web-synth-9x1a/src/audio/nodes/sources.ts +++ b/projects/web-synth-9x1a/src/audio/nodes/sources.ts @@ -5,6 +5,11 @@ export class OscillatorWrapper { public output: GainNode; private baseFreq: number = 440; private octave: number = 0; + private glideTime: number = 0; + private fineTune: number = 0; + private detuneAmt: number = 0; + private subOsc: OscillatorNode; + private subGain: GainNode; constructor(id: string) { const ctx = audioCore.getContext(); @@ -15,6 +20,15 @@ export class OscillatorWrapper { this.output = ctx.createGain(); this.output.gain.value = 1; + this.subOsc = ctx.createOscillator(); + this.subOsc.type = 'square'; + this.subOsc.frequency.value = 220; + this.subOsc.start(); + this.subGain = ctx.createGain(); + this.subGain.gain.value = 0; + this.subOsc.connect(this.subGain); + this.subGain.connect(this.output); + this.node.connect(this.output); audioCore.registerNode(id, this.output); @@ -28,12 +42,23 @@ export class OscillatorWrapper { public setFrequency(freq: number) { this.baseFreq = freq; - this.node.frequency.setValueAtTime(this.baseFreq * Math.pow(2, this.octave), audioCore.getContext().currentTime); + this.node.frequency.linearRampToValueAtTime(this.baseFreq * Math.pow(2, this.octave), audioCore.getContext().currentTime + this.glideTime); + this.subOsc.frequency.linearRampToValueAtTime(this.baseFreq * Math.pow(2, this.octave - 1), audioCore.getContext().currentTime + this.glideTime); + this.subOsc.frequency.linearRampToValueAtTime(this.baseFreq * Math.pow(2, this.octave - 1), audioCore.getContext().currentTime + this.glideTime); } public setOctave(oct: number) { this.octave = oct; - this.node.frequency.setValueAtTime(this.baseFreq * Math.pow(2, this.octave), audioCore.getContext().currentTime); + this.node.frequency.linearRampToValueAtTime(this.baseFreq * Math.pow(2, this.octave), audioCore.getContext().currentTime + this.glideTime); + this.subOsc.frequency.linearRampToValueAtTime(this.baseFreq * Math.pow(2, this.octave - 1), audioCore.getContext().currentTime + this.glideTime); + } + + public setGlideTime(time: number) { + this.glideTime = time; + } + + public setSubOscEnabled(enabled: boolean) { + this.subGain.gain.setValueAtTime(enabled ? 0.5 : 0, audioCore.getContext().currentTime); } public setInvertPhase(invert: boolean) { @@ -41,11 +66,23 @@ export class OscillatorWrapper { } public setDetune(cents: number) { - this.node.detune.setValueAtTime(cents, audioCore.getContext().currentTime); + this.detuneAmt = cents; + this.updateDetune(); + } + + public setFineTune(cents: number) { + this.fineTune = cents; + this.updateDetune(); + } + + private updateDetune() { + this.node.detune.setValueAtTime(this.detuneAmt + this.fineTune, audioCore.getContext().currentTime); } public destroy(id: string) { - this.node.stop(); +this.node.stop(); + if (this.subOsc) { this.subOsc.stop(); this.subOsc.disconnect(); } + if (this.subGain) { this.subGain.disconnect(); } this.node.disconnect(); this.output.disconnect(); audioCore.unregisterNode(id); @@ -75,7 +112,7 @@ export class NoiseWrapper { private generateAndPlayNoise() { if (this.node) { - this.node.stop(); + this.node.stop(); this.node.disconnect(); } @@ -126,7 +163,7 @@ export class NoiseWrapper { public destroy(id: string) { if (this.node) { - this.node.stop(); + this.node.stop(); this.node.disconnect(); } this.output.disconnect(); @@ -172,7 +209,7 @@ export class LfoWrapper { } public destroy(id: string) { - this.node.stop(); +this.node.stop(); this.node.disconnect(); this.depthNode.disconnect(); audioCore.unregisterNode(id); diff --git a/projects/web-synth-9x1a/src/components/nodes/BitcrusherNode.tsx b/projects/web-synth-9x1a/src/components/nodes/BitcrusherNode.tsx index bcc26963..cea630c0 100644 --- a/projects/web-synth-9x1a/src/components/nodes/BitcrusherNode.tsx +++ b/projects/web-synth-9x1a/src/components/nodes/BitcrusherNode.tsx @@ -40,7 +40,10 @@ export function BitcrusherNode({ id, data }: { id: string, data: Record - +
+ + +
diff --git a/projects/web-synth-9x1a/src/components/nodes/OscillatorNode.tsx b/projects/web-synth-9x1a/src/components/nodes/OscillatorNode.tsx index 43642528..4ce18378 100644 --- a/projects/web-synth-9x1a/src/components/nodes/OscillatorNode.tsx +++ b/projects/web-synth-9x1a/src/components/nodes/OscillatorNode.tsx @@ -37,6 +37,16 @@ export function OscillatorNode({ id, data }: { id: string, data: Record + + + + + +