@waveform-playlist/playout 9.1.0 → 9.1.2
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/dist/index.d.mts +72 -26
- package/dist/index.d.ts +72 -26
- package/dist/index.js +74 -78
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +68 -77
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Gain, ToneAudioNode, SynthOptions, Volume, BaseContext, Context } from 'tone';
|
|
2
2
|
import { Fade, Track, MidiNoteData } from '@waveform-playlist/core';
|
|
3
|
+
import { ZoneMap, Generator, GeneratorType } from 'soundfont2';
|
|
3
4
|
import { PlayoutAdapter } from '@waveform-playlist/engine';
|
|
4
5
|
|
|
5
6
|
type TrackEffectsFunction = (graphEnd: Gain, masterGainNode: ToneAudioNode, isOffline: boolean) => void | (() => void);
|
|
@@ -201,6 +202,75 @@ interface SoundFontSample {
|
|
|
201
202
|
/** Volume envelope release time in seconds */
|
|
202
203
|
releaseVolEnv: number;
|
|
203
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Convert SF2 timecents to seconds.
|
|
207
|
+
* SF2 formula: seconds = 2^(timecents / 1200)
|
|
208
|
+
* Default -12000 timecents ≈ 0.001s (effectively instant).
|
|
209
|
+
*/
|
|
210
|
+
declare function timecentsToSeconds(tc: number): number;
|
|
211
|
+
/**
|
|
212
|
+
* Get a numeric generator value from a zone map.
|
|
213
|
+
*/
|
|
214
|
+
declare function getGeneratorValue(generators: ZoneMap<Generator>, type: GeneratorType): number | undefined;
|
|
215
|
+
/**
|
|
216
|
+
* Convert Int16Array sample data to Float32Array.
|
|
217
|
+
* SF2 samples are 16-bit signed integers; Web Audio needs Float32 [-1, 1].
|
|
218
|
+
*/
|
|
219
|
+
declare function int16ToFloat32(samples: Int16Array): Float32Array;
|
|
220
|
+
/**
|
|
221
|
+
* Input parameters for playback rate calculation.
|
|
222
|
+
*/
|
|
223
|
+
interface PlaybackRateParams {
|
|
224
|
+
/** Target MIDI note number (0-127) */
|
|
225
|
+
midiNote: number;
|
|
226
|
+
/** OverridingRootKey generator value, or undefined if not set */
|
|
227
|
+
overrideRootKey: number | undefined;
|
|
228
|
+
/** sample.header.originalPitch (255 means unpitched) */
|
|
229
|
+
originalPitch: number;
|
|
230
|
+
/** CoarseTune generator value in semitones (default 0) */
|
|
231
|
+
coarseTune: number;
|
|
232
|
+
/** FineTune generator value in cents (default 0) */
|
|
233
|
+
fineTune: number;
|
|
234
|
+
/** sample.header.pitchCorrection in cents (default 0) */
|
|
235
|
+
pitchCorrection: number;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Calculate playback rate for a MIDI note using the SF2 generator chain.
|
|
239
|
+
*
|
|
240
|
+
* SF2 root key resolution priority:
|
|
241
|
+
* 1. OverridingRootKey generator (per-zone, most specific)
|
|
242
|
+
* 2. sample.header.originalPitch (sample header)
|
|
243
|
+
* 3. MIDI note 60 (middle C fallback)
|
|
244
|
+
*
|
|
245
|
+
* Tuning adjustments:
|
|
246
|
+
* - CoarseTune generator (semitones, additive)
|
|
247
|
+
* - FineTune generator (cents, additive)
|
|
248
|
+
* - sample.header.pitchCorrection (cents, additive)
|
|
249
|
+
*/
|
|
250
|
+
declare function calculatePlaybackRate(params: PlaybackRateParams): number;
|
|
251
|
+
/**
|
|
252
|
+
* Input parameters for loop and envelope extraction.
|
|
253
|
+
*/
|
|
254
|
+
interface LoopAndEnvelopeParams {
|
|
255
|
+
/** SF2 generators zone map */
|
|
256
|
+
generators: ZoneMap<Generator>;
|
|
257
|
+
/** Sample header with loop points and sample rate */
|
|
258
|
+
header: {
|
|
259
|
+
startLoop: number;
|
|
260
|
+
endLoop: number;
|
|
261
|
+
sampleRate: number;
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Extract loop points and volume envelope data from per-zone generators.
|
|
266
|
+
*
|
|
267
|
+
* Loop points are stored as absolute indices into the SF2 sample pool.
|
|
268
|
+
* We convert to AudioBuffer-relative seconds by subtracting header.start
|
|
269
|
+
* and dividing by sampleRate.
|
|
270
|
+
*
|
|
271
|
+
* Volume envelope times are in SF2 timecents; sustain is centibels attenuation.
|
|
272
|
+
*/
|
|
273
|
+
declare function extractLoopAndEnvelope(params: LoopAndEnvelopeParams): Omit<SoundFontSample, 'buffer' | 'playbackRate'>;
|
|
204
274
|
/**
|
|
205
275
|
* Caches parsed SoundFont2 data and AudioBuffers for efficient playback.
|
|
206
276
|
*
|
|
@@ -239,33 +309,9 @@ declare class SoundFontCache {
|
|
|
239
309
|
* @returns SoundFontSample or null if no sample found for this note
|
|
240
310
|
*/
|
|
241
311
|
getAudioBuffer(midiNote: number, bankNumber?: number, presetNumber?: number): SoundFontSample | null;
|
|
242
|
-
/**
|
|
243
|
-
* Extract loop points and volume envelope data from per-zone generators.
|
|
244
|
-
*
|
|
245
|
-
* Loop points are stored as absolute indices into the SF2 sample pool.
|
|
246
|
-
* We convert to AudioBuffer-relative seconds by subtracting header.start
|
|
247
|
-
* and dividing by sampleRate.
|
|
248
|
-
*
|
|
249
|
-
* Volume envelope times are in SF2 timecents; sustain is centibels attenuation.
|
|
250
|
-
*/
|
|
251
|
-
private extractLoopAndEnvelope;
|
|
252
|
-
/**
|
|
253
|
-
* Calculate playback rate for a MIDI note using the SF2 generator chain.
|
|
254
|
-
*
|
|
255
|
-
* SF2 root key resolution priority:
|
|
256
|
-
* 1. OverridingRootKey generator (per-zone, most specific)
|
|
257
|
-
* 2. sample.header.originalPitch (sample header)
|
|
258
|
-
* 3. MIDI note 60 (middle C fallback)
|
|
259
|
-
*
|
|
260
|
-
* Tuning adjustments:
|
|
261
|
-
* - CoarseTune generator (semitones, additive)
|
|
262
|
-
* - FineTune generator (cents, additive)
|
|
263
|
-
* - sample.header.pitchCorrection (cents, additive)
|
|
264
|
-
*/
|
|
265
|
-
private calculatePlaybackRate;
|
|
266
312
|
/**
|
|
267
313
|
* Convert Int16Array sample data to an AudioBuffer.
|
|
268
|
-
*
|
|
314
|
+
* Uses the extracted int16ToFloat32 for the conversion, then copies into an AudioBuffer.
|
|
269
315
|
*/
|
|
270
316
|
private int16ToAudioBuffer;
|
|
271
317
|
/**
|
|
@@ -536,4 +582,4 @@ interface ToneAdapterOptions {
|
|
|
536
582
|
}
|
|
537
583
|
declare function createToneAdapter(options?: ToneAdapterOptions): PlayoutAdapter;
|
|
538
584
|
|
|
539
|
-
export { type EffectsFunction, type FadeConfig, type FadeType, type MidiClipInfo, MidiToneTrack, type MidiToneTrackOptions, type PlayableTrack, SoundFontCache, type SoundFontSample, SoundFontToneTrack, type SoundFontToneTrackOptions, type ToneAdapterOptions, TonePlayout, type TonePlayoutOptions, ToneTrack, type ToneTrackOptions, type TrackEffectsFunction, applyFadeIn, applyFadeOut, closeGlobalAudioContext, createToneAdapter, getGlobalAudioContext, getGlobalAudioContextState, getGlobalContext, getGlobalToneContext, getMediaStreamSource, getUnderlyingAudioParam, hasMediaStreamSource, releaseMediaStreamSource, resumeGlobalAudioContext };
|
|
585
|
+
export { type EffectsFunction, type FadeConfig, type FadeType, type LoopAndEnvelopeParams, type MidiClipInfo, MidiToneTrack, type MidiToneTrackOptions, type PlayableTrack, type PlaybackRateParams, SoundFontCache, type SoundFontSample, SoundFontToneTrack, type SoundFontToneTrackOptions, type ToneAdapterOptions, TonePlayout, type TonePlayoutOptions, ToneTrack, type ToneTrackOptions, type TrackEffectsFunction, applyFadeIn, applyFadeOut, calculatePlaybackRate, closeGlobalAudioContext, createToneAdapter, extractLoopAndEnvelope, getGeneratorValue, getGlobalAudioContext, getGlobalAudioContextState, getGlobalContext, getGlobalToneContext, getMediaStreamSource, getUnderlyingAudioParam, hasMediaStreamSource, int16ToFloat32, releaseMediaStreamSource, resumeGlobalAudioContext, timecentsToSeconds };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Gain, ToneAudioNode, SynthOptions, Volume, BaseContext, Context } from 'tone';
|
|
2
2
|
import { Fade, Track, MidiNoteData } from '@waveform-playlist/core';
|
|
3
|
+
import { ZoneMap, Generator, GeneratorType } from 'soundfont2';
|
|
3
4
|
import { PlayoutAdapter } from '@waveform-playlist/engine';
|
|
4
5
|
|
|
5
6
|
type TrackEffectsFunction = (graphEnd: Gain, masterGainNode: ToneAudioNode, isOffline: boolean) => void | (() => void);
|
|
@@ -201,6 +202,75 @@ interface SoundFontSample {
|
|
|
201
202
|
/** Volume envelope release time in seconds */
|
|
202
203
|
releaseVolEnv: number;
|
|
203
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Convert SF2 timecents to seconds.
|
|
207
|
+
* SF2 formula: seconds = 2^(timecents / 1200)
|
|
208
|
+
* Default -12000 timecents ≈ 0.001s (effectively instant).
|
|
209
|
+
*/
|
|
210
|
+
declare function timecentsToSeconds(tc: number): number;
|
|
211
|
+
/**
|
|
212
|
+
* Get a numeric generator value from a zone map.
|
|
213
|
+
*/
|
|
214
|
+
declare function getGeneratorValue(generators: ZoneMap<Generator>, type: GeneratorType): number | undefined;
|
|
215
|
+
/**
|
|
216
|
+
* Convert Int16Array sample data to Float32Array.
|
|
217
|
+
* SF2 samples are 16-bit signed integers; Web Audio needs Float32 [-1, 1].
|
|
218
|
+
*/
|
|
219
|
+
declare function int16ToFloat32(samples: Int16Array): Float32Array;
|
|
220
|
+
/**
|
|
221
|
+
* Input parameters for playback rate calculation.
|
|
222
|
+
*/
|
|
223
|
+
interface PlaybackRateParams {
|
|
224
|
+
/** Target MIDI note number (0-127) */
|
|
225
|
+
midiNote: number;
|
|
226
|
+
/** OverridingRootKey generator value, or undefined if not set */
|
|
227
|
+
overrideRootKey: number | undefined;
|
|
228
|
+
/** sample.header.originalPitch (255 means unpitched) */
|
|
229
|
+
originalPitch: number;
|
|
230
|
+
/** CoarseTune generator value in semitones (default 0) */
|
|
231
|
+
coarseTune: number;
|
|
232
|
+
/** FineTune generator value in cents (default 0) */
|
|
233
|
+
fineTune: number;
|
|
234
|
+
/** sample.header.pitchCorrection in cents (default 0) */
|
|
235
|
+
pitchCorrection: number;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Calculate playback rate for a MIDI note using the SF2 generator chain.
|
|
239
|
+
*
|
|
240
|
+
* SF2 root key resolution priority:
|
|
241
|
+
* 1. OverridingRootKey generator (per-zone, most specific)
|
|
242
|
+
* 2. sample.header.originalPitch (sample header)
|
|
243
|
+
* 3. MIDI note 60 (middle C fallback)
|
|
244
|
+
*
|
|
245
|
+
* Tuning adjustments:
|
|
246
|
+
* - CoarseTune generator (semitones, additive)
|
|
247
|
+
* - FineTune generator (cents, additive)
|
|
248
|
+
* - sample.header.pitchCorrection (cents, additive)
|
|
249
|
+
*/
|
|
250
|
+
declare function calculatePlaybackRate(params: PlaybackRateParams): number;
|
|
251
|
+
/**
|
|
252
|
+
* Input parameters for loop and envelope extraction.
|
|
253
|
+
*/
|
|
254
|
+
interface LoopAndEnvelopeParams {
|
|
255
|
+
/** SF2 generators zone map */
|
|
256
|
+
generators: ZoneMap<Generator>;
|
|
257
|
+
/** Sample header with loop points and sample rate */
|
|
258
|
+
header: {
|
|
259
|
+
startLoop: number;
|
|
260
|
+
endLoop: number;
|
|
261
|
+
sampleRate: number;
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Extract loop points and volume envelope data from per-zone generators.
|
|
266
|
+
*
|
|
267
|
+
* Loop points are stored as absolute indices into the SF2 sample pool.
|
|
268
|
+
* We convert to AudioBuffer-relative seconds by subtracting header.start
|
|
269
|
+
* and dividing by sampleRate.
|
|
270
|
+
*
|
|
271
|
+
* Volume envelope times are in SF2 timecents; sustain is centibels attenuation.
|
|
272
|
+
*/
|
|
273
|
+
declare function extractLoopAndEnvelope(params: LoopAndEnvelopeParams): Omit<SoundFontSample, 'buffer' | 'playbackRate'>;
|
|
204
274
|
/**
|
|
205
275
|
* Caches parsed SoundFont2 data and AudioBuffers for efficient playback.
|
|
206
276
|
*
|
|
@@ -239,33 +309,9 @@ declare class SoundFontCache {
|
|
|
239
309
|
* @returns SoundFontSample or null if no sample found for this note
|
|
240
310
|
*/
|
|
241
311
|
getAudioBuffer(midiNote: number, bankNumber?: number, presetNumber?: number): SoundFontSample | null;
|
|
242
|
-
/**
|
|
243
|
-
* Extract loop points and volume envelope data from per-zone generators.
|
|
244
|
-
*
|
|
245
|
-
* Loop points are stored as absolute indices into the SF2 sample pool.
|
|
246
|
-
* We convert to AudioBuffer-relative seconds by subtracting header.start
|
|
247
|
-
* and dividing by sampleRate.
|
|
248
|
-
*
|
|
249
|
-
* Volume envelope times are in SF2 timecents; sustain is centibels attenuation.
|
|
250
|
-
*/
|
|
251
|
-
private extractLoopAndEnvelope;
|
|
252
|
-
/**
|
|
253
|
-
* Calculate playback rate for a MIDI note using the SF2 generator chain.
|
|
254
|
-
*
|
|
255
|
-
* SF2 root key resolution priority:
|
|
256
|
-
* 1. OverridingRootKey generator (per-zone, most specific)
|
|
257
|
-
* 2. sample.header.originalPitch (sample header)
|
|
258
|
-
* 3. MIDI note 60 (middle C fallback)
|
|
259
|
-
*
|
|
260
|
-
* Tuning adjustments:
|
|
261
|
-
* - CoarseTune generator (semitones, additive)
|
|
262
|
-
* - FineTune generator (cents, additive)
|
|
263
|
-
* - sample.header.pitchCorrection (cents, additive)
|
|
264
|
-
*/
|
|
265
|
-
private calculatePlaybackRate;
|
|
266
312
|
/**
|
|
267
313
|
* Convert Int16Array sample data to an AudioBuffer.
|
|
268
|
-
*
|
|
314
|
+
* Uses the extracted int16ToFloat32 for the conversion, then copies into an AudioBuffer.
|
|
269
315
|
*/
|
|
270
316
|
private int16ToAudioBuffer;
|
|
271
317
|
/**
|
|
@@ -536,4 +582,4 @@ interface ToneAdapterOptions {
|
|
|
536
582
|
}
|
|
537
583
|
declare function createToneAdapter(options?: ToneAdapterOptions): PlayoutAdapter;
|
|
538
584
|
|
|
539
|
-
export { type EffectsFunction, type FadeConfig, type FadeType, type MidiClipInfo, MidiToneTrack, type MidiToneTrackOptions, type PlayableTrack, SoundFontCache, type SoundFontSample, SoundFontToneTrack, type SoundFontToneTrackOptions, type ToneAdapterOptions, TonePlayout, type TonePlayoutOptions, ToneTrack, type ToneTrackOptions, type TrackEffectsFunction, applyFadeIn, applyFadeOut, closeGlobalAudioContext, createToneAdapter, getGlobalAudioContext, getGlobalAudioContextState, getGlobalContext, getGlobalToneContext, getMediaStreamSource, getUnderlyingAudioParam, hasMediaStreamSource, releaseMediaStreamSource, resumeGlobalAudioContext };
|
|
585
|
+
export { type EffectsFunction, type FadeConfig, type FadeType, type LoopAndEnvelopeParams, type MidiClipInfo, MidiToneTrack, type MidiToneTrackOptions, type PlayableTrack, type PlaybackRateParams, SoundFontCache, type SoundFontSample, SoundFontToneTrack, type SoundFontToneTrackOptions, type ToneAdapterOptions, TonePlayout, type TonePlayoutOptions, ToneTrack, type ToneTrackOptions, type TrackEffectsFunction, applyFadeIn, applyFadeOut, calculatePlaybackRate, closeGlobalAudioContext, createToneAdapter, extractLoopAndEnvelope, getGeneratorValue, getGlobalAudioContext, getGlobalAudioContextState, getGlobalContext, getGlobalToneContext, getMediaStreamSource, getUnderlyingAudioParam, hasMediaStreamSource, int16ToFloat32, releaseMediaStreamSource, resumeGlobalAudioContext, timecentsToSeconds };
|
package/dist/index.js
CHANGED
|
@@ -27,8 +27,11 @@ __export(index_exports, {
|
|
|
27
27
|
ToneTrack: () => ToneTrack,
|
|
28
28
|
applyFadeIn: () => applyFadeIn,
|
|
29
29
|
applyFadeOut: () => applyFadeOut,
|
|
30
|
+
calculatePlaybackRate: () => calculatePlaybackRate,
|
|
30
31
|
closeGlobalAudioContext: () => closeGlobalAudioContext,
|
|
31
32
|
createToneAdapter: () => createToneAdapter,
|
|
33
|
+
extractLoopAndEnvelope: () => extractLoopAndEnvelope,
|
|
34
|
+
getGeneratorValue: () => getGeneratorValue,
|
|
32
35
|
getGlobalAudioContext: () => getGlobalAudioContext,
|
|
33
36
|
getGlobalAudioContextState: () => getGlobalAudioContextState,
|
|
34
37
|
getGlobalContext: () => getGlobalContext,
|
|
@@ -36,8 +39,10 @@ __export(index_exports, {
|
|
|
36
39
|
getMediaStreamSource: () => getMediaStreamSource,
|
|
37
40
|
getUnderlyingAudioParam: () => getUnderlyingAudioParam,
|
|
38
41
|
hasMediaStreamSource: () => hasMediaStreamSource,
|
|
42
|
+
int16ToFloat32: () => int16ToFloat32,
|
|
39
43
|
releaseMediaStreamSource: () => releaseMediaStreamSource,
|
|
40
|
-
resumeGlobalAudioContext: () => resumeGlobalAudioContext
|
|
44
|
+
resumeGlobalAudioContext: () => resumeGlobalAudioContext,
|
|
45
|
+
timecentsToSeconds: () => timecentsToSeconds
|
|
41
46
|
});
|
|
42
47
|
module.exports = __toCommonJS(index_exports);
|
|
43
48
|
|
|
@@ -1323,6 +1328,52 @@ var MAX_RELEASE_SECONDS = 5;
|
|
|
1323
1328
|
function getGeneratorValue(generators, type) {
|
|
1324
1329
|
return generators[type]?.value;
|
|
1325
1330
|
}
|
|
1331
|
+
function int16ToFloat32(samples) {
|
|
1332
|
+
const floats = new Float32Array(samples.length);
|
|
1333
|
+
for (let i = 0; i < samples.length; i++) {
|
|
1334
|
+
floats[i] = samples[i] / 32768;
|
|
1335
|
+
}
|
|
1336
|
+
return floats;
|
|
1337
|
+
}
|
|
1338
|
+
function calculatePlaybackRate(params) {
|
|
1339
|
+
const { midiNote, overrideRootKey, originalPitch, coarseTune, fineTune, pitchCorrection } = params;
|
|
1340
|
+
const rootKey = overrideRootKey !== void 0 ? overrideRootKey : originalPitch !== 255 ? originalPitch : 60;
|
|
1341
|
+
const totalSemitones = midiNote - rootKey + coarseTune + (fineTune + pitchCorrection) / 100;
|
|
1342
|
+
return Math.pow(2, totalSemitones / 12);
|
|
1343
|
+
}
|
|
1344
|
+
function extractLoopAndEnvelope(params) {
|
|
1345
|
+
const { generators, header } = params;
|
|
1346
|
+
const loopMode = getGeneratorValue(generators, import_soundfont2.GeneratorType.SampleModes) ?? 0;
|
|
1347
|
+
const rawLoopStart = header.startLoop + (getGeneratorValue(generators, import_soundfont2.GeneratorType.StartLoopAddrsOffset) ?? 0) + (getGeneratorValue(generators, import_soundfont2.GeneratorType.StartLoopAddrsCoarseOffset) ?? 0) * 32768;
|
|
1348
|
+
const rawLoopEnd = header.endLoop + (getGeneratorValue(generators, import_soundfont2.GeneratorType.EndLoopAddrsOffset) ?? 0) + (getGeneratorValue(generators, import_soundfont2.GeneratorType.EndLoopAddrsCoarseOffset) ?? 0) * 32768;
|
|
1349
|
+
const loopStart = rawLoopStart / header.sampleRate;
|
|
1350
|
+
const loopEnd = rawLoopEnd / header.sampleRate;
|
|
1351
|
+
const attackVolEnv = timecentsToSeconds(
|
|
1352
|
+
getGeneratorValue(generators, import_soundfont2.GeneratorType.AttackVolEnv) ?? -12e3
|
|
1353
|
+
);
|
|
1354
|
+
const holdVolEnv = timecentsToSeconds(
|
|
1355
|
+
getGeneratorValue(generators, import_soundfont2.GeneratorType.HoldVolEnv) ?? -12e3
|
|
1356
|
+
);
|
|
1357
|
+
const decayVolEnv = timecentsToSeconds(
|
|
1358
|
+
getGeneratorValue(generators, import_soundfont2.GeneratorType.DecayVolEnv) ?? -12e3
|
|
1359
|
+
);
|
|
1360
|
+
const releaseVolEnv = Math.min(
|
|
1361
|
+
timecentsToSeconds(getGeneratorValue(generators, import_soundfont2.GeneratorType.ReleaseVolEnv) ?? -12e3),
|
|
1362
|
+
MAX_RELEASE_SECONDS
|
|
1363
|
+
);
|
|
1364
|
+
const sustainCb = getGeneratorValue(generators, import_soundfont2.GeneratorType.SustainVolEnv) ?? 0;
|
|
1365
|
+
const sustainVolEnv = Math.pow(10, -sustainCb / 200);
|
|
1366
|
+
return {
|
|
1367
|
+
loopMode,
|
|
1368
|
+
loopStart,
|
|
1369
|
+
loopEnd,
|
|
1370
|
+
attackVolEnv,
|
|
1371
|
+
holdVolEnv,
|
|
1372
|
+
decayVolEnv,
|
|
1373
|
+
sustainVolEnv,
|
|
1374
|
+
releaseVolEnv
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1326
1377
|
var SoundFontCache = class {
|
|
1327
1378
|
/**
|
|
1328
1379
|
* @param context Optional AudioContext for createBuffer(). If omitted, uses
|
|
@@ -1385,88 +1436,28 @@ var SoundFontCache = class {
|
|
|
1385
1436
|
buffer = this.int16ToAudioBuffer(sample.data, sample.header.sampleRate);
|
|
1386
1437
|
this.audioBufferCache.set(sampleIndex, buffer);
|
|
1387
1438
|
}
|
|
1388
|
-
const playbackRate =
|
|
1389
|
-
|
|
1439
|
+
const playbackRate = calculatePlaybackRate({
|
|
1440
|
+
midiNote,
|
|
1441
|
+
overrideRootKey: getGeneratorValue(keyData.generators, import_soundfont2.GeneratorType.OverridingRootKey),
|
|
1442
|
+
originalPitch: sample.header.originalPitch,
|
|
1443
|
+
coarseTune: getGeneratorValue(keyData.generators, import_soundfont2.GeneratorType.CoarseTune) ?? 0,
|
|
1444
|
+
fineTune: getGeneratorValue(keyData.generators, import_soundfont2.GeneratorType.FineTune) ?? 0,
|
|
1445
|
+
pitchCorrection: sample.header.pitchCorrection ?? 0
|
|
1446
|
+
});
|
|
1447
|
+
const loopAndEnvelope = extractLoopAndEnvelope({
|
|
1448
|
+
generators: keyData.generators,
|
|
1449
|
+
header: keyData.sample.header
|
|
1450
|
+
});
|
|
1390
1451
|
return { buffer, playbackRate, ...loopAndEnvelope };
|
|
1391
1452
|
}
|
|
1392
|
-
/**
|
|
1393
|
-
* Extract loop points and volume envelope data from per-zone generators.
|
|
1394
|
-
*
|
|
1395
|
-
* Loop points are stored as absolute indices into the SF2 sample pool.
|
|
1396
|
-
* We convert to AudioBuffer-relative seconds by subtracting header.start
|
|
1397
|
-
* and dividing by sampleRate.
|
|
1398
|
-
*
|
|
1399
|
-
* Volume envelope times are in SF2 timecents; sustain is centibels attenuation.
|
|
1400
|
-
*/
|
|
1401
|
-
extractLoopAndEnvelope(keyData) {
|
|
1402
|
-
const { generators } = keyData;
|
|
1403
|
-
const header = keyData.sample.header;
|
|
1404
|
-
const loopMode = getGeneratorValue(generators, import_soundfont2.GeneratorType.SampleModes) ?? 0;
|
|
1405
|
-
const rawLoopStart = header.startLoop + (getGeneratorValue(generators, import_soundfont2.GeneratorType.StartLoopAddrsOffset) ?? 0) + (getGeneratorValue(generators, import_soundfont2.GeneratorType.StartLoopAddrsCoarseOffset) ?? 0) * 32768;
|
|
1406
|
-
const rawLoopEnd = header.endLoop + (getGeneratorValue(generators, import_soundfont2.GeneratorType.EndLoopAddrsOffset) ?? 0) + (getGeneratorValue(generators, import_soundfont2.GeneratorType.EndLoopAddrsCoarseOffset) ?? 0) * 32768;
|
|
1407
|
-
const loopStart = rawLoopStart / header.sampleRate;
|
|
1408
|
-
const loopEnd = rawLoopEnd / header.sampleRate;
|
|
1409
|
-
const attackVolEnv = timecentsToSeconds(
|
|
1410
|
-
getGeneratorValue(generators, import_soundfont2.GeneratorType.AttackVolEnv) ?? -12e3
|
|
1411
|
-
);
|
|
1412
|
-
const holdVolEnv = timecentsToSeconds(
|
|
1413
|
-
getGeneratorValue(generators, import_soundfont2.GeneratorType.HoldVolEnv) ?? -12e3
|
|
1414
|
-
);
|
|
1415
|
-
const decayVolEnv = timecentsToSeconds(
|
|
1416
|
-
getGeneratorValue(generators, import_soundfont2.GeneratorType.DecayVolEnv) ?? -12e3
|
|
1417
|
-
);
|
|
1418
|
-
const releaseVolEnv = Math.min(
|
|
1419
|
-
timecentsToSeconds(getGeneratorValue(generators, import_soundfont2.GeneratorType.ReleaseVolEnv) ?? -12e3),
|
|
1420
|
-
MAX_RELEASE_SECONDS
|
|
1421
|
-
);
|
|
1422
|
-
const sustainCb = getGeneratorValue(generators, import_soundfont2.GeneratorType.SustainVolEnv) ?? 0;
|
|
1423
|
-
const sustainVolEnv = Math.pow(10, -sustainCb / 200);
|
|
1424
|
-
return {
|
|
1425
|
-
loopMode,
|
|
1426
|
-
loopStart,
|
|
1427
|
-
loopEnd,
|
|
1428
|
-
attackVolEnv,
|
|
1429
|
-
holdVolEnv,
|
|
1430
|
-
decayVolEnv,
|
|
1431
|
-
sustainVolEnv,
|
|
1432
|
-
releaseVolEnv
|
|
1433
|
-
};
|
|
1434
|
-
}
|
|
1435
|
-
/**
|
|
1436
|
-
* Calculate playback rate for a MIDI note using the SF2 generator chain.
|
|
1437
|
-
*
|
|
1438
|
-
* SF2 root key resolution priority:
|
|
1439
|
-
* 1. OverridingRootKey generator (per-zone, most specific)
|
|
1440
|
-
* 2. sample.header.originalPitch (sample header)
|
|
1441
|
-
* 3. MIDI note 60 (middle C fallback)
|
|
1442
|
-
*
|
|
1443
|
-
* Tuning adjustments:
|
|
1444
|
-
* - CoarseTune generator (semitones, additive)
|
|
1445
|
-
* - FineTune generator (cents, additive)
|
|
1446
|
-
* - sample.header.pitchCorrection (cents, additive)
|
|
1447
|
-
*/
|
|
1448
|
-
calculatePlaybackRate(midiNote, keyData) {
|
|
1449
|
-
const sample = keyData.sample;
|
|
1450
|
-
const generators = keyData.generators;
|
|
1451
|
-
const overrideRootKey = getGeneratorValue(generators, import_soundfont2.GeneratorType.OverridingRootKey);
|
|
1452
|
-
const originalPitch = sample.header.originalPitch;
|
|
1453
|
-
const rootKey = overrideRootKey !== void 0 ? overrideRootKey : originalPitch !== 255 ? originalPitch : 60;
|
|
1454
|
-
const coarseTune = getGeneratorValue(generators, import_soundfont2.GeneratorType.CoarseTune) ?? 0;
|
|
1455
|
-
const fineTune = getGeneratorValue(generators, import_soundfont2.GeneratorType.FineTune) ?? 0;
|
|
1456
|
-
const pitchCorrection = sample.header.pitchCorrection ?? 0;
|
|
1457
|
-
const totalSemitones = midiNote - rootKey + coarseTune + (fineTune + pitchCorrection) / 100;
|
|
1458
|
-
return Math.pow(2, totalSemitones / 12);
|
|
1459
|
-
}
|
|
1460
1453
|
/**
|
|
1461
1454
|
* Convert Int16Array sample data to an AudioBuffer.
|
|
1462
|
-
*
|
|
1455
|
+
* Uses the extracted int16ToFloat32 for the conversion, then copies into an AudioBuffer.
|
|
1463
1456
|
*/
|
|
1464
1457
|
int16ToAudioBuffer(data, sampleRate) {
|
|
1465
|
-
const
|
|
1466
|
-
const
|
|
1467
|
-
|
|
1468
|
-
channel[i] = data[i] / 32768;
|
|
1469
|
-
}
|
|
1458
|
+
const floats = int16ToFloat32(data);
|
|
1459
|
+
const buffer = this.context.createBuffer(1, floats.length, sampleRate);
|
|
1460
|
+
buffer.getChannelData(0).set(floats);
|
|
1470
1461
|
return buffer;
|
|
1471
1462
|
}
|
|
1472
1463
|
/**
|
|
@@ -1742,8 +1733,11 @@ function createToneAdapter(options) {
|
|
|
1742
1733
|
ToneTrack,
|
|
1743
1734
|
applyFadeIn,
|
|
1744
1735
|
applyFadeOut,
|
|
1736
|
+
calculatePlaybackRate,
|
|
1745
1737
|
closeGlobalAudioContext,
|
|
1746
1738
|
createToneAdapter,
|
|
1739
|
+
extractLoopAndEnvelope,
|
|
1740
|
+
getGeneratorValue,
|
|
1747
1741
|
getGlobalAudioContext,
|
|
1748
1742
|
getGlobalAudioContextState,
|
|
1749
1743
|
getGlobalContext,
|
|
@@ -1751,7 +1745,9 @@ function createToneAdapter(options) {
|
|
|
1751
1745
|
getMediaStreamSource,
|
|
1752
1746
|
getUnderlyingAudioParam,
|
|
1753
1747
|
hasMediaStreamSource,
|
|
1748
|
+
int16ToFloat32,
|
|
1754
1749
|
releaseMediaStreamSource,
|
|
1755
|
-
resumeGlobalAudioContext
|
|
1750
|
+
resumeGlobalAudioContext,
|
|
1751
|
+
timecentsToSeconds
|
|
1756
1752
|
});
|
|
1757
1753
|
//# sourceMappingURL=index.js.map
|