Files
skills/docs/media/audio/audio-cli/src/pipeline/parser.md
2026-05-29 08:20:56 +01:00

2.8 KiB

pipeline/parser — parsePipeline and validatePipeline

(no submodules)

Purpose

This module turns a CLI user's free-form pipeline expression into a typed [opName, params] chain that the rest of the CLI feeds into AudioDocument.run. It supports both the pipe-form string — "trim 30s | normalize -16lufs | fade 0.5s 1s" — and the positional bare-op token array — ["trim", "30s", "normalize", "-16lufs"] — and collapses both surface forms onto the same internal representation so there is exactly one downstream code path.

It also exposes validatePipeline(chain, knownOps) which the CLI calls before opening any file, so an unknown op name throws AUDIO_CLI.UNKNOWN_OP fast and no I/O is wasted.

Features

  • tokenise respects double-quoted strings ("a b") and backslash escapes (a\ b) so a single op can carry a string parameter with spaces.
  • Unit suffix coercion: 30s → start=30, 500ms → start=0.5, -16lufs → lufs=-16, 6db → db=6, 44100hz → rate=44100, 48khz → rate=48000.
  • key=value arguments (foo=bar) become named params; numeric literals without a unit become positional start/duration.
  • validatePipeline lists the registry up-front so the diagnostic names the offending op alongside the sorted set of known ops.

Usage: Basic

import { parsePipeline } from "@suaveplan/audio-cli/pipeline/parser";

const chain = parsePipeline("trim 30s | normalize -16lufs");
// → [["trim", { start: 30 }], ["normalize", { lufs: -16 }]]

Usage: Advanced

import { parsePipeline, validatePipeline } from "@suaveplan/audio-cli/pipeline/parser";
import { OpRegistry } from "@suaveplan/audio-core";

const registry = OpRegistry.createWithDefaults();
const known = new Set(registry.list().map((d) => d.name));
const chain = parsePipeline(["trim", "30s", "normalize", "-16lufs", "fade", "0.5s", "1s"]);
validatePipeline(chain, known);    // throws AUDIO_CLI.UNKNOWN_OP if a name is unknown

API Reference

  • ParsedPipeline — ReadonlyArray<readonly [string, Record<string, unknown>]>.
  • parsePipeline(spec: string | readonly string[]) — produce the chain.
  • validatePipeline(chain, knownOps: ReadonlySet<string>) — throws AudioCliError(AUDIO_CLI.UNKNOWN_OP) on the first unknown op name.

Implementation Notes

The hand-written tokenizer is deliberately small: a single forward pass with two flags (inQuote, escaped) and a per-stage accumulator. The pipe-form splits on |; the bare-op form treats every identifier-shaped token (matching /^[a-zA-Z][a-zA-Z0-9_-]*$/) as the start of a new stage so positional arguments belonging to the previous op are correctly grouped. Unit coercion runs as a small regex per argument so trim 30s always lands as { start: 30 } regardless of which surface form it arrived through.