first commit
This commit is contained in:
86
docs/media/audio-ir/audio-ir-room-plugin-club/README.md
Normal file
86
docs/media/audio-ir/audio-ir-room-plugin-club/README.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# @suaveplan/audio-ir-room-plugin-club - Original audio-ir content package: ships the bundled `club1.wav` and `club2.wav` room impulse responses, registered through @suaveplan/storage-uri-plugin-bundle and @suaveplan/audio-ir-core. Universal; bundle-only content package.
|
||||
|
||||
Original audio-ir content package. Ships two bundled room impulse responses (`club1.wav`, `club2.wav`) wired through the `@suaveplan/audio-ir-*` family.
|
||||
|
||||
This package is the canonical example of the **bundle-only** content-package pattern in the IR family — every future `audio-ir-{family}-plugin-<name>` package that ships only local files follows this shape.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [manifest](./src/manifest/manifest.md) — The `manifest` constant is the single source of truth for which impulse responses this content package ships. It is a `…
|
||||
- [manifest](./src/manifest/manifest.md) — The `manifest` constant is the single source of truth for which impulse responses this content package ships. It is a `…
|
||||
- [register](./src/register/register.md) — This module is the only side-effecting entrypoint a consumer ever calls from `@suaveplan/audio-ir-room-plugin-club`. It…
|
||||
- [register](./src/register/register.md) — This module is the only side-effecting entrypoint a consumer ever calls from `@suaveplan/audio-ir-room-plugin-club`. It…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
bun add @suaveplan/audio-ir-room-plugin-club
|
||||
```
|
||||
|
||||
Peer-installed alongside `@suaveplan/audio-ir-core`, `@suaveplan/storage-uri`, `@suaveplan/storage-uri-plugin-bundle`.
|
||||
|
||||
## Quick start
|
||||
|
||||
```ts
|
||||
import {
|
||||
createIrRegistry,
|
||||
createUriPluginCollector,
|
||||
} from "@suaveplan/audio-ir-core";
|
||||
import { register } from "@suaveplan/audio-ir-room-plugin-club";
|
||||
import { createIrLoader } from "@suaveplan/audio-ir-loader";
|
||||
import { createStorageUriResolver } from "@suaveplan/storage-uri";
|
||||
import { nodeFileFetch } from "@suaveplan/storage-uri-plugin-bundle/node-fetch";
|
||||
import { decodeWav } from "@suaveplan/audio-decode-wav";
|
||||
|
||||
const registry = createIrRegistry();
|
||||
const collector = createUriPluginCollector();
|
||||
register(registry, collector, { fetch: nodeFileFetch }); // omit `fetch` in the browser
|
||||
const resolver = createStorageUriResolver(collector.plugins);
|
||||
|
||||
const loader = createIrLoader({
|
||||
registry,
|
||||
resolver,
|
||||
decoders: { wav: decodeWav },
|
||||
});
|
||||
|
||||
const club1 = await loader.load("club-club1");
|
||||
const club2 = await loader.load("club-club2");
|
||||
```
|
||||
|
||||
## Manifest
|
||||
|
||||
| `id` | URI | Channels | Sample rate | Gain |
|
||||
|---|---|---|---|---|
|
||||
| `club-club1` | `bundle://audio-ir-room-plugin-club/club1.wav` | 2 | 44 100 Hz | 0 dB |
|
||||
| `club-club2` | `bundle://audio-ir-room-plugin-club/club2.wav` | 2 | 44 100 Hz | 0 dB |
|
||||
|
||||
Channels and sample rate are decoded at manifest-authoring time; the loader re-validates and throws `AUDIO_IR_LOADER.MANIFEST_MISMATCH` on disagreement.
|
||||
|
||||
## Modules
|
||||
|
||||
- `./manifest` — `manifest: IrManifest`, `BUNDLE_PACKAGE_SLUG`, `MANIFEST_SLUG`.
|
||||
- `./register` — `register(registry, collector, options?)` (collector pattern).
|
||||
|
||||
The root barrel re-exports every public binding so `import { register } from "@suaveplan/audio-ir-room-plugin-club"` is equivalent to the subpath form.
|
||||
|
||||
## Browser vs Node
|
||||
|
||||
- **Browser**: omit `options.fetch` in `register` — `globalThis.fetch` reads the bundled URL directly.
|
||||
- **Node**: pass `register(registry, collector, { fetch: nodeFileFetch })` from `@suaveplan/storage-uri-plugin-bundle/node-fetch` — Node's undici-backed global `fetch` refuses `file:` URLs.
|
||||
|
||||
## License & sources
|
||||
|
||||
This package is published under the MIT license. Both IR captures (`club1.wav`, `club2.wav`) originate from the operator's Mountain Duck media library at `/media/_/audio/ir/room/club{1,2}.wav` and are redistributed inside Suaveplan under the same MIT license. The IR samples themselves are room captures with no upstream proprietary attribution requirement.
|
||||
|
||||
The `IrManifestEntry.source` field on each entry records the Mountain Duck origin path so a future operator can reproduce the import end-to-end.
|
||||
|
||||
## Pre-publish gate
|
||||
|
||||
```bash
|
||||
bunx turbo lint typecheck test --filter=@suaveplan/audio-ir-room-plugin-club
|
||||
bunx turbo test:coverage --filter=@suaveplan/audio-ir-room-plugin-club # 100/100/100/100
|
||||
bunx turbo build --filter=@suaveplan/audio-ir-room-plugin-club
|
||||
```
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# `manifest` — frozen room-IR manifest for the club package
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The `manifest` constant is the single source of truth for which impulse responses this content package ships. It is a `IrManifest` parsed once through `IrManifestSchema` at module load time, frozen by the schema's `.transform`, and exported for two consumers: (1) `register(registry, resolver)` (the sibling `src/register` module) which walks every entry into the supplied `IrRegistry`, and (2) tooling that wants to enumerate available IRs without touching the storage resolver (catalog UIs, registry warm-up loaders, build-time docs).
|
||||
|
||||
Every value in the manifest is a literal at authoring time. Both `channels` and `sampleRate` were captured by decoding the bundled WAVs through `@suaveplan/audio-decode-wav` and recorded as constants so the manifest can never drift from the on-disk shape. The IR loader (`@suaveplan/audio-ir-loader`) re-validates these fields at decode time and throws `AUDIO_IR_LOADER.MANIFEST_MISMATCH` if they disagree — but for a bundle-only package the on-disk file is also part of the tarball, so the only way the values can disagree is a manual edit, which the manifest-validation gate catches before publish.
|
||||
|
||||
## Features
|
||||
|
||||
- Frozen at parse time — every consumer reads the same `Object.freeze`d value.
|
||||
- Schema-validated id-prefix refinement (`club-*`) and family coherence (`room` on every entry).
|
||||
- Pre-computed bundle URIs that match the package's slug verbatim.
|
||||
- Stable insertion order: `club-club1` before `club-club2`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { manifest } from "@suaveplan/audio-ir-room-plugin-club/manifest";
|
||||
|
||||
console.log(manifest.entries.map((e) => e.id));
|
||||
// → ["club-club1", "club-club2"]
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { manifest, BUNDLE_PACKAGE_SLUG } from "@suaveplan/audio-ir-room-plugin-club/manifest";
|
||||
import { IrManifestSchema } from "@suaveplan/audio-ir-core";
|
||||
import { parseUri } from "@suaveplan/storage-uri";
|
||||
|
||||
// Re-validate (no-op — already parsed at load time, but useful as a smoke test).
|
||||
const reparsed = IrManifestSchema.parse(manifest);
|
||||
console.assert(reparsed.packageSlug === "club");
|
||||
|
||||
// Verify every entry resolves to a well-formed bundle:// URI bound to
|
||||
// THIS package's slug.
|
||||
for (const entry of manifest.entries) {
|
||||
const parts = parseUri(entry.uri);
|
||||
console.assert(parts.scheme === "bundle");
|
||||
console.assert(parts.authority === BUNDLE_PACKAGE_SLUG);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `manifest: IrManifest`
|
||||
|
||||
The frozen, parsed manifest. Two entries, both `family: "room"`, both `gainDb: 0`, both 44.1 kHz stereo.
|
||||
|
||||
### `BUNDLE_PACKAGE_SLUG: "audio-ir-room-plugin-club"`
|
||||
|
||||
The full npm-style slug used as the `bundle://<slug>/<rel>` authority. Exported so tests and the sibling `register` module can reference it without repeating the literal.
|
||||
|
||||
### `MANIFEST_SLUG: "club"`
|
||||
|
||||
The short slug stamped onto every `entry.id` as `<MANIFEST_SLUG>-<filename-stem>`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The two slugs (`MANIFEST_SLUG` and `BUNDLE_PACKAGE_SLUG`) intentionally differ: the manifest slug follows the design's "short id" rule (`design.md` §2) while the bundle slug must match the npm package name so a single resolver can route multiple content packages without collision. The bundle plugin enforces the longer form on `parse`; the registry enforces the shorter form on the entry id. Re-running `IrManifestSchema.parse(manifest)` is idempotent and free of side effects — the schema's `.transform` only freezes, it does not clone.
|
||||
@@ -0,0 +1,130 @@
|
||||
# `register` — collector-pattern wiring entrypoint
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
This module is the only side-effecting entrypoint a consumer ever calls from
|
||||
`@suaveplan/audio-ir-room-plugin-club`. It bridges three pieces that exist in
|
||||
three separate packages: the in-memory `IrRegistry` from
|
||||
`@suaveplan/audio-ir-core` (which holds manifest entries), a
|
||||
`MutableUriPluginRegistrar` collector — typically built via
|
||||
`createUriPluginCollector()` — that gathers `UriPlugin`s before resolver
|
||||
construction, and the `bundleUriPlugin` from
|
||||
`@suaveplan/storage-uri-plugin-bundle` (which knows how to read this
|
||||
package's bundled WAVs).
|
||||
|
||||
The package never auto-registers on import — every consumer calls
|
||||
`register(registry, collector)` explicitly. Importing the package's barrel
|
||||
without calling `register` is a no-op aside from loading the frozen
|
||||
manifest constant. This matches the side-effect-free contract on all
|
||||
`@suaveplan/audio-ir-*` content packages and matches the family-canon
|
||||
shape established by `audio-ir-room-plugin-pool`.
|
||||
|
||||
## Features
|
||||
|
||||
- Single call wires both the bundle adapter (via `target.use(plugin)`) and
|
||||
the manifest registration (via `registerManifest`).
|
||||
- `RegisterOptions.fetch?: FetchLike` lets Node-tier callers pass
|
||||
`nodeFileFetch` so `file://` reads work under undici's global `fetch`.
|
||||
- Default `rootUrl` resolves to the in-tarball `files/` directory via
|
||||
`import.meta.url` — works in both Node and any modern bundler that
|
||||
preserves package URLs.
|
||||
- No `createPlugin()` separate step; no `resolver.hasScheme("bundle")`
|
||||
structural check; the collector pattern makes both unnecessary.
|
||||
- Errors from `registerManifest` (`AUDIO_IR.DUPLICATE_ID`,
|
||||
`AUDIO_IR.ID_PREFIX_MISMATCH`, …) pass through verbatim.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import {
|
||||
createIrRegistry,
|
||||
createUriPluginCollector,
|
||||
} from "@suaveplan/audio-ir-core";
|
||||
import { register } from "@suaveplan/audio-ir-room-plugin-club";
|
||||
import { createStorageUriResolver } from "@suaveplan/storage-uri";
|
||||
|
||||
const registry = createIrRegistry();
|
||||
const collector = createUriPluginCollector();
|
||||
|
||||
register(registry, collector);
|
||||
|
||||
const resolver = createStorageUriResolver(collector.plugins);
|
||||
// registry.has("club-club1") === true
|
||||
// registry.has("club-club2") === true
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
createIrRegistry,
|
||||
createUriPluginCollector,
|
||||
} from "@suaveplan/audio-ir-core";
|
||||
import { createIrLoader } from "@suaveplan/audio-ir-loader";
|
||||
import { decodeWav } from "@suaveplan/audio-decode-wav";
|
||||
import { register } from "@suaveplan/audio-ir-room-plugin-club";
|
||||
import { createStorageUriResolver } from "@suaveplan/storage-uri";
|
||||
import { nodeFileFetch } from "@suaveplan/storage-uri-plugin-bundle/node-fetch";
|
||||
|
||||
// Node.js: globalThis.fetch refuses file:// — wire nodeFileFetch.
|
||||
const registry = createIrRegistry();
|
||||
const collector = createUriPluginCollector();
|
||||
register(registry, collector, { fetch: nodeFileFetch });
|
||||
|
||||
const resolver = createStorageUriResolver(collector.plugins);
|
||||
|
||||
const loader = createIrLoader({
|
||||
registry,
|
||||
resolver,
|
||||
decoders: { wav: decodeWav },
|
||||
});
|
||||
|
||||
const club1 = await loader.load("club-club1");
|
||||
console.log(club1.channelData[0].length, "samples @", club1.sampleRate, "Hz");
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `register(registry: IrRegistry, target: MutableUriPluginRegistrar, options?: RegisterOptions): void`
|
||||
|
||||
Builds the package-scoped `bundleUriPlugin`, hands it to `target.use(plugin)`,
|
||||
then calls `registerManifest(registry, manifest)`.
|
||||
|
||||
- `registry` — an `IrRegistry` from `@suaveplan/audio-ir-core`.
|
||||
- `target` — any object satisfying
|
||||
`MutableUriPluginRegistrar` (`use(plugin: UriPlugin): void`). Typically
|
||||
built via `createUriPluginCollector()`.
|
||||
- `options.fetch?: FetchLike` — overrides the runtime fetch (pass
|
||||
`nodeFileFetch` on Node so `file://` URLs resolve).
|
||||
|
||||
Throws `AUDIO_IR.DUPLICATE_ID` if any manifest entry is already in the
|
||||
registry.
|
||||
|
||||
### `interface RegisterOptions`
|
||||
|
||||
- `readonly fetch?: FetchLike` — see above.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`new URL("../../files/", import.meta.url)` is intentional — the
|
||||
`register/register.ts` file lives at `src/register/register.ts` in the
|
||||
source tree, and at `dist/register/register.js` after build. In both
|
||||
cases two levels up lands on the package root, where `files/` sits at
|
||||
the same level as `dist/` and `src/`. The trailing slash is required by
|
||||
`bundleUriPlugin`'s `rootUrl` validator.
|
||||
|
||||
The `target.use(plugin)` call happens before `registerManifest(...)` so
|
||||
that even when `registerManifest` rejects (for example with
|
||||
`AUDIO_IR.DUPLICATE_ID` on a double-register), the bundle plugin is
|
||||
already in the caller's collector. This matches the family-canon
|
||||
ordering established by `audio-ir-room-plugin-pool` and keeps the
|
||||
collector observable when tests inspect it after a thrown error.
|
||||
|
||||
The double-register test relies on `registerManifest` calling
|
||||
`registry.register(entry, ctx)` for each entry in declaration order. The
|
||||
registry throws `AUDIO_IR.DUPLICATE_ID` on the first colliding id — for
|
||||
this package, `club-club1` — so the rethrown error is deterministic.
|
||||
Reference in New Issue
Block a user