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 track that appears in NEON DJ’s genre dropdown was synthesized entirely in JavaScript — no audio files are loaded from a server, and no copyrighted samples are used. The generateTrack(def) function renders a full multi-bar loop offline in faster-than-real-time, producing an AudioBuffer that the deck can loop indefinitely. This document explains how that function works, how genre definitions control the output, and how the scheduling model maps musical time to Web Audio timestamps.
Track generation happens once during the loading screen, before the user interface becomes interactive. Subsequent plays of the same genre reuse the pre-rendered AudioBuffergenerateTrack is never called again. The progress bar shown at load time advances as each genre is rendered sequentially.

The midiToFreq Utility

All melodic frequencies in NEON DJ are specified as MIDI note numbers and converted to Hz at render time:
function midiToFreq(m) {
  return 440 * Math.pow(2, (m - 69) / 12);
}
This is the standard equal-temperament formula. MIDI note 69 = A4 = 440 Hz. Each semitone step multiplies the frequency by the twelfth root of 2 (~1.05946). Some example values:
MIDINoteFrequency
21A155.00 Hz
28E282.41 Hz
33A2110.00 Hz
40E3164.81 Hz
45A3220.00 Hz
69A4440.00 Hz

generateTrack(def) — Function Signature

async function generateTrack(def)
def
object
required
A genre definition object from the TRACKS map. See the definition shape below.
Returns a Promise<AudioBuffer>. The buffer has two channels at 44100 Hz. Two extra properties are attached after rendering: _bpm (the genre’s BPM, used by the deck’s SYNC feature) and _loop = true (tells AudioBufferSourceNode to loop seamlessly).

Genre Definition Object Shape

Each genre in the TRACKS map is a plain object with the following properties:
name
string
Human-readable name shown in the track dropdown. Example: "House · 124 BPM".
bpm
number
Tempo in beats per minute. Controls step duration and delay times.
bars
number
Number of bars in the loop. All genres use 8 bars.
kick
number[]
Array of 16th-note step indices (0–15) on which a kick drum fires each bar.
clap
number[]
Step indices for clap/snare hits.
ohat
number[]
Step indices for open hi-hat hits (synthHat with open = true).
chat
number[]
Step indices for closed hi-hat hits (synthHat with open = false).
bassRoots
number[] (optional)
Array of MIDI note numbers used as bass/chord roots, one per pair of bars (Math.floor(bar / 2) % length). Used by electronic genres (House, Hip-Hop, Techno, etc.).
chords
number[] (optional)
Array of MIDI root notes for chord-based genres (Rock, Metal, Pop, Punk, Ska, Grunge). Each chord is a power chord (root + fifth + octave) passed to synthChord.
build
function
build(ctx, master, noise, bar, step16, t) — called once per 16th-note step. Contains the melodic/harmonic scheduling logic specific to each genre. Parameters: the offline audio context, the master gain node, the noise buffer, the current bar index (0-based), the step within the bar (0–15), and the absolute timestamp t in seconds.

How Rendering Works

1

Create OfflineAudioContext

A fresh OfflineAudioContext is created for each genre with a length precisely matching the number of steps × step duration × sample rate:
const sr = 44100;
const beat = 60 / def.bpm;
const stepDur = beat / 4;               // one 16th note in seconds
const totalSteps = def.bars * 16;       // e.g. 8 bars × 16 = 128 steps
const length = Math.ceil(totalSteps * stepDur * sr);
const ctx = new OfflineAudioContext(2, length, sr);
2

Create shared resources

A single 1.5-second white noise buffer is created once via makeNoise(ctx, 1.5) and reused by all percussion scheduling calls. A master gain node (gain.value = 0.85) connects to ctx.destination.
3

Schedule all notes

A single for loop iterates over every step from 0 to totalSteps - 1. For each step, the absolute timestamp t = step * stepDur is computed. The loop then checks which instruments fire on this step:
for (let step = 0; step < totalSteps; step++) {
  const t = step * stepDur;
  const s = step % 16;          // position within the bar (0–15)
  const bar = Math.floor(step / 16);

  if (def.kick.includes(s))  synthKick(ctx, master, t);
  if (def.clap.includes(s))  synthClap(ctx, noise, master, t);
  if (def.ohat.includes(s))  synthHat(ctx, noise, master, t, true);
  if (def.chat.includes(s))  synthHat(ctx, noise, master, t, false);
  def.build(ctx, master, noise, bar, s, t);
}
The def.build() call is where bass lines, arpeggios, leads, and chords are scheduled. Build functions read from bassRoots or chords arrays, cycling through harmonic changes every two bars: Math.floor(bar / 2) % array.length.
4

Render offline

const rendered = await ctx.startRendering();
rendered._bpm  = def.bpm;
rendered._loop = true;
return rendered;
OfflineAudioContext.startRendering() processes all scheduled nodes as fast as the CPU allows — for an 8-bar loop this typically takes milliseconds rather than the full loop duration.

Step Timing Model

All musical timing is derived from one value: stepDur = (60 / bpm) / 4 (the duration of a 16th note in seconds). Bar boundaries occur every 16 steps. Beat (quarter-note) boundaries occur every 4 steps. This makes the grid layout immediately readable from the pattern arrays:
Step:  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
Beat:  1        2        3        4
       |        |        |        |
       ↑ Bar start       ↑ Half-time
For example, the House kick pattern [0, 4, 8, 12] places a kick on every quarter note — the classic four-on-the-floor pattern.

Genre Table

GenreBPMKick PatternPrimary Instruments
House1240, 4, 8, 12 (four-on-floor)Bass (sawtooth), pad stabs (sawtooth lead)
Hip-Hop880, 7, 10 (boom-bap)Sub bass, triangle lead melody
Techno1300, 4, 8, 12 (four-on-floor)Off-beat bass, sawtooth lead on bar 3
Synthwave1100, 4, 8, 12Bass + square-wave arpeggio (8-note pattern)
Reggaeton960, 8, 10 (dembow)Bass, square-wave melody
Drum & Bass1740, 10 (breakbeat-style)Sub bass + mid bass, sawtooth chord stabs
Trap1400, 6, 10Long sub bass (0.55 s), triangle ornament
Funk1080, 10Tight 16th-note bass, square-wave punctuation
Rock Español 80/901320, 8Power chords (drive 16), bass guitar
Heavy Metal 80/90160Dense (12 of 16 steps)Distorted power chords (drive 26), bass
Pop Español 80/901180, 4, 8, 12Bass, sawtooth chord stabs, square melody
Punk 80/901720, 4, 8, 12Power chords (drive 20), straight bass
Ska 80/901500, 8Upstroke chord “chink” (off-beats), bass
Grunge 901150, 8, 10Heavy power chords (drive 24), bass
Salsa953, 11Triangle montuno arpeggio, syncopated bass
Cumbia920, 8Sawtooth + triangle melodic line, bass
Jungle / DnB1650, 6, 10 (jungle breaks)Sub bass + mid bass
Dubstep1400 (half-time)Wobble bass (LFO 5 Hz / 8 Hz)

The build Function Pattern

Each genre’s build function follows the same contract, illustrated here with House:
build(ctx, master, noise, bar, step16, t) {
  // Cycle through 4 harmonic sections, 2 bars each
  const root = this.bassRoots[Math.floor(bar / 2) % 4];

  // Bass line — fire on specific 16th-note steps
  if ([0, 3, 6, 10, 11, 14].includes(step16))
    synthBass(ctx, master, t, midiToFreq(root), 0.18);

  // Octave bass hit on step 8
  if (step16 === 8)
    synthBass(ctx, master, t, midiToFreq(root + 12), 0.18);

  // Pad stab on beat 1 of even bars
  if (step16 === 0 && bar % 2 === 0) {
    [root + 12, root + 19, root + 24].forEach(n =>
      synthLead(ctx, master, t, midiToFreq(n), 0.5, "sawtooth", 0.05)
    );
  }
}
The pattern of if (step16 === X) conditionals inside build is how every melodic event is placed. Chord changes happen by indexing into bassRoots or chords based on bar. This gives each genre its own harmonic character over 8 bars before the loop repeats.
An OfflineAudioContext renders audio as fast as the CPU can process it — typically 10–50× faster than real time. A single 8-bar House loop at 124 BPM lasts about 15.5 seconds; rendering it offline takes only a few hundred milliseconds. This approach also avoids the complexity of managing a live audio graph during the loading phase before the user interface is visible. The rendered AudioBuffer is a standard PCM buffer that any AudioBufferSourceNode can play, loop, pitch-shift, and seek within.

Build docs developers (and LLMs) love