diff --git a/src/mame/tvgames/xavix.cpp b/src/mame/tvgames/xavix.cpp index b98ccb85bba5a..3bc1888bc3fa8 100644 --- a/src/mame/tvgames/xavix.cpp +++ b/src/mame/tvgames/xavix.cpp @@ -217,19 +217,19 @@ void xavix_state::xavix_lowbus_map(address_map &map) map(0x7400, 0x757f).ram(); // Sound Control - map(0x75f0, 0x75f1).rw(FUNC(xavix_state::sound_startstop_r), FUNC(xavix_state::sound_startstop_w)); // r/w tested read/written 8 times in a row - map(0x75f2, 0x75f3).rw(FUNC(xavix_state::sound_updateenv_r), FUNC(xavix_state::sound_updateenv_w)); - map(0x75f4, 0x75f5).r(FUNC(xavix_state::sound_sta16_r)); // related to 75f0 / 75f1 (read after writing there - rad_mtrk) - map(0x75f6, 0x75f6).rw(FUNC(xavix_state::sound_volume_r), FUNC(xavix_state::sound_volume_w)); // r/w tested - map(0x75f7, 0x75f7).w(FUNC(xavix_state::sound_regbase_w)); - map(0x75f8, 0x75f8).rw(FUNC(xavix_state::sound_75f8_r), FUNC(xavix_state::sound_75f8_w)); // r/w tested - map(0x75f9, 0x75f9).rw(FUNC(xavix_state::sound_75f9_r), FUNC(xavix_state::sound_75f9_w)); - map(0x75fa, 0x75fa).rw(FUNC(xavix_state::sound_timer0_r), FUNC(xavix_state::sound_timer0_w)); // r/w tested - map(0x75fb, 0x75fb).rw(FUNC(xavix_state::sound_timer1_r), FUNC(xavix_state::sound_timer1_w)); // r/w tested - map(0x75fc, 0x75fc).rw(FUNC(xavix_state::sound_timer2_r), FUNC(xavix_state::sound_timer2_w)); // r/w tested - map(0x75fd, 0x75fd).rw(FUNC(xavix_state::sound_timer3_r), FUNC(xavix_state::sound_timer3_w)); // r/w tested - map(0x75fe, 0x75fe).rw(FUNC(xavix_state::sound_irqstatus_r), FUNC(xavix_state::sound_irqstatus_w)); - map(0x75ff, 0x75ff).w(FUNC(xavix_state::sound_75ff_w)); + map(0x75f0, 0x75f1).rw(FUNC(xavix_state::sound_voice_startstop_r), FUNC(xavix_state::sound_voice_startstop_w)); + map(0x75f2, 0x75f3).rw(FUNC(xavix_state::sound_voice_updateenv_r), FUNC(xavix_state::sound_voice_updateenv_w)); + map(0x75f4, 0x75f5).r(FUNC(xavix_state::sound_voice_status_r)); + map(0x75f6, 0x75f6).rw(FUNC(xavix_state::sound_volume_r), FUNC(xavix_state::sound_volume_w)); + map(0x75f7, 0x75f7).rw(FUNC(xavix_state::sound_regbase_r), FUNC(xavix_state::sound_regbase_w)); + map(0x75f8, 0x75f8).rw(FUNC(xavix_state::sound_cyclerate_r), FUNC(xavix_state::sound_cyclerate_w)); + map(0x75f9, 0x75f9).rw(FUNC(xavix_state::sound_mixer_r), FUNC(xavix_state::sound_mixer_w)); + map(0x75fa, 0x75fa).rw(FUNC(xavix_state::sound_tp0_r), FUNC(xavix_state::sound_tp0_w)); + map(0x75fb, 0x75fb).rw(FUNC(xavix_state::sound_tp1_r), FUNC(xavix_state::sound_tp1_w)); + map(0x75fc, 0x75fc).rw(FUNC(xavix_state::sound_tp2_r), FUNC(xavix_state::sound_tp2_w)); + map(0x75fd, 0x75fd).rw(FUNC(xavix_state::sound_tp3_r), FUNC(xavix_state::sound_tp3_w)); + map(0x75fe, 0x75fe).rw(FUNC(xavix_state::sound_irq_status_r), FUNC(xavix_state::sound_irq_status_w)); + map(0x75ff, 0x75ff).rw(FUNC(xavix_state::sound_dac_control_r), FUNC(xavix_state::sound_dac_control_w)); // Slot Registers map(0x7810, 0x7810).w(FUNC(xavix_state::slotreg_7810_w)); // startup @@ -1624,6 +1624,7 @@ void xavix_state::xavix(machine_config &config) XAVIX_SOUND(config, m_sound, MAIN_CLOCK); m_sound->read_regs_callback().set(FUNC(xavix_state::sound_regram_read_cb)); m_sound->read_samples_callback().set(FUNC(xavix_state::sample_read)); + m_sound->write_regs_callback().set(FUNC(xavix_state::sound_regram_write_cb)); //m_sound->add_route(ALL_OUTPUTS, "mono", 1.0); m_sound->add_route(0, "speaker", 1.0, 0); m_sound->add_route(1, "speaker", 1.0, 1); diff --git a/src/mame/tvgames/xavix.h b/src/mame/tvgames/xavix.h index e98cb00189977..4c2306674db05 100644 --- a/src/mame/tvgames/xavix.h +++ b/src/mame/tvgames/xavix.h @@ -36,38 +36,119 @@ class xavix_sound_device : public device_t, public device_sound_interface xavix_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); auto read_regs_callback() { return m_readregs_cb.bind(); } + auto write_regs_callback() { return m_writeregs_cb.bind(); } // <-- for vm1 LA/RA mirroring auto read_samples_callback() { return m_readsamples_cb.bind(); } + // control API void enable_voice(int voice, bool update_only); void disable_voice(int voice); bool is_voice_enabled(int voice); + // rate handling + void set_tempo(int index, uint8_t value); + void set_cyclerate(uint8_t value); + + // register handlers (from the CPU map) + uint8_t sound_volume_r(); + void sound_volume_w(uint8_t data); + uint8_t sound_mixer_r(); + void sound_mixer_w(uint8_t data); + uint8_t dac_control_r(); + void dac_control_w(uint8_t data); + + // helpers + void set_dac_gain(uint8_t amp_data); + void set_output_mode(bool mono); + void set_mastervol(uint8_t data); + protected: // device-level overrides virtual void device_start() override ATTR_COLD; virtual void device_reset() override ATTR_COLD; - // sound stream update overrides + // sound stream update virtual void sound_stream_update(sound_stream &stream) override; private: - sound_stream *m_stream = nullptr; + // stream + sound_stream* m_stream = nullptr; + + // global timing + uint8_t m_tp_dev[4] = { 0, 0, 0, 0 }; + uint8_t m_cyclerate_dev = 1; + + // callbacks + devcb_read8 m_readregs_cb; + devcb_write8 m_writeregs_cb; // used to mirror vm1 phase to LA/RA + devcb_read8 m_readsamples_cb; + // voice state struct xavix_voice { - bool enabled[2]{}; - uint32_t position[2]{}; - uint32_t startposition[2]{}; - uint8_t bank = 0; // no samples appear to cross a bank boundary, so likely wraps - int type = 0; - int rate = 0; - int vol = 0; + uint8_t enabled = 0; + uint32_t position = 0; + uint32_t loopposition = 0; + uint32_t loopendposition = 0; + uint32_t startposition = 0; + + uint32_t envpositionleft = 0; + uint32_t envpositionright = 0; + uint8_t envbank = 0; + uint8_t envmode = 0; + + uint8_t bank = 0; + uint32_t rate = 0; + uint8_t type = 0; + uint8_t vol = 0; + + uint16_t env_rom_base_left = 0; + uint16_t env_rom_base_right = 0; + + uint8_t env_vol_left = 0xff; + uint8_t env_vol_right = 0xff; + + uint32_t env_period_samples = 0; + uint32_t env_countdown = 0; + uint8_t env_active_left = 1; + uint8_t env_active_right = 1; + + // misc (vm1/vm2 helpers, tickers) + uint8_t env_phase = 0; + + uint8_t la_byte = 0; // shadow of LA (low address byte) + uint8_t ra_byte = 0; // shadow of RA (low address byte) }; - devcb_read8 m_readregs_cb; + // mixer state + struct xavix_mixer + { + uint8_t monoural = 0; + uint8_t capacity = 0; + uint8_t amp = 2; + + uint8_t dac = 0; + uint8_t gap = 0; + uint8_t lead = 0; + uint8_t lag = 0; - devcb_read8 m_readsamples_cb; + uint8_t mastervol = 0xff; + int32_t gain = 2; + }; + xavix_mixer m_mix; xavix_voice m_voice[16]; + + uint32_t m_pitch_countdown[16]; + + // helpers + uint32_t tempo_to_period_samples(uint8_t tp) const; + uint8_t decay(uint8_t x); + void step_envelope(int voice); + uint8_t fetch_env_byte(int voice, int channel, uint32_t idx); + uint8_t fetch_env_byte_direct(int voice, int channel, uint16_t addr); + void step_pitch(int voice); + void step_side1(int channel, int voice, const uint8_t la, const uint8_t ra); + void step_side_env_vm1(int channel, xavix_voice v, int voice); + void step_side_env_vm2(int channel, xavix_voice v, int voice); }; DECLARE_DEVICE_TYPE(XAVIX_SOUND, xavix_sound_device) @@ -385,36 +466,39 @@ class xavix_state : public driver_device uint8_t dispctrl_6ff8_r(); void dispctrl_6ff8_w(uint8_t data); - uint8_t sound_startstop_r(offs_t offset); - void sound_startstop_w(offs_t offset, uint8_t data); - uint8_t sound_updateenv_r(offs_t offset); - void sound_updateenv_w(offs_t offset, uint8_t data); + uint8_t sound_voice_startstop_r(offs_t offset); + void sound_voice_startstop_w(offs_t offset, uint8_t data); + uint8_t sound_voice_updateenv_r(offs_t offset); + void sound_voice_updateenv_w(offs_t offset, uint8_t data); + + uint8_t sound_voice_status_r(offs_t offset); - uint8_t sound_sta16_r(offs_t offset); - uint8_t sound_75f5_r(); uint8_t sound_volume_r(); void sound_volume_w(uint8_t data); + uint8_t sound_regbase_r(); void sound_regbase_w(uint8_t data); - uint8_t sound_75f8_r(); - void sound_75f8_w(uint8_t data); + uint8_t sound_cyclerate_r(); + void sound_cyclerate_w(uint8_t data); + + uint8_t sound_mixer_r(); + void sound_mixer_w(uint8_t data); - uint8_t sound_75f9_r(); - void sound_75f9_w(uint8_t data); + uint8_t sound_tp0_r(); + void sound_tp0_w(uint8_t data); + uint8_t sound_tp1_r(); + void sound_tp1_w(uint8_t data); + uint8_t sound_tp2_r(); + void sound_tp2_w(uint8_t data); + uint8_t sound_tp3_r(); + void sound_tp3_w(uint8_t data); - uint8_t sound_timer0_r(); - void sound_timer0_w(uint8_t data); - uint8_t sound_timer1_r(); - void sound_timer1_w(uint8_t data); - uint8_t sound_timer2_r(); - void sound_timer2_w(uint8_t data); - uint8_t sound_timer3_r(); - void sound_timer3_w(uint8_t data); + uint8_t sound_irq_status_r(); + void sound_irq_status_w(uint8_t data); + void sound_dac_control_w(uint8_t data); + uint8_t sound_dac_control_r(); - uint8_t sound_irqstatus_r(); - void sound_irqstatus_w(uint8_t data); - void sound_75ff_w(uint8_t data); uint8_t m_sound_irqstatus = 0; uint8_t m_soundreg16_0[2]{}; uint8_t m_soundreg16_1[2]{}; @@ -534,11 +618,10 @@ class xavix_state : public driver_device uint8_t m_6ff0 = 0; uint8_t m_video_ctrl = 0; - uint8_t m_mastervol = 0; - uint8_t m_unk_snd75f8 = 0; - uint8_t m_unk_snd75f9 = 0; + uint8_t m_cyclerate = 0; + uint8_t m_mixer = 0; uint8_t m_unk_snd75ff = 0; - uint8_t m_sndtimer[4]{}; + uint8_t m_tp[4]{}; uint8_t m_timer_baseval = 0; @@ -614,6 +697,7 @@ class xavix_state : public driver_device int get_current_address_byte(); uint8_t sound_regram_read_cb(offs_t offset); + void sound_regram_write_cb(offs_t offset, uint8_t data); uint8_t m_extbusctrl[3]{}; diff --git a/src/mame/tvgames/xavix2.cpp b/src/mame/tvgames/xavix2.cpp index 0a08f18554e2f..90b7e30a2a48f 100644 --- a/src/mame/tvgames/xavix2.cpp +++ b/src/mame/tvgames/xavix2.cpp @@ -499,18 +499,14 @@ void xavix2_state::pio_update() void naruto_state::pio_update() { - if (BIT(m_pio_mask_out, 21)) - m_i2cmem->write_sda(BIT(m_pio_dataw, 21)); - if (BIT(m_pio_mask_out, 20)) - m_i2cmem->write_scl(BIT(m_pio_dataw, 20)); + m_i2cmem->write_sda(BIT(m_pio_mask_out, 21) ? BIT(m_pio_dataw, 21) : 1); + m_i2cmem->write_scl(BIT(m_pio_mask_out, 20) ? BIT(m_pio_dataw, 20) : 0); } void domyos_state::pio_update() { - if (BIT(m_pio_mask_out, 16)) - m_i2cmem->write_sda(BIT(m_pio_dataw, 16)); - if (BIT(m_pio_mask_out, 17)) - m_i2cmem->write_scl(BIT(m_pio_dataw, 17)); + m_i2cmem->write_sda(BIT(m_pio_mask_out, 16) ? BIT(m_pio_dataw, 16) : 1); + m_i2cmem->write_scl(BIT(m_pio_mask_out, 17) ? BIT(m_pio_dataw, 17) : 0); } void xavix2_state::pio_w(offs_t offset, u32 data, u32 mem_mask) diff --git a/src/mame/tvgames/xavix_2002.cpp b/src/mame/tvgames/xavix_2002.cpp index cc9603014b789..84c2cc4d74478 100644 --- a/src/mame/tvgames/xavix_2002.cpp +++ b/src/mame/tvgames/xavix_2002.cpp @@ -419,12 +419,8 @@ void superxavix_i2c_jmat_state::write_extended_io1(offs_t offset, uint8_t data, { LOG("%s: io1_data_w %02x\n", machine().describe_context(), data); - if (mem_mask & 0x08) - m_i2cmem->write_sda((data & 0x08) >> 3); - - if (mem_mask & 0x10) - m_i2cmem->write_scl((data & 0x10) >> 4); - + m_i2cmem->write_sda(BIT(mem_mask, 3) ? BIT(data, 3) : 1); + m_i2cmem->write_scl(BIT(mem_mask, 4) ? BIT(data, 4) : 0); } void superxavix_i2c_jmat_state::write_extended_io2(offs_t offset, uint8_t data, uint8_t mem_mask) diff --git a/src/mame/tvgames/xavix_a.cpp b/src/mame/tvgames/xavix_a.cpp index 2baf87b1de6ec..d5591bfb7f389 100644 --- a/src/mame/tvgames/xavix_a.cpp +++ b/src/mame/tvgames/xavix_a.cpp @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:David Haywood +// copyright-holders:Ramacat, David Haywood #include "emu.h" #include "xavix.h" @@ -7,10 +7,6 @@ // #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) @@ -18,6 +14,7 @@ xavix_sound_device::xavix_sound_device(const machine_config &mconfig, const char , device_sound_interface(mconfig, *this) , m_stream(nullptr) , m_readregs_cb(*this, 0xff) + , m_writeregs_cb(*this) , m_readsamples_cb(*this, 0x80) { } @@ -25,69 +22,195 @@ 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); + + save_item(NAME(m_tp_dev)); + save_item(NAME(m_pitch_countdown)); + save_item(NAME(m_cyclerate_dev)); + + 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, 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, 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_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)); } void xavix_sound_device::device_reset() { + m_cyclerate_dev = 0x0f; + + // hack, does not seem to get set up properly? + for (int i = 0; i < 4; i++) + if (m_tp_dev[i] == 0) + m_tp_dev[i] = 1; + 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; + m_voice[v].enabled = 0; + m_voice[v].position = 0; + m_voice[v].loopposition = 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_countdown = m_voice[v].env_period_samples; + m_voice[v].env_active_left = 1; + m_voice[v].env_active_right = 1; + + m_pitch_countdown[v] = 0; } -} + m_mix.monoural = 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; +} 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; - // loop while we still have samples to generate - int samples = stream.samples(); - while (samples-- != 0) + int num_samples = stream.samples(); + + while (num_samples-- != 0) { - for (int channel = 0; channel < 2; channel++) + int64_t total_l = 0; + int64_t total_r = 0; + + // visit voices in order hardware reads seem to indicate + for (int idx = 0; idx < 16; idx++) { - for (int v = 0; v < 16; v++) + const int v = kMuxVisitOrder[idx]; + if (!m_voice[v].enabled) + continue; + + int32_t sample = 0; + uint8_t raw = 0; + uint8_t wv = 0x80; + + // WM1 (square) or ROM path + if (m_voice[v].type == 1) { - if (m_voice[v].enabled[channel] == true) + const bool sq = ((m_voice[v].position >> 14) & 1) != 0; + wv = sq ? 0x81 : 0x7f; + sample = int32_t(wv) - 128; + } + else + { + const uint32_t pos = (m_voice[v].bank << 16) | (m_voice[v].position >> 14); + raw = m_readsamples_cb(pos); + + if (raw == 0x80) { - // 2 is looping? 3 is single shot? 0/1 are something else? - if (m_voice[v].type == 2 || m_voice[v].type == 3) + sample = 0; + if (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); - } - */ - } - - stream.add_int(channel, outpos, sample * (m_voice[v].vol + 1), 32768); - m_voice[v].position[channel] += m_voice[v].rate; + m_voice[v].enabled = 0; + continue; } - else + else if (m_voice[v].type == 2) { - logerror("unsupported voice type %01x", m_voice[v].type); - m_voice[v].enabled[channel] = false; + if ((m_voice[v].position >> 14) != (m_voice[v].loopposition >> 14)) + m_voice[v].position = m_voice[v].loopposition; } } + else + { + 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); + 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 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; + + // engine work for this voice happens in this slot + step_envelope(v); + step_pitch(v); + + if (m_voice[v].env_vol_left == 0 && m_voice[v].env_vol_right == 0) + { + m_voice[v].enabled = 0; + continue; } } + + total_l *= m_mix.gain; + total_r *= m_mix.gain; + + 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) + { + 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++; } } @@ -95,356 +218,672 @@ void xavix_sound_device::sound_stream_update(sound_stream &stream) 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; + 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(); - int voicemembase = voice * 0x10; + const int base = voice * 0x10; + + // 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); + + // 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) + + // 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_rom_base_left = env_addr_left; + m_voice[voice].env_rom_base_right = env_addr_right; + + // Update-only: do NOT re-init pointers/values mid-stream + if (update_only) + 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 + 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) + { + const int base = voice * 0x10; + 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) + { + // Start from the configured 16-bit addresses (not LA/RA mirrors) + const uint16_t start_l = env_addr_left; + const uint16_t start_r = env_addr_right; + + // 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); + m_voice[voice].env_vol_left = v_l; + 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); + + // Keep both sides active; engine will step from envposition{L,R} + m_voice[voice].env_active_left = 1; + m_voice[voice].env_active_right = 1; + + // Mirror current pointer low bytes to LA/RA (hardware behavior) + const int regbase = voice * 0x10; + m_writeregs_cb(regbase + 0x0a, uint8_t(start_l)); // LA low + m_writeregs_cb(regbase + 0x0c, uint8_t(start_r)); // RA low + return; + } - 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? + else if (m_voice[voice].envmode == 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 - // 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?) + step_side1(0, voice, la, ra); + step_side1(1, voice, la, ra); + return; + } + else if (m_voice[voice].envmode == 3) + { + // do nothing; VM3 decays from current register values + } +} - uint32_t sampleaddrleft_full = (sampleaddrleft | sampleaddrbank << 16) & 0x00ffffff; // definitely addresses based on rad_snow - uint32_t sampleaddrright_full = (sampleaddrright | sampleaddrbank << 16) & 0x00ffffff; +void xavix_sound_device::disable_voice(int voice) +{ + m_stream->update(); + m_voice[voice].enabled = 0; +} - 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 xavix_sound_device::sound_volume_r() +{ + return m_mix.mastervol; +} - uint8_t envmode = (envmode_unk >> 4)&3; // upper bits not used? - uint8_t envunk = envmode_unk & 0x0f; +void xavix_sound_device::sound_volume_w(uint8_t data) +{ + set_mastervol(data); +} - if (update_only) LOG("(UPDATE ONLY) "); +uint8_t xavix_sound_device::sound_mixer_r() +{ + return (m_mix.monoural ? 0x80 : 0x00) + | ((m_mix.capacity & 0x03) << 4) + | (m_mix.amp & 0x07); +} - 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); +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; - 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 + set_dac_gain(m_mix.amp); + set_output_mode(m_mix.monoural); + + LOG(" sound_mixer_w monoural=%d capacity=%d amp=%d\n", + m_mix.monoural, 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); +} + +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; + + LOG(" sound_dac_control_w dac=%d gap=%d lead=%d lag=%d\n", + m_mix.dac, m_mix.gap, m_mix.lead, m_mix.lag); +} + +void xavix_sound_device::set_mastervol(uint8_t data) +{ + m_mix.mastervol = 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]; +} + +void xavix_sound_device::set_output_mode(bool mono) +{ + m_mix.monoural = mono; +} + +uint32_t xavix_sound_device::tempo_to_period_samples(uint8_t tp) const +{ + if (tp == 0) + return 0; // paused + + 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); +} + +void xavix_sound_device::set_tempo(int index, uint8_t value) +{ + if (index < 0 || index > 3) + return; + + if (m_stream) + m_stream->update(); + + m_tp_dev[index] = value; // 0 = pause + + LOG(" [snd] tp%d <= %02x\n", index, m_tp_dev[index]); + + for (int v = 0; v < 16; v++) { - // 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 + if ((v & 3) != index) + continue; - 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); + xavix_voice& vv = m_voice[v]; - LOG(" (ENV1 "); - for (int i = 0; i < 8; i++) - { - uint8_t env = m_readsamples_cb(envaddrleft_full+i); - LOG("%02x ", env); - } - LOG(") "); + const uint32_t newp = (value == 0) ? 0u : tempo_to_period_samples(value); - LOG(" (ENV2 "); - for (int i = 0; i < 8; i++) + vv.env_period_samples = newp; + + if (newp) { - uint8_t env = m_readsamples_cb(envaddrright_full+i); - LOG("%02x ", env); + // running (or changed rate): arm a full period before the next tick + vv.env_countdown = newp; } - LOG(") \n"); + LOG(" [snd] v=%d period -> %u (countdown=%u)\n", v, newp, vv.env_countdown); } +} - if (envmode_unk & 0xc0) - { - LOG(" (unexpected bits set in envmode_unk %02x)\n", envmode_unk & 0xc0); - } +void xavix_sound_device::set_cyclerate(uint8_t value) +{ + if (m_stream) + m_stream->update(); + + m_cyclerate_dev = value; - if (!update_only) + // Recompute per-voice periods from TPx with the new samplate + for (int v = 0; v < 16; v++) { - 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]; - } + xavix_voice& vv = m_voice[v]; + const uint8_t tp = m_tp_dev[v & 3]; - // 0320 (800) == 8000hz - // 4000 (16384) == 163840hz ? = 163.840kHz + const uint32_t oldp_raw = vv.env_period_samples; // may be 0 + const uint32_t newp = (tp == 0) ? 0 : tempo_to_period_samples(tp); - // samples appear to be PCM, 0x80 terminated, looks like there's a way of specifying mono (interleave channels?) or stereo, maybe in master regs? + if (oldp_raw && newp) + vv.env_countdown = (uint64_t)vv.env_countdown * newp / oldp_raw; // scale to next tick + else if (!oldp_raw && newp) + vv.env_countdown = newp; // arm one full period + + vv.env_period_samples = newp; + } } -void xavix_sound_device::disable_voice(int voice) +uint8_t xavix_sound_device::fetch_env_byte(int voice, int channel, uint32_t idx) { - m_voice[voice].enabled[0] = false; - m_voice[voice].enabled[1] = false; + 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); + 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 uint32_t rom = (uint32_t(bank) << 16) | uint32_t(addr); + const uint8_t val = m_readsamples_cb(rom); + + // One-shot “first fetch” debug per voice & side to verify addressing. + static bool first_seen[16][2] = { { false } }; + const int side = channel ? 1 : 0; + if (!first_seen[voice][side]) + first_seen[voice][side] = true; -// xavix_state support + return val; +} -uint8_t xavix_state::sound_regram_read_cb(offs_t offset) +inline void xavix_sound_device::step_side_env_vm1(int channel, xavix_voice v, int voice) { - // 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; + if (channel ? !v.env_active_right : !v.env_active_left) + return; - return m_mainram[memorybase + offset]; - } + const uint16_t base = channel ? v.env_rom_base_right : v.env_rom_base_left; + uint32_t& phase = channel ? v.envpositionright : v.envpositionleft; - return 0x00; -} + 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)); +}; -/* 75f0, 75f1 - 2x8 bits (16 voices?) */ -uint8_t xavix_state::sound_startstop_r(offs_t offset) +inline void xavix_sound_device::step_side_env_vm2(int channel, xavix_voice v, int voice) { - LOG("%s: sound_startstop_r %02x\n", machine().describe_context(), offset); - return m_soundreg16_0[offset]; -} + if (channel ? !v.env_active_right : !v.env_active_left) + return; -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); + 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 - 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); + v.env_vol_left = val; + pos = uint16_t(pos + 1); +}; - for (int i = 0; i < 8; i++) +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]; + + // If the voice is disabled, clear flags so next enable logs a fresh START. + if (!v.enabled) { - 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); + s_logged_start[voice] = false; + s_logged_stop[voice] = false; + return; + } - if (voice_state) - { - m_sound->enable_voice(voice, false); - } - else - { - m_sound->disable_voice(voice); - } - } + // One-time START log when we first process an enabled voice. + if (!s_logged_start[voice]) + { + 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; } - m_soundreg16_0[offset] = data; -} + const uint32_t target_period = tempo_to_period_samples(m_tp_dev[voice & 3]); -/* 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]; -} + // Smooth re-tune to new period + if (v.env_period_samples != target_period) + { + const uint32_t oldp = v.env_period_samples ? v.env_period_samples : 1; + v.env_countdown = (uint64_t)v.env_countdown * target_period / oldp; + v.env_period_samples = target_period; + } -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); + // Tick gate + if (v.env_period_samples == 0) + return; - // used to update envelopes without restarting sound? for key-off events? - for (int i = 0; i < 8; i++) + if (v.env_countdown) { - const int voice_state = (data & (1 << i)); - const int old_voice_state = (m_soundreg16_1[offset] & (1 << i)); - if (voice_state != old_voice_state) + v.env_countdown--; + return; + } + + v.env_countdown = v.env_period_samples; + + // ---- VM0 : direct registers reflected into levels ---- + if (v.envmode == 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]) { - const int voice = (offset * 8 + i); + 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; + } + return; + } - if (voice_state) + // ---- VM1 : nibble-table ---- + if (v.envmode == 1) + { + step_side_env_vm1(0, v, voice); + step_side_env_vm1(1, v, voice); + + if ((v.env_vol_left | v.env_vol_right) == 0) + { + if (!s_logged_stop[voice]) { - m_sound->enable_voice(voice, true); + 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; } - else + v.env_active_left = 0; + v.env_active_right = 0; + } + return; + } + + // ---- VM2 : linear ROM stream per side ---- + if (v.envmode == 2) + { + step_side_env_vm2(0, v, voice); + step_side_env_vm2(1, v, voice); + + if ((v.env_vol_left | v.env_vol_right) == 0) + { + if (!s_logged_stop[voice]) { - m_sound->disable_voice(voice); + 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; } + v.env_active_left = 0; + v.env_active_right = 0; } + return; } - m_soundreg16_1[offset] = data; -} + // ---- VM3 : exponential-ish decay ---- + if (v.envmode == 3) + { + 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]) + { + LOG("[ENV STOP ] v=%d vm=3 wm=%u (VM3 decayed to 0)\n", + voice, (unsigned)(v.type & 3)); + s_logged_stop[voice] = true; + } + v.env_active_left = 0; + v.env_active_right = 0; + } + } +} -/* 75f4, 75f5 - 2x8 bits (16 voices?) status? */ -uint8_t xavix_state::sound_sta16_r(offs_t offset) +void xavix_sound_device::step_pitch(int voice) { - uint8_t ret = 0x00; + xavix_voice& v = m_voice[voice]; + if (!v.enabled) + return; - 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; - } + const int base = voice * 0x10; + const uint16_t wave_control = + (uint16_t(m_readregs_cb(base + 0x01)) << 8) | + uint16_t(m_readregs_cb(base + 0x00)); - return ret; -} + const uint32_t target = uint32_t(wave_control >> 2) & 0x3fff; + uint32_t current = v.rate & 0x3fff; -/* 75f6 - master volume control? */ -uint8_t xavix_state::sound_volume_r() -{ - LOG("%s: sound_volume_r\n", machine().describe_context()); - return m_mastervol; -} + // Early out if matched + if (current == target) + return; -void xavix_state::sound_volume_w(uint8_t data) -{ - m_mastervol = data; - LOG("%s: sound_volume_w %02x\n", machine().describe_context(), data); -} + const int32_t diff = int32_t(target) - int32_t(current); + const bool up = (diff > 0); -/* 75f7 - main register base*/ + uint8_t j = up ? (m_mix.lead & 0x07) : (m_mix.lag & 0x03); -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); -} + if ((v.type & 0x3) == 1 && j > 0) + j -= 1; -/* 75f8, 75f9 - misc unknown sound regs*/ + uint32_t mag = uint32_t(up ? diff : -diff); + uint32_t step = (j ? (mag >> j) : mag); -uint8_t xavix_state::sound_75f8_r() -{ - LOG("%s: sound_75f8_r\n", machine().describe_context()); - return m_unk_snd75f8; + 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; + + // 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 (current < target) + current = target; + } + + v.rate = current & 0x3fff; } -void xavix_state::sound_75f8_w(uint8_t data) +uint8_t xavix_state::sound_regram_read_cb(offs_t offset) { - m_unk_snd75f8 = data; - LOG("%s: sound_75f8_w %02x\n", machine().describe_context(), data); + // 0x00 would be zero page memory; assume it's not valid + if ((m_sound_regbase & 0x3f) != 0x00) + { + const uint16_t memorybase = (m_sound_regbase & 0x3f) << 8; + return m_mainram[memorybase + offset]; + } + return 0x00; } -uint8_t xavix_state::sound_75f9_r() +void xavix_state::sound_regram_write_cb(offs_t offset, u8 data) { - LOG("%s: sound_75f9_r\n", machine().describe_context()); - return m_unk_snd75f9; + if ((m_sound_regbase & 0x3f) != 0x00) + { + const uint16_t memorybase = (m_sound_regbase & 0x3f) << 8; + m_mainram[memorybase + offset] = data; + } } -void xavix_state::sound_75f9_w(uint8_t data) +uint8_t xavix_state::sound_voice_startstop_r(offs_t offset) { - m_unk_snd75f9 = data; - LOG("%s: sound_75f9_w %02x\n", machine().describe_context().c_str(), data); + return m_soundreg16_0[offset]; } -/* 75fa, 75fb, 75fc, 75fd - timers?? generate interrupts?? */ - -uint8_t xavix_state::sound_timer0_r() +void xavix_state::sound_voice_startstop_w(offs_t offset, uint8_t data) { - LOG("%s: sound_timer0_r\n", machine().describe_context()); - return m_sndtimer[0]; + 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); + 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; } -void xavix_state::sound_timer0_w(uint8_t data) +uint8_t xavix_state::sound_voice_updateenv_r(offs_t offset) { - m_sndtimer[0] = data; - LOG("%s: sound_timer0_w %02x\n", machine().describe_context(), data); + // On real hardware, might be read-only or always return 0. + return 0x00; } -uint8_t xavix_state::sound_timer1_r() +void xavix_state::sound_voice_updateenv_w(offs_t offset, uint8_t data) { - LOG("%s: sound_timer1_r\n", machine().describe_context()); - return m_sndtimer[1]; + for (int i = 0; i < 8; i++) + { + if (data & (1 << i)) + { + const int voice = (offset * 8 + i); + m_sound->enable_voice(voice, true); // refresh params/envelope from regs/ROM + } + } } -void xavix_state::sound_timer1_w(uint8_t data) +uint8_t xavix_state::sound_voice_status_r(offs_t offset) { - m_sndtimer[1] = data; - LOG("%s: sound_timer1_w %02x\n", machine().describe_context(), data); + uint8_t ret = 0x00; + for (int i = 0; i < 8; i++) + { + const int voice = (offset * 8 + i); + if (m_sound->is_voice_enabled(voice)) + ret |= 1 << i; + } + return ret; } -uint8_t xavix_state::sound_timer2_r() +uint8_t xavix_state::sound_regbase_r() { - LOG("%s: sound_timer2_r\n", machine().describe_context()); - return m_sndtimer[2]; + return m_sound_regbase & 0x3f; // upper bits read as 0 } -void xavix_state::sound_timer2_w(uint8_t data) +void xavix_state::sound_regbase_w(uint8_t data) { - m_sndtimer[2] = data; - LOG("%s: sound_timer2_w %02x\n", machine().describe_context(), 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); } -uint8_t xavix_state::sound_timer3_r() +//------------------------------------------------- +// cyclerate / samplate (0x75F8) +//------------------------------------------------- + +uint8_t xavix_state::sound_cyclerate_r() { - LOG("%s: sound_timer3_r\n", machine().describe_context()); - return m_sndtimer[3]; + return m_cyclerate; } -void xavix_state::sound_timer3_w(uint8_t data) // this one is used by ekara (uk carts) , enabled with 08 in 75fe +void xavix_state::sound_cyclerate_w(uint8_t data) { - m_sndtimer[3] = data; - LOG("%s: sound_timer3_w %02x\n", machine().describe_context(), data); + m_cyclerate = data; // store for readback / debug + if (m_sound) m_sound->set_cyclerate(data); + LOG(" sound_cyclerate_w %02x\n", m_cyclerate); } -/* 75fe - some kind of IRQ status / Timer Status? */ +uint8_t xavix_state::sound_volume_r() { return m_sound->sound_volume_r(); } +void xavix_state::sound_volume_w(uint8_t data) { m_sound->sound_volume_w(data); } + +uint8_t xavix_state::sound_mixer_r() { return m_sound->sound_mixer_r(); } +void xavix_state::sound_mixer_w(uint8_t data) { m_sound->sound_mixer_w(data); } -uint8_t xavix_state::sound_irqstatus_r() +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]; } + +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); } + +uint8_t xavix_state::sound_irq_status_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) + // UK e-kara carts check the upper nibble for sound-timer IRQ source return m_sound_irqstatus; } -void xavix_state::sound_irqstatus_w(uint8_t data) +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 ? @@ -488,7 +927,7 @@ void xavix_state::sound_irqstatus_w(uint8_t data) 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])); + attotime period = attotime::from_hz(10.3f * (m_tp[t])); m_sound_timer[t]->adjust(period, t, period); } else @@ -502,29 +941,19 @@ void xavix_state::sound_irqstatus_w(uint8_t data) 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); + //LOG("%s: sound_irqstatus_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; + const 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; + if (m_sound_irqstatus & 0xf0) m_irqsource |= 0x80; + else m_irqsource &= ~0x80; update_irqs(); } diff --git a/src/mame/tvgames/xavix_m.cpp b/src/mame/tvgames/xavix_m.cpp index c32a60f94ef18..e118ce6230c46 100644 --- a/src/mame/tvgames/xavix_m.cpp +++ b/src/mame/tvgames/xavix_m.cpp @@ -497,29 +497,16 @@ void xavix_state::write_io1(uint8_t data, uint8_t direction) void xavix_i2c_state::write_io1(uint8_t data, uint8_t direction) { - if (direction & 0x08) - { - m_i2cmem->write_sda((data & 0x08) >> 3); - } - - if (direction & 0x10) - { - m_i2cmem->write_scl((data & 0x10) >> 4); - } + m_i2cmem->write_sda(BIT(direction, 3) ? BIT(data, 3) : 1); + m_i2cmem->write_scl(BIT(direction, 4) ? BIT(data, 4) : 0); } // ltv_tam void xavix_i2c_ltv_tam_state::write_io1(uint8_t data, uint8_t direction) { - if (direction & 0x08) - { - m_i2cmem->write_sda((data & 0x08) >> 3); - } + m_i2cmem->write_sda(BIT(direction, 3) ? BIT(data, 3) : 1); + m_i2cmem->write_scl(BIT(direction, 2) ? BIT(data, 2) : 0); - if (direction & 0x04) - { - m_i2cmem->write_scl((data & 0x04) >> 2); - } } void xavix_i2c_mj_state::write_io1(uint8_t data, uint8_t direction) @@ -531,15 +518,8 @@ void xavix_i2c_mj_state::write_io1(uint8_t data, uint8_t direction) // for taikodp void xavix_i2c_cart_state::write_io1(uint8_t data, uint8_t direction) { - if (direction & 0x08) - { - m_i2cmem->write_sda((data & 0x08) >> 3); - } - - if (direction & 0x10) - { - m_i2cmem->write_scl((data & 0x10) >> 4); - } + m_i2cmem->write_sda(BIT(direction, 3) ? BIT(data, 3) : 1); + m_i2cmem->write_scl(BIT(direction, 4) ? BIT(data, 4) : 0); } void xavix_ekara_state::write_io0(uint8_t data, uint8_t direction) @@ -568,8 +548,8 @@ void xavix_popira2_cart_state::write_io1(uint8_t data, uint8_t direction) { if (m_cartslot->has_cart()) { - m_cartslot->write_sda((data & 0x08) >> 3); - m_cartslot->write_scl((data & 0x10) >> 4); + m_cartslot->write_sda(BIT(direction, 3) ? BIT(data, 3) : 1); + m_cartslot->write_scl(BIT(direction, 4) ? BIT(data, 4) : 0); } } @@ -585,8 +565,8 @@ void xavix_evio_cart_state::write_io1(uint8_t data, uint8_t direction) { if (m_cartslot->has_cart()) { - m_cartslot->write_sda((data & 0x10) >> 4); - m_cartslot->write_scl((data & 0x20) >> 5); + m_cartslot->write_sda(BIT(direction, 4) ? BIT(data, 4) : 1); + m_cartslot->write_scl(BIT(direction, 5) ? BIT(data, 5) : 0); } } @@ -892,11 +872,10 @@ void xavix_state::machine_start() save_item(NAME(m_arena_control)); save_item(NAME(m_6ff0)); save_item(NAME(m_video_ctrl)); - save_item(NAME(m_mastervol)); - save_item(NAME(m_unk_snd75f8)); - save_item(NAME(m_unk_snd75f9)); + save_item(NAME(m_cyclerate)); + save_item(NAME(m_mixer)); save_item(NAME(m_unk_snd75ff)); - save_item(NAME(m_sndtimer)); + save_item(NAME(m_tp)); save_item(NAME(m_timer_baseval)); save_item(NAME(m_spritereg)); } @@ -942,14 +921,13 @@ void xavix_state::machine_reset() m_spritereg = 0; - m_mastervol = 0x00; - m_unk_snd75f8 = 0x00; - m_unk_snd75f9 = 0x00; + m_cyclerate = 0x00; + m_mixer = 0x00; m_unk_snd75ff = 0x00; for (int i = 0; i < 4; i++) { - m_sndtimer[i] = 0x00; + m_tp[i] = 0x00; } std::fill(std::begin(m_spritefragment_dmaparam1), std::end(m_spritefragment_dmaparam1), 0x00);