Fix pause/play audio muting + tidy share page UX
- Fix SynthesisEngine to not cleanup layers on stop, preventing audio from going silent after pause/resume - Remove "Reimagined as classic game-console music" line - Remove procedural playback meta block - Move "Generate your own" CTA inside player card - Update footer with Birdmania credit and Twitter link 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -126,12 +126,6 @@
|
|||||||
color: var(--gb-light);
|
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 */
|
/* Buttons */
|
||||||
button {
|
button {
|
||||||
@@ -272,29 +266,20 @@
|
|||||||
width: 0%;
|
width: 0%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Meta block */
|
/* CTA link (inside card) */
|
||||||
.meta-block {
|
.cta-link-inline {
|
||||||
margin-top: calc(var(--spacing-unit) * 2);
|
margin-top: calc(var(--spacing-unit) * 2);
|
||||||
padding-top: calc(var(--spacing-unit) * 2);
|
padding-top: calc(var(--spacing-unit) * 2);
|
||||||
border-top: 2px solid var(--gb-darkest);
|
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;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cta-link a {
|
.cta-link-inline a {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
color: var(--gb-lightest);
|
color: var(--gb-lightest);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
padding: 12px 16px;
|
padding: 12px 20px;
|
||||||
border: var(--pixel-border) solid var(--gb-light);
|
border: var(--pixel-border) solid var(--gb-light);
|
||||||
background: var(--gb-dark);
|
background: var(--gb-dark);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
@@ -302,7 +287,7 @@
|
|||||||
inset 4px 4px 0 var(--gb-light);
|
inset 4px 4px 0 var(--gb-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cta-link a:hover {
|
.cta-link-inline a:hover {
|
||||||
background: var(--gb-light);
|
background: var(--gb-light);
|
||||||
color: var(--gb-darkest);
|
color: var(--gb-darkest);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
@@ -335,11 +320,6 @@
|
|||||||
<h2 id="songTitle" class="song-title">Loading...</h2>
|
<h2 id="songTitle" class="song-title">Loading...</h2>
|
||||||
<div id="songArtist" class="song-artist"></div>
|
<div id="songArtist" class="song-artist"></div>
|
||||||
|
|
||||||
<!-- Transformation Description -->
|
|
||||||
<p id="transformLine" class="transform-line">
|
|
||||||
Reimagined as classic game-console music
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- Play/Pause Control -->
|
<!-- Play/Pause Control -->
|
||||||
<div class="play-container">
|
<div class="play-container">
|
||||||
<button id="playToggleBtn" class="primary" disabled>
|
<button id="playToggleBtn" class="primary" disabled>
|
||||||
@@ -359,20 +339,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Metadata -->
|
<!-- CTA -->
|
||||||
<div class="meta-block" id="metaBlock">
|
<div class="cta-link-inline">
|
||||||
Procedural playback from MIDI structure.<br />
|
<a id="generateOwn" href="/">Generate your own</a>
|
||||||
Rendered using the Wario Synthesis Engine.
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- CTA -->
|
|
||||||
<div class="cta-link">
|
|
||||||
<a id="generateOwn" href="/">Generate your own</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<a href="/">WARIO SYNTH</a> — Turn any song into retro game console music
|
Wario's Synthesis Engine — created by <a href="https://x.com/b1rdmania" target="_blank" rel="noopener">Birdmania</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -81,20 +81,26 @@ export class SynthesisEngine {
|
|||||||
|
|
||||||
start(): void {
|
start(): void {
|
||||||
if (this.isPlaying) return;
|
if (this.isPlaying) return;
|
||||||
|
|
||||||
this.isPlaying = true;
|
this.isPlaying = true;
|
||||||
this.startTime = this.audioContext.currentTime;
|
this.startTime = this.audioContext.currentTime;
|
||||||
|
|
||||||
// Reset event indices
|
// Reset event indices
|
||||||
for (const role of this.roleAssignments.keys()) {
|
for (const role of this.roleAssignments.keys()) {
|
||||||
this.nextEventIndex.set(role, 0);
|
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
|
// Start scheduler to play actual MIDI events
|
||||||
this.schedulerIntervalId = window.setInterval(() => {
|
this.schedulerIntervalId = window.setInterval(() => {
|
||||||
this.scheduleEvents();
|
this.scheduleEvents();
|
||||||
}, this.config.scheduleInterval);
|
}, this.config.scheduleInterval);
|
||||||
|
|
||||||
console.log('Started synthesis with', this.roleAssignments.size, 'roles');
|
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 {
|
private configureLayerForRole(gain: GainNode, filter: BiquadFilterNode, role: Role): void {
|
||||||
|
gain.gain.value = this.getLayerGainForRole(role);
|
||||||
|
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case 'bass':
|
case 'bass':
|
||||||
gain.gain.value = 0.4;
|
|
||||||
filter.type = 'lowpass';
|
filter.type = 'lowpass';
|
||||||
filter.frequency.value = 200;
|
filter.frequency.value = 200;
|
||||||
break;
|
break;
|
||||||
case 'drone':
|
case 'drone':
|
||||||
gain.gain.value = 0.2;
|
|
||||||
filter.type = 'bandpass';
|
filter.type = 'bandpass';
|
||||||
filter.frequency.value = 400;
|
filter.frequency.value = 400;
|
||||||
break;
|
break;
|
||||||
case 'ostinato':
|
case 'ostinato':
|
||||||
gain.gain.value = 0.3;
|
|
||||||
filter.type = 'highpass';
|
filter.type = 'highpass';
|
||||||
filter.frequency.value = 300;
|
filter.frequency.value = 300;
|
||||||
break;
|
break;
|
||||||
case 'texture':
|
case 'texture':
|
||||||
gain.gain.value = 0.1;
|
|
||||||
filter.type = 'bandpass';
|
filter.type = 'bandpass';
|
||||||
filter.frequency.value = 800;
|
filter.frequency.value = 800;
|
||||||
break;
|
break;
|
||||||
case 'accents':
|
case 'accents':
|
||||||
gain.gain.value = 0.5;
|
|
||||||
filter.type = 'peaking';
|
filter.type = 'peaking';
|
||||||
filter.frequency.value = 1000;
|
filter.frequency.value = 1000;
|
||||||
break;
|
break;
|
||||||
case 'melody':
|
case 'melody':
|
||||||
// Passthrough mode - balanced sound for all notes
|
|
||||||
gain.gain.value = 0.35;
|
|
||||||
filter.type = 'lowpass';
|
filter.type = 'lowpass';
|
||||||
filter.frequency.value = 4000; // Brighter sound for full range
|
filter.frequency.value = 4000;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -398,14 +415,16 @@ export class SynthesisEngine {
|
|||||||
private fadeOutAllLayers(): void {
|
private fadeOutAllLayers(): void {
|
||||||
const fadeTime = this.config.fadeTime;
|
const fadeTime = this.config.fadeTime;
|
||||||
const when = this.audioContext.currentTime;
|
const when = this.audioContext.currentTime;
|
||||||
|
|
||||||
for (const layer of this.layers.values()) {
|
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);
|
layer.gainNode.gain.linearRampToValueAtTime(0, when + fadeTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
// Don't cleanup layers - keep them connected for resume
|
||||||
this.cleanupLayers();
|
// Layers are only cleaned up when setupLayers is called with new data
|
||||||
}, fadeTime * 1000 + 100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private scheduleChord(role: Role, pitches: number[], duration: number, velocity: number, when: number): void {
|
private scheduleChord(role: Role, pitches: number[], duration: number, velocity: number, when: number): void {
|
||||||
|
|||||||
Reference in New Issue
Block a user