// Original teaser music, synthesized from nothing with the Web Audio API (OfflineAudioContext): no samples, no // downloaded audio. 128 BPM, F minor, tight 16th arpeggio, kick / hats / clap, offbeat bass, a pad, impacts on every // scene cut (script.js CUTS), key clicks on every typed character, a riser into the end card. Deterministic (seeded). // renderWav() returns a 32-bit float stereo WAV (48 kHz) as base64; render.ts masters it with ffmpeg loudnorm. import { BAR, BEAT, CUTS, DURATION, keyTimes, scene } from './script.js'; const SR = 48000; const midi = m => 440 * Math.pow(2, (m - 69) / 12); function rng(seed) { // mulberry32 let a = seed >>> 0; return () => { a = (a + 0x6d2b79f5) >>> 0; let t = a; t = Math.imul(t ^ (t >>> 15), t | 1); t ^= t + Math.imul(t ^ (t >>> 7), t | 61); return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } // chords per bar (root midi in octave 3, intervals) const CH = { Fm: [53, [0, 3, 7]], Db: [49, [0, 4, 7]], Ab: [56, [0, 4, 7]], Eb: [51, [0, 4, 7]] }; // prompt · config · prices · languages · questionnaires · shots · validate · desktop · submit · brand (script.js SCENES) const BARS = ['Ab', 'Eb', 'Fm', 'Db', 'Ab', 'Eb', 'Fm', 'Db', 'Ab', 'Eb', 'Fm', 'Db', 'Ab', 'Eb', 'Fm', 'Db', 'Ab', 'Eb', 'Fm', 'Eb', 'Eb', 'Db', 'Eb', 'Fm', 'Eb', 'Fm']; const ARP = [0, 2, 4, 3, 5, 4, 2, 3, 0, 2, 4, 6, 5, 4, 3, 2]; export async function renderMusic() { const ctx = new OfflineAudioContext({ numberOfChannels: 2, length: Math.ceil(DURATION * SR) + SR, sampleRate: SR }); const R = rng(20261010); // shared noise const noise = ctx.createBuffer(2, SR * 3, SR); for (let c = 0; c < 2; c++) { const d = noise.getChannelData(c); for (let i = 0; i < d.length; i++) d[i] = R() * 2 - 1; } // reverb impulse const ir = ctx.createBuffer(2, Math.floor(SR * 2.6), SR); for (let c = 0; c < 2; c++) { const d = ir.getChannelData(c); for (let i = 0; i < d.length; i++) d[i] = (R() * 2 - 1) * Math.pow(1 - i / d.length, 3.2); } // buses const comp = ctx.createDynamicsCompressor(); comp.threshold.value = -14; comp.knee.value = 6; comp.ratio.value = 3.5; comp.attack.value = 0.004; comp.release.value = 0.16; const master = ctx.createGain(); master.gain.value = 0.7; master.connect(comp); comp.connect(ctx.destination); const verb = ctx.createConvolver(); verb.buffer = ir; const verbIn = ctx.createGain(); verbIn.gain.value = 0.5; verbIn.connect(verb); verb.connect(master); const duck = ctx.createGain(); duck.connect(master); // sidechained group: pad, bass, arp const drums = ctx.createGain(); drums.gain.value = 0.9; drums.connect(master); const arpBus = ctx.createGain(); arpBus.gain.value = 0.55; const arpLP = ctx.createBiquadFilter(); arpLP.type = 'lowpass'; arpLP.Q.value = 6; arpBus.connect(arpLP); arpLP.connect(duck); // stereo echo on the arp for (const [time, pan] of [[BEAT * 0.75, -0.6], [BEAT * 1.5, 0.6]]) { const dl = ctx.createDelay(2); dl.delayTime.value = time; const fb = ctx.createGain(); fb.gain.value = 0.28; const lp = ctx.createBiquadFilter(); lp.type = 'lowpass'; lp.frequency.value = 2600; const p = ctx.createStereoPanner(); p.pan.value = pan; const wet = ctx.createGain(); wet.gain.value = 0.32; arpLP.connect(dl); dl.connect(lp); lp.connect(fb); fb.connect(dl); lp.connect(wet); wet.connect(p); p.connect(duck); } const arpVerb = ctx.createGain(); arpVerb.gain.value = 0.18; arpLP.connect(arpVerb); arpVerb.connect(verbIn); // arp filter: closed intro, open drop, wide on the riser, dark under the Submit card, bright on the brand const f = arpLP.frequency; const at = s => scene(s).start; f.setValueAtTime(420, 0); f.exponentialRampToValueAtTime(2400, at('config') - 0.02); f.setValueAtTime(3000, at('config')); f.linearRampToValueAtTime(3400, at('submit') - BAR); f.exponentialRampToValueAtTime(7000, at('submit') - 0.01); f.setValueAtTime(900, at('submit')); f.exponentialRampToValueAtTime(1800, at('brand') - 0.01); f.setValueAtTime(4200, at('brand')); f.setValueAtTime(4200, at('brand') + BAR * 2); f.exponentialRampToValueAtTime(800, DURATION); const env = (g, t, a, peak, d, end = 0.0005) => { g.gain.setValueAtTime(0, t); g.gain.linearRampToValueAtTime(peak, t + a); g.gain.exponentialRampToValueAtTime(end, t + a + d); }; const noiseSrc = (t, dur, off = R() * 1.5) => { const s = ctx.createBufferSource(); s.buffer = noise; s.start(t, off, dur); return s; }; // ---- voices ---- function kick(t, g = 1) { const o = ctx.createOscillator(); o.type = 'sine'; o.frequency.setValueAtTime(155, t); o.frequency.exponentialRampToValueAtTime(44, t + 0.11); const v = ctx.createGain(); env(v, t, 0.002, 1.0 * g, 0.42); o.connect(v); v.connect(drums); o.start(t); o.stop(t + 0.5); const n = noiseSrc(t, 0.01); const hp = ctx.createBiquadFilter(); hp.type = 'highpass'; hp.frequency.value = 3500; const nv = ctx.createGain(); env(nv, t, 0.001, 0.22 * g, 0.012); n.connect(hp); hp.connect(nv); nv.connect(drums); duck.gain.setValueAtTime(0.28, t); duck.gain.linearRampToValueAtTime(1, t + 0.22); } function hat(t, g, pan) { const n = noiseSrc(t, 0.06); const hp = ctx.createBiquadFilter(); hp.type = 'highpass'; hp.frequency.value = 7500; const p = ctx.createStereoPanner(); p.pan.value = pan; const v = ctx.createGain(); env(v, t, 0.001, g, 0.04); n.connect(hp); hp.connect(v); v.connect(p); p.connect(drums); } function clap(t, g = 0.4) { const bp = ctx.createBiquadFilter(); bp.type = 'bandpass'; bp.frequency.value = 1300; bp.Q.value = 0.9; const v = ctx.createGain(); v.gain.setValueAtTime(0, t); for (let i = 0; i < 3; i++) { v.gain.setValueAtTime(g, t + i * 0.011); v.gain.exponentialRampToValueAtTime(g * 0.2, t + i * 0.011 + 0.009); } v.gain.setValueAtTime(g * 0.8, t + 0.034); v.gain.exponentialRampToValueAtTime(0.0005, t + 0.2); const n = noiseSrc(t, 0.25); n.connect(bp); bp.connect(v); v.connect(drums); const s = ctx.createGain(); s.gain.value = 0.5; v.connect(s); s.connect(verbIn); } function snare(t, g) { const n = noiseSrc(t, 0.12); const bp = ctx.createBiquadFilter(); bp.type = 'bandpass'; bp.frequency.value = 1900; bp.Q.value = 0.7; const v = ctx.createGain(); env(v, t, 0.001, g, 0.09); n.connect(bp); bp.connect(v); v.connect(drums); const o = ctx.createOscillator(); o.type = 'triangle'; o.frequency.setValueAtTime(240, t); o.frequency.exponentialRampToValueAtTime(160, t + 0.05); const ov = ctx.createGain(); env(ov, t, 0.001, g * 0.5, 0.06); o.connect(ov); ov.connect(drums); o.start(t); o.stop(t + 0.12); } function arpNote(t, m, g = 0.12, len = 0.17) { const v = ctx.createGain(); env(v, t, 0.003, g, len); v.connect(arpBus); for (const [type, det] of [['sawtooth', -7], ['square', 7]]) { const o = ctx.createOscillator(); o.type = type; o.frequency.value = midi(m); o.detune.value = det; const og = ctx.createGain(); og.gain.value = type === 'square' ? 0.5 : 0.8; o.connect(og); og.connect(v); o.start(t); o.stop(t + len + 0.05); } } function bass(t, m, g = 0.32, len = BEAT * 0.45) { const lp = ctx.createBiquadFilter(); lp.type = 'lowpass'; lp.frequency.setValueAtTime(900, t); lp.frequency.exponentialRampToValueAtTime(220, t + len); lp.Q.value = 4; const v = ctx.createGain(); v.gain.setValueAtTime(0, t); v.gain.linearRampToValueAtTime(g, t + 0.004); v.gain.setValueAtTime(g, t + len * 0.7); v.gain.linearRampToValueAtTime(0, t + len); lp.connect(v); v.connect(duck); const o = ctx.createOscillator(); o.type = 'sawtooth'; o.frequency.value = midi(m); o.connect(lp); o.start(t); o.stop(t + len + 0.02); const sub = ctx.createOscillator(); sub.type = 'sine'; sub.frequency.value = midi(m - 12); const sg = ctx.createGain(); sg.gain.value = 0.9; sub.connect(sg); sg.connect(v); sub.start(t); sub.stop(t + len + 0.02); } function pad(t, dur, notes, g = 0.05, cutoff = 1500) { const lp = ctx.createBiquadFilter(); lp.type = 'lowpass'; lp.frequency.value = cutoff; lp.Q.value = 0.7; const v = ctx.createGain(); v.gain.setValueAtTime(0, t); v.gain.linearRampToValueAtTime(g, t + 0.25); v.gain.setValueAtTime(g, t + dur - 0.1); v.gain.linearRampToValueAtTime(0, t + dur + 0.25); lp.connect(v); v.connect(duck); const s = ctx.createGain(); s.gain.value = 0.6; v.connect(s); s.connect(verbIn); for (const m of notes) for (const det of [-9, 9]) { const o = ctx.createOscillator(); o.type = 'sawtooth'; o.frequency.value = midi(m); o.detune.value = det; const p = ctx.createStereoPanner(); p.pan.value = det < 0 ? -0.4 : 0.4; o.connect(p); p.connect(lp); o.start(t); o.stop(t + dur + 0.3); } } function impact(t, big = false) { const o = ctx.createOscillator(); o.type = 'sine'; o.frequency.setValueAtTime(big ? 120 : 100, t); o.frequency.exponentialRampToValueAtTime(32, t + (big ? 0.9 : 0.5)); const v = ctx.createGain(); env(v, t, 0.003, big ? 0.95 : 0.6, big ? 1.3 : 0.6); o.connect(v); v.connect(master); o.start(t); o.stop(t + 1.6); const n = noiseSrc(t, 2.0); const hp = ctx.createBiquadFilter(); hp.type = 'highpass'; hp.frequency.value = big ? 1800 : 3200; const nv = ctx.createGain(); env(nv, t, 0.002, big ? 0.32 : 0.16, big ? 1.8 : 0.9); n.connect(hp); hp.connect(nv); nv.connect(master); const s = ctx.createGain(); s.gain.value = big ? 0.7 : 0.4; nv.connect(s); s.connect(verbIn); } function stab(t, name, g = 0.09) { const [root, iv] = CH[name]; const notes = [root - 12, ...iv.map(i => root + i), ...iv.map(i => root + 12 + i)]; const lp = ctx.createBiquadFilter(); lp.type = 'lowpass'; lp.frequency.setValueAtTime(5000, t); lp.frequency.exponentialRampToValueAtTime(600, t + 1.4); const v = ctx.createGain(); env(v, t, 0.004, g, 1.6); lp.connect(v); v.connect(master); const s = ctx.createGain(); s.gain.value = 0.8; v.connect(s); s.connect(verbIn); for (const m of notes) for (const det of [-11, 0, 11]) { const o = ctx.createOscillator(); o.type = 'sawtooth'; o.frequency.value = midi(m); o.detune.value = det; o.connect(lp); o.start(t); o.stop(t + 1.8); } } function whoosh(t, dur, g = 0.22, from = 500, to = 7000) { const n = noiseSrc(t, dur + 0.05); const bp = ctx.createBiquadFilter(); bp.type = 'bandpass'; bp.Q.value = 2.2; bp.frequency.setValueAtTime(from, t); bp.frequency.exponentialRampToValueAtTime(to, t + dur); const v = ctx.createGain(); v.gain.setValueAtTime(0.0001, t); v.gain.exponentialRampToValueAtTime(g, t + dur * 0.95); v.gain.linearRampToValueAtTime(0, t + dur); n.connect(bp); bp.connect(v); v.connect(master); } function riser(t, dur, g = 0.07) { whoosh(t, dur, 0.3, 300, 9000); const o = ctx.createOscillator(); o.type = 'sawtooth'; o.frequency.setValueAtTime(midi(53), t); o.frequency.exponentialRampToValueAtTime(midi(77), t + dur); const lp = ctx.createBiquadFilter(); lp.type = 'lowpass'; lp.frequency.setValueAtTime(800, t); lp.frequency.exponentialRampToValueAtTime(6000, t + dur); const v = ctx.createGain(); v.gain.setValueAtTime(0, t); v.gain.linearRampToValueAtTime(g, t + dur); v.gain.linearRampToValueAtTime(0, t + dur + 0.02); o.connect(lp); lp.connect(v); v.connect(master); o.start(t); o.stop(t + dur + 0.05); } function key(t, g) { const n = noiseSrc(t, 0.02); const bp = ctx.createBiquadFilter(); bp.type = 'bandpass'; bp.frequency.value = 2600 + R() * 2600; bp.Q.value = 3; const p = ctx.createStereoPanner(); p.pan.value = R() * 0.5 - 0.25; const v = ctx.createGain(); env(v, t, 0.0008, g * (0.7 + R() * 0.6), 0.012); n.connect(bp); bp.connect(v); v.connect(p); p.connect(master); } // ---- arrangement ---- const sub = scene('submit').start, brand = scene('brand').start, drop = scene('config').start; const rollBar = scene('submit').start - BAR; // last bar before the Submit card duck.gain.setValueAtTime(1, 0); const last = BARS.length - 1; for (let b = 0; b <= last; b++) { const t0 = b * BAR, name = BARS[b], [root, iv] = CH[name]; const tones = [0, iv[1], iv[2], 12, 12 + iv[1], 12 + iv[2], 24].map(x => root + 12 + x); const main = t0 >= drop && t0 < sub, end = b === last, calm = t0 >= sub && t0 < brand; const groove = main || (t0 >= brand && !end); // pad if (!end) pad(t0, BAR, [root, root + iv[1], root + iv[2], root + 12], calm ? 0.06 : t0 < drop ? 0.045 : 0.035, calm ? 1100 : 1500); for (let s = 0; s < 16; s++) { const t = t0 + s * BEAT / 4; const beat = s % 4 === 0, off = s % 4 === 2; const cutDrums = t0 === rollBar && s >= 8; // drums drop out for the last half bar of the riser // arp if (!end) { if (calm) { if (s % 2 === 0) arpNote(t, tones[ARP[s]] ?? root, 0.09, 0.3); } else arpNote(t, tones[ARP[s]] ?? root, t0 < drop ? 0.08 : 0.11, 0.16); } if (groove && !cutDrums) { if (beat) kick(t); if (off) { hat(t, 0.14, 0.2); bass(t, root - 12); } else if (!beat) hat(t, 0.045, -0.2); if (t0 >= drop + BAR * 2 && (s === 4 || s === 12)) clap(t); } if (calm && off) hat(t, 0.05, 0.15); if (b === 1 && s >= 8 && s % 2 === 0) hat(t, 0.02 + 0.06 * (s - 8) / 8, 0); // intro tick-in } } // snare roll + riser into the Submit card for (let i = 0; i < 8; i++) snare(rollBar + i * BEAT / 4, 0.04 + 0.1 * i / 7); for (let i = 0; i < 16; i++) snare(rollBar + BAR / 2 + i * BEAT / 8, 0.14 + 0.18 * i / 15); riser(rollBar, BAR); riser(brand - BAR / 2, BAR / 2, 0.05); whoosh(drop - BAR / 2, BAR / 2, 0.25); // hits on the cuts for (const c of CUTS) { const big = c === sub || c === brand; if (!big && c !== drop) whoosh(c - 0.32, 0.32, 0.16, 800, 6000); impact(c, big || c === drop); } stab(drop, 'Fm', 0.06); stab(sub, 'Db', 0.1); stab(brand, 'Fm', 0.1); stab(last * BAR, 'Fm', 0.11); impact(last * BAR, true); kick(last * BAR, 1); // key clicks for (const t of keyTimes()) key(t, t < drop ? 0.16 : 0.07); const buf = await ctx.startRendering(); const n = Math.round(DURATION * SR); return { sampleRate: SR, left: buf.getChannelData(0).slice(0, n), right: buf.getChannelData(1).slice(0, n) }; } /** 32-bit float stereo WAV, base64. */ export async function renderWav() { const { sampleRate, left, right } = await renderMusic(); const n = left.length, bytes = 44 + n * 8; const ab = new ArrayBuffer(bytes), dv = new DataView(ab); const str = (o, s) => { for (let i = 0; i < s.length; i++) dv.setUint8(o + i, s.charCodeAt(i)); }; str(0, 'RIFF'); dv.setUint32(4, bytes - 8, true); str(8, 'WAVE'); str(12, 'fmt '); dv.setUint32(16, 16, true); dv.setUint16(20, 3, true); dv.setUint16(22, 2, true); dv.setUint32(24, sampleRate, true); dv.setUint32(28, sampleRate * 8, true); dv.setUint16(32, 8, true); dv.setUint16(34, 32, true); str(36, 'data'); dv.setUint32(40, n * 8, true); let peak = 0; for (let i = 0; i < n; i++) { dv.setFloat32(44 + i * 8, left[i], true); dv.setFloat32(48 + i * 8, right[i], true); peak = Math.max(peak, Math.abs(left[i]), Math.abs(right[i])); } const u8 = new Uint8Array(ab); let bin = ''; for (let i = 0; i < u8.length; i += 0x8000) bin += String.fromCharCode.apply(null, u8.subarray(i, i + 0x8000)); return { base64: btoa(bin), peak, seconds: n / sampleRate }; }