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.

 */

#include "_multivc.h"

template uint32_t MV_MixMono<uint8_t, int16_t>(struct VoiceNode * const voice, uint32_t length);
template uint32_t MV_MixStereo<uint8_t, int16_t>(struct VoiceNode * const voice, uint32_t length);
template uint32_t MV_MixMono<int16_t, int16_t>(struct VoiceNode * const voice, uint32_t length);
template uint32_t MV_MixStereo<int16_t, int16_t>(struct VoiceNode * const voice, uint32_t length);
template void MV_Reverb<int16_t>(char const *src, char * const dest, const fix16_t volume, int count);

/*
 length = count of samples to mix
 position = offset of starting sample in source
 rate = resampling increment
 volume = direct volume adjustment, 1.0 = no change
 */

// mono source, mono output
template <typename S, typename D>
uint32_t MV_MixMono(struct VoiceNode * const voice, uint32_t length)
{
    auto const * __restrict source = (S const *)voice->sound;
    auto       * __restrict dest   = (D *)MV_MixDestination;

    uint32_t       position = voice->position;
    uint32_t const rate     = voice->RateScale;
    fix16_t const  volume   = fix16_fast_trunc_mul(voice->volume, MV_GlobalVolume);

    do
    {
        auto const isample0 = CONVERT_LE_SAMPLE_TO_SIGNED<S, D>(source[position >> 16]);

        position += rate;

        *dest = MIX_SAMPLES<D>(SCALE_SAMPLE(isample0, fix16_fast_trunc_mul(volume, voice->PannedVolume.Left)), *dest);
        dest++;

        voice->PannedVolume.Left = SMOOTH_VOLUME(voice->PannedVolume.Left, voice->GoalVolume.Left);
    }
    while (--length);

    MV_MixDestination = (char *)dest;

    return position;
}

// mono source, stereo output
template <typename S, typename D>
uint32_t MV_MixStereo(struct VoiceNode * const voice, uint32_t length)
{
    auto const * __restrict source = (S const *)voice->sound;
    auto       * __restrict dest   = (D *)MV_MixDestination;

    uint32_t       position = voice->position;
    uint32_t const rate     = voice->RateScale;
    fix16_t  const volume   = fix16_fast_trunc_mul(voice->volume, MV_GlobalVolume);

    do
    {
        auto const isample0 = CONVERT_LE_SAMPLE_TO_SIGNED<S, D>(source[position >> 16]);

        position += rate;

        *dest = MIX_SAMPLES<D>(SCALE_SAMPLE(isample0, fix16_fast_trunc_mul(volume, voice->PannedVolume.Left)), *dest);
        *(dest + (MV_RightChannelOffset / sizeof(*dest)))
            = MIX_SAMPLES<D>(SCALE_SAMPLE(isample0, fix16_fast_trunc_mul(volume, voice->PannedVolume.Right)), *(dest + (MV_RightChannelOffset / sizeof(*dest))));
        dest += 2;

        voice->PannedVolume = { SMOOTH_VOLUME(voice->PannedVolume.Left, voice->GoalVolume.Left), SMOOTH_VOLUME(voice->PannedVolume.Right, voice->GoalVolume.Right) };
    }
    while (--length);

    MV_MixDestination = (char *)dest;

    return position;
}

template <typename T>
void MV_Reverb(char const *src, char * const dest, const fix16_t volume, int count)
{
    auto input  = (T const *)src;
    auto output = (T *)dest;

    do
    {
        auto const isample0 = CONVERT_SAMPLE_TO_SIGNED<T>(*input++);
        *output++ = CONVERT_SAMPLE_FROM_SIGNED<T>(SCALE_SAMPLE(isample0, volume));
    }
    while (--count > 0);
}

repo_name: orochi/nblood

license: mit


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.

 */

#include "_multivc.h"

template uint32_t MV_MixMonoStereo<uint8_t, int16_t>(struct VoiceNode * const voice, uint32_t length);
template uint32_t MV_MixStereoStereo<uint8_t, int16_t>(struct VoiceNode * const voice, uint32_t length);
template uint32_t MV_MixMonoStereo<int16_t, int16_t>(struct VoiceNode * const voice, uint32_t length);
template uint32_t MV_MixStereoStereo<int16_t, int16_t>(struct VoiceNode * const voice, uint32_t length);

/*
 length = count of samples to mix
 position = offset of starting sample in source
 rate = resampling increment
 volume = direct volume adjustment, 1.0 = no change
 */

// stereo source, mono output
template <typename S, typename D>
uint32_t MV_MixMonoStereo(struct VoiceNode * const voice, uint32_t length)
{
    auto const * __restrict source = (S const *)voice->sound;
    auto       * __restrict dest   = (D *)MV_MixDestination;

    uint32_t       position = voice->position;
    uint32_t const rate     = voice->RateScale;
    fix16_t const  volume   = fix16_fast_trunc_mul(voice->volume, MV_GlobalVolume);

    do
    {
        auto const isample0 = CONVERT_LE_SAMPLE_TO_SIGNED<S, D>(source[(position >> 16) << 1]);
        auto const isample1 = CONVERT_LE_SAMPLE_TO_SIGNED<S, D>(source[((position >> 16) << 1) + 1]);

        position += rate;

        *dest = MIX_SAMPLES<D>((SCALE_SAMPLE((isample0 + isample1) >> 1, fix16_fast_trunc_mul(volume, voice->PannedVolume.Left))), *dest);
        dest++;

        voice->PannedVolume.Left = SMOOTH_VOLUME(voice->PannedVolume.Left, voice->GoalVolume.Left);
    }
    while (--length);

    MV_MixDestination = (char *)dest;

    return position;
}

// stereo source, stereo output
template <typename S, typename D>
uint32_t MV_MixStereoStereo(struct VoiceNode * const voice, uint32_t length)
{
    auto const * __restrict source = (S const *)voice->sound;
    auto       * __restrict dest   = (D *)MV_MixDestination;

    uint32_t       position = voice->position;
    uint32_t const rate     = voice->RateScale;
    fix16_t const  volume   = fix16_fast_trunc_mul(voice->volume, MV_GlobalVolume);

    do
    {
        auto const isample0 = CONVERT_LE_SAMPLE_TO_SIGNED<S, D>(source[(position >> 16) << 1]);
        auto const isample1 = CONVERT_LE_SAMPLE_TO_SIGNED<S, D>(source[((position >> 16) << 1) + 1]);

        position += rate;

        *dest = MIX_SAMPLES<D>(SCALE_SAMPLE(isample0, fix16_fast_trunc_mul(volume, voice->PannedVolume.Left)), *dest);
        *(dest + (MV_RightChannelOffset / sizeof(*dest)))
            = MIX_SAMPLES<D>(SCALE_SAMPLE(isample1, fix16_fast_trunc_mul(volume, voice->PannedVolume.Right)), *(dest + (MV_RightChannelOffset / sizeof(*dest))));
        dest += 2;

        voice->PannedVolume = { SMOOTH_VOLUME(voice->PannedVolume.Left, voice->GoalVolume.Left), SMOOTH_VOLUME(voice->PannedVolume.Right, voice->GoalVolume.Right) };
    }
    while (--length);

    MV_MixDestination = (char *)dest;

    return position;
}


code:
/*
Copyright (C) 1994-1995 Apogee Software, Ltd.
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.

*/
/**********************************************************************
   module: MULTIVOC.C

   author: James R. Dose
   date:   December 20, 1993

   Routines to provide multichannel digitized sound playback for
   Sound Blaster compatible sound cards.

   (c) Copyright 1993 James R. Dose.  All Rights Reserved.
**********************************************************************/

#include "multivoc.h"

#include "_multivc.h"
#include "baselayer.h"
#include "compat.h"
#include "drivers.h"
#include "fx_man.h"
#include "libasync_config.h"
#include "linklist.h"
#include "osd.h"
#include "pitch.h"
#include "pragmas.h"

#ifdef HAVE_XMP
# define BUILDING_STATIC
# include "libxmp-lite/xmp.h"

int MV_XMPInterpolation = XMP_INTERP_NEAREST;
#endif

static void MV_StopVoice(VoiceNode *voice);
static void MV_ServiceVoc(void);

static VoiceNode *MV_GetVoice(int handle);

static int MV_ReverbLevel;
static int MV_ReverbDelay;
static fix16_t MV_ReverbVolume;

Pan MV_PanTable[MV_NUMPANPOSITIONS][MV_MAXVOLUME + 1];

int MV_Installed;

int MV_BufferSize = MV_MIXBUFFERSIZE;
static int MV_BufferLength;

static int MV_NumberOfBuffers = MV_NUMBEROFBUFFERS;

int MV_MaxVoices = 1;
int MV_Channels = 1;
int MV_MixRate;
void *MV_InitDataPtr;

int MV_LazyAlloc = true;

#ifdef ASS_REVERSESTEREO
static int MV_ReverseStereo;
#endif

static int MV_BufferEmpty[MV_NUMBEROFBUFFERS];
char *MV_MixBuffer[(MV_NUMBEROFBUFFERS << 1) + 1];

VoiceNode *MV_Voices;
VoiceNode  VoiceList;
VoiceNode  VoicePool;

static int MV_MixPage;

static void (*MV_CallBackFunc)(intptr_t);

char *MV_MixDestination;
int MV_SampleSize = 1;
int MV_RightChannelOffset;

int MV_ErrorCode = MV_NotInstalled;

fix16_t MV_GlobalVolume = fix16_one;
fix16_t MV_VolumeSmoothFactor = fix16_one;

thread_local int MV_Locked;
char *MV_MusicBuffer;
static void (*MV_MusicCallback)(void);

static VoiceNode **MV_Handles;

static bool MV_Mix(VoiceNode * const voice, int const buffer)
{
    if (voice->task.valid())
    {
        if (!voice->task.ready())
            return true;

        auto result = voice->task.get();

        if (result != MV_Ok)
        {
            LOG_F(ERROR, "Error playing sound 0x%08" PRIxPTR ": %s", voice->callbackval, MV_ErrorString(result));
            return false;
        }
    }

    if (voice->length == 0 && voice->GetSound(voice) != KeepPlaying)
        return false;

    fix16_t const gv = MV_GlobalVolume;

    if (voice->priority == FX_MUSIC_PRIORITY)
        MV_GlobalVolume = fix16_one;

    int            length = MV_MIXBUFFERSIZE;
    uint32_t       bufsiz = voice->FixedPointBufferSize;
    uint32_t const rate   = voice->RateScale;

    MV_MixDestination = MV_MixBuffer[buffer];

    // Add this voice to the mix
    do
    {
        int            mixlen   = length;
        uint32_t const position = voice->position;
        uint32_t const voclen   = voice->length;

        // Check if the last sample in this buffer would be
        // beyond the length of the sample block
        if ((position + bufsiz) >= voclen)
        {
            if (position >= voclen - voice->channels)
            {
                if (voice->GetSound(voice) != KeepPlaying)
                {
                    MV_GlobalVolume = gv;
                    return false;
                }

                break;
            }

            mixlen = (voclen - position + rate - voice->channels) / rate;
        }

        voice->position = voice->mix(voice, mixlen);
        length -= mixlen;

        if (voice->position >= voclen - voice->channels)
        {
            // Get the next block of sound
            if (voice->GetSound(voice) != KeepPlaying)
            {
                MV_GlobalVolume = gv;
                return false;
            }

            // Get the position of the last sample in the buffer
            if (length > (voice->channels - 1))
                bufsiz = voice->RateScale * (length - voice->channels);
        }
    } while (length > 0);

    MV_GlobalVolume = gv;
    return true;
}

void MV_PlayVoice(VoiceNode *voice)
{
    MV_Lock();
    LL::SortedInsert(&VoiceList, voice, &VoiceNode::priority);
    voice->PannedVolume = voice->GoalVolume;
    voice->Paused.store(false, std::memory_order_release);
    MV_Unlock();
}

static void MV_FreeHandle(VoiceNode* voice)
{
    if (voice->handle < MV_MINVOICEHANDLE)
        return;

    MV_Handles[voice->handle - MV_MINVOICEHANDLE] = nullptr;
    voice->handle = 0;
    voice->length = 0;
    voice->sound = nullptr;
    voice->wavetype = FMT_UNKNOWN;
    LL::Move(voice, &VoicePool);
}

static void MV_CleanupVoice(VoiceNode* voice)
{
    if (MV_CallBackFunc)
        MV_CallBackFunc(voice->callbackval);

    switch (voice->wavetype)
    {
#ifdef HAVE_VORBIS
        case FMT_VORBIS: MV_ReleaseVorbisVoice(voice); break;
#endif
#ifdef HAVE_FLAC
        case FMT_FLAC:   MV_ReleaseFLACVoice(voice); break;
#endif
        case FMT_XA:     MV_ReleaseXAVoice(voice); break;
#ifdef HAVE_XMP
        case FMT_XMP:    MV_ReleaseXMPVoice(voice); break;
#endif
        default:
            // these are in the default case of this switch instead of down below because the functions above only zero them if MV_LazyAlloc is false
            voice->rawdataptr = nullptr;
            voice->rawdatasiz = 0;
            break;
    }
}

static void MV_StopVoice(VoiceNode *voice)
{
    MV_CleanupVoice(voice);
    MV_Lock();
    // move the voice from the play list to the free list
    MV_FreeHandle(voice);
    MV_Unlock();
}

/*---------------------------------------------------------------------
   JBF: no synchronisation happens inside MV_ServiceVoc nor the
        supporting functions it calls. This would cause a deadlock
        between the mixer thread in the driver vs the nested
        locking in the user-space functions of MultiVoc. The call
        to MV_ServiceVoc is synchronised in the driver.
---------------------------------------------------------------------*/
static void MV_ServiceVoc(void)
{
    // Toggle which buffer we'll mix next
    ++MV_MixPage;
    MV_MixPage &= MV_NumberOfBuffers-1;

    if (MV_ReverbLevel == 0)
    {
        if (!MV_BufferEmpty[MV_MixPage])
        {
            Bmemset(MV_MixBuffer[MV_MixPage], 0, MV_BufferSize);
            MV_BufferEmpty[MV_MixPage] = TRUE;
        }
    }
    else
    {
        char const *const __restrict end    = MV_MixBuffer[0] + MV_BufferLength;
        char *            __restrict dest   = MV_MixBuffer[MV_MixPage];
        char const *      __restrict source = MV_MixBuffer[MV_MixPage] - MV_ReverbDelay;

        if (source < MV_MixBuffer[0])
            source += MV_BufferLength;

        int length = MV_BufferSize;

        do
        {
            int const count = (source + length > end) ? (end - source) : length;

            MV_Reverb<int16_t>(source, dest, MV_ReverbVolume, count >> 1);

            // if we go through the loop again, it means that we've wrapped around the buffer
            source  = MV_MixBuffer[0];
            dest   += count;
            length -= count;
        } while (length > 0);
    }

    VoiceNode *MusicVoice = nullptr;

    if (VoiceList.next && VoiceList.next != &VoiceList)
    {
        auto voice = VoiceList.next;
        VoiceNode *next;

        do
        {
            next = voice->next;

            if (voice->Paused.load(std::memory_order_acquire))
                continue;

            if (voice->priority == FX_MUSIC_PRIORITY)
            {
                MusicVoice = voice;
                continue;
            }

            MV_BufferEmpty[ MV_MixPage ] = FALSE;

            // Is this voice done?
            if (!MV_Mix(voice, MV_MixPage))
            {
                MV_CleanupVoice(voice);
                MV_FreeHandle(voice);
            }
        }
        while ((voice = next) != &VoiceList);
    }

    Bmemcpy(MV_MixBuffer[MV_MixPage+MV_NumberOfBuffers], MV_MixBuffer[MV_MixPage], MV_BufferSize);

    if (MV_MusicCallback)
    {
        MV_MusicCallback();
        int16_t * __restrict source = (int16_t*)MV_MusicBuffer;
        int16_t * __restrict dest = (int16_t*)MV_MixBuffer[MV_MixPage+MV_NumberOfBuffers];
        for (int32_t i = 0; i < MV_BufferSize>>1; i++, dest++)
            *dest = clamp(*dest + *source++,INT16_MIN, INT16_MAX);
    }

    if (MusicVoice && !MV_Mix(MusicVoice, MV_MixPage + MV_NumberOfBuffers))
    {
        MV_CleanupVoice(MusicVoice);
        MV_FreeHandle(MusicVoice);
    }
}

static VoiceNode *MV_GetVoice(int handle)
{
    if (handle < MV_MINVOICEHANDLE || handle > MV_MaxVoices)
    {
        LOG_F(WARNING, "No voice found for handle 0x%08x", handle);
        return nullptr;
    }

    if (MV_Handles[handle - MV_MINVOICEHANDLE] != nullptr)
        return MV_Handles[handle - MV_MINVOICEHANDLE];

    MV_SetErrorCode(MV_VoiceNotFound);
    return nullptr;
}

VoiceNode *MV_BeginService(int handle)
{
    if (!MV_Installed)
        return nullptr;

    auto voice = MV_GetVoice(handle);

    if (voice == nullptr)
    {
        MV_SetErrorCode(MV_VoiceNotFound);
        return nullptr;
    }

    if (voice->task.valid() && !voice->task.ready())
        voice->task.wait();

    MV_Lock();

    return voice;
}

static inline void MV_EndService(void) { MV_Unlock(); }

int MV_VoicePlaying(int handle)
{
    Bassert(handle <= MV_MaxVoices);
    auto voice = MV_Handles[handle - MV_MINVOICEHANDLE];
    return MV_Installed && voice != nullptr && !voice->Paused.load(std::memory_order_relaxed);
}

int MV_KillAllVoices(void)
{
    if (!MV_Installed)
        return MV_Error;

    MV_Lock();

    if (&VoiceList == VoiceList.next)
    {
        MV_Unlock();
        return MV_Ok;
    }

    auto voice = VoiceList.prev;

    // Remove all the voices from the list
    while (voice != &VoiceList)
    {
        if (voice->priority == MV_MUSIC_PRIORITY)
        {
            voice = voice->prev;
            continue;
        }

        MV_Kill(voice->handle);
        voice = VoiceList.prev;
    }

    MV_Unlock();

    return MV_Ok;
}

int MV_Kill(int handle)
{
    auto voice = MV_BeginService(handle);

    if (voice == nullptr)
        return MV_Error;

    MV_StopVoice(voice);
    MV_EndService();

    return MV_Ok;
}

int MV_VoicesPlaying(void)
{
    if (!MV_Installed)
        return 0;

    MV_Lock();

    int NumVoices = 0;

    for (auto voice = VoiceList.next; voice != &VoiceList; voice = voice->next)
        NumVoices++;

    MV_Unlock();

    return NumVoices;
}

static inline VoiceNode *MV_GetLowestPriorityVoice(void)
{
    auto voice = VoiceList.next;

    // find the voice with the lowest priority and volume
    for (auto node = voice; node != &VoiceList; node = node->next)
    {
        if (node->priority < voice->priority
            || (node->priority == voice->priority && node->PannedVolume.Left < voice->PannedVolume.Left && node->PannedVolume.Right < voice->PannedVolume.Right))
            voice = node;
    }

    return voice;
}

static inline void MV_FinishAllocation(VoiceNode* voice, uint32_t const allocsize)
{
    if (voice->rawdataptr != nullptr && voice->rawdatasiz == allocsize)
        return;
    else if (voice->rawdataptr != nullptr && voice->wavetype >= FMT_VORBIS)
    {
        // this is sort of a hack... wavetypes less than FMT_VORBIS never do their own allocations, so don't bother trying to free them
        ALIGNED_FREE_AND_NULL(voice->rawdataptr);
    }

    voice->rawdatasiz = allocsize;
    voice->rawdataptr = Xaligned_alloc(16, allocsize);
    Bmemset(voice->rawdataptr, 0, allocsize);
}

VoiceNode *MV_AllocVoice(int priority, uint32_t allocsize /* = 0 */)
{
    MV_Lock();

    // Check if we have any free voices
    if (LL::Empty(&VoicePool))
    {
        auto voice = MV_GetLowestPriorityVoice();

        if (voice != &VoiceList && voice->priority <= priority && voice->handle >= MV_MINVOICEHANDLE && FX_SoundValidAndActive(voice->handle))
            MV_Kill(voice->handle);

        if (LL::Empty(&VoicePool))
        {
            // No free voices
            MV_Unlock();
            return nullptr;
        }
    }

    auto voice = VoicePool.next;
    LL::Remove(voice);

    int handle = MV_MINVOICEHANDLE;

    // Find a free voice handle
    do
    {
        if (++handle > MV_MaxVoices)
            handle = MV_MINVOICEHANDLE;
    } while (MV_Handles[handle - MV_MINVOICEHANDLE] != nullptr);
    MV_Handles[handle - MV_MINVOICEHANDLE] = voice;

    voice->length = 0;
    voice->BlockLength = 0;
    voice->handle = handle;
    voice->next = voice->prev = nullptr;
    MV_Unlock();

    if (allocsize)
        MV_FinishAllocation(voice, allocsize);

    return voice;
}

int MV_VoiceAvailable(int priority)
{
    // Check if we have any free voices
    if (!LL::Empty(&VoicePool))
        return TRUE;

    MV_Lock();
    auto const voice = MV_GetLowestPriorityVoice();
    MV_Unlock();

    return (voice == &VoiceList || voice->priority > priority) ? FALSE : TRUE;
}

void MV_SetVoicePitch(VoiceNode *voice, uint32_t rate, int pitchoffset)
{
    voice->SamplingRate = rate;
    voice->PitchScale   = PITCH_GetScale(pitchoffset);
    voice->RateScale    = divideu64((uint64_t)rate * voice->PitchScale, MV_MixRate);

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

int MV_SetPitch(int handle, int pitchoffset)
{
    auto voice = MV_BeginService(handle);

    if (voice == nullptr)
        return MV_Error;

    MV_SetVoicePitch(voice, voice->SamplingRate, pitchoffset);
    MV_EndService();

    return MV_Ok;
}

int MV_SetFrequency(int handle, int frequency)
{
    auto voice = MV_BeginService(handle);

    if (voice == nullptr)
        return MV_Error;

    MV_SetVoicePitch(voice, frequency, 0);
    MV_EndService();

    return MV_Ok;
}

int MV_GetFrequency(int handle, int *frequency)
{
    auto voice = MV_BeginService(handle);

    if (voice == NULL || !frequency)
        return MV_Error;

    if (voice->SamplingRate == 0)
        voice->GetSound(voice);

    *frequency = voice->SamplingRate;
    MV_EndService();

    return MV_Ok;
}

/*---------------------------------------------------------------------
   Function: MV_SetVoiceMixMode

   Selects which method should be used to mix the voice.

   16Bit        16Bit |  8Bit  16Bit  8Bit  16Bit |
   Mono         Ster  |  Mono  Mono   Ster  Ster  |  Mixer
   Out          Out   |  In    In     In    In    |
----------------------+---------------------------+-------------
    X                 |         X                 | MixMono<int16_t, int16_t>
    X                 |   X                       | MixMono<uint8_t, int16_t>
                 X    |         X                 | MixStereo<int16_t, int16_t>
                 X    |   X                       | MixStereo<uint8_t, int16_t>
----------------------+---------------------------+-------------
                 X    |                      X    | MixStereoStereo<int16_t, int16_t>
                 X    |                X          | MixStereoStereo<uint8_t, int16_t>
    X                 |                      X    | MixMonoStereo<int16_t, int16_t>
    X                 |                X          | MixMonoStereo<uint8_t, int16_t>
---------------------------------------------------------------------*/

void MV_SetVoiceMixMode(VoiceNode *voice)
{
    // stereo look-up table
    static constexpr decltype(voice->mix) mixslut[]
    = { MV_MixStereo<uint8_t, int16_t>,       MV_MixMono<uint8_t, int16_t>,       MV_MixStereo<int16_t, int16_t>,       MV_MixMono<int16_t, int16_t>,
        MV_MixStereoStereo<uint8_t, int16_t>, MV_MixMonoStereo<uint8_t, int16_t>, MV_MixStereoStereo<int16_t, int16_t>, MV_MixMonoStereo<int16_t, int16_t> };

    // corresponds to T_MONO, T_16BITSOURCE, and T_STEREOSOURCE
    voice->mix = mixslut[(MV_Channels == 1) | ((voice->bits == 16) << 1) | ((voice->channels == 2) << 2)];
}

void MV_SetVoiceVolume(VoiceNode *voice, int vol, int left, int right, fix16_t volume)
{
    if (MV_Channels == 1)
        left = right = vol;
#ifdef ASS_REVERSESTEREO
    else if (MV_ReverseStereo)
        swap(&left, &right);
#endif

    voice->GoalVolume = { fix16_smul(fix16_from_int(left), F16(1.f/MV_MAXTOTALVOLUME)), fix16_smul(fix16_from_int(right), F16(1.f/MV_MAXTOTALVOLUME)) };
    voice->volume = volume;

    MV_SetVoiceMixMode(voice);
}

int MV_PauseVoice(int handle, int pause)
{
    auto voice = MV_BeginService(handle);

    if (voice == nullptr)
        return MV_Error;

    voice->Paused.store(pause, std::memory_order_release);
    MV_EndService();

    return MV_Ok;
}

int MV_GetPosition(int handle, int *position)
{
    auto voice = MV_BeginService(handle);

    if (voice == nullptr)
        return MV_Error;

    switch (voice->wavetype)
    {
#ifdef HAVE_VORBIS
        case FMT_VORBIS: *position = MV_GetVorbisPosition(voice); break;
#endif
#ifdef HAVE_FLAC
        case FMT_FLAC:   *position = MV_GetFLACPosition(voice); break;
#endif
        case FMT_XA:     *position = MV_GetXAPosition(voice); break;
#ifdef HAVE_XMP
        case FMT_XMP:    *position = MV_GetXMPPosition(voice); break;
#endif
        default:         *position = (int)max<intptr_t>(0, (((intptr_t)voice->NextBlock + (intptr_t)voice->position - (intptr_t)voice->rawdataptr) >> 16) * ((voice->channels * voice->bits) >> 3)); break;
    }

    MV_EndService();

    return MV_Ok;
}

int MV_SetPosition(int handle, int position)
{
    auto voice = MV_BeginService(handle);

    if (voice == nullptr)
        return MV_Error;

    switch (voice->wavetype)
    {
#ifdef HAVE_VORBIS
        case FMT_VORBIS: MV_SetVorbisPosition(voice, position); break;
#endif
#ifdef HAVE_FLAC
        case FMT_FLAC:   MV_SetFLACPosition(voice, position); break;
#endif
        case FMT_XA:     MV_SetXAPosition(voice, position); break;
#ifdef HAVE_XMP
        case FMT_XMP:    MV_SetXMPPosition(voice, position); break;
#endif
        default: break;
    }

    MV_EndService();

    return MV_Ok;
}

int MV_EndLooping(int handle)
{
    auto voice = MV_BeginService(handle);

    if (voice == nullptr)
        return MV_Error;

    voice->Loop = {};

    MV_EndService();

    return MV_Ok;
}

int MV_SetPan(int handle, int vol, int left, int right)
{
    auto voice = MV_BeginService(handle);

    if (voice == nullptr)
        return MV_Error;

    MV_SetVoiceVolume(voice, vol, left, right, voice->volume);
    MV_EndService();
    return MV_Ok;
}

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

    int const volume = MIX_VOLUME(distance);

    angle &= MV_MAXPANPOSITION;

    return MV_SetPan(handle, max(0, 255 - distance),
        MV_PanTable[angle][volume].left,
        MV_PanTable[angle][volume].right);
}

void MV_SetReverb(int reverb)
{
    MV_ReverbLevel = MIX_VOLUME(reverb);
    MV_ReverbVolume = fix16_smul(fix16_from_int(MV_ReverbLevel), F16(1.f/MV_MAXVOLUME));
}

int MV_GetMaxReverbDelay(void) { return MV_MIXBUFFERSIZE * MV_NumberOfBuffers; }
int MV_GetReverbDelay(void) { return tabledivide32(MV_ReverbDelay, MV_SampleSize); }

void MV_SetReverbDelay(int delay)
{
    MV_ReverbDelay = max(MV_MIXBUFFERSIZE, min(delay, MV_GetMaxReverbDelay())) * MV_SampleSize;
}

static int MV_SetMixMode(int numchannels)
{
    if (!MV_Installed)
        return MV_Error;

    MV_Channels = 1 + (numchannels == 2);
    MV_SampleSize = sizeof(int16_t) * MV_Channels;

    MV_BufferSize = MV_MIXBUFFERSIZE * MV_SampleSize;
    MV_NumberOfBuffers = tabledivide32(MV_TOTALBUFFERSIZE, MV_BufferSize);
    Bassert(isPow2(MV_NumberOfBuffers));
    MV_BufferLength = MV_TOTALBUFFERSIZE;

    MV_RightChannelOffset = MV_SampleSize >> 1;

    return MV_Ok;
}

static int MV_StartPlayback(void)
{
    // Initialize the buffers
    Bmemset(MV_MixBuffer[0], 0, MV_TOTALBUFFERSIZE << 1);

    for (int buffer = 0; buffer < MV_NumberOfBuffers; buffer++)
        MV_BufferEmpty[buffer] = TRUE;

    MV_MixPage = 1;

    if (SoundDriver_PCM_BeginPlayback(MV_MixBuffer[MV_NumberOfBuffers], MV_BufferSize, MV_NumberOfBuffers, MV_ServiceVoc) != MV_Ok)
        return MV_SetErrorCode(MV_DriverError);

    return MV_Ok;
}

static void MV_StopPlayback(void)
{
    SoundDriver_PCM_StopPlayback();

    // Make sure all callbacks are done.
    MV_Lock();

    for (VoiceNode *voice = VoiceList.next, *next; voice != &VoiceList; voice = next)
    {
        next = voice->next;
        MV_StopVoice(voice);
    }

    MV_Unlock();
}

static void MV_CalcPanTable(void)
{
    const int HalfAngle = MV_NUMPANPOSITIONS / 2;
    const int QuarterAngle = HalfAngle / 2;

    for (int distance = 0; distance <= MV_MAXVOLUME; distance++)
    {
        const int level = (255 * (MV_MAXVOLUME - distance)) / MV_MAXVOLUME;

        for (int angle = 0; angle <= QuarterAngle; angle++)
        {
            const int ramp = level - (level * angle) / QuarterAngle;

            MV_PanTable[angle][distance].left = ramp;
            MV_PanTable[angle][distance].right = level;

            MV_PanTable[HalfAngle - angle][distance].left = ramp;
            MV_PanTable[HalfAngle - angle][distance].right = level;

            MV_PanTable[HalfAngle + angle][distance].left = level;
            MV_PanTable[HalfAngle + angle][distance].right = ramp;

            MV_PanTable[MV_MAXPANPOSITION - angle][distance].left = level;
            MV_PanTable[MV_MAXPANPOSITION - angle][distance].right = ramp;
        }
    }
}

void MV_SetVolume(int volume) { MV_GlobalVolume = fix16_smul(fix16_from_int(volume), F16(1.f/MV_MAXTOTALVOLUME)); }

int MV_GetVolume(void) { return Blrintf(fix16_to_float(MV_GlobalVolume) * MV_MAXTOTALVOLUME); }

void MV_SetCallBack(void (*function)(intptr_t)) { MV_CallBackFunc = function; }

#ifdef ASS_REVERSESTEREO
void MV_SetReverseStereo(int setting) { MV_ReverseStereo = setting; }
int MV_GetReverseStereo(void) { return MV_ReverseStereo; }
#endif

int MV_Init(int soundcard, int MixRate, int Voices, int numchannels, void *initdata)
{
    if (MV_Installed)
        MV_Shutdown();

    MV_SetErrorCode(MV_Ok);

    int const totalmem = Voices * sizeof(VoiceNode) + (MV_TOTALBUFFERSIZE * sizeof(int16_t)) + (MV_MIXBUFFERSIZE * numchannels * sizeof(int16_t));

    char *ptr = (char *) Xaligned_calloc(16, 1, totalmem);

    MV_Voices = (VoiceNode *)ptr;
    ptr += Voices * sizeof(VoiceNode);
    Bassert(Voices < MV_MAXVOICES);

    MV_MaxVoices = Voices;

    LL::Reset((VoiceNode*) &VoiceList);
    LL::Reset((VoiceNode*) &VoicePool);

    for (int index = 0; index < Voices; index++)
        LL::Insert(&VoicePool, &MV_Voices[index]);

    MV_Handles = (VoiceNode **)Xaligned_calloc(16, Voices, sizeof(intptr_t));
#ifdef ASS_REVERSESTEREO
    MV_SetReverseStereo(FALSE);
#endif

    ASS_PCMSoundDriver = soundcard;

    // Initialize the sound card

    if (SoundDriver_PCM_Init(&MixRate, &numchannels, initdata) != MV_Ok)
        MV_SetErrorCode(MV_DriverError);

    if (MV_ErrorCode != MV_Ok)
    {
        ALIGNED_FREE_AND_NULL(MV_Voices);

        return MV_Error;
    }

    MV_Installed    = TRUE;
    MV_InitDataPtr  = initdata;
    MV_CallBackFunc = nullptr;
    MV_ReverbLevel  = 0;
    MV_ReverbVolume = 0.f;

    // Set the sampling rate
    MV_MixRate = MixRate;

    // Set Mixer to play stereo digitized sound
    MV_SetMixMode(numchannels);
    MV_ReverbDelay = MV_BufferSize * 3;

    // Make sure we don't cross a physical page
    MV_MixBuffer[MV_NumberOfBuffers<<1] = ptr;
    for (int buffer = 0; buffer < MV_NumberOfBuffers<<1; buffer++)
    {
        MV_MixBuffer[buffer] = ptr;
        ptr += MV_BufferSize;
    }

    MV_MusicBuffer = ptr;

    // Calculate pan table
    MV_CalcPanTable();

    MV_VolumeSmoothFactor = fix16_from_float(1.f-powf(0.1f, 30.f/MixRate));

    // Start the playback engine
    if (MV_StartPlayback() != MV_Ok)
    {
        // Preserve error code while we shutdown.
        int status = MV_ErrorCode;
        MV_Shutdown();
        return MV_SetErrorCode(status);
    }

    return MV_Ok;
}

int MV_Shutdown(void)
{
    if (!MV_Installed)
        return MV_Ok;

    MV_KillAllVoices();

    MV_Installed = FALSE;

    // Stop the sound playback engine
    MV_StopPlayback();

    // Shutdown the sound card
    SoundDriver_PCM_Shutdown();

    // Free any voices we allocated
    ALIGNED_FREE_AND_NULL(MV_Voices);

    LL::Reset((VoiceNode*) &VoiceList);
    LL::Reset((VoiceNode*) &VoicePool);

    ALIGNED_FREE_AND_NULL(MV_Handles);

    MV_MaxVoices = 1;

    // Release the descriptor from our mix buffer
    for (int buffer = 0; buffer < MV_NUMBEROFBUFFERS<<1; buffer++)
        MV_MixBuffer[buffer] = nullptr;

    MV_SetErrorCode(MV_NotInstalled);

    return MV_Ok;
}

void MV_HookMusicRoutine(void(*callback)(void))
{
    MV_Lock();
    MV_MusicCallback = callback;
    MV_Unlock();
}

void MV_UnhookMusicRoutine(void)
{
    if (MV_MusicCallback)
    {
        MV_Lock();
        MV_MusicCallback = nullptr;
        MV_Unlock();
    }
}

MV_MusicRoutineBuffer MV_GetMusicRoutineBuffer()
{
    return MV_MusicRoutineBuffer{ MV_MusicBuffer, MV_BufferSize };
}

const char *loopStartTags[loopStartTagCount] = { "LOOP_START", "LOOPSTART", "LOOP" };
const char *loopEndTags[loopEndTagCount] = { "LOOP_END", "LOOPEND" };
const char *loopLengthTags[loopLengthTagCount] = { "LOOP_LENGTH", "LOOPLENGTH" };

const char *MV_ErrorString(int ErrorNumber)
{
    switch (ErrorNumber)
    {
        case MV_Error:
            return MV_ErrorString(MV_ErrorCode);
        case MV_Ok:
            return "Multivoc ok.";
        case MV_NotInstalled:
            return "Multivoc not installed.";
        case MV_DriverError:
            return SoundDriver_PCM_ErrorString(SoundDriver_PCM_GetError());
        case MV_NoVoices:
            return "No free voices available to Multivoc.";
        case MV_VoiceNotFound:
            return "No voice with matching handle found.";
        case MV_InvalidFile:
            return "Invalid file passed in to Multivoc.";
        default:
            return "Unknown Multivoc error code.";
    }
}

static playbackstatus MV_GetNextDemandFeedBlock(VoiceNode* voice)
{
    if (voice->BlockLength > 0)
    {
        voice->position -= voice->length;
        voice->sound += voice->length >> 16;
        voice->length = min(voice->BlockLength, 0x8000u);
        voice->BlockLength -= voice->length;
        voice->length <<= 16;

        return KeepPlaying;
    }

    if (voice->DemandFeed == NULL)
        return NoMoreData;

    voice->position = 0;
    (voice->DemandFeed)(&voice->sound, &voice->BlockLength, voice->rawdataptr);
    voice->length = min(voice->BlockLength, 0x8000u);
    voice->BlockLength -= voice->length;
    voice->length <<= 16;

    if (voice->length > 0 && voice->sound != NULL)
        return KeepPlaying;

    return NoMoreData;
}

int MV_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)
{
    if (!MV_Installed)
        return MV_SetErrorCode(MV_NotInstalled);

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

//    voice->wavetype = FMT_DEMANDFED;
    voice->bits = bitdepth;
    voice->channels = channels;
    voice->GetSound = MV_GetNextDemandFeedBlock;
    voice->DemandFeed = function;
    voice->position = 0;
    voice->sound  = nullptr;
    voice->length = 0;
    voice->priority = priority;
    voice->callbackval = callbackval;
    voice->rawdataptr = userdata;

    voice->Loop = {};

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

    return voice->handle;
}

int MV_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)
{
    if (!MV_Installed)
        return MV_SetErrorCode(MV_NotInstalled);

    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_StartDemandFeedPlayback(function, bitdepth, channels, rate, pitchoffset, max(0, 255 - distance),
        MV_PanTable[ angle ][ vol ].left, MV_PanTable[ angle ][ vol ].right, priority, volume, callbackval, userdata);
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT

This file is part of NBlood.

NBlood 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 "music.h"

#include "compat.h"
#include "drivers.h"
#include "midi.h"
#include "multivoc.h"
#include "sndcards.h"

int MUSIC_ErrorCode = MUSIC_Ok;

static midifuncs MUSIC_MidiFunctions;

#define MUSIC_SetErrorCode(status) MUSIC_ErrorCode = (status);

const char *MUSIC_ErrorString(int const ErrorNumber)
{
    switch (ErrorNumber)
    {
        case MUSIC_Warning:
        case MUSIC_Error:       return MUSIC_ErrorString(MUSIC_ErrorCode);
        case MUSIC_Ok:          return "Music ok.";
        case MUSIC_MidiError:   return "Error playing MIDI file.";
        default:                return "Unknown Music error code.";
    }
}

int MUSIC_Init(int SoundCard)
{
    int detected = 0;

    if (SoundCard == ASS_AutoDetect)
    {
redetect:
        detected++;
        SoundCard = ASS_OPL3;
    }

    if (SoundCard < 0 || SoundCard >= ASS_NumSoundCards)
    {
failed:
        LOG_F(ERROR, "MIDI initialization failed!");
        MUSIC_ErrorCode = MUSIC_MidiError;
        return MUSIC_Error;
    }

    if (!SoundDriver_IsMIDISupported(SoundCard))
    {
        LOG_F(ERROR, "%s MIDI output not supported!", SoundDriver_GetName(SoundCard));

        if (detected < 2)
            goto redetect;

        goto failed;
    }

    ASS_MIDISoundDriver = SoundCard;

    VLOG_F(LOG_ASS, "Initializing MIDI driver: %s", SoundDriver_GetName(SoundCard));

    // SoundDriver_MIDI_Init() prints to the same line in the log for the MME driver
    int status = SoundDriver_MIDI_Init(&MUSIC_MidiFunctions);

    if (status != MUSIC_Ok)
    {
        if (detected < 2)
            goto redetect;

        goto failed;
    }

    MIDI_SetMidiFuncs(&MUSIC_MidiFunctions);

    return MUSIC_Ok;
}

int MUSIC_Shutdown(void)
{
    MIDI_StopSong();
    SoundDriver_MIDI_Shutdown();

    return MUSIC_Ok;
}

void MUSIC_SetVolume(int volume) { MIDI_SetVolume(clamp(volume, 0, 255)); }
int  MUSIC_GetVolume(void)       { return MIDI_GetVolume(); }
void MUSIC_SetLoopFlag(int loop) { MIDI_SetLoopFlag(loop); }
void MUSIC_Continue(void)        { MIDI_ContinueSong(); }
void MUSIC_Pause(void)           { MIDI_PauseSong(); }

int MUSIC_StopSong(void)
{
    MIDI_StopSong();
    MUSIC_SetErrorCode(MUSIC_Ok);
    return MUSIC_Ok;
}

int MUSIC_PlaySong(char *song, int songsize, int loopflag, const char *fn /*= nullptr*/)
{
    UNREFERENCED_PARAMETER(songsize);
    UNREFERENCED_PARAMETER(fn);

    MUSIC_SetErrorCode(MUSIC_Ok)

    if (MIDI_PlaySong(song, loopflag) != MIDI_Ok)
    {
        MUSIC_SetErrorCode(MUSIC_MidiError);
        return MUSIC_Warning;
    }

    return MUSIC_Ok;
}

void MUSIC_SetSongPosition(int measure, int beat, int tick) { MIDI_SetSongPosition(measure, beat, tick); }
void MUSIC_GetSongPosition(songposition *pos) { MIDI_GetSongPosition(pos); }

void MUSIC_Update(void) {}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010 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 "compat.h"
#include "multivoc.h"
#include "music.h"
#include "vfs.h"
#include "baselayer.h"

// fork/exec based external music player
#ifndef _WIN32
#include <signal.h>
#include <sys/mman.h>
#include <sys/wait.h>

#define INVALID_HANDLE_VALUE -1
typedef pid_t proc_t;
#else
#include "winbits.h"
typedef HANDLE proc_t;
#endif

static char ** g_musicPlayerArgv;
static int g_musicPlayerEnabled;
static proc_t  g_musicPlayerHandle = INVALID_HANDLE_VALUE;
static int g_musicPlayerReady;
static int8_t  g_musicPlayerRestart;
static char *  g_musicPlayerCommandLine;

static char g_musicFileName[BMAX_PATH];
static int g_musicFileNameArgvPos;

char const *errorMessage;

int MUSIC_Init(int SoundCard)
{
    // Use an external music player
    g_musicPlayerCommandLine = getenv("EDUKE32_MUSIC_CMD");

    UNREFERENCED_PARAMETER(SoundCard);

    if (g_musicPlayerReady)
    {
        errorMessage = "MUSIC_Init: external player already initialized!";
        return MUSIC_Error;
    } // if

    g_musicPlayerEnabled = (g_musicPlayerCommandLine && g_musicPlayerCommandLine[0]);

    if (!g_musicPlayerEnabled)
    {
        errorMessage = "MUSIC_Init: no external player configured!";
        return MUSIC_Error;
    }

    MV_Printf("Using external music player: \"%s\"\n", g_musicPlayerCommandLine);

#ifndef _WIN32
    int ws=1, numargs=0, pagesize=Bgetpagesize();
    char *c, *cmd;
    size_t sz;

    if (pagesize == -1)
    {
        errorMessage = "MUSIC_Init: unable to determine system page size";
        return MUSIC_Error;
    }
    for (c=g_musicPlayerCommandLine; *c; c++)
    {
        if (isspace(*c))
            ws = 1;
        else if (ws)
        {
            ws = 0;
            numargs++;
        }
    }

    if (numargs == 0)
    {
        errorMessage = "MUSIC_Init: not enough arguments for external player";
        return MUSIC_Error;
    }

    sz = (numargs+2)*sizeof(char *) + (c-g_musicPlayerCommandLine+1);
    sz = ((sz+pagesize-1)/pagesize)*pagesize;
    g_musicPlayerArgv = (char **)Xaligned_alloc(pagesize, sz);
    cmd = (char *)g_musicPlayerArgv + (numargs+2)*sizeof(intptr_t);
    Bmemcpy(cmd, g_musicPlayerCommandLine, c-g_musicPlayerCommandLine+1);

    ws = 1;
    numargs = 0;
    for (c=cmd; *c; c++)
    {
        if (isspace(*c))
        {
            ws = 1;
            *c = 0;
        }
        else if (ws)
        {
            ws = 0;
            g_musicPlayerArgv[numargs++] = c;
        }
    }
    g_musicFileNameArgvPos = numargs;
    g_musicPlayerArgv[numargs] = g_musicFileName;
    g_musicPlayerArgv[numargs+1] = nullptr;

#if 0
    if (mprotect(g_musicPlayerArgv, sz, PROT_READ)==-1)  // make argv and command string read-only
    {
        initprintf("MUSIC_Init: mprotect(): %s\n");
        errorMessage = "MUSIC_Init: mprotect() failure";
        return MUSIC_Error;
    }
#endif
#endif

    g_musicPlayerReady = 1;
    return MUSIC_Ok;
}

int MUSIC_Shutdown(void)
{
    MUSIC_StopSong();
    g_musicPlayerReady = 0;

    return MUSIC_Ok;
} // MUSIC_Shutdown

int MUSIC_StopSong(void)
{
    if (!g_musicPlayerEnabled)
        return MUSIC_Ok;

    if (g_musicPlayerHandle != INVALID_HANDLE_VALUE)
    {
        g_musicPlayerRestart = 0;  // make SIGCHLD handler a no-op

#ifndef _WIN32
        struct timespec ts;

        ts.tv_sec = 0;
        ts.tv_nsec = 5000000;  // sleep 5ms at most

        kill(g_musicPlayerHandle, SIGTERM);
        nanosleep(&ts, nullptr);

        if (int ret = waitpid(g_musicPlayerHandle, nullptr, WNOHANG|WUNTRACED) != g_musicPlayerHandle)
        {
            if (ret==-1)
                initprintf("%s: waitpid: %s\n", __func__, strerror(errno));
            else
            {
                // we tried to be nice, but no...
                kill(g_musicPlayerHandle, SIGKILL);

                initprintf("%s: SIGTERM timed out--trying SIGKILL\n", __func__);

                if (waitpid(g_musicPlayerHandle, nullptr, WUNTRACED)==-1)
                    initprintf("%s: waitpid: %s\n", __func__, strerror(errno));
            }
        }
#else
        TerminateProcess(g_musicPlayerHandle, 0);
#endif
        g_musicPlayerHandle = INVALID_HANDLE_VALUE;
    }

    return MUSIC_Ok;
} // MUSIC_StopSong

static int MUSIC_PlayExternal()
{
#ifdef _WIN32
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory(&si,sizeof(si));
    ZeroMemory(&pi,sizeof(pi));
    si.cb = sizeof(si);

    if (!CreateProcess(nullptr,g_musicPlayerCommandLine,nullptr,nullptr,0,0,nullptr,nullptr,&si,&pi))
    {
        MV_Printf("%s: CreateProcess: %s\n", __func__, windowsGetErrorMessage(GetLastError()));
        return MUSIC_Error;
    }
    else
        g_musicPlayerHandle = pi.hProcess;
#else
    proc_t pid = vfork();

    if (pid==-1)  // error
    {
        initprintf("%s: vfork: %s\n", __func__, strerror(errno));
        return MUSIC_Error;
    }
    else if (pid==0)  // child
    {
        // exec without PATH lookup
        if (execv(g_musicPlayerArgv[0], g_musicPlayerArgv) < 0)
        {
            initprintf("%s: execv: %s\n", __func__, strerror(errno));
            _exit(EXIT_FAILURE);
        }
    }
    else  // parent
    {
        g_musicPlayerHandle = pid;
    }
#endif
    return MUSIC_Ok;
}

#ifndef _WIN32
static void sigchld_handler(int signo)
{
    if (g_musicPlayerHandle <= 0 || !g_musicPlayerRestart || signo != SIGCHLD)
        return;

    int status;

    if (waitpid(g_musicPlayerHandle, &status, WUNTRACED)==-1)
        initprintf("%s: waitpid: %s\n", __func__, strerror(errno));

    if (WIFEXITED(status) && WEXITSTATUS(status)==0)
    {
        // loop ...
        MUSIC_PlayExternal();
    }
}
#endif

int MUSIC_PlaySong(char *song, int songsize, int loopflag, const char *fn /*= nullptr*/)
{
    if (!g_musicPlayerEnabled)
    {
        errorMessage = "MUSIC_Init: no external player configured!";
        return MUSIC_Error;
    }

#ifndef _WIN32
    static int sigchld_handler_set;

    if (!sigchld_handler_set)
    {
        struct sigaction sa;
        sa.sa_handler=sigchld_handler;
        sa.sa_flags=0;
        sigemptyset(&sa.sa_mask);

        if (sigaction(SIGCHLD, &sa, nullptr)==-1)
            initprintf("%s: sigaction: %s\n", __func__, strerror(errno));

        sigchld_handler_set = 1;
    }
#endif

    auto ext = Xstrdup(fn);
    auto const c = Bsnprintf(g_musicFileName, sizeof(g_musicFileName), "%s/external%s", Bgethomedir(), strtok(ext,"."));
    g_musicFileName[c] = '\0';
    Xfree(ext);

    if (auto fp = buildvfs_fopen_write(g_musicFileName))
    {
        buildvfs_fwrite(song, 1, songsize, fp);
        buildvfs_fclose(fp);

        g_musicPlayerRestart = loopflag;
        g_musicPlayerArgv[g_musicFileNameArgvPos] = g_musicFileName;

        if (int retval = MUSIC_PlayExternal() != MUSIC_Ok)
            return retval;
    }
    else
    {
        MV_Printf("%s: fopen: %s\n", __func__, strerror(errno));
        return MUSIC_Error;
    }

    return MUSIC_Ok;
}

void MUSIC_Update(void)
{
#ifdef _WIN32
    if (g_musicPlayerHandle == INVALID_HANDLE_VALUE || !g_musicPlayerRestart)
        return;

    DWORD exitCode = -1;

    GetExitCodeProcess(g_musicPlayerHandle, &exitCode);

    if (exitCode != STILL_ACTIVE)
        MUSIC_PlayExternal();
#endif
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2013-2019 Nuke.YKT

This file is part of NBlood.

NBlood 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.
*/
//-------------------------------------------------------------------------
//
//  Nuked OPL3 emulator.
//  Thanks:
//      MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh):
//          Feedback and Rhythm part calculation information.
//      forums.submarine.org.uk(carbon14, opl3):
//          Tremolo and phase generator calculation information.
//      OPLx decapsulated(Matthew Gambrell, Olli Niemitalo):
//          OPL2 ROMs.
//      siliconpr0n.org(John McMaster, digshadow):
//          YMF262 and VRC VII decaps and die shots.
//
// version: 1.8, with stereo extension
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "opl3.h"

#if OPL_ENABLE_STEREOEXT && !defined OPL_SIN
#ifndef _USE_MATH_DEFINES
#define _USE_MATH_DEFINES 1
#endif
#include <math.h>
/* input: [0, 256), output: [0, 65536] */
#define OPL_SIN(x) ((int32_t)(sin((x) * M_PI / 512.0) * 65536.0))
#endif

/* Quirk: Some FM channels are output one sample later on the left side than the right. */
#ifndef OPL_QUIRK_CHANNELSAMPLEDELAY
#define OPL_QUIRK_CHANNELSAMPLEDELAY (!OPL_ENABLE_STEREOEXT)
#endif

#define RSM_FRAC    10

/* Channel types */

enum {
    ch_2op = 0,
    ch_4op = 1,
    ch_4op2 = 2,
    ch_drum = 3
};

/* Envelope key types */

enum {
    egk_norm = 0x01,
    egk_drum = 0x02
};

/*
    logsin table
*/

static const uint16_t logsinrom[256] = {
    0x859, 0x6c3, 0x607, 0x58b, 0x52e, 0x4e4, 0x4a6, 0x471,
    0x443, 0x41a, 0x3f5, 0x3d3, 0x3b5, 0x398, 0x37e, 0x365,
    0x34e, 0x339, 0x324, 0x311, 0x2ff, 0x2ed, 0x2dc, 0x2cd,
    0x2bd, 0x2af, 0x2a0, 0x293, 0x286, 0x279, 0x26d, 0x261,
    0x256, 0x24b, 0x240, 0x236, 0x22c, 0x222, 0x218, 0x20f,
    0x206, 0x1fd, 0x1f5, 0x1ec, 0x1e4, 0x1dc, 0x1d4, 0x1cd,
    0x1c5, 0x1be, 0x1b7, 0x1b0, 0x1a9, 0x1a2, 0x19b, 0x195,
    0x18f, 0x188, 0x182, 0x17c, 0x177, 0x171, 0x16b, 0x166,
    0x160, 0x15b, 0x155, 0x150, 0x14b, 0x146, 0x141, 0x13c,
    0x137, 0x133, 0x12e, 0x129, 0x125, 0x121, 0x11c, 0x118,
    0x114, 0x10f, 0x10b, 0x107, 0x103, 0x0ff, 0x0fb, 0x0f8,
    0x0f4, 0x0f0, 0x0ec, 0x0e9, 0x0e5, 0x0e2, 0x0de, 0x0db,
    0x0d7, 0x0d4, 0x0d1, 0x0cd, 0x0ca, 0x0c7, 0x0c4, 0x0c1,
    0x0be, 0x0bb, 0x0b8, 0x0b5, 0x0b2, 0x0af, 0x0ac, 0x0a9,
    0x0a7, 0x0a4, 0x0a1, 0x09f, 0x09c, 0x099, 0x097, 0x094,
    0x092, 0x08f, 0x08d, 0x08a, 0x088, 0x086, 0x083, 0x081,
    0x07f, 0x07d, 0x07a, 0x078, 0x076, 0x074, 0x072, 0x070,
    0x06e, 0x06c, 0x06a, 0x068, 0x066, 0x064, 0x062, 0x060,
    0x05e, 0x05c, 0x05b, 0x059, 0x057, 0x055, 0x053, 0x052,
    0x050, 0x04e, 0x04d, 0x04b, 0x04a, 0x048, 0x046, 0x045,
    0x043, 0x042, 0x040, 0x03f, 0x03e, 0x03c, 0x03b, 0x039,
    0x038, 0x037, 0x035, 0x034, 0x033, 0x031, 0x030, 0x02f,
    0x02e, 0x02d, 0x02b, 0x02a, 0x029, 0x028, 0x027, 0x026,
    0x025, 0x024, 0x023, 0x022, 0x021, 0x020, 0x01f, 0x01e,
    0x01d, 0x01c, 0x01b, 0x01a, 0x019, 0x018, 0x017, 0x017,
    0x016, 0x015, 0x014, 0x014, 0x013, 0x012, 0x011, 0x011,
    0x010, 0x00f, 0x00f, 0x00e, 0x00d, 0x00d, 0x00c, 0x00c,
    0x00b, 0x00a, 0x00a, 0x009, 0x009, 0x008, 0x008, 0x007,
    0x007, 0x007, 0x006, 0x006, 0x005, 0x005, 0x005, 0x004,
    0x004, 0x004, 0x003, 0x003, 0x003, 0x002, 0x002, 0x002,
    0x002, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001, 0x001,
    0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000
};

/*
    exp table
*/

static const uint16_t exprom[256] = {
    0x7fa, 0x7f5, 0x7ef, 0x7ea, 0x7e4, 0x7df, 0x7da, 0x7d4,
    0x7cf, 0x7c9, 0x7c4, 0x7bf, 0x7b9, 0x7b4, 0x7ae, 0x7a9,
    0x7a4, 0x79f, 0x799, 0x794, 0x78f, 0x78a, 0x784, 0x77f,
    0x77a, 0x775, 0x770, 0x76a, 0x765, 0x760, 0x75b, 0x756,
    0x751, 0x74c, 0x747, 0x742, 0x73d, 0x738, 0x733, 0x72e,
    0x729, 0x724, 0x71f, 0x71a, 0x715, 0x710, 0x70b, 0x706,
    0x702, 0x6fd, 0x6f8, 0x6f3, 0x6ee, 0x6e9, 0x6e5, 0x6e0,
    0x6db, 0x6d6, 0x6d2, 0x6cd, 0x6c8, 0x6c4, 0x6bf, 0x6ba,
    0x6b5, 0x6b1, 0x6ac, 0x6a8, 0x6a3, 0x69e, 0x69a, 0x695,
    0x691, 0x68c, 0x688, 0x683, 0x67f, 0x67a, 0x676, 0x671,
    0x66d, 0x668, 0x664, 0x65f, 0x65b, 0x657, 0x652, 0x64e,
    0x649, 0x645, 0x641, 0x63c, 0x638, 0x634, 0x630, 0x62b,
    0x627, 0x623, 0x61e, 0x61a, 0x616, 0x612, 0x60e, 0x609,
    0x605, 0x601, 0x5fd, 0x5f9, 0x5f5, 0x5f0, 0x5ec, 0x5e8,
    0x5e4, 0x5e0, 0x5dc, 0x5d8, 0x5d4, 0x5d0, 0x5cc, 0x5c8,
    0x5c4, 0x5c0, 0x5bc, 0x5b8, 0x5b4, 0x5b0, 0x5ac, 0x5a8,
    0x5a4, 0x5a0, 0x59c, 0x599, 0x595, 0x591, 0x58d, 0x589,
    0x585, 0x581, 0x57e, 0x57a, 0x576, 0x572, 0x56f, 0x56b,
    0x567, 0x563, 0x560, 0x55c, 0x558, 0x554, 0x551, 0x54d,
    0x549, 0x546, 0x542, 0x53e, 0x53b, 0x537, 0x534, 0x530,
    0x52c, 0x529, 0x525, 0x522, 0x51e, 0x51b, 0x517, 0x514,
    0x510, 0x50c, 0x509, 0x506, 0x502, 0x4ff, 0x4fb, 0x4f8,
    0x4f4, 0x4f1, 0x4ed, 0x4ea, 0x4e7, 0x4e3, 0x4e0, 0x4dc,
    0x4d9, 0x4d6, 0x4d2, 0x4cf, 0x4cc, 0x4c8, 0x4c5, 0x4c2,
    0x4be, 0x4bb, 0x4b8, 0x4b5, 0x4b1, 0x4ae, 0x4ab, 0x4a8,
    0x4a4, 0x4a1, 0x49e, 0x49b, 0x498, 0x494, 0x491, 0x48e,
    0x48b, 0x488, 0x485, 0x482, 0x47e, 0x47b, 0x478, 0x475,
    0x472, 0x46f, 0x46c, 0x469, 0x466, 0x463, 0x460, 0x45d,
    0x45a, 0x457, 0x454, 0x451, 0x44e, 0x44b, 0x448, 0x445,
    0x442, 0x43f, 0x43c, 0x439, 0x436, 0x433, 0x430, 0x42d,
    0x42a, 0x428, 0x425, 0x422, 0x41f, 0x41c, 0x419, 0x416,
    0x414, 0x411, 0x40e, 0x40b, 0x408, 0x406, 0x403, 0x400
};

/*
    freq mult table multiplied by 2

    1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 12, 12, 15, 15
*/

static const uint8_t mt[16] = {
    1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30
};

/*
    ksl table
*/

static const uint8_t kslrom[16] = {
    0, 32, 40, 45, 48, 51, 53, 55, 56, 58, 59, 60, 61, 62, 63, 64
};

static const uint8_t kslshift[4] = {
    8, 1, 2, 0
};

/*
    envelope generator constants
*/

static const uint8_t eg_incstep[4][4] = {
    { 0, 0, 0, 0 },
    { 1, 0, 0, 0 },
    { 1, 0, 1, 0 },
    { 1, 1, 1, 0 }
};

/*
    address decoding
*/

static const int8_t ad_slot[0x20] = {
    0, 1, 2, 3, 4, 5, -1, -1, 6, 7, 8, 9, 10, 11, -1, -1,
    12, 13, 14, 15, 16, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};

static const uint8_t ch_slot[18] = {
    0, 1, 2, 6, 7, 8, 12, 13, 14, 18, 19, 20, 24, 25, 26, 30, 31, 32
};

#if OPL_ENABLE_STEREOEXT
/*
    stereo extension panning table
*/

static int32_t panpot_lut[256];
static uint8_t panpot_lut_build = 0;
#endif

/*
    Envelope generator
*/

typedef int16_t(*envelope_sinfunc)(uint16_t phase, uint16_t envelope);
typedef void(*envelope_genfunc)(opl3_slot *slott);

static int16_t OPL3_EnvelopeCalcExp(uint32_t level)
{
    if (level > 0x1fff)
    {
        level = 0x1fff;
    }
    return (exprom[level & 0xff] << 1) >> (level >> 8);
}

static int16_t OPL3_EnvelopeCalcSin0(uint16_t phase, uint16_t envelope)
{
    uint16_t out = 0;
    uint16_t neg = 0;
    phase &= 0x3ff;
    if (phase & 0x200)
    {
        neg = 0xffff;
    }
    if (phase & 0x100)
    {
        out = logsinrom[(phase & 0xff) ^ 0xff];
    }
    else
    {
        out = logsinrom[phase & 0xff];
    }
    return OPL3_EnvelopeCalcExp(out + (envelope << 3)) ^ neg;
}

static int16_t OPL3_EnvelopeCalcSin1(uint16_t phase, uint16_t envelope)
{
    uint16_t out = 0;
    phase &= 0x3ff;
    if (phase & 0x200)
    {
        out = 0x1000;
    }
    else if (phase & 0x100)
    {
        out = logsinrom[(phase & 0xff) ^ 0xff];
    }
    else
    {
        out = logsinrom[phase & 0xff];
    }
    return OPL3_EnvelopeCalcExp(out + (envelope << 3));
}

static int16_t OPL3_EnvelopeCalcSin2(uint16_t phase, uint16_t envelope)
{
    uint16_t out = 0;
    phase &= 0x3ff;
    if (phase & 0x100)
    {
        out = logsinrom[(phase & 0xff) ^ 0xff];
    }
    else
    {
        out = logsinrom[phase & 0xff];
    }
    return OPL3_EnvelopeCalcExp(out + (envelope << 3));
}

static int16_t OPL3_EnvelopeCalcSin3(uint16_t phase, uint16_t envelope)
{
    uint16_t out = 0;
    phase &= 0x3ff;
    if (phase & 0x100)
    {
        out = 0x1000;
    }
    else
    {
        out = logsinrom[phase & 0xff];
    }
    return OPL3_EnvelopeCalcExp(out + (envelope << 3));
}

static int16_t OPL3_EnvelopeCalcSin4(uint16_t phase, uint16_t envelope)
{
    uint16_t out = 0;
    uint16_t neg = 0;
    phase &= 0x3ff;
    if ((phase & 0x300) == 0x100)
    {
        neg = 0xffff;
    }
    if (phase & 0x200)
    {
        out = 0x1000;
    }
    else if (phase & 0x80)
    {
        out = logsinrom[((phase ^ 0xff) << 1) & 0xff];
    }
    else
    {
        out = logsinrom[(phase << 1) & 0xff];
    }
    return OPL3_EnvelopeCalcExp(out + (envelope << 3)) ^ neg;
}

static int16_t OPL3_EnvelopeCalcSin5(uint16_t phase, uint16_t envelope)
{
    uint16_t out = 0;
    phase &= 0x3ff;
    if (phase & 0x200)
    {
        out = 0x1000;
    }
    else if (phase & 0x80)
    {
        out = logsinrom[((phase ^ 0xff) << 1) & 0xff];
    }
    else
    {
        out = logsinrom[(phase << 1) & 0xff];
    }
    return OPL3_EnvelopeCalcExp(out + (envelope << 3));
}

static int16_t OPL3_EnvelopeCalcSin6(uint16_t phase, uint16_t envelope)
{
    uint16_t neg = 0;
    phase &= 0x3ff;
    if (phase & 0x200)
    {
        neg = 0xffff;
    }
    return OPL3_EnvelopeCalcExp(envelope << 3) ^ neg;
}

static int16_t OPL3_EnvelopeCalcSin7(uint16_t phase, uint16_t envelope)
{
    uint16_t out = 0;
    uint16_t neg = 0;
    phase &= 0x3ff;
    if (phase & 0x200)
    {
        neg = 0xffff;
        phase = (phase & 0x1ff) ^ 0x1ff;
    }
    out = phase << 3;
    return OPL3_EnvelopeCalcExp(out + (envelope << 3)) ^ neg;
}

static const envelope_sinfunc envelope_sin[8] = {
    OPL3_EnvelopeCalcSin0,
    OPL3_EnvelopeCalcSin1,
    OPL3_EnvelopeCalcSin2,
    OPL3_EnvelopeCalcSin3,
    OPL3_EnvelopeCalcSin4,
    OPL3_EnvelopeCalcSin5,
    OPL3_EnvelopeCalcSin6,
    OPL3_EnvelopeCalcSin7
};

enum envelope_gen_num
{
    envelope_gen_num_attack = 0,
    envelope_gen_num_decay = 1,
    envelope_gen_num_sustain = 2,
    envelope_gen_num_release = 3
};

static void OPL3_EnvelopeUpdateKSL(opl3_slot *slot)
{
    int16_t ksl = (kslrom[slot->channel->f_num >> 6] << 2)
               - ((0x08 - slot->channel->block) << 5);
    if (ksl < 0)
    {
        ksl = 0;
    }
    slot->eg_ksl = (uint8_t)ksl;
}

static void OPL3_EnvelopeCalc(opl3_slot *slot)
{
    uint8_t nonzero;
    uint8_t rate;
    uint8_t rate_hi;
    uint8_t rate_lo;
    uint8_t reg_rate = 0;
    uint8_t ks;
    uint8_t eg_shift, shift;
    uint16_t eg_rout;
    int16_t eg_inc;
    uint8_t eg_off;
    uint8_t reset = 0;
    slot->eg_out = slot->eg_rout + (slot->reg_tl << 2)
                 + (slot->eg_ksl >> kslshift[slot->reg_ksl]) + *slot->trem;
    if (slot->key && slot->eg_gen == envelope_gen_num_release)
    {
        reset = 1;
        reg_rate = slot->reg_ar;
    }
    else
    {
        switch (slot->eg_gen)
        {
        case envelope_gen_num_attack:
            reg_rate = slot->reg_ar;
            break;
        case envelope_gen_num_decay:
            reg_rate = slot->reg_dr;
            break;
        case envelope_gen_num_sustain:
            if (!slot->reg_type)
            {
                reg_rate = slot->reg_rr;
            }
            break;
        case envelope_gen_num_release:
            reg_rate = slot->reg_rr;
            break;
        }
    }
    slot->pg_reset = reset;
    ks = slot->channel->ksv >> ((slot->reg_ksr ^ 1) << 1);
    nonzero = (reg_rate != 0);
    rate = ks + (reg_rate << 2);
    rate_hi = rate >> 2;
    rate_lo = rate & 0x03;
    if (rate_hi & 0x10)
    {
        rate_hi = 0x0f;
    }
    eg_shift = rate_hi + slot->chip->eg_add;
    shift = 0;
    if (nonzero)
    {
        if (rate_hi < 12)
        {
            if (slot->chip->eg_state)
            {
                switch (eg_shift)
                {
                case 12:
                    shift = 1;
                    break;
                case 13:
                    shift = (rate_lo >> 1) & 0x01;
                    break;
                case 14:
                    shift = rate_lo & 0x01;
                    break;
                default:
                    break;
                }
            }
        }
        else
        {
            shift = (rate_hi & 0x03) + eg_incstep[rate_lo][slot->chip->timer & 0x03];
            if (shift & 0x04)
            {
                shift = 0x03;
            }
            if (!shift)
            {
                shift = slot->chip->eg_state;
            }
        }
    }
    eg_rout = slot->eg_rout;
    eg_inc = 0;
    eg_off = 0;
    /* Instant attack */
    if (reset && rate_hi == 0x0f)
    {
        eg_rout = 0x00;
    }
    /* Envelope off */
    if ((slot->eg_rout & 0x1f8) == 0x1f8)
    {
        eg_off = 1;
    }
    if (slot->eg_gen != envelope_gen_num_attack && !reset && eg_off)
    {
        eg_rout = 0x1ff;
    }
    switch (slot->eg_gen)
    {
    case envelope_gen_num_attack:
        if (!slot->eg_rout)
        {
            slot->eg_gen = envelope_gen_num_decay;
        }
        else if (slot->key && shift > 0 && rate_hi != 0x0f)
        {
            eg_inc = ~slot->eg_rout >> (4 - shift);
        }
        break;
    case envelope_gen_num_decay:
        if ((slot->eg_rout >> 4) == slot->reg_sl)
        {
            slot->eg_gen = envelope_gen_num_sustain;
        }
        else if (!eg_off && !reset && shift > 0)
        {
            eg_inc = 1 << (shift - 1);
        }
        break;
    case envelope_gen_num_sustain:
    case envelope_gen_num_release:
        if (!eg_off && !reset && shift > 0)
        {
            eg_inc = 1 << (shift - 1);
        }
        break;
    }
    slot->eg_rout = (eg_rout + eg_inc) & 0x1ff;
    /* Key off */
    if (reset)
    {
        slot->eg_gen = envelope_gen_num_attack;
    }
    if (!slot->key)
    {
        slot->eg_gen = envelope_gen_num_release;
    }
}

static void OPL3_EnvelopeKeyOn(opl3_slot *slot, uint8_t type)
{
    slot->key |= type;
}

static void OPL3_EnvelopeKeyOff(opl3_slot *slot, uint8_t type)
{
    slot->key &= ~type;
}

/*
    Phase Generator
*/

static void OPL3_PhaseGenerate(opl3_slot *slot)
{
    opl3_chip *chip;
    uint16_t f_num;
    uint32_t basefreq;
    uint8_t rm_xor, n_bit;
    uint32_t noise;
    uint16_t phase;

    chip = slot->chip;
    f_num = slot->channel->f_num;
    if (slot->reg_vib)
    {
        int8_t range;
        uint8_t vibpos;

        range = (f_num >> 7) & 7;
        vibpos = slot->chip->vibpos;

        if (!(vibpos & 3))
        {
            range = 0;
        }
        else if (vibpos & 1)
        {
            range >>= 1;
        }
        range >>= slot->chip->vibshift;

        if (vibpos & 4)
        {
            range = -range;
        }
        f_num += range;
    }
    basefreq = (f_num << slot->channel->block) >> 1;
    phase = (uint16_t)(slot->pg_phase >> 9);
    if (slot->pg_reset)
    {
        slot->pg_phase = 0;
    }
    slot->pg_phase += (basefreq * mt[slot->reg_mult]) >> 1;
    /* Rhythm mode */
    noise = chip->noise;
    slot->pg_phase_out = phase;
    if (slot->slot_num == 13) /* hh */
    {
        chip->rm_hh_bit2 = (phase >> 2) & 1;
        chip->rm_hh_bit3 = (phase >> 3) & 1;
        chip->rm_hh_bit7 = (phase >> 7) & 1;
        chip->rm_hh_bit8 = (phase >> 8) & 1;
    }
    if (slot->slot_num == 17 && (chip->rhy & 0x20)) /* tc */
    {
        chip->rm_tc_bit3 = (phase >> 3) & 1;
        chip->rm_tc_bit5 = (phase >> 5) & 1;
    }
    if (chip->rhy & 0x20)
    {
        rm_xor = (chip->rm_hh_bit2 ^ chip->rm_hh_bit7)
               | (chip->rm_hh_bit3 ^ chip->rm_tc_bit5)
               | (chip->rm_tc_bit3 ^ chip->rm_tc_bit5);
        switch (slot->slot_num)
        {
        case 13: /* hh */
            slot->pg_phase_out = rm_xor << 9;
            if (rm_xor ^ (noise & 1))
            {
                slot->pg_phase_out |= 0xd0;
            }
            else
            {
                slot->pg_phase_out |= 0x34;
            }
            break;
        case 16: /* sd */
            slot->pg_phase_out = (chip->rm_hh_bit8 << 9)
                               | ((chip->rm_hh_bit8 ^ (noise & 1)) << 8);
            break;
        case 17: /* tc */
            slot->pg_phase_out = (rm_xor << 9) | 0x80;
            break;
        default:
            break;
        }
    }
    n_bit = ((noise >> 14) ^ noise) & 0x01;
    chip->noise = (noise >> 1) | (n_bit << 22);
}

/*
    Slot
*/

static void OPL3_SlotWrite20(opl3_slot *slot, uint8_t data)
{
    if ((data >> 7) & 0x01)
    {
        slot->trem = &slot->chip->tremolo;
    }
    else
    {
        slot->trem = (uint8_t*)&slot->chip->zeromod;
    }
    slot->reg_vib = (data >> 6) & 0x01;
    slot->reg_type = (data >> 5) & 0x01;
    slot->reg_ksr = (data >> 4) & 0x01;
    slot->reg_mult = data & 0x0f;
}

static void OPL3_SlotWrite40(opl3_slot *slot, uint8_t data)
{
    slot->reg_ksl = (data >> 6) & 0x03;
    slot->reg_tl = data & 0x3f;
    OPL3_EnvelopeUpdateKSL(slot);
}

static void OPL3_SlotWrite60(opl3_slot *slot, uint8_t data)
{
    slot->reg_ar = (data >> 4) & 0x0f;
    slot->reg_dr = data & 0x0f;
}

static void OPL3_SlotWrite80(opl3_slot *slot, uint8_t data)
{
    slot->reg_sl = (data >> 4) & 0x0f;
    if (slot->reg_sl == 0x0f)
    {
        slot->reg_sl = 0x1f;
    }
    slot->reg_rr = data & 0x0f;
}

static void OPL3_SlotWriteE0(opl3_slot *slot, uint8_t data)
{
    slot->reg_wf = data & 0x07;
    if (slot->chip->newm == 0x00)
    {
        slot->reg_wf &= 0x03;
    }
}

static void OPL3_SlotGenerate(opl3_slot *slot)
{
    slot->out = envelope_sin[slot->reg_wf](slot->pg_phase_out + *slot->mod, slot->eg_out);
}

static void OPL3_SlotCalcFB(opl3_slot *slot)
{
    if (slot->channel->fb != 0x00)
    {
        slot->fbmod = (slot->prout + slot->out) >> (0x09 - slot->channel->fb);
    }
    else
    {
        slot->fbmod = 0;
    }
    slot->prout = slot->out;
}

/*
    Channel
*/

static void OPL3_ChannelSetupAlg(opl3_channel *channel);

static void OPL3_ChannelUpdateRhythm(opl3_chip *chip, uint8_t data)
{
    opl3_channel *channel6;
    opl3_channel *channel7;
    opl3_channel *channel8;
    uint8_t chnum;

    chip->rhy = data & 0x3f;
    if (chip->rhy & 0x20)
    {
        channel6 = &chip->channel[6];
        channel7 = &chip->channel[7];
        channel8 = &chip->channel[8];
        channel6->out[0] = &channel6->slots[1]->out;
        channel6->out[1] = &channel6->slots[1]->out;
        channel6->out[2] = &chip->zeromod;
        channel6->out[3] = &chip->zeromod;
        channel7->out[0] = &channel7->slots[0]->out;
        channel7->out[1] = &channel7->slots[0]->out;
        channel7->out[2] = &channel7->slots[1]->out;
        channel7->out[3] = &channel7->slots[1]->out;
        channel8->out[0] = &channel8->slots[0]->out;
        channel8->out[1] = &channel8->slots[0]->out;
        channel8->out[2] = &channel8->slots[1]->out;
        channel8->out[3] = &channel8->slots[1]->out;
        for (chnum = 6; chnum < 9; chnum++)
        {
            chip->channel[chnum].chtype = ch_drum;
        }
        OPL3_ChannelSetupAlg(channel6);
        OPL3_ChannelSetupAlg(channel7);
        OPL3_ChannelSetupAlg(channel8);
        /* hh */
        if (chip->rhy & 0x01)
        {
            OPL3_EnvelopeKeyOn(channel7->slots[0], egk_drum);
        }
        else
        {
            OPL3_EnvelopeKeyOff(channel7->slots[0], egk_drum);
        }
        /* tc */
        if (chip->rhy & 0x02)
        {
            OPL3_EnvelopeKeyOn(channel8->slots[1], egk_drum);
        }
        else
        {
            OPL3_EnvelopeKeyOff(channel8->slots[1], egk_drum);
        }
        /* tom */
        if (chip->rhy & 0x04)
        {
            OPL3_EnvelopeKeyOn(channel8->slots[0], egk_drum);
        }
        else
        {
            OPL3_EnvelopeKeyOff(channel8->slots[0], egk_drum);
        }
        /* sd */
        if (chip->rhy & 0x08)
        {
            OPL3_EnvelopeKeyOn(channel7->slots[1], egk_drum);
        }
        else
        {
            OPL3_EnvelopeKeyOff(channel7->slots[1], egk_drum);
        }
        /* bd */
        if (chip->rhy & 0x10)
        {
            OPL3_EnvelopeKeyOn(channel6->slots[0], egk_drum);
            OPL3_EnvelopeKeyOn(channel6->slots[1], egk_drum);
        }
        else
        {
            OPL3_EnvelopeKeyOff(channel6->slots[0], egk_drum);
            OPL3_EnvelopeKeyOff(channel6->slots[1], egk_drum);
        }
    }
    else
    {
        for (chnum = 6; chnum < 9; chnum++)
        {
            chip->channel[chnum].chtype = ch_2op;
            OPL3_ChannelSetupAlg(&chip->channel[chnum]);
            OPL3_EnvelopeKeyOff(chip->channel[chnum].slots[0], egk_drum);
            OPL3_EnvelopeKeyOff(chip->channel[chnum].slots[1], egk_drum);
        }
    }
}

static void OPL3_ChannelWriteA0(opl3_channel *channel, uint8_t data)
{
    if (channel->chip->newm && channel->chtype == ch_4op2)
    {
        return;
    }
    channel->f_num = (channel->f_num & 0x300) | data;
    channel->ksv = (channel->block << 1)
                 | ((channel->f_num >> (0x09 - channel->chip->nts)) & 0x01);
    OPL3_EnvelopeUpdateKSL(channel->slots[0]);
    OPL3_EnvelopeUpdateKSL(channel->slots[1]);
    if (channel->chip->newm && channel->chtype == ch_4op)
    {
        channel->pair->f_num = channel->f_num;
        channel->pair->ksv = channel->ksv;
        OPL3_EnvelopeUpdateKSL(channel->pair->slots[0]);
        OPL3_EnvelopeUpdateKSL(channel->pair->slots[1]);
    }
}

static void OPL3_ChannelWriteB0(opl3_channel *channel, uint8_t data)
{
    if (channel->chip->newm && channel->chtype == ch_4op2)
    {
        return;
    }
    channel->f_num = (channel->f_num & 0xff) | ((data & 0x03) << 8);
    channel->block = (data >> 2) & 0x07;
    channel->ksv = (channel->block << 1)
                 | ((channel->f_num >> (0x09 - channel->chip->nts)) & 0x01);
    OPL3_EnvelopeUpdateKSL(channel->slots[0]);
    OPL3_EnvelopeUpdateKSL(channel->slots[1]);
    if (channel->chip->newm && channel->chtype == ch_4op)
    {
        channel->pair->f_num = channel->f_num;
        channel->pair->block = channel->block;
        channel->pair->ksv = channel->ksv;
        OPL3_EnvelopeUpdateKSL(channel->pair->slots[0]);
        OPL3_EnvelopeUpdateKSL(channel->pair->slots[1]);
    }
}

static void OPL3_ChannelSetupAlg(opl3_channel *channel)
{
    if (channel->chtype == ch_drum)
    {
        if (channel->ch_num == 7 || channel->ch_num == 8)
        {
            channel->slots[0]->mod = &channel->chip->zeromod;
            channel->slots[1]->mod = &channel->chip->zeromod;
            return;
        }
        switch (channel->alg & 0x01)
        {
        case 0x00:
            channel->slots[0]->mod = &channel->slots[0]->fbmod;
            channel->slots[1]->mod = &channel->slots[0]->out;
            break;
        case 0x01:
            channel->slots[0]->mod = &channel->slots[0]->fbmod;
            channel->slots[1]->mod = &channel->chip->zeromod;
            break;
        }
        return;
    }
    if (channel->alg & 0x08)
    {
        return;
    }
    if (channel->alg & 0x04)
    {
        channel->pair->out[0] = &channel->chip->zeromod;
        channel->pair->out[1] = &channel->chip->zeromod;
        channel->pair->out[2] = &channel->chip->zeromod;
        channel->pair->out[3] = &channel->chip->zeromod;
        switch (channel->alg & 0x03)
        {
        case 0x00:
            channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod;
            channel->pair->slots[1]->mod = &channel->pair->slots[0]->out;
            channel->slots[0]->mod = &channel->pair->slots[1]->out;
            channel->slots[1]->mod = &channel->slots[0]->out;
            channel->out[0] = &channel->slots[1]->out;
            channel->out[1] = &channel->chip->zeromod;
            channel->out[2] = &channel->chip->zeromod;
            channel->out[3] = &channel->chip->zeromod;
            break;
        case 0x01:
            channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod;
            channel->pair->slots[1]->mod = &channel->pair->slots[0]->out;
            channel->slots[0]->mod = &channel->chip->zeromod;
            channel->slots[1]->mod = &channel->slots[0]->out;
            channel->out[0] = &channel->pair->slots[1]->out;
            channel->out[1] = &channel->slots[1]->out;
            channel->out[2] = &channel->chip->zeromod;
            channel->out[3] = &channel->chip->zeromod;
            break;
        case 0x02:
            channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod;
            channel->pair->slots[1]->mod = &channel->chip->zeromod;
            channel->slots[0]->mod = &channel->pair->slots[1]->out;
            channel->slots[1]->mod = &channel->slots[0]->out;
            channel->out[0] = &channel->pair->slots[0]->out;
            channel->out[1] = &channel->slots[1]->out;
            channel->out[2] = &channel->chip->zeromod;
            channel->out[3] = &channel->chip->zeromod;
            break;
        case 0x03:
            channel->pair->slots[0]->mod = &channel->pair->slots[0]->fbmod;
            channel->pair->slots[1]->mod = &channel->chip->zeromod;
            channel->slots[0]->mod = &channel->pair->slots[1]->out;
            channel->slots[1]->mod = &channel->chip->zeromod;
            channel->out[0] = &channel->pair->slots[0]->out;
            channel->out[1] = &channel->slots[0]->out;
            channel->out[2] = &channel->slots[1]->out;
            channel->out[3] = &channel->chip->zeromod;
            break;
        }
    }
    else
    {
        switch (channel->alg & 0x01)
        {
        case 0x00:
            channel->slots[0]->mod = &channel->slots[0]->fbmod;
            channel->slots[1]->mod = &channel->slots[0]->out;
            channel->out[0] = &channel->slots[1]->out;
            channel->out[1] = &channel->chip->zeromod;
            channel->out[2] = &channel->chip->zeromod;
            channel->out[3] = &channel->chip->zeromod;
            break;
        case 0x01:
            channel->slots[0]->mod = &channel->slots[0]->fbmod;
            channel->slots[1]->mod = &channel->chip->zeromod;
            channel->out[0] = &channel->slots[0]->out;
            channel->out[1] = &channel->slots[1]->out;
            channel->out[2] = &channel->chip->zeromod;
            channel->out[3] = &channel->chip->zeromod;
            break;
        }
    }
}

static void OPL3_ChannelWriteC0(opl3_channel *channel, uint8_t data)
{
    channel->fb = (data & 0x0e) >> 1;
    channel->con = data & 0x01;
    channel->alg = channel->con;
    if (channel->chip->newm)
    {
        if (channel->chtype == ch_4op)
        {
            channel->pair->alg = 0x04 | (channel->con << 1) | (channel->pair->con);
            channel->alg = 0x08;
            OPL3_ChannelSetupAlg(channel->pair);
        }
        else if (channel->chtype == ch_4op2)
        {
            channel->alg = 0x04 | (channel->pair->con << 1) | (channel->con);
            channel->pair->alg = 0x08;
            OPL3_ChannelSetupAlg(channel);
        }
        else
        {
            OPL3_ChannelSetupAlg(channel);
        }
    }
    else
    {
        OPL3_ChannelSetupAlg(channel);
    }
    if (channel->chip->newm)
    {
        channel->cha = ((data >> 4) & 0x01) ? ~0 : 0;
        channel->chb = ((data >> 5) & 0x01) ? ~0 : 0;
    }
    else
    {
        channel->cha = channel->chb = (uint16_t)~0;
    }
#if OPL_ENABLE_STEREOEXT
    if (!channel->chip->stereoext)
    {
        channel->leftpan = channel->cha << 16;
        channel->rightpan = channel->chb << 16;
    }
#endif
}

#if OPL_ENABLE_STEREOEXT
static void OPL3_ChannelWriteD0(opl3_channel* channel, uint8_t data)
{
    if (channel->chip->stereoext)
    {
        channel->leftpan = panpot_lut[data ^ 0xff];
        channel->rightpan = panpot_lut[data];
    }
}
#endif

static void OPL3_ChannelKeyOn(opl3_channel *channel)
{
    if (channel->chip->newm)
    {
        if (channel->chtype == ch_4op)
        {
            OPL3_EnvelopeKeyOn(channel->slots[0], egk_norm);
            OPL3_EnvelopeKeyOn(channel->slots[1], egk_norm);
            OPL3_EnvelopeKeyOn(channel->pair->slots[0], egk_norm);
            OPL3_EnvelopeKeyOn(channel->pair->slots[1], egk_norm);
        }
        else if (channel->chtype == ch_2op || channel->chtype == ch_drum)
        {
            OPL3_EnvelopeKeyOn(channel->slots[0], egk_norm);
            OPL3_EnvelopeKeyOn(channel->slots[1], egk_norm);
        }
    }
    else
    {
        OPL3_EnvelopeKeyOn(channel->slots[0], egk_norm);
        OPL3_EnvelopeKeyOn(channel->slots[1], egk_norm);
    }
}

static void OPL3_ChannelKeyOff(opl3_channel *channel)
{
    if (channel->chip->newm)
    {
        if (channel->chtype == ch_4op)
        {
            OPL3_EnvelopeKeyOff(channel->slots[0], egk_norm);
            OPL3_EnvelopeKeyOff(channel->slots[1], egk_norm);
            OPL3_EnvelopeKeyOff(channel->pair->slots[0], egk_norm);
            OPL3_EnvelopeKeyOff(channel->pair->slots[1], egk_norm);
        }
        else if (channel->chtype == ch_2op || channel->chtype == ch_drum)
        {
            OPL3_EnvelopeKeyOff(channel->slots[0], egk_norm);
            OPL3_EnvelopeKeyOff(channel->slots[1], egk_norm);
        }
    }
    else
    {
        OPL3_EnvelopeKeyOff(channel->slots[0], egk_norm);
        OPL3_EnvelopeKeyOff(channel->slots[1], egk_norm);
    }
}

static void OPL3_ChannelSet4Op(opl3_chip *chip, uint8_t data)
{
    uint8_t bit;
    uint8_t chnum;
    for (bit = 0; bit < 6; bit++)
    {
        chnum = bit;
        if (bit >= 3)
        {
            chnum += 9 - 3;
        }
        if ((data >> bit) & 0x01)
        {
            chip->channel[chnum].chtype = ch_4op;
            chip->channel[chnum + 3].chtype = ch_4op2;
        }
        else
        {
            chip->channel[chnum].chtype = ch_2op;
            chip->channel[chnum + 3].chtype = ch_2op;
        }
    }
}

static int16_t OPL3_ClipSample(int32_t sample)
{
    if (sample > 32767)
    {
        sample = 32767;
    }
    else if (sample < -32768)
    {
        sample = -32768;
    }
    return (int16_t)sample;
}

static void OPL3_ProcessSlot(opl3_slot *slot)
{
    OPL3_SlotCalcFB(slot);
    OPL3_EnvelopeCalc(slot);
    OPL3_PhaseGenerate(slot);
    OPL3_SlotGenerate(slot);
}

void OPL3_Generate(opl3_chip *chip, int16_t *buf)
{
    opl3_channel *channel;
    opl3_writebuf *writebuf;
    int16_t **out;
    int32_t mix;
    uint8_t ii;
    int16_t accm;
    uint8_t shift = 0;

    buf[1] = OPL3_ClipSample(chip->mixbuff[1]);

#if OPL_QUIRK_CHANNELSAMPLEDELAY
    for (ii = 0; ii < 15; ii++)
#else
    for (ii = 0; ii < 36; ii++)
#endif
    {
        OPL3_ProcessSlot(&chip->slot[ii]);
    }

    mix = 0;
    for (ii = 0; ii < 18; ii++)
    {
        channel = &chip->channel[ii];
        out = channel->out;
        accm = *out[0] + *out[1] + *out[2] + *out[3];
#if OPL_ENABLE_STEREOEXT
        mix += (int16_t)((accm * channel->leftpan) >> 16);
#else
        mix += (int16_t)(accm & channel->cha);
#endif
    }
    chip->mixbuff[0] = mix;

#if OPL_QUIRK_CHANNELSAMPLEDELAY
    for (ii = 15; ii < 18; ii++)
    {
        OPL3_ProcessSlot(&chip->slot[ii]);
    }
#endif

    buf[0] = OPL3_ClipSample(chip->mixbuff[0]);

#if OPL_QUIRK_CHANNELSAMPLEDELAY
    for (ii = 18; ii < 33; ii++)
    {
        OPL3_ProcessSlot(&chip->slot[ii]);
    }
#endif

    mix = 0;
    for (ii = 0; ii < 18; ii++)
    {
        channel = &chip->channel[ii];
        out = channel->out;
        accm = *out[0] + *out[1] + *out[2] + *out[3];
#if OPL_ENABLE_STEREOEXT
        mix += (int16_t)((accm * channel->rightpan) >> 16);
#else
        mix += (int16_t)(accm & channel->chb);
#endif
    }
    chip->mixbuff[1] = mix;

#if OPL_QUIRK_CHANNELSAMPLEDELAY
    for (ii = 33; ii < 36; ii++)
    {
        OPL3_ProcessSlot(&chip->slot[ii]);
    }
#endif

    if ((chip->timer & 0x3f) == 0x3f)
    {
        chip->tremolopos = (chip->tremolopos + 1) % 210;
    }
    if (chip->tremolopos < 105)
    {
        chip->tremolo = chip->tremolopos >> chip->tremoloshift;
    }
    else
    {
        chip->tremolo = (210 - chip->tremolopos) >> chip->tremoloshift;
    }

    if ((chip->timer & 0x3ff) == 0x3ff)
    {
        chip->vibpos = (chip->vibpos + 1) & 7;
    }

    chip->timer++;

    chip->eg_add = 0;
    if (chip->eg_timer)
    {
        while (shift < 36 && ((chip->eg_timer >> shift) & 1) == 0)
        {
            shift++;
        }
        if (shift > 12)
        {
            chip->eg_add = 0;
        }
        else
        {
            chip->eg_add = shift + 1;
        }
    }

    if (chip->eg_timerrem || chip->eg_state)
    {
        if (chip->eg_timer == 0xfffffffff)
        {
            chip->eg_timer = 0;
            chip->eg_timerrem = 1;
        }
        else
        {
            chip->eg_timer++;
            chip->eg_timerrem = 0;
        }
    }

    chip->eg_state ^= 1;

    while ((writebuf = &chip->writebuf[chip->writebuf_cur]), writebuf->time <= chip->writebuf_samplecnt)
    {
        if (!(writebuf->reg & 0x200))
        {
            break;
        }
        writebuf->reg &= 0x1ff;
        OPL3_WriteReg(chip, writebuf->reg, writebuf->data);
        chip->writebuf_cur = (chip->writebuf_cur + 1) & (OPL_WRITEBUF_SIZE - 1);
    }
    chip->writebuf_samplecnt++;
}

void OPL3_GenerateResampled(opl3_chip *chip, int16_t *buf)
{
    while (chip->samplecnt >= chip->rateratio)
    {
        chip->oldsamples[0] = chip->samples[0];
        chip->oldsamples[1] = chip->samples[1];
        OPL3_Generate(chip, chip->samples);
        chip->samplecnt -= chip->rateratio;
    }
    buf[0] = (int16_t)((chip->oldsamples[0] * (chip->rateratio - chip->samplecnt)
                     + chip->samples[0] * chip->samplecnt) / chip->rateratio);
    buf[1] = (int16_t)((chip->oldsamples[1] * (chip->rateratio - chip->samplecnt)
                     + chip->samples[1] * chip->samplecnt) / chip->rateratio);
    chip->samplecnt += 1 << RSM_FRAC;
}

void OPL3_Reset(opl3_chip *chip, uint32_t samplerate)
{
    opl3_slot *slot;
    opl3_channel *channel;
    uint8_t slotnum;
    uint8_t channum;
    uint8_t local_ch_slot;

    memset(chip, 0, sizeof(opl3_chip));
    for (slotnum = 0; slotnum < 36; slotnum++)
    {
        slot = &chip->slot[slotnum];
        slot->chip = chip;
        slot->mod = &chip->zeromod;
        slot->eg_rout = 0x1ff;
        slot->eg_out = 0x1ff;
        slot->eg_gen = envelope_gen_num_release;
        slot->trem = (uint8_t*)&chip->zeromod;
        slot->slot_num = slotnum;
    }
    for (channum = 0; channum < 18; channum++)
    {
        channel = &chip->channel[channum];
        local_ch_slot = ch_slot[channum];
        channel->slots[0] = &chip->slot[local_ch_slot];
        channel->slots[1] = &chip->slot[local_ch_slot + 3];
        chip->slot[local_ch_slot].channel = channel;
        chip->slot[local_ch_slot + 3].channel = channel;
        if ((channum % 9) < 3)
        {
            channel->pair = &chip->channel[channum + 3];
        }
        else if ((channum % 9) < 6)
        {
            channel->pair = &chip->channel[channum - 3];
        }
        channel->chip = chip;
        channel->out[0] = &chip->zeromod;
        channel->out[1] = &chip->zeromod;
        channel->out[2] = &chip->zeromod;
        channel->out[3] = &chip->zeromod;
        channel->chtype = ch_2op;
        channel->cha = 0xffff;
        channel->chb = 0xffff;
#if OPL_ENABLE_STEREOEXT
        channel->leftpan = 0x10000;
        channel->rightpan = 0x10000;
#endif
        channel->ch_num = channum;
        OPL3_ChannelSetupAlg(channel);
    }
    chip->noise = 1;
    chip->rateratio = (samplerate << RSM_FRAC) / 49716;
    chip->tremoloshift = 4;
    chip->vibshift = 1;

#if OPL_ENABLE_STEREOEXT
    if (!panpot_lut_build)
    {
        int32_t i;
        for (i = 0; i < 256; i++)
        {
            panpot_lut[i] = OPL_SIN(i);
        }
        panpot_lut_build = 1;
    }
#endif
}

void OPL3_WriteReg(opl3_chip *chip, uint16_t reg, uint8_t v)
{
    uint8_t high = (reg >> 8) & 0x01;
    uint8_t regm = reg & 0xff;
    switch (regm & 0xf0)
    {
    case 0x00:
        if (high)
        {
            switch (regm & 0x0f)
            {
            case 0x04:
                OPL3_ChannelSet4Op(chip, v);
                break;
            case 0x05:
                chip->newm = v & 0x01;
#if OPL_ENABLE_STEREOEXT
                chip->stereoext = (v >> 1) & 0x01;
#endif
                break;
            }
        }
        else
        {
            switch (regm & 0x0f)
            {
            case 0x08:
                chip->nts = (v >> 6) & 0x01;
                break;
            }
        }
        break;
    case 0x20:
    case 0x30:
        if (ad_slot[regm & 0x1f] >= 0)
        {
            OPL3_SlotWrite20(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v);
        }
        break;
    case 0x40:
    case 0x50:
        if (ad_slot[regm & 0x1f] >= 0)
        {
            OPL3_SlotWrite40(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v);
        }
        break;
    case 0x60:
    case 0x70:
        if (ad_slot[regm & 0x1f] >= 0)
        {
            OPL3_SlotWrite60(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v);
        }
        break;
    case 0x80:
    case 0x90:
        if (ad_slot[regm & 0x1f] >= 0)
        {
            OPL3_SlotWrite80(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v);
        }
        break;
    case 0xe0:
    case 0xf0:
        if (ad_slot[regm & 0x1f] >= 0)
        {
            OPL3_SlotWriteE0(&chip->slot[18 * high + ad_slot[regm & 0x1f]], v);
        }
        break;
    case 0xa0:
        if ((regm & 0x0f) < 9)
        {
            OPL3_ChannelWriteA0(&chip->channel[9 * high + (regm & 0x0f)], v);
        }
        break;
    case 0xb0:
        if (regm == 0xbd && !high)
        {
            chip->tremoloshift = (((v >> 7) ^ 1) << 1) + 2;
            chip->vibshift = ((v >> 6) & 0x01) ^ 1;
            OPL3_ChannelUpdateRhythm(chip, v);
        }
        else if ((regm & 0x0f) < 9)
        {
            OPL3_ChannelWriteB0(&chip->channel[9 * high + (regm & 0x0f)], v);
            if (v & 0x20)
            {
                OPL3_ChannelKeyOn(&chip->channel[9 * high + (regm & 0x0f)]);
            }
            else
            {
                OPL3_ChannelKeyOff(&chip->channel[9 * high + (regm & 0x0f)]);
            }
        }
        break;
    case 0xc0:
        if ((regm & 0x0f) < 9)
        {
            OPL3_ChannelWriteC0(&chip->channel[9 * high + (regm & 0x0f)], v);
        }
        break;
#if OPL_ENABLE_STEREOEXT
    case 0xd0:
        if ((regm & 0x0f) < 9)
        {
            OPL3_ChannelWriteD0(&chip->channel[9 * high + (regm & 0x0f)], v);
        }
        break;
#endif
    }
}

void OPL3_WriteRegBuffered(opl3_chip *chip, uint16_t reg, uint8_t v)
{
    uint64_t time1, time2;
    opl3_writebuf *writebuf;
    uint32_t writebuf_last;

    writebuf_last = chip->writebuf_last;
    writebuf = &chip->writebuf[writebuf_last];

    if (writebuf->reg & 0x200)
    {
        OPL3_WriteReg(chip, writebuf->reg & 0x1ff, writebuf->data);

        chip->writebuf_cur = (writebuf_last + 1) & (OPL_WRITEBUF_SIZE - 1);
        chip->writebuf_samplecnt = writebuf->time;
    }

    writebuf->reg = reg | 0x200;
    writebuf->data = v;
    time1 = chip->writebuf_lasttime + OPL_WRITEBUF_DELAY;
    time2 = chip->writebuf_samplecnt;

    if (time1 < time2)
    {
        time1 = time2;
    }

    writebuf->time = time1;
    chip->writebuf_lasttime = time1;
    chip->writebuf_last = (writebuf_last + 1) & (OPL_WRITEBUF_SIZE - 1);
}

void OPL3_GenerateStream(opl3_chip *chip, int16_t *sndptr, uint32_t numsamples)
{
    uint32_t i;

    for(i = 0; i < numsamples; i++)
    {
        OPL3_GenerateResampled(chip, sndptr);
        sndptr += 2;
    }
}


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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

*/
/**********************************************************************
   module: PITCH.C

   author: James R. Dose
   date:   June 14, 1993

   Routines for pitch scaling.

   (c) Copyright 1993 James R. Dose.  All Rights Reserved.
**********************************************************************/

#include "compat.h"
#include "pitch.h"

#define MAXDETUNE 50

static uint32_t PitchTable[12][MAXDETUNE];

/*---------------------------------------------------------------------
   Function: PITCH_Init

   Initializes pitch table.
---------------------------------------------------------------------*/

void PITCH_Init(void)
{
    for (int note = 0; note < 12; note++)
        for (int detune = 0; detune < MAXDETUNE; detune++)
            PitchTable[note][detune] = (uint32_t) (65536.f * powf(2.f, (note * MAXDETUNE + detune) / (12.f * MAXDETUNE)));
}

/*---------------------------------------------------------------------
   Function: PITCH_GetScale

   Returns a fixed-point value to scale number the specified amount.
---------------------------------------------------------------------*/

uint32_t PITCH_GetScale(int const pitchoffset)
{
    static bool bInitialized;

    if (!bInitialized)
    {
        PITCH_Init();
        bInitialized = true;
    }

    if (pitchoffset == 0)
        return PitchTable[0][0];

    int noteshift = pitchoffset % 1200;

    if (noteshift < 0)
        noteshift += 1200;

    int const   note   = noteshift / 100;
    int const   detune = (noteshift % 100) / (100 / MAXDETUNE);
    int const   oshift = (pitchoffset - noteshift) / 1200;
    auto const &scale  = PitchTable[note][detune];

    return (oshift < 0) ? (scale >> -oshift) : (scale << oshift);
}


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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

*/
/**********************************************************************
   module: PITCH.H

   author: James R. Dose
   date:   June 14, 1994

   Public header for PITCH.C

   (c) Copyright 1994 James R. Dose.  All Rights Reserved.
**********************************************************************/

#ifndef PITCH_H_
#define PITCH_H_

#include <inttypes.h>

uint32_t PITCH_GetScale(int pitchoffset);
#endif