diff --git a/play.html b/play.html index 13a92e0..508879a 100644 --- a/play.html +++ b/play.html @@ -126,12 +126,6 @@ color: var(--gb-light); } - /* Transform description */ - .transform-line { - margin: 0 0 calc(var(--spacing-unit) * 3) 0; - color: var(--gb-light); - font-size: 8px; - } /* Buttons */ button { @@ -272,29 +266,20 @@ width: 0%; } - /* Meta block */ - .meta-block { + /* CTA link (inside card) */ + .cta-link-inline { margin-top: calc(var(--spacing-unit) * 2); padding-top: calc(var(--spacing-unit) * 2); border-top: 2px solid var(--gb-darkest); - font-size: 8px; - color: var(--gb-light); - line-height: 2.2; - } - - /* CTA link */ - .cta-link { - display: block; - margin-top: calc(var(--spacing-unit) * 3); text-align: center; } - .cta-link a { + .cta-link-inline a { display: inline-block; color: var(--gb-lightest); text-decoration: none; font-size: 10px; - padding: 12px 16px; + padding: 12px 20px; border: var(--pixel-border) solid var(--gb-light); background: var(--gb-dark); box-shadow: @@ -302,7 +287,7 @@ inset 4px 4px 0 var(--gb-light); } - .cta-link a:hover { + .cta-link-inline a:hover { background: var(--gb-light); color: var(--gb-darkest); box-shadow: @@ -335,11 +320,6 @@

Loading...

- -

- Reimagined as classic game-console music -

-
- -
- Procedural playback from MIDI structure.
- Rendered using the Wario Synthesis Engine. + +
- - - diff --git a/src/synthesis/SynthesisEngine.ts b/src/synthesis/SynthesisEngine.ts index 38cfab1..a9fe55c 100644 --- a/src/synthesis/SynthesisEngine.ts +++ b/src/synthesis/SynthesisEngine.ts @@ -81,20 +81,26 @@ export class SynthesisEngine { start(): void { if (this.isPlaying) return; - + this.isPlaying = true; this.startTime = this.audioContext.currentTime; - + // Reset event indices for (const role of this.roleAssignments.keys()) { this.nextEventIndex.set(role, 0); } - + + // Restore layer gains (they may have been faded to 0 on stop) + for (const [role, layer] of this.layers) { + layer.gainNode.gain.cancelScheduledValues(this.audioContext.currentTime); + this.restoreLayerGain(layer.gainNode, role); + } + // Start scheduler to play actual MIDI events this.schedulerIntervalId = window.setInterval(() => { this.scheduleEvents(); }, this.config.scheduleInterval); - + console.log('Started synthesis with', this.roleAssignments.size, 'roles'); } @@ -193,38 +199,49 @@ export class SynthesisEngine { }; } + private getLayerGainForRole(role: Role): number { + switch (role) { + case 'bass': return 0.4; + case 'drone': return 0.2; + case 'ostinato': return 0.3; + case 'texture': return 0.1; + case 'accents': return 0.5; + case 'melody': return 0.35; + default: return 0.3; + } + } + + private restoreLayerGain(gain: GainNode, role: Role): void { + gain.gain.setValueAtTime(this.getLayerGainForRole(role), this.audioContext.currentTime); + } + private configureLayerForRole(gain: GainNode, filter: BiquadFilterNode, role: Role): void { + gain.gain.value = this.getLayerGainForRole(role); + switch (role) { case 'bass': - gain.gain.value = 0.4; filter.type = 'lowpass'; filter.frequency.value = 200; break; case 'drone': - gain.gain.value = 0.2; filter.type = 'bandpass'; filter.frequency.value = 400; break; case 'ostinato': - gain.gain.value = 0.3; filter.type = 'highpass'; filter.frequency.value = 300; break; case 'texture': - gain.gain.value = 0.1; filter.type = 'bandpass'; filter.frequency.value = 800; break; case 'accents': - gain.gain.value = 0.5; filter.type = 'peaking'; filter.frequency.value = 1000; break; case 'melody': - // Passthrough mode - balanced sound for all notes - gain.gain.value = 0.35; filter.type = 'lowpass'; - filter.frequency.value = 4000; // Brighter sound for full range + filter.frequency.value = 4000; break; } } @@ -398,14 +415,16 @@ export class SynthesisEngine { private fadeOutAllLayers(): void { const fadeTime = this.config.fadeTime; const when = this.audioContext.currentTime; - + for (const layer of this.layers.values()) { + // Cancel any scheduled ramps and fade to 0 + layer.gainNode.gain.cancelScheduledValues(when); + layer.gainNode.gain.setValueAtTime(layer.gainNode.gain.value, when); layer.gainNode.gain.linearRampToValueAtTime(0, when + fadeTime); } - - setTimeout(() => { - this.cleanupLayers(); - }, fadeTime * 1000 + 100); + + // Don't cleanup layers - keep them connected for resume + // Layers are only cleaned up when setupLayers is called with new data } private scheduleChord(role: Role, pitches: number[], duration: number, velocity: number, when: number): void {