-- Procedural 8-bit style sound effects -- Each sound is defined as a simple config; add new sounds to SOUNDS table. Sound = {} Sound.SAMPLE_RATE = 22050 Sound.volume = 0.4 Sound.sounds = {} -- Waveform generators (phase-based for proper frequency sweeps) local function sampleWave(waveType, phase) if waveType == "square" then return phase < 0.5 and 1 or -1 elseif waveType == "triangle" then if phase < 0.5 then return phase * 4 - 1 else return (1 - phase) * 4 - 1 end elseif waveType == "noise" then return math.random() * 2 - 1 end return 0 end -- Envelope: attack, sustain level, release (all as fractions of total duration) local function getEnvelopeValue(t, attack, sustainLevel, release) if t < attack then return t / attack elseif t < 1 - release then return sustainLevel else local rt = (t - (1 - release)) / release return sustainLevel * (1 - rt) end end -- Generate PCM data from a sound config local function generatePCM(config) local duration = config.duration or 0.1 local numSamples = math.ceil(Sound.SAMPLE_RATE * duration) local freqStart = config.frequency or 440 local freqEnd = config.frequencyEnd or freqStart local waveType = config.wave or "square" local amp = config.amp or 0.3 local env = config.env or { 0.005, 1, 0.15 } local data = love.sound.newSoundData(numSamples) local phase = 0 for i = 0, numSamples - 1 do local t = i / numSamples local freq = freqStart + (freqEnd - freqStart) * t local gain = getEnvelopeValue(t, env[1], env[2], env[3]) local sample = sampleWave(waveType, phase) data:setSample(i, sample * gain * amp) phase = (phase + freq / Sound.SAMPLE_RATE) % 1 end return data end -- Play a sound by name. Creates on first call, caches, then clones for polyphony. function Sound.play(name) local s = Sound.sounds[name] if not s then return end local source = love.audio.newSource(s.data) source:setVolume(Sound.volume) love.audio.play(source) end -- Initialize all sounds from the SOUNDS table function Sound.init(soundsConfig) for name, config in pairs(soundsConfig) do local data = generatePCM(config) Sound.sounds[name] = { data = data } end end -- Default sound definitions SOUNDS = { jump = { frequency = 220, frequencyEnd = 880, duration = 0.12, wave = "square", amp = 0.15, env = { 0.005, 1, 0.3 }, }, land = { frequency = 150, frequencyEnd = 60, duration = 0.15, wave = "noise", amp = 0.2, env = { 1.005, 1, 0.6 }, }, charge = { frequency = 300, frequencyEnd = 600, duration = 1.5, wave = "triangle", amp = 0.15, env = { 0.01, 1, 0.05 }, }, potion = { frequency = 200, frequencyEnd = 600, duration = 0.7, wave = "square", amp = 0.3, env = { 0.005, 1, 0.5 }, }, casting = { frequency = 100, frequencyEnd = 900, duration = 3.0, wave = "square", amp = 0.5, env = { 0.015, 0.25, 0.5 }, }, damage = { frequency = 200, frequencyEnd = 600, duration = 0.12, wave = "noise", amp = 0.3, env = { 0.005, 1, 0.5 }, }, sword = { frequency = 200, frequencyEnd = 600, duration = 0.12, wave = "noise", amp = 0.3, env = { 0.005, 1, 0.5 }, }, whirlwind = { frequency = 100, frequencyEnd = 500, duration = 2, wave = "noise", amp = 0.25, env = { 0.01, 1, 0.4 }, }, shoot = { frequency = 400, frequencyEnd = 200, duration = 0.1, wave = "square", amp = 0.2, env = { 0.005, 1, 0.8 }, }, shield_bash = { frequency = 100, frequencyEnd = 300, duration = 0.5, wave = "noise", amp = 0.3, env = { 0.005, 3, 1.5 }, }, } return Sound