summaryrefslogblamecommitdiffstatshomepage
path: root/src/devices/sound/ymf278b.cpp
blob: 18451d717327d2431c9a5d155d8dc88026347889 (plain) (tree)
1
2
                       
                                                      






































                                                                                                         



                                 
                                                                                      

  
                
                    
                   
 

                                                        
 
 

                                                                            
                                                            








                          
                                   

                                  
 
                                                                                  






                              
 


                   
                                                                               

                 
                     


                                                   
                                                                                     
                                                              

                                                              
                                      


                         
                                               


                        
            
                                                  



                   
                                                         
 
                      

                
                           


                          

                                                    

 
                                                        
 
                               

                         
                       
                 
                                 
                                                                








                                                                    
                                                       







                                                                                                       

                                                                                                                                   


                              
                 
 
                          


                                    

                                                                                                                                                                                                                                                             





                                                                     
                                                       



                              
                          
                       

                                                                                                                                                                                                                                                                              


                                                     
                                        







                                                                             
                          
                       

                                                                                                                                                                                                                             


                                                     
                                        








                                                         


         




                                                                                                                                
 
                 
                          


                           
 





                                                              
                                                                         
 

                                
                                   


                                 
                                                  
 
                                                     
                         
                                                                   

                                                                                                       
 

                                                                                                                         

                                 

                                                   

                                                
                                                                                                             

                                                      

                                                 
                                                                            

                                                                                                                                   
                                                    

                                                                                                                              

                                                      

                                                 

                                                                                                                  
                                                      




                                                                                                    

                                 

                                                                                                                        


                                                            


                                                                    
                                                                                        

                                                         
                                                               
                                 
                                                                                                                              
                                                               



                         
                                  

                                  
                                     





                                                     
                                
 
                                   

                                                               
                                          

 
    
 




                         
 
                                                                                             
 
                  
         






















                                              


         


                                                                            
                                                   
 
                                                       

                   

                           

                              
 
                                
                          
                                                    
                         
                                                       

                                                             

                                                                                                                    
                         
                              
 
                                
                          
                                                    
                         
                                                       

                                                              

                                                                                                                     
                         
                              
 
                                

                                       
                                                  

                            
                                               
                                                         
                                 

                                                                                                                                  
                                 
                                                         
                                 

                                                                                                                                   

                                 

                                                       
                         
                                    
                              
 

                                                                                   
                              


         
                                                   
 
                                                       








                                                
                                     





                                                                                   

 
                                                      

                           
                              




                                                  
                              
 

                                

 
                                                   



                                             
                                  

                                    
                                      



                                    
                                                

                                                
                                      



                                                    
                                                        
                                                                     
                                                                 
                                    
                                                                                                
                                                        
                                                                   
 
                                                            




                                                                                         


                                                               
                                                                         
 
                                                                                
                                                
                                                                  


                                                                                        
 
                                                         
                                                 
                                                             



                                                             
                                                               
                                 
 

                                      
 


                                                              

                                                            
                                                                                   
                                 

                                                                
                                 
                                      
 
                               



                                                                   
                                                           
                                 
                                                                                                                   
                                                                                                                                                    
                                                                           


                                                         
                                                                      

                                                                        
                                         
                                 
                                      
 
                               
                                                   

                                                    
 
                               

                                                                                              
                                                                                                 


                                                                                
                                                     
                                                            

                                                
                                                                                
                                                         
                                         

                                                                                   
 

                                                      
 
                                                             
                                 
                                                      
                                 

                                                           
                                                               
                                 
                                                              
                                      
 
                               


                                                                           
                                      
 


                                                     

                                                                           
                                      
 


                                                     

                                                                           
                                      
 


                                                    

                                                                           
                                      
 
                               
                                                                   








                                                      

                                   



                                      

                                                             

                                      
                                  

                                                  

                                      
                                  
                                                     
                                                                                         

                                      
                                  
                                              
                                                                    
                                                                     

                                      


                                                
                                  

                                                       


                                      

                                                        
                                      



                                                                                           

                 
 
                              

 
                                                 

                                                                      

                                                                              

 
                                      
 


                       
                       


                                                   
                                                             

                              
                       
                       


                                                             
                                              
                                                             


                              

                                            


                              





                                                                      
                                            
                                            


                              
                                                                                                                                      




                              
                                    
 
                        
 

                       
                                  
                       

                                                                     
                                            

                                                                             
 
                                                                                   
                              
                 
 



                                                                                   

                                                                                                                  


                              
                       
                                                         
                                       

                                      
                                         


                                                
                                                                                                             
                                              
                                       
                                                                  
                                                                             


                                              
                                                                  
                                              

                              
 
                        
                                                                                                                          

                              
 
                   

 
 

                                                                            
                                                   







                                                   



                                                   
 



                                

                          
                               
                          
                                   

                          
 


                                 



                                 
                                                
 

                              












                                    
                                       

         



                                                  
 



                                    






                                    
                           

 



                                               


                                                                                         






                                                                         




                                       
                                             






                                
                                        
                                 
                                              
                    
                                                                         





                                                                                                      
                                        
                                 
                                         
                    
                                                                         
         

 
                                          


              


















                                         
                                        


                                


































                                                            


         




                                                   
 
              
 


                                
                                                                                       








                                                     
 
                                                                            
                                                            
 
                      
                                 
 

                                                            
                                                           
                                    
                                



                                              

                                                                     

         
                                                                               
                          

                                                  
 
                                    
                              
 
                         



                                                                       
                                                                         



                                                                                          


                                                                                         
 

 
                                                                             
 
                                                                                                               




                                                       
 
 
// license:BSD-3-Clause
// copyright-holders:R. Belmont, Olivier Galibert, hap
/*

   YMF278B  FM + Wave table Synthesizer (OPL4)

   Timer and PCM YMF278B.  The FM will be shared with the ymf262, eventually.

   This chip roughly splits the difference between the Sega 315-5560 MultiPCM
   (Multi32, Model 1/2) and YMF 292-F SCSP (later Model 2, STV, Saturn, Model 3).

   Features as listed in LSI-4MF2782 data sheet:
    FM Synthesis (same as YMF262)
     1. Sound generation mode
         Two-operater mode
          Generates eighteen voices or fifteen voices plus five rhythm sounds simultaneously
         Four-operator mode
          Generates six voices in four-operator mode plus six voices in two-operator mode simultaneously,
          or generates six voices in four-operator mode plus three voices in two-operator mode plus five
          rhythm sounds simultaneously
     2. Eight selectable waveforms
     3. Stereo output
    Wave Table Synthesis
     1. Generates twenty-four voices simultaneously
     2. 44.1kHz sampling rate for output sound data
     3. Selectable from 8-bit, 12-bit and 16-bit word lengths for wave data
     4. Stereo output (16-stage panpot for each voice)
    Wave Data
     1. Accepts 32M bit external memory at maximum
     2. Up to 512 wave tables
     3. External ROM or SRAM can be connected. With SRAM connected, the CPU can download wave data
     4. Outputs chip select signals for 1Mbit, 4Mbit, 8Mbit or 16Mbit memory
     5. Can be directly connected to the Yamaha YRW801 (Wave data ROM)
        Features of YRW801 as listed in LSI 4RW801A2
          Built-in wave data of tones which comply with GM system Level 1
           Melody tone ....... 128 tones
           Percussion tone ...  47 tones
          16Mbit capacity (2,097,152word x 8)

   By R. Belmont and O. Galibert.


   TODO:
   - accurate timing of envelopes
   - LFO (vibrato, tremolo)
   - integrate YMF262 mixing (used by Fuuki games, not used by Psikyo and Metro games)
*/

#include "emu.h"
#include "ymf278b.h"
#include "ymf262.h"

#define VERBOSE 0
#define LOG(x) do { if (VERBOSE) logerror x; } while (0)


/**************************************************************************/

int ymf278b_device::compute_rate(YMF278BSlot *slot, int val)
{
	int res, oct;

	if(val == 0)
		return 0;
	if(val == 15)
		return 63;
	if(slot->RC != 15)
	{
		oct = slot->octave;
		if (oct & 8)
			oct |= -8;

		res = (oct+slot->RC)*2 + (slot->F_NUMBER & 0x200 ? 1 : 0) + val*4;
	}
	else
		res = val * 4;
	if(res < 0)
		res = 0;
	else if(res > 63)
		res = 63;

	return res;
}

uint32_t ymf278b_device::compute_decay_env_vol_step(YMF278BSlot *slot, int val)
{
	int rate;
	uint32_t res;

	// rate override with damping/pseudo reverb
	if (slot->DAMP)
		rate = 56; // approximate, datasheet says it's slightly curved though
	else if (slot->preverb && slot->env_vol > ((6*8)<<23))
	{
		// pseudo reverb starts at -18dB (6 in voltab)
		slot->env_preverb = 1;
		rate = 5;
	}
	else
		rate = compute_rate(slot, val);

	if (rate < 4)
		res = 0;
	else
		res = (256U<<23) / m_lut_dr[rate];

	return res;
}

void ymf278b_device::compute_freq_step(YMF278BSlot *slot)
{
	uint32_t step;
	int oct;

	oct = slot->octave;
	if(oct & 8)
		oct |= -8;

	step = (slot->F_NUMBER | 1024) << (oct + 8);
	slot->step = step >> 3;
}

void ymf278b_device::compute_envelope(YMF278BSlot *slot)
{
	switch (slot->env_step)
	{
		// Attack
		case 0:
		{
			// Attack
			int rate = compute_rate(slot, slot->AR);
			slot->env_vol = 256U<<23;
			slot->env_vol_lim = (256U<<23) - 1;

			if (rate==63)
			{
				// immediate
				LOG(("YMF278B: Attack skipped - "));
				slot->env_vol = 0;
				slot->env_step++;
				compute_envelope(slot);
			}
			else if (rate<4)
			{
				slot->env_vol_step = 0;
			}
			else
			{
				// NOTE: attack rate is linear here, but datasheet shows a smooth curve
				LOG(("YMF278B: Attack, val = %d, rate = %d, delay = %g\n", slot->AR, rate, m_lut_ar[rate]*1000.0));
				slot->env_vol_step = ~((256U<<23) / m_lut_ar[rate]);
			}

			break;
		}

		// Decay 1
		case 1:
			if(slot->DL)
			{
				LOG(("YMF278B: Decay step 1, dl=%d, val = %d rate = %d, delay = %g, PRVB = %d, DAMP = %d\n", slot->DL, slot->D1R, compute_rate(slot, slot->D1R), m_lut_dr[compute_rate(slot, slot->D1R)]*1000.0, slot->preverb, slot->DAMP));
				slot->env_vol_step = compute_decay_env_vol_step(slot, slot->D1R);
				slot->env_vol_lim = (slot->DL*8)<<23;
			}
			else
			{
				LOG(("YMF278B: Decay 1 skipped - "));
				slot->env_step++;
				compute_envelope(slot);
			}

			break;

		// Decay 2
		case 2:
			LOG(("YMF278B: Decay step 2, val = %d, rate = %d, delay = %g, , PRVB = %d, DAMP = %d, current vol = %d\n", slot->D2R, compute_rate(slot, slot->D2R), m_lut_dr[compute_rate(slot, slot->D2R)]*1000.0, slot->preverb, slot->DAMP, slot->env_vol >> 23));
			slot->env_vol_step = compute_decay_env_vol_step(slot, slot->D2R);
			slot->env_vol_lim = 256U<<23;
			break;

		// Decay 2 reached -96dB
		case 3:
			LOG(("YMF278B: Voice cleared because of decay 2\n"));
			slot->env_vol = 256U<<23;
			slot->env_vol_step = 0;
			slot->env_vol_lim = 0;
			slot->active = 0;
			break;

		// Release
		case 4:
			LOG(("YMF278B: Release, val = %d, rate = %d, delay = %g, PRVB = %d, DAMP = %d\n", slot->RR, compute_rate(slot, slot->RR), m_lut_dr[compute_rate(slot, slot->RR)]*1000.0, slot->preverb, slot->DAMP));
			slot->env_vol_step = compute_decay_env_vol_step(slot, slot->RR);
			slot->env_vol_lim = 256U<<23;
			break;

		// Release reached -96dB
		case 5:
			LOG(("YMF278B: Release ends\n"));
			slot->env_vol = 256U<<23;
			slot->env_vol_step = 0;
			slot->env_vol_lim = 0;
			slot->active = 0;
			break;

		default: break;
	}
}

//-------------------------------------------------
//  sound_stream_update - handle a stream update
//-------------------------------------------------

void ymf278b_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
	int i, j;
	YMF278BSlot *slot;
	int16_t sample = 0;
	int32_t *mixp;
	int32_t vl, vr;

	if (&stream == m_stream_ymf262)
	{
		ymf262_update_one(m_ymf262, outputs, samples);
		return;
	}

	memset(m_mix_buffer.get(), 0, sizeof(m_mix_buffer[0])*samples*2);

	for (i = 0; i < 24; i++)
	{
		slot = &m_slots[i];

		if (slot->active)
		{
			mixp = m_mix_buffer.get();

			for (j = 0; j < samples; j++)
			{
				if (slot->stepptr >= slot->endaddr)
				{
					slot->stepptr = slot->stepptr - slot->endaddr + slot->loopaddr;

					// NOTE: loop overflow is still possible here if (slot->stepptr >= slot->endaddr)
					// This glitch may be (ab)used to your advantage to create pseudorandom noise.
				}

				switch (slot->bits)
				{
					// 8 bit
					case 0:
						sample = read_byte(slot->startaddr + (slot->stepptr>>16))<<8;
						break;

					// 12 bit
					case 1:
						if (slot->stepptr & 0x10000)
							sample = read_byte(slot->startaddr + (slot->stepptr>>17)*3+2)<<8 |
								(read_byte(slot->startaddr + (slot->stepptr>>17)*3+1) << 4 & 0xf0);
						else
							sample = read_byte(slot->startaddr + (slot->stepptr>>17)*3)<<8 |
								(read_byte(slot->startaddr + (slot->stepptr>>17)*3+1) & 0xf0);
						break;

					// 16 bit
					case 2:
						sample = read_byte(slot->startaddr + ((slot->stepptr>>16)*2))<<8 |
							read_byte(slot->startaddr + ((slot->stepptr>>16)*2)+1);
						break;

					// ?? bit, effect is unknown, datasheet says it's prohibited
					case 3:
						sample = 0;
						break;
				}

				*mixp++ += (sample * m_volume[slot->TL+m_pan_left [slot->pan]+(slot->env_vol>>23)])>>17;
				*mixp++ += (sample * m_volume[slot->TL+m_pan_right[slot->pan]+(slot->env_vol>>23)])>>17;

				// update frequency
				slot->stepptr += slot->step;

				// update envelope
				slot->env_vol += slot->env_vol_step;
				if (((int32_t)(slot->env_vol - slot->env_vol_lim)) >= 0)
				{
					slot->env_step++;
					compute_envelope(slot);
				}
				else if (slot->preverb && !slot->env_preverb && slot->env_step && slot->env_vol > ((6*8)<<23))
					compute_envelope(slot);
			}
		}
	}

	mixp = m_mix_buffer.get();
	vl = m_mix_level[m_pcm_l];
	vr = m_mix_level[m_pcm_r];
	for (i = 0; i < samples; i++)
	{
		outputs[0][i] = (*mixp++ * vl) >> 16;
		outputs[1][i] = (*mixp++ * vr) >> 16;
	}
}

void ymf278b_device::irq_check()
{
	int prev_line = m_irq_line;
	m_irq_line = m_current_irq ? 1 : 0;
	if (m_irq_line != prev_line && !m_irq_handler.isnull())
		m_irq_handler(m_irq_line);
}

enum
{
	TIMER_A = 0,
	TIMER_B,
	TIMER_BUSY_CLEAR,
	TIMER_LD_CLEAR
};

void ymf278b_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
	switch(id)
	{
	case TIMER_A:
		if(!(m_enable & 0x40))
		{
			m_current_irq |= 0x40;
			irq_check();
		}
		break;

	case TIMER_B:
		if(!(m_enable & 0x20))
		{
			m_current_irq |= 0x20;
			irq_check();
		}
		break;

	case TIMER_BUSY_CLEAR:
		m_status_busy = 0;
		break;

	case TIMER_LD_CLEAR:
		m_status_ld = 0;
		break;
	}
}


/**************************************************************************/

void ymf278b_device::A_w(uint8_t reg, uint8_t data)
{
	// FM register array 0 (compatible with YMF262)
	switch(reg)
	{
		// LSI TEST
		case 0x00:
		case 0x01:
			break;

		// timer a count
		case 0x02:
			if (data != m_timer_a_count)
			{
				m_timer_a_count = data;

				// change period, ~80.8us * t
				if (m_enable & 1)
					m_timer_a->adjust(m_timer_a->remaining(), 0, m_timer_base * (256-data) * 4);
			}
			break;

		// timer b count
		case 0x03:
			if (data != m_timer_b_count)
			{
				m_timer_b_count = data;

				// change period, ~323.1us * t
				if (m_enable & 2)
					m_timer_b->adjust(m_timer_b->remaining(), 0, m_timer_base * (256-data) * 16);
			}
			break;

		// timer control
		case 0x04:
			if(data & 0x80)
				m_current_irq = 0;
			else
			{
				// reset timers
				if((m_enable ^ data) & 1)
				{
					attotime period = (data & 1) ? m_timer_base * (256-m_timer_a_count) * 4 : attotime::never;
					m_timer_a->adjust(period, 0, period);
				}
				if((m_enable ^ data) & 2)
				{
					attotime period = (data & 2) ? m_timer_base * (256-m_timer_b_count) * 16 : attotime::never;
					m_timer_b->adjust(period, 0, period);
				}

				m_enable = data;
				m_current_irq &= ~data;
			}
			irq_check();
			break;

		default:
			logerror("YMF278B:  Port A write %02x, %02x\n", reg, data);
			break;
	}
}

void ymf278b_device::B_w(uint8_t reg, uint8_t data)
{
	// FM register array 1 (compatible with YMF262)
	switch(reg)
	{
		// LSI TEST
		case 0x00:
		case 0x01:
			break;

		// expansion register (NEW2/NEW)
		case 0x05:
			m_exp = data;
			break;

		default:
			logerror("YMF278B:  Port B write %02x, %02x\n", reg, data);
			break;
	}
}

void ymf278b_device::retrigger_note(YMF278BSlot *slot)
{
	// activate channel
	if (slot->octave != 8)
		slot->active = 1;

	// reset sample pos and go to attack stage
	slot->stepptr = 0;
	slot->env_step = 0;
	slot->env_preverb = 0;

	compute_freq_step(slot);
	compute_envelope(slot);
}

void ymf278b_device::C_w(uint8_t reg, uint8_t data)
{
	// Handle slot registers specifically
	if (reg >= 0x08 && reg <= 0xf7)
	{
		YMF278BSlot *slot;
		int snum;
		snum = (reg-8) % 24;
		slot = &m_slots[snum];
		switch((reg-8) / 24)
		{
			case 0:
			{
				attotime period;
				uint32_t offset;
				uint8_t p[12];
				int i;

				slot->wave &= 0x100;
				slot->wave |= data;

				// load wavetable header
				if(slot->wave < 384 || !m_wavetblhdr)
					offset = slot->wave * 12;
				else
					offset = m_wavetblhdr*0x80000 + (slot->wave - 384) * 12;
				for (i = 0; i < 12; i++)
					p[i] = read_byte(offset+i);

				slot->bits = (p[0]&0xc0)>>6;
				slot->startaddr = (p[2] | (p[1]<<8) | ((p[0]&0x3f)<<16));
				slot->loopaddr = (p[4]<<16) | (p[3]<<24);
				slot->endaddr = (p[6]<<16) | (p[5]<<24);
				slot->endaddr -= 0x00010000U;
				slot->endaddr ^= 0xffff0000U;

				// copy internal registers data
				for (i = 7; i < 12; i++)
					C_w(8 + snum + (i-2) * 24, p[i]);

				// status register LD bit is on for approx 300us
				m_status_ld = 1;
				period = attotime::from_usec(300);
				if (m_clock != YMF278B_STD_CLOCK)
					period = (period * m_clock) / YMF278B_STD_CLOCK;
				m_timer_ld->adjust(period);

				// retrigger if key is on
				if (slot->KEY_ON)
					retrigger_note(slot);
				else if (slot->active)
				{
					// deactivate channel
					slot->env_step = 5;
					compute_envelope(slot);
				}

				break;
			}

			case 1:
				slot->wave &= 0xff;
				slot->wave |= ((data&0x1)<<8);
				slot->F_NUMBER &= 0x380;
				slot->F_NUMBER |= (data>>1);
				if (slot->active && (data ^ m_pcmregs[reg]) & 0xfe)
				{
					compute_freq_step(slot);
					compute_envelope(slot);
				}
				break;

			case 2:
				slot->F_NUMBER &= 0x07f;
				slot->F_NUMBER |= ((data&0x07)<<7);
				slot->preverb = (data&0x8)>>3;
				slot->octave = (data&0xf0)>>4;
				if (data != m_pcmregs[reg])
				{
					// channel goes off if octave is set to -8 (datasheet says it's prohibited)
					// (it is ok if this activates the channel while it was off: compute_envelope will reset it again if needed)
					slot->active = (slot->octave != 8);

					if (slot->active)
					{
						slot->env_preverb = 0;
						compute_freq_step(slot);
						compute_envelope(slot);
					}
				}
				break;

			case 3:
				slot->TL = data>>1;
				slot->LD = data&0x1;
				break;

			case 4:
				slot->CH = (data&0x10)>>4;
				// CH bit note: output to DO1 pin (1) or DO2 pin (0), this may
				// silence the channel depending on how it's wired up on the PCB.
				// For now, it's always enabled.
				// (bit 5 (LFO reset) is also not hooked up yet)

				slot->pan = data&0xf;
				slot->DAMP = (data&0x40)>>6;
				if (data & 0x80)
				{
					// don't retrigger if key was already on
					if (slot->KEY_ON)
					{
						if ((data ^ m_pcmregs[reg]) & 0x40)
							compute_envelope(slot);

						break;
					}

					retrigger_note(slot);
				}
				else if (slot->active)
				{
					// release
					slot->env_step = 4;
					compute_envelope(slot);
				}
				slot->KEY_ON = (data&0x80)>>7;
				break;

			case 5:
				// LFO and vibrato level, not hooked up yet
				slot->LFO = (data>>3)&0x7;
				slot->VIB = data&0x7;
				break;

			case 6:
				slot->AR = data>>4;
				slot->D1R = data&0xf;
				if (slot->active && data != m_pcmregs[reg])
					compute_envelope(slot);
				break;

			case 7:
				slot->DL = data>>4;
				slot->D2R = data&0xf;
				if (slot->active && data != m_pcmregs[reg])
					compute_envelope(slot);
				break;

			case 8:
				slot->RC = data>>4;
				slot->RR = data&0xf;
				if (slot->active && data != m_pcmregs[reg])
					compute_envelope(slot);
				break;

			case 9:
				// tremolo level, not hooked up yet
				slot->AM = data & 0x7;
				break;
		}
	}
	else
	{
		// All non-slot registers
		switch (reg)
		{
			// LSI TEST
			case 0x00:
			case 0x01:
				break;

			case 0x02:
				m_wavetblhdr = (data>>2)&0x7;
				m_memmode = data&3;
				break;

			case 0x03:
				data &= 0x3f; // !
				break;
			case 0x04:
				break;
			case 0x05:
				// set memory address
				m_memadr = m_pcmregs[3] << 16 | m_pcmregs[4] << 8 | data;
				break;

			case 0x06:
				// memory data
				space(0).write_byte(m_memadr, data);
				m_memadr = (m_memadr + 1) & 0x3fffff;
				break;

			case 0x07:
				break; // unused

			case 0xf8:
				m_fm_l = data & 0x7;
				m_fm_r = (data>>3)&0x7;
				break;

			case 0xf9:
				m_pcm_l = data & 0x7;
				m_pcm_r = (data>>3)&0x7;
				break;

			default:
				logerror("YMF278B:  Port C write %02x, %02x\n", reg, data);
				break;
		}
	}

	m_pcmregs[reg] = data;
}

void ymf278b_device::timer_busy_start(int is_pcm)
{
	// status register BUSY bit is on for 56(FM) or 88(PCM) cycles
	m_status_busy = 1;
	m_timer_busy->adjust(attotime::from_hz(m_clock / (is_pcm ? 88 : 56)));
}

WRITE8_MEMBER( ymf278b_device::write )
{
	switch (offset)
	{
		case 0:
		case 2:
			timer_busy_start(0);
			m_port_AB = data;
			m_lastport = offset>>1 & 1;
			ymf262_write(m_ymf262, offset, data);
			break;

		case 1:
		case 3:
			timer_busy_start(0);
			if (m_lastport) B_w(m_port_AB, data);
			else A_w(m_port_AB, data);
			m_last_fm_data = data;
			ymf262_write(m_ymf262, offset, data);
			break;

		case 4:
			timer_busy_start(1);
			m_port_C = data;
			break;

		case 5:
			// PCM regs are only accessible if NEW2 is set
			if (~m_exp & 2)
				break;

			m_stream->update();

			timer_busy_start(1);
			C_w(m_port_C, data);
			break;

		default:
			logerror("%s: unexpected write at offset %X to ymf278b = %02X\n", machine().describe_context(), offset, data);
			break;
	}
}


READ8_MEMBER( ymf278b_device::read )
{
	uint8_t ret = 0;

	switch (offset)
	{
		// status register
		case 0:
		{
			// bits 0 and 1 are only valid if NEW2 is set
			uint8_t newbits = 0;
			if (m_exp & 2)
				newbits = (m_status_ld << 1) | m_status_busy;

			ret = newbits | m_current_irq | (m_irq_line ? 0x80 : 0x00);
			break;
		}

		// FM regs can be read too (on contrary to what the datasheet says)
		case 1:
		case 3:
			// but they're not implemented here yet
			// This may be incorrect, but it makes the mbwave moonsound detection in msx drivers pass.
			ret = m_last_fm_data;
			break;

		// PCM regs
		case 5:
			// only accessible if NEW2 is set
			if (~m_exp & 2)
				break;

			switch (m_port_C)
			{
				// special cases
				case 2:
					ret = (m_pcmregs[m_port_C] & 0x1f) | 0x20; // device ID in upper bits
					break;
				case 6:
					ret = read_byte(m_memadr);
					m_memadr = (m_memadr + 1) & 0x3fffff;
					break;

				default:
					ret = m_pcmregs[m_port_C];
					break;
			}
			break;

		default:
			logerror("%s: unexpected read at offset %X from ymf278b\n", machine().describe_context(), offset);
			break;
	}

	return ret;
}


/**************************************************************************/

//-------------------------------------------------
//  device_post_load - device-specific post load
//-------------------------------------------------
void ymf278b_device::device_post_load()
{
	ymf262_post_load(m_ymf262);
}

//-------------------------------------------------
//  device_reset - device-specific reset
//-------------------------------------------------

void ymf278b_device::device_reset()
{
	int i;

	// clear registers
	for (i = 0; i <= 4; i++)
		A_w(i, 0);
	B_w(5, 0);
	for (i = 0; i < 8; i++)
		C_w(i, 0);
	for (i = 0xff; i >= 8; i--)
		C_w(i, 0);
	C_w(0xf8, 0x1b);

	m_port_AB = m_port_C = 0;
	m_lastport = 0;
	m_memadr = 0;

	// init/silence channels
	for (i = 0; i < 24 ; i++)
	{
		YMF278BSlot *slot = &m_slots[i];

		slot->LFO = 0;
		slot->VIB = 0;
		slot->AR = 0;
		slot->D1R = 0;
		slot->DL = 0;
		slot->D2R = 0;
		slot->RC = 0;
		slot->RR = 0;
		slot->AM = 0;

		slot->startaddr = 0;
		slot->loopaddr = 0;
		slot->endaddr = 0;

		slot->env_step = 5;
		compute_envelope(slot);
	}

	m_timer_a->reset();
	m_timer_b->reset();
	m_timer_busy->reset();  m_status_busy = 0;
	m_timer_ld->reset();    m_status_ld = 0;

	m_irq_line = 0;
	m_current_irq = 0;
	if (!m_irq_handler.isnull())
		m_irq_handler(0);

	ymf262_reset_chip(m_ymf262);
}

void ymf278b_device::device_stop()
{
	ymf262_shutdown(m_ymf262);
	m_ymf262 = nullptr;
}

void ymf278b_device::device_clock_changed()
{
	m_stream->set_sample_rate(clock()/768);

	m_clock = clock();
	m_timer_base = m_clock ? attotime::from_hz(m_clock) * (19 * 36) : attotime::zero;

	// YMF262 related

	int ymf262_clock = clock() / (19/8.0);
	ymf262_clock_changed(m_ymf262, ymf262_clock, ymf262_clock / 288);
	m_stream_ymf262->set_sample_rate(ymf262_clock / 288);
}

void ymf278b_device::rom_bank_updated()
{
	m_stream->update();
}

void ymf278b_device::precompute_rate_tables()
{
	int i;

	// decay rate
	for (i = 0; i < 64; i++)
	{
		if (i <= 3)
			m_lut_dr[i] = 0;
		else if (i >= 60)
			m_lut_dr[i] = 15 << 4;
		else
			m_lut_dr[i] = (15 << (21 - i / 4)) / (4 + i % 4);
	}

	// attack rate (manual shows curve instead of linear though, so this is not entirely accurate)
	for (i = 0; i < 64; i++)
	{
		if (i <= 3 || i == 63)
			m_lut_ar[i] = 0;
		else if (i >= 60)
			m_lut_ar[i] = 17;
		else
			m_lut_ar[i] = (67 << (15 - i / 4)) / (4 + i % 4);
	}
}

void ymf278b_device::register_save_state()
{
	int i;

	save_item(NAME(m_pcmregs));
	save_item(NAME(m_wavetblhdr));
	save_item(NAME(m_memmode));
	save_item(NAME(m_memadr));
	save_item(NAME(m_status_busy));
	save_item(NAME(m_status_ld));
	save_item(NAME(m_exp));
	save_item(NAME(m_fm_l));
	save_item(NAME(m_fm_r));
	save_item(NAME(m_pcm_l));
	save_item(NAME(m_pcm_r));
	save_item(NAME(m_timer_a_count));
	save_item(NAME(m_timer_b_count));
	save_item(NAME(m_enable));
	save_item(NAME(m_current_irq));
	save_item(NAME(m_irq_line));
	save_item(NAME(m_port_AB));
	save_item(NAME(m_port_C));
	save_item(NAME(m_lastport));
	save_item(NAME(m_last_fm_data));

	for (i = 0; i < 24; ++i)
	{
		save_item(NAME(m_slots[i].wave), i);
		save_item(NAME(m_slots[i].F_NUMBER), i);
		save_item(NAME(m_slots[i].octave), i);
		save_item(NAME(m_slots[i].preverb), i);
		save_item(NAME(m_slots[i].DAMP), i);
		save_item(NAME(m_slots[i].CH), i);
		save_item(NAME(m_slots[i].LD), i);
		save_item(NAME(m_slots[i].TL), i);
		save_item(NAME(m_slots[i].pan), i);
		save_item(NAME(m_slots[i].LFO), i);
		save_item(NAME(m_slots[i].VIB), i);
		save_item(NAME(m_slots[i].AM), i);

		save_item(NAME(m_slots[i].AR), i);
		save_item(NAME(m_slots[i].D1R), i);
		save_item(NAME(m_slots[i].DL), i);
		save_item(NAME(m_slots[i].D2R), i);
		save_item(NAME(m_slots[i].RC), i);
		save_item(NAME(m_slots[i].RR), i);

		save_item(NAME(m_slots[i].step), i);
		save_item(NAME(m_slots[i].stepptr), i);

		save_item(NAME(m_slots[i].active), i);
		save_item(NAME(m_slots[i].KEY_ON), i);
		save_item(NAME(m_slots[i].bits), i);
		save_item(NAME(m_slots[i].startaddr), i);
		save_item(NAME(m_slots[i].loopaddr), i);
		save_item(NAME(m_slots[i].endaddr), i);

		save_item(NAME(m_slots[i].env_step), i);
		save_item(NAME(m_slots[i].env_vol), i);
		save_item(NAME(m_slots[i].env_vol_step), i);
		save_item(NAME(m_slots[i].env_vol_lim), i);
		save_item(NAME(m_slots[i].env_preverb), i);
	}
}

//-------------------------------------------------
//  device_start - device-specific startup
//-------------------------------------------------

void ymf278b_device::device_start()
{
	int i;

	m_clock = clock();
	m_irq_handler.resolve();

	m_timer_base = m_clock ? attotime::from_hz(m_clock) * (19*36) : attotime::zero;
	m_timer_a = timer_alloc(TIMER_A);
	m_timer_b = timer_alloc(TIMER_B);
	m_timer_busy = timer_alloc(TIMER_BUSY_CLEAR);
	m_timer_ld = timer_alloc(TIMER_LD_CLEAR);

	for (i = 0; i < 24; i++)
	{
		m_slots[i].num = i;
	}

	m_stream = machine().sound().stream_alloc(*this, 0, 2, clock()/768);
	m_mix_buffer = std::make_unique<int32_t[]>(44100*2);

	// rate tables
	precompute_rate_tables();

	// Volume table, 1 = -0.375dB, 8 = -3dB, 256 = -96dB
	for(i = 0; i < 256; i++)
		m_volume[i] = 65536*pow(2.0, (-0.375/6)*i);
	for(i = 256; i < 256*4; i++)
		m_volume[i] = 0;

	// Pan values, units are -3dB, i.e. 8.
	for(i = 0; i < 16; i++)
	{
		m_pan_left[i] = i < 7 ? i*8 : i < 9 ? 256 : 0;
		m_pan_right[i] = i < 8 ? 0 : i < 10 ? 256 : (16-i)*8;
	}

	// Mixing levels, units are -3dB, and add some margin to avoid clipping
	for(i=0; i<7; i++)
		m_mix_level[i] = m_volume[8*i+13];
	m_mix_level[7] = 0;

	// Register state for saving
	register_save_state();

	// YMF262 related

	/* stream system initialize */
	int ymf262_clock = clock() / (19/8.0);
	m_ymf262 = ymf262_init(this, ymf262_clock, ymf262_clock / 288);
	assert_always(m_ymf262 != nullptr, "Error creating YMF262 chip");

	m_stream_ymf262 = machine().sound().stream_alloc(*this, 0, 4, ymf262_clock / 288);

	/* YMF262 setup */
	ymf262_set_timer_handler (m_ymf262, ymf278b_device::static_timer_handler, this);
	ymf262_set_irq_handler   (m_ymf262, ymf278b_device::static_irq_handler, this);
	ymf262_set_update_handler(m_ymf262, ymf278b_device::static_update_request, this);
}


DEFINE_DEVICE_TYPE(YMF278B, ymf278b_device, "ymf278b", "Yamaha YMF278B OPL4")

ymf278b_device::ymf278b_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: device_t(mconfig, YMF278B, tag, owner, clock)
	, device_sound_interface(mconfig, *this)
	, device_rom_interface(mconfig, *this, 22)
	, m_irq_handler(*this)
	, m_last_fm_data(0)
{
}