Skip to content

Streaming Sound

Andy Barajas edited this page Nov 10, 2024 · 1 revision

Introduction

This guide focuses on streaming sound on the Sega Dreamcast. Unlike sound effects, streamed sound is not uploaded all-at-once to the Dreamcast AICA RAM. Instead, a small buffer of up to 65536 bytes (per channel) is allocated and refilled while sound plays. Streamed sound can be mono or stereo and supports 16-bit uncompressed PCM, 8-bit PCM, or 4-bit Yamaha ADPCM. You can have up to 4 sound streams playing simultaneously, which doesn't impact sound effects.

Common libraries used to stream sound are libmp3, libogg, and libtremor from kos-ports. These libraries handle most of the work described in the API section, allowing you to skip the API details if you prefer and dive into the code examples.


API

The KOS reference manual has more details on each function, with most functions implemented in snd_stream.c.

Initialize Sound System

int snd_stream_init();

Stream Callback

typedef void*(* snd_stream_callback_t)(snd_stream_hnd_t hnd, int smp_req, int *smp_recv);

Allocate Sound Stream

snd_stream_hnd_t snd_stream_alloc(snd_stream_callback_t callback, int bufsize);

Code Example

Below is a sample code demonstrating sound streaming using libtremor for .ogg files. For more details and resources, see the Github repository.

Note: This code may not work in Sega Dreamcast emulators.

#include <kos.h>
#include <oggvorbis/sndoggvorbis.h>

#define LEFT 0
#define CENTER 128
#define RIGHT 255

#define LOOP 1
#define SHAKER_VOL 200
#define INITIAL_CRY 128
#define LOUDEST_CRY 240

static void draw_instructions(uint8_t volume);

static cont_state_t* get_cont_state();
static int button_pressed(unsigned int current_buttons, unsigned int changed_buttons, unsigned int button);

int main(int argc, char **argv) {
    uint8_t baby_volume = INITIAL_CRY;
    uint8_t shake_volume = SHAKER_VOL;

    uint64_t start_time = timer_ms_gettime64();
    uint64_t end_time = start_time;

    cont_state_t* cond;

    vid_set_mode(DM_640x480, PM_RGB555);
    snd_stream_init();
    sndoggvorbis_init();

    sfxhnd_t shake1 = snd_sfx_load("/rd/shake-1.wav");
    sfxhnd_t shake2 = snd_sfx_load("/rd/shake-2.wav");

    sndoggvorbis_volume(baby_volume);
    sndoggvorbis_start("/rd/baby-whining-01.ogg", LOOP);

    unsigned int current_buttons = 0;
    unsigned int changed_buttons = 0;
    unsigned int previous_buttons = 0;

    for(;;) {
        cond = get_cont_state();
        current_buttons = cond->buttons;
        changed_buttons = current_buttons ^ previous_buttons;
        previous_buttons = current_buttons;

        if(button_pressed(current_buttons, changed_buttons, CONT_X)) {
            snd_sfx_play(shake1, shake_volume, LEFT);
            if(baby_volume > 40)
                baby_volume -= 10;
        }
        if(button_pressed(current_buttons, changed_buttons, CONT_Y)) {
            snd_sfx_play(shake2, shake_volume, RIGHT);
            if(baby_volume > 40)
                baby_volume -= 10;
        }
        if(button_pressed(current_buttons, changed_buttons, CONT_A)) {
            if(!sndoggvorbis_isplaying()) {
                baby_volume = INITIAL_CRY;
                sndoggvorbis_volume(baby_volume);
                sndoggvorbis_start("/rd/baby-whining-01.ogg", LOOP);
            }
        }
        if(button_pressed(current_buttons, changed_buttons, CONT_B)) {
            baby_volume = 0;
            sndoggvorbis_stop();
        }
        if(button_pressed(current_buttons, changed_buttons, CONT_START))
            break;

        end_time = timer_ms_gettime64();
        if((end_time - start_time) >= 1000) {
             baby_volume += 15;
             start_time = end_time;
        }

        if(baby_volume > LOUDEST_CRY) {
            baby_volume = LOUDEST_CRY;
        }

        if(baby_volume <= 40) {
            baby_volume = 0;
            sndoggvorbis_stop();
        } else {
            sndoggvorbis_volume(baby_volume);
        }

        draw_instructions(baby_volume);
    }

    snd_sfx_unload_all();
    sndoggvorbis_stop();
    sndoggvorbis_shutdown();
    snd_stream_shutdown();

    return 0;
}

static void draw_instructions(uint8_t baby_volume) {
    int x = 20, y = 20+24;
    int color = 1;
    char baby_status[50];
    memset(baby_status, 0, 50);

    if(baby_volume == 0) {
        snprintf(baby_status, 50, "%-50s", "Baby is asleep!!! Finally...");
    }
    else if(baby_volume > 40 && baby_volume <= 90) {
        snprintf(baby_status, 50, "%-50s", "Baby is almost asleep. Keep rattling!!");
    }
    else if(baby_volume > 90 && baby_volume <= 180) {
        snprintf(baby_status, 50, "%-50s", "You can do better than that!");
    }
    else if(baby_volume > 180 && baby_volume <= LOUDEST_CRY) {
        snprintf(baby_status, 50, "%-50s", "Are you even rattling!?!?!");
    }

    bfont_draw_str_vram_fmt(x, y, true, 
        "Press X and/or Y to play rattle sounds to calm\n"
        "down the baby so it can go to sleep\n"     
        "Press A to wake the baby up\n"
        "Press B to force baby asleep\n"
        "Press Start to exit program\n\n"
        "%s");
    y += 72;
    bfont_draw_str(vram_s + y*640+x, 640, color, baby_status);
}

static cont_state_t* get_cont_state() {
    maple_device_t* cont;
    cont_state_t* state;

    cont = maple_enum_type(0, MAPLE_FUNC_CONTROLLER);
    if(cont) {
        state = (cont_state_t*)maple_dev_status(cont);
        return state;
    }

    return NULL;
}

static int button_pressed(unsigned int current_buttons, unsigned int changed_buttons, unsigned int button) {
    if(changed_buttons & button) {
        if(current_buttons & button)
            return 1;
    }

    return 0;
}

This example demonstrates the use of libtremor to play .ogg files, as well as handling sound effects.

Clone this wiki locally