// Based off ROBOPLAY's OPL4 MID player code, with some fixes and modifications to make it work well.

#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdatomic.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>

#include <86box/86box.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/midi.h>
#include <86box/plat.h>
#include <86box/thread.h>
#include <86box/rom.h>
#include <86box/sound.h>
#include <86box/ui.h>
#include <86box/snd_opl.h>
#include <86box/opl4_defines.h>

#include "yrw801.h"

#define NR_OF_MIDI_CHANNELS 16
#define NR_OF_WAVE_CHANNELS 24

#define DRUM_CHANNEL        9

typedef struct
{
    uint8_t instrument;
    uint8_t panpot;
    uint8_t vibrato;
    bool    drum_channel;
} MIDI_CHANNEL_DATA;

typedef struct
{
    bool     is_active;
    uint64_t activated;

    uint8_t                 number;
    MIDI_CHANNEL_DATA      *midi_channel;
    uint8_t                 note;
    uint8_t                 velocity;
    const YRW801_WAVE_DATA *wave_data;
    uint8_t                 level_direct;
    uint8_t                 reg_f_number;
    uint8_t                 reg_misc;
    uint8_t                 reg_lfo_vibrato;
} VOICE_DATA;

static const int16_t g_wave_pitch_map[0x600] = {
    0x000, 0x000, 0x001, 0x001, 0x002, 0x002, 0x003, 0x003,
    0x004, 0x004, 0x005, 0x005, 0x006, 0x006, 0x006, 0x007,
    0x007, 0x008, 0x008, 0x009, 0x009, 0x00a, 0x00a, 0x00b,
    0x00b, 0x00c, 0x00c, 0x00d, 0x00d, 0x00d, 0x00e, 0x00e,
    0x00f, 0x00f, 0x010, 0x010, 0x011, 0x011, 0x012, 0x012,
    0x013, 0x013, 0x014, 0x014, 0x015, 0x015, 0x015, 0x016,
    0x016, 0x017, 0x017, 0x018, 0x018, 0x019, 0x019, 0x01a,
    0x01a, 0x01b, 0x01b, 0x01c, 0x01c, 0x01d, 0x01d, 0x01e,
    0x01e, 0x01e, 0x01f, 0x01f, 0x020, 0x020, 0x021, 0x021,
    0x022, 0x022, 0x023, 0x023, 0x024, 0x024, 0x025, 0x025,
    0x026, 0x026, 0x027, 0x027, 0x028, 0x028, 0x029, 0x029,
    0x029, 0x02a, 0x02a, 0x02b, 0x02b, 0x02c, 0x02c, 0x02d,
    0x02d, 0x02e, 0x02e, 0x02f, 0x02f, 0x030, 0x030, 0x031,
    0x031, 0x032, 0x032, 0x033, 0x033, 0x034, 0x034, 0x035,
    0x035, 0x036, 0x036, 0x037, 0x037, 0x038, 0x038, 0x038,
    0x039, 0x039, 0x03a, 0x03a, 0x03b, 0x03b, 0x03c, 0x03c,
    0x03d, 0x03d, 0x03e, 0x03e, 0x03f, 0x03f, 0x040, 0x040,
    0x041, 0x041, 0x042, 0x042, 0x043, 0x043, 0x044, 0x044,
    0x045, 0x045, 0x046, 0x046, 0x047, 0x047, 0x048, 0x048,
    0x049, 0x049, 0x04a, 0x04a, 0x04b, 0x04b, 0x04c, 0x04c,
    0x04d, 0x04d, 0x04e, 0x04e, 0x04f, 0x04f, 0x050, 0x050,
    0x051, 0x051, 0x052, 0x052, 0x053, 0x053, 0x054, 0x054,
    0x055, 0x055, 0x056, 0x056, 0x057, 0x057, 0x058, 0x058,
    0x059, 0x059, 0x05a, 0x05a, 0x05b, 0x05b, 0x05c, 0x05c,
    0x05d, 0x05d, 0x05e, 0x05e, 0x05f, 0x05f, 0x060, 0x060,
    0x061, 0x061, 0x062, 0x062, 0x063, 0x063, 0x064, 0x064,
    0x065, 0x065, 0x066, 0x066, 0x067, 0x067, 0x068, 0x068,
    0x069, 0x069, 0x06a, 0x06a, 0x06b, 0x06b, 0x06c, 0x06c,
    0x06d, 0x06d, 0x06e, 0x06e, 0x06f, 0x06f, 0x070, 0x071,
    0x071, 0x072, 0x072, 0x073, 0x073, 0x074, 0x074, 0x075,
    0x075, 0x076, 0x076, 0x077, 0x077, 0x078, 0x078, 0x079,
    0x079, 0x07a, 0x07a, 0x07b, 0x07b, 0x07c, 0x07c, 0x07d,
    0x07d, 0x07e, 0x07e, 0x07f, 0x07f, 0x080, 0x081, 0x081,
    0x082, 0x082, 0x083, 0x083, 0x084, 0x084, 0x085, 0x085,
    0x086, 0x086, 0x087, 0x087, 0x088, 0x088, 0x089, 0x089,
    0x08a, 0x08a, 0x08b, 0x08b, 0x08c, 0x08d, 0x08d, 0x08e,
    0x08e, 0x08f, 0x08f, 0x090, 0x090, 0x091, 0x091, 0x092,
    0x092, 0x093, 0x093, 0x094, 0x094, 0x095, 0x096, 0x096,
    0x097, 0x097, 0x098, 0x098, 0x099, 0x099, 0x09a, 0x09a,
    0x09b, 0x09b, 0x09c, 0x09c, 0x09d, 0x09d, 0x09e, 0x09f,
    0x09f, 0x0a0, 0x0a0, 0x0a1, 0x0a1, 0x0a2, 0x0a2, 0x0a3,
    0x0a3, 0x0a4, 0x0a4, 0x0a5, 0x0a6, 0x0a6, 0x0a7, 0x0a7,
    0x0a8, 0x0a8, 0x0a9, 0x0a9, 0x0aa, 0x0aa, 0x0ab, 0x0ab,
    0x0ac, 0x0ad, 0x0ad, 0x0ae, 0x0ae, 0x0af, 0x0af, 0x0b0,
    0x0b0, 0x0b1, 0x0b1, 0x0b2, 0x0b2, 0x0b3, 0x0b4, 0x0b4,
    0x0b5, 0x0b5, 0x0b6, 0x0b6, 0x0b7, 0x0b7, 0x0b8, 0x0b8,
    0x0b9, 0x0ba, 0x0ba, 0x0bb, 0x0bb, 0x0bc, 0x0bc, 0x0bd,
    0x0bd, 0x0be, 0x0be, 0x0bf, 0x0c0, 0x0c0, 0x0c1, 0x0c1,
    0x0c2, 0x0c2, 0x0c3, 0x0c3, 0x0c4, 0x0c4, 0x0c5, 0x0c6,
    0x0c6, 0x0c7, 0x0c7, 0x0c8, 0x0c8, 0x0c9, 0x0c9, 0x0ca,
    0x0cb, 0x0cb, 0x0cc, 0x0cc, 0x0cd, 0x0cd, 0x0ce, 0x0ce,
    0x0cf, 0x0d0, 0x0d0, 0x0d1, 0x0d1, 0x0d2, 0x0d2, 0x0d3,
    0x0d3, 0x0d4, 0x0d5, 0x0d5, 0x0d6, 0x0d6, 0x0d7, 0x0d7,
    0x0d8, 0x0d8, 0x0d9, 0x0da, 0x0da, 0x0db, 0x0db, 0x0dc,
    0x0dc, 0x0dd, 0x0de, 0x0de, 0x0df, 0x0df, 0x0e0, 0x0e0,
    0x0e1, 0x0e1, 0x0e2, 0x0e3, 0x0e3, 0x0e4, 0x0e4, 0x0e5,
    0x0e5, 0x0e6, 0x0e7, 0x0e7, 0x0e8, 0x0e8, 0x0e9, 0x0e9,
    0x0ea, 0x0eb, 0x0eb, 0x0ec, 0x0ec, 0x0ed, 0x0ed, 0x0ee,
    0x0ef, 0x0ef, 0x0f0, 0x0f0, 0x0f1, 0x0f1, 0x0f2, 0x0f3,
    0x0f3, 0x0f4, 0x0f4, 0x0f5, 0x0f5, 0x0f6, 0x0f7, 0x0f7,
    0x0f8, 0x0f8, 0x0f9, 0x0f9, 0x0fa, 0x0fb, 0x0fb, 0x0fc,
    0x0fc, 0x0fd, 0x0fd, 0x0fe, 0x0ff, 0x0ff, 0x100, 0x100,
    0x101, 0x101, 0x102, 0x103, 0x103, 0x104, 0x104, 0x105,
    0x106, 0x106, 0x107, 0x107, 0x108, 0x108, 0x109, 0x10a,
    0x10a, 0x10b, 0x10b, 0x10c, 0x10c, 0x10d, 0x10e, 0x10e,
    0x10f, 0x10f, 0x110, 0x111, 0x111, 0x112, 0x112, 0x113,
    0x114, 0x114, 0x115, 0x115, 0x116, 0x116, 0x117, 0x118,
    0x118, 0x119, 0x119, 0x11a, 0x11b, 0x11b, 0x11c, 0x11c,
    0x11d, 0x11e, 0x11e, 0x11f, 0x11f, 0x120, 0x120, 0x121,
    0x122, 0x122, 0x123, 0x123, 0x124, 0x125, 0x125, 0x126,
    0x126, 0x127, 0x128, 0x128, 0x129, 0x129, 0x12a, 0x12b,
    0x12b, 0x12c, 0x12c, 0x12d, 0x12e, 0x12e, 0x12f, 0x12f,
    0x130, 0x131, 0x131, 0x132, 0x132, 0x133, 0x134, 0x134,
    0x135, 0x135, 0x136, 0x137, 0x137, 0x138, 0x138, 0x139,
    0x13a, 0x13a, 0x13b, 0x13b, 0x13c, 0x13d, 0x13d, 0x13e,
    0x13e, 0x13f, 0x140, 0x140, 0x141, 0x141, 0x142, 0x143,
    0x143, 0x144, 0x144, 0x145, 0x146, 0x146, 0x147, 0x148,
    0x148, 0x149, 0x149, 0x14a, 0x14b, 0x14b, 0x14c, 0x14c,
    0x14d, 0x14e, 0x14e, 0x14f, 0x14f, 0x150, 0x151, 0x151,
    0x152, 0x153, 0x153, 0x154, 0x154, 0x155, 0x156, 0x156,
    0x157, 0x157, 0x158, 0x159, 0x159, 0x15a, 0x15b, 0x15b,
    0x15c, 0x15c, 0x15d, 0x15e, 0x15e, 0x15f, 0x160, 0x160,
    0x161, 0x161, 0x162, 0x163, 0x163, 0x164, 0x165, 0x165,
    0x166, 0x166, 0x167, 0x168, 0x168, 0x169, 0x16a, 0x16a,
    0x16b, 0x16b, 0x16c, 0x16d, 0x16d, 0x16e, 0x16f, 0x16f,
    0x170, 0x170, 0x171, 0x172, 0x172, 0x173, 0x174, 0x174,
    0x175, 0x175, 0x176, 0x177, 0x177, 0x178, 0x179, 0x179,
    0x17a, 0x17a, 0x17b, 0x17c, 0x17c, 0x17d, 0x17e, 0x17e,
    0x17f, 0x180, 0x180, 0x181, 0x181, 0x182, 0x183, 0x183,
    0x184, 0x185, 0x185, 0x186, 0x187, 0x187, 0x188, 0x188,
    0x189, 0x18a, 0x18a, 0x18b, 0x18c, 0x18c, 0x18d, 0x18e,
    0x18e, 0x18f, 0x190, 0x190, 0x191, 0x191, 0x192, 0x193,
    0x193, 0x194, 0x195, 0x195, 0x196, 0x197, 0x197, 0x198,
    0x199, 0x199, 0x19a, 0x19a, 0x19b, 0x19c, 0x19c, 0x19d,
    0x19e, 0x19e, 0x19f, 0x1a0, 0x1a0, 0x1a1, 0x1a2, 0x1a2,
    0x1a3, 0x1a4, 0x1a4, 0x1a5, 0x1a6, 0x1a6, 0x1a7, 0x1a8,
    0x1a8, 0x1a9, 0x1a9, 0x1aa, 0x1ab, 0x1ab, 0x1ac, 0x1ad,
    0x1ad, 0x1ae, 0x1af, 0x1af, 0x1b0, 0x1b1, 0x1b1, 0x1b2,
    0x1b3, 0x1b3, 0x1b4, 0x1b5, 0x1b5, 0x1b6, 0x1b7, 0x1b7,
    0x1b8, 0x1b9, 0x1b9, 0x1ba, 0x1bb, 0x1bb, 0x1bc, 0x1bd,
    0x1bd, 0x1be, 0x1bf, 0x1bf, 0x1c0, 0x1c1, 0x1c1, 0x1c2,
    0x1c3, 0x1c3, 0x1c4, 0x1c5, 0x1c5, 0x1c6, 0x1c7, 0x1c7,
    0x1c8, 0x1c9, 0x1c9, 0x1ca, 0x1cb, 0x1cb, 0x1cc, 0x1cd,
    0x1cd, 0x1ce, 0x1cf, 0x1cf, 0x1d0, 0x1d1, 0x1d1, 0x1d2,
    0x1d3, 0x1d3, 0x1d4, 0x1d5, 0x1d5, 0x1d6, 0x1d7, 0x1d7,
    0x1d8, 0x1d9, 0x1d9, 0x1da, 0x1db, 0x1db, 0x1dc, 0x1dd,
    0x1dd, 0x1de, 0x1df, 0x1df, 0x1e0, 0x1e1, 0x1e1, 0x1e2,
    0x1e3, 0x1e4, 0x1e4, 0x1e5, 0x1e6, 0x1e6, 0x1e7, 0x1e8,
    0x1e8, 0x1e9, 0x1ea, 0x1ea, 0x1eb, 0x1ec, 0x1ec, 0x1ed,
    0x1ee, 0x1ee, 0x1ef, 0x1f0, 0x1f0, 0x1f1, 0x1f2, 0x1f3,
    0x1f3, 0x1f4, 0x1f5, 0x1f5, 0x1f6, 0x1f7, 0x1f7, 0x1f8,
    0x1f9, 0x1f9, 0x1fa, 0x1fb, 0x1fb, 0x1fc, 0x1fd, 0x1fe,
    0x1fe, 0x1ff, 0x200, 0x200, 0x201, 0x202, 0x202, 0x203,
    0x204, 0x205, 0x205, 0x206, 0x207, 0x207, 0x208, 0x209,
    0x209, 0x20a, 0x20b, 0x20b, 0x20c, 0x20d, 0x20e, 0x20e,
    0x20f, 0x210, 0x210, 0x211, 0x212, 0x212, 0x213, 0x214,
    0x215, 0x215, 0x216, 0x217, 0x217, 0x218, 0x219, 0x21a,
    0x21a, 0x21b, 0x21c, 0x21c, 0x21d, 0x21e, 0x21e, 0x21f,
    0x220, 0x221, 0x221, 0x222, 0x223, 0x223, 0x224, 0x225,
    0x226, 0x226, 0x227, 0x228, 0x228, 0x229, 0x22a, 0x22b,
    0x22b, 0x22c, 0x22d, 0x22d, 0x22e, 0x22f, 0x230, 0x230,
    0x231, 0x232, 0x232, 0x233, 0x234, 0x235, 0x235, 0x236,
    0x237, 0x237, 0x238, 0x239, 0x23a, 0x23a, 0x23b, 0x23c,
    0x23c, 0x23d, 0x23e, 0x23f, 0x23f, 0x240, 0x241, 0x241,
    0x242, 0x243, 0x244, 0x244, 0x245, 0x246, 0x247, 0x247,
    0x248, 0x249, 0x249, 0x24a, 0x24b, 0x24c, 0x24c, 0x24d,
    0x24e, 0x24f, 0x24f, 0x250, 0x251, 0x251, 0x252, 0x253,
    0x254, 0x254, 0x255, 0x256, 0x257, 0x257, 0x258, 0x259,
    0x259, 0x25a, 0x25b, 0x25c, 0x25c, 0x25d, 0x25e, 0x25f,
    0x25f, 0x260, 0x261, 0x262, 0x262, 0x263, 0x264, 0x265,
    0x265, 0x266, 0x267, 0x267, 0x268, 0x269, 0x26a, 0x26a,
    0x26b, 0x26c, 0x26d, 0x26d, 0x26e, 0x26f, 0x270, 0x270,
    0x271, 0x272, 0x273, 0x273, 0x274, 0x275, 0x276, 0x276,
    0x277, 0x278, 0x279, 0x279, 0x27a, 0x27b, 0x27c, 0x27c,
    0x27d, 0x27e, 0x27f, 0x27f, 0x280, 0x281, 0x282, 0x282,
    0x283, 0x284, 0x285, 0x285, 0x286, 0x287, 0x288, 0x288,
    0x289, 0x28a, 0x28b, 0x28b, 0x28c, 0x28d, 0x28e, 0x28e,
    0x28f, 0x290, 0x291, 0x291, 0x292, 0x293, 0x294, 0x294,
    0x295, 0x296, 0x297, 0x298, 0x298, 0x299, 0x29a, 0x29b,
    0x29b, 0x29c, 0x29d, 0x29e, 0x29e, 0x29f, 0x2a0, 0x2a1,
    0x2a1, 0x2a2, 0x2a3, 0x2a4, 0x2a5, 0x2a5, 0x2a6, 0x2a7,
    0x2a8, 0x2a8, 0x2a9, 0x2aa, 0x2ab, 0x2ab, 0x2ac, 0x2ad,
    0x2ae, 0x2af, 0x2af, 0x2b0, 0x2b1, 0x2b2, 0x2b2, 0x2b3,
    0x2b4, 0x2b5, 0x2b5, 0x2b6, 0x2b7, 0x2b8, 0x2b9, 0x2b9,
    0x2ba, 0x2bb, 0x2bc, 0x2bc, 0x2bd, 0x2be, 0x2bf, 0x2c0,
    0x2c0, 0x2c1, 0x2c2, 0x2c3, 0x2c4, 0x2c4, 0x2c5, 0x2c6,
    0x2c7, 0x2c7, 0x2c8, 0x2c9, 0x2ca, 0x2cb, 0x2cb, 0x2cc,
    0x2cd, 0x2ce, 0x2ce, 0x2cf, 0x2d0, 0x2d1, 0x2d2, 0x2d2,
    0x2d3, 0x2d4, 0x2d5, 0x2d6, 0x2d6, 0x2d7, 0x2d8, 0x2d9,
    0x2da, 0x2da, 0x2db, 0x2dc, 0x2dd, 0x2dd, 0x2de, 0x2df,
    0x2e0, 0x2e1, 0x2e1, 0x2e2, 0x2e3, 0x2e4, 0x2e5, 0x2e5,
    0x2e6, 0x2e7, 0x2e8, 0x2e9, 0x2e9, 0x2ea, 0x2eb, 0x2ec,
    0x2ed, 0x2ed, 0x2ee, 0x2ef, 0x2f0, 0x2f1, 0x2f1, 0x2f2,
    0x2f3, 0x2f4, 0x2f5, 0x2f5, 0x2f6, 0x2f7, 0x2f8, 0x2f9,
    0x2f9, 0x2fa, 0x2fb, 0x2fc, 0x2fd, 0x2fd, 0x2fe, 0x2ff,
    0x300, 0x301, 0x302, 0x302, 0x303, 0x304, 0x305, 0x306,
    0x306, 0x307, 0x308, 0x309, 0x30a, 0x30a, 0x30b, 0x30c,
    0x30d, 0x30e, 0x30f, 0x30f, 0x310, 0x311, 0x312, 0x313,
    0x313, 0x314, 0x315, 0x316, 0x317, 0x318, 0x318, 0x319,
    0x31a, 0x31b, 0x31c, 0x31c, 0x31d, 0x31e, 0x31f, 0x320,
    0x321, 0x321, 0x322, 0x323, 0x324, 0x325, 0x326, 0x326,
    0x327, 0x328, 0x329, 0x32a, 0x32a, 0x32b, 0x32c, 0x32d,
    0x32e, 0x32f, 0x32f, 0x330, 0x331, 0x332, 0x333, 0x334,
    0x334, 0x335, 0x336, 0x337, 0x338, 0x339, 0x339, 0x33a,
    0x33b, 0x33c, 0x33d, 0x33e, 0x33e, 0x33f, 0x340, 0x341,
    0x342, 0x343, 0x343, 0x344, 0x345, 0x346, 0x347, 0x348,
    0x349, 0x349, 0x34a, 0x34b, 0x34c, 0x34d, 0x34e, 0x34e,
    0x34f, 0x350, 0x351, 0x352, 0x353, 0x353, 0x354, 0x355,
    0x356, 0x357, 0x358, 0x359, 0x359, 0x35a, 0x35b, 0x35c,
    0x35d, 0x35e, 0x35f, 0x35f, 0x360, 0x361, 0x362, 0x363,
    0x364, 0x364, 0x365, 0x366, 0x367, 0x368, 0x369, 0x36a,
    0x36a, 0x36b, 0x36c, 0x36d, 0x36e, 0x36f, 0x370, 0x370,
    0x371, 0x372, 0x373, 0x374, 0x375, 0x376, 0x377, 0x377,
    0x378, 0x379, 0x37a, 0x37b, 0x37c, 0x37d, 0x37d, 0x37e,
    0x37f, 0x380, 0x381, 0x382, 0x383, 0x383, 0x384, 0x385,
    0x386, 0x387, 0x388, 0x389, 0x38a, 0x38a, 0x38b, 0x38c,
    0x38d, 0x38e, 0x38f, 0x390, 0x391, 0x391, 0x392, 0x393,
    0x394, 0x395, 0x396, 0x397, 0x398, 0x398, 0x399, 0x39a,
    0x39b, 0x39c, 0x39d, 0x39e, 0x39f, 0x39f, 0x3a0, 0x3a1,
    0x3a2, 0x3a3, 0x3a4, 0x3a5, 0x3a6, 0x3a7, 0x3a7, 0x3a8,
    0x3a9, 0x3aa, 0x3ab, 0x3ac, 0x3ad, 0x3ae, 0x3ae, 0x3af,
    0x3b0, 0x3b1, 0x3b2, 0x3b3, 0x3b4, 0x3b5, 0x3b6, 0x3b6,
    0x3b7, 0x3b8, 0x3b9, 0x3ba, 0x3bb, 0x3bc, 0x3bd, 0x3be,
    0x3bf, 0x3bf, 0x3c0, 0x3c1, 0x3c2, 0x3c3, 0x3c4, 0x3c5,
    0x3c6, 0x3c7, 0x3c7, 0x3c8, 0x3c9, 0x3ca, 0x3cb, 0x3cc,
    0x3cd, 0x3ce, 0x3cf, 0x3d0, 0x3d1, 0x3d1, 0x3d2, 0x3d3,
    0x3d4, 0x3d5, 0x3d6, 0x3d7, 0x3d8, 0x3d9, 0x3da, 0x3da,
    0x3db, 0x3dc, 0x3dd, 0x3de, 0x3df, 0x3e0, 0x3e1, 0x3e2,
    0x3e3, 0x3e4, 0x3e4, 0x3e5, 0x3e6, 0x3e7, 0x3e8, 0x3e9,
    0x3ea, 0x3eb, 0x3ec, 0x3ed, 0x3ee, 0x3ef, 0x3ef, 0x3f0,
    0x3f1, 0x3f2, 0x3f3, 0x3f4, 0x3f5, 0x3f6, 0x3f7, 0x3f8,
    0x3f9, 0x3fa, 0x3fa, 0x3fb, 0x3fc, 0x3fd, 0x3fe, 0x3ff
};

/*
 * Attenuation according to GM recommendations, in -0.375 dB units.
 * table[v] = 40 * log(v / 127) / -0.375
 */
static const uint8_t g_volume_table[128] = {
    255, 224, 192, 173, 160, 150, 141, 134,
    128, 122, 117, 113, 109, 105, 102, 99,
    96, 93, 90, 88, 85, 83, 81, 79,
    77, 75, 73, 71, 70, 68, 67, 65,
    64, 62, 61, 59, 58, 57, 56, 54,
    53, 52, 51, 50, 49, 48, 47, 46,
    45, 44, 43, 42, 41, 40, 39, 39,
    38, 37, 36, 35, 34, 34, 33, 32,
    31, 31, 30, 29, 29, 28, 27, 27,
    26, 25, 25, 24, 24, 23, 22, 22,
    21, 21, 20, 19, 19, 18, 18, 17,
    17, 16, 16, 15, 15, 14, 14, 13,
    13, 12, 12, 11, 11, 10, 10, 9,
    9, 9, 8, 8, 7, 7, 6, 6,
    6, 5, 5, 4, 4, 4, 3, 3,
    2, 2, 2, 1, 1, 0, 0, 0
};

#define BUFFER_SEGMENTS 10
#define RENDER_RATE     (48000 / 100)

typedef struct opl4_midi {
    fm_drv_t          opl4;
    MIDI_CHANNEL_DATA midi_channel_data[16];
    VOICE_DATA        voice_data[24];
    int16_t           buffer[(48000 / 100) * 2 * BUFFER_SEGMENTS];
    float             buffer_float[(48000 / 100) * 2 * BUFFER_SEGMENTS];
    uint32_t          midi_pos;
    bool              on;
    atomic_bool       gen_in_progress;
    thread_t         *thread;
    event_t          *wait_event;
} opl4_midi_t;

static opl4_midi_t *opl4_midi_cur;

static void
opl4_write_wave_register(const uint8_t reg, const uint8_t value, opl4_midi_t *opl4_midi)
{
    opl4_midi->opl4.write(0x380, reg, opl4_midi->opl4.priv);
    opl4_midi->opl4.write(0x381, value, opl4_midi->opl4.priv);
}

VOICE_DATA *
get_voice(const YRW801_WAVE_DATA *wave_data, opl4_midi_t *opl4_midi)
{
    VOICE_DATA *free_voice   = &opl4_midi->voice_data[0];
    VOICE_DATA *oldest_voice = &opl4_midi->voice_data[0];
    for (uint8_t voice = 0; voice < 24; voice++) {
        if (opl4_midi_cur->voice_data[voice].is_active) {
            if (opl4_midi_cur->voice_data[voice].activated < oldest_voice->activated)
                oldest_voice = &opl4_midi_cur->voice_data[voice];
        } else {
            if (opl4_midi_cur->voice_data[voice].wave_data == wave_data) {
                free_voice = &opl4_midi_cur->voice_data[voice];
                break;
            }

            if (opl4_midi_cur->voice_data[voice].activated < free_voice->activated) {
                free_voice = &opl4_midi_cur->voice_data[voice];
                break;
            }
        }
    }

    /* If no free voice is found, look for one with the same instrument */
    if (free_voice->is_active) {
        for (uint8_t voice = 0; voice < 24; voice++) {
            if (opl4_midi_cur->voice_data[voice].is_active
            && opl4_midi_cur->voice_data[voice].wave_data == wave_data) {
                free_voice            = &opl4_midi_cur->voice_data[voice];
                free_voice->is_active = 0;
                free_voice->activated = 0;

                free_voice->reg_misc &= ~OPL4_KEY_ON_BIT;
                opl4_write_wave_register(OPL4_REG_MISC + free_voice->number, free_voice->reg_misc, opl4_midi);
                return free_voice;
            }
        }
    }

    /* If still no free voice found, deactivate the 'oldest' */
    if (free_voice->is_active) {
        free_voice            = oldest_voice;
        free_voice->is_active = 0;
        free_voice->activated = 0;

        free_voice->reg_misc &= ~OPL4_KEY_ON_BIT;
        opl4_write_wave_register(OPL4_REG_MISC + free_voice->number, free_voice->reg_misc, opl4_midi);
    }

    return free_voice;
}

static void
update_pan(VOICE_DATA *voice, opl4_midi_t *opl4_midi)
{
    int8_t pan = voice->wave_data->panpot;

    if (!voice->midi_channel->drum_channel)
        pan += voice->midi_channel->panpot;
    if (pan < -7)
        pan = -7;
    else if (pan > 7)
        pan = 7;

    voice->reg_misc = (voice->reg_misc & ~OPL4_PAN_POT_MASK) | (pan & OPL4_PAN_POT_MASK);
    opl4_write_wave_register(OPL4_REG_MISC + voice->number, voice->reg_misc, opl4_midi);
}

void
update_pitch(VOICE_DATA *voice, uint16_t pitch_bend, opl4_midi_t *opl4_midi)
{
    int32_t pitch = voice->midi_channel->drum_channel ? 0 : (voice->note - 60) * 128;
    pitch         = pitch * (int) voice->wave_data->key_scaling / 100;
    pitch         = pitch + 7680;
    pitch += voice->wave_data->pitch_offset;
    pitch += pitch_bend * 256 / 0x2000;
    if (pitch < 0)
        pitch = 0;
    else if (pitch > 0x5FFF)
        pitch = 0x5FFF;

    int8_t   octave  = pitch / 0x600 - 8;
    uint16_t fnumber = g_wave_pitch_map[pitch % 0x600];

    opl4_write_wave_register(OPL4_REG_OCTAVE + voice->number, (octave << 4) | ((fnumber >> 7) & 0x07), opl4_midi);
    voice->reg_f_number = (voice->reg_f_number & OPL4_TONE_NUMBER_BIT8) | ((fnumber << 1) & OPL4_F_NUMBER_LOW_MASK);
    opl4_write_wave_register(OPL4_REG_F_NUMBER + voice->number, voice->reg_f_number, opl4_midi);
}

void
update_volume(VOICE_DATA *voice, opl4_midi_t *opl4_midi)
{
    int16_t att = voice->wave_data->tone_attenuate;

    att += g_volume_table[voice->velocity];
    att = 0x7F - (0x7F - att) * (voice->wave_data->volume_factor) / 0xFE;
    att -= 16;
    if (att < 0)
        att = 0;
    else if (att > 0x7E)
        att = 0x7E;

    opl4_write_wave_register(OPL4_REG_LEVEL + voice->number, (att << 1) | voice->level_direct, opl4_midi);
    voice->level_direct = 0;
}

void
note_off(uint8_t note, uint8_t velocity, MIDI_CHANNEL_DATA *midi_channel, opl4_midi_t *opl4_midi)
{
    /* Velocity not used */
    (void) velocity;

    while (opl4_midi->gen_in_progress) { }
    for (uint8_t i = 0; i < 24; i++) {
        VOICE_DATA *voice = &opl4_midi->voice_data[i];
        if (voice->is_active && voice->midi_channel == midi_channel && voice->note == note) {
            voice->is_active = false;
            voice->activated = 0;

            voice->reg_misc &= ~OPL4_KEY_ON_BIT;
            opl4_write_wave_register(OPL4_REG_MISC + voice->number, voice->reg_misc, opl4_midi);
        }
    }
}

void
update_vibrato_depth(VOICE_DATA *voice, opl4_midi_t *opl4_midi)
{
    uint16_t depth;

    depth = (7 - voice->wave_data->vibrato) * (voice->midi_channel->vibrato & 0x7F);
    depth = (depth >> 7) + voice->wave_data->vibrato;
    voice->reg_lfo_vibrato &= ~OPL4_VIBRATO_DEPTH_MASK;
    voice->reg_lfo_vibrato |= depth & OPL4_VIBRATO_DEPTH_MASK;
    opl4_write_wave_register(OPL4_REG_LFO_VIBRATO + voice->number, voice->reg_lfo_vibrato, opl4_midi);
}

void
note_on(uint8_t note, uint8_t velocity, MIDI_CHANNEL_DATA *midi_channel, opl4_midi_t *opl4_midi)
{
    const YRW801_REGION_DATA_PTR *region_ptr = &snd_yrw801_regions[0];
    const YRW801_WAVE_DATA       *wave_data[2];
    VOICE_DATA                   *voice[2];
    int                           i      = 0;
    uint8_t                       voices = 0;

    while (opl4_midi->gen_in_progress) { }

    if (midi_channel->drum_channel)
        wave_data[voices++] = &region_ptr[0x80].regions[note - 0x1A].wave_data;
    else {
        /* Determine the number of voices and voice parameters */
        const YRW801_REGION_DATA *region = region_ptr[midi_channel->instrument & 0x7F].regions;

        while (i < region_ptr[midi_channel->instrument & 0x7F].count) {
            if (note >= region[i].key_min && note <= region[i].key_max) {
                wave_data[voices] = &region[i].wave_data;
                if (++voices >= 2)
                    break;
            }
            i++;
        }
    }

    /* Allocate and initialize needed voices */
    for (i = 0; i < voices; i++) {
        voice[i]            = get_voice(wave_data[i], opl4_midi);
        voice[i]->is_active = true;
        voice[i]->activated = plat_get_ticks();

        voice[i]->midi_channel = midi_channel;
        voice[i]->note         = note;
        voice[i]->velocity     = velocity & 0x7F;
    }

    for (i = 0; i < voices; i++) {
        voice[i]->reg_f_number = (wave_data[i]->tone >> 8) & OPL4_TONE_NUMBER_BIT8;
        opl4_write_wave_register(OPL4_REG_F_NUMBER + voice[i]->number, voice[i]->reg_f_number, opl4_midi);

        bool new_wave = (voice[i]->wave_data != wave_data[i]);
        /* Set tone number (triggers header loading) */
        if (new_wave) {
            opl4_write_wave_register(OPL4_REG_TONE_NUMBER + voice[i]->number, wave_data[i]->tone & 0xFF, opl4_midi);
            voice[i]->wave_data = wave_data[i];
        }

        voice[i]->reg_misc = OPL4_LFO_RESET_BIT;
        update_pan(voice[i], opl4_midi);
        update_pitch(voice[i], 0, opl4_midi);
        voice[i]->level_direct = OPL4_LEVEL_DIRECT_BIT;
        update_volume(voice[i], opl4_midi);
        if (new_wave) {
            /* Set remaining parameters */
            opl4_write_wave_register(OPL4_REG_ATTACK_DECAY1 + voice[i]->number, voice[i]->wave_data->reg_attack_decay1, opl4_midi);
            opl4_write_wave_register(OPL4_REG_LEVEL_DECAY2 + voice[i]->number, voice[i]->wave_data->reg_level_decay2, opl4_midi);
            opl4_write_wave_register(OPL4_REG_RELEASE_CORRECTION + voice[i]->number, voice[i]->wave_data->reg_release_correction, opl4_midi);
            opl4_write_wave_register(OPL4_REG_TREMOLO + voice[i]->number, voice[i]->wave_data->reg_tremolo, opl4_midi);

            voice[i]->reg_lfo_vibrato = voice[i]->wave_data->reg_lfo_vibrato;

            if (!midi_channel->drum_channel)
                update_vibrato_depth(voice[i], opl4_midi);
        }
    }

    /* Finally, switch on all voices */
    for (i = 0; i < voices; i++) {
        voice[i]->reg_misc = (voice[i]->reg_misc & 0x1F) | OPL4_KEY_ON_BIT;
        opl4_write_wave_register(OPL4_REG_MISC + voice[i]->number, voice[i]->reg_misc, opl4_midi);
    }
}

void
control_change(uint8_t midi_channel, uint8_t id, uint8_t value, opl4_midi_t *opl4_midi)
{
    switch (id) {
        case 10:
            /* Change stereo panning */
            if (midi_channel != DRUM_CHANNEL) {
                opl4_midi->midi_channel_data[midi_channel].panpot = (value - 0x40) >> 3;
                for (uint8_t i = 0; i < NR_OF_WAVE_CHANNELS; i++) {
                    if (opl4_midi->voice_data[i].is_active && opl4_midi->voice_data[i].midi_channel == &opl4_midi->midi_channel_data[midi_channel]) {
                        update_pan(&opl4_midi->voice_data[i], opl4_midi);
                    }
                }
            }
            break;
    }
}

void
pitch_wheel(uint8_t midi_channel, uint16_t value, opl4_midi_t *opl4_midi)
{
    for (uint8_t i = 0; i < 24; i++) {
        if (opl4_midi->voice_data[i].is_active && opl4_midi->voice_data[i].midi_channel == &opl4_midi->midi_channel_data[midi_channel]) {
            update_pitch(&opl4_midi->voice_data[i], value, opl4_midi);
        }
    }
}

void
channel_pressure(uint8_t midi_channel, uint8_t pressure)
{
    (void) midi_channel;
    (void) pressure;
}

void
key_pressure(uint8_t midi_channel, uint8_t note, uint8_t pressure)
{
    (void) midi_channel;
    (void) note;
    (void) pressure;
}

void
program_change(uint8_t midi_channel, uint8_t program, opl4_midi_t *opl4_midi)
{
    opl4_midi->midi_channel_data[midi_channel].instrument = program;
}

static void
opl4_midi_thread(UNUSED(void *arg))
{
    opl4_midi_t *opl4_midi         = opl4_midi_cur;
    uint32_t     i                 = 0;
    uint32_t     buf_size          = RENDER_RATE * 2;
    uint32_t     buf_size_segments = buf_size * BUFFER_SEGMENTS;
    uint32_t     buf_pos           = 0;

    int32_t buffer[RENDER_RATE * 2];

    extern void givealbuffer_midi(void *buf, uint32_t size);
    while (opl4_midi->on) {
        thread_wait_event(opl4_midi->wait_event, -1);
        thread_reset_event(opl4_midi->wait_event);
        if (!opl4_midi->on)
            break;
        atomic_store(&opl4_midi->gen_in_progress, true);
        opl4_midi->opl4.generate(opl4_midi->opl4.priv, buffer, RENDER_RATE);
        atomic_store(&opl4_midi->gen_in_progress, false);
        if (sound_is_float) {
            for (i = 0; i < (buf_size / 2); i++) {
                opl4_midi->buffer_float[(i + buf_pos) * 2]       = buffer[i * 2] / 32768.0;
                opl4_midi->buffer_float[((i + buf_pos) * 2) + 1] = buffer[(i * 2) + 1] / 32768.0;

                /* Apply sound card MIDI volume and filters */
                if (filter_midi != NULL) {
                    double dl = (double) opl4_midi->buffer_float[(i + buf_pos) * 2];
                    double dr = (double) opl4_midi->buffer_float[((i + buf_pos) * 2) + 1];
                    filter_midi(0, &dl, filter_midi_p);
                    filter_midi(1, &dr, filter_midi_p);
                    opl4_midi->buffer_float[(i + buf_pos) * 2] = (float) dl;
                    opl4_midi->buffer_float[((i + buf_pos) * 2) + 1] = (float) dr;
                }
            }
            buf_pos += buf_size / 2;
            if (buf_pos >= (buf_size_segments / 2)) {
                givealbuffer_midi(opl4_midi->buffer_float, buf_size_segments);
                buf_pos = 0;
            }
        } else {
            for (i = 0; i < (buf_size / 2); i++) {
                opl4_midi->buffer[(i + buf_pos) * 2]       = buffer[i * 2] & 0xFFFF;       /* Outputs are clamped beforehand. */
                opl4_midi->buffer[((i + buf_pos) * 2) + 1] = buffer[(i * 2) + 1] & 0xFFFF; /* Outputs are clamped beforehand. */

                /* Apply sound card MIDI volume and filters */
                if (filter_midi != NULL) {
                    double dl = (double) opl4_midi->buffer[(i + buf_pos) * 2];
                    double dr = (double) opl4_midi->buffer[((i + buf_pos) * 2) + 1];
                    filter_midi(0, &dl, filter_midi_p);
                    filter_midi(1, &dr, filter_midi_p);
                    opl4_midi->buffer[(i + buf_pos) * 2] = (int16_t) round(dl);
                    opl4_midi->buffer[((i + buf_pos) * 2) + 1] = (int16_t) round(dr);
                }
            }
            buf_pos += buf_size / 2;
            if (buf_pos >= (buf_size_segments / 2)) {
                givealbuffer_midi(opl4_midi->buffer, buf_size_segments);
                buf_pos = 0;
            }
        }
    }
}

static void
opl4_midi_poll(void)
{
    opl4_midi_t *opl4_midi = opl4_midi_cur;
    opl4_midi->midi_pos++;
    if (opl4_midi->midi_pos == RENDER_RATE) {
        opl4_midi->midi_pos = 0;
        thread_set_event(opl4_midi->wait_event);
    }
}

void
opl4_midi_msg(uint8_t *val)
{
    opl4_midi_t *opl4_midi = opl4_midi_cur;

    uint32_t msg         = *(uint32_t *) (val);
    uint8_t  data_byte_1 = msg & 0xFF;
    uint8_t  data_byte_2 = (msg >> 8) & 0xFF;
    uint8_t  data_byte_3 = (msg >> 16) & 0xFF;

    uint8_t midi_channel = data_byte_1 & 0x0F;
    uint8_t midi_command = data_byte_1 >> 4;

    switch (midi_command) {
        case 0x8: // Note OFF
            note_off(data_byte_2 & 0x7F, data_byte_3, &opl4_midi->midi_channel_data[midi_channel], opl4_midi);
            break;
        case 0x9: // Note ON
            note_on(data_byte_2 & 0x7F, data_byte_3, &opl4_midi->midi_channel_data[midi_channel], opl4_midi);
            break;
        case 0xA: // Key after-touch
            break;
        case 0xB: // Control change
            control_change(midi_channel, data_byte_2, data_byte_3, opl4_midi);
            break;
        case 0xC: // Program change
            program_change(midi_channel, data_byte_2 & 0x7F, opl4_midi);
            break;
        case 0xD: // Channel after-touch
            break;
        case 0xE: // Pitch wheel
            pitch_wheel(midi_channel, ((data_byte_3 <<= 7) | data_byte_2), opl4_midi);
            break;
    }
}

void
opl4_midi_sysex(UNUSED(uint8_t *data), UNUSED(unsigned int len))
{
    //
}

void *
opl4_init(UNUSED(const device_t *info))
{
    midi_device_t *dev;
    extern void    al_set_midi(int freq, int buf_size);

    dev = calloc(1, sizeof(midi_device_t));

    dev->play_msg   = opl4_midi_msg;
    dev->play_sysex = opl4_midi_sysex;
    dev->poll       = opl4_midi_poll;

    al_set_midi(48000, 4800);

    opl4_midi_cur = calloc(1, sizeof(opl4_midi_t));

    fm_driver_get(FM_YMF278B, &opl4_midi_cur->opl4);

    opl4_midi_cur->opl4.write(0x38A, 0x05, opl4_midi_cur->opl4.priv);
    opl4_midi_cur->opl4.write(0x389, 0x3, opl4_midi_cur->opl4.priv);
    midi_out_init(dev);

    opl4_midi_cur->on                                = true;
    opl4_midi_cur->midi_channel_data[9].drum_channel = true;
    atomic_init(&opl4_midi_cur->gen_in_progress, 0);

    for (uint8_t voice = 0; voice < NR_OF_WAVE_CHANNELS; voice++) {
        opl4_midi_cur->voice_data[voice].number          = voice;
        opl4_midi_cur->voice_data[voice].is_active       = false;
        opl4_midi_cur->voice_data[voice].activated       = 0;
        opl4_midi_cur->voice_data[voice].midi_channel    = NULL;
        opl4_midi_cur->voice_data[voice].note            = 0;
        opl4_midi_cur->voice_data[voice].velocity        = 0;
        opl4_midi_cur->voice_data[voice].wave_data       = NULL;
        opl4_midi_cur->voice_data[voice].level_direct    = 0;
        opl4_midi_cur->voice_data[voice].reg_f_number    = 0;
        opl4_midi_cur->voice_data[voice].reg_misc        = 0;
        opl4_midi_cur->voice_data[voice].reg_lfo_vibrato = 0;
    }
    opl4_midi_cur->wait_event = thread_create_event();
    opl4_midi_cur->thread     = thread_create(opl4_midi_thread, NULL);
    return dev;
}

void
opl4_close(void *p)
{
    if (!p)
        return;

    opl4_midi_cur->on = false;
    thread_set_event(opl4_midi_cur->wait_event);
    thread_wait(opl4_midi_cur->thread);
    free(opl4_midi_cur);
    opl4_midi_cur = NULL;
}

static int
opl4_midi_available(void)
{
    return rom_present("roms/sound/yamaha/yrw801.rom");
}

const device_t opl4_midi_device = {
    .name          = "OPL4-ML Daughterboard",
    .internal_name = "opl4-ml",
    .flags         = 0,
    .local         = 0,
    .init          = opl4_init,
    .close         = opl4_close,
    .reset         = NULL,
    .available     = opl4_midi_available,
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = NULL
};
