updated skills

This commit is contained in:
2026-06-09 21:06:27 +01:00
parent db1ce43ee1
commit 427a5bd919
1283 changed files with 24684 additions and 2313 deletions

View File

@@ -34,10 +34,10 @@ import { AudioDocument } from "@suaveplan/audio-core";
import { BeatDetector } from "@suaveplan/audio-analysis-beat";
const doc = new AudioDocument({
sampleRate: 48_000,
channels: 1,
sourceLength: pcm.length,
pages: [{ channelData: [pcm], startSample: 0 }],
sampleRate: 48_000,
channels: 1,
sourceLength: pcm.length,
pages: [{ channelData: [pcm], startSample: 0 }],
});
const result = await new BeatDetector().analyze(doc);
@@ -65,4 +65,3 @@ console.log(result.bpm, result.beats.length, result.confidence);
See per-module `.md` files for the deep documentation of each algorithm and
entry point.

View File

@@ -30,9 +30,9 @@ gives a confidence near 1, scattered onsets give one near 0.
import { buildBeatGrid } from "@suaveplan/audio-analysis-beat/algorithms/beat-grid";
const result = buildBeatGrid({
period: 60 / 120, // 120 BPM
duration: 10,
onsets: [0.5, 1.0, 1.5, 2.0],
period: 60 / 120, // 120 BPM
duration: 10,
onsets: [0.5, 1.0, 1.5, 2.0],
});
console.log(result.beats.length, result.phase, result.confidence);
```
@@ -46,7 +46,7 @@ import { buildBeatGrid } from "@suaveplan/audio-analysis-beat/algorithms/beat-gr
// more tightly — this is how the BeatDetector breaks ties between candidate
// tempi when an autocorrelation sweep returns two near-equal peaks.
const onsets = new Float64Array([
0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0,
0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0,
]);
const a = buildBeatGrid({ period: 60 / 120, duration: 4.2, onsets, phaseTrials: 48 });

View File

@@ -29,9 +29,9 @@ import { combTempo } from "@suaveplan/audio-analysis-beat/algorithms/comb-tempo"
// ODF sampled every 1024 samples at 48 kHz → hopSeconds = 1024/48000.
const result = combTempo(odf, {
minBpm: 60,
maxBpm: 200,
hopSeconds: 1024 / 48_000,
minBpm: 60,
maxBpm: 200,
hopSeconds: 1024 / 48_000,
});
console.log(result.bpm); // e.g. 120
```
@@ -40,8 +40,8 @@ console.log(result.bpm); // e.g. 120
```ts
import {
combScore,
combTempo,
combScore,
combTempo,
} from "@suaveplan/audio-analysis-beat/algorithms/comb-tempo";
// Draw a tempo-confidence curve over the search range manually:
@@ -50,8 +50,8 @@ const min = 60;
const max = 200;
const curve = new Float64Array(max - min + 1);
for (let bpm = min; bpm <= max; bpm++) {
const period = Math.max(1, Math.round((60 / bpm) / hopSeconds));
curve[bpm - min] = combScore(odf, period);
const period = Math.max(1, Math.round((60 / bpm) / hopSeconds));
curve[bpm - min] = combScore(odf, period);
}
// Fall back to combTempo for the canonical winner including octave handling.

View File

@@ -37,8 +37,8 @@ const odf = energyOdf(energies);
```ts
import {
blockEnergy,
energyOdfFromSamples,
blockEnergy,
energyOdfFromSamples,
} from "@suaveplan/audio-analysis-beat/algorithms/energy-odf";
const sampleRate = 48_000;
@@ -53,10 +53,10 @@ const hopSeconds = blockSize / sampleRate;
let bestIdx = 0;
let best = 0;
for (let i = 0; i < Math.floor(1 / hopSeconds); i++) {
if (odf[i] > best) {
best = odf[i];
bestIdx = i;
}
if (odf[i] > best) {
best = odf[i];
bestIdx = i;
}
}
console.log("onset at", bestIdx * hopSeconds, "seconds");
console.log("avg first-block energy", blockEnergy([left.subarray(0, blockSize)]));

View File

@@ -41,8 +41,8 @@ console.log(odf.length, "frames"); // ceil(192000 / 512) = 375
```ts
import {
magnitudeSpectrum,
spectralFlux,
magnitudeSpectrum,
spectralFlux,
} from "@suaveplan/audio-analysis-beat/algorithms/spectral-flux";
const fftSize = 2048;
@@ -55,8 +55,8 @@ const mono = new Float32Array(48_000 * 10); // 10 seconds
// thread per-frame work through other code, or thin the ODF on the fly.
const odfValues: number[] = [];
for (let start = 0; start + fftSize <= mono.length; start += hop) {
buf.set(mono.subarray(start, start + fftSize));
odfValues.push(spectralFlux(buf, prev, fftSize));
buf.set(mono.subarray(start, start + fftSize));
odfValues.push(spectralFlux(buf, prev, fftSize));
}
// Verify the magnitude spectrum has the expected shape on its own.

View File

@@ -42,10 +42,10 @@ import { AudioDocument } from "@suaveplan/audio-core";
import { BeatDetector } from "@suaveplan/audio-analysis-beat/detector/beat-detector";
const doc = new AudioDocument({
sampleRate: 48_000,
channels: 1,
sourceLength: 48_000 * 4,
pages: [{ startSample: 0, channelData: [Array.from(/* 4 s mono */)] }],
sampleRate: 48_000,
channels: 1,
sourceLength: 48_000 * 4,
pages: [{ startSample: 0, channelData: [Array.from(/* 4 s mono */)] }],
});
const detector = new BeatDetector();
@@ -60,11 +60,11 @@ import { AudioDocument } from "@suaveplan/audio-core";
import { BeatDetector } from "@suaveplan/audio-analysis-beat/detector/beat-detector";
const detector = new BeatDetector({
algorithm: "spectral-flux",
minBpm: 80,
maxBpm: 160,
fftSize: 2048,
hopSize: 1024,
algorithm: "spectral-flux",
minBpm: 80,
maxBpm: 160,
fftSize: 2048,
hopSize: 1024,
});
const controller = new AbortController();
@@ -73,10 +73,10 @@ setTimeout(() => controller.abort(), 5_000);
// Streaming refinement
let final;
for await (const partial of detector.analyzeStream(doc, {
signal: controller.signal,
signal: controller.signal,
})) {
final = partial;
console.log("BPM so far:", partial.bpm, partial.confidence);
final = partial;
console.log("BPM so far:", partial.bpm, partial.confidence);
}
// Or drive it through the audio-core AnalysisOp surface

View File

@@ -34,13 +34,13 @@ right log level without re-deriving it.
```ts
import {
AUDIO_ANALYSIS_BEAT_CODES,
AudioAnalysisBeatError,
AUDIO_ANALYSIS_BEAT_CODES,
AudioAnalysisBeatError,
} from "@suaveplan/audio-analysis-beat/error-codes";
throw new AudioAnalysisBeatError(
AUDIO_ANALYSIS_BEAT_CODES.EMPTY_DOCUMENT,
"audio-analysis-beat.empty-document",
AUDIO_ANALYSIS_BEAT_CODES.EMPTY_DOCUMENT,
"audio-analysis-beat.empty-document",
);
```
@@ -48,21 +48,21 @@ throw new AudioAnalysisBeatError(
```ts
import {
AUDIO_ANALYSIS_BEAT_CODES,
AUDIO_ANALYSIS_BEAT_ERROR_METADATA,
AudioAnalysisBeatError,
AUDIO_ANALYSIS_BEAT_CODES,
AUDIO_ANALYSIS_BEAT_ERROR_METADATA,
AudioAnalysisBeatError,
} from "@suaveplan/audio-analysis-beat/error-codes";
function logError(err: unknown): void {
if (!(err instanceof AudioAnalysisBeatError)) throw err;
const meta = AUDIO_ANALYSIS_BEAT_ERROR_METADATA[err.code];
console[meta.severity === "warning" ? "warn" : "error"](
`[${err.category}] ${err.code}: ${meta.description}`,
{ context: err.context, cause: err.cause },
);
if (err.code === AUDIO_ANALYSIS_BEAT_CODES.ABORTED) return;
if (err.code === AUDIO_ANALYSIS_BEAT_CODES.NO_BEATS_DETECTED) return;
throw err;
if (!(err instanceof AudioAnalysisBeatError)) throw err;
const meta = AUDIO_ANALYSIS_BEAT_ERROR_METADATA[err.code];
console[meta.severity === "warning" ? "warn" : "error"](
`[${err.category}] ${err.code}: ${meta.description}`,
{ context: err.context, cause: err.cause },
);
if (err.code === AUDIO_ANALYSIS_BEAT_CODES.ABORTED) return;
if (err.code === AUDIO_ANALYSIS_BEAT_CODES.NO_BEATS_DETECTED) return;
throw err;
}
```

View File

@@ -53,4 +53,3 @@ console.log({
## License
MIT

View File

@@ -60,4 +60,3 @@ All errors are `AudioAnalysisError`s carrying one of the `AUDIO_ANALYSIS_PITCH.*
## License
MIT

View File

@@ -59,4 +59,3 @@ console.log(result.centroid, result.rolloff, result.flux);
## Status
`0.1.0`. Pre-publish gate (lint + typecheck + test) and 100% coverage are required on every change.

View File

@@ -101,4 +101,3 @@ Stable mapping from `AUDIO_CLI.*` codes onto Unix exit codes:
- 100/100/100/100 coverage on every implementation file.
- Zero mocks — tests drive the real CLI against real `mkdtemp` temp files
and the real `@suaveplan/audio-engine-server` adapters.

View File

@@ -78,4 +78,3 @@ Eight codes namespaced under `AUDIO_CODEC_FFMPEG_NATIVE.*`:
`0.1.0` — first-tier landing. Subpath exports cover every implementation
module so consumers can tree-shake. See the package's OpenSpec change for
the full specification.

View File

@@ -124,4 +124,3 @@ For testing without a real worker, supply:
These are dependency-injection seams, not mocks; production callers
omit them and the real `new Worker(coreUrl)` / dynamic `import()` path
runs unchanged.

View File

@@ -69,10 +69,10 @@ modules.
## Implementation Notes
The COI check intentionally happens *before* the dynamic `import()` so
The COI check intentionally happens _before_ the dynamic `import()` so
that callers without the `@ffmpeg/core-mt` optional dep installed never
incur a module-not-found warning when the COI gate is the real failure.
The fallback path runs only when COI is true *and* the mt import rejects
The fallback path runs only when COI is true _and_ the mt import rejects
— if COI is true and mt loads successfully, no fallback occurs. The
`crossOriginIsolated` override exists so tests can drive both branches
deterministically without manipulating the global.

View File

@@ -7,7 +7,7 @@ _(no submodules)_
## Purpose
This module owns the pure logic of the encode pipeline: validating the
requested format against the supported set *before* the worker is touched,
requested format against the supported set _before_ the worker is touched,
interleaving per-channel PCM into a single `Float32Array`, projecting
that into ffmpeg's `f32le` wire format, building the format-specific
ffmpeg command for each of `wav` / `mp3` / `ogg` / `opus` / `flac` /

View File

@@ -100,7 +100,7 @@ if (fmt === "caf") {
- The format ID at offset 8 of `desc` is `lpcm` (`0x6c70636d`) for
uncompressed integer PCM. The decoder does not currently dispatch on
this field — it assumes `lpcm` and decodes accordingly. Other format
IDs (`alac`, `aac `, `lpcm` floats) would produce wrong output;
IDs (`alac`, `aac`, `lpcm` floats) would produce wrong output;
callers needing compressed CAF support should use the corresponding
WASM codec for the inner format.
- Sample byte order is determined per sample by the

View File

@@ -24,7 +24,7 @@ storage format (PCM int vs IEEE float).
- `WavCodec.canDecode(bytes)` — magic-byte sniff: returns `true` iff the
buffer is at least 12 bytes and starts with `RIFF` … `WAVE`.
- `WavCodec.decode(bytes)` — parses the RIFF header, walks the chunk list
for `fmt ` and `data`, decodes the PCM, and returns a `DecodeResult`
for `fmt` and `data`, decodes the PCM, and returns a `DecodeResult`
with `channelData`, `sampleRate`, and `format: "wav"`.
- `WavCodec.encode(channelData, opts?)` — emits a complete RIFF WAV from
PCM channels at the requested `sampleRate`, `bitDepth`, and
@@ -106,7 +106,7 @@ encoder.close();
- `canDecode(bytes: Uint8Array): boolean`
- `decode(bytes: Uint8Array, opts?: DecodeOpts): Promise<DecodeResult>` —
throws `AudioCodecsError("AUDIO_CODECS.DECODE_FAILED")` on bad magic,
missing `fmt ` chunk, missing `data` chunk, truncated `data`, fmt size
missing `fmt` chunk, missing `data` chunk, truncated `data`, fmt size
< 16, or unsupported `bitsPerSample` (`bps === 0` or `bps > 4`).
- `encode(channelData, opts?: EncodeOpts): Promise<Uint8Array>` — throws
`AudioCodecsError("AUDIO_CODECS.ENCODE_FAILED")` if `channelData` is
@@ -120,7 +120,7 @@ encoder.close();
## Implementation Notes
- The RIFF chunk walker tolerates and skips any chunk whose ID is neither
`fmt ` nor `data` — `LIST`, `bext`, `iXML`, and other metadata chunks
`fmt` nor `data` — `LIST`, `bext`, `iXML`, and other metadata chunks
are passed over silently. This matches the behaviour every real WAV
consumer expects.
- 24-bit PCM uses three signed-magnitude bytes per sample in

View File

@@ -72,4 +72,3 @@ and `bindParameter` integration with the FX plugin family.
## Status
`0.1.0` — Wave 1 migration. 100/100/100/100 coverage.

View File

@@ -98,4 +98,3 @@ for await (const block of doc.stream()) {
- [`src/stats/stats.md`](src/stats/stats.md) — `computeStats` for peak, RMS, and DC offset
- [`src/stream/stream.md`](src/stream/stream.md) — `createPlanTransformStream` for downstream transforms
- [`src/error-codes.md`](src/error-codes.md) — Error codes and `AudioError` class

View File

@@ -78,4 +78,3 @@ try {
- Resampling — surfaced as `RESAMPLE_UNSUPPORTED`; a future `@suaveplan/audio-resample` will pair with this package.
- Non-WAV formats — sibling `audio-decode-{mp3,flac,opus,…}` packages each wrap their own upstream decoder behind the same contract.
- Format dispatching — owned by the upcoming `@suaveplan/audio-decode` umbrella package.

View File

@@ -111,4 +111,3 @@ adapter.dispose();
(`audio_engine_browser_offline_render_duration_ms`).
- 100% test coverage; tests run under `happy-dom` with an injected
context shim.

View File

@@ -69,4 +69,3 @@ exporter is registered and has no import-time side effects.
See the specification for the full
requirement set.

View File

@@ -85,4 +85,3 @@ Both gates hard-fail below 100% coverage. The Python test suite is not currently
- `@suaveplan/react-audio-fingerprint` — the React 19 sibling that runs the browser-tier fingerprinter and, via this package's `python-backend` integration, consults this service for high-accuracy matching.
- `@suaveplan/audio-analysis-spectrum` — the universal FFT/spectrogram primitives the browser-tier fingerprinter is built on.

File diff suppressed because one or more lines are too long

View File

@@ -51,4 +51,3 @@ console.log(jump.length); // encoded byte length
- [`src/types/common.md`](src/types/common.md) — `AudioAdapter` interface, Zod schemas, and public types
- [`src/error-codes.md`](src/error-codes.md) — Error codes and `AudioSpriteError` class
- [`src/__benchmarks__/audio-sprite-packer.bench.md`](src/__benchmarks__/audio-sprite-packer.bench.md) — Benchmark suite notes

View File

@@ -77,4 +77,3 @@ See the co-located `.md` files inside `src/`:
- [`telemetry/telemetry.md`](./src/telemetry/telemetry.md)
- [`schemas.md`](./src/schemas.md)
- [`error-codes.md`](./src/error-codes.md)

View File

@@ -38,4 +38,3 @@ Headless DAW timeline engine with multi-track arrangements, clips, playback, loo
- [zoom](./src/zoom/zoom.md) — The `zoom.ts` module provides a discrete zoom table and navigation helpers for the timeline view. Zoom levels are expre…
- [zoom](./src/zoom/zoom.md) — The `zoom.ts` module provides a discrete zoom table and navigation helpers for the timeline view. Zoom levels are expre…
<!-- end:auto-docs -->

View File

@@ -55,4 +55,3 @@ scheduler.start();
- [Signal](src/signal/signal.md) — tempo automation curves
- [Time](src/time/time.md) — musical time parsing and conversion
- [Error Codes](src/error-codes.md) — structured error registry

View File

@@ -207,4 +207,3 @@ const traktor = {
- [Upgrade](./src/upgrade/upgrade.md)
- [Walk](./src/walk/walk.md)
- [Identity](./src/identity/identity.md)

View File

@@ -87,4 +87,3 @@ const messages = decoder.drain();
## Coverage gate
`bunx turbo test:coverage --filter=@suaveplan/midi` enforces 100/100/100/100 (statements / branches / functions / lines).

View File

@@ -8,7 +8,7 @@ _(no submodules)_
`parseSysEx` and `buildSysEx` are the canonical helpers for working with MIDI System Exclusive payloads — the manufacturer-defined byte streams that sit between an `F0` prefix and an `F7` terminator. Every vendor uses SysEx for patch dumps, parameter sysex, MMC (MIDI Machine Control), and identity-request handshakes; standardising the parse / build code in one place prevents off-by-one errors with the one-byte vs three-byte manufacturer-id forms.
`parseSysEx` expects the *body* of the frame (i.e. everything between `F0` and `F7`, exclusive of both endpoints). `buildSysEx` produces a complete `F0...F7` frame; callers feed the result straight to `MidiPortLike.send`.
`parseSysEx` expects the _body_ of the frame (i.e. everything between `F0` and `F7`, exclusive of both endpoints). `buildSysEx` produces a complete `F0...F7` frame; callers feed the result straight to `MidiPortLike.send`.
## Features

View File

@@ -6,7 +6,7 @@ _(no submodules)_
## Purpose
MIDI 2.0 introduces the Universal MIDI Packet (UMP) format — fixed-width 32, 64, 96, or 128 bit packets that carry MIDI messages alongside high-resolution per-note controllers, per-note pitch, and 32-bit velocity. This module exposes the four packet types, the message-type nibble constants, and a *lossless* bridge between MIDI 1.0 messages and their UMP equivalents.
MIDI 2.0 introduces the Universal MIDI Packet (UMP) format — fixed-width 32, 64, 96, or 128 bit packets that carry MIDI messages alongside high-resolution per-note controllers, per-note pitch, and 32-bit velocity. This module exposes the four packet types, the message-type nibble constants, and a _lossless_ bridge between MIDI 1.0 messages and their UMP equivalents.
The bridge is deliberately one-way for any MIDI 2.0 feature that exceeds MIDI 1.0's expressivity: a MIDI 2.0 channel-voice packet with 32-bit velocity cannot be downgraded to MIDI 1.0 without losing precision, so `umpToMidi1` rejects it with `MidiError("MIDI.UNSUPPORTED_UMP_VARIANT")` rather than silently truncating. Engines that want to "best-effort downgrade" wrap this bridge with their own approximation policy.

View File

@@ -14,4 +14,3 @@
- [types](./src/types/types.md) — Shared types for `@suaveplan/third-party-elevenlabs`. Defines the request / response envelope shapes consumed by the tr…
- [types](./src/types/types.md) — Shared types for `@suaveplan/third-party-elevenlabs`. Defines the request / response envelope shapes consumed by the tr…
<!-- end:auto-docs -->

View File

@@ -143,7 +143,7 @@ WebSocket:
consistently without per-call wiring.
- `MultipartFile` accepts URLs as strings; the client fetches them via
the injected `fetchImpl` before uploading. This is the only place in
the transport that issues a *secondary* network call from a single
the transport that issues a _secondary_ network call from a single
caller invocation.
- `RequestMetadata` keeps every field optional because some endpoints
do not surface `X-RateLimit-Remaining` or `X-Character-Cost`.

View File

@@ -140,4 +140,3 @@ bunx turbo test:coverage --filter=@suaveplan/voice-elevenlabs
```
Coverage gate: 100/100/100/100 statements/branches/functions/lines.

View File

@@ -85,4 +85,3 @@ const result = await synth.synthesize({
Provider-specific extensions (voice cloning, conversational AI, music,
dubbing, voice changing) ship in their own packages under separate
abstractions when those domains stabilise across two or more vendors.