Simplify UX: single preview player before generation

UX Flow: Search → Preview → Generate

Changes:
- Remove 3-column player grid (Tone.js, Soundfont, Custom)
- Keep only Soundfont piano as simple preview
- New section title: "Preview - Review the MIDI before generation"
- Clean horizontal layout: Play | Stop | Volume | Try Next
- This sets up Motif generation as the main event
- Removed Tone.js and EnhancedMIDIPlayer dependencies
- Bundle size reduced from 314KB to 76KB (-238KB!)

Better flow with less confusion. Preview is quick, generation is the focus.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
b1rdmania
2025-12-18 20:07:09 +00:00
parent 9daecd192a
commit 7601909cda
2 changed files with 51 additions and 149 deletions
+39 -42
View File
@@ -274,15 +274,41 @@
background: linear-gradient(90deg, transparent, var(--color-border), transparent); background: linear-gradient(90deg, transparent, var(--color-border), transparent);
} }
.original-grid { .preview-controls {
display: grid; display: flex;
grid-template-columns: repeat(3, minmax(0, 1fr)); gap: calc(var(--spacing-unit) * 1.5);
gap: calc(var(--spacing-unit) * 2); align-items: center;
flex-wrap: wrap;
padding: calc(var(--spacing-unit) * 3);
background: var(--color-surface);
border-radius: var(--radius);
border: 1px solid var(--color-border);
box-shadow: var(--shadow-sm);
} }
@media (max-width: 920px) { .preview-controls button.preview-play {
.original-grid { flex: 1;
grid-template-columns: 1fr; min-width: 160px;
}
.preview-controls button.next-result {
margin-left: auto;
}
.preview-controls .volume-control {
flex: 1;
min-width: 200px;
margin-top: 0;
}
@media (max-width: 768px) {
.preview-controls {
flex-direction: column;
align-items: stretch;
}
.preview-controls button.next-result {
margin-left: 0;
} }
} }
@@ -479,48 +505,19 @@
<div class="section"> <div class="section">
<div class="section-header"> <div class="section-header">
<h3 class="section-title">Original MIDI playback</h3> <h3 class="section-title">Preview</h3>
<p class="section-subtitle">Choose an engine and play the source performance.</p> <p class="section-subtitle">Review the MIDI before generation.</p>
</div> </div>
<div class="divider"></div> <div class="divider"></div>
<div class="original-grid"> <div class="preview-controls">
<div class="player-box"> <button id="soundfontPlayBtn" class="preview-play" disabled>Play Preview</button>
<h4>Tone.js Piano</h4>
<p>High-quality sampled piano</p>
<button id="tonejsPlayBtn" disabled>Play</button>
<button id="tonejsStopBtn" disabled>Stop</button>
<div class="volume-control">
<label for="tonejsVolume">Volume:</label>
<input type="range" id="tonejsVolume" min="0" max="1" step="0.01" value="0.8" />
</div>
</div>
<div class="player-box">
<h4>Soundfont Piano</h4>
<p>Classic soundfont piano</p>
<button id="soundfontPlayBtn" disabled>Play</button>
<button id="soundfontStopBtn" disabled>Stop</button> <button id="soundfontStopBtn" disabled>Stop</button>
<div class="volume-control"> <div class="volume-control">
<label for="soundfontVolume">Volume:</label> <label for="soundfontVolume">Volume</label>
<input type="range" id="soundfontVolume" min="0" max="1" step="0.01" value="0.8" /> <input type="range" id="soundfontVolume" min="0" max="1" step="0.01" value="0.8" />
</div> </div>
</div> <button id="nextResultBtn" class="next-result" disabled>Try Next Result</button>
<div class="player-box">
<h4>Custom Synthesis</h4>
<p>Multi-oscillator web audio</p>
<button id="customPlayBtn" disabled>Play</button>
<button id="customStopBtn" disabled>Stop</button>
<div class="volume-control">
<label for="customVolume">Volume:</label>
<input type="range" id="customVolume" min="0" max="1" step="0.01" value="0.8" />
</div>
</div>
</div>
<div class="try-next">
<button id="nextResultBtn" disabled>Try Next Result</button>
</div> </div>
</div> </div>
+12 -107
View File
@@ -1,9 +1,7 @@
import { MotifEngine } from './core/MotifEngine'; import { MotifEngine } from './core/MotifEngine';
import { MIDIService } from './services/MIDIService'; import { MIDIService } from './services/MIDIService';
import { MIDIParser } from './midi/MIDIParser'; import { MIDIParser } from './midi/MIDIParser';
import { EnhancedMIDIPlayer } from './synthesis/EnhancedMIDIPlayer';
import { SoundfontMIDIPlayer } from './synthesis/SoundfontMIDIPlayer'; import { SoundfontMIDIPlayer } from './synthesis/SoundfontMIDIPlayer';
import { ToneJSMIDIPlayer } from './synthesis/ToneJSMIDIPlayer';
import type { NoteEvent } from './types'; import type { NoteEvent } from './types';
class MotifApp { class MotifApp {
@@ -11,10 +9,8 @@ class MotifApp {
private midiService: MIDIService; private midiService: MIDIService;
private audioContext: AudioContext; private audioContext: AudioContext;
// Multiple player instances // Preview player
private toneJSPlayer: ToneJSMIDIPlayer;
private soundfontPlayer: SoundfontMIDIPlayer; private soundfontPlayer: SoundfontMIDIPlayer;
private customPlayer: EnhancedMIDIPlayer;
private searchBtn!: HTMLButtonElement; private searchBtn!: HTMLButtonElement;
private songInput!: HTMLInputElement; private songInput!: HTMLInputElement;
@@ -26,22 +22,12 @@ class MotifApp {
private selectedTitle!: HTMLElement; private selectedTitle!: HTMLElement;
private selectedMeta!: HTMLElement; private selectedMeta!: HTMLElement;
// Tone.js player controls
private tonejsPlayBtn!: HTMLButtonElement;
private tonejsStopBtn!: HTMLButtonElement;
private tonejsVolumeSlider!: HTMLInputElement;
// Soundfont player controls // Preview player controls
private soundfontPlayBtn!: HTMLButtonElement; private soundfontPlayBtn!: HTMLButtonElement;
private soundfontStopBtn!: HTMLButtonElement; private soundfontStopBtn!: HTMLButtonElement;
private soundfontVolumeSlider!: HTMLInputElement; private soundfontVolumeSlider!: HTMLInputElement;
// Custom player controls
private customPlayBtn!: HTMLButtonElement;
private customStopBtn!: HTMLButtonElement;
private customVolumeSlider!: HTMLInputElement;
// Motif controls // Motif controls
private motifBtn!: HTMLButtonElement; private motifBtn!: HTMLButtonElement;
private motifStopBtn!: HTMLButtonElement; private motifStopBtn!: HTMLButtonElement;
@@ -65,10 +51,8 @@ class MotifApp {
this.motifEngine = new MotifEngine(); this.motifEngine = new MotifEngine();
this.midiService = new MIDIService(); this.midiService = new MIDIService();
// Initialize all players // Initialize preview player
this.toneJSPlayer = new ToneJSMIDIPlayer();
this.soundfontPlayer = new SoundfontMIDIPlayer(this.audioContext); this.soundfontPlayer = new SoundfontMIDIPlayer(this.audioContext);
this.customPlayer = new EnhancedMIDIPlayer(this.audioContext);
this.initializeUI(); this.initializeUI();
this.setupEventListeners(); this.setupEventListeners();
@@ -85,22 +69,12 @@ class MotifApp {
this.selectedTitle = document.getElementById('selectedTitle')!; this.selectedTitle = document.getElementById('selectedTitle')!;
this.selectedMeta = document.getElementById('selectedMeta')!; this.selectedMeta = document.getElementById('selectedMeta')!;
// Tone.js controls
this.tonejsPlayBtn = document.getElementById('tonejsPlayBtn') as HTMLButtonElement;
this.tonejsStopBtn = document.getElementById('tonejsStopBtn') as HTMLButtonElement;
this.tonejsVolumeSlider = document.getElementById('tonejsVolume') as HTMLInputElement;
// Soundfont controls // Preview player controls
this.soundfontPlayBtn = document.getElementById('soundfontPlayBtn') as HTMLButtonElement; this.soundfontPlayBtn = document.getElementById('soundfontPlayBtn') as HTMLButtonElement;
this.soundfontStopBtn = document.getElementById('soundfontStopBtn') as HTMLButtonElement; this.soundfontStopBtn = document.getElementById('soundfontStopBtn') as HTMLButtonElement;
this.soundfontVolumeSlider = document.getElementById('soundfontVolume') as HTMLInputElement; this.soundfontVolumeSlider = document.getElementById('soundfontVolume') as HTMLInputElement;
// Custom controls
this.customPlayBtn = document.getElementById('customPlayBtn') as HTMLButtonElement;
this.customStopBtn = document.getElementById('customStopBtn') as HTMLButtonElement;
this.customVolumeSlider = document.getElementById('customVolume') as HTMLInputElement;
// Motif controls // Motif controls
this.motifBtn = document.getElementById('motifBtn') as HTMLButtonElement; this.motifBtn = document.getElementById('motifBtn') as HTMLButtonElement;
this.motifStopBtn = document.getElementById('motifStopBtn') as HTMLButtonElement; this.motifStopBtn = document.getElementById('motifStopBtn') as HTMLButtonElement;
@@ -122,16 +96,8 @@ class MotifApp {
this.handleSearch(); this.handleSearch();
} }
}); });
// Tone.js player
this.tonejsPlayBtn.addEventListener('click', () => this.handleTonejsPlay());
this.tonejsStopBtn.addEventListener('click', () => this.handleTonejsStop());
this.tonejsVolumeSlider.addEventListener('input', (e) => {
const volume = parseFloat((e.target as HTMLInputElement).value);
this.toneJSPlayer.setVolume(volume);
});
// Soundfont player // Preview player
this.soundfontPlayBtn.addEventListener('click', () => this.handleSoundfontPlay()); this.soundfontPlayBtn.addEventListener('click', () => this.handleSoundfontPlay());
this.soundfontStopBtn.addEventListener('click', () => this.handleSoundfontStop()); this.soundfontStopBtn.addEventListener('click', () => this.handleSoundfontStop());
this.soundfontVolumeSlider.addEventListener('input', (e) => { this.soundfontVolumeSlider.addEventListener('input', (e) => {
@@ -139,14 +105,6 @@ class MotifApp {
this.soundfontPlayer.setVolume(volume); this.soundfontPlayer.setVolume(volume);
}); });
// Custom player
this.customPlayBtn.addEventListener('click', () => this.handleCustomPlay());
this.customStopBtn.addEventListener('click', () => this.handleCustomStop());
this.customVolumeSlider.addEventListener('input', (e) => {
const volume = parseFloat((e.target as HTMLInputElement).value);
this.customPlayer.setVolume(volume);
});
// Motif // Motif
this.motifBtn.addEventListener('click', () => this.handleMotif()); this.motifBtn.addEventListener('click', () => this.handleMotif());
this.motifStopBtn.addEventListener('click', () => this.handleMotifStop()); this.motifStopBtn.addEventListener('click', () => this.handleMotifStop());
@@ -267,13 +225,9 @@ class MotifApp {
this.currentMIDI = { events, metadata: { ...metadata, duration: actualDuration } }; this.currentMIDI = { events, metadata: { ...metadata, duration: actualDuration } };
// Load into all players // Load into preview player
await Promise.all([ await this.soundfontPlayer.load(events);
this.toneJSPlayer.load(events),
this.soundfontPlayer.load(events),
this.customPlayer.load(events)
]);
// Update UI // Update UI
this.selectedTitle.textContent = result.title; this.selectedTitle.textContent = result.title;
this.selectedMeta.innerHTML = ` this.selectedMeta.innerHTML = `
@@ -293,67 +247,24 @@ class MotifApp {
} }
} }
// Tone.js player handlers // Preview player handlers
private async handleTonejsPlay(): Promise<void> {
if (!this.currentMIDI) return;
try {
await this.toneJSPlayer.play();
this.tonejsPlayBtn.disabled = true;
this.tonejsStopBtn.disabled = false;
this.updateStatus('Playing Tone.js piano...');
} catch (error) {
this.updateStatus(`Tone.js error: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
private handleTonejsStop(): void {
console.log('Tone.js stop button clicked');
this.toneJSPlayer.stop();
this.tonejsPlayBtn.disabled = false;
this.tonejsStopBtn.disabled = true;
this.updateStatus('Tone.js stopped.');
}
// Soundfont player handlers
private async handleSoundfontPlay(): Promise<void> { private async handleSoundfontPlay(): Promise<void> {
if (!this.currentMIDI) return; if (!this.currentMIDI) return;
try { try {
await this.soundfontPlayer.play(); await this.soundfontPlayer.play();
this.soundfontPlayBtn.disabled = true; this.soundfontPlayBtn.disabled = true;
this.soundfontStopBtn.disabled = false; this.soundfontStopBtn.disabled = false;
this.updateStatus('Playing Soundfont piano...'); this.updateStatus('Previewing MIDI...');
} catch (error) { } catch (error) {
this.updateStatus(`Soundfont error: ${error instanceof Error ? error.message : 'Unknown error'}`); this.updateStatus(`Preview error: ${error instanceof Error ? error.message : 'Unknown error'}`);
} }
} }
private handleSoundfontStop(): void { private handleSoundfontStop(): void {
console.log('Soundfont stop button clicked');
this.soundfontPlayer.stop(); this.soundfontPlayer.stop();
this.soundfontPlayBtn.disabled = false; this.soundfontPlayBtn.disabled = false;
this.soundfontStopBtn.disabled = true; this.soundfontStopBtn.disabled = true;
this.updateStatus('Soundfont stopped.'); this.updateStatus('Preview stopped.');
}
// Custom player handlers
private async handleCustomPlay(): Promise<void> {
if (!this.currentMIDI) return;
try {
await this.customPlayer.play();
this.customPlayBtn.disabled = true;
this.customStopBtn.disabled = false;
this.updateStatus('Playing custom synthesis...');
} catch (error) {
this.updateStatus(`Custom player error: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
private handleCustomStop(): void {
console.log('Custom stop button clicked');
this.customPlayer.stop();
this.customPlayBtn.disabled = false;
this.customStopBtn.disabled = true;
this.updateStatus('Custom synthesis stopped.');
} }
// Motif handlers // Motif handlers
@@ -449,20 +360,14 @@ class MotifApp {
} }
private enablePlayerControls(): void { private enablePlayerControls(): void {
this.tonejsPlayBtn.disabled = false;
this.soundfontPlayBtn.disabled = false; this.soundfontPlayBtn.disabled = false;
this.customPlayBtn.disabled = false;
this.motifBtn.disabled = false; this.motifBtn.disabled = false;
this.nextResultBtn.disabled = this.searchResults.length <= 1; this.nextResultBtn.disabled = this.searchResults.length <= 1;
} }
private disablePlayerControls(): void { private disablePlayerControls(): void {
this.tonejsPlayBtn.disabled = true;
this.tonejsStopBtn.disabled = true;
this.soundfontPlayBtn.disabled = true; this.soundfontPlayBtn.disabled = true;
this.soundfontStopBtn.disabled = true; this.soundfontStopBtn.disabled = true;
this.customPlayBtn.disabled = true;
this.customStopBtn.disabled = true;
this.motifBtn.disabled = true; this.motifBtn.disabled = true;
this.motifStopBtn.disabled = true; this.motifStopBtn.disabled = true;
this.nextResultBtn.disabled = true; this.nextResultBtn.disabled = true;