import type { EaseShare } from '../../core/model/animation'; import type { SequencePlay } from '../../core/music/playbacks'; import type { FxParams, FxPlan, FxVoice } from '../../core/soundfx/nodes'; /** * What the runtime needs of the outside world to make a noise – the counterpart of `Input` on the * way out. The browser side of it is `viewport/SoundLibrary`; a test (and a run started before * anything wired one in) gets `SILENT_AUDIO` and plays to nobody. * * Files, not asset ids: resolving the id is the `BehaviorSystem`'s job, because it is the one * holding the project. Everything below this line only knows bytes and voices. */ export interface AudioSink { /** * Starts one voice. `volume` is final (0–1) and every call overlaps whatever is already playing. * `owner` is the behavior the sound belongs to: when that behavior is destroyed the voice goes * with it, because a sound is something a thing in the world is doing, not a thing of its own. * `''` means the voice has no owner and outlives whatever started it. `fx` is the sound FX the * voice passes on its way out (#311), with the values of its parameters; `DRY_VOICE` passes none. * Returns the number of the voice, never used twice, or 0 where nothing is going to play. */ play(file: string, volume: number, loop: boolean, owner: string, fx?: FxVoice): number; /** * Starts one note of an instrument (#331), as `play` starts a sound: `layers` are what the note is * made of, played side by side as one voice, each way reading the file of its own source. `name` * stands for the instrument. `params` fill in the parameters of the plans, its velocity among them. * A `duration` above 0 releases it that many seconds after it starts; 0 holds it. */ playNote( name: string, layers: readonly FxPlan[], params: FxParams, volume: number, owner: string, duration: number, timing?: NoteTiming, ): number; /** * Plays the notes of a pattern on the audio clock, each struck through `play.strike`. `name` stands * for the pattern as a file does for a sound, and the number it gives back is one no voice has: * `stopVoice` and `playing` take it like a voice's, `stopOwner` and stopping everything reach it. * Returns 0 where nothing is going to play. */ playSequence(name: string, play: SequencePlay, owner: string): number; /** Stops every pattern of that name, `''` every pattern; with `letRing` its notes fall away in their release. */ stopSequence(name: string, letRing?: boolean): void; /** * The end of a note (#331): its ways that end with the note fade out, those that play through * go on. Nothing happens for a voice that is no note or not there. */ release(voice: number): void; /** * Hands new values to the sound FX parameters of one voice, by the number `play` gave it (#315). * A name its FX does not have is left out, one not given keeps its value. A voice that has ended * is not there any more and nothing happens – a detached one is reached like any other. `time` is * how many seconds the values take to get there, counted on the audio clock from now – or from * the moment the voice really starts, where it still waits on its bytes, along `ease` (linear * where it is not given). 0 is a quick glide. */ tune(voice: number, params: FxParams, time?: number, ease?: EaseShare): void; /** * How many seconds one run of that file lasts, the same with a pitch it starts on (#312); 0 while * its bytes have not arrived. */ length(file: string): number; /** * How many seconds one voice rings on after its sound has ended (#324), at the values it has now: * the echoes of its FX; 0 where it has none or is not there. */ tail(voice: number): number; /** * Silences every voice of that file; `''` silences all of them, patterns too. A voice is faded out in a few * milliseconds, its echoes with it – unless `letRing`, which stops the sound and lets them fade. */ stop(file: string, letRing?: boolean): void; /** Silences one voice, by the number `play` gave it, the same way. */ stopVoice(voice: number, letRing?: boolean): void; /** Silences every voice started by that owner – what destroying a behavior does. */ stopOwner(owner: string): void; } /** * When a note is heard (#332): `at` is the second of the audio clock it starts at, now where it is * left out or already past; `tempo` is what a Delay synced to beats counts by, 120 where it is left out. */ export interface NoteTiming { at?: number; tempo?: number; /** The track of a song it is heard on (#334); straight out where left out. */ bus?: string; } export const SILENT_AUDIO: AudioSink = { play: () => 0, playNote: () => 0, playSequence: () => 0, stopSequence: () => {}, release: () => {}, tune: () => {}, length: () => 0, tail: () => 0, stop: () => {}, stopVoice: () => {}, stopOwner: () => {}, };