[wrap]
MAME source file: / src / emu / sound / bsmt2000.c [download] (view on mamedev.org)
/***************************************************************************

    bsmt2000.c

    BSMT2000 sound emulator.

    Copyright Aaron Giles

    Chip is actually a TMS320C15 DSP with embedded mask rom
    Trivia: BSMT stands for "Brian Schmidt's Mouse Trap"

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

#include <math.h>

#include "sndintrf.h"
#include "streams.h"
#include "bsmt2000.h"


/***************************************************************************
    DEBUGGING/OPTIONS
***************************************************************************/

#define LOG_COMMANDS			0

/* NOTE: the original chip did not support interpolation, but it sounds */
/* nicer if you enable it. For accuracy's sake, we leave it off by default. */
#define ENABLE_INTERPOLATION	0



/***************************************************************************
    CONSTANTS
***************************************************************************/

#define MAX_VOICES				(12+1)
#define ADPCM_VOICE				12



/***************************************************************************
    TYPE DEFINITIONS
***************************************************************************/

/* struct describing a single playing voice */
typedef struct _bsmt2000_voice bsmt2000_voice;
struct _bsmt2000_voice
{
	UINT16		pos;					/* current position */
	UINT16		rate;					/* stepping value */
	UINT16		loopend;				/* loop end value */
	UINT16		loopstart;				/* loop start value */
	UINT16		bank;					/* bank number */
	UINT16		leftvol;				/* left volume */
	UINT16		rightvol;				/* right volume */
	UINT16		fraction;				/* current fractional position */
};

typedef struct _bsmt2000_chip bsmt2000_chip;
struct _bsmt2000_chip
{
	sound_stream *stream;				/* which stream are we using */
	UINT8		last_register;			/* last register address written */

	INT8 *		region_base;			/* pointer to the base of the region */
	int			total_banks;			/* number of total banks in the region */

	bsmt2000_voice voice[MAX_VOICES];	/* the voices */
	UINT16 *	regmap[128];			/* mapping of registers to voice params */

	UINT32		clock;					/* original clock on the chip */
	UINT8		stereo;					/* stereo output? */
	UINT8		voices;					/* number of voices */
	UINT8		adpcm;					/* adpcm enabled? */

	INT32		adpcm_current;			/* current ADPCM sample */
	INT32		adpcm_delta_n;			/* current ADPCM scale factor */
};



/***************************************************************************
    FUNCTION PROTOTYPES
***************************************************************************/

/* core implementation */
static void bsmt2000_reset(bsmt2000_chip *chip);
static STREAM_UPDATE( bsmt2000_update );

/* read/write access */
static void bsmt2000_reg_write(bsmt2000_chip *chip, offs_t offset, UINT16 data);

/* local functions */
static void set_mode(bsmt2000_chip *chip);
static void set_regmap(bsmt2000_chip *chip, UINT8 posbase, UINT8 ratebase, UINT8 endbase, UINT8 loopbase, UINT8 bankbase, UINT8 rvolbase, UINT8 lvolbase);



/***************************************************************************
    CORE IMPLEMENTATION
***************************************************************************/

/*-------------------------------------------------
    SND_START( bsmt2000 ) - initialization callback
-------------------------------------------------*/

static SND_START( bsmt2000 )
{
	bsmt2000_chip *chip;
	int voicenum;

	/* allocate the chip */
	chip = auto_malloc(sizeof(*chip));
	memset(chip, 0, sizeof(*chip));

	/* create a stream at a nominal sample rate (real one specified later) */
	chip->stream = stream_create(device, 0, 2, clock / 1000, chip, bsmt2000_update);
	chip->clock = clock;

	/* initialize the regions */
	chip->region_base = (INT8 *)device->region;
	chip->total_banks = device->regionbytes / 0x10000;

	/* register chip-wide data for save states */
	state_save_register_device_item(device, 0, chip->last_register);
	state_save_register_device_item(device, 0, chip->stereo);
	state_save_register_device_item(device, 0, chip->voices);
	state_save_register_device_item(device, 0, chip->adpcm);
	state_save_register_device_item(device, 0, chip->adpcm_current);
	state_save_register_device_item(device, 0, chip->adpcm_delta_n);

	/* register voice-specific data for save states */
	for (voicenum = 0; voicenum < MAX_VOICES; voicenum++)
	{
		bsmt2000_voice *voice = &chip->voice[voicenum];

		state_save_register_device_item(device, voicenum, voice->pos);
		state_save_register_device_item(device, voicenum, voice->rate);
		state_save_register_device_item(device, voicenum, voice->loopend);
		state_save_register_device_item(device, voicenum, voice->loopstart);
		state_save_register_device_item(device, voicenum, voice->bank);
		state_save_register_device_item(device, voicenum, voice->leftvol);
		state_save_register_device_item(device, voicenum, voice->rightvol);
		state_save_register_device_item(device, voicenum, voice->fraction);
	}

	/* reset the chip -- this also configures the default mode */
	bsmt2000_reset(chip);
	return chip;
}


/*-------------------------------------------------
    SND_RESET( bsmt2000 ) - chip reset callback
-------------------------------------------------*/

static void bsmt2000_reset(bsmt2000_chip *chip)
{
	int voicenum;

	/* reset all the voice data */
	for (voicenum = 0; voicenum < MAX_VOICES; voicenum++)
	{
		bsmt2000_voice *voice = &chip->voice[voicenum];
		memset(voice, 0, sizeof(*voice));
		voice->leftvol = 0x7fff;
		voice->rightvol = 0x7fff;
	}

	/* recompute the mode */
	set_mode(chip);
}

static SND_RESET( bsmt2000 )
{
	bsmt2000_reset(device->token);
}


/*-------------------------------------------------
    bsmt2000_update - update callback for
    sample generation
-------------------------------------------------*/

static STREAM_UPDATE( bsmt2000_update )
{
	stream_sample_t *left = outputs[0];
	stream_sample_t *right = outputs[1];
	bsmt2000_chip *chip = param;
	bsmt2000_voice *voice;
	int samp, voicenum;

	/* clear out the accumulator */
	memset(left, 0, samples * sizeof(left[0]));
	memset(right, 0, samples * sizeof(right[0]));

	/* loop over voices */
	for (voicenum = 0; voicenum < chip->voices; voicenum++)
	{
		voice = &chip->voice[voicenum];

		/* compute the region base */
		if (voice->bank < chip->total_banks)
		{
			INT8 *base = &chip->region_base[voice->bank * 0x10000];
			UINT32 rate = voice->rate;
			INT32 rvol = voice->rightvol;
			INT32 lvol = chip->stereo ? voice->leftvol : rvol;
			UINT16 pos = voice->pos;
			UINT16 frac = voice->fraction;

			/* loop while we still have samples to generate */
			for (samp = 0; samp < samples; samp++)
			{
#if ENABLE_INTERPOLATION
				INT32 sample = (base[pos] * (0x800 - frac) + (base[pos + 1] * frac)) >> 11;
#else
				INT32 sample = base[pos];
#endif
				/* apply volumes and add */
				left[samp] += sample * lvol;
				right[samp] += sample * rvol;

				/* update position */
				frac += rate;
				pos += frac >> 11;
				frac &= 0x7ff;

				/* check for loop end */
				if (pos >= voice->loopend)
					pos += voice->loopstart - voice->loopend;
			}

			/* update the position */
			voice->pos = pos;
			voice->fraction = frac;
		}
	}

	/* compressed voice (11-voice model only) */
	voice = &chip->voice[ADPCM_VOICE];
	if (chip->adpcm && voice->bank < chip->total_banks && voice->rate)
	{
		INT8 *base = &chip->region_base[voice->bank * 0x10000];
		INT32 rvol = voice->rightvol;
		INT32 lvol = chip->stereo ? voice->leftvol : rvol;
		UINT32 pos = voice->pos;
		UINT32 frac = voice->fraction;

		/* loop while we still have samples to generate */
		for (samp = 0; samp < samples && pos < voice->loopend; samp++)
		{
			/* apply volumes and add */
			left[samp] += (chip->adpcm_current * lvol) >> 8;
			right[samp] += (chip->adpcm_current * rvol) >> 8;

			/* update position */
			frac++;
			if (frac == 6)
			{
				pos++;
				frac = 0;
			}

			/* every 3 samples, we update the ADPCM state */
			if (frac == 1 || frac == 4)
			{
				static const UINT8 delta_tab[] = { 58,58,58,58,77,102,128,154 };
				int nibble = base[pos] >> ((frac == 1) ? 4 : 0);
				int value = (INT8)(nibble << 4) >> 4;
				int delta;

				/* compute the delta for this sample */
				delta = chip->adpcm_delta_n * value;
				if (value > 0)
					delta += chip->adpcm_delta_n >> 1;
				else
					delta -= chip->adpcm_delta_n >> 1;

				/* add and clamp against the sample */
				chip->adpcm_current += delta;
				if (chip->adpcm_current >= 32767)
					chip->adpcm_current = 32767;
				else if (chip->adpcm_current <= -32768)
					chip->adpcm_current = -32768;

				/* adjust the delta multiplier */
				chip->adpcm_delta_n = (chip->adpcm_delta_n * delta_tab[abs(value)]) >> 6;
				if (chip->adpcm_delta_n > 2000)
					chip->adpcm_delta_n = 2000;
				else if (chip->adpcm_delta_n < 1)
					chip->adpcm_delta_n = 1;
			}
		}

		/* update the position */
		voice->pos = pos;
		voice->fraction = frac;

		/* "rate" is a control register; clear it to 0 when done */
		if (pos >= voice->loopend)
			voice->rate = 0;
	}

	/* reduce the overall gain */
	for (samp = 0; samp < samples; samp++)
	{
		left[samp] >>= 9;
		right[samp] >>= 9;
	}
}



/***************************************************************************
    READ/WRITE ACCESS
***************************************************************************/

/*-------------------------------------------------
    bsmt2000_reg_write - handle a register write
-------------------------------------------------*/

static void bsmt2000_reg_write(bsmt2000_chip *chip, offs_t offset, UINT16 data)
{
	if (LOG_COMMANDS) mame_printf_debug("BSMT write: reg %02X = %04X\n", offset, data);

	/* remember the last write */
	chip->last_register = offset;

	/* update the register */
	if (offset < 0x80 && chip->regmap[offset] != NULL)
	{
		UINT16 *dest = chip->regmap[offset];

		/* force an update, then write the data */
		stream_update(chip->stream);
		*dest = data;

		/* special case: reset ADPCM parameters when writing to the ADPCM position */
		if (dest == &chip->voice[ADPCM_VOICE].rate)
		{
			chip->adpcm_current = 0;
			chip->adpcm_delta_n = 10;
		}
	}
}



/***************************************************************************
    PER-CHIP READ/WRITE HANDLERS
***************************************************************************/

/*-------------------------------------------------
    bsmt2000_data_0_w - write to chip 0
-------------------------------------------------*/

WRITE16_HANDLER( bsmt2000_data_0_w )
{
	bsmt2000_reg_write(sndti_token(SOUND_BSMT2000, 0), offset, data);
}



/***************************************************************************
    LOCAL FUNCTIONS
***************************************************************************/

/*-------------------------------------------------
    set_mode - set the mode after reset
-------------------------------------------------*/

static void set_mode(bsmt2000_chip *chip)
{
	int sample_rate;

	/* force an update */
	stream_update(chip->stream);

	/* the mode comes from the address of the last register accessed */
	switch (chip->last_register)
	{
		/* mode 0: 24kHz, 12 channel PCM, 1 channel ADPCM, mono */
		default:
		case 0:
			sample_rate = chip->clock / 1000;
			chip->stereo = FALSE;
			chip->voices = 12;
			chip->adpcm = TRUE;
			set_regmap(chip, 0x00, 0x18, 0x24, 0x30, 0x3c, 0x48, 0);
			break;

		/* mode 1: 24kHz, 11 channel PCM, 1 channel ADPCM, stereo */
		case 1:
			sample_rate = chip->clock / 1000;
			chip->stereo = TRUE;
			chip->voices = 11;
			chip->adpcm = TRUE;
			set_regmap(chip, 0x00, 0x16, 0x21, 0x2c, 0x37, 0x42, 0x4d);
			break;

		/* mode 5: 24kHz, 12 channel PCM, stereo */
		case 5:
			sample_rate = chip->clock / 1000;
			chip->stereo = TRUE;
			chip->voices = 12;
			chip->adpcm = FALSE;
			set_regmap(chip, 0x00, 0x18, 0x24, 0x30, 0x3c, 0x54, 0x60);
			break;

		/* mode 6: 34kHz, 8 channel PCM, stereo */
		case 6:
			sample_rate = chip->clock / 706;
			chip->stereo = TRUE;
			chip->voices = 8;
			chip->adpcm = FALSE;
			set_regmap(chip, 0x00, 0x10, 0x18, 0x20, 0x28, 0x38, 0x40);
			break;

		/* mode 7: 32kHz, 9 channel PCM, stereo */
		case 7:
			sample_rate = chip->clock / 750;
			chip->stereo = TRUE;
			chip->voices = 9;
			chip->adpcm = FALSE;
			set_regmap(chip, 0x00, 0x12, 0x1b, 0x24, 0x2d, 0x3f, 0x48);
			break;
	}

	/* update the sample rate */
	stream_set_sample_rate(chip->stream, sample_rate);
}


/*-------------------------------------------------
    set_regmap - initialize the register mapping
-------------------------------------------------*/

static void set_regmap(bsmt2000_chip *chip, UINT8 posbase, UINT8 ratebase, UINT8 endbase, UINT8 loopbase, UINT8 bankbase, UINT8 rvolbase, UINT8 lvolbase)
{
	int voice;

	/* reset the map */
	memset(chip->regmap, 0, sizeof(chip->regmap));

	/* iterate over voices */
	for (voice = 0; voice < chip->voices; voice++)
	{
		chip->regmap[posbase + voice] = &chip->voice[voice].pos;
		chip->regmap[ratebase + voice] = &chip->voice[voice].rate;
		chip->regmap[endbase + voice] = &chip->voice[voice].loopend;
		chip->regmap[loopbase + voice] = &chip->voice[voice].loopstart;
		chip->regmap[bankbase + voice] = &chip->voice[voice].bank;
		chip->regmap[rvolbase + voice] = &chip->voice[voice].rightvol;
		if (chip->stereo)
			chip->regmap[lvolbase + voice] = &chip->voice[voice].leftvol;
	}

	/* set the ADPCM register */
	if (chip->adpcm)
	{
		chip->regmap[0x6d] = &chip->voice[ADPCM_VOICE].loopend;
		chip->regmap[0x6f] = &chip->voice[ADPCM_VOICE].bank;
		chip->regmap[0x73] = &chip->voice[ADPCM_VOICE].rate;
		chip->regmap[0x74] = &chip->voice[ADPCM_VOICE].rightvol;
		chip->regmap[0x75] = &chip->voice[ADPCM_VOICE].pos;
		if (chip->stereo)
			chip->regmap[0x76] = &chip->voice[ADPCM_VOICE].leftvol;
	}
}



/***************************************************************************
    GET/SET INFO CALLBACKS
***************************************************************************/

/*-------------------------------------------------
    SND_SET_INFO( bsmt2000 ) - callback for
    setting chip information
-------------------------------------------------*/

static SND_SET_INFO( bsmt2000 )
{
	switch (state)
	{
		/* no parameters to set */
	}
}


/*-------------------------------------------------
    SND_GET_INFO( bsmt2000 ) - callback for
    retrieving chip information
-------------------------------------------------*/

SND_GET_INFO( bsmt2000 )
{
	switch (state)
	{
		/* --- the following bits of info are returned as 64-bit signed integers --- */

		/* --- the following bits of info are returned as pointers to data or functions --- */
		case SNDINFO_PTR_SET_INFO:						info->set_info = SND_SET_INFO_NAME( bsmt2000 );		break;
		case SNDINFO_PTR_START:							info->start = SND_START_NAME( bsmt2000 );			break;
		case SNDINFO_PTR_STOP:							/* nothing */										break;
		case SNDINFO_PTR_RESET:							info->reset = SND_RESET_NAME( bsmt2000 );			break;

		/* --- the following bits of info are returned as NULL-terminated strings --- */
		case SNDINFO_STR_NAME:							strcpy(info->s, "BSMT2000");						break;
		case SNDINFO_STR_CORE_FAMILY:					strcpy(info->s, "Data East Wavetable");				break;
		case SNDINFO_STR_CORE_VERSION:					strcpy(info->s, "1.0");								break;
		case SNDINFO_STR_CORE_FILE:						strcpy(info->s, __FILE__);							break;
		case SNDINFO_STR_CORE_CREDITS:					strcpy(info->s, "Copyright Nicola Salmoria and the MAME Team"); break;
	}
}
  
2004-2009 MAWS all copyrights belong to their respective owners