// license:BSD-3-Clause
// copyright-holders:Eugene Sandulenko
/***************************************************************************
TIA-MC1 sound hardware
driver by Eugene Sandulenko
special thanks to Shiru for his standalone emulator and documentation
timer1 timer0
|--------| |--------|
| 8253 | | 8253 | |---|
in0 | | | | | 1 | |---|
----+ g0 o0 +---+ g0 o0 +---+ | | & |
in1 | | | | | o---+ | out
----+ g1 o1 +---+ g1 o1 +---+ | | +-----
in2 | | | | |---| | |
----+ g2 o2 +---+ g2 o2 +-----------+ |
clk1| | | | |---|
----+ clk | +-+ clk |
| | | | |
|--------| | |--------|
clk0 |
---------------+
in0-in2 comes from port #da
clk0 comes from 8224 and equals to 1777777Hz, i.e. processor clock
clk1 comes from divider 16000000/4/16/16/2 = 7812Hz
********************
TODO: use machine/pit8253.c
***************************************************************************/
#include "emu.h"
#include "audio/tiamc1.h"
#define CLOCK_DIVIDER 16
#define BUF_LEN 100000
#define T8253_CHAN0 0
#define T8253_CHAN1 1
#define T8253_CHAN2 2
#define T8253_CWORD 3
// device type definition
DEFINE_DEVICE_TYPE(TIAMC1, tiamc1_sound_device, "tiamc1_sound", "TIA-MC1 Custom Sound")
//**************************************************************************
// LIVE DEVICE
//**************************************************************************
//-------------------------------------------------
// tiamc1_sound_device - constructor
//-------------------------------------------------
tiamc1_sound_device::tiamc1_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, TIAMC1, tag, owner, clock),
device_sound_interface(mconfig, *this),
m_channel(nullptr),
m_timer1_divider(0)
{
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void tiamc1_sound_device::device_start()
{
int i, j;
timer8253_reset(&m_timer0);
timer8253_reset(&m_timer1);
m_channel = stream_alloc(0, 1, clock() / CLOCK_DIVIDER);
m_timer1_divider = 0;
for (i = 0; i < 2; i++)
{
struct timer8253struct *t = (i ? &m_timer1 : &m_timer0);
for (j = 0; j < 3; j++)
{
save_item(NAME(t->channel[j].count), i * 3 + j);
save_item(NAME(t->channel[j].cnval), i * 3 + j);
save_item(NAME(t->channel[j].bcdMode), i * 3 + j);
save_item(NAME(t->channel[j].cntMode), i * 3 + j);
save_item(NAME(t->channel[j].valMode), i * 3 + j);
save_item(NAME(t->channel[j].gate), i * 3 + j);
save_item(NAME(t->channel[j].output), i * 3 + j);
save_item(NAME(t->channel[j].loadCnt), i * 3 + j);
save_item(NAME(t->channel[j].enable), i * 3 + j);
}
}
save_item(NAME(m_timer1_divider));
}
//-------------------------------------------------
// sound_stream_update - handle a stream update
//-------------------------------------------------
void tiamc1_sound_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
int count, o0, o1, o2, len, orval = 0;
len = samples * CLOCK_DIVIDER;
for (count = 0; count < len; count++)
{
m_timer1_divider++;
if (m_timer1_divider == 228)
{
m_timer1_divider = 0;
timer8253_tick(&m_timer1, 0);
timer8253_tick(&m_timer1, 1);
timer8253_tick(&m_timer1, 2);
timer8253_set_gate(&m_timer0, 0, timer8253_get_output(&m_timer1, 0));
timer8253_set_gate(&m_timer0, 1, timer8253_get_output(&m_timer1, 1));
timer8253_set_gate(&m_timer0, 2, timer8253_get_output(&m_timer1, 2));
}
timer8253_tick(&m_timer0, 0);
timer8253_tick(&m_timer0, 1);
timer8253_tick(&m_timer0, 2);
o0 = timer8253_get_output(&m_timer0, 0) ? 1 : 0;
o1 = timer8253_get_output(&m_timer0, 1) ? 1 : 0;
o2 = timer8253_get_output(&m_timer0, 2) ? 1 : 0;
orval = (orval << 1) | (((o0 | o1) ^ 0xff) & o2);
if ((count + 1) % CLOCK_DIVIDER == 0)
{
outputs[0][count / CLOCK_DIVIDER] = orval ? 0x2828 : 0;
orval = 0;
}
}
}
void tiamc1_sound_device::timer8253_reset(struct timer8253struct *t)
{
memset(t,0,sizeof(struct timer8253struct));
}
void tiamc1_sound_device::timer8253_tick(struct timer8253struct *t, int chn)
{
if (t->channel[chn].enable && t->channel[chn].gate)
{
switch (t->channel[chn].cntMode)
{
case 0:
t->channel[chn].count--;
if (t->channel[chn].count == 0xffff)
t->channel[chn].output = 1;
break;
case 3:
t->channel[chn].count--;
if (t->channel[chn].count < (t->channel[chn].cnval >> 1))
t->channel[chn].output = 0;
else
t->channel[chn].output = 1;
if (t->channel[chn].count == 0xffff)
t->channel[chn].count = t->channel[chn].cnval;
break;
case 4:
t->channel[chn].count--;
if(t->channel[chn].count==0)
t->channel[chn].output = 1;
if(t->channel[chn].count == 0xffff)
{
t->channel[chn].enable = 0;
t->channel[chn].output = 1;
}
break;
}
}
}
void tiamc1_sound_device::timer8253_wr(struct timer8253struct *t, int reg, uint8_t val)
{
int chn;
switch (reg)
{
case T8253_CWORD:
chn = val >> 6;
if (chn < 3)
{
t->channel[chn].bcdMode = (val & 1) ? 1 : 0;
t->channel[chn].cntMode = (val >> 1) & 0x07;
t->channel[chn].valMode = (val >> 4) & 0x03;
switch (t->channel[chn].valMode)
{
case 1:
case 2:
t->channel[chn].loadCnt = 1;
break;
case 3:
t->channel[chn].loadCnt = 2;
break;
default:
osd_printf_debug("unhandled val mode %i\n", t->channel[chn].valMode);
}
switch (t->channel[chn].cntMode)
{
case 0:
t->channel[chn].output = 0;
t->channel[chn].enable = 0;
break;
case 3:
t->channel[chn].output = 1;
break;
case 4:
t->channel[chn].output = 1;
t->channel[chn].enable = 0;
break;
default:
osd_printf_debug("unhandled cnt mode %i\n", t->channel[chn].cntMode);
}
}
break;
default:
chn = reg;
switch (t->channel[chn].valMode)
{
case 1:
t->channel[chn].cnval = (t->channel[chn].cnval & 0xff00) | val;
break;
case 2:
t->channel[chn].cnval = (t->channel[chn].cnval & 0x00ff) | (val << 8);
break;
case 3:
t->channel[chn].cnval = (t->channel[chn].cnval >> 8) | (val << 8);
break;
default:
osd_printf_debug("unhandled val mode %i\n", t->channel[chn].valMode);
}
if (t->channel[chn].cntMode==0)
{
t->channel[chn].enable = 0;
}
t->channel[chn].loadCnt--;
if (t->channel[chn].loadCnt == 0)
{
switch (t->channel[chn].valMode)
{
case 1:
case 2:
t->channel[chn].loadCnt = 1;
break;
case 3:
t->channel[chn].loadCnt = 2;
break;
default:
osd_printf_debug("unhandled val mode %i\n", t->channel[chn].valMode);
}
switch (t->channel[chn].cntMode)
{
case 3:
t->channel[chn].count = t->channel[chn].cnval;
t->channel[chn].enable = 1;
break;
case 0:
case 4:
t->channel[chn].count = t->channel[chn].cnval;
t->channel[chn].enable = 1;
break;
default:
osd_printf_debug("unhandled cnt mode %i\n", t->channel[chn].cntMode);
}
}
}
}
void tiamc1_sound_device::timer8253_set_gate(struct timer8253struct *t, int chn, uint8_t gate)
{
t->channel[chn].gate = gate;
}
char tiamc1_sound_device::timer8253_get_output(struct timer8253struct *t, int chn)
{
return t->channel[chn].output;
}
WRITE8_MEMBER( tiamc1_sound_device::tiamc1_timer0_w )
{
timer8253_wr(&m_timer0, offset, data);
}
WRITE8_MEMBER( tiamc1_sound_device::tiamc1_timer1_w )
{
timer8253_wr(&m_timer1, offset, data);
}
WRITE8_MEMBER( tiamc1_sound_device::tiamc1_timer1_gate_w )
{
timer8253_set_gate(&m_timer1, 0, (data & 1) ? 1 : 0);
timer8253_set_gate(&m_timer1, 1, (data & 2) ? 1 : 0);
timer8253_set_gate(&m_timer1, 2, (data & 4) ? 1 : 0);
}