diff options
author | 2016-12-23 00:13:25 +0100 | |
---|---|---|
committer | 2016-12-24 12:08:40 +0100 | |
commit | 2c79c1ed0d223447fcd14ba7df369129488d6d14 (patch) | |
tree | 70168d4fbad261b46d3fd96978c6f0b3541ec193 /src | |
parent | ee5d18f65736f70094f673175075becb50254d2f (diff) |
votrax sc01: Simulate [O. Galibert]
Diffstat (limited to 'src')
-rw-r--r-- | src/devices/sound/votrax.cpp | 1824 | ||||
-rw-r--r-- | src/devices/sound/votrax.h | 269 | ||||
-rw-r--r-- | src/mame/audio/gorf.cpp | 190 | ||||
-rw-r--r-- | src/mame/audio/gorf.h | 4 | ||||
-rw-r--r-- | src/mame/audio/gottlieb.cpp | 240 | ||||
-rw-r--r-- | src/mame/audio/gottlieb.h | 25 | ||||
-rw-r--r-- | src/mame/audio/wow.cpp | 183 | ||||
-rw-r--r-- | src/mame/audio/wow.h | 4 | ||||
-rw-r--r-- | src/mame/drivers/astrocde.cpp | 65 | ||||
-rw-r--r-- | src/mame/drivers/gottlieb.cpp | 35 | ||||
-rw-r--r-- | src/mame/includes/astrocde.h | 11 |
11 files changed, 913 insertions, 1937 deletions
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 ®, 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 ®, 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; |