smplr 0.8.0 → 0.8.1

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.ts CHANGED
@@ -94,6 +94,19 @@ type SampleStart = {
94
94
  stopId?: string | number;
95
95
  time?: number;
96
96
  } & SampleOptions;
97
+ type SampleRegion = {
98
+ sampleName: string;
99
+ sampleCenter: number;
100
+ rangeMidi?: [number, number];
101
+ rangeVol?: [number, number];
102
+ offsetVol?: number;
103
+ offsetDetune?: number;
104
+ sample?: Partial<SampleOptions>;
105
+ };
106
+ type SampleLayer = {
107
+ regions: SampleRegion[];
108
+ sample: Partial<SampleOptions>;
109
+ };
97
110
 
98
111
  declare function getDrumMachineNames(): string[];
99
112
  type DrumMachineConfig = ChannelOptions & SampleOptions & {
@@ -305,13 +318,14 @@ declare class Soundfont {
305
318
  readonly context: AudioContext;
306
319
  readonly config: Readonly<SoundfontConfig>;
307
320
  private readonly player;
308
- readonly load: Promise<unknown>;
321
+ readonly load: Promise<this>;
322
+ readonly layer: SampleLayer;
309
323
  constructor(context: AudioContext, options: SoundfontOptions);
310
324
  get output(): OutputChannel;
311
- loaded(): Promise<unknown>;
312
325
  get hasLoops(): boolean;
326
+ loaded(): Promise<this>;
313
327
  disconnect(): void;
314
- start(sample: SampleStart): (time?: number | undefined) => void;
328
+ start(sample: SampleStart | string | number): (time?: number | undefined) => void;
315
329
  stop(sample?: SampleStop | string | number): void;
316
330
  }
317
331
 
package/dist/index.js CHANGED
@@ -140,16 +140,6 @@ function toMidi(note) {
140
140
  function midiVelToGain(vel) {
141
141
  return vel * vel / 16129;
142
142
  }
143
- function findNearestMidi(midi, isAvailable) {
144
- let i = 0;
145
- while (isAvailable[midi + i] === void 0 && i < 128) {
146
- if (i > 0)
147
- i = -i;
148
- else
149
- i = -i + 1;
150
- }
151
- return i === 127 ? [midi, 0] : [midi + i, -i * 100];
152
- }
153
143
 
154
144
  // src/player/signals.ts
155
145
  function createControl(initialValue) {
@@ -1165,7 +1155,7 @@ var Mellotron = class {
1165
1155
  start(sample) {
1166
1156
  const found = findFirstSampleInLayer(
1167
1157
  this.layer,
1168
- typeof sample === "object" ? __spreadValues({}, sample) : { note: sample }
1158
+ typeof sample === "object" ? sample : { note: sample }
1169
1159
  );
1170
1160
  if (!found)
1171
1161
  return () => void 0;
@@ -1366,8 +1356,8 @@ function gleitzKitUrl(name, kit) {
1366
1356
  const format = (_a = findFirstSupportedFormat(["ogg", "mp3"])) != null ? _a : "mp3";
1367
1357
  return `https://gleitz.github.io/midi-js-soundfonts/${kit}/${name}-${format}.js`;
1368
1358
  }
1369
- function soundfontInstrumentLoader(url, storage) {
1370
- return (context, buffers) => __async(this, null, function* () {
1359
+ function soundfontInstrumentLoader(url, buffers, layer) {
1360
+ return (context, storage) => __async(this, null, function* () {
1371
1361
  const sourceFile = yield (yield storage.fetch(url)).text();
1372
1362
  const json = midiJsToJson(sourceFile);
1373
1363
  const noteNames = Object.keys(json);
@@ -1380,9 +1370,14 @@ function soundfontInstrumentLoader(url, storage) {
1380
1370
  removeBase64Prefix(json[noteName])
1381
1371
  );
1382
1372
  const buffer = yield context.decodeAudioData(audioData);
1383
- buffers[midi] = buffer;
1373
+ buffers[noteName] = buffer;
1374
+ layer.regions.push({
1375
+ sampleName: noteName,
1376
+ sampleCenter: midi
1377
+ });
1384
1378
  }))
1385
1379
  );
1380
+ spreadRegions(layer.regions);
1386
1381
  });
1387
1382
  }
1388
1383
  function midiJsToJson(source) {
@@ -1545,26 +1540,25 @@ function getGoldstSoundfontLoopsUrl(instrument, kit) {
1545
1540
  }
1546
1541
  function fetchSoundfontLoopData(url) {
1547
1542
  return __async(this, null, function* () {
1543
+ if (!url)
1544
+ return void 0;
1548
1545
  try {
1549
1546
  const req = yield fetch(url);
1550
1547
  if (req.status !== 200)
1551
- return { status: "error", code: req.status };
1548
+ return;
1552
1549
  const raw = yield req.json();
1553
- const loopData = { status: "loaded", data: {} };
1550
+ const loopData = {};
1554
1551
  const sampleRate = 41e3;
1555
1552
  Object.keys(raw).forEach((key) => {
1556
1553
  const midi = toMidi(key);
1557
1554
  if (midi) {
1558
1555
  const offsets = raw[key];
1559
- loopData.data[midi] = [
1560
- offsets[0] / sampleRate,
1561
- offsets[1] / sampleRate
1562
- ];
1556
+ loopData[midi] = [offsets[0] / sampleRate, offsets[1] / sampleRate];
1563
1557
  }
1564
1558
  });
1565
1559
  return loopData;
1566
1560
  } catch (err) {
1567
- return { status: "error", code: 0 };
1561
+ return void 0;
1568
1562
  }
1569
1563
  });
1570
1564
  }
@@ -1576,64 +1570,79 @@ function getSoundfontKits() {
1576
1570
  function getSoundfontNames() {
1577
1571
  return SOUNDFONT_INSTRUMENTS;
1578
1572
  }
1579
- var _loops;
1573
+ var _hasLoops;
1580
1574
  var Soundfont = class {
1581
1575
  constructor(context, options) {
1582
1576
  this.context = context;
1583
- __privateAdd(this, _loops, void 0);
1577
+ __privateAdd(this, _hasLoops, void 0);
1584
1578
  this.config = getSoundfontConfig(options);
1585
1579
  this.player = new DefaultPlayer(context, options);
1586
- const loader = soundfontInstrumentLoader(
1580
+ this.layer = createEmptySampleLayer();
1581
+ __privateSet(this, _hasLoops, false);
1582
+ const loader = soundfontLoader(
1587
1583
  this.config.instrumentUrl,
1588
- this.config.storage
1584
+ this.config.loopDataUrl,
1585
+ this.player.buffers,
1586
+ this.layer
1589
1587
  );
1590
- this.load = loader(context, this.player.buffers).then(() => this);
1591
- __privateSet(this, _loops, { status: "not-loaded" });
1592
- if (this.config.loopDataUrl) {
1593
- __privateSet(this, _loops, { status: "loading" });
1594
- this.load = Promise.all([
1595
- this.load,
1596
- fetchSoundfontLoopData(this.config.loopDataUrl).then((loopData) => {
1597
- __privateSet(this, _loops, loopData);
1598
- })
1599
- ]).then(() => this);
1600
- }
1588
+ this.load = loader(context, this.config.storage).then((hasLoops) => {
1589
+ __privateSet(this, _hasLoops, hasLoops);
1590
+ return this;
1591
+ });
1601
1592
  const gain = new GainNode(context, { gain: this.config.extraGain });
1602
1593
  this.player.output.addInsert(gain);
1603
1594
  }
1604
1595
  get output() {
1605
1596
  return this.player.output;
1606
1597
  }
1598
+ get hasLoops() {
1599
+ return __privateGet(this, _hasLoops);
1600
+ }
1607
1601
  loaded() {
1608
1602
  return __async(this, null, function* () {
1609
1603
  console.warn("deprecated: use load instead");
1610
1604
  return this.load;
1611
1605
  });
1612
1606
  }
1613
- get hasLoops() {
1614
- return __privateGet(this, _loops).status === "loaded";
1615
- }
1616
1607
  disconnect() {
1617
- console.log("disconnecting");
1618
1608
  this.player.disconnect();
1619
1609
  }
1620
1610
  start(sample) {
1621
- const midi = toMidi(sample.note);
1622
- const [note, detune] = midi === void 0 ? ["", 0] : findNearestMidi(midi, this.player.buffers);
1623
- const loop = typeof midi === "number" && __privateGet(this, _loops).status === "loaded" ? __privateGet(this, _loops).data[midi] : void 0;
1624
- return this.player.start(__spreadProps(__spreadValues({}, sample), {
1625
- note,
1626
- detune,
1627
- loop: loop !== void 0,
1628
- loopStart: loop == null ? void 0 : loop[0],
1629
- loopEnd: loop == null ? void 0 : loop[1]
1630
- }));
1611
+ const found = findFirstSampleInLayer(
1612
+ this.layer,
1613
+ typeof sample === "object" ? sample : { note: sample }
1614
+ );
1615
+ if (!found)
1616
+ return () => void 0;
1617
+ return this.player.start(found);
1631
1618
  }
1632
1619
  stop(sample) {
1633
1620
  return this.player.stop(sample);
1634
1621
  }
1635
1622
  };
1636
- _loops = new WeakMap();
1623
+ _hasLoops = new WeakMap();
1624
+ function soundfontLoader(url, loopsUrl, buffers, layer) {
1625
+ const loadInstrument = soundfontInstrumentLoader(url, buffers, layer);
1626
+ return (context, storage) => __async(this, null, function* () {
1627
+ const [_, loops] = yield Promise.all([
1628
+ loadInstrument(context, storage),
1629
+ fetchSoundfontLoopData(loopsUrl)
1630
+ ]);
1631
+ if (loops) {
1632
+ layer.regions.forEach((region) => {
1633
+ var _a;
1634
+ const loop = loops[region.sampleCenter];
1635
+ if (loop) {
1636
+ (_a = region.sample) != null ? _a : region.sample = {};
1637
+ region.sample.loop = true;
1638
+ region.sample.loopStart = loop[0];
1639
+ region.sample.loopEnd = loop[1];
1640
+ }
1641
+ });
1642
+ }
1643
+ return !!loops;
1644
+ });
1645
+ }
1637
1646
  function getSoundfontConfig(options) {
1638
1647
  var _a, _b, _c, _d;
1639
1648
  if (!options.instrument && !options.instrumentUrl) {