@datamoc/mw_games 0.17.1 → 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.
@@ -0,0 +1,261 @@
1
+ import { midiLoopStart, noteToFrequency, scheduleMidi } from './Midi.js';
2
+ const DEFAULT_SAMPLE_RATE = 22050;
3
+ const DEFAULT_MASTER_GAIN = 0.35;
4
+ const DEFAULT_MAX_DURATION = 8 * 60;
5
+ /**
6
+ * Renders a parsed MIDI file to stereo PCM frames: one offline pass, then the result
7
+ * plays, loops, seeks and re-pitches like any decoded track. Deterministic: the same
8
+ * file and options always produce the same frames, so a test can render and assert
9
+ * without any audio hardware.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { parseMidi, renderMidiToBuffer } from '@datamoc/mw_games/audio';
14
+ *
15
+ * declare const midiBytes: ArrayBuffer;
16
+ *
17
+ * const rendered = renderMidiToBuffer(parseMidi(midiBytes));
18
+ * console.log(rendered.duration, rendered.loopStart, rendered.loopEnd);
19
+ * ```
20
+ */
21
+ export function renderMidiToBuffer(file, options = {}) {
22
+ const sampleRate = options.sampleRate ?? DEFAULT_SAMPLE_RATE;
23
+ const master = options.gain ?? DEFAULT_MASTER_GAIN;
24
+ const maxDuration = options.maxDuration ?? DEFAULT_MAX_DURATION;
25
+ if (!(sampleRate > 0))
26
+ throw new Error('render sample rate must be positive');
27
+ if (!(master >= 0))
28
+ throw new Error('render gain must not be negative');
29
+ if (!(maxDuration > 0))
30
+ throw new Error('render max duration must be positive');
31
+ const notes = scheduleMidi(file).filter((note) => note.time < maxDuration);
32
+ if (notes.length === 0)
33
+ throw new Error('MIDI file has no notes');
34
+ const tail = notes.reduce((latest, note) => Math.max(latest, note.time + note.duration), 0);
35
+ const duration = Math.min(maxDuration, tail + 1.5);
36
+ const frames = Math.max(1, Math.ceil(duration * sampleRate));
37
+ const left = new Float32Array(frames);
38
+ const right = new Float32Array(frames);
39
+ const loopStart = Math.min(midiLoopStart(file) ?? 0, Math.max(0, duration - 0.1));
40
+ const soundfont = options.soundfont ?? null;
41
+ const voiceCache = new Map();
42
+ for (const note of notes) {
43
+ const startFrame = Math.floor(note.time * sampleRate);
44
+ if (startFrame >= frames)
45
+ continue;
46
+ const peak = master * Math.max(0.02, (note.velocity / 127) * note.gain);
47
+ const voices = soundfont ? sampledVoices(soundfont, voiceCache, note) : [];
48
+ if (voices.length > 0) {
49
+ for (const voice of voices) {
50
+ renderSampled(left, right, sampleRate, note, voice, peak, startFrame, frames);
51
+ }
52
+ }
53
+ else {
54
+ renderSynth(left, right, sampleRate, note, peak, startFrame, frames);
55
+ }
56
+ }
57
+ return { sampleRate, left, right, loopStart, loopEnd: duration, duration };
58
+ }
59
+ /** layered SoundFont voices for a note, memoized: drums read bank 128 whatever CC0 says */
60
+ function sampledVoices(soundfont, cache, note) {
61
+ const bank = note.channel === 9 ? 128 : note.bank;
62
+ const key = `${bank}:${note.program}:${note.note}:${note.velocity >> 3}`;
63
+ let voices = cache.get(key);
64
+ if (!voices) {
65
+ voices = soundfont.voices(bank, note.program, note.note, note.velocity);
66
+ cache.set(key, voices);
67
+ }
68
+ return voices;
69
+ }
70
+ /** equal-power stereo gains for a pan in -1 (left) to 1 (right) */
71
+ function equalPower(pan) {
72
+ const angle = ((Math.max(-1, Math.min(1, pan)) + 1) * Math.PI) / 4;
73
+ return [Math.cos(angle), Math.sin(angle)];
74
+ }
75
+ /** one sampled voice, mixed additively into the render */
76
+ function renderSampled(left, right, sampleRate, note, voice, peak, startFrame, frameCount) {
77
+ const data = voice.sample.data;
78
+ if (data.length === 0)
79
+ return;
80
+ const ratio = 2 ** (((note.note - voice.rootKey) * 100 + voice.cents) / 1200);
81
+ const step = (ratio * voice.sample.rate) / sampleRate;
82
+ if (!(step > 0))
83
+ return;
84
+ const endTime = note.time + note.duration;
85
+ const attack = Math.max(0.002, voice.attack);
86
+ const decayEnd = attack + voice.hold + Math.min(voice.decay, 30);
87
+ const release = Math.max(0.03, Math.min(voice.release, 4));
88
+ const sampleSeconds = data.length / voice.sample.rate;
89
+ //Drums ignore note-off: the sample rings out. Melodic voices release at note-off.
90
+ const heldUntil = note.channel === 9
91
+ ? Math.max(endTime, note.time + Math.min(2, sampleSeconds))
92
+ : Math.max(endTime, note.time + attack);
93
+ const heldLen = heldUntil - note.time;
94
+ const level = peak * voice.gain;
95
+ const sustainLevel = level * voice.sustain;
96
+ const [gainLeft, gainRight] = equalPower(note.pan + voice.pan);
97
+ const looping = voice.loop && voice.sample.loopEnd > voice.sample.loopStart;
98
+ const loopSpan = voice.sample.loopEnd - voice.sample.loopStart;
99
+ const lastFrame = Math.min(frameCount, startFrame + Math.ceil((heldUntil + release + 0.02 - note.time) * sampleRate));
100
+ let position = 0;
101
+ for (let frame = startFrame; frame < lastFrame; frame++) {
102
+ const t = frame / sampleRate - note.time;
103
+ let envelope;
104
+ if (t < attack)
105
+ envelope = level * (t / attack);
106
+ else if (t < attack + voice.hold)
107
+ envelope = level;
108
+ else if (t < decayEnd) {
109
+ envelope =
110
+ level +
111
+ (sustainLevel - level) * ((t - attack - voice.hold) / Math.max(1e-6, decayEnd - attack - voice.hold));
112
+ }
113
+ else if (t < heldLen)
114
+ envelope = sustainLevel;
115
+ else if (t < heldLen + release)
116
+ envelope = sustainLevel * (1 - (t - heldLen) / release);
117
+ else
118
+ envelope = 0;
119
+ if (looping && position >= voice.sample.loopEnd) {
120
+ position = voice.sample.loopStart + ((position - voice.sample.loopStart) % loopSpan);
121
+ }
122
+ const floored = Math.floor(position);
123
+ const frac = Math.min(1, Math.max(0, position - floored));
124
+ let value;
125
+ if ((!looping && position >= data.length) || floored < 0)
126
+ value = 0;
127
+ else {
128
+ const first = data[Math.min(floored, data.length - 1)];
129
+ const second = data[Math.min(floored + 1, data.length - 1)];
130
+ value = first + (second - first) * frac;
131
+ }
132
+ left[frame] += value * envelope * gainLeft;
133
+ right[frame] += value * envelope * gainRight;
134
+ position += step;
135
+ }
136
+ }
137
+ /** program family to synthesis recipe: pianos pluck, organs hold, strings swell */
138
+ function recipeFor(program) {
139
+ if (program < 8)
140
+ return { wave: 'triangle', attack: 0.005, decay: 1.2, sustain: 0.25, release: 0.2, detune: 0 };
141
+ if (program < 16)
142
+ return { wave: 'sine', attack: 0.003, decay: 0.8, sustain: 0.1, release: 0.5, detune: 0 };
143
+ if (program < 24) {
144
+ return { wave: 'square', attack: 0.01, decay: 0.1, sustain: 0.7, release: 0.08, detune: 0, cutoff: 2500 };
145
+ }
146
+ if (program < 32) {
147
+ return { wave: 'sawtooth', attack: 0.004, decay: 0.6, sustain: 0.15, release: 0.15, detune: 0, cutoff: 3000 };
148
+ }
149
+ if (program < 40)
150
+ return { wave: 'triangle', attack: 0.005, decay: 0.4, sustain: 0.5, release: 0.1, detune: 0 };
151
+ if (program < 56) {
152
+ return { wave: 'sawtooth', attack: 0.12, decay: 0.2, sustain: 0.8, release: 0.3, detune: 6, cutoff: 3500 };
153
+ }
154
+ if (program < 80) {
155
+ return { wave: 'sawtooth', attack: 0.04, decay: 0.15, sustain: 0.75, release: 0.12, detune: 0, cutoff: 4500 };
156
+ }
157
+ if (program < 88) {
158
+ return { wave: 'square', attack: 0.01, decay: 0.1, sustain: 0.8, release: 0.1, detune: 0, cutoff: 3800 };
159
+ }
160
+ if (program < 104) {
161
+ return { wave: 'sawtooth', attack: 0.25, decay: 0.3, sustain: 0.7, release: 0.5, detune: 8, cutoff: 2200 };
162
+ }
163
+ return { wave: 'triangle', attack: 0.005, decay: 0.5, sustain: 0.2, release: 0.2, detune: 0 };
164
+ }
165
+ function oscillate(wave, phase) {
166
+ switch (wave) {
167
+ case 'sine':
168
+ return Math.sin(phase);
169
+ case 'square':
170
+ return Math.sin(phase) >= 0 ? 1 : -1;
171
+ case 'sawtooth': {
172
+ const cycles = phase / (2 * Math.PI);
173
+ return 2 * (cycles - Math.floor(cycles)) - 1;
174
+ }
175
+ case 'triangle': {
176
+ const cycles = phase / (2 * Math.PI);
177
+ return 4 * Math.abs(cycles - Math.floor(cycles) - 0.5) - 1;
178
+ }
179
+ }
180
+ }
181
+ /** attack-decay-sustain-release level multiplier, 0 to 1, for a note held `held` seconds */
182
+ function adsr(t, attack, decay, sustain, held, release) {
183
+ if (t < attack)
184
+ return t / attack;
185
+ if (t < attack + decay)
186
+ return 1 - (1 - sustain) * ((t - attack) / Math.max(1e-6, decay));
187
+ if (t < held)
188
+ return sustain;
189
+ if (t < held + release)
190
+ return sustain * (1 - (t - held) / release);
191
+ return 0;
192
+ }
193
+ /** deterministic pseudo-random sample in [-1, 1]: the same seed always gives the same noise */
194
+ function noiseAt(seed, index) {
195
+ let h = Math.imul(seed ^ index, 2654435761) >>> 0;
196
+ h = Math.imul(h ^ (h >>> 15), 2246822519) >>> 0;
197
+ h = Math.imul(h ^ (h >>> 13), 3266489917) >>> 0;
198
+ h ^= h >>> 16;
199
+ return ((h >>> 0) / 4294967295) * 2 - 1;
200
+ }
201
+ /** one built-in synth voice, or the drum kit on channel 10, mixed additively */
202
+ function renderSynth(left, right, sampleRate, note, peak, startFrame, frameCount) {
203
+ if (note.channel === 9) {
204
+ renderDrum(left, right, sampleRate, note, peak, startFrame, frameCount);
205
+ return;
206
+ }
207
+ const recipe = recipeFor(note.program);
208
+ const frequency = noteToFrequency(note.note) * 2 ** (recipe.detune / 1200);
209
+ const held = Math.max(note.duration, recipe.attack + 0.01);
210
+ const release = Math.max(0.02, recipe.release);
211
+ const lastFrame = Math.min(frameCount, startFrame + Math.ceil((held + release + 0.02) * sampleRate));
212
+ const [gainLeft, gainRight] = equalPower(note.pan);
213
+ const filtered = recipe.cutoff !== undefined;
214
+ const coefficient = filtered
215
+ ? 1 - Math.exp((-2 * Math.PI * Math.min(recipe.cutoff, frequency * 8 + 800)) / sampleRate)
216
+ : 0;
217
+ let lowpass = 0;
218
+ let phase = 0;
219
+ const phaseStep = (2 * Math.PI * frequency) / sampleRate;
220
+ for (let frame = startFrame; frame < lastFrame; frame++) {
221
+ const t = frame / sampleRate - note.time;
222
+ const value = oscillate(recipe.wave, phase);
223
+ phase += phaseStep;
224
+ lowpass = filtered ? lowpass + coefficient * (value - lowpass) : value;
225
+ const envelope = peak * adsr(t, recipe.attack, recipe.decay, recipe.sustain, held, release);
226
+ left[frame] += lowpass * envelope * gainLeft;
227
+ right[frame] += lowpass * envelope * gainRight;
228
+ }
229
+ }
230
+ /** channel 10: kicks thump, snares and hats hiss, keyed by General MIDI drum note */
231
+ function renderDrum(left, right, sampleRate, note, peak, startFrame, frameCount) {
232
+ const [gainLeft, gainRight] = equalPower(note.pan);
233
+ const seed = note.note * 7919 + startFrame;
234
+ if (note.note === 35 || note.note === 36 || note.note < 45) {
235
+ //Kick: a sine dropping 150 Hz to 45 Hz, decaying exponentially.
236
+ const level = peak * 1.4;
237
+ const lastFrame = Math.min(frameCount, startFrame + Math.ceil(0.27 * sampleRate));
238
+ let phase = 0;
239
+ for (let frame = startFrame; frame < lastFrame; frame++) {
240
+ const t = frame / sampleRate - note.time;
241
+ const frequency = t < 0.12 ? 150 * (45 / 150) ** (t / 0.12) : 45;
242
+ phase += (2 * Math.PI * frequency) / sampleRate;
243
+ const envelope = level * (0.001 / level) ** Math.min(1, t / 0.22);
244
+ left[frame] += Math.sin(phase) * envelope * gainLeft;
245
+ right[frame] += Math.sin(phase) * envelope * gainRight;
246
+ }
247
+ return;
248
+ }
249
+ const snare = note.note === 38 || note.note === 40;
250
+ const length = snare ? 0.16 : note.note >= 49 ? 0.5 : 0.06;
251
+ const level = peak * (snare ? 0.9 : 0.5);
252
+ const lastFrame = Math.min(frameCount, startFrame + Math.ceil((length + 0.02) * sampleRate));
253
+ for (let frame = startFrame; frame < lastFrame; frame++) {
254
+ const t = frame / sampleRate - note.time;
255
+ const envelope = level * (0.001 / level) ** Math.min(1, t / length);
256
+ const value = noiseAt(seed, frame - startFrame) * envelope;
257
+ left[frame] += value * gainLeft;
258
+ right[frame] += value * gainRight;
259
+ }
260
+ }
261
+ //# sourceMappingURL=MidiRender.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MidiRender.js","sourceRoot":"","sources":["../../src/audio/MidiRender.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,YAAY,EAAqC,MAAM,WAAW,CAAC;AA2C5G,MAAM,mBAAmB,GAAG,KAAK,CAAC;AAClC,MAAM,mBAAmB,GAAG,IAAI,CAAC;AACjC,MAAM,oBAAoB,GAAG,CAAC,GAAG,EAAE,CAAC;AAEpC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAc,EAAE,OAAO,GAAsB,EAAE;IACjF,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,mBAAmB,CAAC;IAC7D,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,IAAI,mBAAmB,CAAC;IACnD,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,oBAAoB,CAAC;IAChE,IAAI,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IAC9E,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACxE,IAAI,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAEhF,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC,CAAC;IAC3E,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;IAClE,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5F,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,GAAG,GAAG,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAC;IAC7D,MAAM,IAAI,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;IAEvC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,GAAG,CAAC,CAAC,CAAC;IAClF,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC;IAC5C,MAAM,UAAU,GAAG,IAAI,GAAG,EAA4B,CAAC;IACvD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC,CAAC;QACtD,IAAI,UAAU,IAAI,MAAM;YAAE,SAAS;QACnC,MAAM,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;QACxE,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,SAAS,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3E,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC5B,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;YAC/E,CAAC;QACF,CAAC;aAAM,CAAC;YACP,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;QACtE,CAAC;IACF,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AAC5E,CAAC;AAED,2FAA2F;AAC3F,SAAS,aAAa,CACrB,SAAoB,EACpB,KAAoC,EACpC,IAAmB;IAEnB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAClD,MAAM,GAAG,GAAG,GAAG,IAAI,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,EAAE,CAAC;IACzE,IAAI,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5B,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QACxE,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC;AAED,mEAAmE;AACnE,SAAS,UAAU,CAAC,GAAW;IAC9B,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IACnE,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;AAC3C,CAAC;AAED,0DAA0D;AAC1D,SAAS,aAAa,CACrB,IAAkB,EAClB,KAAmB,EACnB,UAAkB,EAClB,IAAmB,EACnB,KAAqB,EACrB,IAAY,EACZ,UAAkB,EAClB,UAAkB;IAElB,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC;IAC/B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAC9B,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IAC9E,MAAM,IAAI,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC;IACtD,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;QAAE,OAAO;IAExB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7C,MAAM,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACjE,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;IAC3D,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC;IACtD,kFAAkF;IAClF,MAAM,SAAS,GACd,IAAI,CAAC,OAAO,KAAK,CAAC;QACjB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC;QAC3D,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC;IACtC,MAAM,KAAK,GAAG,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IAChC,MAAM,YAAY,GAAG,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC;IAC3C,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;IAE/D,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC;IAC5E,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC;IAC/D,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CACzB,UAAU,EACV,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,SAAS,GAAG,OAAO,GAAG,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,CAC7E,CAAC;IACF,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,KAAK,IAAI,KAAK,GAAG,UAAU,EAAE,KAAK,GAAG,SAAS,EAAE,KAAK,EAAE,EAAE,CAAC;QACzD,MAAM,CAAC,GAAG,KAAK,GAAG,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC;QACzC,IAAI,QAAgB,CAAC;QACrB,IAAI,CAAC,GAAG,MAAM;YAAE,QAAQ,GAAG,KAAK,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;aAC3C,IAAI,CAAC,GAAG,MAAM,GAAG,KAAK,CAAC,IAAI;YAAE,QAAQ,GAAG,KAAK,CAAC;aAC9C,IAAI,CAAC,GAAG,QAAQ,EAAE,CAAC;YACvB,QAAQ;gBACP,KAAK;oBACL,CAAC,YAAY,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACxG,CAAC;aAAM,IAAI,CAAC,GAAG,OAAO;YAAE,QAAQ,GAAG,YAAY,CAAC;aAC3C,IAAI,CAAC,GAAG,OAAO,GAAG,OAAO;YAAE,QAAQ,GAAG,YAAY,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC;;YACnF,QAAQ,GAAG,CAAC,CAAC;QAElB,IAAI,OAAO,IAAI,QAAQ,IAAI,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACjD,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,QAAQ,CAAC,CAAC;QACtF,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC;QAC1D,IAAI,KAAa,CAAC;QAClB,IAAI,CAAC,CAAC,OAAO,IAAI,QAAQ,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,OAAO,GAAG,CAAC;YAAE,KAAK,GAAG,CAAC,CAAC;aAC/D,CAAC;YACL,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;YACvD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,GAAG,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;YAC5D,KAAK,GAAG,KAAK,GAAG,CAAC,MAAM,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC;QACzC,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAC;QAC3C,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC;QAC7C,QAAQ,IAAI,IAAI,CAAC;IAClB,CAAC;AACF,CAAC;AAgBD,mFAAmF;AACnF,SAAS,SAAS,CAAC,OAAe;IACjC,IAAI,OAAO,GAAG,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IAChH,IAAI,OAAO,GAAG,EAAE;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IAC5G,IAAI,OAAO,GAAG,EAAE,EAAE,CAAC;QAClB,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAC3G,CAAC;IACD,IAAI,OAAO,GAAG,EAAE,EAAE,CAAC;QAClB,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAC/G,CAAC;IACD,IAAI,OAAO,GAAG,EAAE;QAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IAChH,IAAI,OAAO,GAAG,EAAE,EAAE,CAAC;QAClB,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAC5G,CAAC;IACD,IAAI,OAAO,GAAG,EAAE,EAAE,CAAC;QAClB,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAC/G,CAAC;IACD,IAAI,OAAO,GAAG,EAAE,EAAE,CAAC;QAClB,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAC1G,CAAC;IACD,IAAI,OAAO,GAAG,GAAG,EAAE,CAAC;QACnB,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAC5G,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;AAC/F,CAAC;AAED,SAAS,SAAS,CAAC,IAAe,EAAE,KAAa;IAChD,QAAQ,IAAI,EAAE,CAAC;QACd,KAAK,MAAM;YACV,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACxB,KAAK,QAAQ;YACZ,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACtC,KAAK,UAAU,EAAE,CAAC;YACjB,MAAM,MAAM,GAAG,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;YACrC,OAAO,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC;QAC9C,CAAC;QACD,KAAK,UAAU,EAAE,CAAC;YACjB,MAAM,MAAM,GAAG,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;YACrC,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5D,CAAC;IACF,CAAC;AACF,CAAC;AAED,4FAA4F;AAC5F,SAAS,IAAI,CAAC,CAAS,EAAE,MAAc,EAAE,KAAa,EAAE,OAAe,EAAE,IAAY,EAAE,OAAe;IACrG,IAAI,CAAC,GAAG,MAAM;QAAE,OAAO,CAAC,GAAG,MAAM,CAAC;IAClC,IAAI,CAAC,GAAG,MAAM,GAAG,KAAK;QAAE,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IAC1F,IAAI,CAAC,GAAG,IAAI;QAAE,OAAO,OAAO,CAAC;IAC7B,IAAI,CAAC,GAAG,IAAI,GAAG,OAAO;QAAE,OAAO,OAAO,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC;IACpE,OAAO,CAAC,CAAC;AACV,CAAC;AAED,+FAA+F;AAC/F,SAAS,OAAO,CAAC,IAAY,EAAE,KAAa;IAC3C,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;IAClD,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;IAChD,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;IAChD,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;IACd,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AACzC,CAAC;AAED,gFAAgF;AAChF,SAAS,WAAW,CACnB,IAAkB,EAClB,KAAmB,EACnB,UAAkB,EAClB,IAAmB,EACnB,IAAY,EACZ,UAAkB,EAClB,UAAkB;IAElB,IAAI,IAAI,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;QACxB,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;QACxE,OAAO;IACR,CAAC;IACD,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC3E,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC3D,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,OAAO,GAAG,IAAI,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;IACrG,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnD,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC;IAC7C,MAAM,WAAW,GAAG,QAAQ;QAC3B,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAO,EAAE,SAAS,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,UAAU,CAAC;QAC3F,CAAC,CAAC,CAAC,CAAC;IACL,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,SAAS,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,GAAG,SAAS,CAAC,GAAG,UAAU,CAAC;IACzD,KAAK,IAAI,KAAK,GAAG,UAAU,EAAE,KAAK,GAAG,SAAS,EAAE,KAAK,EAAE,EAAE,CAAC;QACzD,MAAM,CAAC,GAAG,KAAK,GAAG,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC;QACzC,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC5C,KAAK,IAAI,SAAS,CAAC;QACnB,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,OAAO,GAAG,WAAW,GAAG,CAAC,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QACvE,MAAM,QAAQ,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;QAC5F,IAAI,CAAC,KAAK,CAAC,IAAI,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;QAC7C,KAAK,CAAC,KAAK,CAAC,IAAI,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;IAChD,CAAC;AACF,CAAC;AAED,qFAAqF;AACrF,SAAS,UAAU,CAClB,IAAkB,EAClB,KAAmB,EACnB,UAAkB,EAClB,IAAmB,EACnB,IAAY,EACZ,UAAkB,EAClB,UAAkB;IAElB,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,IAAI,GAAG,UAAU,CAAC;IAC3C,IAAI,IAAI,CAAC,IAAI,KAAK,EAAE,IAAI,IAAI,CAAC,IAAI,KAAK,EAAE,IAAI,IAAI,CAAC,IAAI,GAAG,EAAE,EAAE,CAAC;QAC5D,gEAAgE;QAChE,MAAM,KAAK,GAAG,IAAI,GAAG,GAAG,CAAC;QACzB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC;QAClF,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,IAAI,KAAK,GAAG,UAAU,EAAE,KAAK,GAAG,SAAS,EAAE,KAAK,EAAE,EAAE,CAAC;YACzD,MAAM,CAAC,GAAG,KAAK,GAAG,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC;YACzC,MAAM,SAAS,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACjE,KAAK,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,EAAE,GAAG,SAAS,CAAC,GAAG,UAAU,CAAC;YAChD,MAAM,QAAQ,GAAG,KAAK,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;YAClE,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,QAAQ,GAAG,QAAQ,CAAC;YACrD,KAAK,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,QAAQ,GAAG,SAAS,CAAC;QACxD,CAAC;QACD,OAAO;IACR,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,KAAK,EAAE,IAAI,IAAI,CAAC,IAAI,KAAK,EAAE,CAAC;IACnD,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3D,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;IAC7F,KAAK,IAAI,KAAK,GAAG,UAAU,EAAE,KAAK,GAAG,SAAS,EAAE,KAAK,EAAE,EAAE,CAAC;QACzD,MAAM,CAAC,GAAG,KAAK,GAAG,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC;QACzC,MAAM,QAAQ,GAAG,KAAK,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC;QACpE,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,KAAK,GAAG,UAAU,CAAC,GAAG,QAAQ,CAAC;QAC3D,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,QAAQ,CAAC;QAChC,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,SAAS,CAAC;IACnC,CAAC;AACF,CAAC"}
@@ -0,0 +1,98 @@
1
+ import { type MidiFile } from './Midi.ts';
2
+ /** one mono sample: raw frames plus its loop region in frames and native rate */
3
+ export interface SoundFontSample {
4
+ data: Float32Array;
5
+ rate: number;
6
+ loopStart: number;
7
+ loopEnd: number;
8
+ }
9
+ /** how one layered voice plays its sample for a note */
10
+ export interface SoundFontVoice {
11
+ sample: SoundFontSample;
12
+ rootKey: number;
13
+ /** tuning offset in cents, applied on top of the root key */
14
+ cents: number;
15
+ loop: boolean;
16
+ /** linear level multiplier from attenuation layering */
17
+ gain: number;
18
+ /** stereo position, -1 (left) to 1 (right) */
19
+ pan: number;
20
+ /** envelope stages in seconds, except sustain which is a level multiplier */
21
+ attack: number;
22
+ hold: number;
23
+ decay: number;
24
+ sustain: number;
25
+ release: number;
26
+ }
27
+ /**
28
+ * The samples behind a SoundFont file: one call per note returns the layered voices
29
+ * for its bank, program, key and velocity. A game ships its own file (parsed here)
30
+ * and hands the result wherever samples are needed; nothing here fetches or picks one.
31
+ */
32
+ export interface SoundFont {
33
+ voices(bank: number, program: number, key: number, velocity: number): SoundFontVoice[];
34
+ hasPreset(bank: number, program: number): boolean;
35
+ readonly presetCount: number;
36
+ }
37
+ /** one used program: the voice a game's MIDI files actually request */
38
+ export interface SoundFontProgram {
39
+ bank: number;
40
+ program: number;
41
+ }
42
+ /**
43
+ * Reads SoundFont 2 bytes into layered preset voices.
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * import { parseSoundFont, parseMidi, renderMidiToBuffer } from '@datamoc/mw_games/audio';
48
+ *
49
+ * declare const soundfontBytes: ArrayBuffer;
50
+ * declare const midiBytes: ArrayBuffer;
51
+ *
52
+ * const font = parseSoundFont(soundfontBytes);
53
+ * const rendered = renderMidiToBuffer(parseMidi(midiBytes), { soundfont: font });
54
+ * console.log(font.presetCount, rendered.duration);
55
+ * ```
56
+ */
57
+ export declare function parseSoundFont(data: ArrayBuffer | ArrayBufferView): SoundFont;
58
+ /**
59
+ * Every sampled voice the given MIDI files can request, as (bank, program) pairs:
60
+ * the channel voice in effect at each scheduled note, with drums mapped to bank 128
61
+ * the same way the renderer maps them. A game's compile step feeds this straight
62
+ * into `subsetSoundFont` so the shipped font carries only these programs.
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * import { parseMidi, collectSoundFontUsage } from '@datamoc/mw_games/audio';
67
+ *
68
+ * declare const midiBytes: ArrayBuffer;
69
+ *
70
+ * const usage = collectSoundFontUsage([parseMidi(midiBytes)]);
71
+ * console.log(usage.length);
72
+ * ```
73
+ */
74
+ export declare function collectSoundFontUsage(files: readonly MidiFile[]): SoundFontProgram[];
75
+ /**
76
+ * Writes a smaller, valid SoundFont 2 file carrying only the used programs: their
77
+ * presets (names kept), instruments and samples, with zones, ranges, tuning,
78
+ * envelopes and sample loops copied verbatim and every index remapped. Zones that
79
+ * reference samples missing from the file (ROM samples) are dropped, matching what
80
+ * the reader voices for the full font; the library, genre and morphology metadata
81
+ * is zeroed and the INFO list, when present, rides along untouched. Modulator lists
82
+ * are emptied and 24-bit upper bits are dropped: the reader voices neither, so what
83
+ * it plays is unchanged, while a third-party player would miss those.
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * import { parseMidi, collectSoundFontUsage, subsetSoundFont } from '@datamoc/mw_games/audio';
88
+ *
89
+ * declare const midiBytes: ArrayBuffer;
90
+ * declare const soundfontBytes: ArrayBuffer;
91
+ *
92
+ * const usage = collectSoundFontUsage([parseMidi(midiBytes)]);
93
+ * const pruned = subsetSoundFont(soundfontBytes, usage);
94
+ * console.log(usage.length, pruned.byteLength);
95
+ * ```
96
+ */
97
+ export declare function subsetSoundFont(data: ArrayBuffer | ArrayBufferView, usage: readonly SoundFontProgram[]): ArrayBuffer;
98
+ //# sourceMappingURL=SoundFont.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SoundFont.d.ts","sourceRoot":"","sources":["../../src/audio/SoundFont.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAC;AAwCxD,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,wDAAwD;AACxD,MAAM,WAAW,cAAc;IAC9B,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,OAAO,CAAC;IACd,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACzB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,cAAc,EAAE,CAAC;IACvF,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAClD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC7B;AAED,uEAAuE;AACvE,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CAChB;AAyMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,GAAG,eAAe,GAAG,SAAS,CAkG7E;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,GAAG,gBAAgB,EAAE,CAcpF;AA6CD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,WAAW,GAAG,eAAe,EAAE,KAAK,EAAE,SAAS,gBAAgB,EAAE,GAAG,WAAW,CAiOpH"}