summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/sound/sp0250.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/sound/sp0250.cpp')
-rw-r--r--src/devices/sound/sp0250.cpp234
1 files changed, 155 insertions, 79 deletions
diff --git a/src/devices/sound/sp0250.cpp b/src/devices/sound/sp0250.cpp
index 898bd778dad..03d5004f695 100644
--- a/src/devices/sound/sp0250.cpp
+++ b/src/devices/sound/sp0250.cpp
@@ -21,34 +21,32 @@
#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_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 +69,55 @@ sp0250_device::sp0250_device(const machine_config &mconfig, const char *tag, dev
void sp0250_device::device_start()
{
- m_RNG = 1;
+ // output PWM data at the ROMCLOCK frequency
+ int sample_rate = clock() / 2;
+ int frame_rate = sample_rate / (4 * PWM_CLOCKS);
+ if (!m_pwm_mode)
+ m_stream = machine().sound().stream_alloc(*this, 0, 1, frame_rate);
+ else
+ m_stream = machine().sound().stream_alloc(*this, 0, 1, sample_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
m_drq.resolve_safe();
if (!m_drq.isnull())
{
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);
+ timer_alloc()->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));
+ for (int index = 0; index < 6; index++)
+ {
+ save_item(NAME(m_filter[index].F), index);
+ save_item(NAME(m_filter[index].B), index);
+ save_item(NAME(m_filter[index].z1), index);
+ save_item(NAME(m_filter[index].z2), index);
+ }
+
+ // FIFO state
save_item(NAME(m_fifo));
save_item(NAME(m_fifo_pos));
}
+void sp0250_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
+{
+ m_stream->update();
+}
+
static uint16_t sp0250_ga(uint8_t v)
{
return (v & 0x1f) << (v>>5);
@@ -140,19 +164,11 @@ 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;
-}
-
-TIMER_CALLBACK_MEMBER( sp0250_device::timer_tick )
-{
- m_stream->update();
+ m_filter[f].reset();
}
void sp0250_device::write(uint8_t data)
@@ -175,6 +191,79 @@ uint8_t sp0250_device::drq_r()
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;
+ }
+ }
+
+ int16_t z0;
+ if (m_voiced)
+ z0 = (m_pcount == 0) ? m_amp : 0;
+ else
+ {
+ z0 = (m_lfsr & 1) ? m_amp : -m_amp;
+
+ // 15-bit LFSR algorithm verified by dump from actual hardware
+ m_lfsr ^= (m_lfsr ^ (m_lfsr >> 1)) << 15;
+ m_lfsr >>= 1;
+ }
+
+ 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);
+
+ m_pcount++;
+ if (m_pcount >= m_pitch)
+ {
+ m_pcount = 0;
+ m_rcount++;
+ }
+ return dac;
+}
+
//-------------------------------------------------
// sound_stream_update - handle a stream update
//-------------------------------------------------
@@ -182,62 +271,49 @@ uint8_t sp0250_device::drq_r()
void sp0250_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
stream_sample_t *output = outputs[0];
- for (int i = 0; i < samples; i++)
+ if (!m_pwm_mode)
{
- if (m_playing)
+ while (samples-- != 0)
+ *output++ = next() << 8;
+ }
+ else
+ {
+ while (samples != 0)
{
- 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
+ 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 = 32767;
+ 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;
+ 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 > samples)
+ remaining = samples;
+ m_pwm_index += remaining;
+ samples -= remaining;
+
+ // fill the output
+ while (remaining-- != 0)
+ *output++ = value;
}
}
}