@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.
Files changed (131) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +304 -24
  3. package/dist/cli.js +5 -1
  4. package/dist/cli.js.map +1 -1
  5. package/dist/internal/version.d.ts +1 -1
  6. package/dist/internal/version.js +1 -1
  7. package/dist/music/cli/commands.d.ts +15 -0
  8. package/dist/music/cli/commands.d.ts.map +1 -0
  9. package/dist/music/cli/commands.js +180 -0
  10. package/dist/music/cli/commands.js.map +1 -0
  11. package/dist/music/cli/io.d.ts +11 -0
  12. package/dist/music/cli/io.d.ts.map +1 -0
  13. package/dist/music/cli/io.js +28 -0
  14. package/dist/music/cli/io.js.map +1 -0
  15. package/dist/music/cli/serve.d.ts +13 -0
  16. package/dist/music/cli/serve.d.ts.map +1 -0
  17. package/dist/music/cli/serve.js +168 -0
  18. package/dist/music/cli/serve.js.map +1 -0
  19. package/dist/music/core/audio.d.ts +22 -0
  20. package/dist/music/core/audio.d.ts.map +1 -0
  21. package/dist/music/core/audio.js +95 -0
  22. package/dist/music/core/audio.js.map +1 -0
  23. package/dist/music/core/check.d.ts +51 -0
  24. package/dist/music/core/check.d.ts.map +1 -0
  25. package/dist/music/core/check.js +113 -0
  26. package/dist/music/core/check.js.map +1 -0
  27. package/dist/music/core/instruments.d.ts +179 -0
  28. package/dist/music/core/instruments.d.ts.map +1 -0
  29. package/dist/music/core/instruments.js +531 -0
  30. package/dist/music/core/instruments.js.map +1 -0
  31. package/dist/music/core/listen.d.ts +32 -0
  32. package/dist/music/core/listen.d.ts.map +1 -0
  33. package/dist/music/core/listen.js +350 -0
  34. package/dist/music/core/listen.js.map +1 -0
  35. package/dist/music/core/midi.d.ts +4 -0
  36. package/dist/music/core/midi.d.ts.map +1 -0
  37. package/dist/music/core/midi.js +76 -0
  38. package/dist/music/core/midi.js.map +1 -0
  39. package/dist/music/core/render.d.ts +37 -0
  40. package/dist/music/core/render.d.ts.map +1 -0
  41. package/dist/music/core/render.js +204 -0
  42. package/dist/music/core/render.js.map +1 -0
  43. package/dist/music/core/review.d.ts +41 -0
  44. package/dist/music/core/review.d.ts.map +1 -0
  45. package/dist/music/core/review.js +139 -0
  46. package/dist/music/core/review.js.map +1 -0
  47. package/dist/music/core/song.d.ts +144 -0
  48. package/dist/music/core/song.d.ts.map +1 -0
  49. package/dist/music/core/song.js +291 -0
  50. package/dist/music/core/song.js.map +1 -0
  51. package/dist/music/page/assets/index-BfO_jOqP.js +4 -0
  52. package/dist/music/page/assets/index-Dw14kxkL.css +1 -0
  53. package/dist/music/page/index.html +19 -0
  54. package/dist/pixel/cli/commands.d.ts.map +1 -1
  55. package/dist/pixel/cli/commands.js +122 -46
  56. package/dist/pixel/cli/commands.js.map +1 -1
  57. package/dist/pixel/cli/io.d.ts +5 -1
  58. package/dist/pixel/cli/io.d.ts.map +1 -1
  59. package/dist/pixel/cli/io.js +16 -1
  60. package/dist/pixel/cli/io.js.map +1 -1
  61. package/dist/pixel/core/animation.d.ts +23 -8
  62. package/dist/pixel/core/animation.d.ts.map +1 -1
  63. package/dist/pixel/core/animation.js +41 -18
  64. package/dist/pixel/core/animation.js.map +1 -1
  65. package/dist/pixel/core/grid.d.ts +3 -2
  66. package/dist/pixel/core/grid.d.ts.map +1 -1
  67. package/dist/pixel/core/grid.js +14 -6
  68. package/dist/pixel/core/grid.js.map +1 -1
  69. package/dist/pixel/core/preview.d.ts +8 -3
  70. package/dist/pixel/core/preview.d.ts.map +1 -1
  71. package/dist/pixel/core/preview.js +39 -25
  72. package/dist/pixel/core/preview.js.map +1 -1
  73. package/dist/pixel/core/project.d.ts +27 -0
  74. package/dist/pixel/core/project.d.ts.map +1 -0
  75. package/dist/pixel/core/project.js +117 -0
  76. package/dist/pixel/core/project.js.map +1 -0
  77. package/dist/pixel/core/recipe.d.ts.map +1 -1
  78. package/dist/pixel/core/recipe.js +43 -6
  79. package/dist/pixel/core/recipe.js.map +1 -1
  80. package/dist/pixel/core/report.d.ts +21 -3
  81. package/dist/pixel/core/report.d.ts.map +1 -1
  82. package/dist/pixel/core/report.js +52 -22
  83. package/dist/pixel/core/report.js.map +1 -1
  84. package/dist/pixel/core/sheet.d.ts +68 -8
  85. package/dist/pixel/core/sheet.d.ts.map +1 -1
  86. package/dist/pixel/core/sheet.js +250 -32
  87. package/dist/pixel/core/sheet.js.map +1 -1
  88. package/dist/pixel/core/spritesheet.d.ts +43 -35
  89. package/dist/pixel/core/spritesheet.d.ts.map +1 -1
  90. package/dist/pixel/core/spritesheet.js +63 -59
  91. package/dist/pixel/core/spritesheet.js.map +1 -1
  92. package/dist/sound/cli/commands.d.ts +25 -0
  93. package/dist/sound/cli/commands.d.ts.map +1 -0
  94. package/dist/sound/cli/commands.js +137 -0
  95. package/dist/sound/cli/commands.js.map +1 -0
  96. package/dist/sound/cli/io.d.ts +24 -0
  97. package/dist/sound/cli/io.d.ts.map +1 -0
  98. package/dist/sound/cli/io.js +51 -0
  99. package/dist/sound/cli/io.js.map +1 -0
  100. package/dist/sound/cli/serve.d.ts +11 -0
  101. package/dist/sound/cli/serve.d.ts.map +1 -0
  102. package/dist/sound/cli/serve.js +147 -0
  103. package/dist/sound/cli/serve.js.map +1 -0
  104. package/dist/sound/core/measure.d.ts +67 -0
  105. package/dist/sound/core/measure.d.ts.map +1 -0
  106. package/dist/sound/core/measure.js +138 -0
  107. package/dist/sound/core/measure.js.map +1 -0
  108. package/dist/sound/core/recipe.d.ts +143 -0
  109. package/dist/sound/core/recipe.d.ts.map +1 -0
  110. package/dist/sound/core/recipe.js +292 -0
  111. package/dist/sound/core/recipe.js.map +1 -0
  112. package/dist/sound/core/render.d.ts +37 -0
  113. package/dist/sound/core/render.d.ts.map +1 -0
  114. package/dist/sound/core/render.js +317 -0
  115. package/dist/sound/core/render.js.map +1 -0
  116. package/dist/sound/core/review.d.ts +52 -0
  117. package/dist/sound/core/review.d.ts.map +1 -0
  118. package/dist/sound/core/review.js +89 -0
  119. package/dist/sound/core/review.js.map +1 -0
  120. package/dist/sound/core/takes.d.ts +29 -0
  121. package/dist/sound/core/takes.d.ts.map +1 -0
  122. package/dist/sound/core/takes.js +109 -0
  123. package/dist/sound/core/takes.js.map +1 -0
  124. package/dist/sound/core/wav.d.ts +4 -0
  125. package/dist/sound/core/wav.d.ts.map +1 -0
  126. package/dist/sound/core/wav.js +28 -0
  127. package/dist/sound/core/wav.js.map +1 -0
  128. package/dist/sound/page/assets/index-Cjun5b92.js +6 -0
  129. package/dist/sound/page/assets/index-DZm59wkQ.css +1 -0
  130. package/dist/sound/page/index.html +19 -0
  131. package/package.json +10 -4
@@ -0,0 +1,350 @@
1
+ /**
2
+ * Listening for the agent, which cannot hear: turns a hum, a whistle, a
3
+ * tapped beat, or a recorded tune into what a song is written in, a tempo,
4
+ * a key, and a melody of whole notes on a grid of beats.
5
+ *
6
+ * - The melody follows the strongest pitch moment to moment (YIN, the
7
+ * standard pitch tracker for one voice), cut into notes where the pitch
8
+ * jumps or the voice re-starts. Best on one voice: a hum, a whistle, a
9
+ * lead line; under a full band it follows whatever is loudest.
10
+ * - The tempo is the beat that lines up best with where notes and hits
11
+ * start.
12
+ * - The key is the major or minor key whose notes match how long each
13
+ * pitch sounds (Krumhansl's key profiles), from the whole spectrum, so it
14
+ * works on chords and full recordings too.
15
+ * - A voice that sits off pitch the whole way (most people hum a little
16
+ * flat or sharp) is heard in tune: the offset is measured over all its
17
+ * notes and taken out before they are named.
18
+ */
19
+ import { fft } from "../../sound/core/render.js";
20
+ import { nameOf } from "./song.js";
21
+ /** The rate pitch tracking runs at: plenty for voices (up to about 1.6 kHz) and quick. */
22
+ const RATE = 11025;
23
+ const WINDOW = 512;
24
+ const HOP = 64;
25
+ const LOWEST = 70;
26
+ const HIGHEST = 1600;
27
+ /** Low-passes and decimates to about `RATE`, so pitch tracking is cheap. */
28
+ const downsample = (samples, rate) => {
29
+ const factor = Math.max(1, Math.round(rate / RATE));
30
+ if (factor === 1)
31
+ return { samples, rate };
32
+ const out = new Float32Array(Math.floor(samples.length / factor));
33
+ // Two passes of a box filter as wide as the factor: enough to keep the octave above the cut out.
34
+ for (let i = 0; i < out.length; i++) {
35
+ let sum = 0;
36
+ const from = Math.max(0, i * factor - factor);
37
+ const to = Math.min(samples.length, i * factor + factor);
38
+ for (let j = from; j < to; j++)
39
+ sum += samples[j] * (1 - Math.abs(j - i * factor) / factor);
40
+ out[i] = sum / factor;
41
+ }
42
+ return { samples: out, rate: rate / factor };
43
+ };
44
+ /** YIN: the lag at which the signal best repeats itself is one period of its pitch. */
45
+ const track = (samples, rate) => {
46
+ const maxLag = Math.floor(rate / LOWEST);
47
+ const minLag = Math.floor(rate / HIGHEST);
48
+ const difference = new Float64Array(maxLag + 1);
49
+ const frames = [];
50
+ for (let start = 0; start + WINDOW + maxLag < samples.length; start += HOP) {
51
+ let energy = 0;
52
+ for (let i = 0; i < WINDOW; i++)
53
+ energy += samples[start + i] ** 2;
54
+ const rms = Math.sqrt(energy / WINDOW);
55
+ difference[0] = 1;
56
+ let running = 0;
57
+ let lag = -1;
58
+ for (let tau = 1; tau <= maxLag; tau++) {
59
+ let sum = 0;
60
+ for (let i = 0; i < WINDOW; i++) {
61
+ const d = samples[start + i] - samples[start + i + tau];
62
+ sum += d * d;
63
+ }
64
+ running += sum;
65
+ difference[tau] = running === 0 ? 1 : (sum * tau) / running;
66
+ }
67
+ for (let tau = minLag; tau <= maxLag; tau++) {
68
+ if (difference[tau] < 0.15) {
69
+ while (tau + 1 <= maxLag && difference[tau + 1] < difference[tau])
70
+ tau++;
71
+ lag = tau;
72
+ break;
73
+ }
74
+ }
75
+ if (lag < 0) {
76
+ // No dip under the threshold: the deepest one, if it is deep enough to be a pitch at all.
77
+ let best = minLag;
78
+ for (let tau = minLag; tau <= maxLag; tau++)
79
+ if (difference[tau] < difference[best])
80
+ best = tau;
81
+ if (difference[best] < 0.35)
82
+ lag = best;
83
+ }
84
+ let pitch = 0;
85
+ let clarity = 0;
86
+ if (lag > 0) {
87
+ // A parabola through the dip finds the period between samples.
88
+ const a = difference[lag - 1] ?? difference[lag];
89
+ const b = difference[lag];
90
+ const c = difference[lag + 1] ?? difference[lag];
91
+ const shift = a + c - 2 * b === 0 ? 0 : (a - c) / (2 * (a + c - 2 * b));
92
+ const frequency = rate / (lag + shift);
93
+ pitch = 69 + 12 * Math.log2(frequency / 440);
94
+ clarity = 1 - b;
95
+ }
96
+ frames.push({ time: (start + WINDOW / 2) / rate, rms, pitch, clarity });
97
+ }
98
+ return frames;
99
+ };
100
+ const median = (values) => {
101
+ const sorted = [...values].sort((a, b) => a - b);
102
+ return sorted.length === 0 ? 0 : sorted[Math.floor(sorted.length / 2)];
103
+ };
104
+ /** Cuts the pitch line into notes: a note holds while the pitch stays near its middle and the voice does not re-start. */
105
+ const segment = (frames, rate) => {
106
+ const loud = median(frames.map((frame) => frame.rms).filter((rms) => rms > 0)) * 0.3;
107
+ const peak = frames.reduce((max, frame) => Math.max(max, frame.rms), 0);
108
+ const floor = Math.max(loud, peak * 0.06);
109
+ const voiced = frames.map((frame) => frame.pitch > 0 && frame.clarity > 0.6 && frame.rms > floor);
110
+ // A median over a few frames removes one-frame octave slips.
111
+ const smooth = frames.map((_, i) => {
112
+ if (!voiced[i])
113
+ return 0;
114
+ const near = [];
115
+ for (let j = Math.max(0, i - 3); j <= Math.min(frames.length - 1, i + 3); j++)
116
+ if (voiced[j])
117
+ near.push(frames[j].pitch);
118
+ return median(near);
119
+ });
120
+ const notes = [];
121
+ const frameTime = HOP / rate;
122
+ let current;
123
+ const close = (end) => {
124
+ if (current !== undefined && end - current.start >= 0.07)
125
+ notes.push({ start: current.start, end, pitch: median(current.pitches) });
126
+ current = undefined;
127
+ };
128
+ for (let i = 0; i < frames.length; i++) {
129
+ const frame = frames[i];
130
+ if (!voiced[i]) {
131
+ // A short dip (a breath, a consonant) inside a held note is not its end.
132
+ if (current !== undefined && ++current.quiet * frameTime > 0.04)
133
+ close(frame.time - current.quiet * frameTime);
134
+ continue;
135
+ }
136
+ const pitch = smooth[i];
137
+ // A re-start: the voice comes back up sharply from a dip, as in "da-da" on one pitch.
138
+ const before = frames[i - 6]?.rms ?? 0;
139
+ const restart = current !== undefined && frame.rms > before * 2.2 && before < peak * 0.25;
140
+ if (current !== undefined && (Math.abs(pitch - median(current.pitches)) > 0.7 || restart))
141
+ close(frame.time);
142
+ if (current === undefined)
143
+ current = { start: frame.time, pitches: [], quiet: 0 };
144
+ current.pitches.push(pitch);
145
+ current.quiet = 0;
146
+ }
147
+ close(frames[frames.length - 1]?.time ?? 0);
148
+ return { notes, voiced };
149
+ };
150
+ /** Where sound starts: a rise in loudness, for beats without a pitch (taps, claps, drums). */
151
+ const hits = (frames) => {
152
+ const peak = frames.reduce((max, frame) => Math.max(max, frame.rms), 0);
153
+ const found = [];
154
+ for (let i = 4; i < frames.length; i++) {
155
+ const before = Math.min(...frames.slice(i - 4, i).map((frame) => frame.rms));
156
+ const now = frames[i].rms;
157
+ if (now > peak * 0.2 && now > before * 2.5 && (found.length === 0 || frames[i].time - found[found.length - 1] > 0.09))
158
+ found.push(frames[i].time);
159
+ }
160
+ return found;
161
+ };
162
+ /** The tempo whose beats (and half beats) land nearest the onsets, preferring the 80–140 range songs mostly sit in. */
163
+ const findTempo = (onsets) => {
164
+ if (onsets.length < 3)
165
+ return { tempo: 100, confidence: 0 };
166
+ const scores = [];
167
+ for (let tempo = 60; tempo <= 180; tempo += 0.5) {
168
+ const beat = 60 / tempo;
169
+ // Each onset scores by how near it falls to an eighth note of the grid, the grid starting on the first onset.
170
+ let score = 0;
171
+ for (const onset of onsets) {
172
+ const position = (onset - onsets[0]) / (beat / 2);
173
+ const off = Math.abs(position - Math.round(position));
174
+ score += Math.exp(-((off / 0.12) ** 2)) * (Math.round(position) % 2 === 0 ? 1 : 0.6);
175
+ }
176
+ // A finer grid always fits more onsets; this prior keeps it to tempos people hum and play at.
177
+ const prior = Math.exp(-0.5 * (Math.log2(tempo / 105) / 0.45) ** 2);
178
+ scores.push({ tempo, score: (score / onsets.length) * prior });
179
+ }
180
+ scores.sort((a, b) => b.score - a.score);
181
+ const best = scores[0];
182
+ // The runner-up that is not a near neighbour of the best.
183
+ const rival = scores.find((entry) => Math.abs(entry.tempo - best.tempo) > 4);
184
+ return { tempo: best.tempo, confidence: rival === undefined || best.score === 0 ? 1 : Math.max(0, Math.min(1, (best.score - rival.score) / best.score / 0.25)) };
185
+ };
186
+ // Krumhansl-Kessler key profiles: how strongly each note of the scale belongs to a major or a minor key.
187
+ const MAJOR = [6.35, 2.23, 3.48, 2.33, 4.38, 4.09, 2.52, 5.19, 2.39, 3.66, 2.29, 2.88];
188
+ const MINOR = [6.33, 2.68, 3.52, 5.38, 2.6, 3.53, 2.54, 4.75, 3.98, 2.69, 3.34, 3.17];
189
+ const NAMES = ["C", "C#", "D", "Eb", "E", "F", "F#", "G", "Ab", "A", "Bb", "B"];
190
+ const correlation = (a, b) => {
191
+ const meanA = a.reduce((s, x) => s + x, 0) / a.length;
192
+ const meanB = b.reduce((s, x) => s + x, 0) / b.length;
193
+ let top = 0;
194
+ let left = 0;
195
+ let right = 0;
196
+ for (let i = 0; i < a.length; i++) {
197
+ top += (a[i] - meanA) * (b[i] - meanB);
198
+ left += (a[i] - meanA) ** 2;
199
+ right += (b[i] - meanB) ** 2;
200
+ }
201
+ return left === 0 || right === 0 ? 0 : top / Math.sqrt(left * right);
202
+ };
203
+ /** How much each of the twelve pitch classes sounds, from the spectrum (chords and full recordings) and the heard notes. */
204
+ const chroma = (samples, rate, notes, tuning) => {
205
+ const classes = Array.from({ length: 12 }, () => 0);
206
+ const size = 4096;
207
+ const re = new Float64Array(size);
208
+ const im = new Float64Array(size);
209
+ for (let start = 0; start + size <= samples.length; start += size / 2) {
210
+ for (let i = 0; i < size; i++) {
211
+ re[i] = samples[start + i] * (0.5 - 0.5 * Math.cos((2 * Math.PI * i) / size));
212
+ im[i] = 0;
213
+ }
214
+ fft(re, im, false);
215
+ for (let bin = 1; bin < size / 2; bin++) {
216
+ const frequency = (bin * rate) / size;
217
+ if (frequency < 65 || frequency > 2100)
218
+ continue;
219
+ const midi = 69 + 12 * Math.log2(frequency / 440) - tuning;
220
+ classes[((Math.round(midi) % 12) + 12) % 12] += Math.sqrt(re[bin] ** 2 + im[bin] ** 2);
221
+ }
222
+ }
223
+ const total = classes.reduce((s, x) => s + x, 0) || 1;
224
+ const fromNotes = classesOf(notes, 0);
225
+ const noteTotal = fromNotes.reduce((s, x) => s + x, 0) || 1;
226
+ return classes.map((value, i) => value / total + fromNotes[i] / noteTotal);
227
+ };
228
+ const bestKey = (weights) => {
229
+ let best = { score: -2, key: "C major" };
230
+ for (let root = 0; root < 12; root++) {
231
+ const rotated = weights.map((_, i) => weights[(i + root) % 12]);
232
+ const major = correlation(rotated, MAJOR);
233
+ const minor = correlation(rotated, MINOR);
234
+ if (major > best.score)
235
+ best = { score: major, key: `${NAMES[root]} major` };
236
+ if (minor > best.score)
237
+ best = { score: minor, key: `${NAMES[root]} minor` };
238
+ }
239
+ return best;
240
+ };
241
+ /** How long each pitch class sounds in `notes`, their pitches moved by `shift` semitones. */
242
+ const classesOf = (notes, shift) => {
243
+ const classes = Array.from({ length: 12 }, () => 0);
244
+ for (const note of notes)
245
+ classes[((Math.round(note.pitch - shift) % 12) + 12) % 12] += note.end - note.start;
246
+ return classes;
247
+ };
248
+ /**
249
+ * How far off pitch the voice sits, in semitones: the average of how far
250
+ * each note is from its nearest semitone, weighted by how long it is held
251
+ * (a mean on a circle, so -0.45 and +0.45 are close). Near half a semitone
252
+ * the voice is between two notes everywhere: the smaller shift is kept,
253
+ * unless the other reading's notes fit a key clearly better.
254
+ */
255
+ const tuningOf = (notes) => {
256
+ if (notes.length < 3)
257
+ return 0;
258
+ let x = 0;
259
+ let y = 0;
260
+ for (const note of notes) {
261
+ const angle = 2 * Math.PI * (note.pitch - Math.round(note.pitch));
262
+ x += Math.cos(angle) * (note.end - note.start);
263
+ y += Math.sin(angle) * (note.end - note.start);
264
+ }
265
+ const offset = Math.atan2(y, x) / (2 * Math.PI);
266
+ if (Math.abs(offset) < 0.35)
267
+ return offset;
268
+ // The smaller shift, unless the other reading fits a key clearly better.
269
+ const other = offset - Math.sign(offset);
270
+ return bestKey(classesOf(notes, other)).score > bestKey(classesOf(notes, offset)).score + 0.05 ? other : offset;
271
+ };
272
+ /** The grid notes snap to: sixteenth notes. */
273
+ const GRID = 0.25;
274
+ /** Listens to audio (mono samples) and writes down what it heard. */
275
+ export const listen = (input, sampleRate, options = {}) => {
276
+ const { samples, rate } = downsample(input, sampleRate);
277
+ const frames = track(samples, rate);
278
+ const { notes: heardRaw, voiced } = segment(frames, rate);
279
+ // Heard in tune: the voice's own offset taken out of every pitch.
280
+ const tuning = tuningOf(heardRaw);
281
+ const raw = heardRaw.map((note) => ({ ...note, pitch: note.pitch - tuning }));
282
+ const onsets = raw.length >= 3 ? raw.map((note) => note.start) : hits(frames);
283
+ const found = options.tempo === undefined ? findTempo(onsets) : { tempo: options.tempo, confidence: 1 };
284
+ const tempo = Math.round(found.tempo);
285
+ const beat = 60 / tempo;
286
+ const start = onsets[0] ?? 0;
287
+ const toBeats = (seconds) => Math.max(0, Math.round((seconds - start) / beat / GRID) * GRID);
288
+ const notes = [];
289
+ for (const note of raw) {
290
+ const at = toBeats(note.start);
291
+ const end = Math.max(at + GRID, toBeats(note.end));
292
+ const previous = notes[notes.length - 1];
293
+ const pitch = nameOf(Math.round(note.pitch));
294
+ // Two notes snapped onto one grid step: the longer stays.
295
+ if (previous !== undefined && previous.at === at) {
296
+ if (end - at > previous.length)
297
+ notes[notes.length - 1] = { at, length: end - at, pitch };
298
+ continue;
299
+ }
300
+ if (previous !== undefined && previous.at + previous.length > at)
301
+ notes[notes.length - 1] = { ...previous, length: at - previous.at };
302
+ notes.push({ at, length: end - at, pitch });
303
+ }
304
+ // A sung note mostly stops a moment before the next: one that ends within a sixteenth of the next is held up to it.
305
+ for (let i = 0; i + 1 < notes.length; i++) {
306
+ const note = notes[i];
307
+ const gap = notes[i + 1].at - (note.at + note.length);
308
+ if (gap > 0 && gap <= GRID)
309
+ notes[i] = { ...note, length: note.length + gap };
310
+ }
311
+ const lastTime = frames[frames.length - 1]?.time ?? 0;
312
+ const length = Math.max(GRID, Math.ceil((lastTime - start) / beat / GRID) * GRID);
313
+ // The pitch line, an eighth of a beat a step, for drawing under the song.
314
+ const contour = [];
315
+ for (let b = 0; b < length; b += 0.125) {
316
+ const time = start + b * beat;
317
+ const near = frames.filter((frame, i) => voiced[i] && Math.abs(frame.time - time) < (beat * 0.125) / 2 + 0.003);
318
+ contour.push(near.length === 0 ? 0 : Math.round((median(near.map((frame) => frame.pitch)) - tuning) * 10) / 10);
319
+ }
320
+ const midis = raw.map((note) => Math.round(note.pitch));
321
+ return {
322
+ audio: options.audio,
323
+ tempo,
324
+ key: bestKey(chroma(samples, rate, raw, tuning)).key,
325
+ length,
326
+ notes,
327
+ contour,
328
+ offset: 0,
329
+ start: Math.round(start * 1000) / 1000,
330
+ found: {
331
+ seconds: Math.round((input.length / sampleRate) * 100) / 100,
332
+ voiced: Math.round((voiced.filter(Boolean).length / Math.max(1, voiced.length)) * 100) / 100,
333
+ range: midis.length === 0 ? undefined : [nameOf(Math.min(...midis)), nameOf(Math.max(...midis))],
334
+ tempoConfidence: Math.round(found.confidence * 100) / 100,
335
+ tuning: Math.round(tuning * 100)
336
+ }
337
+ };
338
+ };
339
+ /**
340
+ * Whether the listening found too few notes for its length to be one voice:
341
+ * a full recording (a band, a song played in the room) or a noisy one, which
342
+ * the melody tracker cannot follow. Hum the part alone instead.
343
+ */
344
+ export const isSparse = (heard) => heard.notes.length > 0 && heard.found.seconds > 5 && heard.notes.length / heard.found.seconds < 0.4;
345
+ /** The reference as the song keeps it (what was found is for the agent's eyes only). */
346
+ export const referenceOf = (heard) => {
347
+ const { found: _found, ...reference } = heard;
348
+ return reference;
349
+ };
350
+ //# sourceMappingURL=listen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"listen.js","sourceRoot":"","sources":["../../../src/music/core/listen.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { type Song } from "./song.ts";
2
+ /** The song as a `.mid` file. */
3
+ export declare const toMidi: (song: Song) => Uint8Array;
4
+ //# sourceMappingURL=midi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"midi.d.ts","sourceRoot":"","sources":["../../../src/music/core/midi.ts"],"names":[],"mappings":"AAOA,OAAO,EAAoB,KAAK,IAAI,EAAE,MAAM,WAAW,CAAA;AAwCvD,iCAAiC;AACjC,eAAO,MAAM,MAAM,SAAU,IAAI,KAAG,UAsCnC,CAAA"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * MIDI export: the song as a Standard MIDI File (format 1, one MIDI track
3
+ * per song track), the notes-only format every music program opens. The
4
+ * drum kit plays on channel 10 with General MIDI's drum numbers; every other
5
+ * track names its instrument's General MIDI program.
6
+ */
7
+ import { drums, instruments } from "./instruments.js";
8
+ import { midiOf, parseKey } from "./song.js";
9
+ const TICKS = 480;
10
+ const variable = (value) => {
11
+ const bytes = [value & 0x7f];
12
+ let rest = value >> 7;
13
+ while (rest > 0) {
14
+ bytes.unshift((rest & 0x7f) | 0x80);
15
+ rest >>= 7;
16
+ }
17
+ return bytes;
18
+ };
19
+ const chunk = (id, body) => [...[...id].map((c) => c.charCodeAt(0)), (body.length >>> 24) & 0xff, (body.length >>> 16) & 0xff, (body.length >>> 8) & 0xff, body.length & 0xff, ...body];
20
+ const text = (type, value) => {
21
+ const bytes = [...new TextEncoder().encode(value)];
22
+ return [0xff, type, ...variable(bytes.length), ...bytes];
23
+ };
24
+ const track = (events) => {
25
+ const sorted = [...events].sort((a, b) => a.tick - b.tick || a.order - b.order);
26
+ const body = [];
27
+ let last = 0;
28
+ for (const event of sorted) {
29
+ body.push(...variable(event.tick - last), ...event.bytes);
30
+ last = event.tick;
31
+ }
32
+ body.push(0, 0xff, 0x2f, 0);
33
+ return chunk("MTrk", body);
34
+ };
35
+ /** The song as a `.mid` file. */
36
+ export const toMidi = (song) => {
37
+ const tempo = Math.round(60_000_000 / song.tempo);
38
+ const key = parseKey(song.key);
39
+ // The key signature: sharps (positive) or flats on the circle of fifths, counted from the key's relative major.
40
+ const toMajor = { minor: 3, "harmonic minor": 3, "minor pentatonic": 3, blues: 3, dorian: 10, phrygian: 8, lydian: 7, mixolydian: 5 };
41
+ const minor = key !== undefined && toMajor[key.scale] === 3;
42
+ const fifths = key === undefined ? 0 : (((key.root + (toMajor[key.scale] ?? 0)) % 12) * 7) % 12;
43
+ const signed = fifths > 6 ? fifths - 12 : fifths;
44
+ const head = [
45
+ { tick: 0, order: 0, bytes: text(0x03, song.about ?? "Song") },
46
+ { tick: 0, order: 0, bytes: [0xff, 0x51, 3, (tempo >> 16) & 0xff, (tempo >> 8) & 0xff, tempo & 0xff] },
47
+ { tick: 0, order: 0, bytes: [0xff, 0x58, 4, song.beatsPerBar, 2, 24, 8] },
48
+ { tick: 0, order: 0, bytes: [0xff, 0x59, 2, signed & 0xff, minor ? 1 : 0] }
49
+ ];
50
+ // Channel 10 (9 from 0) is General MIDI's drums; the other instruments share the other fifteen.
51
+ const melodic = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15];
52
+ let next = 0;
53
+ const tracks = song.tracks.map((entry) => {
54
+ const isDrums = instruments[entry.instrument].kind === "drums";
55
+ const ch = isDrums ? 9 : melodic[next++ % melodic.length];
56
+ const events = [
57
+ { tick: 0, order: 0, bytes: text(0x03, entry.name) },
58
+ ...(isDrums ? [] : [{ tick: 0, order: 0, bytes: [0xc0 | ch, instruments[entry.instrument].program] }]),
59
+ { tick: 0, order: 0, bytes: [0xb0 | ch, 7, Math.min(127, Math.round(entry.volume * 100))] },
60
+ { tick: 0, order: 0, bytes: [0xb0 | ch, 10, Math.round((entry.pan + 1) * 63.5)] }
61
+ ];
62
+ for (const note of entry.notes) {
63
+ const number = isDrums ? drums.find((drum) => drum.name === note.pitch)?.gm : midiOf(note.pitch);
64
+ if (number === undefined)
65
+ continue;
66
+ const on = Math.round(note.at * TICKS);
67
+ const off = Math.max(on + 1, Math.round((note.at + note.length) * TICKS));
68
+ const velocity = Math.max(1, Math.round(note.velocity * 127));
69
+ events.push({ tick: on, order: 1, bytes: [0x90 | ch, number, velocity] }, { tick: off, order: 0, bytes: [0x80 | ch, number, 0] });
70
+ }
71
+ return track(events);
72
+ });
73
+ const header = chunk("MThd", [0, 1, ((tracks.length + 1) >> 8) & 0xff, (tracks.length + 1) & 0xff, (TICKS >> 8) & 0xff, TICKS & 0xff]);
74
+ return new Uint8Array([...header, ...track(head), ...tracks.flat()]);
75
+ };
76
+ //# sourceMappingURL=midi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"midi.js","sourceRoot":"","sources":["../../../src/music/core/midi.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Turns a song into stereo samples: every note played by its instrument,
3
+ * placed at its beat, panned, sent to a shared room, and mixed. The same
4
+ * bits come out in Node and in every browser (noise is seeded by the note
5
+ * itself), so the CLI measures exactly what the page plays.
6
+ *
7
+ * A looping song is rendered one pass long, with what rings past its end
8
+ * (a last chord, the room) folded back over its start: played on repeat, it
9
+ * has no seam.
10
+ */
11
+ import { type InstrumentId } from "./instruments.ts";
12
+ import { type Note, type Song } from "./song.ts";
13
+ export declare const SAMPLE_RATE = 44100;
14
+ export interface Stereo {
15
+ readonly sampleRate: number;
16
+ readonly left: Float32Array<ArrayBuffer>;
17
+ readonly right: Float32Array<ArrayBuffer>;
18
+ }
19
+ /** Notes already played, by what they are: a melody repeats, so most notes render once. Pass one cache to many renders (the page does). */
20
+ export type NoteCache = Map<string, Float32Array>;
21
+ export interface RenderOptions {
22
+ readonly sampleRate?: number;
23
+ /** Only these tracks (by index); muted tracks are left out unless named here. */
24
+ readonly tracks?: ReadonlyArray<number>;
25
+ readonly cache?: NoteCache | undefined;
26
+ }
27
+ /** A seeded generator of numbers in [0, 1) (mulberry32). */
28
+ export declare const seeded: (seed: number) => (() => number);
29
+ /** One note's samples, from the cache when it was played before. */
30
+ export declare const noteSamples: (instrument: InstrumentId, note: Pick<Note, "pitch" | "velocity">, held: number, rate: number, cache?: NoteCache) => Float32Array;
31
+ /** Renders the song (or some of its tracks) as stereo samples, at the song's master volume. */
32
+ export declare const renderSong: (song: Song, options?: RenderOptions) => Stereo;
33
+ /** The loudest sample in dBFS (0 is full scale). */
34
+ export declare const peakOf: ({ left, right }: Stereo) => number;
35
+ /** The loudness over the whole song as RMS in dBFS: how loud it feels next to other music. */
36
+ export declare const loudnessOf: ({ left, right }: Stereo) => number;
37
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/music/core/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAsC,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACxF,OAAO,EAA2C,KAAK,IAAI,EAAE,KAAK,IAAI,EAAc,MAAM,WAAW,CAAA;AAErG,eAAO,MAAM,WAAW,QAAQ,CAAA;AAEhC,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC,WAAW,CAAC,CAAA;IACxC,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC,WAAW,CAAC,CAAA;CAC1C;AAED,2IAA2I;AAC3I,MAAM,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;AAEjD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,iFAAiF;IACjF,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;CACvC;AAED,4DAA4D;AAC5D,eAAO,MAAM,MAAM,SAAU,MAAM,KAAG,CAAC,MAAM,MAAM,CASlD,CAAA;AAQD,oEAAoE;AACpE,eAAO,MAAM,WAAW,eAAgB,YAAY,QAAQ,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU,CAAC,QAAQ,MAAM,QAAQ,MAAM,UAAU,SAAS,KAAG,YAoB7I,CAAA;AAmGD,+FAA+F;AAC/F,eAAO,MAAM,UAAU,SAAU,IAAI,YAAW,aAAa,KAAQ,MAuCpE,CAAA;AAED,oDAAoD;AACpD,eAAO,MAAM,MAAM,oBAAqB,MAAM,KAAG,MAIhD,CAAA;AAED,8FAA8F;AAC9F,eAAO,MAAM,UAAU,oBAAqB,MAAM,KAAG,MAKpD,CAAA"}