mirror of
https://github.com/holub/mame
synced 2025-05-25 07:15:25 +03:00
1118 lines
37 KiB
C
1118 lines
37 KiB
C
/**********************************************************************************************
|
|
|
|
TMS5200/5220 simulator
|
|
|
|
Written for MAME by Frank Palazzolo
|
|
With help from Neill Corlett
|
|
Additional tweaking by Aaron Giles
|
|
TMS6100 Speech Rom support added by Raphael Nabet
|
|
PRNG code by Jarek Burczynski backported from tms5110.c by Lord Nightmare
|
|
Chirp/excitation table fixes by Lord Nightmare
|
|
Various fixes by Lord Nightmare
|
|
Modularization by Lord Nightmare
|
|
|
|
Much information regarding these lpc encoding comes from US patent 4,209,844
|
|
US patent 4,331,836 describes the complete 51xx chip
|
|
US patent 4,335,277 describes the complete 52xx chip
|
|
Special Thanks to Larry Brantingham for answering questions regarding the chip details
|
|
|
|
***********************************************************************************************/
|
|
|
|
#include "sndintrf.h"
|
|
#include "tms5220.h"
|
|
|
|
|
|
/* Pull in the ROM tables */
|
|
#include "tms5220r.c"
|
|
|
|
/*
|
|
Changes by R. Nabet
|
|
* added Speech ROM support
|
|
* modified code so that the beast only start speaking at the start of next frame, like the data
|
|
sheet says
|
|
*/
|
|
|
|
|
|
#ifndef TRUE
|
|
#define TRUE 1
|
|
#endif
|
|
#ifndef FALSE
|
|
#define FALSE 0
|
|
#endif
|
|
|
|
|
|
struct tms5220
|
|
{
|
|
/* these contain data that describes the 128-bit data FIFO */
|
|
#define FIFO_SIZE 16
|
|
UINT8 fifo[FIFO_SIZE];
|
|
UINT8 fifo_head;
|
|
UINT8 fifo_tail;
|
|
UINT8 fifo_count;
|
|
UINT8 fifo_bits_taken;
|
|
|
|
|
|
/* these contain global status bits */
|
|
/*
|
|
R Nabet : speak_external is only set when a speak external command is going on.
|
|
tms5220_speaking is set whenever a speak or speak external command is going on.
|
|
Note that we really need to do anything in tms5220_process and play samples only when
|
|
tms5220_speaking is true. Else, we can play nothing as well, which is a
|
|
speed-up...
|
|
*/
|
|
UINT8 tms5220_speaking; /* Speak or Speak External command in progress */
|
|
UINT8 speak_external; /* Speak External command in progress */
|
|
UINT8 talk_status; /* tms5220 is really currently speaking */
|
|
UINT8 first_frame; /* we have just started speaking, and we are to parse the first frame */
|
|
UINT8 last_frame; /* we are doing the frame of sound */
|
|
UINT8 buffer_low; /* FIFO has less than 8 bytes in it */
|
|
UINT8 buffer_empty; /* FIFO is empty*/
|
|
UINT8 irq_pin; /* state of the IRQ pin (output) */
|
|
|
|
void (*irq_func)(int state); /* called when the state of the IRQ pin changes */
|
|
|
|
|
|
/* these contain data describing the current and previous voice frames */
|
|
UINT16 old_energy;
|
|
UINT16 old_pitch;
|
|
INT32 old_k[10];
|
|
|
|
UINT16 new_energy;
|
|
UINT16 new_pitch;
|
|
INT32 new_k[10];
|
|
|
|
|
|
/* these are all used to contain the current state of the sound generation */
|
|
UINT16 current_energy;
|
|
UINT16 current_pitch;
|
|
INT32 current_k[10];
|
|
|
|
UINT16 target_energy;
|
|
UINT16 target_pitch;
|
|
INT32 target_k[10];
|
|
|
|
UINT16 previous_energy; /* needed for lattice filter to match patent */
|
|
|
|
UINT8 interp_count; /* number of interp periods (0-7) */
|
|
UINT8 sample_count; /* sample number within interp (0-24) */
|
|
UINT16 pitch_count;
|
|
|
|
INT32 u[11];
|
|
INT32 x[10];
|
|
|
|
INT32 RNG; /* the random noise generator configuration is: 1 + x + x^3 + x^4 + x^13 */
|
|
INT8 excitation_data;
|
|
|
|
/* R Nabet : These have been added to emulate speech Roms */
|
|
int (*read_callback)(int count);
|
|
void (*load_address_callback)(int data);
|
|
void (*read_and_branch_callback)(void);
|
|
UINT8 schedule_dummy_read; /* set after each load address, so that next read operation
|
|
is preceded by a dummy read */
|
|
|
|
UINT8 data_register; /* data register, used by read command */
|
|
UINT8 RDB_flag; /* whether we should read data register or status register */
|
|
|
|
/* flag for variant tmc0285/tms5200 emulation */
|
|
/* The TMC0285 AKA TMS5200 is an earlier variant of the TMS5220 used in
|
|
the early releases of the Speech Module for the TI-99/4(a) computer,
|
|
in Zaccaria's 'Money Money', and in a few other places.
|
|
The TMS5200 has a different set of LPC coefficients, and a different
|
|
chirp table than the 5220 (which is not yet dumped)
|
|
Due to the vast superiority of the quality of the TMS5220, TI may have
|
|
sold the remaining stocks of TMS5200s at a discount, and provided a
|
|
special encoder to use the older tables.
|
|
Other than those differences, the two chips are identical.
|
|
Another variant of the TMS5220 is the TMS5220C/TSP5220C, which replaces
|
|
the X0X0 'NOP' opcode with an opcode to select the number of
|
|
interpolations per frame to either be defined at each frame, or be fixed
|
|
at either 8, 6, 4, or 2. The TMS5200/5220 is always fixed at 8.
|
|
*/
|
|
tms5220_variant variant;
|
|
/* The TMS52xx has two different ways of providing output data: the
|
|
analog speaker pin (which was usually used) and the Digital I/O pin.
|
|
The internal DAC used to feed the analog pin is only 8 bits, and has the
|
|
funny clipping/clamping logic, while the digital pin gives full 12? bit
|
|
resolution of the output data.
|
|
*/
|
|
UINT8 digital_select;
|
|
};
|
|
|
|
|
|
/* Static function prototypes */
|
|
static void process_command(struct tms5220 *tms);
|
|
static int extract_bits(struct tms5220 *tms, int count);
|
|
static int parse_frame(struct tms5220 *tms, int the_first_frame);
|
|
static void check_buffer_low(struct tms5220 *tms);
|
|
static void set_interrupt_state(struct tms5220 *tms, int state);
|
|
static INT16 lattice_filter(void *chip);
|
|
static INT16 clip_and_wrap(INT16 cliptemp);
|
|
|
|
|
|
#define DEBUG_5220 0
|
|
|
|
|
|
void *tms5220_create(int index)
|
|
{
|
|
struct tms5220 *tms;
|
|
|
|
tms = malloc_or_die(sizeof(*tms));
|
|
memset(tms, 0, sizeof(*tms));
|
|
|
|
state_save_register_item_array("tms5220", index, tms->fifo);
|
|
state_save_register_item("tms5220", index, tms->fifo_head);
|
|
state_save_register_item("tms5220", index, tms->fifo_tail);
|
|
state_save_register_item("tms5220", index, tms->fifo_count);
|
|
state_save_register_item("tms5220", index, tms->fifo_bits_taken);
|
|
|
|
state_save_register_item("tms5220", index, tms->tms5220_speaking);
|
|
state_save_register_item("tms5220", index, tms->speak_external);
|
|
state_save_register_item("tms5220", index, tms->talk_status);
|
|
state_save_register_item("tms5220", index, tms->first_frame);
|
|
state_save_register_item("tms5220", index, tms->last_frame);
|
|
state_save_register_item("tms5220", index, tms->buffer_low);
|
|
state_save_register_item("tms5220", index, tms->buffer_empty);
|
|
state_save_register_item("tms5220", index, tms->irq_pin);
|
|
|
|
state_save_register_item("tms5220", index, tms->old_energy);
|
|
state_save_register_item("tms5220", index, tms->old_pitch);
|
|
state_save_register_item_array("tms5220", index, tms->old_k);
|
|
|
|
state_save_register_item("tms5220", index, tms->new_energy);
|
|
state_save_register_item("tms5220", index, tms->new_pitch);
|
|
state_save_register_item_array("tms5220", index, tms->new_k);
|
|
|
|
state_save_register_item("tms5220", index, tms->current_energy);
|
|
state_save_register_item("tms5220", index, tms->current_pitch);
|
|
state_save_register_item_array("tms5220", index, tms->current_k);
|
|
|
|
state_save_register_item("tms5220", index, tms->target_energy);
|
|
state_save_register_item("tms5220", index, tms->target_pitch);
|
|
state_save_register_item_array("tms5220", index, tms->target_k);
|
|
|
|
state_save_register_item("tms5220", index, tms->previous_energy);
|
|
|
|
state_save_register_item("tms5220", index, tms->interp_count);
|
|
state_save_register_item("tms5220", index, tms->sample_count);
|
|
state_save_register_item("tms5220", index, tms->pitch_count);
|
|
|
|
state_save_register_item_array("tms5220", index, tms->u);
|
|
state_save_register_item_array("tms5220", index, tms->x);
|
|
|
|
state_save_register_item("tms5220", index, tms->RNG);
|
|
state_save_register_item("tms5220", index, tms->excitation_data);
|
|
|
|
state_save_register_item("tms5220", index, tms->schedule_dummy_read);
|
|
state_save_register_item("tms5220", index, tms->data_register);
|
|
state_save_register_item("tms5220", index, tms->RDB_flag);
|
|
state_save_register_item("tms5220", index, tms->digital_select);
|
|
|
|
return tms;
|
|
}
|
|
|
|
|
|
void tms5220_destroy(void *chip)
|
|
{
|
|
free(chip);
|
|
}
|
|
|
|
/**********************************************************************************************
|
|
|
|
tms5220_reset -- resets the TMS5220
|
|
|
|
***********************************************************************************************/
|
|
|
|
void tms5220_reset_chip(void *chip)
|
|
{
|
|
struct tms5220 *tms = chip;
|
|
|
|
/* initialize the FIFO */
|
|
/*memset(tms->fifo, 0, sizeof(tms->fifo));*/
|
|
tms->fifo_head = tms->fifo_tail = tms->fifo_count = tms->fifo_bits_taken = 0;
|
|
|
|
/* initialize the chip state */
|
|
/* Note that we do not actually clear IRQ on start-up : IRQ is even raised if tms->buffer_empty or tms->buffer_low are 0 */
|
|
tms->tms5220_speaking = tms->speak_external = tms->talk_status = tms->first_frame = tms->last_frame = tms->irq_pin = 0;
|
|
if (tms->irq_func) tms->irq_func(0);
|
|
tms->buffer_empty = tms->buffer_low = 1;
|
|
|
|
tms->RDB_flag = FALSE;
|
|
|
|
/* initialize the energy/pitch/k states */
|
|
tms->old_energy = tms->new_energy = tms->current_energy = tms->target_energy = 0;
|
|
tms->old_pitch = tms->new_pitch = tms->current_pitch = tms->target_pitch = 0;
|
|
memset(tms->old_k, 0, sizeof(tms->old_k));
|
|
memset(tms->new_k, 0, sizeof(tms->new_k));
|
|
memset(tms->current_k, 0, sizeof(tms->current_k));
|
|
memset(tms->target_k, 0, sizeof(tms->target_k));
|
|
|
|
/* initialize the sample generators */
|
|
tms->interp_count = tms->sample_count = tms->pitch_count = 0;
|
|
tms->RNG = 0x1FFF;
|
|
memset(tms->u, 0, sizeof(tms->u));
|
|
memset(tms->x, 0, sizeof(tms->x));
|
|
|
|
if (tms->load_address_callback)
|
|
(*tms->load_address_callback)(0);
|
|
|
|
tms->schedule_dummy_read = TRUE;
|
|
}
|
|
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
tms5220_set_irq -- sets the interrupt handler
|
|
|
|
***********************************************************************************************/
|
|
|
|
void tms5220_set_irq(void *chip, void (*func)(int))
|
|
{
|
|
struct tms5220 *tms = chip;
|
|
tms->irq_func = func;
|
|
}
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
tms5220_set_read -- sets the speech ROM read handler
|
|
|
|
***********************************************************************************************/
|
|
|
|
void tms5220_set_read(void *chip, int (*func)(int))
|
|
{
|
|
struct tms5220 *tms = chip;
|
|
tms->read_callback = func;
|
|
}
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
tms5220_set_load_address -- sets the speech ROM load address handler
|
|
|
|
***********************************************************************************************/
|
|
|
|
void tms5220_set_load_address(void *chip, void (*func)(int))
|
|
{
|
|
struct tms5220 *tms = chip;
|
|
tms->load_address_callback = func;
|
|
}
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
tms5220_set_read_and_branch -- sets the speech ROM read and branch handler
|
|
|
|
***********************************************************************************************/
|
|
|
|
void tms5220_set_read_and_branch(void *chip, void (*func)(void))
|
|
{
|
|
struct tms5220 *tms = chip;
|
|
tms->read_and_branch_callback = func;
|
|
}
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
tms5220_set_variant -- sets the tms5220 core to emulate its buggy forerunner, the tmc0285
|
|
|
|
***********************************************************************************************/
|
|
|
|
void tms5220_set_variant(void *chip, tms5220_variant new_variant)
|
|
{
|
|
struct tms5220 *tms = chip;
|
|
tms->variant = new_variant;
|
|
}
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
tms5220_data_write -- handle a write to the TMS5220
|
|
|
|
***********************************************************************************************/
|
|
|
|
void tms5220_data_write(void *chip, int data)
|
|
{
|
|
struct tms5220 *tms = chip;
|
|
|
|
/* add this byte to the FIFO */
|
|
if (tms->fifo_count < FIFO_SIZE)
|
|
{
|
|
tms->fifo[tms->fifo_tail] = data;
|
|
tms->fifo_tail = (tms->fifo_tail + 1) % FIFO_SIZE;
|
|
tms->fifo_count++;
|
|
|
|
/* if we were speaking, then we're no longer empty */
|
|
if (tms->speak_external)
|
|
tms->buffer_empty = 0;
|
|
|
|
if (DEBUG_5220) logerror("Added byte to FIFO (size=%2d)\n", tms->fifo_count);
|
|
}
|
|
else
|
|
{
|
|
if (DEBUG_5220) logerror("Ran out of room in the FIFO!\n");
|
|
}
|
|
|
|
/* update the buffer low state */
|
|
check_buffer_low(tms);
|
|
|
|
if (! tms->speak_external)
|
|
/* R Nabet : we parse commands at once. It is necessary for such commands as read. */
|
|
process_command (tms/*data*/);
|
|
}
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
tms5220_status_read -- read status or data from the TMS5220
|
|
|
|
From the data sheet:
|
|
bit D0(bit 7) = TS - Talk Status is active (high) when the VSP is processing speech data.
|
|
Talk Status goes active at the initiation of a Speak command or after nine
|
|
bytes of data are loaded into the FIFO following a Speak External command. It
|
|
goes inactive (low) when the stop code (Energy=1111) is processed, or
|
|
immediately by a buffer empty condition or a reset command.
|
|
bit D1(bit 6) = BL - Buffer Low is active (high) when the FIFO buffer is more than half empty.
|
|
Buffer Low is set when the "Last-In" byte is shifted down past the half-full
|
|
boundary of the stack. Buffer Low is cleared when data is loaded to the stack
|
|
so that the "Last-In" byte lies above the half-full boundary and becomes the
|
|
ninth data byte of the stack.
|
|
bit D2(bit 5) = BE - Buffer Empty is active (high) when the FIFO buffer has run out of data
|
|
while executing a Speak External command. Buffer Empty is set when the last bit
|
|
of the "Last-In" byte is shifted out to the Synthesis Section. This causes
|
|
Talk Status to be cleared. Speed is terminated at some abnormal point and the
|
|
Speak External command execution is terminated.
|
|
|
|
***********************************************************************************************/
|
|
|
|
int tms5220_status_read(void *chip)
|
|
{
|
|
struct tms5220 *tms = chip;
|
|
|
|
if (tms->RDB_flag)
|
|
{ /* if last command was read, return data register */
|
|
tms->RDB_flag = FALSE;
|
|
return(tms->data_register);
|
|
}
|
|
else
|
|
{ /* read status */
|
|
|
|
/* clear the interrupt pin */
|
|
set_interrupt_state(tms, 0);
|
|
|
|
if (DEBUG_5220) logerror("Status read: TS=%d BL=%d BE=%d\n", tms->talk_status, tms->buffer_low, tms->buffer_empty);
|
|
|
|
return (tms->talk_status << 7) | (tms->buffer_low << 6) | (tms->buffer_empty << 5);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
tms5220_ready_read -- returns the ready state of the TMS5220
|
|
|
|
***********************************************************************************************/
|
|
|
|
int tms5220_ready_read(void *chip)
|
|
{
|
|
struct tms5220 *tms = chip;
|
|
return (tms->fifo_count < FIFO_SIZE-1);
|
|
}
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
tms5220_ready_read -- returns the number of cycles until ready is asserted
|
|
|
|
***********************************************************************************************/
|
|
|
|
int tms5220_cycles_to_ready(void *chip)
|
|
{
|
|
struct tms5220 *tms = chip;
|
|
int answer;
|
|
|
|
|
|
if (tms5220_ready_read(tms))
|
|
answer = 0;
|
|
else
|
|
{
|
|
int val;
|
|
|
|
answer = 200-tms->sample_count+8;
|
|
|
|
/* total number of bits available in current byte is (8 - tms->fifo_bits_taken) */
|
|
/* if more than 4 are available, we need to check the energy */
|
|
if (tms->fifo_bits_taken < 4)
|
|
{
|
|
/* read energy */
|
|
val = (tms->fifo[tms->fifo_head] >> tms->fifo_bits_taken) & 0xf;
|
|
if (val == 0)
|
|
/* 0 -> silence frame: we will only read 4 bits, and we will
|
|
therefore need to read another frame before the FIFO is not
|
|
full any more */
|
|
answer += 200;
|
|
/* 15 -> stop frame, we will only read 4 bits, but the FIFO will
|
|
we cleared */
|
|
/* otherwise, we need to parse the repeat flag (1 bit) and the
|
|
pitch (6 bits), so everything will be OK. */
|
|
}
|
|
}
|
|
|
|
return answer;
|
|
}
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
tms5220_int_read -- returns the interrupt state of the TMS5220
|
|
|
|
***********************************************************************************************/
|
|
|
|
int tms5220_int_read(void *chip)
|
|
{
|
|
struct tms5220 *tms = chip;
|
|
return tms->irq_pin;
|
|
}
|
|
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
tms5220_process -- fill the buffer with a specific number of samples
|
|
|
|
***********************************************************************************************/
|
|
|
|
void tms5220_process(void *chip, INT16 *buffer, unsigned int size)
|
|
{
|
|
struct tms5220 *tms = chip;
|
|
int buf_count=0;
|
|
int i, interp_period, bitout;
|
|
|
|
tryagain:
|
|
|
|
/* if we're not speaking, parse commands */
|
|
/*while (!tms->speak_external && tms->fifo_count > 0)
|
|
process_command(tms);*/
|
|
|
|
/* if we're empty and still not speaking, fill with nothingness */
|
|
if ((!tms->tms5220_speaking) && (!tms->last_frame))
|
|
goto empty;
|
|
|
|
/* if we're to speak, but haven't started, wait for the 9th byte */
|
|
if (!tms->talk_status && tms->speak_external)
|
|
{
|
|
if (tms->fifo_count < 9)
|
|
goto empty;
|
|
|
|
tms->talk_status = 1;
|
|
tms->first_frame = 1; /* will cause the first frame to be parsed */
|
|
tms->buffer_empty = 0;
|
|
}
|
|
|
|
/* loop until the buffer is full or we've stopped speaking */
|
|
while ((size > 0) && tms->talk_status)
|
|
{
|
|
|
|
/* if we're ready for a new frame */
|
|
if ((tms->interp_count == 0) && (tms->sample_count == 0))
|
|
{
|
|
/* Parse a new frame */
|
|
if (!parse_frame(tms, tms->first_frame))
|
|
break;
|
|
tms->first_frame = 0;
|
|
|
|
/* Set old target as new start of frame */
|
|
tms->current_energy = tms->old_energy;
|
|
tms->current_pitch = tms->old_pitch;
|
|
for (i = 0; i < 10; i++)
|
|
tms->current_k[i] = tms->old_k[i];
|
|
|
|
/* is this a zero energy frame? */
|
|
if (tms->current_energy == 0)
|
|
{
|
|
/*mame_printf_debug("processing frame: zero energy\n");*/
|
|
tms->target_energy = 0;
|
|
tms->target_pitch = tms->current_pitch;
|
|
for (i = 0; i < 10; i++)
|
|
tms->target_k[i] = tms->current_k[i];
|
|
}
|
|
|
|
/* is this a stop frame? */
|
|
else if (tms->current_energy == (energytable[15] >> 6))
|
|
{
|
|
/*mame_printf_debug("processing frame: stop frame\n");*/
|
|
tms->current_energy = energytable[0] >> 6;
|
|
tms->target_energy = tms->current_energy;
|
|
/*tms->interp_count = tms->sample_count =*/ tms->pitch_count = 0;
|
|
tms->last_frame = 0;
|
|
if (tms->tms5220_speaking)
|
|
/* new speech command in progress */
|
|
tms->first_frame = 1;
|
|
else
|
|
{
|
|
/* really stop speaking */
|
|
tms->talk_status = 0;
|
|
|
|
/* generate an interrupt if necessary */
|
|
set_interrupt_state(tms, 1);
|
|
}
|
|
|
|
/* try to fetch commands again */
|
|
goto tryagain;
|
|
}
|
|
else
|
|
{
|
|
/* is this the ramp down frame? */
|
|
if (tms->new_energy == (energytable[15] >> 6))
|
|
{
|
|
/*mame_printf_debug("processing frame: ramp down\n");*/
|
|
tms->target_energy = 0;
|
|
tms->target_pitch = tms->current_pitch;
|
|
for (i = 0; i < 10; i++)
|
|
tms->target_k[i] = tms->current_k[i];
|
|
}
|
|
/* Reset the step size */
|
|
else
|
|
{
|
|
/*mame_printf_debug("processing frame: Normal\n");*/
|
|
/*mame_printf_debug("*** Energy = %d\n",tms->current_energy);*/
|
|
/*mame_printf_debug("proc: %d %d\n",last_fbuf_head,fbuf_head);*/
|
|
|
|
tms->target_energy = tms->new_energy;
|
|
tms->target_pitch = tms->new_pitch;
|
|
|
|
for (i = 0; i < 4; i++)
|
|
tms->target_k[i] = tms->new_k[i];
|
|
if (tms->current_pitch == 0)
|
|
for (i = 4; i < 10; i++)
|
|
{
|
|
tms->target_k[i] = tms->current_k[i] = 0;
|
|
}
|
|
else
|
|
for (i = 4; i < 10; i++)
|
|
tms->target_k[i] = tms->new_k[i];
|
|
}
|
|
}
|
|
}
|
|
else if (tms->interp_count == 0)
|
|
{
|
|
/* Update values based on step values */
|
|
/*mame_printf_debug("\n");*/
|
|
|
|
interp_period = tms->sample_count / 25;
|
|
tms->current_energy += ((tms->target_energy - tms->current_energy) >> interp_coeff[interp_period]);
|
|
if (tms->old_pitch != 0)
|
|
tms->current_pitch += ((tms->target_pitch - tms->current_pitch) >> interp_coeff[interp_period]);
|
|
|
|
/*mame_printf_debug("*** Energy = %d\n",tms->current_energy);*/
|
|
|
|
for (i = 0; i < 10; i++)
|
|
{
|
|
tms->current_k[i] += ((tms->target_k[i] - tms->current_k[i]) >> interp_coeff[interp_period]);
|
|
}
|
|
}
|
|
|
|
if (tms->old_energy == 0)
|
|
{
|
|
/* generate silent samples here */
|
|
tms->excitation_data = 0x00; /* I'm not sure if this is actually RIGHT, the current_energy may be forced to zero when we've just passed a zero energy frame. However, this does work too. May be removed later. */
|
|
}
|
|
else if (tms->old_pitch == 0)
|
|
{
|
|
/* generate unvoiced samples here */
|
|
if (tms->RNG & 1)
|
|
tms->excitation_data = -0x40; /* according to the patent it is (either + or -) half of the maximum value in the chirp table, so +-64 */
|
|
else
|
|
tms->excitation_data = 0x40;
|
|
}
|
|
else
|
|
{
|
|
/* generate voiced samples here */
|
|
/* US patent 4331836 Figure 14B shows, and logic would hold, that a pitch based chirp
|
|
* function has a chirp/peak and then a long chain of zeroes.
|
|
* The last entry of the chirp rom is at address 0b110011 (50d), the 51st sample,
|
|
* and if the address reaches that point the ADDRESS incrementer is
|
|
* disabled, forcing all samples beyond 50d to be == 50d
|
|
* (address 50d holds zeroes)
|
|
*/
|
|
if (tms->pitch_count > 50)
|
|
tms->excitation_data = chirptable[50];
|
|
else /*tms->pitch_count <= 50*/
|
|
tms->excitation_data = chirptable[tms->pitch_count];
|
|
}
|
|
|
|
/* Update LFSR every clock, like patent shows */
|
|
bitout = ((tms->RNG >> 12) & 1) ^
|
|
((tms->RNG >> 10) & 1) ^
|
|
((tms->RNG >> 9) & 1) ^
|
|
((tms->RNG >> 0) & 1);
|
|
tms->RNG >>= 1;
|
|
tms->RNG |= bitout << 12;
|
|
|
|
buffer[buf_count] = clip_and_wrap(lattice_filter(tms)); /* execute lattice filter and clipping/wrapping */
|
|
|
|
if (tms->digital_select == 0) /* if digital is NOT selected... */
|
|
buffer[buf_count] &= 0xff00; /* mask out all but the 8 dac bits */
|
|
|
|
/* Update all counts */
|
|
|
|
size--;
|
|
tms->sample_count = (tms->sample_count + 1) % 200;
|
|
|
|
if (tms->current_pitch != 0)
|
|
tms->pitch_count = (tms->pitch_count + 1) % tms->current_pitch;
|
|
else
|
|
tms->pitch_count = 0;
|
|
|
|
tms->interp_count = (tms->interp_count + 1) % 25;
|
|
buf_count++;
|
|
}
|
|
|
|
empty:
|
|
|
|
while (size > 0)
|
|
{
|
|
tms->sample_count = (tms->sample_count + 1) % 200;
|
|
tms->interp_count = (tms->interp_count + 1) % 25;
|
|
buffer[buf_count] = 0x00; /* should be (-1 << 8) ??? (cf note in data sheet, p 10, table 4) */
|
|
buf_count++;
|
|
size--;
|
|
}
|
|
}
|
|
|
|
/**********************************************************************************************
|
|
|
|
clip_and_wrap -- clips and wraps the 14 bit return value from the lattice filter to its final 10 bit value (-512 to 511), and upshifts this to 16 bits
|
|
|
|
***********************************************************************************************/
|
|
|
|
static INT16 clip_and_wrap(INT16 cliptemp)
|
|
{
|
|
/* clipping & wrapping, just like the patent shows */
|
|
|
|
if (cliptemp > 2047) cliptemp = -2048 + (cliptemp-2047);
|
|
else if (cliptemp < -2048) cliptemp = 2047 - (cliptemp+2048);
|
|
|
|
if (cliptemp > 511) { mame_printf_debug ("cliptemp > 511\n");
|
|
return 127<<8; }
|
|
else if (cliptemp < -512) { mame_printf_debug ("cliptemp < -512\n");
|
|
return -128<<8; }
|
|
else
|
|
return cliptemp << 6;
|
|
}
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
lattice_filter -- executes one 'full run' of the lattice filter on a specific byte of
|
|
excitation data, and specific values of all the current k constants, and returns the
|
|
resulting sample.
|
|
Note: the current_k processing here by dividing the result by 32768 is necessary, as the stored
|
|
parameters in the lookup table are the 10 bit coefficients but are pre-multiplied by 512 for
|
|
ease of storage. This is undone on the real chip by a shifter here, after the multiply.
|
|
|
|
***********************************************************************************************/
|
|
|
|
static INT16 lattice_filter(void *chip)
|
|
{
|
|
struct tms5220 *tms = chip;
|
|
/* Lattice filter here */
|
|
/* Aug/05/07: redone as unrolled loop, for clarity - LN*/
|
|
/* Copied verbatim from table I in US patent 4,209,804:
|
|
notation equivalencies from table:
|
|
Yn(i) == tms->u[n-1]
|
|
Kn = tms->current_k[n-1]
|
|
bn = tms->x[n-1]
|
|
*/
|
|
tms->u[10] = (tms->excitation_data * tms->previous_energy) >> 8; /* Y(11) */
|
|
tms->u[9] = tms->u[10] - ((tms->current_k[9] * tms->x[9]) / 32768);
|
|
tms->u[8] = tms->u[9] - ((tms->current_k[8] * tms->x[8]) / 32768);
|
|
tms->x[9] = tms->x[8] + ((tms->current_k[8] * tms->u[8]) / 32768);
|
|
tms->u[7] = tms->u[8] - ((tms->current_k[7] * tms->x[7]) / 32768);
|
|
tms->x[8] = tms->x[7] + ((tms->current_k[7] * tms->u[7]) / 32768);
|
|
tms->u[6] = tms->u[7] - ((tms->current_k[6] * tms->x[6]) / 32768);
|
|
tms->x[7] = tms->x[6] + ((tms->current_k[6] * tms->u[6]) / 32768);
|
|
tms->u[5] = tms->u[6] - ((tms->current_k[5] * tms->x[5]) / 32768);
|
|
tms->x[6] = tms->x[5] + ((tms->current_k[5] * tms->u[5]) / 32768);
|
|
tms->u[4] = tms->u[5] - ((tms->current_k[4] * tms->x[4]) / 32768);
|
|
tms->x[5] = tms->x[4] + ((tms->current_k[4] * tms->u[4]) / 32768);
|
|
tms->u[3] = tms->u[4] - ((tms->current_k[3] * tms->x[3]) / 32768);
|
|
tms->x[4] = tms->x[3] + ((tms->current_k[3] * tms->u[3]) / 32768);
|
|
tms->u[2] = tms->u[3] - ((tms->current_k[2] * tms->x[2]) / 32768);
|
|
tms->x[3] = tms->x[2] + ((tms->current_k[2] * tms->u[2]) / 32768);
|
|
tms->u[1] = tms->u[2] - ((tms->current_k[1] * tms->x[1]) / 32768);
|
|
tms->x[2] = tms->x[1] + ((tms->current_k[1] * tms->u[1]) / 32768);
|
|
tms->u[0] = tms->u[1] - ((tms->current_k[0] * tms->x[0]) / 32768);
|
|
tms->x[1] = tms->x[0] + ((tms->current_k[0] * tms->u[0]) / 32768);
|
|
tms->x[0] = tms->u[0];
|
|
tms->previous_energy = tms->current_energy;
|
|
return tms->u[0];
|
|
}
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
process_command -- extract a byte from the FIFO and interpret it as a command
|
|
|
|
***********************************************************************************************/
|
|
|
|
static void process_command(struct tms5220 *tms)
|
|
{
|
|
unsigned char cmd;
|
|
|
|
/* if there are stray bits, ignore them */
|
|
if (tms->fifo_bits_taken)
|
|
{
|
|
tms->fifo_bits_taken = 0;
|
|
tms->fifo_count--;
|
|
tms->fifo_head = (tms->fifo_head + 1) % FIFO_SIZE;
|
|
}
|
|
|
|
/* grab a full byte from the FIFO */
|
|
if (tms->fifo_count > 0)
|
|
{
|
|
cmd = tms->fifo[tms->fifo_head];
|
|
tms->fifo_count--;
|
|
tms->fifo_head = (tms->fifo_head + 1) % FIFO_SIZE;
|
|
|
|
/* parse the command */
|
|
switch (cmd & 0x70)
|
|
{
|
|
case 0x10 : /* read byte */
|
|
if (tms->schedule_dummy_read)
|
|
{
|
|
tms->schedule_dummy_read = FALSE;
|
|
if (tms->read_callback)
|
|
(*tms->read_callback)(1);
|
|
}
|
|
if (tms->read_callback)
|
|
tms->data_register = (*tms->read_callback)(8); /* read one byte from speech ROM... */
|
|
tms->RDB_flag = TRUE;
|
|
break;
|
|
|
|
case 0x30 : /* read and branch */
|
|
if (DEBUG_5220) logerror("read and branch command received\n");
|
|
tms->RDB_flag = FALSE;
|
|
if (tms->read_and_branch_callback)
|
|
(*tms->read_and_branch_callback)();
|
|
break;
|
|
|
|
case 0x40 : /* load address */
|
|
/* tms5220 data sheet says that if we load only one 4-bit nibble, it won't work.
|
|
This code does not care about this. */
|
|
if (tms->load_address_callback)
|
|
(*tms->load_address_callback)(cmd & 0x0f);
|
|
tms->schedule_dummy_read = TRUE;
|
|
break;
|
|
|
|
case 0x50 : /* speak */
|
|
if (tms->schedule_dummy_read)
|
|
{
|
|
tms->schedule_dummy_read = FALSE;
|
|
if (tms->read_callback)
|
|
(*tms->read_callback)(1);
|
|
}
|
|
tms->tms5220_speaking = 1;
|
|
tms->speak_external = 0;
|
|
if (! tms->last_frame)
|
|
{
|
|
tms->first_frame = 1;
|
|
}
|
|
tms->talk_status = 1; /* start immediately */
|
|
break;
|
|
|
|
case 0x60 : /* speak external */
|
|
tms->tms5220_speaking = tms->speak_external = 1;
|
|
tms->RDB_flag = FALSE;
|
|
|
|
/* according to the datasheet, this will cause an interrupt due to a BE condition */
|
|
if (!tms->buffer_empty)
|
|
{
|
|
tms->buffer_empty = 1;
|
|
set_interrupt_state(tms, 1);
|
|
}
|
|
|
|
tms->talk_status = 0; /* wait to have 8 bytes in buffer before starting */
|
|
break;
|
|
|
|
case 0x70 : /* reset */
|
|
if (tms->schedule_dummy_read)
|
|
{
|
|
tms->schedule_dummy_read = FALSE;
|
|
if (tms->read_callback)
|
|
(*tms->read_callback)(1);
|
|
}
|
|
tms5220_reset_chip(tms);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* update the buffer low state */
|
|
check_buffer_low(tms);
|
|
}
|
|
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
extract_bits -- extract a specific number of bits from the FIFO
|
|
|
|
***********************************************************************************************/
|
|
|
|
static int extract_bits(struct tms5220 *tms, int count)
|
|
{
|
|
int val = 0;
|
|
|
|
if (tms->speak_external)
|
|
{
|
|
/* extract from FIFO */
|
|
while (count--)
|
|
{
|
|
val = (val << 1) | ((tms->fifo[tms->fifo_head] >> tms->fifo_bits_taken) & 1);
|
|
tms->fifo_bits_taken++;
|
|
if (tms->fifo_bits_taken >= 8)
|
|
{
|
|
tms->fifo_count--;
|
|
tms->fifo_head = (tms->fifo_head + 1) % FIFO_SIZE;
|
|
tms->fifo_bits_taken = 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* extract from speech ROM */
|
|
if (tms->read_callback)
|
|
val = (* tms->read_callback)(count);
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
parse_frame -- parse a new frame's worth of data; returns 0 if not enough bits in buffer
|
|
|
|
***********************************************************************************************/
|
|
|
|
static int parse_frame(struct tms5220 *tms, int the_first_frame)
|
|
{
|
|
int bits = 0; /* number of bits in FIFO (speak external only) */
|
|
int indx, i, rep_flag;
|
|
|
|
if (! the_first_frame)
|
|
{
|
|
/* remember previous frame */
|
|
tms->old_energy = tms->new_energy;
|
|
tms->old_pitch = tms->new_pitch;
|
|
for (i = 0; i < 10; i++)
|
|
tms->old_k[i] = tms->new_k[i];
|
|
}
|
|
|
|
/* clear out the new frame */
|
|
tms->new_energy = 0;
|
|
tms->new_pitch = 0;
|
|
for (i = 0; i < 10; i++)
|
|
tms->new_k[i] = 0;
|
|
|
|
/* if the previous frame was a stop frame, don't do anything */
|
|
if ((! the_first_frame) && (tms->old_energy == (energytable[15] >> 6)))
|
|
return 1;
|
|
// WARNING: This code below breaks Victory's power-on test! If you change it
|
|
// make sure you test Victory.
|
|
// {
|
|
// if (DEBUG_5220) logerror("Buffer Empty set - Last frame stop frame\n");
|
|
|
|
// tms->buffer_empty = 1;
|
|
// return 1;
|
|
// }
|
|
|
|
if (tms->speak_external)
|
|
/* count the total number of bits available */
|
|
bits = tms->fifo_count * 8 - tms->fifo_bits_taken;
|
|
|
|
/* attempt to extract the energy index */
|
|
if (tms->speak_external)
|
|
{
|
|
bits -= 4;
|
|
if (bits < 0)
|
|
goto ranout;
|
|
}
|
|
indx = extract_bits(tms, 4);
|
|
tms->new_energy = energytable[indx] >> 6;
|
|
|
|
/* if the index is 0 or 15, we're done */
|
|
if (indx == 0 || indx == 15)
|
|
{
|
|
if (DEBUG_5220) logerror(" (4-bit energy=%d frame)\n",tms->new_energy);
|
|
|
|
/* clear tms->fifo if stop frame encountered */
|
|
if (indx == 15)
|
|
{
|
|
tms->fifo_head = tms->fifo_tail = tms->fifo_count = tms->fifo_bits_taken = 0;
|
|
tms->speak_external = tms->tms5220_speaking = 0;
|
|
tms->last_frame = 1;
|
|
}
|
|
goto done;
|
|
}
|
|
|
|
/* attempt to extract the repeat flag */
|
|
if (tms->speak_external)
|
|
{
|
|
bits -= 1;
|
|
if (bits < 0)
|
|
goto ranout;
|
|
}
|
|
rep_flag = extract_bits(tms, 1);
|
|
|
|
/* attempt to extract the pitch */
|
|
if (tms->speak_external)
|
|
{
|
|
bits -= 6;
|
|
if (bits < 0)
|
|
goto ranout;
|
|
}
|
|
indx = extract_bits(tms, 6);
|
|
tms->new_pitch = pitchtable[indx] / 256;
|
|
|
|
/* if this is a repeat frame, just copy the k's */
|
|
if (rep_flag)
|
|
{
|
|
for (i = 0; i < 10; i++)
|
|
tms->new_k[i] = tms->old_k[i];
|
|
|
|
if (DEBUG_5220) logerror(" (11-bit energy=%d pitch=%d rep=%d frame)\n", tms->new_energy, tms->new_pitch, rep_flag);
|
|
goto done;
|
|
}
|
|
|
|
/* if the pitch index was zero, we need 4 k's */
|
|
if (indx == 0)
|
|
{
|
|
/* attempt to extract 4 K's */
|
|
if (tms->speak_external)
|
|
{
|
|
bits -= 18;
|
|
if (bits < 0)
|
|
goto ranout;
|
|
}
|
|
tms->new_k[0] = k1table[extract_bits(tms, 5)];
|
|
tms->new_k[1] = k2table[extract_bits(tms, 5)];
|
|
tms->new_k[2] = k3table[extract_bits(tms, 4)];
|
|
if (tms->variant == variant_tmc0285)
|
|
tms->new_k[3] = k3table[extract_bits(tms, 4)]; /* ??? */
|
|
else
|
|
tms->new_k[3] = k4table[extract_bits(tms, 4)];
|
|
|
|
if (DEBUG_5220) logerror(" (29-bit energy=%d pitch=%d rep=%d 4K frame)\n", tms->new_energy, tms->new_pitch, rep_flag);
|
|
goto done;
|
|
}
|
|
|
|
/* else we need 10 K's */
|
|
if (tms->speak_external)
|
|
{
|
|
bits -= 39;
|
|
if (bits < 0)
|
|
goto ranout;
|
|
}
|
|
|
|
tms->new_k[0] = k1table[extract_bits(tms, 5)];
|
|
tms->new_k[1] = k2table[extract_bits(tms, 5)];
|
|
tms->new_k[2] = k3table[extract_bits(tms, 4)];
|
|
if (tms->variant == variant_tmc0285)
|
|
tms->new_k[3] = k3table[extract_bits(tms, 4)]; /* ??? */
|
|
else
|
|
tms->new_k[3] = k4table[extract_bits(tms, 4)];
|
|
tms->new_k[4] = k5table[extract_bits(tms, 4)];
|
|
tms->new_k[5] = k6table[extract_bits(tms, 4)];
|
|
tms->new_k[6] = k7table[extract_bits(tms, 4)];
|
|
tms->new_k[7] = k8table[extract_bits(tms, 3)];
|
|
tms->new_k[8] = k9table[extract_bits(tms, 3)];
|
|
tms->new_k[9] = k10table[extract_bits(tms, 3)];
|
|
|
|
if (DEBUG_5220) logerror(" (50-bit energy=%d pitch=%d rep=%d 10K frame)\n", tms->new_energy, tms->new_pitch, rep_flag);
|
|
|
|
done:
|
|
if (DEBUG_5220)
|
|
{
|
|
if (tms->speak_external)
|
|
logerror("Parsed a frame successfully in FIFO - %d bits remaining\n", bits);
|
|
else
|
|
logerror("Parsed a frame successfully in ROM\n");
|
|
}
|
|
|
|
if (the_first_frame)
|
|
{
|
|
/* if this is the first frame, no previous frame to take as a starting point */
|
|
tms->old_energy = tms->new_energy;
|
|
tms->old_pitch = tms->new_pitch;
|
|
for (i = 0; i < 10; i++)
|
|
tms->old_k[i] = tms->new_k[i];
|
|
}
|
|
|
|
/* update the tms->buffer_low status */
|
|
check_buffer_low(tms);
|
|
return 1;
|
|
|
|
ranout:
|
|
|
|
if (DEBUG_5220) logerror("Ran out of bits on a parse!\n");
|
|
|
|
/* this is an error condition; mark the buffer empty and turn off speaking */
|
|
tms->buffer_empty = 1;
|
|
tms->talk_status = tms->speak_external = tms->tms5220_speaking = the_first_frame = tms->last_frame = 0;
|
|
tms->fifo_count = tms->fifo_head = tms->fifo_tail = 0;
|
|
|
|
tms->RDB_flag = FALSE;
|
|
|
|
/* generate an interrupt if necessary */
|
|
set_interrupt_state(tms, 1);
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
check_buffer_low -- check to see if the buffer low flag should be on or off
|
|
|
|
***********************************************************************************************/
|
|
|
|
static void check_buffer_low(struct tms5220 *tms)
|
|
{
|
|
/* did we just become low? */
|
|
if (tms->fifo_count <= 8)
|
|
{
|
|
/* generate an interrupt if necessary */
|
|
if (!tms->buffer_low)
|
|
set_interrupt_state(tms, 1);
|
|
tms->buffer_low = 1;
|
|
|
|
if (DEBUG_5220) logerror("Buffer low set\n");
|
|
}
|
|
|
|
/* did we just become full? */
|
|
else
|
|
{
|
|
tms->buffer_low = 0;
|
|
|
|
if (DEBUG_5220) logerror("Buffer low cleared\n");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
set_interrupt_state -- generate an interrupt
|
|
|
|
***********************************************************************************************/
|
|
|
|
static void set_interrupt_state(struct tms5220 *tms, int state)
|
|
{
|
|
if (tms->irq_func && state != tms->irq_pin)
|
|
tms->irq_func(state);
|
|
tms->irq_pin = state;
|
|
}
|