Files
2026-05-27 23:29:53 +01:00

3.1 KiB

audio-core spec delta — refresh-audio-queue-2026-05-23

ADDED Requirements

Requirement: AnalysisOp operator contract

The audio-core package SHALL expose an AnalysisOp<TConfig, TResult> operator contract that every @suaveplan/audio-analysis-* package implements, and a createAnalysisOp builder that constructs conforming operators with Zod-validated config, OTel instrumentation, and AbortSignal-aware cancellation.

AnalysisOp is a discriminated operator with: name: string (unique within an analysis pipeline), version: string (semver), capabilities: AnalysisCapabilities (streaming/offline/realtime/blocking flags), configSchema: z.ZodTypeAny, run(doc: AudioDocument, config: TConfig, opts?: { signal?: AbortSignal }): Promise<TResult>, and runStream?(source: AsyncIterable<AnalysisFrame>, config: TConfig, opts?: { signal?: AbortSignal }): AsyncIterable<TResult>.

AnalysisResult is the base shape every analyser extends — { analyserName: string; analyserVersion: string; sampleRate: number; durationSamples: number; computedAt: number }. Concrete analyser results add their own fields on top.

Scenario: builder validates config via Zod

  • WHEN createAnalysisOp({ name, version, capabilities, configSchema, run }) is called and a downstream caller invokes op.run(doc, badConfig)
  • THEN the builder MUST reject with AudioError("AUDIO_CORE.ANALYSIS_INVALID_CONFIG") carrying the Zod issues on context.issues, before run is invoked

Scenario: builder instruments runs via OpenTelemetry

  • WHEN op.run(doc, validConfig) is called and succeeds
  • THEN the builder MUST emit one OTel span named analysis.${name} with attributes { "analysis.name": name, "analysis.version": version, "audio.sample_rate": doc.sampleRate, "audio.duration_samples": doc.sourceLength }, increment counter audio_analysis_runs_total{analysis=${name}}, and record histogram audio_analysis_duration_ms{analysis=${name}}

Scenario: builder forwards AbortSignal

  • WHEN op.run(doc, validConfig, { signal }) is invoked and signal.aborted === true before run returns
  • THEN the inner run callback MUST observe the signal and the builder MUST surface the abort as AudioError("AUDIO_CORE.ANALYSIS_ABORTED")

Scenario: capability flags advertise streaming support

  • WHEN a caller inspects op.capabilities.supportsStream
  • THEN the value MUST equal typeof op.runStream === "function"

Requirement: AnalysisFrame streaming window descriptor

The audio-core package SHALL export an AnalysisFrame shape — { samples: Float32Array[]; sampleRate: number; channelCount: number; startSample: number; sampleCount: number } — usable as the streaming-input element type for any AnalysisOp.runStream implementation. The shape is types-only; no runtime construction helper is mandated.

Scenario: streaming analyser consumes AnalysisFrame iterable

  • WHEN a spectrum analyser's runStream(source, config) is invoked with an AsyncIterable<AnalysisFrame>
  • THEN the analyser MUST process each frame independently and MAY yield zero, one, or many AnalysisResult-extending values per input frame