package main

import "core:math"
import "core:math/rand"
import rl "vendor:raylib"

SAMPLE_RATE :: 44100
CYLINDERS :: 4
GAIN :: 0.3

SUB_FRAMES :: 1024

EngineSound :: struct {
	stream:     rl.AudioStream,
	phase:      f32,
	exhaust_lp: f32,
	body_lp:    f32,
	buf:        [SUB_FRAMES]i16,
}

engine_sound_init :: proc() -> EngineSound {
	es: EngineSound

	rl.SetAudioStreamBufferSizeDefault(SUB_FRAMES)

	es.stream = rl.LoadAudioStream(SAMPLE_RATE, 16, 1)
	rl.PlayAudioStream(es.stream)
	return es
}

engine_sound_update :: proc(es: ^EngineSound, rpm: f32) {
	if !rl.IsAudioStreamProcessed(es.stream) do return
	f := rpm / 60.0 * (f32(CYLINDERS) / 2.0)
	phase_step := math.TAU * f / f32(SAMPLE_RATE)

	for i in 0 ..< SUB_FRAMES {
		// Exhaust
		harm1 := math.sin_f32(es.phase) // cyllinder main
		harm2 := math.sin_f32(es.phase * 2.0) * 0.5 // exhaust
		harm_sub := math.sin_f32(es.phase * 0.5) * 0.3 // cyllinder pop

		exhaust_raw := harm1 + harm2 + harm_sub
		exhaust_clipped := math.clamp(exhaust_raw * 1.4, -1.0, 1.0)

		alpha_exhaust: f32 = 0.35
		es.exhaust_lp += alpha_exhaust * (exhaust_clipped - es.exhaust_lp)
		exhaust_sound := es.exhaust_lp * 0.6

		// Body

		mechanical_noise := (rand.float32() * 2.0 - 1.0) * 0.1
		body_raw := harm1 + mechanical_noise

		alpha_body: f32 = 0.08
		es.body_lp += alpha_body * (body_raw - es.body_lp)
		body_sound := es.body_lp * 0.4

		// Final
		final_sample := (exhaust_sound + body_sound) * GAIN
		final_clamped := math.clamp(final_sample, -1.0, 1.0)

		es.buf[i] = i16(final_clamped * 32767.0)

		es.phase += phase_step

		if es.phase >= math.TAU {
			es.phase -= math.TAU
		}
	}

	rl.UpdateAudioStream(es.stream, &es.buf[0], i32(SUB_FRAMES))
}

engine_sound_unload :: proc(es: ^EngineSound) {
	rl.UnloadAudioStream(es.stream)
}
