/* SPEECH.C ======= */ #define __SPEECH__ #include #include "v9t9_common.h" #include "v9t9.h" #include "sound.h" #include "memory.h" #include "speech.h" #include "timer.h" #include "roms.h" #define _L LOG_SPEECH | LOG_INFO #include "tms5220r.c" char speechromfilename[64]="spchrom.bin"; static struct tms5200 sp; static struct LPC lpc; /* LPC info */ u8 speechrom[65536]; static int speech_hertz = 8000; static int speech_length = 200; static s8 *speech_data; /****************************************************/ static int speech_event_tag; static int in_speech_intr = 0; // mutex on speech_intr #define SPEECH_TIMEOUT (25+9) /****************************************************/ static u8 swapped_nybbles[16] = { 0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe, 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf }; static u8 swapbits(u8 in) { return (swapped_nybbles[in & 0xf] << 4) | (swapped_nybbles[(in & 0xf0) >> 4]); } /* static u8 swapbits(u8 in) { in = ((in >> 1) & 0x55) | ((in << 1) & 0xaa); in = ((in >> 2) & 0x33) | ((in << 2) & 0xcc); in = ((in >> 4) & 0x0f) | ((in << 4) & 0xf0); return in; } */ /******************************************************/ static void tms5200purgeFIFO(void) { sp.bit = sp.bits = sp.out = sp.in = sp.len = 0; } static void LPCinit(void) { memset((void *) &lpc, 0, sizeof(lpc)); lpc.decode |= FL_first; /* Reset excitation filters */ lpc.ns1 = 0xaaaaaaaa; lpc.ns2 = 0x1; } static void SpeechOn(void) { if (!speech_event_tag) speech_event_tag = TM_UniqueTag(); TM_SetEvent(speech_event_tag, TM_HZ * 100 / 40, 0, TM_REPEAT | TM_FUNC, speech_intr); } static void SpeechOff(void) { TM_ResetEvent(speech_event_tag); } static void tms5200reset(void) { logger(_L | L_1, "Speech reset"); sp.status = SS_BE | SS_BL; tms5200purgeFIFO(); sp.command = 0x70; sp.data = 0; sp.addr = 0; sp.addr_pos = 0; sp.gate = GT_RSTAT | GT_WCMD; SpeechOff(); sp.status &= ~SS_SPEAKING; sp.timeout = SPEECH_TIMEOUT; LPCinit(); in_speech_intr = 0; } static u8 tms5200read(void) { sp.addr_pos = 0; if (sp.addr >= 32768) sp.data = 0; else sp.data = speechrom[sp.addr]; sp.addr++; sp.gate = (sp.gate & ~GT_RSTAT) | GT_RDAT; logger(_L | L_2, "Speech read: %02X\n\n", sp.data); return sp.data; } static u8 tms5200peek(void) { sp.addr_pos = 0; if (sp.addr >= 32768) sp.data = 0; else sp.data = speechrom[sp.addr]; sp.gate = (sp.gate & ~GT_RSTAT) | GT_RDAT; return sp.data; } static void tms5200loadAddress(u32 nybble) { sp.addr_pos = (sp.addr_pos + 1) % 5; sp.addr = (sp.addr >> 4) | (nybble << 16); logger(_L | L_2, "Speech addr = %04X\n", sp.addr); } static void tms5200readAndBranch(void) { u32 addr; sp.addr_pos = 0; addr = (tms5200read() << 8) + tms5200read(); sp.addr = (sp.addr & 0xc000) | (addr & 0x3fff); sp.gate = (sp.gate & ~GT_RDAT) | GT_RSTAT; } static void tms5200speak(void) { logger(_L | L_1, "Speaking phrase at %04X\n", sp.addr); sp.status |= SS_TS; sp.gate = (sp.gate & ~GT_WDAT) | GT_WCMD; /* just in case */ sp.bit = 0; /* start on byte boundary */ // flush existing sample SPEECHPLAY(vms_Speech, NULL, 0L, speech_hertz); sp.status |= SS_SPEAKING; while (sp.status & SS_SPEAKING) speech_intr(); // not scheduled LPCinit(); } static void tms5200speakExternal(void) { logger(_L | L_1, "Speaking external data"); sp.gate = (sp.gate & ~GT_WCMD) | GT_WDAT; /* accept data from I/O */ tms5200purgeFIFO(); SpeechOn(); /* call speech_intr every 25 ms */ sp.status |= SS_SPEAKING; sp.timeout = SPEECH_TIMEOUT; } /* Undocumented and unknown function... */ static void tms5200loadFrameRate(u8 val) { if (val & 0x4) { /* variable */ } else { /* frameRate = val & 0x3; */ } } static void tms5200command(u8 cmd) { sp.command = cmd & 0x70; logger(_L | L_3, "Cmd=%02X Status: %02X\n\n", cmd, sp.status); switch (sp.command) { case 0x00: case 0x20: tms5200loadFrameRate(cmd & 0x7); break; case 0x10: tms5200read(); break; case 0x30: tms5200readAndBranch(); break; case 0x40: tms5200loadAddress(cmd & 0xf); break; case 0x50: tms5200speak(); break; case 0x60: tms5200speakExternal(); break; case 0x70: tms5200reset(); break; /* default: ignore */ } } static void tms5200writeFIFO(u8 val) { sp.fifo[sp.in] = swapbits(val); sp.in = (sp.in + 1) & 15; logger(_L | L_3, "FIFO write: %02X len=%d\n", val, sp.len); if (sp.len < 16) sp.len++; sp.timeout = SPEECH_TIMEOUT; sp.status &= ~SS_BE; if (sp.len > 8) { sp.status &= ~SS_BL; speech_intr(); } } static u8 tms5200readFIFO(void) { u8 ret = sp.fifo[sp.out]; logger(_L | L_3, "FIFO read: %02X len=%d\n", ret, sp.len); if (sp.len == 0) { sp.status |= SS_BE; tms5200reset(); SpeechOff(); logger(_L | L_1, "Speech timed out\n"); } if (sp.len > 0) { sp.out = (sp.out + 1) & 15; sp.len--; } if (sp.len < 8) sp.status |= SS_BL; if (sp.len == 0) { sp.status |= SS_BE; } return ret; } static u8 tms5200peekFIFO(void) { return sp.fifo[sp.out]; } /* Fetch so many bits. This differs if we're reading from vocabulary or FIFO, in terms of where a byte comes from, but that's all. When reading from the FIFO, we only execute ...readFIFO when we are finished with the byte. */ static u32 LPCfetch(int bits) { u32 cur; if (sp.gate & GT_WDAT) { /* from FIFO */ if (sp.bit + bits >= 8) { /* we will cross into the next byte */ cur = tms5200readFIFO() << 8; cur |= tms5200peekFIFO(); /* we can't read more than 6 bits, so no poss of crossing TWO bytes */ } else cur = tms5200peekFIFO() << 8; } else { /* from vocab */ if (sp.bit + bits >= 8) { cur = tms5200read() << 8; cur |= tms5200peek(); } else cur = tms5200peek() << 8; } /* Get the bits we want. */ cur = (cur << (sp.bit + 16)) >> (32 - bits); /* Adjust bit ptr */ sp.bit = (sp.bit + bits) & 7; sp.bits += bits; return cur; } /*************************************************************** This section handles decoding one LPC equation into the 'lpc' structure. ****************************************************************/ #define SHIFT 4 #define KTRANS(x) (x) static void LPCclearToSilence(void) { lpc.pnv = 12; lpc.env = 0; memset(lpc.knv, 0, sizeof(lpc.knv)); lpc.knv[0] = KTRANS(k1table[0]); lpc.knv[1] = KTRANS(k2table[0]); lpc.knv[2] = KTRANS(k3table[0]); lpc.knv[3] = KTRANS(k4table[0]); lpc.knv[4] = KTRANS(k5table[0]); lpc.knv[5] = KTRANS(k6table[0]); lpc.knv[6] = KTRANS(k7table[0]); lpc.knv[7] = KTRANS(k8table[0]); lpc.knv[8] = KTRANS(k9table[0]); lpc.knv[9] = KTRANS(k10table[0]); // if the previous frame was unvoiced, // it would sound bad to interpolate. // just clear it all out. if (lpc.decode & FL_unvoiced) { lpc.pbf = 12; lpc.ebf = 0; memset(lpc.kbf, 0, sizeof(lpc.kbf)); lpc.kbf[0] = KTRANS(k1table[0]); lpc.kbf[1] = KTRANS(k2table[0]); lpc.kbf[2] = KTRANS(k3table[0]); lpc.kbf[3] = KTRANS(k4table[0]); lpc.kbf[4] = KTRANS(k5table[0]); lpc.kbf[5] = KTRANS(k6table[0]); lpc.kbf[6] = KTRANS(k7table[0]); lpc.kbf[7] = KTRANS(k8table[0]); lpc.kbf[8] = KTRANS(k9table[0]); lpc.kbf[9] = KTRANS(k10table[0]); } } static void LPCreadEquation(void) { char txt[256], *tptr = txt; sp.bits = 0; /* Copy now-old 'new' values into 'buffer' values */ lpc.ebf = lpc.env; lpc.pbf = lpc.pnv; memcpy(lpc.kbf, lpc.knv, sizeof(lpc.kbf)); /* Read energy */ lpc.env = LPCfetch(4); tptr += sprintf(tptr, "E: %d ", lpc.env); if (lpc.env == 15) { lpc.decode |= FL_last; sp.status &= ~SS_TS; /* we will go one more frame to interpolate to silence, then speech_event will be reset */ } else if (lpc.env == 0) { /* silent frame */ LPCclearToSilence(); /* clear params */ lpc.decode |= FL_nointerp; } else { /* Repeat bit */ lpc.rpt = LPCfetch(1); tptr += sprintf(tptr, "R: %d ", lpc.rpt); /* Pitch code */ lpc.pnv = LPCfetch(6); tptr += sprintf(tptr, "P: %d ", lpc.pnv); if (lpc.pnv == 0) { /* unvoiced */ if (lpc.decode & FL_unvoiced) /* voiced before? */ lpc.decode |= FL_nointerp; /* don't interpolate */ else lpc.decode &= ~FL_nointerp; lpc.decode |= FL_unvoiced; lpc.pnv = 12; /* set some pitch */ if (lpc.ebf == 0) /* previous frame silent? */ lpc.decode |= FL_nointerp; } else { /* voiced */ lpc.pnv = pitchtable[lpc.pnv] >> 8; if (lpc.decode & FL_unvoiced) /* unvoiced before? */ lpc.decode |= FL_nointerp; /* don't interpolate */ else lpc.decode &= ~FL_nointerp; lpc.decode &= ~FL_unvoiced; } /* translate energy */ lpc.env = energytable[lpc.env]; /* Get K parameters */ if (!lpc.rpt) { /* don't repeat previous frame? */ u32 tmp; tmp = LPCfetch(5); lpc.knv[0] = KTRANS(k1table[tmp]); tptr += sprintf(tptr, "K0: %d ", tmp); tmp = LPCfetch(5); lpc.knv[1] = KTRANS(k2table[tmp]); tptr += sprintf(tptr, "K1: %d ", tmp); tmp = LPCfetch(4); lpc.knv[2] = KTRANS(k3table[tmp]); tptr += sprintf(tptr, "K2: %d ", tmp); tmp = LPCfetch(4); lpc.knv[3] = KTRANS(k4table[tmp]); tptr += sprintf(tptr, "K3: %d ", tmp); if (!(lpc.decode & FL_unvoiced)) { /* unvoiced? */ tmp = LPCfetch(4); lpc.knv[4] = KTRANS(k5table[tmp]); tptr += sprintf(tptr, "K4: %d ", tmp); tmp = LPCfetch(4); lpc.knv[5] = KTRANS(k6table[tmp]); tptr += sprintf(tptr, "K5: %d ", tmp); tmp = LPCfetch(4); lpc.knv[6] = KTRANS(k7table[tmp]); tptr += sprintf(tptr, "K6: %d ", tmp); tmp = LPCfetch(3); lpc.knv[7] = KTRANS(k8table[tmp]); tptr += sprintf(tptr, "K7: %d ", tmp); tmp = LPCfetch(3); lpc.knv[8] = KTRANS(k9table[tmp]); tptr += sprintf(tptr, "K8: %d ", tmp); tmp = LPCfetch(3); lpc.knv[9] = KTRANS(k10table[tmp]); tptr += sprintf(tptr, "K9: %d ", tmp); } else { lpc.knv[4] = KTRANS(k5table[0]); lpc.knv[5] = KTRANS(k6table[0]); lpc.knv[6] = KTRANS(k7table[0]); lpc.knv[7] = KTRANS(k8table[0]); lpc.knv[8] = KTRANS(k9table[0]); lpc.knv[9] = KTRANS(k10table[0]); } } } logger(_L | L_1, "Equation: %s\n", txt); logger(_L | L_1, "ebf=%d, pbf=%d, env=%d, pnv=%d\n", lpc.ebf, lpc.pbf, lpc.env, lpc.pnv); } /******************************************************************* This section handles the 'execution' of an LPC equation. ********************************************************************/ /* Interpolate "new" values and "buffer" values. */ static void LPCinterpolate(int period) { int x; if (!(lpc.decode & FL_nointerp)) { lpc.ebf += (lpc.env - lpc.ebf) / interp_coeff[period]; lpc.pbf += (lpc.pnv - lpc.pbf) / interp_coeff[period]; for (x = 0; x < 11; x++) lpc.kbf[x] += (lpc.knv[x] - lpc.kbf[x]) / interp_coeff[period]; } /* logger(_L|LOG_USER, "[%d]\n", lpc.kbf[0]); if (period == 7) logger(_L|LOG_USER,"\n");*/ logger(_L|L_1, "[%d] ebf=%d, pbf=%d\n", period, lpc.ebf, lpc.pbf); } #define ONE 32768 // this is safe since our values are ~14 bits #define MD(a,b) (((a)*(b))/ONE) #define B(i) lpc.b[i] #define Y(i) lpc.y[i] #define K(i) lpc.kbf[i] #define U Y(10) static void LPCcalc(void) { int frames, bytes, stage; s8 *ptr; ptr = speech_data; if (!ptr) return; frames = 8; while (frames--) { bytes = speech_length / 8; while (bytes--) { s32 samp; /* Get excitation data in U */ if ((lpc.decode & FL_unvoiced)) { U = (lpc.ns1 & 1) ? lpc.ebf / 4 : -lpc.ebf / 4; /* noise generator */ lpc.ns1 = (lpc.ns1 << 1) | (lpc.ns1 >> 31); lpc.ns1 ^= lpc.ns2; if ((lpc.ns2 += lpc.ns1) == 0) lpc.ns2++; } else { U = lpc.ppctr < 41 ? chirptable[lpc.ppctr] * lpc.ebf / 256 : 0; if (lpc.pbf) lpc.ppctr = (lpc.ppctr + 1) % lpc.pbf; else lpc.ppctr = 0; } /* ----------------------------------------- 10-stage lattice filter. range 1..10 here, 0..9 in our arrays Y10(i) = U(i) - K10*B10(i-1) U(i)=excitation ---- Y9(i) = Y10(i) - K9*B9(i-1) B10(i)= B9(i-1) + K9*Y9(i) ---- ... Y1(i) = Y2(i) - K1*B1(i-1) B2(i) = B1(i-1) + K1*Y1(i) ---- B1(i) = Y1(i) ----------------------------------------- */ /* Stage 10 is different than the others. Instead of calculating B11, we scale the excitation by the energy. */ for (stage = 9; stage >= 0; stage--) { Y(stage) = Y(stage + 1) - MD(K(stage), B(stage)); B(stage + 1) = B(stage) + MD(K(stage), Y(stage)); } samp = Y(0); B(0) = samp; *ptr++ = (samp > 511 ? 127 : samp < -512 ? -128 : samp >> 2); } /* Interpolate values from *nv to *bf */ LPCinterpolate(7 - frames); } } /* Interpolation here is from the previous LPC equation. Another 8 rounds of parameter interpolation occur inside the calculation. */ static void LPCexec(void) { if (lpc.decode & (FL_nointerp | FL_first)) lpc.decode &= ~FL_first; lpc.ppctr = 0; memset(lpc.y, 0, sizeof(lpc.y)); memset(lpc.b, 0, sizeof(lpc.b)); LPCcalc(); /* spit it out */ if (speech_data) SPEECHPLAY(vms_Speech, speech_data, speech_length, speech_hertz); if (lpc.decode & FL_last) { /* last frame */ logger(_L | L_1, "Done with speech phrase\n"); SpeechOff(); /* stop interrupting */ sp.status &= ~SS_SPEAKING; /* stop interrupting */ sp.gate = (sp.gate & ~GT_WDAT) | GT_WCMD; } } /* One LPC frame consists of decoding one equation (or repeating, or stopping), and calculating a speech waveform and outputting it. This happens during an interrupt. If we're here, we have enough data to form any one equation. */ static void LPCframe(void) { LPCreadEquation(); if (sp.status & SS_SPEAKING) /* didn't get empty condition */ LPCexec(); } void speech_intr(void) { if (in_speech_intr) return; in_speech_intr = 1; logger(_L | L_2, "In speech Interrupt\n"); if (sp.gate & GT_WDAT) { /* direct data */ if (!(sp.status & SS_TS)) /* not talking yet... we're waiting for */ if (sp.status & SS_BL) /* enough data in the buffer */ goto out; /* ... not yet */ else sp.status |= SS_TS; /* whee! Start talking */ else if (sp.status & SS_BL) { if (sp.timeout-- <= 0) tms5200reset(); goto out; } } LPCframe(); /* execute a frame */ out: logger(_L | L_2, "Out of speech interrupt\n"); in_speech_intr = 0; } /********************/ u16 speech_mmio_get_addr(void) { return sp.addr; } void speech_mmio_set_addr(u16 addr) { sp.addr = addr; } bool speech_mmio_addr_is_complete(void) { return sp.addr_pos == 0 || sp.addr_pos == 5; } void speech_mmio_write(u8 val) { if (sp.gate & GT_WCMD) tms5200command(val); else tms5200writeFIFO(val); } s8 speech_mmio_read(void) { if (sp.gate & GT_RSTAT) { logger(_L | L_2, "Status: %02X\n\n", sp.status); return sp.status & (SS_TS | SS_BE | SS_BL); } else { sp.gate = (sp.gate & ~GT_RDAT) | GT_RSTAT; return sp.data; } } mrstruct speech_handler = { speechrom, speechrom, 0L, // cannot write NULL, NULL, NULL, NULL }; void speech_memory_init(void) { memory_insert_new_entry(MEMENT_SPEECH, 0x0000, 0x10000, "Speech ROM", // 0L /*filename*/, 0L /*fileoffs*/, speechromfilename, 0 /*fileoffs*/, &speech_handler); } /************************/ static void speech_initLPC(void) { if (!(features & FE_SPEECH)) { features &= ~(FE_PLAYSPEECH | FE_SPEECH); logger(_L | LOG_USER, "Sound module cannot play speech, speech disabled.\n\n"); } else logger(_L | L_0, "Setting up LPC speech...\n\n"); } /***********************************************/ static DECL_SYMBOL_ACTION(speech_set_sample_length) { if (task == csa_WRITE) { xfree(speech_data); if (speech_length <= 0) speech_length = 1; speech_data = (s8*) xmalloc(speech_length); } return 1; } static DECL_SYMBOL_ACTION(speech_define_speech_rom) { char cmdbuf[1024]; char *fname; if (task == csa_READ) { command_arg_get_string(SYM_ARG_1st, &fname); if (!fname || !*fname) return 0; else return (iter == 0); } command_arg_get_string(SYM_ARG_1st, &fname); if (!*fname) fname = "spchrom.bin"; sprintf(cmdbuf, "DefineMemory \"RS\" 0x0000 -0x10000 \"%s\" 0x0 \"Speech ROM\"\n", fname); return command_parse_text(cmdbuf); } int speech_preconfiginit(void) { command_symbol_table *speechcommands = command_symbol_table_new("Speech Options", "These are commands for controlling speech synthesis", command_symbol_new("PlaySpeech", "Control whether speech is played", c_STATIC, NULL /*action*/, RET_FIRST_ARG, command_arg_new_toggle ("on|off", "toggle speech on or off", NULL /* action */ , ARG_NUM(features), FE_PLAYSPEECH, NULL /* next */ ) , command_symbol_new("SpeechROMFileName", "Name of speech ROM", c_DYNAMIC, speech_define_speech_rom /* action */ , RET_FIRST_ARG, command_arg_new_string ("file", "name of binary image", NULL /* action */, NEW_ARG_NEW_STRBUF, NULL /* next */ ) , command_symbol_new("SpeechHertz", "Set sample rate for speech", c_STATIC, NULL /*action*/, RET_FIRST_ARG, command_arg_new_num ("hertz", "normal value is 8000", NULL /* action */ , ARG_NUM(speech_hertz), NULL /* next */ ) , command_symbol_new("SpeechSampleLength", "Set sample length for a unit of speech", c_STATIC, speech_set_sample_length, RET_FIRST_ARG, command_arg_new_num ("length", "in bytes, normal value is 200", NULL /* action */ , ARG_NUM(speech_length), NULL /* next */ ) , NULL /*next*/)))), NULL /*sub*/, NULL /*next*/ ); command_symbol_table_add_subtable(universe, speechcommands); speech_length = 200; speech_data = (s8*) xmalloc(speech_length); LPCinit(); features |= FE_SPEECH|FE_PLAYSPEECH; return 1; } int speech_postconfiginit(void) { /* if (!loadspeech(romspath, systemromspath, speechromfilename, speechrom)) { features &= ~FE_PLAYSPEECH; } */ speech_memory_init(); speech_initLPC(); tms5200reset(); return 1; } int speech_restart(void) { return 1; } void speech_restop(void) { } void speech_shutdown(void) { xfree(speech_data); } /* * The least possible work we can do to enable * saving and loading speech -- hardcoded binary data! */ DECL_SYMBOL_ACTION(speech_machine_state) { char *str; if (task == csa_READ) { char tmp[2*(sizeof(sp)+sizeof(lpc))+1]; if (iter) return 0; emulate_bin2hex((u8 *)&sp, tmp, sizeof(sp)); command_arg_set_string(SYM_ARG_1st, tmp); emulate_bin2hex((u8 *)&lpc, tmp, sizeof(lpc)); command_arg_set_string(SYM_ARG_2nd, tmp); return 1; } if (command_arg_get_string(SYM_ARG_1st, &str)) emulate_hex2bin(str, (u8 *)&sp, sizeof(sp)); if (command_arg_get_string(SYM_ARG_2nd, &str)) emulate_hex2bin(str, (u8 *)&lpc, sizeof(lpc)); // we have to restart speech if necessary, // since it's nearly impossible to reset the speech // synthesizer if it was in direct mode without // setting the timeout function. in_speech_intr = 0; if (sp.timeout && (sp.gate & GT_WDAT)) { SpeechOn(); } else { sp.timeout = 0; } return 1; } /*************************************/