nx.js

Audio

Playing audio files in your nx.js application

nx.js provides two complementary ways to play audio:

  • The Audio element — a simple way to play back an audio file, implementing a subset of the web HTMLAudioElement API. Covered in the first half of this page.
  • The Web Audio API — a node-graph engine for low-latency playback, synthesis, mixing, panning, and real-time processing. Covered in the second half.

Both are backed by the same FFmpeg decoder, so they support the same set of file formats.

Supported Formats

Audio decoding is backed by FFmpeg, so essentially every common audio format is supported, including:

FormatMIME Type
MP3audio/mpeg
WAVaudio/wav
OGG Vorbisaudio/ogg
Opusaudio/opus
FLACaudio/flac
AAC / M4Aaudio/aac

Note

The same FFmpeg pipeline backs the Web Audio API's decodeAudioData() and the Video element.

Basic Playback

Create an Audio instance and wait for the audio data to load before playing:

src/main.ts
const audio = new Audio('romfs:/music.mp3');

audio.addEventListener('canplaythrough', () => {
  audio.play();
});

Important

Audio data is loaded and decoded asynchronously. Always wait for the canplaythrough event (or loadedmetadata) before calling play().

Loading Audio

The src property accepts any URL supported by fetch(), including romfs:/ paths for bundled assets and http:// / https:// URLs:

src/main.ts
// From the ROM filesystem
const sfx = new Audio('romfs:/sounds/jump.wav');

// From the SD card
const music = new Audio('sdmc:/switch/my-app/soundtrack.ogg');

// From a remote server
const stream = new Audio('https://example.com/audio.mp3');

Setting src automatically calls load(), which fetches and decodes the audio data.

Playback Controls

Play and Pause

await audio.play();
audio.pause();

Volume

Volume ranges from 0 (silent) to 1 (full volume):

audio.volume = 0.5; // 50% volume

Looping

audio.loop = true;
audio.play();

Seeking

Use currentTime to seek to a specific position (in seconds):

audio.currentTime = 30; // Jump to 30 seconds
console.log(audio.duration); // Total duration in seconds

Playback Rate

Adjust the playback speed:

audio.playbackRate = 1.5; // 1.5x speed
audio.playbackRate = 0.5; // Half speed

Events

The Audio class dispatches standard media events:

EventDescription
loadedmetadataAudio metadata has been loaded and decoded
canplaythroughEnough data is available to begin playback
playPlayback has started
pausePlayback has been paused
endedPlayback has reached the end (when not looping)
timeupdateThe current playback position has changed
errorAn error occurred during loading or decoding
src/main.ts
const audio = new Audio('romfs:/music.mp3');

audio.onended = () => {
  console.log('Track finished!');
};

audio.onerror = (e) => {
  console.error('Audio error:', e.error);
};

audio.addEventListener('canplaythrough', () => {
  audio.play();
});

Ready States

You can check readyState to determine the loading status:

ConstantValueMeaning
Audio.HAVE_NOTHING0No data loaded
Audio.HAVE_METADATA1Metadata decoded
Audio.HAVE_ENOUGH_DATA4Ready to play

Example: Background Music with Sound Effects

src/main.ts
import { Button } from '@nx.js/constants';

// Background music (looping)
const bgm = new Audio('romfs:/music/background.mp3');
bgm.loop = true;
bgm.volume = 0.3;

// Sound effect
const sfx = new Audio('romfs:/sounds/select.wav');

bgm.addEventListener('canplaythrough', () => {
  bgm.play();
});

function update() {
  requestAnimationFrame(update);

  const [pad] = navigator.getGamepads();
  if (pad?.buttons[Button.A].pressed) {
    // Play sound effect (reload to restart if already playing)
    sfx.load();
    sfx.addEventListener('canplaythrough', () => sfx.play(), { once: true });
  }
}

update();

Tip

For sound effects that need to be played rapidly or overlapping, create multiple Audio instances from the same source.

Web Audio API

For low-latency playback, synthesis, mixing, and real-time processing, nx.js implements the Web Audio API, which builds an audio processing graph out of connected nodes.

Audio is processed by a native graph engine running on a dedicated real-time render thread (in 128-frame quanta, like browsers), and streamed to the Switch's audren audio service.

Implemented Interfaces

InterfaceDescription
AudioContextThe main real-time audio graph
OfflineAudioContextRenders a graph to an AudioBuffer faster than real-time
AudioBufferIn-memory PCM audio data
AudioBufferSourceNodePlays back an AudioBuffer
GainNodeVolume control
StereoPannerNodeLeft/right stereo panning
AudioParamAutomatable parameter (with scheduling)

Playing an AudioBuffer

Decode an audio file with decodeAudioData(), then play it through an AudioBufferSourceNode:

src/main.ts
const ctx = new AudioContext();

const data = await fetch('romfs:/sound.mp3').then((r) => r.arrayBuffer());
const buffer = await ctx.decodeAudioData(data);

const source = ctx.createBufferSource();
source.buffer = buffer;
source.connect(ctx.destination);
source.start();

Synthesizing audio

You don't need any asset files — you can fill an AudioBuffer with samples you compute yourself. Here's a simple sine-wave beep:

src/main.ts
const ctx = new AudioContext();

// 0.5 seconds of a 440 Hz sine wave
const length = Math.floor(0.5 * ctx.sampleRate);
const buffer = ctx.createBuffer(1, length, ctx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < length; i++) {
  const t = i / ctx.sampleRate;
  data[i] = Math.sin(2 * Math.PI * 440 * t) * 0.3;
}

const source = ctx.createBufferSource();
source.buffer = buffer;
source.connect(ctx.destination);
source.start();

Building a graph

Nodes can be chained with connect() to build a processing graph. Here a source is routed through a gain (volume) node and a stereo panner before reaching the destination:

const ctx = new AudioContext();

const gain = ctx.createGain();
gain.gain.value = 0.5; // 50% volume

const panner = ctx.createStereoPanner();
panner.pan.value = -1; // hard left

source.connect(gain).connect(panner).connect(ctx.destination);
source.start();

Parameter automation

AudioParam values can be scheduled to change over time — for example, a fade-out:

const gain = ctx.createGain();
gain.connect(ctx.destination);

// Fade from full volume to silence over 1 second
gain.gain.setValueAtTime(1, ctx.currentTime);
gain.gain.linearRampToValueAtTime(0, ctx.currentTime + 1);

See the apps/web-audio example for a complete demo that synthesizes sound effects and music procedurally.

Learn more

On this page