You've already forked ivan-torvalds-GuitarPedal
forked from AllSpiceMirrors/torvalds-GuitarPedal
The pedal filters Control Change and Program Change by settings.midi_channel and lets SysEx past regardless. The app transmitted on channel 1 always: activeTransmitChannel only ever moved in response to CC 107, and no firmware has ever sent or handled CC 107. So setting "MIDI Ch" to anything but Omni broke global bypass, the tuner toggle, scene loads and the reboot, while parameter edits carried on working - a failure mode that looks like half the app coming loose rather than like one setting. Take the channel from the schema instead, where it has been all along. It arrives in the state dump like any other pot, and it is picked up from three directions: the control on the Settings card, a PARAM_UPDATE from the pedal, or the new copy in the dialog. The pot is found by the effect's 'base' - the header's filename - rather than by its display name, which is the steadier of the two handles. The dead CC 107 paths go. Note that startup was never actually stuck: REQ_SCHEMA and REQ_STATE are SysEx, handle_midi_packet() dispatches SysEx and returns above the channel check, and sysex_send_state_dump() walks every effect including settings. So the state always arrived; nothing read the channel out of it. Rename the dialog to "MIDI Connection" while here, since devices and channel are all it holds, and give the channel a control there as well as on the Settings card. It is a view rather than a second setting - changing it drives the card's control, so one place still decides what gets sent - and it is where you go looking when Control Change appears dead, which is the moment you most need to be able to fix it. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
165 lines
7.0 KiB
HTML
165 lines
7.0 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>RP2350 Pedal Controller</title>
|
|
<link rel="manifest" href="manifest.json">
|
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
|
<link rel="apple-touch-icon" href="icon-192.png">
|
|
<link rel="stylesheet" href="style.css">
|
|
</head>
|
|
<body>
|
|
<div class="app-container">
|
|
<header class="glass-panel app-header">
|
|
<h1 id="app-title" class="title-disconnected">RP2350 Pedal</h1>
|
|
<div class="header-controls">
|
|
<!-- Which scene you're on is worth seeing without opening
|
|
anything. Acting on it is not: Save overwrites the eeprom
|
|
and Load throws away what you have, so both sit behind the
|
|
menu where you can't reach them by accident. -->
|
|
<div class="header-scenes">
|
|
<select id="global-scene-select" title="Scene">
|
|
<!-- Options injected via JS -->
|
|
</select>
|
|
</div>
|
|
<button id="tuner-btn" class="action-btn" title="Tuner Mode">🎵</button>
|
|
<label class="switch" title="Global Enable">
|
|
<input type="checkbox" id="global-enable">
|
|
<span class="slider round"></span>
|
|
</label>
|
|
<div class="burger-wrap">
|
|
<button id="burger-btn" class="action-btn" title="Menu"
|
|
aria-haspopup="true" aria-expanded="false">☰</button>
|
|
<div id="global-menu" class="menu-dropdown hidden" role="menu">
|
|
<!-- The scene labels are filled in from the picker, so it
|
|
always says which one it is about to overwrite. -->
|
|
<button id="global-save-scene-btn" role="menuitem">💾 Save Scene</button>
|
|
<button id="global-load-scene-btn" role="menuitem">📂 Load Scene</button>
|
|
<button id="global-unroute-btn" role="menuitem">⏏ Unroute All Effects</button>
|
|
<hr>
|
|
<button id="update-app-btn" role="menuitem">⬇️ Update App</button>
|
|
<button id="open-settings-btn" role="menuitem">🔌 MIDI Connection…</button>
|
|
<hr>
|
|
<button id="global-program-btn" class="danger-item" role="menuitem">⚠️ Reboot to Programming Mode</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Tuner Panel -->
|
|
<div id="tuner-panel" class="glass-panel hidden">
|
|
<div class="tuner-header">
|
|
<h2>Tuner</h2>
|
|
<div style="display: flex; gap: 15px; align-items: center;">
|
|
<label style="color: var(--text-muted); display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
|
<input type="checkbox" id="tuner-synth-toggle"> Play Tones
|
|
</label>
|
|
<button id="close-tuner-btn" class="action-btn" title="Exit Tuner">✕</button>
|
|
</div>
|
|
</div>
|
|
<div class="tuner-chromatic-display">
|
|
<div class="tuner-note" id="tuner-chromatic-note">--</div>
|
|
<div class="tuner-cents" id="tuner-chromatic-cents">0¢</div>
|
|
<div class="tuner-vol" id="tuner-chromatic-vol">V: 0</div>
|
|
</div>
|
|
<div class="tuner-meter-container">
|
|
<div class="tuner-meter-center"></div>
|
|
<div id="tuner-needle" class="tuner-needle"></div>
|
|
</div>
|
|
<div class="tuner-strings" id="tuner-poly-strings">
|
|
<!-- Strings dynamically added here -->
|
|
</div>
|
|
</div>
|
|
|
|
<main id="effects-container">
|
|
<!-- Effect cards populated dynamically -->
|
|
</main>
|
|
|
|
<!-- Panel Backdrop -->
|
|
<div id="panel-backdrop" class="hidden"></div>
|
|
|
|
<!-- Reached from the menu. Set once and then left alone, which is
|
|
why it isn't in the menu itself. -->
|
|
<div id="settings-panel" class="glass-panel dialog-panel hidden" role="dialog" aria-modal="true">
|
|
<div class="panel-header">
|
|
<h2>MIDI Connection</h2>
|
|
<button id="close-settings" class="action-btn" title="Close">✕</button>
|
|
</div>
|
|
<div class="dialog-body">
|
|
<div class="menu-section">
|
|
<h3>Devices</h3>
|
|
<div class="menu-field">
|
|
<label for="midi-input-select">Input</label>
|
|
<select id="midi-input-select" class="menu-select"></select>
|
|
</div>
|
|
<div class="menu-field">
|
|
<label for="midi-output-select">Output</label>
|
|
<select id="midi-output-select" class="menu-select"></select>
|
|
</div>
|
|
</div>
|
|
<div class="menu-section">
|
|
<h3>Channel</h3>
|
|
<!-- The same value as the Settings card's "MIDI Ch" pot, and
|
|
the same control underneath - see bindMidiChannel(). It
|
|
is repeated here because this is where you come looking
|
|
when Control Change stops working, and the channel is the
|
|
usual reason. -->
|
|
<div class="menu-field">
|
|
<select id="midi-channel-select" class="menu-select"></select>
|
|
</div>
|
|
<p class="menu-hint">The pedal filters Control Change and Program
|
|
Change by this, so bypass, the tuner and scene changes need it to
|
|
match. Parameter edits are SysEx and work either way — which is
|
|
how this dialog can still fix it when it is wrong.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Active Pot Panel -->
|
|
<div id="active-pot-panel" class="glass-panel bottom-panel hidden">
|
|
<div class="panel-header">
|
|
<div class="active-pot-info">
|
|
<span id="active-pot-title">Effect - Pot</span>
|
|
<span id="active-pot-value" class="pot-value">0</span>
|
|
</div>
|
|
<button id="close-active-pot" class="action-btn" title="Close">✕</button>
|
|
</div>
|
|
<div class="active-pot-slider-container">
|
|
<input type="range" id="active-pot-slider" class="large-slider" min="0" max="120" value="60">
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<script src="effects.js"></script>
|
|
<script src="app.js"></script>
|
|
<script>
|
|
// Keep the service worker away from local development. Unregistering
|
|
// one does not stop it controlling the page - the active worker stays
|
|
// in charge of already-controlled clients until they all go away - so
|
|
// a stale app.js can happily survive several reloads and an "Update
|
|
// App" or three. The PWA only matters for the deployed copy anyway.
|
|
//
|
|
// IS_LOCAL_DEV comes from app.js, which is loaded above.
|
|
if ('serviceWorker' in navigator) {
|
|
if (IS_LOCAL_DEV) {
|
|
// And clean up after any worker registered here previously
|
|
navigator.serviceWorker.getRegistrations()
|
|
.then(regs => regs.forEach(reg => reg.unregister()));
|
|
} else {
|
|
window.addEventListener('load', () => {
|
|
navigator.serviceWorker.register('./service-worker.js')
|
|
.then(registration => {
|
|
console.log('ServiceWorker registration successful');
|
|
})
|
|
.catch(err => {
|
|
console.log('ServiceWorker registration failed: ', err);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|