NDS: fix volume
it was twice as loud and global volume would clip
This commit is contained in:
parent
825039ec5a
commit
d16bad802d
|
@ -140,6 +140,7 @@ option(CONSOLE_SUBSYSTEM "Build Furnace with Console subsystem on Windows" OFF)
|
|||
option(FORCE_CODEVIEW "Force -gcodeview on MinGW GCC" OFF)
|
||||
option(FLATPAK_WORKAROUNDS "Enable Flatpak-specific workaround for system file picker" OFF)
|
||||
option(NO_INTRO "Disable intro animation entirely" OFF)
|
||||
option(ORIG_NDS_CORE "Use original NDS emulation core (no acquireDirect)" OFF)
|
||||
if (APPLE)
|
||||
option(FORCE_APPLE_BIN "Force enable binary installation to /bin" OFF)
|
||||
option(MAKE_BUNDLE "Make a bundle" OFF)
|
||||
|
@ -674,8 +675,6 @@ src/engine/platform/sound/c140_c219.c
|
|||
|
||||
src/engine/platform/sound/dave/dave.cpp
|
||||
|
||||
src/engine/platform/sound/nds.cpp
|
||||
|
||||
src/engine/platform/sound/sid2/envelope.cc
|
||||
src/engine/platform/sound/sid2/filter.cc
|
||||
src/engine/platform/sound/sid2/sid.cc
|
||||
|
@ -830,6 +829,13 @@ src/engine/effect/abstract.cpp
|
|||
src/engine/effect/dummy.cpp
|
||||
)
|
||||
|
||||
if (ORIG_NDS_CORE)
|
||||
list(APPEND ENGINE_SOURCES src/engine/platform/sound/nds_unopt.cpp)
|
||||
list(APPEND DEPENDENCIES_DEFINES ORIG_NDS_CORE)
|
||||
else()
|
||||
list(APPEND ENGINE_SOURCES src/engine/platform/sound/nds.cpp)
|
||||
endif()
|
||||
|
||||
if (USE_SNDFILE)
|
||||
list(APPEND ENGINE_SOURCES src/engine/sfWrapper.cpp)
|
||||
endif()
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <math.h>
|
||||
|
||||
#define CHIP_DIVIDER 32
|
||||
#define NDS_CORE_QUALITY 64
|
||||
|
||||
#define rRead8(a) (nds.read8(a))
|
||||
#define rWrite8(a,v) {if(!skipRegisterWrites){writes.push_back(QueuedWrite((a),1,(v)));regPool[(a)]=(v);if(dumpWrites)addWrite((a),(v));}}
|
||||
|
@ -68,6 +69,45 @@ const char** DivPlatformNDS::getRegisterSheet() {
|
|||
return regCheatSheetNDS;
|
||||
}
|
||||
|
||||
#ifdef ORIG_NDS_CORE
|
||||
void DivPlatformNDS::acquire(short** buf, size_t len) {
|
||||
for (int i=0; i<16; i++) {
|
||||
oscBuf[i]->begin(len);
|
||||
}
|
||||
|
||||
while (!writes.empty()) {
|
||||
QueuedWrite w=writes.front();
|
||||
if (w.size==4) {
|
||||
nds.write32(w.addr>>2,w.val);
|
||||
} else if (w.size==2) {
|
||||
nds.write16(w.addr>>1,w.val);
|
||||
} else {
|
||||
nds.write8(w.addr,w.val);
|
||||
}
|
||||
writes.pop();
|
||||
}
|
||||
|
||||
for (size_t h=0; h<len; h++) {
|
||||
nds.tick(NDS_CORE_QUALITY);
|
||||
int lout=((nds.loutput()-0x200)<<5); // scale to 16 bit
|
||||
int rout=((nds.routput()-0x200)<<5); // scale to 16 bit
|
||||
if (lout>32767) lout=32767;
|
||||
if (lout<-32768) lout=-32768;
|
||||
if (rout>32767) rout=32767;
|
||||
if (rout<-32768) rout=-32768;
|
||||
buf[0][h]=lout;
|
||||
buf[1][h]=rout;
|
||||
|
||||
for (int i=0; i<16; i++) {
|
||||
oscBuf[i]->putSample(h,(nds.chan_lout(i)+nds.chan_rout(i))>>1);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<16; i++) {
|
||||
oscBuf[i]->end(len);
|
||||
}
|
||||
}
|
||||
#else
|
||||
void DivPlatformNDS::acquireDirect(blip_buffer_t** bb, size_t len) {
|
||||
for (int i=0; i<16; i++) {
|
||||
oscBuf[i]->begin(len);
|
||||
|
@ -95,13 +135,7 @@ void DivPlatformNDS::acquireDirect(blip_buffer_t** bb, size_t len) {
|
|||
oscBuf[i]->end(len);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformNDS::postProcess(short* buf, int outIndex, size_t len, int sampleRate) {
|
||||
// this is where we handle global volume. it is faster than doing it on each blip...
|
||||
for (size_t i=0; i<len; i++) {
|
||||
buf[i]=((buf[i]*globalVolume)>>7);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
u8 DivPlatformNDS::read_byte(u32 addr) {
|
||||
if (addr<getSampleMemCapacity()) {
|
||||
|
@ -475,7 +509,11 @@ int DivPlatformNDS::getOutputCount() {
|
|||
}
|
||||
|
||||
bool DivPlatformNDS::hasAcquireDirect() {
|
||||
#ifdef ORIG_NDS_CORE
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void DivPlatformNDS::notifyInsChange(int ins) {
|
||||
|
@ -598,7 +636,11 @@ void DivPlatformNDS::setFlags(const DivConfig& flags) {
|
|||
isDSi=flags.getBool("chipType",0);
|
||||
chipClock=33513982;
|
||||
CHECK_CUSTOM_CLOCK;
|
||||
#ifdef ORIG_NDS_CORE
|
||||
rate=chipClock/(2*NDS_CORE_QUALITY);
|
||||
#else
|
||||
rate=chipClock/2;
|
||||
#endif
|
||||
for (int i=0; i<16; i++) {
|
||||
oscBuf[i]->setRate(rate);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,11 @@
|
|||
#define _NDS_H
|
||||
|
||||
#include "../dispatch.h"
|
||||
#ifdef ORIG_NDS_CORE
|
||||
#include "sound/nds_unopt.hpp"
|
||||
#else
|
||||
#include "sound/nds.hpp"
|
||||
#endif
|
||||
|
||||
using namespace nds_sound_emu;
|
||||
|
||||
|
@ -73,8 +77,11 @@ class DivPlatformNDS: public DivDispatch, public nds_sound_intf {
|
|||
virtual u8 read_byte(u32 addr) override;
|
||||
virtual void write_byte(u32 addr, u8 data) override;
|
||||
|
||||
#ifdef ORIG_NDS_CORE
|
||||
virtual void acquire(short** buf, size_t len) override;
|
||||
#else
|
||||
virtual void acquireDirect(blip_buffer_t** bb, size_t len) override;
|
||||
virtual void postProcess(short* buf, int outIndex, size_t len, int sampleRate) override;
|
||||
#endif
|
||||
virtual int dispatch(DivCommand c) override;
|
||||
virtual void* getChanState(int chan) override;
|
||||
virtual DivMacroInt* getChanMacroInt(int ch) override;
|
||||
|
|
|
@ -225,6 +225,9 @@ namespace nds_sound_emu
|
|||
{
|
||||
case 0x00:
|
||||
m_control = (m_control & ~mask) | (data & mask);
|
||||
for (u8 i = 0; i < 16; i++) {
|
||||
m_channel[i].setMasterVol(mvol());
|
||||
}
|
||||
break;
|
||||
case 0x04:
|
||||
mask &= 0x3ff;
|
||||
|
@ -302,6 +305,7 @@ namespace nds_sound_emu
|
|||
m_ctl_repeat = bitfield(m_control, 27, 2);
|
||||
m_ctl_format = bitfield(m_control, 29, 2);
|
||||
m_ctl_busy = bitfield(m_control, 31);
|
||||
computeVol();
|
||||
|
||||
if (bitfield(old ^ m_control, 31))
|
||||
{
|
||||
|
@ -393,14 +397,14 @@ namespace nds_sound_emu
|
|||
advance();
|
||||
m_counter += 0x10000 - m_freq;
|
||||
}
|
||||
m_output = (m_sample * m_ctl_volume) >> (7 + m_ctl_voldiv);
|
||||
const s32 loutput = (m_output * lvol()) >> 7;
|
||||
const s32 routput = (m_output * rvol()) >> 7;
|
||||
m_output = (m_sample * m_final_volume) >> (14 + m_ctl_voldiv);
|
||||
const s32 loutput = (m_output * lvol()) >> 8;
|
||||
const s32 routput = (m_output * rvol()) >> 8;
|
||||
|
||||
i+=cycle-1;
|
||||
|
||||
if (m_loutput!=loutput || m_routput!=routput) {
|
||||
m_oscBuf->putSample(i,(loutput+routput)>>1);
|
||||
m_oscBuf->putSample(i,(loutput+routput));
|
||||
}
|
||||
if (m_loutput!=loutput) {
|
||||
blip_add_delta(m_bb[0],i,loutput-m_loutput);
|
||||
|
@ -559,6 +563,17 @@ namespace nds_sound_emu
|
|||
}
|
||||
}
|
||||
|
||||
void nds_sound_t::channel_t::computeVol()
|
||||
{
|
||||
m_final_volume = m_ctl_volume * m_master_volume;
|
||||
}
|
||||
|
||||
void nds_sound_t::channel_t::setMasterVol(s32 masterVol)
|
||||
{
|
||||
m_master_volume = masterVol;
|
||||
computeVol();
|
||||
}
|
||||
|
||||
// capture
|
||||
void nds_sound_t::capture_t::reset()
|
||||
{
|
||||
|
|
|
@ -232,6 +232,7 @@ namespace nds_sound_emu
|
|||
void write(u32 offset, u32 data, u32 mask = ~0);
|
||||
|
||||
void update(s32 cycle);
|
||||
void setMasterVol(s32 masterVol);
|
||||
void set_bb(blip_buffer_t* bbLeft, blip_buffer_t* bbRight) { m_bb[0] = bbLeft; m_bb[1] = bbRight; }
|
||||
void set_oscbuf(DivDispatchOscBuffer* oscBuf) { m_oscBuf = oscBuf; }
|
||||
void resetTS(u32 what) { m_lastts = what; }
|
||||
|
@ -272,6 +273,7 @@ namespace nds_sound_emu
|
|||
void keyoff();
|
||||
void fetch();
|
||||
void advance();
|
||||
void computeVol();
|
||||
|
||||
// interfaces
|
||||
nds_sound_t &m_host; // host device
|
||||
|
@ -303,6 +305,8 @@ namespace nds_sound_emu
|
|||
|
||||
// internal states
|
||||
bool m_playing = false; // playing flag
|
||||
s32 m_final_volume = 0; // calculated volume
|
||||
s32 m_master_volume = 0; // master volume cache
|
||||
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
|
||||
|
|
634
src/engine/platform/sound/nds_unopt.cpp
Normal file
634
src/engine/platform/sound/nds_unopt.cpp
Normal file
|
@ -0,0 +1,634 @@
|
|||
/*
|
||||
|
||||
============================================================================
|
||||
|
||||
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_unopt.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
|
415
src/engine/platform/sound/nds_unopt.hpp
Normal file
415
src/engine/platform/sound/nds_unopt.hpp
Normal file
|
@ -0,0 +1,415 @@
|
|||
/*
|
||||
|
||||
============================================================================
|
||||
|
||||
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
|
Loading…
Reference in a new issue