summaryrefslogblamecommitdiffstatshomepage
path: root/src/emu/sound/okim6376.c
blob: 7cb547aa843a25470de125c2548ef9b91138e504 (plain) (tree)
1
2
3
4
5
6
7
8







                                                                                               



                                                                                        


                                                                                                
                



















                                                                                 

                                              


                                                 

                                                                                                        


                                                                               







                                                                                           








                                                                                      


                                                                                     
 


                           





                               
                                                       

                               
                                           
                                                                                 


 







































































                                                                                                



                                       



















                                                                                                
                                                                                                      










                                                                     



















                                                                       
                                                                                   
                                                                               



                                                                                                                              
 


                                  



                                           
                                     
















                                                                                                
                                                       

































                                                                                                    
                                                                                            
 






                                                           

 
                                                                                

              
                                             
         
                                                                      


                                                          

 



                                                                   
                                                              

















































                                                                                                                                                                    
 











                                                                                   


                                                                                               
                                                                               


                                                                                                
                               
 
                                                      


                          

                         


                              

                                              





                             
                                                     
                                                                                                                             









                                                          

 


                                                            
 


                                                                


                                                                                               
                                                                              


                                                                                                
                               
 
                                                      

              
                               












                                                                                                
                                  
 
                                                      




                                                                         
                               












                                                           



                                                      
 


                                                           
 





                                                     
 




                                                      
 
                                           
 
                                                      
 
                               
         










                                                       
 





                                                         
 
 


                                                           
 

                                                      
                              
         
 

                                    
 


                                                                                                                                                        

                         

                                                        
                                 
                                                                                                     
                                 
 
                                                                              

                         


                                                                   
 




                                                                         
         
 
 
                                                                                               
 
                                                                             
 
                                                                                                
 


                                                                                                   
 



                                                                                                             



 



                                                                            
                           



                                                                                               
                                                                                                                                                      

                                                                                                      


                                                                                                                                                                              

                                                                                                
                                                                                                                                                                      

                                                                                                                                                                      
                                                                                                                                                              
                                                                                                                                                      


         

                                               
/**********************************************************************************************
 *
 *   OKI MSM6376 ADPCM
 *   by Mirko Buffoni
 *
 *   TODO:
 *     add BEEP tone generator
 *     add 2ch handling (for echoing and continuous speech)
 *     add echo
 *     Something is definitely not right with the rates and divisions here - the maximum
 *     oscillation frequency should only be 256KHz, yet this only sounds decent at 3MHz
 *     add proper NAR handling - this varies with clock rate
 **********************************************************************************************/


#include "emu.h"
#include "okim6376.h"

#define MAX_SAMPLE_CHUNK	10000
#define MAX_WORDS           111


/* struct describing a single playing ADPCM voice */
struct ADPCMVoice
{
	UINT8 playing;			/* 1 if we are actively playing */

	UINT32 base_offset;		/* pointer to the base memory location */
	UINT32 sample;			/* current sample number */
	UINT32 count;			/* total samples to play */

	UINT32 volume;			/* output volume */
	INT32 signal;
	INT32 step;
};

typedef struct _okim6376_state okim6376_state;
struct _okim6376_state
{
	#define OKIM6376_VOICES		2
	struct ADPCMVoice voice[OKIM6376_VOICES];
	INT32 command[OKIM6376_VOICES];
	INT32 latch;			/* Command data is held before transferring to either channel */
	UINT8 *region_base;		/* pointer to the base of the region */
	sound_stream *stream;	/* which stream are we playing on? */
	UINT32 master_clock;	/* master clock frequency */
	UINT8 channel;
	UINT8 nar;				/* Next Address Ready */
	UINT8 busy;
	UINT8 ch2;				/* 2CH pin - enables Channel 2 operation */
	UINT8 st;				/* STart */
	UINT8 st_pulses;		/* Keep track of attenuation */
	UINT8 ch2_update;		/* Pulse shape */
	UINT8 st_update;
};

/* step size index shift table */
static const int index_shift[8] = { -1, -1, -1, -1, 2, 4, 6, 8 };

/* lookup table for the precomputed difference */
static int diff_lookup[49*16];

/* volume lookup table. Upon configuration, the number of ST pulses determine how much
   attenuation to apply to the sound signal. However, this only applies to the second
   channel*/
static const int volume_table[3] =
{
	0x20,	//   0 dB
	0x10,	//  -6.0 dB
	0x08,	// -12.0 dB
};

/* tables computed? */
static int tables_computed = 0;


INLINE okim6376_state *get_safe_token(device_t *device)
{
	assert(device != NULL);
	assert(device->type() == OKIM6376);
	return (okim6376_state *)downcast<legacy_device_base *>(device)->token();
}



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

     compute_tables -- compute the difference tables

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

static void compute_tables(void)
{
	/* nibble to bit map */
	static const int nbl2bit[16][4] =
	{
		{ 1, 0, 0, 0}, { 1, 0, 0, 1}, { 1, 0, 1, 0}, { 1, 0, 1, 1},
		{ 1, 1, 0, 0}, { 1, 1, 0, 1}, { 1, 1, 1, 0}, { 1, 1, 1, 1},
		{-1, 0, 0, 0}, {-1, 0, 0, 1}, {-1, 0, 1, 0}, {-1, 0, 1, 1},
		{-1, 1, 0, 0}, {-1, 1, 0, 1}, {-1, 1, 1, 0}, {-1, 1, 1, 1}
	};

	int step, nib;

	/* loop over all possible steps */
	for (step = 0; step <= 48; step++)
	{
		/* compute the step value */
		int stepval = floor(16.0 * pow(11.0 / 10.0, (double)step));

		/* loop over all nibbles and compute the difference */
		for (nib = 0; nib < 16; nib++)
		{
			diff_lookup[step*16 + nib] = nbl2bit[nib][0] *
				(stepval   * nbl2bit[nib][1] +
				 stepval/2 * nbl2bit[nib][2] +
				 stepval/4 * nbl2bit[nib][3] +
				 stepval/8);
		}
	}

	tables_computed = 1;
}



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

     reset_adpcm -- reset the ADPCM stream

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

static void reset_adpcm(struct ADPCMVoice *voice)
{
	/* make sure we have our tables */
	if (!tables_computed)
		compute_tables();

	/* reset the signal/step */
	voice->signal = -2;
	voice->step = 0;
}



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

     clock_adpcm -- clock the next ADPCM byte

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

static INT16 clock_adpcm(struct ADPCMVoice *voice, UINT8 nibble)
{
	voice->signal += diff_lookup[voice->step * 16 + (nibble & 15)];

	/* clamp to the maximum 12bit */
	if (voice->signal > 2047)
		voice->signal = 2047;
	else if (voice->signal < -2048)
		voice->signal = -2048;

	/* adjust the step size and clamp */
	voice->step += index_shift[nibble & 7];
	if (voice->step > 48)
		voice->step = 48;
	else if (voice->step < 0)
		voice->step = 0;

	/* return the signal */
	return voice->signal;
}



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

     generate_adpcm -- general ADPCM decoding routine

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

static void generate_adpcm(okim6376_state *chip, struct ADPCMVoice *voice, INT16 *buffer, int samples)
{
	/* if this voice is active */
	if (voice->playing)
	{
		UINT8 *base = chip->region_base + voice->base_offset;
		int sample = voice->sample;
		int count = voice->count;

		/* loop while we still have samples to generate */
		while (samples)
		{
			int nibble;

			if (count == 0)
			{
				/* get the number of samples to play */
				count = (base[sample / 2] & 0x7f) << 1;

				/* end of voice marker */
				if (count == 0)
				{
					voice->playing = 0;
					break;
				}
				else
				{
					/* step past the count byte */
					sample += 2;
				}
			}

			/* compute the new amplitude and update the current step */
			nibble = base[sample / 2] >> (((sample & 1) << 2) ^ 4);

			/* output to the buffer, scaling by the volume */
			/* signal in range -4096..4095, volume in range 2..16 => signal * volume / 2 in range -32768..32767 */
			*buffer++ = clock_adpcm(voice, nibble) * voice->volume / 2;

			++sample;
			--count;
			--samples;
		}

		/* update the parameters */
		voice->sample = sample;
		voice->count = count;
	}

	/* fill the rest with silence */
	while (samples--)
		*buffer++ = 0;
}



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

     okim6376_update -- update the sound chip so that it is in sync with CPU execution

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

static STREAM_UPDATE( okim6376_update )
{
	okim6376_state *chip = (okim6376_state *)param;
	int i;

	memset(outputs[0], 0, samples * sizeof(*outputs[0]));

	for (i = 0; i < OKIM6376_VOICES; i++)
	{
		struct ADPCMVoice *voice = &chip->voice[i];
		stream_sample_t *buffer = outputs[0];
		INT16 sample_data[MAX_SAMPLE_CHUNK];
		int remaining = samples;

		/* loop while we have samples remaining */
		while (remaining)
		{
			int samples = (remaining > MAX_SAMPLE_CHUNK) ? MAX_SAMPLE_CHUNK : remaining;
			int samp;

			generate_adpcm(chip, voice, sample_data, samples);
			for (samp = 0; samp < samples; samp++)
				*buffer++ += sample_data[samp];

			remaining -= samples;
		}
	}
}



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

     state save support for MAME

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

static void adpcm_state_save_register(struct ADPCMVoice *voice, device_t *device, int index)
{
	device->save_item(NAME(voice->playing), index);
	device->save_item(NAME(voice->sample), index);
	device->save_item(NAME(voice->count), index);
	device->save_item(NAME(voice->signal), index);
	device->save_item(NAME(voice->step), index);
	device->save_item(NAME(voice->volume), index);
	device->save_item(NAME(voice->base_offset), index);
}

static void okim6376_state_save_register(okim6376_state *info, device_t *device)
{
	int j;
	for (j = 0; j < OKIM6376_VOICES; j++)
	{
		adpcm_state_save_register(&info->voice[j], device, j);
	}
		device->save_item(NAME(info->command[0]));
		device->save_item(NAME(info->command[1]));
}

static void oki_process(device_t *device, int channel, int command)
{
	okim6376_state *info = get_safe_token(device);

	/* if a command is pending, process the second half */
	if ((command != -1) && (command != 0)) //process silence separately
	{
		int start;
		unsigned char *base/*, *base_end*/;
		info->nar = 0;//processing
		/* update the stream */
		info->stream->update();

		/* determine which voice(s) (voice is set by the state of 2CH) */
		{
			struct ADPCMVoice *voice = &info->voice[channel];

			/* determine the start position, max address space is 16Mbit */
			base = &info->region_base[info->command[channel] * 4];
			//base_end = &info->region_base[(MAX_WORDS+1) * 4];
			start = ((base[0] << 16) + (base[1] << 8) + base[2]) & 0x1fffff;

				if (start == 0)
				{
					voice->playing = 0;
				}
				else
				{
					/* set up the voice to play this sample */
					if (!voice->playing)
					{
						voice->playing = 1;
						voice->base_offset = start;
						voice->sample = 0;
						voice->count = 0;

						/* also reset the ADPCM parameters */
						reset_adpcm(voice);
						/* FIX: no attenuation for now, handle for channel 2 separately */
						voice->volume = volume_table[0];
					}
					else
					{
						logerror("OKIM6376:'%s' requested to play sample %02x on non-stopped voice\n",device->tag(),info->command[channel]);
					}
				}
				info->nar = 1;
			}
		}
		/* otherwise, see if this is a silence command */
		else
		{
			info->nar = 0;
			/* update the stream, then turn it off */
			info->stream->update();

			if (command ==0)
			{
				int i;
				for (i = 0; i < OKIM6376_VOICES; i++)
				{
					struct ADPCMVoice *voice = &info->voice[i];
					voice->playing = 0;
			}
			info->nar = 1;
		}
	}
}

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

     DEVICE_START( okim6376 ) -- start emulation of an OKIM6376-compatible chip

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

static DEVICE_START( okim6376 )
{
	okim6376_state *info = get_safe_token(device);
	int voice;
	int divisor = 165;

	compute_tables();

	info->command[0] = -1;
	info->command[1] = -1;
	info->latch = 0;
	info->region_base = *device->region();
	info->master_clock = device->clock();
	info->nar = 1;
	info->busy = 1;
	info->st = 1;
	info->st_update = 0;
	info->ch2_update = 0;
	info->st_pulses = 0;
	/* generate the name and create the stream */
	info->stream = device->machine().sound().stream_alloc(*device, 0, 1, device->clock()/divisor, info, okim6376_update);

	/* initialize the voices */
	for (voice = 0; voice < OKIM6376_VOICES; voice++)
	{
		/* initialize the rest of the structure */
		info->voice[voice].volume = 0;
		reset_adpcm(&info->voice[voice]);
	}

	okim6376_state_save_register(info, device);
}

void okim6376_set_frequency(device_t *device, int frequency)
{
	okim6376_state *info = get_safe_token(device);

	info->master_clock = frequency;
	info->stream->set_sample_rate(info->master_clock / 165);
}

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

     DEVICE_RESET( okim6376 ) -- stop emulation of an OKIM6376-compatible chip

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

static DEVICE_RESET( okim6376 )
{
	okim6376_state *info = get_safe_token(device);
	int i;

	info->stream->update();
	for (i = 0; i < OKIM6376_VOICES; i++)
		info->voice[i].playing = 0;
}




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

     okim6376_status_r -- read the status port of an OKIM6376-compatible chip

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

READ8_DEVICE_HANDLER( okim6376_r )
{
	okim6376_state *info = get_safe_token(device);
	int i, result;

	result = 0xff;

	/* set the bit to 1 if something is playing on a given channel */
	info->stream->update();
	for (i = 0; i < OKIM6376_VOICES; i++)
	{
		struct ADPCMVoice *voice = &info->voice[i];

		/* set the bit if it's playing */
		if (!voice->playing)
			result ^= 1 << i;
	}

	return result;
}


READ_LINE_DEVICE_HANDLER( okim6376_busy_r )
{
	okim6376_state *info = get_safe_token(device);
	int i,result=1;

	for (i = 0; i < OKIM6376_VOICES; i++)
	{
		struct ADPCMVoice *voice = &info->voice[i];

		/* set the bit low if it's playing */
		if (voice->playing)
			result = 0;
	}
	return result;
}

READ_LINE_DEVICE_HANDLER( okim6376_nar_r )
{
	okim6376_state *info = get_safe_token(device);
	return info->nar;
}

WRITE_LINE_DEVICE_HANDLER( okim6376_ch2_w )
{
	okim6376_state *info = get_safe_token(device);

	if (info->ch2 != state)
	{
		info->ch2 = state;
		info->ch2_update = 1;
	}
	if((!info->ch2)&&(info->ch2_update))
	{
		info->channel = 1;
		if (info->command[1] != info->latch)
		{
			info->command[1] = info->latch;
		}
	}

	if((info->ch2)&&(info->ch2_update))
	{
		info->channel = 0;
		oki_process(device, 0, info->command[1]);
	}
}


WRITE_LINE_DEVICE_HANDLER( okim6376_st_w )
{
	//As in STart, presumably, this triggers everything

	okim6376_state *info = get_safe_token(device);

	if (info->st != state)
	{

		info->st = state;
		info->st_update = 1;

		if ((info->channel == 1) & !info->st)//ST acts as attenuation for Channel 2 when low, and stays at that level until the channel is reset
		{
			struct ADPCMVoice *voice = &info->voice[info->channel];
			{

				info->st_pulses ++;
				if (info->st_pulses > 3)
				{
					info->st_pulses = 3; //undocumented behaviour beyond 3 pulses
				}

				voice->volume = volume_table[info->st_pulses];
			}
		}
		if (info->st && info->st_update)
		{
			info->command[info->channel] = info->latch;

			if (info->channel ==0)
			{
				oki_process(device, 0, info->command[0]);
			}
		}
	}
}

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

     okim6376_data_w -- write to the data port of an OKIM6376-compatible chip

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

WRITE8_DEVICE_HANDLER( okim6376_w )
{
	// The data port is purely used to set the latch, everything else is started by an ST pulse

	okim6376_state *info = get_safe_token(device);
	info->latch = data & 0x7f;
	// FIX: maximum adpcm words are 111, there are other 8 commands to generate BEEP tone (0x70 to 0x77),
	// and others for internal testing, that manual explicitly says not to use (0x78 to 0x7f)
}



/**************************************************************************
 * Generic get_info
 **************************************************************************/

DEVICE_GET_INFO( okim6376 )
{
	switch (state)
	{
		/* --- the following bits of info are returned as 64-bit signed integers --- */
		case DEVINFO_INT_TOKEN_BYTES:				info->i = sizeof(okim6376_state);					break;

		/* --- the following bits of info are returned as pointers to data or functions --- */
		case DEVINFO_FCT_START:						info->start = DEVICE_START_NAME( okim6376 );		break;
		case DEVINFO_FCT_STOP:						/* nothing */										break;
		case DEVINFO_FCT_RESET:						info->reset = DEVICE_RESET_NAME( okim6376 );		break;

		/* --- the following bits of info are returned as NULL-terminated strings --- */
		case DEVINFO_STR_NAME:						strcpy(info->s, "OKI6376");							break;
		case DEVINFO_STR_FAMILY:					strcpy(info->s, "OKI ADPCM");						break;
		case DEVINFO_STR_VERSION:					strcpy(info->s, "1.0");								break;
		case DEVINFO_STR_SOURCE_FILE:				strcpy(info->s, __FILE__);							break;
		case DEVINFO_STR_CREDITS:					strcpy(info->s, "Copyright Nicola Salmoria and the MAME Team"); break;
	}
}


DEFINE_LEGACY_SOUND_DEVICE(OKIM6376, okim6376);