Files
factorygame/util/audio.c
2025-06-08 17:22:30 +02:00

562 lines
20 KiB
C
Raw Permalink 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;
MidiEvent midiEvents[MIDI_TRACK_MAX][MAX_MIDI_EVENTS];
int midiEventCount[MIDI_TRACK_MAX];
int nextMidiEvent[MIDI_TRACK_MAX];
uint16_t getAvailableChannel() {
for (uint16_t i = 0; i < NUM_SYNTH_VOICES - MIDI_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;
}
// Improved audio ; with anti-clipping and smooth fade-out
void audio_callback(void *userdata, Uint8 *stream, int len) {
AudioData *audio = (AudioData *) userdata;
int frames = len / (2 * sizeof(float)); // Stereo frame count
float elapsedSec = ((float) audio->totalSamples) / SAMPLE_RATE;
audio->totalSamples += frames;
for (uint8_t midiChannel = 0; midiChannel < MIDI_TRACK_MAX; midiChannel++) {
while (nextMidiEvent[midiChannel] < midiEventCount[midiChannel] &&
midiEvents[midiChannel][nextMidiEvent[midiChannel]].timeSec <= elapsedSec) {
MidiEvent *ev = &midiEvents[midiChannel][nextMidiEvent[midiChannel]];
// printf("Event at %f, %s, note: %d, velocity: %d\n", ev->timeSec, ev->type == MIDI_NOTE_ON ? "ON" : "OFF",
// ev->note, ev->velocity);
uint16_t freq = (uint16_t)(440.0f * powf(2.0f, (ev->note - 69) / 12.0f));
uint8_t midiVoiceIndex = NUM_SYNTH_VOICES - midiChannel - 1;
SynthVoice *v = &audio->synthVoices[midiVoiceIndex];
if (ev->type == MIDI_NOTE_ON && ev->velocity > 0) {
// Note On
v->frequency = freq;
v->volume = ev->velocity * 2;
v->smoothedAmp = 0;
// printf("Playing voice %d at freq %d hz, volume %d\n", midiVoiceIndex, v->frequency,
// v->volume);
} else if (ev->type == MIDI_NOTE_OFF || ev->velocity == 0) {
// Note Off
v->volume = 0;
// printf("Stopping voice %d at freq %d hz, volume %d\n", midiVoiceIndex, v->frequency, v->volume);
} else if ((ev->type & 0xF0) == MIDI_PROGRAM_CHANGE) {
if (ev->note == 0) {
v->waveform = resolvePatch(ev->note);
}
}
nextMidiEvent[midiChannel]++;
}
}
float *outBuf = (float *) stream;
for (int i = 0; i < 2 * frames; ++i) {
outBuf[i] = 0.0f;
}
float listenerCx = audio->playerRect->x + audio->playerRect->w * 0.5f;
int *voiceCounts = calloc(frames, sizeof(int));
for (int v = 0; v < NUM_SYNTH_VOICES; v++) {
SynthVoice *voice = &audio->synthVoices[v];
if ((voice->volume == 0 && voice->smoothedAmp < 0.001f) || voice->frequency == 0)
continue;
float sourceCx = voice->sourceRect.x + TILE_SIZE * 0.5f;
float dx = sourceCx - listenerCx;
float pan = fmaxf(-1.0f, fminf(+1.0f, dx / audio->maxPanDistance));
float gainL = 1;
float gainR = 1;
float targetAmp = (voice->volume / 255.0f) / 2;
if (v < NUM_SYNTH_VOICES - MIDI_VOICES) {
float distanceAtten = 1.0f - fminf(fabsf(dx) / audio->maxPanDistance, 1.0f);
targetAmp *= distanceAtten;
compute_stereo_gains(pan, &gainL, &gainR);
gainL *= 0.7071f;
gainR *= 0.7071f;
}
double phaseInc = ((double) voice->frequency * 256.0) / (double) SAMPLE_RATE;
for (int i = 0; i < frames; i++) {
voice->smoothedAmp += (targetAmp - voice->smoothedAmp) * SMOOTHING_FACTOR;
float amp = voice->smoothedAmp;
double norm = voice->phase / 256.0;
double t = norm * 2.0 - 1.0;
float sample;
switch (voice->waveform) {
case WAVE_SINE:
sample = (float) sin(norm * 2.0 * M_PI);
break;
case WAVE_SQUARE:
sample = (t >= 0.0) ? 1.0f : -1.0f;
break;
case WAVE_SAWTOOTH:
sample = (float) t;
break;
case WAVE_TRIANGLE:
sample = (float) (1.0 - 4.0 * fabs(t - floor(t + 0.5)));
break;
case WAVE_NOISE:
sample = ((float) rand() / (float) RAND_MAX) * 2.0f - 1.0f;
break;
case WAVE_HALF_SINE: // one cycle of sine clipped to [0, 1]
sample = (float) (0.5 * sin(norm * 2.0 * M_PI) + 0.5);
break;
case WAVE_PULSE25:
sample = (norm < 0.25) ? 1.0f : -1.0f;
break;
case WAVE_PULSE10:
sample = (norm < 0.10) ? 1.0f : -1.0f;
break;
case WAVE_CLIPPED_SINE:
sample = fmaxf(-0.7f, fminf(0.7f, (float) sin(norm * 2.0 * M_PI)));
break;
case WAVE_EXP:
sample = copysignf(powf(fabsf(t), 0.5f), t); // softer exponential curve
break;
case WAVE_RAMP:
sample = 2.0f * fmodf(norm, 1.0f) - 1.0f; // ascending ramp (like saw)
break;
case WAVE_REVERSE_SAW:
sample = 1.0f - 2.0f * fmodf(norm, 1.0f); // descending ramp
break;
case WAVE_STAIRCASE:
sample = floorf(norm * 8.0f) / 4.0f - 1.0f; // stepped waveform
break;
default:
sample = (float) sin(norm * 2.0 * M_PI); // fallback to sine
break;
}
voice->phase += phaseInc;
if (voice->phase >= 256.0) voice->phase -= 256.0;
else if (voice->phase < 0.0) voice->phase += 256.0;
int idxL = 2 * i;
int idxR = 2 * i + 1;
outBuf[idxL] += sample * amp * gainL;
outBuf[idxR] += sample * amp * gainR;
voiceCounts[i]++;
}
}
for (int i = 0; i < frames; ++i) {
int count = voiceCounts[i];
if (count > 0) {
outBuf[2 * i + 0] /= count;
outBuf[2 * i + 1] /= count;
}
}
free(voiceCounts);
}
static uint32_t read_be_uint32(const uint8_t *data) {
return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
}
static uint16_t read_be_uint16(const uint8_t *data) {
return (data[0] << 8) | data[1];
}
static uint32_t read_vlq(const uint8_t **ptr) {
uint32_t value = 0;
const uint8_t *p = *ptr;
while (*p & 0x80) {
value = (value << 7) | (*p++ & 0x7F);
}
value = (value << 7) | (*p++ & 0x7F);
*ptr = p;
return value;
}
void load_midi_file(const char *path) {
FILE *f = fopen(path, "rb");
if (!f) return;
fseek(f, 0, SEEK_END);
long size = ftell(f);
rewind(f);
uint8_t *data = malloc(size);
fread(data, 1, size, f);
fclose(f);
const uint8_t *ptr = data;
if (memcmp(ptr, "MThd", 4) != 0) return;
ptr += 8; // skip header length
uint16_t format = read_be_uint16(ptr);
ptr += 2;
uint16_t nTracks = read_be_uint16(ptr);
ptr += 2;
uint16_t ppqn = read_be_uint16(ptr);
ptr += 2;
if (format > 1) {
printf("ERROR: Only MIDI format 0 or 1 supported (found %d)\n", format);
free(data);
return;
}
if (nTracks == 0 || nTracks > MIDI_TRACK_MAX) {
printf("ERROR: Number of tracks %d out of range\n", nTracks);
free(data);
return;
}
for (int trackIndex = 0; trackIndex < nTracks; trackIndex++) {
if (memcmp(ptr, "MTrk", 4) != 0) return;
uint32_t trackLen = read_be_uint32(ptr + 4);
ptr += 8;
const uint8_t *trackEnd = ptr + trackLen;
float curTime = 0.0f;
uint32_t tempo = 500000; // default: 120 BPM
uint8_t lastStatus = 0;
while (ptr < trackEnd && midiEventCount[trackIndex] < MAX_MIDI_EVENTS) {
uint32_t delta = read_vlq(&ptr);
curTime += (delta * (tempo / 1000000.0f)) / ppqn;
uint8_t status = *ptr;
if (status < 0x80) status = lastStatus;
else ptr++;
lastStatus = status;
if (status == 0xFF) {
uint8_t metaType = *ptr++;
uint32_t len = read_vlq(&ptr);
if (metaType == 0x03) {
// This is a track name — skip it
ptr += len;
continue;
}
if (metaType == 0x51 && len == 3) {
tempo = (ptr[0] << 16 | ptr[1] << 8 | ptr[2]);
}
ptr += len;
} else if ((status & 0xF0) == 0x90 || (status & 0xF0) == 0x80) {
uint8_t note = *ptr++;
uint8_t vel = *ptr++;
midiEvents[trackIndex][midiEventCount[trackIndex]++] = (MidiEvent) {
.timeSec = curTime,
.type = status,
.note = note,
.velocity = vel
};
} else {
ptr += 2; // skip unknown
}
}
}
free(data);
}
Waveform resolvePatch(uint8_t patchNum) {
switch (patchNum) {
case 0:
return WAVE_CLIPPED_SINE; // Acoustic Grand Piano (harmonic, percussive)
case 1:
return WAVE_CLIPPED_SINE; // Bright Acoustic Piano
case 2:
return WAVE_CLIPPED_SINE; // Electric Grand Piano
case 3:
return WAVE_CLIPPED_SINE; // Honky-Tonk Piano
case 4:
return WAVE_SINE; // Electric Piano 1 (Rhodes) - smooth bell-like
case 5:
return WAVE_SINE; // Electric Piano 2 (Wurlitzer)
case 6:
return WAVE_CLIPPED_SINE; // Harpsichord (plucked, bright)
case 7:
return WAVE_SQUARE; // Clavinet (sharp, funky)
case 8:
return WAVE_SINE; // Celesta (bell-like, soft)
case 9:
return WAVE_SINE; // Glockenspiel (bright bell)
case 10:
return WAVE_SINE; // Music Box (delicate bell)
case 11:
return WAVE_SINE; // Vibraphone (vibrato sine-like)
case 12:
return WAVE_SINE; // Marimba (warm sine)
case 13:
return WAVE_SINE; // Xylophone (sharp sine)
case 14:
return WAVE_SINE; // Tubular Bells (bell)
case 15:
return WAVE_SINE; // Dulcimer (plucked sine)
case 16:
return WAVE_SAWTOOTH; // Drawbar Organ (rich harmonic)
case 17:
return WAVE_SAWTOOTH; // Percussive Organ
case 18:
return WAVE_SAWTOOTH; // Rock Organ
case 19:
return WAVE_SAWTOOTH; // Church Organ
case 20:
return WAVE_SAWTOOTH; // Reed Organ
case 21:
return WAVE_SQUARE; // Accordion (reedy square)
case 22:
return WAVE_SQUARE; // Harmonica (square-ish)
case 23:
return WAVE_SQUARE; // Tango Accordion
case 24:
return WAVE_PULSE25; // Nylon Guitar (plucked, soft pulse)
case 25:
return WAVE_PULSE25; // Steel Guitar
case 26:
return WAVE_PULSE25; // Jazz Guitar
case 27:
return WAVE_PULSE25; // Clean Electric Guitar
case 28:
return WAVE_PULSE25; // Muted Electric Guitar
case 29:
return WAVE_PULSE10; // Overdriven Guitar (more distorted)
case 30:
return WAVE_PULSE10; // Distortion Guitar
case 31:
return WAVE_PULSE25; // Guitar Harmonics (plucked)
case 32:
return WAVE_SINE; // Acoustic Bass
case 33:
return WAVE_SINE; // Electric Bass (finger)
case 34:
return WAVE_SINE; // Electric Bass (pick)
case 35:
return WAVE_SINE; // Fretless Bass
case 36:
return WAVE_TRIANGLE; // Slap Bass 1 (percussive triangle)
case 37:
return WAVE_TRIANGLE; // Slap Bass 2
case 38:
return WAVE_SAWTOOTH; // Synth Bass 1
case 39:
return WAVE_SAWTOOTH; // Synth Bass 2
case 40:
return WAVE_SINE; // Violin
case 41:
return WAVE_SINE; // Viola
case 42:
return WAVE_SINE; // Cello
case 43:
return WAVE_SINE; // Contrabass
case 44:
return WAVE_SAWTOOTH; // Tremolo Strings (rich)
case 45:
return WAVE_SINE; // Pizzicato Strings (plucked)
case 46:
return WAVE_SINE; // Orchestral Harp (plucked sine)
case 47:
return WAVE_CLIPPED_SINE; // Timpani (percussive sine)
case 48:
return WAVE_SAWTOOTH; // String Ensemble 1 (Slow Strings)
case 49:
return WAVE_SAWTOOTH; // String Ensemble 2 (Fast Strings)
case 50:
return WAVE_SAWTOOTH; // SynthStrings 1
case 51:
return WAVE_SAWTOOTH; // SynthStrings 2
case 52:
return WAVE_SINE; // Choir Aahs
case 53:
return WAVE_SINE; // Voice Oohs
case 54:
return WAVE_SQUARE; // Synth Voice
case 55:
return WAVE_SAWTOOTH; // Orchestra Hit (harsh)
case 56:
return WAVE_SQUARE; // Trumpet
case 57:
return WAVE_SQUARE; // Trombone
case 58:
return WAVE_SQUARE; // Tuba
case 59:
return WAVE_PULSE25; // Muted Trumpet
case 60:
return WAVE_SQUARE; // French Horn
case 61:
return WAVE_SAWTOOTH; // Brass Section
case 62:
return WAVE_SAWTOOTH; // SynthBrass 1
case 63:
return WAVE_SAWTOOTH; // SynthBrass 2
case 64:
return WAVE_SQUARE; // Soprano Sax
case 65:
return WAVE_SQUARE; // Alto Sax
case 66:
return WAVE_SQUARE; // Tenor Sax
case 67:
return WAVE_SQUARE; // Baritone Sax
case 68:
return WAVE_SINE; // Oboe
case 69:
return WAVE_SINE; // English Horn
case 70:
return WAVE_SINE; // Bassoon
case 71:
return WAVE_SINE; // Clarinet
case 72:
return WAVE_SINE; // Piccolo
case 73:
return WAVE_SINE; // Flute
case 74:
return WAVE_SINE; // Recorder
case 75:
return WAVE_SINE; // Pan Flute
case 76:
return WAVE_SINE; // Blown Bottle
case 77:
return WAVE_SINE; // Shakuhachi
case 78:
return WAVE_SINE; // Whistle
case 79:
return WAVE_SINE; // Ocarina
case 80:
return WAVE_SQUARE; // Lead 1 (Square)
case 81:
return WAVE_SAWTOOTH; // Lead 2 (Sawtooth)
case 82:
return WAVE_SAWTOOTH; // Lead 3 (Calliope)
case 83:
return WAVE_SINE; // Lead 4 (Chiff)
case 84:
return WAVE_SQUARE; // Lead 5 (Charang)
case 85:
return WAVE_SQUARE; // Lead 6 (Voice)
case 86:
return WAVE_SAWTOOTH; // Lead 7 (Fifths)
case 87:
return WAVE_SQUARE; // Lead 8 (Bass + Lead)
case 88:
return WAVE_SAWTOOTH; // Pad 1 (New Age)
case 89:
return WAVE_SAWTOOTH; // Pad 2 (Warm)
case 90:
return WAVE_SAWTOOTH; // Pad 3 (Polysynth)
case 91:
return WAVE_SINE; // Pad 4 (Choir)
case 92:
return WAVE_SINE; // Pad 5 (Bowed)
case 93:
return WAVE_SAWTOOTH; // Pad 6 (Metallic)
case 94:
return WAVE_SINE; // Pad 7 (Halo)
case 95:
return WAVE_SINE; // Pad 8 (Sweep)
case 96:
return WAVE_NOISE; // FX 1 (Rain)
case 97:
return WAVE_NOISE; // FX 2 (Soundtrack)
case 98:
return WAVE_NOISE; // FX 3 (Crystal)
case 99:
return WAVE_NOISE; // FX 4 (Atmosphere)
case 100:
return WAVE_NOISE; // FX 5 (Brightness)
case 101:
return WAVE_NOISE; // FX 6 (Goblins)
case 102:
return WAVE_NOISE; // FX 7 (Echoes)
case 103:
return WAVE_NOISE; // FX 8 (Sci-Fi)
case 104:
return WAVE_SAWTOOTH; // Sitar
case 105:
return WAVE_SQUARE; // Banjo
case 106:
return WAVE_PULSE25; // Shamisen
case 107:
return WAVE_SINE; // Koto
case 108:
return WAVE_SINE; // Kalimba
case 109:
return WAVE_SINE; // Bagpipe
case 110:
return WAVE_SINE; // Fiddle
case 111:
return WAVE_SINE; // Shanai
case 112:
return WAVE_SAWTOOTH; // Tinkle Bell
case 113:
return WAVE_NOISE; // Agogo
case 114:
return WAVE_NOISE; // Steel Drums
case 115:
return WAVE_NOISE; // Woodblock
case 116:
return WAVE_NOISE; // Taiko Drum
case 117:
return WAVE_NOISE; // Melodic Tom
case 118:
return WAVE_NOISE; // Synth Drum
case 119:
return WAVE_NOISE; // Reverse Cymbal
case 120:
return WAVE_NOISE; // Guitar Fret Noise
case 121:
return WAVE_NOISE; // Breath Noise
case 122:
return WAVE_NOISE; // Seashore
case 123:
return WAVE_NOISE; // Bird Tweet
case 124:
return WAVE_NOISE; // Telephone Ring
case 125:
return WAVE_NOISE; // Helicopter
case 126:
return WAVE_NOISE; // Applause
case 127:
return WAVE_NOISE; // Gunshot
default:
return WAVE_SINE; // Default fallback
}
}