// Nintendo Game Boy sound hardware emulator with save state support

// Gb_Snd_Emu 0.2.0
#ifndef GB_APU_H
#define GB_APU_H

#include <cstdint>

#include "core/apu/blargg_common.h"
#include "core/apu/Gb_Oscs.h"

struct gb_apu_state_t;

class Gb_Apu
{
        public:
        // Basics

        // Clock rate that sound hardware runs at.
        enum { clock_rate = 4194304 * GB_APU_OVERCLOCK };

        // Sets buffer(s) to generate sound into. If left and right are NULL, output is mono.
        // If all are NULL, no output is generated but other emulation still runs.
        // If chan is specified, only that channel's output is changed, otherwise all are.
        enum { osc_count = 4 }; // 0: Square 1, 1: Square 2, 2: Wave, 3: Noise
        void set_output(Blip_Buffer *center, Blip_Buffer *left = NULL, Blip_Buffer *right = NULL,
                        int chan = osc_count);

        // Resets hardware to initial power on state BEFORE boot ROM runs. Mode selects
        // sound hardware. Additional AGB wave features are enabled separately.
        enum mode_t {
                mode_dmg, // Game Boy monochrome
                mode_cgb, // Game Boy Color
                mode_agb  // Game Boy Advance
        };
        void reset(mode_t mode = mode_cgb, bool agb_wave = false);

        // Reads and writes must be within the start_addr to end_addr range, inclusive.
        // Addresses outside this range are not mapped to the sound hardware.
        enum { start_addr = 0xFF10 };
        enum { end_addr = 0xFF3F };
        enum { register_count = end_addr - start_addr + 1 };

        // Times are specified as the number of clocks since the beginning of the
        // current time frame.

        // Emulates CPU write of data to addr at specified time.
        void write_register(blip_time_t time, unsigned addr, uint8_t data);

        // Emulates CPU read from addr at specified time.
        uint8_t read_register(blip_time_t time, unsigned addr);

        // Emulates sound hardware up to specified time, ends current time frame, then
        // starts a new frame at time 0.
        void end_frame(blip_time_t frame_length);

        // Sound adjustments

        // Sets overall volume, where 1.0 is normal.
        void volume(double);

        // If true, reduces clicking by disabling DAC biasing. Note that this reduces
        // emulation accuracy, since the clicks are authentic.
        void reduce_clicks(bool reduce = true);

        // Sets treble equalization.
        void treble_eq(blip_eq_t const &);

        // Treble and bass values for various hardware.
        enum { speaker_treble = -47, // speaker on system
               speaker_bass = 2000,
               dmg_treble = 0, // headphones on each system
               dmg_bass = 30,
               cgb_treble = 0,
               cgb_bass = 300, // CGB has much less bass
               agb_treble = 0,
               agb_bass = 30 };

        // Sets frame sequencer rate, where 1.0 is normal. Meant for adjusting the
        // tempo in a game music player.
        void set_tempo(double);

        // Save states

        // Saves full emulation state to state_out. Data format is portable and
        // includes some extra space to avoid expansion in case more state needs
        // to be stored in the future.
        void save_state(gb_apu_state_t *state_out);

        // Loads state. You should call reset() BEFORE this.
        blargg_err_t load_state(gb_apu_state_t const &in);

        public:
        Gb_Apu();

        // Use set_output() in place of these
        BLARGG_DEPRECATED void output(Blip_Buffer *c)
        {
                set_output(c, c, c);
        }
        BLARGG_DEPRECATED void output(Blip_Buffer *c, Blip_Buffer *l, Blip_Buffer *r)
        {
                set_output(c, l, r);
        }
        BLARGG_DEPRECATED void osc_output(int i, Blip_Buffer *c)
        {
                set_output(c, c, c, i);
        }
        BLARGG_DEPRECATED void osc_output(int i, Blip_Buffer *c, Blip_Buffer *l, Blip_Buffer *r)
        {
                set_output(c, l, r, i);
        }

        private:
        // noncopyable
        Gb_Apu(const Gb_Apu &);
        Gb_Apu &operator=(const Gb_Apu &);

        Gb_Osc *oscs[osc_count];
        blip_time_t last_time;    // time sound emulator has been run to
        blip_time_t frame_period; // clocks between each frame sequencer step
        double volume_;
        bool reduce_clicks_;

        Gb_Sweep_Square square1;
        Gb_Square square2;
        Gb_Wave wave;
        Gb_Noise noise;
        blip_time_t frame_time; // time of next frame sequencer action
        int frame_phase;        // phase of next frame sequencer step
        enum { regs_size = register_count + 0x10 };
        uint8_t regs[regs_size]; // last values written to registers

        // large objects after everything else
        Gb_Osc::Good_Synth good_synth;
        Gb_Osc::Med_Synth med_synth;

        void reset_lengths();
        void reset_regs();
        int calc_output(int osc) const;
        void apply_stereo();
        void apply_volume();
        void synth_volume(int);
        void run_until_(blip_time_t);
        void run_until(blip_time_t);
        void silence_osc(Gb_Osc &);
        void write_osc(int index, int reg, int old_data, int data);
        const char *save_load(gb_apu_state_t *, bool save);
        void save_load2(gb_apu_state_t *, bool save);
        friend class Gb_Apu_Tester;
};

// Format of save state. Should be stable across versions of the library,
// with earlier versions properly opening later save states. Includes some
// room for expansion so the state size shouldn't increase.
struct gb_apu_state_t {
#if GB_APU_CUSTOM_STATE
        // Values stored as plain int so your code can read/write them easily.
        // Structure can NOT be written to disk, since format is not portable.
        typedef int val_t;
#else
        // Values written in portable little-endian format, allowing structure
        // to be written directly to disk.
        typedef unsigned char val_t[4];
#endif

        enum { format0 = 0x50414247 };

        val_t format;  // format of all following data
        val_t version; // later versions just add fields to end

        unsigned char regs[0x40];
        val_t frame_time;
        val_t frame_phase;

        val_t sweep_freq;
        val_t sweep_delay;
        val_t sweep_enabled;
        val_t sweep_neg;
        val_t noise_divider;
        val_t wave_buf;

        val_t delay[4];
        val_t length_ctr[4];
        val_t phase[4];
        val_t enabled[4];

        val_t env_delay[3];
        val_t env_volume[3];
        val_t env_enabled[3];

        val_t unused[13]; // for future expansion
};

#endif
