Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions packages/core/src/beats/beatDetection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { describe, expect, it } from "vitest";

import { loadBpmDetective } from "./beatDetection";

describe("loadBpmDetective", () => {
it("resolves to null when the import is unavailable", async () => {
const detect = await loadBpmDetective(() => Promise.reject(new Error("missing")));
expect(detect).toBeNull();
});

it("retries a previously failed import instead of caching null forever", async () => {
let shouldFail = true;
const importFn = async () => {
if (shouldFail) {
throw new Error("transient");
}
return { default: () => 120 };
};

const first = await loadBpmDetective(importFn);
expect(first).toBeNull();

shouldFail = false;
const second = await loadBpmDetective(importFn);
expect(second).not.toBeNull();
expect(second?.({} as AudioBuffer)).toBe(120);
});

it("uses the default export if present, otherwise the module object", async () => {
const importFn = async () => ({ default: () => 90 });
const detect = await loadBpmDetective(importFn);
expect(detect?.({} as AudioBuffer)).toBe(90);
});

it("falls back to the module object when there is no default export", async () => {
const fn = (buffer: AudioBuffer) => buffer.duration;
const importFn = async () => fn as unknown;
const detect = await loadBpmDetective(importFn);
expect(detect).toBe(fn);
});
});
34 changes: 25 additions & 9 deletions packages/core/src/beats/beatDetection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,32 @@
// in the browser.
type BpmDetect = (buffer: AudioBuffer) => number;
let bpmDetectivePromise: Promise<BpmDetect | null> | null = null;
function loadBpmDetective(): Promise<BpmDetect | null> {
if (!bpmDetectivePromise) {
bpmDetectivePromise = import(
// @ts-ignore -- no type declarations for bpm-detective
"bpm-detective"
)
.then((m) => ((m as { default?: BpmDetect }).default ?? (m as unknown as BpmDetect)) || null)
.catch(() => null);

const defaultBpmDetectiveImport = () =>
// @ts-ignore -- no type declarations for bpm-detective
import("bpm-detective");

export function loadBpmDetective(
importFn: () => Promise<unknown> = defaultBpmDetectiveImport,
): Promise<BpmDetect | null> {
const useCache = importFn === defaultBpmDetectiveImport;
if (useCache && bpmDetectivePromise) {
return bpmDetectivePromise;
}

const promise = importFn()
.then((m) => ((m as { default?: BpmDetect }).default ?? (m as unknown as BpmDetect)) || null)
.catch(() => {
// Reset the cached promise so a transient import failure can be retried
// on the next call instead of being cached as null for the session.
if (useCache) bpmDetectivePromise = null;
return null;
});

if (useCache) {
bpmDetectivePromise = promise;
}
return bpmDetectivePromise;
return promise;
}

const WINDOW_SIZE = 1024;
Expand Down
Loading