/*
 * OPL2/OPL3 models library - a set of various conversion models for OPL-family chips
 *
 * Copyright (c) 2025-2026 Vitaly Novichkov <admin@wohlnet.ru>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include "opl_models.h"

/***************************************************************
 *          Audio Interface Library frequency model            *
 ***************************************************************/

static const uint_fast16_t s_ail_freqtable[] =
{
    0x02b2, 0x02b4, 0x02b7, 0x02b9, 0x02bc, 0x02be, 0x02c1, 0x02c3,
    0x02c6, 0x02c9, 0x02cb, 0x02ce, 0x02d0, 0x02d3, 0x02d6, 0x02d8,
    0x02db, 0x02dd, 0x02e0, 0x02e3, 0x02e5, 0x02e8, 0x02eb, 0x02ed,
    0x02f0, 0x02f3, 0x02f6, 0x02f8, 0x02fb, 0x02fe, 0x0301, 0x0303,
    0x0306, 0x0309, 0x030c, 0x030f, 0x0311, 0x0314, 0x0317, 0x031a,
    0x031d, 0x0320, 0x0323, 0x0326, 0x0329, 0x032b, 0x032e, 0x0331,
    0x0334, 0x0337, 0x033a, 0x033d, 0x0340, 0x0343, 0x0346, 0x0349,
    0x034c, 0x034f, 0x0352, 0x0356, 0x0359, 0x035c, 0x035f, 0x0362,
    0x0365, 0x0368, 0x036b, 0x036f, 0x0372, 0x0375, 0x0378, 0x037b,
    0x037f, 0x0382, 0x0385, 0x0388, 0x038c, 0x038f, 0x0392, 0x0395,
    0x0399, 0x039c, 0x039f, 0x03a3, 0x03a6, 0x03a9, 0x03ad, 0x03b0,
    0x03b4, 0x03b7, 0x03bb, 0x03be, 0x03c1, 0x03c5, 0x03c8, 0x03cc,
    0x03cf, 0x03d3, 0x03d7, 0x03da, 0x03de, 0x03e1, 0x03e5, 0x03e8,
    0x03ec, 0x03f0, 0x03f3, 0x03f7, 0x03fb, 0x03fe, 0xfe01, 0xfe03,
    0xfe05, 0xfe07, 0xfe08, 0xfe0a, 0xfe0c, 0xfe0e, 0xfe10, 0xfe12,
    0xfe14, 0xfe16, 0xfe18, 0xfe1a, 0xfe1c, 0xfe1e, 0xfe20, 0xfe21,
    0xfe23, 0xfe25, 0xfe27, 0xfe29, 0xfe2b, 0xfe2d, 0xfe2f, 0xfe31,
    0xfe34, 0xfe36, 0xfe38, 0xfe3a, 0xfe3c, 0xfe3e, 0xfe40, 0xfe42,
    0xfe44, 0xfe46, 0xfe48, 0xfe4a, 0xfe4c, 0xfe4f, 0xfe51, 0xfe53,
    0xfe55, 0xfe57, 0xfe59, 0xfe5c, 0xfe5e, 0xfe60, 0xfe62, 0xfe64,
    0xfe67, 0xfe69, 0xfe6b, 0xfe6d, 0xfe6f, 0xfe72, 0xfe74, 0xfe76,
    0xfe79, 0xfe7b, 0xfe7d, 0xfe7f, 0xfe82, 0xfe84, 0xfe86, 0xfe89,
    0xfe8b, 0xfe8d, 0xfe90, 0xfe92, 0xfe95, 0xfe97, 0xfe99, 0xfe9c,
    0xfe9e, 0xfea1, 0xfea3, 0xfea5, 0xfea8, 0xfeaa, 0xfead, 0xfeaf
};

static const uint_fast8_t s_ail_note_octave[] =
{
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
    0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03,
    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
    0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
    0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05,
    0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
    0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
    0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07,
    0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07
};

static const uint_fast8_t s_ail_note_halftone[] =
{
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x0a, 0x0b, 0x00, 0x01, 0x02, 0x03,
    0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x0a, 0x0b, 0x00, 0x01, 0x02, 0x03,
    0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x0a, 0x0b, 0x00, 0x01, 0x02, 0x03,
    0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
    0x08, 0x09, 0x0a, 0x0b, 0x00, 0x01, 0x02, 0x03,
    0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b
};

uint16_t oplModel_ailFreq(double tone, uint32_t *mul_offset)
{
    uint_fast16_t freq, halftones;
    int_fast32_t note, pitch, octave, octaveOffset = 0;
    double bendDec;

    *mul_offset = 0;

    note = (int_fast32_t)(tone);
    bendDec = tone - (int)tone; /* 0.0 ± 1.0 - one halftone */

    if(bendDec > 0.5)
    {
        note += 1;
        bendDec -= 1.0;
    }

    pitch = (int_fast32_t)(bendDec * 4096) + 8192; /* convert to MIDI standard value */
    pitch = ((pitch - 0x2000) / 0x20) * 2;

    note -= 12;

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

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

    pitch += (((uint_fast8_t)note) << 8) + 8;
    pitch /= 16;

    while(pitch < 12 * 16)
        pitch += 12 * 16;

    while(pitch > 96 * 16 - 1)
        pitch -= 12 * 16;

    halftones = (s_ail_note_halftone[pitch >> 4] << 4) + (pitch & 0x0f);
    freq = s_ail_freqtable[halftones];
    octave = s_ail_note_octave[pitch >> 4];

    if((freq & 0x8000) == 0)
    {
        if (octave > 0)
            octave--;
        else
            freq /= 2;
    }

    freq &= 0x3FF;

    octave += octaveOffset;

    while(octave > 7)
    {
        ++(*mul_offset);
        --octave;
    }

    return freq | (octave << 10);
}




/***************************************************************
 *                    AIL volume formula                       *
 ***************************************************************/

static const uint_fast32_t s_ail_vel_graph[16] =
{
    82,   85,  88,  91,  94,  97, 100, 103,
    106, 109, 112, 115, 118, 121, 124, 127
};

void oplModel_ailVolume(struct OPLVolume_t *v)
{
    uint_fast32_t midiVolume, v0_val = (~v->tlMod) & 0x3f, v1_val = (~v->tlCar) & 0x3f;
    uint_fast8_t vel;

    midiVolume = (v->chVol * v->chExpr) * 2;
    midiVolume >>= 8;

    if(midiVolume != 0)
        midiVolume++;

    vel = (v->vel & 0x7F) >> 3;
    vel = s_ail_vel_graph[vel];

    midiVolume = (midiVolume * vel) * 2;
    midiVolume >>= 8;
    if(midiVolume != 0)
        midiVolume++;

    if(v->masterVolume < 127)
        midiVolume = (midiVolume * v->masterVolume) / 127;

    if(midiVolume > 127)
        midiVolume = 127;

    if(v->doMod)
        v0_val = (v0_val * midiVolume) / 127;

    if(v->doCar)
        v1_val = (v1_val * midiVolume) / 127;

    v->tlMod = (~v0_val) & 0x3F;
    v->tlCar = (~v1_val) & 0x3F;
}
