Skip to content
Apexify.jsDocs
Apexify.js version v5.4.5

Advanced / Audio · guide

Procedural audio (createAudio)

Current Apexify.js 6.0.0 documentation for Procedural audio (createAudio).

apexify.jsRuntime: nodeCURRENTSince 6.0.0

painter.createAudio synthesizes 16-bit PCM WAV Buffers — presets, custom multi-layer sounds, timelines, and mixes. Output is ready for mixAudio, videoPipeline().audio(), disk via save, or any API that accepts a WAV buffer.

Phase 9 hardens this surface with pre-allocation resource checks, operation-local seeded randomness, stricter DSP validation, and RIFF-safe PCM16 handling. createAudio remains a complete-buffer API: it returns a finished WAV Buffer, not a streaming audio object.

Hub: Audio (advanced) · Runtime limits: Runtime validation & resource governance · Mux on video: Video audio operations · Pipeline tracks: Video pipeline


Entry points

MethodReturnsUse when
preset(name, overrides?)BufferBuilt-in SFX (laser, coin, jump, …)
synth(options) / custom(options)BufferFull SynthSoundOptions (layers, ADSR, filters)
sequence({ events })BufferTimed preset/custom events on one WAV
compose({ clips })BufferOverlapping clips with pitch, pan, fades, tone
mix(inputs, options?)BufferSum buffers at t = 0 or with per-input at
save(wav, filePath)Promise<void>Write WAV to disk
listPresets()SynthPresetInfo[]Names, descriptions, default durations
presetNamesreadonly arrayAll SynthPresetName values

Types export from apexify.js/types: SynthPresetName, SynthSoundOptions, SynthLayer, SynthSequenceOptions, SynthComposeOptions, …


Deterministic seeded audio

Noise-based synthesis is nondeterministic by default. Supply seed when you need byte-stable procedural output for tests, cache keys, reproducible renders, or parallel jobs.

seed accepts a safe integer or a 1–256 character string on sound, sequence, compose, clip, and mix options where the type exposes it.

SOURCEtypescript
typescript
Studio
const a = painter.createAudio.synth({  seed: 'ui-impact-v1',  layers: [    { waveform: 'pink', duration: 0.18, gain: 0.35 },  ],}); const b = painter.createAudio.synth({  seed: 'ui-impact-v1',  layers: [    { waveform: 'pink', duration: 0.18, gain: 0.35 },  ],}); // a and b are byte-identical WAV buffers.

Seeded layers/events/clips derive independent random streams. Concurrent seeded renders do not share RNG or filter state. Omitting seed intentionally keeps stochastic sounds nondeterministic.


Presets

39 built-in names (game/UI oriented): laser, explosion, coin, jump, whoosh, menuSelect, footstep, thunder, …

SOURCEtypescript
typescript
Studio
const pew = painter.createAudio.preset('laser', { volume: 0.8, transpose: 2 }); for (const info of painter.createAudio.listPresets()) {  console.log(info.name, info.description, info.defaultDuration);}

SynthPresetOverrides: volume, transpose, plus any SynthSoundOptions field layered on the preset definition. Preset definitions are cloned before use, so caller mutation does not alter the shared catalog.


Custom sounds — synth / custom

Each SynthLayer can use waveforms sine, square, sawtooth, triangle, noise, pink:

FieldRole
frequency / frequencyEndStart Hz and optional sweep for tonal waveforms
durationLayer length (seconds)
delayOffset before layer starts
gain, detune, panLevel, cents, stereo pan
adsrAttack / decay / sustain / release
vibrato, tremoloLFO depth + rate
filterlowpass / highpass + cutoff + optional Q
noiseMix, partialsNoise blend and harmonic ratios on tonal layers
SOURCEtypescript
typescript
Studio
const uiTick = painter.createAudio.synth({  seed: 'ui-tick',  layers: [    {      waveform: 'square',      frequency: 880,      frequencyEnd: 440,      duration: 0.06,      gain: 0.35,      adsr: { attack: 0.001, decay: 0.02, sustain: 0, release: 0.02 },    },  ],  masterGain: 1,  limiter: true,});

sampleRate (default 44100), channels (1 or 2), limiter (default true) apply at the sound level.

Noise/pink layers reject tonal-only controls such as frequency sweeps, detune, vibrato, and harmonic partials instead of silently ignoring them. Filter cutoff must be below the selected sample rate's Nyquist frequency.


Timelines — sequence

Events fire at at (seconds). Each event uses exactly one of preset or options (SynthSoundOptions), plus optional gain.

SOURCEtypescript
typescript
Studio
const combo = painter.createAudio.sequence({  seed: 'combo-v1',  events: [    { at: 0, preset: 'hit' },    { at: 0.08, preset: 'coin', gain: 0.85 },    { at: 0.2, preset: 'powerup' },  ],  tail: 0.2,  masterGain: 0.95,});

tail pads silence after the last event ends. Overlapping events sum in the same buffer (limiter applied). The renderer keeps the final output plus one event working buffer instead of retaining every rendered event in memory.


Overlapping clips — compose

compose is for dense beds: multiple presets, custom sounds, or existing WAV buffers on one timeline with per-clip shaping.

SynthComposeClip fieldEffect
at, duration, sourceStartPlacement and trim
preset / sound / wavSource (exactly one required)
gain, volume, transpose, detune, pitch, speedLevel and pitch/playback-rate controls
pan, fadeIn, fadeOutStereo and edges
noise, filter, qualityPost-tone on the clip
overridesPreset tweaks
seedDeterministic clip-local stochastic processing

Top-level SynthComposeOptions: duration, tail, masterGain, postHighpassHz (rumble cleanup), noiseGateThreshold (quiet overlap hiss), and optional seed.

SOURCEtypescript
typescript
Studio
const bossIntro = painter.createAudio.compose({  seed: 'boss-intro-v3',  clips: [    { at: 0, preset: 'rumble', duration: 1.2, gain: 0.5 },    { at: 0.4, preset: 'alarm', gain: 0.4, fadeIn: 0.1 },    { at: 0.9, preset: 'explosion', gain: 0.75 },  ],  postHighpassHz: 220,  tail: 0.3,});

For synthesized preset/custom sources, transpose, detune, and pitch modify oscillator pitch. Existing WAV clips do not accept those procedural pitch controls; use speed instead. speed is playback-rate resampling: it changes duration and pitch together and is not pitch-preserving time stretch.

The composer keeps the final output plus one processed clip working buffer rather than retaining every clip at once.


mix

Combine one or more Buffers (or inline preset/sound definitions where supported):

SOURCEtypescript
typescript
Studio
const layered = painter.createAudio.mix(  [painter.createAudio.preset('beep'), painter.createAudio.preset('whoosh', { volume: 0.5 })],  { masterGain: 0.9, seed: 'layered-v1' });

Use when you already have WAV buffers and need a single file without a full compose timeline. If any input uses timeline/clip controls, mix routes through the validated composition path.


Resource limits and allocation behavior

Audio requests are validated before large sample allocations. Defaults are controlled by the shared Apexify runtime configuration:

LimitDefault
maxAudioDurationSeconds600 seconds
maxAudioSampleRate192,000 Hz
maxAudioChannels2
maxAudioEvents20,000
maxAudioLayers1,024
maxAudioPartials4,096
maxAudioBytes256 MiB

maxAudioBytes is a peak working-memory guard, not just a final WAV-size limit. Apexify.js accounts for the Float32 render buffer, decoded/resampled sources where applicable, transient event/clip buffers, and the final PCM16 WAV allocation when those buffers coexist.

SOURCEtypescript
typescript
Studio
configureApexifyRuntime({  limits: {    maxAudioDurationSeconds: 120,    maxAudioBytes: 96 * 1024 * 1024,  },});

Lower limits for multi-tenant or memory-constrained services. Do not raise them merely to suppress ApexifyResourceLimitError.

Because the API returns a complete WAV Buffer, Phase 9 deliberately enforces bounded complete-buffer rendering rather than claiming streaming synthesis.


WAV behavior

Generated output is mono/stereo PCM16 RIFF/WAVE. Internally, WAV inspection/decoding validates chunk boundaries and does not assume fmt and data occur at fixed offsets. Unsupported formats, bit depths, malformed chunk sizes, incomplete frames, inconsistent byte-rate/block-alignment metadata, and oversized decoded audio are rejected before unsafe allocation.


Put audio on video

Single operation — mixAudio

Pass any createAudio Buffer as MixAudioOverlayClip.source:

SOURCEtypescript
typescript
Studio
const sfx = painter.createAudio.preset('laser'); await painter.createVideo({  source: './clip.mp4',  mixAudio: {    outputPath: './clip-sfx.mp4',    keepOriginalAudio: false,    overlays: [{ source: sfx, startTime: 1.5, volume: 1 }],  },});

keepOriginalAudio: false → overlays only (no silent bed under SFX). Full field reference: Video audio operations.

Editor stack — videoPipeline().audio()

Declare tracks without pre-building every buffer:

typeFields
presetpreset, startTime, gain, volume, transpose
synthsound, startTime, gain
sequenceevents, startTime, tail, masterGain
wavPre-built Buffer, startTime, volume
filePath, URL, or buffer (external assets)

See Video pipeline — Audio layers.


Standalone export

SOURCEtypescript
typescript
Studio
await painter.createAudio.save(  painter.createAudio.sequence({    seed: 'menu-select',    events: [{ at: 0, preset: 'menuSelect' }],  }),  './ui/select.wav');

vs FFmpeg video audio

createAudiocreateVideo audio keys
InputGenerated PCMExisting video/audio files
OutputWAV BufferMP4 (or extracted audio file)
FFmpegOnly when muxing to videoAlways for video ops
Typical useGame SFX, UI sounds, botsPodcast normalize, ducking, strip track

Tips

  • Add seed when output must be reproducible across repeated or concurrent renders.
  • listPresets() before hard-coding names in editor UIs.
  • Prefer sequence for linear SFX chains; compose when clips overlap with independent pitch/fades.
  • For long gameplay videos, generate SFX once, then mixAudio or pipeline type: 'wav' — avoid re-synthesizing on every export pass.
  • Keep deployment-specific audio limits below what the Node process can safely hold at peak.
  • Pair with GIF / animate by muxing SFX after frame encode.

Next steps