summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/sound/ymf278b.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/sound/ymf278b.cpp')
-rw-r--r--src/devices/sound/ymf278b.cpp296
1 files changed, 88 insertions, 208 deletions
diff --git a/src/devices/sound/ymf278b.cpp b/src/devices/sound/ymf278b.cpp
index 042b8951022..9c27849ee1e 100644
--- a/src/devices/sound/ymf278b.cpp
+++ b/src/devices/sound/ymf278b.cpp
@@ -50,7 +50,6 @@
#include "emu.h"
#include "ymf278b.h"
-#include "ymf262.h"
#include <algorithm>
@@ -58,6 +57,18 @@
#define LOG(x) do { if (VERBOSE) logerror x; } while (0)
+// Using the nominal datasheet frequency of 33.868MHz, the output of
+// the chip will be clock/768 = 44.1kHz. However, the FM engine is
+// clocked internally at clock/(19*36), or 49.515kHz, so the FM output
+// needs to be downsampled. The calculations below produce the fractional
+// number of extra FM samples we need to consume for each output sample,
+// as a 0.24 fixed point fraction.
+static constexpr double NOMINAL_CLOCK = 33868800;
+static constexpr double NOMINAL_FM_RATE = NOMINAL_CLOCK / double(ymopl4_registers::DEFAULT_PRESCALE * ymopl4_registers::OPERATORS);
+static constexpr double NOMINAL_OUTPUT_RATE = NOMINAL_CLOCK / 768.0;
+static constexpr uint32_t FM_STEP = uint32_t((NOMINAL_FM_RATE / NOMINAL_OUTPUT_RATE - 1.0) * double(1 << 24));
+
+
/**************************************************************************/
int ymf278b_device::compute_rate(YMF278BSlot *slot, int val)
@@ -221,17 +232,6 @@ void ymf278b_device::sound_stream_update(sound_stream &stream, std::vector<read_
YMF278BSlot *slot;
int16_t sample = 0;
int32_t *mixp;
- int32_t vl, vr;
-
- ymf262_update_one(m_ymf262, outputs);
- stream_buffer::sample_t fvl = stream_buffer::sample_t(m_mix_level[m_fm_l]) * (1.0 / 65536.0);
- stream_buffer::sample_t fvr = stream_buffer::sample_t(m_mix_level[m_fm_r]) * (1.0 / 65536.0);
- for (i = 0; i < outputs[0].samples(); i++)
- {
- // DO2 mixing
- outputs[0].put(i, outputs[0].get(i) * fvl);
- outputs[1].put(i, outputs[1].get(i) * fvr);
- }
std::fill(m_mix_buffer.begin(), m_mix_buffer.end(), 0);
@@ -314,29 +314,46 @@ void ymf278b_device::sound_stream_update(sound_stream &stream, std::vector<read_
}
mixp = &m_mix_buffer[0];
- vl = m_mix_level[m_pcm_l];
- vr = m_mix_level[m_pcm_r];
+ stream_buffer::sample_t wtl = stream_buffer::sample_t(m_mix_level[m_pcm_l]) / (65536.0f * 32768.0f);
+ stream_buffer::sample_t wtr = stream_buffer::sample_t(m_mix_level[m_pcm_r]) / (65536.0f * 32768.0f);
+ stream_buffer::sample_t fml = stream_buffer::sample_t(m_mix_level[m_fm_l]) / (65536.0f * 32768.0f);
+ stream_buffer::sample_t fmr = stream_buffer::sample_t(m_mix_level[m_fm_r]) / (65536.0f * 32768.0f);
for (i = 0; i < outputs[0].samples(); i++)
{
- outputs[0].add_int(i, (*mixp++ * vl) >> 16, 32768);
- outputs[1].add_int(i, (*mixp++ * vr) >> 16, 32768);
+ // the FM_STEP value is the fractional number of extra samples consumed per
+ // output sample; when this overflows, we need to clock the FM engine an
+ // extra time; since the PCM side of the chip doesn't do interpolation, I'm
+ // assuming this resampling stage doesn't either
+ m_fm_pos += FM_STEP;
+ if (BIT(m_fm_pos, 24))
+ {
+ m_fm.clock(fm_engine::ALL_CHANNELS);
+ m_fm_pos &= 0xffffff;
+ }
+
+ // clock the system
+ m_fm.clock(fm_engine::ALL_CHANNELS);
+
+ // update the FM content; clipping is unknown
+ s32 sums[fm_engine::OUTPUTS] = { 0 };
+ m_fm.output(sums, 1, 32767, fm_engine::ALL_CHANNELS);
+
+ // DO2 output: mixed FM channels 0+1 and wavetable channels 0+1
+ outputs[0].put(i, stream_buffer::sample_t(*mixp++) * wtl + stream_buffer::sample_t(sums[0]) * fml);
+ outputs[1].put(i, stream_buffer::sample_t(*mixp++) * wtr + stream_buffer::sample_t(sums[1]) * fmr);
+
+ // DO0 output: FM channels 2+3 only
+ outputs[2].put_int(i, sums[2], 32768);
+ outputs[3].put_int(i, sums[3], 32768);
+
+ // DO1 output: wavetable channels 2+3 only
outputs[4].put_int(i, *mixp++, 32768);
outputs[5].put_int(i, *mixp++, 32768);
}
}
-void ymf278b_device::irq_check()
-{
- int prev_line = m_irq_line;
- m_irq_line = m_current_irq ? 1 : 0;
- if (m_irq_line != prev_line && !m_irq_handler.isnull())
- m_irq_handler(m_irq_line);
-}
-
enum
{
- TIMER_A = 0,
- TIMER_B,
TIMER_BUSY_CLEAR,
TIMER_LD_CLEAR
};
@@ -345,28 +362,12 @@ void ymf278b_device::device_timer(emu_timer &timer, device_timer_id id, int para
{
switch(id)
{
- case TIMER_A:
- if(!(m_enable & 0x40))
- {
- m_current_irq |= 0x40;
- irq_check();
- }
- break;
-
- case TIMER_B:
- if(!(m_enable & 0x20))
- {
- m_current_irq |= 0x20;
- irq_check();
- }
- break;
-
case TIMER_BUSY_CLEAR:
- m_status_busy = 0;
+ m_fm.set_reset_status(0, STATUS_BUSY);
break;
case TIMER_LD_CLEAR:
- m_status_ld = 0;
+ m_fm.set_reset_status(0, STATUS_LD);
break;
}
}
@@ -374,91 +375,6 @@ void ymf278b_device::device_timer(emu_timer &timer, device_timer_id id, int para
/**************************************************************************/
-void ymf278b_device::A_w(uint8_t reg, uint8_t data)
-{
- // FM register array 0 (compatible with YMF262)
- switch(reg)
- {
- // LSI TEST
- case 0x00:
- case 0x01:
- break;
-
- // timer a count
- case 0x02:
- if (data != m_timer_a_count)
- {
- m_timer_a_count = data;
-
- // change period, ~80.8us * t
- if (m_enable & 1)
- m_timer_a->adjust(m_timer_a->remaining(), 0, m_timer_base * (256-data) * 4);
- }
- break;
-
- // timer b count
- case 0x03:
- if (data != m_timer_b_count)
- {
- m_timer_b_count = data;
-
- // change period, ~323.1us * t
- if (m_enable & 2)
- m_timer_b->adjust(m_timer_b->remaining(), 0, m_timer_base * (256-data) * 16);
- }
- break;
-
- // timer control
- case 0x04:
- if(data & 0x80)
- m_current_irq = 0;
- else
- {
- // reset timers
- if((m_enable ^ data) & 1)
- {
- attotime period = (data & 1) ? m_timer_base * (256-m_timer_a_count) * 4 : attotime::never;
- m_timer_a->adjust(period, 0, period);
- }
- if((m_enable ^ data) & 2)
- {
- attotime period = (data & 2) ? m_timer_base * (256-m_timer_b_count) * 16 : attotime::never;
- m_timer_b->adjust(period, 0, period);
- }
-
- m_enable = data;
- m_current_irq &= ~data;
- }
- irq_check();
- break;
-
- default:
- logerror("YMF278B: Port A write %02x, %02x\n", reg, data);
- break;
- }
-}
-
-void ymf278b_device::B_w(uint8_t reg, uint8_t data)
-{
- // FM register array 1 (compatible with YMF262)
- switch(reg)
- {
- // LSI TEST
- case 0x00:
- case 0x01:
- break;
-
- // expansion register (NEW2/NEW)
- case 0x05:
- m_exp = data;
- break;
-
- default:
- logerror("YMF278B: Port B write %02x, %02x\n", reg, data);
- break;
- }
-}
-
void ymf278b_device::retrigger_sample(YMF278BSlot *slot)
{
// activate channel
@@ -515,7 +431,7 @@ void ymf278b_device::C_w(uint8_t reg, uint8_t data)
C_w(8 + snum + (i-2) * 24, p[i]);
// status register LD bit is on for approx 300us
- m_status_ld = 1;
+ m_fm.set_reset_status(STATUS_LD, 0);
period = clocks_to_attotime(10);
m_timer_ld->adjust(period);
@@ -689,29 +605,32 @@ void ymf278b_device::C_w(uint8_t reg, uint8_t data)
void ymf278b_device::timer_busy_start(int is_pcm)
{
// status register BUSY bit is on for 56(FM) or 88(PCM) cycles
- m_status_busy = 1;
+ m_fm.set_reset_status(STATUS_BUSY, 0);
m_timer_busy->adjust(attotime::from_hz(m_clock / (is_pcm ? 88 : 56)));
}
void ymf278b_device::write(offs_t offset, u8 data)
{
- switch (offset)
+ uint32_t old;
+ switch (offset & 7)
{
case 0:
case 2:
timer_busy_start(0);
m_port_AB = data;
- m_lastport = offset>>1 & 1;
- ymf262_write(m_ymf262, offset, data);
+ m_lastport = BIT(offset, 1);
break;
case 1:
case 3:
timer_busy_start(0);
- if (m_lastport) B_w(m_port_AB, data);
- else A_w(m_port_AB, data);
- m_last_fm_data = data;
- ymf262_write(m_ymf262, offset, data);
+ old = m_fm.regs().new2flag();
+ m_fm.write(m_port_AB | (m_lastport << 8), data);
+
+ // if the new2 flag is turned on, the next status read will set bit 1
+ // but only for the first status read after new2 is set
+ if (old == 0 && m_fm.regs().new2flag() != 0)
+ m_next_status_id = true;
break;
case 4:
@@ -721,7 +640,7 @@ void ymf278b_device::write(offs_t offset, u8 data)
case 5:
// PCM regs are only accessible if NEW2 is set
- if (~m_exp & 2)
+ if (!m_fm.regs().new2flag())
break;
m_stream->update();
@@ -741,32 +660,42 @@ u8 ymf278b_device::read(offs_t offset)
{
uint8_t ret = 0;
- switch (offset)
+ switch (offset & 7)
{
// status register
case 0:
- {
- // bits 0 and 1 are only valid if NEW2 is set
- uint8_t newbits = 0;
- if (m_exp & 2)
- newbits = (m_status_ld << 1) | m_status_busy;
+ ret = m_fm.status();
- ret = newbits | m_current_irq | (m_irq_line ? 0x80 : 0x00);
+ // if new2 flag is not set, we're in OPL2 or OPL3 mode
+ if (!m_fm.regs().new2flag())
+ {
+ // these bits are not reported in OPL2/3 mode
+ ret &= ~(STATUS_BUSY | STATUS_LD);
+
+ // if in OPL2 mode, bits 1 and 2 are returned on
+ if (!m_fm.regs().newflag())
+ ret |= 0x06;
+ }
+ else if (m_next_status_id)
+ {
+ // if new2 flag was just changed to on, then the next read will be 0x02
+ ret |= 0x02;
+ m_next_status_id = false;
+ }
break;
- }
// FM regs can be read too (on contrary to what the datasheet says)
case 1:
case 3:
// but they're not implemented here yet
// This may be incorrect, but it makes the mbwave moonsound detection in msx drivers pass.
- ret = m_last_fm_data;
+ ret = m_fm.regs().read(m_port_AB | (m_lastport << 8));
break;
// PCM regs
case 5:
// only accessible if NEW2 is set
- if (~m_exp & 2)
+ if (!m_fm.regs().new2flag())
break;
switch (m_port_C)
@@ -798,15 +727,6 @@ u8 ymf278b_device::read(offs_t offset)
/**************************************************************************/
//-------------------------------------------------
-// device_post_load - device-specific post load
-//-------------------------------------------------
-
-void ymf278b_device::device_post_load()
-{
- ymf262_post_load(m_ymf262);
-}
-
-//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
@@ -815,9 +735,6 @@ void ymf278b_device::device_reset()
int i;
// clear registers
- for (i = 0; i <= 4; i++)
- A_w(i, 0);
- B_w(5, 0);
for (i = 0; i < 8; i++)
C_w(i, 0);
for (i = 0xff; i >= 8; i--)
@@ -826,6 +743,7 @@ void ymf278b_device::device_reset()
m_port_AB = m_port_C = 0;
m_lastport = 0;
+ m_next_status_id = false;
m_memadr = 0;
// init/silence channels
@@ -851,23 +769,10 @@ void ymf278b_device::device_reset()
compute_envelope(slot);
}
- m_timer_a->reset();
- m_timer_b->reset();
- m_timer_busy->reset(); m_status_busy = 0;
- m_timer_ld->reset(); m_status_ld = 0;
-
- m_irq_line = 0;
- m_current_irq = 0;
- if (!m_irq_handler.isnull())
- m_irq_handler(0);
-
- ymf262_reset_chip(m_ymf262);
-}
+ m_timer_busy->reset();
+ m_timer_ld->reset();
-void ymf278b_device::device_stop()
-{
- ymf262_shutdown(m_ymf262);
- m_ymf262 = nullptr;
+ m_fm.reset();
}
void ymf278b_device::device_clock_changed()
@@ -875,18 +780,13 @@ void ymf278b_device::device_clock_changed()
int old_rate = m_rate;
m_clock = clock();
m_rate = m_clock/768;
+ m_fm_pos = 0;
if (m_rate > old_rate)
{
m_mix_buffer.resize(m_rate*4,0);
}
m_stream->set_sample_rate(m_rate);
-
- m_timer_base = m_clock ? attotime::from_hz(m_clock) * (19 * 36) : attotime::zero;
-
- // YMF262 related
-
- ymf262_clock_changed(m_ymf262, clock(), m_rate);
}
void ymf278b_device::rom_bank_updated()
@@ -929,22 +829,15 @@ void ymf278b_device::register_save_state()
save_item(NAME(m_wavetblhdr));
save_item(NAME(m_memmode));
save_item(NAME(m_memadr));
- save_item(NAME(m_status_busy));
- save_item(NAME(m_status_ld));
- save_item(NAME(m_exp));
save_item(NAME(m_fm_l));
save_item(NAME(m_fm_r));
+ save_item(NAME(m_fm_pos));
save_item(NAME(m_pcm_l));
save_item(NAME(m_pcm_r));
- save_item(NAME(m_timer_a_count));
- save_item(NAME(m_timer_b_count));
- save_item(NAME(m_enable));
- save_item(NAME(m_current_irq));
- save_item(NAME(m_irq_line));
save_item(NAME(m_port_AB));
save_item(NAME(m_port_C));
save_item(NAME(m_lastport));
- save_item(NAME(m_last_fm_data));
+ save_item(NAME(m_next_status_id));
for (i = 0; i < 24; ++i)
{
@@ -996,11 +889,8 @@ void ymf278b_device::device_start()
m_clock = clock();
m_rate = m_clock / 768;
- m_irq_handler.resolve();
+ m_fm_pos = 0;
- m_timer_base = m_clock ? attotime::from_hz(m_clock) * (19*36) : attotime::zero;
- m_timer_a = timer_alloc(TIMER_A);
- m_timer_b = timer_alloc(TIMER_B);
m_timer_busy = timer_alloc(TIMER_BUSY_CLEAR);
m_timer_ld = timer_alloc(TIMER_LD_CLEAR);
@@ -1037,16 +927,7 @@ void ymf278b_device::device_start()
register_save_state();
// YMF262 related
-
- /* stream system initialize */
- m_ymf262 = ymf278b_init(this, clock(), m_rate);
- if (!m_ymf262)
- throw emu_fatalerror("ymf278b_device(%s): Error creating YMF262 chip", tag());
-
- /* YMF262 setup */
- ymf262_set_timer_handler (m_ymf262, ymf278b_device::static_timer_handler, this);
- ymf262_set_irq_handler (m_ymf262, ymf278b_device::static_irq_handler, this);
- ymf262_set_update_handler(m_ymf262, ymf278b_device::static_update_request, this);
+ m_fm.save(*this);
}
@@ -1056,7 +937,6 @@ ymf278b_device::ymf278b_device(const machine_config &mconfig, const char *tag, d
: device_t(mconfig, YMF278B, tag, owner, clock)
, device_sound_interface(mconfig, *this)
, device_rom_interface(mconfig, *this)
- , m_irq_handler(*this)
- , m_last_fm_data(0)
+ , m_fm(*this)
{
}