// license:BSD-3-Clause // copyright-holders:hap /*************************************************************************** tc8830f.c - Toshiba TC8830F, CMOS voice recording/reproducing LSI 1-bit ADM (Adaptive Delta Modulation), similar to TC8801 and T6668. Very preliminary... TODO: - improve ADM decoder - remaining commands - manual control - chip read - RAM - recording ***************************************************************************/ #include "emu.h" #include "tc8830f.h" // device type definition DEFINE_DEVICE_TYPE(TC8830F, tc8830f_device, "tc8830f", "Toshiba TC8830F") tc8830f_device::tc8830f_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, TC8830F, tag, owner, clock), device_sound_interface(mconfig, *this), m_stream(nullptr), m_playing(false), m_address(0), m_stop_address(0), m_bitcount(0), m_bitrate(0), m_prevbits(0), m_delta(1), m_output(0), m_command(0), m_cmd_rw(0), m_phrase(0), m_mem(*this, DEVICE_SELF) { } void tc8830f_device::device_start() { // create the stream m_stream = stream_alloc(0, 1, clock() / 0x10); // register for savestates save_item(NAME(m_playing)); save_item(NAME(m_address)); save_item(NAME(m_stop_address)); save_item(NAME(m_bitcount)); save_item(NAME(m_bitrate)); save_item(NAME(m_prevbits)); save_item(NAME(m_delta)); save_item(NAME(m_output)); save_item(NAME(m_command)); save_item(NAME(m_cmd_rw)); save_item(NAME(m_phrase)); reset(); } void tc8830f_device::device_post_load() { device_clock_changed(); } void tc8830f_device::device_clock_changed() { int divisor = 0x10 * (4 - (m_bitrate & 3)); m_stream->set_sample_rate(clock() / divisor); } void tc8830f_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) { int32_t mix = 0; for (int i = 0; i < samples; i++) { if (m_playing) { // get bit int bit = m_mem[m_address] >> m_bitcount & 1; m_bitcount = (m_bitcount + 1) & 7; if (m_bitcount == 0) { m_address = (m_address + 1) & m_mem.mask(); if (m_address == m_stop_address) m_playing = false; } // compute sample, ADM decoding // if previous bits are 111 or 000, delta increases exponentially // otherwise, delta decreases linearly if ((m_prevbits & 7) == 7 || (m_prevbits & 7) == 0) { if (m_delta < 0x2000) m_delta <<= 1; } else m_delta -= 8; if (m_delta <= 0) m_delta = 1; // determine direction if (bit) m_output += m_delta; else m_output -= m_delta; if (m_output > 32767) m_output = 32767; else if (m_output < -32768) m_output = -32768; m_prevbits = m_prevbits << 1 | bit; mix = m_output; } outputs[0][i] = mix; } } void tc8830f_device::reset() { m_stream->update(); m_playing = false; m_address = 0x100; m_bitcount = 0; m_prevbits = 0; m_delta = 1; m_output = 0; m_cmd_rw = 0; // in cpu control, enter play mode and reset bitrate write_p(1); write_p(6); write_p(0); } void tc8830f_device::write_p(uint8_t data) { m_stream->update(); data &= 0xf; if (m_cmd_rw == 0) { // select command m_command = data; switch (m_command) { // immediate commands case 0x1: break; case 0x2: m_playing = true; break; case 0x3: m_playing = false; break; // multi-nibble commands case 0x4: case 0x5: case 0x6: case 0x7: m_cmd_rw = 1; break; case 0x8: case 0x9: case 0xa: case 0xb: logerror("tc8830f: Unemulated command %X\n", m_command); break; default: logerror("tc8830f: Invalid command %X\n", m_command); break; } } else { // write command switch (m_command) { case 0x4: // ADLD1: set address counter m_address = (m_address & ~(0xf << (m_cmd_rw*4))) | (data << (m_cmd_rw*4)); if (m_cmd_rw == 5) { m_address &= m_mem.mask(); m_bitcount = 0; m_cmd_rw = -1; } break; case 0x5: // ADLD2: set address stop m_stop_address = (m_stop_address & ~(0xf << (m_cmd_rw*4))) | (data << (m_cmd_rw*4)); if (m_cmd_rw == 5) { m_stop_address &= m_mem.mask(); m_cmd_rw = -1; } break; case 0x6: // CNDT: d0-d1: bitrate, d2: enable overflow m_bitrate = data & 3; device_clock_changed(); m_cmd_rw = -1; break; case 0x7: // LABEL: set phrase if (m_cmd_rw == 1) { m_phrase = (m_phrase & 0x30) | data; } else { m_phrase = (m_phrase & 0x0f) | (data << 4 & 0x30); // update addresses and start uint8_t offs = m_phrase * 4; m_address = (m_mem[offs] | m_mem[offs|1]<<8 | m_mem[offs|2]<<16) & m_mem.mask(); offs += 4; m_stop_address = (m_mem[offs] | m_mem[offs|1]<<8 | m_mem[offs|2]<<16) & m_mem.mask(); m_bitcount = 0; m_prevbits = 0; m_delta = 1; m_output = 0; m_playing = true; m_cmd_rw = -1; } break; default: m_cmd_rw = -1; break; } m_cmd_rw++; } }