Add spatial audio, spacecraft entity, and level intro sequence
Distance-based sound effects with stereo panning for explosions, impacts, and pickups. Spacecraft entity with full state machine (fly-in, land, take off, fly-out), engine/synth sound loops, thruster particles, and PNG spritesheet. Moon level intro defers player spawn until ship lands. Also untrack build/ objects that were committed by mistake.
This commit is contained in:
@@ -2,9 +2,17 @@
|
||||
#include "config.h"
|
||||
#include <SDL2/SDL_mixer.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
static bool s_initialized = false;
|
||||
|
||||
/* ── Listener state for spatial audio ────────────── */
|
||||
static Vec2 s_listener_pos = {0};
|
||||
static bool s_listener_set = false;
|
||||
|
||||
/* Default max audible distance in pixels (~40 tiles) */
|
||||
#define AUDIO_DEFAULT_MAX_DIST 640.0f
|
||||
|
||||
bool audio_init(void) {
|
||||
/* Initialize MP3 (and OGG) decoder support */
|
||||
int mix_flags = MIX_INIT_MP3 | MIX_INIT_OGG;
|
||||
@@ -66,9 +74,25 @@ void audio_play_sound(Sound s, int volume) {
|
||||
int ch = Mix_PlayChannel(-1, (Mix_Chunk *)s.chunk, 0);
|
||||
if (ch >= 0) {
|
||||
Mix_Volume(ch, volume);
|
||||
Mix_SetPanning(ch, 255, 255); /* reset to center (no panning) */
|
||||
}
|
||||
}
|
||||
|
||||
int audio_play_sound_loop(Sound s, int volume) {
|
||||
if (!s_initialized || !s.chunk) return -1;
|
||||
int ch = Mix_PlayChannel(-1, (Mix_Chunk *)s.chunk, -1); /* -1 = loop forever */
|
||||
if (ch >= 0) {
|
||||
Mix_Volume(ch, volume);
|
||||
Mix_SetPanning(ch, 255, 255); /* reset to center */
|
||||
}
|
||||
return ch;
|
||||
}
|
||||
|
||||
void audio_stop_channel(int ch) {
|
||||
if (!s_initialized || ch < 0) return;
|
||||
Mix_HaltChannel(ch);
|
||||
}
|
||||
|
||||
void audio_play_music(Music m, bool loop) {
|
||||
if (!s_initialized) {
|
||||
fprintf(stderr, "audio_play_music: audio not initialized\n");
|
||||
@@ -99,11 +123,60 @@ void audio_set_music_volume(int volume) {
|
||||
Mix_VolumeMusic(volume);
|
||||
}
|
||||
|
||||
/* ── Positional / spatial audio ──────────────────── */
|
||||
|
||||
void audio_set_listener(Vec2 pos) {
|
||||
s_listener_pos = pos;
|
||||
s_listener_set = true;
|
||||
}
|
||||
|
||||
void audio_play_sound_at(Sound s, int base_volume, Vec2 world_pos, float max_dist) {
|
||||
if (!s_initialized || !s.chunk) return;
|
||||
|
||||
if (max_dist <= 0.0f) max_dist = AUDIO_DEFAULT_MAX_DIST;
|
||||
|
||||
/* Fall back to non-positional if listener was never set */
|
||||
if (!s_listener_set) {
|
||||
audio_play_sound(s, base_volume);
|
||||
return;
|
||||
}
|
||||
|
||||
float dx = world_pos.x - s_listener_pos.x;
|
||||
float dy = world_pos.y - s_listener_pos.y;
|
||||
float dist = sqrtf(dx * dx + dy * dy);
|
||||
|
||||
/* Inaudible beyond max distance */
|
||||
if (dist >= max_dist) return;
|
||||
|
||||
/* Linear attenuation: full volume at dist=0, silent at max_dist */
|
||||
float atten = 1.0f - (dist / max_dist);
|
||||
int vol = (int)(base_volume * atten);
|
||||
if (vol <= 0) return;
|
||||
if (vol > 128) vol = 128;
|
||||
|
||||
int ch = Mix_PlayChannel(-1, (Mix_Chunk *)s.chunk, 0);
|
||||
if (ch < 0) return;
|
||||
|
||||
Mix_Volume(ch, vol);
|
||||
|
||||
/* Stereo panning: center = 127, hard-left = 0, hard-right = 254.
|
||||
* Pan linearly based on horizontal offset relative to half the
|
||||
* viewport width (~half of SCREEN_WIDTH). */
|
||||
float half_screen = (float)SCREEN_WIDTH * 0.5f;
|
||||
float pan_ratio = dx / half_screen; /* -1..+1 roughly */
|
||||
if (pan_ratio < -1.0f) pan_ratio = -1.0f;
|
||||
if (pan_ratio > 1.0f) pan_ratio = 1.0f;
|
||||
Uint8 pan_right = (Uint8)(127 + (int)(pan_ratio * 127.0f));
|
||||
Uint8 pan_left = 254 - pan_right;
|
||||
Mix_SetPanning(ch, pan_left, pan_right);
|
||||
}
|
||||
|
||||
void audio_shutdown(void) {
|
||||
if (!s_initialized) return;
|
||||
Mix_HaltMusic();
|
||||
Mix_CloseAudio();
|
||||
Mix_Quit();
|
||||
s_initialized = false;
|
||||
s_listener_set = false;
|
||||
printf("Audio shut down.\n");
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define JNR_AUDIO_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "util/vec2.h"
|
||||
|
||||
typedef struct Sound {
|
||||
void *chunk; /* Mix_Chunk* */
|
||||
@@ -21,4 +22,23 @@ void audio_free_music(Music *m);
|
||||
void audio_set_music_volume(int volume); /* 0-128 */
|
||||
void audio_shutdown(void);
|
||||
|
||||
/* ── Looping sounds ────────────────────────────── */
|
||||
|
||||
/* Play a sound on loop, returns the channel number (or -1 on failure).
|
||||
* Use audio_stop_channel() to stop it later. */
|
||||
int audio_play_sound_loop(Sound s, int volume);
|
||||
|
||||
/* Stop a specific channel (from audio_play_sound_loop). No-op if ch < 0. */
|
||||
void audio_stop_channel(int ch);
|
||||
|
||||
/* ── Positional / spatial audio ────────────────── */
|
||||
|
||||
/* Set the listener position (call once per frame, typically from camera center) */
|
||||
void audio_set_listener(Vec2 pos);
|
||||
|
||||
/* Play a sound at a world position. Volume is attenuated by distance from the
|
||||
* listener and stereo-panned based on horizontal offset. Sounds beyond
|
||||
* max_dist are inaudible. Pass max_dist <= 0 for a sensible default. */
|
||||
void audio_play_sound_at(Sound s, int base_volume, Vec2 world_pos, float max_dist);
|
||||
|
||||
#endif /* JNR_AUDIO_H */
|
||||
|
||||
@@ -27,6 +27,7 @@ typedef enum EntityType {
|
||||
ENT_POWERUP,
|
||||
ENT_DRONE,
|
||||
ENT_ASTEROID,
|
||||
ENT_SPACECRAFT,
|
||||
ENT_TYPE_COUNT
|
||||
} EntityType;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user