summaryrefslogtreecommitdiffstatshomepage
path: root/src/mame/audio/exidy440.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mame/audio/exidy440.c')
-rw-r--r--src/mame/audio/exidy440.c981
1 files changed, 981 insertions, 0 deletions
diff --git a/src/mame/audio/exidy440.c b/src/mame/audio/exidy440.c
new file mode 100644
index 00000000000..21b7e181302
--- /dev/null
+++ b/src/mame/audio/exidy440.c
@@ -0,0 +1,981 @@
+/***************************************************************************
+
+ 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 "driver.h"
+#include "streams.h"
+#include "exidy440.h"
+#include <math.h>
+
+
+#define MAKE_WAVES 0
+#define SOUND_LOG 0
+#define FADE_TO_ZERO 1
+
+
+
+/* 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;
+
+
+
+/* globals */
+UINT8 *exidy440_m6844_data;
+UINT8 *exidy440_sound_banks;
+UINT8 *exidy440_sound_volume;
+UINT8 exidy440_sound_command;
+UINT8 exidy440_sound_command_ack;
+
+/* local allocated storage */
+static INT32 *mixer_buffer_left;
+static INT32 *mixer_buffer_right;
+static sound_cache_entry *sound_cache;
+static sound_cache_entry *sound_cache_end;
+static sound_cache_entry *sound_cache_max;
+
+/* 6844 description */
+static m6844_channel_data m6844_channel[4];
+static UINT8 m6844_priority;
+static UINT8 m6844_interrupt;
+static UINT8 m6844_chain;
+
+/* sound interface parameters */
+static sound_stream *stream;
+static sound_channel_data sound_channel[4];
+
+/* debugging */
+static FILE *debuglog;
+
+/* channel frequency is configurable */
+static int channel_frequency[4];
+
+/* 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 void channel_update(void *param, stream_sample_t **inputs, stream_sample_t **outputs, int length);
+static void m6844_finished(int ch);
+static void play_cvsd(int ch);
+static void stop_cvsd(int ch);
+
+static void reset_sound_cache(void);
+static INT16 *add_to_sound_cache(UINT8 *input, int address, int length, int bits, int frequency);
+static INT16 *find_or_add_to_sound_cache(int address, int length, int bits, int frequency);
+
+static void decode_and_filter_cvsd(UINT8 *data, int bytes, int maskbits, int frequency, INT16 *dest);
+static void fir_filter(INT32 *input, INT16 *output, int count);
+
+
+/* debugging */
+#if MAKE_WAVES
+
+#ifdef LSB_FIRST
+#define intelShort(x) (x)
+#define intelLong(x) (x)
+#else
+#define intelShort(x) (((x) << 8) | ((x) >> 8))
+#define intelLong(x) ((((x) << 24) | (((UINT32) (x)) >> 24) | (( (x) & 0x0000ff00) << 8) | (( (x) & 0x00ff0000) >> 8)))
+#endif
+
+static FILE *wavfile;
+static int wavlength;
+
+static void write_wav_header(int frequency);
+static void finish_wav_file(void);
+
+#endif
+
+
+
+/*************************************
+ *
+ * Initialize the sound system
+ *
+ *************************************/
+
+void *exidy440_sh_start(int clock, const struct CustomSound_interface *config)
+{
+ int i, length;
+
+ /* reset the system */
+ exidy440_sound_command = 0;
+ exidy440_sound_command_ack = 1;
+ state_save_register_global(exidy440_sound_command);
+ state_save_register_global(exidy440_sound_command_ack);
+
+ /* reset the 6844 */
+ for (i = 0; i < 4; i++)
+ {
+ m6844_channel[i].active = 0;
+ m6844_channel[i].control = 0x00;
+ }
+ m6844_priority = 0x00;
+ m6844_interrupt = 0x00;
+ m6844_chain = 0x00;
+
+ state_save_register_global(m6844_priority);
+ state_save_register_global(m6844_interrupt);
+ state_save_register_global(m6844_chain);
+
+ channel_frequency[0] = clock; /* channels 0 and 1 are run by FCLK */
+ channel_frequency[1] = clock;
+ channel_frequency[2] = clock/2; /* channels 2 and 3 are run by SCLK */
+ channel_frequency[3] = clock/2;
+
+ /* get stream channels */
+ stream = stream_create(0, 2, clock, NULL, channel_update);
+
+ /* allocate the sample cache */
+ length = memory_region_length(REGION_SOUND1) * 16 + MAX_CACHE_ENTRIES * sizeof(sound_cache_entry);
+ sound_cache = auto_malloc(length);
+
+ /* determine the hard end of the cache and reset */
+ sound_cache_max = (sound_cache_entry *)((UINT8 *)sound_cache + length);
+ reset_sound_cache();
+
+ /* allocate the mixer buffer */
+ mixer_buffer_left = auto_malloc(2 * clock * sizeof(INT32));
+ mixer_buffer_right = mixer_buffer_left + clock;
+
+ if (SOUND_LOG)
+ debuglog = fopen("sound.log", "w");
+
+ return auto_malloc(1);
+}
+
+
+
+/*************************************
+ *
+ * Tear down the sound system
+ *
+ *************************************/
+
+void exidy440_sh_stop(void *token)
+{
+ if (SOUND_LOG && debuglog)
+ fclose(debuglog);
+}
+
+
+
+/*************************************
+ *
+ * Add a bunch of samples to the mix
+ *
+ *************************************/
+
+static void add_and_scale_samples(int ch, INT32 *dest, int samples, int volume)
+{
+ sound_channel_data *channel = &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(int length, stream_sample_t *dest_left, stream_sample_t *dest_right)
+{
+ INT32 *mixer_left = mixer_buffer_left;
+ INT32 *mixer_right = 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 void channel_update(void *param, stream_sample_t **inputs, stream_sample_t **outputs, int length)
+{
+ int ch;
+
+ /* reset the mixer buffers */
+ memset(mixer_buffer_left, 0, length * sizeof(INT32));
+ memset(mixer_buffer_right, 0, length * sizeof(INT32));
+
+ /* loop over channels */
+ for (ch = 0; ch < 4; ch++)
+ {
+ sound_channel_data *channel = &sound_channel[ch];
+ int samples, volume, left = length;
+ int effective_offset;
+
+ /* if we're not active, bail */
+ if (channel->remaining <= 0)
+ continue;
+
+ /* see how many samples to copy */
+ samples = (left > channel->remaining) ? channel->remaining : left;
+
+ /* get a pointer to the sample data and copy to the left */
+ volume = exidy440_sound_volume[2 * ch + 0];
+ if (volume)
+ add_and_scale_samples(ch, mixer_buffer_left, samples, volume);
+
+ /* get a pointer to the sample data and copy to the left */
+ volume = exidy440_sound_volume[2 * ch + 1];
+ if (volume)
+ add_and_scale_samples(ch, mixer_buffer_right, samples, volume);
+
+ /* update our counters */
+ channel->offset += samples;
+ channel->remaining -= samples;
+ left -= samples;
+
+ /* update the MC6844 */
+ effective_offset = (ch & 2) ? channel->offset / 2 : channel->offset;
+ m6844_channel[ch].address = m6844_channel[ch].start_address + effective_offset / 8;
+ m6844_channel[ch].counter = m6844_channel[ch].start_counter - effective_offset / 8;
+ if (m6844_channel[ch].counter <= 0)
+ {
+ if (SOUND_LOG && debuglog)
+ fprintf(debuglog, "Channel %d finished\n", ch);
+ m6844_finished(ch);
+ }
+ }
+
+ /* all done, time to mix it */
+ mix_to_16(length, outputs[0], outputs[1]);
+}
+
+
+
+/*************************************
+ *
+ * Sound command register
+ *
+ *************************************/
+
+READ8_HANDLER( exidy440_sound_command_r )
+{
+ /* clear the FIRQ that got us here and acknowledge the read to the main CPU */
+ cpunum_set_input_line(1, 1, CLEAR_LINE);
+ exidy440_sound_command_ack = 1;
+
+ return exidy440_sound_command;
+}
+
+
+
+/*************************************
+ *
+ * Sound volume registers
+ *
+ *************************************/
+
+WRITE8_HANDLER( exidy440_sound_volume_w )
+{
+ if (SOUND_LOG && debuglog)
+ fprintf(debuglog, "Volume %02X=%02X\n", offset, data);
+
+ /* update the stream */
+ stream_update(stream);
+
+ /* set the new volume */
+ exidy440_sound_volume[offset] = ~data;
+}
+
+
+
+/*************************************
+ *
+ * Sound interrupt handling
+ *
+ *************************************/
+
+WRITE8_HANDLER( exidy440_sound_interrupt_clear_w )
+{
+ cpunum_set_input_line(1, 0, CLEAR_LINE);
+}
+
+
+
+/*************************************
+ *
+ * MC6844 DMA controller interface
+ *
+ *************************************/
+
+static void exidy440_m6844_update(void)
+{
+ /* update the stream */
+ stream_update(stream);
+}
+
+
+void m6844_finished(int ch)
+{
+ m6844_channel_data *channel = &m6844_channel[ch];
+
+ /* 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
+ *
+ *************************************/
+
+READ8_HANDLER( exidy440_m6844_r )
+{
+ int result = 0;
+
+ /* first update the current state of the DMA transfers */
+ exidy440_m6844_update();
+
+ /* 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 = m6844_priority;
+ break;
+
+ /* interrupt control */
+ case 0x15:
+
+ /* update the global DMA end flag */
+ m6844_interrupt &= ~0x80;
+ m6844_interrupt |= (m6844_channel[0].control & 0x80) |
+ (m6844_channel[1].control & 0x80) |
+ (m6844_channel[2].control & 0x80) |
+ (m6844_channel[3].control & 0x80);
+
+ result = m6844_interrupt;
+ break;
+
+ /* chaining control */
+ case 0x16:
+ result = m6844_chain;
+ break;
+ }
+
+ return result;
+}
+
+
+WRITE8_HANDLER( exidy440_m6844_w )
+{
+ int i;
+
+ /* first update the current state of the DMA transfers */
+ exidy440_m6844_update();
+
+ /* 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:
+ 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(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(i);
+ }
+ }
+ break;
+
+ /* interrupt control */
+ case 0x15:
+ m6844_interrupt = (m6844_interrupt & 0x80) | (data & 0x7f);
+ break;
+
+ /* chaining control */
+ case 0x16:
+ m6844_chain = data;
+ break;
+ }
+}
+
+
+
+/*************************************
+ *
+ * Sound cache management
+ *
+ *************************************/
+
+void reset_sound_cache(void)
+{
+ sound_cache_end = sound_cache;
+}
+
+
+INT16 *add_to_sound_cache(UINT8 *input, int address, int length, int bits, int frequency)
+{
+ sound_cache_entry *current = sound_cache_end;
+
+ /* compute where the end will be once we add this entry */
+ 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 (sound_cache_end > sound_cache_max)
+ {
+ reset_sound_cache();
+ return add_to_sound_cache(input, address, length, bits, frequency);
+ }
+
+ /* fill in this entry */
+ current->next = 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(input, length, bits, frequency, current->data);
+ return current->data;
+}
+
+
+INT16 *find_or_add_to_sound_cache(int address, int length, int bits, int frequency)
+{
+ sound_cache_entry *current;
+
+ for (current = sound_cache; current < 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(&memory_region(REGION_SOUND1)[address], address, length, bits, frequency);
+}
+
+
+
+/*************************************
+ *
+ * Internal CVSD decoder and player
+ *
+ *************************************/
+
+void play_cvsd(int ch)
+{
+ sound_channel_data *channel = &sound_channel[ch];
+ int address = m6844_channel[ch].address;
+ int length = m6844_channel[ch].counter;
+ INT16 *base;
+
+ /* add the bank number to the address */
+ if (exidy440_sound_banks[ch] & 1)
+ address += 0x00000;
+ else if (exidy440_sound_banks[ch] & 2)
+ address += 0x08000;
+ else if (exidy440_sound_banks[ch] & 4)
+ address += 0x10000;
+ else if (exidy440_sound_banks[ch] & 8)
+ address += 0x18000;
+
+ /* compute the base address in the converted samples array */
+ base = find_or_add_to_sound_cache(address, length, channel_bits[ch], 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(ch);
+ return;
+ }
+
+ if (SOUND_LOG && debuglog)
+ fprintf(debuglog, "Sound channel %d play at %02X,%04X, length = %04X, volume = %02X/%02X\n",
+ ch, exidy440_sound_banks[ch], m6844_channel[ch].address,
+ m6844_channel[ch].counter, exidy440_sound_volume[ch * 2], exidy440_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;
+}
+
+
+void stop_cvsd(int ch)
+{
+ /* the DMA channel is marked inactive; that will kill the audio */
+ sound_channel[ch].remaining = 0;
+ stream_update(stream);
+
+ if (SOUND_LOG && debuglog)
+ fprintf(debuglog, "Channel %d stop\n", ch);
+}
+
+
+
+/*************************************
+ *
+ * FIR digital filter
+ *
+ *************************************/
+
+void fir_filter(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
+ *
+ *************************************/
+
+void decode_and_filter_cvsd(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;
+
+#if MAKE_WAVES
+{
+ static int file_index;
+ char file_name[100];
+ sprintf(file_name, "cvsd%03d.wav", file_index++);
+ wavfile = fopen(file_name, "wb");
+ write_wav_header(frequency);
+}
+#endif
+
+ /* compute the charge, decay, and leak constants */
+ charge = pow(exp(-1), 1.0 / (FILTER_CHARGE_TC * (double)frequency));
+ decay = pow(exp(-1), 1.0 / (FILTER_DECAY_TC * (double)frequency));
+ leak = pow(exp(-1), 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;
+ }
+ }
+
+#if MAKE_WAVES
+ for (ind = 0; ind < chunk_bytes * 8; ind++)
+ {
+ int sample = buffer[FIR_HISTORY_LENGTH + ind];
+ INT16 temp;
+ if (sample > 32767) sample = 32767;
+ else if (sample < -32768) sample = -32768;
+ temp = intelShort(sample);
+ fwrite(&temp, 1, 2, wavfile);
+ }
+ wavlength += chunk_bytes * 8 * 2;
+#endif
+
+ /* all done with this chunk, run the filter on it */
+ fir_filter(&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));
+ }
+
+#if MAKE_WAVES
+ finish_wav_file();
+ fclose(wavfile);
+#endif
+
+ /* 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++;
+ }
+ }
+}
+
+
+
+/*************************************
+ *
+ * Debugging
+ *
+ *************************************/
+
+#if MAKE_WAVES
+
+static void write_wav_header(int frequency)
+{
+ UINT32 temp32;
+ UINT16 temp16;
+
+ fwrite("RIFF", 1, 4, wavfile);
+ temp32 = intelLong(0);
+ fwrite(&temp32, 1, 4, wavfile);
+ fwrite("WAVE", 1, 4, wavfile);
+
+ fwrite("fmt ", 1, 4, wavfile);
+ temp32 = intelLong(16);
+ fwrite(&temp32, 1, 4, wavfile);
+ temp16 = intelShort(1); /* format: PCM */
+ fwrite(&temp16, 1, 2, wavfile);
+ temp16 = intelShort(1); /* channels: 1 */
+ fwrite(&temp16, 1, 2, wavfile);
+ temp32 = intelLong(frequency); /* sample rate */
+ fwrite(&temp32, 1, 4, wavfile);
+ temp32 = intelLong(frequency * 2); /* bytes/second */
+ fwrite(&temp32, 1, 4, wavfile);
+ temp16 = intelShort(2); /* block align */
+ fwrite(&temp16, 1, 2, wavfile);
+ temp16 = intelShort(16); /* bits/sample */
+ fwrite(&temp16, 1, 2, wavfile);
+
+ fwrite("data", 1, 4, wavfile);
+ temp32 = intelLong(0);
+ fwrite(&temp32, 1, 4, wavfile);
+}
+
+
+static void finish_wav_file(void)
+{
+ UINT32 temp32;
+
+ fseek(wavfile, 4, SEEK_SET);
+ temp32 = intelLong(wavlength + 4 + 8 + 16 + 8);
+ fwrite(&temp32, 1, 4, wavfile);
+
+ fseek(wavfile, 40, SEEK_SET);
+ temp32 = intelLong(wavlength);
+ fwrite(&temp32, 1, 4, wavfile);
+}
+
+#endif