mame/src/emu/sound/cem3394.c
Aaron Giles 27fed1ec97 Changed the way memory regions are referenced. Instead of a single
integer value, regions are now referred to by a region class and
a region tag. The class specifies the type of region (one of CPU,
gfx, sound, user, disk, prom, pld) while the tag uniquely specifies
the region. This change required updating all the ROM region
definitions in the project to specify the class/tag instead of
region number.

Updated the core memory_region_* functions to accept a class/tag
pair. Added new memory_region_next() function to allow for iteration
over all memory regions of a given class. Added new function
memory_region_class_name() to return the name for a given CPU
memory region class.

Changed the auto-binding behavior of CPU regions. Previously, the
first CPU would auto-bind to REGION_CPU1 (that is, any ROM references
would automatically assume that they lived in the corresponding
region). Now, each CPU automatically binds to the RGNCLASS_CPU region
with the same tag as the CPU itself. This behavior required ensuring
that all previous REGION_CPU* regions were changed to RGNCLASS_CPU
with the same tag as the CPU.

Introduced a new auto-binding mechanism for sound cores. This works
similarly to the CPU binding. Each sound core that requires a memory
region now auto-binds to the RGNCLASS_SOUND with the same tag as the
sound core. In almost all cases, this allowed for the removal of the
explicit region item in the sound configuration, which in turn 
allowed for many sound configurations to removed altogether.

Updated the expression engine's memory reference behavior. A recent
update expanded the scope of memory references to allow for referencing
data in non-active CPU spaces, in memory regions, and in EEPROMs.
However, this previous update required an index, which is no longer
appropriate for regions and will become increasingly less appropriate
for CPUs over time. Instead, a new syntax is supported, of the form:
"[tag.][space]size@addr", where 'tag' is an optional tag for the CPU
or memory region you wish to access, followed by a period as a 
separator; 'space' is the memory address space or region class you
wish to access (p/d/i for program/data/I/O spaces; o for opcode space;
r for direct RAM; c/u/g/s for CPU/user/gfx/sound regions; e for 
EEPROMs); and 'size' is the usual b/w/d/q for byte/word/dword/qword.

Cleaned up ROM definition flags and removed some ugly hacks that had
existed previously. Expanded to support up to 256 BIOSes. Updated
ROM_COPY to support specifying class/tag for the source region.

Updated the address map AM_REGION macro to support specifying a
class/tag for the region.

Updated debugger windows to display the CPU and region tags where
appropriate.

Updated -listxml to output region class and tag for each ROM entry.
2008-07-28 09:35:36 +00:00

594 lines
17 KiB
C

/***************************************************************************
CEM3394 sound driver.
This driver handles CEM-3394 analog synth chip. Very crudely.
Still to do:
- adjust the overall volume when multiple waves are being generated
- filter internal sound
- support resonance (don't understand how it works)
***************************************************************************/
#include "sndintrf.h"
#include "streams.h"
#include "cem3394.h"
#include <math.h>
/* waveform generation parameters */
#define ENABLE_PULSE 1
#define ENABLE_TRIANGLE 1
#define ENABLE_SAWTOOTH 1
#define ENABLE_EXTERNAL 1
/* pulse shaping parameters */
/* examples: */
/* hat trick - skidding ice sounds too loud if minimum width is too big */
/* snake pit - melody during first level too soft if minimum width is too small */
/* snake pit - bonus counter at the end of level */
/* snacks'n jaxson - laugh at end of level is too soft if minimum width is too small */
#define LIMIT_WIDTH 1
#define MINIMUM_WIDTH 0.25
#define MAXIMUM_WIDTH 0.75
/********************************************************************************
From the datasheet:
CEM3394_VCO_FREQUENCY:
-4.0 ... +4.0
-0.75 V/octave
f = exp(V) * 431.894
CEM3394_MODULATION_AMOUNT
0.0 ... +3.5
0.0 == 0.01 x frequency
3.5 == 2.00 x frequency
CEM3394_WAVE_SELECT
-0.5 ... -0.2 == triangle
+0.9 ... +1.5 == triangle + sawtooth
+2.3 ... +3.9 == sawtooth
CEM3394_PULSE_WIDTH
0.0 ... +2.0
0.0 == 0% duty cycle
+2.0 == 100% duty cycle
CEM3394_MIXER_BALANCE
-4.0 ... +4.0
0.0 both at -6dB
-20 dB/V
CEM3394_FILTER_RESONANCE
0.0 ... +2.5
0.0 == no resonance
+2.5 == oscillation
CEM3394_FILTER_FREQENCY
-3.0 ... +4.0
-0.375 V/octave
0.0 == 1300Hz
CEM3394_FINAL_GAIN
0.0 ... +4.0
-20 dB/V
0.0 == -90dB
4.0 == 0dB
Square wave output = 160 (average is constant regardless of duty cycle)
Sawtooth output = 200
Triangle output = 250
Sawtooth + triangle output = 330
Maximum output = 400
********************************************************************************/
/* various waveforms */
#define WAVE_TRIANGLE 1
#define WAVE_SAWTOOTH 2
#define WAVE_PULSE 4
/* keep lots of fractional bits */
#define FRACTION_BITS 28
#define FRACTION_ONE (1 << FRACTION_BITS)
#define FRACTION_ONE_D ((double)(1 << FRACTION_BITS))
#define FRACTION_MASK (FRACTION_ONE - 1)
#define FRACTION_MULT(a,b) (((a) >> (FRACTION_BITS / 2)) * ((b) >> (FRACTION_BITS - FRACTION_BITS / 2)))
/* this structure defines the parameters for a channel */
typedef struct
{
sound_stream * stream; /* our stream */
int index;
void (*external)(int, int, short *);/* callback to generate external samples */
double vco_zero_freq; /* frequency of VCO at 0.0V */
double filter_zero_freq; /* frequency of filter at 0.0V */
double values[8]; /* raw values of registers */
UINT8 wave_select; /* flags which waveforms are enabled */
UINT32 volume; /* linear overall volume (0-256) */
UINT32 mixer_internal; /* linear internal volume (0-256) */
UINT32 mixer_external; /* linear external volume (0-256) */
UINT32 position; /* current VCO frequency position (0.FRACTION_BITS) */
UINT32 step; /* per-sample VCO step (0.FRACTION_BITS) */
UINT32 filter_position; /* current filter frequency position (0.FRACTION_BITS) */
UINT32 filter_step; /* per-sample filter step (0.FRACTION_BITS) */
UINT32 modulation_depth; /* fraction of total by which we modulate (0.FRACTION_BITS) */
INT16 last_ext; /* last external sample we read */
UINT32 pulse_width; /* fractional pulse width (0.FRACTION_BITS) */
double inv_sample_rate;
int sample_rate;
INT16 *mixer_buffer;
INT16 *external_buffer;
} sound_chip;
/* generate sound to the mix buffer in mono */
static void cem3394_update(void *param, stream_sample_t **inputs, stream_sample_t **_buffer, int length)
{
sound_chip *chip = param;
int int_volume = (chip->volume * chip->mixer_internal) / 256;
int ext_volume = (chip->volume * chip->mixer_external) / 256;
UINT32 step = chip->step, position, end_position = 0;
stream_sample_t *buffer = _buffer[0];
INT16 *mix, *ext;
int i;
/* external volume is effectively 0 if no external function */
if (!chip->external || !ENABLE_EXTERNAL)
ext_volume = 0;
/* adjust the volume for the filter */
if (step > chip->filter_step)
int_volume /= step - chip->filter_step;
/* bail if nothing's going on */
if (int_volume == 0 && ext_volume == 0)
{
memset(buffer, 0, sizeof(*buffer) * length);
return;
}
/* if there's external stuff, fetch and process it now */
if (ext_volume != 0)
{
UINT32 fposition = chip->filter_position, fstep = chip->filter_step, depth;
INT16 last_ext = chip->last_ext;
/* fetch the external data */
(*chip->external)(chip->index, length, chip->external_buffer);
/* compute the modulation depth, and adjust fstep to the maximum frequency */
/* we lop off 13 bits of depth so that we can multiply by stepadjust, below, */
/* which has 13 bits of precision */
depth = FRACTION_MULT(fstep, chip->modulation_depth);
fstep += depth;
depth >>= 13;
/* "apply" the filter: note this is pretty cheesy; it basically just downsamples the
external sample to filter_freq by allowing only 2 transitions for every cycle */
for (i = 0, ext = chip->external_buffer, position = chip->position; i < length; i++, ext++)
{
UINT32 newposition;
INT32 stepadjust;
/* update the position and compute the adjustment from a triangle wave */
if (position & (1 << (FRACTION_BITS - 1)))
stepadjust = 0x2000 - ((position >> (FRACTION_BITS - 14)) & 0x1fff);
else
stepadjust = (position >> (FRACTION_BITS - 14)) & 0x1fff;
position += step;
/* if we cross a half-step boundary, allow the next byte of the external input */
newposition = fposition + fstep - (stepadjust * depth);
if ((newposition ^ fposition) & ~(FRACTION_MASK >> 1))
last_ext = *ext;
else
*ext = last_ext;
fposition = newposition & FRACTION_MASK;
}
/* update the final filter values */
chip->filter_position = fposition;
chip->last_ext = last_ext;
}
/* if there's internal stuff, generate it */
if (int_volume != 0)
{
if (chip->wave_select == 0 && !ext_volume)
logerror("%f V didn't cut it\n", chip->values[CEM3394_WAVE_SELECT]);
/* handle the pulse component; it maxes out at 0x1932, which is 27% smaller than */
/* the sawtooth (since the value is constant, this is the best place to have an */
/* odd value for volume) */
if (ENABLE_PULSE && (chip->wave_select & WAVE_PULSE))
{
UINT32 pulse_width = chip->pulse_width;
/* if the width is wider than the step, we're guaranteed to hit it once per cycle */
if (pulse_width >= step)
{
for (i = 0, mix = chip->mixer_buffer, position = chip->position; i < length; i++, mix++)
{
if (position < pulse_width)
*mix = 0x1932;
else
*mix = 0x0000;
position = (position + step) & FRACTION_MASK;
}
}
/* otherwise, we compute a volume and watch for cycle boundary crossings */
else
{
INT16 volume = 0x1932 * pulse_width / step;
for (i = 0, mix = chip->mixer_buffer, position = chip->position; i < length; i++, mix++)
{
UINT32 newposition = position + step;
if ((newposition ^ position) & ~FRACTION_MASK)
*mix = volume;
else
*mix = 0x0000;
position = newposition & FRACTION_MASK;
}
}
end_position = position;
}
/* otherwise, clear the mixing buffer */
else
memset(chip->mixer_buffer, 0, sizeof(INT16) * length);
/* handle the sawtooth component; it maxes out at 0x2000, which is 27% larger */
/* than the pulse */
if (ENABLE_SAWTOOTH && (chip->wave_select & WAVE_SAWTOOTH))
{
for (i = 0, mix = chip->mixer_buffer, position = chip->position; i < length; i++, mix++)
{
*mix += ((position >> (FRACTION_BITS - 14)) & 0x3fff) - 0x2000;
position += step;
}
end_position = position & FRACTION_MASK;
}
/* handle the triangle component; it maxes out at 0x2800, which is 25% larger */
/* than the sawtooth (should be 27% according to the specs, but 25% saves us */
/* a multiplication) */
if (ENABLE_TRIANGLE && (chip->wave_select & WAVE_TRIANGLE))
{
for (i = 0, mix = chip->mixer_buffer, position = chip->position; i < length; i++, mix++)
{
INT16 value;
if (position & (1 << (FRACTION_BITS - 1)))
value = 0x2000 - ((position >> (FRACTION_BITS - 14)) & 0x1fff);
else
value = (position >> (FRACTION_BITS - 14)) & 0x1fff;
*mix += value + (value >> 2);
position += step;
}
end_position = position & FRACTION_MASK;
}
/* update the final position */
chip->position = end_position;
}
/* mix it down */
mix = chip->mixer_buffer;
ext = chip->external_buffer;
{
/* internal + external */
if (ext_volume != 0 && int_volume != 0)
{
for (i = 0; i < length; i++, mix++, ext++)
*buffer++ = (*mix * int_volume + *ext * ext_volume) / 128;
}
/* internal only */
else if (int_volume != 0)
{
for (i = 0; i < length; i++, mix++)
*buffer++ = *mix * int_volume / 128;
}
/* external only */
else
{
for (i = 0; i < length; i++, ext++)
*buffer++ = *ext * ext_volume / 128;
}
}
}
static void *cem3394_start(const char *tag, int sndindex, int clock, const void *config)
{
const struct cem3394_interface *intf = config;
sound_chip *chip;
chip = auto_malloc(sizeof(*chip));
memset(chip, 0, sizeof(*chip));
chip->index = sndindex;
/* copy global parameters */
chip->sample_rate = CEM3394_SAMPLE_RATE;
chip->inv_sample_rate = 1.0 / (double)chip->sample_rate;
/* allocate stream channels, 1 per chip */
chip->stream = stream_create(0, 1, chip->sample_rate, chip, cem3394_update);
chip->external = intf->external;
chip->vco_zero_freq = intf->vco_zero_freq;
chip->filter_zero_freq = intf->filter_zero_freq;
/* allocate memory for a mixer buffer and external buffer (1 second should do it!) */
chip->mixer_buffer = auto_malloc(chip->sample_rate * sizeof(INT16));
chip->external_buffer = auto_malloc(chip->sample_rate * sizeof(INT16));
state_save_register_item_array("cem3394", sndindex, chip->values);
state_save_register_item("cem3394", sndindex, chip->wave_select);
state_save_register_item("cem3394", sndindex, chip->volume);
state_save_register_item("cem3394", sndindex, chip->mixer_internal);
state_save_register_item("cem3394", sndindex, chip->mixer_external);
state_save_register_item("cem3394", sndindex, chip->position);
state_save_register_item("cem3394", sndindex, chip->step);
state_save_register_item("cem3394", sndindex, chip->filter_position);
state_save_register_item("cem3394", sndindex, chip->filter_step);
state_save_register_item("cem3394", sndindex, chip->modulation_depth);
state_save_register_item("cem3394", sndindex, chip->last_ext);
state_save_register_item("cem3394", sndindex, chip->pulse_width);
return chip;
}
INLINE double compute_db(double voltage)
{
/* assumes 0.0 == full off, 4.0 == full on, with linear taper, as described in the datasheet */
/* above 4.0, maximum volume */
if (voltage >= 4.0)
return 0.0;
/* below 0.0, minimum volume */
else if (voltage <= 0.0)
return 90.0;
/* between 2.5 and 4.0, linear from 20dB to 0dB */
else if (voltage >= 2.5)
return (4.0 - voltage) * (1.0 / 1.5) * 20.0;
/* between 0.0 and 2.5, exponential to 20dB */
else
{
double temp = 20.0 * pow(2.0, 2.5 - voltage);
if (temp < 90.0) return 90.0;
else return temp;
}
}
INLINE UINT32 compute_db_volume(double voltage)
{
double temp;
/* assumes 0.0 == full off, 4.0 == full on, with linear taper, as described in the datasheet */
/* above 4.0, maximum volume */
if (voltage >= 4.0)
return 256;
/* below 0.0, minimum volume */
else if (voltage <= 0.0)
return 0;
/* between 2.5 and 4.0, linear from 20dB to 0dB */
else if (voltage >= 2.5)
temp = (4.0 - voltage) * (1.0 / 1.5) * 20.0;
/* between 0.0 and 2.5, exponential to 20dB */
else
{
temp = 20.0 * pow(2.0, 2.5 - voltage);
if (temp < 50.0) return 0;
}
/* convert from dB to volume and return */
return (UINT32)(256.0 * pow(0.891251, temp));
}
void cem3394_set_voltage(int chipnum, int input, double voltage)
{
sound_chip *chip = sndti_token(SOUND_CEM3394, chipnum);
double temp;
/* don't do anything if no change */
if (voltage == chip->values[input])
return;
chip->values[input] = voltage;
/* update the stream first */
stream_update(chip->stream);
/* switch off the input */
switch (input)
{
/* frequency varies from -4.0 to +4.0, at 0.75V/octave */
case CEM3394_VCO_FREQUENCY:
temp = chip->vco_zero_freq * pow(2.0, -voltage * (1.0 / 0.75));
chip->step = (UINT32)(temp * chip->inv_sample_rate * FRACTION_ONE_D);
break;
/* wave select determines triangle/sawtooth enable */
case CEM3394_WAVE_SELECT:
chip->wave_select &= ~(WAVE_TRIANGLE | WAVE_SAWTOOTH);
if (voltage >= -0.5 && voltage <= -0.2)
chip->wave_select |= WAVE_TRIANGLE;
else if (voltage >= 0.9 && voltage <= 1.5)
chip->wave_select |= WAVE_TRIANGLE | WAVE_SAWTOOTH;
else if (voltage >= 2.3 && voltage <= 3.9)
chip->wave_select |= WAVE_SAWTOOTH;
break;
/* pulse width determines duty cycle; 0.0 means 0%, 2.0 means 100% */
case CEM3394_PULSE_WIDTH:
if (voltage < 0.0)
{
chip->pulse_width = 0;
chip->wave_select &= ~WAVE_PULSE;
}
else
{
temp = voltage * 0.5;
if (LIMIT_WIDTH)
temp = MINIMUM_WIDTH + (MAXIMUM_WIDTH - MINIMUM_WIDTH) * temp;
chip->pulse_width = (UINT32)(temp * FRACTION_ONE_D);
chip->wave_select |= WAVE_PULSE;
}
break;
/* final gain is pretty self-explanatory; 0.0 means ~90dB, 4.0 means 0dB */
case CEM3394_FINAL_GAIN:
chip->volume = compute_db_volume(voltage);
break;
/* mixer balance is a pan between the external input and the internal input */
/* 0.0 is equal parts of both; positive values favor external, negative favor internal */
case CEM3394_MIXER_BALANCE:
if (voltage >= 0.0)
{
chip->mixer_internal = compute_db_volume(3.55 - voltage);
chip->mixer_external = compute_db_volume(3.55 + 0.45 * (voltage * 0.25));
}
else
{
chip->mixer_internal = compute_db_volume(3.55 - 0.45 * (voltage * 0.25));
chip->mixer_external = compute_db_volume(3.55 + voltage);
}
break;
/* filter frequency varies from -4.0 to +4.0, at 0.375V/octave */
case CEM3394_FILTER_FREQENCY:
temp = chip->filter_zero_freq * pow(2.0, -voltage * (1.0 / 0.375));
chip->filter_step = (UINT32)(temp * chip->inv_sample_rate * FRACTION_ONE_D);
break;
/* modulation depth is 0.01 at 0V and 2.0 at 3.5V; how it grows from one to the other */
/* is still unclear at this point */
case CEM3394_MODULATION_AMOUNT:
if (voltage < 0.0)
chip->modulation_depth = (UINT32)(0.01 * FRACTION_ONE_D);
else if (voltage > 3.5)
chip->modulation_depth = (UINT32)(2.00 * FRACTION_ONE_D);
else
chip->modulation_depth = (UINT32)(((voltage * (1.0 / 3.5)) * 1.99 + 0.01) * FRACTION_ONE_D);
break;
/* this is not yet implemented */
case CEM3394_FILTER_RESONANCE:
break;
}
}
double cem3394_get_parameter(int chipnum, int input)
{
sound_chip *chip = sndti_token(SOUND_CEM3394, chipnum);
double voltage = chip->values[input];
switch (input)
{
case CEM3394_VCO_FREQUENCY:
return chip->vco_zero_freq * pow(2.0, -voltage * (1.0 / 0.75));
case CEM3394_WAVE_SELECT:
return voltage;
case CEM3394_PULSE_WIDTH:
if (voltage <= 0.0)
return 0.0;
else if (voltage >= 2.0)
return 1.0;
else
return voltage * 0.5;
case CEM3394_FINAL_GAIN:
return compute_db(voltage);
case CEM3394_MIXER_BALANCE:
return voltage * 0.25;
case CEM3394_MODULATION_AMOUNT:
if (voltage < 0.0)
return 0.01;
else if (voltage > 3.5)
return 2.0;
else
return (voltage * (1.0 / 3.5)) * 1.99 + 0.01;
case CEM3394_FILTER_RESONANCE:
if (voltage < 0.0)
return 0.0;
else if (voltage > 2.5)
return 1.0;
else
return voltage * (1.0 / 2.5);
case CEM3394_FILTER_FREQENCY:
return chip->filter_zero_freq * pow(2.0, -voltage * (1.0 / 0.375));
}
return 0.0;
}
/**************************************************************************
* Generic get_info
**************************************************************************/
static void cem3394_set_info(void *token, UINT32 state, sndinfo *info)
{
switch (state)
{
/* no parameters to set */
}
}
void cem3394_get_info(void *token, UINT32 state, sndinfo *info)
{
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 = cem3394_set_info; break;
case SNDINFO_PTR_START: info->start = cem3394_start; break;
case SNDINFO_PTR_STOP: /* nothing */ break;
case SNDINFO_PTR_RESET: /* nothing */ break;
/* --- the following bits of info are returned as NULL-terminated strings --- */
case SNDINFO_STR_NAME: info->s = "CEM3394"; break;
case SNDINFO_STR_CORE_FAMILY: info->s = "Analog Synth"; break;
case SNDINFO_STR_CORE_VERSION: info->s = "1.0"; break;
case SNDINFO_STR_CORE_FILE: info->s = __FILE__; break;
case SNDINFO_STR_CORE_CREDITS: info->s = "Copyright Nicola Salmoria and the MAME Team"; break;
}
}