//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005-2014 Simon Howard
//
// 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.
//
// DESCRIPTION:
//   System interface for music.
//


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

#include "memio.h"
#include "mus2mid.h"

#include "deh_main.h"
#include "i_sound.h"
#include "i_swap.h"
#include "m_misc.h"
#include "w_wad.h"
#include "z_zone.h"

#include "opl.h"
#include "midifile.h"

// #define OPL_MIDI_DEBUG

#define MAXMIDLENGTH (96 * 1024)
#define GENMIDI_NUM_INSTRS  128
#define GENMIDI_NUM_PERCUSSION 47

#define GENMIDI_HEADER          "#OPL_II#"
#define GENMIDI_FLAG_FIXED      0x0001         /* fixed pitch */
#define GENMIDI_FLAG_2VOICE     0x0004         /* double voice (OPL3) */

#define PERCUSSION_LOG_LEN 16

typedef PACKED_STRUCT (
{
    byte tremolo;
    byte attack;
    byte sustain;
    byte waveform;
    byte scale;
    byte level;
}) genmidi_op_t;

typedef PACKED_STRUCT (
{
    genmidi_op_t modulator;
    byte feedback;
    genmidi_op_t carrier;
    byte unused;
    short base_note_offset;
}) genmidi_voice_t;

typedef PACKED_STRUCT (
{
    unsigned short flags;
    byte fine_tuning;
    byte fixed_note;

    genmidi_voice_t voices[2];
}) genmidi_instr_t;

// Data associated with a channel of a track that is currently playing.

typedef struct
{
    // The instrument currently used for this track.

    genmidi_instr_t *instrument;

    // Volume level

    int volume;
    int volume_base;

    // Pan

    int pan;

    // Pitch bend value:

    int bend;

} opl_channel_data_t;

// Data associated with a track that is currently playing.

typedef struct
{
    // Track iterator used to read new events.

    midi_track_iter_t *iter;
} opl_track_data_t;

typedef struct opl_voice_s opl_voice_t;

struct opl_voice_s
{
    // Index of this voice:
    int index;

    // The operators used by this voice:
    int op1, op2;

    // Array used by voice:
    int array;

    // Currently-loaded instrument data
    genmidi_instr_t *current_instr;

    // The voice number in the instrument to use.
    // This is normally set to zero; if this is a double voice
    // instrument, it may be one.
    unsigned int current_instr_voice;

    // The channel currently using this voice.
    opl_channel_data_t *channel;

    // The midi key that this voice is playing.
    unsigned int key;

    // The note being played.  This is normally the same as
    // the key, but if the instrument is a fixed pitch
    // instrument, it is different.
    unsigned int note;

    // The frequency value being used.
    unsigned int freq;

    // The volume of the note being played on this channel.
    unsigned int note_volume;

    // The current volume (register value) that has been set for this channel.
    unsigned int car_volume;
    unsigned int mod_volume;

    // Pan.
    unsigned int reg_pan;

    // Priority.
    unsigned int priority;
};

// Operators used by the different voices.

static const int voice_operators[2][OPL_NUM_VOICES] = {
    { 0x00, 0x01, 0x02, 0x08, 0x09, 0x0a, 0x10, 0x11, 0x12 },
    { 0x03, 0x04, 0x05, 0x0b, 0x0c, 0x0d, 0x13, 0x14, 0x15 }
};

// Frequency values to use for each note.

static const unsigned short frequency_curve[] = {

    0x133, 0x133, 0x134, 0x134, 0x135, 0x136, 0x136, 0x137,   // -1
    0x137, 0x138, 0x138, 0x139, 0x139, 0x13a, 0x13b, 0x13b,
    0x13c, 0x13c, 0x13d, 0x13d, 0x13e, 0x13f, 0x13f, 0x140,
    0x140, 0x141, 0x142, 0x142, 0x143, 0x143, 0x144, 0x144,

    0x145, 0x146, 0x146, 0x147, 0x147, 0x148, 0x149, 0x149,   // -2
    0x14a, 0x14a, 0x14b, 0x14c, 0x14c, 0x14d, 0x14d, 0x14e,
    0x14f, 0x14f, 0x150, 0x150, 0x151, 0x152, 0x152, 0x153,
    0x153, 0x154, 0x155, 0x155, 0x156, 0x157, 0x157, 0x158,

    // These are used for the first seven MIDI note values:

    0x158, 0x159, 0x15a, 0x15a, 0x15b, 0x15b, 0x15c, 0x15d,   // 0
    0x15d, 0x15e, 0x15f, 0x15f, 0x160, 0x161, 0x161, 0x162,
    0x162, 0x163, 0x164, 0x164, 0x165, 0x166, 0x166, 0x167,
    0x168, 0x168, 0x169, 0x16a, 0x16a, 0x16b, 0x16c, 0x16c,

    0x16d, 0x16e, 0x16e, 0x16f, 0x170, 0x170, 0x171, 0x172,   // 1
    0x172, 0x173, 0x174, 0x174, 0x175, 0x176, 0x176, 0x177,
    0x178, 0x178, 0x179, 0x17a, 0x17a, 0x17b, 0x17c, 0x17c,
    0x17d, 0x17e, 0x17e, 0x17f, 0x180, 0x181, 0x181, 0x182,

    0x183, 0x183, 0x184, 0x185, 0x185, 0x186, 0x187, 0x188,   // 2
    0x188, 0x189, 0x18a, 0x18a, 0x18b, 0x18c, 0x18d, 0x18d,
    0x18e, 0x18f, 0x18f, 0x190, 0x191, 0x192, 0x192, 0x193,
    0x194, 0x194, 0x195, 0x196, 0x197, 0x197, 0x198, 0x199,

    0x19a, 0x19a, 0x19b, 0x19c, 0x19d, 0x19d, 0x19e, 0x19f,   // 3
    0x1a0, 0x1a0, 0x1a1, 0x1a2, 0x1a3, 0x1a3, 0x1a4, 0x1a5,
    0x1a6, 0x1a6, 0x1a7, 0x1a8, 0x1a9, 0x1a9, 0x1aa, 0x1ab,
    0x1ac, 0x1ad, 0x1ad, 0x1ae, 0x1af, 0x1b0, 0x1b0, 0x1b1,

    0x1b2, 0x1b3, 0x1b4, 0x1b4, 0x1b5, 0x1b6, 0x1b7, 0x1b8,   // 4
    0x1b8, 0x1b9, 0x1ba, 0x1bb, 0x1bc, 0x1bc, 0x1bd, 0x1be,
    0x1bf, 0x1c0, 0x1c0, 0x1c1, 0x1c2, 0x1c3, 0x1c4, 0x1c4,
    0x1c5, 0x1c6, 0x1c7, 0x1c8, 0x1c9, 0x1c9, 0x1ca, 0x1cb,

    0x1cc, 0x1cd, 0x1ce, 0x1ce, 0x1cf, 0x1d0, 0x1d1, 0x1d2,   // 5
    0x1d3, 0x1d3, 0x1d4, 0x1d5, 0x1d6, 0x1d7, 0x1d8, 0x1d8,
    0x1d9, 0x1da, 0x1db, 0x1dc, 0x1dd, 0x1de, 0x1de, 0x1df,
    0x1e0, 0x1e1, 0x1e2, 0x1e3, 0x1e4, 0x1e5, 0x1e5, 0x1e6,

    0x1e7, 0x1e8, 0x1e9, 0x1ea, 0x1eb, 0x1ec, 0x1ed, 0x1ed,   // 6
    0x1ee, 0x1ef, 0x1f0, 0x1f1, 0x1f2, 0x1f3, 0x1f4, 0x1f5,
    0x1f6, 0x1f6, 0x1f7, 0x1f8, 0x1f9, 0x1fa, 0x1fb, 0x1fc,
    0x1fd, 0x1fe, 0x1ff, 0x200, 0x201, 0x201, 0x202, 0x203,

    // First note of looped range used for all octaves:

    0x204, 0x205, 0x206, 0x207, 0x208, 0x209, 0x20a, 0x20b,   // 7
    0x20c, 0x20d, 0x20e, 0x20f, 0x210, 0x210, 0x211, 0x212,
    0x213, 0x214, 0x215, 0x216, 0x217, 0x218, 0x219, 0x21a,
    0x21b, 0x21c, 0x21d, 0x21e, 0x21f, 0x220, 0x221, 0x222,

    0x223, 0x224, 0x225, 0x226, 0x227, 0x228, 0x229, 0x22a,   // 8
    0x22b, 0x22c, 0x22d, 0x22e, 0x22f, 0x230, 0x231, 0x232,
    0x233, 0x234, 0x235, 0x236, 0x237, 0x238, 0x239, 0x23a,
    0x23b, 0x23c, 0x23d, 0x23e, 0x23f, 0x240, 0x241, 0x242,

    0x244, 0x245, 0x246, 0x247, 0x248, 0x249, 0x24a, 0x24b,   // 9
    0x24c, 0x24d, 0x24e, 0x24f, 0x250, 0x251, 0x252, 0x253,
    0x254, 0x256, 0x257, 0x258, 0x259, 0x25a, 0x25b, 0x25c,
    0x25d, 0x25e, 0x25f, 0x260, 0x262, 0x263, 0x264, 0x265,

    0x266, 0x267, 0x268, 0x269, 0x26a, 0x26c, 0x26d, 0x26e,   // 10
    0x26f, 0x270, 0x271, 0x272, 0x273, 0x275, 0x276, 0x277,
    0x278, 0x279, 0x27a, 0x27b, 0x27d, 0x27e, 0x27f, 0x280,
    0x281, 0x282, 0x284, 0x285, 0x286, 0x287, 0x288, 0x289,

    0x28b, 0x28c, 0x28d, 0x28e, 0x28f, 0x290, 0x292, 0x293,   // 11
    0x294, 0x295, 0x296, 0x298, 0x299, 0x29a, 0x29b, 0x29c,
    0x29e, 0x29f, 0x2a0, 0x2a1, 0x2a2, 0x2a4, 0x2a5, 0x2a6,
    0x2a7, 0x2a9, 0x2aa, 0x2ab, 0x2ac, 0x2ae, 0x2af, 0x2b0,

    0x2b1, 0x2b2, 0x2b4, 0x2b5, 0x2b6, 0x2b7, 0x2b9, 0x2ba,   // 12
    0x2bb, 0x2bd, 0x2be, 0x2bf, 0x2c0, 0x2c2, 0x2c3, 0x2c4,
    0x2c5, 0x2c7, 0x2c8, 0x2c9, 0x2cb, 0x2cc, 0x2cd, 0x2ce,
    0x2d0, 0x2d1, 0x2d2, 0x2d4, 0x2d5, 0x2d6, 0x2d8, 0x2d9,

    0x2da, 0x2dc, 0x2dd, 0x2de, 0x2e0, 0x2e1, 0x2e2, 0x2e4,   // 13
    0x2e5, 0x2e6, 0x2e8, 0x2e9, 0x2ea, 0x2ec, 0x2ed, 0x2ee,
    0x2f0, 0x2f1, 0x2f2, 0x2f4, 0x2f5, 0x2f6, 0x2f8, 0x2f9,
    0x2fb, 0x2fc, 0x2fd, 0x2ff, 0x300, 0x302, 0x303, 0x304,

    0x306, 0x307, 0x309, 0x30a, 0x30b, 0x30d, 0x30e, 0x310,   // 14
    0x311, 0x312, 0x314, 0x315, 0x317, 0x318, 0x31a, 0x31b,
    0x31c, 0x31e, 0x31f, 0x321, 0x322, 0x324, 0x325, 0x327,
    0x328, 0x329, 0x32b, 0x32c, 0x32e, 0x32f, 0x331, 0x332,

    0x334, 0x335, 0x337, 0x338, 0x33a, 0x33b, 0x33d, 0x33e,   // 15
    0x340, 0x341, 0x343, 0x344, 0x346, 0x347, 0x349, 0x34a,
    0x34c, 0x34d, 0x34f, 0x350, 0x352, 0x353, 0x355, 0x357,
    0x358, 0x35a, 0x35b, 0x35d, 0x35e, 0x360, 0x361, 0x363,

    0x365, 0x366, 0x368, 0x369, 0x36b, 0x36c, 0x36e, 0x370,   // 16
    0x371, 0x373, 0x374, 0x376, 0x378, 0x379, 0x37b, 0x37c,
    0x37e, 0x380, 0x381, 0x383, 0x384, 0x386, 0x388, 0x389,
    0x38b, 0x38d, 0x38e, 0x390, 0x392, 0x393, 0x395, 0x397,

    0x398, 0x39a, 0x39c, 0x39d, 0x39f, 0x3a1, 0x3a2, 0x3a4,   // 17
    0x3a6, 0x3a7, 0x3a9, 0x3ab, 0x3ac, 0x3ae, 0x3b0, 0x3b1,
    0x3b3, 0x3b5, 0x3b7, 0x3b8, 0x3ba, 0x3bc, 0x3bd, 0x3bf,
    0x3c1, 0x3c3, 0x3c4, 0x3c6, 0x3c8, 0x3ca, 0x3cb, 0x3cd,

    // The last note has an incomplete range, and loops round back to
    // the start.  Note that the last value is actually a buffer overrun
    // and does not fit with the other values.

    0x3cf, 0x3d1, 0x3d2, 0x3d4, 0x3d6, 0x3d8, 0x3da, 0x3db,   // 18
    0x3dd, 0x3df, 0x3e1, 0x3e3, 0x3e4, 0x3e6, 0x3e8, 0x3ea,
    0x3ec, 0x3ed, 0x3ef, 0x3f1, 0x3f3, 0x3f5, 0x3f6, 0x3f8,
    0x3fa, 0x3fc, 0x3fe, 0x36c,
};

// Mapping from MIDI volume level to OPL level value.

static const unsigned int volume_mapping_table[] = {
    0, 1, 3, 5, 6, 8, 10, 11,
    13, 14, 16, 17, 19, 20, 22, 23,
    25, 26, 27, 29, 30, 32, 33, 34,
    36, 37, 39, 41, 43, 45, 47, 49,
    50, 52, 54, 55, 57, 59, 60, 61,
    63, 64, 66, 67, 68, 69, 71, 72,
    73, 74, 75, 76, 77, 79, 80, 81,
    82, 83, 84, 84, 85, 86, 87, 88,
    89, 90, 91, 92, 92, 93, 94, 95,
    96, 96, 97, 98, 99, 99, 100, 101,
    101, 102, 103, 103, 104, 105, 105, 106,
    107, 107, 108, 109, 109, 110, 110, 111,
    112, 112, 113, 113, 114, 114, 115, 115,
    116, 117, 117, 118, 118, 119, 119, 120,
    120, 121, 121, 122, 122, 123, 123, 123,
    124, 124, 125, 125, 126, 126, 127, 127
};

static opl_driver_ver_t opl_drv_ver = opl_doom_1_9;
static boolean music_initialized = false;

//static boolean musicpaused = false;
static int start_music_volume;
static int current_music_volume;

// GENMIDI lump instrument data:

static genmidi_instr_t *main_instrs;
static genmidi_instr_t *percussion_instrs;
static char (*main_instr_names)[32];
static char (*percussion_names)[32];

// Voices:

static opl_voice_t voices[OPL_NUM_VOICES * 2];
static opl_voice_t *voice_free_list[OPL_NUM_VOICES * 2];
static opl_voice_t *voice_alloced_list[OPL_NUM_VOICES * 2];
static int voice_free_num;
static int voice_alloced_num;
static int opl_opl3mode;
static int num_opl_voices;

// Data for each channel.

static opl_channel_data_t channels[MIDI_CHANNELS_PER_TRACK];

// Track data for playing tracks:

static opl_track_data_t *tracks;
static unsigned int num_tracks = 0;
static unsigned int running_tracks = 0;
static boolean song_looping;

// Tempo control variables

static unsigned int ticks_per_beat;
static unsigned int us_per_beat;

// Mini-log of recently played percussion instruments:

static uint8_t last_perc[PERCUSSION_LOG_LEN];
static unsigned int last_perc_count;

// Configuration file variable, containing the port number for the
// adlib chip.

char *snd_dmxoption = "";
int opl_io_port = 0x388;

// If true, OPL sound channels are reversed to their correct arrangement
// (as intended by the MIDI standard) rather than the backwards one
// used by DMX due to a bug.

static boolean opl_stereo_correct = false;

// Load instrument table from GENMIDI lump:

static boolean LoadInstrumentTable(void)
{
    byte *lump;

    lump = W_CacheLumpName(DEH_String("genmidi"), PU_STATIC);

    // DMX does not check header

    main_instrs = (genmidi_instr_t *) (lump + strlen(GENMIDI_HEADER));
    percussion_instrs = main_instrs + GENMIDI_NUM_INSTRS;
    main_instr_names =
        (char (*)[32]) (percussion_instrs + GENMIDI_NUM_PERCUSSION);
    percussion_names = main_instr_names + GENMIDI_NUM_INSTRS;

    return true;
}

// Get the next available voice from the freelist.

static opl_voice_t *GetFreeVoice(void)
{
    opl_voice_t *result;
    int i;

    // None available?

    if (voice_free_num == 0)
    {
        return NULL;
    }

    // Remove from free list

    result = voice_free_list[0];

    voice_free_num--;

    for (i = 0; i < voice_free_num; i++)
    {
        voice_free_list[i] = voice_free_list[i + 1];
    }

    // Add to allocated list

    voice_alloced_list[voice_alloced_num++] = result;

    return result;
}

// Release a voice back to the freelist.

static void VoiceKeyOff(opl_voice_t *voice);

static void ReleaseVoice(int index)
{
    opl_voice_t *voice;
    boolean double_voice;
    int i;

    // Doom 2 1.666 OPL crash emulation.
    if (index >= voice_alloced_num)
    {

        voice_alloced_num = 0;
        voice_free_num = 0;
        return;
    }

    voice = voice_alloced_list[index];

    VoiceKeyOff(voice);

    voice->channel = NULL;
    voice->note = 0;

    double_voice = voice->current_instr_voice != 0;
    
    // Remove from alloced list.

    voice_alloced_num--;

    for (i = index; i < voice_alloced_num; i++)
    {
        voice_alloced_list[i] = voice_alloced_list[i + 1];
    }

    // Search to the end of the freelist (This is how Doom behaves!)

    voice_free_list[voice_free_num++] = voice;

    if (double_voice && opl_drv_ver < opl_doom_1_9)
    {
        ReleaseVoice(index);
    }
}

// Load data to the specified operator

static void LoadOperatorData(int operator, genmidi_op_t *data,
                             boolean max_level, unsigned int *volume)
{
    int level;

    // The scale and level fields must be combined for the level register.
    // For the carrier wave we always set the maximum level.

    level = data->scale;

    if (max_level)
    {
        level |= 0x3f;
    }
    else
    {
        level |= data->level;
    }

    *volume = level;

    OPL_WriteRegister(OPL_REGS_LEVEL + operator, level);
    OPL_WriteRegister(OPL_REGS_TREMOLO + operator, data->tremolo);
    OPL_WriteRegister(OPL_REGS_ATTACK + operator, data->attack);
    OPL_WriteRegister(OPL_REGS_SUSTAIN + operator, data->sustain);
    OPL_WriteRegister(OPL_REGS_WAVEFORM + operator, data->waveform);
}

// Set the instrument for a particular voice.

static void SetVoiceInstrument(opl_voice_t *voice,
                               genmidi_instr_t *instr,
                               unsigned int instr_voice)
{
    genmidi_voice_t *data;
    unsigned int modulating;

    // Instrument already set for this channel?

    if (voice->current_instr == instr
     && voice->current_instr_voice == instr_voice)
    {
        return;
    }

    voice->current_instr = instr;
    voice->current_instr_voice = instr_voice;

    data = &instr->voices[instr_voice];

    // Are we usind modulated feedback mode?

    modulating = (data->feedback & 0x01) == 0;

    // Doom loads the second operator first, then the first.
    // The carrier is set to minimum volume until the voice volume
    // is set in SetVoiceVolume (below).  If we are not using
    // modulating mode, we must set both to minimum volume.

    LoadOperatorData(voice->op2 | voice->array, &data->carrier, true,
                     &voice->car_volume);
    LoadOperatorData(voice->op1 | voice->array, &data->modulator, !modulating,
                     &voice->mod_volume);

    // Set feedback register that control the connection between the
    // two operators.  Turn on bits in the upper nybble; I think this
    // is for OPL3, where it turns on channel A/B.

    OPL_WriteRegister((OPL_REGS_FEEDBACK + voice->index) | voice->array,
                      data->feedback | voice->reg_pan);

    // Calculate voice priority.

    voice->priority = 0x0f - (data->carrier.attack >> 4)
                    + 0x0f - (data->carrier.sustain & 0x0f);
}

static void SetVoiceVolume(opl_voice_t *voice, unsigned int volume)
{
    genmidi_voice_t *opl_voice;
    unsigned int midi_volume;
    unsigned int full_volume;
    unsigned int car_volume;
    unsigned int mod_volume;

    voice->note_volume = volume;

    opl_voice = &voice->current_instr->voices[voice->current_instr_voice];

    // Multiply note volume and channel volume to get the actual volume.

    midi_volume = 2 * (volume_mapping_table[voice->channel->volume] + 1);

    full_volume = (volume_mapping_table[voice->note_volume] * midi_volume)
                >> 9;

    // The volume value to use in the register:
    car_volume = 0x3f - full_volume;

    // Update the volume register(s) if necessary.

    if (car_volume != (voice->car_volume & 0x3f))
    {
        voice->car_volume = car_volume | (voice->car_volume & 0xc0);

        OPL_WriteRegister((OPL_REGS_LEVEL + voice->op2) | voice->array,
                          voice->car_volume);

        // If we are using non-modulated feedback mode, we must set the
        // volume for both voices.

        if ((opl_voice->feedback & 0x01) != 0
         && opl_voice->modulator.level != 0x3f)
        {
            mod_volume = opl_voice->modulator.level;
            if (mod_volume < car_volume)
            {
                mod_volume = car_volume;
            }

            mod_volume |= voice->mod_volume & 0xc0;

            if(mod_volume != voice->mod_volume)
            {
                voice->mod_volume = mod_volume;
                OPL_WriteRegister((OPL_REGS_LEVEL + voice->op1) | voice->array,
                                  mod_volume |
                                  (opl_voice->modulator.scale & 0xc0));
            }
        }
    }
}

static void SetVoicePan(opl_voice_t *voice, unsigned int pan)
{
    genmidi_voice_t *opl_voice;

    voice->reg_pan = pan;
    opl_voice = &voice->current_instr->voices[voice->current_instr_voice];;

    OPL_WriteRegister((OPL_REGS_FEEDBACK + voice->index) | voice->array,
                      opl_voice->feedback | pan);
}

// Initialize the voice table and freelist

static void InitVoices(void)
{
    int i;

    // Start with an empty free list.
    
    voice_free_num = num_opl_voices;
    voice_alloced_num = 0;

    // Initialize each voice.

    for (i = 0; i < num_opl_voices; ++i)
    {
        voices[i].index = i % OPL_NUM_VOICES;
        voices[i].op1 = voice_operators[0][i % OPL_NUM_VOICES];
        voices[i].op2 = voice_operators[1][i % OPL_NUM_VOICES];
        voices[i].array = (i / OPL_NUM_VOICES) << 8;
        voices[i].current_instr = NULL;

        // Add this voice to the freelist.

        voice_free_list[i] = &voices[i];
    }
}

static void SetChannelVolume(opl_channel_data_t *channel, unsigned int volume,
                             boolean clip_start);

// Set music volume (0 - 127)

static void I_OPL_SetMusicVolume(int volume)
{
    unsigned int i;

    if (current_music_volume == volume)
    {
        return;
    }

    // Internal state variable.

    current_music_volume = volume;

    // Update the volume of all voices.

    for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
    {
        if (i == 15)
        {
            SetChannelVolume(&channels[i], volume, false);
        }
        else
        {
            SetChannelVolume(&channels[i], channels[i].volume_base, false);
        }
    }
}

static void VoiceKeyOff(opl_voice_t *voice)
{
    OPL_WriteRegister((OPL_REGS_FREQ_2 + voice->index) | voice->array,
                      voice->freq >> 8);
}

static opl_channel_data_t *TrackChannelForEvent(opl_track_data_t *track,
                                                midi_event_t *event)
{
    unsigned int channel_num = event->data.channel.channel;

    // MIDI uses track #9 for percussion, but for MUS it's track #15
    // instead. Because DMX works on MUS data internally, we need to
    // swap back to the MUS version of the channel number.
    if (channel_num == 9)
    {
        channel_num = 15;
    }
    else if (channel_num == 15)
    {
        channel_num = 9;
    }

    return &channels[channel_num];
}

// Get the frequency that we should be using for a voice.

static void KeyOffEvent(opl_track_data_t *track, midi_event_t *event)
{
    opl_channel_data_t *channel;
    int i;
    unsigned int key;

/*
    printf("note off: channel %i, %i, %i\n",
           event->data.channel.channel,
           event->data.channel.param1,
           event->data.channel.param2);
*/

    channel = TrackChannelForEvent(track, event);
    key = event->data.channel.param1;

    // Turn off voices being used to play this key.
    // If it is a double voice instrument there will be two.

    for (i = 0; i < voice_alloced_num; i++)
    {
        if (voice_alloced_list[i]->channel == channel
         && voice_alloced_list[i]->key == key)
        {
            // Finished with this voice now.

            ReleaseVoice(i);

            i--;
        }
    }
}

// When all voices are in use, we must discard an existing voice to
// play a new note.  Find and free an existing voice.  The channel
// passed to the function is the channel for the new note to be
// played.

static void ReplaceExistingVoice(void)
{
    int i;
    int result;

    // Check the allocated voices, if we find an instrument that is
    // of a lower priority to the new instrument, discard it.
    // If a voice is being used to play the second voice of an instrument,
    // use that, as second voices are non-essential.
    // Lower numbered MIDI channels implicitly have a higher priority
    // than higher-numbered channels, eg. MIDI channel 1 is never
    // discarded for MIDI channel 2.

    result = 0;

    for (i = 0; i < voice_alloced_num; i++)
    {
        if (voice_alloced_list[i]->current_instr_voice != 0
         || voice_alloced_list[i]->channel
         >= voice_alloced_list[result]->channel)
        {
            result = i;
        }
    }

    ReleaseVoice(result);
}

// Alternate versions of ReplaceExistingVoice() used when emulating old
// versions of the DMX library used in Doom 1.666, Heretic and Hexen.

static void ReplaceExistingVoiceDoom1(void)
{
    int i;
    int result;

    result = 0;

    for (i = 0; i < voice_alloced_num; i++)
    {
        if (voice_alloced_list[i]->channel
          > voice_alloced_list[result]->channel)
        {
            result = i;
        }
    }

    ReleaseVoice(result);
}

static void ReplaceExistingVoiceDoom2(opl_channel_data_t *channel)
{
    int i;
    int result;
    int priority;

    result = 0;

    priority = 0x8000;

    for (i = 0; i < voice_alloced_num - 3; i++)
    {
        if (voice_alloced_list[i]->priority < priority
         && voice_alloced_list[i]->channel >= channel)
        {
            priority = voice_alloced_list[i]->priority;
            result = i;
        }
    }

    ReleaseVoice(result);
}


static unsigned int FrequencyForVoice(opl_voice_t *voice)
{
    genmidi_voice_t *gm_voice;
    signed int freq_index;
    unsigned int octave;
    unsigned int sub_index;
    signed int note;

    note = voice->note;

    // Apply note offset.
    // Don't apply offset if the instrument is a fixed note instrument.

    gm_voice = &voice->current_instr->voices[voice->current_instr_voice];

    if ((SHORT(voice->current_instr->flags) & GENMIDI_FLAG_FIXED) == 0)
    {
        note += (signed short) SHORT(gm_voice->base_note_offset);
    }

    // Avoid possible overflow due to base note offset:

    while (note < 0)
    {
        note += 12;
    }

    while (note > 95)
    {
        note -= 12;
    }

    freq_index = 64 + 32 * note + voice->channel->bend;

    // If this is the second voice of a double voice instrument, the
    // frequency index can be adjusted by the fine tuning field.

    if (voice->current_instr_voice != 0)
    {
        freq_index += (voice->current_instr->fine_tuning / 2) - 64;
    }

    if (freq_index < 0)
    {
        freq_index = 0;
    }

    // The first 7 notes use the start of the table, while
    // consecutive notes loop around the latter part.

    if (freq_index < 284)
    {
        return frequency_curve[freq_index];
    }

    sub_index = (freq_index - 284) % (12 * 32);
    octave = (freq_index - 284) / (12 * 32);

    // Once the seventh octave is reached, things break down.
    // We can only go up to octave 7 as a maximum anyway (the OPL
    // register only has three bits for octave number), but for the
    // notes in octave 7, the first five bits have octave=7, the
    // following notes have octave=6.  This 7/6 pattern repeats in
    // following octaves (which are technically impossible to
    // represent anyway).

    if (octave >= 7)
    {
        octave = 7;
    }

    // Calculate the resulting register value to use for the frequency.

    return frequency_curve[sub_index + 284] | (octave << 10);
}

// Update the frequency that a voice is programmed to use.

static void UpdateVoiceFrequency(opl_voice_t *voice)
{
    unsigned int freq;

    // Calculate the frequency to use for this voice and update it
    // if neccessary.

    freq = FrequencyForVoice(voice);

    if (voice->freq != freq)
    {
        OPL_WriteRegister((OPL_REGS_FREQ_1 + voice->index) | voice->array,
                          freq & 0xff);
        OPL_WriteRegister((OPL_REGS_FREQ_2 + voice->index) | voice->array,
                          (freq >> 8) | 0x20);

        voice->freq = freq;
    }
}

// Program a single voice for an instrument.  For a double voice
// instrument (GENMIDI_FLAG_2VOICE), this is called twice for each
// key on event.

static void VoiceKeyOn(opl_channel_data_t *channel,
                       genmidi_instr_t *instrument,
                       unsigned int instrument_voice,
                       unsigned int note,
                       unsigned int key,
                       unsigned int volume)
{
    opl_voice_t *voice;

    if (!opl_opl3mode && opl_drv_ver == opl_doom1_1_666)
    {
        instrument_voice = 0;
    }

    // Find a voice to use for this new note.

    voice = GetFreeVoice();

    if (voice == NULL)
    {
        return;
    }

    voice->channel = channel;
    voice->key = key;

    // Work out the note to use.  This is normally the same as
    // the key, unless it is a fixed pitch instrument.

    if ((SHORT(instrument->flags) & GENMIDI_FLAG_FIXED) != 0)
    {
        voice->note = instrument->fixed_note;
    }
    else
    {
        voice->note = note;
    }

    voice->reg_pan = channel->pan;

    // Program the voice with the instrument data:

    SetVoiceInstrument(voice, instrument, instrument_voice);

    // Set the volume level.

    SetVoiceVolume(voice, volume);

    // Write the frequency value to turn the note on.

    voice->freq = 0;
    UpdateVoiceFrequency(voice);
}

static void KeyOnEvent(opl_track_data_t *track, midi_event_t *event)
{
    genmidi_instr_t *instrument;
    opl_channel_data_t *channel;
    unsigned int note, key, volume, voicenum;
    boolean double_voice;

/*
    printf("note on: channel %i, %i, %i\n",
           event->data.channel.channel,
           event->data.channel.param1,
           event->data.channel.param2);
*/

    note = event->data.channel.param1;
    key = event->data.channel.param1;
    volume = event->data.channel.param2;

    // A volume of zero means key off. Some MIDI tracks, eg. the ones
    // in AV.wad, use a second key on with a volume of zero to mean
    // key off.
    if (volume <= 0)
    {
        KeyOffEvent(track, event);
        return;
    }

    // The channel.
    channel = TrackChannelForEvent(track, event);

    // Percussion channel is treated differently.
    if (event->data.channel.channel == 9)
    {
        if (key < 35 || key > 81)
        {
            return;
        }

        instrument = &percussion_instrs[key - 35];

        last_perc[last_perc_count] = key;
        last_perc_count = (last_perc_count + 1) % PERCUSSION_LOG_LEN;
        note = 60;
    }
    else
    {
        instrument = channel->instrument;
    }

    double_voice = (SHORT(instrument->flags) & GENMIDI_FLAG_2VOICE) != 0;

    switch (opl_drv_ver)
    {
        case opl_doom1_1_666:
            voicenum = double_voice + 1;
            if (!opl_opl3mode)
            {
                voicenum = 1;
            }
            while (voice_alloced_num > num_opl_voices - voicenum)
            {
                ReplaceExistingVoiceDoom1();
            }

            // Find and program a voice for this instrument.  If this
            // is a double voice instrument, we must do this twice.

            if (double_voice)
            {
                VoiceKeyOn(channel, instrument, 1, note, key, volume);
            }

            VoiceKeyOn(channel, instrument, 0, note, key, volume);
            break;
        case opl_doom2_1_666:
            if (voice_alloced_num == num_opl_voices)
            {
                ReplaceExistingVoiceDoom2(channel);
            }
            if (voice_alloced_num == num_opl_voices - 1 && double_voice)
            {
                ReplaceExistingVoiceDoom2(channel);
            }

            // Find and program a voice for this instrument.  If this
            // is a double voice instrument, we must do this twice.

            if (double_voice)
            {
                VoiceKeyOn(channel, instrument, 1, note, key, volume);
            }

            VoiceKeyOn(channel, instrument, 0, note, key, volume);
            break;
        default:
        case opl_doom_1_9:
            if (voice_free_num == 0)
            {
                ReplaceExistingVoice();
            }

            // Find and program a voice for this instrument.  If this
            // is a double voice instrument, we must do this twice.

            VoiceKeyOn(channel, instrument, 0, note, key, volume);

            if (double_voice)
            {
                VoiceKeyOn(channel, instrument, 1, note, key, volume);
            }
            break;
    }
}

static void ProgramChangeEvent(opl_track_data_t *track, midi_event_t *event)
{
    opl_channel_data_t *channel;
    int instrument;

    // Set the instrument used on this channel.

    channel = TrackChannelForEvent(track, event);
    instrument = event->data.channel.param1;
    channel->instrument = &main_instrs[instrument];

    // TODO: Look through existing voices that are turned on on this
    // channel, and change the instrument.
}

static void SetChannelVolume(opl_channel_data_t *channel, unsigned int volume,
                             boolean clip_start)
{
    unsigned int i;

    channel->volume_base = volume;

    if (volume > current_music_volume)
    {
        volume = current_music_volume;
    }

    if (clip_start && volume > start_music_volume)
    {
        volume = start_music_volume;
    }

    channel->volume = volume;

    // Update all voices that this channel is using.

    for (i = 0; i < num_opl_voices; ++i)
    {
        if (voices[i].channel == channel)
        {
            SetVoiceVolume(&voices[i], voices[i].note_volume);
        }
    }
}

static void SetChannelPan(opl_channel_data_t *channel, unsigned int pan)
{
    unsigned int reg_pan;
    unsigned int i;

    // The DMX library has the stereo channels backwards, maybe because
    // Paul Radek had a Soundblaster card with the channels reversed, or
    // perhaps it was just a bug in the OPL3 support that was never
    // finished. By default we preserve this bug, but we also provide a
    // secret DMXOPTION to fix it.
    if (opl_stereo_correct)
    {
        pan = 144 - pan;
    }

    if (opl_opl3mode)
    {
        if (pan >= 96)
        {
            reg_pan = 0x10;
        }
        else if (pan <= 48)
        {
            reg_pan = 0x20;
        }
        else
        {
            reg_pan = 0x30;
        }
        if (channel->pan != reg_pan)
        {
            channel->pan = reg_pan;
            for (i = 0; i < num_opl_voices; i++)
            {
                if (voices[i].channel == channel)
                {
                    SetVoicePan(&voices[i], reg_pan);
                }
            }
        }
    }
}

// Handler for the MIDI_CONTROLLER_ALL_NOTES_OFF channel event.
static void AllNotesOff(opl_channel_data_t *channel, unsigned int param)
{
    int i;

    for (i = 0; i < voice_alloced_num; i++)
    {
        if (voice_alloced_list[i]->channel == channel)
        {
            // Finished with this voice now.

            ReleaseVoice(i);
            
            i--;
        }
    }
}

static void ControllerEvent(opl_track_data_t *track, midi_event_t *event)
{
    opl_channel_data_t *channel;
    unsigned int controller;
    unsigned int param;

/*
    printf("change controller: channel %i, %i, %i\n",
           event->data.channel.channel,
           event->data.channel.param1,
           event->data.channel.param2);
*/

    channel = TrackChannelForEvent(track, event);
    controller = event->data.channel.param1;
    param = event->data.channel.param2;

    switch (controller)
    {
        case MIDI_CONTROLLER_MAIN_VOLUME:
            SetChannelVolume(channel, param, true);
            break;

        case MIDI_CONTROLLER_PAN:
            SetChannelPan(channel, param);
            break;

        case MIDI_CONTROLLER_ALL_NOTES_OFF:
            AllNotesOff(channel, param);
            break;

        default:
#ifdef OPL_MIDI_DEBUG
            fprintf(stderr, "Unknown MIDI controller type: %i\n", controller);
#endif
            break;
    }
}

// Process a pitch bend event.

static void PitchBendEvent(opl_track_data_t *track, midi_event_t *event)
{
    opl_channel_data_t *channel;
    int i;
    opl_voice_t *voice_updated_list[OPL_NUM_VOICES * 2];
    unsigned int voice_updated_num = 0;
    opl_voice_t *voice_not_updated_list[OPL_NUM_VOICES * 2];
    unsigned int voice_not_updated_num = 0;

    // Update the channel bend value.  Only the MSB of the pitch bend
    // value is considered: this is what Doom does.

    channel = TrackChannelForEvent(track, event);
    channel->bend = event->data.channel.param2 - 64;

    // Update all voices for this channel.

	for (i = 0; i < voice_alloced_num; ++i)
    {
        if (voice_alloced_list[i]->channel == channel)
        {
            UpdateVoiceFrequency(voice_alloced_list[i]);
            voice_updated_list[voice_updated_num++] = voice_alloced_list[i];
        }
        else
        {
            voice_not_updated_list[voice_not_updated_num++] =
            voice_alloced_list[i];
        }
    }

    for (i = 0; i < voice_not_updated_num; i++)
    {
        voice_alloced_list[i] = voice_not_updated_list[i];
    }

    for (i = 0; i < voice_updated_num; i++)
    {
        voice_alloced_list[i + voice_not_updated_num] = voice_updated_list[i];
    }
}

static void MetaSetTempo(unsigned int tempo)
{
    OPL_AdjustCallbacks((float) us_per_beat / tempo);
    us_per_beat = tempo;
}

// Process a meta event.

static void MetaEvent(opl_track_data_t *track, midi_event_t *event)
{
    byte *data = event->data.meta.data;
    unsigned int data_len = event->data.meta.length;

    switch (event->data.meta.type)
    {
        // Things we can just ignore.

        case MIDI_META_SEQUENCE_NUMBER:
        case MIDI_META_TEXT:
        case MIDI_META_COPYRIGHT:
        case MIDI_META_TRACK_NAME:
        case MIDI_META_INSTR_NAME:
        case MIDI_META_LYRICS:
        case MIDI_META_MARKER:
        case MIDI_META_CUE_POINT:
        case MIDI_META_SEQUENCER_SPECIFIC:
            break;

        case MIDI_META_SET_TEMPO:
            if (data_len == 3)
            {
                MetaSetTempo((data[0] << 16) | (data[1] << 8) | data[2]);
            }
            break;

        // End of track - actually handled when we run out of events
        // in the track, see below.

        case MIDI_META_END_OF_TRACK:
            break;

        default:
#ifdef OPL_MIDI_DEBUG
            fprintf(stderr, "Unknown MIDI meta event type: %i\n",
                            event->data.meta.type);
#endif
            break;
    }
}

// Process a MIDI event from a track.

static void ProcessEvent(opl_track_data_t *track, midi_event_t *event)
{
    switch (event->event_type)
    {
        case MIDI_EVENT_NOTE_OFF:
            KeyOffEvent(track, event);
            break;

        case MIDI_EVENT_NOTE_ON:
            KeyOnEvent(track, event);
            break;

        case MIDI_EVENT_CONTROLLER:
            ControllerEvent(track, event);
            break;

        case MIDI_EVENT_PROGRAM_CHANGE:
            ProgramChangeEvent(track, event);
            break;

        case MIDI_EVENT_PITCH_BEND:
            PitchBendEvent(track, event);
            break;

        case MIDI_EVENT_META:
            MetaEvent(track, event);
            break;

        // SysEx events can be ignored.

        case MIDI_EVENT_SYSEX:
        case MIDI_EVENT_SYSEX_SPLIT:
            break;

        default:
#ifdef OPL_MIDI_DEBUG
            fprintf(stderr, "Unknown MIDI event type %i\n", event->event_type);
#endif
            break;
    }
}

static void ScheduleTrack(opl_track_data_t *track);
static void InitChannel(opl_channel_data_t *channel);

// Restart a song from the beginning.

static void RestartSong(void *unused)
{
    unsigned int i;

    running_tracks = num_tracks;

    start_music_volume = current_music_volume;

    for (i = 0; i < num_tracks; ++i)
    {
        MIDI_RestartIterator(tracks[i].iter);
        ScheduleTrack(&tracks[i]);
    }

    for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
    {
        InitChannel(&channels[i]);
    }
}

// Callback function invoked when another event needs to be read from
// a track.

static void TrackTimerCallback(void *arg)
{
    opl_track_data_t *track = arg;
    midi_event_t *event;

    // Get the next event and process it.

    if (!MIDI_GetNextEvent(track->iter, &event))
    {
        return;
    }

    ProcessEvent(track, event);

    // End of track?

    if (event->event_type == MIDI_EVENT_META
     && event->data.meta.type == MIDI_META_END_OF_TRACK)
    {
        --running_tracks;

        // When all tracks have finished, restart the song.
        // Don't restart the song immediately, but wait for 5ms
        // before triggering a restart.  Otherwise it is possible
        // to construct an empty MIDI file that causes the game
        // to lock up in an infinite loop. (5ms should be short
        // enough not to be noticeable by the listener).

        if (running_tracks <= 0 && song_looping)
        {
            OPL_SetCallback(5000, RestartSong, NULL);
        }

        return;
    }

    // Reschedule the callback for the next event in the track.

    ScheduleTrack(track);
}

static void ScheduleTrack(opl_track_data_t *track)
{
    unsigned int nticks;
    uint64_t us;

    // Get the number of microseconds until the next event.

    nticks = MIDI_GetDeltaTime(track->iter);
    us = ((uint64_t) nticks * us_per_beat) / ticks_per_beat;

    // Set a timer to be invoked when the next event is
    // ready to play.

    OPL_SetCallback(us, TrackTimerCallback, track);
}

// Initialize a channel.

static void InitChannel(opl_channel_data_t *channel)
{
    // TODO: Work out sensible defaults?

    channel->instrument = &main_instrs[0];
    channel->volume = current_music_volume;
    channel->volume_base = 100;
    if (channel->volume > channel->volume_base)
    {
        channel->volume = channel->volume_base;
    }
    channel->pan = 0x30;
    channel->bend = 0;
}

// Start a MIDI track playing:

static void StartTrack(midi_file_t *file, unsigned int track_num)
{
    opl_track_data_t *track;

    track = &tracks[track_num];
    track->iter = MIDI_IterateTrack(file, track_num);

    // Schedule the first event.

    ScheduleTrack(track);
}

// Start playing a mid

static void I_OPL_PlaySong(void *handle, boolean looping)
{
    midi_file_t *file;
    unsigned int i;

    if (!music_initialized || handle == NULL)
    {
        return;
    }

    file = handle;

    // Allocate track data.

    tracks = malloc(MIDI_NumTracks(file) * sizeof(opl_track_data_t));

    num_tracks = MIDI_NumTracks(file);
    running_tracks = num_tracks;
    song_looping = looping;

    ticks_per_beat = MIDI_GetFileTimeDivision(file);

    // Default is 120 bpm.
    // TODO: this is wrong

    us_per_beat = 500 * 1000;

    start_music_volume = current_music_volume;

    for (i = 0; i < num_tracks; ++i)
    {
        StartTrack(file, i);
    }

    for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
    {
        InitChannel(&channels[i]);
    }

    // If the music was previously paused, it needs to be unpaused; playing
    // a new song implies that we turn off pause. This matches vanilla
    // behavior of the DMX library, and some of the higher-level code in
    // s_sound.c relies on this.
    OPL_SetPaused(0);
}

static void I_OPL_PauseSong(void)
{
    unsigned int i;

    if (!music_initialized)
    {
        return;
    }

    // Pause OPL callbacks.

    OPL_SetPaused(1);

    // Turn off all main instrument voices (not percussion).
    // This is what Vanilla does.

    for (i = 0; i < num_opl_voices; ++i)
    {
        if (voices[i].channel != NULL
         && voices[i].current_instr < percussion_instrs)
        {
            VoiceKeyOff(&voices[i]);
        }
    }
}

static void I_OPL_ResumeSong(void)
{
    if (!music_initialized)
    {
        return;
    }

    OPL_SetPaused(0);
}

static void I_OPL_StopSong(void)
{
    unsigned int i;

    if (!music_initialized)
    {
        return;
    }

    OPL_Lock();

    // Stop all playback.

    OPL_ClearCallbacks();

    // Free all voices.

    for (i = 0; i < MIDI_CHANNELS_PER_TRACK; ++i)
    {
        AllNotesOff(&channels[i], 0);
    }

    // Free all track data.

    for (i = 0; i < num_tracks; ++i)
    {
        MIDI_FreeIterator(tracks[i].iter);
    }

    free(tracks);

    tracks = NULL;
    num_tracks = 0;

    OPL_Unlock();
}

static void I_OPL_UnRegisterSong(void *handle)
{
    if (!music_initialized)
    {
        return;
    }

    if (handle != NULL)
    {
        MIDI_FreeFile(handle);
    }
}

// Determine whether memory block is a .mid file

static boolean IsMid(byte *mem, int len)
{
    return len > 4 && !memcmp(mem, "MThd", 4);
}

static boolean ConvertMus(byte *musdata, int len, char *filename)
{
    MEMFILE *instream;
    MEMFILE *outstream;
    void *outbuf;
    size_t outbuf_len;
    int result;

    instream = mem_fopen_read(musdata, len);
    outstream = mem_fopen_write();

    result = mus2mid(instream, outstream);

    if (result == 0)
    {
        mem_get_buf(outstream, &outbuf, &outbuf_len);

        M_WriteFile(filename, outbuf, outbuf_len);
    }

    mem_fclose(instream);
    mem_fclose(outstream);

    return result;
}

static void *I_OPL_RegisterSong(void *data, int len)
{
    midi_file_t *result;
    char *filename;

    if (!music_initialized)
    {
        return NULL;
    }

    // MUS files begin with "MUS"
    // Reject anything which doesnt have this signature

    filename = M_TempFile("doom.mid");

    if (IsMid(data, len) && len < MAXMIDLENGTH)
    {
        M_WriteFile(filename, data, len);
    }
    else
    {
        // Assume a MUS file and try to convert

        ConvertMus(data, len, filename);
    }

    result = MIDI_LoadFile(filename);

    if (result == NULL)
    {
        fprintf(stderr, "I_OPL_RegisterSong: Failed to load MID.\n");
    }

    // remove file now

    remove(filename);
    free(filename);

    return result;
}

// Is the song playing?

static boolean I_OPL_MusicIsPlaying(void)
{
    if (!music_initialized)
    {
        return false;
    }

    return num_tracks > 0;
}

// Shutdown music

static void I_OPL_ShutdownMusic(void)
{
    if (music_initialized)
    {
        // Stop currently-playing track, if there is one:

        I_OPL_StopSong();

        OPL_Shutdown();

        // Release GENMIDI lump

        W_ReleaseLumpName(DEH_String("genmidi"));

        music_initialized = false;
    }
}

// Initialize music subsystem

static boolean I_OPL_InitMusic(void)
{
    char *dmxoption;
    opl_init_result_t chip_type;

    OPL_SetSampleRate(snd_samplerate);

    chip_type = OPL_Init(opl_io_port);
    if (chip_type == OPL_INIT_NONE)
    {
        printf("Dude.  The Adlib isn't responding.\n");
        return false;
    }

    // The DMXOPTION variable must be set to enable OPL3 support.
    // As an extension, we also allow it to be set from the config file.
    dmxoption = getenv("DMXOPTION");
    if (dmxoption == NULL)
    {
        dmxoption = snd_dmxoption != NULL ? snd_dmxoption : "";
    }

    if (chip_type == OPL_INIT_OPL3 && strstr(dmxoption, "-opl3") != NULL)
    {
        opl_opl3mode = 1;
        num_opl_voices = OPL_NUM_VOICES * 2;
    }
    else
    {
        opl_opl3mode = 0;
        num_opl_voices = OPL_NUM_VOICES;
    }

    // Secret, undocumented DMXOPTION that reverses the stereo channels
    // into their correct orientation.
    opl_stereo_correct = strstr(dmxoption, "-reverse") != NULL;

    // Initialize all registers.

    OPL_InitRegisters(opl_opl3mode);

    // Load instruments from GENMIDI lump:

    if (!LoadInstrumentTable())
    {
        OPL_Shutdown();
        return false;
    }

    InitVoices();

    tracks = NULL;
    num_tracks = 0;
    music_initialized = true;

    return true;
}

static snddevice_t music_opl_devices[] =
{
    SNDDEVICE_ADLIB,
    SNDDEVICE_SB,
};

music_module_t music_opl_module =
{
    music_opl_devices,
    arrlen(music_opl_devices),
    I_OPL_InitMusic,
    I_OPL_ShutdownMusic,
    I_OPL_SetMusicVolume,
    I_OPL_PauseSong,
    I_OPL_ResumeSong,
    I_OPL_RegisterSong,
    I_OPL_UnRegisterSong,
    I_OPL_PlaySong,
    I_OPL_StopSong,
    I_OPL_MusicIsPlaying,
    NULL,  // Poll
};

void I_SetOPLDriverVer(opl_driver_ver_t ver)
{
    opl_drv_ver = ver;
}

//----------------------------------------------------------------------
//
// Development / debug message generation, to help developing GENMIDI
// lumps.
//
//----------------------------------------------------------------------

static int NumActiveChannels(void)
{
    int i;

    for (i = MIDI_CHANNELS_PER_TRACK - 1; i >= 0; --i)
    {
        if (channels[i].instrument != &main_instrs[0])
        {
            return i + 1;
        }
    }

    return 0;
}

static int ChannelInUse(opl_channel_data_t *channel)
{
    int i;

    for (i = 0; i < voice_alloced_num; i++)
    {
        if (voice_alloced_list[i]->channel == channel)
        {
            return 1;
        }
    }

    return 0;
}

void I_OPL_DevMessages(char *result, size_t result_len)
{
    char tmp[80];
    int instr_num;
    int lines;
    int i;

    if (num_tracks == 0)
    {
        M_snprintf(result, result_len, "No OPL track!");
        return;
    }

    M_snprintf(result, result_len, "Tracks:\n");
    lines = 1;

    for (i = 0; i < NumActiveChannels(); ++i)
    {
        if (channels[i].instrument == NULL)
        {
            continue;
        }

        instr_num = channels[i].instrument - main_instrs;

        M_snprintf(tmp, sizeof(tmp),
                   "chan %i: %c i#%i (%s)\n",
                   i,
                   ChannelInUse(&channels[i]) ? '\'' : ' ',
                   instr_num + 1,
                   main_instr_names[instr_num]);
        M_StringConcat(result, tmp, result_len);

        ++lines;
    }

    M_snprintf(tmp, sizeof(tmp), "\nLast percussion:\n");
    M_StringConcat(result, tmp, result_len);
    lines += 2;

    i = (last_perc_count + PERCUSSION_LOG_LEN - 1) % PERCUSSION_LOG_LEN;

    do {
        if (last_perc[i] == 0)
        {
            break;
        }

        M_snprintf(tmp, sizeof(tmp),
                   "%cp#%i (%s)\n",
                   i == 0 ? '\'' : ' ',
                   last_perc[i],
                   percussion_names[last_perc[i] - 35]);
        M_StringConcat(result, tmp, result_len);
        ++lines;

        i = (i + PERCUSSION_LOG_LEN - 1) % PERCUSSION_LOG_LEN;
    } while (lines < 25 && i != last_perc_count);
}

