pachet's picture
Fallback MIDI export in embedded Spaces
be6dab9
Raw
History Blame Contribute Delete
46.4 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Web Continuator</title>
<link rel="stylesheet" href="/assets/styles.css?v=20260602-session-midi-zip3" />
</head>
<body>
<div class="page-aura"></div>
<div class="page-grid"></div>
<header class="hero">
<div class="hero-copy">
<h1>Web Continuator</h1>
</div>
<div class="hero-side">
<span id="timing-readout" class="hero-status small-copy">Waiting for first phrase</span>
<div class="hero-actions">
<button
class="hero-utility-button ghost"
id="open-readme-button"
type="button"
aria-haspopup="dialog"
aria-expanded="false"
aria-controls="readme-panel"
>
Read Me
</button>
</div>
<button
class="account-trigger"
id="auth-status"
type="button"
aria-expanded="false"
aria-controls="account-panel"
>
<span id="auth-avatar" class="account-avatar" aria-hidden="true">G</span>
<span class="account-trigger-copy">
<span id="auth-status-label" class="account-trigger-label">Guest mode</span>
<span id="auth-status-copy" class="account-trigger-meta">Click to sign in</span>
</span>
</button>
<section id="account-panel" class="account-panel hero-account-panel" hidden>
<div class="section-head">
<div>
<h3>Account</h3>
<p id="account-panel-copy" class="section-copy">
Guest mode works immediately. Sign in only if you want your
sessions saved and reopenable.
</p>
</div>
</div>
<dl class="info-grid">
<div>
<dt>Current User</dt>
<dd id="auth-user-name">Not signed in</dd>
</div>
<div>
<dt>Saved Sessions</dt>
<dd id="saved-session-count">Guest mode</dd>
</div>
</dl>
<div id="auth-signed-out-panel">
<div class="auth-grid">
<div class="field">
<label for="auth-username-input">Username</label>
<input
id="auth-username-input"
type="text"
minlength="2"
maxlength="32"
autocomplete="username"
placeholder="continuator_user"
/>
<p class="field-hint">
Use 2 to 32 letters, digits, underscores, dots, or dashes.
</p>
</div>
<div class="field">
<label for="auth-password-input">Password</label>
<input
id="auth-password-input"
type="password"
minlength="8"
maxlength="128"
autocomplete="current-password"
placeholder="At least 8 characters"
/>
<p class="field-hint">Passwords need at least 8 characters.</p>
</div>
</div>
<div class="button-row">
<button id="login-button" type="button">Sign In</button>
<button id="register-button" type="button" class="ghost">
Create Account
</button>
</div>
</div>
<div id="auth-signed-in-panel" class="button-row" hidden>
<button
id="account-open-sessions-button"
type="button"
class="ghost"
>
Open Saved Sessions
</button>
<button id="logout-button" type="button" class="ghost">Sign Out</button>
</div>
<div id="auth-message-box" class="message-box auth-message-box">
Guest mode is ready. Sign in only if you want saved sessions.
</div>
</section>
</div>
</header>
<div id="readme-overlay" class="sheet-overlay" hidden></div>
<aside
id="readme-panel"
class="readme-panel"
role="dialog"
aria-modal="true"
aria-labelledby="readme-title"
hidden
>
<div class="readme-head">
<div>
<p class="readme-kicker">Read Me</p>
<h2 id="readme-title">Web Continuator Guide</h2>
<p class="section-copy">
A compact overview of the app, the musical flow, and how sessions
are saved.
</p>
</div>
<button id="close-readme-button" class="ghost sheet-close" type="button">
Close
</button>
</div>
<div class="readme-sections">
<section class="readme-block">
<h3>What It Does</h3>
<p>
Web Continuator captures a phrase from browser MIDI, sends it to
the Python Continuator engine, and plays back the continuation
either through the built-in browser synth or a MIDI output port.
</p>
</section>
<section class="readme-block">
<h3>Quick Start</h3>
<ol class="readme-list">
<li>Create or open a session.</li>
<li>Click <strong>Connect MIDI</strong> and choose an input.</li>
<li>Play a short phrase on your keyboard.</li>
<li>Wait for the phrase to end, or click <strong>Send Phrase</strong>.</li>
<li>Review the continuation in the piano rolls, history, and memory.</li>
</ol>
</section>
<section class="readme-block">
<h3>Saving Session MIDI</h3>
<p>
The Session panel can export the current session as a MIDI ZIP
archive. The archive contains the played input phrases and generated
continuations in session order.
</p>
<p>
In Chrome and Edge, <strong>Don't ask again</strong> remembers the
selected ZIP file and overwrites it on future saves. Other browsers
and embedded Hugging Face pages use the normal download flow.
</p>
</section>
<section class="readme-block">
<h3>Studio Layout</h3>
<ul class="readme-list">
<li><strong>Perform</strong>: live MIDI capture, playback routing, phrase generation, replay, and infinite mode.</li>
<li><strong>Memory</strong>: corpus import, active engine memory, and phrase history.</li>
<li><strong>Session</strong>: session lifecycle, advanced Continuator settings, and saved sessions.</li>
</ul>
</section>
<section class="readme-block">
<h3>History vs Memory</h3>
<p>
<strong>History</strong> is the logged record of phrases sent and
generated for a session. <strong>Memory</strong> is the live state
currently active inside the Continuator engine. They are related,
but not always identical.
</p>
</section>
<section class="readme-block">
<h3>Accounts and Saved Sessions</h3>
<p>
Guest mode works immediately. Sign in only if you want sessions
saved under your username so you can reopen them later. The account
menu lives in the top-right header and includes sign in, sign out,
and saved-session access.
</p>
</section>
<section class="readme-block">
<h3>History and References</h3>
<p>
Continuator was developed by François Pachet at Sony CSL Paris in
1999 as an interactive musical system that learns a
performer’s style in real time and answers with stylistically
coherent continuations. The foundational paper introduced the idea
of a playable “virtual musician”, later articles described the
instrument in practice and in educational settings, and the current
Python implementation extends the approach with more explicit
controllable constraints.
</p>
<ul class="readme-list readme-link-list">
<li>
<a
href="https://github.com/fpachet/Continuator"
target="_blank"
rel="noreferrer noopener"
>
GitHub: fpachet/Continuator
</a>
</li>
<li>
<a
href="https://doi.org/10.1076/jnmr.32.3.333.16861"
target="_blank"
rel="noreferrer noopener"
>
The Continuator: Musical Interaction With Style (2003)
</a>
</li>
<li>
<a
href="https://doi.org/10.1109/MCG.2004.1255806"
target="_blank"
rel="noreferrer noopener"
>
Beyond the Cybernetic Jam Fantasy: the Continuator (2004)
</a>
</li>
<li>
<a
href="https://doi.org/10.1177/1029864906010001021"
target="_blank"
rel="noreferrer noopener"
>
Young Children Confronting the Continuator (2006)
</a>
</li>
<li>
<a
href="https://www.francoispachet.fr/tag/ai-music/"
target="_blank"
rel="noreferrer noopener"
>
François Pachet publications and AI music notes
</a>
</li>
</ul>
</section>
<section class="readme-block">
<h3>Performance Notes</h3>
<ul class="readme-list">
<li><strong>Auto-send phrase</strong> starts generation after the phrase ends.</li>
<li>A phrase closes after the selected phrase gap following the final note-off.</li>
<li><strong>Infinite mode</strong> keeps chaining continuations until you stop it.</li>
<li>Playing live MIDI interrupts whatever continuation is currently playing.</li>
</ul>
</section>
</div>
</aside>
<div id="graph-overlay" class="sheet-overlay" hidden></div>
<aside
id="graph-panel"
class="graph-panel"
role="dialog"
aria-modal="false"
aria-labelledby="graph-title"
hidden
>
<div class="graph-head">
<div>
<p class="readme-kicker">State Graph</p>
<h2 id="graph-title">Continuator Graph</h2>
<p id="graph-meta" class="section-copy">
Snapshot of the active session memory.
</p>
</div>
<button id="close-graph-button" class="ghost sheet-close" type="button">
Close
</button>
</div>
<div class="graph-tabs" role="tablist" aria-label="Continuator graph views">
<button
class="graph-tab is-active"
data-graph-kind="memory"
type="button"
role="tab"
aria-selected="true"
>
Memory Graph
</button>
<button
class="graph-tab"
data-graph-kind="constraints"
type="button"
role="tab"
aria-selected="false"
>
Last Constraint Graph
</button>
</div>
<div id="graph-controls" class="graph-controls" aria-label="Memory graph display options">
<label>
<span>Mode</span>
<select id="graph-mode-select">
<option value="slice">Weighted slice</option>
<option value="all">All</option>
<option value="most_used">Most used</option>
<option value="neighborhood">Latest phrase</option>
</select>
</label>
<label>
<span>Order</span>
<select id="graph-order-select">
<option value="all">All orders</option>
</select>
</label>
</div>
<div class="graph-image-frame">
<img
id="graph-image"
alt="Continuator internal graph snapshot"
/>
</div>
</aside>
<main class="layout">
<section class="column">
<article class="card control-workspace">
<div class="card-head control-head">
<div>
<h2>Studio Controls</h2>
</div>
<div class="status-cluster">
<span class="status-pill" id="session-status">No session</span>
<span class="status-pill" id="midi-status">Disconnected</span>
<span class="status-pill" id="phrase-status">Waiting for MIDI</span>
</div>
</div>
<div class="workspace-tabs" role="tablist" aria-label="Studio control views">
<button
class="workspace-tab is-active"
data-control-tab="perform"
type="button"
role="tab"
aria-selected="true"
aria-controls="control-perform-panel"
>
Perform
</button>
<button
class="workspace-tab"
data-control-tab="memory"
type="button"
role="tab"
aria-selected="false"
aria-controls="control-memory-panel"
>
Memory
</button>
<button
class="workspace-tab"
data-control-tab="session"
type="button"
role="tab"
aria-selected="false"
aria-controls="control-session-panel"
>
Session
</button>
</div>
<div class="performance-strip">
<div class="readiness-steps" aria-label="Performance readiness">
<span id="ready-session-step" class="readiness-step">Session</span>
<span id="ready-midi-step" class="readiness-step">MIDI</span>
<span id="ready-phrase-step" class="readiness-step">Phrase</span>
<span id="ready-output-step" class="readiness-step">Output</span>
</div>
<button id="global-panic-button" class="ghost danger-button global-panic-button" type="button">
Stop / Panic
</button>
</div>
<section
id="control-perform-panel"
class="workspace-panel"
data-control-panel="perform"
role="tabpanel"
>
<div class="panel-stack">
<section class="panel-section accent-section">
<div class="section-head">
<div>
<h3>Live Input</h3>
</div>
</div>
<div class="button-row">
<button id="connect-midi-button">Connect MIDI</button>
<button id="refresh-midi-button" class="ghost">Refresh Ports</button>
<label class="toggle">
<input id="auto-send-toggle" type="checkbox" checked />
<span>Auto-send phrase</span>
</label>
<label class="toggle">
<input id="input-monitor-toggle" type="checkbox" />
<span>Monitor input</span>
</label>
</div>
<div class="field">
<label for="midi-input-select">MIDI Input</label>
<select id="midi-input-select" disabled>
<option>Connect MIDI first</option>
</select>
</div>
<div
id="virtual-keyboard-panel"
class="virtual-keyboard-panel"
aria-label="Virtual MIDI keyboard"
hidden
>
<div class="virtual-keyboard-head">
<div>
<strong>Virtual MIDI Keyboard</strong>
<span id="virtual-octave-label">C4-B5</span>
</div>
<div class="virtual-octave-controls" aria-label="Octave controls">
<button id="virtual-octave-down-button" class="ghost compact-button" type="button">
Oct -
</button>
<button id="virtual-octave-up-button" class="ghost compact-button" type="button">
Oct +
</button>
</div>
</div>
<div class="virtual-keyboard-controls">
<label class="velocity-control" for="virtual-velocity-input">
<span>Velocity</span>
<input
id="virtual-velocity-input"
type="range"
min="1"
max="127"
value="100"
/>
<output id="virtual-velocity-value" for="virtual-velocity-input">100</output>
</label>
<label class="toggle compact-toggle">
<input id="virtual-sustain-toggle" type="checkbox" />
<span>Sustain</span>
</label>
<label class="toggle compact-toggle">
<input id="virtual-latch-toggle" type="checkbox" />
<span>Latch chord</span>
</label>
</div>
<div class="virtual-keyboard-viewport">
<div
id="virtual-keyboard"
class="virtual-keyboard"
aria-label="Playable piano keyboard"
></div>
</div>
<div class="virtual-keyboard-actions">
<button
id="virtual-clear-latch-button"
class="ghost compact-button"
type="button"
aria-label="Clear latched chord"
>
Clear chord
</button>
<button
id="virtual-panic-button"
class="ghost danger-button compact-button"
type="button"
aria-label="All notes off"
>
Panic
</button>
</div>
</div>
<div class="field">
<label for="phrase-timeout-input">Phrase gap (seconds)</label>
<input
id="phrase-timeout-input"
type="number"
min="0.2"
max="5"
step="0.1"
value="1"
inputmode="decimal"
/>
<p class="field-hint">
Capture completes after this delay once all notes are released.
</p>
<div id="phrase-gap-meter" class="phrase-gap-meter" hidden>
<div class="phrase-gap-meter-head">
<span id="phrase-gap-meter-label">Listening for more notes</span>
<strong id="phrase-gap-meter-copy">1.0s</strong>
</div>
<div class="phrase-gap-track" aria-hidden="true">
<div id="phrase-gap-meter-bar" class="phrase-gap-bar"></div>
</div>
</div>
</div>
<label class="toggle">
<input id="learn-input-toggle" type="checkbox" checked />
<span>Learn the played phrase before generating</span>
</label>
</section>
<section class="panel-section">
<div class="section-head">
<div>
<h3>Phrase Flow</h3>
<p class="section-copy">
Send, replay, or keep chaining continuations from the
current phrase.
</p>
</div>
</div>
<div class="button-row">
<button id="send-phrase-button">Send Phrase</button>
<button id="generate-memory-button" class="ghost">
Generate From Memory
</button>
<button id="replay-generated-button" class="ghost">Replay Output</button>
<button id="clear-phrase-button" class="ghost">Clear</button>
</div>
<div class="generation-grid">
<div class="field generation-field">
<label for="continuation-length-input">Continuation length (notes)</label>
<input
id="continuation-length-input"
type="number"
min="1"
step="1"
inputmode="numeric"
placeholder="Same as input"
/>
<p class="field-hint">
Leave empty to match the captured phrase note count.
</p>
</div>
<div class="field generation-field">
<label>Infinite mode</label>
<div class="infinite-transport">
<div class="transport-copy">
<strong id="infinite-state-label">Idle</strong>
<span id="infinite-seed-label">Seed: play or preview a phrase</span>
</div>
<div class="transport-actions">
<button id="start-infinite-button" class="ghost transport-button" type="button">
Play
</button>
<button
id="stop-infinite-button"
class="ghost danger-button transport-button"
type="button"
disabled
>
Stop
</button>
</div>
</div>
</div>
</div>
</section>
<section class="panel-section accent-section">
<div class="section-head">
<div>
<h3>Output</h3>
</div>
</div>
<div class="route-picker">
<label class="route-picker-label" for="midi-output-select">Renderer</label>
<select id="midi-output-select" disabled>
<option>Browser Triangle</option>
</select>
</div>
<div class="route-picker">
<label class="route-picker-label" for="audio-output-select">Sound Output</label>
<select id="audio-output-select" disabled>
<option>System default</option>
</select>
</div>
<div class="playback-status-row">
<span class="playback-status-chip">
<span>Health</span>
<strong id="renderer-health">Browser renderer ready</strong>
</span>
</div>
<div class="sound-actions">
<button id="test-note-button" class="ghost" type="button">
Test Note
</button>
<button id="panic-button" class="ghost danger-button" type="button">
Panic
</button>
</div>
<p class="field-hint compact-hint">
External MIDI appears after MIDI is connected.
</p>
</section>
<section id="faust-renderer-panel" class="panel-section accent-section" hidden>
<div class="section-head">
<div>
<h3>Instrument Settings</h3>
</div>
<div id="faust-renderer-status" class="status-pill">Idle</div>
</div>
<div id="faust-clavier-panel">
<div class="faust-control-grid">
<div class="field faust-field">
<div class="faust-label-row">
<label for="faust-brightness-input">Brightness</label>
<output id="faust-brightness-value" for="faust-brightness-input">0.58</output>
</div>
<input
id="faust-brightness-input"
type="range"
min="0"
max="1"
step="0.01"
value="0.58"
/>
</div>
<div class="field faust-field">
<div class="faust-label-row">
<label for="faust-hardness-input">Hardness</label>
<output id="faust-hardness-value" for="faust-hardness-input">0.34</output>
</div>
<input
id="faust-hardness-input"
type="range"
min="0"
max="1"
step="0.01"
value="0.34"
/>
</div>
<div class="field faust-field">
<div class="faust-label-row">
<label for="faust-damping-input">Damping</label>
<output id="faust-damping-value" for="faust-damping-input">0.42</output>
</div>
<input
id="faust-damping-input"
type="range"
min="0"
max="1"
step="0.01"
value="0.42"
/>
</div>
<div class="field faust-field">
<div class="faust-label-row">
<label for="faust-release-input">Release</label>
<output id="faust-release-value" for="faust-release-input">0.95 s</output>
</div>
<input
id="faust-release-input"
type="range"
min="0.08"
max="4"
step="0.01"
value="0.95"
/>
</div>
<div class="field faust-field">
<div class="faust-label-row">
<label for="faust-body-input">Body</label>
<output id="faust-body-value" for="faust-body-input">0.33</output>
</div>
<input
id="faust-body-input"
type="range"
min="0"
max="1"
step="0.01"
value="0.33"
/>
</div>
<div class="field faust-field">
<div class="faust-label-row">
<label for="faust-stereo-input">Stereo</label>
<output id="faust-stereo-value" for="faust-stereo-input">0.45</output>
</div>
<input
id="faust-stereo-input"
type="range"
min="0"
max="1"
step="0.01"
value="0.45"
/>
</div>
</div>
</div>
<div id="faust-custom-panel" hidden>
<div class="section-head faust-section-head">
<div>
<h4>Custom Faust</h4>
<p class="section-copy">
Paste a polyphonic Faust DSP with hidden
<code>freq</code>, <code>gain</code>, and
<code>gate</code> controls, then compile it locally.
</p>
</div>
<div class="button-row faust-editor-actions">
<button id="faust-custom-compile-button" type="button">
Compile &amp; Use
</button>
<button
id="faust-custom-reset-button"
class="ghost"
type="button"
>
Restore Starter
</button>
</div>
</div>
<div class="field faust-editor-field">
<label for="faust-custom-code-input">DSP Source</label>
<textarea
id="faust-custom-code-input"
class="faust-code-input"
spellcheck="false"
autocapitalize="off"
autocomplete="off"
></textarea>
<p id="faust-custom-dirty-state" class="field-hint">
Edits stay local to this browser. Compile to turn the
latest source into the active renderer.
</p>
</div>
<div class="section-head faust-section-head">
<div>
<h4>Custom Parameters</h4>
<p class="section-copy">
Visible Faust controls appear here automatically after a
successful compile.
</p>
</div>
</div>
<div id="faust-custom-controls" class="faust-control-grid"></div>
<p id="faust-custom-controls-hint" class="field-hint">
Compile a polyphonic instrument to expose its parameters.
</p>
</div>
<p id="faust-renderer-hint" class="field-hint">
Faust instruments are compiled locally in your browser the
first time you use them.
</p>
</section>
</div>
</section>
<section
id="control-memory-panel"
class="workspace-panel"
data-control-panel="memory"
role="tabpanel"
hidden
>
<div class="panel-stack">
<section class="panel-section accent-section">
<div class="section-head">
<div>
<h3>Corpus Import</h3>
<p class="section-copy">
Load existing MIDI material into the current session so it
becomes part of the live Continuator memory.
</p>
</div>
</div>
<div class="button-row">
<button
id="import-midi-files-button"
class="ghost"
type="button"
>
Import MIDI Files
</button>
<button
id="import-midi-folder-button"
class="ghost"
type="button"
>
Import MIDI Folder
</button>
</div>
<p class="field-hint">
Imported phrases are learned into the active session memory
and logged like other learned inputs.
</p>
<input
id="midi-import-input"
type="file"
accept=".mid,.midi,audio/midi"
multiple
hidden
/>
<input
id="midi-folder-import-input"
type="file"
accept=".mid,.midi,audio/midi"
multiple
webkitdirectory
directory
hidden
/>
</section>
<div class="panel-grid">
<section class="panel-section">
<div class="section-head">
<div>
<h3>Style Memory</h3>
<p class="section-copy">
Listen to what the Continuator currently knows as its
musical vocabulary.
</p>
</div>
<button
id="show-graph-button"
class="ghost head-action"
type="button"
aria-haspopup="dialog"
aria-expanded="false"
aria-controls="graph-panel"
disabled
>
Show Memory Graph
</button>
</div>
<div id="memory-summary" class="settings-summary">
<span class="settings-chip">No active memory yet</span>
</div>
<p id="memory-hint" class="small-copy memory-hint">
Create a session and play a phrase to inspect the active Continuator memory.
</p>
<div id="memory-ribbon" class="memory-ribbon"></div>
<div id="memory-list" class="history-list memory-list">
<p class="muted">No active memory to show yet.</p>
</div>
</section>
<section class="panel-section">
<div class="section-head">
<div>
<h3>Phrase History</h3>
<p class="section-copy">
Follow the phrases that were played, learned, and generated in this session.
</p>
</div>
</div>
<div id="history-list" class="history-list">
<p class="muted">Create a session to start building history.</p>
</div>
</section>
</div>
</div>
</section>
<section
id="control-session-panel"
class="workspace-panel"
data-control-panel="session"
role="tabpanel"
hidden
>
<div class="panel-stack">
<section class="panel-section">
<div class="section-head">
<div>
<h3>Current Session</h3>
</div>
</div>
<p id="session-save-note" class="session-save-note">
Guest session: sign in before creating a new session if you want it saved.
</p>
<div class="button-row">
<button id="create-session-button">Create Session</button>
<button id="reset-session-button" class="ghost">Reset Memory</button>
<button id="save-session-midi-button" class="ghost">
Save Session MIDI
</button>
<label class="toggle compact-toggle">
<input id="remember-midi-export-file-toggle" type="checkbox" />
<span>Don't ask again</span>
</label>
</div>
<p
id="session-midi-save-result"
class="session-midi-save-result"
hidden
></p>
<div class="settings-compact">
<div class="settings-label-row">
<span class="small-copy">Current settings</span>
</div>
<div id="settings-summary" class="settings-summary"></div>
</div>
<details class="settings-panel">
<summary>Engine and Memory Settings</summary>
<div class="settings-grid">
<div class="field">
<label for="engine-kind-select">Engine</label>
<select id="engine-kind-select">
<option value="classic">Classic Continuator</option>
<option value="context_bp">ContextBPContinuator</option>
<option value="vo_regular_bp">VO Regular BP</option>
</select>
</div>
<label class="toggle settings-toggle">
<input id="transpose-toggle" type="checkbox" />
<span>Transpose incoming phrases before learning</span>
</label>
<div class="field">
<label for="markov-order-input">Markov order K</label>
<input
id="markov-order-input"
type="number"
min="1"
max="16"
step="1"
value="4"
/>
<p class="field-hint">
Higher K follows longer contexts, but can hit dead ends
sooner.
</p>
</div>
<div class="field">
<label for="decay-mode-select">Decay mode</label>
<select id="decay-mode-select">
<option value="full">Full</option>
<option value="late">Late</option>
<option value="middle">Middle</option>
<option value="early">Early</option>
</select>
</div>
<label class="toggle settings-toggle">
<input id="forget-toggle" type="checkbox" />
<span>Forget old phrases and keep a rolling memory</span>
</label>
<div class="field">
<label for="keep-last-input">Keep only N last inputs</label>
<input
id="keep-last-input"
type="number"
min="1"
max="500"
step="1"
value="20"
/>
<p class="field-hint">
This cap is used when forgetting is enabled.
</p>
</div>
</div>
<div class="button-row">
<button id="apply-settings-button" class="ghost">
Apply to Current Session
</button>
</div>
<p class="small-copy settings-copy">
New sessions start with these settings. Apply Settings
updates the running session without clearing memory.
</p>
</details>
<dl class="info-grid">
<div>
<dt>Session ID</dt>
<dd id="session-id">Not created yet</dd>
</div>
<div>
<dt>Server</dt>
<dd id="server-status">Checking…</dd>
</div>
<div>
<dt>Continuator</dt>
<dd id="continuator-version">Checking…</dd>
</div>
</dl>
</section>
<section class="panel-section">
<div class="section-head">
<div>
<h3>Saved Sessions</h3>
<p id="saved-sessions-copy" class="section-copy">
Sign in to reopen sessions saved under your account.
</p>
</div>
<button
id="refresh-sessions-button"
class="ghost head-action"
type="button"
>
Refresh
</button>
</div>
<div id="my-sessions-list" class="history-list">
<p class="muted">Sign in to see your saved sessions.</p>
</div>
<div id="saved-session-preview" class="session-preview-panel" hidden>
<div class="session-preview-head">
<div>
<span class="small-copy">Selected saved session</span>
<strong id="saved-session-preview-name">No session selected</strong>
</div>
<button
id="saved-session-preview-open-button"
class="ghost session-open-button"
type="button"
>
Open
</button>
</div>
<div class="session-name-editor">
<label for="saved-session-name-input">Name</label>
<div class="session-name-row">
<input
id="saved-session-name-input"
type="text"
maxlength="80"
autocomplete="off"
/>
<button id="rename-session-button" class="ghost" type="button">
Rename
</button>
</div>
</div>
<dl class="session-preview-grid">
<div>
<dt>Memory</dt>
<dd id="saved-session-preview-memory">-</dd>
</div>
<div>
<dt>History</dt>
<dd id="saved-session-preview-history">-</dd>
</div>
<div>
<dt>Settings</dt>
<dd id="saved-session-preview-settings">-</dd>
</div>
<div>
<dt>Last Seen</dt>
<dd id="saved-session-preview-seen">-</dd>
</div>
</dl>
</div>
</section>
</div>
</section>
</article>
</section>
<section class="column wide-column">
<article class="card timeline-card">
<div class="card-head">
<h2>Phrase Timeline</h2>
</div>
<div id="phrase-timeline" class="phrase-timeline">
<div id="timeline-captured" class="timeline-segment">
<span>Captured</span>
<strong>empty</strong>
</div>
<div id="timeline-generated" class="timeline-segment">
<span>Current Reply</span>
<strong>empty</strong>
</div>
<div id="timeline-queued" class="timeline-segment">
<span>Next Queued</span>
<strong>none</strong>
</div>
</div>
</article>
<article class="card roll-card">
<div class="card-head">
<h2>Captured Phrase</h2>
<span class="small-copy">Detected from browser MIDI events</span>
</div>
<canvas
id="input-roll"
class="piano-roll"
role="button"
tabindex="0"
aria-label="Captured phrase piano roll. Click a note to audition it, or click empty space to play the phrase."
></canvas>
</article>
<article class="card roll-card output-card">
<div class="card-head">
<h2>Generated Continuation</h2>
<span class="small-copy">Returned by Continuator</span>
</div>
<div class="roll-viewport">
<canvas
id="output-roll"
class="piano-roll"
role="button"
tabindex="0"
aria-label="Generated continuation piano roll. Click a note to audition it, or click empty space to play the continuation."
></canvas>
<div id="output-playhead" class="roll-playhead" hidden></div>
</div>
</article>
<article class="card">
<div class="card-head">
<h2>Live Snapshot</h2>
<span class="small-copy">Always-visible capture and generation feedback</span>
</div>
<dl class="info-grid">
<div>
<dt>Captured Events</dt>
<dd id="captured-event-count">0</dd>
</div>
<div>
<dt>Captured Notes</dt>
<dd id="captured-note-count">0</dd>
</div>
<div>
<dt>Generated Events</dt>
<dd id="generated-event-count">0</dd>
</div>
<div>
<dt>Generated Notes</dt>
<dd id="generated-note-count">0</dd>
</div>
</dl>
<div class="message-box" id="message-box">
Play a short phrase on the selected MIDI input to capture it.
</div>
<div class="constraint-status" id="constraint-status" hidden></div>
</article>
</section>
</main>
<script type="module" src="/assets/app.js?v=20260602-session-midi-zip3"></script>
</body>
</html>