2024-02-10 21:49:20 -05:00
|
|
|
/*
|
|
|
|
|
|
|
|
============================================================================
|
|
|
|
|
|
|
|
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()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2024-02-11 01:45:18 -05:00
|
|
|
virtual u8 read_byte(u32 addr) { return 0; }
|
2024-02-10 21:49:20 -05:00
|
|
|
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); }
|
|
|
|
|
2024-02-11 01:45:18 -05:00
|
|
|
virtual void write_byte(u32 addr, u8 data) {}
|
2024-02-10 21:49:20 -05:00
|
|
|
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);
|
2025-03-08 04:37:42 -05:00
|
|
|
s32 predict();
|
2024-02-10 21:49:20 -05:00
|
|
|
|
|
|
|
// 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);
|
2025-03-08 04:37:42 -05:00
|
|
|
s32 predict();
|
2024-02-10 21:49:20 -05:00
|
|
|
|
|
|
|
// 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
|