summaryrefslogblamecommitdiffstatshomepage
path: root/src/devices/sound/gb.cpp
blob: e5fe37688cc6f8b5a3e040fd938ef920378eb246 (plain) (tree)
1
2
3
                       

                                                





































                                                                                         










                                                                                     









                                                                                        

                                                       








                                                       



                                                                         
 


                                                                            
 


                                                   
 

                                                                                                                                             
                                                

 
 
 
                                                                                                               
                                                                   

 
 
 
                                                                                                                   
                                                                     



 


                                                   
 
































                                                   
                                         
 
                                                                                         

                                                                                                                             
 
                                        












                                                   
                                              




                              
 
 
 
                                                   









                                                                                                           

                                                   
 
                                         
 



                                             
 







                                   
 
















                                     


 






















                                             
                                                                            


                                                                            




















                                                                
                                                                     

                                                            
                                                                                                         











                                                              
                                                          




































                                                                     
                                                                                                        




















                                                                                        
                                                                                    


                   
                                                 














                                                          
                               
                 


                                                           
                                                                     



                                                         
                                                                    
 







                                                                                  




                 
                                                                            
 

                   
                                                 
























































                                                                                                                 

 
 
                                                                              




















                                                          
                               
                 


                                            
                                                                     




                                                                                          
                                                                    



                                                                                 
                         
                                                         
                         

                                                                                                 
 

                                                                   
 




                                                                       




                 
                                                                                   
 
                                      
         
                                          


                                                                 
                                     


                                                                                          
                                                                                         




                                                                                                





                                                    
                 





                                                           















                                         
                                                                           
 
                                                           




                                                                                         
                                                                                                         





























































                                                                             
                                                    





                                                                        
                                        












                                                                                               
                                          












                                                               
                                               
 
                                              
         




















                                                                                                                                                          
         

 
 
                                                   
 













                                                                       

 
 
                                                     














                                                               
                                                    












                                                                                                                        
                                                      
 

                                                 
                       
 


                                                                                  
 

                                       
 
 















                                                                                                         
                                                                       
 
                             
                                              




                                          
 
                       


                                    







                                                                                       

                                                             






                                                          

                                       







                                                                   

                                           
                                      





                                                                                           

                                                        











































                                                                                                                            







                                                                                                           




                                                             






                                                          

                                       







                                                                   

                                           

                                                                                   

                                                        




































                                                                                                                            



                                                                                                   




                                           




                                           

                                           


                                               

                                            
                                      
                                                   

                                         
                                      
                                                                                   

                                                      
                                      
                 












                                                                                                                     
                         




                                                                                     
                                                   


                                                               
                                                                                                   














                                                                                                                                  
                                 

                                                             
                         



                                                                                                   




                                           


                                               

                                       







                                                                   

                                                     

                                                   

                                                                































                                                                                                                            
                                 


                                                             




                                                                 

                                                             

                                                           







                                                              
                      






                                                                                                                    
                 









                                                                                



                      
 












































                                                    

                                      







                                            
 




                                        
 




























                                                    


                                                   
 
                                                                                                                                      
 
                             
         


                                          
 
                                                           
                               
                 
                                                                         
 
                                                     
                                               
                                                      

                                                
 
                                                 
                               
                 
                                                                         
                                                     
                                               
                                                      

                                                
 
                                                         
                               
                 
                                                
                                                     
                                               
                                                      

                                                
 
                                                  
                               
                 
                                                                         
                                                     
                                               
                                                      

                                                
 
                                              

                                                 
 


                                        
 



                                        
 
// 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 = machine().sound().stream_alloc(*this, 0, 2, machine().sample_rate());
	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 - handle a stream update
//-------------------------------------------------

void gameboy_sound_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
	while (samples-- > 0)
	{
		stream_sample_t sample;
		stream_sample_t left = 0;
		stream_sample_t 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;

		/* pump up the volume */
		left <<= 6;
		right <<= 6;

		/* Update the buffers */
		*(outputs[0]++) = left;
		*(outputs[1]++) = right;
	}
}