Merge branch 'master' into getSampleMemOffset

This commit is contained in:
tildearrow 2025-09-13 04:13:27 -05:00
commit 551da762ee
34 changed files with 1601 additions and 67 deletions

View file

@ -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()

Binary file not shown.

View file

@ -24,6 +24,11 @@ it also offers an additional 6-bit, 64-byte wavetable sound channel with (somewh
- 6: -2
- 7: -1
- **do not use this effect.** it only exists for compatibility reasons
- `16xy`: **enable automatic modulation speed mode.**
- in this mode the modulation speed is set to the channel's notes, multiplied by a fraction.
- `x` is the numerator.
- `y` is the denominator.
- if `x` or `y` are 0 this will disable automatic modulation speed mode.
## info

View file

@ -33,7 +33,7 @@ a sound and input chip developed by Atari for their 8-bit computers (Atari 400,
- use for PWM effects (not automatic!).
- bit 0: 15KHz mode.
- `12xx`: **toggle two-tone mode.**
- when enabled, channel 2 modulates channel 1. I don't know how, but it does.
- when enabled, channel 2 modulates channel 1 in an oscillator sync-like manner.
- only on ASAP core.
## info

View file

@ -1,6 +1,6 @@
# Sharp SM8521
the SM8521 is the CPU and sound chip of the Game.com, a handheld console released in 1997 as a competitor to the infamous Nintendo Virtual Boy.
the SM8521 is the CPU and sound chip of the Game.com, a handheld console released in 1997 as a competitor to the Nintendo Game Boy.
sadly, the Game.com ended up being a failure as well, mostly due to poor quality games. the Game.com only lasted 3 years before being discontinued.

View file

@ -652,6 +652,11 @@ inline void internal::executor::start(int exit_code)
#else
inline void internal::executor::start_process(std::vector<std::string> const &command)
{
printf("---- WILL EXECUTE:\n");
for (const std::string& i: command) {
printf("- %s\n",i.c_str());
}
printf("END\n");
stop();
m_stdout.clear();
m_exit_code = -1;

View file

@ -256,7 +256,7 @@ long brrEncode(short* buf, unsigned char* out, long len, long loopStart, unsigne
total+=9;
}
// encode loop block
if (loopStart>=0) {
if (loopStart>=0 && loopStart<len) {
long p=loopStart;
for (int i=0; i<17; i++) {
if (p>=len) {

View file

@ -683,6 +683,11 @@ bool DivCSPlayer::init() {
}
}
// read stack sizes
for (unsigned int i=0; i<fileChans; i++) {
chan[i].callStackSize=stream.readC();
}
// initialize state
for (int i=0; i<e->getTotalChannelCount(); i++) {
chan[i].volMax=(e->getDispatch(e->dispatchOfChan[i])->dispatch(DivCommand(DIV_CMD_GET_VOLMAX,e->dispatchChanOfChan[i]))<<8)|0xff;

View file

@ -41,7 +41,7 @@ struct DivCSChannelState {
unsigned char arp, arpStage, arpTicks;
unsigned int callStack[DIV_MAX_CSSTACK];
unsigned char callStackPos;
unsigned char callStackPos, callStackSize;
unsigned int trace[DIV_MAX_CSTRACE];
unsigned char tracePos;
@ -67,6 +67,7 @@ struct DivCSChannelState {
arpStage(0),
arpTicks(0),
callStackPos(0),
callStackSize(0),
tracePos(0) {
for (int i=0; i<DIV_MAX_CSTRACE; i++) {
trace[i]=0;

View file

@ -363,6 +363,103 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) {
patPtr[i]=reader.readI();
}
// skip edit history if present
if (special&2) {
logD("skipping edit history...");
unsigned short editHistSize=reader.readS();
if (editHistSize>0) {
if (!reader.seek(editHistSize*8,SEEK_CUR)) {
logV("what? I wasn't expecting that from you.");
}
}
}
// read extension blocks, if any
logD("looking for extensions...");
bool hasExtensions=true;
while (hasExtensions) {
char extType[4];
unsigned int extSize=0;
memset(extType,0,4);
reader.read(extType,4);
extSize=reader.readI();
if (memcmp(extType,"PNAM",4)==0) {
logV("found MPT extension: pattern names");
// check whether this block is valid
if (extSize>patCount*32) {
logV("block may not be valid");
break;
}
// read pattern names
logV("reading pattern names...");
for (unsigned int i=0; i<(extSize>>5); i++) {
DivPattern* p=ds.subsong[0]->pat[0].getPattern(i,true);
p->name=reader.readStringLatin1(32);
}
} else if (memcmp(extType,"CNAM",4)==0) {
logV("found MPT extension: channel names");
// check whether this block is valid
if (extSize>DIV_MAX_CHANS*20) {
logV("block may not be valid");
break;
}
// read channel names
logV("reading channel names...");
for (unsigned int i=0; i<(extSize>>5); i++) {
String chanName=reader.readStringLatin1(20);
for (DivSubSong* j: ds.subsong) {
j->chanName[i]=chanName;
}
}
} else if (memcmp(extType,"CHFX",4)==0) {
logV("found MPT extension: channel effects");
// skip (stop if we cannot seek)
if (!reader.seek(extSize,SEEK_CUR)) {
break;
}
} else if (
extType[0]=='F' &&
(extType[1]=='X' || (extType[1]>='0' && extType[1]<='9')) &&
(extType[2]>='0' && extType[2]<='9') &&
(extType[3]>='0' && extType[3]<='9')
) { // effect slot
logV("found MPT extension: effect slot");
// skip (stop if we cannot seek)
if (!reader.seek(extSize,SEEK_CUR)) {
break;
}
} else {
logV("no further extensions found... %.2x%.2x%.2x%.2x",extType[0],extType[1],extType[2],extType[3]);
hasExtensions=false;
}
}
// read song comment
logD("reading song comment...");
if (reader.seek(commentPtr,SEEK_SET)) {
try {
String comment=reader.readStringLatin1Special(commentLen);
ds.notes="";
ds.notes.reserve(comment.size());
for (char& i: comment) {
if (i=='\r') {
ds.notes+='\n';
} else {
ds.notes+=i;
}
}
} catch (EndOfFileException& e) {
logW("couldn't read song comment due to premature end of file.");
}
} else {
logW("couldn't seek to comment!");
}
// read instruments
for (int i=0; i<ds.insLen; i++) {
DivInstrument* ins=new DivInstrument;
@ -1419,15 +1516,71 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) {
break;
case 'S': // special...
switch (effectVal[chan]>>4) {
case 0x8:
case 0x3: // vibrato waveform
switch (effectVal[chan]&3) {
case 0x0: // sine
p->data[readRow][effectCol[chan]++]=0xe3;
p->data[readRow][effectCol[chan]++]=0x00;
break;
case 0x1: // ramp down
p->data[readRow][effectCol[chan]++]=0xe3;
p->data[readRow][effectCol[chan]++]=0x05;
break;
case 0x2: // square
p->data[readRow][effectCol[chan]++]=0xe3;
p->data[readRow][effectCol[chan]++]=0x06;
break;
case 0x3: // random
p->data[readRow][effectCol[chan]++]=0xe3;
p->data[readRow][effectCol[chan]++]=0x07;
break;
}
break;
case 0x7:
switch (effectVal[chan]&15) {
case 0x7: // volume envelope off
p->data[readRow][effectCol[chan]++]=0xf5;
p->data[readRow][effectCol[chan]++]=0x00;
break;
case 0x8: // volume envelope on
p->data[readRow][effectCol[chan]++]=0xf6;
p->data[readRow][effectCol[chan]++]=0x00;
break;
case 0x9: // panning envelope off
p->data[readRow][effectCol[chan]++]=0xf5;
p->data[readRow][effectCol[chan]++]=0x0c;
p->data[readRow][effectCol[chan]++]=0xf5;
p->data[readRow][effectCol[chan]++]=0x0d;
break;
case 0xa: // panning envelope on
p->data[readRow][effectCol[chan]++]=0xf6;
p->data[readRow][effectCol[chan]++]=0x0c;
p->data[readRow][effectCol[chan]++]=0xf6;
p->data[readRow][effectCol[chan]++]=0x0d;
break;
case 0xb: // pitch envelope off
p->data[readRow][effectCol[chan]++]=0xf5;
p->data[readRow][effectCol[chan]++]=0x04;
break;
case 0xc: //pitch envelope on
p->data[readRow][effectCol[chan]++]=0xf6;
p->data[readRow][effectCol[chan]++]=0x04;
break;
}
break;
case 0x8: // panning
p->data[readRow][effectCol[chan]++]=0x80;
p->data[readRow][effectCol[chan]++]=(effectVal[chan]&15)<<4;
break;
case 0xc:
case 0xa: // offset (high nibble)
p->data[readRow][effectCol[chan]++]=0x92;
p->data[readRow][effectCol[chan]++]=effectVal[chan]&15;
break;
case 0xc: // note cut
p->data[readRow][effectCol[chan]++]=0xec;
p->data[readRow][effectCol[chan]++]=effectVal[chan]&15;
break;
case 0xd:
case 0xd: // note delay
p->data[readRow][effectCol[chan]++]=0xed;
p->data[readRow][effectCol[chan]++]=effectVal[chan]&15;
break;

View file

@ -356,6 +356,20 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
case 2: // single note slide down
writeFxCol(fxTyp-1+0xf1,fxVal);
break;
case 4: // vibrato waveform
switch (fxVal&3) {
case 0: // sine
writeFxCol(0xe3,0x00);
break;
case 1: // ramp down
writeFxCol(0xe3,0x05);
break;
case 2: // square
case 3:
writeFxCol(0xe3,0x06);
break;
}
break;
case 9: // retrigger
writeFxCol(0x0c,fxVal);
break;

View file

@ -1101,15 +1101,35 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) {
break;
case 'S': // special...
switch (effectVal>>4) {
case 0x8:
case 0x3: // vibrato waveform
switch (effectVal&3) {
case 0x0: // sine
p->data[readRow][effectCol[chan]++]=0xe3;
p->data[readRow][effectCol[chan]++]=0x00;
break;
case 0x1: // ramp down
p->data[readRow][effectCol[chan]++]=0xe3;
p->data[readRow][effectCol[chan]++]=0x05;
break;
case 0x2: // square
p->data[readRow][effectCol[chan]++]=0xe3;
p->data[readRow][effectCol[chan]++]=0x06;
break;
case 0x3: // random
p->data[readRow][effectCol[chan]++]=0xe3;
p->data[readRow][effectCol[chan]++]=0x07;
break;
}
break;
case 0x8: // panning
p->data[readRow][effectCol[chan]++]=0x80;
p->data[readRow][effectCol[chan]++]=(effectVal&15)<<4;
break;
case 0xc:
case 0xc: // note cut
p->data[readRow][effectCol[chan]++]=0xec;
p->data[readRow][effectCol[chan]++]=effectVal&15;
break;
case 0xd:
case 0xd: // note delay
p->data[readRow][effectCol[chan]++]=0xed;
p->data[readRow][effectCol[chan]++]=effectVal&15;
break;

View file

@ -325,7 +325,9 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) {
for (unsigned short i=0; i<patCount; i++) {
logV("pattern %d",i);
headerSeek=reader.tell();
headerSeek+=reader.readI();
unsigned int headerSeekAdd=reader.readI();
logV("seek is %d",headerSeekAdd);
headerSeek+=headerSeekAdd;
unsigned char packType=reader.readC();
if (packType!=0) {
@ -350,7 +352,10 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) {
return false;
}
unsigned int packedSeek=headerSeek+(unsigned short)reader.readS();
unsigned short packedSize=reader.readS();
logV("packed size: %d",packedSize);
unsigned int packedSeek=headerSeek+packedSize;
logV("seeking to %x...",headerSeek);
if (!reader.seek(headerSeek,SEEK_SET)) {
@ -363,6 +368,10 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) {
// read data
for (int j=0; j<totalRows; j++) {
if (reader.tell()>=packedSeek) {
logV("end of data - stopping here...");
break;
}
for (int k=0; k<totalChans; k++) {
unsigned char note=reader.readC();
unsigned char vol=0;
@ -836,7 +845,10 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) {
return false;
}
unsigned int packedSeek=headerSeek+(unsigned short)reader.readS();
unsigned short packedSize=reader.readS();
logV("packed size: %d",packedSize);
unsigned int packedSeek=headerSeek+packedSize;
logV("seeking to %x...",headerSeek);
if (!reader.seek(headerSeek,SEEK_SET)) {
@ -849,6 +861,10 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) {
// read data
for (int j=0; j<totalRows; j++) {
if (reader.tell()>=packedSeek) {
logV("end of data - stopping here...");
break;
}
for (int k=0; k<totalChans; k++) {
DivPattern* p=ds.subsong[0]->pat[k].getPattern(i,true);
@ -1131,11 +1147,28 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) {
case 0xe: // special...
// TODO: implement the rest
switch (effectVal>>4) {
case 0x5:
case 0x4: // vibrato waveform
switch (effectVal&3) {
case 0x0: // sine
p->data[j][effectCol[k]++]=0xe3;
p->data[j][effectCol[k]++]=0x00;
break;
case 0x1: // ramp down
p->data[j][effectCol[k]++]=0xe3;
p->data[j][effectCol[k]++]=0x05;
break;
case 0x2: // square
case 0x3:
p->data[j][effectCol[k]++]=0xe3;
p->data[j][effectCol[k]++]=0x06;
break;
}
break;
case 0x5: // fine tune
p->data[j][effectCol[k]++]=0xe5;
p->data[j][effectCol[k]++]=(effectVal&15)<<4;
break;
case 0x9:
case 0x9: // retrigger
p->data[j][effectCol[k]++]=0x0c;
p->data[j][effectCol[k]++]=(effectVal&15);
break;
@ -1155,11 +1188,11 @@ bool DivEngine::loadXM(unsigned char* file, size_t len) {
}
volSliding[k]=true;
break;
case 0xc:
case 0xc: // note cut
p->data[j][effectCol[k]++]=0xdc;
p->data[j][effectCol[k]++]=MAX(1,effectVal&15);
break;
case 0xd:
case 0xd: // note delay
p->data[j][effectCol[k]++]=0xed;
p->data[j][effectCol[k]++]=MAX(1,effectVal&15);
break;

View file

@ -492,6 +492,9 @@ void DivPlatformLynx::forceIns() {
if (chan[i].active) {
chan[i].insChanged=true;
chan[i].freqChanged=true;
if (!chan[i].pcm) {
WRITE_FEEDBACK(i,chan[i].duty.feedback);
}
}
WRITE_ATTEN(i,chan[i].pan);
}

View file

@ -377,6 +377,10 @@ void DivPlatformMMC5::forceIns() {
for (int i=0; i<3; i++) {
chan[i].insChanged=true;
chan[i].prevFreq=65535;
if (i<2) {
// TODO: implement envelope mode
rWrite(0x5000+i*4,(0x30)|(chan[i].active?chan[i].outVol:0)|((chan[i].duty&3)<<6));
}
}
}
@ -429,6 +433,10 @@ void DivPlatformMMC5::reset() {
rWrite(0x5015,0x03);
rWrite(0x5010,0x00);
for (int i=0; i<2; i++) {
rWrite(0x5000+i*4,(0x30)|0|((chan[i].duty&3)<<6));
}
}
bool DivPlatformMMC5::keyOffAffectsArp(int ch) {

View file

@ -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);
}

View file

@ -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;

View file

@ -322,7 +322,7 @@ void DivPlatformQSound::tick(bool sysTick) {
}
int loopStart=s->loopStart;
int length=s->loopEnd;
int length=s->isLoopable()?s->loopEnd:s->samples;
if (i<16) {
if (length>65536-16) {
length=65536-16;

View file

@ -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()
{

View file

@ -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

View 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

View 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

View file

@ -129,7 +129,7 @@ void DivPlatformT6W28::tick(bool sysTick) {
chan[i].freqChanged=true;
}
if (i==3 && chan[i].std.duty.had) {
if (chan[i].duty!=chan[i].std.duty.val) {
if (chan[i].duty!=(((chan[i].std.duty.val==1)?4:0)|3)) {
chan[i].duty=((chan[i].std.duty.val==1)?4:0)|3;
rWrite(1,0xe0+chan[i].duty);
}
@ -153,7 +153,9 @@ void DivPlatformT6W28::tick(bool sysTick) {
chan[i].freqChanged=true;
}
if (chan[i].std.phaseReset.had) {
rWrite(1,0xe0+chan[i].duty);
if (chan[i].std.phaseReset.val==1) {
rWrite(1,0xe0+chan[i].duty);
}
}
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=snCalcFreq(i);

View file

@ -271,6 +271,15 @@ String SafeReader::readStringWithEncoding(DivStringEncoding encoding, size_t stl
ret.push_back(c);
}
}
} else if (encoding==DIV_ENCODING_LATIN1_SPECIAL) {
if (c&0x80) {
if (c>=0xa0) {
ret.push_back(0xc0|(c>>6));
ret.push_back(0x80|(c&63));
}
} else {
ret.push_back(c);
}
} else {
ret.push_back(c);
}
@ -297,6 +306,15 @@ String SafeReader::readStringWithEncoding(DivStringEncoding encoding) {
ret.push_back(c);
}
}
} else if (encoding==DIV_ENCODING_LATIN1_SPECIAL) {
if (c&0x80) {
if (c>=0xa0) {
ret.push_back(0xc0|(c>>6));
ret.push_back(0x80|(c&63));
}
} else {
ret.push_back(c);
}
} else {
ret.push_back(c);
}
@ -320,6 +338,14 @@ String SafeReader::readStringLatin1(size_t stlen) {
return readStringWithEncoding(DIV_ENCODING_LATIN1,stlen);
}
String SafeReader::readStringLatin1Special() {
return readStringWithEncoding(DIV_ENCODING_LATIN1_SPECIAL);
}
String SafeReader::readStringLatin1Special(size_t stlen) {
return readStringWithEncoding(DIV_ENCODING_LATIN1_SPECIAL,stlen);
}
String SafeReader::readStringLine() {
String ret;
unsigned char c;

View file

@ -33,6 +33,7 @@ enum DivStringEncoding {
DIV_ENCODING_NONE=0,
DIV_ENCODING_UTF8,
DIV_ENCODING_LATIN1,
DIV_ENCODING_LATIN1_SPECIAL,
DIV_ENCODING_SHIFT_JIS
};
@ -77,6 +78,8 @@ class SafeReader {
String readString(size_t len);
String readStringLatin1();
String readStringLatin1(size_t len);
String readStringLatin1Special();
String readStringLatin1Special(size_t len);
String readStringLine();
String readStringToken(unsigned char delim, bool stripContiguous);
String readStringToken();

View file

@ -1479,7 +1479,8 @@ void DivSample::render(unsigned int formatMask) {
}
}
if (NOT_IN_FORMAT(DIV_SAMPLE_DEPTH_BRR)) { // BRR
int sampleCount=loop?loopEnd:samples;
int sampleCount=isLoopable()?loopEnd:samples;
if (sampleCount>(int)samples) sampleCount=samples;
if (!initInternal(DIV_SAMPLE_DEPTH_BRR,sampleCount)) return;
brrEncode(data16,dataBRR,sampleCount,loop?loopStart:-1,brrEmphasis,brrNoFilter);
}

View file

@ -525,6 +525,72 @@ void FurnaceGUI::drawCSPlayer() {
ImGui::PopFont();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem(_("Data Access Visualizer"))) {
ImGui::Text("%d bytes",(int)cs->getDataLen());
ImGui::PushFont(patFont);
if (ImGui::BeginTable("CSHexPos",chans,ImGuiTableFlags_SizingStretchSame)) {
ImGui::TableNextRow();
for (int i=0; i<chans; i++) {
ImGui::TableNextColumn();
ImGui::Text("%d",i);
}
ImGui::TableNextRow();
for (int i=0; i<chans; i++) {
DivCSChannelState* state=cs->getChanState(i);
ImGui::TableNextColumn();
ImGui::Text("$%.4x",state->readPos);
}
ImGui::EndTable();
}
ImGui::PopFont();
if (csTex==NULL || !rend->isTextureValid(csTex)) {
logD("recreating command stream data texture.");
csTex=rend->createTexture(true,256,256,false,GUI_TEXFORMAT_ABGR32);
if (csTex==NULL) {
logE("error while creating command stream data texture! %s",SDL_GetError());
}
}
if (csTex!=NULL) {
unsigned int* dataT=NULL;
int pitch=0;
if (!rend->lockTexture(csTex,(void**)&dataT,&pitch)) {
logE("error while locking command stream data texture! %s",SDL_GetError());
} else {
unsigned short* accessTS=cs->getDataAccess();
unsigned int csTick=cs->getCurTick();
const float fadeTime=64.0f;
size_t bufSize=cs->getDataLen();
if (bufSize>65536) bufSize=65536;
for (size_t i=0; i<bufSize; i++) {
float cellAlpha=(float)(fadeTime-(((short)(csTick&0xffff))-(short)accessTS[i]))/fadeTime;
if (cellAlpha>0.0f) {
dataT[i]=ImGui::GetColorU32(ImGuiCol_HeaderActive,cellAlpha);
} else {
dataT[i]=0;
}
}
for (size_t i=bufSize; i<65536; i++) {
dataT[i]=0;
}
for (int i=0; i<e->getTotalChannelCount(); i++) {
unsigned int pos=cs->getChanState(i)->readPos;
if (pos<65536) {
ImVec4 col=ImVec4(1.0f,1.0f,1.0f,1.0f);
ImGui::ColorConvertHSVtoRGB((float)i/(float)e->getTotalChannelCount(),0.8f,1.0f,col.x,col.y,col.z);
dataT[pos]=ImGui::GetColorU32(col);
}
}
rend->unlockTexture(csTex);
}
ImGui::Image(rend->getTextureID(csTex),ImVec2(768.0*dpiScale,768.0*dpiScale));
}
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem(_("Stream Info"))) {
ImGui::Text("%d bytes",(int)cs->getDataLen());
ImGui::Text("%u channels",cs->getFileChans());
@ -538,6 +604,11 @@ void FurnaceGUI::drawCSPlayer() {
ImGui::SameLine();
ImGui::Text("%d",cs->getFastCmds()[i]);
}
ImGui::Text("stack sizes:");
for (unsigned int i=0; i<cs->getFileChans(); i++) {
ImGui::SameLine();
ImGui::Text("%d",cs->getChanState(i)->callStackSize);
}
ImGui::Text("ticks: %u",cs->getCurTick());
ImGui::EndTabItem();
}

View file

@ -365,12 +365,10 @@ void FurnaceGUI::drawDebug() {
ImGui::TreePop();
}
if (ImGui::TreeNode("Scroll Text Test")) {
/*
ImGui::ScrollText(ImGui::GetID("scrolltest1"),"Lorem ipsum, quia dolor sit, amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt, ut labore et dolore magnam aliquam quaerat voluptatem. ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?");
ImGui::ScrollText(ImGui::GetID("scrolltest2"),"quis autem vel eum iure reprehenderit");
ImGui::ScrollText(ImGui::GetID("scrolltest3"),"qui in ea voluptate velit esse",ImVec2(100.0f*dpiScale,0),true);
ImGui::ScrollText(ImGui::GetID("scrolltest4"),"quam nihil molestiae consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla pariatur?",ImVec2(0,0),true);
*/
ImGui::ScrollText(ImGui::GetID("scrolltest1"),"Lorem ipsum, quia dolor sit, amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt, ut labore et dolore magnam aliquam quaerat voluptatem. ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?",ImGui::GetCursorPos());
ImGui::ScrollText(ImGui::GetID("scrolltest2"),"quis autem vel eum iure reprehenderit",ImGui::GetCursorPos());
ImGui::ScrollText(ImGui::GetID("scrolltest3"),"qui in ea voluptate velit esse",ImGui::GetCursorPos(),ImVec2(100.0f*dpiScale,0),true);
ImGui::ScrollText(ImGui::GetID("scrolltest4"),"quam nihil molestiae consequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla pariatur?",ImGui::GetCursorPos(),ImVec2(0,0),true);
ImGui::TreePop();
}
if (ImGui::TreeNode("Pitch Table Calculator")) {

View file

@ -38,9 +38,21 @@ struct InflateBlock {
};
ImFont* FurnaceGUI::addFontZlib(const void* data, size_t len, float size_pixels, const ImFontConfig* font_cfg, const ImWchar* glyph_ranges) {
// find font in cache
logV("addFontZlib...");
for (FurnaceGUIUncompFont& i: fontCache) {
if (i.origPtr==data && i.origLen==len) {
logV("found in cache");
ImFontConfig fontConfig=(font_cfg==NULL)?ImFontConfig():(*font_cfg);
fontConfig.FontDataOwnedByAtlas=false;
return ImGui::GetIO().Fonts->AddFontFromMemoryTTF(i.data,i.len,size_pixels,&fontConfig,glyph_ranges);
}
}
z_stream zl;
memset(&zl,0,sizeof(z_stream));
logV("addFontZlib...");
logV("not found in cache - decompressing...");
zl.avail_in=len;
zl.next_in=(Bytef*)data;
@ -116,10 +128,11 @@ ImFont* FurnaceGUI::addFontZlib(const void* data, size_t len, float size_pixels,
delete i;
}
blocks.clear();
len=finalSize;
fontCache.push_back(FurnaceGUIUncompFont(data,len,finalData,finalSize));
ImFontConfig fontConfig=(font_cfg==NULL)?ImFontConfig():(*font_cfg);
fontConfig.FontDataOwnedByAtlas=true;
fontConfig.FontDataOwnedByAtlas=false;
return ImGui::GetIO().Fonts->AddFontFromMemoryTTF(finalData,finalSize,size_pixels,&fontConfig,glyph_ranges);
}

View file

@ -2634,7 +2634,7 @@ void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) {
e->findSongLength(loopOrder,loopRow,audioExportOptions.fadeOut,songFadeoutSectionLength,songHasSongEndCommand,songOrdersLengths,songLength); // for progress estimation
songLoopedSectionLength=songLength;
for (int i=0; i<loopOrder; i++) {
for (int i=0; i<loopOrder && i<(int)songOrdersLengths.size(); i++) {
songLoopedSectionLength-=songOrdersLengths[i];
}
songLoopedSectionLength-=loopRow;

View file

@ -1546,6 +1546,18 @@ struct FurnaceGUIPerfMetric {
elapsed(0) {}
};
struct FurnaceGUIUncompFont {
const void* origPtr;
size_t origLen;
void* data;
size_t len;
FurnaceGUIUncompFont(const void* ptr, size_t len, void* d, size_t l):
origPtr(ptr),
origLen(len),
data(d),
len(l) {}
};
struct FurnaceGUIBackupEntry {
String name;
uint64_t size;
@ -1666,6 +1678,8 @@ class FurnaceGUI {
int sampleTexW, sampleTexH;
bool updateSampleTex;
FurnaceGUITexture* csTex;
String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile, sysSearchQuery, newSongQuery, paletteQuery, sampleBankSearchQuery;
String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport;
String workingDirVGMExport, workingDirROMExport;
@ -1776,6 +1790,7 @@ class FurnaceGUI {
MIDIMap midiMap;
int learning;
std::vector<FurnaceGUIUncompFont> fontCache;
ImFont* mainFont;
ImFont* iconFont;
ImFont* furIconFont;

View file

@ -2318,6 +2318,32 @@ void FurnaceGUI::drawSampleEdit() {
}
}
}
if (displayLoopHintsNDSA) {
if (sampleZoom<0.5) {
for (int i=0; i<(int)(sampleZoom*avail.x); i++) {
if (((i+samplePos)&7)==0) {
ImVec2 p1=ImVec2(rectMin.x+((float)i/sampleZoom),rectMin.y);
ImVec2 p2=p1;
p2.y=rectMax.y;
dl->AddLine(p1,p2,ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_LOOP_HINT]));
}
}
}
}
if (displayLoopHintsNDS8) {
if (sampleZoom<0.375) {
for (int i=0; i<(int)(sampleZoom*avail.x); i++) {
if (((i+samplePos)&3)==0) {
ImVec2 p1=ImVec2(rectMin.x+((float)i/sampleZoom),rectMin.y);
ImVec2 p2=p1;
p2.y=rectMax.y;
dl->AddLine(p1,p2,ImGui::GetColorU32(uiColors[GUI_COLOR_SAMPLE_LOOP_HINT]));
}
}
}
}
if (displayLoopHintsAmiga) {
if (sampleZoom<0.25) {
for (int i=0; i<(int)(sampleZoom*avail.x); i++) {

View file

@ -6761,7 +6761,7 @@ void FurnaceGUI::applyUISettings(bool updateFonts) {
logW("could not load header font! reverting to default font");
settings.headFont=0;
if ((headFont=addFontZlib(builtinFont[settings.headFont],builtinFontLen[settings.headFont],MAX(1,e->getConfInt("headFontSize",27)*dpiScale),&fontConfH))==NULL) {
logE("could not load header font! falling back to IBM Plex Sans.");
logE("could not load header font! falling back to fallback. wahahaha, get it? fallback.");
headFont=ImGui::GetIO().Fonts->AddFontDefault();
}
}
@ -6775,6 +6775,14 @@ void FurnaceGUI::applyUISettings(bool updateFonts) {
}
}
// four fallback fonts
if (settings.loadFallback) {
headFont=addFontZlib(font_plexSans_compressed_data,font_plexSans_compressed_size,MAX(1,e->getConfInt("headFontSize",27)*dpiScale),&fc1);
headFont=addFontZlib(font_plexSansJP_compressed_data,font_plexSansJP_compressed_size,MAX(1,e->getConfInt("headFontSize",27)*dpiScale),&fc1);
headFont=addFontZlib(font_plexSansKR_compressed_data,font_plexSansKR_compressed_size,MAX(1,e->getConfInt("headFontSize",27)*dpiScale),&fc1);
headFont=addFontZlib(font_unifont_compressed_data,font_unifont_compressed_size,MAX(1,e->getConfInt("headFontSize",27)*dpiScale),&fc1);
}
mainFont->FallbackChar='?';
mainFont->EllipsisChar='.';
//mainFont->EllipsisCharCount=3;

View file

@ -1313,7 +1313,7 @@ void FurnaceCV::buildStage(int which) {
curStage=NULL;
}
if (which>19 || which==4 || which==7 || which==9 || which==11 || which==13 || which==16 || which==17) {
if (which>18 || which==4 || which==7 || which==9 || which==11 || which==13 || which==16 || which==17) {
stageWidth=80;
stageHeight=56;
} else {
@ -1346,26 +1346,7 @@ void FurnaceCV::buildStage(int which) {
memset(busy,0,28*40*sizeof(bool));
// special stages
if ((which%10)==9) {
// vortex
for (int i=0; i<20+(which>>2); i++) {
int tries=0;
while (tries<20) {
int x=rand()%(stageWidth>>1);
int y=rand()%(stageHeight>>1);
int finalX=x<<4;
int finalY=y<<4;
if (busy[y][x]) {
tries++;
continue;
}
createObject<FurnaceCVEnemyVortex>(finalX,finalY);
createObject<FurnaceCVFurBallMedium>(finalX-4,finalY-4);
busy[y][x]=true;
break;
}
}
} else if ((which%10)==19) {
if ((which%20)==19) {
for (int i=0; i<20+(which>>2); i++) {
int tries=0;
while (tries<20) {
@ -1387,6 +1368,25 @@ void FurnaceCV::buildStage(int which) {
break;
}
}
} else if ((which%10)==9) {
// vortex
for (int i=0; i<20+(which>>2); i++) {
int tries=0;
while (tries<20) {
int x=rand()%(stageWidth>>1);
int y=rand()%(stageHeight>>1);
int finalX=x<<4;
int finalY=y<<4;
if (busy[y][x]) {
tries++;
continue;
}
createObject<FurnaceCVEnemyVortex>(finalX,finalY);
createObject<FurnaceCVFurBallMedium>(finalX-4,finalY-4);
busy[y][x]=true;
break;
}
}
} else {
// large
if (which>=2) for (int i=0; i<(rand()%3)+which-2; i++) {
@ -1675,6 +1675,7 @@ void FurnaceCV::render(unsigned char joyIn) {
lives+=lifeBank;
respawnTime=1;
lifeBank=0;
score=0;
gameOver=false;
}