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