diff --git a/CHANGELOG.md b/CHANGELOG.md index 45f0026..547bdfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # smplr +## 0.16.x + +#### DrumMachine sample groups + +DrumMachines group different samples with same prefix under the same group. For example `tom-1.ogg` and `tom-2.ogg` forms the `tom` group: + +```js +const drums = new DrumMachine(context, { instrument: "TR-808" }); +drum.getSampleNames(); // => ['kick-1', 'kick-2', 'snare-1', 'snare-2', ...] +drum.getGroupNames(); // => ['kick', 'snare'] +drum.getSampleNamesForGroup('kick') => // => ['kick-1', 'kick-2'] +``` + +**Deprecations:** + +- `drum.sampleNames` is deprecated in favour of `drum.getSampleNames()` or `drum.getGroupNames()` +- `drum.getVariations` is now called `drum.getSampleNamesForGroup` + ## 0.15.x #### Disable scheduler with `disableScheduler` option diff --git a/README.md b/README.md index 4de5e87..7343d5b 100644 --- a/README.md +++ b/README.md @@ -467,11 +467,14 @@ const context = new AudioContext(); const drums = new DrumMachine(context, { instrument: "TR-808" }); drums.start({ note: "kick" }); -// Drum samples could have variations: -const now = context.currentTime; -drums.getVariations("kick").forEach((variation, index) => { - drums.start({ note: variation, time: now + index }); -}); +// Drum samples are grouped and can have sample variations: +drums.getSampleNames(); // => ['kick-1', 'kick-2', 'snare-1', 'snare-2', ...] +drums.getGroupNames(); // => ['kick', 'snare'] +drums.getSampleNamesForGroup("kick") => // => ['kick-1', 'kick-2'] + +// You can trigger samples by group name or specific sample +drums.start("kick"); // Play the first sample of the group +drums.start("kick-1"); // Play this specific sample ``` ### Smolken double bass diff --git a/package.json b/package.json index e69321c..d4cc849 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "smplr", - "version": "0.15.1", + "version": "0.16.0", "homepage": "https://github.com/danigb/smplr#readme", "description": "A Sampled collection of instruments", "main": "dist/index.js", diff --git a/site/src/DrumMachineExample.tsx b/site/src/DrumMachineExample.tsx index 0d44962..17fa1d5 100644 --- a/site/src/DrumMachineExample.tsx +++ b/site/src/DrumMachineExample.tsx @@ -90,29 +90,29 @@ export function DrumMachineExample({ className }: { className?: string }) { />
- {drums?.sampleNames.map((sample) => ( -
+ {drums?.getGroupNames().map((group) => ( +
- {drums?.getVariations(sample).map((variation) => ( + {drums?.getSampleNamesForGroup(group).map((sample) => ( diff --git a/src/drum-machine/dm-instrument.ts b/src/drum-machine/dm-instrument.ts index 65cbf3d..e2d0b1e 100644 --- a/src/drum-machine/dm-instrument.ts +++ b/src/drum-machine/dm-instrument.ts @@ -8,9 +8,9 @@ export function isDrumMachineInstrument( typeof instrument.baseUrl === "string" && typeof instrument.name === "string" && Array.isArray(instrument.samples) && - Array.isArray(instrument.sampleNames) && - typeof instrument.nameToSample === "object" && - typeof instrument.sampleNameVariations === "object" + Array.isArray(instrument.groupNames) && + typeof instrument.nameToSampleName === "object" && + typeof instrument.sampleGroupVariations === "object" ); } @@ -18,17 +18,17 @@ export type DrumMachineInstrument = { baseUrl: string; name: string; samples: string[]; - sampleNames: string[]; - nameToSample: Record; - sampleNameVariations: Record; + groupNames: string[]; + nameToSampleName: Record; + sampleGroupVariations: Record; }; export const EMPTY_INSTRUMENT: DrumMachineInstrument = { baseUrl: "", name: "", samples: [], - sampleNames: [], - nameToSample: {}, - sampleNameVariations: {}, + groupNames: [], + nameToSampleName: {}, + sampleGroupVariations: {}, }; export async function fetchDrumMachineInstrument( @@ -39,21 +39,20 @@ export async function fetchDrumMachineInstrument( const json = await res.json(); // need to fix json json.baseUrl = url.replace("/dm.json", ""); - json.sampleNames = []; - json.nameToSample = {}; - json.sampleNameVariations = {}; - for (const sampleSrc of json.samples) { - const sample = - sampleSrc.indexOf("/") !== -1 ? sampleSrc : sampleSrc.replace("-", "/"); - json.nameToSample[sample] = sample; - const [base, variation] = sample.split("/"); - if (!json.sampleNames.includes(base)) { - json.sampleNames.push(base); + json.groupNames = []; + json.nameToSampleName = {}; + json.sampleGroupVariations = {}; + for (const sample of json.samples) { + json.nameToSampleName[sample] = sample; + const separator = sample.indexOf("/") !== -1 ? "/" : "-"; + const [base, variation] = sample.split(separator); + if (!json.groupNames.includes(base)) { + json.groupNames.push(base); } - json.nameToSample[base] ??= sample; - json.sampleNameVariations[base] ??= []; + json.nameToSampleName[base] ??= sample; + json.sampleGroupVariations[base] ??= []; if (variation) { - json.sampleNameVariations[base].push(`${base}/${variation}`); + json.sampleGroupVariations[base].push(`${base}${separator}${variation}`); } } diff --git a/src/drum-machine/drum-machine.test.ts b/src/drum-machine/drum-machine.test.ts index 409021a..63b41b5 100644 --- a/src/drum-machine/drum-machine.test.ts +++ b/src/drum-machine/drum-machine.test.ts @@ -6,12 +6,11 @@ function setup() { "https://smpldsnds.github.io/drum-machines/TR-808/dm.json": { baseUrl: "", name: "", - samples: ["kick/low"], - sampleNames: [], - nameToSample: { kick: "kick/low" }, - sampleNameVariations: {}, + samples: ["kick/low", "kick/mid", "kick/high"], }, - "https://smpldsnds.github.io/drum-machines/TR-808/kick/low.ogg": "kick", + "https://smpldsnds.github.io/drum-machines/TR-808/kick/low.ogg": "kick-l", + "https://smpldsnds.github.io/drum-machines/TR-808/kick/mid.ogg": "kick-m", + "https://smpldsnds.github.io/drum-machines/TR-808/kick/high.ogg": "kick-h", }); const mock = createAudioContextMock(); const context = mock.context; @@ -32,6 +31,20 @@ describe("Drum machine", () => { expect(start).toHaveBeenCalledWith({ note: "kick/low", stopId: "kick" }); }); + it("returns all samples", async () => { + const { context } = setup(); + const dm = await new DrumMachine(context, { + instrument: "TR-808", + }).load; + expect(dm.getSampleNames()).toEqual(["kick/low", "kick/mid", "kick/high"]); + expect(dm.getGroupNames()).toEqual(["kick"]); + expect(dm.getSampleNamesForGroup("kick")).toEqual([ + "kick/low", + "kick/mid", + "kick/high", + ]); + }); + it("calls underlying player on stop", () => { const { context } = setup(); const dm = new DrumMachine(context, { instrument: "TR-808" }); diff --git a/src/drum-machine/drum-machine.ts b/src/drum-machine/drum-machine.ts index 5d9cd7e..c68ef32 100644 --- a/src/drum-machine/drum-machine.ts +++ b/src/drum-machine/drum-machine.ts @@ -80,21 +80,20 @@ export class DrumMachine { }); } - async loaded() { - console.warn("deprecated: use load instead"); - return this.load; + getSampleNames(): string[] { + return this.#instrument.samples.slice(); } - get sampleNames(): string[] { - return this.#instrument.sampleNames; + getGroupNames(): string[] { + return this.#instrument.groupNames.slice(); } - getVariations(name: string): string[] { - return this.#instrument.sampleNameVariations[name] ?? []; + getSampleNamesForGroup(groupName: string): string[] { + return this.#instrument.sampleGroupVariations[groupName] ?? []; } start(sample: SampleStart) { - const sampleName = this.#instrument.nameToSample[sample.note]; + const sampleName = this.#instrument.nameToSampleName[sample.note]; return this.player.start({ ...sample, note: sampleName ? sampleName : sample.note, @@ -105,6 +104,22 @@ export class DrumMachine { stop(sample: SampleStop) { return this.player.stop(sample); } + + /** @deprecated */ + async loaded() { + console.warn("deprecated: use load instead"); + return this.load; + } + /** @deprecated */ + get sampleNames(): string[] { + console.log("deprecated: Use getGroupNames instead"); + return this.#instrument.groupNames.slice(); + } + /** @deprecated */ + getVariations(groupName: string): string[] { + console.warn("deprecated: use getSampleNamesForGroup"); + return this.#instrument.sampleGroupVariations[groupName] ?? []; + } } function drumMachineLoader( @@ -116,10 +131,8 @@ function drumMachineLoader( const format = findFirstSupportedFormat(["ogg", "m4a"]) ?? "ogg"; return instrument.then((data) => Promise.all( - data.samples.map(async (sample) => { - const url = `${data.baseUrl}/${sample}.${format}`; - const sampleName = - sample.indexOf("/") !== -1 ? sample : sample.replace("-", "/"); + data.samples.map(async (sampleName) => { + const url = `${data.baseUrl}/${sampleName}.${format}`; const buffer = await loadAudioBuffer(context, url, storage); if (buffer) buffers[sampleName] = buffer; })