/* * Copyright (C) 2008-2009 SVOX AG, Baslerstr. 30, 8048 Zuerich, Switzerland * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @file picospho.c * * sentence phonemic/phonetic FSTs PU * * Copyright (C) 2008-2009 SVOX AG, Baslerstr. 30, 8048 Zuerich, Switzerland * All rights reserved. * * History: * - 2009-04-20 -- initial version * */ #include "picoos.h" #include "picodbg.h" #include "picodata.h" #include "picoknow.h" #include "picokfst.h" #include "picoktab.h" #include "picotrns.h" #include "picospho.h" #ifdef __cplusplus extern "C" { #endif #if 0 } #endif #define SPHO_BUFSIZE (3 * PICODATA_BUFSIZE_DEFAULT) #define SPHO_MAX_ALTDESC_SIZE (60 * PICOTRNS_MAX_NUM_POSSYM) #define SPHO_SMALLEST_SIL_DUR 1 /** @addtogroup picospho * * Algorithmic description * ======================= * The main function, sphoStep, is divided into the subprocesses (processing states) described further down. * * Flow control: * ------------ * The processing flow is controlled by setting * - 'procState' : the next state to be processed * - 'feedFollowState' : the state to be processed after the feed state (the feed state is treated like a primitive "subroutine") * - some other flags * * Buffering: * --------- * - The input items are mainly stored and processed in two buffers, collectively called 'inBuf' * - cbuf : unstructured buffer containing item contents * - headx : structured buffer containing item heads, each expanded by a pointer to the item contents * and space for a boundary potentially to be inserted (to the left of the original item) * - For transduction, phonemes and their position are extracted from inBuf into * - phonBuf, * processed there, and the resulting phonemes realigned with inBuf. * - Word items are split into syllables, stored in * - sylBuf * - Items to be output are stored in outBuf * * Windowing: * --------- * Optimal solutions are achieved if a whole sentence is processed at once. However, if any of the buffers are too small, * only sentence parts are processed. To improve the quality of such sub-optimal solutions, a moving-window-with-overlap is applied: * - [0,headxReadPos[ : the window considered for transduction * - [activeStartPos,activeEndPos[ : the "active" subrange of the window actually used for output * - penultima : the position (within the active range) that should be used as new window start when shifting the window * * After PROCESS_PARSE: * 0 activeStartPos penultima activeEndPos headxReadPos headxWritePos * | | | | | | * |-------------=================================---------------| ['----': context '====' : active subrange) * * After PROCESS_SHIFT: * 0 activeStartPos headWritePos * | | | | * |------------... (only left context is known; new active range, penultima, and right context to be established at next parse) * * Processing states: * ----------------- * - INIT : initialize state variables * - COLLECT : collect items into internal buffers ("inBuf") * - PROCESS_PARSE : go through inBuf items and extract position/phoneme pairs into phoneme buffer 'phonBuf' * word boundary phonemes are inserted between words * - PROCESS_TRANSDUCE : transduce phonBuf * - PROCESS_BOUNDS : go through inBuf items again and match against transduced pos/phoneme * this is the first round of alignment, only inserting/deleting/modifying bounds, according to * - existing BOUND items * - newly produced word bounds separating WORDPHON items * - bound upgrades/downgrades from transduction * - bound upgrades/downgrades/insertions from SIL command items (originating e.g. from text commands) * all relevant bounds are placed in the corresponding headx extention; original bound items become invalid. * - PROCESS_RECOMB : go through inBuf items again and match against transduced pos/phoneme * this is the second round of alignment, treating non-BOUND items * - WORDPHONs are broken into syllables by "calling" PROCESS_SYL * - "side-bounds" (in the headx extension) are output by "calling" FEED * - BOUND items are consumed with no effect * - other items are output unchanged "calling" FEED * - PROCESS_SYL : the WORDPHON coming from RECOMB is matched against the phonBuf and (new) SYLLPHON items * are created. (the original wordphon is consumed) * - FEED : feeds one item and returns to spho->feedFollowState * - SHIFT : items in inBuf are shifted left to make room for new items. If a sentence doesn't fit * inBuf in its entirety, left and/or right contexts are kept so they can be considered in * the next transduction. */ /* PU sphoStep states */ #define SPHO_STEPSTATE_INIT 0 #define SPHO_STEPSTATE_COLLECT 1 #define SPHO_STEPSTATE_PROCESS_PARSE 2 #define SPHO_STEPSTATE_PROCESS_TRANSDUCE 3 #define SPHO_STEPSTATE_PROCESS_BOUNDS 4 #define SPHO_STEPSTATE_PROCESS_RECOMB 5 #define SPHO_STEPSTATE_PROCESS_SYL 6 #define SPHO_STEPSTATE_FEED 7 #define SPHO_STEPSTATE_SHIFT 8 #define SPHO_POS_INVALID (PICOTRNS_POS_INVALID) /* indicates that no position was set yet */ /* nr item restriction: maximum number of extended item heads in headx */ #define SPHO_MAXNR_HEADX 60 /* nr item restriction: maximum size of all item contents together in cont */ #define SPHO_MAXSIZE_CBUF (30 * 255) /* "expanded head": item head expanded by a content position and a by boundary information * potentially inserted "to the left" of the item */ typedef struct { picodata_itemhead_t head; picoos_uint16 cind; picoos_uint8 boundstrength; /* bstrength to the left, 0 if not set */ picoos_uint8 phrasetype; /* btype for following phrase, 0 if not set */ picoos_int16 sildur; /* silence duration for boundary, -1 if not set */ } picospho_headx_t; #define SPHO_MSGSTR_SIZE 32 /** object : SentPhoUnit * shortcut : spho * derived from : picodata_ProcessingUnit */ typedef struct spho_subobj { picoos_Common common; /* we use int16 for buffer positions so we can indicate exceptional positions (invalid etc.) with negative * integers */ picoos_uint8 procState; /* for next processing step decision */ /* buffer for item headers */ picoos_uint8 tmpbuf[PICODATA_MAX_ITEMSIZE]; /* tmp. location for an item */ picospho_headx_t headx[SPHO_MAXNR_HEADX]; /* "expanded head" buffer */ picoos_uint16 headxBufSize; /* actually allocated size (if one day headxBuf is allocated dynamically) */ picoos_uint16 headxReadPos, headxWritePos; picoos_uint8 cbuf[SPHO_MAXSIZE_CBUF]; picoos_uint16 cbufBufSize; /* actually allocated size */ picoos_uint16 cbufWritePos; /* next position to write to, 0 if buffer empty */ picoos_uint8 outBuf[PICODATA_BUFSIZE_DEFAULT]; /* internal output buffer to hold just one item */ picoos_uint16 outBufSize; /* actually allocated size (if one day outBuf is allocated dynamically) */ picoos_uint16 outReadPos; /* next pos to read from inBuf for output */ /* picoos_int16 outWritePos; */ /* next pos to output from in buf */ picoos_uint8 sylBuf[255]; /* internal buffer to hold contents of syl item to be output */ picoos_uint8 sylReadPos, sylWritePos; /* next pos to read from sylBuf, next pos to write to sylBuf */ /* buffer for internal calculation of transducer */ picotrns_AltDesc altDescBuf; /* the number of AltDesc in the buffer */ picoos_uint16 maxAltDescLen; /* the input to a transducer should not be larger than PICOTRNS_MAX_NUM_POSSYM * so the output may expand (up to 4*PICOTRNS_MAX_NUM_POSSYM) */ picotrns_possym_t phonBufA[4 * PICOTRNS_MAX_NUM_POSSYM + 1]; picotrns_possym_t phonBufB[4 * PICOTRNS_MAX_NUM_POSSYM + 1]; picotrns_possym_t * phonBuf; picotrns_possym_t * phonBufOut; picoos_uint16 phonReadPos, phonWritePos; /* next pos to read from phonBufIn, next pos to write to phonBufIn */ picoos_int16 activeStartPos; /* start position of items to be treated (at end of left context) */ picoos_int16 penultima, activeEndPos; /* positions of last two bounds/words; SPHO_POS_INVALID means uninitialized */ picoos_int16 lastPhraseBoundPos; /* position of the last bound encountered (<0 if inexistent or not reachable */ picoos_uint8 lastPhraseType; /* phrase type of the last phrase boundary, 0 if not set */ picoos_uint8 needMoreInput, /* more data necessary to decide on token */ suppressParseWordBound, /* dont produce word boundary */ suppressRecombWordBound, /* dont produce word boundary */ breakPending, /* received a break but didn't interpret it yet */ /* sentEnd, */ /* sentence end detected */ force, /* in forced state */ wordStarted, /* is it the first syl in the word: expect POS */ sentenceStarted; picoos_uint16 breakTime; /* time argument of the pending break command */ picoos_uint8 feedFollowState; /* where to return after feed */ /* fst knowledge bases */ picoos_uint8 numFsts; picokfst_FST fst[PICOKNOW_MAX_NUM_SPHO_FSTS]; picoos_uint8 curFst; /* the fst to be applied next */ /* fixed ids knowledge base */ picoktab_FixedIds fixedIds; /* phones kb */ picoktab_Phones phones; /* some soecial ids from phones */ picoos_uint8 primStressId, secondStressId, syllSepId; } spho_subobj_t; static pico_status_t sphoReset(register picodata_ProcessingUnit this) { spho_subobj_t * spho; if (NULL == this || NULL == this->subObj) { return picoos_emRaiseException(this->common->em, PICO_ERR_NULLPTR_ACCESS, NULL, NULL); } spho = (spho_subobj_t *) this->subObj; spho->curFst = 0; /* processing state */ spho->procState = SPHO_STEPSTATE_INIT; spho->needMoreInput = TRUE; spho->suppressParseWordBound = FALSE; spho->suppressRecombWordBound = FALSE; spho->breakPending = FALSE; spho->force = 0; spho->sentenceStarted = 0; /* item buffer headx/cbuf */ spho->headxBufSize = SPHO_MAXNR_HEADX; spho->headxReadPos = 0; spho->headxWritePos = 0; spho->cbufWritePos = 0; spho->cbufBufSize = SPHO_MAXSIZE_CBUF; /* possym buffer */ spho->phonBuf = spho->phonBufA; spho->phonBufOut = spho->phonBufB; spho->phonReadPos = 0; /* overlapping */ spho->activeStartPos = 0; spho->penultima = SPHO_POS_INVALID; spho->activeEndPos = SPHO_POS_INVALID; return PICO_OK; } static pico_status_t sphoInitialize(register picodata_ProcessingUnit this, picoos_int32 resetMode) { picoos_uint8 i; spho_subobj_t * spho; picokfst_FST fst; picoknow_kb_id_t myKbIds[PICOKNOW_MAX_NUM_SPHO_FSTS] = PICOKNOW_KBID_SPHO_ARRAY; PICODBG_DEBUG(("init")); if (NULL == this || NULL == this->subObj) { return picoos_emRaiseException(this->common->em, PICO_ERR_NULLPTR_ACCESS, NULL, NULL); } spho = (spho_subobj_t *) this->subObj; spho->numFsts = 0; spho->curFst = 0; for (i = 0; ivoice->kbArray[myKbIds[i]]); if (NULL != fst) { spho->fst[spho->numFsts++] = fst; } } spho->fixedIds = picoktab_getFixedIds(this->voice->kbArray[PICOKNOW_KBID_FIXED_IDS]); spho->phones = picoktab_getPhones(this->voice->kbArray[PICOKNOW_KBID_TAB_PHONES]); spho->syllSepId = picoktab_getSyllboundID(spho->phones); spho->primStressId = picoktab_getPrimstressID(spho->phones); spho->secondStressId = picoktab_getSecstressID(spho->phones); PICODBG_DEBUG(("got %i fsts", spho->numFsts)); return sphoReset(this); } static picodata_step_result_t sphoStep(register picodata_ProcessingUnit this, picoos_int16 mode, picoos_uint16 *numBytesOutput); static pico_status_t sphoTerminate(register picodata_ProcessingUnit this) { return PICO_OK; } static pico_status_t sphoSubObjDeallocate(register picodata_ProcessingUnit this, picoos_MemoryManager mm) { spho_subobj_t * spho; spho = (spho_subobj_t *) this->subObj; if (NULL != this) { if (NULL != this->subObj) { spho = (spho_subobj_t *) (this->subObj); picotrns_deallocate_alt_desc_buf(spho->common->mm,&spho->altDescBuf); picoos_deallocate(mm, (void *) &this->subObj); } } return PICO_OK; } picodata_ProcessingUnit picospho_newSentPhoUnit(picoos_MemoryManager mm, picoos_Common common, picodata_CharBuffer cbIn, picodata_CharBuffer cbOut, picorsrc_Voice voice) { spho_subobj_t * spho; picodata_ProcessingUnit this = picodata_newProcessingUnit(mm, common, cbIn, cbOut, voice); if (this == NULL) { return NULL; } this->initialize = sphoInitialize; this->step = sphoStep; this->terminate = sphoTerminate; this->subDeallocate = sphoSubObjDeallocate; this->subObj = picoos_allocate(mm, sizeof(spho_subobj_t)); if (this->subObj == NULL) { picoos_deallocate(mm, (void **)(void*)&this); return NULL; } spho = (spho_subobj_t *) this->subObj; spho->common = this->common; /* these are given by the pre-allocated array sizes */ spho->outBufSize = PICODATA_BUFSIZE_DEFAULT; spho->altDescBuf = picotrns_allocate_alt_desc_buf(spho->common->mm, SPHO_MAX_ALTDESC_SIZE, &spho->maxAltDescLen); if (NULL == spho->altDescBuf) { picotrns_deallocate_alt_desc_buf(spho->common->mm,&spho->altDescBuf); picoos_emRaiseException(spho->common->em,PICO_EXC_OUT_OF_MEM, NULL,NULL); return NULL; } sphoInitialize(this, PICO_RESET_FULL); return this; } /* ***********************************************************************/ /* process buffered item list */ /* ***********************************************************************/ /* shift relevant data in headx/'cbuf' (between 'readPos' incl and writePos non-incl) to 'start'. * modify read/writePos accordingly */ static picoos_int16 shift_range_left_1(spho_subobj_t *spho, picoos_int16 * from, picoos_int16 to) { /* remember shift parameters for cbuf */ picoos_uint16 c_i, c_j, c_diff, c_writePos, i, j, diff, writePos; i = to; j = *from; diff = j-i; writePos = spho->headxWritePos; c_i = spho->headx[to].cind; if (j < writePos) { c_j = spho->headx[j].cind; } else { c_j = spho->cbufWritePos; } c_diff = c_j - c_i; c_writePos = spho->cbufWritePos; PICODBG_DEBUG(( "shifting buffer region [%i,%i[ down to %i",*from, writePos, to )); /* PICODBG_ASSERT((i j) { return -1; } /* shift cbuf */ while (c_j < c_writePos) { spho->cbuf[c_i++] = spho->cbuf[c_j++]; } /* shift headx */ while (j < writePos) { spho->headx[j].cind -= c_diff; spho->headx[i++] = spho->headx[j++]; } spho->headxWritePos -= diff; *from = to; spho->cbufWritePos -= c_diff; /* */ PICODBG_DEBUG(( "readPos,WritePos are now [%i,%i[, returning shift amount %i",*from, spho->headxWritePos, diff )); return diff; } static pico_status_t sphoAddPhoneme(register spho_subobj_t *spho, picoos_int16 pos, picoos_int16 sym) { picoos_uint8 plane, unshifted; /* just for debuging */ unshifted = picotrns_unplane(sym,&plane); PICODBG_TRACE(("adding %i/%i (%c on plane %i) at phonBuf[%i]",pos,sym,unshifted,plane,spho->phonWritePos)); if (2* PICOTRNS_MAX_NUM_POSSYM <= spho->phonWritePos) { /* not an error! */ PICODBG_DEBUG(("couldn't add because phon buffer full")); return PICO_EXC_BUF_OVERFLOW; } else { spho->phonBuf[spho->phonWritePos].pos = pos; spho->phonBuf[spho->phonWritePos].sym = sym; spho->phonWritePos++; return PICO_OK; } } static pico_status_t sphoAddStartPhoneme(register spho_subobj_t *spho) { return sphoAddPhoneme(spho, PICOTRNS_POS_IGNORE, (PICOKFST_PLANE_INTERN << 8) + spho->fixedIds->phonStartId); } static pico_status_t sphoAddTermPhonemes(register spho_subobj_t *spho, picoos_uint16 pos) { return sphoAddPhoneme(spho, pos, (PICOKFST_PLANE_PB_STRENGTHS << 8) + PICODATA_ITEMINFO1_BOUND_SEND) && sphoAddPhoneme(spho, PICOTRNS_POS_IGNORE, (PICOKFST_PLANE_INTERN << 8) + spho->fixedIds->phonTermId); } /* return "syllable accent" (or prominence) symbol, given "word accent" symbol 'wacc' and stress value (no=0, primary=1, secondary=2) */ static picoos_uint16 sphoGetSylAccent(register spho_subobj_t *spho, picoos_uint8 wacc, picoos_uint8 sylStress) { PICODBG_ASSERT(sylStress <= 2); spho = spho; /* avoid warning "var not used in this function"*/ switch (sylStress) { case 0: /* non-stressed syllable gets no prominence */ /* return spho->fixedIds->accId[0]; */ return PICODATA_ACC0; break; case 1: /* primary-stressed syllable gets word prominence */ return wacc; break; case 2: /* secondary-stressed syllable gets no prominence or secondary stress prom. (4) */ return (PICODATA_ACC0 == wacc) ? PICODATA_ACC0 : PICODATA_ACC4; /*return (spho->fixedIds->accId[0] == wacc) ? spho->fixedIds->accId[0] : spho->fixedIds->accId[4]; */ break; default: /* never occurs :-) */ return PICODATA_ACC0; break; } } /* ***********************************************************************/ /* extract phonemes of an item into a phonBuf */ /* ***********************************************************************/ static pico_status_t sphoExtractPhonemes(register picodata_ProcessingUnit this, register spho_subobj_t *spho, picoos_uint16 pos, picoos_uint8 convertAccents, picoos_uint8 * suppressWB) { pico_status_t rv = PICO_OK; picoos_uint16 i, j; picoos_int16 fstSymbol; picoos_uint8 curStress; picotrns_possym_t tmpPosSym; picoos_uint16 oldPos, curPos; picodata_itemhead_t * head; picoos_uint8* content; #if defined(PICO_DEBUG) picoos_char msgstr[SPHO_MSGSTR_SIZE]; #endif /* Items considered in a transduction are a BOUND or a WORDPHON item. its starting offset within the headxBuf is given as 'pos'. Elements that go into the transduction receive "their" position in the buffer. */ oldPos = spho->phonWritePos; head = &(spho->headx[pos].head); content = spho->cbuf + spho->headx[pos].cind; PICODBG_TRACE(("doing item %s\n", picodata_head_to_string(head,msgstr,SPHO_MSGSTR_SIZE))); switch (head->type) { case PICODATA_ITEM_BOUND: /* map SBEG, SEND and TERM (as sentence closing) to SEND */ fstSymbol = (PICODATA_ITEMINFO1_BOUND_SBEG == head->info1 || PICODATA_ITEMINFO1_BOUND_TERM == head->info1) ? PICODATA_ITEMINFO1_BOUND_SEND : head->info1; PICODBG_TRACE(("found bound of type %c\n",head->info1)); /* BOUND() */ /* insert bound strength */ PICODBG_TRACE(("inserting phrase bound phoneme %c and setting suppresWB=1\n",fstSymbol)); fstSymbol += (PICOKFST_PLANE_PB_STRENGTHS << 8); rv = sphoAddPhoneme(spho,pos,fstSymbol); /* phrase type not used */ /* suppress next word boundary */ (*suppressWB) = 1; break; case PICODATA_ITEM_WORDPHON: /* WORDPHON(POS,WACC)phon */ PICODBG_TRACE(("found WORDPHON")); /* insert word boundary if not suppressed */ if (!(*suppressWB)) { fstSymbol = (PICOKFST_PLANE_PB_STRENGTHS << 8) + PICODATA_ITEMINFO1_BOUND_PHR0; PICODBG_TRACE(("adding word boundary phone")); rv = sphoAddPhoneme(spho,pos,fstSymbol); } (*suppressWB) = 0; /* for the time being, we force to use POS so we can transduce all fsts in a row without reconsulting the items */ /* If 'convertAccents' then the accentuation is not directly encoded. It rather influences the mapping of * the word accent symbol to the actual accent phoneme which is put after the syllable separator. */ if (convertAccents) { PICODBG_TRACE(("converting accents")); /* extracting phonemes IN REVERSE order replacing syllable symbols with prominence symbols */ curPos = spho->phonWritePos; curStress = 0; /* no stress */ for (i = head->len; i > 0 ;) { i--; if (spho->primStressId == content[i]) { curStress = 1; PICODBG_DEBUG(("skipping primary stress at pos %i (in 1 .. %i)",i, head->len)); continue; /* skip primary stress symbol */ } else if (spho->secondStressId == content[i]) { curStress = 2; PICODBG_DEBUG(("skipping secondary stress at pos %i (in 1 .. %i)",i, head->len)); continue; /* skip secundary stress symbol */ } else if (spho->syllSepId == content[i]) { fstSymbol = (PICOKFST_PLANE_POS << 8) + head->info1; rv = sphoAddPhoneme(spho, pos, fstSymbol); /* replace syllSepId by combination of syllable stress and word prominence */ fstSymbol = sphoGetSylAccent(spho,head->info2,curStress); curStress = 0; /* add accent */ fstSymbol += (PICOKFST_PLANE_ACCENTS << 8); rv = sphoAddPhoneme(spho,pos,fstSymbol); if (PICO_OK != rv) { break; } /* and keep syllable boundary */ fstSymbol = (PICOKFST_PLANE_PHONEMES << 8) + content[i]; } else { /* normal phoneme */ fstSymbol = (PICOKFST_PLANE_PHONEMES << 8) + content[i]; } if (PICO_OK == rv) { rv = sphoAddPhoneme(spho,pos,fstSymbol); } } if (PICO_OK == rv) { /* bug 366: we position the "head" into the item header and not on the first phoneme * because there might be no phonemes at all */ /* insert head of the first syllable of a word */ fstSymbol = (PICOKFST_PLANE_POS << 8) + head->info1; rv = sphoAddPhoneme(spho,pos,fstSymbol); fstSymbol = sphoGetSylAccent(spho,head->info2,curStress); curStress = 0; fstSymbol += (PICOKFST_PLANE_ACCENTS << 8); rv = sphoAddPhoneme(spho,pos,fstSymbol); } if (PICO_OK == rv) { /* invert sympos portion */ i = curPos; j=spho->phonWritePos-1; while (i < j) { tmpPosSym.pos = spho->phonBuf[i].pos; tmpPosSym.sym = spho->phonBuf[i].sym; spho->phonBuf[i].pos = spho->phonBuf[j].pos; spho->phonBuf[i].sym = spho->phonBuf[j].sym; spho->phonBuf[j].pos = tmpPosSym.pos; spho->phonBuf[j].sym = tmpPosSym.sym; i++; j--; } } } else { /* convertAccents */ for (i = 0; i len; i++) { fstSymbol = (PICOKFST_PLANE_PHONEMES << 8) + content[i]; rv = sphoAddPhoneme(spho,pos,fstSymbol); } } break; default: picoos_emRaiseException(this->common->em,rv,NULL,NULL); break; } /* switch(head->type) */ if (PICO_OK != rv) { spho->phonWritePos = oldPos; } return rv; } #define SPHO_POSSYM_OK 0 #define SPHO_POSSYM_OUT_OF_RANGE 1 #define SPHO_POSSYM_END 2 #define SPHO_POSSYM_INVALID -3 /* *readPos is the next position in phonBuf to be read, and *writePos is the first position not to be read (may be outside * buf). * 'rangeEnd' is the first possym position outside the desired range. * Possible return values: * SPHO_POSSYM_OK : 'pos' and 'sym' are set to the read possym, *readPos is advanced * SPHO_POSSYM_OUT_OF_RANGE : pos is out of range. 'pos' is set to that of the read possym, 'sym' is undefined * SPHO_POSSYM_UNDERFLOW : no more data in buf. 'pos' is set to PICOTRNS_POS_INVALID, 'sym' is undefined * SPHO_POSSYM_INVALID : "strange" pos. 'pos' is set to PICOTRNS_POS_INVALID, 'sym' is undefined */ static pico_status_t getNextPosSym(spho_subobj_t * spho, picoos_int16 * pos, picoos_int16 * sym, picoos_int16 rangeEnd) { /* skip POS_IGNORE */ while ((spho->phonReadPos < spho->phonWritePos) && (PICOTRNS_POS_IGNORE == spho->phonBuf[spho->phonReadPos].pos)) { PICODBG_DEBUG(("ignoring phone at spho->phonBuf[%i] because it has pos==IGNORE",spho->phonReadPos)); spho->phonReadPos++; } if ((spho->phonReadPos < spho->phonWritePos)) { *pos = spho->phonBuf[spho->phonReadPos].pos; if ((PICOTRNS_POS_INSERT == *pos) || ((0 <= *pos) && (*pos < rangeEnd))) { *sym = spho->phonBuf[spho->phonReadPos++].sym; return SPHO_POSSYM_OK; } else if (*pos < 0){ /* *pos is "strange" (e.g. POS_INVALID) */ return SPHO_POSSYM_INVALID; } else { return SPHO_POSSYM_OUT_OF_RANGE; } } else { /* no more possyms to read */ *pos = PICOTRNS_POS_INVALID; return SPHO_POSSYM_END; } } /** Calculate bound strength modified by transduction * * Given the original bound strength 'orig' and the desired target strength 'target' (suggested by fst), * calculate the modified bound strength. * * @param orig original bound strength * @param target target bound strength * @return resulting bound strength */ static picoos_uint8 fstModifiedBoundStrength(picoos_uint8 orig, picoos_uint8 target) { switch (orig) { case PICODATA_ITEMINFO1_BOUND_PHR1: case PICODATA_ITEMINFO1_BOUND_PHR2: /* don't allow primary phrase bounds to be demoted to word bound */ if (PICODATA_ITEMINFO1_BOUND_PHR0 == target) { return PICODATA_ITEMINFO1_BOUND_PHR3; } case PICODATA_ITEMINFO1_BOUND_PHR0: case PICODATA_ITEMINFO1_BOUND_PHR3: return target; break; default: /* don't allow bounds other than phrase or word bounds to be changed */ return orig; break; } } /** Calculate bound strength modified by a \ command * * Given the original (predicted and possibly fst-modified) bound strength, and a time value from an * overwriding \ command, calculate the modified bound strength. * * @param orig original bound strength * @param time time given as property of \ command * @param wasPrimary * @return modified bound strength */ static picoos_uint8 breakModifiedBoundStrength(picoos_uint8 orig, picoos_uint16 time, picoos_bool wasPrimary) { picoos_uint8 modified = (0 == time) ? PICODATA_ITEMINFO1_BOUND_PHR3 : (50 < time) ? PICODATA_ITEMINFO1_BOUND_PHR1 : PICODATA_ITEMINFO1_BOUND_PHR2; switch (orig) { /* for word and phrase breaks, return 'modified', unless a non-silence gets time==0, in which * case return no break (word break) */ case PICODATA_ITEMINFO1_BOUND_PHR0: if (0 == time) { return PICODATA_ITEMINFO1_BOUND_PHR0; } case PICODATA_ITEMINFO1_BOUND_PHR3: if (!wasPrimary && (0 == time)) { return PICODATA_ITEMINFO1_BOUND_PHR0; } case PICODATA_ITEMINFO1_BOUND_PHR1: case PICODATA_ITEMINFO1_BOUND_PHR2: return modified; break; default: return orig; break; } } static picoos_bool breakStateInterrupting(picodata_itemhead_t * head, picoos_bool * breakBefore, picoos_bool * breakAfter) { picoos_bool result = 1; *breakBefore = 0; *breakAfter = 0; if (PICODATA_ITEM_WORDPHON == head->type) { } else if (PICODATA_ITEM_CMD == head->type) { if ((PICODATA_ITEMINFO1_CMD_PLAY == head->info1) || (PICODATA_ITEMINFO1_CMD_SAVE == head->info1) || (PICODATA_ITEMINFO1_CMD_UNSAVE == head->info1)) { *breakBefore = 1; *breakAfter = 1; } else if (PICODATA_ITEMINFO1_CMD_SAVE == head->info1) { *breakBefore = 1; } else if (PICODATA_ITEMINFO1_CMD_UNSAVE == head->info1) { *breakAfter = 1; } else if (PICODATA_ITEMINFO1_CMD_IGNSIG == head->info1) { if (PICODATA_ITEMINFO2_CMD_START == head->info2) { *breakBefore = 1; } else { *breakAfter = 1; } } } else { result = 0; } return result; } static void putSideBoundToOutput(spho_subobj_t * spho) { picodata_itemhead_t ohead; picoos_uint8 ocontent[2*sizeof(picoos_uint16)]; picoos_int16 sildur; picoos_uint16 clen; /* create boundary */ ohead.type = PICODATA_ITEM_BOUND; ohead.info1 = spho->headx[spho->outReadPos].boundstrength; ohead.info2 = spho->headx[spho->outReadPos].phrasetype; sildur = spho->headx[spho->outReadPos].sildur; if ((sildur < 0) || (PICODATA_ITEMINFO1_BOUND_PHR0 == ohead.info1) || (PICODATA_ITEMINFO1_BOUND_PHR3 == ohead.info1)) { PICODBG_DEBUG(("outputting a bound of strength '%c' and type '%c' without duration constraints",ohead.info1, ohead.info2)); ohead.len = 0; } else { picoos_uint32 pos = 0; picoos_write_mem_pi_uint16(ocontent,&pos,sildur); picoos_write_mem_pi_uint16(ocontent,&pos,sildur); PICODBG_DEBUG(("outputting a bound of strength '%c' and type '%c' with duration constraints [%i,%i]",ohead.info1, ohead.info2,sildur, sildur)); ohead.len = pos; } picodata_put_itemparts(&ohead, ocontent, ohead.len, spho->outBuf, spho->outBufSize, &clen); /* disable side bound */ spho->headx[spho->outReadPos].boundstrength = 0; } /** Set bound strength and sil dur. * * given the original bound strength 'orig_strength' and the fst-suggested bound strength 'fst_strength' * and possibly being in a pending break state, calculate the resulting bound strength and set boundstrength * and sildur of the current item (spho->headx[spho->outReadPos]) accordingly. * if a boundstrength was set, also calculate the phrasetype and if necessary (and reachable), modify the phrase type * of the previous phrase boundary. * * @param spho * @param orig_strength * @param orig_type * @param fst_strength */ static void setSideBound(spho_subobj_t * spho, picoos_uint8 orig_strength, picoos_uint8 orig_type, picoos_uint8 fst_strength) { picoos_uint8 strength; /* insert modified bound according to transduction symbol, if any */ if (PICODATA_ITEMINFO1_NA == orig_strength) { /* no original/fst strength given */ orig_strength = PICODATA_ITEMINFO1_BOUND_PHR0; strength = PICODATA_ITEMINFO1_BOUND_PHR0; } else { strength = fstModifiedBoundStrength(orig_strength,fst_strength); spho->headx[spho->outReadPos].boundstrength = strength; spho->headx[spho->outReadPos].sildur = -1; PICODBG_DEBUG(("setting bound strength to fst-suggested value %c (was %c)",strength, spho->headx[spho->outReadPos].boundstrength, spho->breakTime)); } /* insert modified bound according to pending break, if any */ if (spho->breakPending) { /* the calculation is based on the fst-modified value (because this is what the customer wants to * override) */ strength = breakModifiedBoundStrength(strength, spho->breakTime, (PICODATA_ITEMINFO1_BOUND_PHR1 == orig_strength)); PICODBG_DEBUG(("setting bound strength to break-imposed value %c (was %c) and time to %i",strength, spho->headx[spho->outReadPos].boundstrength, spho->breakTime)); spho->headx[spho->outReadPos].boundstrength = strength; spho->headx[spho->outReadPos].sildur = spho->breakTime; spho->breakPending = FALSE; } if (spho->headx[spho->outReadPos].boundstrength) { /* we did set a bound strength, possibly promoting or demoting a boundary; now set the phrase type * possibly also changing the phrase type of the previous phrase bound */ picoos_uint8 fromPhrase = ((PICODATA_ITEMINFO1_BOUND_PHR0 != orig_strength)); picoos_uint8 toPhrase = ((PICODATA_ITEMINFO1_BOUND_PHR0 != strength)); PICODBG_DEBUG(("setting phrase type (wasPhrase=%i, isPhrase=%i)",fromPhrase,toPhrase)); if (toPhrase) { if (fromPhrase) { spho->lastPhraseType = orig_type; } else { /*promote */ if (spho->activeStartPos <= spho->lastPhraseBoundPos) { /* we still can change prev phrase bound */ /* since a new phrase boundary is introduced, we have to 'invent' * an additional phrase type here. For that, we have to use some of the * knowledge that otherwise is handled in picoacph. */ spho->headx[spho->lastPhraseBoundPos].phrasetype = PICODATA_ITEMINFO2_BOUNDTYPE_P; } } spho->lastPhraseBoundPos = spho->outReadPos; spho->headx[spho->lastPhraseBoundPos].phrasetype = spho->lastPhraseType; } else { spho->headx[spho->outReadPos].phrasetype = PICODATA_ITEMINFO2_NA; if (fromPhrase) { /* demote */ spho->lastPhraseType = orig_type; if (spho->activeStartPos <= spho->lastPhraseBoundPos) { /* we still can change prev phrase bound */ spho->headx[spho->lastPhraseBoundPos].phrasetype = spho->lastPhraseType; } } } } } /* ***********************************************************************/ /* sphoStep function */ /* ***********************************************************************/ static picodata_step_result_t sphoStep(register picodata_ProcessingUnit this, picoos_int16 mode, picoos_uint16 * numBytesOutput) { register spho_subobj_t *spho; pico_status_t rv= PICO_OK; picoos_uint16 blen; picodata_itemhead_t ihead, ohead; picoos_uint8 *icontent; picoos_uint16 nextInPos; #if defined(PICO_DEBUG) picoos_char msgstr[SPHO_MSGSTR_SIZE]; #endif /* used in FEED and FEED_SYM */ picoos_uint16 clen; picoos_int16 pos, sym, sylsym; picoos_uint8 plane; /* used in BOUNDS */ picoos_bool breakBefore, breakAfter; /* pico_status_t rvP= PICO_OK; */ picoos_uint16 curPos /*, nextPos */; picoos_uint16 remHeadxSize, remCbufSize; if (NULL == this || NULL == this->subObj) { return PICODATA_PU_ERROR; } spho = (spho_subobj_t *) this->subObj; mode = mode; /* avoid warning "var not used in this function"*/ *numBytesOutput = 0; while (1) { /* exit via return */ PICODBG_INFO(("doing state %i, headxReadPos: %d, headxWritePos: %d", spho->procState, spho->headxReadPos, spho->headxWritePos)); switch (spho->procState) { case SPHO_STEPSTATE_INIT: /* **********************************************************************/ /* INIT */ /* **********************************************************************/ PICODBG_DEBUG(("INIT")); /* (re)set values for PARSE */ spho->penultima = SPHO_POS_INVALID; spho->activeEndPos = SPHO_POS_INVALID; spho->headxReadPos = 0; spho->phonReadPos = 0; spho->phonWritePos = 0; spho->lastPhraseType = PICODATA_ITEMINFO2_NA; spho->lastPhraseBoundPos = -1; spho->procState = SPHO_STEPSTATE_COLLECT; break; case SPHO_STEPSTATE_COLLECT: /* **********************************************************************/ /* COLLECT */ /* **********************************************************************/ /* collect state: get items from charBuf and store in * internal inBuf */ PICODBG_TRACE(("COLLECT")); rv = PICO_OK; remHeadxSize = spho->headxBufSize - spho->headxWritePos; remCbufSize = spho->cbufBufSize - spho->cbufWritePos; curPos = spho->headxWritePos; while ((PICO_OK == rv) && (remHeadxSize > 0) && (remCbufSize > 0)) { PICODBG_DEBUG(("COLLECT getting item at headxWritePos %i (remaining %i)",spho->headxWritePos, remHeadxSize)); rv = picodata_cbGetItem(this->cbIn, spho->tmpbuf, PICODATA_MAX_ITEMSIZE, &blen); if (PICO_OK == rv) { rv = picodata_get_itemparts(spho->tmpbuf, PICODATA_MAX_ITEMSIZE, &(spho->headx[spho->headxWritePos].head), &(spho->cbuf[spho->cbufWritePos]), remCbufSize, &blen); if (PICO_OK == rv) { spho->headx[spho->headxWritePos].cind = spho->cbufWritePos; spho->headx[spho->headxWritePos].boundstrength = 0; spho->headxWritePos++; remHeadxSize--; spho->cbufWritePos += blen; remCbufSize -= blen; } } } if ((PICO_OK == rv) && ((remHeadxSize <= 0) || (remCbufSize <= 0))) { rv = PICO_EXC_BUF_OVERFLOW; } /* in normal circumstances, rv is either PICO_EOF (no more items in cbIn) or PICO_BUF_OVERFLOW * (if no more items fit into headx) */ if ((PICO_EOF != rv) && (PICO_EXC_BUF_OVERFLOW != rv)) { PICODBG_DEBUG(("COLLECT ** problem getting item, unhandled, rv: %i", rv)); picoos_emRaiseException(this->common->em, rv, NULL, NULL); return PICODATA_PU_ERROR; } if (PICO_EOF == rv) { /* there are no more items available */ if (curPos < spho->headxWritePos) { /* we did get some new items */ PICODBG_DEBUG(("COLLECT read %i items", spho->headxWritePos - curPos)); spho->needMoreInput = FALSE; } if (spho->needMoreInput) { /* not enough items to proceed */ PICODBG_DEBUG(("COLLECT need more data, returning IDLE")); return PICODATA_PU_IDLE; } else { spho->procState = SPHO_STEPSTATE_PROCESS_PARSE; /* uncomment next to split into two steps */ /* return PICODATA_PU_ATOMIC; */ } } else { /* input buffer full */ PICODBG_DEBUG(("COLLECT input buffer full")); if (spho->needMoreInput) { /* forced output because we can't get more data */ spho->needMoreInput = FALSE; spho->force = TRUE; } spho->procState = SPHO_STEPSTATE_PROCESS_PARSE; } break; case SPHO_STEPSTATE_PROCESS_PARSE: /* **********************************************************************/ /* PARSE: items -> input pos/phon pairs */ /* **********************************************************************/ /* parse one item at a time */ /* If * - the item is a sentence end or * - it is the last item and force=1 or * - the phon buffer is full * then set inReadPos to 0 and go to TRANSDUCE * else advance by one item */ /* look at the current item */ PICODBG_TRACE(("PARSE")); if (spho->headxReadPos >= spho->headxWritePos) { /* no more items in headx */ if (spho->force) { PICODBG_INFO(("no more items in headx but we are forced to transduce")); /* headx is full; we are forced to transduce before reaching the sentence end */ spho->force = FALSE; if (SPHO_POS_INVALID == spho->activeEndPos) { spho->activeEndPos = spho->headxReadPos; } spho->procState = SPHO_STEPSTATE_PROCESS_TRANSDUCE; } else { /* we try to get more data */ PICODBG_INFO(("no more items in headx, try to collect more")); spho->needMoreInput = TRUE; spho->procState = SPHO_STEPSTATE_COLLECT; } break; } ihead = spho->headx[spho->headxReadPos].head; icontent = spho->cbuf + spho->headx[spho->headxReadPos].cind; PICODBG_DEBUG(("PARSE looking at item %s",picodata_head_to_string(&ihead,msgstr,SPHO_MSGSTR_SIZE))); /* treat header */ if (PICODATA_ITEM_BOUND == ihead.type) { /* see if it is a sentence end or termination boundary (flush) */ if ((PICODATA_ITEMINFO1_BOUND_SEND == ihead.info1) || (PICODATA_ITEMINFO1_BOUND_TERM == ihead.info1)) { PICODBG_INFO(("PARSE found sentence end or term BOUND")); if (spho->sentenceStarted) { /* its the end of the sentence */ PICODBG_INFO(("PARSE found sentence end")); spho->sentenceStarted = 0; /* there is no need for a right context; move the active end to the end */ /* add sentence termination phonemes */ sphoAddTermPhonemes(spho, spho->headxReadPos); spho->headxReadPos++; spho->activeEndPos = spho->headxReadPos; /* we may discard all information up to activeEndPos, after processing of last * sentence part */ spho->penultima = spho->activeEndPos; /* transduce */ spho->procState = SPHO_STEPSTATE_PROCESS_TRANSDUCE; /* uncomment to split */ /* return PICODATA_PU_BUSY; */ break; } else { if (PICODATA_ITEMINFO1_BOUND_TERM == ihead.info1) { /* its the end of input (flush) */ PICODBG_INFO(("PARSE forwarding input end (flush)")); /* copy item unmodified */ picodata_put_itemparts(&ihead, icontent, ihead.len, spho->outBuf, spho->outBufSize, &clen); spho->headxReadPos++; spho->activeEndPos = spho->headxReadPos; spho->penultima = SPHO_POS_INVALID; spho->feedFollowState = SPHO_STEPSTATE_SHIFT; spho->procState = SPHO_STEPSTATE_FEED; break; } else { /* this should never happen */ /* eliminate bound */ spho->headxReadPos++; spho->activeEndPos = spho->headxReadPos; spho->penultima = SPHO_POS_INVALID; PICODBG_ERROR(("PARSE found a sentence end without a sentence start; eliminated")); } } } else if (PICODATA_ITEMINFO1_BOUND_SBEG == ihead.info1) { /* its the start of the sentence */ PICODBG_INFO(("PARSE found sentence start")); /* add sentence starting phoneme */ sphoAddStartPhoneme(spho); spho->sentenceStarted = 1; } } if ((PICODATA_ITEM_WORDPHON == ihead.type) || (PICODATA_ITEM_BOUND == ihead.type)) { /* if it is a word or a bound try to extract phonemes */ PICODBG_INFO(("PARSE found WORD phon or phrase BOUND")); rv = sphoExtractPhonemes(this, spho, spho->headxReadPos, TRUE /* convertAccents */, &spho->suppressParseWordBound); if (PICO_OK == rv) { PICODBG_INFO(("PARSE successfully returned from phoneme extraction")); /* replace activeEndPos if the new item is a word, or activeEndPos was not set yet, or * activeEndPos was a bound */ if ((spho->activeStartPos <= spho->headxReadPos) && ((PICODATA_ITEM_WORDPHON == ihead.type) || (SPHO_POS_INVALID == spho->activeEndPos) || (PICODATA_ITEM_BOUND == spho->headx[spho->activeEndPos].head.type))) { PICODBG_INFO(("PARSE found new activeEndPos: %i,%i -> %i,%i", spho->penultima,spho->activeEndPos,spho->activeEndPos,spho->headxReadPos)); spho->penultima = spho->activeEndPos; spho->activeEndPos = spho->headxReadPos; } } else if (PICO_EXC_BUF_OVERFLOW == rv) { /* phoneme buffer cannot take this item anymore; if the phoneme buffer has some contents, we are forced to transduce before reaching the sentence end else we skip the (too long word) */ PICODBG_INFO(("PARSE returned from phoneme extraction with overflow, number of phonemes in phonBuf: %i; forced to TRANSDUCE", spho->phonWritePos)); if ((SPHO_POS_INVALID == spho->activeEndPos) || (spho->activeStartPos == spho->activeEndPos)) { spho->activeEndPos = spho->headxReadPos; } spho->procState = SPHO_STEPSTATE_PROCESS_TRANSDUCE; break; } else { PICODBG_ERROR(("PARSE returned from phoneme extraction with exception %i",rv)); return (picodata_step_result_t)picoos_emRaiseException(this->common->em, PICO_ERR_OTHER, NULL, NULL); } } else { PICODBG_INFO(("PARSE found other item, passing over")); /* it is "other" item, ignore */ } /* set pos at next item */ PICODBG_INFO(("PARSE going to next item: %i -> %i",spho->headxReadPos, spho->headxReadPos + 1)); spho->headxReadPos++; break; case SPHO_STEPSTATE_PROCESS_TRANSDUCE: /* **********************************************************************/ /* TRANSDUCE: transduction input pos/phon pairs to output pos/phon pairs */ /* **********************************************************************/ PICODBG_DEBUG(("TRANSDUCE (%i-th of %i fsts",spho->curFst+1, spho->numFsts)); /* termination condition first */ if (spho->curFst >= spho->numFsts) { #if defined(PICO_DEBUG) { PICODBG_INFO_CTX(); PICODBG_INFO_MSG(("result of all transductions: ")); PICOTRNS_PRINTSYMSEQ(this->voice->kbArray[PICOKNOW_KBID_DBG], spho->phonBufOut, spho->phonWritePos); PICODBG_INFO_MSG(("\n")); } #endif /* reset for next transduction */ spho->curFst = 0; /* prepare BOUNDS */ spho->outReadPos = 0; spho->phonReadPos = 0; spho->procState = SPHO_STEPSTATE_PROCESS_BOUNDS; break; } /* transduce from phonBufIn to PhonBufOut */ { picoos_uint32 nrSteps; #if defined(PICO_DEBUG) { PICODBG_INFO_CTX(); PICODBG_INFO_MSG(("spho trying to transduce: ")); PICOTRNS_PRINTSYMSEQ(this->voice->kbArray[PICOKNOW_KBID_DBG], spho->phonBuf, spho->phonWritePos); PICODBG_INFO_MSG(("\n")); } #endif rv = picotrns_transduce(spho->fst[spho->curFst], FALSE, picotrns_printSolution, spho->phonBuf, spho->phonWritePos, spho->phonBufOut, &spho->phonWritePos, 4*PICOTRNS_MAX_NUM_POSSYM, spho->altDescBuf, spho->maxAltDescLen, &nrSteps); if (PICO_OK == rv) { #if defined(PICO_DEBUG) { PICODBG_INFO_CTX(); PICODBG_INFO_MSG(("result of transduction: (output symbols: %i)", spho->phonWritePos)); PICOTRNS_PRINTSYMSEQ(this->voice->kbArray[PICOKNOW_KBID_DBG], spho->phonBufOut, spho->phonWritePos); PICODBG_INFO_MSG(("\n")); } #endif PICODBG_TRACE(("number of steps done in tranduction: %i", nrSteps)); } else { picoos_emRaiseWarning(this->common->em, PICO_WARN_FALLBACK,NULL,(picoos_char *)"phon buffer full"); } } /* eliminate deep epsilons */ picotrns_eliminate_epsilons(spho->phonBufOut, spho->phonWritePos, spho->phonBuf, &spho->phonWritePos,4*PICOTRNS_MAX_NUM_POSSYM); spho->curFst++; /* return PICODATA_PU_ATOMIC */ break; case SPHO_STEPSTATE_PROCESS_BOUNDS: /* ************************************************************************/ /* BOUNDS: combine input item with pos/phon pairs to insert/modify bounds */ /* ************************************************************************/ PICODBG_INFO(("BOUNDS")); /* get the suppressRecombWordBound in the left context */ spho->suppressRecombWordBound = FALSE; while (spho->outReadPos < spho->activeStartPos) { /* look at the current item */ ihead = spho->headx[spho->outReadPos].head; /* icontent = spho->cbuf + spho->headx[spho->outReadPos].cind; */ PICODBG_INFO(("in position %i, looking at item %s",spho->outReadPos,picodata_head_to_string(&ihead,msgstr,SPHO_MSGSTR_SIZE))); if (PICODATA_ITEM_BOUND == ihead.type) { spho->suppressRecombWordBound = TRUE; } else if (PICODATA_ITEM_WORDPHON == ihead.type) { spho->suppressRecombWordBound = FALSE; } spho->outReadPos++; } /* spho->outReadPos point now to the active region */ /* advance the phone reading pos to the active range */ spho->phonReadPos = 0; while (SPHO_POSSYM_OK == (rv = getNextPosSym(spho, &pos, &sym, spho->activeStartPos))) { /* ignore */ } PICODBG_INFO(("skipping left context phones results in %s", (SPHO_POSSYM_OUT_OF_RANGE==rv) ? "OUT_OF_RANGE" : (SPHO_POSSYM_END ==rv) ? "END" : "OTHER")); /* * Align input items with transduced phones and note bound stregth changes and break commands */ while (spho->outReadPos < spho->activeEndPos) { /* look at the current item */ ihead = spho->headx[spho->outReadPos].head; icontent = spho->cbuf + spho->headx[spho->outReadPos].cind; nextInPos = spho->outReadPos + 1; /* */ PICODBG_INFO(("in position %i, looking at item %s",spho->outReadPos,picodata_head_to_string(&ihead,msgstr,SPHO_MSGSTR_SIZE))); if ((PICODATA_ITEM_BOUND == ihead.type) || ((PICODATA_ITEM_WORDPHON == ihead.type) && (!spho->suppressRecombWordBound))) { /* there was a boundary originally */ picoos_uint8 orig_strength, orig_type; if (PICODATA_ITEM_BOUND == ihead.type) { orig_strength = ihead.info1; orig_type = ihead.info2; spho->suppressRecombWordBound = TRUE; } else { orig_strength = PICODATA_ITEMINFO1_BOUND_PHR0; orig_type = PICODATA_ITEMINFO2_NA; } /* i expect a boundary phone here */ /* consume FST bound phones, consider pending break and set the side-bound */ PICODBG_INFO(("got BOUND or WORDPHON item and expects corresponding phone")); rv = getNextPosSym(spho, &pos, &sym, nextInPos); if (SPHO_POSSYM_OK != rv) { PICODBG_ERROR(("unexpected symbol or unexpected end of phoneme list (%s)", (SPHO_POSSYM_OUT_OF_RANGE==rv) ? "OUT_OF_RANGE" : (SPHO_POSSYM_END ==rv) ? "END" :"OTHER")); return (picodata_step_result_t)picoos_emRaiseException(this->common->em, PICO_ERR_OTHER, NULL, NULL); } sym = picotrns_unplane(sym, &plane); /* */ PICODBG_ASSERT((PICOKFST_PLANE_PB_STRENGTHS == plane)); /* insert modified bound according to transduction and possibly pending break */ setSideBound(spho, orig_strength, orig_type, (picoos_uint8) sym); } else if ((PICODATA_ITEM_CMD == ihead.type) && (PICODATA_ITEMINFO1_CMD_SIL == ihead.info1)) { /* it's a SIL (break) command */ picoos_uint16 time; picoos_uint32 pos = 0; picoos_read_mem_pi_uint16(icontent, &pos, &time); if (spho->breakPending) { spho->breakTime += time; } else { spho->breakTime = time; spho->breakPending = TRUE; } } else if ((PICODATA_ITEM_CMD == ihead.type) && (PICODATA_ITEMINFO1_CMD_PLAY == ihead.info1)) { /* insert break of at least one ms */ if (!spho->breakPending || (spho->breakTime <= 0)) { spho->breakTime = SPHO_SMALLEST_SIL_DUR; spho->breakPending = TRUE; } setSideBound(spho, PICODATA_ITEMINFO1_NA, PICODATA_ITEMINFO2_NA, PICODATA_ITEMINFO1_NA); /* force following break to be at least one ms */ spho->breakTime = SPHO_SMALLEST_SIL_DUR; spho->breakPending = TRUE; } else if (breakStateInterrupting(&ihead, &breakBefore, &breakAfter)) { if (breakBefore &&(!spho->breakPending || (spho->breakTime <= 0))) { spho->breakTime = SPHO_SMALLEST_SIL_DUR; spho->breakPending = TRUE; } setSideBound(spho, PICODATA_ITEMINFO1_NA, PICODATA_ITEMINFO2_NA, PICODATA_ITEMINFO1_NA); if (breakAfter) { spho->breakTime = SPHO_SMALLEST_SIL_DUR; spho->breakPending = TRUE; } if (PICODATA_ITEM_WORDPHON == ihead.type) { spho->suppressRecombWordBound = FALSE; } } /* skip phones of that item */ while (SPHO_POSSYM_OK == (rv = getNextPosSym(spho, &pos, &sym, nextInPos))) { /* ignore */ } spho->outReadPos++; } /* reset for RECOMB */ spho->outReadPos = 0; spho->phonReadPos = 0; spho->suppressRecombWordBound = FALSE; spho->procState = SPHO_STEPSTATE_PROCESS_RECOMB; return PICODATA_PU_ATOMIC; break; case SPHO_STEPSTATE_PROCESS_RECOMB: /* **********************************************************************/ /* RECOMB: combine input item with pos/phon pairs to output item */ /* **********************************************************************/ PICODBG_TRACE(("RECOMB")); /* default place to come after feed: here */ spho->feedFollowState = SPHO_STEPSTATE_PROCESS_RECOMB; /* check termination condition first */ if (spho->outReadPos >= spho->activeEndPos) { PICODBG_DEBUG(("RECOMB reached active region's end at %i",spho->outReadPos)); spho->procState = SPHO_STEPSTATE_SHIFT; break; } /* look at the current item */ ihead = spho->headx[spho->outReadPos].head; icontent = spho->cbuf + spho->headx[spho->outReadPos].cind; PICODBG_DEBUG(("RECOMB looking at item %s",picodata_head_to_string(&ihead,msgstr,SPHO_MSGSTR_SIZE))); nextInPos = spho->outReadPos + 1; PICODBG_DEBUG(("RECOMB treating item in headx at pos %i",spho->outReadPos)); if (nextInPos <= spho->activeStartPos) { /* we're in the (passive) left context. Just skip it */ PICODBG_DEBUG(("RECOMB skipping item in the left context (%i <= %i)",nextInPos, spho->activeStartPos)); if (PICODATA_ITEM_BOUND == ihead.type) { spho->suppressRecombWordBound = 1; } else if (PICODATA_ITEM_WORDPHON == ihead.type) { spho->suppressRecombWordBound = 0; } /* consume possyms */ while (SPHO_POSSYM_OK == (rv = getNextPosSym(spho,&pos,&sym,nextInPos))) { /* ignore */ } if (rv == SPHO_POSSYM_INVALID) { return (picodata_step_result_t)picoos_emRaiseException(this->common->em, PICO_ERR_OTHER, NULL, NULL); } spho->outReadPos = nextInPos; } else { /* active region */ if (spho->headx[spho->outReadPos].boundstrength) { /* ***************** "side-bound" *********************/ /* copy to outbuf */ putSideBoundToOutput(spho); /* mark as processed */ spho->headx[spho->outReadPos].boundstrength = 0; /* output it */ spho->procState = SPHO_STEPSTATE_FEED; } else if (PICODATA_ITEM_BOUND == ihead.type) { /* ***************** BOUND *********************/ /* expect a boundary phone here */ PICODBG_DEBUG(("RECOMB got BOUND item and expects corresponding phone")); rv = getNextPosSym(spho, &pos, &sym, nextInPos); if (SPHO_POSSYM_OK != rv) { PICODBG_ERROR(("unexpected symbol or unexpected end of phoneme list")); return (picodata_step_result_t)picoos_emRaiseException( this->common->em, PICO_ERR_OTHER, NULL, NULL); } sym = picotrns_unplane(sym, &plane); /* */ PICODBG_ASSERT((PICOKFST_PLANE_PB_STRENGTHS == plane)); spho->suppressRecombWordBound = TRUE; /* if word following, don't need word boundary */ /* just consume item and come back here*/ spho->outReadPos = nextInPos; } else if (PICODATA_ITEM_WORDPHON == ihead.type) { /* ***************** WORDPHON *********************/ spho->wordStarted = TRUE; /* i expect a word boundary symbol in this range unless a phrase boundary was encountered before */ if (spho->suppressRecombWordBound) { PICODBG_DEBUG(("RECOMB got WORDPHON item but skips expecting BOUND")); spho->suppressRecombWordBound = FALSE; } else { PICODBG_DEBUG(("RECOMB got WORDPHON item and expects corresponding bound phone")); rv = getNextPosSym(spho, &pos, &sym, nextInPos); if (SPHO_POSSYM_OK != rv) { PICODBG_ERROR(("unexpected symbol or unexpected end of phoneme list")); return (picodata_step_result_t)picoos_emRaiseException(this->common->em, PICO_ERR_OTHER, NULL, NULL); } } spho->procState = SPHO_STEPSTATE_PROCESS_SYL; } else if ((PICODATA_ITEM_CMD == ihead.type) && (PICODATA_ITEMINFO1_CMD_SIL == ihead.info1)) { /* ***************** BREAK COMMAND *********************/ /* just consume and come back here */ PICODBG_DEBUG(("RECOMB consuming item from inBuf %i -> %i",spho->outReadPos, nextInPos)); spho->outReadPos = nextInPos; } else { /* ***************** OTHER *********************/ /* just copy item */ PICODBG_DEBUG(("RECOMB found other item, just copying")); picodata_put_itemparts(&ihead, icontent, ihead.len, spho->outBuf, spho->outBufSize, &clen); PICODBG_DEBUG(("RECOMB consuming item from inBuf %i -> %i",spho->outReadPos, nextInPos)); spho->outReadPos = nextInPos; /* and output it */ spho->procState = SPHO_STEPSTATE_FEED; } /* if (ihead.type) */ } /* return PICODATA_PU_BUSY; */ break; case SPHO_STEPSTATE_PROCESS_SYL: /* **********************************************************************/ /* SYL: combine input word item with pos/phon pairs to syl output item */ /* **********************************************************************/ /* consume all transduced phonemes with pos in in the range [spho->outReadPos,nextInPos[ */ PICODBG_DEBUG(("SYL")); spho->feedFollowState = SPHO_STEPSTATE_PROCESS_SYL; /* look at the current item */ ihead = spho->headx[spho->outReadPos].head; icontent = spho->cbuf + spho->headx[spho->outReadPos].cind; nextInPos = spho->outReadPos + 1; PICODBG_DEBUG(("SYL (1) treating item in headx at pos %i",spho->outReadPos)); /* create syllable item in ohead (head) and sylBuf (contents) */ ohead.type = PICODATA_ITEM_SYLLPHON; PICODBG_TRACE(("SYL expects accent at phonBuf[%i] = (%i,%i) (outReadPos=%i)", spho->phonReadPos, spho->phonBuf[spho->phonReadPos].pos, spho->phonBuf[spho->phonReadPos].sym,spho->outReadPos)); rv = getNextPosSym(spho,&pos,&sym,nextInPos); if (SPHO_POSSYM_OK != rv) { PICODBG_ERROR(("unexpected symbol or unexpected end of phoneme list (%i)",rv)); return (picodata_step_result_t)picoos_emRaiseException(this->common->em, PICO_ERR_OTHER, NULL, NULL); } ohead.info2 = picotrns_unplane(sym, &plane); PICODBG_ASSERT((PICOKFST_PLANE_ACCENTS == plane)); PICODBG_DEBUG(("SYL sets accent to %c", sym)); /* for the time being, we force to use POS so we can transduce all fsts in a row without reconsulting the items */ PICODBG_TRACE(("SYL expects POS")); PICODBG_DEBUG(("SYL (2) treating item in inBuf range [%i,%i[",spho->outReadPos,nextInPos)); rv = getNextPosSym(spho,&pos,&sym,nextInPos); if (SPHO_POSSYM_OK != rv) { PICODBG_ERROR(("unexpected symbol or unexpected end of phoneme list")); return (picodata_step_result_t)picoos_emRaiseException(this->common->em, PICO_ERR_OTHER, NULL, NULL); } if (spho->wordStarted) { spho->wordStarted = FALSE; ohead.info1 = picotrns_unplane(sym, &plane); /* */ PICODBG_ASSERT(PICOKFST_PLANE_POS == plane); /* */ PICODBG_DEBUG(("SYL setting POS to %c", ohead.info1)); } else { ohead.info1 = PICODATA_ITEMINFO1_NA; } PICODBG_DEBUG(("SYL (3) treating item in inBuf range [%i,%i[",spho->outReadPos,nextInPos)); /* get phonemes of that syllable; stop if syllable boundary or outside word */ sylsym = (PICOKFST_PLANE_PHONEMES << 8) + spho->syllSepId; PICODBG_DEBUG(("collecting syllable phonemes before headx position %i",nextInPos)); spho->sylWritePos = 0; while (SPHO_POSSYM_OK == (rv = getNextPosSym(spho,&pos,&sym,nextInPos)) && (sym != sylsym)) { spho->sylBuf[spho->sylWritePos++] = picotrns_unplane(sym, &plane); /* */ PICODBG_TRACE(("SYL adding phoneme to syllable: (pos %i,sym %i)[plane %i,sym %c]",pos,sym,plane,sym & 0xFF)); PICODBG_ASSERT((PICOKFST_PLANE_PHONEMES == plane)); } PICODBG_DEBUG(("SYL (4) treating item in inBuf range [%i,%i[",spho->outReadPos,nextInPos)); ohead.len = spho->sylWritePos; if (SPHO_POS_INVALID == rv) { PICODBG_ERROR(("unexpected symbol or unexpected end of phoneme list")); return (picodata_step_result_t)picoos_emRaiseException(this->common->em, PICO_WARN_INCOMPLETE, NULL, NULL); } else if ((SPHO_POSSYM_OUT_OF_RANGE == rv) || (SPHO_POSSYM_END == rv)) { PICODBG_DEBUG(("SYL arrived at end of word and/or end of phon buffer, go to next word")); spho->outReadPos = nextInPos; /* advance to next item */ spho->feedFollowState = SPHO_STEPSTATE_PROCESS_RECOMB; /* go to RECOMB after feed */ } else { PICODBG_ASSERT((sym == sylsym)); } PICODBG_DEBUG(("SYL (5) treating item in inBuf range [%i,%i[",spho->outReadPos,nextInPos)); if (ohead.len > 0) { /* prepare syllable output */ picodata_put_itemparts(&ohead, spho->sylBuf, PICODATA_BUFSIZE_DEFAULT, spho->outBuf, spho->outBufSize, &clen); spho->procState = SPHO_STEPSTATE_FEED; } else { /* skip feeding output of empty syllable */ spho->procState = spho->feedFollowState; } break; case SPHO_STEPSTATE_FEED: /* **********************************************************************/ /* FEED: output output item and proceed to feedFollowState */ /* **********************************************************************/ PICODBG_DEBUG(("FEED")); PICODBG_DEBUG(("FEED putting outBuf item into cb")); /*feeding items to PU output buffer*/ rv = picodata_cbPutItem(this->cbOut, spho->outBuf, spho->outBufSize, &clen); PICODATA_INFO_ITEM(this->voice->kbArray[PICOKNOW_KBID_DBG], (picoos_uint8 *)"spho: ", spho->outBuf, spho->outBufSize); if (PICO_EXC_BUF_OVERFLOW == rv) { /* we have to redo this item */ PICODBG_DEBUG(("FEED got overflow, returning ICODATA_PU_OUT_FULL")); return PICODATA_PU_OUT_FULL; } else if (PICO_OK == rv) { *numBytesOutput += clen; spho->procState = spho->feedFollowState; PICODBG_DEBUG(("FEED ok, going back to procState %i", spho->procState)); return PICODATA_PU_BUSY; } else { PICODBG_DEBUG(("FEED got exception %i when trying to output item",rv)); spho->procState = spho->feedFollowState; return (picodata_step_result_t)rv; } break; case SPHO_STEPSTATE_SHIFT: /* **********************************************************************/ /* SHIFT */ /* **********************************************************************/ /* If there exists a valid penultima, it should replace any left context (from 0 to activeStartPos) * else discard the current active range (from activeStartPos to activeEndPos), leaving the current * left context intact. Often, PARSE would move activeStartPos to 0, so that there is no left context * after the shift. */ PICODBG_DEBUG(("SHIFT")); if (spho->penultima != SPHO_POS_INVALID) { picoos_int16 shift; /* set penultima as new left context and set activeStartPos to the shifted activeEndPos */ PICODBG_DEBUG(( "SHIFT shifting penultima from %i to 0", spho->penultima)); shift = shift_range_left_1(spho, &spho->penultima, 0); if (shift < 0) { picoos_emRaiseException(this->common->em,PICO_ERR_OTHER,NULL,NULL); return PICODATA_PU_ERROR; } spho->activeStartPos = spho->activeEndPos - shift; spho->lastPhraseBoundPos -= shift; spho->suppressParseWordBound = FALSE; spho->suppressRecombWordBound = FALSE; } else { picoos_int16 shift; picoos_bool lastPhraseBoundActive; if (spho->activeStartPos == spho->activeEndPos) { /* no items consumed; we have to abandon left context */ spho->activeStartPos = 0; } lastPhraseBoundActive = (spho->lastPhraseBoundPos >= spho->activeStartPos); /* dummy comment */ PICODBG_DEBUG(("SHIFT shift active end from %i to %i", spho->activeEndPos, spho->activeStartPos)); shift = shift_range_left_1(spho, &spho->activeEndPos, spho->activeStartPos); if (shift < 0) { picoos_emRaiseException(this->common->em,PICO_ERR_OTHER,NULL,NULL); return PICODATA_PU_ERROR; } if (lastPhraseBoundActive) { spho->lastPhraseBoundPos -= shift; } } spho->procState = SPHO_STEPSTATE_INIT; break; default: picoos_emRaiseException(this->common->em, PICO_ERR_OTHER, NULL, NULL); return PICODATA_PU_ERROR; break; } /* switch (spho->procState) */ } /* while (1) */ /* should be never reached */ picoos_emRaiseException(this->common->em, PICO_ERR_OTHER, NULL, NULL); return PICODATA_PU_ERROR; } #ifdef __cplusplus } #endif /* end picospho.c */