trunk/src/devices/sound/s14001a.cpp
| r252893 | r252894 | |
| 1 | 1 | // license:BSD-3-Clause |
| 2 | | // copyright-holders:Jonathan Gevaryahu,R. Belmont,Zsolt Vasvari |
| 2 | // copyright-holders:Ed Bernard, Jonathan Gevaryahu, hap |
| 3 | // thanks-to:Kevin Horton |
| 3 | 4 | /* |
| 5 | SSi TSI S14001A speech IC emulator |
| 6 | aka CRC: Custom ROM Controller, designed in 1975, first usage in 1976 on TSI Speech+ calculator |
| 7 | Originally written for MAME by Jonathan Gevaryahu(Lord Nightmare) 2006-2013, |
| 8 | replaced with near-complete rewrite by Ed Bernard in 2016 |
| 4 | 9 | |
| 5 | | TSI S14001A emulator v1.32 |
| 6 | | By Jonathan Gevaryahu ("Lord Nightmare") with help from Kevin Horton ("kevtris") |
| 7 | | MAME conversion and integration by R. Belmont |
| 8 | | Clock Frequency control updated by Zsolt Vasvari |
| 9 | | Other fixes by AtariAce |
| 10 | TODO: |
| 11 | - nothing at the moment? |
| 10 | 12 | |
| 11 | | Copyright (C) 2006-2013 Jonathan Gevaryahu aka Lord Nightmare |
| 12 | | |
| 13 | | Version history: |
| 14 | | 0.8 initial version - LN |
| 15 | | 0.9 MAME conversion, glue code added - R. Belmont |
| 16 | | 1.0 partly fixed stream update - LN (0.111u4) |
| 17 | | 1.01 fixed clipping problem - LN (0.111u5) |
| 18 | | 1.1 add VSU-1000 features, fully fixed stream update by fixing word latching - LN (0.111u6) |
| 19 | | 1.11 fix signedness of output, pre-multiply, fixes clicking on VSU-1000 volume change - LN (0.111u7) |
| 20 | | 1.20 supports setting the clock freq directly - reset is done by external hardware, |
| 21 | | the chip has no reset line ZV (0.122) |
| 22 | | 1.30 move main dac to 4 bits only with no extension (4->16 bit range extension is now done by output). |
| 23 | | Added a somewhat better, but still not perfect, filtering system - LN |
| 24 | | 1.31 fix a minor bug with the dac range. wolfpack clips again, and I'm almost sure its an encoding error on the original speech - LN (0.125u9) |
| 25 | | 1.31a Add chip pinout and other notes - LN (0.128u4) |
| 26 | | 1.31b slight update to notes to clarify input bus stuff, mostly finish the state map in the comments - LN |
| 27 | | 1.31c remove usage of deprecat lib - AtariAce (0.128u5) |
| 28 | | 1.32 fix the squealing noise using a define; it isn't accurate to the chip exactly, but there are other issues which need to be fixed too. see TODO. - LN (0.136u2) |
| 29 | | |
| 30 | | TODO: |
| 31 | | * increase accuracy of internal S14001A 'filter' for both driven and undriven cycles (its not terribly inaccurate for undriven cycles, but the dc sliding of driven cycles is not emulated) |
| 32 | | * add option for and attach Frank P.'s emulation of the Analog external filter from the vsu-1000 using the discrete core. (with the direction of independent sound core and analog stuff, this should actually be attached in the main berzerk/frenzy driver and not here) |
| 33 | | * fix the local and global silence stuff to not force the dac to a specific level, but cease doing deltas (i.e. force all deltas to 0) after the last sample; this should fix the clipping in wolfpack and in the fidelity games in mess. |
| 13 | Further reading: |
| 14 | - http://www.vintagecalculators.com/html/speech-.html |
| 15 | - http://www.vintagecalculators.com/html/development_of_the_tsi_speech-.html |
| 16 | - http://www.vintagecalculators.com/html/speech-_state_machine.html |
| 17 | - https://archive.org/stream/pdfy-QPCSwTWiFz1u9WU_/david_djvu.txt |
| 34 | 18 | */ |
| 35 | 19 | |
| 36 | 20 | /* Chip Pinout: |
| r252893 | r252894 | |
| 99 | 83 | START is pulled high when a word is to be said and the word number is on the |
| 100 | 84 | word select/speech address input lines. The Canon 'Canola' uses a separate 'rom |
| 101 | 85 | strobe' signal independent of the chip to either enable or clock the speech rom. |
| 102 | | Its likely that they did this to be able to force the speech chip to stop talking, |
| 86 | It's likely that they did this to be able to force the speech chip to stop talking, |
| 103 | 87 | which is normally impossible. The later 'version 3' TSI speech board as featured in |
| 104 | 88 | an advertisement in the John Cater book probably also has this feature, in addition |
| 105 | 89 | to external speech rom banking. |
| r252893 | r252894 | |
| 110 | 94 | |
| 111 | 95 | Because it requires -10V to operate, the chip manufacturing process must be PMOS. |
| 112 | 96 | |
| 113 | | /-----------\ |
| 114 | | > Operation < |
| 115 | | \-----------/ |
| 97 | * Operation: |
| 116 | 98 | Put the 6-bit address of the word to be said onto the C0-C5 word select/speech |
| 117 | 99 | address bus lines. Next, clock the START line low-high-low. As long as the START |
| 118 | 100 | line is held high, the first address byte of the first word will be read repeatedly |
| r252893 | r252894 | |
| 121 | 103 | /BUSY line will go low until 3 clocks after the chip is done speaking. |
| 122 | 104 | */ |
| 123 | 105 | |
| 106 | #include "emu.h" |
| 107 | #include "s14001a.h" |
| 124 | 108 | |
| 125 | | /* state map: |
| 109 | // device definition |
| 110 | const device_type S14001A = &device_creator<s14001a_device>; |
| 126 | 111 | |
| 127 | | * state machine 1: odd/even clock state |
| 128 | | * on even clocks, audio output is floating, /romen is low so rom data bus is driven, input is latched? |
| 129 | | * on odd clocks, audio output is driven, /romen is high, state machine 2 is clocked |
| 130 | | * ***** |
| 131 | | * state machine 2: decoder state |
| 132 | | * NOTE: holding the start line high forces the state machine 2 state to go to or remain in state 1! |
| 133 | | * state 0(Idle): Idle (no sample rom bus activity, output at 0), next state is 0(Idle) |
| 112 | s14001a_device::s14001a_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) |
| 113 | : device_t(mconfig, S14001A, "S14001A", tag, owner, clock, "s14001a", __FILE__), |
| 114 | device_sound_interface(mconfig, *this), |
| 115 | m_SpeechRom(*this, DEVICE_SELF), |
| 116 | m_stream(nullptr), |
| 117 | m_bsy_handler(*this), |
| 118 | m_ext_read_handler(*this) |
| 119 | { |
| 120 | } |
| 134 | 121 | |
| 135 | | * state 1(GetHiWord): |
| 136 | | * grab byte at (wordinput<<1) -> register_WH |
| 137 | | * reset output DAC accumulator to 0x8 <- ??? |
| 138 | | * reset OldValHi to 1 |
| 139 | | * reset OldValLo to 0 |
| 140 | | * next state is 2(GetLoWord) UNLESS the PLAY line is still high, in which case the state remains at 1 |
| 122 | //------------------------------------------------- |
| 123 | // device_start - device-specific startup |
| 124 | //------------------------------------------------- |
| 141 | 125 | |
| 142 | | * state 2(GetLoWord): |
| 143 | | * grab byte at (wordinput<<1)+1 -> register_WL |
| 144 | | * next state is 3(GetHiPhon) |
| 126 | ALLOW_SAVE_TYPE(s14001a_device::states); // allow save_item on a non-fundamental type |
| 145 | 127 | |
| 146 | | * state 3(GetHiPhon): |
| 147 | | * grab byte at ((register_WH<<8) + (register_WL))>>4 -> phoneaddress |
| 148 | | * next state is 4(GetLoPhon) |
| 128 | void s14001a_device::device_start() |
| 129 | { |
| 130 | m_stream = machine().sound().stream_alloc(*this, 0, 1, clock() ? clock() : machine().sample_rate()); |
| 149 | 131 | |
| 150 | | * state 4(GetLoPhon): |
| 151 | | * grab byte at (((register_WH<<8) + (register_WL))>>4)+1 -> playparams |
| 152 | | * set phonepos register to 0 |
| 153 | | * set oddphone register to 0 |
| 154 | | * next state is 5(PlayForward1) |
| 155 | | * playparams: |
| 156 | | * 7 6 5 4 3 2 1 0 |
| 157 | | * G G = LastPhone |
| 158 | | * B B = PlayMode |
| 159 | | * Y Y = Silenceflag |
| 160 | | * S S S S = Length count load value |
| 161 | | * R R R = Repeat count reload value (upon carry/overflow of 3 bits) |
| 162 | | * load the repeat counter with the bits 'R R 0' |
| 163 | | * load the length counter with the bits 'S S S 0' |
| 164 | | * NOTE: though only three bits of the length counter load value are controllable, there is a fourth lower bit which is assumed 0 on start and controls the direction of playback, i.e. forwards or backwards within a phone. |
| 165 | | * NOTE: though only two bits of the repeat counter reload value are controllable, there is a third bit which is loaded to 0 on phoneme start, and this hidden low-order bit of the counter itself is what controls whether the output is forced to silence in mirrored mode. the 'carry' from the highest bit of the 3 bit counter is what increments the address pointer for pointing to the next phoneme in mirrored mode |
| 132 | // resolve callbacks |
| 133 | m_ext_read_handler.resolve(); |
| 134 | m_bsy_handler.resolve(); |
| 135 | |
| 136 | // note: zerofill is done already by MAME core |
| 137 | ClearStatistics(); |
| 138 | m_uOutputP1 = m_uOutputP2 = 7; |
| 139 | |
| 140 | // register for savestates |
| 141 | save_item(NAME(m_bPhase1)); |
| 142 | save_item(NAME(m_uStateP1)); |
| 143 | save_item(NAME(m_uStateP2)); |
| 144 | save_item(NAME(m_uDAR13To05P1)); |
| 145 | save_item(NAME(m_uDAR13To05P2)); |
| 146 | save_item(NAME(m_uDAR04To00P1)); |
| 147 | save_item(NAME(m_uDAR04To00P2)); |
| 148 | save_item(NAME(m_uCWARP1)); |
| 149 | save_item(NAME(m_uCWARP2)); |
| 166 | 150 | |
| 151 | save_item(NAME(m_bStopP1)); |
| 152 | save_item(NAME(m_bStopP2)); |
| 153 | save_item(NAME(m_bVoicedP1)); |
| 154 | save_item(NAME(m_bVoicedP2)); |
| 155 | save_item(NAME(m_bSilenceP1)); |
| 156 | save_item(NAME(m_bSilenceP2)); |
| 157 | save_item(NAME(m_uLengthP1)); |
| 158 | save_item(NAME(m_uLengthP2)); |
| 159 | save_item(NAME(m_uXRepeatP1)); |
| 160 | save_item(NAME(m_uXRepeatP2)); |
| 161 | save_item(NAME(m_uDeltaOldP1)); |
| 162 | save_item(NAME(m_uDeltaOldP2)); |
| 163 | save_item(NAME(m_uOutputP1)); |
| 167 | 164 | |
| 168 | | * shift register diagram: |
| 169 | | * F E D C B A 9 8 7 6 5 4 3 2 1 0 |
| 170 | | * <new byte here> |
| 171 | | * C C C = Current delta sample read point |
| 172 | | * O O O = Old delta sample read point |
| 173 | | * I *OPTIMIZED OUT* the shift register by making use of the fact that the device reads each rom byte 4 times |
| 165 | save_item(NAME(m_bDAR04To00CarryP2)); |
| 166 | save_item(NAME(m_bPPQCarryP2)); |
| 167 | save_item(NAME(m_bRepeatCarryP2)); |
| 168 | save_item(NAME(m_bLengthCarryP2)); |
| 169 | save_item(NAME(m_RomAddrP1)); |
| 174 | 170 | |
| 175 | | * state 5(PlayForward1): |
| 176 | | * grab byte at (((phoneaddress<<8)+(oddphone*8))+(phonepos>>2)) -> PlayRegister high end, bits F to 8 |
| 177 | | * if Playmode is mirrored, set OldValHi and OldValLo to 1 and 0 respectively, otherwise leave them with whatever was in them before. |
| 178 | | * Put OldValHi in bit 7 of PlayRegister |
| 179 | | * Put OldValLo in bit 6 of PlayRegister |
| 180 | | * Get new OldValHi from bit 9 |
| 181 | | * Get new OldValLo from bit 8 |
| 182 | | * feed current delta (bits 9 and 8) and olddelta (bits 7 and 6) to delta demodulator table, delta demodulator table applies a delta to the accumulator, accumulator goes to enable/disable latch which Silenceflag enables or disables (forces output to 0x8 on disable), then to DAC to output. |
| 183 | | * next state: state 6(PlayForward2) |
| 171 | save_item(NAME(m_uOutputP2)); |
| 172 | save_item(NAME(m_uRomAddrP2)); |
| 173 | save_item(NAME(m_bBusyP1)); |
| 174 | save_item(NAME(m_bStart)); |
| 175 | save_item(NAME(m_uWord)); |
| 184 | 176 | |
| 185 | | * state 6(PlayForward2): |
| 186 | | * grab byte at (((phoneaddress<<8)+oddphone)+(phonepos>>2)) -> PlayRegister bits D to 6. |
| 187 | | * Put OldValHi in bit 7 of PlayRegister\____already done by above operation |
| 188 | | * Put OldValLo in bit 6 of PlayRegister/ |
| 189 | | * Get new OldValHi from bit 9 |
| 190 | | * Get new OldValLo from bit 8 |
| 191 | | * feed current delta (bits 9 and 8) and olddelta (bits 7 and 6) to delta demodulator table, delta demodulator table applies a delta to the accumulator, accumulator goes to enable/disable latch which Silenceflag enables or disables (forces output to 0x8 on disable), then to DAC to output. |
| 192 | | * next state: state 7(PlayForward3) |
| 177 | save_item(NAME(m_uNPitchPeriods)); |
| 178 | save_item(NAME(m_uNVoiced)); |
| 179 | save_item(NAME(m_uNControlWords)); |
| 180 | save_item(NAME(m_uPrintLevel)); |
| 181 | } |
| 193 | 182 | |
| 194 | | * state 7(PlayForward3): |
| 195 | | * grab byte at (((phoneaddress<<8)+oddphone)+(phonepos>>2)) -> PlayRegister bits B to 4. |
| 196 | | * Put OldValHi in bit 7 of PlayRegister\____already done by above operation |
| 197 | | * Put OldValLo in bit 6 of PlayRegister/ |
| 198 | | * Get new OldValHi from bit 9 |
| 199 | | * Get new OldValLo from bit 8 |
| 200 | | * feed current delta (bits 9 and 8) and olddelta (bits 7 and 6) to delta demodulator table, delta demodulator table applies a delta to the accumulator, accumulator goes to enable/disable latch which Silenceflag enables or disables (forces output to 0x8 on disable), then to DAC to output. |
| 201 | | * next state: state 8(PlayForward4) |
| 202 | 183 | |
| 203 | | * state 8(PlayForward4): |
| 204 | | * grab byte at (((phoneaddress<<8)+oddphone)+(phonepos>>2)) -> PlayRegister bits 9 to 2. |
| 205 | | * Put OldValHi in bit 7 of PlayRegister\____already done by above operation |
| 206 | | * Put OldValLo in bit 6 of PlayRegister/ |
| 207 | | * Get new OldValHi from bit 9 |
| 208 | | * Get new OldValLo from bit 8 |
| 209 | | * feed current delta (bits 9 and 8) and olddelta (bits 7 and 6) to delta demodulator table, delta demodulator table applies a delta to the accumulator, accumulator goes to enable/disable latch which Silenceflag enables or disables (forces output to 0x8 on disable), then to DAC to output. |
| 210 | | * if offset < 8, increment offset within 8-byte phone |
| 211 | | * if offset = 8: (see PostPhoneme code to understand how this part works, its a bit complicated) |
| 184 | //------------------------------------------------- |
| 185 | // sound_stream_update - handle a stream update |
| 186 | //------------------------------------------------- |
| 212 | 187 | |
| 213 | | * next state: depends on playparams: |
| 214 | | * if we're in mirrored mode, next will be LoadAndPlayBackward1 |
| 215 | | * if we're in nonmirrored mode, next will be PlayForward1 |
| 188 | void s14001a_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) |
| 189 | { |
| 190 | for (int i = 0; i < samples; i++) |
| 191 | { |
| 192 | Clock(); |
| 193 | INT16 sample = m_uOutputP2 - 7; // range -7..8 |
| 194 | outputs[0][i] = sample * 0xf00; |
| 195 | } |
| 196 | } |
| 216 | 197 | |
| 217 | | * state 9(LoadAndPlayBackward1) |
| 218 | | * grab byte at (((phoneaddress<<8)+(oddphone*8))+(phonepos>>2)) -> PlayRegister high end, bits F to 8 <- check code on this, I think its backwards here but its correct in the code |
| 219 | | * see code for this, its basically the same as state 8 but with the byte grab mentioned above, and the values fed to the delta demod table are switched |
| 220 | | * state 10(PlayBackward2) |
| 221 | | * see code for this, its basically the same as state 7 but the values fed to the delta demod table are switched |
| 222 | | * state 11(PlayBackward3) |
| 223 | | * see code for this, its basically the same as state 6 but the values fed to the delta demod table are switched |
| 224 | | * state 12(PlayBackward4) |
| 225 | | * see code for this, its basically the same as state 5 but with no byte grab, and the values fed to the delta demod table are switched, and a bit below similar to state 5 |
| 226 | | * if offset > -1, decrement offset within 8-byte phone |
| 227 | | * if offset = -1: (see PostPhoneme code to understand how this part works, its a bit complicated) |
| 228 | | */ |
| 229 | 198 | |
| 230 | | /* increment address function: |
| 231 | | * increment repeat counter |
| 232 | | if repeat counter produces a carry, do two things: |
| 233 | | 1. if mirrored mode is ON, increment oddphone. if oddphone carries out (i.e. if it was 1), increment phoneaddress and zero oddphone |
| 234 | | 2. increment lengthcounter. if lengthcounter carries out, we're done this phone. |
| 235 | | * increment output counter |
| 236 | | * if mirrored mode is on, output direction is |
| 237 | | * if mirrored mode is OFF, increment oddphone. if not, don't touch it here. if oddphone was 1 before the increment, increment phoneaddress and set oddphone to 0 |
| 238 | | * |
| 239 | | */ |
| 199 | /************************************************************************** |
| 200 | External interface |
| 201 | **************************************************************************/ |
| 240 | 202 | |
| 241 | | #undef ACCURATE_SQUEAL |
| 203 | void s14001a_device::force_update() |
| 204 | { |
| 205 | m_stream->update(); |
| 206 | } |
| 242 | 207 | |
| 243 | | #include "emu.h" |
| 244 | | #include "s14001a.h" |
| 208 | READ_LINE_MEMBER(s14001a_device::romen_r) |
| 209 | { |
| 210 | m_stream->update(); |
| 211 | return (m_bPhase1) ? 1 : 0; |
| 212 | } |
| 245 | 213 | |
| 214 | READ_LINE_MEMBER(s14001a_device::busy_r) |
| 215 | { |
| 216 | m_stream->update(); |
| 217 | return (m_bBusyP1) ? 1 : 0; |
| 218 | } |
| 246 | 219 | |
| 247 | | //#define DEBUGSTATE |
| 220 | WRITE8_MEMBER(s14001a_device::data_w) |
| 221 | { |
| 222 | m_stream->update(); |
| 223 | m_uWord = data & 0x3f; // C0-C5 |
| 224 | } |
| 248 | 225 | |
| 249 | | #define SILENCE 0x7 // value output when silent |
| 250 | | #define ALTFLAG 0xFF // value to tell renderer that this frame's output is the average of the 8 prior frames and not directly used. |
| 226 | WRITE_LINE_MEMBER(s14001a_device::start_w) |
| 227 | { |
| 228 | m_stream->update(); |
| 229 | m_bStart = (state != 0); |
| 230 | if (m_bStart) m_uStateP1 = WORDWAIT; |
| 231 | } |
| 251 | 232 | |
| 252 | | #define LASTSYLLABLE ((m_PlayParams & 0x80)>>7) |
| 253 | | #define MIRRORMODE ((m_PlayParams & 0x40)>>6) |
| 254 | | #define SILENCEFLAG ((m_PlayParams & 0x20)>>5) |
| 255 | | #define LENGTHCOUNT ((m_PlayParams & 0x1C)>>1) // remember: its 4 bits and the bottom bit is always zero! |
| 256 | | #define REPEATCOUNT ((m_PlayParams<<1)&0x6) // remember: its 3 bits and the bottom bit is always zero! |
| 257 | | #define LOCALSILENCESTATE ((m_OutputCounter & 0x2) && (MIRRORMODE)) // 1 when silent output, 0 when DAC output. |
| 258 | | |
| 259 | | static const INT8 DeltaTable[4][4] = |
| 233 | void s14001a_device::set_clock(UINT32 clock) |
| 260 | 234 | { |
| 261 | | { -3, -3, -1, -1, }, |
| 262 | | { -1, -1, 0, 0, }, |
| 263 | | { 0, 0, 1, 1, }, |
| 264 | | { 1, 1, 3, 3 }, |
| 265 | | }; |
| 235 | m_stream->update(); |
| 236 | m_stream->set_sample_rate(clock); |
| 237 | } |
| 266 | 238 | |
| 267 | | #ifdef ACCURATE_SQUEAL |
| 268 | | INT16 s14001a_device::audiofilter() /* rewrite me to better match the real filter! */ |
| 239 | |
| 240 | /************************************************************************** |
| 241 | Device emulation |
| 242 | **************************************************************************/ |
| 243 | |
| 244 | UINT8 s14001a_device::readmem(UINT16 offset, bool phase) |
| 269 | 245 | { |
| 270 | | UINT8 temp1; |
| 271 | | INT16 temp2 = 0; |
| 272 | | /* mean averaging filter! 1/n exponential *would* be somewhat better, but I'm lazy... */ |
| 273 | | for (temp1 = 0; temp1 < 8; temp1++) { temp2 += m_filtervals[temp1]; } |
| 274 | | temp2 >>= 3; |
| 275 | | return temp2; |
| 246 | offset &= 0xfff; // 11-bit internal |
| 247 | return ((m_ext_read_handler.isnull()) ? m_SpeechRom[offset & (m_SpeechRom.bytes() - 1)] : m_ext_read_handler(offset)); |
| 276 | 248 | } |
| 277 | 249 | |
| 278 | | void s14001a_device::shiftIntoFilter(INT16 inputvalue) |
| 250 | bool s14001a_device::Clock() |
| 279 | 251 | { |
| 280 | | UINT8 temp1; |
| 281 | | for (temp1 = 7; temp1 > 0; temp1--) |
| 252 | // effectively toggles external clock twice, one cycle |
| 253 | // internal clock toggles on external clock transition from 0 to 1 so internal clock will always transition here |
| 254 | // return false if some emulator problem detected |
| 255 | |
| 256 | // On the actual chip, all register phase 1 values needed to be refreshed from phase 2 values |
| 257 | // or else risk losing their state due to charge loss. |
| 258 | // But on a computer the values are static. |
| 259 | // So to reduce code clutter, phase 1 values are only modified if they are different |
| 260 | // from the preceeding phase 2 values. |
| 261 | |
| 262 | if (m_bPhase1) |
| 282 | 263 | { |
| 283 | | m_filtervals[temp1] = m_filtervals[(temp1 - 1)]; |
| 264 | // transition to phase2 |
| 265 | m_bPhase1 = false; |
| 266 | |
| 267 | // transfer phase1 variables to phase2 |
| 268 | m_uStateP2 = m_uStateP1; |
| 269 | m_uDAR13To05P2 = m_uDAR13To05P1; |
| 270 | m_uDAR04To00P2 = m_uDAR04To00P1; |
| 271 | m_uCWARP2 = m_uCWARP1; |
| 272 | m_bStopP2 = m_bStopP1; |
| 273 | m_bVoicedP2 = m_bVoicedP1; |
| 274 | m_bSilenceP2 = m_bSilenceP1; |
| 275 | m_uLengthP2 = m_uLengthP1; |
| 276 | m_uXRepeatP2 = m_uXRepeatP1; |
| 277 | m_uDeltaOldP2 = m_uDeltaOldP1; |
| 278 | |
| 279 | m_uOutputP2 = m_uOutputP1; |
| 280 | m_uRomAddrP2 = m_RomAddrP1; |
| 281 | |
| 282 | // setup carries from phase 2 values |
| 283 | m_bDAR04To00CarryP2 = m_uDAR04To00P2 == 0x1F; |
| 284 | m_bPPQCarryP2 = m_bDAR04To00CarryP2 && ((m_uLengthP2&0x03) == 0x03); // pitch period quarter |
| 285 | m_bRepeatCarryP2 = m_bPPQCarryP2 && ((m_uLengthP2&0x0C) == 0x0C); |
| 286 | m_bLengthCarryP2 = m_bRepeatCarryP2 && ( m_uLengthP2 == 0x7F); |
| 287 | |
| 288 | return true; |
| 284 | 289 | } |
| 285 | | m_filtervals[0] = inputvalue; |
| 286 | | } |
| 287 | | #endif |
| 290 | m_bPhase1 = true; |
| 288 | 291 | |
| 289 | | void s14001a_device::PostPhoneme() /* figure out what the heck to do after playing a phoneme */ |
| 290 | | { |
| 291 | | #ifdef DEBUGSTATE |
| 292 | | fprintf(stderr,"0: entered PostPhoneme\n"); |
| 293 | | #endif |
| 294 | | m_RepeatCounter++; // increment the repeat counter |
| 295 | | m_OutputCounter++; // increment the output counter |
| 296 | | if (MIRRORMODE) // if mirroring is enabled |
| 292 | // logic done during phase 1 |
| 293 | switch (m_uStateP1) |
| 297 | 294 | { |
| 298 | | #ifdef DEBUGSTATE |
| 299 | | fprintf(stderr,"1: MIRRORMODE was on\n"); |
| 300 | | #endif |
| 301 | | if (m_RepeatCounter == 0x8) // exceeded 3 bits? |
| 295 | case IDLE: |
| 296 | m_uOutputP1 = 7; |
| 297 | if (m_bStart) m_uStateP1 = WORDWAIT; |
| 298 | |
| 299 | if (m_bBusyP1 && !m_bsy_handler.isnull()) |
| 300 | m_bsy_handler(0); |
| 301 | m_bBusyP1 = false; |
| 302 | break; |
| 303 | |
| 304 | case WORDWAIT: |
| 305 | // the delta address register latches the word number into bits 03 to 08 |
| 306 | // all other bits forced to 0. 04 to 08 makes a multiply by two. |
| 307 | m_uDAR13To05P1 = (m_uWord&0x3C)>>2; |
| 308 | m_uDAR04To00P1 = (m_uWord&0x03)<<3; |
| 309 | m_RomAddrP1 = (m_uDAR13To05P1<<3)|(m_uDAR04To00P1>>2); // remove lower two bits |
| 310 | m_uOutputP1 = 7; |
| 311 | if (m_bStart) m_uStateP1 = WORDWAIT; |
| 312 | else m_uStateP1 = CWARMSB; |
| 313 | |
| 314 | if (!m_bBusyP1 && !m_bsy_handler.isnull()) |
| 315 | m_bsy_handler(1); |
| 316 | m_bBusyP1 = true; |
| 317 | break; |
| 318 | |
| 319 | case CWARMSB: |
| 320 | if (m_uPrintLevel >= 1) |
| 321 | printf("\n speaking word %02x",m_uWord); |
| 322 | |
| 323 | // use uDAR to load uCWAR 8 msb |
| 324 | m_uCWARP1 = readmem(m_uRomAddrP2,m_bPhase1)<<4; // note use of rom address setup in previous state |
| 325 | // increment DAR by 4, 2 lsb's count deltas within a byte |
| 326 | m_uDAR04To00P1 += 4; |
| 327 | if (m_uDAR04To00P1 >= 32) m_uDAR04To00P1 = 0; // emulate 5 bit counter |
| 328 | m_RomAddrP1 = (m_uDAR13To05P1<<3)|(m_uDAR04To00P1>>2); // remove lower two bits |
| 329 | |
| 330 | m_uOutputP1 = 7; |
| 331 | if (m_bStart) m_uStateP1 = WORDWAIT; |
| 332 | else m_uStateP1 = CWARLSB; |
| 333 | break; |
| 334 | |
| 335 | case CWARLSB: |
| 336 | m_uCWARP1 = m_uCWARP2|(readmem(m_uRomAddrP2,m_bPhase1)>>4); // setup in previous state |
| 337 | m_RomAddrP1 = m_uCWARP1; |
| 338 | |
| 339 | m_uOutputP1 = 7; |
| 340 | if (m_bStart) m_uStateP1 = WORDWAIT; |
| 341 | else m_uStateP1 = DARMSB; |
| 342 | break; |
| 343 | |
| 344 | case DARMSB: |
| 345 | m_uDAR13To05P1 = readmem(m_uRomAddrP2,m_bPhase1)<<1; // 9 bit counter, 8 MSBs from ROM, lsb zeroed |
| 346 | m_uDAR04To00P1 = 0; |
| 347 | m_uCWARP1++; |
| 348 | m_RomAddrP1 = m_uCWARP1; |
| 349 | m_uNControlWords++; // statistics |
| 350 | |
| 351 | m_uOutputP1 = 7; |
| 352 | if (m_bStart) m_uStateP1 = WORDWAIT; |
| 353 | else m_uStateP1 = CTRLBITS; |
| 354 | break; |
| 355 | |
| 356 | case CTRLBITS: |
| 357 | m_bStopP1 = readmem(m_uRomAddrP2,m_bPhase1)&0x80? true: false; |
| 358 | m_bVoicedP1 = readmem(m_uRomAddrP2,m_bPhase1)&0x40? true: false; |
| 359 | m_bSilenceP1 = readmem(m_uRomAddrP2,m_bPhase1)&0x20? true: false; |
| 360 | m_uXRepeatP1 = readmem(m_uRomAddrP2,m_bPhase1)&0x03; |
| 361 | m_uLengthP1 =(readmem(m_uRomAddrP2,m_bPhase1)&0x1F)<<2; // includes external length and repeat |
| 362 | m_uDAR04To00P1 = 0; |
| 363 | m_uCWARP1++; // gets ready for next DARMSB |
| 364 | m_RomAddrP1 = (m_uDAR13To05P1<<3)|(m_uDAR04To00P1>>2); // remove lower two bits |
| 365 | |
| 366 | m_uOutputP1 = 7; |
| 367 | if (m_bStart) m_uStateP1 = WORDWAIT; |
| 368 | else m_uStateP1 = PLAY; |
| 369 | |
| 370 | if (m_uPrintLevel >= 2) |
| 371 | printf("\n cw %d %d %d %d %d",m_bStopP1,m_bVoicedP1,m_bSilenceP1,m_uLengthP1>>4,m_uXRepeatP1); |
| 372 | |
| 373 | break; |
| 374 | |
| 375 | case PLAY: |
| 376 | { |
| 377 | // statistics |
| 378 | if (m_bPPQCarryP2) |
| 302 | 379 | { |
| 303 | | #ifdef DEBUGSTATE |
| 304 | | fprintf(stderr,"2: RepeatCounter was == 8\n"); |
| 305 | | #endif |
| 306 | | // reset repeat counter, increment length counter |
| 307 | | // but first check if lowest bit is set |
| 308 | | m_RepeatCounter = REPEATCOUNT; // reload repeat counter with reload value |
| 309 | | if (m_LengthCounter & 0x1) // if low bit is 1 (will carry after increment) |
| 380 | // pitch period end |
| 381 | if (m_uPrintLevel >= 3) |
| 382 | printf("\n ppe: RomAddr %03x",m_uRomAddrP2); |
| 383 | |
| 384 | m_uNPitchPeriods++; |
| 385 | if (m_bVoicedP2) m_uNVoiced++; |
| 386 | } |
| 387 | // end statistics |
| 388 | |
| 389 | // modify output |
| 390 | UINT8 uDeltaP2; // signal line |
| 391 | UINT8 uIncrementP2; // signal lines |
| 392 | bool bAddP2; // signal line |
| 393 | uDeltaP2 = Mux8To2(m_bVoicedP2, |
| 394 | m_uLengthP2 & 0x03, // pitch period quater counter |
| 395 | m_uDAR04To00P2 & 0x03, // two bit delta address within byte |
| 396 | readmem(m_uRomAddrP2,m_bPhase1) |
| 397 | ); |
| 398 | CalculateIncrement(m_bVoicedP2, |
| 399 | m_uLengthP2 & 0x03, // pitch period quater counter |
| 400 | m_uDAR04To00P2 == 0, // pitch period quarter start |
| 401 | uDeltaP2, |
| 402 | m_uDeltaOldP2, // input |
| 403 | m_uDeltaOldP1, // output |
| 404 | uIncrementP2, // output 0, 1, or 3 |
| 405 | bAddP2 // output |
| 406 | ); |
| 407 | m_uOutputP1 = CalculateOutput(m_bVoicedP2, |
| 408 | m_bSilenceP2, |
| 409 | m_uLengthP2 & 0x03, // pitch period quater counter |
| 410 | m_uDAR04To00P2 == 0, // pitch period quarter start |
| 411 | m_uOutputP2, // last output |
| 412 | uIncrementP2, |
| 413 | bAddP2 |
| 414 | ); |
| 415 | |
| 416 | // advance counters |
| 417 | m_uDAR04To00P1++; |
| 418 | if (m_bDAR04To00CarryP2) // pitch period quarter end |
| 419 | { |
| 420 | m_uDAR04To00P1 = 0; // emulate 5 bit counter |
| 421 | |
| 422 | m_uLengthP1++; // lower two bits of length count quarter pitch periods |
| 423 | if (m_uLengthP1 >= 0x80) |
| 310 | 424 | { |
| 311 | | #ifdef DEBUGSTATE |
| 312 | | fprintf(stderr,"3: LengthCounter's low bit was 1\n"); |
| 313 | | #endif |
| 314 | | m_PhoneAddress+=8; // go to next phone in this syllable |
| 425 | m_uLengthP1 = 0; // emulate 7 bit counter |
| 315 | 426 | } |
| 316 | | m_LengthCounter++; |
| 317 | | if (m_LengthCounter == 0x10) // if Length counter carried out of 4 bits |
| 318 | | { |
| 319 | | #ifdef DEBUGSTATE |
| 320 | | fprintf(stderr,"3: LengthCounter overflowed\n"); |
| 321 | | #endif |
| 322 | | m_SyllableAddress += 2; // go to next syllable |
| 323 | | m_nextstate = LASTSYLLABLE ? 13 : 3; // if we're on the last syllable, go to end state, otherwise go and load the next syllable. |
| 324 | | } |
| 325 | | else |
| 326 | | { |
| 327 | | #ifdef DEBUGSTATE |
| 328 | | fprintf(stderr,"3: LengthCounter's low bit wasn't 1 and it didn't overflow\n"); |
| 329 | | #endif |
| 330 | | m_PhoneOffset = (m_OutputCounter&1) ? 7 : 0; |
| 331 | | m_nextstate = (m_OutputCounter&1) ? 9 : 5; |
| 332 | | } |
| 333 | 427 | } |
| 334 | | else // repeatcounter did NOT carry out of 3 bits so leave length counter alone |
| 428 | |
| 429 | if (m_bVoicedP2 && m_bRepeatCarryP2) // repeat complete |
| 335 | 430 | { |
| 336 | | #ifdef DEBUGSTATE |
| 337 | | fprintf(stderr,"2: RepeatCounter is less than 8 (its actually %d)\n", m_RepeatCounter); |
| 338 | | #endif |
| 339 | | m_PhoneOffset = (m_OutputCounter&1) ? 7 : 0; |
| 340 | | m_nextstate = (m_OutputCounter&1) ? 9 : 5; |
| 431 | m_uLengthP1 &= 0x70; // keep current "length" |
| 432 | m_uLengthP1 |= (m_uXRepeatP1<<2); // load repeat from external repeat |
| 433 | m_uDAR13To05P1++; // advances ROM address 8 bytes |
| 434 | if (m_uDAR13To05P1 >= 0x200) m_uDAR13To05P1 = 0; // emulate 9 bit counter |
| 341 | 435 | } |
| 342 | | } |
| 343 | | else // if mirroring is NOT enabled |
| 344 | | { |
| 345 | | #ifdef DEBUGSTATE |
| 346 | | fprintf(stderr,"1: MIRRORMODE was off\n"); |
| 347 | | #endif |
| 348 | | if (m_RepeatCounter == 0x8) // exceeded 3 bits? |
| 436 | if (!m_bVoicedP2 && m_bDAR04To00CarryP2) |
| 349 | 437 | { |
| 350 | | #ifdef DEBUGSTATE |
| 351 | | fprintf(stderr,"2: RepeatCounter was == 8\n"); |
| 352 | | #endif |
| 353 | | // reset repeat counter, increment length counter |
| 354 | | m_RepeatCounter = REPEATCOUNT; // reload repeat counter with reload value |
| 355 | | m_LengthCounter++; |
| 356 | | if (m_LengthCounter == 0x10) // if Length counter carried out of 4 bits |
| 357 | | { |
| 358 | | #ifdef DEBUGSTATE |
| 359 | | fprintf(stderr,"3: LengthCounter overflowed\n"); |
| 360 | | #endif |
| 361 | | m_SyllableAddress += 2; // go to next syllable |
| 362 | | m_nextstate = LASTSYLLABLE ? 13 : 3; // if we're on the last syllable, go to end state, otherwise go and load the next syllable. |
| 363 | | #ifdef DEBUGSTATE |
| 364 | | fprintf(stderr,"nextstate is now %d\n", m_nextstate); // see line below, same reason. |
| 365 | | #endif |
| 366 | | return; // need a return here so we don't hit the 'nextstate = 5' line below |
| 367 | | } |
| 438 | // unvoiced advances each quarter pitch period |
| 439 | // note repeat counter not reloaded for non voiced speech |
| 440 | m_uDAR13To05P1++; // advances ROM address 8 bytes |
| 441 | if (m_uDAR13To05P1 >= 0x200) m_uDAR13To05P1 = 0; // emulate 9 bit counter |
| 368 | 442 | } |
| 369 | | m_PhoneAddress += 8; // regardless of counters, the phone address always increments in non-mirrored mode |
| 370 | | m_PhoneOffset = 0; |
| 371 | | m_nextstate = 5; |
| 372 | | } |
| 373 | | #ifdef DEBUGSTATE |
| 374 | | fprintf(stderr,"nextstate is now %d\n", m_nextstate); |
| 375 | | #endif |
| 376 | | } |
| 377 | 443 | |
| 378 | | void s14001a_device::s14001a_clock() /* called once per clock */ |
| 379 | | { |
| 380 | | UINT8 CurDelta; // Current delta |
| 381 | | |
| 382 | | /* on even clocks, audio output is floating, /romen is low so rom data bus is driven |
| 383 | | * on odd clocks, audio output is driven, /romen is high, state machine 2 is clocked |
| 384 | | */ |
| 385 | | m_oddeven = !(m_oddeven); // invert the clock |
| 386 | | if (m_oddeven == 0) // even clock |
| 387 | | { |
| 388 | | #ifdef ACCURATE_SQUEAL |
| 389 | | m_audioout = ALTFLAG; // flag to the renderer that this output should be the average of the last 8 |
| 390 | | #endif |
| 391 | | // DIGITAL INPUT *MIGHT* occur on the test pins occurs on this cycle? |
| 392 | | } |
| 393 | | else // odd clock |
| 394 | | { |
| 395 | | // fix dac output between samples. theoretically this might be unnecessary but it would require some messy logic in state 5 on the first sample load. |
| 396 | | // Note: this behavior is NOT accurate, and needs to be fixed. see TODO. |
| 397 | | if (m_GlobalSilenceState || LOCALSILENCESTATE) |
| 444 | // construct m_RomAddrP1 |
| 445 | m_RomAddrP1 = m_uDAR04To00P1; |
| 446 | if (m_bVoicedP2 && m_uLengthP1&0x1) // mirroring |
| 398 | 447 | { |
| 399 | | m_DACOutput = SILENCE; |
| 400 | | m_OldDelta = 2; |
| 448 | m_RomAddrP1 ^= 0x1f; // count backwards |
| 401 | 449 | } |
| 402 | | m_audioout = (m_GlobalSilenceState || LOCALSILENCESTATE) ? SILENCE : m_DACOutput; // when either silence state is 1, output silence. |
| 403 | | // DIGITAL OUTPUT *might* be driven onto the test pins on this cycle? |
| 404 | | switch(m_machineState) |
| 450 | m_RomAddrP1 = (m_uDAR13To05P1<<3) | m_RomAddrP1>>2; |
| 451 | |
| 452 | // next state |
| 453 | if (m_bStart) m_uStateP1 = WORDWAIT; |
| 454 | else if (m_bStopP2 && m_bLengthCarryP2) m_uStateP1 = DELAY; |
| 455 | else if (m_bLengthCarryP2) |
| 405 | 456 | { |
| 406 | | case 0: // idle state |
| 407 | | m_nextstate = 0; |
| 408 | | break; |
| 409 | | case 1: // read starting syllable high byte from word table |
| 410 | | m_SyllableAddress = 0; // clear syllable address |
| 411 | | m_SyllableAddress |= readmem((m_LatchedWord<<1))<<4; |
| 412 | | m_nextstate = m_resetState ? 1 : 2; |
| 413 | | break; |
| 414 | | case 2: // read starting syllable low byte from word table |
| 415 | | m_SyllableAddress |= readmem((m_LatchedWord<<1)+1)>>4; |
| 416 | | m_nextstate = 3; |
| 417 | | break; |
| 418 | | case 3: // read starting phone address |
| 419 | | m_PhoneAddress = readmem(m_SyllableAddress)<<4; |
| 420 | | m_nextstate = 4; |
| 421 | | break; |
| 422 | | case 4: // read playback parameters and prepare for play |
| 423 | | m_PlayParams = readmem(m_SyllableAddress+1); |
| 424 | | m_GlobalSilenceState = SILENCEFLAG; // load phone silence flag |
| 425 | | m_LengthCounter = LENGTHCOUNT; // load length counter |
| 426 | | m_RepeatCounter = REPEATCOUNT; // load repeat counter |
| 427 | | m_OutputCounter = 0; // clear output counter and disable mirrored phoneme silence indirectly via LOCALSILENCESTATE |
| 428 | | m_PhoneOffset = 0; // set offset within phone to zero |
| 429 | | m_OldDelta = 0x2; // set old delta to 2 <- is this right? |
| 430 | | m_DACOutput = SILENCE ; // set DAC output to center/silence position |
| 431 | | m_nextstate = 5; |
| 432 | | break; |
| 433 | | case 5: // Play phone forward, shift = 0 (also load) |
| 434 | | CurDelta = (readmem((m_PhoneAddress)+m_PhoneOffset)&0xc0)>>6; // grab current delta from high 2 bits of high nybble |
| 435 | | m_DACOutput += DeltaTable[CurDelta][m_OldDelta]; // send data to forward delta table and add result to accumulator |
| 436 | | m_OldDelta = CurDelta; // Move current delta to old |
| 437 | | m_nextstate = 6; |
| 438 | | break; |
| 439 | | case 6: // Play phone forward, shift = 2 |
| 440 | | CurDelta = (readmem((m_PhoneAddress)+m_PhoneOffset)&0x30)>>4; // grab current delta from low 2 bits of high nybble |
| 441 | | m_DACOutput += DeltaTable[CurDelta][m_OldDelta]; // send data to forward delta table and add result to accumulator |
| 442 | | m_OldDelta = CurDelta; // Move current delta to old |
| 443 | | m_nextstate = 7; |
| 444 | | break; |
| 445 | | case 7: // Play phone forward, shift = 4 |
| 446 | | CurDelta = (readmem((m_PhoneAddress)+m_PhoneOffset)&0xc)>>2; // grab current delta from high 2 bits of low nybble |
| 447 | | m_DACOutput += DeltaTable[CurDelta][m_OldDelta]; // send data to forward delta table and add result to accumulator |
| 448 | | m_OldDelta = CurDelta; // Move current delta to old |
| 449 | | m_nextstate = 8; |
| 450 | | break; |
| 451 | | case 8: // Play phone forward, shift = 6 (increment address if needed) |
| 452 | | CurDelta = readmem((m_PhoneAddress)+m_PhoneOffset)&0x3; // grab current delta from low 2 bits of low nybble |
| 453 | | m_DACOutput += DeltaTable[CurDelta][m_OldDelta]; // send data to forward delta table and add result to accumulator |
| 454 | | m_OldDelta = CurDelta; // Move current delta to old |
| 455 | | m_PhoneOffset++; // increment phone offset |
| 456 | | if (m_PhoneOffset == 0x8) // if we're now done this phone |
| 457 | | { |
| 458 | | /* call the PostPhoneme Function */ |
| 459 | | PostPhoneme(); |
| 460 | | } |
| 461 | | else |
| 462 | | { |
| 463 | | m_nextstate = 5; |
| 464 | | } |
| 465 | | break; |
| 466 | | case 9: // Play phone backward, shift = 6 (also load) |
| 467 | | CurDelta = (readmem((m_PhoneAddress)+m_PhoneOffset)&0x3); // grab current delta from low 2 bits of low nybble |
| 468 | | if (m_laststate != 8) // ignore first (bogus) dac change in mirrored backwards mode. observations and the patent show this. |
| 469 | | { |
| 470 | | m_DACOutput -= DeltaTable[m_OldDelta][CurDelta]; // send data to forward delta table and subtract result from accumulator |
| 471 | | } |
| 472 | | m_OldDelta = CurDelta; // Move current delta to old |
| 473 | | m_nextstate = 10; |
| 474 | | break; |
| 475 | | case 10: // Play phone backward, shift = 4 |
| 476 | | CurDelta = (readmem((m_PhoneAddress)+m_PhoneOffset)&0xc)>>2; // grab current delta from high 2 bits of low nybble |
| 477 | | m_DACOutput -= DeltaTable[m_OldDelta][CurDelta]; // send data to forward delta table and subtract result from accumulator |
| 478 | | m_OldDelta = CurDelta; // Move current delta to old |
| 479 | | m_nextstate = 11; |
| 480 | | break; |
| 481 | | case 11: // Play phone backward, shift = 2 |
| 482 | | CurDelta = (readmem((m_PhoneAddress)+m_PhoneOffset)&0x30)>>4; // grab current delta from low 2 bits of high nybble |
| 483 | | m_DACOutput -= DeltaTable[m_OldDelta][CurDelta]; // send data to forward delta table and subtract result from accumulator |
| 484 | | m_OldDelta = CurDelta; // Move current delta to old |
| 485 | | m_nextstate = 12; |
| 486 | | break; |
| 487 | | case 12: // Play phone backward, shift = 0 (increment address if needed) |
| 488 | | CurDelta = (readmem((m_PhoneAddress)+m_PhoneOffset)&0xc0)>>6; // grab current delta from high 2 bits of high nybble |
| 489 | | m_DACOutput -= DeltaTable[m_OldDelta][CurDelta]; // send data to forward delta table and subtract result from accumulator |
| 490 | | m_OldDelta = CurDelta; // Move current delta to old |
| 491 | | m_PhoneOffset--; // decrement phone offset |
| 492 | | if (m_PhoneOffset == 0xFF) // if we're now done this phone |
| 493 | | { |
| 494 | | /* call the PostPhoneme() function */ |
| 495 | | PostPhoneme(); |
| 496 | | } |
| 497 | | else |
| 498 | | { |
| 499 | | m_nextstate = 9; |
| 500 | | } |
| 501 | | break; |
| 502 | | case 13: // For those pedantic among us, consume an extra two clocks like the real chip does. |
| 503 | | m_nextstate = 0; |
| 504 | | break; |
| 457 | m_uStateP1 = DARMSB; |
| 458 | m_RomAddrP1 = m_uCWARP1; // output correct address |
| 505 | 459 | } |
| 460 | else m_uStateP1 = PLAY; |
| 461 | break; |
| 462 | } |
| 506 | 463 | |
| 507 | | /* the dac is 4 bits wide. if a delta step forced it outside of 4 bits, mask it back over here */ |
| 508 | | m_DACOutput &= 0xF; |
| 509 | | |
| 510 | | #ifdef DEBUGSTATE |
| 511 | | fprintf(stderr, "Machine state is now %d, was %d, PhoneOffset is %d\n", m_nextstate, m_machineState, m_PhoneOffset); |
| 512 | | #endif |
| 513 | | m_laststate = m_machineState; |
| 514 | | m_machineState = m_nextstate; |
| 515 | | |
| 516 | | if (bool(m_laststate) != bool(m_machineState) && !m_bsy_handler.isnull()) |
| 517 | | m_bsy_handler((m_machineState) ? 1 : 0); |
| 464 | case DELAY: |
| 465 | m_uOutputP1 = 7; |
| 466 | if (m_bStart) m_uStateP1 = WORDWAIT; |
| 467 | else m_uStateP1 = IDLE; |
| 468 | break; |
| 518 | 469 | } |
| 519 | | } |
| 520 | 470 | |
| 521 | | UINT8 s14001a_device::readmem(UINT16 offset) |
| 522 | | { |
| 523 | | offset &= 0xfff; // 11-bit internal |
| 524 | | return ((m_ext_read_handler.isnull()) ? m_SpeechRom[offset & (m_SpeechRom.bytes() - 1)] : m_ext_read_handler(offset)); |
| 471 | return true; |
| 525 | 472 | } |
| 526 | 473 | |
| 527 | | |
| 528 | | /************************************************************************** |
| 529 | | MAME glue code |
| 530 | | **************************************************************************/ |
| 531 | | |
| 532 | | void s14001a_device::force_update() |
| 474 | UINT8 s14001a_device::Mux8To2(bool bVoicedP2, UINT8 uPPQtrP2, UINT8 uDeltaAdrP2, UINT8 uRomDataP2) |
| 533 | 475 | { |
| 534 | | m_stream->update(); |
| 535 | | } |
| 476 | // pick two bits of rom data as delta |
| 536 | 477 | |
| 537 | | int s14001a_device::bsy_r() |
| 538 | | { |
| 539 | | m_stream->update(); |
| 540 | | #ifdef DEBUGSTATE |
| 541 | | fprintf(stderr,"busy state checked: %d\n",(m_machineState != 0) ); |
| 542 | | #endif |
| 543 | | return (m_machineState != 0); |
| 478 | if (bVoicedP2 && uPPQtrP2&0x01) // mirroring |
| 479 | { |
| 480 | uDeltaAdrP2 ^= 0x03; // count backwards |
| 481 | } |
| 482 | // emulate 8 to 2 mux to obtain delta from byte (bigendian) |
| 483 | switch (uDeltaAdrP2) |
| 484 | { |
| 485 | case 0x00: |
| 486 | return (uRomDataP2&0xC0)>>6; |
| 487 | case 0x01: |
| 488 | return (uRomDataP2&0x30)>>4; |
| 489 | case 0x02: |
| 490 | return (uRomDataP2&0x0C)>>2; |
| 491 | case 0x03: |
| 492 | return (uRomDataP2&0x03)>>0; |
| 493 | } |
| 494 | return 0xFF; |
| 544 | 495 | } |
| 545 | 496 | |
| 546 | | void s14001a_device::reg_w(int data) |
| 497 | void s14001a_device::CalculateIncrement(bool bVoicedP2, UINT8 uPPQtrP2, bool bPPQStartP2, UINT8 uDelta, UINT8 uDeltaOldP2, UINT8 &uDeltaOldP1, UINT8 &uIncrementP2, bool &bAddP2) |
| 547 | 498 | { |
| 548 | | m_stream->update(); |
| 549 | | m_WordInput = data; |
| 550 | | } |
| 499 | // uPPQtr, pitch period quarter counter; 2 lsb of uLength |
| 500 | // bPPStart, start of a pitch period |
| 501 | // implemented to mimic silicon (a bit) |
| 551 | 502 | |
| 552 | | void s14001a_device::rst_w(int data) |
| 553 | | { |
| 554 | | m_stream->update(); |
| 555 | | m_LatchedWord = m_WordInput; |
| 556 | | m_resetState = (data==1); |
| 557 | | m_machineState = m_resetState ? 1 : m_machineState; |
| 558 | | } |
| 503 | // beginning of a pitch period |
| 504 | if (uPPQtrP2 == 0x00 && bPPQStartP2) // note this is done for voiced and unvoiced |
| 505 | { |
| 506 | uDeltaOldP2 = 0x02; |
| 507 | } |
| 508 | static const UINT8 uIncrements[4][4] = |
| 509 | { |
| 510 | // 00 01 10 11 |
| 511 | { 3, 3, 1, 1,}, // 00 |
| 512 | { 1, 1, 0, 0,}, // 01 |
| 513 | { 0, 0, 1, 1,}, // 10 |
| 514 | { 1, 1, 3, 3 }, // 11 |
| 515 | }; |
| 559 | 516 | |
| 560 | | void s14001a_device::set_clock(int clock) |
| 561 | | { |
| 562 | | m_stream->set_sample_rate(clock); |
| 563 | | } |
| 517 | #define MIRROR (uPPQtrP2&0x01) |
| 564 | 518 | |
| 565 | | void s14001a_device::set_volume(int volume) |
| 566 | | { |
| 567 | | m_stream->update(); |
| 568 | | m_VSU1000_amp = volume; |
| 519 | // calculate increment from delta, always done even if silent to update uDeltaOld |
| 520 | // in silicon a PLA determined 0,1,3 and add/subtract and passed uDelta to uDeltaOld |
| 521 | if (!bVoicedP2 || !MIRROR) |
| 522 | { |
| 523 | uIncrementP2 = uIncrements[uDelta][uDeltaOldP2]; |
| 524 | bAddP2 = uDelta >= 0x02; |
| 525 | } |
| 526 | else |
| 527 | { |
| 528 | uIncrementP2 = uIncrements[uDeltaOldP2][uDelta]; |
| 529 | bAddP2 = uDeltaOldP2 < 0x02; |
| 530 | } |
| 531 | uDeltaOldP1 = uDelta; |
| 532 | if (bVoicedP2 && bPPQStartP2 && MIRROR) uIncrementP2 = 0; // no change when first starting mirroring |
| 569 | 533 | } |
| 570 | 534 | |
| 571 | | const device_type S14001A = &device_creator<s14001a_device>; |
| 572 | | |
| 573 | | s14001a_device::s14001a_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) |
| 574 | | : device_t(mconfig, S14001A, "S14001A", tag, owner, clock, "s14001a", __FILE__), |
| 575 | | device_sound_interface(mconfig, *this), |
| 576 | | m_SpeechRom(*this, DEVICE_SELF), |
| 577 | | m_stream(nullptr), |
| 578 | | m_bsy_handler(*this), |
| 579 | | m_ext_read_handler(*this) |
| 535 | UINT8 s14001a_device::CalculateOutput(bool bVoiced, bool bXSilence, UINT8 uPPQtr, bool bPPQStart, UINT8 uLOutput, UINT8 uIncrementP2, bool bAddP2) |
| 580 | 536 | { |
| 581 | | } |
| 537 | // implemented to mimic silicon (a bit) |
| 538 | // limits output to 0x00 and 0x0f |
| 539 | UINT8 uTmp; // used for subtraction |
| 582 | 540 | |
| 583 | | //------------------------------------------------- |
| 584 | | // device_start - device-specific startup |
| 585 | | //------------------------------------------------- |
| 541 | #define SILENCE (uPPQtr&0x02) |
| 586 | 542 | |
| 587 | | void s14001a_device::device_start() |
| 588 | | { |
| 589 | | m_GlobalSilenceState = 1; |
| 590 | | m_OldDelta = 0x02; |
| 591 | | m_DACOutput = SILENCE; |
| 592 | | m_VSU1000_amp = 15; |
| 543 | // determine output |
| 544 | if (bXSilence || (bVoiced && SILENCE)) return 7; |
| 593 | 545 | |
| 594 | | for (int i = 0; i < 8; i++) |
| 546 | // beginning of a pitch period |
| 547 | if (uPPQtr == 0x00 && bPPQStart) // note this is done for voiced and nonvoiced |
| 595 | 548 | { |
| 596 | | m_filtervals[i] = SILENCE; |
| 549 | uLOutput = 7; |
| 597 | 550 | } |
| 598 | 551 | |
| 599 | | m_stream = machine().sound().stream_alloc(*this, 0, 1, clock() ? clock() : machine().sample_rate()); |
| 552 | // adder |
| 553 | uTmp = uLOutput; |
| 554 | if (!bAddP2) uTmp ^= 0x0F; // turns subtraction into addition |
| 600 | 555 | |
| 601 | | // resolve callbacks |
| 602 | | m_ext_read_handler.resolve(); |
| 603 | | m_bsy_handler.resolve(); |
| 556 | // add 0, 1, 3; limit at 15 |
| 557 | uTmp += uIncrementP2; |
| 558 | if (uTmp > 15) uTmp = 15; |
| 604 | 559 | |
| 605 | | save_item(NAME(m_WordInput)); |
| 606 | | save_item(NAME(m_LatchedWord)); |
| 607 | | save_item(NAME(m_SyllableAddress)); |
| 608 | | save_item(NAME(m_PhoneAddress)); |
| 609 | | save_item(NAME(m_PlayParams)); |
| 610 | | save_item(NAME(m_PhoneOffset)); |
| 611 | | save_item(NAME(m_LengthCounter)); |
| 612 | | save_item(NAME(m_RepeatCounter)); |
| 613 | | save_item(NAME(m_OutputCounter)); |
| 614 | | save_item(NAME(m_machineState)); |
| 615 | | save_item(NAME(m_nextstate)); |
| 616 | | save_item(NAME(m_laststate)); |
| 617 | | save_item(NAME(m_resetState)); |
| 618 | | save_item(NAME(m_oddeven)); |
| 619 | | save_item(NAME(m_GlobalSilenceState)); |
| 620 | | save_item(NAME(m_OldDelta)); |
| 621 | | save_item(NAME(m_DACOutput)); |
| 622 | | save_item(NAME(m_audioout)); |
| 623 | | save_item(NAME(m_filtervals)); |
| 624 | | save_item(NAME(m_VSU1000_amp)); |
| 560 | if (!bAddP2) uTmp ^= 0x0F; // turns addition back to subtraction |
| 561 | return uTmp; |
| 625 | 562 | } |
| 626 | 563 | |
| 627 | | //------------------------------------------------- |
| 628 | | // sound_stream_update - handle a stream update |
| 629 | | //------------------------------------------------- |
| 630 | | |
| 631 | | void s14001a_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) |
| 564 | void s14001a_device::ClearStatistics() |
| 632 | 565 | { |
| 633 | | int i; |
| 566 | m_uNPitchPeriods = 0; |
| 567 | m_uNVoiced = 0; |
| 568 | m_uPrintLevel = 0; |
| 569 | m_uNControlWords = 0; |
| 570 | } |
| 634 | 571 | |
| 635 | | for (i = 0; i < samples; i++) |
| 636 | | { |
| 637 | | s14001a_clock(); |
| 638 | | #ifdef ACCURATE_SQUEAL |
| 639 | | if (m_audioout == ALTFLAG) // input from test pins -> output |
| 640 | | { |
| 641 | | shiftIntoFilter(chip, audiofilter(chip)); // shift over the previous outputs and stick in audioout. |
| 642 | | outputs[0][i] = audiofilter(chip)*m_VSU1000_amp; |
| 643 | | } |
| 644 | | else // normal, dac-driven output |
| 645 | | { |
| 646 | | shiftIntoFilter(chip, ((((INT16)m_audioout)-8)<<9)); // shift over the previous outputs and stick in audioout 4 times. note <<9 instead of <<10, to prevent clipping, and to simulate that the filtered output normally has a somewhat lower amplitude than the driven one. |
| 647 | | #endif |
| 648 | | outputs[0][i] = ((((INT16)m_audioout)-8)<<10)*m_VSU1000_amp; |
| 649 | | #ifdef ACCURATE_SQUEAL |
| 650 | | } |
| 651 | | #endif |
| 652 | | } |
| 572 | void s14001a_device::GetStatistics(UINT32 &uNPitchPeriods, UINT32 &uNVoiced, UINT32 uNControlWords) |
| 573 | { |
| 574 | uNPitchPeriods = m_uNPitchPeriods; |
| 575 | uNVoiced = m_uNVoiced; |
| 576 | uNControlWords = m_uNControlWords; |
| 653 | 577 | } |
trunk/src/devices/sound/s14001a_new.cpp
| r252893 | r252894 | |
| 1 | | // license:BSD-3-Clause |
| 2 | | // copyright-holders:Ed Bernard, Jonathan Gevaryahu, hap |
| 3 | | // thanks-to:Kevin Horton |
| 4 | | /* |
| 5 | | SSi TSI S14001A speech IC emulator |
| 6 | | aka CRC: Custom ROM Controller, designed in 1975, first usage in 1976 on TSI Speech+ calculator |
| 7 | | Originally written for MAME by Jonathan Gevaryahu(Lord Nightmare) 2006-2013, |
| 8 | | replaced with near-complete rewrite by Ed Bernard in 2016 |
| 9 | | |
| 10 | | TODO: |
| 11 | | - nothing at the moment? |
| 12 | | |
| 13 | | Further reading: |
| 14 | | - http://www.vintagecalculators.com/html/speech-.html |
| 15 | | - http://www.vintagecalculators.com/html/development_of_the_tsi_speech-.html |
| 16 | | - http://www.vintagecalculators.com/html/speech-_state_machine.html |
| 17 | | - https://archive.org/stream/pdfy-QPCSwTWiFz1u9WU_/david_djvu.txt |
| 18 | | */ |
| 19 | | |
| 20 | | /* Chip Pinout: |
| 21 | | The original datasheet (which is lost as far as I know) clearly called the |
| 22 | | s14001a chip the 'CRC chip', or 'Custom Rom Controller', as it appears with |
| 23 | | this name on the Stern and Canon schematics, as well as on some TSI speech |
| 24 | | print advertisements. |
| 25 | | Labels are not based on the labels used by the Atari wolf pack and Stern |
| 26 | | schematics, as these are inconsistent. Atari calls the word select/speech address |
| 27 | | input pins SAx while Stern calls them Cx. Also Atari and Canon both have the bit |
| 28 | | ordering for the word select/speech address bus backwards, which may indicate it |
| 29 | | was so on the original datasheet. Stern has it correct, and I've used their Cx |
| 30 | | labeling. |
| 31 | | |
| 32 | | ______ ______ |
| 33 | | _|o \__/ |_ |
| 34 | | +5V -- |_|1 40|_| -> /BUSY* |
| 35 | | _| |_ |
| 36 | | ?TEST ?? |_|2 39|_| <- ROM D7 |
| 37 | | _| |_ |
| 38 | | XTAL CLOCK/CKC -> |_|3 38|_| -> ROM A11 |
| 39 | | _| |_ |
| 40 | | ROM CLOCK/CKR <- |_|4 37|_| <- ROM D6 |
| 41 | | _| |_ |
| 42 | | DIGITAL OUT 0 <- |_|5 36|_| -> ROM A10 |
| 43 | | _| |_ |
| 44 | | DIGITAL OUT 1 <- |_|6 35|_| -> ROM A9 |
| 45 | | _| |_ |
| 46 | | DIGITAL OUT 2 <- |_|7 34|_| <- ROM D5 |
| 47 | | _| |_ |
| 48 | | DIGITAL OUT 3 <- |_|8 33|_| -> ROM A8 |
| 49 | | _| |_ |
| 50 | | ROM /EN <- |_|9 32|_| <- ROM D4 |
| 51 | | _| S |_ |
| 52 | | START -> |_|10 7 1 T 31|_| -> ROM A7 |
| 53 | | _| 7 4 S |_ |
| 54 | | AUDIO OUT <- |_|11 3 0 I 30|_| <- ROM D3 |
| 55 | | _| 7 0 |_ |
| 56 | | ROM A0 <- |_|12 1 29|_| -> ROM A6 |
| 57 | | _| A |_ |
| 58 | | SPCH ADR BUS C0 -> |_|13 28|_| <- SPCH ADR BUS C5 |
| 59 | | _| |_ |
| 60 | | ROM A1 <- |_|14 27|_| <- ROM D2 |
| 61 | | _| |_ |
| 62 | | SPCH ADR BUS C1 -> |_|15 26|_| <- SPCH ADR BUS C4 |
| 63 | | _| |_ |
| 64 | | ROM A2 <- |_|16 25|_| <- ROM D1 |
| 65 | | _| |_ |
| 66 | | SPCH ADR BUS C2 -> |_|17 24|_| <- SPCH ADR BUS C3 |
| 67 | | _| |_ |
| 68 | | ROM A3 <- |_|18 23|_| <- ROM D0 |
| 69 | | _| |_ |
| 70 | | ROM A4 <- |_|19 22|_| -> ROM A5 |
| 71 | | _| |_ |
| 72 | | GND -- |_|20 21|_| -- -10V |
| 73 | | |________________| |
| 74 | | |
| 75 | | *Note from Kevin Horton when testing the hookup of the S14001A: the /BUSY line |
| 76 | | is not a standard voltage line: when it is in its HIGH state (i.e. not busy) it |
| 77 | | puts out a voltage of -10 volts, so it needs to be dropped back to a sane |
| 78 | | voltage level before it can be passed to any sort of modern IC. The address |
| 79 | | lines for the speech rom (A0-A11) do not have this problem, they output at a |
| 80 | | TTL/CMOS compatible voltage. The AUDIO OUT pin also outputs a voltage below GND, |
| 81 | | and the TEST pins may do so too. |
| 82 | | |
| 83 | | START is pulled high when a word is to be said and the word number is on the |
| 84 | | word select/speech address input lines. The Canon 'Canola' uses a separate 'rom |
| 85 | | strobe' signal independent of the chip to either enable or clock the speech rom. |
| 86 | | It's likely that they did this to be able to force the speech chip to stop talking, |
| 87 | | which is normally impossible. The later 'version 3' TSI speech board as featured in |
| 88 | | an advertisement in the John Cater book probably also has this feature, in addition |
| 89 | | to external speech rom banking. |
| 90 | | |
| 91 | | The Digital out pins supply a copy of the 4-bit waveform which also goes to the |
| 92 | | internal DAC. They are only valid every other clock cycle. It is possible that |
| 93 | | on 'invalid' cycles they act as a 4 bit input to drive the dac. |
| 94 | | |
| 95 | | Because it requires -10V to operate, the chip manufacturing process must be PMOS. |
| 96 | | |
| 97 | | * Operation: |
| 98 | | Put the 6-bit address of the word to be said onto the C0-C5 word select/speech |
| 99 | | address bus lines. Next, clock the START line low-high-low. As long as the START |
| 100 | | line is held high, the first address byte of the first word will be read repeatedly |
| 101 | | every clock, with the rom enable line enabled constantly (i.e. it doesn't toggle on |
| 102 | | and off as it normally does during speech). Once START has gone low-high-low, the |
| 103 | | /BUSY line will go low until 3 clocks after the chip is done speaking. |
| 104 | | */ |
| 105 | | |
| 106 | | #include "emu.h" |
| 107 | | #include "s14001a_new.h" |
| 108 | | |
| 109 | | // device definition |
| 110 | | const device_type S14001A_NEW = &device_creator<s14001a_new_device>; |
| 111 | | |
| 112 | | s14001a_new_device::s14001a_new_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) |
| 113 | | : device_t(mconfig, S14001A_NEW, "S14001A_NEW", tag, owner, clock, "s14001a_new", __FILE__), |
| 114 | | device_sound_interface(mconfig, *this), |
| 115 | | m_SpeechRom(*this, DEVICE_SELF), |
| 116 | | m_stream(nullptr), |
| 117 | | m_bsy_handler(*this), |
| 118 | | m_ext_read_handler(*this) |
| 119 | | { |
| 120 | | } |
| 121 | | |
| 122 | | //------------------------------------------------- |
| 123 | | // device_start - device-specific startup |
| 124 | | //------------------------------------------------- |
| 125 | | |
| 126 | | ALLOW_SAVE_TYPE(s14001a_new_device::states); // allow save_item on a non-fundamental type |
| 127 | | |
| 128 | | void s14001a_new_device::device_start() |
| 129 | | { |
| 130 | | m_stream = machine().sound().stream_alloc(*this, 0, 1, clock() ? clock() : machine().sample_rate()); |
| 131 | | |
| 132 | | // resolve callbacks |
| 133 | | m_ext_read_handler.resolve(); |
| 134 | | m_bsy_handler.resolve(); |
| 135 | | |
| 136 | | // note: zerofill is done already by MAME core |
| 137 | | ClearStatistics(); |
| 138 | | m_uOutputP1 = m_uOutputP2 = 7; |
| 139 | | |
| 140 | | // register for savestates |
| 141 | | save_item(NAME(m_bPhase1)); |
| 142 | | save_item(NAME(m_uStateP1)); |
| 143 | | save_item(NAME(m_uStateP2)); |
| 144 | | save_item(NAME(m_uDAR13To05P1)); |
| 145 | | save_item(NAME(m_uDAR13To05P2)); |
| 146 | | save_item(NAME(m_uDAR04To00P1)); |
| 147 | | save_item(NAME(m_uDAR04To00P2)); |
| 148 | | save_item(NAME(m_uCWARP1)); |
| 149 | | save_item(NAME(m_uCWARP2)); |
| 150 | | |
| 151 | | save_item(NAME(m_bStopP1)); |
| 152 | | save_item(NAME(m_bStopP2)); |
| 153 | | save_item(NAME(m_bVoicedP1)); |
| 154 | | save_item(NAME(m_bVoicedP2)); |
| 155 | | save_item(NAME(m_bSilenceP1)); |
| 156 | | save_item(NAME(m_bSilenceP2)); |
| 157 | | save_item(NAME(m_uLengthP1)); |
| 158 | | save_item(NAME(m_uLengthP2)); |
| 159 | | save_item(NAME(m_uXRepeatP1)); |
| 160 | | save_item(NAME(m_uXRepeatP2)); |
| 161 | | save_item(NAME(m_uDeltaOldP1)); |
| 162 | | save_item(NAME(m_uDeltaOldP2)); |
| 163 | | save_item(NAME(m_uOutputP1)); |
| 164 | | |
| 165 | | save_item(NAME(m_bDAR04To00CarryP2)); |
| 166 | | save_item(NAME(m_bPPQCarryP2)); |
| 167 | | save_item(NAME(m_bRepeatCarryP2)); |
| 168 | | save_item(NAME(m_bLengthCarryP2)); |
| 169 | | save_item(NAME(m_RomAddrP1)); |
| 170 | | |
| 171 | | save_item(NAME(m_uOutputP2)); |
| 172 | | save_item(NAME(m_uRomAddrP2)); |
| 173 | | save_item(NAME(m_bBusyP1)); |
| 174 | | save_item(NAME(m_bStart)); |
| 175 | | save_item(NAME(m_uWord)); |
| 176 | | |
| 177 | | save_item(NAME(m_uNPitchPeriods)); |
| 178 | | save_item(NAME(m_uNVoiced)); |
| 179 | | save_item(NAME(m_uNControlWords)); |
| 180 | | save_item(NAME(m_uPrintLevel)); |
| 181 | | } |
| 182 | | |
| 183 | | |
| 184 | | //------------------------------------------------- |
| 185 | | // sound_stream_update - handle a stream update |
| 186 | | //------------------------------------------------- |
| 187 | | |
| 188 | | void s14001a_new_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) |
| 189 | | { |
| 190 | | for (int i = 0; i < samples; i++) |
| 191 | | { |
| 192 | | Clock(); |
| 193 | | INT16 sample = m_uOutputP2 - 7; // range -7..8 |
| 194 | | outputs[0][i] = sample * 0xf00; |
| 195 | | } |
| 196 | | } |
| 197 | | |
| 198 | | |
| 199 | | /************************************************************************** |
| 200 | | External interface |
| 201 | | **************************************************************************/ |
| 202 | | |
| 203 | | void s14001a_new_device::force_update() |
| 204 | | { |
| 205 | | m_stream->update(); |
| 206 | | } |
| 207 | | |
| 208 | | READ_LINE_MEMBER(s14001a_new_device::romen_r) |
| 209 | | { |
| 210 | | m_stream->update(); |
| 211 | | return (m_bPhase1) ? 1 : 0; |
| 212 | | } |
| 213 | | |
| 214 | | READ_LINE_MEMBER(s14001a_new_device::busy_r) |
| 215 | | { |
| 216 | | m_stream->update(); |
| 217 | | return (m_bBusyP1) ? 1 : 0; |
| 218 | | } |
| 219 | | |
| 220 | | WRITE8_MEMBER(s14001a_new_device::data_w) |
| 221 | | { |
| 222 | | m_stream->update(); |
| 223 | | m_uWord = data & 0x3f; // C0-C5 |
| 224 | | } |
| 225 | | |
| 226 | | WRITE_LINE_MEMBER(s14001a_new_device::start_w) |
| 227 | | { |
| 228 | | m_stream->update(); |
| 229 | | m_bStart = (state != 0); |
| 230 | | if (m_bStart) m_uStateP1 = WORDWAIT; |
| 231 | | } |
| 232 | | |
| 233 | | void s14001a_new_device::set_clock(UINT32 clock) |
| 234 | | { |
| 235 | | m_stream->update(); |
| 236 | | m_stream->set_sample_rate(clock); |
| 237 | | } |
| 238 | | |
| 239 | | |
| 240 | | /************************************************************************** |
| 241 | | Device emulation |
| 242 | | **************************************************************************/ |
| 243 | | |
| 244 | | UINT8 s14001a_new_device::readmem(UINT16 offset, bool phase) |
| 245 | | { |
| 246 | | offset &= 0xfff; // 11-bit internal |
| 247 | | return ((m_ext_read_handler.isnull()) ? m_SpeechRom[offset & (m_SpeechRom.bytes() - 1)] : m_ext_read_handler(offset)); |
| 248 | | } |
| 249 | | |
| 250 | | bool s14001a_new_device::Clock() |
| 251 | | { |
| 252 | | // effectively toggles external clock twice, one cycle |
| 253 | | // internal clock toggles on external clock transition from 0 to 1 so internal clock will always transition here |
| 254 | | // return false if some emulator problem detected |
| 255 | | |
| 256 | | // On the actual chip, all register phase 1 values needed to be refreshed from phase 2 values |
| 257 | | // or else risk losing their state due to charge loss. |
| 258 | | // But on a computer the values are static. |
| 259 | | // So to reduce code clutter, phase 1 values are only modified if they are different |
| 260 | | // from the preceeding phase 2 values. |
| 261 | | |
| 262 | | if (m_bPhase1) |
| 263 | | { |
| 264 | | // transition to phase2 |
| 265 | | m_bPhase1 = false; |
| 266 | | |
| 267 | | // transfer phase1 variables to phase2 |
| 268 | | m_uStateP2 = m_uStateP1; |
| 269 | | m_uDAR13To05P2 = m_uDAR13To05P1; |
| 270 | | m_uDAR04To00P2 = m_uDAR04To00P1; |
| 271 | | m_uCWARP2 = m_uCWARP1; |
| 272 | | m_bStopP2 = m_bStopP1; |
| 273 | | m_bVoicedP2 = m_bVoicedP1; |
| 274 | | m_bSilenceP2 = m_bSilenceP1; |
| 275 | | m_uLengthP2 = m_uLengthP1; |
| 276 | | m_uXRepeatP2 = m_uXRepeatP1; |
| 277 | | m_uDeltaOldP2 = m_uDeltaOldP1; |
| 278 | | |
| 279 | | m_uOutputP2 = m_uOutputP1; |
| 280 | | m_uRomAddrP2 = m_RomAddrP1; |
| 281 | | |
| 282 | | // setup carries from phase 2 values |
| 283 | | m_bDAR04To00CarryP2 = m_uDAR04To00P2 == 0x1F; |
| 284 | | m_bPPQCarryP2 = m_bDAR04To00CarryP2 && ((m_uLengthP2&0x03) == 0x03); // pitch period quarter |
| 285 | | m_bRepeatCarryP2 = m_bPPQCarryP2 && ((m_uLengthP2&0x0C) == 0x0C); |
| 286 | | m_bLengthCarryP2 = m_bRepeatCarryP2 && ( m_uLengthP2 == 0x7F); |
| 287 | | |
| 288 | | return true; |
| 289 | | } |
| 290 | | m_bPhase1 = true; |
| 291 | | |
| 292 | | // logic done during phase 1 |
| 293 | | switch (m_uStateP1) |
| 294 | | { |
| 295 | | case IDLE: |
| 296 | | m_uOutputP1 = 7; |
| 297 | | if (m_bStart) m_uStateP1 = WORDWAIT; |
| 298 | | |
| 299 | | if (m_bBusyP1 && !m_bsy_handler.isnull()) |
| 300 | | m_bsy_handler(0); |
| 301 | | m_bBusyP1 = false; |
| 302 | | break; |
| 303 | | |
| 304 | | case WORDWAIT: |
| 305 | | // the delta address register latches the word number into bits 03 to 08 |
| 306 | | // all other bits forced to 0. 04 to 08 makes a multiply by two. |
| 307 | | m_uDAR13To05P1 = (m_uWord&0x3C)>>2; |
| 308 | | m_uDAR04To00P1 = (m_uWord&0x03)<<3; |
| 309 | | m_RomAddrP1 = (m_uDAR13To05P1<<3)|(m_uDAR04To00P1>>2); // remove lower two bits |
| 310 | | m_uOutputP1 = 7; |
| 311 | | if (m_bStart) m_uStateP1 = WORDWAIT; |
| 312 | | else m_uStateP1 = CWARMSB; |
| 313 | | |
| 314 | | if (!m_bBusyP1 && !m_bsy_handler.isnull()) |
| 315 | | m_bsy_handler(1); |
| 316 | | m_bBusyP1 = true; |
| 317 | | break; |
| 318 | | |
| 319 | | case CWARMSB: |
| 320 | | if (m_uPrintLevel >= 1) |
| 321 | | printf("\n speaking word %02x",m_uWord); |
| 322 | | |
| 323 | | // use uDAR to load uCWAR 8 msb |
| 324 | | m_uCWARP1 = readmem(m_uRomAddrP2,m_bPhase1)<<4; // note use of rom address setup in previous state |
| 325 | | // increment DAR by 4, 2 lsb's count deltas within a byte |
| 326 | | m_uDAR04To00P1 += 4; |
| 327 | | if (m_uDAR04To00P1 >= 32) m_uDAR04To00P1 = 0; // emulate 5 bit counter |
| 328 | | m_RomAddrP1 = (m_uDAR13To05P1<<3)|(m_uDAR04To00P1>>2); // remove lower two bits |
| 329 | | |
| 330 | | m_uOutputP1 = 7; |
| 331 | | if (m_bStart) m_uStateP1 = WORDWAIT; |
| 332 | | else m_uStateP1 = CWARLSB; |
| 333 | | break; |
| 334 | | |
| 335 | | case CWARLSB: |
| 336 | | m_uCWARP1 = m_uCWARP2|(readmem(m_uRomAddrP2,m_bPhase1)>>4); // setup in previous state |
| 337 | | m_RomAddrP1 = m_uCWARP1; |
| 338 | | |
| 339 | | m_uOutputP1 = 7; |
| 340 | | if (m_bStart) m_uStateP1 = WORDWAIT; |
| 341 | | else m_uStateP1 = DARMSB; |
| 342 | | break; |
| 343 | | |
| 344 | | case DARMSB: |
| 345 | | m_uDAR13To05P1 = readmem(m_uRomAddrP2,m_bPhase1)<<1; // 9 bit counter, 8 MSBs from ROM, lsb zeroed |
| 346 | | m_uDAR04To00P1 = 0; |
| 347 | | m_uCWARP1++; |
| 348 | | m_RomAddrP1 = m_uCWARP1; |
| 349 | | m_uNControlWords++; // statistics |
| 350 | | |
| 351 | | m_uOutputP1 = 7; |
| 352 | | if (m_bStart) m_uStateP1 = WORDWAIT; |
| 353 | | else m_uStateP1 = CTRLBITS; |
| 354 | | break; |
| 355 | | |
| 356 | | case CTRLBITS: |
| 357 | | m_bStopP1 = readmem(m_uRomAddrP2,m_bPhase1)&0x80? true: false; |
| 358 | | m_bVoicedP1 = readmem(m_uRomAddrP2,m_bPhase1)&0x40? true: false; |
| 359 | | m_bSilenceP1 = readmem(m_uRomAddrP2,m_bPhase1)&0x20? true: false; |
| 360 | | m_uXRepeatP1 = readmem(m_uRomAddrP2,m_bPhase1)&0x03; |
| 361 | | m_uLengthP1 =(readmem(m_uRomAddrP2,m_bPhase1)&0x1F)<<2; // includes external length and repeat |
| 362 | | m_uDAR04To00P1 = 0; |
| 363 | | m_uCWARP1++; // gets ready for next DARMSB |
| 364 | | m_RomAddrP1 = (m_uDAR13To05P1<<3)|(m_uDAR04To00P1>>2); // remove lower two bits |
| 365 | | |
| 366 | | m_uOutputP1 = 7; |
| 367 | | if (m_bStart) m_uStateP1 = WORDWAIT; |
| 368 | | else m_uStateP1 = PLAY; |
| 369 | | |
| 370 | | if (m_uPrintLevel >= 2) |
| 371 | | printf("\n cw %d %d %d %d %d",m_bStopP1,m_bVoicedP1,m_bSilenceP1,m_uLengthP1>>4,m_uXRepeatP1); |
| 372 | | |
| 373 | | break; |
| 374 | | |
| 375 | | case PLAY: |
| 376 | | { |
| 377 | | // statistics |
| 378 | | if (m_bPPQCarryP2) |
| 379 | | { |
| 380 | | // pitch period end |
| 381 | | if (m_uPrintLevel >= 3) |
| 382 | | printf("\n ppe: RomAddr %03x",m_uRomAddrP2); |
| 383 | | |
| 384 | | m_uNPitchPeriods++; |
| 385 | | if (m_bVoicedP2) m_uNVoiced++; |
| 386 | | } |
| 387 | | // end statistics |
| 388 | | |
| 389 | | // modify output |
| 390 | | UINT8 uDeltaP2; // signal line |
| 391 | | UINT8 uIncrementP2; // signal lines |
| 392 | | bool bAddP2; // signal line |
| 393 | | uDeltaP2 = Mux8To2(m_bVoicedP2, |
| 394 | | m_uLengthP2 & 0x03, // pitch period quater counter |
| 395 | | m_uDAR04To00P2 & 0x03, // two bit delta address within byte |
| 396 | | readmem(m_uRomAddrP2,m_bPhase1) |
| 397 | | ); |
| 398 | | CalculateIncrement(m_bVoicedP2, |
| 399 | | m_uLengthP2 & 0x03, // pitch period quater counter |
| 400 | | m_uDAR04To00P2 == 0, // pitch period quarter start |
| 401 | | uDeltaP2, |
| 402 | | m_uDeltaOldP2, // input |
| 403 | | m_uDeltaOldP1, // output |
| 404 | | uIncrementP2, // output 0, 1, or 3 |
| 405 | | bAddP2 // output |
| 406 | | ); |
| 407 | | m_uOutputP1 = CalculateOutput(m_bVoicedP2, |
| 408 | | m_bSilenceP2, |
| 409 | | m_uLengthP2 & 0x03, // pitch period quater counter |
| 410 | | m_uDAR04To00P2 == 0, // pitch period quarter start |
| 411 | | m_uOutputP2, // last output |
| 412 | | uIncrementP2, |
| 413 | | bAddP2 |
| 414 | | ); |
| 415 | | |
| 416 | | // advance counters |
| 417 | | m_uDAR04To00P1++; |
| 418 | | if (m_bDAR04To00CarryP2) // pitch period quarter end |
| 419 | | { |
| 420 | | m_uDAR04To00P1 = 0; // emulate 5 bit counter |
| 421 | | |
| 422 | | m_uLengthP1++; // lower two bits of length count quarter pitch periods |
| 423 | | if (m_uLengthP1 >= 0x80) |
| 424 | | { |
| 425 | | m_uLengthP1 = 0; // emulate 7 bit counter |
| 426 | | } |
| 427 | | } |
| 428 | | |
| 429 | | if (m_bVoicedP2 && m_bRepeatCarryP2) // repeat complete |
| 430 | | { |
| 431 | | m_uLengthP1 &= 0x70; // keep current "length" |
| 432 | | m_uLengthP1 |= (m_uXRepeatP1<<2); // load repeat from external repeat |
| 433 | | m_uDAR13To05P1++; // advances ROM address 8 bytes |
| 434 | | if (m_uDAR13To05P1 >= 0x200) m_uDAR13To05P1 = 0; // emulate 9 bit counter |
| 435 | | } |
| 436 | | if (!m_bVoicedP2 && m_bDAR04To00CarryP2) |
| 437 | | { |
| 438 | | // unvoiced advances each quarter pitch period |
| 439 | | // note repeat counter not reloaded for non voiced speech |
| 440 | | m_uDAR13To05P1++; // advances ROM address 8 bytes |
| 441 | | if (m_uDAR13To05P1 >= 0x200) m_uDAR13To05P1 = 0; // emulate 9 bit counter |
| 442 | | } |
| 443 | | |
| 444 | | // construct m_RomAddrP1 |
| 445 | | m_RomAddrP1 = m_uDAR04To00P1; |
| 446 | | if (m_bVoicedP2 && m_uLengthP1&0x1) // mirroring |
| 447 | | { |
| 448 | | m_RomAddrP1 ^= 0x1f; // count backwards |
| 449 | | } |
| 450 | | m_RomAddrP1 = (m_uDAR13To05P1<<3) | m_RomAddrP1>>2; |
| 451 | | |
| 452 | | // next state |
| 453 | | if (m_bStart) m_uStateP1 = WORDWAIT; |
| 454 | | else if (m_bStopP2 && m_bLengthCarryP2) m_uStateP1 = DELAY; |
| 455 | | else if (m_bLengthCarryP2) |
| 456 | | { |
| 457 | | m_uStateP1 = DARMSB; |
| 458 | | m_RomAddrP1 = m_uCWARP1; // output correct address |
| 459 | | } |
| 460 | | else m_uStateP1 = PLAY; |
| 461 | | break; |
| 462 | | } |
| 463 | | |
| 464 | | case DELAY: |
| 465 | | m_uOutputP1 = 7; |
| 466 | | if (m_bStart) m_uStateP1 = WORDWAIT; |
| 467 | | else m_uStateP1 = IDLE; |
| 468 | | break; |
| 469 | | } |
| 470 | | |
| 471 | | return true; |
| 472 | | } |
| 473 | | |
| 474 | | UINT8 s14001a_new_device::Mux8To2(bool bVoicedP2, UINT8 uPPQtrP2, UINT8 uDeltaAdrP2, UINT8 uRomDataP2) |
| 475 | | { |
| 476 | | // pick two bits of rom data as delta |
| 477 | | |
| 478 | | if (bVoicedP2 && uPPQtrP2&0x01) // mirroring |
| 479 | | { |
| 480 | | uDeltaAdrP2 ^= 0x03; // count backwards |
| 481 | | } |
| 482 | | // emulate 8 to 2 mux to obtain delta from byte (bigendian) |
| 483 | | switch (uDeltaAdrP2) |
| 484 | | { |
| 485 | | case 0x00: |
| 486 | | return (uRomDataP2&0xC0)>>6; |
| 487 | | case 0x01: |
| 488 | | return (uRomDataP2&0x30)>>4; |
| 489 | | case 0x02: |
| 490 | | return (uRomDataP2&0x0C)>>2; |
| 491 | | case 0x03: |
| 492 | | return (uRomDataP2&0x03)>>0; |
| 493 | | } |
| 494 | | return 0xFF; |
| 495 | | } |
| 496 | | |
| 497 | | void s14001a_new_device::CalculateIncrement(bool bVoicedP2, UINT8 uPPQtrP2, bool bPPQStartP2, UINT8 uDelta, UINT8 uDeltaOldP2, UINT8 &uDeltaOldP1, UINT8 &uIncrementP2, bool &bAddP2) |
| 498 | | { |
| 499 | | // uPPQtr, pitch period quarter counter; 2 lsb of uLength |
| 500 | | // bPPStart, start of a pitch period |
| 501 | | // implemented to mimic silicon (a bit) |
| 502 | | |
| 503 | | // beginning of a pitch period |
| 504 | | if (uPPQtrP2 == 0x00 && bPPQStartP2) // note this is done for voiced and unvoiced |
| 505 | | { |
| 506 | | uDeltaOldP2 = 0x02; |
| 507 | | } |
| 508 | | static const UINT8 uIncrements[4][4] = |
| 509 | | { |
| 510 | | // 00 01 10 11 |
| 511 | | { 3, 3, 1, 1,}, // 00 |
| 512 | | { 1, 1, 0, 0,}, // 01 |
| 513 | | { 0, 0, 1, 1,}, // 10 |
| 514 | | { 1, 1, 3, 3 }, // 11 |
| 515 | | }; |
| 516 | | |
| 517 | | #define MIRROR (uPPQtrP2&0x01) |
| 518 | | |
| 519 | | // calculate increment from delta, always done even if silent to update uDeltaOld |
| 520 | | // in silicon a PLA determined 0,1,3 and add/subtract and passed uDelta to uDeltaOld |
| 521 | | if (!bVoicedP2 || !MIRROR) |
| 522 | | { |
| 523 | | uIncrementP2 = uIncrements[uDelta][uDeltaOldP2]; |
| 524 | | bAddP2 = uDelta >= 0x02; |
| 525 | | } |
| 526 | | else |
| 527 | | { |
| 528 | | uIncrementP2 = uIncrements[uDeltaOldP2][uDelta]; |
| 529 | | bAddP2 = uDeltaOldP2 < 0x02; |
| 530 | | } |
| 531 | | uDeltaOldP1 = uDelta; |
| 532 | | if (bVoicedP2 && bPPQStartP2 && MIRROR) uIncrementP2 = 0; // no change when first starting mirroring |
| 533 | | } |
| 534 | | |
| 535 | | UINT8 s14001a_new_device::CalculateOutput(bool bVoiced, bool bXSilence, UINT8 uPPQtr, bool bPPQStart, UINT8 uLOutput, UINT8 uIncrementP2, bool bAddP2) |
| 536 | | { |
| 537 | | // implemented to mimic silicon (a bit) |
| 538 | | // limits output to 0x00 and 0x0f |
| 539 | | UINT8 uTmp; // used for subtraction |
| 540 | | |
| 541 | | #define SILENCE (uPPQtr&0x02) |
| 542 | | |
| 543 | | // determine output |
| 544 | | if (bXSilence || (bVoiced && SILENCE)) return 7; |
| 545 | | |
| 546 | | // beginning of a pitch period |
| 547 | | if (uPPQtr == 0x00 && bPPQStart) // note this is done for voiced and nonvoiced |
| 548 | | { |
| 549 | | uLOutput = 7; |
| 550 | | } |
| 551 | | |
| 552 | | // adder |
| 553 | | uTmp = uLOutput; |
| 554 | | if (!bAddP2) uTmp ^= 0x0F; // turns subtraction into addition |
| 555 | | |
| 556 | | // add 0, 1, 3; limit at 15 |
| 557 | | uTmp += uIncrementP2; |
| 558 | | if (uTmp > 15) uTmp = 15; |
| 559 | | |
| 560 | | if (!bAddP2) uTmp ^= 0x0F; // turns addition back to subtraction |
| 561 | | return uTmp; |
| 562 | | } |
| 563 | | |
| 564 | | void s14001a_new_device::ClearStatistics() |
| 565 | | { |
| 566 | | m_uNPitchPeriods = 0; |
| 567 | | m_uNVoiced = 0; |
| 568 | | m_uPrintLevel = 0; |
| 569 | | m_uNControlWords = 0; |
| 570 | | } |
| 571 | | |
| 572 | | void s14001a_new_device::GetStatistics(UINT32 &uNPitchPeriods, UINT32 &uNVoiced, UINT32 uNControlWords) |
| 573 | | { |
| 574 | | uNPitchPeriods = m_uNPitchPeriods; |
| 575 | | uNVoiced = m_uNVoiced; |
| 576 | | uNControlWords = m_uNControlWords; |
| 577 | | } |