Plugin API
@amsvs/api defines a plugin’s public package structure, inspector parameters, render operations, and supporting types. It includes the built-in TypeScript shapes for the authored track, phoneme plan, and engine score used by language packages; packages may augment those shapes when they need a narrower compile-time model.
import { f32simd, param, params, plugin } from "@amsvs/api";import type { LanguageResult, PluginBuilder, PluginDiagnostic, PostPitchContext,} from "@amsvs/api";import { f32simd, param, params, plugin } from "@amsvs/api";API index
Section titled “API index”plugin(id)creates a fluent package definition.paramcreates typed inspector fields.params(schema)creates a standalone parameter schema.f32simdprovides numeric helpers forFloat32Arraydata.Timenames a canonical 100 ns timeline value.PluginBuilderis the package-definition interface.PostPitchContextis the pitch-hook input.PluginDiagnosticis an operation diagnostic.
Type model
Section titled “Type model”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.
Time is the public type for a timeline value at the plugin boundary. It is an alias of number whose canonical unit is one hundred nanoseconds (100 ns). Importing @amsvs/api also makes Time available as a global TypeScript type; builtin.Time, global.Time, and utility.Time are equivalent namespaced forms.
import type { builtin, global, utility } from "@amsvs/api";
const start: Time = 0;const builtInTime: builtin.Time = start;const globalTime: global.Time = start;const utilityTime: utility.Time = start;Time is type-only. There is no runtime Time value or prototype, and the serialized representation remains a number. The standalone time utility provides unit conversion helpers without changing that representation:
import { Time } from "@amsvs/api";
const start: Time = Time.fromMilliseconds(250);const seconds = Time.toSeconds(start);Forward conversions round to the nearest 100 ns and reject non-finite values. The same utility is also available as lowercase time, and importing the SDK installs the runtime value at globalThis.Time; the type Time remains a primitive number. The Amadeus host kernel also installs globalThis.Time before evaluating a plugin, so plugin code may use the runtime utility without importing it. TypeScript still needs to load the declarations once; configure the package globally:
{ "compilerOptions": { "types": ["@amsvs/api"] }}After that, this is valid without an SDK import:
const a: Time = Time.fromSeconds(1667);Use Time for timeline fields such as the absolute positions start and end, or the signed score-relative offset boundaryOffset. The legacy wire spellings start100ns, end100ns, and boundaryOffset100ns remain accepted for compatibility. Time does not represent score ticks, frame indices, frame-hop milliseconds, hertz, or wall-clock time.
The preferred plugin-facing property names are start, end, and boundaryOffset. The suffixed names remain available for older plugins and are normalized at the host boundary. A timing edit must provide at least one offset spelling; if both are present they must match, and omitting both is invalid.
plugin(id)
Section titled “plugin(id)”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
Section titled “PluginBuilder”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)
Section titled “ .id(id)”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)
Section titled “ .name(name)”name(name: string) sets the package’s display name. If omitted, Amadeus uses the identifier where a name is required.
.version(version)
Section titled “ .version(version)”version(version: string) records package version metadata. It is distinct from SCHEMA_REV, which identifies SDK compatibility.
.role(role)
Section titled “ .role(role)”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(stage)
Section titled “ .stage(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)
Section titled “ .order(order)”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)
Section titled “ .preservesUnvoiced(value)”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)
Section titled “ .params(schema, options)”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)
Section titled “ .hooks(definition)”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(id, configure)
Section titled “ .member(id, configure)”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() and .exports()
Section titled “ .imports() and .exports()”.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()
Section titled “ .toDef()”toDef() returns the current package definition without registration. Use it in tests or tooling that must inspect the package shape.
.register()
Section titled “ .register()”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.
param.boolean(options)
Section titled “ param.boolean(options)”Creates a boolean field. Use it for independent enabled states or binary processing choices.
param.integer(options)
Section titled “ param.integer(options)”Creates a numeric field intended for integral values. Set step: 1 where the user should not select fractional values.
param.number(options)
Section titled “ param.number(options)”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).
param.string(options)
Section titled “ param.string(options)”Creates a string field. Use it only for concise configuration values; a language package should validate its expected text format.
param.choice(options)
Section titled “ param.choice(options)”Creates an enumerated string field. choices is an ordered array of stable values; default must be one of those values.
params(schema)
Section titled “params(schema)”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: numberParameter types
Section titled “Parameter types”PluginCacheScope
Section titled “ PluginCacheScope”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
Section titled “ UnitSelf”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;}Contracts
Section titled “ Contracts”The SDK includes the built-in amadeus.track/v2 and amadeus.language/v2 contracts. Track, PhonePlan, and EngineScore are typed from those shapes by default, so ordinary language packages do not need to maintain a separate domain.ts. A package may still augment Contracts to supply a narrower compile-time domain model without changing runtime payloads.
declare module "@amsvs/api" { interface Contracts { track: MyAuthoredTrack; phonePlan: MyPhonePlan; engineScore: MyEngineScore; }}The concrete built-in pieces are exported as AuthoredTrack, LanguagePhonePlan, EngineRow, LanguageEngineScore, and their supporting types such as PlannedPhone, ContextGroups, and ModuleProvenance. The hook aliases Track, PhonePlan, and EngineScore resolve to these defaults.
Note pitch expression and vibrato are readable structured values, not opaque metadata. PlannedPhone.metadata, PlannedPhone.note, and extension maps use the exported recursive JsonValue/JsonObject types, so a consumer can inspect them while still narrowing values safely at runtime.
PluginDiagnostic
Section titled “ PluginDiagnostic”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.
Language operations
Section titled “Language operations”plan(track, parameters)
Section titled “ plan(track, parameters)”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 | LanguageResultfinalize(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 the preferred boundaryOffset; the legacy boundaryOffset100ns spelling is also accepted. The offset moves a boundary in 100 ns units and is not a duration. Hook input is normalized with both spellings; hook output may use either spelling.
finalize( plan: PhonePlan, timingEdits: TimingEditWire[], parameters: P & Parameters,): EngineScore | LanguageResultHook operations
Section titled “Hook operations”postPitch(context, parameters)
Section titled “ postPitch(context, parameters)”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 | PostPitchResultplanPatch(context, parameters)
Section titled “ planPatch(context, parameters)”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(context, parameters)
Section titled “ finalizePatch(context, parameters)”finalizePatch() receives the phoneme plan, timing edits, and engine score after language finalization. Return a partial LanguageResult or no value.
PostPitchContext
Section titled “PostPitchContext”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[];}Import and export operations
Section titled “Import and export operations”These operations are unavailable until importer and exporter registration is supported.
preImport(context, parameters)
Section titled “ preImport(context, parameters)”preImport() examines an input source before importing. It may return suggested parameters and diagnostics.
onImport(context, parameters)
Section titled “ onImport(context, parameters)”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[] } | voidimport(context, parameters)
Section titled “ import(context, parameters)”import() is the asynchronous alias of onImport(). It returns a track, tracks, or an object that contains them and may include diagnostics.
postImport(context, parameters)
Section titled “ postImport(context, parameters)”postImport() receives imported tracks and may replace them or return diagnostics.
onExport(context, parameters)
Section titled “ onExport(context, parameters)”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(context, parameters)
Section titled “ export(context, parameters)”export() is the asynchronous alias of onExport().
f32simd
Section titled “f32simd”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().
Pitch conversion
Section titled “Pitch conversion”hertzToCent() and centToHertz() convert between Hz and cents, with a 440 Hz reference by default. Work in cents for additive pitch offsets.
Level and dynamics
Section titled “Level and dynamics”gainToDb(), dbToGain(), rms(), peak(), normalizeInPlace(), clamp(), and mix() provide level and range operations.
Vector and shaping
Section titled “Vector and shaping”add(), sub(), mul(), scale(), axpy(), dot(), sum(), copyInto(), relu(), abs(), sine(), and sigmoid() provide the remaining vector operations.
Compatibility
Section titled “Compatibility”PLUGIN_PROTOCOL identifies the package contract. SCHEMA_REV identifies the internal SDK shape and is not a package version. SDK 1.2.1 checks the host’s numeric __amadeusSchemaVersion and requires Amadeus 0.1.0-beta.3 or newer. The check runs once at .register(); a missing or older host contract throws incompatible_host before the package is usable.
The numeric schema version is monotonic: higher levels preserve lower-level behavior, and breaking changes require a new level or a new capability. The editor supplies __amadeusSchemaVersion from the generated host kernel and __amadeusHostVersion from its Rust application build. Build and test the completed entry against the Amadeus release you intend to support; the distributed type declarations remain the final signature reference.