Skip to content

AudioGraphPlugin

AudioGraphPlugin is the foundation of the Web Audio signal chain. When you register it, it creates an AudioContext, wraps the backend’s media element in a MediaElementAudioSourceNode, and wires the baseline chain. Every other audio plugin (EqualizerPlugin, SpectrumPlugin, custom graph plugins) depends on this plugin being registered first.

AudioGraphPlugin originates in @nomercy-entertainment/nomercy-player-core and is re-exported by the music package for ergonomic imports.

TypeScript
import { AudioGraphPlugin } from '@nomercy-entertainment/nomercy-music-player/plugins';

Plugin id: 'audio-graph'

What it does

On use() the plugin creates (or reuses) an AudioContext, mounts the backend’s media element as a MediaElementAudioSourceNode, and connects the baseline chain: source → destination. Subsequent plugins extend the chain by calling insertEffect(), pre(), or post(). The shared AnalyserNode returned by analyserSource() is tapped parallel to the source so visualisers can read frequency data without interrupting the signal path.

Signal chain topology:

NodePosition in chainDescription
Media element / backend outputSourceRaw audio from the active backend.
Source node (MediaElementAudioSourceNode)EntryWraps the media element; feeds all downstream branches.
Shared AnalyserNodeParallel tapRead-only branch off the source; SpectrumPlugin and custom visualisers read here without affecting the signal path.
preEffects[0..n]Pre-effectsOrdered chain of pre-effects inserted via pre() or insertEffect(node, 'pre'), e.g. EQ BiquadFilters.
postEffects[0..n]Post-effectsOrdered chain of post-effects inserted via post() or insertEffect(node, 'post'), e.g. MixerPlugin gain/pan.
AudioContext.destinationOutputFinal output node; receives the tail of the post-effects chain.

Browsers start AudioContext in 'suspended' state. This plugin resumes it on the first play event, which always fires inside or immediately after a user gesture, satisfying the browser’s activation requirement.

Options

OptionTypeDefaultDescription
latencyHint'playback' | 'interactive' | 'balanced''playback'Passed to new AudioContext({ latencyHint }). Use 'playback' for music, the browser optimises buffer sizes for lowest power rather than lowest latency. Use 'interactive' when round-trip latency matters.
fftSize256 | 512 | 1024 | 2048 | 4096 | 8192 | 163842048FFT size for the shared AnalyserNode. Higher values give finer frequency resolution at the cost of time resolution.
smoothingnumber0.8Smoothing time constant (0 to 1) for the shared analyser. Higher values make spectrum output lag behind transients.

Events

EventPayloadDescription
context:ready{ sampleRate: number }Fired once after the AudioContext is created and the baseline chain is wired.
context:closedvoidFired when the AudioContext is closed on dispose.
chain:rebuiltvoidFired every time insertEffect() or removeEffect() causes the chain to reconnect. EqualizerPlugin listens to this to relink its internal filter series.
unsupported{ reason: string }Fired when AudioContext is not available in the current environment. The plugin does not activate.

Listen using the namespaced string form:

TypeScript
player.on('plugin:audio-graph:context:ready', ({ sampleRate }) => {
console.log('AudioContext running at', sampleRate, 'Hz');
});

Methods

context()

TypeScript
context(): AudioContext

Returns the player’s AudioContext. If use() has already run, the existing context is returned. Throws BrowserPolicyError in environments without AudioContext.

outputNode()

TypeScript
outputNode(): AudioNode

Returns the last node in the effect chain, the node currently wired directly to AudioContext.destination. Throws PluginError if called before use().

analyserSource()

TypeScript
analyserSource(): AnalyserNode

Returns the shared AnalyserNode tapped parallel to the chain source. Created once on first call and reused. Multiple consumers (e.g. SpectrumPlugin, custom visualisers) all read from the same node.

insertEffect(node, position?)

TypeScript
insertEffect(node: AudioNode, position?: 'pre' | 'post'): AudioNode

Appends an effect node to the signal chain and triggers a full chain rebuild. 'pre' inserts before post-effects (e.g. EQ filter banks). 'post' inserts after all pre-effects, and is the default (e.g. master gain). Returns the same node for convenience.

removeEffect(node)

TypeScript
removeEffect(node: AudioNode): void

Removes a previously inserted effect node and triggers a chain rebuild. Safe to call with a node that was never inserted.

pre(node)

TypeScript
pre(node: AudioNode): AudioNode

Shorthand for insertEffect(node, 'pre').

post(node)

TypeScript
post(node: AudioNode): AudioNode

Shorthand for insertEffect(node, 'post').

route(from, to)

TypeScript
route(from: AudioNode, to: AudioNode): void

Manually connects two nodes outside the managed effect chain. All connections registered here are disconnected on dispose.

unroute(from, to)

TypeScript
unroute(from: AudioNode, to: AudioNode): void

Disconnects a manual node pair previously registered with route(). Safe to call if the pair was never routed.

Registration

Always register AudioGraphPlugin before any plugin that requires a Web Audio graph:

TypeScript
import nmMPlayer from '@nomercy-entertainment/nomercy-music-player';
import { AudioGraphPlugin, EqualizerPlugin } from '@nomercy-entertainment/nomercy-music-player/plugins';
import type { MusicPlaylistItem } from '@nomercy-entertainment/nomercy-music-player';

const tracks: MusicPlaylistItem[] = [
{
id: 'bent-wyre-01',
name: 'Ants Of The Beat',
url: '/B/bent%20wyre/%5B2025%5D%20If%20Only%20Life%20Was%20This%20Easy%20Volume%205%20-%20The%20Beat%20Misdirect/01%20Ants%20Of%20The%20Beat.mp3',
artist: 'bent wyre',
},
];

const player = nmMPlayer('main')
.addPlugin(AudioGraphPlugin, { latencyHint: 'playback' })
.addPlugin(EqualizerPlugin)
.setup({
baseUrl: 'https://raw.githubusercontent.com/NoMercy-Entertainment/nomercy-media/master/Music',
playlist: tracks,
});

Custom visualiser example

TypeScript
import nmMPlayer from '@nomercy-entertainment/nomercy-music-player';
import { AudioGraphPlugin } from '@nomercy-entertainment/nomercy-music-player/plugins';
import type { MusicPlaylistItem } from '@nomercy-entertainment/nomercy-music-player';

const tracks: MusicPlaylistItem[] = [
{
id: 'bent-wyre-01',
name: 'Ants Of The Beat',
url: '/B/bent%20wyre/%5B2025%5D%20If%20Only%20Life%20Was%20This%20Easy%20Volume%205%20-%20The%20Beat%20Misdirect/01%20Ants%20Of%20The%20Beat.mp3',
artist: 'bent wyre',
},
];

const player = nmMPlayer('main')
.addPlugin(AudioGraphPlugin, { fftSize: 2048 })
.setup({
baseUrl: 'https://raw.githubusercontent.com/NoMercy-Entertainment/nomercy-media/master/Music',
playlist: tracks,
});

player.on('plugin:audio-graph:context:ready', () => {
const graph = player.getPlugin(AudioGraphPlugin)!;
const analyser = graph.analyserSource();
const frequencyData = new Uint8Array(analyser.frequencyBinCount);

const canvas = document.getElementById('vis') as HTMLCanvasElement;
const canvasCtx = canvas.getContext('2d')!;

const draw = (): void => {
requestAnimationFrame(draw);
analyser.getByteFrequencyData(frequencyData);

canvasCtx.clearRect(0, 0, canvas.width, canvas.height);

frequencyData.forEach((amplitude, binIndex) => {
const barHeight = (amplitude / 255) * canvas.height;
canvasCtx.fillRect(binIndex * 3, canvas.height - barHeight, 2, barHeight);
});
};

draw();
});

See also