/*

============================================================================

NDS sound emulator
by cam900

This file is licensed under zlib license.

============================================================================

zlib License

(C) 2024-present cam900 and contributors

This software is provided 'as-is', without any express or implied
warranty.  In no event will the authors be held liable for any damages
arising from the use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not
   claim that you wrote the original software. If you use this software
   in a product, an acknowledgment in the product documentation would be
   appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
   misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.

============================================================================
TODO:
- needs to further verifications from real hardware

Tech info: https://problemkaputt.de/gbatek.htm

*/

#ifndef NDS_SOUND_EMU_H
#define NDS_SOUND_EMU_H

namespace nds_sound_emu
{
	using u8 = unsigned char;
	using u16 = unsigned short;
	using u32 = unsigned int;
	using u64 = unsigned long long;
	using s8 = signed char;
	using s16 = signed short;
	using s32 = signed int;
	using s64 = signed long long;

	template<typename T>
	static const inline T bitfield(const T in, const u8 pos)
	{
		return (in >> pos) & 1;
	} // bitfield

	template<typename T>
	static const inline T bitfield(const T in, const u8 pos, const u8 len)
	{
		return (in >> pos) & ((1 << len) - 1);
	} // bitfield

	template<typename T>
	static const inline T clamp(const T in, const T min, const T max)
	{
		return (in < min) ? min : ((in > max) ? max : in);
	} // clamp

	class nds_sound_intf
	{
		public:
			nds_sound_intf()
			{
			}

			virtual u8 read_byte(u32 addr) { return 0; }
			inline u16 read_word(u32 addr) { return read_byte(addr) | (u16(read_byte(addr + 1)) << 8); }
			inline u32 read_dword(u32 addr) { return read_word(addr) | (u16(read_word(addr + 2)) << 16); }

			virtual void write_byte(u32 addr, u8 data) {}
			inline void write_word(u32 addr, u16 data)
			{
				write_byte(addr, data & 0xff);
				write_byte(addr + 1, data >> 8);
			}
			inline void write_dword(u32 addr, u32 data)
			{
				write_word(addr, data & 0xffff);
				write_word(addr + 2, data >> 16);
			}
	};

	class nds_sound_t
	{
		public:
			nds_sound_t(nds_sound_intf &intf)
				: m_intf(intf)
				, m_channel{
					channel_t(*this, false, false), channel_t(*this, false, false),
					channel_t(*this, false, false), channel_t(*this, false, false), 
					channel_t(*this, false, false), channel_t(*this, false, false),
					channel_t(*this, false, false), channel_t(*this, false, false), 
					channel_t(*this, true, false), channel_t(*this, true, false),
					channel_t(*this, true, false), channel_t(*this, true, false), 
					channel_t(*this, true, false), channel_t(*this, true, false),
					channel_t(*this, false, true), channel_t(*this, false, true)
				}
				, m_capture{
					capture_t(*this, m_channel[0], m_channel[1]),
					capture_t(*this, m_channel[2], m_channel[3])
				}
				, m_control(0)
				, m_bias(0)
				, m_loutput(0)
				, m_routput(0)
			{
			}

			void reset();
			void tick(s32 cycle);

			// host accesses
			u32 read32(u32 addr);
			void write32(u32 addr, u32 data, u32 mask = ~0);

			u16 read16(u32 addr);
			void write16(u32 addr, u16 data, u16 mask = ~0);

			u8 read8(u32 addr);
			void write8(u32 addr, u8 data);

			s32 loutput() { return m_loutput; }
			s32 routput() { return m_routput; }

			// for debug
			s32 chan_lout(u8 ch) { return m_channel[ch].loutput(); }
			s32 chan_rout(u8 ch) { return m_channel[ch].routput(); }

		private:
			// ADPCM tables
			s8 adpcm_index_table[8] =
			{
				-1, -1, -1, -1, 2, 4, 6, 8
			};

			u16 adpcm_diff_table[89] =
			{
				0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x0010,
				0x0011, 0x0013, 0x0015, 0x0017, 0x0019, 0x001c, 0x001f, 0x0022, 0x0025,
				0x0029, 0x002d, 0x0032, 0x0037, 0x003c, 0x0042, 0x0049, 0x0050, 0x0058,
				0x0061, 0x006b, 0x0076, 0x0082, 0x008f, 0x009d, 0x00ad, 0x00be, 0x00d1,
				0x00e6, 0x00fd, 0x0117, 0x0133, 0x0151, 0x0173, 0x0198, 0x01c1, 0x01ee,
				0x0220, 0x0256, 0x0292, 0x02d4, 0x031c, 0x036c, 0x03c3, 0x0424, 0x048e,
				0x0502, 0x0583, 0x0610, 0x06ab, 0x0756, 0x0812, 0x08e0, 0x09c3, 0x0abd,
				0x0bd0, 0x0cff, 0x0e4c, 0x0fba, 0x114c, 0x1307, 0x14ee, 0x1706, 0x1954,
				0x1bdc, 0x1ea5, 0x21b6, 0x2515, 0x28ca, 0x2cdf, 0x315b, 0x364b, 0x3bb9,
				0x41b2, 0x4844, 0x4f7e, 0x5771, 0x602f, 0x69ce, 0x7462, 0x7fff
			};

			// structs
			enum
			{
				STATE_ADPCM_LOAD = 0,
				STATE_PRE_LOOP,
				STATE_POST_LOOP
			};

			class channel_t 
			{
				public:
					channel_t(nds_sound_t &host, bool psg, bool noise)
						: m_host(host)

						, m_psg(psg)
						, m_noise(noise)

						, m_control(0)
						, m_sourceaddr(0)
						, m_freq(0)
						, m_loopstart(0)
						, m_length(0)
						, m_playing(false)
						, m_adpcm_out(0)
						, m_adpcm_index(0)
						, m_prev_adpcm_out(0)
						, m_prev_adpcm_index(0)
						, m_cur_addr(0)
						, m_cur_state(0)
						, m_cur_bitaddr(0)
						, m_delay(0)
						, m_sample(0)
						, m_lfsr(0x7fff)
						, m_lfsr_out(0x7fff)
						, m_counter(0x10000)
						, m_output(0)
						, m_loutput(0)
						, m_routput(0)
					{
					}

					void reset();
					void write(u32 offset, u32 data, u32 mask = ~0);

					void update(s32 cycle);

					// getters
					// control word
					u32 control() const { return m_control; }
					u32 freq() const    { return m_freq; }

					// outputs
					s32 output() const  { return m_output; }
					s32 loutput() const { return m_loutput; }
					s32 routput() const { return m_routput; }

				private:
					// inline constants
					const u8 m_voldiv_shift[4] = {0, 1, 2, 4};

					// control bits
					s32 volume() const  { return bitfield(m_control, 0, 7); } // global volume
					u32 voldiv() const  { return m_voldiv_shift[bitfield(m_control, 8, 2)]; } // volume shift
					bool hold() const   { return bitfield(m_control, 15); } // hold bit
					u32 pan() const     { return bitfield(m_control, 16, 7); } // panning (0...127, 0 = left, 127 = right, 64 = half)
					u32 duty() const    { return bitfield(m_control, 24, 3); } // PSG duty
					u32 repeat() const  { return bitfield(m_control, 27, 2); } // Repeat mode (Manual, Loop infinitely, One-shot)
					u32 format() const  { return bitfield(m_control, 29, 2); } // Sound Format (PCM8, PCM16, ADPCM, PSG/Noise when exists)
					bool busy() const   { return bitfield(m_control, 31); } // Busy flag

					// calculated values
					s32 lvol() const    { return (pan() == 0x7f) ? 0 : 128 - pan(); } // calculated left volume
					s32 rvol() const    { return (pan() == 0x7f) ? 128 : pan(); } // calculated right volume

					// calculated address
					u32 addr() const    { return (m_sourceaddr & ~3) + (m_cur_bitaddr >> 3) + (m_cur_state == STATE_POST_LOOP ? ((m_loopstart + m_cur_addr) << 2) : (m_cur_addr << 2)); }

					void keyon();
					void keyoff();
					void fetch();
					void advance();

					// interfaces
					nds_sound_t &m_host; // host device

					// configuration
					bool m_psg             = false;   // PSG Enable
					bool m_noise           = false;   // Noise Enable

					// registers
					u32 m_control    = 0; // Control
					u32 m_sourceaddr = 0; // Source Address
					u16 m_freq       = 0; // Frequency
					u16 m_loopstart  = 0; // Loop Start
					u32 m_length     = 0; // Length

					// internal states
					bool m_playing         = false;   // playing flag
					s32 m_adpcm_out        = 0;       // current ADPCM sample value
					s32 m_adpcm_index      = 0;       // current ADPCM step
					s32 m_prev_adpcm_out   = 0;       // previous ADPCM sample value
					s32 m_prev_adpcm_index = 0;       // previous ADPCM step
					u32 m_cur_addr         = 0;       // current address
					s32 m_cur_state        = 0;       // current state
					s32 m_cur_bitaddr      = 0;       // bit address
					s32 m_delay            = 0;       // delay
					s16 m_sample           = 0;       // current sample
					u32 m_lfsr             = 0x7fff;  // noise LFSR
					s16 m_lfsr_out         = 0x7fff;  // LFSR output
					s32 m_counter          = 0x10000; // clock counter
					s32 m_output           = 0;       // current output
					s32 m_loutput          = 0;       // current left output
					s32 m_routput          = 0;       // current right output
			};

			class capture_t
			{
				public:
					capture_t(nds_sound_t &host, channel_t &input, channel_t &output)
						: m_host(host)
						, m_input(input)
						, m_output(output)
						
						, m_control(0)
						, m_dstaddr(0)
						, m_length(0)

						, m_counter(0x10000)
						, m_cur_addr(0)
						, m_cur_waddr(0)
						, m_cur_bitaddr(0)
						, m_enable(0)

						, m_fifo{
							fifo_data_t(), fifo_data_t(), fifo_data_t(), fifo_data_t(),
							fifo_data_t(), fifo_data_t(), fifo_data_t(), fifo_data_t()
						}
						, m_fifo_head(0)
						, m_fifo_tail(0)
						, m_fifo_empty(true)
						, m_fifo_full(false)
					{
					}

					void reset();
					void update(s32 mix, s32 cycle);

					void control_w(u8 data);
					void addrlen_w(u32 offset, u32 data, u32 mask = ~0);

					// getters
					u32 control() const { return m_control; }
					u32 dstaddr() const { return m_dstaddr; }

				private:
					// inline constants
					// control bits
					bool addmode() const    { return bitfield(m_control, 0); } // Add mode (add channel 1/3 output with channel 0/2)
					bool get_source() const { return bitfield(m_control, 1); } // Select source (left or right mixer, channel 0/2)
					bool repeat() const     { return bitfield(m_control, 2); } // repeat flag
					bool format() const     { return bitfield(m_control, 3); } // store format (PCM16, PCM8)
					bool busy() const       { return bitfield(m_control, 7); } // busy flag

					// FIFO offset mask
					u32 fifo_mask() const   { return format() ? 7 : 3; }

					// calculated address
					u32 waddr() const { return (m_dstaddr & ~3) + (m_cur_waddr << 2); }

					void capture_on();
					void capture_off();
					bool fifo_write();

					// interfaces
					nds_sound_t &m_host;      // host device
					channel_t &m_input;       // Input channel
					channel_t &m_output;      // Output channel

					// registers
					u8 m_control  = 0;   // Control
					u32 m_dstaddr = 0;   // Destination Address
					u32 m_length  = 0;   // Buffer Length

					// internal states
					u32 m_counter          = 0x10000; // clock counter
					u32 m_cur_addr         = 0;       // current address
					u32 m_cur_waddr        = 0;       // current write address
					s32 m_cur_bitaddr      = 0;       // bit address
					bool m_enable          = false;   // capture enable

					// FIFO
					class fifo_data_t
					{
						public:
							fifo_data_t()
								: m_data(0)
							{
							}

							void reset()
							{
								m_data = 0;
							}

							// accessors
							void write_byte(const u8 bit, const u8 data)
							{
								u32 input = u32(data) << bit;
								u32 mask = (0xff << bit);
								m_data = (m_data & ~mask) | (input & mask);
							}

							void write_word(const u8 bit, const u16 data)
							{
								u32 input = u32(data) << bit;
								u32 mask = (0xffff << bit);
								m_data = (m_data & ~mask) | (input & mask);
							}

							// getters
							u32 data() const { return m_data; }

						private:
							u32 m_data = 0;
					};

					fifo_data_t m_fifo[8];          // FIFO (8 word, for 16 sample delay)
					u32 m_fifo_head        = 0;     // FIFO head
					u32 m_fifo_tail        = 0;     // FIFO tail
					bool m_fifo_empty      = true;  // FIFO empty flag
					bool m_fifo_full       = false; // FIFO full flag
			};

			nds_sound_intf &m_intf; // memory interface

			channel_t m_channel[16]; // 16 channels
			capture_t m_capture[2]; // 2 capture channels

			inline u8 mvol() const      { return bitfield(m_control, 0, 7); } // master volume
			inline u8 lout_from() const { return bitfield(m_control, 8, 2); } // left output source (mixer, channel 1, channel 3, channel 1+3)
			inline u8 rout_from() const { return bitfield(m_control, 10, 2); } // right output source (mixer, channel 1, channel 3, channel 1+3)
			inline bool mix_ch1() const { return bitfield(m_control, 12); } // mix/bypass channel 1
			inline bool mix_ch3() const { return bitfield(m_control, 13); } // mix/bypass channel 3
			inline bool enable() const  { return bitfield(m_control, 15); } // global enable

			u32 m_control = 0; // global control
			u32 m_bias = 0; // output bias
			s32 m_loutput = 0; // left output
			s32 m_routput = 0; // right output
	};
}; // namespace nds_sound_emu

#endif // NDS_SOUND_EMU_H