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:
+39
-42
@@ -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>
|
||||||
|
|
||||||
|
|||||||
+11
-106
@@ -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;
|
||||||
@@ -27,21 +23,11 @@ class MotifApp {
|
|||||||
private selectedTitle!: HTMLElement;
|
private selectedTitle!: HTMLElement;
|
||||||
private selectedMeta!: HTMLElement;
|
private selectedMeta!: HTMLElement;
|
||||||
|
|
||||||
// Tone.js player controls
|
// Preview player controls
|
||||||
private tonejsPlayBtn!: HTMLButtonElement;
|
|
||||||
private tonejsStopBtn!: HTMLButtonElement;
|
|
||||||
private tonejsVolumeSlider!: HTMLInputElement;
|
|
||||||
|
|
||||||
// Soundfont 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();
|
||||||
@@ -86,21 +70,11 @@ 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
|
// Preview player controls
|
||||||
this.tonejsPlayBtn = document.getElementById('tonejsPlayBtn') as HTMLButtonElement;
|
|
||||||
this.tonejsStopBtn = document.getElementById('tonejsStopBtn') as HTMLButtonElement;
|
|
||||||
this.tonejsVolumeSlider = document.getElementById('tonejsVolume') as HTMLInputElement;
|
|
||||||
|
|
||||||
// Soundfont 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;
|
||||||
@@ -123,15 +97,7 @@ class MotifApp {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Tone.js player
|
// Preview 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
|
|
||||||
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,12 +225,8 @@ 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;
|
||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user