/*
Copyright 2004-2011 Tom Rothamel <pytom@bishoujo.us>

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#include "renpysound_core.h"
#include <Python.h>
#include <SDL.h>
#include <SDL_thread.h>
#include <stdio.h>
#include <string.h>
#include <pygame_sdl2/pygame_sdl2.h>

SDL_mutex *name_mutex;

#ifdef __EMSCRIPTEN__

#define LOCK_AUDIO() { }
#define UNLOCK_AUDIO() { }

#define LOCK_NAME() { }
#define UNLOCK_NAME() { }

#else

/* These prevent the audio callback from running when held. Use this to
   prevent the audio callback from running while the state of the audio
   system is being changed. */
#define LOCK_AUDIO() { SDL_LockAudio(); }
#define UNLOCK_AUDIO() { SDL_UnlockAudio(); }

/* This is held while the current track is being changed by the audio callback,
   and can also be held to the current track doesn't change while things are
   being processed. */
#define LOCK_NAME() { SDL_LockMutex(name_mutex); }
#define UNLOCK_NAME() { SDL_UnlockMutex(name_mutex); }

#endif

/* Declarations of ffdecode functions. */
struct MediaState;
typedef struct MediaState MediaState;

void media_init(int rate, int status, int equal_mono);

void media_advance_time(void);
void media_sample_surfaces(SDL_Surface *rgb, SDL_Surface *rgba);
MediaState *media_open(SDL_RWops *, const char *);
void media_want_video(MediaState *, int);
void media_start_end(MediaState *, double, double);
void media_start(MediaState *);
void media_pause(MediaState *, int);
void media_close(MediaState *);

int media_read_audio(struct MediaState *is, Uint8 *stream, int len);

int media_video_ready(struct MediaState *ms);
SDL_Surface *media_read_video(struct MediaState *ms);

double media_duration(struct MediaState *ms);
void media_wait_ready(struct MediaState *ms);

/* Min and Max */
#define min(a, b) (((a) < (b)) ? (a) : (b))
#define max(a, b) (((a) > (b)) ? (a) : (b))

/* Various error codes. */
#define SUCCESS 0
#define SDL_ERROR -1
#define SOUND_ERROR -2
#define RPS_ERROR -3

/* This is called with the appropriate error code at the end of a
 * function. */
#define error(err) RPS_error = err
int RPS_error = SUCCESS;
static const char *error_msg = NULL;

/* Have we been initialized? */
static int initialized = 0;

/** Should fades be linear rather than logarithmic? */
static int linear_fades = 0;


struct Interpolate {
    /* The number of samples that are finished so far. */
    unsigned int done;

    /* The duration of the interpolation, in samples. */
    unsigned int duration;

    /* The starting value. */
    float start;

    /* The ending value. */
    float end;
};


void init_interpolate(struct Interpolate *i, float value) {
    i->done = 0;
    i->duration = 0;
    i->start = value;
    i->end = value;
}

static inline float lerp(float start, float end, float done) {
    return start + (end - start) * done;
}

static inline void tick_interpolate(struct Interpolate *i) {
    if (i->done < i->duration) {
        i->done += 1;
    }
}

static inline float get_interpolate(struct Interpolate *i) {
    if (i->done >= i->duration) {
        return i->end;
    } else {
        return lerp(i->start, i->end, (float) i->done / (float) i->duration);
    }
}


// The power corresponding to a magnitude of 0.0.
#define MIN_POWER 0.0

// The power corresponding to a magnitude of 1.0. This controls the
// range in dB that fades happen over. The formula for this is
// fade_range_in_db = 20 * math.log10(2 ** MAX_POWER).
//
// The 6 corresponds to a 36.12 dB range.
#define MAX_POWER 6

/**
 * This interpolates a logarithmic power level to a magnitude.
 *
 * The units of the log power level are odd, to make things faster - it's
 * log2f(power) + MAX_POWER, to make calculations faster.
 */
static inline float get_interpolate_power(struct Interpolate *i) {

    float log_power = get_interpolate(i);

    if (linear_fades) {
        return log_power / MAX_POWER;
    }

    if (log_power == MIN_POWER) {
        return 0;
    } else if (log_power == MAX_POWER) {
        return 1.0;
    } else {
        return powf(2, log_power - MAX_POWER);
    }
}

/**
 * This converts a magnitude to a logarithmic power level.
 */
static inline float log_power(float power) {

    if (linear_fades) {
        return power * MAX_POWER;
    }

    if (power <= 0.0) {
        return MIN_POWER;
    } else if (power >= 1.0) {
        return MAX_POWER;
    } else {
        return log2f(power) + MAX_POWER;
    }
}


/*
 * This structure represents a channel the system knows about
 * and can play from.
 */
struct Channel {

    /* The currently playing stream, NULL if this sample isn't playing
       anything. */
    struct MediaState *playing;

    /* The name of the playing stream. */
    char *playing_name;

    /* The number of ms to take to fade in the playing stream. */
    int playing_fadein;

    /* Is the playing stream tight? */
    int playing_tight;

    /* The start time of the playing stream, in ms. */
    int playing_start_ms;

    /* The relative volume of the playing stream. */
    float playing_relative_volume;

    /* The queued up stream. */
    struct MediaState *queued;

    /* The name of the queued up stream. */
    char *queued_name;

    /* The number of ms to take to fade in the queued stream. */
    int queued_fadein;

    /* Is the queued stream tight? */
    int queued_tight;

    /* The start time of the queued stream, in ms. */
    int queued_start_ms;

    /* The relative volume of the queued stream. */
    float queued_relative_volume;

    /* Is this channel paused? */
    int paused;

    /* The user set mixer volume. */
    float mixer_volume;

    /* The channel specific volume. */
    struct Interpolate secondary_volume;

    /* The position (in samples) that this channel has queued to. */
    int pos;

    /* Information about the current fading. */
    struct Interpolate fade;

    /* The number of samples in which we'll stop. */
    int stop_samples;

    /* The event posted to the queue when we finish the playing stream. */
    int event;

    /* The pan of the channel. */
    struct Interpolate pan;

    /* This is set to 1 if this is a movie channel with dropping, 2 if it's a
     * video channel without dropping. */
    int video;

    /**
     * Was this playing the last time we checked?
     */
    int last_playing;

    /**
     * The last volume.
     */
    float last_volume;

};

struct Dying {
    struct MediaState *stream;
    struct Dying *next;
};

static struct Dying *dying = NULL;

/*
 * The number of channels the system knows about.
 */
int num_channels = 0;

/*
 * All of the channels that the system knows about.
 */
struct Channel *channels = NULL;

/*
 * The spec of the audio that is playing.
 */
SDL_AudioSpec audio_spec;

static int ms_to_samples(int ms) {
    return ((long long) ms) * audio_spec.freq / 1000;
}

static int samples_to_ms(int samples) {
    return ((long long) samples) * 1000 / audio_spec.freq;
}

static void start_stream(struct Channel* c, int reset_fade) {

    if (!c) return;

    c->pos = 0;

    if (reset_fade) {

        c->fade.start = MIN_POWER;
        c->fade.end = MAX_POWER;
        c->fade.done = 0;
        c->fade.duration = ms_to_samples(c->playing_fadein);

        c->stop_samples = -1;
    }
}

static void free_stream(struct MediaState *ss) {
    media_close(ss);
}


#define MAX_SHORT (32767)
#define MIN_SHORT (-32768)



static void post_event(struct Channel *c) {
    if (! c->event) {
        return;
    }

    SDL_Event e;
    memset(&e, 0, sizeof(e));
    e.type = c->event;
    SDL_PushEvent(&e);
}

#define PI 3.14159265358979323846
#define ZERO_PAN 0.7071067811865476 // cos(PI / 4) and sin(PI / 4)


static inline void mix_sample(struct Channel *c, short left_in, short right_in, float *left_out, float *right_out) {

    tick_interpolate(&c->fade);
    tick_interpolate(&c->secondary_volume);
    tick_interpolate(&c->pan);

    float left = left_in / 1.0 / -MIN_SHORT;
    float right = right_in / 1.0 / -MIN_SHORT;

    float pan = get_interpolate(&c->pan);

    if (pan == 0.0) {
        left *= ZERO_PAN;
        right *= ZERO_PAN;
    } else {
        float theta = PI * (pan + 1) / 4;
        left *= cosf(theta);
        right *= sinf(theta);
    }

    float target_volume = get_interpolate_power(&c->fade) * get_interpolate_power(&c->secondary_volume) * c->playing_relative_volume * c->mixer_volume;
    float volume = c->last_volume + .01 * (target_volume - c->last_volume);

    if (!c->last_playing) {
        volume = target_volume;
    }

    *left_out += left * volume;
    *right_out += right * volume;

    c->last_volume = volume;
}


/** If not NULL, this can be replaced with a function that will be called
    to generate audio. The functtio is called with a consistion of 2*length
    shorts, and should fill the buffer with audio data. */
void (*RPS_generate_audio_c_function)(float *stream, int length) = NULL;


static void callback(void *userdata, Uint8 *stream, int length) {

    // Convert the length to samples.
    length /= 4;

    float mix_buffer[length * 2];
    short stream_buffer[length * 2];

    memset(mix_buffer, 0, length * 2 * sizeof(float));

    if (RPS_generate_audio_c_function) {
        RPS_generate_audio_c_function(mix_buffer, length);
    }

    for (int channel = 0; channel < num_channels; channel++) {

        // The number of samples that have been mixed.
        int mixed = 0;

        struct Channel *c = &channels[channel];

        if (! c->playing || c->paused) {
            c->last_playing = 0;
            continue;
        }

        while (mixed < length && c->playing) {

            // How much do we have left to mix on this channel?
            int mixleft = length - mixed;

            // The number of samples that we read.
            int read_length;

            // Decode some amount of data.
            read_length = media_read_audio(c->playing, (Uint8 *) stream_buffer, mixleft * 2 * sizeof(short));
            read_length /= (2 * sizeof(short));

            // If we're done with this stream, skip to the next.
            if (c->stop_samples == 0 || read_length == 0) {

                int old_tight = c->playing_tight;
                struct Dying *d;

                post_event(c);

                LOCK_NAME()

                d = malloc(sizeof(struct Dying));
                d->next = dying;
                d->stream = c->playing;
                dying = d;

                free(c->playing_name);

                c->playing = c->queued;
                c->playing_name = c->queued_name;
                c->playing_fadein = c->queued_fadein;
                c->playing_tight = c->queued_tight;
                c->playing_start_ms = c->queued_start_ms;
                c->playing_relative_volume = c->queued_relative_volume;

                c->queued = NULL;
                c->queued_name = NULL;
                c->queued_fadein = 0;
                c->queued_tight = 0;
                c->queued_start_ms = 0;
                c->queued_relative_volume = 1.0;

                if (c->playing_fadein) {
                    old_tight = 0;
                }

                UNLOCK_NAME()

                start_stream(c, !old_tight);

                continue;
            }

            // We have some data in the buffer, so mix it.
            for (int i = 0; (i < read_length) && c->stop_samples; i++) {

                mix_sample(c, stream_buffer[i * 2], stream_buffer[i * 2 + 1], &mix_buffer[mixed * 2], &mix_buffer[mixed * 2 + 1]);

                if (c->stop_samples > 0) {
                    c->stop_samples--;
                }

                c->pos++;
                mixed += 1;
            }

        }

        c->last_playing = 1;
    }

    // Actually output the sound.
    for (int i = 0; i < length; i++) {
        int left = mix_buffer[i * 2] * MAX_SHORT;
        int right = mix_buffer[i * 2 + 1] * MAX_SHORT;

        if (left > MAX_SHORT) {
            left = MAX_SHORT;
        }
        if (left < MIN_SHORT) {
            left = MIN_SHORT;
        }
        if (right > MAX_SHORT) {
            right = MAX_SHORT;
        }
        if (right < MIN_SHORT) {
            right = MIN_SHORT;
        }

        ((short *) stream)[i * 2] = left;
        ((short *) stream)[i * 2 + 1] = right;
    }


}


/*
 * Checks that the given channel is in range. Returns 0 if it is,
 * sets an error and returns -1 if it is not. Allocates channels
 * that don't already exist.
 */
static int check_channel(int c) {
    int i;

    if (c < 0) {
        error(RPS_ERROR);
        error_msg = "Channel number out of range.";
        return -1;
    }

    if (c >= num_channels) {
        struct Channel *extended_channels = realloc(channels, sizeof(struct Channel) * (c + 1));
        if (extended_channels == NULL) {
            error(RPS_ERROR);
            error_msg = "Unable to allocate additional channels.";
            return -1;
        }
        channels = extended_channels;

        for (i = num_channels; i <= c; i++) {

        	memset(&channels[i], 0, sizeof(struct Channel));

            channels[i].mixer_volume = 1.0;
            channels[i].paused = 1;
            channels[i].event = 0;

            init_interpolate(&channels[i].fade, MAX_POWER);
            init_interpolate(&channels[i].secondary_volume, MAX_POWER);
            init_interpolate(&channels[i].pan, 0.0);
        }

        num_channels = c + 1;
    }

    return 0;
}


/*
 * Loads the provided stream. Returns the stream on success, NULL on
 * failure.
 */
struct MediaState *load_stream(SDL_RWops *rw, const char *ext, double start, double end, int video) {
    struct MediaState *rv;
    rv = media_open(rw, ext);
    if (rv == NULL)
    {
        return NULL;
    }
    media_start_end(rv, start, end);

    if (video) {
        media_want_video(rv, video);
    }

    media_start(rv);
    return rv;
}


void RPS_play(int channel, SDL_RWops *rw, const char *ext, const char *name, int fadein, int tight, int paused, double start, double end, float relative_volume) {

    struct Channel *c;

    if (check_channel(channel)) {
        return;
    }

    c = &channels[channel];

    LOCK_AUDIO();

    /* Free playing and queued samples. */
    if (c->playing) {
        free_stream(c->playing);
        c->playing = NULL;
        free(c->playing_name);
        c->playing_name = NULL;
        c->playing_tight = 0;
        c->playing_start_ms = 0;
        c->playing_relative_volume = 1.0;
    }

    if (c->queued) {
        free_stream(c->queued);
        c->queued = NULL;
        free(c->queued_name);
        c->queued_name = NULL;
        c->queued_tight = 0;
        c->queued_start_ms = 0;
        c->queued_relative_volume = 1.0;
    }

    /* Allocate playing sample. */

    c->playing = load_stream(rw, ext, start, end, c->video);

    if (! c->playing) {
        UNLOCK_AUDIO();
        error(SOUND_ERROR);
        return;
    }

    c->playing_name = strdup(name);
    c->playing_fadein = fadein;
    c->playing_tight = tight;
    c->playing_start_ms = (int) (start * 1000);
    c->playing_relative_volume = relative_volume;

    c->paused = paused;

    start_stream(c, 1);

    UNLOCK_AUDIO();

    error(SUCCESS);
}

void RPS_queue(int channel, SDL_RWops *rw, const char *ext, const char *name, int fadein, int tight, double start, double end, float relative_volume) {

    struct Channel *c;

    if (check_channel(channel)) {
        return;
    }

    c = &channels[channel];

    /* If we're not playing, then we should play instead of queue. */
    if (!c->playing) {
        RPS_play(channel, rw, ext, name, fadein, tight, 0, start, end, relative_volume);
        return;
    }

    MediaState *ms = load_stream(rw, ext, start, end, c->video);

    LOCK_AUDIO();

    /* Free queued sample. */

    if (c->queued) {
        free_stream(c->queued);
        c->queued = NULL;
        free(c->queued_name);
        c->queued_name = NULL;
        c->queued_tight = 0;
    }

    /* Allocate queued sample. */
    c->queued = ms;

    if (! c->queued) {
        UNLOCK_AUDIO();

        error(SOUND_ERROR);
        return;
    }

    c->queued_name = strdup(name);
    c->queued_fadein = fadein;
    c->queued_tight = tight;

    c->queued_start_ms = (int) (start * 1000);
    c->queued_relative_volume = relative_volume;

    UNLOCK_AUDIO();

    error(SUCCESS);
}


/*
 * Stops all music from playing, freeing the data used by the
 * music.
 */
void RPS_stop(int channel) {

    struct Channel *c;

    if (check_channel(channel)) {
        return;
    }

    c = &channels[channel];

    LOCK_AUDIO();

    if (c->playing) {
        post_event(c);
    }

    /* Free playing and queued samples. */
    if (c->playing) {
        free_stream(c->playing);
        c->playing = NULL;
        free(c->playing_name);
        c->playing_name = NULL;
        c->playing_start_ms = 0;
        c->playing_relative_volume = 1.0;
    }

    if (c->queued) {
        free_stream(c->queued);
        c->queued = NULL;
        free(c->queued_name);
        c->queued_name = NULL;
        c->queued_start_ms = 0;
        c->queued_relative_volume = 1.0;
    }

    UNLOCK_AUDIO();

    error(SUCCESS);
}

/*
 * This dequeues the queued sound from the supplied channel, if
 * such a sound is queued. This does nothing to the playing
 * sound.
 *
 * This does nothing if the playing sound is tight, ever_tight is
 * false.
 */
void RPS_dequeue(int channel, int even_tight) {

    struct Channel *c;

    if (check_channel(channel)) {
        return;
    }

    c = &channels[channel];

    LOCK_AUDIO();

    if (c->queued && (! c->playing_tight || even_tight)) {
        free_stream(c->queued);
        c->queued = NULL;
        free(c->queued_name);
        c->queued_name = NULL;
    } else {
        c->queued_tight = 0;
    }

    c->queued_start_ms = 0;

    UNLOCK_AUDIO();

    error(SUCCESS);
}

/*
 * Returns the queue depth of the current channel. This is 0 if we're
 * stopped, 1 if there's something playing but nothing queued, and 2
 * if there's both something playing and something queued.
 */
int RPS_queue_depth(int channel) {
    int rv = 0;

    struct Channel *c;

    if (check_channel(channel)) {
        return 0;
    }

    c = &channels[channel];

    LOCK_NAME();

    if (c->playing) rv++;
    if (c->queued) rv++;

    UNLOCK_NAME();

    error(SUCCESS);

    return rv;
}

PyObject *RPS_playing_name(int channel) {
    PyObject *rv;
    struct Channel *c;

    if (check_channel(channel)) {
        Py_INCREF(Py_None);
        return Py_None;
    }

    c = &channels[channel];

    LOCK_NAME();

    if (c->playing_name) {
        rv = PyBytes_FromString(c->playing_name);
    } else {
        Py_INCREF(Py_None);
        rv = Py_None;
    }

    UNLOCK_NAME();

    error(SUCCESS);

    return rv;
}

/*
 * Causes the given channel to fadeout playing after a specified
 * number of milliseconds. The playing sound stops once the
 * fadeout finishes (a queued sound may then start at full volume).
 */
void RPS_fadeout(int channel, int ms) {

    struct Channel *c;

    if (check_channel(channel)) {
        return;
    }

    c = &channels[channel];

    LOCK_AUDIO();

    if (c->queued) {

        float position = samples_to_ms(c->pos) / 1000.0 + c->playing_start_ms;
        float duration = media_duration(c->playing);

        // If the fadeout will fit into the current file, dequeue the next file, so
        // that the next track will begin playing immediately.
        if ((position + ms / 1000.0 < duration) || (! c->playing_tight) || (ms <= 32)) {
                free_stream(c->queued);
                c->queued = NULL;
                free(c->queued_name);
                c->queued_name = NULL;
                c->queued_start_ms = 0;
                c->queued_relative_volume = 1.0;
        }
    }


    if (ms == 0) {
        c->stop_samples = 0;
        c->playing_tight = 0;
        UNLOCK_AUDIO();

        error(SUCCESS);
        return;
    }

    if (ms > 16) {
        c->fade.start = get_interpolate(&c->fade);
        c->fade.end = MIN_POWER;
        c->fade.done = 0;
        c->fade.duration = ms_to_samples(ms - 16);
    } else {
        c->fade.start = MIN_POWER;
        c->fade.end = MIN_POWER;
        c->fade.done = 1;
        c->fade.duration = 1;
    }

    c->stop_samples = ms_to_samples(ms);
    c->queued_tight = 0;

    if (!c->queued) {
        c->playing_tight = 0;
    }

    UNLOCK_AUDIO();

    error(SUCCESS);
}

/*
 * Sets the pause flag on the given channel 0 = unpaused, 1 = paused.
 */
void RPS_pause(int channel, int pause) {

    struct Channel *c;

    if (check_channel(channel)) {
        return;
    }

    c = &channels[channel];

    c->paused = pause;

    if (c->playing) {
        media_pause(c->playing, pause);
    }

    error(SUCCESS);

}

void RPS_unpause_all_at_start(void) {

    int i;

    /* Since media_wait_ready can block, we need to release the GIL. */
    Py_BEGIN_ALLOW_THREADS

    for (i = 0; i < num_channels; i++) {
        if (channels[i].playing && channels[i].paused && channels[i].pos == 0) {
            media_wait_ready(channels[i].playing);
        }
    }

    Py_END_ALLOW_THREADS

    for (i = 0; i < num_channels; i++) {
        if (channels[i].playing && channels[i].pos == 0) {
            channels[i].paused = 0;
            media_pause(channels[i].playing, 0);
        }
    }

    error(SUCCESS);

}


/**
 * Starts and stops the SDL audio playback.
 */
void RPS_global_pause(int pause) {
    SDL_PauseAudio(pause);
}


/*
 * Returns the position of the given channel, in ms.
 */
int RPS_get_pos(int channel) {
    int rv;
    struct Channel *c;

    if (check_channel(channel)) {
        return -1;
    }

    c = &channels[channel];

    LOCK_NAME();

    if (c->playing) {
        rv = samples_to_ms(c->pos) + c->playing_start_ms;
    } else {
        rv = -1;
    }

    UNLOCK_NAME();

    error(SUCCESS);
    return rv;
}

/*
 * Returns the duration of the file playing on the given channel, in
 * seconds.
 */
double RPS_get_duration(int channel) {
    double rv;
    struct Channel *c;

    if (check_channel(channel)) {
        return 0.0;
    }

    c = &channels[channel];

    LOCK_NAME();

    if (c->playing) {
        rv = media_duration(c->playing);
    } else {
        rv = 0.0;
    }

    UNLOCK_NAME();

    error(SUCCESS);
    return rv;
}

/*
 * Sets an event that is queued up when the track on the given channel
 * ends due to natural termination or a forced stop.
 */
void RPS_set_endevent(int channel, int event) {
    struct Channel *c;

    if (check_channel(channel)) {
        return;
    }

    c = &channels[channel];

    c->event = event;

    error(SUCCESS);
}

/*
 * This sets the mixer volume of the channel.
 */
void RPS_set_volume(int channel, float volume) {
    struct Channel *c;

    if (check_channel(channel)) {
        return;
    }

    c = &channels[channel];
    c->mixer_volume = volume;

    error(SUCCESS);
}


float RPS_get_volume(int channel) {

    struct Channel *c;

    if (check_channel(channel)) {
        return 0.0;
    }

    c = &channels[channel];

    error(SUCCESS);
    return c->mixer_volume;
}

/*
 * This sets the pan of the channel... independent volumes for the
 * left and right channels.
 */
void RPS_set_pan(int channel, float pan, float delay) {
    struct Channel *c;

    if (check_channel(channel)) {
        return;
    }

    c = &channels[channel];

    LOCK_AUDIO();

    c->pan.start = get_interpolate(&c->pan);
    c->pan.end = pan;
    c->pan.done = 0;
    c->pan.duration = ms_to_samples(delay * 1000);

    UNLOCK_AUDIO();

    error(SUCCESS);
}

/*
 * This sets the secondary volume of the channel.
 */
void RPS_set_secondary_volume(int channel, float vol2, float delay) {
    struct Channel *c;

    if (check_channel(channel)) {
        return;
    }

    c = &channels[channel];

    LOCK_AUDIO();

    c->secondary_volume.start = get_interpolate(&c->secondary_volume);
    c->secondary_volume.end = log_power(vol2);
    c->secondary_volume.done = 0;
    c->secondary_volume.duration = ms_to_samples(delay * 1000);

    UNLOCK_AUDIO();

    error(SUCCESS);
}

PyObject *RPS_read_video(int channel) {
    struct Channel *c;
    SDL_Surface *surf = NULL;

    if (check_channel(channel)) {
        Py_INCREF(Py_None);
        return Py_None;
    }

    c = &channels[channel];

    if (c->playing) {
        Py_BEGIN_ALLOW_THREADS
        surf = media_read_video(c->playing);
        Py_END_ALLOW_THREADS
    }

    error(SUCCESS);

    if (surf) {
        return PySurface_New(surf);
    } else {
        Py_INCREF(Py_None);
        return Py_None;
    }

}

int RPS_video_ready(int channel) {
    struct Channel *c;
    int rv;

    if (check_channel(channel)) {
        return 1;
    }

    c = &channels[channel];

    if (c->playing) {
        rv = media_video_ready(c->playing);
    } else {
        rv = 1;
    }

    error(SUCCESS);

    return rv;
}

/**
 * Marks channel as a video channel.
 */
void RPS_set_video(int channel, int video) {
	struct Channel *c;

	if (check_channel(channel)) {
        return;
    }

    c = &channels[channel];

    c->video = video;
}


/*
 * Initializes the sound to the given frequencies, channels, and
 * sample buffer size.
 */
void RPS_init(int freq, int stereo, int samples, int status, int equal_mono, int linear_fades_) {

    if (initialized) {
        return;
    }

    name_mutex = SDL_CreateMutex();

#ifndef __EMSCRIPTEN__
#if PY_VERSION_HEX < 0x03070000
    PyEval_InitThreads();
#endif
#endif

    import_pygame_sdl2();

    if (SDL_Init(SDL_INIT_AUDIO)) {
        error(SDL_ERROR);
        return;
    }

    audio_spec.freq = freq;
    audio_spec.format = AUDIO_S16SYS;
    audio_spec.channels = stereo;
    audio_spec.samples = samples;
    audio_spec.callback = callback;
    audio_spec.userdata = NULL;

    if (SDL_OpenAudio(&audio_spec, NULL)) {
        error(SDL_ERROR);
        return;
    }

    media_init(audio_spec.freq, status, equal_mono);

    SDL_PauseAudio(0);

    linear_fades = linear_fades_;

    initialized = 1;

    error(SUCCESS);
}

void RPS_quit() {

    if (! initialized) {
        return;
    }

    int i;

    LOCK_AUDIO();
    SDL_PauseAudio(1);
    UNLOCK_AUDIO();

    for (i = 0; i < num_channels; i++) {
        RPS_stop(i);
    }

    SDL_CloseAudio();

    num_channels = 0;
    initialized = 0;
    error(SUCCESS);
}

/* This must be called frequently, to take care of deallocating dead
 * streams. */
void RPS_periodic() {

    LOCK_NAME();
    struct Dying *d = dying;
    dying = NULL;
    UNLOCK_NAME();

    while (d) {
        media_close(d->stream);
        struct Dying *next_d = d->next;
        free(d);
        d = next_d;
    }

}

void RPS_advance_time(void) {
	media_advance_time();
}

void RPS_sample_surfaces(PyObject *rgb, PyObject *rgba) {
    import_pygame_sdl2();

    media_sample_surfaces(
			PySurface_AsSurface(rgb),
			PySurface_AsSurface(rgba)
		);

}

/*
 * Returns the error message string if an error has occured, or
 * NULL if no error has happened.
 */
char *RPS_get_error() {
    switch(RPS_error) {
    case 0:
        return (char *) "";
    case SDL_ERROR:
        return (char *) SDL_GetError();
    case SOUND_ERROR:
        return (char *) "Some sort of codec error.";
    case RPS_ERROR:
        return (char *) error_msg;
    default:
        return (char *) "Error getting error.";
    }
}
