# `stream/stream-encoder` `TransformStream` factory — composes a PCM-block stream into encoded bytes by driving a codec's `StreamingEncoder`. ## Purpose The mirror of `stream-decoder` for the encode direction. Callers building a `@suaveplan/streams` pipeline (decode → DSP → encode) need a `TransformStream` node for the encode stage; this module provides it. ## Features - `createEncodeTransform(format, sampleRate, channels, registry?)` — returns a `TransformStream` where each input `Float32Array[]` chunk represents one block of PCM (one `Float32Array` per channel, same length per channel) and each output `Uint8Array` carries encoded bytes from the codec's `StreamingEncoder`. - Lazy codec resolution — the transform constructs the codec's encoder on first chunk (or on flush if no chunks arrived). The encoder's `start()` is called once to emit header bytes, then `write()` for every chunk, then `flush()` to emit any trailer. - Sample rate + channel count are captured at construction so the encoder's `start()` can pre-compute its header without waiting for the first chunk. - Empty-source handling — even when no PCM chunks arrive, `flush()` still calls `start()` once and emits the codec's trailer (the 44-byte WAV header for the WAV codec). Downstream consumers always receive a structurally-valid file. - Default registry — when `registry` is omitted, the transform lazily loads `WavCodec` into a shared cached registry. Codecs beyond WAV that support encode (FLAC, MP3, Opus, QOA) must be passed explicitly via the `registry` parameter. ## Usage: Basic Example ```ts import { createEncodeTransform } from "@suaveplan/audio-codecs/stream/stream-encoder"; import { CodecRegistry, WavCodec } from "@suaveplan/audio-codecs"; const registry = new CodecRegistry({ codecs: [WavCodec] }); const transform = createEncodeTransform("wav", 44_100, 2, registry); const pcmStream = makePcmBlockStream(); const wavBytes = pcmStream.pipeThrough(transform); await wavBytes.pipeTo(myFileSink); ``` ## Usage: Advanced Example Decode-then-encode pipeline (transcode FLAC → WAV via streaming): ```ts import { createDecodeTransform, createEncodeTransform, } from "@suaveplan/audio-codecs/stream/stream-encoder"; const decodeTx = createDecodeTransform("flac", registry); const encodeTx = createEncodeTransform("wav", 44_100, 2, registry); // The mid pipeline converts AudioPage into Float32Array[] form. const pageToBlock = new TransformStream({ transform(page, controller) { controller.enqueue(page.channelData); }, }); await fetchAudio() .pipeThrough(decodeTx) .pipeThrough(pageToBlock) .pipeThrough(encodeTx) .pipeTo(myDestination); ``` ## API Reference - `function createEncodeTransform(format: string, sampleRate: number, channels: number, registry?: CodecRegistry): TransformStream` — produces a transform that emits encoded byte chunks; errors with `AUDIO_CODECS.NOT_SUPPORTED` for unknown formats or `AUDIO_CODECS.STREAM_ERROR` for codecs without `createEncoder()`. ## Implementation Notes - The `started` flag ensures the encoder's `start(sampleRate, channels)` is invoked exactly once even when no chunks arrive (the `flush()` path also checks the flag and calls `start()` if needed before invoking `flush()`). - WAV's `start()` returns an empty `Uint8Array` because the WAV header includes the total data-chunk size which is only known at flush time; the transform handles that by emitting zero-byte chunks (skipped via the `byteLength > 0` check) so downstream consumers don't see empty data. - Codec-thrown `AudioCodecsError` instances pass through unchanged; non-API errors from `write` or `flush` are wrapped with `STREAM_ERROR`. - The default registry registers only `WavCodec`. Encoding via FLAC, MP3, Opus, or QOA requires the caller to construct a registry with those codecs and pass it as the fourth argument. This choice keeps the default-registry path tree-shakeable when callers don't use streaming. - `cancel()` on the underlying transform stream calls `encoder.close()` to release any internal buffers — even half-streamed encodes leak no resources on cancellation.