From 5c3378e477bd6c4202348691ed3a2d5cf7a88f2a Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Tue, 9 Jun 2026 17:12:20 +0700 Subject: [PATCH] Add NES emulation cores Only APU and MMC5 are built and exported for now --- build-wasm.ps1 | 18 +- build-wasm.sh | 22 +- external/nsfplug/nes_apu.c | 368 ++++++++++++++++++ external/nsfplug/nes_apu.h | 70 ++++ external/nsfplug/nes_dmc.c | 725 ++++++++++++++++++++++++++++++++++++ external/nsfplug/nes_dmc.h | 106 ++++++ external/nsfplug/nes_fds.c | 376 +++++++++++++++++++ external/nsfplug/nes_fds.h | 66 ++++ external/nsfplug/nes_mmc5.c | 384 +++++++++++++++++++ external/nsfplug/nes_mmc5.h | 58 +++ external/nsfplug/nes_n106.c | 310 +++++++++++++++ external/nsfplug/nes_n106.h | 54 +++ external/nsfplug/nes_vrc6.c | 225 +++++++++++ external/nsfplug/nes_vrc6.h | 41 ++ external/nsfplug/readme.txt | 81 ++++ 15 files changed, 2897 insertions(+), 7 deletions(-) create mode 100644 external/nsfplug/nes_apu.c create mode 100644 external/nsfplug/nes_apu.h create mode 100644 external/nsfplug/nes_dmc.c create mode 100644 external/nsfplug/nes_dmc.h create mode 100644 external/nsfplug/nes_fds.c create mode 100644 external/nsfplug/nes_fds.h create mode 100644 external/nsfplug/nes_mmc5.c create mode 100644 external/nsfplug/nes_mmc5.h create mode 100644 external/nsfplug/nes_n106.c create mode 100644 external/nsfplug/nes_n106.h create mode 100644 external/nsfplug/nes_vrc6.c create mode 100644 external/nsfplug/nes_vrc6.h create mode 100644 external/nsfplug/readme.txt diff --git a/build-wasm.ps1 b/build-wasm.ps1 index 8b610189..28de3c37 100644 --- a/build-wasm.ps1 +++ b/build-wasm.ps1 @@ -3,12 +3,20 @@ if ($env:EMSDK) { if (Test-Path $envScript) { & $envScript } } -$emccArgs = "external/ayumi/ayumi.c", "-o", "public/ayumi.wasm", "-O3", +$emccArgs = "-O3", "-s", "WASM=1", - "-s", "EXPORTED_FUNCTIONS=`"[\`"_ayumi_configure\`", \`"_ayumi_set_pan\`", \`"_ayumi_set_tone\`", \`"_ayumi_set_noise\`", \`"_ayumi_set_mixer\`", \`"_ayumi_set_volume\`", \`"_ayumi_set_timer_effect\`", \`"_ayumi_set_timer_effect_slot\`", \`"_ayumi_set_timer_effect_waveform\`", \`"_ayumi_timer_effect_reset\`", \`"_ayumi_get_timer_effect_active_period\`", \`"_ayumi_get_registers\`", \`"_ayumi_struct_size\`", \`"_ayumi_set_envelope\`", \`"_ayumi_set_envelope_shape\`", \`"_ayumi_process\`", \`"_ayumi_remove_dc\`", \`"_malloc\`", \`"_free\`"]`"", "-s", "ALLOW_MEMORY_GROWTH=1", "-s", "INITIAL_MEMORY=16777216", "-s", "MAXIMUM_MEMORY=16777216", "-s", "ENVIRONMENT=web", "-s", "STANDALONE_WASM=1", "--no-entry" +$ayumiArgs = "external/ayumi/ayumi.c", "-o", "public/ayumi.wasm", + "-s", "EXPORTED_FUNCTIONS=`"[\`"_ayumi_configure\`", \`"_ayumi_set_pan\`", \`"_ayumi_set_tone\`", \`"_ayumi_set_noise\`", \`"_ayumi_set_mixer\`", \`"_ayumi_set_volume\`", \`"_ayumi_set_timer_effect\`", \`"_ayumi_set_timer_effect_slot\`", \`"_ayumi_set_timer_effect_waveform\`", \`"_ayumi_timer_effect_reset\`", \`"_ayumi_get_timer_effect_active_period\`", \`"_ayumi_get_registers\`", \`"_ayumi_struct_size\`", \`"_ayumi_set_envelope\`", \`"_ayumi_set_envelope_shape\`", \`"_ayumi_process\`", \`"_ayumi_remove_dc\`", \`"_malloc\`", \`"_free\`"]`"", + +$nesApuArgs = "external/nsfplug/nes_apu.c", "external/nsfplug/nes_dmc.c", "-o", "public/nes_apu.wasm", + "-s", "EXPORTED_FUNCTIONS=`"[\`"_nes_apu_Init\`", \`"_nes_apu_Reset\`", \`"_nes_apu_Tick\`", \`"_nes_apu_Render\`", \`"_nes_apu_Write\`", \`"_nes_apu_SetMask\`", \`"_nes_apu_SetStereoMix\`", \`"_nes_dmc_Init\`", \`"_nes_dmc_Reset\`", \`"_nes_dmc_Tick\`", \`"_nes_dmc_Render\`", \`"_nes_dmc_Write\`", \`"_nes_dmc_SetMask\`", \`"_nes_dmc_SetStereoMix\`", \`"_nes_dmc_SetPal\`", \`"_nes_dmc_SetAPU\`", \`"_nes_dmc_SetMemory_Read\`", \`"_nes_dmc_TickFrameSequence\`"]`"" + +$nesMmc5Args = "external/nsfplug/nes_mmc5.c", "-o", "public/nes_mmc5.wasm", + "-s", "EXPORTED_FUNCTIONS=`"[\`"_nes_mmc5_Init\`", \`"_nes_mmc5_Reset\`", \`"_nes_mmc5_Tick\`", \`"_nes_mmc5_Render\`", \`"_nes_mmc5_Write\`", \`"_nes_mmc5_SetMask\`", \`"_nes_mmc5_SetStereoMix\`", \`"_nes_mmc5_TickFrameSequence\`"]`"" + $python = $null foreach ($name in @('python', 'python3')) { $c = Get-Command $name -EA SilentlyContinue @@ -54,5 +62,9 @@ if (-not $python) { exit 1 } -& $python $emccPy @emccArgs +& $python $emccPy @emccArgs @ayumiArgs +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +& $python $emccPy @emccArgs @nesApuArgs +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +& $python $emccPy @emccArgs @nesMmc5Args if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } diff --git a/build-wasm.sh b/build-wasm.sh index 7647152c..5ea36216 100755 --- a/build-wasm.sh +++ b/build-wasm.sh @@ -10,14 +10,28 @@ if ! command -v emcc &> /dev/null; then exit 1 fi -emcc external/ayumi/ayumi.c \ - -o public/ayumi.wasm \ +EMCC_ARGS="\ -O3 \ -s WASM=1 \ - -s EXPORTED_FUNCTIONS='["_ayumi_configure", "_ayumi_set_pan", "_ayumi_set_tone", "_ayumi_set_noise", "_ayumi_set_mixer", "_ayumi_set_volume", "_ayumi_set_timer_effect", "_ayumi_set_timer_effect_slot", "_ayumi_set_timer_effect_waveform", "_ayumi_timer_effect_reset", "_ayumi_get_timer_effect_active_period", "_ayumi_get_registers", "_ayumi_struct_size", "_ayumi_set_envelope", "_ayumi_set_envelope_shape", "_ayumi_process", "_ayumi_remove_dc", "_malloc", "_free"]' \ -s ALLOW_MEMORY_GROWTH=1 \ -s INITIAL_MEMORY=16777216 \ -s MAXIMUM_MEMORY=16777216 \ -s ENVIRONMENT='web' \ -s STANDALONE_WASM=1 \ - --no-entry + --no-entry \ + " + +emcc ${EMCC_ARGS} \ + external/ayumi/ayumi.c \ + -o public/ayumi.wasm \ + -s EXPORTED_FUNCTIONS='["_ayumi_configure", "_ayumi_set_pan", "_ayumi_set_tone", "_ayumi_set_noise", "_ayumi_set_mixer", "_ayumi_set_volume", "_ayumi_set_timer_effect", "_ayumi_set_timer_effect_slot", "_ayumi_set_timer_effect_waveform", "_ayumi_timer_effect_reset", "_ayumi_get_timer_effect_active_period", "_ayumi_get_registers", "_ayumi_struct_size", "_ayumi_set_envelope", "_ayumi_set_envelope_shape", "_ayumi_process", "_ayumi_remove_dc", "_malloc", "_free"]' + +emcc ${EMCC_ARGS} \ + external/nsfplug/nes_apu.c external/nsfplug/nes_dmc.c \ + -o public/nes_apu.wasm \ + -s EXPORTED_FUNCTIONS='["_nes_apu_Init", "_nes_apu_Reset", "_nes_apu_Tick", "_nes_apu_Render", "_nes_apu_Write", "_nes_apu_SetMask", "_nes_apu_SetStereoMix", "_nes_dmc_Init", "_nes_dmc_Reset", "_nes_dmc_Tick", "_nes_dmc_Render", "_nes_dmc_Write", "_nes_dmc_SetMask", "_nes_dmc_SetStereoMix", "_nes_dmc_SetPal", "_nes_dmc_SetAPU", "_nes_dmc_SetMemory_Read", "_nes_dmc_TickFrameSequence"]' + +emcc ${EMCC_ARGS} \ + external/nsfplug/nes_mmc5.c \ + -o public/nes_mmc5.wasm \ + -s EXPORTED_FUNCTIONS='["_nes_mmc5_Init", "_nes_mmc5_Reset", "_nes_mmc5_Tick", "_nes_mmc5_Render", "_nes_mmc5_Write", "_nes_mmc5_SetMask", "_nes_mmc5_SetStereoMix", "_nes_mmc5_TickFrameSequence"]' diff --git a/external/nsfplug/nes_apu.c b/external/nsfplug/nes_apu.c new file mode 100644 index 00000000..b5fd9a59 --- /dev/null +++ b/external/nsfplug/nes_apu.c @@ -0,0 +1,368 @@ +// +// NES 2A03 +// +#include +#include "nes_apu.h" + +int32_t square_table[32]; // nonlinear mixer + +static void sweep_sqr (nes_apu_t *s, int ch); // calculates target sweep frequency +static int32_t calc_sqr (nes_apu_t *s, int ch, uint32_t clocks); + +void sweep_sqr (nes_apu_t *s, int i) +{ + int shifted = s->freq[i] >> s->sweep_amount[i]; + if (i == 0 && s->sweep_mode[i]) shifted += 1; + s->sfreq[i] = s->freq[i] + (s->sweep_mode[i] ? -shifted : shifted); + //DEBUG_OUT("shifted[%d] = %d (%d >> %d)\n",i,shifted,freq[i],sweep_amount[i]); +} + +void nes_apu_FrameSequence(nes_apu_t *s, int seq) +{ + //DEBUG_OUT("FrameSequence(%d)\n",s); + + if (seq > 3) return; // no operation in step 4 + + // 240hz clock + for (int i=0; i < 2; ++i) + { + bool divider = false; + if (s->envelope_write[i]) + { + s->envelope_write[i] = false; + s->envelope_counter[i] = 15; + s->envelope_div[i] = 0; + } + else + { + ++s->envelope_div[i]; + if (s->envelope_div[i] > s->envelope_div_period[i]) + { + divider = true; + s->envelope_div[i] = 0; + } + } + if (divider) + { + if (s->envelope_loop[i] && s->envelope_counter[i] == 0) + s->envelope_counter[i] = 15; + else if (s->envelope_counter[i] > 0) + --s->envelope_counter[i]; + } + } + + // 120hz clock + if ((seq&1) == 0) + for (int i=0; i < 2; ++i) + { + if (!s->envelope_loop[i] && (s->length_counter[i] > 0)) + --s->length_counter[i]; + + if (s->sweep_enable[i]) + { + //DEBUG_OUT("Clock sweep: %d\n", i); + + --s->sweep_div[i]; + if (s->sweep_div[i] <= 0) + { + sweep_sqr(s, i); // calculate new sweep target + + //DEBUG_OUT("sweep_div[%d] (0/%d)\n",i,sweep_div_period[i]); + //DEBUG_OUT("freq[%d]=%d > sfreq[%d]=%d\n",i,freq[i],i,sfreq[i]); + + if (s->freq[i] >= 8 && s->sfreq[i] < 0x800 && s->sweep_amount[i] > 0) // update frequency if appropriate + { + s->freq[i] = s->sfreq[i] < 0 ? 0 : s->sfreq[i]; + } + s->sweep_div[i] = s->sweep_div_period[i] + 1; + + //DEBUG_OUT("freq[%d]=%d\n",i,freq[i]); + } + + if (s->sweep_write[i]) + { + s->sweep_div[i] = s->sweep_div_period[i] + 1; + s->sweep_write[i] = false; + } + } + } + +} + +int32_t calc_sqr (nes_apu_t *s, int i, uint32_t clocks) +{ + static const int16_t sqrtbl[4][16] = { + {0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0}, + {1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} + }; + + s->scounter[i] -= clocks; + while (s->scounter[i] < 0) + { + s->sphase[i] = (s->sphase[i] + 1) & 15; + s->scounter[i] += s->freq[i] + 1; + } + + int32_t ret = 0; + if (s->length_counter[i] > 0 && + s->freq[i] >= 8 && + s->sfreq[i] < 0x800 + ) + { + int v = s->envelope_disable[i] ? s->volume[i] : s->envelope_counter[i]; + ret = sqrtbl[s->duty[i]][s->sphase[i]] ? v : 0; + } + + return ret; +} + +bool nes_apu_Read (nes_apu_t *s, uint32_t adr, uint32_t* val) +{ + // if (0x4000 <= adr && adr < 0x4008) + // { + // *val |= s->reg[adr&0x7]; + // return true; + // } + // else if(adr==0x4015) + if(adr==0x4015) + { + *val |= (s->length_counter[1]?2:0)|(s->length_counter[0]?1:0); + return true; + } + else + return false; +} + +void nes_apu_Tick (nes_apu_t *s, uint32_t clocks) +{ + s->out[0] = calc_sqr(s, 0, clocks); + s->out[1] = calc_sqr(s, 1, clocks); +} + +// 生成される波形の振幅は0-8191 +uint32_t nes_apu_Render (nes_apu_t *s, int32_t b[2]) +{ + s->out[0] = (s->mask & 1) ? 0 : s->out[0]; + s->out[1] = (s->mask & 2) ? 0 : s->out[1]; + + int32_t m[2]; + + if(s->option[NES_APU_OPT_NONLINEAR_MIXER]) + { + int32_t voltage = square_table[s->out[0] + s->out[1]]; + m[0] = s->out[0] << 6; + m[1] = s->out[1] << 6; + int32_t ref = m[0] + m[1]; + if (ref > 0) + { + m[0] = (m[0] * voltage) / ref; + m[1] = (m[1] * voltage) / ref; + } + else + { + m[0] = voltage; + m[1] = voltage; + } + } + else + { + m[0] = (s->out[0] * s->square_linear) / 15; + m[1] = (s->out[1] * s->square_linear) / 15; + } + + b[0] = m[0] * s->sm[0][0]; + b[0] += m[1] * s->sm[0][1]; + b[0] >>= 7; + + b[1] = m[0] * s->sm[1][0]; + b[1] += m[1] * s->sm[1][1]; + b[1] >>= 7; + + return 2; +} + +void nes_apu_Init (nes_apu_t *s) +{ + s->option[NES_APU_OPT_UNMUTE_ON_RESET] = true; + s->option[NES_APU_OPT_PHASE_REFRESH] = true; + s->option[NES_APU_OPT_NONLINEAR_MIXER] = true; + s->option[NES_APU_OPT_DUTY_SWAP] = false; + s->option[NES_APU_OPT_NEGATE_SWEEP_INIT] = false; + + square_table[0] = 0; + for(int i=1;i<32;i++) + square_table[i]=(int32_t)((8192.0*95.88)/(8128.0/i+100)); + + s->square_linear = square_table[15]; // match linear scale to one full volume square of nonlinear + + for(int c=0;c<2;++c) + for(int t=0;t<2;++t) + s->sm[c][t] = 128; +} + +void nes_apu_Reset (nes_apu_t *s) +{ + int i; + s->mask = 0; + + for (int i=0; i<2; ++i) + { + s->scounter[i] = 0; + s->sphase[i] = 0; + s->duty[i] = 0; + s->volume[i] = 0; + s->freq[i] = 0; + s->sfreq[i] = 0; + s->sweep_enable[i] = 0; + s->sweep_mode[i] = 0; + s->sweep_write[i] = 0; + s->sweep_div_period[i] = 0; + s->sweep_div[i] = 1; + s->sweep_amount[i] = 0; + s->envelope_disable[i] = 0; + s->envelope_loop[i] = 0; + s->envelope_write[i] = 0; + s->envelope_div_period[i] = 0; + s->envelope_div[0] = 0; + s->envelope_counter[i] = 0; + s->length_counter[i] = 0; + s->enable[i] = 0; + } + + for (i = 0x4000; i < 0x4008; i++) + nes_apu_Write (s, i, 0); + + nes_apu_Write (s, 0x4015, 0); + if (s->option[NES_APU_OPT_UNMUTE_ON_RESET]) + nes_apu_Write (s, 0x4015, 0x0f); + if (s->option[NES_APU_OPT_NEGATE_SWEEP_INIT]) + { + nes_apu_Write (s, 0x4001, 0x08); + nes_apu_Write (s, 0x4005, 0x08); + } + + for (i = 0; i < 2; i++) + s->out[i] = 0; +} + +void nes_apu_SetOption (nes_apu_t *s, int id, int val) +{ + if(idoption[id] = val; +} + +void nes_apu_SetStereoMix(nes_apu_t *s, int trk, int32_t mixl, int32_t mixr) +{ + if (trk < 0) return; + if (trk > 1) return; + s->sm[0][trk] = mixl; + s->sm[1][trk] = mixr; +} + +bool nes_apu_Write (nes_apu_t *s, uint32_t adr, uint32_t val) +{ + int ch; + + static const uint8_t length_table[32] = { + 0x0A, 0xFE, + 0x14, 0x02, + 0x28, 0x04, + 0x50, 0x06, + 0xA0, 0x08, + 0x3C, 0x0A, + 0x0E, 0x0C, + 0x1A, 0x0E, + 0x0C, 0x10, + 0x18, 0x12, + 0x30, 0x14, + 0x60, 0x16, + 0xC0, 0x18, + 0x48, 0x1A, + 0x10, 0x1C, + 0x20, 0x1E + }; + + if (0x4000 <= adr && adr < 0x4008) + { + //DEBUG_OUT("$%04X = %02X\n",adr,val); + + adr &= 0xf; + ch = adr >> 2; + switch (adr) + { + case 0x0: + case 0x4: + s->volume[ch] = val & 15; + s->envelope_disable[ch] = (val >> 4) & 1; + s->envelope_loop[ch] = (val >> 5) & 1; + s->envelope_div_period[ch] = (val & 15); + s->duty[ch] = (val >> 6) & 3; + if (s->option[NES_APU_OPT_DUTY_SWAP]) + { + if (s->duty[ch] == 1) s->duty[ch] = 2; + else if (s->duty[ch] == 2) s->duty[ch] = 1; + } + break; + + case 0x1: + case 0x5: + s->sweep_enable[ch] = (val >> 7) & 1; + s->sweep_div_period[ch] = (((val >> 4) & 7)); + s->sweep_mode[ch] = (val >> 3) & 1; + s->sweep_amount[ch] = val & 7; + s->sweep_write[ch] = true; + sweep_sqr(s, ch); + break; + + case 0x2: + case 0x6: + s->freq[ch] = val | (s->freq[ch] & 0x700) ; + sweep_sqr(s, ch); + break; + + case 0x3: + case 0x7: + s->freq[ch] = (s->freq[ch] & 0xFF) | ((val & 0x7) << 8) ; + if (s->option[NES_APU_OPT_PHASE_REFRESH]) + s->sphase[ch] = 0; + s->envelope_write[ch] = true; + if (s->enable[ch]) + { + s->length_counter[ch] = length_table[(val >> 3) & 0x1f]; + } + sweep_sqr(s, ch); + break; + + default: + return false; + } + // s->reg[adr] = val; + return true; + } + else if (adr == 0x4015) + { + s->enable[0] = (val & 1) ? true : false; + s->enable[1] = (val & 2) ? true : false; + + if (!s->enable[0]) + s->length_counter[0] = 0; + if (!s->enable[1]) + s->length_counter[1] = 0; + + // s->reg[adr-0x4000] = val; + return true; + } + + // 4017 is handled in nes_dmc.cpp + //else if (adr == 0x4017) + //{ + //} + + return false; +} + +void nes_apu_SetMask(nes_apu_t* s, int m) +{ + s->mask = m; +} diff --git a/external/nsfplug/nes_apu.h b/external/nsfplug/nes_apu.h new file mode 100644 index 00000000..46a093ff --- /dev/null +++ b/external/nsfplug/nes_apu.h @@ -0,0 +1,70 @@ +#ifndef _NES_APU_H_ +#define _NES_APU_H_ +#include "nes_dmc.h" + +/** Upper half of APU **/ +typedef struct nes_apu +{ + enum + { + NES_APU_OPT_UNMUTE_ON_RESET=0, + NES_APU_OPT_PHASE_REFRESH, + NES_APU_OPT_NONLINEAR_MIXER, + NES_APU_OPT_DUTY_SWAP, + NES_APU_OPT_NEGATE_SWEEP_INIT, + NES_APU_OPT_END }; + + enum + { SQR0_MASK = 1, SQR1_MASK = 2, }; + + int option[NES_APU_OPT_END]; // 各種オプション + int mask; + int32_t sm[2][2]; + + uint32_t gclock; + // uint8_t reg[0x20]; + int32_t out[2]; + double rate, clock; + + int32_t square_linear; // linear mix approximation + + int scounter[2]; // frequency divider + int sphase[2]; // phase counter + + int duty[2]; + int volume[2]; + int freq[2]; + int sfreq[2]; + + bool sweep_enable[2]; + bool sweep_mode[2]; + bool sweep_write[2]; + int sweep_div_period[2]; + int sweep_div[2]; + int sweep_amount[2]; + + bool envelope_disable[2]; + bool envelope_loop[2]; + bool envelope_write[2]; + int envelope_div_period[2]; + int envelope_div[2]; + int envelope_counter[2]; + + int length_counter[2]; + + bool enable[2]; +} nes_apu_t; + +void nes_apu_FrameSequence(nes_apu_t *s, int seq); + +void nes_apu_Init (nes_apu_t *s); +void nes_apu_Reset (nes_apu_t *s); +void nes_apu_Tick (nes_apu_t *s, uint32_t clocks); +uint32_t nes_apu_Render (nes_apu_t *s, int32_t b[2]); +bool nes_apu_Read (nes_apu_t *s, uint32_t adr, uint32_t* val); +bool nes_apu_Write (nes_apu_t *s, uint32_t adr, uint32_t val); +void nes_apu_SetOption (nes_apu_t *s, int id, int b); +void nes_apu_SetMask(nes_apu_t *s, int m); +void nes_apu_SetStereoMix (nes_apu_t *s, int trk, int32_t mixl, int32_t mixr); + +#endif diff --git a/external/nsfplug/nes_dmc.c b/external/nsfplug/nes_dmc.c new file mode 100644 index 00000000..8054e2b6 --- /dev/null +++ b/external/nsfplug/nes_dmc.c @@ -0,0 +1,725 @@ +#include "nes_dmc.h" +#include "nes_apu.h" +#include +#include + +static const uint32_t wavlen_table[2][16] = { +{ // NTSC + 4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068 +}, +{ // PAL + 4, 8, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778 +}}; + +static const uint32_t freq_table[2][16] = { +{ // NTSC + 428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54 +}, +{ // PAL + 398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50 +}}; + +static const uint32_t BITREVERSE[256] = { + 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, + 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, + 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, + 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, + 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, + 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, + 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, + 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, + 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1, + 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, + 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, + 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD, + 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, + 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB, + 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, + 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF, +}; + +uint32_t tnd_table[2][16][16][128]; + +static uint32_t calc_tri (nes_dmc_t* s, uint32_t clocks); +static uint32_t calc_dmc (nes_dmc_t* s, uint32_t clocks); +static uint32_t calc_noise (nes_dmc_t* s, uint32_t clocks); + +void nes_dmc_Init (nes_dmc_t* s) +{ + nes_dmc_SetPal (s, false); + s->option[NES_DMC_OPT_ENABLE_4011] = 1; + s->option[NES_DMC_OPT_ENABLE_PNOISE] = 1; + s->option[NES_DMC_OPT_UNMUTE_ON_RESET] = 1; + s->option[NES_DMC_OPT_DPCM_ANTI_CLICK] = 0; + s->option[NES_DMC_OPT_NONLINEAR_MIXER] = 1; + s->option[NES_DMC_OPT_RANDOMIZE_NOISE] = 1; + s->option[NES_DMC_OPT_RANDOMIZE_TRI] = 1; + s->option[NES_DMC_OPT_TRI_MUTE] = 1; + s->option[NES_DMC_OPT_DPCM_REVERSE] = 0; + tnd_table[0][0][0][0] = 0; + tnd_table[1][0][0][0] = 0; + + s->apu = NULL; + s->frame_sequence_count = 0; + s->frame_sequence_length = 7458; + s->frame_sequence_steps = 4; + + for(int c=0;c<2;++c) + for(int t=0;t<3;++t) + s->sm[c][t] = 128; +} + +void nes_dmc_SetStereoMix(nes_dmc_t* s, int trk, int32_t mixl, int32_t mixr) +{ + if (trk < 0) return; + if (trk > 2) return; + s->sm[0][trk] = mixl; + s->sm[1][trk] = mixr; +} + +void nes_dmc_FrameSequence(nes_dmc_t* s, int seq) +{ + //DEBUG_OUT("FrameSequence: %d\n",s); + + if (seq > 3) return; // no operation in step 4 + + if (s->apu) + { + nes_apu_FrameSequence(s->apu, seq); + } + + if (seq == 0 && (s->frame_sequence_steps == 4)) + { + if (s->frame_irq_enable) s->frame_irq = true; + // s->cpu->UpdateIRQ(NES_CPU::IRQD_FRAME, s->frame_irq & s->frame_irq_enable); + } + + // 240hz clock + { + // triangle linear counter + if (s->linear_counter_halt) + { + s->linear_counter = s->linear_counter_reload; + } + else + { + if (s->linear_counter > 0) --s->linear_counter; + } + if (!s->linear_counter_control) + { + s->linear_counter_halt = false; + } + + // noise envelope + bool divider = false; + if (s->envelope_write) + { + s->envelope_write = false; + s->envelope_counter = 15; + s->envelope_div = 0; + } + else + { + ++s->envelope_div; + if (s->envelope_div > s->envelope_div_period) + { + divider = true; + s->envelope_div = 0; + } + } + if (divider) + { + if (s->envelope_loop && s->envelope_counter == 0) + s->envelope_counter = 15; + else if (s->envelope_counter > 0) + --s->envelope_counter; + } + } + + // 120hz clock + if ((seq&1) == 0) + { + // triangle length counter + if (!s->linear_counter_control && (s->length_counter[0] > 0)) + --s->length_counter[0]; + + // noise length counter + if (!s->envelope_loop && (s->length_counter[1] > 0)) + --s->length_counter[1]; + } + +} + +// 三角波チャンネルの計算 戻り値は0-15 +uint32_t calc_tri (nes_dmc_t* s, uint32_t clocks) +{ + static uint32_t tritbl[32] = + { + 15,14,13,12,11,10, 9, 8, + 7, 6, 5, 4, 3, 2, 1, 0, + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9,10,11,12,13,14,15, + }; + + if (s->linear_counter > 0 && s->length_counter[0] > 0 + && (!s->option[NES_DMC_OPT_TRI_MUTE] || s->tri_freq > 0)) + { + s->counter[0] -= clocks; + while (s->counter[0] < 0) + { + s->tphase = (s->tphase + 1) & 31; + s->counter[0] += (s->tri_freq + 1); + } + } + + uint32_t ret = tritbl[s->tphase]; + return ret; +} + +// ノイズチャンネルの計算 戻り値は0-127 +// 低サンプリングレートで合成するとエイリアスノイズが激しいので +// ノイズだけはこの関数内で高クロック合成し、簡易なサンプリングレート +// 変換を行っている。 +uint32_t calc_noise(nes_dmc_t* s, uint32_t clocks) +{ + uint32_t env = s->envelope_disable ? s->noise_volume : s->envelope_counter; + if (s->length_counter[1] < 1) env = 0; + + uint32_t last = (s->noise & 0x4000) ? 0 : env; + if (clocks < 1) return last; + + // simple anti-aliasing (noise requires it, even when oversampling is off) + uint32_t count = 0; + uint32_t accum = s->counter[1] * last; // samples pending from previous calc + uint32_t accum_clocks = s->counter[1]; + #ifdef _DEBUG + int32_t start_clocks = counter[1]; + #endif + if (s->counter[1] < 0) // only happens on startup when using the randomize noise option + { + accum = 0; + accum_clocks = 0; + } + + s->counter[1] -= clocks; + assert (s->nfreq > 0); // prevent infinite loop + while (s->counter[1] < 0) + { + // tick the noise generator + uint32_t feedback = (s->noise&1) ^ ((s->noise&s->noise_tap)?1:0); + s->noise = (s->noise>>1) | (feedback<<14); + + last = (s->noise & 0x4000) ? 0 : env; + accum += (last * s->nfreq); + s->counter[1] += s->nfreq; + ++count; + accum_clocks += s->nfreq; + } + + if (count < 1) // no change over interval, don't anti-alias + { + return last; + } + + accum -= (last * s->counter[1]); // remove these samples which belong in the next calc + accum_clocks -= s->counter[1]; + #ifdef _DEBUG + if (start_clocks >= 0) assert(accum_clocks == clocks); // these should be equal + #endif + + uint32_t average = accum / accum_clocks; + assert(average <= 15); // above this would indicate overflow + return average; +} + +// Tick the DMC for the number of clocks, and return output counter; +uint32_t calc_dmc (nes_dmc_t* s, uint32_t clocks) +{ + s->counter[2] -= clocks; + assert (s->dfreq > 0); // prevent infinite loop + while (s->counter[2] < 0) + { + s->counter[2] += s->dfreq; + + if ( s->data > 0x100 ) // data = 0x100 when shift register is empty + { + if (!s->empty) + { + if ((s->data & 1) && (s->damp < 63)) + s->damp++; + else if (!(s->data & 1) && (0 < s->damp)) + s->damp--; + } + s->data >>=1; + } + + if ( s->data <= 0x100 ) // shift register is empty + { + if (s->dlength > 0) + { + s->memory_Read (s->daddress, &s->data); + // cpu->StealCycles(4); // DMC read takes 3 or 4 CPU cycles, usually 4 + // (checking for the 3-cycle case would require sub-instruction emulation) + s->data &= 0xFF; // read 8 bits + if (s->option[NES_DMC_OPT_DPCM_REVERSE]) s->data = BITREVERSE[s->data]; + s->data |= 0x10000; // use an extra bit to signal end of data + s->empty = false; + s->daddress = ((s->daddress+1)&0xFFFF)|0x8000 ; + --s->dlength; + if (s->dlength == 0) + { + if (s->mode & 1) // looped DPCM = auto-reload + { + s->daddress = ((s->adr_reg<<6)|0xC000); + s->dlength = (s->len_reg<<4)+1; + } + else if (s->mode & 2) // IRQ and not looped + { + s->irq = true; + // s->cpu->UpdateIRQ(NES_CPU::IRQD_DMC, true); + } + } + } + else + { + s->data = 0x10000; // DMC will do nothing + s->empty = true; + } + } + } + + return (s->damp<<1) + s->dac_lsb; +} + +void nes_dmc_TickFrameSequence (nes_dmc_t* s, uint32_t clocks) +{ + s->frame_sequence_count += clocks; + while (s->frame_sequence_count > s->frame_sequence_length) + { + nes_dmc_FrameSequence(s, s->frame_sequence_step); + s->frame_sequence_count -= s->frame_sequence_length; + ++s->frame_sequence_step; + if(s->frame_sequence_step >= s->frame_sequence_steps) + s->frame_sequence_step = 0; + } +} + +void nes_dmc_Tick (nes_dmc_t* s, uint32_t clocks) +{ + s->out[0] = calc_tri(s, clocks); + s->out[1] = calc_noise(s, clocks); + s->out[2] = calc_dmc(s, clocks); +} + +uint32_t nes_dmc_Render (nes_dmc_t* s, int32_t b[2]) +{ + s->out[0] = (s->mask & 1) ? 0 : s->out[0]; + s->out[1] = (s->mask & 2) ? 0 : s->out[1]; + s->out[2] = (s->mask & 4) ? 0 : s->out[2]; + + int32_t m[3]; + m[0] = tnd_table[0][s->out[0]][0][0]; + m[1] = tnd_table[0][0][s->out[1]][0]; + m[2] = tnd_table[0][0][0][s->out[2]]; + + if (s->option[NES_DMC_OPT_NONLINEAR_MIXER]) + { + int32_t ref = m[0] + m[1] + m[2]; + int32_t voltage = tnd_table[1][s->out[0]][s->out[1]][s->out[2]]; + if (ref) + { + for (int i=0; i < 3; ++i) + m[i] = (m[i] * voltage) / ref; + } + else + { + for (int i=0; i < 3; ++i) + m[i] = voltage; + } + } + + // anti-click nullifies any 4011 write but preserves nonlinearity + if (s->option[NES_DMC_OPT_DPCM_ANTI_CLICK]) + { + if (s->dmc_pop) // $4011 will cause pop this frame + { + // adjust offset to counteract pop + s->dmc_pop_offset += s->dmc_pop_follow - m[2]; + s->dmc_pop = false; + + // prevent overflow, keep headspace at edges + const int32_t OFFSET_MAX = (1 << 30) - (4 << 16); + if (s->dmc_pop_offset > OFFSET_MAX) s->dmc_pop_offset = OFFSET_MAX; + if (s->dmc_pop_offset < -OFFSET_MAX) s->dmc_pop_offset = -OFFSET_MAX; + } + s->dmc_pop_follow = m[2]; // remember previous position + + m[2] += s->dmc_pop_offset; // apply offset + + // TODO implement this in a better way + // roll off offset (not ideal, but prevents overflow) + if (s->dmc_pop_offset > 0) --s->dmc_pop_offset; + else if (s->dmc_pop_offset < 0) ++s->dmc_pop_offset; + } + + b[0] = m[0] * s->sm[0][0]; + b[0] += m[1] * s->sm[0][1]; + b[0] += m[2] * s->sm[0][2]; + b[0] >>= 7; + + b[1] = m[0] * s->sm[1][0]; + b[1] += m[1] * s->sm[1][1]; + b[1] += m[2] * s->sm[1][2]; + b[1] >>= 7; + + return 2; +} + +void nes_dmc_SetPal (nes_dmc_t* s, bool is_pal) +{ + s->pal = (is_pal ? 1 : 0); + // set CPU cycles in frame_sequence + s->frame_sequence_length = is_pal ? 8314 : 7458; +} + +void nes_dmc_SetAPU (nes_dmc_t* s, nes_apu_t* apu_) +{ + s->apu = apu_; +} + +// Initializing TRI, NOISE, DPCM mixing table +void nes_dmc_InitializeTNDTable(double wt, double wn, double wd) { + + // volume adjusted by 0.95 based on empirical measurements + const double MASTER = 8192.0 * 0.95; + // truthfully, the nonlinear curve does not appear to match well + // with my tests. Do more testing of the APU/DMC DAC later. + // this value keeps the triangle consistent with measured levels, + // but not necessarily the rest of this APU channel, + // because of the lack of a good DAC model, currently. + + { // Linear Mixer + for(int t=0; t<16 ; t++) { + for(int n=0; n<16; n++) { + for(int d=0; d<128; d++) { + tnd_table[0][t][n][d] = (uint32_t)(MASTER*(3.0*t+2.0*n+d)/208.0); + } + } + } + } + { // Non-Linear Mixer + tnd_table[1][0][0][0] = 0; + for(int t=0; t<16 ; t++) { + for(int n=0; n<16; n++) { + for(int d=0; d<128; d++) { + if(t!=0||n!=0||d!=0) + tnd_table[1][t][n][d] = (uint32_t)((MASTER*159.79)/(100.0+1.0/((double)t/wt+(double)n/wn+(double)d/wd))); + } + } + } + } + +} + +void nes_dmc_Reset (nes_dmc_t* s) +{ + int i; + s->mask = 0; + + nes_dmc_InitializeTNDTable(8227,12241,22638); + + s->counter[0] = 0; + s->counter[1] = 0; + s->counter[2] = 0; + s->tphase = 0; + s->nfreq = wavlen_table[0][0]; + s->dfreq = freq_table[0][0]; + s->tri_freq = 0; + s->linear_counter = 0; + s->linear_counter_reload = 0; + s->linear_counter_halt = 0; + s->linear_counter_control = 0; + s->noise_volume = 0; + s->noise = 0; + s->noise_tap = 0; + s->envelope_loop = 0; + s->envelope_disable = 0; + s->envelope_write = 0; + s->envelope_div_period = 0; + s->envelope_div = 0; + s->envelope_counter = 0; + s->enable[0] = 0; + s->enable[1] = 0; + s->length_counter[0] = 0; + s->length_counter[1] = 0; + s->frame_irq = false; + s->frame_irq_enable = false; + s->frame_sequence_count = 0; + s->frame_sequence_steps = 4; + s->frame_sequence_step = 0; + // s->cpu->UpdateIRQ(NES_CPU::IRQD_FRAME, false); + + for (i = 0; i < 0x0F; i++) + nes_dmc_Write (s, 0x4008 + i, 0); + nes_dmc_Write (s, 0x4017, 0x40); + + s->irq = false; + nes_dmc_Write (s, 0x4015, 0x00); + if (s->option[NES_DMC_OPT_UNMUTE_ON_RESET]) + nes_dmc_Write (s, 0x4015, 0x0f); + // s->cpu->UpdateIRQ(NES_CPU::IRQD_DMC, false); + + s->out[0] = s->out[1] = s->out[2] = 0; + s->damp = 0; + s->dmc_pop = false; + s->dmc_pop_offset = 0; + s->dmc_pop_follow = 0; + s->dac_lsb = 0; + s->data = 0x100; + s->empty = true; + s->adr_reg = 0; + s->dlength = 0; + s->len_reg = 0; + s->daddress = 0; + s->noise = 1; + s->noise_tap = (1<<1); + + if (s->option[NES_DMC_OPT_RANDOMIZE_NOISE]) + { + s->noise |= rand(); + s->counter[1] = -(rand() & 511); + } + if (s->option[NES_DMC_OPT_RANDOMIZE_TRI]) + { + s->tphase = rand() & 31; + s->counter[0] = -(rand() & 2047); + } +} + +void nes_dmc_SetMemory_Read (nes_dmc_t* s, read_func * r) +{ + s->memory_Read = r; +} + +void nes_dmc_SetOption (nes_dmc_t* s, int id, int val) +{ + if(idoption[id] = val; + if(id==NES_DMC_OPT_NONLINEAR_MIXER) + nes_dmc_InitializeTNDTable(8227,12241,22638); + } +} + +bool nes_dmc_Write (nes_dmc_t* s, uint32_t adr, uint32_t val) +{ + static const uint8_t length_table[32] = { + 0x0A, 0xFE, + 0x14, 0x02, + 0x28, 0x04, + 0x50, 0x06, + 0xA0, 0x08, + 0x3C, 0x0A, + 0x0E, 0x0C, + 0x1A, 0x0E, + 0x0C, 0x10, + 0x18, 0x12, + 0x30, 0x14, + 0x60, 0x16, + 0xC0, 0x18, + 0x48, 0x1A, + 0x10, 0x1C, + 0x20, 0x1E + }; + + if (adr == 0x4015) + { + s->enable[0] = (val & 4) ? true : false; + s->enable[1] = (val & 8) ? true : false; + + if (!s->enable[0]) + { + s->length_counter[0] = 0; + } + if (!s->enable[1]) + { + s->length_counter[1] = 0; + } + + if ((val & 16) && s->dlength == 0) + { + s->daddress = (0xC000 | (s->adr_reg << 6)); + s->dlength = (s->len_reg << 4) + 1; + } + else if (!(val & 16)) + { + s->dlength = 0; + } + + s->irq = false; + // s->cpu->UpdateIRQ(NES_CPU::IRQD_DMC, false); + + s->reg[adr-0x4008] = val; + return true; + } + + if (adr == 0x4017) + { + //DEBUG_OUT("4017 = %02X\n", val); + s->frame_irq_enable = ((val & 0x40) != 0x40); + if (s->frame_irq_enable) s->frame_irq = false; + // s->cpu->UpdateIRQ(NES_CPU::IRQD_FRAME, false); + + s->frame_sequence_count = 0; + if (val & 0x80) + { + s->frame_sequence_steps = 5; + s->frame_sequence_step = 0; + nes_dmc_FrameSequence(s, s->frame_sequence_step); + ++s->frame_sequence_step; + } + else + { + s->frame_sequence_steps = 4; + s->frame_sequence_step = 1; + } + } + + if (adr<0x4008||0x4013reg[adr-0x4008] = val&0xff; + + //DEBUG_OUT("$%04X %02X\n", adr, val); + + switch (adr) + { + + // tri + + case 0x4008: + s->linear_counter_control = (val >> 7) & 1; + s->linear_counter_reload = val & 0x7F; + break; + + case 0x4009: + break; + + case 0x400a: + s->tri_freq = val | (s->tri_freq & 0x700) ; + break; + + case 0x400b: + s->tri_freq = (s->tri_freq & 0xff) | ((val & 0x7) << 8) ; + s->linear_counter_halt = true; + if (s->enable[0]) + { + s->length_counter[0] = length_table[(val >> 3) & 0x1f]; + } + break; + + // noise + + case 0x400c: + s->noise_volume = val & 15; + s->envelope_div_period = val & 15; + s->envelope_disable = (val >> 4) & 1; + s->envelope_loop = (val >> 5) & 1; + break; + + case 0x400d: + break; + + case 0x400e: + if (s->option[NES_DMC_OPT_ENABLE_PNOISE]) + s->noise_tap = (val & 0x80) ? (1<<6) : (1<<1); + else + s->noise_tap = (1<<1); + s->nfreq = wavlen_table[s->pal][val&15]; + break; + + case 0x400f: + if (s->enable[1]) + { + s->length_counter[1] = length_table[(val >> 3) & 0x1f]; + } + s->envelope_write = true; + break; + + // dmc + + case 0x4010: + s->mode = (val >> 6) & 3; + if (!(s->mode & 2)) + { + s->irq = false; + // s->cpu->UpdateIRQ(NES_CPU::IRQD_DMC, false); + } + s->dfreq = freq_table[s->pal][val&15]; + break; + + case 0x4011: + if (s->option[NES_DMC_OPT_ENABLE_4011]) + { + s->damp = (val >> 1) & 0x3f; + s->dac_lsb = val & 1; + s->dmc_pop = true; + } + break; + + case 0x4012: + s->adr_reg = val&0xff; + // ここでdaddressは更新されない + break; + + case 0x4013: + s->len_reg = val&0xff; + // ここでlengthは更新されない + break; + + default: + return false; + } + + return true; +} + +bool nes_dmc_Read (nes_dmc_t* s, uint32_t adr, uint32_t* val) +{ + if (adr == 0x4015) + { + *val |=(s->irq ? 0x80 : 0) + | (s->frame_irq ? 0x40 : 0) + | ((s->dlength>0) ? 0x10 : 0) + | (s->length_counter[1] ? 0x08 : 0) + | (s->length_counter[0] ? 0x04 : 0) + ; + + s->frame_irq = false; + // s->cpu->UpdateIRQ(NES_CPU::IRQD_FRAME, false); + return true; + } + else if (0x4008<=adr&&adr<=0x4014) + { + *val |= s->reg[adr-0x4008]; + return true; + } + else + return false; +} + +// // IRQ support requires CPU read access +// void nes_dmc_SetCPU(nes_dmc_t* s, NES_CPU* cpu_) +// { +// s->cpu = cpu_; +// } + +int nes_dmc_GetDamp(nes_dmc_t* s) +{ + return (s->damp<<1)|s->dac_lsb; +} + +void nes_dmc_SetMask(nes_dmc_t* s, int m) +{ + s->mask = m; +} diff --git a/external/nsfplug/nes_dmc.h b/external/nsfplug/nes_dmc.h new file mode 100644 index 00000000..d2d94c1d --- /dev/null +++ b/external/nsfplug/nes_dmc.h @@ -0,0 +1,106 @@ +#ifndef _NES_DMC_H_ +#define _NES_DMC_H_ + +#include +#include + +typedef struct nes_apu nes_apu_t; // forward declaration +typedef bool(read_func)(uint32_t,uint32_t*); + +/** Bottom Half of APU **/ +typedef struct nes_dmc +{ + enum + { + NES_DMC_OPT_ENABLE_4011=0, + NES_DMC_OPT_ENABLE_PNOISE, + NES_DMC_OPT_UNMUTE_ON_RESET, + NES_DMC_OPT_DPCM_ANTI_CLICK, + NES_DMC_OPT_NONLINEAR_MIXER, + NES_DMC_OPT_RANDOMIZE_NOISE, + NES_DMC_OPT_TRI_MUTE, + NES_DMC_OPT_RANDOMIZE_TRI, + NES_DMC_OPT_DPCM_REVERSE, + NES_DMC_OPT_END + }; + + int option[NES_DMC_OPT_END]; + int mask; + int32_t sm[2][3]; + uint8_t reg[0x10]; + uint32_t len_reg; + uint32_t adr_reg; + read_func* memory_Read; + uint32_t out[3]; + uint32_t daddress; + uint32_t dlength; + uint32_t data; + bool empty; + uint16_t damp; + int dac_lsb; + bool dmc_pop; + int32_t dmc_pop_offset; + int32_t dmc_pop_follow; + double clock; + uint32_t rate; + int pal; + int mode; + bool irq; + + int32_t counter[3]; // frequency dividers + int tphase; // triangle phase + uint32_t nfreq; // noise frequency + uint32_t dfreq; // DPCM frequency + + uint32_t tri_freq; + int linear_counter; + int linear_counter_reload; + bool linear_counter_halt; + bool linear_counter_control; + + int noise_volume; + uint32_t noise, noise_tap; + + // noise envelope + bool envelope_loop; + bool envelope_disable; + bool envelope_write; + int envelope_div_period; + int envelope_div; + int envelope_counter; + + bool enable[2]; // tri/noise enable + int length_counter[2]; // 0=tri, 1=noise + + // frame sequencer + nes_apu_t* apu; // apu is clocked by DMC's frame sequencer + int frame_sequence_count; // current cycle count + int frame_sequence_length; // CPU cycles per FrameSequence + int frame_sequence_step; // current step of frame sequence + int frame_sequence_steps; // 4/5 steps per frame + bool frame_irq; + bool frame_irq_enable; + + // NES_CPU* cpu; // IRQ needs CPU access +} nes_dmc_t; + +void nes_dmc_InitializeTNDTable(double wt, double wn, double wd); +void nes_dmc_SetPal (nes_dmc_t* s, bool is_pal); +void nes_dmc_SetAPU (nes_dmc_t* s, nes_apu_t* apu_); +void nes_dmc_SetMemory_Read (nes_dmc_t* s, read_func* r); +void nes_dmc_FrameSequence(nes_dmc_t* s, int seq); +int nes_dmc_GetDamp(nes_dmc_t* s); +void nes_dmc_TickFrameSequence (nes_dmc_t* s, uint32_t clocks); + +void nes_dmc_Init (nes_dmc_t* s); +void nes_dmc_Reset (nes_dmc_t* s); +void nes_dmc_Tick (nes_dmc_t* s, uint32_t clocks); +uint32_t nes_dmc_Render (nes_dmc_t* s, int32_t b[2]); +bool nes_dmc_Write (nes_dmc_t* s, uint32_t adr, uint32_t val); +bool nes_dmc_Read (nes_dmc_t* s, uint32_t adr, uint32_t* val); +void nes_dmc_SetOption (nes_dmc_t* s, int id, int b); +void nes_dmc_SetStereoMix (nes_dmc_t* s, int trk, int32_t mixl, int32_t mixr); + +// void nes_dmc_SetCPU(nes_dmc_t* s, NES_CPU* cpu_); + +#endif diff --git a/external/nsfplug/nes_fds.c b/external/nsfplug/nes_fds.c new file mode 100644 index 00000000..3a354a45 --- /dev/null +++ b/external/nsfplug/nes_fds.c @@ -0,0 +1,376 @@ +#include +#include +#include "nes_fds.h" + +const int RC_BITS = 12; +const double DEFAULT_RATE = 48000; + +void nes_fds_Init (nes_fds_t* s) +{ + s->option[NES_FDS_OPT_CUTOFF] = 2000; + s->option[NES_FDS_OPT_4085_RESET] = 0; + s->option[NES_FDS_OPT_WRITE_PROTECT] = 0; // not used here, see nsfplay.cpp + + s->rc_k = 0; + s->rc_l = (1<sm[0] = 128; + s->sm[1] = 128; + + nes_fds_Reset(s); +} + +void nes_fds_SetStereoMix(nes_fds_t* s, int trk, int32_t mixl, int32_t mixr) +{ + if (trk < 0) return; + if (trk > 1) return; + s->sm[0] = mixl; + s->sm[1] = mixr; +} + +void nes_fds_SetRate (nes_fds_t* s, double r) +{ + s->rate = r; + + // configure lowpass filter + double cutoff = (double)(s->option[NES_FDS_OPT_CUTOFF]); + double leak = 0.0; + if (cutoff > 0) + leak = exp(-2.0 * 3.14159 * cutoff / s->rate); + s->rc_k = (int32_t)(leak * (double)(1<rc_l = (1<rc_k; +} + +void nes_fds_SetOption (nes_fds_t* s, int id, int val) +{ + if(idoption[id] = val; + + // update cutoff immediately + if (id == NES_FDS_OPT_CUTOFF) nes_fds_SetRate(s, s->rate); +} + +void nes_fds_Reset (nes_fds_t* s) +{ + s->master_io = true; + s->master_vol = 0; + s->last_freq = 0; + s->last_vol = 0; + + s->rc_accum = 0; + + for (int i=0; i<2; ++i) + { + memset(s->wave[i], 0, sizeof(s->wave[i])); + s->freq[i] = 0; + s->phase[i] = 0; + } + s->wav_write = false; + s->wav_halt = true; + s->env_halt = true; + s->mod_halt = true; + s->mod_pos = 0; + s->mod_write_pos = 0; + + for (int i=0; i<2; ++i) + { + s->env_mode[i] = false; + s->env_disable[i] = true; + s->env_timer[i] = 0; + s->env_speed[i] = 0; + s->env_out[i] = 0; + } + s->master_env_speed = 0xFF; + + // NOTE: the FDS BIOS reset only does the following related to audio: + // $4023 = $00 + // $4023 = $83 enables master_io + // $4080 = $80 output volume = 0, envelope disabled + // $408A = $E8 master envelope speed + nes_fds_Write(s, 0x4023, 0x00); + nes_fds_Write(s, 0x4023, 0x83); + nes_fds_Write(s, 0x4080, 0x80); + nes_fds_Write(s, 0x408A, 0xE8); + + // reset other stuff + nes_fds_Write(s, 0x4082, 0x00); // wav freq 0 + nes_fds_Write(s, 0x4083, 0x80); // wav disable + nes_fds_Write(s, 0x4084, 0x80); // mod strength 0 + nes_fds_Write(s, 0x4085, 0x00); // mod position 0 + nes_fds_Write(s, 0x4086, 0x00); // mod freq 0 + nes_fds_Write(s, 0x4087, 0x80); // mod disable + nes_fds_Write(s, 0x4089, 0x00); // wav write disable, max global volume} +} + +void nes_fds_Tick (nes_fds_t* s, uint32_t clocks) +{ + // clock envelopes + if (!s->env_halt && !s->wav_halt && (s->master_env_speed != 0)) + { + for (int i=0; i<2; ++i) + { + if (!s->env_disable[i]) + { + s->env_timer[i] += clocks; + uint32_t period = ((s->env_speed[i]+1) * s->master_env_speed) << 3; + while (s->env_timer[i] >= period) + { + // clock the envelope + if (s->env_mode[i]) + { + if (s->env_out[i] < 32) ++s->env_out[i]; + } + else + { + if (s->env_out[i] > 0 ) --s->env_out[i]; + } + s->env_timer[i] -= period; + } + } + } + } + + // clock the mod table + if (!s->mod_halt) + { + // advance phase, adjust for modulator + uint32_t start_pos = s->phase[TMOD] >> 16; + s->phase[TMOD] += (clocks * s->freq[TMOD]); + uint32_t end_pos = s->phase[TMOD] >> 16; + + // wrap the phase to the 64-step table (+ 16 bit accumulator) + s->phase[TMOD] = s->phase[TMOD] & 0x3FFFFF; + + // execute all clocked steps + for (uint32_t p = start_pos; p < end_pos; ++p) + { + int32_t wv = s->wave[TMOD][p & 0x3F]; + if (wv == 4) // 4 resets mod position + s->mod_pos = 0; + else + { + const int32_t BIAS[8] = { 0, 1, 2, 4, 0, -4, -2, -1 }; + s->mod_pos += BIAS[wv]; + s->mod_pos &= 0x7F; // 7-bit clamp + } + } + } + + // clock the wav table + if (!s->wav_halt) + { + // complex mod calculation + int32_t mod = 0; + if (s->env_out[EMOD] != 0) // skip if modulator off + { + // convert mod_pos to 7-bit signed + int32_t pos = (s->mod_pos < 64) ? s->mod_pos : (s->mod_pos-128); + + // multiply pos by gain, + // shift off 4 bits but with odd "rounding" behaviour + int32_t temp = pos * s->env_out[EMOD]; + int32_t rem = temp & 0x0F; + temp >>= 4; + if ((rem > 0) && ((temp & 0x80) == 0)) + { + if (pos < 0) temp -= 1; + else temp += 2; + } + + // wrap if range is exceeded + while (temp >= 192) temp -= 256; + while (temp < -64) temp += 256; + + // multiply result by pitch, + // shift off 6 bits, round to nearest + temp = s->freq[TWAV] * temp; + rem = temp & 0x3F; + temp >>= 6; + if (rem >= 32) temp += 1; + + mod = temp; + } + + // advance wavetable position + int32_t f = s->freq[TWAV] + mod; + s->phase[TWAV] = s->phase[TWAV] + (clocks * f); + s->phase[TWAV] = s->phase[TWAV] & 0x3FFFFF; // wrap + + // store for trackinfo + s->last_freq = f; + } + + // output volume caps at 32 + int32_t vol_out = s->env_out[EVOL]; + if (vol_out > 32) vol_out = 32; + + // final output + if (!s->wav_write) + s->fout = s->wave[TWAV][(s->phase[TWAV]>>16)&0x3F] * vol_out; + + // NOTE: during wav_halt, the unit still outputs (at phase 0) + // and volume can affect it if the first sample is nonzero. + // haven't worked out 100% of the conditions for volume to + // effect (vol envelope does not seem to run, but am unsure) + // but this implementation is very close to correct + + // store for trackinfo + s->last_vol = vol_out; +} + +uint32_t nes_fds_Render (nes_fds_t* s, int32_t b[2]) +{ + // 8 bit approximation of master volume + const double MASTER_VOL = 2.4 * 1223.0; // max FDS vol vs max APU square (arbitrarily 1223) + const double MAX_OUT = 32.0f * 63.0f; // value that should map to master vol + const int32_t MASTER[4] = { + (int32_t)((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 2.0f), + (int32_t)((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 3.0f), + (int32_t)((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 4.0f), + (int32_t)((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 5.0f) }; + + int32_t v = s->fout * MASTER[s->master_vol] >> 8; + + // lowpass RC filter + int32_t rc_out = ((s->rc_accum * s->rc_k) + (v * s->rc_l)) >> RC_BITS; + s->rc_accum = rc_out; + v = rc_out; + + // output mix + int32_t m = s->mask ? 0 : v; + b[0] = (m * s->sm[0]) >> 7; + b[1] = (m * s->sm[1]) >> 7; + return 2; +} + +bool nes_fds_Write (nes_fds_t* s, uint32_t adr, uint32_t val) +{ + // $4023 master I/O enable/disable + if (adr == 0x4023) + { + s->master_io = ((val & 2) != 0); + return true; + } + + if (!s->master_io) + return false; + if (adr < 0x4040 || adr > 0x408A) + return false; + + if (adr < 0x4080) // $4040-407F wave table write + { + if (s->wav_write) + s->wave[TWAV][adr - 0x4040] = val & 0x3F; + return true; + } + + switch (adr & 0x00FF) + { + case 0x80: // $4080 volume envelope + s->env_disable[EVOL] = ((val & 0x80) != 0); + s->env_mode[EVOL] = ((val & 0x40) != 0); + s->env_timer[EVOL] = 0; + s->env_speed[EVOL] = val & 0x3F; + if (s->env_disable[EVOL]) + s->env_out[EVOL] = s->env_speed[EVOL]; + return true; + case 0x81: // $4081 --- + return false; + case 0x82: // $4082 wave frequency low + s->freq[TWAV] = (s->freq[TWAV] & 0xF00) | val; + return true; + case 0x83: // $4083 wave frequency high / enables + s->freq[TWAV] = (s->freq[TWAV] & 0x0FF) | ((val & 0x0F) << 8); + s->wav_halt = ((val & 0x80) != 0); + s->env_halt = ((val & 0x40) != 0); + if (s->wav_halt) + s->phase[TWAV] = 0; + if (s->env_halt) + { + s->env_timer[EMOD] = 0; + s->env_timer[EVOL] = 0; + } + return true; + case 0x84: // $4084 mod envelope + s->env_disable[EMOD] = ((val & 0x80) != 0); + s->env_mode[EMOD] = ((val & 0x40) != 0); + s->env_timer[EMOD] = 0; + s->env_speed[EMOD] = val & 0x3F; + if (s->env_disable[EMOD]) + s->env_out[EMOD] = s->env_speed[EMOD]; + return true; + case 0x85: // $4085 mod position + s->mod_pos = val & 0x7F; + // not hardware accurate., but prevents detune due to cycle inaccuracies + // (notably in Bio Miracle Bokutte Upa) + if (s->option[NES_FDS_OPT_4085_RESET]) + s->phase[TMOD] = s->mod_write_pos << 16; + return true; + case 0x86: // $4086 mod frequency low + s->freq[TMOD] = (s->freq[TMOD] & 0xF00) | val; + return true; + case 0x87: // $4087 mod frequency high / enable + s->freq[TMOD] = (s->freq[TMOD] & 0x0FF) | ((val & 0x0F) << 8); + s->mod_halt = ((val & 0x80) != 0); + if (s->mod_halt) + s->phase[TMOD] = s->phase[TMOD] & 0x3F0000; // reset accumulator phase + return true; + case 0x88: // $4088 mod table write + if (s->mod_halt) + { + // writes to current playback position (there is no direct way to set phase) + s->wave[TMOD][(s->phase[TMOD] >> 16) & 0x3F] = val & 0x07; + s->phase[TMOD] = (s->phase[TMOD] + 0x010000) & 0x3FFFFF; + s->wave[TMOD][(s->phase[TMOD] >> 16) & 0x3F] = val & 0x07; + s->phase[TMOD] = (s->phase[TMOD] + 0x010000) & 0x3FFFFF; + s->mod_write_pos = s->phase[TMOD] >> 16; // used by OPT_4085_RESET + } + return true; + case 0x89: // $4089 wave write enable, master volume + s->wav_write = ((val & 0x80) != 0); + s->master_vol = val & 0x03; + return true; + case 0x8A: // $408A envelope speed + s->master_env_speed = val; + // haven't tested whether this register resets phase on hardware, + // but this ensures my inplementation won't spam envelope clocks + // if this value suddenly goes low. + s->env_timer[EMOD] = 0; + s->env_timer[EVOL] = 0; + return true; + default: + return false; + } + return false; +} + +bool nes_fds_Read (nes_fds_t* s, uint32_t adr, uint32_t* val) +{ + if (adr >= 0x4040 && adr <= 0x407F) + { + // TODO: if wav_write is not enabled, the + // read address may not be reliable? need + // to test this on hardware. + *val = s->wave[TWAV][adr - 0x4040]; + return true; + } + + if (adr == 0x4090) // $4090 read volume envelope + { + *val = s->env_out[EVOL] | 0x40; + return true; + } + + if (adr == 0x4092) // $4092 read mod envelope + { + *val = s->env_out[EMOD] | 0x40; + return true; + } + + return false; +} + +void nes_mmc5_SetMask(nes_fds_t* s, int m) +{ + s->mask = m&1; +} diff --git a/external/nsfplug/nes_fds.h b/external/nsfplug/nes_fds.h new file mode 100644 index 00000000..ecab3161 --- /dev/null +++ b/external/nsfplug/nes_fds.h @@ -0,0 +1,66 @@ +#ifndef _NES_FDS_H_ +#define _NES_FDS_H_ + +#include +#include + +typedef struct nes_fds +{ + enum + { + NES_FDS_OPT_CUTOFF=0, + NES_FDS_OPT_4085_RESET, + NES_FDS_OPT_WRITE_PROTECT, + NES_FDS_OPT_END + }; + + double rate, clock; + int mask; + int32_t sm[2]; // stereo mix + int32_t fout; // current output + int option[NES_FDS_OPT_END]; + + bool master_io; + uint32_t master_vol; + uint32_t last_freq; // for trackinfo + uint32_t last_vol; // for trackinfo + + // two wavetables + enum { TMOD=0, TWAV=1 }; + int32_t wave[2][64]; + uint32_t freq[2]; + uint32_t phase[2]; + bool wav_write; + bool wav_halt; + bool env_halt; + bool mod_halt; + uint32_t mod_pos; + uint32_t mod_write_pos; + + // two ramp envelopes + enum { EMOD=0, EVOL=1 }; + bool env_mode[2]; + bool env_disable[2]; + uint32_t env_timer[2]; + uint32_t env_speed[2]; + uint32_t env_out[2]; + uint32_t master_env_speed; + + // 1-pole RC lowpass filter + int32_t rc_accum; + int32_t rc_k; + int32_t rc_l; +} nes_fds_t; + +void nes_fds_Init (nes_fds_t* s); +void nes_fds_Reset (nes_fds_t* s); +void nes_fds_Tick (nes_fds_t* s, uint32_t clocks); +uint32_t nes_fds_Render (nes_fds_t* s, int32_t b[2]); +bool nes_fds_Write (nes_fds_t* s, uint32_t adr, uint32_t val); +bool nes_fds_Read (nes_fds_t* s, uint32_t adr, uint32_t* val); +void nes_fds_SetRate (nes_fds_t* s, double r); +void nes_fds_SetOption (nes_fds_t* s, int id, int b); +void nes_fds_SetMask(nes_fds_t* s, int m); +void nes_fds_SetStereoMix (nes_fds_t* s, int trk, int32_t mixl, int32_t mixr); + +#endif diff --git a/external/nsfplug/nes_mmc5.c b/external/nsfplug/nes_mmc5.c new file mode 100644 index 00000000..5927aeb0 --- /dev/null +++ b/external/nsfplug/nes_mmc5.c @@ -0,0 +1,384 @@ +#include "nes_mmc5.h" +#include + +int32_t square_table[32]; +int32_t pcm_table[256]; + +static int32_t calc_sqr (nes_mmc5_t* s, int i, uint32_t clocks); + +void nes_mmc5_Init (nes_mmc5_t* s) +{ + s->option[NES_MMC5_OPT_NONLINEAR_MIXER] = true; + s->option[NES_MMC5_OPT_PHASE_REFRESH] = true; + s->frame_sequence_count = 0; + + // square nonlinear mix, same as 2A03 + square_table[0] = 0; + for(int i=1;i<32;i++) + square_table[i]=(int32_t)((8192.0*95.88)/(8128.0/i+100)); + + // 2A03 style nonlinear pcm mix with double the bits + //pcm_table[0] = 0; + //int32_t wd = 22638; + //for(int d=1;d<256; ++d) + // pcm_table[d] = (int32_t)((8192.0*159.79)/(100.0+1.0/((double)d/wd))); + + // linear pcm mix (actual hardware seems closer to this) + pcm_table[0] = 0; + double pcm_scale = 32.0; + for (int d=1; d<256; ++d) + pcm_table[d] = (int32_t)((double)d * pcm_scale); + + // stereo mix + for(int c=0;c<2;++c) + for(int t=0;t<3;++t) + s->sm[c][t] = 128; + nes_mmc5_Reset(s); +} + +void nes_mmc5_Reset (nes_mmc5_t* s) +{ + int i; + + s->scounter[0] = 0; + s->scounter[1] = 0; + s->sphase[0] = 0; + s->sphase[1] = 0; + + s->envelope_div[0] = 0; + s->envelope_div[1] = 0; + s->length_counter[0] = 0; + s->length_counter[1] = 0; + s->envelope_counter[0] = 0; + s->envelope_counter[1] = 0; + s->frame_sequence_count = 0; + + s->freq[0] = 0; + s->freq[1] = 0; + s->enable[0] = false; + s->enable[1] = false; + + for (i = 0; i < 8; i++) + nes_mmc5_Write (s, 0x5000 + i, 0); + + nes_mmc5_Write(s, 0x5015, 0); + + for (i = 0; i < 3; ++i) + s->out[i] = 0; + + s->mask = 0; + s->pcm = 0; // PCM channel + s->pcm_mode = false; // write mode +} + +void nes_mmc5_SetOption (nes_mmc5_t* s, int id, int val) +{ + if(idoption[id] = val; +} + +void nes_mmc5_FrameSequence (nes_mmc5_t* s) +{ + // 240hz clock + for (int i=0; i < 2; ++i) + { + bool divider = false; + if (s->envelope_write[i]) + { + s->envelope_write[i] = false; + s->envelope_counter[i] = 15; + s->envelope_div[i] = 0; + } + else + { + ++s->envelope_div[i]; + if (s->envelope_div[i] > s->envelope_div_period[i]) + { + divider = true; + s->envelope_div[i] = 0; + } + } + if (divider) + { + if (s->envelope_loop[i] && s->envelope_counter[i] == 0) + s->envelope_counter[i] = 15; + else if (s->envelope_counter[i] > 0) + --s->envelope_counter[i]; + } + } + + // MMC5 length counter is clocked at 240hz, unlike 2A03 + for (int i=0; i < 2; ++i) + { + if (!s->envelope_loop[i] && (s->length_counter[i] > 0)) + --s->length_counter[i]; + } +} + +int32_t calc_sqr (nes_mmc5_t* s, int i, uint32_t clocks) +{ + static const int16_t sqrtbl[4][16] = { + {0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0}, + {1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} + }; + + s->scounter[i] += clocks; + while (s->scounter[i] > s->freq[i]) + { + s->sphase[i] = (s->sphase[i] + 1) & 15; + s->scounter[i] -= (s->freq[i] + 1); + } + + int32_t ret = 0; + if (s->length_counter[i] > 0) + { + // note MMC5 does not silence the highest 8 frequencies like APU, + // because this is done by the sweep unit. + + int v = s->envelope_disable[i] ? s->volume[i] : s->envelope_counter[i]; + ret = sqrtbl[s->duty[i]][s->sphase[i]] ? v : 0; + } + + return ret; +} + +void nes_mmc5_TickFrameSequence (nes_mmc5_t* s, uint32_t clocks) +{ + s->frame_sequence_count += clocks; + while (s->frame_sequence_count > 7458) + { + nes_mmc5_FrameSequence(s); + s->frame_sequence_count -= 7458; + } +} + +void nes_mmc5_Tick (nes_mmc5_t* s, uint32_t clocks) +{ + s->out[0] = calc_sqr(s, 0, clocks); + s->out[1] = calc_sqr(s, 1, clocks); + s->out[2] = s->pcm; +} + +uint32_t nes_mmc5_Render (nes_mmc5_t* s, int32_t b[2]) +{ + s->out[0] = (s->mask & 1) ? 0 : s->out[0]; + s->out[1] = (s->mask & 2) ? 0 : s->out[1]; + s->out[2] = (s->mask & 4) ? 0 : s->out[2]; + + int32_t m[3]; + + if(s->option[NES_MMC5_OPT_NONLINEAR_MIXER]) + { + // squares nonlinear + int32_t voltage = square_table[s->out[0] + s->out[1]]; + m[0] = s->out[0] << 6; + m[1] = s->out[1] << 6; + int32_t ref = m[0] + m[1]; + if (ref > 0) + { + m[0] = (m[0] * voltage) / ref; + m[1] = (m[1] * voltage) / ref; + } + else + { + m[0] = voltage; + m[1] = voltage; + } + + // pcm nonlinear + m[2] = pcm_table[s->out[2]]; + } + else + { + // squares + m[0] = s->out[0] << 6; + m[1] = s->out[1] << 6; + + // pcm channel + m[2] = s->out[2] << 5; + } + + // note polarity is flipped on output + + b[0] = m[0] * -s->sm[0][0]; + b[0] += m[1] * -s->sm[0][1]; + b[0] += m[2] * -s->sm[0][2]; + b[0] >>= 7; + + b[1] = m[0] * -s->sm[1][0]; + b[1] += m[1] * -s->sm[1][1]; + b[1] += m[2] * -s->sm[1][2]; + b[1] >>= 7; + + return 2; +} + +bool nes_mmc5_Write (nes_mmc5_t* s, uint32_t adr, uint32_t val) +{ + int ch; + + static const uint8_t length_table[32] = { + 0x0A, 0xFE, + 0x14, 0x02, + 0x28, 0x04, + 0x50, 0x06, + 0xA0, 0x08, + 0x3C, 0x0A, + 0x0E, 0x0C, + 0x1A, 0x0E, + 0x0C, 0x10, + 0x18, 0x12, + 0x30, 0x14, + 0x60, 0x16, + 0xC0, 0x18, + 0x48, 0x1A, + 0x10, 0x1C, + 0x20, 0x1E + }; + + // if ((0x5c00 <= adr) && (adr < 0x5ff0)) + // { + // s->ram[adr & 0x3ff] = val; + // return true; + // } + // else if ((0x5000 <= adr) && (adr < 0x5008)) + // { + // s->reg[adr & 0x7] = val; + // } + + switch (adr) + { + case 0x5000: + case 0x5004: + ch = (adr >> 2) & 1; + s->volume[ch] = val & 15; + s->envelope_disable[ch] = (val >> 4) & 1; + s->envelope_loop[ch] = (val >> 5) & 1; + s->envelope_div_period[ch] = (val & 15); + s->duty[ch] = (val >> 6) & 3; + break; + + case 0x5002: + case 0x5006: + ch = (adr >> 2) & 1; + s->freq[ch] = val + (s->freq[ch] & 0x700); + if (s->scounter[ch] > s->freq[ch]) s->scounter[ch] = s->freq[ch]; + break; + + case 0x5003: + case 0x5007: + ch = (adr >> 2) & 1; + s->freq[ch] = (s->freq[ch] & 0xff) + ((val & 7) << 8); + if (s->scounter[ch] > s->freq[ch]) s->scounter[ch] = s->freq[ch]; + // phase reset + if (s->option[NES_MMC5_OPT_PHASE_REFRESH]) + s->sphase[ch] = 0; + s->envelope_write[ch] = true; + if (s->enable[ch]) + { + s->length_counter[ch] = length_table[(val >> 3) & 0x1f]; + } + break; + + // PCM channel control + case 0x5010: + s->pcm_mode = ((val & 1) != 0); // 0 = write, 1 = read + break; + + // PCM channel control + case 0x5011: + if (!s->pcm_mode) + { + val &= 0xFF; + if (val != 0) s->pcm = val; + } + break; + + case 0x5015: + s->enable[0] = (val & 1) ? true : false; + s->enable[1] = (val & 2) ? true : false; + if (!s->enable[0]) + s->length_counter[0] = 0; + if (!s->enable[1]) + s->length_counter[1] = 0; + break; + + // case 0x5205: + // s->mreg[0] = val; + // break; + + // case 0x5206: + // s->mreg[1] = val; + // break; + + default: + return false; + + } + return true; +} + +bool nes_mmc5_Read (nes_mmc5_t* s, uint32_t adr, uint32_t* val) +{ + // // in PCM read mode, reads from $8000-$C000 automatically load the PCM output + // if (pcm_mode && (0x8000 <= adr) && (adr < 0xC000) && cpu) + // { + // pcm_mode = false; // prevent recursive entry + // uint32_t pcm_read; + // s->cpu->Read(adr, pcm_read); + // pcm_read &= 0xFF; + // if (pcm_read != 0) + // pcm = pcm_read; + // pcm_mode = true; + // } + + // if ((0x5000 <= adr) && (adr < 0x5008)) + // { + // *val = s->reg[adr&0x7]; + // return true; + // } + // else if(adr == 0x5015) + if(adr == 0x5015) + { + *val = (s->enable[1]?2:0)|(s->enable[0]?1:0); + return true; + } + + // if ((0x5c00 <= adr) && (adr < 0x5ff0)) + // { + // *val = ram[adr & 0x3ff]; + // return true; + // } + // else if (adr == 0x5205) + // { + // *val = (s->mreg[0] * s->mreg[1]) & 0xff; + // return true; + // } + // else if (adr == 0x5206) + // { + // *val = (s->mreg[0] * s->mreg[1]) >> 8; + // return true; + // } + + return false; +} + +void nes_mmc5_SetStereoMix(nes_mmc5_t* s, int trk, int32_t mixl, int32_t mixr) +{ + if (trk < 0) return; + if (trk > 2) return; + s->sm[0][trk] = mixl; + s->sm[1][trk] = mixr; +} + +// // pcm read mode requires CPU read access +// void nes_mmc5_SetCPU(nes_mmc5_t* s, NES_CPU* cpu_) +// { +// s->cpu = cpu_; +// } + +void nes_mmc5_SetMask(nes_mmc5_t* s, int m) +{ + s->mask = m; +} diff --git a/external/nsfplug/nes_mmc5.h b/external/nsfplug/nes_mmc5.h new file mode 100644 index 00000000..fdff087c --- /dev/null +++ b/external/nsfplug/nes_mmc5.h @@ -0,0 +1,58 @@ +#ifndef _NES_MMC5_H_ +#define _NES_MMC5_H_ + +#include +#include + +typedef struct nes_mmc5 +{ + enum + { NES_MMC5_OPT_NONLINEAR_MIXER=0, NES_MMC5_OPT_PHASE_REFRESH, NES_MMC5_OPT_END }; + + int option[NES_MMC5_OPT_END]; + int mask; + int32_t sm[2][3]; // stereo panning + // uint8_t ram[0x6000 - 0x5c00]; + // uint8_t reg[8]; + // uint8_t mreg[2]; + uint8_t pcm; // PCM channel + bool pcm_mode; // PCM channel + // NES_CPU* cpu; // PCM channel reads need CPU access + + uint32_t scounter[2]; // frequency divider + uint32_t sphase[2]; // phase counter + + uint32_t duty[2]; + uint32_t volume[2]; + uint32_t freq[2]; + int32_t out[3]; + bool enable[2]; + + bool envelope_disable[2]; // エンベロープ有効フラグ + bool envelope_loop[2]; // エンベロープループ + bool envelope_write[2]; + int envelope_div_period[2]; + int envelope_div[2]; + int envelope_counter[2]; + + int length_counter[2]; + + int frame_sequence_count; +} nes_mmc5_t; + +void nes_mmc5_FrameSequence (nes_mmc5_t* s); +void nes_mmc5_TickFrameSequence (nes_mmc5_t* s, uint32_t clocks); + +void nes_mmc5_Init (nes_mmc5_t* s); +void nes_mmc5_Reset (nes_mmc5_t* s); +void nes_mmc5_Tick (nes_mmc5_t* s, uint32_t clocks); +uint32_t nes_mmc5_Render (nes_mmc5_t* s, int32_t b[2]); +bool nes_mmc5_Write (nes_mmc5_t* s, uint32_t adr, uint32_t val); +bool nes_mmc5_Read (nes_mmc5_t* s, uint32_t adr, uint32_t* val); +void nes_mmc5_SetOption (nes_mmc5_t* s, int id, int b); +void nes_mmc5_SetMask (nes_mmc5_t* s, int m); +void nes_mmc5_SetStereoMix (nes_mmc5_t* s, int trk, int32_t mixl, int32_t mixr); + +// void SetCPU(nes_mmc5_t* s, NES_CPU* cpu_); + +#endif diff --git a/external/nsfplug/nes_n106.c b/external/nsfplug/nes_n106.c new file mode 100644 index 00000000..76bc2ddc --- /dev/null +++ b/external/nsfplug/nes_n106.c @@ -0,0 +1,310 @@ +#include +#include "nes_n106.h" + +void nes_n106_Init (nes_n106_t* s) +{ + s->option[NES_N106_OPT_SERIAL] = 0; + s->option[NES_N106_OPT_PHASE_READ_ONLY] = 0; + s->option[NES_N106_OPT_LIMIT_WAVELENGTH] = 0; + for (int i=0; i < 8; ++i) + { + s->sm[0][i] = 128; + s->sm[1][i] = 128; + } + nes_n106_Reset(s); +} + +void nes_n106_SetStereoMix (nes_n106_t* s, int trk, int32_t mixl, int32_t mixr) +{ + if (trk < 0 || trk >= 8) return; + trk = 7-trk; // displayed channels are inverted + s->sm[0][trk] = mixl; + s->sm[1][trk] = mixr; +} + +void nes_n106_SetMask (nes_n106_t* s, int m) +{ + // bit reverse the mask, + // N163 waves are displayed in reverse order + s->mask = 0 + | ((m & (1<<0)) ? (1<<7) : 0) + | ((m & (1<<1)) ? (1<<6) : 0) + | ((m & (1<<2)) ? (1<<5) : 0) + | ((m & (1<<3)) ? (1<<4) : 0) + | ((m & (1<<4)) ? (1<<3) : 0) + | ((m & (1<<5)) ? (1<<2) : 0) + | ((m & (1<<6)) ? (1<<1) : 0) + | ((m & (1<<7)) ? (1<<0) : 0); +} + +void nes_n106_SetOption (nes_n106_t* s, int id, int val) +{ + if (idoption[id] = val; +} + +void nes_n106_Reset (nes_n106_t* s) +{ + s->master_disable = false; + memset(s->reg, 0, sizeof(s->reg)); + s->reg_select = 0; + s->reg_advance = false; + s->tick_channel = 0; + s->tick_clock = 0; + s->render_channel = 0; + s->render_clock = 0; + s->render_subclock = 0; + + for (int i=0; i<8; ++i) s->fout[i] = 0; + + nes_n106_Write(s, 0xE000, 0x00); // master disable off + nes_n106_Write(s, 0xF800, 0x80); // select $00 with auto-increment + for (unsigned int i=0; i<0x80; ++i) // set all regs to 0 + { + nes_n106_Write(s, 0x4800, 0x00); + } + nes_n106_Write(s, 0xF800, 0x00); // select $00 without auto-increment +} + +void nes_n106_Tick (nes_n106_t* s, uint32_t clocks) +{ + if (s->master_disable) return; + + int channels = nes_n106_get_channels(s); + + s->tick_clock += clocks; + s->render_clock += clocks; // keep render in sync + while (s->tick_clock > 0) + { + int channel = 7-s->tick_channel; + + uint32_t phase = nes_n106_get_phase(s, channel); + uint32_t freq = nes_n106_get_freq(s, channel); + uint32_t len = nes_n106_get_len(s, channel); + uint32_t off = nes_n106_get_off(s, channel); + int32_t vol = nes_n106_get_vol(s, channel); + + // accumulate 24-bit phase + phase = (phase + freq) & 0x00FFFFFF; + + // wrap phase if wavelength exceeded + uint32_t hilen = len << 16; + while (phase >= hilen) phase -= hilen; + + // write back phase + nes_n106_set_phase(s, phase, channel); + + // fetch sample (note: N163 output is centred at 8, and inverted w.r.t 2A03) + int32_t sample = 8 - nes_n106_get_sample(s, ((phase >> 16) + off) & 0xFF); + s->fout[channel] = sample * vol; + + // cycle to next channel every 15 clocks + s->tick_clock -= 15; + ++s->tick_channel; + if (s->tick_channel >= channels) + s->tick_channel = 0; + } +} + +uint32_t nes_n106_Render (nes_n106_t* s, int32_t b[2]) +{ + b[0] = 0; + b[1] = 0; + if (s->master_disable) return 2; + + int channels = nes_n106_get_channels(s); + + if (s->option[NES_N106_OPT_SERIAL]) // hardware accurate serial multiplexing + { + // this could be made more efficient than going clock-by-clock + // but this way is simpler + int clocks = s->render_clock; + while (clocks > 0) + { + int c = 7-s->render_channel; + if (0 == ((s->mask >> c) & 1)) + { + b[0] += s->fout[c] * s->sm[0][c]; + b[1] += s->fout[c] * s->sm[1][c]; + } + + ++s->render_subclock; + if (s->render_subclock >= 15) // each channel gets a 15-cycle slice + { + s->render_subclock = 0; + ++s->render_channel; + if (s->render_channel >= channels) + s->render_channel = 0; + } + --clocks; + } + + // increase output level by 1 bits (7 bits already added from sm) + b[0] <<= 1; + b[1] <<= 1; + + // average the output + if (s->render_clock > 0) + { + b[0] /= s->render_clock; + b[1] /= s->render_clock; + } + s->render_clock = 0; + } + else // just mix all channels + { + for (int i = (8-channels); i<8; ++i) + { + if (0 == ((s->mask >> i) & 1)) + { + b[0] += s->fout[i] * s->sm[0][i]; + b[1] += s->fout[i] * s->sm[1][i]; + } + } + + // mix together, increase output level by 8 bits, roll off 7 bits from sm + int32_t MIX[9] = { 256/1, 256/1, 256/2, 256/3, 256/4, 256/5, 256/6, 256/6, 256/6 }; + b[0] = (b[0] * MIX[channels]) >> 7; + b[1] = (b[1] * MIX[channels]) >> 7; + // when approximating the serial multiplex as a straight mix, once the + // multiplex frequency gets below the nyquist frequency an average mix + // begins to sound too quiet. To approximate this effect, I don't attenuate + // any further after 6 channels are active. + } + + // 8 bit approximation of master volume + // max N163 vol vs max APU square + // unfortunately, games have been measured as low as 3.4x and as high as 8.5x + // with higher volumes on Erika, King of Kings, and Rolling Thunder + // and lower volumes on others. Using 6.0x as a rough "one size fits all". + const double MASTER_VOL = 6.0 * 1223.0; + const double MAX_OUT = 15.0 * 15.0 * 256.0; // max digital value + const int32_t GAIN = (int)((MASTER_VOL / MAX_OUT) * 256.0f); + b[0] = (b[0] * GAIN) >> 8; + b[1] = (b[1] * GAIN) >> 8; + + return 2; +} + +bool nes_n106_Write (nes_n106_t* s, uint32_t adr, uint32_t val) +{ + if (adr == 0xE000) // master disable + { + s->master_disable = ((val & 0x40) != 0); + return true; + } + else if (adr == 0xF800) // register select + { + s->reg_select = (val & 0x7F); + s->reg_advance = (val & 0x80) != 0; + return true; + } + else if (adr == 0x4800) // register write + { + if (s->option[NES_N106_OPT_PHASE_READ_ONLY]) // old emulators didn't know phase was stored here + { + int c = 15 - (s->reg_select/8); + int r = s->reg_select & 7; + if (c < nes_n106_get_channels(s) && + (r == 1 || + r == 3 || + r == 5)) + { + if (s->reg_advance) + s->reg_select = (s->reg_select + 1) & 0x7F; + return true; + } + } + if (s->option[NES_N106_OPT_LIMIT_WAVELENGTH]) // old emulators ignored top 3 bits of length + { + int c = 15 - (s->reg_select/8); + int r = s->reg_select & 7; + if (c < nes_n106_get_channels(s) && r == 4) + { + val |= 0xE0; + } + } + s->reg[s->reg_select] = val; + if (s->reg_advance) + s->reg_select = (s->reg_select + 1) & 0x7F; + return true; + } + return false; +} + +bool nes_n106_Read (nes_n106_t* s, uint32_t adr, uint32_t* val) +{ + if (adr == 0x4800) // register read + { + *val = s->reg[s->reg_select]; + if (s->reg_advance) + s->reg_select = (s->reg_select + 1) & 0x7F; + return true; + } + return false; +} + +// +// register decoding/encoding functions +// + +inline uint32_t nes_n106_get_phase (nes_n106_t* s, int channel) +{ + // 24-bit phase stored in channel regs 1/3/5 + channel = channel << 3; + return (s->reg[0x41 + channel] ) + + (s->reg[0x43 + channel] << 8 ) + + (s->reg[0x45 + channel] << 16); +} + +inline uint32_t nes_n106_get_freq (nes_n106_t* s, int channel) +{ + // 19-bit frequency stored in channel regs 0/2/4 + channel = channel << 3; + return ( s->reg[0x40 + channel] ) + + ( s->reg[0x42 + channel] << 8 ) + + ((s->reg[0x44 + channel] & 0x03) << 16); +} + +inline uint32_t nes_n106_get_off (nes_n106_t* s, int channel) +{ + // 8-bit offset stored in channel reg 6 + channel = channel << 3; + return s->reg[0x46 + channel]; +} + +inline uint32_t nes_n106_get_len (nes_n106_t* s, int channel) +{ + // 6-bit<<3 length stored obscurely in channel reg 4 + channel = channel << 3; + return 256 - (s->reg[0x44 + channel] & 0xFC); +} + +inline int32_t nes_n106_get_vol (nes_n106_t* s, int channel) +{ + // 4-bit volume stored in channel reg 7 + channel = channel << 3; + return s->reg[0x47 + channel] & 0x0F; +} + +inline int32_t nes_n106_get_sample (nes_n106_t* s, uint32_t index) +{ + // every sample becomes 2 samples in regs + return (index&1) ? + ((s->reg[index>>1] >> 4) & 0x0F) : + ( s->reg[index>>1] & 0x0F) ; +} + +inline int nes_n106_get_channels (nes_n106_t* s) +{ + // 3-bit channel count stored in reg 0x7F + return ((s->reg[0x7F] >> 4) & 0x07) + 1; +} + +inline void nes_n106_set_phase (nes_n106_t* s, uint32_t phase, int channel) +{ + // 24-bit phase stored in channel regs 1/3/5 + channel = channel << 3; + s->reg[0x41 + channel] = phase & 0xFF; + s->reg[0x43 + channel] = (phase >> 8 ) & 0xFF; + s->reg[0x45 + channel] = (phase >> 16) & 0xFF; +} diff --git a/external/nsfplug/nes_n106.h b/external/nsfplug/nes_n106.h new file mode 100644 index 00000000..8be14ef7 --- /dev/null +++ b/external/nsfplug/nes_n106.h @@ -0,0 +1,54 @@ +#ifndef _NES_N106_H_ +#define _NES_N106_H_ + +#include +#include + +typedef struct nes_n106 +{ + enum + { + NES_N106_OPT_SERIAL = 0, + NES_N106_OPT_PHASE_READ_ONLY = 1, + NES_N106_OPT_LIMIT_WAVELENGTH = 2, + NES_N106_OPT_END + }; + + int mask; + int32_t sm[2][8]; // stereo mix + int32_t fout[8]; // current output + int option[NES_N106_OPT_END]; + + bool master_disable; + uint32_t reg[0x80]; // all state is contained here + unsigned int reg_select; + bool reg_advance; + int tick_channel; + int tick_clock; + int render_channel; + int render_clock; + int render_subclock; +} nes_n106_t; + +// convenience functions to interact with regs +inline uint32_t nes_n106_get_phase (nes_n106_t* s, int channel); +inline uint32_t nes_n106_get_freq (nes_n106_t* s, int channel); +inline uint32_t nes_n106_get_off (nes_n106_t* s, int channel); +inline uint32_t nes_n106_get_len (nes_n106_t* s, int channel); +inline int32_t nes_n106_get_vol (nes_n106_t* s, int channel); +inline int32_t nes_n106_get_sample (nes_n106_t* s, uint32_t index); +inline int nes_n106_get_channels (nes_n106_t* s); +// for storing back the phase after modifying +inline void nes_n106_set_phase (nes_n106_t* s, uint32_t phase, int channel); + +void nes_n106_Init (nes_n106_t* s); +void nes_n106_Reset (nes_n106_t* s); +void nes_n106_Tick (nes_n106_t* s, uint32_t clocks); +uint32_t nes_n106_Render (nes_n106_t* s, int32_t b[2]); +bool nes_n106_Write (nes_n106_t* s, uint32_t adr, uint32_t val); +bool nes_n106_Read (nes_n106_t* s, uint32_t adr, uint32_t* val); +void nes_n106_SetOption (nes_n106_t* s, int id, int b); +void nes_n106_SetMask (nes_n106_t* s, int m); +void nes_n106_SetStereoMix (nes_n106_t* s, int trk, int32_t mixl, int32_t mixr); + +#endif diff --git a/external/nsfplug/nes_vrc6.c b/external/nsfplug/nes_vrc6.c new file mode 100644 index 00000000..b18daefa --- /dev/null +++ b/external/nsfplug/nes_vrc6.c @@ -0,0 +1,225 @@ +#include "nes_vrc6.h" + +static int16_t calc_sqr (nes_vrc6_t* s, int i, uint32_t clocks); +static int16_t calc_saw (nes_vrc6_t* s, uint32_t clocks); + +void nes_vrc6_Init (nes_vrc6_t* s) +{ + s->halt = false; + s->freq_shift = 0; + + for(int c=0;c<2;++c) + for(int t=0;t<3;++t) + s->sm[c][t] = 128; + nes_vrc6_Reset(s); +} + +void nes_vrc6_SetStereoMix(nes_vrc6_t* s, int trk, int32_t mixl, int32_t mixr) +{ + if (trk < 0) return; + if (trk > 2) return; + s->sm[0][trk] = mixl; + s->sm[1][trk] = mixr; +} + +void nes_vrc6_SetOption (nes_vrc6_t* s, int id, int val) +{ + if(idfreq[0] = 0; + s->freq[1] = 0; + s->freq[2] = 0; + s->counter[0] = 0; + s->counter[1] = 0; + s->counter[2] = 0; + s->enable[0] = 0; + s->enable[1] = 0; + s->enable[2] = 0; + nes_vrc6_Write (s, 0x9003, 0); + for (int i = 0; i < 3; i++) + { + nes_vrc6_Write (s, 0x9000 + i, 0); + nes_vrc6_Write (s, 0xa000 + i, 0); + nes_vrc6_Write (s, 0xb000 + i, 0); + } + s->count14 = 0; + s->mask = 0; + s->phase[0] = 0; + s->phase[1] = 0; + s->phase[2] = 0; +} + +int16_t calc_sqr (nes_vrc6_t* s, int i, uint32_t clocks) +{ + static const int16_t sqrtbl[8][16] = { + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1}, + {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1}, + {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1} + }; + + if (!s->enable[i]) + return 0; + + if (!s->halt) + { + s->counter[i] += clocks; + while(s->counter[i] > s->freq2[i]) + { + s->phase[i] = (s->phase[i] + 1) & 15; + s->counter[i] -= (s->freq2[i] + 1); + } + } + + return (s->gate[i] + || sqrtbl[s->duty[i]][s->phase[i]])? s->volume[i] : 0; +} + +int16_t calc_saw (nes_vrc6_t* s, uint32_t clocks) +{ + if (!s->enable[2]) + return 0; + + if (!s->halt) + { + s->counter[2] += clocks; + while(s->counter[2] > s->freq2[2]) + { + s->counter[2] -= (s->freq2[2] + 1); + + // accumulate saw + ++s->count14; + if (s->count14 >= 14) + { + s->count14 = 0; + s->phase[2] = 0; + } + else if (0 == (s->count14 & 1)) // only accumulate on even ticks + { + s->phase[2] = (s->phase[2] + s->volume[2]) & 0xFF; // note 8-bit wrapping behaviour + } + } + } + + // only top 5 bits of saw are output + return s->phase[2] >> 3; +} + +void nes_vrc6_Tick (nes_vrc6_t* s, uint32_t clocks) +{ + s->out[0] = calc_sqr(s, 0, clocks); + s->out[1] = calc_sqr(s, 1, clocks); + s->out[2] = calc_saw(s, clocks); +} + +uint32_t nes_vrc6_Render (nes_vrc6_t* s, int32_t b[2]) +{ + int32_t m[3]; + m[0] = s->out[0]; + m[1] = s->out[1]; + m[2] = s->out[2]; + + // note: signal is inverted compared to 2A03 + + m[0] = (s->mask & 1) ? 0 : -m[0]; + m[1] = (s->mask & 2) ? 0 : -m[1]; + m[2] = (s->mask & 4) ? 0 : -m[2]; + + b[0] = m[0] * s->sm[0][0]; + b[0] += m[1] * s->sm[0][1]; + b[0] += m[2] * s->sm[0][2]; + //b[0] >>= (7 - 7); + + b[1] = m[0] * s->sm[1][0]; + b[1] += m[1] * s->sm[1][1]; + b[1] += m[2] * s->sm[1][2]; + //b[1] >>= (7 - 7); + + // master volume adjustment + const int32_t MASTER = (int32_t)(256.0 * 1223.0 / 1920.0); + b[0] = (b[0] * MASTER) >> 8; + b[1] = (b[1] * MASTER) >> 8; + + return 2; +} + +bool nes_vrc6_Write (nes_vrc6_t* s, uint32_t adr, uint32_t val) +{ + int ch, cmap[4] = { 0, 0, 1, 2 }; + + switch (adr) + { + case 0x9000: + case 0xa000: + ch = cmap[(adr >> 12) & 3]; + s->volume[ch] = val & 15; + s->duty[ch] = (val >> 4) & 7; + s->gate[ch] = (val >> 7) & 1; + break; + case 0xb000: + s->volume[2] = val & 63; + break; + + case 0x9001: + case 0xa001: + case 0xb001: + ch = cmap[(adr >> 12) & 3]; + s->freq[ch] = (s->freq[ch] & 0xf00) | val; + s->freq2[ch] = (s->freq[ch] >> s->freq_shift); + if (s->counter[ch] > s->freq2[ch]) s->counter[ch] = s->freq2[ch]; + break; + + case 0x9002: + case 0xa002: + case 0xb002: + ch = cmap[(adr >> 12) & 3]; + s->freq[ch] = ((val & 0xf) << 8) + (s->freq[ch] & 0xff); + s->freq2[ch] = (s->freq[ch] >> s->freq_shift); + if (s->counter[ch] > s->freq2[ch]) s->counter[ch] = s->freq2[ch]; + if (!s->enable[ch]) // if enable is being turned on, phase should be reset + { + if (ch == 2) + { + s->count14 = 0; // reset saw + } + s->phase[ch] = 0; + } + s->enable[ch] = (val >> 7) & 1; + break; + + case 0x9003: + s->halt = val & 1; + s->freq_shift = + (val & 4) ? 8 : + (val & 2) ? 4 : + 0; + s->freq2[0] = (s->freq[0] >> s->freq_shift); + s->freq2[1] = (s->freq[1] >> s->freq_shift); + s->freq2[2] = (s->freq[2] >> s->freq_shift); + if (s->counter[0] > s->freq2[0]) s->counter[0] = s->freq2[0]; + if (s->counter[1] > s->freq2[1]) s->counter[1] = s->freq2[1]; + if (s->counter[2] > s->freq2[2]) s->counter[2] = s->freq2[2]; + break; + + default: + return false; + + } + + return true; +} + +bool nes_vrc6_Read (nes_vrc6_t* s, uint32_t adr, uint32_t* val) +{ + return false; +} diff --git a/external/nsfplug/nes_vrc6.h b/external/nsfplug/nes_vrc6.h new file mode 100644 index 00000000..84d28af2 --- /dev/null +++ b/external/nsfplug/nes_vrc6.h @@ -0,0 +1,41 @@ +#ifndef _NES_VRC6_H_ +#define _NES_VRC6_H_ + +#include +#include + +typedef struct nes_vrc6 +{ + enum + { + NES_VRC6_OPT_END + }; + uint32_t counter[3]; // frequency divider + uint32_t phase[3]; // phase counter + uint32_t freq2[3]; // adjusted frequency + int count14; // saw 14-stage counter + + //int option[OPT_END]; + int mask; + int32_t sm[2][3]; // stereo mix + int duty[2]; + int volume[3]; + int enable[3]; + int gate[3]; + uint32_t freq[3]; + bool halt; + int freq_shift; + int32_t out[3]; +} nes_vrc6_t; + +void nes_vrc6_Init (nes_vrc6_t* s); +void nes_vrc6_Reset (nes_vrc6_t* s); +void nes_vrc6_Tick (nes_vrc6_t* s, uint32_t clocks); +uint32_t nes_vrc6_Render (nes_vrc6_t* s, int32_t b[2]); +bool nes_vrc6_Read (nes_vrc6_t* s, uint32_t adr, uint32_t* val); +bool nes_vrc6_Write (nes_vrc6_t* s, uint32_t adr, uint32_t val); +void nes_vrc6_SetOption (nes_vrc6_t* s, int id, int b); +void nes_vrc6_SetMask (nes_vrc6_t* s, int m); +void nes_vrc6_SetStereoMix (nes_vrc6_t* s, int trk, int32_t mixl, int32_t mixr); + +#endif diff --git a/external/nsfplug/readme.txt b/external/nsfplug/readme.txt new file mode 100644 index 00000000..b929b1f8 --- /dev/null +++ b/external/nsfplug/readme.txt @@ -0,0 +1,81 @@ +This is a C port of NSFPlug soundchip cores by Natt Akuma +It is based on Brad Smith's NSFPlay 2.6 fork at +https://github.com/bbbradsmith/nsfplay/tree/6af5406e3325b5507bea1ae1a57c77d5efe5c7f3 +5B and VRC7 files are not included as Bitphase uses better YM cores for those + +Original readme.txt files follow: + +------------------------------------------------------------------------------- + +This code contains several modifications to original, see nsfplay.txt for a list of changes. + +I have presumed based on text comments and readme files in the original code that it is distributed freely, and modification and redistribution is permitted. The same permissive license applies to this version of the code I maintain. You may reuse this code without restriction, and no warranty or liability is implied on my part. + +Feel free to contact me with questions or comments. + +Brad Smith +nsfplay AT rainwarrior.ca +http://rainwarrior.ca + +Original NSFPlay/NFSPlug project available at: http://www.pokipoki.org/dsa/ + +------------------------------------------------------------------------------- + +XGM SOURCE ARCHIVE + +This source archive is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY. You can reuse these source code freely. However, +we possibly change the structure and interface of the program code without +any advance notice. + +HOW TO COMPILE + +Open the workspace file top.sln with Visual C++ 7.0 or later +version. We can compile both KbMediaPlayer and Winamp version of +NSFplug on this workspace. + +To make a KbMediaPlayer version of NSFplug, that is, in_nsf.kpi, +Please choose 'kbnsf' project as an active project. Then, you can +build in_nsf.kpi by build menu. + +On the other hand, to make a Winamp version of NSFplug, activate +'wa2nsf' project and build it. + +Note that after the build process, VC++ copies the plugin files to: + +C:\Program Files\KbMediaPlayer\Plugins\OK\in_nsf\in_nsf.kpi +C:\Program Files\Windamp\Plugins\in_nsf.dll + +If you don't need to have these copies, please remove or modify the + custom build settings. + +ACKNOWLEDGEMENT + +I thank Mamiya and Kobarin and Nullsoft for their great source code. +I thank Norix and Izumi for the fruitful discussions and the NSFplug +users for their comments and bug reports. + +COPYRIGHTS + +NSFplug is built on KM6502, KbMediaPlayer plugin SDK and Winamp2 +plugin SDK. + +NSFplug uses KM6502 in emulating a 6502 cpu. KM6502 code is stored in +devices\CPU\km6502 folder of this source archive. KM6502 is a public +domain software. See the document of KM6502 stored in the folder. + +KbMediaPlayer Plugin SDK is provided by Kobarin. The SDK code is also +packed in the kbmedia\sdk folder of this archive. The copyright of +the source code remains with Kobarin. + +The files in winamp/sdk folder of this archive are the header files +from Winamp2 Plugin SDK provided by Nullsoft. The copyright of these +header files remains with Nullsoft. + +CONTACT + +Digital Sound Antiques +http://dsa.sakura.ne.jp/ + + +