Skip to content

Usage Audio

Mathew Storm edited this page Oct 5, 2025 · 1 revision

Audio

The audio system provides simple sound effect playback using SDL2's audio subsystem.

Overview

The audio module handles:

  • Audio device initialization
  • WAV file loading
  • Sound effect playback
  • Audio clip management

Audio Clip Structure

typedef struct {
    char filename[MAX_FILENAME_LENGTH];
    SDL_AudioSpec spec;
    uint32_t length;
    uint8_t* buffer;
} aAudioClip_t;

Core Functions

a_InitAudio()

int a_InitAudio(void);

Initializes the audio device. Called automatically by a_Init().

Returns:

  • 0 on success
  • 1 on failure

a_LoadSounds()

void a_LoadSounds(const char *filename, aAudioClip_t *clip);

Loads a WAV audio file into an audio clip structure.

Parameters:

  • filename - Path to the WAV file
  • clip - Pointer to audio clip structure to populate

Example:

aAudioClip_t shootSound;
a_LoadSounds("resources/sounds/shoot.wav", &shootSound);

a_PlaySoundEffect()

void a_PlaySoundEffect(aAudioClip_t *clip);

Plays a loaded audio clip.

Parameters:

  • clip - Pointer to the loaded audio clip

Example:

a_PlaySoundEffect(&shootSound);

Basic Usage

Loading and Playing Sounds

#include "Archimedes.h"

aAudioClip_t jumpSound;
aAudioClip_t coinSound;
aAudioClip_t explosionSound;

void loadAudio(void) {
    a_LoadSounds("resources/sounds/jump.wav", &jumpSound);
    a_LoadSounds("resources/sounds/coin.wav", &coinSound);
    a_LoadSounds("resources/sounds/explosion.wav", &explosionSound);
}

void logic(float dt) {
    a_DoInput();

    // Play sound on key press
    if (app.keyboard[SDL_SCANCODE_SPACE]) {
        a_PlaySoundEffect(&jumpSound);
    }
}

int main(void) {
    a_Init(SCREEN_WIDTH, SCREEN_HEIGHT, "Audio Example");

    loadAudio();

    app.delegate.logic = logic;

    while (app.running) {
        a_PrepareScene();
        app.delegate.logic(a_GetDeltaTime());
        a_PresentScene();
    }

    a_Quit();
    return 0;
}

Sound Manager Pattern

Organized Sound System

typedef struct {
    aAudioClip_t jump;
    aAudioClip_t shoot;
    aAudioClip_t hit;
    aAudioClip_t powerup;
    aAudioClip_t gameOver;
} SoundManager;

SoundManager sounds;

void initSounds(void) {
    a_LoadSounds("resources/sounds/jump.wav", &sounds.jump);
    a_LoadSounds("resources/sounds/shoot.wav", &sounds.shoot);
    a_LoadSounds("resources/sounds/hit.wav", &sounds.hit);
    a_LoadSounds("resources/sounds/powerup.wav", &sounds.powerup);
    a_LoadSounds("resources/sounds/gameover.wav", &sounds.gameOver);
}

// Usage in game
void playerJump(void) {
    a_PlaySoundEffect(&sounds.jump);
    // ... jump logic ...
}

void collectPowerup(void) {
    a_PlaySoundEffect(&sounds.powerup);
    // ... powerup logic ...
}

Advanced Patterns

Sound with Cooldown

typedef struct {
    aAudioClip_t clip;
    float cooldown;
    float cooldownTimer;
} CooldownSound;

CooldownSound shootSound;

void initCooldownSound(void) {
    a_LoadSounds("resources/sounds/shoot.wav", &shootSound.clip);
    shootSound.cooldown = 0.2f;  // 0.2 seconds between shots
    shootSound.cooldownTimer = 0.0f;
}

void updateCooldownSound(CooldownSound* snd, float dt) {
    if (snd->cooldownTimer > 0.0f) {
        snd->cooldownTimer -= dt;
    }
}

int playCooldownSound(CooldownSound* snd) {
    if (snd->cooldownTimer <= 0.0f) {
        a_PlaySoundEffect(&snd->clip);
        snd->cooldownTimer = snd->cooldown;
        return 1;  // Played
    }
    return 0;  // On cooldown
}

// In game loop
void logic(float dt) {
    updateCooldownSound(&shootSound, a_GetDeltaTime());

    if (app.keyboard[SDL_SCANCODE_SPACE]) {
        if (playCooldownSound(&shootSound)) {
            // Shoot bullet
        }
    }
}

Random Sound Variations

#define NUM_FOOTSTEP_SOUNDS 4

typedef struct {
    aAudioClip_t sounds[NUM_FOOTSTEP_SOUNDS];
    int numSounds;
} RandomSoundBank;

RandomSoundBank footsteps;

void initRandomSounds(void) {
    a_LoadSounds("resources/sounds/footstep1.wav", &footsteps.sounds[0]);
    a_LoadSounds("resources/sounds/footstep2.wav", &footsteps.sounds[1]);
    a_LoadSounds("resources/sounds/footstep3.wav", &footsteps.sounds[2]);
    a_LoadSounds("resources/sounds/footstep4.wav", &footsteps.sounds[3]);
    footsteps.numSounds = 4;
}

void playRandomSound(RandomSoundBank* bank) {
    int index = rand() % bank->numSounds;
    a_PlaySoundEffect(&bank->sounds[index]);
}

// Usage
playRandomSound(&footsteps);  // Plays random footstep sound

Event-Driven Sounds

typedef enum {
    EVENT_PLAYER_JUMP,
    EVENT_ENEMY_HIT,
    EVENT_COIN_COLLECT,
    EVENT_LEVEL_COMPLETE,
    EVENT_MAX
} GameEvent;

aAudioClip_t eventSounds[EVENT_MAX];

void initEventSounds(void) {
    a_LoadSounds("resources/sounds/jump.wav", &eventSounds[EVENT_PLAYER_JUMP]);
    a_LoadSounds("resources/sounds/hit.wav", &eventSounds[EVENT_ENEMY_HIT]);
    a_LoadSounds("resources/sounds/coin.wav", &eventSounds[EVENT_COIN_COLLECT]);
    a_LoadSounds("resources/sounds/complete.wav", &eventSounds[EVENT_LEVEL_COMPLETE]);
}

void triggerGameEvent(GameEvent event) {
    if (event >= 0 && event < EVENT_MAX) {
        a_PlaySoundEffect(&eventSounds[event]);
    }
}

// Usage
triggerGameEvent(EVENT_COIN_COLLECT);
triggerGameEvent(EVENT_PLAYER_JUMP);

Complete Example

#include "Archimedes.h"
#include <stdlib.h>

typedef struct {
    aAudioClip_t jump;
    aAudioClip_t coin;
    aAudioClip_t hurt;
} GameSounds;

GameSounds sounds;
int score = 0;
int coins[10][2];  // x, y positions

void initGame(void) {
    // Load sounds
    a_LoadSounds("resources/sounds/jump.wav", &sounds.jump);
    a_LoadSounds("resources/sounds/coin.wav", &sounds.coin);
    a_LoadSounds("resources/sounds/hurt.wav", &sounds.hurt);

    // Initialize coins
    for (int i = 0; i < 10; i++) {
        coins[i][0] = rand() % SCREEN_WIDTH;
        coins[i][1] = rand() % SCREEN_HEIGHT;
    }
}

void logic(float dt) {
    a_DoInput();

    // Jump sound
    static int spaceWasPressed = 0;
    if (app.keyboard[SDL_SCANCODE_SPACE] && !spaceWasPressed) {
        a_PlaySoundEffect(&sounds.jump);
        spaceWasPressed = 1;
    } else if (!app.keyboard[SDL_SCANCODE_SPACE]) {
        spaceWasPressed = 0;
    }

    // Coin collection (simplified)
    if (app.mouse.pressed) {
        for (int i = 0; i < 10; i++) {
            int dx = app.mouse.x - coins[i][0];
            int dy = app.mouse.y - coins[i][1];
            if (dx*dx + dy*dy < 400) {  // 20px radius
                a_PlaySoundEffect(&sounds.coin);
                score += 10;
                coins[i][0] = -100;  // Hide coin
            }
        }
    }
}

void render(float dt) {
    // Draw coins
    for (int i = 0; i < 10; i++) {
        if (coins[i][0] >= 0) {
            a_DrawFilledCircle(coins[i][0], coins[i][1], 10, yellow);
        }
    }

    // Draw score
    char scoreText[64];
    snprintf(scoreText, sizeof(scoreText), "Score: %d", score);
    a_DrawText(scoreText, 10, 10, 255, 255, 255, FONT_GAME, TEXT_ALIGN_LEFT, 0);
}

int main(void) {
    a_Init(SCREEN_WIDTH, SCREEN_HEIGHT, "Audio Example");

    initGame();

    app.delegate.logic = logic;
    app.delegate.draw = render;

    while (app.running) {
        a_PrepareScene();
        app.delegate.logic(a_GetDeltaTime());
        app.delegate.draw(a_GetDeltaTime());
        a_PresentScene();
    }

    a_Quit();
    return 0;
}

Audio Device

Device ID

SDL_AudioDeviceID app.deviceID;  // Initialized by a_InitAudio()

The audio device is automatically opened and started during initialization.

Supported Formats

Archimedes audio system uses SDL_LoadWAV() which supports:

  • WAV format (recommended)
  • Uncompressed PCM audio

For other formats (MP3, OGG), you would need additional SDL_mixer integration.

Performance Notes

  • Sounds are loaded into memory when a_LoadSounds() is called
  • Sound playback uses SDL's audio queue system
  • Multiple sounds can play simultaneously
  • Audio clips remain in memory until program exit
  • No automatic cleanup - manage audio clip lifetimes manually if needed

Common Issues

Sound Not Playing

// Check if audio initialization succeeded
if (a_InitAudio() != 0) {
    printf("Audio initialization failed!\n");
}

// Check if sound loaded successfully
a_LoadSounds("missing.wav", &clip);
// Check SDL_GetError() if no sound plays

Audio Clipping/Distortion

Too many simultaneous sounds can cause clipping. Limit concurrent sounds or use volume mixing.

Delayed Playback

SDL_QueueAudio() may have slight delay. For real-time audio, consider using callbacks (advanced).

Limitations

  • No built-in volume control (would need manual implementation)
  • No pitch shifting or audio effects
  • No music playback (use SDL_mixer for music)
  • No 3D positional audio
  • Sound clips must be manually managed (no automatic cleanup)

Best Practices

  1. Preload all sounds at startup to avoid loading delays during gameplay
  2. Use cooldowns to prevent sound spam from repeated actions
  3. Check return values to handle loading failures gracefully
  4. Keep sound files small - use short clips for sound effects
  5. Use consistent sample rates (44100 Hz recommended) for all audio files

Notes

  • Audio device is opened automatically by a_Init()
  • Audio playback starts immediately (device is unpaused)
  • The framework uses SDL's simple audio API, not SDL_mixer
  • For more advanced audio features (music, mixing, effects), consider integrating SDL_mixer

Clone this wiki locally