Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/kepabilbao67-bot/musicplayer2/llms.txt

Use this file to discover all available pages before exploring further.

Every sound in NEON DJ travels through a graph of Web Audio API nodes before reaching your speakers. Understanding this graph helps you predict how controls interact with each other — why turning the FILTER knob affects both the dry signal and the echo tail, or why the flanger effect colours the entire mix rather than a single deck. This document traces the complete path from audio source to output.

Per-Deck Signal Chain

Each Deck instance builds its own Web Audio subgraph inside buildGraph(). The primary (dry) path is:
AudioBufferSourceNode
  → eqLow  (BiquadFilter: lowshelf,  200 Hz)
  → eqMid  (BiquadFilter: peaking,  1000 Hz, Q=0.9)
  → eqHigh (BiquadFilter: highshelf, 3800 Hz)
  → filter (BiquadFilter: LP/HP, switchable)
  → channelGain (GainNode — channel fader)
  → crossGain   (GainNode — crossfader send)
  → masterGain  (master output)
The source node is an AudioBufferSourceNode. A new instance is created each time play() is called (this is the standard Web Audio pattern — source nodes are single-use and disposable). It connects directly into eqLow, the first stage of the three-band EQ.

EQ Nodes

The three EQ bands are BiquadFilterNode instances chained in series:
BandFilter TypeCentre / Shelf FrequencyKnob Range
HIGHhighshelf3800 Hz−26 dB to +12 dB
MIDpeaking1000 Hz (Q = 0.9)−26 dB to +12 dB
LOWlowshelf200 Hz−26 dB to +12 dB
Each knob maps directly to the gain AudioParam of its filter node. Double-clicking a knob resets it to 0 dB.

Filter Node

The FILTER knob drives a single BiquadFilterNode that switches type depending on knob direction:
applyFilter(v) {
  if (Math.abs(v) < 0.03) {
    // Dead zone at centre — filter bypassed (22 kHz lowpass = wide open)
    this.filter.type = "lowpass";
    this.filter.frequency.value = 22000;
  } else if (v < 0) {
    // Negative → lowpass closes down (LP sweep)
    this.filter.type = "lowpass";  this.filter.Q.value = 4;
    this.filter.frequency.value = 200 + (1 + v) * (8000 - 200);
  } else {
    // Positive → highpass opens up (HP sweep)
    this.filter.type = "highpass"; this.filter.Q.value = 2;
    this.filter.frequency.value = 40 + v * 5000;
  }
}
Turning the knob left sweeps a resonant lowpass from 8 kHz down to 200 Hz. Turning it right lifts a highpass from 40 Hz up toward 5 kHz.

Parallel Effect Sends

After channelGain, two parallel send buses branch off. Both feed back into crossGain so they are subject to the crossfader:

Echo / Delay Send

channelGain
  → delaySend  (GainNode, default gain 0)
  → delay      (DelayNode, delayTime = musical 3/4-beat)
  → feedback   (GainNode, gain = 0.38)
  ↻ (feedback → delay loop)
  → crossGain
The delay time is set musically when a track loads: (60 / bpm) * 0.75 seconds — a dotted-eighth note at the track’s BPM. The ECO knob sets delaySend.gain between 0 and 0.85.

Reverb Send

channelGain
  → reverbSend (GainNode, default gain 0)
  → reverb     (ConvolverNode, synthesised impulse response)
  → crossGain
The ConvolverNode uses a synthesised impulse response: 1.8 seconds of exponentially decaying white noise (decay exponent 2.5), generated once and cached in _reverbIR. The REVERB knob sets reverbSend.gain between 0 and 0.9.

Master Chain

Both crossGain outputs merge into the shared master chain built by masterNode():
deckA.crossGain ─┐
                 ├→ masterGain (GainNode, default 0.85)
deckB.crossGain ─┘     → masterLimiter (DynamicsCompressor)
                              → ctx.destination  (speakers)
The DynamicsCompressorNode acts as a brickwall limiter:
ParameterValue
threshold−3 dB
knee0 dB
ratio20:1
attack3 ms
release250 ms

Master FX: Flanger

The flanger is inserted as a parallel send from masterGain, not from individual decks:
masterGain
  → fl         (DelayNode, base delayTime = 0.005 s)
  → flFb       (GainNode, feedback = 0.55)
  ↻ (flFb → fl loop)
  fl → masterFlangerWet (GainNode, default gain 0)
  → masterLimiter

flLfo (OscillatorNode, sine, 0.35 Hz)
  → flDepth (GainNode, gain = 0.003)
  → fl.delayTime  (AudioParam modulation)
The LFO oscillator runs continuously at 0.35 Hz and modulates the delay time with a depth of ±3 ms, creating the characteristic chorus/flanger sweep. masterFlangerWet is normally at 0 and is ramped to 0.85 for 3.3 seconds by flangerThrow().

Master FX: Echo Throw

A second send from masterGain provides the master echo effect:
masterGain
  → masterEchoWet (GainNode, default gain 0)
  → ec             (DelayNode, delayTime = 0.34 s)
  → ecFb           (GainNode, feedback = 0.45)
  ↻ (ecFb → ec loop)
  ec → masterLimiter
masterEchoWet is ramped from 0 → 0.6 → 0 over 2.6 seconds by echoThrow().

Complete ASCII Signal Diagram

┌──────────────────────────────────┐  ┌──────────────────────────────────┐
│            DECK A                │  │            DECK B                │
│                                  │  │                                  │
│  AudioBufferSourceNode           │  │  AudioBufferSourceNode           │
│       │                          │  │       │                          │
│  eqLow (lowshelf 200 Hz)         │  │  eqLow (lowshelf 200 Hz)         │
│       │                          │  │       │                          │
│  eqMid (peaking 1 kHz)           │  │  eqMid (peaking 1 kHz)           │
│       │                          │  │       │                          │
│  eqHigh (highshelf 3.8 kHz)      │  │  eqHigh (highshelf 3.8 kHz)      │
│       │                          │  │       │                          │
│  filter (LP/HP switchable)       │  │  filter (LP/HP switchable)       │
│       │                          │  │       │                          │
│  channelGain ──────────────────┐ │  │  channelGain ──────────────────┐ │
│       │ (also feeds analyser)  │ │  │       │ (also feeds analyser)  │ │
│       │          delaySend     │ │  │       │          delaySend     │ │
│       │          → delay       │ │  │       │          → delay       │ │
│       │          → feedback ↺  │ │  │       │          → feedback ↺  │ │
│       │          → ──────────┐ │ │  │       │          → ──────────┐ │ │
│       │          reverbSend  │ │ │  │       │          reverbSend  │ │ │
│       │          → ConvolverNode │ │  │       │          → ConvolverNode │ │
│       │          → ──────────┘ │ │  │       │          → ──────────┘ │ │
│  crossGain ←───────────────────┘ │  │  crossGain ←───────────────────┘ │
│       │                          │  │       │                          │
└───────┼──────────────────────────┘  └───────┼──────────────────────────┘
        │                                      │
        └──────────────────┬───────────────────┘

                      masterGain

             ┌─────────────┴──────────────┐
             │                            │
         masterLimiter          flanger send (masterFlangerWet)
         (DynamicsCompressor)   + echo send (masterEchoWet)

       ctx.destination
       (+ ScriptProcessor for recording)
The AudioContext is created lazily inside the AC() helper function — it is only instantiated on the first call, which always happens inside a user-gesture handler (button click, key press, vinyl drag). This is required by the browser autoplay policy: an AudioContext created before a user gesture starts in the suspended state and will not produce audio until ctx.resume() is called. NEON DJ also calls AC().resume() explicitly at the start of every interactive handler to guarantee the context is running.

Build docs developers (and LLMs) love