Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
export type { MutableUriPluginRegistrar } from "@suaveplan/audio-ir-core";
export {
BUNDLE_PACKAGE_SLUG,
BUNDLE_URI,
CDN_URI,
CHANNELS,
ENTRY_ID,
manifest,
SAMPLE_RATE,
} from "./manifest/manifest.js";
export { type RegisterOptions, register } from "./register/register.js";

View File

@@ -0,0 +1,9 @@
export {
BUNDLE_PACKAGE_SLUG,
BUNDLE_URI,
CDN_URI,
CHANNELS,
ENTRY_ID,
manifest,
SAMPLE_RATE,
} from "./manifest.js";

View File

@@ -0,0 +1,89 @@
# `manifest` — single-entry `IrManifest` for the outdoor-swimming-pool IR
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Declares the lone `IrManifestEntry` shipped by `@suaveplan/audio-ir-room-plugin-pool`.
The entry's `uri` is a `fallback://?u=<bundle>&u=<https>` URI built at module
load by `buildFallbackUri` from `@suaveplan/storage-uri-plugin-fallback` —
when the consuming resolver has both a `bundle://` plugin and an `https://`
plugin wired, the shipped `fallback://` walk hits the in-package WAV first
and only reaches for the CDN copy when the local file is absent.
The manifest also pins the **decoded** `channels` and `sampleRate` of the
bundled WAV (`channels = 1`, `sampleRate = 48000`) so the loader's
`validateAndFinalize` step can reject any subsequent re-encode that drifts
from those values. Both numbers were obtained by feeding
`files/outdoor-swimming-pool.wav` through `decodeWav` once at
manifest-authoring time.
## Features
- Round-trips through `IrManifestSchema` at module load — any drift in the
schema fails the build before `dist/` is emitted.
- `BUNDLE_PACKAGE_SLUG`, `BUNDLE_URI`, `CDN_URI`, `ENTRY_ID`, `CHANNELS`,
`SAMPLE_RATE` exported as named `const` literals so consumers can refer
to them without re-deriving from `manifest.entries[0]`.
- Frozen at runtime (`IrManifestSchema` applies `Object.freeze` to the
parsed manifest + every entry).
## Usage: Basic
```ts
import { manifest } from "@suaveplan/audio-ir-room-plugin-pool/manifest";
console.log(manifest.packageSlug); // "pool"
console.log(manifest.entries[0].id); // "pool-outdoor-swimming-pool"
```
## Usage: Advanced
```ts
import {
BUNDLE_URI,
CDN_URI,
ENTRY_ID,
manifest,
} from "@suaveplan/audio-ir-room-plugin-pool/manifest";
import { parseFallbackUri } from "@suaveplan/storage-uri-plugin-fallback";
const entry = manifest.entries[0];
const parsed = parseFallbackUri(entry.uri);
console.log(parsed.candidates); // [BUNDLE_URI, CDN_URI]
console.log(entry.id === ENTRY_ID); // true
```
## API Reference
| Symbol | Kind | Description |
| --- | --- | --- |
| `manifest` | `IrManifest` | Frozen single-entry manifest. |
| `BUNDLE_PACKAGE_SLUG` | `"audio-ir-room-plugin-pool"` | Slug expected by the bundle plugin. |
| `BUNDLE_URI` | string literal | Bundle-local URI for the WAV. |
| `CDN_URI` | string literal | HTTPS CDN URI for the WAV. |
| `ENTRY_ID` | `"pool-outdoor-swimming-pool"` | Entry id within the registry. |
| `CHANNELS` | `1` | Decoded channel count of the WAV. |
| `SAMPLE_RATE` | `48000` | Decoded sample rate of the WAV (Hz). |
## Implementation Notes
The manifest constants are NOT optional — the spec for this package
requires them to equal the values measured by decoding
`files/outdoor-swimming-pool.wav` at authoring time. If the bundled WAV is
ever re-mastered, both `CHANNELS` and `SAMPLE_RATE` MUST be updated to
match the new decode result, and the file-presence integration test
SHOULD re-run as part of that change to assert the new constants.
`buildFallbackUri([BUNDLE_URI, CDN_URI])` runs at module load. The
ordering of candidates IS the failover policy — bundle wins when present,
CDN wins when bundle reports `BUNDLE_URI.NOT_FOUND`. Reversing the array
would silently flip that policy without a separate spec change, so the
constants are kept verbose and explicit rather than inlined.
The `license` field intentionally says "All Rights Reserved — Suaveplan
internal media library" because the source media library has no public
license attached. Consumers SHOULD NOT redistribute the bundled WAV
outside the Suaveplan distribution.

View File

@@ -0,0 +1,63 @@
import { IrManifestSchema } from "@suaveplan/audio-ir-core";
import { parseFallbackUri } from "@suaveplan/storage-uri-plugin-fallback";
import { describe, expect, it } from "@suaveplan/testing/runner";
import {
BUNDLE_PACKAGE_SLUG,
BUNDLE_URI,
CDN_URI,
CHANNELS,
ENTRY_ID,
manifest,
SAMPLE_RATE,
} from "./manifest.js";
describe("manifest", () => {
it("manifest parses through IrManifestSchema", { timeout: 5000 }, () => {
const reparsed = IrManifestSchema.parse(manifest);
expect(reparsed).toEqual(manifest);
expect(manifest.packageSlug).toBe("pool");
expect(manifest.family).toBe("room");
expect(manifest.entries.length).toBe(1);
const entry = manifest.entries[0];
expect(entry).toBeDefined();
if (entry === undefined) throw new Error("unreachable");
expect(entry.id).toBe(ENTRY_ID);
expect(entry.id).toBe("pool-outdoor-swimming-pool");
expect(entry.family).toBe("room");
expect(entry.channels).toBe(CHANNELS);
expect(entry.sampleRate).toBe(SAMPLE_RATE);
expect(entry.gainDb).toBe(0);
expect(entry.license.length).toBeGreaterThan(0);
expect(entry.source.length).toBeGreaterThan(0);
});
it("URI parses through parseFallbackUri with two ordered candidates", {
timeout: 5000,
}, () => {
const entry = manifest.entries[0];
if (entry === undefined) throw new Error("unreachable");
const parsed = parseFallbackUri(entry.uri);
expect(parsed.candidates.length).toBe(2);
const first = parsed.candidates[0];
const second = parsed.candidates[1];
expect(first).toBeDefined();
expect(second).toBeDefined();
if (first === undefined || second === undefined) {
throw new Error("unreachable");
}
expect(first.startsWith("bundle://audio-ir-room-plugin-pool/")).toBe(
true
);
expect(first).toBe(BUNDLE_URI);
expect(second).toBe(CDN_URI);
expect(second).toBe(
"https://swch.b-cdn.net/_/audio/ir/room/roompool.wav"
);
});
it("exports the canonical constants", { timeout: 5000 }, () => {
expect(BUNDLE_PACKAGE_SLUG).toBe("audio-ir-room-plugin-pool");
expect(CHANNELS).toBe(1);
expect(SAMPLE_RATE).toBe(48_000);
});
});

View File

@@ -0,0 +1,67 @@
import type { IrManifest } from "@suaveplan/audio-ir-core";
import { IrManifestSchema } from "@suaveplan/audio-ir-core";
import { buildFallbackUri } from "@suaveplan/storage-uri-plugin-fallback";
/**
* Slug embedded in the bundle URL. MUST match the `packageSlug` passed to
* `bundleUriPlugin({ packageSlug })` inside `register` — the bundle plugin
* uses this to validate that the URL authority matches the consuming
* package.
*/
export const BUNDLE_PACKAGE_SLUG = "audio-ir-room-plugin-pool" as const;
/**
* Bundle-local URI for the shipped WAV. Resolves against the package's
* `files/` directory at runtime via `bundleUriPlugin`.
*/
export const BUNDLE_URI =
"bundle://audio-ir-room-plugin-pool/outdoor-swimming-pool.wav" as const;
/**
* CDN fallback URI. The shipped `fallback://` walk targets this when the
* bundle adapter reports `BUNDLE_URI.NOT_FOUND`.
*/
export const CDN_URI =
"https://swch.b-cdn.net/_/audio/ir/room/roompool.wav" as const;
/**
* Entry id for the lone IR shipped by this package.
*/
export const ENTRY_ID = "pool-outdoor-swimming-pool" as const;
/**
* Decoded channel count of `files/outdoor-swimming-pool.wav` — measured by
* decoding the bundled file at manifest-authoring time with
* `@suaveplan/audio-decode-wav`.
*/
export const CHANNELS = 1 as const;
/**
* Decoded sample rate of `files/outdoor-swimming-pool.wav` (Hz) — measured by
* decoding the bundled file at manifest-authoring time.
*/
export const SAMPLE_RATE = 48_000 as const;
/**
* Single-entry `IrManifest` validated through `IrManifestSchema`.
*
* The lone entry's `uri` is a `fallback://?u=<bundle>&u=<https>` URI in
* declared order — bundle first, CDN second. The shipped `fallback://` plugin
* handles the ordered walk + transient classification.
*/
export const manifest: IrManifest = IrManifestSchema.parse({
packageSlug: "pool",
family: "room",
entries: [
{
id: ENTRY_ID,
family: "room",
uri: buildFallbackUri([BUNDLE_URI, CDN_URI]),
channels: CHANNELS,
sampleRate: SAMPLE_RATE,
gainDb: 0,
license: "All Rights Reserved — Suaveplan internal media library",
source: "Mountain Duck media library + https://swch.b-cdn.net CDN",
},
],
});

View File

@@ -0,0 +1,2 @@
export type { MutableUriPluginRegistrar } from "@suaveplan/audio-ir-core";
export { type RegisterOptions, register } from "./register.js";

View File

@@ -0,0 +1,124 @@
# `register` — install bundle plugin + register manifest
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Single entry point for plugging `@suaveplan/audio-ir-room-plugin-pool` into a
consumer's `IrRegistry` + `StorageUriResolver` composition. Constructs the
package-scoped `bundleUriPlugin` (rooted at the in-package `files/`
directory via `import.meta.url`) and hands it to a `MutableUriPluginRegistrar`,
then calls `registerManifest(registry, manifest)` so the lone
`pool-outdoor-swimming-pool` entry is reachable via `registry.get(id)`.
The `MutableUriPluginRegistrar` shape is the canonical collector interface
shipped by `@suaveplan/audio-ir-core` — see that package's
`registrar/registrar.md` for the type definition and the
`createUriPluginCollector()` factory every consumer should reach for
first. This package only consumes the interface; it does NOT redeclare it.
The helper deliberately does NOT install the `fallback://` plugin or any
`https://` plugin. Both are resolver-composition concerns the consumer
owns once per resolver (the `fallback://` plugin is stateful across
candidates; sharing a single instance keeps the per-candidate counter
attribution consistent across every content package that uses it).
## Features
- One call wires both the bundle adapter and the manifest registration.
- Uses `import.meta.url` to locate `files/` — works in both Node and any
modern bundler that preserves package URLs.
- No fallback plugin registration — single-responsibility wrt content.
- `target` is typed against the foundation's `MutableUriPluginRegistrar`
so a single shape works across every `audio-ir-{family}-plugin-<name>`
content package.
## Usage: Basic
```ts
import {
createIrRegistry,
createUriPluginCollector,
} from "@suaveplan/audio-ir-core";
import { createStorageUriResolver } from "@suaveplan/storage-uri";
import { register } from "@suaveplan/audio-ir-room-plugin-pool/register";
const registry = createIrRegistry();
const collector = createUriPluginCollector();
register(registry, collector);
const resolver = createStorageUriResolver(collector.plugins);
```
## Usage: Advanced
Compose multiple content packages into a single resolver, then wrap an
HTTPS-aware plugin and the shared `fallbackUriPlugin` on top:
```ts
import {
createIrRegistry,
createUriPluginCollector,
} from "@suaveplan/audio-ir-core";
import { createIrLoader } from "@suaveplan/audio-ir-loader";
import { decodeWav } from "@suaveplan/audio-decode-wav";
import { createStorageUriResolver } from "@suaveplan/storage-uri";
import { fallbackUriPlugin } from "@suaveplan/storage-uri-plugin-fallback";
import { register } from "@suaveplan/audio-ir-room-plugin-pool/register";
const registry = createIrRegistry();
const collector = createUriPluginCollector();
register(registry, collector);
// Append the consumer's https://-aware plugin.
const innerPlugins = [...collector.plugins, myHttpsPlugin];
const innerResolver = createStorageUriResolver(innerPlugins);
const outerResolver = createStorageUriResolver([
...innerPlugins,
fallbackUriPlugin({ resolver: innerResolver, policy: { errorClass: "any" } }),
]);
const loader = createIrLoader({
registry,
resolver: outerResolver,
decoders: { wav: decodeWav },
});
const ir = await loader.load("pool-outdoor-swimming-pool");
```
## API Reference
| Symbol | Kind | Description |
| --- | --- | --- |
| `register(registry, target, options?)` | function | Wire the bundle plugin into `target` and register the manifest into `registry`. |
| `RegisterOptions` | interface | Optional `{ fetch?: FetchLike }` override for the bundle adapter (use `nodeFileFetch` under Node). |
| `MutableUriPluginRegistrar` | type | Re-exported from `@suaveplan/audio-ir-core`. Canonical `{ use(plugin: UriPlugin): void }` collector shape. |
## Implementation Notes
`new URL("../../files/", import.meta.url)` is intentional — the
`manifest/manifest.ts` file lives at `src/manifest/manifest.ts` in the
source tree, and at `dist/manifest/manifest.js` after build. In both
cases the relative climb two levels up from the file's URL lands on the
package root, where `files/` lives at the same level as `dist/` and
`src/`. The trailing slash is required by `bundleUriPlugin` — it
rejects `rootUrl` values whose pathname does not end with `/`.
The bundle plugin's `packageSlug` is `"audio-ir-room-plugin-pool"`,
which matches the `bundle://audio-ir-room-plugin-pool/...` authority in
the manifest's URI. Mismatch would surface as `BUNDLE_URI.INVALID_URI`
at first dispatch, not at registration — so the manifest's bundle URI
and `BUNDLE_PACKAGE_SLUG` are both exported from `manifest.ts` as
single-source-of-truth constants.
The helper does NOT clear or re-init the registry. Repeated calls
against the same registry will fail on `AUDIO_IR.DUPLICATE_ID` from
`registerManifest`, which is the desired behaviour — content packages
should install exactly once per process.
`MutableUriPluginRegistrar` is no longer declared locally; the type is
imported from `@suaveplan/audio-ir-core` so every content package
targets exactly one shape. The function body is unchanged across the
type-only switch.

View File

@@ -0,0 +1,115 @@
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import {
createIrRegistry,
createUriPluginCollector,
} from "@suaveplan/audio-ir-core";
import {
createStorageUriResolver,
STORAGE_URI_CODES,
StorageUriError,
type UriPlugin,
} from "@suaveplan/storage-uri";
import { describe, expect, it } from "@suaveplan/testing/runner";
import { ENTRY_ID } from "../manifest/manifest.js";
import { register } from "./register.js";
describe("register", () => {
it("register populates collector and registry via foundation interface", {
timeout: 5000,
}, () => {
const collector = createUriPluginCollector();
const registry = createIrRegistry();
register(registry, collector);
expect(collector.plugins.length).toBe(1);
const installed = collector.plugins[0];
expect(installed).toBeDefined();
if (installed === undefined) throw new Error("unreachable");
expect(installed.scheme).toBe("bundle");
// The resolver, once constructed from the collected plugin list,
// resolves `bundle://...` to this plugin (proves the package put
// the plugin onto the registrar).
const resolver = createStorageUriResolver([...collector.plugins]);
expect(resolver.hasScheme("bundle")).toBe(true);
expect(registry.has(ENTRY_ID)).toBe(true);
const entry = registry.get(ENTRY_ID);
expect(entry.family).toBe("room");
});
it("MutableUriPluginRegistrar is imported from @suaveplan/audio-ir-core not declared locally", {
timeout: 5000,
}, () => {
const sourcePath = fileURLToPath(
new URL("./register.ts", import.meta.url)
);
const source = readFileSync(sourcePath, "utf8");
// Match either the single-line or biome's multi-line wrapped form
// of the type-import. Both must come from `@suaveplan/audio-ir-core`.
expect(source).toMatch(
/import\s+type\s+\{[^}]*\bMutableUriPluginRegistrar\b[^}]*\}\s+from\s+"@suaveplan\/audio-ir-core"/s
);
expect(source).not.toMatch(/^interface MutableUriPluginRegistrar/m);
expect(source).not.toMatch(
/^export interface MutableUriPluginRegistrar/m
);
});
it("does not register the fallback plugin", { timeout: 5000 }, async () => {
const collector = createUriPluginCollector();
const registry = createIrRegistry();
register(registry, collector);
// Build a resolver with ONLY what `register` added — i.e. the
// bundle plugin. The fallback plugin is NOT in the list.
const resolver = createStorageUriResolver([...collector.plugins]);
expect(resolver.hasScheme("fallback")).toBe(false);
// Confirm dispatching a `fallback://` URI through the resolver
// surfaces `STORAGE_URI.UNKNOWN_SCHEME` — proving the package
// did not silently install a fallback plugin behind our back.
await expect(
resolver.read("fallback://?u=bundle%3A%2F%2Fx%2Fy")
).rejects.toMatchObject({
code: STORAGE_URI_CODES.UNKNOWN_SCHEME,
});
});
it("does not register an https plugin either", { timeout: 5000 }, () => {
const collector = createUriPluginCollector();
register(createIrRegistry(), collector);
const resolver = createStorageUriResolver([...collector.plugins]);
expect(resolver.hasScheme("https")).toBe(false);
expect(resolver.hasScheme("http")).toBe(false);
});
it("re-running register throws DUPLICATE_ID", { timeout: 5000 }, () => {
const registry = createIrRegistry();
const collector = createUriPluginCollector();
register(registry, collector);
expect(() => register(registry, collector)).toThrow();
});
it("register returns void and throws on registry shape mismatch", {
timeout: 5000,
}, () => {
// Sanity: passing a non-registry surfaces a thrown error from
// `registerManifest` → `registry.register`, not a silent no-op.
const target = {
use: (_p: UriPlugin) => {},
};
const fakeRegistry = {} as ReturnType<typeof createIrRegistry>;
expect(() => register(fakeRegistry, target)).toThrow();
// Sanity check: StorageUriError shape is what we expect (smoke
// the import).
const err = new StorageUriError(
STORAGE_URI_CODES.UNKNOWN_SCHEME,
"smoke",
{ scheme: "x" }
);
expect(err.code).toBe(STORAGE_URI_CODES.UNKNOWN_SCHEME);
});
});

View File

@@ -0,0 +1,53 @@
import type {
IrRegistry,
MutableUriPluginRegistrar,
} from "@suaveplan/audio-ir-core";
import { registerManifest } from "@suaveplan/audio-ir-core";
import { bundleUriPlugin } from "@suaveplan/storage-uri-plugin-bundle";
import type { FetchLike } from "@suaveplan/types";
import { BUNDLE_PACKAGE_SLUG, manifest } from "../manifest/manifest.js";
/** Optional overrides for `register`. */
export interface RegisterOptions {
/**
* Override the `fetch` implementation passed to `bundleUriPlugin`. Useful
* under Node (which does NOT support `fetch('file://...')`) — pass
* `nodeFileFetch` from `@suaveplan/storage-uri-plugin-bundle/node-fetch`.
* Defaults to `globalThis.fetch`.
*/
readonly fetch?: FetchLike;
}
/**
* Install this content package's bundle plugin onto `target` and register
* every entry from `manifest` into `registry`.
*
* `target` conforms to the canonical `MutableUriPluginRegistrar` interface
* shipped by `@suaveplan/audio-ir-core` — typically a
* `createUriPluginCollector()` instance from the foundation, but any
* `{ use(plugin: UriPlugin): void }` sink works.
*
* `register` does NOT install the shipped `fallback://` plugin — that
* responsibility lives at the resolver-composition layer so a single
* `fallbackUriPlugin` instance is shared across every fallback-using
* content package. It also does NOT install an `https://` plugin —
* choose one (or write a thin `globalThis.fetch` wrapper) at the same
* layer as `fallbackUriPlugin`.
*
* `rootUrl` is computed from `import.meta.url` so the package's
* `files/` directory is located deterministically regardless of where
* the consumer installs the published tarball.
*/
export function register(
registry: IrRegistry,
target: MutableUriPluginRegistrar,
options?: RegisterOptions
): void {
const plugin = bundleUriPlugin({
packageSlug: BUNDLE_PACKAGE_SLUG,
rootUrl: new URL("../../files/", import.meta.url),
...(options?.fetch !== undefined ? { fetch: options.fetch } : {}),
});
target.use(plugin);
registerManifest(registry, manifest);
}