445 lines
12 KiB
HTML
445 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Wario Synth v2 - Sound Test</title>
|
|
<style>
|
|
:root {
|
|
--bg-dark: #0a0a0a;
|
|
--bg-panel: #1a1a1a;
|
|
--gb-green: #9bbc0f;
|
|
--gb-dark-green: #0f380f;
|
|
--gb-light-green: #8bac0f;
|
|
--text: #e0e0e0;
|
|
--text-dim: #888;
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Courier New', monospace;
|
|
background: var(--bg-dark);
|
|
color: var(--text);
|
|
min-height: 100vh;
|
|
padding: 2rem;
|
|
}
|
|
|
|
.container {
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
h1 {
|
|
color: var(--gb-green);
|
|
text-align: center;
|
|
font-size: 1.5rem;
|
|
margin-bottom: 0.5rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 2px;
|
|
}
|
|
|
|
.subtitle {
|
|
text-align: center;
|
|
color: var(--text-dim);
|
|
margin-bottom: 2rem;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.panel {
|
|
background: var(--bg-panel);
|
|
border: 2px solid var(--gb-dark-green);
|
|
border-radius: 8px;
|
|
padding: 1.5rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.panel h2 {
|
|
color: var(--gb-light-green);
|
|
font-size: 1rem;
|
|
margin-bottom: 1rem;
|
|
border-bottom: 1px solid var(--gb-dark-green);
|
|
padding-bottom: 0.5rem;
|
|
}
|
|
|
|
.btn-row {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.75rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
button {
|
|
background: var(--gb-dark-green);
|
|
color: var(--gb-green);
|
|
border: 2px solid var(--gb-green);
|
|
padding: 0.75rem 1.25rem;
|
|
font-family: inherit;
|
|
font-size: 0.85rem;
|
|
cursor: pointer;
|
|
border-radius: 4px;
|
|
transition: all 0.15s;
|
|
text-transform: uppercase;
|
|
letter-spacing: 1px;
|
|
}
|
|
|
|
button:hover {
|
|
background: var(--gb-green);
|
|
color: var(--gb-dark-green);
|
|
}
|
|
|
|
button:active {
|
|
transform: scale(0.98);
|
|
}
|
|
|
|
button:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
button.primary {
|
|
background: var(--gb-green);
|
|
color: var(--gb-dark-green);
|
|
font-weight: bold;
|
|
}
|
|
|
|
button.primary:hover {
|
|
background: var(--gb-light-green);
|
|
}
|
|
|
|
.log {
|
|
background: #000;
|
|
border: 1px solid var(--gb-dark-green);
|
|
border-radius: 4px;
|
|
padding: 1rem;
|
|
height: 200px;
|
|
overflow-y: auto;
|
|
font-size: 0.8rem;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.log-entry {
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.log-entry.success {
|
|
color: var(--gb-green);
|
|
}
|
|
|
|
.log-entry.error {
|
|
color: #ff6b6b;
|
|
}
|
|
|
|
.log-entry.info {
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
.status {
|
|
display: inline-block;
|
|
padding: 0.25rem 0.5rem;
|
|
border-radius: 4px;
|
|
font-size: 0.75rem;
|
|
margin-left: 0.5rem;
|
|
}
|
|
|
|
.status.ready {
|
|
background: var(--gb-dark-green);
|
|
color: var(--gb-green);
|
|
}
|
|
|
|
.status.playing {
|
|
background: var(--gb-green);
|
|
color: var(--bg-dark);
|
|
animation: pulse 1s infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0.7; }
|
|
}
|
|
|
|
.verification-results {
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.result-item {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 0.5rem;
|
|
border-bottom: 1px solid var(--bg-dark);
|
|
}
|
|
|
|
.result-item .icon {
|
|
width: 20px;
|
|
margin-right: 0.75rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.result-item.pass .icon {
|
|
color: var(--gb-green);
|
|
}
|
|
|
|
.result-item.fail .icon {
|
|
color: #ff6b6b;
|
|
}
|
|
|
|
.result-item .name {
|
|
flex: 1;
|
|
}
|
|
|
|
.result-item .message {
|
|
color: var(--text-dim);
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.warning {
|
|
background: #332200;
|
|
border: 1px solid #665500;
|
|
color: #ffcc00;
|
|
padding: 1rem;
|
|
border-radius: 4px;
|
|
margin-bottom: 1rem;
|
|
font-size: 0.85rem;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>Wario Synth v2</h1>
|
|
<p class="subtitle">Game Boy Sound Engine Test Suite</p>
|
|
|
|
<div class="warning">
|
|
⚠️ This is the v2 test page. The main site (v1) is unaffected.
|
|
Click any button to start audio (required by browsers).
|
|
</div>
|
|
|
|
<div class="panel">
|
|
<h2>Verification Tests</h2>
|
|
<div class="btn-row">
|
|
<button id="btn-verify" class="primary">Run Verification</button>
|
|
</div>
|
|
<div id="verification-results" class="verification-results"></div>
|
|
</div>
|
|
|
|
<div class="panel">
|
|
<h2>Audio Tests <span id="status" class="status ready">Ready</span></h2>
|
|
<div class="btn-row">
|
|
<button id="btn-duty">Test Duty Cycles</button>
|
|
<button id="btn-wave">Test Wave Channel</button>
|
|
<button id="btn-noise">Test Noise Channel</button>
|
|
</div>
|
|
<div class="btn-row">
|
|
<button id="btn-combined" class="primary">Test Combined</button>
|
|
<button id="btn-all">Run All Tests</button>
|
|
<button id="btn-stop" style="background: #661111; border-color: #ff4444; color: #ff4444;">■ STOP ALL</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="panel">
|
|
<h2>Quick Sound Check</h2>
|
|
<div class="btn-row">
|
|
<button id="btn-note-c">Play C4</button>
|
|
<button id="btn-note-e">Play E4</button>
|
|
<button id="btn-note-g">Play G4</button>
|
|
<button id="btn-bass">Play Bass</button>
|
|
<button id="btn-kick">Kick</button>
|
|
<button id="btn-snare">Snare</button>
|
|
<button id="btn-hihat">HiHat</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="panel">
|
|
<h2>Console Log</h2>
|
|
<div id="log" class="log"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="module">
|
|
import {
|
|
runVerificationTests,
|
|
createTestChannels,
|
|
testDutyCycles,
|
|
testWaveChannel,
|
|
testNoiseChannel,
|
|
testCombined,
|
|
runAllAudioTests
|
|
} from './src-v2/audio/test/soundTest.ts';
|
|
|
|
let audioContext = null;
|
|
let channels = null;
|
|
const activeNodes = new Set();
|
|
|
|
const logEl = document.getElementById('log');
|
|
const statusEl = document.getElementById('status');
|
|
const verificationEl = document.getElementById('verification-results');
|
|
|
|
// Helper to track and auto-cleanup nodes
|
|
function trackNode(node) {
|
|
activeNodes.add(node);
|
|
node.onended = () => activeNodes.delete(node);
|
|
return node;
|
|
}
|
|
|
|
// Stop all playing sounds
|
|
function stopAll() {
|
|
if (audioContext) {
|
|
const now = audioContext.currentTime;
|
|
for (const node of activeNodes) {
|
|
try {
|
|
node.stop(now);
|
|
} catch (e) {
|
|
// Already stopped
|
|
}
|
|
}
|
|
activeNodes.clear();
|
|
addLog('All sounds stopped', 'info');
|
|
setStatus('Ready');
|
|
}
|
|
}
|
|
|
|
// Override console.log to show in UI
|
|
const originalLog = console.log;
|
|
console.log = (...args) => {
|
|
originalLog(...args);
|
|
addLog(args.join(' '), 'info');
|
|
};
|
|
|
|
function addLog(message, type = 'info') {
|
|
const entry = document.createElement('div');
|
|
entry.className = `log-entry ${type}`;
|
|
entry.textContent = `> ${message}`;
|
|
logEl.appendChild(entry);
|
|
logEl.scrollTop = logEl.scrollHeight;
|
|
}
|
|
|
|
function setStatus(status) {
|
|
statusEl.textContent = status;
|
|
statusEl.className = `status ${status.toLowerCase()}`;
|
|
}
|
|
|
|
async function ensureAudioContext() {
|
|
if (!audioContext) {
|
|
audioContext = new AudioContext();
|
|
channels = createTestChannels(audioContext);
|
|
addLog('Audio context initialized', 'success');
|
|
}
|
|
if (audioContext.state === 'suspended') {
|
|
await audioContext.resume();
|
|
}
|
|
}
|
|
|
|
// Verification tests
|
|
document.getElementById('btn-verify').addEventListener('click', () => {
|
|
const results = runVerificationTests();
|
|
|
|
verificationEl.innerHTML = results.map(r => `
|
|
<div class="result-item ${r.passed ? 'pass' : 'fail'}">
|
|
<span class="icon">${r.passed ? '✓' : '✗'}</span>
|
|
<span class="name">${r.name}</span>
|
|
<span class="message">${r.message}</span>
|
|
</div>
|
|
`).join('');
|
|
|
|
results.forEach(r => {
|
|
addLog(`${r.passed ? '✓' : '✗'} ${r.name}: ${r.message}`, r.passed ? 'success' : 'error');
|
|
});
|
|
});
|
|
|
|
// Audio tests
|
|
document.getElementById('btn-duty').addEventListener('click', async () => {
|
|
await ensureAudioContext();
|
|
setStatus('Playing');
|
|
await testDutyCycles(channels.pulse, audioContext);
|
|
setStatus('Ready');
|
|
});
|
|
|
|
document.getElementById('btn-wave').addEventListener('click', async () => {
|
|
await ensureAudioContext();
|
|
setStatus('Playing');
|
|
await testWaveChannel(channels.wave, audioContext);
|
|
setStatus('Ready');
|
|
});
|
|
|
|
document.getElementById('btn-noise').addEventListener('click', async () => {
|
|
await ensureAudioContext();
|
|
setStatus('Playing');
|
|
await testNoiseChannel(channels.noise, audioContext);
|
|
setStatus('Ready');
|
|
});
|
|
|
|
document.getElementById('btn-combined').addEventListener('click', async () => {
|
|
await ensureAudioContext();
|
|
setStatus('Playing');
|
|
await testCombined(channels.pulse, channels.wave, channels.noise, audioContext);
|
|
setStatus('Ready');
|
|
});
|
|
|
|
document.getElementById('btn-all').addEventListener('click', async () => {
|
|
await ensureAudioContext();
|
|
setStatus('Playing');
|
|
await runAllAudioTests(audioContext);
|
|
setStatus('Ready');
|
|
});
|
|
|
|
// Stop button
|
|
document.getElementById('btn-stop').addEventListener('click', () => {
|
|
stopAll();
|
|
});
|
|
|
|
// Quick sound checks
|
|
document.getElementById('btn-note-c').addEventListener('click', async () => {
|
|
await ensureAudioContext();
|
|
channels.pulse.setDutyCycle(2);
|
|
const result = channels.pulse.playNote(60, 0.3, 100);
|
|
trackNode(result.oscillator);
|
|
});
|
|
|
|
document.getElementById('btn-note-e').addEventListener('click', async () => {
|
|
await ensureAudioContext();
|
|
channels.pulse.setDutyCycle(1);
|
|
const result = channels.pulse.playNote(64, 0.3, 100);
|
|
trackNode(result.oscillator);
|
|
});
|
|
|
|
document.getElementById('btn-note-g').addEventListener('click', async () => {
|
|
await ensureAudioContext();
|
|
channels.pulse.setDutyCycle(0);
|
|
const result = channels.pulse.playNote(67, 0.3, 100);
|
|
trackNode(result.oscillator);
|
|
});
|
|
|
|
document.getElementById('btn-bass').addEventListener('click', async () => {
|
|
await ensureAudioContext();
|
|
channels.wave.loadPreset('bass');
|
|
const result = channels.wave.playNote(36, 0.5, 100);
|
|
trackNode(result.oscillator);
|
|
});
|
|
|
|
document.getElementById('btn-kick').addEventListener('click', async () => {
|
|
await ensureAudioContext();
|
|
const result = channels.noise.playKick(100);
|
|
if (result) trackNode(result.source);
|
|
});
|
|
|
|
document.getElementById('btn-snare').addEventListener('click', async () => {
|
|
await ensureAudioContext();
|
|
const result = channels.noise.playSnare(100);
|
|
if (result) trackNode(result.source);
|
|
});
|
|
|
|
document.getElementById('btn-hihat').addEventListener('click', async () => {
|
|
await ensureAudioContext();
|
|
const result = channels.noise.playHihat(80, false);
|
|
if (result) trackNode(result.source);
|
|
});
|
|
|
|
addLog('Wario Synth v2 Test Page loaded', 'success');
|
|
addLog('Click any button to initialize audio', 'info');
|
|
</script>
|
|
</body>
|
|
</html>
|