package audio import "core:math" import "core:math/rand" import raylib "vendor:raylib" // All sounds are synthesized at startup, so no audio files are needed SAMPLE_RATE :: 44100 Sound_Effect :: enum { FLIP, MATCH, MISMATCH, WIN, CLICK, } sounds: [Sound_Effect]raylib.Sound ready: bool @(private) Tone :: struct { start: f32, // seconds freq: f32, // Hz duration: f32, // seconds amp: f32, } // Sine with a little 2nd/3rd harmonic, soft attack and exponential decay: a small bell @(private) add_tone :: proc(samples: []f32, tone: Tone) { first := int(tone.start * SAMPLE_RATE) count := int(tone.duration * SAMPLE_RATE) attack: f32 = 0.006 for i in 0 ..< count { idx := first + i if idx >= len(samples) { break } t := f32(i) / SAMPLE_RATE env := min(t / attack, 1) * math.exp(-5 * t / tone.duration) phase := 2 * math.PI * tone.freq * t s := math.sin(phase) + 0.25 * math.sin(2 * phase) + 0.08 * math.sin(3 * phase) samples[idx] += tone.amp * env * s } } // Card "snap": low-passed noise with a fast decay over a short low thump @(private) add_flip :: proc(samples: []f32) { lp: f32 = 0 for i in 0 ..< len(samples) { t := f32(i) / SAMPLE_RATE noise := rand.float32_range(-1, 1) lp += 0.35 * (noise - lp) swish := lp * math.exp(-t * 55) * min(t / 0.004, 1) thump := math.sin(2 * math.PI * 150 * t) * math.exp(-t * 70) samples[i] += 0.55 * swish + 0.35 * thump } } @(private) make_sound :: proc(samples: []f32) -> raylib.Sound { pcm := make([]i16, len(samples)) defer delete(pcm) for s, i in samples { pcm[i] = i16(math.clamp(s, -1, 1) * 32000) } wave := raylib.Wave{ frameCount = u32(len(pcm)), sampleRate = SAMPLE_RATE, sampleSize = 16, channels = 1, data = raw_data(pcm), } // LoadSoundFromWave copies the samples into its own buffer return raylib.LoadSoundFromWave(wave) } @(private) build :: proc(duration: f32, tones: []Tone, flip := false) -> raylib.Sound { samples := make([]f32, int(duration * SAMPLE_RATE)) defer delete(samples) if flip { add_flip(samples) } for tone in tones { add_tone(samples, tone) } return make_sound(samples) } // Call after raylib.InitAudioDevice load :: proc() { if !raylib.IsAudioDeviceReady() { return } E5 :: 659.25 A5 :: 880.00 B5 :: 987.77 C5 :: 523.25 G5 :: 783.99 C6 :: 1046.50 E6 :: 1318.51 sounds[.FLIP] = build(0.12, {}, flip = true) sounds[.MATCH] = build(0.6, { {start = 0.00, freq = E5, duration = 0.35, amp = 0.30}, {start = 0.09, freq = B5, duration = 0.50, amp = 0.28}, }) sounds[.MISMATCH] = build(0.45, { {start = 0.00, freq = 330, duration = 0.20, amp = 0.20}, {start = 0.11, freq = 262, duration = 0.30, amp = 0.20}, }) sounds[.WIN] = build(1.3, { {start = 0.00, freq = C5, duration = 0.40, amp = 0.18}, {start = 0.11, freq = E5, duration = 0.40, amp = 0.18}, {start = 0.22, freq = G5, duration = 0.40, amp = 0.18}, {start = 0.33, freq = C6, duration = 0.90, amp = 0.2}, {start = 0.33, freq = E6, duration = 0.90, amp = 0.07}, }) sounds[.CLICK] = build(0.08, {{start = 0, freq = A5, duration = 0.06, amp = 0.15}}) raylib.SetSoundVolume(sounds[.FLIP], 0.7) ready = true } unload :: proc() { if !ready { return } for s in sounds { raylib.UnloadSound(s) } ready = false } play :: proc(effect: Sound_Effect) { if !ready { return } if effect == .FLIP { // Slight pitch variation so repeated flips don't sound identical raylib.SetSoundPitch(sounds[.FLIP], rand.float32_range(0.9, 1.12)) } raylib.PlaySound(sounds[effect]) }