mame/src/devices/sound/gb.cpp
Aaron Giles 4849a5aeba sound: Add a few new helpers to write_stream_view
* put_clamp - clamps the input value before writing
* put_int - takes an integer and coverts it to float
* put_int_clamp - converts and clamps an integer
* add_int - converts an int and adds to the current sample
2020-09-20 19:40:21 -07:00

1277 lines
32 KiB
C++

// license:BSD-3-Clause
// copyright-holders:Wilbert Pol, Anthony Kruize
// thanks-to:Shay Green
/**************************************************************************************
* Game Boy sound emulation (c) Anthony Kruize (trandor@labyrinth.net.au)
*
* Anyways, sound on the Game Boy consists of 4 separate 'channels'
* Sound1 = Quadrangular waves with SWEEP and ENVELOPE functions (NR10,11,12,13,14)
* Sound2 = Quadrangular waves with ENVELOPE functions (NR21,22,23,24)
* Sound3 = Wave patterns from WaveRAM (NR30,31,32,33,34)
* Sound4 = White noise with an envelope (NR41,42,43,44)
*
* Each sound channel has 2 modes, namely ON and OFF... whoa
*
* These tend to be the two most important equations in
* converting between Hertz and GB frequency registers:
* (Sounds will have a 2.4% higher frequency on Super GB.)
* gb = 2048 - (131072 / Hz)
* Hz = 131072 / (2048 - gb)
*
* Changes:
*
* 10/2/2002 AK - Preliminary sound code.
* 13/2/2002 AK - Added a hack for mode 4, other fixes.
* 23/2/2002 AK - Use lookup tables, added sweep to mode 1. Re-wrote the square
* wave generation.
* 13/3/2002 AK - Added mode 3, better lookup tables, other adjustments.
* 15/3/2002 AK - Mode 4 can now change frequencies.
* 31/3/2002 AK - Accidently forgot to handle counter/consecutive for mode 1.
* 3/4/2002 AK - Mode 1 sweep can still occur if shift is 0. Don't let frequency
* go past the maximum allowed value. Fixed Mode 3 length table.
* Slight adjustment to Mode 4's period table generation.
* 5/4/2002 AK - Mode 4 is done correctly, using a polynomial counter instead
* of being a total hack.
* 6/4/2002 AK - Slight tweak to mode 3's frequency calculation.
* 13/4/2002 AK - Reset envelope value when sound is initialized.
* 21/4/2002 AK - Backed out the mode 3 frequency calculation change.
* Merged init functions into gameboy_sound_w().
* 14/5/2002 AK - Removed magic numbers in the fixed point math.
* 12/6/2002 AK - Merged SOUNDx structs into one SOUND struct.
* 26/10/2002 AK - Finally fixed channel 3!
* xx/4-5/2016 WP - Rewrote sound core. Most of the code is not optimized yet.
TODO:
- Implement different behavior of CGB-02.
- Implement different behavior of CGB-05.
- Perform more tests on real hardware to figure out when the frequency counters are
reloaded.
- Perform more tests on real hardware to understand when changes to the noise divisor
and shift kick in.
- Optimize the channel update methods.
***************************************************************************************/
#include "emu.h"
#include "gb.h"
/***************************************************************************
CONSTANTS
***************************************************************************/
/* Represents wave duties of 12.5%, 25%, 50% and 75% */
const int gameboy_sound_device::wave_duty_table[4][8] =
{
{ -1, -1, -1, -1, -1, -1, -1, 1},
{ 1, -1, -1, -1, -1, -1, -1, 1},
{ 1, -1, -1, -1, -1, 1, 1, 1},
{ -1, 1, 1, 1, 1, 1, 1, -1}
};
// device type definitions
DEFINE_DEVICE_TYPE(DMG_APU, dmg_apu_device, "dmg_apu", "LR35902 APU")
//DEFINE_DEVICE_TYPE(CGB02_APU, cgb02_apu_device, "cgb02_apu", fullname)
DEFINE_DEVICE_TYPE(CGB04_APU, cgb04_apu_device, "cgb04_apu", "CGB04 APU")
//DEFINE_DEVICE_TYPE(CGB05_APU, cgb05_apu_device, "cgb05_apu", fullname)
//**************************************************************************
// LIVE DEVICE
//**************************************************************************
//-------------------------------------------------
// gameboy_sound_device - constructor
//-------------------------------------------------
gameboy_sound_device::gameboy_sound_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, type, tag, owner, clock)
, device_sound_interface(mconfig, *this)
{
}
dmg_apu_device::dmg_apu_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: gameboy_sound_device(mconfig, DMG_APU, tag, owner, clock)
{
}
cgb04_apu_device::cgb04_apu_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: gameboy_sound_device(mconfig, CGB04_APU, tag, owner, clock)
{
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
#define SAVE_CHANNEL(snd) \
save_item(NAME(snd.reg)); \
save_item(NAME(snd.on)); \
save_item(NAME(snd.channel)); \
save_item(NAME(snd.length)); \
save_item(NAME(snd.length_mask)); \
save_item(NAME(snd.length_counting)); \
save_item(NAME(snd.length_enabled)); \
save_item(NAME(snd.cycles_left)); \
save_item(NAME(snd.duty)); \
save_item(NAME(snd.envelope_enabled)); \
save_item(NAME(snd.envelope_value)); \
save_item(NAME(snd.envelope_direction)); \
save_item(NAME(snd.envelope_time)); \
save_item(NAME(snd.envelope_count)); \
save_item(NAME(snd.signal)); \
save_item(NAME(snd.frequency)); \
save_item(NAME(snd.frequency_counter)); \
save_item(NAME(snd.sweep_enabled)); \
save_item(NAME(snd.sweep_neg_mode_used)); \
save_item(NAME(snd.sweep_shift)); \
save_item(NAME(snd.sweep_direction)); \
save_item(NAME(snd.sweep_time)); \
save_item(NAME(snd.sweep_count)); \
save_item(NAME(snd.level)); \
save_item(NAME(snd.offset)); \
save_item(NAME(snd.duty_count)); \
save_item(NAME(snd.current_sample)); \
save_item(NAME(snd.sample_reading)); \
save_item(NAME(snd.noise_short)); \
save_item(NAME(snd.noise_lfsr));
void gameboy_sound_device::device_start()
{
m_channel = stream_alloc(0, 2, SAMPLE_RATE_OUTPUT_ADAPTIVE);
m_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(gameboy_sound_device::timer_callback),this));
m_timer->adjust(clocks_to_attotime(FRAME_CYCLES/128), 0, clocks_to_attotime(FRAME_CYCLES/128));
save_item(NAME(m_last_updated));
save_item(NAME(m_snd_regs));
// sound control
save_item(NAME(m_snd_control.on));
save_item(NAME(m_snd_control.vol_left));
save_item(NAME(m_snd_control.vol_right));
save_item(NAME(m_snd_control.mode1_left));
save_item(NAME(m_snd_control.mode1_right));
save_item(NAME(m_snd_control.mode2_left));
save_item(NAME(m_snd_control.mode2_right));
save_item(NAME(m_snd_control.mode3_left));
save_item(NAME(m_snd_control.mode3_right));
save_item(NAME(m_snd_control.mode4_left));
save_item(NAME(m_snd_control.mode4_right));
save_item(NAME(m_snd_control.cycles));
SAVE_CHANNEL(m_snd_1);
SAVE_CHANNEL(m_snd_2);
SAVE_CHANNEL(m_snd_3);
SAVE_CHANNEL(m_snd_4);
}
//-------------------------------------------------
// device_clock_changed
//-------------------------------------------------
void gameboy_sound_device::device_clock_changed()
{
m_timer->adjust(clocks_to_attotime(FRAME_CYCLES / 128), 0, clocks_to_attotime(FRAME_CYCLES / 128));
}
//-------------------------------------------------
// device_reset
//-------------------------------------------------
void gameboy_sound_device::device_reset()
{
memset(&m_snd_1, 0, sizeof(m_snd_1));
memset(&m_snd_2, 0, sizeof(m_snd_2));
memset(&m_snd_3, 0, sizeof(m_snd_3));
memset(&m_snd_4, 0, sizeof(m_snd_4));
m_snd_1.channel = 1;
m_snd_1.length_mask = 0x3F;
m_snd_2.channel = 2;
m_snd_2.length_mask = 0x3F;
m_snd_3.channel = 3;
m_snd_3.length_mask = 0xFF;
m_snd_4.channel = 4;
m_snd_4.length_mask = 0x3F;
sound_w_internal(NR52, 0x00);
m_snd_regs[AUD3W0] = 0xac;
m_snd_regs[AUD3W1] = 0xdd;
m_snd_regs[AUD3W2] = 0xda;
m_snd_regs[AUD3W3] = 0x48;
m_snd_regs[AUD3W4] = 0x36;
m_snd_regs[AUD3W5] = 0x02;
m_snd_regs[AUD3W6] = 0xcf;
m_snd_regs[AUD3W7] = 0x16;
m_snd_regs[AUD3W8] = 0x2c;
m_snd_regs[AUD3W9] = 0x04;
m_snd_regs[AUD3WA] = 0xe5;
m_snd_regs[AUD3WB] = 0x2c;
m_snd_regs[AUD3WC] = 0xac;
m_snd_regs[AUD3WD] = 0xdd;
m_snd_regs[AUD3WE] = 0xda;
m_snd_regs[AUD3WF] = 0x48;
}
void cgb04_apu_device::device_reset()
{
gameboy_sound_device::device_reset();
m_snd_regs[AUD3W0] = 0x00;
m_snd_regs[AUD3W1] = 0xFF;
m_snd_regs[AUD3W2] = 0x00;
m_snd_regs[AUD3W3] = 0xFF;
m_snd_regs[AUD3W4] = 0x00;
m_snd_regs[AUD3W5] = 0xFF;
m_snd_regs[AUD3W6] = 0x00;
m_snd_regs[AUD3W7] = 0xFF;
m_snd_regs[AUD3W8] = 0x00;
m_snd_regs[AUD3W9] = 0xFF;
m_snd_regs[AUD3WA] = 0x00;
m_snd_regs[AUD3WB] = 0xFF;
m_snd_regs[AUD3WC] = 0x00;
m_snd_regs[AUD3WD] = 0xFF;
m_snd_regs[AUD3WE] = 0x00;
m_snd_regs[AUD3WF] = 0xFF;
}
/***************************************************************************
IMPLEMENTATION
***************************************************************************/
TIMER_CALLBACK_MEMBER(gameboy_sound_device::timer_callback)
{
m_channel->update();
update_state();
}
void gameboy_sound_device::tick_length(struct SOUND &snd)
{
if (snd.length_enabled)
{
snd.length = (snd.length + 1) & snd.length_mask;
if (snd.length == 0)
{
snd.on = false;
snd.length_counting = false;
}
}
}
int32_t gameboy_sound_device::calculate_next_sweep(struct SOUND &snd)
{
snd.sweep_neg_mode_used = (snd.sweep_direction < 0);
int32_t new_frequency = snd.frequency + snd.sweep_direction * (snd.frequency >> snd.sweep_shift);
if (new_frequency > 0x7FF)
{
snd.on = false;
}
return new_frequency;
}
void gameboy_sound_device::apply_next_sweep(struct SOUND &snd)
{
int32_t new_frequency = calculate_next_sweep(snd);
if (snd.on && snd.sweep_shift > 0)
{
snd.frequency = new_frequency;
snd.reg[3] = snd.frequency & 0xFF;
}
}
void gameboy_sound_device::tick_sweep(struct SOUND &snd)
{
snd.sweep_count = (snd.sweep_count - 1) & 0x07;
if (snd.sweep_count == 0)
{
snd.sweep_count = snd.sweep_time;
if (snd.sweep_enabled && snd.sweep_time > 0)
{
apply_next_sweep(snd);
calculate_next_sweep(snd);
}
}
}
void gameboy_sound_device::tick_envelope(struct SOUND &snd)
{
if (snd.envelope_enabled)
{
snd.envelope_count = (snd.envelope_count - 1) & 0x07;
if (snd.envelope_count == 0)
{
snd.envelope_count = snd.envelope_time;
if (snd.envelope_count)
{
int8_t new_envelope_value = snd.envelope_value + snd.envelope_direction;
if (new_envelope_value >= 0 && new_envelope_value <= 15)
{
snd.envelope_value = new_envelope_value;
}
else
{
snd.envelope_enabled = false;
}
}
}
}
}
bool gameboy_sound_device::dac_enabled(struct SOUND &snd)
{
return (snd.channel != 3) ? snd.reg[2] & 0xF8 : snd.reg[0] & 0x80;
}
void gameboy_sound_device::update_square_channel(struct SOUND &snd, uint64_t cycles)
{
if (snd.on)
{
// compensate for leftover cycles
if (snd.cycles_left > 0)
{
// Emit sample(s)
if (cycles <= snd.cycles_left)
{
snd.cycles_left -= cycles;
cycles = 0;
}
else
{
cycles -= snd.cycles_left;
snd.cycles_left = 0;
}
}
if (cycles & 3)
{
snd.cycles_left = 4 - (cycles & 3);
}
cycles >>= 2;
uint16_t distance = 0x800 - snd.frequency_counter;
if (cycles >= distance)
{
cycles -= distance;
distance = 0x800 - snd.frequency;
uint64_t counter = 1 + cycles / distance;
snd.duty_count = (snd.duty_count + counter) & 0x07;
snd.signal = wave_duty_table[snd.duty][snd.duty_count];
snd.frequency_counter = snd.frequency + cycles % distance;
}
else
{
snd.frequency_counter += cycles;
}
}
}
void dmg_apu_device::update_wave_channel(struct SOUND &snd, uint64_t cycles)
{
if (snd.on)
{
// compensate for leftover cycles
if (snd.cycles_left > 0)
{
if (cycles <= snd.cycles_left)
{
// Emit samples
snd.cycles_left -= cycles;
cycles = 0;
}
else
{
// Emit samples
cycles -= snd.cycles_left;
snd.cycles_left = 0;
}
}
while (cycles > 0)
{
// Emit current sample
// cycles -= 2
if (cycles < 2)
{
snd.cycles_left = 2 - cycles;
cycles = 0;
}
else
{
cycles -= 2;
// Calculate next state
snd.frequency_counter = (snd.frequency_counter + 1) & 0x7FF;
snd.sample_reading = false;
if (snd.frequency_counter == 0x7ff)
{
snd.offset = (snd.offset + 1) & 0x1F;
}
if (snd.frequency_counter == 0)
{
// Read next sample
snd.sample_reading = true;
snd.current_sample = m_snd_regs[AUD3W0 + (snd.offset/2)];
if (!(snd.offset & 0x01))
{
snd.current_sample >>= 4;
}
snd.current_sample = (snd.current_sample & 0x0F) - 8;
snd.signal = snd.level ? snd.current_sample / (1 << (snd.level - 1)) : 0;
// Reload frequency counter
snd.frequency_counter = snd.frequency;
}
}
}
}
}
void cgb04_apu_device::update_wave_channel(struct SOUND &snd, uint64_t cycles)
{
if (snd.on)
{
// compensate for left over cycles
if (snd.cycles_left > 0)
{
if (cycles <= snd.cycles_left)
{
// Emit samples
snd.cycles_left -= cycles;
cycles = 0;
}
else
{
// Emit samples
cycles -= snd.cycles_left;
snd.cycles_left = 0;
}
}
if (cycles & 1)
{
snd.cycles_left = 1;
}
cycles >>= 1;
uint16_t distance = 0x800 - snd.frequency_counter;
if (cycles >= distance)
{
cycles -= distance;
distance = 0x800 - snd.frequency;
// How many times the condition snd.frequency_counter == 0 is true
uint64_t counter = 1 + cycles / distance;
snd.offset = (snd.offset + counter) & 0x1F;
snd.current_sample = m_snd_regs[AUD3W0 + snd.offset / 2];
if (!(snd.offset & 1))
{
snd.current_sample >>= 4;
}
snd.current_sample = (snd.current_sample & 0x0F) - 8;
snd.signal = snd.level ? snd.current_sample / (1 << (snd.level - 1)) : 0;
cycles %= distance;
snd.sample_reading = cycles ? false : true;
snd.frequency_counter = snd.frequency + cycles;
}
else
{
snd.frequency_counter += cycles;
}
}
}
void gameboy_sound_device::update_noise_channel(struct SOUND &snd, uint64_t cycles)
{
if (cycles >= snd.cycles_left)
{
cycles -= snd.cycles_left;
uint64_t period = noise_period_cycles();
uint64_t counter = 1 + cycles / period, i = 0;
uint16_t start = snd.noise_lfsr;
while (i < counter) {
/* Using a Polynomial Counter (aka Linear Feedback Shift Register)
Mode 4 has a 15 bit counter so we need to shift the
bits around accordingly */
uint16_t feedback = ((snd.noise_lfsr >> 1) ^ snd.noise_lfsr) & 1;
snd.noise_lfsr = (snd.noise_lfsr >> 1) | (feedback << 14);
if (snd.noise_short)
{
snd.noise_lfsr = (snd.noise_lfsr & ~(1 << 6)) | (feedback << 6);
}
i += 1;
if (snd.noise_lfsr == start)
{
counter %= i;
i = 0;
}
}
snd.signal = (snd.noise_lfsr & 1) ? -1 : 1;
snd.cycles_left = period - cycles % period;
}
else
{
snd.cycles_left -= cycles;
}
}
void gameboy_sound_device::update_state()
{
attotime now = machine().time();
// No time travelling
if (now <= m_last_updated)
{
return;
}
if (m_snd_control.on)
{
uint64_t cycles = attotime_to_clocks(now - m_last_updated);
uint64_t old_cycles = m_snd_control.cycles;
m_snd_control.cycles += cycles;
if ((old_cycles / FRAME_CYCLES) != (m_snd_control.cycles / FRAME_CYCLES))
{
// Left over cycles in current frame
uint64_t cycles_current_frame = FRAME_CYCLES - (old_cycles & (FRAME_CYCLES - 1));
update_square_channel(m_snd_1, cycles_current_frame);
update_square_channel(m_snd_2, cycles_current_frame);
update_wave_channel(m_snd_3, cycles_current_frame);
update_noise_channel(m_snd_4, cycles_current_frame);
cycles -= cycles_current_frame;
// Switch to next frame
switch ((m_snd_control.cycles / FRAME_CYCLES) & 0x07)
{
case 0:
// length
tick_length(m_snd_1);
tick_length(m_snd_2);
tick_length(m_snd_3);
tick_length(m_snd_4);
break;
case 2:
// sweep
tick_sweep(m_snd_1);
// length
tick_length(m_snd_1);
tick_length(m_snd_2);
tick_length(m_snd_3);
tick_length(m_snd_4);
break;
case 4:
// length
tick_length(m_snd_1);
tick_length(m_snd_2);
tick_length(m_snd_3);
tick_length(m_snd_4);
break;
case 6:
// sweep
tick_sweep(m_snd_1);
// length
tick_length(m_snd_1);
tick_length(m_snd_2);
tick_length(m_snd_3);
tick_length(m_snd_4);
break;
case 7:
// update envelope
tick_envelope(m_snd_1);
tick_envelope(m_snd_2);
tick_envelope(m_snd_4);
break;
}
}
update_square_channel(m_snd_1, cycles);
update_square_channel(m_snd_2, cycles);
update_wave_channel(m_snd_3, cycles);
update_noise_channel(m_snd_4, cycles);
}
m_last_updated = now;
}
uint64_t gameboy_sound_device::noise_period_cycles()
{
static const int divisor[8] = { 8, 16,32, 48, 64, 80, 96, 112 };
return divisor[m_snd_4.reg[3] & 7] << (m_snd_4.reg[3] >> 4);
}
u8 dmg_apu_device::wave_r(offs_t offset)
{
m_channel->update();
update_state();
if (m_snd_3.on)
{
return m_snd_3.sample_reading ? m_snd_regs[AUD3W0 + (m_snd_3.offset/2)] : 0xFF;
}
return m_snd_regs[AUD3W0 + offset];
}
u8 cgb04_apu_device::wave_r(offs_t offset)
{
m_channel->update();
update_state();
if (m_snd_3.on)
{
return m_snd_regs[AUD3W0 + (m_snd_3.offset/2)];
}
return m_snd_regs[AUD3W0 + offset];
}
u8 gameboy_sound_device::sound_r(offs_t offset)
{
static const uint8_t read_mask[0x40] =
{
0x80,0x3F,0x00,0xFF,0xBF,0xFF,0x3F,0x00,0xFF,0xBF,0x7F,0xFF,0x9F,0xFF,0xBF,0xFF,
0xFF,0x00,0x00,0xBF,0x00,0x00,0x70,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
// Make sure we are up to date.
m_channel->update();
update_state();
if (m_snd_control.on)
{
if (offset == NR52)
{
return (m_snd_regs[NR52]&0xf0) | (m_snd_1.on ? 1 : 0) | (m_snd_2.on ? 2 : 0) | (m_snd_3.on ? 4 : 0) | (m_snd_4.on ? 8 : 0) | 0x70;
}
return m_snd_regs[offset] | read_mask[offset & 0x3F];
}
else
{
return read_mask[offset & 0x3F];
}
}
void dmg_apu_device::wave_w(offs_t offset, u8 data)
{
m_channel->update();
update_state();
if (m_snd_3.on)
{
if (m_snd_3.sample_reading)
{
m_snd_regs[AUD3W0 + (m_snd_3.offset/2)] = data;
}
}
else
{
m_snd_regs[AUD3W0 + offset] = data;
}
}
void cgb04_apu_device::wave_w(offs_t offset, u8 data)
{
m_channel->update();
update_state();
if (m_snd_3.on)
{
m_snd_regs[AUD3W0 + (m_snd_3.offset/2)] = data;
}
else
{
m_snd_regs[AUD3W0 + offset] = data;
}
}
void dmg_apu_device::sound_w(offs_t offset, u8 data)
{
/* change in registers so update first */
m_channel->update();
update_state();
/* Only register NR52 is accessible if the sound controller is disabled */
if (!m_snd_control.on && offset != NR52 && offset != NR11 && offset != NR21 && offset != NR31 && offset != NR41)
return;
sound_w_internal(offset, data);
}
void cgb04_apu_device::sound_w(offs_t offset, u8 data)
{
/* change in registers so update first */
m_channel->update();
update_state();
/* Only register NR52 is accessible if the sound controller is disabled */
if (!m_snd_control.on && offset != NR52)
return;
sound_w_internal(offset, data);
}
void dmg_apu_device::corrupt_wave_ram()
{
if (m_snd_3.offset < 8)
{
m_snd_regs[AUD3W0] = m_snd_regs[AUD3W0 + (m_snd_3.offset/2)];
}
else
{
for (int i = 0; i < 4; i++)
{
m_snd_regs[AUD3W0 + i] = m_snd_regs[AUD3W0 + ((m_snd_3.offset / 2) & ~0x03) + i];
}
}
}
void gameboy_sound_device::sound_w_internal( int offset, uint8_t data )
{
/* Store the value */
uint8_t old_data = m_snd_regs[offset];
if (m_snd_control.on)
{
m_snd_regs[offset] = data;
}
switch (offset)
{
/*MODE 1 */
case NR10: /* Sweep (R/W) */
m_snd_1.reg[0] = data;
m_snd_1.sweep_shift = data & 0x7;
m_snd_1.sweep_direction = (data & 0x8) ? -1 : 1;
m_snd_1.sweep_time = (data & 0x70) >> 4;
if ((old_data & 0x08) && !(data & 0x08) && m_snd_1.sweep_neg_mode_used)
{
m_snd_1.on = false;
}
break;
case NR11: /* Sound length/Wave pattern duty (R/W) */
m_snd_1.reg[1] = data;
if (m_snd_control.on)
{
m_snd_1.duty = (data & 0xc0) >> 6;
}
m_snd_1.length = data & 0x3f;
m_snd_1.length_counting = true;
break;
case NR12: /* Envelope (R/W) */
m_snd_1.reg[2] = data;
m_snd_1.envelope_value = data >> 4;
m_snd_1.envelope_direction = (data & 0x8) ? 1 : -1;
m_snd_1.envelope_time = data & 0x07;
if (!dac_enabled(m_snd_1))
{
m_snd_1.on = false;
}
break;
case NR13: /* Frequency lo (R/W) */
m_snd_1.reg[3] = data;
// Only enabling the frequency line breaks blarggs's sound test #5
// This condition may not be correct
if (!m_snd_1.sweep_enabled)
{
m_snd_1.frequency = ((m_snd_1.reg[4] & 0x7) << 8) | m_snd_1.reg[3];
}
break;
case NR14: /* Frequency hi / Initialize (R/W) */
m_snd_1.reg[4] = data;
{
bool length_was_enabled = m_snd_1.length_enabled;
m_snd_1.length_enabled = (data & 0x40) ? true : false;
m_snd_1.frequency = ((m_snd_regs[NR14] & 0x7) << 8) | m_snd_1.reg[3];
if (!length_was_enabled && !(m_snd_control.cycles & FRAME_CYCLES) && m_snd_1.length_counting)
{
if (m_snd_1.length_enabled)
{
tick_length(m_snd_1);
}
}
if (data & 0x80)
{
m_snd_1.on = true;
m_snd_1.envelope_enabled = true;
m_snd_1.envelope_value = m_snd_1.reg[2] >> 4;
m_snd_1.envelope_count = m_snd_1.envelope_time;
m_snd_1.sweep_count = m_snd_1.sweep_time;
m_snd_1.sweep_neg_mode_used = false;
m_snd_1.signal = 0;
m_snd_1.length_counting = true;
m_snd_1.frequency = ((m_snd_1.reg[4] & 0x7) << 8) | m_snd_1.reg[3];
m_snd_1.frequency_counter = m_snd_1.frequency;
m_snd_1.cycles_left = 0;
m_snd_1.duty_count = 0;
m_snd_1.sweep_enabled = (m_snd_1.sweep_shift != 0) || (m_snd_1.sweep_time != 0);
if (!dac_enabled(m_snd_1))
{
m_snd_1.on = false;
}
if (m_snd_1.sweep_shift > 0)
{
calculate_next_sweep(m_snd_1);
}
if (m_snd_1.length == 0 && m_snd_1.length_enabled && !(m_snd_control.cycles & FRAME_CYCLES))
{
tick_length(m_snd_1);
}
}
else
{
// This condition may not be correct
if (!m_snd_1.sweep_enabled)
{
m_snd_1.frequency = ((m_snd_1.reg[4] & 0x7) << 8) | m_snd_1.reg[3];
}
}
}
break;
/*MODE 2 */
case NR21: /* Sound length/Wave pattern duty (R/W) */
m_snd_2.reg[1] = data;
if (m_snd_control.on)
{
m_snd_2.duty = (data & 0xc0) >> 6;
}
m_snd_2.length = data & 0x3f;
m_snd_2.length_counting = true;
break;
case NR22: /* Envelope (R/W) */
m_snd_2.reg[2] = data;
m_snd_2.envelope_value = data >> 4;
m_snd_2.envelope_direction = (data & 0x8) ? 1 : -1;
m_snd_2.envelope_time = data & 0x07;
if (!dac_enabled(m_snd_2))
{
m_snd_2.on = false;
}
break;
case NR23: /* Frequency lo (R/W) */
m_snd_2.reg[3] = data;
m_snd_2.frequency = ((m_snd_2.reg[4] & 0x7) << 8) | m_snd_2.reg[3];
break;
case NR24: /* Frequency hi / Initialize (R/W) */
m_snd_2.reg[4] = data;
{
bool length_was_enabled = m_snd_2.length_enabled;
m_snd_2.length_enabled = (data & 0x40) ? true : false;
if (!length_was_enabled && !(m_snd_control.cycles & FRAME_CYCLES) && m_snd_2.length_counting)
{
if (m_snd_2.length_enabled)
{
tick_length(m_snd_2);
}
}
if (data & 0x80)
{
m_snd_2.on = true;
m_snd_2.envelope_enabled = true;
m_snd_2.envelope_value = m_snd_2.reg[2] >> 4;
m_snd_2.envelope_count = m_snd_2.envelope_time;
m_snd_2.frequency = ((m_snd_2.reg[4] & 0x7) << 8) | m_snd_2.reg[3];
m_snd_2.frequency_counter = m_snd_2.frequency;
m_snd_2.cycles_left = 0;
m_snd_2.duty_count = 0;
m_snd_2.signal = 0;
m_snd_2.length_counting = true;
if (!dac_enabled(m_snd_2))
{
m_snd_2.on = false;
}
if (m_snd_2.length == 0 && m_snd_2.length_enabled && !(m_snd_control.cycles & FRAME_CYCLES))
{
tick_length(m_snd_2);
}
}
else
{
m_snd_2.frequency = ((m_snd_2.reg[4] & 0x7) << 8) | m_snd_2.reg[3];
}
}
break;
/*MODE 3 */
case NR30: /* Sound On/Off (R/W) */
m_snd_3.reg[0] = data;
if (!dac_enabled(m_snd_3))
{
m_snd_3.on = false;
}
break;
case NR31: /* Sound Length (R/W) */
m_snd_3.reg[1] = data;
m_snd_3.length = data;
m_snd_3.length_counting = true;
break;
case NR32: /* Select Output Level */
m_snd_3.reg[2] = data;
m_snd_3.level = (data & 0x60) >> 5;
break;
case NR33: /* Frequency lo (W) */
m_snd_3.reg[3] = data;
m_snd_3.frequency = ((m_snd_3.reg[4] & 0x7) << 8) | m_snd_3.reg[3];
break;
case NR34: /* Frequency hi / Initialize (W) */
m_snd_3.reg[4] = data;
{
bool length_was_enabled = m_snd_3.length_enabled;
m_snd_3.length_enabled = (data & 0x40) ? true : false;
if (!length_was_enabled && !(m_snd_control.cycles & FRAME_CYCLES) && m_snd_3.length_counting)
{
if (m_snd_3.length_enabled)
{
tick_length(m_snd_3);
}
}
if (data & 0x80)
{
if (m_snd_3.on && m_snd_3.frequency_counter == 0x7ff)
{
corrupt_wave_ram();
}
m_snd_3.on = true;
m_snd_3.offset = 0;
m_snd_3.duty = 1;
m_snd_3.duty_count = 0;
m_snd_3.length_counting = true;
m_snd_3.frequency = ((m_snd_3.reg[4] & 0x7) << 8) | m_snd_3.reg[3];
m_snd_3.frequency_counter = m_snd_3.frequency;
// There is a tiny bit of delay in starting up the wave channel(?)
//
// Results from older code where corruption of wave ram was triggered when sample_reading == true:
// 4 breaks test 09 (read wram), fixes test 10 (write trigger), breaks test 12 (write wram)
// 6 fixes test 09 (read wram), breaks test 10 (write trigger), fixes test 12 (write wram)
m_snd_3.cycles_left = 0 + 6;
m_snd_3.sample_reading = false;
if (!dac_enabled(m_snd_3))
{
m_snd_3.on = false;
}
if (m_snd_3.length == 0 && m_snd_3.length_enabled && !(m_snd_control.cycles & FRAME_CYCLES))
{
tick_length(m_snd_3);
}
}
else
{
m_snd_3.frequency = ((m_snd_3.reg[4] & 0x7) << 8) | m_snd_3.reg[3];
}
}
break;
/*MODE 4 */
case NR41: /* Sound Length (R/W) */
m_snd_4.reg[1] = data;
m_snd_4.length = data & 0x3f;
m_snd_4.length_counting = true;
break;
case NR42: /* Envelope (R/W) */
m_snd_4.reg[2] = data;
m_snd_4.envelope_value = data >> 4;
m_snd_4.envelope_direction = (data & 0x8) ? 1 : -1;
m_snd_4.envelope_time = data & 0x07;
if (!dac_enabled(m_snd_4))
{
m_snd_4.on = false;
}
break;
case NR43: /* Polynomial Counter/Frequency */
m_snd_4.reg[3] = data;
m_snd_4.noise_short = (data & 0x8);
break;
case NR44: /* Counter/Consecutive / Initialize (R/W) */
m_snd_4.reg[4] = data;
{
bool length_was_enabled = m_snd_4.length_enabled;
m_snd_4.length_enabled = (data & 0x40) ? true : false;
if (!length_was_enabled && !(m_snd_control.cycles & FRAME_CYCLES) && m_snd_4.length_counting)
{
if (m_snd_4.length_enabled)
{
tick_length(m_snd_4);
}
}
if (data & 0x80)
{
m_snd_4.on = true;
m_snd_4.envelope_enabled = true;
m_snd_4.envelope_value = m_snd_4.reg[2] >> 4;
m_snd_4.envelope_count = m_snd_4.envelope_time;
m_snd_4.frequency_counter = 0;
m_snd_4.cycles_left = noise_period_cycles();
m_snd_4.signal = -1;
m_snd_4.noise_lfsr = 0x7fff;
m_snd_4.length_counting = true;
if (!dac_enabled(m_snd_4))
{
m_snd_4.on = false;
}
if (m_snd_4.length == 0 && m_snd_4.length_enabled && !(m_snd_control.cycles & FRAME_CYCLES))
{
tick_length(m_snd_4);
}
}
}
break;
/* CONTROL */
case NR50: /* Channel Control / On/Off / Volume (R/W) */
m_snd_control.vol_left = data & 0x7;
m_snd_control.vol_right = (data & 0x70) >> 4;
break;
case NR51: /* Selection of Sound Output Terminal */
m_snd_control.mode1_right = data & 0x1;
m_snd_control.mode1_left = (data & 0x10) >> 4;
m_snd_control.mode2_right = (data & 0x2) >> 1;
m_snd_control.mode2_left = (data & 0x20) >> 5;
m_snd_control.mode3_right = (data & 0x4) >> 2;
m_snd_control.mode3_left = (data & 0x40) >> 6;
m_snd_control.mode4_right = (data & 0x8) >> 3;
m_snd_control.mode4_left = (data & 0x80) >> 7;
break;
case NR52: // Sound On/Off (R/W)
// Only bit 7 is writable, writing to bits 0-3 does NOT enable or disable sound. They are read-only.
if (!(data & 0x80))
{
// On DMG the length counters are not affected and not clocked
// powering off should actually clear all registers
apu_power_off();
}
else
{
if (!m_snd_control.on)
{
// When switching on, the next step should be 0.
m_snd_control.cycles |= 7 * FRAME_CYCLES;
}
}
m_snd_control.on = (data & 0x80) ? true : false;
m_snd_regs[NR52] = data & 0x80;
break;
}
}
void dmg_apu_device::apu_power_off()
{
sound_w_internal(NR10, 0x00);
m_snd_1.duty = 0;
m_snd_regs[NR11] = 0;
sound_w_internal(NR12, 0x00);
sound_w_internal(NR13, 0x00);
sound_w_internal(NR14, 0x00);
m_snd_1.length_counting = false;
m_snd_1.sweep_neg_mode_used = false;
m_snd_regs[NR21] = 0;
sound_w_internal(NR22, 0x00);
sound_w_internal(NR23, 0x00);
sound_w_internal(NR24, 0x00);
m_snd_2.length_counting = false;
sound_w_internal(NR30, 0x00);
sound_w_internal(NR32, 0x00);
sound_w_internal(NR33, 0x00);
sound_w_internal(NR34, 0x00);
m_snd_3.length_counting = false;
m_snd_3.current_sample = 0;
m_snd_regs[NR41] = 0;
sound_w_internal(NR42, 0x00);
sound_w_internal(NR43, 0x00);
sound_w_internal(NR44, 0x00);
m_snd_4.length_counting = false;
m_snd_4.cycles_left = noise_period_cycles();
m_snd_1.on = false;
m_snd_2.on = false;
m_snd_3.on = false;
m_snd_4.on = false;
m_snd_control.wave_ram_locked = false;
for (int i = NR44 + 1; i < NR52; i++)
{
sound_w_internal(i, 0x00);
}
}
void cgb04_apu_device::apu_power_off()
{
sound_w_internal(NR10, 0x00);
m_snd_1.duty = 0;
sound_w_internal(NR11, 0x00);
sound_w_internal(NR12, 0x00);
sound_w_internal(NR13, 0x00);
sound_w_internal(NR14, 0x00);
m_snd_1.length_counting = false;
m_snd_1.sweep_neg_mode_used = false;
sound_w_internal(NR21, 0x00);
sound_w_internal(NR22, 0x00);
sound_w_internal(NR23, 0x00);
sound_w_internal(NR24, 0x00);
m_snd_2.length_counting = false;
sound_w_internal(NR30, 0x00);
sound_w_internal(NR31, 0x00);
sound_w_internal(NR32, 0x00);
sound_w_internal(NR33, 0x00);
sound_w_internal(NR34, 0x00);
m_snd_3.length_counting = false;
m_snd_3.current_sample = 0;
sound_w_internal(NR41, 0x00);
sound_w_internal(NR42, 0x00);
sound_w_internal(NR43, 0x00);
sound_w_internal(NR44, 0x00);
m_snd_4.length_counting = false;
m_snd_4.cycles_left = noise_period_cycles();
m_snd_1.on = false;
m_snd_2.on = false;
m_snd_3.on = false;
m_snd_4.on = false;
m_snd_control.wave_ram_locked = false;
for (int i = NR44 + 1; i < NR52; i++)
{
sound_w_internal(i, 0x00);
}
}
//-------------------------------------------------
// sound_stream_update_legacy - handle a stream update
//-------------------------------------------------
void gameboy_sound_device::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)
{
auto &outputl = outputs[0];
auto &outputr = outputs[1];
for (int sampindex = 0; sampindex < outputl.samples(); sampindex++)
{
s32 sample;
s32 left = 0;
s32 right = 0;
/* Mode 1 - Wave with Envelope and Sweep */
if (m_snd_1.on)
{
sample = m_snd_1.signal * m_snd_1.envelope_value;
if (m_snd_control.mode1_left)
left += sample;
if (m_snd_control.mode1_right)
right += sample;
}
/* Mode 2 - Wave with Envelope */
if (m_snd_2.on)
{
sample = m_snd_2.signal * m_snd_2.envelope_value;
if (m_snd_control.mode2_left)
left += sample;
if (m_snd_control.mode2_right)
right += sample;
}
/* Mode 3 - Wave patterns from WaveRAM */
if (m_snd_3.on)
{
sample = m_snd_3.signal;
if (m_snd_control.mode3_left)
left += sample;
if (m_snd_control.mode3_right)
right += sample;
}
/* Mode 4 - Noise with Envelope */
if (m_snd_4.on)
{
sample = m_snd_4.signal * m_snd_4.envelope_value;
if (m_snd_control.mode4_left)
left += sample;
if (m_snd_control.mode4_right)
right += sample;
}
/* Adjust for master volume */
left *= m_snd_control.vol_left;
right *= m_snd_control.vol_right;
/* Update the buffers */
outputl.put_int(sampindex, left, 32768 / 64);
outputr.put_int(sampindex, right, 32768 / 64);
}
}