2<html lang="en">
3 <head>
4 <meta charset="utf-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1" />
6 <title>instrawave-compose</title>
7 <style>
8 :root {
9 color-scheme: light dark;
10 font-family: system-ui, sans-serif;
11 }
12 body {
13 margin: 0 auto;
14 max-width: 72rem;
15 padding: 1rem 1.5rem 4rem;
16 line-height: 1.45;
17 }
18 h1 { font-size: 1.3rem; margin: 0.3rem 0 0; }
19 h1 + p { margin-top: 0.2rem; opacity: 0.75; }
20 h2 { font-size: 0.95rem; margin: 1.4rem 0 0.4rem; }
21 .cost { font-weight: normal; opacity: 0.6; font-size: 0.8rem; }
22 .bar { display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap; margin: 0.5rem 0; }
23 button { font: inherit; padding: 0.3rem 0.8rem; }
24 button:disabled { opacity: 0.5; }
25 select, input[type='text'] { font: inherit; }
26 input[type='range'] { width: 9rem; }
27 .voice-row {
28 display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap;
29 padding: 0.3rem 0.4rem; border-radius: 6px; font-size: 0.9rem;
30 }
31 .voice-row:nth-child(odd) { background: rgba(128, 128, 128, 0.09); }
32 .voice-row .swatch { width: 0.8rem; height: 0.8rem; border-radius: 3px; flex: none; }
33 .voice-row input.name { width: 8rem; border: none; background: transparent; font-weight: 600; }
34 .voice-row .count { opacity: 0.65; font-size: 0.8rem; min-width: 7.5rem; }
35 .tuning-panel {
36 display: grid; grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
37 gap: 0.2rem 1.5rem; padding: 0.3rem 0.6rem 0.5rem 1.8rem; font-size: 0.82rem;
38 }
39 .tuning-panel .param { display: grid; }
40 .tuning-panel label { display: flex; justify-content: space-between; }
41 .tuning-panel label span:last-child { opacity: 0.7; font-variant-numeric: tabular-nums; }
42 .tuning-panel input[type='range'] { width: 100%; }
43 .voice-row progress { width: 6rem; }
44 #iwedit { border: 1px solid rgba(128,128,128,0.35); border-radius: 8px; padding: 0.6rem 0.8rem; margin: 0.5rem 0; }
45 .iwedit-grid { display: grid; grid-template-columns: minmax(0, 1fr) 24rem; gap: 0 1.5rem; align-items: start; }
46 @media (max-width: 56rem) { .iwedit-grid { grid-template-columns: 1fr; } }
47 .params { display: grid; grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); gap: 0.3rem 1.5rem; }
48 .params .param { display: grid; font-size: 0.82rem; }
49 .params label { display: flex; justify-content: space-between; }
50 .params label span:last-child { opacity: 0.7; font-variant-numeric: tabular-nums; }
51 .params input[type='range'] { width: 100%; }
52 .scene { position: relative; width: 24rem; max-width: 100%; aspect-ratio: 4 / 3; }
53 .scene canvas { position: absolute; inset: 0; width: 100%; height: 100%; border-radius: 6px; }
54 .scene .scene-hint { position: absolute; right: 0.5rem; bottom: 0.3rem; font-size: 0.7rem; color: rgba(255,255,255,0.45); }
55 .voice-row .spacer { flex: 1; }
56 .rec { color: #d33; }
57 canvas.wave { width: 100%; height: 110px; border-radius: 6px; display: block; touch-action: none; }
58 canvas.tl { width: 100%; border-radius: 6px; display: block; touch-action: none; }
59 .meter { width: 7rem; height: 0.55rem; border-radius: 3px; background: rgba(128,128,128,0.25); overflow: hidden; }
60 .meter div { height: 100%; width: 0; background: #3dbb6e; }
61 #capture { border: 1px solid rgba(128,128,128,0.35); border-radius: 8px; padding: 0.6rem 0.8rem; margin-top: 0.8rem; }
62 #status { font-size: 0.85rem; opacity: 0.8; min-height: 1.3em; }
63 .hint { font-size: 0.8rem; opacity: 0.6; }
64 label { display: inline-flex; gap: 0.35rem; align-items: center; }
65 </style>
66 </head>
67 <body>
68 <h1>instrawave-compose</h1>
69 <p>
70 Teach it your sounds as named voices, then perform a rhythm in one take;
71 hits are classified back onto the voices and rendered on instruments.
72 </p>
74 <h2>Voices <span class="cost">record several examples of each sound; recordings stay editable</span></h2>
75 <div id="voice-list"></div>
76 <div class="bar">
77 <input id="voice-name" type="text" placeholder="new voice name (e.g. doo)" />
78 <button id="add-voice">Add voice</button>
79 <span class="spacer" style="flex:1"></span>
80 <button id="reset-all">Forget everything</button>
81 </div>
83 <div id="capture" hidden>
84 <div class="bar">
85 <strong id="capture-title"></strong>
86 <select id="capture-clip"></select>
87 <span class="hint">click a hit to audition · drag or shift-click to select several · X toggles exclusion</span>
88 </div>
89 <canvas id="capture-wave" class="wave"></canvas>
90 <div class="bar">
91 <label>sensitivity <input id="capture-sens" type="range" min="0" max="1" step="0.01" value="0.6" /></label>
92 <button id="capture-exclude" disabled>Exclude</button>
93 <span id="capture-count" class="hint"></span>
94 <span style="flex:1"></span>
95 <button id="capture-delete">Delete clip</button>
96 <button id="capture-done">Done</button>
97 </div>
98 </div>
100 <h2>Take <span class="cost">one pass, exactly as performed; no grid</span></h2>
101 <div class="bar">
102 <button id="take-record">● Record take</button>
103 <div class="meter" id="meter" hidden><div></div></div>
104 <label>sensitivity <input id="take-sens" type="range" min="0" max="1" step="0.01" value="0.6" /></label>
105 <button id="take-redetect" disabled>Re-detect & classify</button>
106 <span style="flex:1"></span>
107 <button id="take-play" disabled>Play</button>
108 <button id="take-save" disabled>Save .wav</button>
109 </div>
110 <div id="iwedit" hidden>
111 <div class="bar">
112 <strong id="iwedit-title"></strong>
113 <span class="hint">geometry and accuracy feed the bake; changing one makes it stale · shared by every voice bound to this instrument</span>
114 <span style="flex:1"></span>
115 <button id="iwedit-done">Done</button>
116 </div>
117 <div class="iwedit-grid">
118 <div class="params" id="iwedit-params"></div>
119 <div class="scene">
120 <canvas id="iw-volume"></canvas>
121 <canvas id="iw-overlay"></canvas>
122 <span class="hint scene-hint">drag to orbit · wheel to zoom</span>
123 </div>
124 </div>
125 </div>
126 <canvas id="timeline" class="tl"></canvas>
127 <div class="bar">
128 <span class="hint">drag a hit to another lane to reassign · double-click a lane to add a hit · delete key removes</span>
129 <span style="flex:1"></span>
130 <button id="hit-learn" disabled>Learn selected into its voice</button>
131 <button id="hit-delete" disabled>Delete selected</button>
132 </div>
133 <p id="status"></p>
134 <script type="module" src="./src/main.ts"></script>
135 </body>
136</html>