summaryrefslogtreecommitdiffstatshomepage
path: root/src/mame/tvgames/xavix_a.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mame/tvgames/xavix_a.cpp')
-rw-r--r--src/mame/tvgames/xavix_a.cpp1019
1 files changed, 614 insertions, 405 deletions
diff --git a/src/mame/tvgames/xavix_a.cpp b/src/mame/tvgames/xavix_a.cpp
index d5591bfb7f3..24a0345c1b4 100644
--- a/src/mame/tvgames/xavix_a.cpp
+++ b/src/mame/tvgames/xavix_a.cpp
@@ -1,14 +1,71 @@
// license:BSD-3-Clause
-// copyright-holders:Ramacat, David Haywood
+// copyright-holders:ramacat, David Haywood
+
+// XaviX Audio Renderer overview:
+// - The sound core mixes up to 16 voices per sample at a fixed render rate.
+// - Each voice reads 8-bit inverted sign-magnitude PCM, with four wave modes (WM):
+// WM0: RAM wavetable (CPU can live-edit) WM1: noise LFSR
+// WM2: ROM wavetable w/ loop WM3: ROM one-shot (stops at 0x80)
+// - Phase is 18.14 fixed-point; per-voice 'rate' (from wave_control>>2) is scaled by 'phase_step_per_tick()'.
+// - Byte value 0x80 is a sentinel: WM2 loops to 'loop_position'; WM3 disables the voice.
+// - Envelope Modes (VM) control per-side levels:
+// VM0: direct LA/RA registers (no clock); VM1: nibble table; VM2: byte stream; VM3: exponential-ish decay.
+// - Envelope advance is driven by per-group tempo tp[0..3] and 'cyclerate':
+// tempo_to_period_samples(tp) -> ticks; VM1/2/3 step on ticks; VM0 ignores tempo.
+// - Hardware defaults tp[*]=0 pauses ticks -> VM1/2/3 won’t finish; we use a generic fallback prevent stuck voices.
+// - Pitch bends: 'step_pitch()' slews 'rate' by +/-1 per render tick toward wave_control>>2,
+// producing smooth glides without zippering.
+// - Mixer visit order emulates hardware scan; order changes when DAC broadcast mode is set.
+// - Per-voice volume = sample * voice gain(4-bit) * master volume * envelope L/R, then summed.
+// - 'capacity' may drop higher-index voices; 'monaural' averages L/R at the end.
+// - Output gain uses a small discrete table; results are clamped to 16-bit before stream write.
+// - 'cyclerate' retunes tempo periods on the fly; countdowns are rescaled to avoid phase jumps.
+// - Title code controls registers via a memory-mapped page; RAM reads reflect live CPU edits.
+// - Noise (WM1) uses a 16-bit LFSR with a fixed/nonzero seed; seed writes reinitialize it.
+// - DAC "gap/lead/lag" bits are exposed but many titles leave them at reset defaults.
+// - IRQs: per-group tempo timers latch pending bits; VM ticking doesn’t imply an IRQ unless
+// the group enable bit is set (emulation mirrors that behavior).
#include "emu.h"
#include "xavix.h"
-// #define VERBOSE 1
+#define VERBOSE (0)
+
+#define LOG_CFG (1U << 1)
+#define LOG_TEMPO (1U << 2)
+#define LOG_TIMER (1U << 3)
+#define LOG_IRQ (1U << 4)
+#define LOG_VOICE (1U << 5)
+#define LOG_WAVE (1U << 6)
+#define LOG_ENV (1U << 7)
+#define LOG_ENV_DATA (1U << 8)
+#define LOG_PITCH (1U << 9)
+#define LOG_SCAN (1U << 10)
+#define LOG_CLIP (1U << 11)
+#define LOG_NOISE (1U << 12)
+
+//#define LOGCTX(mask, fmt, ...) LOGMASKED((mask), "%s: " fmt, machine().describe_context(), ##__VA_ARGS__)
#include "logmacro.h"
DEFINE_DEVICE_TYPE(XAVIX_SOUND, xavix_sound_device, "xavix_sound", "XaviX Sound")
+namespace
+{
+ // Internal sequencer rate works out to be 167'791 Hz.
+ // - default cyclerate at reset is 0x0f; 2 phase updates per 16-state frame -> CORE_CLK / (( cyclerate + 1 ) * 8 )
+ // - titles never seem to change cyclerate, and if they did, it's not possible to dynamically change the MAME stream after stream_alloc.
+ // - 42Mhz CPU devices also run their sound core at 21MHz.
+ // - DAC timing knobs:
+ // - lead/lag shift the latch strobe within the 16-cycle frame,
+ // - gap!=0 issues a second write strobe (at 3−lag+gap), duplicating the held sample within the frame.
+ // - no titles found that change these from the defaults, so unable to test.
+ // These alter *when* a channel is latched (and can duplicate it), but not the state-machine rate itself.
+
+ static constexpr uint8_t MIXER_ORDER_MULTIPLEX[16] = { 0x0, 0xa, 0x7, 0xd, 0xc, 0x6, 0xb, 0x1, 0x4, 0xe, 0x3, 0x9, 0x8, 0x2, 0xf, 0x5 };
+ static constexpr uint8_t MIXER_ORDER_BROADCAST[16] = { 0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe, 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf };
+ static constexpr int AMP_TABLE[8] = { 2, 4, 8, 12, 16, 20, 20, 20 };
+}
+
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)
@@ -21,109 +78,141 @@ xavix_sound_device::xavix_sound_device(const machine_config &mconfig, const char
void xavix_sound_device::device_start()
{
- m_stream = stream_alloc(0, 2, 163840);
+ m_sequencer_rate_hz = clock() / ((0x0f + 1u) * 8u); // Hardware Rate (167'791)
+ m_stream = stream_alloc(0, 2, m_sequencer_rate_hz);
+ LOGMASKED(LOG_CFG, "[cfg] start render_rate=%uHz\n", m_sequencer_rate_hz);
- save_item(NAME(m_tp_dev));
- save_item(NAME(m_pitch_countdown));
- save_item(NAME(m_cyclerate_dev));
+ // dividers
+ save_item(NAME(m_tempo_div));
+ save_item(NAME(m_cyclerate_div));
+ // voice parameters
save_item(STRUCT_MEMBER(m_voice, enabled));
save_item(STRUCT_MEMBER(m_voice, position));
- save_item(STRUCT_MEMBER(m_voice, loopposition));
- save_item(STRUCT_MEMBER(m_voice, loopendposition));
- save_item(STRUCT_MEMBER(m_voice, startposition));
-
- save_item(STRUCT_MEMBER(m_voice, envpositionleft));
- save_item(STRUCT_MEMBER(m_voice, envpositionright));
- save_item(STRUCT_MEMBER(m_voice, envbank));
- save_item(STRUCT_MEMBER(m_voice, envmode));
+ save_item(STRUCT_MEMBER(m_voice, loop_position));
+ save_item(STRUCT_MEMBER(m_voice, start_position));
save_item(STRUCT_MEMBER(m_voice, bank));
save_item(STRUCT_MEMBER(m_voice, rate));
save_item(STRUCT_MEMBER(m_voice, type));
save_item(STRUCT_MEMBER(m_voice, vol));
+ save_item(STRUCT_MEMBER(m_voice, noise_state));
+
+ // envelope parameters
save_item(STRUCT_MEMBER(m_voice, env_rom_base_left));
save_item(STRUCT_MEMBER(m_voice, env_rom_base_right));
save_item(STRUCT_MEMBER(m_voice, env_vol_left));
save_item(STRUCT_MEMBER(m_voice, env_vol_right));
+ save_item(STRUCT_MEMBER(m_voice, env_pos_left));
+ save_item(STRUCT_MEMBER(m_voice, env_pos_right));
+ save_item(STRUCT_MEMBER(m_voice, env_bank));
+ save_item(STRUCT_MEMBER(m_voice, env_mode));
+
save_item(STRUCT_MEMBER(m_voice, env_period_samples));
save_item(STRUCT_MEMBER(m_voice, env_countdown));
save_item(STRUCT_MEMBER(m_voice, env_active_left));
save_item(STRUCT_MEMBER(m_voice, env_active_right));
- save_item(STRUCT_MEMBER(m_voice, env_phase));
- save_item(STRUCT_MEMBER(m_voice, la_byte));
- save_item(STRUCT_MEMBER(m_voice, ra_byte));
- save_item(STRUCT_MEMBER(m_mix, monoural));
- save_item(STRUCT_MEMBER(m_mix,capacity));
- save_item(STRUCT_MEMBER(m_mix,amp));
- save_item(STRUCT_MEMBER(m_mix,dac));
- save_item(STRUCT_MEMBER(m_mix,gap));
- save_item(STRUCT_MEMBER(m_mix,lead));
- save_item(STRUCT_MEMBER(m_mix,lag));
- save_item(STRUCT_MEMBER(m_mix,mastervol));
- save_item(STRUCT_MEMBER(m_mix,gain));
+ // dac parameters
+ save_item(STRUCT_MEMBER(m_mix, monaural));
+ save_item(STRUCT_MEMBER(m_mix, capacity));
+ save_item(STRUCT_MEMBER(m_mix, amp));
+ save_item(STRUCT_MEMBER(m_mix, dac));
+ save_item(STRUCT_MEMBER(m_mix, gap));
+ save_item(STRUCT_MEMBER(m_mix, lead));
+ save_item(STRUCT_MEMBER(m_mix, lag));
+ save_item(STRUCT_MEMBER(m_mix, mastervol));
+ save_item(STRUCT_MEMBER(m_mix, gain));
+
+ // log triggers
+ save_item(STRUCT_MEMBER(m_voice, log_env_started));
+ save_item(STRUCT_MEMBER(m_voice, log_env_stopped));
+ save_item(STRUCT_MEMBER(m_voice, log_env_paused));
}
void xavix_sound_device::device_reset()
{
- m_cyclerate_dev = 0x0f;
-
- // hack, does not seem to get set up properly?
+ // hardware default on init
+ m_cyclerate_div = 0x0f;
+
+ // TODO: on tempo group (tp[0..3]) defaults:
+ // - On reset the hardware tempo registers power up as 0x00. When tp==0, the tempo
+ // tick for that group is halted: VM1/VM2/VM3 envelopes do not advance, and the
+ // group’s audio timer IRQ source remains idle. VM0 (direct levels) is unaffected.
+ // - Many titles either never program any tp or only write tp[3] (e.g. hikara, popira).
+ // If left at 0x00, non-VM0 voices can 'stick on' because their envelopes never
+ // progress to zero.
+ // - We set a generic default of 0x40 which is a compromise across titles.
+ // - This results in the engine also generating audio IRQs for those the affected tempo groups, however,
+ // in practice this does not seem to cause any issues and reflects the actual hardware behaviour.
+ // - With this generic default, some games sound unnaturally long/echoey (e.g. taitons2) while others become too abrupt.
+ // - When device-/title-specific initialization is confirmed, replace the generic default
+ // with those exact defaults and remove the heuristic.
for (int i = 0; i < 4; i++)
- if (m_tp_dev[i] == 0)
- m_tp_dev[i] = 1;
+ m_tempo_div[i] = 0x40; // initialised by hardware to 0x00
for (int v = 0; v < 16; v++)
{
m_voice[v].enabled = 0;
m_voice[v].position = 0;
- m_voice[v].loopposition = 0;
+ m_voice[v].loop_position = 0;
m_voice[v].bank = 0;
m_voice[v].env_vol_left = 0x00;
m_voice[v].env_vol_right = 0x00;
- m_voice[v].env_period_samples = tempo_to_period_samples(m_tp_dev[v & 3]);
+ m_voice[v].env_period_samples = envelope_period_ticks(m_tempo_div[v & 3]);
m_voice[v].env_countdown = m_voice[v].env_period_samples;
m_voice[v].env_active_left = 1;
m_voice[v].env_active_right = 1;
+ m_voice[v].noise_state = 0;
- m_pitch_countdown[v] = 0;
}
- m_mix.monoural = 0;
+ // hardware defaults on init
+ m_mix.monaural = 0;
m_mix.capacity = 0;
m_mix.amp = 2;
m_mix.dac = 0;
m_mix.gap = 0;
m_mix.lead = 0;
m_mix.lag = 0;
-
m_mix.mastervol = 0xff;
m_mix.gain = 2;
+
+ LOGMASKED(LOG_CFG, "[cfg] reset cyclerate=%02x tempo_default=%02x monaural=%d capacity=%d amp=%d\n",
+ m_cyclerate_div, m_tempo_div[0], m_mix.monaural, m_mix.capacity, m_mix.amp);
}
void xavix_sound_device::sound_stream_update(sound_stream &stream)
{
- // multiplexed DAC channel visit order
- static const uint8_t kMuxVisitOrder[16] = { 0x0, 0xa, 0x7, 0xd, 0xc, 0x6, 0xB, 0x1, 0x4, 0xe, 0x3, 0x9, 0x8, 0x2, 0xf, 0x5 };
-
- int outpos = 0;
+ int out_pos = 0;
int num_samples = stream.samples();
while (num_samples-- != 0)
{
+ const uint8_t* visit_order = m_mix.dac ? MIXER_ORDER_BROADCAST : MIXER_ORDER_MULTIPLEX;
+
+ // capacity (00=16ch, 01=8ch, 1x=4ch), applied to the *visit order* like the hardware does.
+ const uint8_t cap = m_mix.capacity & 0x03;
+ const int allowed_channels = (cap & 0x02) ? 4 : (cap & 0x01) ? 8 : 16;
+
+ LOGMASKED(LOG_SCAN, "[scan] order=%s capacity=%u allowed=%d\n",
+ m_mix.dac ? "broadcast" : "multiplex", unsigned(cap), allowed_channels);
+
int64_t total_l = 0;
int64_t total_r = 0;
- // visit voices in order hardware reads seem to indicate
+ // visit voices in the observed hardware order; drop anything past the capacity window
for (int idx = 0; idx < 16; idx++)
{
- const int v = kMuxVisitOrder[idx];
+ if (idx >= allowed_channels)
+ continue;
+
+ const int v = visit_order[idx];
if (!m_voice[v].enabled)
continue;
@@ -131,64 +220,82 @@ void xavix_sound_device::sound_stream_update(sound_stream &stream)
uint8_t raw = 0;
uint8_t wv = 0x80;
- // WM1 (square) or ROM path
+ // WM1 (noise)
if (m_voice[v].type == 1)
{
- const bool sq = ((m_voice[v].position >> 14) & 1) != 0;
- wv = sq ? 0x81 : 0x7f;
- sample = int32_t(wv) - 128;
+ uint16_t state = m_voice[v].noise_state ? m_voice[v].noise_state : 0xace1u;
+ const uint16_t feedback = ((state >> 0) ^ (state >> 2) ^ (state >> 3) ^ (state >> 5)) & 1;
+ state = (state >> 1) | (feedback << 15);
+ if (state == 0) {
+ state = 0xace1u; // TODO: Use actual seed value
+ LOGMASKED(LOG_NOISE, "[noise] v=%2d reseed=ACE1\n", v);
+ }
+ m_voice[v].noise_state = state;
+ const int8_t noise = int8_t(((state >> 8) & 0xff) ^ 0x80);
+ sample = int32_t(noise);
}
else
{
- const uint32_t pos = (m_voice[v].bank << 16) | (m_voice[v].position >> 14);
- raw = m_readsamples_cb(pos);
+ const uint32_t phase = m_voice[v].position >> 14;
+
+ if (m_voice[v].type == 0)
+ {
+ // TODO: is anything using this voice type?
+ // should it read from map(0x7400, 0x757f).ram() which is tested by gungunrv?
+ raw = 0x80;
+ }
+ else
+ {
+ const uint32_t pos = (m_voice[v].bank << 16) | phase;
+ raw = m_readsamples_cb(pos);
+ }
if (raw == 0x80)
{
sample = 0;
if (m_voice[v].type == 3)
{
- m_voice[v].enabled = 0;
+ LOGMASKED(LOG_WAVE, "[wave] v=%2d one-shot end @%06x\n",
+ v, (m_voice[v].bank << 16) | (m_voice[v].position >> 14));
+ m_voice[v].enabled = 0; // one-shot ends
continue;
}
- else if (m_voice[v].type == 2)
+ else if (m_voice[v].type == 2 || m_voice[v].type == 0)
{
- if ((m_voice[v].position >> 14) != (m_voice[v].loopposition >> 14))
- m_voice[v].position = m_voice[v].loopposition;
+ LOGMASKED(LOG_WAVE, "[wave] v=%2d loop -> %06x\n",
+ v, unsigned(m_voice[v].loop_position >> 14));
+ if ((m_voice[v].position >> 14) != (m_voice[v].loop_position >> 14))
+ m_voice[v].position = m_voice[v].loop_position;
}
}
else
{
- wv = ((raw & 0x7f) == 0)
- ? 0x80
- : (uint8_t)((~raw & 0x80) | (raw & 0x7f));
+ // inverted sign-magnitude to signed PCM
+ wv = ((raw & 0x7f) == 0) ? 0x80 : (uint8_t)((~raw & 0x80) | (raw & 0x7f));
sample = int32_t(wv) - 128;
}
}
- // mix
- const int32_t gn = (m_voice[v].vol & 0x0f);
+ // per-voice + master + envelope
+ const int32_t gn = (m_voice[v].vol & 0x0f);
const int32_t mvol = m_mix.mastervol;
const int32_t env_l = m_voice[v].env_vol_left;
const int32_t env_r = m_voice[v].env_vol_right;
const int64_t base = int64_t(sample) * int64_t(gn);
- int64_t left = (base * int64_t(mvol)) / 255;
- left = (left * int64_t(env_l)) / 255;
- total_l += left;
-
+ int64_t left = (base * int64_t(mvol)) / 255;
int64_t right = (base * int64_t(mvol)) / 255;
- right = (right * int64_t(env_r)) / 255;
- total_r += right;
- // advance phase
- m_voice[v].position += m_voice[v].rate;
+ total_l += (left * int64_t(env_l)) / 255;
+ total_r += (right * int64_t(env_r)) / 255;
- // engine work for this voice happens in this slot
+ // advance voice state
+ m_voice[v].position += phase_step_per_tick(m_voice[v].rate);
step_envelope(v);
step_pitch(v);
+ // auto-disable if both sides silent
if (m_voice[v].env_vol_left == 0 && m_voice[v].env_vol_right == 0)
{
m_voice[v].enabled = 0;
@@ -196,109 +303,143 @@ void xavix_sound_device::sound_stream_update(sound_stream &stream)
}
}
- total_l *= m_mix.gain;
- total_r *= m_mix.gain;
+ auto apply_final_gain = [&](int64_t x) -> int64_t
+ {
+ // capacity: 00=16ch, 01=8ch, 1x=4ch
+ const uint8_t cap = m_mix.capacity & 0x03;
+ const int allowed = (cap & 0x02) ? 4 : (cap & 0x01) ? 8 : 16;
+ const int amp = m_mix.gain;
+
+ // post-mix is multiplied by the amp code and divided by ~1000;
+ // approximate that non-power-of-two divide with a 3/128 fixed-point factor.
+ // this is imperfect as it will depend on the analgue path implementation surrounding the XaviX core
+ // which will likely vary based on the physical characteristics of the device.
+ int64_t y = x * int64_t(amp) * int64_t(allowed) * 3;
+ return (y >= 0) ? ((y + 64) >> 7) : -(((-y) + 64) >> 7);
+ };
+
+ total_l = apply_final_gain(total_l);
+ total_r = apply_final_gain(total_r);
+
+ if (total_l > 32767 || total_l < -32768 || total_r > 32767 || total_r < -32768)
+ {
+ LOGMASKED(LOG_CLIP, "[clip] L=%lld R=%lld\n",
+ (long long)total_l, (long long)total_r);
+ }
int32_t out_l = (total_l > 32767) ? 32767 : (total_l < -32768 ? -32768 : int32_t(total_l));
int32_t out_r = (total_r > 32767) ? 32767 : (total_r < -32768 ? -32768 : int32_t(total_r));
- if (m_mix.monoural)
+ if (m_mix.monaural)
{
const int32_t mono = (out_l + out_r) / 2;
out_l = out_r = mono;
}
- stream.add_int(0, outpos, out_l, 32768);
- stream.add_int(1, outpos, out_r, 32768);
-
- outpos++;
+ stream.add_int(0, out_pos, out_l, 32768);
+ stream.add_int(1, out_pos, out_r, 32768);
+ out_pos++;
}
}
+
bool xavix_sound_device::is_voice_enabled(int voice)
{
m_stream->update();
return m_voice[voice].enabled ? true : false;
}
-static inline uint16_t inc_low_nibble(uint16_t x)
-{
- return uint16_t((x & 0xfff0) | ((x + 1) & 0x000f));
-};
-
-inline void xavix_sound_device::step_side1(int channel, int voice, const uint8_t la, const uint8_t ra)
-{
- uint32_t& pos = channel ? m_voice[voice].envpositionright : m_voice[voice].envpositionleft;
- uint8_t& lvl = channel ? m_voice[voice].env_vol_right : m_voice[voice].env_vol_left;
- const uint16_t addr = pos;
- const uint8_t val = fetch_env_byte_direct(voice, channel, addr);
- lvl = val;
- const bool adv = channel ? (ra != 0) : (la != 0);
- if (adv)
- pos = uint16_t(addr + 1);
-};
-
void xavix_sound_device::enable_voice(int voice, bool update_only)
{
m_stream->update();
const int base = voice * 0x10;
+ LOGMASKED(LOG_VOICE, "[voice] enable v=%2d update_only=%d type(prev)=%u env_mode(prev)=%u\n",
+ voice, update_only ? 1 : 0, unsigned(m_voice[voice].type & 3), unsigned(m_voice[voice].env_mode & 3));
+
// Wave registers
const uint16_t wave_control = (m_readregs_cb(base + 0x1) << 8) | m_readregs_cb(base + 0x0);
const uint16_t wave_addr = (m_readregs_cb(base + 0x3) << 8) | m_readregs_cb(base + 0x2);
const uint16_t wave_loop_addr = (m_readregs_cb(base + 0x5) << 8) | m_readregs_cb(base + 0x4);
const uint8_t wave_addr_bank = m_readregs_cb(base + 0x6);
+ const uint8_t new_type = wave_control & 0x3;
+ const uint32_t new_rate = wave_control >> 2;
// Envelope registers
const uint8_t env_config = m_readregs_cb(base + 0x8);
- const uint16_t env_addr_left = (m_readregs_cb(base + 0xb) << 8) | m_readregs_cb(base + 0xa);
- const uint16_t env_addr_right = (m_readregs_cb(base + 0xd) << 8) | m_readregs_cb(base + 0xc);
- const uint8_t env_addr_bank = m_readregs_cb(base + 0xe);
- // const uint8_t env_vol_reg = m_readregs_cb(base + 0xf); // (read but not used here)
+ const uint16_t env_addr_left = (m_readregs_cb(base + 0xB) << 8) | m_readregs_cb(base + 0xA);
+ const uint16_t env_addr_right = (m_readregs_cb(base + 0xD) << 8) | m_readregs_cb(base + 0xC);
+ const uint8_t env_addr_bank = m_readregs_cb(base + 0xE);
+ //const uint8_t env_vol_reg = m_readregs_cb(base + 0xF); // unused but read for completeness
+ //(void)env_vol_reg;
// Always refresh fields that may be live-tweaked from RAM
m_voice[voice].vol = env_config & 0x0f;
- m_voice[voice].envmode = (env_config >> 4) & 0x03;
- m_voice[voice].envbank = env_addr_bank;
+ m_voice[voice].env_mode = (env_config >> 4) & 0x03;
+ m_voice[voice].env_bank = env_addr_bank;
m_voice[voice].env_rom_base_left = env_addr_left;
m_voice[voice].env_rom_base_right = env_addr_right;
+ m_voice[voice].type = new_type;
+ m_voice[voice].rate = new_rate;
+
+ LOGMASKED(LOG_VOICE,
+ "[voice] v=%2d regs type=%u rate=%u bank=%02x start=%06x loop=%06x\n"
+ " vm=%u vol=%u env_bank=%02x la=%04x ra=%04x\n",
+ voice,
+ unsigned(new_type), unsigned(new_rate), unsigned(wave_addr_bank),
+ unsigned((wave_addr_bank << 16) | wave_addr),
+ unsigned((wave_addr_bank << 16) | wave_loop_addr),
+ unsigned(m_voice[voice].env_mode), unsigned(m_voice[voice].vol),
+ unsigned(m_voice[voice].env_bank), unsigned(env_addr_left), unsigned(env_addr_right));
+
+ if (new_type == 1 && m_voice[voice].noise_state == 0)
+ m_voice[voice].noise_state = wave_addr ? wave_addr : 0xace1u;
- // Update-only: do NOT re-init pointers/values mid-stream
if (update_only)
return;
+ // udance enables what seems like an invalid voice when it should be silent, not clear what the hardware would do in this case
+ if (!update_only && !(new_type & 1) && wave_addr == wave_loop_addr)
+ {
+ m_voice[voice].enabled = 0;
+ return;
+ }
+
// Full (re)start
m_voice[voice].enabled = 1;
m_voice[voice].bank = wave_addr_bank;
m_voice[voice].position = uint32_t(wave_addr) << 14;
- m_voice[voice].loopposition = (wave_loop_addr ? (uint32_t(wave_loop_addr - 1) << 14) : 0);
- m_voice[voice].type = wave_control & 0x3;
- m_voice[voice].rate = wave_control >> 2;
- m_voice[voice].startposition = m_voice[voice].position;
-
- // Env tempo
- const uint8_t tp = m_tp_dev[voice & 3];
- m_voice[voice].env_period_samples = tp ? tempo_to_period_samples(tp) : 0;
- m_voice[voice].env_countdown = m_voice[voice].env_period_samples; // 0 means paused
+ // TODO: Work out why subtracting a single sample from the wave loop address is needed for loops to be in tune.
+ m_voice[voice].loop_position = (wave_loop_addr ? (uint32_t(wave_loop_addr - 1) << 14) : 0);
+ m_voice[voice].start_position = m_voice[voice].position;
+ m_voice[voice].noise_state = wave_addr ? wave_addr : 0xace1u;
+
+ // Envelope tempo
+ const uint8_t tp = m_tempo_div[voice & 3];
+ m_voice[voice].env_period_samples = tp ? envelope_period_ticks(tp) : 0;
+ m_voice[voice].env_countdown = m_voice[voice].env_period_samples; // 0 means envelope paused
m_voice[voice].env_active_left = 1;
m_voice[voice].env_active_right = 1;
- m_voice[voice].env_phase = 0;
// Initial envelope values per voice mode
- if (m_voice[voice].envmode == 0)
+ if (m_voice[voice].env_mode == 0)
{
const int base = voice * 0x10;
- const uint8_t la = m_readregs_cb(base + 0xA);
- const uint8_t ra = m_readregs_cb(base + 0xC);
+ const uint8_t la = m_readregs_cb(base + 0xa);
+ const uint8_t ra = m_readregs_cb(base + 0xc);
m_voice[voice].env_vol_left = la;
m_voice[voice].env_vol_right = ra;
}
- else if (m_voice[voice].envmode == 1)
+ else if (m_voice[voice].env_mode == 1)
{
- // Start from the configured 16-bit addresses (not LA/RA mirrors)
+ // Start from the configured 16-bit addresses
const uint16_t start_l = env_addr_left;
const uint16_t start_r = env_addr_right;
+ auto inc_low_nibble = [](uint16_t x) {
+ return uint16_t((x & 0xfff0) | ((x + 1) & 0x000f));
+ };
+
// Fetch first envelope levels
const uint8_t v_l = fetch_env_byte_direct(voice, false, start_l);
const uint8_t v_r = fetch_env_byte_direct(voice, true, start_r);
@@ -306,10 +447,10 @@ void xavix_sound_device::enable_voice(int voice, bool update_only)
m_voice[voice].env_vol_right = v_r;
// Prime per-side phase counters to the *next* nibble entry
- m_voice[voice].envpositionleft = inc_low_nibble(start_l);
- m_voice[voice].envpositionright = inc_low_nibble(start_r);
+ m_voice[voice].env_pos_left = inc_low_nibble(start_l);
+ m_voice[voice].env_pos_right = inc_low_nibble(start_r);
- // Keep both sides active; engine will step from envposition{L,R}
+ // Keep both sides active;
m_voice[voice].env_active_left = 1;
m_voice[voice].env_active_right = 1;
@@ -320,26 +461,49 @@ void xavix_sound_device::enable_voice(int voice, bool update_only)
return;
}
- else if (m_voice[voice].envmode == 2)
+ else if (m_voice[voice].env_mode == 2)
{
- const int base = voice * 0x10;
- const uint8_t la = m_readregs_cb(base + 0xA); // enable for L
- const uint8_t ra = m_readregs_cb(base + 0xC); // enable for R
+ const int regbase = voice * 0x10;
+ // VM2 streams: seed both sides from their 16-bit ROM addresses
+ m_voice[voice].env_pos_left = env_addr_left;
+ m_voice[voice].env_pos_right = env_addr_right;
+
+ auto prime_side = [&](int channel) {
+ uint32_t& pos = channel ? m_voice[voice].env_pos_right : m_voice[voice].env_pos_left;
+ uint8_t& lvl = channel ? m_voice[voice].env_vol_right : m_voice[voice].env_vol_left;
+ const uint16_t addr = uint16_t(pos & 0xffff);
+ const uint8_t val = fetch_env_byte_direct(voice, channel, addr);
+ lvl = val;
+
+ if (channel) m_writeregs_cb(regbase + 0x0c, uint8_t(addr)); // ra low mirror
+ else m_writeregs_cb(regbase + 0x0a, uint8_t(addr)); // la low mirror
- step_side1(0, voice, la, ra);
- step_side1(1, voice, la, ra);
+ pos = uint16_t(addr + 1);
+ };
+
+ prime_side(0);
+ prime_side(1);
return;
}
- else if (m_voice[voice].envmode == 3)
+ else if (m_voice[voice].env_mode == 3)
{
- // do nothing; VM3 decays from current register values
+ // VM3 starts from the register-programmed levels
+ m_voice[voice].env_vol_left = m_readregs_cb(base + 0xa);
+ m_voice[voice].env_vol_right = m_readregs_cb(base + 0xc);
}
+
+ m_voice[voice].log_env_started = 0;
+ m_voice[voice].log_env_stopped = 0;
+ m_voice[voice].log_env_paused = 0;
}
void xavix_sound_device::disable_voice(int voice)
{
m_stream->update();
m_voice[voice].enabled = 0;
+ m_voice[voice].log_env_started = 0;
+ m_voice[voice].log_env_stopped = 0;
+ m_voice[voice].log_env_paused = 0;
}
uint8_t xavix_sound_device::sound_volume_r()
@@ -349,45 +513,49 @@ uint8_t xavix_sound_device::sound_volume_r()
void xavix_sound_device::sound_volume_w(uint8_t data)
{
+ m_stream->update();
set_mastervol(data);
+ LOGMASKED(LOG_CFG, "[cfg] mixer mastervol=%u\n", unsigned(data));
}
uint8_t xavix_sound_device::sound_mixer_r()
{
- return (m_mix.monoural ? 0x80 : 0x00)
+ return (m_mix.monaural ? 0x80 : 0x00)
| ((m_mix.capacity & 0x03) << 4)
| (m_mix.amp & 0x07);
}
void xavix_sound_device::sound_mixer_w(uint8_t data)
{
- m_mix.monoural = (data >> 7) & 0x01;
- m_mix.capacity = (data >> 4) & 0x03;
- m_mix.amp = data & 0x07;
+ m_stream->update();
+ m_mix.monaural = BIT(data, 7);
+ m_mix.capacity = BIT(data, 4, 2);
+ m_mix.amp = BIT(data, 0, 3);
set_dac_gain(m_mix.amp);
- set_output_mode(m_mix.monoural);
+ set_output_mode(m_mix.monaural);
- LOG(" sound_mixer_w monoural=%d capacity=%d amp=%d\n",
- m_mix.monoural, m_mix.capacity, m_mix.amp);
+ LOGMASKED(LOG_CFG, "[cfg] mixer monaural=%d capacity=%d amp=%d\n",
+ m_mix.monaural, m_mix.capacity, m_mix.amp);
}
uint8_t xavix_sound_device::dac_control_r()
{
- return (m_mix.dac << 7)
- | ((m_mix.gap & 0x03) << 5)
- | ((m_mix.lead & 0x07) << 2)
- | (m_mix.lag & 0x03);
+ return (BIT(m_mix.dac, 0) << 7)
+ | (BIT(m_mix.gap, 0, 2) << 5)
+ | (BIT(m_mix.lead, 0, 3) << 2)
+ | BIT(m_mix.lag, 0, 2);
}
void xavix_sound_device::dac_control_w(uint8_t data)
{
- m_mix.dac = (data >> 7) & 0x01;
- m_mix.gap = (data >> 5) & 0x03;
- m_mix.lead = (data >> 2) & 0x07;
- m_mix.lag = data & 0x03;
+ m_stream->update();
+ m_mix.dac = BIT(data, 7);
+ m_mix.gap = BIT(data, 5, 2);
+ m_mix.lead = BIT(data, 2, 3);
+ m_mix.lag = BIT(data, 0, 2);
- LOG(" sound_dac_control_w dac=%d gap=%d lead=%d lag=%d\n",
+ LOGMASKED(LOG_CFG, "[cfg] dac dac=%d gap=%d lead=%d lag=%d\n",
m_mix.dac, m_mix.gap, m_mix.lead, m_mix.lag);
}
@@ -398,24 +566,50 @@ void xavix_sound_device::set_mastervol(uint8_t data)
void xavix_sound_device::set_dac_gain(uint8_t amp_data)
{
- static const int s_amp_table[8] = { 2, 4, 8, 12, 16, 20, 20, 20 };
- m_mix.gain = s_amp_table[amp_data & 0x07];
+ m_mix.gain = AMP_TABLE[amp_data & 0x07];
}
void xavix_sound_device::set_output_mode(bool mono)
{
- m_mix.monoural = mono;
+ m_mix.monaural = mono;
}
-uint32_t xavix_sound_device::tempo_to_period_samples(uint8_t tp) const
+uint32_t xavix_sound_device::tempo_to_period_ticks(uint8_t tp) const
{
- if (tp == 0)
- return 0; // paused
+ if (!tp) return 0; // paused
+ const uint32_t s = uint32_t(m_cyclerate_div) + 1; // cyclerate+1
+ uint64_t samples = (uint64_t(s) * 1024u + (tp / 2)) / tp; // round-nearest
+ if (samples < 1) samples = 1;
+ if (samples > 2'000'000u) samples = 2'000'000u;
+ return uint32_t(samples);
+}
- const uint32_t samplate_div = uint32_t(m_cyclerate_dev) + 1;
- const uint32_t tp_units = uint32_t(tp) << 4;
- const uint64_t period = uint64_t(samplate_div) * tp_units * 16u;
- return period > 2'000'000 ? 2'000'000u : uint32_t(period);
+uint32_t xavix_sound_device::envelope_period_ticks(uint8_t tp) const
+{
+ const uint32_t base = tempo_to_period_ticks(tp);
+ if (!base) return 0;
+ const uint64_t scaled = uint64_t(base) * 16u; // envelope cadence
+ return uint32_t(scaled > 64'000'000u ? 64'000'000u : scaled);
+}
+
+attotime xavix_sound_device::tempo_period(uint8_t tempo) const
+{
+ const uint32_t samples = tempo_to_period_ticks(tempo);
+ if (!samples) return attotime::never;
+ return attotime::from_ticks(samples, m_sequencer_rate_hz); // engine ticks
+}
+
+double xavix_sound_device::tempo_tick_hz(uint8_t tempo) const
+{
+ const uint32_t samples = tempo_to_period_ticks(tempo);
+ return samples ? (double(m_sequencer_rate_hz) / double(samples)) : 0.0;
+}
+
+uint32_t xavix_sound_device::phase_step_per_tick(uint32_t rate) const
+{
+ if (!rate) return 0;
+ uint64_t step = rate; // sequencer rate equals timing base
+ return uint32_t(step ? step : 1);
}
void xavix_sound_device::set_tempo(int index, uint8_t value)
@@ -426,9 +620,9 @@ void xavix_sound_device::set_tempo(int index, uint8_t value)
if (m_stream)
m_stream->update();
- m_tp_dev[index] = value; // 0 = pause
+ m_tempo_div[index] = value; // 0 = pause
- LOG(" [snd] tp%d <= %02x\n", index, m_tp_dev[index]);
+ LOGMASKED(LOG_TEMPO, "[tempo] tp[%d]=%02x\n", index, m_tempo_div[index]);
for (int v = 0; v < 16; v++)
{
@@ -437,7 +631,7 @@ void xavix_sound_device::set_tempo(int index, uint8_t value)
xavix_voice& vv = m_voice[v];
- const uint32_t newp = (value == 0) ? 0u : tempo_to_period_samples(value);
+ const uint32_t newp = (value == 0) ? 0u : envelope_period_ticks(value);
vv.env_period_samples = newp;
@@ -447,7 +641,7 @@ void xavix_sound_device::set_tempo(int index, uint8_t value)
vv.env_countdown = newp;
}
- LOG(" [snd] v=%d period -> %u (countdown=%u)\n", v, newp, vv.env_countdown);
+ LOGMASKED(LOG_TEMPO, "[tempo] v=%2d period=%u countdown=%u\n", v, newp, vv.env_countdown);
}
}
@@ -456,16 +650,17 @@ void xavix_sound_device::set_cyclerate(uint8_t value)
if (m_stream)
m_stream->update();
- m_cyclerate_dev = value;
+ m_cyclerate_div = value;
+ LOGMASKED(LOG_TEMPO, "[tempo] cyclerate=%02x\n", m_cyclerate_div);
- // Recompute per-voice periods from TPx with the new samplate
+ // Recompute per-voice periods
for (int v = 0; v < 16; v++)
{
xavix_voice& vv = m_voice[v];
- const uint8_t tp = m_tp_dev[v & 3];
+ const uint8_t tp = m_tempo_div[v & 3];
- const uint32_t oldp_raw = vv.env_period_samples; // may be 0
- const uint32_t newp = (tp == 0) ? 0 : tempo_to_period_samples(tp);
+ const uint32_t oldp_raw = vv.env_period_samples; // may be 0
+ const uint32_t newp = (tp == 0) ? 0 : envelope_period_ticks(tp);
if (oldp_raw && newp)
vv.env_countdown = (uint64_t)vv.env_countdown * newp / oldp_raw; // scale to next tick
@@ -480,13 +675,13 @@ uint8_t xavix_sound_device::fetch_env_byte(int voice, int channel, uint32_t idx)
{
const xavix_voice& v = m_voice[voice];
const uint16_t base = channel ? v.env_rom_base_right : v.env_rom_base_left;
- const uint32_t addr = (uint32_t(v.envbank) << 16) | uint32_t((base + (idx & 0xffff)) & 0xffff);
+ const uint32_t addr = (uint32_t(v.env_bank) << 16) | uint32_t((base + (idx & 0xffff)) & 0xffff);
return m_readsamples_cb(addr);
}
uint8_t xavix_sound_device::fetch_env_byte_direct(int voice, int channel, uint16_t addr)
{
- const uint8_t bank = m_voice[voice].envbank;
+ const uint8_t bank = m_voice[voice].env_bank;
const uint32_t rom = (uint32_t(bank) << 16) | uint32_t(addr);
const uint8_t val = m_readsamples_cb(rom);
@@ -499,94 +694,47 @@ uint8_t xavix_sound_device::fetch_env_byte_direct(int voice, int channel, uint16
return val;
}
-inline void xavix_sound_device::step_side_env_vm1(int channel, xavix_voice v, int voice)
-{
- if (channel ? !v.env_active_right : !v.env_active_left)
- return;
-
- const uint16_t base = channel ? v.env_rom_base_right : v.env_rom_base_left;
- uint32_t& phase = channel ? v.envpositionright : v.envpositionleft;
-
- const uint8_t ph = uint8_t(phase & 0x000f);
- const uint16_t addr = uint16_t((base & 0xfff0) | ph);
- const uint8_t lvl = fetch_env_byte_direct(voice, channel, addr);
-
- if (channel)
- v.env_vol_right = lvl;
- else
- v.env_vol_left = lvl;
-
- const int regbase = voice * 0x10;
-
- if (channel)
- m_writeregs_cb(regbase + 0x0c, uint8_t(addr)); // RA low
- else
- m_writeregs_cb(regbase + 0x0a, uint8_t(addr)); // LA low
-
- phase = uint16_t((phase & 0xfff0) | ((ph + 1) & 0x0f));
-};
-
-inline void xavix_sound_device::step_side_env_vm2(int channel, xavix_voice v, int voice)
-{
- if (channel ? !v.env_active_right : !v.env_active_left)
- return;
-
- uint32_t& pos = channel ? v.envpositionright : v.envpositionleft;
-
- const uint8_t val = fetch_env_byte_direct(voice, channel, pos);
-
- if (channel)
- v.env_vol_right = val;
- else
- v.env_vol_left = val;
-
- pos = uint16_t(pos + 1);
-};
-
-inline uint8_t xavix_sound_device::decay(uint8_t x)
-{
- return uint8_t(x - (x >> 4) - ((x & 0x0f) ? 1 : 0));
-};
-
void xavix_sound_device::step_envelope(int voice)
{
- // Per-voice one-shot logging flags, local to this function only.
- static bool s_logged_start[16] = { false };
- static bool s_logged_stop[16] = { false };
-
xavix_voice& v = m_voice[voice];
+ bool& logged_start = v.log_env_started;
+ bool& logged_stop = v.log_env_stopped;
+ bool& logged_pause = v.log_env_paused;
+
// If the voice is disabled, clear flags so next enable logs a fresh START.
if (!v.enabled)
{
- s_logged_start[voice] = false;
- s_logged_stop[voice] = false;
+ logged_start = false;
+ logged_stop = false;
+ logged_pause = false;
return;
}
// One-time START log when we first process an enabled voice.
- if (!s_logged_start[voice])
+ if (!logged_start)
{
const uint8_t group = voice & 3;
- const uint8_t tp = m_tp_dev[group];
- const uint32_t period = (tp == 0) ? 0u : tempo_to_period_samples(tp);
-
- LOG("[ENV START] v=%d vm=%u wm=%u gn=%u tp[%u]=%02x cr=%u period=%u\n"
- " envbank=%02x LA_base=%04x RA_base=%04x\n"
- " L=%02x R=%02x posL=%04x posR=%04x wbank=%02x waddr=%06x loop=%06x rate=%u\n",
- voice, (unsigned)(v.envmode & 3), (unsigned)(v.type & 3), (unsigned)(v.vol & 0x0f),
- (unsigned)group, (unsigned)tp, (unsigned)(m_cyclerate_dev + 1), (unsigned)period,
- (unsigned)v.envbank, (unsigned)v.env_rom_base_left, (unsigned)v.env_rom_base_right,
- (unsigned)v.env_vol_left, (unsigned)v.env_vol_right,
- (unsigned)(v.envpositionleft & 0xffff), (unsigned)(v.envpositionright & 0xffff),
- (unsigned)v.bank, (unsigned)((v.bank << 16) | (v.position >> 14)),
- (unsigned)(v.loopposition >> 14), (unsigned)v.rate);
-
- s_logged_start[voice] = true;
- s_logged_stop[voice] = false;
+ const uint8_t tp = m_tempo_div[group];
+ const uint32_t period = (tp == 0) ? 0u : envelope_period_ticks(tp);
+
+ LOGMASKED(LOG_ENV,
+ "[env] start v=%2d vm=%u wm=%u gn=%u tp[%u]=%02x cr=%u period=%u\n"
+ " env_bank=%02x la_base=%04x ra_base=%04x\n"
+ " l=%02x r=%02x posl=%04x posr=%04x wbank=%02x waddr=%06x loop=%06x rate=%u\n",
+ voice, unsigned(v.env_mode & 3), unsigned(v.type & 3), unsigned(v.vol & 0x0f),
+ unsigned(group), unsigned(tp), unsigned(m_cyclerate_div + 1), unsigned(period),
+ unsigned(v.env_bank), unsigned(v.env_rom_base_left), unsigned(v.env_rom_base_right),
+ unsigned(v.env_vol_left), unsigned(v.env_vol_right),
+ unsigned(v.env_pos_left & 0xffff), unsigned(v.env_pos_right & 0xffff),
+ unsigned(v.bank), unsigned((v.bank << 16) | (v.position >> 14)),
+ unsigned(v.loop_position >> 14), unsigned(v.rate));
+
+ logged_start = true;
+ logged_stop = false;
}
- const uint32_t target_period = tempo_to_period_samples(m_tp_dev[voice & 3]);
+ const uint32_t target_period = envelope_period_ticks(m_tempo_div[voice & 3]);
// Smooth re-tune to new period
if (v.env_period_samples != target_period)
@@ -598,89 +746,152 @@ void xavix_sound_device::step_envelope(int voice)
// Tick gate
if (v.env_period_samples == 0)
- return;
-
- if (v.env_countdown)
{
- v.env_countdown--;
- return;
+ if (!logged_pause)
+ {
+ LOGMASKED(LOG_ENV, "[env] pause v=%2d vm=%u tp=%02x\n",
+ voice, unsigned(v.env_mode & 3), unsigned(m_tempo_div[voice & 3]));
+ logged_pause = true;
+ }
+ return; // tempo paused (tp==0)
}
-
+ else if (logged_pause)
+ {
+ LOGMASKED(LOG_ENV, "[env] resume v=%2d vm=%u tp=%02x\n",
+ voice, unsigned(v.env_mode & 3), unsigned(m_tempo_div[voice & 3]));
+ logged_pause = false;
+ }
+ if (v.env_countdown) { v.env_countdown--; return; }
v.env_countdown = v.env_period_samples;
- // ---- VM0 : direct registers reflected into levels ----
- if (v.envmode == 0)
+ // VM0: direct registers reflected into levels
+ if (v.env_mode == 0)
{
const int base = voice * 0x10;
v.env_vol_left = m_readregs_cb(base + 0xa); // LA
v.env_vol_right = m_readregs_cb(base + 0xc); // RA
- if ((v.env_vol_left | v.env_vol_right) == 0 && !s_logged_stop[voice])
+ if ((v.env_vol_left | v.env_vol_right) == 0 && !logged_stop)
{
- LOG("[ENV STOP ] v=%d vm=0 wm=%u L=00 R=00 (VM0 both zero)\n",
- voice, (unsigned)(v.type & 3));
- s_logged_stop[voice] = true;
+ LOGMASKED(LOG_ENV, "[env] stop v=%2d vm=0 wm=%u L=00 R=00\n", voice, unsigned(v.type & 3));
+ logged_stop = true;
}
return;
}
- // ---- VM1 : nibble-table ----
- if (v.envmode == 1)
+ // VM1: nibble-table
+ if (v.env_mode == 1)
{
- step_side_env_vm1(0, v, voice);
- step_side_env_vm1(1, v, voice);
+ auto advance_nibble = [](uint16_t value) {
+ return uint16_t((value & 0xfff0) | ((value + 1) & 0x000f));
+ };
+
+ auto step_side = [&](int channel)
+ {
+ if (channel ? !v.env_active_right : !v.env_active_left)
+ return;
+
+ uint32_t &ptr32 = channel ? v.env_pos_right : v.env_pos_left;
+ const uint16_t ptr = uint16_t(ptr32);
+ const uint8_t level = fetch_env_byte_direct(voice, channel, ptr);
+
+ if (channel)
+ v.env_vol_right = level;
+ else
+ v.env_vol_left = level;
+
+ const uint16_t next_ptr = advance_nibble(ptr);
+ ptr32 = next_ptr;
+
+ const int regbase = voice * 0x10;
+ if (channel)
+ m_writeregs_cb(regbase + 0x0c, uint8_t(next_ptr));
+ else
+ m_writeregs_cb(regbase + 0x0a, uint8_t(next_ptr));
+ };
+
+ step_side(0);
+ step_side(1);
if ((v.env_vol_left | v.env_vol_right) == 0)
{
- if (!s_logged_stop[voice])
+ if (!logged_stop)
{
- LOG("[ENV STOP ] v=%d vm=1 wm=%u L=%02x R=%02x (VM1 L|R==0)\n",
- voice, (unsigned)(v.type & 3),
- (unsigned)v.env_vol_left, (unsigned)v.env_vol_right);
- s_logged_stop[voice] = true;
+ LOGMASKED(LOG_ENV, "[env] stop v=%2d vm=1 wm=%u L=%02x R=%02x\n",
+ voice, unsigned(v.type & 3), unsigned(v.env_vol_left), unsigned(v.env_vol_right));
+ logged_stop = true;
}
v.env_active_left = 0;
v.env_active_right = 0;
}
return;
}
-
- // ---- VM2 : linear ROM stream per side ----
- if (v.envmode == 2)
+ // VM2: linear ROM stream per side
+ if (v.env_mode == 2)
{
- step_side_env_vm2(0, v, voice);
- step_side_env_vm2(1, v, voice);
+ const int regbase = voice * 0x10;
+
+ auto step_side = [&](int channel)
+ {
+ if (channel ? !v.env_active_right : !v.env_active_left)
+ return;
+
+ uint32_t &ptr32 = channel ? v.env_pos_right : v.env_pos_left;
+ const uint16_t ptr = uint16_t(ptr32);
+ const uint8_t level = fetch_env_byte_direct(voice, channel, ptr);
+
+ if (channel)
+ v.env_vol_right = level;
+ else
+ v.env_vol_left = level;
+
+ if (channel)
+ m_writeregs_cb(regbase + 0x0c, uint8_t(ptr));
+ else
+ m_writeregs_cb(regbase + 0x0a, uint8_t(ptr));
+
+ ptr32 = uint16_t(ptr + 1);
+ };
+
+ step_side(0);
+ step_side(1);
if ((v.env_vol_left | v.env_vol_right) == 0)
{
- if (!s_logged_stop[voice])
+ if (!logged_stop)
{
- LOG("[ENV STOP ] v=%d vm=2 wm=%u L=%02x R=%02x (VM2 L|R==0)\n",
- voice, (unsigned)(v.type & 3),
- (unsigned)v.env_vol_left, (unsigned)v.env_vol_right);
- s_logged_stop[voice] = true;
+ LOGMASKED(LOG_ENV, "[env] stop v=%2d vm=2 wm=%u L=%02x R=%02x\n",
+ voice, unsigned(v.type & 3), unsigned(v.env_vol_left), unsigned(v.env_vol_right));
+ logged_stop = true;
}
- v.env_active_left = 0;
- v.env_active_right = 0;
+ v.env_active_left = false;
+ v.env_active_right = false;
}
return;
}
-
- // ---- VM3 : exponential-ish decay ----
- if (v.envmode == 3)
+ // VM3: exponential-ish decay
+ if (v.env_mode == 3)
{
+ auto decay = [](uint8_t x) -> uint8_t
+ {
+ const uint8_t high = x >> 4;
+ const uint8_t low = x & 0x0f;
+ const uint8_t subtract = high + (low ? 1 : 0);
+ return (subtract >= x) ? 0 : uint8_t(x - subtract);
+ };
+
v.env_vol_left = decay(v.env_vol_left);
v.env_vol_right = decay(v.env_vol_right);
if ((v.env_vol_left | v.env_vol_right) == 0)
{
- if (!s_logged_stop[voice])
+ if (!logged_stop)
{
- LOG("[ENV STOP ] v=%d vm=3 wm=%u (VM3 decayed to 0)\n",
- voice, (unsigned)(v.type & 3));
- s_logged_stop[voice] = true;
+ LOGMASKED(LOG_ENV, "[env] stop v=%2d vm=3 wm=%u (decayed)\n",
+ voice, unsigned(v.type & 3));
+ logged_stop = true;
}
- v.env_active_left = 0;
- v.env_active_right = 0;
+ v.env_active_left = false;
+ v.env_active_right = false;
}
}
}
@@ -696,64 +907,20 @@ void xavix_sound_device::step_pitch(int voice)
(uint16_t(m_readregs_cb(base + 0x01)) << 8) |
uint16_t(m_readregs_cb(base + 0x00));
- const uint32_t target = uint32_t(wave_control >> 2) & 0x3fff;
- uint32_t current = v.rate & 0x3fff;
-
- // Early out if matched
- if (current == target)
- return;
-
- const int32_t diff = int32_t(target) - int32_t(current);
- const bool up = (diff > 0);
-
- uint8_t j = up ? (m_mix.lead & 0x07) : (m_mix.lag & 0x03);
-
- if ((v.type & 0x3) == 1 && j > 0)
- j -= 1;
-
- uint32_t mag = uint32_t(up ? diff : -diff);
- uint32_t step = (j ? (mag >> j) : mag);
-
- if (j)
- {
- const uint32_t frac_mask = (1u << j) - 1u;
- const uint32_t frac = mag & frac_mask;
- m_pitch_countdown[voice] += frac;
- if (m_pitch_countdown[voice] >= (1u << j))
- {
- m_pitch_countdown[voice] -= (1u << j);
- step += 1;
- }
- }
- else
- {
- // No fraction when j==0; keep accumulator quiet
- m_pitch_countdown[voice] = 0;
- }
-
- // Enforce a minimum step so very small diffs still move
- const uint32_t min_step = uint32_t(m_mix.gap & 0x03) + 1u;
- if (step < min_step) step = min_step;
+ const uint32_t target = uint32_t(wave_control >> 2);
+ const uint32_t old = v.rate;
- // Apply step with no overshoot (borrow/carry guarded)
- if (up)
- {
- current += step;
- if (current > target)
- current = target;
- }
- else
- {
- if (current > step)
- current -= step;
- else
- current = 0;
+ if (v.rate < target) v.rate += 1;
+ else if (v.rate > target) v.rate -= 1;
- if (current < target)
- current = target;
- }
+ if (v.rate != old)
+ LOGMASKED(LOG_PITCH, "[pitch] v=%2d %u->%u target=%u\n",
+ voice, old, v.rate, target);
+}
- v.rate = current & 0x3fff;
+uint8_t xavix_state::sound_current_page() const
+{
+ return m_sound_regbase & 0x3f;
}
uint8_t xavix_state::sound_regram_read_cb(offs_t offset)
@@ -783,11 +950,12 @@ uint8_t xavix_state::sound_voice_startstop_r(offs_t offset)
void xavix_state::sound_voice_startstop_w(offs_t offset, uint8_t data)
{
+ LOGMASKED(LOG_VOICE, "[voice] startstop offs=%d data=%02x prev=%02x\n",
+ offset, data, m_soundreg16_0[offset]);
for (int i = 0; i < 8; i++)
{
- const int mask = (1 << i);
- const int voice_state = (data & mask);
- const int old_voice_state = (m_soundreg16_0[offset] & mask);
+ const int voice_state = BIT(data, i);
+ const int old_voice_state = BIT(m_soundreg16_0[offset], i);
if (voice_state != old_voice_state)
{
const int voice = (offset * 8 + i);
@@ -806,12 +974,13 @@ uint8_t xavix_state::sound_voice_updateenv_r(offs_t offset)
void xavix_state::sound_voice_updateenv_w(offs_t offset, uint8_t data)
{
+ LOGMASKED(LOG_ENV, "[env] update offs=%d mask=%02x\n", offset, data);
for (int i = 0; i < 8; i++)
{
- if (data & (1 << i))
+ if (BIT(data, i))
{
const int voice = (offset * 8 + i);
- m_sound->enable_voice(voice, true); // refresh params/envelope from regs/ROM
+ m_sound->enable_voice(voice, true);
}
}
}
@@ -837,14 +1006,8 @@ void xavix_state::sound_regbase_w(uint8_t data)
{
// upper 6 bits of RAM address where the per-voice register sets live
m_sound_regbase = data & 0x3f;
- //LOG("%s: sound_regbase_w %02x (sound regs at %02x00-%02xff)\n",
- // machine().describe_context(), data, m_sound_regbase & 0x3f, m_sound_regbase & 0x3f);
}
-//-------------------------------------------------
-// cyclerate / samplate (0x75F8)
-//-------------------------------------------------
-
uint8_t xavix_state::sound_cyclerate_r()
{
return m_cyclerate;
@@ -854,7 +1017,7 @@ void xavix_state::sound_cyclerate_w(uint8_t data)
{
m_cyclerate = data; // store for readback / debug
if (m_sound) m_sound->set_cyclerate(data);
- LOG(" sound_cyclerate_w %02x\n", m_cyclerate);
+ LOGMASKED(LOG_CFG, "[cfg] cyclerate=%02x\n", m_cyclerate);
}
uint8_t xavix_state::sound_volume_r() { return m_sound->sound_volume_r(); }
@@ -867,15 +1030,42 @@ uint8_t xavix_state::sound_dac_control_r() { return m_sound->dac_control_r(); }
void xavix_state::sound_dac_control_w(uint8_t data) { m_sound->dac_control_w(data); }
// tempo registers
-uint8_t xavix_state::sound_tp0_r() { LOG("%s: sound_tp0_r\n", machine().describe_context()); return m_tp[0]; }
-uint8_t xavix_state::sound_tp1_r() { LOG("%s: sound_tp1_r\n", machine().describe_context()); return m_tp[1]; }
-uint8_t xavix_state::sound_tp2_r() { LOG("%s: sound_tp2_r\n", machine().describe_context()); return m_tp[2]; }
-uint8_t xavix_state::sound_tp3_r() { LOG("%s: sound_tp3_r\n", machine().describe_context()); return m_tp[3]; }
+uint8_t xavix_state::sound_tp0_r() { return m_tp[0]; }
+uint8_t xavix_state::sound_tp1_r() { return m_tp[1]; }
+uint8_t xavix_state::sound_tp2_r() { return m_tp[2]; }
+uint8_t xavix_state::sound_tp3_r() { return m_tp[3]; }
+
+void xavix_state::sound_tp0_w(uint8_t data)
+{
+ m_tp[0] = data;
+ if (m_sound) m_sound->set_tempo(0, data);
+ LOGMASKED(LOG_TEMPO, "[tempo] tp[%d]=%02x\n", 0, data);
+ reprogram_sound_timer(0);
+}
+
+void xavix_state::sound_tp1_w(uint8_t data)
+{
+ m_tp[1] = data;
+ if (m_sound) m_sound->set_tempo(1, data);
+ LOGMASKED(LOG_TEMPO, "[tempo] tp[%d]=%02x\n", 1, data);
+ reprogram_sound_timer(1);
+}
+
+void xavix_state::sound_tp2_w(uint8_t data)
+{
+ m_tp[2] = data;
+ if (m_sound) m_sound->set_tempo(2, data);
+ LOGMASKED(LOG_TEMPO, "[tempo] tp[%d]=%02x\n", 2, data);
+ reprogram_sound_timer(2);
+}
-void xavix_state::sound_tp0_w(uint8_t data) { m_tp[0] = data; if (m_sound) m_sound->set_tempo(0, data); LOG(" sound_tp0_w %02x\n", data); }
-void xavix_state::sound_tp1_w(uint8_t data) { m_tp[1] = data; if (m_sound) m_sound->set_tempo(1, data); LOG(" sound_tp1_w %02x\n", data); }
-void xavix_state::sound_tp2_w(uint8_t data) { m_tp[2] = data; if (m_sound) m_sound->set_tempo(2, data); LOG(" sound_tp2_w %02x\n", data); }
-void xavix_state::sound_tp3_w(uint8_t data) { m_tp[3] = data; if (m_sound) m_sound->set_tempo(3, data); LOG(" sound_tp3_w %02x\n", data); }
+void xavix_state::sound_tp3_w(uint8_t data)
+{
+ m_tp[3] = data;
+ if (m_sound) m_sound->set_tempo(3, data);
+ LOGMASKED(LOG_TEMPO, "[tempo] tp[%d]=%02x\n", 3, data);
+ reprogram_sound_timer(3);
+}
uint8_t xavix_state::sound_irq_status_r()
{
@@ -885,75 +1075,94 @@ uint8_t xavix_state::sound_irq_status_r()
void xavix_state::sound_irq_status_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 ?
+ const uint8_t old_enable = m_sound_irqstatus & 0x0f;
- for (int t = 0; t < 4; t++)
- {
- int bit = (1 << t) << 4;
+ const uint8_t clear_mask = (data >> 4) & 0x0f;
+ if (clear_mask)
+ m_sound_irqstatus &= ~(clear_mask << 4);
- if (data & bit)
- {
- m_sound_irqstatus &= ~data & bit;
- }
- }
+ const uint8_t new_enable = data & 0x0f;
+ m_sound_irqstatus = (m_sound_irqstatus & 0xf0) | new_enable;
- // 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;
+ const uint8_t pending = (m_sound_irqstatus >> 4) & 0x0f;
+ LOGMASKED(LOG_IRQ, "[irq] status_w %02x old_en=%02x new_en=%02x clear=%02x pending=%02x\n",
+ data, old_enable, new_enable, clear_mask, pending);
- update_irqs();
-
-
- for (int t = 0; t < 4; t++)
+ const uint8_t changed = old_enable ^ new_enable;
+ if (changed)
{
- 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?
+ for (int t = 0; t < 4; t++)
+ if (changed & (1 << t))
+ reprogram_sound_timer(t);
+ }
- 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)
+ refresh_sound_irq_state();
+ update_irqs();
+}
- 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
+// 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
+ const uint8_t enable_mask = 1U << param;
+ if (!BIT(m_sound_irqstatus, param))
+ return;
- the usual clock divided by 2 would be 10.738636 but that's too high
- */
- attotime period = attotime::from_hz(10.3f * (m_tp[t]));
- m_sound_timer[t]->adjust(period, t, period);
- }
- else
- {
- m_sound_timer[t]->adjust(attotime::never, t);
- }
- }
- }
+ m_sound_irqstatus |= (enable_mask << 4);
+ LOGMASKED(LOG_TIMER, "[timer] %d latch pending=%02x\n",
+ param, (m_sound_irqstatus >> 4) & 0x0f);
+ refresh_sound_irq_state();
+ update_irqs();
+}
- // 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?
+void xavix_state::refresh_sound_irq_state()
+{
+ const uint8_t enable = m_sound_irqstatus & 0x0f;
+ const uint8_t pending = (m_sound_irqstatus >> 4) & 0x0f;
+ if (enable & pending)
+ m_irqsource |= 0x80;
+ else
+ m_irqsource &= ~0x80;
- //LOG("%s: sound_irqstatus_w %02x\n", machine().describe_context(), data);
+ LOGMASKED(LOG_IRQ, "[irq] line %s enable=%02x pending=%02x\n",
+ ((enable & pending) ? "assert" : "clear"), enable, pending);
}
-// used by ekara (UK cartridges), rad_bass, rad_crdn
-TIMER_CALLBACK_MEMBER(xavix_state::sound_timer_done)
+void xavix_state::reprogram_sound_timer(int index)
{
- // param = timer number 0,1,2 or 3
- const int bit = (1 << param) << 4;
- m_sound_irqstatus |= bit;
+ if (index < 0 || index >= 4)
+ return;
+ if (!m_sound_timer[index])
+ return;
- // if any of the sound timers are causing an interrupt...
- if (m_sound_irqstatus & 0xf0) m_irqsource |= 0x80;
- else m_irqsource &= ~0x80;
+ const uint8_t mask = 1U << index;
+ if (!(m_sound_irqstatus & mask))
+ {
+ LOGMASKED(LOG_TIMER, "[timer] %d stop (enable=0)\n", index);
+ m_sound_timer[index]->adjust(attotime::never, index);
+ return;
+ }
- update_irqs();
+ const uint8_t tempo = m_tp[index];
+ if (tempo == 0)
+ {
+ LOGMASKED(LOG_TIMER, "[timer] %d stop (tempo=0)\n", index);
+ m_sound_timer[index]->adjust(attotime::never, index);
+ return;
+ }
+
+ const double frequency = m_sound->tempo_tick_hz(tempo);
+ if (frequency <= 0.0)
+ {
+ LOGMASKED(LOG_TIMER, "[timer] %d stop (period unavailable)\n", index);
+ m_sound_timer[index]->adjust(attotime::never, index);
+ return;
+ }
+
+ const attotime period = attotime::from_hz(frequency);
+ const std::string period_text = period.as_string(18);
+ m_sound_timer[index]->adjust(period, index, period);
+ LOGMASKED(LOG_TIMER, "[timer] %d arm tempo=%02x freq=%.6fHz period=%s\n",
+ index, tempo, frequency, period_text.c_str());
}