/**
 * SDL_sound; An abstract sound format decoding API.
 *
 * Please see the file LICENSE.txt in the source's root directory.
 *
 *  This file written by Ryan C. Gordon.
 */

/**
 * This is a quick and dirty test of SDL_sound.
 */

#ifdef _WIN32
#define SDL_MAIN_HANDLED /* this is a console-only app */
#endif
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3_sound/SDL_sound.h>

#include <stdio.h>

#if HAVE_SIGNAL_H
#  include <signal.h>
#endif

#define DEFAULT_DECODEBUF 16384
#define DEFAULT_AUDIOBUF  4096

#define PLAYSOUND_VER_MAJOR  3
#define PLAYSOUND_VER_MINOR  2
#define PLAYSOUND_VER_PATCH  0


static const char *option_list[] =
{
    "--rate",      "n       Playback at sample rate of n HZ.",
    "--format",    "fmt   Playback in fmt format (see below).",
    "--channels",  "n   Playback on n channels (1 or 2).",
    "--decodebuf", "n  Buffer n decoded bytes at a time (default 16384).",
    "--audiobuf",  "n   Buffer n samples to audio device (default 4096).",
    "--volume",    "n     Playback volume multiplier (default 1.0).",
    "--window",    "      Open a window for playback.",
    "--version",   "     Display version information and exit.",
    "--decoders",  "    List supported data formats and exit.",
    "--predecode", "   Decode entire sample before playback.",
    "--loop",      "n       Loop playback n times.",
    "--seek",      "list    List of seek points and playback durations.",
    "--credits",   "     Shameless promotion.",
    "--help",      "        Display this information and exit.",
    NULL,          NULL
};


static void output_versions(const char *argv0)
{
    const int compiled = SDL_SOUND_VERSION;
    const int linked = Sound_Version();
    const int sdl_compiled = SDL_VERSION;
    const int sdl_linked = SDL_GetVersion();

    fprintf(stdout,
           "%s version %d.%d.%d\n"
           "Copyright 2001-2026 Ryan C. Gordon and others.\n"
           "\n"
           " Compiled against SDL_sound version %d.%d.%d,\n"
           " and linked against %d.%d.%d.\n"
           " Compiled against SDL version %d.%d.%d,\n"
           " and linked against %d.%d.%d.\n\n",
            argv0,
            PLAYSOUND_VER_MAJOR, PLAYSOUND_VER_MINOR, PLAYSOUND_VER_PATCH,
            SDL_VERSIONNUM_MAJOR(compiled),
            SDL_VERSIONNUM_MINOR(compiled),
            SDL_VERSIONNUM_MICRO(compiled),
            SDL_VERSIONNUM_MAJOR(linked),
            SDL_VERSIONNUM_MINOR(linked),
            SDL_VERSIONNUM_MICRO(linked),
            SDL_VERSIONNUM_MAJOR(sdl_compiled),
            SDL_VERSIONNUM_MINOR(sdl_compiled),
            SDL_VERSIONNUM_MICRO(sdl_compiled),
            SDL_VERSIONNUM_MAJOR(sdl_linked),
            SDL_VERSIONNUM_MINOR(sdl_linked),
            SDL_VERSIONNUM_MICRO(sdl_linked));
} /* output_versions */


static void output_decoders(void)
{
    const Sound_DecoderInfo **rc = Sound_AvailableDecoders();
    const Sound_DecoderInfo **i;
    const char **ext;

    fprintf(stdout, "Supported sound formats:\n");
    if (rc == NULL)
        fprintf(stdout, " * Apparently, NONE!\n");
    else
    {
        for (i = rc; *i != NULL; i++)
        {
            fprintf(stdout, " * %s\n", (*i)->description);

            for (ext = (*i)->extensions; *ext != NULL; ext++)
                fprintf(stdout, "   File extension \"%s\"\n", *ext);

            fprintf(stdout, "   Written by %s.\n   %s\n\n",
                    (*i)->author, (*i)->url);
        } /* for */
    } /* else */

    fprintf(stdout, "\n");
} /* output_decoders */


static void output_usage(const char *argv0)
{
    const char **i = option_list;

    fprintf(stderr,
        "USAGE: %s [...options...] [soundFile1] ... [soundFileN]\n"
        "\n"
        "   Options:\n",
            argv0);

    while (*i != NULL)
    {
        const char *option = *(i++);
        const char *optiondesc = *(i++);
        fprintf(stderr, "     %s %s\n", option, optiondesc);
    } /* while */

    fprintf(stderr,
        "\n"
        "   Valid arguments to the --format option are:\n"
        "     U8      Unsigned 8-bit.\n"
        "     S8      Signed 8-bit.\n"
        "     S16LSB  Signed 16-bit (least significant byte first).\n"
        "     S16MSB  Signed 16-bit (most significant byte first).\n"
        "     S32LSB  Signed 32-bit (least significant byte first).\n"
        "     S32MSB  Signed 32-bit (most significant byte first).\n"
        "     F32LSB  Float 32-bit (least significant byte first).\n"
        "     F32MSB  Float 32-bit (most significant byte first).\n"
        "\n"
        "   Valid arguments to the --seek options look like:\n"
        "     --seek \"mm:SS:ss;mm:SS:ss;mm:SS:ss\"\n"
        "     Where the first \"mm:SS:ss\" is the position, in minutes,\n"
        "     seconds and milliseconds to seek to at start of playback. The\n"
        "     next mm:SS:ss is how long to play audio from that point.\n"
        "     The third mm:SS:ss is another seek after the duration of\n"
        "     playback has completed. If the final playback duration is\n"
        "     omitted, playback continues until the end of the file.\n"
        "     The \"mm\" and \"SS\" portions may be omitted. --loop\n"
        "     and --seek can coexist.\n"
        "\n");
} /* output_usage */


static void output_credits(void)
{
    fprintf(stdout,
           "playsound version %d.%d.%d\n"
           "Copyright 2001-2026 Ryan C. Gordon and others.\n"
           "\n"
           "    Written by Ryan C. Gordon, Torbjörn Andersson, Max Horn,\n"
           "     Tsuyoshi Iguchi, Tyler Montbriand, Darrell Walisser,\n"
           "     and a cast of thousands.\n"
           "\n"
           "    Website and source code: https://icculus.org/SDL_sound/\n"
           "\n",
            PLAYSOUND_VER_MAJOR, PLAYSOUND_VER_MINOR, PLAYSOUND_VER_PATCH);
} /* output_credits */


static volatile int done_flag = 0;

#if HAVE_SIGNAL_H
void sigint_catcher(int signum)
{
    static Uint32 last_sigint = 0;
    Uint32 ticks = SDL_GetTicks();

    SDL_assert(signum == SIGINT);
    if (done_flag < 0)
        return;  /* mashing CTRL-C, we get it already. */

    done_flag = ((last_sigint != 0) && (ticks - last_sigint < 500)) ? -1 : 1;
    last_sigint = ticks;
} /* sigint_catcher */
#endif


/* global decoding state. */
typedef struct
{
    Uint8 *decoded_ptr;
    Uint32 decoded_bytes;
    int predecode;
    int looping;
    int wants_volume_change;
    float volume;
    Uint32 total_seeks;
    Uint32 *seek_list;
    Uint32 seek_index;
    Sint32 bytes_before_next_seek;
} playsound_global_state;

static volatile playsound_global_state global_state;


static Uint32 cvtMsToBytePos(SDL_AudioSpec *info, Uint32 ms)
{
    /* "frames" == "sample frames" */
    float frames_per_ms = ((float) info->freq) / 1000.0f;
    Uint32 frame_offset = (Uint32) (frames_per_ms * ((float) ms));
    Uint32 frame_size = (Uint32) ((info->format & 0xFF) / 8) * info->channels;
    return(frame_offset * frame_size);
} /* cvtMsToBytePos */


static void do_seek(Sound_Sample *sample)
{
    Uint32 *seek_list = global_state.seek_list;
    Uint32 seek_index = global_state.seek_index;
    Uint32 total_seeks = global_state.total_seeks;

    fprintf(stdout, "Seeking to %.2d:%.2d:%.4d...\n",
            (int) ((seek_list[seek_index] / 1000) / 60),
            (int) ((seek_list[seek_index] / 1000) % 60),
            (int) ((seek_list[seek_index] % 1000)));

    if (global_state.predecode)
    {
        Uint32 pos = cvtMsToBytePos(&sample->desired, seek_list[seek_index]);
        if (pos > sample->buffer_size)
        {
            fprintf(stderr, "Seek past end of predecoded buffer.\n");
            done_flag = 1;
        } /* if */
        else
        {
            global_state.decoded_ptr = (((Uint8 *) sample->buffer) + pos);
            global_state.decoded_bytes = sample->buffer_size - pos;
        } /* else */
    } /* if */
    else
    {
        if (!Sound_Seek(sample, seek_list[seek_index]))
        {
            fprintf(stderr, "Sound_Seek() failed: %s\n", Sound_GetError());
            done_flag = 1;
        } /* if */
    } /* else */

    seek_index++;
    if (seek_index >= total_seeks)
        global_state.bytes_before_next_seek = -1;  /* no more seeks. */
    else
    {
        global_state.bytes_before_next_seek = cvtMsToBytePos(&sample->desired,
                                                        seek_list[seek_index]);
        seek_index++;
    } /* else */

    global_state.seek_index = seek_index;
} /* do_seek */


/*
 * This updates (decoded_bytes) and (decoded_ptr) with more audio data,
 *  taking into account potential looping, seeking and predecoding.
 */
static int read_more_data(Sound_Sample *sample)
{
    if (done_flag)              /* probably a sigint; stop trying to read. */
    {
        global_state.decoded_bytes = 0;
        return(0);
    } /* if */

    if ((global_state.bytes_before_next_seek >= 0) &&
        (global_state.decoded_bytes > (Uint32)global_state.bytes_before_next_seek))
    {
        global_state.decoded_bytes = global_state.bytes_before_next_seek;
    } /* if */

    if (global_state.decoded_bytes > 0) /* don't need more data; just return. */
        return(global_state.decoded_bytes);

        /* Need more audio data. See if we're supposed to seek... */
    if ((global_state.bytes_before_next_seek == 0) &&
        (global_state.seek_index < global_state.total_seeks))
    {
        do_seek(sample);  /* do it, baby! */
        return(read_more_data(sample));  /* handle loops conditions. */
    } /* if */

        /* See if there's more to be read... */
    if ( (global_state.bytes_before_next_seek != 0) &&
         (!(sample->flags & (SOUND_SAMPLEFLAG_ERROR | SOUND_SAMPLEFLAG_EOF))) )
    {
        global_state.decoded_bytes = Sound_Decode(sample);
        if (sample->flags & SOUND_SAMPLEFLAG_ERROR)
        {
            fprintf(stderr, "Error in decoding sound file!\n"
                            "  reason: [%s].\n", Sound_GetError());
        } /* if */

        global_state.decoded_ptr = sample->buffer;
        return(read_more_data(sample));  /* handle loops conditions. */
    } /* if */

    /* No more to be read from stream, but we may want to loop the sample. */

    if (!global_state.looping)
        return(0);

    global_state.looping--;

    global_state.seek_index = 0;
    global_state.bytes_before_next_seek =
        (global_state.total_seeks > 0) ? 0 : -1;

    /* we just need to point predecoded samples to the start of the buffer. */
    if (global_state.predecode)
    {
        global_state.decoded_bytes = sample->buffer_size;
        global_state.decoded_ptr = sample->buffer;
    } /* if */
    else
    {
        Sound_Rewind(sample);  /* error is checked in recursion. */
    } /* else */

    return(read_more_data(sample));
} /* read_more_data */


static void memcpy_with_volume(Sound_Sample *sample,
                               Uint8 *dst, Uint8 *src, int len)
{
    int i;
    Sint16 *s16src = NULL;
    Sint16 *s16dst = NULL;
    Sint32 *s32src = NULL;
    Sint32 *s32dst = NULL;
    union { float f; Uint32 ui32; } floatswapper;
    float *f32dst = NULL;
    float *f32src = NULL;
    float volume = global_state.volume;

    if (!global_state.wants_volume_change)
    {
        SDL_memcpy(dst, src, len);
        return;
    } /* if */

    /* !!! FIXME: This would be more efficient with a lookup table. */
    switch (sample->desired.format)
    {
        case SDL_AUDIO_U8:
            for (i = 0; i < len; i++, src++, dst++)
                *dst = (Uint8) (((float) (*src)) * volume);
            break;

        case SDL_AUDIO_S8:
            for (i = 0; i < len; i++, src++, dst++)
                *dst = (Sint8) (((float) (*src)) * volume);
            break;

        case SDL_AUDIO_S16LE:
            s16src = (Sint16 *) src;
            s16dst = (Sint16 *) dst;
            for (i = 0; i < len; i += sizeof (Sint16), s16src++, s16dst++)
            {
                *s16dst = (Sint16) (((float) (SDL_Swap16LE(*s16src))) * volume);
                *s16dst = SDL_Swap16LE(*s16dst);
            } /* for */
            break;

        case SDL_AUDIO_S16BE:
            s16src = (Sint16 *) src;
            s16dst = (Sint16 *) dst;
            for (i = 0; i < len; i += sizeof (Sint16), s16src++, s16dst++)
            {
                *s16dst = (Sint16)(((float) (SDL_Swap16BE(*s16src))) * volume);
                *s16dst = SDL_Swap16BE(*s16dst);
            } /* for */
            break;

        case SDL_AUDIO_S32LE:
            s32src = (Sint32 *) src;
            s32dst = (Sint32 *) dst;
            for (i = 0; i < len; i += sizeof (Sint32), s32src++, s32dst++)
            {
                *s32dst = (Sint32)(((double) (SDL_Swap32LE(*s32src))) * volume);
                *s32dst = SDL_Swap32LE(*s32dst);
            } /* for */
            break;

        case SDL_AUDIO_S32BE:
            s32src = (Sint32 *) src;
            s32dst = (Sint32 *) dst;
            for (i = 0; i < len; i += sizeof (Sint32), s32src++, s32dst++)
            {
                *s32dst = (Sint32)(((double) (SDL_Swap32BE(*s32src))) * volume);
                *s32dst = SDL_Swap32BE(*s32dst);
            } /* for */
            break;

        case SDL_AUDIO_F32LE:
            f32src = (float *) src;
            f32dst = (float *) dst;
            for (i = 0; i < len; i += sizeof (float), f32src++, f32dst++)
            {
                floatswapper.f = *f32src;
                floatswapper.ui32 = SDL_Swap32LE(floatswapper.ui32);
                floatswapper.f *= volume;
                floatswapper.ui32 = SDL_Swap32LE(floatswapper.ui32);
                *f32dst = floatswapper.f;
            } /* for */
            break;

        case SDL_AUDIO_F32BE:
            f32src = (float *) src;
            f32dst = (float *) dst;
            for (i = 0; i < len; i += sizeof (float), f32src++, f32dst++)
            {
                floatswapper.f = *f32src;
                floatswapper.ui32 = SDL_Swap32BE(floatswapper.ui32);
                floatswapper.f *= volume;
                floatswapper.ui32 = SDL_Swap32BE(floatswapper.ui32);
                *f32dst = floatswapper.f;
            } /* for */
            break;

        default:
            SDL_memcpy(dst, src, len);  /* oh well. */
            break;
    } /* switch */
} /* memcpy_with_volume */


static void SDLCALL audio_callback(void *userdata, Uint8 *stream, int len)
{
    Sound_Sample *sample = (Sound_Sample *) userdata;
    int bw = 0; /* bytes written to stream this time through the callback */

    while (bw < len)
    {
        int cpysize;  /* bytes to copy on this iteration of the loop. */

        if (!read_more_data(sample)) /* read more data, if needed. */
        {
            /* ...there isn't any more data to read! */
            SDL_memset(stream + bw, '\0', len - bw);
            done_flag = 1;
            return;
        } /* if */

        /* decoded_bytes and decoder_ptr are updated as necessary... */

        cpysize = len - bw;
        if (cpysize > (Sint32)global_state.decoded_bytes)
            cpysize = (Sint32)global_state.decoded_bytes;

        if (cpysize > 0)
        {
            memcpy_with_volume(sample, stream + bw,
                               (Uint8 *) global_state.decoded_ptr,
                               cpysize);

            bw += cpysize;
            global_state.decoded_ptr += cpysize;
            global_state.decoded_bytes -= cpysize;
            if (global_state.bytes_before_next_seek >= 0)
                global_state.bytes_before_next_seek -= cpysize;
        } /* if */
    } /* while */
} /* audio_callback */

void SDLCALL sdl3_audio_callback(void *userdata, SDL_AudioStream *stream, int additional_amount, int total_amount)
{
    if (additional_amount > 0)
    {
        Uint8 *data = SDL_stack_alloc(Uint8, additional_amount);
        if (data)
        {
            audio_callback(userdata, data, additional_amount);
            SDL_PutAudioStreamData(stream, data, additional_amount);
            SDL_stack_free(data);
        }
    }
}

static int count_seek_list(const char *list)
{
    const char *ptr;
    int retval = 0;

    for (ptr = list; ptr != NULL; ptr = SDL_strchr(ptr + 1, ';'))
        retval++;

    return(retval);
} /* count_seek_list */


static Uint32 parse_time_str(char *str)
{
    Uint32 minutes = 0;
    Uint32 seconds = 0;
    Uint32 ms = 0;

    char *ptr = SDL_strchr(str, ':');
    if (ptr != NULL)
    {
        char *ptr2;

        *ptr = '\0';
        ptr2 = SDL_strchr(ptr + 1, ':');
        if (ptr2 != NULL)
        {
            *ptr2 = '\0';
            minutes = SDL_atoi(str);
            str = ptr + 1;
            ptr = ptr2;
        } /* if */

        seconds = SDL_atoi(str);
        str = ptr + 1;
    } /* if */

    ms = SDL_atoi(str);
    return( (((minutes * 60) + seconds) * 1000) + ms );
} /* parse_time_str */


static void parse_seek_list(const char *_list)
{
    Uint32 i;

    const size_t listlen = SDL_strlen(_list) + 1;
    char *list = (char*) SDL_malloc(listlen);
    char *save_list = list;
    if (list == NULL)
    {
        fprintf(stderr, "malloc() failed. Skipping seek list.\n");
        return;
    } /* if */

    SDL_strlcpy(list, _list, listlen);

    if (global_state.seek_list != NULL)
        SDL_free((void *) global_state.seek_list);

    global_state.total_seeks = count_seek_list(list);

    global_state.seek_list =
              (Uint32 *) SDL_malloc(global_state.total_seeks * sizeof (Uint32));

    if (global_state.seek_list == NULL)
    {
        fprintf(stderr, "malloc() failed. Skipping seek list.\n");
        global_state.total_seeks = 0;
        return;
    } /* if */

    for (i = 0; i < global_state.total_seeks; i++)
    {
        char *ptr = SDL_strchr(list, ';');
        if (ptr != NULL)
            *ptr = '\0';
        global_state.seek_list[i] = parse_time_str(list);
        list = ptr + 1;
    } /* for */

    global_state.bytes_before_next_seek = 0;

    SDL_free(save_list);
} /* parse_seek_list */


static int str_to_fmt(char *str)
{
    if (SDL_strcmp(str, "U8") == 0)
        return SDL_AUDIO_U8;
    if (SDL_strcmp(str, "S8") == 0)
        return SDL_AUDIO_S8;
    if (SDL_strcmp(str, "S16LSB") == 0)
        return SDL_AUDIO_S16LE;
    if (SDL_strcmp(str, "S16MSB") == 0)
        return SDL_AUDIO_S16BE;
    if (SDL_strcmp(str, "S32LSB") == 0)
        return SDL_AUDIO_S32LE;
    if (SDL_strcmp(str, "S32MSB") == 0)
        return SDL_AUDIO_S32BE;
    if (SDL_strcmp(str, "F32LSB") == 0)
        return SDL_AUDIO_F32LE;
    if (SDL_strcmp(str, "F32MSB") == 0)
        return SDL_AUDIO_F32BE;
    return 0;
} /* str_to_fmt */


static int valid_cmdline(int argc, char **argv)
{
    int i;

    if (argc < 2)  /* no command line? Show help text and quit. */
    {
        output_usage(argv[0]);
        return(0);
    } /* if */

    /* Make sure all command line options are valid. */
    for (i = 1; i < argc; i++)
    {
        const char **opts = option_list;

        if (SDL_strncmp(argv[i], "--", 2) != 0)  /* not an option; skip it. */
            continue;

        while (*opts != NULL)
        {
            if (SDL_strcmp(argv[i], *(opts++)) == 0)
                break;

            opts++;  /* skip option description. */
        } /* else */

        if (*opts == NULL)  /* didn't find it in option_list... */
        {
            fprintf(stderr, "unknown option: \"%s\"\n", argv[i]);
            return(0);
        } /* if */
    } /* for */

    return(1);  /* everything appears to be in order. */
} /* valid_cmdline */


static void report_filename(const char *filename)
{
    const char *icon = "playsound";
    char *ptr = NULL;

    ptr = SDL_strrchr(filename, '/');
    if (ptr != NULL)
        filename = ptr + 1;
    ptr = SDL_strrchr(filename, '\\');
    if (ptr != NULL)
        filename = ptr + 1;

    /* SDL2's PulseAudio backend picks up these hints. */
    SDL_SetHint("SDL_AUDIO_DEVICE_APP_NAME", icon);
    SDL_SetHint("SDL_AUDIO_DEVICE_STREAM_NAME", filename);

    fprintf(stdout, "%s: Now playing [%s]...\n", icon, filename);
    fflush(stdout);
} /* report_filename */


int main(int argc, char **argv)
{
    SDL_AudioSpec sound_desired;
    SDL_AudioSpec sdl_desired;
    Uint32 audio_buffersize = DEFAULT_AUDIOBUF;
    Uint32 decode_buffersize = DEFAULT_DECODEBUF;
    Sound_Sample *sample;
    int use_specific_audiofmt = 0;
    int i;
    int delay;
    int new_sample = 1;
    SDL_Window *window = NULL;
    SDL_Renderer *renderer = NULL;
    Uint32 sdl_init_flags = SDL_INIT_AUDIO;

    #ifdef HAVE_SETBUF
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
    #endif

    if (!valid_cmdline(argc, argv))
        return(42);

    /* Handle some command lines upfront. */
    for (i = 1; i < argc; i++)
    {
        if (SDL_strncmp(argv[i], "--", 2) != 0)
            continue;

        if (SDL_strcmp(argv[i], "--version") == 0)
        {
            output_versions(argv[0]);
            return(42);
        } /* if */

        if (SDL_strcmp(argv[i], "--credits") == 0)
        {
            output_credits();
            return(42);
        } /* if */

        else if (SDL_strcmp(argv[i], "--help") == 0)
        {
            output_usage(argv[0]);
            return(42);
        } /* if */

        else if (SDL_strcmp(argv[i], "--decoders") == 0)
        {
            if (!Sound_Init())
            {
                fprintf(stderr, "Sound_Init() failed!\n"
                                "  reason: [%s].\n", Sound_GetError());
                SDL_Quit();
                return(42);
            } /* if */

            output_decoders();
            Sound_Quit();
            return(0);
        } /* if */

        else if (SDL_strcmp(argv[i], "--window") == 0)
        {
            sdl_init_flags |= SDL_INIT_VIDEO;
        } /* if */
    } /* for */

    if (!SDL_Init(sdl_init_flags))
    {
        fprintf(stderr, "SDL_Init() failed!\n"
                        "  reason: [%s].\n", SDL_GetError());
        return(42);
    } /* if */

    if (!Sound_Init())
    {
        fprintf(stderr, "Sound_Init() failed!\n"
                        "  reason: [%s].\n", Sound_GetError());
        SDL_Quit();
        return(42);
    } /* if */

    #if HAVE_SIGNAL_H
    signal(SIGINT, sigint_catcher);
    #endif

    if (sdl_init_flags & SDL_INIT_VIDEO) {
        window = SDL_CreateWindow("playsound", 320, 240, 0);
        if (window == NULL) {
            fprintf(stderr, "SDL_CreateWindow() failed!\n"
                            "  reason: [%s].\n"
                            "Going on without a window.\n", SDL_GetError());
        } else {
            /* some video targets need renderers or they won't work. Make one just in case. */
            renderer = SDL_CreateRenderer(window, NULL);
            if (!renderer) {
                fprintf(stderr, "SDL_CreateRenderer() failed!\n"
                                "  reason: [%s].\n"
                                "Going on without a renderer.\n", SDL_GetError());
            }
        }
    }

    SDL_memset(&sound_desired, '\0', sizeof (SDL_AudioSpec));

    for (i = 1; i < argc; i++)
    {
        const char *filename = NULL;
        SDL_AudioStream *stream;

        /* set variables back to defaults for next file... */
        if (new_sample)
        {
            if (global_state.seek_list != NULL)
                SDL_free((void *) global_state.seek_list);

            SDL_memset((void *) &global_state, '\0', sizeof (global_state));
            SDL_memset(&sdl_desired, '\0', sizeof (SDL_AudioSpec));
            global_state.volume = 1.0;
            global_state.bytes_before_next_seek = -1;
            audio_buffersize = DEFAULT_AUDIOBUF;
            decode_buffersize = DEFAULT_DECODEBUF;
            new_sample = 0;
        } /* if */

        if (SDL_strcmp(argv[i], "--rate") == 0 && argc > i + 1)
        {
            Sint32 r;
            use_specific_audiofmt = 1;
            r = SDL_atoi(argv[++i]);
            if (r <= 0)
            {
                fprintf(stderr, "Bad argument to --rate!\n");
                return(42);
            } /* if */
            sound_desired.freq = (Uint32)r;
        } /* else if */

        else if (SDL_strcmp(argv[i], "--format") == 0 && argc > i + 1)
        {
            use_specific_audiofmt = 1;
            sound_desired.format = str_to_fmt(argv[++i]);
            if (sound_desired.format == 0)
            {
                fprintf(stderr, "Bad argument to --format! Try one of:\n"
                                "U8, S8, S16LSB, S16MSB, S32LSB, S32MSB, F32LSB, L32MSB\n");
                return(42);
            } /* if */
        } /* else if */

        else if (SDL_strcmp(argv[i], "--channels") == 0 && argc > i + 1)
        {
            use_specific_audiofmt = 1;
            sound_desired.channels = SDL_atoi(argv[++i]);
            if (sound_desired.channels < 1 || sound_desired.channels > 2)
            {
                fprintf(stderr,
                        "Bad argument to --channels! Try 1 (mono) or 2 "
                        "(stereo).\n");
                return(42);
            } /* if */
        } /* else if */

        else if (SDL_strcmp(argv[i], "--audiobuf") == 0 && argc > i + 1)
        {
            audio_buffersize = SDL_atoi(argv[++i]);
        } /* else if */

        else if (SDL_strcmp(argv[i], "--decodebuf") == 0 && argc > i + 1)
        {
            decode_buffersize = SDL_atoi(argv[++i]);
        } /* else if */

        else if (SDL_strcmp(argv[i], "--volume") == 0 && argc > i + 1)
        {
            global_state.volume = (float)SDL_atof(argv[++i]);
            if (global_state.volume != 1.0f)
                global_state.wants_volume_change = 1;
        } /* else if */

        else if (SDL_strcmp(argv[i], "--predecode") == 0)
        {
            global_state.predecode = 1;
        } /* else if */

        else if (SDL_strcmp(argv[i], "--loop") == 0 && argc > i + 1)
        {
            global_state.looping = SDL_atoi(argv[++i]);
        } /* else if */

        else if (SDL_strcmp(argv[i], "--seek") == 0)
        {
            parse_seek_list(argv[++i]);
        } /* else if */

        else if (SDL_strncmp(argv[i], "--", 2) == 0)
        {
            /* ignore it, since it was handled at startup. */
        } /* else if */

        else
        {
            filename = argv[i];
            sample = Sound_NewSampleFromFile(filename,
                            use_specific_audiofmt ? &sound_desired : NULL,
                            decode_buffersize);
        } /* else */

        if (filename == NULL) /* still parsing command line stuff? */
            continue;

        new_sample = 1;

        if (sample == NULL)
        {
            fprintf(stderr, "Couldn't load \"%s\"!\n"
                            "  reason: [%s].\n",
                            filename, Sound_GetError());
            continue;
        } /* if */

        if (global_state.total_seeks > 0)
        {
            if ((!global_state.predecode) &&
                (!(sample->flags & SOUND_SAMPLEFLAG_CANSEEK)))
            {
                fprintf(stderr, "Want seeks, but sample cannot handle it!\n");
                Sound_FreeSample(sample);
                continue;
            } /* if */
        } /* if */

            /*
             * Unless explicitly specified, pick the format from the sound
             * to be played.
             */
        if (use_specific_audiofmt)
        {
            sdl_desired.freq = sample->desired.freq;
            sdl_desired.format = sample->desired.format;
            sdl_desired.channels = sample->desired.channels;
        } /* if */
        else
        {
            sdl_desired.freq = sample->actual.freq;
            sdl_desired.format = sample->actual.format;
            sdl_desired.channels = sample->actual.channels;
        } /* else */

        /* grr, SDL_CloseAudio() calls SDL_QuitSubSystem internally. */
        if (!SDL_WasInit(SDL_INIT_AUDIO))
        {
            if (!SDL_Init(SDL_INIT_AUDIO))
            {
                fprintf(stderr, "SDL_Init() failed!\n"
                                "  reason: [%s].\n", SDL_GetError());
                Sound_Quit();
                SDL_Quit();
                return(42);
            } /* if */
        } /* if */

        report_filename(filename);

        stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &sdl_desired, sdl3_audio_callback, sample);
        if (stream == NULL)
        {
            fprintf(stderr, "Couldn't open audio device!\n"
                            "  reason: [%s].\n", SDL_GetError());
            Sound_Quit();
            SDL_Quit();
            return(42);
        } /* if */

        if (global_state.predecode)
        {
            fprintf(stdout, "  predecoding..."); fflush(stdout);
            global_state.decoded_bytes = Sound_DecodeAll(sample);
            global_state.decoded_ptr = sample->buffer;
            if (sample->flags & SOUND_SAMPLEFLAG_ERROR)
            {
                fprintf(stderr,
                        "Couldn't fully decode \"%s\"!\n"
                        "  reason: [%s].\n"
                        "  (playing first %lu bytes of decoded data...)\n",
                        filename, Sound_GetError(),
                        (unsigned long) global_state.decoded_bytes);
            } /* if */
            else
            {
                fprintf(stdout, "done.\n");
            } /* else */
            fflush(stdout);
        } /* if */

        SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream));

        done_flag = 0;  /* the audio callback will flip this flag. */
        while (!done_flag)
        {
            if (window) {
                SDL_Event event;
                SDL_PollEvent(&event);
                if (event.type == SDL_EVENT_KEY_DOWN || event.type == SDL_EVENT_QUIT)
                    done_flag = 1;

                /* some video targets need renderers or they won't work. Just clear and present here. */
                if (renderer) {
                    SDL_RenderClear(renderer);
                    SDL_RenderPresent(renderer);
                }
            }

            SDL_Delay(10);
        } /* while */

        SDL_PauseAudioDevice(SDL_GetAudioStreamDevice(stream));

            /*
             * Sleep two buffers' worth of audio before closing, in order
             *  to allow the playback to finish. This isn't always enough;
             *   perhaps SDL needs a way to explicitly wait for device drain?
             */
        delay = 2 * 1000 * audio_buffersize / sdl_desired.freq;
        SDL_Delay(delay);

        SDL_CloseAudioDevice(SDL_GetAudioStreamDevice(stream)); /* reopen with next sample's format if possible */
        Sound_FreeSample(sample);

        if (done_flag < 0)
            break;
    } /* for */

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    Sound_Quit();
    SDL_Quit();
    return((done_flag < 0) ? 1 : 0);
} /* main */

/* end of playsound.c ... */

