#include <array>
#include <cmath>
#include <fstream>
// #include <iostream>
#include <string>

uint16_t note_to_pitch(int note, double note_freq_a4, int tstates, int wavelength, int clock_rate)
{
    constexpr int a4 {69}; // MIDI note number for A4

    const double freq = note_freq_a4 * std::pow(2, (note - a4) / 12.0);

    const double step = freq * tstates * wavelength / clock_rate;
    auto pitch {static_cast<uint16_t>(std::round(step * 256))};

    // negate the pitch value.
    // this is to simplify oscillator sync, where we repeatedly
    // add the pitch and check for carry.
    // in this instance, resetting on nocarry is simpler than resetting on carry.
    pitch = -pitch;

    return pitch;
}

uint16_t unison_note_to_pitch(int note, double note_freq_a4, int tstates, int wavelength, int clock_rate)
{
    constexpr int a4 {69}; // MIDI note number for A4

    const double freq = note_freq_a4 * std::pow(2, (note - a4) / 12.0);

    const double step = freq * tstates * wavelength / clock_rate;
    auto pitch {static_cast<uint8_t>(std::round(step))};

    // for now we just duplicate the high byte
    return pitch | (pitch << 8);
}

int main()
{
    // generate table for 96 notes, from C0 to B7.

    constexpr double note_freq_a4 = 440;  // A4 = 440Hz

    constexpr int tstates = 331; // prev 327, 339, 351
    constexpr double clock_rate = 3.5469e6;

    constexpr int wavelength = 256;

    // std::cout << "============\n";
    // std::cout << "Pitch table:\n";
    // std::cout << "============\n";

    {
        std::fstream pitch_fs;

        pitch_fs.open("pitch_table.bin", std::ios::out | std::ios::binary);

        for (int note {12}; note != 108; ++note)
        {
            uint16_t pitch {note_to_pitch(note, note_freq_a4, tstates, wavelength, clock_rate)};

            // output little endian
            pitch_fs << static_cast<uint8_t>(pitch) << static_cast<uint8_t>(pitch >> 8);

            // std::cout << "Note " << note << " = " << pitch << '\n';
        }
    }

    // std::cout << "=============\n";
    // std::cout << "Unison table:\n";
    // std::cout << "=============\n";

    {
        std::fstream unison_fs;

        unison_fs.open("unison_pitch_table.bin", std::ios::out | std::ios::binary);

        // some notes are unsuitable for bass unison, as they
        // can't be produced with the low precision available,
        // so they are left out
        std::array<int, 47> notes
        {
            28, // E1
            40, // E2
            47, // B2
            52, // E3
            56, // G#3
            59, // B3
            62, // D4
            64, // E4
            66, // F#4
            68, // G#4
            70, // A#4
            71, // B4
            73, // C#5
            74, // D5
            75, // D#5
            76, // E5
            77, // F5
            78, // F#5
            79, // G5
            80, // G#5
            81, // A5
            82, // A#5
            83, // B5
            84, // C6
            85, // C#6
            86, // D6
            87, // D#6
            88, // E6
            89, // F6
            90, // F#6
            91, // G6
            92, // G#6
            93, // A6
            94, // A#6
            95, // B6
            96, // C7
            97, // C#7
            98, // D7
            99, // D#7
            100,// E7
            101,// F7
            102,// F#7
            103,// G7
            104,// G#7
            105,// A7
            106,// A#7
            107 // B7
        };

        for (int note: notes) // from 24 to 119, relative to A4 (69)
        {
            uint16_t pitch {unison_note_to_pitch(note, note_freq_a4, tstates, wavelength, clock_rate)};

            // output little endian
            unison_fs << static_cast<uint8_t>(pitch) << static_cast<uint8_t>(pitch >> 8);

            // std::cout << "Note " << note << " = " << pitch << '\n';
        }
    }

    // std::cout << "Press a key..." << std::endl;
    // std::string s;
    // std::cin >> s;
}
