#include "core/audio.h" #include #include #include #define RATE 44100 #define VOICES_MAX 96 #define CHUNK 512 /* frames mixed at a time */ #define MASTER 0.55f #define BED_EASE 3.0f /* 1/s: beds turn up and down this quickly */ #define TWO_PI 6.28318531f typedef struct { /* a one-pole lowpass (cascaded twice: steeper) */ float a, y1, y2; } Low; typedef struct { Voice v; float t; /* s since it started (negative: still waiting) */ float phase; float click; /* crackle: the click sounding now */ Low low, high; unsigned rng; bool on; } Playing; typedef struct { float gain, pan, param; /* where it's going ... */ float g, p, k; /* ... where it is */ float phase, phase2, click; Low low, low2, high; float mod, mod_v; /* a slow wander (wind gusts, a fire breathing) */ unsigned rng; double t; /* music: s it's been playing */ float kick, osc[3], hiss; /* ... the kick's phase, the other tones' phases, a noise burst's state */ Low tone, tone2; /* ... their filters */ } BedState; static SDL_AudioStream *g_stream; static SDL_Mutex *g_lock; static Playing g_voices[VOICES_MAX]; static BedState g_beds[BED_COUNT]; static bool g_muted, g_paused; static float g_fade = 1.0f; /* (muting fades, no click) */ static float coef(float hz) { return hz <= 0.0f ? 1.0f : 1.0f - expf(-TWO_PI * fminf(hz, RATE * 0.45f) / RATE); } static float lowpass(Low *f, float x) { f->y1 += f->a * (x - f->y1); f->y2 += f->a * (f->y1 - f->y2); return f->y2; } static float onePole(Low *f, float x) { f->y1 += f->a * (x - f->y1); return f->y1; } static float noise(unsigned *r) { *r ^= *r << 13; *r ^= *r >> 17; *r ^= *r << 5; return (float)(*r & 0xFFFFFFu) / 8388608.0f - 1.0f; } static float chance(unsigned *r) { return 0.5f * (noise(r) + 1.0f); } static void panGains(float pan, float *l, float *r) { float a = (fmaxf(-1.0f, fminf(1.0f, pan)) + 1.0f) * 0.25f * 3.14159265f; *l = cosf(a); *r = sinf(a); } /* ---------- Voices ---------- */ static float voiceSample(Playing *p) { const Voice *v = &p->v; float t = p->t, x = 0.0f; float f = v->freq; if (v->freq_end > 0.0f && v->freq > 0.0f) { float span = v->length > 0.0f ? v->length : v->attack + 3.0f * v->decay; f = v->freq * powf(v->freq_end / v->freq, fminf(t / fmaxf(span, 1e-3f), 1.0f)); } switch (v->wave) { case WAVE_NOISE: x = noise(&p->rng); break; case WAVE_SINE: p->phase += f / RATE; x = sinf(TWO_PI * p->phase); break; case WAVE_SAW: p->phase += f / RATE; x = 2.0f * (p->phase - floorf(p->phase)) - 1.0f; break; case WAVE_CRACKLE: if (chance(&p->rng) < f / RATE) p->click = noise(&p->rng) > 0.0f ? 1.0f : -1.0f; x = p->click + 0.15f * noise(&p->rng); p->click *= 0.82f; break; } if (p->phase > 1.0f) p->phase -= floorf(p->phase); if (v->low > 0.0f) x = lowpass(&p->low, x); if (v->high > 0.0f) x -= onePole(&p->high, x); float env = t < v->attack ? t / fmaxf(v->attack, 1e-4f) : expf(-(t - v->attack) / fmaxf(v->decay, 1e-3f)); return x * env * v->gain; } static void mixVoices(float *out, int frames) { for (int i = 0; i < VOICES_MAX; i++) { Playing *p = &g_voices[i]; if (!p->on) continue; float l, r; panGains(p->v.pan, &l, &r); float end = p->v.length > 0.0f ? p->v.length : p->v.attack + 6.0f * p->v.decay; for (int k = 0; k < frames; k++) { p->t += 1.0f / RATE; if (p->t < 0.0f) continue; if (p->t > end) { p->on = false; break; } float s = voiceSample(p); out[2 * k] += s * l; out[2 * k + 1] += s * r; } } } /* ---------- Beds ---------- */ /* The floors' music: a drum machine each, its own groove (made up on the spot, like everything here). 0 the Hofbuehne: a rock band - kick, snare on two and four, a chugging power chord; 1 the Obstwiese: psytrance - four on the floor, a rolling bass on the sixteenths, open hats; 2 the Kartoffelacker: hand drums - low and high toms in a pattern, a shaker; 3 the hidden floor: techno - kick, an off-beat hat, a clap, an off-beat bass. */ static const float FLOOR_BPM[4] = { 118.0f, 144.0f, 100.0f, 130.0f }; /* (the dancers move to these: game_festival.c FLOOR_BPM) */ static float sweptKick(BedState *b, float since, float from, float to) { /* (a sine sweeping down fast: the thump) */ float f = to + (from - to) * expf(-since * 28.0f); b->kick += f / RATE; if (b->kick > 1.0f) b->kick -= 1.0f; return sinf(TWO_PI * b->kick) * expf(-since * 7.0f); } static float sawAt(float *phase, float f) { *phase += f / RATE; if (*phase > 1.0f) *phase -= 1.0f; return 2.0f * *phase - 1.0f; } static float floorSample(int style, BedState *b) { float bpm = FLOOR_BPM[style], spb = 60.0f / bpm; b->t += 1.0 / RATE; if (b->t > 3600.0) b->t -= 3600.0 - fmod(3600.0, (double)(spb * 16.0f)); /* (whole bars: no jump in the groove) */ float beats = (float)(b->t / spb); int beat = (int)beats % 16; /* (16 beats: four bars) */ float in_beat = (beats - floorf(beats)) * spb; /* s since this beat */ float sixteenth = beats * 4.0f, in_six = (sixteenth - floorf(sixteenth)) * spb * 0.25f; int six = (int)sixteenth % 4; float noise_v = noise(&b->rng), x = 0.0f; switch (style) { case 0: { /* rock */ bool kick = beat % 4 == 0 || beat % 4 == 2 || (beat % 8 == 7 && six == 2); if (kick) x += 0.9f * sweptKick(b, beat % 8 == 7 ? in_six : in_beat, 110.0f, 50.0f); if (beat % 2 == 1) { /* the snare: a crack of noise and a body */ b->tone.a = coef(2500.0f); x += 0.5f * lowpass(&b->tone, noise_v) * expf(-in_beat * 14.0f) + 0.2f * sinf(TWO_PI * 190.0f * in_beat) * expf(-in_beat * 20.0f); } static const float ROOT[4] = { 82.4f, 82.4f, 98.0f, 73.4f }; /* (E, E, G, D) */ float root = ROOT[(beat / 4) % 4]; float chug = expf(-fmodf(in_beat, spb * 0.5f) * 6.0f) * 0.8f + 0.2f; float gtr = sawAt(&b->osc[0], root) + sawAt(&b->osc[1], root * 1.5f) + 0.6f * sawAt(&b->osc[2], root * 2.0f); gtr = tanhf(gtr * 2.5f) * chug; /* (overdriven) */ b->tone2.a = coef(1800.0f); x += 0.28f * lowpass(&b->tone2, gtr); b->hiss = b->hiss * 0.5f + 0.5f * noise_v; /* (the hi-hat on the eighths) */ x += 0.06f * (noise_v - b->hiss) * expf(-fmodf(in_beat, spb * 0.5f) * 40.0f); break; } case 1: { /* psytrance */ x += 1.0f * sweptKick(b, in_beat, 140.0f, 48.0f); if (six != 0) { /* the rolling bass: three on the sixteenths after each kick */ static const float NOTE[4] = { 46.2f, 46.2f, 51.9f, 43.6f }; float f = NOTE[(beat / 4) % 4]; b->tone.a = coef(300.0f + 1400.0f * expf(-in_six * 18.0f)); x += 0.55f * lowpass(&b->tone, sawAt(&b->osc[0], f)) * expf(-in_six * 10.0f); } if (six == 2) { /* the open hat on the off-beat */ b->hiss = b->hiss * 0.3f + 0.7f * noise_v; x += 0.12f * (noise_v - b->hiss) * expf(-in_six * 18.0f); } float acid = sinf(TWO_PI * (float)fmod(b->t * 0.07, 1.0)); /* (a squelchy lead now and then, every other four bars) */ if (beat >= 8 && six % 2 == 1) { b->tone2.a = coef(600.0f + 1800.0f * (0.5f + 0.5f * acid)); x += 0.12f * lowpass(&b->tone2, sawAt(&b->osc[1], 185.0f * (six == 3 ? 1.5f : 1.0f))) * expf(-in_six * 12.0f); } break; } case 2: { /* hand drums */ static const signed char LOW[16] = { 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0 }; /* (on the sixteenths of a bar) */ static const signed char HIGH[16] = { 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1 }; int step = ((int)sixteenth) % 16; if (LOW[step]) x += 0.7f * sinf(TWO_PI * (95.0f + 40.0f * expf(-in_six * 30.0f)) * in_six) * expf(-in_six * 9.0f); if (HIGH[step]) { b->tone.a = coef(1800.0f); x += 0.4f * sinf(TWO_PI * 330.0f * in_six) * expf(-in_six * 16.0f) + 0.15f * lowpass(&b->tone, noise_v) * expf(-in_six * 40.0f); } b->hiss = b->hiss * 0.2f + 0.8f * noise_v; /* (the shaker on every sixteenth, the off ones louder) */ x += (six % 2 ? 0.07f : 0.04f) * (noise_v - b->hiss) * expf(-in_six * 25.0f); float drone = sinf(TWO_PI * (float)fmod(b->t * 110.0, 1.0)) + 0.5f * sinf(TWO_PI * (float)fmod(b->t * 165.0, 1.0)); /* (a drone under it) */ x += 0.05f * drone * (0.7f + 0.3f * sinf(TWO_PI * (float)fmod(b->t * 0.1, 1.0))); break; } default: { /* techno */ x += 1.0f * sweptKick(b, in_beat, 120.0f, 46.0f); if (six == 2) { b->hiss = b->hiss * 0.4f + 0.6f * noise_v; x += 0.1f * (noise_v - b->hiss) * expf(-in_six * 30.0f); x += 0.4f * tanhf(1.5f * sawAt(&b->osc[0], 49.0f)) * expf(-in_six * 8.0f); /* (the off-beat bass) */ } if (beat % 2 == 1) { /* the clap: noise in bursts */ b->tone.a = coef(1600.0f); float burst = in_beat < 0.03f ? (fmodf(in_beat, 0.01f) < 0.004f ? 1.0f : 0.3f) : expf(-(in_beat - 0.03f) * 12.0f); x += 0.35f * lowpass(&b->tone, noise_v) * burst; } break; } } b->low.a = coef(9000.0f - 8300.0f * b->k); /* (far off: only the low end carries) */ return lowpass(&b->low, x) * 1.2f; } static float bedSample(Bed which, BedState *b) { float x = 0.0f, k = b->k; b->mod_v += (noise(&b->rng) * 0.6f - b->mod * 0.4f - b->mod_v * 0.9f) / RATE; /* (a slow wander, -1..1) */ b->mod = fmaxf(-1.0f, fminf(1.0f, b->mod + b->mod_v * 4.0f / RATE)); switch (which) { case BED_WIND: { /* noise through a lowpass that opens with the gusts */ float cut = 180.0f + 900.0f * k + 400.0f * k * (0.5f + 0.5f * b->mod); b->low.a = coef(cut); x = lowpass(&b->low, noise(&b->rng)) * (0.8f + 0.5f * (0.5f + 0.5f * b->mod) * k) * 2.2f; break; } case BED_RAIN: { /* hiss and drops; through a tent: dull, drumming */ b->low.a = coef(7000.0f - 5500.0f * k); b->high.a = coef(600.0f - 400.0f * k); float hiss = noise(&b->rng); if (chance(&b->rng) < (400.0f + 900.0f * k) / RATE) b->click = noise(&b->rng); float drop = b->click; b->click *= 0.9f - 0.08f * k; x = lowpass(&b->low, hiss * (0.5f - 0.3f * k) + drop * (0.6f + 0.6f * k)); x -= onePole(&b->high, x); x *= 1.4f; break; } case BED_FIRE: { /* a low roar, crackles and now and then a pop */ b->low.a = coef(250.0f + 250.0f * k); float roar = lowpass(&b->low, noise(&b->rng)) * (0.6f + 0.3f * b->mod) * 2.0f; if (chance(&b->rng) < (6.0f + 30.0f * k) / RATE) b->click = noise(&b->rng) * (chance(&b->rng) < 0.1f ? 2.0f : 0.8f); b->high.a = coef(1500.0f); float c = b->click; b->click *= 0.7f; c -= onePole(&b->high, c); x = roar * (0.4f + 0.6f * k) + c; break; } case BED_SAW: { /* a two-stroke buzz, lower and rougher cutting */ float f = 140.0f - 35.0f * k + 6.0f * b->mod; b->phase += f / RATE; if (b->phase > 1.0f) b->phase -= 1.0f; b->phase2 += 0.5f * f / RATE; if (b->phase2 > 1.0f) b->phase2 -= 1.0f; float saw = 2.0f * b->phase - 1.0f, chug = 0.75f + 0.25f * sinf(TWO_PI * b->phase2); b->low.a = coef(1800.0f + 1500.0f * k); x = lowpass(&b->low, saw * chug + 0.35f * k * noise(&b->rng)) * 1.3f; break; } case BED_ENGINE: { /* a low hum that rises with the speed */ float f = 32.0f + 45.0f * k; b->phase += f / RATE; if (b->phase > 1.0f) b->phase -= 1.0f; b->low.a = coef(220.0f + 250.0f * k); x = lowpass(&b->low, (2.0f * b->phase - 1.0f) + 0.3f * noise(&b->rng)) * 1.8f; break; } case BED_CRICKETS: { /* a few crickets: bursts of a high tone, three pulses then a rest, each its own time */ b->phase += 4500.0f / RATE; if (b->phase > 1.0f) b->phase -= 1.0f; b->phase2 += 1.0f / RATE; float s = 0.0f; for (int c = 0; c < 3; c++) { float cyc = 0.55f + 0.17f * (float)c, tt = fmodf(b->phase2 + 0.31f * (float)c, cyc); float pulse = tt < 0.12f ? fmaxf(0.0f, sinf(tt / 0.12f * 3.0f * TWO_PI * 0.5f)) : 0.0f; s += pulse * (c == 1 ? 0.7f : 1.0f); } x = sinf(TWO_PI * b->phase) * s * 0.5f; if (b->phase2 > 1000.0f) b->phase2 -= 1000.0f; break; } case BED_HOSE: { /* a steady spray */ b->low.a = coef(6000.0f); b->high.a = coef(1500.0f); x = lowpass(&b->low, noise(&b->rng)); x -= onePole(&b->high, x); x *= 1.6f; break; } default: if (which >= BED_FLOOR && which < BED_FLOOR + 4) x = floorSample(which - BED_FLOOR, b); break; } return x; } static void mixBeds(float *out, int frames) { for (int i = 0; i < BED_COUNT; i++) { BedState *b = &g_beds[i]; float goal = g_paused ? 0.0f : b->gain; if (b->g < 1e-4f && goal < 1e-4f) continue; float ease = 1.0f - expf(-BED_EASE / RATE); for (int k = 0; k < frames; k++) { b->g += (goal - b->g) * ease; b->p += (b->pan - b->p) * ease; b->k += (b->param - b->k) * ease; float l, r, s = bedSample((Bed)i, b) * b->g; panGains(b->p, &l, &r); out[2 * k] += s * l; out[2 * k + 1] += s * r; } } } /* ---------- The device ---------- */ static void SDLCALL fill(void *user, SDL_AudioStream *stream, int additional, int total) { (void)user; (void)total; static float buf[CHUNK * 2]; int frames = additional / (int)(2 * sizeof(float)); while (frames > 0) { int n = frames < CHUNK ? frames : CHUNK; memset(buf, 0, sizeof(float) * 2 * (size_t)n); SDL_LockMutex(g_lock); mixVoices(buf, n); mixBeds(buf, n); bool muted = g_muted; SDL_UnlockMutex(g_lock); for (int k = 0; k < 2 * n; k++) { if (k % 2 == 0) g_fade += ((muted ? 0.0f : 1.0f) - g_fade) * 0.002f; float s = buf[k] * MASTER * g_fade; buf[k] = s / (1.0f + fabsf(s)); /* (soft: never clipping) */ } SDL_PutAudioStreamData(stream, buf, (int)(sizeof(float) * 2 * (size_t)n)); frames -= n; } } bool Audio_init(void) { if (g_stream) return true; if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) { SDL_Log("No sound: %s", SDL_GetError()); return false; } g_lock = SDL_CreateMutex(); for (int i = 0; i < BED_COUNT; i++) g_beds[i].rng = 0x9E3779B9u * (unsigned)(i + 1); SDL_AudioSpec spec = { SDL_AUDIO_F32, 2, RATE }; g_stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, fill, NULL); if (!g_stream) { SDL_Log("No sound device: %s", SDL_GetError()); SDL_DestroyMutex(g_lock); g_lock = NULL; SDL_QuitSubSystem(SDL_INIT_AUDIO); return false; } SDL_ResumeAudioStreamDevice(g_stream); return true; } void Audio_shutdown(void) { if (!g_stream) return; SDL_DestroyAudioStream(g_stream); g_stream = NULL; SDL_DestroyMutex(g_lock); g_lock = NULL; SDL_QuitSubSystem(SDL_INIT_AUDIO); } void Audio_play(const Voice *voice) { if (!g_stream || voice->gain < 0.002f) return; static unsigned seed = 0x1234567u; SDL_LockMutex(g_lock); int pick = -1; float oldest = -1.0f; for (int i = 0; i < VOICES_MAX; i++) { /* (a free one, else the one playing longest) */ if (!g_voices[i].on) { pick = i; break; } if (g_voices[i].t > oldest) { oldest = g_voices[i].t; pick = i; } } Playing *p = &g_voices[pick]; memset(p, 0, sizeof(*p)); p->v = *voice; p->t = -fmaxf(voice->delay, 0.0f); p->low.a = coef(voice->low); p->high.a = coef(voice->high); seed = seed * 1664525u + 1013904223u; p->rng = seed | 1u; p->on = true; SDL_UnlockMutex(g_lock); } void Audio_bed(Bed bed, float gain, float pan, float param) { if (!g_stream || bed < 0 || bed >= BED_COUNT) return; SDL_LockMutex(g_lock); g_beds[bed].gain = fmaxf(0.0f, fminf(1.0f, gain)); g_beds[bed].pan = pan; g_beds[bed].param = fmaxf(0.0f, fminf(1.0f, param)); SDL_UnlockMutex(g_lock); } void Audio_mute(bool mute) { if (g_lock) SDL_LockMutex(g_lock); g_muted = mute; if (g_lock) SDL_UnlockMutex(g_lock); } bool Audio_muted(void) { return g_muted; } void Audio_pause(bool paused) { if (g_lock) SDL_LockMutex(g_lock); g_paused = paused; if (g_lock) SDL_UnlockMutex(g_lock); }