summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author Olivier Galibert <galibert@pobox.com>2016-12-23 00:13:25 +0100
committer Olivier Galibert <galibert@pobox.com>2016-12-24 12:08:40 +0100
commit2c79c1ed0d223447fcd14ba7df369129488d6d14 (patch)
tree70168d4fbad261b46d3fd96978c6f0b3541ec193
parentee5d18f65736f70094f673175075becb50254d2f (diff)
votrax sc01: Simulate [O. Galibert]
-rw-r--r--scripts/target/mame/arcade.lua4
-rw-r--r--scripts/target/mame/mess.lua2
-rw-r--r--scripts/target/mame/tiny.lua2
-rw-r--r--src/devices/sound/votrax.cpp1824
-rw-r--r--src/devices/sound/votrax.h269
-rw-r--r--src/mame/audio/gorf.cpp190
-rw-r--r--src/mame/audio/gorf.h4
-rw-r--r--src/mame/audio/gottlieb.cpp240
-rw-r--r--src/mame/audio/gottlieb.h25
-rw-r--r--src/mame/audio/wow.cpp183
-rw-r--r--src/mame/audio/wow.h4
-rw-r--r--src/mame/drivers/astrocde.cpp65
-rw-r--r--src/mame/drivers/gottlieb.cpp35
-rw-r--r--src/mame/includes/astrocde.h11
14 files changed, 913 insertions, 1945 deletions
diff --git a/scripts/target/mame/arcade.lua b/scripts/target/mame/arcade.lua
index 6a4276981b3..65200a0029f 100644
--- a/scripts/target/mame/arcade.lua
+++ b/scripts/target/mame/arcade.lua
@@ -2492,10 +2492,6 @@ files {
MAME_DIR .. "src/mame/drivers/astrocde.cpp",
MAME_DIR .. "src/mame/includes/astrocde.h",
MAME_DIR .. "src/mame/video/astrocde.cpp",
- MAME_DIR .. "src/mame/audio/gorf.cpp",
- MAME_DIR .. "src/mame/audio/gorf.h",
- MAME_DIR .. "src/mame/audio/wow.cpp",
- MAME_DIR .. "src/mame/audio/wow.h",
MAME_DIR .. "src/mame/drivers/atlantis.cpp",
MAME_DIR .. "src/mame/drivers/balsente.cpp",
MAME_DIR .. "src/mame/includes/balsente.h",
diff --git a/scripts/target/mame/mess.lua b/scripts/target/mame/mess.lua
index 7a0a8f3e4b3..1fed55efffa 100644
--- a/scripts/target/mame/mess.lua
+++ b/scripts/target/mame/mess.lua
@@ -1141,8 +1141,6 @@ files {
MAME_DIR .. "src/mame/video/jagblit.h",
MAME_DIR .. "src/mame/video/jagblit.hxx",
MAME_DIR .. "src/mame/video/jagobj.hxx",
- MAME_DIR .. "src/mame/audio/gorf.cpp",
- MAME_DIR .. "src/mame/audio/wow.cpp",
MAME_DIR .. "src/mame/drivers/astrocde.cpp",
MAME_DIR .. "src/mame/includes/astrocde.h",
MAME_DIR .. "src/mame/video/astrocde.cpp",
diff --git a/scripts/target/mame/tiny.lua b/scripts/target/mame/tiny.lua
index 0e770933bef..129e276e208 100644
--- a/scripts/target/mame/tiny.lua
+++ b/scripts/target/mame/tiny.lua
@@ -139,8 +139,6 @@ files{
MAME_DIR .. "src/mame/audio/williams.cpp",
MAME_DIR .. "src/mame/audio/williams.h",
MAME_DIR .. "src/mame/video/williams.cpp",
- MAME_DIR .. "src/mame/audio/gorf.cpp",
- MAME_DIR .. "src/mame/audio/wow.cpp",
MAME_DIR .. "src/mame/drivers/gaelco.cpp",
MAME_DIR .. "src/mame/includes/gaelco.h",
MAME_DIR .. "src/mame/video/gaelco.cpp",
diff --git a/src/devices/sound/votrax.cpp b/src/devices/sound/votrax.cpp
index 505ccd3d0a6..b368a35ea54 100644
--- a/src/devices/sound/votrax.cpp
+++ b/src/devices/sound/votrax.cpp
@@ -1,56 +1,41 @@
// license:BSD-3-Clause
-// copyright-holders:Aaron Giles
+// copyright-holders:Olivier Galibert
/***************************************************************************
votrax.c
- Simple VOTRAX SC-01 simulator based on sample fragments.
+ Votrax SC01A simulation
***************************************************************************/
-#include "emu.h"
-#include "votrax.h"
-
-
-//**************************************************************************
-// DEBUGGING
-//**************************************************************************
-
-#define TEMP_HACKS (1)
-
-#define LOG_TIMING (0)
-#define LOG_LOWPARAM (0)
-#define LOG_GLOTTAL (0)
-#define LOG_TRANSITION (0)
-
-
-
-//**************************************************************************
-// CONSTANTS
-//**************************************************************************
-
-// note that according to the patent timing circuit, p1/p2 and phi1/phi2
-// run 4x faster than all references in the patent text
-const uint32_t P_CLOCK_BIT = 5; // 5 according to timing diagram
-const uint32_t PHI_CLOCK_BIT = 3; // 3 according to timing diagram
+/*
+ tp3 stb i1 i2 tp2
+ 1 1 o o white noise
+ 1 0 - 1 phone timing clock
+ 1 0 1 0 closure tick
+ 1 0 0 0 sram write pulse
+ 0 - - - sram write pulse
+i1.o = glottal impulse
+i2.o = white noise
+tp1 = phi clock (tied to f2q rom access)
+*/
+
+#include "emu.h"
+#include "votrax.h"
-//**************************************************************************
-// GLOBAL VARIABLES
-//**************************************************************************
-// device type definition
const device_type VOTRAX_SC01 = &device_creator<votrax_sc01_device>;
-// ROM definition for the Votrax phoneme ROM
+// ROM definition for the Votrax phone ROM
ROM_START( votrax_sc01 )
- ROM_REGION( 0x200, "phoneme", 0 )
- ROM_LOAD( "sc01.bin", 0x0000, 0x200, CRC(0353dd6c) SHA1(00e8e497b96a10bd9f4d7e559433c3c209b0d3a8) )
+ ROM_REGION64_LE( 0x200, "internal", 0 )
+ ROM_LOAD( "sc01a.bin", 0x000, 0x200, CRC(fc416227) SHA1(1d6da90b1807a01b5e186ef08476119a862b5e6d) )
ROM_END
-// textual phoneme names for debugging
-const char *const votrax_sc01_device::s_phoneme_table[64] =
+// textual phone names for debugging
+const char *const votrax_sc01_device::s_phone_table[64] =
{
"EH3", "EH2", "EH1", "PA0", "DT", "A1", "A2", "ZH",
"AH2", "I3", "I2", "I1", "M", "N", "B", "V",
@@ -62,82 +47,65 @@ const char *const votrax_sc01_device::s_phoneme_table[64] =
"THV", "TH", "ER", "EH", "E1", "AW", "PA1", "STOP"
};
-// this waveform is derived from measuring fig. 10 in the patent
-// it is only an approximation
-const double votrax_sc01_device::s_glottal_wave[16] =
+// This waveform is built using a series of transistors as a resistor
+// ladder. There is first a transistor to ground, then a series of
+// seven transistors one quarter the size of the first one, then it
+// finishes by an active resistor to +9V.
+//
+// The terminal of the transistor to ground is used as a middle value.
+// Index 0 is at that value. Index 1 is at 0V. Index 2 to 8 start at
+// just after the resistor down the latter. Indices 9+ are the middle
+// value again.
+//
+// For simplicity, we rescale the values to get the middle at 0 and
+// the top at 1. The final wave is very similar to the patent
+// drawing.
+
+const double votrax_sc01_device::s_glottal_wave[9] =
{
0,
- 16.0/22.0,
- -22.0/22.0,
- -17.0/22.0,
- -15.0/22.0,
- -10.0/22.0,
- -7.0/22.0,
- -4.0/22.0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0,
- 0
+ -4/7.0,
+ 7/7.0,
+ 6/7.0,
+ 5/7.0,
+ 4/7.0,
+ 3/7.0,
+ 2/7.0,
+ 1/7.0
};
-
-//**************************************************************************
-// LIVE DEVICE
-//**************************************************************************
-
-//-------------------------------------------------
-// votrax_sc01_device - constructor
-//-------------------------------------------------
-
votrax_sc01_device::votrax_sc01_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, VOTRAX_SC01, "Votrax SC-01", tag, owner, clock, "votrax", __FILE__),
- device_sound_interface(mconfig, *this),
- m_stream(nullptr),
- m_phoneme_timer(nullptr), m_rom(nullptr), m_inflection(0), m_phoneme(0),
- m_request_cb(*this), m_request_state(0), m_internal_request(0), m_master_clock_freq(0), m_master_clock(0), m_counter_34(0),
- m_latch_70(0), m_latch_72(0), m_beta1(0), m_p2(0), m_p1(0), m_phi2(0), m_phi1(0), m_phi2_20(0), m_phi1_20(0), m_subphoneme_period(0),
- m_subphoneme_count(0), m_clock_88(0), m_latch_42(0), m_counter_84(0), m_latch_92(0), m_srff_132(false), m_srff_114(false), m_srff_112(false),
- m_srff_142(false), m_latch_80(0), m_counter_220(0), m_counter_222(0), m_counter_224(0), m_counter_234(0), m_counter_236(0), m_fgate(0),
- m_glottal_sync(0), m_0625_clock(0), m_counter_46(0), m_latch_46(0), m_latch_168(0), m_latch_170(0), m_f1(0), m_f2(0), m_fc(0), m_f3(0),
- m_f2q(0), m_va(0), m_fa(0), m_noise_clock(0), m_shift_252(0), m_counter_250(0)
+ device_sound_interface(mconfig, *this),
+ m_stream(nullptr),
+ m_rom(*this, "internal"),
+ m_ar_cb(*this)
{
}
-//**************************************************************************
-// READ/WRITE HANDLERS
-//**************************************************************************
-
-//-------------------------------------------------
-// write - handle a write to the control register
-//-------------------------------------------------
-
WRITE8_MEMBER( votrax_sc01_device::write )
{
// flush out anything currently processing
m_stream->update();
+ u8 prev = m_phone;
+
// only 6 bits matter
- m_phoneme = data & 0x3f;
-const uint8_t *rom = m_rom + (m_phoneme << 3);
-osd_printf_debug("%s: STROBE %s (F1=%X F2=%X FC=%X F3=%X F2Q=%X VA=%X FA=%X CL=%X CLD=%X VD=%X PAC=%X PH=%02X)\n",
- machine().time().as_string(3), s_phoneme_table[m_phoneme],
- rom[0] >> 4, rom[1] >> 4, rom[2] >> 4, rom[3] >> 4, rom[4] >> 4, rom[5] >> 4, rom[6] >> 4,
- rom[3] & 0xf, rom[4] & 0xf, rom[5] & 0xf, rom[6] & 0xf, rom[7]);
-
- // the STROBE signal resets the phoneme counter
- m_counter_84 = 0xf;
-
- // not in the schematics, but necessary to fully reset the request latch
- m_latch_92 = 0;
-
- // clear the request signal
- m_request_cb(m_request_state = m_internal_request = CLEAR_LINE);
- m_phoneme_timer->adjust(attotime::zero);
+ m_phone = data & 0x3f;
+
+ if(m_phone != prev || m_phone != 0x3f)
+ logerror("phone %02x.%d %s\n", m_phone, m_inflection, s_phone_table[m_phone]);
+
+ m_ar_state = CLEAR_LINE;
+
+ // Schedule a commit/ar reset at roughly 0.1ms in the future (one
+ // phi1 transition followed by the rom extra state in practice),
+ // but only if there isn't already one on the fly. It will
+ // override an end-of-phone timeout if there's one pending, but
+ // that's not a problem since stb does that anyway.
+ if(m_timer->expire().is_never() || m_timer->param() != T_COMMIT_PHONE)
+ m_timer->adjust(attotime::from_ticks(72, m_mainclock), T_COMMIT_PHONE);
}
@@ -150,134 +118,518 @@ WRITE8_MEMBER( votrax_sc01_device::inflection_w )
{
// only 2 bits matter
data &= 3;
- if (m_inflection == data)
+ if(m_inflection == data)
return;
- // append an inflection marker
m_stream->update();
m_inflection = data;
}
+//-------------------------------------------------
+// sound_stream_update - handle update requests
+// for our sound stream
+//-------------------------------------------------
+
+void votrax_sc01_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
+{
+ for(int i=0; i<samples; i++) {
+ m_sample_count++;
+ if(m_sample_count & 1)
+ chip_update();
+ outputs[0][i] = analog_calc();
+ }
+}
+
+
//**************************************************************************
-// CORE LOGIC
+// DEVICE INTERFACE
//**************************************************************************
//-------------------------------------------------
-// update_subphoneme_clock_period - re-compute the
-// period of the sub-phoneme clock, as a multiple
-// of the master clock
+// rom_region - return a pointer to the device's
+// internal ROM region
//-------------------------------------------------
-void votrax_sc01_device::update_subphoneme_clock_period()
+const tiny_rom_entry *votrax_sc01_device::device_rom_region() const
{
- assert(m_latch_80 < 128);
+ return ROM_NAME( votrax_sc01 );
+}
-/*
- The sub-phoneme timing circuit is based off the switching capacitor
- technique described in the Votrax patent. Replacing the capacitor
- ladder with [Rx] representing the effective resistance, the circuit
- becomes essentially a pair of op-amps:
-
- VM
- | i1
- [R1]
- | Vc
- +----------------------+
- | +---|C1|---+ |
- [R2] | | | |\
- |Vb i2 | |\ | +--++\
- +--[Rx]--+----+-\ | | >
- | | >--+-----+-/
- [R3] +----++/ Vc |/
- |i3 | |/
- +--------+ Va
- |
- [R4]
- |
- 0
-
- We have two op-amps, the left used as a standard amplifier, the right
- one as a comparator. The circuit triggers when the two inputs of the
- right op-amp are equal.
-
- The left part of the circuit (before C1) is simply a current injector.
- It's all made of resistors, there's no modulated input, so everything
- is going to be constant. If you don't know about op-amps used as
- amplifiers, you just need to know that it forces its two inputs to
- have the same voltage while not sending or providing any current
- through there (only though its output in fact).
-
- In the schema, the injected current is i2. Basic equations apply:
- Va = R4.i3
- Vb = Va + R3.i3
- Vb = Va + Rx.i2
- Vc = Vb + R2.i1
- VM = Vc + R1.i1
- i1 = i2 + i3
-
- And the tipping happens when the voltage on the right of C1 reaches
- Vc, so:
- Vc = Va + i2.T/C1
-
- (i2 being a constant, the integration is kinda easy)
-
- Some maths later:
- R3.i3 = Rx.i2 -> i3 = Rx/R3.i2
- i1 = (1+Rx/R3).i2
- Va + (Rx + R2 + R2.Rx/R3).i2 = Va + T/C1.i2
- T = C1*(Rx*(1+R2/R3) + R2)
-
- Which isn't, interestingly though not surprisingly, dependant on Vm,
- R1 or R4. And you have to round it to the next multiple of
- 0.2ms+0.1ms due to the clocking on p2 and its offset to p1 (charging
- only happens on p1 active), and add one p1/p2 cycle (0.2ms) for the
- discharge.
-
- So now you have your base clock, which you have to multiply by 16 to
- get the phoneme length.
-
- r2 = 9e3
- r3 = 1e3
- c1 = 1000e-12
- rx = 1/(5KHz * cx)
-*/
- // determine total capacitance
- double cx = 0;
- if ((m_latch_80 & 0x01) != 0) cx += 5e-12;
- if ((m_latch_80 & 0x02) != 0) cx += 11e-12;
- if ((m_latch_80 & 0x04) != 0) cx += 21e-12;
- if ((m_latch_80 & 0x08) != 0) cx += 43e-12;
- if ((m_latch_80 & 0x10) != 0) cx += 86e-12;
- if ((m_latch_80 & 0x20) != 0) cx += 173e-12;
- if ((m_latch_80 & 0x40) != 0) cx += 345e-12;
-
- // apply the equation above to determine charging time
- // note that the 5kHz listed above for P1 is for a nominal master
- // clock frequency of 1.28MHz, meaning it is master clock / 128
- // which should be the P1 clock but appears to be a bit different
- double p1_frequency = double(m_master_clock_freq) / double(1 << (P_CLOCK_BIT + 2));
- double rx = 1.0 / (p1_frequency * cx);
- double period = 1000e-12 * (rx * (1.0 + 9e3 / 1e3) + 9e3);
-
- // convert to master clock cycles and round up
- m_subphoneme_period = uint32_t(ceil(period * double(m_master_clock_freq)));
+//-------------------------------------------------
+// device_start - handle device startup
+//-------------------------------------------------
+
+void votrax_sc01_device::device_start()
+{
+ // initialize internal state
+ m_mainclock = clock();
+ m_sclock = m_mainclock / 18.0;
+ m_cclock = m_mainclock / 36.0;
+ m_stream = stream_alloc(0, 1, m_sclock);
+ m_timer = timer_alloc();
+
+ // reset outputs
+ m_ar_cb.resolve_safe();
+ m_ar_state = ASSERT_LINE;
+
+ // save inputs
+ save_item(NAME(m_inflection));
+ save_item(NAME(m_phone));
+
+ // save outputs
+ save_item(NAME(m_ar_state));
+
+ // save internal state
+ save_item(NAME(m_rom_duration));
+ save_item(NAME(m_rom_vd));
+ save_item(NAME(m_rom_cld));
+ save_item(NAME(m_rom_fa));
+ save_item(NAME(m_rom_fc));
+ save_item(NAME(m_rom_va));
+ save_item(NAME(m_rom_f1));
+ save_item(NAME(m_rom_f2));
+ save_item(NAME(m_rom_f2q));
+ save_item(NAME(m_rom_f3));
+ save_item(NAME(m_rom_closure));
+ save_item(NAME(m_rom_pause));
+ save_item(NAME(m_cur_fa));
+ save_item(NAME(m_cur_fc));
+ save_item(NAME(m_cur_va));
+ save_item(NAME(m_cur_f1));
+ save_item(NAME(m_cur_f2));
+ save_item(NAME(m_cur_f2q));
+ save_item(NAME(m_cur_f3));
+ save_item(NAME(m_filt_fa));
+ save_item(NAME(m_filt_fc));
+ save_item(NAME(m_filt_va));
+ save_item(NAME(m_filt_f1));
+ save_item(NAME(m_filt_f2));
+ save_item(NAME(m_filt_f2q));
+ save_item(NAME(m_filt_f3));
+ save_item(NAME(m_phonetick));
+ save_item(NAME(m_ticks));
+ save_item(NAME(m_pitch));
+ save_item(NAME(m_closure));
+ save_item(NAME(m_update_counter));
+ save_item(NAME(m_cur_closure));
+ save_item(NAME(m_noise));
+ save_item(NAME(m_cur_noise));
+ save_item(NAME(m_voice_1));
+ save_item(NAME(m_voice_2));
+ save_item(NAME(m_voice_3));
+ save_item(NAME(m_noise_1));
+ save_item(NAME(m_noise_2));
+ save_item(NAME(m_noise_3));
+ save_item(NAME(m_noise_4));
+ save_item(NAME(m_vn_1));
+ save_item(NAME(m_vn_2));
+ save_item(NAME(m_vn_3));
+ save_item(NAME(m_vn_4));
+ save_item(NAME(m_vn_5));
+ save_item(NAME(m_vn_6));
+ save_item(NAME(m_f1_a));
+ save_item(NAME(m_f1_b));
+ save_item(NAME(m_f2v_a));
+ save_item(NAME(m_f2v_b));
+ save_item(NAME(m_f2n_a));
+ save_item(NAME(m_f2n_b));
+ save_item(NAME(m_f3_a));
+ save_item(NAME(m_f3_b));
+ save_item(NAME(m_f4_a));
+ save_item(NAME(m_f4_b));
+ save_item(NAME(m_fx_a));
+ save_item(NAME(m_fx_b));
+ save_item(NAME(m_fn_a));
+ save_item(NAME(m_fn_b));
+}
+
+
+//-------------------------------------------------
+// device_reset - handle device reset
+//-------------------------------------------------
+
+void votrax_sc01_device::device_reset()
+{
+ // Technically, there's no reset in this chip, and initial state
+ // is random. Still, it's a good idea to start it with something
+ // sane.
+
+ m_phone = 0x3f;
+ m_inflection = 0;
+ m_ar_state = ASSERT_LINE;
+ m_ar_cb(m_ar_state);
+
+ m_sample_count = 0;
+
+ // Initialize the m_rom* values
+ phone_commit();
+
+ // Clear the interpolation sram
+ m_cur_fa = m_cur_fc = m_cur_va = 0;
+ m_cur_f1 = m_cur_f2 = m_cur_f2q = m_cur_f3 = 0;
+
+ // Initialize the m_filt* values and the filter coefficients
+ filters_commit(true);
+
+ // Clear the rest of the internal digital state
+ m_pitch = 0;
+ m_closure = 0;
+ m_update_counter = 0;
+ m_cur_closure = true;
+ m_noise = 0;
+ m_cur_noise = false;
+
+ // Clear the analog level histories
+ memset(m_voice_1, 0, sizeof(m_voice_1));
+ memset(m_voice_2, 0, sizeof(m_voice_2));
+ memset(m_voice_3, 0, sizeof(m_voice_3));
+
+ memset(m_noise_1, 0, sizeof(m_noise_1));
+ memset(m_noise_2, 0, sizeof(m_noise_2));
+ memset(m_noise_3, 0, sizeof(m_noise_3));
+ memset(m_noise_4, 0, sizeof(m_noise_4));
+
+ memset(m_vn_1, 0, sizeof(m_vn_1));
+ memset(m_vn_2, 0, sizeof(m_vn_2));
+ memset(m_vn_3, 0, sizeof(m_vn_3));
+ memset(m_vn_4, 0, sizeof(m_vn_4));
+ memset(m_vn_5, 0, sizeof(m_vn_5));
+ memset(m_vn_6, 0, sizeof(m_vn_6));
+}
+
+
+//-------------------------------------------------
+// device_clock_changed - handle dynamic clock
+// changes by altering our output frequency
+//-------------------------------------------------
+
+void votrax_sc01_device::device_clock_changed()
+{
+ // lookup the new frequency of the master clock, and update if changed
+ u32 newfreq = clock();
+ if(newfreq != m_mainclock) {
+ m_stream->update();
+
+ if(!m_timer->expire().is_never()) {
+ // determine how many clock ticks remained on the timer
+ u64 remaining = m_timer->remaining().as_ticks(m_mainclock);
+
+ // adjust the timer to the same number of ticks based on the new frequency
+ m_timer->adjust(attotime::from_ticks(remaining, newfreq));
+ }
+ m_mainclock = newfreq;
+ m_sclock = m_mainclock / 18.0;
+ m_cclock = m_mainclock / 36.0;
+ m_stream->set_sample_rate(m_sclock);
+ filters_commit(true);
+ }
}
+
//-------------------------------------------------
-// bits_to_caps - compute the final capacity from
-// a grid of bit-selected caps
+// device_timer - handle device timer
//-------------------------------------------------
-double votrax_sc01_device::bits_to_caps(uint32_t value, int caps_count, const double *caps_values)
+void votrax_sc01_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
- double sum = 0;
- for(int i=0; i<caps_count; i++)
- if(value & (1<<i))
- sum += caps_values[i];
- return sum;
+ m_stream->update();
+
+ switch(param) {
+ case T_COMMIT_PHONE:
+ // strobe -> commit transition,
+ phone_commit();
+ m_timer->adjust(attotime::from_ticks(16*(m_rom_duration*4+1)*4*9+2, m_mainclock), T_END_OF_PHONE);
+ break;
+
+ case T_END_OF_PHONE:
+ // end of phone
+ m_ar_state = ASSERT_LINE;
+ break;
+ }
+
+ m_ar_cb(m_ar_state);
+}
+
+void votrax_sc01_device::phone_commit()
+{
+ // Only these two counters are reset on phone change, the rest is
+ // free-running.
+ m_phonetick = 0;
+ m_ticks = 0;
+
+ // In the real chip, the rom is re-read all the time. Since it's
+ // internal and immutable, no point in not caching it though.
+ for(int i=0; i<64; i++) {
+ u64 val = reinterpret_cast<const u64 *>(m_rom->base())[i];
+ if(m_phone == ((val >> 56) & 0x3f)) {
+ m_rom_f1 = bitswap(val, 0, 7, 14, 21);
+ m_rom_va = bitswap(val, 1, 8, 15, 22);
+ m_rom_f2 = bitswap(val, 2, 9, 16, 23);
+ m_rom_fc = bitswap(val, 3, 10, 17, 24);
+ m_rom_f2q = bitswap(val, 4, 11, 18, 25);
+ m_rom_f3 = bitswap(val, 5, 12, 19, 26);
+ m_rom_fa = bitswap(val, 6, 13, 20, 27);
+
+ // These two values have their bit orders inverted
+ // compared to everything else due to a bug in the
+ // prototype (miswiring of the comparator with the ticks
+ // count) they compensated in the rom.
+
+ m_rom_cld = bitswap(val, 34, 32, 30, 28);
+ m_rom_vd = bitswap(val, 35, 33, 31, 29);
+
+ m_rom_closure = bitswap(val, 36);
+ m_rom_duration = bitswap(~val, 37, 38, 39, 40, 41, 42, 43);
+
+ // Hard-wired on the die, not an actual part of the rom.
+ m_rom_pause = (m_phone == 0x03) || (m_phone == 0x3e);
+
+ if(0)
+ logerror("commit fa=%x va=%x fc=%x f1=%x f2=%x f2q=%x f3=%x dur=%02x cld=%x vd=%d cl=%d pause=%d\n", m_rom_fa, m_rom_va, m_rom_fc, m_rom_f1, m_rom_f2, m_rom_f2q, m_rom_f3, m_rom_duration, m_rom_cld, m_rom_vd, m_rom_closure, m_rom_pause);
+
+ // That does not happen in the sc01(a) rom, but let's
+ // cover our behind.
+ if(m_rom_cld == 0)
+ m_cur_closure = m_rom_closure;
+
+ return;
+ }
+ }
+}
+
+void votrax_sc01_device::interpolate(u8 &reg, u8 target)
+{
+ // One step of interpolation, adds one eight of the distance
+ // between the current value and the target.
+ reg = reg - (reg >> 3) + (target << 1);
+}
+
+void votrax_sc01_device::chip_update()
+{
+ // Phone tick counter update. Stopped when ticks reach 16.
+ // Technically the counter keeps updating, but the comparator is
+ // disabled.
+ if(m_ticks != 0x10) {
+ m_phonetick++;
+ // Comparator is with duration << 2, but there's a one-tick
+ // delay in the path.
+ if(m_phonetick == ((m_rom_duration << 2) | 1)) {
+ m_phonetick = 0;
+ m_ticks++;
+ if(m_ticks == m_rom_cld)
+ m_cur_closure = m_rom_closure;
+ }
+ }
+
+ // The two update timing counters. One divides by 16, the other
+ // by 48, and they're phased so that the 208Hz counter ticks
+ // exactly between two 625Hz ticks.
+ m_update_counter++;
+ if(m_update_counter == 0x30)
+ m_update_counter = 0;
+
+ bool tick_625 = !(m_update_counter & 0xf);
+ bool tick_208 = m_update_counter == 0x28;
+
+ // Formant update. Die bug there: fc should be updated, not va.
+ // The formants are frozen on a pause phone unless both voice and
+ // noise volumes are zero.
+ if(tick_208 && (!m_rom_pause || !(m_filt_fa || m_filt_va))) {
+ // interpolate(m_cur_va, m_rom_va);
+ interpolate(m_cur_fc, m_rom_fc);
+ interpolate(m_cur_f1, m_rom_f1);
+ interpolate(m_cur_f2, m_rom_f2);
+ interpolate(m_cur_f2q, m_rom_f2q);
+ interpolate(m_cur_f3, m_rom_f3);
+ // logerror("int fa=%x va=%x fc=%x f1=%x f2=%02x f2q=%02x f3=%x\n", m_cur_fa >> 4, m_cur_va >> 4, m_cur_fc >> 4, m_cur_f1 >> 4, m_cur_f2 >> 3, m_cur_f2q >> 4, m_cur_f3 >> 4);
+ }
+
+ // Non-formant update. Same bug there, va should be updated, not fc.
+ if(tick_625) {
+ if(m_ticks >= m_rom_vd)
+ interpolate(m_cur_fa, m_rom_fa);
+ if(m_ticks >= m_rom_cld)
+ // interpolate(m_cur_fc, m_rom_fc);
+ interpolate(m_cur_va, m_rom_va);
+ // logerror("int fa=%x va=%x fc=%x f1=%x f2=%02x f2q=%02x f3=%x\n", m_cur_fa >> 4, m_cur_va >> 4, m_cur_fc >> 4, m_cur_f1 >> 4, m_cur_f2 >> 3, m_cur_f2q >> 4, m_cur_f3 >> 4);
+ }
+
+ // Closure counter, reset every other tick in theory when not
+ // active (on the extra rom cycle).
+ //
+ // The closure level is immediatly used in the analog path,
+ // there's no pitch synchronization.
+
+ if(!m_cur_closure && (m_filt_fa || m_filt_va))
+ m_closure = 0;
+ else if(m_closure != 7 << 2)
+ m_closure ++;
+
+ // Pitch counter. Equality comparison, so it's possible to make
+ // it miss by manipulating the inflection inputs, but it'll wrap.
+ // There's a delay, hence the +1.
+ m_pitch = (m_pitch + 1) & 0x7f;
+ if(m_pitch == (0x7f ^ (m_inflection << 4) ^ m_filt_f1) + 1)
+ m_pitch = 0;
+
+ // Filters are updated in index 1 of the pitch wave, which does
+ // indeed mean four times in a row.
+ if((m_pitch >> 2) == 1)
+ filters_commit(false);
+
+ // Noise shift register. 15 bits, with a nxor on the last two
+ // bits for the loop.
+ bool inp = (1||m_filt_fa) && m_cur_noise && (m_noise != 0x7fff);
+ m_noise = ((m_noise << 1) & 0x7ffe) | inp;
+ m_cur_noise = !(((m_noise >> 14) ^ (m_noise >> 13)) & 1);
+
+ // logerror("tick %02x.%03x 625=%d 208=%d pitch=%02x.%x ns=%04x ni=%d noise=%d cl=%x.%x clf=%d/%d\n", m_ticks, m_phonetick, tick_625, tick_208, m_pitch >> 2, m_pitch & 3, m_noise, inp, m_cur_noise, m_closure >> 2, m_closure & 3, m_rom_closure, m_cur_closure);
+}
+
+void votrax_sc01_device::filters_commit(bool force)
+{
+ m_filt_fa = m_cur_fa >> 4;
+ m_filt_fc = m_cur_fc >> 4;
+ m_filt_va = m_cur_va >> 4;
+
+ if(force || m_filt_f1 != m_cur_f1 >> 4) {
+ m_filt_f1 = m_cur_f1 >> 4;
+
+ build_standard_filter(m_f1_a, m_f1_b,
+ 11247,
+ 11797,
+ 949,
+ 52067,
+ 2280 + bits_to_caps(m_filt_f1, { 2546, 4973, 9861, 19724 }),
+ 166272);
+ }
+
+ if(force || m_filt_f2 != m_cur_f2 >> 3 || m_filt_f2q != m_cur_f2q >> 4) {
+ m_filt_f2 = m_cur_f2 >> 3;
+ m_filt_f2q = m_cur_f2q >> 4;
+
+ build_standard_filter(m_f2v_a, m_f2v_b,
+ 24840,
+ 29154,
+ 829 + bits_to_caps(m_filt_f2q, { 1390, 2965, 5875, 11297 }),
+ 38180,
+ 2352 + bits_to_caps(m_filt_f2, { 833, 1663, 3164, 6327, 12654 }),
+ 34270);
+
+ build_injection_filter(m_f2n_a, m_f2n_b,
+ 29154,
+ 829 + bits_to_caps(m_filt_f2q, { 1390, 2965, 5875, 11297 }),
+ 38180,
+ 2352 + bits_to_caps(m_filt_f2, { 833, 1663, 3164, 6327, 12654 }),
+ 34270);
+ }
+
+ if(force || m_filt_f3 != m_cur_f3 >> 4) {
+ m_filt_f3 = m_cur_f3 >> 4;
+ build_standard_filter(m_f3_a, m_f3_b,
+ 0,
+ 17594,
+ 868,
+ 18828,
+ 8480 + bits_to_caps(m_filt_f3, { 2226, 4485, 9056, 18111 }),
+ 50019);
+ }
+
+ if(force) {
+ build_standard_filter(m_f4_a, m_f4_b,
+ 0,
+ 28810,
+ 1165,
+ 21457,
+ 8558,
+ 7289);
+
+ build_lowpass_filter(m_fx_a, m_fx_b,
+ 1122,
+ 23131);
+
+ build_noise_shaper_filter(m_fn_a, m_fn_b,
+ 15500,
+ 14854,
+ 8450,
+ 9523,
+ 14083);
+ }
+
+ if(0)
+ if(m_filt_fa || m_filt_va || m_filt_fc || m_filt_f1 || m_filt_f2 || m_filt_f2q || m_filt_f3)
+ logerror("filter fa=%x va=%x fc=%x f1=%x f2=%02x f2q=%x f3=%x\n",
+ m_filt_fa, m_filt_va, m_filt_fc, m_filt_f1, m_filt_f2, m_filt_f2q, m_filt_f3);
+}
+
+stream_sample_t votrax_sc01_device::analog_calc()
+{
+ // Voice-only path.
+ // 1. Pick up the pitch wave
+
+ double v = m_pitch >= (9 << 2) ? 0 : s_glottal_wave[m_pitch >> 2];
+
+ // 2. Multiply by the initial amplifier. It's linear on the die,
+ // even if it's not in the patent.
+ v = v * m_filt_va / 15.0;
+ shift_hist(v, m_voice_1);
+
+ // 3. Apply the f1 filter
+ v = apply_filter(m_voice_1, m_voice_2, m_f1_a, m_f1_b);
+ shift_hist(v, m_voice_2);
+
+ // 4. Apply the f2 filter, voice half
+ v = apply_filter(m_voice_2, m_voice_3, m_f2v_a, m_f2v_b);
+ shift_hist(v, m_voice_3);
+
+ // Noise-only path
+ // 5. Pick up the noise pitch. Amplitude is linear. Base
+ // intensity should be checked w.r.t the voice.
+ double n = 1e4 * ((m_pitch & 0x40 ? m_cur_noise : false) ? 1 : -1);
+ n = n * m_filt_fa / 15.0;
+ shift_hist(n, m_noise_1);
+
+ // 6. Apply the noise shaper
+ n = apply_filter(m_noise_1, m_noise_2, m_fn_a, m_fn_b);
+ shift_hist(n, m_noise_2);
+
+ // 7. Scale with the f2 noise input
+ double n2 = n * m_filt_fc / 15.0;
+ shift_hist(n2, m_noise_3);
+
+ // 8. Apply the f2 filter, noise half,
+ n2 = apply_filter(m_noise_3, m_noise_4, m_f2n_a, m_f2n_b);
+ shift_hist(n2, m_noise_4);
+
+ // Mixed path
+ // 9. Add the f2 voice and f2 noise outputs
+ double vn = v + n2;
+ shift_hist(vn, m_vn_1);
+
+ // 10. Apply the f3 filter
+ vn = apply_filter(m_vn_1, m_vn_2, m_f3_a, m_f3_b);
+ shift_hist(vn, m_vn_2);
+
+ // 11. Second noise insertion
+ vn += n * (5 + (15^m_filt_fc))/20.0;
+ shift_hist(vn, m_vn_3);
+
+ // 12. Apply the f4 filter
+ vn = apply_filter(m_vn_3, m_vn_4, m_f4_a, m_f4_b);
+ shift_hist(vn, m_vn_4);
+
+ // 13. Apply the glottal closure amplitude, also linear
+ vn = vn * (7 ^ (m_cur_closure >> 2)) / 7.0;
+ shift_hist(vn, m_vn_5);
+
+ // 13. Apply the final fixed filter
+ vn = apply_filter(m_vn_5, m_vn_6, m_fx_a, m_fx_b);
+ shift_hist(vn, m_vn_6);
+
+ return int(vn*50000);
}
/*
@@ -301,6 +653,8 @@ double votrax_sc01_device::bits_to_caps(uint32_t value, int caps_count, const do
| V2^ \++-0
| \|
+ It happens to be what most of the filters in the sc01a look like.
+
You need to determine the transfer function H(s) of the circuit, which is
defined as the ratio Vo/Vi. To do that, you use some properties:
@@ -437,24 +791,56 @@ double votrax_sc01_device::bits_to_caps(uint32_t value, int caps_count, const do
- apply the now non-amplifying filter to the historized amplified
input
- That way reduces the probability of the output boucing all over the
+ That way reduces the probability of the output bouncing all over the
place.
+ Except, we're not done yet. Doing resistors precisely in an IC is
+ very hard and/or expensive (you may have heard of "laser cut
+ resistors" in DACs of the time). Doing capacitors is easier, and
+ their value is proportional to their surface. So there are no
+ resistors on the sc01 die (which is a lie, there are three, but not
+ in the filter path. They are used to scale the voltage in the pitch
+ wave and to generate +5V from the +9V), but a magic thing called a
+ switched capacitor. Lookup patent 4,433,210 for details. Using
+ high frequency switching a capacitor can be turned into a resistor
+ of value 1/(C*f) where f is the switching frequency (20Khz,
+ main/36). And the circuit is such that the absolute value of the
+ capacitors is irrelevant, only their ratio is useful, which factors
+ out the intrinsic capacity-per-surface-area of the IC which may be
+ hard to keep stable from one die to another. As a result all the
+ capacitor values we use are actually surfaces in square micrometers.
+
+ For the curious, it looks like the actual capacitance was around 25
+ femtofarad per square micrometer.
+
*/
+void votrax_sc01_device::build_standard_filter(double *a, double *b,
+ double c1t, // Unswitched cap, input, top
+ double c1b, // Switched cap, input, bottom
+ double c2t, // Unswitched cap, over first amp-op, top
+ double c2b, // Switched cap, over first amp-op, bottom
+ double c3, // Cap between the two op-amps
+ double c4) // Cap over second op-amp
+{
+ // First compute the three coefficients of H(s). One can note
+ // that there is as many capacitor values on both sides of the
+ // division, which confirms that the capacity-per-surface-area
+ // is not needed.
+ double k0 = c1t / (m_cclock * c1b);
+ double k1 = c4 * c2t / (m_cclock * c1b * c3);
+ double k2 = c4 * c2b / (m_cclock * m_cclock * c1b * c3);
-//-------------------------------------------------------------
-// filter_s_to_z - analog to digital filter transformation
-//-------------------------------------------------------------
+ // Estimate the filter cutoff frequency
+ double fpeak = sqrt(fabs(k0*k1 - k2))/(2*M_PI*k2);
-void votrax_sc01_device::filter_s_to_z(const double *k, double fs, double *a, double *b)
-{
- double fpeak = sqrt(fabs(k[0]*k[1]-k[2]))/(2*M_PI*k[2]);
- double zc = 2*M_PI*fpeak/tan(M_PI*fpeak/fs);
+ // Turn that into a warp multiplier
+ double zc = 2*M_PI*fpeak/tan(M_PI*fpeak / m_sclock);
- double m0 = zc*k[0];
- double m1 = zc*k[1];
- double m2 = zc*zc*k[2];
+ // Finally compute the result of the z-transform
+ double m0 = zc*k0;
+ double m1 = zc*k1;
+ double m2 = zc*zc*k2;
a[0] = 1+m0;
a[1] = 3+m0;
@@ -466,912 +852,154 @@ void votrax_sc01_device::filter_s_to_z(const double *k, double fs, double *a, do
b[3] = 1-m1+m2;
}
+/*
+ Second filter type used once at the end, much simpler:
-//-------------------------------------------------------------
-// apply_filter - apply the digital filter (before output
-// shifting, so y[0] is one step in the past)
-//-------------------------------------------------------------
-double votrax_sc01_device::apply_filter(const double *x, const double *y, const double *a, const double *b)
-{
- return (x[0]*a[0] + x[1]*a[1] + x[2]*a[2] + x[3]*a[3] - y[0]*b[1] - y[1]*b[2] - y[2]*b[3]) / b[0];
-}
-
-
-//-------------------------------------------------------------
-// shift_hist - shift a value in an output history
-//-------------------------------------------------------------
-
-void votrax_sc01_device::shift_hist(double val, double *hist_array, int hist_size)
-{
- for(int i = 0; i < hist_size-1; i++)
- hist_array[hist_size-1-i] = hist_array[hist_size-2-i];
- hist_array[0] = val;
-}
-
-
-//-------------------------------------------------
-// sound_stream_update - handle update requests
-// for our sound stream
-//-------------------------------------------------
-
-void votrax_sc01_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
-{
- // determine how many master half-clocks per sample
- int half_clocks_per_sample = (m_master_clock_freq * 2) / stream.sample_rate();
-
- // iterate over clocks (samples)
- stream_sample_t *dest = outputs[0];
- while (samples--)
- {
- // run the digital logic at the master clock rate
- double glottal_out = 0;
- uint8_t noise_out_digital = 0;
- for (int curclock = 0; curclock < half_clocks_per_sample; curclock++)
- {
-if (LOG_TIMING | LOG_LOWPARAM | LOG_GLOTTAL | LOG_TRANSITION)
-{
- if (m_counter_34 % 32 == 0 && m_master_clock == 0)
- {
- if (LOG_TIMING)
- osd_printf_debug("MCLK C034 L070 L072 BET1 P1 P2 PHI1 PHI2 PH1' PH2' SUBC C088 C084 L092 IIRQ ");
- if (LOG_LOWPARAM)
- osd_printf_debug("F132 F114 F112 F142 L080 ");
- if (LOG_GLOTTAL)
- osd_printf_debug("C220 C222 C224 C234 C236 FGAT GLSY ");
- if (LOG_TRANSITION)
- osd_printf_debug("0625 C046 L046 A0-2 L168 L170 FC VA FA F1 F2 F3 F2Q ");
- osd_printf_debug("\n");
- }
- if (LOG_TIMING)
- osd_printf_debug("%4X %4X %4X %4X %4X %4X %4X %4X %4X %4X %4X %4X %4X %4X %4X %4X ", m_master_clock, m_counter_34, m_latch_70, m_latch_72, m_beta1, m_p1, m_p2, m_phi1, m_phi2, m_phi1_20, m_phi2_20, m_subphoneme_count, m_clock_88, m_counter_84, m_latch_92, m_internal_request);
- if (LOG_LOWPARAM)
- osd_printf_debug("%d %d %d %d %d ", m_srff_132, m_srff_114, m_srff_112, m_srff_142, m_latch_80);
- if (LOG_GLOTTAL)
- osd_printf_debug("%4X %4X %4X %4X %4X %4X %4X ", m_counter_220, m_counter_222, m_counter_224, m_counter_234, m_counter_236, m_fgate, m_glottal_sync);
- if (LOG_TRANSITION)
- osd_printf_debug("%4X %4X %4X %4X %4X %4X %4X %4X %4X %4X %4X %4X %4X ", m_0625_clock, m_counter_46, m_latch_46, m_latch_72 & 7, m_latch_168, m_latch_170, m_fc, m_va, m_fa, m_f1, m_f2, m_f3, m_f2q);
- osd_printf_debug("\n");
-}
-
- //==============================================
- //
- // Timing circuit (patent figure 2a)
- //
- //==============================================
-
- // update master clock
- m_master_clock ^= 1;
-
- // on the falling edge of the master clock, advance the 10-bit counter at 34
- uint8_t old_latch_72 = m_latch_72;
- if (m_master_clock == 0)
- m_counter_34 = (m_counter_34 + 1) & 0x3ff;
- else
- {
- m_latch_70 = m_counter_34 & 0xf;
- m_latch_72 = ((m_counter_34 >> 4) & 7) | ((m_counter_34 >> 6) & 8);
- }
-
- // derive beta 1 clock:
- // set if m_latch_70.0 == 1
- // reset if m_latch_70.0 == 0
-// uint8_t old_beta1 = m_beta1;
- m_beta1 = BIT(m_latch_70, 0);
-
- // derive p2 clock:
- // set if (m_counter_34.P_CLOCK_BIT & clock) == 1
- // reset if (m_counter_34.P_CLOCK_BIT == 0)
- uint8_t old_p2 = m_p2;
- if (BIT(m_counter_34, P_CLOCK_BIT) & m_master_clock)
- m_p2 = 1;
- else if (!BIT(m_counter_34, P_CLOCK_BIT))
- m_p2 = 0;
-
- // derive p1 clock:
- // set if (!m_counter_34.P_CLOCK_BIT & clock) == 1
- // reset if (m_counter_34.P_CLOCK_BIT == 1)
-// uint8_t old_p1 = m_p1;
- if (BIT(~m_counter_34, P_CLOCK_BIT) & m_master_clock)
- m_p1 = 1;
- else if (BIT(m_counter_34, P_CLOCK_BIT))
- m_p1 = 0;
-
- // derive phi2 clock:
- // set if (m_counter_34.PHI_CLOCK_BIT & clock) == 1
- // reset if (m_counter_34.PHI_CLOCK_BIT == 0)
- uint8_t old_phi2 = m_phi2;
- if (BIT(m_counter_34, PHI_CLOCK_BIT) & m_master_clock)
- m_phi2 = 1;
- else if (!BIT(m_counter_34, PHI_CLOCK_BIT))
- m_phi2 = 0;
-
- // derive phi1 clock:
- // set if (!m_counter_34.PHI_CLOCK_BIT & clock) == 1
- // reset if (m_counter_34.PHI_CLOCK_BIT == 1)
- uint8_t old_phi1 = m_phi1;
- if (BIT(~m_counter_34, PHI_CLOCK_BIT) & m_master_clock)
- m_phi1 = 1;
- else if (BIT(m_counter_34, PHI_CLOCK_BIT))
- m_phi1 = 0;
-
- // derive alternate phi2 clock:
- // set if (m_counter_34.PHI_CLOCK_BIT & clock) == 1
- // reset if (m_counter_34.PHI_CLOCK_BIT == 0)
- uint8_t old_phi2_20 = m_phi2_20;
- if (BIT(m_counter_34, PHI_CLOCK_BIT + 2) & m_master_clock)
- m_phi2_20 = 1;
- else if (!BIT(m_counter_34, PHI_CLOCK_BIT + 2))
- m_phi2_20 = 0;
-
- // derive alternate phi1 clock:
- // set if (!m_counter_34.PHI_CLOCK_BIT & clock) == 1
- // reset if (m_counter_34.PHI_CLOCK_BIT == 1)
-// uint8_t old_phi1_20 = m_phi1_20;
- if (BIT(~m_counter_34, PHI_CLOCK_BIT + 2) & m_master_clock)
- m_phi1_20 = 1;
- else if (BIT(m_counter_34, PHI_CLOCK_BIT + 2))
- m_phi1_20 = 0;
-
- // determine rising edges of each clock of interest
-// uint8_t beta1_rising = (old_beta1 ^ m_beta1) & m_beta1;
- uint8_t p2_rising = (old_p2 ^ m_p2) & m_p2;
-// uint8_t p1_rising = (old_p1 ^ m_p1) & m_p1;
- uint8_t phi2_rising = (old_phi2 ^ m_phi2) & m_phi2;
- uint8_t phi1_rising = (old_phi1 ^ m_phi1) & m_phi1;
- uint8_t phi2_20_rising = (old_phi2_20 ^ m_phi2_20) & m_phi2_20;
-// uint8_t phi1_20_rising = (old_phi1_20 ^ m_phi1_20) & m_phi1_20;
- uint8_t a0_rising = BIT((old_latch_72 ^ m_latch_72) & m_latch_72, 0);
- uint8_t a2_rising = BIT((old_latch_72 ^ m_latch_72) & m_latch_72, 2);
- uint8_t _125k_rising = BIT((old_latch_72 ^ m_latch_72) & m_latch_72, 3);
-
- // track subphoneme counter state
- if (!(m_latch_42 | m_phi1))
- m_subphoneme_count = 0;
- else
- m_subphoneme_count++;
- if (p2_rising)
- m_latch_42 = (m_subphoneme_count < m_subphoneme_period);
-
- // update the state of the subphoneme clock line
- uint8_t old_clock_88 = m_clock_88;
- m_clock_88 = !m_latch_42; //!(m_latch_42 | m_phi1); -- figure 7 seems to be wrong here
- uint8_t clock_88_rising = (old_clock_88 ^ m_clock_88) & m_clock_88;
-
- // the A/R line holds the counter in reset except during phoneme processing,
- // when it is clocked on the rising edge of the subphoneme timer clock
- if (m_internal_request != CLEAR_LINE)
- m_counter_84 = 0xf;
- else if (clock_88_rising)
- {
- m_counter_84 = (m_counter_84 - 1) & 0x0f;
-osd_printf_debug("counter=%d\n", m_counter_84);
- }
-
- // clock the zero count latch
- if (p2_rising)
- m_latch_92 = ((m_counter_84 == 0) | (m_latch_92 << 1)) & 3;
-
- // once both bits are set, the request line goes high
- if (m_latch_92 == 3)
- {
- // if the request line was previously low, reset the VD/CLD flip-flops
- if (m_internal_request == CLEAR_LINE)
- {
- m_srff_112 = m_srff_114 = false;
- }
- m_internal_request = ASSERT_LINE;
- }
-
- //==============================================
- //
- // Low parameter clocking (patent figure 2b)
- //
- //==============================================
-
- // fetch ROM data; note that the address lines come directly from
- // counter_34 and not from the latches, which are 1 cycle delayed
- uint8_t romdata = m_rom[(m_phoneme << 3) | ((m_counter_34 >> 4) & 7)];
-
- // update the ROM data; ROM format is (upper nibble/lower nibble)
- // +00 = F1 parameter / 0
- // +01 = F2 parameter / 0
- // +02 = FC parameter / 0
- // +03 = F3 parameter / CL
- // +04 = F2Q Parameter / CLD
- // +05 = VA Parameter / VD
- // +06 = FA Parameter / PAC
- // +07 = Phoneme timing (full 7 bits)
-
- // latch a new value from ROM on phi2
- uint8_t a = m_latch_72 & 7;
- uint8_t romdata_swapped;
- if (phi2_rising)
- {
- switch (a)
- {
- // update CL
- case 3:
- m_srff_132 = m_srff_114 && BIT(~romdata, 3);
- break;
-
- // update CLD
- case 4:
- romdata_swapped = (BIT(romdata, 0) << 3) | (BIT(romdata, 1) << 2) | (BIT(romdata, 2) << 1) | (BIT(romdata, 3) << 0);
- if (m_counter_84 != 0 && romdata_swapped == (m_counter_84 ^ 0xf))
- {
- m_srff_114 = true;
- }
- break;
-
- // update VD
- case 5:
- romdata_swapped = (BIT(romdata, 0) << 3) | (BIT(romdata, 1) << 2) | (BIT(romdata, 2) << 1) | (BIT(romdata, 3) << 0);
- if (m_counter_84 != 0 && romdata_swapped == (m_counter_84 ^ 0xf))
- {
- m_srff_112 = true;
- }
- break;
-
- // update FF == PAC & (VA | FA)
- case 6:
- m_srff_142 = BIT(romdata, 3);
- break;
-
- // update PH
- case 7:
- if (m_latch_80 != (romdata & 0x7f))
- {
- m_latch_80 = romdata & 0x7f;
-osd_printf_debug("[PH=%02X]\n", m_latch_80);
- uint32_t old_period = m_subphoneme_period;
- update_subphoneme_clock_period();
- m_subphoneme_count = (m_subphoneme_count * m_subphoneme_period) / old_period;
- m_phoneme_timer->adjust(attotime::zero);
- }
- break;
- }
- }
-
- //==============================================
- //
- // Glottal circuit (patent figure 6)
- //
- //==============================================
-
- // determine the TC output from the counters (note that TC requires ET)
- uint8_t counter_222_tc = (m_counter_222 == 0xf);
- uint8_t counter_220_tc = (m_counter_220 == 0xf && counter_222_tc);
- uint8_t counter_224_tc = (m_counter_224 == 0xf && counter_222_tc);
-
- // clock glottal counter 224 on rising edge of a0
- if (a0_rising)
- {
- // counter 224 is only enabled if TC of counter 222 is 1
- if (counter_222_tc)
- {
- // if counter 220's TC is 1, do a load instead of a count
- if (counter_220_tc)
- m_counter_224 = (m_inflection << 1) | ((~m_f1 & 0x8) >> 3);
- else
- m_counter_224 = (m_counter_224 + 1) & 0xf;
- }
- }
-
- // clock remaining glottal counters (220, 222, 236) on rising edge of phi2
- if (phi2_20_rising)
- {
- // counter 220 is only enabled if TC of counter 222 is 1
- if (counter_222_tc)
- {
- // if counter 220's TC is 1, do a load instead of a count
- if (counter_220_tc)
- m_counter_220 = (m_inflection << 1) | ((~m_f1 & 0x8) >> 3);
- else
- m_counter_220 = (m_counter_220 + 1) & 0xf;
- }
-
- // counter 222 is always enabled
- if (1)
- {
- // if counter 220's TC is 1, do a load instead of a count
- if (counter_220_tc)
- m_counter_222 = (~m_f1 & 0x7) << 1;
- else
- m_counter_222 = (m_counter_222 + 1) & 0xf;
- }
-
- // counter 236 is always enabled
- if (1)
- {
- m_counter_236 = (m_counter_236 + 1) & 0xf;
-
- // rising edge of Q1 from counter 236 clocks counter 234
- if ((m_counter_236 & 0x3) == 0x2)
- {
- // counter 234 is only enabled if it has not reached terminal
- if (m_counter_234 != 0xf)
- m_counter_234 = (m_counter_234 + 1) & 0xf;
- }
- }
- }
-
- // update FGATE state
- if (counter_220_tc)
- m_fgate = 0;
- if (counter_224_tc)
- m_fgate = 1;
-
- // apply asynchronous clear to counters 234/236
- if (counter_220_tc && m_phi1_20)
- m_counter_236 = m_counter_234 = 0;
-
- // derive glottal circuit output signals
-#if !TEMP_HACKS
- uint8_t old_glottal_sync = m_glottal_sync;
-#endif
- m_glottal_sync = (m_counter_234 == 0);
- glottal_out = s_glottal_wave[m_counter_234];
-
- //==============================================
- //
- // Transition circuit (patent figure 3a/3b)
- //
- //==============================================
-
- // divide 1.25k clock by 2 (lower-left of 46)
- uint8_t old_0625_clock = m_0625_clock;
- if (_125k_rising)
- m_0625_clock = !m_0625_clock;
- uint8_t _0625_rising = (old_0625_clock ^ m_0625_clock) & m_0625_clock;
-
- // update counter above
- if (_0625_rising)
- {
- if (m_counter_46 == 0xf)
- m_counter_46 = 0xd;
- else
- m_counter_46 = (m_counter_46 + 1) & 0xf;
- }
-
- // and then the latch to the right
- if (a2_rising)
- m_latch_46 = (BIT(m_counter_46, 1) << 0) |
- (BIT(m_latch_46, 0) << 1) |
- (m_0625_clock << 2) |
- (BIT(m_latch_46, 2) << 3);
-
-#if TEMP_HACKS
- m_latch_46 = 0xf;
-#endif
-
- // determine the read/write signal
- uint8_t ram_write = 0;
- switch (a)
- {
- // write if not FF and low 2 bits of latch
- // FF is the S/R flip-flop at 142 ANDed with !(/FA & /VA)
- case 0: case 1: case 2: case 3: case 4:
- if ((m_srff_142 && !((m_fa == 0) && (m_va == 0))) == 0 && (m_latch_46 & 0x3) == 0x3)
- ram_write = 1;
- break;
-
- case 5:
- if ((m_latch_46 & 0xc) == 0xc && m_srff_112)
- {
- ram_write = 1;
- }
- break;
-
- case 6:
- if ((m_latch_46 & 0xc) == 0xc && m_srff_114)
- {
- ram_write = 1;
- }
- break;
- }
-
- // gate on the phi2 clock (OR gate @ 172)
- ram_write &= m_phi2;
-
- // write the transitioned values to RAM if requested
- // (note we consolidate the serial addition and clocking steps here)
- if (ram_write)
- {
- uint8_t old = (m_latch_168 << 4) | m_latch_170;
- m_ram[a] = old - (old >> 3) + ((romdata & 0xf0) >> 3);
- }
-
- // latch some parameter values on rising edge of phi2
- if (phi2_rising)
- {
- switch (a)
- {
- case 2:
- m_fc = m_latch_168;
- break;
-
- case 5:
- m_va = m_latch_168;
- break;
-
- case 6:
- m_fa = m_latch_168;
- break;
- }
- }
-
- // latch remaining parameter values on rising edge of (phi2 & glottal sync)
-#if TEMP_HACKS
- if (phi2_rising)
-#else
- uint8_t old_phi2_glottal = (old_phi2 & old_glottal_sync);
- uint8_t new_phi2_glottal = m_phi2 & m_glottal_sync;
- if ((old_phi2_glottal ^ new_phi2_glottal) & new_phi2_glottal)
-#endif
- switch (a)
- {
- case 0:
- m_f1 = m_latch_168;
- break;
-
- case 1:
- m_f2 = (m_latch_168 << 1) | (m_latch_170 >> 3);
- break;
-
- case 3:
- m_f3 = m_latch_168;
- break;
-
- case 4:
- m_f2q = m_latch_168;
- break;
- }
-
- // latch value from RAM on rising edge of phi1
- if (phi1_rising)
- {
- m_latch_168 = m_ram[a] >> 4;
- m_latch_170 = m_ram[a] & 0xf;
- }
-
- //==============================================
- //
- // Noise generator circuit (patent figure 8)
- //
- //==============================================
-
- // nose is clocked by the NOR of /FA and P1
- uint8_t old_noise_clock = m_noise_clock;
- m_noise_clock = !((m_fa == 0) | m_p1);
- uint8_t noise_clock_rising = (old_noise_clock ^ m_noise_clock) & m_noise_clock;
- uint8_t noise_clock_falling = (old_noise_clock ^ m_noise_clock) & old_noise_clock;
-
- // falling edge clocks the shift register
- if (noise_clock_falling)
- {
- // shift register 252 is actually 4 shift registers (2 4-bit, 2 5-bit)
- // d1 and d3 are the 4-bit registers, d2 and d4 are the 5-bit registers
- // XOR'ed input goes into d4, which shifts in to d2, then d3, then d1
- // thus the full 18-bit value is effectively
- //
- // d4 = (m_shift_252 >> 0) & 0x1f;
- // d2 = (m_shift_252 >> 5) & 0x1f;
- // d3 = (m_shift_252 >> 10) & 0xf;
- // d1 = (m_shift_252 >> 14) & 0xf;
- //
- // input at the low end is ((d1+4 ^ d2+5) ^ (d4+4 ^ d4+5)) ^ !(counter2 | counter3)
- // output is tapped at d3+4
-
- uint32_t old_shift = m_shift_252;
- m_shift_252 <<= 1;
- m_shift_252 |= ((BIT(old_shift, 17) ^ BIT(old_shift, 9)) ^ (BIT(old_shift, 3) ^ BIT(old_shift, 4))) ^
- ((m_counter_250 & 0xc) == 0);
- }
-
- // rising edge clocks the counter
- if (noise_clock_rising)
- {
- // counter is reset to 1 if terminal, otherwise it increments
- if (m_counter_250 == 0xf)
- m_counter_250 = 0x1;
- else
- m_counter_250 = (m_counter_250 + 1) & 0xf;
- }
-
- // compute final noise out signal
- noise_out_digital = !(BIT(m_shift_252, 13) & (m_fgate | (m_va == 0)));
- }
-
- // TODO: cache the filters
- // filter coefs
- double k[3], a[4], b[4];
-
- // base frequencies
- double fc = m_master_clock_freq / 30.0; // Nominal is 20KHz
- double fs = stream.sample_rate();
-
- // useful temporaries
- double rcp, rcq, rca;
-
- // amplification stage
- static const double va_caps[4] = { 27, 53, 107, 213 };
- double va_out = glottal_out * bits_to_caps(m_va, 4, va_caps) / 400;
-
- shift_hist(va_out, m_va_hist, 4);
-
-
- // noise shaping
- static const double fa_caps[4] = { 27, 53, 107, 213 };
- rcp = bits_to_caps(m_fa, 4, fa_caps);
-
- shift_hist(-noise_out_digital * 400*rcp/(358.0*100000*566*(fc*rcp*1e-12 + 1.0/100000 + 1.0/2000)), m_ni_hist, 4);
-
- k[0] = 400/(fc*358);
- k[1] = 400*400/(fc*358*566);
- k[2] = 400*400/(fc*fc*358*358);
-
- filter_s_to_z(k, fs, a, b);
- double no_out = apply_filter(m_ni_hist, m_no_hist, a, b);
- shift_hist(no_out, m_no_hist, 4);
-
-
- // stage 1 filter
-
- static const double s1_p_caps[4] = { 16.4, 33, 66, 130 };
- rcp = 24 + bits_to_caps(m_f1, 4, s1_p_caps);
- rcq = 20;
-
- k[0] = 253/(fc*270);
- k[1] = 1080*rcq/(fc*270*rcp);
- k[2] = 1080*1080/(fc*fc*270*rcp);
-
- filter_s_to_z(k, fs, a, b);
- double s1_out = apply_filter(m_va_hist, m_s1_hist, a, b);
- shift_hist(s1_out, m_s1_hist, 4);
-
-
- // stage 2 filter, glottal half
-
- static const double s2_p_caps[5] = { 14, 28, 56, 113, 226 };
- static const double s2_q_caps[4] = { 23, 46, 93, 186 };
- rcp = 46 + bits_to_caps(m_f2, 5, s2_p_caps);
- rcq = 20 + bits_to_caps(m_f2q, 4, s2_q_caps);;
-
- k[0] = 400/(fc*470);
- k[1] = 620*rcq/(fc*470*rcp);
- k[2] = 620*620/(fc*fc*470*rcp);
-
- filter_s_to_z(k, fs, a, b);
- double s2g_out = apply_filter(m_s1_hist, m_s2g_hist, a, b);
- shift_hist(s2g_out, m_s2g_hist, 4);
-
-
- // stage 2 filter, noise half (rcp and rcq kept from stage 2 glottal)
-
- static const double s2_n_caps[5] = { 19, 38, 76, 152 };
- rca = bits_to_caps(m_fc, 4, s2_n_caps);
-
- shift_hist(-no_out*rcq*rca/(470*rcp), m_s2ni_hist, 4);
-
- k[0] = 400/(fc*470);
- k[1] = 620*rcq/(fc*470*rcp);
- k[2] = 620*620/(fc*fc*470*rcp);
-
- filter_s_to_z(k, fs, a, b);
- double s2n_out = apply_filter(m_s2ni_hist, m_s2n_hist, a, b);
- shift_hist(s2n_out, m_s2n_hist, 4);
-
- // sum the stage 2 outputs
- double s2_out = s2g_out + s2n_out;
- shift_hist(s2_out, m_s2_hist, 4);
-
-
- // stage 3 filter
-
- static const double s3_p_caps[4] = { 21, 42, 84, 168 };
- rcp = 76 + bits_to_caps(m_f3, 4, s3_p_caps);
- rcq = 20;
-
- k[0] = 0;
- k[1] = 420*rcq/(fc*390*rcp);
- k[2] = 420*420/(fc*fc*390*rcp);
-
- filter_s_to_z(k, fs, a, b);
- double s3_out = apply_filter(m_s2_hist, m_s3_hist, a, b);
- shift_hist(s3_out, m_s3_hist, 4);
-
-
- // stage 4 filter, noise injection
-
- // The resulting non-amplifying filter is identical, so we
- // inject instead of splitting
-
- static const double s4_n_caps[4] = { 24, 48, 96, 192 };
- rca = 115 + bits_to_caps(~m_fc, 4, s4_n_caps);
-
- shift_hist(s3_out + no_out*470/rca, m_s4i_hist, 4);
-
-
- // stage 4 filter
-
- rcp = 30;
- rcq = 20;
-
- k[0] = 0;
- k[1] = 338*rcq/(fc*470*rcp);
- k[2] = 338*338/(fc*fc*470*rcp);
-
- filter_s_to_z(k, fs, a, b);
- double s4_out = apply_filter(m_s4i_hist, m_s4_hist, a, b);
- shift_hist(s4_out, m_s4_hist, 4);
-
-
- // TODO: apply closure circuit (undocumented)
-
- // output the current result
- *dest++ = int16_t(s4_out * 4000);
- }
-}
-
-
-
-//**************************************************************************
-// DEVICE INTERFACE
-//**************************************************************************
-
-//-------------------------------------------------
-// rom_region - return a pointer to the device's
-// internal ROM region
-//-------------------------------------------------
-
-const tiny_rom_entry *votrax_sc01_device::device_rom_region() const
-{
- return ROM_NAME( votrax_sc01 );
-}
+ | +--[R1]--+
+ | | |
+ | +--|C1|--+
+ | | |
+ | Vi | |\ |
+ | ---[R0]--+--+-\ |
+ | | >--+------ Vo
+ | 0-++/
+ | |/
-//-------------------------------------------------
-// device_start - handle device startup
-//-------------------------------------------------
+ Vi/R0 = Vo / (1/(1/R1 + s.C1)) = Vo (1/R1 + s.C1)
+ H(s) = Vo/Vi = (R1/R0) * (1 / (1 + s.R1.C1))
+*/
-void votrax_sc01_device::device_start()
+void votrax_sc01_device::build_lowpass_filter(double *a, double *b,
+ double c1t, // Unswitched cap, over amp-op, top
+ double c1b) // Switched cap, over amp-op, bottom
{
- // initialize internal state
- m_master_clock_freq = clock();
- m_stream = stream_alloc(0, 1, m_master_clock_freq / 16);
- m_phoneme_timer = timer_alloc();
- m_rom = memregion("phoneme")->base();
+ // Compute the only coefficient we care about
+ double k = c1b / (m_cclock * c1t);
- // reset inputs
- m_inflection = 0;
- m_phoneme = 0x3f;
+ // Compute the filter cutoff frequency
+ double fpeak = 1/(2*M_PI*k);
- // reset outputs
- m_request_cb.resolve_safe();
- m_request_state = ASSERT_LINE;
- m_internal_request = ASSERT_LINE;
+ // Turn that into a warp multiplier
+ double zc = 2*M_PI*fpeak/tan(M_PI*fpeak / m_sclock);
- // save inputs
- save_item(NAME(m_inflection));
- save_item(NAME(m_phoneme));
+ // Finally compute the result of the z-transform
+ double m = zc*k;
- // save outputs
- save_item(NAME(m_request_state));
- save_item(NAME(m_internal_request));
-
- // save timing circuit
- save_item(NAME(m_master_clock_freq));
- save_item(NAME(m_master_clock));
- save_item(NAME(m_counter_34));
- save_item(NAME(m_latch_70));
- save_item(NAME(m_latch_72));
- save_item(NAME(m_beta1));
- save_item(NAME(m_p2));
- save_item(NAME(m_p1));
- save_item(NAME(m_phi2));
- save_item(NAME(m_phi1));
- save_item(NAME(m_subphoneme_period));
- save_item(NAME(m_subphoneme_count));
- save_item(NAME(m_clock_88));
- save_item(NAME(m_latch_42));
- save_item(NAME(m_counter_84));
- save_item(NAME(m_latch_92));
-
- // save low parameter clocking
- save_item(NAME(m_srff_132));
- save_item(NAME(m_srff_114));
- save_item(NAME(m_srff_112));
- save_item(NAME(m_srff_142));
- save_item(NAME(m_latch_80));
-
- // save glottal circuit
- save_item(NAME(m_counter_220));
- save_item(NAME(m_counter_222));
- save_item(NAME(m_counter_224));
- save_item(NAME(m_counter_234));
- save_item(NAME(m_counter_236));
- save_item(NAME(m_fgate));
- save_item(NAME(m_glottal_sync));
-
- // save transition circuit
- save_item(NAME(m_0625_clock));
- save_item(NAME(m_counter_46));
- save_item(NAME(m_latch_46));
- save_item(NAME(m_ram));
- save_item(NAME(m_latch_168));
- save_item(NAME(m_latch_170));
- save_item(NAME(m_f1));
- save_item(NAME(m_f2));
- save_item(NAME(m_fc));
- save_item(NAME(m_f3));
- save_item(NAME(m_f2q));
- save_item(NAME(m_va));
- save_item(NAME(m_fa));
-
- // save noise generator circuit
- save_item(NAME(m_noise_clock));
- save_item(NAME(m_shift_252));
- save_item(NAME(m_counter_250));
-
- // save filter histories
- save_item(NAME(m_ni_hist));
- save_item(NAME(m_no_hist));
- save_item(NAME(m_va_hist));
- save_item(NAME(m_s1_hist));
- save_item(NAME(m_s2g_hist));
- save_item(NAME(m_s2n_hist));
- save_item(NAME(m_s2ni_hist));
- save_item(NAME(m_s2_hist));
- save_item(NAME(m_s3_hist));
- save_item(NAME(m_s4i_hist));
- save_item(NAME(m_s4_hist));
+ a[0] = 1;
+ b[0] = 1+m;
+ b[1] = 1-m;
}
+/*
+ Used to shape the white noise
+
+ +-------------------------------------------------------------------+
+ | |
+ +--|C1|--+---------|C3|----------+--|C4|--+ |
+ | | + + | | |
+ Vi | |\ | (1) (1) | | + + |
+ -|R0|-+--+-\ | | | | |\ | (1) (1) |
+ | >--+--(2)-+--|C2|--+---(2)-+--+-\ | | | |
+ 0-++/ | | >--+--(2)--+--|C5|--+---(2)--+
+ |/ Vo 0-++/
+ |/
+ Equivalent:
+
+ +------------------|R5|-------------------+
+ | |
+ +--|C1|--+---------|C3|----------+--|C4|--+
+ | | | |
+ Vi | |\ | | |
+ -|R0|-+--+-\ | | |\ |
+ | >--+---------|R2|----------+--+-\ |
+ 0-++/ | | >--+
+ |/ Vo 0-++/
+ |/
+
+ We assume r0 = r2
+*/
-//-------------------------------------------------
-// device_reset - handle device reset
-//-------------------------------------------------
-
-void votrax_sc01_device::device_reset()
+void votrax_sc01_device::build_noise_shaper_filter(double *a, double *b,
+ double c1, // Cap over first amp-op
+ double c2t, // Unswitched cap between amp-ops, input, top
+ double c2b, // Switched cap between amp-ops, input, bottom
+ double c3, // Cap over second amp-op
+ double c4) // Switched cap after second amp-op
{
- // set the initial state
- m_stream->update();
+ // Coefficients of H(s) = k1*s / (1 + k2*s + k3*s^2)
+ double k0 = c2t*c3*c2b/c4;
+ double k1 = c2t*(m_cclock * c2b);
+ double k2 = c1*c2t*c3/(m_cclock * c4);
- // reset inputs
- m_phoneme = 0x3f;
- m_request_cb(m_internal_request = m_request_state = ASSERT_LINE);
-
- // reset timing circuit
- m_master_clock = 0;
- m_counter_34 = 0;
- m_latch_70 = 0;
- m_latch_72 = 0;
- m_beta1 = 0;
- m_p2 = 0;
- m_p1 = 0;
- m_phi2 = 0;
- m_phi1 = 0;
- m_subphoneme_period = 1000;
- m_subphoneme_count = 0;
- m_clock_88 = 0;
- m_latch_42 = 0;
- m_counter_84 = 0;
- m_latch_92 = 0;
-
- // reset low parameter clocking
- m_srff_132 = false;
- m_srff_114 = false;
- m_srff_112 = false;
- m_srff_142 = false;
- m_latch_80 = 50;
- update_subphoneme_clock_period();
-
- // reset glottal circuit
- m_counter_220 = 0;
- m_counter_222 = 0;
- m_counter_224 = 0;
- m_counter_234 = 0;
- m_counter_236 = 0;
- m_fgate = 0;
- m_glottal_sync = 0;
-
- // reset transition circuit
- m_0625_clock = 0;
- m_counter_46 = 0;
- m_latch_46 = 0;
- memset(m_ram, 0, sizeof(m_ram));
- m_latch_168 = 0;
- m_latch_170 = 0;
- m_f1 = 0;
- m_f2 = 0;
- m_fc = 0;
- m_f3 = 0;
- m_f2q = 0;
- m_va = 0;
- m_fa = 0;
-
- // reset noise circuit
- m_noise_clock = 0;
- m_shift_252 = 0;
- m_counter_250 = 0;
-
- // reset filter histories
- memset(m_ni_hist, 0, sizeof(m_ni_hist));
- memset(m_no_hist, 0, sizeof(m_no_hist));
- memset(m_va_hist, 0, sizeof(m_va_hist));
- memset(m_s1_hist, 0, sizeof(m_s1_hist));
- memset(m_s2g_hist, 0, sizeof(m_s2g_hist));
- memset(m_s2n_hist, 0, sizeof(m_s2n_hist));
- memset(m_s2ni_hist, 0, sizeof(m_s2ni_hist));
- memset(m_s2_hist, 0, sizeof(m_s2_hist));
- memset(m_s3_hist, 0, sizeof(m_s3_hist));
- memset(m_s4i_hist, 0, sizeof(m_s4i_hist));
- memset(m_s4_hist, 0, sizeof(m_s4_hist));
-}
+ // Estimate the filter cutoff frequency
+ double fpeak = sqrt(1/k2)/(2*M_PI);
+ // Turn that into a warp multiplier
+ double zc = 2*M_PI*fpeak/tan(M_PI*fpeak / m_sclock);
-//-------------------------------------------------
-// device_clock_changed - handle dynamic clock
-// changes by altering our output frequency
-//-------------------------------------------------
-
-void votrax_sc01_device::device_clock_changed()
-{
- // compute new frequency of the master clock, and update if changed
- uint32_t newfreq = clock();
- if (newfreq != m_master_clock_freq)
- {
- // if we have a stream
- if (m_stream != nullptr)
- {
- m_stream->update();
- m_stream->set_sample_rate(newfreq / 16);
- }
-
- // determine how many clock ticks remained on the phoneme timer
- uint64_t remaining = m_phoneme_timer->remaining().as_ticks(m_master_clock_freq);
-
- // recompute the master clock
- m_master_clock_freq = newfreq;
+ // Finally compute the result of the z-transform
+ double m0 = zc*k0;
+ double m1 = zc*k1;
+ double m2 = zc*zc*k2;
- // adjust the phoneme timer to the same number of ticks based on the new frequency
- if (remaining > 0)
- m_phoneme_timer->adjust(attotime::from_ticks(remaining, newfreq));
- }
+ a[0] = m0;
+ a[1] = 0;
+ a[2] = -m0;
+ b[0] = 1+m1+m2;
+ b[1] = 2-2*m2;
+ b[2] = 1-m1+m2;
}
+/*
+ Noise injection in f2
-//-------------------------------------------------
-// device_timer - handle device timer
-//-------------------------------------------------
-
-void votrax_sc01_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
-{
- // force a stream update
- m_stream->update();
-
- // if we're requesting more data, no need for timing
- if (m_request_state == ASSERT_LINE)
- return;
-
- // if we're supposed to have fired, do it now
- if (m_internal_request == ASSERT_LINE)
- {
-osd_printf_debug("%s: REQUEST\n", timer.machine().time().as_string(3));
- m_request_cb(m_request_state = ASSERT_LINE);
- return;
- }
+ | +--[R2]--+ +--[R1]-------- Vi
+ | | | |
+ | +--|C2|--+<V1 +--|C3|--+
+ | | | | |
+ | | |\ | | |\ |
+ | +----+--+-\ | +--+-\ |
+ | | | >--+--[Rx]--+ | >--+----- Vo
+ | | 0-++/ 0-++/ |
+ | | |/ +--[R0]--+ |/ |
+ | | | | |
+ | | | /| | |
+ | | | /-+--+--[R0]--+
+ | +--[R4]-------+--< |
+ | V2^ \++-0
+ | \|
- // account for the rest of this subphoneme clock
- uint32_t clocks_until_request = 0;
- if (m_counter_84 != 0)
- {
- if (m_subphoneme_count < m_subphoneme_period)
- clocks_until_request += m_subphoneme_period - m_subphoneme_count;
- clocks_until_request += m_subphoneme_period * (m_counter_84 - 1);
- }
+ We drop r0/r1 out of the equation (it factorizes), and we rescale so
+ that H(infinity)=1.
+*/
- // plus 1/2
- clocks_until_request = std::max(clocks_until_request, uint32_t(1 << P_CLOCK_BIT) / 2);
- timer.adjust(attotime::from_ticks(clocks_until_request, m_master_clock_freq));
+void votrax_sc01_device::build_injection_filter(double *a, double *b,
+ double c1b, // Switched cap, input, bottom
+ double c2t, // Unswitched cap, over first amp-op, top
+ double c2b, // Switched cap, over first amp-op, bottom
+ double c3, // Cap between the two op-amps
+ double c4) // Cap over second op-amp
+{
+ // First compute the three coefficients of H(s) = (k0 + k2*s)/(k1 - k2*s)
+ double k0 = m_cclock * c2t;
+ double k1 = m_cclock * (c1b * c3 / c2t - c2t);
+ double k2 = c2b;
+
+ // Don't pre-warp
+ double zc = 2*m_sclock;
+
+ // Finally compute the result of the z-transform
+ double m = zc*k2;
+
+ a[0] = k0 + m;
+ a[1] = k0 - m;
+ b[0] = k1 - m;
+ b[1] = k1 + m;
+
+ // That ends up in a numerically unstable filter. Neutralize it for now.
+ a[0] = 1;
+ a[1] = 0;
+ b[0] = 1;
+ b[1] = 0;
}
diff --git a/src/devices/sound/votrax.h b/src/devices/sound/votrax.h
index adf6b429dad..2192166d351 100644
--- a/src/devices/sound/votrax.h
+++ b/src/devices/sound/votrax.h
@@ -1,34 +1,22 @@
// license:BSD-3-Clause
-// copyright-holders:Aaron Giles
+// copyright-holders:Olivier Galibert
/***************************************************************************
votrax.h
- Simple VOTRAX SC-01 simulator based on sample fragments.
+ Votrax SC01A simulation
***************************************************************************/
#pragma once
-#ifndef __VOTRAX_H__
-#define __VOTRAX_H__
-
-#include "sound/samples.h"
-
-
-//**************************************************************************
-// INTERFACE CONFIGURATION MACROS
-//**************************************************************************
+#ifndef VOTRAX_H
+#define VOTRAX_H
#define MCFG_VOTRAX_SC01_REQUEST_CB(_devcb) \
- devcb = &votrax_sc01_device::set_request_callback(*device, DEVCB_##_devcb);
+ devcb = &downcast<votrax_sc01_device *>(device)->set_ar_callback(DEVCB_##_devcb);
-
-//**************************************************************************
-// TYPE DEFINITIONS
-//**************************************************************************
-
-// ======================> votrax_sc01_device
+#include <assert.h>
class votrax_sc01_device : public device_t,
public device_sound_interface
@@ -37,12 +25,11 @@ public:
// construction/destruction
votrax_sc01_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
- template<class _Object> static devcb_base &set_request_callback(device_t &device, _Object object) { return downcast<votrax_sc01_device &>(device).m_request_cb.set_callback(object); }
+ template<class _Object> devcb_base &set_ar_callback(_Object object) { return m_ar_cb.set_callback(object); }
- // writers
- DECLARE_WRITE8_MEMBER( write );
- DECLARE_WRITE8_MEMBER( inflection_w );
- DECLARE_READ_LINE_MEMBER( request ) { m_stream->update(); return m_request_state; }
+ DECLARE_WRITE8_MEMBER(write);
+ DECLARE_WRITE8_MEMBER(inflection_w);
+ DECLARE_READ_LINE_MEMBER(request) { m_stream->update(); return m_ar_state; }
protected:
// device-level overrides
@@ -56,99 +43,146 @@ protected:
virtual void sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) override;
private:
- // internal helpers
- void update_subphoneme_clock_period();
- static double bits_to_caps(uint32_t value, int caps_count, const double *caps_values);
- static void shift_hist(double val, double *hist_array, int hist_size);
- static void filter_s_to_z(const double *k, double fs, double *a, double *b);
- static double apply_filter(const double *x, const double *y, const double *a, const double *b);
-
- // internal state
- sound_stream * m_stream; // output stream
- emu_timer * m_phoneme_timer; // phoneme timer
- const uint8_t * m_rom; // pointer to our ROM
-
- // inputs
- uint8_t m_inflection; // 2-bit inflection value
- uint8_t m_phoneme; // 6-bit phoneme value
-
- // outputs
- devcb_write_line m_request_cb; // callback for request
- uint8_t m_request_state; // request as seen to the outside world
- uint8_t m_internal_request; // request managed by stream timing
-
- // timing circuit
- uint32_t m_master_clock_freq; // frequency of the master clock
- uint8_t m_master_clock; // master clock
- uint16_t m_counter_34; // ripple counter @ 34
- uint8_t m_latch_70; // 4-bit latch @ 70
- uint8_t m_latch_72; // 4-bit latch @ 72
- uint8_t m_beta1; // beta1 clock state
- uint8_t m_p2; // P2 clock state
- uint8_t m_p1; // P1 clock state
- uint8_t m_phi2; // phi2 clock state
- uint8_t m_phi1; // phi1 clock state
- uint8_t m_phi2_20; // alternate phi2 clock state (20kHz)
- uint8_t m_phi1_20; // alternate phi1 clock state (20kHz)
- uint32_t m_subphoneme_period; // period of the subphoneme timer
- uint32_t m_subphoneme_count; // number of ticks executed already
- uint8_t m_clock_88; // subphoneme clock output @ 88
- uint8_t m_latch_42; // D flip-flop @ 42
- uint8_t m_counter_84; // 4-bit phoneme counter @ 84
- uint8_t m_latch_92; // 2-bit latch @ 92
-
- // low parameter clocking
- bool m_srff_132; // S/R flip-flop @ 132
- bool m_srff_114; // S/R flip-flop @ 114
- bool m_srff_112; // S/R flip-flop @ 112
- bool m_srff_142; // S/R flip-flop @ 142
- uint8_t m_latch_80; // phoneme timing latch @ 80
-
- // glottal circuit
- uint8_t m_counter_220; // 4-bit counter @ 220
- uint8_t m_counter_222; // 4-bit counter @ 222
- uint8_t m_counter_224; // 4-bit counter @ 224
- uint8_t m_counter_234; // 4-bit counter @ 234
- uint8_t m_counter_236; // 4-bit counter @ 236
- uint8_t m_fgate; // FGATE signal
- uint8_t m_glottal_sync; // Glottal Sync signal
-
- // transition circuit
- uint8_t m_0625_clock; // state of 0.625kHz clock
- uint8_t m_counter_46; // 4-bit counter in block @ 46
- uint8_t m_latch_46; // 4-bit latch in block @ 46
- uint8_t m_ram[8]; // RAM to hold parameters
- uint8_t m_latch_168; // 4-bit latch @ 168
- uint8_t m_latch_170; // 4-bit latch @ 170
- uint8_t m_f1; // latched 4-bit F1 value
- uint8_t m_f2; // latched 5-bit F2 value
- uint8_t m_fc; // latched 4-bit FC value
- uint8_t m_f3; // latched 4-bit F3 value
- uint8_t m_f2q; // latched 4-bit F2Q value
- uint8_t m_va; // latched 4-bit VA value
- uint8_t m_fa; // latched 4-bit FA value
-
- // noise generator circuit
- uint8_t m_noise_clock; // clock input to noise generator
- uint32_t m_shift_252; // shift register @ 252
- uint8_t m_counter_250; // 4-bit counter @ 250
-
- // stages outputs history
- double m_ni_hist[4];
- double m_no_hist[4];
- double m_va_hist[4];
- double m_s1_hist[4];
- double m_s2g_hist[4];
- double m_s2ni_hist[4];
- double m_s2n_hist[4];
- double m_s2_hist[4];
- double m_s3_hist[4];
- double m_s4i_hist[4];
- double m_s4_hist[4];
-
- // static tables
- static const char *const s_phoneme_table[64];
- static const double s_glottal_wave[16];
+ // Possible timer parameters
+ enum {
+ T_COMMIT_PHONE,
+ T_END_OF_PHONE
+ };
+
+ static const char *const s_phone_table[64];
+ static const double s_glottal_wave[9];
+
+ sound_stream *m_stream; // Output stream
+ emu_timer *m_timer; // General timer
+ required_memory_region m_rom; // Internal ROM
+ u32 m_mainclock; // Current main clock
+ double m_sclock; // Stream sample clock (40KHz, main/18)
+ double m_cclock; // 20KHz capacitor switching clock (main/36)
+ u32 m_sample_count; // Sample counter, to cadence chip updates
+
+ // Inputs
+ u8 m_inflection; // 2-bit inflection value
+ u8 m_phone; // 6-bit phone value
+
+ // Outputs
+ devcb_write_line m_ar_cb; // Callback for ar
+ bool m_ar_state; // Current ar state
+
+ // "Unpacked" current rom values
+ u8 m_rom_duration; // Duration in 5KHz units (main/144) of one tick, 16 ticks per phone, 7 bits
+ u8 m_rom_vd, m_rom_cld; // Duration in ticks of the "voice" and "closure" delays, 4 bits
+ u8 m_rom_fa, m_rom_fc, m_rom_va; // Analog parameters, noise volume, noise freq cutoff and voice volume, 4 bits each
+ u8 m_rom_f1, m_rom_f2, m_rom_f2q, m_rom_f3; // Analog parameters, formant frequencies and Q, 4 bits each
+ bool m_rom_closure; // Closure bit, true = silence at cld
+ bool m_rom_pause; // Pause bit
+
+ // Current interpolated values (8 bits each)
+ u8 m_cur_fa, m_cur_fc, m_cur_va;
+ u8 m_cur_f1, m_cur_f2, m_cur_f2q, m_cur_f3;
+
+ // Current committed values
+ u8 m_filt_fa, m_filt_fc, m_filt_va; // Analog parameters, noise volume, noise freq cutoff and voice volume, 4 bits each
+ u8 m_filt_f1, m_filt_f2, m_filt_f2q, m_filt_f3; // Analog parameters, formant frequencies/Q on 4 bits except f2 on 5 bits
+
+ // Internal counters
+ u16 m_phonetick; // 9-bits phone tick duration counter
+ u8 m_ticks; // 5-bits tick counter
+ u8 m_pitch; // 7-bits pitch counter
+ u8 m_closure; // 5-bits glottal closure counter
+ u8 m_update_counter; // 6-bits counter for the 625Hz (main/1152) and 208Hz (main/3456) update timing generators
+
+ // Internal state
+ bool m_cur_closure; // Current internal closure state
+ u16 m_noise; // 15-bit noise shift register
+ bool m_cur_noise; // Current noise output
+
+ // Filter coefficients and level histories
+ double m_voice_1[4];
+ double m_voice_2[4];
+ double m_voice_3[4];
+
+ double m_noise_1[3];
+ double m_noise_2[3];
+ double m_noise_3[2];
+ double m_noise_4[2];
+
+ double m_vn_1[4];
+ double m_vn_2[4];
+ double m_vn_3[4];
+ double m_vn_4[4];
+ double m_vn_5[2];
+ double m_vn_6[2];
+
+ double m_f1_a[4], m_f1_b[4]; // F1 filtering
+ double m_f2v_a[4], m_f2v_b[4]; // F2 voice filtering
+ double m_f2n_a[2], m_f2n_b[2]; // F2 noise filtering
+ double m_f3_a[4], m_f3_b[4]; // F3 filtering
+ double m_f4_a[4], m_f4_b[4]; // F4 filtering
+ double m_fx_a[1], m_fx_b[2]; // Final filtering
+ double m_fn_a[3], m_fn_b[3]; // Noise shaping
+
+ // Compute a total capacitor value based on which bits are currently active
+ static double bits_to_caps(u32 value, std::initializer_list<double> caps_values) {
+ double total = 0;
+ for(double d : caps_values) {
+ if(value & 1)
+ total += d;
+ value >>= 1;
+ }
+ return total;
+ }
+
+ // Shift a history of values by one and insert the new value at the front
+ template<u32 N> static void shift_hist(double val, double (&hist_array)[N]) {
+ for(u32 i=N-1; i>0; i--)
+ hist_array[i] = hist_array[i-1];
+ hist_array[0] = val;
+ }
+
+ // Apply a filter and compute the result. 'a' is applied to x (inputs) and 'b' to y (outputs)
+ template<u32 Nx, u32 Ny, u32 Na, u32 Nb> static double apply_filter(const double (&x)[Nx], const double (&y)[Ny], const double (&a)[Na], const double (&b)[Nb]) {
+ static_assert(Nx >= Na);
+ static_assert(Ny >= Nb);
+ double total = 0;
+ for(u32 i=0; i<Na; i++)
+ total += x[i] * a[i];
+ for(u32 i=1; i<Nb; i++)
+ total -= y[i-1] * b[i];
+ return total / b[0];
+ }
+
+ void build_standard_filter(double *a, double *b,
+ double c1t, // Unswitched cap, input, top
+ double c1b, // Switched cap, input, bottom
+ double c2t, // Unswitched cap, over first amp-op, top
+ double c2b, // Switched cap, over first amp-op, bottom
+ double c3, // Cap between the two op-amps
+ double c4); // Cap over second op-amp
+
+ void build_noise_shaper_filter(double *a, double *b,
+ double c1, // Cap over first amp-op
+ double c2t, // Unswitched cap between amp-ops, input, top
+ double c2b, // Switched cap between amp-ops, input, bottom
+ double c3, // Cap over second amp-op
+ double c4); // Switched cap after second amp-op
+
+ void build_lowpass_filter(double *a, double *b,
+ double c1t, // Unswitched cap, over amp-op, top
+ double c1b); // Switched cap, over amp-op, bottom
+
+ void build_injection_filter(double *a, double *b,
+ double c1b, // Switched cap, input, bottom
+ double c2t, // Unswitched cap, over first amp-op, top
+ double c2b, // Switched cap, over first amp-op, bottom
+ double c3, // Cap between the two op-amps
+ double c4); // Cap over second op-amp
+
+
+ static void interpolate(u8 &reg, u8 target); // Do one interpolation step
+ void chip_update(); // Global update called at 20KHz (main/36)
+ void filters_commit(bool force); // Commit the currently computed interpolation values to the filters
+ void phone_commit(); // Commit the current phone id
+ stream_sample_t analog_calc(); // Compute one more sample
};
@@ -160,5 +194,4 @@ private:
// device type definition
extern const device_type VOTRAX_SC01;
-
-#endif /* __VOTRAX_H__ */
+#endif /* VOTRAX_H */
diff --git a/src/mame/audio/gorf.cpp b/src/mame/audio/gorf.cpp
deleted file mode 100644
index 21f7272925c..00000000000
--- a/src/mame/audio/gorf.cpp
+++ /dev/null
@@ -1,190 +0,0 @@
-// license:BSD-3-Clause
-// copyright-holders:Mike Coates
-/**************************************************************************
-
- Gorf/Votrax SC-01 Emulator
-
- Mike@Dissfulfils.co.uk
-
- Modified to match phonemes to words
-
- Ajudd@quantime.co.uk
-
-**************************************************************************
-
-gorf_sh_w - Write data to votrax port
-gorf_sh_status - Return busy status (-1 = busy)
-gorf_port_2_r - Returns status of voice port
-gorf_sh_ update- Null
-
-**************************************************************************/
-
-#include "emu.h"
-#include "cpu/z80/z80.h"
-#include "includes/astrocde.h"
-#include "audio/gorf.h"
-
-
-/****************************************************************************
- * 64 Phonemes - currently 1 sample per phoneme, will be combined sometime!
- ****************************************************************************/
-
-static const char *const PhonemeTable[65] =
-{
- "EH3","EH2","EH1","PA0","DT" ,"A1" ,"A2" ,"ZH",
- "AH2","I3" ,"I2" ,"I1" ,"M" ,"N" ,"B" ,"V",
- "CH" ,"SH" ,"Z" ,"AW1","NG" ,"AH1","OO1","OO",
- "L" ,"K" ,"J" ,"H" ,"G" ,"F" ,"D" ,"S",
- "A" ,"AY" ,"Y1" ,"UH3","AH" ,"P" ,"O" ,"I",
- "U" ,"Y" ,"T" ,"R" ,"E" ,"W" ,"AE" ,"AE1",
- "AW2","UH2","UH1","UH" ,"O2" ,"O1" ,"IU" ,"U1",
- "THV","TH" ,"ER" ,"EH" ,"E1" ,"AW" ,"PA1","STOP",
- nullptr
-};
-
-static const char *const GorfWordTable[] =
-{
- "A2AYY1","A2E1","UH1GEH1I3N","AE1EH2M","AEM",
- "AE1EH3ND","UH1NAH2I1YLA2SHUH2N","AH2NUHTHER","AH1NUHTHVRR",
- "AH1R","UHR","UH1VEH1EH3NNDJER","BAEEH3D","BAEEH1D","BE",
- "BEH3EH1N","buht","BUH1DTTEH2NN","KUHDEH2T",
- "KAE1NUH1T","KAE1EH3PTI3N",
- "KRAH2UH3NI3KUH3O2LZ","KO1UH3I3E1N","KO1UH3I3E1NS",
- "KERNAH2L","KAH1NCHEHSNEHS","DE1FEH1NDER",
- "DE1STRO1I1Y","DE1STRO1I1Y1D",
- "DU1UM","DRAW1S","EHMPAH2I3YR","EHND",
- "EH1NEH1MY","EH1SKA1E1P","FLEHGSHIP",
- "FOR","GUH1LAEKTI1K",
- "DJEH2NERUH3L","GDTO1O1RRFF","GDTO1RFYA2N","GDTO1RFE1EH2N","GDTO1RFYA2NS",
- "HAH1HAH1HAH1HAH1","hahaher.wav","HUHRDER",
- "HAE1EH3V","HI1TI1NG","AH1I1Y", "AH1I1Y1","I1MPAH1SI1BL",
- "IN*","INSERT","I1S","LI1V","LAWNG","MEE1T","MUU1V",
- "MAH2I1Y","MAH2I3Y","NIR","NEHKST","NUH3AH2YS","NO",
- "NAH1O1U1W","PA1","PLA1AYER","PRE1PAE1ER","PRI1SI3NEH3RS",
- "PRUH2MOTEH3D","POO1IUSH","RO1U1BAH1T","RO1U1BAH1TS",
- "RO1U1BAH1UH3TS","SEK", "SHIP","SHAH1UH3T","SUHM","SPA2I3YS","PA0",
- "SERVAH2I1Y1VUH3L","TAK","THVUH","THVUH1",
- "THUH","TAH1EH3YM","TU","TIUU1",
- "UH2NBE1AYTUH3BUH3L",
- "WORAYY1EH3R","WORAYY1EH3RS","WI1L",
- "Y1I3U1","YIUU1U1","YI1U1U1","Y1IUU1U1","Y1I1U1U1","YOR","YU1O1RSEH1LF","s.wav",
- "FO1R","FO2R","WIL","GDTO1RVYA2N",
-
- "KO1UH3I3AYNN",
- "UH1TAEEH3K","BAH2I3Y1T","KAH1NKER","DYVAH1U1ER","DUHST","GAE1LUH1KSY","GAH1EH3T",
- "PAH1I1R","TRAH2I1Y","SU1PRE1N","AWL","HA2AYL",
- "EH1MPAH1I1R",
-nullptr
-};
-
-#define num_samples (sizeof(GorfWordTable)/sizeof(char *))
-
-const char *const gorf_sample_names[] =
-{
- "*gorf","a","a","again","am","am","and","anhilatn",
- "another","another","are","are",
- "avenger","bad","bad","be",
- "been","but","button","cadet",
- "cannot","captain","chronicl","coin","coins","colonel",
- "consciou","defender","destroy","destroyd",
- "doom","draws","empire","end",
- "enemy","escape","flagship","for","galactic",
- "general","gorf","gorphian","gorphian","gorphins",
- "hahahahu","hahaher","harder","have",
- "hitting","i","i","impossib","in","insert",
- "is","live","long","meet","move",
- "my","my",
- "near","next","nice","no",
- "now","pause","player","prepare","prisonrs",
- "promoted","push","robot","robots","robots",
- "seek","ship","shot","some","space","spause",
- "survival","take","the","the","the","time",
- "to","to","unbeatab",
- "warrior","warriors","will",
- "you","you","you","you","your","your","yourself",
- "s","for","for","will","gorph",
-// Missing Samples
- "coin", "attack","bite","conquer","devour","dust",
- "galaxy","got","power","try","supreme","all",
- "hail","emperor",
- nullptr
-};
-
-
-READ8_MEMBER( astrocde_state::gorf_speech_r )
-{
- uint8_t data = offset >> 8;
-#if USE_FAKE_VOTRAX
- int Phoneme, Intonation;
- int i = 0;
- offset &= 0xff;
-
- m_totalword_ptr = m_totalword;
-
- Phoneme = data & 0x3F;
- Intonation = data >> 6;
-
- logerror("Date : %d Speech : %s at intonation %d\n",Phoneme, PhonemeTable[Phoneme],Intonation);
-
- if(Phoneme==63) {
- m_samples->stop(0);
- if (strlen(m_totalword)>2) logerror("Clearing sample %s\n",m_totalword);
- m_totalword[0] = 0; /* Clear the total word stack */
- return data;
- }
-
- /* Phoneme to word translation */
-
- if (*(m_totalword) == 0) {
- strcpy(m_totalword,PhonemeTable[Phoneme]); /* Copy over the first phoneme */
- if (m_plural != 0) {
- logerror("found a possible plural at %d\n",m_plural-1);
- if (!strcmp("S",m_totalword)) { /* Plural check */
- m_samples->start(0, num_samples-2); /* play the sample at position of word */
- m_samples->set_frequency(0, 11025); /* play at correct rate */
- m_totalword[0] = 0; /* Clear the total word stack */
- m_oldword[0] = 0; /* Clear the total word stack */
- return data;
- } else {
- m_plural=0;
- }
- }
- } else
- strcat(m_totalword,PhonemeTable[Phoneme]); /* Copy over the first phoneme */
-
- logerror("Total word = %s\n",m_totalword);
-
- for (i=0; GorfWordTable[i]; i++) {
- if (!strcmp(GorfWordTable[i],m_totalword)) { /* Scan the word (sample) table for the complete word */
- if ((!strcmp("GDTO1RFYA2N",m_totalword)) || (!strcmp("RO1U1BAH1T",m_totalword)) || (!strcmp("KO1UH3I3E1N",m_totalword)) || (!strcmp("WORAYY1EH3R",m_totalword)) || (!strcmp("IN",m_totalword)) ) { /* May be m_plural */
- m_plural=i+1;
- strcpy(m_oldword,m_totalword);
- logerror("Storing sample position %d and copying string %s\n",m_plural,m_oldword);
- } else {
- m_plural=0;
- }
- m_samples->start(0, i); /* play the sample at position of word */
- m_samples->set_frequency(0, 11025); /* play at correct rate */
- logerror("Playing sample %d",i);
- m_totalword[0] = 0; /* Clear the total word stack */
- return data;
- }
- }
-#else
- m_votrax->inflection_w(space, 0, data >> 6);
- m_votrax->write(space, 0, data);
-#endif
-
- /* Note : We should really also use volume in this as well as frequency */
- return data; /* Return nicely */
-}
-
-
-CUSTOM_INPUT_MEMBER( astrocde_state::gorf_speech_status_r )
-{
-#if USE_FAKE_VOTRAX
- return !m_samples->playing(0);
-#else
- return m_votrax->request();
-#endif
-}
diff --git a/src/mame/audio/gorf.h b/src/mame/audio/gorf.h
deleted file mode 100644
index 0d14337f9cf..00000000000
--- a/src/mame/audio/gorf.h
+++ /dev/null
@@ -1,4 +0,0 @@
-// license:BSD-3-Clause
-// copyright-holders:Mike Coates
-
-extern const char *const gorf_sample_names[];
diff --git a/src/mame/audio/gottlieb.cpp b/src/mame/audio/gottlieb.cpp
index 5a7924a491b..75308e7902b 100644
--- a/src/mame/audio/gottlieb.cpp
+++ b/src/mame/audio/gottlieb.cpp
@@ -30,225 +30,6 @@ extern const device_type GOTTLIEB_SOUND_REV1_WITH_VOTRAX = &device_creator<gottl
extern const device_type GOTTLIEB_SOUND_REV2 = &device_creator<gottlieb_sound_r2_device>;
-
-//**************************************************************************
-// OLD CRAPPY SAMPLE PLAYER
-//**************************************************************************
-
-#if USE_FAKE_VOTRAX
-
-void gottlieb_sound_r1_device::trigger_sample(uint8_t data)
-{
- /* Reactor samples */
- if (strcmp(machine().system().name, "reactor") == 0)
- {
- switch (data)
- {
- case 55:
- case 56:
- case 57:
- case 59:
- m_samples->start(0, data - 53);
- break;
-
- case 31:
- m_score_sample = 7;
- break;
-
- case 39:
- m_score_sample++;
- if (m_score_sample < 20)
- m_samples->start(0, m_score_sample);
- break;
- }
- }
-
- /* Q*Bert samples */
- else
- {
- switch (data)
- {
- case 17:
- case 18:
- case 19:
- case 20:
- case 21:
- m_samples->start(0, (data - 17) * 8 + m_random_offset);
- m_random_offset = (m_random_offset + 1) & 7;
- break;
-
- case 22:
- m_samples->start(0,40);
- break;
-
- case 23:
- m_samples->start(0,41);
- break;
- }
- }
-}
-
-void gottlieb_sound_r1_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
-{
- m_audiocpu->set_input_line(INPUT_LINE_NMI, PULSE_LINE);
-}
-
-void gottlieb_sound_r1_device::fake_votrax_data_w(uint8_t data)
-{
- static const char *const PhonemeTable[0x40] =
- {
- "EH3", "EH2", "EH1", "PA0", "DT" , "A1" , "A2" , "ZH",
- "AH2", "I3" , "I2" , "I1" , "M" , "N" , "B" , "V",
- "CH" , "SH" , "Z" , "AW1", "NG" , "AH1", "OO1", "OO",
- "L" , "K" , "J" , "H" , "G" , "F" , "D" , "S",
- "A" , "AY" , "Y1" , "UH3", "AH" , "P" , "O" , "I",
- "U" , "Y" , "T" , "R" , "E" , "W" , "AE" , "AE1",
- "AW2", "UH2", "UH1", "UH" , "O2" , "O1" , "IU" , "U1",
- "THV", "TH" , "ER" , "EH" , "E1" , "AW" , "PA1", "STOP"
- };
-
- data ^= 0xff;
-
-logerror("Votrax: intonation %d, phoneme %02x %s\n",data >> 6,data & 0x3f,PhonemeTable[data & 0x3f]);
-
- m_votrax_queue[m_votrax_queuepos++] = data;
-
- if ((data & 0x3f) == 0x3f)
- {
- if (m_votrax_queuepos > 1)
- {
- int last = -1;
- int i;
- char phonemes[200];
-
- phonemes[0] = 0;
- for (i = 0;i < m_votrax_queuepos-1;i++)
- {
- static const char *const inf[4] = { "[0]", "[1]", "[2]", "[3]" };
- int phoneme = m_votrax_queue[i] & 0x3f;
- int inflection = m_votrax_queue[i] >> 6;
- if (inflection != last) strcat(phonemes, inf[inflection]);
- last = inflection;
- if (phoneme == 0x03 || phoneme == 0x3e) strcat(phonemes," ");
- else strcat(phonemes,PhonemeTable[phoneme]);
- }
-
- osd_printf_debug("Votrax played '%s'\n", phonemes);
-
- if (strcmp(phonemes, "[0] HEH3LOOW AH1EH3I3YMTERI2NDAHN") == 0) /* Q-Bert & Tylz - Hello, I am turned on */
- m_samples->start(0, 42);
- else if (strcmp(phonemes, "[0]BAH1EH1Y") == 0) /* Q-Bert - Bye, bye */
- m_samples->start(0, 43);
- else if (strcmp(phonemes, "[0]A2YHT LEH2FTTH") == 0) /* Reactor - Eight left */
- m_samples->start(0, 0);
- else if (strcmp(phonemes, "[0]SI3KS DTYN LEH2FTTH") == 0) /* Reactor - Sixteen left */
- m_samples->start(0, 1);
- else if (strcmp(phonemes, "[0]WO2RNYNG KO2R UH1NSDTABUH1L") == 0) /* Reactor - Warning core unstable */
- m_samples->start(0, 5);
- else if (strcmp(phonemes, "[0]CHAMBERR AE1EH2KTI1VA1I3DTEH1DT ") == 0) /* Reactor - Chamber activated */
- m_samples->start(0, 7);
- }
-
- m_votrax_queuepos = 0;
- }
-
- /* generate a NMI after a while to make the CPU continue to send data */
- timer_set(attotime::from_usec(50));
-}
-
-static const char *const reactor_sample_names[] =
-{
- "*reactor",
- "fx_53", /* "8 left" */
- "fx_54", /* "16 left" */
- "fx_55", /* "24 left" */
- "fx_56", /* "32 left" */
- "fx_57", /* "40 left" */
- "fx_58", /* "warning, core unstable" */
- "fx_59", /* "bonus" */
- "fx_31", /* "chamber activated" */
- "fx_39a", /* "2000" */
- "fx_39b", /* "5000" */
- "fx_39c", /* "10000" */
- "fx_39d", /* "15000" */
- "fx_39e", /* "20000" */
- "fx_39f", /* "25000" */
- "fx_39g", /* "30000" */
- "fx_39h", /* "35000" */
- "fx_39i", /* "40000" */
- "fx_39j", /* "45000" */
- "fx_39k", /* "50000" */
- "fx_39l", /* "55000" */
- nullptr /* end of array */
-};
-
-static const char *const qbert_sample_names[] =
-{
- "*qbert",
- "fx_17a", /* random speech, voice clock 255 */
- "fx_17b", /* random speech, voice clock 255 */
- "fx_17c", /* random speech, voice clock 255 */
- "fx_17d", /* random speech, voice clock 255 */
- "fx_17e", /* random speech, voice clock 255 */
- "fx_17f", /* random speech, voice clock 255 */
- "fx_17g", /* random speech, voice clock 255 */
- "fx_17h", /* random speech, voice clock 255 */
- "fx_18a", /* random speech, voice clock 176 */
- "fx_18b", /* random speech, voice clock 176 */
- "fx_18c", /* random speech, voice clock 176 */
- "fx_18d", /* random speech, voice clock 176 */
- "fx_18e", /* random speech, voice clock 176 */
- "fx_18f", /* random speech, voice clock 176 */
- "fx_18g", /* random speech, voice clock 176 */
- "fx_18h", /* random speech, voice clock 176 */
- "fx_19a", /* random speech, voice clock 128 */
- "fx_19b", /* random speech, voice clock 128 */
- "fx_19c", /* random speech, voice clock 128 */
- "fx_19d", /* random speech, voice clock 128 */
- "fx_19e", /* random speech, voice clock 128 */
- "fx_19f", /* random speech, voice clock 128 */
- "fx_19g", /* random speech, voice clock 128 */
- "fx_19h", /* random speech, voice clock 128 */
- "fx_20a", /* random speech, voice clock 96 */
- "fx_20b", /* random speech, voice clock 96 */
- "fx_20c", /* random speech, voice clock 96 */
- "fx_20d", /* random speech, voice clock 96 */
- "fx_20e", /* random speech, voice clock 96 */
- "fx_20f", /* random speech, voice clock 96 */
- "fx_20g", /* random speech, voice clock 96 */
- "fx_20h", /* random speech, voice clock 96 */
- "fx_21a", /* random speech, voice clock 62 */
- "fx_21b", /* random speech, voice clock 62 */
- "fx_21c", /* random speech, voice clock 62 */
- "fx_21d", /* random speech, voice clock 62 */
- "fx_21e", /* random speech, voice clock 62 */
- "fx_21f", /* random speech, voice clock 62 */
- "fx_21g", /* random speech, voice clock 62 */
- "fx_21h", /* random speech, voice clock 62 */
- "fx_22", /* EH2 with decreasing voice clock */
- "fx_23", /* O1 with varying voice clock */
- "fx_28",
- "fx_36",
- nullptr /* end of array */
-};
-
-MACHINE_CONFIG_FRAGMENT( reactor_samples )
- MCFG_SOUND_ADD("samples", SAMPLES, 0)
- MCFG_SAMPLES_CHANNELS(1)
- MCFG_SAMPLES_NAMES(reactor_sample_names)
- MCFG_SOUND_ROUTE(ALL_OUTPUTS, "speaker", 1.0)
-MACHINE_CONFIG_END
-
-MACHINE_CONFIG_FRAGMENT( qbert_samples )
- MCFG_SOUND_ADD("samples", SAMPLES, 0)
- MCFG_SAMPLES_CHANNELS(1)
- MCFG_SAMPLES_NAMES(qbert_sample_names)
- MCFG_SOUND_ROUTE(ALL_OUTPUTS, "speaker", 1.0)
-MACHINE_CONFIG_END
-
-#endif
-
-
//**************************************************************************
// REV 0 SOUND BOARD: 6502 + 6530 + DAC
//**************************************************************************
@@ -390,12 +171,6 @@ gottlieb_sound_r1_device::gottlieb_sound_r1_device(const machine_config &mconfig
m_votrax(*this, "votrax"),
//m_populate_votrax(false),
m_last_speech_clock(0)
-#if USE_FAKE_VOTRAX
- , m_samples(*this, ":samples"),
- m_score_sample(0),
- m_random_offset(0),
- m_votrax_queuepos(0)
-#endif
{
}
@@ -407,12 +182,6 @@ gottlieb_sound_r1_device::gottlieb_sound_r1_device(const machine_config &mconfig
m_votrax(*this, "votrax"),
//m_populate_votrax(populate_votrax),
m_last_speech_clock(0)
-#if USE_FAKE_VOTRAX
- , m_samples(*this, ":samples"),
- m_score_sample(0),
- m_random_offset(0),
- m_votrax_queuepos(0)
-#endif
{
}
@@ -427,11 +196,6 @@ WRITE8_MEMBER( gottlieb_sound_r1_device::write )
uint8_t pa7 = (data & 0x0f) != 0xf;
uint8_t pa0_5 = ~data & 0x3f;
m_riot->porta_in_set(pa0_5 | (pa7 << 7), 0xbf);
-
-#if USE_FAKE_VOTRAX
- if (pa7 && m_samples != nullptr)
- trigger_sample(pa0_5);
-#endif
}
@@ -469,10 +233,6 @@ WRITE8_MEMBER( gottlieb_sound_r1_device::votrax_data_w )
m_votrax->inflection_w(space, offset, data >> 6);
m_votrax->write(space, offset, ~data & 0x3f);
}
-
-#if USE_FAKE_VOTRAX
- fake_votrax_data_w(data);
-#endif
}
diff --git a/src/mame/audio/gottlieb.h b/src/mame/audio/gottlieb.h
index 77753f57e8d..b32b70c4c69 100644
--- a/src/mame/audio/gottlieb.h
+++ b/src/mame/audio/gottlieb.h
@@ -15,11 +15,6 @@
#include "sound/votrax.h"
-// set to 0 to enable Votrax device and disable samples
-#define USE_FAKE_VOTRAX (1)
-
-
-
//**************************************************************************
// GLOBAL VARIABLES
//**************************************************************************
@@ -108,19 +103,6 @@ private:
// internal state
//bool m_populate_votrax;
uint8_t m_last_speech_clock;
-
-#if USE_FAKE_VOTRAX
-protected:
- virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override;
-private:
- void fake_votrax_data_w(uint8_t data);
- void trigger_sample(uint8_t data);
- optional_device<samples_device> m_samples;
- uint8_t m_score_sample;
- uint8_t m_random_offset;
- uint8_t m_votrax_queue[100];
- uint8_t m_votrax_queuepos;
-#endif
};
// fully populated rev 1 sound board
@@ -203,10 +185,3 @@ private:
uint8_t m_sp0250_latch;
};
-
-/*----------- defined in audio/gottlieb.c -----------*/
-
-#if USE_FAKE_VOTRAX
-MACHINE_CONFIG_EXTERN( reactor_samples );
-MACHINE_CONFIG_EXTERN( qbert_samples );
-#endif
diff --git a/src/mame/audio/wow.cpp b/src/mame/audio/wow.cpp
deleted file mode 100644
index e09cd7b57de..00000000000
--- a/src/mame/audio/wow.cpp
+++ /dev/null
@@ -1,183 +0,0 @@
-// license:BSD-3-Clause
-// copyright-holders:Mike Coates
-/**************************************************************************
-
- WOW/Votrax SC-01 Emulator
-
- Mike@Dissfulfils.co.uk
-
- Modified to match phonemes to words
-
- Ajudd@quantime.co.uk
-
-**************************************************************************
-
-wow_sh_start - Start emulation, load samples from Votrax subdirectory
-wow_sh_w - Write data to votrax port
-wow_sh_status - Return busy status (-1 = busy)
-wow_port_2_r - Returns status of voice port
-wow_sh_ update- Null
-
-**************************************************************************/
-
-#include "emu.h"
-#include "cpu/z80/z80.h"
-#include "includes/astrocde.h"
-#include "audio/wow.h"
-
-/****************************************************************************
- * 64 Phonemes - currently 1 sample per phoneme, will be combined sometime!
- ****************************************************************************/
-
-static const char *const PhonemeTable[65] =
-{
- "EH3","EH2","EH1","PA0","DT" ,"A1" ,"A2" ,"ZH",
- "AH2","I3" ,"I2" ,"I1" ,"M" ,"N" ,"B" ,"V",
- "CH" ,"SH" ,"Z" ,"AW1","NG" ,"AH1","OO1","OO",
- "L" ,"K" ,"J" ,"H" ,"G" ,"F" ,"D" ,"S",
- "A" ,"AY" ,"Y1" ,"UH3","AH" ,"P" ,"O" ,"I",
- "U" ,"Y" ,"T" ,"R" ,"E" ,"W" ,"AE" ,"AE1",
- "AW2","UH2","UH1","UH" ,"O2" ,"O1" ,"IU" ,"U1",
- "THV","TH" ,"ER" ,"EH" ,"E1" ,"AW" ,"PA1","STOP",
- nullptr
-};
-
-/* Missing samples : ready, from, one, bite, youl, explode, if, myself, back,
- cant, do, wait, worlings, very, babies, breath, fire, beat, rest,
- then, never, worlock, escape, door, try, any, harder, only, meet, with,
- doom, pop,
- Problems with YOU and YOU'LL and YOU'DD */
-
-static const char *const wowWordTable[] =
-{
-"AH1I3Y1", "UH1GA1EH1N", "AHAH2", "AE1EH3M", "AE1EH3ND",
-"anew.wav", "AH1NUHTHER", "AE1NY", "anyone.wav", "appear.wav", "AH1UH3R", "UHR", "BABYY1S", "BAE1EH3K",
-"BE1T", "become.wav", "BEHST", "BEH1TER", "BUH3AH2YT", "bones.wav", "BRE1YTH", "but.wav", "can.wav", "KAE1EH3NT",
-"chance.wav", "CHEHST", "KO1O2I3Y1N", "dance.wav", "DE1STRO1UH3I3AY",
-"DE1VEH1LUH3PT", "DIUU", "DONT", "DUUM", "DOO1R", "draw.wav", "DUHNJEH1N", "DUHNJEH1NZ",
-"each.wav", "eaten.wav", "EHSPA0KA2I3Y1P", "EHKPA0SPLOU1D", "fear.wav", "FAH1I3YND", "FAH1I3Y1ND", "FAH1EH3AYR", "FOR", "FRUHMM",
-"garwor.wav", "GEHT", "GEH1T", "GEHEH3T", "GEHTING", "good.wav", "HAH1HAH1HAH1HAH1", "HAH1RDER",
-"hasnt.wav", "have.wav", "HEH1I3VE1WA1I3Y1TS", "HAI1Y1", "HOP",
-"HUHNGRY", "HUHNGGRY", "HERRY", "AH1EH3I3Y", "AH1UH3I3Y", "IF", "I1F", "AH1I3YM", "AH1EH3I3YL", "AH1I3Y1L", "IN1",
-"INSERT", "invisibl.wav", "IT", "lie.wav", "MAE1EH3DJI1KUH1L",
-"MAE1EH3DJI1KUH1L", "MEE1", "MEE1T", "months.wav",
-"MAH1EH3I3Y", "MAH2AH2EH3I3Y", "MAH1I1Y", "MAH1I3Y1", "MAH1I3Y", "MAH1I3YSEHLF", "near.wav", "NEH1VER",
-"NAH1UH3U1", "UHV", "AWF", "WUHN", "O1NLY", "UHVEHN", "PA1", "PEHTS", "PAH1WERFUH1L", "PAH1P",
-"radar.wav", "REHDY",
-"REHST", "say.wav", "SAH1I3AYEHNS", "SE1Y", "PA0", "start.wav", "THVAYAY", "THVUH", "THVUH1", "THUH1", "THVEH1N",
-"THVRU", "thurwor.wav", "time.wav", "TU1", "TUU1", "TIUU1", "TREH1ZHERT", "TRAH1EH3I3Y", "VEHEH3RY", "WA2AYYT",
-"WOO1R", "WORYER", "watch.wav", "WE1Y", "WEHLKUHM",
-"WERR", "WAH1EH3I3L", "WIL", "WITH", "WIZERD", "wont.wav",
-"WO1O2R", "WO1ERLD", "WORLINGS", "WORLUHK",
-"YI3U", "Y1IUU", "YIUUI", "Y1IUU1U1", "YI3U1", "Y1IUUL", "YIUU1L", "Y1IUUD", "YO2O2R",nullptr
-};
-
-#define num_samples (sizeof(wowWordTable)/sizeof(char *))
-
-
-const char *const wow_sample_names[] =
-{
- "*wow",
- "a", "again", "ahh", "am", "and",
- "anew", "another", "any", "anyone", "appear", "are", "are", "babies", "back",
- "beat", "become", "best", "better", "bite", "bones", "breath", "but", "can", "cant",
- "chance", "chest", "coin", "dance", "destroy",
- "develop", "do", "dont", "doom", "door", "draw", "dungeon", "dungeons",
- "each", "eaten", "escape", "explode", "fear", "find", "find", "fire", "for", "from",
- "garwor", "get", "get", "get", "getting", "good", "hahahaha", "harder",
- "hasnt", "have", "heavyw", "hey", "hope",
- "hungry", "hungry", "hurry", "i", "i", "if", "if", "im", "i1", "ill", "in",
- "insert", "invisibl", "it", "lie", "magic",
- "magical", "me", "meet", "months",
- "my", "my", "my", "my", "my", "myself", "near", "never",
- "now", "of", "off", "one", "only", "oven", "pause", "pets", "powerful", "pop",
- "radar", "ready",
- "rest", "say", "science", "see", "spause", "start", "the", "the", "the", "the", "then",
- "through", "thurwor", "time", "to", "to", "to", "treasure", "try", "very", "wait",
- "war", "warrior", "watch", "we", "welcome",
- "were", "while", "will", "with", "wizard", "wont",
- "wor", "world", "worlings", "worlock",
- "you", "you", "you", "you", "you", "youl", "youl", "youd", "your",nullptr
-};
-
-
-READ8_MEMBER( astrocde_state::wow_speech_r )
-{
- uint8_t data = offset >> 8;
-#if USE_FAKE_VOTRAX
- int Phoneme/*, Intonation*/;
- int i = 0;
- offset &= 0xff;
-
- m_totalword_ptr = m_totalword;
-
- Phoneme = data & 0x3F;
- //Intonation = data >> 6;
-
- //logerror("Data : %d Speech : %s at intonation %d\n",Phoneme, PhonemeTable[Phoneme],Intonation);
-
- if(Phoneme==63) {
- m_samples->stop(0);
- //logerror("Clearing sample %s\n",m_totalword);
- m_totalword[0] = 0; /* Clear the total word stack */
- return data;
- }
- if (Phoneme==3) /* We know PA0 is never part of a word */
- m_totalword[0] = 0; /* Clear the total word stack */
-
- /* Phoneme to word translation */
-
- if (*(m_totalword) == 0) {
- strcpy(m_totalword,PhonemeTable[Phoneme]); /* Copy over the first phoneme */
- if (m_plural != 0) {
- //logerror("found a possible plural at %d\n",m_plural-1);
- if (!strcmp("S",m_totalword)) { /* Plural check */
- m_samples->start(0, num_samples-2); /* play the sample at position of word */
- m_samples->set_frequency(0, 11025); /* play at correct rate */
- m_totalword[0] = 0; /* Clear the total word stack */
- m_oldword[0] = 0; /* Clear the total word stack */
- return data;
- } else {
- m_plural=0;
- }
- }
- } else
- strcat(m_totalword,PhonemeTable[Phoneme]); /* Copy over the first phoneme */
-
- //logerror("Total word = %s\n",m_totalword);
-
- for (i=0; wowWordTable[i]; i++) {
- if (!strcmp(wowWordTable[i],m_totalword)) { /* Scan the word (sample) table for the complete word */
- /* WOW has Dungeon */
- if ((!strcmp("GDTO1RFYA2N",m_totalword)) || (!strcmp("RO1U1BAH1T",m_totalword)) || (!strcmp("KO1UH3I3E1N",m_totalword))) { /* May be plural */
- m_plural=i+1;
- strcpy(m_oldword,m_totalword);
- //logerror("Storing sample position %d and copying string %s\n",m_plural,m_oldword);
- } else {
- m_plural=0;
- }
- m_samples->start(0, i); /* play the sample at position of word */
- m_samples->set_frequency(0, 11025); /* play at correct rate */
- //logerror("Playing sample %d\n",i);
- m_totalword[0] = 0; /* Clear the total word stack */
- return data;
- }
- }
-#else
- m_votrax->inflection_w(space, 0, data >> 6);
- m_votrax->write(space, 0, data);
-#endif
-
- /* Note : We should really also use volume in this as well as frequency */
- return data; /* Return nicely */
-}
-
-
-CUSTOM_INPUT_MEMBER( astrocde_state::wow_speech_status_r )
-{
-#if USE_FAKE_VOTRAX
- return !m_samples->playing(0);
-#else
- return m_votrax->request();
-#endif
-}
diff --git a/src/mame/audio/wow.h b/src/mame/audio/wow.h
deleted file mode 100644
index a523dbb41e2..00000000000
--- a/src/mame/audio/wow.h
+++ /dev/null
@@ -1,4 +0,0 @@
-// license:BSD-3-Clause
-// copyright-holders:Mike Coates
-
-extern const char *const wow_sample_names[];
diff --git a/src/mame/drivers/astrocde.cpp b/src/mame/drivers/astrocde.cpp
index 82d31451c07..62545270da2 100644
--- a/src/mame/drivers/astrocde.cpp
+++ b/src/mame/drivers/astrocde.cpp
@@ -120,9 +120,7 @@
#include "machine/z80ctc.h"
#include "machine/nvram.h"
#include "sound/ay8910.h"
-
-#include "audio/wow.h"
-#include "audio/gorf.h"
+#include "sound/votrax.h"
#include "gorf.lh"
#include "seawolf2.lh"
@@ -292,7 +290,6 @@ READ8_MEMBER(astrocde_state::wow_io_r)
}
-
/*************************************
*
* Gorf specific input/output
@@ -313,11 +310,7 @@ READ8_MEMBER(astrocde_state::gorf_io_1_r)
case 5: m_sparkle[3] = data; break;
case 6:
m_astrocade_sound1->set_output_gain(0, data ? 0.0 : 1.0);
-#if USE_FAKE_VOTRAX
- m_samples->set_output_gain(0, data ? 1.0 : 0.0);
-#else
m_votrax->set_output_gain(0, data ? 1.0 : 0.0);
-#endif
break;
case 7: osd_printf_debug("io_1:%d\n", data); break;
}
@@ -512,6 +505,28 @@ WRITE8_MEMBER(astrocde_state::tenpindx_lights_w)
}
+/*************************************
+ *
+ * Votrax SC01 specific input/output
+ *
+ *************************************/
+
+READ8_MEMBER( astrocde_state::votrax_speech_r )
+{
+ uint8_t data = offset >> 8;
+ m_votrax->inflection_w(space, 0, data >> 6);
+ m_votrax->write(space, 0, data);
+
+ /* Note : We should really also use volume in this as well as frequency */
+ return data; /* Return nicely */
+}
+
+
+CUSTOM_INPUT_MEMBER( astrocde_state::votrax_speech_status_r )
+{
+ return m_votrax->request();
+}
+
/*************************************
*
@@ -879,7 +894,7 @@ static INPUT_PORTS_START( wow )
PORT_BIT( 0x10, IP_ACTIVE_HIGH, IPT_BUTTON2 )
PORT_BIT( 0x20, IP_ACTIVE_LOW, IPT_BUTTON1 )
PORT_BIT( 0x40, IP_ACTIVE_LOW, IPT_UNKNOWN )
- PORT_BIT( 0x80, IP_ACTIVE_HIGH, IPT_SPECIAL ) PORT_CUSTOM_MEMBER(DEVICE_SELF, astrocde_state, wow_speech_status_r, nullptr)
+ PORT_BIT( 0x80, IP_ACTIVE_HIGH, IPT_SPECIAL ) PORT_CUSTOM_MEMBER(DEVICE_SELF, astrocde_state, votrax_speech_status_r, nullptr)
PORT_START("P4HANDLE")
/* "If S1:1,2,3 are all ON or all OFF, only coin meter number 1 will count." */
@@ -949,7 +964,7 @@ static INPUT_PORTS_START( gorf )
PORT_BIT( 0x08, IP_ACTIVE_LOW, IPT_JOYSTICK_RIGHT ) PORT_8WAY
PORT_BIT( 0x10, IP_ACTIVE_LOW, IPT_BUTTON1 )
PORT_BIT( 0x60, IP_ACTIVE_LOW, IPT_UNUSED )
- PORT_BIT( 0x80, IP_ACTIVE_HIGH, IPT_SPECIAL ) PORT_CUSTOM_MEMBER(DEVICE_SELF, astrocde_state, gorf_speech_status_r, nullptr)
+ PORT_BIT( 0x80, IP_ACTIVE_HIGH, IPT_SPECIAL ) PORT_CUSTOM_MEMBER(DEVICE_SELF, astrocde_state, votrax_speech_status_r, nullptr)
PORT_START("P4HANDLE")
PORT_DIPNAME( 0x01, 0x01, DEF_STR( Coin_A ) ) PORT_DIPLOCATION("S1:1")
@@ -1354,13 +1369,7 @@ static MACHINE_CONFIG_DERIVED( wow, astrocade_base )
/* sound hardware */
MCFG_SPEAKER_ADD("center", 0.0, 0.0, 1.0)
-#if USE_FAKE_VOTRAX
- MCFG_SOUND_ADD("samples", SAMPLES, 0)
- MCFG_SAMPLES_CHANNELS(1)
- MCFG_SAMPLES_NAMES(wow_sample_names)
-#else
- MCFG_VOTRAX_SC01_ADD("votrax", VOTRAX_SC01, 720000)
-#endif
+ MCFG_SOUND_ADD("votrax", VOTRAX_SC01, 720000)
MCFG_SOUND_ROUTE(ALL_OUTPUTS, "center", 0.85)
MACHINE_CONFIG_END
@@ -1386,13 +1395,7 @@ static MACHINE_CONFIG_DERIVED( gorf, astrocade_base )
MCFG_ASTROCADE_ADD("astrocade2", ASTROCADE_CLOCK/4)
MCFG_SOUND_ROUTE(ALL_OUTPUTS, "lower", 1.0)
-#if USE_FAKE_VOTRAX
- MCFG_SOUND_ADD("samples", SAMPLES, 0)
- MCFG_SAMPLES_CHANNELS(1)
- MCFG_SAMPLES_NAMES(gorf_sample_names)
-#else
- MCFG_VOTRAX_SC01_ADD("votrax", VOTRAX_SC01, 720000)
-#endif
+ MCFG_SOUND_ADD("votrax", VOTRAX_SC01, 720000)
MCFG_SOUND_ROUTE(ALL_OUTPUTS, "upper", 0.85)
MACHINE_CONFIG_END
@@ -1709,7 +1712,7 @@ DRIVER_INIT_MEMBER(astrocde_state,wow)
{
m_video_config = AC_SOUND_PRESENT | AC_LIGHTPEN_INTS | AC_STARS;
m_maincpu->space(AS_IO).install_read_handler(0x15, 0x15, 0, 0xf000, 0x0f00, read8_delegate(FUNC(astrocde_state::wow_io_r), this));
- m_maincpu->space(AS_IO).install_read_handler(0x17, 0x17, 0, 0x0000, 0xff00, read8_delegate(FUNC(astrocde_state::wow_speech_r), this));
+ m_maincpu->space(AS_IO).install_read_handler(0x17, 0x17, 0, 0x0000, 0xff00, read8_delegate(FUNC(astrocde_state::votrax_speech_r), this));
}
@@ -1718,7 +1721,7 @@ DRIVER_INIT_MEMBER(astrocde_state,gorf)
m_video_config = AC_SOUND_PRESENT | AC_LIGHTPEN_INTS | AC_STARS;
m_maincpu->space(AS_IO).install_read_handler(0x15, 0x15, 0, 0xf000, 0x0f00, read8_delegate(FUNC(astrocde_state::gorf_io_1_r), this));
m_maincpu->space(AS_IO).install_read_handler(0x16, 0x16, 0, 0xf000, 0x0f00, read8_delegate(FUNC(astrocde_state::gorf_io_2_r), this));
- m_maincpu->space(AS_IO).install_read_handler(0x17, 0x17, 0, 0x0000, 0xff00, read8_delegate(FUNC(astrocde_state::gorf_speech_r), this));
+ m_maincpu->space(AS_IO).install_read_handler(0x17, 0x17, 0, 0x0000, 0xff00, read8_delegate(FUNC(astrocde_state::votrax_speech_r), this));
}
@@ -1797,13 +1800,13 @@ GAMEL(1980, ebases, 0, ebases, ebases, astrocde_state, ebases, ROT0,
GAMEL(1980, spacezap, 0, spacezap, spacezap, astrocde_state, spacezap, ROT0, "Midway", "Space Zap", MACHINE_SUPPORTS_SAVE, layout_spacezap )
/* 91354 CPU board + 90708 game board + 91356 RAM board + 91355 pattern board + 91397 memory board */
-GAME( 1980, wow, 0, wow, wow, astrocde_state, wow, ROT0, "Dave Nutting Associates / Midway", "Wizard of Wor", MACHINE_IMPERFECT_SOUND | MACHINE_SUPPORTS_SAVE )
-GAME( 1980, wowg, wow, wow, wowg, astrocde_state, wow, ROT0, "Dave Nutting Associates / Midway", "Wizard of Wor (with German Language ROM)", MACHINE_IMPERFECT_SOUND | MACHINE_SUPPORTS_SAVE )
+GAME( 1980, wow, 0, wow, wow, astrocde_state, wow, ROT0, "Dave Nutting Associates / Midway", "Wizard of Wor", MACHINE_SUPPORTS_SAVE )
+GAME( 1980, wowg, wow, wow, wowg, astrocde_state, wow, ROT0, "Dave Nutting Associates / Midway", "Wizard of Wor (with German Language ROM)", MACHINE_SUPPORTS_SAVE )
/* 91354 CPU board + 90708 game board + 91356 RAM board + 91355 pattern board + 91364 ROM/RAM board */
-GAMEL(1981, gorf, 0, gorf, gorf, astrocde_state, gorf, ROT270, "Dave Nutting Associates / Midway", "Gorf", MACHINE_IMPERFECT_SOUND | MACHINE_SUPPORTS_SAVE, layout_gorf )
-GAMEL(1981, gorfpgm1, gorf, gorf, gorf, astrocde_state, gorf, ROT270, "Dave Nutting Associates / Midway", "Gorf (program 1)", MACHINE_IMPERFECT_SOUND | MACHINE_SUPPORTS_SAVE, layout_gorf )
-GAMEL(1981, gorfpgm1g,gorf, gorf, gorfpgm1g, astrocde_state,gorf, ROT270, "Dave Nutting Associates / Midway", "Gorf (program 1, with German Language ROM)", MACHINE_IMPERFECT_SOUND | MACHINE_SUPPORTS_SAVE, layout_gorf )
+GAMEL(1981, gorf, 0, gorf, gorf, astrocde_state, gorf, ROT270, "Dave Nutting Associates / Midway", "Gorf", MACHINE_SUPPORTS_SAVE, layout_gorf )
+GAMEL(1981, gorfpgm1, gorf, gorf, gorf, astrocde_state, gorf, ROT270, "Dave Nutting Associates / Midway", "Gorf (program 1)", MACHINE_SUPPORTS_SAVE, layout_gorf )
+GAMEL(1981, gorfpgm1g,gorf, gorf, gorfpgm1g, astrocde_state,gorf, ROT270, "Dave Nutting Associates / Midway", "Gorf (program 1, with German Language ROM)", MACHINE_SUPPORTS_SAVE, layout_gorf )
/* 91354 CPU board + 90708 game board + 91356 RAM board + 91355 pattern board + 91423 memory board */
GAME( 1981, robby, 0, robby, robby, astrocde_state, robby, ROT0, "Dave Nutting Associates / Bally Midway", "The Adventures of Robby Roto!", MACHINE_SUPPORTS_SAVE )
diff --git a/src/mame/drivers/gottlieb.cpp b/src/mame/drivers/gottlieb.cpp
index 032cdc9f4ed..ece752481fd 100644
--- a/src/mame/drivers/gottlieb.cpp
+++ b/src/mame/drivers/gottlieb.cpp
@@ -1822,38 +1822,6 @@ MACHINE_CONFIG_END
*
*************************************/
-#if USE_FAKE_VOTRAX
-
-static MACHINE_CONFIG_DERIVED( reactor, gottlieb1 )
-
- /* basic machine hardware */
- MCFG_CPU_MODIFY("maincpu")
- MCFG_CPU_PROGRAM_MAP(reactor_map)
-
- MCFG_DEVICE_REMOVE("nvram")
-
- /* sound hardware */
- MCFG_FRAGMENT_ADD(reactor_samples)
-MACHINE_CONFIG_END
-
-
-static MACHINE_CONFIG_DERIVED( qbert, gottlieb1 )
-
- /* sound hardware */
- MCFG_FRAGMENT_ADD(qbert_knocker)
- MCFG_FRAGMENT_ADD(qbert_samples)
-MACHINE_CONFIG_END
-
-
-static MACHINE_CONFIG_DERIVED( tylz, gottlieb1 )
-
- /* sound hardware */
- MCFG_FRAGMENT_ADD(qbert_samples)
-MACHINE_CONFIG_END
-
-
-#else
-
static MACHINE_CONFIG_DERIVED( gottlieb1_votrax, gottlieb_core )
MCFG_SOUND_ADD("r1sound", GOTTLIEB_SOUND_REV1_WITH_VOTRAX, 0)
@@ -1882,9 +1850,6 @@ static MACHINE_CONFIG_DERIVED( tylz, gottlieb1_votrax )
MACHINE_CONFIG_END
-#endif
-
-
static MACHINE_CONFIG_DERIVED( screwloo, gottlieb2 )
MCFG_VIDEO_START_OVERRIDE(gottlieb_state,screwloo)
diff --git a/src/mame/includes/astrocde.h b/src/mame/includes/astrocde.h
index 25b4861b32a..74cfd060422 100644
--- a/src/mame/includes/astrocde.h
+++ b/src/mame/includes/astrocde.h
@@ -19,8 +19,6 @@
#define AC_STARS (0x04)
#define AC_MONITOR_BW (0x08)
-#define USE_FAKE_VOTRAX (1)
-
class astrocde_state : public driver_device
{
@@ -195,13 +193,8 @@ public:
void init_sparklestar();
virtual void machine_start() override;
- /*----------- defined in audio/wow.c -----------*/
- DECLARE_READ8_MEMBER( wow_speech_r );
- CUSTOM_INPUT_MEMBER( wow_speech_status_r );
-
- /*----------- defined in audio/gorf.c -----------*/
- DECLARE_READ8_MEMBER( gorf_speech_r );
- CUSTOM_INPUT_MEMBER( gorf_speech_status_r );
+ DECLARE_READ8_MEMBER( votrax_speech_r );
+ CUSTOM_INPUT_MEMBER( votrax_speech_status_r );
protected:
virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override;