@typeonce/ritmo-tools 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/README.md +304 -24
- package/dist/cli.js +5 -1
- package/dist/cli.js.map +1 -1
- package/dist/internal/version.d.ts +1 -1
- package/dist/internal/version.js +1 -1
- package/dist/music/cli/commands.d.ts +15 -0
- package/dist/music/cli/commands.d.ts.map +1 -0
- package/dist/music/cli/commands.js +180 -0
- package/dist/music/cli/commands.js.map +1 -0
- package/dist/music/cli/io.d.ts +11 -0
- package/dist/music/cli/io.d.ts.map +1 -0
- package/dist/music/cli/io.js +28 -0
- package/dist/music/cli/io.js.map +1 -0
- package/dist/music/cli/serve.d.ts +13 -0
- package/dist/music/cli/serve.d.ts.map +1 -0
- package/dist/music/cli/serve.js +168 -0
- package/dist/music/cli/serve.js.map +1 -0
- package/dist/music/core/audio.d.ts +22 -0
- package/dist/music/core/audio.d.ts.map +1 -0
- package/dist/music/core/audio.js +95 -0
- package/dist/music/core/audio.js.map +1 -0
- package/dist/music/core/check.d.ts +51 -0
- package/dist/music/core/check.d.ts.map +1 -0
- package/dist/music/core/check.js +113 -0
- package/dist/music/core/check.js.map +1 -0
- package/dist/music/core/instruments.d.ts +179 -0
- package/dist/music/core/instruments.d.ts.map +1 -0
- package/dist/music/core/instruments.js +531 -0
- package/dist/music/core/instruments.js.map +1 -0
- package/dist/music/core/listen.d.ts +32 -0
- package/dist/music/core/listen.d.ts.map +1 -0
- package/dist/music/core/listen.js +350 -0
- package/dist/music/core/listen.js.map +1 -0
- package/dist/music/core/midi.d.ts +4 -0
- package/dist/music/core/midi.d.ts.map +1 -0
- package/dist/music/core/midi.js +76 -0
- package/dist/music/core/midi.js.map +1 -0
- package/dist/music/core/render.d.ts +37 -0
- package/dist/music/core/render.d.ts.map +1 -0
- package/dist/music/core/render.js +204 -0
- package/dist/music/core/render.js.map +1 -0
- package/dist/music/core/review.d.ts +41 -0
- package/dist/music/core/review.d.ts.map +1 -0
- package/dist/music/core/review.js +139 -0
- package/dist/music/core/review.js.map +1 -0
- package/dist/music/core/song.d.ts +144 -0
- package/dist/music/core/song.d.ts.map +1 -0
- package/dist/music/core/song.js +291 -0
- package/dist/music/core/song.js.map +1 -0
- package/dist/music/page/assets/index-BfO_jOqP.js +4 -0
- package/dist/music/page/assets/index-Dw14kxkL.css +1 -0
- package/dist/music/page/index.html +19 -0
- package/dist/pixel/cli/commands.d.ts.map +1 -1
- package/dist/pixel/cli/commands.js +122 -46
- package/dist/pixel/cli/commands.js.map +1 -1
- package/dist/pixel/cli/io.d.ts +5 -1
- package/dist/pixel/cli/io.d.ts.map +1 -1
- package/dist/pixel/cli/io.js +16 -1
- package/dist/pixel/cli/io.js.map +1 -1
- package/dist/pixel/core/animation.d.ts +23 -8
- package/dist/pixel/core/animation.d.ts.map +1 -1
- package/dist/pixel/core/animation.js +41 -18
- package/dist/pixel/core/animation.js.map +1 -1
- package/dist/pixel/core/grid.d.ts +3 -2
- package/dist/pixel/core/grid.d.ts.map +1 -1
- package/dist/pixel/core/grid.js +14 -6
- package/dist/pixel/core/grid.js.map +1 -1
- package/dist/pixel/core/preview.d.ts +8 -3
- package/dist/pixel/core/preview.d.ts.map +1 -1
- package/dist/pixel/core/preview.js +39 -25
- package/dist/pixel/core/preview.js.map +1 -1
- package/dist/pixel/core/project.d.ts +27 -0
- package/dist/pixel/core/project.d.ts.map +1 -0
- package/dist/pixel/core/project.js +117 -0
- package/dist/pixel/core/project.js.map +1 -0
- package/dist/pixel/core/recipe.d.ts.map +1 -1
- package/dist/pixel/core/recipe.js +43 -6
- package/dist/pixel/core/recipe.js.map +1 -1
- package/dist/pixel/core/report.d.ts +21 -3
- package/dist/pixel/core/report.d.ts.map +1 -1
- package/dist/pixel/core/report.js +52 -22
- package/dist/pixel/core/report.js.map +1 -1
- package/dist/pixel/core/sheet.d.ts +68 -8
- package/dist/pixel/core/sheet.d.ts.map +1 -1
- package/dist/pixel/core/sheet.js +250 -32
- package/dist/pixel/core/sheet.js.map +1 -1
- package/dist/pixel/core/spritesheet.d.ts +43 -35
- package/dist/pixel/core/spritesheet.d.ts.map +1 -1
- package/dist/pixel/core/spritesheet.js +63 -59
- package/dist/pixel/core/spritesheet.js.map +1 -1
- package/dist/sound/cli/commands.d.ts +25 -0
- package/dist/sound/cli/commands.d.ts.map +1 -0
- package/dist/sound/cli/commands.js +137 -0
- package/dist/sound/cli/commands.js.map +1 -0
- package/dist/sound/cli/io.d.ts +24 -0
- package/dist/sound/cli/io.d.ts.map +1 -0
- package/dist/sound/cli/io.js +51 -0
- package/dist/sound/cli/io.js.map +1 -0
- package/dist/sound/cli/serve.d.ts +11 -0
- package/dist/sound/cli/serve.d.ts.map +1 -0
- package/dist/sound/cli/serve.js +147 -0
- package/dist/sound/cli/serve.js.map +1 -0
- package/dist/sound/core/measure.d.ts +67 -0
- package/dist/sound/core/measure.d.ts.map +1 -0
- package/dist/sound/core/measure.js +138 -0
- package/dist/sound/core/measure.js.map +1 -0
- package/dist/sound/core/recipe.d.ts +143 -0
- package/dist/sound/core/recipe.d.ts.map +1 -0
- package/dist/sound/core/recipe.js +292 -0
- package/dist/sound/core/recipe.js.map +1 -0
- package/dist/sound/core/render.d.ts +37 -0
- package/dist/sound/core/render.d.ts.map +1 -0
- package/dist/sound/core/render.js +317 -0
- package/dist/sound/core/render.js.map +1 -0
- package/dist/sound/core/review.d.ts +52 -0
- package/dist/sound/core/review.d.ts.map +1 -0
- package/dist/sound/core/review.js +89 -0
- package/dist/sound/core/review.js.map +1 -0
- package/dist/sound/core/takes.d.ts +29 -0
- package/dist/sound/core/takes.d.ts.map +1 -0
- package/dist/sound/core/takes.js +109 -0
- package/dist/sound/core/takes.js.map +1 -0
- package/dist/sound/core/wav.d.ts +4 -0
- package/dist/sound/core/wav.d.ts.map +1 -0
- package/dist/sound/core/wav.js +28 -0
- package/dist/sound/core/wav.js.map +1 -0
- package/dist/sound/page/assets/index-Cjun5b92.js +6 -0
- package/dist/sound/page/assets/index-DZm59wkQ.css +1 -0
- package/dist/sound/page/index.html +19 -0
- package/package.json +10 -4
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Takes: other versions of a sound for a person to choose from. The agent
|
|
3
|
+
* (or the page) makes a few at once, each nudged toward what was asked
|
|
4
|
+
* ("heavier", "brighter") and a little different from the others, and the
|
|
5
|
+
* person picks the one that sounds right. Picking is quicker and surer than
|
|
6
|
+
* describing a sound in words.
|
|
7
|
+
*/
|
|
8
|
+
import { applyTune } from "./recipe.js";
|
|
9
|
+
import { seeded } from "./render.js";
|
|
10
|
+
export const towards = [
|
|
11
|
+
{ id: "wider", label: "Anything", hint: "Takes spread around the sound, in no one direction." },
|
|
12
|
+
{ id: "heavier", label: "Heavier", hint: "Lower, longer, with more weight." },
|
|
13
|
+
{ id: "lighter", label: "Lighter", hint: "Higher, shorter, thinner." },
|
|
14
|
+
{ id: "brighter", label: "Brighter", hint: "More highs in the noise; the notes stay." },
|
|
15
|
+
{ id: "darker", label: "Darker", hint: "Fewer highs in the noise; the notes stay." },
|
|
16
|
+
{ id: "punchier", label: "Punchier", hint: "A sharper start and a quicker end." },
|
|
17
|
+
{ id: "softer", label: "Softer", hint: "A gentler start, rounder, quieter." },
|
|
18
|
+
{ id: "shorter", label: "Shorter", hint: "Everything quicker." },
|
|
19
|
+
{ id: "longer", label: "Longer", hint: "Everything drawn out." }
|
|
20
|
+
];
|
|
21
|
+
const pushes = {
|
|
22
|
+
wider: { pitch: 0, time: 1, gain: 1, filters: 0, attack: 1, reverb: 0 },
|
|
23
|
+
heavier: { pitch: -3, time: 1.25, gain: 1.15, filters: -3, attack: 1, reverb: 0.05 },
|
|
24
|
+
lighter: { pitch: 3, time: 0.8, gain: 0.9, filters: 3, attack: 1, reverb: -0.03 },
|
|
25
|
+
brighter: { pitch: 0, time: 1, gain: 1, filters: 5, attack: 1, reverb: 0 },
|
|
26
|
+
darker: { pitch: 0, time: 1, gain: 1, filters: -5, attack: 1, reverb: 0 },
|
|
27
|
+
punchier: { pitch: 0, time: 0.85, gain: 1.15, filters: 1, attack: 0.4, reverb: -0.04 },
|
|
28
|
+
softer: { pitch: 0, time: 1.1, gain: 0.8, filters: -2, attack: 2.5, reverb: 0.04 },
|
|
29
|
+
shorter: { pitch: 0, time: 0.7, gain: 1, filters: 0, attack: 0.8, reverb: -0.03 },
|
|
30
|
+
longer: { pitch: 0, time: 1.4, gain: 1, filters: 0, attack: 1.2, reverb: 0.04 }
|
|
31
|
+
};
|
|
32
|
+
/** How much the takes differ from each other, around the push: pitch and filters in semitones, time and gain as a share. */
|
|
33
|
+
const SPREAD = { pitch: 1.5, filters: 3, time: 0.15, gain: 0.2 };
|
|
34
|
+
const clampHz = (hz) => Math.min(20000, Math.max(20, hz));
|
|
35
|
+
const describe = (pitch, filters, time, gain) => {
|
|
36
|
+
const parts = [];
|
|
37
|
+
if (Math.abs(pitch) >= 0.3)
|
|
38
|
+
parts.push(`${Math.abs(Math.round(pitch * 10) / 10)} semitones ${pitch < 0 ? "lower" : "higher"}`);
|
|
39
|
+
if (Math.abs(filters - pitch) >= 0.5)
|
|
40
|
+
parts.push(filters - pitch > 0 ? "brighter" : "darker");
|
|
41
|
+
if (Math.abs(time - 1) >= 0.04)
|
|
42
|
+
parts.push(`${Math.round(Math.abs(time - 1) * 100)}% ${time > 1 ? "longer" : "shorter"}`);
|
|
43
|
+
if (Math.abs(gain - 1) >= 0.05)
|
|
44
|
+
parts.push(`${Math.abs(Math.round(20 * Math.log10(gain) * 10) / 10)} dB ${gain > 1 ? "louder" : "quieter"}`);
|
|
45
|
+
return parts.length === 0 ? "about the same" : parts.join(", ");
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* `count` takes of a recipe, pushed `toward` a direction. Each take moves
|
|
49
|
+
* the whole sound (so it stays itself) and each layer a little on its own
|
|
50
|
+
* (so the takes differ in character, not only in pitch). The same seed
|
|
51
|
+
* makes the same takes. A tune on the recipe is folded into the takes.
|
|
52
|
+
*/
|
|
53
|
+
export const makeTakes = (recipe, toward, count, seed = 1) => {
|
|
54
|
+
const base = applyTune(recipe);
|
|
55
|
+
const push = pushes[toward];
|
|
56
|
+
const random = seeded(seed * 7919 + toward.length);
|
|
57
|
+
const around = (spread) => (random() * 2 - 1) * spread;
|
|
58
|
+
return Array.from({ length: Math.max(1, Math.min(12, Math.round(count))) }, (_, i) => {
|
|
59
|
+
const pitch = push.pitch + around(SPREAD.pitch);
|
|
60
|
+
const filters = pitch + push.filters - push.pitch + around(SPREAD.filters);
|
|
61
|
+
const time = push.time * (1 + around(SPREAD.time));
|
|
62
|
+
const gain = push.gain * (1 + around(SPREAD.gain));
|
|
63
|
+
const p = Math.pow(2, pitch / 12);
|
|
64
|
+
const f = Math.pow(2, filters / 12);
|
|
65
|
+
// Each layer wanders a little more on its own.
|
|
66
|
+
const layers = base.layers.map((layer) => {
|
|
67
|
+
const own = 1 + around(0.08);
|
|
68
|
+
const g = layer.gain * gain * (1 + around(0.12));
|
|
69
|
+
const delay = layer.delay * time;
|
|
70
|
+
switch (layer.type) {
|
|
71
|
+
case "tone":
|
|
72
|
+
return { ...layer, from: clampHz(layer.from * p * own), to: clampHz(layer.to * p * own), duration: layer.duration * time * own, attack: Math.max(0.0005, layer.attack * push.attack), delay, gain: g };
|
|
73
|
+
case "noise":
|
|
74
|
+
return { ...layer, from: clampHz(layer.from * f * own), to: clampHz(layer.to * f * own), duration: layer.duration * time * own, attack: Math.max(0.0005, layer.attack * push.attack), delay, gain: g };
|
|
75
|
+
case "swish":
|
|
76
|
+
return { ...layer, low: clampHz(layer.low * f * own), peak: clampHz(layer.peak * f * own), duration: layer.duration * time * own, delay, gain: g };
|
|
77
|
+
case "crunch":
|
|
78
|
+
return { ...layer, from: clampHz(layer.from * f * own), to: clampHz(layer.to * f * own), duration: layer.duration * time * own, delay, gain: g };
|
|
79
|
+
case "metal":
|
|
80
|
+
return { ...layer, pitch: clampHz(layer.pitch * p * own), duration: layer.duration * time * own, delay, gain: g };
|
|
81
|
+
case "click":
|
|
82
|
+
return { ...layer, delay, gain: g };
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
const reverb = Math.min(1, Math.max(0, base.reverb + push.reverb + around(0.03)));
|
|
86
|
+
return { id: `t${i + 1}`, label: describe(pitch, filters, time, gain), recipe: { ...base, reverb: round(reverb), layers: layers.map(roundLayer) } };
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
const round = (value) => Math.round(value * 1000) / 1000;
|
|
90
|
+
const roundHz = (value) => Math.round(value);
|
|
91
|
+
/** Takes are written to files: keep their numbers short. */
|
|
92
|
+
const roundLayer = (layer) => {
|
|
93
|
+
const time = { delay: round(layer.delay), gain: round(layer.gain) };
|
|
94
|
+
switch (layer.type) {
|
|
95
|
+
case "tone":
|
|
96
|
+
return { ...layer, ...time, from: roundHz(layer.from), to: roundHz(layer.to), duration: round(layer.duration), attack: Math.max(0.0005, round(layer.attack)) };
|
|
97
|
+
case "noise":
|
|
98
|
+
return { ...layer, ...time, from: roundHz(layer.from), to: roundHz(layer.to), duration: round(layer.duration), attack: Math.max(0.0005, round(layer.attack)) };
|
|
99
|
+
case "swish":
|
|
100
|
+
return { ...layer, ...time, low: roundHz(layer.low), peak: roundHz(layer.peak), duration: round(layer.duration) };
|
|
101
|
+
case "crunch":
|
|
102
|
+
return { ...layer, ...time, from: roundHz(layer.from), to: roundHz(layer.to), duration: round(layer.duration) };
|
|
103
|
+
case "metal":
|
|
104
|
+
return { ...layer, ...time, pitch: roundHz(layer.pitch), duration: round(layer.duration) };
|
|
105
|
+
case "click":
|
|
106
|
+
return { ...layer, ...time };
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
//# sourceMappingURL=takes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"takes.js","sourceRoot":"","sources":["../../../src/sound/core/takes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wav.d.ts","sourceRoot":"","sources":["../../../src/sound/core/wav.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAE3C,eAAO,MAAM,KAAK,4BAA6B,QAAQ,KAAG,UAAU,CAAC,WAAW,CAyB/E,CAAA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const toWav = ({ samples, sampleRate }) => {
|
|
2
|
+
const bytes = new Uint8Array(44 + samples.length * 2);
|
|
3
|
+
const view = new DataView(bytes.buffer);
|
|
4
|
+
const ascii = (at, text) => {
|
|
5
|
+
for (let i = 0; i < text.length; i++)
|
|
6
|
+
bytes[at + i] = text.charCodeAt(i);
|
|
7
|
+
};
|
|
8
|
+
ascii(0, "RIFF");
|
|
9
|
+
view.setUint32(4, 36 + samples.length * 2, true);
|
|
10
|
+
ascii(8, "WAVE");
|
|
11
|
+
ascii(12, "fmt ");
|
|
12
|
+
view.setUint32(16, 16, true);
|
|
13
|
+
view.setUint16(20, 1, true);
|
|
14
|
+
view.setUint16(22, 1, true);
|
|
15
|
+
view.setUint32(24, sampleRate, true);
|
|
16
|
+
view.setUint32(28, sampleRate * 2, true);
|
|
17
|
+
view.setUint16(32, 2, true);
|
|
18
|
+
view.setUint16(34, 16, true);
|
|
19
|
+
ascii(36, "data");
|
|
20
|
+
view.setUint32(40, samples.length * 2, true);
|
|
21
|
+
for (let i = 0; i < samples.length; i++) {
|
|
22
|
+
// Past full scale is clipped here, as any playback would.
|
|
23
|
+
const x = Math.max(-1, Math.min(1, samples[i]));
|
|
24
|
+
view.setInt16(44 + i * 2, Math.round(x * 32767), true);
|
|
25
|
+
}
|
|
26
|
+
return bytes;
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=wav.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wav.js","sourceRoot":"","sources":["../../../src/sound/core/wav.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,EAAE,OAAO,EAAE,UAAU,EAAY,EAA2B,EAAE;IAClF,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IACrD,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IACvC,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,IAAY,EAAQ,EAAE;QAC/C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;IAC1E,CAAC,CAAA;IACD,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;IAChB,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,CAAA;IAChD,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;IAChB,KAAK,CAAC,EAAE,EAAE,MAAM,CAAC,CAAA;IACjB,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,CAAA;IAC5B,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,CAAA;IAC3B,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,CAAA;IAC3B,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,CAAA;IACpC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,UAAU,GAAG,CAAC,EAAE,IAAI,CAAC,CAAA;IACxC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,CAAA;IAC3B,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,CAAA;IAC5B,KAAK,CAAC,EAAE,EAAE,MAAM,CAAC,CAAA;IACjB,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,CAAA;IAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,0DAA0D;QAC1D,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAE,CAAC,CAAC,CAAA;QAChD,IAAI,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,EAAE,IAAI,CAAC,CAAA;IACxD,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC,CAAA"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e=(e,n)=>{let r=e.trim();if(r===``)return;let i=/^[*/+]/.test(r);if(i&&n===void 0)return;let a=t(i?`(${n})${r}`:r);return a===void 0||!Number.isFinite(a)?void 0:a},t=e=>{let t=e.match(/\d*\.?\d+(?:e[+-]?\d+)?|\d+\.|[-+*/()]|\S/gi)??[],n=0,r=()=>t[n],i=()=>{let e=a();for(;e!==void 0&&(r()===`+`||r()===`-`);){let r=t[n++],i=a();if(i===void 0)return;e=r===`+`?e+i:e-i}return e},a=()=>{let e=o();for(;e!==void 0&&(r()===`*`||r()===`/`);){let r=t[n++],i=o();if(i===void 0||r===`/`&&i===0)return;e=r===`*`?e*i:e/i}return e},o=()=>{let e=t[n++];if(e===`-`||e===`+`){let t=o();return t===void 0?void 0:e===`-`?-t:t}if(e===`(`){let e=i();return t[n++]===`)`?e:void 0}if(e!==void 0&&/^(\d*\.?\d+(e[+-]?\d+)?|\d+\.)$/i.test(e))return Number(e)},s=i();return n===t.length?s:void 0},n=(e,t={},...n)=>{let i=document.createElement(e);t.class!==void 0&&(i.className=t.class),t.title!==void 0&&(i.title=t.title);for(let[e,n]of Object.entries(t.attrs??{}))n!==void 0&&n!==!1&&i.setAttribute(e,n===!0?``:String(n));for(let[e,n]of Object.entries(t.props??{}))Reflect.set(i,e,n);let a=t.on??{};for(let e of Object.keys(a)){let t=a[e];t!==void 0&&i.addEventListener(e,t)}return r(i,n),i},r=(e,t)=>{for(let n of t)n!=null&&n!==!1&&(Array.isArray(n)?r(e,n):e.appendChild(n instanceof Node?n:document.createTextNode(String(n))))},i=(e,...t)=>{e.replaceChildren(),r(e,t)},a={move:`<path d="M5 9l-3 3 3 3M9 5l3-3 3 3M15 19l-3 3-3-3M19 9l3 3-3 3M2 12h20M12 2v20"/>`,erase:`<path d="m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21"/><path d="M22 21H7"/><path d="m5 11 9 9"/>`,restore:`<path d="m9.06 11.9 8.07-8.06a2.85 2.85 0 1 1 4.03 4.03l-8.06 8.08"/><path d="M7.07 14.94c-1.66 0-3 1.35-3 3.02 0 1.33-2.5 1.52-2 2.02 1.08 1.1 2.49 2.02 4 2.02 2.2 0 4-1.8 4-4.04a3.01 3.01 0 0 0-3-3.02z"/>`,pick:`<path d="m2 22 1-1h3l9-9"/><path d="M3 21v-3l9-9"/><path d="m15 6 3.4-3.4a2.1 2.1 0 1 1 3 3L18 9l.4.4a2.1 2.1 0 1 1-3 3l-3.8-3.8a2.1 2.1 0 1 1 3-3l.4.4Z"/>`,undo:`<path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 0 11H11"/>`,redo:`<path d="m15 14 5-5-5-5"/><path d="M20 9H9.5a5.5 5.5 0 0 0 0 11H13"/>`,minus:`<path d="M5 12h14"/>`,plus:`<path d="M5 12h14M12 5v14"/>`,fit:`<path d="M3 7V5a2 2 0 0 1 2-2h2M17 3h2a2 2 0 0 1 2 2v2M21 17v2a2 2 0 0 1-2 2h-2M7 21H5a2 2 0 0 1-2-2v-2"/>`,open:`<path d="m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2"/>`,download:`<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/><path d="M12 15V3"/>`,chevron:`<path d="m6 9 6 6 6-6"/>`,info:`<circle cx="12" cy="12" r="9"/><path d="M12 16v-4M12 8h.01"/>`,lock:`<rect x="4" y="11" width="16" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/>`,unlock:`<rect x="4" y="11" width="16" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 7.9-1"/>`,trash:`<path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>`,split:`<circle cx="6" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M20 4 8.12 15.88M14.47 14.48 20 20M8.12 8.12 12 12"/>`,wand:`<path d="m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72"/><path d="m14 7 3 3"/><path d="M5 6v4M19 14v4M10 2v2M7 8H3M21 16h-4M11 3H9"/>`,image:`<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.09-3.09a2 2 0 0 0-2.82 0L6 21"/>`,close:`<path d="M18 6 6 18M6 6l12 12"/>`,check:`<path d="M20 6 9 17l-5-5"/>`,alert:`<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4M12 17h.01"/>`,sparkle:`<path d="M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M5.6 18.4l2.1-2.1M16.3 7.7l2.1-2.1"/>`,lines:`<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M3 15h18M9 3v18M15 3v18"/>`,brush:`<path d="m14.6 3.4 6 6L10 20H4v-6z"/><path d="m12 6 6 6"/>`,pixel:`<rect x="6" y="6" width="12" height="12" rx="1"/>`,area:`<path d="M4 4h7v7H4zM13 4h7v7h-7zM4 13h7v7H4z"/>`,play:`<path d="M7 4.5v15a1 1 0 0 0 1.5.86l12.5-7.5a1 1 0 0 0 0-1.72L8.5 3.64A1 1 0 0 0 7 4.5z"/>`,pause:`<rect x="6" y="4" width="4" height="16" rx="1"/><rect x="14" y="4" width="4" height="16" rx="1"/>`,previous:`<path d="M19 20 9 12l10-8v16zM5 19V5"/>`,next:`<path d="m5 4 10 8-10 8V4zM19 5v14"/>`,frames:`<rect x="2" y="6" width="6" height="12" rx="1"/><rect x="9" y="6" width="6" height="12" rx="1"/><rect x="16" y="6" width="6" height="12" rx="1"/>`,onion:`<rect x="3" y="3" width="13" height="13" rx="2" stroke-dasharray="2 2"/><rect x="8" y="8" width="13" height="13" rx="2"/>`,film:`<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M7 3v18M17 3v18M3 7.5h4M3 12h18M3 16.5h4M17 7.5h4M17 16.5h4"/>`,volume:`<path d="M11 4.7a.7.7 0 0 0-1.2-.5L6.4 7.6A1.4 1.4 0 0 1 5.4 8H3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2.4a1.4 1.4 0 0 1 1 .4l3.4 3.4a.7.7 0 0 0 1.2-.5z"/><path d="M16 9a5 5 0 0 1 0 6"/><path d="M19.4 18.4a9 9 0 0 0 0-12.7"/>`,search:`<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>`,wave:`<path d="M2 13a2 2 0 0 0 2-2V7a2 2 0 0 1 4 0v13a2 2 0 0 0 4 0V4a2 2 0 0 1 4 0v13a2 2 0 0 0 4 0v-4a2 2 0 0 1 2-2"/>`,shuffle:`<path d="m18 14 4 4-4 4"/><path d="m18 2 4 4-4 4"/><path d="M2 18h1.97a4 4 0 0 0 3.3-1.7l5.46-7.6a4 4 0 0 1 3.3-1.7H22"/><path d="M2 6h1.97a4 4 0 0 1 3.3 1.7l.67.94"/><path d="M22 18h-6.03a4 4 0 0 1-3.3-1.7l-.67-.94"/>`,layers:`<path d="M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z"/><path d="m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65"/><path d="m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65"/>`,mic:`<rect x="9" y="2" width="6" height="12" rx="3"/><path d="M19 10v1a7 7 0 0 1-14 0v-1M12 18v4M8 22h8"/>`,music:`<path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/>`,stop:`<rect x="5" y="5" width="14" height="14" rx="2"/>`,repeat:`<path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/>`,comment:`<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>`},o=e=>{let t=n(`span`,{class:`icon`,attrs:{"aria-hidden":`true`}});return t.innerHTML=`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${a[e]}</svg>`,t},s=()=>{let e=n(`span`,{class:`mark`,attrs:{"aria-hidden":`true`}});return e.innerHTML=`<svg viewBox="0 0 100 100"><g fill="currentColor"><rect x="6" y="6" width="40" height="40" rx="6"/><rect x="54" y="6" width="40" height="40" rx="6"/><rect x="6" y="54" width="40" height="40" rx="6"/></g><rect x="62" y="62" width="24" height="24" rx="4" fill="#FF5A36"/></svg>`,e},c=(e,t,r={})=>{let i=[`btn`,r.variant??`secondary`];return r.small===!0&&i.push(`small`),r.iconOnly===!0&&i.push(`icon-only`),r.class!==void 0&&i.push(r.class),n(`button`,{class:i.join(` `),title:r.title??(r.iconOnly===!0?e:void 0),attrs:{type:`button`,disabled:r.disabled===!0,"aria-pressed":r.pressed===void 0?void 0:String(r.pressed),"aria-label":r.iconOnly===!0?e:void 0},on:{click:t}},r.icon!==void 0&&o(r.icon),r.iconOnly!==!0&&n(`span`,{},e))},l=(e,t,r,i={})=>n(`div`,{class:`segmented${i.class===void 0?``:` ${i.class}`}`,attrs:{role:`group`,"aria-label":i.label}},t.map(t=>n(`button`,{class:t.id===e?`on`:``,title:t.title??(t.iconOnly===!0?t.label:void 0),attrs:{type:`button`,"aria-pressed":String(t.id===e),"aria-label":t.iconOnly===!0?t.label:void 0},on:{click:()=>r(t.id)}},t.icon!==void 0&&o(t.icon),t.iconOnly!==!0&&n(`span`,{},t.label)))),u=(e,t,r)=>n(`div`,{class:`prop`,title:r},n(`span`,{class:`prop-label`},e),n(`div`,{class:`prop-control`},t)),d=1e4,f=(t,r,i={})=>{let a=t===void 0?``:String(t),o=e=>Math.min(i.max??1/0,Math.max(i.min??-1/0,Math.round(e*d)/d)),s=(e,n)=>{let i=o(n);e.value=String(i),i!==t&&r(i)};return n(`label`,{class:`input`},i.prefix!==void 0&&n(`span`,{class:`affix`},i.prefix),n(`input`,{props:{type:`text`,value:a},attrs:{inputmode:`decimal`,autocomplete:`off`,spellcheck:`false`,placeholder:i.placeholder,"aria-label":i.label??i.prefix,title:`Math works: 1015/51, or *2 to double the value`},on:{change:n=>{let i=n.currentTarget;if(!(i instanceof HTMLInputElement))return;if(i.value.trim()===``){r(0);return}let o=e(i.value,t);o===void 0?i.value=a:s(i,o)},keydown:n=>{let r=n.currentTarget;if(r instanceof HTMLInputElement){if(n.key===`Enter`)r.blur();else if(n.key===`Escape`)r.value=a,r.blur();else if(n.key===`ArrowUp`||n.key===`ArrowDown`){n.preventDefault();let a=e(r.value,t)??t??0,o=(i.step??.01)*(n.shiftKey?10:1);s(r,a+(n.key===`ArrowUp`?o:-o))}}}}}),i.suffix!==void 0&&n(`span`,{class:`affix`},i.suffix))},p=(e,t,r,i)=>n(`label`,{class:`input select`},n(`select`,{attrs:{"aria-label":i},on:{change:e=>{let n=e.currentTarget;if(!(n instanceof HTMLSelectElement))return;let i=t.find(e=>e.id===n.value);i!==void 0&&r(i.id)}}},t.map(t=>n(`option`,{props:{value:t.id,selected:t.id===e}},t.label))),o(`chevron`)),ee=(e,t,r,i)=>n(`label`,{class:`switch-row`,title:i},n(`span`,{},e),n(`input`,{class:`switch`,props:{type:`checkbox`,checked:t},attrs:{role:`switch`},on:{change:e=>{let t=e.currentTarget;t instanceof HTMLInputElement&&r(t.checked)}}})),m=(e,t,r,i,a,o,s)=>{let c=n(`output`,{},i(t)),l=e=>{let t=(Number(e.value)-r.min)/Math.max(1e-9,r.max-r.min);e.style.setProperty(`--fill`,`${Math.round(t*1e3)/10}%`)},u=n(`input`,{class:`range`,props:{type:`range`,min:String(r.min),max:String(r.max),step:String(r.step),value:String(t)},attrs:{"aria-label":e},on:{input:e=>{let t=e.currentTarget;t instanceof HTMLInputElement&&(c.textContent=i(Number(t.value)),l(t),a(Number(t.value)))},change:()=>o()}});return l(u),n(`div`,{class:`prop slider`,title:s},n(`span`,{class:`prop-label`},e),n(`div`,{class:`prop-control`},u,c))},te=(...e)=>n(`p`,{class:`hint`},...e),ne=(e,t,r)=>n(`span`,{class:`badge ${t}`},r!==void 0&&o(r),e),re=(e,...t)=>n(`div`,{class:`callout ${e}`},o(e===`good`?`check`:e===`neutral`||e===`accent`?`info`:`alert`),n(`div`,{},...t)),ie=`ritmo-tools:sections`,ae=(()=>{try{let e=localStorage.getItem(ie),t=e===null?{}:JSON.parse(e);return typeof t!=`object`||!t||Array.isArray(t)?new Map:new Map(Object.entries(t).filter(e=>typeof e[1]==`boolean`))}catch{return new Map}})(),oe=new Set,se=()=>{try{localStorage.setItem(ie,JSON.stringify(Object.fromEntries(ae)))}catch{}},h=(e,...t)=>{let r=ae.get(e.id)??e.closed!==!0,i=oe.has(e.id);return n(`section`,{class:`section${r?``:` closed`}`,attrs:{"data-section":e.id}},n(`header`,{class:`section-head`},n(`button`,{class:`section-toggle`,attrs:{type:`button`,"aria-expanded":String(r)},on:{click:()=>{ae.set(e.id,!r),se(),e.onToggle()}}},o(`chevron`),n(`h2`,{},e.title)),r&&e.help!==void 0&&n(`button`,{class:`btn ghost icon-only small${i?` on`:``}`,title:i?`Hide help`:`What is this?`,attrs:{type:`button`,"aria-pressed":String(i),"aria-label":`Help`},on:{click:()=>{i?oe.delete(e.id):oe.add(e.id),e.onToggle()}}},o(`info`)),r&&e.action!==void 0&&n(`div`,{class:`section-action`},e.action)),r&&n(`div`,{class:`section-body`},i&&e.help!==void 0&&n(`p`,{class:`help`},e.help),...t.flat()))},ce=(e,...t)=>n(`div`,{class:`group`},n(`h3`,{},e),...t.flat()),le=350,ue=200,de=(e,t,n)=>{let r=[],i=[],a,o,s=()=>{o!==void 0&&clearTimeout(o),o=void 0;let t=e();t!==a&&(a!==void 0&&(r.push(a),r.length>ue&&r.shift(),i.length=0),a=t,n())},c=(e,r)=>{s();let i=e.pop();i!==void 0&&a!==void 0&&(r.push(a),a=i,t(i),n())};return{schedule:()=>{o!==void 0&&clearTimeout(o),o=setTimeout(s,le)},undo:()=>c(r,i),redo:()=>c(i,r),canUndo:()=>r.length>0,canRedo:()=>i.length>0,reset:()=>{o!==void 0&&clearTimeout(o),o=void 0,r.length=0,i.length=0,a=void 0}}},fe=[`sine`,`triangle`,`square`,`sawtooth`],pe=[`lowpass`,`highpass`,`bandpass`],me=[`tone`,`noise`,`swish`,`crunch`,`metal`,`click`],g={pitch:0,length:1,brightness:0,echo:0,volume:0},_=e=>typeof e==`object`&&!!e&&!Array.isArray(e),v=e=>({number:(t,n,r,i,a)=>t===void 0?(r===void 0&&e.push(`${n} is missing`),r??0):typeof t!=`number`||!Number.isFinite(t)?(e.push(`${n} must be a number`),r??0):(i!==void 0&&t<i&&e.push(`${n} must be at least ${i}`),a!==void 0&&t>a&&e.push(`${n} must be at most ${a}`),t),oneOf:(t,n,r,i)=>{if(t===void 0)return i;let a=r.find(e=>e===t);return a===void 0?(e.push(`${n} must be one of ${r.join(`, `)}`),i):a}}),he=2e4,ge=(e,t,n)=>{if(!_(e)){n.push(`${t} must be an object`);return}let{number:r,oneOf:i}=v(n),a=e.type,o=r(e.delay,`${t}.delay`,0,0),s=r(e.gain,`${t}.gain`,void 0,0,4),c=(n,i)=>r(e[n],`${t}.${n}`,i,1,he),l=()=>r(e.duration,`${t}.duration`,void 0,.001,10);switch(a){case`tone`:return{type:a,wave:i(e.wave,`${t}.wave`,fe,`sine`),from:c(`from`),to:c(`to`,typeof e.from==`number`?e.from:void 0),duration:l(),gain:s,delay:o,attack:r(e.attack,`${t}.attack`,.006,5e-4)};case`noise`:return{type:a,filter:i(e.filter,`${t}.filter`,pe,`lowpass`),from:c(`from`),to:c(`to`,typeof e.from==`number`?e.from:void 0),q:r(e.q,`${t}.q`,1,.01,40),duration:l(),gain:s,delay:o,attack:r(e.attack,`${t}.attack`,.004,5e-4)};case`swish`:return{type:a,low:c(`low`),peak:c(`peak`),peakAt:r(e.peakAt,`${t}.peakAt`,.4,.01,.99),q:r(e.q,`${t}.q`,1,.01,40),duration:l(),gain:s,delay:o};case`crunch`:return{type:a,from:c(`from`),to:c(`to`,typeof e.from==`number`?e.from:void 0),duration:l(),gain:s,delay:o};case`metal`:return{type:a,pitch:c(`pitch`),duration:l(),gain:s,delay:o};case`click`:return{type:a,gain:s,delay:o};default:n.push(`${t}.type must be one of ${me.join(`, `)}`);return}},_e=(e,t,n)=>{if(e===void 0)return;if(!_(e)){n.push(`${t} must be an object`);return}let{number:r}=v(n);return{pitch:r(e.pitch,`${t}.pitch`,0,-24,24),length:r(e.length,`${t}.length`,1,.25,4),brightness:r(e.brightness,`${t}.brightness`,0,-24,24),echo:r(e.echo,`${t}.echo`,0,-1,1),volume:r(e.volume,`${t}.volume`,0,-40,24)}},ve=(e,t,n)=>{if(!_(e)){n.push(`${t} must be an object`);return}let{number:r}=v(n);if(!Array.isArray(e.layers)){n.push(`${t}.layers must be a list`);return}let i=e.layers.flatMap((e,r)=>ge(e,`${t}.layers[${r}]`,n)??[]);return e.about!==void 0&&typeof e.about!=`string`&&n.push(`${t}.about must be text`),e.heavy!==void 0&&typeof e.heavy!=`boolean`&&n.push(`${t}.heavy must be true or false`),{about:typeof e.about==`string`?e.about:void 0,reverb:r(e.reverb,`${t}.reverb`,0,0,1),heavy:e.heavy===!0||void 0,interval:e.interval===void 0?void 0:r(e.interval,`${t}.interval`,0,0),layers:i,tune:_e(e.tune,`${t}.tune`,n)}},ye=e=>{try{return{ok:!0,value:JSON.parse(e)}}catch(e){return{ok:!1,error:`not JSON: ${e instanceof Error?e.message:String(e)}`}}},be=e=>/^[A-Za-z][\w.-]{0,63}$/.test(e),xe=e=>{let t=ye(e);if(!t.ok)return t;let n=t.value;if(!_(n)||!_(n.sounds))return{ok:!1,error:`a bank is {"version": 1, "sounds": {"name": recipe, ...}}`};if(n.version!==void 0&&n.version!==1)return{ok:!1,error:`bank version ${String(n.version)} is not known (this tool reads version 1)`};let r=[],i={};for(let[e,t]of Object.entries(n.sounds)){be(e)||r.push(`"${e}" is not a sound name (letters, digits, -, _, and ., starting with a letter)`);let n=ve(t,e,r);n!==void 0&&(i[e]=n)}let a=n.master===void 0?void 0:v(r).number(n.master,`master`,1,.01,4);return r.length>0?{ok:!1,error:r.join(`; `)}:{ok:!0,value:{master:a,sounds:i}}},Se=e=>{let t=e.layers.map(e=>{let{delay:t,...n}=e,r={...n};return t!==0&&(r.delay=t),r}),n=e.tune===void 0||Ce(e.tune)?void 0:e.tune;return{...e.about===void 0?{}:{about:e.about},reverb:e.reverb,...e.heavy===!0?{heavy:!0}:{},...e.interval===void 0?{}:{interval:e.interval},layers:t,...n===void 0?{}:{tune:n}}},Ce=e=>e.pitch===0&&e.length===1&&e.brightness===0&&e.echo===0&&e.volume===0,we=(e,t)=>{if(Array.isArray(e))return e.length===0?`[]`:`[\n${e.map(e=>`${t} ${JSON.stringify(e)}`).join(`,
|
|
2
|
+
`)}\n${t}]`;if(_(e)){let n=Object.entries(e).filter(([,e])=>e!==void 0);return n.length===0?`{}`:`{\n${n.map(([e,n])=>`${t} ${JSON.stringify(e)}: ${e===`tune`?JSON.stringify(n):we(n,`${t} `)}`).join(`,
|
|
3
|
+
`)}\n${t}}`}return JSON.stringify(e)},Te=e=>{let t={};for(let[n,r]of Object.entries(e.sounds))t[n]=Se(r);return we({version:1,...e.master===void 0?{}:{master:e.master},sounds:t},``)+`
|
|
4
|
+
`},y=e=>{let t=e.tune;if(t===void 0||Ce(t))return e;let n=2**(t.pitch/12),r=n*2**(t.brightness/12),i=10**(t.volume/20),a=t.length,o=e.layers.map(e=>{let t=e.delay*a,o=e.gain*i;switch(e.type){case`tone`:return{...e,from:b(e.from*n),to:b(e.to*n),duration:e.duration*a,attack:e.attack*a,delay:t,gain:o};case`noise`:return{...e,from:b(e.from*r),to:b(e.to*r),duration:e.duration*a,attack:e.attack*a,delay:t,gain:o};case`swish`:return{...e,low:b(e.low*r),peak:b(e.peak*r),duration:e.duration*a,delay:t,gain:o};case`crunch`:return{...e,from:b(e.from*r),to:b(e.to*r),duration:e.duration*a,delay:t,gain:o};case`metal`:return{...e,pitch:b(e.pitch*n),duration:e.duration*a,delay:t,gain:o};case`click`:return{...e,delay:t,gain:o}}});return{...e,reverb:Math.min(1,Math.max(0,e.reverb+t.echo)),layers:o,tune:void 0}},b=e=>Math.min(he,Math.max(1,e)),Ee=e=>{let t=e=>`${e<.1?Math.round(e*1e3)+` ms`:Math.round(e*100)/100+` s`}`,n=(e,t)=>Math.round(e)===Math.round(t)?`${Math.round(e)} Hz`:`${Math.round(e)}→${Math.round(t)} Hz`;switch(e.type){case`tone`:return`${e.wave} ${n(e.from,e.to)}, ${t(e.duration)}`;case`noise`:return`${e.filter} ${n(e.from,e.to)}, ${t(e.duration)}`;case`swish`:return`${Math.round(e.low)}→${Math.round(e.peak)} Hz, ${t(e.duration)}`;case`crunch`:return`${n(e.from,e.to)}, ${t(e.duration)}`;case`metal`:return`${Math.round(e.pitch)} Hz, ${t(e.duration)}`;case`click`:return`tick`}},De=1.3,Oe=.9,x=1e-4,ke=e=>{let t=e>>>0;return()=>{t=t+1831565813>>>0;let e=t;return e=Math.imul(e^e>>>15,e|1),e^=e+Math.imul(e^e>>>7,e|61),((e^e>>>14)>>>0)/4294967296}},S=(e,t,n,r)=>r<=0?t:e*(t/e)**+Math.min(1,Math.max(0,n/r)),Ae=(e,t,n,r)=>{if(e<0||e>=t)return 0;let i=Math.max(2e-4,n),a=Math.min(r,t*.9);return e<a?S(x,i,e,a):S(i,x,e-a,t-a)},je=(e,t)=>{if(e<t){let n=e/t;return n+n-n*n-1}if(e>1-t){let n=(e-1)/t;return n*n+n+n+1}return 0},Me=(e,t,n)=>{switch(e){case`sine`:return Math.sin(2*Math.PI*t);case`triangle`:return t<.5?4*t-1:3-4*t;case`square`:return(t<.5?1:-1)+je(t,n)-je((t+.5)%1,n);case`sawtooth`:return 2*t-1-je(t,n)}},Ne=(e,t,n)=>{let r=0,i=0,a=0,o=0,s=0,c=0,l=0,u=0,d=0;return{set:c=>{let l=2*Math.PI*Math.min(c,n*.49)/n,u=Math.cos(l),d=e===`bandpass`?Math.max(1e-4,t):10**(t/20),f=Math.sin(l)/(2*d),p=1+f;e===`lowpass`?(r=(1-u)/2/p,i=(1-u)/p,a=r):e===`highpass`?(r=(1+u)/2/p,i=-(1+u)/p,a=r):(r=f/p,i=0,a=-f/p),o=-2*u/p,s=(1-f)/p},step:e=>{let t=r*e+i*c+a*l-o*u-s*d;return l=c,c=e,d=u,u=t,t}}},Pe=16,Fe=[[1,1,1],[2.76,.6,.7],[5.4,.4,.45],[8.93,.25,.3]],C={filter:`highpass`,from:5e3,to:3e3,q:.8,duration:.016,attack:.001},Ie=e=>e.delay+(e.type===`click`?C.duration:e.duration),Le=(e,t,n,r,i,a,o,s,c)=>{let l=Math.round(s*t),u=Math.ceil(a*t),d=0;for(let s=0;s<u&&l+s<e.length;s++){let u=s/t,f=S(r,Math.max(1,i),u,a)/t;e[l+s]+=Me(n,d,f)*Ae(u,a,o,c),d+=f,d-=Math.floor(d)}},w=(e,t,n,r,i,a,o,s,c,l)=>{let u=Math.round(a*t),d=Math.ceil(o*t),f=Ne(r,i,t);for(let r=0;r<d&&u+r<e.length;r++){let i=r/t;r%Pe===0&&f.set(s(i));let a=f.step(n()*2-1);l!==void 0&&(a=l(a)),e[u+r]+=a*c(i)}},Re=e=>Math.tanh(3*Math.max(-1,Math.min(1,e*6))),ze=(e,t,n,r)=>{switch(n.type){case`tone`:Le(e,t,n.wave,n.from,n.to,n.duration,n.gain,n.delay,n.attack);return;case`noise`:w(e,t,r,n.filter,n.q,n.delay,n.duration,e=>S(n.from,Math.max(1,n.to),e,n.duration),e=>Ae(e,n.duration,n.gain,n.attack));return;case`click`:w(e,t,r,C.filter,C.q,n.delay,C.duration,e=>S(C.from,C.to,e,C.duration),e=>Ae(e,C.duration,n.gain,C.attack));return;case`swish`:{let i=n.duration*n.peakAt;w(e,t,r,`bandpass`,n.q,n.delay,n.duration,e=>e<i?S(n.low,n.peak,e,i):S(n.peak,n.low*.8,e-i,n.duration-i),e=>e<i?S(x,Math.max(2e-4,n.gain),e,i):S(Math.max(2e-4,n.gain),x,e-i,n.duration-i));return}case`crunch`:w(e,t,r,`bandpass`,1.4,n.delay,n.duration,e=>S(n.from,Math.max(1,n.to),e,n.duration),e=>Ae(e,n.duration,n.gain,.002),Re);return;case`metal`:for(let[r,i,a]of Fe)Le(e,t,`sine`,n.pitch*r,n.pitch*r*.998,n.duration*a,n.gain*i,n.delay,.002);return}},T=(e,t,n)=>{let r=e.length;for(let n=1,i=0;n<r;n++){let a=r>>1;for(;i&a;a>>=1)i^=a;i^=a,n<i&&([e[n],e[i]]=[e[i],e[n]],[t[n],t[i]]=[t[i],t[n]])}for(let i=2;i<=r;i<<=1){let a=(n?2:-2)*Math.PI/i,o=Math.cos(a),s=Math.sin(a);for(let n=0;n<r;n+=i){let r=1,a=0;for(let c=0;c<i/2;c++){let l=n+c,u=l+i/2,d=e[u]*r-t[u]*a,f=e[u]*a+t[u]*r;e[u]=e[l]-d,t[u]=t[l]-f,e[l]=e[l]+d,t[l]=t[l]+f;let p=r*o-a*s;a=r*s+a*o,r=p}}}},Be=e=>{let t=ke(24301),n=Math.floor(e*De),r=Math.floor(e*.012),i=new Float64Array(n),a=0,o=0;for(let e=r;e<n;e++){let s=(e-r)/(n-r),c=.15+.8*s;a=a*c+(t()*2-1)*(1-c),i[e]=a*(1-s)**3*(1.6+2*s),o+=i[e]*i[e]}let s=.00125/Math.sqrt(o/n);for(let e=0;e<n;e++)i[e]*=s;return i},Ve=new Map,He=(e,t,n,r)=>{if(r<=0)return;let i=1;for(;i<t+Math.floor(n*De);)i<<=1;let a=Ve.get(n);if(a===void 0||a.size!==i){let e=Be(n),t=new Float64Array(i),r=new Float64Array(i);t.set(e),T(t,r,!1),a={size:i,re:t,im:r},Ve.set(n,a)}let o=new Float64Array(i),s=new Float64Array(i);for(let n=0;n<t;n++)o[n]=e[n]*r;T(o,s,!1);for(let e=0;e<i;e++){let t=o[e]*a.re[e]-s[e]*a.im[e];s[e]=o[e]*a.im[e]+s[e]*a.re[e],o[e]=t}T(o,s,!0);for(let t=0;t<e.length&&t<i;t++)e[t]+=o[t]/i*Oe},Ue=e=>y(e).layers.reduce((e,t)=>Math.max(e,Ie(t)),0),We=1e-4,Ge=(e,t={})=>{let n=t.sampleRate??44100,r=y(e),i=ke(t.seed??1),a=Math.ceil((Ue(r)+.02)*n),o=a+(r.reverb>0?Math.ceil(De*n):0),s=new Float32Array(o);for(let e of r.layers)ze(s,n,e,i);He(s,a,n,r.reverb);let c=t.master??1;if(c!==1)for(let e=0;e<s.length;e++)s[e]*=c;let l=o;for(;l>1&&Math.abs(s[l-1])<We;)l--;return{sampleRate:n,samples:s.slice(0,l)}},Ke=e=>e<=1e-9?-180:20*Math.log10(e),E=(e,t=1)=>Math.round(e*10**t)/10**t,qe=({samples:e,sampleRate:t})=>{let n=0;for(let t of e)n=Math.max(n,Math.abs(t));let r=0;for(let t=e.length-1;t>=0;t--)if(Math.abs(e[t])>=.001){r=t+1;break}let i=0;for(;i<e.length&&Math.abs(e[i])<n*.1;)i++;let a=Math.round(t*.05),o=Math.round(t*.005),s=0,c=0;for(let t=0;t<Math.max(1,e.length-a+1);t+=o){let n=0,r=Math.min(e.length,t+a);for(let i=t;i<r;i++)n+=e[i]*e[i];let i=Math.sqrt(n/a);i>s&&(s=i,c=t)}let l=Math.round(t*.001),u=0;for(let t=Math.max(0,r-l);t<r;t++)u=Math.max(u,Math.abs(e[t]));return{duration:E(r/t,3),peak:E(Ke(n)),loudness:E(Ke(s)),attack:E(i/t*1e3),brightness:Math.round(Je(e,t,c,a)),ending:E(Ke(u))}},Je=(e,t,n,r)=>{let i=1;for(;i<r;)i<<=1;let a=new Float64Array(i),o=new Float64Array(i);for(let t=0;t<r&&n+t<e.length;t++)a[t]=e[n+t]*(.5-.5*Math.cos(2*Math.PI*t/(r-1)));T(a,o,!1);let s=0,c=0;for(let e=1;e<i/2;e++){let n=Math.hypot(a[e],o[e]);s+=n*(e*t/i),c+=n}return c===0?0:s/c},D={silent:-40,clipping:0,lateStart:80,long:3,endClick:-40,louder:10,quieter:14},Ye=(e,t,n)=>{let r=[];return e.layers.length===0?[{rule:`empty`,severity:`error`,message:`No layers: the sound is silent.`}]:(t.peak<D.silent&&r.push({rule:`silent`,severity:`error`,message:`Peaks at ${t.peak} dBFS: too quiet to be heard. Raise the layers' gain.`}),t.peak>D.clipping&&r.push({rule:`clipping`,severity:`error`,message:`Peaks at +${t.peak} dBFS: it clips and distorts. Lower the gain (or the tune's volume) by ${Math.ceil(t.peak+1)} dB.`}),t.attack>D.lateStart&&r.push({rule:`late-start`,severity:`warning`,message:`Reaches a tenth of its peak after ${t.attack} ms: it will feel late if it answers an action (fine for a swell). Shorten the first layer's attack, or start a layer at once.`}),t.duration>D.long&&r.push({rule:`long`,severity:`warning`,message:`Lasts ${t.duration} s, long for an effect.`}),t.ending>D.endClick&&r.push({rule:`end-click`,severity:`warning`,message:`Stops abruptly (${t.ending} dBFS in its last millisecond): it ends with a click.`}),n!==void 0&&t.loudness>n+D.louder&&r.push({rule:`too-loud`,severity:`warning`,message:`${E(t.loudness-n)} dB louder than the middle of its kind (${n} dBFS${e.heavy===!0?`, heavy sounds`:``}): it will stand out.`}),n!==void 0&&t.loudness<n-D.quieter&&r.push({rule:`too-quiet`,severity:`warning`,message:`${E(n-t.loudness)} dB quieter than the middle of its kind (${n} dBFS${e.heavy===!0?`, heavy sounds`:``}): it may get lost.`}),r)},Xe=e=>{if(e.length===0)return;let t=[...e].sort((e,t)=>e-t),n=Math.floor(t.length/2);return E(t.length%2==1?t[n]:(t[n-1]+t[n])/2)},Ze=(e,t)=>{let n=Object.entries(e.sounds).map(([t,n])=>({name:t,recipe:n,measures:qe(Ge(n,{master:e.master}))})),r=e=>n.filter(t=>t.recipe.layers.length>0&&t.recipe.heavy===!0===e).map(e=>e.measures.loudness),i=r(!0),a=r(!1),o={heavy:i.length>=3?Xe(i):void 0,other:a.length>=3?Xe(a):void 0},s=(t===void 0||t.length===0?n:n.filter(e=>t.includes(e.name))).map(({name:e,recipe:t,measures:n})=>({name:e,about:t.about,...n,issues:Ye(t,n,t.heavy===!0?o.heavy:o.other)})),c=s.reduce((e,t)=>e+t.issues.filter(e=>e.severity===`error`).length,0),l=s.reduce((e,t)=>e+t.issues.filter(e=>e.severity===`warning`).length,0),u=c>0?`errors`:l>0?`warnings`:`clean`;return{verdict:u,summary:u===`clean`?`${s.length} sound${s.length===1?``:`s`}, nothing to fix.`:`${s.length} sound${s.length===1?``:`s`}: ${c} error${c===1?``:`s`}, ${l} warning${l===1?``:`s`}.`,middle:o,sounds:s}},O=[{id:`wider`,label:`Anything`,hint:`Takes spread around the sound, in no one direction.`},{id:`heavier`,label:`Heavier`,hint:`Lower, longer, with more weight.`},{id:`lighter`,label:`Lighter`,hint:`Higher, shorter, thinner.`},{id:`brighter`,label:`Brighter`,hint:`More highs in the noise; the notes stay.`},{id:`darker`,label:`Darker`,hint:`Fewer highs in the noise; the notes stay.`},{id:`punchier`,label:`Punchier`,hint:`A sharper start and a quicker end.`},{id:`softer`,label:`Softer`,hint:`A gentler start, rounder, quieter.`},{id:`shorter`,label:`Shorter`,hint:`Everything quicker.`},{id:`longer`,label:`Longer`,hint:`Everything drawn out.`}],Qe={wider:{pitch:0,time:1,gain:1,filters:0,attack:1,reverb:0},heavier:{pitch:-3,time:1.25,gain:1.15,filters:-3,attack:1,reverb:.05},lighter:{pitch:3,time:.8,gain:.9,filters:3,attack:1,reverb:-.03},brighter:{pitch:0,time:1,gain:1,filters:5,attack:1,reverb:0},darker:{pitch:0,time:1,gain:1,filters:-5,attack:1,reverb:0},punchier:{pitch:0,time:.85,gain:1.15,filters:1,attack:.4,reverb:-.04},softer:{pitch:0,time:1.1,gain:.8,filters:-2,attack:2.5,reverb:.04},shorter:{pitch:0,time:.7,gain:1,filters:0,attack:.8,reverb:-.03},longer:{pitch:0,time:1.4,gain:1,filters:0,attack:1.2,reverb:.04}},k={pitch:1.5,filters:3,time:.15,gain:.2},A=e=>Math.min(2e4,Math.max(20,e)),$e=(e,t,n,r)=>{let i=[];return Math.abs(e)>=.3&&i.push(`${Math.abs(Math.round(e*10)/10)} semitones ${e<0?`lower`:`higher`}`),Math.abs(t-e)>=.5&&i.push(t-e>0?`brighter`:`darker`),Math.abs(n-1)>=.04&&i.push(`${Math.round(Math.abs(n-1)*100)}% ${n>1?`longer`:`shorter`}`),Math.abs(r-1)>=.05&&i.push(`${Math.abs(Math.round(20*Math.log10(r)*10)/10)} dB ${r>1?`louder`:`quieter`}`),i.length===0?`about the same`:i.join(`, `)},et=(e,t,n,r=1)=>{let i=y(e),a=Qe[t],o=ke(r*7919+t.length),s=e=>(o()*2-1)*e;return Array.from({length:Math.max(1,Math.min(12,Math.round(n)))},(e,t)=>{let n=a.pitch+s(k.pitch),r=n+a.filters-a.pitch+s(k.filters),o=a.time*(1+s(k.time)),c=a.gain*(1+s(k.gain)),l=2**(n/12),u=2**(r/12),d=i.layers.map(e=>{let t=1+s(.08),n=e.gain*c*(1+s(.12)),r=e.delay*o;switch(e.type){case`tone`:return{...e,from:A(e.from*l*t),to:A(e.to*l*t),duration:e.duration*o*t,attack:Math.max(5e-4,e.attack*a.attack),delay:r,gain:n};case`noise`:return{...e,from:A(e.from*u*t),to:A(e.to*u*t),duration:e.duration*o*t,attack:Math.max(5e-4,e.attack*a.attack),delay:r,gain:n};case`swish`:return{...e,low:A(e.low*u*t),peak:A(e.peak*u*t),duration:e.duration*o*t,delay:r,gain:n};case`crunch`:return{...e,from:A(e.from*u*t),to:A(e.to*u*t),duration:e.duration*o*t,delay:r,gain:n};case`metal`:return{...e,pitch:A(e.pitch*l*t),duration:e.duration*o*t,delay:r,gain:n};case`click`:return{...e,delay:r,gain:n}}}),f=Math.min(1,Math.max(0,i.reverb+a.reverb+s(.03)));return{id:`t${t+1}`,label:$e(n,r,o,c),recipe:{...i,reverb:j(f),layers:d.map(tt)}}})},j=e=>Math.round(e*1e3)/1e3,M=e=>Math.round(e),tt=e=>{let t={delay:j(e.delay),gain:j(e.gain)};switch(e.type){case`tone`:return{...e,...t,from:M(e.from),to:M(e.to),duration:j(e.duration),attack:Math.max(5e-4,j(e.attack))};case`noise`:return{...e,...t,from:M(e.from),to:M(e.to),duration:j(e.duration),attack:Math.max(5e-4,j(e.attack))};case`swish`:return{...e,...t,low:M(e.low),peak:M(e.peak),duration:j(e.duration)};case`crunch`:return{...e,...t,from:M(e.from),to:M(e.to),duration:j(e.duration)};case`metal`:return{...e,...t,pitch:M(e.pitch),duration:j(e.duration)};case`click`:return{...e,...t}}},nt=[`new`,`approved`,`changes`],rt={sounds:{}},it={status:`new`,note:``,takes:[]},N=e=>typeof e==`object`&&!!e&&!Array.isArray(e),at=e=>{let t;try{t=JSON.parse(e)}catch(e){return{ok:!1,error:`not JSON: ${e instanceof Error?e.message:String(e)}`}}if(!N(t)||!N(t.sounds))return{ok:!1,error:`a review is {"version": 1, "sounds": {"name": {"status": ..., "note": ...}}}`};let n=[],r={};for(let[e,i]of Object.entries(t.sounds)){if(!N(i)){n.push(`${e} must be an object`);continue}let t=nt.find(e=>e===i.status);i.status!==void 0&&t===void 0&&n.push(`${e}.status must be one of ${nt.join(`, `)}`);let a=[];i.takes!==void 0&&!Array.isArray(i.takes)&&n.push(`${e}.takes must be a list`);for(let[t,r]of(Array.isArray(i.takes)?i.takes:[]).entries()){if(!N(r)||typeof r.id!=`string`){n.push(`${e}.takes[${t}] needs an id`);continue}let i=ve(r.recipe,`${e}.takes[${t}].recipe`,n);i!==void 0&&a.push({id:r.id,label:typeof r.label==`string`?r.label:``,recipe:i})}let o=O.find(e=>e.id===i.toward)?.id;r[e]={status:t??`new`,note:typeof i.note==`string`?i.note:``,takes:a,toward:o,picked:typeof i.picked==`string`?i.picked:void 0}}return n.length>0?{ok:!1,error:n.join(`; `)}:{ok:!0,value:{sounds:r}}},ot=e=>{let t={};for(let[n,r]of Object.entries(e.sounds))t[n]={status:r.status,...r.note===``?{}:{note:r.note},...r.picked===void 0?{}:{picked:r.picked},...r.toward===void 0?{}:{toward:r.toward},...r.takes.length===0?{}:{takes:r.takes.map(e=>({id:e.id,label:e.label,recipe:Se(e.recipe)}))}};return JSON.stringify({version:1,sounds:t},null,2)+`
|
|
5
|
+
`},st=({samples:e,sampleRate:t})=>{let n=new Uint8Array(44+e.length*2),r=new DataView(n.buffer),i=(e,t)=>{for(let r=0;r<t.length;r++)n[e+r]=t.charCodeAt(r)};i(0,`RIFF`),r.setUint32(4,36+e.length*2,!0),i(8,`WAVE`),i(12,`fmt `),r.setUint32(16,16,!0),r.setUint16(20,1,!0),r.setUint16(22,1,!0),r.setUint32(24,t,!0),r.setUint32(28,t*2,!0),r.setUint16(32,2,!0),r.setUint16(34,16,!0),i(36,`data`),r.setUint32(40,e.length*2,!0);for(let t=0;t<e.length;t++){let n=Math.max(-1,Math.min(1,e[t]));r.setInt16(44+t*2,Math.round(n*32767),!0)}return n},ct=e=>{let t,n,r,i=()=>{r=void 0,e(),n!==void 0&&(r=requestAnimationFrame(i))},a=()=>{if(n===void 0)return;let{source:t}=n;n=void 0,t.onended=null,t.stop(),e()};return{play:(o,s)=>{a(),t??=new AudioContext,t.resume();let c=t.createBuffer(1,Math.max(1,o.samples.length),o.sampleRate);c.copyToChannel(o.samples,0);let l=t.createBufferSource();l.buffer=c,l.connect(t.destination);let u={source:l,id:s,start:t.currentTime,length:c.duration};l.onended=()=>{n?.source===l&&(n=void 0,e())},n=u,l.start(),r===void 0&&(r=requestAnimationFrame(i))},stop:a,playing:()=>n===void 0||t===void 0?void 0:{id:n.id,at:Math.min(n.length,t.currentTime-n.start),length:n.length}}},lt=e=>{let t=Math.min(2,window.devicePixelRatio||1),n=Math.max(1,Math.round(e.clientWidth*t)),r=Math.max(1,Math.round(e.clientHeight*t));return e.width!==n&&(e.width=n),e.height!==r&&(e.height=r),{width:n,height:r,ratio:t}},ut=[.05,.1,.25,.5,1,2,5],dt=(e,t)=>ut.find(n=>n*e>=t)??10,ft=(e,t,n)=>{let{width:r,height:i,ratio:a}=lt(e),o=e.getContext(`2d`);if(o===null)return;o.clearRect(0,0,r,i);let s=n.ruler===void 0?0:Math.round(18*a),c=s+(i-s)/2,l=(i-s)/2-2*a,u=r/Math.max(1e-6,n.seconds);if(n.ruler!==void 0){let e=dt(u,44*a);o.fillStyle=n.ruler.text,o.font=`${10*a}px "JetBrains Mono", ui-monospace, monospace`,o.textBaseline=`top`;for(let t=0;t*e<=n.seconds+1e-9;t++){let c=Math.round(t*e*u)+.5;o.fillStyle=n.ruler.line,o.fillRect(c,s,Math.max(1,a),i-s),t>0&&c<r-24*a&&(o.fillStyle=n.ruler.text,o.fillText(`${Math.round(t*e*100)/100}s`,c+3*a,3*a))}o.fillStyle=n.ruler.line,o.fillRect(0,Math.round(c),r,Math.max(1,a))}if(t!==void 0&&t.samples.length>0){let{samples:e,sampleRate:i}=t,s=Math.min(r,Math.ceil(e.length/i*u));o.fillStyle=n.color;for(let t=0;t<s;t++){let n=Math.floor(t/u*i|0),r=Math.min(e.length,Math.floor((t+1)/u*i|0)),s=0,d=0;for(let t=n;t<r;t++){let n=e[t];n<s&&(s=n),n>d&&(d=n)}let f=c-Math.min(1,d)*l,p=c-Math.max(-1,s)*l;o.fillRect(t,f,1,Math.max(a,p-f))}}if(n.playhead!==void 0){let e=Math.round(n.playhead*u);o.fillStyle=n.playheadColor??`#ff5a36`,o.fillRect(e,s,Math.max(1,Math.round(1.5*a)),i-s)}},pt=`https://ritmo.typeonce.dev/`,P={bankFile:``,name:``,bank:{sounds:{}},review:rt,selected:``,show:`all`,search:``,toward:`wider`,count:4,seed:1,layer:0,message:``,saving:`saved`,version:``,loaded:!1},F=new Map,mt={heavy:void 0,other:void 0},I=e=>P.bank.sounds[e],L=e=>P.review.sounds[e]??it,R=()=>Object.keys(P.bank.sounds),z=new Map,B=e=>{let t=JSON.stringify([P.bank.master??1,e]),n=z.get(t);return n===void 0&&(z.size>200&&z.clear(),n=Ge(e,{master:P.bank.master}),z.set(t,n)),n},V=e=>{let t=I(e);if(t===void 0)return;let n=qe(B(t));F.set(e,{measures:n,issues:Ye(t,n,t.heavy===!0?mt.heavy:mt.other)})},ht=()=>{let e=Ze(P.bank);mt=e.middle,F=new Map(e.sounds.map(e=>[e.name,{measures:e,issues:e.issues}]))},H=!1,U=!1,W,gt=async(e,t)=>{let n=await fetch(`/api/${e}`,{method:`PUT`,body:t}),r=await n.json();if(!n.ok)throw Error(typeof r==`object`&&r&&`error`in r?String(r.error):n.statusText);typeof r==`object`&&r&&`version`in r&&typeof r.version==`string`&&(P.version=r.version)},_t=async()=>{W=void 0;try{H&&(H=!1,await gt(`bank`,Te(P.bank))),U&&(U=!1,await gt(`review`,ot(P.review))),P.saving=H||U?`saving`:`saved`}catch(e){P.saving=`failed`,Q(`Not saved: ${e instanceof Error?e.message:String(e)}`)}Vt()},vt=e=>{e.bank===!0&&(H=!0),e.review===!0&&(U=!0),P.saving=`saving`,W!==void 0&&clearTimeout(W),W=setTimeout(()=>void _t(),400)},G=de(()=>`${Te(P.bank)}\u0000${ot(P.review)}`,e=>{let[t=``,n=``]=e.split(`\0`),r=xe(t),i=at(n);r.ok&&(P.bank=r.value),i.ok&&(P.review=i.value),V(P.selected),vt({bank:!0,review:!0}),$()},()=>Vt()),K=e=>{e.bank===!0&&V(P.selected),vt(e),G.schedule(),$()},q=(e,t)=>{P.bank={...P.bank,sounds:{...P.bank.sounds,[e]:t}}},yt=(e,t)=>{P.review={sounds:{...P.review.sounds,[e]:{...L(e),...t}}}},bt=e=>typeof e==`object`&&!!e&&`bankText`in e&&typeof e.bankText==`string`&&`version`in e&&typeof e.version==`string`,xt=async()=>{let e=await(await fetch(`/api/session`)).json();if(!bt(e))return Q(`The server sent no bank.`);let t=xe(e.bankText);if(!t.ok)return Q(`The bank has problems: ${t.error}`);let n=e.reviewText===null?void 0:at(e.reviewText);P.bank=t.value,P.review=n?.ok===!0?n.value:rt,P.bankFile=e.bank,P.name=e.name,P.version=e.version,I(P.selected)===void 0&&(P.selected=R()[0]??``),ht(),G.reset(),G.schedule(),P.loaded=!0,$()},St=async()=>{if(W===void 0&&P.saving!==`saving`)try{let e=await(await fetch(`/api/version`)).json();if(typeof e!=`object`||!e||!(`version`in e)||e.version===P.version)return;await xt(),Q(`The agent changed the bank: the page shows it now.`)}catch{}};setInterval(()=>void St(),1500);var J=ct(()=>Lt()),Ct=e=>{let t=I(e);t!==void 0&&J.play(B(t),e)},wt=e=>J.play(B(e.recipe),`take:${e.id}`),Tt=()=>{J.playing()===void 0?Ct(P.selected):J.stop()},Et=(e,t=!1)=>{if(I(e)===void 0)return;P.selected=e,P.layer=0,$();let n=Z.querySelector(`[data-sound="${CSS.escape(e)}"]`);n instanceof HTMLElement&&(n.offsetTop<Z.scrollTop?Z.scrollTop=n.offsetTop:n.offsetTop+n.offsetHeight>Z.scrollTop+Z.clientHeight&&(Z.scrollTop=n.offsetTop+n.offsetHeight-Z.clientHeight)),t&&Ct(e)},Dt=()=>R().filter(e=>{if(P.show!==`all`&&L(e).status!==P.show)return!1;let t=P.search.trim().toLowerCase();return t===``||e.toLowerCase().includes(t)||(I(e)?.about??``).toLowerCase().includes(t)}),Ot=e=>{let t=Dt();t.length!==0&&Et(t[(t.indexOf(P.selected)+e+t.length)%t.length],!0)},kt=(e,t)=>{let r=URL.createObjectURL(new Blob([st(e)],{type:`audio/wav`}));n(`a`,{attrs:{href:r,download:t}}).click(),setTimeout(()=>URL.revokeObjectURL(r),1e3)},At=document.getElementById(`app`),jt=n(`header`,{class:`topbar`}),Mt=n(`aside`,{class:`sounds`,attrs:{"aria-label":`Sounds`}}),Nt=n(`div`,{class:`sound-view`}),Y=n(`div`,{class:`floating playback`}),X=n(`div`,{class:`toast`,attrs:{role:`status`,"aria-live":`polite`}}),Pt=n(`aside`,{class:`inspector`,attrs:{"aria-label":`Settings`}}),Ft=n(`div`,{class:`stage`},Nt,Y,X);At.append(jt,n(`main`,{class:`layout lab`},Mt,n(`section`,{class:`workspace`},Ft),Pt));var It=[],Lt=()=>{for(let e of It)e.draw();Yt()},Rt={new:`To review`,approved:`Approved`,changes:`Changes asked`},zt=e=>R().filter(t=>L(t).status===e).length,Bt=e=>e<1?`${Math.round(e*1e3)} ms`:`${Math.round(e*100)/100} s`,Vt=()=>{let e=R().length;i(jt,n(`div`,{class:`topbar-start`},n(`div`,{class:`brand`},s(),n(`span`,{},`Sound lab`),n(`a`,{class:`by`,title:`Ritmo, the agent-first type safe ECS runtime`,attrs:{href:pt,target:`_blank`,rel:`noopener`}},`by `,n(`b`,{},`ritmo`))),P.loaded&&n(`div`,{class:`file`,title:P.bankFile},n(`span`,{class:`file-name`},P.name),n(`span`,{class:`file-meta`},`${e} sounds · ${zt(`approved`)} approved`))),n(`div`,{class:`topbar-center`},P.loaded&&l(P.show,[{id:`all`,label:`All ${e}`},{id:`new`,label:`To review ${zt(`new`)}`},{id:`changes`,label:`Changes ${zt(`changes`)}`},{id:`approved`,label:`Approved ${zt(`approved`)}`}],e=>{P.show=e,$()},{label:`Show`})),n(`div`,{class:`topbar-end`},n(`div`,{class:`button-group`},c(`Undo`,()=>G.undo(),{variant:`ghost`,icon:`undo`,iconOnly:!0,disabled:!G.canUndo(),title:`Undo (Ctrl/Cmd+Z)`}),c(`Redo`,()=>G.redo(),{variant:`ghost`,icon:`redo`,iconOnly:!0,disabled:!G.canRedo(),title:`Redo (Ctrl/Cmd+Shift+Z)`})),n(`span`,{class:`divider`}),n(`span`,{class:`save-state ${P.saving}`,title:`Changes are written to the bank and its review as you make them`},P.saving===`saved`?o(`check`):P.saving===`failed`?o(`alert`):null,P.saving===`saved`?`Saved`:P.saving===`saving`?`Saving…`:`Not saved`),c(`WAV`,()=>{let e=I(P.selected);e!==void 0&&kt(B(e),`${P.selected}.wav`)},{variant:`primary`,icon:`download`,disabled:I(P.selected)===void 0,title:`Download this sound as a WAV file`})))},Ht=e=>n(`span`,{class:`dot ${e}`,title:Rt[e]}),Ut=()=>{let e=Dt();i(Mt,n(`div`,{class:`sounds-head`},n(`label`,{class:`input search`},o(`search`),n(`input`,{props:{type:`search`,value:P.search},attrs:{placeholder:`Find a sound`,"aria-label":`Find a sound`,spellcheck:`false`},on:{input:e=>{let t=e.currentTarget;t instanceof HTMLInputElement&&(P.search=t.value,Wt())}}}))),Z),Wt(e)},Z=n(`div`,{class:`sound-rows`,attrs:{role:`listbox`}}),Wt=(e=Dt())=>{i(Z,e.length===0&&n(`p`,{class:`hint empty-list`},`No sound matches.`),e.map(e=>{let t=L(e),r=F.get(e),i=r?.issues.some(e=>e.severity===`error`)===!0,a=r?.issues.length??0;return n(`div`,{class:`sound-row${e===P.selected?` on`:``}`,attrs:{role:`option`,"aria-selected":String(e===P.selected),"data-sound":e,tabindex:0},on:{click:()=>Et(e,!0)}},Ht(t.status),n(`span`,{class:`sound-name`},e),a>0&&n(`span`,{class:`sound-flag ${i?`bad`:`warn`}`,title:r?.issues.map(e=>e.message).join(`
|
|
6
|
+
`)},o(`alert`)),t.takes.length>0&&t.picked===void 0&&n(`span`,{class:`sound-takes`,title:`${t.takes.length} takes to choose from`},`${t.takes.length}`),n(`span`,{class:`sound-length`},r===void 0?``:Bt(r.measures.duration)))}))},Gt={tone:`#7aa2f7`,noise:`#9aa0a9`,swish:`#4cc38a`,crunch:`#f0a843`,metal:`#c0a6ff`,click:`#ececef`},Kt=(e,t,r,i)=>{let a=n(`canvas`,{class:i.class});return It.push({canvas:a,draw:()=>{let n=J.playing();ft(a,e,{seconds:r,color:n?.id===t?`#f3f1ec`:`#bdbab3`,playhead:n?.id===t?n.at:void 0,ruler:i.ruler===!0?{line:`rgba(243, 241, 236, 0.07)`,text:`rgba(243, 241, 236, 0.4)`}:void 0})}}),a},qt=()=>{It=[];let e=P.selected,t=I(e);if(t===void 0){i(Nt,n(`div`,{class:`empty-card lab-empty`},n(`div`,{class:`empty-icon`},s()),n(`h2`,{},P.loaded?`No sounds in this bank`:`Opening the bank…`)));return}let r=B(t),a=r.samples.length/r.sampleRate,l=L(e),u=l.takes,d=u.map(e=>B(e.recipe)),f=Math.max(.2,a,...d.map(e=>e.samples.length/e.sampleRate)),p=t.tune!==void 0&&JSON.stringify(t.tune)!==JSON.stringify(g),ee=y(t).layers,m=F.get(e);i(Nt,n(`div`,{class:`sound-head`},n(`div`,{class:`sound-title`},n(`h1`,{},e),n(`span`,{class:`stage-pill ${l.status}`},Rt[l.status]),t.heavy===!0&&n(`span`,{class:`stage-pill`},`Heavy`),p&&n(`span`,{class:`stage-pill tuned`},`Tuned`)),t.about!==void 0&&n(`p`,{class:`sound-about`},t.about),m!==void 0&&n(`p`,{class:`sound-stats`},n(`span`,{},Bt(m.measures.duration)),n(`span`,{},`${m.measures.loudness} dBFS loud`),n(`span`,{},`peak ${m.measures.peak} dBFS`),n(`span`,{},`${Math.round(m.measures.brightness)} Hz bright`))),n(`div`,{class:`wave-card`},n(`div`,{class:`lane mix`},n(`span`,{class:`lane-label`,title:`Every layer together: the sound as the game plays it`},`all`),Kt(r,e,f,{ruler:!0,class:`wave big`})),n(`div`,{class:`lanes`},ee.map((e,t)=>{let r=e.delay,i=Ie(e);return n(`div`,{class:`lane${P.layer===t?` on`:``}`},n(`span`,{class:`lane-label`},n(`span`,{class:`lane-swatch`,attrs:{style:`--color:${Gt[e.type]}`}}),e.type),n(`div`,{class:`lane-track`},n(`button`,{class:`lane-bar`,title:`${Ee(e)}, gain ${e.gain}. Edit it under Layers.`,attrs:{type:`button`,style:`--color:${Gt[e.type]};left:${r/f*100}%;width:${Math.max(.6,(i-r)/f*100)}%;opacity:${.45+Math.min(1,e.gain)*.55}`},on:{click:()=>{P.layer=t,$()}}},n(`span`,{},Ee(e)))))}))),u.length>0&&n(`div`,{class:`takes`},n(`div`,{class:`takes-head`},n(`h2`,{},`Takes`),n(`span`,{},`${O.find(e=>e.id===(l.toward??`wider`))?.label??``} · pick the one that sounds right; it becomes the sound`)),n(`div`,{class:`take-grid`},u.map((e,t)=>{let r=l.picked===e.id,i=`take:${e.id}`;return n(`div`,{class:`take${r?` picked`:``}${J.playing()?.id===i?` playing`:``}`},n(`button`,{class:`take-play`,title:`Play take ${t+1} (${t+1})`,attrs:{type:`button`},on:{click:()=>wt(e)}},Kt(d[t],i,f,{class:`wave small`})),n(`div`,{class:`take-foot`},n(`span`,{class:`take-number`},String(t+1)),n(`span`,{class:`take-label`,title:e.label},e.label),r?n(`span`,{class:`take-picked`},o(`check`),`In use`):c(`Use`,()=>Jt(e),{small:!0,variant:`ghost`,class:`take-use`,title:`Make this take the sound`})))})))),requestAnimationFrame(Lt)},Jt=e=>{let t=I(P.selected);t!==void 0&&(q(P.selected,{...e.recipe,about:t.about,heavy:t.heavy,interval:t.interval,tune:void 0}),yt(P.selected,{picked:e.id}),Q(`Take ${e.id.slice(1)} is the sound now. Undo puts the old one back.`),K({bank:!0,review:!0}),wt(e))},Yt=()=>{let e=J.playing(),t=I(P.selected);if(Y.hidden=t===void 0,t===void 0)return;let r=B(t).samples.length/44100,a=e===void 0?`${P.selected} · ${Bt(r)}`:`${e.id.startsWith(`take:`)?`take ${e.id.slice(6)}`:e.id} · ${(Math.round(e.at*100)/100).toFixed(2)} s`;(Y.dataset.label!==a||Y.dataset.on!==String(e!==void 0))&&(Y.dataset.label=a,Y.dataset.on=String(e!==void 0),i(Y,c(`Previous sound`,()=>Ot(-1),{variant:`ghost`,icon:`previous`,iconOnly:!0,title:`Previous sound (↑)`}),c(e===void 0?`Play`:`Stop`,Tt,{variant:`ghost`,icon:e===void 0?`play`:`pause`,iconOnly:!0,title:`Play (Space)`}),c(`Next sound`,()=>Ot(1),{variant:`ghost`,icon:`next`,iconOnly:!0,title:`Next sound (↓)`}),n(`span`,{class:`divider`}),n(`span`,{class:`frame-counter`},a)))},Xt=[{id:`new`,label:`To review`,icon:`sparkle`,title:`Not reviewed yet`},{id:`approved`,label:`Approved`,icon:`check`,title:`It sounds right`},{id:`changes`,label:`Changes`,icon:`alert`,title:`Ask the agent for changes: say what in the note`}],Zt=e=>{let t=L(e);return h({id:`review`,title:`Review`,help:'Approve the sound, or ask for changes and say what in your own words ("too thin", "more crunch, less ring"). The agent reads every note with `ritmo-tools sound notes`.',onToggle:$},l(t.status,Xt,t=>{yt(e,{status:t}),K({review:!0})},{label:`Status`,class:`fill`}),n(`label`,{class:`input note`},n(`textarea`,{props:{value:t.note,rows:3},attrs:{placeholder:t.status===`changes`?`What should change?`:`A note for the agent (optional)`,"aria-label":`Note`},on:{input:t=>{let n=t.currentTarget;n instanceof HTMLTextAreaElement&&(yt(e,{note:n.value}),vt({review:!0}),G.schedule(),Vt())}}})))},Qt=e=>{let t=F.get(e),r=t?.issues.filter(e=>e.severity===`error`)??[];return h({id:`check`,title:`Check`,help:"What can be measured without listening: the same numbers `ritmo-tools sound check` gives the agent. Loudness is compared with the middle of the bank's sounds of the same kind (heavy, or the rest).",onToggle:$,action:t===void 0?void 0:ne(r.length>0?`Errors`:t.issues.length>0?`Warnings`:`Clean`,r.length>0?`bad`:t.issues.length>0?`warn`:`good`)},t===void 0?te(`Measuring…`):[u(`Length`,n(`span`,{class:`value`},Bt(t.measures.duration))),u(`Loudness`,n(`span`,{class:`value`},`${t.measures.loudness} dBFS`),`The loudest 50 ms`),u(`Peak`,n(`span`,{class:`value`},`${t.measures.peak} dBFS`),`The loudest sample: above 0 clips`),u(`Attack`,n(`span`,{class:`value`},`${t.measures.attack} ms`),`How long until it is heard`),u(`Brightness`,n(`span`,{class:`value`},`${Math.round(t.measures.brightness)} Hz`),`Low for thuds, high for hiss`),u(`Ending`,n(`span`,{class:`value`},`${t.measures.ending} dBFS`),`Loud here means a click at the end`),t.issues.length===0?re(`good`,`Nothing to fix.`):n(`ul`,{class:`issues`},t.issues.map(e=>n(`li`,{class:e.severity},o(`alert`),n(`span`,{},e.message))))])},$t=[{key:`pitch`,label:`Pitch`,min:-12,max:12,step:.5,show:e=>`${e>0?`+`:``}${e} st`,title:`Every frequency, in semitones`},{key:`length`,label:`Length`,min:.5,max:2,step:.05,show:e=>`×${e.toFixed(2)}`,title:`Every duration and delay`},{key:`brightness`,label:`Brightness`,min:-12,max:12,step:.5,show:e=>`${e>0?`+`:``}${e} st`,title:`The noise's filters only: brighter or darker, the notes stay`},{key:`echo`,label:`Echo`,min:-.5,max:.5,step:.01,show:e=>`${e>0?`+`:``}${Math.round(e*100)}%`,title:`More or less of the room`},{key:`volume`,label:`Volume`,min:-12,max:12,step:.5,show:e=>`${e>0?`+`:``}${e} dB`,title:`Louder or quieter`}],en=(e,t)=>{let n=t.tune??g;return h({id:`tune`,title:`Tune`,help:"Broad changes over the whole sound, kept apart from its layers in the bank (`tune`), so the agent sees exactly what you moved.",onToggle:$,action:c(`Reset`,()=>{q(e,{...t,tune:void 0}),K({bank:!0})},{small:!0,variant:`ghost`,disabled:JSON.stringify(n)===JSON.stringify(g)})},$t.map(t=>m(t.label,n[t.key],t,t.show,n=>{let r=I(e);r!==void 0&&(q(e,{...r,tune:{...r.tune??g,[t.key]:n}}),V(e),qt(),Yt())},()=>{K({bank:!0}),Ct(e)},t.title)))},tn=(e,t)=>{let n=O.find(e=>e.id===P.toward);return h({id:`takes`,title:`Takes`,help:"Other versions of this sound to choose from, nudged one way and each a little different. Listen, then Use the one that sounds right. The agent makes them too, with `ritmo-tools sound vary`.",onToggle:$,closed:!0},u(`Toward`,p(P.toward,O.map(e=>({id:e.id,label:e.label})),e=>{P.toward=e,$()},`Toward`)),n!==void 0&&te(n.hint),u(`Takes`,f(P.count,e=>{P.count=Math.round(e),$()},{min:1,max:12,step:1,label:`How many takes`})),c(`Make takes`,()=>{P.seed+=1;let r=et(t,P.toward,P.count,P.seed);yt(e,{takes:r,toward:P.toward,picked:void 0}),K({review:!0}),Q(`${r.length} takes, ${n?.label.toLowerCase()??``}: play them with 1 to ${Math.min(9,r.length)}.`)},{variant:`primary`,icon:`shuffle`,class:`block`}))},nn={tone:[{key:`from`,label:`From`,step:1,min:20,max:2e4,suffix:`Hz`},{key:`to`,label:`To`,step:1,min:20,max:2e4,suffix:`Hz`},{key:`duration`,label:`Duration`,step:.01,min:.001,max:5,suffix:`s`},{key:`attack`,label:`Attack`,step:.001,min:5e-4,max:2,suffix:`s`}],noise:[{key:`from`,label:`From`,step:1,min:20,max:2e4,suffix:`Hz`},{key:`to`,label:`To`,step:1,min:20,max:2e4,suffix:`Hz`},{key:`q`,label:`Q`,step:.1,min:1e-4,max:40},{key:`duration`,label:`Duration`,step:.01,min:.001,max:5,suffix:`s`},{key:`attack`,label:`Attack`,step:.001,min:5e-4,max:2,suffix:`s`}],swish:[{key:`low`,label:`Low`,step:1,min:20,max:2e4,suffix:`Hz`},{key:`peak`,label:`Peak`,step:1,min:20,max:2e4,suffix:`Hz`},{key:`peakAt`,label:`Peak at`,step:.05,min:0,max:1},{key:`q`,label:`Q`,step:.1,min:1e-4,max:40},{key:`duration`,label:`Duration`,step:.01,min:.001,max:5,suffix:`s`}],crunch:[{key:`from`,label:`From`,step:1,min:20,max:2e4,suffix:`Hz`},{key:`to`,label:`To`,step:1,min:20,max:2e4,suffix:`Hz`},{key:`duration`,label:`Duration`,step:.01,min:.001,max:5,suffix:`s`}],metal:[{key:`pitch`,label:`Pitch`,step:1,min:20,max:2e4,suffix:`Hz`},{key:`duration`,label:`Duration`,step:.01,min:.001,max:5,suffix:`s`}],click:[]},rn=(e,t)=>{let n=Reflect.get(e,t);return typeof n==`number`?n:0},an=(e,t)=>{let r=(n,r)=>{q(e,{...t,layers:t.layers.map((e,t)=>t===n?r:e)}),K({bank:!0})},i=Math.min(P.layer,t.layers.length-1),a=t.layers[i];return h({id:`layers`,title:`Layers`,help:`The recipe itself: the room, and each layer's numbers. Pick a layer here or on its bar over the stage.`,onToggle:$,closed:!0,action:n(`span`,{class:`meta`},`${t.layers.length}`)},m(`Room`,t.reverb,{min:0,max:1,step:.01},e=>`${Math.round(e*100)}%`,n=>{q(e,{...t,reverb:n}),V(e),qt()},()=>K({bank:!0}),`How much of the sound goes to the room`),ee(`Heavy`,t.heavy===!0,n=>{q(e,{...t,heavy:n?!0:void 0}),K({bank:!0})},`A heavy sound turns the others down for a moment in the game`),t.layers.length>0&&n(`div`,{class:`layer-tabs`},t.layers.map((e,t)=>n(`button`,{class:`layer-tab${t===i?` on`:``}`,attrs:{type:`button`,style:`--color:${Gt[e.type]}`},on:{click:()=>{P.layer=t,$()}}},n(`span`,{class:`lane-swatch`}),e.type))),a!==void 0&&ce(Ee(a),a.type===`tone`&&u(`Wave`,p(a.wave,fe.map(e=>({id:e,label:e})),e=>r(i,{...a,wave:e}),`Wave`)),a.type===`noise`&&u(`Filter`,p(a.filter,pe.map(e=>({id:e,label:e})),e=>r(i,{...a,filter:e}),`Filter`)),nn[a.type].map(e=>u(e.label,f(rn(a,e.key),t=>r(i,{...a,[e.key]:t}),{min:e.min,max:e.max,step:e.step,label:e.label,...e.suffix===void 0?{}:{suffix:e.suffix}}))),u(`Gain`,f(a.gain,e=>r(i,{...a,gain:e}),{min:0,max:4,step:.01,label:`Gain`})),u(`Delay`,f(a.delay,e=>r(i,{...a,delay:e}),{min:0,max:5,step:.01,label:`Delay`,suffix:`s`}))))},on=()=>n(`footer`,{class:`about`},n(`div`,{class:`ruler`,attrs:{"aria-hidden":`true`}}),n(`div`,{class:`about-head`},s(),`Sound lab, by Ritmo`),n(`p`,{},`Game sounds as JSON recipes. Agents check, render, and vary them from the command line: `,n(`code`,{},`ritmo-tools sound check`),`, `,n(`code`,{},`ritmo-tools sound vary`),`, `,n(`code`,{},`ritmo-tools sound notes`),`. What you pick, tune, and note here is saved to `,n(`code`,{},P.name),` and its review.`)),sn=()=>{let e=I(P.selected),t=Pt.scrollTop;i(Pt,e!==void 0&&[Zt(P.selected),Qt(P.selected),tn(P.selected,e),en(P.selected,e),an(P.selected,e)],on()),Pt.scrollTop=t},cn;function Q(e){P.message=e,cn!==void 0&&clearTimeout(cn),i(X,n(`span`,{},e),c(`Dismiss`,()=>X.classList.remove(`on`),{variant:`ghost`,icon:`close`,iconOnly:!0,small:!0})),X.classList.add(`on`),cn=setTimeout(()=>X.classList.remove(`on`),5e3)}var $=()=>{Vt(),Ut(),qt(),Yt(),sn()};window.addEventListener(`keydown`,e=>{let t=e.target;if(t instanceof HTMLInputElement&&t.type!==`range`&&t.type!==`checkbox`||t instanceof HTMLTextAreaElement||t instanceof HTMLSelectElement)return;let n=e.metaKey||e.ctrlKey;if(n&&e.key.toLowerCase()===`z`)e.preventDefault(),e.shiftKey?G.redo():G.undo();else if(n&&e.key.toLowerCase()===`y`)e.preventDefault(),G.redo();else if(e.key===` `)e.preventDefault(),Tt();else if(e.key===`ArrowDown`||e.key===`ArrowUp`)e.preventDefault(),Ot(e.key===`ArrowDown`?1:-1);else if(/^[1-9]$/.test(e.key)&&!n){let t=L(P.selected).takes[Number(e.key)-1];t!==void 0&&wt(t)}}),window.addEventListener(`resize`,()=>Lt()),$(),xt();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--ink:#0e1013;--paper:#f3f1ec;--tick:#ff5a36;--graphite:#262a31;--steel:#8a8f98;--line:#d9d5cc;--bg:#efede7;--surface:#faf9f6;--surface-2:#f1efea;--surface-3:#e6e3dc;--border:#e3e0d8;--border-strong:var(--line);--text:var(--ink);--text-2:#4b4f57;--muted:#80858e;--primary:var(--ink);--primary-hover:#262a31;--primary-text:var(--paper);--accent:var(--tick);--accent-soft:#ff5a361f;--accent-text:#fff;--good:#1b7a4e;--good-soft:#1b7a4e17;--warn:#a35a00;--warn-soft:#c478001c;--bad:#c23a2b;--bad-soft:#c23a2b17;--stage:var(--ink);--stage-dots:#f3f1ec0d;--floating:#262a31e6;--floating-border:#f3f1ec14;--shadow-float:0 8px 24px #00000052, 0 0 0 1px var(--floating-border);--focus:0 0 0 2px var(--surface), 0 0 0 4px #ff5a3673;--radius:6px;--radius-lg:10px;--control:28px;--font:"Inter", "InterVariable", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--mono:"JetBrains Mono", "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}@media (prefers-color-scheme:dark){:root{--bg:#0b0d10;--surface:var(--ink);--surface-2:#171a1f;--surface-3:var(--graphite);--border:#1f2328;--border-strong:#30353d;--text:var(--paper);--text-2:#bdbab3;--muted:var(--steel);--primary:var(--paper);--primary-hover:#fff;--primary-text:var(--ink);--accent-soft:#ff5a3629;--good:#4cc38a;--good-soft:#4cc38a1f;--warn:#f0a843;--warn-soft:#f0a8431f;--bad:#ff7a5c;--bad-soft:#ff7a5c1f;--stage:#08090b;--focus:0 0 0 2px var(--surface), 0 0 0 4px #ff5a368c;--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}}*{box-sizing:border-box}html,body{background:var(--bg);height:100%;color:var(--text);font:13px/1.45 var(--font);font-feature-settings:"cv11", "ss01";-webkit-font-smoothing:antialiased;-webkit-tap-highlight-color:transparent;margin:0}#app{flex-direction:column;height:100%;display:flex}[hidden]{display:none!important}h1,h2,h3,p{margin:0}:focus-visible{box-shadow:var(--focus);outline:none}.icon{flex:none;width:16px;height:16px;display:inline-flex}.icon svg{width:100%;height:100%}.btn{height:var(--control);border-radius:var(--radius);color:var(--text);font:500 13px/1 var(--font);white-space:nowrap;cursor:pointer;-webkit-user-select:none;user-select:none;background:0 0;border:1px solid #0000;justify-content:center;align-items:center;gap:6px;padding:0 10px;transition:background-color .12s,border-color .12s,color .12s;display:inline-flex}.btn .icon{width:15px;height:15px}.btn.small{gap:4px;height:24px;padding:0 8px;font-size:12px}.btn.small .icon{width:14px;height:14px}.btn.icon-only{width:var(--control);padding:0}.btn.small.icon-only{width:24px}.btn.block{width:100%}.btn.primary{background:var(--primary);color:var(--primary-text);box-shadow:0 1px 2px #0000001f}.btn.primary:hover:not(:disabled){background:var(--primary-hover)}.btn.secondary{background:var(--surface);border-color:var(--border-strong);box-shadow:0 1px 1px #00000008}.btn.secondary:hover:not(:disabled){background:var(--surface-2)}.btn.ghost{color:var(--text-2)}.btn.ghost:hover:not(:disabled),.btn.ghost.on,.btn.ghost[aria-pressed=true]{background:var(--surface-3);color:var(--text)}.btn:disabled{opacity:.45;cursor:default;box-shadow:none}.segmented{background:var(--surface-3);border-radius:8px;gap:2px;padding:2px;display:inline-flex}.segmented button{height:24px;color:var(--text-2);font:500 12.5px/1 var(--font);white-space:nowrap;cursor:pointer;background:0 0;border:0;border-radius:6px;justify-content:center;align-items:center;gap:6px;padding:0 10px;transition:background-color .12s,color .12s;display:inline-flex}.segmented button:hover{color:var(--text)}.segmented button.on{background:var(--surface);color:var(--text);box-shadow:0 1px 2px #0000001a,0 0 0 .5px #0000000f}.segmented.fill{width:100%;display:flex}.segmented.fill button{flex:1;padding:0 4px}.topbar{border-bottom:1px solid var(--border);background:var(--surface);grid-template-columns:minmax(0,1fr) auto minmax(0,1fr);align-items:center;gap:12px;height:48px;padding:0 12px 0 16px;display:grid}.topbar-start,.topbar-end{align-items:center;gap:8px;min-width:0;display:flex}.topbar-end{justify-content:flex-end}.brand{letter-spacing:-.01em;white-space:nowrap;align-items:center;gap:8px;font-size:13.5px;font-weight:600;display:flex}.mark{width:18px;height:18px;color:var(--text);flex:none;display:inline-flex}.mark svg{width:100%;height:100%}.by{color:var(--muted);font-family:var(--mono);font-size:11.5px;font-weight:500;text-decoration:none}.by:hover{color:var(--text)}.by b{color:var(--text-2);font-weight:600}.file{border-left:1px solid var(--border);align-items:baseline;gap:8px;min-width:0;padding-left:12px;font-size:12.5px;display:flex}.file-name{text-overflow:ellipsis;white-space:nowrap;color:var(--text);font-weight:500;overflow:hidden}.file-meta{color:var(--muted);font-variant-numeric:tabular-nums;flex:none}.button-group{gap:2px;display:flex}.divider{background:var(--border);width:1px;height:20px}.layout{flex:1;grid-template-columns:minmax(0,1fr) 320px;min-height:0;display:grid}.workspace{min-width:0;min-height:0;display:flex}.stage{background-color:var(--stage);background-image:radial-gradient(var(--stage-dots) 1px, transparent 1px);background-size:16px 16px;flex:1;min-height:0;position:relative;overflow:hidden}.stage.dropping:after{content:"";border:2px dashed var(--accent);border-radius:var(--radius-lg);background:var(--accent-soft);pointer-events:none;position:absolute;inset:12px}.view{touch-action:none;cursor:grab;width:100%;height:100%;position:absolute;inset:0}.floating{background:var(--floating);box-shadow:var(--shadow-float);-webkit-backdrop-filter:blur(12px);color:#ececef;border-radius:10px;align-items:center;gap:6px;padding:4px;display:flex;position:absolute}.tools{bottom:16px;left:50%;transform:translate(-50%)}.zoom{gap:0;bottom:16px;right:16px}.floating .segmented{background:0 0;padding:0}.floating .segmented+.segmented{border-left:1px solid #ffffff1a;border-radius:0;padding-left:6px}.floating .segmented button{color:#a8a8b2;height:30px;padding:0 10px}.floating .segmented button:hover{color:#fff;background:#ffffff0f}.floating .segmented button.on{background:var(--accent);color:#fff;box-shadow:none}.floating .btn.ghost{color:#a8a8b2}.floating .btn.ghost:hover:not(:disabled){color:#fff;background:#ffffff14}.zoom-value{text-align:center;font-variant-numeric:tabular-nums;color:#d4d4da;min-width:44px;font-size:12px}.toast{background:var(--floating);max-width:min(520px,100% - 32px);box-shadow:var(--shadow-float);-webkit-backdrop-filter:blur(12px);color:#ececef;opacity:0;pointer-events:none;border-radius:10px;align-items:center;gap:8px;padding:6px 6px 6px 14px;font-size:12.5px;transition:opacity .18s,transform .18s;display:flex;position:absolute;top:16px;left:50%;transform:translate(-50%,-8px)}.toast.on{opacity:1;pointer-events:auto;transform:translate(-50%)}.toast .btn.ghost{color:#a8a8b2}.toast .btn.ghost:hover{color:#fff;background:#ffffff14}.empty{pointer-events:none;place-items:center;padding:24px;display:grid;position:absolute;inset:0}.empty-card{color:#a8a8b2;text-align:center;pointer-events:auto;background:#ffffff05;border:1px dashed #ffffff29;border-radius:14px;flex-direction:column;align-items:center;gap:8px;width:min(420px,100%);padding:32px 28px 24px;display:flex}.empty-icon{color:#ececef;background:#ffffff0f;border-radius:12px;place-items:center;width:44px;height:44px;margin-bottom:4px;display:grid}.empty-icon .icon,.empty-icon .mark{width:22px;height:22px;color:var(--paper)}.empty-card h2{color:#fff;letter-spacing:-.01em;font-size:16px;font-weight:600}.empty-actions{gap:8px;margin-top:12px;display:flex}.empty-actions .btn{height:32px;padding:0 14px}.empty-actions .btn.secondary{color:#ececef;background:#ffffff0f;border-color:#ffffff1f}.empty-actions .btn.secondary:hover{background:#ffffff1a}.empty-note{color:#7c7c85;margin-top:12px;font-size:12px}.inspector{overscroll-behavior:contain;border-left:1px solid var(--border);background:var(--surface);overflow-y:auto}.section{border-bottom:1px solid var(--border)}.section-head{align-items:center;gap:4px;min-height:44px;padding:0 12px 0 8px;display:flex}.section-toggle{border-radius:var(--radius);min-width:0;height:32px;color:var(--text);font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;flex:1;align-items:center;gap:4px;padding:0 4px;display:flex}.section-toggle .icon{width:14px;height:14px;color:var(--muted);transition:transform .15s}.section.closed .section-toggle .icon{transform:rotate(-90deg)}.section-toggle h2{font-family:var(--mono);letter-spacing:-.01em;font-size:12px;font-weight:600}.section-action{align-items:center;gap:6px;display:flex}.section-body{flex-direction:column;gap:8px;padding:0 16px 16px;display:flex}.help{border-radius:var(--radius);background:var(--surface-2);color:var(--text-2);padding:10px 12px;font-size:12px;line-height:1.5}.group{border-top:1px solid var(--border);flex-direction:column;gap:8px;margin-top:4px;padding-top:12px;display:flex}.group h3{color:var(--muted);font-size:11.5px;font-weight:500}.stack{flex-direction:column;gap:8px;display:flex}.meta{color:var(--muted);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:12px}.hint{color:var(--muted);font-size:12px}.summary{color:var(--text-2);font-size:12.5px}.prop{min-height:var(--control);grid-template-columns:92px minmax(0,1fr);align-items:center;gap:8px;display:grid}.prop-label{color:var(--text-2);font-size:12.5px}.prop-control{align-items:center;gap:8px;min-width:0;display:flex}.prop-control>*{flex:1;min-width:0}.pair{grid-template-columns:1fr 1fr;gap:6px;display:grid}.combo{align-items:flex-start;gap:6px;display:flex}.combo>.input{flex:1}.inline-action{min-height:var(--control);color:var(--muted);justify-content:space-between;align-items:center;gap:8px;font-size:12px;display:flex}.input{min-width:0;height:var(--control);border-radius:var(--radius);background:var(--surface-2);border:1px solid #0000;align-items:center;transition:border-color .12s,background-color .12s;display:flex;position:relative}.input:hover{border-color:var(--border-strong)}.input:focus-within{border-color:var(--accent);background:var(--surface);box-shadow:0 0 0 3px var(--accent-soft)}.input input,.input select,.input textarea{width:100%;min-width:0;height:100%;color:var(--text);font:13px/1 var(--font);font-variant-numeric:tabular-nums;background:0 0;border:0;outline:none;flex:1;padding:0 8px}.input input::placeholder,.input textarea::placeholder{color:var(--muted)}.input:has(textarea){height:auto}.input textarea{resize:vertical;line-height:1.4;font-family:var(--mono);padding:6px 8px;font-size:12px}.affix{color:var(--muted);flex:none;padding-left:8px;font-size:11.5px;font-weight:500}.affix+input{padding-left:6px}input+.affix{padding:0 8px 0 0}.input.select select{appearance:none;cursor:pointer;padding-right:28px}.input.select .icon{width:14px;height:14px;color:var(--muted);pointer-events:none;position:absolute;right:8px}.switch-row{min-height:var(--control);color:var(--text-2);cursor:pointer;justify-content:space-between;align-items:center;gap:12px;font-size:12.5px;display:flex}.switch{appearance:none;background:var(--surface-3);width:30px;height:18px;box-shadow:inset 0 0 0 1px var(--border-strong);cursor:pointer;border-radius:999px;flex:none;margin:0;transition:background-color .15s,box-shadow .15s;position:relative}.switch:after{content:"";background:#fff;border-radius:50%;width:14px;height:14px;transition:transform .15s;position:absolute;top:2px;left:2px;box-shadow:0 1px 2px #00000040}.switch:checked{background:var(--primary);box-shadow:none}.switch:checked:after{background:var(--primary-text);transform:translate(12px)}.prop.slider output{min-width:36px;color:var(--text);font-variant-numeric:tabular-nums;text-align:right;flex:none;font-size:12px}.range{--fill:50%;appearance:none;cursor:pointer;background:0 0;height:20px;margin:0}.range::-webkit-slider-runnable-track{background:linear-gradient(to right, var(--primary) var(--fill), var(--surface-3) var(--fill));border-radius:999px;height:4px}.range::-moz-range-track{background:var(--surface-3);border-radius:999px;height:4px}.range::-moz-range-progress{background:var(--primary);border-radius:999px;height:4px}.range::-webkit-slider-thumb{appearance:none;background:#fff;border:0;border-radius:50%;width:14px;height:14px;margin-top:-5px;box-shadow:0 0 0 1px #0000001f,0 1px 3px #00000040}.range::-moz-range-thumb{background:#fff;border:0;border-radius:50%;width:14px;height:14px;box-shadow:0 0 0 1px #0000001f,0 1px 3px #00000040}.range:focus-visible{box-shadow:none}.range:focus-visible::-webkit-slider-thumb{box-shadow:var(--focus)}.badge{white-space:nowrap;border-radius:999px;align-items:center;gap:4px;height:20px;padding:0 7px;font-size:11.5px;font-weight:500;display:inline-flex}.badge .icon{width:12px;height:12px}.badge.neutral{background:var(--surface-3);color:var(--text-2)}.badge.accent{background:var(--accent-soft);color:var(--accent)}.badge.good{background:var(--good-soft);color:var(--good)}.badge.warn{background:var(--warn-soft);color:var(--warn)}.badge.bad{background:var(--bad-soft);color:var(--bad)}.callout{border-radius:var(--radius);gap:8px;padding:8px 10px;font-size:12px;line-height:1.45;display:flex}.callout .icon{width:14px;height:14px;margin-top:1px}.callout.good{background:var(--good-soft);color:var(--good)}.callout.warn{background:var(--warn-soft);color:var(--warn)}.callout.bad{background:var(--bad-soft);color:var(--bad)}.callout.neutral,.callout.accent{background:var(--surface-2);color:var(--text-2)}.issues{flex-direction:column;gap:6px;margin:0;padding:0;font-size:12px;line-height:1.45;list-style:none;display:flex}.issues li{color:var(--text-2);gap:8px;display:flex}.issues .icon{width:14px;height:14px;margin-top:1px}.issues .error .icon{color:var(--bad)}.issues .warning .icon{color:var(--warn)}.chip{background:var(--color);border-radius:4px;flex:none;width:18px;height:18px;display:inline-block;box-shadow:inset 0 0 0 1px #00000024}.chips{gap:4px;display:flex}.swatches{grid-template-columns:repeat(auto-fill,minmax(28px,1fr));gap:6px;display:grid}.swatch{aspect-ratio:1;background:var(--color);cursor:pointer;border:0;border-radius:6px;min-width:0;padding:0;transition:transform .1s,box-shadow .1s,opacity .1s;position:relative;box-shadow:inset 0 0 0 1px #00000024}.swatch:hover{transform:scale(1.06)}.swatch.unused{opacity:.38}.swatch.selected{opacity:1;box-shadow:inset 0 0 0 1px #00000024, 0 0 0 2px var(--surface), 0 0 0 4px var(--accent)}.swatch-lock{background:var(--surface);width:14px;height:14px;color:var(--text);box-shadow:0 0 0 1px var(--border-strong);border-radius:50%;place-items:center;display:grid;position:absolute;bottom:-3px;right:-3px}.swatch-lock .icon{width:9px;height:9px}.swatch.add{box-shadow:inset 0 0 0 1px var(--border-strong);color:var(--muted);background:0 0;place-items:center;display:grid}.swatch.add:hover{color:var(--text);background:var(--surface-2)}.swatch.add .icon{width:14px;height:14px}.color-input{opacity:0;cursor:pointer;border:0;width:100%;height:100%;padding:0;position:absolute;inset:0}.swatch-detail{background:var(--surface-2);border-radius:8px;align-items:center;gap:10px;padding:6px;display:flex}.swatch.big{flex:none;width:32px}.swatch.big:hover{transform:none}.swatch-info{flex-direction:column;flex:1;min-width:0;font-size:12px;display:flex}.swatch-info strong{font-family:var(--mono);text-transform:uppercase;font-size:12px;font-weight:500}.swatch-info span{color:var(--muted)}.proposals{grid-template-columns:repeat(auto-fill,minmax(32px,1fr));gap:10px 8px;padding:3px}.proposal{color:inherit;font:inherit;cursor:pointer;opacity:.4;background:0 0;border:0;flex-direction:column;gap:3px;padding:0;display:flex}.proposal.on{opacity:1}.proposal.on .swatch{box-shadow:inset 0 0 0 1px #00000024, 0 0 0 2px var(--surface), 0 0 0 3.5px var(--accent)}.share{color:var(--muted);text-align:center;font-variant-numeric:tabular-nums;font-size:10.5px}.list{flex-direction:column;margin:0 -8px;display:flex}.list-row{border-radius:var(--radius);align-items:center;gap:8px;min-height:32px;padding:0 8px;display:flex}.list-row:hover{background:var(--surface-2)}.arrow{color:var(--muted);font-size:12px}.list-text{color:var(--text-2);font-variant-numeric:tabular-nums;flex:1;font-size:12px}.preview-box{background:repeating-conic-gradient(var(--surface-3) 0 25%, var(--surface-2) 0 50%) 0 0 / 12px 12px;min-height:120px;box-shadow:inset 0 0 0 1px var(--border);border-radius:8px;place-items:center;padding:16px;display:grid}.preview{max-width:100%;image-rendering:pixelated}.export-main,.export-grid{grid-template-columns:1fr 1fr;gap:6px;display:grid}.intro{border-bottom:1px solid var(--border);flex-direction:column;gap:10px;padding:20px 16px;display:flex}.intro h2{font-size:14px;font-weight:600}.intro ol{color:var(--text-2);flex-direction:column;gap:6px;margin:0;padding-left:18px;font-size:12.5px;display:flex}.intro strong{color:var(--text);font-weight:500}@media (width<=900px){.hide-narrow,.file-meta{display:none}}@media (width<=760px){.topbar{grid-template-rows:48px auto;grid-template-columns:minmax(0,1fr) auto;row-gap:0;height:auto;padding:0 8px 0 12px}.topbar-center{grid-area:2/1/auto/-1;padding-bottom:8px}.topbar-center .segmented{width:100%;display:flex}.topbar-center .segmented button{flex:1}.topbar-center:empty,.file,.topbar-end .btn:not(.icon-only)>span:not(.icon){display:none}.topbar-end .btn:not(.icon-only){width:var(--control);padding:0}.layout{flex-direction:column;display:flex;overflow-y:auto}.workspace{flex:none;height:58vh;min-height:320px}.inspector{border-left:0;overflow:visible}.tools{bottom:10px;left:10px;transform:none}.tools .segmented button>span:not(.icon){display:none}.tools .segmented button{width:36px;padding:0}.zoom{bottom:10px;right:10px}.zoom .zoom-value{display:none}.section-head{min-height:48px}.switch-row,.prop{min-height:34px}}.floating .segmented+.segmented button.on{background:#ffffff24}.about{color:var(--muted);flex-direction:column;gap:8px;padding:16px 16px 24px;font-size:12px;line-height:1.5;display:flex}.about-head{color:var(--text);font-family:var(--mono);align-items:center;gap:8px;font-size:12px;font-weight:600;display:flex}.about-head .mark{width:16px;height:16px}.about code{background:var(--surface-2);color:var(--text-2);font-family:var(--mono);border-radius:4px;padding:1px 4px;font-size:11.5px}.about a{color:var(--text-2);text-decoration:underline;-webkit-text-decoration-color:var(--border-strong);text-decoration-color:var(--border-strong);text-underline-offset:2px}.about a:hover{color:var(--text);-webkit-text-decoration-color:var(--accent);text-decoration-color:var(--accent)}.ruler{background:linear-gradient(to right, var(--border-strong) 1px, transparent 1px) 0 0 / 8px 4px repeat-x, linear-gradient(to right, var(--muted) 1px, transparent 1px) 0 0 / 40px 6px repeat-x;opacity:.8;height:6px}@media (width<=760px){.by{display:none}}.paint-colors{border-left:1px solid #ffffff1a;flex-wrap:wrap;align-items:center;gap:4px;max-width:min(360px,60vw);padding-left:6px;display:flex}.paint-color{background:var(--color);cursor:pointer;border:0;border-radius:4px;width:20px;height:20px;padding:0;box-shadow:inset 0 0 0 1px #ffffff2e}.paint-color.on{box-shadow:0 0 0 2px var(--floating), 0 0 0 4px var(--accent)}.paint-hint{color:#a8a8b2;white-space:nowrap;padding:0 4px;font-size:12px}.topbar-start .segmented.kind{flex:none}.playback{max-width:calc(100% - 32px);bottom:16px;left:50%;transform:translate(-50%)}.playback .btn.ghost.icon-only{width:30px;height:30px}.playback .divider{background:#ffffff1f;height:20px}.frame-counter{text-align:center;font-variant-numeric:tabular-nums;color:#d4d4da;white-space:nowrap;min-width:92px;font-size:12px}.playback .fps .input{color:#ececef;background:#ffffff0f;border-color:#0000;width:84px;height:30px}.playback .fps .input input{color:#ececef}.box-actions{border-left:1px solid #ffffff1a;align-items:center;gap:2px;padding-left:8px;display:flex}.box-actions .paint-hint{margin-right:4px}.frame-list{flex-direction:column;margin:0 -8px;display:flex}.frame-row{border-radius:var(--radius);cursor:pointer;align-items:center;gap:8px;min-height:32px;padding:0 8px;display:flex}.frame-row:hover{background:var(--surface-2)}.frame-row.on{background:var(--accent-soft)}.frame-row.out .frame-number,.frame-row.out .list-text{opacity:.45;text-decoration:line-through}.frame-number{background:var(--surface-3);min-width:22px;height:20px;color:var(--text);font:600 11.5px/1 var(--font);font-variant-numeric:tabular-nums;border-radius:5px;place-items:center;display:inline-grid}.frame-row.on .frame-number{background:var(--accent);color:#fff}@media (width<=760px){.topbar-start .segmented.kind button{padding:0 8px}.topbar-start .segmented.kind button>span:not(.icon){display:none}.playback{flex-wrap:wrap;justify-content:center;bottom:10px;left:10px;right:10px;transform:none}.frame-counter{min-width:0}.stage:has(.playback:not([hidden])) .zoom{display:none}.box-actions .paint-hint{display:none}}.split-seam{width:100%;height:14px;color:var(--muted);font:500 11px/1 var(--font);cursor:pointer;opacity:.45;background:0 0;border:0;align-items:center;gap:6px;margin:0;padding:0 8px;display:flex}.split-seam:before,.split-seam:after{content:"";border-top:1px dashed;flex:1}.split-seam:hover,.split-seam:focus-visible{color:var(--accent);opacity:1}.split-seam .icon{order:1}.split-seam span{order:2}.split-seam:after{order:3}.layout.lab{grid-template-columns:232px minmax(0,1fr) 320px}.sounds{border-right:1px solid var(--border);background:var(--surface);flex-direction:column;min-height:0;display:flex}.sounds-head{border-bottom:1px solid var(--border);padding:10px 10px 8px}.input.search{padding-left:8px}.input.search .icon{width:14px;height:14px;color:var(--muted)}.input.search input::-webkit-search-cancel-button{display:none}.sound-rows{overscroll-behavior:contain;flex:1;padding:6px;position:relative;overflow-y:auto}.empty-list{padding:12px 8px}.sound-row{border-radius:var(--radius);cursor:pointer;-webkit-user-select:none;user-select:none;align-items:center;gap:8px;min-height:30px;padding:0 8px;display:flex}.sound-row:hover{background:var(--surface-2)}.sound-row.on{background:var(--accent-soft)}.sound-name{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:var(--mono);color:var(--text);flex:1;font-size:12px;overflow:hidden}.sound-length{color:var(--muted);font-variant-numeric:tabular-nums;font-size:11.5px}.sound-flag .icon{width:13px;height:13px}.sound-flag.warn{color:var(--warn)}.sound-flag.bad{color:var(--bad)}.sound-takes{background:var(--accent);color:#fff;border-radius:999px;place-items:center;min-width:18px;height:16px;padding:0 4px;font-size:10.5px;font-weight:600;display:inline-grid}.dot{border:1.5px solid var(--muted);border-radius:50%;flex:none;width:8px;height:8px}.dot.approved{border-color:var(--good);background:var(--good)}.dot.changes{border-color:var(--warn);background:var(--warn)}.save-state{color:var(--muted);white-space:nowrap;align-items:center;gap:4px;margin-right:6px;font-size:12px;display:inline-flex}.save-state .icon{width:14px;height:14px}.save-state.saved .icon{color:var(--good)}.save-state.failed{color:var(--bad)}.playback .frame-counter{padding-right:8px}.sound-view{color:#ececef;padding:28px 28px 96px;position:absolute;inset:0;overflow-y:auto}.sound-view>*{max-width:980px;margin-left:auto;margin-right:auto}.lab-empty{color:#ececef;margin-top:18vh}.sound-head{flex-direction:column;gap:6px;margin-bottom:18px;display:flex}.sound-title{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.sound-title h1{font-family:var(--mono);letter-spacing:-.02em;color:#f3f1ec;font-size:22px;font-weight:600}.stage-pill{color:#bdbab3;background:#f3f1ec14;border-radius:999px;align-items:center;height:20px;padding:0 8px;font-size:11.5px;font-weight:500;display:inline-flex}.stage-pill.approved{color:#4cc38a;background:#4cc38a29}.stage-pill.changes{color:#f0a843;background:#f0a84329}.stage-pill.tuned{color:#ff7a5c;background:#ff5a3629}.sound-about{color:#bdbab3;font-size:13.5px}.sound-stats{color:#8a8f98;font-family:var(--mono);font-variant-numeric:tabular-nums;flex-wrap:wrap;gap:4px 16px;font-size:11.5px;display:flex}.wave-card{border-radius:var(--radius-lg);box-shadow:0 0 0 1px var(--floating-border);background:#262a318c;flex-direction:column;gap:2px;padding:10px 12px 12px;display:flex}.lane{border-radius:6px;grid-template-columns:76px minmax(0,1fr);align-items:center;gap:10px;min-height:26px;display:grid}.lane.on .lane-label{color:#f3f1ec}.lane.on .lane-track{background:#f3f1ec0f}.lane-label{color:#8a8f98;font-family:var(--mono);align-items:center;gap:6px;font-size:11.5px;display:flex}.lane-swatch{background:var(--color);border-radius:2px;flex:none;width:8px;height:8px}.lane.mix{align-items:stretch;margin-bottom:8px}.lane.mix .lane-label{align-self:center;padding-top:18px}.wave{width:100%;display:block}.wave.big{height:170px}.wave.small{height:64px}.lanes{flex-direction:column;gap:2px;display:flex}.lane-track{border-radius:5px;height:24px;position:relative}.lane-bar{background:color-mix(in srgb, var(--color) 30%, transparent);min-width:4px;box-shadow:inset 2px 0 0 var(--color);color:#ececef;font:11px/1 var(--mono);white-space:nowrap;cursor:pointer;border:0;border-radius:4px;align-items:center;padding:0 6px;display:flex;position:absolute;top:3px;bottom:3px;overflow:hidden}.lane-bar span{text-overflow:ellipsis;overflow:hidden}.lane-bar:hover{background:color-mix(in srgb, var(--color) 45%, transparent)}.takes{margin-top:22px}.takes-head{flex-wrap:wrap;align-items:baseline;gap:4px 10px;margin-bottom:10px;display:flex}.takes-head h2{font-family:var(--mono);color:#f3f1ec;font-size:12px;font-weight:600}.takes-head span{color:#8a8f98;font-size:12px}.take-grid{grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:10px;display:grid}.take{border-radius:var(--radius-lg);box-shadow:0 0 0 1px var(--floating-border);background:#262a318c;flex-direction:column;display:flex;overflow:hidden}.take.playing{box-shadow:0 0 0 1px #f3f1ec4d}.take.picked{box-shadow:0 0 0 2px var(--accent)}.take-play{cursor:pointer;background:0 0;border:0;padding:10px 10px 4px;display:block}.take-play:hover{background:#f3f1ec0a}.take-foot{align-items:center;gap:8px;padding:6px 8px 8px 10px;display:flex}.take-number{color:#f3f1ec;min-width:20px;height:20px;font:600 11.5px/1 var(--font);background:#f3f1ec1a;border-radius:5px;place-items:center;display:inline-grid}.take.picked .take-number{background:var(--accent);color:#fff}.take-label{-webkit-line-clamp:2;color:#bdbab3;-webkit-box-orient:vertical;flex:1;min-width:0;font-size:11.5px;line-height:1.35;display:-webkit-box;overflow:hidden}.take .btn.ghost.take-use{color:#ececef;background:#f3f1ec14}.take .btn.ghost.take-use:hover{background:var(--accent);color:#fff}.take-picked{color:var(--accent);white-space:nowrap;align-items:center;gap:4px;font-size:11.5px;font-weight:600;display:inline-flex}.take-picked .icon{width:13px;height:13px}.value{color:var(--text);font-family:var(--mono);font-variant-numeric:tabular-nums;text-align:right;font-size:12px}.input.note textarea{min-height:64px;font-family:var(--font);font-size:12.5px}.layer-tabs{flex-wrap:wrap;gap:4px;display:flex}.layer-tab{border:1px solid var(--border);border-radius:var(--radius);background:var(--surface);height:24px;color:var(--text-2);font:11.5px/1 var(--mono);cursor:pointer;align-items:center;gap:6px;padding:0 8px;display:inline-flex}.layer-tab .lane-swatch{background:var(--color)}.layer-tab.on{border-color:var(--accent);background:var(--accent-soft);color:var(--text)}.group h3{font-family:var(--mono)}@media (width<=1100px){.layout.lab{grid-template-columns:200px minmax(0,1fr) 300px}}@media (width<=760px){.layout.lab{flex-direction:column;display:flex;overflow-y:auto}.layout.lab>*{flex:none}.layout.lab .workspace{order:1;height:62vh}.sounds{border-right:0;border-bottom:1px solid var(--border);order:2;max-height:40vh}.layout.lab .inspector{order:3}.sound-view{padding:16px 12px 88px}.lane{grid-template-columns:58px minmax(0,1fr);gap:6px}.wave.big{height:130px}.save-state{display:none}}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
6
|
+
<title>Sound lab · Ritmo</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
9
|
+
<link
|
|
10
|
+
rel="stylesheet"
|
|
11
|
+
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;600&display=swap"
|
|
12
|
+
/>
|
|
13
|
+
<script type="module" crossorigin src="./assets/index-Cjun5b92.js"></script>
|
|
14
|
+
<link rel="stylesheet" crossorigin href="./assets/index-DZm59wkQ.css">
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<div id="app"></div>
|
|
18
|
+
</body>
|
|
19
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typeonce/ritmo-tools",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Asset tools for games made with Ritmo, for agents: fixes AI-made pixel art and animation sheets onto a true pixel grid and a palette,
|
|
3
|
+
"version": "0.19.0",
|
|
4
|
+
"description": "Asset tools for games made with Ritmo, for agents: fixes AI-made pixel art and animation sheets onto a true pixel grid and a palette, keeps game sounds as JSON recipes, and writes game music as JSON songs built around a hum, each with a review page for a person",
|
|
5
5
|
"author": "Sandro Maglione",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
"ritmo",
|
|
19
19
|
"pixel-art",
|
|
20
20
|
"sprite-sheet",
|
|
21
|
+
"sound-effects",
|
|
22
|
+
"music",
|
|
21
23
|
"agents",
|
|
22
24
|
"cli"
|
|
23
25
|
],
|
|
@@ -25,6 +27,8 @@
|
|
|
25
27
|
"files": [
|
|
26
28
|
"dist",
|
|
27
29
|
"!dist/pixel/ui",
|
|
30
|
+
"!dist/sound/ui",
|
|
31
|
+
"!dist/music/ui",
|
|
28
32
|
"!dist/**/*.tsbuildinfo",
|
|
29
33
|
"README.md",
|
|
30
34
|
"CHANGELOG.md",
|
|
@@ -48,13 +52,15 @@
|
|
|
48
52
|
},
|
|
49
53
|
"devDependencies": {
|
|
50
54
|
"@types/pngjs": "^6.0.5",
|
|
51
|
-
"tsx": "^4.21.0"
|
|
55
|
+
"tsx": "^4.21.0",
|
|
56
|
+
"vite": "^8.0.3"
|
|
52
57
|
},
|
|
53
58
|
"engines": {
|
|
54
59
|
"node": ">=20"
|
|
55
60
|
},
|
|
56
61
|
"scripts": {
|
|
57
62
|
"build": "tsc -b tsconfig.json --pretty false",
|
|
58
|
-
"check": "tsc -b tsconfig.json --pretty false"
|
|
63
|
+
"check": "tsc -b tsconfig.json --pretty false",
|
|
64
|
+
"build:page": "vite build src/sound/ui --outDir ../../../dist/sound/page --emptyOutDir --base ./ --logLevel warn && vite build src/music/ui --outDir ../../../dist/music/page --emptyOutDir --base ./ --logLevel warn"
|
|
59
65
|
}
|
|
60
66
|
}
|