Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions assets/sounds/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Sounds

## Attribution

### `bubble-pop.wav`

Trimmed from a Pixabay sound effect.

- Title: Bubble Pop 06
- Creator: Universfield
- Source: [pixabay.com/sound-effects/bubble-pop-06-351337/](https://pixabay.com/sound-effects/bubble-pop-06-351337/)
- License: Pixabay Content License
- License summary: [pixabay.com/service/license-summary/](https://pixabay.com/service/license-summary/)
- License terms: [pixabay.com/service/terms/](https://pixabay.com/service/terms/)

Optional credit line (Pixabay suggested): `by Universfield via Pixabay`
Binary file added assets/sounds/bubble-pop.wav
Binary file not shown.
142 changes: 142 additions & 0 deletions src/helpers/audio.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#include "audio.h"

#include <string.h>
#include <SDL3/SDL.h>
#include <SDL3/SDL_log.h>

bool audio_manager_create(audio_manager_t* manager) {
SDL_assert(manager != NULL);

memset(manager, 0, sizeof(*manager));

if (SDL_InitSubSystem(SDL_INIT_AUDIO) == false) {
SDL_Log("Failed to initialize SDL audio subsystem: %s", SDL_GetError());
return false;
}

const SDL_AudioSpec spec = {.format = SDL_AUDIO_F32, .channels = 2, .freq = 44100};

manager->stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, NULL, NULL);
if (manager->stream == NULL) {
SDL_Log("Failed to open audio device stream: %s", SDL_GetError());
SDL_QuitSubSystem(SDL_INIT_AUDIO);
return false;
}

if (SDL_ResumeAudioStreamDevice(manager->stream) == false) {
SDL_Log("Failed to resume audio stream device: %s", SDL_GetError());
SDL_DestroyAudioStream(manager->stream);
manager->stream = NULL;
SDL_QuitSubSystem(SDL_INIT_AUDIO);
return false;
}

manager->is_initialized = true;
return true;
}

void audio_manager_destroy(audio_manager_t* manager) {
SDL_assert(manager != NULL);

if (manager->is_initialized == false) {
return;
}

for (size_t i = 0; i < SOUND_COUNT; ++i) {
if (manager->sounds[i].buffer != NULL) {
SDL_free(manager->sounds[i].buffer);
manager->sounds[i].buffer = NULL;
}
manager->sounds[i].length = 0;
}

if (manager->stream != NULL) {
SDL_DestroyAudioStream(manager->stream);
manager->stream = NULL;
}

SDL_QuitSubSystem(SDL_INIT_AUDIO);

manager->is_initialized = false;
}

bool audio_manager_load_sound(audio_manager_t* manager, sound_id_t id, const char* filepath) {
SDL_assert(manager != NULL);
SDL_assert(filepath != NULL);

if (manager->is_initialized == false) {
SDL_Log("Audio manager not initialized, cannot load sound: %s", filepath);
return false;
}

if (id >= SOUND_COUNT) {
SDL_Log("Invalid sound ID: %d", id);
return false;
}

if (manager->sounds[id].buffer != NULL) {
SDL_Log("Sound already loaded for ID %d, skipping: %s", id, filepath);
return true;
}

SDL_AudioSpec spec;
uint8_t* buffer = NULL;
Uint32 length = 0;

if (SDL_LoadWAV(filepath, &spec, &buffer, &length) == false) {
SDL_Log("Failed to load audio file '%s': %s", filepath, SDL_GetError());
return false;
}

SDL_AudioSpec target_spec;
if (SDL_GetAudioStreamFormat(manager->stream, &target_spec, NULL) == false) {
SDL_Log("Failed to get audio stream format: %s", SDL_GetError());
SDL_free(buffer);
return false;
}

uint8_t* converted_buffer = NULL;
int converted_length = 0;

if (SDL_ConvertAudioSamples(&spec, buffer, (int)length, &target_spec, &converted_buffer, &converted_length) ==
false) {
SDL_Log("Failed to convert audio samples for '%s': %s", filepath, SDL_GetError());
SDL_free(buffer);
return false;
}

SDL_free(buffer);

manager->sounds[id].buffer = converted_buffer;
manager->sounds[id].length = (size_t)converted_length;
manager->sounds[id].spec = target_spec;

SDL_Log("Successfully loaded sound: %s (ID: %d, %d bytes)", filepath, id, converted_length);
return true;
}

bool audio_manager_play_sound(audio_manager_t* manager, sound_id_t id) {
SDL_assert(manager != NULL);

if (manager->is_initialized == false) {
return false;
}

if (id >= SOUND_COUNT) {
SDL_Log("Invalid sound ID: %d", id);
return false;
}

if (manager->sounds[id].buffer == NULL) {
SDL_Log("Sound not loaded for ID %d, cannot play", id);
return false;
}

if (SDL_PutAudioStreamData(manager->stream, manager->sounds[id].buffer, (int)manager->sounds[id].length) ==
false) {
SDL_Log("Failed to queue audio data for playback: %s", SDL_GetError());
return false;
}

return true;
}
31 changes: 31 additions & 0 deletions src/helpers/audio.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#ifndef AUDIO_H
#define AUDIO_H

#include <stdbool.h>
#include <stddef.h>
#include <SDL3/SDL_audio.h>

typedef enum {
SOUND_EAT_FOOD,
SOUND_COUNT // Must be last
} sound_id_t;

typedef struct {
uint8_t* buffer;
size_t length;
SDL_AudioSpec spec;
} sound_data_t;

typedef struct {
SDL_AudioStream* stream;
sound_data_t sounds[SOUND_COUNT];
bool is_initialized;
} audio_manager_t;

bool audio_manager_create(audio_manager_t* manager);
void audio_manager_destroy(audio_manager_t* manager);

bool audio_manager_load_sound(audio_manager_t* manager, sound_id_t id, const char* filepath);
bool audio_manager_play_sound(audio_manager_t* manager, sound_id_t id);

#endif // AUDIO_H
11 changes: 11 additions & 0 deletions src/snake.c
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ bool snake_create(snake_t* snake, const char* title) {
return false;
}

if (audio_manager_create(&snake->audio) == false) {
SDL_Log("Warning: Failed to initialize audio, continuing without sound");
} else {
if (audio_manager_load_sound(&snake->audio, SOUND_EAT_FOOD, "assets/sounds/bubble-pop.wav") == false) {
SDL_Log("Warning: Failed to load eating sound effect");
}
}

Uint64 seed = SDL_GetTicksNS();
seed ^= SDL_GetPerformanceCounter();
seed ^= (Uint64)(uintptr_t)snake;
Expand Down Expand Up @@ -175,6 +183,7 @@ void snake_destroy(snake_t* snake) {
snake->text_score = NULL;
}

audio_manager_destroy(&snake->audio);
window_destroy(&snake->window);

vector2i_set(&snake->position_head, 0, 0);
Expand Down Expand Up @@ -353,6 +362,8 @@ void snake_update_fixed(snake_t* snake) {

// Grow the snake if it hits array_food.
if (test_food_collision(snake) == true) {
audio_manager_play_sound(&snake->audio, SOUND_EAT_FOOD);

vector2i_t new_segment_position;
if (dynamic_array_is_empty(&snake->array_body) == true) {
new_segment_position = snake->previous_position_head;
Expand Down
2 changes: 2 additions & 0 deletions src/snake.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define SNAKE_H

#include "helpers/window.h"
#include "helpers/audio.h"
#include "utils/vector.h"
#include "utils/dynamic_array.h"

Expand Down Expand Up @@ -32,6 +33,7 @@ typedef struct {

typedef struct {
window_t window;
audio_manager_t audio;

bool is_paused;

Expand Down