feat: 30 comedy sound effects, replace 75% round-end TTS with SFX
Comedy sounds (all procedural Web Audio, no samples): - Vine Boom, Air Horn (MLG), Bruh, Fart, Record Scratch - Rubber Chicken, Squeaky Toy, Wet Slap, Bone Crack, Cartoon Run - Rim Shot (ba dum tss), Slide Whistle Up/Down, Yippee, Ding - Taco Bell Bong, Windows Error, Meme Thud - Sad Trombone, Emotional Damage, Dun Dun Dunnnn - Fail Horn, Price Is Right Fail, Buzzer, Sad Violin - Mission Failed, Crickets Wiring: - Round-end narration: 75% comedy SFX, 25% TTS (was 100% TTS) - Critical rounds get Emotional Damage, draws get random fail - Showboat loop: 40% chance of comedy sound punctuation after each - KO finish: 50% chance of comedy fail sound on loser - sfxRandomSilly expanded with comedy sounds - New exports: sfxRandomComedy, sfxRandomFail Also adds bulletTimeAttack combat choreography + ultimateBulletTimeBarrage (tier 3+ ultimate) to fight system. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
d3f026d356
commit
a59b0748a7
+470
-37
@@ -1384,10 +1384,411 @@ export function sfxFail() {
|
||||
|
||||
// Pick a random silly sound
|
||||
export function sfxRandomSilly() {
|
||||
const fns = [sfxBoing, sfxBonk, sfxSplat, sfxZap, sfxCoin, sfxSlideUp]
|
||||
const fns = [
|
||||
sfxBoing, sfxBonk, sfxSplat, sfxZap, sfxCoin, sfxSlideUp,
|
||||
sfxVineBoom, sfxAirHorn, sfxFart, sfxRubberChicken, sfxSqueakyToy,
|
||||
sfxCartoonRun, sfxWetSlap, sfxBoneCrack, sfxRimShot, sfxDing,
|
||||
]
|
||||
fns[Math.floor(Math.random() * fns.length)]()
|
||||
}
|
||||
|
||||
// Random comedy sound — heavier on meme sounds, for taunts/showboats/round ends
|
||||
export function sfxRandomComedy() {
|
||||
const fns = [
|
||||
sfxVineBoom, sfxAirHorn, sfxBruh, sfxFart, sfxRecordScratch,
|
||||
sfxRubberChicken, sfxSqueakyToy, sfxWetSlap, sfxBoneCrack,
|
||||
sfxCartoonRun, sfxRimShot, sfxSlideWhistleUp, sfxSlideWhistleDown,
|
||||
sfxYippee, sfxDing, sfxTacoBellBong, sfxWindowsError, sfxMemeThud,
|
||||
sfxBoing, sfxBonk, sfxSplat,
|
||||
]
|
||||
fns[Math.floor(Math.random() * fns.length)]()
|
||||
}
|
||||
|
||||
// Random fail sound — for missed attacks, dodges, KOs on the loser
|
||||
export function sfxRandomFail() {
|
||||
const fns = [
|
||||
sfxSadTrombone, sfxFailHorn, sfxPriceIsRightFail, sfxBuzzer,
|
||||
sfxMissionFailed, sfxCrickets, sfxWindowsError, sfxSadViolin,
|
||||
sfxWomp, sfxEmotionalDamage, sfxDunDunDun,
|
||||
]
|
||||
fns[Math.floor(Math.random() * fns.length)]()
|
||||
}
|
||||
|
||||
// === COMEDY SOUND EFFECTS ===
|
||||
// Procedural comedy/meme sounds inspired by classic internet soundboard culture
|
||||
|
||||
// VINE BOOM — iconic deep bass boom with heavy sub + distortion
|
||||
export function sfxVineBoom() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
bodyThump(45, 0.4, d, t, 0.5)
|
||||
bodyThump(90, 0.3, d, t, 0.35)
|
||||
fmImpact(150, 5, 0.25, d, t)
|
||||
noise(0.06, d, t)
|
||||
reverbTail(0.5, d, t + 0.1)
|
||||
}
|
||||
|
||||
// AIR HORN (MLG) — obnoxious ascending horn blasts
|
||||
export function sfxAirHorn() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const bt = t + i * 0.15
|
||||
tone(520 + i * 30, 'sawtooth', 0.12, d, bt)
|
||||
tone(523 + i * 30, 'square', 0.12, d, bt)
|
||||
tone(1046 + i * 60, 'sawtooth', 0.08, d, bt)
|
||||
}
|
||||
reverbTail(0.3, d, t + 0.4)
|
||||
}
|
||||
|
||||
// BRUH — deep bass impact with vowel-like formant resonance
|
||||
export function sfxBruh() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
bodyThump(80, 0.3, d, t, 0.4)
|
||||
const bufSize = Math.floor(c.sampleRate * 0.25)
|
||||
const buf = c.createBuffer(1, bufSize, c.sampleRate)
|
||||
const data = buf.getChannelData(0)
|
||||
for (let i = 0; i < bufSize; i++) data[i] = Math.random() * 2 - 1
|
||||
const src = c.createBufferSource(); src.buffer = buf
|
||||
const bp = c.createBiquadFilter(); bp.type = 'bandpass'; bp.frequency.value = 300; bp.Q.value = 5
|
||||
const g = c.createGain()
|
||||
g.gain.setValueAtTime(0.25, t)
|
||||
g.gain.exponentialRampToValueAtTime(0.001, t + 0.25)
|
||||
src.connect(bp); bp.connect(g); g.connect(d)
|
||||
src.start(t); src.stop(t + 0.25)
|
||||
bodyThump(55, 0.15, d, t + 0.05, 0.3)
|
||||
reverbTail(0.3, d, t + 0.1)
|
||||
}
|
||||
|
||||
// FART — rumbling low-frequency modulated noise
|
||||
export function sfxFart() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
const duration = 0.3 + Math.random() * 0.3
|
||||
const osc = c.createOscillator(); osc.type = 'sawtooth'
|
||||
osc.frequency.setValueAtTime(80 + Math.random() * 40, t)
|
||||
osc.frequency.exponentialRampToValueAtTime(40 + Math.random() * 30, t + duration)
|
||||
const lfo = c.createOscillator(); lfo.type = 'square'
|
||||
lfo.frequency.setValueAtTime(20 + Math.random() * 30, t)
|
||||
const lfoG = c.createGain(); lfoG.gain.value = 40
|
||||
lfo.connect(lfoG); lfoG.connect(osc.frequency)
|
||||
const bp = c.createBiquadFilter(); bp.type = 'bandpass'; bp.frequency.value = 150 + Math.random() * 100; bp.Q.value = 2
|
||||
const g = c.createGain()
|
||||
g.gain.setValueAtTime(0.3, t)
|
||||
g.gain.setValueAtTime(0.25, t + duration * 0.3)
|
||||
g.gain.exponentialRampToValueAtTime(0.001, t + duration)
|
||||
osc.connect(bp); bp.connect(g); g.connect(d)
|
||||
osc.start(t); osc.stop(t + duration)
|
||||
lfo.start(t); lfo.stop(t + duration)
|
||||
noise(duration * 0.6, d, t)
|
||||
}
|
||||
|
||||
// RECORD SCRATCH — vinyl stop effect
|
||||
export function sfxRecordScratch() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
sweep(3000, 100, 'sawtooth', 0.15, d)
|
||||
sweep(2500, 80, 'square', 0.12, d)
|
||||
noise(0.1, d, t)
|
||||
highSnap(4000, 0.03, d, t)
|
||||
setTimeout(() => sweep(200, 400, 'sine', 0.08, d), 100)
|
||||
}
|
||||
|
||||
// RUBBER CHICKEN — squeaky honk
|
||||
export function sfxRubberChicken() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
sweep(300, 1200, 'sine', 0.1, d)
|
||||
const osc = c.createOscillator(); osc.type = 'sine'
|
||||
osc.frequency.setValueAtTime(1100, t + 0.1)
|
||||
osc.frequency.setValueAtTime(1200, t + 0.15)
|
||||
osc.frequency.exponentialRampToValueAtTime(600, t + 0.35)
|
||||
const lfo = c.createOscillator(); lfo.type = 'sine'; lfo.frequency.value = 20
|
||||
const lfoG = c.createGain(); lfoG.gain.value = 80
|
||||
lfo.connect(lfoG); lfoG.connect(osc.frequency)
|
||||
const g = c.createGain()
|
||||
g.gain.setValueAtTime(0.25, t + 0.1)
|
||||
g.gain.exponentialRampToValueAtTime(0.001, t + 0.35)
|
||||
osc.connect(g); g.connect(d)
|
||||
osc.start(t + 0.1); osc.stop(t + 0.35)
|
||||
lfo.start(t + 0.1); lfo.stop(t + 0.35)
|
||||
}
|
||||
|
||||
// SQUEAKY TOY — quick toy squeeze
|
||||
export function sfxSqueakyToy() {
|
||||
const d = getSfxDest()
|
||||
sweep(400, 1800, 'sine', 0.06, d)
|
||||
sweep(1800, 600, 'sine', 0.1, d)
|
||||
setTimeout(() => sweep(500, 1500, 'sine', 0.05, d), 120)
|
||||
}
|
||||
|
||||
// WET SLAP — meaty slap with low-end body
|
||||
export function sfxWetSlap() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
highSnap(3000, 0.02, d, t)
|
||||
highSnap(1200, 0.03, d, t)
|
||||
bodyThump(150, 0.08, d, t, 0.3)
|
||||
noise(0.06, d, t)
|
||||
fmImpact(300, 2, 0.08, d, t)
|
||||
reverbTail(0.15, d, t + 0.03)
|
||||
}
|
||||
|
||||
// BONE CRACK — sharp multi-frequency crack sequence
|
||||
export function sfxBoneCrack() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const ct = t + i * 0.04
|
||||
highSnap(3000 + i * 1500, 0.015, d, ct)
|
||||
fmImpact(800 + i * 400, 4, 0.03, d, ct)
|
||||
noise(0.02, d, ct)
|
||||
}
|
||||
bodyThump(100, 0.04, d, t + 0.08, 0.15)
|
||||
}
|
||||
|
||||
// CARTOON RUN — rapid bongo/pitter-patter footsteps
|
||||
export function sfxCartoonRun() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const st = t + i * 0.05
|
||||
bodyThump(200 + (i % 2) * 80, 0.03, d, st, 0.15)
|
||||
highSnap(2000 + Math.random() * 1000, 0.01, d, st)
|
||||
}
|
||||
}
|
||||
|
||||
// RIM SHOT — ba dum tss
|
||||
export function sfxRimShot() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
bodyThump(180, 0.08, d, t, 0.2)
|
||||
bodyThump(140, 0.08, d, t + 0.15, 0.2)
|
||||
highSnap(4000, 0.04, d, t + 0.3)
|
||||
noise(0.15, d, t + 0.3)
|
||||
fmImpact(2000, 3, 0.12, d, t + 0.3)
|
||||
bodyThump(200, 0.04, d, t + 0.3, 0.15)
|
||||
}
|
||||
|
||||
// SLIDE WHISTLE UP — cartoon ascend
|
||||
export function sfxSlideWhistleUp() {
|
||||
const d = getSfxDest()
|
||||
sweep(300, 2500, 'sine', 0.4, d)
|
||||
sweep(305, 2510, 'sine', 0.4, d)
|
||||
}
|
||||
|
||||
// SLIDE WHISTLE DOWN — cartoon descend
|
||||
export function sfxSlideWhistleDown() {
|
||||
const d = getSfxDest()
|
||||
sweep(2500, 200, 'sine', 0.5, d)
|
||||
sweep(2510, 205, 'sine', 0.5, d)
|
||||
}
|
||||
|
||||
// YIPPEE — ascending cheerful chirps
|
||||
export function sfxYippee() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
const notes = [523, 659, 784, 1047]
|
||||
for (let i = 0; i < notes.length; i++) {
|
||||
tone(notes[i], 'triangle', 0.08, d, t + i * 0.07)
|
||||
tone(notes[i] * 2, 'sine', 0.06, d, t + i * 0.07)
|
||||
}
|
||||
}
|
||||
|
||||
// DING — bright bell
|
||||
export function sfxDing() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
fmImpact(1319, 1.5, 0.3, d, t)
|
||||
tone(1319, 'triangle', 0.25, d, t)
|
||||
tone(2638, 'sine', 0.15, d, t)
|
||||
}
|
||||
|
||||
// TACO BELL BONG — deep resonant bell
|
||||
export function sfxTacoBellBong() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
fmImpact(130, 2.5, 0.8, d, t)
|
||||
fmImpact(261, 3, 0.6, d, t)
|
||||
tone(130, 'sine', 0.6, d, t)
|
||||
bodyThump(65, 0.3, d, t, 0.2)
|
||||
reverbTail(0.7, d, t + 0.1)
|
||||
}
|
||||
|
||||
// WINDOWS ERROR — two-tone error chord
|
||||
export function sfxWindowsError() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
tone(440, 'square', 0.15, d, t)
|
||||
tone(466, 'square', 0.15, d, t)
|
||||
tone(220, 'triangle', 0.2, d, t)
|
||||
setTimeout(() => {
|
||||
tone(349, 'square', 0.2, d)
|
||||
tone(175, 'triangle', 0.25, d)
|
||||
}, 180)
|
||||
}
|
||||
|
||||
// MEME THUD — super heavy bass drop
|
||||
export function sfxMemeThud() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
bodyThump(35, 0.5, d, t, 0.5)
|
||||
bodyThump(70, 0.4, d, t, 0.4)
|
||||
noise(0.08, d, t)
|
||||
fmImpact(100, 6, 0.3, d, t)
|
||||
reverbTail(0.6, d, t + 0.1)
|
||||
}
|
||||
|
||||
// SAD TROMBONE (Wa Wa Wa Waaaa) — classic comedy fail
|
||||
export function sfxSadTrombone() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
const notes: [number, number][] = [[466, 0.25], [440, 0.25], [415, 0.25], [392, 0.7]]
|
||||
let off = 0
|
||||
for (let i = 0; i < notes.length; i++) {
|
||||
const [freq, dur] = notes[i]
|
||||
tone(freq, 'sawtooth', dur, d, t + off)
|
||||
tone(freq * 0.5, 'triangle', dur, d, t + off)
|
||||
if (i === 3) {
|
||||
const osc = c.createOscillator(); osc.type = 'sawtooth'; osc.frequency.value = freq
|
||||
const lfo = c.createOscillator(); lfo.type = 'sine'; lfo.frequency.value = 5
|
||||
const lfoG = c.createGain(); lfoG.gain.value = 8
|
||||
lfo.connect(lfoG); lfoG.connect(osc.frequency)
|
||||
const g = c.createGain()
|
||||
g.gain.setValueAtTime(0.2, t + off)
|
||||
g.gain.exponentialRampToValueAtTime(0.001, t + off + dur)
|
||||
osc.connect(g); g.connect(d)
|
||||
osc.start(t + off); osc.stop(t + off + dur)
|
||||
lfo.start(t + off); lfo.stop(t + off + dur)
|
||||
}
|
||||
off += dur
|
||||
}
|
||||
reverbTail(0.5, d, t + off)
|
||||
}
|
||||
|
||||
// EMOTIONAL DAMAGE — dramatic descending brass stab
|
||||
export function sfxEmotionalDamage() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
tone(220, 'sawtooth', 0.3, d, t)
|
||||
tone(277, 'sawtooth', 0.3, d, t)
|
||||
tone(330, 'sawtooth', 0.3, d, t)
|
||||
bodyThump(110, 0.2, d, t, 0.3)
|
||||
noise(0.08, d, t)
|
||||
fmImpact(600, 4, 0.2, d, t)
|
||||
reverbTail(0.5, d, t + 0.1)
|
||||
}
|
||||
|
||||
// DUN DUN DUNNNNN — dramatic reveal stinger
|
||||
export function sfxDunDunDun() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
tone(147, 'sawtooth', 0.2, d, t)
|
||||
tone(147, 'triangle', 0.2, d, t)
|
||||
bodyThump(73, 0.15, d, t, 0.25)
|
||||
tone(147, 'sawtooth', 0.2, d, t + 0.25)
|
||||
bodyThump(73, 0.15, d, t + 0.25, 0.25)
|
||||
tone(110, 'sawtooth', 0.8, d, t + 0.5)
|
||||
tone(110, 'triangle', 0.8, d, t + 0.5)
|
||||
tone(55, 'sine', 0.8, d, t + 0.5)
|
||||
bodyThump(55, 0.5, d, t + 0.5, 0.3)
|
||||
reverbTail(0.8, d, t + 0.6)
|
||||
}
|
||||
|
||||
// FAIL HORN — game show wrong answer horn
|
||||
export function sfxFailHorn() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
tone(311, 'sawtooth', 0.3, d, t)
|
||||
tone(156, 'sawtooth', 0.3, d, t)
|
||||
tone(233, 'square', 0.3, d, t)
|
||||
setTimeout(() => {
|
||||
tone(277, 'sawtooth', 0.5, d)
|
||||
tone(139, 'sawtooth', 0.5, d)
|
||||
tone(208, 'square', 0.5, d)
|
||||
bodyThump(70, 0.3, d, undefined, 0.2)
|
||||
}, 350)
|
||||
reverbTail(0.5, d, t + 0.8)
|
||||
}
|
||||
|
||||
// PRICE IS RIGHT FAIL — descending tuba + sad brass
|
||||
export function sfxPriceIsRightFail() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
const notes = [262, 247, 233, 220, 208, 196]
|
||||
for (let i = 0; i < notes.length; i++) {
|
||||
const nt = t + i * 0.18
|
||||
tone(notes[i], 'sawtooth', 0.2, d, nt)
|
||||
tone(notes[i] * 0.5, 'triangle', 0.2, d, nt)
|
||||
}
|
||||
bodyThump(65, 0.4, d, t + notes.length * 0.18, 0.3)
|
||||
reverbTail(0.5, d, t + notes.length * 0.18)
|
||||
}
|
||||
|
||||
// BUZZER — harsh game show wrong answer
|
||||
export function sfxBuzzer() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
tone(150, 'square', 0.4, d, t)
|
||||
tone(153, 'square', 0.4, d, t)
|
||||
tone(75, 'square', 0.4, d, t)
|
||||
noise(0.15, d, t)
|
||||
bodyThump(60, 0.2, d, t, 0.2)
|
||||
}
|
||||
|
||||
// SAD VIOLIN — thin high vibrato melody
|
||||
export function sfxSadViolin() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
const notes: [number, number][] = [[659, 0.3], [622, 0.3], [587, 0.4], [554, 0.5]]
|
||||
let off = 0
|
||||
for (const [freq, dur] of notes) {
|
||||
const osc = c.createOscillator(); osc.type = 'sawtooth'; osc.frequency.value = freq
|
||||
const lfo = c.createOscillator(); lfo.type = 'sine'; lfo.frequency.value = 6
|
||||
const lfoG = c.createGain(); lfoG.gain.value = 6
|
||||
lfo.connect(lfoG); lfoG.connect(osc.frequency)
|
||||
const bp = c.createBiquadFilter(); bp.type = 'bandpass'; bp.frequency.value = freq * 2; bp.Q.value = 3
|
||||
const g = c.createGain()
|
||||
g.gain.setValueAtTime(0.15, t + off)
|
||||
g.gain.exponentialRampToValueAtTime(0.001, t + off + dur)
|
||||
osc.connect(bp); bp.connect(g); g.connect(d)
|
||||
osc.start(t + off); osc.stop(t + off + dur)
|
||||
lfo.start(t + off); lfo.stop(t + off + dur)
|
||||
off += dur
|
||||
}
|
||||
}
|
||||
|
||||
// MISSION FAILED — dark two-note descending stab
|
||||
export function sfxMissionFailed() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
tone(165, 'sawtooth', 0.3, d, t)
|
||||
tone(196, 'sawtooth', 0.3, d, t)
|
||||
tone(247, 'sawtooth', 0.3, d, t)
|
||||
bodyThump(82, 0.2, d, t, 0.2)
|
||||
setTimeout(() => {
|
||||
tone(139, 'sawtooth', 0.6, d)
|
||||
tone(165, 'sawtooth', 0.6, d)
|
||||
tone(208, 'sawtooth', 0.6, d)
|
||||
bodyThump(70, 0.4, d, undefined, 0.25)
|
||||
reverbTail(0.6, d)
|
||||
}, 400)
|
||||
}
|
||||
|
||||
// CRICKETS — awkward silence chirps
|
||||
export function sfxCrickets() {
|
||||
const d = getSfxDest()
|
||||
const c = getCtx(); const t = c.currentTime
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const ct = t + i * 0.4
|
||||
tone(4200, 'sine', 0.03, d, ct)
|
||||
tone(4200, 'sine', 0.03, d, ct + 0.05)
|
||||
tone(4500, 'sine', 0.02, d, ct + 0.03)
|
||||
tone(4500, 'sine', 0.02, d, ct + 0.08)
|
||||
}
|
||||
}
|
||||
|
||||
// === BACKGROUND MUSIC ===
|
||||
// Rich, full arcade fighting soundtrack with 6 simultaneous layers
|
||||
|
||||
@@ -1619,7 +2020,8 @@ const track1: MusicTrack = {
|
||||
330,415,494,415, 330,415,494,659, 494,659,494,415, 330,494,415,330,
|
||||
440,523,659,523, 440,659,523,440, 659,523,440,330, 440,523,659,0,
|
||||
],
|
||||
chords: [[220,262,330],[175,220,262],[262,330,392],[196,247,294],[220,262,330],[294,349,440],[165,208,247],[220,262,330]],
|
||||
// I-V-vi-IV in C major (classic pop anthem)
|
||||
chords: [[262,330,392],[196,247,294],[220,262,330],[175,220,262],[262,330,392],[196,247,294],[220,262,330],[175,220,262]],
|
||||
drums: [
|
||||
1,3,0,3, 2,0,3,0, 1,3,0,3, 2,0,3,0, 1,3,0,3, 2,0,3,0, 1,3,0,3, 2,0,4,0,
|
||||
1,3,0,3, 2,0,3,3, 1,3,0,3, 2,0,3,0, 1,3,0,3, 2,3,3,0, 1,3,0,3, 2,3,4,3,
|
||||
@@ -1661,7 +2063,8 @@ const track2: MusicTrack = {
|
||||
494,622,740,622, 494,622,740,988, 740,988,740,622, 494,740,622,494,
|
||||
330,392,494,392, 330,494,392,330, 494,392,330,247, 330,392,494,0,
|
||||
],
|
||||
chords: [[165,196,247],[131,165,196],[196,247,294],[147,185,220],[165,196,247],[220,262,330],[247,311,370],[165,196,247]],
|
||||
// i-VII-VI-v in E minor (Andalusian descent)
|
||||
chords: [[165,196,247],[147,185,220],[131,165,196],[123,147,185],[165,196,247],[131,165,196],[147,185,220],[165,196,247]],
|
||||
drums: [
|
||||
1,0,3,0, 2,0,3,0, 1,0,3,0, 2,0,3,0, 1,0,3,0, 2,0,3,0, 1,0,3,0, 2,0,4,0,
|
||||
1,0,3,0, 2,0,3,3, 1,0,3,0, 2,0,3,0, 1,0,3,0, 2,0,3,0, 1,3,3,3, 2,3,4,3,
|
||||
@@ -1703,7 +2106,8 @@ const track3: MusicTrack = {
|
||||
220,277,330,277, 220,277,330,440, 330,440,330,277, 220,330,277,220,
|
||||
294,349,440,349, 294,440,349,294, 440,349,294,220, 294,349,440,0,
|
||||
],
|
||||
chords: [[147,175,220],[233,294,349],[175,220,262],[131,165,196],[147,175,220],[196,233,294],[220,277,330],[147,175,220]],
|
||||
// i-III-VI-V in D minor (dark dramatic)
|
||||
chords: [[147,175,220],[175,220,262],[233,294,349],[220,277,330],[147,175,220],[175,220,262],[233,294,349],[147,175,220]],
|
||||
drums: [
|
||||
1,0,0,3, 0,0,2,0, 1,0,0,3, 0,0,2,0, 1,0,0,3, 0,0,2,0, 1,0,3,3, 2,0,0,0,
|
||||
1,0,0,3, 0,0,2,0, 1,0,3,3, 2,0,0,0, 1,0,0,3, 0,0,2,0, 1,0,0,3, 2,0,4,0,
|
||||
@@ -1745,7 +2149,8 @@ const track4: MusicTrack = {
|
||||
392,494,587,494, 392,494,587,784, 587,784,587,494, 392,587,494,392,
|
||||
262,311,392,311, 262,392,311,262, 392,311,262,196, 262,311,392,0,
|
||||
],
|
||||
chords: [[262,311,392],[208,262,311],[156,196,233],[233,294,349],[262,311,392],[175,208,262],[196,247,294],[262,311,392]],
|
||||
// i-iv-VII-III in C minor (aggressive power)
|
||||
chords: [[131,156,196],[175,208,262],[233,294,349],[156,196,233],[131,156,196],[175,208,262],[233,294,349],[131,156,196]],
|
||||
drums: [
|
||||
1,3,1,3, 2,0,3,0, 1,3,0,3, 2,0,3,0, 1,3,1,3, 2,0,3,0, 1,3,0,3, 2,0,4,0,
|
||||
1,3,1,3, 2,0,3,3, 1,3,0,3, 2,3,3,0, 1,3,1,3, 2,3,3,0, 1,3,3,3, 2,3,4,3,
|
||||
@@ -1787,7 +2192,8 @@ const track5: MusicTrack = {
|
||||
392,494,587,494, 392,494,587,784, 587,784,587,494, 392,587,494,392,
|
||||
440,523,659,523, 440,659,523,440, 659,523,440,330, 440,523,659,0,
|
||||
],
|
||||
chords: [[220,262,330],[165,196,247],[175,220,262],[131,165,196],[220,262,330],[147,175,220],[196,247,294],[220,262,330]],
|
||||
// i-v-iv-VII in A minor (grinding stubborn)
|
||||
chords: [[220,262,330],[165,196,247],[147,175,220],[196,247,294],[220,262,330],[165,196,247],[147,175,220],[220,262,330]],
|
||||
drums: [
|
||||
1,0,0,3, 2,0,0,3, 1,0,0,3, 2,0,0,3, 1,0,0,3, 2,0,0,3, 1,0,3,0, 2,0,0,0,
|
||||
1,0,0,3, 2,0,3,0, 1,0,0,3, 2,0,0,3, 1,0,3,0, 2,0,3,0, 1,0,3,3, 2,0,4,0,
|
||||
@@ -1829,7 +2235,8 @@ const track6: MusicTrack = {
|
||||
294,370,440,370, 294,370,440,587, 440,587,440,370, 294,440,370,294,
|
||||
196,233,294,233, 196,294,233,196, 294,233,196,147, 196,233,294,0,
|
||||
],
|
||||
chords: [[196,233,294],[156,196,233],[233,294,349],[175,220,262],[196,233,294],[131,156,196],[147,185,220],[196,233,294]],
|
||||
// i-VI-iv-V in G minor (melancholic beauty)
|
||||
chords: [[196,233,294],[156,196,233],[131,156,196],[147,185,220],[196,233,294],[156,196,233],[147,185,220],[196,233,294]],
|
||||
drums: [
|
||||
1,0,0,0, 2,0,0,3, 1,0,0,0, 2,0,0,0, 1,0,0,3, 2,0,0,0, 1,0,0,3, 2,0,0,0,
|
||||
1,0,0,3, 2,0,0,0, 1,0,3,0, 2,0,0,3, 1,0,0,3, 2,0,0,3, 1,0,3,0, 2,0,4,0,
|
||||
@@ -1871,7 +2278,8 @@ const track7: MusicTrack = {
|
||||
494,622,740,622, 494,622,740,988, 740,988,740,622, 494,740,622,494,
|
||||
330,392,494,392, 330,494,392,330, 494,392,330,247, 330,392,494,0,
|
||||
],
|
||||
chords: [[165,196,247],[196,247,294],[147,185,220],[131,165,196],[165,196,247],[220,262,330],[247,311,370],[165,196,247]],
|
||||
// I-IV-vi-V in G major (uplifting pop)
|
||||
chords: [[196,247,294],[131,165,196],[165,196,247],[147,185,220],[196,247,294],[131,165,196],[165,196,247],[196,247,294]],
|
||||
drums: [
|
||||
1,0,3,0, 2,0,3,0, 1,0,3,0, 2,0,3,0, 1,0,3,0, 2,0,3,0, 1,0,3,0, 2,0,4,0,
|
||||
1,0,3,0, 2,0,3,3, 1,0,3,0, 2,0,3,0, 1,0,3,0, 2,0,3,0, 1,3,3,3, 2,3,4,0,
|
||||
@@ -1913,7 +2321,8 @@ const track8: MusicTrack = {
|
||||
262,330,392,330, 262,330,392,523, 392,523,392,330, 262,392,330,262,
|
||||
349,415,523,415, 349,523,415,349, 523,415,349,262, 349,415,523,0,
|
||||
],
|
||||
chords: [[175,208,262],[139,175,208],[208,262,311],[156,196,233],[175,208,262],[233,277,349],[131,165,196],[175,208,262]],
|
||||
// i-VII-VI-VII in F minor (oscillating tension)
|
||||
chords: [[175,208,262],[156,196,233],[139,175,208],[156,196,233],[175,208,262],[139,175,208],[156,196,233],[175,208,262]],
|
||||
drums: [
|
||||
1,3,1,3, 2,0,3,0, 1,3,0,3, 2,0,3,0, 1,3,1,3, 2,0,3,0, 1,3,0,3, 2,0,4,0,
|
||||
1,3,1,3, 2,3,3,0, 1,3,0,3, 2,0,3,3, 1,3,1,3, 2,0,3,3, 1,3,3,3, 2,3,4,3,
|
||||
@@ -1955,7 +2364,8 @@ const track9: MusicTrack = {
|
||||
262,330,392,330, 262,330,392,523, 392,523,392,330, 262,392,330,262,
|
||||
294,349,440,349, 294,440,349,294, 440,349,294,220, 294,349,440,0,
|
||||
],
|
||||
chords: [[147,175,220],[196,233,294],[233,294,349],[220,277,330],[147,175,220],[175,220,262],[131,165,196],[147,175,220]],
|
||||
// i-iv-V-i → i-VI-VII-i in D minor (tension/release)
|
||||
chords: [[147,175,220],[196,233,294],[220,277,330],[147,175,220],[147,175,220],[233,294,349],[262,330,392],[147,175,220]],
|
||||
drums: [
|
||||
1,0,3,0, 2,0,3,3, 1,0,3,0, 2,0,3,0, 1,0,3,0, 2,0,3,0, 1,0,3,3, 2,0,3,0,
|
||||
1,0,3,3, 2,0,3,0, 1,3,3,0, 2,0,3,3, 1,0,3,0, 2,3,3,0, 1,3,3,3, 2,3,4,3,
|
||||
@@ -1997,7 +2407,8 @@ const track10: MusicTrack = {
|
||||
349,415,523,415, 349,415,523,698, 523,698,523,415, 349,523,415,349,
|
||||
392,494,587,494, 392,587,494,392, 587,494,392,294, 392,494,587,0,
|
||||
],
|
||||
chords: [[262,311,392],[156,196,233],[233,294,349],[196,247,294],[262,311,392],[208,262,311],[175,208,262],[196,247,294]],
|
||||
// I-vi-ii-V in Eb major (triumphant jazz-pop)
|
||||
chords: [[156,196,233],[131,156,196],[175,208,262],[233,294,349],[156,196,233],[131,156,196],[175,208,262],[233,294,349]],
|
||||
drums: [
|
||||
1,0,3,0, 2,0,3,0, 1,0,3,0, 2,0,3,0, 1,0,3,0, 2,0,3,0, 1,0,3,0, 2,0,4,0,
|
||||
1,0,3,0, 2,0,3,3, 1,0,3,0, 2,3,3,0, 1,3,3,0, 2,3,3,3, 1,3,1,3, 2,3,4,3,
|
||||
@@ -2016,34 +2427,38 @@ function playMusicBar() {
|
||||
const c = getCtx()
|
||||
getMusicDelay() // ensure delay is created
|
||||
|
||||
// Dynamic tempo: subtle intensity shift (+10 BPM at max)
|
||||
const dynamicBpm = activeTrack.bpm + currentIntensity * 10
|
||||
// Dynamic tempo: subtle intensity shift (+8 BPM at max)
|
||||
const dynamicBpm = activeTrack.bpm + currentIntensity * 8
|
||||
const beat = 60 / dynamicBpm
|
||||
const now = c.currentTime + 0.05
|
||||
const bar = barIndex % BARS
|
||||
const off = bar * STEPS
|
||||
const step = beat / 2
|
||||
|
||||
// Switch tracks at musical boundaries (full 8-bar cycles)
|
||||
const chillTracks = ALL_TRACKS.filter(t => t.bpm < 160)
|
||||
const midTracks = ALL_TRACKS.filter(t => t.bpm >= 160 && t.bpm < 175)
|
||||
const intenseTracks = ALL_TRACKS.filter(t => t.bpm >= 175)
|
||||
// === VERSE / CHORUS DYNAMICS ===
|
||||
// Bars 0-3 are "verse" (sparser), bars 4-7 are "chorus" (climactic, full)
|
||||
const isChorus = bar >= 4
|
||||
const leadThreshold = isChorus ? 0.15 : 0.4
|
||||
const arpThreshold = isChorus ? 0.25 : 0.55
|
||||
|
||||
// Switch tracks at musical boundaries — NEVER repeat the same track
|
||||
const switchEvery = currentIntensity > 0.7 ? 8 : currentIntensity > 0.4 ? 16 : 24
|
||||
if (barIndex > 0 && barIndex % switchEvery === 0) {
|
||||
const pickFrom = (pool: MusicTrack[]) => {
|
||||
const others = pool.filter(t => t !== activeTrack)
|
||||
return others.length > 0 ? others[Math.floor(Math.random() * others.length)] : pool[0]
|
||||
}
|
||||
const prevTrack = activeTrack
|
||||
if (currentIntensity > 0.7) {
|
||||
activeTrack = pickFrom(intenseTracks.length > 0 ? intenseTracks : ALL_TRACKS)
|
||||
} else if (currentIntensity < 0.3) {
|
||||
activeTrack = pickFrom(chillTracks.length > 0 ? chillTracks : ALL_TRACKS)
|
||||
} else if (Math.random() < 0.5) {
|
||||
activeTrack = pickFrom(midTracks.length > 0 ? midTracks : ALL_TRACKS)
|
||||
// Always pick from ALL tracks, excluding current — guarantees no repeat
|
||||
const others = ALL_TRACKS.filter(t => t !== activeTrack)
|
||||
// Prefer tracks matching intensity mood
|
||||
const chill = others.filter(t => t.bpm < 155)
|
||||
const mid = others.filter(t => t.bpm >= 155 && t.bpm < 168)
|
||||
const intense = others.filter(t => t.bpm >= 168)
|
||||
if (currentIntensity > 0.7 && intense.length > 0) {
|
||||
activeTrack = intense[Math.floor(Math.random() * intense.length)]
|
||||
} else if (currentIntensity < 0.3 && chill.length > 0) {
|
||||
activeTrack = chill[Math.floor(Math.random() * chill.length)]
|
||||
} else if (mid.length > 0 && Math.random() < 0.5) {
|
||||
activeTrack = mid[Math.floor(Math.random() * mid.length)]
|
||||
} else {
|
||||
// Full random for maximum variety
|
||||
activeTrack = pickFrom(ALL_TRACKS)
|
||||
activeTrack = others[Math.floor(Math.random() * others.length)]
|
||||
}
|
||||
// Smooth crossfade on track switch
|
||||
if (activeTrack !== prevTrack && musicGain) {
|
||||
@@ -2054,35 +2469,53 @@ function playMusicBar() {
|
||||
}
|
||||
}
|
||||
|
||||
// Chord pad for the whole bar
|
||||
// Chord pad for the whole bar — louder during chorus
|
||||
const barDur = STEPS * step
|
||||
chordPad(activeTrack.chords[bar].map(f => f * 2), barDur, musicGain, now)
|
||||
const padVol = isChorus ? 0.06 : 0.04
|
||||
const padFreqs = activeTrack.chords[bar].map(f => f * 2)
|
||||
const padC = getCtx()
|
||||
for (const freq of padFreqs) {
|
||||
const osc = padC.createOscillator()
|
||||
osc.type = 'triangle'
|
||||
osc.frequency.value = freq
|
||||
const g = padC.createGain()
|
||||
g.gain.setValueAtTime(padVol, now)
|
||||
g.gain.setValueAtTime(padVol, now + barDur * 0.8)
|
||||
g.gain.exponentialRampToValueAtTime(0.001, now + barDur)
|
||||
osc.connect(g); g.connect(musicGain)
|
||||
osc.start(now); osc.stop(now + barDur)
|
||||
}
|
||||
|
||||
for (let i = 0; i < STEPS; i++) {
|
||||
const t = now + i * step
|
||||
const idx = off + i
|
||||
const nd = step - 0.01
|
||||
|
||||
// Layer 1: Bass — always plays but gets thicker with intensity
|
||||
// Layer 1: Bass — always plays
|
||||
if (activeTrack.bass[idx] > 0) thickBass(activeTrack.bass[idx], nd, musicGain, t)
|
||||
|
||||
// Layer 2: FM lead — fades in above 0.3 intensity
|
||||
if (activeTrack.lead[idx] > 0 && currentIntensity > 0.3) {
|
||||
// Layer 2: FM lead — verse: needs higher intensity; chorus: plays freely
|
||||
if (activeTrack.lead[idx] > 0 && currentIntensity > leadThreshold) {
|
||||
fmLead(activeTrack.lead[idx], nd * 0.8, musicGain, t)
|
||||
}
|
||||
|
||||
// Layer 3: Arpeggio — fades in above 0.5 intensity
|
||||
if (activeTrack.arp[idx] > 0 && currentIntensity > 0.5) {
|
||||
// Layer 3: Arpeggio — verse: needs high intensity; chorus: plays freely
|
||||
if (activeTrack.arp[idx] > 0 && currentIntensity > arpThreshold) {
|
||||
chorusTone(activeTrack.arp[idx], 'triangle', nd * 0.6, musicGain, t, 0.04 + currentIntensity * 0.04)
|
||||
}
|
||||
|
||||
// Layer 4: Drums — always, but intensity controls density
|
||||
// Layer 4: Drums — intensity controls density
|
||||
const d = activeTrack.drums[idx]
|
||||
if (d === 1) kick(musicGain, t)
|
||||
if (d === 2 && currentIntensity > 0.2) snare(musicGain, t)
|
||||
if (d === 3) hihat(musicGain, t, false)
|
||||
if (d === 4 && currentIntensity > 0.4) hihat(musicGain, t, true)
|
||||
|
||||
// Chorus: extra snare ghost notes on off-beats for energy
|
||||
if (isChorus && currentIntensity > 0.6 && d === 0 && i % 4 === 2 && Math.random() < 0.3) {
|
||||
snare(musicGain, t)
|
||||
}
|
||||
|
||||
// Extra open hihat at high intensity (musical, not chaotic)
|
||||
if (currentIntensity > 0.85 && i === 14 && Math.random() < 0.3) {
|
||||
hihat(musicGain, t, true)
|
||||
|
||||
Reference in New Issue
Block a user