code:
/*
 Copyright (C) 2009 Jonathon Fowler <jf@jonof.id.au>
 Copyright (C) EDuke32 developers and contributors

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

 */

/**
 * WinMM MIDI output driver
 */

#include "driver_winmm.h"

#include "baselayer.h"
#include "linklist.h"
#include "midi.h"
#include "midifuncs.h"
#include "multivoc.h"
#include "osd.h"
#include "winbits.h"

#include <mmsystem.h>

#ifdef _MSC_VER
#define inline _inline
#endif

UINT WinMM_DeviceID = MIDI_MAPPER;

static int ErrorCode = WinMMErr_Ok;

static BOOL      midiInstalled;
static HMIDISTRM midiStream;
static uint32_t midiThreadTimer;
static uint32_t midiLastEventTime;
static uint32_t midiThreadQueueTimer;
static uint32_t midiThreadQueueTicks;
static HANDLE   midiThread;
static HANDLE   midiThreadQuitEvent;
static HANDLE   midiBufferFinishedEvent;
static HANDLE   midiThreadResetEvent;
static HANDLE   midiMutex;
static int      midiStreamRunning;
static int      midiLastDivision;

#define MME_THREAD_QUEUE_INTERVAL 10        // 1/10 sec
#define MME_MIDI_BUFFER_SPACE (12 * 128u)  // 128 note-on events

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4200)
#endif
typedef struct MidiBuffer
{
    struct MidiBuffer *next;
    struct MidiBuffer *prev;
    MIDIHDR hdr;
    DWORD data[];
} MidiBuffer;
#ifdef _MSC_VER
#pragma warning(pop)
#endif

static MidiBuffer finishedBufferList;
static MidiBuffer activeBufferList;
static MidiBuffer spareBufferList;
static MidiBuffer *currentMidiBuffer;

static int maxActiveMidiBuffers, maxSpareMidiBuffers;
static int numActiveMidiBuffers, numSpareMidiBuffers;

static void midi_destroy_thread(void);
static void CALLBACK midi_callback(HMIDIOUT out, UINT msg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2);

int WinMMDrv_GetError(void) { return ErrorCode; }

const char *WinMMDrv_ErrorString(int ErrorNumber)
{
    switch (ErrorNumber)
    {
        case WinMMErr_Error:             return WinMMDrv_ErrorString(ErrorCode);
        case WinMMErr_Ok:                return "MME ok.";
        case WinMMErr_MIDIStreamOpen:    return "MIDI error: failed opening stream.";
        case WinMMErr_MIDIStreamRestart: return "MIDI error: failed starting stream.";
        case WinMMErr_MIDICreateEvent:   return "MIDI error: failed creating play thread quit event.";
        case WinMMErr_MIDIPlayThread:    return "MIDI error: failed creating play thread.";
        case WinMMErr_MIDICreateMutex:   return "MIDI error: failed creating play mutex.";
        default:                         return "Unknown MME error code.";
    }
}

// will append "err nnn (ssss)\n" to the end of the string it emits
static void midi_error(MMRESULT rv, const char *str)
{
    const char * errtxt = "?";

    switch (rv)
    {
        case MMSYSERR_NOERROR:      errtxt = "MMSYSERR_NOERROR";      break;
        case MMSYSERR_BADDEVICEID:  errtxt = "MMSYSERR_BADDEVICEID";  break;
        case MMSYSERR_NOTENABLED:   errtxt = "MMSYSERR_NOTENABLED";   break;
        case MMSYSERR_ALLOCATED:    errtxt = "MMSYSERR_ALLOCATED";    break;
        case MMSYSERR_INVALHANDLE:  errtxt = "MMSYSERR_INVALHANDLE";  break;
        case MMSYSERR_NODRIVER:     errtxt = "MMSYSERR_NODRIVER";     break;
        case MMSYSERR_NOMEM:        errtxt = "MMSYSERR_NOMEM";        break;
        case MMSYSERR_NOTSUPPORTED: errtxt = "MMSYSERR_NOTSUPPORTED"; break;
        case MMSYSERR_BADERRNUM:    errtxt = "MMSYSERR_BADERRNUM";    break;
        case MMSYSERR_INVALFLAG:    errtxt = "MMSYSERR_INVALFLAG";    break;
        case MMSYSERR_INVALPARAM:   errtxt = "MMSYSERR_INVALPARAM";   break;
        case MMSYSERR_HANDLEBUSY:   errtxt = "MMSYSERR_HANDLEBUSY";   break;
        case MMSYSERR_INVALIDALIAS: errtxt = "MMSYSERR_INVALIDALIAS"; break;
        case MMSYSERR_BADDB:        errtxt = "MMSYSERR_BADDB";        break;
        case MMSYSERR_KEYNOTFOUND:  errtxt = "MMSYSERR_KEYNOTFOUND";  break;
        case MMSYSERR_READERROR:    errtxt = "MMSYSERR_READERROR";    break;
        case MMSYSERR_WRITEERROR:   errtxt = "MMSYSERR_WRITEERROR";   break;
        case MMSYSERR_DELETEERROR:  errtxt = "MMSYSERR_DELETEERROR";  break;
        case MMSYSERR_VALNOTFOUND:  errtxt = "MMSYSERR_VALNOTFOUND";  break;
        case MMSYSERR_NODRIVERCB:   errtxt = "MMSYSERR_NODRIVERCB";   break;
        default:                                                      break;
    }
    debug_break();

    LOG_F(ERROR, "%s: %s (0x%08x)", str, errtxt, rv);
}

// AddressSanitizer bug causes heap destruction with some functions from winmm.lib
// see https://vsf-prod.westus.cloudapp.azure.com/content/problem/1100813/false-positive-bad-free-when-using-asan-with-winmm.html
static void midi_dispose_buffer(MidiBuffer *node, const char *caller)
{
    if (node->hdr.dwFlags & MHDR_PREPARED)
    {
#if __SANITIZE_ADDRESS__ != 1
        VLOG_F(LOG_DEBUG, "%s/midi_dispose_buffer unpreparing buffer %p", caller, node);
        auto rv = midiOutUnprepareHeader((HMIDIOUT)midiStream, &node->hdr, sizeof(MIDIHDR));
        if (rv != MMSYSERR_NOERROR)
            midi_error(rv, "midi_dispose_buffer: error in midiOutUnprepareHeader");
#endif
       node->hdr.dwFlags &= ~MHDR_PREPARED;
    }

    node->hdr.dwBufferLength = max<DWORD>(MME_MIDI_BUFFER_SPACE, node->hdr.dwBufferLength);
    LL::Move(node, &spareBufferList);

    if (++numSpareMidiBuffers > maxSpareMidiBuffers)
        maxSpareMidiBuffers = numSpareMidiBuffers;
    VLOG_F(LOG_DEBUG, "MME %s/midi_dispose_buffer recycling buffer %p", caller, node);
}

static void midi_gc_buffers(void)
{
    if (!midiStreamRunning)
        return;

    for (auto node = finishedBufferList.next, next = node->next; node != &finishedBufferList; node = next, next = node->next)
    {
        if (node->hdr.dwFlags & MHDR_DONE)
        {
            midi_dispose_buffer(node, "midi_gc_buffers");
        }
    }

    // prune excess spare buffers for when users hold down buttons on the music toggle in the menu
    for (auto node = spareBufferList.next, next = node->next; node != &spareBufferList; node = next, next = node->next)
    {
        if (numSpareMidiBuffers < numActiveMidiBuffers)
            break;

        LL::Remove(node);
        Xfree(node);
        numSpareMidiBuffers--;
        VLOG_F(LOG_DEBUG, "MME midi_gc_buffers pruning spare buffer %p", node);
    }
}

static void midi_free_buffers(void)
{
    //Bassert(activeBufferList.next == activeBufferList.prev);
    while (activeBufferList.next != activeBufferList.prev)
        WaitForSingleObject(midiBufferFinishedEvent, INFINITE);

    for (auto node = finishedBufferList.next, next = node->next; node != &finishedBufferList; node = next, next = node->next)
    {
        LL::Move(node, &spareBufferList);
        numSpareMidiBuffers++;
    }

    for (auto node = spareBufferList.next, next = node->next; node != &spareBufferList; node = next, next = node->next)
    {
        LL::Remove(node);
        Xfree(node);
        numSpareMidiBuffers--;
        VLOG_F(LOG_DEBUG, "MME midi_free_buffers pruning spare buffer %p", node);
    }

    Bassert(numSpareMidiBuffers == 0);

    numActiveMidiBuffers = 0;
    currentMidiBuffer = nullptr;
}

static void midi_flush_current_buffer(void)
{
    BOOL needsPrepare = FALSE;
    BOOL running = midiStreamRunning;

    if (!currentMidiBuffer)
    {
//        MV_Printf("no buffer\n");
        return;
    }
    auto evt = (MIDIEVENT *)&currentMidiBuffer->data[0];

    if (!running)
    {
        // immediate messages don't use a MIDIEVENT header so strip it off and
        // make some adjustments

        if (currentMidiBuffer->hdr.dwBytesRecorded)
        {
            currentMidiBuffer->hdr.dwBufferLength  = currentMidiBuffer->hdr.dwBytesRecorded - 12;
            currentMidiBuffer->hdr.dwBytesRecorded = 0;
        }

        if (currentMidiBuffer->hdr.dwBufferLength > 0)
            needsPrepare = TRUE;
    }
    else
        needsPrepare = TRUE;

    if (needsPrepare)
    {
        Bassert((currentMidiBuffer->hdr.dwFlags & MHDR_PREPARED) == 0);
        // playing a file, or sending a sysex when not playing means
        // we need to prepare the buffer
        currentMidiBuffer->hdr.dwBufferLength = currentMidiBuffer->hdr.dwBytesRecorded;
        auto rv = midiOutPrepareHeader((HMIDIOUT)midiStream, &currentMidiBuffer->hdr, sizeof(MIDIHDR));
        if (rv != MMSYSERR_NOERROR)
        {
            midi_error(rv, "midi_flush_current_buffer: error in midiOutPrepareHeader");
            return;
        }
    }

    if (running)
    {
        // midi file playing, so send events to the stream
        auto rv = midiStreamOut(midiStream, &currentMidiBuffer->hdr, sizeof(MIDIHDR));
        if (rv != MMSYSERR_NOERROR)
            midi_error(rv, "midi_flush_current_buffer: error in midiStreamOut");

        //MV_Printf("MME midi_flush_current_buffer queued buffer %p\n", currentMidiBuffer);
    }
    else
    {
        // midi file not playing, so send immediately
        if (currentMidiBuffer->hdr.dwBufferLength > 0)
        {
            currentMidiBuffer->hdr.dwUser = 0x1337;
            auto rv = midiOutLongMsg((HMIDIOUT)midiStream, &currentMidiBuffer->hdr, sizeof(MIDIHDR));
            if (rv == MMSYSERR_NOERROR)
            {
                // busy-wait for Windows to be done with it
                WaitForSingleObject(midiBufferFinishedEvent, INFINITE);

                //MV_Printf("MME midi_flush_current_buffer sent immediate long\n");
            }
            else
                midi_error(rv, "midi_flush_current_buffer: error in midiOutLongMsg");

            midi_dispose_buffer(currentMidiBuffer, "midi_flush_current_buffer");
            currentMidiBuffer = 0;
        }
        else
        {
            auto rv = midiOutShortMsg((HMIDIOUT)midiStream, evt->dwEvent);
            if (rv != MMSYSERR_NOERROR)
                midi_error(rv, "midi_flush_current_buffer: error in midiOutShortMsg");
        }
        return;
    }

    LL::Insert((MidiBuffer *)&activeBufferList, currentMidiBuffer);

    if (++numActiveMidiBuffers > maxActiveMidiBuffers)
        maxActiveMidiBuffers = numActiveMidiBuffers;

    currentMidiBuffer = 0;
}

static void midi_setup_event(int length, unsigned char **data)
{
    int i = currentMidiBuffer->hdr.dwBytesRecorded / sizeof(DWORD);
    auto evt = (MIDIEVENT *)&currentMidiBuffer->data[i];

    evt->dwDeltaTime = midiThread ? (midiThreadTimer - midiLastEventTime) : 0;
    evt->dwStreamID  = 0;

    if (length <= 3)
    {
        evt->dwEvent = (DWORD)MEVT_SHORTMSG << 24;
        *data        = (unsigned char *)&evt->dwEvent;
    }
    else
    {
        evt->dwEvent = ((DWORD)MEVT_LONGMSG << 24) | (length & 0x00ffffff);
        *data        = (unsigned char *)&evt->dwParms[0];
    }
}

/* Gets space in the buffer presently being filled.
   If insufficient space can be found in the buffer,
   what is there is flushed to the stream and a new
   buffer large enough is allocated.

   Returns a pointer to starting writing at in 'data'.
 */
static BOOL midi_get_buffer(int length, unsigned char **data)
{
    BOOL const running = midiStreamRunning;
    uint32_t    datalen;

    // determine the space to alloc.
    // the size of a MIDIEVENT is 3*sizeof(DWORD) = 12.
    // short messages need only that amount of space.
    // long messages need additional space equal to the length of
    //    the message, padded to 4 bytes

    if (length <= 3)
        datalen = 12;
    else
        datalen = 12 + ((length + 3) & ~3);

    if (midiStreamRunning && currentMidiBuffer && currentMidiBuffer->hdr.dwBytesRecorded + datalen <= MME_MIDI_BUFFER_SPACE)
    {
        // there was enough space in the current buffer, so hand that back
        midi_setup_event(length, data);
        currentMidiBuffer->hdr.dwBytesRecorded += datalen;
        return TRUE;
    }

    if (currentMidiBuffer)
    {
        // not enough space in the current buffer to accommodate the
        // new data, so flush it to the stream
        midi_flush_current_buffer();
        currentMidiBuffer = 0;
    }

    // check if there's a spare buffer big enough to hold the message
    if (running)
    {
        for (auto node = spareBufferList.next; node != &spareBufferList; node = node->next)
        {
            if (node->hdr.dwBufferLength >= datalen)
            {
                // yes!
                LL::Remove(node);
                numSpareMidiBuffers--;
                node->hdr.dwBytesRecorded = 0;
                Bmemset(node->hdr.lpData, 0, node->hdr.dwBufferLength);

                currentMidiBuffer = node;

                VLOG_F(LOG_DEBUG, "MME midi_get_buffer fetched buffer %p", node);
                break;
            }
        }
    }

    if (!currentMidiBuffer)
    {
        // there were no spare buffers, or none were big enough, so allocate a new one
        int const size = max(MME_MIDI_BUFFER_SPACE, datalen);
        auto      node = (MidiBuffer *)Xcalloc(1, sizeof(MidiBuffer) + size);

        node->hdr.dwUser = (DWORD_PTR)node;
        node->hdr.lpData = (LPSTR)node->data;

        node->hdr.dwBufferLength  = size;
        node->hdr.dwBytesRecorded = 0;

        currentMidiBuffer = node;
        LL::Reset(node);
        VLOG_F(LOG_DEBUG, "MME midi_get_buffer allocated buffer %p", node);
    }

    midi_setup_event(length, data);

    currentMidiBuffer->hdr.dwBytesRecorded += datalen;

    return TRUE;
}

static inline void midi_sequence_event(void)
{
    if (!midiThread)
    {
        // a midi event being sent out of playback (streaming) mode
        midi_flush_current_buffer();
        return;
    }

    //MV_Printf("MME midi_sequence_event buffered\n");

    midiLastEventTime = midiThreadTimer;
}

static void MME_NoteOff(int channel, int key, int velocity)
{
    unsigned char *data;

    if (midi_get_buffer(3, &data))
    {
        data[0] = WINMM_NOTE_OFF | channel;
        data[1] = key;
        data[2] = velocity;
        midi_sequence_event();
    }
    else
        LOG_F(ERROR, "Error in MME_NoteOff()");
}

static void MME_NoteOn(int channel, int key, int velocity)
{
    unsigned char *data;

    if (midi_get_buffer(3, &data))
    {
        data[0] = WINMM_NOTE_ON | channel;
        data[1] = key;
        data[2] = velocity;
        midi_sequence_event();
    }
    else
        LOG_F(ERROR, "Error in MME_NoteOn()");
}

static void MME_PolyAftertouch(int channel, int key, int pressure)
{
    unsigned char *data;

    if (midi_get_buffer(3, &data))
    {
        data[0] = WINMM_POLY_AFTER_TCH | channel;
        data[1] = key;
        data[2] = pressure;
        midi_sequence_event();
    }
    else
        LOG_F(ERROR, "Error in MME_PolyAftertouch()");
}

static void MME_ControlChange(int channel, int number, int value)
{
    unsigned char *data;

    if (midi_get_buffer(3, &data))
    {
        data[0] = WINMM_CONTROL_CHANGE | channel;
        data[1] = number;
        data[2] = value;
        midi_sequence_event();
    }
    else
        LOG_F(ERROR, "Error in MME_ControlChange()");
}

static void MME_ProgramChange(int channel, int program)
{
    unsigned char *data;

    if (midi_get_buffer(2, &data))
    {
        data[0] = WINMM_PROGRAM_CHANGE | channel;
        data[1] = program;
        midi_sequence_event();
    }
    else
        LOG_F(ERROR, "Error in MME_ProgramChange()");
}

static void MME_ChannelAftertouch(int channel, int pressure)
{
    unsigned char *data;

    if (midi_get_buffer(2, &data))
    {
        data[0] = WINMM_AFTER_TOUCH | channel;
        data[1] = pressure;
        midi_sequence_event();
    }
    else
        LOG_F(ERROR, "Error in MME_ChannelAftertouch()");
}

static void MME_PitchBend(int channel, int lsb, int msb)
{
    unsigned char *data;

    if (midi_get_buffer(3, &data))
    {
        data[0] = WINMM_PITCH_BEND | channel;
        data[1] = lsb;
        data[2] = msb;
        midi_sequence_event();
    }
    else
        LOG_F(ERROR, "Error in MME_PitchBend()");
}

static void MME_SysEx(const unsigned char *data, int length)
{
    unsigned char *wdata;

    if (midi_get_buffer(length, &wdata))
    {
        Bmemcpy(wdata, data, length);
        midi_sequence_event();
    }
    else
        LOG_F(ERROR, "Error in MME_SysEx()");
}

void WinMMDrv_MIDI_PrintDevices(void)
{
    auto numDevices = (int)midiOutGetNumDevs();
    MIDIOUTCAPS midicaps;

    for (int i = -1; i < numDevices; i++)
    {
        if (!midiOutGetDevCaps(i, &midicaps, sizeof(MIDIOUTCAPS)))
            LOG_F(INFO, "%d: %s  ", i, midicaps.szPname);
    }
}

int WinMMDrv_MIDI_PrintBufferInfo(osdcmdptr_t UNUSED(parm))
{
    UNREFERENCED_CONST_PARAMETER(parm);
    LOG_F(INFO, "MME MIDI buffers:");
    LOG_F(INFO, "%6s: %d (max %d)", "active", numActiveMidiBuffers, maxActiveMidiBuffers);
    LOG_F(INFO, "%6s: %d (max %d)", "spare", numSpareMidiBuffers, maxSpareMidiBuffers);
    return OSDCMD_OK;
}

int WinMMDrv_MIDI_GetNumDevices(void) { return midiOutGetNumDevs(); }

int WinMMDrv_MIDI_Init(midifuncs * funcs)
{
    if (midiInstalled)
        WinMMDrv_MIDI_Shutdown();

    LL::Reset(&finishedBufferList);
    LL::Reset(&activeBufferList);
    LL::Reset(&spareBufferList);

    Bmemset(funcs, 0, sizeof(midifuncs));

    if ((midiMutex = CreateMutex(0, FALSE, 0)) == 0)
    {
        ErrorCode = WinMMErr_MIDICreateMutex;
        return WinMMErr_Error;
    }

    if ((midiBufferFinishedEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr)) == 0)
    {
        ErrorCode = WinMMErr_MIDICreateEvent;
        return WinMMErr_Error;
    }

    MIDIOUTCAPS midicaps;

    if (WinMM_DeviceID > midiOutGetNumDevs() || midiOutGetDevCaps(WinMM_DeviceID, &midicaps, sizeof(MIDIOUTCAPS)))
        WinMM_DeviceID = MIDI_MAPPER;

    if (!midiOutGetDevCaps(WinMM_DeviceID, &midicaps, sizeof(MIDIOUTCAPS)))
        LOG_F(INFO, ": [%d] %s", WinMM_DeviceID, midicaps.szPname);

    auto rv = midiStreamOpen(&midiStream, &WinMM_DeviceID, 1, (DWORD_PTR)midi_callback, (DWORD_PTR)0, CALLBACK_FUNCTION);

    if (rv != MMSYSERR_NOERROR)
    {
        WinMMDrv_MIDI_Shutdown();
        midi_error(rv, "WinMMDrv_MIDI_Init: error in midiStreamOpen");
        ErrorCode = WinMMErr_MIDIStreamOpen;
        return WinMMErr_Error;
    }

    funcs->NoteOff           = MME_NoteOff;
    funcs->NoteOn            = MME_NoteOn;
    funcs->PolyAftertouch    = MME_PolyAftertouch;
    funcs->ControlChange     = MME_ControlChange;
    funcs->ProgramChange     = MME_ProgramChange;
    funcs->ChannelAftertouch = MME_ChannelAftertouch;
    funcs->PitchBend         = MME_PitchBend;
    funcs->SysEx             = MME_SysEx;

    midiInstalled = TRUE;

    return WinMMErr_Ok;
}

void WinMMDrv_MIDI_Shutdown(void)
{
    WinMMDrv_MIDI_HaltPlayback();
    midi_free_buffers();

    if (midiStream)
    {
        // MV_Printf("stopping stream\n");
        auto rv = midiStreamClose(midiStream);
        if (rv != MMSYSERR_NOERROR)
            midi_error(rv, "WinMMDrv_MIDI_Shutdown: error in midiStreamClose");
        // MV_Printf("stream stopped\n");

        midiStream = 0;
    }

    midi_destroy_thread();

    if (midiMutex)
    {
        CloseHandle(midiMutex);
        midiMutex = 0;
    }

    if (midiBufferFinishedEvent)
    {
        CloseHandle(midiBufferFinishedEvent);
        midiBufferFinishedEvent = 0;
    }

    midiInstalled = FALSE;

    VLOG_F(LOG_DEBUG, "MME finished, max active buffers: %d max spare buffers: %d", maxActiveMidiBuffers, maxSpareMidiBuffers);
}

static DWORD midi_get_tick(void)
{
    if (!midiStreamRunning)
        return 0;

    MMTIME mmtime = { TIME_TICKS, 0 };

    auto rv = midiStreamPosition(midiStream, &mmtime, sizeof(MMTIME));
    if (rv != MMSYSERR_NOERROR)
    {
        midi_error(rv, "midi_get_tick: error in midiStreamPosition");
        return 0;
    }

    return mmtime.u.ticks;
}

static void CALLBACK midi_callback(HMIDIOUT out, UINT msg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
    UNREFERENCED_PARAMETER(out);
    UNREFERENCED_PARAMETER(dwInstance);
    UNREFERENCED_PARAMETER(dwParam2);

    if (msg != MOM_DONE)
        return;

    WinMMDrv_MIDI_Lock();
    for (auto node = activeBufferList.next; node != &activeBufferList; node = node->next)
    {
        if (&node->hdr == (MIDIHDR *)dwParam1)
        {
            Bassert(node->hdr.dwUser != 0x1337);
            numActiveMidiBuffers--;
            LL::Move(node, &finishedBufferList);
            SetEvent(midiBufferFinishedEvent);
            WinMMDrv_MIDI_Unlock();
            return;
        }
    }
    debug_break();
    WinMMDrv_MIDI_Unlock();
}

static unsigned WINAPI midiDataThread(LPVOID lpParameter)
{
    UNREFERENCED_PARAMETER(lpParameter);

    debugThreadName("midiDataThread");

    DWORD sleepAmount = 100 / MME_THREAD_QUEUE_INTERVAL;

    do
    {
        HANDLE const events[3] = { midiThreadQuitEvent, midiBufferFinishedEvent, midiThreadResetEvent };
        auto result = WaitForMultipleObjects(3, events, false, sleepAmount);

        if (result != WAIT_OBJECT_0 && midiStreamRunning == false)
            continue;

        switch (result)
        {
        case WAIT_OBJECT_0+2: // midiThreadResetEvent
            midiThreadTimer      = midi_get_tick();
            midiLastEventTime    = midiThreadTimer;
            midiThreadQueueTimer = midiThreadTimer + midiThreadQueueTicks;
            break;
        case WAIT_OBJECT_0+1: // midiBufferFinishedEvent
            sleepAmount = 0;
            WinMMDrv_MIDI_Lock();
            midi_gc_buffers();
            WinMMDrv_MIDI_Unlock();
            break;
        case WAIT_TIMEOUT:
        {
            // queue a tick
            WinMMDrv_MIDI_Lock();
            DWORD sequenceTime = midi_get_tick();
            sleepAmount = 100 / MME_THREAD_QUEUE_INTERVAL;

            if (((int64_t)midiThreadTimer - (int64_t)sequenceTime) > midiThreadQueueTicks)
            {
                // we're running ahead, so sleep for half the usual
                // amount and try again
                sleepAmount /= 2;
                WinMMDrv_MIDI_Unlock();
                continue;
            }

            midiThreadQueueTimer = sequenceTime + midiThreadQueueTicks;
            while (midiThreadTimer < midiThreadQueueTimer)
            {
                WinMMDrv_MIDI_Service();
                midiThreadTimer++;
            }
            midi_flush_current_buffer();
            WinMMDrv_MIDI_Unlock();
            break;
        }
        case WAIT_OBJECT_0: // midiThreadQuitEvent
            return 0;
        }
    } while (1);

    return 0;
}

int midi_create_thread(void)
{
    midiThreadQuitEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
    if (!midiThreadQuitEvent)
    {
        ErrorCode = WinMMErr_MIDICreateEvent;
        return WinMMErr_Error;
    }

    midiThreadResetEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
    if (!midiThreadResetEvent)
    {
        ErrorCode = WinMMErr_MIDICreateEvent;
        return WinMMErr_Error;
    }

    midiThread = (HANDLE)_beginthreadex(nullptr, 0, midiDataThread, 0, 0, 0);
    if (!midiThread)
    {
        ErrorCode = WinMMErr_MIDIPlayThread;
        return WinMMErr_Error;
    }

    return WinMMErr_Ok;
}

void midi_destroy_thread(void)
{
    if (midiThread)
    {
        SetEvent(midiThreadQuitEvent);
        WaitForSingleObject(midiThread, INFINITE);
        // MV_Printf("MME MIDI_HaltPlayback synched\n");
        CloseHandle(midiThread);
        midiThread = 0;
        CloseHandle(midiThreadQuitEvent);
        midiThreadQuitEvent = 0;
        CloseHandle(midiThreadResetEvent);
        midiThreadResetEvent = 0;
    }
}

int WinMMDrv_MIDI_StartPlayback(void)
{
    WinMMDrv_MIDI_HaltPlayback();

    auto rv = midiStreamRestart(midiStream);
    if (rv != MMSYSERR_NOERROR)
    {
        midi_error(rv, "WinMMDrv_MIDI_StartPlayback: error in midiStreamRestart");
        WinMMDrv_MIDI_HaltPlayback();
        ErrorCode = WinMMErr_MIDIStreamRestart;
        return WinMMErr_Error;
    }

    if (!midiThread && midi_create_thread() != WinMMErr_Ok)
        return WinMMErr_Error;

    midiLastDivision = 0;

    if (midiThread)
        SetEvent(midiThreadResetEvent);

    midiStreamRunning = TRUE;

    return WinMMErr_Ok;
}

void WinMMDrv_MIDI_HaltPlayback(void)
{
    midiStreamRunning = FALSE;
}

void WinMMDrv_MIDI_SetTempo(int tempo, int division)
{
    //MV_Printf("MIDI_SetTempo %d/%d\n", tempo, division);
    MIDIPROPTEMPO   propTempo   = { sizeof(MIDIPROPTEMPO), (DWORD)(60000000l / tempo) };
    MIDIPROPTIMEDIV propTimediv = { sizeof(MIDIPROPTIMEDIV), (DWORD)division };

    if (midiLastDivision != division)
    {
        auto rv = midiStreamStop(midiStream);
        if (rv != MMSYSERR_NOERROR)
            midi_error(rv, "WinMMDrv_MIDI_SetTempo: error in midiStreamStop");

        rv = midiStreamProperty(midiStream, (LPBYTE)&propTimediv, MIDIPROP_SET | MIDIPROP_TIMEDIV);
        if (rv != MMSYSERR_NOERROR)
            midi_error(rv, "WinMMDrv_MIDI_SetTempo: error in midiStreamProperty (MIDIPROP_TIMEDIV)");
    }

    auto rv = midiStreamProperty(midiStream, (LPBYTE)&propTempo, MIDIPROP_SET | MIDIPROP_TEMPO);
    if (rv != MMSYSERR_NOERROR)
        midi_error(rv, "WinMMDrv_MIDI_SetTempo: error in midiStreamProperty (MIDIPROP_TEMPO)");

    if (midiLastDivision != division)
    {
        if (midiStreamRunning)
            midiStreamRestart(midiStream);

        midiLastDivision = division;
    }

    midiThreadQueueTicks = (int)ceil((((double)tempo * (double)division) / 60.0) / (double)MME_THREAD_QUEUE_INTERVAL);
    if (midiThreadQueueTicks <= 0)
        midiThreadQueueTicks = 1;
}

void WinMMDrv_MIDI_Lock(void)
{
    DWORD err = WaitForSingleObject(midiMutex, INFINITE);
    if (err != WAIT_OBJECT_0)
        LOG_F(ERROR, "Error in WinMMDrv_MIDI_Lock(): WaitForSingleObject() returned %d", (int) err);
}

void WinMMDrv_MIDI_Unlock(void)  { ReleaseMutex(midiMutex); }
void WinMMDrv_MIDI_Service(void) { MIDI_ServiceRoutine(); }


code:
/*
 Copyright (C) 2009 Jonathon Fowler <jf@jonof.id.au>

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

 */

#include "osd.h"
#include "midifuncs.h"
#include "windows_inc.h"

#define WINMM_NOTE_OFF         0x80
#define WINMM_NOTE_ON          0x90
#define WINMM_POLY_AFTER_TCH   0xA0
#define WINMM_CONTROL_CHANGE   0xB0
#define WINMM_PROGRAM_CHANGE   0xC0
#define WINMM_AFTER_TOUCH      0xD0
#define WINMM_PITCH_BEND       0xE0

enum
{
    WinMMErr_Error = -1,
    WinMMErr_Ok    = 0,
    WinMMErr_MIDIStreamOpen,
    WinMMErr_MIDIStreamRestart,
    WinMMErr_MIDICreateEvent,
    WinMMErr_MIDIPlayThread,
    WinMMErr_MIDICreateMutex
};

extern UINT WinMM_DeviceID;

int WinMMDrv_GetError(void);
const char *WinMMDrv_ErrorString( int ErrorNumber );

int  WinMMDrv_MIDI_Init(midifuncs *);
void WinMMDrv_MIDI_Shutdown(void);
int  WinMMDrv_MIDI_StartPlayback(void);
void WinMMDrv_MIDI_HaltPlayback(void);
void WinMMDrv_MIDI_SetTempo(int tempo, int division);
void WinMMDrv_MIDI_Lock(void);
void WinMMDrv_MIDI_Unlock(void);
void WinMMDrv_MIDI_Service(void);

void WinMMDrv_MIDI_PrintDevices(void);
int WinMMDrv_MIDI_GetNumDevices(void);

int WinMMDrv_MIDI_PrintBufferInfo(osdcmdptr_t);

repo_name: orochi/nblood

license: mit


code:
/*
 Copyright (C) 2009 Jonathon Fowler <jf@jonof.id.au>
 Copyright (C) EDuke32 developers and contributors

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

 */

/**
 * Abstraction layer for hiding the various supported sound devices
 * behind a common and opaque interface called on by MultiVoc.
 */

#include "drivers.h"

#include "driver_adlib.h"
#include "driver_sf2.h"
#include "_midi.h"

#ifdef RENDERTYPESDL
# include "driver_sdl.h"
#endif

#ifdef _WIN32
# include "driver_directsound.h"
# include "driver_winmm.h"
#endif

#ifdef __linux__
# include "driver_alsa.h"
#endif

int ASS_PCMSoundDriver  = ASS_AutoDetect;
int ASS_MIDISoundDriver = ASS_AutoDetect;
int ASS_EMIDICard = -1;

#define UNSUPPORTED_PCM          nullptr,nullptr,nullptr,nullptr,nullptr,nullptr
#define UNSUPPORTED_MIDI         EMIDI_GeneralMIDI,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr
#define UNSUPPORTED_COMPLETELY   nullptr,nullptr,UNSUPPORTED_PCM,UNSUPPORTED_MIDI

static struct
{
    const char *DriverName;

    int  (*GetError)(void);
    const char *(*ErrorString)(int);

    int  (*PCM_Init)(int *, int *, void *);
    void (*PCM_Shutdown)(void);
    int  (*PCM_BeginPlayback)(char *, int, int, void (*)(void));
    void (*PCM_StopPlayback)(void);
    void (*PCM_Lock)(void);
    void (*PCM_Unlock)(void);

    int  EMIDICardType;
    int  (*MIDI_Init)(midifuncs *);
    void (*MIDI_Shutdown)(void);
    int  (*MIDI_StartPlayback)(void);
    void (*MIDI_HaltPlayback)(void);
    void (*MIDI_SetTempo)(int tempo, int division);
    void (*MIDI_Lock)(void);
    void (*MIDI_Unlock)(void);
    void (*MIDI_Service)(void);
} SoundDrivers[ASS_NumSoundCards] = {

    // Simple DirectMedia Layer
    {
        "SDL",
    #ifdef RENDERTYPESDL
        SDLDrv_GetError,
        SDLDrv_ErrorString,
        SDLDrv_PCM_Init,
        SDLDrv_PCM_Shutdown,
        SDLDrv_PCM_BeginPlayback,
        SDLDrv_PCM_StopPlayback,
        SDLDrv_PCM_Lock,
        SDLDrv_PCM_Unlock,
        UNSUPPORTED_MIDI,
    #else
        UNSUPPORTED_COMPLETELY
    #endif
    },

    // Windows DirectSound
    {
        "DirectSound",
    #ifdef RENDERTYPEWIN
        DirectSoundDrv_GetError,
        DirectSoundDrv_ErrorString,
        DirectSoundDrv_PCM_Init,
        DirectSoundDrv_PCM_Shutdown,
        DirectSoundDrv_PCM_BeginPlayback,
        DirectSoundDrv_PCM_StopPlayback,
        DirectSoundDrv_PCM_Lock,
        DirectSoundDrv_PCM_Unlock,
        UNSUPPORTED_MIDI,
    #else
        UNSUPPORTED_COMPLETELY
    #endif
    },

    // OPL3 emulation
    {
        "AdLib OPL3 emulation",
        AdLibDrv_GetError,
        AdLibDrv_ErrorString,

        UNSUPPORTED_PCM,

        EMIDI_AdLib,
        AdLibDrv_MIDI_Init,
        AdLibDrv_MIDI_Shutdown,
        AdLibDrv_MIDI_StartPlayback,
        AdLibDrv_MIDI_HaltPlayback,
        AdLibDrv_MIDI_SetTempo,
        nullptr,
        nullptr,
        AdLibDrv_MIDI_Service,
    },

    // Windows MultiMedia system
    {
        "Windows MME",
    #ifdef _WIN32
        WinMMDrv_GetError,
        WinMMDrv_ErrorString,

        UNSUPPORTED_PCM,

        EMIDI_GeneralMIDI,
        WinMMDrv_MIDI_Init,
        WinMMDrv_MIDI_Shutdown,
        WinMMDrv_MIDI_StartPlayback,
        WinMMDrv_MIDI_HaltPlayback,
        WinMMDrv_MIDI_SetTempo,
        WinMMDrv_MIDI_Lock,
        WinMMDrv_MIDI_Unlock,
        WinMMDrv_MIDI_Service,
    #else
        UNSUPPORTED_COMPLETELY
    #endif
    },

    // TinySoundFont
    {
        "SoundFont2 synthesizer",
        SF2Drv_GetError,
        SF2Drv_ErrorString,

        UNSUPPORTED_PCM,

        EMIDI_GeneralMIDI,
        SF2Drv_MIDI_Init,
        SF2Drv_MIDI_Shutdown,
        SF2Drv_MIDI_StartPlayback,
        SF2Drv_MIDI_HaltPlayback,
        SF2Drv_MIDI_SetTempo,
        nullptr,
        nullptr,
        SF2Drv_MIDI_Service,
    },

    // ALSA MIDI synthesiser
    {
        "ALSA",
    #ifdef __linux__
        ALSADrv_GetError,
        ALSADrv_ErrorString,

        UNSUPPORTED_PCM,

        EMIDI_GeneralMIDI,
        ALSADrv_MIDI_Init,
        ALSADrv_MIDI_Shutdown,
        ALSADrv_MIDI_StartPlayback,
        ALSADrv_MIDI_HaltPlayback,
        ALSADrv_MIDI_SetTempo,
        ALSADrv_MIDI_Lock,
        ALSADrv_MIDI_Unlock,
        ALSADrv_MIDI_Service,
    #else
        UNSUPPORTED_COMPLETELY
    #endif
    },
};

int SoundDriver_IsPCMSupported(int driver)  { return (SoundDrivers[driver].PCM_Init != 0); }
int SoundDriver_IsMIDISupported(int driver) { return (SoundDrivers[driver].MIDI_Init != 0); }
const char *SoundDriver_GetName(int driver) { return  SoundDrivers[driver].DriverName; }

int SoundDriver_PCM_GetError(void)
{
    if (!SoundDriver_IsPCMSupported(ASS_PCMSoundDriver))
        return -1;

    return SoundDrivers[ASS_PCMSoundDriver].GetError();
}

const char * SoundDriver_PCM_ErrorString( int ErrorNumber )
{
    if (ASS_PCMSoundDriver < 0 || ASS_PCMSoundDriver >= ASS_NumSoundCards)
        return "No sound driver selected.";

    if (!SoundDriver_IsPCMSupported(ASS_PCMSoundDriver))
        return "Unsupported sound driver selected.";

    return SoundDrivers[ASS_PCMSoundDriver].ErrorString(ErrorNumber);
}

int SoundDriver_MIDI_GetError(void)
{
    if (!SoundDriver_IsMIDISupported(ASS_MIDISoundDriver))
        return -1;

    return SoundDrivers[ASS_MIDISoundDriver].GetError();
}

const char * SoundDriver_MIDI_ErrorString( int ErrorNumber )
{
    if (ASS_MIDISoundDriver < 0 || ASS_MIDISoundDriver >= ASS_NumSoundCards)
        return "No sound driver selected.";

    if (!SoundDriver_IsMIDISupported(ASS_MIDISoundDriver))
        return "Unsupported sound driver selected.";

    return SoundDrivers[ASS_MIDISoundDriver].ErrorString(ErrorNumber);
}

int SoundDriver_PCM_Init(int *mixrate, int *numchannels, void *initdata)
{
    return SoundDrivers[ASS_PCMSoundDriver].PCM_Init(mixrate, numchannels, initdata);
}

int SoundDriver_PCM_BeginPlayback(char *BufferStart, int BufferSize, int NumDivisions, void (*CallBackFunc)(void))
{
    return SoundDrivers[ASS_PCMSoundDriver].PCM_BeginPlayback(BufferStart, BufferSize, NumDivisions, CallBackFunc);
}

void SoundDriver_PCM_Shutdown(void)                        { SoundDrivers[ASS_PCMSoundDriver].PCM_Shutdown(); }
void SoundDriver_PCM_StopPlayback(void)                    { SoundDrivers[ASS_PCMSoundDriver].PCM_StopPlayback(); }
void SoundDriver_PCM_Lock(void)                            { SoundDrivers[ASS_PCMSoundDriver].PCM_Lock(); }
void SoundDriver_PCM_Unlock(void)                          { SoundDrivers[ASS_PCMSoundDriver].PCM_Unlock(); }
int  SoundDriver_MIDI_Init(midifuncs *funcs)               { return SoundDrivers[ASS_MIDISoundDriver].MIDI_Init(funcs); }
int  SoundDriver_MIDI_StartPlayback(void)                  { return SoundDrivers[ASS_MIDISoundDriver].MIDI_StartPlayback(); }
void SoundDriver_MIDI_Shutdown(void)                       { SoundDrivers[ASS_MIDISoundDriver].MIDI_Shutdown(); }
void SoundDriver_MIDI_HaltPlayback(void)                   { SoundDrivers[ASS_MIDISoundDriver].MIDI_HaltPlayback(); }
void SoundDriver_MIDI_SetTempo(int tempo, int division)    { SoundDrivers[ASS_MIDISoundDriver].MIDI_SetTempo(tempo, division); }
void SoundDriver_MIDI_Lock(void)                           { if (SoundDrivers[ASS_MIDISoundDriver].MIDI_Lock) SoundDrivers[ASS_MIDISoundDriver].MIDI_Lock(); }
void SoundDriver_MIDI_Unlock(void)                         { if (SoundDrivers[ASS_MIDISoundDriver].MIDI_Unlock) SoundDrivers[ASS_MIDISoundDriver].MIDI_Unlock(); }
int  SoundDriver_MIDI_GetCardType(void)                    { return SoundDrivers[ASS_MIDISoundDriver].EMIDICardType; }

// vim:ts=4:sw=4:expandtab:


code:
/*
 Copyright (C) 2009 Jonathon Fowler <jf@jonof.id.au>

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

 */

#ifndef DRIVERS_H
#define DRIVERS_H

#include "sndcards.h"
#include "midifuncs.h"

#ifdef __cplusplus
extern "C" {
#endif

int SoundDriver_IsPCMSupported(int driver);
int SoundDriver_IsMIDISupported(int driver);

const char *SoundDriver_GetName(int driver);

int         SoundDriver_PCM_GetError(void);
const char *SoundDriver_PCM_ErrorString(int ErrorNumber);
int         SoundDriver_MIDI_GetError(void);
const char *SoundDriver_MIDI_ErrorString(int ErrorNumber);

int  SoundDriver_PCM_Init(int *mixrate, int *numchannels, void *initdata);
void SoundDriver_PCM_Shutdown(void);
int  SoundDriver_PCM_BeginPlayback(char *BufferStart, int BufferSize, int NumDivisions, void (*CallBackFunc)(void));
void SoundDriver_PCM_StopPlayback(void);
void SoundDriver_PCM_Lock(void);
void SoundDriver_PCM_Unlock(void);

int  SoundDriver_MIDI_Init(midifuncs *);
void SoundDriver_MIDI_Shutdown(void);
int  SoundDriver_MIDI_StartPlayback(void);
void SoundDriver_MIDI_HaltPlayback(void);
void SoundDriver_MIDI_SetTempo(int tempo, int division);
void SoundDriver_MIDI_Lock(void);
void SoundDriver_MIDI_Unlock(void);
int  SoundDriver_MIDI_GetCardType(void);

#ifdef __cplusplus
}
#endif

#endif


code:
/*
 Copyright (C) 2009 Jonathon Fowler <jf@jonof.id.au>

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

 */

/**
 * FLAC source support for MultiVoc
 */

#include "compat.h"

#ifdef HAVE_FLAC

#define FLAC__NO_DLL

#ifdef __APPLE__
#include <FLAC/all.h>
#else
#include "FLAC/all.h"
#endif

#include "_multivc.h"
#include "multivoc.h"
#include "pitch.h"
#include "pragmas.h"

typedef struct
{
    void *ptr;
    size_t length;
    size_t pos;

    FLAC__StreamDecoder *stream;
    FLAC__uint64 sample_pos;

    char *block;
    size_t blocksize;

    VoiceNode *owner;
} flac_data;

// callbacks, round 1

static size_t read_flac(void *ptr, size_t size, size_t nmemb, FLAC__IOHandle datasource)
{
    flac_data *flac = (flac_data *)datasource;
    size_t nread = 0;
    size_t bytes;

    errno = 0;

    if (flac->length == flac->pos)
    {
        return 0;
    }

    for (; nmemb > 0; nmemb--, nread++)
    {
        bytes = flac->length - flac->pos;
        if (size < bytes)
        {
            bytes = size;
        }

        memcpy(ptr, (uint8_t *)flac->ptr + flac->pos, bytes);
        flac->pos += bytes;
        ptr = (uint8_t *)ptr + bytes;

        if (flac->length == flac->pos)
        {
            nread++;
            break;
        }
    }

    return nread;
}

static size_t write_flac(const void *ptr, size_t size, size_t nmemb, FLAC__IOHandle datasource)
{
    UNREFERENCED_PARAMETER(ptr);
    UNREFERENCED_PARAMETER(size);
    UNREFERENCED_PARAMETER(nmemb);
    UNREFERENCED_PARAMETER(datasource);

    return 0;
}

static int seek_flac(FLAC__IOHandle datasource, FLAC__int64 offset, int whence)
{
    flac_data *flac = (flac_data *)datasource;

    switch (whence)
    {
        case SEEK_SET: flac->pos = 0; break;
        case SEEK_CUR: break;
        case SEEK_END: flac->pos = flac->length; break;
    }

    flac->pos += offset;

    if (flac->pos > flac->length)
    {
        flac->pos = flac->length;
    }

    return 0;
}

static FLAC__int64 tell_flac(FLAC__IOHandle datasource)
{
    flac_data *flac = (flac_data *)datasource;

    return flac->pos;
}

static FLAC__int64 length_flac(FLAC__IOHandle datasource)
{
    flac_data *flac = (flac_data *)datasource;

    return flac->length;
}

static int eof_flac(FLAC__IOHandle datasource)
{
    flac_data *flac = (flac_data *)datasource;

    return (flac->pos == flac->length);
}

static int close_flac(FLAC__IOHandle datasource)
{
    UNREFERENCED_PARAMETER(datasource);
    return 0;
}

static FLAC__IOCallbacks flac_callbacks = {
    read_flac, write_flac, seek_flac, tell_flac, eof_flac, close_flac,
};

// callbacks, round 2

FLAC__StreamDecoderReadStatus read_flac_stream(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes,
                                               void *client_data)
{
    UNREFERENCED_PARAMETER(decoder);
    if (*bytes > 0)
    {
        *bytes = read_flac(buffer, sizeof(FLAC__byte), *bytes, client_data);
        if (errno)
            return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
        else if (*bytes == 0)
            return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
        else
            return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
    }
    else
        return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
}

FLAC__StreamDecoderSeekStatus seek_flac_stream(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset,
                                               void *client_data)
{
    UNREFERENCED_PARAMETER(decoder);
    if (seek_flac(client_data, absolute_byte_offset, SEEK_SET) < 0)
        return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
    else
        return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
}

FLAC__StreamDecoderTellStatus tell_flac_stream(const FLAC__StreamDecoder *decoder, FLAC__uint64 *absolute_byte_offset,
                                               void *client_data)
{
    UNREFERENCED_PARAMETER(decoder);
    *absolute_byte_offset = tell_flac(client_data);
    return FLAC__STREAM_DECODER_TELL_STATUS_OK;
}

FLAC__StreamDecoderLengthStatus length_flac_stream(const FLAC__StreamDecoder *decoder, FLAC__uint64 *stream_length,
                                                   void *client_data)
{
    UNREFERENCED_PARAMETER(decoder);
    *stream_length = length_flac(client_data);
    return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
}

FLAC__bool eof_flac_stream(const FLAC__StreamDecoder *decoder, void *client_data)
{
    UNREFERENCED_PARAMETER(decoder);
    return eof_flac(client_data);
}

FLAC__StreamDecoderWriteStatus write_flac_stream(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame,
                                                 const FLAC__int32 *const ibuffer[], void *client_data)
{
    flac_data *fd = (flac_data *)client_data;
    VoiceNode *voice = fd->owner;
    FLAC__uint64 samples = frame->header.blocksize;

    UNREFERENCED_PARAMETER(decoder);

    voice->channels = frame->header.channels;
    voice->bits = frame->header.bits_per_sample;
    voice->SamplingRate = frame->header.sample_rate;

    if (frame->header.number_type == FLAC__FRAME_NUMBER_TYPE_FRAME_NUMBER)
        fd->sample_pos = frame->header.number.frame_number;
    else if (frame->header.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER)
        fd->sample_pos = frame->header.number.sample_number;

    if ((FLAC__uint64)(uintptr_t)voice->Loop.End > 0 &&
        fd->sample_pos + samples >= (FLAC__uint64)(uintptr_t)voice->Loop.End)
    {
        samples = (FLAC__uint64)(uintptr_t)voice->Loop.End - fd->sample_pos;
        if (!FLAC__stream_decoder_seek_absolute(fd->stream, (FLAC__uint64)(uintptr_t)voice->Loop.Start))
            LOG_F(ERROR, "write_flac_stream: error in FLAC__stream_decoder_seek_absolute (LOOP_START %" PRIu64 ", LOOP_END %" PRIu64 ")",
                      (FLAC__uint64)(uintptr_t)voice->Loop.Start, (FLAC__uint64)(uintptr_t)voice->Loop.End);
    }

    size_t const size = samples * voice->channels * (voice->bits >> 3);

    voice->position = 0;
    // CODEDUP multivoc.c MV_SetVoicePitch
    voice->RateScale = divideu64((uint64_t)voice->SamplingRate * voice->PitchScale, MV_MixRate);
    voice->FixedPointBufferSize = (voice->RateScale * MV_MIXBUFFERSIZE) - voice->RateScale;
    MV_SetVoiceMixMode(voice);

    char * block = fd->block;

    if (size > fd->blocksize)
    {
        block = (char *)Xaligned_alloc(16, size);

        if (block == nullptr)
            return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
    }

    {
        char *obuffer = block;
        FLAC__uint64 sample;
        uint8_t channel;

        // this loop is adapted from code in ov_read_filter() in vorbisfile.c in libvorbis
        for (sample = 0; sample < samples; ++sample)
            for (channel = 0; channel < frame->header.channels; ++channel)
            {
                int8_t byte;
                FLAC__int32 val = ibuffer[channel][sample];
                if (val > (1 << (voice->bits - 1)) - 1)
                    val = (1 << (voice->bits - 1)) - 1;
                else if (val < -(1 << (voice->bits - 1)))
                    val = -(1 << (voice->bits - 1));
                for (byte = 0; byte < voice->bits; byte += 8) *obuffer++ = ((val >> byte) & 0x000000FF);
            }
    }

    voice->sound = block;
    voice->length = samples << 16;

    if (block != fd->block)
    {
        char * oldblock = fd->block;
        fd->block = block;
        fd->blocksize = size;
        ALIGNED_FREE_AND_NULL(oldblock);
    }

    return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}

void error_flac_stream(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data)
{
    // flac_data * fd = (flac_data *) client_data;
    UNREFERENCED_PARAMETER(client_data);
    UNREFERENCED_PARAMETER(decoder);
    LOG_F(ERROR, "%s", FLAC__StreamDecoderErrorStatusString[status]);
    // FLAC__stream_decoder_flush(fd->stream);
}

int MV_GetFLACPosition(VoiceNode *voice)
{
    FLAC__uint64 position = 0;
    flac_data *fd = (flac_data *)voice->rawdataptr;

    FLAC__stream_decoder_get_decode_position(fd->stream, &position);

    return position;
}

void MV_SetFLACPosition(VoiceNode *voice, int position)
{
    flac_data *fd = (flac_data *)voice->rawdataptr;

    FLAC__stream_decoder_seek_absolute(fd->stream, position);
}

/*---------------------------------------------------------------------
Function: MV_GetNextFLACBlock

Controls playback of FLAC data
---------------------------------------------------------------------*/

static playbackstatus MV_GetNextFLACBlock(VoiceNode *voice)

{
    flac_data *fd = (flac_data *)voice->rawdataptr;
    FLAC__StreamDecoderState decode_state;
    // FLAC__bool decode_status;

    if ((FLAC__uint64)(uintptr_t)voice->Loop.End > 0 && fd->sample_pos >= (FLAC__uint64)(uintptr_t)voice->Loop.End)
        if (!FLAC__stream_decoder_seek_absolute(fd->stream, (FLAC__uint64)(uintptr_t)voice->Loop.Start))
            LOG_F(ERROR, "MV_GetNextFLACBlock: error in FLAC__stream_decoder_seek_absolute (LOOP_START %" PRIu64 ", LOOP_END %" PRIu64 ")",
                      (FLAC__uint64)(uintptr_t)voice->Loop.Start, (FLAC__uint64)(uintptr_t)voice->Loop.End);

    /*decode_status =*/FLAC__stream_decoder_process_single(fd->stream);
    decode_state = FLAC__stream_decoder_get_state(fd->stream);

    /*
        if (!decode_status)
        {
            MV_Printf("MV_GetNextFLACBlock: %s\n", FLAC__StreamDecoderStateString[decode_state]);
            return NoMoreData;
        }
    */

    if (decode_state == FLAC__STREAM_DECODER_SEEK_ERROR)
    {
        FLAC__stream_decoder_flush(fd->stream);
        decode_state = FLAC__stream_decoder_get_state(fd->stream);
    }

    if (decode_state == FLAC__STREAM_DECODER_END_OF_STREAM)
    {
        if (voice->Loop.Size > 0)
        {
            if (!FLAC__stream_decoder_seek_absolute(fd->stream, (FLAC__uint64)(uintptr_t)voice->Loop.Start))
                LOG_F(ERROR, "MV_GetNextFLACBlock: error in FLAC__stream_decoder_seek_absolute (LOOP_START %" PRIu64 ")",
                          (FLAC__uint64)(uintptr_t)voice->Loop.Start);
        }
        else
            return NoMoreData;
    }

#if 0
    // unnecessary: duplicated in write_flac_stream()
    voice->channels     = FLAC__stream_decoder_get_channels(fd->stream);
    voice->bits         = FLAC__stream_decoder_get_bits_per_sample(fd->stream);
    voice->SamplingRate = FLAC__stream_decoder_get_sample_rate(fd->stream);
    // CODEDUP multivoc.c MV_SetVoicePitch
    voice->RateScale    = ( voice->SamplingRate * voice->PitchScale ) / MV_MixRate;
    voice->FixedPointBufferSize = ( voice->RateScale * MV_MIXBUFFERSIZE ) - voice->RateScale;
    MV_SetVoiceMixMode( voice );
#endif

    return KeepPlaying;
}

/*---------------------------------------------------------------------
Function: MV_PlayFLAC3D

Begin playback of sound data at specified angle and distance
from listener.
---------------------------------------------------------------------*/

int MV_PlayFLAC3D(char *ptr, uint32_t length, int loophow, int pitchoffset, int angle, int distance, int priority, fix16_t volume, intptr_t callbackval)
{
    int left;
    int right;
    int mid;
    int vol;
    int status;

    if (!MV_Installed)
        return MV_SetErrorCode(MV_NotInstalled);

    if (distance < 0)
    {
        distance = -distance;
        angle += MV_NUMPANPOSITIONS / 2;
    }

    vol = MIX_VOLUME(distance);

    // Ensure angle is within 0 - 127
    angle &= MV_MAXPANPOSITION;

    left = MV_PanTable[angle][vol].left;
    right = MV_PanTable[angle][vol].right;
    mid = max(0, 255 - distance);

    status = MV_PlayFLAC(ptr, length, loophow, -1, pitchoffset, mid, left, right, priority, volume, callbackval);

    return status;
}

/*---------------------------------------------------------------------
Function: MV_PlayFLAC

Begin playback of sound data with the given sound levels and
priority.
---------------------------------------------------------------------*/

int MV_PlayFLAC(char *ptr, uint32_t length, int loopstart, int loopend, int pitchoffset, int vol, int left, int right, int priority, fix16_t volume, intptr_t callbackval)
{
    UNREFERENCED_PARAMETER(loopend);

    if (!MV_Installed)
        return MV_SetErrorCode(MV_NotInstalled);

    // Request a voice from the voice pool
    auto voice = MV_AllocVoice(priority, sizeof(flac_data));
    if (voice == nullptr)
        return MV_SetErrorCode(MV_NoVoices);

    auto fd = (flac_data *)voice->rawdataptr;

    fd->owner      = voice;
    fd->ptr        = ptr;
    fd->pos        = 0;
    fd->blocksize  = 0;
    fd->length     = length;
    fd->block      = nullptr;
    fd->stream     = FLAC__stream_decoder_new();
    fd->sample_pos = 0;

    FLAC__stream_decoder_set_metadata_ignore_all(fd->stream);

    if (FLAC__stream_decoder_init_stream(fd->stream, read_flac_stream, seek_flac_stream, tell_flac_stream,
                                         length_flac_stream, eof_flac_stream, write_flac_stream,
                                         /*metadata_flac_stream*/ nullptr, error_flac_stream,
                                         (void *)fd) != FLAC__STREAM_DECODER_INIT_STATUS_OK)
    {
        LOG_F(ERROR, "MV_PlayFLAC: error in FLAC__stream_decoder_init_stream: %s", FLAC__stream_decoder_get_resolved_state_string(fd->stream));
        ALIGNED_FREE_AND_NULL(fd);
        return MV_SetErrorCode(MV_InvalidFile);
    }

    voice->wavetype = FMT_FLAC;
    voice->GetSound = MV_GetNextFLACBlock;
    voice->NextBlock = fd->block;
    voice->PitchScale = PITCH_GetScale(pitchoffset);
    voice->priority = priority;
    voice->callbackval = callbackval;

    voice->Loop = { nullptr, nullptr, 0, loopstart >= 0 };

    // parse metadata
    // loop parsing designed with multiple repetitions in mind
    // In retrospect, it may be possible to MV_GetVorbisCommentLoops(voice, (vorbis_comment *)
    // &tags->data.vorbis_comment)
    // but libvorbisfile may be confused by the signedness of char* vs FLAC__byte* and this code does not depend on
    // HAVE_VORBIS.
    auto metadata_chain = FLAC__metadata_chain_new();
    if (metadata_chain != nullptr)
    {
        if (FLAC__metadata_chain_read_with_callbacks(metadata_chain, fd, flac_callbacks))
        {
            FLAC__Metadata_Iterator *metadata_iterator = FLAC__metadata_iterator_new();
            if (metadata_iterator != nullptr)
            {
                char *vc_loopstart = nullptr;
                char *vc_loopend = nullptr;
                char *vc_looplength = nullptr;

                FLAC__metadata_iterator_init(metadata_iterator, metadata_chain);

                do
                {
                    FLAC__StreamMetadata *tags = FLAC__metadata_iterator_get_block(metadata_iterator);

                    if (tags->type == FLAC__METADATA_TYPE_STREAMINFO)
                    {
                        const FLAC__StreamMetadata_StreamInfo *info = &tags->data.stream_info;

                        if (info->channels != 1 && info->channels != 2)
                        {
                            FLAC__metadata_object_delete(tags);
                            FLAC__metadata_iterator_delete(metadata_iterator);
                            // FLAC__metadata_chain_delete(metadata_chain);
                            FLAC__stream_decoder_finish(fd->stream);
                            FLAC__stream_decoder_delete(fd->stream);
                            ALIGNED_FREE_AND_NULL(fd);
                            return MV_SetErrorCode(MV_InvalidFile);
                        }

                        voice->channels = info->channels;
                        voice->bits = info->bits_per_sample;
                        voice->SamplingRate = info->sample_rate;
                    }

                    // load loop tags from metadata
                    if (tags->type == FLAC__METADATA_TYPE_VORBIS_COMMENT)
                    {
                        FLAC__uint32 comment;
                        for (comment = 0; comment < tags->data.vorbis_comment.num_comments; ++comment)
                        {
                            const char *entry = (const char *)tags->data.vorbis_comment.comments[comment].entry;
                            if (entry != nullptr && entry[0] != '\0')
                            {
                                const char *value = strchr(entry, '=');
                                const size_t field = value - entry;
                                value += 1;

                                for (int t = 0; t < loopStartTagCount && vc_loopstart == nullptr; ++t)
                                {
                                    char const * const tag = loopStartTags[t];
                                    if (field == strlen(tag) && Bstrncasecmp(entry, tag, field) == 0)
                                        vc_loopstart = Xstrdup(value);
                                }

                                for (int t = 0; t < loopEndTagCount && vc_loopend == nullptr; ++t)
                                {
                                    char const * const tag = loopEndTags[t];
                                    if (field == strlen(tag) && Bstrncasecmp(entry, tag, field) == 0)
                                        vc_loopend = Xstrdup(value);
                                }

                                for (int t = 0; t < loopLengthTagCount && vc_looplength == nullptr; ++t)
                                {
                                    char const * const tag = loopLengthTags[t];
                                    if (field == strlen(tag) && Bstrncasecmp(entry, tag, field) == 0)
                                        vc_looplength = Xstrdup(value);
                                }
                            }
                        }
                    }

                    FLAC__metadata_object_delete(
                    tags);  // If it were not for this, I would assign pointers instead of strdup().
                } while (FLAC__metadata_iterator_next(metadata_iterator));

                if (vc_loopstart != nullptr)
                {
                    {
                        const FLAC__int64 flac_loopstart = atol(vc_loopstart);
                        if (flac_loopstart >= 0)  // a loop starting at 0 is valid
                        {
                            voice->Loop.Start = (const char *)(intptr_t)flac_loopstart;
                            voice->Loop.Size = 1;
                        }
                    }
                    Xfree(vc_loopstart);
                }
                if (vc_loopend != nullptr)
                {
                    if (voice->Loop.Size > 0)
                    {
                        const FLAC__int64 flac_loopend = atol(vc_loopend);
                        if (flac_loopend > 0)  // a loop ending at 0 is invalid
                            voice->Loop.End = (const char *)(intptr_t)flac_loopend;
                    }
                    Xfree(vc_loopend);
                }
                if (vc_looplength != nullptr)
                {
                    if (voice->Loop.Size > 0 && voice->Loop.End == 0)
                    {
                        const FLAC__int64 flac_looplength = atol(vc_looplength);
                        if (flac_looplength > 0)  // a loop of length 0 is invalid
                            voice->Loop.End = (const char *)((intptr_t)flac_looplength + (intptr_t)voice->Loop.Start);
                    }
                    Xfree(vc_looplength);
                }

                FLAC__metadata_iterator_delete(metadata_iterator);
            }
            else
                LOG_F(ERROR, "MV_PlayFLAC: error in FLAC__metadata_iterator_new");
        }
        else
            LOG_F(ERROR, "MV_PlayFLAC: error in FLAC__metadata_chain_read_with_callbacks: %s", FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(metadata_chain)]);

        // FLAC__metadata_chain_delete(metadata_chain); // when run with GDB, this throws SIGTRAP about freed heap
        // memory being modified
    }
    else
        LOG_F(ERROR, "MV_PlayFLAC: error in FLAC__metadata_chain_new");

    // CODEDUP multivoc.c MV_SetVoicePitch
    voice->RateScale = divideu64((uint64_t)voice->SamplingRate * voice->PitchScale, MV_MixRate);
    voice->FixedPointBufferSize = (voice->RateScale * MV_MIXBUFFERSIZE) - voice->RateScale;
    MV_SetVoiceMixMode(voice);

    MV_SetVoiceVolume(voice, vol, left, right, volume);
    MV_PlayVoice(voice);

    return voice->handle;
}

void MV_ReleaseFLACVoice(VoiceNode *voice)
{
    Bassert(voice->wavetype == FMT_FLAC && voice->rawdataptr != nullptr && voice->rawdatasiz == sizeof(flac_data));

    flac_data *fd = (flac_data *)voice->rawdataptr;
    voice->rawdataptr = nullptr;
    voice->rawdatasiz = 0;

    if (fd->stream != nullptr)
    {
        auto stream = fd->stream;
        fd->stream = nullptr;

        FLAC__stream_decoder_finish(stream);
        FLAC__stream_decoder_delete(stream);
    }

    auto block = fd->block;
    fd->block = nullptr;

    ALIGNED_FREE_AND_NULL(block);
    ALIGNED_FREE_AND_NULL(fd);
}
#else
#include "_multivc.h"

int MV_PlayFLAC(char *, uint32_t, int, int, int, int, int, int, int, fix16_t, intptr_t)
{
    LOG_F(ERROR, "MV_PlayFLAC: FLAC support not included in this binary.");
    return -1;
}

int MV_PlayFLAC3D(char *, uint32_t, int, int, int, int, int, fix16_t, intptr_t)
{
    LOG_F(ERROR, "MV_PlayFLAC: FLAC support not included in this binary.");
    return -1;
}
#endif  // HAVE_FLAC


code:
/*
 Copyright (C) 2009 Jonathon Fowler <jf@jonof.id.au>
 Copyright (C) 2015 EDuke32 developers
 Copyright (C) 2015 Voidpoint, LLC

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

 */

/**
 * Raw, WAV, and VOC source support for MultiVoc
 */

#include "_multivc.h"
#include "compat.h"
#include "multivoc.h"
#include "pitch.h"
#include "pragmas.h"

static playbackstatus MV_GetNextWAVBlock(VoiceNode *voice)
{
    if (voice->BlockLength == 0)
    {
        if (voice->Loop.Start == nullptr)
            return NoMoreData;

        voice->BlockLength = voice->Loop.Size;
        voice->NextBlock   = voice->Loop.Start;
        voice->length      = 0;
        voice->position    = 0;
    }

    voice->sound        = voice->NextBlock;
    voice->position    -= voice->length;
    voice->length       = min(voice->BlockLength, 0x8000u);
    voice->NextBlock   += voice->length * ((voice->channels * voice->bits) >> 3);
    voice->BlockLength -= voice->length;
    voice->length     <<= 16;

    return KeepPlaying;
}

static playbackstatus MV_GetNextVOCBlock(VoiceNode *voice)
{
    size_t   blocklength = 0;
    uint32_t samplespeed = 0;  // XXX: compiler-happy on synthesis
    uint32_t tc          = 0;
    unsigned BitsPerSample;
    unsigned Channels;
    unsigned Format;

    if (voice->BlockLength > 0)
    {
        voice->position    -= voice->length;
        voice->sound       += (voice->length >> 16) * ((voice->channels * voice->bits) >> 3);
        voice->length       = min(voice->BlockLength, 0x8000u);
        voice->BlockLength -= voice->length;
        voice->length     <<= 16;
        return KeepPlaying;
    }

    auto ptr = (uint8_t const *)voice->NextBlock;

    voice->Paused = FALSE;

    int voicemode = 0;
    int blocktype = 0;
    int lastblocktype = 0;
    int packtype = 0;

    int done = FALSE;

    do
    {
        // Stop playing if we get a null pointer
        if (ptr == nullptr)
        {
            done = 2;
            break;
        }

        // terminator is not mandatory according to
        // http://wiki.multimedia.cx/index.php?title=Creative_Voice

        if ((uint32_t)(ptr - (uint8_t *)voice->rawdataptr) >= voice->rawdatasiz)
            blocktype = 0;  // fake a terminator
        else
            blocktype = *ptr;

        if (blocktype != 0)
            blocklength = ptr[1]|(ptr[2]<<8)|(ptr[3]<<16);
        else
            blocklength = 0;
        // would need one byte pad at end of alloc'd region:
//        blocklength = B_LITTLE32(*(uint32_t *)(ptr + 1)) & 0x00ffffff;

        ptr += 4;

        switch (blocktype)
        {
        case 0 :
end_of_data:
            // End of data
            if ((voice->Loop.Start == nullptr) ||
                    ((intptr_t) voice->Loop.Start >= ((intptr_t) ptr - 4)))
            {
                done = 2;
            }
            else
            {
                voice->NextBlock    = voice->Loop.Start;
                voice->BlockLength  = 0;
                voice->position     = 0;
                return MV_GetNextVOCBlock(voice);
            }
            break;

        case 1 :
            // Sound data block
            voice->bits  = 8;
            voice->channels = voicemode + 1;
            if (lastblocktype != 8)
            {
                tc = (uint32_t)*ptr << 8;
                packtype = *(ptr + 1);
            }

            ptr += 2;
            blocklength -= 2;

            samplespeed = 256000000L / (voice->channels * (65536 - tc));

            // Skip packed or stereo data
            if ((packtype != 0) || (voicemode != 0 && voicemode != 1))
                ptr += blocklength;
            else
                done = TRUE;

            if ((uint32_t)(ptr - (uint8_t *)voice->rawdataptr) >= voice->rawdatasiz)
                goto end_of_data;

            voicemode = 0;
            break;

        case 2 :
            // Sound continuation block
            samplespeed = voice->SamplingRate;
            done = TRUE;
            break;

        case 3 :
            // Silence
        case 4 :
            // Marker
        case 5 :
            // ASCII string
            // All not implemented.
            ptr += blocklength;
            break;

        case 6 :
            // Repeat begin
            if (voice->Loop.End == nullptr)
            {
                voice->Loop.Count = B_LITTLE16(*(uint16_t const *)ptr);
                voice->Loop.Start = (char *)((intptr_t) ptr + blocklength);
            }
            ptr += blocklength;
            break;

        case 7 :
            // Repeat end
            ptr += blocklength;
            if (lastblocktype == 6)
                voice->Loop.Count = 0;
            else
            {
                if ((voice->Loop.Count > 0) && (voice->Loop.Start != nullptr))
                {
                    ptr = (uint8_t const *) voice->Loop.Start;

                    if (voice->Loop.Count < 0xffff)
                    {
                        if (--voice->Loop.Count == 0)
                            voice->Loop.Start = nullptr;
                    }
                }
            }
            break;

        case 8 :
            // Extended block
            voice->bits  = 8;
            voice->channels = 1;
            tc = B_LITTLE16(*(uint16_t const *)ptr);
            packtype = *(ptr + 2);
            voicemode = *(ptr + 3);
            ptr += blocklength;
            break;

        case 9 :
            // New sound data block
            samplespeed = B_LITTLE32(*(uint32_t const *)ptr);
            BitsPerSample = (unsigned)*(ptr + 4);
            Channels = (unsigned)*(ptr + 5);
            Format = (unsigned)B_LITTLE16(*(uint16_t const *)(ptr + 6));

            if ((BitsPerSample == 8) && (Channels == 1 || Channels == 2) && (Format == VOC_8BIT))
            {
                ptr         += 12;
                blocklength -= 12;
                voice->bits  = 8;
                voice->channels = Channels;
                done         = TRUE;
            }
            else if ((BitsPerSample == 16) && (Channels == 1 || Channels == 2) && (Format == VOC_16BIT))
            {
                ptr         += 12;
                blocklength -= 12;
                voice->bits  = 16;
                voice->channels = Channels;
                done         = TRUE;
            }
            else
            {
                ptr += blocklength;
            }

            // CAUTION:
            //  SNAKRM.VOC is corrupt!  blocklength gets us beyond the
            //  end of the file.
            if ((uint32_t)(ptr - (uint8_t *)voice->rawdataptr) >= voice->rawdatasiz)
                goto end_of_data;

            break;

        default :
            // Unknown data.  Probably not a VOC file.
            done = 2;
            break;
        }

        lastblocktype = blocktype;
    }
    while (!done);

    if (done != 2)
    {
        voice->NextBlock    = (char const *)ptr + blocklength;
        voice->sound        = (char const *)ptr;

        // CODEDUP multivoc.c MV_SetVoicePitch
        voice->SamplingRate = samplespeed;
        voice->RateScale    = divideu64((uint64_t)voice->SamplingRate * voice->PitchScale, MV_MixRate);

        // Multiply by MV_MIXBUFFERSIZE - 1
        voice->FixedPointBufferSize = (voice->RateScale * MV_MIXBUFFERSIZE) -
                                      voice->RateScale;

        if (voice->Loop.End != nullptr)
        {
            if (blocklength > (uintptr_t)voice->Loop.End)
                blocklength = (uintptr_t)voice->Loop.End;
            else
                voice->Loop.End = (char *)blocklength;

            voice->Loop.Start = voice->sound + (uintptr_t)voice->Loop.Start;
            voice->Loop.End   = voice->sound + (uintptr_t)voice->Loop.End;
            voice->Loop.Size  = voice->Loop.End - voice->Loop.Start;
        }

        if (voice->bits == 16)
            blocklength /= 2;

        if (voice->channels == 2)
            blocklength /= 2;

        voice->position     = 0;
        voice->length       = min<uint32_t>(blocklength, 0x8000u);
        voice->BlockLength  = blocklength - voice->length;
        voice->length     <<= 16;

        MV_SetVoiceMixMode(voice);

        return KeepPlaying;
    }

    return NoMoreData;
}

static playbackstatus MV_GetNextRAWBlock(VoiceNode *voice)
{
    if (voice->BlockLength == 0)
    {
        if (voice->Loop.Start == NULL)
            return NoMoreData;

        voice->BlockLength = voice->Loop.Size;
        voice->NextBlock   = voice->Loop.Start;
        voice->length      = 0;
        voice->position    = 0;
    }

    voice->sound        = voice->NextBlock;
    voice->position    -= voice->length;
    voice->length       = min(voice->BlockLength, 0x8000u);
    voice->NextBlock   += voice->length * (voice->channels * voice->bits / 8);
    voice->BlockLength -= voice->length;
    voice->length     <<= 16;

    return KeepPlaying;
}

int MV_PlayWAV3D(char *ptr, uint32_t length, int loophow, int pitchoffset, int angle, int distance,
                     int priority, fix16_t volume, intptr_t callbackval)
{
    if (!MV_Installed)
        return MV_Error;

    if (distance < 0)
    {
        distance  = -distance;
        angle    += MV_NUMPANPOSITIONS / 2;
    }

    int const vol = MIX_VOLUME(distance);

    // Ensure angle is within 0 - 127
    angle &= MV_MAXPANPOSITION;

    return MV_PlayWAV(ptr, length, loophow, -1, pitchoffset, max(0, 255 - distance),
        MV_PanTable[angle][vol].left, MV_PanTable[angle][vol].right, priority, volume, callbackval);
}

int MV_PlayWAV(char *ptr, uint32_t length, int loopstart, int loopend, int pitchoffset, int vol,
                   int left, int right, int priority, fix16_t volume, intptr_t callbackval)
{
    if (!MV_Installed)
        return MV_Error;

    riff_header   riff;
    memcpy(&riff, ptr, sizeof(riff_header));
    riff.file_size   = B_LITTLE32(riff.file_size);
    riff.format_size = B_LITTLE32(riff.format_size);

    if ((memcmp(riff.RIFF, "RIFF", 4) != 0) || (memcmp(riff.WAVE, "WAVE", 4) != 0) || (memcmp(riff.fmt, "fmt ", 4) != 0))
        return MV_SetErrorCode(MV_InvalidFile);

    format_header format;
    memcpy(&format, ptr + sizeof(riff_header), sizeof(format_header));
    format.wFormatTag      = B_LITTLE16(format.wFormatTag);
    format.nChannels       = B_LITTLE16(format.nChannels);
    format.nSamplesPerSec  = B_LITTLE32(format.nSamplesPerSec);
    format.nAvgBytesPerSec = B_LITTLE32(format.nAvgBytesPerSec);
    format.nBlockAlign     = B_LITTLE16(format.nBlockAlign);
    format.nBitsPerSample  = B_LITTLE16(format.nBitsPerSample);

    data_header   data;
    memcpy(&data, ptr + sizeof(riff_header) + riff.format_size, sizeof(data_header));
    data.size = B_LITTLE32(data.size);

    // Check if it's PCM data.
    if (format.wFormatTag != 1 || (format.nChannels != 1 && format.nChannels != 2) ||
        ((format.nBitsPerSample != 8) && (format.nBitsPerSample != 16)) || memcmp(data.DATA, "data", 4) != 0)
        return MV_SetErrorCode(MV_InvalidFile);

    // Request a voice from the voice pool

    auto voice = MV_AllocVoice(priority);

    if (voice == nullptr)
        return MV_SetErrorCode(MV_NoVoices);

    voice->wavetype    = FMT_WAV;
    voice->bits        = format.nBitsPerSample;
    voice->channels    = format.nChannels;
    voice->GetSound    = MV_GetNextWAVBlock;

    int blocklen = data.size;

    if (voice->bits == 16)
    {
        data.size  &= ~1;
        blocklen     /= 2;
    }

    if (voice->channels == 2)
    {
        data.size &= ~1;
        blocklen    /= 2;
    }

    voice->rawdataptr   = (uint8_t *)ptr;
    voice->rawdatasiz   = length;
    voice->position     = 0;
    voice->BlockLength  = blocklen;
    voice->NextBlock    = (char *)((intptr_t)ptr + sizeof(riff_header) + riff.format_size + sizeof(data_header));
    voice->priority     = priority;
    voice->callbackval  = callbackval;
    voice->Loop.Start   = loopstart >= 0 ? voice->NextBlock : nullptr;
    voice->Loop.End     = nullptr;
    voice->Loop.Count   = 0;
    voice->Loop.Size    = loopend > 0 ? loopend - loopstart + 1 : blocklen;

    MV_SetVoicePitch(voice, format.nSamplesPerSec, pitchoffset);
    MV_SetVoiceVolume(voice, vol, left, right, volume);
    MV_PlayVoice(voice);

    return voice->handle;
}

int MV_PlayVOC3D(char *ptr, uint32_t length, int loophow, int pitchoffset, int angle,
                     int distance, int priority, fix16_t volume, intptr_t callbackval)
{
    if (!MV_Installed)
        return MV_Error;

    if (distance < 0)
    {
        distance  = -distance;
        angle    += MV_NUMPANPOSITIONS / 2;
    }

    int const vol = MIX_VOLUME(distance);

    // Ensure angle is within 0 - 127
    angle &= MV_MAXPANPOSITION;

    return MV_PlayVOC(ptr, length, loophow, -1, pitchoffset, max(0, 255 - distance),
        MV_PanTable[angle][vol].left, MV_PanTable[angle][vol].right, priority, volume, callbackval);
}

int MV_PlayVOC(char *ptr, uint32_t length, int loopstart, int loopend, int pitchoffset, int vol,
                   int left, int right, int priority, fix16_t volume, intptr_t callbackval)
{
    if (!MV_Installed)
        return MV_Error;

    // Make sure it looks like a valid VOC file.
    if (memcmp(ptr, "Creative Voice File", 19) != 0)
        return MV_SetErrorCode(MV_InvalidFile);

    // Request a voice from the voice pool
    auto voice = MV_AllocVoice(priority);

    if (voice == nullptr)
        return MV_SetErrorCode(MV_NoVoices);

    voice->rawdataptr  = (uint8_t *)ptr;
    voice->rawdatasiz  = length;
    voice->wavetype    = FMT_VOC;
    voice->bits        = 8;
    voice->channels    = 1;
    voice->GetSound    = MV_GetNextVOCBlock;
    voice->NextBlock   = ptr + B_LITTLE16(*(uint16_t *)(ptr + 0x14));
    voice->PitchScale  = PITCH_GetScale(pitchoffset);
    voice->priority    = priority;
    voice->callbackval = callbackval;
    voice->Loop        = { loopstart >= 0 ? voice->NextBlock : nullptr, nullptr, 0, (uint32_t)(loopend - loopstart + 1) };

    MV_SetVoiceVolume(voice, vol, left, right, volume);
    MV_PlayVoice(voice);

    return voice->handle;
}

int MV_PlayRAW(char *ptr, uint32_t length, int rate, char *loopstart, char *loopend, int pitchoffset, int vol,
                   int left, int right, int priority, fix16_t volume, intptr_t callbackval)
{
    if (!MV_Installed)
        return MV_Error;

    // Request a voice from the voice pool
    auto voice = MV_AllocVoice(priority);

    if (voice == nullptr)
        return MV_SetErrorCode(MV_NoVoices);

    voice->rawdataptr  = (uint8_t *)ptr;
    voice->rawdatasiz  = length;
    voice->wavetype    = FMT_RAW;
    voice->bits        = 8;
    voice->channels    = 1;
    voice->GetSound    = MV_GetNextRAWBlock;
    voice->NextBlock   = ptr;
    voice->position    = 0;
    voice->BlockLength = length;
    voice->PitchScale  = PITCH_GetScale(pitchoffset);
    voice->priority    = priority;
    voice->callbackval = callbackval;
    voice->Loop        = { loopstart, loopend, 0, (uint32_t)(loopend - loopstart + 1) };
    voice->volume      = volume;

    MV_SetVoicePitch(voice, rate, pitchoffset);
    MV_SetVoiceVolume(voice, vol, left, right, volume);
    MV_PlayVoice(voice);

    return voice->handle;
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2016 EDuke32 developers and contributors

This file is part of EDuke32.

EDuke32 is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------

#include "fx_man.h"

#include "_multivc.h"
#include "compat.h"
#include "drivers.h"
#include "driver_adlib.h"
#include "driver_sf2.h"
#include "midi.h"
#include "multivoc.h"
#include "music.h"
#include "osd.h"

#ifdef _WIN32
# include "driver_winmm.h"
# include <mmsystem.h>
#endif

#ifdef RENDERTYPESDL
# include "driver_sdl.h"
#endif

#ifdef __linux__
# include "driver_alsa.h"
#endif

int FX_ErrorCode = FX_Ok;
int FX_Installed;
int FX_MixRate;

const char *FX_ErrorString(int const ErrorNumber)
{
    switch (ErrorNumber)
    {
        case FX_Warning:
        case FX_Error:          return FX_ErrorString(FX_ErrorCode);
        case FX_Ok:             return "Fx ok.";
        case FX_MultiVocError:  return MV_ErrorString(MV_Error);
        default:                return "Unknown Fx error code.";
    }
}

static int osdcmd_cvar_set_audiolib(osdcmdptr_t parm)
{
    int32_t r = osdcmd_cvar_set(parm);

    if (parm->numparms == 0)
    {
#ifdef _WIN32
        if (!Bstrcasecmp(parm->name, "mus_mme_device"))
            WinMMDrv_MIDI_PrintDevices();
#endif
#if defined RENDERTYPESDL && SDL_MAJOR_VERSION >= 2
        if (!Bstrcasecmp(parm->name, "snd_sdl_audiodriver"))
            SDLDrv_PCM_PrintDrivers();
#endif
    }

    if (r != OSDCMD_OK || parm->numparms < 1) return r;

    if (ASS_MIDISoundDriver == ASS_OPL3 && !Bstrcasecmp(parm->name, "mus_emidicard"))
        MIDI_Restart();
#ifdef _WIN32
    else if (ASS_MIDISoundDriver == ASS_WinMM && !Bstrcasecmp(parm->name, "mus_mme_device"))
        MIDI_Restart();
#endif
#ifdef __linux__
    else if (ASS_MIDISoundDriver == ASS_ALSA && (!Bstrcasecmp(parm->name, "mus_alsa_clientid") || !Bstrcasecmp(parm->name, "mus_alsa_portid")))
        MIDI_Restart();
#endif
    else if (ASS_MIDISoundDriver == ASS_SF2 && (!Bstrcasecmp(parm->name, "mus_sf2_bank") || !Bstrcasecmp(parm->name, "mus_sf2_sampleblocksize")))
        MIDI_Restart();
    else if (!Bstrcasecmp(parm->name, "mus_al_stereo"))
        AL_SetStereo(AL_Stereo);
#ifdef HAVE_XMP
    else if (!Bstrcasecmp(parm->name, "mus_xmp_interpolation"))
        MV_SetXMPInterpolation();
#endif
#if defined RENDERTYPESDL && SDL_MAJOR_VERSION >= 2
    else if (!Bstrcasecmp(parm->name, "snd_sdl_audiodriver"))
    {
        if (!FX_Installed || !Bstrcasecmp(parm->parms[0], SDLDrv_PCM_GetDriverName()))
            return r;

        if (!SDLDrv_PCM_CheckDriverName(parm->parms[0]))
        {
            SDLDrv_PCM_PrintDrivers();
            return r;
        }

        FX_Init(MV_MaxVoices, MV_Channels, MV_MixRate, MV_InitDataPtr);
    }
#endif

    return r;
}

void FX_InitCvars(void)
{
    static osdcvardata_t cvars_audiolib [] ={
        { "mus_emidicard", "force a specific EMIDI instrument set", (void*) &ASS_EMIDICard, CVAR_INT | CVAR_FUNCPTR, -1, 10 },
#ifdef __linux__
        { "mus_alsa_clientid", "specify the ALSA MIDI client ID", (void*) &ALSA_ClientID, CVAR_INT | CVAR_FUNCPTR, 0, 255 },
        { "mus_alsa_portid", "specify the ALSA MIDI port ID", (void*) &ALSA_PortID, CVAR_INT | CVAR_FUNCPTR, 0, 15 },
#endif
        { "mus_al_additivemode", "enable/disable alternate additive AdLib timbre mode", (void*) &AL_AdditiveMode, CVAR_BOOL, 0, 1 },
        { "mus_al_postamp", "controls post-synthesization OPL3 volume amplification", (void*) &AL_PostAmp, CVAR_FLOAT, 1, 6 },
        { "mus_al_stereo", "enable/disable OPL3 stereo mode", (void*) &AL_Stereo, CVAR_BOOL | CVAR_FUNCPTR, 0, 1 },
        { "mus_sf2_bank", "SoundFont 2 (.sf2) bank filename",  (void*) SF2_BankFile, CVAR_STRING | CVAR_FUNCPTR, 0, sizeof(SF2_BankFile) - 1 },
        { "mus_sf2_sampleblocksize", "number of samples per effect processing block", (void*) &SF2_EffectSampleBlockSize, CVAR_INT | CVAR_FUNCPTR, 1, 64 },
#ifdef _WIN32
        { "mus_mme_device", "select Windows MME MIDI output device", (void*) &WinMM_DeviceID, CVAR_INT | CVAR_FUNCPTR, -1, WinMMDrv_MIDI_GetNumDevices()-1 },
#endif
#if defined HAVE_XMP && 0
        { "mus_xmp_interpolation", "XMP output interpolation: 0: none  1: linear  2: spline", (void*) &MV_XMPInterpolation, CVAR_INT | CVAR_FUNCPTR, 0, 2 },
#endif
#if defined RENDERTYPESDL && SDL_MAJOR_VERSION >= 2
        { "snd_sdl_audiodriver", "select SDL audio driver (platform-specific)",
          (void *)SDLAudioDriverName, CVAR_STRING | CVAR_FUNCPTR, 0, sizeof(SDLAudioDriverName) - 1 },
#endif
        { "snd_lazyalloc", "use lazy sound allocations", (void*) &MV_LazyAlloc, CVAR_BOOL, 0, 1 },
    };

    for (auto& i : cvars_audiolib)
        OSD_RegisterCvar(&i, (i.flags & CVAR_FUNCPTR) ? osdcmd_cvar_set_audiolib : osdcmd_cvar_set);

#ifdef _WIN32
    OSD_RegisterFunction("mus_mme_debuginfo", "Windows MME MIDI buffer debug information", WinMMDrv_MIDI_PrintBufferInfo);
#endif
}

int FX_Init(int numvoices, int numchannels, int mixrate, void* initdata)
{
    if (FX_Installed)
        FX_Shutdown();

#if defined RENDERTYPESDL
    int SoundCard = ASS_SDL;
#elif defined RENDERTYPEWIN
    int SoundCard = ASS_DirectSound;
#endif

    VLOG_F(LOG_ASS, "Initializing Apogee Sound System");

    if (SoundDriver_IsPCMSupported(SoundCard) == 0)
    {
        // unsupported cards fall back to no sound
        FX_SetErrorCode(FX_InvalidCard);
        return FX_Error;
    }

    int status = FX_Ok;

    if (MV_Init(SoundCard, mixrate, numvoices, numchannels, initdata) != MV_Ok)
    {
        FX_SetErrorCode(FX_MultiVocError);
        LOG_F(ERROR, "Failed initializing sound: %s", MV_ErrorString(MV_DriverError));
        status = FX_Error;
    }

    FX_MixRate = MV_MixRate;

    if (status == FX_Ok)
    {
        VLOG_F(LOG_ASS, "Initialized sound at %.1f KHz %s with %d voices", MV_MixRate/1000.f, numchannels == 1 ? "mono" : "stereo", numvoices);
        FX_Installed = TRUE;
    }

    return status;
}

int FX_Shutdown(void)
{
    if (!FX_Installed)
        return FX_Ok;

    int status = MV_Shutdown();

    if (status != MV_Ok)
    {
        FX_SetErrorCode(FX_MultiVocError);
        status = FX_Error;
    }

    FX_Installed = FALSE;

    return status;
}

int FX_GetDevice(void) { return ASS_PCMSoundDriver; }

#define FMT_MAGIC(i, j, k, l) (i + (j << 8) + (k << 16) + (l << 24))
uint32_t constexpr FMT_CDXA_MAGIC = FMT_MAGIC('C','D','X','A');
uint32_t constexpr FMT_FLAC_MAGIC = FMT_MAGIC('f','L','a','C');
uint32_t constexpr FMT_OGG_MAGIC  = FMT_MAGIC('O','g','g','S');
uint32_t constexpr FMT_RIFF_MAGIC = FMT_MAGIC('R','I','F','F');
uint32_t constexpr FMT_VOC_MAGIC  = FMT_MAGIC('C','r','e','a');
uint32_t constexpr FMT_WAVE_MAGIC = FMT_MAGIC('W','A','V','E');
#undef FMT_MAGIC

static wavefmt_t FX_ReadFmt(char const * const ptr, uint32_t length)
{
    if (length < 16)
        return FMT_UNKNOWN;

    switch (B_LITTLE32(*(uint32_t const *)ptr))
    {
        case FMT_OGG_MAGIC:  return FMT_VORBIS;
        case FMT_VOC_MAGIC:  return FMT_VOC;
        case FMT_FLAC_MAGIC: return FMT_FLAC;
        case FMT_RIFF_MAGIC:
            if (B_LITTLE32(((uint32_t const *)ptr)[2]) == FMT_WAVE_MAGIC) return FMT_WAV;
            if (B_LITTLE32(((uint32_t const *)ptr)[2]) == FMT_CDXA_MAGIC) return FMT_XA;
            break;
        default:
            if (MV_IdentifyXMP(ptr, length)) return FMT_XMP;
            break;
    }

    return FMT_UNKNOWN;
}

static int FX_BadFmt(char *, uint32_t, int, int, int, int, int, int, int, fix16_t, intptr_t) { return MV_SetErrorCode(MV_InvalidFile); }
static int FX_BadFmt3D(char *, uint32_t, int, int, int, int, int, fix16_t, intptr_t)         { return MV_SetErrorCode(MV_InvalidFile); }

int FX_Play(char *ptr, uint32_t ptrlength, int loopstart, int loopend, int pitchoffset,
            int vol, int left, int right, int priority, fix16_t volume, intptr_t callbackval)
{
    static constexpr decltype(FX_Play) *func[] = { FX_BadFmt, nullptr, MV_PlayVOC, MV_PlayWAV, MV_PlayVorbis, MV_PlayFLAC, MV_PlayXA, MV_PlayXMP };

    EDUKE32_STATIC_ASSERT(FMT_MAX == ARRAY_SIZE(func));

    int handle = func[FX_ReadFmt(ptr, ptrlength)](ptr, ptrlength, loopstart, loopend, pitchoffset, vol, left, right, priority, volume, callbackval);

    if (EDUKE32_PREDICT_FALSE(handle <= MV_Ok))
    {
        FX_SetErrorCode(FX_MultiVocError);
        handle = FX_Warning;
    }

    return handle;
}

int FX_Play3D(char *ptr, uint32_t ptrlength, int loophow, int pitchoffset, int angle, int distance,
              int priority, fix16_t volume, intptr_t callbackval)
{
    static constexpr decltype(FX_Play3D) *func[] = { FX_BadFmt3D, nullptr, MV_PlayVOC3D, MV_PlayWAV3D, MV_PlayVorbis3D, MV_PlayFLAC3D, MV_PlayXA3D, MV_PlayXMP3D };

    EDUKE32_STATIC_ASSERT(FMT_MAX == ARRAY_SIZE(func));

    int handle = func[FX_ReadFmt(ptr, ptrlength)](ptr, ptrlength, loophow, pitchoffset, angle, distance, priority, volume, callbackval);

    if (EDUKE32_PREDICT_FALSE(handle <= MV_Ok))
    {
        FX_SetErrorCode(FX_MultiVocError);
        handle = FX_Warning;
    }

    return handle;
}

int FX_PlayRaw(char *ptr, uint32_t ptrlength, int rate, int pitchoffset, int vol,
    int left, int right, int priority, fix16_t volume, intptr_t callbackval)
{
    int handle = MV_PlayRAW(ptr, ptrlength, rate, NULL, NULL, pitchoffset, vol, left, right, priority, volume, callbackval);

    if (EDUKE32_PREDICT_FALSE(handle <= MV_Ok))
    {
        FX_SetErrorCode(FX_MultiVocError);
        handle = FX_Warning;
    }

    return handle;
}

int FX_PlayLoopedRaw(char *ptr, uint32_t ptrlength, char *loopstart, char *loopend, int rate,
    int pitchoffset, int vol, int left, int right, int priority, fix16_t volume, intptr_t callbackval)
{
    int handle = MV_PlayRAW(ptr, ptrlength, rate, loopstart, loopend, pitchoffset, vol, left, right, priority, volume, callbackval);

    if (EDUKE32_PREDICT_FALSE(handle <= MV_Ok))
    {
        FX_SetErrorCode(FX_MultiVocError);
        handle = FX_Warning;
    }

    return handle;
}

int FX_StartDemandFeedPlayback(void (*function)(const char** ptr, uint32_t* length, void* userdata), int bitdepth, int channels, int rate, int pitchoffset,
    int vol, int left, int right, int priority, fix16_t volume, intptr_t callbackval, void* userdata)
{
    int handle = MV_StartDemandFeedPlayback(function, bitdepth, channels, rate,
        pitchoffset, vol, left, right, priority, volume, callbackval, userdata);

    if (EDUKE32_PREDICT_FALSE(handle <= MV_Ok))
    {
        FX_SetErrorCode(FX_MultiVocError);
        handle = FX_Warning;
    }

    return handle;
}

int FX_StartDemandFeedPlayback3D(void (*function)(const char** ptr, uint32_t* length, void* userdata), int bitdepth, int channels, int rate, int pitchoffset,
    int angle, int distance, int priority, fix16_t volume, intptr_t callbackval, void* userdata)
{
    int handle = MV_StartDemandFeedPlayback3D(function, bitdepth, channels, rate,
        pitchoffset, angle, distance, priority, volume, callbackval, userdata);

    if (EDUKE32_PREDICT_FALSE(handle <= MV_Ok))
    {
        FX_SetErrorCode(FX_MultiVocError);
        handle = FX_Warning;
    }

    return handle;
}


code:
/*
Copyright (C) 1994-1995 Apogee Software, Ltd.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
#ifndef __linklist_h
#define __linklist_h
#ifdef __cplusplus
extern "C" {
#endif

#define NewNode(type)  ((type*)SafeMalloc(sizeof(type)))

#define LL_CreateNewLinkedList(rootnode,type,next,prev) 			\
   { 																				\
   (rootnode) = NewNode(type);                            		\
   (rootnode)->prev = (rootnode);                         		\
   (rootnode)->next = (rootnode);                         		\
   }

#define LL_AddNode(rootnode, newnode, next, prev) 			\
   {                                              			\
   (newnode)->next = (rootnode);                  			\
   (newnode)->prev = (rootnode)->prev;                	\
   (rootnode)->prev->next = (newnode);                	\
   (rootnode)->prev = (newnode);                      	\
   }

#define LL_TransferList(oldroot,newroot,next,prev)  \
   {                                                \
   if ((oldroot)->prev != (oldroot))                    \
      {                                             \
      (oldroot)->prev->next = (newroot);                \
      (oldroot)->next->prev = (newroot)->prev;          \
      (newroot)->prev->next = (oldroot)->next;          \
      (newroot)->prev = (oldroot)->prev;                \
      (oldroot)->next = (oldroot);                      \
      (oldroot)->prev = (oldroot);                      \
      }                                             \
   }

#define LL_ReverseList(root,type,next,prev)              \
   {                                                     \
   type *newend,*trav,*tprev;                            \
                                                         \
   newend = (root)->next;                                  \
   for(trav = (root)->prev; trav != newend; trav = tprev)  \
      {                                                  \
      tprev = trav->prev;                                \
      LL_MoveNode(trav,newend,next,prev);                \
      }                                                  \
   }

#define LL_RemoveNode(node,next,prev) \
   {                                  \
   (node)->prev->next = (node)->next;     \
   (node)->next->prev = (node)->prev;     \
   (node)->next = (node);                 \
   (node)->prev = (node);                 \
   }

#define LL_SortedInsertion(rootnode,insertnode,next,prev,type,sortparm) \
   {                                                                    \
   type *hoya;                                                          \
                                                                        \
   hoya = (rootnode)->next;                                               \
   while((hoya != (rootnode)) && ((insertnode)->sortparm > hoya->sortparm)) \
      {                                                                 \
      hoya = hoya->next;                                                \
      }                                                                 \
   LL_AddNode(hoya,(insertnode),next,prev);                               \
   }

#define LL_MoveNode(node,newroot,next,prev) \
   {                                        \
   LL_RemoveNode((node),next,prev);           \
   LL_AddNode((newroot),(node),next,prev);      \
   }

#define LL_ListEmpty(list,next,prev) \
   (                                 \
   ((list)->next == (list)) &&       \
   ((list)->prev == (list))          \
   )

#define LL_Free(list)   SafeFree(list)
#define LL_Reset(list,next,prev)    (list)->next = (list)->prev = (list)
#define LL_New      LL_CreateNewLinkedList
#define LL_Remove   LL_RemoveNode
#define LL_Add      LL_AddNode
#define LL_Empty    LL_ListEmpty
#define LL_Move     LL_MoveNode

#ifdef __cplusplus
};
#endif
#endif