extern U0 (*fp_snd_fill_buf)(U32* buf, I64 buf_num);

#define AUDIO_MAX_STREAMS 16

#define AUDIO_OUTPUT_BUFFER_SIZE 1024

#define AUDIO_STREAM_FIFO_SIZE 1048576
#define AUDIO_STREAM_TYPE_INPUT 0
#define AUDIO_STREAM_TYPE_OUTPUT 1

class Sound {
    // For simplicity, all samples will be converted to 44100 Hz, 2 channels, 16
    // bit when they are loaded.
    I64 rate;
    I64 channels;
    I64 bits;
    U32* data;
    I64 length; // in samples
};

class @audio_device
{
    Bool enabled;
};

class @audio_mixer
{
    I64 left;
    I64 right;
};

class @audio_stream
{
    I64 type;
    I64 rate;
    I64 channels;
    I64 bits;
    CFifoI64* data;
};

class @audio_wave_generator
{
    F64 duration;
    I64 frequency;
};

class @audio
{
    I64 driver;
    @audio_device device;
    @audio_mixer mixer;
    @audio_stream output[AUDIO_MAX_STREAMS + 1];
    @audio_wave_generator wavegen;
    U0 (*Beep)();
    U0 (*Init)();
    U0 (*MixOutput)(U64 buf, I64);
    Sound (*SoundFromFile)(U8* filename);
    U0 (*FreeSound)(Sound* snd);
    I64 (*PlaySound)(Sound* snd);
};

@audio Audio;

U0 @audio_mix_output(U32* buf, I64 length = NULL)
{
    I64 i;
    I64 j;
    I64 acc_sample_L = 0;
    I64 acc_sample_R = 0;
    I64 acc_streams = 0;
    U32 sample;
    if (!length)
        length = AUDIO_OUTPUT_BUFFER_SIZE;
    for (i = 0; i < length / 4; i++) {
        acc_sample_L = 0;
        acc_sample_R = 0;
        acc_streams = 0;
        if (Audio.wavegen.frequency) {
            sample.i16[0] = T(Sin(Audio.wavegen.frequency * Audio.wavegen.duration) >= 0.0,
                I16_MAX / 8, I16_MIN / 8);
            sample.i16[1] = sample.i16[0];
            FifoI64Ins(Audio.output[AUDIO_MAX_STREAMS].data, sample);
            Audio.wavegen.duration += 6.4 / 48000.0;
        }
        for (j = 0; j < AUDIO_MAX_STREAMS + 1; j++) {
            if (FifoI64Cnt(Audio.output[j].data)) {
                FifoI64Rem(Audio.output[j].data, &sample);
                acc_streams++;
                acc_sample_L += sample.i16[0];
                acc_sample_R += sample.i16[1];
            }
        }
        buf[i].i16[0] = ToI64(acc_sample_L / Sqrt(acc_streams) * Audio.mixer.left / 100);
        buf[i].i16[1] = ToI64(acc_sample_R / Sqrt(acc_streams) * Audio.mixer.right / 100);
    }
}

U0 @audio_init()
{
    I64 i = 0;
    for (i = 0; i < AUDIO_MAX_STREAMS + 1; i++)
        Audio.output[i].data = FifoI64New(AUDIO_STREAM_FIFO_SIZE);
    Audio.mixer.left = 100;
    Audio.mixer.right = 100;
    Audio.wavegen.duration = 0.0;
    Audio.wavegen.frequency = 0;
    Audio.device.enabled = TRUE;
}

Audio.driver = NULL;
Audio.Init = &@audio_init;
Audio.MixOutput = &@audio_mix_output;

// Initialize Audio
Audio.Init();

"audio ";