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
-
-
-
-
-
-
-
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 {