summaryrefslogtreecommitdiffstatshomepage
path: root/trunk/src/mame/audio/exidy440.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/src/mame/audio/exidy440.c')
-rw-r--r--trunk/src/mame/audio/exidy440.c1020
1 files changed, 1020 insertions, 0 deletions
diff --git a/trunk/src/mame/audio/exidy440.c b/trunk/src/mame/audio/exidy440.c
new file mode 100644
index 00000000000..61cc35c8861
--- /dev/null
+++ b/trunk/src/mame/audio/exidy440.c
@@ -0,0 +1,1020 @@
+/***************************************************************************
+
+ Exidy 440 sound system
+
+ Special thanks to Zonn Moore and Neil Bradley for letting me hack
+ their Retrocade CVSD decoder into the sound system here.
+
+***************************************************************************/
+
+#include "emu.h"
+#include "cpu/m6809/m6809.h"
+#include "audio/exidy440.h"
+
+
+#define SOUND_LOG 0
+#define FADE_TO_ZERO 1
+
+
+#define EXIDY440_AUDIO_CLOCK (XTAL_12_9792MHz / 16)
+#define EXIDY440_MC3418_CLOCK (EXIDY440_AUDIO_CLOCK / 16)
+#define EXIDY440_MC3417_CLOCK (EXIDY440_AUDIO_CLOCK / 32)
+
+
+/* internal caching */
+#define MAX_CACHE_ENTRIES 1024 /* maximum separate samples we expect to ever see */
+#define SAMPLE_BUFFER_LENGTH 1024 /* size of temporary decode buffer on the stack */
+
+/* FIR digital filter parameters */
+#define FIR_HISTORY_LENGTH 57 /* number of FIR coefficients */
+
+/* CVSD decoding parameters */
+#define INTEGRATOR_LEAK_TC (10e3 * 0.1e-6)
+#define FILTER_DECAY_TC ((18e3 + 3.3e3) * 0.33e-6)
+#define FILTER_CHARGE_TC (18e3 * 0.33e-6)
+#define FILTER_MIN 0.0416
+#define FILTER_MAX 1.0954
+#define SAMPLE_GAIN 10000.0
+
+
+/* channel_data structure holds info about each 6844 DMA channel */
+typedef struct m6844_channel_data
+{
+ int active;
+ int address;
+ int counter;
+ UINT8 control;
+ int start_address;
+ int start_counter;
+} m6844_channel_data;
+
+
+/* channel_data structure holds info about each active sound channel */
+typedef struct sound_channel_data
+{
+ INT16 *base;
+ int offset;
+ int remaining;
+} sound_channel_data;
+
+
+/* sound_cache_entry structure contains info on each decoded sample */
+typedef struct sound_cache_entry
+{
+ struct sound_cache_entry *next;
+ int address;
+ int length;
+ int bits;
+ int frequency;
+ INT16 data[1];
+} sound_cache_entry;
+
+
+
+typedef struct
+{
+ UINT8 sound_command;
+ UINT8 sound_command_ack;
+
+ UINT8 sound_banks[4];
+ UINT8 m6844_data[0x20];
+ UINT8 sound_volume[0x10];
+ INT32 *mixer_buffer_left;
+ INT32 *mixer_buffer_right;
+ sound_cache_entry *sound_cache;
+ sound_cache_entry *sound_cache_end;
+ sound_cache_entry *sound_cache_max;
+
+ /* 6844 description */
+ m6844_channel_data m6844_channel[4];
+ UINT8 m6844_priority;
+ UINT8 m6844_interrupt;
+ UINT8 m6844_chain;
+
+ /* sound interface parameters */
+ sound_stream *stream;
+ sound_channel_data sound_channel[4];
+
+ /* debugging */
+ FILE *debuglog;
+
+ /* channel frequency is configurable */
+ int channel_frequency[4];
+} exidy440_audio_state;
+
+/* constant channel parameters */
+static const int channel_bits[4] =
+{
+ 4, 4, /* channels 0 and 1 are MC3418s, 4-bit CVSD */
+ 3, 3 /* channels 2 and 3 are MC3417s, 3-bit CVSD */
+};
+
+
+/* function prototypes */
+static STREAM_UPDATE( channel_update );
+static void m6844_finished(m6844_channel_data *channel);
+static void play_cvsd(device_t *device, int ch);
+static void stop_cvsd(device_t *device, int ch);
+
+static void reset_sound_cache(device_t *device);
+static INT16 *add_to_sound_cache(device_t *device, UINT8 *input, int address, int length, int bits, int frequency);
+static INT16 *find_or_add_to_sound_cache(device_t *device, int address, int length, int bits, int frequency);
+
+static void decode_and_filter_cvsd(device_t *device, UINT8 *data, int bytes, int maskbits, int frequency, INT16 *dest);
+static void fir_filter(device_t *device, INT32 *input, INT16 *output, int count);
+
+
+DECLARE_LEGACY_SOUND_DEVICE(EXIDY440, exidy440_sound);
+
+/*************************************
+ *
+ * Initialize the sound system
+ *
+ *************************************/
+
+INLINE exidy440_audio_state *get_safe_token(device_t *device)
+{
+ assert(device != NULL);
+ assert(device->type() == EXIDY440);
+
+ return (exidy440_audio_state *)downcast<legacy_device_base *>(device)->token();
+}
+
+static DEVICE_START( exidy440_sound )
+{
+ exidy440_audio_state *state = get_safe_token(device);
+ running_machine &machine = device->machine();
+ int i, length;
+
+ /* reset the system */
+ state->sound_command = 0;
+ state->sound_command_ack = 1;
+ state_save_register_global(machine, state->sound_command);
+ state_save_register_global(machine, state->sound_command_ack);
+
+ /* reset the 6844 */
+ for (i = 0; i < 4; i++)
+ {
+ state->m6844_channel[i].active = 0;
+ state->m6844_channel[i].control = 0x00;
+ }
+ state->m6844_priority = 0x00;
+ state->m6844_interrupt = 0x00;
+ state->m6844_chain = 0x00;
+
+ state_save_register_global(machine, state->m6844_priority);
+ state_save_register_global(machine, state->m6844_interrupt);
+ state_save_register_global(machine, state->m6844_chain);
+
+ state->channel_frequency[0] = device->clock(); /* channels 0 and 1 are run by FCLK */
+ state->channel_frequency[1] = device->clock();
+ state->channel_frequency[2] = device->clock()/2; /* channels 2 and 3 are run by SCLK */
+ state->channel_frequency[3] = device->clock()/2;
+
+ /* get stream channels */
+ state->stream = device->machine().sound().stream_alloc(*device, 0, 2, device->clock(), NULL, channel_update);
+
+ /* allocate the sample cache */
+ length = machine.root_device().memregion("cvsd")->bytes() * 16 + MAX_CACHE_ENTRIES * sizeof(sound_cache_entry);
+ state->sound_cache = (sound_cache_entry *)auto_alloc_array(machine, UINT8, length);
+
+ /* determine the hard end of the cache and reset */
+ state->sound_cache_max = (sound_cache_entry *)((UINT8 *)state->sound_cache + length);
+ reset_sound_cache(device);
+
+ /* allocate the mixer buffer */
+ state->mixer_buffer_left = auto_alloc_array(machine, INT32, 2 * device->clock());
+ state->mixer_buffer_right = state->mixer_buffer_left + device->clock();
+
+ if (SOUND_LOG)
+ state->debuglog = fopen("sound.log", "w");
+}
+
+
+
+/*************************************
+ *
+ * Tear down the sound system
+ *
+ *************************************/
+
+static DEVICE_STOP( exidy440_sound )
+{
+ exidy440_audio_state *state = get_safe_token(device);
+ if (SOUND_LOG && state->debuglog)
+ fclose(state->debuglog);
+}
+
+
+
+/*************************************
+ *
+ * Add a bunch of samples to the mix
+ *
+ *************************************/
+
+static void add_and_scale_samples(device_t *device, int ch, INT32 *dest, int samples, int volume)
+{
+ exidy440_audio_state *state = get_safe_token(device);
+ sound_channel_data *channel = &state->sound_channel[ch];
+ INT16 *srcdata;
+ int i;
+
+ /* channels 2 and 3 are half-rate samples */
+ if (ch & 2)
+ {
+ srcdata = &channel->base[channel->offset >> 1];
+
+ /* handle the edge case */
+ if (channel->offset & 1)
+ {
+ *dest++ += *srcdata++ * volume / 256;
+ samples--;
+ }
+
+ /* copy 1 for 2 to the destination */
+ for (i = 0; i < samples; i += 2)
+ {
+ INT16 sample = *srcdata++ * volume / 256;
+ *dest++ += sample;
+ *dest++ += sample;
+ }
+ }
+
+ /* channels 0 and 1 are full-rate samples */
+ else
+ {
+ srcdata = &channel->base[channel->offset];
+ for (i = 0; i < samples; i++)
+ *dest++ += *srcdata++ * volume / 256;
+ }
+}
+
+
+
+/*************************************
+ *
+ * Mix the result to 16 bits
+ *
+ *************************************/
+
+static void mix_to_16(device_t *device, int length, stream_sample_t *dest_left, stream_sample_t *dest_right)
+{
+ exidy440_audio_state *state = get_safe_token(device);
+ INT32 *mixer_left = state->mixer_buffer_left;
+ INT32 *mixer_right = state->mixer_buffer_right;
+ int i, clippers = 0;
+
+ for (i = 0; i < length; i++)
+ {
+ INT32 sample_left = *mixer_left++;
+ INT32 sample_right = *mixer_right++;
+
+ if (sample_left < -32768) { sample_left = -32768; clippers++; }
+ else if (sample_left > 32767) { sample_left = 32767; clippers++; }
+ if (sample_right < -32768) { sample_right = -32768; clippers++; }
+ else if (sample_right > 32767) { sample_right = 32767; clippers++; }
+
+ *dest_left++ = sample_left;
+ *dest_right++ = sample_right;
+ }
+}
+
+
+
+/*************************************
+ *
+ * Stream callback
+ *
+ *************************************/
+
+static STREAM_UPDATE( channel_update )
+{
+ exidy440_audio_state *state = get_safe_token(device);
+ int ch;
+
+ /* reset the mixer buffers */
+ memset(state->mixer_buffer_left, 0, samples * sizeof(INT32));
+ memset(state->mixer_buffer_right, 0, samples * sizeof(INT32));
+
+ /* loop over channels */
+ for (ch = 0; ch < 4; ch++)
+ {
+ sound_channel_data *channel = &state->sound_channel[ch];
+ int length, volume, left = samples;
+ int effective_offset;
+
+ /* if we're not active, bail */
+ if (channel->remaining <= 0)
+ continue;
+
+ /* see how many samples to copy */
+ length = (left > channel->remaining) ? channel->remaining : left;
+
+ /* get a pointer to the sample data and copy to the left */
+ volume = state->sound_volume[2 * ch + 0];
+ if (volume)
+ add_and_scale_samples(device, ch, state->mixer_buffer_left, length, volume);
+
+ /* get a pointer to the sample data and copy to the left */
+ volume = state->sound_volume[2 * ch + 1];
+ if (volume)
+ add_and_scale_samples(device, ch, state->mixer_buffer_right, length, volume);
+
+ /* update our counters */
+ channel->offset += length;
+ channel->remaining -= length;
+ left -= length;
+
+ /* update the MC6844 */
+ effective_offset = (ch & 2) ? channel->offset / 2 : channel->offset;
+ state->m6844_channel[ch].address = state->m6844_channel[ch].start_address + effective_offset / 8;
+ state->m6844_channel[ch].counter = state->m6844_channel[ch].start_counter - effective_offset / 8;
+ if (state->m6844_channel[ch].counter <= 0)
+ {
+ if (SOUND_LOG && state->debuglog)
+ fprintf(state->debuglog, "Channel %d finished\n", ch);
+ m6844_finished(&state->m6844_channel[ch]);
+ }
+ }
+
+ /* all done, time to mix it */
+ mix_to_16(device, samples, outputs[0], outputs[1]);
+}
+
+
+
+/*************************************
+ *
+ * Sound command register
+ *
+ *************************************/
+
+static READ8_DEVICE_HANDLER( sound_command_r )
+{
+ exidy440_audio_state *state = get_safe_token(device);
+ /* clear the FIRQ that got us here and acknowledge the read to the main CPU */
+ cputag_set_input_line(device->machine(), "audiocpu", 1, CLEAR_LINE);
+ state->sound_command_ack = 1;
+
+ return state->sound_command;
+}
+
+
+void exidy440_sound_command(device_t *device, UINT8 param)
+{
+ exidy440_audio_state *state = get_safe_token(device);
+ state->sound_command = param;
+ state->sound_command_ack = 0;
+ cputag_set_input_line(device->machine(), "audiocpu", INPUT_LINE_IRQ1, ASSERT_LINE);
+}
+
+
+UINT8 exidy440_sound_command_ack(device_t *device)
+{
+ exidy440_audio_state *state = get_safe_token(device);
+ return state->sound_command_ack;
+}
+
+
+
+/*************************************
+ *
+ * Sound volume registers
+ *
+ *************************************/
+
+static READ8_DEVICE_HANDLER( sound_volume_r )
+{
+ exidy440_audio_state *state = get_safe_token(device);
+ return state->sound_volume[offset];
+}
+
+static WRITE8_DEVICE_HANDLER( sound_volume_w )
+{
+ exidy440_audio_state *state = get_safe_token(device);
+ if (SOUND_LOG && state->debuglog)
+ fprintf(state->debuglog, "Volume %02X=%02X\n", offset, data);
+
+ /* update the stream */
+ state->stream->update();
+
+ /* set the new volume */
+ state->sound_volume[offset] = ~data;
+}
+
+
+
+/*************************************
+ *
+ * Sound interrupt handling
+ *
+ *************************************/
+
+static WRITE8_DEVICE_HANDLER( sound_interrupt_clear_w )
+{
+ cputag_set_input_line(device->machine(), "audiocpu", 0, CLEAR_LINE);
+}
+
+
+
+/*************************************
+ *
+ * MC6844 DMA controller interface
+ *
+ *************************************/
+
+static void m6844_update(device_t *device)
+{
+ exidy440_audio_state *state = get_safe_token(device);
+ /* update the stream */
+ state->stream->update();
+}
+
+
+static void m6844_finished(m6844_channel_data *channel)
+{
+ /* mark us inactive */
+ channel->active = 0;
+
+ /* set the final address and counter */
+ channel->counter = 0;
+ channel->address = channel->start_address + channel->start_counter;
+
+ /* clear the DMA busy bit and set the DMA end bit */
+ channel->control &= ~0x40;
+ channel->control |= 0x80;
+}
+
+
+
+/*************************************
+ *
+ * MC6844 DMA controller I/O
+ *
+ *************************************/
+
+static READ8_DEVICE_HANDLER( m6844_r )
+{
+ exidy440_audio_state *state = get_safe_token(device);
+ m6844_channel_data *m6844_channel = state->m6844_channel;
+ int result = 0;
+
+ /* first update the current state of the DMA transfers */
+ m6844_update(device);
+
+ /* switch off the offset we were given */
+ switch (offset)
+ {
+ /* upper byte of address */
+ case 0x00:
+ case 0x04:
+ case 0x08:
+ case 0x0c:
+ result = m6844_channel[offset / 4].address >> 8;
+ break;
+
+ /* lower byte of address */
+ case 0x01:
+ case 0x05:
+ case 0x09:
+ case 0x0d:
+ result = m6844_channel[offset / 4].address & 0xff;
+ break;
+
+ /* upper byte of counter */
+ case 0x02:
+ case 0x06:
+ case 0x0a:
+ case 0x0e:
+ result = m6844_channel[offset / 4].counter >> 8;
+ break;
+
+ /* lower byte of counter */
+ case 0x03:
+ case 0x07:
+ case 0x0b:
+ case 0x0f:
+ result = m6844_channel[offset / 4].counter & 0xff;
+ break;
+
+ /* channel control */
+ case 0x10:
+ case 0x11:
+ case 0x12:
+ case 0x13:
+ result = m6844_channel[offset - 0x10].control;
+
+ /* a read here clears the DMA end flag */
+ m6844_channel[offset - 0x10].control &= ~0x80;
+ break;
+
+ /* priority control */
+ case 0x14:
+ result = state->m6844_priority;
+ break;
+
+ /* interrupt control */
+ case 0x15:
+
+ /* update the global DMA end flag */
+ state->m6844_interrupt &= ~0x80;
+ state->m6844_interrupt |= (m6844_channel[0].control & 0x80) |
+ (m6844_channel[1].control & 0x80) |
+ (m6844_channel[2].control & 0x80) |
+ (m6844_channel[3].control & 0x80);
+
+ result = state->m6844_interrupt;
+ break;
+
+ /* chaining control */
+ case 0x16:
+ result = state->m6844_chain;
+ break;
+
+ /* 0x17-0x1f not used */
+ default: break;
+ }
+
+ return result;
+}
+
+
+static WRITE8_DEVICE_HANDLER( m6844_w )
+{
+ exidy440_audio_state *state = get_safe_token(device);
+ m6844_channel_data *m6844_channel = state->m6844_channel;
+ int i;
+
+ /* first update the current state of the DMA transfers */
+ m6844_update(device);
+
+ /* switch off the offset we were given */
+ switch (offset)
+ {
+ /* upper byte of address */
+ case 0x00:
+ case 0x04:
+ case 0x08:
+ case 0x0c:
+ m6844_channel[offset / 4].address = (m6844_channel[offset / 4].address & 0xff) | (data << 8);
+ break;
+
+ /* lower byte of address */
+ case 0x01:
+ case 0x05:
+ case 0x09:
+ case 0x0d:
+ m6844_channel[offset / 4].address = (m6844_channel[offset / 4].address & 0xff00) | (data & 0xff);
+ break;
+
+ /* upper byte of counter */
+ case 0x02:
+ case 0x06:
+ case 0x0a:
+ case 0x0e:
+ m6844_channel[offset / 4].counter = (m6844_channel[offset / 4].counter & 0xff) | (data << 8);
+ break;
+
+ /* lower byte of counter */
+ case 0x03:
+ case 0x07:
+ case 0x0b:
+ case 0x0f:
+ m6844_channel[offset / 4].counter = (m6844_channel[offset / 4].counter & 0xff00) | (data & 0xff);
+ break;
+
+ /* channel control */
+ case 0x10:
+ case 0x11:
+ case 0x12:
+ case 0x13:
+ m6844_channel[offset - 0x10].control = (m6844_channel[offset - 0x10].control & 0xc0) | (data & 0x3f);
+ break;
+
+ /* priority control */
+ case 0x14:
+ state->m6844_priority = data;
+
+ /* update the sound playback on each channel */
+ for (i = 0; i < 4; i++)
+ {
+ /* if we're going active... */
+ if (!m6844_channel[i].active && (data & (1 << i)))
+ {
+ /* mark us active */
+ m6844_channel[i].active = 1;
+
+ /* set the DMA busy bit and clear the DMA end bit */
+ m6844_channel[i].control |= 0x40;
+ m6844_channel[i].control &= ~0x80;
+
+ /* set the starting address, counter, and time */
+ m6844_channel[i].start_address = m6844_channel[i].address;
+ m6844_channel[i].start_counter = m6844_channel[i].counter;
+
+ /* generate and play the sample */
+ play_cvsd(device, i);
+ }
+
+ /* if we're going inactive... */
+ else if (m6844_channel[i].active && !(data & (1 << i)))
+ {
+ /* mark us inactive */
+ m6844_channel[i].active = 0;
+
+ /* stop playing the sample */
+ stop_cvsd(device, i);
+ }
+ }
+ break;
+
+ /* interrupt control */
+ case 0x15:
+ state->m6844_interrupt = (state->m6844_interrupt & 0x80) | (data & 0x7f);
+ break;
+
+ /* chaining control */
+ case 0x16:
+ state->m6844_chain = data;
+ break;
+
+ /* 0x17-0x1f not used */
+ default: break;
+ }
+}
+
+
+
+/*************************************
+ *
+ * Sound cache management
+ *
+ *************************************/
+
+static void reset_sound_cache(device_t *device)
+{
+ exidy440_audio_state *state = get_safe_token(device);
+ state->sound_cache_end = state->sound_cache;
+}
+
+
+static INT16 *add_to_sound_cache(device_t *device, UINT8 *input, int address, int length, int bits, int frequency)
+{
+ exidy440_audio_state *state = get_safe_token(device);
+ sound_cache_entry *current = state->sound_cache_end;
+
+ /* compute where the end will be once we add this entry */
+ state->sound_cache_end = (sound_cache_entry *)((UINT8 *)current + sizeof(sound_cache_entry) + length * 16);
+
+ /* if this will overflow the cache, reset and re-add */
+ if (state->sound_cache_end > state->sound_cache_max)
+ {
+ reset_sound_cache(device);
+ return add_to_sound_cache(device, input, address, length, bits, frequency);
+ }
+
+ /* fill in this entry */
+ current->next = state->sound_cache_end;
+ current->address = address;
+ current->length = length;
+ current->bits = bits;
+ current->frequency = frequency;
+
+ /* decode the data into the cache */
+ decode_and_filter_cvsd(device, input, length, bits, frequency, current->data);
+ return current->data;
+}
+
+
+static INT16 *find_or_add_to_sound_cache(device_t *device, int address, int length, int bits, int frequency)
+{
+ exidy440_audio_state *state = get_safe_token(device);
+ sound_cache_entry *current;
+
+ for (current = state->sound_cache; current < state->sound_cache_end; current = current->next)
+ if (current->address == address && current->length == length && current->bits == bits && current->frequency == frequency)
+ return current->data;
+
+ return add_to_sound_cache(device, &device->machine().root_device().memregion("cvsd")->base()[address], address, length, bits, frequency);
+}
+
+
+
+/*************************************
+ *
+ * Internal CVSD decoder and player
+ *
+ *************************************/
+
+static void play_cvsd(device_t *device, int ch)
+{
+ exidy440_audio_state *state = get_safe_token(device);
+ sound_channel_data *channel = &state->sound_channel[ch];
+ int address = state->m6844_channel[ch].address;
+ int length = state->m6844_channel[ch].counter;
+ INT16 *base;
+
+ /* add the bank number to the address */
+ if (state->sound_banks[ch] & 1)
+ address += 0x00000;
+ else if (state->sound_banks[ch] & 2)
+ address += 0x08000;
+ else if (state->sound_banks[ch] & 4)
+ address += 0x10000;
+ else if (state->sound_banks[ch] & 8)
+ address += 0x18000;
+
+ /* compute the base address in the converted samples array */
+ base = find_or_add_to_sound_cache(device, address, length, channel_bits[ch], state->channel_frequency[ch]);
+ if (!base)
+ return;
+
+ /* if the length is 0 or 1, just do an immediate end */
+ if (length <= 3)
+ {
+ channel->base = base;
+ channel->offset = length;
+ channel->remaining = 0;
+ m6844_finished(&state->m6844_channel[ch]);
+ return;
+ }
+
+ if (SOUND_LOG && state->debuglog)
+ fprintf(state->debuglog, "Sound channel %d play at %02X,%04X, length = %04X, volume = %02X/%02X\n",
+ ch, state->sound_banks[ch],
+ state->m6844_channel[ch].address, state->m6844_channel[ch].counter,
+ state->sound_volume[ch * 2], state->sound_volume[ch * 2 + 1]);
+
+ /* set the pointer and count */
+ channel->base = base;
+ channel->offset = 0;
+ channel->remaining = length * 8;
+
+ /* channels 2 and 3 play twice as slow, so we need to count twice as many samples */
+ if (ch & 2) channel->remaining *= 2;
+}
+
+
+static void stop_cvsd(device_t *device, int ch)
+{
+ exidy440_audio_state *state = get_safe_token(device);
+ /* the DMA channel is marked inactive; that will kill the audio */
+ state->sound_channel[ch].remaining = 0;
+ state->stream->update();
+
+ if (SOUND_LOG && state->debuglog)
+ fprintf(state->debuglog, "Channel %d stop\n", ch);
+}
+
+
+
+/*************************************
+ *
+ * FIR digital filter
+ *
+ *************************************/
+
+static void fir_filter(device_t *device, INT32 *input, INT16 *output, int count)
+{
+ while (count--)
+ {
+ INT32 result = (input[-1] - input[-8] - input[-48] + input[-55]) << 2;
+ result += (input[0] + input[-18] + input[-38] + input[-56]) << 3;
+ result += (-input[-2] - input[-4] + input[-5] + input[-51] - input[-52] - input[-54]) << 4;
+ result += (-input[-3] - input[-11] - input[-45] - input[-53]) << 5;
+ result += (input[-6] + input[-7] - input[-9] - input[-15] - input[-41] - input[-47] + input[-49] + input[-50]) << 6;
+ result += (-input[-10] + input[-12] + input[-13] + input[-14] + input[-21] + input[-35] + input[-42] + input[-43] + input[-44] - input[-46]) << 7;
+ result += (-input[-16] - input[-17] + input[-19] + input[-37] - input[-39] - input[-40]) << 8;
+ result += (input[-20] - input[-22] - input[-24] + input[-25] + input[-31] - input[-32] - input[-34] + input[-36]) << 9;
+ result += (-input[-23] - input[-33]) << 10;
+ result += (input[-26] + input[-30]) << 11;
+ result += (input[-27] + input[-28] + input[-29]) << 12;
+ result >>= 14;
+
+ if (result < -32768)
+ result = -32768;
+ else if (result > 32767)
+ result = 32767;
+
+ *output++ = result;
+ input++;
+ }
+}
+
+
+
+/*************************************
+ *
+ * CVSD decoder
+ *
+ *************************************/
+
+static void decode_and_filter_cvsd(device_t *device, UINT8 *input, int bytes, int maskbits, int frequency, INT16 *output)
+{
+ INT32 buffer[SAMPLE_BUFFER_LENGTH + FIR_HISTORY_LENGTH];
+ int total_samples = bytes * 8;
+ int mask = (1 << maskbits) - 1;
+ double filter, integrator, leak;
+ double charge, decay, gain;
+ int steps;
+ int chunk_start;
+
+ /* compute the charge, decay, and leak constants */
+ charge = pow(exp(-1.0), 1.0 / (FILTER_CHARGE_TC * (double)frequency));
+ decay = pow(exp(-1.0), 1.0 / (FILTER_DECAY_TC * (double)frequency));
+ leak = pow(exp(-1.0), 1.0 / (INTEGRATOR_LEAK_TC * (double)frequency));
+
+ /* compute the gain */
+ gain = SAMPLE_GAIN;
+
+ /* clear the history words for a start */
+ memset(&buffer[0], 0, FIR_HISTORY_LENGTH * sizeof(INT32));
+
+ /* initialize the CVSD decoder */
+ steps = 0xaa;
+ filter = FILTER_MIN;
+ integrator = 0.0;
+
+ /* loop over chunks */
+ for (chunk_start = 0; chunk_start < total_samples; chunk_start += SAMPLE_BUFFER_LENGTH)
+ {
+ INT32 *bufptr = &buffer[FIR_HISTORY_LENGTH];
+ int chunk_bytes;
+ int ind;
+
+ /* how many samples do we generate in this chunk? */
+ if (chunk_start + SAMPLE_BUFFER_LENGTH > total_samples)
+ chunk_bytes = (total_samples - chunk_start) / 8;
+ else
+ chunk_bytes = SAMPLE_BUFFER_LENGTH / 8;
+
+ /* loop over samples */
+ for (ind = 0; ind < chunk_bytes; ind++)
+ {
+ double temp;
+ int databyte = *input++;
+ int bit;
+ int sample;
+
+ /* loop over bits in the byte, low to high */
+ for (bit = 0; bit < 8; bit++)
+ {
+ /* move the estimator up or down a step based on the bit */
+ if (databyte & (1 << bit))
+ {
+ integrator += filter;
+ steps = (steps << 1) | 1;
+ }
+ else
+ {
+ integrator -= filter;
+ steps <<= 1;
+ }
+
+ /* keep track of the last n bits */
+ steps &= mask;
+
+ /* simulate leakage */
+ integrator *= leak;
+
+ /* if we got all 0's or all 1's in the last n bits, bump the step up */
+ if (steps == 0 || steps == mask)
+ {
+ filter = FILTER_MAX - ((FILTER_MAX - filter) * charge);
+ if (filter > FILTER_MAX)
+ filter = FILTER_MAX;
+ }
+
+ /* simulate decay */
+ else
+ {
+ filter *= decay;
+ if (filter < FILTER_MIN)
+ filter = FILTER_MIN;
+ }
+
+ /* compute the sample as a 32-bit word */
+ temp = integrator * gain;
+
+ /* compress the sample range to fit better in a 16-bit word */
+ if (temp < 0)
+ sample = (int)(temp / (-temp * (1.0 / 32768.0) + 1.0));
+ else
+ sample = (int)(temp / (temp * (1.0 / 32768.0) + 1.0));
+
+ /* store the result to our temporary buffer */
+ *bufptr++ = sample;
+ }
+ }
+
+ /* all done with this chunk, run the filter on it */
+ fir_filter(device, &buffer[FIR_HISTORY_LENGTH], &output[chunk_start], chunk_bytes * 8);
+
+ /* copy the last few input samples down to the start for a new history */
+ memcpy(&buffer[0], &buffer[SAMPLE_BUFFER_LENGTH], FIR_HISTORY_LENGTH * sizeof(INT32));
+ }
+
+ /* make sure the volume goes smoothly to 0 over the last 512 samples */
+ if (FADE_TO_ZERO)
+ {
+ INT16 *data;
+
+ chunk_start = (total_samples > 512) ? total_samples - 512 : 0;
+ data = output + chunk_start;
+ for ( ; chunk_start < total_samples; chunk_start++)
+ {
+ *data = (*data * ((total_samples - chunk_start) >> 9));
+ data++;
+ }
+ }
+}
+
+
+static WRITE8_DEVICE_HANDLER( sound_banks_w )
+{
+ exidy440_audio_state *state = get_safe_token(device);
+ state->sound_banks[offset] = data;
+}
+
+
+/*************************************
+ *
+ * Memory handlers
+ *
+ *************************************/
+
+static ADDRESS_MAP_START( exidy440_audio_map, AS_PROGRAM, 8, driver_device )
+ AM_RANGE(0x0000, 0x7fff) AM_NOP
+ AM_RANGE(0x8000, 0x801f) AM_MIRROR(0x03e0) AM_DEVREADWRITE_LEGACY("custom", m6844_r, m6844_w)
+ AM_RANGE(0x8400, 0x840f) AM_MIRROR(0x03f0) AM_DEVREADWRITE_LEGACY("custom", sound_volume_r, sound_volume_w)
+ AM_RANGE(0x8800, 0x8800) AM_MIRROR(0x03ff) AM_DEVREAD_LEGACY("custom", sound_command_r) AM_WRITENOP
+ AM_RANGE(0x8c00, 0x93ff) AM_NOP
+ AM_RANGE(0x9400, 0x9403) AM_MIRROR(0x03fc) AM_READNOP AM_DEVWRITE_LEGACY("custom", sound_banks_w)
+ AM_RANGE(0x9800, 0x9800) AM_MIRROR(0x03ff) AM_READNOP AM_DEVWRITE_LEGACY("custom", sound_interrupt_clear_w)
+ AM_RANGE(0x9c00, 0x9fff) AM_NOP
+ AM_RANGE(0xa000, 0xbfff) AM_RAM
+ AM_RANGE(0xc000, 0xdfff) AM_NOP
+ AM_RANGE(0xe000, 0xffff) AM_ROM
+ADDRESS_MAP_END
+
+
+
+/*************************************
+ *
+ * Audio definitions
+ *
+ *************************************/
+
+DEVICE_GET_INFO( exidy440_sound )
+{
+ switch (state)
+ {
+ /* --- the following bits of info are returned as 64-bit signed integers --- */
+ case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(exidy440_audio_state); break;
+
+ /* --- the following bits of info are returned as pointers to data or functions --- */
+ case DEVINFO_FCT_START: info->start = DEVICE_START_NAME(exidy440_sound); break;
+ case DEVINFO_FCT_STOP: info->stop = DEVICE_STOP_NAME(exidy440_sound); break;
+
+ /* --- the following bits of info are returned as NULL-terminated strings --- */
+ case DEVINFO_STR_NAME: strcpy(info->s, "Exidy 440 CVSD"); break;
+ case DEVINFO_STR_SOURCE_FILE: strcpy(info->s, __FILE__); break;
+ }
+}
+
+
+DEFINE_LEGACY_SOUND_DEVICE(EXIDY440, exidy440_sound);
+
+
+
+/*************************************
+ *
+ * Machine driver
+ *
+ *************************************/
+
+MACHINE_CONFIG_FRAGMENT( exidy440_audio )
+
+ MCFG_CPU_ADD("audiocpu", M6809, EXIDY440_AUDIO_CLOCK)
+ MCFG_CPU_PROGRAM_MAP(exidy440_audio_map)
+ MCFG_CPU_VBLANK_INT("screen", irq0_line_assert)
+
+ MCFG_SPEAKER_STANDARD_STEREO("lspeaker", "rspeaker")
+
+ MCFG_SOUND_ADD("custom", EXIDY440, EXIDY440_AUDIO_CLOCK/16)
+ MCFG_SOUND_ROUTE(0, "lspeaker", 1.0)
+ MCFG_SOUND_ROUTE(1, "rspeaker", 1.0)
+
+// MCFG_SOUND_ADD("cvsd1", MC3418, EXIDY440_MC3418_CLOCK)
+// MCFG_SOUND_ROUTE(ALL_OUTPUTS, "lspeaker", 1.0)
+
+// MCFG_SOUND_ADD("cvsd2", MC3418, EXIDY440_MC3418_CLOCK)
+// MCFG_SOUND_ROUTE(ALL_OUTPUTS, "rspeaker", 1.0)
+
+// MCFG_SOUND_ADD("cvsd3", MC3417, EXIDY440_MC3417_CLOCK)
+// MCFG_SOUND_ROUTE(ALL_OUTPUTS, "lspeaker", 1.0)
+
+// MCFG_SOUND_ADD("cvsd4", MC3417, EXIDY440_MC3417_CLOCK)
+// MCFG_SOUND_ROUTE(ALL_OUTPUTS, "rspeaker", 1.0)
+MACHINE_CONFIG_END