From 1f99365bbd49bedcb9bcfaef15b4cba7a6dbdd63 Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 17 Feb 2022 12:16:27 +0900 Subject: nes_apu.cpp: Implement non linear mixer output, some misc fixes and improvements (#9258) * nes_apu.cpp: Implement non linear mixer output, some misc fixes and improvements Fix Pulse channel duty behavior Fix triangle output behavior Fix noise output behavior Fix DMC output and clamp behavior - now DMC output is affects triangle and noise volume. Reference: https://wiki.nesdev.org/w/index.php?title=APU https://wiki.nesdev.org/w/index.php?title=APU_Pulse https://wiki.nesdev.org/w/index.php?title=APU_Triangle https://wiki.nesdev.org/w/index.php?title=APU_Noise https://wiki.nesdev.org/w/index.php?title=APU_DMC https://wiki.nesdev.org/w/index.php?title=APU_Mixer * nes_apu.cpp: Fix mixer output correction Reduce unnecessary variables Split channel update function and output variable Add notes --- src/devices/sound/nes_apu.cpp | 250 +++++++++++++++++++++++++----------------- src/devices/sound/nes_apu.h | 11 +- src/devices/sound/nes_defs.h | 16 ++- 3 files changed, 169 insertions(+), 108 deletions(-) diff --git a/src/devices/sound/nes_apu.cpp b/src/devices/sound/nes_apu.cpp index 8c61315581d..ff67cb23f44 100644 --- a/src/devices/sound/nes_apu.cpp +++ b/src/devices/sound/nes_apu.cpp @@ -143,6 +143,48 @@ void nesapu_device::device_start() calculate_rates(); + // calculate mixer output + /* + pulse channel output: + + 95.88 + ----------------------- + 8128 + ----------------- + 100 + pulse 1 + pulse 2 + + */ + for (int i = 0; i < 31; i++) + { + stream_buffer::sample_t pulse_out = (i == 0) ? 0.0 : 95.88 / ((8128.0 / i) + 100.0); + m_square_lut[i] = pulse_out; + } + + /* + triangle, noise, DMC channel output: + + 159.79 + ------------------------------- + 1 + ------------------------- + 100 + triangle noise dmc + -------- + ----- + ----- + 8227 12241 22638 + + */ + for (int t = 0; t < 16; t++) + { + for (int n = 0; n < 16; n++) + { + for (int d = 0; d < 128; d++) + { + stream_buffer::sample_t tnd_out = (t / 8227.0) + (n / 12241.0) + (d / 22638.0); + tnd_out = (tnd_out == 0.0) ? 0.0 : 159.79 / ((1.0 / tnd_out) + 100.0); + m_tnd_lut[t][n][d] = tnd_out; + } + } + } + /* register for save */ for (int i = 0; i < 2; i++) { @@ -150,12 +192,12 @@ void nesapu_device::device_start() save_item(NAME(m_APU.squ[i].vbl_length), i); save_item(NAME(m_APU.squ[i].freq), i); save_item(NAME(m_APU.squ[i].phaseacc), i); - save_item(NAME(m_APU.squ[i].output_vol), i); save_item(NAME(m_APU.squ[i].env_phase), i); save_item(NAME(m_APU.squ[i].sweep_phase), i); save_item(NAME(m_APU.squ[i].adder), i); save_item(NAME(m_APU.squ[i].env_vol), i); save_item(NAME(m_APU.squ[i].enabled), i); + save_item(NAME(m_APU.squ[i].output), i); } save_item(NAME(m_APU.tri.regs)); @@ -163,30 +205,31 @@ void nesapu_device::device_start() save_item(NAME(m_APU.tri.vbl_length)); save_item(NAME(m_APU.tri.write_latency)); save_item(NAME(m_APU.tri.phaseacc)); - save_item(NAME(m_APU.tri.output_vol)); save_item(NAME(m_APU.tri.adder)); save_item(NAME(m_APU.tri.counter_started)); save_item(NAME(m_APU.tri.enabled)); + save_item(NAME(m_APU.tri.output)); + save_item(NAME(m_APU.tri.output_next)); save_item(NAME(m_APU.noi.regs)); save_item(NAME(m_APU.noi.seed)); save_item(NAME(m_APU.noi.vbl_length)); save_item(NAME(m_APU.noi.phaseacc)); - save_item(NAME(m_APU.noi.output_vol)); save_item(NAME(m_APU.noi.env_phase)); save_item(NAME(m_APU.noi.env_vol)); save_item(NAME(m_APU.noi.enabled)); + save_item(NAME(m_APU.noi.output)); save_item(NAME(m_APU.dpcm.regs)); save_item(NAME(m_APU.dpcm.address)); save_item(NAME(m_APU.dpcm.length)); save_item(NAME(m_APU.dpcm.bits_left)); save_item(NAME(m_APU.dpcm.phaseacc)); - save_item(NAME(m_APU.dpcm.output_vol)); save_item(NAME(m_APU.dpcm.cur_byte)); save_item(NAME(m_APU.dpcm.enabled)); save_item(NAME(m_APU.dpcm.irq_occurred)); save_item(NAME(m_APU.dpcm.vol)); + save_item(NAME(m_APU.dpcm.output)); save_item(NAME(m_APU.regs)); @@ -202,12 +245,11 @@ void nesapu_device::device_start() /* TODO: sound channels should *ALL* have DC volume decay */ -/* OUTPUT SQUARE WAVE SAMPLE (VALUES FROM -16 to +15) */ -s8 nesapu_device::apu_square(apu_t::square_t *chan) +/* OUTPUT SQUARE WAVE SAMPLE (VALUES FROM 0 to +15) */ +void nesapu_device::apu_square(apu_t::square_t *chan) { int env_delay; int sweep_delay; - s8 output; /* reg0: 0-3=volume, 4=envelope, 5=hold, 6-7=duty cycle ** reg1: 0-2=sweep shifts, 3=sweep inc/dec, 4-6=sweep length, 7=sweep on @@ -215,11 +257,14 @@ s8 nesapu_device::apu_square(apu_t::square_t *chan) ** reg3: 0-2=high freq, 7-4=vbl length counter */ - if (false == chan->enabled) - return 0; + if (!chan->enabled) + { + chan->output = 0; + return; + } /* enveloping */ - env_delay = m_sync_times1[chan->regs[0] & 0x0F]; + env_delay = m_sync_times1[chan->regs[0] & 0x0f]; /* decay is at a rate of (env_regs + 1) / 240 secs */ chan->env_phase -= 4; @@ -233,11 +278,14 @@ s8 nesapu_device::apu_square(apu_t::square_t *chan) } /* vbl length counter */ - if (chan->vbl_length > 0 && 0 == (chan->regs [0] & 0x20)) + if (chan->vbl_length > 0 && !(chan->regs[0] & 0x20)) chan->vbl_length--; - if (0 == chan->vbl_length) - return 0; + if (!chan->vbl_length) + { + chan->output = 0; + return; + } /* freqsweeps */ if ((chan->regs[1] & 0x80) && (chan->regs[1] & 7)) @@ -254,47 +302,49 @@ s8 nesapu_device::apu_square(apu_t::square_t *chan) } } - if ((0 == (chan->regs[1] & 8) && (chan->freq >> 16) > freq_limit[chan->regs[1] & 7]) + if ((!(chan->regs[1] & 8) && (chan->freq >> 16) > freq_limit[chan->regs[1] & 7]) || (chan->freq >> 16) < 4) - return 0; + { + chan->output = 0; + return; + } chan->phaseacc -= 4; while (chan->phaseacc < 0) { chan->phaseacc += (chan->freq >> 16); - chan->adder = (chan->adder + 1) & 0x0F; + chan->adder = (chan->adder + 1) & 0x0f; } if (chan->regs[0] & 0x10) /* fixed volume */ - output = chan->regs[0] & 0x0F; + chan->output = chan->regs[0] & 0x0f; else - output = 0x0F - chan->env_vol; - - if (chan->adder < (duty_lut[chan->regs[0] >> 6])) - output = -output; + chan->output = 0x0f - chan->env_vol; - return (s8) output; + chan->output *= BIT(duty_lut[chan->regs[0] >> 6], 7 - BIT(chan->adder, 1, 3)); } -/* OUTPUT TRIANGLE WAVE SAMPLE (VALUES FROM -16 to +15) */ -s8 nesapu_device::apu_triangle(apu_t::triangle_t *chan) +/* OUTPUT TRIANGLE WAVE SAMPLE (VALUES FROM 0 to +15) */ +void nesapu_device::apu_triangle(apu_t::triangle_t *chan) { int freq; - s8 output; /* reg0: 7=holdnote, 6-0=linear length counter ** reg2: low 8 bits of frequency ** reg3: 7-3=length counter, 2-0=high 3 bits of frequency */ - if (false == chan->enabled) - return 0; + if (!chan->enabled) + { + chan->output = 0; + return; + } - if (false == chan->counter_started && 0 == (chan->regs[0] & 0x80)) + if (!chan->counter_started && !(chan->regs[0] & 0x80)) { if (chan->write_latency) chan->write_latency--; - if (0 == chan->write_latency) + if (!chan->write_latency) chan->counter_started = true; } @@ -302,56 +352,63 @@ s8 nesapu_device::apu_triangle(apu_t::triangle_t *chan) { if (chan->linear_length > 0) chan->linear_length--; - if (chan->vbl_length && 0 == (chan->regs[0] & 0x80)) + if (chan->vbl_length && !(chan->regs[0] & 0x80)) chan->vbl_length--; - if (0 == chan->vbl_length) - return 0; + if (!chan->vbl_length) + { + chan->output = 0; + return; + } } - if (0 == chan->linear_length) - return 0; + if (!chan->linear_length) + { + chan->output = 0; + return; + } freq = (((chan->regs[3] & 7) << 8) + chan->regs[2]) + 1; if (freq < 4) /* inaudible */ - return 0; + { + chan->output = 0; + return; + } chan->phaseacc -= 4; while (chan->phaseacc < 0) { chan->phaseacc += freq; - chan->adder = (chan->adder + 1) & 0x1F; + chan->adder = (chan->adder + 1) & 0x1f; - output = (chan->adder & 7) << 1; - if (chan->adder & 8) - output = 0x10 - output; if (chan->adder & 0x10) - output = -output; - - chan->output_vol = output; + chan->output_next = chan->adder & 0xf; + else + chan->output_next = 0xf - (chan->adder & 0xf); } - return (s8) chan->output_vol; + chan->output = chan->output_next; } -/* OUTPUT NOISE WAVE SAMPLE (VALUES FROM -16 to +15) */ -s8 nesapu_device::apu_noise(apu_t::noise_t *chan) +/* OUTPUT NOISE WAVE SAMPLE (VALUES FROM 0 to +15) */ +void nesapu_device::apu_noise(apu_t::noise_t *chan) { int freq, env_delay; - u8 outvol; - u8 output; /* reg0: 0-3=volume, 4=envelope, 5=hold ** reg2: 7=small(93 byte) sample,3-0=freq lookup ** reg3: 7-4=vbl length counter */ - if (false == chan->enabled) - return 0; + if (!chan->enabled) + { + chan->output = 0; + return; + } /* enveloping */ - env_delay = m_sync_times1[chan->regs[0] & 0x0F]; + env_delay = m_sync_times1[chan->regs[0] & 0x0f]; /* decay is at a rate of (env_regs + 1) / 240 secs */ chan->env_phase -= 4; @@ -365,16 +422,19 @@ s8 nesapu_device::apu_noise(apu_t::noise_t *chan) } /* length counter */ - if (0 == (chan->regs[0] & 0x20)) + if (!(chan->regs[0] & 0x20)) { if (chan->vbl_length > 0) chan->vbl_length--; } - if (0 == chan->vbl_length) - return 0; + if (!chan->vbl_length) + { + chan->output = 0; + return; + } - freq = noise_freq[m_is_pal][chan->regs[2] & 0x0F]; + freq = noise_freq[m_is_pal][chan->regs[2] & 0x0f]; chan->phaseacc -= 4; while (chan->phaseacc < 0) { @@ -382,25 +442,22 @@ s8 nesapu_device::apu_noise(apu_t::noise_t *chan) chan->seed = (chan->seed >> 1) | ((BIT(chan->seed, 0) ^ BIT(chan->seed, (chan->regs[2] & 0x80) ? 6 : 1)) << 14); } + if (BIT(chan->seed, 0)) /* make it silence */ + { + chan->output = 0; + return; + } + if (chan->regs[0] & 0x10) /* fixed volume */ - outvol = chan->regs[0] & 0x0F; + chan->output = chan->regs[0] & 0x0f; else - outvol = 0x0F - chan->env_vol; - - output = chan->seed & 0xff; - if (output > outvol) - output = outvol; - - if (chan->seed & 0x80) /* make it negative */ - output = -output; - - return (s8) output; + chan->output = 0x0f - chan->env_vol; } /* RESET DPCM PARAMETERS */ static inline void apu_dpcmreset(apu_t::dpcm_t *chan) { - chan->address = 0xC000 + u16(chan->regs[2] << 6); + chan->address = 0xc000 + u16(chan->regs[2] << 6); chan->length = u16(chan->regs[3] << 4) + 1; chan->bits_left = chan->length << 3; chan->irq_occurred = false; @@ -408,9 +465,9 @@ static inline void apu_dpcmreset(apu_t::dpcm_t *chan) chan->vol = 0; /* Fixed * DPCM DAC resets itself when restarted */ } -/* OUTPUT DPCM WAVE SAMPLE (VALUES FROM -64 to +63) */ +/* OUTPUT DPCM WAVE SAMPLE (VALUES FROM 0 to +127) */ /* TODO: centerline naughtiness */ -s8 nesapu_device::apu_dpcm(apu_t::dpcm_t *chan) +void nesapu_device::apu_dpcm(apu_t::dpcm_t *chan) { int freq, bit_pos; @@ -422,17 +479,17 @@ s8 nesapu_device::apu_dpcm(apu_t::dpcm_t *chan) if (chan->enabled) { - freq = dpcm_clocks[m_is_pal][chan->regs[0] & 0x0F]; + freq = dpcm_clocks[m_is_pal][chan->regs[0] & 0x0f]; chan->phaseacc -= 4; while (chan->phaseacc < 0) { chan->phaseacc += freq; - if (0 == chan->length) + if (!chan->length) { chan->enabled = false; /* Fixed * Proper DPCM channel ENABLE/DISABLE flag behaviour*/ - chan->vol=0; /* Fixed * DPCM DAC resets itself when restarted */ + chan->vol = 0; /* Fixed * DPCM DAC resets itself when restarted */ if (chan->regs[0] & 0x40) apu_dpcmreset(chan); else @@ -456,21 +513,16 @@ s8 nesapu_device::apu_dpcm(apu_t::dpcm_t *chan) chan->length--; } - if (chan->cur_byte & (1 << bit_pos)) -// chan->regs[1]++; - chan->vol+=2; /* FIXED * DPCM channel only uses the upper 6 bits of the DAC */ - else -// chan->regs[1]--; - chan->vol-=2; + if ((chan->cur_byte & (1 << bit_pos)) && (chan->vol <= 125)) +// chan->regs[1] += 2; + chan->vol += 2; /* FIXED * DPCM channel only uses the upper 6 bits of the DAC */ + else if (chan->vol >= 2) +// chan->regs[1] -= 2; + chan->vol -= 2; } } - if (chan->vol > 63) - chan->vol = 63; - else if (chan->vol < -64) - chan->vol = -64; - - return (s8) (chan->vol); + chan->output = (u8)(chan->vol); } /* WRITE REGISTER VALUE */ @@ -517,8 +569,8 @@ inline void nesapu_device::apu_regwrite(int address, u8 value) if (m_APU.tri.enabled) { /* ??? */ - if (false == m_APU.tri.counter_started) - m_APU.tri.linear_length = m_sync_times2[value & 0x7F]; + if (!m_APU.tri.counter_started) + m_APU.tri.linear_length = m_sync_times2[value & 0x7f]; } break; @@ -557,7 +609,7 @@ inline void nesapu_device::apu_regwrite(int address, u8 value) { m_APU.tri.counter_started = false; m_APU.tri.vbl_length = m_vbl_times[value >> 3]; - m_APU.tri.linear_length = m_sync_times2[m_APU.tri.regs[0] & 0x7F]; + m_APU.tri.linear_length = m_sync_times2[m_APU.tri.regs[0] & 0x7f]; } break; @@ -589,16 +641,15 @@ inline void nesapu_device::apu_regwrite(int address, u8 value) /* DMC */ case apu_t::WRE0: m_APU.dpcm.regs[0] = value; - if (0 == (value & 0x80)) { + if (!(value & 0x80)) { m_irq_handler(false); m_APU.dpcm.irq_occurred = false; } break; case apu_t::WRE1: /* 7-bit DAC */ - //m_APU.dpcm.regs[1] = value - 0x40; - m_APU.dpcm.regs[1] = value & 0x7F; - m_APU.dpcm.vol = (m_APU.dpcm.regs[1]-64); + m_APU.dpcm.regs[1] = value & 0x7f; + m_APU.dpcm.vol = m_APU.dpcm.regs[1]; break; case apu_t::WRE2: @@ -656,7 +707,7 @@ inline void nesapu_device::apu_regwrite(int address, u8 value) if (value & 0x10) { /* only reset dpcm values if DMA is finished */ - if (false == m_APU.dpcm.enabled) + if (!m_APU.dpcm.enabled) { m_APU.dpcm.enabled = true; apu_dpcmreset(&m_APU.dpcm); @@ -724,17 +775,20 @@ void nesapu_device::write(offs_t address, u8 value) void nesapu_device::sound_stream_update(sound_stream &stream, std::vector const &inputs, std::vector &outputs) { - int accum; + stream_buffer::sample_t accum = 0.0; auto &output = outputs[0]; for (int sampindex = 0; sampindex < output.samples(); sampindex++) { - accum = apu_square(&m_APU.squ[0]); - accum += apu_square(&m_APU.squ[1]); - accum += apu_triangle(&m_APU.tri); - accum += apu_noise(&m_APU.noi); - accum += apu_dpcm(&m_APU.dpcm); + apu_square(&m_APU.squ[0]); + apu_square(&m_APU.squ[1]); + apu_triangle(&m_APU.tri); + apu_noise(&m_APU.noi); + apu_dpcm(&m_APU.dpcm); + + accum = m_square_lut[m_APU.squ[0].output + m_APU.squ[1].output]; + accum += m_tnd_lut[m_APU.tri.output][m_APU.noi.output][m_APU.dpcm.output]; - output.put_int_clamp(sampindex, accum, 128); + output.put(sampindex, accum); } } diff --git a/src/devices/sound/nes_apu.h b/src/devices/sound/nes_apu.h index 921548af13a..86a69440f5e 100644 --- a/src/devices/sound/nes_apu.h +++ b/src/devices/sound/nes_apu.h @@ -79,16 +79,19 @@ private: u32 m_vbl_times[0x20]; /* VBL durations in samples */ u32 m_sync_times1[SYNCS_MAX1]; /* Samples per sync table */ u32 m_sync_times2[SYNCS_MAX2]; /* Samples per sync table */ + stream_buffer::sample_t m_square_lut[31]; // Non-linear Square wave output LUT + stream_buffer::sample_t m_tnd_lut[16][16][128]; // Non-linear Triangle, Noise, DMC output LUT + sound_stream *m_stream; devcb_write_line m_irq_handler; devcb_read8 m_mem_read_cb; void calculate_rates(); void create_syncs(unsigned long sps); - s8 apu_square(apu_t::square_t *chan); - s8 apu_triangle(apu_t::triangle_t *chan); - s8 apu_noise(apu_t::noise_t *chan); - s8 apu_dpcm(apu_t::dpcm_t *chan); + void apu_square(apu_t::square_t *chan); + void apu_triangle(apu_t::triangle_t *chan); + void apu_noise(apu_t::noise_t *chan); + void apu_dpcm(apu_t::dpcm_t *chan); inline void apu_regwrite(int address, u8 value); }; diff --git a/src/devices/sound/nes_defs.h b/src/devices/sound/nes_defs.h index 0a32a148de6..ce0909542a0 100644 --- a/src/devices/sound/nes_defs.h +++ b/src/devices/sound/nes_defs.h @@ -56,12 +56,12 @@ struct apu_t int vbl_length = 0; int freq = 0; float phaseacc = 0.0; - float output_vol = 0.0; float env_phase = 0.0; float sweep_phase = 0.0; uint8 adder = 0; uint8 env_vol = 0; bool enabled = false; + uint8 output = 0; }; /* Triangle Wave */ @@ -78,10 +78,11 @@ struct apu_t int vbl_length = 0; int write_latency = 0; float phaseacc = 0.0; - float output_vol = 0.0; uint8 adder = 0; bool counter_started = false; bool enabled = false; + uint8 output = 0; + uint8 output_next = 0; }; /* Noise Wave */ @@ -97,10 +98,10 @@ struct apu_t u32 seed = 1; int vbl_length = 0; float phaseacc = 0.0; - float output_vol = 0.0; float env_phase = 0.0; uint8 env_vol = 0; bool enabled = false; + uint8 output = 0; }; /* DPCM Wave */ @@ -117,11 +118,11 @@ struct apu_t uint32 length = 0; int bits_left = 0; float phaseacc = 0.0; - float output_vol = 0.0; uint8 cur_byte = 0; bool enabled = false; bool irq_occurred = false; - signed char vol = 0; + int16 vol = 0; + uint8 output = 0; }; @@ -225,7 +226,10 @@ static const int dpcm_clocks[2][16] = /* 2/16 = 12.5%, 4/16 = 25%, 8/16 = 50%, 12/16 = 75% */ static const int duty_lut[4] = { - 2, 4, 8, 12 + 0b01000000, // 01000000 (12.5%) + 0b01100000, // 01100000 (25%) + 0b01111000, // 01111000 (50%) + 0b10011111, // 10011111 (25% negated) }; #endif // MAME_SOUND_NES_DEFS_H -- cgit v1.2.3