diff options
Diffstat (limited to 'src/devices/sound/sp0250.cpp')
-rw-r--r-- | src/devices/sound/sp0250.cpp | 251 |
1 files changed, 157 insertions, 94 deletions
diff --git a/src/devices/sound/sp0250.cpp b/src/devices/sound/sp0250.cpp index 9894b2cbe6c..763fc5b11e0 100644 --- a/src/devices/sound/sp0250.cpp +++ b/src/devices/sound/sp0250.cpp @@ -5,50 +5,40 @@ By O. Galibert. - Unknown: - - Exact clock divider - - Exact noise algorithm - - Exact noise pitch (probably ok) - - 7 bits output mapping - - Whether the pitch starts counting from 0 or 1 - Unimplemented: - Direct Data test mode (pin 7) - - Sound quite reasonably already though. */ #include "emu.h" #include "sp0250.h" -/* -standard external clock is 3.12MHz -the chip provides a 445.7kHz output clock, which is = 3.12MHz / 7 -therefore I expect the clock divider to be a multiple of 7 -Also there are 6 cascading filter stages so I expect the divider to be a multiple of 6. - -The SP0250 manual states that the original speech is sampled at 10kHz, so the divider -should be 312, but 312 = 39*8 so it doesn't look right because a divider by 39 is unlikely. +// +// Input clock is divided by 2 to make ROMCLOCK. +// Output is via pulse-width modulation (PWM) over the course of 39 ROMCLOCKs. +// 4 PWM periods per frame. +// +static constexpr int PWM_CLOCKS = 39; -7*6*8 = 336 gives a 9.286kHz sample rate and matches the samples from the Sega boards. -*/ -#define CLOCK_DIVIDER (7*6*8) DEFINE_DEVICE_TYPE(SP0250, sp0250_device, "sp0250", "GI SP0250 LPC") sp0250_device::sp0250_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, SP0250, tag, owner, clock), device_sound_interface(mconfig, *this), + m_timer(nullptr), + m_pwm_mode(false), + m_pwm_index(PWM_CLOCKS), + m_pwm_count(0), + m_pwm_counts(0), + m_voiced(0), m_amp(0), + m_lfsr(0x7fff), m_pitch(0), - m_repeat(0), m_pcount(0), + m_repeat(0), m_rcount(0), - m_playing(0), - m_RNG(0), - m_stream(nullptr), - m_voiced(0), m_fifo_pos(0), + m_stream(nullptr), m_drq(*this) { for (auto & elem : m_fifo) @@ -71,29 +61,54 @@ sp0250_device::sp0250_device(const machine_config &mconfig, const char *tag, dev void sp0250_device::device_start() { - m_RNG = 1; - m_drq.resolve_safe(); - if (!m_drq.isnull()) + // output PWM data at the ROMCLOCK frequency + int sample_rate = clock() / 2; + int frame_rate = sample_rate / (4 * PWM_CLOCKS); + m_stream = stream_alloc(0, 1, m_pwm_mode ? sample_rate : frame_rate); + + // if a DRQ callback is offered, run a timer at the frame rate + // to ensure the DRQ gets picked up in a timely manner + if (!m_drq.isunset()) { m_drq(ASSERT_LINE); - m_tick_timer= machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(sp0250_device::timer_tick), this)); - m_tick_timer->adjust(attotime::from_hz(clock()) * CLOCK_DIVIDER, 0, attotime::from_hz(clock()) * CLOCK_DIVIDER); + attotime period = attotime::from_hz(frame_rate); + m_timer = timer_alloc(FUNC(sp0250_device::delayed_stream_update), this); + m_timer->adjust(period, 0, period); } - m_stream = machine().sound().stream_alloc(*this, 0, 1, clock() / CLOCK_DIVIDER); + // PWM state + save_item(NAME(m_pwm_index)); + save_item(NAME(m_pwm_count)); + save_item(NAME(m_pwm_counts)); + // LPC state + save_item(NAME(m_voiced)); save_item(NAME(m_amp)); + save_item(NAME(m_lfsr)); save_item(NAME(m_pitch)); - save_item(NAME(m_repeat)); save_item(NAME(m_pcount)); + save_item(NAME(m_repeat)); save_item(NAME(m_rcount)); - save_item(NAME(m_playing)); - save_item(NAME(m_RNG)); - save_item(NAME(m_voiced)); + + save_item(STRUCT_MEMBER(m_filter, F)); + save_item(STRUCT_MEMBER(m_filter, B)); + save_item(STRUCT_MEMBER(m_filter, z1)); + save_item(STRUCT_MEMBER(m_filter, z2)); + + // FIFO state save_item(NAME(m_fifo)); save_item(NAME(m_fifo_pos)); } +//------------------------------------------------- +// device_reset - device-specific reset +//------------------------------------------------- + +void sp0250_device::device_reset() +{ + load_values(); +} + static uint16_t sp0250_ga(uint8_t v) { return (v & 0x1f) << (v>>5); @@ -140,22 +155,14 @@ void sp0250_device::load_values() m_filter[5].F = sp0250_gc(m_fifo[14]); m_fifo_pos = 0; m_drq(ASSERT_LINE); - m_pcount = 0; m_rcount = 0; for (int f = 0; f < 6; f++) - m_filter[f].z1 = m_filter[f].z2 = 0; - - m_playing = 1; + m_filter[f].reset(); } -TIMER_CALLBACK_MEMBER( sp0250_device::timer_tick ) -{ - m_stream->update(); -} - -WRITE8_MEMBER( sp0250_device::write ) +void sp0250_device::write(uint8_t data) { m_stream->update(); if (m_fifo_pos != 15) @@ -169,75 +176,131 @@ WRITE8_MEMBER( sp0250_device::write ) } -uint8_t sp0250_device::drq_r() +int sp0250_device::drq_r() { m_stream->update(); return (m_fifo_pos == 15) ? CLEAR_LINE : ASSERT_LINE; } +int8_t sp0250_device::next() +{ + if (m_rcount >= m_repeat) + { + if (m_fifo_pos == 15) + load_values(); + else + { + // According to http://www.cpcwiki.eu/index.php/SP0256_Measured_Timings + // the SP0250 executes "NOPs" with a repeat count of 1 and unchanged + // pitch while waiting for input + m_repeat = 1; + m_pcount = 0; + m_rcount = 0; + } + } + + // 15-bit LFSR algorithm verified by dump from actual hardware + // clocks every cycle regardless of voiced/unvoiced setting + m_lfsr ^= (m_lfsr ^ (m_lfsr >> 1)) << 15; + m_lfsr >>= 1; + + int16_t z0; + if (m_voiced) + z0 = (m_pcount == 0) ? m_amp : 0; + else + z0 = (m_lfsr & 1) ? m_amp : -m_amp; + + for (int f = 0; f < 6; f++) + z0 = m_filter[f].apply(z0); + + // maximum amp value is effectively 13 bits + // reduce to 7 bits; due to filter effects it + // may occasionally clip + int dac = z0 >> 6; + if (dac < -64) + dac = -64; + if (dac > 63) + dac = 63; + + // PWM is divided into 4x 5-bit sections; the lower + // bits of the original 7-bit value are added to only + // some of the pulses in the following pattern: + // + // DAC -64 -> 1,1,1,1 + // DAC -63 -> 2,1,1,1 + // DAC -62 -> 2,1,2,1 + // DAC -61 -> 2,2,2,1 + // DAC -60 -> 2,2,2,2 + // ... + // DAC -1 -> 17,17,17,16 + // DAC 0 -> 17,17,17,17 + // DAC 1 -> 18,17,17,17 + // ... + // DAC 60 -> 32,32,32,32 + // DAC 61 -> 33,32,32,32 + // DAC 62 -> 33,32,33,32 + // DAC 63 -> 33,33,33,32 + m_pwm_counts = (((dac + 68 + 3) >> 2) << 0) + + (((dac + 68 + 1) >> 2) << 8) + + (((dac + 68 + 2) >> 2) << 16) + + (((dac + 68 + 0) >> 2) << 24); + + if (m_pcount++ == m_pitch) + { + m_pcount = 0; + m_rcount++; + } + return dac; +} + //------------------------------------------------- // sound_stream_update - handle a stream update //------------------------------------------------- -void sp0250_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) +void sp0250_device::sound_stream_update(sound_stream &stream) { - stream_sample_t *output = outputs[0]; - for (int i = 0; i < samples; i++) + if (!m_pwm_mode) { - if (m_playing) + for (int sampindex = 0; sampindex < stream.samples(); sampindex++) + stream.put_int(0, sampindex, next(), 128); + } + else + { + for (int sampindex = 0; sampindex < stream.samples(); ) { - int16_t z0; - - if (m_voiced) + // see where we're at in the current PWM cycle + if (m_pwm_index >= PWM_CLOCKS) { - if(!m_pcount) - z0 = m_amp; - else - z0 = 0; - } - else - { - // Borrowing the ay noise generation LFSR - if(m_RNG & 1) - { - z0 = m_amp; - m_RNG ^= 0x24000; - } - else - z0 = -m_amp; - - m_RNG >>= 1; + m_pwm_index = 0; + if (m_pwm_counts == 0) + next(); + m_pwm_count = m_pwm_counts & 0xff; + m_pwm_counts >>= 8; } - for (int f = 0; f < 6; f++) + // determine the value to fill and the number of samples remaining + // until it changes + sound_stream::sample_t value; + int remaining; + if (m_pwm_index < m_pwm_count) { - z0 += ((m_filter[f].z1 * m_filter[f].F) >> 8) - + ((m_filter[f].z2 * m_filter[f].B) >> 9); - m_filter[f].z2 = m_filter[f].z1; - m_filter[f].z1 = z0; + value = 1.0; + remaining = m_pwm_count - m_pwm_index; } - - // Physical resolution is only 7 bits, but heh - - // max amplitude is 0x0f80 so we have margin to push up the output - output[i] = z0 << 3; - - m_pcount++; - if (m_pcount >= m_pitch) + else { - m_pcount = 0; - m_rcount++; - if (m_rcount >= m_repeat) - m_playing = 0; + value = 0.0; + remaining = PWM_CLOCKS - m_pwm_index; } - } - else - output[i] = 0; - if (!m_playing) - { - if(m_fifo_pos == 15) - load_values(); + // clamp to the number of samples requested and advance the counters + if (remaining > stream.samples() - sampindex) + remaining = stream.samples() - sampindex; + m_pwm_index += remaining; + + // fill the output + while (remaining-- != 0) + stream.put(0, sampindex++, value); } } } |