Skip to content

Plugin API

@amsvs/api defines a plugin’s public package structure, inspector parameters, render operations, and supporting types. It does not prescribe the application-specific structure of an authored track, phoneme plan, or engine score. A language or format package supplies those domain types.

import { f32simd, param, params, plugin } from "@amsvs/api";
import type {
LanguageResult,
PluginBuilder,
PluginDiagnostic,
PostPitchContext,
} from "@amsvs/api";
  • plugin(id) creates a fluent package definition.
  • param creates typed inspector fields.
  • params(schema) creates a standalone parameter schema.
  • f32simd provides numeric helpers for Float32Array data.
  • PluginBuilder is the package-definition interface.
  • PostPitchContext is the pitch-hook input.
  • PluginDiagnostic is an operation diagnostic.

The SDK is generic at the package boundary. A parameter schema contributes its inferred value type to the builder and to this.params in each hook.

const controls = {
depth: param.number({ default: 25, min: 0, max: 100 }),
enabled: param.boolean({ default: true }),
};
type Controls = {
depth: number;
enabled: boolean;
};

The equivalent builder shape is PluginBuilder<Controls>. In ordinary code, the SDK infers Controls from the object passed to .params(); authors do not normally need to write the generic argument.


plugin(id: string): PluginBuilder starts a root package. Use a stable, absolute identifier, normally a reverse-domain name such as com.example.vibrato. Complete a package by calling .register().

plugin("com.example.vibrato")
.name("Vibrato")
.hooks({
stage: "post_pitch",
postPitch(ctx) {
return ctx.f0;
},
})
.register();

PluginBuilder is chainable. Its generic type records the fields available through this.params, and a member builder records its parent unit.

interface PluginBuilder<P, Parent = undefined> {
params<S>(schema: S, options?: ParamGroupOptions): PluginBuilder<P & InferParams<S>, Parent>;
member(id: string, configure: (child: PluginBuilder<{}, UnitSelf<P, Parent>>) => unknown): this;
register(): RegisteredPlugin<PluginDef<P, Parent>, P, Parent>;
}

id(id: string) sets a unit identifier. Root packages already receive their identifier from plugin(id). A member normally receives a relative identifier through .member().

name(name: string) sets the package’s display name. If omitted, Amadeus uses the identifier where a name is required.

version(version: string) records package version metadata. It is distinct from SCHEMA_REV, which identifies SDK compatibility.

role("language" | "hook") explicitly sets the package role. It is usually inferred: a unit that defines plan or finalize is a language unit; other hook units use a stage.

stage("post_pitch" | "language.plan" | "language.finalize") selects a hook seam. Use post_pitch for F0 processing, language.plan to patch a phoneme plan, and language.finalize to patch a finalized score.

order(order: number) sets relative execution order within a stage. Lower values execute before higher values. Specify an order only where interaction with another processor is intentional.

preservesUnvoiced(value: boolean) declares whether a post-pitch hook keeps input unvoiced frames unvoiced. The default is true. Set false only when the processor is designed to introduce voiced data.

params(schema, options?) adds one inspector group and extends this.params with the schema’s inferred fields. The optional group settings are label, group (an alias), stages, stage (an alias), and scope.

.params(
{ depth: param.number({ default: 25, min: 0, max: 100 }) },
{ label: "Vibrato", stages: ["pitch"] },
)

A group is global by default. The note scope is reserved for per-note values and should not be relied on unless the target Amadeus release documents support for it.

Use as const for a choice list when TypeScript should preserve the literal union rather than widen it to string.

.params({
shape: param.choice({
choices: ["sine", "triangle"] as const,
default: "sine",
}),
})
// this.params.shape: "sine" | "triangle"

hooks(definition) supplies the package’s role, stage, and executable operations. Use method syntax rather than arrow functions: the SDK supplies this.params and, for a member, this.parent.

member(relativeId, configure) adds a nested unit. The relative id is resolved below its parent: member("tone", ...) in lang.vi.vlp becomes lang.vi.vlp.tone. Define parent parameters before the member if the member reads this.parent.params.

Importer and exporter declarations and operations are (WIP).

.registerImport(formatId, displayName, schema, options)

Section titled “ .registerImport(formatId, displayName, schema, options)”

registerImport() registers an importer endpoint and may attach a parameter schema. formatId is the stable format identifier; displayName is its user-facing name.

.registerExport(formatId, displayName, schema, options)

Section titled “ .registerExport(formatId, displayName, schema, options)”

registerExport() registers an exporter endpoint with the same parameter and display-name conventions as .registerImport().

.imports(id, label?) and .exports(id, label?) declare dynamic format endpoints. The object form also accepts extensions, without leading dots, for file-picker filtering.

toDef() returns the current package definition without registration. Use it in tests or tooling that must inspect the package shape.

register() validates and registers the completed definition. It returns the registered unit, including its typed parameter state.


param supplies the field factories used by .params(). Each field requires a default. All factories accept label, help, and cacheScope; numeric factories also accept min, max, and step.

Creates a boolean field. Use it for independent enabled states or binary processing choices.

Creates a numeric field intended for integral values. Set step: 1 where the user should not select fractional values.

Creates a floating-point field. Use a unit in the label when a number has a musical or physical interpretation, for example Depth (cents) or Rate (Hz).

Creates a string field. Use it only for concise configuration values; a language package should validate its expected text format.

Creates an enumerated string field. choices is an ordered array of stable values; default must be one of those values.


params(schema) returns a ParamsBox: a standalone typed parameter schema. .toWire() exposes the inspector field definitions and .bind(preset?) returns a typed unit-state object. Normal package entries may pass a field map directly to .params().

const schema = params({ amount: param.number({ default: 0 }) });
const state = schema.bind({ amount: 12 });
// state.params.amount: number

A cache scope identifies the earliest affected render stage: timing_and_later, f0_and_later, mel_and_later, stem_and_later, or mix_only. Set the narrowest correct scope; an overly narrow scope can retain an incompatible earlier result.

UnitSelf provides this.params, the resolved field values, and this.parent, the immediate parent unit. A root unit has no parent.

interface UnitSelf<P, Parent = undefined> {
readonly params: P;
readonly parent: Parent;
}

Track, PhonePlan, and EngineScore default to unknown. A package may augment Contracts to supply its own compile-time domain model without changing runtime payloads.

declare module "@amsvs/api" {
interface Contracts {
track: import("./domain").AuthoredTrack;
phonePlan: import("./domain").PhonePlan;
engineScore: import("./domain").EngineScore;
}
}

A diagnostic contains severity (info, warning, or error), a stable code, and a user-readable message. It may identify an owner, phone, or 10 ms frame. Return diagnostics for actionable processing outcomes, not ordinary trace output.


plan() receives the authored track and current parameters. It returns a PhonePlan or LanguageResult. A language package should preserve stable phone identities so later timing edits remain associated with the intended phones.

plan(track: Track, parameters: P & Parameters): PhonePlan | LanguageResult

finalize(plan, timingEdits, parameters)

Section titled “ finalize(plan, timingEdits, parameters)”

finalize() receives a phoneme plan, score-relative timing edits, and current parameters. It returns an EngineScore or LanguageResult. A timing edit contains phoneId and boundaryOffset100ns; the offset moves a boundary in 100 ns units and is not a duration.

finalize(
plan: PhonePlan,
timingEdits: TimingEditWire[],
parameters: P & Parameters,
): EngineScore | LanguageResult

postPitch() receives PostPitchContext after pitch generation. Return a same-length, finite Float32Array, or a PostPitchResult with f0 and optional diagnostics. The context F0 is shared through the hook chain.

postPitch(
context: PostPitchContext,
parameters: P & Parameters,
): Float32Array | PostPitchResult

planPatch() receives the authored track and current phoneme plan after the language package plans it. Return a LanguageResult with only the fields that require replacement, or no value when no change is required.

finalizePatch() receives the phoneme plan, timing edits, and engine score after language finalization. Return a partial LanguageResult or no value.


PostPitchContext contains frameMs, mutable f0 in Hz, score-derived scoreF0, one-based alignment, labels, noteIds, and optional acousticRows. An F0 value of zero denotes an unvoiced frame. Return the unchanged shared contour when the hook has no effect. Unless .preservesUnvoiced(false) is declared, preserve unvoiced input frames.

interface PostPitchContext {
frameMs: number;
f0: Float32Array;
scoreF0: Float32Array;
alignment: number[];
labels: LabelSummary[];
noteIds: (string | null)[];
acousticRows?: AcousticRowSummary[];
}

These operations are unavailable until importer and exporter registration is supported.

preImport() examines an input source before importing. It may return suggested parameters and diagnostics.

onImport() creates or returns a track or tracks. An import context supplies file metadata, available casts and channels, file readers, and a mutable project handle. Read binary content with file.readBytes() or file.arrayBuffer(); use file.readText() for text formats.

onImport(context: DynamicImportContext, parameters: P & Parameters):
| Track
| Track[]
| { track: Track; diagnostics?: PluginDiagnostic[] }
| { tracks: Track[]; diagnostics?: PluginDiagnostic[] }
| void

import() is the asynchronous alias of onImport(). It returns a track, tracks, or an object that contains them and may include diagnostics.

postImport() receives imported tracks and may replace them or return diagnostics.

onExport() receives the selected track and an optional format identifier. It returns encoded text, or an object with content and optional diagnostics.

onExport(context: { track: Track; format?: string }, parameters: P & Parameters):
| string
| { content: string; diagnostics?: PluginDiagnostic[] }

export() is the asynchronous alias of onExport().


f32simd operates on Float32Array pitch and DSP data. Most vector functions provide allocating, output-buffer, and in-place variants: for example add(), addInto(), and addInPlace().

hertzToCent() and centToHertz() convert between Hz and cents, with a 440 Hz reference by default. Work in cents for additive pitch offsets.

gainToDb(), dbToGain(), rms(), peak(), normalizeInPlace(), clamp(), and mix() provide level and range operations.

add(), sub(), mul(), scale(), axpy(), dot(), sum(), copyInto(), relu(), abs(), sine(), and sigmoid() provide the remaining vector operations.


PLUGIN_PROTOCOL identifies the package contract. SCHEMA_REV identifies SDK compatibility and is not a package version. Build and test the completed entry against the Amadeus release you intend to support; the distributed type declarations remain the final signature reference.