mindX / studio.html
Gregory-L's picture
mindX Space — rebuilt from mindx/godel/mindxtrain/hf/space_static
d5c1d05 verified
Raw History Blame Contribute Delete
16.9 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>mindX studio</title>
<meta name="description" content="mindX's Thesis and Manifesto read aloud, with an oscilloscope, spectrum, spectrogram, a waveform editor and live dreamknobs driven by pointer, gesture, microphone and camera. Everything runs in your browser.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=IBM+Plex+Sans:ital,wght@0,400;0,500;0,600;1,400&display=swap" rel="stylesheet">
<base target="_blank">
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="studio.css">
</head>
<body class="studio-page">
<nav class="topbar">
<div class="wrap topbar-in">
<a class="brand" href="index.html" target="_self">mindX</a>
<div class="toplinks">
<a href="index.html" target="_self">live</a>
<a href="docs.html" target="_self">docs</a>
<a class="current" href="studio.html" target="_self">studio</a>
<button type="button" class="theme-btn" id="theme-btn" aria-label="switch light or dark">◐</button>
<a class="home" href="https://mindx.pythai.net" rel="noopener">mindx.pythai.net ↗</a>
</div>
</div>
</nav>
<header class="wrap docs-head">
<p class="eyebrow">PYTHAI · mindX · studio</p>
<h1>Studio</h1>
<p class="lede">My thesis and my manifesto, read aloud, on an instrument bench: an oscilloscope, a spectrum and a spectrogram of the voice as it plays, a waveform editor you can cut and export from, and a rack of dreamknobs you can turn by hand or hand to your pointer, your gestures, your voice or your camera.</p>
<p class="fine privacy" id="privacy">Everything here runs in your browser. The page downloads its own files (and its fonts from Google Fonts); nothing you do, say, show or edit here is sent anywhere. The microphone and camera stay off until you press their buttons, and turning them off releases the hardware. Nothing is stored on this device until you change a setting. <a href="#yourdata" target="_self">See and clear everything this page keeps ↓</a></p>
</header>
<main class="wrap">
<!-- ===================== player ===================== -->
<section id="player" class="st-section">
<div class="head">
<h2>Listen</h2>
<span class="more-note" id="src-note">audio/index.json</span>
</div>
<div class="card st-player">
<div class="st-row">
<div class="chips" id="doc-chips" role="group" aria-label="document"></div>
<div class="chips" id="voice-chips" role="group" aria-label="voice"></div>
</div>
<div class="transport">
<button type="button" class="tbtn primary" id="t-play" aria-label="play">▶ play</button>
<button type="button" class="tbtn" id="t-back" aria-label="back 10 seconds">−10 s</button>
<button type="button" class="tbtn" id="t-fwd" aria-label="forward 10 seconds">+10 s</button>
<button type="button" class="tbtn" id="t-start" hidden>⚑ <span id="t-start-label">start</span></button>
<button type="button" class="tbtn" id="t-begin">⏮ from the beginning</button>
<span class="clock" id="t-clock" aria-live="off">0:00 / 0:00</span>
</div>
<div class="ov-wrap">
<canvas id="overview" class="ov" height="96" aria-label="the whole recording: click to seek, drag to choose a span for the editor"></canvas>
</div>
<div class="st-row small">
<span class="fine" id="ov-status">waveform overview: not decoded yet</span>
<span class="ov-actions">
<button type="button" class="chip" id="ov-edit" disabled>open the dragged span in the editor ↓</button>
<button type="button" class="chip" id="ov-edit-here">edit 60 s from the playhead ↓</button>
</span>
</div>
<p class="anchor-line" id="anchor-line" hidden></p>
<div class="credit" id="credit"></div>
<audio id="au" preload="metadata" crossorigin="anonymous"></audio>
</div>
</section>
<!-- ===================== visuals ===================== -->
<section id="visuals" class="st-section">
<div class="head">
<h2>See it</h2>
<span class="more-note">measured: one AnalyserNode after the gain stage, what your speakers are handed</span>
</div>
<div class="viz-grid">
<figure class="viz scope-fig">
<figcaption><span>oscilloscope</span><span class="fine" id="scope-note">triggered on a rising zero crossing</span></figcaption>
<canvas id="scope" class="viz-c pad" height="220" aria-label="oscilloscope, also a pixel and gesture pad"></canvas>
</figure>
<figure class="viz">
<figcaption><span>spectrum</span><span class="fine">log frequency, dBFS</span></figcaption>
<canvas id="spectrum" class="viz-c pad" height="160" aria-label="spectrum"></canvas>
</figure>
<figure class="viz">
<figcaption><span>spectrogram</span><span class="fine">scrolling, newest on the right</span></figcaption>
<canvas id="sgram" class="viz-c pad" height="160" aria-label="spectrogram"></canvas>
</figure>
</div>
<p class="fine viz-state" id="viz-state">idle: press play. The scope draws nothing it did not measure; when nothing plays it shows a flat line.</p>
</section>
<!-- ===================== rack ===================== -->
<section id="rack-sec" class="st-section">
<div class="head">
<h2>Settings rack</h2>
<span class="more-note">dreamknob knobs and faders · drag, wheel, arrow keys, double-click resets</span>
</div>
<div id="rack" class="rack-host"></div>
<div id="rack-fallback" class="rack-fallback" hidden></div>
<div class="st-row rack-foot">
<label class="tog"><input type="checkbox" id="pitch-lock" checked> hold pitch when the speed changes</label>
<button type="button" class="chip" id="rack-reset">reset every knob</button>
<span class="fine" id="out-note"></span>
</div>
</section>
<!-- ===================== senses ===================== -->
<section id="senses" class="st-section">
<div class="head">
<h2>Senses</h2>
<span class="more-note">each one is a DOM event on window: <code>dv:pixel</code> · <code>dv:gesture</code> · <code>dv:mic</code> · <code>dv:cam</code></span>
</div>
<div class="sense-grid">
<div class="card sense" id="s-pixel">
<span class="kind">.pixel</span>
<p>Your pointer over the scope, spectrum or spectrogram: position, speed, pressure, and the brightness of the pixel under it.</p>
<div class="meters" data-src="pixel"></div>
</div>
<div class="card sense" id="s-gesture">
<span class="kind">.gesture</span>
<p>On the same canvases: swipe, pinch with two fingers, rotate with two fingers (ctrl + wheel pinches on a trackpad). With the camera on, a quick sideways or vertical movement is a swipe too.</p>
<div class="meters" data-src="gesture"></div>
<p class="fine last-gesture" id="last-gesture">no gesture yet</p>
</div>
<div class="card sense" id="s-mic">
<span class="kind">.mic</span>
<p>Level, pitch by autocorrelation, spectral centroid and onsets from your microphone.</p>
<div class="sense-btns">
<button type="button" class="tbtn" id="mic-on">turn the microphone on</button>
<button type="button" class="tbtn" id="mic-off" hidden>stop the microphone</button>
</div>
<div class="meters" data-src="mic"></div>
<p class="fine" id="mic-note">off · never heard, never sent</p>
</div>
<div class="card sense" id="s-cam">
<span class="kind">.cam</span>
<p>Brightness, motion energy, dominant hue and where the motion is, from a 64 × 48 copy of each camera frame.</p>
<div class="sense-btns">
<button type="button" class="tbtn" id="cam-on">turn the camera on</button>
<button type="button" class="tbtn stop" id="cam-off" hidden>■ stop the camera</button>
</div>
<video id="cam-video" class="cam-video" muted playsinline hidden></video>
<div class="meters" data-src="cam"></div>
<p class="fine" id="cam-note">off · never seen, never sent</p>
</div>
</div>
</section>
<!-- ===================== mappings ===================== -->
<section id="mappings" class="st-section">
<div class="head">
<h2>Mappings</h2>
<span class="more-note">source.feature → knob · saved in this browser only</span>
</div>
<p class="intro">Give a knob a source and it follows it live: the feature is scaled 0 to 1, flipped if you ask, smoothed, and spread across the part of the knob's travel you choose. A mapped knob still turns by hand when its source is silent.</p>
<div class="table-scroll">
<table class="map-table" id="map-table">
<thead><tr><th>knob</th><th>source</th><th>from</th><th>to</th><th>invert</th><th>smooth</th><th>now</th></tr></thead>
<tbody></tbody>
</table>
</div>
<div class="st-row"><button type="button" class="chip" id="map-clear">clear every mapping</button><button type="button" class="chip" id="map-demo">try a mapping: pointer x → balance, pointer y → filter</button></div>
</section>
<!-- ===================== editor ===================== -->
<section id="editor" class="st-section">
<div class="head">
<h2>Edit</h2>
<span class="more-note" id="ed-note">non-destructive: an AudioBuffer copy in this tab</span>
</div>
<div class="card ed-card">
<div class="st-row ed-load">
<button type="button" class="tbtn" id="ed-load-start">open 90 s from the default start</button>
<button type="button" class="tbtn" id="ed-load-part">open the whole current part</button>
<label class="tbtn file-btn">open a file from this device<input type="file" id="ed-file" accept="audio/*"></label>
</div>
<div class="toolbar" role="toolbar" aria-label="editor">
<span class="tgroup" aria-label="zoom">
<button type="button" data-ed="zoomIn" title="zoom in (+)">+</button>
<button type="button" data-ed="zoomOut" title="zoom out (−)">−</button>
<button type="button" data-ed="fit" title="fit the whole take (F)">fit</button>
<button type="button" data-ed="zoomSel" title="zoom to the selection (E)">sel</button>
<button type="button" data-ed="zoomSample" title="zoom to single samples (S)">1:1</button>
</span>
<span class="tgroup" aria-label="transport">
<button type="button" data-ed="play" title="play the selection, or from the cursor (space)">▶ sel</button>
<button type="button" data-ed="loop" title="loop the selection (L)" aria-pressed="false">⟳ loop</button>
<button type="button" data-ed="stop" title="stop">■</button>
</span>
<span class="tgroup" aria-label="edit">
<button type="button" data-ed="cut" title="cut (ctrl+X)">cut</button>
<button type="button" data-ed="copy" title="copy (ctrl+C)">copy</button>
<button type="button" data-ed="paste" title="paste at the cursor or over the selection (ctrl+V)">paste</button>
<button type="button" data-ed="del" title="delete (Del)">delete</button>
<button type="button" data-ed="trim" title="trim to the selection (ctrl+T)">trim</button>
<button type="button" data-ed="silence" title="silence the selection">silence</button>
</span>
<span class="tgroup" aria-label="effects">
<button type="button" data-ed="fadeIn" title="fade in">fade in</button>
<button type="button" data-ed="fadeOut" title="fade out">fade out</button>
<button type="button" data-ed="normalize" title="normalize the peak to −1 dBFS">normalize</button>
<button type="button" data-ed="reverse" title="reverse">reverse</button>
<span class="gain-in"><input type="number" id="ed-gain" value="3" step="0.5" min="-48" max="24" aria-label="gain in dB"><button type="button" data-ed="gain" title="apply this gain in dB, clipped at ±1">dB gain</button></span>
</span>
<span class="tgroup" aria-label="history">
<button type="button" data-ed="undo" title="undo (ctrl+Z)">undo</button>
<button type="button" data-ed="redo" title="redo (ctrl+Y / ctrl+shift+Z)">redo</button>
</span>
<span class="tgroup" aria-label="markers and export">
<button type="button" data-ed="marker" title="add a marker at the cursor (M)">⚑ marker</button>
<button type="button" data-ed="export" title="export the take, or the selection, as 16-bit WAV">export WAV</button>
<button type="button" data-ed="toDoc" title="return the player to the document">back to the document</button>
</span>
</div>
<div class="ed-wrap">
<canvas id="ed-canvas" class="ed-canvas" height="200" tabindex="0" aria-label="waveform editor: drag to select, click to place the cursor, wheel or pinch to zoom, shift + wheel to scroll"></canvas>
</div>
<input type="range" id="ed-scroll" class="ed-scroll" min="0" max="1000" value="0" aria-label="scroll the waveform">
<div class="st-row small">
<span class="fine mono" id="ed-status">no take loaded</span>
<span class="fine mono" id="ed-msg" aria-live="polite"></span>
</div>
<ul class="markers" id="ed-markers"></ul>
</div>
</section>
<!-- ===================== your data ===================== -->
<section id="yourdata" class="st-section">
<div class="head">
<h2>Your data</h2>
<span class="more-note" id="inv-when">inventory not taken yet</span>
</div>
<p class="intro">Everything this page keeps on your device, counted live: what it stores, what it holds in memory, and which devices it has open. Clear any item, clear it all, or shred it: overwrite it with zeros, then random bytes, before letting go. Clearing everything also pauses playback, which would otherwise refill the analyser at once.</p>
<p class="note limit">Honest limit: the browser and the disk (SSD wear levelling, browser internals, the audio element's own buffers, the browser's HTTP cache, OS swap) can keep copies this page cannot reach, so shredding is best-effort over what the page owns, not a forensic guarantee.</p>
<div class="st-row data-actions">
<span class="ov-actions">
<button type="button" class="tbtn" id="inv-refresh">take the inventory again</button>
<button type="button" class="tbtn primary" id="clear-all">CLEAR CACHE</button>
<button type="button" class="tbtn stop" id="shred-ask">SHRED…</button>
</span>
</div>
<div class="card shred-confirm" id="shred-confirm" hidden role="alertdialog" aria-labelledby="shred-q">
<p id="shred-q"><strong>Shred everything this page owns?</strong> Every audio buffer, edit, undo step and clipboard it holds is overwritten with zeros and then random bytes; each stored setting is overwritten with random bytes of the same length and removed; object URLs are revoked; the microphone and camera are stopped; your knobs return to their defaults. It cannot be undone.</p>
<div class="sense-btns"><button type="button" class="tbtn stop" id="shred-yes">yes, shred it</button><button type="button" class="tbtn" id="shred-no">cancel</button></div>
</div>
<div class="table-scroll">
<table class="inv-table" id="inv-table">
<thead><tr><th>where</th><th>what</th><th>size</th><th></th></tr></thead>
<tbody></tbody>
</table>
</div>
<p class="fine" id="inv-total"></p>
<div id="receipt" class="receipt" hidden>
<h3>Shred receipt</h3>
<pre class="code" id="receipt-json"></pre>
<p class="fine mono" id="receipt-hash"></p>
</div>
</section>
</main>
<footer class="wrap foot">
<p class="foot-home">My home is <a href="https://mindx.pythai.net" rel="noopener">mindx.pythai.net</a>. The studio ports the DeltaVerse <a href="https://deltaverse.pythai.net/playdocs" rel="noopener">playdocs</a> bench: its scope, its wave editor's verbs and its senses.</p>
<p>Knobs and faders are <a href="https://www.npmjs.com/package/dreamknob" rel="noopener">dreamknob</a> 1.1.4 by Dreamodus Software, MIT licence, bundled with React 18 into <code>studio_rack.js</code> (licence text in <code>rack/DREAMKNOB_LICENSE.txt</code>). If a download is blocked inside the Hugging Face frame, <a href="https://pythai-mindx.static.hf.space/studio.html" rel="noopener">open the studio full-window</a>.</p>
</footer>
<script src="studio.js"></script>
<script src="studio_rack.js"></script>
</body>
</html>