smplr 0.25.0 → 1.0.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/README.md +74 -67
- package/dist/index.d.mts +79 -50
- package/dist/index.d.ts +79 -50
- package/dist/index.js +72 -70
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +72 -70
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -4
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,15 @@ declare class Channel {
|
|
|
26
26
|
get pan(): number;
|
|
27
27
|
set pan(value: number);
|
|
28
28
|
addInsert(effect: AudioNode | AudioInsert): void;
|
|
29
|
+
/**
|
|
30
|
+
* Add a send effect on a parallel bus.
|
|
31
|
+
*
|
|
32
|
+
* The send is **post-fader**: it taps the channel signal after the volume
|
|
33
|
+
* gain (and after any inserts added via {@link addInsert}), before the
|
|
34
|
+
* panner. Lowering `volume` proportionally lowers the send level; `volume = 0`
|
|
35
|
+
* silences the send too. Inserts are upstream of the tap, so they are heard
|
|
36
|
+
* on the send.
|
|
37
|
+
*/
|
|
29
38
|
addEffect(name: string, effect: AudioNode | {
|
|
30
39
|
input: AudioNode;
|
|
31
40
|
}, mixValue: number): void;
|
|
@@ -101,7 +110,6 @@ type SmplrRegion = PlaybackParams & {
|
|
|
101
110
|
group?: number;
|
|
102
111
|
offBy?: number;
|
|
103
112
|
trigger?: "first" | "legato";
|
|
104
|
-
ampVelCurve?: [number, number];
|
|
105
113
|
/** Auto-compute loop points from buffer duration ratios (0–1). */
|
|
106
114
|
loopAuto?: {
|
|
107
115
|
startRatio: number;
|
|
@@ -186,7 +194,7 @@ type LoadProgress = {
|
|
|
186
194
|
};
|
|
187
195
|
/**
|
|
188
196
|
* Fully resolved playback parameters for a single Voice.
|
|
189
|
-
* Output of resolveParams() — all fields are required, no optionals except
|
|
197
|
+
* Output of resolveParams() — all fields are required, no optionals except loopAuto.
|
|
190
198
|
*/
|
|
191
199
|
type VoiceParams = {
|
|
192
200
|
detune: number;
|
|
@@ -199,7 +207,6 @@ type VoiceParams = {
|
|
|
199
207
|
loop: boolean;
|
|
200
208
|
loopStart: number;
|
|
201
209
|
loopEnd: number;
|
|
202
|
-
ampVelCurve?: [number, number];
|
|
203
210
|
/** If set, loop points are computed from buffer.duration at play time. */
|
|
204
211
|
loopAuto?: {
|
|
205
212
|
startRatio: number;
|
|
@@ -208,68 +215,90 @@ type VoiceParams = {
|
|
|
208
215
|
reverse?: boolean;
|
|
209
216
|
};
|
|
210
217
|
|
|
218
|
+
/** Options accepted by `SampleLoader(context, options)`. */
|
|
219
|
+
type SampleLoaderOptions = {
|
|
220
|
+
/** Custom storage backend (e.g. `CacheStorage` for offline). Defaults to `HttpStorage`. */
|
|
221
|
+
storage?: Storage;
|
|
222
|
+
};
|
|
223
|
+
/** Options accepted by `loader.load(json, options)`. */
|
|
224
|
+
type SampleLoaderLoadOptions = {
|
|
225
|
+
/** Pre-decoded buffers keyed by sample name — skip fetch for these. */
|
|
226
|
+
buffers?: Map<string, AudioBuffer>;
|
|
227
|
+
/** Called once per sample (including cache hits) with cumulative progress. */
|
|
228
|
+
onProgress?: (loaded: number, total: number) => void;
|
|
229
|
+
};
|
|
230
|
+
type SampleLoaderFactory = {
|
|
231
|
+
(context: BaseAudioContext, options?: SampleLoaderOptions): SampleLoader;
|
|
232
|
+
/** @deprecated Call as a function: `SampleLoader(...)` instead of `new SampleLoader(...)`. */
|
|
233
|
+
new (context: BaseAudioContext, options?: SampleLoaderOptions): SampleLoader;
|
|
234
|
+
};
|
|
211
235
|
/**
|
|
212
|
-
* Loads and
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
* or decoded twice. Multiple Smplr instances can share one SampleLoader by
|
|
216
|
-
* passing it via SmplrOptions.loader.
|
|
236
|
+
* Loads and decodes AudioBuffers for the samples referenced by a {@link SmplrPreset}.
|
|
237
|
+
* Used internally by every smplr instrument; pass an instance via
|
|
238
|
+
* {@link SmplrOptions.loader} to share buffer caching across multiple instruments.
|
|
217
239
|
*/
|
|
218
|
-
|
|
219
|
-
#private;
|
|
220
|
-
constructor(context: BaseAudioContext, options?: {
|
|
221
|
-
storage?: Storage;
|
|
222
|
-
});
|
|
240
|
+
interface SampleLoader {
|
|
223
241
|
/**
|
|
224
|
-
* Load all samples referenced
|
|
225
|
-
*
|
|
226
|
-
*
|
|
242
|
+
* Load all samples referenced by `json`. Returns a Map keyed by sample
|
|
243
|
+
* name (`region.sample`), values are decoded `AudioBuffer`s. Failed
|
|
244
|
+
* samples are silently omitted (callers handle absence at lookup time).
|
|
245
|
+
*
|
|
246
|
+
* Internally cached by resolved URL, so repeated calls with the same
|
|
247
|
+
* baseUrl/format/path do not re-fetch.
|
|
227
248
|
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
249
|
+
* @param json The preset describing samples to load.
|
|
250
|
+
* @param options
|
|
251
|
+
* - `buffers`: pre-decoded buffers keyed by sample name — skip fetch for these.
|
|
252
|
+
* - `onProgress`: called with `(loaded, total)` per sample (including cache hits).
|
|
230
253
|
*/
|
|
231
|
-
load(json: SmplrPreset,
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
254
|
+
load(json: SmplrPreset, options?: SampleLoaderLoadOptions): Promise<Map<string, AudioBuffer>>;
|
|
255
|
+
/**
|
|
256
|
+
* @deprecated Pass `{ onProgress }` instead. The bare-callback form is kept
|
|
257
|
+
* for compatibility; the options form is the canonical 1.x signature.
|
|
258
|
+
*/
|
|
259
|
+
load(json: SmplrPreset, onProgress: (loaded: number, total: number) => void): Promise<Map<string, AudioBuffer>>;
|
|
235
260
|
}
|
|
236
|
-
declare const SampleLoader:
|
|
237
|
-
storage?: Storage;
|
|
238
|
-
} | undefined], SampleLoaderImpl>;
|
|
239
|
-
type SampleLoader = ReturnType<typeof SampleLoader>;
|
|
261
|
+
declare const SampleLoader: SampleLoaderFactory;
|
|
240
262
|
|
|
263
|
+
/** Options accepted by `Scheduler(context, options)`. */
|
|
264
|
+
type SchedulerOptions = {
|
|
265
|
+
/**
|
|
266
|
+
* How far ahead of `currentTime` events are dispatched synchronously.
|
|
267
|
+
* Defaults to 200ms.
|
|
268
|
+
*/
|
|
269
|
+
lookaheadMs?: number;
|
|
270
|
+
/**
|
|
271
|
+
* How often the queue is polled for events ready to dispatch.
|
|
272
|
+
* Defaults to 50ms.
|
|
273
|
+
*/
|
|
274
|
+
intervalMs?: number;
|
|
275
|
+
};
|
|
276
|
+
type SchedulerFactory = {
|
|
277
|
+
(context: BaseAudioContext, options?: SchedulerOptions): Scheduler;
|
|
278
|
+
/** @deprecated Call as a function: `Scheduler(...)` instead of `new Scheduler(...)`. */
|
|
279
|
+
new (context: BaseAudioContext, options?: SchedulerOptions): Scheduler;
|
|
280
|
+
};
|
|
241
281
|
/**
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
* Multiple Smplr instances can share a single Scheduler for coordinated timing.
|
|
282
|
+
* Schedules note events for future dispatch. Used internally by every smplr
|
|
283
|
+
* instrument; pass an instance via {@link SmplrOptions.scheduler} to share one
|
|
284
|
+
* scheduler across multiple instruments.
|
|
246
285
|
*/
|
|
247
|
-
|
|
248
|
-
#private;
|
|
249
|
-
constructor(context: BaseAudioContext, options?: {
|
|
250
|
-
lookaheadMs?: number;
|
|
251
|
-
intervalMs?: number;
|
|
252
|
-
});
|
|
286
|
+
interface Scheduler {
|
|
253
287
|
/**
|
|
254
|
-
*
|
|
288
|
+
* Dispatch `callback` at `event.time`. If `event.time` is within the
|
|
289
|
+
* scheduler's lookahead window (or omitted), the callback fires synchronously
|
|
290
|
+
* and the returned {@link StopFn} is a no-op. Otherwise the event is queued.
|
|
255
291
|
*
|
|
256
|
-
*
|
|
257
|
-
* callback is called synchronously and a no-op StopFn is returned.
|
|
258
|
-
* - Otherwise the event is queued, the interval is started if needed, and a StopFn
|
|
259
|
-
* is returned that removes the event from the queue before it is dispatched.
|
|
292
|
+
* The returned function removes the event from the queue before dispatch.
|
|
260
293
|
*/
|
|
261
294
|
schedule(event: NoteEvent, callback: (event: NoteEvent) => void): StopFn;
|
|
262
295
|
/**
|
|
263
|
-
* Clear all queued (not-yet-dispatched) events and stop the
|
|
264
|
-
* Does not affect voices
|
|
296
|
+
* Clear all queued (not-yet-dispatched) events and stop the polling
|
|
297
|
+
* interval. Does not affect voices already playing.
|
|
265
298
|
*/
|
|
266
299
|
stop(): void;
|
|
267
300
|
}
|
|
268
|
-
declare const Scheduler:
|
|
269
|
-
lookaheadMs?: number;
|
|
270
|
-
intervalMs?: number;
|
|
271
|
-
} | undefined], SchedulerImpl>;
|
|
272
|
-
type Scheduler = ReturnType<typeof Scheduler>;
|
|
301
|
+
declare const Scheduler: SchedulerFactory;
|
|
273
302
|
|
|
274
303
|
type SmplrOptions = {
|
|
275
304
|
/** Custom storage backend for sample fetching (e.g. CacheStorage). */
|
|
@@ -1208,7 +1237,7 @@ declare function sf2InstrumentToPreset(sf2Instrument: Sf2Instrument, context: Ba
|
|
|
1208
1237
|
};
|
|
1209
1238
|
type Soundfont2SamplerExtras = {
|
|
1210
1239
|
readonly instrumentNames: string[];
|
|
1211
|
-
loadInstrument(instrumentName: string): Promise<void
|
|
1240
|
+
loadInstrument(instrumentName: string): Promise<void>;
|
|
1212
1241
|
};
|
|
1213
1242
|
declare const Soundfont2: InstrumentFactory<Soundfont2Options, Soundfont2SamplerExtras>;
|
|
1214
1243
|
/** Instance type returned by the {@link Soundfont2} factory. */
|
|
@@ -1273,4 +1302,4 @@ declare const LAYERS: ({
|
|
|
1273
1302
|
cutoff?: undefined;
|
|
1274
1303
|
})[];
|
|
1275
1304
|
|
|
1276
|
-
export { type AddTrackOptions, CacheStorage, DRUM_ABUSE_PACKS, DrumAbuse, type DrumAbuseConfig, type DrumAbuseExtras, type DrumAbuseOptions, type DrumAbusePackId, type DrumAbuseSource, DrumMachine, type DrumMachineOptions, ElectricPiano, type ElectricPianoOptions, HttpStorage, Instrument, LAYERS, type LoadProgress, Mallet, Mellotron, type MellotronConfig, type MellotronOptions, NAME_TO_PATH, type NoteEvent, type PatternInput, type PlaybackParams, type RenderOfflineOptions, RenderResult, Reverb, SampleLoader, Sampler, type SamplerConfig, type SamplerReloadInput, Scheduler, Sequencer, type SequencerInstrument, type SequencerNote, type SequencerNoteEvent, type SequencerOptions, Smolken, type SmolkenConfig, type SmolkenOptions, type Smplr, type SmplrGroup, type SmplrOptions, type SmplrPlugin, type SmplrPreset, type SmplrRegion, type SmplrSamples, Soundfont, Soundfont2, type Soundfont2Options, Soundfont2Sampler, type SoundfontOptions, SplendidGrandPiano, type SplendidGrandPianoConfig, type StopFn, type StopTarget, type Storage, type StorageResponse, type TimeSignature, Versilian, type VersilianConfig, type VersilianOptions, type VoiceParams, audioBufferToWav, audioBufferToWav16, drumAbuseSampleUrl, drumMachineToPreset, getDrumAbuseMachineNames, getDrumAbuseMachinePack, getDrumAbuseMachinesForPack, getDrumAbusePackNames, getDrumMachineNames, getElectricPianoNames, getMalletNames, getMellotronNames, getSmolkenNames, getSoundfontKits, getSoundfontNames, getVersilianInstruments, loadVersilianInstrument, mellotronToPreset, pianoToPreset, renderOffline, samplerToPreset, sf2InstrumentToPreset, soundfontToPreset, trimSilence };
|
|
1305
|
+
export { type AddTrackOptions, CacheStorage, DRUM_ABUSE_PACKS, DrumAbuse, type DrumAbuseConfig, type DrumAbuseExtras, type DrumAbuseOptions, type DrumAbusePackId, type DrumAbuseSource, DrumMachine, type DrumMachineOptions, ElectricPiano, type ElectricPianoOptions, HttpStorage, Instrument, LAYERS, type LoadProgress, Mallet, Mellotron, type MellotronConfig, type MellotronOptions, NAME_TO_PATH, type NoteEvent, type PatternInput, type PlaybackParams, type RenderOfflineOptions, RenderResult, Reverb, SampleLoader, type SampleLoaderLoadOptions, type SampleLoaderOptions, Sampler, type SamplerConfig, type SamplerReloadInput, Scheduler, type SchedulerOptions, Sequencer, type SequencerInstrument, type SequencerNote, type SequencerNoteEvent, type SequencerOptions, Smolken, type SmolkenConfig, type SmolkenOptions, type Smplr, type SmplrGroup, type SmplrOptions, type SmplrPlugin, type SmplrPreset, type SmplrRegion, type SmplrSamples, Soundfont, Soundfont2, type Soundfont2Options, Soundfont2Sampler, type SoundfontOptions, SplendidGrandPiano, type SplendidGrandPianoConfig, type StopFn, type StopTarget, type Storage, type StorageResponse, type TimeSignature, Versilian, type VersilianConfig, type VersilianOptions, type VoiceParams, audioBufferToWav, audioBufferToWav16, drumAbuseSampleUrl, drumMachineToPreset, getDrumAbuseMachineNames, getDrumAbuseMachinePack, getDrumAbuseMachinesForPack, getDrumAbusePackNames, getDrumMachineNames, getElectricPianoNames, getMalletNames, getMellotronNames, getSmolkenNames, getSoundfontKits, getSoundfontNames, getVersilianInstruments, loadVersilianInstrument, mellotronToPreset, pianoToPreset, renderOffline, samplerToPreset, sf2InstrumentToPreset, soundfontToPreset, trimSilence };
|
package/dist/index.js
CHANGED
|
@@ -136,11 +136,12 @@ var HttpStorage = {
|
|
|
136
136
|
return fetch(url);
|
|
137
137
|
}
|
|
138
138
|
};
|
|
139
|
-
var _cache, _CacheStorageImpl_instances, tryFromCache_fn, saveResponse_fn;
|
|
139
|
+
var _cache, _warned, _CacheStorageImpl_instances, tryFromCache_fn, saveResponse_fn;
|
|
140
140
|
var CacheStorageImpl = class {
|
|
141
141
|
constructor(name = "smplr") {
|
|
142
142
|
__privateAdd(this, _CacheStorageImpl_instances);
|
|
143
143
|
__privateAdd(this, _cache);
|
|
144
|
+
__privateAdd(this, _warned, false);
|
|
144
145
|
if (typeof window === "undefined" || !("caches" in window)) {
|
|
145
146
|
__privateSet(this, _cache, Promise.reject("CacheStorage not supported"));
|
|
146
147
|
__privateGet(this, _cache).catch(() => {
|
|
@@ -163,6 +164,7 @@ var CacheStorageImpl = class {
|
|
|
163
164
|
}
|
|
164
165
|
};
|
|
165
166
|
_cache = new WeakMap();
|
|
167
|
+
_warned = new WeakMap();
|
|
166
168
|
_CacheStorageImpl_instances = new WeakSet();
|
|
167
169
|
tryFromCache_fn = function(request) {
|
|
168
170
|
return __async(this, null, function* () {
|
|
@@ -178,6 +180,10 @@ saveResponse_fn = function(request, response) {
|
|
|
178
180
|
const cache = yield __privateGet(this, _cache);
|
|
179
181
|
yield cache.put(request, response.clone());
|
|
180
182
|
} catch (err) {
|
|
183
|
+
if (!__privateGet(this, _warned)) {
|
|
184
|
+
__privateSet(this, _warned, true);
|
|
185
|
+
console.warn("smplr: failed to cache response", err);
|
|
186
|
+
}
|
|
181
187
|
}
|
|
182
188
|
});
|
|
183
189
|
};
|
|
@@ -297,6 +303,15 @@ var Channel = class {
|
|
|
297
303
|
__privateGet(this, _config).destination
|
|
298
304
|
]));
|
|
299
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* Add a send effect on a parallel bus.
|
|
308
|
+
*
|
|
309
|
+
* The send is **post-fader**: it taps the channel signal after the volume
|
|
310
|
+
* gain (and after any inserts added via {@link addInsert}), before the
|
|
311
|
+
* panner. Lowering `volume` proportionally lowers the send level; `volume = 0`
|
|
312
|
+
* silences the send too. Inserts are upstream of the tap, so they are heard
|
|
313
|
+
* on the send.
|
|
314
|
+
*/
|
|
300
315
|
addEffect(name, effect, mixValue) {
|
|
301
316
|
var _a;
|
|
302
317
|
if (__privateGet(this, _disconnected)) {
|
|
@@ -386,7 +401,7 @@ function pickPlaybackParams(obj) {
|
|
|
386
401
|
return result;
|
|
387
402
|
}
|
|
388
403
|
function resolveParams(defaults, group, region, midi, velocity, overrides) {
|
|
389
|
-
var _a, _b, _c, _d, _e;
|
|
404
|
+
var _a, _b, _c, _d, _e, _f;
|
|
390
405
|
const merged = __spreadValues(__spreadValues(__spreadValues(__spreadValues({}, PARAM_DEFAULTS), defaults), pickPlaybackParams(group)), pickPlaybackParams(region));
|
|
391
406
|
const pitch = (_b = (_a = region.pitch) != null ? _a : region.key) != null ? _b : midi;
|
|
392
407
|
const semitones = midi - pitch;
|
|
@@ -403,9 +418,8 @@ function resolveParams(defaults, group, region, midi, velocity, overrides) {
|
|
|
403
418
|
loop: (_e = overrides == null ? void 0 : overrides.loop) != null ? _e : merged.loop,
|
|
404
419
|
loopStart: merged.loopStart,
|
|
405
420
|
loopEnd: merged.loopEnd,
|
|
406
|
-
ampVelCurve: region.ampVelCurve,
|
|
407
421
|
loopAuto: region.loopAuto,
|
|
408
|
-
reverse: overrides == null ? void 0 : overrides.reverse
|
|
422
|
+
reverse: (_f = overrides == null ? void 0 : overrides.reverse) != null ? _f : merged.reverse
|
|
409
423
|
};
|
|
410
424
|
}
|
|
411
425
|
|
|
@@ -583,14 +597,6 @@ var SampleLoaderImpl = class {
|
|
|
583
597
|
__privateSet(this, _context, context);
|
|
584
598
|
__privateSet(this, _storage, (_a = options == null ? void 0 : options.storage) != null ? _a : HttpStorage);
|
|
585
599
|
}
|
|
586
|
-
/**
|
|
587
|
-
* Load all samples referenced in `json`. Returns a Map of sample name →
|
|
588
|
-
* AudioBuffer. Progress is reported via `onProgress` callback or via
|
|
589
|
-
* options object.
|
|
590
|
-
*
|
|
591
|
-
* - `buffers` in options: pre-loaded buffers — skips fetch for these names.
|
|
592
|
-
* - All samples load in parallel. Failed samples are silently omitted.
|
|
593
|
-
*/
|
|
594
600
|
load(json, onProgressOrOptions) {
|
|
595
601
|
return __async(this, null, function* () {
|
|
596
602
|
var _a, _b;
|
|
@@ -710,14 +716,6 @@ var SchedulerImpl = class {
|
|
|
710
716
|
__privateSet(this, _intervalMs, (_b = options == null ? void 0 : options.intervalMs) != null ? _b : INTERVAL_MS_DEFAULT);
|
|
711
717
|
__privateSet(this, _queue, new SortedQueue((a, b) => a.time - b.time));
|
|
712
718
|
}
|
|
713
|
-
/**
|
|
714
|
-
* Schedule a callback for a NoteEvent.
|
|
715
|
-
*
|
|
716
|
-
* - If the event's time falls within the lookahead window (or has no time), the
|
|
717
|
-
* callback is called synchronously and a no-op StopFn is returned.
|
|
718
|
-
* - Otherwise the event is queued, the interval is started if needed, and a StopFn
|
|
719
|
-
* is returned that removes the event from the queue before it is dispatched.
|
|
720
|
-
*/
|
|
721
719
|
schedule(event, callback) {
|
|
722
720
|
var _a;
|
|
723
721
|
const now = __privateGet(this, _context2).currentTime;
|
|
@@ -733,10 +731,6 @@ var SchedulerImpl = class {
|
|
|
733
731
|
__privateGet(this, _queue).removeAll((q) => q === item);
|
|
734
732
|
};
|
|
735
733
|
}
|
|
736
|
-
/**
|
|
737
|
-
* Clear all queued (not-yet-dispatched) events and stop the interval.
|
|
738
|
-
* Does not affect voices that are already playing.
|
|
739
|
-
*/
|
|
740
734
|
stop() {
|
|
741
735
|
__privateGet(this, _queue).clear();
|
|
742
736
|
if (__privateGet(this, _intervalId) !== void 0) {
|
|
@@ -826,7 +820,7 @@ var Voice = class {
|
|
|
826
820
|
__privateSet(this, _startAt, startAt);
|
|
827
821
|
let offsetSec = 0;
|
|
828
822
|
if (params.offset > 0) {
|
|
829
|
-
offsetSec = params.reverse ?
|
|
823
|
+
offsetSec = params.reverse ? buffer.duration - params.offset : params.offset;
|
|
830
824
|
}
|
|
831
825
|
source.start(startAt, offsetSec);
|
|
832
826
|
__privateSet(this, _source, source);
|
|
@@ -1009,10 +1003,12 @@ var SmplrImpl = class {
|
|
|
1009
1003
|
__privateSet(this, _voices2, new VoiceManager());
|
|
1010
1004
|
this.loader = (_c = options == null ? void 0 : options.loader) != null ? _c : SampleLoader(context, { storage: options == null ? void 0 : options.storage });
|
|
1011
1005
|
if (json) {
|
|
1012
|
-
this.ready = this.loader.load(json,
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1006
|
+
this.ready = this.loader.load(json, {
|
|
1007
|
+
onProgress: (loaded, total) => {
|
|
1008
|
+
var _a2;
|
|
1009
|
+
__privateSet(this, _loadProgress, { loaded, total });
|
|
1010
|
+
(_a2 = __privateGet(this, _onLoadProgress)) == null ? void 0 : _a2.call(this, { loaded, total });
|
|
1011
|
+
}
|
|
1016
1012
|
}).then((buffers) => {
|
|
1017
1013
|
__privateSet(this, _buffers, buffers);
|
|
1018
1014
|
});
|
|
@@ -1751,6 +1747,9 @@ function fetchJSON(url, storage) {
|
|
|
1751
1747
|
return r.json();
|
|
1752
1748
|
});
|
|
1753
1749
|
jsonCache.set(url, p);
|
|
1750
|
+
p.catch(() => {
|
|
1751
|
+
if (jsonCache.get(url) === p) jsonCache.delete(url);
|
|
1752
|
+
});
|
|
1754
1753
|
}
|
|
1755
1754
|
return p;
|
|
1756
1755
|
}
|
|
@@ -2571,6 +2570,7 @@ var SequencerImpl = class {
|
|
|
2571
2570
|
this._clock.stop();
|
|
2572
2571
|
this._stopLoop();
|
|
2573
2572
|
this._endScheduled = false;
|
|
2573
|
+
for (const stopFn of this._activeVoices.values()) stopFn();
|
|
2574
2574
|
this._activeVoices.clear();
|
|
2575
2575
|
this._emitStateChange("stopped");
|
|
2576
2576
|
return this;
|
|
@@ -3074,8 +3074,6 @@ function buildRegion(props, pathFromSampleName) {
|
|
|
3074
3074
|
if (tune !== void 0) region.tune = tune / 100;
|
|
3075
3075
|
const ampRelease = num(props, "ampeg_release");
|
|
3076
3076
|
if (ampRelease !== void 0) region.ampRelease = ampRelease;
|
|
3077
|
-
const ampVelcurve = numArr(props, "amp_velcurve");
|
|
3078
|
-
if (ampVelcurve) region.ampVelCurve = ampVelcurve;
|
|
3079
3077
|
return region;
|
|
3080
3078
|
}
|
|
3081
3079
|
function resolveDefines(sfz) {
|
|
@@ -3153,16 +3151,18 @@ function str(props, key) {
|
|
|
3153
3151
|
if (typeof v === "string") return v;
|
|
3154
3152
|
return void 0;
|
|
3155
3153
|
}
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3154
|
+
|
|
3155
|
+
// src/fetch-ok.ts
|
|
3156
|
+
function fetchOk(url) {
|
|
3157
|
+
return __async(this, null, function* () {
|
|
3158
|
+
const res = yield fetch(url);
|
|
3159
|
+
if (!res.ok) {
|
|
3160
|
+
throw new Error(
|
|
3161
|
+
`smplr: failed to fetch ${url} (${res.status} ${res.statusText})`
|
|
3162
|
+
);
|
|
3163
3163
|
}
|
|
3164
|
-
|
|
3165
|
-
|
|
3164
|
+
return res;
|
|
3165
|
+
});
|
|
3166
3166
|
}
|
|
3167
3167
|
|
|
3168
3168
|
// src/tremolo.ts
|
|
@@ -3208,8 +3208,10 @@ function createTremolo(context, depth) {
|
|
|
3208
3208
|
splitter.disconnect(ampR, 1);
|
|
3209
3209
|
ampL.disconnect(merger, 0, 0);
|
|
3210
3210
|
ampR.disconnect(merger, 0, 1);
|
|
3211
|
-
lfoL.disconnect(
|
|
3212
|
-
|
|
3211
|
+
lfoL.disconnect(lfoLAmp);
|
|
3212
|
+
lfoLAmp.disconnect(ampL.gain);
|
|
3213
|
+
lfoR.disconnect(lfoRAmp);
|
|
3214
|
+
lfoRAmp.disconnect(ampR.gain);
|
|
3213
3215
|
merger.disconnect(output);
|
|
3214
3216
|
};
|
|
3215
3217
|
return { input, output };
|
|
@@ -3262,7 +3264,7 @@ var ElectricPiano = Instrument(
|
|
|
3262
3264
|
};
|
|
3263
3265
|
const tremoloNode = createTremolo(ctx, depth.subscribe);
|
|
3264
3266
|
smplr.output.addInsert(tremoloNode);
|
|
3265
|
-
const ready =
|
|
3267
|
+
const ready = fetchOk(config.sfzUrl).then((r) => r.text()).then(
|
|
3266
3268
|
(sfzText) => {
|
|
3267
3269
|
var _a;
|
|
3268
3270
|
return smplr.loadInstrument(
|
|
@@ -3280,15 +3282,11 @@ var ElectricPiano = Instrument(
|
|
|
3280
3282
|
|
|
3281
3283
|
// src/versilian.ts
|
|
3282
3284
|
var VCSL_BASE_URL = "https://smpldsnds.github.io/sgossner-vcsl";
|
|
3283
|
-
var
|
|
3285
|
+
var instrumentsPromise;
|
|
3284
3286
|
function getVersilianInstruments() {
|
|
3285
|
-
return
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
(res) => res.json()
|
|
3289
|
-
);
|
|
3290
|
-
return instruments;
|
|
3291
|
-
});
|
|
3287
|
+
return instrumentsPromise != null ? instrumentsPromise : instrumentsPromise = fetchOk(
|
|
3288
|
+
VCSL_BASE_URL + "/sfz_files.json"
|
|
3289
|
+
).then((res) => res.json());
|
|
3292
3290
|
}
|
|
3293
3291
|
var Versilian = Instrument(
|
|
3294
3292
|
(ctx, options = {}, smplr) => loadVersilianInstrument(smplr, options)
|
|
@@ -3299,7 +3297,7 @@ function loadVersilianInstrument(smplr, options) {
|
|
|
3299
3297
|
const sfzUrl = `${VCSL_BASE_URL}/${instrument}.sfz`;
|
|
3300
3298
|
const base = instrument.slice(0, instrument.lastIndexOf("/") + 1);
|
|
3301
3299
|
const sampleBaseUrl2 = `${VCSL_BASE_URL}/${base}`;
|
|
3302
|
-
return
|
|
3300
|
+
return fetchOk(sfzUrl).then((r) => r.text()).then(
|
|
3303
3301
|
(sfzText) => smplr.loadInstrument(
|
|
3304
3302
|
sfzToPreset(sfzText, {
|
|
3305
3303
|
baseUrl: sampleBaseUrl2,
|
|
@@ -3404,7 +3402,7 @@ var Mellotron = Instrument(
|
|
|
3404
3402
|
const variation = INSTRUMENT_VARIATIONS[instrument];
|
|
3405
3403
|
const instrumentName = variation ? variation[0] : instrument;
|
|
3406
3404
|
const baseUrl = `https://smpldsnds.github.io/archiveorg-mellotron/${instrumentName}/`;
|
|
3407
|
-
return
|
|
3405
|
+
return fetchOk(baseUrl + "files.json").then((r) => r.json()).then(
|
|
3408
3406
|
(names) => smplr.loadInstrument(
|
|
3409
3407
|
mellotronToPreset(names, {
|
|
3410
3408
|
instrument: instrumentName,
|
|
@@ -3420,7 +3418,7 @@ function mellotronToPreset(sampleNames, config) {
|
|
|
3420
3418
|
for (const sampleName of sampleNames) {
|
|
3421
3419
|
if (config.variation && !sampleName.includes(config.variation)) continue;
|
|
3422
3420
|
const midi = toMidi((_a = sampleName.split(" ")[0]) != null ? _a : "");
|
|
3423
|
-
if (
|
|
3421
|
+
if (midi === void 0) continue;
|
|
3424
3422
|
entries.push([midi, sampleName]);
|
|
3425
3423
|
}
|
|
3426
3424
|
const spread = spreadKeyRanges(entries);
|
|
@@ -3473,7 +3471,7 @@ function createDattorroReverbEffect(context) {
|
|
|
3473
3471
|
if (!ready) {
|
|
3474
3472
|
const blob = new Blob([PROCESSOR], { type: "application/javascript" });
|
|
3475
3473
|
const url = URL.createObjectURL(blob);
|
|
3476
|
-
ready = context.audioWorklet.addModule(url);
|
|
3474
|
+
ready = context.audioWorklet.addModule(url).finally(() => URL.revokeObjectURL(url));
|
|
3477
3475
|
init.set(context, ready);
|
|
3478
3476
|
}
|
|
3479
3477
|
yield ready;
|
|
@@ -3505,7 +3503,7 @@ var ReverbImpl = class {
|
|
|
3505
3503
|
}
|
|
3506
3504
|
getParam(name) {
|
|
3507
3505
|
var _a;
|
|
3508
|
-
return (_a = __privateGet(this, _effect)) == null ? void 0 : _a.parameters.get(
|
|
3506
|
+
return (_a = __privateGet(this, _effect)) == null ? void 0 : _a.parameters.get(name);
|
|
3509
3507
|
}
|
|
3510
3508
|
get isReady() {
|
|
3511
3509
|
return __privateGet(this, _effect) !== void 0;
|
|
@@ -3514,11 +3512,13 @@ var ReverbImpl = class {
|
|
|
3514
3512
|
return __privateGet(this, _ready);
|
|
3515
3513
|
}
|
|
3516
3514
|
connect(output) {
|
|
3517
|
-
if (__privateGet(this, _effect)) {
|
|
3518
|
-
__privateGet(this, _effect).disconnect(__privateGet(this, _output));
|
|
3519
|
-
__privateGet(this, _effect).connect(output);
|
|
3520
|
-
}
|
|
3521
3515
|
__privateSet(this, _output, output);
|
|
3516
|
+
__privateGet(this, _ready).then(() => {
|
|
3517
|
+
if (__privateGet(this, _effect)) {
|
|
3518
|
+
__privateGet(this, _effect).disconnect();
|
|
3519
|
+
__privateGet(this, _effect).connect(__privateGet(this, _output));
|
|
3520
|
+
}
|
|
3521
|
+
});
|
|
3522
3522
|
}
|
|
3523
3523
|
};
|
|
3524
3524
|
_effect = new WeakMap();
|
|
@@ -3665,7 +3665,7 @@ var Smolken = Instrument(
|
|
|
3665
3665
|
(ctx, options = {}, smplr) => {
|
|
3666
3666
|
var _a;
|
|
3667
3667
|
const sfzUrl = getSmolkenUrl((_a = options.instrument) != null ? _a : "Arco");
|
|
3668
|
-
return
|
|
3668
|
+
return fetchOk(sfzUrl).then((r) => r.text()).then(
|
|
3669
3669
|
(sfzText) => smplr.loadInstrument(
|
|
3670
3670
|
sfzToPreset(sfzText, {
|
|
3671
3671
|
baseUrl: SMOLKEN_BASE_URL,
|
|
@@ -3826,16 +3826,14 @@ function fetchSoundfontLoopData(url, sampleRate = 44100) {
|
|
|
3826
3826
|
return __async(this, null, function* () {
|
|
3827
3827
|
if (!url) return void 0;
|
|
3828
3828
|
try {
|
|
3829
|
-
const req = yield
|
|
3830
|
-
if (req.status !== 200) return;
|
|
3829
|
+
const req = yield fetchOk(url);
|
|
3831
3830
|
const raw = yield req.json();
|
|
3832
3831
|
const loopData = {};
|
|
3833
3832
|
Object.keys(raw).forEach((key) => {
|
|
3834
3833
|
const midi = toMidi(key);
|
|
3835
|
-
if (midi)
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
}
|
|
3834
|
+
if (midi === void 0) return;
|
|
3835
|
+
const offsets = raw[key];
|
|
3836
|
+
loopData[midi] = [offsets[0] / sampleRate, offsets[1] / sampleRate];
|
|
3839
3837
|
});
|
|
3840
3838
|
return loopData;
|
|
3841
3839
|
} catch (err) {
|
|
@@ -3880,7 +3878,7 @@ function decodeSoundfontFile(context, config) {
|
|
|
3880
3878
|
yield Promise.all(
|
|
3881
3879
|
noteNames.map((noteName) => __async(null, null, function* () {
|
|
3882
3880
|
const midi = toMidi(noteName);
|
|
3883
|
-
if (
|
|
3881
|
+
if (midi === void 0) return;
|
|
3884
3882
|
try {
|
|
3885
3883
|
const audioData = base64ToArrayBuffer(
|
|
3886
3884
|
removeBase64Prefix(json[noteName])
|
|
@@ -3981,7 +3979,7 @@ function removeBase64Prefix(audioBase64) {
|
|
|
3981
3979
|
return audioBase64.slice(audioBase64.indexOf(",") + 1);
|
|
3982
3980
|
}
|
|
3983
3981
|
function base64ToArrayBuffer(base64) {
|
|
3984
|
-
const decoded =
|
|
3982
|
+
const decoded = atob(base64);
|
|
3985
3983
|
const len = decoded.length;
|
|
3986
3984
|
const bytes = new Uint8Array(len);
|
|
3987
3985
|
for (let i = 0; i < len; i++) {
|
|
@@ -4038,7 +4036,11 @@ var Soundfont2 = Instrument(
|
|
|
4038
4036
|
const sf2inst = soundfont == null ? void 0 : soundfont.instruments.find(
|
|
4039
4037
|
(inst) => inst.header.name === instrumentName
|
|
4040
4038
|
);
|
|
4041
|
-
if (!sf2inst)
|
|
4039
|
+
if (!sf2inst) {
|
|
4040
|
+
throw new Error(
|
|
4041
|
+
`Soundfont2: instrument "${instrumentName}" not found`
|
|
4042
|
+
);
|
|
4043
|
+
}
|
|
4042
4044
|
const { json, buffers } = sf2InstrumentToPreset(sf2inst, ctx);
|
|
4043
4045
|
return baseLoadInstrument(json, buffers);
|
|
4044
4046
|
}
|