// license:BSD-3-Clause
// copyright-holders:David Haywood
#include "emu.h"
#include "includes/xavix.h"
// #define VERBOSE 1
#include "logmacro.h"
// 16 stereo voices?
// xavix_sound_device
DEFINE_DEVICE_TYPE(XAVIX_SOUND, xavix_sound_device, "xavix_sound", "XaviX Sound")
xavix_sound_device::xavix_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, XAVIX_SOUND, tag, owner, clock)
, device_sound_interface(mconfig, *this)
, m_stream(nullptr)
, m_readregs_cb(*this)
, m_readsamples_cb(*this)
{
}
void xavix_sound_device::device_start()
{
m_readregs_cb.resolve_safe(0xff);
m_readsamples_cb.resolve_safe(0x80);
m_stream = stream_alloc(0, 2, 163840);
}
void xavix_sound_device::device_reset()
{
for (int v = 0; v < 16; v++)
{
m_voice[v].enabled[0] = false;
m_voice[v].enabled[1] = false;
m_voice[v].position[0] = 0x00;
m_voice[v].position[1] = 0x00;
m_voice[v].bank = 0x00;
}
}
void xavix_sound_device::sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs)
{
// reset the output stream
outputs[0].fill(0);
outputs[1].fill(0);
int outpos = 0;
// loop while we still have samples to generate
int samples = outputs[0].samples();
while (samples-- != 0)
{
for (int channel = 0; channel < 2; channel++)
{
for (int v = 0; v < 16; v++)
{
if (m_voice[v].enabled[channel] == true)
{
// 2 is looping? 3 is single shot? 0/1 are something else?
if (m_voice[v].type == 2 || m_voice[v].type == 3)
{
uint32_t pos = (m_voice[v].bank << 16) | (m_voice[v].position[channel] >> 14);
int8_t sample = m_readsamples_cb(pos);
if ((uint8_t)sample == 0x80) // would both channels stop / loop or just one?, Yellow submarine indicates just one, but might be running in some interleaved mode?
{
//if (m_voice[v].type == 3)
{
m_voice[v].enabled[channel] = false;
break;
}
/* need envelopes or some of these loop forever!
else if (m_voice[v].type == 2)
{
m_voice[v].position[channel] = m_voice[v].startposition[channel];
// presumably don't want to play 0x80 byte, so read in a new one
pos = (m_voice[v].bank << 16) | (m_voice[v].position[channel] >> 14);
sample = m_readsamples_cb(pos);
}
*/
}
outputs[channel].add_int(outpos, sample * (m_voice[v].vol + 1), 32768);
m_voice[v].position[channel] += m_voice[v].rate;
}
else
{
popmessage("unsupported voice type %01x", m_voice[v].type);
m_voice[v].enabled[channel] = false;
}
}
}
}
outpos++;
}
}
bool xavix_sound_device::is_voice_enabled(int voice)
{
m_stream->update();
/*
if ((m_voice[voice].enabled[0] == true) || (m_voice[voice].enabled[1] == true))
return true;
else
return false;
*/
if ((m_voice[voice].enabled[0] == true) && (m_voice[voice].enabled[1] == true))
return true;
else
return false;
}
void xavix_sound_device::enable_voice(int voice, bool update_only)
{
m_stream->update();
int voicemembase = voice * 0x10;
uint16_t freq_mode = (m_readregs_cb(voicemembase + 0x1) << 8) | (m_readregs_cb(voicemembase + 0x0)); // sample rate maybe?
uint16_t sampleaddrleft = (m_readregs_cb(voicemembase + 0x3) << 8) | (m_readregs_cb(voicemembase + 0x2)); // seems to be a start position
uint16_t sampleaddrright = (m_readregs_cb(voicemembase + 0x5) << 8) | (m_readregs_cb(voicemembase + 0x4)); // another start position? sometimes same as envaddrleft
uint8_t unused1 = (m_readregs_cb(voicemembase + 0x7)); // data gets written but doesn't look like it's used by the chip?
uint8_t sampleaddrbank = (m_readregs_cb(voicemembase + 0x6)); // upper 8 bits of memory address? 8 bits unused?
// these don't seem to be populated as often, maybe some kind of effect / envelope filter?
uint8_t envfreq = (m_readregs_cb(voicemembase + 0x9));
uint8_t envmode_unk = (m_readregs_cb(voicemembase + 0x8));
uint16_t envaddrleft = (m_readregs_cb(voicemembase + 0xb) << 8) | (m_readregs_cb(voicemembase + 0xa)); // seems to be a start position, lower byte is direct value, not address in some modes??
uint16_t envaddrright = (m_readregs_cb(voicemembase + 0xd) << 8) | (m_readregs_cb(voicemembase + 0xc)); // another start position? sometimes same as envaddrleft
uint8_t unused2 = (m_readregs_cb(voicemembase + 0xf)); // data gets written but doesn't look like it's used by the chip?
uint8_t envaddrbank = (m_readregs_cb(voicemembase + 0xe)); // upper 8 bits of memory address? 8 bits unused (or not unused?, get populated with increasing values sometimes?)
uint32_t sampleaddrleft_full = (sampleaddrleft | sampleaddrbank << 16) & 0x00ffffff; // definitely addresses based on rad_snow
uint32_t sampleaddrright_full = (sampleaddrright | sampleaddrbank << 16) & 0x00ffffff;
uint32_t envaddrleft_full = (envaddrleft | envaddrbank << 16) & 0x00ffffff; // still looks like addresses, sometimes pointing at RAM
uint32_t envaddrright_full = (envaddrright | envaddrbank << 16) & 0x00ffffff;
uint8_t envmode = (envmode_unk >> 4)&3; // upper bits not used?
uint8_t envunk = envmode_unk & 0x0f;
if (update_only) LOG("(UPDATE ONLY) ");
LOG("voice %01x (params %04x %04x %04x %02x %02x %02x %02x %04x %04x %02x %02x)\n", voice, freq_mode, sampleaddrleft, sampleaddrright, unused1, sampleaddrbank, envfreq, envmode_unk, envaddrleft, envaddrright, unused2, envaddrbank);
if (envmode == 0)
{
// mode 0 doesn't seem to use a second pair of addresses for the envelope but instead a fixed value in the lower part of the address registers?, usually used with non-looping samples too? upper bytes of address end up being leftovers from previous sounds
LOG("voice %01x (possible meanings mode %01x rate %04x sampleaddrleft_full %08x sampleaddrright_full %08x envvalue_left %02x envvalue_right %02x envfreq %02x envmode_unk [%01x, %01x])\n", voice, freq_mode & 0x3, freq_mode >> 2, sampleaddrleft_full, sampleaddrright_full, envaddrleft_full & 0xff, envaddrright_full & 0xff, envfreq, envmode, envunk);
}
else
{
// envelopes usually point to 8-byte sequences of values?
// when written from sound_updateenv_w (update only) then mode is usually 0x3 (key off?)
// mode 1 is used for most samples (key on?)
// mode 2 is used on monster truck
LOG("voice %01x (possible meanings mode %01x rate %04x sampleaddrleft_full %08x sampleaddrright_full %08x envaddrleft_full %08x envaddrright_full %08x envfreq %02x envmode_unk [%01x, %01x])\n", voice, freq_mode & 0x3, freq_mode >> 2, sampleaddrleft_full, sampleaddrright_full, envaddrleft_full, envaddrright_full, envfreq, envmode, envunk);
LOG(" (ENV1 ");
for (int i = 0; i < 8; i++)
{
uint8_t env = m_readsamples_cb(envaddrleft_full+i);
LOG("%02x ", env);
}
LOG(") ");
LOG(" (ENV2 ");
for (int i = 0; i < 8; i++)
{
uint8_t env = m_readsamples_cb(envaddrright_full+i);
LOG("%02x ", env);
}
LOG(") \n");
}
if (envmode_unk & 0xc0)
{
LOG(" (unexpected bits set in envmode_unk %02x)\n", envmode_unk & 0xc0);
}
if (!update_only)
{
m_voice[voice].enabled[0] = true;
m_voice[voice].enabled[1] = true;
m_voice[voice].bank = sampleaddrbank;
m_voice[voice].position[0] = sampleaddrleft << 14;
m_voice[voice].position[1] = sampleaddrright << 14;
m_voice[voice].type = freq_mode & 0x3;
m_voice[voice].rate = freq_mode >> 2;
m_voice[voice].vol = envunk;
m_voice[voice].startposition[0] = m_voice[voice].position[0]; // for looping
m_voice[voice].startposition[1] = m_voice[voice].position[1];
}
// 0320 (800) == 8000hz
// 4000 (16384) == 163840hz ? = 163.840kHz
// samples appear to be PCM, 0x80 terminated, looks like there's a way of specifying mono (interleave channels?) or stereo, maybe in master regs?
}
void xavix_sound_device::disable_voice(int voice)
{
m_voice[voice].enabled[0] = false;
m_voice[voice].enabled[1] = false;
}
// xavix_state support
uint8_t xavix_state::sound_regram_read_cb(offs_t offset)
{
// 0x00 would be zero page memory, and problematic for many reasons, assume it just doesn't work like that
if ((m_sound_regbase & 0x3f) != 0x00)
{
uint16_t memorybase = (m_sound_regbase & 0x3f) << 8;
return m_mainram[memorybase + offset];
}
return 0x00;
}
/* 75f0, 75f1 - 2x8 bits (16 voices?) */
uint8_t xavix_state::sound_startstop_r(offs_t offset)
{
LOG("%s: sound_startstop_r %02x\n", machine().describe_context(), offset);
return m_soundreg16_0[offset];
}
void xavix_state::sound_startstop_w(offs_t offset, uint8_t data)
{
/* looks like the sound triggers
offset 0
data & 0x01 - voice 0 (registers at regbase + 0x00) eg 0x3b00 - 0x3b0f in monster truck
data & 0x02 - voice 1 (registers at regbase + 0x10) eg 0x3b10 - 0x3b1f in monster truck
data & 0x04 - voice 2
data & 0x08 - voice 3
data & 0x10 - voice 4
data & 0x20 - voice 5
data & 0x40 - voice 6
data & 0x80 - voice 7
offset 1
data & 0x01 - voice 8
data & 0x02 - voice 9
data & 0x04 - voice 10
data & 0x08 - voice 11
data & 0x10 - voice 12
data & 0x20 - voice 13
data & 0x40 - voice 14 (registers at regbase + 0xf0) eg 0x3be0 - 0x3bef in monster truck
data & 0x80 - voice 15 (registers at regbase + 0xf0) eg 0x3bf0 - 0x3bff in monster truck
*/
if (offset == 0)
LOG("%s: sound_startstop_w %02x, %02x (%d %d %d %d %d %d %d %d - - - - - - - -)\n", machine().describe_context(), offset, data, (data & 0x01) ? 1 : 0, (data & 0x02) ? 1 : 0, (data & 0x04) ? 1 : 0, (data & 0x08) ? 1 : 0, (data & 0x10) ? 1 : 0, (data & 0x20) ? 1 : 0, (data & 0x40) ? 1 : 0, (data & 0x80) ? 1 : 0);
else
LOG("%s: sound_startstop_w %02x, %02x (- - - - - - - - %d %d %d %d %d %d %d %d)\n", machine().describe_context(), offset, data, (data & 0x01) ? 1 : 0, (data & 0x02) ? 1 : 0, (data & 0x04) ? 1 : 0, (data & 0x08) ? 1 : 0, (data & 0x10) ? 1 : 0, (data & 0x20) ? 1 : 0, (data & 0x40) ? 1 : 0, (data & 0x80) ? 1 : 0);
for (int i = 0; i < 8; i++)
{
const int voice_state = (data & (1 << i));
const int old_voice_state = (m_soundreg16_0[offset] & (1 << i));
if (voice_state != old_voice_state)
{
const int voice = (offset * 8 + i);
if (voice_state)
{
m_sound->enable_voice(voice, false);
}
else
{
m_sound->disable_voice(voice);
}
}
}
m_soundreg16_0[offset] = data;
}
/* 75f0, 75f1 - 2x8 bits (16 voices?) */
uint8_t xavix_state::sound_updateenv_r(offs_t offset)
{
LOG("%s: sound_updateenv_r %02x\n", machine().describe_context(), offset);
return m_soundreg16_1[offset];
}
void xavix_state::sound_updateenv_w(offs_t offset, uint8_t data)
{
if (offset == 0)
LOG("%s: sound_updateenv_w %02x, %02x (%d %d %d %d %d %d %d %d - - - - - - - -)\n", machine().describe_context(), offset, data, (data & 0x01) ? 1 : 0, (data & 0x02) ? 1 : 0, (data & 0x04) ? 1 : 0, (data & 0x08) ? 1 : 0, (data & 0x10) ? 1 : 0, (data & 0x20) ? 1 : 0, (data & 0x40) ? 1 : 0, (data & 0x80) ? 1 : 0);
else
LOG("%s: sound_updateenv_w %02x, %02x (- - - - - - - - %d %d %d %d %d %d %d %d)\n", machine().describe_context(), offset, data, (data & 0x01) ? 1 : 0, (data & 0x02) ? 1 : 0, (data & 0x04) ? 1 : 0, (data & 0x08) ? 1 : 0, (data & 0x10) ? 1 : 0, (data & 0x20) ? 1 : 0, (data & 0x40) ? 1 : 0, (data & 0x80) ? 1 : 0);
// used to update envelopes without restarting sound? for key-off events?
for (int i = 0; i < 8; i++)
{
const int voice_state = (data & (1 << i));
const int old_voice_state = (m_soundreg16_1[offset] & (1 << i));
if (voice_state != old_voice_state)
{
const int voice = (offset * 8 + i);
if (voice_state)
{
m_sound->enable_voice(voice, true);
}
else
{
m_sound->disable_voice(voice);
}
}
}
m_soundreg16_1[offset] = data;
}
/* 75f4, 75f5 - 2x8 bits (16 voices?) status? */
uint8_t xavix_state::sound_sta16_r(offs_t offset)
{
uint8_t ret = 0x00;
for (int i = 0; i < 8; i++)
{
const int voice = (offset * 8 + i);
const bool enabled = m_sound->is_voice_enabled(voice);
ret |= enabled ? 1 << i : 0;
}
return ret;
}
/* 75f6 - master volume control? */
uint8_t xavix_state::sound_volume_r()
{
LOG("%s: sound_volume_r\n", machine().describe_context());
return m_mastervol;
}
void xavix_state::sound_volume_w(uint8_t data)
{
m_mastervol = data;
LOG("%s: sound_volume_w %02x\n", machine().describe_context(), data);
}
/* 75f7 - main register base*/
void xavix_state::sound_regbase_w(uint8_t data)
{
// this is the upper 6 bits of the RAM address where the actual sound register sets are
// (16x16 regs, so complete 0x100 bytes of RAM eg 0x3b means the complete 0x3b00 - 0x3bff range with 0x3b00 - 0x3b0f being voice 1 etc)
m_sound_regbase = data;
LOG("%s: sound_regbase_w %02x (sound regs are at 0x%02x00 to 0x%02xff)\n", machine().describe_context(), data, m_sound_regbase & 0x3f, m_sound_regbase & 0x3f);
}
/* 75f8, 75f9 - misc unknown sound regs*/
uint8_t xavix_state::sound_75f8_r()
{
LOG("%s: sound_75f8_r\n", machine().describe_context());
return m_unk_snd75f8;
}
void xavix_state::sound_75f8_w(uint8_t data)
{
m_unk_snd75f8 = data;
LOG("%s: sound_75f8_w %02x\n", machine().describe_context(), data);
}
uint8_t xavix_state::sound_75f9_r()
{
LOG("%s: sound_75f9_r\n", machine().describe_context());
return m_unk_snd75f9;
}
void xavix_state::sound_75f9_w(uint8_t data)
{
m_unk_snd75f9 = data;
LOG("%s: sound_75f9_w %02x\n", machine().describe_context().c_str(), data);
}
/* 75fa, 75fb, 75fc, 75fd - timers?? generate interrupts?? */
uint8_t xavix_state::sound_timer0_r()
{
LOG("%s: sound_timer0_r\n", machine().describe_context());
return m_sndtimer[0];
}
void xavix_state::sound_timer0_w(uint8_t data)
{
m_sndtimer[0] = data;
LOG("%s: sound_timer0_w %02x\n", machine().describe_context(), data);
}
uint8_t xavix_state::sound_timer1_r()
{
LOG("%s: sound_timer1_r\n", machine().describe_context());
return m_sndtimer[1];
}
void xavix_state::sound_timer1_w(uint8_t data)
{
m_sndtimer[1] = data;
LOG("%s: sound_timer1_w %02x\n", machine().describe_context(), data);
}
uint8_t xavix_state::sound_timer2_r()
{
LOG("%s: sound_timer2_r\n", machine().describe_context());
return m_sndtimer[2];
}
void xavix_state::sound_timer2_w(uint8_t data)
{
m_sndtimer[2] = data;
LOG("%s: sound_timer2_w %02x\n", machine().describe_context(), data);
}
uint8_t xavix_state::sound_timer3_r()
{
LOG("%s: sound_timer3_r\n", machine().describe_context());
return m_sndtimer[3];
}
void xavix_state::sound_timer3_w(uint8_t data) // this one is used by ekara (uk carts) , enabled with 08 in 75fe
{
m_sndtimer[3] = data;
LOG("%s: sound_timer3_w %02x\n", machine().describe_context(), data);
}
/* 75fe - some kind of IRQ status / Timer Status? */
uint8_t xavix_state::sound_irqstatus_r()
{
// rad_rh checks this after doing something that looks like an irq ack
// the UK ekara sets check the upper bit to see if the interrupt is from the sound timer (rather than checking interrupt source register)
// and decrease a counter that controls the tempo (the US / Japan sets don't enable the sound timer at all)
return m_sound_irqstatus;
}
void xavix_state::sound_irqstatus_w(uint8_t data)
{
// these look like irq ack bits, 4 sources?
// related to sound_timer0_w , sound_timer1_w, sound_timer2_w, sound_timer3_w ?
for (int t = 0; t < 4; t++)
{
int bit = (1 << t) << 4;
if (data & bit)
{
m_sound_irqstatus &= ~data & bit;
}
}
// check if all interrupts have been cleared to see if the line should be lowered
if (m_sound_irqstatus & 0xf0)
m_irqsource |= 0x80;
else
m_irqsource &= ~0x80;
update_irqs();
for (int t = 0; t < 4; t++)
{
int bit = 1 << t;
if ((m_sound_irqstatus & bit) != (data & bit))
{
if (data & bit)
{
/* period should be based on m_sndtimer[t] at least, maybe also some other regs?
rad_crdn : sound_timer0_w 06
ddrfammt, popira etc. : sound_timer3_w 80
so higher value definitely needs to be faster? (unless there's another multiplier elsewhere)
11 is too fast (popira checked on various tracks, finish before getting to 100% then jump to 100%) where is this multiplier coming from? clock divided?
10 seems close to correct for ddrfammt, popira, might need fine tuning. seems too slow for rad_crdn / rad_bass?
tweaked to 10.3f stay in time with the first song in https://www.youtube.com/watch?v=3x1C9bhC2rc
the usual clock divided by 2 would be 10.738636 but that's too high
*/
attotime period = attotime::from_hz(10.3f * (m_sndtimer[t]));
m_sound_timer[t]->adjust(period, t, period);
}
else
{
m_sound_timer[t]->adjust(attotime::never, t);
}
}
}
// see if we're enabling any timers (should probably check if they're already running so we don't end up restarting them)
m_sound_irqstatus |= data & 0x0f; // look like IRQ enable flags - 4 sources? voices? timers?
LOG("%s: sound_irqstatus_w %02x\n", machine().describe_context(), data);
}
void xavix_state::sound_75ff_w(uint8_t data)
{
m_unk_snd75ff = data;
LOG("%s: sound_75ff_w %02x\n", machine().describe_context(), data);
}
// used by ekara (UK cartridges), rad_bass, rad_crdn
TIMER_CALLBACK_MEMBER(xavix_state::sound_timer_done)
{
// param = timer number 0,1,2 or 3
int bit = (1 << param) << 4;
m_sound_irqstatus |= bit;
// if any of the sound timers are causing an interrupt...
if (m_sound_irqstatus & 0xf0)
m_irqsource |= 0x80;
else
m_irqsource &= ~0x80;
update_irqs();
}