/*
	License: Zlib
	see https://gitlab.com/cam900/vgsound_emu/-/blob/main/LICENSE for more details

	Copyright holder(s): cam900
	Ensoniq ES5504/ES5505/ES5506 emulation core
*/

#ifndef _VGSOUND_EMU_SRC_ES550X_HPP
#define _VGSOUND_EMU_SRC_ES550X_HPP

#pragma once

#include "../core/core.hpp"
#include "../core/util/clock_pulse.hpp"

#include <string.h>

using namespace vgsound_emu;

// ES5504/ES5505/ES5506 interface
class es550x_intf : public vgsound_emu_core
{
	public:
		es550x_intf()
			: vgsound_emu_core("es550x_intf")
		{
		}

		virtual void e_pin(bool state) {}  // E output

		virtual void bclk(bool state) {}  // BCLK output (serial specific)

		virtual void lrclk(bool state) {}  // LRCLK output (serial specific)

		virtual void wclk(bool state) {}  // WCLK output (serial specific)

		virtual void irqb(bool state) {}  // irqb output

		virtual u16 adc_r() { return 0; }  // ADC input

		virtual void adc_w(u16 data) {}	 // ADC output

		virtual s16 read_sample(u8 bank, u32 address) { return 0; }
};

// Shared functions for ES5504/ES5505/ES5506
class es550x_shared_core : public vgsound_emu_core
{
		friend class es550x_intf;  // es550x specific memory interface

	private:
		const u8 m_max_voices = 32;

	protected:
		// Interrupt bits
		class es550x_irq_t : public vgsound_emu_core
		{
			public:
				es550x_irq_t()
					: vgsound_emu_core("es550x_irq")
					, m_voice(0)
					, m_irqb(1)
				{
				}

				void reset()
				{
					m_voice = 0;
					m_irqb	= 1;
				}

				// setter
				void set(u8 index)
				{
					m_irqb	= 0;
					m_voice = index & 0x1f;
				}

				void clear()
				{
					m_irqb	= 1;
					m_voice = 0;
				}

				// getter
				inline bool irqb() { return m_irqb; }

				inline u8 voice() { return m_voice; }

				inline u8 get() { return (m_irqb << 7) | (m_voice & 0x1f); }

				u8 m_voice : 5;
				u8 m_irqb  : 1;
		};

		// Common voice class
		class es550x_voice_t : public vgsound_emu_core
		{
			private:
				// Common control bits
				class es550x_control_t : public vgsound_emu_core
				{
					public:
						es550x_control_t()
							: vgsound_emu_core("es550x_voice_control")
							, m_ca(0)
							, m_adc(0)
							, m_bs(0)
							, m_cmpd(0)
						{
						}

						void reset()
						{
							m_ca   = 0;
							m_adc  = 0;
							m_bs   = 0;
							m_cmpd = 0;
						}

						// setters
						inline void set_ca(u8 ca) { m_ca = ca & 0xf; }

						inline void set_adc(bool adc) { m_adc = adc ? 1 : 0; }

						inline void set_bs(u8 bs) { m_bs = bs & 0x3; }

						inline void set_cmpd(bool cmpd) { m_cmpd = cmpd ? 1 : 0; }

						// getters
						inline u8 ca() { return m_ca; }

						inline bool adc() { return m_adc; }

						inline u8 bs() { return m_bs; }

						inline bool cmpd() { return m_cmpd; }

					public:
						// Channel assign -
						// 4 bit (16 channel or Bank) for ES5504
						// 2 bit (4 stereo channels) for ES5505
						// 3 bit (6 stereo channels) for ES5506
						u8 m_ca : 4;

						// ES5504 Specific
						u8 m_adc : 1;  // Start ADC

						// ES5505/ES5506 Specific
						u8 m_bs	  : 2;	// Bank bit (1 bit for ES5505, 2 bit for ES5506)
						u8 m_cmpd : 1;	// Use compressed sample format (ES5506)
				};

				// Accumulator
				class es550x_alu_t : public vgsound_emu_core
				{
					public:
						es550x_alu_t(u8 integer, u8 fraction, bool transwave)
							: vgsound_emu_core("es550x_voice_alu")
							, m_integer(integer)
							, m_fraction(fraction)
              , m_fraction_m9(std::max<s8>(0, m_fraction - 9))
							, m_total_bits(integer + fraction)
							, m_accum_mask(
								u32(std::min<u64>(~0, u64(u64(1) << u64(integer + fraction)) - 1)))
							, m_transwave(transwave)
							, m_fc(0)
							, m_start(0)
							, m_end(0)
							, m_accum(0)
              , m_last_accum(0)
              , m_sample{0,0}
						{
						}

						// configurations
						const u8 m_integer	   = 21;
						const u8 m_fraction	   = 11;
            const u8 m_fraction_m9 = 2;
						const u8 m_total_bits  = 32;
						const u32 m_accum_mask = 0xffffffff;
						const bool m_transwave = true;

						// internal states
						void reset();
						bool tick();

						void loop_exec();
						bool busy();
						s32 interpolation();
						u32 get_accum_integer();

						void irq_exec(es550x_intf &intf, es550x_irq_t &irqv, u8 index);

						void irq_update(es550x_intf &intf, es550x_irq_t &irqv)
						{
							intf.irqb(irqv.irqb() ? false : true);
						}

						// setters
						inline void set_stop0(bool stop0) { m_cr.set_stop0(stop0); }

						inline void set_stop1(bool stop1) { m_cr.set_stop1(stop1); }

						inline void set_lpe(bool lpe) { m_cr.set_lpe(lpe); }

						inline void set_ble(bool ble) { m_cr.set_ble(ble); }

						inline void set_irqe(bool irqe) { m_cr.set_irqe(irqe); }

						inline void set_dir(bool dir) { m_cr.set_dir(dir); }

						inline void set_irq(bool irq) { m_cr.set_irq(irq); }

						inline void set_lei(bool lei) { m_cr.set_lei(lei); }

						inline void set_stop(u8 stop) { m_cr.set_stop(stop); }

						inline void set_loop(u8 loop) { m_cr.set_loop(loop); }

						inline void set_fc(u32 fc) { m_fc = fc; }

						inline void set_start(u32 start, u32 mask = ~0)
						{
							m_start = (m_start & ~mask) | (start & mask);
						}

						inline void set_end(u32 end, u32 mask = ~0)
						{
							m_end = (m_end & ~mask) | (end & mask);
						}

						inline void set_accum(u32 accum, u32 mask = ~0)
						{
							m_accum = (m_accum & ~mask) | (accum & mask);
						}

						inline void set_sample(u8 slot, s32 sample) { m_sample[slot & 1] = sample; }

						// getters
						inline bool stop0() { return m_cr.stop0(); }

						inline bool stop1() { return m_cr.stop1(); }

						inline bool lpe() { return m_cr.lpe(); }

						inline bool ble() { return m_cr.ble(); }

						inline bool irqe() { return m_cr.irqe(); }

						inline bool dir() { return m_cr.dir(); }

						inline bool irq() { return m_cr.irq(); }

						inline bool lei() { return m_cr.lei(); }

						inline u8 stop() { return m_cr.stop(); }

						inline u8 loop() { return m_cr.loop(); }

						inline u32 fc() { return m_fc; }

						inline u32 start() { return m_start; }

						inline u32 end() { return m_end; }

						inline u32 accum() { return m_accum; }

						inline s32 sample(u8 slot) { return m_sample[slot & 1]; }

					public:
						class es550x_alu_cr_t : public vgsound_emu_core
						{
							public:
								es550x_alu_cr_t()
									: vgsound_emu_core("es550x_voice_alu_cr")
									, m_stop0(0)
									, m_stop1(0)
									, m_lpe(0)
									, m_ble(0)
									, m_irqe(0)
									, m_dir(0)
									, m_irq(0)
									, m_lei(0)
								{
								}

								void reset()
								{
									m_stop0 = 0;
									m_stop1 = 0;
									m_lpe	= 0;
									m_ble	= 0;
									m_irqe	= 0;
									m_dir	= 0;
									m_irq	= 0;
									m_lei	= 0;
								}

								// setters
								inline void set_stop0(bool stop0) { m_stop0 = stop0 ? 1 : 0; }

								inline void set_stop1(bool stop1) { m_stop1 = stop1 ? 1 : 0; }

								inline void set_lpe(bool lpe) { m_lpe = lpe ? 1 : 0; }

								inline void set_ble(bool ble) { m_ble = ble ? 1 : 0; }

								inline void set_irqe(bool irqe) { m_irqe = irqe ? 1 : 0; }

								inline void set_dir(bool dir) { m_dir = dir ? 1 : 0; }

								inline void set_irq(bool irq) { m_irq = irq ? 1 : 0; }

								inline void set_lei(bool lei) { m_lei = lei ? 1 : 0; }

								inline void set_stop(u8 stop)
								{
									m_stop0 = (stop >> 0) & 1;
									m_stop1 = (stop >> 1) & 1;
								}

								inline void set_loop(u8 loop)
								{
									m_lpe = (loop >> 0) & 1;
									m_ble = (loop >> 1) & 1;
								}

								// getters
								inline bool stop0() { return m_stop0; }

								inline bool stop1() { return m_stop1; }

								inline bool lpe() { return m_lpe; }

								inline bool ble() { return m_ble; }

								inline bool irqe() { return m_irqe; }

								inline bool dir() { return m_dir; }

								inline bool irq() { return m_irq; }

								inline bool lei() { return m_lei; }

								inline u8 stop() { return (m_stop0 << 0) | (m_stop1 << 1); }

								inline u8 loop() { return (m_lpe << 0) | (m_ble << 1); }

							public:
								u8 m_stop0 : 1;	 // Stop with ALU
								u8 m_stop1 : 1;	 // Stop with processor
								u8 m_lpe   : 1;	 // Loop enable
								u8 m_ble   : 1;	 // Bidirectional loop enable
								u8 m_irqe  : 1;	 // IRQ enable
								u8 m_dir   : 1;	 // Playback direction
								u8 m_irq   : 1;	 // IRQ bit
								u8 m_lei   : 1;	 // Loop end ignore (ES5506 specific)
						};

						es550x_alu_cr_t m_cr;
						// Frequency -
						// 6 integer, 9 fraction for ES5504/ES5505
						// 6 integer, 11 fraction for ES5506
						u32 m_fc	= 0;
						u32 m_start = 0;  // Start register
						u32 m_end	= 0;  // End register

						// Accumulator -
						// 20 integer, 9 fraction for ES5504/ES5505
						// 21 integer, 11 fraction for ES5506
						u32 m_accum = 0;
            u32 m_last_accum = 0;
						// Samples
						s32 m_sample[2];
				};

				// Filter
				class es550x_filter_t : public vgsound_emu_core
				{
					public:
						es550x_filter_t()
							: vgsound_emu_core("es550x_voice_filter")
							, m_lp(0)
							, m_k2(0)
							, m_k1(0)
						{
							memset(m_o,0,2*5*sizeof(s32));
						}

						void reset();
						void tick(s32 in);

						// setters
						inline void set_lp(u8 lp) { m_lp = lp & 3; }

						inline void set_k2(s32 k2) { m_k2 = k2; }

						inline void set_k1(s32 k1) { m_k1 = k1; }

						inline void set_o1_1(s32 o1_1) { m_o[1][0] = o1_1; }

						inline void set_o2_1(s32 o2_1) { m_o[2][0] = o2_1; }

						inline void set_o2_2(s32 o2_2) { m_o[2][1] = o2_2; }

						inline void set_o3_1(s32 o3_1) { m_o[3][0] = o3_1; }

						inline void set_o3_2(s32 o3_2) { m_o[3][1] = o3_2; }

						inline void set_o4_1(s32 o4_1) { m_o[4][0] = o4_1; }

						// getters
						inline u8 lp() { return m_lp; }

						inline s32 k2() { return m_k2; }

						inline s32 k1() { return m_k1; }

						inline s32 o1_1() { return m_o[1][0]; }

						inline s32 o2_1() { return m_o[2][0]; }

						inline s32 o2_2() { return m_o[2][1]; }

						inline s32 o3_1() { return m_o[3][0]; }

						inline s32 o3_2() { return m_o[3][1]; }

						inline s32 o4_1() { return m_o[4][0]; }

					private:

						// Registers
						u8 m_lp = 0;  // Filter mode
						// Filter coefficient registers
						// 12 bit for filter calculation, 4
						// LSBs are used for fine control of ramp increment for
						// hardware envelope (ES5506)
						s32 m_k2 = 0;  // Filter coefficient 2
						s32 m_k1 = 0;  // Filter coefficient 1

						// Filter storage registers
						s32 m_o[5][2];
				};

			public:
				es550x_voice_t(std::string tag, u8 integer, u8 fraction, bool transwave)
					: vgsound_emu_core(tag)
					, m_cr(es550x_control_t())
					, m_alu(integer, fraction, transwave)
					, m_filter(es550x_filter_t())
				{
				}

				// internal state
				virtual void reset();
				virtual void fetch(u8 cycle) = 0;
				virtual void tick(u8 voice)			   = 0;

				void irq_update(es550x_intf &intf, es550x_irq_t &irqv)
				{
					m_alu.irq_update(intf, irqv);
				}

				// Getters
				es550x_control_t &cr() { return m_cr; }

				es550x_alu_t &alu() { return m_alu; }

				es550x_filter_t &filter() { return m_filter; }

			protected:
				es550x_control_t m_cr;
				es550x_alu_t m_alu;
				es550x_filter_t m_filter;
		};

		// Host interfaces
		class host_interface_flag_t : public vgsound_emu_core
		{
			public:
				host_interface_flag_t()
					: vgsound_emu_core("es550x_host_interface_flag")
					, m_host_access(0)
					, m_host_access_strobe(0)
					, m_rw(0)
					, m_rw_strobe(0)
				{
				}

				// Accessors
				void reset()
				{
					m_host_access		 = 0;
					m_host_access_strobe = 0;
					m_rw				 = 0;
					m_rw_strobe			 = 0;
				}

				// Setters
				void set_strobe(bool rw)
				{
					m_rw_strobe			 = rw ? 1 : 0;
					m_host_access_strobe = 1;
				}

				void clear_strobe() { m_host_access_strobe = 0; }

				void clear_host_access() { m_host_access = 0; }

				void update_strobe()
				{
					m_rw		  = m_rw_strobe;
					m_host_access = m_host_access_strobe;
				}


			public:
				u8 m_host_access		;  // Host access trigger
				u8 m_host_access_strobe ;  // Host access strobe
				u8 m_rw					;  // R/W state
				u8 m_rw_strobe			;  // R/W strobe
		};

	public:
		// internal state
		virtual void reset();

		virtual void tick() {}

		// clock outputs
		inline bool _cas() { return m_cas.current_edge(); }

		inline bool _cas_rising_edge() { return m_cas.rising_edge(); }

		inline bool _cas_falling_edge() { return m_cas.falling_edge(); }

		inline bool e() { return m_e.current_edge(); }

		inline bool e_rising_edge() { return m_e.rising_edge(); }

		inline bool e_falling_edge() { return m_e.falling_edge(); }

		//-----------------------------------------------------------------
		//
		//	for preview/debug purpose only, not for serious emulators
		//
		//-----------------------------------------------------------------

		// voice cycle
		inline u8 voice_cycle() { return m_voice_cycle; }

		// voice update flag
		inline bool voice_update() { return m_voice_update; }

		inline bool voice_end() { return m_voice_end; }

	protected:
		// constructor
		es550x_shared_core(std::string tag, const u8 voice, es550x_intf &intf)
			: vgsound_emu_core(tag)
			, m_max_voices(voice)
			, m_intf(intf)
			, m_host_intf(host_interface_flag_t())
			, m_ha(0)
			, m_hd(0)
			, m_page(0)
			, m_irqv(es550x_irq_t())
			, m_active(m_max_voices - 1)
			, m_voice_cycle(0)
			, m_voice_fetch(0)
			, m_voice_update(0)
			, m_voice_end(0)
			, m_clkin(clock_pulse_t<s8>(1, 0))
			, m_cas(clock_pulse_t<s8>(2, 1))
			, m_e(clock_pulse_t<s8>(4, 0))
		{
		}

		// Constants
		virtual inline u8 max_voices() { return m_max_voices; }

		// Shared registers, functions
		virtual void voice_tick() {}  // voice tick

		es550x_intf &m_intf;				// es550x specific memory interface
		host_interface_flag_t m_host_intf;	// Host interface flag
		u8 m_ha	  = 0;						// Host address (4 bit)
		u16 m_hd  = 0;		  // Host data (16 bit for ES5504/ES5505, 8 bit for ES5506)
		u8 m_page = 0;		  // Page
		es550x_irq_t m_irqv;  // Voice interrupt vector registers

		// Internal states
		u8 m_active = 31;			  // Activated voices
									  // -1, ~25 for ES5504,
									  // ~32 for ES5505/ES5506
		u8 m_voice_cycle	= 0;	  // Voice cycle
		u8 m_voice_fetch	= 0;	  // Voice fetch cycle
		bool m_voice_update = false;  // Voice update flag
		bool m_voice_end	= false;  // End of one voice cycle flag
		clock_pulse_t<s8> m_clkin;	  // CLKIN clock
		clock_pulse_t<s8> m_cas;	  // /CAS clock (CLKIN / 4), falling edge of
									  // CLKIN trigger this clock
		clock_pulse_t<s8> m_e;		  // E clock (CLKIN / 8),
									  // falling edge of CLKIN trigger this clock
};

#endif
