Nimbin[12]?Games / Entropie / entropie/src/game/sound.c

Entropie git · main

Entropie festival game; first steps

sdl c game entropie cmake · first commit 2026-10-10 · last commit 2026-11-02 (18 hours ago) · synced 10 hours ago

C 88.5% Markdown 9.1% GLSL 2.3%
git clone https://git.christianimmanuel.de/games/Entropie.gitwget https://git.christianimmanuel.de/games/Entropie/archive/Entropie.tar.gz
entropie/src/game/sound.c 14.1 KB · 261 lines raw
#include "game/sound.h"
#include "core/audio.h"
#include "world/camp.h"
#include "world/weather.h"

#include <math.h>

#define HEAR_REF   4.0f   /* m: full loudness this near ... */
#define HEAR_MOST  70.0f  /* ... nothing from further than this */
#define STEP_GAIN  0.45f  /* steps are soft */
#define BIRD_EVERY 1.6f   /* s: a bird now and then, by day */

static unsigned g_rng = 0xC0FFEEu;
static float dice(void) { g_rng = g_rng * 1664525u + 1013904223u; return (float)(g_rng >> 8) / 16777216.0f; }

typedef struct { Vec3 eye, right; } Ear;

/* How loud from here (0..1) and from which side (-1 left .. 1 right); muffled: the high end goes further off. */
static float hear(const Ear *ear, Vec3 at, float *pan, float *dull) {
    Vec3 to = vec3_sub(at, ear->eye);
    float d = vec3_length(to);
    *pan = d > 0.3f ? 0.85f * vec3_dot(vec3_scale(to, 1.0f / d), ear->right) : 0.0f;
    if (dull) *dull = 1.0f / (1.0f + d / 25.0f);
    if (d > HEAR_MOST) return 0.0f;
    return HEAR_REF / (HEAR_REF + d) * (1.0f - d / HEAR_MOST);
}

static void play(Wave wave, float f0, float f1, float low, float high, float attack, float decay, float length, float gain, float pan, float delay) {
    Voice v = { wave, f0, f1, low, high, attack, decay, length, gain, pan, delay };
    Audio_play(&v);
}

static void step(const Noise *n, float g, float pan, float dull) {
    g *= STEP_GAIN * (0.85f + 0.3f * dice());
    float lo = dull;
    switch (n->surface) {
    case STEP_GRAVEL:
        play(WAVE_CRACKLE, 900.0f, 0.0f, 7000.0f * lo, 900.0f, 0.004f, 0.05f, 0.14f, 0.9f * g, pan, 0.0f);
        play(WAVE_NOISE, 0.0f, 0.0f, 2500.0f * lo, 300.0f, 0.004f, 0.04f, 0.1f, 0.4f * g, pan, 0.0f);
        break;
    case STEP_HARD:
        play(WAVE_NOISE, 0.0f, 0.0f, 3500.0f * lo, 400.0f, 0.001f, 0.018f, 0.06f, 0.7f * g, pan, 0.0f);
        play(WAVE_SINE, 160.0f, 110.0f, 0.0f, 0.0f, 0.001f, 0.02f, 0.06f, 0.35f * g, pan, 0.0f);
        break;
    case STEP_WOOD:
        play(WAVE_SINE, 150.0f, 120.0f, 0.0f, 0.0f, 0.002f, 0.05f, 0.15f, 0.6f * g, pan, 0.0f);
        play(WAVE_NOISE, 0.0f, 0.0f, 1500.0f * lo, 200.0f, 0.002f, 0.025f, 0.08f, 0.35f * g, pan, 0.0f);
        break;
    case STEP_EARTH:
        play(WAVE_NOISE, 0.0f, 0.0f, 900.0f * lo, 90.0f, 0.004f, 0.045f, 0.12f, 0.7f * g, pan, 0.0f);
        break;
    case STEP_MUD: /* a squelch */
        play(WAVE_NOISE, 0.0f, 0.0f, 700.0f * lo, 60.0f, 0.01f, 0.08f, 0.2f, 0.7f * g, pan, 0.0f);
        play(WAVE_SINE, 220.0f, 90.0f, 0.0f, 0.0f, 0.01f, 0.07f, 0.16f, 0.35f * g, pan, 0.03f);
        break;
    case STEP_WATER: /* a splash */
        play(WAVE_NOISE, 0.0f, 0.0f, 6000.0f * lo, 900.0f, 0.005f, 0.09f, 0.25f, 0.6f * g, pan, 0.0f);
        play(WAVE_CRACKLE, 300.0f, 0.0f, 5000.0f * lo, 1500.0f, 0.005f, 0.1f, 0.25f, 0.4f * g, pan, 0.02f);
        break;
    default: /* grass: a soft swish */
        play(WAVE_NOISE, 0.0f, 0.0f, 2800.0f * lo, 700.0f, 0.01f, 0.05f, 0.14f, 0.45f * g, pan, 0.0f);
        break;
    }
}

static void noiseOut(const Noise *n, const Ear *ear) {
    float pan, dull, g = hear(ear, n->at, &pan, &dull) * n->loud;
    if (g < 0.003f) return;
    switch (n->kind) {
    case NOISE_STEP: step(n, g, pan, dull); break;
    case NOISE_CHOP: /* the edge into the wood: a knock and a crack */
        play(WAVE_SINE, 110.0f, 70.0f, 0.0f, 0.0f, 0.001f, 0.06f, 0.2f, 0.8f * g, pan, 0.0f);
        play(WAVE_NOISE, 0.0f, 0.0f, 3000.0f * dull, 250.0f, 0.001f, 0.035f, 0.12f, 0.7f * g, pan, 0.0f);
        play(WAVE_CRACKLE, 700.0f, 0.0f, 6000.0f * dull, 1200.0f, 0.001f, 0.04f, 0.1f, 0.4f * g, pan, 0.01f);
        break;
    case NOISE_CLINK: { /* glass on glass, twice */
        float f = 2400.0f + 600.0f * dice();
        play(WAVE_SINE, f, 0.0f, 0.0f, 0.0f, 0.001f, 0.12f, 0.5f, 0.35f * g, pan, 0.0f);
        play(WAVE_SINE, f * 1.62f, 0.0f, 0.0f, 0.0f, 0.001f, 0.08f, 0.4f, 0.22f * g, pan, 0.0f);
        play(WAVE_SINE, f * 1.1f, 0.0f, 0.0f, 0.0f, 0.001f, 0.07f, 0.3f, 0.2f * g, pan, 0.07f + 0.05f * dice());
        break;
    }
    case NOISE_SMASH: /* glass breaking: a crash and the shards tinkling */
        play(WAVE_CRACKLE, 2500.0f, 0.0f, 9000.0f * dull, 1800.0f, 0.001f, 0.12f, 0.5f, 0.9f * g, pan, 0.0f);
        for (int k = 0; k < 6; k++)
            play(WAVE_SINE, 3000.0f + 4000.0f * dice(), 0.0f, 0.0f, 0.0f, 0.001f, 0.05f, 0.2f, 0.18f * g, pan, 0.03f + 0.25f * dice());
        break;
    case NOISE_THUD: /* somebody down on the ground */
        play(WAVE_SINE, 75.0f, 45.0f, 0.0f, 0.0f, 0.002f, 0.12f, 0.4f, 0.9f * g, pan, 0.0f);
        play(WAVE_NOISE, 0.0f, 0.0f, 500.0f * dull, 40.0f, 0.002f, 0.08f, 0.3f, 0.6f * g, pan, 0.0f);
        break;
    case NOISE_CRASH: /* a tree down: branches cracking, the trunk hitting the ground */
        play(WAVE_CRACKLE, 400.0f, 0.0f, 5000.0f * dull, 600.0f, 0.02f, 0.5f, 2.0f, 0.9f * g, pan, 0.0f);
        play(WAVE_NOISE, 0.0f, 0.0f, 260.0f, 25.0f, 0.03f, 0.7f, 2.5f, 1.0f * g, pan, 0.05f);
        play(WAVE_SINE, 55.0f, 35.0f, 0.0f, 0.0f, 0.01f, 0.4f, 1.5f, 0.8f * g, pan, 0.05f);
        break;
    case NOISE_VOICE: { /* words heard, not made out: a murmur, syllable after syllable, rising and falling */
        static const float PITCH[3] = { 95.0f, 125.0f, 215.0f };
        int voice = n->surface & 3, syllables = n->surface >> 2;
        float f = PITCH[voice > 2 ? 1 : voice] * (0.92f + 0.16f * dice()), at = 0.0f;
        for (int k = 0; k < syllables; k++) {
            float lift = 1.0f + 0.12f * sinf(1.7f * (float)k + 6.0f * dice()) - (k == syllables - 1 ? 0.1f : 0.0f); /* (and down at the end) */
            float len = 0.09f + 0.08f * dice();
            play(WAVE_SAW, f * lift, f * lift * (0.95f + 0.1f * dice()), (700.0f + 1100.0f * dice()) * dull, 150.0f, 0.02f, 0.05f, len, 0.22f * g, pan, at);
            at += len + 0.03f + (dice() < 0.15f ? 0.12f : 0.0f); /* (a pause now and then) */
        }
        break;
    }
    case NOISE_BARK: /* woof */
        for (int k = 0; k < n->surface && k < 4; k++) {
            float at = 0.3f * (float)k;
            play(WAVE_SAW, 420.0f + 40.0f * dice(), 250.0f, 1900.0f * dull, 220.0f, 0.004f, 0.06f, 0.16f, 0.7f * g, pan, at);
            play(WAVE_NOISE, 0.0f, 0.0f, 2500.0f * dull, 400.0f, 0.003f, 0.04f, 0.1f, 0.35f * g, pan, at);
        }
        break;
    case NOISE_GULP:
        if (n->surface == 1) /* a bite: crunch */
            for (int k = 0; k < 3; k++) play(WAVE_CRACKLE, 900.0f, 0.0f, 4500.0f * dull, 500.0f, 0.003f, 0.04f, 0.1f, 0.5f * g, pan, 0.13f * (float)k);
        else { /* a swallow */
            play(WAVE_SINE, 320.0f, 150.0f, 0.0f, 0.0f, 0.01f, 0.05f, 0.12f, 0.35f * g, pan, 0.0f);
            play(WAVE_NOISE, 0.0f, 0.0f, 900.0f * dull, 100.0f, 0.01f, 0.06f, 0.15f, 0.2f * g, pan, 0.02f);
        }
        break;
    case NOISE_FLUSH: /* the cistern: a rush, then gurgling down */
        play(WAVE_NOISE, 0.0f, 0.0f, 2600.0f * dull, 300.0f, 0.25f, 1.0f, 3.0f, 0.6f * g, pan, 0.0f);
        play(WAVE_NOISE, 0.0f, 0.0f, 500.0f * dull, 60.0f, 0.4f, 1.4f, 3.5f, 0.5f * g, pan, 0.3f);
        play(WAVE_CRACKLE, 60.0f, 0.0f, 1200.0f * dull, 100.0f, 0.5f, 1.2f, 3.0f, 0.3f * g, pan, 0.6f);
        break;
    case NOISE_RUSTLE: /* leaves and twigs */
        play(WAVE_NOISE, 0.0f, 0.0f, 5000.0f * dull, 900.0f, 0.03f, 0.25f, 0.7f, 0.6f * g, pan, 0.0f);
        play(WAVE_CRACKLE, 500.0f, 0.0f, 6000.0f * dull, 1200.0f, 0.02f, 0.2f, 0.6f, 0.5f * g, pan, 0.05f);
        break;
    case NOISE_CREAK: { /* old hinges: a wavering squeak, then the leaf against the stop */
        float f = 500.0f + 300.0f * dice();
        play(WAVE_SAW, f, f * (0.7f + 0.2f * dice()), 2500.0f * dull, 300.0f, 0.08f, 0.25f, 0.7f, 0.12f * g, pan, 0.0f);
        play(WAVE_SINE, 120.0f, 80.0f, 0.0f, 0.0f, 0.002f, 0.08f, 0.25f, 0.4f * g, pan, 0.8f);
        break;
    }
    case NOISE_SLAM: /* a car door or the boot: a solid thunk */
        play(WAVE_SINE, 90.0f, 55.0f, 0.0f, 0.0f, 0.002f, 0.09f, 0.3f, 0.8f * g, pan, 0.0f);
        play(WAVE_NOISE, 0.0f, 0.0f, 1200.0f * dull, 80.0f, 0.002f, 0.05f, 0.2f, 0.5f * g, pan, 0.0f);
        play(WAVE_CRACKLE, 300.0f, 0.0f, 4000.0f * dull, 1500.0f, 0.001f, 0.03f, 0.08f, 0.3f * g, pan, 0.0f); /* (the latch) */
        break;
    case NOISE_PANT: { /* hah - hah: a breath out, a quicker one in */
        float per = n->surface > 0 ? 10.0f / (float)n->surface : 0.3f;
        play(WAVE_NOISE, 0.0f, 0.0f, 1700.0f * dull, 450.0f, 0.012f, 0.05f, 0.11f, 0.45f * g, pan, 0.0f);
        play(WAVE_NOISE, 0.0f, 0.0f, 2400.0f * dull, 800.0f, 0.01f, 0.035f, 0.08f, 0.25f * g, pan, 0.45f * per);
        break;
    }
    case NOISE_SNAP: /* a dry branch underfoot */
        play(WAVE_CRACKLE, 1500.0f, 0.0f, 7000.0f * dull, 900.0f, 0.001f, 0.03f, 0.08f, 0.8f * g, pan, 0.0f);
        play(WAVE_NOISE, 0.0f, 0.0f, 2500.0f * dull, 400.0f, 0.001f, 0.02f, 0.06f, 0.5f * g, pan, 0.0f);
        break;
    default: break;
    }
}

/* Lightning just now: the thunder rolling, long and low (near: a crack first). */
static void thunder(void) {
    float near = dice(), pan = 1.6f * dice() - 0.8f;
    if (near > 0.6f) play(WAVE_CRACKLE, 600.0f, 0.0f, 3500.0f, 300.0f, 0.005f, 0.25f, 0.8f, 0.7f, pan, 0.0f);
    play(WAVE_NOISE, 0.0f, 0.0f, 140.0f + 120.0f * near, 20.0f, 0.3f, 1.6f + 1.0f * (1.0f - near), 7.0f, 0.9f, pan, 0.05f);
    play(WAVE_NOISE, 0.0f, 0.0f, 90.0f, 15.0f, 0.6f, 2.2f, 7.0f, 0.7f, -pan, 0.9f + 0.6f * dice());
}

/* A bird somewhere near: a few quick rising whistles. */
static void bird(void) {
    float f = 2200.0f + 2600.0f * dice(), pan = 1.8f * dice() - 0.9f, g = 0.05f + 0.06f * dice();
    int notes = 2 + (int)(4.0f * dice());
    for (int k = 0; k < notes; k++)
        play(WAVE_SINE, f, f * (1.15f + 0.4f * dice()), 0.0f, 0.0f, 0.006f, 0.03f, 0.09f, g, pan, 0.11f * (float)k);
}

void Sound_update(Game *game, float dt, bool paused) {
    World *w = &game->world;
    Audio_pause(paused);
    if (paused) { w->noise_count = 0; return; }
    Ear ear = { game->camera.position, game->camera.right };
    const Object *you = game->player;
    bool tent = you && you->in_tent;

    for (int i = 0; i < w->noise_count; i++) noiseOut(&w->noises[i], &ear);
    w->noise_count = 0;
    if (w->thunder) thunder();

    /* the air: wind and rain (in a tent: dull and drumming on it) */
    float wind = fminf(1.0f, hypotf(w->wind.x, w->wind.z) / WIND_MAX);
    Audio_bed(BED_WIND, (0.06f + 0.55f * wind) * (tent ? 0.5f : 1.0f), 0.0f, wind);
    bool berlin = you && Game_inBerlin(you); /* (at a meeting: indoors, far off) */
    Audio_bed(BED_RAIN, berlin ? 0.0f : 0.6f * smoothstepf(0.02f, 0.6f, w->rain), 0.0f, tent ? 1.0f : 0.0f);

    /* fires, a chainsaw, the hose: the loudest near you */
    float fire_g = 0.0f, fire_pan = 0.0f, fire_k = 0.0f, saw_g = 0.0f, saw_pan = 0.0f, saw_k = 0.0f, hose_g = 0.0f, hose_pan = 0.0f;
    for (int i = 0; i < w->count; i++) {
        const Object *o = w->objects[i];
        float pan, g;
        if (o->type == OBJ_FIREPLACE && o->burning) {
            float size = Camp_flameSize(o);
            g = hear(&ear, o->position, &pan, NULL) * (0.4f + 0.6f * size);
            if (g > fire_g) { fire_g = g; fire_pan = pan; fire_k = size; }
        } else if (o->is_character) {
            const Work *ws = &o->work_state;
            if (ws->tool == TOOL_CHAINSAW && Tools_held(o, NULL) == TOOL_CHAINSAW && (Tools_isWorking(o) || ws->saw_in > 0.05f)) {
                g = hear(&ear, o->position, &pan, NULL);
                if (g > saw_g) { saw_g = g; saw_pan = pan; saw_k = ws->saw_in; }
            } else if (o->carving && Tools_held(o, NULL) == TOOL_CHAINSAW) { /* (Kerry carving: in and out of the wood) */
                g = hear(&ear, o->position, &pan, NULL);
                if (g > saw_g) { saw_g = g; saw_pan = pan; saw_k = 0.5f + 0.4f * sinf(o->body.clock * 1.7f); }
            }
            if (o->spraying) {
                g = hear(&ear, o->position, &pan, NULL);
                if (g > hose_g) { hose_g = g; hose_pan = pan; }
            }
        }
    }
    Audio_bed(BED_FIRE, 0.9f * fire_g, fire_pan, fire_k);
    Audio_bed(BED_SAW, 0.8f * saw_g, saw_pan, saw_k);
    Audio_bed(BED_HOSE, 0.5f * hose_g, hose_pan, 0.0f);

    /* the car coming in */
    const Object *car = game->arrival.car;
    if (car && game->arrival.stage == ARRIVAL_DRIVING) {
        float pan, g = you && you->riding ? 0.6f : hear(&ear, car->position, &pan, NULL);
        Audio_bed(BED_ENGINE, g, you && you->riding ? 0.0f : pan, game->arrival.speed / 5.5f);
    } else Audio_bed(BED_ENGINE, 0.0f, 0.0f, 0.0f);

    /* the ambulance: the two-tone horn (driving in and out, a while once it's stopped) */
    static float horn_t = 0.0f;
    static int horn_k = 0;
    const Ambulance *amb = &game->ambulance;
    if (amb->car && (amb->stage == AMB_IN || amb->stage == AMB_OUT || (amb->stage == AMB_PARKED && amb->t < 3.0f)) && (horn_t -= dt) <= 0.0f) {
        float pan, g = hear(&ear, amb->car->position, &pan, NULL);
        horn_t = 0.62f;
        horn_k ^= 1;
        float f = horn_k ? 466.0f : 622.0f; /* (B flat and E flat: tatue - tata) */
        play(WAVE_SAW, f, f, 2200.0f, 300.0f, 0.02f, 0.6f, 0.6f, 0.25f * fmaxf(g, 0.05f), pan, 0.0f);
    }

    /* the floors playing: each its own music, from its stage (far off only the bass and the kick carry) */
    for (int k = 0; k < FEST_FLOORS; k++) {
        float pan = 0.0f, g = 0.0f, far = 1.0f;
        if (w->fest.on & (1u << k)) {
            Vec3 at = w->fest.dance[k];
            float d = hypotf(at.x - ear.eye.x, at.z - ear.eye.z);
            hear(&ear, at, &pan, NULL);
            g = 0.75f / (1.0f + d / 14.0f) * (tent ? 0.6f : 1.0f) * (Game_inBerlin(you) ? 0.0f : 1.0f);
            far = clampf(d / 90.0f, 0.0f, 1.0f);
        }
        Audio_bed((Bed)(BED_FLOOR + k), g, pan, far);
    }

    /* night: crickets; day: birds (not in the rain, less in the wind) */
    float hour = w->hour, calm = (1.0f - smoothstepf(0.05f, 0.4f, w->rain)) * (1.0f - 0.7f * wind);
    bool night = hour > 21.0f || hour < 5.0f;
    Audio_bed(BED_CRICKETS, night ? 0.12f * calm * (tent ? 0.5f : 1.0f) : 0.0f, 0.0f, 0.0f);
    static float bird_t = 0.0f;
    if (!night && hour > 5.5f && hour < 20.5f && (bird_t -= dt) <= 0.0f) {
        bird_t = BIRD_EVERY * (0.3f + 1.7f * dice()) / fmaxf(0.15f, calm);
        if (dice() < calm * (hour < 9.0f ? 1.0f : 0.5f)) bird(); /* (mornings: more of them) */
    }
}