/** Audio a sample loader accepts: encoded file bytes or decoded PCM. */ export declare type AudioInput = ArrayBuffer | AudioBuffer; declare const AudioRecorderState: { readonly IDLE: "IDLE"; readonly ARMED: "ARMED"; readonly RECORDING: "RECORDING"; readonly STOPPED: "STOPPED"; }; declare type AudioRecorderState = (typeof AudioRecorderState)[keyof typeof AudioRecorderState]; declare type BaseNodeType = 'instrument' | 'voice' | 'fx' | 'param' | 'container' | 'recorder' | 'audiograph'; declare type BusNodeName = 'input' | 'output' | 'dryMix' | 'wetMix' | 'hpf' | 'lpf' | 'distortion' | 'feedback' | 'reverb' | 'compressor' | 'limiter' | 'delay'; /** False in Safari — AudioContext.setSinkId is Chromium/Firefox only */ export declare function canSetOutputDevice(): boolean; declare type ContainerType = 'pool' | 'chain' | 'audiolib'; /** * Creates and initializes a new audio recorder * * @param context - Optional AudioContext to use. If not provided, the global audio context will be used. * @returns A promise that resolves to the initialized Recorder instance // ! unnecessary async */ export declare function createAudioRecorder(context?: AudioContext): Promise; /** Throws while a global context is open. To change options, dispose the nodes on it * and `await` its `close()` first: `state` only becomes 'closed' once that resolves. */ export declare function createGlobalAudioContext(options?: AudioContextOptions): AudioContext; /** * Creates a new SamplePlayer instance * * @param options - Optional player configuration; `audio` is loaded on init * @returns A new SamplePlayer instance */ export declare function createSamplePlayer(options?: Partial): Promise; /** Custom Audiolib waveform */ declare const CUSTOM_WAVEFORMS: readonly ["pulse", "bandlimited-sawtooth", "supersaw", "warm-pad", "metallic", "formant", "white-noise", "pink-noise", "brown-noise", "colored-noise", "random-harmonic", "custom-function"]; declare type CustomFxType = 'feedback-delay' | 'harmonic-feedback' | 'distortion' | 'dattorro-reverb'; declare type CustomLibWaveform = (typeof CUSTOM_WAVEFORMS)[number]; export declare const DEFAULT_KEYMAP_KEY: KeymapKey; /** Serializable config shared by editors and envelope players. */ export declare type EnvelopeConfig = { readonly enabled: boolean; /** Depth multiplier, default 1: gain for amp, 1200 cents for pitch, 12000 cents for filter. */ readonly depth?: number; /** Timing multiplier; values above 1 play the envelope faster. */ readonly timeScale: number; /** Scales timing by the note's playback rate, so higher notes run the envelope faster. */ readonly playbackRateSync?: boolean; readonly shape: EnvelopeShape; }; declare type EnvelopeCurve = 'step' | 'linear' | 'exponential'; export declare type EnvelopeMode = /** Play through once. */ { readonly type: 'once'; } /** Hold the envelope's sustain point until release. */ | { readonly type: 'sustain'; } /** Repeat the whole envelope until release. */ | { readonly type: 'loop'; }; export declare type EnvelopePoint = { readonly time: number; readonly value: number; /** Curve from this point to the next one. Defaults to linear. */ readonly curve?: EnvelopeCurve; }; /** * Play through once, hold at sustain, or loop until release. * Loop and sustain are exclusive modes. */ export declare type EnvelopeShape = { /** * Times are offsets from `points[0].time`, so point 0 lands on the trigger time * whatever that value is. A delay before the attack is a second point at the same * value, not a non-zero first time. * * Assumed sorted by time. Nothing re-sorts them on the audio path. */ readonly points: readonly EnvelopePoint[]; /** How the envelope advances until it is released. */ readonly mode: EnvelopeMode; /** Point held while the envelope is in sustain mode. */ readonly sustainPoint: number; /** * Index of the point whose time is the start of the release tail's time scale. * `release()` holds the run's current value at the requested release time, does not set * the parameter to `points[releasePoint].value`, then schedules each later point after the * same time difference it has from `points[releasePoint].time`. The release point's curve * controls the transition to the first later point. * * Independent of a sustain point, though presets normally align them. This does not * mark the end of a loop: a loop repeats the whole envelope. */ readonly releasePoint: number; }; /** * Fade length per side, in milliseconds. "default" is the shortest fade that * still hides a cut at the buffer's sample rate; 0 skips that side. */ declare type FadeMs = { in: number | 'default'; out: number | 'default'; }; /** * Named tap points for level monitoring. A list of nodes under one name is * metered as their sum, not individually. */ export declare type GainStages = Record; /** Device labels are only populated once mic permission has been granted */ export declare function getAudioInputDevices(): Promise; /** Device labels are only populated once mic permission has been granted */ export declare function getAudioOutputDevices(): Promise; /** Output device id of `context` (default: the global AudioContext). '' means system default. */ export declare function getCurrentOutputDeviceId(context?: AudioContext): string; /** Creates the global context on first use, or replaces a closed one using the last * options. May return it suspended; the next user gesture resumes it. */ export declare function getGlobalAudioContext(): AudioContext; /** Adapter interface for web native and Audiolib's custom audio nodes */ declare interface ILibAudioNode extends LibNode { connect(destination: ILibAudioNode | AudioNode): void; disconnect(destination?: ILibAudioNode | AudioNode): void; setParam(name: string, value: number, time?: number): void; getAudioParam(name: string): AudioParam | null; onMessage?(type: string, handler: MessageHandler): () => void; readonly now: number; readonly audioNode: T; readonly context: AudioContext | BaseAudioContext; readonly connections: { outgoing: NodeID_2[]; incoming: NodeID_2[]; }; readonly input: AudioNode; readonly output: AudioNode; } /** Extends ILibAudioNode with core Instrument features */ declare interface ILibInstrumentNode extends ILibAudioNode { play(midiNote: MidiValue, velocity?: number): MidiValue | null; release(note: MidiValue): this; releaseAll(): this; } declare type InstrumentType = 'sample-player' | 'synth'; export declare type KeyMap = Record; export declare type KeymapKey = 'piano' | 'major' | 'minor' | 'pentatonic' | 'chromatic'; export declare const keymaps: Record; declare type KnownNodeType = NativeAudioNode | BaseNodeType | InstrumentType | VoiceType | ContainerType | CustomFxType; declare class LFO { #private; constructor(context: AudioContext); /** * Key sync: replace the oscillator so its phase is 0 at `timestamp`. * The old one stops at the same time, so there is no gap or overlap. * * Call this from a note-on to make the modulation land identically on * every note. Costs one OscillatorNode allocation per call. * * The replacement starts at the frequency last requested via * setFrequency/setMusicalNote. Pending frequency automation on the old * oscillator is dropped, whether a ramp or an event scheduled for a * future timestamp, so skip the retrigger while gliding and set the * frequency after retriggering rather than before. */ retrigger(timestamp?: number): this; setFrequency(hz: number, timestamp?: number): void; /** Signed depth in the connected parameter's units; negative values invert the waveform. */ setDepth(depth: number, timestamp?: number): void; setWaveform(waveform: OscillatorType | PeriodicWave | CustomLibWaveform, customWaveOptions?: WaveformOptions): void; setPeriodicWave(wave: PeriodicWave): void; connect(audioParam: AudioParam): void; disconnect(audioParam?: AudioParam): void; setMusicalNote(midiNote: number, options?: { divisor?: number; glideTime?: number; timestamp?: number; glideFromMidiNote?: number; }): this | undefined; storeCurrentValues: () => void; getStoredValues: () => { rate: number; depth?: number; } | null; getPitchWobbleWaveform(): PeriodicWave; get now(): number; dispose(): void; } declare interface LibNode { readonly nodeId: NodeID_2; readonly nodeType: NodeType; readonly initialized: boolean; dispose(): void; } declare interface Message { readonly type: string; readonly senderId: NodeID_2; [key: string]: any; } declare type MessageHandler = (data: T) => void; declare type NativeAudioNode = 'AudioWorkletNode' | 'GainNode' | 'BiquadFilterNode' | 'AudioBufferSourceNode' | 'DynamicsCompressorNode'; declare type NodeID_2 = string; declare type NodeType = KnownNodeType | (string & Record); declare type PreProcessOptions = { skipPreProcessing?: boolean; normalize?: { enabled: boolean; maxAmplitudePeak?: number; }; compress?: { enabled: boolean; threshold?: number; ratio?: number; makeupGain?: number; }; trimSilence?: { enabled: boolean; threshold?: number; }; fadeMs: FadeMs; tune?: { detectPitch?: boolean; autotune: boolean; targetMidiNote?: number; /** Minimum periodicity [0-1] required to autotune - see detectSinglePitchAC. * Gates noise out, not wrong notes: a chord scores high at its common period. * Transposition wraps to the nearest target note in any octave, so octave * errors are harmless and a loose gate costs little on inharmonic sources. */ minPeriodicity?: number; }; hpf?: { auto?: boolean; } | { cutoff?: number; }; getZeroCrossings?: boolean; }; export declare class Recorder implements LibNode { #private; readonly nodeId: NodeID_2; readonly nodeType = "recorder"; constructor(context: AudioContext); start(options?: RecorderStartOptions): Promise; forceStart(): boolean; /** * Aborts an armed or in-progress recording without producing a buffer. * Releases the input stream, so a new `start()` is required to record again. * * @returns true if a recording was cancelled, false if there was nothing to cancel. */ cancel(): boolean; stop(): Promise; onMessage(type: string, handler: MessageHandler): () => void; protected sendMessage(type: string, data: any): void; connect(destination: LibNode & SampleLoader): this; disconnect(): void; dispose(): void; get isArmed(): boolean; get isRecording(): boolean; get state(): AudioRecorderState; get initialized(): boolean; get now(): number; get destination(): (LibNode & SampleLoader) | null; } export declare type RecorderInput = { type: 'microphone'; deviceId?: string; } | { type: 'display'; } | { type: 'audio-node'; node: AudioNode; }; declare type RecorderOptions = { mediaRecorderOptions: MediaRecorderOptions; useThreshold: boolean; startThreshold: number; autoStop: boolean; stopThreshold: number; silenceTimeoutMs: number; }; export declare type RecorderStartOptions = Partial & { input?: RecorderInput; }; declare const ROOT_NOTES: { readonly C: 0; readonly 'C#': 1; readonly Db: 1; readonly D: 2; readonly 'D#': 3; readonly Eb: 3; readonly E: 4; readonly F: 5; readonly 'F#': 6; readonly Gb: 6; readonly G: 7; readonly 'G#': 8; readonly Ab: 8; readonly A: 9; readonly 'A#': 10; readonly Bb: 10; readonly B: 11; }; /** * Sampler defaults, parameter mapping and trigger options. * Envelope owns scheduling; unfinished behavior is tracked in envelopes/KNOWN-ISSUES.md. */ declare const SAMPLE_ENVELOPE_IDS: readonly ["amp", "pitch", "filter"]; export declare type SampleEnvelopeId = (typeof SAMPLE_ENVELOPE_IDS)[number]; export declare interface SampleLoader { loadAudio(audio: AudioInput | AudioInput[]): Promise; } export declare class SamplePlayer implements ILibInstrumentNode { #private; readonly nodeId: NodeID_2; readonly nodeType: "sample-player"; readonly context: AudioContext; private readonly envelopeConfigs; static readonly MAX_SAMPLES = 4; constructor(options: SamplePlayerOptions); init(): Promise; onMessage(type: string, handler: MessageHandler): () => void; connect(destination: ILibInstrumentNode | AudioNode): void; disconnect(destination?: ILibInstrumentNode | AudioNode): void; addIncoming(source: ILibInstrumentNode): void; removeIncoming(source: ILibInstrumentNode): void; get connections(): { outgoing: string[]; incoming: string[]; }; get audioNode(): GainNode; get input(): GainNode; get output(): GainNode; get now(): number; get initialized(): boolean; setModulationDepth: (modType: "AM" | "FM", depth: number) => void; /** @deprecated Use setModulationDepth. */ setModulationAmount: (modType: "AM" | "FM", depth: number) => void; setAMModOctaveOffset: (offset: number) => void; setModulationWaveform(modType?: 'AM' | 'FM', waveform?: CustomLibWaveform | OscillatorType | PeriodicWave, customWaveOptions?: WaveformOptions): void; syncLFOsToNoteFreq(lfoId: 'gain-lfo' | 'pitch-lfo', enabled: boolean): void; /** * Replace the whole sample set with one sample or several. Samples are * summed inside the voice worklet at one shared playhead, so they play in * unison and sample 0 is the authority for duration, loop points, start/end * and zero crossings. Samples shorter than sample 0 fall silent at their own * end; longer ones are truncated. Every sample is converted to the context's * sample rate: encoded input by decodeAudioData, AudioBuffers by * resampleAudioBuffer. Always resolves to an array, even for one sample. */ loadAudio(audio: AudioInput | AudioInput[], preprocessOptions?: Partial): Promise; /** * Replace the loaded sample with the region between its start and end points, * reloading it without re-running preprocessing. * * Points may be in either order. Seconds are clamped to the buffer. Returns null if there is no sample * loaded, the bounds aren't finite, or the region is empty. * Crops all samples to sample 0's frame range, zero-padding shorter samples. */ cropSample(startSeconds?: number, endSeconds?: number, fadeMs?: FadeMs): Promise; /** * `secondsFromNow` schedules the note on the audio clock. The voice is * allocated now, so a scheduled note counts toward polyphony straight away. */ play(midiNote: MidiValue, velocity?: MidiValue, glideTime?: number, secondsFromNow?: number): MidiValue | null; /** `secondsFromNow` is ignored while the sustain pedal holds the note. */ release(midiNote: MidiValue, secondsFromNow?: number): this; releaseAll(): this; get transposedBySemitones(): number; set transposeSemitones(semitones: number); /** * Sends scale periods in seconds to every voice for processor-side snapping. */ setScale(options: { rootNote: keyof typeof ROOT_NOTES; /** Custom pattern as semitone offsets from the root. */ scale: number[]; /** Shifts every allowed period by this many semitones. Positive is up. */ tuningOffset: number; highestOctave: number; lowestOctave: number; }): this; /** Transposes playback to the new root and rebuilds processor snapping periods. */ setRootNote(note: keyof typeof ROOT_NOTES): this; /** PARAM SETTERS */ setVolume(volume: number): this; setSampleStartPoint(seconds: number): this; setSampleEndPoint(seconds: number): this; /** Posts `voice:position` (0..1 of the sample, ~60 Hz) per sounding voice. Off by default. */ enablePositionTracking(enabled: boolean): this; setLoopRampDuration(seconds: number): this; setGlideTime(seconds: number): this; setLoopEnabled(enabled: boolean): this; setLoopLocked(locked: boolean): this; setHoldEnabled(enabled: boolean): this; setHoldLocked(locked: boolean): this; setSustainPedal(pressed: boolean): this; setPlaybackDirection(direction: 'forward' | 'reverse'): this; /** * Ping-pong loops turn around at both loop edges instead of jumping back, so * loop point order no longer matters. Without it, loop points set in reverse * order (start after end) make a backward loop: the voice plays through the * loop once, then cycles it backward. A short pitched loop played ping-pong * sounds an octave lower, since one cycle covers the loop twice. */ setLoopPingPong(enabled: boolean): this; setLoopDurationDriftAmount(amount: number): this; setPanDriftEnabled: (enabled: boolean) => this; setTimestretchEnabled: (enabled: boolean) => this; setLoopStart: (seconds: number) => this; setLoopEnd: (seconds: number) => this; setLoopDuration: (seconds: number) => this; setTempo(bpm: number): this | undefined; syncLoopToTempo(enabled: boolean): this; setKeytrackLoopAmount(amount: number): this; getKeytrackLoopAmount: () => number; get tempo(): number; scrollLoopPoints(loopStart: number): this; setParam(name: string, value: number): this; applyParams(params: SamplerParams): this; /** PARAM GETTERS */ getAudioParam(_name: string): AudioParam | null; getStartPoint(): number; getEndPoint(): number; getLoopRampDuration(): number; getGlideTime(): number; getHpfCutoff: () => number; getLpfCutoff: () => number; getParameterValue(name: string): number | undefined; setPitchEnabled(enabled: boolean): this; /** * Returns a detached, serializable envelope config. * * SamplePlayer owns the editable config; voices receive snapshots to schedule from. */ getEnvelope(id: SampleEnvelopeId): EnvelopeConfig; /** * Merges `patch` into the current config and emits one `envelope:changed` message. * The merge is shallow: a `shape` in the patch replaces the whole shape. */ updateEnvelope(id: SampleEnvelopeId, patch: Partial): void; /** Restores one envelope, or all when `id` is omitted, to defaults sized to the current sample. */ resetEnvelope(id?: SampleEnvelopeId): void; /** Envelope ids on the current voices; empty until the pool is initialized. */ get envelopeIds(): SampleEnvelopeId[]; /** * Named tap points covering the whole instrument, from inside the voices * through the bus to master out. * Pass to `monitorLevels` from `@kidlib/web-audio/debug`. */ getGainStages({ includeVoices }?: { includeVoices?: boolean | undefined; }): GainStages; setDistortionMacro(amount: number): void; setDrive(amount: number): void; setClippingMacro(amount: number): void; setDelayTime(seconds: number): void; setDelayFeedback(amount: number): void; setDryWetMix: (mix: { dry: number; wet: number; }) => void; sendToFx: (effect: BusNodeName, amount: number) => void; setLpfCutoff: (hz: number, preOrPostFx?: "pre" | "post" | "all") => void; setHpfCutoff: (hz: number, preOrPostFx?: "pre" | "post") => void; setReverbAmount: (amount: number) => void; setFeedbackDecay(value: number): void; setFeedbackLowpassCutoff(freqHz: number): void; setFeedbackAmount: (amount: number) => void; setFeedbackMode(mode: 'monophonic' | 'polyphonic' | 'double-trouble'): void; setFeedbackPitchScale(value: number): void; get sampleDuration(): number; get volume(): number; set volume(value: number); get loopEnabled(): boolean; get holdEnabled(): boolean; get gainLFO(): LFO | null; /** Modulates voice `detune`; `setDepth()` takes cents. */ get pitchLFO(): LFO | null; get loopStart(): number; get loopEnd(): number; get isLoaded(): boolean; get audiobuffer(): AudioBuffer; /** All loaded samples. Index 0 is the authority sample (=== `audiobuffer`). */ get samples(): readonly AudioBuffer[]; dispose(): void; } export declare type SamplePlayerOptions = { context: AudioContext; polyphony?: number; audio?: AudioInput | AudioInput[]; voiceSignalChain?: readonly SampleVoiceChainNode[]; }; export declare interface SamplerParamDescriptor { label: string; min: number; max: number; /** * Suggested initial control position. Not applied automatically: the player starts * from its own internal state, which can differ. Call `apply(player, defaultValue)` * to make the player match. */ defaultValue: number; /** * Control taper: `value = min + (max - min) * position ** curve`, position in [0, 1]. * 1 (default) = linear, >1 = finer control near min, <1 = finer control near max. */ curve?: number; /** Snap increment for UI controls */ step?: number; /** Discrete values only (e.g. pitch scale ratios) */ allowedValues?: readonly number[]; format?: (value: number, sampleDurationSeconds: number) => string; apply: (player: SamplePlayer, value: number) => void; } export declare type SamplerParamKey = keyof typeof samplerParams; /** Control position in [0, 1] for a value: inverse of the `curve` taper. */ export declare const samplerParamPosition: (d: SamplerParamDescriptor, v: number) => number; export declare type SamplerParams = Partial; export declare const samplerParams: Record<"loopStart" | "loopEnd" | "tempo" | "distortion" | "volume" | "dryWet" | "glide" | "lowpassFilter" | "highpassFilter" | "feedback" | "feedbackPitch" | "feedbackDecay" | "feedbackLpf" | "drive" | "clipping" | "amMod" | "amModOctaveOffset" | "reverbSend" | "reverbSize" | "delaySend" | "delayTime" | "delayFeedback" | "gainLFORate" | "gainLFODepth" | "pitchLFORate" | "pitchLFODepth" | "trimStart" | "trimEnd" | "loopDuration" | "loopRampDuration" | "loopDurationDrift" | "keytrackLoop", SamplerParamDescriptor>; export declare type SamplerParamValues = Record; export declare type SampleVoiceChainNode = 'feedback' | 'am' | 'hpf' | 'lpf'; /** Routes `context` (default: the global AudioContext) to the given output device. * Pass '' or 'default' to restore the system default output. */ export declare function setAudioOutputDevice(deviceId: string, context?: AudioContext): Promise; /** All supported oscillator waveforms */ export declare const SUPPORTED_WAVEFORMS: readonly SupportedWaveform[]; /** Union type of all supported waveforms */ export declare type SupportedWaveform = 'sine' | 'sawtooth' | 'square' | 'triangle' | CustomLibWaveform; declare type VoiceType = 'sample-voice' | 'karplus-strong-voice' | 'osc-voice' | 'noise-voice'; /** * Generic options interface that can be used with any CustomLibWaveform type */ declare interface WaveformOptions { harmonics?: number; seed?: number; dutyCycle?: number; rolloff?: number; voices?: number; detune?: number; brightness?: number; inharmonicity?: number; formantFreqs?: number[]; formantBandwidths?: number[]; fundamentalFreq?: number; slope?: number; chaos?: number; harmonicDensity?: number; waveFunction?: (phase: number) => number; } export { }