Files
factorygame/util/audio.c
2025-06-01 22:13:02 +02:00

131 lines
4.7 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
// Created by bruno on 16.2.2025.
*/
#include "audio.h"
AudioData audioData;
uint16_t getAvailableChannel() {
for (uint16_t i = 0; i < NUM_SYNTH_VOICES; i++) {
if (audioData.synthVoices[i].volume == 0) {
return i;
}
}
return -1;
}
// Helper: compute left/right gains from a pan value in [1..+1]
// pan = 1.0 → full left (L=1, R=0)
// pan = +1.0 → full right (L=0, R=1)
// pan = 0.0 → center (L=R=1/sqrt(2) or just 0.707 to avoid clipping)
static void compute_stereo_gains(float pan, float *outL, float *outR) {
// Simple linear panning (no constantpower law).
// If you prefer constantpower, you could do:
// float angle = (pan + 1.0f) * (M_PI / 4.0f);
// *outL = cosf(angle);
// *outR = sinf(angle);
//
// Here well just do linear:
pan = fmaxf(-1.0f, fminf(+1.0f, pan));
if (pan <= 0.0f) {
*outL = 1.0f;
*outR = 1.0f + pan; // pan is negative, so R < 1
} else {
*outL = 1.0f - pan; // pan is positive, so L < 1
*outR = 1.0f;
}
// Optionally, scale down both so we never exceed 1.0f / sqrt(2)
// e.g. *outL *= 0.7071f; *outR *= 0.7071f;
}
// This callback now writes stereo frames: interleaved L/R floats.
void audio_callback(void *userdata, Uint8 *stream, int len) {
AudioData *audio = (AudioData *) userdata;
// 'len' is total bytes; each sampleframe is 2 floats (L+R), i.e. 2 * sizeof(float).
int frames = len / (2 * sizeof(float));
// Zero out the entire output buffer (silence)
// Well accumulate into it.
// Each float is 4 bytes, so total floats = 2 * frames.
float *outBuf = (float *) stream;
for (int i = 0; i < 2 * frames; ++i) {
outBuf[i] = 0.0f;
}
// Precompute the listener center
float listenerCx = audio->playerRect->x + audio->playerRect->w * 0.5f;
// For each synth voice, mix into the stereo buffer
for (int v = 0; v < NUM_SYNTH_VOICES; v++) {
SynthVoice *voice = &audio->synthVoices[v];
if (voice->volume == 0 || voice->frequency == 0) {
continue; // skip silent or inactive voices
}
// Compute source center X
float sourceCx = voice->sourceRect.x + voice->sourceRect.w * 0.5f;
float dx = sourceCx - listenerCx;
// Normalize for pan. If |dx| >= maxPanDistance → full left or full right.
float pan = dx / audio->maxPanDistance;
if (pan < -1.0f) pan = -1.0f;
if (pan > +1.0f) pan = +1.0f;
float gainL, gainR;
compute_stereo_gains(pan, &gainL, &gainR);
// Optional: You could also attenuate overall volume with distance
// float dist = fabsf(dx);
// float distanceAtten = 1.0f - fminf(dist / audio->maxPanDistance, 1.0f);
// float finalVolume = (voice->volume / 255.0f) * distanceAtten;
// But for now, well just use voice->volume for amplitude.
float amp = (voice->volume / 255.0f);
// Phase increment per sampleframe:
// (freq * 256) / SAMPLE_RATE tells how many phase steps per mono-sample.
// Because were writing stereo, we still advance phase once per frame.
uint8_t phaseInc = (uint8_t)((voice->frequency * 256) / SAMPLE_RATE);
// Mix into each frame
for (int i = 0; i < frames; i++) {
float t = (float) voice->phase / 255.0f * 2.0f - 1.0f;
float sample;
switch (voice->waveform) {
default:
case WAVE_SINE:
sample = sinf(voice->phase * 2.0f * M_PI / 256.0f);
break;
case WAVE_SQUARE:
sample = (t >= 0.0f) ? 1.0f : -1.0f;
break;
case WAVE_SAWTOOTH:
sample = t;
break;
case WAVE_TRIANGLE:
sample = (t < 0.0f) ? -t : t;
break;
case WAVE_NOISE:
sample = ((float) rand() / RAND_MAX) * 2.0f - 1.0f;
break;
}
voice->phase += phaseInc;
// Interleaved index: left = 2*i, right = 2*i + 1
int idxL = 2 * i;
int idxR = 2 * i + 1;
// Accumulate into buffer
outBuf[idxL] += sample * amp * gainL;
outBuf[idxR] += sample * amp * gainR;
}
}
// Note: We did not normalize by active voices here, because each voice already
// uses its own volume. If you still want an automatic “divide by N active voices”,
// you would need to track active voices perframe, which is relatively expensive.
// In practice, you manage the volume per voice so clipping doesnt occur.
}