sound/upd933.cpp: Use a timer to activate interrupt output. (#11750)

* This decouples the interrupt output from the sound update cycle so it's timed correctly.
* Also improved interrupt priority handling.
This commit is contained in:
Devin Acker 2023-11-18 10:41:19 -05:00 committed by GitHub
parent 61fe61537a
commit a8015550ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 121 additions and 37 deletions

View File

@ -9,6 +9,7 @@
#include "upd933.h"
#include <algorithm>
#include <climits>
#include <cmath>
DEFINE_DEVICE_TYPE(UPD933, upd933_device, "upd933", "NEC uPD933")
@ -25,6 +26,8 @@ void upd933_device::device_start()
{
m_stream = stream_alloc(0, 1, clock() / CLOCKS_PER_SAMPLE);
m_irq_timer = timer_alloc(FUNC(upd933_device::timer_tick), this);
for (int i = 0; i < 0x800; i++)
m_cosine[i] = 0xfff * (1 - cos(2.0 * M_PI * i / 0x7ff)) / 2;
@ -44,16 +47,17 @@ void upd933_device::device_start()
m_volume[i] = pow(2 << VOLUME_SHIFT, (double)i / 0x1ff);
m_volume[0] = 0;
m_cs = 1;
m_cs = m_id = 1;
save_item(NAME(m_irq_pending));
save_item(NAME(m_irq_state));
save_item(NAME(m_cs));
save_item(NAME(m_id));
save_item(NAME(m_sound_data));
save_item(NAME(m_sound_data_pos));
save_item(NAME(m_sound_regs));
save_item(NAME(m_sample_count));
save_item(NAME(m_last_sample));
save_item(NAME(m_irq_data));
save_item(STRUCT_MEMBER(m_voice, m_wave));
save_item(STRUCT_MEMBER(m_voice, m_window));
@ -88,10 +92,17 @@ void upd933_device::device_start()
save_item(STRUCT_MEMBER(m_dco, m_current));
}
/**************************************************************************/
TIMER_CALLBACK_MEMBER(upd933_device::timer_tick)
{
m_irq_pending = 1;
update_irq();
}
/**************************************************************************/
void upd933_device::device_reset()
{
m_irq_state = 0;
m_irq_pending = m_irq_state = 0;
m_sound_data[0] = m_sound_data[1] = 0;
m_sound_data_pos = 0;
@ -104,7 +115,6 @@ void upd933_device::device_reset()
m_sample_count = 0;
m_last_sample = 0;
m_irq_data = 0;
}
/**************************************************************************/
@ -113,40 +123,86 @@ void upd933_device::device_clock_changed()
m_stream->set_sample_rate(clock() / CLOCKS_PER_SAMPLE);
}
/**************************************************************************/
int upd933_device::rq_r()
{
if (!machine().side_effects_disabled())
m_stream->update();
return m_irq_state;
}
/**************************************************************************/
void upd933_device::cs_w(int state)
{
m_stream->update();
if (!m_cs && state)
check_irq();
update_pending_irq();
m_cs = state;
update_irq();
}
/**************************************************************************/
void upd933_device::check_irq()
void upd933_device::id_w(int state)
{
m_stream->update();
m_id = state;
update_irq();
}
/**************************************************************************/
u8 upd933_device::irq_data() const
{
// TODO: do these have the correct priority?
for (int i = 0; i < 8; i++)
{
if (m_dco[i].m_irq)
return 4 | (i << 3);
}
for (int i = 0; i < 8; i++)
{
if (m_dcw[i].m_irq)
return 2 | (i << 2);
}
for (int i = 0; i < 8; i++)
{
if (m_dca[i].m_irq)
{
m_irq_data = 1 | (i << 1);
break;
return 1 | (i << 1);
}
if (m_dcw[i].m_irq)
return 0;
}
/**************************************************************************/
void upd933_device::update_pending_irq()
{
m_irq_pending = 0;
bool env_active = false;
unsigned new_time = UINT_MAX;
for (int i = 0; i < 8; i++)
{
m_irq_data = 2 | (i << 2);
break;
}
if (m_dco[i].m_irq)
{
m_irq_data = 4 | (i << 3);
break;
}
env_active |= (m_dca[i].calc_timeout(new_time)
| m_dco[i].calc_timeout(new_time)
| m_dcw[i].calc_timeout(new_time));
}
if (m_irq_data)
m_irq_cb(m_irq_state = 1);
if (!new_time)
m_irq_pending = 1;
else if (env_active)
m_irq_timer->adjust(clocks_to_attotime((u64)new_time * CLOCKS_PER_SAMPLE));
}
/**************************************************************************/
void upd933_device::update_irq()
{
u8 const irq_state = m_cs & m_id & m_irq_pending;
if (irq_state != m_irq_state)
{
m_irq_state = irq_state;
m_irq_cb(m_irq_state);
}
}
/**************************************************************************/
@ -155,7 +211,7 @@ u8 upd933_device::read()
if (!machine().side_effects_disabled())
m_stream->update();
return m_cs ? 0xff : m_irq_data;
return m_cs ? 0xff : irq_data();
}
/**************************************************************************/
@ -167,9 +223,6 @@ void upd933_device::write(u8 data)
{
m_stream->update();
m_irq_data = 0;
m_irq_cb(m_irq_state = 0);
const u8 reg = m_sound_data[0];
const u16 value = m_sound_regs[reg] = (m_sound_data[1] << 8) | data;
@ -263,9 +316,12 @@ void upd933_device::write(u8 data)
mod_voice.m_mute_other = BIT(value, 2);
break;
case 0x13: // 98-9f
// unknown, but cz101 sets these to zero when starting a note, probably to reset the oscillator
voice.m_position = value << PITCH_SHIFT;
case 0x13: // 98-9f: phase counter
/*
cz101 sets these to zero when starting a note to reset the oscillator.
cz1 writes 0x0000, 0x0080, 0x0100, or 0x0180 for up to four voices of a tone instead
*/
voice.m_position = value << (PITCH_SHIFT - 4);
break;
default:
@ -303,9 +359,6 @@ void upd933_device::sound_stream_update(sound_stream &stream, std::vector<read_s
outputs[0].put_int_clamp(i, sample, 1 << 15);
m_sample_count++;
if (!m_irq_data && m_cs)
check_irq();
}
}
@ -504,6 +557,30 @@ void upd933_device::env_t::update()
m_irq = true;
}
/**************************************************************************/
bool upd933_device::env_t::calc_timeout(unsigned &samples)
{
if (m_sustain || !m_rate)
{
return false;
}
else if (m_irq)
{
samples = 0;
}
else
{
const unsigned remaining = m_direction ? (m_current - m_target) : (m_target - m_current);
unsigned new_time = remaining / m_rate;
if (remaining % m_rate)
new_time++;
if (new_time < samples)
samples = new_time;
}
return true;
}
/**************************************************************************/
void upd933_device::update_pitch_step(int vnum)
{

View File

@ -19,8 +19,9 @@ public:
auto irq_cb() { return m_irq_cb.bind(); }
int rq_r() const { return m_irq_state; }
int rq_r();
void cs_w(int state); // chip select, active low
void id_w(int state); // irq disable, active low
void write(u8 data);
u8 read();
@ -48,6 +49,8 @@ private:
u32 m_rate = 0, m_target = 0, m_current = 0;
void update();
// calculate the next time this envelope generates an interrupt
bool calc_timeout(unsigned &samples);
};
struct voice_t
@ -61,8 +64,12 @@ private:
s16 m_pm_level = 0;
};
TIMER_CALLBACK_MEMBER(timer_tick);
s16 update(int vnum);
void check_irq();
u8 irq_data() const;
void update_pending_irq();
void update_irq();
u32 env_rate(u8 data) const;
void update_pitch_step(int vnum);
@ -71,8 +78,9 @@ private:
static constexpr unsigned CLOCKS_PER_SAMPLE = 112;
devcb_write_line m_irq_cb;
u8 m_irq_state;
u8 m_cs;
u8 m_irq_pending, m_irq_state;
u8 m_cs, m_id;
emu_timer *m_irq_timer;
u16 m_cosine[0x800];
u32 m_pitch[0x80];
@ -85,7 +93,6 @@ private:
u32 m_sample_count;
s16 m_last_sample;
u8 m_irq_data;
std::array<voice_t, 8> m_voice;
std::array<env_t, 8> m_dca, m_dco, m_dcw;