oma-lander/audio/sounds.lua
28allday 790ca87bfb Lunar Lander: complete Love2D game with Omarchy integration
Faithful recreation of Atari Lunar Lander (1979) with vector wireframe aesthetic.
Auto-detects Omarchy system theme and font on launch.

Features:
- Dynamic zoom camera (zooms in as you approach the surface)
- Procedural jagged terrain with flat landing pads (2X, 3X, 5X multipliers)
- Apollo-style wireframe lander with thrust flame
- Gravity, thrust, rotation physics
- Landing evaluation: good (speed/angle/pad check), hard, crash
- Fuel management (750 starting, +50 for good landings)
- Crash debris particles, thrust exhaust particles
- Star field background
- HUD: altitude, horizontal/vertical speed, fuel bar, score, time
- Persistent high scores with 3-letter initial entry
- Procedural sound effects (thrust, landing chimes, crash, fuel warning)
- Full-screen scaling, system font detection

Controls: Arrows/WASD rotate+thrust, Space abort, Enter start

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:42:13 +01:00

84 lines
2.2 KiB
Lua

local Sounds = {}
local sources = {}
local SAMPLE_RATE = 44100
local function makeSoundData(duration, generator)
local samples = math.floor(SAMPLE_RATE * duration)
local sd = love.sound.newSoundData(samples, SAMPLE_RATE, 16, 1)
for i = 0, samples - 1 do
local t = i / SAMPLE_RATE
local p = i / samples
sd:setSample(i, math.max(-1, math.min(1, generator(t, p))))
end
return sd
end
local function makeSource(sd)
return love.audio.newSource(sd, "static")
end
local function genThrust(t, p)
local noise = (math.random() * 2 - 1) * 0.25
local low = math.sin(2 * math.pi * 45 * t) * 0.2
local rumble = math.sin(2 * math.pi * 25 * t) * 0.15
return (noise + low + rumble) * 0.4
end
local function genLandGood(t, p)
local freq
if p < 0.33 then freq = 500
elseif p < 0.66 then freq = 700
else freq = 900 end
local env = 0.7
if p > 0.9 then env = (1-p)/0.1 * 0.7 end
return math.sin(2 * math.pi * freq * t) * env * 0.35
end
local function genLandHard(t, p)
local env = (1 - p) ^ 2
return math.sin(2 * math.pi * 250 * t) * env * 0.3
end
local function genCrash(t, p)
local env = (1 - p) ^ 1.2
local sine = math.sin(2 * math.pi * (60 + 30*(1-p)) * t) * 0.5
local noise = (math.random() * 2 - 1) * 0.5
return (sine + noise) * env * 0.5
end
local function genFuelWarn(t, p)
local freq = 800
local env = 0.6
if p > 0.5 then env = 0 end
return math.sin(2 * math.pi * freq * t) * env * 0.3
end
local function genAbort(t, p)
local freq = 600 - p * 300
local env = (1 - p) ^ 1.5
local noise = (math.random() * 2 - 1) * 0.3
return (math.sin(2 * math.pi * freq * t) * 0.4 + noise) * env * 0.4
end
function Sounds.init()
sources = {}
local defs = {
thrust = {0.25, genThrust},
land_good = {0.4, genLandGood},
land_hard = {0.3, genLandHard},
crash = {0.6, genCrash},
fuel_warn = {0.15, genFuelWarn},
abort = {0.3, genAbort},
}
for name, def in pairs(defs) do
sources[name] = makeSource(makeSoundData(def[1], def[2]))
end
end
function Sounds.play(name)
local src = sources[name]
if not src then return end
src:clone():play()
end
return Sounds