1/*
2 * Copyright (C) 2008-2009 SVOX AG, Baslerstr. 30, 8048 Zuerich, Switzerland
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16/**
17 * @file picospho.c
18 *
19 * sentence phonemic/phonetic FSTs PU
20 *
21 * Copyright (C) 2008-2009 SVOX AG, Baslerstr. 30, 8048 Zuerich, Switzerland
22 * All rights reserved.
23 *
24 * History:
25 * - 2009-04-20 -- initial version
26 *
27 */
28
29#include "picoos.h"
30#include "picodbg.h"
31#include "picodata.h"
32
33#include "picoknow.h"
34#include "picokfst.h"
35#include "picoktab.h"
36#include "picotrns.h"
37
38#include "picospho.h"
39
40#ifdef __cplusplus
41extern "C" {
42#endif
43#if 0
44}
45#endif
46
47#define SPHO_BUFSIZE (3 * PICODATA_BUFSIZE_DEFAULT)
48
49
50
51#define SPHO_MAX_ALTDESC_SIZE (60 * PICOTRNS_MAX_NUM_POSSYM)
52
53
54#define SPHO_SMALLEST_SIL_DUR 1
55
56
57/** @addtogroup picospho
58 *
59 * Algorithmic description
60 * =======================
61 * The main function, sphoStep, is divided into the subprocesses (processing states) described further down.
62 *
63 * Flow control:
64 * ------------
65 * The processing flow is controlled by setting
66 *                       - 'procState' :       the next state to be processed
67 *                       - 'feedFollowState' : the state to be processed after the feed state (the feed state is treated like a primitive "subroutine")
68 *                       - some other flags
69 *
70 * Buffering:
71 * ---------
72 * - The input items are mainly stored and processed in two buffers, collectively called 'inBuf'
73 *                       - cbuf  : unstructured buffer containing item contents
74 *                       - headx : structured buffer containing item heads, each expanded by a pointer to the item contents
75 *                                 and space for a boundary potentially to be inserted (to the left of the original item)
76 * - For transduction, phonemes and their position are extracted from inBuf into
77 *                       - phonBuf,
78 *   processed there, and the resulting phonemes realigned with inBuf.
79 * - Word items are split into syllables, stored in
80 *                       - sylBuf
81 * - Items to be output are stored in outBuf
82 *
83 * Windowing:
84 * ---------
85 *   Optimal solutions are achieved if a whole sentence is processed at once. However, if any of the buffers are too small,
86 *   only sentence parts are processed. To improve the quality of such sub-optimal solutions, a moving-window-with-overlap is applied:
87 *   - [0,headxReadPos[              : the window considered for transduction
88 *   - [activeStartPos,activeEndPos[ : the "active" subrange of the window actually used for output
89 *   - penultima                     : the position (within the active range) that should be used as new window start when shifting the window
90 *
91 * After PROCESS_PARSE:
92 *   0             activeStartPos      penultima    activeEndPos   headxReadPos              headxWritePos
93 *  |             |                   |            |              |                         |
94 *  |-------------=================================---------------|                                         ['----': context '====' : active subrange)
95 *
96 * After PROCESS_SHIFT:
97 *                                     0            activeStartPos                           headWritePos
98 *                  |                 |            |                                        |
99 *                                    |------------... (only left context is known; new active range,  penultima, and right context to be established at next parse)
100 *
101 * Processing states:
102 * -----------------
103 * - INIT              : initialize state variables
104 * - COLLECT           : collect items into internal buffers ("inBuf")
105 * - PROCESS_PARSE     : go through inBuf items and extract position/phoneme pairs into phoneme buffer 'phonBuf'
106 *                       word boundary phonemes are inserted between words
107 * - PROCESS_TRANSDUCE : transduce phonBuf
108 * - PROCESS_BOUNDS    : go through inBuf items again and match against transduced pos/phoneme
109 *                       this is the first round of alignment, only inserting/deleting/modifying bounds, according to
110 *                       - existing BOUND items
111 *                       - newly produced word bounds separating WORDPHON items
112 *                       - bound upgrades/downgrades from transduction
113 *                       - bound upgrades/downgrades/insertions from SIL command items (originating e.g. from <break> text commands)
114 *                       all relevant bounds are placed in the corresponding headx extention; original bound items become invalid.
115 * - PROCESS_RECOMB    : go through inBuf items again and match against transduced pos/phoneme
116 *                       this is the second round of alignment, treating non-BOUND items
117 *                       - WORDPHONs are broken into syllables by "calling" PROCESS_SYL
118 *                       - "side-bounds" (in the headx extension) are output by "calling" FEED
119 *                       - BOUND items are consumed with no effect
120 *                       - other items are output unchanged "calling" FEED
121 * - PROCESS_SYL       : the WORDPHON coming from RECOMB is matched against the phonBuf and (new) SYLLPHON items
122 *                       are created. (the original wordphon is consumed)
123 * - FEED              : feeds one item and returns to spho->feedFollowState
124 * - SHIFT             : items in inBuf are shifted left to make room for new items. If a sentence doesn't fit
125 *                       inBuf in its entirety, left and/or right contexts are kept so they can be considered in
126 *                       the next transduction.
127 */
128
129
130
131/* PU sphoStep states */
132#define SPHO_STEPSTATE_INIT               0
133#define SPHO_STEPSTATE_COLLECT            1
134#define SPHO_STEPSTATE_PROCESS_PARSE      2
135#define SPHO_STEPSTATE_PROCESS_TRANSDUCE  3
136#define SPHO_STEPSTATE_PROCESS_BOUNDS     4
137#define SPHO_STEPSTATE_PROCESS_RECOMB     5
138#define SPHO_STEPSTATE_PROCESS_SYL        6
139#define SPHO_STEPSTATE_FEED               7
140#define SPHO_STEPSTATE_SHIFT              8
141
142#define SPHO_POS_INVALID (PICOTRNS_POS_INVALID)   /* indicates that no position was set yet */
143
144/* nr item restriction: maximum number of extended item heads in headx */
145#define SPHO_MAXNR_HEADX    60
146
147/* nr item restriction: maximum size of all item contents together in cont */
148#define SPHO_MAXSIZE_CBUF (30 * 255)
149
150/* "expanded head": item head expanded by a content position and a by boundary information
151 *  potentially inserted "to the left" of the item */
152typedef struct {
153    picodata_itemhead_t head;
154    picoos_uint16 cind;
155    picoos_uint8 boundstrength; /* bstrength to the left, 0 if not set */
156    picoos_uint8 phrasetype; /* btype for following phrase, 0 if not set */
157    picoos_int16 sildur; /* silence duration for boundary, -1 if not set */
158} picospho_headx_t;
159
160
161
162#define SPHO_MSGSTR_SIZE 32
163
164/** object       : SentPhoUnit
165 *  shortcut     : spho
166 *  derived from : picodata_ProcessingUnit
167 */
168typedef struct spho_subobj {
169    picoos_Common common;
170
171    /* we use int16 for buffer positions so we can indicate exceptional positions (invalid etc.) with negative
172     * integers */
173    picoos_uint8 procState; /* for next processing step decision */
174
175    /* buffer for item headers */
176    picoos_uint8 tmpbuf[PICODATA_MAX_ITEMSIZE]; /* tmp. location for an item */
177
178    picospho_headx_t headx[SPHO_MAXNR_HEADX]; /* "expanded head" buffer */
179    picoos_uint16 headxBufSize; /* actually allocated size (if one day headxBuf is allocated dynamically) */
180    picoos_uint16 headxReadPos, headxWritePos;
181
182    picoos_uint8 cbuf[SPHO_MAXSIZE_CBUF];
183    picoos_uint16 cbufBufSize; /* actually allocated size */
184    picoos_uint16 cbufWritePos; /* next position to write to, 0 if buffer empty */
185
186    picoos_uint8 outBuf[PICODATA_BUFSIZE_DEFAULT]; /* internal output buffer to hold just one item */
187    picoos_uint16 outBufSize; /* actually allocated size (if one day outBuf is allocated dynamically) */
188    picoos_uint16 outReadPos; /* next pos to read from inBuf for output */
189
190    /* picoos_int16 outWritePos; */ /* next pos to output from in buf */
191
192    picoos_uint8 sylBuf[255]; /* internal buffer to hold contents of syl item to be output */
193    picoos_uint8 sylReadPos, sylWritePos; /* next pos to read from sylBuf, next pos to write to sylBuf */
194
195    /* buffer for internal calculation of transducer */
196    picotrns_AltDesc altDescBuf;
197    /* the number of AltDesc in the buffer */
198    picoos_uint16 maxAltDescLen;
199
200    /* the input to a transducer should not be larger than PICOTRNS_MAX_NUM_POSSYM
201     * so the output may expand (up to 4*PICOTRNS_MAX_NUM_POSSYM) */
202
203    picotrns_possym_t phonBufA[4 * PICOTRNS_MAX_NUM_POSSYM + 1];
204    picotrns_possym_t phonBufB[4 * PICOTRNS_MAX_NUM_POSSYM + 1];
205    picotrns_possym_t * phonBuf;
206    picotrns_possym_t * phonBufOut;
207    picoos_uint16 phonReadPos, phonWritePos; /* next pos to read from phonBufIn, next pos to write to phonBufIn */
208
209    picoos_int16 activeStartPos; /* start position of items to be treated (at end of left context) */
210    picoos_int16 penultima, activeEndPos; /* positions of last two bounds/words; SPHO_POS_INVALID means uninitialized */
211    picoos_int16 lastPhraseBoundPos; /* position of the last bound encountered (<0 if inexistent or not reachable */
212    picoos_uint8 lastPhraseType; /* phrase type of the last phrase boundary, 0 if not set */
213
214    picoos_uint8 needMoreInput, /* more data necessary to decide on token */
215    suppressParseWordBound, /* dont produce word boundary */
216    suppressRecombWordBound, /* dont produce word boundary */
217    breakPending, /* received a break but didn't interpret it yet */
218    /* sentEnd, */ /* sentence end detected */
219    force, /* in forced state */
220    wordStarted, /* is it the first syl in the word: expect POS */
221    sentenceStarted;
222
223    picoos_uint16 breakTime; /* time argument of the pending break command */
224
225    picoos_uint8 feedFollowState; /* where to return after feed */
226
227    /* fst knowledge bases */
228    picoos_uint8 numFsts;
229    picokfst_FST fst[PICOKNOW_MAX_NUM_SPHO_FSTS];
230    picoos_uint8 curFst; /* the fst to be applied next */
231
232    /* fixed ids knowledge base */
233    picoktab_FixedIds fixedIds;
234
235    /* phones kb */
236    picoktab_Phones phones;
237
238    /* some soecial ids from phones */
239    picoos_uint8 primStressId, secondStressId, syllSepId;
240
241} spho_subobj_t;
242
243
244static pico_status_t sphoReset(register picodata_ProcessingUnit this)
245{
246
247    spho_subobj_t * spho;
248
249    if (NULL == this || NULL == this->subObj) {
250        return picoos_emRaiseException(this->common->em,
251                                       PICO_ERR_NULLPTR_ACCESS, NULL, NULL);
252    }
253    spho = (spho_subobj_t *) this->subObj;
254
255    spho->curFst = 0;
256
257/* processing state */
258    spho->procState = SPHO_STEPSTATE_INIT;
259    spho->needMoreInput = TRUE;
260    spho->suppressParseWordBound = FALSE;
261    spho->suppressRecombWordBound = FALSE;
262    spho->breakPending = FALSE;
263    spho->force = 0;
264    spho->sentenceStarted = 0;
265
266
267    /* item buffer headx/cbuf */
268    spho->headxBufSize = SPHO_MAXNR_HEADX;
269    spho->headxReadPos = 0;
270    spho->headxWritePos = 0;
271
272    spho->cbufWritePos = 0;
273    spho->cbufBufSize = SPHO_MAXSIZE_CBUF;
274
275    /* possym buffer */
276    spho->phonBuf = spho->phonBufA;
277    spho->phonBufOut = spho->phonBufB;
278    spho->phonReadPos = 0;
279
280    /* overlapping */
281    spho->activeStartPos = 0;
282    spho->penultima = SPHO_POS_INVALID;
283    spho->activeEndPos = SPHO_POS_INVALID;
284
285    return PICO_OK;
286}
287
288
289static pico_status_t sphoInitialize(register picodata_ProcessingUnit this, picoos_int32 resetMode)
290{
291    picoos_uint8 i;
292    spho_subobj_t * spho;
293    picokfst_FST fst;
294
295    picoknow_kb_id_t myKbIds[PICOKNOW_MAX_NUM_SPHO_FSTS] = PICOKNOW_KBID_SPHO_ARRAY;
296
297    PICODBG_DEBUG(("init"));
298
299    if (NULL == this || NULL == this->subObj) {
300        return picoos_emRaiseException(this->common->em,
301                                       PICO_ERR_NULLPTR_ACCESS, NULL, NULL);
302    }
303
304    spho = (spho_subobj_t *) this->subObj;
305
306    spho->numFsts = 0;
307
308    spho->curFst = 0;
309
310    for (i = 0; i<PICOKNOW_MAX_NUM_SPHO_FSTS; i++) {
311        fst = picokfst_getFST(this->voice->kbArray[myKbIds[i]]);
312        if (NULL != fst) {
313            spho->fst[spho->numFsts++] = fst;
314        }
315    }
316    spho->fixedIds = picoktab_getFixedIds(this->voice->kbArray[PICOKNOW_KBID_FIXED_IDS]);
317    spho->phones = picoktab_getPhones(this->voice->kbArray[PICOKNOW_KBID_TAB_PHONES]);
318
319    spho->syllSepId = picoktab_getSyllboundID(spho->phones);
320    spho->primStressId = picoktab_getPrimstressID(spho->phones);
321    spho->secondStressId = picoktab_getSecstressID(spho->phones);
322
323    PICODBG_DEBUG(("got %i fsts", spho->numFsts));
324
325
326    return sphoReset(this);
327
328}
329
330static picodata_step_result_t sphoStep(register picodata_ProcessingUnit this,
331        picoos_int16 mode, picoos_uint16 *numBytesOutput);
332
333
334
335
336static pico_status_t sphoTerminate(register picodata_ProcessingUnit this)
337{
338    return PICO_OK;
339}
340
341
342static pico_status_t sphoSubObjDeallocate(register picodata_ProcessingUnit this,
343        picoos_MemoryManager mm)
344{
345    spho_subobj_t * spho;
346
347    spho = (spho_subobj_t *) this->subObj;
348
349    if (NULL != this) {
350        if (NULL != this->subObj) {
351            spho = (spho_subobj_t *) (this->subObj);
352            picotrns_deallocate_alt_desc_buf(spho->common->mm,&spho->altDescBuf);
353            picoos_deallocate(mm, (void *) &this->subObj);
354        }
355    }
356    return PICO_OK;
357}
358
359picodata_ProcessingUnit picospho_newSentPhoUnit(picoos_MemoryManager mm,
360        picoos_Common common, picodata_CharBuffer cbIn,
361        picodata_CharBuffer cbOut, picorsrc_Voice voice)
362{
363    spho_subobj_t * spho;
364
365    picodata_ProcessingUnit this = picodata_newProcessingUnit(mm, common, cbIn, cbOut, voice);
366    if (this == NULL) {
367        return NULL;
368    }
369
370    this->initialize = sphoInitialize;
371    this->step = sphoStep;
372    this->terminate = sphoTerminate;
373    this->subDeallocate = sphoSubObjDeallocate;
374
375    this->subObj = picoos_allocate(mm, sizeof(spho_subobj_t));
376    if (this->subObj == NULL) {
377        picoos_deallocate(mm, (void **)(void*)&this);
378        return NULL;
379    }
380    spho = (spho_subobj_t *) this->subObj;
381
382    spho->common = this->common;
383
384    /* these are given by the pre-allocated array sizes */
385    spho->outBufSize = PICODATA_BUFSIZE_DEFAULT;
386
387
388    spho->altDescBuf = picotrns_allocate_alt_desc_buf(spho->common->mm, SPHO_MAX_ALTDESC_SIZE, &spho->maxAltDescLen);
389    if (NULL == spho->altDescBuf) {
390        picotrns_deallocate_alt_desc_buf(spho->common->mm,&spho->altDescBuf);
391        picoos_emRaiseException(spho->common->em,PICO_EXC_OUT_OF_MEM, NULL,NULL);
392        return NULL;
393    }
394
395    sphoInitialize(this, PICO_RESET_FULL);
396    return this;
397}
398
399
400/* ***********************************************************************/
401/*                          process buffered item list                   */
402/* ***********************************************************************/
403
404
405/* shift relevant data in headx/'cbuf' (between 'readPos' incl and writePos non-incl) to 'start'.
406 * modify read/writePos accordingly */
407static picoos_int16 shift_range_left_1(spho_subobj_t *spho, picoos_int16 * from, picoos_int16 to)
408{
409
410    /* remember shift parameters for cbuf */
411    picoos_uint16
412        c_i,
413        c_j,
414        c_diff,
415        c_writePos,
416        i,
417        j,
418        diff,
419        writePos;
420    i = to;
421    j = *from;
422    diff = j-i;
423    writePos = spho->headxWritePos;
424    c_i = spho->headx[to].cind;
425    if (j < writePos) {
426      c_j = spho->headx[j].cind;
427    } else {
428        c_j = spho->cbufWritePos;
429    }
430    c_diff = c_j - c_i;
431    c_writePos = spho->cbufWritePos;
432
433    PICODBG_DEBUG((
434                    "shifting buffer region [%i,%i[ down to %i",*from, writePos, to
435                    ));
436
437
438    /* PICODBG_ASSERT((i<j)); */
439    if (i > j) {
440        return -1;
441    }
442    /* shift cbuf */
443    while (c_j < c_writePos) {
444        spho->cbuf[c_i++] = spho->cbuf[c_j++];
445    }
446    /* shift headx */
447    while (j < writePos) {
448        spho->headx[j].cind -= c_diff;
449        spho->headx[i++] = spho->headx[j++];
450    }
451    spho->headxWritePos -= diff;
452    *from = to;
453    spho->cbufWritePos -= c_diff;
454    /*  */
455    PICODBG_DEBUG((
456                    "readPos,WritePos are now [%i,%i[, returning shift amount %i",*from, spho->headxWritePos, diff
457            ));
458    return diff;
459}
460
461static pico_status_t sphoAddPhoneme(register spho_subobj_t *spho, picoos_int16 pos, picoos_int16 sym) {
462    picoos_uint8 plane, unshifted;
463    /* just for debuging */
464    unshifted = picotrns_unplane(sym,&plane);
465    PICODBG_TRACE(("adding %i/%i (%c on plane %i) at phonBuf[%i]",pos,sym,unshifted,plane,spho->phonWritePos));
466    if (2* PICOTRNS_MAX_NUM_POSSYM <= spho->phonWritePos) {
467        /* not an error! */
468        PICODBG_DEBUG(("couldn't add because phon buffer full"));
469        return PICO_EXC_BUF_OVERFLOW;
470    } else {
471        spho->phonBuf[spho->phonWritePos].pos = pos;
472        spho->phonBuf[spho->phonWritePos].sym = sym;
473        spho->phonWritePos++;
474        return PICO_OK;
475    }
476}
477
478static pico_status_t sphoAddStartPhoneme(register spho_subobj_t *spho) {
479    return sphoAddPhoneme(spho, PICOTRNS_POS_IGNORE,
480            (PICOKFST_PLANE_INTERN << 8) + spho->fixedIds->phonStartId);
481}
482
483static pico_status_t sphoAddTermPhonemes(register spho_subobj_t *spho, picoos_uint16 pos) {
484    return sphoAddPhoneme(spho, pos,
485            (PICOKFST_PLANE_PB_STRENGTHS << 8) + PICODATA_ITEMINFO1_BOUND_SEND)
486            && sphoAddPhoneme(spho, PICOTRNS_POS_IGNORE,
487                    (PICOKFST_PLANE_INTERN << 8) + spho->fixedIds->phonTermId);
488}
489
490/* return "syllable accent" (or prominence) symbol, given "word accent" symbol 'wacc' and stress value (no=0, primary=1, secondary=2) */
491static picoos_uint16 sphoGetSylAccent(register spho_subobj_t *spho,
492        picoos_uint8 wacc, picoos_uint8 sylStress)
493{
494    PICODBG_ASSERT(sylStress <= 2);
495
496    spho = spho;        /* avoid warning "var not used in this function"*/
497
498    switch (sylStress) {
499        case 0: /* non-stressed syllable gets no prominence */
500            /* return spho->fixedIds->accId[0]; */
501            return PICODATA_ACC0;
502            break;
503        case 1: /* primary-stressed syllable gets word prominence */
504            return wacc;
505            break;
506        case 2: /* secondary-stressed syllable gets no prominence or secondary stress prom. (4) */
507            return (PICODATA_ACC0 == wacc) ? PICODATA_ACC0
508                     : PICODATA_ACC4;
509            /*return (spho->fixedIds->accId[0] == wacc) ? spho->fixedIds->accId[0]
510                     : spho->fixedIds->accId[4]; */
511             break;
512        default:
513            /* never occurs :-) */
514            return PICODATA_ACC0;
515            break;
516    }
517}
518
519
520/* ***********************************************************************/
521/*                          extract phonemes of an item into a phonBuf   */
522/* ***********************************************************************/
523static pico_status_t sphoExtractPhonemes(register picodata_ProcessingUnit this,
524        register spho_subobj_t *spho, picoos_uint16 pos,
525        picoos_uint8 convertAccents, picoos_uint8 * suppressWB)
526{
527    pico_status_t rv = PICO_OK;
528    picoos_uint16 i, j;
529    picoos_int16 fstSymbol;
530    picoos_uint8 curStress;
531    picotrns_possym_t tmpPosSym;
532    picoos_uint16 oldPos, curPos;
533    picodata_itemhead_t * head;
534    picoos_uint8* content;
535
536#if defined(PICO_DEBUG)
537    picoos_char msgstr[SPHO_MSGSTR_SIZE];
538#endif
539
540
541    /*
542     Items considered in a transduction are a BOUND or a WORDPHON item. its starting offset within the
543     headxBuf is given as 'pos'.
544     Elements that go into the transduction receive "their" position in the buffer.
545     */
546
547    oldPos = spho->phonWritePos;
548
549    head = &(spho->headx[pos].head);
550    content = spho->cbuf + spho->headx[pos].cind;
551
552    PICODBG_TRACE(("doing item %s\n",
553            picodata_head_to_string(head,msgstr,SPHO_MSGSTR_SIZE)));
554
555    switch (head->type) {
556        case PICODATA_ITEM_BOUND:
557            /* map SBEG, SEND and TERM (as sentence closing) to SEND */
558            fstSymbol = (PICODATA_ITEMINFO1_BOUND_SBEG == head->info1 || PICODATA_ITEMINFO1_BOUND_TERM == head->info1) ? PICODATA_ITEMINFO1_BOUND_SEND : head->info1;
559            PICODBG_TRACE(("found bound of type %c\n",head->info1));
560           /* BOUND(<bound strength><phrase type>) */
561            /* insert bound strength */
562            PICODBG_TRACE(("inserting phrase bound phoneme %c and setting suppresWB=1\n",fstSymbol));
563            fstSymbol += (PICOKFST_PLANE_PB_STRENGTHS << 8);
564            rv = sphoAddPhoneme(spho,pos,fstSymbol);
565            /* phrase type not used */
566            /* suppress next word boundary */
567            (*suppressWB) = 1;
568            break;
569
570        case PICODATA_ITEM_WORDPHON:
571            /* WORDPHON(POS,WACC)phon */
572            PICODBG_TRACE(("found WORDPHON"));
573            /* insert word boundary if not suppressed */
574            if (!(*suppressWB)) {
575                fstSymbol = (PICOKFST_PLANE_PB_STRENGTHS << 8) + PICODATA_ITEMINFO1_BOUND_PHR0;
576                PICODBG_TRACE(("adding word boundary phone"));
577                rv = sphoAddPhoneme(spho,pos,fstSymbol);
578            }
579            (*suppressWB) = 0;
580            /* for the time being, we force to use POS so we can transduce all fsts in a row without reconsulting the items */
581
582
583            /* If 'convertAccents' then the accentuation is not directly encoded. It rather influences the mapping of
584             * the word accent symbol to the actual accent phoneme which is put after the syllable separator. */
585            if (convertAccents) {
586                PICODBG_TRACE(("converting accents"));
587                /* extracting phonemes IN REVERSE order replacing syllable symbols with prominence symbols */
588                curPos = spho->phonWritePos;
589                curStress = 0; /* no stress */
590                for (i = head->len; i > 0 ;) {
591                    i--;
592                    if (spho->primStressId == content[i]) {
593                        curStress = 1;
594                        PICODBG_DEBUG(("skipping primary stress at pos %i (in 1 .. %i)",i, head->len));
595                        continue; /* skip primary stress symbol */
596                    } else if (spho->secondStressId == content[i]) {
597                        curStress = 2;
598                        PICODBG_DEBUG(("skipping secondary stress at pos %i (in 1 .. %i)",i, head->len));
599                        continue; /* skip secundary stress symbol */
600                    } else if (spho->syllSepId == content[i]) {
601                        fstSymbol = (PICOKFST_PLANE_POS << 8) + head->info1;
602                        rv = sphoAddPhoneme(spho, pos, fstSymbol);
603                        /* replace syllSepId by combination of syllable stress and word prominence */
604                        fstSymbol = sphoGetSylAccent(spho,head->info2,curStress);
605                        curStress = 0;
606                        /* add accent */
607                        fstSymbol += (PICOKFST_PLANE_ACCENTS << 8);
608                        rv = sphoAddPhoneme(spho,pos,fstSymbol);
609                        if (PICO_OK != rv) {
610                            break;
611                        }
612                       /* and keep syllable boundary */
613                        fstSymbol = (PICOKFST_PLANE_PHONEMES << 8) + content[i];
614                    } else {
615                        /* normal phoneme */
616                        fstSymbol = (PICOKFST_PLANE_PHONEMES << 8) + content[i];
617                    }
618                    if (PICO_OK == rv) {
619                        rv = sphoAddPhoneme(spho,pos,fstSymbol);
620                    }
621                }
622                if (PICO_OK == rv) {
623                    /* bug 366: we position the "head" into the item header and not on the first phoneme
624                     * because there might be no phonemes at all */
625                    /* insert head of the first syllable of a word */
626                         fstSymbol = (PICOKFST_PLANE_POS << 8) + head->info1;
627                        rv = sphoAddPhoneme(spho,pos,fstSymbol);
628                    fstSymbol = sphoGetSylAccent(spho,head->info2,curStress);
629                    curStress = 0;
630                   fstSymbol += (PICOKFST_PLANE_ACCENTS << 8);
631                   rv = sphoAddPhoneme(spho,pos,fstSymbol);
632                }
633                if (PICO_OK == rv) {
634                    /* invert sympos portion */
635                    i = curPos;
636                    j=spho->phonWritePos-1;
637                    while (i < j) {
638                        tmpPosSym.pos = spho->phonBuf[i].pos;
639                        tmpPosSym.sym = spho->phonBuf[i].sym;
640                        spho->phonBuf[i].pos = spho->phonBuf[j].pos;
641                        spho->phonBuf[i].sym = spho->phonBuf[j].sym;
642                        spho->phonBuf[j].pos = tmpPosSym.pos;
643                        spho->phonBuf[j].sym = tmpPosSym.sym;
644                        i++;
645                        j--;
646                    }
647                }
648            } else { /* convertAccents */
649                for (i = 0; i <head->len; i++) {
650                    fstSymbol = (PICOKFST_PLANE_PHONEMES << 8) + content[i];
651                    rv = sphoAddPhoneme(spho,pos,fstSymbol);
652                }
653            }
654            break;
655        default:
656            picoos_emRaiseException(this->common->em,rv,NULL,NULL);
657            break;
658    } /* switch(head->type) */
659    if (PICO_OK != rv) {
660        spho->phonWritePos = oldPos;
661    }
662    return rv;
663}
664
665
666
667
668
669#define SPHO_POSSYM_OK           0
670#define SPHO_POSSYM_OUT_OF_RANGE 1
671#define SPHO_POSSYM_END          2
672#define SPHO_POSSYM_INVALID     -3
673/* *readPos is the next position in phonBuf to be read, and *writePos is the first position not to be read (may be outside
674 * buf).
675 * 'rangeEnd' is the first possym position outside the desired range.
676 * Possible return values:
677 * SPHO_POSSYM_OK            : 'pos' and 'sym' are set to the read possym, *readPos is advanced
678 * SPHO_POSSYM_OUT_OF_RANGE  : pos is out of range. 'pos' is set to that of the read possym, 'sym' is undefined
679 * SPHO_POSSYM_UNDERFLOW     : no more data in buf. 'pos' is set to PICOTRNS_POS_INVALID,    'sym' is undefined
680 * SPHO_POSSYM_INVALID       : "strange" pos.       'pos' is set to PICOTRNS_POS_INVALID,    'sym' is undefined
681 */
682static pico_status_t getNextPosSym(spho_subobj_t * spho, picoos_int16 * pos, picoos_int16 * sym,
683        picoos_int16 rangeEnd) {
684    /* skip POS_IGNORE */
685    while ((spho->phonReadPos < spho->phonWritePos) && (PICOTRNS_POS_IGNORE == spho->phonBuf[spho->phonReadPos].pos))  {
686        PICODBG_DEBUG(("ignoring phone at spho->phonBuf[%i] because it has pos==IGNORE",spho->phonReadPos));
687        spho->phonReadPos++;
688    }
689    if ((spho->phonReadPos < spho->phonWritePos)) {
690        *pos = spho->phonBuf[spho->phonReadPos].pos;
691        if ((PICOTRNS_POS_INSERT == *pos) || ((0 <= *pos) && (*pos < rangeEnd))) {
692            *sym = spho->phonBuf[spho->phonReadPos++].sym;
693            return SPHO_POSSYM_OK;
694        } else if (*pos < 0){ /* *pos is "strange" (e.g. POS_INVALID) */
695            return SPHO_POSSYM_INVALID;
696        } else {
697            return SPHO_POSSYM_OUT_OF_RANGE;
698        }
699    } else {
700        /* no more possyms to read */
701        *pos = PICOTRNS_POS_INVALID;
702        return SPHO_POSSYM_END;
703    }
704}
705
706
707
708/** Calculate bound strength modified by transduction
709 *
710 * Given the original bound strength 'orig' and the desired target strength 'target' (suggested by fst),
711 *  calculate the modified bound strength.
712 *
713 * @param orig  original bound strength
714 * @param target target bound strength
715 * @return resulting bound strength
716 */
717static picoos_uint8 fstModifiedBoundStrength(picoos_uint8 orig, picoos_uint8 target)
718{
719    switch (orig) {
720        case PICODATA_ITEMINFO1_BOUND_PHR1:
721        case PICODATA_ITEMINFO1_BOUND_PHR2:
722            /* don't allow primary phrase bounds to be demoted to word bound */
723            if (PICODATA_ITEMINFO1_BOUND_PHR0 == target) {
724                return PICODATA_ITEMINFO1_BOUND_PHR3;
725            }
726        case PICODATA_ITEMINFO1_BOUND_PHR0:
727        case PICODATA_ITEMINFO1_BOUND_PHR3:
728            return target;
729            break;
730        default:
731            /* don't allow bounds other than phrase or word bounds to be changed */
732            return orig;
733            break;
734    }
735}
736
737/** Calculate bound strength modified by a \<break> command
738 *
739 * Given the original (predicted and possibly fst-modified) bound strength, and a time value from an
740 * overwriding \<break> command, calculate the modified bound strength.
741 *
742 * @param orig original bound strength
743 * @param time time given as property of \<break> command
744 * @param wasPrimary
745 * @return modified bound strength
746 */
747static picoos_uint8 breakModifiedBoundStrength(picoos_uint8 orig, picoos_uint16 time, picoos_bool wasPrimary)
748{
749    picoos_uint8 modified = (0 == time) ? PICODATA_ITEMINFO1_BOUND_PHR3 :
750        (50 < time) ? PICODATA_ITEMINFO1_BOUND_PHR1 : PICODATA_ITEMINFO1_BOUND_PHR2;
751    switch (orig) {
752        /* for word and phrase breaks, return 'modified', unless a non-silence gets time==0, in which
753         * case return no break (word break) */
754        case PICODATA_ITEMINFO1_BOUND_PHR0:
755            if (0 == time) {
756                return PICODATA_ITEMINFO1_BOUND_PHR0;
757            }
758        case PICODATA_ITEMINFO1_BOUND_PHR3:
759            if (!wasPrimary && (0 == time)) {
760                return PICODATA_ITEMINFO1_BOUND_PHR0;
761            }
762        case PICODATA_ITEMINFO1_BOUND_PHR1:
763        case PICODATA_ITEMINFO1_BOUND_PHR2:
764            return modified;
765            break;
766        default:
767            return orig;
768            break;
769    }
770}
771
772static picoos_bool breakStateInterrupting(picodata_itemhead_t * head,
773        picoos_bool * breakBefore, picoos_bool * breakAfter) {
774
775    picoos_bool result = 1;
776
777    *breakBefore = 0;
778    *breakAfter = 0;
779
780    if (PICODATA_ITEM_WORDPHON == head->type) {
781
782    } else if (PICODATA_ITEM_CMD == head->type) {
783        if ((PICODATA_ITEMINFO1_CMD_PLAY == head->info1)
784                || (PICODATA_ITEMINFO1_CMD_SAVE == head->info1)
785                || (PICODATA_ITEMINFO1_CMD_UNSAVE == head->info1)) {
786            *breakBefore = 1;
787            *breakAfter = 1;
788        } else if (PICODATA_ITEMINFO1_CMD_SAVE == head->info1) {
789            *breakBefore = 1;
790        } else if (PICODATA_ITEMINFO1_CMD_UNSAVE == head->info1) {
791            *breakAfter = 1;
792        } else if (PICODATA_ITEMINFO1_CMD_IGNSIG == head->info1) {
793            if (PICODATA_ITEMINFO2_CMD_START == head->info2) {
794                *breakBefore = 1;
795            } else {
796                *breakAfter = 1;
797            }
798        }
799    } else {
800        result = 0;
801    }
802    return result;
803}
804
805
806static void putSideBoundToOutput(spho_subobj_t * spho)
807{
808
809    picodata_itemhead_t ohead;
810    picoos_uint8 ocontent[2*sizeof(picoos_uint16)];
811    picoos_int16 sildur;
812    picoos_uint16 clen;
813
814    /* create boundary */
815    ohead.type = PICODATA_ITEM_BOUND;
816    ohead.info1 = spho->headx[spho->outReadPos].boundstrength;
817    ohead.info2 = spho->headx[spho->outReadPos].phrasetype;
818    sildur = spho->headx[spho->outReadPos].sildur;
819    if ((sildur < 0)
820            || (PICODATA_ITEMINFO1_BOUND_PHR0 == ohead.info1)
821            || (PICODATA_ITEMINFO1_BOUND_PHR3 == ohead.info1)) {
822        PICODBG_DEBUG(("outputting a bound of strength '%c' and type '%c' without duration constraints",ohead.info1, ohead.info2));
823        ohead.len = 0;
824    } else {
825        picoos_uint32 pos = 0;
826        picoos_write_mem_pi_uint16(ocontent,&pos,sildur);
827        picoos_write_mem_pi_uint16(ocontent,&pos,sildur);
828        PICODBG_DEBUG(("outputting a bound of strength '%c' and type '%c' with duration constraints [%i,%i]",ohead.info1, ohead.info2,sildur, sildur));
829        ohead.len = pos;
830    }
831    picodata_put_itemparts(&ohead, ocontent, ohead.len,
832            spho->outBuf, spho->outBufSize, &clen);
833    /* disable side bound */
834    spho->headx[spho->outReadPos].boundstrength = 0;
835}
836
837/** Set bound strength and sil dur.
838 *
839 * given the original bound strength 'orig_strength' and the fst-suggested bound strength 'fst_strength'
840 * and possibly being in a pending break state, calculate the resulting bound strength and set boundstrength
841 * and sildur of the current item (spho->headx[spho->outReadPos]) accordingly.
842 * if a boundstrength was set, also calculate the phrasetype and if necessary (and reachable), modify the phrase type
843 * of the previous phrase boundary.
844 *
845 * @param spho
846 * @param orig_strength
847 * @param orig_type
848 * @param fst_strength
849 */
850static void setSideBound(spho_subobj_t * spho, picoos_uint8 orig_strength, picoos_uint8 orig_type, picoos_uint8 fst_strength) {
851    picoos_uint8 strength;
852
853    /* insert modified bound according to transduction symbol, if any */
854    if (PICODATA_ITEMINFO1_NA == orig_strength) {
855        /* no original/fst strength given */
856        orig_strength = PICODATA_ITEMINFO1_BOUND_PHR0;
857        strength = PICODATA_ITEMINFO1_BOUND_PHR0;
858    } else {
859        strength = fstModifiedBoundStrength(orig_strength,fst_strength);
860        spho->headx[spho->outReadPos].boundstrength = strength;
861        spho->headx[spho->outReadPos].sildur = -1;
862        PICODBG_DEBUG(("setting bound strength to fst-suggested value %c (was %c)",strength, spho->headx[spho->outReadPos].boundstrength, spho->breakTime));
863    }
864
865    /* insert modified bound according to pending break, if any */
866    if (spho->breakPending) {
867        /* the calculation is based on the fst-modified value (because this is what the customer wants to
868         * override)
869         */
870        strength = breakModifiedBoundStrength(strength, spho->breakTime, (PICODATA_ITEMINFO1_BOUND_PHR1 == orig_strength));
871        PICODBG_DEBUG(("setting bound strength to break-imposed value %c (was %c) and time to %i",strength, spho->headx[spho->outReadPos].boundstrength, spho->breakTime));
872        spho->headx[spho->outReadPos].boundstrength =  strength;
873        spho->headx[spho->outReadPos].sildur = spho->breakTime;
874        spho->breakPending = FALSE;
875    }
876    if (spho->headx[spho->outReadPos].boundstrength) {
877        /* we did set a bound strength, possibly promoting or demoting a boundary; now set the phrase type
878         * possibly also changing the phrase type of the previous phrase bound
879         */
880        picoos_uint8 fromPhrase = ((PICODATA_ITEMINFO1_BOUND_PHR0 != orig_strength));
881        picoos_uint8 toPhrase = ((PICODATA_ITEMINFO1_BOUND_PHR0 != strength));
882
883        PICODBG_DEBUG(("setting phrase type (wasPhrase=%i, isPhrase=%i)",fromPhrase,toPhrase));
884        if (toPhrase) {
885            if (fromPhrase) {
886                spho->lastPhraseType = orig_type;
887            } else { /*promote */
888                if (spho->activeStartPos <= spho->lastPhraseBoundPos) {
889                    /* we still can change prev phrase bound */
890                    /* since a new phrase boundary is introduced, we have to 'invent'
891                     * an additional phrase type here. For that, we have to use some of the
892                     * knowledge that otherwise is handled in picoacph.
893                     */
894                    spho->headx[spho->lastPhraseBoundPos].phrasetype
895                            = PICODATA_ITEMINFO2_BOUNDTYPE_P;
896                }
897            }
898            spho->lastPhraseBoundPos = spho->outReadPos;
899            spho->headx[spho->lastPhraseBoundPos].phrasetype
900                    = spho->lastPhraseType;
901
902        } else {
903            spho->headx[spho->outReadPos].phrasetype = PICODATA_ITEMINFO2_NA;
904            if (fromPhrase) { /* demote */
905                spho->lastPhraseType = orig_type;
906                if (spho->activeStartPos <= spho->lastPhraseBoundPos) {
907                    /* we still can change prev phrase bound */
908                    spho->headx[spho->lastPhraseBoundPos].phrasetype
909                        = spho->lastPhraseType;
910                }
911            }
912        }
913    }
914}
915
916
917/* ***********************************************************************/
918/*                          sphoStep function                            */
919/* ***********************************************************************/
920
921
922static picodata_step_result_t sphoStep(register picodata_ProcessingUnit this,
923        picoos_int16 mode, picoos_uint16 * numBytesOutput)
924{
925
926    register spho_subobj_t *spho;
927    pico_status_t rv= PICO_OK;
928    picoos_uint16 blen;
929    picodata_itemhead_t ihead, ohead;
930    picoos_uint8 *icontent;
931    picoos_uint16 nextInPos;
932#if defined(PICO_DEBUG)
933    picoos_char msgstr[SPHO_MSGSTR_SIZE];
934#endif
935
936    /* used in FEED and FEED_SYM */
937    picoos_uint16 clen;
938    picoos_int16 pos, sym, sylsym;
939    picoos_uint8 plane;
940
941    /* used in BOUNDS */
942    picoos_bool breakBefore, breakAfter;
943
944    /* pico_status_t rvP= PICO_OK; */
945
946    picoos_uint16 curPos /*, nextPos */;
947    picoos_uint16 remHeadxSize, remCbufSize;
948
949
950    if (NULL == this || NULL == this->subObj) {
951        return PICODATA_PU_ERROR;
952    }
953    spho = (spho_subobj_t *) this->subObj;
954
955    mode = mode;        /* avoid warning "var not used in this function"*/
956
957    *numBytesOutput = 0;
958    while (1) { /* exit via return */
959        PICODBG_INFO(("doing state %i, headxReadPos: %d, headxWritePos: %d",
960                        spho->procState, spho->headxReadPos, spho->headxWritePos));
961
962        switch (spho->procState) {
963
964            case SPHO_STEPSTATE_INIT:
965                /* **********************************************************************/
966                /* INIT                                                              */
967                /* **********************************************************************/
968                PICODBG_DEBUG(("INIT"));
969            /* (re)set values for PARSE */
970            spho->penultima = SPHO_POS_INVALID;
971            spho->activeEndPos = SPHO_POS_INVALID;
972            spho->headxReadPos = 0;
973            spho->phonReadPos = 0;
974            spho->phonWritePos = 0;
975            spho->lastPhraseType = PICODATA_ITEMINFO2_NA;
976            spho->lastPhraseBoundPos = -1;
977
978            spho->procState = SPHO_STEPSTATE_COLLECT;
979            break;
980
981
982            case SPHO_STEPSTATE_COLLECT:
983                /* **********************************************************************/
984                /* COLLECT                                                              */
985                /* **********************************************************************/
986                /* collect state: get items from charBuf and store in
987                 * internal inBuf
988                 */
989                PICODBG_TRACE(("COLLECT"));
990                rv = PICO_OK;
991                remHeadxSize = spho->headxBufSize - spho->headxWritePos;
992                remCbufSize = spho->cbufBufSize - spho->cbufWritePos;
993                curPos = spho->headxWritePos;
994                while ((PICO_OK == rv) && (remHeadxSize > 0) && (remCbufSize > 0)) {
995                    PICODBG_DEBUG(("COLLECT getting item at headxWritePos %i (remaining %i)",spho->headxWritePos, remHeadxSize));
996                    rv = picodata_cbGetItem(this->cbIn, spho->tmpbuf, PICODATA_MAX_ITEMSIZE, &blen);
997                    if (PICO_OK == rv) {
998                        rv = picodata_get_itemparts(spho->tmpbuf,
999                                            PICODATA_MAX_ITEMSIZE, &(spho->headx[spho->headxWritePos].head),
1000                                                    &(spho->cbuf[spho->cbufWritePos]), remCbufSize, &blen);
1001                        if (PICO_OK == rv) {
1002                            spho->headx[spho->headxWritePos].cind = spho->cbufWritePos;
1003                            spho->headx[spho->headxWritePos].boundstrength = 0;
1004                            spho->headxWritePos++;
1005                            remHeadxSize--;
1006                            spho->cbufWritePos += blen;
1007                            remCbufSize -= blen;
1008                        }
1009                    }
1010                }
1011                if ((PICO_OK == rv) && ((remHeadxSize <= 0) || (remCbufSize <= 0))) {
1012                    rv = PICO_EXC_BUF_OVERFLOW;
1013                }
1014
1015                /* in normal circumstances, rv is either PICO_EOF (no more items in cbIn) or PICO_BUF_OVERFLOW
1016                 * (if no more items fit into headx) */
1017                if ((PICO_EOF != rv) && (PICO_EXC_BUF_OVERFLOW != rv)) {
1018                    PICODBG_DEBUG(("COLLECT ** problem getting item, unhandled, rv: %i", rv));
1019                    picoos_emRaiseException(this->common->em, rv,
1020                    NULL, NULL);
1021                    return PICODATA_PU_ERROR;
1022                }
1023                if (PICO_EOF == rv) { /* there are no more items available */
1024                    if (curPos < spho->headxWritePos) { /* we did get some new items */
1025                        PICODBG_DEBUG(("COLLECT read %i items",
1026                                        spho->headxWritePos - curPos));
1027                        spho->needMoreInput = FALSE;
1028                    }
1029                    if (spho->needMoreInput) { /* not enough items to proceed */
1030                        PICODBG_DEBUG(("COLLECT need more data, returning IDLE"));
1031                        return PICODATA_PU_IDLE;
1032                    } else {
1033                        spho->procState = SPHO_STEPSTATE_PROCESS_PARSE;
1034                        /* uncomment next to split into two steps */
1035                        /* return PICODATA_PU_ATOMIC; */
1036                    }
1037                } else { /* input buffer full */
1038                    PICODBG_DEBUG(("COLLECT input buffer full"));
1039                    if (spho->needMoreInput) { /* forced output because we can't get more data */
1040                        spho->needMoreInput = FALSE;
1041                        spho->force = TRUE;
1042                    }
1043                    spho->procState = SPHO_STEPSTATE_PROCESS_PARSE;
1044                }
1045                break;
1046
1047           case SPHO_STEPSTATE_PROCESS_PARSE:
1048
1049                /* **********************************************************************/
1050                /* PARSE: items -> input pos/phon pairs */
1051                /* **********************************************************************/
1052
1053                /* parse one item at a time */
1054                /* If
1055                 *    - the item is a sentence end or
1056                 *    - it is the last item and force=1 or
1057                 *    - the phon buffer is full
1058                 * then set inReadPos to 0 and go to TRANSDUCE
1059                 * else advance by one item */
1060
1061                /* look at the current item */
1062                PICODBG_TRACE(("PARSE"));
1063                if (spho->headxReadPos >= spho->headxWritePos) {
1064                    /* no more items in headx */
1065                    if (spho->force) {
1066                        PICODBG_INFO(("no more items in headx but we are forced to transduce"));
1067
1068                        /* headx is full; we are forced to transduce before reaching the sentence end */
1069                        spho->force = FALSE;
1070                        if (SPHO_POS_INVALID == spho->activeEndPos) {
1071                            spho->activeEndPos = spho->headxReadPos;
1072                        }
1073                        spho->procState = SPHO_STEPSTATE_PROCESS_TRANSDUCE;
1074                    } else {
1075                        /* we try to get more data */
1076                        PICODBG_INFO(("no more items in headx, try to collect more"));
1077                        spho->needMoreInput = TRUE;
1078                        spho->procState = SPHO_STEPSTATE_COLLECT;
1079                    }
1080                    break;
1081                }
1082
1083                ihead = spho->headx[spho->headxReadPos].head;
1084                icontent = spho->cbuf + spho->headx[spho->headxReadPos].cind;
1085
1086                PICODBG_DEBUG(("PARSE looking at item %s",picodata_head_to_string(&ihead,msgstr,SPHO_MSGSTR_SIZE)));
1087                /* treat header */
1088                if (PICODATA_ITEM_BOUND == ihead.type) {
1089                    /* see if it is a sentence end or termination boundary (flush) */
1090                    if ((PICODATA_ITEMINFO1_BOUND_SEND == ihead.info1)
1091                    || (PICODATA_ITEMINFO1_BOUND_TERM == ihead.info1)) {
1092                        PICODBG_INFO(("PARSE found sentence  end or term BOUND"));
1093
1094                        if (spho->sentenceStarted) {
1095                            /* its the end of the sentence */
1096                            PICODBG_INFO(("PARSE found sentence end"));
1097                            spho->sentenceStarted = 0;
1098                            /* there is no need for a right context; move the active end to the end */
1099                            /* add sentence termination phonemes */
1100                            sphoAddTermPhonemes(spho, spho->headxReadPos);
1101                            spho->headxReadPos++;
1102                            spho->activeEndPos = spho->headxReadPos;
1103                            /* we may discard all information up to activeEndPos, after processing of last
1104                             * sentence part
1105                             */
1106                            spho->penultima = spho->activeEndPos;
1107
1108                            /* transduce */
1109                            spho->procState = SPHO_STEPSTATE_PROCESS_TRANSDUCE;
1110                            /* uncomment to split */
1111                            /* return PICODATA_PU_BUSY; */
1112                            break;
1113                        } else {
1114                            if (PICODATA_ITEMINFO1_BOUND_TERM == ihead.info1) {
1115                                /* its the end of input (flush) */
1116                                PICODBG_INFO(("PARSE forwarding input end (flush)"));
1117                                /* copy item unmodified */
1118                                picodata_put_itemparts(&ihead,
1119                                         icontent,
1120                                         ihead.len,
1121                                         spho->outBuf, spho->outBufSize,
1122                                         &clen);
1123
1124                                spho->headxReadPos++;
1125                                spho->activeEndPos = spho->headxReadPos;
1126                                spho->penultima = SPHO_POS_INVALID;
1127                                spho->feedFollowState = SPHO_STEPSTATE_SHIFT;
1128                                spho->procState = SPHO_STEPSTATE_FEED;
1129                                break;
1130                            } else {
1131                                /* this should never happen */
1132                                /* eliminate bound */
1133                                spho->headxReadPos++;
1134                                spho->activeEndPos = spho->headxReadPos;
1135                                spho->penultima = SPHO_POS_INVALID;
1136                                PICODBG_ERROR(("PARSE found a sentence end without a sentence start; eliminated"));
1137                            }
1138                        }
1139                    } else if (PICODATA_ITEMINFO1_BOUND_SBEG == ihead.info1) {
1140                            /* its the start of the sentence */
1141                            PICODBG_INFO(("PARSE found sentence start"));
1142                            /* add sentence starting phoneme */
1143                            sphoAddStartPhoneme(spho);
1144
1145                            spho->sentenceStarted = 1;
1146                    }
1147                }
1148
1149                if ((PICODATA_ITEM_WORDPHON == ihead.type)
1150                        || (PICODATA_ITEM_BOUND == ihead.type)) {
1151                    /* if it is a word or a bound try to extract phonemes */
1152                    PICODBG_INFO(("PARSE found WORD phon or phrase BOUND"));
1153                    rv = sphoExtractPhonemes(this, spho, spho->headxReadPos,
1154                            TRUE /* convertAccents */,
1155                            &spho->suppressParseWordBound);
1156                    if (PICO_OK == rv) {
1157                        PICODBG_INFO(("PARSE successfully returned from phoneme extraction"));
1158                        /* replace activeEndPos if the new item is a word, or activeEndPos was not set yet, or
1159                         * activeEndPos was a bound */
1160                        if ((spho->activeStartPos <= spho->headxReadPos) && ((PICODATA_ITEM_WORDPHON == ihead.type)
1161                                || (SPHO_POS_INVALID == spho->activeEndPos)
1162                                || (PICODATA_ITEM_BOUND == spho->headx[spho->activeEndPos].head.type))) {
1163                            PICODBG_INFO(("PARSE found new activeEndPos: %i,%i -> %i,%i",
1164                                            spho->penultima,spho->activeEndPos,spho->activeEndPos,spho->headxReadPos));
1165                            spho->penultima = spho->activeEndPos;
1166                            spho->activeEndPos = spho->headxReadPos;
1167                        }
1168
1169                    } else if (PICO_EXC_BUF_OVERFLOW == rv) {
1170                        /* phoneme buffer cannot take this item anymore;
1171                           if the phoneme buffer has some contents, we are forced to transduce before reaching the sentence end
1172                           else we skip the (too long word) */
1173                        PICODBG_INFO(("PARSE returned from phoneme extraction with overflow, number of phonemes in phonBuf: %i; forced to TRANSDUCE", spho->phonWritePos));
1174                        if ((SPHO_POS_INVALID == spho->activeEndPos) || (spho->activeStartPos == spho->activeEndPos)) {
1175                            spho->activeEndPos = spho->headxReadPos;
1176                        }
1177                        spho->procState = SPHO_STEPSTATE_PROCESS_TRANSDUCE;
1178                        break;
1179                    } else {
1180                        PICODBG_ERROR(("PARSE returned from phoneme extraction with exception %i",rv));
1181                        return (picodata_step_result_t)picoos_emRaiseException(this->common->em,
1182                        PICO_ERR_OTHER, NULL, NULL);
1183                    }
1184                } else {
1185                    PICODBG_INFO(("PARSE found other item, passing over"));
1186                    /* it is "other" item, ignore */
1187                }
1188                /* set pos at next item */
1189                PICODBG_INFO(("PARSE going to next item: %i -> %i",spho->headxReadPos, spho->headxReadPos + 1));
1190                spho->headxReadPos++;
1191                break;
1192
1193            case SPHO_STEPSTATE_PROCESS_TRANSDUCE:
1194
1195                /* **********************************************************************/
1196                /* TRANSDUCE: transduction input pos/phon pairs to output pos/phon pairs */
1197                /* **********************************************************************/
1198                PICODBG_DEBUG(("TRANSDUCE (%i-th of %i fsts",spho->curFst+1, spho->numFsts));
1199
1200                /* termination condition first */
1201                if (spho->curFst >= spho->numFsts) {
1202
1203#if defined(PICO_DEBUG)
1204                    {
1205                        PICODBG_INFO_CTX();
1206                        PICODBG_INFO_MSG(("result of all transductions: "));
1207                        PICOTRNS_PRINTSYMSEQ(this->voice->kbArray[PICOKNOW_KBID_DBG], spho->phonBufOut, spho->phonWritePos);
1208                        PICODBG_INFO_MSG(("\n"));
1209                    }
1210#endif
1211
1212                    /* reset for next transduction */
1213                    spho->curFst = 0;
1214                    /* prepare BOUNDS */
1215                    spho->outReadPos = 0;
1216                    spho->phonReadPos = 0;
1217
1218                    spho->procState = SPHO_STEPSTATE_PROCESS_BOUNDS;
1219                    break;
1220                }
1221
1222                /* transduce from phonBufIn to PhonBufOut */
1223                {
1224
1225                    picoos_uint32 nrSteps;
1226#if defined(PICO_DEBUG)
1227                    {
1228                        PICODBG_INFO_CTX();
1229                        PICODBG_INFO_MSG(("spho trying to transduce: "));
1230                        PICOTRNS_PRINTSYMSEQ(this->voice->kbArray[PICOKNOW_KBID_DBG], spho->phonBuf, spho->phonWritePos);
1231                        PICODBG_INFO_MSG(("\n"));
1232                    }
1233#endif
1234                    rv = picotrns_transduce(spho->fst[spho->curFst], FALSE,
1235                    picotrns_printSolution, spho->phonBuf, spho->phonWritePos, spho->phonBufOut,
1236                            &spho->phonWritePos,
1237                            4*PICOTRNS_MAX_NUM_POSSYM, spho->altDescBuf,
1238                            spho->maxAltDescLen, &nrSteps);
1239                    if (PICO_OK == rv) {
1240#if defined(PICO_DEBUG)
1241                    {
1242                        PICODBG_INFO_CTX();
1243                        PICODBG_INFO_MSG(("result of transduction: (output symbols: %i)", spho->phonWritePos));
1244                        PICOTRNS_PRINTSYMSEQ(this->voice->kbArray[PICOKNOW_KBID_DBG], spho->phonBufOut, spho->phonWritePos);
1245                        PICODBG_INFO_MSG(("\n"));
1246                    }
1247#endif
1248                        PICODBG_TRACE(("number of steps done in tranduction: %i", nrSteps));
1249                    } else {
1250                        picoos_emRaiseWarning(this->common->em, PICO_WARN_FALLBACK,NULL,(picoos_char *)"phon buffer full");
1251                    }
1252                }
1253                /* eliminate deep epsilons */
1254                picotrns_eliminate_epsilons(spho->phonBufOut, spho->phonWritePos, spho->phonBuf,
1255                        &spho->phonWritePos,4*PICOTRNS_MAX_NUM_POSSYM);
1256
1257                spho->curFst++;
1258
1259                /* return PICODATA_PU_ATOMIC */
1260                break;
1261
1262
1263            case SPHO_STEPSTATE_PROCESS_BOUNDS:
1264                /* ************************************************************************/
1265                /* BOUNDS: combine input item with pos/phon pairs to insert/modify bounds */
1266                /* ************************************************************************/
1267
1268                PICODBG_INFO(("BOUNDS"));
1269
1270                /* get the suppressRecombWordBound in the left context */
1271                spho->suppressRecombWordBound = FALSE;
1272                while (spho->outReadPos < spho->activeStartPos) {
1273                    /* look at the current item */
1274                    ihead = spho->headx[spho->outReadPos].head;
1275                    /* icontent = spho->cbuf + spho->headx[spho->outReadPos].cind; */
1276                    PICODBG_INFO(("in position %i, looking at item %s",spho->outReadPos,picodata_head_to_string(&ihead,msgstr,SPHO_MSGSTR_SIZE)));
1277                    if (PICODATA_ITEM_BOUND == ihead.type) {
1278                        spho->suppressRecombWordBound = TRUE;
1279                    } else if (PICODATA_ITEM_WORDPHON == ihead.type) {
1280                        spho->suppressRecombWordBound = FALSE;
1281                    }
1282                    spho->outReadPos++;
1283                }
1284                /* spho->outReadPos point now to the active region */
1285
1286                /* advance the phone reading pos to the active range */
1287                spho->phonReadPos = 0;
1288                while (SPHO_POSSYM_OK == (rv = getNextPosSym(spho, &pos, &sym,
1289                        spho->activeStartPos))) {
1290                    /* ignore */
1291                }
1292                PICODBG_INFO(("skipping left context phones results in %s", (SPHO_POSSYM_OUT_OF_RANGE==rv) ? "OUT_OF_RANGE" : (SPHO_POSSYM_END ==rv) ? "END" : "OTHER"));
1293
1294                /*
1295                 * Align input items with transduced phones and note bound stregth changes and break commands
1296                 */
1297
1298                while (spho->outReadPos < spho->activeEndPos) {
1299
1300                    /* look at the current item */
1301                    ihead = spho->headx[spho->outReadPos].head;
1302                    icontent = spho->cbuf + spho->headx[spho->outReadPos].cind;
1303                    nextInPos = spho->outReadPos + 1;
1304                    /*  */
1305                    PICODBG_INFO(("in position %i, looking at item %s",spho->outReadPos,picodata_head_to_string(&ihead,msgstr,SPHO_MSGSTR_SIZE)));
1306
1307                    if ((PICODATA_ITEM_BOUND == ihead.type)
1308                            || ((PICODATA_ITEM_WORDPHON == ihead.type)
1309                                    && (!spho->suppressRecombWordBound))) {
1310                        /* there was a boundary originally */
1311                        picoos_uint8 orig_strength, orig_type;
1312                        if (PICODATA_ITEM_BOUND == ihead.type) {
1313                            orig_strength = ihead.info1;
1314                            orig_type = ihead.info2;
1315                            spho->suppressRecombWordBound = TRUE;
1316                        } else {
1317                            orig_strength = PICODATA_ITEMINFO1_BOUND_PHR0;
1318                            orig_type = PICODATA_ITEMINFO2_NA;
1319                        }
1320                        /* i expect a boundary phone here */
1321                        /* consume FST bound phones, consider pending break and set the side-bound */
1322                        PICODBG_INFO(("got BOUND or WORDPHON item and expects corresponding phone"));
1323                        rv = getNextPosSym(spho, &pos, &sym, nextInPos);
1324                        if (SPHO_POSSYM_OK != rv) {
1325                            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"));
1326                            return (picodata_step_result_t)picoos_emRaiseException(this->common->em,
1327                                    PICO_ERR_OTHER, NULL, NULL);
1328                        }
1329                        sym = picotrns_unplane(sym, &plane);
1330                        /*   */
1331                        PICODBG_ASSERT((PICOKFST_PLANE_PB_STRENGTHS == plane));
1332
1333                        /* insert modified bound according to transduction and possibly pending break */
1334                        setSideBound(spho, orig_strength, orig_type,
1335                                (picoos_uint8) sym);
1336                    } else if ((PICODATA_ITEM_CMD == ihead.type)
1337                            && (PICODATA_ITEMINFO1_CMD_SIL == ihead.info1)) {
1338                        /* it's a SIL (break) command */
1339                        picoos_uint16 time;
1340                        picoos_uint32 pos = 0;
1341                        picoos_read_mem_pi_uint16(icontent, &pos, &time);
1342                        if (spho->breakPending) {
1343                            spho->breakTime += time;
1344                        } else {
1345                            spho->breakTime = time;
1346                            spho->breakPending = TRUE;
1347                        }
1348                    } else if ((PICODATA_ITEM_CMD == ihead.type) && (PICODATA_ITEMINFO1_CMD_PLAY == ihead.info1)) {
1349                        /* insert break of at least one ms */
1350                        if (!spho->breakPending || (spho->breakTime <= 0)) {
1351                            spho->breakTime = SPHO_SMALLEST_SIL_DUR;
1352                            spho->breakPending = TRUE;
1353                        }
1354                        setSideBound(spho, PICODATA_ITEMINFO1_NA,
1355                                PICODATA_ITEMINFO2_NA, PICODATA_ITEMINFO1_NA);
1356                        /* force following break to be at least one ms */
1357                        spho->breakTime = SPHO_SMALLEST_SIL_DUR;
1358                        spho->breakPending = TRUE;
1359                    } else if (breakStateInterrupting(&ihead, &breakBefore, &breakAfter)) {
1360
1361                        if (breakBefore &&(!spho->breakPending || (spho->breakTime <= 0))) {
1362                            spho->breakTime = SPHO_SMALLEST_SIL_DUR;
1363                            spho->breakPending = TRUE;
1364                        }
1365                        setSideBound(spho, PICODATA_ITEMINFO1_NA,
1366                                PICODATA_ITEMINFO2_NA, PICODATA_ITEMINFO1_NA);
1367
1368                        if (breakAfter) {
1369                            spho->breakTime = SPHO_SMALLEST_SIL_DUR;
1370                            spho->breakPending = TRUE;
1371                        }
1372                        if (PICODATA_ITEM_WORDPHON == ihead.type) {
1373                            spho->suppressRecombWordBound = FALSE;
1374                        }
1375                    }
1376
1377                    /* skip phones of that item */
1378                    while (SPHO_POSSYM_OK == (rv = getNextPosSym(spho, &pos,
1379                            &sym, nextInPos))) {
1380                        /* ignore */
1381                    }
1382                    spho->outReadPos++;
1383                }
1384
1385                /* reset for RECOMB */
1386                spho->outReadPos = 0;
1387                spho->phonReadPos = 0;
1388                spho->suppressRecombWordBound = FALSE;
1389
1390                spho->procState = SPHO_STEPSTATE_PROCESS_RECOMB;
1391                return PICODATA_PU_ATOMIC;
1392
1393                break;
1394
1395           case SPHO_STEPSTATE_PROCESS_RECOMB:
1396                /* **********************************************************************/
1397                /* RECOMB: combine input item with pos/phon pairs to output item */
1398                /* **********************************************************************/
1399
1400                PICODBG_TRACE(("RECOMB"));
1401
1402                /* default place to come after feed: here */
1403                spho->feedFollowState = SPHO_STEPSTATE_PROCESS_RECOMB;
1404
1405                /* check termination condition first */
1406                if (spho->outReadPos >= spho->activeEndPos) {
1407                    PICODBG_DEBUG(("RECOMB reached active region's end at %i",spho->outReadPos));
1408                    spho->procState = SPHO_STEPSTATE_SHIFT;
1409                    break;
1410                }
1411
1412                /* look at the current item */
1413                ihead = spho->headx[spho->outReadPos].head;
1414                icontent = spho->cbuf + spho->headx[spho->outReadPos].cind;
1415
1416                PICODBG_DEBUG(("RECOMB looking at item %s",picodata_head_to_string(&ihead,msgstr,SPHO_MSGSTR_SIZE)));
1417
1418                nextInPos = spho->outReadPos + 1;
1419
1420                PICODBG_DEBUG(("RECOMB treating item in headx at pos %i",spho->outReadPos));
1421                if (nextInPos <= spho->activeStartPos) { /* we're in the (passive) left context. Just skip it */
1422                    PICODBG_DEBUG(("RECOMB skipping item in the left context (%i <= %i)",nextInPos, spho->activeStartPos));
1423                    if (PICODATA_ITEM_BOUND == ihead.type) {
1424                        spho->suppressRecombWordBound = 1;
1425                    } else if (PICODATA_ITEM_WORDPHON == ihead.type) {
1426                        spho->suppressRecombWordBound = 0;
1427                    }
1428
1429                    /* consume possyms */
1430                    while (SPHO_POSSYM_OK == (rv = getNextPosSym(spho,&pos,&sym,nextInPos))) {
1431                        /* ignore */
1432                    }
1433                    if (rv == SPHO_POSSYM_INVALID) {
1434                        return (picodata_step_result_t)picoos_emRaiseException(this->common->em,
1435                        PICO_ERR_OTHER, NULL, NULL);
1436                    }
1437                    spho->outReadPos = nextInPos;
1438                } else { /* active region */
1439                    if (spho->headx[spho->outReadPos].boundstrength) {
1440/* ***************** "side-bound" *********************/
1441                        /* copy to outbuf */
1442                        putSideBoundToOutput(spho);
1443                        /* mark as processed */
1444                        spho->headx[spho->outReadPos].boundstrength = 0;
1445                        /* output it */
1446                        spho->procState = SPHO_STEPSTATE_FEED;
1447                    } else if (PICODATA_ITEM_BOUND == ihead.type) {
1448/* ***************** BOUND *********************/
1449                        /* expect a boundary phone here */
1450                        PICODBG_DEBUG(("RECOMB got BOUND item and expects corresponding phone"));
1451                        rv = getNextPosSym(spho, &pos, &sym, nextInPos);
1452                        if (SPHO_POSSYM_OK != rv) {
1453                            PICODBG_ERROR(("unexpected symbol or unexpected end of phoneme list"));
1454                            return (picodata_step_result_t)picoos_emRaiseException(
1455                                    this->common->em, PICO_ERR_OTHER, NULL,
1456                                    NULL);
1457                        }
1458                        sym = picotrns_unplane(sym, &plane);
1459                        /*   */
1460                        PICODBG_ASSERT((PICOKFST_PLANE_PB_STRENGTHS == plane));
1461
1462                        spho->suppressRecombWordBound = TRUE; /* if word following, don't need word boundary */
1463                        /* just consume item and come back here*/
1464                        spho->outReadPos = nextInPos;
1465
1466                    } else if (PICODATA_ITEM_WORDPHON == ihead.type) {
1467/* ***************** WORDPHON *********************/
1468                        spho->wordStarted = TRUE;
1469                        /* i expect a word boundary symbol in this range unless a phrase boundary was encountered before */
1470                        if (spho->suppressRecombWordBound) {
1471                            PICODBG_DEBUG(("RECOMB got WORDPHON item but skips expecting BOUND"));
1472                            spho->suppressRecombWordBound = FALSE;
1473                        } else {
1474                            PICODBG_DEBUG(("RECOMB got WORDPHON item and expects corresponding bound phone"));
1475                            rv = getNextPosSym(spho, &pos, &sym, nextInPos);
1476                            if (SPHO_POSSYM_OK != rv) {
1477                                PICODBG_ERROR(("unexpected symbol or unexpected end of phoneme list"));
1478                                return (picodata_step_result_t)picoos_emRaiseException(this->common->em,
1479                                PICO_ERR_OTHER, NULL, NULL);
1480                            }
1481                        }
1482                        spho->procState = SPHO_STEPSTATE_PROCESS_SYL;
1483                    } else if ((PICODATA_ITEM_CMD == ihead.type) && (PICODATA_ITEMINFO1_CMD_SIL == ihead.info1)) {
1484/* ***************** BREAK COMMAND *********************/
1485                        /* just consume and come back here */
1486                        PICODBG_DEBUG(("RECOMB consuming item from inBuf %i -> %i",spho->outReadPos, nextInPos));
1487                        spho->outReadPos = nextInPos;
1488                    } else {
1489/* ***************** OTHER *********************/
1490                        /* just copy item */
1491                        PICODBG_DEBUG(("RECOMB found other item, just copying"));
1492                        picodata_put_itemparts(&ihead, icontent, ihead.len,
1493                                spho->outBuf, spho->outBufSize, &clen);
1494                        PICODBG_DEBUG(("RECOMB consuming item from inBuf %i -> %i",spho->outReadPos, nextInPos));
1495                        spho->outReadPos = nextInPos;
1496                        /* and output it */
1497                        spho->procState = SPHO_STEPSTATE_FEED;
1498                    } /* if (ihead.type) */
1499
1500                }
1501
1502                /* return PICODATA_PU_BUSY; */
1503                break;
1504
1505            case SPHO_STEPSTATE_PROCESS_SYL:
1506                /* **********************************************************************/
1507                /* SYL: combine input word item with pos/phon pairs to syl output item */
1508                /* **********************************************************************/
1509
1510                /* consume all transduced phonemes with pos in in the range [spho->outReadPos,nextInPos[ */
1511               PICODBG_DEBUG(("SYL"));
1512
1513               spho->feedFollowState = SPHO_STEPSTATE_PROCESS_SYL;
1514
1515               /* look at the current item */
1516               ihead = spho->headx[spho->outReadPos].head;
1517               icontent = spho->cbuf + spho->headx[spho->outReadPos].cind;
1518                nextInPos = spho->outReadPos + 1;
1519                PICODBG_DEBUG(("SYL (1) treating item in headx at pos %i",spho->outReadPos));
1520                /* create syllable item in ohead (head) and sylBuf (contents) */
1521                ohead.type = PICODATA_ITEM_SYLLPHON;
1522
1523                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));
1524                rv = getNextPosSym(spho,&pos,&sym,nextInPos);
1525                if (SPHO_POSSYM_OK != rv) {
1526                    PICODBG_ERROR(("unexpected symbol or unexpected end of phoneme list (%i)",rv));
1527                    return (picodata_step_result_t)picoos_emRaiseException(this->common->em, PICO_ERR_OTHER, NULL, NULL);
1528                }
1529                ohead.info2 = picotrns_unplane(sym, &plane);
1530                PICODBG_ASSERT((PICOKFST_PLANE_ACCENTS == plane));
1531                PICODBG_DEBUG(("SYL sets accent to %c", sym));
1532
1533                /* for the time being, we force to use POS so we can transduce all fsts in a row without reconsulting the items */
1534                PICODBG_TRACE(("SYL expects POS"));
1535                PICODBG_DEBUG(("SYL (2) treating item in inBuf range [%i,%i[",spho->outReadPos,nextInPos));
1536                rv = getNextPosSym(spho,&pos,&sym,nextInPos);
1537                if (SPHO_POSSYM_OK != rv) {
1538                    PICODBG_ERROR(("unexpected symbol or unexpected end of phoneme list"));
1539                    return (picodata_step_result_t)picoos_emRaiseException(this->common->em, PICO_ERR_OTHER, NULL, NULL);
1540                }
1541                if (spho->wordStarted) {
1542                    spho->wordStarted = FALSE;
1543                    ohead.info1 = picotrns_unplane(sym, &plane);
1544                    /*  */
1545                    PICODBG_ASSERT(PICOKFST_PLANE_POS == plane);
1546                    /*  */
1547                    PICODBG_DEBUG(("SYL setting POS to %c", ohead.info1));
1548                } else {
1549                    ohead.info1 = PICODATA_ITEMINFO1_NA;
1550                }
1551
1552                PICODBG_DEBUG(("SYL (3) treating item in inBuf range [%i,%i[",spho->outReadPos,nextInPos));
1553                /* get phonemes of that syllable; stop if syllable boundary or outside word */
1554                sylsym = (PICOKFST_PLANE_PHONEMES << 8)
1555                        + spho->syllSepId;
1556                PICODBG_DEBUG(("collecting syllable phonemes before headx position %i",nextInPos));
1557                spho->sylWritePos = 0;
1558                while (SPHO_POSSYM_OK == (rv = getNextPosSym(spho,&pos,&sym,nextInPos)) && (sym != sylsym)) {
1559                    spho->sylBuf[spho->sylWritePos++] = picotrns_unplane(sym, &plane);
1560                    /*  */
1561                   PICODBG_TRACE(("SYL adding phoneme to syllable: (pos %i,sym %i)[plane %i,sym %c]",pos,sym,plane,sym  & 0xFF));
1562                    PICODBG_ASSERT((PICOKFST_PLANE_PHONEMES == plane));
1563                }
1564                PICODBG_DEBUG(("SYL (4) treating item in inBuf range [%i,%i[",spho->outReadPos,nextInPos));
1565                ohead.len = spho->sylWritePos;
1566                if (SPHO_POS_INVALID == rv) {
1567                    PICODBG_ERROR(("unexpected symbol or unexpected end of phoneme list"));
1568                    return (picodata_step_result_t)picoos_emRaiseException(this->common->em, PICO_WARN_INCOMPLETE, NULL, NULL);
1569                } else if ((SPHO_POSSYM_OUT_OF_RANGE == rv) || (SPHO_POSSYM_END == rv)) {
1570                    PICODBG_DEBUG(("SYL arrived at end of word and/or end of phon buffer, go to next word"));
1571                    spho->outReadPos = nextInPos; /* advance to next item */
1572                    spho->feedFollowState = SPHO_STEPSTATE_PROCESS_RECOMB; /* go to RECOMB after feed */
1573                 } else {
1574                    PICODBG_ASSERT((sym == sylsym));
1575                }
1576                PICODBG_DEBUG(("SYL (5) treating item in inBuf range [%i,%i[",spho->outReadPos,nextInPos));
1577
1578                if (ohead.len > 0) {
1579                    /* prepare syllable output */
1580                    picodata_put_itemparts(&ohead, spho->sylBuf,
1581                            PICODATA_BUFSIZE_DEFAULT, spho->outBuf,
1582                            spho->outBufSize, &clen);
1583
1584                    spho->procState = SPHO_STEPSTATE_FEED;
1585                } else { /* skip feeding output of empty syllable */
1586                    spho->procState = spho->feedFollowState;
1587                }
1588                break;
1589
1590             case SPHO_STEPSTATE_FEED:
1591                /* **********************************************************************/
1592                /* FEED: output output item and proceed to feedFollowState */
1593                /* **********************************************************************/
1594
1595                PICODBG_DEBUG(("FEED"));
1596
1597                PICODBG_DEBUG(("FEED putting outBuf item into cb"));
1598
1599                /*feeding items to PU output buffer*/
1600                rv = picodata_cbPutItem(this->cbOut, spho->outBuf,
1601                        spho->outBufSize, &clen);
1602
1603                PICODATA_INFO_ITEM(this->voice->kbArray[PICOKNOW_KBID_DBG],
1604                        (picoos_uint8 *)"spho: ",
1605                        spho->outBuf, spho->outBufSize);
1606
1607                if (PICO_EXC_BUF_OVERFLOW == rv) {
1608                    /* we have to redo this item */
1609                    PICODBG_DEBUG(("FEED got overflow, returning ICODATA_PU_OUT_FULL"));
1610                    return PICODATA_PU_OUT_FULL;
1611                } else if (PICO_OK == rv) {
1612                    *numBytesOutput += clen;
1613                    spho->procState = spho->feedFollowState;
1614                    PICODBG_DEBUG(("FEED ok, going back to procState %i", spho->procState));
1615                    return PICODATA_PU_BUSY;
1616                } else {
1617                    PICODBG_DEBUG(("FEED got exception %i when trying to output item",rv));
1618                    spho->procState = spho->feedFollowState;
1619                    return (picodata_step_result_t)rv;
1620                }
1621                break;
1622
1623            case SPHO_STEPSTATE_SHIFT:
1624                /* **********************************************************************/
1625                /* SHIFT                                                              */
1626                /* **********************************************************************/
1627                /* If there exists a valid penultima, it should replace any left context (from 0 to activeStartPos)
1628                 * else discard the current active range (from activeStartPos to activeEndPos), leaving the current
1629                 * left context intact. Often, PARSE would move activeStartPos to 0, so that there is no left context
1630                 * after the shift.
1631                 */
1632
1633                PICODBG_DEBUG(("SHIFT"));
1634
1635                if (spho->penultima != SPHO_POS_INVALID) {
1636                    picoos_int16 shift;
1637                    /* set penultima as new left context and set activeStartPos to the shifted activeEndPos */
1638                    PICODBG_DEBUG((
1639                                    "SHIFT shifting penultima from %i to 0",
1640                                    spho->penultima));
1641                    shift = shift_range_left_1(spho, &spho->penultima, 0);
1642                    if (shift < 0) {
1643                        picoos_emRaiseException(this->common->em,PICO_ERR_OTHER,NULL,NULL);
1644                        return PICODATA_PU_ERROR;
1645                    }
1646                    spho->activeStartPos = spho->activeEndPos
1647                            - shift;
1648                    spho->lastPhraseBoundPos -= shift;
1649                    spho->suppressParseWordBound = FALSE;
1650                    spho->suppressRecombWordBound = FALSE;
1651
1652                } else {
1653                    picoos_int16 shift;
1654                    picoos_bool lastPhraseBoundActive;
1655                    if (spho->activeStartPos == spho->activeEndPos) {
1656                        /* no items consumed; we have to abandon left context */
1657                        spho->activeStartPos = 0;
1658                    }
1659                    lastPhraseBoundActive = (spho->lastPhraseBoundPos >= spho->activeStartPos);
1660                    /* dummy comment */
1661                    PICODBG_DEBUG(("SHIFT shift active end from %i to %i",
1662                                    spho->activeEndPos, spho->activeStartPos));
1663                    shift = shift_range_left_1(spho, &spho->activeEndPos, spho->activeStartPos);
1664                    if (shift < 0) {
1665                        picoos_emRaiseException(this->common->em,PICO_ERR_OTHER,NULL,NULL);
1666                        return PICODATA_PU_ERROR;
1667                    }
1668                    if (lastPhraseBoundActive) {
1669                        spho->lastPhraseBoundPos -= shift;
1670                    }
1671                }
1672
1673                spho->procState = SPHO_STEPSTATE_INIT;
1674                break;
1675
1676            default:
1677                picoos_emRaiseException(this->common->em, PICO_ERR_OTHER, NULL, NULL);
1678                return PICODATA_PU_ERROR;
1679                break;
1680
1681        } /* switch (spho->procState) */
1682
1683    } /* while (1) */
1684
1685    /* should be never reached */
1686    picoos_emRaiseException(this->common->em, PICO_ERR_OTHER, NULL, NULL);
1687    return PICODATA_PU_ERROR;
1688}
1689
1690#ifdef __cplusplus
1691}
1692#endif
1693
1694/* end picospho.c */
1695