/*

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

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

*/

#include "nds.hpp"

namespace nds_sound_emu
{
	void nds_sound_t::reset()
	{
		for (channel_t &elem : m_channel)
			elem.reset();
		for (capture_t &elem : m_capture)
			elem.reset();

		m_control = 0;
		m_bias = 0;
		m_loutput = 0;
		m_routput = 0;
	}

	void nds_sound_t::tick(s32 cycle)
	{
		m_loutput = m_routput = (m_bias & 0x3ff);
		if (!enable())
			return;

		// mix outputs
		s32 lmix = 0, rmix = 0;
		for (u8 i = 0; i < 16; i++)
		{
			channel_t &channel = m_channel[i];
			channel.update(cycle);
			// bypass mixer
			if (((i == 1) && (mix_ch1())) || ((i == 3) && (mix_ch3())))
				continue;

			lmix += channel.loutput();
			rmix += channel.routput();
		}

		// send mixer output to capture
		m_capture[0].update(lmix, cycle);
		m_capture[1].update(rmix, cycle);

		// select left/right output source
		switch (lout_from())
		{
		case 0: // left mixer
			break;
		case 1: // channel 1
			lmix = m_channel[1].loutput();
			break;
		case 2: // channel 3
			lmix = m_channel[3].loutput();
			break;
		case 3: // channel 1 + 3
			lmix = m_channel[1].loutput() + m_channel[3].loutput();
			break;
		}

		switch (rout_from())
		{
		case 0: // right mixer
			break;
		case 1: // channel 1
			rmix = m_channel[1].routput();
			break;
		case 2: // channel 3
			rmix = m_channel[3].routput();
			break;
		case 3: // channel 1 + 3
			rmix = m_channel[1].routput() + m_channel[3].routput();
			break;
		}

		// adjust master volume
		lmix = (lmix * mvol()) >> 13;
		rmix = (rmix * mvol()) >> 13;

		// add bias and clip output
		m_loutput = clamp<s32>((lmix + (m_bias & 0x3ff)), 0, 0x3ff);
		m_routput = clamp<s32>((rmix + (m_bias & 0x3ff)), 0, 0x3ff);
	}

	u8 nds_sound_t::read8(u32 addr)
	{
		return bitfield(read32(addr >> 2), bitfield(addr, 0, 2) << 3, 8);
	}

	u16 nds_sound_t::read16(u32 addr)
	{
		return bitfield(read32(addr >> 1), bitfield(addr, 0) << 4, 16);
	}

	u32 nds_sound_t::read32(u32 addr)
	{
		addr <<= 2; // word address

		switch (addr & 0x100)
		{
			case 0x000:
				if ((addr & 0xc) == 0)
					return m_channel[bitfield(addr, 4, 4)].control();
				break;
			case 0x100:
				switch (addr & 0xff)
				{
					case 0x00:
						return m_control;
					case 0x04:
						return m_bias;
					case 0x08:
						return m_capture[0].control() | (m_capture[1].control() << 8);
					case 0x10:
					case 0x18:
						return m_capture[bitfield(addr, 3)].dstaddr();
					default:
						break;
				}
				break;
		}
		return 0;
	}

	void nds_sound_t::write8(u32 addr, u8 data)
	{
		const u8 bit = bitfield(addr, 0, 2);
		const u32 in = u32(data) << (bit << 3);
		const u32 in_mask = 0xff << (bit << 3);
		write32(addr >> 2, in, in_mask);
	}

	void nds_sound_t::write16(u32 addr, u16 data, u16 mask)
	{
		const u8 bit = bitfield(addr, 0);
		const u32 in = u32(data) << (bit << 4);
		const u32 in_mask = u32(mask) << (bit << 4);
		write32(addr >> 1, in, in_mask);
	}

	void nds_sound_t::write32(u32 addr, u32 data, u32 mask)
	{
		addr <<= 2; // word address
		
		switch (addr & 0x100)
		{
			case 0x000:
				m_channel[bitfield(addr, 4, 4)].write(bitfield(addr, 2, 2), data, mask);
				break;
			case 0x100:
				switch (addr & 0xff)
				{
					case 0x00:
						m_control = (m_control & ~mask) | (data & mask);
						break;
					case 0x04:
						mask &= 0x3ff;
						m_bias = (m_bias & ~mask) | (data & mask);
						break;
					case 0x08:
						if (bitfield(mask, 0, 8))
							m_capture[0].control_w(data & 0xff);
						if (bitfield(mask, 8, 8))
							m_capture[1].control_w((data >> 8) & 0xff);
						break;
					case 0x10:
					case 0x14:
					case 0x18:
					case 0x1c:
						m_capture[bitfield(addr, 3)].addrlen_w(bitfield(addr, 2), data, mask);
						break;
					default:
						break;
				}
				break;
		}
	}

	// channels
	void nds_sound_t::channel_t::reset()
	{
		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 nds_sound_t::channel_t::write(u32 offset, u32 data, u32 mask)
	{
		const u32 old = m_control;
		switch (offset & 3)
		{
			case 0: // Control/Status
				m_control = (m_control & ~mask) | (data & mask);
				if (bitfield(old ^ m_control, 31))
				{
					if (busy())
						keyon();
					else if (!busy())
						keyoff();
				}
				// reset hold flag
				if (!m_playing && !hold())
				{
					m_sample = m_lfsr_out = 0;
					m_output = m_loutput = m_routput = 0;
				}
				break;
			case 1: // Source address
				mask &= 0x7ffffff;
				m_sourceaddr = (m_sourceaddr & ~mask) | (data & mask);
				break;
			case 2: // Frequency, Loopstart
				if (bitfield(mask, 0, 16))
					m_freq = (m_freq & bitfield(~mask, 0, 16)) | (bitfield(data & mask, 0, 16));
				if (bitfield(mask, 16, 16))
					m_loopstart = (m_loopstart & bitfield(~mask, 16, 16)) | (bitfield(data & mask, 16, 16));
				break;
			case 3: // Length
				mask &= 0x3fffff;
				m_length = (m_length & ~mask) | (data & mask);
				break;
		}
	}

	void nds_sound_t::channel_t::keyon()
	{
		if (!m_playing)
		{
			m_playing = true;
			m_delay = format() == 2 ? 11 : 3; // 3 (11 for ADPCM) delay for playing sample
			m_cur_bitaddr = m_cur_addr = 0;
			m_cur_state = (format() == 2) ? STATE_ADPCM_LOAD : ((m_loopstart == 0) ? STATE_POST_LOOP : STATE_PRE_LOOP);
			m_counter = 0x10000;
			m_sample = 0;
			m_lfsr_out = 0x7fff;
			m_lfsr = 0x7fff;
		}
	}


	void nds_sound_t::channel_t::keyoff()
	{
		if (m_playing)
		{
			if (busy())
				m_control &= ~(1 << 31);
			if (!hold())
			{
				m_sample = m_lfsr_out = 0;
				m_output = m_loutput = m_routput = 0;
			}

			m_playing = false;
		}
	}

	void nds_sound_t::channel_t::update(s32 cycle)
	{
		if (m_playing)
		{
			// get output
			fetch();
			m_counter -= cycle;
			while (m_counter <= m_freq)
			{
				// advance
				advance();
				m_counter += 0x10000 - m_freq;
			}
			m_output = (m_sample * volume()) >> (7 + voldiv());
			m_loutput = (m_output * lvol()) >> 7;
			m_routput = (m_output * rvol()) >> 7;
		}
	}

	void nds_sound_t::channel_t::fetch()
	{
		if (m_playing)
		{
			// fetch samples
			switch (format())
			{
			case 0: // PCM8
				m_sample = s16(m_host.m_intf.read_byte(addr()) << 8);
				break;
			case 1: // PCM16
				m_sample = m_host.m_intf.read_word(addr());
				break;
			case 2: // ADPCM
				m_sample = m_cur_state == STATE_ADPCM_LOAD ? 0 : m_adpcm_out;
				break;
			case 3: // PSG or Noise
				m_sample = 0;
				if (m_psg) // psg
					m_sample = (duty() == 7) ? -0x7fff : ((m_cur_bitaddr < s32(u32(7) - duty())) ? -0x7fff : 0x7fff);
				else if (m_noise) // noise
					m_sample = m_lfsr_out;
				break;
			}
		}

		// apply delay
		if (format() != 3 && m_delay > 0)
			m_sample = 0;
	}

	void nds_sound_t::channel_t::advance()
	{
		if (m_playing)
		{
			// advance bit address
			switch (format())
			{
			case 0: // PCM8
				m_cur_bitaddr += 8;
				break;
			case 1: // PCM16
				m_cur_bitaddr += 16;
				break;
			case 2: // ADPCM
				if (m_cur_state == STATE_ADPCM_LOAD) // load ADPCM data
				{
					if (m_cur_bitaddr == 0)
						m_prev_adpcm_out = m_adpcm_out = m_host.m_intf.read_word(addr());
					if (m_cur_bitaddr == 16)
						m_prev_adpcm_index = m_adpcm_index = clamp<s32>(m_host.m_intf.read_byte(addr()) & 0x7f, 0, 88);
				}
				else // decode ADPCM
				{
					const u8 input = bitfield(m_host.m_intf.read_byte(addr()), m_cur_bitaddr & 4, 4);
					s32 diff = ((bitfield(input, 0, 3) * 2 + 1) * m_host.adpcm_diff_table[m_adpcm_index] / 8);
					if (bitfield(input, 3)) diff = -diff;
					m_adpcm_out = clamp<s32>(m_adpcm_out + diff, -0x8000, 0x7fff);
					m_adpcm_index = clamp<s32>(m_adpcm_index + m_host.adpcm_index_table[bitfield(input, 0, 3)], 0, 88);
				}
				m_cur_bitaddr += 4;
				break;
			case 3: // PSG or Noise
				if (m_psg) // psg
					m_cur_bitaddr = (m_cur_bitaddr + 1) & 7;
				else if (m_noise) // noise
				{
					if (bitfield(m_lfsr, 0))
					{
						m_lfsr = (m_lfsr >> 1) ^ 0x6000;
						m_lfsr_out = -0x7fff;
					}
					else
					{
						m_lfsr >>= 1;
						m_lfsr_out = 0x7fff;
					}
				}
				break;
			}

			// address update
			if (format() != 3)
			{
				// adjust delay
				m_delay--;

				// update address, loop
				while (m_cur_bitaddr >= 32)
				{
					// already loaded?
					if (format() == 2 && m_cur_state == STATE_ADPCM_LOAD)
					{
						m_cur_state = m_loopstart == 0 ? STATE_POST_LOOP : STATE_PRE_LOOP;
					}
					m_cur_addr++;
					if (m_cur_state == STATE_PRE_LOOP && m_cur_addr >= m_loopstart)
					{
						m_cur_state = STATE_POST_LOOP;
						m_cur_addr = 0;
						if (format() == 2)
						{
							m_prev_adpcm_out = m_adpcm_out;
							m_prev_adpcm_index = m_adpcm_index;
						}
					}
					else if (m_cur_state == STATE_POST_LOOP && m_cur_addr >= m_length)
					{
						switch (repeat())
						{
						case 0: // manual; not correct?
						case 2: // one-shot
						case 3: // prohibited
							keyoff();
							break;
						case 1: // loop infinitely
							if (format() == 2)
							{
								if (m_loopstart == 0) // reload ADPCM
								{
									m_cur_state = STATE_ADPCM_LOAD;
								}
								else // restore
								{
									m_adpcm_out = m_prev_adpcm_out;
									m_adpcm_index = m_prev_adpcm_index;
								}
							}
							m_cur_addr = 0;
							break;
						}
					}
					m_cur_bitaddr -= 32;
				}
			}
		}
	}

	// capture
	void nds_sound_t::capture_t::reset()
	{
		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 = false;
	}

	void nds_sound_t::capture_t::control_w(u8 data)
	{
		const u8 old = m_control;
		m_control = data;
		if (bitfield(old ^ m_control, 7))
		{
			if (busy())
				capture_on();
			else if (!busy())
				capture_off();
		}
	}

	void nds_sound_t::capture_t::addrlen_w(u32 offset, u32 data, u32 mask)
	{
		switch (offset & 1)
		{
			case 0: // Destination Address
				mask &= 0x7ffffff;
				m_dstaddr = (m_dstaddr & ~mask) | (data & mask);
				break;
			case 1: // Buffer Length
				mask &= 0xffff;
				m_length = (m_length & ~mask) | (data & mask);
				break;
		}
	}

	void nds_sound_t::capture_t::update(s32 mix, s32 cycle)
	{
		if (m_enable)
		{
			s32 inval = 0;
			// get inputs
			// TODO: hardware bugs aren't emulated, add mode behavior not verified
			if (addmode())
				inval = get_source() ? m_input.output() + m_output.output() : mix;
			else
				inval = get_source() ? m_input.output() : mix;

			// clip output
			inval = clamp<s32>(inval, -0x8000, 0x7fff);

			// update counter
			m_counter -= cycle;
			while (m_counter <= m_output.freq())
			{
				// write to memory; TODO: verify write behavior
				if (format()) // 8 bit output
				{
					m_fifo[m_fifo_head & 7].write_byte(m_cur_bitaddr & 0x18, (inval >> 8) & 0xff);
					m_cur_bitaddr += 8;
				}
				else
				{
					m_fifo[m_fifo_head & 7].write_word(m_cur_bitaddr & 0x10, inval & 0xffff);
					m_cur_bitaddr += 16;
				}

				// update address
				while (m_cur_bitaddr >= 32)
				{
					// clear FIFO empty flag
					m_fifo_empty = false;

					// advance FIFO head position
					m_fifo_head = (m_fifo_head + 1) & 7;
					if ((m_fifo_head & fifo_mask()) == (m_fifo_tail & fifo_mask()))
						m_fifo_full = true;

					// update loop
					if (++m_cur_addr >= m_length)
					{
						if (repeat())
							m_cur_addr = 0;
						else
							capture_off();
					}

					if (m_fifo_full)
					{
						// execute FIFO
						fifo_write();

						// check repeat
						if (m_cur_waddr >= m_length && repeat())
							m_cur_waddr = 0;
					}

					m_cur_bitaddr -= 32;
				}
				m_counter += 0x10000 - m_output.freq();
			}
		}
	}

	bool nds_sound_t::capture_t::fifo_write()
	{
		if (m_fifo_empty)
			return true;

		// clear FIFO full flag
		m_fifo_full = false;

		// write FIFO data to memory
		m_host.m_intf.write_dword(waddr(), m_fifo[m_fifo_tail].data());
		m_cur_waddr++;

		// advance FIFO tail position
		m_fifo_tail = (m_fifo_tail + 1) & 7;
		if ((m_fifo_head & fifo_mask()) == (m_fifo_tail & fifo_mask()))
			m_fifo_empty = true;

		return m_fifo_empty;
	}

	void nds_sound_t::capture_t::capture_on()
	{
		if (!m_enable)
		{
			m_enable = true;

			// reset address
			m_cur_bitaddr = 0;
			m_cur_addr = m_cur_waddr = 0;
			m_counter = 0x10000;

			// reset FIFO
			m_fifo_head = m_fifo_tail = 0;
			m_fifo_empty = true;
			m_fifo_full = false;
		}
	}

	void nds_sound_t::capture_t::capture_off()
	{
		if (m_enable)
		{
			// flush FIFO
			while (m_cur_waddr < m_length)
			{
				if (fifo_write())
					break;
			}

			m_enable = false;
			if (busy())
				m_control &= ~(1 << 7);
		}
	}

}; // namespace nds_sound_emu