// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/***************************************************************************
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 "audio/exidy440.h"
#include "includes/exidy440.h"
#include "cpu/m6809/m6809.h"
#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
/* 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 */
};
/*************************************
*
* Audio CPU memory map
*
*************************************/
void exidy440_sound_device::exidy440_audio_map(address_map &map)
{
map(0x0000, 0x7fff).noprw();
map(0x8000, 0x801f).mirror(0x03e0).rw(FUNC(exidy440_sound_device::m6844_r), FUNC(exidy440_sound_device::m6844_w));
map(0x8400, 0x840f).mirror(0x03f0).rw(FUNC(exidy440_sound_device::sound_volume_r), FUNC(exidy440_sound_device::sound_volume_w));
map(0x8800, 0x8800).mirror(0x03ff).r(FUNC(exidy440_sound_device::sound_command_r)).nopw();
map(0x8c00, 0x93ff).noprw();
map(0x9400, 0x9403).mirror(0x03fc).nopr().w(FUNC(exidy440_sound_device::sound_banks_w));
map(0x9800, 0x9800).mirror(0x03ff).nopr().w(FUNC(exidy440_sound_device::sound_interrupt_clear_w));
map(0x9c00, 0x9fff).noprw();
map(0xa000, 0xbfff).ram();
map(0xc000, 0xdfff).noprw();
map(0xe000, 0xffff).rom().region("audiocpu", 0);
}
DEFINE_DEVICE_TYPE(EXIDY440, exidy440_sound_device, "exidy440_sound", "Exidy 440 CVSD")
exidy440_sound_device::exidy440_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, EXIDY440, tag, owner, clock),
device_sound_interface(mconfig, *this),
m_audiocpu(*this, "audiocpu"),
m_samples(*this, "samples"),
m_sound_command(0),
m_sound_command_ack(0),
m_mixer_buffer_left(nullptr),
m_mixer_buffer_right(nullptr),
m_sound_cache(nullptr),
m_sound_cache_end(nullptr),
m_sound_cache_max(nullptr),
m_m6844_priority(0x00),
m_m6844_interrupt(0x00),
m_m6844_chain(0x00),
m_stream(nullptr)
{
m_sound_banks[0] = m_sound_banks[1] = m_sound_banks[2] = m_sound_banks[3] = 0;
for (auto & elem : m_sound_channel)
{
elem.base = nullptr;
elem.offset = 0;
elem.remaining = 0;
}
}
//-------------------------------------------------
// device_add_mconfig - add device configuration
//-------------------------------------------------
void exidy440_sound_device::device_add_mconfig(machine_config &config)
{
MC6809(config, m_audiocpu, EXIDY440_AUDIO_CLOCK);
m_audiocpu->set_addrmap(AS_PROGRAM, &exidy440_sound_device::exidy440_audio_map);
// MC3418(config, "cvsd1", EXIDY440_MC3418_CLOCK).add_route(ALL_OUTPUTS, "lspeaker", 1.0);
// MC3418(config, "cvsd2", EXIDY440_MC3418_CLOCK).add_route(ALL_OUTPUTS, "rspeaker", 1.0);
// MC3417(config, "cvsd3", EXIDY440_MC3417_CLOCK).add_route(ALL_OUTPUTS, "lspeaker", 1.0);
// MC3417(config, "cvsd4", EXIDY440_MC3417_CLOCK).add_route(ALL_OUTPUTS, "rspeaker", 1.0);
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void exidy440_sound_device::device_start()
{
int i, length;
/* reset the system */
m_sound_command = 0;
m_sound_command_ack = 1;
save_item(NAME(m_sound_command));
save_item(NAME(m_sound_command_ack));
/* reset the 6844 */
for (i = 0; i < 4; i++)
{
m_m6844_channel[i].active = 0;
m_m6844_channel[i].control = 0x00;
}
m_m6844_priority = 0x00;
m_m6844_interrupt = 0x00;
m_m6844_chain = 0x00;
save_item(NAME(m_m6844_priority));
save_item(NAME(m_m6844_interrupt));
save_item(NAME(m_m6844_chain));
m_channel_frequency[0] = clock(); /* channels 0 and 1 are run by FCLK */
m_channel_frequency[1] = clock();
m_channel_frequency[2] = clock()/2; /* channels 2 and 3 are run by SCLK */
m_channel_frequency[3] = clock()/2;
/* get stream channels */
m_stream = machine().sound().stream_alloc(*this, 0, 2, clock());
/* allocate the sample cache */
length = m_samples.bytes() * 16 + MAX_CACHE_ENTRIES * sizeof(sound_cache_entry);
m_sound_cache = (sound_cache_entry *)auto_alloc_array_clear(machine(), uint8_t, length);
/* determine the hard end of the cache and reset */
m_sound_cache_max = (sound_cache_entry *)((uint8_t *)m_sound_cache + length);
reset_sound_cache();
/* allocate the mixer buffer */
m_mixer_buffer_left = make_unique_clear<int32_t[]>(clock());
m_mixer_buffer_right = make_unique_clear<int32_t[]>(clock());
if (SOUND_LOG)
m_debuglog = fopen("sound.log", "w");
}
//-------------------------------------------------
// device_stop - device-specific stop
//-------------------------------------------------
void exidy440_sound_device::device_stop()
{
if (SOUND_LOG && m_debuglog)
fclose(m_debuglog);
}
/*************************************
*
* Add a bunch of samples to the mix
*
*************************************/
void exidy440_sound_device::add_and_scale_samples(int ch, int32_t *dest, int samples, int volume)
{
sound_channel_data *channel = &m_sound_channel[ch];
int16_t *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_t 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
*
*************************************/
void exidy440_sound_device::mix_to_16(int length, stream_sample_t *dest_left, stream_sample_t *dest_right)
{
int32_t *mixer_left = m_mixer_buffer_left.get();
int32_t *mixer_right = m_mixer_buffer_right.get();
int i, clippers = 0;
for (i = 0; i < length; i++)
{
int32_t sample_left = *mixer_left++;
int32_t 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;
}
}
/*************************************
*
* Sound command register
*
*************************************/
READ8_MEMBER(exidy440_sound_device::sound_command_r)
{
/* clear the FIRQ that got us here and acknowledge the read to the main CPU */
m_audiocpu->set_input_line(M6809_FIRQ_LINE, CLEAR_LINE);
m_sound_command_ack = 1;
return m_sound_command;
}
void exidy440_sound_device::exidy440_sound_command(uint8_t param)
{
m_sound_command = param;
m_sound_command_ack = 0;
m_audiocpu->set_input_line(M6809_FIRQ_LINE, ASSERT_LINE);
}
uint8_t exidy440_sound_device::exidy440_sound_command_ack()
{
return m_sound_command_ack;
}
/*************************************
*
* Sound volume registers
*
*************************************/
READ8_MEMBER(exidy440_sound_device::sound_volume_r)
{
return m_sound_volume[offset];
}
WRITE8_MEMBER(exidy440_sound_device::sound_volume_w)
{
if (SOUND_LOG && m_debuglog)
fprintf(m_debuglog, "Volume %02X=%02X\n", offset, data);
/* update the stream */
m_stream->update();
/* set the new volume */
m_sound_volume[offset] = ~data;
}
/*************************************
*
* Sound interrupt handling
*
*************************************/
WRITE_LINE_MEMBER(exidy440_sound_device::sound_interrupt_w)
{
if (state)
m_audiocpu->set_input_line(M6809_IRQ_LINE, ASSERT_LINE);
}
WRITE8_MEMBER(exidy440_sound_device::sound_interrupt_clear_w)
{
m_audiocpu->set_input_line(M6809_IRQ_LINE, CLEAR_LINE);
}
WRITE_LINE_MEMBER(exidy440_sound_device::sound_reset_w)
{
m_audiocpu->set_input_line(INPUT_LINE_RESET, state);
}
/*************************************
*
* MC6844 DMA controller interface
*
*************************************/
void exidy440_sound_device::m6844_update()
{
/* update the stream */
m_stream->update();
}
void exidy440_sound_device::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
*
*************************************/
READ8_MEMBER(exidy440_sound_device::m6844_r)
{
m6844_channel_data *m6844_channel = m_m6844_channel;
int result = 0;
/* first update the current state of the DMA transfers */
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 = m_m6844_priority;
break;
/* interrupt control */
case 0x15:
/* update the global DMA end flag */
m_m6844_interrupt &= ~0x80;
m_m6844_interrupt |= (m6844_channel[0].control & 0x80) |
(m6844_channel[1].control & 0x80) |
(m6844_channel[2].control & 0x80) |
(m6844_channel[3].control & 0x80);
result = m_m6844_interrupt;
break;
/* chaining control */
case 0x16:
result = m_m6844_chain;
break;
/* 0x17-0x1f not used */
default: break;
}
return result;
}
WRITE8_MEMBER(exidy440_sound_device::m6844_w)
{
m6844_channel_data *m6844_channel = m_m6844_channel;
int i;
/* first update the current state of the DMA transfers */
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:
m_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:
m_m6844_interrupt = (m_m6844_interrupt & 0x80) | (data & 0x7f);
break;
/* chaining control */
case 0x16:
m_m6844_chain = data;
break;
/* 0x17-0x1f not used */
default: break;
}
}
/*************************************
*
* Sound cache management
*
*************************************/
void exidy440_sound_device::reset_sound_cache()
{
m_sound_cache_end = m_sound_cache;
}
int16_t *exidy440_sound_device::add_to_sound_cache(uint8_t *input, int address, int length, int bits, int frequency)
{
sound_cache_entry *current = m_sound_cache_end;
/* compute where the end will be once we add this entry */
m_sound_cache_end = (sound_cache_entry *)((uint8_t *)current + sizeof(sound_cache_entry) + length * 16);
/* if this will overflow the cache, reset and re-add */
if (m_sound_cache_end > m_sound_cache_max)
{
reset_sound_cache();
return add_to_sound_cache(input, address, length, bits, frequency);
}
/* fill in this entry */
current->next = m_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_t *exidy440_sound_device::find_or_add_to_sound_cache(int address, int length, int bits, int frequency)
{
sound_cache_entry *current;
for (current = m_sound_cache; current < m_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(&m_samples[address], address, length, bits, frequency);
}
/*************************************
*
* Internal CVSD decoder and player
*
*************************************/
void exidy440_sound_device::play_cvsd(int ch)
{
sound_channel_data *channel = &m_sound_channel[ch];
int address = m_m6844_channel[ch].address;
int length = m_m6844_channel[ch].counter;
int16_t *base;
/* add the bank number to the address */
if (m_sound_banks[ch] & 1)
address += 0x00000;
else if (m_sound_banks[ch] & 2)
address += 0x08000;
else if (m_sound_banks[ch] & 4)
address += 0x10000;
else if (m_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], m_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(&m_m6844_channel[ch]);
return;
}
if (SOUND_LOG && m_debuglog)
fprintf(m_debuglog, "Sound channel %d play at %02X,%04X, length = %04X, volume = %02X/%02X\n",
ch, m_sound_banks[ch],
m_m6844_channel[ch].address, m_m6844_channel[ch].counter,
m_sound_volume[ch * 2], m_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 exidy440_sound_device::stop_cvsd(int ch)
{
/* the DMA channel is marked inactive; that will kill the audio */
m_sound_channel[ch].remaining = 0;
m_stream->update();
if (SOUND_LOG && m_debuglog)
fprintf(m_debuglog, "Channel %d stop\n", ch);
}
/*************************************
*
* FIR digital filter
*
*************************************/
void exidy440_sound_device::fir_filter(int32_t *input, int16_t *output, int count)
{
while (count--)
{
int32_t 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 exidy440_sound_device::decode_and_filter_cvsd(uint8_t *input, int bytes, int maskbits, int frequency, int16_t *output)
{
int32_t 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_t));
/* 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_t *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(&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_t));
}
/* make sure the volume goes smoothly to 0 over the last 512 samples */
if (FADE_TO_ZERO)
{
int16_t *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++;
}
}
}
WRITE8_MEMBER(exidy440_sound_device::sound_banks_w)
{
m_sound_banks[offset] = data;
}
//-------------------------------------------------
// sound_stream_update - handle a stream update
//-------------------------------------------------
void exidy440_sound_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
int ch;
/* reset the mixer buffers */
memset(m_mixer_buffer_left.get(), 0, samples * sizeof(int32_t));
memset(m_mixer_buffer_right.get(), 0, samples * sizeof(int32_t));
/* loop over channels */
for (ch = 0; ch < 4; ch++)
{
sound_channel_data *channel = &m_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 = m_sound_volume[2 * ch + 0];
if (volume)
add_and_scale_samples(ch, m_mixer_buffer_left.get(), length, volume);
/* get a pointer to the sample data and copy to the left */
volume = m_sound_volume[2 * ch + 1];
if (volume)
add_and_scale_samples(ch, m_mixer_buffer_right.get(), 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;
m_m6844_channel[ch].address = m_m6844_channel[ch].start_address + effective_offset / 8;
m_m6844_channel[ch].counter = m_m6844_channel[ch].start_counter - effective_offset / 8;
if (m_m6844_channel[ch].counter <= 0)
{
if (SOUND_LOG && m_debuglog)
fprintf(m_debuglog, "Channel %d finished\n", ch);
m6844_finished(&m_m6844_channel[ch]);
}
}
/* all done, time to mix it */
mix_to_16(samples, outputs[0], outputs[1]);
}