/* ============================================================================ 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 static const inline T bitfield(const T in, const u8 pos) { return (in >> pos) & 1; } // bitfield template static const inline T bitfield(const T in, const u8 pos, const u8 len) { return (in >> pos) & ((1 << len) - 1); } // bitfield template 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