code:
/*
 * Copyright by Hannu Savolainen 1993-1996
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer. 2.
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

// heavily modified for audiolib
// original definitions found at http://www.cs.albany.edu/~sdc/Linux/linux-2.0/drivers/sound/opl3.h
// it's from old Linux source but the license is pretty clearly 2-clause BSD.

#ifndef opl3_reg_h_
#define opl3_reg_h_

#define OPL3_TEST_REGISTER                  0x01
#define     OPL3_ENABLE_WAVE_SELECT         0x20

#define OPL3_TIMER1_REGISTER                0x02
#define OPL3_TIMER2_REGISTER                0x03
#define OPL3_TIMER_CONTROL_REGISTER         0x04 /* Left side */
#define     OPL3_IRQ_RESET                  0x80
#define     OPL3_TIMER1_MASK                0x40
#define     OPL3_TIMER2_MASK                0x20
#define     OPL3_TIMER1_START               0x01
#define     OPL3_TIMER2_START               0x02

#define OPL3_CONNECTION_SELECT_REGISTER     0x04 /* Right side */
#define     OPL3_RIGHT_4OP_0                0x01
#define     OPL3_RIGHT_4OP_1                0x02
#define     OPL3_RIGHT_4OP_2                0x04
#define     OPL3_LEFT_4OP_0                 0x08
#define     OPL3_LEFT_4OP_1                 0x10
#define     OPL3_LEFT_4OP_2                 0x20

#define OPL3_MODE_REGISTER                  0x05 /* Right side */
#define     OPL3_ENABLE                     0x01
#define     OPL3_OPL4_ENABLE                0x02

#define OPL3_KBD_SPLIT_REGISTER             0x08 /* Left side */
#define     OPL3_COMPOSITE_SINE_WAVE_MODE   0x80 /* Don't use with OPL-3? */
#define     OPL3_KEYBOARD_SPLIT             0x40

#define OPL3_PERCUSSION_REGISTER            0xbd /* Left side only */
#define     OPL3_TREMOLO_DEPTH              0x80
#define     OPL3_VIBRATO_DEPTH              0x40
#define     OPL3_PERCUSSION_ENABLE          0x20
#define     OPL3_BASSDRUM_ON                0x10
#define     OPL3_SNAREDRUM_ON               0x08
#define     OPL3_TOMTOM_ON                  0x04
#define     OPL3_CYMBAL_ON                  0x02
#define     OPL3_HIHAT_ON                   0x01

/*
 *	Offsets to the register banks for operators. To get the
 *	register number just add the operator offset to the bank offset
 *
 *	AM/VIB/EG/KSR/Multiple (0x20 to 0x35)
 */
#define OPL3_AM_VIB                         0x20
#define     OPL3_TREMOLO_ON                 0x80
#define     OPL3_VIBRATO_ON                 0x40
#define     OPL3_SUSTAIN_ON                 0x20
#define     OPL3_KSR                        0x10 /* Key scaling rate */
#define     OPL3_MULTIPLE_MASK              0x0f /* Frequency multiplier */

/*
  *	KSL/Total level (0x40 to 0x55)
  */
#define OPL3_KSL_LEVEL                      0x40
#define     OPL3_KSL_MASK                   0xc0 /* Envelope scaling bits */
#define     OPL3_TOTAL_LEVEL_MASK           0x3f /* Strength (volume) of OP */

/*
 *	Attack / Decay rate (0x60 to 0x75)
 */
#define OPL3_ATTACK_DECAY                   0x60
#define     OPL3_ATTACK_MASK                0xf0
#define     OPL3_DECAY_MASK                 0x0f

/*
 * Sustain level / Release rate (0x80 to 0x95)
 */
#define OPL3_SUSTAIN_RELEASE                0x80
#define     OPL3_SUSTAIN_MASK               0xf0
#define     OPL3_RELEASE_MASK               0x0f

/*
 * Wave select (0xE0 to 0xF5)
 */
#define OPL3_WAVE_SELECT                    0xe0

/*
 *	Offsets to the register banks for voices. Just add to the
 *	voice number to get the register number.
 *
 *	F-Number low bits (0xA0 to 0xA8).
 */
#define OPL3_FNUM_LOW                       0xa0

/*
 *	F-number high bits / Key on / Block (octave) (0xB0 to 0xB8)
 */
#define OPL3_KEYON_BLOCK                    0xb0
#define     OPL3_KEYON_BIT                  0x20
#define     OPL3_BLOCKNUM_MASK              0x1c
#define     OPL3_FNUM_HIGH_MASK             0x03

/*
 *	Feedback / Connection (0xc0 to 0xc8)
 *
 *	These registers have two new bits when the OPL-3 mode
 *	is selected. These bits controls connecting the voice
 *	to the stereo channels. For 4 OP voices this bit is
 *	defined in the second half of the voice (add 3 to the
 *	register offset).
 *
 *	For 4 OP voices the connection bit is used in the
 *	both halfs (gives 4 ways to connect the operators).
 */
#define OPL3_FEEDBACK_CONNECTION            0xc0
#define     OPL3_FEEDBACK_MASK              0x0e /* Valid just for 1st OP of a voice */
#define     OPL3_CONNECTION_BIT             0x01
#define     OPL3_STEREO_BITS                0x30 /* OPL-3 only */
#define         OPL3_VOICE_TO_LEFT          0x10
#define         OPL3_VOICE_TO_RIGHT         0x20

#endif /* opl3_reg_h_ */


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

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

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

See the GNU General Public License for more details.

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

*/
/**********************************************************************
   module: SNDCARDS.H

   author: James R. Dose
   date:   March 31, 1994

   Contains enumerated type definitions for sound cards.

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

#ifndef __SNDCARDS_H
#define __SNDCARDS_H

extern int ASS_PCMSoundDriver;
extern int ASS_MIDISoundDriver;
extern int ASS_EMIDICard;

typedef enum
{
    ASS_SDL,
    ASS_DirectSound,
    ASS_OPL3,
    ASS_WinMM,
    ASS_SF2,
    ASS_ALSA,
    ASS_NumSoundCards,
    ASS_AutoDetect = -2
} soundcardnames;

#endif

repo_name: orochi/nblood

license: mit


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

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

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

See the GNU General Public License for more details.

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

*/
#ifndef ___AL_MIDI_H
#define ___AL_MIDI_H

#include <inttypes.h>
#include "_midi.h"

#define lobyte(num) ((uint32_t)*((char *)&(num)))
#define hibyte(num) ((uint32_t)*(((char *)&(num)) + 1))

#define AL_MaxVolume             127
#define AL_DefaultChannelVolume  90
#define AL_DefaultPitchBendRange 200
#define AL_VoiceNotFound -1

#define ADLIB_PORT 0x388

/* Number of slots for the voices on the chip */
#define AL_NumChipSlots 18

#define NUMADLIBVOICES 9
#define NUMADLIBCHANNELS 16

#define NOTE_ON  0x2000 /* Used to turn note on or toggle note */
#define NOTE_OFF 0x0000

#define MAX_VELOCITY 0x7f
#define MAX_OCTAVE 7
#define MAX_NOTE (MAX_OCTAVE * 12 + 11)
#define FINETUNE_MAX 31
#define FINETUNE_RANGE (FINETUNE_MAX + 1)

#define PITCHBEND_CENTER 1638400

typedef struct AdLibVoice
{
    struct AdLibVoice *next;
    struct AdLibVoice *prev;

    uint32_t num;
    uint32_t key;
    uint32_t velocity;
    uint32_t channel;
    uint32_t pitchleft;
    uint32_t pitchright;
    int      timbre;
    int      port;
    uint32_t status;
} AdLibVoice;

typedef struct
{
    AdLibVoice *start;
    AdLibVoice *end;
} AdLibVoiceList;

typedef struct
{
    AdLibVoiceList Voices;

    int      Timbre;
    int      Pitchbend;
    int      KeyOffset;
    uint32_t KeyDetune;
    uint32_t Volume;
    uint32_t EffectiveVolume;
    int      Pan;
    int      Detune;
    uint32_t RPN;
    int16_t  PitchBendRange;
    int16_t  PitchBendSemiTones;
    int16_t  PitchBendHundreds;
} AdLibChannel;

static int  AL_Init(void);
static void AL_NoteOff(int channel, int key, int velocity);
static void AL_NoteOn(int channel, int key, int vel);
static void AL_ControlChange(int channel, int type, int data);
static void AL_ProgramChange(int channel, int patch);
static void AL_SetPitchBend(int channel, int lsb, int msb);
static void AL_SetVolume(int volume);

#endif


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.
*/
//-------------------------------------------------------------------------

#ifndef ___MIDI_H
#define ___MIDI_H
#include "compat.h"

#define RELATIVE_BEAT(measure, beat, tick) ((tick) + ((beat) << 9) + ((measure) << 16))

//Bobby Prince thinks this may be 100
//#define GENMIDI_DefaultVolume 100
#define GENMIDI_DefaultVolume 90

#define MAX_FORMAT            1

#define NUM_MIDI_CHANNELS     16

#define TIME_PRECISION        16

#define MIDI_HEADER_SIGNATURE 0x6468544d    // "MThd"
#define MIDI_TRACK_SIGNATURE  0x6b72544d    // "MTrk"

#define MIDI_VOLUME                7
#define MIDI_PAN                   10
#define MIDI_DETUNE                94
#define MIDI_RHYTHM_CHANNEL        9
#define MIDI_BANK_SELECT_MSB       0
#define MIDI_BANK_SELECT_LSB       32
#define MIDI_RPN_MSB               100
#define MIDI_RPN_LSB               101
#define MIDI_DATAENTRY_MSB         6
#define MIDI_DATAENTRY_LSB         38
#define MIDI_PITCHBEND_MSB         0
#define MIDI_PITCHBEND_LSB         0
#define MIDI_RUNNING_STATUS        0x80
#define MIDI_NOTE_OFF              0x8
#define MIDI_NOTE_ON               0x9
#define MIDI_POLY_AFTER_TCH        0xA
#define MIDI_CONTROL_CHANGE        0xB
#define MIDI_PROGRAM_CHANGE        0xC
#define MIDI_AFTER_TOUCH           0xD
#define MIDI_PITCH_BEND            0xE
#define MIDI_SPECIAL               0xF
#define MIDI_SYSEX                 0xF0
#define MIDI_SYSEX_CONTINUE        0xF7
#define MIDI_META_EVENT            0xFF
#define MIDI_END_OF_TRACK          0x2F
#define MIDI_HOLD1                 0x40
#define MIDI_SOSTENUTO             0x42
#define MIDI_TEMPO_CHANGE          0x51
#define MIDI_TIME_SIGNATURE        0x58
#define MIDI_REVERB                0x5b
#define MIDI_CHORUS                0x5d
#define MIDI_ALL_SOUNDS_OFF        0x78
#define MIDI_RESET_ALL_CONTROLLERS 0x79
#define MIDI_ALL_NOTES_OFF         0x7b
#define MIDI_MONO_MODE_ON          0x7E
#define MIDI_SYSTEM_RESET          0xFF

#define GET_NEXT_EVENT( track, data ) do { \
    ( data ) = *( track )->pos; \
    ( track )->pos += 1; \
} while (0)

#define GET_MIDI_CHANNEL( event )       ( ( event ) & 0xf )
#define GET_MIDI_COMMAND( event )       ( ( event ) >> 4 )

#define EMIDI_INFINITE          -1
#define EMIDI_END_LOOP_VALUE    127
#define EMIDI_ALL_CARDS         127
#define EMIDI_INCLUDE_TRACK     110
#define EMIDI_EXCLUDE_TRACK     111
#define EMIDI_PROGRAM_CHANGE    112
#define EMIDI_VOLUME_CHANGE     113
#define EMIDI_CONTEXT_START     114
#define EMIDI_CONTEXT_END       115
#define EMIDI_LOOP_START        116
#define EMIDI_LOOP_END          117
#define EMIDI_SONG_LOOP_START   118
#define EMIDI_SONG_LOOP_END     119

#define EMIDI_GeneralMIDI       0
#define EMIDI_SoundBlaster      4
#define EMIDI_AdLib             7

#define EMIDI_AffectsCurrentCard(c, type) (((c) == EMIDI_ALL_CARDS) || ((c) == (type)))
#define EMIDI_NUM_CONTEXTS      7

typedef struct
{
    char *pos;
    char *loopstart;
    int16_t loopcount;
    int16_t RunningStatus;
    unsigned time;
    int FPSecondsPerTick;
    int16_t tick;
    int16_t beat;
    int16_t measure;
    int16_t BeatsPerMeasure;
    int16_t TicksPerBeat;
    int16_t TimeBase;
    int delay;
    int16_t active;
} songcontext;

typedef struct
{
    char *start;
    char *pos;

    int delay;
    int16_t active;
    int16_t RunningStatus;

    int16_t currentcontext;
    songcontext context[EMIDI_NUM_CONTEXTS];

    char EMIDI_IncludeTrack;
    char EMIDI_ProgramChange;
    char EMIDI_VolumeChange;
} track;

#endif


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.

*/
/**********************************************************************
   file:   _MULTIVC.H

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

   Private header for MULTIVOC.C

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

#ifndef MULTIVC_H_
#define MULTIVC_H_

#include "multivoc.h"
#include "libasync_config.h"

#define VOC_8BIT            0x0
#define VOC_16BIT           0x4

#define T_SIXTEENBIT_STEREO 0
#define T_MONO         1
#define T_16BITSOURCE  2
#define T_STEREOSOURCE 4
#define T_DEFAULT      T_SIXTEENBIT_STEREO

#define MV_MAXPANPOSITION  127  /* formerly 31 */
#define MV_NUMPANPOSITIONS ( MV_MAXPANPOSITION + 1 )
#define MV_MAXTOTALVOLUME  255
#define MV_MAXVOLUME       127  /* formerly 63 */

// mirrors FX_MUSIC_PRIORITY from fx_man.h
#define MV_MUSIC_PRIORITY INT_MAX

#define MIX_VOLUME(volume) ((max(0, min((volume), 255)) * (MV_MAXVOLUME + 1)) >> 8)

extern struct VoiceNode *MV_Voices;
extern struct VoiceNode  VoiceList;
extern struct VoiceNode  VoicePool;

extern fix16_t MV_GlobalVolume;
extern fix16_t MV_VolumeSmoothFactor;

static FORCE_INLINE fix16_t SMOOTH_VOLUME(fix16_t const volume, fix16_t const dest) { return volume + fix16_fast_trunc_mul(dest - volume, MV_VolumeSmoothFactor); }

template <typename T> static inline conditional_t<is_signed<T>::value, make_unsigned_t<T>, make_signed_t<T>> FLIP_SIGN(T src)
{
    static constexpr make_unsigned_t<T> msb = ((make_unsigned_t<T>)1) << (sizeof(T) * CHAR_BIT - 1u);
    return src ^ msb;
}

template <typename T> static inline enable_if_t<is_signed<T>::value, T> SCALE_SAMPLE(T src, fix16_t volume)
{
    return (T)fix16_fast_trunc_mul_int_by_fix16(src, volume);
}

template <typename T> static inline T CONVERT_SAMPLE_FROM_SIGNED(int src);
template <> inline int16_t CONVERT_SAMPLE_FROM_SIGNED<int16_t>(int src) { return src; }

template <typename T> static inline int CONVERT_SAMPLE_TO_SIGNED(T src);
template <> inline int CONVERT_SAMPLE_TO_SIGNED<int16_t>(int16_t src) { return src; }

template <typename S, typename D> static inline int CONVERT_LE_SAMPLE_TO_SIGNED(S src);
template <> inline int CONVERT_LE_SAMPLE_TO_SIGNED<uint8_t, int16_t>(uint8_t src) { return FLIP_SIGN(src) << 8; }
template <> inline int CONVERT_LE_SAMPLE_TO_SIGNED<int16_t, int16_t>(int16_t src) { return B_LITTLE16(src); }

template <typename T> static int CLAMP_SAMPLE(int src);
template <> inline int CLAMP_SAMPLE<int16_t>(int src) { return clamp(src, INT16_MIN, INT16_MAX); }

template <typename T> static T MIX_SAMPLES(int signed_sample, T untouched_sample)
{
    return CONVERT_SAMPLE_FROM_SIGNED<T>(CLAMP_SAMPLE<T>(signed_sample + CONVERT_SAMPLE_TO_SIGNED<T>(untouched_sample)));
}

struct split16_t
{
    explicit split16_t(uint16_t x) : v{x} {}

    uint8_t l() const
    {
        return (v & 0x00FFu);
    }
    uint8_t h() const
    {
        return (v & 0xFF00u) >> CHAR_BIT;
    }

private:
    uint16_t v;
};

#define MV_MIXBUFFERSIZE     256
#define MV_NUMBEROFBUFFERS   32
#define MV_TOTALBUFFERSIZE   ( MV_MIXBUFFERSIZE * MV_NUMBEROFBUFFERS )

#define MV_MAXVOICES 256

typedef enum : bool
{
    NoMoreData,
    KeepPlaying
} playbackstatus;

typedef struct VoiceNode
{
    struct VoiceNode *next;
    struct VoiceNode *prev;

    playbackstatus (*GetSound)(struct VoiceNode *);

    uint32_t (*mix)(struct VoiceNode *, uint32_t);

    const char *sound;
    void *rawdataptr;

    union
    {
        const char *NextBlock;
        void (*DemandFeed)(const char** ptr, uint32_t* length, void* userdata);
    };

    struct
    {
        const char *Start;
        const char *End;
        int         Count;
        uint32_t    Size;
    } Loop;

    intptr_t callbackval;

    struct
    {
        fix16_t Left;
        fix16_t Right;
    } PannedVolume, GoalVolume;

    wavefmt_t wavetype;

    int bits;
    int channels;

    fix16_t volume;

    uint32_t BlockLength;

    uint32_t rawdatasiz;  // rawdatasiz-1 is the max permissible index for rawdataptr

    uint32_t PitchScale;
    uint32_t FixedPointBufferSize;

    uint32_t length;
    uint32_t SamplingRate;
    uint32_t RateScale;
    uint32_t position;
    std::atomic<int> Paused;

    int handle;
    int priority;

    async::task<int> task;
} VoiceNode;

typedef struct
{
    uint8_t left;
    uint8_t right;
} Pan;

typedef struct
{
    char RIFF[4];
    uint32_t file_size;
    char WAVE[4];
    char fmt[4];
    uint32_t format_size;
} riff_header;

typedef struct
{
    uint16_t wFormatTag;
    uint16_t nChannels;
    uint32_t nSamplesPerSec;
    uint32_t nAvgBytesPerSec;
    uint16_t nBlockAlign;
    uint16_t nBitsPerSample;
} format_header;

typedef struct
{
    uint8_t DATA[4];
    uint32_t size;
} data_header;

extern Pan MV_PanTable[MV_NUMPANPOSITIONS][MV_MAXVOLUME + 1];
extern int MV_ErrorCode;
extern int MV_Installed;
extern int MV_MixRate;
extern char *MV_MusicBuffer;
extern int MV_BufferSize;
extern int MV_LazyAlloc;

extern int MV_MaxVoices;
extern int MV_Channels;
extern int MV_MixRate;
extern void *MV_InitDataPtr;

extern int MV_MIDIRenderTempo;
extern int MV_MIDIRenderTimer;

static FORCE_INLINE int MV_SetErrorCode(int status)
{
    MV_ErrorCode = status;
    return MV_Error;
}

void MV_PlayVoice(VoiceNode *voice);

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

void MV_SetVoiceMixMode(VoiceNode *voice);
void MV_SetVoiceVolume(VoiceNode *voice, int vol, int left, int right, fix16_t volume);
void MV_SetVoicePitch(VoiceNode *voice, uint32_t rate, int pitchoffset);

int  MV_GetVorbisPosition(VoiceNode *voice);
void MV_SetVorbisPosition(VoiceNode *voice, int position);
int  MV_GetFLACPosition(VoiceNode *voice);
void MV_SetFLACPosition(VoiceNode *voice, int position);
int  MV_GetXAPosition(VoiceNode *voice);
void MV_SetXAPosition(VoiceNode *voice, int position);
int  MV_GetXMPPosition(VoiceNode *voice);
void MV_SetXMPPosition(VoiceNode *voice, int position);

void MV_ReleaseVorbisVoice(VoiceNode *voice);
void MV_ReleaseFLACVoice(VoiceNode *voice);
void MV_ReleaseXAVoice(VoiceNode *voice);
void MV_ReleaseXMPVoice(VoiceNode *voice);

#ifdef HAVE_XMP
extern int MV_XMPInterpolation;
#endif

// implemented in mix.c
template <typename S, typename D> uint32_t MV_MixMono(struct VoiceNode * const voice, uint32_t length);
template <typename S, typename D> uint32_t MV_MixStereo(struct VoiceNode * const voice, uint32_t length);
template <typename T> void MV_Reverb(char const *src, char * const dest, const fix16_t volume, int count);

// implemented in mixst.c
template <typename S, typename D> uint32_t MV_MixMonoStereo(struct VoiceNode * const voice, uint32_t length);
template <typename S, typename D> uint32_t MV_MixStereoStereo(struct VoiceNode * const voice, uint32_t length);

extern char *MV_MixDestination;  // pointer to the next output sample
extern int MV_SampleSize;
extern int MV_RightChannelOffset;

#define loopStartTagCount 3
extern const char *loopStartTags[loopStartTagCount];
#define loopEndTagCount 2
extern const char *loopEndTags[loopEndTagCount];
#define loopLengthTagCount 2
extern const char *loopLengthTags[loopLengthTagCount];

#if defined __POWERPC__ || defined GEKKO
# define BIGENDIAN
#endif

#endif


code:
/*
Copyright (C) 1994-1995 Apogee Software, Ltd.
Copyright (C) 2009 Jonathon Fowler <jf@jonof.id.au>
Copyright (C) EDuke32 developers and contributors

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

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

See the GNU General Public License for more details.

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

*/
/**********************************************************************
   module: AL_MIDI.C

   author: James R. Dose
   date:   April 1, 1994

   Low level routines to support General MIDI music on AdLib compatible
   cards.

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

#include "driver_adlib.h"

#include "_al_midi.h"
#include "_multivc.h"
#include "compat.h"
#include "midi.h"
#include "midifuncs.h"
#include "opl3.h"
#include "opl3_reg.h"

enum
{
   AdLibErr_Error   = -1,
   AdLibErr_Ok      = 0,
};

static int AL_Volume = MIDI_MaxVolume;
static opl3_chip AL_Chip;

static void AL_Shutdown(void);
static int ErrorCode;

int AdLibDrv_GetError(void) { return ErrorCode; }

const char *AdLibDrv_ErrorString(int const ErrorNumber)
{
    switch (ErrorNumber)
    {
        case AdLibErr_Error: return AdLibDrv_ErrorString(ErrorCode);
        case AdLibErr_Ok:    return "AdLib ok.";
        default:             return "Unknown AdLib error.";
    }
}

int AdLibDrv_MIDI_Init(midifuncs * const funcs)
{
    AdLibDrv_MIDI_Shutdown();
    Bmemset(funcs, 0, sizeof(midifuncs));

    funcs->NoteOff           = AL_NoteOff;
    funcs->NoteOn            = AL_NoteOn;
    funcs->PolyAftertouch    = nullptr;
    funcs->ControlChange     = AL_ControlChange;
    funcs->ProgramChange     = AL_ProgramChange;
    funcs->ChannelAftertouch = nullptr;
    funcs->PitchBend         = AL_SetPitchBend;
    funcs->SetVolume         = AL_SetVolume;

    AL_Volume = MIDI_MaxVolume;

    return AdLibErr_Ok;
}

void AdLibDrv_MIDI_HaltPlayback(void) { MV_UnhookMusicRoutine(); }

void AdLibDrv_MIDI_Shutdown(void)
{
    AdLibDrv_MIDI_HaltPlayback();
    AL_Shutdown();
}

int AdLibDrv_MIDI_StartPlayback(void)
{
    AdLibDrv_MIDI_HaltPlayback();

    AL_Init();
    MV_HookMusicRoutine(AdLibDrv_MIDI_Service);

    return MIDI_Ok;
}

void AdLibDrv_MIDI_SetTempo(int const tempo, int const division)
{
    MV_MIDIRenderTempo = tempo * division / 60;
    MV_MIDIRenderTimer = 0;
}

void AdLibDrv_MIDI_Service(void)
{
    int16_t * buffer16 = (int16_t *)MV_MusicBuffer;

    for (int i = 0; i < MV_MIXBUFFERSIZE; i++)
    {
        int16_t buf[2];
        while (MV_MIDIRenderTimer >= MV_MixRate)
        {
            if (MV_MIDIRenderTempo >= 0)
                MIDI_ServiceRoutine();
            MV_MIDIRenderTimer -= MV_MixRate;
        }
        if (MV_MIDIRenderTempo >= 0) MV_MIDIRenderTimer += MV_MIDIRenderTempo;
        OPL3_GenerateResampled(&AL_Chip, buf);
        if (MV_Channels == 2)
        {
            *buffer16++ = clamp((buf[0] * AL_PostAmp * AL_Volume * (1.f / MIDI_MaxVolume)), INT16_MIN, INT16_MAX);
            *buffer16++ = clamp((buf[1] * AL_PostAmp * AL_Volume * (1.f / MIDI_MaxVolume)), INT16_MIN, INT16_MAX);
        }
        else
            *buffer16++ = clamp(((buf[0] + buf[1]) * AL_PostAmp * AL_Volume * (.5f / MIDI_MaxVolume)), INT16_MIN, INT16_MAX);
    }
}

/* Definition of octave information to be ORed onto F-Number */

static uint32_t constexpr OctavePitch[MAX_OCTAVE+1] = {
    0x0000, 0x0400, 0x0800, 0x0C00, 0x1000, 0x1400, 0x1800, 0x1C00,
};

static uint32_t NoteMod12[MAX_NOTE+1];
static uint32_t NoteDiv12[MAX_NOTE+1];

// Pitch table

//static unsigned NotePitch[FINETUNE_MAX+1][12] =
//   {
//      { C, C_SHARP, D, D_SHARP, E, F, F_SHARP, G, G_SHARP, A, A_SHARP, B },
//   };

static uint32_t constexpr NotePitch[FINETUNE_MAX+1][12] = {
    { 0x157, 0x16b, 0x181, 0x198, 0x1b0, 0x1ca, 0x1e5, 0x202, 0x220, 0x241, 0x263, 0x287 },
    { 0x157, 0x16b, 0x181, 0x198, 0x1b0, 0x1ca, 0x1e5, 0x202, 0x220, 0x242, 0x264, 0x288 },
    { 0x158, 0x16c, 0x182, 0x199, 0x1b1, 0x1cb, 0x1e6, 0x203, 0x221, 0x243, 0x265, 0x289 },
    { 0x158, 0x16c, 0x183, 0x19a, 0x1b2, 0x1cc, 0x1e7, 0x204, 0x222, 0x244, 0x266, 0x28a },
    { 0x159, 0x16d, 0x183, 0x19a, 0x1b3, 0x1cd, 0x1e8, 0x205, 0x223, 0x245, 0x267, 0x28b },
    { 0x15a, 0x16e, 0x184, 0x19b, 0x1b3, 0x1ce, 0x1e9, 0x206, 0x224, 0x246, 0x268, 0x28c },
    { 0x15a, 0x16e, 0x185, 0x19c, 0x1b4, 0x1ce, 0x1ea, 0x207, 0x225, 0x247, 0x269, 0x28e },
    { 0x15b, 0x16f, 0x185, 0x19d, 0x1b5, 0x1cf, 0x1eb, 0x208, 0x226, 0x248, 0x26a, 0x28f },
    { 0x15b, 0x170, 0x186, 0x19d, 0x1b6, 0x1d0, 0x1ec, 0x209, 0x227, 0x249, 0x26b, 0x290 },
    { 0x15c, 0x170, 0x187, 0x19e, 0x1b7, 0x1d1, 0x1ec, 0x20a, 0x228, 0x24a, 0x26d, 0x291 },
    { 0x15d, 0x171, 0x188, 0x19f, 0x1b7, 0x1d2, 0x1ed, 0x20b, 0x229, 0x24b, 0x26e, 0x292 },
    { 0x15d, 0x172, 0x188, 0x1a0, 0x1b8, 0x1d3, 0x1ee, 0x20c, 0x22a, 0x24c, 0x26f, 0x293 },
    { 0x15e, 0x172, 0x189, 0x1a0, 0x1b9, 0x1d4, 0x1ef, 0x20d, 0x22b, 0x24d, 0x270, 0x295 },
    { 0x15f, 0x173, 0x18a, 0x1a1, 0x1ba, 0x1d4, 0x1f0, 0x20e, 0x22c, 0x24e, 0x271, 0x296 },
    { 0x15f, 0x174, 0x18a, 0x1a2, 0x1bb, 0x1d5, 0x1f1, 0x20f, 0x22d, 0x24f, 0x272, 0x297 },
    { 0x160, 0x174, 0x18b, 0x1a3, 0x1bb, 0x1d6, 0x1f2, 0x210, 0x22e, 0x250, 0x273, 0x298 },
    { 0x161, 0x175, 0x18c, 0x1a3, 0x1bc, 0x1d7, 0x1f3, 0x211, 0x22f, 0x251, 0x274, 0x299 },
    { 0x161, 0x176, 0x18c, 0x1a4, 0x1bd, 0x1d8, 0x1f4, 0x212, 0x230, 0x252, 0x276, 0x29b },
    { 0x162, 0x176, 0x18d, 0x1a5, 0x1be, 0x1d9, 0x1f5, 0x212, 0x231, 0x254, 0x277, 0x29c },
    { 0x162, 0x177, 0x18e, 0x1a6, 0x1bf, 0x1d9, 0x1f5, 0x213, 0x232, 0x255, 0x278, 0x29d },
    { 0x163, 0x178, 0x18f, 0x1a6, 0x1bf, 0x1da, 0x1f6, 0x214, 0x233, 0x256, 0x279, 0x29e },
    { 0x164, 0x179, 0x18f, 0x1a7, 0x1c0, 0x1db, 0x1f7, 0x215, 0x235, 0x257, 0x27a, 0x29f },
    { 0x164, 0x179, 0x190, 0x1a8, 0x1c1, 0x1dc, 0x1f8, 0x216, 0x236, 0x258, 0x27b, 0x2a1 },
    { 0x165, 0x17a, 0x191, 0x1a9, 0x1c2, 0x1dd, 0x1f9, 0x217, 0x237, 0x259, 0x27c, 0x2a2 },
    { 0x166, 0x17b, 0x192, 0x1aa, 0x1c3, 0x1de, 0x1fa, 0x218, 0x238, 0x25a, 0x27e, 0x2a3 },
    { 0x166, 0x17b, 0x192, 0x1aa, 0x1c3, 0x1df, 0x1fb, 0x219, 0x239, 0x25b, 0x27f, 0x2a4 },
    { 0x167, 0x17c, 0x193, 0x1ab, 0x1c4, 0x1e0, 0x1fc, 0x21a, 0x23a, 0x25c, 0x280, 0x2a6 },
    { 0x168, 0x17d, 0x194, 0x1ac, 0x1c5, 0x1e0, 0x1fd, 0x21b, 0x23b, 0x25d, 0x281, 0x2a7 },
    { 0x168, 0x17d, 0x194, 0x1ad, 0x1c6, 0x1e1, 0x1fe, 0x21c, 0x23c, 0x25e, 0x282, 0x2a8 },
    { 0x169, 0x17e, 0x195, 0x1ad, 0x1c7, 0x1e2, 0x1ff, 0x21d, 0x23d, 0x260, 0x283, 0x2a9 },
    { 0x16a, 0x17f, 0x196, 0x1ae, 0x1c8, 0x1e3, 0x1ff, 0x21e, 0x23e, 0x261, 0x284, 0x2ab },
    { 0x16a, 0x17f, 0x197, 0x1af, 0x1c8, 0x1e4, 0x200, 0x21f, 0x23f, 0x262, 0x286, 0x2ac }
};

// Slot numbers as a function of the voice and the operator.
// ( melodic only)

static int constexpr slotVoice[NUMADLIBVOICES][2] = {
    { 0, 3 },    // voice 0
    { 1, 4 },    // 1
    { 2, 5 },    // 2
    { 6, 9 },    // 3
    { 7, 10 },   // 4
    { 8, 11 },   // 5
    { 12, 15 },  // 6
    { 13, 16 },  // 7
    { 14, 17 },  // 8
};

static int VoiceLevel[AL_NumChipSlots][2];
static int VoiceKsl[AL_NumChipSlots][2];

// This table gives the offset of each slot within the chip.
// offset = fn( slot)

static int8_t constexpr offsetSlot[AL_NumChipSlots] = { 0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19, 20, 21 };

static int VoiceReserved[NUMADLIBVOICES * 2];

static AdLibVoice     Voice[NUMADLIBVOICES * 2];
static AdLibVoiceList Voice_Pool;

static AdLibChannel Channel[NUMADLIBCHANNELS];

static int constexpr AL_LeftPort  = ADLIB_PORT;
static int constexpr AL_RightPort = ADLIB_PORT + 2;

static int constexpr AL_MaxMidiChannel = ARRAY_SIZE(Channel);

int AL_Stereo = TRUE;
int AL_AdditiveMode;

float AL_PostAmp = 3.f;

// TODO: clean up this shit...
#define OFFSET(structure, offset) (*((char **)&(structure)[offset]))

#define LL_AddToTail(type, listhead, node)                                                                                \
    LL_AddNode((char *)(node), (char **)&((listhead)->end), (char **)&((listhead)->start), (intptr_t)offsetof(type, prev), \
               (intptr_t)offsetof(type, next))

#define LL_Remove(type, listhead, node)                                                                                \
    LL_RemoveNode((char *)(node), (char **)&((listhead)->start), (char **)&((listhead)->end), (intptr_t)offsetof(type, next), \
                  (intptr_t)offsetof(type, prev))

static void LL_RemoveNode(char * __restrict item, char ** __restrict head, char ** __restrict tail, intptr_t next, intptr_t prev)
{
    if (OFFSET(item, prev) == nullptr)
        *head = OFFSET(item, next);
    else
        OFFSET(OFFSET(item, prev), next) = OFFSET(item, next);

    if (OFFSET(item, next) == nullptr)
        *tail = OFFSET(item, prev);
    else
        OFFSET(OFFSET(item, next), prev) = OFFSET(item, prev);

    OFFSET(item, next) = nullptr;
    OFFSET(item, prev) = nullptr;
}

static void LL_AddNode(char * __restrict item, char ** __restrict head, char ** __restrict tail, intptr_t next, intptr_t prev)
{
    OFFSET(item, prev) = nullptr;
    OFFSET(item, next) = *head;

    if (*head)
        OFFSET(*head, prev) = item;
    else
        *tail = item;

    *head = item;
}

static FORCE_INLINE void AL_SendOutputToPort(int const port, int const reg, int const data)
{
    OPL3_WriteRegBuffered(&AL_Chip, (uint16_t)(reg + ((port & 2) << 7)), (uint8_t)data);
}

static FORCE_INLINE void AL_SendOutput(int const voice, int const reg, int const data)
{
    AL_SendOutputToPort(voice ? AL_LeftPort : AL_RightPort, reg, data);
}

static void AL_SetVoiceTimbre(int const voice)
{
    int const channel = Voice[voice].channel;
    int const patch = (channel == 9) ? Voice[voice].key + 128 : Channel[channel].Timbre;

    if (Voice[voice].timbre == patch)
        return;

    Voice[voice].timbre = patch;

    auto const timbre = &ADLIB_TimbreBank[patch];

    int const port = Voice[voice].port;
    int const voc  = (voice >= NUMADLIBVOICES) ? voice - NUMADLIBVOICES : voice;
    int       slot = slotVoice[voc][0];
    int       off  = offsetSlot[slot];

    VoiceLevel[slot][port] = OPL3_TOTAL_LEVEL_MASK - (timbre->Level[0] & OPL3_TOTAL_LEVEL_MASK);
    VoiceKsl[slot][port]   = timbre->Level[0] & OPL3_KSL_MASK;

    AL_SendOutput(port, OPL3_FNUM_LOW + voc, 0);
    AL_SendOutput(port, OPL3_KEYON_BLOCK + voc, 0);

    // Let voice clear the release
    AL_SendOutput(port, OPL3_SUSTAIN_RELEASE + off, 0xff);

    AL_SendOutput(port, OPL3_ATTACK_DECAY + off, timbre->Env1[0]);
    AL_SendOutput(port, OPL3_SUSTAIN_RELEASE + off, timbre->Env2[0]);
    AL_SendOutput(port, OPL3_ENABLE_WAVE_SELECT + off, timbre->SAVEK[0]);
    AL_SendOutput(port, OPL3_WAVE_SELECT + off, timbre->Wave[0]);

    AL_SendOutput(port, OPL3_KSL_LEVEL + off, timbre->Level[0]);
    slot = slotVoice[voc][1];

    AL_SendOutput(port, OPL3_FEEDBACK_CONNECTION + voc, (timbre->Feedback & OPL3_FEEDBACK_MASK) | OPL3_STEREO_BITS);

    off = offsetSlot[slot];

    VoiceLevel[slot][port] = OPL3_TOTAL_LEVEL_MASK - (timbre->Level[1] & OPL3_TOTAL_LEVEL_MASK);
    VoiceKsl[slot][port]   = timbre->Level[1] & OPL3_KSL_MASK;

    AL_SendOutput(port, OPL3_KSL_LEVEL + off, OPL3_TOTAL_LEVEL_MASK);

    // Let voice clear the release
    AL_SendOutput(port, OPL3_SUSTAIN_RELEASE + off, 0xff);

    AL_SendOutput(port, OPL3_ATTACK_DECAY + off, timbre->Env1[1]);
    AL_SendOutput(port, OPL3_SUSTAIN_RELEASE + off, timbre->Env2[1]);
    AL_SendOutput(port, OPL3_ENABLE_WAVE_SELECT + off, timbre->SAVEK[1]);
    AL_SendOutput(port, OPL3_WAVE_SELECT + off, timbre->Wave[1]);
}

static void AL_SetVoiceVolume(int const voice)
{
    int  const channel  = Voice[voice].channel;
    auto const timbre   = &ADLIB_TimbreBank[Voice[voice].timbre];
    int  const velocity = min<int>(Voice[voice].velocity + timbre->Velocity, MAX_VELOCITY);

    int const voc  = (voice >= NUMADLIBVOICES) ? voice - NUMADLIBVOICES : voice;
    int const slot = slotVoice[voc][1];
    int const port = Voice[voice].port;

    // amplitude
    auto t1 = (uint32_t)VoiceLevel[slot][port] * (velocity + 0x80);
    t1 = (Channel[channel].Volume * t1) >> 15;

    uint32_t volume = t1 ^ OPL3_TOTAL_LEVEL_MASK;
    volume |= (uint32_t)VoiceKsl[slot][port];

    AL_SendOutput(port, OPL3_KSL_LEVEL + offsetSlot[slot], volume);

    // Check if this timbre is Additive
    if (timbre->Feedback & 0x01)
    {
        int const slot = slotVoice[voc][0];
        uint32_t t2;

        // amplitude
        if (AL_AdditiveMode)
            t1 = (uint32_t)VoiceLevel[slot][port] * (velocity + 0x80);

        t2 = (Channel[channel].Volume * t1) >> 15;

        volume = t2 ^ OPL3_TOTAL_LEVEL_MASK;
        volume |= (uint32_t)VoiceKsl[slot][port];

        AL_SendOutput(port, OPL3_KSL_LEVEL + offsetSlot[slot], volume);
    }
}

static int AL_AllocVoice(void)
{
    if (!Voice_Pool.start)
        return AL_VoiceNotFound;

    int const voice = Voice_Pool.start->num;
    LL_Remove(AdLibVoice, &Voice_Pool, &Voice[voice]);
    return voice;
}

static int AL_GetVoice(int const channel, int const key)
{
    auto const *voice = Channel[channel].Voices.start;

    while (voice != nullptr)
    {
        if (voice->key == (uint32_t)key)
            return  voice->num;
        voice = voice->next;
    }

    return  AL_VoiceNotFound;
}

static void AL_SetVoicePitch(int const voice)
{
    int const port    = Voice[voice].port;
    int const voc     = (voice >= NUMADLIBVOICES) ? voice - NUMADLIBVOICES : voice;
    int const channel = Voice[voice].channel;

    int patch, note;

    if (channel == 9)
    {
        patch = Voice[voice].key + 128;
        note  = ADLIB_TimbreBank[patch].Transpose;
    }
    else
    {
        patch = Channel[channel].Timbre;
        note  = Voice[voice].key + ADLIB_TimbreBank[patch].Transpose;
    }

    note += Channel[channel].KeyOffset - 12;
    note = clamp(note, 0, MAX_NOTE);

    int detune = Channel[channel].KeyDetune;

    int ScaleNote = NoteMod12[note];
    int Octave    = NoteDiv12[note];

    int pitch = OctavePitch[Octave] | NotePitch[detune][ScaleNote];

    Voice[voice].pitchleft = pitch;

    pitch |= Voice[voice].status;

    AL_SendOutput(port, OPL3_FNUM_LOW + voc, pitch);
    AL_SendOutput(port, OPL3_KEYON_BLOCK + voc, pitch >> 8);
}

static void AL_SetVoicePan(int const voice)
{
    int const port    = Voice[voice].port;
    int const voc     = (voice >= NUMADLIBVOICES) ? voice - NUMADLIBVOICES : voice;
    int const channel = Voice[voice].channel;

    if (AL_Stereo)
        AL_SendOutput(port, 0xD0 + voc, Channel[channel].Pan << 1);
}

static void AL_SetChannelVolume(int const channel, int const volume)
{
    Channel[channel].Volume = clamp(volume, 0, AL_MaxVolume);

    auto voice = Channel[channel].Voices.start;

    while (voice != nullptr)
    {
        AL_SetVoiceVolume(voice->num);
        voice = voice->next;
    }
}

static void AL_SetChannelPan(int const channel, int const pan)
{
    // Don't pan drum sounds
    if (channel != 9)
        Channel[channel].Pan = pan;

    auto voice = Channel[channel].Voices.start;
    while (voice != nullptr)
    {
        AL_SetVoicePan(voice->num);
        voice = voice->next;
    }
}

static void AL_SetChannelDetune(int const channel, int const detune) { Channel[channel].Detune = detune; }

static void AL_ResetVoices(void)
{
    Voice_Pool.start = nullptr;
    Voice_Pool.end   = nullptr;

    int const numvoices = NUMADLIBVOICES * 2;

    for (int index = 0; index < numvoices; index++)
    {
        if (VoiceReserved[index] == FALSE)
        {
            Voice[index].num      = index;
            Voice[index].key      = 0;
            Voice[index].velocity = 0;
            Voice[index].channel  = -1;
            Voice[index].timbre   = -1;
            Voice[index].port     = (index < NUMADLIBVOICES) ? 0 : 1;
            Voice[index].status   = NOTE_OFF;
            LL_AddToTail(AdLibVoice, &Voice_Pool, &Voice[index]);
        }
    }

    for (int index = 0; index < NUMADLIBCHANNELS; index++)
    {
        Channel[index] = {};
        Channel[index].Volume             = AL_DefaultChannelVolume;
        Channel[index].Pan                = 64;
        Channel[index].PitchBendRange     = AL_DefaultPitchBendRange;
        Channel[index].PitchBendSemiTones = AL_DefaultPitchBendRange / 100;
        Channel[index].PitchBendHundreds  = AL_DefaultPitchBendRange % 100;
    }
}

static void AL_CalcPitchInfo(void)
{
    for (int note = 0; note <= MAX_NOTE; note++)
    {
        NoteMod12[note] = note % 12;
        NoteDiv12[note] = note / 12;
    }

#if 0
    for (int finetune = 1; finetune <= FINETUNE_MAX; finetune++)
    {
        double detune = pow(2, (double)finetune / (12.0 * FINETUNE_RANGE));

        for (int note = 0; note < 12; note++)
            NotePitch[finetune][note] = ((double)NotePitch[0][note] * detune);
    }
#endif
}

static void AL_FlushCard(int const port)
{
    for (int i = 0; i < NUMADLIBVOICES; i++)
    {
        if (VoiceReserved[i])
            continue;

        auto slot1 = offsetSlot[slotVoice[i][0]];
        auto slot2 = offsetSlot[slotVoice[i][1]];

        AL_SendOutputToPort(port, OPL3_FNUM_LOW + i, 0);
        AL_SendOutputToPort(port, OPL3_KEYON_BLOCK + i, 0);

        AL_SendOutputToPort(port, OPL3_WAVE_SELECT + slot1, 0);
        AL_SendOutputToPort(port, OPL3_WAVE_SELECT + slot2, 0);

        // Set the envelope to be fast and quiet
        AL_SendOutputToPort(port, OPL3_ATTACK_DECAY + slot1, 0xff);
        AL_SendOutputToPort(port, OPL3_ATTACK_DECAY + slot2, 0xff);
        AL_SendOutputToPort(port, OPL3_SUSTAIN_RELEASE + slot1, 0xff);
        AL_SendOutputToPort(port, OPL3_SUSTAIN_RELEASE + slot2, 0xff);

        // Maximum attenuation
        AL_SendOutputToPort(port, OPL3_KSL_LEVEL + slot1, 0xff);
        AL_SendOutputToPort(port, OPL3_KSL_LEVEL + slot2, 0xff);
    }
}

static void AL_Reset(void)
{
    AL_SendOutputToPort(ADLIB_PORT, 1, OPL3_ENABLE_WAVE_SELECT);
    AL_SendOutputToPort(ADLIB_PORT, OPL3_KBD_SPLIT_REGISTER, 0);

    // Set the values: AM Depth, VIB depth & Rhythm
    AL_SendOutputToPort(ADLIB_PORT, OPL3_PERCUSSION_REGISTER, 0);

    AL_SetStereo(AL_Stereo);

    AL_FlushCard(AL_LeftPort);
    AL_FlushCard(AL_RightPort);
}

void AL_SetStereo(int const stereo) { AL_SendOutputToPort(AL_RightPort, OPL3_MODE_REGISTER, (stereo << 1) + 1); }

static void AL_NoteOff(int const channel, int const key, int velocity)
{
    UNREFERENCED_PARAMETER(velocity);

    // We only play channels 1 through 10
    if (channel >= AL_MaxMidiChannel)
        return;

    int const voice = AL_GetVoice(channel, key);

    if (voice == AL_VoiceNotFound)
        return;

    Voice[voice].status = NOTE_OFF;

    int const port = Voice[voice].port;
    int const voc  = (voice >= NUMADLIBVOICES) ? voice - NUMADLIBVOICES : voice;

    AL_SendOutput(port, OPL3_KEYON_BLOCK + voc, hibyte(Voice[voice].pitchleft));

    LL_Remove(AdLibVoice, &Channel[channel].Voices, &Voice[voice]);
    LL_AddToTail(AdLibVoice, &Voice_Pool, &Voice[voice]);
}

static void AL_NoteOn(int const channel, int const key, int const velocity)
{
    // We only play channels 1 through 10
    if (channel >= AL_MaxMidiChannel)
        return;

    if (velocity == 0)
    {
        AL_NoteOff(channel, key, velocity);
        return;
    }

    int voice = AL_AllocVoice();

    if (voice == AL_VoiceNotFound)
    {
        if (Channel[9].Voices.start)
        {
            AL_NoteOff(9, Channel[9].Voices.start->key, 0);
            voice = AL_AllocVoice();
        }

        if (voice == AL_VoiceNotFound)
            return;
    }

    Voice[voice].key      = key;
    Voice[voice].channel  = channel;
    Voice[voice].velocity = velocity;
    Voice[voice].status   = NOTE_ON;

    LL_AddToTail(AdLibVoice, &Channel[channel].Voices, &Voice[voice]);

    AL_SetVoiceTimbre(voice);
    AL_SetVoiceVolume(voice);
    AL_SetVoicePitch(voice);
    AL_SetVoicePan(voice);
}

static FORCE_INLINE void AL_AllNotesOff(int const channel)
{
    while (Channel[channel].Voices.start != nullptr)
        AL_NoteOff(channel, Channel[channel].Voices.start->key, 0);
}

static void AL_ControlChange(int const channel, int const type, int const data)
{
    // We only play channels 1 through 10
    if (channel >= AL_MaxMidiChannel)
        return;

    switch (type)
    {
        case MIDI_VOLUME:
            AL_SetChannelVolume(channel, data);
            break;

        case MIDI_PAN:
            AL_SetChannelPan(channel, data);
            break;

        case MIDI_DETUNE:
            AL_SetChannelDetune(channel, data);
            break;

        case MIDI_ALL_NOTES_OFF:
            AL_AllNotesOff(channel);
            break;

        case MIDI_RESET_ALL_CONTROLLERS:
            AL_ResetVoices();
            AL_SetChannelVolume(channel, AL_DefaultChannelVolume);
            AL_SetChannelPan(channel, 64);
            AL_SetChannelDetune(channel, 0);
            break;

        case MIDI_RPN_MSB:
            Channel[channel].RPN &= 0x00FF;
            Channel[channel].RPN |= (data & 0xFF) << 8;
            break;

        case MIDI_RPN_LSB:
            Channel[channel].RPN &= 0xFF00;
            Channel[channel].RPN |= data & 0xFF;
            break;

        case MIDI_DATAENTRY_MSB:
            if (Channel[channel].RPN == MIDI_PITCHBEND_MSB)
            {
                Channel[channel].PitchBendSemiTones = data;
                Channel[channel].PitchBendRange     = Channel[channel].PitchBendSemiTones * 100 + Channel[channel].PitchBendHundreds;
            }
            break;

        case MIDI_DATAENTRY_LSB:
            if (Channel[channel].RPN == MIDI_PITCHBEND_LSB)
            {
                Channel[channel].PitchBendHundreds = data;
                Channel[channel].PitchBendRange    = Channel[channel].PitchBendSemiTones * 100 + Channel[channel].PitchBendHundreds;
            }
            break;
    }
}

static void AL_ProgramChange(int const channel, int const patch)
{
    // We only play channels 1 through 10
    if (channel >= AL_MaxMidiChannel)
        return;

    Channel[channel].Timbre = patch;
}

static void AL_SetPitchBend(int const channel, int const lsb, int const msb)
{
    // We only play channels 1 through 10
    if (channel >= AL_MaxMidiChannel)
        return;

    int const pitchbend = lsb + (msb << 8);
    int const TotalBend = pitchbend * Channel[channel].PitchBendRange / (PITCHBEND_CENTER / FINETUNE_RANGE);

    Channel[channel].Pitchbend = pitchbend;
    Channel[channel].KeyOffset = (int)(TotalBend / FINETUNE_RANGE);
    Channel[channel].KeyOffset -= Channel[channel].PitchBendSemiTones;

    Channel[channel].KeyDetune = (uint32_t)(TotalBend % FINETUNE_RANGE);

    auto voice = Channel[channel].Voices.start;
    while (voice != nullptr)
    {
        AL_SetVoicePitch(voice->num);
        voice = voice->next;
    }
}

static void AL_SetVolume(int volume) { AL_Volume = clamp(volume, 0, MIDI_MaxVolume); }

static void AL_Shutdown(void)
{
    OPL3_Reset(&AL_Chip, MV_MixRate);
    AL_Reset();
    AL_ResetVoices();
}

static int AL_Init(void)
{
    AL_Shutdown();
    AL_CalcPitchInfo();

    return AdLibErr_Ok;
}

void AL_RegisterTimbreBank(uint8_t *timbres)
{
    for (int i = 0; i < 256; i++)
    {
        ADLIB_TimbreBank[i].SAVEK[0]  = *(timbres++);
        ADLIB_TimbreBank[i].SAVEK[1]  = *(timbres++);
        ADLIB_TimbreBank[i].Level[0]  = *(timbres++);
        ADLIB_TimbreBank[i].Level[1]  = *(timbres++);
        ADLIB_TimbreBank[i].Env1[0]   = *(timbres++);
        ADLIB_TimbreBank[i].Env1[1]   = *(timbres++);
        ADLIB_TimbreBank[i].Env2[0]   = *(timbres++);
        ADLIB_TimbreBank[i].Env2[1]   = *(timbres++);
        ADLIB_TimbreBank[i].Wave[0]   = *(timbres++);
        ADLIB_TimbreBank[i].Wave[1]   = *(timbres++);
        ADLIB_TimbreBank[i].Feedback  = *(timbres++);
        ADLIB_TimbreBank[i].Transpose = *(int8_t *)(timbres++);
        ADLIB_TimbreBank[i].Velocity  = *(int8_t *)(timbres++);
    }
}


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

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

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

 See the GNU General Public License for more details.

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

 */

#include "al_midi.h"
#include "midifuncs.h"
#include "opl3.h"

int         AdLibDrv_GetError(void);
const char *AdLibDrv_ErrorString(int ErrorNumber);

int  AdLibDrv_MIDI_Init(midifuncs *);
void AdLibDrv_MIDI_Shutdown(void);
int  AdLibDrv_MIDI_StartPlayback(void);
void AdLibDrv_MIDI_HaltPlayback(void);
void AdLibDrv_MIDI_SetTempo(int tempo, int division);
void AdLibDrv_MIDI_Service(void);


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

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

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

 See the GNU General Public License for more details.

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

 */

/**
 * ALSA MIDI output
 */

#include "driver_alsa.h"

#include "midi.h"
#include "multivoc.h"

#include <alsa/asoundlib.h>
#include <math.h>
#include <pthread.h>
#include <unistd.h>

enum
{
    ALSAErr_Warning = -2,
    ALSAErr_Error   = -1,
    ALSAErr_Ok      = 0,
    ALSAErr_Uninitialised,
    ALSAErr_SeqOpen,
    ALSAErr_CreateSimplePort,
    ALSAErr_AllocQueue,
    ALSAErr_ConnectTo,
    ALSAErr_DeviceNotFound,
    ALSAErr_StartQueue,
    ALSAErr_StopQueue,
    ALSAErr_PlayThread
};

int ALSA_ClientID = 0;
int ALSA_PortID   = 0;

static int ErrorCode = ALSAErr_Ok;

static snd_seq_t *seq;
static int        seq_port  = -1;
static int        seq_queue = -1;
static int        queueRunning;

static pthread_t thread;
static int       threadRunning;
static int       threadQuit;
static void (*threadService)(void);

static unsigned int threadTimer;
static unsigned int threadQueueTimer;
static int          threadQueueTicks;
#define THREAD_QUEUE_INTERVAL 20    // 1/20 sec

int ALSADrv_GetError(void) { return ErrorCode; }

const char *ALSADrv_ErrorString(int ErrorNumber)
{
    const char *ErrorString;

    switch (ErrorNumber)
    {
        case ALSAErr_Warning:
        case ALSAErr_Error:
            ErrorString = ALSADrv_ErrorString(ErrorCode);
            break;

        case ALSAErr_Ok:
            ErrorString = "ALSA ok.";
            break;

        case ALSAErr_Uninitialised:
            ErrorString = "ALSA uninitialised.";
            break;

        case ALSAErr_SeqOpen:
            ErrorString = "ALSA error: failed in snd_seq_open.\n";
            break;

        case ALSAErr_CreateSimplePort:
            ErrorString = "ALSA error: failed in snd_seq_create_simple_port.\n";
            break;

        case ALSAErr_AllocQueue:
            ErrorString = "ALSA error: failed in snd_seq_alloc_queue.\n";
            break;

        case ALSAErr_ConnectTo:
            ErrorString = "ALSA error: failed in snd_seq_connect_to.\n";
            break;

        case ALSAErr_DeviceNotFound:
            ErrorString = "ALSA error: device not found.\n";
            break;

        default:
            ErrorString = "Unknown ALSA error.";
            break;
    }

    return ErrorString;
}

static inline void sequence_event(snd_seq_event_t *ev, bool sync_output_queue)
{
    int result = 0;

    snd_seq_ev_set_subs(ev);
    snd_seq_ev_set_source(ev, seq_port);
    snd_seq_ev_schedule_tick(ev, seq_queue, 0, threadTimer);
    result = snd_seq_event_output(seq, ev);

    if (result < 0)
        LOG_F(ERROR, "Unable to queue ALSA event: snd_seq_event_output error %d", result);
    else
    {
        do { } while ((result = snd_seq_drain_output(seq)) > 0);

        if (result < 0)
            LOG_F(ERROR, "Unable to drain ALSA output: snd_seq_drain_output error %d", result);

        if (sync_output_queue)
            snd_seq_sync_output_queue(seq);
    }
}

static void Func_NoteOff(int channel, int key, int velocity)
{
    snd_seq_event_t ev;
    snd_seq_ev_clear(&ev);
    snd_seq_ev_set_noteoff(&ev, channel, key, velocity);
    sequence_event(&ev, true);
}

static void Func_NoteOn(int channel, int key, int velocity)
{
    snd_seq_event_t ev;
    snd_seq_ev_clear(&ev);
    snd_seq_ev_set_noteon(&ev, channel, key, velocity);
    sequence_event(&ev, true);
}

static void Func_PolyAftertouch(int channel, int key, int pressure)
{
    snd_seq_event_t ev;
    snd_seq_ev_clear(&ev);
    snd_seq_ev_set_keypress(&ev, channel, key, pressure);
    sequence_event(&ev, true);
}

static void Func_ControlChange(int channel, int number, int value)
{
    snd_seq_event_t ev;
    snd_seq_ev_clear(&ev);
    snd_seq_ev_set_controller(&ev, channel, number, value);
    sequence_event(&ev, false);
}

static void Func_ProgramChange(int channel, int program)
{
    snd_seq_event_t ev;
    snd_seq_ev_clear(&ev);
    snd_seq_ev_set_pgmchange(&ev, channel, program);
    sequence_event(&ev, true);
}

static void Func_ChannelAftertouch(int channel, int pressure)
{
    snd_seq_event_t ev;
    snd_seq_ev_clear(&ev);
    snd_seq_ev_set_chanpress(&ev, channel, pressure);
    sequence_event(&ev, true);
}

static void Func_PitchBend(int channel, int lsb, int msb)
{
    snd_seq_event_t ev;
    snd_seq_ev_clear(&ev);
    snd_seq_ev_set_pitchbend(&ev, channel, (lsb|(msb << 7)) - 8192);
    sequence_event(&ev, true);
}

static unsigned int get_tick(void)
{
    snd_seq_queue_status_t *status;
    snd_seq_queue_status_alloca(&status);

    int result = snd_seq_get_queue_status(seq, seq_queue, status);
    if (result < 0)
    {
        LOG_F(ERROR, "Unable to read ALSA queue status: snd_seq_get_queue_status error %d", result);
        return 0;
    }

    return (unsigned int) snd_seq_queue_status_get_tick_time(status);
}

static void * threadProc(void *)
{
    struct timeval tv;
    int sleepAmount = 1000000 / THREAD_QUEUE_INTERVAL;
    unsigned int sequenceTime;

    // prime the pump
    threadTimer = get_tick();
    threadQueueTimer = threadTimer + threadQueueTicks;

    while (threadTimer < threadQueueTimer)
    {
        if (threadService)
            threadService();
        threadTimer++;
    }

    while (!threadQuit)
    {
        tv.tv_sec = 0;
        tv.tv_usec = sleepAmount;

        select(0, NULL, NULL, NULL, &tv);

        sequenceTime = get_tick();
        sleepAmount = 1000000 / THREAD_QUEUE_INTERVAL;

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

        threadQueueTimer = sequenceTime + threadQueueTicks;
        while (threadTimer < threadQueueTimer)
        {
            if (threadService)
                threadService();
            threadTimer++;
        }
    }

    return NULL;
}

int ALSADrv_MIDI_Init(midifuncs * funcs)
{
    int result;

    ALSADrv_MIDI_Shutdown();
    memset(funcs, 0, sizeof(midifuncs));

    result = snd_seq_open(&seq, "default", SND_SEQ_OPEN_OUTPUT, 0);
    if (result < 0)
    {
        LOG_F(ERROR, "Unable to initialize ALSA: snd_seq_open error %d", result);
        ErrorCode = ALSAErr_SeqOpen;
        return ALSAErr_Error;
    }

    std::vector<alsa_mididevinfo_t> alsaDevices = ALSADrv_MIDI_ListPorts();
    bool deviceFound = false;
    for (const alsa_mididevinfo_t &device : alsaDevices)
        if (device.clntid == ALSA_ClientID && device.portid == ALSA_PortID)
        {
            deviceFound = true;
            break;
        }
    if (!deviceFound)
    {
        ALSADrv_MIDI_Shutdown();
        LOG_F(ERROR, "Unable to find ALSA device at %d:%d", ALSA_ClientID, ALSA_PortID);
        ErrorCode = ALSAErr_DeviceNotFound;
        return ALSAErr_Error;
    }

    seq_port = snd_seq_create_simple_port(seq, "output",
                  SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_WRITE,
                  SND_SEQ_PORT_TYPE_APPLICATION);
    if (seq_port < 0)
    {
        ALSADrv_MIDI_Shutdown();
        LOG_F(ERROR, "Unable to create ALSA port: snd_seq_create_simple_port error %d", seq_port);
        ErrorCode = ALSAErr_CreateSimplePort;
        return ALSAErr_Error;
    }

    snd_seq_set_client_name(seq, "EDuke32");

    seq_queue = snd_seq_alloc_queue(seq);
    if (seq_queue < 0)
    {
        ALSADrv_MIDI_Shutdown();
        LOG_F(ERROR, "Unable to allocate ALSA queue: snd_seq_alloc_queue erorr %d", seq_queue);
        ErrorCode = ALSAErr_AllocQueue;
        return ALSAErr_Error;
    }

    result = snd_seq_connect_to(seq, seq_port, ALSA_ClientID, ALSA_PortID);
    if (result < 0)
    {
        ALSADrv_MIDI_Shutdown();
        LOG_F(ERROR, "Unable to connect ALSA port to device at %d:%d: snd_seq_connect_to error %d", ALSA_ClientID, ALSA_PortID, result);
        ErrorCode = ALSAErr_ConnectTo;
        return ALSAErr_Error;
    }

    funcs->NoteOff           = Func_NoteOff;
    funcs->NoteOn            = Func_NoteOn;
    funcs->PolyAftertouch    = Func_PolyAftertouch;
    funcs->ControlChange     = Func_ControlChange;
    funcs->ProgramChange     = Func_ProgramChange;
    funcs->ChannelAftertouch = Func_ChannelAftertouch;
    funcs->PitchBend         = Func_PitchBend;

    return ALSAErr_Ok;
}

void ALSADrv_MIDI_Shutdown(void)
{
    ALSADrv_MIDI_HaltPlayback();

    if (seq_queue >= 0)
        snd_seq_free_queue(seq, seq_queue);

    if (seq_port >= 0)
        snd_seq_delete_simple_port(seq, seq_port);

    if (seq)
        snd_seq_close(seq);

    seq_queue = -1;
    seq_port  = -1;
    seq       = 0;
}

int ALSADrv_MIDI_StartPlayback(void)
{
    ALSADrv_MIDI_HaltPlayback();

    threadService = ALSADrv_MIDI_Service;
    threadQuit = 0;

    ALSADrv_MIDI_QueueStart();

    if (pthread_create(&thread, NULL, threadProc, NULL))
    {
        LOG_F(ERROR, "Unable to create thread for ALSA playback.");
        ALSADrv_MIDI_HaltPlayback();
        return ALSAErr_PlayThread;
    }

    threadRunning = 1;
    return 0;
}

void ALSADrv_MIDI_HaltPlayback(void)
{
    if (!threadRunning)
        return;

    threadQuit = 1;

    void *ret;
    if (pthread_join(thread, &ret))
        LOG_F(ERROR, "Unable to terminate ALSA playback thread.");

    ALSADrv_MIDI_QueueStop();

    threadRunning = 0;
}

void ALSADrv_MIDI_SetTempo(int tempo, int division)
{
    double tps;
    snd_seq_queue_tempo_t * t;

    ALSADrv_MIDI_QueueStop();

    snd_seq_queue_tempo_alloca(&t);
    snd_seq_queue_tempo_set_tempo(t, 60000000 / tempo);
    snd_seq_queue_tempo_set_ppq(t, division);
    snd_seq_set_queue_tempo(seq, seq_queue, t);

    tps = ((double)tempo * (double)division) / 60.0;
    threadQueueTicks = (int)ceil(tps / (double)THREAD_QUEUE_INTERVAL);

    ALSADrv_MIDI_QueueStart();
}

void ALSADrv_MIDI_Lock(void)    {}
void ALSADrv_MIDI_Unlock(void)  {}
void ALSADrv_MIDI_Service(void) { MIDI_ServiceRoutine(); }

void ALSADrv_MIDI_QueueStart(void)
{
    int result;

    if (!queueRunning)
    {
        result = snd_seq_start_queue(seq, seq_queue, NULL);

        if (result < 0)
            LOG_F(ERROR, "Unable to start ALSA queue: snd_seq_start_queue error %d", result);

        do { } while ((result = snd_seq_drain_output(seq)) > 0);

        if (result < 0)
            LOG_F(ERROR, "Unable to drain ALSA output: snd_seq_drain_output error %d", result);

        snd_seq_sync_output_queue(seq);

        queueRunning = 1;
    }
}

void ALSADrv_MIDI_QueueStop(void)
{
    int result;

    if (queueRunning)
    {
        result = snd_seq_stop_queue(seq, seq_queue, NULL);

        if (result < 0)
            LOG_F(ERROR, "Unable to stop ALSA queue: snd_seq_stop_queue error %d", result);

        do { } while ((result = snd_seq_drop_output(seq)) > 0);

        if (result < 0)
            LOG_F(ERROR, "Unable to drop ALSA output: snd_seq_drop_output error %d", result);

        snd_seq_sync_output_queue(seq);

        queueRunning = 0;
    }
}

std::vector<alsa_mididevinfo_t> const ALSADrv_MIDI_ListPorts(void)
{
    int result;
    bool requiredInit;
    std::vector<alsa_mididevinfo_t> devices;

    if (seq == 0)
    {
        requiredInit = true;

        result = snd_seq_open(&seq, "default", SND_SEQ_OPEN_OUTPUT, 0);
        if (result < 0)
        {
            LOG_F(ERROR, "Unable to initialize ALSA: snd_seq_open error %d", result);
            return devices;
        }
    }
    else
        requiredInit = false;

    snd_seq_client_info_t *cinfo;
    snd_seq_port_info_t *pinfo;

    snd_seq_client_info_alloca(&cinfo);
    snd_seq_port_info_alloca(&pinfo);

    snd_seq_client_info_set_client(cinfo, -1);

    while (snd_seq_query_next_client(seq, cinfo) >= 0)
    {
        // Ignore 'ALSA oddities' that we don't want to use.
        int const client = snd_seq_client_info_get_client(cinfo);

        if (client < 16)
            continue;

        snd_seq_port_info_set_client(pinfo, client);
        snd_seq_port_info_set_port(pinfo, -1);

        while (snd_seq_query_next_port(seq, pinfo) >= 0)
        {
            /* port must understand MIDI messages */
            if (!(snd_seq_port_info_get_type(pinfo) & SND_SEQ_PORT_TYPE_MIDI_GENERIC))
                continue;

            /* we need both WRITE and SUBS_WRITE */
            if ((snd_seq_port_info_get_capability(pinfo) & (SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE))
                != (SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE))
                continue;

            devices.push_back(alsa_mididevinfo_t(
                                     snd_seq_port_info_get_name(pinfo),
                                     snd_seq_port_info_get_client(pinfo),
                                     snd_seq_port_info_get_port(pinfo)));
        }
    }

    if (requiredInit)
    {
        if (seq)
            snd_seq_close(seq);

        seq = 0;
    }

    return devices;
}


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

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

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

 See the GNU General Public License for more details.

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

 */

#include "music.h"
#include "midifuncs.h"

int ALSADrv_GetError(void);
const char *ALSADrv_ErrorString( int ErrorNumber );

int  ALSADrv_MIDI_Init(midifuncs *);
void ALSADrv_MIDI_Shutdown(void);
int  ALSADrv_MIDI_StartPlayback(void);
void ALSADrv_MIDI_HaltPlayback(void);
unsigned int ALSADrv_MIDI_GetTick(void);
void ALSADrv_MIDI_SetTempo(int tempo, int division);
void ALSADrv_MIDI_Lock(void);
void ALSADrv_MIDI_Unlock(void);
void ALSADrv_MIDI_Service(void);
void ALSADrv_MIDI_QueueStart(void);
void ALSADrv_MIDI_QueueStop(void);


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

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

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

 See the GNU General Public License for more details.

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

 */

/**
 * DirectSound output driver for MultiVoc
 */

#define NEED_MMSYSTEM_H
#define NEED_DSOUND_H

#include "driver_directsound.h"

#include "baselayer.h"
#include "multivoc.h"
#include "mutex.h"
#include "windows_inc.h"
#include "winbits.h"

#define MIXBUFFERPOSITIONS 8

static int ErrorCode;
static int Initialised;
static int Playing;

static char *  MixBuffer;
static int MixBufferSize;
static int MixBufferCount;
static int MixBufferCurrent;
static int MixBufferUsed;

static void (*MixCallBack)(void);

static LPDIRECTSOUND lpds;
static LPDIRECTSOUNDBUFFER lpdsbprimary, lpdsbsec;
static LPDIRECTSOUNDNOTIFY lpdsnotify;

static HANDLE mixThread;
static mutex_t mutex;

static DSBPOSITIONNOTIFY notifyPositions[MIXBUFFERPOSITIONS + 1] = {};

static void FillBufferPosition(char * ptr, int remaining)
{
    int len = 0;

    do
    {
        if (MixBufferUsed == MixBufferSize)
        {
            MixCallBack();
            MixBufferUsed = 0;

            if (++MixBufferCurrent >= MixBufferCount)
                MixBufferCurrent -= MixBufferCount;
        }

        do
        {
            char *sptr = MixBuffer + (MixBufferCurrent * MixBufferSize) + MixBufferUsed;

            len = MixBufferSize - MixBufferUsed;

            if (remaining < len)
                len = remaining;

            memcpy(ptr, sptr, len);

            ptr += len;
            MixBufferUsed += len;
            remaining -= len;
        }
        while (remaining >= len && MixBufferUsed < MixBufferSize);
    }
    while (remaining >= len);
}

static void FillBuffer(int bufnum)
{
    LPVOID ptr, ptr2;
    DWORD remaining, remaining2;
    int retries = 1;

    do
    {
        HRESULT err = IDirectSoundBuffer_Lock(lpdsbsec, notifyPositions[bufnum].dwOffset, notifyPositions[1].dwOffset,
                                              &ptr, &remaining, &ptr2, &remaining2, 0);

        if (EDUKE32_PREDICT_FALSE(FAILED(err)))
        {
            if (err == DSERR_BUFFERLOST)
            {
                if (FAILED(err = IDirectSoundBuffer_Restore(lpdsbsec)))
                    goto fail;

                if (retries-- > 0)
                    continue;
            }
fail:
            LOG_F(ERROR, "Unable to lock DirectSound buffer: IDirectSoundBuffer_Lock error %x", (uint32_t)err);

            return;
        }
        break;
    }
    while (1);

    if (ptr && remaining)
        FillBufferPosition((char *)ptr, remaining);

    if (ptr2 && remaining2)
        FillBufferPosition((char *)ptr2, remaining2);

    IDirectSoundBuffer_Unlock(lpdsbsec, ptr, remaining, ptr2, remaining2);
}

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

    debugThreadName("DSound_fillData");

    HANDLE handles[MIXBUFFERPOSITIONS+1];

    for (int i = 0; i < ARRAY_SSIZE(handles); i++)
        handles[i] = notifyPositions[i].hEventNotify;

    do
    {
        DWORD const waitret = WaitForMultipleObjects(MIXBUFFERPOSITIONS, handles, FALSE, INFINITE);

        if (waitret >= WAIT_OBJECT_0 && waitret < WAIT_OBJECT_0+MIXBUFFERPOSITIONS)
        {
            mutex_lock(&mutex);
            FillBuffer((waitret + MIXBUFFERPOSITIONS - 1 - WAIT_OBJECT_0) % MIXBUFFERPOSITIONS);
            mutex_unlock(&mutex);
            SwitchToThread(); // this signals the OS to context switch
        }
        else
        {
            switch (waitret)
            {
                case WAIT_OBJECT_0 + MIXBUFFERPOSITIONS:
                    return 0;
                case WAIT_FAILED:
                    {
                        auto err = GetLastError();
                        LOG_F(ERROR, "Unable to fill DirectSound buffer: WaitForMultipleObjects failed: %s", windowsGetErrorMessage(err));
                    }
                    break;
                default:
                    LOG_F(ERROR, "Unable to fill DirectSound buffer: WaitForMultipleObjects returned %d", (int)waitret);
                    break;
            }
        }
    }
    while (1);

    return 0;
}

static void TeardownDSound(HRESULT err)
{
    if (FAILED(err))
        LOG_F(ERROR, "DirectSound error: %x", (uint32_t)err);

    if (lpdsnotify)
        IDirectSoundNotify_Release(lpdsnotify), lpdsnotify = nullptr;

    for (int i = 0; i < MIXBUFFERPOSITIONS + 1; i++)
    {
        if (notifyPositions[i].hEventNotify)
            CloseHandle(notifyPositions[i].hEventNotify);
        notifyPositions[i].hEventNotify = 0;
    }

#ifdef RENDERTYPEWIN
    mutex_destroy(&mutex);
#endif

    if (lpdsbsec)
        IDirectSoundBuffer_Release(lpdsbsec), lpdsbsec = nullptr;

    if (lpdsbprimary)
        IDirectSoundBuffer_Release(lpdsbprimary), lpdsbprimary = nullptr;

    if (lpds)
        IDirectSound_Release(lpds), lpds = nullptr;
}

static int DirectSound_Error(HRESULT err, int code)
{
    TeardownDSound(err);
    ErrorCode = code;
    return DSErr_Error;
}

int DirectSoundDrv_PCM_Init(int *mixrate, int *numchannels, void * initdata)
{
    HRESULT err;
    DSBUFFERDESC bufdesc = {};
    WAVEFORMATEX wfex    = {};

    if (Initialised)
        DirectSoundDrv_PCM_Shutdown();

    if (FAILED(err = DirectSoundCreate(0, &lpds, 0)))
        return DirectSound_Error(err, DSErr_DirectSoundCreate);

    if (FAILED(err = IDirectSound_SetCooperativeLevel(lpds, (HWND) initdata, DSSCL_PRIORITY)))
        return DirectSound_Error(err, DSErr_SetCooperativeLevel);

    bufdesc.dwSize = sizeof(DSBUFFERDESC);
    bufdesc.dwFlags = DSBCAPS_LOCSOFTWARE | DSBCAPS_PRIMARYBUFFER | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_STICKYFOCUS;

    if (FAILED(err = IDirectSound_CreateSoundBuffer(lpds, &bufdesc, &lpdsbprimary, 0)))
        return DirectSound_Error(err, DSErr_CreateSoundBuffer);

    wfex.wFormatTag      = WAVE_FORMAT_PCM;
    wfex.nChannels       = *numchannels;
    wfex.nSamplesPerSec  = *mixrate;
    wfex.wBitsPerSample  = 16;
    wfex.nBlockAlign     = wfex.nChannels * wfex.wBitsPerSample / 8;
    wfex.nAvgBytesPerSec = wfex.nSamplesPerSec * wfex.nBlockAlign;

    if (FAILED(err = IDirectSoundBuffer_SetFormat(lpdsbprimary, &wfex)))
        return DirectSound_Error(err, DSErr_SetFormat);

    bufdesc.dwFlags = DSBCAPS_LOCSOFTWARE | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_STICKYFOCUS;

    bufdesc.dwBufferBytes = wfex.nBlockAlign * 2048 * 2;
    bufdesc.lpwfxFormat = &wfex;

    if (FAILED(err = IDirectSound_CreateSoundBuffer(lpds, &bufdesc, &lpdsbsec, 0)))
        return DirectSound_Error(err, DSErr_CreateSoundBufferSecondary);

    if (FAILED(err = IDirectSoundBuffer_QueryInterface(lpdsbsec, &IID_IDirectSoundNotify, (LPVOID *)&lpdsnotify)))
        return DirectSound_Error(err, DSErr_Notify);

    for (int i = 0; i < MIXBUFFERPOSITIONS; i++)
    {
        notifyPositions[i].dwOffset = (bufdesc.dwBufferBytes/MIXBUFFERPOSITIONS)*i;
        notifyPositions[i].hEventNotify = CreateEvent(nullptr, FALSE, FALSE, nullptr);
        if (!notifyPositions[i].hEventNotify)
            return DirectSound_Error(DS_OK, DSErr_NotifyEvents);
    }

    notifyPositions[MIXBUFFERPOSITIONS].dwOffset = DSBPN_OFFSETSTOP;
    notifyPositions[MIXBUFFERPOSITIONS].hEventNotify = CreateEvent(nullptr, FALSE, FALSE, nullptr);

    if (FAILED(err = IDirectSoundNotify_SetNotificationPositions(lpdsnotify, MIXBUFFERPOSITIONS+1, notifyPositions)))
        return DirectSound_Error(err, DSErr_SetNotificationPositions);

    if (FAILED(err = IDirectSoundBuffer_Play(lpdsbprimary, 0, 0, DSBPLAY_LOOPING)))
        return DirectSound_Error(err, DSErr_Play);

    mutex_init(&mutex);

    Initialised = 1;

    return DSErr_Ok;
}

void DirectSoundDrv_PCM_Shutdown(void)
{
    if (!Initialised)
        return;

    DirectSoundDrv_PCM_StopPlayback();
    TeardownDSound(DS_OK);

    Initialised = 0;
}

int DirectSoundDrv_PCM_BeginPlayback(char *BufferStart, int BufferSize, int NumDivisions, void (*CallBackFunc)(void))
{
    if (!Initialised)
    {
        ErrorCode = DSErr_Uninitialised;
        return DSErr_Error;
    }

    DirectSoundDrv_PCM_StopPlayback();

    MixBuffer        = BufferStart;
    MixBufferSize    = BufferSize;
    MixBufferCount   = NumDivisions;
    MixBufferCurrent = 0;
    MixBufferUsed    = 0;
    MixCallBack      = CallBackFunc;

    // prime the buffer
    FillBuffer(0);

    if ((mixThread = (HANDLE)_beginthreadex(nullptr, 0, fillDataThread, 0, 0, 0)) == nullptr)
    {
        ErrorCode = DSErr_CreateThread;
        return DSErr_Error;
    }

    SetThreadPriority(mixThread, THREAD_PRIORITY_ABOVE_NORMAL);

    HRESULT err = IDirectSoundBuffer_Play(lpdsbsec, 0, 0, DSBPLAY_LOOPING);

    if (FAILED(err))
    {
        ErrorCode = DSErr_PlaySecondary;
        return DSErr_Error;
    }

    Playing = 1;

    return DSErr_Ok;
}

void DirectSoundDrv_PCM_StopPlayback(void)
{
    if (!Playing)
        return;

    IDirectSoundBuffer_Stop(lpdsbsec);
    IDirectSoundBuffer_SetCurrentPosition(lpdsbsec, 0);

    if (mixThread)
        CloseHandle(mixThread);

    mixThread = 0;
    Playing = 0;
}

void DirectSoundDrv_PCM_Lock(void)   { mutex_lock(&mutex); }
void DirectSoundDrv_PCM_Unlock(void) { mutex_unlock(&mutex); }
int DirectSoundDrv_GetError(void)    { return ErrorCode; }

const char *DirectSoundDrv_ErrorString(int ErrorNumber)
{
    switch (ErrorNumber)
    {
        case DSErr_Error:                      return DirectSoundDrv_ErrorString(ErrorCode);
        case DSErr_Ok:                         return "DirectSound ok.";
        case DSErr_Uninitialised:              return "DirectSound uninitialized.";
        case DSErr_DirectSoundCreate:          return "DirectSound error: DirectSoundCreate failed.";
        case DSErr_SetCooperativeLevel:        return "DirectSound error: SetCooperativeLevel failed.";
        case DSErr_CreateSoundBuffer:          return "DirectSound error: primary CreateSoundBuffer failed.";
        case DSErr_CreateSoundBufferSecondary: return "DirectSound error: secondary CreateSoundBuffer failed.";
        case DSErr_SetFormat:                  return "DirectSound error: primary buffer SetFormat failed.";
        case DSErr_SetFormatSecondary:         return "DirectSound error: secondary buffer SetFormat failed.";
        case DSErr_Notify:                     return "DirectSound error: failed querying secondary buffer for notify interface.";
        case DSErr_NotifyEvents:               return "DirectSound error: failed creating notify events.";
        case DSErr_SetNotificationPositions:   return "DirectSound error: failed setting notification positions.";
        case DSErr_Play:                       return "DirectSound error: primary buffer Play failed.";
        case DSErr_PlaySecondary:              return "DirectSound error: secondary buffer Play failed.";
        case DSErr_CreateThread:               return "DirectSound error: failed creating mix thread.";
        default:                               return "Unknown DirectSound error code.";
    }
}


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 <inttypes.h>

enum
{
    DSErr_Error   = -1,
    DSErr_Ok      = 0,
    DSErr_Uninitialised,
    DSErr_DirectSoundCreate,
    DSErr_SetCooperativeLevel,
    DSErr_CreateSoundBuffer,
    DSErr_CreateSoundBufferSecondary,
    DSErr_SetFormat,
    DSErr_SetFormatSecondary,
    DSErr_Notify,
    DSErr_NotifyEvents,
    DSErr_SetNotificationPositions,
    DSErr_Play,
    DSErr_PlaySecondary,
    DSErr_CreateThread,
};

int         DirectSoundDrv_GetError(void);
const char *DirectSoundDrv_ErrorString(int ErrorNumber);

int  DirectSoundDrv_PCM_Init(int *mixrate, int *numchannels, void *initdata);
void DirectSoundDrv_PCM_Shutdown(void);
int  DirectSoundDrv_PCM_BeginPlayback(char *BufferStart, int BufferSize, int NumDivisions, void (*CallBackFunc)(void));
void DirectSoundDrv_PCM_StopPlayback(void);
void DirectSoundDrv_PCM_Lock(void);
void DirectSoundDrv_PCM_Unlock(void);


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

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

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

 See the GNU General Public License for more details.

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

 */

/**
 * libSDL output driver for MultiVoc
 */

#include "driver_sdl.h"

#include "compat.h"
#include "multivoc.h"
#include "mutex.h"
#include "sdl_inc.h"
#include "vfs.h"

#ifdef __ANDROID__
#include "duke3d.h"
#include "android.h"
#endif

enum
{
    SDLErr_Error   = -1,
    SDLErr_Ok      = 0,
    SDLErr_Uninitialised,
    SDLErr_InitSubSystem,
    SDLErr_OpenAudio
};

static int ErrorCode = SDLErr_Ok;
static int Initialised;
static int Playing;
static uint32_t StartedSDL;

static char *MixBuffer;
static int MixBufferSize;
static int MixBufferCount;
static int MixBufferCurrent;
static int MixBufferUsed;
static void (*MixCallBack)(void);

#if (SDL_MAJOR_VERSION >= 2)
static SDL_AudioDeviceID audio_dev;
#endif

#if SDL_MAJOR_VERSION >= 2
char SDLAudioDriverName[16];
char *SDLAudioDriverNameEnv;
#endif

static void fillData(void * userdata, Uint8 * ptr, int remaining)
{
    UNREFERENCED_PARAMETER(userdata);

    if (!MixBuffer || !MixCallBack)
      return;

    while (remaining > 0)
    {
        if (MixBufferUsed == MixBufferSize)
        {
            MixCallBack();
            MixBufferUsed = 0;

            if (++MixBufferCurrent >= MixBufferCount)
                MixBufferCurrent -= MixBufferCount;
        }

        while (remaining > 0 && MixBufferUsed < MixBufferSize)
        {
            auto sptr = MixBuffer + (MixBufferCurrent * MixBufferSize) + MixBufferUsed;
            int  len  = MixBufferSize - MixBufferUsed;

            if (remaining < len)
                len = remaining;

            memcpy(ptr, sptr, len);

            ptr += len;
            MixBufferUsed += len;
            remaining -= len;
        }
    }
}

int SDLDrv_GetError(void) { return ErrorCode; }

const char *SDLDrv_ErrorString(int ErrorNumber)
{
    switch (ErrorNumber)
    {
        case SDLErr_Error:         return SDLDrv_ErrorString(ErrorCode);
        case SDLErr_Ok:            return "SDL Audio ok.";
        case SDLErr_Uninitialised: return "SDL Audio uninitialized.";
        case SDLErr_InitSubSystem: return "SDL Audio: error in Init or InitSubSystem.";
        case SDLErr_OpenAudio:     return "SDL Audio: error in OpenAudio.";
        default:                   return "Unknown SDL Audio error code.";
    }
}

#if SDL_MAJOR_VERSION >= 2
void SDLDrv_PCM_PrintDrivers(void)
{
    LOG_F(INFO, "Available audio drivers:");

    for (int i = 0; i < SDL_GetNumAudioDrivers(); ++i)
        LOG_F(INFO, "%s", SDL_GetAudioDriver(i));
}

int SDLDrv_PCM_CheckDriverName(char const *dev)
{
    for (int i = 0; i < SDL_GetNumAudioDrivers(); ++i)
        if (!Bstrcasecmp(dev, SDL_GetAudioDriver(i)))
            return true;

    return false;
}

char const *SDLDrv_PCM_GetDriverName(void) { return SDL_GetCurrentAudioDriver(); }
static void SDLDrv_Cleanup(void) { DO_FREE_AND_NULL(SDLAudioDriverNameEnv); }
#endif

int SDLDrv_PCM_Init(int *mixrate, int *numchannels, void * initdata)
{
    UNREFERENCED_PARAMETER(initdata);

    Uint32 inited;
    int err = 0;

    if (Initialised)
        SDLDrv_PCM_Shutdown();
#if SDL_MAJOR_VERSION >= 2
    else if (SDLAudioDriverNameEnv == nullptr)
    {
        static int done;

        if (!done)
        {
            if (auto s = SDL_getenv("SDL_AUDIODRIVER"))
                SDLAudioDriverNameEnv = Xstrdup(s);
        }
    }

    if (SDLAudioDriverName[0])
        SDL_setenv("SDL_AUDIODRIVER", SDLAudioDriverName, true);
#endif

    inited = SDL_WasInit(SDL_INIT_AUDIO);

    if (!(inited & SDL_INIT_AUDIO))
    {
        err        = SDL_InitSubSystem(SDL_INIT_AUDIO);
        StartedSDL = SDL_WasInit(SDL_INIT_AUDIO);
    }

    if (err < 0)
    {
        ErrorCode = SDLErr_InitSubSystem;
        return SDLErr_Error;
    }

    int chunksize = 512;
#ifdef __ANDROID__
    chunksize = droidinfo.audio_buffer_size;
#endif

    SDL_AudioSpec spec = {};

    spec.freq = *mixrate;
    spec.format = AUDIO_S16SYS;
    spec.channels = *numchannels;
    spec.samples = chunksize;
    spec.callback = fillData;
    spec.userdata = nullptr;

    SDL_AudioSpec actual = {};

#if (SDL_MAJOR_VERSION >= 2)
    audio_dev = err = SDL_OpenAudioDevice(nullptr, 0, &spec, &actual, 0);
#else
    err = !SDL_OpenAudio(&spec, &actual);
#endif

#if SDL_MAJOR_VERSION >= 2
    // restore original value
    if (SDLAudioDriverNameEnv)
        SDL_setenv("SDL_AUDIODRIVER", SDLAudioDriverNameEnv, true);
#endif

    if (err == 0)
    {
        ErrorCode = SDLErr_OpenAudio;
        return SDLErr_Error;
    }

#if (SDL_MAJOR_VERSION >= 2)
    char *drivername = Xstrdup(SDL_GetCurrentAudioDriver());

    for (int i=0;drivername[i] != 0;++i)
        drivername[i] = toupperlookup[drivername[i]];

    auto devname = Xstrdup(SDL_GetAudioDeviceName(0, 0));
    auto pdevname = Bstrchr(devname, '(');

    if (pdevname)
    {
        auto rt = Bstrchr(pdevname++, ')');
        if (rt != nullptr) *rt = '\0';
    }
    else
        pdevname = devname;

    VLOG_F(LOG_ASS, "Using SDL %s driver on %s", drivername, pdevname);

    Xfree(devname);
    Xfree(drivername);
#else
    char drivernamestr[64] = "(error)";
    SDL_AudioDriverName(drivernamestr, sizeof(drivernamestr));
    VLOG_F(LOG_ASS, "Using SDL %s driver", drivernamestr);
    VLOG_F(LOG_ASS, "SDL %s driver", drivernamestr);

    if (actual.freq == 0 || actual.channels == 0)
    {
        // hack for when SDL said it opened the audio, but clearly didn't
        SDL_CloseAudio();
        ErrorCode = SDLErr_OpenAudio;
        return SDLErr_Error;
    }
#endif
    err = 0;

    *mixrate = actual.freq;
    if (actual.format == AUDIO_U8 || actual.format == AUDIO_S8)
    {
        ErrorCode = SDLErr_OpenAudio;
        err = 1;
    }

    *numchannels = actual.channels;
    if (actual.channels != 1 && actual.channels != 2)
    {
        ErrorCode = SDLErr_OpenAudio;
        err = 1;
    }

    if (err)
    {
        SDL_CloseAudio();
        return SDLErr_Error;
    }

    Initialised = 1;
    return SDLErr_Ok;
}

void SDLDrv_PCM_Shutdown(void)
{
    if (!Initialised)
        return;

    if (StartedSDL)
        SDL_QuitSubSystem(StartedSDL);

    StartedSDL = 0;
    Initialised = 0;
#if SDL_MAJOR_VERSION >= 2
    SDLDrv_Cleanup();
#endif
}

int SDLDrv_PCM_BeginPlayback(char *BufferStart, int BufferSize,
                        int NumDivisions, void ( *CallBackFunc )( void ) )
{
    if (!Initialised)
    {
        ErrorCode = SDLErr_Uninitialised;
        return SDLErr_Error;
    }

    if (Playing)
        SDLDrv_PCM_StopPlayback();

    MixBuffer = BufferStart;
    MixBufferSize = BufferSize;
    MixBufferCount = NumDivisions;
    MixBufferCurrent = 0;
    MixBufferUsed = 0;
    MixCallBack = CallBackFunc;

    // prime the buffer
    MixCallBack();

#if (SDL_MAJOR_VERSION >= 2)
    SDL_PauseAudioDevice(audio_dev, 0);
#else
    SDL_PauseAudio(0);
#endif
    Playing = 1;

    return SDLErr_Ok;
}

void SDLDrv_PCM_StopPlayback(void)
{
    if (!Initialised || !Playing)
        return;

#if (SDL_MAJOR_VERSION >= 2)
    SDL_PauseAudioDevice(audio_dev, 1);
#else
    SDL_PauseAudio(1);
#endif

    Playing = 0;
}

void SDLDrv_PCM_Lock(void)
{
#if (SDL_MAJOR_VERSION >= 2)
    SDL_LockAudioDevice(audio_dev);
#else
    SDL_LockAudio();
#endif
}

void SDLDrv_PCM_Unlock(void)
{
#if (SDL_MAJOR_VERSION >= 2)
    SDL_UnlockAudioDevice(audio_dev);
#else
    SDL_UnlockAudio();
#endif
}


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

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

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

 See the GNU General Public License for more details.

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

 */

#ifndef driver_sdl_h__
#define driver_sdl_h__

extern char SDLAudioDriverName[16];
const char *SDLDrv_ErrorString(int ErrorNumber);

int  SDLDrv_GetError(void);
int  SDLDrv_PCM_Init(int *mixrate, int *numchannels, void *initdata);
void SDLDrv_PCM_Shutdown(void);
int  SDLDrv_PCM_BeginPlayback(char *BufferStart, int BufferSize, int NumDivisions, void (*CallBackFunc)(void));
void SDLDrv_PCM_StopPlayback(void);
void SDLDrv_PCM_Lock(void);
void SDLDrv_PCM_Unlock(void);

void SDLDrv_PCM_PrintDrivers(void);
int  SDLDrv_PCM_CheckDriverName(char const *dev);
char const *SDLDrv_PCM_GetDriverName(void);

#endif // driver_sdl_h__


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2020 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 "driver_sf2.h"

#include "_multivc.h"
#include "common.h"
#include "midi.h"

int SF2_EffectSampleBlockSize = 16;

#define TSF_IMPLEMENTATION
#define TSF_NO_STDIO
#define TSF_RENDER_EFFECTSAMPLEBLOCK SF2_EffectSampleBlockSize
#define TSF_MALLOC  Xmalloc
#define TSF_REALLOC Xrealloc
#define TSF_FREE    Xfree
#define TSF_MEMCPY Bmemcpy
#define TSF_MEMSET Bmemset

#include "tsf.h"

static tsf *sf2_synth;
char        SF2_BankFile[BMAX_PATH];
static int  SF2_Volume = MIDI_MaxVolume;
static int  ErrorCode  = SF2_Ok;

static inline int SF2_SetError(int const status) { return (ErrorCode = status); }

int SF2Drv_GetError(void) { return ErrorCode; }

static void SF2_NoteOff(int channel, int key, int velocity)
{
    UNREFERENCED_PARAMETER(velocity);
    tsf_channel_note_off(sf2_synth, channel, key);
}

static void SF2_NoteOn(int channel, int key, int velocity)        { tsf_channel_note_on(sf2_synth, channel, key, velocity * (1.f / 127.f)); }
static void SF2_ControlChange(int channel, int number, int value) { tsf_channel_midi_control(sf2_synth, channel, number, value); }
static void SF2_ProgramChange(int channel, int program)           { tsf_channel_set_presetnumber(sf2_synth, channel, program, channel == 9); }
static void SF2_SetPitchBend(int channel, int lsb, int msb)       { tsf_channel_set_pitchwheel(sf2_synth, channel, (msb << 7) | lsb); }
static void SF2_SetVolume(int volume)                             { SF2_Volume = clamp(volume, 0, MIDI_MaxVolume); }

const char *SF2Drv_ErrorString(int ErrorNumber)
{
    switch (ErrorNumber)
    {
        case SF2_Error:     return SF2Drv_ErrorString(ErrorCode);
        case SF2_Ok:        return "SF2 ok.";
        case SF2_BankError: return "SF2 bank error.";
        default:            return "Unknown SF2 error.";
    }
}

static int sf2_stream_read(void *handle, void *ptr, unsigned int size) { return kread(*(buildvfs_kfd *)handle, ptr, size); };
static int sf2_stream_skip(void *handle, unsigned int size)            { return !klseek(*(buildvfs_kfd *)handle, size, SEEK_CUR); };

static int SF2_LoadBank(char const *const filename)
{
    buildvfs_kfd sf2_kfd    = kopen4loadfrommod(filename, 0);
    tsf_stream   sf2_stream = { &sf2_kfd, &sf2_stream_read, &sf2_stream_skip };

    if (sf2_kfd != buildvfs_kfd_invalid)
    {
        tsf_close(sf2_synth);
        sf2_synth = tsf_load(&sf2_stream);
        kclose(sf2_kfd);

        if (sf2_synth)
        {
            VLOG_F(LOG_ASS, "Loaded \"%s\"", filename);
            return SF2_Ok;
        }
    }

    LOG_F(ERROR, "Unable to load \"%s\"!", filename);
    return SF2_Error;
}

int SF2Drv_MIDI_Init(midifuncs* const funcs)
{
    SF2Drv_MIDI_Shutdown();

    auto filename = SF2_BankFile;

    if (!filename[0])
    {
        fnlist_t fnl = FNLIST_INITIALIZER;

        // default to the first .sf2 we find if cvar mus_sf2_bank is unset
        fnlist_getnames(&fnl, g_modDir, "*.sf2", 0, 0);

        if (!fnl.findfiles)
            fnlist_getnames(&fnl, "/", "*.sf2", 0, 0);

        if (fnl.findfiles)
            filename = Xstrdup(fnl.findfiles->name);

        fnlist_clearnames(&fnl);

        if (!filename[0])
        {
            LOG_F(WARNING, "No .sf2 data found!");
            return SF2_SetError(SF2_BankError);
        }
    }

    int const loaded = SF2_LoadBank(filename);

    if (filename != SF2_BankFile)
        Xfree(filename);

    if (loaded != SF2_Ok || !sf2_synth)
        return SF2_SetError(SF2_BankError);

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

    funcs->NoteOff           = SF2_NoteOff;
    funcs->NoteOn            = SF2_NoteOn;
    funcs->PolyAftertouch    = nullptr;
    funcs->ControlChange     = SF2_ControlChange;
    funcs->ProgramChange     = SF2_ProgramChange;
    funcs->ChannelAftertouch = nullptr;
    funcs->PitchBend         = SF2_SetPitchBend;
    funcs->SetVolume         = SF2_SetVolume;

    SF2_Volume = MIDI_MaxVolume;

    return SF2_Ok;
}

void SF2Drv_MIDI_HaltPlayback(void) { MV_UnhookMusicRoutine(); }

void SF2Drv_MIDI_Shutdown(void)
{
    SF2Drv_MIDI_HaltPlayback();

    tsf_close(sf2_synth);
    sf2_synth = nullptr;
    ErrorCode = SF2_Ok;
}

int SF2Drv_MIDI_StartPlayback(void)
{
    SF2Drv_MIDI_HaltPlayback();

    tsf_set_output(sf2_synth, MV_Channels == 1 ? TSF_MONO : TSF_STEREO_INTERLEAVED, MV_MixRate, 0);
    tsf_channel_set_bank_preset(sf2_synth, 9, 128, 0);
    tsf_reset(sf2_synth);

    for (int channel = 0; channel < 16; channel++)
        SF2_ProgramChange(channel, 0);

    MV_HookMusicRoutine(SF2Drv_MIDI_Service);

    return MIDI_Ok;
}

void SF2Drv_MIDI_SetTempo(int const tempo, int const division)
{
    MV_MIDIRenderTempo = tempo * division / 60;
    MV_MIDIRenderTimer = 0;
}

void SF2Drv_MIDI_Service(void)
{
    int16_t *    buffer16 = (int16_t *)MV_MusicBuffer;
    static float fbuf[MV_MIXBUFFERSIZE * 2];
    float const  fvolume = SF2_Volume * (32768.f / MIDI_MaxVolume);

    for (int i = 0; i < MV_MIXBUFFERSIZE;)
    {
        while (MV_MIDIRenderTimer >= MV_MixRate)
        {
            if (MV_MIDIRenderTempo >= 0)
                MIDI_ServiceRoutine();
            MV_MIDIRenderTimer -= MV_MixRate;
        }

        int samples = MV_MIDIRenderTempo > 0 ? (MV_MixRate - MV_MIDIRenderTimer + MV_MIDIRenderTempo - 1) / MV_MIDIRenderTempo : MV_MIXBUFFERSIZE;
        samples     = min(samples, MV_MIXBUFFERSIZE - i);
        tsf_render_float(sf2_synth, fbuf, samples);

        int const nsamples = samples * MV_Channels;

        for (int j = 0; j < nsamples; j++)
            *buffer16++ = clamp((fbuf[j] * fvolume), INT16_MIN, INT16_MAX);

        if (MV_MIDIRenderTempo >= 0)
            MV_MIDIRenderTimer += MV_MIDIRenderTempo * samples;

        i += samples;
    }
}