/* vlm5030.c Sanyo VLM5030 emulator Written by Tatsuyuki Satoh Based on TMS5220 simulator (tms5220.c) +-------,_,-------+ GND -- | 1 40 | <- RST (gnd) TST1 -> | 2 39 | ?? TST4 OSC2 ck | 3 _ 38 | ?? TST3 OSC1 ck | 4 (_) 37 | ?? TST2 D0 -> | 5 36 | -> DAO D1 -> | 6 35 | -- VREF (+5v through 5.6k resistor) D2 -> | 7 34 | -> MTE D3 -> | 8 V 33 | -> /ME D4 -> | 9 L 32 | <- VCU D5 -> | 10 M 31 | <- START D6 -> | 11 5 30 | -> BSY D7 -> | 12 0 29 | -- Vdd (+5v) A0 <- | 13 3 28 | -> A15 A1 <- | 14 0 27 | -> A14 A2 <- | 15 26 | -> A13 A3 <- | 16 _ 25 | -> A12 A4 <- | 17 (_) 24 | -> A11 A5 <- | 18 23 | -> A10 A6 <- | 19 22 | -> A9 A7 <- | 20 21 | -> A8 +-----------------+ TST1 is probably a test mode enable pin, must be grounded for normal operation. TST2-4 are some sort of test pins but can be left floating? VREF is probably the 0v ref for the output dac DAO is the output dac /ME is connected to the voice data rom /OE enable START strobes in a byte of data over the data bus from host cpu OSC1/2 are to both ends of a 3.579545MHz xtal with a 100pf cap from each end to gnd VCU makes the data bus select the upper 8 bits of the word register internally instead of the lower 8 bits. it is only useful if you need more than 256 phrases in rom? (recheck this) MTE is an output for roms which need to be clocked to latch address before use, or for a latch sitting in front of the voice rom address lines? (recheck this) RST not only resets the chip on its rising edge but grabs a byte of mode state data from the data bus on its falling edge? (recheck this) note: memory read cycle(==sampling rate) = 122.9u(440clock) interpolator (LC8109 = 2.5ms) = 20 * samples(125us) frame time (20ms) = 4 * interpolator 9bit DAC is composed of 5bit Physical and 3bitPWM. todo: Noise Generator circuit without 'machine.rand()' function. ----------- command format (Analytical result) ---------- 1)end of speech (8bit) :00000011: 2)silent some frame (8bit) :????SS01: SS : number of silent frames 00 = 2 frame 01 = 4 frame 10 = 6 frame 11 = 8 frame 3)-speech frame (48bit) function: 6th : 5th : 4th : 3rd : 2nd : 1st : end : --- : --- : --- : --- : --- :00000011: silent : --- : --- : --- : --- : --- :0000SS01: speech :11111122:22233334:44455566:67778889:99AAAEEE:EEPPPPP0: EEEEE : energy : volume 0=off,0x1f=max PPPPP : pitch : 0=noize , 1=fast,0x1f=slow 111111 : K1 : 48=off 22222 : K2 : 0=off,1=+min,0x0f=+max,0x10=off,0x11=+max,0x1f=-min : 16 == special function?? 3333 : K3 : 0=off,1=+min,0x07=+max,0x08=-max,0x0f=-min 4444 : K4 : 555 : K5 : 0=off,1=+min,0x03=+max,0x04=-max,0x07=-min 666 : K6 : 777 : K7 : 888 : K8 : 999 : K9 : AAA : K10 : ---------- chirp table information ---------- DAC PWM cycle == 88system clock , (11clock x 8 pattern) = 40.6KHz one chirp == 5 x PWM cycle == 440systemclock(8,136Hz) chirp 0 : volume 10- 8 : with filter chirp 1 : volume 8- 6 : with filter chirp 2 : volume 6- 4 : with filter chirp 3 : volume 4 : no filter ?? chirp 4- 5: volume 4- 2 : with filter chirp 6-11: volume 2- 0 : with filter chirp 12-..: vokume 0 : silent ---------- digial output information ---------- when ME pin = high , some status output to A0..15 pins A0..8 : DAC output value (abs) A9 : DAC sign flag , L=minus,H=Plus A10 : energy reload flag (pitch pulse) A11..15 : unknown [DAC output value(signed 6bit)] = A9 ? A0..8 : -(A0..8) */ #include "emu.h" #include "vlm5030.h" /* interpolator per frame */ #define FR_SIZE 4 /* samples per interpolator */ #define IP_SIZE_SLOWER (240/FR_SIZE) #define IP_SIZE_SLOW (200/FR_SIZE) #define IP_SIZE_NORMAL (160/FR_SIZE) #define IP_SIZE_FAST (120/FR_SIZE) #define IP_SIZE_FASTER ( 80/FR_SIZE) typedef struct _vlm5030_state vlm5030_state; struct _vlm5030_state { device_t *device; const vlm5030_interface *intf; sound_stream * channel; /* coefficient tables */ const struct tms5100_coeffs *coeff; /* need to save state */ UINT8 *rom; int address_mask; UINT16 address; UINT8 pin_BSY; UINT8 pin_ST; UINT8 pin_VCU; UINT8 pin_RST; UINT8 latch_data; UINT16 vcu_addr_h; UINT8 parameter; UINT8 phase; /* state of option paramter */ int frame_size; int pitch_offset; UINT8 interp_step; UINT8 interp_count; /* number of interp periods */ UINT8 sample_count; /* sample number within interp */ UINT8 pitch_count; /* these contain data describing the current and previous voice frames */ UINT16 old_energy; UINT8 old_pitch; INT16 old_k[10]; UINT16 target_energy; UINT8 target_pitch; INT16 target_k[10]; UINT16 new_energy; UINT8 new_pitch; INT16 new_k[10]; /* these are all used to contain the current state of the sound generation */ unsigned int current_energy; unsigned int current_pitch; int current_k[10]; INT32 x[10]; }; /* phase value */ enum { PH_RESET, PH_IDLE, PH_SETUP, PH_WAIT, PH_RUN, PH_STOP, PH_END }; /* Pull in the ROM tables */ #include "tms5110r.c" /* speed parameter SPC SPB SPA 1 0 1 more slow (05h) : 42ms (150%) : 60sample 1 1 x slow (06h,07h) : 34ms (125%) : 50sample x 0 0 normal (00h,04h) : 25.6ms (100%) : 40samplme 0 0 1 fast (01h) : 20.2ms (75%) : 30sample 0 1 x more fast (02h,03h) : 12.2ms (50%) : 20sample */ static const int vlm5030_speed_table[8] = { IP_SIZE_NORMAL, IP_SIZE_FAST, IP_SIZE_FASTER, IP_SIZE_FASTER, IP_SIZE_NORMAL, IP_SIZE_SLOWER, IP_SIZE_SLOW, IP_SIZE_SLOW }; static const char VLM_NAME[] = "VLM5030"; INLINE vlm5030_state *get_safe_token(device_t *device) { assert(device != NULL); assert(device->type() == VLM5030); return (vlm5030_state *)downcast(device)->token(); } static int get_bits(vlm5030_state *chip, int sbit,int bits) { int offset = chip->address + (sbit>>3); int data; data = chip->rom[offset&chip->address_mask] + (((int)chip->rom[(offset+1)&chip->address_mask])*256); data >>= (sbit&7); data &= (0xff>>(8-bits)); return data; } /* get next frame */ static int parse_frame (vlm5030_state *chip) { unsigned char cmd; int i; /* remember previous frame */ chip->old_energy = chip->new_energy; chip->old_pitch = chip->new_pitch; for(i=0;i<=9;i++) chip->old_k[i] = chip->new_k[i]; /* command byte check */ cmd = chip->rom[chip->address&chip->address_mask]; if( cmd & 0x01 ) { /* extend frame */ chip->new_energy = chip->new_pitch = 0; for(i=0;i<=9;i++) chip->new_k[i] = 0; chip->address++; if( cmd & 0x02 ) { /* end of speech */ /* logerror("VLM5030 %04X end \n",chip->address ); */ return 0; } else { /* silent frame */ int nums = ( (cmd>>2)+1 )*2; /* logerror("VLM5030 %04X silent %d frame\n",chip->address,nums ); */ return nums * FR_SIZE; } } /* pitch */ chip->new_pitch = ( chip->coeff->pitchtable[get_bits(chip, 1,chip->coeff->pitch_bits)] + chip->pitch_offset )&0xff; /* energy */ chip->new_energy = chip->coeff->energytable[get_bits(chip, 6,chip->coeff->energy_bits)]; /* 10 K's */ chip->new_k[9] = chip->coeff->ktable[9][get_bits(chip,11,chip->coeff->kbits[9])]; chip->new_k[8] = chip->coeff->ktable[8][get_bits(chip,14,chip->coeff->kbits[8])]; chip->new_k[7] = chip->coeff->ktable[7][get_bits(chip,17,chip->coeff->kbits[7])]; chip->new_k[6] = chip->coeff->ktable[6][get_bits(chip,20,chip->coeff->kbits[6])]; chip->new_k[5] = chip->coeff->ktable[5][get_bits(chip,23,chip->coeff->kbits[5])]; chip->new_k[4] = chip->coeff->ktable[4][get_bits(chip,26,chip->coeff->kbits[4])]; chip->new_k[3] = chip->coeff->ktable[3][get_bits(chip,29,chip->coeff->kbits[3])]; chip->new_k[2] = chip->coeff->ktable[2][get_bits(chip,33,chip->coeff->kbits[2])]; chip->new_k[1] = chip->coeff->ktable[1][get_bits(chip,37,chip->coeff->kbits[1])]; chip->new_k[0] = chip->coeff->ktable[0][get_bits(chip,42,chip->coeff->kbits[0])]; chip->address+=6; logerror("VLM5030 %04X voice \n",chip->address ); //fprintf(stderr,"*** target Energy, Pitch, and Ks = %04d, %04d, %04d, %04d, %04d, %04d, %04d, %04d, %04d, %04d, %04d, %04d\n",chip->new_energy, chip->new_pitch, chip->new_k[0], chip->new_k[1], chip->new_k[2], chip->new_k[3], chip->new_k[4], chip->new_k[5], chip->new_k[6], chip->new_k[7], chip->new_k[8], chip->new_k[9]); return FR_SIZE; } /* decode and buffering data */ static STREAM_UPDATE( vlm5030_update_callback ) { vlm5030_state *chip = (vlm5030_state *)param; int buf_count=0; int interp_effect; int i; int u[11]; stream_sample_t *buffer = outputs[0]; /* running */ if( chip->phase == PH_RUN || chip->phase == PH_STOP ) { /* playing speech */ while (samples > 0) { int current_val; /* check new interpolator or new frame */ if( chip->sample_count == 0 ) { if( chip->phase == PH_STOP ) { chip->phase = PH_END; chip->sample_count = 1; goto phase_stop; /* continue to end phase */ } chip->sample_count = chip->frame_size; /* interpolator changes */ if ( chip->interp_count == 0 ) { /* change to new frame */ chip->interp_count = parse_frame(chip); /* with change phase */ if ( chip->interp_count == 0 ) { /* end mark found */ chip->interp_count = FR_SIZE; chip->sample_count = chip->frame_size; /* end -> stop time */ chip->phase = PH_STOP; } /* Set old target as new start of frame */ chip->current_energy = chip->old_energy; chip->current_pitch = chip->old_pitch; for(i=0;i<=9;i++) chip->current_k[i] = chip->old_k[i]; /* is this a zero energy frame? */ if (chip->current_energy == 0) { /*mame_printf_debug("processing frame: zero energy\n");*/ chip->target_energy = 0; chip->target_pitch = chip->current_pitch; for(i=0;i<=9;i++) chip->target_k[i] = chip->current_k[i]; } else { /*mame_printf_debug("processing frame: Normal\n");*/ /*mame_printf_debug("*** Energy = %d\n",chip->current_energy);*/ /*mame_printf_debug("proc: %d %d\n",last_fbuf_head,fbuf_head);*/ chip->target_energy = chip->new_energy; chip->target_pitch = chip->new_pitch; for(i=0;i<=9;i++) chip->target_k[i] = chip->new_k[i]; } } /* next interpolator */ /* Update values based on step values 25% , 50% , 75% , 100% */ chip->interp_count -= chip->interp_step; /* 3,2,1,0 -> 1,2,3,4 */ interp_effect = FR_SIZE - (chip->interp_count%FR_SIZE); chip->current_energy = chip->old_energy + (chip->target_energy - chip->old_energy) * interp_effect / FR_SIZE; if (chip->old_pitch > 1) chip->current_pitch = chip->old_pitch + (chip->target_pitch - chip->old_pitch) * interp_effect / FR_SIZE; for (i = 0; i <= 9 ; i++) chip->current_k[i] = chip->old_k[i] + (chip->target_k[i] - chip->old_k[i]) * interp_effect / FR_SIZE; } /* calcrate digital filter */ if (chip->old_energy == 0) { /* generate silent samples here */ current_val = 0x00; } else if (chip->old_pitch <= 1) { /* generate unvoiced samples here */ current_val = (chip->device->machine().rand()&1) ? chip->current_energy : -chip->current_energy; } else { /* generate voiced samples here */ current_val = ( chip->pitch_count == 0) ? chip->current_energy : 0; } /* Lattice filter here */ u[10] = current_val; for (i = 9; i >= 0; i--) u[i] = u[i+1] - ((-chip->current_k[i] * chip->x[i]) / 512); for (i = 9; i >= 1; i--) chip->x[i] = chip->x[i-1] + ((-chip->current_k[i-1] * u[i-1]) / 512); chip->x[0] = u[0]; /* clipping, buffering */ if (u[0] > 511) buffer[buf_count] = 511<<6; else if (u[0] < -511) buffer[buf_count] = -511<<6; else buffer[buf_count] = (u[0] << 6); buf_count++; /* sample count */ chip->sample_count--; /* pitch */ chip->pitch_count++; if (chip->pitch_count >= chip->current_pitch ) chip->pitch_count = 0; /* size */ samples--; } /* return;*/ } /* stop phase */ phase_stop: switch( chip->phase ) { case PH_SETUP: if( chip->sample_count <= samples) { chip->sample_count = 0; /* logerror("VLM5030 BSY=H\n" ); */ /* pin_BSY = 1; */ chip->phase = PH_WAIT; } else { chip->sample_count -= samples; } break; case PH_END: if( chip->sample_count <= samples) { chip->sample_count = 0; /* logerror("VLM5030 BSY=L\n" ); */ chip->pin_BSY = 0; chip->phase = PH_IDLE; } else { chip->sample_count -= samples; } } /* silent buffering */ while (samples > 0) { buffer[buf_count++] = 0x00; samples--; } } /* realtime update */ static void vlm5030_update(vlm5030_state *chip) { chip->channel->update(); } /* setup parameteroption when RST=H */ static void vlm5030_setup_parameter(vlm5030_state *chip, UINT8 param) { /* latch parameter value */ chip->parameter = param; /* bit 0,1 : 4800bps / 9600bps , interporator step */ if(param&2) /* bit 1 = 1 , 9600bps */ chip->interp_step = 4; /* 9600bps : no interporator */ else if(param&1) /* bit1 = 0 & bit0 = 1 , 4800bps */ chip->interp_step = 2; /* 4800bps : 2 interporator */ else /* bit1 = bit0 = 0 : 2400bps */ chip->interp_step = 1; /* 2400bps : 4 interporator */ /* bit 3,4,5 : speed (frame size) */ chip->frame_size = vlm5030_speed_table[(param>>3) &7]; /* bit 6,7 : low / high pitch */ if(param&0x80) /* bit7=1 , high pitch */ chip->pitch_offset = -8; else if(param&0x40) /* bit6=1 , low pitch */ chip->pitch_offset = 8; else chip->pitch_offset = 0; } static void vlm5030_restore_state(vlm5030_state *chip) { int i; int interp_effect = FR_SIZE - (chip->interp_count%FR_SIZE); /* restore parameter data */ vlm5030_setup_parameter(chip, chip->parameter); /* restore current energy,pitch & filter */ chip->current_energy = chip->old_energy + (chip->target_energy - chip->old_energy) * interp_effect / FR_SIZE; if (chip->old_pitch > 1) chip->current_pitch = chip->old_pitch + (chip->target_pitch - chip->old_pitch) * interp_effect / FR_SIZE; for (i = 0; i <= 9 ; i++) chip->current_k[i] = chip->old_k[i] + (chip->target_k[i] - chip->old_k[i]) * interp_effect / FR_SIZE; } static void vlm5030_reset(vlm5030_state *chip) { chip->phase = PH_RESET; chip->address = 0; chip->vcu_addr_h = 0; chip->pin_BSY = 0; chip->old_energy = chip->old_pitch = 0; chip->new_energy = chip->new_pitch = 0; chip->current_energy = chip->current_pitch = 0; chip->target_energy = chip->target_pitch = 0; memset(chip->old_k, 0, sizeof(chip->old_k)); memset(chip->new_k, 0, sizeof(chip->new_k)); memset(chip->current_k, 0, sizeof(chip->current_k)); memset(chip->target_k, 0, sizeof(chip->target_k)); chip->interp_count = chip->sample_count = chip->pitch_count = 0; memset(chip->x, 0, sizeof(chip->x)); /* reset parameters */ vlm5030_setup_parameter(chip, 0x00); } static DEVICE_RESET( vlm5030 ) { vlm5030_reset(get_safe_token(device)); } /* set speech rom address */ void vlm5030_set_rom(device_t *device, void *speech_rom) { vlm5030_state *chip = get_safe_token(device); chip->rom = (UINT8 *)speech_rom; } /* get BSY pin level */ int vlm5030_bsy(device_t *device) { vlm5030_state *chip = get_safe_token(device); vlm5030_update(chip); return chip->pin_BSY; } /* latch contoll data */ WRITE8_DEVICE_HANDLER( vlm5030_data_w ) { vlm5030_state *chip = get_safe_token(device); chip->latch_data = (UINT8)data; } /* set RST pin level : reset / set table address A8-A15 */ void vlm5030_rst (device_t *device, int pin ) { vlm5030_state *chip = get_safe_token(device); if( chip->pin_RST ) { if( !pin ) { /* H -> L : latch parameters */ chip->pin_RST = 0; vlm5030_setup_parameter(chip, chip->latch_data); } } else { if( pin ) { /* L -> H : reset chip */ chip->pin_RST = 1; if( chip->pin_BSY ) { vlm5030_reset(chip); } } } } /* set VCU pin level : ?? unknown */ void vlm5030_vcu(device_t *device, int pin) { vlm5030_state *chip = get_safe_token(device); /* direct mode / indirect mode */ chip->pin_VCU = pin; return; } /* set ST pin level : set table address A0-A7 / start speech */ void vlm5030_st(device_t *device, int pin ) { vlm5030_state *chip = get_safe_token(device); int table; if( chip->pin_ST != pin ) { /* pin level is change */ if( !pin ) { /* H -> L */ chip->pin_ST = 0; if( chip->pin_VCU ) { /* direct access mode & address High */ chip->vcu_addr_h = ((int)chip->latch_data<<8) + 0x01; } else { /* start speech */ /* check access mode */ if( chip->vcu_addr_h ) { /* direct access mode */ chip->address = (chip->vcu_addr_h&0xff00) + chip->latch_data; chip->vcu_addr_h = 0; } else { /* indirect accedd mode */ table = (chip->latch_data&0xfe) + (((int)chip->latch_data&1)<<8); chip->address = (((int)chip->rom[table&chip->address_mask])<<8) | chip->rom[(table+1)&chip->address_mask]; #if 0 /* show unsupported parameter message */ if( chip->interp_step != 1) popmessage("No %d %dBPS parameter",table/2,chip->interp_step*2400); #endif } vlm5030_update(chip); /* logerror("VLM5030 %02X start adr=%04X\n",table/2,chip->address ); */ /* reset process status */ chip->sample_count = chip->frame_size; chip->interp_count = FR_SIZE; /* clear filter */ /* start after 3 sampling cycle */ chip->phase = PH_RUN; } } else { /* L -> H */ chip->pin_ST = 1; /* setup speech , BSY on after 30ms? */ chip->phase = PH_SETUP; chip->sample_count = 1; /* wait time for busy on */ chip->pin_BSY = 1; /* */ } } } /* start VLM5030 with sound rom */ /* speech_rom == 0 -> use sampling data mode */ static DEVICE_START( vlm5030 ) { const vlm5030_interface defintrf = { 0 }; int emulation_rate; vlm5030_state *chip = get_safe_token(device); chip->device = device; chip->coeff = &vlm5030_coeff; chip->intf = (device->static_config() != NULL) ? (const vlm5030_interface *)device->static_config() : &defintrf; emulation_rate = device->clock() / 440; /* reset input pins */ chip->pin_RST = chip->pin_ST = chip->pin_VCU= 0; chip->latch_data = 0; vlm5030_reset(chip); chip->phase = PH_IDLE; chip->rom = *device->region(); /* memory size */ if( chip->intf->memory_size == 0) chip->address_mask = device->region()->bytes()-1; else chip->address_mask = chip->intf->memory_size-1; chip->channel = device->machine().sound().stream_alloc(*device, 0, 1, emulation_rate,chip,vlm5030_update_callback); /* don't restore "UINT8 *chip->rom" when use vlm5030_set_rom() */ device->save_item(NAME(chip->address)); device->save_item(NAME(chip->pin_BSY)); device->save_item(NAME(chip->pin_ST)); device->save_item(NAME(chip->pin_VCU)); device->save_item(NAME(chip->pin_RST)); device->save_item(NAME(chip->latch_data)); device->save_item(NAME(chip->vcu_addr_h)); device->save_item(NAME(chip->parameter)); device->save_item(NAME(chip->phase)); device->save_item(NAME(chip->interp_count)); device->save_item(NAME(chip->sample_count)); device->save_item(NAME(chip->pitch_count)); device->save_item(NAME(chip->old_energy)); device->save_item(NAME(chip->old_pitch)); device->save_item(NAME(chip->old_k)); device->save_item(NAME(chip->target_energy)); device->save_item(NAME(chip->target_pitch)); device->save_item(NAME(chip->target_k)); device->save_item(NAME(chip->x)); device->machine().save().register_postload(save_prepost_delegate(FUNC(vlm5030_restore_state), chip)); } /************************************************************************** * Generic get_info **************************************************************************/ DEVICE_GET_INFO( vlm5030 ) { switch (state) { /* --- the following bits of info are returned as 64-bit signed integers --- */ case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(vlm5030_state); break; /* --- the following bits of info are returned as pointers to data or functions --- */ case DEVINFO_FCT_START: info->start = DEVICE_START_NAME( vlm5030 ); break; case DEVINFO_FCT_STOP: /* Nothing */ break; case DEVINFO_FCT_RESET: info->reset = DEVICE_RESET_NAME( vlm5030 ); break; /* --- the following bits of info are returned as NULL-terminated strings --- */ case DEVINFO_STR_NAME: strcpy(info->s, "VLM5030"); break; case DEVINFO_STR_FAMILY: strcpy(info->s, "VLM speech"); break; case DEVINFO_STR_VERSION: strcpy(info->s, "1.0"); break; case DEVINFO_STR_SOURCE_FILE: strcpy(info->s, __FILE__); break; case DEVINFO_STR_CREDITS: strcpy(info->s, "Copyright Nicola Salmoria and the MAME Team"); break; } } const device_type VLM5030 = &device_creator; vlm5030_device::vlm5030_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) : device_t(mconfig, VLM5030, "VLM5030", tag, owner, clock), device_sound_interface(mconfig, *this) { m_token = global_alloc_array_clear(UINT8, sizeof(vlm5030_state)); } //------------------------------------------------- // device_config_complete - perform any // operations now that the configuration is // complete //------------------------------------------------- void vlm5030_device::device_config_complete() { } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void vlm5030_device::device_start() { DEVICE_START_NAME( vlm5030 )(this); } //------------------------------------------------- // device_reset - device-specific reset //------------------------------------------------- void vlm5030_device::device_reset() { DEVICE_RESET_NAME( vlm5030 )(this); } //------------------------------------------------- // sound_stream_update - handle a stream update //------------------------------------------------- void vlm5030_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) { // should never get here fatalerror("sound_stream_update called; not applicable to legacy sound devices\n"); }