@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.
- package/CHANGELOG.md +27 -0
- package/dist/audio/Channels.d.ts +198 -0
- package/dist/audio/Channels.d.ts.map +1 -0
- package/dist/audio/Channels.js +456 -0
- package/dist/audio/Channels.js.map +1 -0
- package/dist/audio/Midi.d.ts +71 -9
- package/dist/audio/Midi.d.ts.map +1 -1
- package/dist/audio/Midi.js +107 -15
- package/dist/audio/Midi.js.map +1 -1
- package/dist/audio/MidiRender.d.ts +57 -0
- package/dist/audio/MidiRender.d.ts.map +1 -0
- package/dist/audio/MidiRender.js +261 -0
- package/dist/audio/MidiRender.js.map +1 -0
- package/dist/audio/SoundFont.d.ts +98 -0
- package/dist/audio/SoundFont.d.ts.map +1 -0
- package/dist/audio/SoundFont.js +578 -0
- package/dist/audio/SoundFont.js.map +1 -0
- package/dist/audio/index.d.ts +9 -2
- package/dist/audio/index.d.ts.map +1 -1
- package/dist/audio/index.js +4 -1
- package/dist/audio/index.js.map +1 -1
- package/dist/mw_games.global.js +52 -52
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
|
@@ -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":"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"}
|
|
@@ -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"}
|