/*************************************************************************************
CMTTS - TTS voice.

begun 27/9/03 by Mike Rozak.
Copyright 2003 by Mike Rozak. All rights reserved
*/

#include <windows.h>
#include <crtdbg.h>
#include "escarpment.h"
#include "..\M3D.h"
#include "resource.h"
#include "m3dwave.h"


// BUGFIX - Take these out, make Roger Blizzard2008 sound better without them
// since he has large variation in pitch and volume
#define NOMODS_GLOTTALPULSECHANGE   // glottal pulse change for energy
#define NOMODS_ENERGYPERPITCHGET // energy per pitch
#define NOMODS_ENERGYPERVOLUMEGET   // energyper volume
#define NOMODS_ENERGYPERVOLUMETUNE  // fine-tune energy per volume to keep same energy afterwards

// BUGFIX - Turn off phase interpolation since trying to do exact phase match per
// frame of PCM
#define NOMODS_INTERPPHASEDETAILED    // interpolate phase when adding unit
#define NOMODS_INTERPPHASE    // phase interpolation as stretch

// BUGFIX - Must have TURNOFFPHASEBEND OFF since doing no code with SRDETAILEDPHASE
#define NOMODS_TURNOFFPHASEBEND // turn off phase bend for SRDETAILEDPHASE

// BUGFIX - Take out BLURPHASE because shouldnt be necessary since have NOMODS_ALIGNPCM
// BUGFIX - turn off blurphase disable because not doing NOMODS_ALIGNPCM if not full PCM
// BUGFIX - Turn off THIS blurphase because will have a blurphase for SRDETAILEDPHASE
#define NOMODS_BLURPHASE      // blur phase

// BUGFIX - Dont use triphone pitch to modify prosody since should be handled
// okay by the fujisaki prosody
// NOTE: Tri-phone pitch doesnt work since fujisaki added
#define NOMODS_DISABLETRIPHONEPITCH // disable pitch bulge

// BUGFIX - Disabling this so prosody works better when it confronts phrases
#define NOMODS_DISABLETYPICALSYLINFO   // prosody, disable typicalsylinfoget()

// #define USEPROSODYTREND       // turn this on to use prosdy trend - BUGFIX - off because done elsewhere

#ifdef _DEBUG
#define TURNOFFRANDOM         // so dont have to worry about random errors
#define NOMODS       // if defined this won't do any modifications to the original TTS voice units
#endif // _DEBUG

#ifdef NOMODS
#define TURNOFFRANDOM         // so dont have to worry about random errors


// phase effects
// #define NOMODS_INTERPPHASEDETAILED    // interpolate phase when adding unit
// #define NOMODS_SRDETAILEDPHASE // turn off detailed phase
// #define NOMODS_INTERPPHASE    // phase interpolation as stretch
// #define NOMODS_TURNOFFPHASEBEND // turn off phase bend for SRDETAILEDPHASE

// #define NOMODS_DISABLEUNITCONTINUITY   // disable unit-continuity score, encouring left units to have similar characteristics
// #define NOMODS_DISABLEPCM     // disable PCM for testing
// #define NOMODS_CANSKIP        // cant skip end /star of phoneme
// #define NOMODS_BLENDUNITS     // blend units together
// #define NOMODS_GLOTTALPULSECHANGE   // glottal pulse change for energy
// #define NOMODS_BLURPHASE      // blur phase
// #define NOMODS_BLURPHASEDETAILED // blur phsae using detailed SRFeatures
// #define NOMODS_BENDPHASEDETAILED   // bend phases up/down for better match
// #define NOMODS_VOICEDISGUISE  // voice disguise
// #define NOMODS_SHOUTWHISPER   // shout/whisper effect
// #define NOMODS_CHANGEDURATION // change the duration
// #define NOMODS_ENERGYPERPITCHGET // energy per pitch
// #define NOMODS_ENERGYPERVOLUMEGET   // energyper volume
// #define NOMODS_ENERGYPERVOLUMETUNE  // fine-tune energy per volume to keep same energy afterwards
// #define NOMODS_LOWPASS        // low-passs blurring of pitch and volume
// #define NOMODS_ORIGPITCH      // use original pitch
// #define NOMODS_SNAPTODURATION // snap-to-duration enables
// #define NOMODS_SNAPTOPITCH    // snap to pitch enabled
// #define NOMODS_DISABLERESIDUAL    // prosody, disable residual
// #define NOMODS_DISABLEWORDSYL     // prosody, disable word syl
// #define NOMODS_DISABLEPHONEMOD      // prosody - disable phonemod values
// #define NOMODS_DISABLEPROSODYNGRAM   // prosody - disable ngram
// #define NOMODS_DISABLEPROSODYWORDMATCH // prosody - only accept matches in original speech, let phrase matches pass
// #define NOMODS_DISABLEPROSODYWORDRESIDUAL // prosody - if set then disable word-based residual prosody and use syllable-based instead
// #define NOMODS_DISABLEPROSODYMERGEMATCH   // prosody - combine the top two matches to reduce prosody noise
// #define NOMODS_DISABLEPROSODYWORDEXAMPLES   // prosody - use word examples to improve prosody
// #define NOMODS_VOLUME         // no volume adjust
// #define NOMODS_STRETCH        // non-linear stretch of phonemes
// #define NOMODS_ALIGNPCM       // align PCM for fullPCM audio
// #define NOMODS_DISABLEFUNCWORDGROUP   // if set then disable function word group for the scoring
#endif

#define TTSWAVECALSRANALBLOCK_CREATETHREADS  // if on, then will do background analysis of TTS voice audio
// BUGFIX - Disable this because trying to minimize memory usage, and even releasing
// the calculated information after awhile
// BUGFIX - Renable because TTS is too slow without this. Will take more memory.

#define TEMPLATEPROOSDYMODEL     // use the new template prosody model, that relies on matches

#ifdef TEMPLATEPROOSDYMODEL
// don't want the N-gram used at all with the template model
#define NOMODS_DISABLEPROSODYNGRAM   // prosody - disable ngram
#endif

#define USEPSOLA     // if set, then use psola, with different weights

// #define HOWMUCHSYNTHLIKEPCM      0.5      // how much of the PCM-oriented mods/values are used for spectrum synthesis
// #define HOWMUCHSYNTHLIKEPCM      0.2      // how much of the PCM-oriented mods/values are used for spectrum synthesis
   // BUGFIX - Fine-tuned to 0.2 using roger voice


#define USEDURSYL                // set to using syllable duration

#define DURPHONEPOW           0.5      // so not as much duration variation, so easier to understand
                                 // BUGFIX - Put in because of roger voice, but should make all voices clearer
#define VOLUMEPOW             0.5      // reduce the variability in volume, so easier to understand
                                 // BUGFIX - Put in here because of roger voice, but should make all voices clearer
#define PITCHPOW              1.0      // reduce the variability in pitch, so easier to understand
                                 // NOTE: Keeping this as 1.0 since doesn't make the voice any easier to understand
                                 // but removes some of the personality


// BUGFIX - Volume adjust up
#define TTSOUTPUTVOLUMESCALE     2.0     // how much the output volume is scaled, so TTS speaks loudly enough
         // Doing this since THEORETICALMAXENERGY seems to be too low
         // BUGFIX - Was sqrt((fp)2.0) but upped to 2.0

#define SNAPTOSCALEPITCH            ((fp)SRCOMPAREWEIGHT / 8.0)      // controls how much unit pitch/duration will snap to
#define SNAPTOSCALEDURATION         ((fp)SNAPTOSCALEPITCH / 2.0)     // duration much more likely to snap since doesn't affect prosody perception as much
                                 // the original. Seems like 10 is a good value.

#define SNAPTOSCALEBYUNITS          sqrt((fp)m_dwUnits / 10000.0)

#define FUJIDELTASCALE              5.0     // mismatches in fuji delta are very important
#define FUJIPHRASEBREAK             0.10     // any jumps more than this indicate a phrase break
               // BUGFIX - Increase from 0.5 to .10

// BUGFIX - Extra penalties since PSOLA also affecting phase, and since pitch detect NOT 100% accurate,
// and since stretching PSOLA sounds bad
// BUGFIX - Make relative to PCM pitch
// #define EXTRAPSOLAPITCHPENALTY        ((fp)SRCOMPAREWEIGHT / 12.0)  // extra penalty for pitch if using PSOLA, around 6
#define EXTRAPSOLAPITCHPENALTY         0.5      // when multiplied by PCMpitch, around 5

#define EXTRAPSOLADURATIONPENALTY     ((fp)EXTRAPSOLAPITCHPENALTY / 2.0)  // extra penalty for duration if using PSOLA, half that of pitch since not as bad

#define MODDURATION_UNITS_START        50000     // point at which duraton/pitch mod to exact copies of original starts
#define MODDURATION_UNITS_STOP         200000    // point at which duration/pitch mod at max

#define JOINFINDBESTRANGE              3        // can wiggle the range by 1/3 of a demiphone
   // BUGFIX - Was 1/2 of a demiphone, but think too much, cause some units to stretch out a lot

// BUGFIX - Turn this off just in case signficantly affecting voices, since NOT minor
// effect. Blizzard voice came out lousy, but probably because of bug, but still putting off
// BUGFIX - Reenabling to test out
// #define NOMODS_ENERGYPERPITCHGET // energy per pitch
// #define NOMODS_ENERGYPERVOLUMEGET   // energyper volume
// #define NOMODS_ENERGYPERVOLUMETUNE  // fine-tune energy per volume to keep same energy afterwards

// #define SNAPTOPITCH_10000     0.3   // snap to pitch at 10,000 units
// #define SNAPTOPITCH_100000    0.85   // snap to pitch at 100,000 units

// #define FULLPCM_PITCHERRORADD  50.0   // if full PCM, then pitch errors are increased in importance.
         // BUGFIX - hack from 20 to 50
         // BUGFIX - Changed to an addition so would do plosives too
         // BUGFIX - Actually caclulated and stored in gafUnitScorePCMPitch

// #define SNAPTODURATION_10000  0.2   // how much snap to duration with 10,000 units
// #define SNAPTODURATION_100000 0.6   // how much snap to duration with 100K units

#define TTSVERSION_DERIVED    0     // to make sure can't load in bad format
#define TTSVERSION_MASTER     14     // to make sure can't load in bad format
   // BUGFIX - Upped to 2 since just changed file format
   // BUGFIX - Upped to 4 since changed format of SRFEATURESMALL
   // BUGFIX - Upped to 5 because addid bDurSkew

#define OPTIMALNUMUNITS       10000  // optimal number of units that tested for
#define MAXPHONEHYP           (MAXUNITOPTION*2)   // maximum hypothesis for entire sentence, at OPTIMALNUMUNITS
#define MAXUNITOPTION         50    // maximum unit options, at OPTIMALNUMUNITS
                                    // BUGFIX - Lowered from 100 to 50 as speed optimization
                                    // Tried to lower to 25 but too many changes resulted
                                    // NOTE: Sounds slightly better with MAXUNITOPTION of 100, but too slow, using 50
#define MAXUNITOPTIONTOTAL    200   // no matter how large the voice, never have any more than this number of units
#define FIRSTPASSMAXUNITOPTIONSSCALE      4     // 4x as many units in first pass

#define PROSODYNGRAMRANGE     (POS_MAJOR_NUMPLUSONE*pLexTTS->Stresses())      // since need to handle stressed and unstressed

#define MINPROSODYSAMPLES     5     // must have 5 copies of a sample used for prosody calculation
#define MAXSUBVOICES          9     // maximum number of subvoices in a tts voice

#define COMPAREREGIONSIZE        3     // left/right by 3 syllables
// #define MAXCOMPAREREGIONERR      (256 * 4)   // maximum error per
// #define MAXCOMPAREREGIONERRTOTAL (MAXCOMPAREREGIONERR * COMPAREREGIONSIZE * COMPAREREGIONSIZE)

// #define COMPARESINGLESYLLABLE_CROSSSENTENCEPENALTY 12    // penalty for crossing a sentence boundary
// #define COMPARESINGLESYLLABLE_RANDOMIZE   6     // how much to randomize every value
         // BUGFIX - Upped from 4 to 6 to increase the amount of randomization
#define COMPARESINGLESYLLABLE_NOMATCH     (SRCOMPAREWEIGHT*100000)    // error if no match at all

#define FINDBESTMATCH_BEAMSEARCHNUM       10       // do 10 passes of beam searhc
#define FINDBESTMATCH_SEARCHGROUP         3        // group FINDBESTMATCH_BEAMSEARCHNUM together to find best version

// #define BEAMSEARCHSWITCHPENALTY           5       // how much penalty to include for switching sentences. This is a DIVISOR
      // 5 or 10 both seems to be ok. Choosing 5 so that will try to keep entire sentences together more
// #define BEAMSEARCHSWITCHSENTENCEPENALTY_LESSFORPOS 20 // include only a slight penalty for switching POS

                                                   // previous one chosen, then some penality

#define WORDSENTSYL_MINEXAMPLES           8        // must have 8 examples minimum
      // BUGFIX - Was 4, but upped to 8
#define WORDSENTSYL_KEEPEXAMPLES          (WORDSENTSYL_MINEXAMPLES*2)  // number of examples to keep around


// SENTSYLHEADER - Header for binary version
typedef struct {
   DWORD             dwNum;      // number of syllables
   DWORD             dwPhrases;  // number of phrases in m_lSENTSYLPHRASE
} SENTSYLHEADER, *PSENTSYLHEADER;


// TTSMMP - For storing information passed to dialog
typedef struct {
   PCMTTS               pTTS;       // TTS working on
   PCM3DWave            pWave;      // wave for testing
   PCMTTSSubVoice       pSubVoice;  // subvoice to use
   WCHAR                szTTSImport[256]; // TTS to import
   PCMTTS               pTTSImport; // TTS to import
} TTSMMP, *PTTSMMP;


// PHONEHYP - Hypothesis about what triphone is used
#define PHONEHISTORY    (TTSDEMIPHONES+3 + TTSDEMIPHONES*5)        // number of phonemes in past that remember history
                                 // for hypothesis
                  // Since may make score change based on one complete phone back, and then a little bit
                  // BUGFIX - Make the phone history go 5 (or 6) phonmems back. Used to be TTSDEMIPHONES+3 (or 2 phonemes)

typedef struct {
   PCMTTSTriPhoneAudio  aPCMTTSTriPhoneAudio[PHONEHISTORY];   // previous triphones
   double               fScoreWithEnergy;    // score so far, lower = better
   double               fScoreNoEnergy;   // score so far, excluding eneryg
   DWORD                dwPreviousIndex;  // previous index into UNITOPTION list
   BOOL                 fEndSilence;   // set to TRUE if the last phoneme ended in a silence
} PHONEHYP, *PPHONEHYP;

// UNITOPTION - For storing information about potential unit
typedef struct {
   double               fScore;          // score to sort on
   PCMTTSTriPhoneAudio  pTPA;        // triphone audio to use. Can be NULL if silence
} UNITOPTION, *PUNITOPTION;


// FBMSUBSENT - Subsentnece information for find best match
typedef struct {
   DWORD                dwSubSentence;    // subsentence number
   DWORD                dwIndex;          // index into subsentence
   DWORD                dwSubSentenceLength; // number of (spoken) syllables in subsentence
   DWORD                dwSentenceType;   // TYPICALSYLINFO_XXX
} FBMSUBSENT, *PFBMSUBSENT;

#define TCEVALUEBITS       10                   // store 10 bits for value
#define TCEUNIQUEIDBITS    (32 - TCEVALUEBITS)
#define TCEVALUEMAXASFP    (SRCOMPAREWEIGHT)    // maximum error that expect to get
#define TCEVALUEMAXASINT   ((1 << TCEVALUEBITS) - 1)                // maximum value as integer

#define TCEVALUEEXTRACT(x)    ((x) >> TCEUNIQUEIDBITS)  // extract a value from dwTCECompact
#define TCEUNIQUEIDEXTRACT(x) ((x) & ((1 << TCEUNIQUEIDBITS) - 1) )  // extract unique ID from dwTCECompact
#define TCECOMPACTCREATE(x,y) (((x) << TCEUNIQUEIDBITS) | (y))

#define USESMALLTPACONNECTIONERROR        // so save memory
// TPACONNECTIONERROR - Stor information about the quality of a conneciton
#ifdef USESMALLTPACONNECTIONERROR
typedef struct {
   DWORD                      dwTCECompact;     // high 10 bits (TCEVALUEEXTRACT) have value, lower bits have uniqueID
//   WORD                       wTriPhoneIndex;       // triphone that associated with
//   BYTE                       bPhone;     // phoneme
//   BYTE                       bValue;     // value stored, 0 = 0.0, TCEVALUEMAXASINT = TCEVALUEMAX
} TPACONNECTIONERROR, *PTPACONNECTIONERROR;
#else
typedef struct {
   PCMTTSTriPhoneAudio        pTPA;       // triphone that associated with
   fp                         fValue;     // value stored
} TPACONNECTIONERROR, *PTPACONNECTIONERROR;
#endif

// TTSFEATURECOMPPCM - Extra PCM information
typedef struct {
   BYTE           bPCMHarmFadeStart;         // harmonic number where the fading starts, 0 = DC, 1= fund, 2 = 1st harm, etc.
   BYTE           bPCMHarmFadeFull;          // harmonic number where fading is full
   BYTE           bPCMHarmNyquist;           // harmonic number where the nyquist limit kicks in. If 0, then no data in features
   BYTE           bPCMFill;                  // nothing
   float          fPCMScale;                 // how much to scale the PCM values by
#ifdef SRFEATUREINCLUDEPCM_SHORT
   short          asPCM[SRFEATUREPCM];       // points to store
#else
   char           acPCM[SRFEATUREPCM];       // points to store
#endif
} TTSFEATURECOMPPCM, *PTTSFEATURECOMPPCM;



// MATCHRANKFILTER - For filtering
typedef struct {
   PBESTSENTSWEEP       pBest;         // pointer to original data stored here
   fp                   fRank;         // rank at the time
} MATCHRANKFILTER, *PMATCHRANKFILTER;

// BEAMDIFFERENCE - Structure where can store the beams and difference them
typedef struct {
   PCSentenceSyllable   pSS;           // sentence
   fp                   fScore;        // SR score
   fp                   fCompareRegion;     // from CompareRegion()
} BEAMDIFFERENCE, *PBEAMDIFFERENCE;




#ifndef TEMPLATEPROOSDYMODEL
// EMTFINDBESTMATCH - Prosody beam search info
typedef struct {
   // on all EMTCxxx
   DWORD          dwStart;       // start count
   DWORD          dwEnd;         // end count
   DWORD          dwType;        // type

   // specific
   PCListFixed    plPerSyl;
   fp             fAccuracy;
   DWORD          dwRandomness;  // how much to randomize. usually 1, but may be more
   PCSentenceSyllable pThis;
   CTTSProsody    **ppCTTSProsody;
   DWORD          dwNum;
   PCListFixed    plBEAMDIFFERENCE;
   PCOMPARESYLINFO   pInfo; // paramter info for penalties
   DWORD          dwMultiPass;
   PCMLexicon     pLexTTS;

} EMTFINDBESTMATCH, *PEMTFINDBESTMATCH;
#endif

// EMTSENTENCEMATCH - Fills in PCSentenceMatch
typedef struct {
   // on all EMTCxxx
   DWORD          dwStart;       // start count
   DWORD          dwEnd;         // end count
   DWORD          dwType;        // type

   // specific
   PCListFixed    plPCSentenceMatch;   // list of sentence matches, evaluate from start to end
   PCSentenceSyllable pSS;
   CTTSProsody    **ppCTTSProsody;
   DWORD          dwNum;
   PCOMPARESYLINFO   pInfo; // paramter info for penalties
   int            iTTSQuality;
   DWORD          dwMultiPass;
   BOOL           fWord;
   WORD           wPeriod;
   PCMLexicon     pLexTTS; // tts with phonemes in it

} EMTSENTENCEMATCH, *PEMTSENTENCEMATCH;


// BUGFIX - Changed to 1/10th of a second since maybe slightly better than 1/20th of a sec
// for small-unit voices, and probably (though not tested) noticably better for many-unit voices
#define JOINHALFWINDOWSIZE8000               (SRSAMPLESPERSEC / 20)  // 1/20th of a second on either side
#define JOINHALFWINDOWSIZE32000              (SRSAMPLESPERSEC / 10)  // 1/20th of a second on either side
#define JOINHALFWINDOWSIZE128000             (SRSAMPLESPERSEC / 5)  // 1/20th of a second on either side
   // BUGBUG - SRSAMPLESPERSEC/5 may be too large, might want to change to SRSAMPLESPERSEC/10

#define JOINHALFWINDOWSIZE(dwUnits)             (JoinHalfWindowSize(dwUnits))  // 1/20th of a second on either side

#define MAXJOINRANGE(dwUnits)                   (JoinHalfWindowSize(dwUnits) / 2)  // join isn't allowed to be stretched any more than this

// BUGFIX - 1/20th of a second too small. Not good joins.
//#define JOINHALFWINDOWSIZE                   (SRSAMPLESPERSEC / 20)  // 1/20th of a second on either side
//#define MAXJOINRANGE                         (SRSAMPLESPERSEC / 50)  // join isn't allowed to be stretched any more than this

#define SCORECALCDISCONTINUITYSCALE                0.5      // if the previous unit to the proposed one is different than the

// #define  SCORECALCWEIGHTBOUNDARY_LONG        (128)         // how much the boundary is weighted compared to the
                                                // "self" score. 0 = none, 128 = 50%, 256 = 100%
      // BUGFIX - changed long from 64 to 128 so not that much difference
      // BUGFIX - returned SCORECALCWEIGHTBOUNDARY_XXX after fixed bug in scores. Was 128 and 128+64
      // BUGFIX - restored do 128 and 128+64 since contiguous phonemes important (from 128-32 and 128+32)
// #define  SCORECALCWEIGHTBOUNDARY_SHORT       (128+64)         // how much the boundary is weighted compared to the
// #define  LONGPHONEME                         (SRSAMPLESPERSEC/10)
// #define  SHORTPHONEME                        (SRSAMPLESPERSEC/25)
      // NOTE: weighting boundary as less important for long phonemes than short

// BUGFIX - Was 0.2, but should really be 1/SRSAMPLESPERSEC so negates * SRSAMPLESPERSEC
// BUGFIX - Was 0.2, but put in better phase so make dependent on non-PCM like
// BUGGFIX - Changed to 1 frame so that it's technically correct, with new substitution costs for mismatched context
// #define  SCORECALCWEIGHTBOUNDARY             (1.0/(fp)SRSAMPLESPERSEC)     // a change in unit affects the sound of the surround 0.2 second
// #define  SCORECALCWEIGHTBOUNDARY             (1.0/(fp)SRSAMPLESPERSEC + HOWMUCHSYNTHLIKEPCM / 0.5 * 0.2)     // a change in unit affects the sound of the surround 0.2 second
                                                      // SRFEATUREs signficantly
//#define  SCORECALCWEIGHTBOUNDARY_SCALEPLOSIVE   4.0   // encourage plosives to stick
//#define  SCORECALCWEIGHTBOUNDARY_SCALEUNVOICED  0.25  // don't need unvoiced (non-plosive) to stick

// BUGFIX - Changed to 0 so it's technically correct, with new substitution costs for mismatched context
#define  SCORECALCWEIGHTTHEORETICALBOUNDARY  0.0 // how much the theoretical boundary is weighted compared
// #define  SCORECALCWEIGHTTHEORETICALBOUNDARY  0.25 // how much the theoretical boundary is weighted compared
   // BUGFIX - Was 0.5. Theoertically should be 0, but think will stick with 0.25
// #define  SCORECALCWEIGHTTHEORETICALBOUNDARY  0.5 // how much the theoretical boundary is weighted compared
                                                // to the calculated boundar. 0 = none, 128=50%, 256=100%
                                                // BUGFIX - CHanged from (128+64) to 128
                                                // BUGFIX - Changed to fp
                                                // BUGFIX - Was 0.5, but thought issue through and should be 0

#define  MAXBORDERERROR    (BORDERDBSCALE * 60.0) // maximum error from border, for each side. so total is 2x this

// #define  HYPBADMEGAGROUP   MAXBORDERERROR       // error if found crossing megagroup
// #define  HYPBADGROUP       (MAXBORDERERROR/4)   // if bad group then this is the error
// #define  HYPBADSTRESS      (MAXBORDERERROR/16)   // error if found with same phoneme, but wrong stress
   // BUGFIX - changed from /8 to /16

// #define  HYPCONTIGUOUSUNITS      (MAXBORDERERROR/4)   // encourage contigous units
   // BUGFIX - Was MAXBORDERERROR/2, but decreased to MAXBORDERERROR/4 so that pitch would be relatively more important
// #define  MAXNOTCONTGIOUSNUM       0              // maximum count for not contious
   // BUGFIX - Can go up to NUMPHONECONTIGOUS, but puts way too much emphasis on contigous units
   // NOTE - Not sure if this should be any value at all, should probably all be in hypcontiguous
   // BUGFIX - Made 0 since I don't think this is right thing to do
// #define  HYPNOTCONTIGUOUS  (3*(fPlosive ? (MAXBORDERERROR*1) : (fVoiced ? (MAXBORDERERROR/4) : (MAXBORDERERROR/16))))   // error if same phoneme, but not contiguous
   // BUGFIX - Upped HYPNOTCONTIGUOUS to encourage contiguous chunks
   // Note: Plosive check is really high to make sure plosives stay attached to other stuff
   // BUGFIX - Multiply all by 4 to encourage contiguous
   // BUGFIX - Was multiplied by 4, but changed to 2 so that pitch would be relatively more important
   // BUGFIX - Reduced error for unvoiced non-plosive to 1/32 (from 1/8) to encourage cutting there
   // BUGGIX - was 2 - upped to 8 and improved quality of voice somewhat
   // BUGFIX - Lowered back to 3 since was going for long sequences, but at the expense of pitch and stretching. Seems to be between 2 and 4
   // BUGFIX - Was plosive w *2, unvoiced w/ /32, changed to *1 and /16 so not so severe

//#define  HYPBADWORD        (MAXBORDERERROR/16)  // error is same phoneme, contiguous, but from wrong word
#if 0 // no longer used
#define  HYPBADWORD        0  // error is same phoneme, contiguous, but from wrong word
#endif
   // BUGFIX - Remove this since no longer storing word number
#define  HYPINSERTSILENCE  (MAXBORDERERROR/2)   // penalty for coming out of or going into silence
   // BUGFIX - Used to include HYPBADSTRESS but it's no longer a define
#define  AVERAGEUNITLENGTH (SRSAMPLESPERSEC / 10)   // one fifth of a second for the averag length of a phoneme
   // BUGFIX - Was 1/5 of a second, should really be 1/10th of a second, which halves HYPAUTOCULL
#define  HYPAUTOCULL       (MAXBORDERERROR *2 * fEnergyAverage * AVERAGEUNITLENGTH)         // if a score is this much worse than the best one then cull
   // BUGFIX - Increase from HYPBADGROUP to HYPBADGROUP*2
   // BUGFIX - Moved back to HYPBADGROUP*1 because too slow
   // BUGFIX - Just changed to 3/2 just to be paranoid
   // NOTE - scaling HYPAUTOCULL by TTSDEMIPHONES since related to PHONEHISTORY
   // BUGFIX - Changed from 3/2 to *3 to make sure doesn't autocull too quickly
   // BUGFIX - Remove  * TTSDEMIPHONES since now enery is always sclaed by averageunitlength, independent of TTSDEMIPHONE
   // BUGFIX - Changed from MAXBORDERERROR*3 to MAXBORDERROR*2 because no difference



#define  ATSTARTOFPHONE                      ((dwTime%TTSDEMIPHONES)==0)
#define  ATENDOFPHONE                        ((dwTime%TTSDEMIPHONES)==(TTSDEMIPHONES-1))
//#define  CROSSWORDLESSPENALTYLEFT(x)         (fWordStart ? (x)/2 : (fSylStart ? (x)*2/3 : (x) ))
//#define  CROSSWORDLESSPENALTYRIGHT(x)        (fWordEnd ? (x)/2 : (fSylEnd ? (x)*2/3 : (x) ))
   // BUGFIX - take into account syllable boundary, and less penalty if bad units across words
   // BUGFIX - Maks ure CROSSWORDLESSPENALTYxxx takes into account demiphone... changed so doesnt
// #define  HYPBADWORDPOS     (10.0)          // penalty for bad word position, 5 db for each side off
// #define  HYPSCOREPITCHDELTAPEROCTAVE (HYPPITCHPENALTYPEROCTAVE)  // one octave difference in pitch is this bad
//#define  HYPSCOREPITCHDELTAPEROCTAVEFORGIVE (HYPPITCHPENALTYPEROCTAVEFORGIVE)  // forgive pitch errors very close
//#define  HYPSCOREDURDELTA  (HYPDURATIONPENALTYPERDOUBLE)   // doubling or halving of duration


// Values for gafUnitScoreXXX from MikeRozak analysis of unit costs
// old: Values for gafUnitScoreXXX from RMS analysis of unit costs

static float gafUnitScorePitchPerPhone[NUMLEXENGLISHPHONE * MAXSTRESSES * 2] = {
   // positive (target octave above), negative
	30.1368, 31.7744, // phone <s>, stress 0
	30.1368, 31.7744, // phone <s>, stress 1
	30.1368, 31.7744, // phone <s>, stress 2
	30.1368, 31.7744, // phone <s>, stress 3
	30.1368, 31.7744, // phone <s>, stress 4
	30.1368, 31.7744, // phone <s>, stress 5
	30.1368, 31.7744, // phone <s>, stress 6
	30.1368, 31.7744, // phone <s>, stress 7
	159.093, 121.736, // phone aa, stress 0
	32.0882, 30.6145, // phone aa, stress 1
	34.4786, 37.5861, // phone aa, stress 2
	34.4786, 37.5861, // phone aa, stress 3
	34.4786, 37.5861, // phone aa, stress 4
	34.4786, 37.5861, // phone aa, stress 5
	34.4786, 37.5861, // phone aa, stress 6
	34.4786, 37.5861, // phone aa, stress 7
	98.7022, 133.522, // phone ae, stress 0
	28.8143, 25.9252, // phone ae, stress 1
	34.4786, 37.5861, // phone ae, stress 2
	34.4786, 37.5861, // phone ae, stress 3
	34.4786, 37.5861, // phone ae, stress 4
	34.4786, 37.5861, // phone ae, stress 5
	34.4786, 37.5861, // phone ae, stress 6
	34.4786, 37.5861, // phone ae, stress 7
	29.5303, 54.8137, // phone ah, stress 0
	33.2092, 30.9277, // phone ah, stress 1
	37.4274, 44.1134, // phone ah, stress 2
	37.4274, 44.1134, // phone ah, stress 3
	37.4274, 44.1134, // phone ah, stress 4
	37.4274, 44.1134, // phone ah, stress 5
	37.4274, 44.1134, // phone ah, stress 6
	37.4274, 44.1134, // phone ah, stress 7
	85.733, 93.7592, // phone ao, stress 0
	29.036, 20.3006, // phone ao, stress 1
	37.4274, 44.1134, // phone ao, stress 2
	37.4274, 44.1134, // phone ao, stress 3
	37.4274, 44.1134, // phone ao, stress 4
	37.4274, 44.1134, // phone ao, stress 5
	37.4274, 44.1134, // phone ao, stress 6
	37.4274, 44.1134, // phone ao, stress 7
	60.2821, 54.6461, // phone aw, stress 0
	43.2258, 42.9907, // phone aw, stress 1
	60.2821, 54.6461, // phone aw, stress 2
	60.2821, 54.6461, // phone aw, stress 3
	60.2821, 54.6461, // phone aw, stress 4
	60.2821, 54.6461, // phone aw, stress 5
	60.2821, 54.6461, // phone aw, stress 6
	60.2821, 54.6461, // phone aw, stress 7
	37.4274, 44.1134, // phone ax, stress 0
	37.4274, 44.1134, // phone ax, stress 1
	37.4274, 44.1134, // phone ax, stress 2
	37.4274, 44.1134, // phone ax, stress 3
	37.4274, 44.1134, // phone ax, stress 4
	37.4274, 44.1134, // phone ax, stress 5
	37.4274, 44.1134, // phone ax, stress 6
	37.4274, 44.1134, // phone ax, stress 7
	99.0401, 124.159, // phone ay, stress 0
	43.0026, 39.0295, // phone ay, stress 1
	43.5937, 48.6633, // phone ay, stress 2
	43.5937, 48.6633, // phone ay, stress 3
	43.5937, 48.6633, // phone ay, stress 4
	43.5937, 48.6633, // phone ay, stress 5
	43.5937, 48.6633, // phone ay, stress 6
	43.5937, 48.6633, // phone ay, stress 7
	27.4337, 26.4896, // phone b, stress 0
	19.3411, 17.725, // phone b, stress 1
	19.3411, 17.725, // phone b, stress 2
	19.3411, 17.725, // phone b, stress 3
	19.3411, 17.725, // phone b, stress 4
	19.3411, 17.725, // phone b, stress 5
	19.3411, 17.725, // phone b, stress 6
	19.3411, 17.725, // phone b, stress 7
	40.1515, 28.5677, // phone ch, stress 0
	45.5609, 36.1753, // phone ch, stress 1
	45.5609, 36.1753, // phone ch, stress 2
	45.5609, 36.1753, // phone ch, stress 3
	45.5609, 36.1753, // phone ch, stress 4
	45.5609, 36.1753, // phone ch, stress 5
	45.5609, 36.1753, // phone ch, stress 6
	45.5609, 36.1753, // phone ch, stress 7
	27.4413, 37.042, // phone d, stress 0
	20.3764, 23.1684, // phone d, stress 1
	20.3764, 23.1684, // phone d, stress 2
	20.3764, 23.1684, // phone d, stress 3
	20.3764, 23.1684, // phone d, stress 4
	20.3764, 23.1684, // phone d, stress 5
	20.3764, 23.1684, // phone d, stress 6
	20.3764, 23.1684, // phone d, stress 7
	24.8374, 21.8028, // phone dh, stress 0
	31.3934, 25.8108, // phone dh, stress 1
	31.3934, 25.8108, // phone dh, stress 2
	31.3934, 25.8108, // phone dh, stress 3
	31.3934, 25.8108, // phone dh, stress 4
	31.3934, 25.8108, // phone dh, stress 5
	31.3934, 25.8108, // phone dh, stress 6
	31.3934, 25.8108, // phone dh, stress 7
	47.3622, 82.1135, // phone eh, stress 0
	36.2407, 33.4193, // phone eh, stress 1
	34.4786, 37.5861, // phone eh, stress 2
	34.4786, 37.5861, // phone eh, stress 3
	34.4786, 37.5861, // phone eh, stress 4
	34.4786, 37.5861, // phone eh, stress 5
	34.4786, 37.5861, // phone eh, stress 6
	34.4786, 37.5861, // phone eh, stress 7
	39.8427, 51.7553, // phone er, stress 0
	58.5108, 67.7219, // phone er, stress 1
	37.4274, 44.1134, // phone er, stress 2
	37.4274, 44.1134, // phone er, stress 3
	37.4274, 44.1134, // phone er, stress 4
	37.4274, 44.1134, // phone er, stress 5
	37.4274, 44.1134, // phone er, stress 6
	37.4274, 44.1134, // phone er, stress 7
	134.105, 146.035, // phone ey, stress 0
	48.2884, 50.0335, // phone ey, stress 1
	43.5937, 48.6633, // phone ey, stress 2
	43.5937, 48.6633, // phone ey, stress 3
	43.5937, 48.6633, // phone ey, stress 4
	43.5937, 48.6633, // phone ey, stress 5
	43.5937, 48.6633, // phone ey, stress 6
	43.5937, 48.6633, // phone ey, stress 7
	24.1418, 22.2857, // phone f, stress 0
	29.6463, 30.5532, // phone f, stress 1
	29.6463, 30.5532, // phone f, stress 2
	29.6463, 30.5532, // phone f, stress 3
	29.6463, 30.5532, // phone f, stress 4
	29.6463, 30.5532, // phone f, stress 5
	29.6463, 30.5532, // phone f, stress 6
	29.6463, 30.5532, // phone f, stress 7
	43.0041, 47.6185, // phone g, stress 0
	19.9681, 16.8024, // phone g, stress 1
	19.9681, 16.8024, // phone g, stress 2
	19.9681, 16.8024, // phone g, stress 3
	19.9681, 16.8024, // phone g, stress 4
	19.9681, 16.8024, // phone g, stress 5
	19.9681, 16.8024, // phone g, stress 6
	19.9681, 16.8024, // phone g, stress 7
	28.0196, 19.1868, // phone h, stress 0
	28.0112, 19.1968, // phone h, stress 1
	28.0112, 19.1968, // phone h, stress 2
	28.0112, 19.1968, // phone h, stress 3
	28.0112, 19.1968, // phone h, stress 4
	28.0112, 19.1968, // phone h, stress 5
	28.0112, 19.1968, // phone h, stress 6
	28.0112, 19.1968, // phone h, stress 7
	25.8377, 43.3521, // phone ih, stress 0
	34.121, 30.8155, // phone ih, stress 1
	34.4786, 37.5861, // phone ih, stress 2
	34.4786, 37.5861, // phone ih, stress 3
	34.4786, 37.5861, // phone ih, stress 4
	34.4786, 37.5861, // phone ih, stress 5
	34.4786, 37.5861, // phone ih, stress 6
	34.4786, 37.5861, // phone ih, stress 7
	31.4079, 44.9845, // phone iy, stress 0
	48.5651, 50.8575, // phone iy, stress 1
	43.5937, 48.6633, // phone iy, stress 2
	43.5937, 48.6633, // phone iy, stress 3
	43.5937, 48.6633, // phone iy, stress 4
	43.5937, 48.6633, // phone iy, stress 5
	43.5937, 48.6633, // phone iy, stress 6
	43.5937, 48.6633, // phone iy, stress 7
	51.2204, 41.3874, // phone jh, stress 0
	45.5609, 36.1753, // phone jh, stress 1
	45.5609, 36.1753, // phone jh, stress 2
	45.5609, 36.1753, // phone jh, stress 3
	45.5609, 36.1753, // phone jh, stress 4
	45.5609, 36.1753, // phone jh, stress 5
	45.5609, 36.1753, // phone jh, stress 6
	45.5609, 36.1753, // phone jh, stress 7
	14.8659, 6.3766, // phone k, stress 0
	19.9681, 16.8024, // phone k, stress 1
	19.9681, 16.8024, // phone k, stress 2
	19.9681, 16.8024, // phone k, stress 3
	19.9681, 16.8024, // phone k, stress 4
	19.9681, 16.8024, // phone k, stress 5
	19.9681, 16.8024, // phone k, stress 6
	19.9681, 16.8024, // phone k, stress 7
	26.8301, 32.1748, // phone l, stress 0
	27.5833, 30.5264, // phone l, stress 1
	27.5833, 30.5264, // phone l, stress 2
	27.5833, 30.5264, // phone l, stress 3
	27.5833, 30.5264, // phone l, stress 4
	27.5833, 30.5264, // phone l, stress 5
	27.5833, 30.5264, // phone l, stress 6
	27.5833, 30.5264, // phone l, stress 7
	32.9177, 40.8805, // phone m, stress 0
	24.8213, 30.2762, // phone m, stress 1
	24.8213, 30.2762, // phone m, stress 2
	24.8213, 30.2762, // phone m, stress 3
	24.8213, 30.2762, // phone m, stress 4
	24.8213, 30.2762, // phone m, stress 5
	24.8213, 30.2762, // phone m, stress 6
	24.8213, 30.2762, // phone m, stress 7
	20.7164, 25.6194, // phone n, stress 0
	24.8213, 30.2762, // phone n, stress 1
	24.8213, 30.2762, // phone n, stress 2
	24.8213, 30.2762, // phone n, stress 3
	24.8213, 30.2762, // phone n, stress 4
	24.8213, 30.2762, // phone n, stress 5
	24.8213, 30.2762, // phone n, stress 6
	24.8213, 30.2762, // phone n, stress 7
	27.8083, 21.0161, // phone nx, stress 0
	24.8213, 30.2762, // phone nx, stress 1
	24.8213, 30.2762, // phone nx, stress 2
	24.8213, 30.2762, // phone nx, stress 3
	24.8213, 30.2762, // phone nx, stress 4
	24.8213, 30.2762, // phone nx, stress 5
	24.8213, 30.2762, // phone nx, stress 6
	24.8213, 30.2762, // phone nx, stress 7
	78.9421, 100.395, // phone ow, stress 0
	57.9572, 50.4228, // phone ow, stress 1
	60.2821, 54.6461, // phone ow, stress 2
	60.2821, 54.6461, // phone ow, stress 3
	60.2821, 54.6461, // phone ow, stress 4
	60.2821, 54.6461, // phone ow, stress 5
	60.2821, 54.6461, // phone ow, stress 6
	60.2821, 54.6461, // phone ow, stress 7
	60.2821, 54.6461, // phone oy, stress 0
	68.1546, 62.8197, // phone oy, stress 1
	60.2821, 54.6461, // phone oy, stress 2
	60.2821, 54.6461, // phone oy, stress 3
	60.2821, 54.6461, // phone oy, stress 4
	60.2821, 54.6461, // phone oy, stress 5
	60.2821, 54.6461, // phone oy, stress 6
	60.2821, 54.6461, // phone oy, stress 7
	14.17, 6.94493, // phone p, stress 0
	19.3411, 17.725, // phone p, stress 1
	19.3411, 17.725, // phone p, stress 2
	19.3411, 17.725, // phone p, stress 3
	19.3411, 17.725, // phone p, stress 4
	19.3411, 17.725, // phone p, stress 5
	19.3411, 17.725, // phone p, stress 6
	19.3411, 17.725, // phone p, stress 7
	28.0694, 28.5581, // phone r, stress 0
	27.5833, 30.5264, // phone r, stress 1
	27.5833, 30.5264, // phone r, stress 2
	27.5833, 30.5264, // phone r, stress 3
	27.5833, 30.5264, // phone r, stress 4
	27.5833, 30.5264, // phone r, stress 5
	27.5833, 30.5264, // phone r, stress 6
	27.5833, 30.5264, // phone r, stress 7
	13.6144, 8.11766, // phone s, stress 0
	15.9053, 15.1973, // phone s, stress 1
	15.9053, 15.1973, // phone s, stress 2
	15.9053, 15.1973, // phone s, stress 3
	15.9053, 15.1973, // phone s, stress 4
	15.9053, 15.1973, // phone s, stress 5
	15.9053, 15.1973, // phone s, stress 6
	15.9053, 15.1973, // phone s, stress 7
	25.1195, 22.9944, // phone sh, stress 0
	34.2866, 28.5224, // phone sh, stress 1
	34.2866, 28.5224, // phone sh, stress 2
	34.2866, 28.5224, // phone sh, stress 3
	34.2866, 28.5224, // phone sh, stress 4
	34.2866, 28.5224, // phone sh, stress 5
	34.2866, 28.5224, // phone sh, stress 6
	34.2866, 28.5224, // phone sh, stress 7
	16.584, 12.5944, // phone t, stress 0
	20.3764, 23.1684, // phone t, stress 1
	20.3764, 23.1684, // phone t, stress 2
	20.3764, 23.1684, // phone t, stress 3
	20.3764, 23.1684, // phone t, stress 4
	20.3764, 23.1684, // phone t, stress 5
	20.3764, 23.1684, // phone t, stress 6
	20.3764, 23.1684, // phone t, stress 7
	49.3865, 43.5358, // phone th, stress 0
	31.3934, 25.8108, // phone th, stress 1
	31.3934, 25.8108, // phone th, stress 2
	31.3934, 25.8108, // phone th, stress 3
	31.3934, 25.8108, // phone th, stress 4
	31.3934, 25.8108, // phone th, stress 5
	31.3934, 25.8108, // phone th, stress 6
	31.3934, 25.8108, // phone th, stress 7
	37.4274, 31.9449, // phone uh, stress 0
	33.8254, 29.9716, // phone uh, stress 1
	37.4274, 44.1134, // phone uh, stress 2
	37.4274, 44.1134, // phone uh, stress 3
	37.4274, 44.1134, // phone uh, stress 4
	37.4274, 44.1134, // phone uh, stress 5
	37.4274, 44.1134, // phone uh, stress 6
	37.4274, 44.1134, // phone uh, stress 7
	70.6073, 123.125, // phone uw, stress 0
	36.8518, 39.8876, // phone uw, stress 1
	37.4274, 44.1134, // phone uw, stress 2
	37.4274, 44.1134, // phone uw, stress 3
	37.4274, 44.1134, // phone uw, stress 4
	37.4274, 44.1134, // phone uw, stress 5
	37.4274, 44.1134, // phone uw, stress 6
	37.4274, 44.1134, // phone uw, stress 7
	33.0131, 42.3115, // phone v, stress 0
	29.6463, 30.5532, // phone v, stress 1
	29.6463, 30.5532, // phone v, stress 2
	29.6463, 30.5532, // phone v, stress 3
	29.6463, 30.5532, // phone v, stress 4
	29.6463, 30.5532, // phone v, stress 5
	29.6463, 30.5532, // phone v, stress 6
	29.6463, 30.5532, // phone v, stress 7
	22.3796, 13.0321, // phone w, stress 0
	31.48, 19.2303, // phone w, stress 1
	31.48, 19.2303, // phone w, stress 2
	31.48, 19.2303, // phone w, stress 3
	31.48, 19.2303, // phone w, stress 4
	31.48, 19.2303, // phone w, stress 5
	31.48, 19.2303, // phone w, stress 6
	31.48, 19.2303, // phone w, stress 7
	30.8575, 35.7837, // phone y, stress 0
	31.48, 19.2303, // phone y, stress 1
	31.48, 19.2303, // phone y, stress 2
	31.48, 19.2303, // phone y, stress 3
	31.48, 19.2303, // phone y, stress 4
	31.48, 19.2303, // phone y, stress 5
	31.48, 19.2303, // phone y, stress 6
	31.48, 19.2303, // phone y, stress 7
	18.9162, 12.8571, // phone z, stress 0
	15.9053, 15.1973, // phone z, stress 1
	15.9053, 15.1973, // phone z, stress 2
	15.9053, 15.1973, // phone z, stress 3
	15.9053, 15.1973, // phone z, stress 4
	15.9053, 15.1973, // phone z, stress 5
	15.9053, 15.1973, // phone z, stress 6
	15.9053, 15.1973, // phone z, stress 7
	69.1111, 110.147, // phone zh, stress 0
	34.2866, 28.5224, // phone zh, stress 1
	34.2866, 28.5224, // phone zh, stress 2
	34.2866, 28.5224, // phone zh, stress 3
	34.2866, 28.5224, // phone zh, stress 4
	34.2866, 28.5224, // phone zh, stress 5
	34.2866, 28.5224, // phone zh, stress 6
	34.2866, 28.5224, // phone zh, stress 7

};


static float gafUnitScorePCMPitchPhone[NUMLEXENGLISHPHONE * MAXSTRESSES * 2] = {  // how much PCM affects score per HALF octave
   // positive (target octave above), negative
	23.6506, 23.6192, // english phone <s>, stress 0
	23.6506, 23.6192, // english phone <s>, stress 1
	23.6506, 23.6192, // english phone <s>, stress 2
	23.6506, 23.6192, // english phone <s>, stress 3
	23.6506, 23.6192, // english phone <s>, stress 4
	23.6506, 23.6192, // english phone <s>, stress 5
	23.6506, 23.6192, // english phone <s>, stress 6
	23.6506, 23.6192, // english phone <s>, stress 7
	39.7134, 40.9098, // english phone aa, stress 0
	31.9203, 30.8605, // english phone aa, stress 1
	29.6158, 30.0626, // english phone aa, stress 2
	29.6158, 30.0626, // english phone aa, stress 3
	29.6158, 30.0626, // english phone aa, stress 4
	29.6158, 30.0626, // english phone aa, stress 5
	29.6158, 30.0626, // english phone aa, stress 6
	29.6158, 30.0626, // english phone aa, stress 7
	35.1554, 36.6935, // english phone ae, stress 0
	29.9401, 30.0835, // english phone ae, stress 1
	29.6158, 30.0626, // english phone ae, stress 2
	29.6158, 30.0626, // english phone ae, stress 3
	29.6158, 30.0626, // english phone ae, stress 4
	29.6158, 30.0626, // english phone ae, stress 5
	29.6158, 30.0626, // english phone ae, stress 6
	29.6158, 30.0626, // english phone ae, stress 7
	24.8413, 25.0595, // english phone ah, stress 0
	28.9577, 28.7779, // english phone ah, stress 1
	31.5339, 30.3827, // english phone ah, stress 2
	31.5339, 30.3827, // english phone ah, stress 3
	31.5339, 30.3827, // english phone ah, stress 4
	31.5339, 30.3827, // english phone ah, stress 5
	31.5339, 30.3827, // english phone ah, stress 6
	31.5339, 30.3827, // english phone ah, stress 7
	42.0587, 39.6057, // english phone ao, stress 0
	31.2771, 30.7766, // english phone ao, stress 1
	31.5339, 30.3827, // english phone ao, stress 2
	31.5339, 30.3827, // english phone ao, stress 3
	31.5339, 30.3827, // english phone ao, stress 4
	31.5339, 30.3827, // english phone ao, stress 5
	31.5339, 30.3827, // english phone ao, stress 6
	31.5339, 30.3827, // english phone ao, stress 7
	35.9547, 34.0543, // english phone aw, stress 0
	31.1963, 30.3614, // english phone aw, stress 1
	34.0079, 31.905, // english phone aw, stress 2
	34.0079, 31.905, // english phone aw, stress 3
	34.0079, 31.905, // english phone aw, stress 4
	34.0079, 31.905, // english phone aw, stress 5
	34.0079, 31.905, // english phone aw, stress 6
	34.0079, 31.905, // english phone aw, stress 7
	31.5339, 30.3827, // english phone ax, stress 0
	31.5339, 30.3827, // english phone ax, stress 1
	31.5339, 30.3827, // english phone ax, stress 2
	31.5339, 30.3827, // english phone ax, stress 3
	31.5339, 30.3827, // english phone ax, stress 4
	31.5339, 30.3827, // english phone ax, stress 5
	31.5339, 30.3827, // english phone ax, stress 6
	31.5339, 30.3827, // english phone ax, stress 7
	41.2704, 42.9846, // english phone ay, stress 0
	31.6954, 32.6136, // english phone ay, stress 1
	35.0716, 35.9987, // english phone ay, stress 2
	35.0716, 35.9987, // english phone ay, stress 3
	35.0716, 35.9987, // english phone ay, stress 4
	35.0716, 35.9987, // english phone ay, stress 5
	35.0716, 35.9987, // english phone ay, stress 6
	35.0716, 35.9987, // english phone ay, stress 7
	17.4526, 16.5278, // english phone b, stress 0
	14.1021, 12.641, // english phone b, stress 1
	14.1021, 12.641, // english phone b, stress 2
	14.1021, 12.641, // english phone b, stress 3
	14.1021, 12.641, // english phone b, stress 4
	14.1021, 12.641, // english phone b, stress 5
	14.1021, 12.641, // english phone b, stress 6
	14.1021, 12.641, // english phone b, stress 7
	22.6726, 24.0739, // english phone ch, stress 0
	24.5105, 26.0977, // english phone ch, stress 1
	24.5105, 26.0977, // english phone ch, stress 2
	24.5105, 26.0977, // english phone ch, stress 3
	24.5105, 26.0977, // english phone ch, stress 4
	24.5105, 26.0977, // english phone ch, stress 5
	24.5105, 26.0977, // english phone ch, stress 6
	24.5105, 26.0977, // english phone ch, stress 7
	16.3196, 15.6825, // english phone d, stress 0
	14.2186, 13.187, // english phone d, stress 1
	14.2186, 13.187, // english phone d, stress 2
	14.2186, 13.187, // english phone d, stress 3
	14.2186, 13.187, // english phone d, stress 4
	14.2186, 13.187, // english phone d, stress 5
	14.2186, 13.187, // english phone d, stress 6
	14.2186, 13.187, // english phone d, stress 7
	17.7049, 18.8596, // english phone dh, stress 0
	16.4374, 18.1197, // english phone dh, stress 1
	16.4374, 18.1197, // english phone dh, stress 2
	16.4374, 18.1197, // english phone dh, stress 3
	16.4374, 18.1197, // english phone dh, stress 4
	16.4374, 18.1197, // english phone dh, stress 5
	16.4374, 18.1197, // english phone dh, stress 6
	16.4374, 18.1197, // english phone dh, stress 7
	31.2033, 32.5741, // english phone eh, stress 0
	31.8927, 32.4742, // english phone eh, stress 1
	29.6158, 30.0626, // english phone eh, stress 2
	29.6158, 30.0626, // english phone eh, stress 3
	29.6158, 30.0626, // english phone eh, stress 4
	29.6158, 30.0626, // english phone eh, stress 5
	29.6158, 30.0626, // english phone eh, stress 6
	29.6158, 30.0626, // english phone eh, stress 7
	39.9675, 35.9269, // english phone er, stress 0
	46.4194, 42.3735, // english phone er, stress 1
	31.5339, 30.3827, // english phone er, stress 2
	31.5339, 30.3827, // english phone er, stress 3
	31.5339, 30.3827, // english phone er, stress 4
	31.5339, 30.3827, // english phone er, stress 5
	31.5339, 30.3827, // english phone er, stress 6
	31.5339, 30.3827, // english phone er, stress 7
	46.7861, 47.8538, // english phone ey, stress 0
	39.2594, 40.4522, // english phone ey, stress 1
	35.0716, 35.9987, // english phone ey, stress 2
	35.0716, 35.9987, // english phone ey, stress 3
	35.0716, 35.9987, // english phone ey, stress 4
	35.0716, 35.9987, // english phone ey, stress 5
	35.0716, 35.9987, // english phone ey, stress 6
	35.0716, 35.9987, // english phone ey, stress 7
	7.63564, 10.5418, // english phone f, stress 0
	10.8984, 12.6911, // english phone f, stress 1
	10.8984, 12.6911, // english phone f, stress 2
	10.8984, 12.6911, // english phone f, stress 3
	10.8984, 12.6911, // english phone f, stress 4
	10.8984, 12.6911, // english phone f, stress 5
	10.8984, 12.6911, // english phone f, stress 6
	10.8984, 12.6911, // english phone f, stress 7
	24.1949, 23.7846, // english phone g, stress 0
	14.8455, 13.3957, // english phone g, stress 1
	14.8455, 13.3957, // english phone g, stress 2
	14.8455, 13.3957, // english phone g, stress 3
	14.8455, 13.3957, // english phone g, stress 4
	14.8455, 13.3957, // english phone g, stress 5
	14.8455, 13.3957, // english phone g, stress 6
	14.8455, 13.3957, // english phone g, stress 7
	20.3138, 20.0445, // english phone h, stress 0
	20.3107, 20.0447, // english phone h, stress 1
	20.3107, 20.0447, // english phone h, stress 2
	20.3107, 20.0447, // english phone h, stress 3
	20.3107, 20.0447, // english phone h, stress 4
	20.3107, 20.0447, // english phone h, stress 5
	20.3107, 20.0447, // english phone h, stress 6
	20.3107, 20.0447, // english phone h, stress 7
	24.7731, 25.5843, // english phone ih, stress 0
	30.4469, 31.3504, // english phone ih, stress 1
	29.6158, 30.0626, // english phone ih, stress 2
	29.6158, 30.0626, // english phone ih, stress 3
	29.6158, 30.0626, // english phone ih, stress 4
	29.6158, 30.0626, // english phone ih, stress 5
	29.6158, 30.0626, // english phone ih, stress 6
	29.6158, 30.0626, // english phone ih, stress 7
	30.781, 31.3245, // english phone iy, stress 0
	38.8123, 39.9472, // english phone iy, stress 1
	35.0716, 35.9987, // english phone iy, stress 2
	35.0716, 35.9987, // english phone iy, stress 3
	35.0716, 35.9987, // english phone iy, stress 4
	35.0716, 35.9987, // english phone iy, stress 5
	35.0716, 35.9987, // english phone iy, stress 6
	35.0716, 35.9987, // english phone iy, stress 7
	26.2307, 27.9872, // english phone jh, stress 0
	24.5105, 26.0977, // english phone jh, stress 1
	24.5105, 26.0977, // english phone jh, stress 2
	24.5105, 26.0977, // english phone jh, stress 3
	24.5105, 26.0977, // english phone jh, stress 4
	24.5105, 26.0977, // english phone jh, stress 5
	24.5105, 26.0977, // english phone jh, stress 6
	24.5105, 26.0977, // english phone jh, stress 7
	12.0656, 10.3063, // english phone k, stress 0
	14.8455, 13.3957, // english phone k, stress 1
	14.8455, 13.3957, // english phone k, stress 2
	14.8455, 13.3957, // english phone k, stress 3
	14.8455, 13.3957, // english phone k, stress 4
	14.8455, 13.3957, // english phone k, stress 5
	14.8455, 13.3957, // english phone k, stress 6
	14.8455, 13.3957, // english phone k, stress 7
	22.249, 21.4213, // english phone l, stress 0
	27.3302, 24.1736, // english phone l, stress 1
	27.3302, 24.1736, // english phone l, stress 2
	27.3302, 24.1736, // english phone l, stress 3
	27.3302, 24.1736, // english phone l, stress 4
	27.3302, 24.1736, // english phone l, stress 5
	27.3302, 24.1736, // english phone l, stress 6
	27.3302, 24.1736, // english phone l, stress 7
	19.541, 21.5867, // english phone m, stress 0
	17.8681, 19.145, // english phone m, stress 1
	17.8681, 19.145, // english phone m, stress 2
	17.8681, 19.145, // english phone m, stress 3
	17.8681, 19.145, // english phone m, stress 4
	17.8681, 19.145, // english phone m, stress 5
	17.8681, 19.145, // english phone m, stress 6
	17.8681, 19.145, // english phone m, stress 7
	16.6324, 18.3625, // english phone n, stress 0
	17.8681, 19.145, // english phone n, stress 1
	17.8681, 19.145, // english phone n, stress 2
	17.8681, 19.145, // english phone n, stress 3
	17.8681, 19.145, // english phone n, stress 4
	17.8681, 19.145, // english phone n, stress 5
	17.8681, 19.145, // english phone n, stress 6
	17.8681, 19.145, // english phone n, stress 7
	20.6936, 17.3868, // english phone nx, stress 0
	17.8681, 19.145, // english phone nx, stress 1
	17.8681, 19.145, // english phone nx, stress 2
	17.8681, 19.145, // english phone nx, stress 3
	17.8681, 19.145, // english phone nx, stress 4
	17.8681, 19.145, // english phone nx, stress 5
	17.8681, 19.145, // english phone nx, stress 6
	17.8681, 19.145, // english phone nx, stress 7
	37.7175, 35.3969, // english phone ow, stress 0
	33.9927, 31.1425, // english phone ow, stress 1
	34.0079, 31.905, // english phone ow, stress 2
	34.0079, 31.905, // english phone ow, stress 3
	34.0079, 31.905, // english phone ow, stress 4
	34.0079, 31.905, // english phone ow, stress 5
	34.0079, 31.905, // english phone ow, stress 6
	34.0079, 31.905, // english phone ow, stress 7
	36.0888, 34.2706, // english phone oy, stress 0
	39.3488, 37.5203, // english phone oy, stress 1
	34.0079, 31.905, // english phone oy, stress 2
	34.0079, 31.905, // english phone oy, stress 3
	34.0079, 31.905, // english phone oy, stress 4
	34.0079, 31.905, // english phone oy, stress 5
	34.0079, 31.905, // english phone oy, stress 6
	34.0079, 31.905, // english phone oy, stress 7
	11.1189, 9.17999, // english phone p, stress 0
	14.1021, 12.641, // english phone p, stress 1
	14.1021, 12.641, // english phone p, stress 2
	14.1021, 12.641, // english phone p, stress 3
	14.1021, 12.641, // english phone p, stress 4
	14.1021, 12.641, // english phone p, stress 5
	14.1021, 12.641, // english phone p, stress 6
	14.1021, 12.641, // english phone p, stress 7
	31.8001, 26.5945, // english phone r, stress 0
	27.3302, 24.1736, // english phone r, stress 1
	27.3302, 24.1736, // english phone r, stress 2
	27.3302, 24.1736, // english phone r, stress 3
	27.3302, 24.1736, // english phone r, stress 4
	27.3302, 24.1736, // english phone r, stress 5
	27.3302, 24.1736, // english phone r, stress 6
	27.3302, 24.1736, // english phone r, stress 7
	19.4508, 23.2669, // english phone s, stress 0
	19.9717, 22.8362, // english phone s, stress 1
	19.9717, 22.8362, // english phone s, stress 2
	19.9717, 22.8362, // english phone s, stress 3
	19.9717, 22.8362, // english phone s, stress 4
	19.9717, 22.8362, // english phone s, stress 5
	19.9717, 22.8362, // english phone s, stress 6
	19.9717, 22.8362, // english phone s, stress 7
	22.5265, 26.2839, // english phone sh, stress 0
	23.259, 26.8741, // english phone sh, stress 1
	23.259, 26.8741, // english phone sh, stress 2
	23.259, 26.8741, // english phone sh, stress 3
	23.259, 26.8741, // english phone sh, stress 4
	23.259, 26.8741, // english phone sh, stress 5
	23.259, 26.8741, // english phone sh, stress 6
	23.259, 26.8741, // english phone sh, stress 7
	12.8316, 11.5397, // english phone t, stress 0
	14.2186, 13.187, // english phone t, stress 1
	14.2186, 13.187, // english phone t, stress 2
	14.2186, 13.187, // english phone t, stress 3
	14.2186, 13.187, // english phone t, stress 4
	14.2186, 13.187, // english phone t, stress 5
	14.2186, 13.187, // english phone t, stress 6
	14.2186, 13.187, // english phone t, stress 7
	12.0081, 15.5302, // english phone th, stress 0
	16.4374, 18.1197, // english phone th, stress 1
	16.4374, 18.1197, // english phone th, stress 2
	16.4374, 18.1197, // english phone th, stress 3
	16.4374, 18.1197, // english phone th, stress 4
	16.4374, 18.1197, // english phone th, stress 5
	16.4374, 18.1197, // english phone th, stress 6
	16.4374, 18.1197, // english phone th, stress 7
	41.6266, 41.0921, // english phone uh, stress 0
	33.0056, 32.7405, // english phone uh, stress 1
	31.5339, 30.3827, // english phone uh, stress 2
	31.5339, 30.3827, // english phone uh, stress 3
	31.5339, 30.3827, // english phone uh, stress 4
	31.5339, 30.3827, // english phone uh, stress 5
	31.5339, 30.3827, // english phone uh, stress 6
	31.5339, 30.3827, // english phone uh, stress 7
	33.4047, 31.6861, // english phone uw, stress 0
	30.5986, 29.2531, // english phone uw, stress 1
	31.5339, 30.3827, // english phone uw, stress 2
	31.5339, 30.3827, // english phone uw, stress 3
	31.5339, 30.3827, // english phone uw, stress 4
	31.5339, 30.3827, // english phone uw, stress 5
	31.5339, 30.3827, // english phone uw, stress 6
	31.5339, 30.3827, // english phone uw, stress 7
	14.5587, 15.0977, // english phone v, stress 0
	10.8984, 12.6911, // english phone v, stress 1
	10.8984, 12.6911, // english phone v, stress 2
	10.8984, 12.6911, // english phone v, stress 3
	10.8984, 12.6911, // english phone v, stress 4
	10.8984, 12.6911, // english phone v, stress 5
	10.8984, 12.6911, // english phone v, stress 6
	10.8984, 12.6911, // english phone v, stress 7
	18.2768, 17.2246, // english phone w, stress 0
	22.3904, 22.1414, // english phone w, stress 1
	22.3904, 22.1414, // english phone w, stress 2
	22.3904, 22.1414, // english phone w, stress 3
	22.3904, 22.1414, // english phone w, stress 4
	22.3904, 22.1414, // english phone w, stress 5
	22.3904, 22.1414, // english phone w, stress 6
	22.3904, 22.1414, // english phone w, stress 7
	32.3667, 34.0666, // english phone y, stress 0
	22.3904, 22.1414, // english phone y, stress 1
	22.3904, 22.1414, // english phone y, stress 2
	22.3904, 22.1414, // english phone y, stress 3
	22.3904, 22.1414, // english phone y, stress 4
	22.3904, 22.1414, // english phone y, stress 5
	22.3904, 22.1414, // english phone y, stress 6
	22.3904, 22.1414, // english phone y, stress 7
	20.8062, 22.1499, // english phone z, stress 0
	19.9717, 22.8362, // english phone z, stress 1
	19.9717, 22.8362, // english phone z, stress 2
	19.9717, 22.8362, // english phone z, stress 3
	19.9717, 22.8362, // english phone z, stress 4
	19.9717, 22.8362, // english phone z, stress 5
	19.9717, 22.8362, // english phone z, stress 6
	19.9717, 22.8362, // english phone z, stress 7
	32.0834, 34.0837, // english phone zh, stress 0
	23.259, 26.8741, // english phone zh, stress 1
	23.259, 26.8741, // english phone zh, stress 2
	23.259, 26.8741, // english phone zh, stress 3
	23.259, 26.8741, // english phone zh, stress 4
	23.259, 26.8741, // english phone zh, stress 5
	23.259, 26.8741, // english phone zh, stress 6
	23.259, 26.8741, // english phone zh, stress 7

};

static float gafUnitScorePCMPitchPSOLAPhone[NUMLEXENGLISHPHONE * MAXSTRESSES * 2] = {
   // positive (target octave above), negative
	2.52368, 1.70945, // english phone <s>, stress 0
	2.52368, 1.70945, // english phone <s>, stress 1
	2.52368, 1.70945, // english phone <s>, stress 2
	2.52368, 1.70945, // english phone <s>, stress 3
	2.52368, 1.70945, // english phone <s>, stress 4
	2.52368, 1.70945, // english phone <s>, stress 5
	2.52368, 1.70945, // english phone <s>, stress 6
	2.52368, 1.70945, // english phone <s>, stress 7
	3.29043, 3.12747, // english phone aa, stress 0
	1.4048, 1.82457, // english phone aa, stress 1
	2.16349, 1.8288, // english phone aa, stress 2
	2.16349, 1.8288, // english phone aa, stress 3
	2.16349, 1.8288, // english phone aa, stress 4
	2.16349, 1.8288, // english phone aa, stress 5
	2.16349, 1.8288, // english phone aa, stress 6
	2.16349, 1.8288, // english phone aa, stress 7
	3.37472, 2.66768, // english phone ae, stress 0
	0.989594, 1.56863, // english phone ae, stress 1
	2.16349, 1.8288, // english phone ae, stress 2
	2.16349, 1.8288, // english phone ae, stress 3
	2.16349, 1.8288, // english phone ae, stress 4
	2.16349, 1.8288, // english phone ae, stress 5
	2.16349, 1.8288, // english phone ae, stress 6
	2.16349, 1.8288, // english phone ae, stress 7
	2.12664, 2.28831, // english phone ah, stress 0
	1.82356, 2.22876, // english phone ah, stress 1
	3.09718, 2.23993, // english phone ah, stress 2
	3.09718, 2.23993, // english phone ah, stress 3
	3.09718, 2.23993, // english phone ah, stress 4
	3.09718, 2.23993, // english phone ah, stress 5
	3.09718, 2.23993, // english phone ah, stress 6
	3.09718, 2.23993, // english phone ah, stress 7
	3.58865, 2.75462, // english phone ao, stress 0
	1.47315, 2.24999, // english phone ao, stress 1
	3.09718, 2.23993, // english phone ao, stress 2
	3.09718, 2.23993, // english phone ao, stress 3
	3.09718, 2.23993, // english phone ao, stress 4
	3.09718, 2.23993, // english phone ao, stress 5
	3.09718, 2.23993, // english phone ao, stress 6
	3.09718, 2.23993, // english phone ao, stress 7
	3.33079, 2.56852, // english phone aw, stress 0
	1.8658, 1.94009, // english phone aw, stress 1
	3.03973, 2.54188, // english phone aw, stress 2
	3.03973, 2.54188, // english phone aw, stress 3
	3.03973, 2.54188, // english phone aw, stress 4
	3.03973, 2.54188, // english phone aw, stress 5
	3.03973, 2.54188, // english phone aw, stress 6
	3.03973, 2.54188, // english phone aw, stress 7
	3.09718, 2.23993, // english phone ax, stress 0
	3.09718, 2.23993, // english phone ax, stress 1
	3.09718, 2.23993, // english phone ax, stress 2
	3.09718, 2.23993, // english phone ax, stress 3
	3.09718, 2.23993, // english phone ax, stress 4
	3.09718, 2.23993, // english phone ax, stress 5
	3.09718, 2.23993, // english phone ax, stress 6
	3.09718, 2.23993, // english phone ax, stress 7
	3.71577, 2.64198, // english phone ay, stress 0
	1.76039, 1.74678, // english phone ay, stress 1
	4.18736, 2.16298, // english phone ay, stress 2
	4.18736, 2.16298, // english phone ay, stress 3
	4.18736, 2.16298, // english phone ay, stress 4
	4.18736, 2.16298, // english phone ay, stress 5
	4.18736, 2.16298, // english phone ay, stress 6
	4.18736, 2.16298, // english phone ay, stress 7
	3.06713, 2.49939, // english phone b, stress 0
	2.05734, 1.3872, // english phone b, stress 1
	2.05734, 1.3872, // english phone b, stress 2
	2.05734, 1.3872, // english phone b, stress 3
	2.05734, 1.3872, // english phone b, stress 4
	2.05734, 1.3872, // english phone b, stress 5
	2.05734, 1.3872, // english phone b, stress 6
	2.05734, 1.3872, // english phone b, stress 7
	0.446288, 0.339997, // english phone ch, stress 0
	0.981373, 0.692816, // english phone ch, stress 1
	0.981373, 0.692816, // english phone ch, stress 2
	0.981373, 0.692816, // english phone ch, stress 3
	0.981373, 0.692816, // english phone ch, stress 4
	0.981373, 0.692816, // english phone ch, stress 5
	0.981373, 0.692816, // english phone ch, stress 6
	0.981373, 0.692816, // english phone ch, stress 7
	3.08561, 1.59179, // english phone d, stress 0
	1.79623, 0.73839, // english phone d, stress 1
	1.79623, 0.73839, // english phone d, stress 2
	1.79623, 0.73839, // english phone d, stress 3
	1.79623, 0.73839, // english phone d, stress 4
	1.79623, 0.73839, // english phone d, stress 5
	1.79623, 0.73839, // english phone d, stress 6
	1.79623, 0.73839, // english phone d, stress 7
	2.15425, 2.15489, // english phone dh, stress 0
	1.93113, 1.91599, // english phone dh, stress 1
	1.93113, 1.91599, // english phone dh, stress 2
	1.93113, 1.91599, // english phone dh, stress 3
	1.93113, 1.91599, // english phone dh, stress 4
	1.93113, 1.91599, // english phone dh, stress 5
	1.93113, 1.91599, // english phone dh, stress 6
	1.93113, 1.91599, // english phone dh, stress 7
	2.64702, 2.43923, // english phone eh, stress 0
	2.16488, 2.43118, // english phone eh, stress 1
	2.16349, 1.8288, // english phone eh, stress 2
	2.16349, 1.8288, // english phone eh, stress 3
	2.16349, 1.8288, // english phone eh, stress 4
	2.16349, 1.8288, // english phone eh, stress 5
	2.16349, 1.8288, // english phone eh, stress 6
	2.16349, 1.8288, // english phone eh, stress 7
	3.54473, 1.93565, // english phone er, stress 0
	3.72334, 2.03831, // english phone er, stress 1
	3.09718, 2.23993, // english phone er, stress 2
	3.09718, 2.23993, // english phone er, stress 3
	3.09718, 2.23993, // english phone er, stress 4
	3.09718, 2.23993, // english phone er, stress 5
	3.09718, 2.23993, // english phone er, stress 6
	3.09718, 2.23993, // english phone er, stress 7
	4.15042, 3.13516, // english phone ey, stress 0
	2.97372, 2.17275, // english phone ey, stress 1
	4.18736, 2.16298, // english phone ey, stress 2
	4.18736, 2.16298, // english phone ey, stress 3
	4.18736, 2.16298, // english phone ey, stress 4
	4.18736, 2.16298, // english phone ey, stress 5
	4.18736, 2.16298, // english phone ey, stress 6
	4.18736, 2.16298, // english phone ey, stress 7
	0.656883, 0.868828, // english phone f, stress 0
	1.4296, 1.38619, // english phone f, stress 1
	1.4296, 1.38619, // english phone f, stress 2
	1.4296, 1.38619, // english phone f, stress 3
	1.4296, 1.38619, // english phone f, stress 4
	1.4296, 1.38619, // english phone f, stress 5
	1.4296, 1.38619, // english phone f, stress 6
	1.4296, 1.38619, // english phone f, stress 7
	3.45539, 1.93997, // english phone g, stress 0
	1.49838, 0.41697, // english phone g, stress 1
	1.49838, 0.41697, // english phone g, stress 2
	1.49838, 0.41697, // english phone g, stress 3
	1.49838, 0.41697, // english phone g, stress 4
	1.49838, 0.41697, // english phone g, stress 5
	1.49838, 0.41697, // english phone g, stress 6
	1.49838, 0.41697, // english phone g, stress 7
	1.7931, 1.466, // english phone h, stress 0
	1.79194, 1.46519, // english phone h, stress 1
	1.79194, 1.46519, // english phone h, stress 2
	1.79194, 1.46519, // english phone h, stress 3
	1.79194, 1.46519, // english phone h, stress 4
	1.79194, 1.46519, // english phone h, stress 5
	1.79194, 1.46519, // english phone h, stress 6
	1.79194, 1.46519, // english phone h, stress 7
	2.90336, 1.98386, // english phone ih, stress 0
	2.88822, 1.41466, // english phone ih, stress 1
	2.16349, 1.8288, // english phone ih, stress 2
	2.16349, 1.8288, // english phone ih, stress 3
	2.16349, 1.8288, // english phone ih, stress 4
	2.16349, 1.8288, // english phone ih, stress 5
	2.16349, 1.8288, // english phone ih, stress 6
	2.16349, 1.8288, // english phone ih, stress 7
	4.56855, 2.25228, // english phone iy, stress 0
	6.94777, 2.36209, // english phone iy, stress 1
	4.18736, 2.16298, // english phone iy, stress 2
	4.18736, 2.16298, // english phone iy, stress 3
	4.18736, 2.16298, // english phone iy, stress 4
	4.18736, 2.16298, // english phone iy, stress 5
	4.18736, 2.16298, // english phone iy, stress 6
	4.18736, 2.16298, // english phone iy, stress 7
	1.47472, 1.01728, // english phone jh, stress 0
	0.981373, 0.692816, // english phone jh, stress 1
	0.981373, 0.692816, // english phone jh, stress 2
	0.981373, 0.692816, // english phone jh, stress 3
	0.981373, 0.692816, // english phone jh, stress 4
	0.981373, 0.692816, // english phone jh, stress 5
	0.981373, 0.692816, // english phone jh, stress 6
	0.981373, 0.692816, // english phone jh, stress 7
	0.916147, 0.000349104, // english phone k, stress 0
	1.49838, 0.41697, // english phone k, stress 1
	1.49838, 0.41697, // english phone k, stress 2
	1.49838, 0.41697, // english phone k, stress 3
	1.49838, 0.41697, // english phone k, stress 4
	1.49838, 0.41697, // english phone k, stress 5
	1.49838, 0.41697, // english phone k, stress 6
	1.49838, 0.41697, // english phone k, stress 7
	1.83162, 2.20261, // english phone l, stress 0
	1.89921, 2.12228, // english phone l, stress 1
	1.89921, 2.12228, // english phone l, stress 2
	1.89921, 2.12228, // english phone l, stress 3
	1.89921, 2.12228, // english phone l, stress 4
	1.89921, 2.12228, // english phone l, stress 5
	1.89921, 2.12228, // english phone l, stress 6
	1.89921, 2.12228, // english phone l, stress 7
	5.39346, 3.17854, // english phone m, stress 0
	5.1561, 2.89143, // english phone m, stress 1
	5.1561, 2.89143, // english phone m, stress 2
	5.1561, 2.89143, // english phone m, stress 3
	5.1561, 2.89143, // english phone m, stress 4
	5.1561, 2.89143, // english phone m, stress 5
	5.1561, 2.89143, // english phone m, stress 6
	5.1561, 2.89143, // english phone m, stress 7
	4.93955, 2.78374, // english phone n, stress 0
	5.1561, 2.89143, // english phone n, stress 1
	5.1561, 2.89143, // english phone n, stress 2
	5.1561, 2.89143, // english phone n, stress 3
	5.1561, 2.89143, // english phone n, stress 4
	5.1561, 2.89143, // english phone n, stress 5
	5.1561, 2.89143, // english phone n, stress 6
	5.1561, 2.89143, // english phone n, stress 7
	5.79984, 2.77664, // english phone nx, stress 0
	5.1561, 2.89143, // english phone nx, stress 1
	5.1561, 2.89143, // english phone nx, stress 2
	5.1561, 2.89143, // english phone nx, stress 3
	5.1561, 2.89143, // english phone nx, stress 4
	5.1561, 2.89143, // english phone nx, stress 5
	5.1561, 2.89143, // english phone nx, stress 6
	5.1561, 2.89143, // english phone nx, stress 7
	4.09426, 2.57742, // english phone ow, stress 0
	3.32261, 2.81466, // english phone ow, stress 1
	3.03973, 2.54188, // english phone ow, stress 2
	3.03973, 2.54188, // english phone ow, stress 3
	3.03973, 2.54188, // english phone ow, stress 4
	3.03973, 2.54188, // english phone ow, stress 5
	3.03973, 2.54188, // english phone ow, stress 6
	3.03973, 2.54188, // english phone ow, stress 7
	4.025, 2.74087, // english phone oy, stress 0
	4.15663, 2.91877, // english phone oy, stress 1
	3.03973, 2.54188, // english phone oy, stress 2
	3.03973, 2.54188, // english phone oy, stress 3
	3.03973, 2.54188, // english phone oy, stress 4
	3.03973, 2.54188, // english phone oy, stress 5
	3.03973, 2.54188, // english phone oy, stress 6
	3.03973, 2.54188, // english phone oy, stress 7
	1.15868, 0.397781, // english phone p, stress 0
	2.05734, 1.3872, // english phone p, stress 1
	2.05734, 1.3872, // english phone p, stress 2
	2.05734, 1.3872, // english phone p, stress 3
	2.05734, 1.3872, // english phone p, stress 4
	2.05734, 1.3872, // english phone p, stress 5
	2.05734, 1.3872, // english phone p, stress 6
	2.05734, 1.3872, // english phone p, stress 7
	1.95763, 2.05142, // english phone r, stress 0
	1.89921, 2.12228, // english phone r, stress 1
	1.89921, 2.12228, // english phone r, stress 2
	1.89921, 2.12228, // english phone r, stress 3
	1.89921, 2.12228, // english phone r, stress 4
	1.89921, 2.12228, // english phone r, stress 5
	1.89921, 2.12228, // english phone r, stress 6
	1.89921, 2.12228, // english phone r, stress 7
	0.139892, 0.127775, // english phone s, stress 0
	0.230324, 0.18395, // english phone s, stress 1
	0.230324, 0.18395, // english phone s, stress 2
	0.230324, 0.18395, // english phone s, stress 3
	0.230324, 0.18395, // english phone s, stress 4
	0.230324, 0.18395, // english phone s, stress 5
	0.230324, 0.18395, // english phone s, stress 6
	0.230324, 0.18395, // english phone s, stress 7
	0.0105755, 0.193394, // english phone sh, stress 0
	0.0934827, 0.238485, // english phone sh, stress 1
	0.0934827, 0.238485, // english phone sh, stress 2
	0.0934827, 0.238485, // english phone sh, stress 3
	0.0934827, 0.238485, // english phone sh, stress 4
	0.0934827, 0.238485, // english phone sh, stress 5
	0.0934827, 0.238485, // english phone sh, stress 6
	0.0934827, 0.238485, // english phone sh, stress 7
	0.945103, 0.175028, // english phone t, stress 0
	1.79623, 0.73839, // english phone t, stress 1
	1.79623, 0.73839, // english phone t, stress 2
	1.79623, 0.73839, // english phone t, stress 3
	1.79623, 0.73839, // english phone t, stress 4
	1.79623, 0.73839, // english phone t, stress 5
	1.79623, 0.73839, // english phone t, stress 6
	1.79623, 0.73839, // english phone t, stress 7
	1.1562, 1.08626, // english phone th, stress 0
	1.93113, 1.91599, // english phone th, stress 1
	1.93113, 1.91599, // english phone th, stress 2
	1.93113, 1.91599, // english phone th, stress 3
	1.93113, 1.91599, // english phone th, stress 4
	1.93113, 1.91599, // english phone th, stress 5
	1.93113, 1.91599, // english phone th, stress 6
	1.93113, 1.91599, // english phone th, stress 7
	5.00431, 3.58704, // english phone uh, stress 0
	3.57074, 1.46953, // english phone uh, stress 1
	3.09718, 2.23993, // english phone uh, stress 2
	3.09718, 2.23993, // english phone uh, stress 3
	3.09718, 2.23993, // english phone uh, stress 4
	3.09718, 2.23993, // english phone uh, stress 5
	3.09718, 2.23993, // english phone uh, stress 6
	3.09718, 2.23993, // english phone uh, stress 7
	6.74366, 2.96194, // english phone uw, stress 0
	6.19265, 2.59255, // english phone uw, stress 1
	3.09718, 2.23993, // english phone uw, stress 2
	3.09718, 2.23993, // english phone uw, stress 3
	3.09718, 2.23993, // english phone uw, stress 4
	3.09718, 2.23993, // english phone uw, stress 5
	3.09718, 2.23993, // english phone uw, stress 6
	3.09718, 2.23993, // english phone uw, stress 7
	2.29997, 1.96923, // english phone v, stress 0
	1.4296, 1.38619, // english phone v, stress 1
	1.4296, 1.38619, // english phone v, stress 2
	1.4296, 1.38619, // english phone v, stress 3
	1.4296, 1.38619, // english phone v, stress 4
	1.4296, 1.38619, // english phone v, stress 5
	1.4296, 1.38619, // english phone v, stress 6
	1.4296, 1.38619, // english phone v, stress 7
	1.12359, 2.43904, // english phone w, stress 0
	2.41755, 2.33663, // english phone w, stress 1
	2.41755, 2.33663, // english phone w, stress 2
	2.41755, 2.33663, // english phone w, stress 3
	2.41755, 2.33663, // english phone w, stress 4
	2.41755, 2.33663, // english phone w, stress 5
	2.41755, 2.33663, // english phone w, stress 6
	2.41755, 2.33663, // english phone w, stress 7
	5.5523, 2.08775, // english phone y, stress 0
	2.41755, 2.33663, // english phone y, stress 1
	2.41755, 2.33663, // english phone y, stress 2
	2.41755, 2.33663, // english phone y, stress 3
	2.41755, 2.33663, // english phone y, stress 4
	2.41755, 2.33663, // english phone y, stress 5
	2.41755, 2.33663, // english phone y, stress 6
	2.41755, 2.33663, // english phone y, stress 7
	0.374229, 0.273108, // english phone z, stress 0
	0.230324, 0.18395, // english phone z, stress 1
	0.230324, 0.18395, // english phone z, stress 2
	0.230324, 0.18395, // english phone z, stress 3
	0.230324, 0.18395, // english phone z, stress 4
	0.230324, 0.18395, // english phone z, stress 5
	0.230324, 0.18395, // english phone z, stress 6
	0.230324, 0.18395, // english phone z, stress 7
	1.0356, 0.744002, // english phone zh, stress 0
	0.0934827, 0.238485, // english phone zh, stress 1
	0.0934827, 0.238485, // english phone zh, stress 2
	0.0934827, 0.238485, // english phone zh, stress 3
	0.0934827, 0.238485, // english phone zh, stress 4
	0.0934827, 0.238485, // english phone zh, stress 5
	0.0934827, 0.238485, // english phone zh, stress 6
	0.0934827, 0.238485, // english phone zh, stress 7

};

static float gafUnitScoreEnergyPerPhone[NUMLEXENGLISHPHONE * MAXSTRESSES * 2] = {
   // positive (target 2x energy), negative
	11.4834, 11.5198, // phone <s>, stress 0
	11.4834, 11.5198, // phone <s>, stress 1
	11.4834, 11.5198, // phone <s>, stress 2
	11.4834, 11.5198, // phone <s>, stress 3
	11.4834, 11.5198, // phone <s>, stress 4
	11.4834, 11.5198, // phone <s>, stress 5
	11.4834, 11.5198, // phone <s>, stress 6
	11.4834, 11.5198, // phone <s>, stress 7
	33.8226, 34.8123, // phone aa, stress 0
	7.32, 10.4213, // phone aa, stress 1
	12.5909, 14.4396, // phone aa, stress 2
	12.5909, 14.4396, // phone aa, stress 3
	12.5909, 14.4396, // phone aa, stress 4
	12.5909, 14.4396, // phone aa, stress 5
	12.5909, 14.4396, // phone aa, stress 6
	12.5909, 14.4396, // phone aa, stress 7
	33.2349, 34.2281, // phone ae, stress 0
	8.71889, 12.0662, // phone ae, stress 1
	12.5909, 14.4396, // phone ae, stress 2
	12.5909, 14.4396, // phone ae, stress 3
	12.5909, 14.4396, // phone ae, stress 4
	12.5909, 14.4396, // phone ae, stress 5
	12.5909, 14.4396, // phone ae, stress 6
	12.5909, 14.4396, // phone ae, stress 7
	20.8961, 18.0061, // phone ah, stress 0
	9.09369, 12.9299, // phone ah, stress 1
	15.3266, 17.2291, // phone ah, stress 2
	15.3266, 17.2291, // phone ah, stress 3
	15.3266, 17.2291, // phone ah, stress 4
	15.3266, 17.2291, // phone ah, stress 5
	15.3266, 17.2291, // phone ah, stress 6
	15.3266, 17.2291, // phone ah, stress 7
	22.5567, 27.5614, // phone ao, stress 0
	9.38972, 14.7462, // phone ao, stress 1
	15.3266, 17.2291, // phone ao, stress 2
	15.3266, 17.2291, // phone ao, stress 3
	15.3266, 17.2291, // phone ao, stress 4
	15.3266, 17.2291, // phone ao, stress 5
	15.3266, 17.2291, // phone ao, stress 6
	15.3266, 17.2291, // phone ao, stress 7
	14.7022, 15.4235, // phone aw, stress 0
	11.2733, 13.2745, // phone aw, stress 1
	14.7022, 15.4235, // phone aw, stress 2
	14.7022, 15.4235, // phone aw, stress 3
	14.7022, 15.4235, // phone aw, stress 4
	14.7022, 15.4235, // phone aw, stress 5
	14.7022, 15.4235, // phone aw, stress 6
	14.7022, 15.4235, // phone aw, stress 7
	15.3266, 17.2291, // phone ax, stress 0
	15.3266, 17.2291, // phone ax, stress 1
	15.3266, 17.2291, // phone ax, stress 2
	15.3266, 17.2291, // phone ax, stress 3
	15.3266, 17.2291, // phone ax, stress 4
	15.3266, 17.2291, // phone ax, stress 5
	15.3266, 17.2291, // phone ax, stress 6
	15.3266, 17.2291, // phone ax, stress 7
	27.1841, 22.5825, // phone ay, stress 0
	7.83694, 11.4741, // phone ay, stress 1
	13.1566, 14.3929, // phone ay, stress 2
	13.1566, 14.3929, // phone ay, stress 3
	13.1566, 14.3929, // phone ay, stress 4
	13.1566, 14.3929, // phone ay, stress 5
	13.1566, 14.3929, // phone ay, stress 6
	13.1566, 14.3929, // phone ay, stress 7
	13.0561, 13.8501, // phone b, stress 0
	9.13933, 6.85731, // phone b, stress 1
	9.13933, 6.85731, // phone b, stress 2
	9.13933, 6.85731, // phone b, stress 3
	9.13933, 6.85731, // phone b, stress 4
	9.13933, 6.85731, // phone b, stress 5
	9.13933, 6.85731, // phone b, stress 6
	9.13933, 6.85731, // phone b, stress 7
	13.6238, 13.7675, // phone ch, stress 0
	14.5071, 14.8572, // phone ch, stress 1
	14.5071, 14.8572, // phone ch, stress 2
	14.5071, 14.8572, // phone ch, stress 3
	14.5071, 14.8572, // phone ch, stress 4
	14.5071, 14.8572, // phone ch, stress 5
	14.5071, 14.8572, // phone ch, stress 6
	14.5071, 14.8572, // phone ch, stress 7
	13.9624, 9.82842, // phone d, stress 0
	12.3253, 7.44906, // phone d, stress 1
	12.3253, 7.44906, // phone d, stress 2
	12.3253, 7.44906, // phone d, stress 3
	12.3253, 7.44906, // phone d, stress 4
	12.3253, 7.44906, // phone d, stress 5
	12.3253, 7.44906, // phone d, stress 6
	12.3253, 7.44906, // phone d, stress 7
	6.88336, 11.243, // phone dh, stress 0
	8.69416, 11.7693, // phone dh, stress 1
	8.69416, 11.7693, // phone dh, stress 2
	8.69416, 11.7693, // phone dh, stress 3
	8.69416, 11.7693, // phone dh, stress 4
	8.69416, 11.7693, // phone dh, stress 5
	8.69416, 11.7693, // phone dh, stress 6
	8.69416, 11.7693, // phone dh, stress 7
	27.4756, 23.8496, // phone eh, stress 0
	10.3566, 12.3409, // phone eh, stress 1
	12.5909, 14.4396, // phone eh, stress 2
	12.5909, 14.4396, // phone eh, stress 3
	12.5909, 14.4396, // phone eh, stress 4
	12.5909, 14.4396, // phone eh, stress 5
	12.5909, 14.4396, // phone eh, stress 6
	12.5909, 14.4396, // phone eh, stress 7
	17.085, 19.4128, // phone er, stress 0
	18.6415, 17.6243, // phone er, stress 1
	15.3266, 17.2291, // phone er, stress 2
	15.3266, 17.2291, // phone er, stress 3
	15.3266, 17.2291, // phone er, stress 4
	15.3266, 17.2291, // phone er, stress 5
	15.3266, 17.2291, // phone er, stress 6
	15.3266, 17.2291, // phone er, stress 7
	18.0776, 27.9943, // phone ey, stress 0
	12.1176, 14.8086, // phone ey, stress 1
	13.1566, 14.3929, // phone ey, stress 2
	13.1566, 14.3929, // phone ey, stress 3
	13.1566, 14.3929, // phone ey, stress 4
	13.1566, 14.3929, // phone ey, stress 5
	13.1566, 14.3929, // phone ey, stress 6
	13.1566, 14.3929, // phone ey, stress 7
	9.73295, 10.0132, // phone f, stress 0
	9.5795, 10.5942, // phone f, stress 1
	9.5795, 10.5942, // phone f, stress 2
	9.5795, 10.5942, // phone f, stress 3
	9.5795, 10.5942, // phone f, stress 4
	9.5795, 10.5942, // phone f, stress 5
	9.5795, 10.5942, // phone f, stress 6
	9.5795, 10.5942, // phone f, stress 7
	12.6586, 13.3731, // phone g, stress 0
	9.70373, 3.86373, // phone g, stress 1
	9.70373, 3.86373, // phone g, stress 2
	9.70373, 3.86373, // phone g, stress 3
	9.70373, 3.86373, // phone g, stress 4
	9.70373, 3.86373, // phone g, stress 5
	9.70373, 3.86373, // phone g, stress 6
	9.70373, 3.86373, // phone g, stress 7
	9.18206, 9.52179, // phone h, stress 0
	9.18266, 9.52273, // phone h, stress 1
	9.18266, 9.52273, // phone h, stress 2
	9.18266, 9.52273, // phone h, stress 3
	9.18266, 9.52273, // phone h, stress 4
	9.18266, 9.52273, // phone h, stress 5
	9.18266, 9.52273, // phone h, stress 6
	9.18266, 9.52273, // phone h, stress 7
	16.2706, 13.2187, // phone ih, stress 0
	13.0113, 13.9482, // phone ih, stress 1
	12.5909, 14.4396, // phone ih, stress 2
	12.5909, 14.4396, // phone ih, stress 3
	12.5909, 14.4396, // phone ih, stress 4
	12.5909, 14.4396, // phone ih, stress 5
	12.5909, 14.4396, // phone ih, stress 6
	12.5909, 14.4396, // phone ih, stress 7
	12.3607, 13.3569, // phone iy, stress 0
	16.4742, 16.1119, // phone iy, stress 1
	13.1566, 14.3929, // phone iy, stress 2
	13.1566, 14.3929, // phone iy, stress 3
	13.1566, 14.3929, // phone iy, stress 4
	13.1566, 14.3929, // phone iy, stress 5
	13.1566, 14.3929, // phone iy, stress 6
	13.1566, 14.3929, // phone iy, stress 7
	15.391, 15.7552, // phone jh, stress 0
	14.5071, 14.8572, // phone jh, stress 1
	14.5071, 14.8572, // phone jh, stress 2
	14.5071, 14.8572, // phone jh, stress 3
	14.5071, 14.8572, // phone jh, stress 4
	14.5071, 14.8572, // phone jh, stress 5
	14.5071, 14.8572, // phone jh, stress 6
	14.5071, 14.8572, // phone jh, stress 7
	8.65939, 1.79822, // phone k, stress 0
	9.70373, 3.86373, // phone k, stress 1
	9.70373, 3.86373, // phone k, stress 2
	9.70373, 3.86373, // phone k, stress 3
	9.70373, 3.86373, // phone k, stress 4
	9.70373, 3.86373, // phone k, stress 5
	9.70373, 3.86373, // phone k, stress 6
	9.70373, 3.86373, // phone k, stress 7
	11.4557, 13.0171, // phone l, stress 0
	11.0709, 14.1229, // phone l, stress 1
	11.0709, 14.1229, // phone l, stress 2
	11.0709, 14.1229, // phone l, stress 3
	11.0709, 14.1229, // phone l, stress 4
	11.0709, 14.1229, // phone l, stress 5
	11.0709, 14.1229, // phone l, stress 6
	11.0709, 14.1229, // phone l, stress 7
	11.9001, 10.7187, // phone m, stress 0
	8.14233, 8.32759, // phone m, stress 1
	8.14233, 8.32759, // phone m, stress 2
	8.14233, 8.32759, // phone m, stress 3
	8.14233, 8.32759, // phone m, stress 4
	8.14233, 8.32759, // phone m, stress 5
	8.14233, 8.32759, // phone m, stress 6
	8.14233, 8.32759, // phone m, stress 7
	6.5762, 6.74109, // phone n, stress 0
	8.14233, 8.32759, // phone n, stress 1
	8.14233, 8.32759, // phone n, stress 2
	8.14233, 8.32759, // phone n, stress 3
	8.14233, 8.32759, // phone n, stress 4
	8.14233, 8.32759, // phone n, stress 5
	8.14233, 8.32759, // phone n, stress 6
	8.14233, 8.32759, // phone n, stress 7
	8.30461, 10.763, // phone nx, stress 0
	8.14233, 8.32759, // phone nx, stress 1
	8.14233, 8.32759, // phone nx, stress 2
	8.14233, 8.32759, // phone nx, stress 3
	8.14233, 8.32759, // phone nx, stress 4
	8.14233, 8.32759, // phone nx, stress 5
	8.14233, 8.32759, // phone nx, stress 6
	8.14233, 8.32759, // phone nx, stress 7
	22.079, 25.0621, // phone ow, stress 0
	14.7101, 15.31, // phone ow, stress 1
	14.7022, 15.4235, // phone ow, stress 2
	14.7022, 15.4235, // phone ow, stress 3
	14.7022, 15.4235, // phone ow, stress 4
	14.7022, 15.4235, // phone ow, stress 5
	14.7022, 15.4235, // phone ow, stress 6
	14.7022, 15.4235, // phone ow, stress 7
	14.7022, 15.4235, // phone oy, stress 0
	17.9768, 16.0965, // phone oy, stress 1
	14.7022, 15.4235, // phone oy, stress 2
	14.7022, 15.4235, // phone oy, stress 3
	14.7022, 15.4235, // phone oy, stress 4
	14.7022, 15.4235, // phone oy, stress 5
	14.7022, 15.4235, // phone oy, stress 6
	14.7022, 15.4235, // phone oy, stress 7
	7.1949, 1.8108, // phone p, stress 0
	9.13933, 6.85731, // phone p, stress 1
	9.13933, 6.85731, // phone p, stress 2
	9.13933, 6.85731, // phone p, stress 3
	9.13933, 6.85731, // phone p, stress 4
	9.13933, 6.85731, // phone p, stress 5
	9.13933, 6.85731, // phone p, stress 6
	9.13933, 6.85731, // phone p, stress 7
	11.0114, 15.0775, // phone r, stress 0
	11.0709, 14.1229, // phone r, stress 1
	11.0709, 14.1229, // phone r, stress 2
	11.0709, 14.1229, // phone r, stress 3
	11.0709, 14.1229, // phone r, stress 4
	11.0709, 14.1229, // phone r, stress 5
	11.0709, 14.1229, // phone r, stress 6
	11.0709, 14.1229, // phone r, stress 7
	7.70716, 6.28034, // phone s, stress 0
	8.53191, 7.81895, // phone s, stress 1
	8.53191, 7.81895, // phone s, stress 2
	8.53191, 7.81895, // phone s, stress 3
	8.53191, 7.81895, // phone s, stress 4
	8.53191, 7.81895, // phone s, stress 5
	8.53191, 7.81895, // phone s, stress 6
	8.53191, 7.81895, // phone s, stress 7
	10.096, 9.47851, // phone sh, stress 0
	11.7438, 10.9198, // phone sh, stress 1
	11.7438, 10.9198, // phone sh, stress 2
	11.7438, 10.9198, // phone sh, stress 3
	11.7438, 10.9198, // phone sh, stress 4
	11.7438, 10.9198, // phone sh, stress 5
	11.7438, 10.9198, // phone sh, stress 6
	11.7438, 10.9198, // phone sh, stress 7
	11.2871, 6.13074, // phone t, stress 0
	12.3253, 7.44906, // phone t, stress 1
	12.3253, 7.44906, // phone t, stress 2
	12.3253, 7.44906, // phone t, stress 3
	12.3253, 7.44906, // phone t, stress 4
	12.3253, 7.44906, // phone t, stress 5
	12.3253, 7.44906, // phone t, stress 6
	12.3253, 7.44906, // phone t, stress 7
	15.2654, 13.62, // phone th, stress 0
	8.69416, 11.7693, // phone th, stress 1
	8.69416, 11.7693, // phone th, stress 2
	8.69416, 11.7693, // phone th, stress 3
	8.69416, 11.7693, // phone th, stress 4
	8.69416, 11.7693, // phone th, stress 5
	8.69416, 11.7693, // phone th, stress 6
	8.69416, 11.7693, // phone th, stress 7
	10.6281, 13.8717, // phone uh, stress 0
	11.7071, 15.2856, // phone uh, stress 1
	15.3266, 17.2291, // phone uh, stress 2
	15.3266, 17.2291, // phone uh, stress 3
	15.3266, 17.2291, // phone uh, stress 4
	15.3266, 17.2291, // phone uh, stress 5
	15.3266, 17.2291, // phone uh, stress 6
	15.3266, 17.2291, // phone uh, stress 7
	31.9078, 29.1998, // phone uw, stress 0
	16.1294, 15.1085, // phone uw, stress 1
	15.3266, 17.2291, // phone uw, stress 2
	15.3266, 17.2291, // phone uw, stress 3
	15.3266, 17.2291, // phone uw, stress 4
	15.3266, 17.2291, // phone uw, stress 5
	15.3266, 17.2291, // phone uw, stress 6
	15.3266, 17.2291, // phone uw, stress 7
	9.45188, 10.8351, // phone v, stress 0
	9.5795, 10.5942, // phone v, stress 1
	9.5795, 10.5942, // phone v, stress 2
	9.5795, 10.5942, // phone v, stress 3
	9.5795, 10.5942, // phone v, stress 4
	9.5795, 10.5942, // phone v, stress 5
	9.5795, 10.5942, // phone v, stress 6
	9.5795, 10.5942, // phone v, stress 7
	6.77098, 7.70997, // phone w, stress 0
	9.73505, 9.89495, // phone w, stress 1
	9.73505, 9.89495, // phone w, stress 2
	9.73505, 9.89495, // phone w, stress 3
	9.73505, 9.89495, // phone w, stress 4
	9.73505, 9.89495, // phone w, stress 5
	9.73505, 9.89495, // phone w, stress 6
	9.73505, 9.89495, // phone w, stress 7
	14.332, 13.587, // phone y, stress 0
	9.73505, 9.89495, // phone y, stress 1
	9.73505, 9.89495, // phone y, stress 2
	9.73505, 9.89495, // phone y, stress 3
	9.73505, 9.89495, // phone y, stress 4
	9.73505, 9.89495, // phone y, stress 5
	9.73505, 9.89495, // phone y, stress 6
	9.73505, 9.89495, // phone y, stress 7
	9.63124, 10.3357, // phone z, stress 0
	8.53191, 7.81895, // phone z, stress 1
	8.53191, 7.81895, // phone z, stress 2
	8.53191, 7.81895, // phone z, stress 3
	8.53191, 7.81895, // phone z, stress 4
	8.53191, 7.81895, // phone z, stress 5
	8.53191, 7.81895, // phone z, stress 6
	8.53191, 7.81895, // phone z, stress 7
	25.2179, 26.0868, // phone zh, stress 0
	11.7438, 10.9198, // phone zh, stress 1
	11.7438, 10.9198, // phone zh, stress 2
	11.7438, 10.9198, // phone zh, stress 3
	11.7438, 10.9198, // phone zh, stress 4
	11.7438, 10.9198, // phone zh, stress 5
	11.7438, 10.9198, // phone zh, stress 6
	11.7438, 10.9198, // phone zh, stress 7

};

static float gafUnitScoreDurationPerPhone[NUMLEXENGLISHPHONE * MAXSTRESSES * 2] = {
   // positive (target 2x duration), negative
	12.9697, 21.2308, // phone <s>, stress 0
	12.9697, 21.2308, // phone <s>, stress 1
	12.9697, 21.2308, // phone <s>, stress 2
	12.9697, 21.2308, // phone <s>, stress 3
	12.9697, 21.2308, // phone <s>, stress 4
	12.9697, 21.2308, // phone <s>, stress 5
	12.9697, 21.2308, // phone <s>, stress 6
	12.9697, 21.2308, // phone <s>, stress 7
	64.4704, 87.3851, // phone aa, stress 0
	14.8486, 18.9067, // phone aa, stress 1
	13.1203, 22.7355, // phone aa, stress 2
	13.1203, 22.7355, // phone aa, stress 3
	13.1203, 22.7355, // phone aa, stress 4
	13.1203, 22.7355, // phone aa, stress 5
	13.1203, 22.7355, // phone aa, stress 6
	13.1203, 22.7355, // phone aa, stress 7
	33.8478, 69.1245, // phone ae, stress 0
	11.8677, 20.492, // phone ae, stress 1
	13.1203, 22.7355, // phone ae, stress 2
	13.1203, 22.7355, // phone ae, stress 3
	13.1203, 22.7355, // phone ae, stress 4
	13.1203, 22.7355, // phone ae, stress 5
	13.1203, 22.7355, // phone ae, stress 6
	13.1203, 22.7355, // phone ae, stress 7
	14.943, 25.6847, // phone ah, stress 0
	11.4995, 18.8181, // phone ah, stress 1
	15.8564, 25.176, // phone ah, stress 2
	15.8564, 25.176, // phone ah, stress 3
	15.8564, 25.176, // phone ah, stress 4
	15.8564, 25.176, // phone ah, stress 5
	15.8564, 25.176, // phone ah, stress 6
	15.8564, 25.176, // phone ah, stress 7
	34.7377, 47.5992, // phone ao, stress 0
	8.79712, 26.745, // phone ao, stress 1
	15.8564, 25.176, // phone ao, stress 2
	15.8564, 25.176, // phone ao, stress 3
	15.8564, 25.176, // phone ao, stress 4
	15.8564, 25.176, // phone ao, stress 5
	15.8564, 25.176, // phone ao, stress 6
	15.8564, 25.176, // phone ao, stress 7
	29.0187, 31.0873, // phone aw, stress 0
	26.0498, 26.7702, // phone aw, stress 1
	29.0187, 31.0873, // phone aw, stress 2
	29.0187, 31.0873, // phone aw, stress 3
	29.0187, 31.0873, // phone aw, stress 4
	29.0187, 31.0873, // phone aw, stress 5
	29.0187, 31.0873, // phone aw, stress 6
	29.0187, 31.0873, // phone aw, stress 7
	15.8564, 25.176, // phone ax, stress 0
	15.8564, 25.176, // phone ax, stress 1
	15.8564, 25.176, // phone ax, stress 2
	15.8564, 25.176, // phone ax, stress 3
	15.8564, 25.176, // phone ax, stress 4
	15.8564, 25.176, // phone ax, stress 5
	15.8564, 25.176, // phone ax, stress 6
	15.8564, 25.176, // phone ax, stress 7
	48.7869, 48.3164, // phone ay, stress 0
	19.2778, 25.0975, // phone ay, stress 1
	18.4246, 25.5911, // phone ay, stress 2
	18.4246, 25.5911, // phone ay, stress 3
	18.4246, 25.5911, // phone ay, stress 4
	18.4246, 25.5911, // phone ay, stress 5
	18.4246, 25.5911, // phone ay, stress 6
	18.4246, 25.5911, // phone ay, stress 7
	14.5327, 12.4941, // phone b, stress 0
	15.2055, 17.553, // phone b, stress 1
	15.2055, 17.553, // phone b, stress 2
	15.2055, 17.553, // phone b, stress 3
	15.2055, 17.553, // phone b, stress 4
	15.2055, 17.553, // phone b, stress 5
	15.2055, 17.553, // phone b, stress 6
	15.2055, 17.553, // phone b, stress 7
	20.4272, 23.1045, // phone ch, stress 0
	23.3647, 29.5108, // phone ch, stress 1
	23.3647, 29.5108, // phone ch, stress 2
	23.3647, 29.5108, // phone ch, stress 3
	23.3647, 29.5108, // phone ch, stress 4
	23.3647, 29.5108, // phone ch, stress 5
	23.3647, 29.5108, // phone ch, stress 6
	23.3647, 29.5108, // phone ch, stress 7
	10.5903, 18.7781, // phone d, stress 0
	8.71087, 28.4729, // phone d, stress 1
	8.71087, 28.4729, // phone d, stress 2
	8.71087, 28.4729, // phone d, stress 3
	8.71087, 28.4729, // phone d, stress 4
	8.71087, 28.4729, // phone d, stress 5
	8.71087, 28.4729, // phone d, stress 6
	8.71087, 28.4729, // phone d, stress 7
	6.71473, 18.4378, // phone dh, stress 0
	8.62653, 21.0566, // phone dh, stress 1
	8.62653, 21.0566, // phone dh, stress 2
	8.62653, 21.0566, // phone dh, stress 3
	8.62653, 21.0566, // phone dh, stress 4
	8.62653, 21.0566, // phone dh, stress 5
	8.62653, 21.0566, // phone dh, stress 6
	8.62653, 21.0566, // phone dh, stress 7
	24.6813, 39.9859, // phone eh, stress 0
	15.0577, 17.3987, // phone eh, stress 1
	13.1203, 22.7355, // phone eh, stress 2
	13.1203, 22.7355, // phone eh, stress 3
	13.1203, 22.7355, // phone eh, stress 4
	13.1203, 22.7355, // phone eh, stress 5
	13.1203, 22.7355, // phone eh, stress 6
	13.1203, 22.7355, // phone eh, stress 7
	23.5797, 29.2806, // phone er, stress 0
	27.9316, 28.0741, // phone er, stress 1
	15.8564, 25.176, // phone er, stress 2
	15.8564, 25.176, // phone er, stress 3
	15.8564, 25.176, // phone er, stress 4
	15.8564, 25.176, // phone er, stress 5
	15.8564, 25.176, // phone er, stress 6
	15.8564, 25.176, // phone er, stress 7
	43.3659, 62.3357, // phone ey, stress 0
	17.7861, 25.8136, // phone ey, stress 1
	18.4246, 25.5911, // phone ey, stress 2
	18.4246, 25.5911, // phone ey, stress 3
	18.4246, 25.5911, // phone ey, stress 4
	18.4246, 25.5911, // phone ey, stress 5
	18.4246, 25.5911, // phone ey, stress 6
	18.4246, 25.5911, // phone ey, stress 7
	10.7912, 17.7448, // phone f, stress 0
	13.0626, 18.3888, // phone f, stress 1
	13.0626, 18.3888, // phone f, stress 2
	13.0626, 18.3888, // phone f, stress 3
	13.0626, 18.3888, // phone f, stress 4
	13.0626, 18.3888, // phone f, stress 5
	13.0626, 18.3888, // phone f, stress 6
	13.0626, 18.3888, // phone f, stress 7
	21.7178, 27.3711, // phone g, stress 0
	12.9355, 20.5983, // phone g, stress 1
	12.9355, 20.5983, // phone g, stress 2
	12.9355, 20.5983, // phone g, stress 3
	12.9355, 20.5983, // phone g, stress 4
	12.9355, 20.5983, // phone g, stress 5
	12.9355, 20.5983, // phone g, stress 6
	12.9355, 20.5983, // phone g, stress 7
	9.7089, 17.1077, // phone h, stress 0
	9.71722, 17.1166, // phone h, stress 1
	9.71722, 17.1166, // phone h, stress 2
	9.71722, 17.1166, // phone h, stress 3
	9.71722, 17.1166, // phone h, stress 4
	9.71722, 17.1166, // phone h, stress 5
	9.71722, 17.1166, // phone h, stress 6
	9.71722, 17.1166, // phone h, stress 7
	11.0388, 17.7445, // phone ih, stress 0
	11.1558, 17.4264, // phone ih, stress 1
	13.1203, 22.7355, // phone ih, stress 2
	13.1203, 22.7355, // phone ih, stress 3
	13.1203, 22.7355, // phone ih, stress 4
	13.1203, 22.7355, // phone ih, stress 5
	13.1203, 22.7355, // phone ih, stress 6
	13.1203, 22.7355, // phone ih, stress 7
	15.4317, 26.9595, // phone iy, stress 0
	19.9825, 22.998, // phone iy, stress 1
	18.4246, 25.5911, // phone iy, stress 2
	18.4246, 25.5911, // phone iy, stress 3
	18.4246, 25.5911, // phone iy, stress 4
	18.4246, 25.5911, // phone iy, stress 5
	18.4246, 25.5911, // phone iy, stress 6
	18.4246, 25.5911, // phone iy, stress 7
	26.3213, 33.5093, // phone jh, stress 0
	23.3647, 29.5108, // phone jh, stress 1
	23.3647, 29.5108, // phone jh, stress 2
	23.3647, 29.5108, // phone jh, stress 3
	23.3647, 29.5108, // phone jh, stress 4
	23.3647, 29.5108, // phone jh, stress 5
	23.3647, 29.5108, // phone jh, stress 6
	23.3647, 29.5108, // phone jh, stress 7
	10.1228, 19.2001, // phone k, stress 0
	12.9355, 20.5983, // phone k, stress 1
	12.9355, 20.5983, // phone k, stress 2
	12.9355, 20.5983, // phone k, stress 3
	12.9355, 20.5983, // phone k, stress 4
	12.9355, 20.5983, // phone k, stress 5
	12.9355, 20.5983, // phone k, stress 6
	12.9355, 20.5983, // phone k, stress 7
	9.79373, 12.9984, // phone l, stress 0
	9.8501, 12.7095, // phone l, stress 1
	9.8501, 12.7095, // phone l, stress 2
	9.8501, 12.7095, // phone l, stress 3
	9.8501, 12.7095, // phone l, stress 4
	9.8501, 12.7095, // phone l, stress 5
	9.8501, 12.7095, // phone l, stress 6
	9.8501, 12.7095, // phone l, stress 7
	12.8754, 13.3006, // phone m, stress 0
	9.38918, 11.7066, // phone m, stress 1
	9.38918, 11.7066, // phone m, stress 2
	9.38918, 11.7066, // phone m, stress 3
	9.38918, 11.7066, // phone m, stress 4
	9.38918, 11.7066, // phone m, stress 5
	9.38918, 11.7066, // phone m, stress 6
	9.38918, 11.7066, // phone m, stress 7
	7.74478, 10.7302, // phone n, stress 0
	9.38918, 11.7066, // phone n, stress 1
	9.38918, 11.7066, // phone n, stress 2
	9.38918, 11.7066, // phone n, stress 3
	9.38918, 11.7066, // phone n, stress 4
	9.38918, 11.7066, // phone n, stress 5
	9.38918, 11.7066, // phone n, stress 6
	9.38918, 11.7066, // phone n, stress 7
	11.3672, 10.9297, // phone nx, stress 0
	9.38918, 11.7066, // phone nx, stress 1
	9.38918, 11.7066, // phone nx, stress 2
	9.38918, 11.7066, // phone nx, stress 3
	9.38918, 11.7066, // phone nx, stress 4
	9.38918, 11.7066, // phone nx, stress 5
	9.38918, 11.7066, // phone nx, stress 6
	9.38918, 11.7066, // phone nx, stress 7
	49.5944, 55.9346, // phone ow, stress 0
	25.2723, 27.2382, // phone ow, stress 1
	29.0187, 31.0873, // phone ow, stress 2
	29.0187, 31.0873, // phone ow, stress 3
	29.0187, 31.0873, // phone ow, stress 4
	29.0187, 31.0873, // phone ow, stress 5
	29.0187, 31.0873, // phone ow, stress 6
	29.0187, 31.0873, // phone ow, stress 7
	29.0187, 31.0873, // phone oy, stress 0
	43.1771, 39.5986, // phone oy, stress 1
	29.0187, 31.0873, // phone oy, stress 2
	29.0187, 31.0873, // phone oy, stress 3
	29.0187, 31.0873, // phone oy, stress 4
	29.0187, 31.0873, // phone oy, stress 5
	29.0187, 31.0873, // phone oy, stress 6
	29.0187, 31.0873, // phone oy, stress 7
	16.3391, 22.8738, // phone p, stress 0
	15.2055, 17.553, // phone p, stress 1
	15.2055, 17.553, // phone p, stress 2
	15.2055, 17.553, // phone p, stress 3
	15.2055, 17.553, // phone p, stress 4
	15.2055, 17.553, // phone p, stress 5
	15.2055, 17.553, // phone p, stress 6
	15.2055, 17.553, // phone p, stress 7
	9.99327, 12.4614, // phone r, stress 0
	9.8501, 12.7095, // phone r, stress 1
	9.8501, 12.7095, // phone r, stress 2
	9.8501, 12.7095, // phone r, stress 3
	9.8501, 12.7095, // phone r, stress 4
	9.8501, 12.7095, // phone r, stress 5
	9.8501, 12.7095, // phone r, stress 6
	9.8501, 12.7095, // phone r, stress 7
	6.81054, 12.7322, // phone s, stress 0
	11.4519, 21.5554, // phone s, stress 1
	11.4519, 21.5554, // phone s, stress 2
	11.4519, 21.5554, // phone s, stress 3
	11.4519, 21.5554, // phone s, stress 4
	11.4519, 21.5554, // phone s, stress 5
	11.4519, 21.5554, // phone s, stress 6
	11.4519, 21.5554, // phone s, stress 7
	13.5904, 14.969, // phone sh, stress 0
	16.2296, 18.6074, // phone sh, stress 1
	16.2296, 18.6074, // phone sh, stress 2
	16.2296, 18.6074, // phone sh, stress 3
	16.2296, 18.6074, // phone sh, stress 4
	16.2296, 18.6074, // phone sh, stress 5
	16.2296, 18.6074, // phone sh, stress 6
	16.2296, 18.6074, // phone sh, stress 7
	7.27792, 34.0294, // phone t, stress 0
	8.71087, 28.4729, // phone t, stress 1
	8.71087, 28.4729, // phone t, stress 2
	8.71087, 28.4729, // phone t, stress 3
	8.71087, 28.4729, // phone t, stress 4
	8.71087, 28.4729, // phone t, stress 5
	8.71087, 28.4729, // phone t, stress 6
	8.71087, 28.4729, // phone t, stress 7
	18.8344, 30.9987, // phone th, stress 0
	8.62653, 21.0566, // phone th, stress 1
	8.62653, 21.0566, // phone th, stress 2
	8.62653, 21.0566, // phone th, stress 3
	8.62653, 21.0566, // phone th, stress 4
	8.62653, 21.0566, // phone th, stress 5
	8.62653, 21.0566, // phone th, stress 6
	8.62653, 21.0566, // phone th, stress 7
	12.1972, 25.176, // phone uh, stress 0
	13.9411, 20.3768, // phone uh, stress 1
	15.8564, 25.176, // phone uh, stress 2
	15.8564, 25.176, // phone uh, stress 3
	15.8564, 25.176, // phone uh, stress 4
	15.8564, 25.176, // phone uh, stress 5
	15.8564, 25.176, // phone uh, stress 6
	15.8564, 25.176, // phone uh, stress 7
	34.5455, 60.073, // phone uw, stress 0
	15.3805, 26.8719, // phone uw, stress 1
	15.8564, 25.176, // phone uw, stress 2
	15.8564, 25.176, // phone uw, stress 3
	15.8564, 25.176, // phone uw, stress 4
	15.8564, 25.176, // phone uw, stress 5
	15.8564, 25.176, // phone uw, stress 6
	15.8564, 25.176, // phone uw, stress 7
	15.0922, 19.242, // phone v, stress 0
	13.0626, 18.3888, // phone v, stress 1
	13.0626, 18.3888, // phone v, stress 2
	13.0626, 18.3888, // phone v, stress 3
	13.0626, 18.3888, // phone v, stress 4
	13.0626, 18.3888, // phone v, stress 5
	13.0626, 18.3888, // phone v, stress 6
	13.0626, 18.3888, // phone v, stress 7
	7.07436, 10.6158, // phone w, stress 0
	9.24944, 12.3161, // phone w, stress 1
	9.24944, 12.3161, // phone w, stress 2
	9.24944, 12.3161, // phone w, stress 3
	9.24944, 12.3161, // phone w, stress 4
	9.24944, 12.3161, // phone w, stress 5
	9.24944, 12.3161, // phone w, stress 6
	9.24944, 12.3161, // phone w, stress 7
	12.2364, 17.4016, // phone y, stress 0
	9.24944, 12.3161, // phone y, stress 1
	9.24944, 12.3161, // phone y, stress 2
	9.24944, 12.3161, // phone y, stress 3
	9.24944, 12.3161, // phone y, stress 4
	9.24944, 12.3161, // phone y, stress 5
	9.24944, 12.3161, // phone y, stress 6
	9.24944, 12.3161, // phone y, stress 7
	16.0242, 31.6037, // phone z, stress 0
	11.4519, 21.5554, // phone z, stress 1
	11.4519, 21.5554, // phone z, stress 2
	11.4519, 21.5554, // phone z, stress 3
	11.4519, 21.5554, // phone z, stress 4
	11.4519, 21.5554, // phone z, stress 5
	11.4519, 21.5554, // phone z, stress 6
	11.4519, 21.5554, // phone z, stress 7
	54.0407, 61.1679, // phone zh, stress 0
	16.2296, 18.6074, // phone zh, stress 1
	16.2296, 18.6074, // phone zh, stress 2
	16.2296, 18.6074, // phone zh, stress 3
	16.2296, 18.6074, // phone zh, stress 4
	16.2296, 18.6074, // phone zh, stress 5
	16.2296, 18.6074, // phone zh, stress 6
	16.2296, 18.6074, // phone zh, stress 7

};

static float gafUnitScoreMismatchedWordSylPos[WORDPOS_MAX] = {
	0, // mismatch 0
	14.9676, // mismatch 1
	15.9565, // mismatch 2
	36.0232, // mismatch 3
	15.6645, // mismatch 4
	18.8734, // mismatch 5
	30.7782, // mismatch 6
	26.9639, // mismatch 7
	12.3808, // mismatch 8
	25.4282, // mismatch 9
	15.3592, // mismatch 10
	28.1725, // mismatch 11
	16.4232, // mismatch 12
	15.9692, // mismatch 13
	20.0615, // mismatch 14
	19.8408, // mismatch 15
};

// mid phoneme unit score
static float gafUnitScoreNonContiguousMid[PIS_PHONEGROUPNUM] = {
	24.7349, // <s>
	18.7265, // ch, jh
	19.7491, // w, y
	25.7091, // l, r
	21.3255, // m, n, ng
	16.5314, // sh, zh
	16.8455, // s, z
	23.8836, // v, f
	24.9329, // th, dh
	31.1981, // hh
	28.3317, // k, g
	29.8266, // t, d
	25.6917, // p, b
	20.0819, // ow0, oy0, aw0
	26.2385, // iy0, ey0, ay0
	24.7021, // ah0, er0, uw0, ao0, uh0
	26.6937, // ih0, eh0, aa0, ae0

};

static float gafUnitScoreNonContiguous[PIS_PHONEGROUPNUM*PIS_PHONEGROUPNUM] = {
   // Right-unvoiced-unplosive, Right-unvoiced-plosive, Right-voiced-unplosive, Right-voice-plosive
	22.7093, 22.7093, 22.7093, 22.7093, 22.7093, 22.7093, 22.7093, 22.7093, 22.7093, 22.7093, 22.7093, 22.7093, 22.7093, 22.7093, 22.7093, 22.7093, 22.7093,  // <s>
	22.7093, 18.6312, 24.2272, 26.7321, 22.2572, 20.4521, 15.7962, 17.2687, 18.1303, 20.669, 15.8291, 15.7902, 15.7367, 18.5251, 19.236, 21.7755, 21.2073,  // ch, jh
	22.7093, 30.435, 16.6188, 15.9735, 16.6188, 30.435, 30.435, 30.435, 30.435, 30.435, 20.4769, 20.4769, 20.4769, 13.9416, 20.0461, 20.9388, 20.7596,  // w, y
	22.7093, 18.5369, 16.309, 16.3726, 17.5234, 41.3186, 36.6767, 21.9045, 19.8122, 22.2713, 18.9069, 23.1471, 16.849, 17.4179, 22.1687, 18.7442, 20.5882,  // l, r
	22.7093, 31.8705, 17.0564, 17.626, 13.6617, 48.7291, 40.3118, 23.7884, 18.1965, 24.6656, 19.3449, 21.0825, 14.1085, 21.3493, 22.361, 21.1025, 23.1926,  // m, n, ng
	22.7093, 20.4309, 24.929, 30.9625, 21.52, 19.7534, 19.22, 17.5713, 19.1415, 18.8025, 16.4994, 11.7703, 13.8529, 23.5106, 25.5616, 24.8466, 31.468,  // sh, zh
	22.7093, 12.5783, 22.4826, 29.5752, 24.6684, 22.4624, 13.6251, 18.1653, 20.0405, 24.6212, 13.2844, 13.217, 9.50113, 37.7099, 34.1685, 36.289, 34.5166,  // s, z
	22.7093, 21.122, 21.7694, 25.4205, 16.0847, 23.7762, 21.855, 19.3171, 19.7291, 26.4164, 16.4883, 20.3291, 10.3407, 15.1343, 18.6074, 18.4024, 20.4789,  // v, f
	22.7093, 19.7534, 24.9644, 17.8548, 25.5111, 18.2347, 18.2695, 18.6466, 23.0195, 27.4598, 13.9451, 18.0893, 15.1846, 18.0486, 23.6391, 24.2184, 20.4846,  // th, dh
	22.7093, 19.7534, 19.7661, 24.1235, 20.0295, 19.7534, 19.7534, 19.7534, 19.7534, 19.7534, 13.6123, 13.6123, 13.6123, 17.3228, 21.8321, 16.6659, 22.0468,  // hh
	22.7093, 16.8666, 21.6533, 22.8731, 22.7102, 15.9006, 18.6946, 21.1993, 18.9849, 19.7354, 18.1707, 18.4859, 15.2528, 20.4404, 23.7507, 23.8828, 23.1645,  // k, g
	22.7093, 20.9096, 20.6199, 23.4134, 18.4317, 20.8327, 17.8294, 22.9174, 22.0658, 26.0343, 21.4167, 22.4475, 14.9084, 21.6066, 24.0678, 23.5925, 26.0224,  // t, d
	22.7093, 20.5654, 23.1797, 22.5468, 12.9193, 18.0206, 18.1123, 20.4331, 20.8669, 25.206, 25.8041, 24.1927, 16.2944, 14.8816, 21.1182, 16.7743, 19.832,  // p, b
	22.7093, 16.9115, 13.0999, 15.734, 19.914, 42.8884, 31.4986, 18.3824, 21.4658, 19.9265, 16.3784, 23.4025, 14.443, 21.4195, 19.5178, 17.8665, 20.8027,  // ow0, oy0, aw0
	22.7093, 14.3509, 15.3786, 20.6642, 20.8462, 59.3358, 36.0862, 21.7588, 20.8471, 21.4195, 19.1467, 22.5042, 16.2173, 25.3455, 20.0678, 23.5699, 24.1184,  // iy0, ey0, ay0
	22.7093, 16.707, 15.3882, 19.8033, 20.7811, 40.3489, 33.9662, 21.6282, 18.599, 22.0239, 17.626, 22.894, 14.2957, 19.619, 19.7414, 20.8216, 23.1462,  // ah0, er0, uw0, ao0, uh0
	22.7093, 17.771, 14.8344, 18.7398, 22.885, 49.7438, 34.2278, 22.6786, 21.2296, 22.3425, 19.0377, 26.1029, 17.7612, 16.6858, 16.3315, 20.7959, 26.4701,  // ih0, eh0, aa0, ae0

};

static float gafUnitScoreLRMismatch[PIS_PHONEGROUPNUM*5*2*6] = {
   // Exact Match, Same triphone, Bad-stress, Right-group but bad phoneme, Bad-group, Bad-megagroup
	0.0391008, 21.695, 4.33636, 5.71035, 7.48005, 9.68083,  // Center context = <s>, left context mega group = 0
	0.0391008, 21.695, 11.2241, 15.2913, 13.2345, 16.7147,  // Center context = <s>, right context mega group = 0
	0.0391008, 21.695, 4.33636, 5.71035, 7.48005, 9.68083,  // Center context = <s>, left context mega group = 1
	0.0391008, 21.695, 11.2241, 15.2913, 13.2345, 16.7147,  // Center context = <s>, right context mega group = 1
	0.0391008, 21.695, 4.33636, 5.71035, 7.48005, 9.68083,  // Center context = <s>, left context mega group = 2
	0.0391008, 21.695, 11.2241, 15.2913, 13.2345, 16.7147,  // Center context = <s>, right context mega group = 2
	0.0391008, 21.695, 4.33636, 5.71035, 7.48005, 9.68083,  // Center context = <s>, left context mega group = 3
	0.0391008, 21.695, 11.2241, 15.2913, 13.2345, 16.7147,  // Center context = <s>, right context mega group = 3
	0.0391008, 21.695, 4.33636, 5.71035, 7.48005, 9.68083,  // Center context = <s>, left context mega group = 4
	0.0391008, 21.695, 11.2241, 15.2913, 13.2345, 16.7147,  // Center context = <s>, right context mega group = 4
	0.0256533, 18.6376, 4.33642, 5.71041, 7.48011, 8.75816,  // Center context = ch, jh, left context mega group = 0
	0.149153, 18.1367, 11.2241, 15.2914, 13.2345, 10.8924,  // Center context = ch, jh, right context mega group = 0
	0.0180558, 11.2716, 4.33654, 24.5681, 14.542, 15.8112,  // Center context = ch, jh, left context mega group = 1
	0.0315006, 9.10798, 11.2242, 23.4001, 33.9301, 22.1956,  // Center context = ch, jh, right context mega group = 1
	0.0224534, 15.6256, 4.33656, 28.0143, 31.7807, 16.8968,  // Center context = ch, jh, left context mega group = 2
	0.0655587, 8.7198, 11.2243, 33.6026, 32.2428, 24.847,  // Center context = ch, jh, right context mega group = 2
	0.0351081, 15.0097, 4.33662, 23.4181, 14.8411, 12.546,  // Center context = ch, jh, left context mega group = 3
	0.0779513, 6.1187, 11.2243, 42.3869, 35.6448, 32.8345,  // Center context = ch, jh, right context mega group = 3
	0.106415, 16.3589, 14.5284, 12.5872, 7.92904, 15.3892,  // Center context = ch, jh, left context mega group = 4
	0.0483509, 16.6885, 18.0108, 13.8057, 11.1751, 29.0623,  // Center context = ch, jh, right context mega group = 4
	0.0208851, 17.9265, 4.3364, 5.71038, 7.48008, 3.91342,  // Center context = w, y, left context mega group = 0
	0.0314285, 20.7361, 11.2241, 15.2913, 13.2345, 7.61443,  // Center context = w, y, right context mega group = 0
	0.0191495, 16.0814, 4.33645, 9.68553, 12.4364, 11.8965,  // Center context = w, y, left context mega group = 1
	0.00339857, 21.6345, 11.2241, 6.21093, 13.042, 8.58533,  // Center context = w, y, right context mega group = 1
	0.0199846, 16.128, 4.33649, 7.6776, 12.7139, 11.7573,  // Center context = w, y, left context mega group = 2
	0.011751, 17.8534, 11.2242, 7.32534, 14.5367, 13.9855,  // Center context = w, y, right context mega group = 2
	0.0264656, 13.8655, 4.33645, 11.3933, 12.2126, 11.3126,  // Center context = w, y, left context mega group = 3
	0.0255501, 12.6257, 11.2241, 20.7623, 19.241, 45.5078,  // Center context = w, y, right context mega group = 3
	0.0856672, 14.4941, 8.36883, 11.1612, 10.2872, 11.1331,  // Center context = w, y, left context mega group = 4
	0.0357859, 17.4799, 14.5321, 11.005, 6.8276, 69.7238,  // Center context = w, y, right context mega group = 4
	0.0154706, 16.1519, 4.3364, 5.71038, 7.48008, 16.282,  // Center context = l, r, left context mega group = 0
	0.0538864, 20.5367, 11.2241, 15.2914, 13.2345, 9.77033,  // Center context = l, r, right context mega group = 0
	0.000159649, 14.6011, 4.33645, 13.9915, 13.5861, 17.0904,  // Center context = l, r, left context mega group = 1
	0.00540614, 17.3707, 11.2241, 8.202, 17.8411, 15.228,  // Center context = l, r, right context mega group = 1
	0.000687809, 16.2667, 4.33649, 18.4768, 19.5903, 15.6287,  // Center context = l, r, left context mega group = 2
	0.0140672, 16.7947, 11.2242, 10.8034, 18.4689, 18.2605,  // Center context = l, r, right context mega group = 2
	0.00290131, 18.1393, 4.33646, 13.0897, 12.2936, 14.6588,  // Center context = l, r, left context mega group = 3
	0.00931468, 14.1919, 11.2241, 22.7442, 21.3385, 24.0483,  // Center context = l, r, right context mega group = 3
	0.0186668, 18.7925, 15.3293, 9.94103, 9.59991, 18.3713,  // Center context = l, r, left context mega group = 4
	0.00583457, 15.9367, 14.9451, 28.9436, 17.1146, 25.9778,  // Center context = l, r, right context mega group = 4
	0.0101149, 16.9373, 4.3364, 5.71039, 7.48009, 8.09798,  // Center context = m, n, ng, left context mega group = 0
	0.0200537, 20.8094, 11.2241, 15.2914, 13.2345, 6.58072,  // Center context = m, n, ng, right context mega group = 0
	0.00502445, 10.7555, 4.33645, 16.733, 25.8434, 18.1088,  // Center context = m, n, ng, left context mega group = 1
	0.00276688, 21.8138, 11.2241, 5.97752, 10.1001, 6.5672,  // Center context = m, n, ng, right context mega group = 1
	0.00302612, 8.56701, 4.33649, 17.7984, 21.2177, 19.6693,  // Center context = m, n, ng, left context mega group = 2
	0.0104972, 18.2402, 11.2242, 6.09119, 12.5264, 12.0067,  // Center context = m, n, ng, right context mega group = 2
	0.00408284, 7.77376, 4.33647, 19.7513, 18.27, 17.5535,  // Center context = m, n, ng, left context mega group = 3
	0.0138254, 13.3396, 11.2242, 15.3267, 14.5296, 15.6998,  // Center context = m, n, ng, right context mega group = 3
	0.011888, 21.1303, 4.03566, 4.10615, 6.0995, 16.7061,  // Center context = m, n, ng, left context mega group = 4
	0.0126748, 16.1323, 16.07, 15.7278, 11.8797, 20.8577,  // Center context = m, n, ng, right context mega group = 4
	0.275839, 17.3416, 4.33642, 5.71041, 7.48011, 5.1184,  // Center context = sh, zh, left context mega group = 0
	0.175747, 15.5528, 11.2241, 15.2914, 13.2345, 9.93103,  // Center context = sh, zh, right context mega group = 0
	0.107489, 16.2894, 4.33654, 8.96619, 11.0223, 6.51021,  // Center context = sh, zh, left context mega group = 1
	0.0118666, 8.05339, 11.2242, 22.0598, 27.8208, 17.6923,  // Center context = sh, zh, right context mega group = 1
	0.203623, 7.85004, 4.33656, 22.7591, 18.9273, 15.383,  // Center context = sh, zh, left context mega group = 2
	0.0556786, 3.19788, 11.2243, 26.0071, 41.3332, 23.7274,  // Center context = sh, zh, right context mega group = 2
	0.142243, 11.4297, 4.33658, 19.7706, 18.6023, 11.2976,  // Center context = sh, zh, left context mega group = 3
	0.18178, 9.67447, 11.2243, 36.7828, 33.9922, 24.4482,  // Center context = sh, zh, right context mega group = 3
	0.0883859, 17.7734, 10.0301, 10.3767, 7.70197, 7.33225,  // Center context = sh, zh, left context mega group = 4
	0.13019, 17.6005, 14.8821, 12.8397, 9.87189, 17.349,  // Center context = sh, zh, right context mega group = 4
	0.158341, 16.5258, 4.33643, 5.71042, 7.48012, 5.20974,  // Center context = s, z, left context mega group = 0
	0.0652031, 17.8823, 11.2241, 15.2914, 13.2346, 4.7252,  // Center context = s, z, right context mega group = 0
	0.0536228, 16.3262, 4.33661, 7.96031, 3.16603, 3.29934,  // Center context = s, z, left context mega group = 1
	0.0257501, 16.0869, 11.2242, 15.7731, 7.2371, 10.1357,  // Center context = s, z, right context mega group = 1
	0.0896047, 13.4439, 4.3366, 8.76283, 11.9235, 6.78035,  // Center context = s, z, left context mega group = 2
	0.0264077, 12.5417, 11.2244, 25.9419, 24.0402, 17.0983,  // Center context = s, z, right context mega group = 2
	0.055259, 16.1983, 4.33663, 4.28281, 3.39279, 3.3863,  // Center context = s, z, left context mega group = 3
	0.142798, 14.6251, 11.2243, 19.1817, 19.7948, 20.5812,  // Center context = s, z, right context mega group = 3
	0.0530074, 17.651, 2.67959, 4.51433, 4.28251, 4.44894,  // Center context = s, z, left context mega group = 4
	0.0719236, 14.8127, 8.19931, 9.34569, 6.92392, 15.426,  // Center context = s, z, right context mega group = 4
	0.0226042, 21.0966, 4.33643, 5.71042, 7.48012, 14.3065,  // Center context = v, f, left context mega group = 0
	0.0263726, 20.2413, 11.2241, 15.2914, 13.2345, 11.9272,  // Center context = v, f, right context mega group = 0
	0.100272, 14.8056, 4.33655, 6.8094, 15.9338, 13.1435,  // Center context = v, f, left context mega group = 1
	0.00808321, 17.5282, 11.2242, 27.9266, 37.2658, 20.831,  // Center context = v, f, right context mega group = 1
	0.0343737, 10.4423, 4.33658, 16.9611, 20.0647, 19.5386,  // Center context = v, f, left context mega group = 2
	0.0141325, 17.038, 11.2243, 25.9762, 21.9889, 18.3959,  // Center context = v, f, right context mega group = 2
	0.067628, 15.4772, 4.3366, 11.5358, 11.0596, 13.1662,  // Center context = v, f, left context mega group = 3
	0.0553855, 18.6957, 11.2243, 16.7722, 23.646, 17.0697,  // Center context = v, f, right context mega group = 3
	0.0386965, 18.7619, 3.06713, 9.97255, 9.00875, 15.9298,  // Center context = v, f, left context mega group = 4
	0.0512363, 19.3191, 12.1917, 20.4436, 18.5755, 22.8576,  // Center context = v, f, right context mega group = 4
	0.0590394, 24.6863, 4.33643, 5.71042, 7.48012, 2.61535,  // Center context = th, dh, left context mega group = 0
	0.0148106, 19.9484, 11.2241, 15.2914, 13.2346, 13.8274,  // Center context = th, dh, right context mega group = 0
	0.0853913, 16.446, 4.33654, 19.2437, 30.4866, 13.5313,  // Center context = th, dh, left context mega group = 1
	0.0410208, 2.84326, 11.2242, 16.2784, 35.0757, 28.874,  // Center context = th, dh, right context mega group = 1
	0.0439889, 19.4895, 4.33657, 23.9387, 21.864, 14.5025,  // Center context = th, dh, left context mega group = 2
	0.045069, 14.1587, 11.2243, 25.4021, 20.1029, 18.28,  // Center context = th, dh, right context mega group = 2
	0.0275938, 20.6368, 4.33659, 8.70181, 9.36712, 8.84526,  // Center context = th, dh, left context mega group = 3
	0.226941, 20.3125, 11.2243, 37.3237, 33.7281, 27.5284,  // Center context = th, dh, right context mega group = 3
	0.0347282, 19.2596, 12.8776, 12.0018, 9.56234, 13.8754,  // Center context = th, dh, left context mega group = 4
	0.0429397, 24.256, 10.8443, 9.20377, 5.26901, 26.2165,  // Center context = th, dh, right context mega group = 4
	0.00642252, 22.4284, 4.33642, 5.71041, 7.48011, 5.35976,  // Center context = hh, left context mega group = 0
	0.0633239, 18.2696, 11.2241, 15.2914, 13.2345, 6.33862,  // Center context = hh, right context mega group = 0
	0.0179001, 19.9616, 4.33654, 5.94805, 16.4459, 9.1252,  // Center context = hh, left context mega group = 1
	0.0240806, 16.143, 11.2242, 16.2783, 7.38463, 10.8732,  // Center context = hh, right context mega group = 1
	0.0035697, 15.8454, 4.33656, 14.3856, 21.4895, 17.0276,  // Center context = hh, left context mega group = 2
	0.0262705, 14.8775, 11.2243, 26.0071, 23.2906, 17.5902,  // Center context = hh, right context mega group = 2
	0.0327842, 15.2553, 4.33657, 18.8058, 19.4102, 17.8527,  // Center context = hh, left context mega group = 3
	0.0942102, 5.67343, 11.2243, 17.842, 41.0309, 36.4775,  // Center context = hh, right context mega group = 3
	0.0467694, 19.5302, 10.4722, 8.7995, 13.7974, 14.458,  // Center context = hh, left context mega group = 4
	0.0184664, 21.8741, 21.746, 24.8843, 23.7144, 32.7293,  // Center context = hh, right context mega group = 4
	0.00386957, 24.0122, 4.33641, 5.7104, 7.4801, 14.3577,  // Center context = k, g, left context mega group = 0
	0.00907786, 16.8614, 11.2241, 15.2914, 13.2345, 13.743,  // Center context = k, g, right context mega group = 0
	0.0924499, 7.45739, 4.33649, 18.646, 31.2707, 23.9297,  // Center context = k, g, left context mega group = 1
	0.0188555, 15.3305, 11.2242, 19.1645, 20.358, 14.2143,  // Center context = k, g, right context mega group = 1
	0.00806122, 11.6605, 4.33657, 19.5522, 27.324, 17.679,  // Center context = k, g, left context mega group = 2
	0.00145328, 15.3264, 11.2243, 28.8049, 20.799, 19.4317,  // Center context = k, g, right context mega group = 2
	0.0286205, 13.6947, 4.33655, 22.6471, 15.6304, 15.2462,  // Center context = k, g, left context mega group = 3
	0.0282487, 21.54, 11.2243, 22.9762, 25.6843, 21.4874,  // Center context = k, g, right context mega group = 3
	0.030582, 15.8992, 5.52301, 6.31696, 8.69358, 18.0216,  // Center context = k, g, left context mega group = 4
	0.0311832, 21.7947, 9.04138, 15.658, 16.884, 23.3235,  // Center context = k, g, right context mega group = 4
	0.00367166, 24.9924, 4.33641, 5.7104, 7.4801, 11.6986,  // Center context = t, d, left context mega group = 0
	0.00602712, 23.9757, 11.2241, 15.2914, 13.2346, 12.5463,  // Center context = t, d, right context mega group = 0
	0.0131268, 19.1917, 4.33652, 19.3527, 5.30386, 8.23063,  // Center context = t, d, left context mega group = 1
	0.00404231, 8.39638, 11.2242, 23.5707, 22.5851, 26.3594,  // Center context = t, d, right context mega group = 1
	0.00334096, 20.3034, 4.33662, 11.0666, 7.77582, 7.85729,  // Center context = t, d, left context mega group = 2
	0.0029913, 16.8016, 11.2243, 14.68, 23.576, 23.561,  // Center context = t, d, right context mega group = 2
	0.00434247, 25.1163, 4.33658, 8.65452, 7.37948, 6.54785,  // Center context = t, d, left context mega group = 3
	0.0160359, 20.618, 11.2243, 25.1534, 20.3094, 22.3339,  // Center context = t, d, right context mega group = 3
	0.0142372, 22.1956, 5.05762, 4.70294, 6.49296, 10.5134,  // Center context = t, d, left context mega group = 4
	0.0111756, 24.1805, 9.91858, 13.7673, 11.6262, 22.4724,  // Center context = t, d, right context mega group = 4
	0.00138587, 22.6707, 4.33641, 5.7104, 7.4801, 17.3508,  // Center context = p, b, left context mega group = 0
	0.00345731, 11.8319, 11.2241, 15.2914, 13.2345, 30.2964,  // Center context = p, b, right context mega group = 0
	0.0831955, 7.15976, 4.33648, 18.081, 25.0274, 26.1872,  // Center context = p, b, left context mega group = 1
	0.0199962, 13.8471, 11.2242, 31.503, 23.0515, 22.4176,  // Center context = p, b, right context mega group = 1
	0.0167042, 12.6061, 4.33656, 15.6506, 26.4873, 20.7956,  // Center context = p, b, left context mega group = 2
	0.00356144, 5.55451, 11.2243, 20.6409, 29.3662, 31.2696,  // Center context = p, b, right context mega group = 2
	0.0354261, 15.3668, 4.33655, 17.2911, 15.7469, 19.062,  // Center context = p, b, left context mega group = 3
	0.0505315, 23.4241, 11.2243, 22.1422, 43.7277, 15.962,  // Center context = p, b, right context mega group = 3
	0.0414746, 12.7419, 14.1662, 10.8676, 9.70013, 22.1864,  // Center context = p, b, left context mega group = 4
	0.0203371, 20.2853, 8.35021, 24.9612, 17.0462, 22.289,  // Center context = p, b, right context mega group = 4
	0.16632, 17.5384, 4.33659, 5.71058, 7.48028, 9.8404,  // Center context = ow0, oy0, aw0, left context mega group = 0
	0.281399, 19.2751, 11.2243, 15.2916, 13.2347, 8.99291,  // Center context = ow0, oy0, aw0, right context mega group = 0
	0.142942, 17.4015, 4.33728, 10.9012, 11.9896, 10.3556,  // Center context = ow0, oy0, aw0, left context mega group = 1
	0.114889, 16.3767, 11.225, 16.4479, 21.3553, 18.9651,  // Center context = ow0, oy0, aw0, right context mega group = 1
	0.144611, 15.0235, 4.33764, 21.4819, 16.991, 12.2751,  // Center context = ow0, oy0, aw0, left context mega group = 2
	0.155578, 15.2663, 11.2253, 16.0999, 22.959, 19.3425,  // Center context = ow0, oy0, aw0, right context mega group = 2
	0.165717, 17.1581, 4.33731, 16.2134, 13.5862, 11.6122,  // Center context = ow0, oy0, aw0, left context mega group = 3
	0.0992952, 16.1735, 11.225, 18.2531, 16.3902, 16.5157,  // Center context = ow0, oy0, aw0, right context mega group = 3
	0.146414, 13.3457, 22.1938, 26.3695, 25.033, 17.734,  // Center context = ow0, oy0, aw0, left context mega group = 4
	0.183164, 14.9239, 30.0848, 27.7875, 20.414, 22.8304,  // Center context = ow0, oy0, aw0, right context mega group = 4
	0.158241, 20.7815, 4.33661, 5.7106, 7.4803, 11.4985,  // Center context = iy0, ey0, ay0, left context mega group = 0
	0.15725, 23.7407, 11.2243, 15.2916, 13.2348, 9.28987,  // Center context = iy0, ey0, ay0, right context mega group = 0
	0.0746222, 19.9277, 4.33737, 5.78514, 8.94251, 8.98803,  // Center context = iy0, ey0, ay0, left context mega group = 1
	0.0523709, 18.6169, 11.2251, 12.6232, 12.4481, 17.4444,  // Center context = iy0, ey0, ay0, right context mega group = 1
	0.0680932, 20.1254, 4.3378, 12.7622, 10.6059, 10.9321,  // Center context = iy0, ey0, ay0, left context mega group = 2
	0.0815194, 17.5189, 11.2255, 12.9565, 17.1492, 19.1071,  // Center context = iy0, ey0, ay0, right context mega group = 2
	0.0776432, 22.0273, 4.33739, 6.44144, 9.71084, 8.09,  // Center context = iy0, ey0, ay0, left context mega group = 3
	0.0437843, 20.0453, 11.225, 19.018, 20.0994, 21.2936,  // Center context = iy0, ey0, ay0, right context mega group = 3
	0.0755582, 16.8699, 14.8196, 13.6541, 12.7335, 13.5474,  // Center context = iy0, ey0, ay0, left context mega group = 4
	0.14678, 12.7578, 18.9486, 20.4628, 18.4313, 23.9258,  // Center context = iy0, ey0, ay0, right context mega group = 4
	0.0714714, 20.2133, 4.33662, 5.7106, 7.4803, 7.97531,  // Center context = ah0, er0, uw0, ao0, uh0, left context mega group = 0
	0.121136, 21.091, 11.2243, 15.2916, 13.2348, 8.41957,  // Center context = ah0, er0, uw0, ao0, uh0, right context mega group = 0
	0.0409498, 19.605, 4.33743, 7.48995, 11.1739, 9.02937,  // Center context = ah0, er0, uw0, ao0, uh0, left context mega group = 1
	0.0212448, 20.4586, 11.2251, 10.4205, 14.4887, 17.4443,  // Center context = ah0, er0, uw0, ao0, uh0, right context mega group = 1
	0.036532, 20.4623, 4.33788, 13.9918, 13.5266, 9.3971,  // Center context = ah0, er0, uw0, ao0, uh0, left context mega group = 2
	0.0360595, 22.7628, 11.2256, 15.1043, 18.5459, 20.1944,  // Center context = ah0, er0, uw0, ao0, uh0, right context mega group = 2
	0.0346516, 22.0184, 4.33745, 15.7858, 12.2814, 10.4978,  // Center context = ah0, er0, uw0, ao0, uh0, left context mega group = 3
	0.026723, 19.7375, 11.2251, 17.7242, 19.2922, 18.1284,  // Center context = ah0, er0, uw0, ao0, uh0, right context mega group = 3
	0.0522495, 11.6721, 20.182, 23.7668, 22.5906, 19.7748,  // Center context = ah0, er0, uw0, ao0, uh0, left context mega group = 4
	0.084284, 12.0431, 25.0066, 24.5996, 21.1701, 28.5205,  // Center context = ah0, er0, uw0, ao0, uh0, right context mega group = 4
	0.0925417, 25.8332, 4.33662, 5.71061, 7.48031, 7.25733,  // Center context = ih0, eh0, aa0, ae0, left context mega group = 0
	0.289041, 11.8246, 11.2243, 15.2915, 13.2347, 22.1982,  // Center context = ih0, eh0, aa0, ae0, right context mega group = 0
	0.0316299, 21.6543, 4.33734, 6.65856, 9.32137, 9.54274,  // Center context = ih0, eh0, aa0, ae0, left context mega group = 1
	0.0424373, 22.6049, 11.2251, 13.4664, 17.5714, 15.79,  // Center context = ih0, eh0, aa0, ae0, right context mega group = 1
	0.0288879, 23.4683, 4.33773, 8.42039, 11.4563, 11.5007,  // Center context = ih0, eh0, aa0, ae0, left context mega group = 2
	0.0356658, 22.4562, 11.2255, 14.948, 18.7786, 17.9942,  // Center context = ih0, eh0, aa0, ae0, right context mega group = 2
	0.0267563, 20.7997, 4.33736, 14.3467, 11.6056, 12.4163,  // Center context = ih0, eh0, aa0, ae0, left context mega group = 3
	0.0370592, 24.901, 11.2251, 13.1908, 23.2006, 15.8773,  // Center context = ih0, eh0, aa0, ae0, right context mega group = 3
	0.0365833, 12.9801, 20.2524, 22.6909, 22.4397, 20.8342,  // Center context = ih0, eh0, aa0, ae0, left context mega group = 4
	0.193453, 2.73639, 23.7313, 28.9822, 35.8601, 34.2809,  // Center context = ih0, eh0, aa0, ae0, right context mega group = 4

};


static float gafUnitScoreFuncPerPhone[NUMLEXENGLISHPHONE * MAXSTRESSES] = {

	1.54842, // phone <s>, stress 0
	1.54842, // phone <s>, stress 1
	1.54842, // phone <s>, stress 2
	1.54842, // phone <s>, stress 3
	1.54842, // phone <s>, stress 4
	1.54842, // phone <s>, stress 5
	1.54842, // phone <s>, stress 6
	1.54842, // phone <s>, stress 7
	0.638348, // phone aa, stress 0
	1.31883, // phone aa, stress 1
	0.638348, // phone aa, stress 2
	0.638348, // phone aa, stress 3
	0.638348, // phone aa, stress 4
	0.638348, // phone aa, stress 5
	0.638348, // phone aa, stress 6
	0.638348, // phone aa, stress 7
	0.638348, // phone ae, stress 0
	0.216552, // phone ae, stress 1
	0.638348, // phone ae, stress 2
	0.638348, // phone ae, stress 3
	0.638348, // phone ae, stress 4
	0.638348, // phone ae, stress 5
	0.638348, // phone ae, stress 6
	0.638348, // phone ae, stress 7
	2.03523, // phone ah, stress 0
	0.066837, // phone ah, stress 1
	1.31759, // phone ah, stress 2
	1.31759, // phone ah, stress 3
	1.31759, // phone ah, stress 4
	1.31759, // phone ah, stress 5
	1.31759, // phone ah, stress 6
	1.31759, // phone ah, stress 7
	36.492, // phone ao, stress 0
	0.491488, // phone ao, stress 1
	1.31759, // phone ao, stress 2
	1.31759, // phone ao, stress 3
	1.31759, // phone ao, stress 4
	1.31759, // phone ao, stress 5
	1.31759, // phone ao, stress 6
	1.31759, // phone ao, stress 7
	2.27369, // phone aw, stress 0
	1.03992, // phone aw, stress 1
	2.27369, // phone aw, stress 2
	2.27369, // phone aw, stress 3
	2.27369, // phone aw, stress 4
	2.27369, // phone aw, stress 5
	2.27369, // phone aw, stress 6
	2.27369, // phone aw, stress 7
	1.31759, // phone ax, stress 0
	1.31759, // phone ax, stress 1
	1.31759, // phone ax, stress 2
	1.31759, // phone ax, stress 3
	1.31759, // phone ax, stress 4
	1.31759, // phone ax, stress 5
	1.31759, // phone ax, stress 6
	1.31759, // phone ax, stress 7
	1.44821, // phone ay, stress 0
	2.01249, // phone ay, stress 1
	2.46195, // phone ay, stress 2
	2.46195, // phone ay, stress 3
	2.46195, // phone ay, stress 4
	2.46195, // phone ay, stress 5
	2.46195, // phone ay, stress 6
	2.46195, // phone ay, stress 7
	1.0916, // phone b, stress 0
	2.11896, // phone b, stress 1
	2.11896, // phone b, stress 2
	2.11896, // phone b, stress 3
	2.11896, // phone b, stress 4
	2.11896, // phone b, stress 5
	2.11896, // phone b, stress 6
	2.11896, // phone b, stress 7
	5.52657, // phone ch, stress 0
	5.11113, // phone ch, stress 1
	5.11113, // phone ch, stress 2
	5.11113, // phone ch, stress 3
	5.11113, // phone ch, stress 4
	5.11113, // phone ch, stress 5
	5.11113, // phone ch, stress 6
	5.11113, // phone ch, stress 7
	1.6491, // phone d, stress 0
	0.915392, // phone d, stress 1
	0.915392, // phone d, stress 2
	0.915392, // phone d, stress 3
	0.915392, // phone d, stress 4
	0.915392, // phone d, stress 5
	0.915392, // phone d, stress 6
	0.915392, // phone d, stress 7
	1.10619e-005, // phone dh, stress 0
	0.000983304, // phone dh, stress 1
	0.000983304, // phone dh, stress 2
	0.000983304, // phone dh, stress 3
	0.000983304, // phone dh, stress 4
	0.000983304, // phone dh, stress 5
	0.000983304, // phone dh, stress 6
	0.000983304, // phone dh, stress 7
	14.5183, // phone eh, stress 0
	1.53812, // phone eh, stress 1
	0.638348, // phone eh, stress 2
	0.638348, // phone eh, stress 3
	0.638348, // phone eh, stress 4
	0.638348, // phone eh, stress 5
	0.638348, // phone eh, stress 6
	0.638348, // phone eh, stress 7
	9.06158, // phone er, stress 0
	1.95747, // phone er, stress 1
	1.31759, // phone er, stress 2
	1.31759, // phone er, stress 3
	1.31759, // phone er, stress 4
	1.31759, // phone er, stress 5
	1.31759, // phone er, stress 6
	1.31759, // phone er, stress 7
	2.46195, // phone ey, stress 0
	2.6815, // phone ey, stress 1
	2.46195, // phone ey, stress 2
	2.46195, // phone ey, stress 3
	2.46195, // phone ey, stress 4
	2.46195, // phone ey, stress 5
	2.46195, // phone ey, stress 6
	2.46195, // phone ey, stress 7
	1.60158, // phone f, stress 0
	1.09062, // phone f, stress 1
	1.09062, // phone f, stress 2
	1.09062, // phone f, stress 3
	1.09062, // phone f, stress 4
	1.09062, // phone f, stress 5
	1.09062, // phone f, stress 6
	1.09062, // phone f, stress 7
	6.6467, // phone g, stress 0
	4.48921, // phone g, stress 1
	4.48921, // phone g, stress 2
	4.48921, // phone g, stress 3
	4.48921, // phone g, stress 4
	4.48921, // phone g, stress 5
	4.48921, // phone g, stress 6
	4.48921, // phone g, stress 7
	1.32964, // phone h, stress 0
	1.32933, // phone h, stress 1
	1.32933, // phone h, stress 2
	1.32933, // phone h, stress 3
	1.32933, // phone h, stress 4
	1.32933, // phone h, stress 5
	1.32933, // phone h, stress 6
	1.32933, // phone h, stress 7
	8.31578, // phone ih, stress 0
	0.453729, // phone ih, stress 1
	0.638348, // phone ih, stress 2
	0.638348, // phone ih, stress 3
	0.638348, // phone ih, stress 4
	0.638348, // phone ih, stress 5
	0.638348, // phone ih, stress 6
	0.638348, // phone ih, stress 7
	1.87522, // phone iy, stress 0
	2.33485, // phone iy, stress 1
	2.46195, // phone iy, stress 2
	2.46195, // phone iy, stress 3
	2.46195, // phone iy, stress 4
	2.46195, // phone iy, stress 5
	2.46195, // phone iy, stress 6
	2.46195, // phone iy, stress 7
	3.37032, // phone jh, stress 0
	5.11113, // phone jh, stress 1
	5.11113, // phone jh, stress 2
	5.11113, // phone jh, stress 3
	5.11113, // phone jh, stress 4
	5.11113, // phone jh, stress 5
	5.11113, // phone jh, stress 6
	5.11113, // phone jh, stress 7
	3.98771, // phone k, stress 0
	4.48921, // phone k, stress 1
	4.48921, // phone k, stress 2
	4.48921, // phone k, stress 3
	4.48921, // phone k, stress 4
	4.48921, // phone k, stress 5
	4.48921, // phone k, stress 6
	4.48921, // phone k, stress 7
	5.95496, // phone l, stress 0
	2.89284, // phone l, stress 1
	2.89284, // phone l, stress 2
	2.89284, // phone l, stress 3
	2.89284, // phone l, stress 4
	2.89284, // phone l, stress 5
	2.89284, // phone l, stress 6
	2.89284, // phone l, stress 7
	2.11026, // phone m, stress 0
	1.10232, // phone m, stress 1
	1.10232, // phone m, stress 2
	1.10232, // phone m, stress 3
	1.10232, // phone m, stress 4
	1.10232, // phone m, stress 5
	1.10232, // phone m, stress 6
	1.10232, // phone m, stress 7
	0.607573, // phone n, stress 0
	1.10232, // phone n, stress 1
	1.10232, // phone n, stress 2
	1.10232, // phone n, stress 3
	1.10232, // phone n, stress 4
	1.10232, // phone n, stress 5
	1.10232, // phone n, stress 6
	1.10232, // phone n, stress 7
	4.93145, // phone nx, stress 0
	1.10232, // phone nx, stress 1
	1.10232, // phone nx, stress 2
	1.10232, // phone nx, stress 3
	1.10232, // phone nx, stress 4
	1.10232, // phone nx, stress 5
	1.10232, // phone nx, stress 6
	1.10232, // phone nx, stress 7
	13.2917, // phone ow, stress 0
	2.59017, // phone ow, stress 1
	2.27369, // phone ow, stress 2
	2.27369, // phone ow, stress 3
	2.27369, // phone ow, stress 4
	2.27369, // phone ow, stress 5
	2.27369, // phone ow, stress 6
	2.27369, // phone ow, stress 7
	2.27369, // phone oy, stress 0
	1.31595, // phone oy, stress 1
	2.27369, // phone oy, stress 2
	2.27369, // phone oy, stress 3
	2.27369, // phone oy, stress 4
	2.27369, // phone oy, stress 5
	2.27369, // phone oy, stress 6
	2.27369, // phone oy, stress 7
	7.12299, // phone p, stress 0
	2.11896, // phone p, stress 1
	2.11896, // phone p, stress 2
	2.11896, // phone p, stress 3
	2.11896, // phone p, stress 4
	2.11896, // phone p, stress 5
	2.11896, // phone p, stress 6
	2.11896, // phone p, stress 7
	2.28778, // phone r, stress 0
	2.89284, // phone r, stress 1
	2.89284, // phone r, stress 2
	2.89284, // phone r, stress 3
	2.89284, // phone r, stress 4
	2.89284, // phone r, stress 5
	2.89284, // phone r, stress 6
	2.89284, // phone r, stress 7
	1.9551, // phone s, stress 0
	0.878108, // phone s, stress 1
	0.878108, // phone s, stress 2
	0.878108, // phone s, stress 3
	0.878108, // phone s, stress 4
	0.878108, // phone s, stress 5
	0.878108, // phone s, stress 6
	0.878108, // phone s, stress 7
	2.85612, // phone sh, stress 0
	2.84661, // phone sh, stress 1
	2.84661, // phone sh, stress 2
	2.84661, // phone sh, stress 3
	2.84661, // phone sh, stress 4
	2.84661, // phone sh, stress 5
	2.84661, // phone sh, stress 6
	2.84661, // phone sh, stress 7
	0.688122, // phone t, stress 0
	0.915392, // phone t, stress 1
	0.915392, // phone t, stress 2
	0.915392, // phone t, stress 3
	0.915392, // phone t, stress 4
	0.915392, // phone t, stress 5
	0.915392, // phone t, stress 6
	0.915392, // phone t, stress 7
	1.05431, // phone th, stress 0
	0.000983304, // phone th, stress 1
	0.000983304, // phone th, stress 2
	0.000983304, // phone th, stress 3
	0.000983304, // phone th, stress 4
	0.000983304, // phone th, stress 5
	0.000983304, // phone th, stress 6
	0.000983304, // phone th, stress 7
	1.31759, // phone uh, stress 0
	0.00871263, // phone uh, stress 1
	1.31759, // phone uh, stress 2
	1.31759, // phone uh, stress 3
	1.31759, // phone uh, stress 4
	1.31759, // phone uh, stress 5
	1.31759, // phone uh, stress 6
	1.31759, // phone uh, stress 7
	3.59251, // phone uw, stress 0
	0.869589, // phone uw, stress 1
	1.31759, // phone uw, stress 2
	1.31759, // phone uw, stress 3
	1.31759, // phone uw, stress 4
	1.31759, // phone uw, stress 5
	1.31759, // phone uw, stress 6
	1.31759, // phone uw, stress 7
	1.07423, // phone v, stress 0
	1.09062, // phone v, stress 1
	1.09062, // phone v, stress 2
	1.09062, // phone v, stress 3
	1.09062, // phone v, stress 4
	1.09062, // phone v, stress 5
	1.09062, // phone v, stress 6
	1.09062, // phone v, stress 7
	0.373325, // phone w, stress 0
	0.83923, // phone w, stress 1
	0.83923, // phone w, stress 2
	0.83923, // phone w, stress 3
	0.83923, // phone w, stress 4
	0.83923, // phone w, stress 5
	0.83923, // phone w, stress 6
	0.83923, // phone w, stress 7
	1.12422, // phone y, stress 0
	0.83923, // phone y, stress 1
	0.83923, // phone y, stress 2
	0.83923, // phone y, stress 3
	0.83923, // phone y, stress 4
	0.83923, // phone y, stress 5
	0.83923, // phone y, stress 6
	0.83923, // phone y, stress 7
	0.756341, // phone z, stress 0
	0.878108, // phone z, stress 1
	0.878108, // phone z, stress 2
	0.878108, // phone z, stress 3
	0.878108, // phone z, stress 4
	0.878108, // phone z, stress 5
	0.878108, // phone z, stress 6
	0.878108, // phone z, stress 7
	2.84661, // phone zh, stress 0
	2.84661, // phone zh, stress 1
	2.84661, // phone zh, stress 2
	2.84661, // phone zh, stress 3
	2.84661, // phone zh, stress 4
	2.84661, // phone zh, stress 5
	2.84661, // phone zh, stress 6
	2.84661, // phone zh, stress 7
};

static float gafUnitScoreDurationPSOLAPitchCertaintyPerPhone[NUMLEXENGLISHPHONE * MAXSTRESSES * 2] = {
	-3.96685, 14.8264, // mX + b, phone <s>, stress 0
	-3.96685, 14.8264, // mX + b, phone <s>, stress 1
	-3.96685, 14.8264, // mX + b, phone <s>, stress 2
	-3.96685, 14.8264, // mX + b, phone <s>, stress 3
	-3.96685, 14.8264, // mX + b, phone <s>, stress 4
	-3.96685, 14.8264, // mX + b, phone <s>, stress 5
	-3.96685, 14.8264, // mX + b, phone <s>, stress 6
	-3.96685, 14.8264, // mX + b, phone <s>, stress 7
	-7.30768, 12.6565, // mX + b, phone aa, stress 0
	-3.57124, 9.82755, // mX + b, phone aa, stress 1
	-7.02206, 14.8184, // mX + b, phone aa, stress 2
	-7.02206, 14.8184, // mX + b, phone aa, stress 3
	-7.02206, 14.8184, // mX + b, phone aa, stress 4
	-7.02206, 14.8184, // mX + b, phone aa, stress 5
	-7.02206, 14.8184, // mX + b, phone aa, stress 6
	-7.02206, 14.8184, // mX + b, phone aa, stress 7
	-7.89176, 14.2089, // mX + b, phone ae, stress 0
	-4.35403, 12.3831, // mX + b, phone ae, stress 1
	-7.02206, 14.8184, // mX + b, phone ae, stress 2
	-7.02206, 14.8184, // mX + b, phone ae, stress 3
	-7.02206, 14.8184, // mX + b, phone ae, stress 4
	-7.02206, 14.8184, // mX + b, phone ae, stress 5
	-7.02206, 14.8184, // mX + b, phone ae, stress 6
	-7.02206, 14.8184, // mX + b, phone ae, stress 7
	-7.48417, 14.825, // mX + b, phone ah, stress 0
	-4.11479, 12.4412, // mX + b, phone ah, stress 1
	-5.77685, 12.2162, // mX + b, phone ah, stress 2
	-5.77685, 12.2162, // mX + b, phone ah, stress 3
	-5.77685, 12.2162, // mX + b, phone ah, stress 4
	-5.77685, 12.2162, // mX + b, phone ah, stress 5
	-5.77685, 12.2162, // mX + b, phone ah, stress 6
	-5.77685, 12.2162, // mX + b, phone ah, stress 7
	-9.06682, 13.3728, // mX + b, phone ao, stress 0
	-3.21585, 8.45912, // mX + b, phone ao, stress 1
	-5.77685, 12.2162, // mX + b, phone ao, stress 2
	-5.77685, 12.2162, // mX + b, phone ao, stress 3
	-5.77685, 12.2162, // mX + b, phone ao, stress 4
	-5.77685, 12.2162, // mX + b, phone ao, stress 5
	-5.77685, 12.2162, // mX + b, phone ao, stress 6
	-5.77685, 12.2162, // mX + b, phone ao, stress 7
	-4.24663, 9.75006, // mX + b, phone aw, stress 0
	-7.00996, 11.3584, // mX + b, phone aw, stress 1
	-4.24663, 9.75006, // mX + b, phone aw, stress 2
	-4.24663, 9.75006, // mX + b, phone aw, stress 3
	-4.24663, 9.75006, // mX + b, phone aw, stress 4
	-4.24663, 9.75006, // mX + b, phone aw, stress 5
	-4.24663, 9.75006, // mX + b, phone aw, stress 6
	-4.24663, 9.75006, // mX + b, phone aw, stress 7
	-5.77685, 12.2162, // mX + b, phone ax, stress 0
	-5.77685, 12.2162, // mX + b, phone ax, stress 1
	-5.77685, 12.2162, // mX + b, phone ax, stress 2
	-5.77685, 12.2162, // mX + b, phone ax, stress 3
	-5.77685, 12.2162, // mX + b, phone ax, stress 4
	-5.77685, 12.2162, // mX + b, phone ax, stress 5
	-5.77685, 12.2162, // mX + b, phone ax, stress 6
	-5.77685, 12.2162, // mX + b, phone ax, stress 7
	-3.84428, 10.9216, // mX + b, phone ay, stress 0
	-3.80383, 10.0529, // mX + b, phone ay, stress 1
	-4.52727, 11.2506, // mX + b, phone ay, stress 2
	-4.52727, 11.2506, // mX + b, phone ay, stress 3
	-4.52727, 11.2506, // mX + b, phone ay, stress 4
	-4.52727, 11.2506, // mX + b, phone ay, stress 5
	-4.52727, 11.2506, // mX + b, phone ay, stress 6
	-4.52727, 11.2506, // mX + b, phone ay, stress 7
	3.20035, 13.3195, // mX + b, phone b, stress 0
	0.509153, 15.8956, // mX + b, phone b, stress 1
	0.509153, 15.8956, // mX + b, phone b, stress 2
	0.509153, 15.8956, // mX + b, phone b, stress 3
	0.509153, 15.8956, // mX + b, phone b, stress 4
	0.509153, 15.8956, // mX + b, phone b, stress 5
	0.509153, 15.8956, // mX + b, phone b, stress 6
	0.509153, 15.8956, // mX + b, phone b, stress 7
	6.49993, 17.7935, // mX + b, phone ch, stress 0
	-1.50186, 18.2396, // mX + b, phone ch, stress 1
	-1.50186, 18.2396, // mX + b, phone ch, stress 2
	-1.50186, 18.2396, // mX + b, phone ch, stress 3
	-1.50186, 18.2396, // mX + b, phone ch, stress 4
	-1.50186, 18.2396, // mX + b, phone ch, stress 5
	-1.50186, 18.2396, // mX + b, phone ch, stress 6
	-1.50186, 18.2396, // mX + b, phone ch, stress 7
	-8.47774, 20.6669, // mX + b, phone d, stress 0
	-5.86823, 20.5295, // mX + b, phone d, stress 1
	-5.86823, 20.5295, // mX + b, phone d, stress 2
	-5.86823, 20.5295, // mX + b, phone d, stress 3
	-5.86823, 20.5295, // mX + b, phone d, stress 4
	-5.86823, 20.5295, // mX + b, phone d, stress 5
	-5.86823, 20.5295, // mX + b, phone d, stress 6
	-5.86823, 20.5295, // mX + b, phone d, stress 7
	-11.1824, 17.3251, // mX + b, phone dh, stress 0
	-17.1592, 21.0028, // mX + b, phone dh, stress 1
	-17.1592, 21.0028, // mX + b, phone dh, stress 2
	-17.1592, 21.0028, // mX + b, phone dh, stress 3
	-17.1592, 21.0028, // mX + b, phone dh, stress 4
	-17.1592, 21.0028, // mX + b, phone dh, stress 5
	-17.1592, 21.0028, // mX + b, phone dh, stress 6
	-17.1592, 21.0028, // mX + b, phone dh, stress 7
	-8.21826, 16.8939, // mX + b, phone eh, stress 0
	-5.62445, 13.7663, // mX + b, phone eh, stress 1
	-7.02206, 14.8184, // mX + b, phone eh, stress 2
	-7.02206, 14.8184, // mX + b, phone eh, stress 3
	-7.02206, 14.8184, // mX + b, phone eh, stress 4
	-7.02206, 14.8184, // mX + b, phone eh, stress 5
	-7.02206, 14.8184, // mX + b, phone eh, stress 6
	-7.02206, 14.8184, // mX + b, phone eh, stress 7
	-6.25262, 11.0771, // mX + b, phone er, stress 0
	-5.06296, 11.0309, // mX + b, phone er, stress 1
	-5.77685, 12.2162, // mX + b, phone er, stress 2
	-5.77685, 12.2162, // mX + b, phone er, stress 3
	-5.77685, 12.2162, // mX + b, phone er, stress 4
	-5.77685, 12.2162, // mX + b, phone er, stress 5
	-5.77685, 12.2162, // mX + b, phone er, stress 6
	-5.77685, 12.2162, // mX + b, phone er, stress 7
	-3.04133, 9.90764, // mX + b, phone ey, stress 0
	-4.4259, 11.1495, // mX + b, phone ey, stress 1
	-4.52727, 11.2506, // mX + b, phone ey, stress 2
	-4.52727, 11.2506, // mX + b, phone ey, stress 3
	-4.52727, 11.2506, // mX + b, phone ey, stress 4
	-4.52727, 11.2506, // mX + b, phone ey, stress 5
	-4.52727, 11.2506, // mX + b, phone ey, stress 6
	-4.52727, 11.2506, // mX + b, phone ey, stress 7
	5.62193, 17.8982, // mX + b, phone f, stress 0
	-14.4415, 19.9824, // mX + b, phone f, stress 1
	-14.4415, 19.9824, // mX + b, phone f, stress 2
	-14.4415, 19.9824, // mX + b, phone f, stress 3
	-14.4415, 19.9824, // mX + b, phone f, stress 4
	-14.4415, 19.9824, // mX + b, phone f, stress 5
	-14.4415, 19.9824, // mX + b, phone f, stress 6
	-14.4415, 19.9824, // mX + b, phone f, stress 7
	-4.54836, 17.0444, // mX + b, phone g, stress 0
	-7.1842, 20.6731, // mX + b, phone g, stress 1
	-7.1842, 20.6731, // mX + b, phone g, stress 2
	-7.1842, 20.6731, // mX + b, phone g, stress 3
	-7.1842, 20.6731, // mX + b, phone g, stress 4
	-7.1842, 20.6731, // mX + b, phone g, stress 5
	-7.1842, 20.6731, // mX + b, phone g, stress 6
	-7.1842, 20.6731, // mX + b, phone g, stress 7
	-8.15316, 20.2554, // mX + b, phone h, stress 0
	-7.89921, 20.2029, // mX + b, phone h, stress 1
	-7.89921, 20.2029, // mX + b, phone h, stress 2
	-7.89921, 20.2029, // mX + b, phone h, stress 3
	-7.89921, 20.2029, // mX + b, phone h, stress 4
	-7.89921, 20.2029, // mX + b, phone h, stress 5
	-7.89921, 20.2029, // mX + b, phone h, stress 6
	-7.89921, 20.2029, // mX + b, phone h, stress 7
	-7.53291, 16.0398, // mX + b, phone ih, stress 0
	-6.99024, 16.4198, // mX + b, phone ih, stress 1
	-7.02206, 14.8184, // mX + b, phone ih, stress 2
	-7.02206, 14.8184, // mX + b, phone ih, stress 3
	-7.02206, 14.8184, // mX + b, phone ih, stress 4
	-7.02206, 14.8184, // mX + b, phone ih, stress 5
	-7.02206, 14.8184, // mX + b, phone ih, stress 6
	-7.02206, 14.8184, // mX + b, phone ih, stress 7
	-4.65523, 11.7336, // mX + b, phone iy, stress 0
	-3.95751, 11.027, // mX + b, phone iy, stress 1
	-4.52727, 11.2506, // mX + b, phone iy, stress 2
	-4.52727, 11.2506, // mX + b, phone iy, stress 3
	-4.52727, 11.2506, // mX + b, phone iy, stress 4
	-4.52727, 11.2506, // mX + b, phone iy, stress 5
	-4.52727, 11.2506, // mX + b, phone iy, stress 6
	-4.52727, 11.2506, // mX + b, phone iy, stress 7
	-1.49409, 17.785, // mX + b, phone jh, stress 0
	-1.50186, 18.2396, // mX + b, phone jh, stress 1
	-1.50186, 18.2396, // mX + b, phone jh, stress 2
	-1.50186, 18.2396, // mX + b, phone jh, stress 3
	-1.50186, 18.2396, // mX + b, phone jh, stress 4
	-1.50186, 18.2396, // mX + b, phone jh, stress 5
	-1.50186, 18.2396, // mX + b, phone jh, stress 6
	-1.50186, 18.2396, // mX + b, phone jh, stress 7
	3.43784, 19.3153, // mX + b, phone k, stress 0
	-7.1842, 20.6731, // mX + b, phone k, stress 1
	-7.1842, 20.6731, // mX + b, phone k, stress 2
	-7.1842, 20.6731, // mX + b, phone k, stress 3
	-7.1842, 20.6731, // mX + b, phone k, stress 4
	-7.1842, 20.6731, // mX + b, phone k, stress 5
	-7.1842, 20.6731, // mX + b, phone k, stress 6
	-7.1842, 20.6731, // mX + b, phone k, stress 7
	-3.14588, 10.3739, // mX + b, phone l, stress 0
	-4.06639, 11.2727, // mX + b, phone l, stress 1
	-4.06639, 11.2727, // mX + b, phone l, stress 2
	-4.06639, 11.2727, // mX + b, phone l, stress 3
	-4.06639, 11.2727, // mX + b, phone l, stress 4
	-4.06639, 11.2727, // mX + b, phone l, stress 5
	-4.06639, 11.2727, // mX + b, phone l, stress 6
	-4.06639, 11.2727, // mX + b, phone l, stress 7
	-2.1299, 9.64531, // mX + b, phone m, stress 0
	-3.81331, 10.7331, // mX + b, phone m, stress 1
	-3.81331, 10.7331, // mX + b, phone m, stress 2
	-3.81331, 10.7331, // mX + b, phone m, stress 3
	-3.81331, 10.7331, // mX + b, phone m, stress 4
	-3.81331, 10.7331, // mX + b, phone m, stress 5
	-3.81331, 10.7331, // mX + b, phone m, stress 6
	-3.81331, 10.7331, // mX + b, phone m, stress 7
	-4.12125, 11.1278, // mX + b, phone n, stress 0
	-3.81331, 10.7331, // mX + b, phone n, stress 1
	-3.81331, 10.7331, // mX + b, phone n, stress 2
	-3.81331, 10.7331, // mX + b, phone n, stress 3
	-3.81331, 10.7331, // mX + b, phone n, stress 4
	-3.81331, 10.7331, // mX + b, phone n, stress 5
	-3.81331, 10.7331, // mX + b, phone n, stress 6
	-3.81331, 10.7331, // mX + b, phone n, stress 7
	-7.74632, 11.3921, // mX + b, phone nx, stress 0
	-3.81331, 10.7331, // mX + b, phone nx, stress 1
	-3.81331, 10.7331, // mX + b, phone nx, stress 2
	-3.81331, 10.7331, // mX + b, phone nx, stress 3
	-3.81331, 10.7331, // mX + b, phone nx, stress 4
	-3.81331, 10.7331, // mX + b, phone nx, stress 5
	-3.81331, 10.7331, // mX + b, phone nx, stress 6
	-3.81331, 10.7331, // mX + b, phone nx, stress 7
	-4.05164, 9.81902, // mX + b, phone ow, stress 0
	-3.49267, 9.2116, // mX + b, phone ow, stress 1
	-4.24663, 9.75006, // mX + b, phone ow, stress 2
	-4.24663, 9.75006, // mX + b, phone ow, stress 3
	-4.24663, 9.75006, // mX + b, phone ow, stress 4
	-4.24663, 9.75006, // mX + b, phone ow, stress 5
	-4.24663, 9.75006, // mX + b, phone ow, stress 6
	-4.24663, 9.75006, // mX + b, phone ow, stress 7
	-3.79078, 9.59376, // mX + b, phone oy, stress 0
	-3.43793, 9.27842, // mX + b, phone oy, stress 1
	-4.24663, 9.75006, // mX + b, phone oy, stress 2
	-4.24663, 9.75006, // mX + b, phone oy, stress 3
	-4.24663, 9.75006, // mX + b, phone oy, stress 4
	-4.24663, 9.75006, // mX + b, phone oy, stress 5
	-4.24663, 9.75006, // mX + b, phone oy, stress 6
	-4.24663, 9.75006, // mX + b, phone oy, stress 7
	0.272237, 16.977, // mX + b, phone p, stress 0
	0.509153, 15.8956, // mX + b, phone p, stress 1
	0.509153, 15.8956, // mX + b, phone p, stress 2
	0.509153, 15.8956, // mX + b, phone p, stress 3
	0.509153, 15.8956, // mX + b, phone p, stress 4
	0.509153, 15.8956, // mX + b, phone p, stress 5
	0.509153, 15.8956, // mX + b, phone p, stress 6
	0.509153, 15.8956, // mX + b, phone p, stress 7
	-4.52161, 11.865, // mX + b, phone r, stress 0
	-4.06639, 11.2727, // mX + b, phone r, stress 1
	-4.06639, 11.2727, // mX + b, phone r, stress 2
	-4.06639, 11.2727, // mX + b, phone r, stress 3
	-4.06639, 11.2727, // mX + b, phone r, stress 4
	-4.06639, 11.2727, // mX + b, phone r, stress 5
	-4.06639, 11.2727, // mX + b, phone r, stress 6
	-4.06639, 11.2727, // mX + b, phone r, stress 7
	12.7366, 16.5543, // mX + b, phone s, stress 0
	15.1674, 16.5212, // mX + b, phone s, stress 1
	15.1674, 16.5212, // mX + b, phone s, stress 2
	15.1674, 16.5212, // mX + b, phone s, stress 3
	15.1674, 16.5212, // mX + b, phone s, stress 4
	15.1674, 16.5212, // mX + b, phone s, stress 5
	15.1674, 16.5212, // mX + b, phone s, stress 6
	15.1674, 16.5212, // mX + b, phone s, stress 7
	4.63284, 18.1084, // mX + b, phone sh, stress 0
	10.5292, 17.4889, // mX + b, phone sh, stress 1
	10.5292, 17.4889, // mX + b, phone sh, stress 2
	10.5292, 17.4889, // mX + b, phone sh, stress 3
	10.5292, 17.4889, // mX + b, phone sh, stress 4
	10.5292, 17.4889, // mX + b, phone sh, stress 5
	10.5292, 17.4889, // mX + b, phone sh, stress 6
	10.5292, 17.4889, // mX + b, phone sh, stress 7
	0.950076, 19.7124, // mX + b, phone t, stress 0
	-5.86823, 20.5295, // mX + b, phone t, stress 1
	-5.86823, 20.5295, // mX + b, phone t, stress 2
	-5.86823, 20.5295, // mX + b, phone t, stress 3
	-5.86823, 20.5295, // mX + b, phone t, stress 4
	-5.86823, 20.5295, // mX + b, phone t, stress 5
	-5.86823, 20.5295, // mX + b, phone t, stress 6
	-5.86823, 20.5295, // mX + b, phone t, stress 7
	-0.257901, 20.9055, // mX + b, phone th, stress 0
	-17.1592, 21.0028, // mX + b, phone th, stress 1
	-17.1592, 21.0028, // mX + b, phone th, stress 2
	-17.1592, 21.0028, // mX + b, phone th, stress 3
	-17.1592, 21.0028, // mX + b, phone th, stress 4
	-17.1592, 21.0028, // mX + b, phone th, stress 5
	-17.1592, 21.0028, // mX + b, phone th, stress 6
	-17.1592, 21.0028, // mX + b, phone th, stress 7
	-11.1153, 15.2043, // mX + b, phone uh, stress 0
	-4.18834, 14.3927, // mX + b, phone uh, stress 1
	-5.77685, 12.2162, // mX + b, phone uh, stress 2
	-5.77685, 12.2162, // mX + b, phone uh, stress 3
	-5.77685, 12.2162, // mX + b, phone uh, stress 4
	-5.77685, 12.2162, // mX + b, phone uh, stress 5
	-5.77685, 12.2162, // mX + b, phone uh, stress 6
	-5.77685, 12.2162, // mX + b, phone uh, stress 7
	-7.18925, 12.1454, // mX + b, phone uw, stress 0
	-5.32473, 11.089, // mX + b, phone uw, stress 1
	-5.77685, 12.2162, // mX + b, phone uw, stress 2
	-5.77685, 12.2162, // mX + b, phone uw, stress 3
	-5.77685, 12.2162, // mX + b, phone uw, stress 4
	-5.77685, 12.2162, // mX + b, phone uw, stress 5
	-5.77685, 12.2162, // mX + b, phone uw, stress 6
	-5.77685, 12.2162, // mX + b, phone uw, stress 7
	-7.47784, 14.2925, // mX + b, phone v, stress 0
	-14.4415, 19.9824, // mX + b, phone v, stress 1
	-14.4415, 19.9824, // mX + b, phone v, stress 2
	-14.4415, 19.9824, // mX + b, phone v, stress 3
	-14.4415, 19.9824, // mX + b, phone v, stress 4
	-14.4415, 19.9824, // mX + b, phone v, stress 5
	-14.4415, 19.9824, // mX + b, phone v, stress 6
	-14.4415, 19.9824, // mX + b, phone v, stress 7
	-3.62822, 11.1116, // mX + b, phone w, stress 0
	-5.47209, 12.1911, // mX + b, phone w, stress 1
	-5.47209, 12.1911, // mX + b, phone w, stress 2
	-5.47209, 12.1911, // mX + b, phone w, stress 3
	-5.47209, 12.1911, // mX + b, phone w, stress 4
	-5.47209, 12.1911, // mX + b, phone w, stress 5
	-5.47209, 12.1911, // mX + b, phone w, stress 6
	-5.47209, 12.1911, // mX + b, phone w, stress 7
	-8.92627, 13.9669, // mX + b, phone y, stress 0
	-5.47209, 12.1911, // mX + b, phone y, stress 1
	-5.47209, 12.1911, // mX + b, phone y, stress 2
	-5.47209, 12.1911, // mX + b, phone y, stress 3
	-5.47209, 12.1911, // mX + b, phone y, stress 4
	-5.47209, 12.1911, // mX + b, phone y, stress 5
	-5.47209, 12.1911, // mX + b, phone y, stress 6
	-5.47209, 12.1911, // mX + b, phone y, stress 7
	13.59, 17.1138, // mX + b, phone z, stress 0
	15.1674, 16.5212, // mX + b, phone z, stress 1
	15.1674, 16.5212, // mX + b, phone z, stress 2
	15.1674, 16.5212, // mX + b, phone z, stress 3
	15.1674, 16.5212, // mX + b, phone z, stress 4
	15.1674, 16.5212, // mX + b, phone z, stress 5
	15.1674, 16.5212, // mX + b, phone z, stress 6
	15.1674, 16.5212, // mX + b, phone z, stress 7
	-5.22202, 22.425, // mX + b, phone zh, stress 0
	10.5292, 17.4889, // mX + b, phone zh, stress 1
	10.5292, 17.4889, // mX + b, phone zh, stress 2
	10.5292, 17.4889, // mX + b, phone zh, stress 3
	10.5292, 17.4889, // mX + b, phone zh, stress 4
	10.5292, 17.4889, // mX + b, phone zh, stress 5
	10.5292, 17.4889, // mX + b, phone zh, stress 6
	10.5292, 17.4889, // mX + b, phone zh, stress 7
};

static float gafUnitScorePitchPSOLAPitchCertaintyPerPhone[NUMLEXENGLISHPHONE * MAXSTRESSES * 2] = {
	0.387589, 22.7924, // mX + b, phone <s>, stress 0
	0.387589, 22.7924, // mX + b, phone <s>, stress 1
	0.387589, 22.7924, // mX + b, phone <s>, stress 2
	0.387589, 22.7924, // mX + b, phone <s>, stress 3
	0.387589, 22.7924, // mX + b, phone <s>, stress 4
	0.387589, 22.7924, // mX + b, phone <s>, stress 5
	0.387589, 22.7924, // mX + b, phone <s>, stress 6
	0.387589, 22.7924, // mX + b, phone <s>, stress 7
	-0.400897, 20.2694, // mX + b, phone aa, stress 0
	-6.25762, 22.2098, // mX + b, phone aa, stress 1
	-7.50773, 25.8849, // mX + b, phone aa, stress 2
	-7.50773, 25.8849, // mX + b, phone aa, stress 3
	-7.50773, 25.8849, // mX + b, phone aa, stress 4
	-7.50773, 25.8849, // mX + b, phone aa, stress 5
	-7.50773, 25.8849, // mX + b, phone aa, stress 6
	-7.50773, 25.8849, // mX + b, phone aa, stress 7
	-7.95139, 23.7631, // mX + b, phone ae, stress 0
	-5.36783, 22.7866, // mX + b, phone ae, stress 1
	-7.50773, 25.8849, // mX + b, phone ae, stress 2
	-7.50773, 25.8849, // mX + b, phone ae, stress 3
	-7.50773, 25.8849, // mX + b, phone ae, stress 4
	-7.50773, 25.8849, // mX + b, phone ae, stress 5
	-7.50773, 25.8849, // mX + b, phone ae, stress 6
	-7.50773, 25.8849, // mX + b, phone ae, stress 7
	-5.76696, 25.4007, // mX + b, phone ah, stress 0
	-5.79107, 24.3869, // mX + b, phone ah, stress 1
	-6.22431, 25.2607, // mX + b, phone ah, stress 2
	-6.22431, 25.2607, // mX + b, phone ah, stress 3
	-6.22431, 25.2607, // mX + b, phone ah, stress 4
	-6.22431, 25.2607, // mX + b, phone ah, stress 5
	-6.22431, 25.2607, // mX + b, phone ah, stress 6
	-6.22431, 25.2607, // mX + b, phone ah, stress 7
	-9.61702, 25.709, // mX + b, phone ao, stress 0
	-1.39258, 20.7263, // mX + b, phone ao, stress 1
	-6.22431, 25.2607, // mX + b, phone ao, stress 2
	-6.22431, 25.2607, // mX + b, phone ao, stress 3
	-6.22431, 25.2607, // mX + b, phone ao, stress 4
	-6.22431, 25.2607, // mX + b, phone ao, stress 5
	-6.22431, 25.2607, // mX + b, phone ao, stress 6
	-6.22431, 25.2607, // mX + b, phone ao, stress 7
	-5.87949, 23.7005, // mX + b, phone aw, stress 0
	-2.87715, 20.1331, // mX + b, phone aw, stress 1
	-5.87949, 23.7005, // mX + b, phone aw, stress 2
	-5.87949, 23.7005, // mX + b, phone aw, stress 3
	-5.87949, 23.7005, // mX + b, phone aw, stress 4
	-5.87949, 23.7005, // mX + b, phone aw, stress 5
	-5.87949, 23.7005, // mX + b, phone aw, stress 6
	-5.87949, 23.7005, // mX + b, phone aw, stress 7
	-6.22431, 25.2607, // mX + b, phone ax, stress 0
	-6.22431, 25.2607, // mX + b, phone ax, stress 1
	-6.22431, 25.2607, // mX + b, phone ax, stress 2
	-6.22431, 25.2607, // mX + b, phone ax, stress 3
	-6.22431, 25.2607, // mX + b, phone ax, stress 4
	-6.22431, 25.2607, // mX + b, phone ax, stress 5
	-6.22431, 25.2607, // mX + b, phone ax, stress 6
	-6.22431, 25.2607, // mX + b, phone ax, stress 7
	-0.465457, 18.5868, // mX + b, phone ay, stress 0
	-5.30138, 21.1498, // mX + b, phone ay, stress 1
	-3.94325, 23.7013, // mX + b, phone ay, stress 2
	-3.94325, 23.7013, // mX + b, phone ay, stress 3
	-3.94325, 23.7013, // mX + b, phone ay, stress 4
	-3.94325, 23.7013, // mX + b, phone ay, stress 5
	-3.94325, 23.7013, // mX + b, phone ay, stress 6
	-3.94325, 23.7013, // mX + b, phone ay, stress 7
	4.05585, 24.3293, // mX + b, phone b, stress 0
	13.5065, 18.6841, // mX + b, phone b, stress 1
	13.5065, 18.6841, // mX + b, phone b, stress 2
	13.5065, 18.6841, // mX + b, phone b, stress 3
	13.5065, 18.6841, // mX + b, phone b, stress 4
	13.5065, 18.6841, // mX + b, phone b, stress 5
	13.5065, 18.6841, // mX + b, phone b, stress 6
	13.5065, 18.6841, // mX + b, phone b, stress 7
	8.73054, 17.5042, // mX + b, phone ch, stress 0
	12.9415, 17.7295, // mX + b, phone ch, stress 1
	12.9415, 17.7295, // mX + b, phone ch, stress 2
	12.9415, 17.7295, // mX + b, phone ch, stress 3
	12.9415, 17.7295, // mX + b, phone ch, stress 4
	12.9415, 17.7295, // mX + b, phone ch, stress 5
	12.9415, 17.7295, // mX + b, phone ch, stress 6
	12.9415, 17.7295, // mX + b, phone ch, stress 7
	-5.74333, 28.1171, // mX + b, phone d, stress 0
	7.77664, 21.4693, // mX + b, phone d, stress 1
	7.77664, 21.4693, // mX + b, phone d, stress 2
	7.77664, 21.4693, // mX + b, phone d, stress 3
	7.77664, 21.4693, // mX + b, phone d, stress 4
	7.77664, 21.4693, // mX + b, phone d, stress 5
	7.77664, 21.4693, // mX + b, phone d, stress 6
	7.77664, 21.4693, // mX + b, phone d, stress 7
	-7.58958, 26.5044, // mX + b, phone dh, stress 0
	-4.58481, 24.6105, // mX + b, phone dh, stress 1
	-4.58481, 24.6105, // mX + b, phone dh, stress 2
	-4.58481, 24.6105, // mX + b, phone dh, stress 3
	-4.58481, 24.6105, // mX + b, phone dh, stress 4
	-4.58481, 24.6105, // mX + b, phone dh, stress 5
	-4.58481, 24.6105, // mX + b, phone dh, stress 6
	-4.58481, 24.6105, // mX + b, phone dh, stress 7
	-5.83634, 26.1398, // mX + b, phone eh, stress 0
	-6.22709, 25.3618, // mX + b, phone eh, stress 1
	-7.50773, 25.8849, // mX + b, phone eh, stress 2
	-7.50773, 25.8849, // mX + b, phone eh, stress 3
	-7.50773, 25.8849, // mX + b, phone eh, stress 4
	-7.50773, 25.8849, // mX + b, phone eh, stress 5
	-7.50773, 25.8849, // mX + b, phone eh, stress 6
	-7.50773, 25.8849, // mX + b, phone eh, stress 7
	-6.5628, 24.0263, // mX + b, phone er, stress 0
	-8.96518, 24.9087, // mX + b, phone er, stress 1
	-6.22431, 25.2607, // mX + b, phone er, stress 2
	-6.22431, 25.2607, // mX + b, phone er, stress 3
	-6.22431, 25.2607, // mX + b, phone er, stress 4
	-6.22431, 25.2607, // mX + b, phone er, stress 5
	-6.22431, 25.2607, // mX + b, phone er, stress 6
	-6.22431, 25.2607, // mX + b, phone er, stress 7
	-5.97245, 22.9424, // mX + b, phone ey, stress 0
	-4.23563, 22.3049, // mX + b, phone ey, stress 1
	-3.94325, 23.7013, // mX + b, phone ey, stress 2
	-3.94325, 23.7013, // mX + b, phone ey, stress 3
	-3.94325, 23.7013, // mX + b, phone ey, stress 4
	-3.94325, 23.7013, // mX + b, phone ey, stress 5
	-3.94325, 23.7013, // mX + b, phone ey, stress 6
	-3.94325, 23.7013, // mX + b, phone ey, stress 7
	11.7375, 17.977, // mX + b, phone f, stress 0
	1.1645, 20.3412, // mX + b, phone f, stress 1
	1.1645, 20.3412, // mX + b, phone f, stress 2
	1.1645, 20.3412, // mX + b, phone f, stress 3
	1.1645, 20.3412, // mX + b, phone f, stress 4
	1.1645, 20.3412, // mX + b, phone f, stress 5
	1.1645, 20.3412, // mX + b, phone f, stress 6
	1.1645, 20.3412, // mX + b, phone f, stress 7
	-1.41849, 24.4443, // mX + b, phone g, stress 0
	9.00309, 19.6476, // mX + b, phone g, stress 1
	9.00309, 19.6476, // mX + b, phone g, stress 2
	9.00309, 19.6476, // mX + b, phone g, stress 3
	9.00309, 19.6476, // mX + b, phone g, stress 4
	9.00309, 19.6476, // mX + b, phone g, stress 5
	9.00309, 19.6476, // mX + b, phone g, stress 6
	9.00309, 19.6476, // mX + b, phone g, stress 7
	-7.93125, 27.0524, // mX + b, phone h, stress 0
	-7.53039, 26.8376, // mX + b, phone h, stress 1
	-7.53039, 26.8376, // mX + b, phone h, stress 2
	-7.53039, 26.8376, // mX + b, phone h, stress 3
	-7.53039, 26.8376, // mX + b, phone h, stress 4
	-7.53039, 26.8376, // mX + b, phone h, stress 5
	-7.53039, 26.8376, // mX + b, phone h, stress 6
	-7.53039, 26.8376, // mX + b, phone h, stress 7
	-5.03742, 26.0397, // mX + b, phone ih, stress 0
	-7.91548, 28.3197, // mX + b, phone ih, stress 1
	-7.50773, 25.8849, // mX + b, phone ih, stress 2
	-7.50773, 25.8849, // mX + b, phone ih, stress 3
	-7.50773, 25.8849, // mX + b, phone ih, stress 4
	-7.50773, 25.8849, // mX + b, phone ih, stress 5
	-7.50773, 25.8849, // mX + b, phone ih, stress 6
	-7.50773, 25.8849, // mX + b, phone ih, stress 7
	-0.604853, 23.4089, // mX + b, phone iy, stress 0
	-4.38022, 26.5233, // mX + b, phone iy, stress 1
	-3.94325, 23.7013, // mX + b, phone iy, stress 2
	-3.94325, 23.7013, // mX + b, phone iy, stress 3
	-3.94325, 23.7013, // mX + b, phone iy, stress 4
	-3.94325, 23.7013, // mX + b, phone iy, stress 5
	-3.94325, 23.7013, // mX + b, phone iy, stress 6
	-3.94325, 23.7013, // mX + b, phone iy, stress 7
	6.55593, 20.035, // mX + b, phone jh, stress 0
	12.9415, 17.7295, // mX + b, phone jh, stress 1
	12.9415, 17.7295, // mX + b, phone jh, stress 2
	12.9415, 17.7295, // mX + b, phone jh, stress 3
	12.9415, 17.7295, // mX + b, phone jh, stress 4
	12.9415, 17.7295, // mX + b, phone jh, stress 5
	12.9415, 17.7295, // mX + b, phone jh, stress 6
	12.9415, 17.7295, // mX + b, phone jh, stress 7
	8.8641, 19.434, // mX + b, phone k, stress 0
	9.00309, 19.6476, // mX + b, phone k, stress 1
	9.00309, 19.6476, // mX + b, phone k, stress 2
	9.00309, 19.6476, // mX + b, phone k, stress 3
	9.00309, 19.6476, // mX + b, phone k, stress 4
	9.00309, 19.6476, // mX + b, phone k, stress 5
	9.00309, 19.6476, // mX + b, phone k, stress 6
	9.00309, 19.6476, // mX + b, phone k, stress 7
	-0.748773, 23.3081, // mX + b, phone l, stress 0
	-3.0773, 23.9663, // mX + b, phone l, stress 1
	-3.0773, 23.9663, // mX + b, phone l, stress 2
	-3.0773, 23.9663, // mX + b, phone l, stress 3
	-3.0773, 23.9663, // mX + b, phone l, stress 4
	-3.0773, 23.9663, // mX + b, phone l, stress 5
	-3.0773, 23.9663, // mX + b, phone l, stress 6
	-3.0773, 23.9663, // mX + b, phone l, stress 7
	-1.41795, 24.4182, // mX + b, phone m, stress 0
	0.366615, 23.562, // mX + b, phone m, stress 1
	0.366615, 23.562, // mX + b, phone m, stress 2
	0.366615, 23.562, // mX + b, phone m, stress 3
	0.366615, 23.562, // mX + b, phone m, stress 4
	0.366615, 23.562, // mX + b, phone m, stress 5
	0.366615, 23.562, // mX + b, phone m, stress 6
	0.366615, 23.562, // mX + b, phone m, stress 7
	0.80501, 23.4959, // mX + b, phone n, stress 0
	0.366615, 23.562, // mX + b, phone n, stress 1
	0.366615, 23.562, // mX + b, phone n, stress 2
	0.366615, 23.562, // mX + b, phone n, stress 3
	0.366615, 23.562, // mX + b, phone n, stress 4
	0.366615, 23.562, // mX + b, phone n, stress 5
	0.366615, 23.562, // mX + b, phone n, stress 6
	0.366615, 23.562, // mX + b, phone n, stress 7
	-0.968742, 23.1196, // mX + b, phone nx, stress 0
	0.366615, 23.562, // mX + b, phone nx, stress 1
	0.366615, 23.562, // mX + b, phone nx, stress 2
	0.366615, 23.562, // mX + b, phone nx, stress 3
	0.366615, 23.562, // mX + b, phone nx, stress 4
	0.366615, 23.562, // mX + b, phone nx, stress 5
	0.366615, 23.562, // mX + b, phone nx, stress 6
	0.366615, 23.562, // mX + b, phone nx, stress 7
	1.41784, 21.0575, // mX + b, phone ow, stress 0
	-6.8636, 24.7433, // mX + b, phone ow, stress 1
	-5.87949, 23.7005, // mX + b, phone ow, stress 2
	-5.87949, 23.7005, // mX + b, phone ow, stress 3
	-5.87949, 23.7005, // mX + b, phone ow, stress 4
	-5.87949, 23.7005, // mX + b, phone ow, stress 5
	-5.87949, 23.7005, // mX + b, phone ow, stress 6
	-5.87949, 23.7005, // mX + b, phone ow, stress 7
	-12.5894, 26.6729, // mX + b, phone oy, stress 0
	-5.78924, 24.7796, // mX + b, phone oy, stress 1
	-5.87949, 23.7005, // mX + b, phone oy, stress 2
	-5.87949, 23.7005, // mX + b, phone oy, stress 3
	-5.87949, 23.7005, // mX + b, phone oy, stress 4
	-5.87949, 23.7005, // mX + b, phone oy, stress 5
	-5.87949, 23.7005, // mX + b, phone oy, stress 6
	-5.87949, 23.7005, // mX + b, phone oy, stress 7
	2.88892, 19.8171, // mX + b, phone p, stress 0
	13.5065, 18.6841, // mX + b, phone p, stress 1
	13.5065, 18.6841, // mX + b, phone p, stress 2
	13.5065, 18.6841, // mX + b, phone p, stress 3
	13.5065, 18.6841, // mX + b, phone p, stress 4
	13.5065, 18.6841, // mX + b, phone p, stress 5
	13.5065, 18.6841, // mX + b, phone p, stress 6
	13.5065, 18.6841, // mX + b, phone p, stress 7
	-4.72797, 24.3829, // mX + b, phone r, stress 0
	-3.0773, 23.9663, // mX + b, phone r, stress 1
	-3.0773, 23.9663, // mX + b, phone r, stress 2
	-3.0773, 23.9663, // mX + b, phone r, stress 3
	-3.0773, 23.9663, // mX + b, phone r, stress 4
	-3.0773, 23.9663, // mX + b, phone r, stress 5
	-3.0773, 23.9663, // mX + b, phone r, stress 6
	-3.0773, 23.9663, // mX + b, phone r, stress 7
	10.4813, 16.1258, // mX + b, phone s, stress 0
	14.0847, 15.9745, // mX + b, phone s, stress 1
	14.0847, 15.9745, // mX + b, phone s, stress 2
	14.0847, 15.9745, // mX + b, phone s, stress 3
	14.0847, 15.9745, // mX + b, phone s, stress 4
	14.0847, 15.9745, // mX + b, phone s, stress 5
	14.0847, 15.9745, // mX + b, phone s, stress 6
	14.0847, 15.9745, // mX + b, phone s, stress 7
	1.43515, 18.788, // mX + b, phone sh, stress 0
	8.65489, 18.1241, // mX + b, phone sh, stress 1
	8.65489, 18.1241, // mX + b, phone sh, stress 2
	8.65489, 18.1241, // mX + b, phone sh, stress 3
	8.65489, 18.1241, // mX + b, phone sh, stress 4
	8.65489, 18.1241, // mX + b, phone sh, stress 5
	8.65489, 18.1241, // mX + b, phone sh, stress 6
	8.65489, 18.1241, // mX + b, phone sh, stress 7
	10.3479, 19.5789, // mX + b, phone t, stress 0
	7.77664, 21.4693, // mX + b, phone t, stress 1
	7.77664, 21.4693, // mX + b, phone t, stress 2
	7.77664, 21.4693, // mX + b, phone t, stress 3
	7.77664, 21.4693, // mX + b, phone t, stress 4
	7.77664, 21.4693, // mX + b, phone t, stress 5
	7.77664, 21.4693, // mX + b, phone t, stress 6
	7.77664, 21.4693, // mX + b, phone t, stress 7
	-3.60016, 22.3704, // mX + b, phone th, stress 0
	-4.58481, 24.6105, // mX + b, phone th, stress 1
	-4.58481, 24.6105, // mX + b, phone th, stress 2
	-4.58481, 24.6105, // mX + b, phone th, stress 3
	-4.58481, 24.6105, // mX + b, phone th, stress 4
	-4.58481, 24.6105, // mX + b, phone th, stress 5
	-4.58481, 24.6105, // mX + b, phone th, stress 6
	-4.58481, 24.6105, // mX + b, phone th, stress 7
	-6.64591, 24.8647, // mX + b, phone uh, stress 0
	-13.946, 30.7809, // mX + b, phone uh, stress 1
	-6.22431, 25.2607, // mX + b, phone uh, stress 2
	-6.22431, 25.2607, // mX + b, phone uh, stress 3
	-6.22431, 25.2607, // mX + b, phone uh, stress 4
	-6.22431, 25.2607, // mX + b, phone uh, stress 5
	-6.22431, 25.2607, // mX + b, phone uh, stress 6
	-6.22431, 25.2607, // mX + b, phone uh, stress 7
	-4.81848, 26.6955, // mX + b, phone uw, stress 0
	-7.00751, 29.3907, // mX + b, phone uw, stress 1
	-6.22431, 25.2607, // mX + b, phone uw, stress 2
	-6.22431, 25.2607, // mX + b, phone uw, stress 3
	-6.22431, 25.2607, // mX + b, phone uw, stress 4
	-6.22431, 25.2607, // mX + b, phone uw, stress 5
	-6.22431, 25.2607, // mX + b, phone uw, stress 6
	-6.22431, 25.2607, // mX + b, phone uw, stress 7
	-5.32696, 22.506, // mX + b, phone v, stress 0
	1.1645, 20.3412, // mX + b, phone v, stress 1
	1.1645, 20.3412, // mX + b, phone v, stress 2
	1.1645, 20.3412, // mX + b, phone v, stress 3
	1.1645, 20.3412, // mX + b, phone v, stress 4
	1.1645, 20.3412, // mX + b, phone v, stress 5
	1.1645, 20.3412, // mX + b, phone v, stress 6
	1.1645, 20.3412, // mX + b, phone v, stress 7
	-2.48096, 24.3214, // mX + b, phone w, stress 0
	-5.08232, 26.4506, // mX + b, phone w, stress 1
	-5.08232, 26.4506, // mX + b, phone w, stress 2
	-5.08232, 26.4506, // mX + b, phone w, stress 3
	-5.08232, 26.4506, // mX + b, phone w, stress 4
	-5.08232, 26.4506, // mX + b, phone w, stress 5
	-5.08232, 26.4506, // mX + b, phone w, stress 6
	-5.08232, 26.4506, // mX + b, phone w, stress 7
	-7.56662, 29.2678, // mX + b, phone y, stress 0
	-5.08232, 26.4506, // mX + b, phone y, stress 1
	-5.08232, 26.4506, // mX + b, phone y, stress 2
	-5.08232, 26.4506, // mX + b, phone y, stress 3
	-5.08232, 26.4506, // mX + b, phone y, stress 4
	-5.08232, 26.4506, // mX + b, phone y, stress 5
	-5.08232, 26.4506, // mX + b, phone y, stress 6
	-5.08232, 26.4506, // mX + b, phone y, stress 7
	14.9773, 16.073, // mX + b, phone z, stress 0
	14.0847, 15.9745, // mX + b, phone z, stress 1
	14.0847, 15.9745, // mX + b, phone z, stress 2
	14.0847, 15.9745, // mX + b, phone z, stress 3
	14.0847, 15.9745, // mX + b, phone z, stress 4
	14.0847, 15.9745, // mX + b, phone z, stress 5
	14.0847, 15.9745, // mX + b, phone z, stress 6
	14.0847, 15.9745, // mX + b, phone z, stress 7
	1.49705, 21.2043, // mX + b, phone zh, stress 0
	8.65489, 18.1241, // mX + b, phone zh, stress 1
	8.65489, 18.1241, // mX + b, phone zh, stress 2
	8.65489, 18.1241, // mX + b, phone zh, stress 3
	8.65489, 18.1241, // mX + b, phone zh, stress 4
	8.65489, 18.1241, // mX + b, phone zh, stress 5
	8.65489, 18.1241, // mX + b, phone zh, stress 6
	8.65489, 18.1241, // mX + b, phone zh, stress 7
};


static float gafUnitScoreAccentPerPhone[NUMLEXENGLISHPHONE * MAXSTRESSES * 2] = {
   // positive (target 2x energy), negative
	4.92223, 16.3825, // phone <s>, stress 0
	4.92223, 16.3825, // phone <s>, stress 1
	4.92223, 16.3825, // phone <s>, stress 2
	4.92223, 16.3825, // phone <s>, stress 3
	4.92223, 16.3825, // phone <s>, stress 4
	4.92223, 16.3825, // phone <s>, stress 5
	4.92223, 16.3825, // phone <s>, stress 6
	4.92223, 16.3825, // phone <s>, stress 7
	6.81805, 8.12642, // phone aa, stress 0
	11.3398, 20.3718, // phone aa, stress 1
	3.40118, 17.434, // phone aa, stress 2
	3.40118, 17.434, // phone aa, stress 3
	3.40118, 17.434, // phone aa, stress 4
	3.40118, 17.434, // phone aa, stress 5
	3.40118, 17.434, // phone aa, stress 6
	3.40118, 17.434, // phone aa, stress 7
	0.222309, 32.4229, // phone ae, stress 0
	5.00728, 21.1789, // phone ae, stress 1
	3.40118, 17.434, // phone ae, stress 2
	3.40118, 17.434, // phone ae, stress 3
	3.40118, 17.434, // phone ae, stress 4
	3.40118, 17.434, // phone ae, stress 5
	3.40118, 17.434, // phone ae, stress 6
	3.40118, 17.434, // phone ae, stress 7
	1.16734e-005, 13.8615, // phone ah, stress 0
	2.69371, 19.3342, // phone ah, stress 1
	0.0008996, 19.5594, // phone ah, stress 2
	0.0008996, 19.5594, // phone ah, stress 3
	0.0008996, 19.5594, // phone ah, stress 4
	0.0008996, 19.5594, // phone ah, stress 5
	0.0008996, 19.5594, // phone ah, stress 6
	0.0008996, 19.5594, // phone ah, stress 7
	11.3352, 22.8844, // phone ao, stress 0
	1.28349e-005, 33.8628, // phone ao, stress 1
	0.0008996, 19.5594, // phone ao, stress 2
	0.0008996, 19.5594, // phone ao, stress 3
	0.0008996, 19.5594, // phone ao, stress 4
	0.0008996, 19.5594, // phone ao, stress 5
	0.0008996, 19.5594, // phone ao, stress 6
	0.0008996, 19.5594, // phone ao, stress 7
	2.6684, 10.3821, // phone aw, stress 0
	5.86297, 19.7007, // phone aw, stress 1
	2.6684, 10.3821, // phone aw, stress 2
	2.6684, 10.3821, // phone aw, stress 3
	2.6684, 10.3821, // phone aw, stress 4
	2.6684, 10.3821, // phone aw, stress 5
	2.6684, 10.3821, // phone aw, stress 6
	2.6684, 10.3821, // phone aw, stress 7
	0.0008996, 19.5594, // phone ax, stress 0
	0.0008996, 19.5594, // phone ax, stress 1
	0.0008996, 19.5594, // phone ax, stress 2
	0.0008996, 19.5594, // phone ax, stress 3
	0.0008996, 19.5594, // phone ax, stress 4
	0.0008996, 19.5594, // phone ax, stress 5
	0.0008996, 19.5594, // phone ax, stress 6
	0.0008996, 19.5594, // phone ax, stress 7
	4.57618, 24.0525, // phone ay, stress 0
	6.70793, 23.5304, // phone ay, stress 1
	2.38295, 27.3398, // phone ay, stress 2
	2.38295, 27.3398, // phone ay, stress 3
	2.38295, 27.3398, // phone ay, stress 4
	2.38295, 27.3398, // phone ay, stress 5
	2.38295, 27.3398, // phone ay, stress 6
	2.38295, 27.3398, // phone ay, stress 7
	3.50761, 5.86534, // phone b, stress 0
	8.34419, 8.00099, // phone b, stress 1
	8.34419, 8.00099, // phone b, stress 2
	8.34419, 8.00099, // phone b, stress 3
	8.34419, 8.00099, // phone b, stress 4
	8.34419, 8.00099, // phone b, stress 5
	8.34419, 8.00099, // phone b, stress 6
	8.34419, 8.00099, // phone b, stress 7
	13.4165, 13.3398, // phone ch, stress 0
	14.0517, 14.1763, // phone ch, stress 1
	14.0517, 14.1763, // phone ch, stress 2
	14.0517, 14.1763, // phone ch, stress 3
	14.0517, 14.1763, // phone ch, stress 4
	14.0517, 14.1763, // phone ch, stress 5
	14.0517, 14.1763, // phone ch, stress 6
	14.0517, 14.1763, // phone ch, stress 7
	16.7603, 0.00588062, // phone d, stress 0
	3.57578, 4.88906, // phone d, stress 1
	3.57578, 4.88906, // phone d, stress 2
	3.57578, 4.88906, // phone d, stress 3
	3.57578, 4.88906, // phone d, stress 4
	3.57578, 4.88906, // phone d, stress 5
	3.57578, 4.88906, // phone d, stress 6
	3.57578, 4.88906, // phone d, stress 7
	3.01282e-005, 26.1701, // phone dh, stress 0
	0.00969768, 17.9814, // phone dh, stress 1
	0.00969768, 17.9814, // phone dh, stress 2
	0.00969768, 17.9814, // phone dh, stress 3
	0.00969768, 17.9814, // phone dh, stress 4
	0.00969768, 17.9814, // phone dh, stress 5
	0.00969768, 17.9814, // phone dh, stress 6
	0.00969768, 17.9814, // phone dh, stress 7
	0.0493023, 14.6256, // phone eh, stress 0
	13.5743, 16.8986, // phone eh, stress 1
	3.40118, 17.434, // phone eh, stress 2
	3.40118, 17.434, // phone eh, stress 3
	3.40118, 17.434, // phone eh, stress 4
	3.40118, 17.434, // phone eh, stress 5
	3.40118, 17.434, // phone eh, stress 6
	3.40118, 17.434, // phone eh, stress 7
	1.64614, 23.0456, // phone er, stress 0
	6.16139, 10.7513, // phone er, stress 1
	0.0008996, 19.5594, // phone er, stress 2
	0.0008996, 19.5594, // phone er, stress 3
	0.0008996, 19.5594, // phone er, stress 4
	0.0008996, 19.5594, // phone er, stress 5
	0.0008996, 19.5594, // phone er, stress 6
	0.0008996, 19.5594, // phone er, stress 7
	0.449622, 10.6908, // phone ey, stress 0
	4.53973, 26.1943, // phone ey, stress 1
	2.38295, 27.3398, // phone ey, stress 2
	2.38295, 27.3398, // phone ey, stress 3
	2.38295, 27.3398, // phone ey, stress 4
	2.38295, 27.3398, // phone ey, stress 5
	2.38295, 27.3398, // phone ey, stress 6
	2.38295, 27.3398, // phone ey, stress 7
	7.12159, 9.54105, // phone f, stress 0
	8.06519, 13.4119, // phone f, stress 1
	8.06519, 13.4119, // phone f, stress 2
	8.06519, 13.4119, // phone f, stress 3
	8.06519, 13.4119, // phone f, stress 4
	8.06519, 13.4119, // phone f, stress 5
	8.06519, 13.4119, // phone f, stress 6
	8.06519, 13.4119, // phone f, stress 7
	6.42409, 14.586, // phone g, stress 0
	21.4378, 2.02379, // phone g, stress 1
	21.4378, 2.02379, // phone g, stress 2
	21.4378, 2.02379, // phone g, stress 3
	21.4378, 2.02379, // phone g, stress 4
	21.4378, 2.02379, // phone g, stress 5
	21.4378, 2.02379, // phone g, stress 6
	21.4378, 2.02379, // phone g, stress 7
	1.87441, 15.4831, // phone h, stress 0
	1.8855, 15.4859, // phone h, stress 1
	1.8855, 15.4859, // phone h, stress 2
	1.8855, 15.4859, // phone h, stress 3
	1.8855, 15.4859, // phone h, stress 4
	1.8855, 15.4859, // phone h, stress 5
	1.8855, 15.4859, // phone h, stress 6
	1.8855, 15.4859, // phone h, stress 7
	0.00605223, 11.7581, // phone ih, stress 0
	3.83486, 16.4553, // phone ih, stress 1
	3.40118, 17.434, // phone ih, stress 2
	3.40118, 17.434, // phone ih, stress 3
	3.40118, 17.434, // phone ih, stress 4
	3.40118, 17.434, // phone ih, stress 5
	3.40118, 17.434, // phone ih, stress 6
	3.40118, 17.434, // phone ih, stress 7
	0.00414705, 34.2839, // phone iy, stress 0
	7.77767, 20.8998, // phone iy, stress 1
	2.38295, 27.3398, // phone iy, stress 2
	2.38295, 27.3398, // phone iy, stress 3
	2.38295, 27.3398, // phone iy, stress 4
	2.38295, 27.3398, // phone iy, stress 5
	2.38295, 27.3398, // phone iy, stress 6
	2.38295, 27.3398, // phone iy, stress 7
	16.1022, 15.2645, // phone jh, stress 0
	14.0517, 14.1763, // phone jh, stress 1
	14.0517, 14.1763, // phone jh, stress 2
	14.0517, 14.1763, // phone jh, stress 3
	14.0517, 14.1763, // phone jh, stress 4
	14.0517, 14.1763, // phone jh, stress 5
	14.0517, 14.1763, // phone jh, stress 6
	14.0517, 14.1763, // phone jh, stress 7
	18.1102, 0.0039412, // phone k, stress 0
	21.4378, 2.02379, // phone k, stress 1
	21.4378, 2.02379, // phone k, stress 2
	21.4378, 2.02379, // phone k, stress 3
	21.4378, 2.02379, // phone k, stress 4
	21.4378, 2.02379, // phone k, stress 5
	21.4378, 2.02379, // phone k, stress 6
	21.4378, 2.02379, // phone k, stress 7
	1.2145, 29.1647, // phone l, stress 0
	2.71256, 29.3865, // phone l, stress 1
	2.71256, 29.3865, // phone l, stress 2
	2.71256, 29.3865, // phone l, stress 3
	2.71256, 29.3865, // phone l, stress 4
	2.71256, 29.3865, // phone l, stress 5
	2.71256, 29.3865, // phone l, stress 6
	2.71256, 29.3865, // phone l, stress 7
	12.8016, 6.32864, // phone m, stress 0
	8.4229, 9.9569, // phone m, stress 1
	8.4229, 9.9569, // phone m, stress 2
	8.4229, 9.9569, // phone m, stress 3
	8.4229, 9.9569, // phone m, stress 4
	8.4229, 9.9569, // phone m, stress 5
	8.4229, 9.9569, // phone m, stress 6
	8.4229, 9.9569, // phone m, stress 7
	5.21906, 9.52053, // phone n, stress 0
	8.4229, 9.9569, // phone n, stress 1
	8.4229, 9.9569, // phone n, stress 2
	8.4229, 9.9569, // phone n, stress 3
	8.4229, 9.9569, // phone n, stress 4
	8.4229, 9.9569, // phone n, stress 5
	8.4229, 9.9569, // phone n, stress 6
	8.4229, 9.9569, // phone n, stress 7
	11.1214, 10.4036, // phone nx, stress 0
	8.4229, 9.9569, // phone nx, stress 1
	8.4229, 9.9569, // phone nx, stress 2
	8.4229, 9.9569, // phone nx, stress 3
	8.4229, 9.9569, // phone nx, stress 4
	8.4229, 9.9569, // phone nx, stress 5
	8.4229, 9.9569, // phone nx, stress 6
	8.4229, 9.9569, // phone nx, stress 7
	0.0630926, 6.665, // phone ow, stress 0
	3.30773, 9.41613, // phone ow, stress 1
	2.6684, 10.3821, // phone ow, stress 2
	2.6684, 10.3821, // phone ow, stress 3
	2.6684, 10.3821, // phone ow, stress 4
	2.6684, 10.3821, // phone ow, stress 5
	2.6684, 10.3821, // phone ow, stress 6
	2.6684, 10.3821, // phone ow, stress 7
	2.6684, 10.3821, // phone oy, stress 0
	10.1226, 1.79732, // phone oy, stress 1
	2.6684, 10.3821, // phone oy, stress 2
	2.6684, 10.3821, // phone oy, stress 3
	2.6684, 10.3821, // phone oy, stress 4
	2.6684, 10.3821, // phone oy, stress 5
	2.6684, 10.3821, // phone oy, stress 6
	2.6684, 10.3821, // phone oy, stress 7
	3.69905, 7.63299, // phone p, stress 0
	8.34419, 8.00099, // phone p, stress 1
	8.34419, 8.00099, // phone p, stress 2
	8.34419, 8.00099, // phone p, stress 3
	8.34419, 8.00099, // phone p, stress 4
	8.34419, 8.00099, // phone p, stress 5
	8.34419, 8.00099, // phone p, stress 6
	8.34419, 8.00099, // phone p, stress 7
	4.26287, 29.6628, // phone r, stress 0
	2.71256, 29.3865, // phone r, stress 1
	2.71256, 29.3865, // phone r, stress 2
	2.71256, 29.3865, // phone r, stress 3
	2.71256, 29.3865, // phone r, stress 4
	2.71256, 29.3865, // phone r, stress 5
	2.71256, 29.3865, // phone r, stress 6
	2.71256, 29.3865, // phone r, stress 7
	8.99458, 10.1075, // phone s, stress 0
	6.42765, 18.1234, // phone s, stress 1
	6.42765, 18.1234, // phone s, stress 2
	6.42765, 18.1234, // phone s, stress 3
	6.42765, 18.1234, // phone s, stress 4
	6.42765, 18.1234, // phone s, stress 5
	6.42765, 18.1234, // phone s, stress 6
	6.42765, 18.1234, // phone s, stress 7
	14.8586, 12.0763, // phone sh, stress 0
	14.0651, 13.2239, // phone sh, stress 1
	14.0651, 13.2239, // phone sh, stress 2
	14.0651, 13.2239, // phone sh, stress 3
	14.0651, 13.2239, // phone sh, stress 4
	14.0651, 13.2239, // phone sh, stress 5
	14.0651, 13.2239, // phone sh, stress 6
	14.0651, 13.2239, // phone sh, stress 7
	0.00250217, 9.00209, // phone t, stress 0
	3.57578, 4.88906, // phone t, stress 1
	3.57578, 4.88906, // phone t, stress 2
	3.57578, 4.88906, // phone t, stress 3
	3.57578, 4.88906, // phone t, stress 4
	3.57578, 4.88906, // phone t, stress 5
	3.57578, 4.88906, // phone t, stress 6
	3.57578, 4.88906, // phone t, stress 7
	7.81271e-005, 11.7919, // phone th, stress 0
	0.00969768, 17.9814, // phone th, stress 1
	0.00969768, 17.9814, // phone th, stress 2
	0.00969768, 17.9814, // phone th, stress 3
	0.00969768, 17.9814, // phone th, stress 4
	0.00969768, 17.9814, // phone th, stress 5
	0.00969768, 17.9814, // phone th, stress 6
	0.00969768, 17.9814, // phone th, stress 7
	7.23412, 58.9406, // phone uh, stress 0
	1.45982, 15.6491, // phone uh, stress 1
	0.0008996, 19.5594, // phone uh, stress 2
	0.0008996, 19.5594, // phone uh, stress 3
	0.0008996, 19.5594, // phone uh, stress 4
	0.0008996, 19.5594, // phone uh, stress 5
	0.0008996, 19.5594, // phone uh, stress 6
	0.0008996, 19.5594, // phone uh, stress 7
	3.07851e-005, 4.11587, // phone uw, stress 0
	1.26142e-005, 15.0376, // phone uw, stress 1
	0.0008996, 19.5594, // phone uw, stress 2
	0.0008996, 19.5594, // phone uw, stress 3
	0.0008996, 19.5594, // phone uw, stress 4
	0.0008996, 19.5594, // phone uw, stress 5
	0.0008996, 19.5594, // phone uw, stress 6
	0.0008996, 19.5594, // phone uw, stress 7
	8.80714, 18.8967, // phone v, stress 0
	8.06519, 13.4119, // phone v, stress 1
	8.06519, 13.4119, // phone v, stress 2
	8.06519, 13.4119, // phone v, stress 3
	8.06519, 13.4119, // phone v, stress 4
	8.06519, 13.4119, // phone v, stress 5
	8.06519, 13.4119, // phone v, stress 6
	8.06519, 13.4119, // phone v, stress 7
	3.0442, 18.1101, // phone w, stress 0
	3.18505, 18.4068, // phone w, stress 1
	3.18505, 18.4068, // phone w, stress 2
	3.18505, 18.4068, // phone w, stress 3
	3.18505, 18.4068, // phone w, stress 4
	3.18505, 18.4068, // phone w, stress 5
	3.18505, 18.4068, // phone w, stress 6
	3.18505, 18.4068, // phone w, stress 7
	3.15885, 19.3717, // phone y, stress 0
	3.18505, 18.4068, // phone y, stress 1
	3.18505, 18.4068, // phone y, stress 2
	3.18505, 18.4068, // phone y, stress 3
	3.18505, 18.4068, // phone y, stress 4
	3.18505, 18.4068, // phone y, stress 5
	3.18505, 18.4068, // phone y, stress 6
	3.18505, 18.4068, // phone y, stress 7
	0.602724, 33.1277, // phone z, stress 0
	6.42765, 18.1234, // phone z, stress 1
	6.42765, 18.1234, // phone z, stress 2
	6.42765, 18.1234, // phone z, stress 3
	6.42765, 18.1234, // phone z, stress 4
	6.42765, 18.1234, // phone z, stress 5
	6.42765, 18.1234, // phone z, stress 6
	6.42765, 18.1234, // phone z, stress 7
	3.17257, 25.8514, // phone zh, stress 0
	14.0651, 13.2239, // phone zh, stress 1
	14.0651, 13.2239, // phone zh, stress 2
	14.0651, 13.2239, // phone zh, stress 3
	14.0651, 13.2239, // phone zh, stress 4
	14.0651, 13.2239, // phone zh, stress 5
	14.0651, 13.2239, // phone zh, stress 6
	14.0651, 13.2239, // phone zh, stress 7
};

/*************************************************************************************
CProgressWaveTTSToWave::TTSWaveData - As per standard callback
*/
BOOL CProgressWaveTTSToWave::TTSWaveData (PCM3DWave pWave)
{
   if (pWave) {
      if (!m_pWave->AppendWave (pWave))
         return FALSE;
   }

   return TRUE;
}


/*************************************************************************************
CProgressWaveTTSToWave::TTSSpeedVolume - As per standard callback
*/
BOOL CProgressWaveTTSToWave::TTSSpeedVolume (fp *pafSpeed, fp *pafVolume)
{
   return TRUE;
}

/*************************************************************************************
UnitScoreScoreCombine - Combines the UnitScoreXXX values together using
euclidean distance instead of manhattan distance. Do this because many of the
distances (pitch, duration, energy) are correlated.

inputs
   fp             fScorePitch - Pitch score
   fp             fScoreDuration - Duration score
   fp             fScoreEnergy - Energy score
returns
   fp - combined score
*/
__inline fp UnitScoreCombine (fp fScorePitch, fp fScoreDuration, fp fScoreEnergy, fp fScoreFunc)
{
   return sqrt(
      fScorePitch * fScorePitch +
      fScoreDuration * fScoreDuration +
      fScoreEnergy * fScoreEnergy +
      fScoreFunc * fScoreFunc);
}


/*************************************************************************************
UnitScoreScoreLRMismatch - Returns unit-score for non-contiguous units

inputs
   PCMTTS         pTTS - If not NULL, used to get voice-specific target costs
   DWORD          dwPhoneCenter - Phoneme at the center context
   DWORD          dwPhoneLR - Phoneme at the left/right context (see fRightContext)
   PCMLexicon     pLex - Lexicon
   BOOL           fRightContext - TRUE if right context, FALSE if left context
   DWORD          dwMismatch - 0 for exact match, 1 for trihonematch, 2 for bad stress, 3 right group but bad phone, 4 bad group, 5 bad megagroup
returns
   fp - Error
*/
__inline fp UnitScoreScoreLRMismatch (PCMTTS pTTS, DWORD dwPhoneCenter, DWORD dwPhoneLR, PCMLexicon pLex, BOOL fRightContext, DWORD dwMismatch)
{
   DWORD dwGroupCenter = pLex->PhonemeToGroup (dwPhoneCenter);
   DWORD dwMegaGroupLR = pLex->PhonemeToMegaGroup (dwPhoneLR);

   PTTSTARGETCOSTS pTarget = (pTTS && (pTTS->m_memTTSTARGETCOSTS.m_dwCurPosn == sizeof(TTSTARGETCOSTS))) ?
      (PTTSTARGETCOSTS)pTTS->m_memTTSTARGETCOSTS.p : NULL;
   float *pafUnitScoreLRMismatch = pTarget ? &pTarget->afUnitScoreLRMismatch[0][0][0][0] : &gafUnitScoreLRMismatch[0];

   return pafUnitScoreLRMismatch[
      (dwGroupCenter * PIS_PHONEMEGAGROUPNUM * 2 * 6) +
      (dwMegaGroupLR * 2 * 6) +
      (fRightContext ? (1*6) : 0) +
      dwMismatch];
}


/*************************************************************************************
UnitScoreScoreNonContiguous - Returns unit-score for non-contiguous units

inputs
   PCMTTS         pTTS - If not NULL, used to get voice-specific target costs
   DWORD       dwLeftPhone - Left phoneme
   DWORD       dwRightPhone - RIght phoneme
   BOOL        fMidPhone - If TRUE then dwLeftPhone == dwRightPhone, and this is a mid-phone split
   PCMLexicon  pLex - Lexicon
returns
   fp - Error
*/
__inline fp UnitScoreScoreNonContiguous (PCMTTS pTTS, DWORD dwLeftPhone, DWORD dwRightPhone, BOOL fMidPhone, PCMLexicon pLex)
{
   DWORD dwLeftPhoneGroup = pLex->PhonemeToGroup(dwLeftPhone);
   DWORD dwRightPhoneGroup = pLex->PhonemeToGroup(dwRightPhone);

   PTTSTARGETCOSTS pTarget = (pTTS && (pTTS->m_memTTSTARGETCOSTS.m_dwCurPosn == sizeof(TTSTARGETCOSTS))) ?
      (PTTSTARGETCOSTS)pTTS->m_memTTSTARGETCOSTS.p : NULL;
   float *pafUnitScoreNonContiguousMid = pTarget ? &pTarget->afUnitScoreNonContiguousMid[0] : &gafUnitScoreNonContiguousMid[0];
   float *pafUnitScoreNonContiguous = pTarget ? &pTarget->afUnitScoreNonContiguous[0][0] : &gafUnitScoreNonContiguous[0];

   if (fMidPhone) {
      _ASSERTE (dwLeftPhoneGroup == dwRightPhoneGroup);
      return pafUnitScoreNonContiguousMid[dwLeftPhoneGroup];
   }
   else
      return pafUnitScoreNonContiguous[dwLeftPhoneGroup * PIS_PHONEGROUPNUM + dwRightPhoneGroup];
}


/*************************************************************************************
JoinHalfWindowSize - Calculated the value for join half window size.

inputs
   DWORD          dwUnits - Number of units in the TTS voice
returns
   DWORD - Half window size
*/
DWORD JoinHalfWindowSize (DWORD dwUnits)
{
   if (dwUnits <= 16000)
      return JOINHALFWINDOWSIZE8000;
   else if (dwUnits <= 64000)
      return JOINHALFWINDOWSIZE32000;
   else
      return JOINHALFWINDOWSIZE128000;
}

/*************************************************************************************
UnitScoreJoinEstimate - Estimate the cost of a join.

inputs
   PCMTTS         pTTS - If not NULL, used to get voice-specific target costs
   DWORD       dwPhoneLeft - Left phoneme
   DWORD       dwPhoneRight - Right phoneme
   BOOL        fMidPhone - Set to TRUE if middle of phoneme, FALSE if dwPhoneLeft and dwPhoneRight straddle boundary
   PCMLexicon  pLex - Lexicon
returns
   fp - Total error over all frames.
*/
__inline fp UnitScoreJoinEstimate (PCMTTS pTTS, DWORD dwPhoneLeft, DWORD dwPhoneRight, BOOL fMidPhone, PCMLexicon pLex)
{
   return
      4.0 * UnitScoreScoreNonContiguous (pTTS, dwPhoneLeft, dwPhoneRight, fMidPhone, pLex) +  // match, but not contiguous
      (JOINHALFWINDOWSIZE(pTTS->m_dwUnits) - 2) / 2.0 * UnitScoreScoreLRMismatch (pTTS, dwPhoneLeft, dwPhoneRight, pLex, TRUE, 1) + // mismatch from left to right
      (JOINHALFWINDOWSIZE(pTTS->m_dwUnits) - 2) / 2.0 * UnitScoreScoreLRMismatch (pTTS, dwPhoneRight, dwPhoneLeft, pLex, FALSE, 1); // mismatch from right to right
}

/*************************************************************************************
UnitScorePitch - Returns unit-score based on pitch (per octave).

inputs
   PCMTTS         pTTS - If not NULL, used to get voice-specific target costs
   DWORD          dwPhone - Phoneme
   PCMLexicon     pLex - Lexicon
   BOOL           fHigher - TRUE if the target pitch is higher than the original unit pitch
   BOOL           fFullPCM - If full PCM then greatly increase this since large pitch error with PCM
   fp             fPitchConfidence - Pitch confidence, from 0.0 (unconfident) to 1.0 (confident).
                     Only used if fFullPCM true. If -1.0 then won't use.
returns
   fp - Error per octave
*/
__inline fp UnitScorePitch (PCMTTS pTTS, DWORD dwPhone, PCMLexicon pLex, BOOL fHigher, BOOL fFullPCM,
                            fp fPitchConfidence)
{
   DWORD dwStress;
   DWORD dwEnglishPhone = pLex->PhonemeToEnglishPhone (dwPhone, &dwStress);

   DWORD dwIndexPSOLAPitch = ((dwEnglishPhone * MAXSTRESSES + dwStress) * 2);
   DWORD dwIndex = dwIndexPSOLAPitch + (fHigher ? 0 : 1);
         // BUGFIX - Had (fHigher ? 1 : 0), which was opposite

   PTTSTARGETCOSTS pTarget = (pTTS && (pTTS->m_memTTSTARGETCOSTS.m_dwCurPosn == sizeof(TTSTARGETCOSTS))) ?
      (PTTSTARGETCOSTS)pTTS->m_memTTSTARGETCOSTS.p : NULL;

   float *pafUnitScorePitchPerPhone = pTarget ? &pTarget->afUnitScorePitchPerPhone[0][0][0] : &gafUnitScorePitchPerPhone[0];
   //float *pafUnitScorePCMPitchPSOLA = pTarget ? &pTarget->afUnitScorePCMPitchPSOLAPhone[0][0][0] : &gafUnitScorePCMPitchPSOLAPhone[0];
   //float *pafUnitScorePCMPitch = pTarget ? &pTarget->afUnitScorePCMPitchPhone[0][0][0] : &gafUnitScorePCMPitchPhone[0];

   fp fScore = pafUnitScorePitchPerPhone[dwIndex];
         //+ pafUnitScorePCMPitchPSOLA[dwIndex] * 2.0 /* since half octave*/ +
         //EXTRAPSOLAPITCHPENALTY * pafUnitScorePCMPitch[dwIndex];
            // BUGFIX - Changed meaning of EXTRAPSOLAPITCHPENALITY slightly

   if (fFullPCM && (fPitchConfidence >= 0.0)) {
      float *pafUnitScorePSOLAPitch = pTarget ? &pTarget->afUnitScorePitchPSOLAPitchCertaintyPerPhone[0][0][0] : &gafUnitScorePitchPSOLAPitchCertaintyPerPhone[0];
      fScore += (fPitchConfidence * pafUnitScorePSOLAPitch[dwIndexPSOLAPitch] + pafUnitScorePSOLAPitch[dwIndexPSOLAPitch+1]) * 2.0;
         // BUGFIX - Added *2.0 because only calculated for half an octave
   }


   return fScore;
}


/*************************************************************************************
UnitScoreEnergy - Returns unit-score based on Energy (per doubling of energy).

inputs
   PCMTTS         pTTS - If not NULL, used to get voice-specific target costs
   DWORD          dwPhone - Phoneme
   PCMLexicon     pLex - Lexicon
   BOOL           fHigher - TRUE if the target energy is higher than the original unit energy
returns
   fp - Error per octave
*/
fp UnitScoreEnergy (PCMTTS pTTS, DWORD dwPhone, PCMLexicon pLex, BOOL fHigher)
{
   PTTSTARGETCOSTS pTarget = (pTTS && (pTTS->m_memTTSTARGETCOSTS.m_dwCurPosn == sizeof(TTSTARGETCOSTS))) ?
      (PTTSTARGETCOSTS)pTTS->m_memTTSTARGETCOSTS.p : NULL;
   float *pafUnitScoreEnergyPerPhone = pTarget ? &pTarget->afUnitScoreEnergyPerPhone[0][0][0] : &gafUnitScoreEnergyPerPhone[0];

   DWORD dwStress;
   DWORD dwEnglishPhone = pLex->PhonemeToEnglishPhone (dwPhone, &dwStress);

   DWORD dwIndex = ((dwEnglishPhone * MAXSTRESSES + dwStress) * 2) + (fHigher ? 0 : 1);
         // BUGFIX - Had (fHigher ? 1 : 0), which was opposite

   return pafUnitScoreEnergyPerPhone[dwIndex];
      // BUGFIX - Had (fHigher ? 1 : 0)
}


/*************************************************************************************
UnitScoreAccent - Returns unit-score based on accent difference.

inputs
   PCMTTS         pTTS - If not NULL, used to get voice-specific target costs
   DWORD          dwPhone - Phoneme
   PCMLexicon     pLex - Lexicon
   BOOL           fHigher - TRUE if the target accent is higher than the original unit accent
returns
   fp - Error per octave
*/
fp UnitScoreAccent (PCMTTS pTTS, DWORD dwPhone, PCMLexicon pLex, BOOL fHigher)
{
   PTTSTARGETCOSTS pTarget = (pTTS && (pTTS->m_memTTSTARGETCOSTS.m_dwCurPosn == sizeof(TTSTARGETCOSTS))) ?
      (PTTSTARGETCOSTS)pTTS->m_memTTSTARGETCOSTS.p : NULL;
   float *pafUnitScoreAccentPerPhone = pTarget ? &pTarget->afUnitScoreAccentPerPhone[0][0][0] : &gafUnitScoreAccentPerPhone[0];

   DWORD dwStress;
   DWORD dwEnglishPhone = pLex->PhonemeToEnglishPhone (dwPhone, &dwStress);

   DWORD dwIndex = ((dwEnglishPhone * MAXSTRESSES + dwStress) * 2) + (fHigher ? 0 : 1);
         // BUGFIX - Had (fHigher ? 1 : 0), which was opposite

   return pafUnitScoreAccentPerPhone[dwIndex];
      // BUGFIX - Had (fHigher ? 1 : 0)
}


/*************************************************************************************
UnitScoreDuration - Returns unit-score based on duration (per doubling of duration).

inputs
   PCMTTS         pTTS - If not NULL, used to get voice-specific target costs
   DWORD          dwPhone - Phoneme
   PCMLexicon     pLex - Lexicon
   BOOL           fHigher - TRUE if the target duration is higher than the original unit duration
   BOOL           fFullPCM - If full PCM then greatly increase this since large pitch error with PCM
   fp             fPitchConfidence - Pitch confidence, from 0.0 (unconfident) to 1.0 (confident).
                     Only used if fFullPCM true. If -1.0 then won't use.
returns
   fp - Error per octave
*/
__inline fp UnitScoreDuration (PCMTTS pTTS, DWORD dwPhone, PCMLexicon pLex, BOOL fHigher, BOOL fFullPCM,
                               fp fPitchConfidence)
{
   PTTSTARGETCOSTS pTarget = (pTTS && (pTTS->m_memTTSTARGETCOSTS.m_dwCurPosn == sizeof(TTSTARGETCOSTS))) ?
      (PTTSTARGETCOSTS)pTTS->m_memTTSTARGETCOSTS.p : NULL;
   float *pafUnitScoreDurationPerPhone = pTarget ? &pTarget->afUnitScoreDurationPerPhone[0][0][0] : &gafUnitScoreDurationPerPhone[0];

   DWORD dwStress;
   DWORD dwEnglishPhone = pLex->PhonemeToEnglishPhone (dwPhone, &dwStress);

   DWORD dwIndexPSOLAPitch = ((dwEnglishPhone * MAXSTRESSES + dwStress) * 2);
   DWORD dwIndex = dwIndexPSOLAPitch + (fHigher ? 0 : 1);
   // DWORD dwIndex = (pLex->PhonemeToGroup (dwPhone) * 2) + (fHigher ? 0 : 1);
         // BUGFIX - Had (fHigher ? 1 : 0), which was opposite

   // BUGFIX - If full PCM, include some of the PCM pitch penalty in duration since
   // TD-PSOLA doesn't sound very good when stretched
   float *pafUnitScorePCMPitchPhone = pTarget ? &pTarget->afUnitScorePCMPitchPhone[0][0][0] : &gafUnitScorePCMPitchPhone[0];


   fp fScore = pafUnitScoreDurationPerPhone[dwIndex];
   if (fFullPCM && (fPitchConfidence >= 0.0)) {
      float *pafUnitScorePSOLADuration = pTarget ? &pTarget->afUnitScoreDurationPSOLAPitchCertaintyPerPhone[0][0][0] : &gafUnitScoreDurationPSOLAPitchCertaintyPerPhone[0];
      fScore += (fPitchConfidence * pafUnitScorePSOLADuration[dwIndexPSOLAPitch] + pafUnitScorePSOLADuration[dwIndexPSOLAPitch+1]) * 2.0;
         // BUGFIX - Added *2.0 because only calculated for sqrt(2.0) of duration increase
      // fScore += (EXTRAPSOLADURATIONPENALTY * pafUnitScorePCMPitchPhone[dwIndex]);
   }

   return fScore;
}


/*************************************************************************************
UnitScoreFunc - Returns unit-score based on function word differnce.

NOTE: This is ignoring the fact that function words are already slightly penalized
with a bad SR score because based SR score on overweighted with non-function units

inputs
   PCMTTS         pTTS - If not NULL, used to get voice-specific target costs
   DWORD          dwPhone - Phoneme
   PCMLexicon     pLex - Lexicon
   DWORD          dwFuncWordGroupOrig - Function word group of the original unit.
   DWORD          dwFuncWordGroupTarget - Function word group of the unit that trying to create
returns
   fp - Error
*/
fp UnitScoreFunc (PCMTTS pTTS, DWORD dwPhone, PCMLexicon pLex, DWORD dwFuncWordGroupOrig, DWORD dwFuncWordGroupTarget)
{
   PTTSTARGETCOSTS pTarget = (pTTS && (pTTS->m_memTTSTARGETCOSTS.m_dwCurPosn == sizeof(TTSTARGETCOSTS))) ?
      (PTTSTARGETCOSTS)pTTS->m_memTTSTARGETCOSTS.p : NULL;
   float *pafUnitScoreFuncPerPhone = pTarget ? &pTarget->afUnitScoreFuncPerPhone[0][0] : &gafUnitScoreFuncPerPhone[0];


   DWORD dwStress;
   DWORD dwEnglishPhone = pLex->PhonemeToEnglishPhone (dwPhone, &dwStress);

   DWORD dwIndex = (dwEnglishPhone * MAXSTRESSES + dwStress);

   return pafUnitScoreFuncPerPhone[dwIndex] * (fp)abs((int)dwFuncWordGroupOrig - (int)dwFuncWordGroupTarget);
}



/*************************************************************************************
UnitScoreMismatchedWordPos - Returns unit-score caused by a mismatched word position.

inputs
   PCMTTS         pTTS - If not NULL, used to get voice-specific target costs
   DWORD          dwTarget - Target word position, bit 0x01 is start of word, bit 0x02 is end of word
                     Takes WORDPOS_XXX flags
   DWORD          dwOrig - Original word position, bit 0x01 is start of word, bit 0x02 is end of word
                     Takes WORDPOS_XXX flags
returns
   fp - Error per octave
*/
__inline fp UnitScoreMismatchedWordPos (PCMTTS pTTS, DWORD dwTarget, DWORD dwOrig)
{
   dwTarget = WORDPOS_MAXONLY(dwTarget);
   dwOrig = WORDPOS_MAXONLY(dwOrig);

   _ASSERTE ((dwTarget & WORDPOS_WORDSTART) ? (dwTarget & WORDPOS_SYLSTART) : TRUE);
   _ASSERTE ((dwTarget & WORDPOS_WORDEND) ? (dwTarget & WORDPOS_SYLEND) : TRUE);
   _ASSERTE ((dwOrig & WORDPOS_WORDSTART) ? (dwOrig & WORDPOS_SYLSTART) : TRUE);
   _ASSERTE ((dwOrig & WORDPOS_WORDEND) ? (dwOrig & WORDPOS_SYLEND) : TRUE);

   PTTSTARGETCOSTS pTarget = (pTTS && (pTTS->m_memTTSTARGETCOSTS.m_dwCurPosn == sizeof(TTSTARGETCOSTS))) ?
      (PTTSTARGETCOSTS)pTTS->m_memTTSTARGETCOSTS.p : NULL;
   float *pafUnitScoreMismatchedWordPos = pTarget ? &pTarget->afUnitScoreMismatchedWordSylPos[0] : &gafUnitScoreMismatchedWordSylPos[0];

   return pafUnitScoreMismatchedWordPos[dwTarget ^ dwOrig];
}



/*************************************************************************************
TTS tags - this is a list of the TTS tags supported:

<phoneme ph="hh eh1 l oe0">Hello</phoneme> - Uses the given phonemes.

<emphasis xyz>Hello</emphasis> - Emphasized the word(s)
   If xyz is none then default to "level=moderate"
   If xyz is level=xxx, then xxx is strong, moderate, none, reduced
   Can also have PitchRel=, VolRel=, DurRel= for relative pitch, volume, and duration
      where 1.0 is no change
   Or can have PitchAbs=x (x is in hz), VolAbs=x (x is what?, DurAbs=x (x is in seconds)
   VolAbs = sum of SRFEATUREEnergy()/# features

<punct text="PUNCT" durabs=SEC/> - Punctuation notation

<break/> - Puts a break between words
   Can have "time=xxx", where xxx = "none", "x-small", "small", "medium" (default),
   "large", and "x-large". Can also be "XXXms" or "XXXs".

<TransPros>...</TransPros> - Indicate transplanted prosody
   Can contain <OrigText>xxx</OrigText> to indicate original text
   Can contain other stuff too.. tppitchrel, tppitchabs, tpvolrel, tpvolabs, tpdurrel, tpdurabs

<prosody xyz>...</prosody> - Controls how the text is read within the prosody tag.
   XYZ can be:
   pitch=XXX. Can be a number followed by hz, percent (100% = norm pitch),
      a relative change (+/- number or percent, or "st" for semitones),
      or x-high, high, medium, low, x-low, or default
   range=xxx. Can be a percent (100% = normal), +/- percent, or x-high,high,medium,
      low, x-low, or default
   rate=xxx. Can be a number (wpm), percent (as percent of wpm), +/- percent,
      +/- number, or x-fast, fast,medium,slow,x-slow, or default
   volume=xxx. Can be a number (100 = def volume), percent (100% = def percent),
      +/- percent, +/- number, or silent, x-soft, soft, medium, loud, x-loud, default

<pos major=xxx>...</pos> - Sets the pos for the words, disambiguate "invalid" (in-vuh-lid or in-val-id)
   XXX can be: n/noun, pron/pronoun, adj/adjective, prep/preposition, art/article,
   v/verb, adv/adverb, aux v/auxiliary verb, conj/conjunction, interj/interjection

<subvoice attribs/> - Can occur anywhere in the text and affects the subvoice for everything (for now)
   subvoice=x - If used, this uses subvoice 1..9 (or however many). Otherise, mix...
   mixvoice=x - Number. Use each of the digits to determine a mix
   mixpros=x - Number. Mixes the prosody.
   mixpron=x - Number. Mixes pronunciations. At the moment, only takes the least-signficant digit
               NOTE: Mixing for all of these is based on a series of digits. Repeating a digit
               increases the weight.

<emotion attribs>...</emotion> - Sets the emotion
   whisper=x - If wispering, 0..100
   shout=x - If shouting, 0..100
   quiet=x - If quiet, 0..100 (opposite of shout)
   happy=x - 0..100
   sad=x - 0..100
   afraid=x - 0..100
   drunk=x - 0..100
*/



// globals
static CListFixed  glPCMTTS;       // list of tts voices cached
static BOOL gfPCMTTSValid = FALSE; // set to TRUE if initalized
static PWSTR gpszPunctPros = L"PunctPros";
static PWSTR gpszPunct = L"Punct";
static PWSTR gpszFuncWord = L"FuncWord";
static PWSTR gpszEmphasis = L"Emphasis";
static PWSTR gpszTransPros = L"TransPros";
static PWSTR gpszOrigText = L"OrigText";
static PWSTR gpszLevel = L"Level";
static PWSTR gpszStrong = L"Strong";
static PWSTR gpszModerate = L"Moderate";
static PWSTR gpszNone = L"None";
static PWSTR gpszReduced = L"Reduced";

static PWSTR gpszPitchRel = L"PitchRel";
static PWSTR gpszVolRel = L"VolRel";
static PWSTR gpszDurRel = L"DurRel";
static PWSTR gpszPitchAbs = L"PitchAbs";
static PWSTR gpszVolAbs = L"VolAbs";
static PWSTR gpszDurAbs = L"DurAbs";
static PWSTR gpszInflectPoint = L"InflectPoint";
static PWSTR gpszBreak = L"Break";
static PWSTR gpszTime = L"Time";
static PWSTR gpszPh = L"ph";
static PWSTR gpszBookmark = L"BookMark";

static PWSTR gpszPauseLeft = L"PauseLeft";
static PWSTR gpszSylDurPhone = L"SylDurPhone";
static PWSTR gpszSylDurSyl = L"SylDurSyl";
static PWSTR gpszSylDurSkew = L"SylDurSkew";
static PWSTR gpszSylPitchF0ExtraArray = L"SylPitchF0ExtraArray";
static PWSTR gpszSylPitchF0Fuji = L"SylPitchF0Fuji";
//static PWSTR gpszSylPitchSweep = L"SylPitchSweep";
//static PWSTR gpszSylPitchBulge = L"SylPitchBulge";
static PWSTR gpszSylVol = L"SylVol";
static PWSTR gpszSylAccent = L"SylAccent";
static PWSTR gpszSyllables = L"Syllables";
static PWSTR gpszDerMicropauses = L"DerMicropauses";

static PWSTR gpszTPDurRel = L"TPDurRel";
static PWSTR gpszTPDurAbs = L"TPDurAbs";
static PWSTR gpszTPVolRel = L"TPVolRel";
static PWSTR gpszTPVolAbs = L"TPVolAbs";
static PWSTR gpszTPPitchRel = L"TPPitchRel";
static PWSTR gpszTPPitchAbs = L"TPPitchAbs";

static PWSTR gpszEmotion = L"Emotion";
static PWSTR gpszWhisper = L"Whisper";
static PWSTR gpszShout = L"Shout";
static PWSTR gpszQuiet = L"Quiet";
static PWSTR gpszHappy = L"Happy";
static PWSTR gpszSad = L"Sad";
static PWSTR gpszAfraid = L"Afraid";
static PWSTR gpszDrunk = L"Drunk";

static PWSTR gpszTTSGlobalState = L"TTSGlobalState";
static PWSTR gpszAvgPitch = L"AvgPitch";
static PWSTR gpszAvgPitchF0Fuji = L"AvgPitchF0Fuji";
static PWSTR gpszFujiRangeLow = L"FujiRangeLow";
static PWSTR gpszFujiRangeMed = L"FujiRangeMed";
static PWSTR gpszFujiRangeHigh = L"FujiRangeHigh";
static PWSTR gpszAvgSyllableDur = L"AvgSyllableDur";
static PWSTR gpszWPM = L"WPM";
static PWSTR gpszVol = L"Vol";
static PWSTR gpszPitchExpress = L"PitchExpress";
static PWSTR gpszEmotionWhisper = L"EmotionWhisper";
static PWSTR gpszEmotionShout = L"EmotionShout";
static PWSTR gpszEmotionQuiet = L"EmotionQuiet";
static PWSTR gpszEmotionHappy = L"EmotionHappy";
static PWSTR gpszEmotionSad = L"EmotionSad";
static PWSTR gpszEmotionAfraid = L"EmotionAfraid";
static PWSTR gpszEmotionDrunk = L"EmotionDrunk";

static PWSTR gpszProsody = L"Prosody";
static PWSTR gpszPitch = L"Pitch";
static PWSTR gpszRange = L"Range";
static PWSTR gpszRate = L"Rate";
static PWSTR gpszVolume = L"Volume";

static PWSTR gpszPOS = L"POS";
static PWSTR gpszMajor = L"Major";

static char gszKeyTTSFile[] = "TTSFile";

static PCM3DWave     gpTTSCacheSpeakWave = NULL;

/*************************************************************************************
SentenceLengthToBin - Given a sentence length (in syllables), this returns
the bin to use, as well as the average length for the sentence.

inputs
   DWORD       dwLength - Length in syllables
   DWORD       *pdwAvgLength - If not NULL, filled in with the average length of the bin sentence
returns
   DWORD - Bin number, from 0 to SENTENCELENGTHTOBINNUM-1
*/
DWORD SentenceLengthToBin (DWORD dwLength)
{
   DWORD dwRet = 0;
   //BYTE bLastBit = 0;
   while (dwLength) {
      //bLastBit <<= 1;
      //bLastBit |= (BYTE)(dwLength & 0x01);
      dwLength /= 2;
      dwRet++;
   }
   if (dwRet)
      dwRet--; // since should always be at least one bit

   dwRet = dwRet; //  * 2 + ((bLastBit & 0x2) ? 1 : 0);
   dwRet = min(dwRet, SENTENCELENGTHTOBINNUM-1);

   return dwRet;
}


/*************************************************************************************
SentenceLengthFromBin - Given a bin number (for SentenceLengthToBin()), this
returns the average sentence length.

inputs
   DWORD       dwBin - From 0 .. SENTENCELENGTHTOBINNUM-1
returns
   DWORD - Average sentence length
*/
DWORD SentenceLengthFromBin (DWORD dwBin)
{
   //*pdwAvgLength = (1 << (dwRet/2));
   //if (dwRet % 3)
   //   *pdwAvgLength += (*pdwAvgLength)/2;
   DWORD dwRet = (1 << dwBin);
   dwRet += dwRet/2; // so mid-way between this and the next

   return dwRet;
}


/*************************************************************************************
SentenceLengthTotal - Returns the total number of entries for all the
sentences. This is the sum of SentenceLengthFromBin() over 0 .. SENTENCELENGTHTOBINNUM-1

inputs
   DWORD          dwNum - If -1 (default) then sums up over all bins.
                        If less than, then sums up to the given bin; used to
                        index into m_amemTYPICALSYLINFO.
returns
   DWORD - Number
*/
DWORD SentenceLengthTotal (DWORD dwNum)
{
   if (dwNum >= SENTENCELENGTHTOBINNUM)
      dwNum = SENTENCELENGTHTOBINNUM;

   DWORD i;
   DWORD dwSum = 0;
   for (i = 0; i < dwNum; i++)
      dwSum += SentenceLengthFromBin(i);

   return dwSum;
}


/*************************************************************************************
CTTSProsody::ProsodyNGramInfoCount - Returns the number of elements stored for the Ngrams

inputs
   PCListFixed       pLexTTS - Lexicon with all the phonemes in it.
returns
   DWORD - Number
*/
#ifndef NOMODS_DISABLEPROSODYNGRAM
DWORD CTTSProsody::ProsodyNGramInfoCount (PCMLexicon pLexTTS)
{
   DWORD dwRet = 1;
   DWORD i;
   for (i = 0; i <= PROSODYNGRAMNUM*2; i++)
      dwRet *= PROSODYNGRAMRANGE;

   return dwRet;
}
#endif



/*************************************************************************************
AttribGetArray - Gets an array of DWORDs from an attribute.

inputs
   PCMMLNode2     pNode - Node
   PWSTR          pszName - Attribute name
   PCListFixed    plDWORD - Initialized to sizeof(DWORD) and then values are gotten
returns
   BOOL - TRUE if found, FALSE if not
*/
BOOL AttribGetArray (PCMMLNode2 pNode, PWSTR pszName, PCListFixed plDWORD)
{
   size_t dwSize = 0;
   PBYTE pb = (PBYTE) pNode->AttribGetBinary (pszName, &dwSize);
   dwSize /= sizeof(DWORD);
   if (!pb || !dwSize) {
      plDWORD->Clear();
      return FALSE;
   }

   plDWORD->Init (sizeof(DWORD), pb, (DWORD) dwSize);
   return TRUE;
}



/*************************************************************************************
AttribSetArray - Sets an array of DWORDs from an attribute.

inputs
   PCMMLNode2     pNode - Node
   PWSTR          pszName - Attribute name
   DWORD          dwNum - Number of values
   DWORD          *pdwVal - Values
returns
   BOOL - TRUE if found, FALSE if not
*/
BOOL AttribSetArray (PCMMLNode2 pNode, PWSTR pszName, DWORD dwNum, DWORD *pdwVal)
{
   return pNode->AttribSetBinary (pszName, pdwVal, dwNum*sizeof(DWORD));
}


#if 0 // not ued
/*************************************************************************************
CTTSProsody::LexiconSet - Sets the lexicon to use for TTS.

inputs
   PCMLexicon        pLexTTS - Lexicon to use, from TTS. Should have all the phonemes defined.

*/
void CTTSProsody::LexiconSet (PCMLexicon pLexTTS)
{
   m_pLexTTS = pLexTTS;
}


/*************************************************************************************
CTTSProsody::LexiconGet - Gets the lexicon to use for TTS.

*/
PCMLexicon CTTSProsody::LexiconGet (void)
{
   return m_pLexTTS;
}
#endif // 0


/*************************************************************************************
CTTSProsody::Construcor and destructor
*/
CTTSProsody::CTTSProsody (void)
{
   m_lPCSentenceSyllable.Init (sizeof(PCSentenceSyllable));
   m_lPCSentenceSyllableWord.Init (sizeof(PCSentenceSyllable));
   m_pPCSSRandom.Init (sizeof(PCSentenceSyllable));
   m_lPCHashStringWordSyl.Init (sizeof(PCHashString));
   m_pLexInProsody = new CMLexicon;
   // m_pLexTTS = NULL;
   // m_memPhonemePause - Do nothing

   InitializeCriticalSection (&m_csProsody);

   m_fSENTENCEINDEXValid = FALSE;
   m_fSENTENCEINDEXValidWord = FALSE;

   memset (m_aplSENTENCEINDEX, 0, sizeof(m_aplSENTENCEINDEX));
   memset (m_aplSENTENCEINDEXWord, 0, sizeof(m_aplSENTENCEINDEXWord));
   //DWORD i;
   //for (i = 0; i < sizeof(m_alSENTENCEINDEX) / sizeof(m_alSENTENCEINDEX[0][0][0][0]); i++) {
   //   (&m_alSENTENCEINDEX[0][0][0][0])[i].Init (sizeof(SENTENCEINDEX));
   //   (&m_alSENTENCEINDEXWord[0][0][0][0])[i].Init (sizeof(SENTENCEINDEX));
   //}

}

CTTSProsody::~CTTSProsody (void)
{
   DeleteCriticalSection (&m_csProsody);

   // free sentece syllables
   PCSentenceSyllable *ppss = (PCSentenceSyllable*)m_lPCSentenceSyllable.Get(0);
   DWORD i;
   for (i = 0; i < m_lPCSentenceSyllable.Num(); i++)
      if (ppss[i])
         delete ppss[i];
   m_lPCSentenceSyllable.Clear();

   // free sentece syllables
   ppss = (PCSentenceSyllable*)m_lPCSentenceSyllableWord.Get(0);
   for (i = 0; i < m_lPCSentenceSyllableWord.Num(); i++)
      if (ppss[i])
         delete ppss[i];
   m_lPCSentenceSyllableWord.Clear();

   // free hashes
   PCHashString *pphd = (PCHashString*)m_lPCHashStringWordSyl.Get(0);
   DWORD j;
   for (i = 0; i < m_lPCHashStringWordSyl.Num(); i++) {
      if (!pphd[i])
         continue;

      for (j = 0; j < pphd[i]->Num(); j++) {
         PTTSPWORDSYLHEADER pwsh = (PTTSPWORDSYLHEADER) pphd[i]->Get(j);
         if (pwsh->pss)
            delete pwsh->pss;
      } // j

      delete pphd[i];
   } // i


   if (m_pLexInProsody)
      delete m_pLexInProsody;

   // free the lists
   for (i = 0; i < sizeof(m_aplSENTENCEINDEX) / sizeof(m_aplSENTENCEINDEX[0][0][0][0]); i++)
      if ((&m_aplSENTENCEINDEX[0][0][0][0])[i])
         delete (&m_aplSENTENCEINDEX[0][0][0][0])[i];
   for (i = 0; i < sizeof(m_aplSENTENCEINDEXWord) / sizeof(m_aplSENTENCEINDEXWord[0][0][0][0]); i++)
      if ((&m_aplSENTENCEINDEXWord[0][0][0][0])[i])
         delete (&m_aplSENTENCEINDEXWord[0][0][0][0])[i];
   memset (m_aplSENTENCEINDEX, 0, sizeof(m_aplSENTENCEINDEX));
   memset (m_aplSENTENCEINDEXWord, 0, sizeof(m_aplSENTENCEINDEXWord));
}

static PWSTR gpszTTSProsody = L"TTSProsody";
static PWSTR gpszSentenceSyllable = L"SentenceSyllable";
static PWSTR gpszLexicon = L"Lexicon";
static PWSTR gpszPhonemePause = L"PhonemePause";
// static PWSTR gpszTYPICALSYLINFO = L"TYPICALSYLINFO";
static PWSTR gpszPROSODYNGRAMINFO = L"PROSODYNGRAMINFO";
static PWSTR gpszHashDWORDWordSyl = L"HashDWORDWordSyl";

/*************************************************************************************
CTTSProsody::MemoryTouch - Call this to ensure that TTS stays in memory
*/
DWORD CTTSProsody::MemoryTouch (void)
{
   DWORD dwRet = 0;

   if (m_pLexInProsody)
      dwRet += m_pLexInProsody->MemoryTouch();

   DWORD dwIndex;
   if (m_lPCSentenceSyllable.Num()) {
      dwIndex = (DWORD)rand() % m_lPCSentenceSyllable.Num();
      PCSentenceSyllable *ppss = (PCSentenceSyllable*)m_lPCSentenceSyllable.Get(dwIndex);
      if (ppss)
         dwRet += ppss[0]->MemoryTouch();
   }
   if (m_lPCSentenceSyllableWord.Num()) {
      dwIndex = (DWORD)rand() % m_lPCSentenceSyllableWord.Num();
      PCSentenceSyllable *ppss = (PCSentenceSyllable*)m_lPCSentenceSyllableWord.Get(dwIndex);
      if (ppss)
         dwRet += ppss[0]->MemoryTouch();
   }

   DWORD *pdw;
   if (m_memPhonemePause.m_dwCurPosn / sizeof(DWORD)) {
      dwIndex = (DWORD)rand() % (DWORD)(m_memPhonemePause.m_dwCurPosn / sizeof(DWORD));
      pdw = (DWORD*)m_memPhonemePause.p;
      dwRet += pdw[dwIndex];
   }

   if (m_memPauseNGram.m_dwCurPosn / sizeof(DWORD)) {
      dwIndex = (DWORD)rand() % (DWORD)(m_memPauseNGram.m_dwCurPosn / sizeof(DWORD));
      pdw = (DWORD*)m_memPauseNGram.p;
      dwRet += pdw[dwIndex];
   }

   if (m_memPROSODYTRENDS.m_dwCurPosn / sizeof(DWORD)) {
      dwIndex = (DWORD)rand() % (DWORD)(m_memPROSODYTRENDS.m_dwCurPosn / sizeof(DWORD));
      pdw = (DWORD*)m_memPROSODYTRENDS.p;
      dwRet += pdw[dwIndex];
   }

   DWORD dwIndex2 = (DWORD)rand() % TYPICALSYLINFO_NUM;
   if (m_amemTYPICALSYLINFO[dwIndex2].m_dwCurPosn / sizeof(DWORD)) {
      dwIndex = (DWORD)rand() % (DWORD)(m_amemTYPICALSYLINFO[dwIndex2].m_dwCurPosn / sizeof(DWORD));
      pdw = (DWORD*)m_amemTYPICALSYLINFO[dwIndex2].p;
      dwRet += pdw[dwIndex];
   }

   if (m_memPROSODYNGRAMINFO.m_dwCurPosn / sizeof(DWORD)) {
      dwIndex = (DWORD)rand() % (DWORD)(m_memPROSODYNGRAMINFO.m_dwCurPosn / sizeof(DWORD));
      pdw = (DWORD*)m_memPROSODYNGRAMINFO.p;
      dwRet += pdw[dwIndex];
   }

   if (m_lPCHashStringWordSyl.Num()) {
      dwIndex = (DWORD)rand() % m_lPCHashStringWordSyl.Num();
      PCHashString *pphd = (PCHashString*)m_lPCHashStringWordSyl.Get(dwIndex);
      PCHashString phd;
      phd = pphd ? pphd[0] : NULL;
      if (phd) {
         DWORD dwIndex2 = (DWORD)rand() % phd->Num();
         PTTSPWORDSYLHEADER pwsh = (PTTSPWORDSYLHEADER) phd->Get(dwIndex2);
         if (pwsh) {
            dwRet += pwsh->dwSyllables;
            if (pwsh->pss)
               pwsh->pss->MemoryTouch();
         }

      }
   }

   dwIndex = (DWORD)rand() % (sizeof(m_aplSENTENCEINDEX) / sizeof(m_aplSENTENCEINDEX[0][0][0][0]));
   if (m_aplSENTENCEINDEX[0][0][0][dwIndex] && m_aplSENTENCEINDEX[0][0][0][dwIndex]->Num()) {
      dwIndex2 = (DWORD)rand() % m_aplSENTENCEINDEX[0][0][0][dwIndex]->Num();

      PSENTENCEINDEX pSI = (PSENTENCEINDEX)m_aplSENTENCEINDEX[0][0][0][dwIndex]->Get(dwIndex2);

      if (pSI)
         dwRet += pSI->iStart;
   }

   dwIndex = (DWORD)rand() % (sizeof(m_aplSENTENCEINDEXWord) / sizeof(m_aplSENTENCEINDEXWord[0][0][0][0]));
   if (m_aplSENTENCEINDEXWord[0][0][0][dwIndex] && m_aplSENTENCEINDEXWord[0][0][0][dwIndex]->Num()) {
      dwIndex2 = (DWORD)rand() % m_aplSENTENCEINDEXWord[0][0][0][dwIndex]->Num();

      PSENTENCEINDEX pSI = (PSENTENCEINDEX)m_aplSENTENCEINDEXWord[0][0][0][dwIndex]->Get(dwIndex2);

      if (pSI)
         dwRet += pSI->iStart;
   }

   return dwRet;
}

/*************************************************************************************
CTTSProsody::MMLTo - Standard API
*/
PCMMLNode2 CTTSProsody::MMLTo (void)
{
   PCMMLNode2 pNode = new CMMLNode2;
   if (!pNode)
      return NULL;
   pNode->NameSet (gpszTTSProsody);

   // convert to sentence syllables
   CMem mem;
   PCSentenceSyllable *ppss = (PCSentenceSyllable*)m_lPCSentenceSyllable.Get(0);
   mem.m_dwCurPosn = 0;
   DWORD i;
   for (i = 0; i < m_lPCSentenceSyllable.Num(); i++)
      ppss[i]->MMLToBinary (&mem);
   MMLValueSet (pNode, gpszSentenceSyllable, (PBYTE)mem.p, mem.m_dwCurPosn);

   // NOTE: DON'T bother saving m_lPCSentenceSyllableWord

   // save the prosody
   if (m_memPhonemePause.m_dwCurPosn)
      MMLValueSet (pNode, gpszPhonemePause, (PBYTE)m_memPhonemePause.p, m_memPhonemePause.m_dwCurPosn);

   // save the default length
   WCHAR szTemp[64];
   for (i = 0; i < TYPICALSYLINFO_NUM; i++) {
      swprintf (szTemp, L"TypicalSylInfo%d", (int)i);
      if (m_amemTYPICALSYLINFO[i].m_dwCurPosn)
         MMLValueSet (pNode, szTemp, (PBYTE)m_amemTYPICALSYLINFO[i].p, m_amemTYPICALSYLINFO[i].m_dwCurPosn);
   } // i

   // save the NGram
   CMem  memRLE;
#ifndef NOMODS_DISABLEPROSODYNGRAM
   if (m_memPROSODYNGRAMINFO.m_dwCurPosn) {
      // convert to RLE
      memRLE.m_dwCurPosn = 0;
      if (RLEEncode ((PBYTE)m_memPROSODYNGRAMINFO.p, m_memPROSODYNGRAMINFO.m_dwCurPosn / sizeof(float), sizeof(float), &memRLE))
         return FALSE;
      size_t dwRLESize = memRLE.m_dwCurPosn;
      if (!dwRLESize)
         return FALSE;

      // write to MML
      MMLValueSet (pNode, gpszPROSODYNGRAMINFO, (PBYTE)memRLE.p, memRLE.m_dwCurPosn);
   }
#endif

   // save the hash
   DWORD j;
   CMem memSS;
   mem.m_dwCurPosn = 0;
   PCHashString *pphd = (PCHashString*)m_lPCHashStringWordSyl.Get(0);
   TTSPWORDSYLHEADERFILE WSHHeader;
   for (i = 0; i < m_lPCHashStringWordSyl.Num(); i++) {
      PCHashString phd = pphd[i];
      for (j = 0; j < phd->Num(); j++) {
         PTTSPWORDSYLHEADER pwsh = (PTTSPWORDSYLHEADER) phd->Get(j);
         if (!pwsh)
            continue;

         PWSTR pszWord = phd->GetString (j);
         if (!pszWord)
            continue;

         memSS.m_dwCurPosn = 0;
         if (!pwsh->pss || !pwsh->pss->MMLToBinary (&memSS))
            memSS.m_dwCurPosn = 0;

         memset (&WSHHeader, 0, sizeof(WSHHeader));
         WSHHeader.dwSSSize = (DWORD)memSS.m_dwCurPosn;
         WSHHeader.dwStressBitsMulti = pwsh->dwStressBitsMulti;
         WSHHeader.dwTrainCount = pwsh->dwTrainCount;
         WSHHeader.dwSyllables = pwsh->dwSyllables;

         DWORD dwSizeHeader = sizeof(DWORD) + (DWORD)(wcslen(pszWord)+1) * sizeof(WCHAR);
         DWORD dwSizeWSHHeader = sizeof(WSHHeader) + WSHHeader.dwSyllables * sizeof(PROSODYTREND);
         DWORD dwSize = dwSizeHeader + dwSizeWSHHeader + (DWORD)WSHHeader.dwSSSize;
         if (!mem.Required (mem.m_dwCurPosn + dwSize))
            continue;

         DWORD *pdwSizeInfo = (DWORD*) ((PBYTE)mem.p + mem.m_dwCurPosn);
         *pdwSizeInfo = (DWORD)(wcslen(pszWord)+1) * sizeof(WCHAR);
         memcpy (pdwSizeInfo+1, pszWord, (wcslen(pszWord)+1)*sizeof(WCHAR));

         memcpy ((PBYTE)mem.p + (mem.m_dwCurPosn + dwSizeHeader), &WSHHeader, sizeof(WSHHeader));
         memcpy ((PBYTE)mem.p + (mem.m_dwCurPosn + dwSizeHeader + sizeof(WSHHeader)), pwsh+1, dwSizeWSHHeader - sizeof(WSHHeader));
         memcpy ((PBYTE)mem.p + (mem.m_dwCurPosn + dwSizeHeader + dwSizeWSHHeader), memSS.p, WSHHeader.dwSSSize);

         mem.m_dwCurPosn += dwSize;
      } // j
   } // i
   MMLValueSet (pNode, gpszHashDWORDWordSyl, (PBYTE)mem.p, mem.m_dwCurPosn);


   // save the lexicon
   PCMMLNode2 pSub = m_pLexInProsody->MMLTo ();
   if (pSub) {
      pSub->NameSet (gpszLexicon);
      pNode->ContentAdd (pSub);
   }

   return pNode;
}


/*************************************************************************************
CTTSProsody::MMLFrom - Standard API
*/
BOOL CTTSProsody::MMLFrom (PCMMLNode2 pNode, PWSTR pszSrcFile)
{
   Clear();

   // get the sentence syllables
   CMem memRLE;
   memRLE.m_dwCurPosn = 0;
   MMLValueGetBinary (pNode, gpszSentenceSyllable, &memRLE);
   if (memRLE.m_dwCurPosn) {
      // found sentences
      PBYTE pbCur = (PBYTE)memRLE.p;
      size_t dwLeft = memRLE.m_dwCurPosn;
      size_t dwUsed;
      while (dwLeft) {
         PCSentenceSyllable pss = new CSentenceSyllable;
         if (!pss)
            break;

         dwUsed = pss->MMLFromBinary (pbCur, dwLeft);
         if (!dwUsed) {
            // error
            delete pss;
            break;
         }
         
         // advance the pointer
         pbCur += dwUsed;
         dwLeft -= dwUsed;

         // add to list
         m_lPCSentenceSyllable.Add (&pss);
      }
   } // if found sentence syllable

   // NOTE: Don't bother getting m_lPCSentenceSyllableWord since automatic

   // get the phoneme pauses
   m_memPhonemePause.m_dwCurPosn = 0;
   m_memPhonemePause.m_dwCurPosn = MMLValueGetBinary (pNode, gpszPhonemePause, &m_memPhonemePause);

   // get the phoneme pauses
   // save the default length
   WCHAR szTemp[64];
   DWORD i;
   for (i = 0; i < TYPICALSYLINFO_NUM; i++) {
      swprintf (szTemp, L"TypicalSylInfo%d", (int)i);
      m_amemTYPICALSYLINFO[i].m_dwCurPosn = 0;
      m_amemTYPICALSYLINFO[i].m_dwCurPosn = MMLValueGetBinary (pNode, szTemp, &m_amemTYPICALSYLINFO[i]);
      if (m_amemTYPICALSYLINFO[i].m_dwCurPosn != SentenceLengthTotal () * sizeof(TYPICALSYLINFO))
         m_amemTYPICALSYLINFO[i].m_dwCurPosn = 0;   // must be the right length
   } // i


   // prosody Ngram
   memRLE.m_dwCurPosn = 0;
#ifndef NOMODS_DISABLEPROSODYNGRAM
   MMLValueGetBinary (pNode, gpszPROSODYNGRAMINFO, &memRLE);
   //psz = MMLValueGet (pNode, gpszNGram);
   if (memRLE.m_dwCurPosn /*psz*/) {
      //if (!memRLE.Required (wcslen(psz)/2))
      //   return FALSE;
      PBYTE pb = (PBYTE)memRLE.p;
      //DWORD dwSize = MMLBinaryFromString (psz, pb, memRLE.m_dwAllocated);
      size_t dwSize = memRLE.m_dwCurPosn;

      // RLE decode
      m_memPROSODYNGRAMINFO.m_dwCurPosn = 0;   // BUGFIX - Was just mem, wrong var
      size_t dwUsed;
      if (RLEDecode ((PBYTE)memRLE.p, dwSize, sizeof(float), &m_memPROSODYNGRAMINFO, &dwUsed))
         return FALSE;
      dwSize = m_memPROSODYNGRAMINFO.m_dwCurPosn;
      if (!dwSize)
         return FALSE;

      // make sure size is correct
      DWORD dwExpect = ProsodyNGramInfoCount() * sizeof(PROSODYNGRAMINFO);
      if (dwExpect != m_memPROSODYNGRAMINFO.m_dwCurPosn)
         m_memPROSODYNGRAMINFO.m_dwCurPosn = 0;   // invalid
   }
#endif // 0

   // get the hash syllable info
   MMLValueGetBinary (pNode, gpszHashDWORDWordSyl, &memRLE);
   if (memRLE.m_dwCurPosn) {
      PBYTE pb = (PBYTE)memRLE.p;
      size_t dwSize = memRLE.m_dwCurPosn;

      while (dwSize >= sizeof(DWORD)) {
         // get the number of bytes for the string
         DWORD dwSizeString = *((DWORD*)pb);
         pb += sizeof(DWORD);
         dwSize -= sizeof(DWORD);

         // get the string
         if (dwSize < dwSizeString)
            continue;
         PWSTR pszWord = (PWSTR)pb;
         pb += dwSizeString;
         dwSize -= dwSizeString;

         // get the data
         if (dwSize < sizeof(PTTSPWORDSYLHEADERFILE))
            continue;
         PTTSPWORDSYLHEADERFILE pwshFile = (PTTSPWORDSYLHEADERFILE) pb;
         DWORD dwSyllableSize = pwshFile->dwSyllables * sizeof(PROSODYTREND);
         DWORD dwNeeded = sizeof(*pwshFile) + dwSyllableSize + pwshFile->dwSSSize;
         if (dwSize < dwNeeded)
            break;   // error, shouldnt happen

         
         PTTSPWORDSYLHEADER pwshNew = WordSylGetInternal (pszWord, pwshFile->dwSyllables, pwshFile->dwStressBitsMulti, TRUE);
         if (!pwshNew)
            break;   // error
         pwshNew->dwTrainCount = pwshFile->dwTrainCount;

         memcpy (pwshNew + 1, pwshFile+1, dwSyllableSize);

         // sentence
         if (pwshFile->dwSSSize) {
            PCSentenceSyllable pss = new CSentenceSyllable;
            size_t dwSizeRet;
            if (pss)
               dwSizeRet = pss->MMLFromBinary (pb + (sizeof(*pwshFile) + dwSyllableSize), pwshFile->dwSSSize);
            else
               dwSizeRet = 0;
            if (dwSizeRet == pwshFile->dwSSSize) {
               if (pwshNew->pss)
                  delete pwshNew->pss;
               pwshNew->pss = pss;
            }
            else if (pss)
               delete pss;
         } // file

         pb += dwNeeded;
         dwSize -= dwNeeded;
      } // dwSize
   }

   // get the lexicon
   PCMMLNode2 pSub;
   PWSTR psz;
   pSub = NULL;
   pNode->ContentEnum (pNode->ContentFind (gpszLexicon), &psz, &pSub);
   if (pSub)
      m_pLexInProsody->MMLFrom (pSub, pszSrcFile, FALSE);

   return TRUE;
}



/*************************************************************************************
CTTSProsody::CloneTo - Standard API
*/
BOOL CTTSProsody::CloneTo (CTTSProsody *pTo)
{
   // NOTE: Clone to not tested
   pTo->Clear();

   DWORD i;
   pTo->m_lPCSentenceSyllable.Init (sizeof(PCSentenceSyllable), m_lPCSentenceSyllable.Get(0), m_lPCSentenceSyllable.Num());
   PCSentenceSyllable* ppss = (PCSentenceSyllable*)pTo->m_lPCSentenceSyllable.Get(0);  // BUGFIX - Didn't have pTo->
   for (i = 0; i < pTo->m_lPCSentenceSyllable.Num(); i++)
      if (ppss[i])
         ppss[i] = ppss[i]->Clone();

   // NOTE: Don't bother copying m_lPCSentenceSyllableWord since automatic

   // clone the hashes
   // NOTE: Hashes clone not tested
   pTo->m_lPCHashStringWordSyl.Init (sizeof(PCHashString), m_lPCHashStringWordSyl.Get(0), m_lPCHashStringWordSyl.Num());
   PCHashString *pphd = (PCHashString*)pTo->m_lPCHashStringWordSyl.Get(0);
   DWORD j;
   for (i = 0; i < pTo->m_lPCHashStringWordSyl.Num(); i++) {
      if (!pphd[i])
         continue;

      pphd[i] = pphd[i]->Clone();
      if (!pphd[i])
         continue;

      for (j = 0; j < pphd[i]->Num(); j++) {
         PTTSPWORDSYLHEADER pwsh = (PTTSPWORDSYLHEADER) pphd[i]->Get(j);
         if (pwsh->pss)
            pwsh->pss = pwsh->pss->Clone();
      } // j
   } // i

   if (!pTo->m_memPhonemePause.Required(m_memPhonemePause.m_dwCurPosn))
      return FALSE;
   memcpy (pTo->m_memPhonemePause.p, m_memPhonemePause.p, m_memPhonemePause.m_dwCurPosn);
   pTo->m_memPhonemePause.m_dwCurPosn = m_memPhonemePause.m_dwCurPosn;

   for (i = 0; i < TYPICALSYLINFO_NUM; i++) {
      if (!pTo->m_amemTYPICALSYLINFO[i].Required(m_amemTYPICALSYLINFO[i].m_dwCurPosn))
         return FALSE;
      memcpy (pTo->m_amemTYPICALSYLINFO[i].p, m_amemTYPICALSYLINFO[i].p, m_amemTYPICALSYLINFO[i].m_dwCurPosn);
      pTo->m_amemTYPICALSYLINFO[i].m_dwCurPosn = m_amemTYPICALSYLINFO[i].m_dwCurPosn;
   }

   // ngram
   if (!pTo->m_memPROSODYNGRAMINFO.Required(m_memPROSODYNGRAMINFO.m_dwCurPosn))
      return FALSE;
   memcpy (pTo->m_memPROSODYNGRAMINFO.p, m_memPROSODYNGRAMINFO.p, m_memPROSODYNGRAMINFO.m_dwCurPosn);
   pTo->m_memPROSODYNGRAMINFO.m_dwCurPosn = m_memPROSODYNGRAMINFO.m_dwCurPosn;

   PCMLexicon pLex = m_pLexInProsody->Clone ();
   if (pLex) {
      delete pTo->m_pLexInProsody;
      pTo->m_pLexInProsody = pLex;
   }

   return TRUE;
}


/*************************************************************************************
CTTSProsody::Clone - Standard API
*/
CTTSProsody *CTTSProsody::Clone (void)
{
   // NOTE: Clone not tested
   PCTTSProsody pp = new CTTSProsody ();
   if (!pp)
      return NULL;

   // pp->LexiconSet (m_pLexTTS);

   if (!CloneTo(pp)) {
      delete pp;
      return NULL;
   }

   return pp;
}


/*************************************************************************************
CTTSProsody::Clear - Standard API
*/
void CTTSProsody::Clear (void)
{
   // free sentece syllables
   PCSentenceSyllable *ppss = (PCSentenceSyllable*)m_lPCSentenceSyllable.Get(0);
   DWORD i;
   for (i = 0; i < m_lPCSentenceSyllable.Num(); i++)
      if (ppss[i])
         delete ppss[i];
   m_lPCSentenceSyllable.Clear();

   ppss = (PCSentenceSyllable*)m_lPCSentenceSyllableWord.Get(0);
   for (i = 0; i < m_lPCSentenceSyllableWord.Num(); i++)
      if (ppss[i])
         delete ppss[i];
   m_lPCSentenceSyllableWord.Clear();

   // free hashes
   PCHashString *pphd = (PCHashString*)m_lPCHashStringWordSyl.Get(0);
   DWORD j;
   for (i = 0; i < m_lPCHashStringWordSyl.Num(); i++) {
      if (!pphd[i])
         continue;

      for (j = 0; j < pphd[i]->Num(); j++) {
         PTTSPWORDSYLHEADER pwsh = (PTTSPWORDSYLHEADER) pphd[i]->Get(j);
         if (pwsh->pss)
            delete pwsh->pss;
      } // j

      delete pphd[i];
   } // i
   m_lPCHashStringWordSyl.Clear();

   m_memPhonemePause.m_dwCurPosn = 0;
   for (i = 0; i < TYPICALSYLINFO_NUM; i++)
      m_amemTYPICALSYLINFO[i].m_dwCurPosn = 0;
   m_memPROSODYNGRAMINFO.m_dwCurPosn = 0;

   m_memPauseNGram.m_dwCurPosn = 0;
   m_memPROSODYTRENDS.m_dwCurPosn = 0;

   m_pPCSSRandom.Clear();  // since only pointers

   m_fSENTENCEINDEXValid = FALSE;
   m_fSENTENCEINDEXValidWord = FALSE;
   // free the lists
   for (i = 0; i < sizeof(m_aplSENTENCEINDEX) / sizeof(m_aplSENTENCEINDEX[0][0][0][0]); i++)
      if ((&m_aplSENTENCEINDEX[0][0][0][0])[i])
         delete (&m_aplSENTENCEINDEX[0][0][0][0])[i];
   for (i = 0; i < sizeof(m_aplSENTENCEINDEXWord) / sizeof(m_aplSENTENCEINDEXWord[0][0][0][0]); i++)
      if ((&m_aplSENTENCEINDEXWord[0][0][0][0])[i])
         delete (&m_aplSENTENCEINDEXWord[0][0][0][0])[i];
   memset (m_aplSENTENCEINDEX, 0, sizeof(m_aplSENTENCEINDEX));
   memset (m_aplSENTENCEINDEXWord, 0, sizeof(m_aplSENTENCEINDEXWord));

   PCMLexicon pLex = new CMLexicon;
   if (m_pLexInProsody) {
      delete m_pLexInProsody;
      m_pLexInProsody = pLex;
   }
}


/*************************************************************************************
CTTSProsody::CreateWordSentencesIfNeccssary - Makes sure that word-based versions
of all the syllable sentences are created.

inputs
   none
returns
   none
*/
void CTTSProsody::CreateWordSentencesIfNeccssary (void)
{
   // if smae number of syllables then assume no need to change
   if (m_lPCSentenceSyllable.Num() == m_lPCSentenceSyllableWord.Num())
      return;

   // free up existing list
   DWORD i;
   PCSentenceSyllable *ppss = (PCSentenceSyllable*)m_lPCSentenceSyllableWord.Get(0);
   ppss = (PCSentenceSyllable*)m_lPCSentenceSyllableWord.Get(0);
   for (i = 0; i < m_lPCSentenceSyllableWord.Num(); i++)
      if (ppss[i])
         delete ppss[i];
   m_lPCSentenceSyllableWord.Clear();

   // intialize to existing sullables
   m_lPCSentenceSyllableWord.Init (sizeof(PCSentenceSyllable), m_lPCSentenceSyllable.Get(0), m_lPCSentenceSyllable.Num());

   // create word versions
   ppss = (PCSentenceSyllable*)m_lPCSentenceSyllableWord.Get(0);
   for (i = 0; i < m_lPCSentenceSyllableWord.Num(); i++)
      ppss[i] = ppss[i]->ToWords();

}


/*************************************************************************************
CTTSProsody::CreateIndexIfNecessary - This creates the index of part-of-speech and stress
to sentences, if necessary.

inputs
   PCMLexicon     pLexTTS - Lexicon with phonemes in it
   BOOL           fWord - if TRUE then making sure the word-based sentences are used.
                     If FALSE, then syllable-based sentences
returns
   none
*/
void CTTSProsody::CreateIndexIfNecessary (PCMLexicon pLexTTS, BOOL fWord)
{
   if (fWord)
      CreateWordSentencesIfNeccssary ();

   BOOL *pfCheck = fWord ? &m_fSENTENCEINDEXValidWord : &m_fSENTENCEINDEXValid;
   PCListFixed plSent = fWord ? &m_lPCSentenceSyllableWord : &m_lPCSentenceSyllable;

   if (*pfCheck)
      return;

   // loop through all the sentences
   PCSentenceSyllable *ppss = (PCSentenceSyllable*)plSent->Get(0);
   DWORD i;
   for (i = 0; i < plSent->Num(); i++)
      if (ppss[i]) {
         PCListFixed *ppl = fWord ? &m_aplSENTENCEINDEXWord[0][0][0][0] : &m_aplSENTENCEINDEX[0][0][0][0];
         ppss[i]->AddToIndex (pLexTTS, ppl);
      }

   *pfCheck = TRUE;
}


/*************************************************************************************
CTTSProsody::FindAllMatches - Find all matches to the current sentence.

inputs
   PCMLexicon     pLexTTS - Lexicon with phonemes in it
   PCSentenceSyllable   pSS - Sentence
   int                  iStart - Starting point (from start of sentece) for the match. Must be < m_dwNum
   int                  iEnd - Ending point for the match. Must be > 0. iEnd - Start >= 2
   BOOL                 fMustMatchPhrase - If TRUE, then the match must be either the entire sentence, or an
                           exact phrase match.
   BOOL                 fWord - If TRUE matching against the word-based residitual. IF FALSE, the syllable-based ones.
   CTTSProsody          **ppCTTSProsody - This of prosody objects to use for the comparison,
                        including this one.
   DWORD                dwNum - Number of prosody objects to use. Must be at least one
   PCListFixed          plSENTENCEMATCH - Has appropriate SENTENCEMATCH structures appended
                        to it.
returns
   BOOL - TRUE if succes.
*/
BOOL CTTSProsody::FindAllMatches (PCMLexicon pLexTTS, PCSentenceSyllable pSS, int iStart, int iEnd, BOOL fMustMatchPhrase, BOOL fWord,
                                  CTTSProsody **ppCTTSProsody, DWORD dwNum, PCListFixed plSENTENCEMATCH)
{
   if (iStart >= (int)pSS->m_dwNum)
      return FALSE;
   if (iEnd <= 0)
      return FALSE;
   int iNum = iEnd - iStart;
   if (iNum < 2)
      return FALSE;

   // loop through all real starts looking for the combination of two units
   // with the LEAST number of matches, so that will speed up
   int iBestStart = -1000;
   DWORD dwBestStartScore = 0;
   DWORD adwBestPOS[2];
   DWORD adwBestStress[2];
   DWORD adwPOS[2];
   DWORD adwStress[2];
   DWORD dwOffset;
   DWORD i;
   DWORD dwPros;
   PCTTSProsody pTTSP;
   PCListFixed plSI, *pplSI;
   int iCur, iRealStart;
   for (iRealStart = iStart; iRealStart < iEnd - 1; iRealStart++) {
      // if iRealStart+1 < 0 then will just be two punctuations, so no match
      if (iRealStart + 1 < 0)
         continue;

      // if iRealStart >= m_dwNum, then will be two punctiaons, so no match
      if (iRealStart >= (int)pSS->m_dwNum)
         continue;

      for (dwOffset = 0; dwOffset < 2; dwOffset++) {
         iCur = iRealStart + (int)dwOffset;

         if ((iCur < 0) || (iCur >= (int)pSS->m_dwNum)) {
            // out of range
            adwPOS[dwOffset] = POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION);
            adwStress[dwOffset] = 0;
         }
         else {
            adwPOS[dwOffset] = pSS->m_pabPOSStress[iCur] & 0x0f;
            adwStress[dwOffset] = (pSS->m_pabPOSStress[iCur] >> 4) & 0x0f;
         }
      } // dwOffset

      // loop through all the prosody models and sum up the number
      DWORD dwNumFind = 0;
      for (dwPros = 0; dwPros < dwNum; dwPros++) {
         pTTSP = ppCTTSProsody[dwPros];
         if (!pTTSP)
            continue;

         pTTSP->CreateIndexIfNecessary (pLexTTS, fWord);

         if (fWord)
            pplSI = &pTTSP->m_aplSENTENCEINDEXWord[adwPOS[0]][adwStress[0]][adwPOS[1]][adwStress[1]];
         else
            pplSI = &pTTSP->m_aplSENTENCEINDEX[adwPOS[0]][adwStress[0]][adwPOS[1]][adwStress[1]];
         plSI = *pplSI;
         
         dwNumFind += (plSI ? plSI->Num() : 0);
      } // dwPros

      // if find FEWER then keep that
      if ((iBestStart <= -1000) || (dwNumFind < dwBestStartScore)) {
         iBestStart = iRealStart;
         dwBestStartScore = dwNumFind;
         memcpy (adwBestPOS, adwPOS, sizeof(adwPOS));
         memcpy (adwBestStress, adwStress, sizeof(adwStress));
      }
   } // iRealStart
   if (iBestStart <= -1000)
      return FALSE;  // error

   // real start, so have something in the sentence to match
   iRealStart = iBestStart;
   int iRealStartDelta = iRealStart - iStart;
   memcpy (adwPOS, adwBestPOS, sizeof(adwPOS));
   memcpy (adwStress, adwBestStress, sizeof(adwStress));

   // search for these
   SENTENCEMATCH SM;
   DWORD j;
   memset (&SM, 0, sizeof(SM));
   for (dwPros = 0; dwPros < dwNum; dwPros++) {
      pTTSP = ppCTTSProsody[dwPros];
      if (!pTTSP)
         continue;

      // dont need to call here because already called: pTTSP->CreateIndexIfNecessary ();

      if (fWord)
         pplSI = &pTTSP->m_aplSENTENCEINDEXWord[adwPOS[0]][adwStress[0]][adwPOS[1]][adwStress[1]];
      else
         pplSI = &pTTSP->m_aplSENTENCEINDEX[adwPOS[0]][adwStress[0]][adwPOS[1]][adwStress[1]];
      plSI = *pplSI;

      plSENTENCEMATCH->Required (plSENTENCEMATCH->Num() + (plSI ? plSI->Num() : 0) );

      PSENTENCEINDEX pSI = plSI ? (PSENTENCEINDEX)plSI->Get(0) : NULL;
      if (plSI) for (i = 0; i < plSI->Num(); i++, pSI++) {
         int iCompareTo = pSI->iStart - iRealStartDelta;
         if (!pSS->CompareSequenceQuick (iStart, pSI->pSS, iCompareTo, (DWORD)iNum))
            continue;   // no match

         // if must match prhase then do some checks
         if (fMustMatchPhrase) {
            // if this matches an entire sentence then ok
            if (!iCompareTo && (iNum == (DWORD)pSI->pSS->m_dwNum))
               goto matched;

            // else, look through the phrases in the sentence for a match
            PSENTSYLPHRASE pSSP = (PSENTSYLPHRASE) pSI->pSS->m_lSENTSYLPHRASE.Get(0);
            for (j = 0; j < pSI->pSS->m_lSENTSYLPHRASE.Num(); j++, pSSP++)
               if ( ((int)pSSP->wStart == iCompareTo) && ((int)pSSP->wEnd == (iCompareTo + iNum)) )
                  break;
            if (j < pSI->pSS->m_lSENTSYLPHRASE.Num())
               goto matched;   // found a match

            // else no match
            continue;
         }

matched:
         // else, match
         SM.iStart = iCompareTo;
         SM.dwNum = iNum;
         SM.pSS = pSI->pSS;
         SM.pTTSProsody = pTTSP;
         plSENTENCEMATCH->Add (&SM);
      } // i
   } // dwPros

   return TRUE;
}



/*************************************************************************************
CTTSProsody::MaxJoinDistance - Returns the maximum join distance possible
given the TTS Quality.

inputs
   int               iTTSQuality - Typical TTS quality setting. Used to determine maximum
                        number of matches
                        0 for fast, 1 for normal, 2 for slow, 3 for best, 4 for multipass
returns
   DWORD - Max number of joins.
*/
DWORD CTTSProsody::MaxJoinDistance (int iTTSQuality)
{
   iTTSQuality = max(iTTSQuality, 0);
   iTTSQuality = min(iTTSQuality, 3);
   return 1 + (DWORD)iTTSQuality;
}



/*************************************************************************************
CTTSProsody::MaxMinMatches - Calculate the maximum and minimum number of matches
given TTS quality

inputs
   int               iTTSQuality - Typical TTS quality setting. Used to determine maximum
                        number of matches
                        0 for fast, 1 for normal, 2 for slow, 3 for best, 4 for multipass
   DWORD          *pdwMax - Filled with the maximum number of matches. Can be NULL
   DWORD          *pdwMin - Filled with the minimum number of matches. Can be NULL;
returns
   none
*/
void CTTSProsody::MaxMinMatches (int iTTSQuality, DWORD *pdwMax, DWORD *pdwMin)
{
   DWORD dwMin = MINPROSODYSAMPLES;
   if (iTTSQuality >= 2)
      dwMin *= 2; // more minimums required for higher quality
#ifdef _DEBUG
   // dwMin = 1; // to test
#endif

   // truncate so not too many units
   iTTSQuality = max(iTTSQuality, 0);
   iTTSQuality = min(iTTSQuality, 3);
   DWORD dwMax = MINPROSODYSAMPLES * (DWORD)(iTTSQuality+2);

   if (pdwMax)
      *pdwMax = dwMax;
   if (pdwMin)
      *pdwMin = dwMin;
}


/*************************************************************************************
CTTSProsody::ScoreMatches - Takes a list of matches from FindAllMatches() and does the
following:

- If there aren't any matches then returns FALSE
- If there aren't enough matches (and fIgnoreNotEnough == FALSE) then returns
- The list is shuffled
- If there are too many matches then the list is truncated
- All matches are compared against one another, genrating a 2x2 matrix of comparisons.
- For each match, the scores are sorted, and the top 25% (?) weighted are used
   for the final scored, stored back in the SENTENCEMATCH structure. This
   indicates how "typical" then sequence is.

NOTE: This code WON'T work as well with real-time mixed prosody models because it's
not remapping all the word numbers together. To do so would be too slow.

inputs
   PCOMPARESYLINFO pInfo - Score penalties to use.
   PCListFixed       plSENTENCEMATCH - List of matches. Modified in place. The number
                     of matches is halved to reduce calculation. The best scorind ones
                     are kept.
   int               iTTSQuality - Typical TTS quality setting. Used to determine maximum
                        number of matches
                        0 for fast, 1 for normal, 2 for slow, 3 for best, 4 for multipass
   WORD              wPeriod - What word number to use for a period.
   BOOL              fIgnoreNotEnough - If TRUE, then even if there aren't enough matches
                        then go ahead and do processing.
   BOOL              fIgnoreProsody - Set to TRUE if duration, energy, F0 are unknown in at least one of the setneces
   BOOL                 fWord - If TRUE matching against the word-based residitual. IF FALSE, the syllable-based ones.
   BOOL              fTriangle - If TRUE then compare using a triangle window. FALSE then
                        compare using a square window.
   PCSentenceSyllable pSSCompare - If not NULL, then do a non-prosodic compare against this
                     sentence to determine how close the sentence matches are to the target.
                     Include this in the score
   int               iStartCompare - Where comparing to against the start, in syllables. Only used if pSSCompare
returns
   BOOL - TRUE if succes. FALSE if there aren't enough matches
*/

// SSMATCHSORT - Structure used to sort the matches
typedef struct {
   fp             fScore;     // score
   DWORD          dwOrig;     // original index
} SSMATCHSORT, *PSSMATCHSORT;

static int _cdecl SSMATCHSORTSortScore (const void *elem1, const void *elem2)
{
   SSMATCHSORT *pdw1, *pdw2;
   pdw1 = (SSMATCHSORT*) elem1;
   pdw2 = (SSMATCHSORT*) elem2;

   if (pdw1->fScore > pdw2->fScore)
      return 1;
   else if (pdw1->fScore < pdw2->fScore)
      return -1;
   else
      return 0;
}



static int _cdecl SENTENCEMATCHSortScore (const void *elem1, const void *elem2)
{
   SENTENCEMATCH *pdw1, *pdw2;
   pdw1 = (SENTENCEMATCH*) elem1;
   pdw2 = (SENTENCEMATCH*) elem2;

   if (pdw1->fScore > pdw2->fScore)
      return 1;
   else if (pdw1->fScore < pdw2->fScore)
      return -1;
   else
      return 0;
}



BOOL CTTSProsody::ScoreMatches (PCOMPARESYLINFO pInfo, PCListFixed plSENTENCEMATCH, int iTTSQuality, WORD wPeriod,
                                BOOL fIgnoreNotEnough, BOOL fIgnoreProsody, BOOL fWord, BOOL fTriangle,
                                PCSentenceSyllable pSSCompare, int iStartCompare)
{
   // if 0 matches then always FALSE
   if (!plSENTENCEMATCH->Num())
      return FALSE;

   DWORD dwMin, dwMax;
   MaxMinMatches (iTTSQuality, &dwMax, &dwMin);
   if (!fIgnoreNotEnough && (plSENTENCEMATCH->Num() < dwMin))
      return FALSE;

   // randomize this list
   PSENTENCEMATCH pSI = (PSENTENCEMATCH) plSENTENCEMATCH->Get(0);
   SENTENCEMATCH SITemp;
   DWORD i, j;
   for (i = 0; i < plSENTENCEMATCH->Num(); i++) {
      j = (DWORD)rand() % plSENTENCEMATCH->Num();

      // shuffle
      SITemp = pSI[i];
      pSI[i] = pSI[j];
      pSI[j] = SITemp;
   } // i

   // truncate so not too many units
   dwMax = min(dwMax, plSENTENCEMATCH->Num());
   if (dwMax < plSENTENCEMATCH->Num())
      plSENTENCEMATCH->Truncate (dwMax);
   pSI = (PSENTENCEMATCH) plSENTENCEMATCH->Get(0);

   // allocate memory for the comparisons
   DWORD dwNum = plSENTENCEMATCH->Num();
   CMem memScores;
   if (!memScores.Required (dwNum * dwNum * sizeof(SSMATCHSORT)))
      return FALSE;  // error
   PSSMATCHSORT paSSMATCHSORT = (PSSMATCHSORT)memScores.p;

   // make the window
   CMem memWindow;
   fp *pafWindow = pSI[0].pSS->CompareWindowGenerate (0, (int)pSI[0].dwNum, fTriangle, &memWindow);
   if (!pafWindow)
      return FALSE;  // error

   // loop over all combinations and generate the scores
   fp fScore;
   for (i = 0; i < dwNum; i++) {
      for (j = 0; j <= i; j++) {
         // if same unit, trivial match
         if (i == j) {
            paSSMATCHSORT[i * dwNum + j].fScore = 0.0;
            continue;
         }

         // else, compare
         fScore = pSI[i].pSS->CompareSequenceWindowed (pInfo, pSI[i].iStart, pSI[j].pSS, pSI[j].iStart, pSI[i].dwNum,
            wPeriod, fIgnoreProsody, fWord, pafWindow);

         // store this away
         paSSMATCHSORT[i * dwNum + j].fScore = fScore;
         paSSMATCHSORT[j * dwNum + i].fScore = fScore;
      } // j
   } // i

   // how many in the top
   DWORD dwTop = (dwNum + 3) / 4;   // take the top 25%
   dwTop = max(dwTop, 2);  // at least 2
   dwTop = min(dwTop, dwNum); // but no more than all of them

   // loop over all the scores: sort and weighted average the top
   for (i = 0; i < dwNum; i++) {
      // fill in the original ID
      for (j = 0; j < dwNum; j++)
         paSSMATCHSORT[i*dwNum + j].dwOrig = j;

      qsort (paSSMATCHSORT + (i * dwNum), dwNum, sizeof(SSMATCHSORT), SSMATCHSORTSortScore);

      // loop over the top elements and average their scores together
      fp fSum = 0.0;
      fp fWeightSum = 0.0;
      for (j = 0; j < dwTop; j++) {
         fp fWeight = dwTop - j + 1;
         fSum += paSSMATCHSORT[i*dwNum + j].fScore * fWeight;
         fWeightSum += fWeight;
      }

      // store weighted version away
      pSI[i].fScore = fSum / fWeightSum;

      // include comparison against target?
      if (pSSCompare) {
         pSI[i].fScore += pSSCompare->CompareSequenceWindowed (pInfo, iStartCompare,
            pSI[i].pSS, pSI[i].iStart, pSI[i].dwNum,
            wPeriod, TRUE, fWord, pafWindow);
               // NOTE: Always ignoring prosody in this case
      }

      // remember the second best
      if (dwNum >= 2) {
         // take the best one which isn't this
         DWORD dwRunnerUpIndex;
         if (paSSMATCHSORT[i*dwNum+0].dwOrig == i)
            dwRunnerUpIndex = paSSMATCHSORT[i*dwNum+1].dwOrig;
         else
            dwRunnerUpIndex = paSSMATCHSORT[i*dwNum+0].dwOrig;
         pSI[i].pSSRunnerUp = pSI[dwRunnerUpIndex].pSS;
         pSI[i].iStartRunnerUp = pSI[dwRunnerUpIndex].iStart;
      }
      else {
         pSI[i].pSSRunnerUp = NULL;
         pSI[i].iStartRunnerUp = 0;
      }
   } // i

   // sort all the score matches so the lower scores at top
   qsort (pSI, dwNum, sizeof(*pSI), SENTENCEMATCHSortScore);

   // halve this
   dwNum = (dwNum+1) / 2;
   plSENTENCEMATCH->Truncate (dwNum);
   
   // done
   return TRUE;
}


/*************************************************************************************
CTTSProsody::SubdivideAddAsSingle - Add as single trianglular-shaped sentence matches.

inputs
   PCSentenceSyllable         pSS - Sentence whose prosody is to be generated.
   DWORD                dwStartSyl - Starting syllable to process
   DWORD                dwEndSyl - End syllable (exclusive) to process
   DWORD                dwWindow - Size of the triangluar window, based on iTTSQuality
   PCListFixed          plPCSentenceMatch - List to which new PCSentenceMatch structures
                        are appended with appropriate search info.
returns
   none
*/
void CTTSProsody::SubdivideAddAsSingle (PCSentenceSyllable pSS, DWORD dwStartSyl, DWORD dwEndSyl,
                                        DWORD dwWindow, PCListFixed plPCSentenceMatch)
{
   DWORD i;
   PCSentenceMatch pSM;
   for (i = dwStartSyl; i < dwEndSyl; i++) {
      pSM = new CSentenceMatch;
      if (!pSM)
         return;  // error
      
      // fill in
      pSM->m_fIgnoreNotEnough = FALSE;
      pSM->m_fIgnoreProsody = FALSE;
      pSM->m_fTriangle = TRUE;
      pSM->m_iStartUse = (int)i;
      pSM->m_iEndUse = (int)pSM->m_iStartUse + 1;
      pSM->m_iEnd = pSM->m_iEndUse + (int)dwWindow;
      pSM->m_iStart = pSM->m_iStartUse - (int)dwWindow;
      pSM->m_fCompareAgainstTarget = TRUE;   // compare this against the target
      if (dwWindow <= 1)
         pSM->m_fIgnoreNotEnough = TRUE;  // down to the last dregs

      // add this to list
      plPCSentenceMatch->Add (&pSM);
   } // i
}


/*************************************************************************************
CTTSProsody::SubdivideSentenceByPhrases - This recusrively subdivides sentences
by phrases, and adds the phrases (and non-phrase bits) into plPCSentenceMatch.

inputs
   PCSentenceSyllable         pSS - Sentence whose prosody is to be generated.
   DWORD                dwStartSyl - Starting syllable to process
   DWORD                dwEndSyl - End syllable (exclusive) to process
   DWORD                dwWindow - Size of the triangluar window, based on iTTSQuality
   BOOL                 fCanIncludeAll - If TRUE, can include this entire range as a phrase.
   PCListFixed          plPCSentenceMatch - List to which new PCSentenceMatch structures
                        are appended with appropriate search info.
returns
   none
*/
void CTTSProsody::SubdivideSentenceByPhrases (PCSentenceSyllable pSS, DWORD dwStartSyl, DWORD dwEndSyl, DWORD dwWindow,
                                              BOOL fCanIncludeAll, PCListFixed plPCSentenceMatch)
{
   if (dwEndSyl <= dwStartSyl)
      return;  // too small

   // if this is the entire sentence, and we're allowed to include all, then do so
   PCSentenceMatch pSM;
   PSENTSYLPHRASE pSSPBest = NULL;
   SENTSYLPHRASE SSPHack;
   if (!dwStartSyl && (dwEndSyl == pSS->m_dwNum) && fCanIncludeAll) {
      SSPHack.wStart = (WORD)dwStartSyl;
      SSPHack.wEnd = (WORD)dwEndSyl;
      pSSPBest = &SSPHack;
      goto addphrasetolist;
   }

   // otherwise, loop through and find the largest parse phrase in the sentence
   // that also fits in the dwStartSyl to dwEndSyl range
   DWORD i;
   PSENTSYLPHRASE pSSP = (PSENTSYLPHRASE)pSS->m_lSENTSYLPHRASE.Get(0);
   DWORD dwBestLength = 0;
   for (i = 0; i < pSS->m_lSENTSYLPHRASE.Num(); i++, pSSP++) {
      // make sure it's in range
      if ((DWORD)pSSP->wEnd > dwEndSyl)
         continue;   // out of range
      if ((DWORD)pSSP->wStart < dwStartSyl)
         continue;
      if (pSSP->wEnd <= pSSP->wStart)
         continue;   // shouldnt happen, but just in case

      // if not allowed to take an exact match, and this is one, then skip
      if (!fCanIncludeAll && ((DWORD)pSSP->wStart == dwStartSyl) && ((DWORD)pSSP->wEnd == dwEndSyl))
         continue;

      // is this the longest
      DWORD dwLength = pSSP->wEnd - pSSP->wStart;
      if (!pSSPBest || (dwLength > dwBestLength)) {
         pSSPBest = pSSP;
         dwBestLength = dwLength;
      }
   } // i

   // if there aren't any matches, then add all
   if (!pSSPBest) {
      SubdivideAddAsSingle (pSS, dwStartSyl, dwEndSyl, dwWindow, plPCSentenceMatch);
      return;
   }
   
addphrasetolist:
   // else, found at least one matching phrase, add that
   pSM = new CSentenceMatch;
   if (!pSM)
      return;  // error
   
   // fill in
   pSM->m_fIgnoreNotEnough = FALSE;
   pSM->m_fIgnoreProsody = FALSE;
   pSM->m_fTriangle = FALSE;
   pSM->m_iStartUse = (int)pSSPBest->wStart;
   pSM->m_iEndUse = (int)pSSPBest->wEnd;
   pSM->m_iEnd = pSM->m_iEndUse;
   pSM->m_iStart = pSM->m_iStartUse;
   pSM->m_fCompareAgainstTarget = TRUE;   // compare this against the target

   // add this to list
   plPCSentenceMatch->Add (&pSM);

   // recurse, and add any bits to the left or right
   SubdivideSentenceByPhrases (pSS, dwStartSyl, pSSPBest->wStart, dwWindow, TRUE, plPCSentenceMatch);
   SubdivideSentenceByPhrases (pSS, pSSPBest->wEnd, dwEndSyl, dwWindow, TRUE, plPCSentenceMatch);

   // done
}


/*************************************************************************************
CTTSProsody::GenerateMatches - This takes a PCSentenceSyllable with the sentence
to synthesize. It then fills in two lists. The first is a list of PCSentenceMatch
covering the sentence from start to end, indicating what sequences from the prosody
model could be used to synthesize the prosody. The second is a list of PCSentenceMatch
that indicates reasonable joins between units.

NOTE: This doesn't do word # translation if there is more than one prosody model.
This means that muliple run-time prosodies won't work as well because of possible
word mismatches. However, doing the remapping would be too slow.

inputs
   PCMLexicon        pLexTTS - TTS With phonemes in it
   PCOMPARESYLINFO pInfo - Score penalties to use.
   PCSentenceSyllable         pSS - Sentence whose prosody is to be generated.
   int               iTTSQuality - Typical TTS quality setting. Used to determine maximum
                        number of matches
                        0 for fast, 1 for normal, 2 for slow, 3 for best, 4 for multipass
   BOOL           fWord - if TRUE then making sure the word-based sentences are used.
                     If FALSE, then syllable-based sentences
   CTTSProsody          **ppCTTSProsody - This of prosody objects to use for the comparison,
                        including this one.
   DWORD                dwNum - Number of prosody objects to use. Must be at least one
   DWORD                dwMultiPass - Pass number when generating several different versions of the
                        same sentence, and selecting the best one for TTS. Used to make the
                        random work better.
   PCListFixed          plResynth - Initialized to sizeof(PCSentenceMatch), and filled in
                        with list of matches covering entire sentence. (Some matches may NOT
                        have any matches in their list though.) This needs to be freed by the caller.
   PCListFixed          plJoin - List of PCSentenceMatch used to join plResynth[i-1] with plResythn[i].
                        There will be one more plJoin->Num() than plResynth()->Num() because of this.
                        Elements must be freed by the caller.
returns
   BOOL - TRUE if success
*/


static int _cdecl PCSentenceMatchSortScore (const void *elem1, const void *elem2)
{
   PCSentenceMatch pdw1, pdw2;
   pdw1 = *((PCSentenceMatch*) elem1);
   pdw2 = *((PCSentenceMatch*) elem2);

   return pdw1->m_iStartUse - pdw2->m_iStartUse;
}


BOOL CTTSProsody::GenerateMatches (PCMLexicon pLexTTS, PCOMPARESYLINFO pInfo, PCSentenceSyllable pSS, int iTTSQuality, BOOL fWord,
                                   CTTSProsody **ppCTTSProsody, DWORD dwNum, DWORD dwMultiPass, PCListFixed plResynth, PCListFixed plJoin)
{
   plResynth->Init (sizeof(PCSentenceMatch));
   plResynth->Required (pSS->m_dwNum);
   plJoin->Init (sizeof(PCSentenceMatch));
   plJoin->Required (pSS->m_dwNum+1);

   // default L/R match depends on quality
   DWORD dwWindow = MaxJoinDistance (iTTSQuality);

   // loop through all the language models making sure they've calculated all the indecies
   // before go multithreaded
   DWORD i;
   for (i = 0; i < dwNum; i++) {
      if (fWord)  // if doing word matching, make sure produced
         ppCTTSProsody[i]->CreateWordSentencesIfNeccssary ();
      ppCTTSProsody[i]->CreateIndexIfNecessary (pLexTTS, fWord);
   }

   // create all the sentence match, one per
   PCSentenceMatch pSM;
   CListFixed lTemp, lBad;
   lTemp.Init (sizeof(PCSentenceMatch));
   lBad.Init (sizeof(PCSentenceMatch));
   SubdivideSentenceByPhrases (pSS, 0, pSS->m_dwNum, dwWindow, TRUE, &lTemp);
   //for (i = 0; i < pSS->m_dwNum; i++) {
   //   pSM = new CSentenceMatch;
   //   if (!pSM)
   //      return FALSE;
      
   //   // fill in
   //   pSM->m_fIgnoreNotEnough = FALSE;
   //   pSM->m_fIgnoreProsody = FALSE;
   //   pSM->m_fTriangle = TRUE;
   //   pSM->m_iStartUse = (int)i;
   //   pSM->m_iEndUse = (int)pSM->m_iStartUse + 1;
   //   pSM->m_iEnd = pSM->m_iEndUse + (int)dwWindow;
   //   pSM->m_iStart = pSM->m_iStartUse - (int)dwWindow;
   //   pSM->m_fCompareAgainstTarget = TRUE;   // compare this against the target
   //   if (dwWindow <= 1)
   //      pSM->m_fIgnoreNotEnough = TRUE;  // down to the last dregs

   //   // add this to list
   //   lTemp.Add (&pSM);
   //} // i

#ifdef _DEBUG
   WCHAR szTemp[256];
#endif

   // repeat, calling multithreaded code until all matches have been found
   PCSentenceMatch *ppSM;

   // fill in multithreaded info
   EMTSENTENCEMATCH em;
   memset (&em, 0, sizeof(em));
   em.dwType = 100;
   em.pInfo = pInfo;
   em.plPCSentenceMatch = &lTemp;
   em.fWord = fWord;
   em.ppCTTSProsody = ppCTTSProsody;
   em.dwNum = dwNum;
   em.pSS = pSS;
   em.iTTSQuality = iTTSQuality;
   em.wPeriod = (WORD) m_pLexInProsody->WordFind (L".");
   em.dwMultiPass = dwMultiPass;
   em.pLexTTS = pLexTTS;

   // find matches for the syllables in the sentences
   while (lTemp.Num()) {
      // call
      ThreadLoop (0, lTemp.Num(), 1, &em, sizeof(em), NULL);

      // move ones that success over
      ppSM = (PCSentenceMatch*)lTemp.Get(0);
      for (i = 0; i < lTemp.Num(); i++) {
         pSM = ppSM[i];

#ifdef _DEBUG
         swprintf (szTemp, L"\r\nGenerateMatches - %s %s %d to %d (%d - %d, size=%d), %d matches",
            pSM->m_fTriangle ? L"Single-syllable" : L"Phrase",
            (pSM->m_fSuccess || pSM->m_fIgnoreNotEnough) ? L"Success!" : L"Failure",
            (int)pSM->m_iStartUse, (int)pSM->m_iEndUse, (int)pSM->m_iStart, (int)pSM->m_iEnd,
            (int)(pSM->m_iEnd - pSM->m_iStart), (int)pSM->m_lSENTENCEMATCH.Num());
         OutputDebugStringW (szTemp);
#endif
         // if succeded then move, or if last attempt
         if (pSM->m_fSuccess || pSM->m_fIgnoreNotEnough) {

            // add
            plResynth->Add (&pSM);

            // remove from this list
            lTemp.Remove (i);
            i--;
            ppSM = (PCSentenceMatch*)lTemp.Get(0);

            continue;
         }

         // else, didn't work! need to try a smaller search

         // if this is a phrase that didn't succede then add to lBad list and deal with in a moment
         if (!pSM->m_fTriangle) {
            lBad.Add (&pSM);
            lTemp.Remove (i);
            i--;
            ppSM = (PCSentenceMatch*)lTemp.Get(0);
            continue;
         }

         // narrow the search space
         int iToRight = pSM->m_iEnd - pSM->m_iEndUse;
         int iToLeft = pSM->m_iStartUse - pSM->m_iStart;
         if (iToRight > iToLeft)
            pSM->m_iEnd--;
         else if (iToRight < iToLeft)
            pSM->m_iStart++;
         else
            pSM->m_iEnd--; // decrease from the end first

         pSM->m_fIgnoreNotEnough = (pSM->m_iStart >= pSM->m_iStartUse - 1) && (pSM->m_iEnd <= pSM->m_iEndUse + 1);
      } // i

      // if any in bad list then subdivide them into smaller rules, or individual syllables
      ppSM = (PCSentenceMatch*)lBad.Get(0);
      for (i = 0; i < lBad.Num(); i++) {
         pSM = ppSM[i];

         SubdivideSentenceByPhrases (pSS, (DWORD) max(pSM->m_iStartUse, 0), (DWORD) min(pSM->m_iEndUse, (int)pSS->m_dwNum),
            dwWindow, FALSE, &lTemp);

         delete pSM;
      } // i
      lBad.Clear();
   } // while lTemp.Num();

   // sort by m_iStartUse so have in order of generation
   qsort (plResynth->Get(0), plResynth->Num(), sizeof(PCSentenceMatch), PCSentenceMatchSortScore);

   // generate joins
   ppSM = (PCSentenceMatch*)plResynth->Get(0);
   for (i = 0; i <= plResynth->Num(); i++) {
      pSM = new CSentenceMatch;
      if (!pSM)
         return FALSE;
      
      // fill in
      pSM->m_fIgnoreNotEnough = FALSE;
      pSM->m_fIgnoreProsody = FALSE;
      pSM->m_fTriangle = TRUE;
      pSM->m_iStartUse = i ? ppSM[i-1]->m_iEndUse : 0;
      pSM->m_iEndUse = (int)pSM->m_iStartUse;   // NOTE: NOT adding 1, want joins to be 0-sized
      pSM->m_iEnd = pSM->m_iEndUse + (int)dwWindow;
      pSM->m_iStart = pSM->m_iStartUse - (int)dwWindow;
      pSM->m_fCompareAgainstTarget = FALSE;  // don't compare against target, since need to compare against final audio
      if (dwWindow <= 1)
         pSM->m_fIgnoreNotEnough = TRUE;  // down to the last dregs

      // add this to list
      lTemp.Add (&pSM);
   } // i

   // find best joins
   while (lTemp.Num()) {
      // call
      ThreadLoop (0, lTemp.Num(), 1, &em, sizeof(em), NULL);

      // move ones that success over
      ppSM = (PCSentenceMatch*)lTemp.Get(0);
      for (i = 0; i < lTemp.Num(); i++) {
         pSM = ppSM[i];

#ifdef _DEBUG
         swprintf (szTemp, L"\r\nGenerateMatches - Joins - %s %d to %d (%d - %d, size=%d), %d matches",
            (pSM->m_fSuccess || pSM->m_fIgnoreNotEnough) ? L"Success!" : L"Failure",
            (int)pSM->m_iStartUse, (int)pSM->m_iEndUse, (int)pSM->m_iStart, (int)pSM->m_iEnd,
            (int)(pSM->m_iEnd - pSM->m_iStart), (int)pSM->m_lSENTENCEMATCH.Num());
         OutputDebugStringW (szTemp);
#endif
         // if succeded then move, or if last attempt
         if (pSM->m_fSuccess || pSM->m_fIgnoreNotEnough) {

            // add
            plJoin->Add (&pSM);

            // remove from this list
            lTemp.Remove (i);
            i--;
            ppSM = (PCSentenceMatch*)lTemp.Get(0);

            continue;
         }

         // else, didn't work! need to try a smaller search
         // narrow the search space
         int iToRight = pSM->m_iEnd - pSM->m_iEndUse;
         int iToLeft = pSM->m_iStartUse - pSM->m_iStart;
         if (iToRight > iToLeft)
            pSM->m_iEnd--;
         else if (iToRight < iToLeft)
            pSM->m_iStart++;
         else
            pSM->m_iEnd--; // decrease from the end first

         pSM->m_fIgnoreNotEnough = (pSM->m_iStart >= pSM->m_iStartUse - 1) && (pSM->m_iEnd <= pSM->m_iEndUse + 1);
      } // i
   } // while lTemp.Num();

   // resort joins
   qsort (plJoin->Get(0), plJoin->Num(), sizeof(PCSentenceMatch), PCSentenceMatchSortScore);

   // done
   return TRUE;
}


/*************************************************************************************
CTTSProsody::WordSylGetInternal - Internal method, given a string, this returns the TTSPWORDSYLHEADER
for it.

inputs
   PCWSTR         psz - String for the hash
   DWORD          dwSyllables - Number of syllables
   DWORD          dwStressBits - Stress bits to look for, as per TTSPWORDSYLHEADER.dwStressBits
   BOOL           fCreateIfNotExist - If TRUE, and can't find existing element, then
                  create one if it doesn't exist.
returns
   PTTSPWORDSYLHEADER - Information, or NULL if can't find. The pointer will become
      unvalid if add any more word syllables.
*/
PTTSPWORDSYLHEADER CTTSProsody::WordSylGetInternal (PCWSTR psz, DWORD dwSyllables,
                         DWORD dwStressBitsMulti, BOOL fCreateIfNotExist)
{
   // make sure has entry
   PCHashString phd;
   while (m_lPCHashStringWordSyl.Num() < dwSyllables) {
      if (!fCreateIfNotExist)
         return NULL;

      // create
      phd = new CHashString;
      if (!phd)
         return NULL;
      phd->Init (sizeof(TTSPWORDSYLHEADER) + dwSyllables * sizeof(PROSODYTREND));
      m_lPCHashStringWordSyl.Add (&phd);
   }

   PCHashString *pphd = (PCHashString*)m_lPCHashStringWordSyl.Get(0);
   phd = pphd[dwSyllables-1];

   // get the info
   PTTSPWORDSYLHEADER pwsh = (PTTSPWORDSYLHEADER) phd->Find ((PWSTR)psz);
   if (pwsh)
      return pwsh;
   if (!fCreateIfNotExist)
      return NULL;   // dont create

   // else, create
   CMem mem;
   if (!mem.Required (sizeof(TTSPWORDSYLHEADER) + dwSyllables * sizeof(PROSODYTREND)))
      return NULL;
   pwsh = (PTTSPWORDSYLHEADER)mem.p;
   memset (pwsh, 0, sizeof(TTSPWORDSYLHEADER) + dwSyllables * sizeof(PROSODYTREND));
   pwsh->dwStressBitsMulti = dwStressBitsMulti;
   pwsh->dwSyllables = dwSyllables;
   // pwsh->dwTrainCount = 0;
   // pwsh->dwWordID = dwWordID;
   phd->Add ((PWSTR)psz, pwsh);
   pwsh = (PTTSPWORDSYLHEADER) phd->Find ((PWSTR)psz);

   return pwsh;
}


/*************************************************************************************
CTTSProsody::WordSylGet - Given a word string (or POS number) his returns the TTSPWORDSYLHEADER
for it.

inputs
   PCMLexicon     pLexTTS - TTS with phonemes in it
   PCWSTR         pszWord - String for the hash. If NULL, the use POS number
   DWORD          dwPOS - Only used if !pszWord. POS number, from 0..POS_MAJOR_NUMPLUSONE-1
   DWORD          dwSyllables - Number of syllables
   DWORD          dwStressBits - Stress bits to look for, as per TTSPWORDSYLHEADER.dwStressBits
   BOOL           fCreateIfNotExist - If TRUE, and can't find existing element, then
                  create one if it doesn't exist.
returns
   PTTSPWORDSYLHEADER - Information, or NULL if can't find. The pointer will become
      invalid if add any more word syllables.
*/
PTTSPWORDSYLHEADER CTTSProsody::WordSylGet (PCMLexicon pLexTTS, PCWSTR pszWord, DWORD dwPOS, DWORD dwSyllables,
                         DWORD dwStressBitsMulti, BOOL fCreateIfNotExist)
{
   WCHAR szTemp[256];
   if (pszWord && (wcslen(pszWord) > sizeof(szTemp)/sizeof(WCHAR)/2))
      return NULL;   // word too large

   if ((dwSyllables <= 1) && !pLexTTS->ChineseUse())
      dwStressBitsMulti = 1; // just to make sure

   if (pszWord)
      swprintf (szTemp, L"%s:%d", pszWord, (int)dwStressBitsMulti);
   else {
      if (dwPOS == POS_MAJOR_EXTRACT(POS_MAJOR_UNKNOWN))
         dwPOS = POS_MAJOR_EXTRACT(POS_MAJOR_NOUN);  // if unknown then default to noun
      swprintf (szTemp, L"@%d:%d", (int)dwPOS, (int)dwStressBitsMulti);
   }

   return WordSylGetInternal (szTemp, dwSyllables, dwStressBitsMulti, fCreateIfNotExist);
}


/*************************************************************************************
CTTSProsody::TYPICALSYLINFOAverage - Average one or more TYPICALSYLINFO from several
different syllables.

inputs
   DWORD          dwSyllables - Number of syllables
   PTYPICALSYLINFO pass - Array of dwSyllables TYPICALSYLINFO..
   PTYPICALSYLINFO passAvg - Filled in.
returns
   none
*/
void CTTSProsody::TYPICALSYLINFOAverage (DWORD dwSyllables, PTYPICALSYLINFO pass,
                                         PTYPICALSYLINFO passAvg)
{
   memset (passAvg, 0, sizeof(*passAvg));

   DWORD i;
   for (i = 0; i < dwSyllables; i++, pass++) {
      fp fScale = pass->fCount;
      if (fScale)
         fScale = 1.0 / fScale;
      else
         fScale = 1.0;

      DWORD dwPitchPoint;
      for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
         passAvg->afPitchF0ExtraSum[dwPitchPoint] += pass->afPitchF0ExtraSum[dwPitchPoint] * fScale;
      passAvg->fDurPhoneSum += pass->fDurPhoneSum * fScale;
      passAvg->fDurSkewSum += pass->fDurSkewSum * fScale;
      passAvg->fDurSylSum += pass->fDurSylSum * fScale;
      passAvg->fPitchF0FujiDeltaSum += pass->fPitchF0FujiDeltaSum * fScale;
      passAvg->fPitchF0FujiSum += pass->fPitchF0FujiSum * fScale;
      passAvg->fVolumeSum += pass->fVolumeSum * fScale;
      passAvg->fAccentSum += pass->fAccentSum * fScale;
      passAvg->fCount++;
   } // i
}


/*************************************************************************************
CTTSProsody::TYPICALSYLINFOToSENTSYLEMPH - Convert a TYPICALSYLINFO to a SENTSYLEMPH

inputs
   PTYPICALSYLINFO         pTSI - Info
   PSENTSYLEMPH            pEmph - Where to fill into
returns
   none
*/
void CTTSProsody::TYPICALSYLINFOToSENTSYLEMPH (PTYPICALSYLINFO pTSI, PSENTSYLEMPH pEmph)
{
   fp fScale = pTSI->fCount;
   if (fScale)
      fScale = 1.0 / fScale;
   else
      fScale = 1.0;

   DWORD dwPitchPoint;
   for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
      pEmph->afPitchF0Extra[dwPitchPoint] = pTSI->afPitchF0ExtraSum[dwPitchPoint] * fScale / log(2.0);
      // BUGFIX - divide by log(2) to keep right units
   pEmph->fPitchF0FujiDelta = pTSI->fPitchF0FujiDeltaSum * fScale / log(2.0);
      // BUGFIX - divide by log(2) to keep right units
   pEmph->fPitchF0Fuji = exp(pTSI->fPitchF0FujiSum * fScale);
   pEmph->fVolume = exp(pTSI->fVolumeSum * fScale);
   pEmph->fAccent = pTSI->fAccentSum * fScale;
   pEmph->fDurPhone = exp(pTSI->fDurPhoneSum * fScale);
   pEmph->fDurSyl = exp(pTSI->fDurSylSum * fScale);
   pEmph->fDurSkew = pTSI->fDurSkewSum * fScale / log(2.0);
      // BUGFIX - Divide by log(2) to keep right units
   // Emph.Emph.fPitchBulge += pTSI->fPitchBulgeSum;
   // Emph.Emph.fPitchSweep += pTSI->fPitchSweepSum;
}


/*************************************************************************************
CTTSProsody::WordSylTrain - Trains a wordsyl information (WordSylGet) based
on a SENTENCESYLLABLE.

inputs
   PCMLexicon     pLexTTS - Lexicon with phonemes in it
   DWORD          dwSyllables - Number of syllables
   PTYPICALSYLINFO pass - Array of dwSyllables TYPICALSYLINFO..
   PCWSTR         pszWord - String for the hash. If NULL, the use POS number
   DWORD          dwPOS - Only used if !pszWord. POS number, from 0..POS_MAJOR_NUMPLUSONE-1
   DWORD          dwStressBitsMulti - Stress bits to look for, as per TTSPWORDSYLHEADER.dwStressBitsMulti

   DWORD          dwSyllablesLeft - Number of syllables in left
   PTYPICALSYLINFO passLeft - Array of TYICALSYLINFO for left
   DWORD          dwPOSLeft - POS for left, 0..POS_MAJOR_NUMPLUSONE-1
   DWORD          dwSyllablesRight - Number of syllables in left
   PTYPICALSYLINFO passRight - Array of TYICALSYLINFO for left
   DWORD          dwPOSRight - POS for left, 0..POS_MAJOR_NUMPLUSONE-1

returns
   BOOL - TRUE if success
*/
BOOL CTTSProsody::WordSylTrain (PCMLexicon pLexTTS, DWORD dwSyllables, PTYPICALSYLINFO pass, PCWSTR pszWord,
                                DWORD dwPOS, DWORD dwStressBitsMulti,
                                DWORD dwSyllablesLeft, PTYPICALSYLINFO passLeft, DWORD dwPOSLeft,
                                DWORD dwSyllablesRight, PTYPICALSYLINFO passRight, DWORD dwPOSRight
                                )
{
   PTTSPWORDSYLHEADER pwsh = WordSylGet (pLexTTS, pszWord, dwPOS, dwSyllables, dwStressBitsMulti, TRUE);
   if (!pwsh)
      return FALSE;

   // all the individual syllables
   DWORD i;
   PPROSODYTREND ppt = (PPROSODYTREND) (pwsh+1);
   for (i = 0; i < dwSyllables; i++, ppt++) {
      ppt->iCount ++;
      ppt->iDurPhone += (int)(pass[i].fDurPhoneSum / pass[i].fCount / log(2.0) * 50.0); // (int)(log((fp)pass[i].bDurPhone / 100.0) / log(2.0) * 50.0);
      ppt->iDurSkew += (int)(pass[i].fDurSkewSum / pass[i].fCount / log(2.0) * 100.0); // pass[i].cDurSkew;
      ppt->iDurSyl += (int)(pass[i].fDurSylSum / pass[i].fCount / log(2.0) * 50.0); // (int)(log((fp)pass[i].bDurSyl / 100.0) / log(2.0) * 50.0);

      DWORD dwPitchPoint;
      for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
         ppt->aiPitchF0Extra[dwPitchPoint] += (int)(pass[i].afPitchF0ExtraSum[dwPitchPoint] / pass[i].fCount / log(2.0) * 100.0);
      ppt->iPitchF0Fuji += (int)(pass[i].fPitchF0FujiSum / pass[i].fCount / log(2.0) * 100.0); // (int)(log((fp)pass[i].bPitch / 100.0) / log(2.0) * 100.0);
      ppt->iPitchF0FujiDelta += (int)(pass[i].fPitchF0FujiDeltaSum / pass[i].fCount / log(2.0) * 100.0);
      // ppt->iPitchBulge += (int)(pass[i].fPitchBulgeSum / pass[i].fCount / log(2.0) * 100.0); // pass[i].cPitchBulge;
      // ppt->iPitchSweep += (int)(pass[i].fPitchSweepSum / pass[i].fCount / log(2.0) * 100.0); // pass[i].cPitchSweep;
      // ppt->iPitchRelative - not set
      ppt->iVol += (int)(pass[i].fVolumeSum / pass[i].fCount / log(2.0) * 50.0); // (int)(log((fp)pass[i].bVol / 100.0) / log(2.0) * 50.0);
      ppt->iAccent += (int)(pass[i].fAccentSum / pass[i].fCount * 100.0);
   } // i

   // might not want to train
   if ((pwsh->dwTrainCount >= WORDSENTSYL_KEEPEXAMPLES) && (randf(0.0, 1.0) > (fp)WORDSENTSYL_KEEPEXAMPLES / (fp)(pwsh->dwTrainCount+1)) ) {
      pwsh->dwTrainCount++;
      return TRUE;   // randomly not selected to exist
   }
   pwsh->dwTrainCount++;

   // keep examples around
   if (!pwsh->pss)
      pwsh->pss = new CSentenceSyllable;
   if (!pwsh->pss)
      return TRUE;   // error, but ignore this

   // how many syllables in this
   DWORD dwExampleSyl = dwSyllables + 2; // left and righ

   // remove if too many examples
   while ((pwsh->pss->m_dwNum / dwExampleSyl + 1) > WORDSENTSYL_KEEPEXAMPLES) {
      DWORD dwIndex = rand() % (pwsh->pss->m_dwNum / dwExampleSyl);
      pwsh->pss->Remove (dwIndex * dwExampleSyl, (dwIndex+1) * dwExampleSyl);
   } // remove

   // average out the left and right syllables
   TYPICALSYLINFO TSILeft, TSIRight;
   TYPICALSYLINFOAverage (dwSyllablesLeft, passLeft, &TSILeft);
   TYPICALSYLINFOAverage (dwSyllablesRight, passRight, &TSIRight);

   // add these

   // left
   SENTSYLEMPH sse;
   TYPICALSYLINFOToSENTSYLEMPH (&TSILeft, &sse);
   pwsh->pss->Add ((DWORD)-1, dwPOSLeft, 0, 0, &sse, 0, 1, 0, 0, 0);

   // main syllables
   for (i = 0; i < dwSyllables; i++) {
      TYPICALSYLINFOToSENTSYLEMPH (pass + i, &sse);
      pwsh->pss->Add ((DWORD)-1, dwPOS, 0, 0, &sse, 0, 1, 0, 0, 0);
   } // i

   // right
   TYPICALSYLINFOToSENTSYLEMPH (&TSIRight, &sse);
   pwsh->pss->Add ((DWORD)-1, dwPOSRight, 0, 0, &sse, 0, 1, 0, 0, 0);

   return TRUE;
}


/*************************************************************************************
CTTSProsody::WordSylGetBestExample - Find the best example version of the word.

inputs
   PCOMPARESYLINFO pInfo - COmparison info to use (kind of)
   PCMLexicon     pLexTTS - Lexicon with phonemes in it
   PCWSTR         pszWord - String for the hash. If NULL, then assume unknown word
   DWORD          dwPOS - POS number, from 0..POS_MAJOR_NUMPLUSONE-1
   DWORD          dwSyllables - Number of syllables
   DWORD          dwStressBits - Stress bits to look for, as per TTSPWORDSYLHEADER.dwStressBits.

   CTTSProsody    **ppCTTSProsody - List of other prosody models, including this one
   DWORD          dwNum - Number of other prosody models

   PSENTSYLEMPH      pssLeft - Average of syllables in left word
   PSENTSYLEMPH      pssThis - Average of syllables in this word
   PSENTSYLEMPH      pssRight - Average of syllables in right word

   PSENTSEYLEMPH     passResult - If returns TRUE, this is filled in with the best-matching
                     syllables (dwSyllables). They have had their duration and energy scaled
                     so the average matches pssThis
returns
   BOOL - TRUE if success
*/
BOOL CTTSProsody::WordSylGetBestExample (PCOMPARESYLINFO pInfo, PCMLexicon pLexTTS, PCWSTR pszWord, DWORD dwPOS, DWORD dwSyllables,
                                              DWORD dwStressBitsMulti, CTTSProsody **ppCTTSProsody,
                                              DWORD dwNum,
                                              PSENTSYLEMPH pssLeft, PSENTSYLEMPH pssThis, PSENTSYLEMPH pssRight,
                                              PSENTSYLEMPH passResult)
{
   DWORD dwExampleSyl = dwSyllables + 2;  // one for left and right

   // create a mini-sentence with left, this, right
   CSentenceSyllable ssThis, ssTest;
   DWORD i, j, k, dwExamples;
   ssThis.Add ((DWORD)-1, 0, 0, 0, pssLeft, 0, 1, 0, 0, 0);
   ssThis.Add ((DWORD)-1, 0, 0, 0, pssThis, 0, 1, 0, 0, 0);
   ssThis.Add ((DWORD)-1, 0, 0, 0, pssRight, 0, 1, 0, 0, 0);

   // compare syllable information. Only care about pitch extra and duration skew
   COMPARESYLINFO Info;
   memset (&Info, 0, sizeof(Info));
   Info.fPitchF0Extra = pInfo->fPitchF0Extra;
   Info.fDurationSkew = pInfo->fDurationSkew;
   Info.fPitchF0Fuji = pInfo->fPitchF0Fuji;  // so will compare cPitchF0FujiDelta

   // loop to find best, from word and pos
   DWORD dwUsePOS;
   PTTSPWORDSYLHEADER pwsh;
   DWORD dwWordSpecificViewed = 0;
   fp fBestScore = 0.0;
   PSENTENCESYLLABLE pssBest = NULL;

   for (dwUsePOS = 0; dwUsePOS < 2; dwUsePOS++) {
      if (!dwUsePOS && !pszWord)
         continue;   // no word, so can't do

      for (i = 0; i < dwNum; i++) {
         pwsh = ppCTTSProsody[i]->WordSylGet (pLexTTS, dwUsePOS ? NULL : pszWord, dwPOS, dwSyllables, dwStressBitsMulti, FALSE);
         if (!pwsh || !pwsh->pss)
            continue;   // no reference found
         
         // how many examples
         dwExamples = pwsh->pss->m_dwNum / dwExampleSyl;
         if (!dwExamples)
            continue;

         // if we are using POS, and have already looked through enough non-POS versions
         // then just exit
         if (dwUsePOS) {
            if (dwWordSpecificViewed >= WORDSENTSYL_KEEPEXAMPLES)
               continue;   // skip this

            // else, limit number of examples that will look through
            if (dwExamples > WORDSENTSYL_KEEPEXAMPLES - dwWordSpecificViewed)
               dwExamples = WORDSENTSYL_KEEPEXAMPLES - dwWordSpecificViewed;
         }

         // keep track of how many have looked through, including POS-only models
         dwWordSpecificViewed += dwExamples;

         for (j = 0; j < dwExamples; j++) {

            // fill in an example syllable from a combination of words
            ssTest.Clear();
            ssTest.Append (pwsh->pss, j*dwExampleSyl + 1, j * dwExampleSyl + 1 + dwSyllables);
            for (k = 0; k < ssTest.m_dwNum; k++)   // make sure syllable numbers increasing properly
               ssTest.m_pabSylIndex[k] = (ssTest.m_pabSylIndex[k] & 0x07) | min(k, 0x07);

            PCSentenceSyllable pssWords = ssTest.ToWords ();
            if (!pssWords)
               continue;   // error
            _ASSERTE (pssWords->m_dwNum == 1);
            if (pssWords->m_dwNum != 1) {
               delete pssWords;
               continue;   // error
            }

            fp fScore = 0.0;
            fScore += ssThis.CompareSingleSyllable (&Info, 0, pwsh->pss, j*dwExampleSyl,
               (WORD)-1, TRUE, FALSE, FALSE);
            fScore += ssThis.CompareSingleSyllable (&Info, 1, pssWords, 0,
               (WORD)-1, TRUE, FALSE, FALSE) * (fp)dwSyllables;
            fScore += ssThis.CompareSingleSyllable (&Info, 2, pwsh->pss, j*dwExampleSyl + dwExampleSyl-1,
               (WORD)-1, TRUE, FALSE, FALSE);

            delete pssWords;

            // best?
            if (!pssBest || (fScore < fBestScore)) {
               pssBest = pwsh->pss->m_paSyl + (j * dwExampleSyl+1);  // skip left context
               fBestScore = fScore;
            }
         } // j
      } // i
   } // dwUsePOS

   // if no best then return false
   if (!pssBest)
      return FALSE;

   // convert to passResult
   DWORD dwPitchPoint;
   for (i = 0; i < dwSyllables; i++) {
      for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
         passResult[i].afPitchF0Extra[dwPitchPoint] = (fp) pssBest[i].acPitchF0Extra[dwPitchPoint] / 100.0;
      passResult[i].fDurSkew = (fp) pssBest[i].cDurSkew / 100.0;
      passResult[i].fPitchF0FujiDelta = (fp) pssBest[i].cPitchF0FujiDelta / 100.0;
      passResult[i].fDurPhone = (fp) pssBest[i].bDurPhone / 100.0;
      passResult[i].fDurSyl = (fp) pssBest[i].bDurSyl / 100.0;
      passResult[i].fPitchF0Fuji = (fp) pssBest[i].bPitchF0Fuji / 100.0;
      passResult[i].fVolume = (fp) pssBest[i].bVol / 100.0;
      passResult[i].fAccent = (fp) pssBest[i].cAccent / 100.0;
   } // i

   // else, average best's values together
   SENTSYLEMPH sseBest;
   memset (&sseBest, 0, sizeof(sseBest));
   sseBest.fDurPhone = sseBest.fDurSyl = sseBest.fPitchF0Fuji = sseBest.fVolume = 1.0;
   // don't need to set sseBest.fAccent
   for (i = 0; i < dwSyllables; i++) {
      for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
         sseBest.afPitchF0Extra[dwPitchPoint] += passResult[i].afPitchF0Extra[dwPitchPoint];
      sseBest.fDurSkew += passResult[i].fDurSkew;
      sseBest.fPitchF0FujiDelta += passResult[i].fPitchF0FujiDelta;
      sseBest.fDurPhone *= passResult[i].fDurPhone;
      sseBest.fDurSyl *= passResult[i].fDurSyl;
      sseBest.fPitchF0Fuji *= passResult[i].fPitchF0Fuji;
      sseBest.fVolume *= passResult[i].fVolume;
      sseBest.fAccent += passResult[i].fAccent;
   } // i

   // normalize
   for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
      sseBest.afPitchF0Extra[dwPitchPoint] /= (fp)dwSyllables;
   sseBest.fDurSkew /= (fp)dwSyllables;
   sseBest.fPitchF0FujiDelta /= (fp)dwSyllables;
   sseBest.fDurPhone = pow (sseBest.fDurPhone, 1.0 / (fp)dwSyllables);
   sseBest.fDurSyl = pow (sseBest.fDurSyl, 1.0 / (fp)dwSyllables);
   sseBest.fPitchF0Fuji = pow (sseBest.fPitchF0Fuji, 1.0 / (fp)dwSyllables);
   sseBest.fVolume = pow (sseBest.fVolume, 1.0 / (fp)dwSyllables);
   sseBest.fAccent /= (fp)dwSyllables;

   // determine a scale
   fp fScaleDurPhone = pssThis->fDurPhone / sseBest.fDurPhone;
   fp fScaleDurSyl = pssThis->fDurSyl / sseBest.fDurSyl;
   fp fScaleVolume = pssThis->fVolume / sseBest.fVolume;
   fp fScaleAccent = pssThis->fAccent - sseBest.fAccent;

   // scale passResult
   for (i = 0; i < dwSyllables; i++) {
      passResult[i].fDurPhone *= fScaleDurPhone;
      passResult[i].fDurSyl *= fScaleDurSyl;
      passResult[i].fVolume *= fScaleVolume;
      passResult[i].fAccent += fScaleAccent;
   } // i

   return TRUE;
}

/*************************************************************************************
CTTSProsody::WordSylGetTYPICALSYLINFO - Gets the default prosody information
for the word and fills it into the SETENCESYLLABLE list.

inputs
   PCMLexicon     pLexTTS - Lexicon with phonemes in it
   PCWSTR         pszWord - String for the hash. If NULL, then assume unknown word
   DWORD          dwPOS - POS number, from 0..POS_MAJOR_NUMPLUSONE-1
   DWORD          dwSyllables - Number of syllables
   DWORD          dwStressBits - Stress bits to look for, as per TTSPWORDSYLHEADER.dwStressBits.

   CTTSProsody    **ppCTTSProsody - List of other prosody models, including this one
   DWORD          dwNum - Number of other prosody models

   PTYPICALSYLINFO pass - Array of dwSyllables sentence syllables that are filled in.
            NOTE: Count will ALWAYS be 1.0
returns
   BOOL - TRUE if success
*/
BOOL CTTSProsody::WordSylGetTYPICALSYLINFO (PCMLexicon pLexTTS, PCWSTR pszWord, DWORD dwPOS, DWORD dwSyllables,
                                              DWORD dwStressBitsMulti, CTTSProsody **ppCTTSProsody,
                                              DWORD dwNum, PTYPICALSYLINFO pass)
{
   // scratch memory
   BYTE abTempWord[sizeof(TTSPWORDSYLHEADER) + 16 * sizeof(PROSODYTREND)];
   BYTE abTempPOS[sizeof(TTSPWORDSYLHEADER) + 16 * sizeof(PROSODYTREND)];
   CMem memWord, memPOS;
   PTTSPWORDSYLHEADER pwshWord = (PTTSPWORDSYLHEADER) abTempWord;
   PTTSPWORDSYLHEADER pwshPOS = (PTTSPWORDSYLHEADER) abTempPOS;
   size_t dwNeed = sizeof(TTSPWORDSYLHEADER) + dwSyllables *sizeof(PROSODYTREND);
   if (dwNeed > sizeof(abTempWord)) {
      // if buffer not large enough then alloc
      if (!memWord.Required (dwNeed) || !memPOS.Required (dwNeed))
         return FALSE;
      pwshWord = (PTTSPWORDSYLHEADER)memWord.p;
      pwshPOS = (PTTSPWORDSYLHEADER)memPOS.p;
   }
   memset (pwshWord, 0, dwNeed);
   memset (pwshPOS, 0, dwNeed);

   // loop over all the prosody models getting the info
   DWORD dwUsePOS;
   DWORD i, j;
   PPROSODYTREND ppt, pptTo;
   PTTSPWORDSYLHEADER pwsh, pwshTo;
   for (i = 0; i < dwNum; i++)
      for (dwUsePOS = 0; dwUsePOS < 2; dwUsePOS++) {
         if (!dwUsePOS && !pszWord)
            continue;   // don't have a word string

         pwsh = ppCTTSProsody[i]->WordSylGet (pLexTTS, dwUsePOS ? NULL : pszWord, dwPOS,dwSyllables, dwStressBitsMulti, FALSE);
         if (!pwsh)
            continue;   // no reference found

         pwshTo = dwUsePOS ? pwshPOS : pwshWord;

         // sum in the weights
         ppt = (PPROSODYTREND) (pwsh+1);
         pptTo = (PPROSODYTREND) (pwshTo+1);

         for (j = 0; j < dwSyllables; j++, ppt++, pptTo++) {
            pptTo->iCount += ppt->iCount;
            pptTo->iDurPhone += ppt->iDurPhone;
            pptTo->iDurSkew += ppt->iDurSkew;
            pptTo->iDurSyl += ppt->iDurSyl;
            DWORD dwPitchPoint;
            for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
               pptTo->aiPitchF0Extra[dwPitchPoint] += ppt->aiPitchF0Extra[dwPitchPoint];
            pptTo->iPitchF0Fuji += ppt->iPitchF0Fuji;
            pptTo->iPitchF0FujiDelta += ppt->iPitchF0FujiDelta;
            // pptTo->iPitchBulge += ppt->iPitchBulge;
            // pptTo->iPitchSweep += ppt->iPitchSweep;
            pptTo->iVol += ppt->iVol;
            pptTo->iAccent += ppt->iAccent;
         } // j

      } // dwUsePOS

   // add in defaults to POS if not enough entries
   pwshTo = pwshPOS;
   pptTo = (PPROSODYTREND) (pwshTo+1);
   for (j = 0; j < dwSyllables; j++, pptTo++) {
      pptTo->iCount += WORDSENTSYL_MINEXAMPLES;

      // NOTE: Leave all the values as +0 since 0 * WORDSENTSYL_MINEXAMPLES == 0
      // and they're all stored as log
   }

   // scale so no more than WORDSENTSYL_MINEXAMPLES
   pwshTo = pwshPOS;
   pptTo = (PPROSODYTREND) (pwshTo+1);
   for (j = 0; j < dwSyllables; j++, pptTo++)
      if (pptTo->iCount > WORDSENTSYL_MINEXAMPLES) {
         pptTo->iDurPhone = (pptTo->iDurPhone * WORDSENTSYL_MINEXAMPLES) / pptTo->iCount;
         pptTo->iDurSkew = (pptTo->iDurSkew * WORDSENTSYL_MINEXAMPLES) / pptTo->iCount;
         pptTo->iDurSyl = (pptTo->iDurSyl * WORDSENTSYL_MINEXAMPLES) / pptTo->iCount;
         DWORD dwPitchPoint;
         for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
            pptTo->aiPitchF0Extra[dwPitchPoint] = (pptTo->aiPitchF0Extra[dwPitchPoint] * WORDSENTSYL_MINEXAMPLES) / pptTo->iCount;
         pptTo->iPitchF0Fuji = (pptTo->iPitchF0Fuji * WORDSENTSYL_MINEXAMPLES) / pptTo->iCount;
         pptTo->iPitchF0FujiDelta = (pptTo->iPitchF0FujiDelta * WORDSENTSYL_MINEXAMPLES) / pptTo->iCount;;
         // pptTo->iPitchBulge = (pptTo->iPitchBulge * WORDSENTSYL_MINEXAMPLES) / pptTo->iCount;
         // pptTo->iPitchSweep = (pptTo->iPitchSweep * WORDSENTSYL_MINEXAMPLES) / pptTo->iCount;
         pptTo->iVol = (pptTo->iVol * WORDSENTSYL_MINEXAMPLES) / pptTo->iCount;
         pptTo->iAccent = (pptTo->iAccent * WORDSENTSYL_MINEXAMPLES) / pptTo->iCount;
         pptTo->iCount = WORDSENTSYL_MINEXAMPLES;
      } // j
   
   // incorporate POS count into the word's count
   pwsh = pwshPOS;
   ppt = (PPROSODYTREND) (pwsh+1);
   pwshTo = pwshWord;
   pptTo = (PPROSODYTREND) (pwshTo+1);
   for (j = 0; j < dwSyllables; j++, ppt++, pptTo++) {
      pptTo->iCount += ppt->iCount;
      pptTo->iDurPhone += ppt->iDurPhone;
      pptTo->iDurSkew += ppt->iDurSkew;
      pptTo->iDurSyl += ppt->iDurSyl;
      DWORD dwPitchPoint;
      for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
         pptTo->aiPitchF0Extra[dwPitchPoint] += ppt->aiPitchF0Extra[dwPitchPoint];
      pptTo->iPitchF0Fuji += ppt->iPitchF0Fuji;
      pptTo->iPitchF0FujiDelta += ppt->iPitchF0FujiDelta;
      // pptTo->iPitchBulge += ppt->iPitchBulge;
      // pptTo->iPitchSweep += ppt->iPitchSweep;
      pptTo->iVol += ppt->iVol;
      pptTo->iAccent += ppt->iAccent;
   } // j

   // NOTE: Know there are at least WORDSENTSYL_MINEXAMPLES elements
   
   // fill in the SENTENCESYLLABLE entreis
   pwsh = pwshWord;
   ppt = (PPROSODYTREND) (pwsh+1);
   for (j = 0; j < dwSyllables; j++, ppt++, pass++) {
      memset (pass, 0, sizeof(*pass));

      pass->fCount = 1.0;

      pass->fDurPhoneSum = (fp)(ppt->iDurPhone / ppt->iCount) / 50.0 * log(2.0);
      pass->fDurSylSum = (fp)(ppt->iDurSyl / ppt->iCount) / 50.0 * log(2.0);
      pass->fVolumeSum = (fp)(ppt->iVol / ppt->iCount) / 50.0 * log(2.0);
      pass->fAccentSum = (fp)(ppt->iAccent / ppt->iCount) / 100.0;

      DWORD dwPitchPoint;
      for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
         pass->afPitchF0ExtraSum[dwPitchPoint] = (fp)ppt->aiPitchF0Extra[dwPitchPoint] / (fp)ppt->iCount / 100.0 * log(2.0);
      pass->fPitchF0FujiSum = 0.0; // (fp)(ppt->iPitchF0Fuji / ppt->iCount) / 100.0 * log(2.0);
         // NOTE: Syllable info for fujisaki is always 0
      pass->fPitchF0FujiDeltaSum = 0.0; // (fp)ppt->iPitchF0FujiDelta / (fp)ppt->iCount / 100.0 * log(2.0);
         // NOTE: Don't want words to affect fuji delta
      pass->fDurSkewSum = ppt->iDurSkew / ppt->iCount / 100.0 * log(2.0);
      // pass->fPitchBulgeSum = ppt->iPitchBulge / ppt->iCount / 100.0 * log(2.0);
      // pass->fPitchRelative - not used
      // pass->fPitchSweepSum = ppt->iPitchSweep / ppt->iCount / 100.0 * log(2.0);
   } // j


   return TRUE;
}

#if 0 // not used
/*************************************************************************************
CTTSProsody::WordSylGetSENTENCESYLLABLE - Gets the default prosody information
for the word and fills it into the SETENCESYLLABLE list.

inputs
   PCWSTR         pszWord - String for the hash. If NULL, then assume unknown word
   DWORD          dwPOS - POS number, from 0..POS_MAJOR_NUMPLUSONE-1
   DWORD          dwSyllables - Number of syllables
   DWORD          dwStressBits - Stress bits to look for, as per TTSPWORDSYLHEADER.dwStressBits.

   CTTSProsody    **ppCTTSProsody - List of other prosody models, including this one
   DWORD          dwNum - Number of other prosody models

   PSENTENCESYLLABLE pass - Array of dwSyllables sentence syllables that are filled in.
returns
   BOOL - TRUE if success
*/
BOOL CTTSProsody::WordSylGetSENTENCESYLLABLE (PCWSTR pszWord, DWORD dwPOS, DWORD dwSyllables,
                                              DWORD dwStressBits, CTTSProsody **ppCTTSProsody,
                                              DWORD dwNum, PSENTENCESYLLABLE pass)
{
   // scratch
   TYPICALSYLINFO aTSI[16];
   PTYPICALSYLINFO pTSI = aTSI;
   CMem mem;
   size_t dwNeed = dwSyllables * sizeof(TYPICALSYLINFO);
   if (dwNeed > sizeof(aTSI)) {
      if (!mem.Required (dwNeed))
         return FALSE;
      pTSI = (PTYPICALSYLINFO) mem.p;
   }

   if (!WordSylGetTYPICALSYLINFO (pszWord, dwPOS, dwSyllables, dwStressBits, ppCTTSProsody, dwNum, pTSI))
      return FALSE;

   // conver over
   DWORD i;
   fp f;
   for (i = 0; i < dwSyllables; i++, pTSI++, pass++) {
      memset (pass, 0, sizeof(*pass));

      f = pow (2.0, pTSI->fDurPhoneSum) * 100.0; wrong - need to include log(2) for all of these
      f = max (f, 0);
      f = min (f, 255);
      pass->bDurPhone = (BYTE) f;

      f = pow (2.0, pTSI->fDurSylSum) * 100.0;
      f = max (f, 0);
      f = min (f, 255);
      pass->bDurSyl = (BYTE) f;

      f = pow (2.0, pTSI->fVolumeSum) * 100.0;
      f = max (f, 0);
      f = min (f, 255);
      pass->bVol = (BYTE) f;

      f = pow (2.0, pTSI->fPitchSum) * 100.0;
      f = max (f, 0);
      f = min (f, 255);
      pass->bPitch = (BYTE) f;

      f = pTSI->fDurSkewSum * 100.0;
      f = max (f, -127);
      f = min (f, 127);
      pass->cDurSkew = (char) f;

      f = pTSI->fPitchBulgeSum * 100.0;
      f = max (f, -127);
      f = min (f, 127);
      pass->cPitchBulge = (char) f;

      f = pTSI->fPitchSweepSum * 100.0;
      f = max (f, -127);
      f = min (f, 127);
      pass->cPitchSweep = (char) f;

   } // i

   return TRUE;
}
#endif // 0


/*************************************************************************************
CTTSProsody::ProsodyTrends - Given stress and POS, returns the PPROSODYTRENDS.

inputs
   PCMLexicon     pLexTTS - Lexicon with phonemes in it
   DWORD          dwStress - Stress level of this unit
   DWORD          dwPOS - Part of speech, 0..POS_MAJOR_NUMPLUSONE-1
returns
   PPROSODYTRENDS - Appropriate prosody trend, or NULL if error
*/
PPROSODYTRENDS CTTSProsody::ProsodyTrends (PCMLexicon pLexTTS, DWORD dwStress, DWORD dwPOS)
{
   CalcNGramIfNecessary(pLexTTS);
   PPROSODYTRENDS papt = (PPROSODYTRENDS) m_memPROSODYTRENDS.p;
   if (!papt)
      return NULL;

   return papt + (dwPOS * pLexTTS->Stresses() + dwStress);
}


/*************************************************************************************
CTTSProsody::ProsodyTrend - Extract the prosody trend from the PROSODYTRENDS list

inputs
   PCMLexicon     pLexTTS- Lexicon with phonemes in it
   DWORD          dwStress - Stress number, 0+
   DWORD          dwPOS - Part of speech, 0..POS_MAJOR_NUMPLUSONE-1
   DWORD          dwCommon - How common the word is, from 0..3
   DWORD          dwPhonemes - How many phonemes the syllable has (minus one), from 0..3
   DWORD          dwSyllable - What syllable index number (minus one), from 0..7
   DWORD          dwParseLevel - Location in the parse level, from 0..15
   PPROSODYTREND  ptCommon - Should intiially be filled with something. Values
                  from PROSODYTRENDS will be summed in
   PPROSODYTREND  ptPhonemes - As above
   PPROSODYTREND  ptSyllable - As above
   PPROSODYTREND  ptParseLevel - As above
   PPROSODYTREND  ptAll - Everything thats added into ptCommon, ptPhonemes, ptSyllable,
                  and ptParseLevel will be added into ptAll
returns
   BOOL - TRUE if success
*/
#ifdef USEPROSODYTREND
BOOL CTTSProsody::ProsodyTrend (PCMLexicon pLexTTS, DWORD dwStress, DWORD dwPOS, DWORD dwCommon, DWORD dwPhonemes,
                                 DWORD dwSyllable, DWORD dwParseLevel, PPROSODYTREND ptCommon,
                                 PPROSODYTREND ptPhonemes, PPROSODYTREND ptSyllable,
                                 PPROSODYTREND ptParseLevel, PPROSODYTREND ptAll)
{
   PPROSODYTRENDS ppt = ProsodyTrends (pLexTTS, dwStress, dwPOS);
   if (!ppt)
      return FALSE;

   DWORD i, j;
   int *piSource, *piDest;
   int *piAll = (int*)ptAll;
   for (i = 0; i < 4; i++) {
      switch (i) {
      case 0:  // common
         piSource = (int*)&ppt->aCommon[dwCommon];
         piDest = (int*)ptCommon;
         break;
      case 1:  // phonemes
         piSource = (int*)&ppt->aPhonemes[dwPhonemes];
         piDest = (int*)ptPhonemes;
         break;
      case 2:  // syllable
         piSource = (int*)&ppt->aSyllable[dwSyllable];
         piDest = (int*)ptSyllable;
         break;
      case 3:  // level
         piSource = (int*)&ppt->aParse[dwParseLevel];
         piDest = (int*)ptParseLevel;
         break;
      } // switch

      // copy them over
      for (j = 0; j < sizeof(PROSODYTREND)/sizeof(int); j++) {
         piDest[j] += piSource[j];
         piAll[j] += piSource[j];
      } // j
   } // i

   return TRUE;
}
#endif //USEPROSODYTREND

/*************************************************************************************
CTTSProsody::ProsodyTrend - Extract the prosody trend from the PROSODYTRENDS list

inputs
   PCMLexicon     pLexTTS - Lexicon with phonemes in it
   DWORD          dwStress - Stress number, 0+
   DWORD          dwPOS - Part of speech, 0..POS_MAJOR_NUMPLUSONE-1
   DWORD          dwCommon - How common the word is, from 0..3
   DWORD          dwPhonemes - How many phonemes the syllable has (minus one), from 0..3
   DWORD          dwSyllable - What syllable index number (minus one), from 0..7
   DWORD          dwParseLevel - Location in the parse level, from 0..15
   PPROSODYTREND  ptCommon - Should intiially be filled with something. Values
                  from PROSODYTRENDS will be summed in
   PPROSODYTREND  ptPhonemes - As above
   PPROSODYTREND  ptSyllable - As above
   PPROSODYTREND  ptParseLevel - As above
   PPROSODYTREND  ptAll - Everything thats added into ptCommon, ptPhonemes, ptSyllable,
                  and ptParseLevel will be added into ptAll
   CTTSProsody **ppCTTSProsody - List of other prosody models, including this one
   DWORD       dwNum - Number of other prosody models
returns
   BOOL - TRUE if success
*/
#ifdef USEPROSODYTREND
BOOL CTTSProsody::ProsodyTrend (PCMLexicon pLexTTS, DWORD dwStress, DWORD dwPOS, DWORD dwCommon, DWORD dwPhonemes,
                                 DWORD dwSyllable, DWORD dwParseLevel, PPROSODYTREND ptCommon,
                                 PPROSODYTREND ptPhonemes, PPROSODYTREND ptSyllable,
                                 PPROSODYTREND ptParseLevel, PPROSODYTREND ptAll,
                                 CTTSProsody **ppCTTSProsody, DWORD dwNum)
{
   DWORD i;
   for (i = 0; i < dwNum; i++)
      ppCTTSProsody[i]->ProsodyTrend (pLexTTS, dwStress, dwPOS, dwCommon, dwPhonemes,
         dwSyllable, dwParseLevel, ptCommon, ptPhonemes, ptSyllable,
         ptParseLevel, ptAll);

   return TRUE;
}
#endif

/*************************************************************************************
CTTSProsody::ProsodyTrend - Fill in the prosody trend adjustments

inputs
   PCMLexicon  pLexTTS - LExicon with phonemes in it
   BYTE        bPOSStressModel - Part of speech from the model in low 4 bits, stress in high 4 bits
   BYTE        bSylIndexModel - Syllable index in low 3 bits
   BYTE        bRuleDepdthModel - Rule depth from the model
   BYTE        bPOSStressWant - Part of speech that want in low 4 bits, stress in high 4 bits
   BYTE        bSylIndexWant - Syllable index in low 3 bits
   BYTE        bRuleDepthWant - Rule depth that want
   PPROSODYTREND  ptMods - Filled in the the prosodytrend information for how to
               modify the model to get it to simulate what want. Ignore the count
   CTTSProsody **ppCTTSProsody - List of other prosody models, including this one
   DWORD       dwNum - Number of other prosody models
returns
   BOOL - TRUE if success
*/
#ifdef USEPROSODYTREND
BOOL CTTSProsody::ProsodyTrend (PCMLexicon pLexTTS, BYTE bPOSStressModel, BYTE bSylIndexModel, BYTE bRuleDepthModel,
                                   BYTE bPOSStressWant, BYTE bSylIndexWant, BYTE bRuleDepthWant,
                                PPROSODYTREND ptMods,
                                CTTSProsody **ppCTTSProsody, DWORD dwNum)
{
   memset (ptMods, 0, sizeof(*ptMods));

   // trivial reject
   if ((bPOSStressModel == bPOSStressWant) && (bSylIndexModel == bSylIndexWant) && (bRuleDepthModel == bRuleDepthWant))
      return TRUE;

   PROSODYTREND aProsTrend[2][4];   // prosody trends
   PROSODYTREND ProsTrendAll[2];    // all
   memset (aProsTrend, 0, sizeof(aProsTrend));
   memset (ProsTrendAll, 0, sizeof(ProsTrendAll));

   // get the trends
   // model
   ProsodyTrend (
      pLexTTS,
      (bPOSStressModel >> 4) & 0x0f,  // stress
      bPOSStressModel & 0x0f, // POS
      (bRuleDepthModel >> 6) & 0x03,   // common
      (bRuleDepthModel >> 4) & 0x03,   // phonemes
      bSylIndexModel & 0x07,   // syllable number
      bRuleDepthModel & 0x0f, // rule depth
      &aProsTrend[0][0], &aProsTrend[0][1], &aProsTrend[0][2], &aProsTrend[0][3],
      &ProsTrendAll[0],
      ppCTTSProsody, dwNum);
   // what want
   ProsodyTrend (
      pLexTTS,
      (bPOSStressWant >> 4) & 0x0f,  // stress
      bPOSStressWant & 0x0f, // POS
      (bRuleDepthWant >> 6) & 0x03,   // common
      (bRuleDepthWant >> 4) & 0x03,   // phonemes
      bSylIndexWant & 0x07,   // syllable number
      bRuleDepthWant & 0x0f, // rule depth
      &aProsTrend[1][0], &aProsTrend[1][1], &aProsTrend[1][2], &aProsTrend[1][3],
      &ProsTrendAll[1],
      ppCTTSProsody, dwNum);

   // figure out the values
   DWORD i, j, k;
   int *piCur;
   int iCount;
   for (i = 0; i < 2; i++) {
      for (j = 0; j <= 4; j++) {
         piCur = (j < 4) ? (int*)&aProsTrend[i][j] : (int*)&ProsTrendAll[i];

         iCount = piCur[0];
         if (!iCount)
            continue;   // cant determine since would be divide by zero

         for (k = 1; k < sizeof(PROSODYTREND)/sizeof(int); k++)   // intentionally start at 1
            piCur[k] /= iCount;
      } // j
   } // i

   // determine the deltas
   int *piWant;
   for (j = 0; j <= 4; j++) {
      piCur = (j < 4) ? (int*)&aProsTrend[0][j] : (int*)&ProsTrendAll[0];
      piWant = (j < 4) ? (int*)&aProsTrend[1][j] : (int*)&ProsTrendAll[1];

      // if either the count for current or want are < MINPROSODYSAMPLES
      // then not enough data points, so wipe to zero
      if ((piCur[0] < MINPROSODYSAMPLES) || (piWant[0] < MINPROSODYSAMPLES)) {
         memset (piCur, 0, sizeof(PROSODYTREND));
         continue;
      }

      // else, take delta
      for (k = 1; k < sizeof(PROSODYTREND)/sizeof(int); k++)   // intentionally start at 1
         piCur[k] = piWant[k] - piCur[k];
   } // k

   // for case 1, sum up all the deltas from all 4 different models
   piWant = (int*) &aProsTrend[0][0];
   for (j = 1; j < 4; j++) {
      piCur = (int*) &aProsTrend[0][j];
      for (k = 1; k < sizeof(PROSODYTREND)/sizeof(int); k++)   // intentionally start at 1
         piWant[k] +=  piCur[k];
   } // j

   // average result from 4 models (summed), along with result from all models averaged
   // do this because sum of all 4 models assumes data is independent, while average
   // assumed all depdendent. Neither is completely true, so take mid case
   memcpy (ptMods, &aProsTrend[0][0], sizeof(*ptMods));
   piWant = (int*)ptMods;
   piCur = (int*)&ProsTrendAll[0];
   for (k = 1; k < sizeof(PROSODYTREND)/sizeof(int); k++)   // intentionally start at 1
      piWant[k] = (piWant[k] + piCur[k]) / 2;

   return TRUE;
}
#endif // USEPROSODYTREND

/*************************************************************************************
CTTSProsody::CalcNGramIfNecessary - Calculates information such as m_memPauseNGram
and m_memPROSODYTRENDS if it isn't already calcualted.

inputs
   PCMLexicon     pLexTTS - Lexicon with phonemes in it
*/
void CTTSProsody::CalcNGramIfNecessary (PCMLexicon pLexTTS)
{
   if (m_memPauseNGram.m_dwCurPosn)
      return;

   // how much need for NGram
   DWORD dwNeed = POS_MAJOR_NUMPLUSONE * POS_MAJOR_NUMPLUSONE * POS_MAJOR_NUMPLUSONE * POS_MAJOR_NUMPLUSONE *
      2 * sizeof(DWORD);
   if (!m_memPauseNGram.Required (dwNeed))
      return;
   m_memPauseNGram.m_dwCurPosn = dwNeed;
   memset (m_memPauseNGram.p, 0, dwNeed);
   DWORD *padwNGram = (DWORD*)m_memPauseNGram.p;

   // prosody trends
   dwNeed = pLexTTS->Stresses() * POS_MAJOR_NUMPLUSONE * sizeof(PROSODYTRENDS);
   if (!m_memPROSODYTRENDS.Required (dwNeed)) {
      m_memPauseNGram.m_dwCurPosn = 0;
      return;
   }
   m_memPROSODYTRENDS.m_dwCurPosn = dwNeed;
   memset (m_memPROSODYTRENDS.p, 0, dwNeed);
   PPROSODYTRENDS papt;
   PPROSODYTREND ppt;

   // loop over all sentences
   PCSentenceSyllable *ppss = (PCSentenceSyllable*)m_lPCSentenceSyllable.Get(0);
   DWORD i, j, k;
   DWORD dwOffset;
   fp f;
   for (i = 0; i < m_lPCSentenceSyllable.Num(); i++) {
      PCSentenceSyllable pss = ppss[i];

      BYTE abNGramPOS[4], abNGramPause[4];
      for (j = 0; j < 4; j++) {
         abNGramPOS[j] = POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION);
         abNGramPause[j] = 0; // assume 0 pause
      }

      for (j = 0; j < pss->m_dwNum; j++) {
         PSENTENCESYLLABLE ps = &pss->m_paSyl[j];
         
         // pitch
         f = ps->bPitchF0Fuji ? (log((fp)ps->bPitchF0Fuji / (fp)100.0) / log((fp)2) * 100.0) : 0;
         f = max(f, -100);
         f = min(f, 100);
         char cPitchF0Fuji = (char)(int)f;

         // volume
         f = ps->bVol ? (log((fp)ps->bVol / (fp)100.0) / log((fp)2) * 50.0) : 0;
         f = max(f, -100);
         f = min(f, 100);
         char cVol = (char)(int)f;

         // accent
         char cAccent = ps->cAccent;

         // phone duration
         f = ps->bDurPhone ? (log((fp)ps->bDurPhone / (fp)100.0) / log((fp)2) * 50.0) : 0;
         f = max(f, -100);
         f = min(f, 100);
         char cDurPhone = (char)(int)f;

         // phone duration
         f = ps->bDurSyl ? (log((fp)ps->bDurSyl / (fp)100.0) / log((fp)2) * 50.0) : 0;
         f = max(f, -100);
         f = min(f, 100);
         char cDurSyl = (char)(int)f;

         // duration skew
         char cDurSkew = ps->cDurSkew;

         // stuff done fo every syllable
         papt = ProsodyTrends (pLexTTS, (pss->m_pabPOSStress[j] >> 4) & 0x0f, pss->m_pabPOSStress[j] & 0x0f);
         if (papt) for (k = 0; k < 4; k++) {
            // sum into where
            switch (k) {
            case 0:  // how common word is
               ppt = &papt->aCommon[(pss->m_pabRuleDepth[j] >> 6) & 0x03];
               break;
            case 1:  // how many phonemes in syllable
               ppt = &papt->aPhonemes[(pss->m_pabRuleDepth[j] >> 4) & 0x03];
               break;
            case 2:  // what syllable number this is
               ppt = &papt->aSyllable[pss->m_pabSylIndex[j] & 0x07];
               break;
            case 3:  // location in the parse level
               ppt = &papt->aParse[pss->m_pabRuleDepth[j] & 0x0f];
               break;
            } // k

            ppt->iCount++;
            ppt->iDurPhone += (int)cDurPhone;
            ppt->iDurSyl += (int)cDurSyl;
            ppt->iDurSkew += (int)cDurSkew;
            DWORD dwPitchPoint;
            for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
               ppt->aiPitchF0Extra[dwPitchPoint] += (int)ps->acPitchF0Extra[dwPitchPoint];
            ppt->iPitchF0Fuji += (int)cPitchF0Fuji;
            ppt->iPitchF0FujiDelta += (int)ps->cPitchF0FujiDelta;
            // ppt->iPitchBulge += (int)ps->cPitchBulge;
            // ppt->iPitchSweep += (int)ps->cPitchSweep;
            ppt->iVol += (int)cVol;
            ppt->iAccent += (int)cAccent;
         }

         // if its a new word then shift NGram and add
         if (pss->IsStartOfWord (j)) {
            memmove (abNGramPOS, abNGramPOS+1, sizeof(abNGramPOS)-sizeof(BYTE));
            memmove (abNGramPause, abNGramPause+1, sizeof(abNGramPause)-sizeof(BYTE));

            abNGramPOS[3] = pss->m_pabPOSStress[j] & 0x0f;
            abNGramPause[3] = pss->m_paSyl[j].bPauseProb & 0x0f;

            // add this
            dwOffset = ((( ( (((DWORD)abNGramPOS[0] * POS_MAJOR_NUMPLUSONE) +
               (DWORD)abNGramPOS[1]) * POS_MAJOR_NUMPLUSONE) +
               (DWORD)abNGramPOS[2]) * POS_MAJOR_NUMPLUSONE) +
               (DWORD)abNGramPOS[3]) * 2;
            padwNGram[dwOffset] += 1;
            padwNGram[dwOffset+1] += (DWORD)abNGramPause[2];
         }
      } // j, over each syllable

      // finish up the sentenace POS
      for (j = 0; j < 3; j++) {
         memmove (abNGramPOS, abNGramPOS+1, sizeof(abNGramPOS)-sizeof(BYTE));
         memmove (abNGramPause, abNGramPause+1, sizeof(abNGramPause)-sizeof(BYTE));

         abNGramPOS[3] = POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION);
         abNGramPause[3] = 0;

         // add this
         dwOffset = ((( ( (((DWORD)abNGramPOS[0] * POS_MAJOR_NUMPLUSONE) +
            (DWORD)abNGramPOS[1]) * POS_MAJOR_NUMPLUSONE) +
            (DWORD)abNGramPOS[2]) * POS_MAJOR_NUMPLUSONE) +
            (DWORD)abNGramPOS[3]) * 2;
         padwNGram[dwOffset] += 1;
         padwNGram[dwOffset+1] += (DWORD)abNGramPause[2];
      } // j
   } // i, over all sentences

}


/*************************************************************************************
CTTSProsody::PauseNGram - Given a list of parts of speech, returns the pause NGram.

inputs
   PCMLexicon  pLexTTS - Lexicon with phonemes in it
   BYTE        bWord1 - First word in list. Low 4 bits are POS, upper 4 are 0. 2 words before potential pause
   BYTE        bWord2 - Second word.
   BYTE        bWord3 - Third word. Right after potential pause
   BYTE        bWord4 - Fourth word
returns
   DWORD * - [0] is the count. [1] is sum of pause probability scores. Can be NULL.
*/
DWORD *CTTSProsody::PauseNGram (PCMLexicon pLexTTS, BYTE bWord1, BYTE bWord2, BYTE bWord3, BYTE bWord4)
{
   CalcNGramIfNecessary (pLexTTS);
   DWORD *padwNGram = (DWORD*)m_memPauseNGram.p;
   if (!padwNGram)
      return NULL;

   DWORD dwOffset = ((( ( (((DWORD)bWord1 * POS_MAJOR_NUMPLUSONE) +
      (DWORD)bWord2) * POS_MAJOR_NUMPLUSONE) +
      (DWORD)bWord3) * POS_MAJOR_NUMPLUSONE) +
      (DWORD)bWord4) * 2;

   return padwNGram + dwOffset;
}



/*************************************************************************************
CTTSProsody::PauseNGram - Returns the pause probability given a list
of extra prosody models.

inputs
   PCMLexicon  pLexTTS - Lexicon with phonemes in it
   BYTE        bWord1 - First word in list. Low 4 bits are POS, upper 4 are 0. 2 words before potential pause
   BYTE        bWord2 - Second word.
   BYTE        bWord3 - Third word. Right after potential pause
   BYTE        bWord4 - Fourth word
   CTTSProsody **ppCTTSProsody - List of other prosody models, including this one
   DWORD       dwNum - Number of other prosody models
returns
   fp - Value, from 0.0 to 1.0. -1 if not enough information
*/
fp CTTSProsody::PauseNGram (PCMLexicon pLexTTS, BYTE bWord1, BYTE bWord2, BYTE bWord3, BYTE bWord4,
                            CTTSProsody **ppCTTSProsody, DWORD dwNum)
{
   DWORD dwCount = 0, dwSum = 0;
   DWORD *padw;
   DWORD i;
   for (i = 0; i < dwNum; i++) {
      padw = PauseNGram (pLexTTS, bWord1, bWord2, bWord3, bWord4);
      if (!padw)
         continue;

      dwCount += padw[0];
      dwSum += padw[1];
   } // i

   if (dwCount < MINPROSODYSAMPLES)
      return -1.0;   // not enough to make a decision

   // else
   return (fp)dwSum / (fp)dwCount / 15.0;
}

/*************************************************************************************
CTTSProsody::Save - Standard api
*/
BOOL CTTSProsody::Save (PWSTR pszFile)
{
   if (!pszFile)
      // m_pszFile = pszFile;
      return FALSE;

   PCMMLNode2 pNode = MMLTo();
   if (!pNode)
      return FALSE;
   BOOL fRet;
   fRet = MMLFileSave (pszFile, &GUID_TTSProsody, pNode);
   delete pNode;

   //if (fRet && (pszFile != m_szFile))
   //   wcscpy (m_szFile, pszFile);

   return fRet;
}

/*************************************************************************************
CTTSProsody::Open - Standard api
*/
BOOL CTTSProsody::Open (PWSTR pszFile)
{
   // BUGFIX - Ignore directory
   PCMMLNode2 pNode = MMLFileOpen (pszFile, &GUID_TTSProsody, NULL, TRUE);
   if (!pNode)
      return FALSE;

   if (!MMLFrom (pNode, pszFile)) {
      delete pNode;
      return FALSE;
   }

   // rembmeber the file
   // wcscpy (m_szFile, pszFile);

   delete pNode;
   return TRUE;
}


/*************************************************************************************
CTTSProsody::Merge - Merges another prosody module in with this one

inputs
   PCMLexicon        pLexTTS - LExicon with phonemes in it
   PCTTSProsody      pOther - Othre prosody module to merge in
returns
   BOOL - TRUE if success
*/
BOOL CTTSProsody::Merge (PCMLexicon pLexTTS, PCTTSProsody pOther)
{
   PCSentenceSyllable *ppss = (PCSentenceSyllable*)pOther->m_lPCSentenceSyllable.Get(0);
   DWORD i;
   for (i = 0; i < pOther->m_lPCSentenceSyllable.Num(); i++) {
      PCSentenceSyllable pClone = ppss[i]->Clone();
      if (!pClone)
         return FALSE;

      SentenceAdd (pLexTTS, pClone, pOther->m_pLexInProsody);
   } // i

   // NOTE: Don't bother with m_lPCSentenceSyllableWord, and no need to free since
   // will be cleared if not used

   if (m_memPhonemePause.m_dwCurPosn && (m_memPhonemePause.m_dwCurPosn == pOther->m_memPhonemePause.m_dwCurPosn)) {
      DWORD *pdw = (DWORD*)m_memPhonemePause.p;
      DWORD *pdwOther = (DWORD*)pOther->m_memPhonemePause.p;
      for (i = 0; i < m_memPhonemePause.m_dwCurPosn / sizeof(DWORD); i++, pdw++, pdwOther++)
         *pdw += *pdwOther;
   }
   else if (!m_memPhonemePause.m_dwCurPosn && m_memPhonemePause.Required(pOther->m_memPhonemePause.m_dwCurPosn)) {
      // copy over
      m_memPhonemePause.m_dwCurPosn = pOther->m_memPhonemePause.m_dwCurPosn;
      memcpy (m_memPhonemePause.p, pOther->m_memPhonemePause.p, m_memPhonemePause.m_dwCurPosn);
   }

   for (i = 0; i < TYPICALSYLINFO_NUM; i++) {
      if (m_amemTYPICALSYLINFO[i].m_dwCurPosn && (m_amemTYPICALSYLINFO[i].m_dwCurPosn == pOther->m_amemTYPICALSYLINFO[i].m_dwCurPosn)) {
         double *pf = (double*)m_amemTYPICALSYLINFO[i].p;
         double *pfOther = (double*)pOther->m_amemTYPICALSYLINFO[i].p;
         for (i = 0; i < m_amemTYPICALSYLINFO[i].m_dwCurPosn / sizeof(double); i++, pf++, pfOther++)
            *pf += *pfOther;
      }
      else if (!m_amemTYPICALSYLINFO[i].m_dwCurPosn && m_amemTYPICALSYLINFO[i].Required(pOther->m_amemTYPICALSYLINFO[i].m_dwCurPosn)) {
         // copy over
         m_amemTYPICALSYLINFO[i].m_dwCurPosn = pOther->m_amemTYPICALSYLINFO[i].m_dwCurPosn;
         memcpy (m_amemTYPICALSYLINFO[i].p, pOther->m_amemTYPICALSYLINFO[i].p, m_amemTYPICALSYLINFO[i].m_dwCurPosn);
      }
   } // i

   // NGram
   if (m_memPROSODYNGRAMINFO.m_dwCurPosn && (m_memPROSODYNGRAMINFO.m_dwCurPosn == pOther->m_memPROSODYNGRAMINFO.m_dwCurPosn)) {
      float *pf = (float*)m_memPROSODYNGRAMINFO.p;
      float *pfOther = (float*)pOther->m_memPROSODYNGRAMINFO.p;
      for (i = 0; i < m_memPROSODYNGRAMINFO.m_dwCurPosn / sizeof(float); i++, pf++, pfOther++)
         *pf += *pfOther;
   }
   else if (!m_memPROSODYNGRAMINFO.m_dwCurPosn && m_memPROSODYNGRAMINFO.Required(pOther->m_memPROSODYNGRAMINFO.m_dwCurPosn)) {
      // copy over
      m_memPROSODYNGRAMINFO.m_dwCurPosn = pOther->m_memPROSODYNGRAMINFO.m_dwCurPosn;
      memcpy (m_memPROSODYNGRAMINFO.p, pOther->m_memPROSODYNGRAMINFO.p, m_memPROSODYNGRAMINFO.m_dwCurPosn);
   }

   // loop through all the prosody per word
   PCHashString *pphd = (PCHashString*)pOther->m_lPCHashStringWordSyl.Get(0);
   PCHashString phd;
   PTTSPWORDSYLHEADER pwsh, pwshExist;
   DWORD j, k;
   for (i = 0; i < pOther->m_lPCHashStringWordSyl.Num(); i++) {
      phd = pphd[i];
      for (j = 0; j < phd->Num(); j++) {
         pwsh = (PTTSPWORDSYLHEADER) phd->Get(i);
         if (!pwsh)
            continue;

         PWSTR pszWord = phd->GetString (i);
         if (!pszWord)
            continue;

         // create new entry
         pwshExist = WordSylGetInternal (pszWord, pwsh->dwSyllables, pwsh->dwStressBitsMulti, TRUE);
         if (!pwshExist)
            continue;

         // add in new values
         PPROSODYTREND ppt = (PPROSODYTREND) (pwsh+1);
         PPROSODYTREND pptExist = (PPROSODYTREND) (pwshExist+1);
         for (k = 0; k < pwsh->dwSyllables; k++, ppt++, pptExist++) {
            pptExist->iCount += ppt->iCount;
            pptExist->iDurPhone += ppt->iDurPhone;
            pptExist->iDurSkew += ppt->iDurSkew;
            pptExist->iDurSyl += ppt->iDurSyl;
            DWORD dwPitchPoint;
            for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
               pptExist->aiPitchF0Extra[dwPitchPoint] += ppt->aiPitchF0Extra[dwPitchPoint];
            pptExist->iPitchF0Fuji += ppt->iPitchF0Fuji;
            pptExist->iPitchF0FujiDelta += ppt->iPitchF0FujiDelta;
            // pptExist->iPitchBulge += ppt->iPitchBulge;
            // pptExist->iPitchSweep += ppt->iPitchSweep;
            pptExist->iVol += ppt->iVol;
            pptExist->iAccent += ppt->iAccent;
         } // k

         // merge examples
         if (pwsh->pss || pwshExist->pss) {
            if (!pwshExist->pss)
               pwshExist->pss = new CSentenceSyllable;
            if (pwshExist->pss && pwsh->pss && pwsh->pss->m_dwNum) {
               // NOTE: if had more than WORDSENTSYL_KEEPEXAMPLES then not accurately
               // merging these two together
               DWORD dwExampleSyl = pwsh->dwSyllables + 2;  // one for left and right

               // how many definitely get copied
               DWORD dwTrainCount, dwCopy, dwTrainCountExist;
               dwTrainCount = pwsh->dwTrainCount;
               dwTrainCountExist = pwshExist->dwTrainCount;
               if (dwTrainCountExist < WORDSENTSYL_KEEPEXAMPLES) {
                  dwCopy = min(WORDSENTSYL_KEEPEXAMPLES - dwTrainCountExist, dwTrainCount);

                  DWORD dwCopyScale = dwCopy * dwTrainCount / max(pwsh->pss->m_dwNum / dwExampleSyl, 1);
                  dwCopyScale = min(dwCopyScale, dwTrainCount);
                  dwTrainCount -= dwCopyScale;
                  dwTrainCountExist += dwCopyScale;
               }
               else
                  dwCopy = 0;

               // extra
               if (dwTrainCount && dwTrainCountExist) {
                  fp fExtra = (fp)WORDSENTSYL_KEEPEXAMPLES * (fp)dwTrainCount / (fp)(dwTrainCount + dwTrainCountExist);
                  dwCopy += (DWORD)floor(fExtra);
                  fExtra -= floor(fExtra);
                  if (randf(0.0,1.0) < fExtra)
                     dwCopy++;
               }


               for (k = 0; k < dwCopy; k++) {
                 // remove if too many examples
                  DWORD dwIndex;
                  while ((pwshExist->pss->m_dwNum / dwExampleSyl + 1) > WORDSENTSYL_KEEPEXAMPLES) {
                     dwIndex = rand() % (pwshExist->pss->m_dwNum / dwExampleSyl);
                     pwshExist->pss->Remove (dwIndex * dwExampleSyl, (dwIndex+1) * dwExampleSyl);
                  } // remove

                  // add
                  dwIndex = rand() % (pwsh->pss->m_dwNum / dwExampleSyl);
                  pwshExist->pss->Append (pwsh->pss, dwIndex * dwExampleSyl, (dwIndex+1) * dwExampleSyl);
               } // k

               pwshExist->dwTrainCount += pwsh->dwTrainCount;
            } // if pwshExist->pss
         }

      } // j
   } // i

   return TRUE;
}

/*************************************************************************************
CTTSProsody::ProsodyNGramInfoGetInternal - Iternal function for getting values

inputs
   PCMLexicon              pLexTTS - Lexicon with phonemes in it
   PBYTE                   pabPOS - Array. POS in low 4 bits. High four bits indicates if stressed.
   DWORD                   dwNum - Number of entries in pabPOS
   DWORD                   dwIndex - Incex into pabPOS where the center is for the NGram.
   BOOL                    fCreateIfNotExist - If TRUE then create the data if it doesn't exist
returns
   PPROSODYNGRAMINFO - Pointer to the memory containing the data, or NULL if not there.
*/
#ifndef NOMODS_DISABLEPROSODYNGRAM
PPROSODYNGRAMINFO CTTSProsody::ProsodyNGramInfoGetInternal (PCMLexicon pLexTTS, PBYTE pabPOS, DWORD dwNum, DWORD dwIndex,
                                                            BOOL fCreateIfNotExist)
{
   // create if doesn't exist
   DWORD dwNeed = ProsodyNGramInfoCount(pLexTTS) * sizeof(PROSODYNGRAMINFO);
   if (m_memPROSODYNGRAMINFO.m_dwCurPosn < dwNeed) {
      if (!fCreateIfNotExist)
         return NULL;

      if (!m_memPROSODYNGRAMINFO.Required (dwNeed))
         return NULL;
      m_memPROSODYNGRAMINFO.m_dwCurPosn = dwNeed;
      memset (m_memPROSODYNGRAMINFO.p, 0, dwNeed);
   }

   // figure out the index
   int iCur;
   BYTE bPOS;
   DWORD dwOffset = 0;
   DWORD dwPOS;
   for (iCur = (int)dwIndex - (int)PROSODYNGRAMNUM; iCur <= (int)dwIndex + (int)PROSODYNGRAMNUM; iCur++) {
      if ((iCur < 0) || (iCur >= (int)dwNum))
         bPOS = POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION);   // | 0x00 - no stress for punct
      else
         bPOS = pabPOS[iCur];

      dwPOS = (DWORD)(bPOS & 0x0f);
      dwPOS += POS_MAJOR_NUMPLUSONE * (DWORD)((bPOS >> 4) & 0x0f);
      //if (bPOS & 0x80)
      //   dwPOS += POS_MAJOR_NUMPLUSONE;
      dwOffset = (dwOffset * PROSODYNGRAMRANGE) + dwPOS;
   }
   if (dwOffset * sizeof(PROSODYNGRAMINFO) >= dwNeed)
      return NULL;   // shouldnt happen

   return (PPROSODYNGRAMINFO) m_memPROSODYNGRAMINFO.p + dwOffset;
}
#endif



/*************************************************************************************
CTTSProsody::ProsodyNGramInfoGet - Iternal function for getting values

inputs
   PBYTE                   pabPOS - Array. POS in low 4 bits. High 4 bits indicates if stressed.
   DWORD                   dwNum - Number of entries in pabPOS
   DWORD                   dwIndex - Incex into pabPOS where the center is for the NGram.
   PTYPICALSYLINFO       pInfo - Filled in with the info.
returns
   none
*/
void CTTSProsody::ProsodyNGramInfoGet (PBYTE pabPOS, DWORD dwNum, DWORD dwIndex, PTYPICALSYLINFO pInfo)
{
#ifdef NOMODS_DISABLEPROSODYNGRAM
   // so won't use prosody N-gram in training
   memset (pInfo, 0, sizeof(*pInfo));
   return;
#else
   PPROSODYNGRAMINFO pCur = ProsodyNGramInfoGetInternal (pabPOS, dwNum, dwIndex, FALSE);
   if (!pCur) {
      memset (pInfo, 0, sizeof(*pInfo));
      return;
   }

   // else, convert from floats
   DWORD i;
   float *pafFrom = (float*)pCur;
   double *pafTo = (double*)pInfo;
   for (i = 0; i < sizeof(*pInfo)/sizeof(double); i++)
      pafTo[i] = pafFrom[i];
#endif
}


/*************************************************************************************
CTTSProsody::ProsodyNGramInfoGet - This fills in the typical syllable info.

inputs
   PBYTE                   pabPOS - Array. POS in low 4 bits. High 4 bits indicates if stressed.
   DWORD                   dwNumPOS - Number of entries in pabPOS
   DWORD                   dwIndex - Incex into pabPOS where the center is for the NGram.
   CTTSProsody    **ppCTTSProsody - This of prosody objects to use for the comparison,
                  including this one.
   DWORD          dwNum - Number of prosody objects to use. Must be at least one
   PTYPICALSYLINFO       pInfo - Filled in with the info.
returns
   none
*/
#ifndef NOMODS_DISABLEPROSODYNGRAM
void CTTSProsody::ProsodyNGramInfoGet (PBYTE pabPOS, DWORD dwNumPOS, DWORD dwIndex,
                                     CTTSProsody **ppCTTSProsody, DWORD dwNum, PTYPICALSYLINFO pInfo)
{
   DWORD i, j;
   TYPICALSYLINFO TSI;
   double *pfFrom = (double*)&TSI;
   double *pfTo = (double*)pInfo;
   for (i = 0; i < dwNum; i++) {
      if (!i) {
         ppCTTSProsody[i]->ProsodyNGramInfoGet (pabPOS, dwNumPOS, dwIndex, pInfo);
         continue;
      }

      // else, sum in
      ppCTTSProsody[i]->ProsodyNGramInfoGet (pabPOS, dwNumPOS, dwIndex, &TSI);

      for (j = 0; j < sizeof(TSI) / sizeof(double); j++)
         pfTo[j] += pfFrom[j];
   }
}
#endif




/*************************************************************************************
CTTSProsody::ProsodyNGramInfoTrain - Train the info

inputs
   PTYPICALSYLINFO         pInfo - Train this. All valid, except fCount ignored and treated as if 1.
   PBYTE                   pabPOS - Array. POS in low 4 bits. High 4 bits indicates if stressed.
   DWORD                   dwNum - Number of entries in pabPOS
   DWORD                   dwIndex - Index into pabPOS where the center is for the NGram.
returns
   none
*/
void CTTSProsody::ProsodyNGramInfoTrain (PTYPICALSYLINFO pInfo, PBYTE pabPOS, DWORD dwNum, DWORD dwIndex)
{
#ifdef NOMODS_DISABLEPROSODYNGRAM
   // don't want prosody N-gram info at all
   return;
#else
   PPROSODYNGRAMINFO pCur = ProsodyNGramInfoGetInternal (pabPOS, dwNum, dwIndex, TRUE);
   if (!pCur)
      return;

   pCur->fCount++;
   pCur->fDurPhoneSum += (float) pInfo->fDurPhoneSum;
   pCur->fDurSylSum += (float) pInfo->fDurSylSum;
   pCur->fDurSkewSum += (float) pInfo->fDurSkewSum;
   pCur->fPitchBulgeSum += (float) pInfo->fPitchBulgeSum;
   pCur->fPitchSweepSum += (float) pInfo->fPitchSweepSum;
   pCur->fPitchSum += (float)pInfo->fPitchSum;
   pCur->fVolumeSum += (float) pInfo->fVolumeSum;
#endif
}


/*************************************************************************************
CTTSProsody::TypicalSylInfoGetInternal - Iternal function for getting values

inputs
   DWORD                   dwSentenceType - TYPICALSYLINFO_XXX. If dwSentenceType != TYPICALSYLINFO_STATEMENT
                              then this recursively calls into itself and makes sure to include TYPICALSYLINFO_STATEMENT
                              data if there isn't enough data
   DWORD                   dwBin - Bin to use
   double                  fAlpha - Alpha, dwIndex / (dwSentenceLength-1)
   PTYPICALSYLINFO         pInfo - Filled in with the info.
returns
   none
*/
void CTTSProsody::TypicalSylInfoGetInternal (DWORD dwSentenceType, DWORD dwBin, double fAlpha, PTYPICALSYLINFO pInfo)
{
   // find the offset for the sentence
   DWORD dwStoredSentenceLength = SentenceLengthFromBin (dwBin);
   DWORD dwOffset = SentenceLengthTotal (dwBin);
   PTYPICALSYLINFO pTSI = (PTYPICALSYLINFO)m_amemTYPICALSYLINFO[dwSentenceType].p + dwOffset;

   // figure out which two elements get trained
   fAlpha *= (fp)(dwStoredSentenceLength-1);
   DWORD dwIndex = (DWORD)fAlpha;
   DWORD dwIndex2 = min(dwIndex+1, dwStoredSentenceLength -1);
   fAlpha -= (fp)dwIndex;
   double fAlphaInv = (1.0 - fAlpha);

   // zero this out
   memset (pInfo, 0, sizeof(*pInfo));

   if (dwOffset + dwIndex2 >= m_amemTYPICALSYLINFO[dwSentenceType].m_dwCurPosn / sizeof(TYPICALSYLINFO))
      goto tryrecurse;  // beyond end of data, so dont do
      // BUGFIX - Was >, should be >=

   // copy two alphas
   DWORD dwPass, i;
   double *pfTo = (double*)pInfo;
   double *pfFrom;
   double fScale;
   for (dwPass = 0; dwPass < 2; dwPass++) {
      pfFrom = (double*) (pTSI + (dwPass ? dwIndex2 : dwIndex));
      fScale = dwPass ? fAlpha : fAlphaInv;

      for (i = 0; i < sizeof(TYPICALSYLINFO)/sizeof(double); i++)
         if ((pfFrom[i] > -1000000000) && (pfFrom[i] < 1000000000))  // BUGFIX - make sure it's a valid number
            pfTo[i] += pfFrom[i] * fScale;
   } // dwPass

   // fall through
tryrecurse:
   // if a statement, have everything
   if (dwSentenceType == TYPICALSYLINFO_STATEMENT)
      return;

   // else, call into self asking for a statement
   TYPICALSYLINFO TSIState;
   memset (&TSIState, 0, sizeof(TSIState));
   TypicalSylInfoGetInternal (TYPICALSYLINFO_STATEMENT, dwBin, fAlpha, &TSIState);
   if (TSIState.fCount > MINPROSODYSAMPLES)
      fScale = (double)MINPROSODYSAMPLES / TSIState.fCount;
   else
      fScale = 1.0;

   // Add the statement into the score of question mark or exclamation mark, since they're unlikely
   // to have much data
   pfTo = (double*)pInfo;
   pfFrom = (double*) (&TSIState);
   for (i = 0; i < sizeof(TYPICALSYLINFO)/sizeof(double); i++)
      if ((pfFrom[i] > -1000000000) && (pfFrom[i] < 1000000000))  // BUGFIX - make sure it's a valid number
         pfTo[i] += pfFrom[i] * fScale;

}


/*************************************************************************************
CTTSProsody::TypicalSylInfoGet - This fills in the typical syllable info.

inputs
   DWORD                   dwSentenceType - TYPICALSYLINFO_XXX
   DWORD                   dwIndex - Syllable index of the word into the subsentence (with dwSentenceLength syllables)
   DWORD                   dwSentenceLength - Number of syllables in the sentence
   PTYPICALSYLINFO         pInfo - Filled in with the info.
   BOOL                    fAlsoSurround - If TRUE (Default), also gets surrounding bins just
                              to make sure there's some data.
returns
   none
*/
void CTTSProsody::TypicalSylInfoGet (DWORD dwSentenceType, DWORD dwIndex, DWORD dwSentenceLength, PTYPICALSYLINFO pInfo,
                                     BOOL fAlsoSurround)
{
#ifdef NOMODS_DISABLETYPICALSYLINFO
   memset (pInfo, 0, sizeof(*pInfo));
#else
   // find the offset for the sentence
   DWORD dwBin = SentenceLengthToBin (dwSentenceLength);
   double fAlpha = (fp) dwIndex / (fp)((dwSentenceLength >= 2) ? (dwSentenceLength-1) : 1);

   TypicalSylInfoGetInternal (dwSentenceType, dwBin, fAlpha, pInfo);

   if (!fAlsoSurround)
      return;

   // surrounding
   int iOffset, iUse;
   TYPICALSYLINFO TSI;
   double *pfFrom = (double*)&TSI;
   double *pfTo = (double*)pInfo;
   DWORD i;
   for (iOffset = -1; iOffset <= 1; iOffset += 2) {
      iUse = iOffset + (int)dwBin;
      if ((iUse < 0) || (iUse >= SENTENCELENGTHTOBINNUM))
         continue;   // out of range

      TypicalSylInfoGetInternal (dwSentenceType, (DWORD)iUse, fAlpha, &TSI);

      for (i = 0; i < sizeof(TYPICALSYLINFO) / sizeof(double); i++)
         pfTo[i] += pfFrom[i] * 0.1;  // 10% of left/right

   } // iOffset

   // BUGFIX - Don't affect pitchF0Extra sum with the sentence
   memset (pInfo->afPitchF0ExtraSum, 0, sizeof(pInfo->afPitchF0ExtraSum));

   because incorporating F0FujiDelta, missing out on learning from
      beginning of sentence and beginning of phrase, so never get phrase breaks
#endif
}



/*************************************************************************************
CTTSProsody::TypicalSylInfoGet - This fills in the typical syllable info.

inputs
   DWORD                   dwSentenceType - TYPICALSYLINFO_XXX
   DWORD                   dwIndex - Syllable index of the word into the subsentence (with dwSentenceLength syllables)
   DWORD                   dwSentenceLength - Number of syllables in the sentence
   CTTSProsody    **ppCTTSProsody - This of prosody objects to use for the comparison,
                  including this one.
   DWORD          dwNum - Number of prosody objects to use. Must be at least one
   PTYPICALSYLINFO         pInfo - Filled in with the info.
   BOOL                    fAlsoSurround - If TRUE (Default), also gets surrounding bins just
                              to make sure there's some data.
returns
   none
*/
void CTTSProsody::TypicalSylInfoGet (DWORD dwSentenceType, DWORD dwIndex, DWORD dwSentenceLength,
                                     CTTSProsody **ppCTTSProsody, DWORD dwNum, PTYPICALSYLINFO pInfo,
                                     BOOL fAlsoSurround)
{
#ifdef NOMODS_DISABLETYPICALSYLINFO
   memset (pInfo, 0, sizeof(*pInfo));
#else
   DWORD i, j;
   TYPICALSYLINFO TSI;
   double *pfFrom = (double*)&TSI;
   double *pfTo = (double*)pInfo;
   for (i = 0; i < dwNum; i++) {
      if (!i) {
         ppCTTSProsody[i]->TypicalSylInfoGet (dwSentenceType, dwIndex, dwSentenceLength, pInfo, fAlsoSurround);
         continue;
      }

      // else, sum in
      ppCTTSProsody[i]->TypicalSylInfoGet (dwSentenceType, dwIndex, dwSentenceLength, &TSI, fAlsoSurround);

      for (j = 0; j < sizeof(TYPICALSYLINFO) / sizeof(double); j++)
         pfTo[j] += pfFrom[j];
   }
#endif
}



/*************************************************************************************
CTTSProsody::TypicalSylInfoTrain - Trains a typical syllable information.

inputs
   DWORD                   dwSentenceType - TYPICALSYLINFO_XXX
   PTYPICALSYLINFO         pInfo - Info, except that all XXXCount are assumed to be 1.0
   DWORD                   dwIndex - Syllable index of the word into the subsentence (with dwSentenceLength syllables)
   DWORD                   dwSentenceLength - Number of syllables in the sentence
returns
   BOOL - TRUE if success
*/
BOOL CTTSProsody::TypicalSylInfoTrain (DWORD dwSentenceType, PTYPICALSYLINFO pInfo, DWORD dwIndex, DWORD dwSentenceLength)
{
#ifdef NOMODS_DISABLETYPICALSYLINFO
   return TRUE;
#else
   // make sure there's some info
   if (!m_amemTYPICALSYLINFO[dwSentenceType].m_dwCurPosn) {
      DWORD dwSize = SentenceLengthTotal () * sizeof(TYPICALSYLINFO);
      if (!m_amemTYPICALSYLINFO[dwSentenceType].Required (dwSize))
         return FALSE;
      m_amemTYPICALSYLINFO[dwSentenceType].m_dwCurPosn = dwSize;
      memset (m_amemTYPICALSYLINFO[dwSentenceType].p, 0, dwSize);
   }

   // find the offset for the sentence
   DWORD dwBin = SentenceLengthToBin (dwSentenceLength);
   DWORD dwStoredSentenceLength = SentenceLengthFromBin (dwBin);
   DWORD dwOffset = SentenceLengthTotal (dwBin);
   PTYPICALSYLINFO pTSI = (PTYPICALSYLINFO)m_amemTYPICALSYLINFO[dwSentenceType].p + dwOffset;

   // figure out which two elements get trained
   double fAlpha = (fp) dwIndex / (fp)((dwSentenceLength >= 2) ? (dwSentenceLength-1) : 1);
   fAlpha *= (fp)(dwStoredSentenceLength-1);
   dwIndex = (DWORD)fAlpha;
   DWORD dwIndex2 = min(dwIndex+1, dwStoredSentenceLength -1);
   fAlpha -= (fp)dwIndex;
   double fAlphaInv = (1.0 - fAlpha);

   DWORD dwPitchPoint;
   for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
      pTSI[dwIndex].afPitchF0ExtraSum[dwPitchPoint] += pInfo->afPitchF0ExtraSum[dwPitchPoint] * fAlphaInv;
   pTSI[dwIndex].fPitchF0FujiDeltaSum += pInfo->fPitchF0FujiDeltaSum * fAlphaInv;
      // NOTE: The above values will be ignored for TypicalSylInfoGet()
   pTSI[dwIndex].fPitchF0FujiSum += pInfo->fPitchF0FujiSum * fAlphaInv;
   // pTSI[dwIndex].fPitchSweepSum += pInfo->fPitchSweepSum * fAlphaInv;
   // pTSI[dwIndex].fPitchBulgeSum += pInfo->fPitchBulgeSum * fAlphaInv;
   pTSI[dwIndex].fVolumeSum += pInfo->fVolumeSum * fAlphaInv;
   pTSI[dwIndex].fDurPhoneSum += pInfo->fDurPhoneSum * fAlphaInv;
   pTSI[dwIndex].fDurSylSum += pInfo->fDurSylSum * fAlphaInv;
   pTSI[dwIndex].fDurSkewSum += pInfo->fDurSkewSum * fAlphaInv;

   pTSI[dwIndex].fCount += fAlphaInv;

   for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
      pTSI[dwIndex2].afPitchF0ExtraSum[dwPitchPoint] += pInfo->afPitchF0ExtraSum[dwPitchPoint] * fAlpha;
   pTSI[dwIndex2].fPitchF0FujiDeltaSum += pInfo->fPitchF0FujiDeltaSum * fAlpha;
      // NOTE: The above values will be ignored for TypicalSylInfoGet()

   pTSI[dwIndex2].fPitchF0FujiSum += pInfo->fPitchF0FujiSum * fAlpha;
   //pTSI[dwIndex2].fPitchSweepSum += pInfo->fPitchSweepSum * fAlpha;
   //pTSI[dwIndex2].fPitchBulgeSum += pInfo->fPitchBulgeSum * fAlpha;
   pTSI[dwIndex2].fVolumeSum += pInfo->fVolumeSum * fAlpha;
   pTSI[dwIndex2].fDurPhoneSum += pInfo->fDurPhoneSum * fAlpha;
   pTSI[dwIndex2].fDurSylSum += pInfo->fDurSylSum * fAlpha;
   pTSI[dwIndex2].fDurSkewSum += pInfo->fDurSkewSum * fAlpha;

   pTSI[dwIndex2].fCount += fAlpha;

   return TRUE;
#endif
}

   
/*************************************************************************************
CTTSProsody::PhonemePauseGet - Gets the memory thats used for phoneme pauses.

inputs
   DWORD          dwPhoneLeft - Phoneme on the left. (Unsorted phoneme number)
   DWORD          dwPhoneRight - Phoneme on the right. (Unsorted phoneme number)
   BOOL           fCreateIfNotExist - Create if doesn't exist
   PCMLexicon     pLex - Used to get the number of phonemes
returns
   DWORD * -   if dwPhoneLeft == 0 and dwPhoneRight == 0, an
               Array of dwPhones (left) x dwPhones (right) x 2 DWORDs.
               DWORD [0] = total count encountered, [1] = count with pauses between them

               Otherwise, end up pointing to 2 DWORDs, [0] = total encountered, [1] = count between pauses
               If dwPhoneLeft or dwPhoneRight exceed pLex->PhonemeNum() then will return NULL
*/
DWORD *CTTSProsody::PhonemePauseGet (DWORD dwPhoneLeft, DWORD dwPhoneRight, BOOL fCreateIfNotExist, PCMLexicon pLex)
{
   DWORD dwPhones = pLex->PhonemeNum();
   if ((dwPhoneLeft >= dwPhones) || (dwPhoneRight >= dwPhones))
      return NULL;

   DWORD dwNeed = dwPhones * dwPhones * 2 * sizeof(DWORD);
   if (!m_memPhonemePause.m_dwCurPosn) {
      if (!m_memPhonemePause.Required(dwNeed))
         return NULL;
      m_memPhonemePause.m_dwCurPosn = dwNeed;
      memset (m_memPhonemePause.p, 0, dwNeed);
   }

   if (m_memPhonemePause.m_dwCurPosn != dwNeed)
      return NULL;

   return (DWORD*)m_memPhonemePause.p + (dwPhoneLeft * dwPhones + dwPhoneRight) * 2;
}


/*************************************************************************************
CTTSProsody::Randomize - This randomizes all the sentences in the lexicon to
ensure that a search won't always produce the same results, and there
algorithm will never get "stuck".

Call this before instigating a seach... or is this done internally?
*/
void CTTSProsody::Randomize (void)
{
   m_pPCSSRandom.Init (sizeof(PCSentenceSyllable), m_lPCSentenceSyllable.Get(0), m_lPCSentenceSyllable.Num());

   // NOTE: No point randomizing m_lPCSentenceSyllableWord

   // loop
   DWORD i;
   PCSentenceSyllable *ppss = (PCSentenceSyllable*)m_pPCSSRandom.Get(0);
   PCSentenceSyllable *ppssOrig = (PCSentenceSyllable*)m_lPCSentenceSyllable.Get(0);
   for (i = 0; i < m_pPCSSRandom.Num(); i++) {
#ifdef _DEBUG
      ppssOrig[i]->m_dwSentIndex = i;  // for test
#endif

      DWORD dwIndex = (DWORD)rand() % m_pPCSSRandom.Num();
      PCSentenceSyllable pTemp = ppss[i];
      ppss[i] = ppss[dwIndex];
      ppss[dwIndex] = pTemp;
   } // i
}


/*************************************************************************************
CTTSProsody::SentenceAdd - This adds a PCSentenceSyllable to the list.

inputs
   PCMLexicon              pLexTTS - Lexicon with phonemes in it
   PCSentenceSyllable      pss - Sentence to add. The actual object is taken
                              over by the CTTSProsody object.
   PCMLexicon              pLex - Lexicon that's used to determine the
                              word index numbers.
returns
   BOOL - TRUE if success
*/
BOOL CTTSProsody::SentenceAdd (PCMLexicon pLexTTS, PCSentenceSyllable pss, PCMLexicon pLex)
{
   // remap the words
   pss->RemapWords (pLex, m_pLexInProsody, TRUE, &m_lPCSentenceSyllable);

   // add
   m_lPCSentenceSyllable.Add (&pss);
   m_pPCSSRandom.Clear();
   
   return TRUE;
}

/*************************************************************************************
CTTSProsody::CompareSweepSequence - This finds all matches of this sentence in
the prosody test sentences. NOTE: The test sentences should have been Randomized().

inputs
   PCMLexicon     pLexTTS - Lexicon with phonemes in it
   PCOMPARESYLINFO pInfo - Score penalties to use.
   PCSentenceSyllable      pThis - Current sentence being tested. This does NOT
                           have extra punctuation added, NOR is it necessarily
                           in the same lexicon.
   int            iOffsetThis - offset, in syllables, into THIS sentence that
                     will be the start. Can be negative or beyond edge of sentence
   PCMLexicon     pLex - Lexicon that pThis uses
   PCListFixed    plBESTSENTSWEEP - This should be filled in with a set of
                     BESTSENTSWEEP that are already known for dwOffsetThis.
                     They should be in sorted order, from highest score to
                     lowest.
                     
                     If a candidate is already represented on
                     plBESTSENTSWEEP, AND the right dwOffset is there,
                     then this won't bother doing a sweep for it.

                     If a comparison is successful AND its score is better
                     than the worse score (or there are empty slots) then
                     it will be added.
   DWORD          dwSlots - Number of slots allowed in plBESTSENTSWEEP.
                     Basically, keep the best N of these.
   PCListFixed    plBESTSENTSWEEPExclude - If find a match for any of these
                     then don't bother adding them to plBESTSENTSWEEP.
                     Can be NULL.
returns
   none
*/
#define PUNCTEXTRA         2        // allow 2 extra slots for punctuation, to left and right
void CTTSProsody::CompareSweepSequence (PCMLexicon pLexTTS, PCOMPARESYLINFO pInfo, PCSentenceSyllable pThis, int iOffsetThis, PCMLexicon pLex,
                                      PCListFixed plBESTSENTSWEEP, DWORD dwSlots,
                                      PCListFixed plBESTSENTSWEEPExclude)
{
   // make a clone of the sentence, convert words, and add punctuation
   PCSentenceSyllable pClone = pThis->Clone();
   if (!pClone)
      return;
   pClone->RemapWords (pLex, m_pLexInProsody, FALSE, NULL);

   WORD wPeriod = (WORD) m_pLexInProsody->WordFind (L".");

#if 0 // no longer needed
   // potentially add functuation at end
   DWORD i, dwPunctAtEnd = 0;
   DWORD dwPunctWord = (DWORD)-1;
   for (i = pClone->m_dwNum-1; i < pClone->m_dwNum; i++)
      if (pClone->m_pabPOS[i] == POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION)) {
         // if there is punctuation at end, also extend it a few more
         if (!dwPunctAtEnd)
            dwPunctWord = pClone->m_paSyl[i].wWord;
         dwPunctAtEnd ++;
      }
      else
         break;
   while (dwPunctAtEnd < PUNCTEXTRA) { // extra punctuation at end
      SENTSYLEMPH Emph;
      memset (&Emph, 0, sizeof(Emph));
      Emph.fDurPhone = Emph.fDurSyl = Emph.fPitch = Emph.fVolume = 1;
      pClone->Add (dwPunctWord, POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION), 0, &Emph, FALSE, 0, 0, 0, 0, 0);
      dwPunctAtEnd++;
   }
#endif

   // loop
   PCSentenceSyllable *ppss = (PCSentenceSyllable*)m_pPCSSRandom.Get(0);
   DWORD i;
   for (i = 0; i < m_pPCSSRandom.Num(); i++)
      pClone->CompareSweepSequence (pInfo, iOffsetThis, ppss[i], wPeriod, TRUE, plBESTSENTSWEEP, dwSlots, plBESTSENTSWEEPExclude);
   
   // clean up
   delete pClone;
}


// BEAMSEARCHHYP - Beam search hypothesis
#define BEAMSEARCHHYP_HISTORY       10     // remember 10 syllables back
#define BEAMSEARCHHYP_MAX           100   // maximum hypothesis to keep.
// #define BEAMSEARCHHYP_VERYBADSCORE  (BEAMSEARCHHYP_HISTORY * COMPARESINGLESYLLABLE_CROSSSENTENCEPENALTY)

typedef struct {
   DWORD       adwHistory[BEAMSEARCHHYP_HISTORY];  // history of the last 5 syllables, indecies into
                                                   // plSyl. Reverse order, so most recent is at [0]
   fp          fScore;                             // score. Lower is better
} BEAMSEARCHHYP, *PBEAMSEARCHHYP;


/*************************************************************************************
CompareSENTENCESYLLABLE - Compares two SENTENCESYLLABLE structures for
an "acoustic" distance.

inputs
   PSENTENCESYLLABLE pssThis - This one
   PSENTENCESYLLABLE pssTest - Test one
returns
   DWORD - Score. Higher is worse
*/

// #define MAXCOMPARESENTENCESYLLABLE     (256 * 6 + 256/4)      // maximum value that expect to get from CompareSENTENCESYLLABLE

#if 0 // old code
__inline DWORD CompareSENTENCESYLLABLE (PSENTENCESYLLABLE pssThis, PSENTENCESYLLABLE pssTest)
{
   return
#ifdef USEDURSYL
      (DWORD)abs((int)pssThis->bDurPhone - (int)pssTest->bDurPhone) +
      (DWORD)abs((int)pssThis->bDurSyl - (int)pssTest->bDurSyl) +
#else
      // BUGFIX - Since ignoring bDurSyl when synthesize now, since didn't sound as good,
      // make sure to double weight of bDurPhone, and ignore bDurSyl for this calculation
      (DWORD)abs((int)pssThis->bDurPhone - (int)pssTest->bDurPhone) * 2 +
#endif
      (DWORD)abs((int)pssThis->bPitch - (int)pssTest->bPitch) * 2 +  // emphasize pitch a bit more
      (DWORD)abs((int)pssThis->bVol - (int)pssTest->bVol) / 4 +   // BUGFIX - demphasize volume
      (DWORD)abs((int)pssThis->cPitchBulge - (int)pssTest->cPitchBulge) +
      (DWORD)abs((int)pssThis->cPitchSweep - (int)pssTest->cPitchSweep) +
      (DWORD)abs((int)pssThis->cDurSkew - (int)pssTest->cDurSkew)
      ;
}
#endif // 0

/*************************************************************************************
CTTSProsody::BeamSearchSwitchSentencePenalty - Figure out the penalty for
switching from one sentence to another.

inputs
   PCOMPARESYLINFO pInfo - Score penalties to use.
   PCSentenceSyllable      pssCur - Current sentence syllable
   int                     iOffsetCur - Offset into current sentence for the pre-switch syllable
   PCSentenceSyllable      pssTo - Switching to this sentenc syllable
   int                     iOffsetTo - Offset into To sentence, for the pre-switch syllable.
   WORD                    wPeriod - Definition of the period word, so sentence start/end match better
returns
   fp - Score to add
*/
fp CTTSProsody::BeamSearchSwitchSentencePenalty (PCOMPARESYLINFO pInfo, PCSentenceSyllable pssCur, int iOffsetCur,
                                                  PCSentenceSyllable pssTo, int iOffsetTo, WORD wPeriod)
{
#define SWITCHPENALTYDIST        3     // compare three syllables to left
   int iOffset;
   fp fSum = 0.0;
   for (iOffset = -SWITCHPENALTYDIST; iOffset < SWITCHPENALTYDIST; iOffset++) {
      fp fScore = pssCur->CompareSingleSyllable (pInfo, iOffsetCur + iOffset,
         pssTo, iOffsetTo + iOffset, wPeriod, TRUE, FALSE, FALSE);

      fp fWeight = SWITCHPENALTYDIST - abs(iOffset) + ((iOffset < 0) ? 1 : 0);
      fWeight /= (fp)SWITCHPENALTYDIST;   // so weight goes from 1.0 to 0.0

      fSum += fScore * fWeight;
   } // iOffset
   
   return fSum;

#if 0 // old code
   // return COMPARESINGLESYLLABLE_CROSSSENTENCEPENALTY * BEAMSEARCHSWITCHPENALTY;

   PSENTENCESYLLABLE psCur, psTo;
   SENTENCESYLLABLE sDefault;

   if (!pssCur || !pssTo) {
      memset (&sDefault, 0, sizeof(sDefault));
      sDefault.bPitch = 100;
      sDefault.bVol = 100;
      sDefault.bDurPhone = 100;
      sDefault.bDurSyl = 100;
      // NOTE: Don't bother with cDurSkew since defaults to 0
   }

   int i;
   int iErr = 0;
   int iErrWeight = 0;
   // loop over this syllable and the next one
   iOffsetCur--;  // so start before
   iOffsetTo--;
   for (i = 0; i < 4; i++, iOffsetCur++, iOffsetTo++) {
      int iWeight = ((i >= 1) && (i <= 2)) ? 2 : 1;
      iErrWeight += iWeight;

      // figure out the SENTENCESYLLABLE to use
      if (pssCur) {
         if ((iOffsetCur >= 0) && (iOffsetCur < (int) pssCur->m_dwNum)) {
            if ((pssCur->m_pabPOS[iOffsetCur] & 0x0f) == POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION))
               psCur = NULL;  // since punctuation
            else
               psCur = &pssCur->m_paSyl[iOffsetCur];
         }
         else
            psCur = NULL;  // since beyond edge of setnence, so must be punctuation
      }
      else // default, so no real change
         psCur = &sDefault;

      if (pssTo) {
         if ((iOffsetTo >= 0) && (iOffsetTo < (int) pssTo->m_dwNum)) {
            if ((pssTo->m_pabPOS[iOffsetTo] & 0x0f) == POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION))
               psTo = NULL;  // since punctuation
            else
               psTo = &pssTo->m_paSyl[iOffsetTo];
         }
         else
            psTo = NULL;  // since beyond edge of setnence, so must be punctuation
      }
      else // default, so no real change
         psTo = &sDefault;

      // see if either is NULL
      if (!psCur || !psTo) {
         // if both POS then no penalty
         if (!psCur && !psTo) {
            iErr += iWeight * MAXCOMPARESENTENCESYLLABLE / BEAMSEARCHSWITCHSENTENCEPENALTY_LESSFORPOS;
            continue;
         }

         // if only one POS then max penalty
         iErr += iWeight * MAXCOMPARESENTENCESYLLABLE;
         continue;
      }

      // else, add
      iErr += iWeight * (int) CompareSENTENCESYLLABLE (psCur, psTo);
   } // i

   // else, return comparison
   return iErr / iErrWeight;
#endif // 0
}

/*************************************************************************************
CTTSProsody::BeamSearch - This does a beam search through all the syllables,
looking for the highest score.

inputs
   PCOMPARESYLINFO pInfo - Score penalties to use.
   DWORD             dwThread - 0..MAXRAYTHREAD-1
   PCListFixed       plSyl - One entry per syllable (including PUNCTEXTRA at beginning
                        and end). Each entry is a PCListFixed of BESTSENTWEEP. Each
                        list MUST have at least one entry.
   fp                *pfScore - Filled in with the score of the match
   fp                fAccuracy - Use 1.0 for normal accuracy (10,000 units), 2.0 for higher, etc.
                        More accurate is slower
   DWORD             dwRandomness - How much to randomize the match. Defaults to 1. Use 2 if
                        doing multiple pass synthesis.
returns
   PCListFixed - List of BESTSENTWEEP structures. One entry per syllable in plSyl MINUS
         2 * PUNCTEXTRA, so extra punctation at start/end excluded.
         You must delete the returned list.
*/


static int _cdecl BEAMSEARCHHYPSortScore (const void *elem1, const void *elem2)
{
   BEAMSEARCHHYP *pdw1, *pdw2;
   pdw1 = (BEAMSEARCHHYP*) elem1;
   pdw2 = (BEAMSEARCHHYP*) elem2;

   if (pdw1->fScore > pdw2->fScore)
      return 1;
   else if (pdw1->fScore < pdw2->fScore)
      return -1;
   else
      return 0;
   // return (int)pdw1->iScore - (int)pdw2->iScore;
}



static int _cdecl BEAMSEARCHHYPSortMatch(const void *elem1, const void *elem2)
{
   BEAMSEARCHHYP *pdw1, *pdw2;
   pdw1 = (BEAMSEARCHHYP*) elem1;
   pdw2 = (BEAMSEARCHHYP*) elem2;

   int iRet = memcmp (&pdw1->adwHistory[0], &pdw2->adwHistory[0], sizeof(pdw1->adwHistory));
   if (iRet)
      return iRet;

   // else, compare by score
   if (pdw1->fScore > pdw2->fScore)
      return 1;
   else if (pdw1->fScore < pdw2->fScore)
      return -1;
   else
      return 0;
   // return (int)pdw1->iScore - (int)pdw2->iScore;
}

PCListFixed CTTSProsody::BeamSearch (PCOMPARESYLINFO pInfo, DWORD dwThread, PCListFixed plSyl, fp *pfScore, fp fAccuracy, DWORD dwRandomness)
{
#if 0 // def _DEBUG
   DWORD dwStartTime = GetTickCount();
#endif
   *pfScore = 0;
   // start out with a default hypothesis
   BEAMSEARCHHYP bsh;
   memset (&bsh, 0, sizeof(bsh));

   WORD wPeriod = (WORD) m_pLexInProsody->WordFind (L".");

   // ping-pong buffers
   CListFixed alPingPong[2];
   alPingPong[0].Init (sizeof(BEAMSEARCHHYP));
   alPingPong[1].Init (sizeof(BEAMSEARCHHYP));
   alPingPong[0].Add (&bsh);

   CListFixed lIndexToKeep;
   lIndexToKeep.Init (sizeof(DWORD));

   DWORD dwMaxHyp = (DWORD)((fp)BEAMSEARCHHYP_MAX * fAccuracy);

   // loop over all syllabled
   DWORD dwSyl, i;
   PCListFixed *ppl = (PCListFixed*)plSyl->Get(0);
   DWORD dwPingFrom, dwPingTo;
   PCListFixed plPingFrom, plPingTo;
   PBEAMSEARCHHYP pBSH;
   for (dwSyl = 0; dwSyl < plSyl->Num(); dwSyl++) {
      // what pingpong buffer from/to
      dwPingFrom = dwSyl % 2;
      dwPingTo = (dwSyl+1) %2;
      plPingFrom = &alPingPong[dwPingFrom];
      plPingTo = &alPingPong[dwPingTo];

      // sort by score
      qsort (plPingFrom->Get(0), plPingFrom->Num(), sizeof(BEAMSEARCHHYP), BEAMSEARCHHYPSortScore);

      // what is the index to use
      pBSH = (PBEAMSEARCHHYP) plPingFrom->Get(0);
      DWORD dwIndexToKeep = pBSH->adwHistory[BEAMSEARCHHYP_HISTORY-1];
      lIndexToKeep.Add (&dwIndexToKeep);

      // get rid of any results whose oldest match is NOT the same is the syllable
      // index to keep
      for (i = plPingFrom->Num()-1; i < plPingFrom->Num(); i--)
         if (pBSH[i].adwHistory[BEAMSEARCHHYP_HISTORY-1] != dwIndexToKeep) {
            plPingFrom->Remove (i);
            pBSH = (PBEAMSEARCHHYP) plPingFrom->Get(0);  // just in case changed
         }


      // if there are too many hypothesis then prune down
      if (plPingFrom->Num() > dwMaxHyp) {
         plPingFrom->Truncate (dwMaxHyp);
         pBSH = (PBEAMSEARCHHYP) plPingFrom->Get(0);  // just in case changed
      }

      // remove any score that's really bad
      for (i = 1; i < plPingFrom->Num(); i++)
         if (pBSH[i].fScore > pBSH[0].fScore + pInfo->fVeryBadScore)
            break;
      if (i < plPingFrom->Num()) {
         plPingFrom->Truncate (i);
         pBSH = (PBEAMSEARCHHYP) plPingFrom->Get(0);  // just in case changed
      }


#if 0 // never does anything so dont bother
      // get rid of any identical matches and keep the best score
      // NOTE: This code never seems to do anything, but it may not need to the way I'm eliminating
      // any hypothesis that don't match the best
      qsort (plPingFrom->Get(0), plPingFrom->Num(), sizeof(BEAMSEARCHHYP), BEAMSEARCHHYPSortMatch);
      for (i = plPingFrom->Num()-2; i < plPingFrom->Num(); i--) {
         if (pBSH[i].adwHistory[0] == pBSH[i+1].adwHistory[0])
            continue;   // different

         // else, the same, so keep the lower score
         if (pBSH[i].iScore <= pBSH[i+1].iScore)
            plPingFrom->Remove (i+1);
         else
            plPingFrom->Remove (i);
         pBSH = (PBEAMSEARCHHYP) plPingFrom->Get(0);  // just in case changed
      }
#endif

      // hypothesize
      plPingTo->Clear();
      PCListFixed plBESTSENTSWEEP = ppl[dwSyl];
      PBESTSENTSWEEP pBSS = (PBESTSENTSWEEP) plBESTSENTSWEEP->Get(0);

      fp fRandomAmount = pInfo->fRandomize * (fp) (1+dwRandomness) / 2.0;

      // add jitter so won't choose same result all the time
      for (i = 0; i < plBESTSENTSWEEP->Num(); i++)
         pBSS[i].afScoreSylWithRand[dwThread] = pBSS[i].fScoreSyl + randf(0.0, fRandomAmount);

      pBSH = (PBEAMSEARCHHYP) plPingFrom->Get(0);  // just in case changed

#if 0 // swap the loops to make faster
      for (i = 0; i < plPingFrom->Num(); i++, pBSH++) {
         int iScoreBase = pBSH->iScore;
         DWORD dwLastIndex = pBSH->adwHistory[0];
         PCListFixed plBESTSENTSWEEPLast = dwSyl ? ppl[dwSyl-1] : 0;
         PBESTSENTSWEEP pBSSLast = plBESTSENTSWEEPLast ? (PBESTSENTSWEEP) plBESTSENTSWEEPLast->Get(dwLastIndex) : NULL;

         // move all the bits up so can insert new
         memmove (&pBSH->adwHistory[1], &pBSH->adwHistory[0], sizeof(DWORD)*(BEAMSEARCHHYP_HISTORY-1));


         // loop over all indecies
         DWORD dwIndex;
         for (dwIndex = 0; dwIndex < plBESTSENTSWEEP->Num(); dwIndex++) {
            // hypothesize jumping to this index
            pBSH->adwHistory[0] = dwIndex;
            pBSH->iScore = iScoreBase + pBSS[dwIndex].iScoreSylWithRand;

            // penalty for switching sentences
            if (pBSSLast) {
               if ((pBSSLast->pSent != pBSS[dwIndex].pSent) || (pBSSLast->iOffsetTest+1 != pBSS[dwIndex].iOffsetTest))
                  pBSH->iScore += COMPARESINGLESYLLABLE_CROSSSENTENCEPENALTY;
               else
                  pBSH->iScore += 0;   // so can test a contiguous block
            }

            plPingTo->Add (pBSH);
         } // dwIndex
      } // i
#endif // 0

      // do a memmove for all existing hypothesis
      for (i = 0; i < plPingFrom->Num(); i++) {
         // move all the bits up so can insert new
         memmove (&pBSH[i].adwHistory[1], &pBSH[i].adwHistory[0], sizeof(DWORD)*(BEAMSEARCHHYP_HISTORY-1));
      } // i

      // swap the loops to make faster
      DWORD dwIndex;
      PCListFixed plBESTSENTSWEEPLast = dwSyl ? ppl[dwSyl-1] : 0;
      PBESTSENTSWEEP pBSSLast = plBESTSENTSWEEPLast ? (PBESTSENTSWEEP) plBESTSENTSWEEPLast->Get(0) : NULL;
      for (dwIndex = 0; dwIndex < plBESTSENTSWEEP->Num(); dwIndex++) {
         DWORD dwBestIndex = (DWORD)-1;
         fp fBestScore = 0;

         for (i = 0; i < plPingFrom->Num(); i++) {
            fp fScore = pBSH[i].fScore + pBSS[dwIndex].afScoreSylWithRand[dwThread];

            // penalty for switching sentences
            if (pBSSLast) {
               DWORD dwLastIndex = pBSH[i].adwHistory[1];
               if ((pBSSLast[dwLastIndex].pSent != pBSS[dwIndex].pSent) || (pBSSLast[dwLastIndex].iOffsetTest+1 != pBSS[dwIndex].iOffsetTest))
                  fScore += BeamSearchSwitchSentencePenalty (pInfo, pBSSLast[dwLastIndex].pSent, pBSSLast[dwLastIndex].iOffsetTest,
                     pBSS[dwIndex].pSent, pBSS[dwIndex].iOffsetTest-1, wPeriod); // / BEAMSEARCHSWITCHPENALTY;
               else
                  fScore += 0;   // so can test a contiguous block
            }

            // keep the best
            if ((dwBestIndex == (DWORD)-1) || (fScore < fBestScore)) {
               dwBestIndex = i;
               fBestScore = fScore;
            }
         } // i

         // add the best to the hypothesis
         if (dwBestIndex != (DWORD)-1) {
            pBSH[dwBestIndex].fScore = fBestScore;
            pBSH[dwBestIndex].adwHistory[0] = dwIndex;
            plPingTo->Add (&pBSH[dwBestIndex]);
         }
      } //dwIndex

   } // dwSyl

   // find the best result and keep that
   dwPingFrom = dwSyl % 2;
   plPingFrom = &alPingPong[dwPingFrom];
   qsort (plPingFrom->Get(0), plPingFrom->Num(), sizeof(BEAMSEARCHHYP), BEAMSEARCHHYPSortScore);
   pBSH = (PBEAMSEARCHHYP) plPingFrom->Get(0);
   for (i = BEAMSEARCHHYP_HISTORY-1; i < BEAMSEARCHHYP_HISTORY; i--)
      lIndexToKeep.Add (&pBSH->adwHistory[i]);  // so have it added to keep

   // create the list of BESTSENTSWEEP. Exclude zero's at beginning, as well as PUNCTEXTRA and beginning and end
   *pfScore = pBSH->fScore;
   PCListFixed plRet = new CListFixed;
   if (!plRet)
      return NULL;
   plRet->Init (sizeof(BESTSENTSWEEP));
   DWORD *padwIndexToKeep = (DWORD*)lIndexToKeep.Get(0);
   for (i = lIndexToKeep.Num() - plSyl->Num() + PUNCTEXTRA; i < lIndexToKeep.Num() - PUNCTEXTRA; i++) {
      int iSyl = (int)i - (int)(lIndexToKeep.Num() - plSyl->Num());
      if (iSyl < 0)
         continue;   // zero index

      PCListFixed plBESTSENTSWEEP = ppl[iSyl];
      PBESTSENTSWEEP pBSS = (PBESTSENTSWEEP) plBESTSENTSWEEP->Get(padwIndexToKeep[i]);

      plRet->Add (pBSS);
   } // i

#if 0 // def _DEBUG
   WCHAR szTemp[64];
   swprintf (szTemp, L"\r\nBeamSearch time=%d", (int)(GetTickCount() - dwStartTime));
   OutputDebugStringW (szTemp);
#endif

   // done
   return plRet;
}


/*************************************************************************************
CTTSProsody::BeamSearchHypToSentenceSyllable - This takes the output
from BeamSearch() and fills in a SentenceSyllable().

inputs
   PCListFixed       plBESTSENTSWEEP - List of BESTSENTSWEEP from BeamSearch().
returns
   PCSentenceSyllable - New sentence syllable object. It must be deleted by the
      caller
*/
PCSentenceSyllable CTTSProsody::BeamSearchHypToSentenceSyllable (PCListFixed plBESTSENTSWEEP)
{
   PBESTSENTSWEEP pBSS = (PBESTSENTSWEEP) plBESTSENTSWEEP->Get(0);
   DWORD dwNum = plBESTSENTSWEEP->Num();

   PCSentenceSyllable pSS = new CSentenceSyllable;
   if (!pSS)
      return NULL;

   DWORD i;
   for (i = 0; i < dwNum; i++, pBSS++) {
      PCSentenceSyllable pSent = pBSS->pSent;

      // if beyond edge then null.
      if ((pBSS->iOffsetTest < 0) || !pSent || (pBSS->iOffsetTest >= (int)pSent->m_dwNum)) {
         SENTSYLEMPH Emph;
         memset (&Emph, 0, sizeof(Emph));
         Emph.fPitchF0Fuji = Emph.fVolume = Emph.fDurPhone = Emph.fDurSyl = 1;
         // NOTE: Not modifying fDurSkew and fAccent because should be 0

         pSS->Add (
            (DWORD)-1, 
            POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION),
            0, // sylindex
            0, // rule depth
            &Emph, FALSE,
            0, // NOTE: with punctuation must be syllable 0 and no phonemes
            0, // NOTE: With functuation, always in most common "words"
            0,
            0);
         continue;
      }

      pSS->Append (pSent, (DWORD)pBSS->iOffsetTest, (DWORD)pBSS->iOffsetTest+1);
   } // i

   return pSS;
}


/*************************************************************************************
CTTSProsody::SentenceSyllablesCombineWithProsody - Combine one or more
sentence syllables together, and add generic prosody rules.

inputs
   PCOMPARESYLINFO pInfo - COmparison info to use (kind of)
   fp                      fFujiRangeMax - Maximum fuji range that will allow in the synthesized sentence
   fp                      fFujiPhraseRangeMax - Maximum that a single phrase can be
   fp                      fFujiPhraseRangeMin - Phrases should (ideally) cover this much
   PCMLexicon              pLexTTS - Lexicon with phonemes in it
   PCSentenceSyllable      pThis - Should be the same length as the sentences
                           to average. Will be modified in place, so can
                           be returned from FindBestMatch. Should already have POS
                           properly filled in. (Basically, passed in through
                           FindBestMatch())
   PCSentenceSyllable      *papSS - Pointer to an array of sentence syllables to combine
   DWORD                   dwNumSent - Number to combine
   PCMLexicon     pLex - Lexicon that pThis uses
   CTTSProsody    **ppCTTSProsody - This of prosody objects to use for the comparison,
                  including this one.
   DWORD          dwNum - Number of prosody objects to use. Must be at least one
returns
   none... although it changes the values. NOTE: bPauseProb will be filled with 0..15,
      where 0 is no microsilnece, 15 is always microsilence
*/

// #define RELATIVEPITCHWEIGHT   0.5   // how much to weight the relative pitch with absolute pitch
// #define RPWINDOWSIZE       40       // filter window size, in syllables, to prevent too great a pitch change


// SSCWINFO - Internal structure on a per syllable basis
typedef struct {
   SYLEMPH        Emph;    // emphasis
   BYTE           abNGramPOS[4]; // ngram
   fp             fMicroSilence; // microsilence
} SSCWINFO, *PSSCWINFO;

// SSCWWORDSTART - Internal structure for word start info
typedef struct {
   DWORD          dwSyllable; // syllable number
   DWORD          dwPOS;      // part of speech
   DWORD          dwStressBitsMulti;   // multistress bits
   WCHAR          szWord[256];   // word
} SSCWWORDSTART, *PSSCWWORDSTART;

void CTTSProsody::SentenceSyllablesCombineWithProsody (PCOMPARESYLINFO pInfo, fp fFujiRangeMax, fp fFujiPhraseRangeMax, fp fFujiPhraseRangeMin,
                                                       PCMLexicon pLexTTS, PCSentenceSyllable pThis,
                                                       PCSentenceSyllable *papSS, DWORD dwNumSent,
                                                       PCMLexicon pLex,
                                 CTTSProsody **ppCTTSProsody, DWORD dwNum)
{
   // just to test, make sure have right number of words
   if (pThis->m_dwNum != papSS[0]->m_dwNum)
      return;  // error

   // now that have a lot of templates for each word, blend them all together, weighting
   // by the dwscore... that way, if there are bad recordings, they will tend to be
   // counteracted by having lots of data

   DWORD i, j;

   // loop through and blend the results of the best ones
   CMem memRelative;
   SYLEMPH Emph;
   fp fWeight;
   if (!memRelative.Required (pThis->m_dwNum * sizeof(fp)))
      return;  // not likely to happen
   fp *pafRelative = (fp*)memRelative.p;
   memset (pafRelative, 0, pThis->m_dwNum * sizeof(fp));
   fp fPitchAverage = 0;

#if 0 // to test
#ifdef _DEBUG
   FILE *file = fopen ("c:\\test.txt", "wt");
#else
   FILE *file = fopen ("c:\\testr.txt", "wt");
#endif
#endif // 0

   // look ahead and determine the word parts of speech
   BYTE abNGramPOS[4];
   DWORD dwNextPOSSyl = 0;
   for (j = 0; j < 4; j++) {
      if (j < 3) {
         abNGramPOS[j] = POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION);
         continue;
      }

      for ( ; (dwNextPOSSyl < pThis->m_dwNum) && !pThis->IsStartOfWord(dwNextPOSSyl); dwNextPOSSyl++);

      if (dwNextPOSSyl < pThis->m_dwNum) {
         abNGramPOS[j] = pThis->m_pabPOSStress[dwNextPOSSyl] & 0x0f;
         dwNextPOSSyl++;
      }
      else
         abNGramPOS[j] = POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION);   // beyond the end
   }

   // figure out which subsentence a word is in, and what the index is, as well as how long the subsentence is
   CListFixed lFBMSUBSENT;
   FBMSUBSENT fss;
   FBMSUBSENT *pfss;
   lFBMSUBSENT.Init (sizeof(FBMSUBSENT));
   WORD wPeriod = (WORD) m_pLexInProsody->WordFind (L".");
   WORD wQuestion = (WORD) m_pLexInProsody->WordFind (L"?");
   WORD wExclamation = (WORD) m_pLexInProsody->WordFind (L"!");
   DWORD dwSubSentence = 0;
   DWORD dwIndex = 0;
   DWORD dwSentenceType = TYPICALSYLINFO_STATEMENT;
   for (i = 0; i < pThis->m_dwNum; i++) {
      if ( ((pThis->m_pabPOSStress[i] & 0x0f) == POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION)) ) {
         // if it's a sentence boundary then set some stuff
         if (
            (pThis->m_paSyl[i].wWord != (WORD)-1) &&
            ( (pThis->m_paSyl[i].wWord == wPeriod) || (pThis->m_paSyl[i].wWord == wQuestion) || (pThis->m_paSyl[i].wWord == wExclamation) ) ) {

               // loop back over everything have written for this sentence and change the subsentence
               if (pThis->m_paSyl[i].wWord == wQuestion)
                  dwSentenceType = TYPICALSYLINFO_QUESTION;
               else if (pThis->m_paSyl[i].wWord == wExclamation)
                  dwSentenceType = TYPICALSYLINFO_EXCLAMATION;
               pfss = (PFBMSUBSENT) lFBMSUBSENT.Get(0);
               for (j = 0; j < lFBMSUBSENT.Num(); j++, pfss++)
                  if (pfss->dwSubSentence == dwSubSentence)
                     pfss->dwSentenceType = dwSentenceType;

               dwSubSentence++;
               dwIndex = 0;

               // revert
               dwSentenceType = TYPICALSYLINFO_STATEMENT;;
            };

   
         fss.dwIndex = fss.dwSubSentenceLength = 0;
         fss.dwSubSentence = (DWORD)-1;
         fss.dwSentenceType = dwSentenceType;
         lFBMSUBSENT.Add (&fss);

         // ignore punctuation
         continue;
      }

      // else, add
      fss.dwSubSentenceLength = 0;
      fss.dwSubSentence = dwSubSentence;
      fss.dwIndex = dwIndex;
      fss.dwSentenceType = dwSentenceType;
      dwIndex++;
      lFBMSUBSENT.Add (&fss);
   } // i

   // go back over and calculate total length
   pfss = (PFBMSUBSENT) lFBMSUBSENT.Get(0);
   for (i = 0; i < lFBMSUBSENT.Num(); i++) {
      if (pfss[i].dwSubSentence == (DWORD)-1)
         continue;

      if (pfss[i].dwSubSentenceLength)
         continue;   // length already calculated

      DWORD dwCount  = 0;
      for (j = i; j < lFBMSUBSENT.Num(); j++)
         if (pfss[i].dwSubSentence == pfss[j].dwSubSentence)
            dwCount++;
      for (j = i; j < lFBMSUBSENT.Num(); j++)
         if (pfss[i].dwSubSentence == pfss[j].dwSubSentence)
            pfss[j].dwSubSentenceLength = dwCount;
   }

   PSENTENCESYLLABLE pss;
   PROSODYTREND ProsTrend;
   CMem memTYPICALSYLINFO;
   PTYPICALSYLINFO paTSI = NULL;
   DWORD dwWordStart = 0;
   DWORD dwSyllables = 0;
   CListFixed lSSCWINFO;
   lSSCWINFO.Init (sizeof(SSCWINFO));
   CListFixed lSSCWWORDSTART;
   lSSCWWORDSTART.Init (sizeof(SSCWWORDSTART));
   SSCWWORDSTART sswWordStart;
   for (i = 0; i < pThis->m_dwNum; i++) {
      fp fMicroSilence, fMicroSilenceSum;

      // if its a new word then shift NGram and add
      if (pThis->IsStartOfWord(i)) {
         memset (&sswWordStart, 0, sizeof(sswWordStart));

         dwWordStart = i;
         memmove (abNGramPOS, abNGramPOS+1, sizeof(abNGramPOS)-sizeof(BYTE));

         // find the next POS
         for ( ; (dwNextPOSSyl < pThis->m_dwNum) && !pThis->IsStartOfWord(dwNextPOSSyl); dwNextPOSSyl++);

         // get the prosody info
         dwSyllables = dwNextPOSSyl - i;

         size_t dwNeed = sizeof(TYPICALSYLINFO) * dwSyllables;
         if (!memTYPICALSYLINFO.Required (dwNeed))
            paTSI = NULL;
         else
            paTSI = (PTYPICALSYLINFO) memTYPICALSYLINFO.p;
         if (paTSI) {
            DWORD dwStressBitsMulti = 0;
            DWORD dwScale = 1;
            for (j = 0; j < dwSyllables; j++, dwScale *= pLexTTS->Stresses())
               dwStressBitsMulti += (DWORD)((pThis->m_pabPOSStress[j+1] >> 4) & 0x0f) * dwScale;
               // if (pThis->m_pabPOS[j+i] & 0x80)
               //   dwStressBits |= (1 << j);

            PCWSTR pszWord = NULL;
            WCHAR szWord[256];
            if ((pThis->m_paSyl[i].wWord != (WORD)-1) && pLexTTS) {
               if (m_pLexInProsody->WordGet (pThis->m_paSyl[i].wWord, szWord, sizeof(szWord), NULL))
                  pszWord = szWord;
            }

            wcscpy (sswWordStart.szWord, pszWord ? pszWord : L"");
            sswWordStart.dwPOS = pThis->m_pabPOSStress[i] & 0x0f;
            sswWordStart.dwStressBitsMulti = dwStressBitsMulti;
            if (!WordSylGetTYPICALSYLINFO (pLexTTS, pszWord, pThis->m_pabPOSStress[i] & 0x0f, dwSyllables, dwStressBitsMulti, ppCTTSProsody, dwNum, paTSI))
               paTSI = NULL;
         }

         if (dwNextPOSSyl < pThis->m_dwNum) {
            abNGramPOS[3] = pThis->m_pabPOSStress[dwNextPOSSyl] & 0x0f;
            dwNextPOSSyl++;
         }
         else
            abNGramPOS[3] = POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION);   // beyond the end

         // add this
         sswWordStart.dwSyllable = i;
         lSSCWWORDSTART.Add (&sswWordStart);

      } // if new word

      // do a weighted sum of all the surrounding bits
      memset (&Emph, 0, sizeof(Emph));
      fMicroSilence = fMicroSilenceSum = 0;


      // blend the elements in the stack
      for (j = 0; j < dwNumSent; j++) {
         fWeight = 1.0; // equal blend

         // else, add, weighting the scores of the blend
         int iOffsetTemp = i;
         PCSentenceSyllable pSent = papSS[j];

         pss = pSent->m_paSyl + iOffsetTemp;

         memset (&ProsTrend, 0, sizeof(ProsTrend));

#ifdef USEPROSODYTREND // don't call ProsodyTrend() because prosody incorporated earlier so better comparisons
         // determine the prosody trend
         ProsodyTrend (
            pSent->m_pabPOS[iOffsetTemp], pSent->m_pabRuleDepth[iOffsetTemp],
            pThis->m_pabPOS[i], pThis->m_pabRuleDepth[i],
            &ProsTrend,
            ppCTTSProsody, dwNum);
#endif

         DWORD dwPitchPoint;
         for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
            Emph.Emph.afPitchF0Extra[dwPitchPoint] +=
               ( (fp)pss->acPitchF0Extra[dwPitchPoint] / 100.0 +
                  (fp)ProsTrend.aiPitchF0Extra[dwPitchPoint] / (fp)100.0 ) * fWeight;
         Emph.Emph.fPitchF0Fuji +=
            ( log((fp)pss->bPitchF0Fuji / 100.0) + (fp)ProsTrend.iPitchF0Fuji / (fp)100.0 * log((fp)2) ) * fWeight;
         Emph.Emph.fPitchF0FujiDelta +=
               ( (fp)pss->cPitchF0FujiDelta / 100.0 + (fp)ProsTrend.iPitchF0FujiDelta / 100.0) * fWeight;
         //Emph.Emph.fPitchSweep +=
         //   ( (fp)pss->cPitchSweep / 100.0 + (fp)ProsTrend.iPitchSweep / 100.0 ) * fWeight;
         //Emph.Emph.fPitchBulge +=
         //   ( (fp)pss->cPitchBulge / 100.0 + (fp)ProsTrend.iPitchBulge / 100.0 ) * fWeight;
         Emph.Emph.fVolume +=
            ( log((fp)pss->bVol / 100.0) + (fp)ProsTrend.iVol / (fp)50.0 * log((fp)2) ) * fWeight;
         Emph.Emph.fAccent +=
            ( (fp)pss->cAccent / 100.0 + (fp)ProsTrend.iAccent / 100.0 ) * fWeight;

         Emph.Emph.fDurPhone +=
            ( log((fp)pss->bDurPhone / 100.0) + (fp)ProsTrend.iDurPhone / (fp)50.0 * log((fp)2) ) * fWeight;
         Emph.Emph.fDurSyl +=
            ( log((fp)pss->bDurSyl / 100.0) + (fp)ProsTrend.iDurSyl / (fp)50.0 * log((fp)2) ) * fWeight;
         Emph.Emph.fDurSkew +=
            ( (fp)pss->cDurSkew / 100.0 + (fp)ProsTrend.iDurSkew / 100.0 ) * fWeight;
         Emph.fMultiMisc += fWeight;

         // BUGFIX - look at the POS for only the first syllable to see if should include
         BYTE bSylIndex = pSent->m_pabSylIndex[iOffsetTemp];
            // NOTE: Ignoring blending of bRuleDepth
         if (!(bSylIndex & 0x7)) {
            // this is the start of a word
            fMicroSilenceSum += fWeight;
            fMicroSilence += fWeight * (fp)(pss->bPauseProb & 0x0f) / 15.0;
         }
      } // j, over templates for the word

      // if there's no weight then make a neutral case
      if (Emph.fMultiMisc) {
         fp fScale = 1.0 / Emph.fMultiMisc;

         DWORD dwPitchPoint;
         for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
            Emph.Emph.afPitchF0Extra[dwPitchPoint] *= fScale;
         Emph.Emph.fPitchF0Fuji = exp(Emph.Emph.fPitchF0Fuji * fScale);
         Emph.Emph.fPitchF0FujiDelta *= fScale;
         // Emph.Emph.fPitchSweep *= fScale;
         // Emph.Emph.fPitchBulge *= fScale;
         Emph.Emph.fVolume = exp(Emph.Emph.fVolume * fScale);
         Emph.Emph.fAccent *= fScale;
         Emph.Emph.fDurPhone = exp(Emph.Emph.fDurPhone * fScale);
         Emph.Emph.fDurSyl = exp(Emph.Emph.fDurSyl * fScale);
         Emph.Emph.fDurSkew *= fScale;

         if (fMicroSilenceSum)
            fMicroSilence = fMicroSilence / fMicroSilenceSum;
         else
            fMicroSilence = 0.0;
         //if (fMicroSilence > fMicroSilenceSum * fMicroPauseThreshhold)
         //   fMicroSilence = TRUE;
         //else
         //   fMicroSilence = FALSE;
      }
      else {
         memset (Emph.Emph.afPitchF0Extra, 0, sizeof(Emph.Emph.afPitchF0Extra));
         // Emph.Emph.fPitchSweep = 0;
         // Emph.Emph.fPitchBulge = 0;
         Emph.Emph.fDurSkew = 0;
         Emph.Emph.fPitchF0FujiDelta = 0;
         Emph.Emph.fPitchF0Fuji = Emph.Emph.fVolume = Emph.Emph.fDurPhone = Emph.Emph.fDurSyl = 1;
         Emph.Emph.fAccent = 0;
         fMicroSilence = 0.0;   // no microsilnce
      }

#ifdef NOMODS_DISABLERESIDUAL  //  zero out to test typical info
      Emph.Emph.fPitchSweep = 0;
      Emph.Emph.fPitchBulge = 0;
      Emph.Emph.fDurSkew = 0;
      Emph.Emph.fPitch = Emph.Emph.fVolume = Emph.Emph.fDurPhone = Emph.Emph.fDurSyl = 1;
#endif

#ifndef NOMODS_DISABLETYPICALSYLINFO
      if (pfss[i].dwSubSentence != (DWORD)-1) {
         TYPICALSYLINFO TSI;
         TypicalSylInfoGet (pfss[i].dwSentenceType, pfss[i].dwIndex, pfss[i].dwSubSentenceLength, ppCTTSProsody, dwNum, &TSI);

         if (TSI.fCount) {
            DWORD dwPitchPoint;
            for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
               Emph.Emph.afPitchF0Extra[dwPitchPoint] += TSI.afPitchF0ExtraSum[dwPitchPoint] / TSI.fCount / log(2.0);
               // BUGFIX - Divide by log(2.0) to keep right units
            Emph.Emph.fPitchF0Fuji *= exp(TSI.fPitchF0FujiSum / TSI.fCount);
            Emph.Emph.fPitchF0FujiDelta += TSI.fPitchF0FujiDeltaSum / TSI.fCount / log(2.0);
               // BUGFIX - Divide by log(2.0) to keep right units
            Emph.Emph.fVolume *= exp(TSI.fVolumeSum / TSI.fCount);
            Emph.Emph.fDurPhone *= exp(TSI.fDurPhoneSum / TSI.fCount);
            Emph.Emph.fDurSyl *= exp(TSI.fDurSylSum / TSI.fCount);
            Emph.Emph.fDurSkew += TSI.fDurSkewSum / TSI.fCount / log(2.0);
               // BUGFIX - Divide by log(2) to keep right units
            // Emph.Emph.fPitchBulge += TSI.fPitchBulgeSum / TSI.fCount;
            // Emph.Emph.fPitchSweep += TSI.fPitchSweepSum / TSI.fCount;
         }
      } // typical info
#endif // DISABLETYPICALSYLINFO

#ifndef NOMODS_DISABLEWORDSYL
      if ((pfss[i].dwSubSentence != (DWORD)-1) && (i >= dwWordStart) && (i < dwWordStart + dwSyllables)) {
         // already have the syllable info in paTSI
         PTYPICALSYLINFO pTSI = paTSI + (i - dwWordStart);

         // know that pTSI->fCount == 1
         DWORD dwPitchPoint;
         for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
            Emph.Emph.afPitchF0Extra[dwPitchPoint] += pTSI->afPitchF0ExtraSum[dwPitchPoint] / log(2.0);
            // BUGFIX - divide by log(2) to keep right units
         Emph.Emph.fPitchF0FujiDelta += pTSI->fPitchF0FujiDeltaSum / log(2.0);
            // BUGFIX - divide by log(2) to keep right units
         Emph.Emph.fPitchF0Fuji *= exp(pTSI->fPitchF0FujiSum);
         Emph.Emph.fVolume *= exp(pTSI->fVolumeSum);
         Emph.Emph.fAccent += pTSI->fAccentSum;
         Emph.Emph.fDurPhone *= exp(pTSI->fDurPhoneSum);
         Emph.Emph.fDurSyl *= exp(pTSI->fDurSylSum);
         Emph.Emph.fDurSkew += pTSI->fDurSkewSum / log(2.0);
            // BUGFIX - Divide by log(2) to keep right units
         // Emph.Emph.fPitchBulge += pTSI->fPitchBulgeSum;
         // Emph.Emph.fPitchSweep += pTSI->fPitchSweepSum;
      }
#endif

      // include prosody ngram
#ifndef NOMODS_DISABLEPROSODYNGRAM
      if (pfss[i].dwSubSentence != (DWORD)-1) {
         TYPICALSYLINFO TSI, TSILast;
         ProsodyNGramInfoGet (pThis->m_pabPOS, pThis->m_dwNum, i, ppCTTSProsody, dwNum, &TSI);
         BOOL fUseRelative = (i && (pfss[i-1].dwSubSentence != (DWORD)-1));
         if (fUseRelative)
            ProsodyNGramInfoGet (pThis->m_pabPOS, pThis->m_dwNum, i - 1, ppCTTSProsody, dwNum, &TSILast);

         if (TSI.fCount) {
            Emph.Emph.fPitch *= exp(TSI.fPitchSum / TSI.fCount);
            Emph.Emph.fVolume *= exp(TSI.fVolumeSum / TSI.fCount);
            Emph.Emph.fDurPhone *= exp(TSI.fDurPhoneSum / TSI.fCount);
            Emph.Emph.fDurSyl *= exp(TSI.fDurSylSum / TSI.fCount);
            Emph.Emph.fDurSkew += TSI.fDurSkewSum / TSI.fCount;
            Emph.Emph.fPitchBulge += TSI.fPitchBulgeSum / TSI.fCount;
            Emph.Emph.fPitchSweep += TSI.fPitchSweepSum / TSI.fCount;

            // relative
            if (fUseRelative && TSI.fCount && TSILast.fCount) {
               fWeight = fWeightCur;
               pafRelative[i] += ((TSI.fPitchSum / TSI.fCount) - (TSILast.fPitchSum / TSILast.fCount)) * fWeight;
               pafRelativeSum[i] += fWeight;
            }
         }
      } // prosody NGram
#endif

      // add this
      SSCWINFO swi;
      memset (&swi, 0, sizeof(swi));
      swi.Emph = Emph;
      swi.fMicroSilence = fMicroSilence;
      _ASSERTE (sizeof(swi.abNGramPOS) == sizeof(abNGramPOS));
      memcpy (swi.abNGramPOS, abNGramPOS, sizeof(abNGramPOS));
      lSSCWINFO.Add (&swi);
   } // i, over this num

   PSSCWINFO pse = (PSSCWINFO)lSSCWINFO.Get(0);
#ifndef NOMODS_DISABLEPROSODYWORDEXAMPLES
   // loop over all the words and find examples of the words in the
   // database, and incorporate those examples
   PSSCWWORDSTART paWordStart = (PSSCWWORDSTART) lSSCWWORDSTART.Get(0);
   SENTSYLEMPH EmphLeft, EmphThis, EmphRight;
   PSENTSYLEMPH pEmph;
   CMem memResult;
   for (i = 0; i < lSSCWWORDSTART.Num(); i++) {
      // loop over left, this/center, and right words and average together the
      // SYLEMPH of each syllable
      int iOffset;
      DWORD dwSyllablesThis = 0;
      for (iOffset = -1; iOffset <= 1; iOffset++) {
         switch (iOffset) {
         case -1:
            pEmph = &EmphLeft;
            break;
         case 1:
            pEmph = &EmphRight;
            break;
         case 0:
         default:
            pEmph = &EmphThis;
            break;
         } // switch
         memset (pEmph, 0, sizeof(*pEmph));
         pEmph->fDurPhone = 1.0;
         pEmph->fDurSyl = 1.0;
         pEmph->fPitchF0Fuji = 1.0;
         pEmph->fVolume = 1.0;
         // don't need to set pEmph->fAccent = 0

         int iCur = (int)i + iOffset;
         if ((iCur < 0) || (iCur >= (int)lSSCWWORDSTART.Num()))
            continue;   // leave what have there

         // else, find the length
         DWORD dwStart = paWordStart[iCur].dwSyllable;
         DWORD dwEnd = (iCur+1 < (int)lSSCWWORDSTART.Num()) ? paWordStart[iCur+1].dwSyllable : lSSCWINFO.Num();
         DWORD dwPitchPoint;
         for (j = dwStart; j < dwEnd; j++) {
            for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
               pEmph->afPitchF0Extra[dwPitchPoint] += pse[j].Emph.Emph.afPitchF0Extra[dwPitchPoint];
            pEmph->fDurSkew += pse[j].Emph.Emph.fDurSkew;
            pEmph->fPitchF0FujiDelta += pse[j].Emph.Emph.fPitchF0FujiDelta;
            pEmph->fDurPhone *= pse[j].Emph.Emph.fDurPhone;
            pEmph->fDurSyl *= pse[j].Emph.Emph.fDurSyl;
            pEmph->fPitchF0Fuji *= pse[j].Emph.Emph.fPitchF0Fuji;
            pEmph->fVolume *= pse[j].Emph.Emph.fVolume;
            pEmph->fAccent += pse[j].Emph.Emph.fAccent;
         } // j

         // normalize
         DWORD dwNumSyl = dwEnd - dwStart;
         if (iOffset == 0)
            dwSyllablesThis = dwNumSyl;
         for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
            pEmph->afPitchF0Extra[dwPitchPoint] /= (fp)dwNumSyl;
         pEmph->fDurSkew /= (fp)dwNumSyl;
         pEmph->fPitchF0FujiDelta /= (fp)dwNumSyl;
         pEmph->fDurPhone = pow (pEmph->fDurPhone, 1.0 / (fp)dwNumSyl);
         pEmph->fDurSyl = pow (pEmph->fDurSyl, 1.0 / (fp)dwNumSyl);
         pEmph->fPitchF0Fuji = pow (pEmph->fPitchF0Fuji, 1.0 / (fp)dwNumSyl);
         pEmph->fVolume = pow (pEmph->fVolume, 1.0 / (fp)dwNumSyl);
         pEmph->fAccent /= (fp)dwNumSyl;
      } // iOffset

      _ASSERTE(dwSyllablesThis);

      // make sure enoug memory
      if (!memResult.Required (dwSyllablesThis * sizeof(SENTSYLEMPH)))
         continue;   // error
      pEmph = (PSENTSYLEMPH) memResult.p;

      // find the closest syllable
      if (!WordSylGetBestExample (pInfo, pLexTTS, 
         paWordStart[i].szWord[0] ? paWordStart[i].szWord : NULL,
         paWordStart[i].dwPOS, dwSyllablesThis, paWordStart[i].dwStressBitsMulti,
         ppCTTSProsody, dwNum,
         &EmphLeft, &EmphThis, &EmphRight, pEmph))
         continue;   // couldnt find

      // copy over
      for (j = 0; j < dwSyllablesThis; j++) {
         DWORD dwCur = paWordStart[i].dwSyllable + j;
         DWORD dwPitchPoint;
         for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
            pse[dwCur].Emph.Emph.afPitchF0Extra[dwPitchPoint] = pEmph[j].afPitchF0Extra[dwPitchPoint];
         pse[dwCur].Emph.Emph.fDurPhone = pEmph[j].fDurPhone;
         pse[dwCur].Emph.Emph.fDurSkew = pEmph[j].fDurSkew;
         pse[dwCur].Emph.Emph.fDurSyl = pEmph[j].fDurSyl;
         pse[dwCur].Emph.Emph.fVolume = pEmph[j].fVolume;
         pse[dwCur].Emph.Emph.fAccent = pEmph[j].fAccent;
         // pse[dwCur].Emph.Emph.fPitchF0Fuji - untouched
         // pse[dwCur].Emph.Emph.fPitchF0FujiDelta - untouched
      }

   } // i
#endif // NOMODS_DISABLEPROSODYWORDEXAMPLES

   // write all the syllable information in
   for (i = 0; i < pThis->m_dwNum; i++) {
      // write the values in
      pss = pThis->m_paSyl + i;

      Emph = pse[i].Emph;
      memcpy (abNGramPOS, pse[i].abNGramPOS, sizeof(abNGramPOS));
      fp fMicroSilence = pse[i].fMicroSilence;

      // store this away
      fp f;
      DWORD dwPitchPoint;
      for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++) {
         f = Emph.Emph.afPitchF0Extra[dwPitchPoint] * 100.0;
         f = max(f, -126);  // dont be too restrtive.. and shouldnt happen anyway
         f = min(f, 126);  // dont be too restrtive.. and shouldnt happen anyway
         pss->acPitchF0Extra[dwPitchPoint] = (char)(int)floor(f+0.5);
      } // dwPitchPoint

      f = Emph.Emph.fPitchF0Fuji * 100.0;
      f = max(f, 25);  // dont be too restrtive.. and shouldnt happen anyway
      f = min(f, 254);  // dont be too restrtive.. and shouldnt happen anyway
      pss->bPitchF0Fuji = (BYTE)(f+0.5);

      f = Emph.Emph.fPitchF0FujiDelta * 100.0;
      f = max(f, -126);  // dont be too restrtive.. and shouldnt happen anyway
      f = min(f, 126);  // dont be too restrtive.. and shouldnt happen anyway
      pss->cPitchF0FujiDelta = (char)(int)floor(f+0.5);

      //f = Emph.Emph.fPitchSweep * 100.0;
      //f = max(f, -126);  // dont be too restrtive.. and shouldnt happen anyway
      //f = min(f, 126);  // dont be too restrtive.. and shouldnt happen anyway
      //pss->cPitchSweep = (char)(int)floor(f+0.5);

      //f = Emph.Emph.fPitchBulge * 100.0;
      //f = max(f, -126);  // dont be too restrtive.. and shouldnt happen anyway
      //f = min(f, 126);  // dont be too restrtive.. and shouldnt happen anyway
      //pss->cPitchBulge = (char)(int)floor(f+0.5);

      f = Emph.Emph.fVolume * 100.0;
      f = max(f, 25);  // dont be too restrtive.. and shouldnt happen anyway
      f = min(f, 254);  // dont be too restrtive.. and shouldnt happen anyway
      pss->bVol = (BYTE)(f+0.5);

      f = Emph.Emph.fAccent * 100.0;
      f = max(f, -126);
      f = min(f, 126);
      pss->cAccent = (char)(int)floor(f+0.5);

      f = Emph.Emph.fDurPhone * 100.0;
      f = max(f, 25);  // dont be too restrtive.. and shouldnt happen anyway
      f = min(f, 254);  // dont be too restrtive.. and shouldnt happen anyway
      pss->bDurPhone = (BYTE)(f+0.5);

      f = Emph.Emph.fDurSyl * 100.0;
      f = max(f, 25);  // dont be too restrtive.. and shouldnt happen anyway
      f = min(f, 254);  // dont be too restrtive.. and shouldnt happen anyway
      pss->bDurSyl = (BYTE)(f+0.5);

      f = Emph.Emph.fDurSkew * 100.0;
      f = max(f, -126);  // dont be too restrtive.. and shouldnt happen anyway
      f = min(f, 126);  // dont be too restrtive.. and shouldnt happen anyway
      pss->cDurSkew = (char)(int)floor(f+0.5);

      // given the part of speech, determine the location in the N gram
      // average the N-gram probability in with the value that have stored
      fp fPauseNGram = PauseNGram (pLexTTS, abNGramPOS[0], abNGramPOS[1], abNGramPOS[2], abNGramPOS[3],
         ppCTTSProsody, dwNum);
      if (fPauseNGram >= 0)
         fMicroSilence = (fMicroSilence + fPauseNGram) / 2.0;


      // NOTE: bPauseProb used as microsilnce value... if 0 NEVER insert, if 15 always insert
      fMicroSilence = max(fMicroSilence, 0);
      fMicroSilence = min(fMicroSilence, 1);
      pss->bPauseProb = (BYTE)(fMicroSilence*15.0 + 0.5);   // 0..15 only

      // relative pitch that will override fuji
      if (i)
         pafRelative[i] = pafRelative[i-1] + Emph.Emph.fPitchF0FujiDelta; // so pitch achored from previous pitch
      else
         pafRelative[i] = 0;  // no relative pitch for first entry
      fPitchAverage += pafRelative[i]; // so cancalulate the average
   } // i

   // loop through all the pitch scale values and make sure average is 0
   fPitchAverage /= (fp)max(pThis->m_dwNum,1);
   fp f;
   fp fSentMax = -1000000, fSentMin = 1000000;
   for (i = 0; i < pThis->m_dwNum; i++) {
      pafRelative[i] -= fPitchAverage; // so average around speakingpitch
      fSentMax = max(fSentMax, pafRelative[i]);
      fSentMin = min(fSentMin, pafRelative[i]);
   }
   fp fSentRangeOrig = fSentMax - fSentMin;
   fp fFujiPhraseTrigger = fFujiPhraseRangeMax / 5.0;  // with this much of an increase, count as jumpup

   // find the sub-segment with the largest pitch range
   DWORD dwSubStart, dwSubEnd;
   fp fPhraseMin, fPhraseMax;
   fp fPhraseRangeFoundMax = 0;
   for (dwSubStart = 0; dwSubStart < pThis->m_dwNum; dwSubStart = dwSubEnd) {
      for (dwSubEnd = dwSubStart + 1; dwSubEnd < pThis->m_dwNum; dwSubEnd++)
         if (pafRelative[dwSubEnd] > pafRelative[dwSubEnd-1] + fFujiPhraseTrigger)
            break;

      // find the min and max for the phrase
      fPhraseMin = 1000000;
      fPhraseMax = -1000000;
      for (i = dwSubStart; i < dwSubEnd; i++) {
         fPhraseMin = min(fPhraseMin, pafRelative[i]);
         fPhraseMax = max(fPhraseMax, pafRelative[i]);
      } // i
      fPhraseRangeFoundMax = max(fPhraseRangeFoundMax, fPhraseMax - fPhraseMin);
   } // dwSubStart
   fp fSentRangeIdeal = (fFujiPhraseRangeMax + fPhraseRangeFoundMax * 1.5) / 2.0; // so pitch can go down gradually over sentence
   fSentRangeIdeal = max(fSentRangeIdeal, fPhraseRangeFoundMax);

   fFujiPhraseRangeMax = min(fFujiRangeMax, fFujiPhraseRangeMax);   // just to make sure
   fFujiPhraseRangeMin = min(fFujiPhraseRangeMax, fFujiPhraseRangeMin);
   fp fSentRange = fSentRangeIdeal;
   fSentRange = min(fSentRange, fFujiRangeMax);

   // find the sub-segments and determine their min/max
   for (dwSubStart = 0; dwSubStart < pThis->m_dwNum; dwSubStart = dwSubEnd) {
      for (dwSubEnd = dwSubStart + 1; dwSubEnd < pThis->m_dwNum; dwSubEnd++)
         if (pafRelative[dwSubEnd] > pafRelative[dwSubEnd-1] + fFujiPhraseTrigger)
            break;

      // find the min and max for the phrase
      fPhraseMin = 1000000;
      fPhraseMax = -1000000;
      for (i = dwSubStart; i < dwSubEnd; i++) {
         fPhraseMin = min(fPhraseMin, pafRelative[i]);
         fPhraseMax = max(fPhraseMax, pafRelative[i]);
      } // i

      // make sure phrase range isn't too large
      fp fPhraseRangeOrig = fPhraseMax - fPhraseMin;
      fp fPhraseRange = min(fPhraseRangeOrig, fFujiPhraseRangeMax);

      // if phrase range is a bit small then encourage it to be larger
      if (fPhraseRange < fFujiPhraseRangeMin) {
         fPhraseRange *= 1.5; // dont want to affect phrase range too much
         fPhraseRange = min(fPhraseRange, fFujiPhraseRangeMin);
      }

      // rescale and position this so that fPhraseMin is moved to 0, so
      // fPhraseMax will be fPhraseRange
      for (i = dwSubStart; i < dwSubEnd; i++) {
         pafRelative[i] -= fPhraseMin;
         if ((fabs(fPhraseRange - fPhraseRangeOrig) > CLOSE) && fPhraseRangeOrig)
            pafRelative[i] *= fPhraseRange / fPhraseRangeOrig;
      } // i

      // adjust this up/down depending on where it is in the sentence,
      // so that over the course of the sentence the phrase ends up
      // being lowest
      fp fAdjust;
      fp fRangeToPlayWith = fSentRange - fPhraseRange;
      DWORD dwSylToPlayWith = pThis->m_dwNum - (dwSubEnd - dwSubStart);

      fp fAlpha;
      if (dwSylToPlayWith)
         fAlpha = (fp)dwSubStart / (fp)dwSylToPlayWith;
      else
         fAlpha = 0.5;

      fAdjust = -fPhraseRange/2.0 - fRangeToPlayWith * fAlpha + fRangeToPlayWith/2.0;

      for (i = dwSubStart; i < dwSubEnd; i++)
         pafRelative[i] += fAdjust;
   } // dwSubStart
   
   // recobine into main pitch
   for (i = 0; i < pThis->m_dwNum; i++) {
      f = pow (2.0, pafRelative[i]);
      f *= 100.0; // sine 100 is typical value

      // store in fuji
      pss = pThis->m_paSyl + i;
      f = max(f,25);
      f = min(f,255);
      pss->bPitchF0Fuji = (BYTE) (f+0.5);
   } // i
}


/*************************************************************************************
CTTSProsody::EscMultiThreadedCallback - Standard call
*/
void CTTSProsody::EscMultiThreadedCallback (PVOID pParams, DWORD dwParamSize, DWORD dwThread)
{
   DWORD *padw = (DWORD*)pParams;
   DWORD dwStart = padw[0];
   DWORD dwEnd = padw[1];
   DWORD dwType = padw[2];
   // DWORD i; //, j;

   switch (dwType) {
   case 100: // Sentence match
      {
         PEMTSENTENCEMATCH pe = (PEMTSENTENCEMATCH) pParams;
         PCListFixed plPCSentenceMatch = pe->plPCSentenceMatch;
         PCSentenceSyllable pSS = pe->pSS;
         CTTSProsody **ppCTTSProsody = pe->ppCTTSProsody;
         DWORD dwNum = pe->dwNum;
         PCOMPARESYLINFO pInfo = pe->pInfo;
         int iTTSQuality = pe->iTTSQuality;
         WORD wPeriod = pe->wPeriod;
         DWORD dwMultiPass = pe->dwMultiPass;
         BOOL fWord = pe->fWord;
         PCMLexicon pLexTTS = pe->pLexTTS;

         DWORD dwBeam;

         // since using rand, and rand per thread, create a sizeed based on the time and dwStart
         srand (GetTickCount() + dwStart * 1234 + dwMultiPass * 4321);

         for (dwBeam = dwStart; dwBeam < dwEnd; dwBeam++) {
            PCSentenceMatch pSM = *((PCSentenceMatch*)plPCSentenceMatch->Get(dwBeam));

#ifdef TURNOFFRANDOM
            // to make reproducable must random seed based on beam
            srand (pSM->m_iStart + 1000 * pSM->m_iEnd + dwMultiPass * 4321);
#endif

            // compare
            pSM->m_fSuccess = pSM->FindAndScoreMatches (pLexTTS, pInfo, pSS, ppCTTSProsody, dwNum, iTTSQuality, fWord, wPeriod);

         } // dwBeam

      }
      return;

#ifdef TEMPLATEPROOSDYMODEL
   case 20: // prosody beam search
      _ASSERTE (FALSE);
      return;
#else
      not tested with changes for chinese stresses
   case 20: // prosody beam search
      {
         PEMTFINDBESTMATCH pe = (PEMTFINDBESTMATCH) pParams;

         PCListFixed plPerSyl = pe->plPerSyl;
         fp fAccuracy = pe->fAccuracy;
         DWORD dwRandomness = pe->dwRandomness;
         PCSentenceSyllable pThis = pe->pThis;
         CTTSProsody **ppCTTSProsody = pe->ppCTTSProsody;
         DWORD dwNum = pe->dwNum;
         PCListFixed plBEAMDIFFERENCE = pe->plBEAMDIFFERENCE;
         PCOMPARESYLINFO pInfo = pe->pInfo;
         DWORD dwMultiPass = pe->dwMultiPass;
         PCMLexicon pLexTTS = pe->pLexTTS; 


         DWORD dwBeam;
         PROSODYTREND ProsTrend;
         BEAMDIFFERENCE bd, bdBlank;
         memset (&bd, 0, sizeof(bd));
         memset (&bdBlank, 0, sizeof(bdBlank));

         // since using rand, and rand per thread, create a sizeed based on the time and dwStart
         srand (GetTickCount() + dwStart * 1234 + dwMultiPass * 4321);

         for (dwBeam = dwStart; dwBeam < dwEnd; dwBeam++) {

#ifdef TURNOFFRANDOM
            // to make reproducable must random seed based on beam
            srand (dwBeam + dwMultiPass * 4321);
#endif

            // do a beam search to find the best
            fp fScoreBeamSearch;
            PCListFixed plBeamSearch;
            plBeamSearch = BeamSearch (pInfo, dwThread, plPerSyl, &fScoreBeamSearch, fAccuracy, dwRandomness);
            // NOTE: Should ALWAYS have something, unless ran out of memory
            if (!plBeamSearch)
               continue;

            // convert the info to a sentnece syllable
            PCSentenceSyllable pSSBeam = BeamSearchHypToSentenceSyllable (plBeamSearch);
            if (!pSSBeam) {
               delete plBeamSearch;
               continue;
            }

            // apply some prosody fine-tuning to this so more accurate
            for (i = 0; i < pSSBeam->m_dwNum; i++) {
               ProsodyTrend (pLexTTS,
                  pSSBeam->m_pabPOSStress[i], pSSBeam->m_pabSylIndex[i], pSSBeam->m_pabRuleDepth[i],
                  pThis->m_pabPOSStress[i], pThis->m_pabSylIndex[i], pThis->m_pabRuleDepth[i],
                  &ProsTrend,
                  ppCTTSProsody, dwNum);

               // incorporate this prosdy trend in
               PSENTENCESYLLABLE pss = &pSSBeam->m_paSyl[i];
               fp f;
               int iSum;

               // pitch
               f = (fp)pss->bPitch * pow (2.0, (fp)ProsTrend.iPitch / 100.0);
               f = max(f, 0);
               f = min(f, 255);
               pss->bPitch = (BYTE) f;

               // pitch sweep
               iSum = (int)pss->cPitchSweep + ProsTrend.iPitchSweep;
               iSum = max(iSum, -127);
               iSum = min(iSum, 127);
               pss->cPitchSweep = (char)iSum;

               // pitch bulge
               iSum = (int)pss->cPitchBulge + ProsTrend.iPitchBulge;
               iSum = max(iSum, -127);
               iSum = min(iSum, 127);
               pss->cPitchBulge = (char)iSum;

               // volume
               f = (fp)pss->bVol * pow (2.0, (fp)ProsTrend.iVol / 50.0);
               f = max(f, 0);
               f = min(f, 255);
               pss->bVol = (BYTE) f;

               // dur phone
               f = (fp)pss->bDurPhone * pow (2.0, (fp)ProsTrend.iDurPhone / 50.0);
               f = max(f, 0);
               f = min(f, 255);
               pss->bDurPhone = (BYTE) f;

               // dur syl
               f = (fp)pss->bDurSyl * pow (2.0, (fp)ProsTrend.iDurSyl / 50.0);
               f = max(f, 0);
               f = min(f, 255);
               pss->bDurSyl = (BYTE) f;

               // pitch sweep
               iSum = (int)pss->cDurSkew + ProsTrend.iDurSkew;
               iSum = max(iSum, -127);
               iSum = min(iSum, 127);
               pss->cDurSkew = (char)iSum;

            } // i

            bd.pSS = pSSBeam;
            bd.fScore = fScoreBeamSearch;

            EnterCriticalSection (&m_csProsody);

            // BUGFIX - Need to write them into plBEAMDIFFERENCE in the order of
            // dwBeam so can be reproducible in debug
            while (plBEAMDIFFERENCE->Num() <=dwBeam)
               plBEAMDIFFERENCE->Add (&bdBlank);

            PBEAMDIFFERENCE pFill = (PBEAMDIFFERENCE) plBEAMDIFFERENCE->Get(dwBeam);
            *pFill = bd;
            // plBEAMDIFFERENCE->Add (&bd);

         #ifdef _DEBUG
            PBESTSENTSWEEP pBSS;
            // display the beam search
            WCHAR szTemp[256];
            swprintf (szTemp, L"\r\nBeamSearch score=%g: ", (double)fScoreBeamSearch);
            OutputDebugStringW (szTemp);
            pBSS = (PBESTSENTSWEEP) plBeamSearch->Get(0);
            for (i = 0; i < plBeamSearch->Num(); i++) {
               // if changed sentences then display new one
               if (!i || (pBSS[i-1].pSent != pBSS[i].pSent) ) {
                  swprintf (szTemp, L" [New sent = %x]", (int)(__int64)pBSS[i].pSent);
                  OutputDebugStringW (szTemp);
               }

               // show the number and the score
               swprintf (szTemp, L"%d (score=%g) ", (int)pBSS[i].iOffsetTest, (double)pBSS[i].fScoreSyl);
               OutputDebugStringW (szTemp);
            } // i
         #endif

            LeaveCriticalSection (&m_csProsody);

            delete plBeamSearch;
         } // dwBeam      
      }
      return;
#endif

   } // switch
}


/*************************************************************************************
CTTSProsody::BeamSearchMatches - This does a beam search using sentence matches.

inputs
   PCMLexicon     pLexTTS - Lexicon with phonemes
   PCOMPARESYLINFO pInfo - Score penalties to use.
   PCSentenceSyllable      pThis - Current sentence being tested. This does NOT
                           have extra punctuation added, NOR is it necessarily
                           in the same lexicon.
   PCMLexicon     pLex - Lexicon that pThis uses
   CTTSProsody    **ppCTTSProsody - This of prosody objects to use for the comparison,
                  including this one.
   DWORD          dwNum - Number of prosody objects to use. Must be at least one
   int               iTTSQuality - TTS quality. 0 for fast, 1 for normal, 2 for slow, 3 for best, 4 for multipass
   BOOL           fWord - if TRUE then making sure the word-based sentences are used.
                     If FALSE, then syllable-based sentences
   fp             fAccuracy - How accurate to make the calculations. 1.0 = 10K units, 2.0 = 100K units, etc.
   DWORD          dwRandomness - Use 1 for normal amount of random. 2 for very random prosody, used
                     with multipass TTS to maximize acoustic scores
   DWORD                dwMultiPass - Pass number when generating several different versions of the
                        same sentence, and selecting the best one for TTS. Used to make the
                        random work better.

returns
   BOOL - TRUE if success
*/

static int _cdecl PCTTSProsodyMatchHypSort (const void *elem1, const void *elem2)
{
   PCTTSProsodyMatchHyp *pdw1, *pdw2;
   pdw1 = (PCTTSProsodyMatchHyp*) elem1;
   pdw2 = (PCTTSProsodyMatchHyp*) elem2;

   return pdw1[0]->Compare (pdw2[0], TRUE);
}


static int _cdecl PCTTSProsodyMatchHypSortScore (const void *elem1, const void *elem2)
{
   PCTTSProsodyMatchHyp *pdw1, *pdw2;
   pdw1 = (PCTTSProsodyMatchHyp*) elem1;
   pdw2 = (PCTTSProsodyMatchHyp*) elem2;

   if (pdw1[0]->m_fScore > pdw2[0]->m_fScore)
      return 1;
   else if (pdw1[0]->m_fScore < pdw2[0]->m_fScore)
      return -1;
   else
      return 0;
}

BOOL CTTSProsody::BeamSearchMatches (PCMLexicon pLexTTS, PCOMPARESYLINFO pInfo, PCSentenceSyllable pThis, PCMLexicon pLex,
                                 CTTSProsody **ppCTTSProsody, DWORD dwNum, int iTTSQuality, BOOL fWord, fp fAccuracy, DWORD dwRandomness,
                                 DWORD dwMultiPass)
{
   BOOL fRet = FALSE;
   // synthesize matches
   CListFixed alPingPong[2];
   CListFixed lResynth, lJoin;
   if (!GenerateMatches (pLexTTS, pInfo, pThis, iTTSQuality, fWord, ppCTTSProsody, dwNum, dwMultiPass, &lResynth, &lJoin))
      goto done;  // error

   // min/max matches
   WORD wPeriod = (WORD) m_pLexInProsody->WordFind (L".");
   DWORD dwMaxJoin = MaxJoinDistance (iTTSQuality);
   DWORD dwMaxMatch;
   MaxMinMatches (iTTSQuality, &dwMaxMatch, NULL);

   // determine the maximum number of hypothesis
   DWORD dwMaxHyp = dwMaxMatch * dwMaxMatch;
   if (iTTSQuality >= 2)
      dwMaxHyp *= max(1, dwMaxMatch / 4); // BUGFIX - so not too many

   // set up a ping-poing
   PCTTSProsodyMatchHyp *ppPMH;
   PCTTSProsodyMatchHyp pPMH;
   alPingPong[0].Init (sizeof(PCTTSProsodyMatchHyp));
   alPingPong[1].Init (sizeof(PCTTSProsodyMatchHyp));
   pPMH = new CTTSProsodyMatchHyp;
   if (!pPMH)
      goto done;
   pThis->CloneTo (&pPMH->m_SS); // starting sentence
   alPingPong[0].Add (&pPMH); // add to list

   // how much to randomize
   fp fRandomAmount = pInfo->fRandomize * (fp) (1+dwRandomness) / 2.0;

   // loop over all matches
   DWORD i, j;
   PCSentenceMatch *ppSM = (PCSentenceMatch*) lResynth.Get(0);
   DWORD dwSynth;
   DWORD dwPing = 0, dwPong = 1;
   for (dwSynth = 0; dwSynth < lResynth.Num(); dwSynth++, dwPing = !dwPing, dwPong = !dwPong) {
      // clear out pong
      ppPMH = (PCTTSProsodyMatchHyp*)alPingPong[dwPong].Get(0);
      for (i = 0; i < alPingPong[dwPong].Num(); i++)
         delete ppPMH[i];
      alPingPong[dwPong].Clear();

      // advance everything in ping
      ppPMH = (PCTTSProsodyMatchHyp*)alPingPong[dwPing].Get(0);
      for (i = 0; i < alPingPong[dwPing].Num(); i++)
         ppPMH[i]->Expand (pInfo, dwSynth, &lResynth, wPeriod, &alPingPong[dwPong]);

      // bring all the joins up to date
      ppPMH = (PCTTSProsodyMatchHyp*)alPingPong[dwPong].Get(0);
      for (i = 0; i < alPingPong[dwPong].Num(); i++)
         ppPMH[i]->IncludeJoinCosts (pInfo, dwSynth+1, &lResynth, &lJoin, dwMaxJoin, fWord, wPeriod);

      // sort this so exact matches can be eliminated
      qsort (ppPMH, alPingPong[dwPong].Num(), sizeof(PCTTSProsodyMatchHyp), PCTTSProsodyMatchHypSort);

      // eliminate exact matches
      for (i = alPingPong[dwPong].Num()-1; i && (i < alPingPong[dwPong].Num()); i--) {   // intitionally starting at 1
         // if they're different then continue
         if (ppPMH[i-1]->Compare (ppPMH[i], FALSE))
            continue;

         // else, the same except for score, so delete the second one
         _ASSERT (ppPMH[i-1]->m_fScore <= ppPMH[i]->m_fScore);
         delete ppPMH[i];
         alPingPong[dwPong].Remove (i);
         // i--;
         ppPMH = (PCTTSProsodyMatchHyp*)alPingPong[dwPong].Get(0);
      } // i

      // sort so best scores at top
      qsort (ppPMH, alPingPong[dwPong].Num(), sizeof(PCTTSProsodyMatchHyp), PCTTSProsodyMatchHypSortScore);

      // eliminate low scores
      if (alPingPong[dwPong].Num() > dwMaxHyp) {
         for (i = dwMaxHyp; i < alPingPong[dwPong].Num(); i++)
            delete ppPMH[i];
         alPingPong[dwPong].Truncate (dwMaxHyp);
         ppPMH = (PCTTSProsodyMatchHyp*)alPingPong[dwPong].Get(0);
      } // if too many hyp

      // randomize what's left
      if ((dwSynth < lResynth.Num()) && fRandomAmount) {
         DWORD dwRandomizeTimes = (DWORD)(ppSM[dwSynth]->m_iEndUse - ppSM[dwSynth]->m_iStartUse);
         for (i = 0; i < alPingPong[dwPong].Num(); i++)
            for (j = 0; j < dwRandomizeTimes; j++)
               ppPMH[i]->m_fScore += randf (0, fRandomAmount);
      }
   } // dwSynth

   // when gets here, dwPing will have the best match at the top of the list.
   // copy that back into the setnce
   ppPMH = (PCTTSProsodyMatchHyp*)alPingPong[dwPing].Get(0);
   _ASSERTE (alPingPong[dwPing].Num() == 1);
   ppPMH[0]->m_SS.CloneTo (pThis);

   // if got here then success
   fRet = TRUE;

done:
   ppSM = (PCSentenceMatch*) lResynth.Get(0);
   for (i = 0; i < lResynth.Num(); i++)
      delete ppSM[i];
   ppSM = (PCSentenceMatch*) lJoin.Get(0);
   for (i = 0; i < lJoin.Num(); i++)
      delete ppSM[i];

   // delete from the pingpong
   for (j = 0; j < 2; j++) {
      ppPMH = (PCTTSProsodyMatchHyp*)alPingPong[j].Get(0);
      for (i = 0; i < alPingPong[j].Num(); i++)
         delete ppPMH[i];
   }

   return fRet;
}

/*************************************************************************************
CTTSProsody::FindBestMatch - This takes a sentence split into syllables, which
will be spoken by TTS, and compares it against a list of sentences in the database.
It then finds the best matching sentences and uses them as templates to fill in
this sentences bpitch, pvolume, bduration, and microsilence.

inputs
   fp             fFujiRangeMax - Maximum range for fuji prosody that will allow
   fp             fFujiPhraseRangeMax - Maximum range for a phrase within the sentnece
   PCMLexicon     pLexTTS - TTS with phonemes in it
   PCOMPARESYLINFO pInfo - Score penalties to use.
   PCSentenceSyllable      pThis - Current sentence being tested. This does NOT
                           have extra punctuation added, NOR is it necessarily
                           in the same lexicon.
   PCMLexicon     pLex - Lexicon that pThis uses
   CTTSProsody    **ppCTTSProsody - This of prosody objects to use for the comparison,
                  including this one.
   DWORD          dwNum - Number of prosody objects to use. Must be at least one
   int               iTTSQuality - TTS quality. 0 for fast, 1 for normal, 2 for slow, 3 for best, 4 for multipass
   fp             fAccuracy - How accurate to make the calculations. 1.0 = 10K units, 2.0 = 100K units, etc.
   DWORD          dwRandomness - Use 1 for normal amount of random. 2 for very random prosody, used
                     with multipass TTS to maximize acoustic scores
   DWORD                dwMultiPass - Pass number when generating several different versions of the
                        same sentence, and selecting the best one for TTS. Used to make the
                        random work better.
returns
   none... although it changes the values. NOTE: bPauseProb will be filled with 0..15,
      where 0 is no microsilnece, 15 is always microsilence
*/
#define  MATCHSLOTS           10        // keep the top 5 matches for each slot
                                          // BUGFIX - Upped from 5 to 10
#define  MATCHPOWER           10.0      // how much to raise power to for weighting, higher numbers cause only top-ranked to be used
   // BUGFIX - Used to be 2.0. Upped to 10.0 so much steeper cutoff
#define  MATCHUNWEIGHTSCORE   5.0      // the higher this number, the more the original score is unweighted,
                                       // and the more the smoothing effect (for MATCHPOWER) is used

#define RANKTENTSIZE       2        // size of the rank tent-filter, in syllables
      // BUGFIX - RANKTENTSIZE used to be 4. lowered down

static int _cdecl BEAMDIFFERENCESort (const void *elem1, const void *elem2)
{
   BEAMDIFFERENCE *pdw1, *pdw2;
   pdw1 = (BEAMDIFFERENCE*) elem1;
   pdw2 = (BEAMDIFFERENCE*) elem2;

   if (pdw1->fCompareRegion > pdw2->fCompareRegion)
      return 1;
   else if (pdw1->fCompareRegion < pdw2->fCompareRegion)
      return -1;
   else
      return 0;
   // return (int)pdw1->dwCompareRegion - (int)pdw2->dwCompareRegion;
}

static int _cdecl BESTSENTSWEEPSort (const void *elem1, const void *elem2)
{
   BESTSENTSWEEP *pdw1, *pdw2;
   pdw1 = (BESTSENTSWEEP*) elem1;
   pdw2 = (BESTSENTSWEEP*) elem2;

   return (int)pdw1->dwCompareRegion - (int)pdw2->dwCompareRegion;
}

static int _cdecl BESTSENTSWEEPSortRank (const void *elem1, const void *elem2)
{
   BESTSENTSWEEP *pdw1, *pdw2;
   pdw1 = (BESTSENTSWEEP*) elem1;
   pdw2 = (BESTSENTSWEEP*) elem2;

   if (pdw1->fRank > pdw2->fRank)
      return 1;
   else if (pdw1->fRank < pdw2->fRank)
      return -1;
   else
      return 0;
}

void CTTSProsody::FindBestMatch (fp fFujiRangeMax, fp fFujiPhraseRangeMax, fp fFujiPhraseRangeMin,
                                 PCMLexicon pLexTTS, PCOMPARESYLINFO pInfo, PCSentenceSyllable pThis, PCMLexicon pLex,
                                 CTTSProsody **ppCTTSProsody, DWORD dwNum, int iTTSQuality, fp fAccuracy, DWORD dwRandomness,
                                 DWORD dwMultiPass)
{
#ifdef TEMPLATEPROOSDYMODEL
   // new way of doing prosody
   // search for best combination of matches
   CSentenceSyllable SSTemp;
   pThis->CloneTo (&SSTemp);

   BOOL fWord = FALSE;

   PCSentenceSyllable pSSWord = NULL;
#ifndef NOMODS_DISABLEPROSODYWORDRESIDUAL
   fWord = TRUE;
   pSSWord = pThis->ToWords();
   if (pSSWord) {
      pSSWord->CloneTo (&SSTemp);
      delete pSSWord;
   }
   else
      fWord = FALSE;
#endif

#ifdef _DEBUG
   // iTTSQuality = 3;  // to test - Just for testing purposes
#endif
   BeamSearchMatches (pLexTTS, pInfo, &SSTemp, pLex, ppCTTSProsody, dwNum, iTTSQuality, fWord, fAccuracy, dwRandomness, dwMultiPass);

   // convert back to syllable-based prosody
   if (fWord) {
      pSSWord = SSTemp.FromWords (pThis);
      if (pSSWord) {
         pSSWord->CloneTo (&SSTemp);
         delete pSSWord;
      }
   }

   // combine (only one sentence) and apply extra prosody models
   PCSentenceSyllable apSS[1];
   apSS[0] = &SSTemp;
   SentenceSyllablesCombineWithProsody (pInfo, fFujiRangeMax, fFujiPhraseRangeMax, fFujiPhraseRangeMin,
      pLexTTS, pThis, apSS, 1, pLex, ppCTTSProsody, dwNum);

   // done
   return;
#else


   // don't turn off random here since want to use for muliple passes
//#ifdef TURNOFFRANDOM
//   srand (0);
//#endif // 0

   // keep a list of lists
   CListFixed lPerSyl;
   lPerSyl.Init (sizeof(PCListFixed));

   // randomize all the prosodies
   DWORD i, j;
   for (i = 0; i < dwNum; i++)
      ppCTTSProsody[i]->Randomize();

   // accuracy
   DWORD dwMatchSlots = (DWORD)((fp)MATCHSLOTS * fAccuracy);

   // loop through all the syllables and find the best match amongst them
   PCListFixed pl;
   PCListFixed *ppl;
   PBESTSENTSWEEP pBSS;
   lPerSyl.Required (pThis->m_dwNum);
   int iCur;
   CListFixed lTemp;
   lTemp.Init (sizeof(BESTSENTSWEEP));
   for (iCur = -PUNCTEXTRA; iCur < (int)pThis->m_dwNum + PUNCTEXTRA; iCur++) {
      // if this is the first entry then create a new list, else
      // clone previous list and adjust offsets
      pl = new CListFixed;
      if (!pl)
         return;   // error
      lPerSyl.Add (&pl);
      if (iCur > -PUNCTEXTRA) {
         // had previous entry, so initialize this with the previous once so don't
         // bother recalculating score
         ppl = (PCListFixed*)lPerSyl.Get(0);
         pl->Init (sizeof(BESTSENTSWEEP), ppl[iCur+PUNCTEXTRA-1]->Get(0), ppl[iCur+PUNCTEXTRA -1]->Num());

         // wipe out old score syllable lists
         pBSS = (PBESTSENTSWEEP)ppl[iCur+PUNCTEXTRA-1]->Get(0);
         for (j = 0; j < ppl[iCur+PUNCTEXTRA -1]->Num(); j++, pBSS++)
            pBSS->plfScoreSyl = NULL;

         pBSS = (PBESTSENTSWEEP)pl->Get(0);
         for (j = 0; j < pl->Num(); j++) {
            // if offset would go beyond edge of sentence, or down to 0 matches,
            // then remove from the list
            pBSS[j].iOffsetTest++;
            if ((pBSS[j].dwSylMatch <= 1) || (pBSS[j].iOffsetTest >= (int)pBSS[j].pSent->m_dwNum + PUNCTEXTRA) || !pBSS[j].plfScoreSyl ) {
               // end of the line, so delete this
               if (pBSS[j].plfScoreSyl)
                  delete pBSS[j].plfScoreSyl;
               pl->Remove (j);
               j--;
               pBSS = (PBESTSENTSWEEP)pl->Get(0);
               continue;
            }
            pBSS[j].dwSylMatch--;  // reduce number of matches
         } // i
      }
      else
         pl->Init (sizeof(BESTSENTSWEEP));

      // loop through all the prosody modules and see what they have to add
      lTemp.Clear();
      for (j = 0; j < dwNum; j++)
         ppCTTSProsody[j]->CompareSweepSequence (pInfo, pThis, iCur, pLex, &lTemp, dwMatchSlots, pl);
            // NOTE: Always allow previous matches to continue on so they're not
            // unintentionally pushed out by something that's temporarily better

      // add everything in lTemp to pl
      pBSS = (PBESTSENTSWEEP) lTemp.Get(0);
      for (j = 0; j < lTemp.Num(); j++, pBSS++)
         pl->Add (pBSS);

      // look through all the slots and calculate the score for the specific syllable
      pBSS = (PBESTSENTSWEEP)pl->Get(0);
      for (j = 0; j < pl->Num(); j++, pBSS++) {
         // just in case, make sure that have list
         if (!pBSS->plfScoreSyl || !pBSS->plfScoreSyl->Num()) {
            if (pBSS->plfScoreSyl) {
               delete pBSS->plfScoreSyl;
               pBSS->plfScoreSyl = NULL;
            }
            pBSS->fScoreSyl = COMPARESINGLESYLLABLE_NOMATCH;   // since don't know
            continue;
         }

         // get first score on the list
         pBSS->fScoreSyl = *((fp*)pBSS->plfScoreSyl->Get(0));
#ifdef _DEBUG
         if (pBSS->fScoreSyl == COMPARESINGLESYLLABLE_NOMATCH)
            pBSS->fScoreSyl = COMPARESINGLESYLLABLE_NOMATCH;
#endif
         pBSS->plfScoreSyl->Remove (0);

         // clear the score list if it's empty, of if we're at the end of the checks
         if (!pBSS->plfScoreSyl->Num() || (iCur+1 >= (int)pThis->m_dwNum+PUNCTEXTRA) ) {
            delete pBSS->plfScoreSyl;
            pBSS->plfScoreSyl = NULL;
         }
      }
   } // i

   // if any of the lists are blank then add a dummy hypothesis that will have no pitch or inflection
   BESTSENTSWEEP bs;
   memset (&bs, 0, sizeof(bs));
   bs.iOffsetTest = 0;
   bs.fScoreAvg = 0.0; // BUGFIX - was 1, but not sure that should use 1 with new floating point values;
   bs.pSent = NULL;
   bs.fScoreSyl = COMPARESINGLESYLLABLE_NOMATCH;
   ppl = (PCListFixed*)lPerSyl.Get(0);
   for (i = 0; i < lPerSyl.Num(); i++) {
      pl = ppl[i];
      if (!pl->Num())
         pl->Add (&bs);
   } // i

   // PROSODYTREND ProsTrend;
   DWORD dwNumBeamSearch = (DWORD) ((fp)FINDBESTMATCH_BEAMSEARCHNUM * fAccuracy);
   dwNumBeamSearch = max(dwNumBeamSearch, FINDBESTMATCH_SEARCHGROUP);   // must have enough for a few searches
      // BUGFIX - Was FINDBESTMATCH_SEARCHGROUP*2, but changed to FINDBESTMATCH_SEARCHGROUP so can get ultra-fast on low quality voice
   // DWORD dwBeam;
   CListFixed lBEAMDIFFERENCE;
   lBEAMDIFFERENCE.Init (sizeof(BEAMDIFFERENCE));
   // BEAMDIFFERENCE bd;
   // memset (&bd, 0, sizeof(bd));

#ifdef _DEBUG
   DWORD dwStartTime = GetTickCount();
#endif

   // multithreaded
   EMTFINDBESTMATCH em;
   memset (&em, 0, sizeof(em));
   em.dwType = 20;
   em.fAccuracy = fAccuracy;
   em.dwRandomness = dwRandomness;
   em.dwNum = dwNum;
   em.plBEAMDIFFERENCE = &lBEAMDIFFERENCE;
   em.plPerSyl = &lPerSyl;
   em.ppCTTSProsody = ppCTTSProsody;
   em.pThis = pThis;
   em.pInfo = pInfo;
   em.dwMultiPass = dwMultiPass;
   em.pLexTTS = pLexTTS;

   ThreadLoop (0, dwNumBeamSearch, 1, &em, sizeof(em), NULL);

   // make sure no blank ones
   PBEAMDIFFERENCE pBD = (PBEAMDIFFERENCE) lBEAMDIFFERENCE.Get(0);
   BEAMDIFFERENCE bdBlank;
   memset (&bdBlank, 0, sizeof(bdBlank));
   for (i = lBEAMDIFFERENCE.Num()-1; i < lBEAMDIFFERENCE.Num(); i--)
      if (!memcmp (pBD + i, &bdBlank, sizeof(bdBlank))) {
         lBEAMDIFFERENCE.Remove (i);
         pBD = (PBEAMDIFFERENCE) lBEAMDIFFERENCE.Get(0); // just in case
      }

#if 0 // replaced by multithreaded
   for (dwBeam = 0; dwBeam < dwNumBeamSearch; dwBeam++) {
      // do a beam search to find the best
      int iScoreBeamSearch;
      PCListFixed plBeamSearch;
      plBeamSearch = BeamSearch (&lPerSyl, &iScoreBeamSearch, fAccuracy);
      // NOTE: Should ALWAYS have something, unless ran out of memory
      if (!plBeamSearch)
         goto done;

   #ifdef _DEBUG
      // display the beam search
      WCHAR szTemp[256];
      swprintf (szTemp, L"\r\nBeamSearch score=%d: ", (int)iScoreBeamSearch);
      OutputDebugStringW (szTemp);
      pBSS = (PBESTSENTSWEEP) plBeamSearch->Get(0);
      for (i = 0; i < plBeamSearch->Num(); i++) {
         // if changed sentences then display new one
         if (!i || (pBSS[i-1].pSent != pBSS[i].pSent) ) {
            swprintf (szTemp, L" [New sent = %x]", (int)(__int64)pBSS[i].pSent);
            OutputDebugStringW (szTemp);
         }

         // show the number and the score
         swprintf (szTemp, L"%d (score=%d) ", (int)pBSS[i].iOffsetTest, (int)pBSS[i].iScoreSyl);
         OutputDebugStringW (szTemp);
      } // i
   #endif

      // convert the info to a sentnece syllable
      PCSentenceSyllable pSSBeam = BeamSearchHypToSentenceSyllable (plBeamSearch);
      delete plBeamSearch;
      if (!pSSBeam)
         goto done;

      // apply some prosody fine-tuning to this so more accurate
      for (i = 0; i < pSSBeam->m_dwNum; i++) {
         ProsodyTrend (
            pSSBeam->m_pabPOS[i], pSSBeam->m_pabRuleDepth[i],
            pThis->m_pabPOS[i], pThis->m_pabRuleDepth[i],
            &ProsTrend,
            ppCTTSProsody, dwNum);

         // incorporate this prosdy trend in
         PSENTENCESYLLABLE pss = &pSSBeam->m_paSyl[i];
         fp f;
         int iSum;

         // pitch
         f = (fp)pss->bPitch * pow (2.0, (fp)ProsTrend.iPitch / 100.0);
         f = max(f, 0);
         f = min(f, 255);
         pss->bPitch = (BYTE) f;

         // pitch sweep
         iSum = (int)pss->cPitchSweep + ProsTrend.iPitchSweep;
         iSum = max(iSum, -127);
         iSum = min(iSum, 127);
         pss->cPitchSweep = (char)iSum;

         // pitch bulge
         iSum = (int)pss->cPitchBulge + ProsTrend.iPitchBulge;
         iSum = max(iSum, -127);
         iSum = min(iSum, 127);
         pss->cPitchBulge = (char)iSum;

         // volume
         f = (fp)pss->bVol * pow (2.0, (fp)ProsTrend.iVol / 50.0);
         f = max(f, 0);
         f = min(f, 255);
         pss->bVol = (BYTE) f;

         // dur phone
         f = (fp)pss->bDurPhone * pow (2.0, (fp)ProsTrend.iDurPhone / 50.0);
         f = max(f, 0);
         f = min(f, 255);
         pss->bDurPhone = (BYTE) f;

         // dur syl
         f = (fp)pss->bDurSyl * pow (2.0, (fp)ProsTrend.iDurSyl / 50.0);
         f = max(f, 0);
         f = min(f, 255);
         pss->bDurSyl = (BYTE) f;

         NOTE: Not doing cDurSkew here
      } // i

      bd.pSS = pSSBeam;
      bd.iScore = iScoreBeamSearch;
      lBEAMDIFFERENCE.Add (&bd);
   } // dwBeam
#endif // 0, multithreaded

#ifdef _DEBUG
   WCHAR szTemp[64];
   swprintf (szTemp, L"\r\nTotal beamsearch time = %d", (int)(GetTickCount()-dwStartTime));
   OutputDebugStringW (szTemp);
#endif

   WORD wPeriod = (WORD) m_pLex->WordFind (L".");

   // group these together
#define MAXGROUPSIZE       20
   pBD = (PBEAMDIFFERENCE) lBEAMDIFFERENCE.Get(0);
   DWORD dwGroupSize = dwNumBeamSearch / FINDBESTMATCH_SEARCHGROUP;
   dwGroupSize = min(dwGroupSize, MAXGROUPSIZE);
   DWORD dwGroupStart;
   DWORD dwGroupStartBest = (DWORD)-1;
   fp fGroupScoreBest = 0;
   for (dwGroupStart = 0; dwGroupStart + dwGroupSize <= lBEAMDIFFERENCE.Num(); dwGroupStart += dwGroupSize) {
      // do a compare from dwGroupStart and on
      pBD[dwGroupStart].fCompareRegion = 0; // always 0 error with itself
      for (i = dwGroupStart+1; i < lBEAMDIFFERENCE.Num(); i++)
         pBD[i].fCompareRegion = pBD[dwGroupStart].pSS->CompareRegion (pInfo, 0, pBD[i].pSS, 0, wPeriod);

      // sort from group start and below, by the best compare
      qsort (pBD + dwGroupStart, lBEAMDIFFERENCE.Num() - dwGroupStart, sizeof(BEAMDIFFERENCE), BEAMDIFFERENCESort);

      // take the score of this group and store away
      fp fScoreTotal = 0;
      for (i = dwGroupStart; i < dwGroupStart + dwGroupSize; i++)
         fScoreTotal += pBD[i].fCompareRegion;

      // keep the best/closest match, so most similar sounding
      if ((dwGroupStartBest == (DWORD)-1) || (fScoreTotal < fGroupScoreBest)) {
         dwGroupStartBest = dwGroupStart;
         fGroupScoreBest = fScoreTotal;
      }
   } // dwGroupStart
   if (dwGroupStartBest == (DWORD)-1)
      goto done;  // shoudlnt happen

   // make a list of all the bits to combine
   PCSentenceSyllable papSS[MAXGROUPSIZE];
   for (i = 0; i < dwGroupSize; i++)
      papSS[i] = pBD[dwGroupStartBest + i].pSS;

   // combine and done with it
   SentenceSyllablesCombineWithProsody (pThis, papSS, dwGroupSize, pLex, ppCTTSProsody, dwNum);

   // deleat all the beams
   for (i = 0; i < lBEAMDIFFERENCE.Num(); i++)
      delete pBD[i].pSS;




#if 0 // no longer used because replaced by SentenceSyllablesCombineWithProsody()

   CMem memRelative;
   CListFixed lFBMSUBSENT;
   CListFixed lMATCHRANKFILTER;


   // clear all the bestsentsweep ranks to -1
   DWORD dwNumInStack;
   PBESTSENTSWEEP pStack;
   PBESTSENTSWEEP pBlendWith;
   for (i = 0; i < lPerSyl.Num(); i++) {
      dwNumInStack = ppl[i]->Num();
      pStack = (PBESTSENTSWEEP)ppl[i]->Get(0);
      for (j = 0; j < dwNumInStack; j++) {
         // blend with
         pBlendWith = pStack + j;
         pBlendWith->fRank = -1.0;
      } // j
   } // i

   // hack, remove starting/ending punct
   DWORD dwStart;
   for (dwStart = 0; dwStart < 2; dwStart++) {
      for (i = 0; i < PUNCTEXTRA; i++) {
         DWORD dwIndex = dwStart ? 0 : (lPerSyl.Num() - 1);
         delete ppl[dwIndex];
         lPerSyl.Remove (dwIndex);
         ppl = (PCListFixed*)lPerSyl.Get(0);
      } // i
   } // dwStart

   // loop through every item that appears in bestsentsweep and store the rankings away
   // sepearately so can filter. Filter them. And store filtered value back in fRank
   MATCHRANKFILTER mrf;
   PMATCHRANKFILTER pmrf;
   lMATCHRANKFILTER.Init (sizeof(MATCHRANKFILTER));
   DWORD k, m;
   for (i = 0; i < pThis->m_dwNum; i++) {
      dwNumInStack = ppl[i]->Num();
      pStack = (PBESTSENTSWEEP)ppl[i]->Get(0);
      for (j = 0; j < dwNumInStack; j++) {
         // blend with
         pBlendWith = pStack + j;

         if (pBlendWith->fRank >= 0)
            continue;   // already did this one

         // else, need to add it

         // set as lowest rank up until this point
         lMATCHRANKFILTER.Clear ();
         mrf.fRank = ppl[i]->Num(); // MATCHSLOTS;
         mrf.pBest = NULL;
         lMATCHRANKFILTER.Required (i+1);
         for (k = 0; k < i; k++)
            lMATCHRANKFILTER.Add (&mrf);

         // add this one
         mrf.fRank = j;
         mrf.pBest = pBlendWith;
         lMATCHRANKFILTER.Add (&mrf);

         // subsequent ones
         for (k = i+1; k < pThis->m_dwNum; k++) {
            DWORD dwNumInStack2 = ppl[k]->Num();
            PBESTSENTSWEEP pStack2 = (PBESTSENTSWEEP)ppl[k]->Get(0);
            for (m = 0; m < dwNumInStack2; m++) {
                     // BUGFIX - Was m < dwNumInStack, which is wrong, should be dwNumInStack2
               PBESTSENTSWEEP pBlendWith2 = pStack2 + m;
               if (pBlendWith2->pSent != pBlendWith->pSent)
                  continue;   // no match of original sentences
               if (pBlendWith2->iOffsetTest != pBlendWith->iOffsetTest + (int)(k - i))
                  continue;   // wrong offset

               // else, match
               break;
            } // m
            if (m >= dwNumInStack2) {  // BUGFIX - Changed dwNumInStack to dwNumInStack2
               // didn't find an occurance here
               mrf.fRank = ppl[i]->Num(); // MATCHSLOTS;
               mrf.pBest = NULL;
            }
            else {
               // found
               mrf.fRank = m;
               mrf.pBest = pStack2 + m;
            }
            lMATCHRANKFILTER.Add (&mrf);
         } // k

         // when get here, lMATCHRANKFILTER has been filled in, so need to do tent filter
         pmrf = (PMATCHRANKFILTER) lMATCHRANKFILTER.Get(0);
         fp fRankFirst = lMATCHRANKFILTER.Num() ? pmrf[0].fRank : (fp)ppl[i]->Num(); // MATCHSLOTS;
         fp fRankLast = lMATCHRANKFILTER.Num() ? pmrf[lMATCHRANKFILTER.Num()-1].fRank : (fp)ppl[i]->Num(); // MATCHSLOTS;
         for (k = 0; k < lMATCHRANKFILTER.Num(); k++) {
            if (!pmrf[k].pBest)
               continue;   // empty, so dont bother filling

            // sum for tent filter
            fp fSum = 0;
            DWORD dwCount = 0;
            int iTent;
            for (iTent = -RANKTENTSIZE; iTent <= RANKTENTSIZE; iTent++) {
               int iCur = (int)k + iTent;
               DWORD dwWeight = RANKTENTSIZE + 1 - (DWORD)abs(iTent);
               if (iCur < 0)
                  fSum += fRankFirst * (fp)dwWeight;
               else if (iCur >= (int)lMATCHRANKFILTER.Num())
                  fSum += fRankLast * (fp)dwWeight;
               else
                  fSum += pmrf[iCur].fRank * (fp)dwWeight;
               dwCount += dwWeight;
            } // iTent

            // store averaged rank
            if (dwCount)
               fSum /= (fp)dwCount;
            pmrf[k].pBest->fRank = fSum;
         } // k

      } // j, all in stack
   } // i

   // now that have a lot of templates for each word, blend them all together, weighting
   // by the dwscore... that way, if there are bad recordings, they will tend to be
   // counteracted by having lots of data

   // loop through and blend the results of the best ones
   SYLEMPH Emph;
   fp fWeight;
   fp fMicroSilence, fMicroSilenceSum;
   if (!memRelative.Required (pThis->m_dwNum * sizeof(fp) * 3))
      goto done;  // not likely to happen
   fp *pafRelative = (fp*)memRelative.p;
   fp *pafRelativeSum = pafRelative + pThis->m_dwNum;
   fp *pafFilter = pafRelativeSum + pThis->m_dwNum;
   memset (pafRelative, 0, pThis->m_dwNum * sizeof(fp) * 3);
   fp fPitchAverage = 0;

#if 0 // to test
#ifdef _DEBUG
   FILE *file = fopen ("c:\\test.txt", "wt");
#else
   FILE *file = fopen ("c:\\testr.txt", "wt");
#endif
#endif // 0

   // look ahead and determine the word parts of speech
   BYTE abNGramPOS[4];
   DWORD dwNextPOSSyl = 0;
   for (j = 0; j < 4; j++) {
      if (j < 3) {
         abNGramPOS[j] = POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION);
         continue;
      }

      for ( ; (dwNextPOSSyl < pThis->m_dwNum) && !pThis->IsStartOfWord(dwNextPOSSyl); dwNextPOSSyl++);

      if (dwNextPOSSyl < pThis->m_dwNum) {
         abNGramPOS[j] = pThis->m_pabPOS[dwNextPOSSyl] & 0x0f;
         dwNextPOSSyl++;
      }
      else
         abNGramPOS[j] = POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION);   // beyond the end
   }

   // figure out which subsentence a word is in, and what the index is, as well as how long the subsentence is
   FBMSUBSENT fss;
   lFBMSUBSENT.Init (sizeof(FBMSUBSENT));
   WORD wPeriod = (WORD) m_pLex->WordFind (L".");
   WORD wQuestion = (WORD) m_pLex->WordFind (L"?");
   WORD wExclamation = (WORD) m_pLex->WordFind (L"!");
   DWORD dwSubSentence = 0;
   DWORD dwIndex = 0;
   for (i = 0; i < pThis->m_dwNum; i++) {
      if ( ((pThis->m_pabPOS[i] & 0x0f) == POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION)) ) {
         // if it's a sentence boundary then set some stuff
         if (
            (pThis->m_paSyl[i].wWord != (WORD)-1) &&
            ( (pThis->m_paSyl[i].wWord == wPeriod) || (pThis->m_paSyl[i].wWord == wQuestion) || (pThis->m_paSyl[i].wWord == wExclamation) ) ) {

               dwSubSentence++;
               dwIndex = 0;
            };

   
         fss.dwIndex = fss.dwSubSentenceLength = 0;
         fss.dwSubSentence = (DWORD)-1;
         lFBMSUBSENT.Add (&fss);

         // ignore punctuation
         continue;
      }

      // else, add
      fss.dwSubSentenceLength = 0;
      fss.dwSubSentence = dwSubSentence;
      fss.dwIndex = dwIndex;
      dwIndex++;
      lFBMSUBSENT.Add (&fss);
   } // i

   // go back over and calculate total length
   FBMSUBSENT *pfss;
   pfss = (PFBMSUBSENT) lFBMSUBSENT.Get(0);
   for (i = 0; i < lFBMSUBSENT.Num(); i++) {
      if (pfss[i].dwSubSentence == (DWORD)-1)
         continue;

      if (pfss[i].dwSubSentenceLength)
         continue;   // length already calculated

      DWORD dwCount  = 0;
      for (j = i; j < lFBMSUBSENT.Num(); j++)
         if (pfss[i].dwSubSentence == pfss[j].dwSubSentence)
            dwCount++;
      for (j = i; j < lFBMSUBSENT.Num(); j++)
         if (pfss[i].dwSubSentence == pfss[j].dwSubSentence)
            pfss[j].dwSubSentenceLength = dwCount;
   }

   PSENTENCESYLLABLE pss;
   PROSODYTREND ProsTrend;
   for (i = 0; i < pThis->m_dwNum; i++) {
      // if its a new word then shift NGram and add
      if (pThis->IsStartOfWord(i)) {
         memmove (abNGramPOS, abNGramPOS+1, sizeof(abNGramPOS)-sizeof(BYTE));

         // find the next POS
         for ( ; (dwNextPOSSyl < pThis->m_dwNum) && !pThis->IsStartOfWord(dwNextPOSSyl); dwNextPOSSyl++);

         if (dwNextPOSSyl < pThis->m_dwNum) {
            abNGramPOS[3] = pThis->m_pabPOS[dwNextPOSSyl] & 0x0f;
            dwNextPOSSyl++;
         }
         else
            abNGramPOS[3] = POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION);   // beyond the end
      } // if new word

      // do a weighted sum of all the surrounding bits
      memset (&Emph, 0, sizeof(Emph));
      fMicroSilence = fMicroSilenceSum = 0;

#ifdef _DEBUG
      WCHAR szTemp[128];
      swprintf (szTemp, L"\r\n\r\nWord: %d", (int)i);
      OutputDebugStringW (szTemp);
#endif
#if 0
      fprintf (file, "\r\n\r\nWord: %d", (int)i);
#endif // 0

      dwNumInStack = ppl[i]->Num();
      pStack = (PBESTSENTSWEEP)ppl[i]->Get(0);

      // sort by fRank so the best is at the top
      qsort (pStack, dwNumInStack, sizeof(BESTSENTSWEEP), BESTSENTSWEEPSortRank);

      // determine the best rank and use that
      DWORD dwBestRank = 0;
      for (j = 0; j < dwNumInStack; j++) {   // inteinoally start at 1
         // calculate fRankScore
         _ASSERTE (pStack[j].fRank >= 0);
         pStack[j].fRankScore = (fp)j / (fp)dwNumInStack;
         // note: dont do pStack[j].fRankScore *= pStack[j].fRankScore; since shouldnt need
         pStack[j].fRankScore = pow(cos(pStack[j].fRankScore * PI/2),2);  // to demphasize ones at bottom
         // pStack[j].fRankScore = pow (0.75, pStack[j].fRank);
            // BUGFIX - Was pow (0.5). Reduced because also include weighting of similar-sounding areas
            // BUGFIX - Remove code for:  * (fp)pStack[j].dwScore;, since redundant with rank

         // determine the best rank
         if (pStack[j].fRank < pStack[dwBestRank].fRank)
            dwBestRank = j;
      }

      // see how much each of these different from the best rank
      for (j = 0; j < dwNumInStack; j++) {
         pStack[j].dwCompareRegion = (pStack[j].pSent && pStack[dwBestRank].pSent) ?
            pStack[j].pSent->CompareRegion (pStack[j].iOffsetTest, pStack[dwBestRank].pSent, pStack[dwBestRank].iOffsetTest) :
            MAXCOMPAREREGIONERRTOTAL;
      } // j

      // sort by dwCompareRegion so the closest matches are at the top
      qsort (pStack, dwNumInStack, sizeof(BESTSENTSWEEP), BESTSENTSWEEPSort);

      // blend the elements in the stack
      for (j = 0; j < dwNumInStack; j++) {
         // blend with
         pBlendWith = pStack + j;

         //fWeight = pow(0.5, pBlendWith->fRank);   // so lower ranked sentences are much lower
         //_ASSERTE (pBlendWith->fRank >= 0);
         //fWeight *= (fp) pBlendWith->dwScore;
         // BUGFIX - reduce the emphasis on the original ranking score, so that can emphasize the closeness
         // of the match from CompareRegion()
         fWeight = pBlendWith->fRankScore;
         fWeight = max(fWeight, EPSILON);
         fWeight = pow ((double)fWeight, 1.0 / (double)MATCHUNWEIGHTSCORE);
         

         // since sorted by how close they compare with the best, also include index in scale
         // using this ensures that the top-ranking sentence is mimiced fairly well,
         // but that if there are any wierdnesses in the sentence, they're ironed out by
         // overweighting other sentences that are similar to the top-ranked one
         fp fAlpha = (fp)j / (fp)dwNumInStack;
         // note: dont do fAlpha = sqrt(fAlpha);  // so only emphasizes most similar, and demphasizes others
         fWeight *= pow(cos (fAlpha * PI/2), MATCHPOWER); // * (1.0 - fAlpha);
            // use cos so ones at bottom are very de-emphasized

#ifdef _DEBUG
      swprintf (szTemp, L"\r\nBlend in word %d from sentence %d, orig weight %d, final = %g",
         (int)pBlendWith->iOffsetTest, pBlendWith->pSent ? (int)pBlendWith->pSent->m_dwSentIndex : -1, (int)pBlendWith->iScoreAvg,
         (double)fWeight);
      OutputDebugStringW (szTemp);
#endif
#if 0
      fprintf (file, "\r\nBlend in word %d from sentence %d, weight %d",
         (int)pBlendWith->dwOffset, pBlendWith->pSent ? (int)pBlendWith->pSent->m_dwSentIndex : -1, (int)pBlendWith->dwScore);
#endif // 0

         // if there isn't any available match then skip
         if (!pBlendWith->pSent)
            continue;

         // else, add, weighting the scores of the blend
         int iOffsetTemp = pBlendWith->iOffsetTest;
         iOffsetTemp = max(iOffsetTemp, 0);  // just in case, hack anyway
         iOffsetTemp = min(iOffsetTemp, (int)(pBlendWith->pSent->m_dwNum-1)); // hack anyway

         pss = pBlendWith->pSent->m_paSyl + iOffsetTemp;

         // determine the prosody trend
         ProsodyTrend (
            pBlendWith->pSent->m_pabPOS[iOffsetTemp], pBlendWith->pSent->m_pabRuleDepth[iOffsetTemp],
            pThis->m_pabPOS[i], pThis->m_pabRuleDepth[i],
            &ProsTrend,
            ppCTTSProsody, dwNum);

         Emph.Emph.fPitch +=
            ( log((fp)pss->bPitch / 100.0) + (fp)ProsTrend.iPitch / (fp)100.0 * log((fp)2) ) * fWeight;
         Emph.Emph.fPitchSweep +=
            ( (fp)pss->cPitchSweep / 100.0 + (fp)ProsTrend.iPitchSweep / 100.0 ) * fWeight;
         Emph.Emph.fPitchBulge +=
            ( (fp)pss->cPitchBulge / 100.0 + (fp)ProsTrend.iPitchBulge / 100.0 ) * fWeight;
         Emph.Emph.fVolume +=
            ( log((fp)pss->bVol / 100.0) + (fp)ProsTrend.iVol / (fp)50.0 * log((fp)2) ) * fWeight;
#ifdef _DEBUG
         swprintf (szTemp, L"\r\n\tVolume = %g", (double)Emph.Emph.fVolume);
         OutputDebugStringW (szTemp);
#endif
         Emph.Emph.fDurPhone +=
            ( log((fp)pss->bDurPhone / 100.0) + (fp)ProsTrend.iDurPhone / (fp)50.0 * log((fp)2) ) * fWeight;
         Emph.Emph.fDurSyl +=
            ( log((fp)pss->bDurSyl / 100.0) + (fp)ProsTrend.iDurSyl / (fp)50.0 * log((fp)2) ) * fWeight;
         NOTE: Not during fDurSkew
         Emph.fMisc += fWeight;

         // BUGFIX - look at the POS for only the first syllable to see if should include
         BYTE bPOS = pBlendWith->pSent->m_pabPOS[iOffsetTemp];
            // NOTE: Ignoring blending of bRuleDepth
         if (!(bPOS & 0x70)) {
            // this is the start of a word
            fMicroSilenceSum += fWeight;
            fMicroSilence += fWeight * (fp)(pss->bPauseProb & 0x0f) / 15.0;
         }

         // if there's a previous word, and its pitch isnt' 0, and this one's
         // pithc isn't 0, then determine relative pitch
         if (iOffsetTemp && pss->bPitch && pss[-1].bPitch) {
            pafRelative[i] +=
               ( log((fp)pss->bPitch / (fp)pss[-1].bPitch) + (fp)ProsTrend.iPitchRelative / (fp)100.0 * log((fp)2) ) * fWeight;

// #define DISABLERESIDUAL
#ifdef DISABLERESIDUAL  // so no relative pitch component for test
            pafRelative[i] = 0;
#endif // _DEBUG

            // BUGFIX - Was just afRelativeSum, but should be pafRelativeSum
            pafRelativeSum[i] += fWeight;
         }
      } // j, over templates for the word

      // if there's no weight then make a neutral case
      if (Emph.fMisc) {
         fp fScale = 1.0 / Emph.fMisc;

         Emph.Emph.fPitch = exp(Emph.Emph.fPitch * fScale);
         Emph.Emph.fPitchSweep *= fScale;
         Emph.Emph.fPitchBulge *= fScale;
         Emph.Emph.fVolume = exp(Emph.Emph.fVolume * fScale);
         Emph.Emph.fDurPhone = exp(Emph.Emph.fDurPhone * fScale);
         Emph.Emph.fDurSyl = exp(Emph.Emph.fDurSyl * fScale);
         NOTE: Not doing fDurSkew

         if (fMicroSilenceSum)
            fMicroSilence = fMicroSilence / fMicroSilenceSum;
         else
            fMicroSilence = 0.0;
         //if (fMicroSilence > fMicroSilenceSum * fMicroPauseThreshhold)
         //   fMicroSilence = TRUE;
         //else
         //   fMicroSilence = FALSE;
      }
      else {
         Emph.Emph.fPitchSweep = 0;
         Emph.Emph.fPitchBulge = 0;
         NOTE: Not doing fDurSkew
         Emph.Emph.fPitch = Emph.Emph.fVolume = Emph.Emph.fDurPhone = Emph.Emph.fDurSyl = 1;
         fMicroSilence = 0.0;   // no microsilnce
      }

#ifdef DISABLERESIDUAL  //  zero out to test typical info
      Emph.Emph.fPitchSweep = 0;
      Emph.Emph.fPitchBulge = 0;
      NOTE: Not doing fDurSkew
      Emph.Emph.fPitch = Emph.Emph.fVolume = Emph.Emph.fDurPhone = Emph.Emph.fDurSyl = 1;
#endif

      // restore typical info
      fp fWeightCur = pafRelativeSum[i];
      if (!fWeightCur)
         fWeightCur = 1;

// #define DISABLETYPICALSYLINFO
#ifndef DISABLETYPICALSYLINFO
      if (pfss[i].dwSubSentence != (DWORD)-1) {
         TYPICALSYLINFO TSI, TSILast;
         TypicalSylInfoGet (pfss[i].dwIndex, pfss[i].dwSubSentenceLength, ppCTTSProsody, dwNum, &TSI);
         BOOL fUseRelative = (i && (pfss[i-1].dwSubSentence != (DWORD)-1));
         if (fUseRelative)
            TypicalSylInfoGet (pfss[i-1].dwIndex, pfss[i-1].dwSubSentenceLength, ppCTTSProsody, dwNum, &TSILast);

         if (TSI.fCount) {
            Emph.Emph.fPitch *= exp(TSI.fPitchSum / TSI.fCount);
            Emph.Emph.fVolume *= exp(TSI.fVolumeSum / TSI.fCount);
            Emph.Emph.fDurPhone *= exp(TSI.fDurPhoneSum / TSI.fCount);
            Emph.Emph.fDurSyl *= exp(TSI.fDurSylSum / TSI.fCount);
            NOTE: Not doing fDurSkew
            Emph.Emph.fPitchBulge += TSI.fPitchBulgeSum / TSI.fCount;
            Emph.Emph.fPitchSweep += TSI.fPitchSweepSum / TSI.fCount;

            // relative
            if (fUseRelative && TSI.fCount && TSILast.fCount) {
               fWeight = fWeightCur;
               pafRelative[i] += ((TSI.fPitchSum / TSI.fCount) - (TSILast.fPitchSum / TSILast.fCount)) * fWeight;
               pafRelativeSum[i] += fWeight;
            }
         }
      } // typical info
#endif // DISABLETYPICALSYLINFO

      // include prosody ngram
// #define DISABLEPROSODYNGRAM
#ifndef DISABLEPROSODYNGRAM
      if (pfss[i].dwSubSentence != (DWORD)-1) {
         TYPICALSYLINFO TSI, TSILast;
         ProsodyNGramInfoGet (pThis->m_pabPOS, pThis->m_dwNum, i, ppCTTSProsody, dwNum, &TSI);
         BOOL fUseRelative = (i && (pfss[i-1].dwSubSentence != (DWORD)-1));
         if (fUseRelative)
            ProsodyNGramInfoGet (pThis->m_pabPOS, pThis->m_dwNum, i - 1, ppCTTSProsody, dwNum, &TSILast);

         if (TSI.fCount) {
            Emph.Emph.fPitch *= exp(TSI.fPitchSum / TSI.fCount);
            Emph.Emph.fVolume *= exp(TSI.fVolumeSum / TSI.fCount);
            Emph.Emph.fDurPhone *= exp(TSI.fDurPhoneSum / TSI.fCount);
            Emph.Emph.fDurSyl *= exp(TSI.fDurSylSum / TSI.fCount);
            NOTE: Not doing fDurSkew
            Emph.Emph.fPitchBulge += TSI.fPitchBulgeSum / TSI.fCount;
            Emph.Emph.fPitchSweep += TSI.fPitchSweepSum / TSI.fCount;

            // relative
            if (fUseRelative && TSI.fCount && TSILast.fCount) {
               fWeight = fWeightCur;
               pafRelative[i] += ((TSI.fPitchSum / TSI.fCount) - (TSILast.fPitchSum / TSILast.fCount)) * fWeight;
               pafRelativeSum[i] += fWeight;
            }
         }
      } // prosody NGram
#endif

      // write the values in
      pss = pThis->m_paSyl + i;

      // store this away
      fp f;
      f = Emph.Emph.fPitch * 100.0;
      f = max(f, 25);  // dont be too restrtive.. and shouldnt happen anyway
      f = min(f, 254);  // dont be too restrtive.. and shouldnt happen anyway
      pss->bPitch = (BYTE)(f+0.5);

      f = Emph.Emph.fPitchSweep * 100.0;
      f = max(f, -126);  // dont be too restrtive.. and shouldnt happen anyway
      f = min(f, 126);  // dont be too restrtive.. and shouldnt happen anyway
      pss->cPitchSweep = (char)(int)floor(f+0.5);

      f = Emph.Emph.fPitchBulge * 100.0;
      f = max(f, -126);  // dont be too restrtive.. and shouldnt happen anyway
      f = min(f, 126);  // dont be too restrtive.. and shouldnt happen anyway
      pss->cPitchBulge = (char)(int)floor(f+0.5);

      f = Emph.Emph.fVolume * 100.0;
      f = max(f, 25);  // dont be too restrtive.. and shouldnt happen anyway
      f = min(f, 254);  // dont be too restrtive.. and shouldnt happen anyway
      pss->bVol = (BYTE)(f+0.5);

      f = Emph.Emph.fDurPhone * 100.0;
      f = max(f, 25);  // dont be too restrtive.. and shouldnt happen anyway
      f = min(f, 254);  // dont be too restrtive.. and shouldnt happen anyway
      pss->bDurPhone = (BYTE)(f+0.5);

      f = Emph.Emph.fDurSyl * 100.0;
      f = max(f, 25);  // dont be too restrtive.. and shouldnt happen anyway
      f = min(f, 254);  // dont be too restrtive.. and shouldnt happen anyway
      pss->bDurSyl = (BYTE)(f+0.5);

      NOTE: Not doing fDurSkew

      // given the part of speech, determine the location in the N gram
      // average the N-gram probability in with the value that have stored
      fp fPauseNGram = PauseNGram (abNGramPOS[0], abNGramPOS[1], abNGramPOS[2], abNGramPOS[3],
         ppCTTSProsody, dwNum);
      if (fPauseNGram >= 0)
         fMicroSilence = (fMicroSilence + fPauseNGram) / 2.0;


      // NOTE: bPauseProb used as microsilnce value... if 0 NEVER insert, if 15 always insert
      fMicroSilence = max(fMicroSilence, 0);
      fMicroSilence = min(fMicroSilence, 1);
      pss->bPauseProb = (BYTE)(fMicroSilence*15.0 + 0.5);   // 0..15 only

      // scale sum
      if (pafRelativeSum[i])
         pafRelative[i] /= pafRelativeSum[i];
      if (i)
         pafRelative[i] += pafRelative[i-1]; // so pitch achored from previous pitch
      else
         pafRelative[i] = 0;  // no relative pitch for first entry
      fPitchAverage += pafRelative[i]; // so cancalulate the average
   } // i

   // loop through all the pitch scale values and make sure average is 0
   fPitchAverage /= (fp)max(pThis->m_dwNum,1);
   fp f, fSum;
   for (i = 0; i < pThis->m_dwNum; i++)
      pafRelative[i] -= fPitchAverage; // so average around speakingpitch

   // filter so there isn't too much of a pitch delta
   for (i = 0; i < pThis->m_dwNum; i++) {
      int iWindow, iCur;
      f = fSum = 0;
      for (iWindow = -(RPWINDOWSIZE/2); iWindow <= (RPWINDOWSIZE/2); iWindow++) {
         iCur = iWindow + (int)i;
         if ((iCur < 0) || (iCur >= (int)pThis->m_dwNum))
            continue;

         fWeight = RPWINDOWSIZE/2 - abs(iWindow) + 1;
         f += fWeight;
         fSum += pafRelative[iCur] * fWeight;
      } // iCur

      if (f)
         fSum /= f;
      pafFilter[i] = fSum;
   } // i 

   
   // recobine into main pitch
   for (i = 0; i < pThis->m_dwNum; i++) {
      f = pafRelative[i] - pafFilter[i]; // so average around speakingpitch
      f = exp(f); // so no longer log
      f *= 100.0; // sine 100 is typical value

      pss = pThis->m_paSyl + i;
      f = RELATIVEPITCHWEIGHT * f + (1.0 - RELATIVEPITCHWEIGHT) * (fp)pss->bPitch;
      f = max(f,25);
      f = min(f,255);
      pss->bPitch = (BYTE) f;
   } // i

#if 0 // to test
   for (i = 0; i < pThis->m_dwNum; i++) {
      pss = pThis->m_paSyl + i;
      fprintf (file, "\r\nSyl %d : %x,%x,%x,%x,%x,%x,%x,%x,%x,%x",
         (DWORD) i,
         (DWORD)pss->bDurPhone, (DWORD)pss->bDurSyl, (DWORD)pss->bPauseProb, (DWORD)pss->bPitch,
         (DWORD)pss->bVol, (DWORD)(BYTE)pss->cPitchSweep, (DWORD)(BYTE)pss->cPitchBulge, (DWORD)pss->wWord,
         (DWORD)pThis->m_pabRuleDepth[i], (DWORD)pThis->m_pabPOS[i]);
   } // i

   fclose (file);
#endif // 0
#endif 0 // no longer used because replaced by SentenceSyllablesCombineWithProsody()

done:
   // free the list of lists
   for (i = 0; i < lPerSyl.Num(); i++)
      delete ppl[i];

#endif // TEMPLATEPROOSDYMODEL
}



/*************************************************************************************
CTTSProsodyMatchHyp::Constructor and destructor
*/
CTTSProsodyMatchHyp::CTTSProsodyMatchHyp (void)
{
   m_dwNextJoinIndex = 0;
   m_fScore = 0;
   m_iResynthNextSyl = 0;
   m_iJoinNextSyl = 0;
}

CTTSProsodyMatchHyp::~CTTSProsodyMatchHyp (void)
{
   // do nothing
}



/*************************************************************************************
CTTSProsodyMatchHyp::CloneTo - Standard clone to

inputs
   PCTTSProsodyMatchHyp    pTo - Clone to
returns
   BOOL - TRUE if successful
*/
BOOL CTTSProsodyMatchHyp::CloneTo (CTTSProsodyMatchHyp *pTo)
{
   m_SS.CloneTo (&pTo->m_SS);
   pTo->m_dwNextJoinIndex = m_dwNextJoinIndex;
   pTo->m_fScore = m_fScore;
   pTo->m_iResynthNextSyl = m_iResynthNextSyl;
   pTo->m_iJoinNextSyl = m_iJoinNextSyl;

   return TRUE;
}

/*************************************************************************************
CTTSProsodyMatchHyp::Clone - Standard API

inputs
   none
returns
   PCTTSProsodyMatchHyp - New object, NULL if error
*/
CTTSProsodyMatchHyp *CTTSProsodyMatchHyp::Clone (void)
{
   PCTTSProsodyMatchHyp pNew = new CTTSProsodyMatchHyp;
   if (!pNew)
      return NULL;

   if (!CloneTo (pNew)) {
      delete pNew;
      return NULL;
   }

   return pNew;
}


/*************************************************************************************
CTTSProsodyMatchHyp::IncludeJoinCosts - Bring join-costs calculations up to date.

inputs
   PCOMPARESYLINFO   pInfo - Score penalties to use.
   DWORD             dwNextResynthIndex - Next resynth index (into plResynth) that will be
                     be doing. Use this to determine where the m_SS sentence is valid
                     up to.
   PCListFixed       plResynth - List of PCSentenceMatch with all the matches to generate
                     the sentence. From GenerateMatches()
   PCListFixed       plJoin - List of PCSentenceMatch with all the joins for the sentence.
                     From GenerateMatches().
   DWORD             dwMaxJoin - Max join distance, from MaxJoinDistance().
   BOOL           fWord - if TRUE then making sure the word-based sentences are used.
                     If FALSE, then syllable-based sentences
   WORD              wPeriod - Word ID for a period
returns
   BOOL - TRUE if success
*/
BOOL CTTSProsodyMatchHyp::IncludeJoinCosts (PCOMPARESYLINFO pInfo, DWORD dwNextResynthIndex, PCListFixed plResynth,
                                            PCListFixed plJoin, DWORD dwMaxJoin, BOOL fWord, WORD wPeriod)
{
   // figure out what syllable number is valid up to
   int iSylValid;
   PCSentenceMatch *ppSM;
   PCSentenceMatch pSM;
   if (dwNextResynthIndex >= plResynth->Num())
      iSylValid = 1000000000; // valid to the end
   else {
      ppSM = (PCSentenceMatch*) plResynth->Get(dwNextResynthIndex);
      pSM = ppSM[0];

      iSylValid = pSM->m_iStartUse;
   }

   // note that we're included scores up to this one
   m_iJoinNextSyl = max(m_iJoinNextSyl, iSylValid - (int)dwMaxJoin * 2 + 1);
      // this is the technical maximum

   // repeat, trying the next join to see if it's OK to process it
   ppSM = (PCSentenceMatch*) plJoin->Get(0);
   DWORD i;
   CMem memWeight;
   while (m_dwNextJoinIndex < plJoin->Num()) {
      pSM = ppSM[m_dwNextJoinIndex];

      // if end of join goes beyond what's been calculated as valid then
      // stop
      if (pSM->m_iEnd > iSylValid)
         break;

      // NOTE: Some joins are fewer units than others, so they won't all be
      // calculated at the same pace... which means that the score comparison
      // will be slightly erroneous in the short term... but, bad candidates
      // can be elimniated more quickly
      // The foolproff way is to use dwMaxJoin + m_pSM->m_iEndUse

      // if no entries, then join cost is 0, so just skip. UNlikely that this
      // will happen, but it might
      if (!pSM->m_lSENTENCEMATCH.Num()) {
         m_dwNextJoinIndex++;
         continue;
      }

      // use beginning of this as a maximum for joins that have goten up to
      // NOTE: Not 100% technically correct since this join could be extra
      // small, and the next one larger, resulting in some erroneous elinianation
      // of data
      m_iJoinNextSyl = max(m_iJoinNextSyl, pSM->m_iStart + 1);
         // Add +1 because the next time eliminate will have advanced at least one syllable

      PSENTENCEMATCH paSM = (PSENTENCEMATCH) pSM->m_lSENTENCEMATCH.Get(0);

      // calculate the join weight
      fp *pafWindow = paSM[0].pSS->CompareWindowGenerate (0, paSM[0].dwNum, TRUE, &memWeight);
      if (!pafWindow)
         break;   // error

      // if get here have a join that can calculate because all the data
      // is valid
      fp fBestScore = 0;
      DWORD dwBestScore = (DWORD)-1;
      for (i = 0; i < pSM->m_lSENTENCEMATCH.Num(); i++) {
         // include how different/alike the join is compared to what commonly happens
         fp fScore = paSM[i].fScore;

         // quick skip
         if ((dwBestScore != (DWORD)-1) && (fScore >= fBestScore))
            continue;   // already worse, so don't bother going further
         
         // calculate how close the produced audio is compared to the hypothesized join
         _ASSERTE ((int)paSM[i].dwNum == (pSM->m_iEnd - pSM->m_iStart));
         fScore += m_SS.CompareSequenceWindowed (pInfo, pSM->m_iStart, paSM[i].pSS, paSM[i].iStart, paSM[i].dwNum,
            wPeriod, FALSE, fWord, pafWindow);

         // if this is better then remember it
         if ((dwBestScore == (DWORD)-1) || (fScore < fBestScore)) {
            dwBestScore = i;
            fBestScore = fScore;
         }
      } // i

      // scale the score by a triangle window sized based on the join compare area
      fBestScore *= (fp)paSM[dwBestScore].dwNum / 2.0;
      m_fScore += fBestScore;
      m_dwNextJoinIndex++;
   } // join
   
   return TRUE;
}



/*************************************************************************************
CTTSProsodyMatchHyp::Expand - Modify and clone this to produce alternate prosodies.

inputs
   PCOMPARESYLINFO   pInfo - Score penalties to use.
   DWORD             dwNextResynthIndex - Next resynth index (into plResynth) that will be
                     be doing.
   PCListFixed       plResynth - List of PCSentenceMatch with all the matches to generate
                     the sentence. From GenerateMatches()
   WORD              wPeriod - Word ID for a period
   PCListFixed       plPCTTSProsodyMatchHyp - Append all expansions to this hyp.
returns
   BOOL - TRUE if success
*/
BOOL CTTSProsodyMatchHyp::Expand (PCOMPARESYLINFO pInfo, DWORD dwNextResynthIndex, PCListFixed plResynth,
                                  WORD wPeriod, PCListFixed plPCTTSProsodyMatchHyp)
{
   PCTTSProsodyMatchHyp pNew;

   // if there's nothing to expand then just copy over
   if (dwNextResynthIndex >= plResynth->Num()) {
      pNew = Clone();
      if (!pNew)
         return FALSE;
      plPCTTSProsodyMatchHyp->Add (&pNew);
      return TRUE;
   }

   // blank syllables out for now
   PCSentenceMatch *ppSM;
   PCSentenceMatch pSM;
   int iSyl;
   ppSM = (PCSentenceMatch*) plResynth->Get(dwNextResynthIndex);
   pSM = ppSM[0];

   PSENTENCESYLLABLE paSylNew, paSylFrom;
   for (iSyl = pSM->m_iStartUse; iSyl < pSM->m_iEndUse; iSyl++) {
      // make sure it's in range
      if ((iSyl < 0) || (iSyl >= (int)m_SS.m_dwNum))
         continue;   // out of range

      paSylNew = &m_SS.m_paSyl[iSyl];

      // set defaults
      paSylNew->bDurPhone = 100;
      paSylNew->bDurSyl = 100;
      paSylNew->bPauseProb = 0;
      memset (paSylNew->acPitchF0Extra, 0, sizeof(paSylNew->acPitchF0Extra));
      paSylNew->bPitchF0Fuji = 100;
      paSylNew->bVol = 100;
      paSylNew->cAccent = 0;
      paSylNew->cPitchF0FujiDelta = 0;
      paSylNew->cDurSkew = 0;
      // paSylNew->cPitchBulge = 0;
      // paSylNew->cPitchSweep = 0;
   } // iSyl

   // note that have filled in new audio up to this point
   m_iResynthNextSyl = max(m_iResynthNextSyl, pSM->m_iEndUse);

   // if there aren't any hypothesis to expand, then blank the syllable settings to default
   // and add
   if (!pSM->m_lSENTENCEMATCH.Num()) {
      pNew = Clone();
      if (!pNew)
         return FALSE;
      // add
      plPCTTSProsodyMatchHyp->Add (&pNew);
      return TRUE;
   }


   // if get here, create lots of clones, each taking a different path
   // in the possible sentences

   // calculate the window
   PSENTENCEMATCH paSM = (PSENTENCEMATCH)pSM->m_lSENTENCEMATCH.Get(0);
   //CMem memWeight;
   //fp *pafWindow = paSM[0].pSS->CompareWindowGenerate (0, paSM[0].dwNum, pSM->m_fTriangle, &memWeight);
   //if (!pafWindow)
   //   return FALSE;   // error


   // try different paths
   DWORD i;
   int iSylFrom, iSylFromRunnerUp;
   fp fScore;
   for (i = 0; i < pSM->m_lSENTENCEMATCH.Num(); i++, paSM++) {
      pNew = Clone();
      if (!pNew)
         return FALSE;

      // calculate the score
      fScore = paSM->fScore; // include how like/unlike it is from the norm
      _ASSERTE ((int)paSM->dwNum == (pSM->m_iEnd - pSM->m_iStart));
      // NOTE: Don't need to do the comparison since already precalculated and included in paSM->fScore
      //fScore += pNew->m_SS.CompareSequenceWindowed (pInfo, pSM->m_iStart, paSM->pSS, paSM->iStart, paSM->dwNum,
      //      wPeriod, TRUE, pafWindow); // non-prosody compare

      fScore *= (fp)(pSM->m_iEndUse - pSM->m_iStartUse); // include volume
      // Don't do this becausing only including the score for the syllables that are repealced
      //if (pSM->m_fTriangle)
      //   fScore /= 2.0; // since triangle has half volume
      pNew->m_fScore += fScore;

      // copy over prosody
      for (iSyl = pSM->m_iStartUse, 
            iSylFrom = paSM->iStart + (pSM->m_iStartUse - pSM->m_iStart),
            iSylFromRunnerUp = paSM->iStartRunnerUp + (pSM->m_iStartUse - pSM->m_iStart);
         iSyl < pSM->m_iEndUse; 
         iSyl++, iSylFrom++, iSylFromRunnerUp++) {
         // make sure it's in range
         if ((iSyl < 0) || (iSyl >= (int)pNew->m_SS.m_dwNum))
            continue;   // out of range
         if ((iSylFrom < 0) || (iSylFrom >= (int)paSM->pSS->m_dwNum))
            continue;   // out of range

         _ASSERTE ( pNew->m_SS.m_pabPOSStress[iSyl] == paSM->pSS->m_pabPOSStress[iSylFrom]);
         // _ASSERTE ( (pNew->m_SS.m_pabPOS[iSyl] & 0x8f) == (paSM->pSS->m_pabPOS[iSylFrom] & 0x8f));

#ifdef NOMODS_DISABLEPROSODYWORDMATCH
         // only let entire phrase matches through
         if (pSM->m_fTriangle)
            continue;   // don't modify this, leave as defaults
#endif
         // quick pointers
         paSylNew = &pNew->m_SS.m_paSyl[iSyl];
         paSylFrom = &paSM->pSS->m_paSyl[iSylFrom];

         // copy the prosody values over
         paSylNew->bDurPhone = paSylFrom->bDurPhone;
         paSylNew->bDurSyl = paSylFrom->bDurSyl;
         paSylNew->bPauseProb = paSylFrom->bPauseProb;
         memcpy (paSylNew->acPitchF0Extra, paSylFrom->acPitchF0Extra, sizeof(paSylNew->acPitchF0Extra));
         paSylNew->bPitchF0Fuji = paSylFrom->bPitchF0Fuji;
         paSylNew->cPitchF0FujiDelta = paSylFrom->cPitchF0FujiDelta;
         paSylNew->bVol = paSylFrom->bVol;
         paSylNew->cAccent = paSylFrom->cAccent;
         paSylNew->cDurSkew = paSylFrom->cDurSkew;
         // paSylNew->cPitchBulge = paSylFrom->cPitchBulge;
         // paSylNew->cPitchSweep = paSylFrom->cPitchSweep;

#ifndef NOMODS_DISABLEPROSODYMERGEMATCH
         // if there's a runner up, average in
         if (paSM->pSSRunnerUp && (iSylFromRunnerUp >= 0) && (iSylFromRunnerUp < (int)paSM->pSSRunnerUp->m_dwNum) ) {
            _ASSERTE ( pNew->m_SS.m_pabPOSStress[iSyl] == paSM->pSSRunnerUp->m_pabPOSStress[iSylFromRunnerUp]);
            //_ASSERTE ( (pNew->m_SS.m_pabPOS[iSyl] & 0x8f) == (paSM->pSSRunnerUp->m_pabPOS[iSylFromRunnerUp] & 0x8f));

            paSylFrom = &paSM->pSSRunnerUp->m_paSyl[iSylFromRunnerUp];

            paSylNew->bDurPhone = (BYTE)(sqrt((fp)paSylNew->bDurPhone * (fp)paSylFrom->bDurPhone) + 0.5);
            paSylNew->bDurSyl = (BYTE)(sqrt((fp)paSylNew->bDurSyl * (fp)paSylFrom->bDurSyl) + 0.5);
            paSylNew->bPauseProb = ((paSylNew->bPauseProb & 0x0f) + (paSylFrom->bPauseProb & 0x0f)) / 2 + (paSylNew->bPauseProb & 0xf0);

            // BUGFIX - DON'T average in F0Extra
            //DWORD dwPitchPoint;
            //for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
            //   paSylNew->acPitchF0Extra[dwPitchPoint] = (char) ( ( (int)paSylNew->acPitchF0Extra[dwPitchPoint] + (int)paSylFrom->acPitchF0Extra[dwPitchPoint] + 1) / 2);

            paSylNew->bPitchF0Fuji = (BYTE)(sqrt((fp)paSylNew->bPitchF0Fuji * (fp)paSylFrom->bPitchF0Fuji) + 0.5);
            paSylNew->cPitchF0FujiDelta = (char) (((int)paSylNew->cPitchF0FujiDelta + (int)paSylFrom->cPitchF0FujiDelta + 1) / 2);

            paSylNew->bVol = (BYTE)(sqrt((fp)paSylNew->bVol * (fp)paSylFrom->bVol) + 0.5);
            paSylNew->cAccent = (char) (((int)paSylNew->cAccent + (int)paSylFrom->cAccent) / 2 + 1);
            paSylNew->cDurSkew = (char) (((int)paSylNew->cDurSkew + (int)paSylFrom->cDurSkew) / 2 + 1);
            // paSylNew->cPitchBulge = (char) (((int)paSylNew->cPitchBulge + (int)paSylFrom->cPitchBulge) / 2 + 1);
            // paSylNew->cPitchSweep = (char) (((int)paSylNew->cPitchSweep + (int)paSylFrom->cPitchSweep) / 2 + 1);
         }
#endif
      } // iSyl

      // add
      plPCTTSProsodyMatchHyp->Add (&pNew);
   } // i

   // done
   return TRUE;
}



/*************************************************************************************
CTTSProsodyMatchHyp::Compare - Compares this hypothesis against another one, and returns
a signed number indicating which is higher in the list.

inputs
   CTTSProsodyMatchHyp     *pWith - Compare with
   BOOL                    fIncludeScore - If the unscored syllables at the end are
                           an exact match, then this will go on to compare the scores.
                           If FALSE, then scores are NOT compared.
returns
   int - Positive if this appears lower in list, negative if higher, 0 if exact match
*/
int CTTSProsodyMatchHyp::Compare (CTTSProsodyMatchHyp *pWith, BOOL fIncludeScore)
{
   if (m_iJoinNextSyl != pWith->m_iJoinNextSyl)
      return m_iJoinNextSyl - pWith->m_iJoinNextSyl;  // shouldnt happen
   if (m_iResynthNextSyl != pWith->m_iResynthNextSyl)
      return m_iResynthNextSyl - pWith->m_iResynthNextSyl;  // shouldnt happen

   int iStart = max(m_iJoinNextSyl, 0);
   int iEnd = min(m_iResynthNextSyl, (int)m_SS.m_dwNum);
   int iRet;
   if (iStart < iEnd) {
      // only need to compare m_paSyl since that's all that changed
      iRet = memcmp (&m_SS.m_paSyl[iStart], &pWith->m_SS.m_paSyl[iStart], (iEnd - iStart) * sizeof(m_SS.m_paSyl[iStart]));
      if (iRet)
         return iRet;   // found a difference
   }

   // compare score
   if (fIncludeScore) {
      if (m_fScore > pWith->m_fScore)
         return 1;
      else if (m_fScore < pWith->m_fScore)
         return -1;
   }

   // else, no differnece
   return 0;
}



/*************************************************************************************
CSentenceMatch::Constructor and destructor
*/
CSentenceMatch::CSentenceMatch (void)
{
   m_lSENTENCEMATCH.Init (sizeof(SENTENCEMATCH));

   m_iStart = 0;
   m_iEnd = 0;
   m_fTriangle = TRUE;
   m_fIgnoreNotEnough = FALSE;
   m_fIgnoreProsody = FALSE;
   m_fSuccess = FALSE;
   m_fCompareAgainstTarget = FALSE;
   m_iStartUse = 0;
   m_iEndUse = 0;
}

CSentenceMatch::~CSentenceMatch (void)
{
   // do nothing
}



/*************************************************************************************
CSentenceMatch::FindAndScoreMatches - Finds all matches to the given sentence
and creates a "how typical is it" score. This modifies m_lSENTENCEMATCH
with the matched sentences

inputs
   PCMLexicon        pLexTTS - TTS with phonemes in it
   PCOMPARESYLINFO   pInfo - Score penalties to use.
   PCSentenceSyllable   pSS - Sentence that matching against
   CTTSProsody          **ppCTTSProsody - This of prosody objects to use for the comparison,
                        including this one.
   DWORD                dwNum - Number of prosody objects to use. Must be at least one
   int               iTTSQuality - TTS quality. 0 for fast, 1 for normal, 2 for slow, 3 for best, 4 for multipass
   BOOL           fWord - if TRUE then making sure the word-based sentences are used.
                     If FALSE, then syllable-based sentences
   WORD              wPeriod - Word that indicates the punctuation

   LOTS of proeprties, see "user-settable, used by FindAndScoreMatched()"
returns
   BOOL - TRUE if success, FALSE if not enough matches to create a model
*/
BOOL CSentenceMatch::FindAndScoreMatches (PCMLexicon pLexTTS, PCOMPARESYLINFO pInfo, PCSentenceSyllable pSS,
                                          CTTSProsody **ppCTTSProsody, DWORD dwNum, int iTTSQuality, BOOL fWord, WORD wPeriod)
{
   // clear, just in case
   m_lSENTENCEMATCH.Clear();
   m_fSuccess = FALSE;
   if (!dwNum)
      return FALSE;

   // enumerate
   if (!ppCTTSProsody[0]->FindAllMatches (pLexTTS, pSS, m_iStart, m_iEnd, !m_fTriangle, fWord, ppCTTSProsody, dwNum, &m_lSENTENCEMATCH))
            // NOTE: If !m_fTriagle then must have an exact phrase match
      return FALSE;  // error

   // do comparisons
   m_fSuccess = ppCTTSProsody[0]->ScoreMatches (pInfo, &m_lSENTENCEMATCH, iTTSQuality, wPeriod, m_fIgnoreNotEnough, m_fIgnoreProsody,
      fWord, m_fTriangle,
      m_fCompareAgainstTarget ? pSS : NULL, m_iStart);

   return m_fSuccess;
}

/*************************************************************************************
CSentenceSyllable::Construcor and destructor
*/
CSentenceSyllable::CSentenceSyllable (void)
{
   m_dwNum = 0;
   m_pabPOSStress = NULL;
   m_pabSylIndex = NULL;
   m_pabRuleDepth = NULL;
   m_paSyl = NULL;
   m_lPOSStress.Init (sizeof(BYTE));
   m_lSylIndex.Init (sizeof(BYTE));
   m_lRuleDepth.Init (sizeof(BYTE));
   m_lSENTSYLPHRASE.Init (sizeof(SENTSYLPHRASE));
   m_lSENTENCESYLLABLE.Init (sizeof(SENTENCESYLLABLE));
}

CSentenceSyllable::~CSentenceSyllable (void)
{
   // do nothing for now
}



/*************************************************************************************
CSentenceSyllable::MemoryTouch - Call this occasionally to keep TTS from being
cached out of memory
*/
DWORD CSentenceSyllable::MemoryTouch (void)
{
   DWORD dwRet = 0;

   if (!m_dwNum)
      return 0;   // error

   DWORD dwIndex = (DWORD)rand() % m_dwNum;
   dwRet += m_pabPOSStress[dwIndex];
   dwRet += m_pabSylIndex[dwIndex];
   dwRet += m_pabRuleDepth[dwIndex];
   dwRet += m_paSyl[dwIndex].bDurPhone;

   return dwRet;
}


/*************************************************************************************
CSentenceSyllable::Clear - Clears out the information
*/
void CSentenceSyllable::Clear (void)
{
   m_dwNum = 0;
   m_lPOSStress.Clear();
   m_lSylIndex.Clear();
   m_lRuleDepth.Clear();
   m_lSENTSYLPHRASE.Clear();
   m_lSENTENCESYLLABLE.Clear();
}



/*************************************************************************************
CSentenceSyllable::Append - Append a sentence syllable onto this. It DOESN'T do
any word remapping.

inputs
   PCSentenceSyllable         pssAppend - to append
   DWORD                      dwStartIndex - Starting index. If -1 then uses 0.
   DWORD                      dwEndIndex - Ending index to append, If -1 then uses pssAppend->m_dwNum
returns
   BOOL - TRUE if success
*/
BOOL CSentenceSyllable::Append (PCSentenceSyllable pssAppend, DWORD dwStartIndex, DWORD dwEndIndex)
{
   DWORD i;
   if (dwStartIndex == (DWORD)-1)
      dwStartIndex = 0;
   if (dwEndIndex == (DWORD)-1)
      dwEndIndex = pssAppend->m_dwNum;
   DWORD dwNum = dwEndIndex - dwStartIndex;

   m_lPOSStress.Required (m_lPOSStress.Num() + dwNum);
   m_lSylIndex.Required (m_lPOSStress.Num() + dwNum);
   m_lRuleDepth.Required (m_lRuleDepth.Num() + dwNum);
   m_lSENTENCESYLLABLE.Required (m_lSENTENCESYLLABLE.Num() + dwNum);
   for (i = dwStartIndex; i < dwEndIndex; i++) {
      m_lPOSStress.Add (pssAppend->m_pabPOSStress + i);
      m_lSylIndex.Add (pssAppend->m_pabSylIndex + i);
      m_lRuleDepth.Add (pssAppend->m_pabRuleDepth + i);
      m_lSENTENCESYLLABLE.Add (pssAppend->m_paSyl + i);
   } // i

   // append the parses
   PSENTSYLPHRASE pSSP = (PSENTSYLPHRASE) pssAppend->m_lSENTSYLPHRASE.Get(0);
   SENTSYLPHRASE SSP;
   for (i = 0; i < pssAppend->m_lSENTSYLPHRASE.Num(); i++, pSSP++) {
      if ( ((DWORD)pSSP->wStart < dwStartIndex) || ((DWORD)pSSP->wEnd > dwEndIndex) )
         continue;   // not in part that appended

      SSP = *pSSP;
      SSP.wStart = SSP.wStart - (WORD)pSSP->wStart + m_dwNum;
      SSP.wEnd = SSP.wEnd - (WORD)pSSP->wStart + m_dwNum;

      m_lSENTSYLPHRASE.Add (&SSP);
   }

   m_dwNum = m_lSENTENCESYLLABLE.Num();
   m_pabPOSStress = (PBYTE)m_lPOSStress.Get(0);
   m_pabSylIndex = (PBYTE)m_lSylIndex.Get(0);
   m_pabRuleDepth = (PBYTE)m_lRuleDepth.Get(0);
   m_paSyl = (PSENTENCESYLLABLE) m_lSENTENCESYLLABLE.Get(0);

   return TRUE;
}


/*************************************************************************************
CSentenceSyllable::PARSERULEDEPTHToSENTSYLPHRASE - This internal method is recurve.
It takes a list of PARSERULEDEPTH structures, and uses them to figure out where
the phrases in the sentence begin and end.

inputs
   PPARSERULEDEPTH         pPRD - Array of PARSERULEDEPTH, one for each syllable already in the sentnece
   DWORD                   dwNum - Number of syllables in entire sentence. Won't ever add entire sentence as a rule
                              just to reduce memory storage
   DWORD                   dwStart - Start parsing at syllable number. Initially pass in 0.
   DWORD                   dwEnd - Finish parsing at syllable number (exclusive). Initially pass in number of elements in the sentence
   PCListFixed             plSENTSYLPHRASE - List initialized to sizeof SENTSYLPHRASE.
                              Whenever a new phrase is found (that's unique),
                              the phrase is added.
returns
   none
*/
void CSentenceSyllable::PARSERULEDEPTHToSENTSYLPHRASE (PPARSERULEDEPTH pPRD, DWORD dwNum, DWORD dwStart, DWORD dwEnd, PCListFixed plSENTSYLPHRASE)
{
   // if no entries then exit, or if only one, then don't add
   if (dwStart + 2 > dwEnd)
      return;

   // always add this, so long as not entire sentence
   DWORD i;
   if (dwStart || (dwEnd != dwNum)) {
      SENTSYLPHRASE SSP;
      SSP.wStart = (WORD)dwStart;
      SSP.wEnd = (WORD)dwEnd;

      // make sure no match
      PSENTSYLPHRASE pSSP = (PSENTSYLPHRASE)plSENTSYLPHRASE->Get(0);
      for (i = 0; i < plSENTSYLPHRASE->Num(); i++, pSSP++)
         if (!memcmp (pSSP, &SSP, sizeof(SSP)))
            return;  // already there. shouldnt happen

      // add it
      plSENTSYLPHRASE->Add (&SSP);
   }

   // find the minimum and maximum depths
   BYTE bMinDepth = 255, bMaxDepth = 0;
   for (i = dwStart; i < dwEnd; i++) {
      // minimum
      bMinDepth = min(bMinDepth, pPRD[i].bAfter);
      bMinDepth = min(bMinDepth, pPRD[i].bBefore);
      bMinDepth = min(bMinDepth, pPRD[i].bDuring);

      // maximum
      bMaxDepth = max(bMaxDepth, pPRD[i].bAfter);
      bMaxDepth = max(bMaxDepth, pPRD[i].bBefore);
      bMaxDepth = max(bMaxDepth, pPRD[i].bDuring);
   }

   // loop from minimum depth to maximum, looking for portions
   // of the syllable that are above water
   for (; bMinDepth < bMaxDepth; bMinDepth++) {

      // figure out where starts breaking the water
      DWORD dwStartSub;
      DWORD dwEndSub = dwStart;
      BOOL fAdded = FALSE;
      while (dwEndSub < dwEnd) {
         for (dwStartSub = dwEndSub; dwStartSub < dwEnd; dwStartSub++)
            if (pPRD[dwStartSub].bDuring > bMinDepth)
               break;
         if (dwStartSub >= dwEnd)
            break;   // never breaks the water

         // find out where this ends
         for (dwEndSub = dwStartSub + 1; dwEndSub < dwEnd; dwEndSub++) {
            // if after had sunk down then ends
            if (dwEndSub && pPRD[dwEndSub-1].bAfter <= bMinDepth)
               break;   // ends here

            // if beginning is below then end... shouldnt happen
            if (pPRD[dwEndSub].bBefore <= bMinDepth)
               break;
         } // dwEndSub

         // if have selected entire sequence, then don't bother because
         // already did this (since recursing)
         if ((dwStartSub <= dwStart) && (dwEndSub >= dwEnd))
            break;

         // remember that found at least one sequence above water
         fAdded = TRUE;

         // make sure that there are at least two words in this
         DWORD dwWords = 0;
         _ASSERTE (IsStartOfWord(dwStartSub));
         for (i = dwStartSub; i < dwEndSub; i++)
            if (IsStartOfWord (i))
               dwWords++;
         if (dwWords < 2)
            continue;   // shouldnt have gotten this, but appears to be only one word here

         // recurse and add this
         PARSERULEDEPTHToSENTSYLPHRASE (pPRD, dwNum, dwStartSub, dwEndSub, plSENTSYLPHRASE);

         // continue after this end and see if any other rules to add
      } // while haven't come to the end

      // if added anything then done
      if (fAdded)
         break;
      // otherwise, increase bMinDepth and continue

   } // bMinDepth

   // done
}


/*************************************************************************************
CSentenceSyllable::SetPARSERULEDEPTH - This takes an array of PARSERULEDEPTH
and uses them to set m_lSENTSYLPHRASE for the sentence. Call this AFTER all the
syllables have been added.

inputs
   PPARSERULEDEPTH      pPRD - Array of rule depths, one per syllable
   DWORD                dwNum - Number of rule-depth elements
returns
   BOOL - TRUE if success. Fail if dwNum != m_dwNum
*/
BOOL CSentenceSyllable::SetPARSERULEDEPTH (PPARSERULEDEPTH pPRD, DWORD dwNum)
{
   if (dwNum != m_dwNum)
      return FALSE;

   // clear out existing list
   m_lSENTSYLPHRASE.Init (sizeof(SENTSYLPHRASE));

   // recurse and add
   PARSERULEDEPTHToSENTSYLPHRASE (pPRD, dwNum, 0, dwNum, &m_lSENTSYLPHRASE);

   return TRUE;
}


/*************************************************************************************
CSentenceSyllable::EnumWords - This fills in a list with all the words, their
starts, and their lengths in syllables.

inputs
   PCListFixed       plSSENUMWORDS - Initialized and filled in with one SSENUMWORDS
                     entry per word, in sequence
   PCListFixed       plRemap - Can be NULL. Intiialized and filled with one DWORD
                     per syllable. Each entry is filled with the word number.
returns
   none
*/
void CSentenceSyllable::EnumWords (PCListFixed plSSENUMWORDS, PCListFixed plRemap)
{
   plSSENUMWORDS->Init (sizeof(SSENUMWORDS));
   if (plRemap)
      plRemap->Init (sizeof(DWORD));

   DWORD i;
   SSENUMWORDS sew;
   PSSENUMWORDS pSEW;
   DWORD dwNumWords;
   memset (&sew, 0, sizeof(sew));
   for (i = 0; i < m_dwNum; i++) {
      // first one is always the start of a word, or text
      if (!i || IsStartOfWord(i)) {
         // add the entry
         sew.dwStart = i;
         sew.dwLength = 1;
         plSSENUMWORDS->Add (&sew);
      }
      else {
         // else, it's a continuation
         pSEW = (PSSENUMWORDS)plSSENUMWORDS->Get(plSSENUMWORDS->Num()-1);
         pSEW->dwLength++;
      }

         // add the remap
      if (plRemap) {
         dwNumWords = plSSENUMWORDS->Num()-1;
         plRemap->Add (&dwNumWords);
      }

   } // i
}


/*************************************************************************************
CSentenceSyllable::ToWords - Converts a sentence composed of syllables into
a sentence with one "syllable" entry per word.

NOTE: Because m_pabRuleDepth only stores 2 bits for the number of phonemes,
the high 4 bits of m_paSyl->bPauseProb will contain bits 2-6 of the number
of phonemes in the word.

returns
   PCSentenceSyllable - Word-only syllable. NULL if error
*/
CSentenceSyllable *CSentenceSyllable::ToWords (void)
{
   PCSentenceSyllable pSS = Clone();
   if (!pSS)
      return NULL;

   // enumerate the words and their mapping
   CListFixed lSSENUMWORDS, lRemap;
   EnumWords (&lSSENUMWORDS, &lRemap);

   // combine syllables together
   DWORD dwWord, dwSyl, dwCurSyl;
   PSSENUMWORDS pSEW = (PSSENUMWORDS)lSSENUMWORDS.Get(0);
   for (dwWord = 0; dwWord < lSSENUMWORDS.Num(); dwWord++, pSEW++) {
      DWORD dwPhonemesTotal = 0;

      // copy some bits over
      dwCurSyl = pSEW->dwStart;
      pSS->m_pabPOSStress[dwWord] = m_pabPOSStress[dwCurSyl] & 0x0f; // only keep POS. Toss out stress and syllable number
      pSS->m_pabSylIndex[dwWord] = 0;  // toss out syllable number
      pSS->m_pabRuleDepth[dwWord] = m_pabRuleDepth[dwCurSyl];  // keep all, but will change nubmer of syllables later
      pSS->m_paSyl[dwWord] = m_paSyl[dwCurSyl];

      // average a lot
      fp fWeightSum = 0.0;
      fp fPitchF0Fuji = 0.0, fVol = 0.0, fDurPhone = 0.0, fDurSyl = 0.0, fDurSkew = 0.0, fPitchF0FujiDelta = 0.0;
      fp fAccent = 0.0;
      fp afPitchF0Extra[SSPITCHPOINTS];
      memset (afPitchF0Extra, 0, sizeof(afPitchF0Extra));

      PSENTENCESYLLABLE pSyl = &m_paSyl[dwCurSyl];
      for (dwSyl = 0; dwSyl < pSEW->dwLength; dwSyl++, dwCurSyl++, pSyl++) {
         DWORD dwPhonemesInThis = ((m_pabRuleDepth[dwCurSyl] >> 4) & 0x03) + 1;
         dwPhonemesTotal += dwPhonemesInThis;

         // weight
         fp fWeight = (fp)dwPhonemesInThis;
         fWeightSum += fWeight;
         DWORD dwPitchPoint;
         for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
            afPitchF0Extra[dwPitchPoint] += (fp)pSyl->acPitchF0Extra[dwPitchPoint] * fWeight;
         fPitchF0Fuji += log((fp)pSyl->bPitchF0Fuji) / log(2.0) * fWeight;
         fPitchF0FujiDelta += (fp)pSyl[- (int)dwSyl].cPitchF0FujiDelta * fWeight;
               // NOTE: Only using the fuji delta of the first syllable
         // fPitchSweep += (fp)pSyl->cPitchSweep * fWeight;
         // fPitchBulge += (fp)pSyl->cPitchBulge * fWeight;
         fVol += log((fp)pSyl->bVol) / log(2.0) * fWeight;
         fAccent = (fp)pSyl->cAccent * fWeight;
         fDurPhone += log((fp)pSyl->bDurPhone) / log(2.0) * fWeight;
         fDurSyl += log((fp)pSyl->bDurSyl) / log(2.0) * fWeight;
         fDurSkew += (fp)pSyl->cDurSkew * fWeight;
      } // dwSyl

      // Store back
      pSyl = &pSS->m_paSyl[dwWord];
      fWeightSum = 1.0 / fWeightSum;

      DWORD dwPitchPoint;
      for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++) {
         afPitchF0Extra[dwPitchPoint] = afPitchF0Extra[dwPitchPoint] * fWeightSum + 0.5;
         afPitchF0Extra[dwPitchPoint] = max(afPitchF0Extra[dwPitchPoint], -128.0);
         afPitchF0Extra[dwPitchPoint] = min(afPitchF0Extra[dwPitchPoint], 127.0);
         pSyl->acPitchF0Extra[dwPitchPoint] = (char)floor(afPitchF0Extra[dwPitchPoint]);
      }

      fPitchF0Fuji = pow(2.0, fPitchF0Fuji * fWeightSum) + 0.5;
      fPitchF0Fuji = max(fPitchF0Fuji, 0.0);
      fPitchF0Fuji = min(fPitchF0Fuji, 255.0);
      pSyl->bPitchF0Fuji = (BYTE)fPitchF0Fuji;

      fPitchF0FujiDelta = fPitchF0FujiDelta * fWeightSum + 0.5;
      fPitchF0FujiDelta = max(fPitchF0FujiDelta, -128.0);
      fPitchF0FujiDelta = min(fPitchF0FujiDelta, 127.0);
      pSyl->cPitchF0FujiDelta = (char)floor(fPitchF0FujiDelta);

      fVol = pow(2.0, fVol * fWeightSum) + 0.5;
      fVol = max(fVol, 0.0);
      fVol = min(fVol, 255.0);
      pSyl->bVol = (BYTE)fVol;

      fAccent = fAccent * fWeightSum + 0.5;
      fAccent = max(fAccent, -128.0);
      fAccent = min(fAccent, 127.0);
      pSyl->cAccent = (char)floor(fAccent);


      fDurPhone = pow(2.0, fDurPhone * fWeightSum) + 0.5;
      fDurPhone = max(fDurPhone, 0.0);
      fDurPhone = min(fDurPhone, 255.0);
      pSyl->bDurPhone = (BYTE)fDurPhone;

      fDurSyl = pow(2.0, fDurSyl * fWeightSum) + 0.5;
      fDurSyl = max(fDurSyl, 0.0);
      fDurSyl = min(fDurSyl, 255.0);
      pSyl->bDurSyl = (BYTE)fDurSyl;

      //fPitchSweep = fPitchSweep * fWeightSum + 0.5;
      //fPitchSweep = max(fPitchSweep, -128.0);
      //fPitchSweep = min(fPitchSweep, 127.0);
      //pSyl->cPitchSweep = (char)floor(fPitchSweep);

      //fPitchBulge = fPitchBulge * fWeightSum + 0.5;
      //fPitchBulge = max(fPitchBulge, -128.0);
      //fPitchBulge = min(fPitchBulge, 127.0);
      //pSyl->cPitchBulge = (char)floor(fPitchBulge);

      fDurSkew = fDurSkew * fWeightSum + 0.5;
      fDurSkew = max(fDurSkew, -128.0);
      fDurSkew = min(fDurSkew, 127.0);
      pSyl->cDurSkew = (char)floor(fDurSkew);

      // write out the number of phonemes
      dwPhonemesTotal--;   // so 0-based
      pSS->m_pabRuleDepth[dwWord] = (pSS->m_pabRuleDepth[dwWord] & 0xcf) | ((dwPhonemesTotal & 0x03) << 4);
      pSS->m_paSyl[dwWord].bPauseProb = (pSS->m_paSyl[dwWord].bPauseProb & 0x0f) | ((dwPhonemesTotal >> 2) << 4);
   } // dwWord

   // tuncate the list
   pSS->m_dwNum = lSSENUMWORDS.Num();
   pSS->m_lPOSStress.Truncate (pSS->m_dwNum);
   pSS->m_lSylIndex.Truncate (pSS->m_dwNum);
   pSS->m_lRuleDepth.Truncate (pSS->m_dwNum);
   pSS->m_lSENTENCESYLLABLE.Truncate (pSS->m_dwNum);
   // just in case was moved
   pSS->m_pabPOSStress = (PBYTE)pSS->m_lPOSStress.Get(0);
   pSS->m_pabSylIndex = (PBYTE)pSS->m_lSylIndex.Get(0);
   pSS->m_pabRuleDepth = (PBYTE)pSS->m_lRuleDepth.Get(0);
   pSS->m_paSyl = (PSENTENCESYLLABLE) pSS->m_lSENTENCESYLLABLE.Get(0);

   // fix phrase markings
   PSENTSYLPHRASE pSSP = (PSENTSYLPHRASE) pSS->m_lSENTSYLPHRASE.Get(0);
   DWORD *padwRemap = (DWORD*)lRemap.Get(0);
   DWORD i;
   for (i = 0; i < pSS->m_lSENTSYLPHRASE.Num(); i++, pSSP++) {
      pSSP->wStart = (WORD)padwRemap[pSSP->wStart];
      pSSP->wEnd = (WORD)padwRemap[pSSP->wEnd];
   }

   return pSS;
}


/*************************************************************************************
CSentenceSyllable::FromWords - Call this on a sentence made of words (from ToWords()),
to convert it back to syllables.

inputs
   PCSentenceSyllable   pOrig - Original sentence, which still has all the syllables.
returns
   PCSentenceSyllable - Copy of this converted back to syllables (from one "syllable" per word)
*/
CSentenceSyllable *CSentenceSyllable::FromWords (CSentenceSyllable *pOrig)
{
   // clone the original and just copy prosody info
   PCSentenceSyllable pSS = pOrig->Clone();
   if (!pSS)
      return NULL;

   // enumerate the words and their mapping
   CListFixed lSSENUMWORDS;
   pSS->EnumWords (&lSSENUMWORDS, NULL);

   // make sure match up. Shouldnt happen
   if (lSSENUMWORDS.Num() != m_dwNum) {
      delete pSS;
      return NULL;
   }

   PSSENUMWORDS pSEW = (PSSENUMWORDS) lSSENUMWORDS.Get(0);
   DWORD dwWord, dwCurSyl, dwSyl;
   for (dwWord = 0; dwWord < lSSENUMWORDS.Num(); dwWord++, pSEW++) {
      dwCurSyl = pSEW->dwStart;

      // copy the syllables in this word back
      PSENTENCESYLLABLE pSylSS = &pSS->m_paSyl[dwCurSyl];
      PSENTENCESYLLABLE pSylThis = &m_paSyl[dwWord];
      for (dwSyl = 0; dwSyl < pSEW->dwLength; dwSyl++, dwCurSyl++, pSylSS++) {
         pSylSS->bDurPhone = pSylThis->bDurPhone;
         pSylSS->bDurSyl = pSylThis->bDurSyl;
         pSylSS->bPauseProb = pSylThis->bPauseProb & 0x0f;  // 0x0f gets rid of high nibble with number of syllables in it
         memcpy (pSylSS->acPitchF0Extra, pSylThis->acPitchF0Extra, sizeof(pSylSS->acPitchF0Extra));
         // pSylSS->bPitchF0Extra = pSylThis->bPitchF0Extra;
         pSylSS->bPitchF0Fuji = pSylThis->bPitchF0Fuji;
         pSylSS->cPitchF0FujiDelta = dwSyl ? 0 : pSylThis->cPitchF0FujiDelta;
            // NOTE: Only keeping fuju delta for first syllable
            // BUGFIX - Had used pSylSS->cPitchF0FujiDelta instead of pSylThis->cPitchF0FujiDelta
         pSylSS->bVol = pSylThis->bVol;
         pSylSS->cAccent = pSylThis->cAccent;
         pSylSS->cDurSkew = pSylThis->cDurSkew;
         // pSylSS->cPitchBulge = pSylThis->cPitchBulge;
         // pSylSS->cPitchSweep = pSylThis->cPitchSweep;
      } // dwSyl, in the word
   } // dwWord

   // done
   return pSS;
}

/*************************************************************************************
CSentenceSyllable::IsStartOfWord - Given an index, see if at the start of a word

inputs
   DWORD    dwIndex- Index from 0 .. m_dwNum-1
returns
   BOOL - TRUE if at start
*/
BOOL CSentenceSyllable::IsStartOfWord (DWORD dwIndex)
{
   // if first
   if (!dwIndex)
      return TRUE;

   // syllable 0
   if (m_pabSylIndex[dwIndex] & 0x7)
      return FALSE;
   else
      return TRUE;

#if 0 // not needed
   // if POS changed
   if ((m_pabPOS[dwIndex-1] & 0x0f) != (m_pabPOS[dwIndex] & 0x0f))
      return TRUE;

   // if any of the high-bits of rule depth info changed
   if ((m_pabRuleDepth[dwIndex-1] & 0xc0) != (m_pabRuleDepth[dwIndex] & 0xc0))
      return TRUE;

   // if words changed
   if (m_paSyl[dwIndex-1].wWord != m_paSyl[dwIndex].wWord)
      return TRUE;

   // NOTE: A series of two-syllable words with the same POS, same rule depth, could get through

   // else, not
   return FALSE;
#endif
}

/*************************************************************************************
CSentenceSyllable::CloneTo - Standard api
*/
BOOL CSentenceSyllable::CloneTo (CSentenceSyllable *pTo)
{
   pTo->m_lPOSStress.Init (sizeof(BYTE), m_lPOSStress.Get(0), m_lPOSStress.Num());
   pTo->m_lSylIndex.Init (sizeof(BYTE), m_lSylIndex.Get(0), m_lSylIndex.Num());
   pTo->m_lRuleDepth.Init (sizeof(BYTE), m_lRuleDepth.Get(0), m_lRuleDepth.Num());
   pTo->m_lSENTENCESYLLABLE.Init (sizeof(SENTENCESYLLABLE), m_lSENTENCESYLLABLE.Get(0), m_lSENTENCESYLLABLE.Num());
   pTo->m_lSENTSYLPHRASE.Init (sizeof(SENTSYLPHRASE), m_lSENTSYLPHRASE.Get(0), m_lSENTSYLPHRASE.Num());

   pTo->m_dwNum = pTo->m_lSENTENCESYLLABLE.Num();
   pTo->m_pabPOSStress = (PBYTE)pTo->m_lPOSStress.Get(0);
   pTo->m_pabSylIndex = (PBYTE)pTo->m_lSylIndex.Get(0);
   pTo->m_pabRuleDepth = (PBYTE)pTo->m_lRuleDepth.Get(0);
   pTo->m_paSyl = (PSENTENCESYLLABLE) pTo->m_lSENTENCESYLLABLE.Get(0);

   return TRUE;
}


/*************************************************************************************
CSentenceSyllable::Clone - Standard api
*/
CSentenceSyllable *CSentenceSyllable::Clone (void)
{
   PCSentenceSyllable pNew = new CSentenceSyllable;
   if (!pNew)
      return NULL;

   if (!CloneTo (pNew)) {
      delete pNew;
      return NULL;
   }

   return pNew;
}


/*************************************************************************************
CSentenceSyllable::RemapWords - Loop through all the word IDs and remap them
from one lexicon to the next. This is used when transferring the prosody model
from one TTS voice to the next

inputs
   PCMLexicon        pLexOrig - Lexicon that the word IDs currently come from
   PCMLexicon        pLexNew - New lexicon to use
   BOOL              fAddIfNotExist - If TRUE then add the word to pLexNew if
                     it doesn't exist.
   PCListFixed       plPCSentenceSyllable - If this is not NULL, then all the
                     sentence syllables will have their word ID's increased
                     if they're >= the added word. Ignored if !fAddIfNotExist.
returns
   none
*/
void CSentenceSyllable::RemapWords (PCMLexicon pLexOrig, PCMLexicon pLexNew,
                                    BOOL fAddIfNotExist, PCListFixed plPCSentenceSyllable)
{
   PSENTENCESYLLABLE pss = (PSENTENCESYLLABLE) m_lSENTENCESYLLABLE.Get(0);
   PSENTENCESYLLABLE pssOrig = pss;
   DWORD i, j, k;
   WCHAR szWord[256];
   CListVariable lForm;
   for (i = 0; i < m_lSENTENCESYLLABLE.Num(); i++, pss++) {
      if (pss->wWord == (WORD)-1)
         continue;   // nothing to remap

      if (!pLexOrig->WordGet (pss->wWord, szWord, sizeof(szWord), NULL)) {
         // cant find word in original, which shouldnt happen
         pss->wWord = (WORD)-1;
         continue;
      }

      // find it in the new one
      DWORD dwWord = pLexNew->WordFind (szWord);
      pss->wWord = (dwWord < 0xffff) ? (WORD)dwWord : (WORD)-1;

      if (pss->wWord != (WORD) -1)
         continue;

      // consider adding
      if (!fAddIfNotExist)
         continue;
      pLexNew->WordSet (szWord, &lForm);
      dwWord = pLexNew->WordFind (szWord);
      pss->wWord = (dwWord < 0xffff) ? (WORD)dwWord : (WORD)-1;

      // modify in this sentence
      for (k = 0; k < i; k++)
         if ((pssOrig[k].wWord != (WORD)-1) && (pssOrig[k].wWord >= pss->wWord))
            pssOrig[k].wWord++;  // since just inserted before it

      // modify in sentence syllable
      if (plPCSentenceSyllable) {
         PCSentenceSyllable *ppss = (PCSentenceSyllable*)plPCSentenceSyllable->Get(0);
         for (j = 0; j < plPCSentenceSyllable->Num(); j++) {
            PCSentenceSyllable pss2 = ppss[j];

            for (k = 0; k < pss2->m_dwNum; k++)
               if ((pss2->m_paSyl[k].wWord != (WORD)-1) && (pss2->m_paSyl[k].wWord >= pss->wWord))
                  pss2->m_paSyl[k].wWord++;  // since just inserted before it
         } // j
      } // if plPCSentenceSyllable
   } // i
}


/*************************************************************************************
CSentenceSyllable::PhoneGroupBitsCalc - Calculte the phone group bits.

inputs
   PBYTE          pabPhone - Array of phoneme numbers for the phonemes in the syllable.
   DWORD          dwNum - Number of phonemes
   PCMLexicon     pLex - Lexicon to use
returns
   DWORD - Value to pass into CSentenceSyllable::Add ()
*/
DWORD CSentenceSyllable::PhoneGroupBitsCalc (PBYTE pabPhone, DWORD dwNum, PCMLexicon pLex)
{
   DWORD wRet = 0;
   DWORD i;
   for (i = 0; i < dwNum; i++, pabPhone++) {
      PLEXPHONE plp = pLex->PhonemeGetUnsort (*pabPhone);
      if (!plp)
         continue;   // shouldnt happen

      PLEXENGLISHPHONE ple = MLexiconEnglishPhoneGet(plp->bEnglishPhone);
      if (!ple)
         continue;

      DWORD dwGroup = PIS_FROMPHONEGROUP(ple->dwShape);
      if (!dwGroup)
         continue;   // since silence
      dwGroup--;

      // set bit
      wRet |= (1 << dwGroup);
   } // i

   return wRet;
}

/*************************************************************************************
CSentenceSyllable::ReplaceUnknownPOSWithNoun - Loop through the sentence syllable
and replace any unknown parts of speech with "noun" parts of speech.
*/
void CSentenceSyllable::ReplaceUnknownPOSWithNoun (void)
{
   DWORD i;
   PBYTE pabPOSStress = m_pabPOSStress;
   for (i = 0; i < m_dwNum; i++, pabPOSStress++) {
      if ((pabPOSStress[0] & 0x0f) == POS_MAJOR_EXTRACT(POS_MAJOR_UNKNOWN))
         pabPOSStress[0] = (pabPOSStress[0] & 0xf0) | POS_MAJOR_EXTRACT(POS_MAJOR_NOUN);
   } // i
}


/*************************************************************************************
CSentenceSyllable::Remove - Removes syllables from start to end.

inputs
   DWORD          dwStart - Start
   DWORD          dwEnd - end
*/
BOOL CSentenceSyllable::Remove (DWORD dwStart, DWORD dwEnd)
{
   if ((dwEnd > m_dwNum) || (dwStart > dwEnd))
      return FALSE;

   DWORD i;
   for (i = dwStart; i < dwEnd; i++) {
      m_lSENTENCESYLLABLE.Remove (dwStart);  // NOTE: Always removing dwStart
      m_lPOSStress.Remove (dwStart);
      m_lSylIndex.Remove (dwStart);
      m_lRuleDepth.Remove (dwStart);
   }

   m_dwNum = m_lSENTENCESYLLABLE.Num();
   m_pabPOSStress = (PBYTE)m_lPOSStress.Get(0);
   m_pabSylIndex = (PBYTE)m_lSylIndex.Get(0);
   m_pabRuleDepth = (PBYTE)m_lRuleDepth.Get(0);
   m_paSyl = (PSENTENCESYLLABLE) m_lSENTENCESYLLABLE.Get(0);

   return TRUE;
}

/*************************************************************************************
CSentenceSyllable::Add - Adds a new syllable onto the end of the sentence.

inputs
   DWORD          dwWord - Word index, into internal lexicon of words in the trianing
                           set. If the word isn't there, or is unknown, then use -1.
   BYTE           bPOSStress - Part of speech, in the low nibble.
   BYTE           bSylIndex - Low 3 bits are the syllable index
   BYTE           bRuleDepth - Rule depth in low nibble, as set of two 2-bit values.
   PSENTSYLEMPH   pEmph - Emphasis information.
   BYTE           bStress - Stress number, from 0+ for the phoneme
   // DWORD          dwSyllable - Syllable number. 0 = 1st syllable, 1 = 2nd, etc.
   DWORD          dwPhonemes - Number of phonemes in the syllable
   DWORD          dwWordRank - Word rank, in how common. 0 is the most common, 3 is the least.
                                       2 is used for a normally-3 word that was recently spoken
   DWORD          dwPauseProb - Probability of a pause before. 0 (none) to 15 (100%).
                                 Should have subtracted the micropause probability from this
   DWORD          dwPhonemeGroupBits - From PhoneGroupBitsCalc()
returns
   BOOL - TRUE if success
*/
BOOL CSentenceSyllable::Add (DWORD dwWord, BYTE bPOSStress, BYTE bSylIndex, BYTE bRuleDepth, PSENTSYLEMPH pEmph, BYTE bStress,
                             DWORD dwPhonemes, DWORD dwWordRank, DWORD dwPauseProb,
                             DWORD dwPhonemeGroupBits)
{
   // BUGFIX - was only going stressed in high bit... but instead store syllable inforamation
   // too.
   bPOSStress = (bPOSStress & 0x0f) | (bStress << 4);
   m_lPOSStress.Add (&bPOSStress);
   bSylIndex = (BYTE)bSylIndex & 0x07;
   m_lSylIndex.Add (&bSylIndex);
   if (dwPhonemes)
      dwPhonemes--;  // since everything should have at least one phoneme
   dwPhonemes = min(dwPhonemes, 3); // to bits
   dwWordRank = min(dwWordRank, 3); // to make sure
   bRuleDepth = (bRuleDepth & 0x0f) | ((BYTE)dwPhonemes << 4) | ((BYTE)dwWordRank << 6);
   m_lRuleDepth.Add (&bRuleDepth);

   SENTENCESYLLABLE ss;
   fp f;
   memset (&ss, 0, sizeof(ss));
   ss.wWord = (dwWord < 0xffff) ? (WORD)dwWord : (WORD)-1;
   ss.wPhoneGroupBits = (WORD)dwPhonemeGroupBits;
   ss.bPauseProb = (BYTE)dwPauseProb;

   // pitch
   DWORD dwPitchPoint;
   for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++) {
      f = pEmph->afPitchF0Extra[dwPitchPoint] * 100.0;
      f = max(f, -120);
      f = min(f, 120);
      ss.acPitchF0Extra[dwPitchPoint] = (char)(int)floor(f+0.5);
   }

   // pitch
   f = pEmph->fPitchF0Fuji * 100.0;
   f = max(f, 50);
   f = min(f, 200);
   ss.bPitchF0Fuji = (BYTE)(f+0.5);

   // fuju delta
   f = pEmph->fPitchF0FujiDelta * 100.0;
   f = max(f, -120);
   f = min(f, 120);
   ss.cPitchF0FujiDelta = (char)(int)floor(f+0.5);

   // pitch sweep
   //f = pEmph->fPitchSweep * 100.0;
   //f = max(f, -120);
   //f = min(f, 120);
   //ss.cPitchSweep = (char)(int)floor(f+0.5);

   // pitch bulge
   //f = pEmph->fPitchBulge * 100.0;
   //f = max(f, -120);
   //f = min(f, 120);
   //ss.cPitchBulge = (char)(int)floor(f+0.5);

   // volume
   f = pEmph->fVolume * 100.0;
   f = max(f, 50);
   f = min(f, 200);
   ss.bVol = (BYTE)(f+0.5);

   // accent
   f = pEmph->fAccent * 100.0;
   f = max(f, -120);
   f = min(f, 120);
   ss.cAccent = (char)(int)floor(f+0.5);

   // duration phone
   f = pEmph->fDurPhone * 100.0;
   f = max(f, 50);
   f = min(f, 200);
   ss.bDurPhone = (BYTE)(f+0.5);

   // duration, syllable
   f = pEmph->fDurSyl * 100.0;
   f = max(f, 50);
   f = min(f, 200);
   ss.bDurSyl = (BYTE)(f+0.5);

   // pitch sweep
   f = pEmph->fDurSkew * 100.0;
   f = max(f, -120);
   f = min(f, 120);
   ss.cDurSkew = (char)(int)floor(f+0.5);

   m_lSENTENCESYLLABLE.Add (&ss);

   m_dwNum = m_lSENTENCESYLLABLE.Num();
   m_pabPOSStress = (PBYTE)m_lPOSStress.Get(0);
   m_pabSylIndex = (PBYTE)m_lSylIndex.Get(0);
   m_pabRuleDepth = (PBYTE)m_lRuleDepth.Get(0);
   m_paSyl = (PSENTENCESYLLABLE) m_lSENTENCESYLLABLE.Get(0);

   return TRUE;
}


/*************************************************************************************
CSentenceSyllable::MMLToBinary - Converts this syllable information into binary
form and appends it onto the end of pMem. pMem's m_dwCurPosn is updated to include
the new memory
*/
BOOL CSentenceSyllable::MMLToBinary (PCMem pMem)
{
   // how much need
   SENTSYLHEADER SSH;
   SSH.dwNum = m_dwNum;
   SSH.dwPhrases = m_lSENTSYLPHRASE.Num();
   size_t dwNeed = sizeof(SSH) + m_dwNum * (sizeof(BYTE) + sizeof(BYTE) + sizeof(BYTE) + sizeof(SENTENCESYLLABLE)) +
      sizeof(SENTSYLPHRASE) * SSH.dwPhrases +
      pMem->m_dwCurPosn;
   if (!pMem->Required (dwNeed))
      return FALSE;

   // info
   PBYTE pbCur = (PBYTE)pMem->p + pMem->m_dwCurPosn;
   pMem->m_dwCurPosn = dwNeed;

   // copy number of syllables
   memcpy (pbCur, &SSH, sizeof(SSH));
   pbCur += sizeof(SSH);

   // copy syllable POS
   memcpy (pbCur, m_pabPOSStress, m_dwNum * sizeof(BYTE));
   pbCur += m_dwNum * sizeof(BYTE);

   // copy syllable sylindex
   memcpy (pbCur, m_pabSylIndex, m_dwNum * sizeof(BYTE));
   pbCur += m_dwNum * sizeof(BYTE);

   // copy syllable rule depth
   memcpy (pbCur, m_pabRuleDepth, m_dwNum * sizeof(BYTE));
   pbCur += m_dwNum * sizeof(BYTE);

   // copy syllable details
   memcpy (pbCur, m_paSyl, m_dwNum * sizeof(SENTENCESYLLABLE));
   pbCur += m_dwNum * sizeof(SENTENCESYLLABLE);

   // copy the phrases
   memcpy (pbCur, m_lSENTSYLPHRASE.Get(0), SSH.dwPhrases * sizeof(SENTSYLPHRASE));

   // done
   return TRUE;
}


/*************************************************************************************
CSentenceSyllable::MMLFromBinary - Reads in a sentence syllable from binary memory

inputs
   PBYTE       pabMem - Memory to read from
   DWORD       dwLeft - Number of bytes left in the memory
returns
   DWORD - Number of bytes actually used, or 0 if error
*/
size_t CSentenceSyllable::MMLFromBinary (PBYTE pabMem, size_t dwLeft)
{
   SENTSYLHEADER SSH;
   if (dwLeft < sizeof(SSH))
      return 0;

   memcpy (&SSH, pabMem, sizeof(SSH));
   pabMem += sizeof(SSH);
   dwLeft -= sizeof(SSH);
   DWORD dwUsed = sizeof(SSH);
   m_dwNum = SSH.dwNum;

   // make sure enough
   DWORD dwNeed = m_dwNum * (sizeof(BYTE) + sizeof(BYTE) + sizeof(BYTE) + sizeof(SENTENCESYLLABLE)) + SSH.dwPhrases * sizeof(SENTSYLPHRASE);
   if (dwLeft < dwNeed)
      return 0;
   dwUsed += dwNeed;

   // get the syllables
   m_lPOSStress.Init (sizeof(BYTE), pabMem, m_dwNum);
   pabMem += m_dwNum * sizeof(BYTE);
   m_lSylIndex.Init (sizeof(BYTE), pabMem, m_dwNum);
   pabMem += m_dwNum * sizeof(BYTE);
   m_lRuleDepth.Init (sizeof(BYTE), pabMem, m_dwNum);
   pabMem += m_dwNum * sizeof(BYTE);
   m_lSENTENCESYLLABLE.Init (sizeof(SENTENCESYLLABLE), pabMem, m_dwNum);
   pabMem += m_dwNum * sizeof(SENTENCESYLLABLE);
   m_lSENTSYLPHRASE.Init (sizeof(SENTSYLPHRASE), pabMem, SSH.dwPhrases);

   // update pointers
   m_pabPOSStress = (PBYTE)m_lPOSStress.Get(0);
   m_pabSylIndex = (PBYTE)m_lSylIndex.Get(0);
   m_pabRuleDepth = (PBYTE)m_lRuleDepth.Get(0);
   m_paSyl = (PSENTENCESYLLABLE) m_lSENTENCESYLLABLE.Get(0);

   return dwUsed;
}



/*************************************************************************************
CSentenceSyllable::AddToIndex - Method that causes the sentence to add
refernces to itself to an index of two sequential part-of-speech combinations (stressed
and unstressed). The items added to the index range from (-1,0) to (Num in sentence, Num
in sentence+1)

inputs
   PCMLexicon        pLexTTS - Lexicon to use
   PCListFixed       *ppaIndex - Pointer to anrray with [POS_MAJOR_NUMPLUSONE][MAXSTRESSES][POS_MAJOR_NUMPLUSONE][MAXSTRESSES] entries.
                              [POS of 1st element][Stress of 1st element][POS of 2nd element][Stress of 2nd element].
                              Each list should have been initialized to use a SENTENCEINDEX structure
returns
   BOOL - TRUE if success
*/
BOOL CSentenceSyllable::AddToIndex (PCMLexicon pLexTTS, PCListFixed *ppaIndex)
{
   int i, iCur;
   DWORD dwOffset;
   DWORD adwPOS[2];
   DWORD adwStress[2];
   SENTENCEINDEX si;
   memset (&si, 0, sizeof(si));
   // DWORD dwStresses = pLexTTS->Stresses();
   for (i = -1; i <= (int)m_dwNum; i++) { // intentionally using <=
      for (dwOffset = 0; dwOffset < 2; dwOffset++) {
         iCur = i + (int)dwOffset;

         if ((iCur < 0) || (iCur >= (int)m_dwNum)) {
            // out of range
            adwPOS[dwOffset] = POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION);
            adwStress[dwOffset] = 0;
         }
         else {
            adwPOS[dwOffset] = m_pabPOSStress[iCur] & 0x0f;
            adwStress[dwOffset] = (m_pabPOSStress[iCur] >> 4) & 0x0f;
         }
      } // dwOffset

      si.iStart = i;
      si.pSS = this;

      DWORD dwIndex = (adwPOS[0] * MAXSTRESSES + adwStress[0]) * (POS_MAJOR_NUMPLUSONE*MAXSTRESSES) +
         (adwPOS[1] * MAXSTRESSES + adwStress[1]);
      if (!ppaIndex[dwIndex]) {
         ppaIndex[dwIndex] = new CListFixed;
         if (!ppaIndex[dwIndex])
            return FALSE;  // error
         ppaIndex[dwIndex]->Init (sizeof(SENTENCEINDEX));
      }
      ppaIndex[dwIndex]->Add (&si);
   } // i

   return TRUE;
}


/*************************************************************************************
CSentenceSyllable::CompareRegion - This compares a region of this sentence with
a region of another and determines how close (acoustically) the sentences are to
one another. The more different, the higher the score.

The acoustic comparison uses pitch, duration, and volume.

inputs
   PCOMPARESYLINFO pInfo - Score penalties to use.
   int                  iOffsetThis - Offset into this
   CSentenceSyllable    *pTest - Sentence to compare to
   int                  iOffsetTest - Offset into test
   BOOL           fWord - If TRUE then the sentence is a list of words, FALSE then list of syllables.
                     If list of words, duration of the phoneme is gotten slightly differently
   WORD                 wPeriod - Word that indicates the punctuation
returns
   fp - Score, from 0 +. Low scores are better
*/

fp CSentenceSyllable::CompareRegion (PCOMPARESYLINFO pInfo, int iOffsetThis, CSentenceSyllable *pTest, int iOffsetTest, BOOL fWord, WORD wPeriod)
{
   fp fScore = 0;
   fp fCount = 0;
   // BYTE bPOSThis, bPOSTest;
   // PSENTENCESYLLABLE pssThis, pssTest;
   fp fErr, fWeightThis;
   int iCur, iCurThis, iCurTest;
   for (iCur = -COMPAREREGIONSIZE; iCur <= COMPAREREGIONSIZE; iCur++) {
      iCurThis = iOffsetThis + iCur;
      iCurTest = iOffsetTest + iCur;

      fErr = CompareSingleSyllable (pInfo, iCurThis, pTest, iCurTest, wPeriod, TRUE, FALSE, fWord);

#if 0 // old code
      // get values
      if ((iCurThis < 0) || (iCurThis >= (int)m_dwNum)) {
         bPOSThis = POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION);
         pssThis = NULL;
      }
      else {
         bPOSThis = m_pabPOS[iCurThis];
         pssThis = &m_paSyl[iCurThis];
      }
      if ((iCurTest < 0) || (iCurTest >= (int)pTest->m_dwNum)) {
               // BUGFIX - Was checking against m_dwNum, should have been pTest->m_dwNum
         bPOSTest = POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION);
         pssTest = NULL;
      }
      else {
         bPOSTest = pTest->m_pabPOS[iCurTest];
         pssTest = &pTest->m_paSyl[iCurTest];
      }

      // figure out the unweighted error
      if ((bPOSTest & 0x8f) != (bPOSThis & 0x8f))
         dwErr = MAXCOMPAREREGIONERR;  // maximum error
      else if ((bPOSThis & 0x0f) == POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION))
         dwErr = 0;  // assume no error
      else
         dwErr = CompareSENTENCESYLLABLE (pssThis, pssTest);
#endif // 0

      fWeightThis = COMPAREREGIONSIZE - (DWORD)abs(iCur) + 1;
      fCount += fWeightThis;
      fScore += fWeightThis * fErr;
   } // iCur

   return fScore / fWeightThis;
}

/*************************************************************************************
CSentenceSyllable::CompareSingleSyllable - This compares a single syllable
in this sentence against a single syllable in another.

NOTE: Assume that both the sentences are using the same lexicon.

inputs
   PCOMPARESYLINFO pInfo - Score penalties to use.
   int            iOffsetThis - Offset, in syllable, in this sentence. This can
                  be a negative number or >= the number of syllables, in which
                  case the last syllable is assumed to be the punctuation ('.', '!', '?')
                  for the sentence.
   PCSentenceSyllable pTest - Sentence to test agsint
   int            iOffsetTest - Offset into test, like iOffsetThis
   WORD           wPeriod - Word number for period, used as default punctiation
   BOOL           fIgnorePOS - If TRUE then ignore the part of speech mismatches
   BOOL           fIgnoreProsody - Set to TRUE if duration, energy, F0 are unknown in at least one of the setneces
   BOOL           fWord - If TRUE then the sentence is a list of words, FALSE then list of syllables.
                     If list of words, duration of the phoneme is gotten slightly differently

returns
   fp - Score. Lower is better. If this is >= COMPARESINGLESYLLABLE_NOMATCH
         then the syllables are different parts of speech, and not compared.
*/
// #define WORDMISMATCHPENALTY               2     // penalize for no word match
// #define PUNCTMISMATCHPENALTY              (WORDMISMATCHPENALTY*8)   // higher pentalty for punctuation mismatch
                                          // BUGFIX - Was *2, increase to *4 to encourage punctation match
                                          // BUGFIX - Was *4, increased to *8

fp CSentenceSyllable::CompareSingleSyllable (PCOMPARESYLINFO pInfo, int iOffsetThis, PCSentenceSyllable pTest,
                                              int iOffsetTest, WORD wPeriod, BOOL fIgnorePOS, BOOL fIgnoreProsody,
                                              BOOL fWord)
{
   // figure out parameters for this
   BYTE bPOSStressThis, bSylThis;
   WORD wWordThis;
   BYTE bDepthThis;
   PSENTENCESYLLABLE pSSThis;
   if ((iOffsetThis < 0) || (iOffsetThis >= (int)m_dwNum)) {
      // beyond the end of the sentences
      bPOSStressThis = POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION);
      bSylThis = 0;
      if (m_dwNum && ((m_pabPOSStress[m_dwNum-1] & 0x0f) == POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION)))
         wWordThis = m_paSyl[m_dwNum-1].wWord;
      else
         wWordThis = wPeriod;
      pSSThis = NULL;
      bDepthThis = 0;
   }
   else {
      bPOSStressThis = m_pabPOSStress[iOffsetThis];
      bSylThis = m_pabSylIndex[iOffsetThis] & 0x07;
      pSSThis = &m_paSyl[iOffsetThis];
      bDepthThis = m_pabRuleDepth[iOffsetThis];
      wWordThis = pSSThis->wWord;
   }

   // figure out the parameters for what comparing
   BYTE bPOSStressTest, bSylTest;
   WORD wWordTest;
   BYTE bDepthTest;
   PSENTENCESYLLABLE pSSTest;
   if ((iOffsetTest < 0) || (iOffsetTest >= (int)pTest->m_dwNum)) {
      // beyond the end of the sentences
      bPOSStressTest = POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION);
      bSylTest = 0;
      if (pTest->m_dwNum && ((pTest->m_pabPOSStress[pTest->m_dwNum-1] & 0x0f) == POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION)))
         wWordTest = pTest->m_paSyl[pTest->m_dwNum-1].wWord;
      else
         wWordTest = wPeriod;
      pSSTest = NULL;
      bDepthTest = 0;
   }
   else {
      bPOSStressTest = pTest->m_pabPOSStress[iOffsetTest];
      bSylTest = pTest->m_pabSylIndex[iOffsetTest];
      pSSTest = &pTest->m_paSyl[iOffsetTest];
      bDepthTest = pTest->m_pabRuleDepth[iOffsetTest];
      wWordTest = pSSTest->wWord;
   }

#if 0
   // BUGFIX - If low-nibble is punctation then make all punct
   if ((bPOSThis & 0x0f) == POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION))
      bPOSThis &= 0x0f;
   if ((bPOSTest & 0x0f) == POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION))
      bPOSTest &= 0x0f;
#endif // 0

   // BUGFIX - Put in asserts to double-check that have non-unknown POS for everything
   // disabling because dont need anymore
   // _ASSERTE (bPOSThis & 0x0f);
   // _ASSERTE (bPOSTest & 0x0f);

   // if syllable POS not the same, then max error
   if ((bPOSStressThis != bPOSStressTest) && (bSylThis != bSylTest) && !fIgnorePOS)
      return COMPARESINGLESYLLABLE_NOMATCH;

   // if it's just punctuation then simple test
   if ((bPOSStressTest == POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION)) && (bPOSStressThis == POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION))) {
      if (wWordThis == wWordTest)
         return 0;   // no error
      else
         return pInfo->fPunctMismatchPenalty; // since mismatched punctation
   }

   fp fError = 0.0;

   // word mismatch
   if ((wWordThis != wWordTest) || (wWordThis == (WORD)-1))
      fError += pInfo->fWordMismatchPenalty;


   // guestimate duration
   int iSylA, iSylB;
   if (fWord) {
      iSylA = ((bDepthThis & 0x30) >> 4) + ((pSSThis->bPauseProb >> 4) << 2) + 1;
      iSylB = ((bDepthTest & 0x30) >> 4) + ((pSSTest->bPauseProb >> 4) << 2) + 1;
   }
   else {
      iSylA = (int)((bDepthThis & 0x30) >> 4) + 1;
      iSylB = (int)((bDepthTest & 0x30) >> 4) + 1;
   }
   
   fp fScoreDuration = 0.0;

   // convert to floating point
   fp fDurA = iSylA, fDurB = iSylB;
   if (pSSThis && pSSThis->bDurPhone && pSSThis->bDurSyl && !fIgnoreProsody)
      fDurA *= (fp)(pSSThis->bDurPhone + pSSThis->bDurSyl) / (100.0 * 2.0);
   if (pSSTest && pSSTest->bDurPhone && pSSTest->bDurSyl && !fIgnoreProsody)
      fDurB *= (fp)(pSSTest->bDurPhone + pSSTest->bDurSyl) / (100.0 * 2.0);
   fp fDurLog = log(fDurA / fDurB) / log(2.0);
   fScoreDuration += fabs(fDurLog) * pInfo->fDuration;

   // duration bulge
   if (pSSThis && pSSTest && !fIgnoreProsody)
      fScoreDuration += fabs((fp)pSSThis->cDurSkew - (fp)pSSTest->cDurSkew) / 100.0 * pInfo->fDurationSkew;
      // NOTE: Not exactly the right value to use for duration bulge, but a guess


   // energy
   fp fScoreEnergy = 0.0;
   if (pSSThis && pSSTest && pSSThis->bVol && pSSTest->bVol && !fIgnoreProsody) {
      fp fEnergyLog = log((fp)pSSThis->bVol / (fp)pSSTest->bVol) / log(2.0);
      fScoreEnergy += fabs(fEnergyLog) * pInfo->fEnergy;
   }
   
   // accent
   if (pSSThis && pSSTest && !fIgnoreProsody) {
      fp fAccentDelta = ((fp)pSSThis->cAccent - (fp)pSSTest->cAccent) / 100.0;
      fError += fabs(fAccentDelta) * pInfo->fAccent;
         // NOT included in UnitScoreCombine() because has already had it removed
   }

   // pitch at 3 points
   fp fScorePitch = 0.0;
   if (pSSThis && pSSTest && !fIgnoreProsody) {
      DWORD dwPitchPoint;
      fp fPitchLog;
      for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++) {
         fPitchLog = ((fp)pSSThis->acPitchF0Extra[dwPitchPoint] - (fp)pSSTest->acPitchF0Extra[dwPitchPoint]) / 100.0;
         fScorePitch += fabs(fPitchLog) * pInfo->fPitchF0Extra / (fp)SSPITCHPOINTS;
      }

      // BUGFIX - Ignore fuji compare completely
      //if (pSSThis->bPitchF0Fuji && pSSTest->bPitchF0Fuji) {
      //   fPitchLog = log((fp)pSSThis->bPitchF0Fuji / (fp)pSSTest->bPitchF0Fuji) / log(2.0);
      //   fScorePitch += fabs(fPitchLog) * pInfo->fPitchF0Fuji;
      //}

      // fuji delta
      char cMax = (char)(FUJIPHRASEBREAK * 100.0) + 1;
      char cMin = -cMax;
      BOOL fCloseToZeroThis = ((pSSThis->cPitchF0FujiDelta >= cMin) && (pSSThis->cPitchF0FujiDelta <= cMax));
      BOOL fCloseToZeroTest = ((pSSTest->cPitchF0FujiDelta >= cMin) && (pSSTest->cPitchF0FujiDelta <= cMax));
      // if they're both within the phrase break range then 0 error
      if (fCloseToZeroThis && fCloseToZeroTest)
         fScorePitch += 0.0;
      else {
         BOOL fSameSign = ((int) pSSThis->cPitchF0FujiDelta * (int)pSSTest->cPitchF0FujiDelta) >= 0;

         // if they're both NOT close to zero and they're the same sign then zero error
         if (!fCloseToZeroThis && !fCloseToZeroTest && fSameSign)
            fScorePitch += 0.0;
         else {
            // they are sufficiently different that should include as error
            fPitchLog = ((fp)pSSThis->cPitchF0FujiDelta - (fp)pSSTest->cPitchF0FujiDelta) / 100.0;
            fPitchLog = fabs(fPitchLog) * pInfo->fPitchF0Fuji;
            fPitchLog *= FUJIDELTASCALE;  // really crank up the error
            fScorePitch += fPitchLog;
         }
      }


      // dont do anytmore
      // fError += (fabs((fp)pSSThis->cPitchBulge - (fp)pSSTest->cPitchBulge) +
      //   fabs((fp)pSSThis->cPitchSweep - (fp)pSSTest->cPitchSweep)) / 100.0 * pInfo->fPitchF0Extra;
   }

   // function word mistmatch
   fp fScoreFunc = 0.0;
   int iRankA = (int)((bDepthThis & 0xc0) >> 6);
   int iRankB = (int)((bDepthTest & 0xc0) >> 6);
   if (iRankA != iRankB)
      fScoreFunc += (fp) abs(iRankA - iRankB) * pInfo->fFuncWord * FUNCWORDGROUPPERPROSODYMODELCOMMON;

   // start of word mistmatch
   // BYTE bSylThis = (bPOSThis >> 4) & 0x07;
   // BYTE bSylTest = (bPOSTest >> 4) & 0x07;
   if ( (!bSylThis && bSylTest) || (!bSylTest && bSylThis) )
      fScoreFunc += pInfo->fWordPosMismatchStart;   // mismatch at the start of the word

   // end of word mistmatch
   iOffsetThis++;
   iOffsetTest++;
   BYTE bSylThis2, bSylTest2;
   if ((iOffsetThis < 0) || (iOffsetThis >= (int)m_dwNum)) {
      // beyond the end of the sentences
      bPOSStressThis = POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION);
      bSylThis2 = 0;
   }
   else {
      bPOSStressThis = m_pabPOSStress[iOffsetThis];
      bSylThis2 = m_pabSylIndex[iOffsetThis] & 0x07;
   }
   if ((iOffsetTest < 0) || (iOffsetTest >= (int)pTest->m_dwNum)) {
      // beyond the end of the sentences
      bPOSStressTest = POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION);
      bSylTest2 = 0;
   }
   else {
      bPOSStressTest = pTest->m_pabPOSStress[iOffsetTest];
      bSylTest2 = pTest->m_pabSylIndex[iOffsetTest] & 0x07;
   }
   if ((bPOSStressThis & 0x0f) != (bPOSStressTest & 0x0f))
      fScoreFunc += pInfo->fWordPosMismatchEnd;  // since different POS, know end of word
   else {
      // BYTE bSylThis2 = (bPOSThis >> 4) & 0x07;
      // BYTE bSylTest2 = (bPOSTest >> 4) & 0x07;

      if (bSylThis2 >= 7)
         bSylThis2++;   // since max out at 7
      if (bSylTest2 >= 7)
         bSylTest2++;   // since max out at 7

      BOOL fNewWordThis = (bSylThis2 <= bSylThis);
      BOOL fNewWordTest = (bSylTest2 <= bSylTest);
      if ( (fNewWordThis && !fNewWordTest) || (fNewWordTest && !fNewWordThis) )
         fScoreFunc += pInfo->fWordPosMismatchEnd;  // changed syllable numbers so know at end
   }

#if 0 // no longer doing rule-depth comparisons
   // if rule depths are different then error
   int iDepthA1 = (int)(bDepthThis & 0x03);
   int iDepthA2 = (int)((bDepthThis & 0x0c) >> 2);
   int iDepthB1 = (int)(bDepthTest & 0x03);
   int iDepthB2 = (int)((bDepthTest & 0x0c) >> 2);
   iError += (abs(iDepthA1 - iDepthB1) + abs(iDepthA2 - iDepthB2));  // different depths
   iError += ((iDepthA1 - iDepthA2) - (iDepthB1 - iDepthB2)) * 2; // start/end indicator
#endif


#if 0 // no longer doing this
   // BUGFIX - Compare phonemes
   DWORD dwXOR = pSSThis->wPhoneGroupBits ^ pSSTest->wPhoneGroupBits;
   DWORD dwDiff;
   for (dwDiff = 0; dwXOR; dwDiff++, dwXOR = dwXOR & (dwXOR-1));
   iError += ((int)dwDiff+1)/2;  // so add one penalty per phoneme difference.
         // Use /2 since if two phonemes don't match then will (usually) get 2 points worth of error
#endif // 0

   fError += UnitScoreCombine (fScorePitch, fScoreDuration, fScoreEnergy, fScoreFunc);

   return fError;
}


/*************************************************************************************
CSentenceSyllable::CompareSingleSyllableQuick - This quickly compares a single syllable
in this sentence against a single syllable in another.

NOTE: Assume that both the sentences are using the same lexicon.

inputs
   int            iOffsetThis - Offset, in syllable, in this sentence. This can
                  be a negative number or >= the number of syllables, in which
                  case the last syllable is assumed to be the punctuation ('.', '!', '?')
                  for the sentence.
   PCSentenceSyllable pTest - Sentence to test agsint
   int            iOffsetTest - Offset into test, like iOffsetThis

returns
   BOOL - TRUE if the same POS and stress, FALSE if different
*/

__inline fp CSentenceSyllable::CompareSingleSyllableQuick (int iOffsetThis, PCSentenceSyllable pTest, int iOffsetTest)
{
   // figure out parameters for this
   BYTE bPOSStressThis;
   if ((iOffsetThis < 0) || (iOffsetThis >= (int)m_dwNum))
      // beyond the end of the sentences
      bPOSStressThis = POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION);
   else
      bPOSStressThis = m_pabPOSStress[iOffsetThis];

   // figure out the parameters for what comparing
   BYTE bPOSStressTest;
   if ((iOffsetTest < 0) || (iOffsetTest >= (int)pTest->m_dwNum))
      bPOSStressTest = POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION);
   else
      bPOSStressTest = pTest->m_pabPOSStress[iOffsetTest];

   // NOTE: Ignroing syllable index, but ignored syllable index before
   return (bPOSStressThis == bPOSStressTest);
}


/*************************************************************************************
CSentenceSyllable::CompareSequence - Compares one sentence with another, based on offsets.

NOTE: Assume that both the sentences are using the same lexicon.

inputs
   PCOMPARESYLINFO pInfo - Score penalties to use.
   int            iOffsetThis - offset, in syllables, into THIS sentence that
                     will be the start.
   PCSentenceSyllable - Sentence to test against
   int            iOffsetTest - Offset, in syllables, into the TEST sentence.
   WORD           wPeriod - Word number for period, used as default punctiation. -1 if don't know
   BOOL           fIgnoreProsody - Set to TRUE if duration, energy, F0 are unknown in at least one of the setneces
   BOOL           fWord - If TRUE then the sentence is a list of words, FALSE then list of syllables.
                     If list of words, duration of the phoneme is gotten slightly differently
   PBESTSENTSWEEP pSweep - Filled in with information if there's a match.

returns
   BOOL - TRUE if success and pSweep is filled in, FALSE if no match
*/

BOOL CSentenceSyllable::CompareSequence (PCOMPARESYLINFO pInfo, int iOffsetThis, PCSentenceSyllable pTest,
                                 int iOffsetTest, WORD wPeriod, BOOL fIgnoreProsody, BOOL fWord, PBESTSENTSWEEP pSweep)
{
   fp fCompare;

   // quick test - iOffsetThis < 0, then make sure that compare is OK
   // when the sentence actually starts
   if (iOffsetThis < 0) {
      fCompare = CompareSingleSyllable (pInfo, 0, pTest, iOffsetTest - iOffsetThis, wPeriod, FALSE, fIgnoreProsody, fWord);
      if (fCompare >= COMPARESINGLESYLLABLE_NOMATCH)
         return FALSE;
   }

   // quick test - If iOffsetTest < 0, then make sure match at start
   if (iOffsetTest < 0) {
      fCompare = CompareSingleSyllable (pInfo, iOffsetThis - iOffsetTest, pTest, 0, wPeriod, FALSE, fIgnoreProsody, fWord);
      if (fCompare >= COMPARESINGLESYLLABLE_NOMATCH)
         return FALSE;
   }

   // loop
   int iOffsetCur;
   fp fScore = 0.0;
   for (iOffsetCur = iOffsetThis; iOffsetCur < (int)m_dwNum + PUNCTEXTRA; iOffsetCur++) {
      fCompare = CompareSingleSyllable (pInfo, iOffsetCur, pTest, iOffsetTest + iOffsetCur - iOffsetThis, wPeriod, FALSE, fIgnoreProsody, fWord);
      if (fCompare >= COMPARESINGLESYLLABLE_NOMATCH)
         break;   // end of match

      fScore += fCompare;
   } // iOffsetCur
   if (iOffsetCur <= iOffsetThis)
      return FALSE;  // no match at all

   // else, found something
   pSweep->iOffsetTest = iOffsetTest;
   pSweep->dwSylMatch = (DWORD)(iOffsetCur - iOffsetThis);
   fScore += pInfo->fCrossSentencePenalty; // always assume that crossed sentence at some point
   pSweep->fScoreAvg = fScore / (fp)pSweep->dwSylMatch;  // BUGFIX - Was multiplying by 16, but dont do know that using fp
      // NOTE: Multiplying iScoreAvg by 16 so more resolution
   pSweep->pSent = pTest;
   return TRUE;
}


/*************************************************************************************
CSentenceSyllable::CompareSequenceQuick - Qiuckly compares part of one sentence with another, based on offsets.

inputs
   int            iOffsetThis - offset, in syllables, into THIS sentence that
                     will be the start.
   PCSentenceSyllable - Sentence to test against
   int            iOffsetTest - Offset, in syllables, into the TEST sentence.
   DWORD          dwNum - Number of syllables to compare

returns
   BOOL - TRUE if the two match, FALSE if they're different
*/

BOOL CSentenceSyllable::CompareSequenceQuick (int iOffsetThis, PCSentenceSyllable pTest,
                                 int iOffsetTest, DWORD dwNum)
{
   DWORD i;
   for (i = 0; i < dwNum; i++, iOffsetThis++, iOffsetTest++)
      if (!CompareSingleSyllableQuick (iOffsetThis, pTest, iOffsetTest))
         return FALSE;

   return TRUE;
}


/*************************************************************************************
CSentenceSyllable::CompareWindowGenerate - Generates a window used to compare
two lists of syllables (for CompareSequenceWindowed()).

inputs
   int            iStart - Start
   int            iEnd - End (exclusive). iEnd > iStart.
   BOOL           fTriangle - If TRUE then create a triangle window. FALSE then a square window
   PCMem          pMem - Will be initialized to sizeof(fp) and filled with (iEnd - iStart) elements.
returns
   fp * - Start of the window (to pass into CompareSequenceWIndowed()), or NULL if error
*/
fp* CSentenceSyllable::CompareWindowGenerate (int iStart, int iEnd, BOOL fTriangle, PCMem pMem)
{
   if (iEnd <= iStart)
      return NULL;   // error
   DWORD dwNum = (DWORD)(iEnd - iStart);


   if (!pMem->Required (dwNum * sizeof(fp)))
      return FALSE;
   fp *pf = (fp*)pMem->p;

   DWORD i;
   fp fSum = 0.0;
   for (i = 0; i < dwNum; i++) {
      if (fTriangle)
         pf[i] = ((fp)i+0.5 > (fp)dwNum/2.0) ? ((fp)dwNum - ((fp)i+0.5)) : ((fp)i+0.5);
      else
         pf[i] = 1.0;

      fSum += pf[i];
   } // i

   // normalize
   fSum = 1.0 / fSum;
   for (i = 0; i < dwNum; i++)
      pf[i] *= fSum;

   return pf;
}



/*************************************************************************************
CSentenceSyllable::CompareSequenceWindowed - Compares one sentence with another, based on offsets.

NOTE: Assume that both the sentences are using the same lexicon.

inputs
   PCOMPARESYLINFO pInfo - Score penalties to use.
   int            iOffsetThis - offset, in syllables, into THIS sentence that
                     will be the start.
   PCSentenceSyllable - Sentence to test against
   int            iOffsetTest - Offset, in syllables, into the TEST sentence.
   DWORD          dwNum - Number of syllables to test.
   WORD           wPeriod - Word number for period, used as default punctiation. -1 if don't know
   BOOL           fIgnoreProsody - Set to TRUE if duration, energy, F0 are unknown in at least one of the setneces
   BOOL           fWord - If TRUE then the sentence is a list of words, FALSE then list of syllables.
                     If list of words, duration of the phoneme is gotten slightly differently
   fp             *pafWindow - From CompareWindowGenerate().

returns
   fp - Score
*/

fp CSentenceSyllable::CompareSequenceWindowed (PCOMPARESYLINFO pInfo, int iOffsetThis, PCSentenceSyllable pTest,
                                 int iOffsetTest, DWORD dwNum, WORD wPeriod, BOOL fIgnoreProsody, BOOL fWord, fp *pafWindow)
{
   fp fSum = 0.0;
   DWORD i;
   for (i = 0; i < dwNum; i++, iOffsetThis++, iOffsetTest++, pafWindow++)
      fSum += CompareSingleSyllable (pInfo, iOffsetThis, pTest, iOffsetTest, wPeriod, TRUE, fIgnoreProsody, fWord) * pafWindow[0];
   
   return fSum;
}


#if 0 // Old code, made obsolete when converted to beam search
/*************************************************************************************
CSentenceSyllable::Compare- Compares one sentence with another, based on offsets.

NOTE: Assume that both the sentences are using the same lexicon.

inputs
   DWORD          dwOffsetThis - offset, in syllables, into THIS sentence that
                     will be the start.
                  NOTE: This should have PUNCTEXTRA POS_MAJOR_PUNCTUATION syllables
                     already pre-attached to the end. This ends up causing
                     sentences that end together to have a higher score. The
                     same happens for ones beginning
   PCSentenceSyllable - Sentence to test against
   DWORD          dwOffsetTest - Offset, in syllables, into the TEST sentence.
   PBESTSENTSWEEP pSweep - Filled in with information if there's a match.

returns
   BOOL - TRUE if success and pSweep is filled in, FALSE if no match
*/

BOOL CSentenceSyllable::Compare (DWORD dwOffsetThis, PCSentenceSyllable pTest,
                                 DWORD dwOffsetTest, PBESTSENTSWEEP pSweep)
{
   // quick test, if the two central values don't match at all then
   // fail
   if (m_pabPOS[dwOffsetThis] != pTest->m_pabPOS[dwOffsetTest])
      return FALSE;

   // number of syllables left
   DWORD dwLeftThis = m_dwNum - dwOffsetThis;
   DWORD dwLeftTest = pTest->m_dwNum - dwOffsetTest + PUNCTEXTRA;
   DWORD dwLeft = min(dwLeftThis, dwLeftTest);
   DWORD i;
   DWORD dwCurThis = dwOffsetThis;
   DWORD dwCurTest = dwOffsetTest;
   DWORD dwWordMatch = 0;

#define MAXWORDERROR     10       // maximum number of error points before considers too different
      // BUGFIX - Upped from 8 because also testing for phoneme difference

   // figure out what punctuation is at the end for both of these sentences
   WORD wPunctTest = (WORD)-1, wPunctThis = (WORD)-1;
   if (pTest->m_dwNum && ((pTest->m_pabPOS[pTest->m_dwNum-1] & 0x0f) == POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION)))
      wPunctTest = pTest->m_paSyl[pTest->m_dwNum-1].wWord;
   if (m_dwNum && ((m_pabPOS[m_dwNum-1] & 0x0f) == POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION)))
      wPunctThis = m_paSyl[m_dwNum-1].wWord;

   DWORD dwErrors;
   for (i = 0; i < dwLeft; i++, dwCurThis++, dwCurTest++) {
      // errors
      dwErrors = 0;

      if (dwCurTest >= pTest->m_dwNum) {
         // pretend there's punctuation at the end
         if (m_pabPOS[dwCurThis] != POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION))
            break;
         else {
            // BUGFIX - If type of punctuation deosnt match then reduce amount
            if (m_paSyl[dwCurThis].wWord != wPunctTest)
               dwErrors += WORDMISMATCHPENALTY;

            dwWordMatch += MAXWORDERROR - dwErrors;
            continue;
         }
      }

      if (m_pabPOS[dwCurThis] != pTest->m_pabPOS[dwCurTest])
         break;


      // if not the same word, then error
      if ((m_paSyl[dwCurThis].wWord != pTest->m_paSyl[dwCurTest].wWord) || (m_paSyl[dwCurThis].wWord == (WORD)-1))
         dwErrors += WORDMISMATCHPENALTY;

      BYTE bDepthThis = m_pabRuleDepth[dwCurThis];
      BYTE bDepthTest = pTest->m_pabRuleDepth[dwCurTest];
      // if rule depths are different then error
      int iDepthA1 = (int)(bDepthThis & 0x03);
      int iDepthA2 = (int)((bDepthThis & 0x0c) >> 2);
      int iDepthB1 = (int)(bDepthTest & 0x03);
      int iDepthB2 = (int)((bDepthTest & 0x0c) >> 2);
      dwErrors += (DWORD) (abs(iDepthA1 - iDepthB1) + abs(iDepthA2 - iDepthB2));  // different depths
      dwErrors += (DWORD) ((iDepthA1 - iDepthA2) - (iDepthB1 - iDepthB2)) * 2; // start/end indicator

      // if different number of syllables then error
      int iSylA = (int)((bDepthThis & 0x30) >> 4);
      int iSylB = (int)((bDepthTest & 0x30) >> 4);
      dwErrors += (DWORD) abs(iSylA - iSylB);

      // compare word rank
      int iRankA = (int)((bDepthThis & 0xc0) >> 6);
      int iRankB = (int)((bDepthTest & 0xc0) >> 6);
      dwErrors += (DWORD) abs(iRankA - iRankB);

      // BUGFIX - Compare phonemes
      DWORD dwXOR = m_paSyl[dwCurThis].wPhoneGroupBits ^ pTest->m_paSyl[dwCurTest].wPhoneGroupBits;
      DWORD dwDiff;
      for (dwDiff = 0; dwXOR; dwDiff++, dwXOR = dwXOR & (dwXOR-1));
      dwErrors += (dwDiff+1)/2;  // so add one penalty per phoneme difference.
            // Use /2 since if two phonemes don't match then will (usually) get 2 points worth of error

      // if too much error then stop
      if (dwErrors >= MAXWORDERROR)
         break;

      // else, increase word match
      dwWordMatch += (MAXWORDERROR - dwErrors);
   } // i

   // BUGFIX - In case decided to abort right away
   if (!i)
      return FALSE;

   DWORD dwScore = dwWordMatch;
   if (!dwOffsetThis && !dwOffsetTest) {
      dwErrors = 0;
      if (wPunctTest != wPunctThis)
         dwErrors += WORDMISMATCHPENALTY;  // since not the right type of punctuation at end of sentence
                                          // and automatically transferring to beginning

      dwScore += (PUNCTEXTRA - dwErrors) * MAXWORDERROR;  // since both started at beginning of sentence
         // BUGFIX - dwScore was only incremented by PUNCEXTRA
   }
   // NOTE: PunctExtra already added at the end

   pSweep->dwOffset = dwOffsetTest;
   pSweep->dwScore = dwScore;   // NOTE: Squaring score so long sequences are encouraged
      // dwScore * dwScore
      // BUGFIX - Took out square because doing some different weighting by ranking
      // NOTE: Made a HUGE difference in quality when took out square!
   pSweep->dwSylMatch = i;
   pSweep->pSent = pTest;
   return TRUE;
}
#endif


/*************************************************************************************
CSentenceSyllable::CompareSweepSequence - This finds all matches of this sentence in
the test sentence, and adds any matches to plBESTSENTSWEEP.

inputs
   PCOMPARESYLINFO pInfo - Score penalties to use.
   int            iOffsetThis - offset, in syllables, into THIS sentence that
                     will be the start. Can be negative too, or beyond edge of sentnece
   PCSentenceSyllable pTest - Sentence to test against
   WORD           wPeriod - Word number for period, used as default punctiation. -1 if don't know
   BOOL           fIgnoreProsody - Set to TRUE if duration, energy, F0 are unknown in at least one of the setneces
   PCListFixed    plBESTSENTSWEEP - This should be filled in with a set of
                     BESTSENTSWEEP that are already known for dwOffsetThis.
                     They should be in sorted order, from LOWEST score to
                     HIGHEST.
                     
                     If pTest is already represented on
                     plBESTSENTSWEEP, AND the right dwOffset is there,
                     then this won't bother doing a sweep for it.

                     If a comparison is successful AND its score is better
                     than the worse score (or there are empty slots) then
                     it will be added.
   DWORD          dwSlots - Number of slots allowed in plBESTSENTSWEEP.
                     Basically, keep the best N of these.
   PCListFixed    plBESTSENTSWEEPExclude - If find a match for any of these
                     then don't bother adding them to plBESTSENTSWEEP.
                     Can be NULL.
returns
   none
*/
void CSentenceSyllable::CompareSweepSequence (PCOMPARESYLINFO pInfo, int iOffsetThis, PCSentenceSyllable pTest,
                                      WORD wPeriod, BOOL fIgnoreProsody, PCListFixed plBESTSENTSWEEP, DWORD dwSlots,
                                      PCListFixed plBESTSENTSWEEPExclude)
{
   // see if this sentence is mentioned at all in plBESTSENTSWEEP. If it isn't then
   // note that, so won't need to check again
   BOOL fIsMentioned = FALSE;
   DWORD i, j, k;
   PBESTSENTSWEEP pBSS = (PBESTSENTSWEEP) plBESTSENTSWEEP->Get(0);
   for (i = 0; i < plBESTSENTSWEEP->Num(); i++)
      if (pBSS[i].pSent == pTest)
         fIsMentioned = TRUE;
   PBESTSENTSWEEP pBSSExclude = plBESTSENTSWEEPExclude ? (PBESTSENTSWEEP) plBESTSENTSWEEPExclude->Get(0) : NULL;
   if (pBSSExclude) for (i = 0; i < plBESTSENTSWEEPExclude->Num(); i++)
      if (pBSSExclude[i].pSent == pTest)
         fIsMentioned = TRUE;

   // loop to see if matches any
   BESTSENTSWEEP Sweep;
   int iOffsetTest;
   for (iOffsetTest = -PUNCTEXTRA; iOffsetTest < (int)pTest->m_dwNum; iOffsetTest++) { // NOTE: NOT going beyond pTest->m_dwNum
      if (!CompareSequence (pInfo, iOffsetThis, pTest, iOffsetTest, wPeriod, fIgnoreProsody, FALSE, &Sweep))
         continue;   // no match

      // if this score is too low then stop now
      // check before suck up cycles with a rand() call
      if ((plBESTSENTSWEEP->Num() >= dwSlots) && (pBSS[dwSlots-1].fScoreAvg <= Sweep.fScoreAvg))
         continue;   // score too high

      // make sure this isn't already on the list
      if (fIsMentioned) {
         // try the existing senteces
         for (j = 0; j < plBESTSENTSWEEP->Num(); j++)
            if ((pBSS[j].pSent == pTest) && (pBSS[j].iOffsetTest == iOffsetTest))
               break;
         if (j < plBESTSENTSWEEP->Num())
            continue;   // already exists, so dont re-add

         // try the exclude
         if (plBESTSENTSWEEPExclude) {
            for (j = 0; j < plBESTSENTSWEEPExclude->Num(); j++)
               if ((pBSSExclude[j].pSent == pTest) && (pBSSExclude[j].iOffsetTest == iOffsetTest))
                  break;
            if (j < plBESTSENTSWEEPExclude->Num())
               continue;   // already exists, so dont re-add
         }
      }
      
      // else, is reasonable score, so want to add
      DWORD dwOldNum = plBESTSENTSWEEP->Num();
      if (dwOldNum < dwSlots) {
         plBESTSENTSWEEP->Add (&Sweep);   // temp
         pBSS = (PBESTSENTSWEEP) plBESTSENTSWEEP->Get(0);
      }

      // find where it fits and insert it
      for (j = 0; j < dwOldNum; j++)
         if (Sweep.fScoreAvg < pBSS[j].fScoreAvg)
            break;   // insert before point
      if (j >= plBESTSENTSWEEP->Num())
         continue;   // shouldnt happen

      Sweep.plfScoreSyl = new CListFixed;
      if (!Sweep.plfScoreSyl)
         continue;   // shouldnt happen
      Sweep.plfScoreSyl->Init (sizeof(Sweep.fScoreSyl));
      for (k = 0; k < Sweep.dwSylMatch; k++) {
         Sweep.fScoreSyl = CompareSingleSyllable (pInfo, iOffsetThis + (int)k, pTest, Sweep.iOffsetTest + (int)k, wPeriod,
            FALSE, fIgnoreProsody, FALSE);
         Sweep.plfScoreSyl->Add (&Sweep.fScoreSyl);
      }

      if (dwOldNum < dwSlots)
         memmove (pBSS + (j+1), pBSS + j, (dwOldNum - j) * sizeof(BESTSENTSWEEP));

      else {
         // delete the last one's list
         if (pBSS[dwOldNum-1].plfScoreSyl)
            delete pBSS[dwOldNum-1].plfScoreSyl;

         memmove (pBSS + (j+1), pBSS + j, (dwOldNum - j-1) * sizeof(BESTSENTSWEEP));   // dont move last one
      }
      memcpy (pBSS + j, &Sweep, sizeof(Sweep));
   } // i
}


#if 0 // old compare code
/*************************************************************************************
CSentenceSyllable::Compare - Compares one sentence syllable with another, returning
a score indicating how closely they match (higher numbers are better).

inputs
   DWORD          dwOffsetThis - Offset, in syllables, into THIS sentence that will
                  be the central/focal syllable for comparing
   CSentenceSyllable *pTest - Sentence to test against
   DWORD          dwOffsetTest - Offset, in syllables, into the test sentece for
                  the central/focal syllable.
returns
   DWORD - Score. 0 indicates there is no match. Higher values are better
*/
#define MAXCOMPAREDIST        5     // compare up to 5 syllables away on either side
__inline DWORD CSentenceSyllable::Compare (DWORD dwOffsetThis, CSentenceSyllable *pTest, DWORD dwOffsetTest)
{
   // quick test, if the two central values don't match at all then
   // fail
   if (m_pabPOS[dwOffsetThis] != pTest->m_pabPOS[dwOffsetTest])
      return 0;

   // determine the score, looping outward from the central point until reach
   // syllable that doesn't match at all, or until go beyond max
   DWORD dwScore = 0;
   DWORD dwDirection, i, dwTempScore;
   for (dwDirection = 0; dwDirection < 2; dwDirection++) {
      for (i = (dwDirection ? 1 : 0); i < MAXCOMPAREDIST; i++) {
         int iThis, iTest;
         BYTE bThis, bTest;
         if (dwDirection) {
            iThis = (int) dwOffsetThis + (int)i;
            iTest = (int) dwOffsetTest + (int)i;

            // NOTE: Dead code. ignoring bRuleDepth
            if (iThis < (int)m_dwNum)
               bThis = m_pabPOS[iThis];
            else
               bThis = POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION);

            if (iTest < (int)pTest->m_dwNum)
               bTest = pTest->m_pabPOS[iTest];
            else
               bTest = POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION);
         }
         else {
            iThis = (int) dwOffsetThis - (int)i;
            iTest = (int) dwOffsetTest - (int)i;

            if (iThis >= 0)
               bThis = m_pabPOS[iThis];
            else
               bThis = POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION);

            if (iTest >= 0)
               bTest = pTest->m_pabPOS[iTest];
            else
               bTest = POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION);
         }

         // if they're not the same then break, since stop matching
         if (bThis != bTest)
            break;

         // temporary score
         dwTempScore = (MAXCOMPAREDIST - i) * 10;

         // else, need to compare word #s
         DWORD dwThis, dwTest;
         if (dwDirection) {
            if (iThis < (int)m_dwNum)
               dwThis = m_paSyl[iThis].dwWord;
            else
               dwThis = (DWORD)-2;  // intentionally not using -1

            if (iTest < (int)pTest->m_dwNum)
               dwTest = pTest->m_paSyl[iTest].dwWord;
            else
               dwTest = (DWORD)-2;  // intentionally not using -1
         }
         else {
            if (iThis >= 0)
               dwThis = m_paSyl[iThis].dwWord;
            else
               dwThis = (DWORD)-2;  // intentionally not using -1

            if (iTest >= 0)
               dwTest = pTest->m_paSyl[iTest].dwWord;
            else
               dwTest = (DWORD)-2;  // intentionally not using -1
         }
         if ((dwThis == dwTest) && (dwTest != (DWORD)-1))
            dwTempScore += dwTempScore / 2;  // exact word match creates a bonus score
                  // BUGFIX - higher score for exact word match

         dwScore += dwTempScore;
      } // i distance
   } // dwDirection

   return dwScore;
}
#endif // 0



#if 0 // old compare code
/*************************************************************************************
CSentenceSyllable::CompareSweep - Compares this sentence against a test sentences,
sweeping across all syllable positions in the test sentences. Any scores > 0 are
tested against the list of best scores and potentially inserted into the list.

inputs
   DWORD          dwOffsetThis - Offset, in syllables, into THIS sentence that will
                  be the central/focal syllable for comparing
   CSentenceSyllable *pTest - Sentence to test against
   DWORD          *pdwNum - Originally filled with the number of elements in pBESTSENTSWEEP.
                  This will be modified if new sentences are added.
   DWORD          dwMax - Maximum number of sentences alloed in pBESTSENTSWEEP. pdwNum
                  will not be modified beyond this
   PBESTSENTSWEEP pBESTSENTSWEEP - Sorted list of best-case matches for sentences, with
                  highest scores at the top of the list.
returns
   none
*/
void CSentenceSyllable::CompareSweep (DWORD dwOffsetThis, CSentenceSyllable *pTest,
                                      DWORD *pdwNum, DWORD dwMax, PBESTSENTSWEEP pBESTSENTSWEEP)
{
   DWORD i, dwScore;
   int iInsert;
   DWORD dwCurrentWorst = dwCurrentWorst = (*pdwNum < dwMax) ? 0 : pBESTSENTSWEEP[*pdwNum-1].dwScore;
      // BUGFIX - Was (*pdwNum) ? pBESTSENTSWEEP[*pdwNum-1].dwScore : 0;
   for (i = 0; i < pTest->m_dwNum; i++) {
      dwScore = Compare (dwOffsetThis, pTest, i);
      if (dwScore <= dwCurrentWorst)
         continue;   // no match at all

      // apply some randomization so that if there's a bad template at the start
      // of the list won't mar all the sentences from then on. Also
      // provides a bit of variety
      DWORD dwRandom = (DWORD)(rand() % 20) * dwScore / 100;   // 20% random
      if (dwRandom < dwScore)
         dwScore -= dwRandom;
      else
         dwScore = 1;   // so doesnt go to 0
      // recheck
      if (dwScore <= dwCurrentWorst)
         continue;   // no match at all

      // else, better than the current worst, so add it
      for (iInsert = (int)(*pdwNum) - 1; iInsert >= 0; iInsert--)
         if (dwScore <= pBESTSENTSWEEP[iInsert].dwScore)
            break;
      iInsert++;  // so know what to insert before

      DWORD dwMove = *pdwNum - (DWORD)iInsert;
      if (*pdwNum >= dwMax)
         dwMove--; // so will end up discarding the last one

      // move
      if (dwMove)
         memmove (pBESTSENTSWEEP + (iInsert+1), pBESTSENTSWEEP + iInsert, dwMove * sizeof(BESTSENTSWEEP));

      // fill in
      pBESTSENTSWEEP[iInsert].dwScore = dwScore;
      pBESTSENTSWEEP[iInsert].dwOffset = i;
      pBESTSENTSWEEP[iInsert].pSent = pTest;

      // new numbre
      if (*pdwNum < dwMax)
         *pdwNum = *pdwNum + 1;
      else
         *pdwNum = dwMax;
      dwCurrentWorst = (*pdwNum < dwMax) ? 0 : pBESTSENTSWEEP[*pdwNum-1].dwScore;
   } // i, over all syllable indecies
}
#endif // 0


#if 0 // old compare code
/*************************************************************************************
CSentenceSyllable::CompareSweep - This compares the test sentence against a list
of other test sentneces, filling in the best fit. See the ::CompareSweep() method
above for some details on paramters


inputs
   DWORD          dwOffsetThis - Offset, in syllables, into THIS sentence that will
                  be the central/focal syllable for comparing
   PCListFixed    plPCSentenceSyllable - List of PCSentenceSyllable to compare this
                  sentence against.
   DWORD          *pdwNum - Originally filled with the number of elements in pBESTSENTSWEEP.
                  This will be modified if new sentences are added.
   DWORD          dwMax - Maximum number of sentences alloed in pBESTSENTSWEEP. pdwNum
                  will not be modified beyond this
   PBESTSENTSWEEP pBESTSENTSWEEP - Sorted list of best-case matches for sentences, with
                  highest scores at the top of the list.
returns
   none
*/
void CSentenceSyllable::CompareSweep (DWORD dwOffsetThis, PCListFixed plPCSentenceSyllable,
                                      DWORD *pdwNum, DWORD dwMax, PBESTSENTSWEEP pBESTSENTSWEEP)
{
   PCSentenceSyllable *ppss = (PCSentenceSyllable*)plPCSentenceSyllable->Get(0);
   DWORD dwNum = plPCSentenceSyllable->Num();

   DWORD i;
   for (i = 0; i < dwNum; i++)
      CompareSweep (dwOffsetThis, ppss[i], pdwNum, dwMax, pBESTSENTSWEEP);
}
#endif // 0



#if 0 // old compare code
/*************************************************************************************
CSentenceSyllable::FindBestMatch - This takes a sentence split into syllables, which
will be spoken by TTS, and compares it against a list of sentences in the database.
It then finds the best matching sentences and uses them as templates to fill in
this sentences bpitch, pvolume, bduration, and microsilence.

inputs
   PCListFixed    plPCSentenceSyllable - List of PCSentenceSyllable to compare this
                  sentence against.
returns
   none... although it changes the values. NOTE: bFlags will be filled with 0..255,
      where 0 is no microsilnece, 255 is always microsilence
*/
#define MAXBESTSYLLABLE          5     // maximum number of best-matches to look for
#define SYLLABLEBLEND            3     // blend syllables this far around

void CSentenceSyllable::FindBestMatch (PCListFixed plPCSentenceSyllable)
{
   // keep a list of lists
   CListFixed lPerSyl;
   lPerSyl.Init (sizeof(PCListFixed));

   // loop through all the syllables and find the best match amongst them
   DWORD i, dwNumBest;
   BESTSENTSWEEP aBestSent[MAXBESTSYLLABLE];
   PCListFixed pl;
   for (i = 0; i < m_dwNum; i++) {
      dwNumBest = 0;
      CompareSweep (i, plPCSentenceSyllable, &dwNumBest, MAXBESTSYLLABLE, aBestSent);

      // create a new list and add these
      pl = new CListFixed;
      if (!pl)
         return;  // error
      pl->Init (sizeof(BESTSENTSWEEP), aBestSent, dwNumBest);

      lPerSyl.Add (&pl);
   } // i

   // go through all the matching sentences and see if they can be forward or
   // back-propogated to adjacent slots where they didn't match. This way there
   // will be as few template-sentence transitions as possible
   DWORD j, k, dwForward, dwDistance;
   PCListFixed *ppl = (PCListFixed*)lPerSyl.Get(0);
   BESTSENTSWEEP bs;
   for (i = 0; i < m_dwNum; i++) {
      pl = ppl[i];

      for (j = 0; j < pl->Num(); j++) {
         // get info
         PBESTSENTSWEEP pSweep = (PBESTSENTSWEEP) pl->Get(j);

         // forward/back
         for (dwForward = 0; dwForward < 2; dwForward++) for (dwDistance = 0; dwDistance < m_dwNum; dwDistance++) {
            // figure out the index into ppl that looking at
            int iThis;
            if (dwForward)
               iThis = (int)i + (int)dwDistance + 1;
            else
               iThis = (int)i - (int)dwDistance - 1;
            if ((iThis >= (int)m_dwNum) || (iThis < 0))
               break;   // beyond the edge of this sentence

           
            // see what testing against
            int iTest;
            if (dwForward)
               iTest = (int)pSweep->dwOffset + (int)dwDistance + 1;
            else
               iTest = (int)pSweep->dwOffset - (int)dwDistance - 1;
            if ((iTest >= (int)pSweep->pSent->m_dwNum) || (iTest < 0))
               break;   // dont test beyond edge of other one

            // if the current sweep sentence is already found in an adjacent slot
            // then stop going any further
            PBESTSENTSWEEP pAdjacent = (PBESTSENTSWEEP) ppl[iThis]->Get(0);
            for (k = 0; k < ppl[iThis]->Num(); k++, pAdjacent++)
               if ((pAdjacent->pSent == pSweep->pSent) && (pAdjacent->dwOffset == (DWORD)iTest))
                  break;
            if (k < ppl[iThis]->Num())
               break;   // stop since already have a matching version in that direction

            // get a comparison score
            bs.dwScore = Compare ((DWORD)iThis, pSweep->pSent, (DWORD)iTest);
            if (!bs.dwScore)
               break;   // got toa point where they no longer match, so abandon

            // else, these sentences still match up, so add to the list
            bs.dwOffset = (DWORD)iTest;
            bs.pSent = pSweep->pSent;
            ppl[iThis]->Add (&bs);
         } // dwForward
      } // j, over all good matches
   } // i, over all indecies
   
   // if any of the lists are blank then add a dummy hypothesis that will have no pitch or inflection
   bs.dwOffset = 0;
   bs.dwScore = 1;
   bs.pSent = NULL;
   for (i = 0; i < m_dwNum; i++) {
      pl = ppl[i];
      if (!pl->Num())
         pl->Add (&bs);
   } // i

   // now that have a lot of templates for each word, blend them all together, weighting
   // by the dwscore... that way, if there are bad recordings, they will tend to be
   // counteracted by having lots of data

   // loop through and blend the results of the best ones
   CPoint pSum; note - this wont work anymore
   fp fWeight;
   fp fMicroSilence, fMicroSilenceSum;
   for (i = 0; i < m_dwNum; i++) {
      // do a weighted sum of all the surrounding bits
      pSum.Zero();
      pSum.p[3] = 0;
      fMicroSilence = fMicroSilenceSum = 0;

      int iLook;
      PSENTENCESYLLABLE pss;
      for (iLook = (int)i-SYLLABLEBLEND; iLook <= (int)i+SYLLABLEBLEND; iLook++) {
         if ((iLook < 0) || (iLook >= (int)m_dwNum))
            continue; // blending too far back

         DWORD dwNumInStack = ppl[iLook]->Num();
         PBESTSENTSWEEP pStack = (PBESTSENTSWEEP)ppl[iLook]->Get(0);
         for (j = 0; j < dwNumInStack; j++) {
            // blend with
            PBESTSENTSWEEP pBlendWith = pStack + j;

            // if there isn't any available match then skip
            if (!pBlendWith->pSent)
               continue;

            // make sure that can get the appropriate value
            int iTest = (int) pBlendWith->dwOffset - (iLook - (int)i);
            if ((iTest < 0) || (iTest >= (int) pBlendWith->pSent->m_dwNum))
               continue;   // would be beyond the edge of that

            // else, add, weighting the scores of the blend
            pss = pBlendWith->pSent->m_paSyl + iTest;
            fWeight = (SYLLABLEBLEND - fabs(iLook - (int)i)) * (fp) pBlendWith->dwScore;
            pSum.p[0] += log((fp)pss->bPitch / 100.0) * fWeight;
            pSum.p[1] += log((fp)pss->bVol / 100.0) * fWeight;
            pSum.p[2] += log((fp)pss->bDur / 100.0) * fWeight;
            pSum.p[3] += fWeight;

            // BUGFIX - look at the POS for only the first syllable to see if should include
            BYTE bPOS = pBlendWith->pSent->m_pabPOS[iTest];
               // Dead code. Not dealing with bRuleDepth
            if (!(bPOS & 0x70)) {
               // this is the start of a word
               fMicroSilenceSum += fWeight;

               if (pss->bFlags & 0x01)
                  fMicroSilence += fWeight;
            }
         } // j, over templates for the word
      } // iLook, blend in

      // if there's no weight then make a neutral case
      if (pSum.p[3]) {
         pSum.Scale (1.0  / pSum.p[3]);
         pSum.p[0] = exp (pSum.p[0]);  // so pitch, duration, and volume
         pSum.p[1] = exp (pSum.p[1]);  // so pitch, duration, and volume
         pSum.p[2] = exp (pSum.p[2]);  // so pitch, duration, and volume
         if (fMicroSilenceSum)
            fMicroSilence = fMicroSilence / fMicroSilenceSum;
         else
            fMicroSilence = 0.0;
         //if (fMicroSilence > fMicroSilenceSum * fMicroPauseThreshhold)
         //   fMicroSilence = TRUE;
         //else
         //   fMicroSilence = FALSE;
      }
      else {
         pSum.p[0] = pSum.p[1] = pSum.p[2] = 1.0;
         fMicroSilence = 0.0;   // no microsilnce
      }

      // store this away
      for (j = 0; j < 3; j++) {
         pSum.p[j] *= 100.0;
         pSum.p[j] = max(pSum.p[j], 25);  // dont be too restrtive.. and shouldnt happen anyway
         pSum.p[j] = min(pSum.p[j], 255);  // dont be too restrtive.. and shouldnt happen anyway
      }

      // write the values in
      pss = m_paSyl + i;
      pss->bPitch = (BYTE)pSum.p[0];
      pss->bVol = (BYTE)pSum.p[1];
      pss->bDur = (BYTE)pSum.p[2];
      // NOTE: bFlags used as microsilnce value... if 0 NEVER insert, if 255 always insert
      fMicroSilence = max(fMicroSilence, 0);
      fMicroSilence = min(fMicroSilence, 1);
      pss->bFlags = (BYTE)(fMicroSilence*255.0);
   } // i

   // free the list of lists
   for (i = 0; i < lPerSyl.Num(); i++)
      delete ppl[i];
}
#endif // 0



/*************************************************************************************
GlobalStateToMML - Creates a CMMLNode2 and fills in the parameters for the global
state into it.

inputs
   PTTSGLOBALSTATE pState - Current state
returns
   PCMMLNode2 - Node of type gpszTTSGlobalState. This need to be freed by the calelr
*/
static PCMMLNode2 GlobalStateToMML (PTTSGLOBALSTATE pState)
{
   PCMMLNode2 pNode = new CMMLNode2;
   if (!pNode)
      return NULL;
   pNode->NameSet (gpszTTSGlobalState);

   WCHAR szTemp[32];

   // pitch
   MMLDoubleToString (pState->fProsodyAvgPitch, szTemp);
   pNode->AttribSetString (gpszAvgPitch, szTemp);

   // syllabledur
   //MMLDoubleToString (pState->fProsodyAvgSyllableDur, szTemp);
   //pNode->AttribSetString (gpszAvgSyllableDur, szTemp);

   // words per minute
   MMLDoubleToString (pState->fProsodyWPM, szTemp);
   pNode->AttribSetString (gpszWPM, szTemp);

   // volume
   MMLDoubleToString (pState->fProsodyVol, szTemp);
   pNode->AttribSetString (gpszVol, szTemp);

   // pitch expressieness
   MMLDoubleToString (pState->fProsodyPitchExpress, szTemp);
   pNode->AttribSetString (gpszPitchExpress, szTemp);

   // emotions
   MMLDoubleToString (pState->fEmotionWhisper, szTemp);
   pNode->AttribSetString (gpszEmotionWhisper, szTemp);

   // emotions
   MMLDoubleToString (pState->fEmotionShout, szTemp);
   pNode->AttribSetString (gpszEmotionShout, szTemp);

   // emotions
   MMLDoubleToString (pState->fEmotionQuiet, szTemp);
   pNode->AttribSetString (gpszEmotionQuiet, szTemp);

   // emotions
   MMLDoubleToString (pState->fEmotionHappy, szTemp);
   pNode->AttribSetString (gpszEmotionHappy, szTemp);

   // emotions
   MMLDoubleToString (pState->fEmotionSad, szTemp);
   pNode->AttribSetString (gpszEmotionSad, szTemp);

   // emotions
   MMLDoubleToString (pState->fEmotionAfraid, szTemp);
   pNode->AttribSetString (gpszEmotionAfraid, szTemp);

   // emotions
   MMLDoubleToString (pState->fEmotionDrunk, szTemp);
   pNode->AttribSetString (gpszEmotionDrunk, szTemp);

   return pNode;
}



/*************************************************************************************
GlobalStateFromMML - Takes a MML that's of type gpszTTSGlobalState (from GlobalStateToMML())
and pulls out the state information.

inputs
   PCMMLNode2         pNode - Node from GlobalStateToMML(). Can be NULL
   PCMTTSSubVoice     pSubVoice - Subvoice to use
   PTTSGLOBALSTATE   pState - Current state. Should have some valid values in case not all
      all attributes are read
*/
static void GlobalStateFromMML (PCMMLNode2 pNode, PCMTTSSubVoice pSubVoice, PTTSGLOBALSTATE pState)
{
   if (pNode) {
      // pitch
      PWSTR psz = pNode->AttribGetString (gpszAvgPitch);
      if (psz)
         pState->fProsodyAvgPitch = _wtof(psz);

      // syllable duration
      //psz = pNode->AttribGetString (gpszAvgSyllableDur);
      //if (psz)
      //   pState->fProsodyAvgSyllableDur = _wtof(psz);

      //wpm
      psz = pNode->AttribGetString (gpszWPM);
      if (psz)
         pState->fProsodyWPM = _wtof(psz);

      // volume
      psz = pNode->AttribGetString (gpszVol);
      if (psz)
         pState->fProsodyVol = _wtof(psz);

      // pitch expressieness
      psz = pNode->AttribGetString (gpszPitchExpress);
      if (psz)
         pState->fProsodyPitchExpress = _wtof(psz);

      // emotions
      if (psz = pNode->AttribGetString (gpszEmotionWhisper))
         pState->fEmotionWhisper = _wtof(psz);
      if (psz = pNode->AttribGetString (gpszEmotionShout))
         pState->fEmotionShout = _wtof(psz);
      if (psz = pNode->AttribGetString (gpszEmotionQuiet))
         pState->fEmotionQuiet = _wtof(psz);
      if (psz = pNode->AttribGetString (gpszEmotionHappy))
         pState->fEmotionHappy = _wtof(psz);
      if (psz = pNode->AttribGetString (gpszEmotionSad))
         pState->fEmotionSad = _wtof(psz);
      if (psz = pNode->AttribGetString (gpszEmotionAfraid))
         pState->fEmotionAfraid = _wtof(psz);
      if (psz = pNode->AttribGetString (gpszEmotionDrunk))
         pState->fEmotionDrunk = _wtof(psz);
   } // if pNode

   // start with prosody as set
   pState->fDerAvgPitch = pState->fProsodyAvgPitch;
   // pState->fDerAvgSyllableDur = pState->fProsodyAvgSyllableDur;
   pState->fDerWPM = pState->fProsodyWPM;
   pState->fDerVol = pState->fProsodyVol;
   pState->fDerPitchExpress = pState->fProsodyPitchExpress;
   pState->fDerDurationExpress = 1;
   pState->fDerVolumeExpress = 1;
   pState->fDerShout = 0;
   pState->fDerWhisper = 0;
   pState->fDerMicropauses = pSubVoice->m_fMicroPauseThreshhold;

   // apply emotions
   if (pState->fEmotionWhisper) {
      pState->fDerWhisper = pState->fEmotionWhisper / 100.0;
      pState->fDerPitchExpress *= 1.0 - (pState->fEmotionWhisper / 100.0) * 0.5;
      pState->fDerWPM /= 1.0 + (pState->fEmotionWhisper / 100) * 0.3;
   }
   if (pState->fEmotionShout) {
      pState->fEmotionShout = max(pState->fEmotionShout, 0);
      pState->fEmotionShout = min(pState->fEmotionShout, 100);
      pState->fDerShout = pState->fEmotionShout / 100.0;
      pState->fDerVol *= 1.0 + (pState->fEmotionShout / 100.0);
      pState->fDerMicropauses -= pState->fEmotionShout / 100.0 * 0.5;   // more pauses
      pState->fDerPitchExpress *= 1.0 - (pState->fEmotionShout / 100.0) * 0.5;
      pState->fDerDurationExpress *= 1.0 - (pState->fEmotionShout / 100.0) * 0.5; // less expressive duration
      pState->fDerAvgPitch *= 1.0 + (pState->fEmotionShout / 100.0) * 0.1; // slight increase in pitch
      pState->fDerWPM *= 1.0 + (pState->fEmotionShout / 100) * 0.2;
   }
   if (pState->fEmotionQuiet) {
      pState->fEmotionQuiet = max(pState->fEmotionQuiet, 0);
      pState->fEmotionQuiet = min(pState->fEmotionQuiet, 100);
      pState->fDerShout = -pState->fEmotionQuiet / 100.0;
      pState->fDerVol /= 1.0 + (pState->fEmotionQuiet / 100.0);
      pState->fDerPitchExpress *= 1.0 + (pState->fEmotionQuiet / 100.0) * 0.25;  // slightly more expressive
      pState->fDerAvgPitch /= 1.0 + (pState->fEmotionQuiet / 100.0) * 0.1; // slight decrease in pitch
      pState->fDerWPM /= 1.0 + (pState->fEmotionQuiet / 100) * 0.1;
   }
   if (pState->fEmotionHappy) {
      pState->fDerAvgPitch *= 1.0 + (pState->fEmotionHappy / 100.0) * 0.1; // slight increase in pitch
      pState->fDerVol *= 1.0 + (pState->fEmotionHappy / 100.0) * 0.3; // slight increase in volume
      pState->fDerWPM *= 1.0 + (pState->fEmotionHappy / 100.0) * 0.1; // slight increase in WPM
      pState->fDerPitchExpress *= 1.0 + (pState->fEmotionHappy / 100.0) * 0.5;   // more expressive pitch
      pState->fDerDurationExpress *= 1.0 + (pState->fEmotionHappy / 100.0) * 0.5; // more expressive duration
      pState->fDerVolumeExpress *= 1.0 + (pState->fEmotionHappy / 100.0) * 0.5;  // more expressive volume
   }
   if (pState->fEmotionSad) {
      pState->fDerAvgPitch /= 1.0 + (pState->fEmotionSad / 100.0) * 0.1; // slight decrease in pitch
      pState->fDerVol /= 1.0 + (pState->fEmotionSad / 100.0) * 0.3; // slight decrease in volume
      pState->fDerMicropauses += pState->fEmotionSad / 100.0 * 0.5;   // fewer pauses
      pState->fDerWPM /= 1.0 + (pState->fEmotionSad / 100.0)*0.5; // slight increase in WPM
      pState->fDerPitchExpress /= 1.0 + (pState->fEmotionSad / 100.0);   // less expressive pitch
      pState->fDerDurationExpress /= 1.0 + (pState->fEmotionSad / 100.0) * 2.0; // less expressive duration
      pState->fDerVolumeExpress /= 1.0 + (pState->fEmotionSad / 100.0) * 2.0;  // less expressive volume
      pState->fDerAvgPitch /= 1.0 + (pState->fEmotionSad / 100.0) * 0.1; // slight decrease in pitch
   }
   if (pState->fEmotionAfraid) {
      pState->fDerShout = pState->fEmotionAfraid / 100.0 * 0.5;   // some tension in the voice
      pState->fDerWhisper = pState->fEmotionAfraid / 100.0 * 0.25;   // some whispering in the voice
      pState->fDerWPM *= 1.0 + (pState->fEmotionAfraid / 100.0)*0.2; // slight increase in WPM
      pState->fDerAvgPitch *= 1.0 + (pState->fEmotionAfraid / 100.0) * 0.2; // slight higher in pitch
   }
   if (pState->fEmotionDrunk) {
      pState->fEmotionDrunk = max(pState->fEmotionDrunk, 0);
      pState->fEmotionDrunk = min(pState->fEmotionDrunk, 100);
      pState->fDerMicropauses += pState->fEmotionDrunk / 100.0 * 0.5;   // fewer pauses
      pState->fDerWPM /= 1.0 + (pState->fEmotionDrunk / 100.0); // slight increase in WPM
      pState->fDerPitchExpress *= 1.0 + (pState->fEmotionDrunk / 100.0);   // more expressive pitch
   }

   // min/max
   pState->fDerPitchExpress = max(pState->fDerPitchExpress, CLOSE);
   pState->fDerDurationExpress = max(pState->fDerDurationExpress, CLOSE);
   pState->fDerVolumeExpress = max(pState->fDerVolumeExpress, CLOSE);
   pState->fDerVol *= 0.66;    // BUGFIX - Volume was too loud after bug-fix to feature extraction, so made it quieter
   pState->fDerVol = max(pState->fDerVol, CLOSE);
   pState->fDerAvgPitch = max(pState->fDerAvgPitch, 1);
   //pState->fDerAvgSyllableDur = max(pState->fDerAvgSyllableDur, 0.01);
}


/****************************************************************************
CSVParse - This takes an attribute (such as TPVolRel) which is a comma
separated variable, and parses it into a list of fp. If any comma items
are blank then their value is filled with -1234.

inputs
   PWSTR                psz - String
   PCListFixed           plParse - Initialized to sizeof(fp), and filled with
                           one value per element parsed.
returns
   none
*/
static void CSVParse (PWSTR psz, PCListFixed plParse)
{
   plParse->Init (sizeof(fp));

   while (TRUE) {
      // find the next comma
      PWSTR p;
      for (p = psz; *p; p++)
         if (*p == L',')
            break;

      fp fVal;
      fVal = _wtof (psz);

      if (p == psz)
         fVal = -1234;  // since blank
      plParse->Add (&fVal);

      if (!p[0])
         break;   // all done since at end
      psz = p+1;  // next
   }
}

/****************************************************************************
CTTSPunctPros::construcotr and destructor
*/
CTTSPunctPros::CTTSPunctPros (void)
{
   DWORD i;
   for (i = 0; i < PUNCTPROS; i++) {
      m_apBefore[i].Zero();
      m_apAfter[i].Zero();
   }

   m_wPunct = m_wFuncWord = 0;
}

CTTSPunctPros::~CTTSPunctPros (void)
{
   // do nothing
}


/****************************************************************************
CTTSPunctPros::MMLTo - Standard API
*/
PCMMLNode2 CTTSPunctPros::MMLTo (void)
{
   PCMMLNode2 pNode = new CMMLNode2;
   if (!pNode)
      return NULL;
   pNode->NameSet (gpszPunctPros);

   WCHAR szTemp[64];
   DWORD i;
   for (i = 0; i < PUNCTPROS; i++) {
      swprintf (szTemp, L"Before%d", (int)i);
      MMLValueSet (pNode, szTemp, &m_apBefore[i]);

      swprintf (szTemp, L"After%d", (int)i);
      MMLValueSet (pNode, szTemp, &m_apAfter[i]);
   }

   MMLValueSet (pNode, gpszPunct, (int)m_wPunct);
   MMLValueSet (pNode, gpszFuncWord, (int)m_wFuncWord);

   return pNode;
}

/****************************************************************************
CTTSPunctPros::MMLFrom - Standard API
*/
BOOL CTTSPunctPros::MMLFrom (PCMMLNode2 pNode)
{
   WCHAR szTemp[64];
   DWORD i;
   for (i = 0; i < PUNCTPROS; i++) {
      swprintf (szTemp, L"Before%d", (int)i);
      MMLValueGetPoint (pNode, szTemp, &m_apBefore[i]);

      swprintf (szTemp, L"After%d", (int)i);
      MMLValueGetPoint (pNode, szTemp, &m_apAfter[i]);
   }

   m_wPunct = (WORD) MMLValueGetInt (pNode, gpszPunct, (int)0);
   m_wFuncWord = (WORD) MMLValueGetInt (pNode, gpszFuncWord, (int)0);

   return TRUE;
}

/****************************************************************************
CTTSPunctPros::Clone - Standard API
*/
CTTSPunctPros *CTTSPunctPros::Clone (void)
{
   PCTTSPunctPros pNew = new CTTSPunctPros;

   memcpy (pNew->m_apBefore, m_apBefore, sizeof(m_apBefore));
   memcpy (pNew->m_apAfter, m_apAfter, sizeof(m_apAfter));
   pNew->m_wFuncWord = m_wFuncWord;
   pNew->m_wPunct = m_wPunct;

   return pNew;
}


/*************************************************************************************
TTSCacheDefaultGet - Get the default TTS file.

inputs
   PWSTR          pszFile - Filled with the file name
   DWORD          dwChars - Number of characters in pws
returns
   none
*/
void TTSCacheDefaultGet (PWSTR pszFile, DWORD dwChars)
{
   // at least fill with null
   pszFile[0] = 0;

   HKEY  hKey = NULL;
   DWORD dwDisp;
   char szTemp[512];
   szTemp[0] = 0;
   RegCreateKeyEx (HKEY_CURRENT_USER, RegBase(), 0, 0, REG_OPTION_NON_VOLATILE,
      KEY_READ | KEY_WRITE, NULL, &hKey, &dwDisp);
   if (hKey) {
      DWORD dw, dwType;
      LONG lRet;
      dw = sizeof(szTemp);
      lRet = RegQueryValueEx (hKey, gszKeyTTSFile, NULL, &dwType, (LPBYTE) szTemp, &dw);
      RegCloseKey (hKey);

      if (lRet != ERROR_SUCCESS)
         szTemp[0] = 0;
   }
   MultiByteToWideChar (CP_ACP, 0, szTemp, -1, pszFile, dwChars);

}


/*************************************************************************************
TTSCacheDefaultGet - Get the default TTS file and write it in a control

inputs
   PCEscPage         pPage - Page
   PWSTR             pszControl - Control name
returns
   none
*/
void TTSCacheDefaultGet (PCEscPage pPage, PWSTR pszControl)
{
   PCEscControl pControl = pPage->ControlFind (pszControl);
   if (!pControl)
      return;

   WCHAR szFile[256];
   TTSCacheDefaultGet (szFile, sizeof(szFile) / sizeof(WCHAR));
   pControl->AttribSet (Text(), szFile[0] ? szFile : L"(No TTS file)");
}

/*************************************************************************************
TTSCacheDefaultSet - Set the default TTS file.

inputs
   PWSTR          pszFile - File name
returns
   none
*/
void TTSCacheDefaultSet (PWSTR pszFile)
{
   // save to registry
   HKEY  hKey = NULL;
   DWORD dwDisp;
   RegCreateKeyEx (HKEY_CURRENT_USER, RegBase(), 0, 0, REG_OPTION_NON_VOLATILE,
      KEY_READ | KEY_WRITE, NULL, &hKey, &dwDisp);

   if (hKey) {
      char szTemp[512];
      WideCharToMultiByte (CP_ACP, 0, pszFile, -1, szTemp, sizeof(szTemp), 0, 0);
      RegSetValueEx (hKey, gszKeyTTSFile, 0, REG_SZ, (BYTE*) szTemp,
         (DWORD)strlen(szTemp)+1);

      RegCloseKey (hKey);
   }
}



/*************************************************************************************
TTSCacheDefaultUI - UI to select a new TTS voice.

inputs
   HWND           hWnd - To display the window off of
returns
   BOOL - TRUE if changed
*/
BOOL TTSCacheDefaultUI (HWND hWnd)
{
   WCHAR szFile[256];
   TTSCacheDefaultGet (szFile, sizeof(szFile)/sizeof(WCHAR));

   if (!TTSFileOpenDialog (hWnd, szFile, sizeof(szFile)/sizeof(WCHAR), FALSE))
      return FALSE;

   // save the file
   TTSCacheDefaultSet (szFile);
   return TRUE;
}

/*************************************************************************************
TTSCacheMemoryTouch - Touches randomly selected memory in a randomly selected TTS
engine. Call this once a second to ensure that the TTS voices aren't cached out.
*/
DLLEXPORT void TTSCacheMemoryTouch (void)
{
   // must have TTS engine
   if (!glPCMTTS.Num())
      return;

   // pick one
   DWORD dwIndex = (DWORD)rand() % glPCMTTS.Num();

   PCMTTS *ppl = (PCMTTS*) glPCMTTS.Get(0);

   ppl[dwIndex]->MemoryTouch();

}


/*************************************************************************************
TTSCacheMemoryFree - Frees a bit of the TTS memory that has been cached.
Call this once a second to reduce the amount of memory used by TTS voices.

inputs
   fp    fPercentJoin - Percentage of join-cost memory to free. 1.0 = 1%.
   fp    fPercentWave - Percentage of wave memory to free. 1.0 = 1%
*/
DLLEXPORT void TTSCacheMemoryFree (fp fPercentJoin, fp fPercentWaves)
{
   // must have TTS engine
   if (!glPCMTTS.Num())
      return;

   // pick one
   DWORD dwIndex = (DWORD)rand() % glPCMTTS.Num();

   PCMTTS *ppl = (PCMTTS*) glPCMTTS.Get(0);

   ppl[dwIndex]->MemoryCacheFree(fPercentJoin, fPercentWaves);

}

/*************************************************************************************
TTSCacheOpen - Opens a TTS using the TTS cache. If the item already
exists in memory then that one is used. If you open a TTS using this then
you should use MTTSCacheClose() instead of delete, so that several tools can
use the same TTS.

inputs
   PWSTR          pszFile - File to open. If this is NULL then the default TTS
                     voice is used, from TTSCacheDefaultGet().
   BOOL           fLoadIfNotExist - If TRUE (default) then load the TTS voice
                     if it isn't already loaded. If FALSE then fail.
   BOOL           fThreadSafe - If TRUE then this doesn't actually use the cache because
                     it's trying to be thread safe (since TTS engines aren't).
                     Usually, pass in FALSE since it's faster.
returns
   PCMTTS - TTS, or NULL if cant open
*/
DLLEXPORT PCMTTS TTSCacheOpen (PWSTR pszFile, BOOL fLoadIfNotExist, BOOL fThreadSafe)
{
   WCHAR szDefault[256];
   if (!pszFile) {
      TTSCacheDefaultGet (szDefault, sizeof(szDefault)/sizeof(WCHAR));
      if (!szDefault[0])
         return NULL;
      pszFile = szDefault;
   }

   if (!fThreadSafe) {
      if (!gfPCMTTSValid) {
         gfPCMTTSValid = TRUE;
         glPCMTTS.Init (sizeof(PCMTTS));
      }

      // look through existing ones
      PCMTTS *ppl = (PCMTTS*) glPCMTTS.Get(0);
      DWORD i;
      for (i = 0; i < glPCMTTS.Num(); i++) {
         if (!_wcsicmp(pszFile, ppl[i]->m_szFile)) {
            // found
            ppl[i]->m_dwRefCount++;
            return ppl[i];
         }
      } // i
   } // if !fThreadSafe

   // if not supposed to load, then fail here
   if (!fLoadIfNotExist)
      return NULL;

#ifdef _DEBUG
   DWORD dwStartTime = GetTickCount();
   __int64 iMem = EscMemoryAllocated (FALSE);
   OutputDebugStringW (L"\r\nTTSCacheOpen file = ");
   OutputDebugStringW (pszFile);
#endif

#if 0 // def _DEBUG
   WCHAR szTemp[128];
   swprintf (szTemp, L"\r\n\tCurMem A = %d K", (int)(EscMemoryAllocated(FALSE)/1024));
   OutputDebugStringW (szTemp);
#endif
   // else cant find
   PCMTTS pNew;
   pNew = new CMTTS;
   if (!pNew)
      return NULL;
#if 0 // def _DEBUG
   swprintf (szTemp, L"\r\n\tCurMem B = %d K", (int)(EscMemoryAllocated(FALSE)/1024));
   OutputDebugStringW (szTemp);
#endif
   if (!pNew->Open (pszFile, fThreadSafe)) {
      delete pNew;

#ifdef _DEBUG
   OutputDebugStringW (L"\r\n\tTTSCacheOpen failed");
#endif
      return NULL;
   }
#if 0 // def _DEBUG
   swprintf (szTemp, L"\r\n\tCurMem C = %d K", (int)(EscMemoryAllocated(FALSE)/1024));
   OutputDebugStringW (szTemp);
#endif
#ifdef _DEBUG
   char szaTemp[128];
   sprintf (szaTemp, "\r\n\tTTSCacheOpen: mem = %d K, time = %d ms\r\n", 
      (int)(((__int64)EscMemoryAllocated(FALSE) - iMem) / 1024), (int)(GetTickCount()-dwStartTime));
   OutputDebugString (szaTemp);
#endif

   // ref count
   pNew->m_dwRefCount = 1;

   if (!fThreadSafe)
      glPCMTTS.Add (&pNew);

   return pNew;
}


/*************************************************************************************
TTSCacheClose - Closes a TTS opened with MTTSCacheOpen()

NOTE: Because TTSs can take a very long time to load, this doesn't actually
free the TTS.

inputs
   PCMTTS        pTTS - TTS to close
   BOOL           fThreadSafe - Pass in the same value as when called TTSCacheOpen().
returns
   BOOL - TRUE if found and closed
*/
DLLEXPORT BOOL TTSCacheClose (PCMTTS pTTS)
{
   if (pTTS && pTTS->m_fThreadSafe) {
      delete pTTS;
      return TRUE;
   }

   // else, look through the cache

   // look through existing ones
   PCMTTS *ppl = (PCMTTS*) glPCMTTS.Get(0);
   DWORD i;
   for (i = 0; i < glPCMTTS.Num(); i++) {
      if (pTTS == ppl[i]) {
         // ref count
         if (pTTS->m_dwRefCount)
            pTTS->m_dwRefCount--;

#ifdef _DEBUG
         if (!pTTS->m_dwRefCount) {
            OutputDebugStringW (L"\r\nTTSCacheClose file = ");
            OutputDebugStringW (pTTS->m_szFile);
         }
#endif
         return TRUE;
      }
   } // i


   return FALSE;
}


/*************************************************************************************
TTSCacheErrorDisplay - Display and error that can't load the TTS.

inputs
   HWND              hWnd - Window
*/
void TTSCacheErrorDisplay (HWND hWnd)
{
   EscMessageBox (hWnd, L"Text-to-speech", L"The TTS voice can't be loaded.",
      L"Try selecting a TTS voice to use at the bottom of the page.", MB_OK);
}


/*************************************************************************************
TTSCacheSpellCheck - Use a TTS voice for spell checking

inputs
   PWSTR             pszSpeak - To spell check
   HWND              hWndProgress - Window to display the progress on. Will also display
                        an error if TTS couldn't be loaded.
returns
   BOOL - TRUE if success.
*/
BOOL TTSCacheSpellCheck (PWSTR pszSpeak, HWND hWndProgress)
{
   CTextParse TP;

   if (!pszSpeak)
      pszSpeak = L"";

   PCMTTS pTTS = TTSCacheOpen (NULL, TRUE, FALSE);
   if (!pTTS) {
      TTSCacheErrorDisplay (hWndProgress);
      return FALSE;
   }

   PCMLexicon pLex = pTTS->Lexicon ();
   if (!pLex) {
      TTSCacheClose (pTTS);
      TTSCacheErrorDisplay (hWndProgress);
      return FALSE;
   }

   TP.Init (pLex->LangIDGet(), pLex);
   PCMMLNode2 pNode = TP.ParseFromText (pszSpeak, FALSE, FALSE);
   if (!pNode) {
      TTSCacheClose (pTTS);
      TTSCacheErrorDisplay (hWndProgress);
      return FALSE;
   }

   DWORD i;
   CMem mem;
   MemZero (&mem);
   for (i = 0; i < pNode->ContentNum(); i++) {
      PWSTR psz;
      PCMMLNode2 pElem;
      pElem = NULL;
      if (!pNode->ContentEnum (i, &psz, &pElem))
         break;
      if (!pElem)
         continue;

      // only care about words
      psz = pElem->NameGet();
      if (!psz || _wcsicmp(psz, TP.Word()))
         continue;   // not important

      psz = pElem->AttribGetString(TP.Text());
      if (!psz)
         continue;

      // see if unknown
      if (!pLex->WordExists(psz)) {
         if (((PWSTR)mem.p)[0])
            MemCat (&mem, L", ");
         MemCat (&mem, psz);
      }
   } // while pNode->ContentNum()
   delete pNode;


   TTSCacheClose (pTTS);

   // display list
   if (!((PWSTR)mem.p)[0]) {
      EscMessageBox (hWndProgress, L"Spell check", L"No misspelled words",
         L"All the words seem to be spelled correctly.", MB_OK);
      return TRUE;
   }

   EscMessageBox (hWndProgress, L"Spell check", L"The follow words are misspelled:",
      (PWSTR)mem.p, MB_OK);

   return TRUE;
}

/*************************************************************************************
TTSCacheSpeak - Speak using the default TTS voice.

inputs
   PWSTR             pszSpeak - To speak
   BOOL              fTagged - If TRUE, speak tagged text
   BOOL              fDisablePCM - If TRUE then disable PCM, FALSE (best) then use PCM.
   HWND              hWndProgress - Window to display the progress on. Will also display
                        an error if TTS couldn't be loaded.
returns
   BOOL - TRUE if success.
*/
BOOL TTSCacheSpeak (PWSTR pszSpeak, BOOL fTagged, BOOL fDisablePCM, HWND hWndProgress)
{
   if (!pszSpeak)
      pszSpeak = L"";

   PCMTTS pTTS = TTSCacheOpen (NULL, TRUE, FALSE);
   if (!pTTS) {
      TTSCacheErrorDisplay (hWndProgress);
      return FALSE;
   }

   if (!gpTTSCacheSpeakWave)
      gpTTSCacheSpeakWave = new CM3DWave;
   if (!gpTTSCacheSpeakWave)
      return FALSE;  // shouldnt happen

   // clear
   gpTTSCacheSpeakWave->QuickPlayStop();
   gpTTSCacheSpeakWave->BlankWaveToSize (0, TRUE);


   // convert
   {
      CProgress Progress;
      Progress.Start (hWndProgress, "Synthesizing...");
      pTTS->SynthGenWave (gpTTSCacheSpeakWave, gpTTSCacheSpeakWave->m_dwSamplesPerSec, pszSpeak, fTagged, 1 /*iTTSQuality*/, fDisablePCM, &Progress);
   }
   TTSCacheClose (pTTS);

   // play
   gpTTSCacheSpeakWave->QuickPlay();
   return TRUE;
}


/*************************************************************************************
TTSCacheShutDown - Call this before the app shuts down. This frees up all
the TTSs loaded in the TTS cache.

NOTE: Call this before MLexiconCacheShutDown().
*/
DLLEXPORT void TTSCacheShutDown (void)
{
   // Will need special shutdown code so no problems with dependences,
   // shutting down ones with fewest ref counts first... of maybe derived ones
   // first
   static BOOL fIsShuttingDown = FALSE;
   if (fIsShuttingDown)
      return;  // do nothing because already shutting down
   fIsShuttingDown = TRUE;

   // stop speaking
   if (gpTTSCacheSpeakWave) {
      gpTTSCacheSpeakWave->QuickPlayStop();
      delete gpTTSCacheSpeakWave;
      gpTTSCacheSpeakWave = NULL;
   }

   // free up those with 0 ref-count
   PCMTTS *ppl;
   PCMTTS ptts;
   DWORD i;
   while (TRUE) {
      BOOL fFoundZeroRef = FALSE;
      ppl = (PCMTTS*) glPCMTTS.Get(0);

      for (i = 0; i < glPCMTTS.Num(); i++) {
         if (ppl[i]->m_dwRefCount)
            continue;

         // no ref count, so delete (remember this first, in case glPCMTTS is changed in the proecess)
         ptts = ppl[i];
         glPCMTTS.Remove (i);

         delete ptts;
         ppl = (PCMTTS*) glPCMTTS.Get(0); // just in case
         fFoundZeroRef = TRUE;
      } // i

      // if didn't find a zero ref, then exit
      if (!fFoundZeroRef)
         break;
   }

   // force delete
   ppl = (PCMTTS*) glPCMTTS.Get(0);
   for (i = 0; i < glPCMTTS.Num(); i++) {
      delete ppl[i];
      ppl[i] = NULL; // BUGFIX - Put in so dont crash when have subordinate lexs
   } // i
   glPCMTTS.Clear ();

   fIsShuttingDown = FALSE;
}


/*************************************************************************************
CMTTSTriPhoneAudio::Constructor and destructor
*/
CMTTSTriPhoneAudio::CMTTSTriPhoneAudio (void)
{
   m_dwWord = -1;
   m_wOrigWave = m_wOrigPhone = -1;
   m_bPhoneLeft = m_bPhoneRight = 0;
   memset (m_abRank, 0, sizeof(m_abRank));
   memset (m_aMMISpecific, 0, sizeof(m_aMMISpecific));
   m_dwMismatchAccuracy = 0;
   memset (m_abPhoneContiguous, 0, sizeof(m_abPhoneContiguous));
   m_dwTrimLeft = m_dwTrimRight = 0;
   m_wWordSylPos = 0;
   // m_wDuration = 0;
   //m_wFlags = 0;
   // m_iPitch = m_iPitchDelta = 0;
   m_fOrigPitchF0Extra = 0;
   m_fOrigPitchF0Fuji = 0;
   m_dwFuncWordGroup = 0;
   m_fEnergyAvg = 0;
   memset (m_afPitchConfidence, 0, sizeof(m_afPitchConfidence));
   m_fAccent = 0.0;
   m_fPitchDelta = 1;
   m_fPitchBulge = 1;
   memset (m_afPitchF0Extra, 0, sizeof(m_afPitchF0Extra));
   memset (m_afPitchF0Fuji, 0, sizeof(m_afPitchF0Fuji));
   memset (m_aiPitchF0Extra, 0, sizeof(m_aiPitchF0Extra));
   memset (m_aiPitchF0Fuji, 0, sizeof(m_aiPitchF0Fuji));
   m_dwFeatureStart = m_dwFeatureEnd = 0;
   m_iFeatureAdd = 0;
//   m_dwNumSRFEATURE = 0;
//   m_fEnergyStart = m_fEnergyEnd = 0;
//   m_fEnergyIsValid = FALSE;
   m_fReviewed = FALSE;
   m_fCenterEnergy = m_fLeftPitchF0Extra = m_fLeftPitchF0Fuji = m_fLeftEnergy = 0;
   m_dwLeftDuration = 0;

   m_dwUniqueID = (DWORD)-1;
   //m_wPhone = (WORD)-1;
   //m_wTriPhoneIndex = (WORD)-1;

//   m_fKeepMemory = FALSE;
//   m_pKeepMemoryCompressed = NULL;
//   m_dwKeepMemorySize = 0;

   m_fCached = FALSE;

//   m_pmemSRFEATURE = NULL;
//   m_pmemTTSFEATURECOMPEXTRA = NULL;

//   memset (m_aSRFeatBoundary, 0, sizeof(m_aSRFeatBoundary));
//   memset (m_afSRFeatBoundary, 0, sizeof(m_afSRFeatBoundary));

   DWORD i, j;
   for (j = 0; j < NUMJOINQUALITY; j++)
      for (i = 0; i < TTSDEMIPHONES; i++)
         m_alTPACONNECTIONERROR[j][i].Init (sizeof(TPACONNECTIONERROR));

}

CMTTSTriPhoneAudio::~CMTTSTriPhoneAudio (void)
{
//   if (m_pmemSRFEATURE) {
//      delete m_pmemSRFEATURE;
//      m_pmemSRFEATURE = NULL;
//   }
//   if (m_pmemTTSFEATURECOMPEXTRA) {
//      delete m_pmemTTSFEATURECOMPEXTRA;
//      m_pmemTTSFEATURECOMPEXTRA = NULL;
//   }
}



/*************************************************************************************
CMTTSTriPhoneAudio::MemoryTouch - Use to make sure TTS stays in memory
*/
DWORD CMTTSTriPhoneAudio::MemoryTouch (void)
{
   DWORD dwRet = 0;

   dwRet += m_wOrigWave;   // to touch this structure

   DWORD dwIndex = (DWORD)rand() % (sizeof(m_alTPACONNECTIONERROR) / sizeof(m_alTPACONNECTIONERROR[0][0]));
   DWORD dwIndex2;
   if (m_alTPACONNECTIONERROR[0][dwIndex].Num()) {
      dwIndex2 = (DWORD)rand() % m_alTPACONNECTIONERROR[0][dwIndex].Num();
      PTPACONNECTIONERROR ptce = (PTPACONNECTIONERROR)m_alTPACONNECTIONERROR[0][dwIndex].Get(dwIndex2);

      if (ptce)
#ifdef USESMALLTPACONNECTIONERROR
         dwRet += (DWORD) ptce->dwTCECompact;
#else
         dwRet += (DWORD) ptce->fValue;
#endif
   }

   return dwRet;
}

/*************************************************************************************
CMTTSTriPhoneAudio::Clone - Standard clone
*/
CMTTSTriPhoneAudio *CMTTSTriPhoneAudio::Clone (void)
{
   PCMTTSTriPhoneAudio pNew = new CMTTSTriPhoneAudio;
   if (!pNew)
      return NULL;

   pNew->m_dwFeatureStart = m_dwFeatureStart;
   pNew->m_dwFeatureEnd = m_dwFeatureEnd;
   pNew->m_iFeatureAdd = m_iFeatureAdd;
//   pNew->m_dwNumSRFEATURE = m_dwNumSRFEATURE;
   pNew->m_dwWord = m_dwWord;
   pNew->m_wOrigWave = m_wOrigWave;
   pNew->m_wOrigPhone = m_wOrigPhone;
   pNew->m_fReviewed = m_fReviewed;
//   pNew->m_fEnergyEnd = m_fEnergyEnd;
//   pNew->m_fEnergyStart = m_fEnergyStart;
//   pNew->m_fEnergyIsValid = m_fEnergyIsValid;
   pNew->m_bPhoneLeft = m_bPhoneLeft;
   memcpy (pNew->m_abPhoneContiguous, m_abPhoneContiguous, sizeof(m_abPhoneContiguous));
   pNew->m_bPhoneRight = m_bPhoneRight;
   pNew->m_dwTrimLeft = m_dwTrimLeft;
   pNew->m_dwTrimRight = m_dwTrimRight;
   memcpy (pNew->m_abRank, m_abRank, sizeof(m_abRank));
   memcpy (pNew->m_aMMISpecific, m_aMMISpecific, sizeof(m_aMMISpecific));
   pNew->m_dwMismatchAccuracy = m_dwMismatchAccuracy;
   // pNew->m_wDuration = m_wDuration;
   pNew->m_wWordSylPos = m_wWordSylPos;
   //pNew->m_wFlags = m_wFlags;
   memcpy (pNew->m_aiPitchF0Extra, m_aiPitchF0Extra, sizeof(m_aiPitchF0Extra));
   memcpy (pNew->m_aiPitchF0Fuji, m_aiPitchF0Fuji, sizeof(m_aiPitchF0Fuji));
   memcpy (pNew->m_afPitchF0Extra, m_afPitchF0Extra, sizeof(m_afPitchF0Extra));
   memcpy (pNew->m_afPitchF0Fuji, m_afPitchF0Fuji, sizeof(m_afPitchF0Fuji));

   // pNew->m_iPitch = m_iPitch;
   // pNew->m_iPitchDelta = m_iPitchDelta;
   pNew->m_dwUniqueID = (DWORD)-1;  // dont want to copy
   //pNew->m_wPhone = (WORD)-1; // dont want to copy
   //pNew->m_wTriPhoneIndex = (WORD)-1; // dont want to copy
   pNew->m_fOrigPitchF0Extra = m_fOrigPitchF0Extra;
   pNew->m_fOrigPitchF0Fuji = m_fOrigPitchF0Fuji;
   pNew->m_dwFuncWordGroup = m_dwFuncWordGroup;
   pNew->m_fEnergyAvg = m_fEnergyAvg;
   memcpy (pNew->m_afPitchConfidence, m_afPitchConfidence, sizeof(m_afPitchConfidence));
   pNew->m_fAccent = m_fAccent;
   pNew->m_fPitchDelta = m_fPitchDelta;
   pNew->m_fPitchBulge = m_fPitchBulge;
   pNew->m_fCenterEnergy = m_fCenterEnergy;
   pNew->m_fLeftEnergy = m_fLeftEnergy;
   pNew->m_fLeftPitchF0Extra = m_fLeftPitchF0Extra;
   pNew->m_fLeftPitchF0Fuji = m_fLeftPitchF0Fuji;
   pNew->m_dwLeftDuration = m_dwLeftDuration;
   memcpy (pNew->m_awTriPhone, m_awTriPhone, sizeof(m_awTriPhone));

   // NOTE: NOT cloniing m_alTPCONNECTERRORCache

   // NOTE: Clone NOT cloning the compressed data. May not be an issue

//   if (m_pmemSRFEATURE) {
//      if (!pNew->m_pmemSRFEATURE)
//         pNew->m_pmemSRFEATURE = new CMem;
//      if (!pNew->m_pmemSRFEATURE) {
//         delete pNew;
//         return NULL;
//      }
//      if (!pNew->m_pmemSRFEATURE->Required (m_dwNumSRFEATURE * sizeof(SRFEATURE))) {
//         delete pNew;
//         return NULL;
//      }
//      memcpy (pNew->m_pmemSRFEATURE->p, m_pmemSRFEATURE->p,
//         m_dwNumSRFEATURE * sizeof(SRFEATURE));
//   }
//   else if (pNew->m_pmemSRFEATURE) {
//      delete pNew->m_pmemSRFEATURE;
//      pNew->m_pmemSRFEATURE = NULL;
//   }

//   if (m_pmemTTSFEATURECOMPEXTRA) {
//      if (!pNew->m_pmemTTSFEATURECOMPEXTRA)
//         pNew->m_pmemTTSFEATURECOMPEXTRA = new CMem;
//      if (!pNew->m_pmemTTSFEATURECOMPEXTRA) {
//         delete pNew;
//         return NULL;
//      }
//      if (!pNew->m_pmemTTSFEATURECOMPEXTRA->Required (m_dwNumSRFEATURE * sizeof(TTSFEATURECOMPEXTRA))) {
//         delete pNew;
//         return NULL;
//      }
//      memcpy (pNew->m_pmemTTSFEATURECOMPEXTRA->p, m_pmemTTSFEATURECOMPEXTRA->p,
//         m_dwNumSRFEATURE * sizeof(TTSFEATURECOMPEXTRA));
//   }
//   else if (pNew->m_pmemTTSFEATURECOMPEXTRA) {
//      delete pNew->m_pmemTTSFEATURECOMPEXTRA;
//      pNew->m_pmemTTSFEATURECOMPEXTRA = NULL;
//   }

//   memcpy (pNew->m_aSRFeatBoundary, m_aSRFeatBoundary, sizeof(m_aSRFeatBoundary));
//   memcpy (pNew->m_afSRFeatBoundary, m_afSRFeatBoundary, sizeof(m_afSRFeatBoundary));

   return pNew;
}


static int _cdecl TPACONNECTIONERRORSort (const void *elem1, const void *elem2)
{
   TPACONNECTIONERROR *pdw1, *pdw2;
   pdw1 = (TPACONNECTIONERROR*) elem1;
   pdw2 = (TPACONNECTIONERROR*) elem2;

#ifdef USESMALLTPACONNECTIONERROR
   int iRet = (int)TCEUNIQUEIDEXTRACT(pdw1->dwTCECompact) - (int)TCEUNIQUEIDEXTRACT(pdw2->dwTCECompact);
//   int iRet = (int)pdw1->bPhone - (int)pdw2->bPhone;
//   if (iRet)
//      return iRet;
//   iRet = (int)pdw1->wTriPhoneIndex - (int)pdw2->wTriPhoneIndex;
   return iRet;
#else
   if (pdw1->pTPA > pdw2->pTPA)
      return 1;
   else if (pdw1->pTPA < pdw2->pTPA)
      return -1;
   else
      return 0;
#endif
}

/*************************************************************************************
CMTTSTriPhoneAudio::ConnectErrorCacheFree - Frees up the cache for this triphone.
*/
void CMTTSTriPhoneAudio::ConnectErrorCacheFree (void)
{
   DWORD i, dwDemi;
   for (i = 0; i < NUMJOINQUALITY; i++)
      for (dwDemi = 0; dwDemi < TTSDEMIPHONES; dwDemi++)
         m_alTPACONNECTIONERROR[i][dwDemi].ClearCompletely();
}

/*************************************************************************************
CMTTSTriPhoneAudio::ConnectErrorCacheSet - Sets a cache value for how much
of a connection penalty there is between the two units.

inputs
   DWORD                   dwJoinQuality - From 0..NUMJOINQUALITY-1
   PCMTTSTriPhoneAudio     *papPTA - List of triphone that caching with (previous unit)
   fp                      *pafValue - List of values to write, one per papPTA
   DWORD                   dwNum -Number of values in papPTA, pafValue
   DWORD                   dwSubPhone - 0..(TTSDEMIPHONES-1)
   DWORD                   dwMaxCache - Max number cached
returns
   BOOL - TRUE if success
*/
BOOL CMTTSTriPhoneAudio::ConnectErrorCacheSet (DWORD dwJoinQuality, PCMTTSTriPhoneAudio *papPTA, fp *pafValue, DWORD dwNum, DWORD dwSubPhone,
                                               DWORD dwMaxCache)
{
   DWORD dwNumCache = m_alTPACONNECTIONERROR[dwJoinQuality][dwSubPhone].Num();   // remember since will be added unsorted ones

   // cant add any more than the max
   dwMaxCache = max(dwMaxCache, 10);   // at least some
   if (dwMaxCache < dwNum)
      dwMaxCache = max(dwMaxCache, dwNum);   // must always be able to fit at least this many in
   dwNum = min(dwNum, dwMaxCache);

   // delete
   // BUGFIX - Make sure won't create too large a list that uses all the memory
   // potentially delete some
   PTPACONNECTIONERROR pFound;
   if (dwNumCache + dwNum > dwMaxCache) {
      DWORD dwDelete = dwNumCache + dwNum - dwMaxCache;
#ifndef TURNOFFRANDOM   // cant do because random off
      DWORD dwStart = (dwNumCache - dwDelete) ? (DWORD)rand() % (dwNumCache - dwDelete) : 0;
#else
      DWORD dwStart = 0;   // delete early ones
#endif

      pFound = (PTPACONNECTIONERROR)m_alTPACONNECTIONERROR[dwJoinQuality][dwSubPhone].Get(0);
      memmove (pFound + dwStart, pFound + (dwStart+dwDelete), (dwNumCache - dwDelete - dwStart) * sizeof(TPACONNECTIONERROR));
      // NOTE: Deleteion will NOT mess up sorting

      m_alTPACONNECTIONERROR[dwJoinQuality][dwSubPhone].Truncate (dwNumCache - dwDelete);
   }

#if 0 // def _DEBUG  // to test min/max
   static fp fMin = 0, fMax = 0;
#endif

   // add several at once so that only need to do one qsort
   // see if can find
   TPACONNECTIONERROR tpaFind;
   dwNumCache = m_alTPACONNECTIONERROR[dwJoinQuality][dwSubPhone].Num();
   memset (&tpaFind, 0, sizeof(tpaFind));
   DWORD i;
   m_alTPACONNECTIONERROR[dwJoinQuality][dwSubPhone].Required (dwNumCache + dwNum); // to speed to alloc
      // BUGFIX - Was only (dwNumCache + dwNum), but this might cause reallocing
      // BUGFIX - Took out max(dwNumCache + dwNum,dwMaxCache) because used too much memory
   for (i = 0; i < dwNum; i++) {
#ifdef USESMALLTPACONNECTIONERROR
      _ASSERTE (m_dwUniqueID != (DWORD)-1);
      _ASSERTE (papPTA[i]->m_dwUniqueID != (DWORD)-1);
      //_ASSERTE (m_wPhone != (WORD)-1);
      //_ASSERTE (m_wTriPhoneIndex != (WORD)-1);
      //_ASSERTE (papPTA[i]->m_wPhone != (WORD)-1);
      //_ASSERTE (papPTA[i]->m_wTriPhoneIndex != (WORD)-1);
      // _ASSERTE (papPTA[i]->m_wPhone == (dwSubPhone ? m_wPhone : (WORD)m_bPhoneLeft));
      //tpaFind.wTriPhoneIndex = papPTA[i]->m_wTriPhoneIndex;
      //tpaFind.bPhone = (BYTE) papPTA[i]->m_wPhone;

      fp f = pafValue[i] / (fp)TCEVALUEMAXASFP * (fp)TCEVALUEMAXASINT;
      _ASSERTE (f >= 0.0);
      _ASSERTE (f < TCEVALUEMAXASINT*2.0);   // to make sure not trimming off too much
      f = max(f, 0);
      f = min(f, (fp)TCEVALUEMAXASINT);
      //tpaFind.bValue = (BYTE)f;
      tpaFind.dwTCECompact = TCECOMPACTCREATE((DWORD)f, papPTA[i]->m_dwUniqueID);
#else
      tpaFind.pTPA = papPTA[i];
      tpaFind.fValue = pafValue[i];
#endif
      pFound = (PTPACONNECTIONERROR) bsearch (&tpaFind, m_alTPACONNECTIONERROR[dwJoinQuality][dwSubPhone].Get(0), dwNumCache, sizeof(TPACONNECTIONERROR), TPACONNECTIONERRORSort);
      if (pFound) {
         *pFound = tpaFind;
         continue;
      }

      // else, add. Don't worry about sorting yet because will sort as a block
      m_alTPACONNECTIONERROR[dwJoinQuality][dwSubPhone].Add (&tpaFind);

#if 0 // def _DEBUG  // to test
      fMin = min(fMin, tpaFind.fValue);
      fMax = max(fMax, tpaFind.fValue);
#endif
   } // i

   // else, add and qsort
   // NOTE: QSort should be fast with only one new element added
   if (dwNumCache != m_alTPACONNECTIONERROR[dwJoinQuality][dwSubPhone].Num())
      // sort these
      qsort (m_alTPACONNECTIONERROR[dwJoinQuality][dwSubPhone].Get(0), m_alTPACONNECTIONERROR[dwJoinQuality][dwSubPhone].Num(), sizeof(TPACONNECTIONERROR), TPACONNECTIONERRORSort);

   return TRUE;
}



/*************************************************************************************
CMTTSTriPhoneAudio::ConnectErrorCacheGet - Gets a cache value for how much
of a connection penalty there is between the two units.

inputs
   DWORD                   dwJoinQuality - From 0..NUMJOINQUALITY-1
   PCMTTSTriPhoneAudio     pPTA - Previous triphone that caching with
   DWORD                   dwSubPhone - 0..(TTSDEMIPHONES-1)
   fp                      *pfValue - Filled in with the connection value if successful
returns
   BOOL - TRUE if success. FALSE if cant find cache of connection
*/
BOOL CMTTSTriPhoneAudio::ConnectErrorCacheGet (DWORD dwJoinQuality, PCMTTSTriPhoneAudio pPTA, DWORD dwSubPhone, fp *pfValue)
{
   // see if can find
   TPACONNECTIONERROR tpaFind;
   PTPACONNECTIONERROR pFound;
   //memset (&tpaFind, 0, sizeof(tpaFind));

#ifdef USESMALLTPACONNECTIONERROR
   _ASSERTE (m_dwUniqueID != (DWORD)-1);
   _ASSERTE (pPTA->m_dwUniqueID != (DWORD)-1);
   //_ASSERTE (m_wPhone != (WORD)-1);
   //_ASSERTE (m_wTriPhoneIndex != (WORD)-1);
   //_ASSERTE (pPTA->m_wPhone != (WORD)-1);
   //_ASSERTE (pPTA->m_wTriPhoneIndex != (WORD)-1);
   // _ASSERTE (pPTA->m_wPhone == (dwSubPhone ? m_wPhone : (WORD)m_bPhoneLeft));
   //tpaFind.wTriPhoneIndex = pPTA->m_wTriPhoneIndex;
   //tpaFind.bPhone = (BYTE) pPTA->m_wPhone;
   tpaFind.dwTCECompact = TCECOMPACTCREATE(0, pPTA->m_dwUniqueID);
#else
   tpaFind.pTPA = pPTA;
#endif
   //tpaFind.bValue = bValue;
   pFound = (PTPACONNECTIONERROR) bsearch (&tpaFind, m_alTPACONNECTIONERROR[dwJoinQuality][dwSubPhone].Get(0), m_alTPACONNECTIONERROR[dwJoinQuality][dwSubPhone].Num(), sizeof(TPACONNECTIONERROR), TPACONNECTIONERRORSort);
   if (!pFound)
      return FALSE;

   // else, found
#ifdef USESMALLTPACONNECTIONERROR
   *pfValue = (fp)TCEVALUEEXTRACT(pFound->dwTCECompact) / (fp)TCEVALUEMAXASINT * TCEVALUEMAXASFP;
#else
   *pfValue = pFound->fValue;
#endif
   return TRUE;
}


/*************************************************************************************
CMTTSTriPhoneAudio::SRFEATUREGetRange - This gets the SRFEATURE from the object, assumes
that Decompress() has already been called.

inputs
   PCMTTS               pTTS - TTS so can get the audio
   PCMem                pMem - Memory to fill features into. Doing this so can incorporate m_iFeatureAdd.
   DWORD                dwFeatureStart - Starting feature
   DWORD                dwFeatureEnd - Ending feature
   DWORD                *pdwNumFeat - Filled with the number of features
   BOOL                 *pfToLeft - Filled with TRUE if returns PSRFEATURE[-1] is valid.
                           Can be NULL
   float                *pfPitch - Filled with the pitch at the point. Can be NULL
   PTTSFEATURECOMPEXTRA  *ppaTFCA - Filled with a point to the location of the pitch point.
                                    Can be NULL.
returns
   PSRFEATURE - SRFEATURE to use.
*/
PSRFEATURE CMTTSTriPhoneAudio::SRFEATUREGetRange (PCMTTS pTTS, PCMem pMem, DWORD dwFeatureStart, DWORD dwFeatureEnd, DWORD *pdwNumFeat,
                                             BOOL *pfToLeft, float *pfPitch, PTTSFEATURECOMPEXTRA *ppaTFCA)
{
   if (ppaTFCA)
      *ppaTFCA = NULL;
   if (pfPitch)
      *pfPitch = 0.0;
   *pdwNumFeat = 0;  // in case exit
   if (pfToLeft)
      *pfToLeft = FALSE;

   PCTTSWave pTW = pTTS->TTSFindWave (m_wOrigWave);
   if (!pTW)
      return NULL;
   DWORD dwPrior, dwAfter;
   PTTSFEATURECOMPEXTRA pTFCE;
   PSRFEATURE pSRFGet = pTW->GetSRFEATURE (&pTTS->m_acsTTSWave[m_wOrigWave % MAXRAYTHREAD], dwFeatureStart, dwFeatureEnd, &dwPrior, &dwAfter, &pTFCE);
   if (!pSRFGet)
      return NULL;

   // include a left and right feature if possible
   DWORD dwNumGotten = dwFeatureEnd - dwFeatureStart;
   if (dwPrior) {
      pSRFGet--;
      dwNumGotten++;
   }
   if (dwAfter)
      dwNumGotten++;
   if (!pMem->Required (dwNumGotten * sizeof(SRFEATURE)))
      return NULL;
   PSRFEATURE pSRFMod = (PSRFEATURE)pMem->p;
   memcpy (pSRFMod, pSRFGet, dwNumGotten * sizeof(SRFEATURE));

   // modify
   PSRFEATURE psr = pSRFMod;
   int iTemp;
   DWORD i, j;
   for (i = 0; i < dwNumGotten; i++, psr++) {
      for (j = 0; j < SRDATAPOINTS; j++) {
         iTemp = (int)psr->acVoiceEnergy[j] + m_iFeatureAdd;
         iTemp = max(iTemp, -127);
         iTemp = min(iTemp, 127);
         psr->acVoiceEnergy[j] = (char)iTemp;

         iTemp = (int)psr->acNoiseEnergy[j] + m_iFeatureAdd;
         iTemp = max(iTemp, -127);   // BUGFIX - Make this -100 so that when do RLE
                                    // encoding on MMLTo output will get lots of compression
         iTemp = min(iTemp, 127);
         psr->acNoiseEnergy[j] = (char)iTemp;
      } // j
   } // i

   PSRFEATURE pSROrig = pSRFMod + (dwPrior ? 1 : 0);
   if (pdwNumFeat)
      *pdwNumFeat = dwFeatureEnd - dwFeatureStart;
   if (pfToLeft)
      *pfToLeft = (pSROrig > pSRFMod);
   if (pfPitch)
      *pfPitch = pTFCE->fPitchF0All;
   if (ppaTFCA)
      *ppaTFCA = pTFCE;
   return pSROrig;
}

#if 0 // old implementation
/*************************************************************************************
CMTTSTriPhoneAudio::SRFEATUREGet - This gets the SRFEATURE from the object, assumes
that Decompress() has already been called.

inputs
   PCMTTS               pTTS - TTS so can get the audio
   PCMem                pMem - Memory to fill features into. Doing this so can incorporate m_iFeatureAdd.
   DWORD                dwSkip - Bit field of 0x01 if the m_dwSkipLeft can be usd,
                           0x02 if m_dwSkipRight can be used
   DWORD                dwDemiPhone - Demiphone number, or -1 if all
   DWORD                *pdwNumFeat - Filled with the number of features
   BOOL                 *pfToLeft - Filled with TRUE if returns PSRFEATURE[-1] is valid.
                           Can be NULL
   float                *pfPitch - Filled with the pitch at the point. Can be NULL
   PTTSFEATURECOMPEXTRA  *ppaTFCA - Filled with a point to the location of the pitch point.
                                    Can be NULL.
returns
   PSRFEATURE - SRFEATURE to use.
*/
PSRFEATURE CMTTSTriPhoneAudio::SRFEATUREGet (PCMTTS pTTS, PCMem pMem, DWORD dwSkip, DWORD dwDemiPhone, DWORD *pdwNumFeat,
                                             BOOL *pfToLeft, float *pfPitch, PTTSFEATURECOMPEXTRA *ppaTFCA)
{
   if (ppaTFCA)
      *ppaTFCA = NULL;
   if (pfPitch)
      *pfPitch = 0.0;
   *pdwNumFeat = 0;  // in case exit
   if (pfToLeft)
      *pfToLeft = FALSE;

   PCTTSWave pTW = pTTS->TTSFindWave (m_wOrigWave);
   if (!pTW)
      return NULL;
   DWORD dwPrior, dwAfter;
   PTTSFEATURECOMPEXTRA pTFCE;
   PSRFEATURE pSRFGet = pTW->GetSRFEATURE (&m_acsTTSWave[m_wOrigWave % MAXRAYTHREAD], m_dwFeatureStart, m_dwFeatureEnd, &dwPrior, &dwAfter, &pTFCE);
   if (!pSRFGet)
      return NULL;

   // include a left and right feature if possible
   DWORD dwNumGotten = m_dwFeatureEnd - m_dwFeatureStart;
   if (dwPrior) {
      pSRFGet--;
      dwNumGotten++;
   }
   if (dwAfter)
      dwNumGotten++;
   if (!pMem->Required (dwNumGotten * sizeof(SRFEATURE)))
      return NULL;
   PSRFEATURE pSRFMod = (PSRFEATURE)pMem->p;
   memcpy (pSRFMod, pSRFGet, dwNumGotten * sizeof(SRFEATURE));

   // modify
   PSRFEATURE psr = pSRFMod;
   int iTemp;
   DWORD i, j;
   for (i = 0; i < dwNumGotten; i++, psr++) {
      for (j = 0; j < SRDATAPOINTS; j++) {
         iTemp = (int)psr->acVoiceEnergy[j] + m_iFeatureAdd;
         iTemp = max(iTemp, -127);
         iTemp = min(iTemp, 127);
         psr->acVoiceEnergy[j] = (char)iTemp;

         iTemp = (int)psr->acNoiseEnergy[j] + m_iFeatureAdd;
         iTemp = max(iTemp, -127);   // BUGFIX - Make this -100 so that when do RLE
                                    // encoding on MMLTo output will get lots of compression
         iTemp = min(iTemp, 127);
         psr->acNoiseEnergy[j] = (char)iTemp;
      } // j
   } // i

   // BUGFIX - determine if can skip the left/right amount, to counteract segmentation vagueness
   BOOL fSkipLeft = (dwSkip & 0x01) ? TRUE : FALSE;
   BOOL fSkipRight = (dwSkip & 0x02) ? TRUE : FALSE;

   DWORD dwNumFeat = m_dwFeatureEnd - m_dwFeatureStart;
   if (!dwNumFeat)
      return NULL;  // shouldnt happen

   PSRFEATURE pSROrig = pSRFMod + (dwPrior ? 1 : 0);

   // skip
   if (fSkipLeft) {
      pSROrig += m_dwTrimLeft;
      dwNumFeat -= m_dwTrimLeft;
   }
   if (fSkipRight)
      dwNumFeat -= m_dwTrimRight;
   if (!dwNumFeat)
      return NULL;  // shouldnt happen

   // if have selected to get the entire wave then exit now
   if (dwDemiPhone == (DWORD)-1) {
      *pdwNumFeat = dwNumFeat;
      if (pfToLeft)
         *pfToLeft = (pSROrig > pSRFMod);

      // leave pitch as 0

      return pSROrig;
   }

   // else, figure out start and end
   DWORD dwSubStart = dwDemiPhone * dwNumFeat / TTSDEMIPHONES;
   DWORD dwSubEnd = (dwDemiPhone+1) * dwNumFeat / TTSDEMIPHONES;
   if (dwSubEnd <= dwSubStart)
      dwSubEnd = dwSubStart + 1; // so at least have soemthing
   if (dwSubEnd > dwNumFeat) {
      dwSubEnd = dwNumFeat;
      if (dwSubStart >= dwSubEnd)
         dwSubStart = dwSubEnd - 1;
   }
   pSROrig += dwSubStart;
   dwNumFeat = dwSubEnd - dwSubStart;
   if (!dwNumFeat)
      return NULL;  // shouldnt happen

   *pdwNumFeat = dwNumFeat;
   if (pfToLeft)
      *pfToLeft = (pSROrig > pSRFMod);

   if (pfPitch)
      *pfPitch = pTFCE[dwSubStart].fPitch;

   if (ppaTFCA)
      *ppaTFCA = pTFCE + dwSubStart;

   return pSROrig;
}
#endif // 0

/*************************************************************************************
CMTTSTriPhoneAudio::SRFEATURESet - Copies the SRFEATUREs from pSrc into the object.
In the process it normalizes based on fMaxEnergyForWave so that all the units will
have the same maximum energy. It also calculates the energy start and end.

Generally, when setting SRFEATUREs, should call this.

inputs
   PCMTTS               pTTS - TTS to use, where to get audio from
   DWORD                dwSentenceNum - Sentence number
   DWORD                dwFeatureStart - Start SRFEATURE.
   DWORD                dwFeatureEnd - Ending feature
   fp                   fMaxEnergyForWave - Maximum energy encountered in the training
                        data. Used to control boundary normalization
   fp                   fMaxEnergyForWaveMod - Potentially extra modifier. Used to normalize all.
//   PSRFEATURE           pLeft - SRFeature immediately before audio for triphone. If NULL, assume silence
//   PSRFEATURE           pRight - SRFeature immediately after audio for triphone. If NULL, assume silence

returns
   BOOL - TRUE if success
*/
BOOL CMTTSTriPhoneAudio::SRFEATURESet (PCMTTS pTTS, DWORD dwSentenceNum, DWORD dwFeatureStart, DWORD dwFeatureEnd,
                                       fp fMaxEnergyForWave, fp fMaxEnergyForWaveMod)
{
   // make sure exists
   PCTTSWave pTW = pTTS->TTSFindWave (dwSentenceNum);
   if (!pTW)
      return FALSE;
   DWORD dwPrior, dwAfter;
   PTTSFEATURECOMPEXTRA pTFCE;
   PSRFEATURE pSrc = pTW->GetSRFEATURE (&pTTS->m_acsTTSWave[dwSentenceNum % MAXRAYTHREAD],
      dwFeatureStart, dwFeatureEnd, &dwPrior, &dwAfter, &pTFCE);
   if (!pSrc)
      return FALSE;
   DWORD dwNum = dwFeatureEnd - dwFeatureStart;
   PSRFEATURE pLeft = dwPrior ? (pSrc - 1) : NULL;
   PSRFEATURE pRight = dwAfter ? (pSrc + dwNum) : NULL;



   // figure out how much to normalize by
   fMaxEnergyForWave = max(fMaxEnergyForWave, CLOSE);
   fp fScaleBy = THEORETICALMAXENERGY / fMaxEnergyForWave;
   fp fScaleByMod = THEORETICALMAXENERGY / fMaxEnergyForWaveMod;
   int iAdd = (int)(log10(fScaleByMod) * 20.0);

   // store away left/right audio so can make smoother transitions
   SRFEATURE cSilence;
   DWORD i; // , j;
   if (!pLeft || !pRight) {
      memset (&cSilence, 0, sizeof(cSilence));
      for (i = 0; i < SRDATAPOINTS; i++) {
         cSilence.acNoiseEnergy[i] = SRNOISEFLOOR;
         cSilence.acVoiceEnergy[i] = SRABSOLUTESILENCE;
      }

      if (!pLeft)
         pLeft = &cSilence;
      if (!pRight)
         pRight = &cSilence;
   }
   for (i = 0; i < (TTSDEMIPHONES+1)*2; i++) {
      PSRFEATURE psr, psrLeft;
      int iIndex = (int)(i/2) * (int)dwNum / (int)TTSDEMIPHONES;
      if (!(i%2))
         iIndex--;   // look back

      if (iIndex < 0) {
         psr = pLeft;
         psrLeft = NULL;
      }
      else if (iIndex >= (int)dwNum) {
         psr = pRight;
         psrLeft = dwNum ? (pSrc + (dwNum-1)) : pLeft;
      }
      else {
         psr = pSrc + iIndex;
         psrLeft = (iIndex ? &pSrc[iIndex-1] : pLeft);
      }

//      // store the energy away
//      SRFEATUREConvert (psr, psrLeft, &m_aSRFeatBoundary[i]);
//      m_afSRFeatBoundary[i] = SRFEATUREEnergySmall (TRUE, &m_aSRFeatBoundary[i], FALSE, TRUE);
//      m_afSRFeatBoundary[i] = max(m_afSRFeatBoundary[i], 1);   // so have something
//      SRFEATUREScale (&m_aSRFeatBoundary[i], PHONESAMPLENORMALIZED / m_afSRFeatBoundary[i]);
//      m_afSRFeatBoundary[i] *= fScaleBy;  // so all noramlized to what expected

   } // i

   m_dwFeatureStart = dwFeatureStart;
   m_dwFeatureEnd = dwFeatureEnd;
   m_iFeatureAdd = iAdd;

   // allocate memory for srfeature
//   if (!m_pmemSRFEATURE)
//      m_pmemSRFEATURE = new CMem;
//   if (!m_pmemSRFEATURE)
//      return FALSE;
//   m_memCompressed.m_dwCurPosn = 0; // since compression data will be invalid
//   m_dwKeepMemorySize = 0;

   // copy over
//   if (!m_pmemSRFEATURE->Required (dwNum * sizeof(SRFEATURE)))
//      return FALSE;
//   memcpy (m_pmemSRFEATURE->p, pSrc, dwNum * sizeof(SRFEATURE));
//   m_dwNumSRFEATURE = dwNum;
//   PSRFEATURE psr = (PSRFEATURE) m_pmemSRFEATURE->p;
//   int iTemp;
//   for (i = 0; i < dwNum; i++, psr++) {
//      for (j = 0; j < SRDATAPOINTS; j++) {
//         iTemp = (int)psr->acVoiceEnergy[j] + iAdd;
//         iTemp = max(iTemp, -127);
//         iTemp = min(iTemp, 127);
//         psr->acVoiceEnergy[j] = (char)iTemp;

//         iTemp = (int)psr->acNoiseEnergy[j] + iAdd;
//         iTemp = max(iTemp, -100);   // BUGFIX - Make this -100 so that when do RLE
                                    // encoding on MMLTo output will get lots of compression
//         iTemp = min(iTemp, 127);
//         psr->acNoiseEnergy[j] = (char)iTemp;
//      } // j
//   } // i

//   if (!m_pmemTTSFEATURECOMPEXTRA)
//      m_pmemTTSFEATURECOMPEXTRA = new CMem;
//   if (!m_pmemTTSFEATURECOMPEXTRA)
//      return FALSE;
//   if (!m_pmemTTSFEATURECOMPEXTRA->Required (dwNum * sizeof(TTSFEATURECOMPEXTRA)))
//      return FALSE;
//   PTTSFEATURECOMPEXTRA ptce = (PTTSFEATURECOMPEXTRA)m_pmemTTSFEATURECOMPEXTRA->p;
//   memset (ptce, 0, dwNum * sizeof(TTSFEATURECOMPEXTRA));
//   for (i = 0; i < dwNum; i++)
//      ptce[i].fPitch = pafPitch[i];

   // calculate the energy
   // BUGFIX - No longer used
   // CalcEnergy (); // let calcenergy recompress

   // done
   return TRUE;
}

/*************************************************************************************
TimeMaxCalc - Calculates time/phase max.

inputs
   DWORD          dwSkip - Amount to skip
   DWORD          dwSamples - Number of samples (m_dwNumSRFEATURE)
returns
   DWORD - Number of entries
*/
DWORD TimeMaxCalc (DWORD dwSkip, DWORD dwSamples)
{
   // if two or fewer, always that many
   if ((dwSamples <= 2) || (dwSkip <= 1))
      return dwSamples;

   return (dwSamples + (dwSkip-1)) / dwSkip + 1;
}

#if 0 // no longer support
/*************************************************************************************
CMTTSTriPhoneAudio::Compress - Compresses the memory in m_pmemSRFEATURE into m_memCompressed.
It then deletes m_pmemSRFEATURE. NOTE: This will NOT do anything if m_memCompressed
already has a m_dwCurPosn.

Also uses m_pmemTTSFEATURECOMPEXTRA.

returns
   BOOL - TRUE if success, FALSE if fail
*/
BOOL CMTTSTriPhoneAudio::Compress (void)
{
   if (!m_pmemSRFEATURE || !m_pmemTTSFEATURECOMPEXTRA)
      return TRUE;

   if (m_dwKeepMemorySize /*m_memCompressed.m_dwCurPosn*/) {
      // already data
      delete m_pmemSRFEATURE;
      m_pmemSRFEATURE = NULL;
      
      delete m_pmemTTSFEATURECOMPEXTRA;
      m_pmemTTSFEATURECOMPEXTRA = NULL;

      return TRUE;
   }

   // allocate a block large enough to compress to... which is basically
   // the size of the original data
   CMem mem;
   if (!mem.Required(m_dwNumSRFEATURE * (sizeof(SRFEATURE) + sizeof(TTSFEATURECOMPPCM) + sizeof(TTSFEATURECOMPEXTRA) )))
      return FALSE;

   // compression settings
   // BOOL fTimeCompress = (m_wFlags & TPMML_TIMECOMPRESS) ? TRUE : FALSE;
   // BOOL fTimePhaseCompress = FALSE;
   BOOL fFreqCompress;
   BOOL fIncludePCM;
   switch (m_wFlags & TPMML_FREQCOMPRESSMASK) {
   default:
   case TPMML_FREQCOMPRESS_0: // none
      fFreqCompress = FALSE;
      fIncludePCM = TRUE;
      break;
   case TPMML_FREQCOMPRESS_1: // some
      fFreqCompress = FALSE;
      fIncludePCM = FALSE;
      break;
   case TPMML_FREQCOMPRESS_2: // max
      fFreqCompress = TRUE;
      fIncludePCM = FALSE;
      break;
   } // switch

   // BUGFIX - More compression options
   DWORD dwTimeSkip = 1, dwTimePhaseSkip = 1;
   switch (m_wFlags & TPMML_TIMECOMPRESSMASK) {
   case TPMML_TIMECOMPRESS_0: // none
      dwTimeSkip = 1;
      dwTimePhaseSkip = 1;
      break;
   case TPMML_TIMECOMPRESS_1: // minimal
      dwTimeSkip = 2;
      dwTimePhaseSkip = 1;
      break;
   case TPMML_TIMECOMPRESS_2: // more
      dwTimeSkip = 3;
      dwTimePhaseSkip = 2;
      break;
   case TPMML_TIMECOMPRESS_3: // most
      dwTimeSkip = 4;
      dwTimePhaseSkip = 2;
      break;
   } // switch
   dwTimePhaseSkip = min(dwTimePhaseSkip, 2);   // this code can't handle a phase skip > 2

   // how much time
   DWORD dwTimeMax = TimeMaxCalc (dwTimeSkip, m_dwNumSRFEATURE);
   DWORD dwTimePhaseMax = TimeMaxCalc (dwTimePhaseSkip, m_dwNumSRFEATURE);

   // loop through all frequencies of voiced
   DWORD dwFreq, dwSize, dwTime;
   PSRFEATURE psr;
   char cAverage;
   dwFreq = fFreqCompress ? 3 : 0;
   while (dwFreq < SRDATAPOINTS) {
      // increase counter
      if (!fFreqCompress)
         dwSize = 1;   // no compression
      else if (dwFreq < SRDATAPOINTS/3)
         dwSize = 3;   // skip heaps in low frequencies
      else if (dwFreq < SRDATAPOINTS*2/3)
         dwSize = 2;   // skip some
      else
         dwSize = 1;   // high resolution

      // loop over all time
      for (dwTime = 0; dwTime < dwTimeMax; dwTime++) {
         psr = (PSRFEATURE) m_pmemSRFEATURE->p + ((dwTimeSkip>1) ?
            min(dwTime*dwTimeSkip, m_dwNumSRFEATURE-1) : dwTime);

         // get value
         switch (dwSize) {
         case 1:
            cAverage = psr->acVoiceEnergy[dwFreq];
            break;
         case 2:
            cAverage = AmplitudeToDb ((DbToAmplitude(psr->acVoiceEnergy[dwFreq-1]) + DbToAmplitude(psr->acVoiceEnergy[dwFreq])) / 2.0);
            break;
         case 3:
            cAverage = AmplitudeToDb ((DbToAmplitude(psr->acVoiceEnergy[dwFreq-1]) + DbToAmplitude(psr->acVoiceEnergy[dwFreq]) + 
               DbToAmplitude(psr->acVoiceEnergy[dwFreq+1])) / 3.0);
            break;
         }

         // write the value out
         ((char*)mem.p)[mem.m_dwCurPosn++] = cAverage;
      } // dwTime

      // increase counter
      dwFreq += dwSize;
   } // while dwFreq < SRDATAPOINTS

   // loop through all frequencies of unvoiced
   dwFreq = fFreqCompress ? 4 : 1;
   while (dwFreq < SRDATAPOINTS) {
      // increase counter
      if (!fFreqCompress)
         dwSize = 2;   // not much compression
      else if (dwFreq < SRDATAPOINTS/3)
         dwSize = 4;   // skip heaps in low frequencies
      else if (dwFreq < SRDATAPOINTS*2/3)
         dwSize = 3;   // skip some
      else
         dwSize = 2;   // high resolution

      // loop over all time
      for (dwTime = 0; dwTime < dwTimeMax; dwTime++) {
         psr = (PSRFEATURE) m_pmemSRFEATURE->p + ((dwTimeSkip>1) ?
            min(dwTime*dwTimeSkip, m_dwNumSRFEATURE-1) : dwTime);

         // get value
         switch (dwSize) {
         case 2:
            cAverage = AmplitudeToDb ((DbToAmplitude(psr->acNoiseEnergy[dwFreq-1]) + DbToAmplitude(psr->acNoiseEnergy[dwFreq])) / 2.0);
            break;
         case 3:
            cAverage = AmplitudeToDb ((DbToAmplitude(psr->acNoiseEnergy[dwFreq-1]) + DbToAmplitude(psr->acNoiseEnergy[dwFreq]) + 
               DbToAmplitude(psr->acNoiseEnergy[dwFreq+1])) / 3.0);
            break;
         case 4:
            cAverage = AmplitudeToDb ((DbToAmplitude(psr->acNoiseEnergy[dwFreq-2]) + DbToAmplitude(psr->acNoiseEnergy[dwFreq-1]) +
               DbToAmplitude(psr->acNoiseEnergy[dwFreq]) + DbToAmplitude(psr->acNoiseEnergy[dwFreq+1])) / 4.0);
            break;
         }

         // write the value out
         ((char*)mem.p)[mem.m_dwCurPosn++] = cAverage;
      } // dwTime

      // increase counter
      dwFreq += dwSize;
   } // while dwFreq < SRDATAPOINTS

   // loop through all frequencies of phase
   DWORD dwFreqMax = fFreqCompress ? (SRPHASENUM/2) : SRPHASENUM;
   for (dwFreq = 0; dwFreq < dwFreqMax; dwFreq++) {
      // loop over all time
      for (dwTime = 0; dwTime < dwTimePhaseMax; dwTime++) {
         psr = (PSRFEATURE) m_pmemSRFEATURE->p + ((dwTimePhaseSkip > 1) ?
            min(dwTime*dwTimePhaseSkip, m_dwNumSRFEATURE-1) : dwTime);

         // write the value out
         ((BYTE*)mem.p)[mem.m_dwCurPosn++] = psr->abPhase[dwFreq];
      } // dwTime
   } // dwFreq

   // write the pitch and PCM
   TTSFEATURECOMPEXTRA tce;
   TTSFEATURECOMPPCM tcp;
   memset (&tce, 0, sizeof(tce));
   memset (&tcp, 0, sizeof(tcp));
   for (dwTime = 0; dwTime < dwTimePhaseMax; dwTime++) {
      DWORD dwIndex = ((dwTimePhaseSkip > 1) ?
         min(dwTime*dwTimePhaseSkip, m_dwNumSRFEATURE-1) : dwTime);
      psr = (PSRFEATURE) m_pmemSRFEATURE->p + dwIndex;

      // write the pitch out
      tce.fPitch = ((PTTSFEATURECOMPEXTRA) m_pmemTTSFEATURECOMPEXTRA->p)[dwIndex].fPitch;
      memcpy ((BYTE*)mem.p + mem.m_dwCurPosn, &tce, sizeof(tce));
      mem.m_dwCurPosn += sizeof(tce);

      // write out the PCM
      if (fIncludePCM) {
         tcp.bPCMHarmFadeStart = psr->bPCMHarmFadeStart;
         tcp.bPCMHarmFadeFull = psr->bPCMHarmFadeFull;
         tcp.bPCMHarmNyquist = psr->bPCMHarmNyquist;
         tcp.bPCMFill = psr->bPCMFill;
         tcp.fPCMScale = psr->fPCMScale;
         memcpy (tcp.acPCM, psr->acPCM, sizeof(tcp.acPCM));

         memcpy ((BYTE*)mem.p + mem.m_dwCurPosn, &tcp, sizeof(tcp));
         mem.m_dwCurPosn += sizeof(tcp);
      }
   } // dwTime


   // compress
   m_memCompressed.m_dwCurPosn = m_dwKeepMemorySize = 0;
   if (RLEEncode ((PBYTE)mem.p, mem.m_dwCurPosn, 1, &m_memCompressed))
      return FALSE;
   m_dwKeepMemorySize = m_memCompressed.m_dwCurPosn;
   m_pKeepMemoryCompressed = m_memCompressed.p;
   m_fKeepMemory = FALSE;

   delete m_pmemSRFEATURE;
   m_pmemSRFEATURE = NULL;
   delete m_pmemTTSFEATURECOMPEXTRA;
   m_pmemTTSFEATURECOMPEXTRA = NULL;
   return TRUE;
}
#endif // 0 - no longer support


#if 0 // no longer support
/*************************************************************************************
CMTTSTriPhoneAudio::Decompress - Decompresses the memory in m_memCompressed into m_pmemSRFEATURE.
NOTE: This will NOT do anything if m_pmemSRFEATURE already has anything.

Also fils in m_pmemTTSFEATURECOMPEXTRA.


returns
   BOOL - TRUE if success, FALSE if fail
*/
BOOL CMTTSTriPhoneAudio::Decompress (void)
{
   if (m_pmemSRFEATURE || m_pmemTTSFEATURECOMPEXTRA)
      return TRUE;   // already data

   // create new memory
   m_pmemSRFEATURE = new CMem;
   if (!m_pmemSRFEATURE)
      return FALSE;

   m_pmemTTSFEATURECOMPEXTRA = new CMem;
   if (!m_pmemTTSFEATURECOMPEXTRA)
      return FALSE;

   if (!m_dwKeepMemorySize /*m_memCompressed.m_dwCurPosn*/)
      return TRUE;   // no data

   // allocate memory for srfeature
   if (!m_pmemSRFEATURE->Required (m_dwNumSRFEATURE * (sizeof(SRFEATURE) + sizeof(TTSFEATURECOMPPCM) + sizeof(TTSFEATURECOMPEXTRA) ) ))
      return FALSE;  // error
   
   if (!m_pmemTTSFEATURECOMPEXTRA->Required (m_dwNumSRFEATURE * sizeof(TTSFEATURECOMPEXTRA)))
      return FALSE;

   // compression settings
   // BOOL fTimeCompress = (m_wFlags & TPMML_TIMECOMPRESS) ? TRUE : FALSE;
   // BOOL fTimePhaseCompress = FALSE;
   BOOL fFreqCompress;
   BOOL fIncludePCM;
   switch (m_wFlags & TPMML_FREQCOMPRESSMASK) {
   default:
   case TPMML_FREQCOMPRESS_0: // none
      fFreqCompress = FALSE;
      fIncludePCM = TRUE;
      break;
   case TPMML_FREQCOMPRESS_1: // some
      fFreqCompress = FALSE;
      fIncludePCM = FALSE;
      break;
   case TPMML_FREQCOMPRESS_2: // max
      fFreqCompress = TRUE;
      fIncludePCM = FALSE;
      break;
   } // switch

   // BUGFIX - More compression options
   DWORD dwTimeSkip = 1, dwTimePhaseSkip = 1;
   switch (m_wFlags & TPMML_TIMECOMPRESSMASK) {
   case TPMML_TIMECOMPRESS_0: // none
      dwTimeSkip = 1;
      dwTimePhaseSkip = 1;
      break;
   case TPMML_TIMECOMPRESS_1: // minimal
      dwTimeSkip = 2;
      dwTimePhaseSkip = 1;
      break;
   case TPMML_TIMECOMPRESS_2: // more
      dwTimeSkip = 3;
      dwTimePhaseSkip = 2;
      break;
   case TPMML_TIMECOMPRESS_3: // most
      dwTimeSkip = 4;
      dwTimePhaseSkip = 2;
      break;
   } // switch
   dwTimePhaseSkip = min(dwTimePhaseSkip, 2);   // this code can't handle a phase skip > 2


   // create a remap table for voiced and unvoiced
   DWORD adwRemapVoiced[SRDATAPOINTS], adwRemapUnvoiced[SRDATAPOINTS];
   memset (adwRemapVoiced, 0, sizeof(adwRemapVoiced));
   memset (adwRemapUnvoiced, 0, sizeof(adwRemapUnvoiced));
   DWORD dwFreq, dwSize, dwVoiced, dwUnvoiced;
   dwFreq = fFreqCompress ? 3 : 0;
   dwVoiced = 0;
   while (dwFreq < SRDATAPOINTS) {
      // increase counter
      if (!fFreqCompress)
         dwSize = 1;   // no compression
      else if (dwFreq < SRDATAPOINTS/3)
         dwSize = 3;   // skip heaps in low frequencies
      else if (dwFreq < SRDATAPOINTS*2/3)
         dwSize = 2;   // skip some
      else
         dwSize = 1;   // high resolution

      adwRemapVoiced[dwVoiced++] = dwFreq;
      dwFreq += dwSize;
   }

   dwFreq = fFreqCompress ? 4 : 1;
   dwUnvoiced = 0;
   while (dwFreq < SRDATAPOINTS) {
      // increase counter
      if (!fFreqCompress)
         dwSize = 2;   // not much compression
      else if (dwFreq < SRDATAPOINTS/3)
         dwSize = 4;   // skip heaps in low frequencies
      else if (dwFreq < SRDATAPOINTS*2/3)
         dwSize = 3;   // skip some
      else
         dwSize = 2;   // high resolution

      adwRemapUnvoiced[dwUnvoiced++] = dwFreq;
      dwFreq += dwSize;
   }

   DWORD dwTimeMax = TimeMaxCalc (dwTimeSkip, m_dwNumSRFEATURE);
   DWORD dwTimePhaseMax = TimeMaxCalc (dwTimePhaseSkip, m_dwNumSRFEATURE);

   DWORD dwFreqMax = fFreqCompress ? (SRPHASENUM/2) : SRPHASENUM;

   // run-length decode
   size_t dwUsed;
   CMem mem;
   if (RLEDecode ((PBYTE)m_pKeepMemoryCompressed /*m_memCompressed.p*/, m_dwKeepMemorySize /*m_memCompressed.m_dwCurPosn*/, 1, &mem, &dwUsed))
      return FALSE;
   if (mem.m_dwCurPosn != dwTimeMax * (dwVoiced + dwUnvoiced) + dwTimePhaseMax * (dwFreqMax + sizeof(TTSFEATURECOMPEXTRA) + (fIncludePCM ? sizeof(TTSFEATURECOMPPCM) : 0)) ) {
      delete m_pmemSRFEATURE;
      m_pmemSRFEATURE = NULL;
      delete m_pmemTTSFEATURECOMPEXTRA;
      m_pmemTTSFEATURECOMPEXTRA = NULL;
      return FALSE;
   }

   // setup for loops
   char *pc = (char*)mem.p;
   PSRFEATURE psr = (PSRFEATURE)m_pmemSRFEATURE->p;
   DWORD i;

   // decompress voiced frequency wise
   DWORD dwTime, dwLast, dwThis, dwMap;
   for (dwFreq = 0; dwFreq < dwVoiced; dwFreq++) {
      dwLast = -1;
      for (dwTime = 0; dwTime < dwTimeMax; dwTime++) {
         dwThis = ((dwTimeSkip>1) ? min(dwTime*dwTimeSkip, m_dwNumSRFEATURE-1) : dwTime);
         psr = (PSRFEATURE) m_pmemSRFEATURE->p + dwThis;

         // write the value out
         dwMap = adwRemapVoiced[dwFreq];
         psr->acVoiceEnergy[dwMap] = *pc;

         // see if skipped entries
         if ((dwLast != (DWORD)-1) && (dwLast+1 < dwThis)) {
            DWORD dwDelta = dwThis - dwLast;
            for (i = 1; i < dwDelta; i++)
               psr[(int)i - (int)dwDelta].acVoiceEnergy[dwMap] = AmplitudeToDb (
                  DbToAmplitude(psr[-(int)dwDelta].acVoiceEnergy[dwMap]) * (fp)(dwDelta - i) / (fp)dwDelta +
                  DbToAmplitude(*pc) * (fp)i / (fp)dwDelta);
         }

         dwLast = dwThis;
         pc++;
      } // dwTime
   } // dwFreq

   // because decompress unvoiced may not fill in the highest value, do so
   for (dwTime = 0; dwTime < dwTimeMax; dwTime++) {
      dwThis = ((dwTimeSkip>1) ? min(dwTime*dwTimeSkip, m_dwNumSRFEATURE-1) : dwTime);
      psr = (PSRFEATURE) m_pmemSRFEATURE->p + dwThis;
      psr->acNoiseEnergy[SRDATAPOINTS-1] = 111;   // special code
   }
   // decompress unvoiced frequency wise
   for (dwFreq = 0; dwFreq < dwUnvoiced; dwFreq++) {
      dwLast = -1;
      for (dwTime = 0; dwTime < dwTimeMax; dwTime++) {
         dwThis = ((dwTimeSkip>1) ? min(dwTime*dwTimeSkip, m_dwNumSRFEATURE-1) : dwTime);
         psr = (PSRFEATURE) m_pmemSRFEATURE->p + dwThis;

         // write the value out
         dwMap = adwRemapUnvoiced[dwFreq];
         psr->acNoiseEnergy[dwMap] = *pc;

         // see if skipped entries
         if ((dwLast != (DWORD)-1) && (dwLast+1 < dwThis)) {
            DWORD dwDelta = dwThis - dwLast;
            for (i = 1; i < dwDelta; i++)
               psr[(int)i - (int)dwDelta].acNoiseEnergy[dwMap] = AmplitudeToDb (
                  DbToAmplitude(psr[-(int)dwDelta].acNoiseEnergy[dwMap]) * (fp)(dwDelta - i) / (fp)dwDelta +
                  DbToAmplitude(*pc) * (fp)i / (fp)dwDelta);
         }

         dwLast = dwThis;
         pc++;
      } // dwTime
   } // dwFreq

   // check last value
   for (dwTime = 0; dwTime < dwTimeMax; dwTime++) {
      dwThis = ((dwTimeSkip>1) ? min(dwTime*dwTimeSkip, m_dwNumSRFEATURE-1) : dwTime);
      psr = (PSRFEATURE) m_pmemSRFEATURE->p + dwThis;
      if (psr->acNoiseEnergy[SRDATAPOINTS-1] == 111)  // special code
         psr->acNoiseEnergy[SRDATAPOINTS-1] = psr->acNoiseEnergy[SRDATAPOINTS-2];
      else
         break;   // if -111 case didn't get called then never weill
   }

   // decompress phase frequency wise
   psr = (PSRFEATURE) m_pmemSRFEATURE->p;
   for (dwTime = 0; dwTime < m_dwNumSRFEATURE; dwTime++, psr++) {
      // BUGFIX - randomize phase instead of zeroing i
      //memset (psr->abPhase, 0, sizeof(psr->abPhase));
      psr->abPhase[0] = 0; // always

      for (dwFreq = 1; dwFreq < SRPHASENUM; dwFreq++)
         psr->abPhase[dwFreq] = (BYTE)rand();

   } // dwTime
   for (dwFreq = 0; dwFreq < dwFreqMax; dwFreq++) {
      dwLast = -1;
      for (dwTime = 0; dwTime < dwTimePhaseMax; dwTime++) {
         dwThis = ((dwTimePhaseSkip > 1) ? min(dwTime*dwTimePhaseSkip, m_dwNumSRFEATURE-1) : dwTime);
         psr = (PSRFEATURE) m_pmemSRFEATURE->p + dwThis;

         // write the value out
         psr->abPhase[dwFreq] = (BYTE)(*pc);

         // if last+2 == dwThis then skipped an entry, so write that in too
         if ((dwLast != (DWORD)-1) && (dwLast+2 == dwThis)) {
            DWORD dwA = (BYTE) psr[-2].abPhase[dwFreq];  // BUGFIX - was wrong
#if 0 // BUGFIX - DON'T interpolate phase, especially in this way
            DWORD dwA = (BYTE)(*pc);
            DWORD dwB = (BYTE) psr[-2].abPhase[dwFreq];  // BUGFIX - was wrong
            if (max(dwA,dwB) - min(dwA,dwB) >= 128) {
               // flipped a phase, so average
               if (dwA < dwB)
                  dwA += 0x100;
               else
                  dwB += 0x100;
            }
            dwA = (dwA + dwB) / 2;
#endif // 0
            psr[-1].abPhase[dwFreq] = (BYTE)dwA;
         }

         dwLast = dwThis;
         pc++;
      } // dwTime
   } // dwFreq

   // read the pitch and PCM
   PTTSFEATURECOMPEXTRA ptce;
   PTTSFEATURECOMPPCM ptcp;
   dwLast = (DWORD)-1;
   for (dwTime = 0; dwTime < dwTimePhaseMax; dwTime++) {
      dwThis = ((dwTimePhaseSkip > 1) ? min(dwTime*dwTimePhaseSkip, m_dwNumSRFEATURE-1) : dwTime);
      psr = (PSRFEATURE) m_pmemSRFEATURE->p + dwThis;

      // pitch
      ptce = (PTTSFEATURECOMPEXTRA)pc;
      pc += sizeof(*ptce);
      ((PTTSFEATURECOMPEXTRA) m_pmemTTSFEATURECOMPEXTRA->p)[dwThis] = *ptce;

      if (fIncludePCM) {
         // PCM
         ptcp = (PTTSFEATURECOMPPCM)pc;
         pc += sizeof(*ptcp);

         psr->bPCMHarmFadeStart = ptcp->bPCMHarmFadeStart;
         psr->bPCMHarmFadeFull = ptcp->bPCMHarmFadeFull;
         psr->bPCMHarmNyquist = ptcp->bPCMHarmNyquist;
         psr->bPCMFill = ptcp->bPCMFill;
         psr->fPCMScale = ptcp->fPCMScale;
         memcpy (psr->acPCM, ptcp->acPCM, sizeof(psr->acPCM));
      }
      else {
         psr->bPCMFill = psr->bPCMHarmFadeFull = psr->bPCMHarmFadeStart = psr->bPCMHarmNyquist = 0;
         psr->fPCMScale = 0;
         memset (psr->acPCM, 0, sizeof(psr->acPCM));
      }

      // if last+2 == dwThis then skipped an entry, so write that in too
      if ((dwLast != (DWORD)-1) && (dwLast+2 == dwThis)) {
         psr[-1].bPCMHarmFadeStart = psr[-2].bPCMHarmFadeStart;
         psr[-1].bPCMHarmFadeFull = psr[-2].bPCMHarmFadeFull;
         psr[-1].bPCMHarmNyquist = psr[-2].bPCMHarmNyquist;
         psr[-1].bPCMFill = psr[-2].bPCMFill;
         psr[-1].fPCMScale = psr[-2].fPCMScale;
         memcpy (psr[-1].acPCM, psr[-2].acPCM, sizeof(psr[-1].acPCM));

         ((PTTSFEATURECOMPEXTRA) m_pmemTTSFEATURECOMPEXTRA->p)[dwThis-1] = ((PTTSFEATURECOMPEXTRA) m_pmemTTSFEATURECOMPEXTRA->p)[dwThis-2];
      }

      dwLast = dwThis;
   } // dwTime


   // fill in gaps between frequencies
   psr = (PSRFEATURE) m_pmemSRFEATURE->p;
   int iLastValue, iThisValue;
   for (dwTime = 0; dwTime < m_dwNumSRFEATURE; dwTime++, psr++) {
      for (dwVoiced = 0; dwVoiced < 2; dwVoiced++) {
         DWORD *padwMap = dwVoiced ? adwRemapVoiced : adwRemapUnvoiced;
         char *pac = dwVoiced ? psr->acVoiceEnergy : psr->acNoiseEnergy;

         DWORD dwLast = (DWORD)-1, dwCur;
         iLastValue = -100;   // to silence
         for (dwMap = 0; dwMap < SRDATAPOINTS; dwMap++, dwLast = dwCur) {
            dwCur = padwMap[dwMap];
            if (dwCur <= dwLast+1)
               break;   // end of mapping, or already on 1-step so nothing to interpolate

            iThisValue = pac[dwCur];

            switch (dwCur - dwLast) {
            case 2:  // skipped one
               pac[dwCur-1] = AmplitudeToDb ((DbToAmplitude(iLastValue) + DbToAmplitude(iThisValue))/2.0);
               break;
            case 3:  // skipped two
               pac[dwCur-2] = AmplitudeToDb ((DbToAmplitude(iLastValue)*2 + DbToAmplitude(iThisValue))/3.0);
               pac[dwCur-1] = AmplitudeToDb ((DbToAmplitude(iLastValue) + DbToAmplitude(iThisValue)*2)/3.0);
               break;
            case 4:  // skipped three
               pac[dwCur-3] = AmplitudeToDb ((DbToAmplitude(iLastValue)*3 + DbToAmplitude(iThisValue)*1)/4.0);
               pac[dwCur-2] = AmplitudeToDb ((DbToAmplitude(iLastValue)*2 + DbToAmplitude(iThisValue)*2)/4.0);
               pac[dwCur-1] = AmplitudeToDb ((DbToAmplitude(iLastValue)*1 + DbToAmplitude(iThisValue)*3)/4.0);
               break;
            case 5:  // skipped four
               pac[dwCur-4] = AmplitudeToDb ((DbToAmplitude(iLastValue)*4 + DbToAmplitude(iThisValue)*1)/5.0);
               pac[dwCur-3] = AmplitudeToDb ((DbToAmplitude(iLastValue)*3 + DbToAmplitude(iThisValue)*2)/5.0);
               pac[dwCur-2] = AmplitudeToDb ((DbToAmplitude(iLastValue)*2 + DbToAmplitude(iThisValue)*3)/5.0);
               pac[dwCur-1] = AmplitudeToDb ((DbToAmplitude(iLastValue)*1 + DbToAmplitude(iThisValue)*4)/5.0);
               break;
            } // switch

            iLastValue = iThisValue;
         } // dwMap
      } // dwVoiced
   } // dwTime

   return TRUE;
}
#endif // 0 - no longer support


#if 0 // no longer used
/*************************************************************************************
CMTTSTriPhoneAudio::CalcEnergyIfNecessary - If the energy isn't valid,
m_fEnergyIsValid, then calls CalcEnergy()
*/
void CMTTSTriPhoneAudio::CalcEnergyIfNecessary (void)
{
   if (!m_fEnergyIsValid)
      CalcEnergy ();
}
#endif


#if 0 // no longer used
/*************************************************************************************
CMTTSTriPhoneAudio::CalcEnergy - Calculates the energy levels for the 1st and last
SR features so that when they're abutted against one another don't have any problems.
*/
void CMTTSTriPhoneAudio::CalcEnergy (void)
{
   if (!m_dwNumSRFEATURE || !Decompress()) {
      m_fEnergyStart = m_fEnergyEnd = 0;
      m_fEnergyIsValid = TRUE;
      return;
   }

   PSRFEATURE psr = (PSRFEATURE) m_pmemSRFEATURE->p;
   m_fEnergyStart = SRFEATUREEnergy (FALSE, psr);
   m_fEnergyEnd = SRFEATUREEnergy(FALSE, psr + (m_dwNumSRFEATURE-1));
   m_fEnergyIsValid = TRUE;

   // recompress the data
   Compress ();
}
#endif // 0

// WAVESEGMML - For MMLTo/From
typedef struct {
   DWORD             dwFeatureStart;   // start feature
   DWORD             dwFeatureEnd;     // end feature (exclusive)
   DWORD             dwSamplesPerSec;  // sampling rate
   WORD              wFlags;        // flags, TPMML_XXX
   WORD              wSRSkip;          // samples per SRFeature
} WAVESEGMML, *PWAVESEGMML;

// TPMML - For MMLTo/From.
typedef struct {
   DWORD             dwWord;      // word number this is specific to, or -1 if word independent
   float             fEnergyAvg;    // average energy for the unit, after some adjustment so that max energy in wave is PHONESAMPLENORMALIZED
   float             fOrigPitchF0Extra;    // original pitch for the unit
   float             fOrigPitchF0Fuji;    // original pitch for the unit
   float             fPitchDelta;   // ratio of pitch on right of phoneme to pitch on left
   float             fPitchBulge;   // pitch bulge in center. ratio. 2 = octave above, 1 = no change, 1/2 = octave below
   float             afPitchF0Extra[TPAPITCHPOINTS];    // pitch over phoneme, in hz
   float             afPitchF0Fuji[TPAPITCHPOINTS];      // pitch over phoneme, in Hz
   float             afPitchConfidence[TTSDEMIPHONES];   // how confident about the pitch. 0.0 to 1.0
   float             fAccent;       // accent of phoneme, from -1 to 1
   float             fCenterEnergy; // energy of this, used for continuity
   float             fLeftEnergy;   // left unit energy, 0 if silence
   float             fLeftPitchF0Extra;    // left unit pitch, 0 if silence
   float             fLeftPitchF0Fuji;    // left unit pitch, 0 if silence
   DWORD             dwLeftDuration;   // left duration in SRFEATUREs, 0 if silence
   // WORD              wDuration;   // typical duration, in SRFEATURE units
   short             iPitch;      // pitch increase/decraase. 0=no change, 1000=1 octave higher, -1000=1 octave lower, etc.
   short             iPitchDelta;   // change in pitch over phoneme, same scale as iPitch
   short             iPitchBulge;   // bulge in pitch. Same scale as iPitch
   BYTE              abPhoneContiguous[8]; // array of 8 phonemes that immediately follow this one. Phone numbers
                                    // are +1 from unsort-phone number. phoneme 0 is blank (or end of list)
   BYTE              bPhoneLeft;    // phoneme to left
   BYTE              bPhoneRight;   // phoneme to right
   WORD              wWordSylPos;    // position within word, 0=middle, 1=start, 2=end, 4=start&end
                                    // WORDSYL_XXX flags
   BYTE              abRank[TTSDEMIPHONES];         // rank, from 0..100, 0 = best sounding
   MISMATCHINFO      aMMISpecific[TRIPHONESPECIFICMISMATCH]; // scores to use if mismatch
   WORD              wMismatchAccuracy;  // how accurate the mismatch is
//   WORD              wElem;         // number of elements
//   WORD              wFlags;        // flags, TPMML_XXX
   WORD              wOrigWave;     // original wave this was in, so can keep adjacent units
   WORD              wOrigPhone;    // original phone index this was in, so can keep adhance units
   WORD              wFeatureStart; // starting feature of wave
   WORD              wFeatureEnd;   // ending feature of wave
   short             iFeatureAdd;   // dB to add to feature to normalize
   WORD              wTrimLeft;     // number of SRFEATURE units can trim left if unit all alone
   WORD              wTrimRight;    // number of SRFEATURE units can trim right if unit all alone
   WORD              wFuncWordGroup;   // function word classification 0..NUMFUNCWORDGROUP(inclusive)
//   SRFEATURESMALL    aSRFeatBoundary[(TTSDEMIPHONES+1)*2];   // SR features on the boundary. [0] = SR feat immediately before phone begins,
//   float             afSRFeatBoundary[(TTSDEMIPHONES+1)*2];   // energy before normalization of each of the boundaries
            // BUGFIX - Both of these values harcoded to 4! Should be (TTSDEMIPHONES+1)*2
   // followed by compressed data
} TPMML, *PTPMML;

/*************************************************************************************
CMTTSTriPhoneAudio::MMLToBinary - Like MMLTo, except this fills in a binary buffer
with information.

inputs
   PCMem       pmem - Memory to write to. Start at m_dwCurPosn and add on. Should
                     update m_dwCurPosn in the process
returns
   DWORD - Size of memory added, or 0 if error
*/
size_t CMTTSTriPhoneAudio::MMLToBinary (PCMem pmem)
{
   // make sure it's compressed
//   if (!Compress())
//      return FALSE;

   size_t dwNeed = sizeof(TPMML); // + m_dwKeepMemorySize; // m_memCompressed.m_dwCurPosn;
   if (!pmem->Required (pmem->m_dwCurPosn + dwNeed))
      return 0;
   PTPMML ptp = (PTPMML) ((PBYTE)pmem->p + pmem->m_dwCurPosn);
   PVOID psr = (PSRFEATURE) (ptp+1);
   pmem->m_dwCurPosn += dwNeed;

   memset (ptp, 0, sizeof(*ptp));
   ptp->dwWord = m_dwWord;
   //ptp->iPitch = m_iPitch;
   //ptp->iPitchDelta = m_iPitchDelta;
   ptp->fOrigPitchF0Extra = m_fOrigPitchF0Extra;
   ptp->fOrigPitchF0Fuji = m_fOrigPitchF0Fuji;
   ptp->wFuncWordGroup = (WORD) m_dwFuncWordGroup;
   ptp->fEnergyAvg = m_fEnergyAvg;
   DWORD i;
   for (i = 0; i < TTSDEMIPHONES; i++)
      ptp->afPitchConfidence[i] = m_afPitchConfidence[i];
   ptp->fAccent = m_fAccent;
   ptp->fPitchDelta = m_fPitchDelta;
   ptp->fPitchBulge = m_fPitchBulge;

   for (i = 0; i < TPAPITCHPOINTS; i++) {
      ptp->afPitchF0Extra[i] = m_afPitchF0Extra[i];
      ptp->afPitchF0Fuji[i] = m_afPitchF0Fuji[i];
   } // i
   ptp->fCenterEnergy = m_fCenterEnergy;
   ptp->fLeftEnergy = m_fLeftEnergy;
   ptp->fLeftPitchF0Extra = m_fLeftPitchF0Extra;
   ptp->fLeftPitchF0Fuji = m_fLeftPitchF0Fuji;
   ptp->dwLeftDuration = m_dwLeftDuration;
   // ptp->wDuration = m_wDuration;
   memcpy (ptp->abPhoneContiguous, m_abPhoneContiguous, sizeof(m_abPhoneContiguous));
   ptp->bPhoneLeft = m_bPhoneLeft;
   ptp->bPhoneRight = m_bPhoneRight;
   memcpy (ptp->abRank, m_abRank, sizeof(m_abRank));
   memcpy (ptp->aMMISpecific, m_aMMISpecific, sizeof(m_aMMISpecific));
   ptp->wMismatchAccuracy = (WORD) m_dwMismatchAccuracy;
   ptp->wWordSylPos = m_wWordSylPos;
   ptp->wFeatureStart = (WORD)m_dwFeatureStart;
   ptp->wFeatureEnd = (WORD)m_dwFeatureEnd;
   ptp->iFeatureAdd = (short)m_iFeatureAdd;
//   ptp->wElem = (WORD) m_dwNumSRFEATURE;
//   ptp->wFlags = m_wFlags;
   ptp->wOrigWave = m_wOrigWave;
   ptp->wOrigPhone = m_wOrigPhone;
   ptp->wTrimLeft = (WORD)m_dwTrimLeft;
   ptp->wTrimRight = (WORD)m_dwTrimRight;
//   memcpy (ptp->aSRFeatBoundary, m_aSRFeatBoundary, sizeof(m_aSRFeatBoundary));
//   memcpy (ptp->afSRFeatBoundary, m_afSRFeatBoundary, sizeof(m_afSRFeatBoundary));

//   memcpy (psr, m_pKeepMemoryCompressed /*m_memCompressed.p*/, m_dwKeepMemorySize /*m_memCompressed.m_dwCurPosn*/);
   
   return dwNeed;
}

/*************************************************************************************
CMTTSTriPhoneAudio::MMLFromBinary - Reads triphone information from binary memory.

inputs
   PVOID             pvMem - Memory to read from
   DWORD             dwSize - Number of bytes. This is the same value as returned from MMLToBinary()
   PCMLexicon        pLexicon - Lexicon to use for triphones
//   BOOL              fKeepMemory - If TRUE, can keep memory pointed to by pvMem. If FALSE, allocate new
returns
   BOOL - TRUE if success
*/
BOOL CMTTSTriPhoneAudio::MMLFromBinary (PVOID pMem, DWORD dwSize, PCMLexicon pLexicon)
{
//    m_fEnergyIsValid = FALSE;
   // delete existing SR feature
//   if (m_pmemSRFEATURE) {
//      delete m_pmemSRFEATURE;
//      m_pmemSRFEATURE = NULL;
//   }
//   if (m_pmemTTSFEATURECOMPEXTRA) {
//      delete m_pmemTTSFEATURECOMPEXTRA;
//      m_pmemTTSFEATURECOMPEXTRA = NULL;
//   }

   PTPMML ptp = (PTPMML) pMem;
//   PVOID psr = (PSRFEATURE) (ptp+1);

   m_fReviewed = FALSE;

   m_dwWord = ptp->dwWord;
   //m_wDuration = ptp->wDuration;
   //m_iPitch = ptp->iPitch;
   //m_iPitchDelta = ptp->iPitchDelta;
   m_fOrigPitchF0Extra = ptp->fOrigPitchF0Extra;
   m_fOrigPitchF0Fuji = ptp->fOrigPitchF0Fuji;
   m_dwFuncWordGroup = ptp->wFuncWordGroup;
   m_fEnergyAvg = ptp->fEnergyAvg;
   DWORD i;
   for (i = 0; i < TTSDEMIPHONES; i++)
      m_afPitchConfidence[i] = ptp->afPitchConfidence[i];
   m_fAccent = ptp->fAccent;
   m_fPitchDelta = ptp->fPitchDelta;
   m_fPitchBulge = ptp->fPitchBulge;
   for (i = 0; i < TPAPITCHPOINTS; i++) {
      m_afPitchF0Extra[i] = ptp->afPitchF0Extra[i];
      m_afPitchF0Fuji[i] = ptp->afPitchF0Fuji[i];
   } // i
   m_fCenterEnergy = ptp->fCenterEnergy;
   m_fLeftEnergy = ptp->fLeftEnergy;
   m_fLeftPitchF0Extra = ptp->fLeftPitchF0Extra;
   m_fLeftPitchF0Fuji = ptp->fLeftPitchF0Fuji;
   m_dwLeftDuration = ptp->dwLeftDuration;
   memcpy (m_abPhoneContiguous, ptp->abPhoneContiguous, sizeof(m_abPhoneContiguous));
   m_bPhoneLeft = ptp->bPhoneLeft;
   m_bPhoneRight = ptp->bPhoneRight;
   memcpy (m_abRank, ptp->abRank, sizeof(m_abRank));
   memcpy (m_aMMISpecific, ptp->aMMISpecific, sizeof(m_aMMISpecific));
   m_dwMismatchAccuracy = ptp->wMismatchAccuracy;
   m_wWordSylPos = ptp->wWordSylPos;
   m_dwFeatureStart = ptp->wFeatureStart;
   m_dwFeatureEnd = ptp->wFeatureEnd;
   m_iFeatureAdd = ptp->iFeatureAdd;
//   m_dwNumSRFEATURE = ptp->wElem;
//   m_wFlags = ptp->wFlags;
   m_wOrigWave = ptp->wOrigWave;
   m_wOrigPhone = ptp->wOrigPhone;
   m_dwTrimLeft = ptp->wTrimLeft;
   m_dwTrimRight = ptp->wTrimRight;
//   memcpy (m_aSRFeatBoundary, ptp->aSRFeatBoundary, sizeof(m_aSRFeatBoundary));
//   memcpy (m_afSRFeatBoundary, ptp->afSRFeatBoundary, sizeof(m_afSRFeatBoundary));


   CalcInfo (pLexicon);

   // copy over remaining
   dwSize -= sizeof(*ptp);

//   m_fKeepMemory = fKeepMemory;
//   m_dwKeepMemorySize = dwSize;
//   if (fKeepMemory)
//      m_pKeepMemoryCompressed = psr;
//   else {
//      if (!m_memCompressed.Required (dwSize))
//         return FALSE;
//      memcpy (m_memCompressed.p, psr, dwSize);
//      m_memCompressed.m_dwCurPosn = dwSize;
//
//      m_pKeepMemoryCompressed = m_memCompressed.p;
//   }

   // BUGFIX - no longer call CalcEnergy() here because slow loading
   //CalcEnergy();
   return TRUE;
}

/*************************************************************************************
CMTTSTriPhoneAudio::CalcInfo - Calculates some of the calculated variables
in the object
*/
void CMTTSTriPhoneAudio::CalcInfo (PCMLexicon pLexicon)
{
   DWORD j;
   for (j = 0; j < NUMTRIPHONEGROUP; j++)
      m_awTriPhone[j] = PhoneToTriPhoneNumber (m_bPhoneLeft, m_bPhoneRight, pLexicon, j);

   // BUGFIX - Use m_fPitchLeft, m_fPitchCenter, and m_fPitchRight
   DWORD i;
   for (i = 0; i < TPAPITCHPOINTS; i++) {
      m_aiPitchF0Extra[i] = log((fp)(m_afPitchF0Extra[i])) / log((fp)2) * 1000.0;
         // BUGFIX - Was /SRBASEPITCH but dont want for extra
      m_aiPitchF0Fuji[i] = log((fp)(m_afPitchF0Fuji[i] / SRBASEPITCH)) / log((fp)2) * 1000.0;
   } // i

#if 0 // old code
   // convert the pitches
   fp fDelta = sqrt (m_fPitchDelta);
   fp fLeft = m_fOrigPitch / fDelta;
   fp fRight = m_fOrigPitch * fDelta;
   m_iPitchLeft = log((fp)(fLeft / SRBASEPITCH)) / log((fp)2) * 1000.0;
   m_iPitchRight = log((fp)(fRight / SRBASEPITCH)) / log((fp)2) * 1000.0;
   
   // center pitch
   fp fCenter = m_fOrigPitch * m_fPitchBulge;
   m_iPitchCenter = log((fp)(fCenter / SRBASEPITCH)) / log((fp)2) * 1000.0;

   // NOTE; Since was dealing with average, need to counterweight left/right upwards
   int iCenter = (int)(log(m_fPitchBulge) / log((fp)2) * 1000.0) / 2;
   m_iPitchLeft -= iCenter;
   m_iPitchRight -= iCenter;
#endif
}














/*************************************************************************************
CMTTSTriPhonePros::Constructor and destructor
*/
CMTTSTriPhonePros::CMTTSTriPhonePros (void)
{
   m_bPhoneLeft = m_bPhoneRight = 0;
   m_wWordSylPos = m_wDuration = 0;
   m_iPitchF0Extra = m_iPitchF0Fuji = m_iPitchDelta = m_iPitchBulge = 0;
   m_fEnergyAvg = 0;
   m_fCached = FALSE;
}

CMTTSTriPhonePros::~CMTTSTriPhonePros (void)
{
   // do nothing
}


/*************************************************************************************
CMTTSTriPhonePros::MemoryTouch - Use to make sure TTS stays in memory
*/
DWORD CMTTSTriPhonePros::MemoryTouch (void)
{
   return m_wDuration;  // since no sub-memory
}

/*************************************************************************************
CMTTSTriPhonePros::Clone - Standard clone
*/
CMTTSTriPhonePros *CMTTSTriPhonePros::Clone (void)
{
   PCMTTSTriPhonePros pNew = new CMTTSTriPhonePros;
   if (!pNew)
      return NULL;

   pNew->m_bPhoneLeft = m_bPhoneLeft;
   pNew->m_bPhoneRight = m_bPhoneRight;
   pNew->m_wDuration = m_wDuration;
   pNew->m_wWordSylPos = m_wWordSylPos;
   pNew->m_iPitchF0Extra = m_iPitchF0Extra;
   pNew->m_iPitchF0Fuji = m_iPitchF0Fuji;
   pNew->m_iPitchDelta = m_iPitchDelta;
   pNew->m_iPitchBulge = m_iPitchBulge;
   pNew->m_fEnergyAvg = m_fEnergyAvg;
   memcpy (pNew->m_awTriPhone, m_awTriPhone, sizeof(m_awTriPhone));


   return pNew;
}


// TPMMLPROS - For MMLTo/From.

typedef struct {
   WORD              wDuration;   // typical duration, in SRFEATURE units
   short             iPitchF0Extra;      // pitch increase/decraase. 0=no change, 1000=1 octave higher, -1000=1 octave lower, etc.
   short             iPitchF0Fuji;      // pitch increase/decraase. 0=no change, 1000=1 octave higher, -1000=1 octave lower, etc.
   short             iPitchDelta;   // change in pitch over phoneme, same scale as iPitch
   short             iPitchBulge;   // bulge in pitch at center. Same scale as iPitch
   BYTE              bPhoneLeft;    // phoneme to left
   BYTE              bPhoneRight;   // phoneme to right
   WORD              wWordSylPos;    // position within word, 0=middle, 1=start, 2=end, 4=start&end, WORDPOS_XXX
   float             fEnergyAvg;    // average energy
} TPMMLPROS, *PTPMMLPROS;

/*************************************************************************************
CMTTSTriPhonePros::MMLToBinary - Like MMLTo, except this fills in a binary buffer
with information.

inputs
   PCMem       pmem - Memory to write to. Start at m_dwCurPosn and add on. Should
                     update m_dwCurPosn in the process
returns
   DWORD - Size of memory added, or 0 if error
*/
DWORD CMTTSTriPhonePros::MMLToBinary (PCMem pmem)
{
   DWORD dwNeed = sizeof(TPMMLPROS);
   if (!pmem->Required (pmem->m_dwCurPosn + dwNeed))
      return 0;
   PTPMMLPROS ptp = (PTPMMLPROS) ((PBYTE)pmem->p + pmem->m_dwCurPosn);
   PVOID psr = (PSRFEATURE) (ptp+1);
   pmem->m_dwCurPosn += dwNeed;

   memset (ptp, 0, sizeof(*ptp));
   ptp->iPitchF0Extra = m_iPitchF0Extra;
   ptp->iPitchF0Fuji = m_iPitchF0Fuji;
   ptp->iPitchDelta = m_iPitchDelta;
   ptp->iPitchBulge = m_iPitchBulge;
   ptp->fEnergyAvg = m_fEnergyAvg;
   ptp->wDuration = m_wDuration;
   ptp->bPhoneLeft = m_bPhoneLeft;
   ptp->bPhoneRight = m_bPhoneRight;
   ptp->wWordSylPos = m_wWordSylPos;
   
   return dwNeed;
}

/*************************************************************************************
CMTTSTriPhonePros::MMLFromBinary - Reads triphone information from binary memory.

inputs
   PVOID             pvMem - Memory to read from
   DWORD             dwSize - Number of bytes. This is the same value as returned from MMLToBinary()
   PCMLexicon        pLexicon - Lexicon to use for triphones
returns
   BOOL - TRUE if success
*/
BOOL CMTTSTriPhonePros::MMLFromBinary (PVOID pMem, DWORD dwSize, PCMLexicon pLexicon)
{
   PTPMMLPROS ptp = (PTPMMLPROS) pMem;
   PVOID psr = (PSRFEATURE) (ptp+1);

   m_wDuration = ptp->wDuration;
   m_iPitchF0Extra = ptp->iPitchF0Extra;
   m_iPitchF0Fuji = ptp->iPitchF0Fuji;
   m_iPitchDelta = ptp->iPitchDelta;
   m_iPitchBulge = ptp->iPitchBulge;
   m_fEnergyAvg = ptp->fEnergyAvg;
   m_bPhoneLeft = ptp->bPhoneLeft;
   m_bPhoneRight = ptp->bPhoneRight;

   CalcInfo(pLexicon);

   return TRUE;
}


/*************************************************************************************
CMTTSTriPhonePros::CalcInfo - Calculates some of the calculated variables
in the object
*/
void CMTTSTriPhonePros::CalcInfo (PCMLexicon pLexicon)
{
   DWORD j;
   for (j = 0; j < NUMTRIPHONEGROUP; j++)
      m_awTriPhone[j] = PhoneToTriPhoneNumber (m_bPhoneLeft, m_bPhoneRight, pLexicon, j);
}








/*************************************************************************************
CMTTS::Construcotr and destructor
*/
CMTTS::CMTTS (void)
{
   m_fThreadSafe = FALSE;  // if never do a MMLFrom() or Open(), then defaults to cache
   m_dwRefCount = 0;
   m_szFile[0] = 0;
   m_dwTriPhoneGroup = 0;
   m_fKeepLog = FALSE;
   m_fFullPCM = FALSE;
   m_fAvgPitch = 100;   // BUGFIX - set to 100 so have something
   m_fAvgPitchF0Fuji = 100;
   m_fFujiRangeLow = m_fFujiRangeMed = m_fFujiRangeHigh = 0.01;
   m_dwUnits = 0;
   m_fPauseLessOften = FALSE;
   m_dwWordsPerMinute = 100;  // BUGFIX - Set to 100 so have something
   m_fAvgSyllableDur = 100;   // so have something
   m_pLexTrainingWords = new CMLexicon;
   m_pLexWords = new CMLexicon;

#if 0 // old prosody
   m_pLexFuncWords = new CMLexicon;
   m_lPCTTSPunctPros.Init (sizeof(PCTTSPunctPros));
#endif // 0

   // for sapi
   m_LangID = 1033;  // default
   MemZero (&m_memSAPIToken);
   MemZero (&m_memSAPIName);
   MemZero (&m_memSAPIVendor);
   m_dwAge = 2;
   m_dwGender = 0;
   m_dwVersionMajor = 1;
   m_dwVersionMinor = 0;
   m_dwVersionBuild = 1;



   DWORD i, j;
#if 0 // old prosody
   m_pPunctAccentuate.Copy (&m_pPOSAccentuate);

   memset (m_apLexWordEmph, 0, sizeof(m_apLexWordEmph));
   for (i = 0; i < NUMLEXWORDEMPH+1; i++) {
      m_apLexWordEmphScale[i].Zero();
      m_apLexWordEmphScale[i].p[0] = 1;
      m_apLexWordEmphScale[i].p[1] = 1;
      m_apLexWordEmphScale[i].p[2] = 1;
   }
   for (i = 0; i < NUMPROSWORDLENGTH; i++) {
      m_apProsWordEmphFromWordLength[i].Zero();
      m_apProsWordEmphFromWordLength[i].p[0] = 1;
      m_apProsWordEmphFromWordLength[i].p[1] = 1;
      m_apProsWordEmphFromWordLength[i].p[2] = 1;
   }
#endif // 0
   for (i = 0; i < NUMPHONEEMPH*2; i++) for (j = 0; j < PHONEPOSBIN; j++) {
      m_apPhoneEmph[i][j].Zero();
      m_apPhoneEmph[i][j].p[0] = 1;
      m_apPhoneEmph[i][j].p[1] = 1;
      m_apPhoneEmph[i][j].p[2] = 1;
   }

   m_pCTTSProsody = new CTTSProsody;

   m_szLexicon[0] = 0;
   m_palPCMTTSTriPhoneAudio = NULL;
   m_palPCMTTSTriPhonePros = NULL;
   // m_pamemTriPhoneAudio = NULL;
   m_papObjectCachePCMTTSTriPhoneAudio = NULL;
   m_papObjectCachePCMTTSTriPhonePros = NULL;
   m_dwNumPhone = 0;
   m_pLexMain = NULL;
   m_lPCTTSWave.Init (sizeof(PCTTSWave));
   m_lPCPhaseModel.Init (sizeof(PCPhaseModel));

   m_fIsDerived = FALSE;
   m_szTTSMaster[0] = 0;
   m_pTTSMaster = NULL;

   memset (m_apLexFuncWord, 0, sizeof(m_apLexFuncWord));

   for (i = 0; i < NUMPROSODYTTS; i++)
      m_aszProsodyTTS[i][0] = 0;

   m_lPCMTTSSubVoice.Init (sizeof(PCMTTSSubVoice));

   InitializeCriticalSection (&m_csSynthBeamSearch);

   // clrea thread info
   InitializeCriticalSection (&m_csTTSWaveDecompress);
   for (i = 0; i < MAXRAYTHREAD; i++)
      InitializeCriticalSection (&m_acsTTSWave[i]);
   m_dwTTSWaveAnalyzing = 0;
   m_fTTSWaveCalcSRANALBLOCKWantToQuit = FALSE;
   m_fTTSWaveDisableCompress = FALSE;
   memset (m_aThreadWaveCalcInfo, 0, sizeof(m_aThreadWaveCalcInfo));
}

CMTTS::~CMTTS (void)
{
   // free up threads
   m_fTTSWaveCalcSRANALBLOCKWantToQuit = TRUE;
   DWORD i;
   for (i = 0; i < MAXRAYTHREAD; i++) {
      if (!m_aThreadWaveCalcInfo[i].hThread)
         continue;
      WaitForSingleObject (m_aThreadWaveCalcInfo[i].hThread, INFINITE);
      CloseHandle (m_aThreadWaveCalcInfo[i].hThread);
      m_aThreadWaveCalcInfo[i].hThread = NULL;
   }

   for (i = 0; i < MAXRAYTHREAD; i++)
      DeleteCriticalSection (&m_acsTTSWave[i]);
   DeleteCriticalSection (&m_csTTSWaveDecompress);

   DeleteCriticalSection (&m_csSynthBeamSearch);

   // free master
   if (m_pTTSMaster)
      TTSCacheClose (m_pTTSMaster);
   m_pTTSMaster = NULL;

   if (m_pLexTrainingWords)
      delete m_pLexTrainingWords;
   m_pLexTrainingWords = NULL;

   if (m_pLexWords)
      delete m_pLexWords;
   m_pLexWords = NULL;

   if (m_pLexMain)
      MLexiconCacheClose (m_pLexMain);
   m_pLexMain = NULL;

   FreeUnits();

   m_dwNumPhone = 0;

   for (i = 0; i < NUMFUNCWORDGROUP; i++) {
      if (m_apLexFuncWord[i])
         delete m_apLexFuncWord[i];
      m_apLexFuncWord[i] = NULL;
   }

#if 0 // old prosody
   if (m_pLexFuncWords)
      delete m_pLexFuncWords;
   m_pLexFuncWords = NULL;

   for (i = 0; i < NUMLEXWORDEMPH; i++) {
      if (m_apLexWordEmph[i])
         delete m_apLexWordEmph[i];
      m_apLexWordEmph[i] = NULL;
   }

   // delete punctionan prosody
   PCTTSPunctPros *ppp = (PCTTSPunctPros*)m_lPCTTSPunctPros.Get(0);
   for (i = 0; i < m_lPCTTSPunctPros.Num(); i++)
      if (ppp[i])
         delete ppp[i];
   m_lPCTTSPunctPros.Clear();
#endif // 0

   if (m_pCTTSProsody)
      delete m_pCTTSProsody;

   // free up sub-voices
   PCMTTSSubVoice *ppsv = (PCMTTSSubVoice*) m_lPCMTTSSubVoice.Get(0);
   for (i = 0; i < m_lPCMTTSSubVoice.Num(); i++)
      delete ppsv[i];
   m_lPCMTTSSubVoice.Clear();
}



/*************************************************************************************
CMTTS::PhaseModelGet - Given a phoneme, get the phase model for it.

inputs
   DWORD          dwPhoneme - Unsorted phoneme number
   BOOL           fCreateIfNotExist - If TRUE, then create if this doesn't exist
returns
   PCPhaseModel - Phase model
*/
PCPhaseModel CMTTS::PhaseModelGet (DWORD dwPhoneme, BOOL fCreateIfNotExist)
{
   // BUGFIX - disable phase models since not as good as psola, and use more memory
   // to use phases than old phase writeup.
   return NULL;

   PCPhaseModel *pppm = (PCPhaseModel*)m_lPCPhaseModel.Get(dwPhoneme);
   if (pppm)
      return pppm[0];
   
   // if don't want to create then exit where
   if (!fCreateIfNotExist)
      return NULL;

   while (dwPhoneme >= m_lPCPhaseModel.Num()) {
      PCPhaseModel pNew = new CPhaseModel;
      if (!pNew)
         return NULL;   // eror, shouldnt happen
      m_lPCPhaseModel.Add (&pNew);
   }

   pppm = (PCPhaseModel*)m_lPCPhaseModel.Get(dwPhoneme);
   return pppm[0];
}


/*************************************************************************************
CMTTS::FillInTriPhoneIndex - Loops through all the m_palPCMTTSTriPhoneAudio and
fills in m_wPhone and m_wTriPhoneIndex
*/
void CMTTS::FillInTriPhoneIndex (void)
{
   DWORD dwPhone, dwIndex;
   DWORD dwUniqueID = 0;
   for (dwPhone = 0; dwPhone < m_dwNumPhone; dwPhone++) {
      PCListFixed pl = m_palPCMTTSTriPhoneAudio ? m_palPCMTTSTriPhoneAudio[dwPhone] : NULL;
      if (!pl)
         continue;

      PCMTTSTriPhoneAudio *pptp = (PCMTTSTriPhoneAudio*)pl->Get(0);
      for (dwIndex = 0; dwIndex < pl->Num(); dwIndex++, pptp++) {
         pptp[0]->m_dwUniqueID = dwUniqueID++;
         //pptp[0]->m_wPhone = (WORD)dwPhone;
         //pptp[0]->m_wTriPhoneIndex = (WORD)dwIndex;
      }
   } // dwPhone

}

/*************************************************************************************
CMTTS::MemoryTouch - Randomly touches some memory in the TTS engine.
Call this once a second to ensure that TTS isn't cached out.
*/
DWORD CMTTS::MemoryTouch (void)
{
   // return a value so that C++ optimizer doesn't eliminate this code entirely
   DWORD dwRet = 0;

   // target costs
   DWORD dwIndex, dwIndex2;
   if (m_memTTSTARGETCOSTS.m_dwCurPosn / sizeof(DWORD)) {
      dwIndex = ((DWORD)rand() * 1000) % (DWORD)(m_memTTSTARGETCOSTS.m_dwCurPosn / sizeof(DWORD));
      dwRet += ((DWORD*)m_memTTSTARGETCOSTS.p)[dwIndex];
   }

   dwIndex = rand() % NUMFUNCWORDGROUP;
   if (m_apLexFuncWord[dwIndex])
      dwRet += m_apLexFuncWord[dwIndex]->MemoryTouch();

   if (m_dwNumPhone) {
      dwIndex = rand() % m_dwNumPhone;

      // triphone audio
      PCListFixed pl = m_palPCMTTSTriPhoneAudio ? m_palPCMTTSTriPhoneAudio[dwIndex] : NULL;
      if (pl) {
         dwIndex2 = rand() % pl->Num();
         PCMTTSTriPhoneAudio *pptp = (PCMTTSTriPhoneAudio*)pl->Get(dwIndex2);
         if (pptp)
            dwRet += pptp[0]->MemoryTouch();
      }

      // triphone prosody
      pl = m_palPCMTTSTriPhonePros ? m_palPCMTTSTriPhonePros[dwIndex] : NULL;
      if (pl) {
         dwIndex2 = rand() % pl->Num();
         PCMTTSTriPhonePros *pptp = (PCMTTSTriPhonePros*)pl->Get(dwIndex2);
         if (pptp)
            dwRet += pptp[0]->MemoryTouch();
      }
   }

   // touch the wave
   if (m_lPCTTSWave.Num()) {
      dwIndex = rand() % m_lPCTTSWave.Num();
      PCTTSWave *ppTW = (PCTTSWave*) m_lPCTTSWave.Get(dwIndex);
      if (ppTW && ppTW[0]) {
         EnterCriticalSection (&m_acsTTSWave[dwIndex % MAXRAYTHREAD]);
         dwRet += ppTW[0]->MemoryTouch();
         LeaveCriticalSection (&m_acsTTSWave[dwIndex % MAXRAYTHREAD]);
      }
   }

   // NOTE: Don't worry about touching m_lPCPhaseModel because they're small

   // touch a few more lexicons
   if (m_pLexTrainingWords)
      dwRet += m_pLexTrainingWords->MemoryTouch();
   if (m_pLexWords)
      dwRet += m_pLexWords->MemoryTouch();
   if (m_pLexMain)
      dwRet += m_pLexMain->MemoryTouch();

   // prosody
   if (m_pCTTSProsody)
      dwRet += m_pCTTSProsody->MemoryTouch();

   // master
   if (m_pTTSMaster)
      dwRet += m_pTTSMaster->MemoryTouch();

   return dwRet;
}

/*************************************************************************************
CMTTS::FreeUnits - Frees the memory allocated for the units
*/
void CMTTS::FreeUnits (void)
{

   // free up the wave memory
   DWORD i,j;
   PCTTSWave *ppTW = (PCTTSWave*) m_lPCTTSWave.Get(0);
   for (i = 0; i < m_lPCTTSWave.Num(); i++)
      if (ppTW[i])
         delete ppTW[i];
   m_lPCTTSWave.Clear();

   // free up triphones
   for (i = 0; i < m_dwNumPhone; i++) {
      PCListFixed pl = m_palPCMTTSTriPhoneAudio ? m_palPCMTTSTriPhoneAudio[i] : NULL;
      if (!pl)
         continue;

      PCMTTSTriPhoneAudio *pptp = (PCMTTSTriPhoneAudio*)pl->Get(0);
      for (j = 0; j < pl->Num(); j++)
         if (!pptp[j]->m_fCached)
            delete pptp[j];

      delete pl;
   } // i

   PCPhaseModel *pppm = (PCPhaseModel*)m_lPCPhaseModel.Get(0);
   for (i = 0; i < m_lPCPhaseModel.Num(); i++) {
      if (pppm[i])
         delete pppm[i];
   } // i
   m_lPCPhaseModel.Clear();

   // free up triphonem memory
//   if (m_pamemTriPhoneAudio) for (i = 0; i < m_dwNumPhone; i++)
//      if (m_pamemTriPhoneAudio[i])
//         delete m_pamemTriPhoneAudio[i];

   if (m_papObjectCachePCMTTSTriPhoneAudio) for (i = 0; i < m_dwNumPhone; i++)
      if (m_papObjectCachePCMTTSTriPhoneAudio[i])
         delete [] m_papObjectCachePCMTTSTriPhoneAudio[i];

   for (i = 0; i < m_dwNumPhone; i++) {
      PCListFixed pl = m_palPCMTTSTriPhonePros ? m_palPCMTTSTriPhonePros[i] : NULL;
      if (!pl)
         continue;

      PCMTTSTriPhonePros *pptp = (PCMTTSTriPhonePros*)pl->Get(0);
      for (j = 0; j < pl->Num(); j++)
         if (!pptp[j]->m_fCached)
            delete pptp[j];

      delete pl;
   } // i

   if (m_papObjectCachePCMTTSTriPhonePros) for (i = 0; i < m_dwNumPhone; i++)
      if (m_papObjectCachePCMTTSTriPhonePros[i])
         delete [] m_papObjectCachePCMTTSTriPhonePros[i];

   m_palPCMTTSTriPhoneAudio = NULL;
   m_palPCMTTSTriPhonePros = NULL;
//   m_pamemTriPhoneAudio = NULL;
   m_papObjectCachePCMTTSTriPhoneAudio = NULL;
   m_papObjectCachePCMTTSTriPhonePros = NULL;

   m_dwNumPhone = 0;
}


/*************************************************************************************
CMTTS::MakeSureEnoughPhone - This internal function makes sure that  m_palPCMTTSTriPhoneAudio & Pros
contains memory allocated for the number of phonemes.

inputs
   DWORD          dwNum - Number of phonemes needed. If -1 then use m_dwNumPhone and initialize
returns
   BOOL - TRUE if sueccess, FALSE if error
*/
BOOL CMTTS::MakeSureEnoughPhone (DWORD dwNum)
{
   DWORD dwOldNumPhone;
   if (dwNum == (DWORD)-1) {
      dwNum = m_dwNumPhone-1;
      dwOldNumPhone = 0;
   }
   else {
      if (dwNum < m_dwNumPhone)
         return TRUE;
      dwOldNumPhone = m_dwNumPhone;
   }

   // BUGFIX - make sure plenty of slots for phonemes
   PCMLexicon pLex = Lexicon();
   if (pLex)
      dwNum = max(dwNum, pLex->PhonemeNum());

   DWORD dwNewNumPhone = dwNum+1;

   // need to allocate more
   if (!m_memTriPhoneAudio.Required (dwNewNumPhone * sizeof(PCListFixed)))
      return FALSE;
   m_palPCMTTSTriPhoneAudio = (PCListFixed*) m_memTriPhoneAudio.p;
   memset (m_palPCMTTSTriPhoneAudio + dwOldNumPhone, 0, (dwNewNumPhone - dwOldNumPhone)*sizeof(PCListFixed*));

   // allocate for memory
//   if (!m_memTriPhoneAudioMem.Required (dwNewNumPhone * sizeof(PCMem)))
//      return FALSE;
//   m_pamemTriPhoneAudio = (PCMem*) m_memTriPhoneAudioMem.p;
//   memset (m_pamemTriPhoneAudio + dwOldNumPhone, 0, (dwNewNumPhone - dwOldNumPhone)*sizeof(PCMem));

   // need to allocate more
   if (!m_memTriPhonePros.Required (dwNewNumPhone * sizeof(PCListFixed)))
      return FALSE;
   m_palPCMTTSTriPhonePros = (PCListFixed*) m_memTriPhonePros.p;
   memset (m_palPCMTTSTriPhonePros + dwOldNumPhone, 0, (dwNewNumPhone - dwOldNumPhone)*sizeof(PCListFixed*));

   // allocate for object caches
   if (!m_memObjectCachePCMTTSTriPhonePros.Required (dwNewNumPhone * sizeof(PCMTTSTriPhonePros)))
      return FALSE;
   m_papObjectCachePCMTTSTriPhonePros = (PCMTTSTriPhonePros*)m_memObjectCachePCMTTSTriPhonePros.p;
   memset (m_papObjectCachePCMTTSTriPhonePros + dwOldNumPhone, 0, (dwNewNumPhone - dwOldNumPhone)*sizeof(PCMTTSTriPhonePros*));

   // allocate for object caches
   if (!m_memObjectCachePCMTTSTriPhoneAudio.Required (dwNewNumPhone * sizeof(PCMTTSTriPhoneAudio)))
      return FALSE;
   m_papObjectCachePCMTTSTriPhoneAudio = (PCMTTSTriPhoneAudio*)m_memObjectCachePCMTTSTriPhoneAudio.p;
   memset (m_papObjectCachePCMTTSTriPhoneAudio + dwOldNumPhone, 0, (dwNewNumPhone - dwOldNumPhone)*sizeof(PCMTTSTriPhoneAudio*));

   m_dwNumPhone = dwNewNumPhone;
   return TRUE;
}

/*************************************************************************************
CMTTS::Clone - Standard api
*/
CMTTS *CMTTS::Clone (void)
{
   PCMTTS pNew = new CMTTS;
   if (!pNew)
      return NULL;

   if (pNew->m_pLexMain)
      MLexiconCacheClose (pNew->m_pLexMain); // BUGFIX - forgot to put pNew in
   pNew->m_pLexMain = NULL;

   pNew->FreeUnits();

   wcscpy (pNew->m_szFile, m_szFile);
   pNew->m_dwTriPhoneGroup = m_dwTriPhoneGroup;
   pNew->m_fKeepLog = m_fKeepLog;
   pNew->m_fFullPCM = m_fFullPCM;
   pNew->m_fAvgPitch = m_fAvgPitch;
   pNew->m_fAvgPitchF0Fuji = m_fAvgPitchF0Fuji;
   pNew->m_fFujiRangeLow = m_fFujiRangeLow;
   pNew->m_fFujiRangeMed = m_fFujiRangeMed;
   pNew->m_fFujiRangeHigh = m_fFujiRangeHigh;
   pNew->m_dwUnits = m_dwUnits;
   pNew->m_fPauseLessOften = m_fPauseLessOften;
   pNew->m_fAvgSyllableDur = m_fAvgSyllableDur;
   pNew->m_dwWordsPerMinute = pNew->m_dwWordsPerMinute;
   wcscpy (pNew->m_szLexicon, m_szLexicon);
   pNew->m_dwNumPhone = m_dwNumPhone;
   pNew->m_dwRefCount = m_dwRefCount;
   pNew->m_fThreadSafe = m_fThreadSafe;

   // for SAPI
   pNew->m_LangID = m_LangID;
   pNew->m_dwAge = m_dwAge;
   pNew->m_dwGender = m_dwGender;
   pNew->m_dwVersionMajor = m_dwVersionMajor;
   pNew->m_dwVersionMinor = m_dwVersionMinor;
   pNew->m_dwVersionBuild = m_dwVersionBuild;
   MemZero (&pNew->m_memSAPIToken);
   MemCat (&pNew->m_memSAPIToken, (PWSTR)m_memSAPIToken.p);
   MemZero (&pNew->m_memSAPIName);
   MemCat (&pNew->m_memSAPIName, (PWSTR)m_memSAPIName.p);
   MemZero (&pNew->m_memSAPIVendor);
   MemCat (&pNew->m_memSAPIVendor, (PWSTR)m_memSAPIVendor.p);

   if (pNew->m_pLexTrainingWords)
      delete m_pLexTrainingWords;
   pNew->m_pLexTrainingWords = m_pLexTrainingWords->Clone();

   if (pNew->m_pLexWords)
      delete pNew->m_pLexWords;
   pNew->m_pLexWords = m_pLexWords->Clone();


   pNew->m_memTTSTARGETCOSTS.m_dwCurPosn = m_memTTSTARGETCOSTS.m_dwCurPosn;
   if (pNew->m_memTTSTARGETCOSTS.Required(m_memTTSTARGETCOSTS.m_dwCurPosn))
      memcpy (pNew->m_memTTSTARGETCOSTS.p, m_memTTSTARGETCOSTS.p, m_memTTSTARGETCOSTS.m_dwCurPosn);
   else
      pNew->m_memTTSTARGETCOSTS.p = NULL;

   // note: not cloning or doing MMLTo/From the generic voice disguise

#if 0 // old prosody
   pNew->m_pPunctAccentuate.Copy (&m_pPunctAccentuate);
#endif // 0

   if (!pNew->MakeSureEnoughPhone ()) {
      delete pNew;
      return NULL;
   }

#if 0 // handled by pNew->MakeSureEnoughPhone
   // copy over triphones
   if (!pNew->m_memTriPhoneAudio.Required (pNew->m_dwNumPhone * sizeof(PCListFixed))) {
      delete pNew;
      return NULL;
   }
   pNew->m_palPCMTTSTriPhoneAudio = (PCListFixed*) pNew->m_memTriPhoneAudio.p;
#endif // 0
   memcpy (pNew->m_palPCMTTSTriPhoneAudio, m_palPCMTTSTriPhoneAudio,pNew->m_dwNumPhone * sizeof(PCListFixed));
   DWORD i, j;
   for (i = 0; i < m_dwNumPhone; i++) {
      PCListFixed pl = new CListFixed;
      if (!pl) {
         pNew->m_palPCMTTSTriPhoneAudio[i] = NULL;
         continue;
      }
      pl->Init (sizeof(PCMTTSTriPhoneAudio), m_palPCMTTSTriPhoneAudio[i]->Get(0),
         m_palPCMTTSTriPhoneAudio[i]->Num());
      pNew->m_palPCMTTSTriPhoneAudio[i] = pl;

      // go through this list
      PCMTTSTriPhoneAudio *pptp = (PCMTTSTriPhoneAudio*) pl->Get(0);
      for (j = 0; j < pl->Num(); j++)
         pptp[j] = pptp[j]->Clone();
   } // i

   // NOTE: Don't clone m_pamemTriPhoneAudio because memory invalid

   for (i = 0; i < NUMFUNCWORDGROUP; i++) {
      if (pNew->m_apLexFuncWord[i])
         delete pNew->m_apLexFuncWord[i];
      pNew->m_apLexFuncWord[i] = NULL;

      if (!m_apLexFuncWord[i])
         continue;
      pNew->m_apLexFuncWord[i] = m_apLexFuncWord[i]->Clone();
   }

   // copy over audio
   // NOT TESTED
   pNew->m_lPCTTSWave.Init (sizeof(PCTTSWave), m_lPCTTSWave.Get(0), m_lPCTTSWave.Num());
   PCTTSWave *ppTW = (PCTTSWave*)pNew->m_lPCTTSWave.Get(0);
   for (i = 0; i < pNew->m_lPCTTSWave.Num(); i++)
      if (ppTW[i])
         ppTW[i] = ppTW[i]->Clone();

   // NOT TESTED
   pNew->m_lPCPhaseModel.Init (sizeof(PCPhaseModel), m_lPCPhaseModel.Get(0), m_lPCPhaseModel.Num());
   PCPhaseModel *pppm = (PCPhaseModel*)pNew->m_lPCPhaseModel.Get(0);
   for (i = 0; i < pNew->m_lPCPhaseModel.Num(); i++)
      if (pppm[i])
         pppm[i] = pppm[i]->Clone();

   // copy over triphones
#if 0 // handled by pNew->MakeSureEnoughPhone
   if (!pNew->m_memTriPhonePros.Required (pNew->m_dwNumPhone * sizeof(PCListFixed))) {
      delete pNew;
      return NULL;
   }
   pNew->m_palPCMTTSTriPhonePros = (PCListFixed*) pNew->m_memTriPhonePros.p;
#endif // 0
   memcpy (pNew->m_palPCMTTSTriPhonePros, m_palPCMTTSTriPhonePros,pNew->m_dwNumPhone * sizeof(PCListFixed));
   for (i = 0; i < m_dwNumPhone; i++) {
      PCListFixed pl = new CListFixed;
      if (!pl) {
         pNew->m_palPCMTTSTriPhonePros[i] = NULL;
         continue;
      }
      pl->Init (sizeof(PCMTTSTriPhonePros), m_palPCMTTSTriPhonePros[i]->Get(0),
         m_palPCMTTSTriPhonePros[i]->Num());
      pNew->m_palPCMTTSTriPhonePros[i] = pl;

      // go through this list
      PCMTTSTriPhonePros *pptp = (PCMTTSTriPhonePros*) pl->Get(0);
      for (j = 0; j < pl->Num(); j++)
         pptp[j] = pptp[j]->Clone();
   } // i

#if 0 // old prosody
   if (pNew->m_pLexFuncWords)
      delete pNew->m_pLexFuncWords;
   pNew->m_pLexFuncWords = m_pLexFuncWords->Clone();

   for (i = 0; i < NUMLEXWORDEMPH; i++) {
      if (pNew->m_apLexWordEmph[i])
         delete pNew->m_apLexWordEmph[i];
      pNew->m_apLexWordEmph[i] = NULL;

      if (!m_apLexWordEmph[i])
         continue;
      pNew->m_apLexWordEmph[i] = m_apLexWordEmph[i]->Clone();
   }
   memcpy (pNew->m_apLexWordEmphScale, m_apLexWordEmphScale, sizeof(m_apLexWordEmphScale));
   memcpy (pNew->m_apProsWordEmphFromWordLength, m_apProsWordEmphFromWordLength, sizeof(m_apProsWordEmphFromWordLength));

   // punct pros
   PCTTSPunctPros *ppp = (PCTTSPunctPros*)m_lPCTTSPunctPros.Get(0);
   for (i = 0; i < m_lPCTTSPunctPros.Num(); i++)
      if (ppp[i])
         delete ppp[i];
   m_lPCTTSPunctPros.Clear();
   pNew->m_lPCTTSPunctPros.Init (sizeof(PCTTSPunctPros), m_lPCTTSPunctPros.Get(0), m_lPCTTSPunctPros.Num());
   ppp = (PCTTSPunctPros*)m_lPCTTSPunctPros.Get(0);
   for (i = 0; i < pNew->m_lPCTTSPunctPros.Num(); i++)
      if (ppp[i])
         ppp[i] = ppp[i]->Clone();
#endif // 0

   // NOTE: NOT cloning the prosody/audio object caches:
   //  m_papObjectCachePCMTTSTriPhoneAudio
   //  m_papObjectCachePCMTTSTriPhonePros

   // sentence samples
   // NOTE: Clone not tested
   m_pCTTSProsody->CloneTo (pNew->m_pCTTSProsody);

   // copy over micropauses
   //if (!pNew->m_memMicroPause.Required (m_memMicroPause.m_dwCurPosn)) {
   //   delete pNew;
   //   return NULL;
   //}
   //memcpy (pNew->m_memMicroPause.p, m_memMicroPause.p, m_memMicroPause.m_dwCurPosn);
   //pNew->m_memMicroPause.m_dwCurPosn = m_memMicroPause.m_dwCurPosn;

   // copy over energy-per-pitch
   if (!pNew->m_memEnergyPerPitch.Required (m_memEnergyPerPitch.m_dwCurPosn)) {
      delete pNew;
      return NULL;
   }
   memcpy (pNew->m_memEnergyPerPitch.p, m_memEnergyPerPitch.p, m_memEnergyPerPitch.m_dwCurPosn);
   pNew->m_memEnergyPerPitch.m_dwCurPosn = m_memEnergyPerPitch.m_dwCurPosn;

   // copy over energy-per-volume
   if (!pNew->m_memEnergyPerVolume.Required (m_memEnergyPerVolume.m_dwCurPosn)) {
      delete pNew;
      return NULL;
   }
   memcpy (pNew->m_memEnergyPerVolume.p, m_memEnergyPerVolume.p, m_memEnergyPerVolume.m_dwCurPosn);
   pNew->m_memEnergyPerVolume.m_dwCurPosn = m_memEnergyPerVolume.m_dwCurPosn;

#if 0 // old prosody
   // copy over ngram
   if (!pNew->m_memNGram.Required (m_memNGram.m_dwCurPosn)) {
      delete pNew;
      return NULL;
   }
   memcpy (pNew->m_memNGram.p, m_memNGram.p, m_memNGram.m_dwCurPosn);
   pNew->m_memNGram.m_dwCurPosn = m_memNGram.m_dwCurPosn;
#endif // 0

   pNew->m_fIsDerived = m_fIsDerived;
   wcscpy (pNew->m_szTTSMaster, m_szTTSMaster);
   if (pNew->m_pTTSMaster)
      TTSCacheClose (pNew->m_pTTSMaster);
   pNew->m_pTTSMaster = m_pTTSMaster;
   if (pNew->m_pTTSMaster)
      pNew->m_pTTSMaster->m_dwRefCount++;


   // NOTE: Clone not tested
   // free up sub-voices in pNew
   PCMTTSSubVoice *ppsv = (PCMTTSSubVoice*) pNew->m_lPCMTTSSubVoice.Get(0);
   for (i = 0; i < pNew->m_lPCMTTSSubVoice.Num(); i++)
      delete ppsv[i];
   pNew->m_lPCMTTSSubVoice.Clear();
   pNew->m_lPCMTTSSubVoice.Init (sizeof(PCMTTSSubVoice), m_lPCMTTSSubVoice.Get(0), m_lPCMTTSSubVoice.Num());
   ppsv = (PCMTTSSubVoice*) pNew->m_lPCMTTSSubVoice.Get(0);
   for (i = 0; i < pNew->m_lPCMTTSSubVoice.Num(); i++)
      ppsv[i] = ppsv[i]->Clone();

   return pNew;
}

static PWSTR gpszTTS = L"TTS";
static PWSTR gpszTriPhoneGroup = L"TriPhoneGroup";
static PWSTR gpszWordsPerMinute = L"WordsPerMinute";
static PWSTR gpszNumPhone = L"NumPhone";
static PWSTR gpszLexWords = L"LexWOrds";
static PWSTR gpszLexFuncWords = L"LexFuncWords";
static PWSTR gpszMicroPause = L"MicroPause";
static PWSTR gpszEnergyPerPitch = L"EnergyPerPitch";
static PWSTR gpszEnergyPerVolume = L"EnergyPerVolume";
static PWSTR gpszNGram = L"NGram";
static PWSTR gpszKeepLog = L"KeepLog";
static PWSTR gpszIsDerived = L"IsDerived";
static PWSTR gpszTTSMaster = L"TTSMaster";
static PWSTR gpszVoiceSynth = L"VoiceSynth";
static PWSTR gpszTTSAccentRule = L"TTSAccentRule";
static PWSTR gpszBlurPitch = L"BlurPitch";
static PWSTR gpszBlurVolume = L"BlurVolume";
static PWSTR gpszPhoneRiseAccentuate = L"PhoneRiseAccentuate";
static PWSTR gpszPhonePitchAccentuate = L"PhonePitchAccentuate";
static PWSTR gpszPOSAccentuate = L"POSAccentuate";
static PWSTR gpszPunctAccentuate = L"PunctAccentuate";
static PWSTR gpszDurWordStart = L"DurWordStart";
static PWSTR gpszDurWordEnd = L"DurWordEnd";
static PWSTR gpszDurPerPhone = L"DurPerPhone";
static PWSTR gpszLexTrainingWords = L"LexTrainingWords";
static PWSTR gpszSubVoice = L"SubVoice";
static PWSTR gpszUnits = L"Units";
static PWSTR gpszVersion = L"Version";
static PWSTR gpszFullPCM = L"FullPCM";
static PWSTR gpszTTSWave = L"TTSWave";
static PWSTR gpszTTSTARGETCOSTS = L"TTSTARGETCOSTS";
static PWSTR gpszPauseLessOften = L"PauseLessOften";
static PWSTR gpszCPhaseModel = L"CPhaseModel";

// for sapi
static PWSTR gpszLangID = L"LangID";
static PWSTR gpszAge = L"Age";
static PWSTR gpszGender = L"Gender";
static PWSTR gpszVersionMajor = L"VersionMajor";
static PWSTR gpszVersionMinor = L"VersionMinor";
static PWSTR gpszVersionBuild = L"VersionBuild";
static PWSTR gpszSAPIToken = L"SAPIToken";
static PWSTR gpszSAPIName = L"SAPIName";
static PWSTR gpszSAPIVendor = L"SAPIVendor";


/*************************************************************************************
CMTTS::CMTTS - Standard api
*/
PCMMLNode2 CMTTS::MMLTo (void)
{
   PCMMLNode2 pNode = new CMMLNode2;
   if (!pNode)
      return NULL;
   pNode->NameSet (gpszTTS);

   // this is a bit unusual, but because only saving tts voice after building it,
   // do the calculation for words per minute here
   if (!m_fIsDerived)   // BUGFIX - Only calc words per minute if not derived
      m_dwWordsPerMinute = CalcWordsPerMinute ();

   MMLValueSet (pNode, gpszVersion, m_fIsDerived ? TTSVERSION_DERIVED : TTSVERSION_MASTER);

   MMLValueSet (pNode, gpszTriPhoneGroup, (int) m_dwTriPhoneGroup);
   MMLValueSet (pNode, gpszKeepLog, (int) m_fKeepLog);
   MMLValueSet (pNode, gpszFullPCM, (int) m_fFullPCM);
   MMLValueSet (pNode, gpszAvgPitch, m_fAvgPitch);
   MMLValueSet (pNode, gpszAvgPitchF0Fuji, m_fAvgPitchF0Fuji);
   MMLValueSet (pNode, gpszFujiRangeLow, m_fFujiRangeLow);
   MMLValueSet (pNode, gpszFujiRangeMed, m_fFujiRangeMed);
   MMLValueSet (pNode, gpszFujiRangeHigh, m_fFujiRangeHigh);
   MMLValueSet (pNode, gpszUnits, (int)m_dwUnits);
   MMLValueSet (pNode, gpszPauseLessOften, (int)m_fPauseLessOften);
   MMLValueSet (pNode, gpszAvgSyllableDur, m_fAvgSyllableDur);
   MMLValueSet (pNode, gpszWordsPerMinute, (int)m_dwWordsPerMinute);
   MMLValueSet (pNode, gpszNumPhone, (int) m_dwNumPhone);
   if (m_szLexicon[0])
      MMLValueSet (pNode, gpszLexicon, m_szLexicon);
   MMLValueSet (pNode, gpszIsDerived, (int)m_fIsDerived);
   if (m_szTTSMaster[0])
      MMLValueSet (pNode, gpszTTSMaster, m_szTTSMaster);


   // for SAPI
   MMLValueSet (pNode, gpszLangID, (int)m_LangID);
   MMLValueSet (pNode, gpszAge, (int)m_dwAge);
   MMLValueSet (pNode, gpszGender, (int)m_dwGender);
   MMLValueSet (pNode, gpszVersionMajor, (int)m_dwVersionMajor);
   MMLValueSet (pNode, gpszVersionMinor, (int)m_dwVersionMinor);
   MMLValueSet (pNode, gpszVersionBuild, (int)m_dwVersionBuild);
   if (((PWSTR)m_memSAPIToken.p)[0])
      MMLValueSet (pNode, gpszSAPIToken, (PWSTR)m_memSAPIToken.p);
   if (((PWSTR)m_memSAPIName.p)[0])
      MMLValueSet (pNode, gpszSAPIName, (PWSTR)m_memSAPIName.p);
   if (((PWSTR)m_memSAPIVendor.p)[0])
      MMLValueSet (pNode, gpszSAPIVendor, (PWSTR)m_memSAPIVendor.p);

   //if (m_memMicroPause.m_dwCurPosn)
   //   MMLValueSet (pNode, gpszMicroPause, (PBYTE)m_memMicroPause.p, m_memMicroPause.m_dwCurPosn);

   if (m_memEnergyPerPitch.m_dwCurPosn)
      MMLValueSet (pNode, gpszEnergyPerPitch, (PBYTE)m_memEnergyPerPitch.p, m_memEnergyPerPitch.m_dwCurPosn);
   if (m_memEnergyPerVolume.m_dwCurPosn)
      MMLValueSet (pNode, gpszEnergyPerVolume, (PBYTE)m_memEnergyPerVolume.p, m_memEnergyPerVolume.m_dwCurPosn);


   if (m_memTTSTARGETCOSTS.m_dwCurPosn == sizeof(TTSTARGETCOSTS))
      MMLValueSet (pNode, gpszTTSTARGETCOSTS, (PBYTE)m_memTTSTARGETCOSTS.p, m_memTTSTARGETCOSTS.m_dwCurPosn);

   DWORD i;
   WCHAR szTemp[64];
   if (m_fIsDerived) for (i = 0; i < NUMPROSODYTTS; i++) {
      if (!m_aszProsodyTTS[i][0])
         continue;
      swprintf (szTemp, L"ProsodyTTS%d", (int)i);
      MMLValueSet (pNode, szTemp, m_aszProsodyTTS[i]);
   } // i

   // convert to sentence syllables
   PCMMLNode2 pSub = m_pCTTSProsody->MMLTo();
   if (pSub) {
      pSub->NameSet (gpszTTSProsody);
      pNode->ContentAdd (pSub);
   }

#if 0 // old prosody
   CMem  memRLE;
   mem.m_dwCurPosn = 0;
   if (m_memNGram.m_dwCurPosn) {
      // convert to RLE
      memRLE.m_dwCurPosn = 0;
      if (RLEEncode ((PBYTE)m_memNGram.p, m_memNGram.m_dwCurPosn, 1, &memRLE))
         return FALSE;
      DWORD dwRLESize = memRLE.m_dwCurPosn;
      if (!dwRLESize)
         return FALSE;

      // write to MML
      MMLValueSet (pNode, gpszNGram, (PBYTE)memRLE.p, memRLE.m_dwCurPosn);
   }
#endif // 0

   pSub = m_pLexTrainingWords->MMLTo();
   if (pSub) {
      pSub->NameSet (gpszLexTrainingWords);
      pNode->ContentAdd (pSub);
   }

   pSub = m_pLexWords->MMLTo();
   if (pSub) {
      pSub->NameSet (gpszLexWords);
      pNode->ContentAdd (pSub);
   }

   for (i = 0; i < NUMFUNCWORDGROUP; i++) {
      if (!m_apLexFuncWord[i])
         continue;

      WCHAR szTemp[64];
      swprintf (szTemp, L"FuncWordGroup%d", (int)i);
      pSub = m_apLexFuncWord[i]->MMLTo();
      if (pSub) {
         pSub->NameSet (szTemp);
         pNode->ContentAdd (pSub);
      }
   }

   // note: not cloning or doing MMLTo/From the generic voice disguise

#if 0 // old prosody
   MMLValueSet (pNode, gpszPunctAccentuate, &m_pPunctAccentuate);
#endif // 0

#if 0 // def _WIN64
   // to test really large voice
   DWORD dwMultiCopies = 100000 / m_dwUnits;
   dwMultiCopies = max(dwMultiCopies, 1);
#endif

   // loop through all the triphones and add them
   DWORD j;
   CMem mem;
   for (i = 0; i < m_dwNumPhone; i++) {
      // get the elem
      PCListFixed pl = m_palPCMTTSTriPhoneAudio[i];
      if (!pl)
         continue;
      PCMTTSTriPhoneAudio *pptp = (PCMTTSTriPhoneAudio*) pl->Get(0);

      // loop through triphones
      mem.m_dwCurPosn = 0;
#if 0 //def _WIN64
      // simulate a lot of copies
      DWORD dwCopy;
      for (dwCopy = 0; dwCopy < dwMultiCopies; dwCopy++)
#endif
      for (j = 0; j < pl->Num(); j++) {
         if (!pptp[j])
            continue;

         size_t dwSize = mem.m_dwCurPosn;
         size_t dwUsed;
         mem.m_dwCurPosn += sizeof(DWORD);
         dwUsed = pptp[j]->MMLToBinary (&mem);
         if (!dwUsed) {
            mem.m_dwCurPosn = dwSize;
            continue;
         }

         DWORD *pdw = (DWORD*)((PBYTE)mem.p + dwSize);
         pdw[0] = (DWORD) dwUsed;
      } // j

      // NOTE: Not using RLE encode here because MMLToBinary does the same
#if 0 // def _WIN64
      // randomize so compress problems
      DWORD k;
      for (k = 0; k < (DWORD)mem.m_dwCurPosn; k++)
         ((PBYTE)mem.p)[k] = (BYTE) rand();
#endif

      // write to MML
      WCHAR szTemp[64];
      swprintf (szTemp, L"TriPhoneAudio%d", (int)i);
      MMLValueSet (pNode, szTemp, (PBYTE)mem.p, mem.m_dwCurPosn);
   } // i

   for (i = 0; i < m_dwNumPhone; i++) {
      // get the elem
      PCListFixed pl = m_palPCMTTSTriPhonePros[i];
      if (!pl)
         continue;
      PCMTTSTriPhonePros *pptp = (PCMTTSTriPhonePros*) pl->Get(0);

      // loop through triphones
      mem.m_dwCurPosn = 0;
      for (j = 0; j < pl->Num(); j++) {
         if (!pptp[j])
            continue;

         size_t dwSize = mem.m_dwCurPosn;
         size_t dwUsed;
         mem.m_dwCurPosn += sizeof(DWORD);
         dwUsed = pptp[j]->MMLToBinary (&mem);
         if (!dwUsed) {
            mem.m_dwCurPosn = dwSize;
            continue;
         }

         DWORD *pdw = (DWORD*)((PBYTE)mem.p + dwSize);
         pdw[0] = (DWORD) dwUsed;
      } // j

      // NOTE: Not using RLE encode here because MMLToBinary does the same

      // write to MML
      WCHAR szTemp[64];
      swprintf (szTemp, L"TriPhonePros%d", (int)i);
      MMLValueSet (pNode, szTemp, (PBYTE)mem.p, mem.m_dwCurPosn);
   } // i

   // WCHAR szTemp[64];

#if 0 // old prosody
   // write out function words
   pSub = m_pLexFuncWords->MMLTo();
   if (pSub) {
      pSub->NameSet (gpszLexFuncWords);
      pNode->ContentAdd (pSub);
   }

   for (i = 0; i < NUMLEXWORDEMPH; i++) {
      if (!m_apLexWordEmph[i])
         continue;

      swprintf (szTemp, L"LexWordEmph%d", (int)i);
      pSub = m_apLexWordEmph[i]->MMLTo();
      if (pSub) {
         pSub->NameSet (szTemp);
         pNode->ContentAdd (pSub);
      }
   }
   for (i = 0; i < NUMLEXWORDEMPH+1; i++) {
      swprintf (szTemp, L"LexWordEmphScale%d", (int)i);
      MMLValueSet (pNode, szTemp, &m_apLexWordEmphScale[i]);
   }
   for (i = 0; i < NUMPROSWORDLENGTH; i++) {
      swprintf (szTemp, L"ProsFromWordLength%d", (int)i);
      MMLValueSet (pNode, szTemp, &m_apProsWordEmphFromWordLength[i]);
   }

   // write out the punctuation prosody
   PCTTSPunctPros *ppp = (PCTTSPunctPros*)m_lPCTTSPunctPros.Get(0);
   for (i = 0; i < m_lPCTTSPunctPros.Num(); i++) {
      if (!ppp[i])
         continue;
      pSub = ppp[i]->MMLTo();
      if (pSub)
         pNode->ContentAdd (pSub);
   } // i
#endif // 0
   for (i = 0; i < NUMPHONEEMPH*2; i++) for (j = 0; j < PHONEPOSBIN; j++) {
      swprintf (szTemp, L"PhoneEmp%dx%d", (int)i, (int)j);
      MMLValueSet (pNode, szTemp, &m_apPhoneEmph[i][j]);
   }

   // write out subvoices
   PCMTTSSubVoice *ppsv = (PCMTTSSubVoice*) m_lPCMTTSSubVoice.Get(0);
   for (i = 0; i < m_lPCMTTSSubVoice.Num(); i++) {
      pSub = ppsv[i]->MMLTo ();
      if (pSub) {
         pSub->NameSet (gpszSubVoice);
         pNode->ContentAdd (pSub);
      }
   } // i


   // write out the phase model
   PCPhaseModel *pppm = (PCPhaseModel*)m_lPCPhaseModel.Get(0);
   mem.m_dwCurPosn = 0;
   for (i = 0; i < m_lPCPhaseModel.Num(); i++) {
      _ASSERTE (pppm[i]);
      if (!pppm[i])  // shouldnt happen
         break;

      if (!pppm[i]->MMLToBinary (&mem))
         break;
   } //
   if ((i >= m_lPCPhaseModel.Num()) && mem.m_dwCurPosn)
      MMLValueSet(pNode, gpszCPhaseModel, (PBYTE) mem.p, mem.m_dwCurPosn);

   // write out the waves
   PCTTSWave *ppTW = (PCTTSWave*)m_lPCTTSWave.Get(0);
   for (i = 0; i < m_lPCTTSWave.Num(); i++) {
      if (!ppTW[i])
         continue;   // empty

      mem.m_dwCurPosn = 0;
      size_t dwSize;
      dwSize = ppTW[i]->MMLToBinary (&mem);
      if (!dwSize)
         continue;   // error

      pSub = pNode->ContentAddNewNode ();
      if (!pSub)
         continue;

      pSub->NameSet (gpszTTSWave);
      MMLValueSet(pSub, gpszTTSWave, (PBYTE) mem.p, dwSize);
   } // i

   return pNode;
}

/*************************************************************************************
CMTTS::MMLFrom - Standard api

inputs
   PCMMLNode2         pNode - Node to read from
   PWSTR             pszSrcFile - If the main lexicon doesnt exist, then the root
                           directory is taken from this and used
   BOOL              fThreadSafe - Set to TRUE if loading in a thread-safe manner (which means
                        that things won't be cached). FALSE if non thread-safe.
*/
BOOL CMTTS::MMLFrom (PCMMLNode2 pNode, PWSTR pszSrcFile, BOOL fThreadSafe)
{
   WCHAR szTemp[128];
#if 0 // def _DEBUG
   swprintf (szTemp, L"\r\n\tCurMem D = %d K", (int)(EscMemoryAllocated(FALSE)/1024));
   OutputDebugStringW (szTemp);
#endif

   // clear out what have
   //m_memMicroPause.m_dwCurPosn = 0;
   m_memEnergyPerPitch.m_dwCurPosn = 0;
   m_memEnergyPerVolume.m_dwCurPosn = 0;
#if 0 // old prosody
   m_memNGram.m_dwCurPosn = 0;
#endif // 0
   m_pLexTrainingWords->Clear();
   m_pLexWords->Clear();
   if (m_pLexMain)
      MLexiconCacheClose (m_pLexMain);
   m_pLexMain = NULL;
   if (m_pTTSMaster)
      TTSCacheClose (m_pTTSMaster);
   m_pTTSMaster = NULL;

   DWORD i,j;
   for (i = 0; i < NUMFUNCWORDGROUP; i++) {
      if (m_apLexFuncWord[i])
         delete m_apLexFuncWord[i];
      m_apLexFuncWord[i] = NULL;
   }

   // free sentece syllables
   m_pCTTSProsody->Clear();

   // clear prosody info
   for (i = 0; i < NUMPROSODYTTS; i++)
      m_aszProsodyTTS[i][0] = 0;

   FreeUnits ();
#if 0 // dont need because handled by free units
   // free up triphones
   for (i = 0; i < m_dwNumPhone; i++) {
      PCListFixed pl = m_palPCMTTSTriPhoneAudio[i];
      if (!pl)
         continue;

      PCMTTSTriPhoneAudio *pptp = (PCMTTSTriPhoneAudio*)pl->Get(0);
      for (j = 0; j < pl->Num(); j++)
         delete pptp[j];

      delete pl;
   } // i
   for (i = 0; i < m_dwNumPhone; i++) {
      PCListFixed pl = m_palPCMTTSTriPhonePros[i];
      if (!pl)
         continue;

      PCMTTSTriPhonePros *pptp = (PCMTTSTriPhonePros*)pl->Get(0);
      for (j = 0; j < pl->Num(); j++)
         delete pptp[j];

      delete pl;
   } // i
   m_dwNumPhone = 0;
#endif // 0

#if 0 // old prosody
   for (i = 0; i < NUMLEXWORDEMPH; i++) {
      if (m_apLexWordEmph[i])
         delete m_apLexWordEmph[i];
      m_apLexWordEmph[i] = NULL;
   }

   // delete punctionan prosody
   PCTTSPunctPros *ppp = (PCTTSPunctPros*)m_lPCTTSPunctPros.Get(0);
   for (i = 0; i < m_lPCTTSPunctPros.Num(); i++)
      if (ppp[i])
         delete ppp[i];
   m_lPCTTSPunctPros.Clear();
#endif // 0

   // clear sub-voices
   PCMTTSSubVoice *ppsv = (PCMTTSSubVoice*) m_lPCMTTSSubVoice.Get(0);
   for (i = 0; i < m_lPCMTTSSubVoice.Num(); i++)
      delete ppsv[i];
   m_lPCMTTSSubVoice.Clear();

#if 0 // def _DEBUG
   swprintf (szTemp, L"\r\n\tCurMem E = %d K", (int)(EscMemoryAllocated(FALSE)/1024));
   OutputDebugStringW (szTemp);
#endif

   m_fIsDerived = (BOOL) MMLValueGetInt (pNode, gpszIsDerived, FALSE);
   DWORD dwVersion = (DWORD)MMLValueGetInt (pNode, gpszVersion, 0);
   if (dwVersion != (m_fIsDerived ? TTSVERSION_DERIVED : TTSVERSION_MASTER)) {
      return FALSE;
   }

   m_fThreadSafe = fThreadSafe;

   // read in
   m_dwTriPhoneGroup = (DWORD) MMLValueGetInt (pNode, gpszTriPhoneGroup, (int) 0);
   m_fKeepLog = (BOOL) MMLValueGetInt (pNode, gpszKeepLog, (int) 0);
   m_fFullPCM = (BOOL) MMLValueGetInt (pNode, gpszFullPCM, (int) 0);
   m_fAvgPitch = MMLValueGetDouble (pNode, gpszAvgPitch, 0);
   m_fAvgPitchF0Fuji= MMLValueGetDouble (pNode, gpszAvgPitchF0Fuji, 0);
   m_fFujiRangeLow = MMLValueGetDouble (pNode, gpszFujiRangeLow, 0.01);
   m_fFujiRangeMed = MMLValueGetDouble (pNode, gpszFujiRangeMed, 0.01);
   m_fFujiRangeHigh = MMLValueGetDouble (pNode, gpszFujiRangeHigh, 0.01);
   m_dwUnits = (DWORD)MMLValueGetInt (pNode, gpszUnits, OPTIMALNUMUNITS);
   m_dwUnits = max(m_dwUnits, 1);
   m_fPauseLessOften = (BOOL) MMLValueGetInt (pNode, gpszPauseLessOften, (int)FALSE);
   m_fAvgSyllableDur = MMLValueGetDouble (pNode, gpszAvgSyllableDur, 0);
   m_dwWordsPerMinute = (DWORD)MMLValueGetInt (pNode, gpszWordsPerMinute, (int)0);
   m_dwWordsPerMinute = max(m_dwWordsPerMinute, 1);
   m_dwNumPhone = (DWORD) MMLValueGetInt (pNode, gpszNumPhone, (int) 0);
   PWSTR psz = MMLValueGet (pNode, gpszLexicon);
   if (psz)
      wcscpy (m_szLexicon, psz);
   else
      m_szLexicon[0] = 0;



   // for SAPI
   m_LangID = (LANGID) MMLValueGetInt (pNode, gpszLangID, 1033);
   m_dwAge = (DWORD) MMLValueGetInt (pNode, gpszAge, 2);
   m_dwGender = (DWORD) MMLValueGetInt (pNode, gpszGender, 0);
   m_dwVersionMajor = (DWORD) MMLValueGetInt (pNode, gpszVersionMajor, 1);
   m_dwVersionMinor = (DWORD) MMLValueGetInt (pNode, gpszVersionMinor, 0);
   m_dwVersionBuild = (DWORD) MMLValueGetInt (pNode, gpszVersionBuild, 1);
   psz = MMLValueGet (pNode, gpszSAPIToken);
   MemZero (&m_memSAPIToken);
   if (psz)
      MemCat (&m_memSAPIToken, psz);
   psz = MMLValueGet (pNode, gpszSAPIName);
   MemZero (&m_memSAPIName);
   if (psz)
      MemCat (&m_memSAPIName, psz);
   psz = MMLValueGet (pNode, gpszSAPIVendor);
   MemZero (&m_memSAPIVendor);
   if (psz)
      MemCat (&m_memSAPIVendor, psz);


   psz = MMLValueGet (pNode, gpszTTSMaster);
   if (psz)
      wcscpy (m_szTTSMaster, psz);
   else
      m_szTTSMaster[0] = 0;

   if (m_fIsDerived) for (i = 0; i < NUMPROSODYTTS; i++) {
      swprintf (szTemp, L"ProsodyTTS%d", (int)i);
      psz = MMLValueGet (pNode, szTemp);
      if (psz)
         wcscpy (m_aszProsodyTTS[i], psz);
      else
         m_aszProsodyTTS[i][0] = 0;
   } // i

#if 0 // def _DEBUG
   swprintf (szTemp, L"\r\n\tCurMem F = %d K", (int)(EscMemoryAllocated(FALSE)/1024));
   OutputDebugStringW (szTemp);
#endif


   // target costs
   m_memTTSTARGETCOSTS.m_dwCurPosn = 0;
   MMLValueGetBinary (pNode, gpszTTSTARGETCOSTS, &m_memTTSTARGETCOSTS);


   PCMMLNode2 pSub;
   for (i = 0; i < NUMFUNCWORDGROUP; i++) {
      WCHAR szTemp[64];
      swprintf (szTemp, L"FuncWordGroup%d", (int)i);
      pSub = NULL;
      pNode->ContentEnum(pNode->ContentFind (szTemp), &psz, &pSub);
      if (!pSub)
         continue;

#if 0 // def _DEBUG
   swprintf (szTemp, L"\r\n\tCurMem G%da = %d K", (int)i, (int)(EscMemoryAllocated(FALSE)/1024));
   OutputDebugStringW (szTemp);
#endif
      m_apLexFuncWord[i] = new CMLexicon;
      if (!m_apLexFuncWord[i])
         continue;
#if 0 // def _DEBUG
   swprintf (szTemp, L"\r\n\tCurMem G%db = %d K", (int)i, (int)(EscMemoryAllocated(FALSE)/1024));
   OutputDebugStringW (szTemp);
#endif
      m_apLexFuncWord[i]->MMLFrom (pSub, NULL, FALSE);
#if 0 // def _DEBUG
   swprintf (szTemp, L"\r\n\tCurMem G%dc = %d K", (int)i, (int)(EscMemoryAllocated(FALSE)/1024));
   OutputDebugStringW (szTemp);
#endif
   }

   // get the words...
#if 0 // def _DEBUG
   swprintf (szTemp, L"\r\n\tCurMem G = %d K", (int)(EscMemoryAllocated(FALSE)/1024));
   OutputDebugStringW (szTemp);
#endif



   pSub = NULL;
   pNode->ContentEnum (pNode->ContentFind (gpszLexTrainingWords), &psz, &pSub);
   if (pSub)
      m_pLexTrainingWords->MMLFrom (pSub, NULL, FALSE);

#if 0 // def _DEBUG
   swprintf (szTemp, L"\r\n\tCurMem H = %d K", (int)(EscMemoryAllocated(FALSE)/1024));
   OutputDebugStringW (szTemp);
#endif

   pSub = NULL;
   pNode->ContentEnum (pNode->ContentFind (gpszLexWords), &psz, &pSub);
   if (pSub)
      m_pLexWords->MMLFrom (pSub, NULL, FALSE);

   // note: not cloning or doing MMLTo/From the generic voice disguise

   // tts accent
   CMem memRLE, mem;

#if 0 // old prosody
   m_pPunctAccentuate.Copy (&m_pPOSAccentuate);
   MMLValueGetPoint (pNode, gpszPunctAccentuate, &m_pPunctAccentuate);
#endif // 0

      // BUGFIX - Moved the lexicon code from the end of the load to this part
   if (!LexiconExists (m_szLexicon, pszSrcFile)) {
      if (!m_fIsDerived)
         return FALSE;
   }
   if (!LexiconRequired()) {   // if cant load the lexicon the fail altogether
      if (!m_fIsDerived)
         return FALSE;
   }

   // load in tts master
   // BUGFIX - Was LexiconExists(), but wrong call
   //LexiconExists (m_szTTSMaster, pszSrcFile);
   if (m_szTTSMaster[0]) {
      ResolvePathIfNotExist(m_szTTSMaster, pszSrcFile);
      m_pTTSMaster = TTSCacheOpen (m_szTTSMaster, TRUE, m_fThreadSafe);
   }
   else
      m_pTTSMaster = NULL;

#if 0 // def _DEBUG
   swprintf (szTemp, L"\r\n\tCurMem I = %d K", (int)(EscMemoryAllocated(FALSE)/1024));
   OutputDebugStringW (szTemp);
#endif

   // set the lexicon
   // m_pCTTSProsody->LexiconSet (Lexicon(TRUE));

   if (!MakeSureEnoughPhone ())
      return FALSE;

#if 0 // handled by MakeSureEnoughPhone
   // allocate memory for triphones
   if (!m_memTriPhoneAudio.Required (m_dwNumPhone * sizeof(PCListFixed)))
      return FALSE;
   m_palPCMTTSTriPhoneAudio = (PCListFixed*) m_memTriPhoneAudio.p;
   memset (m_palPCMTTSTriPhoneAudio, 0, m_dwNumPhone * sizeof(PCListFixed));

   // allocate memory for triphones
   if (!m_memTriPhonePros.Required (m_dwNumPhone * sizeof(PCListFixed)))
      return FALSE;
   m_palPCMTTSTriPhonePros = (PCListFixed*) m_memTriPhonePros.p;
   memset (m_palPCMTTSTriPhonePros, 0, m_dwNumPhone * sizeof(PCListFixed));
#endif // 0

#if 0 // def _DEBUG
   swprintf (szTemp, L"\r\n\tCurMem J = %d K", (int)(EscMemoryAllocated(FALSE)/1024));
   OutputDebugStringW (szTemp);
#endif

   // loop through all the phones...
   PCMLexicon pLexicon = Lexicon();
   for (i = 0; i < m_dwNumPhone; i++) {
      WCHAR szTemp[64];
      swprintf (szTemp, L"TriPhoneAudio%d", (int)i);

      // try decompressing this into a large memory that can keep, for faster
      // loads and close down
      PCMem pMemToUse = NULL;
      pMemToUse = &mem;
      mem.m_dwCurPosn = 0;
      //if (!m_pamemTriPhoneAudio[i])
      //   pMemToUse = m_pamemTriPhoneAudio[i] = new CMem;
      //if (!pMemToUse)
      //   pMemToUse = &mem;

      // BUGFIX - Use new MMLValueGetbinary
      MMLValueGetBinary (pNode, szTemp, pMemToUse);
      //psz = MMLValueGet (pNode, szTemp);
      size_t dwSize = pMemToUse->m_dwCurPosn;
      if (!dwSize)
         continue;
      PBYTE pb = (PBYTE)pMemToUse->p;

      // make a list
      PCListFixed pl = new CListFixed;
      if (!pl)
         return FALSE;
      pl->Init (sizeof(PCMTTSTriPhoneAudio));

      // count the number of elements
      DWORD dwCur = 0;
      DWORD dwCount;
      for (dwCount = 0; dwCur+sizeof(DWORD) < dwSize; dwCount++) {
         DWORD dwElem = *((DWORD*)(pb + dwCur));
         dwCur += sizeof(DWORD);
         if (dwElem+dwCur > dwSize)
            break;
         DWORD dwOld = dwCur;
         dwCur += dwElem;
      } // while

      // make sure enough elements
      pl->Required (dwCount);

      // minimize the mallocs and frees necessary
      BOOL fCanCache = FALSE;
      if (!m_papObjectCachePCMTTSTriPhoneAudio[i]) {
         m_papObjectCachePCMTTSTriPhoneAudio[i] = new CMTTSTriPhoneAudio[dwCount];
         fCanCache = m_papObjectCachePCMTTSTriPhoneAudio[i] ? TRUE : FALSE;
      }

      // loop
      dwCur = 0;
      while (dwCur+sizeof(DWORD) < dwSize) {
         DWORD dwElem = *((DWORD*)(pb + dwCur));
         dwCur += sizeof(DWORD);
         if (dwElem+dwCur > dwSize)
            break;
         DWORD dwOld = dwCur;
         dwCur += dwElem;

         PCMTTSTriPhoneAudio ptp;
         if (fCanCache) {
            ptp = &m_papObjectCachePCMTTSTriPhoneAudio[i][pl->Num()];
            ptp->m_fCached = TRUE;
         }
         else
            ptp = new CMTTSTriPhoneAudio;
         if (!ptp)
            continue;
         if (!ptp->MMLFromBinary (pb + dwOld, dwElem, pLexicon /*, pMemToUse != &mem*/)) {
            if (!fCanCache)
               delete ptp;
            continue;
         }

         pl->Add (&ptp);
      }

      // fill in list
      m_palPCMTTSTriPhoneAudio[i] = pl;
   } // i

#if 0 // def _DEBUG
   swprintf (szTemp, L"\r\n\tCurMem K = %d K", (int)(EscMemoryAllocated(FALSE)/1024));
   OutputDebugStringW (szTemp);
#endif

   // all the prosody triphones
   for (i = 0; i < m_dwNumPhone; i++) {
      WCHAR szTemp[64];
      swprintf (szTemp, L"TriPhonePros%d", (int)i);


      // BUGFIX - Use new MMLValueGetbinary
      MMLValueGetBinary (pNode, szTemp, &mem);
      //psz = MMLValueGet (pNode, szTemp);
      size_t dwSize = mem.m_dwCurPosn;
      if (!dwSize)
         continue;
      PBYTE pb = (PBYTE)mem.p;

      // make a list
      PCListFixed pl = new CListFixed;
      if (!pl)
         return FALSE;
      pl->Init (sizeof(PCMTTSTriPhonePros));

      // count the number of elements
      DWORD dwCur = 0;
      DWORD dwCount;
      for (dwCount = 0; dwCur+sizeof(DWORD) < dwSize; dwCount++) {
         DWORD dwElem = *((DWORD*)(pb + dwCur));
         dwCur += sizeof(DWORD);
         if (dwElem+dwCur > dwSize)
            break;
         DWORD dwOld = dwCur;
         dwCur += dwElem;
      } // dwCount
      pl->Required (dwCount);

      // minimize the mallocs and frees necessary
      BOOL fCanCache = FALSE;
      if (!m_papObjectCachePCMTTSTriPhonePros[i]) {
         m_papObjectCachePCMTTSTriPhonePros[i] = new CMTTSTriPhonePros[dwCount];
         fCanCache = m_papObjectCachePCMTTSTriPhonePros[i] ? TRUE : FALSE;
      }

      // loop
      dwCur = 0;
      while (dwCur+sizeof(DWORD) < dwSize) {
         DWORD dwElem = *((DWORD*)(pb + dwCur));
         dwCur += sizeof(DWORD);
         if (dwElem+dwCur > dwSize)
            break;
         DWORD dwOld = dwCur;
         dwCur += dwElem;

         PCMTTSTriPhonePros ptp;
         if (fCanCache) {
            ptp = &m_papObjectCachePCMTTSTriPhonePros[i][pl->Num()];
            ptp->m_fCached = TRUE;
         }
         else
            ptp = new CMTTSTriPhonePros;
         if (!ptp)
            continue;
         if (!ptp->MMLFromBinary (pb + dwOld, dwElem, pLexicon)) {
            if (!fCanCache)
               delete ptp;
            continue;
         }

         pl->Add (&ptp);
      }

      // fill in list
      m_palPCMTTSTriPhonePros[i] = pl;
   } // i

#if 0 // def _DEBUG
   swprintf (szTemp, L"\r\n\tCurMem L = %d K", (int)(EscMemoryAllocated(FALSE)/1024));
   OutputDebugStringW (szTemp);
#endif

   // WCHAR szTemp[64];
#if 0 // old prosody
   for (i = 0; i < NUMLEXWORDEMPH; i++) {
      swprintf (szTemp, L"LexWordEmph%d", (int)i);
      pSub = NULL;
      pNode->ContentEnum(pNode->ContentFind (szTemp), &psz, &pSub);
      if (!pSub)
         continue;

      m_apLexWordEmph[i] = new CMLexicon;
      if (!m_apLexWordEmph[i])
         continue;
      m_apLexWordEmph[i]->MMLFrom (pSub, NULL);
   }
   for (i = 0; i < NUMLEXWORDEMPH+1; i++) {
      swprintf (szTemp, L"LexWordEmphScale%d", (int)i);
      MMLValueGetPoint (pNode, szTemp, &m_apLexWordEmphScale[i]);
   }
   for (i = 0; i < NUMPROSWORDLENGTH; i++) {
      swprintf (szTemp, L"ProsFromWordLength%d", (int)i);
      m_apProsWordEmphFromWordLength[i].p[0] = 
         m_apProsWordEmphFromWordLength[i].p[1] = 
         m_apProsWordEmphFromWordLength[i].p[2] = 1;
      MMLValueGetPoint (pNode, szTemp, &m_apProsWordEmphFromWordLength[i]);
   }
#endif // 0
   for (i = 0; i < NUMPHONEEMPH*2; i++) for (j = 0; j < PHONEPOSBIN; j++) {
      swprintf (szTemp, L"PhoneEmp%dx%d", (int)i, (int)j);
      m_apPhoneEmph[i][j].p[0] = m_apPhoneEmph[i][j].p[1] = m_apPhoneEmph[i][j].p[2] = 1;
      MMLValueGetPoint (pNode, szTemp, &m_apPhoneEmph[i][j]);
   }


#if 0 // old prosody
   // function words
   pSub = NULL;
   pNode->ContentEnum (pNode->ContentFind (gpszLexFuncWords), &psz, &pSub);
   if (pSub)
      m_pLexFuncWords->MMLFrom (pSub, NULL);
#endif // 0

   // get the phase model
   size_t dwSize = MMLValueGetBinary (pNode, gpszCPhaseModel, &mem);
   PBYTE pabCur = (PBYTE)mem.p;
   _ASSERTE (!m_lPCPhaseModel.Num());
   for (i = 0; dwSize; i++) {
      PCPhaseModel ppm = new CPhaseModel;
      if (!ppm)
         break;   // error, shouldnt happen
      size_t dwUsed = ppm->MMLFromBinary (pabCur, dwSize);
      if (!dwUsed) {
         delete ppm;
         break;
      }

      // add
      m_lPCPhaseModel.Add (&ppm);
      dwSize -= dwUsed;
      pabCur += dwUsed;
   } // i and dwSize
   _ASSERTE (!dwSize);

   // find misc stuff
   for (i = 0; i < pNode->ContentNum(); i++) {
      pSub = NULL;
      pNode->ContentEnum (i, &psz, &pSub);
      if (!pSub)
         continue;
      psz = pSub->NameGet();
      if (!psz)
         continue;

      if (!_wcsicmp(psz, gpszTTSWave)) {
         mem.m_dwCurPosn = 0;
         size_t dwSize = MMLValueGetBinary (pSub, gpszTTSWave, &mem);
         if (!dwSize)
            continue;

         PCTTSWave pTW = new CTTSWave;
         if (!pTW)
            continue;
         if (!pTW->MMLFromBinary (mem.p, (DWORD) dwSize)) {
            delete pTW;
            continue;
         }

         // while not enough elements add
         PCTTSWave pTWNULL = NULL;
         while (m_lPCTTSWave.Num() <= pTW->m_dwSentenceNum)
            m_lPCTTSWave.Add (&pTWNULL);

         PCTTSWave *ppTW = (PCTTSWave*)m_lPCTTSWave.Get(0);
         if (ppTW[pTW->m_dwSentenceNum])
            delete ppTW[pTW->m_dwSentenceNum];  // shouldnt happen
         ppTW[pTW->m_dwSentenceNum] = pTW;
         continue;
      }
      else if (!_wcsicmp(psz, gpszSubVoice)) {
         PCMTTSSubVoice pNew = new CMTTSSubVoice;
         if (!pNew)
            continue;
         if (!pNew->MMLFrom (pSub, pszSrcFile)) {
            delete pNew;
            continue;
         }
         m_lPCMTTSSubVoice.Add (&pNew);
         continue;
      }
      else if (!_wcsicmp(psz, gpszTTSProsody)) {
         m_pCTTSProsody->MMLFrom (pSub, pszSrcFile);
         continue;
      }
#if 0 // old prosody
      if (!_wcsicmp(psz, gpszPunctPros)) {
         PCTTSPunctPros pNew = new CTTSPunctPros;
         if (!pNew)
            continue;
         pNew->MMLFrom (pSub);
         m_lPCTTSPunctPros.Add (&pNew);
         continue;
      }
#endif // 0
   }

#if 0 // def _DEBUG
   swprintf (szTemp, L"\r\n\tCurMem M = %d K", (int)(EscMemoryAllocated(FALSE)/1024));
   OutputDebugStringW (szTemp);
#endif


   // micropauses
   // BUGFIX - Use new binary call
   //MMLValueGetBinary (pNode, gpszMicroPause, &m_memMicroPause);
   // psz = MMLValueGet (pNode, gpszMicroPause);
   //if (psz) {
   //   if (!m_memMicroPause.Required (wcslen(psz)/2))
   //      return FALSE;
   //   m_memMicroPause.m_dwCurPosn = MMLBinaryFromString(psz,
   //      (PBYTE)m_memMicroPause.p, m_memMicroPause.m_dwAllocated);
   //}

   // energy per pitch
   MMLValueGetBinary (pNode, gpszEnergyPerPitch, &m_memEnergyPerPitch);

   // energy per volume
   MMLValueGetBinary (pNode, gpszEnergyPerVolume, &m_memEnergyPerVolume);
#if 0 // old prosody
   // micropauses
   // BUGFIX - new binary call
   memRLE.m_dwCurPosn = 0;
   MMLValueGetBinary (pNode, gpszNGram, &memRLE);
   //psz = MMLValueGet (pNode, gpszNGram);
   if (memRLE.m_dwCurPosn /*psz*/) {
      //if (!memRLE.Required (wcslen(psz)/2))
      //   return FALSE;
      PBYTE pb = (PBYTE)memRLE.p;
      //DWORD dwSize = MMLBinaryFromString (psz, pb, memRLE.m_dwAllocated);
      DWORD dwSize = memRLE.m_dwCurPosn;

      // RLE decode
      m_memNGram.m_dwCurPosn = 0;   // BUGFIX - Was just mem, wrong var
      DWORD dwUsed;
      if (RLEDecode ((PBYTE)memRLE.p, dwSize, 1, &m_memNGram, &dwUsed))
         return FALSE;
      dwSize = m_memNGram.m_dwCurPosn;
      if (!dwSize)
         return FALSE;

      // make sure size is correct
      DWORD dwExpect = (DWORD)pow(POS_MAJOR_NUM+1, TTSPROSNGRAM*2+1) *
         (DWORD) pow(3, TTSPROSNGRAMBIT*2) * sizeof(TTSNGRAM);
      if (dwExpect != m_memNGram.m_dwCurPosn)
         m_memNGram.m_dwCurPosn = 0;   // invalid
   }
#endif // 0

#if 0 // def _DEBUG
   swprintf (szTemp, L"\r\n\tCurMem N = %d K", (int)(EscMemoryAllocated(FALSE)/1024));
   OutputDebugStringW (szTemp);
#endif


   // precalc SRANALBLOCK
#ifdef TTSWAVECALSRANALBLOCK_CREATETHREADS
   if (m_lPCTTSWave.Num())
      TTSWaveCalcSRANALBLOCKCreateThreads ();
#endif

   // make sure filled in
   FillInTriPhoneIndex ();

   return TRUE;
}

/*************************************************************************************
CMTTS::Save - Standard api
*/
BOOL CMTTS::Save (PWSTR pszFile)
{
   if (!pszFile)
      pszFile = m_szFile;

   PCMMLNode2 pNode = MMLTo();
   if (!pNode)
      return FALSE;
   BOOL fRet;
   fRet = MMLFileSave (pszFile, &GUID_TTS, pNode);
   delete pNode;

   if (fRet && (pszFile != m_szFile))
      wcscpy (m_szFile, pszFile);

   return fRet;
}

/*************************************************************************************
CMTTS::Open - Standard api

inputs
   PWSTR          pszFile - Filename
   BOOL           fThreadSafe - TRUE if open so it's thread safe (not using caching).
                     FALSE if allowed to use caching.
*/
BOOL CMTTS::Open (PWSTR pszFile, BOOL fThreadSafe)
{
#ifdef _DEBUG
   DWORD dwTime = GetTickCount();
#endif

   // BUGFIX - Ignore directory
   PCMMLNode2 pNode = MMLFileOpen (pszFile, &GUID_TTS, NULL, TRUE);
   if (!pNode) {
      // BUGBUG - more or less temporary hack. can get rid of eventually
      pNode = MMLFileOpen (pszFile, &GUID_TTSOLD, NULL, TRUE);

      if (!pNode)
         return FALSE;

      // if old version, only accept derived
      if (!MMLValueGetInt (pNode, gpszIsDerived, FALSE)) {
         delete pNode;
         return FALSE;
      }

   } // old version

   if (!MMLFrom (pNode, pszFile, fThreadSafe)) {
      delete pNode;
      return FALSE;
   }

#ifdef _DEBUG
   WCHAR szTemp[64];
   swprintf (szTemp, L"\r\nTTSLoadTime = %d", (int) (GetTickCount() - dwTime));
   OutputDebugStringW (szTemp);
#endif
   // rembmeber the file
   wcscpy (m_szFile, pszFile);

   delete pNode;
   return TRUE;
}



/*************************************************************************************
CMTTS::LexiconRequired - Loads the lexicon if it isn't already loaded.
Returns TRUE if success, FALSE if error
*/
BOOL CMTTS::LexiconRequired (void)
{
   if (m_pLexMain)
      return TRUE;

   m_pLexMain = MLexiconCacheOpen(m_szLexicon, FALSE);
   return (m_pLexMain ? TRUE : FALSE);
}

/*************************************************************************************
CMTTS::LexiconSet - Sets a new lexicon to use

inputs
   PWSTR          pszLexicon - Lexicon
returns
   BOOL - TRUE if was able to open, FALSE if not
*/
BOOL CMTTS::LexiconSet (PWSTR pszLexicon)
{
   if (m_pLexMain)
      MLexiconCacheClose (m_pLexMain);
   m_pLexMain = NULL;
   wcscpy (m_szLexicon, pszLexicon);

   BOOL fRet = LexiconRequired ();


   return fRet;
}

/*************************************************************************************
CMTTS::LexiconGet - Returns a pointer to the lexicon string. DO NOT modify it.
*/
PWSTR CMTTS::LexiconGet (void)
{
   return m_szLexicon;
}

/*************************************************************************************
CMTTS::Lexicon - Returns a pointer to the lexicon to use for SR. NULL if cant open

inputs
   BOOL        fBackoffToMaster - If TRUE then backs off to the master lexicon if this
               happens to be a derived voice and has no lexicon of its own
*/
PCMLexicon CMTTS::Lexicon (BOOL fBackoffToMaster)
{
   LexiconRequired ();

   // BUGFIX - If this is a derived TTS voice and no lexicon of its own, use
   // its master
   if (m_fIsDerived && !m_pLexMain && m_pTTSMaster && fBackoffToMaster)
      return m_pTTSMaster->Lexicon();

   return m_pLexMain;
}



/*************************************************************************************
CMTTS::TTSWaveCompress - Compress all TTSWaves
*/
void CMTTS::TTSWaveCompress (void)
{
   // don't bother doing any compression if still analyzing and creating SRANALBLOCKS
   // because ends up slowing things way down
   if (m_dwTTSWaveAnalyzing)
      return;

   DWORD i;
   PCTTSWave *ppTW = (PCTTSWave*)m_lPCTTSWave.Get(0);
   for (i = 0; i < m_lPCTTSWave.Num(); i++)
      if (ppTW[i] && ppTW[i]->NeedToCompress()) {
         EnterCriticalSection (&m_acsTTSWave[i % MAXRAYTHREAD]);
         ppTW[i]->Compress();
         LeaveCriticalSection (&m_acsTTSWave[i % MAXRAYTHREAD]);
      }

}


/*************************************************************************************
CMTTS::TTSWaveCalcSRFEATURESMALL - Calculates the SRFEATURESMALL for all TTS Waves

inputs
   DWORD       dwInital - Inital wave number
   DWORD       dwModulo - Skip this number of waves. Used to to multthreaded calcs.
*/
void CMTTS::TTSWaveCalcSRANALBLOCK (DWORD dwInitial, DWORD dwModulo)
{
   EnterCriticalSection (&m_csTTSWaveDecompress);
   m_dwTTSWaveAnalyzing++;
   LeaveCriticalSection (&m_csTTSWaveDecompress);

   DWORD i;
   PCTTSWave *ppTW = (PCTTSWave*)m_lPCTTSWave.Get(0);
   for (i = dwInitial; i < m_lPCTTSWave.Num(); i += dwModulo)
      if (ppTW[i]) {
         ppTW[i]->CalcSRANALBLOCK(&m_acsTTSWave[i % MAXRAYTHREAD]);

         // compress back, assuming that allowed to
         EnterCriticalSection (&m_acsTTSWave[i % MAXRAYTHREAD]);
         if (!m_fTTSWaveDisableCompress)
            ppTW[i]->Compress();
         LeaveCriticalSection (&m_acsTTSWave[i % MAXRAYTHREAD]);

         // exit if flag set saying that want to quit
         if (m_fTTSWaveCalcSRANALBLOCKWantToQuit)
            break;

         // BUGFIX - Put a sleep for 10 milliseconds so acts as very low priority
         Sleep(10);
      }

   EnterCriticalSection (&m_csTTSWaveDecompress);
   m_dwTTSWaveAnalyzing--;
   LeaveCriticalSection (&m_csTTSWaveDecompress);

}

/*************************************************************************************
TTSWaveCalcSRANALBLOCKThread - Thread used for each wave segment.
*/
static DWORD WINAPI TTSWaveCalcSRANALBLOCKThread(LPVOID lpParameter)
{
   PTHREADWAVECALCINFO pInfo = (PTHREADWAVECALCINFO) lpParameter;

   pInfo->pThis->TTSWaveCalcSRANALBLOCK (pInfo->dwInitial, pInfo->dwModulo);

   return 0;
}

/*************************************************************************************
CMTTS::TTSWaveCalcSRANALBLOCKCreateThreads - This creates several threads
used to calculate all the features.
*/
void CMTTS::TTSWaveCalcSRANALBLOCKCreateThreads (void)
{
   if (m_aThreadWaveCalcInfo[0].hThread)
      return;  // shouldnt happen

   DWORD dwThreads = 1; // HowManyProcessors ();
      // BUGFIX - Don't allocate one per processor, just one thread to do the background
      // processing

   // set up the threads
   DWORD i;
   for (i = 0; i < dwThreads; i++) {
      m_aThreadWaveCalcInfo[i].pThis = this;
      m_aThreadWaveCalcInfo[i].dwInitial = i;
      m_aThreadWaveCalcInfo[i].dwModulo = dwThreads;

      m_aThreadWaveCalcInfo[i].hThread = CreateThread (NULL, 0, TTSWaveCalcSRANALBLOCKThread, &m_aThreadWaveCalcInfo[i], 0, &m_aThreadWaveCalcInfo[i].dwThreadID);
      SetThreadPriority (m_aThreadWaveCalcInfo[i].hThread, VistaThreadPriorityHack(THREAD_PRIORITY_BELOW_NORMAL));  // so doesnt suck up all CPU
         // BUGFIX - Try lowest instead of THREAD_PRIORITY_BELOW_NORMAL because added Sleep(10)
         // BUGFIX - Was THREAD_PRIORITY_LOWEST, but would cause hang-ups in TTS if this has audio
         // checked out at same time wants to access
   }

}


/*************************************************************************************
CMTTS::TTSWaveFind - Find the required CTTSWave

inputs
   DWORD                dwSentenceNum - Sentence number
returns
   PCTTSWave - Wave, or NULL if can't find
*/
PCTTSWave CMTTS::TTSFindWave (DWORD dwSentenceNum)
{
   if (dwSentenceNum >= m_lPCTTSWave.Num())
      return NULL;
   PCTTSWave *ppTW = (PCTTSWave*)m_lPCTTSWave.Get(0);
   return ppTW[dwSentenceNum];
}



/*************************************************************************************
CMTTS::TTSWaveAdd - Adds audio to the wave segment.

inputs
   WORD           wFlags - Compression flags, to be stored in CTTSWaveSegment.m_wFlags
   PCM3DWave      pWave - Wave to use. Must have valid pitch and SRFEATUREs
                  If the wave is to store away PCM (wFlags contains TPMML_PCMCOMPRESSMASK)
                  then this wave must contain PCM, and can't merely be cached
   DWORD          dwSentenceNum - Sentence number.
   DWORD          dwFeatureStart - Feature to use
   DWORD          dwFeatureEnd - Up to this feature (exclusive)
   PSRFEATURE     pSRFToUse - If NULL then get features from pWave. If not, use features
                  from this, assuming that pSRFToUse[0] is from dwFeatureStart.
returns
   BOOL - TRUE if success of if already exists
*/
BOOL CMTTS::TTSWaveAdd (WORD wFlags, PCM3DWave pWave, DWORD dwSentenceNum, DWORD dwFeatureStart, DWORD dwFeatureEnd, PSRFEATURE pSRFToUse)
{
   // fill up to the sentence number
   PCTTSWave pTWNULL = NULL;
   while (m_lPCTTSWave.Num() <= dwSentenceNum)
      m_lPCTTSWave.Add (&pTWNULL);

   PCTTSWave *ppTW = (PCTTSWave*)m_lPCTTSWave.Get(0);
   if (!ppTW[dwSentenceNum]) {
      PCTTSWave pTW = new CTTSWave;
      if (!pTW)
         return FALSE;

      pTW->m_dwSentenceNum = dwSentenceNum;
      ppTW[dwSentenceNum] = pTW;
   }

   // pass it down
   return ppTW[dwSentenceNum]->Add (wFlags, pWave, dwFeatureStart, dwFeatureEnd, pSRFToUse);
}

/*************************************************************************************
CMTTS::TriPhonAudioeSet - Sets a new triphone. If the existing triphone exists, it
is overwritten (assuming flag is set to check for already exists).

Before calling this, you mus have called TTSWaveAdd(), probably indirectly through
another function. The audio for the phone's dwFeatureStart to dwFeatureEnd must exist

inputs
   DWORD             dwOrigWave - Original wave number. Useful for debugging
   DWORD             dwOrigPhone - Original phoneme index into th eoriginal wave
   DWORD             dwPhone - Phoneme to set
   DWORD             dwWord - Word this is specific to, or -1 if for all words
   WORD              wWordSylPos - Word position. Combination of WORDPOS_XXX flags.
   BYTE              bPhoneLeft - Phoneme to the left
   BYTE              bPhoneRight - Phoneme to the right
   DWORD             dwFuncWordGroup - How much of a function word this is, from 0..NUMFUNCWORDGROUP(inclusive)
   BYTE              *pabRank - Array of TTSDEMIPHONE ranks. Rank, from 0..100, where 0 is the best-sounding version
   PMISMATCHINFO     paMMI - Array of TRIPHONEMISMATCH mismatch info for alternate recognition values for similar triphones
   DWORD             dwMismatchAccuracy - What units the paMMI info is in. 0 = phoneme group, 1 = unstressed phone, 2 = phone
   WORD              wFlags - Flags TPMML_XXX
   float             fOrigPitchF0Extra - Original pitch in the unit taken (above fuji)
   float             fOrigPitchF0Fuji - Original fuji pitch in the unit taken
   float             fPitchDelta - Ratio of right pitch to left pitch
   float             fPitchBulge - How much pitch bulges up/down in center (ratio)
   float             fCenterEnergy - Calculated energy
   float             fLeftPitchF0Extra - Pitch of left unit, or 0 if silence... LeftXXX and fEnergy (and fOrigPitch) are used to ensure continuity
   float             fLeftPitchF0Fuji - Pitch of left unit
   float             fLeftEnergy - Energy of left unit, or 0 if sielnce
   DWORD             dwLeftDuration - Duration of the left unit (in SRFEATUREs)
   DWORD             dwFeatureStart - Starting SRFEATURE in the wave
   DWORD             dwFeatureEnd - Ending SRFEATURE in the wave
//   PSRFEATURE        pSRF - Pointer to SRFeatures to set
//   float             *pafPitch - Array of dwNum pitch (in Hz) entries
//   DWORD             dwNum - Number
//   BOOL              fCanUseLeft - If TRUE, pSRF[-1] is valid and can be used to smoothing audio
//   BOOL              fCanUseRight - If TRUE, pSRF[dwNum] is valid and can be used for smoothing audio
   DWORD             dwTrimLeft - Number of SRFEATURE units can trim from left if all alone, 0..dwNum-1
   DWORD             dwTrimRight - Number of SRFEATURE units can trim from right if all alone, 0..dwNum-1
   fp                fMaxEnergyInWave - Maximum energy in the wave (from wave)
   fp                fMaxEnergyInWaveMod - Potentially modified some more
   PBYTE             pabPhoneContigous - Pointer to an array of the next 8 phonemes in the wave (excluding silence).
                     Phoneme numbers are +1. Use 0 to indicate blank/end-of-string
   fp                *pafPitchConfidence - Array of TTSDEMIPHONE value with pitch confidence, from 0.0 to 1.0
   fp                fAccent - Amount of accent on the phoneme, from -1.0 to 1.0
   BOOL              fCheckForExist - If TRUE, look for an existing one and replace
                     that, if FALSE just add on because know there wont be an existing one
returns
   BOOL - TRUE if success
*/
BOOL CMTTS::TriPhoneAudioSet (DWORD dwOrigWave, DWORD dwOrigPhone, DWORD dwPhone, DWORD dwWord,
                         WORD wWordSylPos, BYTE bPhoneLeft, BYTE bPhoneRight,
                         DWORD dwFuncWordGroup,
                         BYTE *pabRank, PMISMATCHINFO paMMI, DWORD dwMismatchAccuracy,
                         // WORD wFlags,
                         float fOrigPitchF0Extra, float fOrigPitchF0Fuji, float fPitchDelta, float fPitchBulge,
                         float fCenterEnergy, float fLeftPitchF0Extra, float fLeftPitchF0Fuji, float fLeftEnergy, DWORD dwLeftDuration,
                         DWORD dwFeatureStart, DWORD dwFeatureEnd,
//                         PSRFEATURE pSRF, float *pafPitch, DWORD dwNum,
//                         BOOL fCanUseLeft, BOOL fCanUseRight,
                         DWORD dwTrimLeft, DWORD dwTrimRight,
                         fp fMaxEnergyInWave,
                         fp fMaxEnergyInWaveMod,
                         PBYTE pabPhoneContigous,
                         fp *pafPitchConfidence,
                         fp fAccent,
                         BOOL fCheckForExist)
{
   // if derived dont allow
   if (m_fIsDerived)
      return FALSE;

   // make sure this audio exists
   PCTTSWave pTW = TTSFindWave (dwOrigWave);
   if (!pTW)
      return FALSE;
   PTTSFEATURECOMPEXTRA pTFCE;
   PSRFEATURE pSRFGot = pTW->GetSRFEATURE (&m_acsTTSWave[dwOrigWave % MAXRAYTHREAD], dwFeatureStart, dwFeatureEnd, NULL, NULL, &pTFCE);
   if (!pSRFGot)
      return FALSE;

   // get the list
   if (!MakeSureEnoughPhone(dwPhone))
      return FALSE;
   PCListFixed pl = m_palPCMTTSTriPhoneAudio[dwPhone];
   if (!pl) {
      pl = m_palPCMTTSTriPhoneAudio[dwPhone] = new CListFixed;
      if (!pl)
         return FALSE;
      pl->Init (sizeof(PCMTTSTriPhoneAudio));
   }

   // if checkforexist see if exists
   DWORD i;
   PCMTTSTriPhoneAudio pNew = NULL;
   if (fCheckForExist) {
      PCMTTSTriPhoneAudio *pptp = (PCMTTSTriPhoneAudio*)pl->Get(0);
      for (i = 0; i < pl->Num(); i++) {
         if (pptp[i]->m_dwWord != dwWord)
            continue;
         if ((pptp[i]->m_bPhoneLeft != bPhoneLeft) || (pptp[i]->m_bPhoneRight != bPhoneRight))
            continue;
         if (pptp[i]->m_wWordSylPos != wWordSylPos)
            continue;

         // else found a match
         pNew = pptp[i];
         break;
      } // i
   }

   if (!pNew) {
      // create
      pNew = new CMTTSTriPhoneAudio;
      if (!pNew)
         return FALSE;
      pl->Add (&pNew);
   }

   // set
   pNew->m_wOrigWave = (WORD)dwOrigWave;
   pNew->m_wOrigPhone = (WORD)dwOrigPhone;
   pNew->m_fReviewed = FALSE;
   pNew->m_dwWord = dwWord;
   pNew->m_bPhoneLeft = bPhoneLeft;
   pNew->m_bPhoneRight = bPhoneRight;
   memcpy (pNew->m_abRank, pabRank, sizeof(pNew->m_abRank));
   memcpy (pNew->m_aMMISpecific, paMMI, sizeof(pNew->m_aMMISpecific));
   pNew->m_dwMismatchAccuracy = dwMismatchAccuracy;
   pNew->m_wWordSylPos = wWordSylPos;
   //pNew->m_wFlags = wFlags;
   pNew->SRFEATURESet (this, dwOrigWave, dwFeatureStart, dwFeatureEnd, fMaxEnergyInWave, fMaxEnergyInWaveMod);

   DWORD dwNum = dwFeatureEnd - dwFeatureStart;
   DWORD j;
   for (i = 0; i < TPAPITCHPOINTS; i++) {
      DWORD dwStart = i * dwNum / TPAPITCHPOINTS;
      DWORD dwEnd = ((i+1) * dwNum + TPAPITCHPOINTS-1) / TPAPITCHPOINTS;
      dwStart = min(dwStart, dwNum-1);
      dwEnd = max(dwEnd, dwStart+1);   // always at least one

      pNew->m_afPitchF0Extra[i] = 0.0;
      pNew->m_afPitchF0Fuji[i] = 0.0;
      for (j = dwStart; j < dwEnd; j++) {
         pNew->m_afPitchF0Extra[i] += pTFCE[j].fPitchF0All;
         pNew->m_afPitchF0Fuji[i] += pTFCE[j].fPitchF0Fuji;
      }
      pNew->m_afPitchF0Fuji[i] /= (fp)(dwEnd - dwStart);
      pNew->m_afPitchF0Extra[i] /= (fp)(dwEnd - dwStart) * pNew->m_afPitchF0Fuji[i];
   } // i

   // caculate average energy
   // pNew->Decompress ();
   fp fEnergy = 0;
   // fp fScale = PHONESAMPLENORMALIZED / max(fMaxEnergyInWaveMod, 1.0);
   // PSRFEATURE psr = (PSRFEATURE) pNew->m_pmemSRFEATURE->p;
   DWORD dwNumSRFEATURE = dwFeatureEnd - dwFeatureStart;
   for (i = 0; i < dwNumSRFEATURE; i++) {
      fp fEnergyThis = SRFEATUREEnergy (FALSE, pSRFGot + i);
      // fEnergyThis *= fScale;
      fEnergy += fEnergyThis;
   } // i
   // BUGFIX - scale energy accoding to iAdd
   fEnergy *= (fp)DbToAmplitude(pNew->m_iFeatureAdd) / (fp)DbToAmplitude(0);

   for (i = 0; i < TTSDEMIPHONES; i++)
      pNew->m_afPitchConfidence[i] = pafPitchConfidence[i];
   pNew->m_fAccent = fAccent;

   pNew->m_fEnergyAvg = fEnergy / (fp)dwNumSRFEATURE;
   // pNew->Compress();

   pNew->m_fOrigPitchF0Extra = fOrigPitchF0Extra;
   pNew->m_fOrigPitchF0Fuji = fOrigPitchF0Fuji;
   pNew->m_dwFuncWordGroup = dwFuncWordGroup;
   pNew->m_fPitchDelta = fPitchDelta;
   pNew->m_fPitchBulge = fPitchBulge;
   pNew->m_dwTrimLeft = dwTrimLeft;
   pNew->m_dwTrimRight = dwTrimRight;

   pNew->m_fCenterEnergy = fCenterEnergy;
   pNew->m_fLeftPitchF0Extra = fLeftPitchF0Extra;
   pNew->m_fLeftPitchF0Fuji = fLeftPitchF0Fuji;
   pNew->m_fLeftEnergy = fLeftEnergy;
   pNew->m_dwLeftDuration = dwLeftDuration;

   memcpy (pNew->m_abPhoneContiguous, pabPhoneContigous, sizeof(pNew->m_abPhoneContiguous));

   pNew->CalcInfo (Lexicon());

   return TRUE;
}



/*************************************************************************************
CMTTS::TriPhoneProsSet - Sets a new triphone. If the existing triphone exists, it
is overwritten (assuming flag is set to check for already exists)

inputs
   DWORD             dwPhone - Phoneme to set
   WORD              wWordPos - Word position, 0=middle, 1=start,2=end
   BYTE              bPhoneLeft - Phoneme to the left
   BYTE              bPhoneRight - Phoneme to the right
   WORD              wAvgDuration - Average duration for the unit (in SRFEATURE length)
   short             iPitchF0Extra - How much pitch of unit varies from word (above fuji). 0 = no change,
                     1000 = 1 octave higher, -1000 = 1 octave lower, et.
   short             iPitchF0Fuji - Like iPitchF0Extra, but fuji pitcj
   short             iPitchDelta - Change in pitch over the phoneme, same units as iPitch
   short             iPitchBulge - How much pitch bulges up/down in center, 1000 = 1 octave higher,
                        -1000 = 1 octave lower
   fp                fEnergyAvg - Average energy for the triphone... all phonemes should
                     be normalized to this average energy
returns
   BOOL - TRUE if success
*/
BOOL CMTTS::TriPhoneProsSet (DWORD dwPhone, WORD wWordSylPos, BYTE bPhoneLeft, BYTE bPhoneRight,
                         WORD wAvgDuration, short iPitchF0Extra, short iPitchF0Fuji, short iPitchDelta, short iPitchBulge,
                         fp fEnergyAvg,
                         BOOL fCheckForExist)
{
   // if derived dont allow
   if (m_fIsDerived)
      return FALSE;

   // get the list
   if (!MakeSureEnoughPhone(dwPhone))
      return FALSE;
   PCListFixed pl = m_palPCMTTSTriPhonePros[dwPhone];
   if (!pl) {
      pl = m_palPCMTTSTriPhonePros[dwPhone] = new CListFixed;
      if (!pl)
         return FALSE;
      pl->Init (sizeof(PCMTTSTriPhonePros));
   }

   // if checkforexist see if exists
   DWORD i;
   PCMTTSTriPhonePros pNew = NULL;
   if (fCheckForExist) {
      PCMTTSTriPhonePros *pptp = (PCMTTSTriPhonePros*)pl->Get(0);
      for (i = 0; i < pl->Num(); i++) {
         if ((pptp[i]->m_bPhoneLeft != bPhoneLeft) || (pptp[i]->m_bPhoneRight != bPhoneRight))
            continue;
         if (pptp[i]->m_wWordSylPos != wWordSylPos)
            continue;

         // else found a match
         pNew = pptp[i];
         break;
      } // i
   }

   if (!pNew) {
      // create
      pNew = new CMTTSTriPhonePros;
      if (!pNew)
         return FALSE;
      pl->Add (&pNew);
   }

   // set
   pNew->m_bPhoneLeft = bPhoneLeft;
   pNew->m_bPhoneRight = bPhoneRight;
   pNew->m_wWordSylPos = wWordSylPos;
   pNew->m_wDuration = wAvgDuration;
   pNew->m_iPitchF0Extra = iPitchF0Extra;
   pNew->m_iPitchF0Fuji = iPitchF0Fuji;
   pNew->m_iPitchDelta = iPitchDelta;
   pNew->m_iPitchBulge = iPitchBulge;
   pNew->m_fEnergyAvg = fEnergyAvg;

   pNew->CalcInfo (Lexicon());

   return TRUE;
}


/*************************************************************************************
CMTTS::TriPhoneClearWord - This looks through all the triphones and clears out
any entries specific to a given word. Use this when retraining a word

inputs
   DWORD             dwWord - Word that looking for
returns
   none
*/
void CMTTS::TriPhoneClearWord (DWORD dwWord)
{
   // NOTE: Dont bother with the prosody equivalent

   // if is derived dont allow
   if (m_fIsDerived)
      return;

   DWORD i, dwPhone;
   for (dwPhone = 0; dwPhone < m_dwNumPhone; dwPhone++) {
      PCListFixed pl = m_palPCMTTSTriPhoneAudio[dwPhone];
      if (!pl)
         continue;
      if (!pl->Num())
         continue;

      PCMTTSTriPhoneAudio *pptp = (PCMTTSTriPhoneAudio*)pl->Get(0);
      for (i = pl->Num()-1; i < pl->Num(); i--) {
         if (pptp[i]->m_dwWord == -1)
            continue;

         if (pptp[i]->m_dwWord != dwWord)
            continue;

         // else found a match
         delete pptp[i];
         pl->Remove (i);
      } // i
   } // dwPhone

   // done
}



/*************************************************************************************
CMTTS::SynthUnitCanSkip - Call this to see if the synth unit can skip on the left/right.

inputs
   DWORD                dwDemiPhone - Demiphone that this is for, from 0..TTSDEMIPHONES-1
   PCMTTSTriPhoneAudio   pCenter - Central unit to synthesize. If NULL then silence
   PCMTTSTriPhoneAudio   pLeft - Left unit or NULL (full phone, not just demiphone over)
   PCMTTSTriPhoneAudio   pLeftDemi - Demiphone over
   PCMTTSTriPhoneAudio   pRight - Right unit or null (full phone, not just demiphone over)
                        NOTE: pLeft and pRight must have ->Decompress() called before doing this
   BYTE                 bPhoneCenter - Phoneme in the center
   BYTE                 bPhoneLeft - Phoneme on the left (not just a demiphone over)
   BYTE                 bPhoneRight - Phoneme on the right (not just a demiphone over)
returns
   DWORD - Bit flag with 0x01 if can skip on left, 0x02 if skip on right,
            0x04 if need to use phase on left for blending(before start of this unit), 0x08 use phase on right for blending(at start of this unit)
*/

// BUGFIX - Because blending with SRDETAILEDPHASE, DON'T define ONLYPHASEONNONPLOSIVE
// #define ONLYPHASEONNONPLOSIVE          // if defined then only ask for phase blend on non-plosives

DWORD CMTTS::SynthUnitCanSkip (DWORD dwDemiPhone,
                               PCMTTSTriPhoneAudio pCenter,
                               PCMTTSTriPhoneAudio pLeft, PCMTTSTriPhoneAudio pLeftDemi,
                               PCMTTSTriPhoneAudio pRight,
                               BYTE bPhoneCenter, BYTE bPhoneLeft, BYTE bPhoneRight)
{
#ifdef NOMODS_CANSKIP
   return 0;   // cant skip
#endif

   PCMLexicon pLex = Lexicon ();
   PLEXPHONE plp;
   PLEXENGLISHPHONE ple;

   if (!pCenter)
      return 0;   // cant skip

   // BUGFIX - determine if can skip the left/right amount, to counteract segmentation vagueness
   BOOL fSkipLeft = pCenter->m_dwTrimLeft ? TRUE : FALSE;
   BOOL fBlendPhaseLeft = TRUE, fBlendPhaseRight=TRUE;
   if (!pLeft)
      fSkipLeft = FALSE;
   else if ((pLeft->m_wOrigWave == pCenter->m_wOrigWave) && (pLeft->m_wOrigPhone+1 == pCenter->m_wOrigPhone))
      fSkipLeft = FALSE;
   plp = pLex->PhonemeGetUnsort (bPhoneLeft);
   ple = plp ? MLexiconEnglishPhoneGet(plp->bEnglishPhone) : NULL;
   if (!ple || (ple->dwCategory & PIC_PLOSIVE)) {
      fSkipLeft = FALSE;   // since if no trim then assume it's a plosive
      // BUGFIX - Dont do if (!dwDemiPhone)
      //   fBlendPhase = FALSE; // since can don't blend phase for plosive
   }

   // BUGFIX - need to take demiphone into account for phase
   if (!pLeftDemi)
      fBlendPhaseLeft = fBlendPhaseRight = FALSE;
   else if ((pLeftDemi->m_wOrigWave == pCenter->m_wOrigWave) && (pLeftDemi->m_wOrigPhone+(dwDemiPhone ? 0 : 1) == pCenter->m_wOrigPhone))
      fBlendPhaseLeft = fBlendPhaseRight = FALSE;  // adjacent, in same wave
   BYTE bPhoneLeftDemi = dwDemiPhone ? bPhoneCenter : bPhoneLeft;
   plp = pLex->PhonemeGetUnsort (bPhoneLeftDemi);
   ple = plp ? MLexiconEnglishPhoneGet(plp->bEnglishPhone) : NULL;
#ifdef ONLYPHASEONNONPLOSIVE
   if (!ple || !(ple->dwCategory & PIC_VOICED) )
      fBlendPhaseLeft = FALSE; // dont blend for unvoiced

   // don't blend phase if this one isn't voiced
   if (fBlendPhaseRight) {
      plp = pLex->PhonemeGetUnsort (bPhoneCenter);
      ple = plp ? MLexiconEnglishPhoneGet(plp->bEnglishPhone) : NULL;
      // BUGFIX - Dont do if (!ple || (ple->dwCategory & PIC_PLOSIVE))
      //    fBlendPhase = FALSE;   // since if no trim then assume it's a plosive
      if (!ple || !(ple->dwCategory & PIC_VOICED))
         fBlendPhaseRight = FALSE; // dont blend for unvoiced
   }
#endif

   BOOL fSkipRight = pCenter->m_dwTrimRight ? TRUE : FALSE;
   if (!pRight)
      fSkipRight = FALSE;
   else if ((pRight->m_wOrigWave == pCenter->m_wOrigWave) && (pRight->m_wOrigPhone == pCenter->m_wOrigPhone+1))
      fSkipRight = FALSE;
   plp = pLex->PhonemeGetUnsort (bPhoneRight);
   ple = plp ? MLexiconEnglishPhoneGet(plp->bEnglishPhone) : NULL;
   if (!ple || (ple->dwCategory & PIC_PLOSIVE))
      fSkipRight = FALSE;   // since if no trim then assume it's a plosive

   return (fSkipLeft ? 0x01 : 0) | (fSkipRight ? 0x02 : 0) |
      (fBlendPhaseLeft ? 0x04 : 0) | (fBlendPhaseRight ? 0x08 : 0);
}



/*************************************************************************************
DetermineBlend - Determines how much to blend two units together.

inputs
   BOOL              fFullSRFEATURE - If TRUE, then psrfA and psrfB are full SRFEATURES.
                     If FALSE, they're SRFEATURESMALLs
   PSRFEATURE        psrfA - A feature, one to be modified
   PSRFEATURE        psrdB - B features, holding static
   fp                fScaleA - Amount that A is oringally scaled by
   fp                fScaleB - Amount that B is originally scaled by
   BOOL              fHalfWay - If TRUE, then all values in aiOctaveBend and afOctaveScale
                        will be halved/sqrted, assuming that the inverse transform will
                        be done for B
   int               *pafOctaveBend - Filled with an array of SROCTAVE integers, indicating the number of bins that the
                        psrfA is moved up/down by (per octave).
   fp                *pafOctaveScale - Filled with an array of SROCTAVE scaling, indicating how much
                        each octave is increased/decreased. fScakeA will automatically be incorporated
returns
   none
*/
void DetermineBlend (BOOL fFullSRFEATURE, PSRFEATURE psrfA, PSRFEATURE psrfB, fp fScaleA, fp fScaleB,
                            BOOL fHalfWay, fp *pafOctaveBend, fp *pafOctaveScale)
{
   DWORD i;

   fp afAMain[2][SRDATAPOINTS], afBMain[2][SRDATAPOINTS];
#ifndef SRACCURACY_PSYCHOWEIGHTS_DELTA_000
   fp afADelta[2][SRDATAPOINTS], afBDelta[2][SRDATAPOINTS];
#endif
   DWORD dwSweepMax = (SRPOINTSPEROCTAVE/2);
   DWORD dwSRDataPoints;
   if (fFullSRFEATURE) {
      dwSRDataPoints = SRDATAPOINTS;
      for (i = 0; i < SRDATAPOINTS; i++) {
         afAMain[0][i] = DbToAmplitude (psrfA->acVoiceEnergy[i]) * fScaleA;
         afAMain[1][i] = DbToAmplitude (psrfA->acNoiseEnergy[i]) * fScaleA;
         afBMain[0][i] = DbToAmplitude (psrfB->acVoiceEnergy[i]) * fScaleB;
         afBMain[1][i] = DbToAmplitude (psrfB->acNoiseEnergy[i]) * fScaleB;
      } // i

#ifndef SRACCURACY_PSYCHOWEIGHTS_DELTA_000
      memset (afADelta, 0, sizeof(afADelta));
      memset (afBDelta, 0, sizeof(afBDelta));
#endif
   }
   else {
      dwSweepMax /= SRSMALL;
      dwSRDataPoints = SRDATAPOINTSSMALL;

      PSRFEATURESMALL psrfAs = (PSRFEATURESMALL)psrfA;
      PSRFEATURESMALL psrfBs = (PSRFEATURESMALL)psrfB;

      for (i = 0; i < SRDATAPOINTSSMALL; i++) {
         afAMain[0][i] = DbToAmplitude (psrfAs->acVoiceEnergyMain[i]) * fScaleA;
         afAMain[1][i] = DbToAmplitude (psrfAs->acNoiseEnergyMain[i]) * fScaleA;
         afBMain[0][i] = DbToAmplitude (psrfBs->acVoiceEnergyMain[i]) * fScaleB;
         afBMain[1][i] = DbToAmplitude (psrfBs->acNoiseEnergyMain[i]) * fScaleB;

#ifndef SRACCURACY_PSYCHOWEIGHTS_DELTA_000
         afADelta[0][i] = DbToAmplitude (psrfAs->acVoiceEnergyDelta[i]) * fScaleA;
         afADelta[1][i] = DbToAmplitude (psrfAs->acNoiseEnergyDelta[i]) * fScaleA;
         afBDelta[0][i] = DbToAmplitude (psrfBs->acVoiceEnergyDelta[i]) * fScaleB;
         afBDelta[1][i] = DbToAmplitude (psrfBs->acNoiseEnergyDelta[i]) * fScaleB;
#endif
      } // i
   }

   // figure out values for each octave
   DWORD dwOctave;
#define MAXAMP          12
   for (dwOctave = 0; dwOctave < SROCTAVE; dwOctave++) {
      // do a sweep
      int iSweep;
      BOOL fFound = FALSE;
      fp fBest = 0;
      pafOctaveBend[dwOctave] = 0;  // default to no change
      pafOctaveScale[dwOctave] = fScaleA;
      fp fPow = 0.25 * pow((fp)(dwOctave+1) / (fp)(SROCTAVE), 2);
            // BUGFIX - Scale by (dwOctave+1)/SROCTAVE so bends more at lower
      for (iSweep = -(int)dwSweepMax; iSweep <= (int)dwSweepMax; iSweep++) {
         fp fEnergyA, fEnergyB;
#ifndef SRACCURACY_PSYCHOWEIGHTS_DELTA_000
         fp fCompare = SubSRFEATURECompare (
            dwSRDataPoints,
            afAMain[0], afAMain[1], afADelta[0], afADelta[1], (int)dwOctave * SRPOINTSPEROCTAVE, (int)(dwOctave+1)*SRPOINTSPEROCTAVE,
            afBMain[0], afBMain[1], afBDelta[0], afBDelta[1], (int)dwOctave * SRPOINTSPEROCTAVE + iSweep, (int)(dwOctave+1)*SRPOINTSPEROCTAVE + iSweep,
            &fEnergyA, &fEnergyB);
#else
         fp fCompare = SubSRFEATURECompare (
            dwSRDataPoints,
            afAMain[0], afAMain[1], (int)dwOctave * SRPOINTSPEROCTAVE, (int)(dwOctave+1)*SRPOINTSPEROCTAVE,
            afBMain[0], afBMain[1], (int)dwOctave * SRPOINTSPEROCTAVE + iSweep, (int)(dwOctave+1)*SRPOINTSPEROCTAVE + iSweep,
            &fEnergyA, &fEnergyB);
#endif

         // same-ness affected by energy difference between A and B
         fp fDelta = max(fEnergyA, CLOSE) / max(fEnergyB, CLOSE);
         fp fAmp = log10(fDelta)*20.0;
         fAmp = fabs(fAmp);

         // scale to determine total compre
         // make sure if needed to raise/lower more that 12 dB that 0 score
         // and if at edge of sweep then 0 score
         // but slight score if in center
         fCompare = fCompare * (1.0 - min(fAmp, MAXAMP) / (fp)MAXAMP);
         fCompare += pow(((int)dwSweepMax - abs(iSweep)) / (fp)dwSweepMax, fPow);  // so only roll off at the edges

         // if found match then keep
         if (!fFound || (fCompare > fBest)) {
            fBest = fCompare;
            fFound = TRUE;
            pafOctaveBend[dwOctave] = iSweep;
            pafOctaveScale[dwOctave] = fScaleA / fDelta;

            if (fHalfWay) {
               pafOctaveBend[dwOctave] /= 2.0;
               pafOctaveScale[dwOctave] = sqrt(pafOctaveScale[dwOctave]);
            }
         }
      }
   } // dwOctave

   // done
}

/*************************************************************************************
SRFEATUREBendAndScale - Applied both pitch band and scaling, copying the new
features over

inputs
   BOOL              fFullSRFEATURE - If TRUE, then psrfOrig and psrfMod are full SRFEATURES.
                     If FALSE, they're SRFEATURESMALLs
   PSRFEATURE        psrfOrig - Original
   PSRDETAILEDPHASE  pSDPOrig - Original detailed phase
   fp                *pafOctaveBend - Array of SROCTAVE bend amounts
   fp                *pafOctaveScale - Array of SROCTAVE scale amounts
   PSRFEATURE        psrfMod - Copy the modified information here
   PSRDETAILEDPHASE  pSDPMod - Filled in with modified detailed phase
returns
   none
*/
void SRFEATUREBendAndScale (BOOL fFullSRFEATURE, PSRFEATURE psrfOrig, PSRDETAILEDPHASE pSDPOrig,
                            fp *pafOctaveBend, fp *pafOctaveScale, PSRFEATURE psrfMod, PSRDETAILEDPHASE pSDPMod)
{
   // copy over intiially, since harmonic phase is the same
   if (pSDPOrig && pSDPMod)
      *pSDPMod = *pSDPOrig;

   DWORD dwSRPointPerOctave = SRPOINTSPEROCTAVE;
   DWORD dwSRDataPoints;
   PSRFEATURESMALL psrfOrigs, psrfMods;
   if (fFullSRFEATURE) {
      dwSRDataPoints = SRDATAPOINTS;

#ifdef _DEBUG
      psrfOrigs = psrfMods = NULL; // so dont use
#endif
      // copy over the phase, which doesn't exist in the small version
      memcpy (psrfMod->abPhase, psrfOrig->abPhase, sizeof(psrfOrig->abPhase));
   }
   else {
      dwSRPointPerOctave /= SRSMALL;
      dwSRDataPoints = SRDATAPOINTSSMALL;
      psrfOrigs = (PSRFEATURESMALL) psrfOrig;
      psrfMods = (PSRFEATURESMALL) psrfMod;
#ifdef _DEBUG
      psrfOrig = psrfMod = NULL; // so dont use
#endif
   }

   DWORD i;
   // figure out the locations or the original octaves, and their bends
   fp afOrig[SROCTAVE+1], afNew[SROCTAVE+1], afDeltaNew[SROCTAVE+1], afDeltaScale[SROCTAVE+1];
   for (i = 0; i < SROCTAVE+1; i++) {
      afOrig[i] = (fp)(i * dwSRPointPerOctave) + (fp)(dwSRPointPerOctave/2);
      afNew[i] = afOrig[i] + ((i < SROCTAVE) ? pafOctaveBend[i] : 0);
      if (i)
         afDeltaNew[i-1] = max(afNew[i] - afNew[i-1], CLOSE);
      if (i)
         afDeltaScale[i-1] = pafOctaveScale[i] - pafOctaveScale[i-1];
   }

   // loop over all the points
   fp f, fAlpha, fIndex, fScale;
   DWORD j;
   for (i = 0; i < dwSRDataPoints; i++) {
      // see where it is
      f = (fp)i;
      for (j = 0; j < SROCTAVE+1; j++)
         if (f <= afNew[j])
            break;
      j = min(j, SROCTAVE);   // so not too high

      // figure out the values
      if (!j) {
         // at the very top, above first element
         fAlpha = f / max(afNew[0],CLOSE);
         fIndex = fAlpha * afOrig[0];
         fScale = pafOctaveScale[0];
      }
      else if (j >= SROCTAVE) {
         fAlpha = (f - afNew[SROCTAVE-1]) / afDeltaNew[SROCTAVE-1];
         fIndex = fAlpha * (fp)dwSRPointPerOctave + afOrig[SROCTAVE-1];
         fScale = pafOctaveScale[SROCTAVE-1];
      }
      else {
         j--;
         fAlpha = (f - afNew[j]) / afDeltaNew[j];
         fIndex = fAlpha * (fp)dwSRPointPerOctave + afOrig[j];
         fScale = fAlpha * afDeltaScale[j] + pafOctaveScale[j];
      }


      DWORD dwIndex;
  
      if (!fFullSRFEATURE) {
         // interpolation version, but ends up blurring slightly
         DWORD dwIndex2;
         fp fOneMinus;
         // get value
         fIndex = max(fIndex, 0);
         fIndex = min(fIndex, dwSRDataPoints-1);
         dwIndex = (DWORD)fIndex;
         dwIndex2 = min(dwIndex+1, dwSRDataPoints-1);
         fAlpha = fIndex - (fp)dwIndex;
         fOneMinus = 1.0 - fAlpha;

         // values
         if (fFullSRFEATURE) {
            psrfMod->acVoiceEnergy[i] = AmplitudeToDb(
               (DbToAmplitude (psrfOrig->acVoiceEnergy[dwIndex]) * fOneMinus +
               DbToAmplitude (psrfOrig->acVoiceEnergy[dwIndex2]) * fAlpha) * fScale);
            psrfMod->acNoiseEnergy[i] = AmplitudeToDb(
               (DbToAmplitude (psrfOrig->acNoiseEnergy[dwIndex]) * fOneMinus +
               DbToAmplitude (psrfOrig->acNoiseEnergy[dwIndex2]) * fAlpha) * fScale);
         }
         else {
            psrfMods->acVoiceEnergyMain[i] = AmplitudeToDb(
               (DbToAmplitude (psrfOrigs->acVoiceEnergyMain[dwIndex]) * fOneMinus +
               DbToAmplitude (psrfOrigs->acVoiceEnergyMain[dwIndex2]) * fAlpha) * fScale);
            psrfMods->acNoiseEnergyMain[i] = AmplitudeToDb(
               (DbToAmplitude (psrfOrigs->acNoiseEnergyMain[dwIndex]) * fOneMinus +
               DbToAmplitude (psrfOrigs->acNoiseEnergyMain[dwIndex2]) * fAlpha) * fScale);

#ifndef SRACCURACY_PSYCHOWEIGHTS_DELTA_000
            psrfMods->acVoiceEnergyDelta[i] = AmplitudeToDb(
               (DbToAmplitude (psrfOrigs->acVoiceEnergyDelta[dwIndex]) * fOneMinus +
               DbToAmplitude (psrfOrigs->acVoiceEnergyDelta[dwIndex2]) * fAlpha) * fScale);
            psrfMods->acNoiseEnergyDelta[i] = AmplitudeToDb(
               (DbToAmplitude (psrfOrigs->acNoiseEnergyDelta[dwIndex]) * fOneMinus +
               DbToAmplitude (psrfOrigs->acNoiseEnergyDelta[dwIndex2]) * fAlpha) * fScale);
#endif

         }
      }
      else { // if fFullSRFEATURE
         // non-interpolated. no blurring. fast
         fIndex = floor(fIndex + 0.5);
         fIndex = max(fIndex, 0);
         fIndex = min(fIndex, dwSRDataPoints-1);
         dwIndex = (DWORD)fIndex;
         if (fFullSRFEATURE) {
            psrfMod->acVoiceEnergy[i] = AmplitudeToDb(
               DbToAmplitude (psrfOrig->acVoiceEnergy[dwIndex]) * fScale);
            psrfMod->acNoiseEnergy[i] = AmplitudeToDb(
               DbToAmplitude (psrfOrig->acNoiseEnergy[dwIndex]) * fScale);
         }
         else {
            psrfMods->acVoiceEnergyMain[i] = AmplitudeToDb(
               DbToAmplitude (psrfOrigs->acVoiceEnergyMain[dwIndex]) * fScale);
            psrfMods->acNoiseEnergyMain[i] = AmplitudeToDb(
               DbToAmplitude (psrfOrigs->acNoiseEnergyMain[dwIndex]) * fScale);

#ifndef SRACCURACY_PSYCHOWEIGHTS_DELTA_000
            psrfMods->acVoiceEnergyDelta[i] = AmplitudeToDb(
               DbToAmplitude (psrfOrigs->acVoiceEnergyDelta[dwIndex]) * fScale);
            psrfMods->acNoiseEnergyDelta[i] = AmplitudeToDb(
               DbToAmplitude (psrfOrigs->acNoiseEnergyDelta[dwIndex]) * fScale);
#endif

         }
      }
   } // i

#ifndef NOMODS_TURNOFFPHASEBEND
#if 0 // to test bending
   if (pSDPOrig) for (i = 0; i < SRDATAPOINTSDETAILED; i++) {
      // need a test to make sure this is changing stuff
      if (i < SRPHASENUM)
         pSDPMod->afHarmPhase[i][0] = pSDPMod->afHarmPhase[i][1] = 0.0; // mod on purpose
      fp fAngle = (fp)i / (fp)SRDATAPOINTSDETAILED * 2.0 * PI * 5.0;
      pSDPOrig->afVoicedPhase[i][0] = sin(fAngle);
      pSDPOrig->afVoicedPhase[i][1] = cos(fAngle);
   }
#endif
   // need to bend detailed phase too
   if (pSDPOrig && pSDPMod) for (i = 0; i < SRDATAPOINTSDETAILED; i++) {
      // see where it is
      f = (fp)i / (fp)SRDATAPOINTSDETAILED * (fp)dwSRDataPoints;
      for (j = 0; j < SROCTAVE+1; j++)
         if (f <= afNew[j])
            break;
      j = min(j, SROCTAVE);   // so not too high

      // figure out the values
      if (!j) {
         // at the very top, above first element
         fAlpha = f / max(afNew[0],CLOSE);
         fIndex = fAlpha * afOrig[0];
      }
      else if (j >= SROCTAVE) {
         fAlpha = (f - afNew[SROCTAVE-1]) / afDeltaNew[SROCTAVE-1];
         fIndex = fAlpha * (fp)dwSRPointPerOctave + afOrig[SROCTAVE-1];
      }
      else {
         j--;
         fAlpha = (f - afNew[j]) / afDeltaNew[j];
         fIndex = fAlpha * (fp)dwSRPointPerOctave + afOrig[j];
      }

      // need to scale the index
      fIndex *= (fp)SRDATAPOINTSDETAILED / (fp)dwSRDataPoints;


      DWORD dwIndex;
  

      // non-interpolated. no blurring. fast
      fIndex = floor(fIndex + 0.5);
      fIndex = max(fIndex, 0);
      fIndex = min(fIndex, SRDATAPOINTSDETAILED-1);
      dwIndex = (DWORD)fIndex;
      pSDPMod->afVoicedPhase[i][0] = pSDPOrig->afVoicedPhase[dwIndex][0];
      pSDPMod->afVoicedPhase[i][1] = pSDPOrig->afVoicedPhase[dwIndex][1];

#if 0
      // need a test to make sure this is changing stuff
      if (i < SRPHASENUM)
         pSDPMod->afHarmPhase[i][0] = pSDPMod->afHarmPhase[i][1] = 0.0;
      fp fAngle = (fp)i / (fp)SRDATAPOINTSDETAILED * 2.0 * PI * 5.0;
      pSDPMod->afVoicedPhase[i][0] = sin(fAngle);
      pSDPMod->afVoicedPhase[i][1] = cos(fAngle);
#endif
   } // i, bend phase
#endif
}


/*************************************************************************************
CMTTS::SythUnitCalcFeatureJoins - Calculate the feature joins (without doing any
actual join-error tests) for a unit.

inputs
   DWORD             dwDemiPhone - Demiphone to synthesize, from 0..TTSDEMIPHONES-1
   PCMTTSTriPhoneAudio  pTPA - Triphone
   DWORD             dwSkip - Skip values, from SynthUnitCanSkip()
   DWORD             *pdwLeft - Filled with left feature
   DWORD             *pdwRight - Filled with right feature
   DWORD             *pdwLeftIdeal - Filled with ideal left feature
   DWORD             *pdwRightIdeal - Filled with ideal eight feature
returns
   BOOL - TRUE if succes. FALSE if fails (such as NULL pTPA)
*/
BOOL CMTTS::SynthUnitCalcFeatureJoins (DWORD dwDemiPhone, PCMTTSTriPhoneAudio pTPA, DWORD dwSkip,
                                       DWORD *pdwLeft, DWORD *pdwRight,
                                       DWORD *pdwLeftIdeal, DWORD *pdwRightIdeal)
{
   // if silence then no joins
   if (!pTPA) {
      *pdwLeft = *pdwRight = *pdwLeftIdeal = *pdwRightIdeal = 0;
      return FALSE;
   }

   if (pTPA->m_dwFeatureStart >= pTPA->m_dwFeatureEnd) {
      *pdwLeft = *pdwRight = *pdwLeftIdeal = *pdwRightIdeal = pTPA->m_dwFeatureStart;
      return FALSE;
   }

   DWORD dwLeftIdeal = pTPA->m_dwFeatureStart;
   DWORD dwRightIdeal = pTPA->m_dwFeatureEnd;
   DWORD dwSize = pTPA->m_dwFeatureEnd - pTPA->m_dwFeatureStart;

   // BUGFIX - determine if can skip the left/right amount, to counteract segmentation vagueness
   BOOL fSkipLeft = (dwSkip & 0x01) ? TRUE : FALSE;
   BOOL fSkipRight = (dwSkip & 0x02) ? TRUE : FALSE;

   if (fSkipLeft)
      dwLeftIdeal += pTPA->m_dwTrimLeft;
   if (fSkipRight)
      dwRightIdeal -= pTPA->m_dwTrimRight;

   // which demiphone
   dwDemiPhone = dwDemiPhone % TTSDEMIPHONES;
   DWORD dwLeftToUse = pTPA->m_dwFeatureStart + dwSize * dwDemiPhone / TTSDEMIPHONES;
   DWORD dwRightToUse = pTPA->m_dwFeatureStart + dwSize * (dwDemiPhone+1) / TTSDEMIPHONES;
   // check for 0-length
   if (dwLeftToUse >= dwRightToUse) {
      dwRightToUse = dwLeftToUse + 1;

      // don't go beyond edge
      if (dwRightToUse > pTPA->m_dwFeatureEnd) {
         dwLeftToUse -= (dwRightToUse - pTPA->m_dwFeatureEnd);
         dwRightToUse = pTPA->m_dwFeatureEnd;
      }
   }

   *pdwLeft = dwLeftToUse;
   *pdwRight = dwRightToUse;
   *pdwLeftIdeal = (dwLeftToUse == pTPA->m_dwFeatureStart) ? dwLeftIdeal : dwLeftToUse;
   *pdwRightIdeal = (dwRightToUse == pTPA->m_dwFeatureEnd) ? dwRightIdeal : dwRightToUse;

   return TRUE;
}


/*************************************************************************************
CMTTS::SynthUnit - Synthesize a unit into a wave file.

inputs
   fp                fModDuration - If > 0/0, duration will be modified to exactly match
                     the duration in the original wave. 1.0 = full mods.
   DWORD             dwDemiPhone - Index into phonemes. Runs up to # of phonemes * TTSDEMIPHONES
   PCM3DWave         pWave - Wave to synthesize to. The SRFEATUREs must already
                        be allocated.
   PCListFixed       plSRDETAILEDPHASE - Pointer to list with an Array of SRDETAILEDPHASE, same as pWave->m_dwSRSamples. The same
                        portions of this is filled in as pWave->m_paSRSample. This may be expanded
   PCListFixed       plPitch - Pointer to a list with an Array of fp for pitch, in Hz, for ewach pWave->m_dwSRSamples.
                        This is initially filled in. Will be modified in place according to
                        the fSnapToPitch value. plPitch may be added to or removed

                        NOTE: I think has been changed to that it means the absolutely accurate pitch, and is initially
                        filled in with unknown values

   // fp                fSnapToPitch - How much to snap to the original pitch of the unit.
   //                      Use 0.0 for no snap, 1.0 for full snap
   PCMTTSTriPhoneAudio    pCenter - Central unit to synthesize. If NULL then silence
                        NOTE: pCenter must have pCenter->Deocompress() called before doing this
   PCMTTSTriPhonePros   pCenterPros - Prosody info of center, so know if need to scale energy
   DWORD             dwStart - Start time (in SR samples) to synthesize to. The
                        SR memory must already be allocated
   DWORD             dwEnd - End time in SR samples (exclusive) to synthesize to
   PCMTTSTriPhoneAudio    pLeft - Left unit. NULL if silence
   PCMTTSTriPhonePros   pLeftPros - Prosody info of left, so know if need to scale energy
   PCMTTSTriPhoneAudio    pRight - Right unit. NULL if sielnce
   PCMTTSTriPhonePros   pRightPros - Prosody info of right, so know if need to scale energy
                        NOTE: pLeft and pRight must have ->Decompress() called before doing this
   PTTSVOICEMOD   pVoiceMod - Parameters controlling how the voice is modified by
                  the original. Generally, pass in NULL. Only internal functions
                  will pass in non-null.
   DWORD             dwSkip - Flags from SynthUnitCanSkip().
   DWORD             dwSkipLeft - Skip from the left
   DWORD             dwSkipRight - Skip from the right
   PCMTTSTriPhoneAudio  *ppLastTPAA - Initially fill this pointed-to-value in with NULL. SynthUnit() will update
                     it as necessary.
   PCMTTSTriPhoneAudio  *ppLastTPAB - Initially fill this pointed-to-value in with NULL. SynthUnit() will update
                     it as necessary.
   DWORD             *pdwLastFeatureA - Initially fill this pointed-to-value in with NULL. SynthUnit() will update
                     it as necessary.
   DWORD             *pdwLastFeatureB - Initially fill this pointed-to-value in with NULL. SynthUnit() will update
                     it as necessary.
   DWORD             *pdwLastTime - Initially fill this pointed-to-value in with NULL. SynthUnit() will update
                     it as necessary.returns
   int               iTTSQuality - TTS quality. 0 for fast, 1 for normal, 2 for slow, 3 for best, 4 for multipass
   PSYNTHUNITJOININFO pSUJI - Precalulated join location for this demiphone
   PCListFixed       plPSOLASTRUCT - Has PSOLA information appended. Can be NULL.
   int               *piDurationMod - Filled in with the number of SRSAMPLES that the duration was increased or decreates
returns
   BOOL - TRUE if success
*/
BOOL CMTTS::SynthUnit (fp fModDuration,
                       DWORD dwDemiPhone, PCM3DWave pWave, PCListFixed plSRDETAILEDPHASE,
                       PCListFixed plPitch, // fp fSnapToPitch,
                       PCMTTSTriPhoneAudio pCenter, PCMTTSTriPhonePros pCenterPros,
                       DWORD dwStart, DWORD dwEnd,
                       PCMTTSTriPhoneAudio pLeft, PCMTTSTriPhonePros pLeftPros,
                       PCMTTSTriPhoneAudio pRight, PCMTTSTriPhonePros pRightPros,
                       PTTSVOICEMOD pVoiceMod,
                       DWORD dwSkip, DWORD dwSkipLeft, DWORD dwSkipRight,
                       PCMTTSTriPhoneAudio *ppLastTPAA, PCMTTSTriPhoneAudio *ppLastTPAB,
                       DWORD *pdwLastFeatureA, DWORD *pdwLastFeatureB, DWORD *pdwLastTime,
                       int iTTSQuality, PSYNTHUNITJOININFO pSUJI, PCListFixed plPSOLASTRUCT,
                       int *piDurationMod)
{
   *piDurationMod = 0;

   if ((dwEnd <= dwStart) || (dwEnd > pWave->m_dwSRSamples))
      return FALSE;

   PSRDETAILEDPHASE paSDP = (PSRDETAILEDPHASE) plSRDETAILEDPHASE->Get(0);
   fp *pafPitch = (fp*)plPitch->Get(0);

   DWORD dwDemiPhoneMod = dwDemiPhone % TTSDEMIPHONES;

   // determine how much need to scale center, left, and right
   fp fScaleCenter = 1, fScaleLeft = 1, fScaleRight = 1;
   if (pCenterPros && pCenter) {
      fScaleCenter = pCenterPros->m_fEnergyAvg / TriPhoneEnergy (pCenter);
      fScaleLeft = fScaleRight = fScaleCenter;
   }
   if (!dwDemiPhoneMod && pLeftPros && pLeft)   // BUGFIX only use pLeftPros here if start of demiphone
      fScaleLeft = pLeftPros->m_fEnergyAvg / TriPhoneEnergy (pLeft);
   if ((dwDemiPhoneMod == TTSDEMIPHONES-1) && pRightPros && pRight)  // BUGFIX - onlyuse pRightPros if end of demiphone
      fScaleRight = pRightPros->m_fEnergyAvg / TriPhoneEnergy (pRight);

   // BUGFIX - TTS coming out a bit quiet so make it louder
   fScaleCenter *= TTSOUTPUTVOLUMESCALE;
   fScaleRight *= TTSOUTPUTVOLUMESCALE;
   fScaleLeft *= TTSOUTPUTVOLUMESCALE;
   // BUGFIX

   // if all silence then easy
   DWORD i, j;
   if (!pCenter) {
      for (i = dwStart; i < dwEnd; i++) {
         PSRFEATURE psr = &pWave->m_paSRFeature[i];
         memset (psr,0, sizeof(*psr));

         for (j = 0; j < SRDATAPOINTS; j++)
            psr->acNoiseEnergy[j] = psr->acVoiceEnergy[j] = SRABSOLUTESILENCE;

      } // i

      // clear out phase
      memset (paSDP + dwStart, 0, (dwEnd - dwStart) * sizeof(SRDETAILEDPHASE));

      return TRUE;
   }

   // where are the joins for the left, center, and right?
   DWORD adwFeatureJoin[3][2];   // [0=left,1=center,2=right][0=left,1=right]
   DWORD adwFeatureJoinIdeal[3][2]; // like adwFeatureJoin, but ideal locations
   SynthUnitCalcFeatureJoins (dwDemiPhoneMod + TTSDEMIPHONES - 1 /* so modulo around */, pLeft, dwSkipLeft,
      &adwFeatureJoin[0][0], &adwFeatureJoin[0][1], &adwFeatureJoinIdeal[0][0], &adwFeatureJoinIdeal[0][1]);
   SynthUnitCalcFeatureJoins (dwDemiPhoneMod, pCenter, dwSkip,
      &adwFeatureJoin[1][0], &adwFeatureJoin[1][1], &adwFeatureJoinIdeal[1][0], &adwFeatureJoinIdeal[1][1]);
   SynthUnitCalcFeatureJoins (dwDemiPhoneMod + 1, pRight, dwSkipRight,
      &adwFeatureJoin[2][0], &adwFeatureJoin[2][1], &adwFeatureJoinIdeal[2][0], &adwFeatureJoinIdeal[2][1]);

   // look at history
   if (pLeft && pCenter && (*ppLastTPAA == pLeft) && (*ppLastTPAB == pCenter) && (*pdwLastTime + 1 == dwDemiPhone)) {
      // found a match for previous
      adwFeatureJoin[0][1] = adwFeatureJoinIdeal[0][1] = *pdwLastFeatureA;
      adwFeatureJoin[1][0] = adwFeatureJoinIdeal[1][0] = *pdwLastFeatureB;

#ifdef _DEBUG
      WCHAR szTemp[256];
      swprintf (szTemp, L"\r\nContinuing join from index %g,",
         (double)dwDemiPhone / 2.0 - 0.5);
      OutputDebugStringW (szTemp);
#endif
   }

   // see where the center unit joins up with the right
   if (pCenter && pRight && pSUJI && (pSUJI->dwBestA != (DWORD)-1) && (pSUJI->dwBestB != (DWORD)-1) ) {
      _ASSERTE (pSUJI);
      _ASSERTE (pSUJI->dwBestA != (DWORD)-1);
      _ASSERTE (pSUJI->dwBestB != (DWORD)-1);

#ifdef _DEBUG
      DWORD dwOldA = adwFeatureJoin[1][1];
      DWORD dwOldB = adwFeatureJoin[2][0];
#endif
      DWORD dwBestA, dwBestB;
      fp fJoinScore;

      dwBestA = pSUJI->dwBestA;
      dwBestB = pSUJI->dwBestB;
      fJoinScore = pSUJI->fJoinScore;

#if 0 // old code, not used because precalculated

      int iBestCompareQuality;
      int iJoinCompareQuality;
      if (iTTSQuality <= 0) {
         iBestCompareQuality = 1;
         iJoinCompareQuality = 0;
      }
      else if (    == 1) {
         iBestCompareQuality = 2;
         iJoinCompareQuality = 1;
      }
      else if (iTTSQuality == 2) {
         iBestCompareQuality = 3;
         iJoinCompareQuality = 2;
      }
      else {   // iTTSQUality >= 3
         iBestCompareQuality = 3;
         iJoinCompareQuality = 3;
      }

      // if have center phoneme and right phoneme, then ignore skip (because will get solved eventually)
      // and calculate the right error
      DWORD dwRangeA = (adwFeatureJoin[1][1] - adwFeatureJoin[1][0]) / 2;  // half a demiphone
      DWORD dwRangeB = (adwFeatureJoin[2][1] - adwFeatureJoin[2][0]) / 2; // half a demiphone
      dwRangeA = min(dwRangeA, MAXJOINRANGE);
      dwRangeB = min(dwRangeB, MAXJOINRANGE);
      DWORD dwHalfWindow = JOINHALFWINDOWSIZE;
      fJoinScore = JoinFindBest (&m_acsTTSWave[pCenter->m_wOrigWave % MAXRAYTHREAD], &m_acsTTSWave[pRight->m_wOrigWave % MAXRAYTHREAD],
         pCenter->m_wOrigWave, pRight->m_wOrigWave,
         adwFeatureJoin[1][1], adwFeatureJoin[2][0],
         dwRangeA, dwRangeB,
         adwFeatureJoinIdeal[1][1], adwFeatureJoinIdeal[2][1],
         dwHalfWindow,
         iBestCompareQuality, iBestCompareQuality,
         &dwBestA, &dwBestB);

#if 0 // to test
      dwBestA = adwFeatureJoinIdeal[1][1];
      dwBestB = adwFeatureJoinIdeal[2][0];
#endif
#endif // 0

      // only do if there's a join
      if ((adwFeatureJoin[1][1] != dwBestA) || (adwFeatureJoin[2][0] != dwBestB)) {
         // use these new values, and store them away for the next time that come around
         *pdwLastFeatureA = adwFeatureJoin[1][1] = max(adwFeatureJoin[1][0] + 1, dwBestA);
         *pdwLastFeatureB = adwFeatureJoin[2][0] = min(adwFeatureJoin[2][1] - 1, dwBestB);
         *ppLastTPAA = pCenter;
         *ppLastTPAB = pRight;
         *pdwLastTime = dwDemiPhone;


   #ifdef _DEBUG
         if ((dwOldA != adwFeatureJoin[1][1]) && (dwOldB != adwFeatureJoin[2][0])) {
            WCHAR szTemp[256];
            swprintf (szTemp, L"\r\nIn phoneme index %g, join score = %g",
               (double)dwDemiPhone / 2.0, (double)fJoinScore);
            OutputDebugStringW (szTemp);
            swprintf (szTemp, L"\r\n\tChanged border from wave %d sample %d to sample %d,",
               (int)pCenter->m_wOrigWave, (int)dwOldA, (int)adwFeatureJoin[1][1]);
            OutputDebugStringW (szTemp);
            swprintf (szTemp, L"\r\n\tand border from wave %d sample %d to sample %d,",
               (int)pRight->m_wOrigWave, (int)dwOldB, (int)adwFeatureJoin[2][0]);
            OutputDebugStringW (szTemp);
         }
   #endif
      } // if there's a joing
   }


#if 0 // no loger used
   if (pCenter)
      pCenter->CalcEnergyIfNecessary();

   // figure out how much to scale at beginning/end
   fp fScaleStart = fScaleCenter, fScaleEnd = fScaleCenter;
   fp fAverage;
   if (pLeft) {
      fAverage = (pLeft->m_fEnergyEnd * fScaleLeft + pCenter->m_fEnergyStart * fScaleCenter)/2.0;
      fAverage = max(fAverage, CLOSE);
      fScaleStart = fAverage / max(pCenter->m_fEnergyStart, CLOSE);

      // BUGFIX - if adjacent then return scale start to center
      if ((pLeft->m_wOrigWave == pCenter->m_wOrigWave) && (pLeft->m_wOrigPhone+(dwDemiPhone ? 0 : 1) == pCenter->m_wOrigPhone))
         fScaleStart = fScaleCenter;
   }
   fScaleStart = log10(fScaleStart)*20.0;

   if (pRight) {
      fAverage = (pCenter->m_fEnergyEnd * fScaleCenter + pRight->m_fEnergyStart * fScaleRight)/2.0;
      fAverage = max(fAverage, CLOSE);
      fScaleEnd = fAverage / max(pCenter->m_fEnergyEnd, CLOSE);

      // BUGFIX - if adjacent then return scale start to center
      if ((pRight->m_wOrigWave == pCenter->m_wOrigWave) && (pRight->m_wOrigPhone == pCenter->m_wOrigPhone+((dwDemiPhone == TTSDEMIPHONES-1) ? 1 : 0) ))
         fScaleEnd = fScaleCenter;
   }
   fScaleEnd = log10(fScaleEnd)*20.0;

#define MAXDBCHANGE        3           // BUGFIX - Was 6, but affects volumes of phones too much
   // dont allow to scale too much
   fScaleStart = min(fScaleStart, MAXDBCHANGE);
   fScaleStart = max(fScaleStart, -MAXDBCHANGE);
   fScaleEnd = min(fScaleEnd, MAXDBCHANGE);
   fScaleEnd = max(fScaleEnd, -MAXDBCHANGE);
#endif // 0

   // figure out the features
   DWORD dwNumFeat;
   BOOL fToLeft;
   PTTSFEATURECOMPEXTRA pTFCA;
   CMem memCenter, memLeft, memRight;
   PSRFEATURE pSROrig = pCenter->SRFEATUREGetRange (this, &memCenter,
      adwFeatureJoin[1][0], adwFeatureJoin[1][1], &dwNumFeat, &fToLeft, NULL, &pTFCA);
   if (!pSROrig || !dwNumFeat)
      return FALSE;

   DWORD dwNewEnd = dwEnd;
   DWORD dwOldEnd = dwEnd;
   if (fModDuration) {
      // NOTE: This may mess up transplanted prosody with absolute durations
      dwNewEnd = dwStart + (adwFeatureJoin[1][1] - adwFeatureJoin[1][0]);
      dwNewEnd = (DWORD) floor(fModDuration * (fp)dwNewEnd + (1.0 - fModDuration) * (fp)dwOldEnd + 0.5);
   }
   if (dwNewEnd != dwOldEnd) {
      // if have changed the length of the unit, then need to do a LOT of repair work
      dwEnd = dwNewEnd;
      DWORD dwSamplesOld = pWave->m_dwSamples;
      DWORD dwSRSamplesOld = pWave->m_dwSRSamples;
      DWORD dwPitchSamplesOld = pWave->m_adwPitchSamples[PITCH_F0];

      // wave
#if 0 // no point moving the wave info since will all be overwritten
      if (dwNewEnd < dwOldEnd) {
         // move down
         memmove (
            pWave->m_psWave + pWave->m_dwChannels * pWave->m_dwSRSkip * dwNewEnd,
            pWave->m_psWave + pWave->m_dwChannels * pWave->m_dwSRSkip * dwOldEnd,
            sizeof(short) * pWave->m_dwChannels * max((int)dwSamplesOld - (int)pWave->m_dwSRSkip * (int)dwOldEnd, 0));

         if (pWave->m_paSRFeature)
            memmove (
               pWave->m_paSRFeature + dwNewEnd,
               pWave->m_paSRFeature + dwOldEnd,
               sizeof(SRFEATURE) * max((int)dwSRSamplesOld - (int)dwOldEnd, 0));

         if (pWave->m_apPitch[PITCH_F0])
            memmove (
               pWave->m_apPitch[PITCH_F0] + pWave->m_dwChannels * dwNewEnd,
               pWave->m_apPitch[PITCH_F0] + pWave->m_dwChannels * dwOldEnd,
               sizeof(WVPITCH) * pWave->m_dwChannels * max((int)dwPitchSamplesOld - (int)dwOldEnd, 0));
      }
#endif // 0
      
      // change the memory
      pWave->Allocate ( (DWORD)((int)pWave->m_dwSamples + (int)pWave->m_dwSRSkip * ((int)dwNewEnd - (int)dwOldEnd)) );

      // move up
#if 0 // no point moving the wave info since will all be overwritten
      if (dwNewEnd > dwOldEnd) {
         // move up
         memmove (
            pWave->m_psWave + pWave->m_dwChannels * pWave->m_dwSRSkip * dwNewEnd,
            pWave->m_psWave + pWave->m_dwChannels * pWave->m_dwSRSkip * dwOldEnd,
            sizeof(short) * pWave->m_dwChannels * max((int)dwSamplesOld - (int)pWave->m_dwSRSkip * (int)dwOldEnd, 0));

         if (pWave->m_paSRFeature)
            memmove (
               pWave->m_paSRFeature + dwNewEnd,
               pWave->m_paSRFeature + dwOldEnd,
               sizeof(SRFEATURE) * max((int)dwSRSamplesOld - (int)dwOldEnd, 0));

         if (pWave->m_apPitch[PITCH_F0])
            memmove (
               pWave->m_apPitch[PITCH_F0] + pWave->m_dwChannels * dwNewEnd,
               pWave->m_apPitch[PITCH_F0] + pWave->m_dwChannels * dwOldEnd,
               sizeof(WVPITCH) * pWave->m_dwChannels * max((int)dwPitchSamplesOld - (int)dwOldEnd, 0));
      }
#endif // 0

      // detailed phase
      // don't bother copying stuff that move since nothing in it yet
      if (dwNewEnd > dwOldEnd) {
         SRDETAILEDPHASE sdp;
         memset (&sdp, 0, sizeof(sdp));
         for (i = 0; i < (dwNewEnd - dwOldEnd); i++)
            plSRDETAILEDPHASE->Add (&dwNewEnd);
      }
      else
         plSRDETAILEDPHASE->Truncate (plSRDETAILEDPHASE->Num() - (dwOldEnd - dwNewEnd));  // shrink
      // refresh value
      paSDP = (PSRDETAILEDPHASE) plSRDETAILEDPHASE->Get(0);

      // pitch (
      if (dwNewEnd > dwOldEnd) {
         fp fPitchUnk = pafPitch[dwStart];
         for (i = 0; i < (dwNewEnd - dwOldEnd); i++)
            plPitch->Add (&fPitchUnk);
      }
      else
         plPitch->Truncate (plPitch->Num() - (dwOldEnd - dwNewEnd));  // shrink
      // refresh value
      pafPitch = (fp*) plPitch->Get(0);

      // others
      *piDurationMod = (int)dwNewEnd - (int)dwOldEnd;
   }

   // get psola
   if (plPSOLASTRUCT) {
      PCTTSWave pTTSWave = TTSFindWave (pCenter->m_wOrigWave);
      DWORD dwPrior, dwAfter;
      PTTSFEATURECOMPEXTRA pTFCE;
      short *psWave;
      PSRFEATURE pSRFeature = pTTSWave ? pTTSWave->GetSRFEATURE (
         &m_acsTTSWave[pCenter->m_wOrigWave % MAXRAYTHREAD],
         adwFeatureJoin[1][0], adwFeatureJoin[1][1],
         &dwPrior, &dwAfter, &pTFCE, &psWave) : NULL;
      PCTTSWaveSegment pWaveSeg = pTTSWave ? pTTSWave->Find (adwFeatureJoin[1][0], adwFeatureJoin[1][1]) : NULL;

      if (pSRFeature && psWave && pWaveSeg) {
         // found PSOLA info
         PSOLASTRUCT PS;
         memset (&PS, 0, sizeof(PS));
         PS.dwFeatureStartSrc = dwPrior;
         PS.dwFeatureEndSrc = PS.dwFeatureStartSrc + (adwFeatureJoin[1][1] - adwFeatureJoin[1][0]);

         // make sure if modifying duration that end up being same length
         _ASSERTE ((fModDuration < 1.0) || ((PS.dwFeatureEndSrc - PS.dwFeatureStartSrc) == (dwEnd - dwStart)));

         PS.dwNumSRFEATURE = PS.dwFeatureEndSrc + dwAfter;
         PS.dwSRSkip = pWaveSeg->m_dwSRSkipUp;
         PS.dwSamplesPerSec = pWaveSeg->m_dwSamplesPerSecUp;
         PS.pasWave = psWave - dwPrior * pWaveSeg->m_dwSRSkipUp;
         PS.paTFCE = pTFCE - dwPrior;
         PS.iFeatureStartDest = (int)dwStart;
         PS.iFeatureEndDest = (int)dwEnd;

         plPSOLASTRUCT->Add (&PS);
      }
   }

   // while at it, figure out the features in the left/right contexts too
   DWORD dwNumFeatLeft = 0, dwNumFeatRight = 0;
   PSRFEATURE pSROrigLeft = NULL, pSROrigRight = NULL;
   if (pLeft)
      pSROrigLeft = pLeft->SRFEATUREGetRange (this, &memLeft,
         adwFeatureJoin[0][0], adwFeatureJoin[0][1], &dwNumFeatLeft, NULL, NULL, NULL);
   if (pRight)
      pSROrigRight = pRight->SRFEATUREGetRange (this, &memRight,
         adwFeatureJoin[2][0], adwFeatureJoin[2][1], &dwNumFeatRight, NULL, NULL, NULL);

   // determine how much to blend between left and right
   fp afOctaveBend[3][SROCTAVE];
   fp afOctaveScale[3][SROCTAVE];
   for (i = 0; i < SROCTAVE; i++) {
      afOctaveBend[0][i] = afOctaveBend[1][i] = 0;
      afOctaveScale[0][i] = afOctaveScale[1][i] = fScaleCenter;
   }

#ifndef NOMODS_BLENDUNITS
   // figure amount to blend by
   if (pSROrigLeft && dwNumFeatLeft)
      DetermineBlend (TRUE, pSROrig+0, pSROrigLeft+(dwNumFeatLeft-1), fScaleCenter, fScaleLeft,
         TRUE, afOctaveBend[0], afOctaveScale[0]);
   if (pSROrigRight && dwNumFeatRight)
      DetermineBlend (TRUE, pSROrig+(dwNumFeat-1), pSROrigRight+0, fScaleCenter, fScaleRight,
         TRUE, afOctaveBend[1], afOctaveScale[1]);
#endif


   // limit the scaling
#define MAXSCALE        2.0      // how much can add/remove to energy of formants
   fp fScaleMax = fScaleCenter * MAXSCALE;
   fp fScaleMin = fScaleCenter / MAXSCALE;
   for (j = 0; j < 2; j++) for (i = 0; i < SROCTAVE; i++) {
      afOctaveScale[j][i] = max(afOctaveScale[j][i], fScaleMin);
      afOctaveScale[j][i] = min(afOctaveScale[j][i], fScaleMax);
   }

   // do non-linear scaling, causing areas of the voice with high energy deltas
   // to be kept un-stretched as much as possible
   CMem memStretch;
   DWORD dwNewLength = dwEnd - dwStart;
   if (!memStretch.Required ((dwNumFeat * 2 + dwNewLength) * sizeof(fp)))
      return FALSE;
   fp *pafStretchEnergy = (fp*)memStretch.p;
   fp *pafStretchDelta = pafStretchEnergy + dwNumFeat;
   fp *pafStretchAlpha = pafStretchDelta + dwNumFeat;
   fp fEnergyMax = CLOSE;
   for (i = 0; i < dwNumFeat; i++) {
      pafStretchEnergy[i] = SRFEATUREEnergy (FALSE, pSROrig + i);
      fEnergyMax = max(fEnergyMax, pafStretchEnergy[i]);
   }
   fp fDeltaSum = 0;
   fp fDeltaMax = 0;
   for (i = 0; i < dwNumFeat; i++) {
      fp fDelta = 0;
      if (i)
         fDelta = max(fDelta, fabs(pafStretchEnergy[i-1] - pafStretchEnergy[i]));
      if (i+1 < dwNumFeat)
         fDelta = max(fDelta, fabs(pafStretchEnergy[i+1] - pafStretchEnergy[i]));

      pafStretchDelta[i] = fDelta;
      fDeltaSum += fDelta;
      fDeltaMax = max(fDeltaMax, fDelta);
   } // i
   // normalize deltas
   for (i = 0; i < dwNumFeat; i++)
      if (fDeltaMax) // was fDeltaSum
         pafStretchDelta[i] /= fDeltaMax; // was fDelta Sum
      else
         pafStretchDelta[i] = 0; // since all the same, no change
   // determine weighting
   fDeltaSum = 0;
   fDeltaMax /= fEnergyMax;
   for (i = 0; i < dwNumFeat; i++) {
      if (dwNewLength >= 2)
         pafStretchEnergy[i] = ((fp)dwNumFeat - (fp)dwNewLength) / (fp)(dwNewLength-1);
      else
         pafStretchEnergy[i] = 1;
      pafStretchEnergy[i] = pafStretchEnergy[i] * pafStretchDelta[i] * fDeltaMax + 1.0;
      pafStretchEnergy[i] = max(pafStretchEnergy[i], CLOSE);   // always some

      fDeltaSum += pafStretchEnergy[i];
   }
   // fill in representeed location
   fp fCurAlpha;
   for (i = 0, fCurAlpha = 0; i < dwNumFeat; i++) {
      pafStretchDelta[i] = fCurAlpha / fDeltaSum;  // so this will go from 0..1
      fCurAlpha += pafStretchEnergy[i];
   }
   // fill in the alpha values for the offset
   for (i = 0; i < dwNewLength; i++) {
#ifdef NOMODS_STRETCH
      pafStretchAlpha[i] = (fp)i / (fp)dwNewLength * (fp)dwNumFeat;
#else
      fCurAlpha = (fp)i / (fp)dwNewLength;
      for (j = 1; j < dwNumFeat; j++)
         if (fCurAlpha < pafStretchDelta[j])
            break;

      // write new values
      fp fLow = pafStretchDelta[j-1];
      fp fHigh = (j < dwNumFeat) ? pafStretchDelta[j] : 1.0;
      pafStretchAlpha[i] = (fp)(j-1) + (fCurAlpha - fLow) / (fHigh - fLow);
#endif

      // if modifying duration, no stretch whatsoever, since want exact copy
      if (fModDuration) {
         fp fNoMod = (fp)i / (fp)dwNewLength * (fp)dwNumFeat;
         pafStretchAlpha[i] = (1.0 - fModDuration) * pafStretchAlpha[i] + fModDuration * fNoMod;
         continue;
      }

   } // i

   SRDETAILEDPHASE SDP;
   fp fPitchThis;

   for (i = dwStart; i < dwEnd; i++) {
      // determine the current scaling/bending
      fp fAlpha = (dwEnd-dwStart-1) ? ((fp)(i - dwStart) / (fp)(dwEnd - dwStart - 1)) : 0.5;;
      fp fOneMinus = 1.0 - fAlpha;
      for (j = 0; j < SROCTAVE; j++) {
         afOctaveBend[2][j] = afOctaveBend[0][j] * fOneMinus + afOctaveBend[1][j] * fAlpha;
         afOctaveScale[2][j] = afOctaveScale[0][j] * fOneMinus + afOctaveScale[1][j] * fAlpha;
      } // j

      PSRFEATURE psr = &pWave->m_paSRFeature[i];


      // BUGFIX - Was doing interpolation of features based on alpha, but
      // took out because when interpolate two adjacent features they end
      // up loosing some of their clarity, resulting in over-all lower quality
      // of couse, the method of just skipping will fall apart if speak very
      // slowly (> 2x) but not likely
      DWORD dwOffset;
      //fAlpha = (fp)(i-dwStart) / (fp)(dwEnd-dwStart); // BUFIX - Was dwEnd-dwStart-1
      //dwOffset = (i-dwStart) * dwNumFeat / (dwEnd - dwStart);
      dwOffset = floor(pafStretchAlpha[i-dwStart] + 0.5);
      dwOffset = min(dwOffset, dwNumFeat-1);

      // make sure offset is increasing by one if modifying duration to exact match
      _ASSERTE ((fModDuration < 1.0) || (dwOffset == i - dwStart));

      // extract the phase information from the original
#ifdef NOMODS_INTERPPHASEDETAILED
      SRDETAILEDPHASEFromSRFEATURE (pSROrig + dwOffset, pTFCA[dwOffset].fPitchF0All, &SDP);
      fPitchThis = pTFCA[dwOffset].fPitchF0All;
#else
      SRDETAILEDPHASE SDP2;
      DWORD dwLeft = (DWORD)pafStretchAlpha[i-dwStart];
      DWORD dwRight = min(dwLeft+1, dwNumFeat-1);
      fAlpha =  pafStretchAlpha[i-dwStart] - (fp)dwLeft;
      fOneMinus = 1.0 - fAlpha;

      SRDETAILEDPHASEFromSRFEATURE (pSROrig + dwLeft, pTFCA[dwLeft].fPitch, &SDP);
      SRDETAILEDPHASEFromSRFEATURE (pSROrig + dwRight, pTFCA[dwRight].fPitch, &SDP2);

      fPitchThis = fOneMinus * pTFCA[dwLeft].fPitch + fAlpha * pTFCA[dwRight].fPitch;

      // interpolte
      for (j = 0; j < SRPHASENUM; j++) {
         SDP.afHarmPhase[j][0] = fOneMinus * SDP.afHarmPhase[j][0] + fAlpha * SDP2.afHarmPhase[j][0];
         SDP.afHarmPhase[j][1] = fOneMinus * SDP.afHarmPhase[j][1] + fAlpha * SDP2.afHarmPhase[j][1];
      }
      for (j = 0; j < SRDATAPOINTSDETAILED; j++) {
         SDP.afVoicedPhase[j][0] = fOneMinus * SDP.afVoicedPhase[j][0] + fAlpha * SDP2.afVoicedPhase[j][0];
         SDP.afVoicedPhase[j][1] = fOneMinus * SDP.afVoicedPhase[j][1] + fAlpha * SDP2.afVoicedPhase[j][1];
      }
#endif

#ifdef NOMODS_BLENDUNITS
      memcpy (psr, pSROrig+dwOffset, sizeof(*psr));
      memcpy (paSDP + i, &SDP, sizeof(SDP)); // BUGFIX
#else
      // BUGFIX - Do blend
      SRFEATUREBendAndScale (TRUE, pSROrig + dwOffset, &SDP, afOctaveBend[2], afOctaveScale[2], psr, paSDP + i);

      // copy over the PCM
      psr->bPCMHarmFadeStart = pSROrig[dwOffset].bPCMHarmFadeStart;
      psr->bPCMHarmFadeFull = pSROrig[dwOffset].bPCMHarmFadeFull;
      psr->bPCMHarmNyquist = pSROrig[dwOffset].bPCMHarmNyquist;
      psr->bPCMFill = pSROrig[dwOffset].bPCMFill;
      psr->fPCMScale = pSROrig[dwOffset].fPCMScale;

#ifdef SRFEATUREINCLUDEPCM_SHORT
      memcpy (psr->asPCM, pSROrig[dwOffset].asPCM, sizeof(psr->asPCM));
#else
      memcpy (psr->acPCM, pSROrig[dwOffset].acPCM, sizeof(psr->acPCM));
#endif

#endif

#ifdef NOMODS_DISABLEPCM
      memset (psr->acPCM, 0, sizeof(psr->acPCM));
      psr->bPCMFill = psr->bPCMHarmFadeFull = psr->bPCMHarmFadeStart = psr->bPCMHarmNyquist = 0;
      psr->fPCMScale = 0;
#endif

#ifndef NOMODS_INTERPPHASE
      // BUGFIX - Interpolate the phase... try this since it seems that when
      // stretch/shrink segments, the phase is messed up
      // I can't hear the difference, but it does show up as minor changes
      // on the spectrogram
      fp fOffset;
      fOffset = pafStretchAlpha[i-dwStart]; // fAlpha * (fp)dwNumFeat;
      dwOffset = (DWORD)floor(fOffset);
      fOffset -= (fp)dwOffset;
      if (dwOffset + 1 < dwNumFeat) {
         fp af[2];
         af[0] = 1.0 - fOffset;
         af[1] = fOffset;
         SRFEATUREInterpolatePhase (pSROrig + dwOffset, 2, af, psr);
      }
#endif

#if 0 // old code - take out blurring in time because it ends up blurring the
      // formants and reducing the overall quality of the audio

      // figure out weighting
      // fp fOffset, fAlpha;
      if ((dwNumFeat == 1) || (dwEnd-dwStart == 1))
         fOffset = fAlpha = 0;
      else {
         fAlpha = (fp)(i-dwStart) / (fp)(dwEnd-dwStart-1);
         fOffset = fAlpha * (fp)(dwNumFeat-1);
      }

      // if 0 offset then just copy
      if (fOffset == 0)
         memcpy (psr, pSROrig+0, sizeof(*psr));
      else if (fOffset+1 >= dwNumFeat)
         memcpy (psr, pSROrig+(dwNumFeat-1), sizeof(*psr));
      else {
         int iOffset = (int)floor(fOffset);
         fOffset -= (fp)iOffset;
         SRFEATUREInterpolate (pSROrig + iOffset, pSROrig + (iOffset+1), 1.0 - fOffset, psr);
      }
#endif // 0

#if 0 // no longer need to scale since doing it in SRFEATUREBendAndScale
      // scale by given db
      int iScale = (int)((1.0 - fAlpha) * fScaleStart + fAlpha * fScaleEnd);
      if (iScale) for (j = 0; j < SRDATAPOINTS; j++) {
         int iTemp;

         iTemp = iScale + (int)psr->acVoiceEnergy[j];
         iTemp = max(iTemp, -127);
         iTemp = min(iTemp, 127);
         psr->acVoiceEnergy[j] = (char)iTemp;

         iTemp = iScale + (int)psr->acNoiseEnergy[j];
         iTemp = max(iTemp, -127);
         iTemp = min(iTemp, 127);
         psr->acNoiseEnergy[j] = (char)iTemp;
      } // j
#endif // 0

      if (pafPitch)
         pafPitch[i] = fPitchThis;

      // snap to the pitch
      // BUGFIX - Moved this elsewhere
      //if (fSnapToPitch && pafPitch[i] && fPitchThis) {
      //   fp fOrigPitch = pVoiceMod->pSubVoice->m_fAvgPitch;
      //
      //   // if voice is permanently pitch shifted then take this into account
      //   fPitchThis = fPitchThis / max(fOrigPitch, 1.0) * max(m_fAvgPitch, 1.0);
      //
      //   pafPitch[i] = exp(fSnapToPitch * log(fPitchThis) + (1.0 - fSnapToPitch) * log(pafPitch[i]));
      //}

      // go through and apply the glottal pulse mods
      // BUGFIX - Dont bother calling glottal pulse change since it seems to
      // do very little, plus it's technically not correct since the pitch in the
      // original audio might be sweeping, and it doesnt account for this
      // BUGFIX - Put back in because affects very low voice, and female
      // BUGFIX - remove this since may be mucking up female too much, making sound
      // too nasal, since female's phonemes have pitches all over the place

      // BUGFIX - Put back in because have new and improved code that should hopefully work
#ifndef NOMODS_GLOTTALPULSECHANGE
      GlottalPulseChange (psr, /*pCenter->m_fOrigPitch * (*/pVoiceMod->pSubVoice->m_fAvgPitch /*/ m_fAvgPitch)*/,
        pafPitch[i], // BUGFIX - Was pWave->PitchAtSample (i*pWave->m_dwPitchSkip, 0),
        TRUE);
            // NOTE: Always passing in "voiced" flag, which is technically incorrect,
            // but which shouldnt have much perceptable effect to the final voice
            // since will result in slight unwanted changes to unvoiced values
#endif // 0
   } // i


#if 0 // this doesn't work right... need to blend phases over distance
   // BUGFIX - Make phase a delta of previous phases so that don't get any
   // wierd phasing in voice
   // NOTE: This delta phase may cause problems with plosives
   for (i = 0; i < SRPHASENUM; i++) {
      // what's the previous phase immediately before this first phse
      PSRFEATURE pfThisPrevious = fToLeft ? (pSROrig-1) : NULL;

      // what's the previous phase
      PSRFEATURE pfWavePrevious = dwStart ? &pWave->m_paSRFeature[dwStart-1] : NULL;

      // what's the phase delta
      BYTE bDelta;
      // first see how much should be moving
      if (pfThisPrevious)
         bDelta = pSROrig->abPhase[i] - pfThisPrevious->abPhase[i];  // since have the info
      else if (dwEnd > dwStart)
         bDelta = pWave->m_paSRFeature[dwStart+1].abPhase[i] - pWave->m_paSRFeature[dwStart].abPhase[i];  // make it up, interpolation
      else
         bDelta = 0; // dont know. nothing

      // base it off of previous phase
      bDelta -= (pfWavePrevious ? (pWave->m_paSRFeature[dwStart].abPhase[i] - pfWavePrevious->abPhase[i]) : 0);

      // modify all the phases
      for (j = dwStart; j < dwEnd; j++)
         pWave->m_paSRFeature[j].abPhase[i] += bDelta;
   } // i
#endif // 0

   if (pLeft && pCenter) {
   }
   return TRUE;
}


/*************************************************************************************
CMTTS::PhonemeBackoff - This gets the phoneme and backs off to an alternate
version if it doesn't exist... stressed or unstressed

inputs
   DWORD              bPhone - Phoneme
   PCMLexicon        pLex - Lexicon
   DWORD              bSilence - Silence phoneme
returns
   BYTE bPhone - Phoneme number if finds a backoff
*/
DWORD CMTTS::PhonemeBackoff (DWORD bPhone, PCMLexicon pLex, DWORD bSilence)
{
   if (bPhone == bSilence)
      return bPhone;

   PCListFixed pl = m_palPCMTTSTriPhoneAudio ? m_palPCMTTSTriPhoneAudio[bPhone] : NULL;
   if (pl && pl->Num())
      return bPhone; // ok

   // serious problem because have selected phoneme but no recordsing, therefore
   // try to find an alternate stress...
   DWORD i;
   PLEXPHONE plp = pLex->PhonemeGetUnsort(bPhone);
   DWORD bPhoneTemp = bSilence;
   if (plp && ((DWORD)plp->wPhoneOtherStress < pLex->PhonemeNum()))
      bPhoneTemp = (BYTE)plp->wPhoneOtherStress;
   DWORD dwNum = pLex->PhonemeNum();
   if (bPhoneTemp == bSilence) for (i = 0; i < dwNum; i++) {
      plp = pLex->PhonemeGetUnsort (i);
      if (!plp)
         continue;
      if (plp->wPhoneOtherStress == (WORD)bPhone) {
         bPhoneTemp = i;
         break;
      }
   } // i

   pl = (bPhoneTemp != bSilence) ? (m_palPCMTTSTriPhoneAudio ? m_palPCMTTSTriPhoneAudio[bPhoneTemp] : NULL) : NULL;
   if (pl && pl->Num())
      return bPhoneTemp;
   else
      return bPhone; // original
}



#if 0 // replaced by new code
/*************************************************************************************
ScoreCalcWeightBoundary - Calculates the weight boundary based on the phoneme
length. Longer phonemes weight the SR score more, while short phonemes care
about boundary conditions.

inputs
   DWORD       dwDurationPrev - Duration in SR features for previous (half) unit
   DWORD       dwDuration - Duration in SR features for new (half) unit
   WORD        wPosPrev - Word position for previous, used to determine if plosive (0x10) or voiced (0x20)
   WORD        wPos - Current word position
returns
   DWORD - Weighting, from 0 indicates no effect
*/
fp ScoreCalcWeightBoundary (DWORD dwDurationPrev, DWORD dwDuration, WORD wPosPrev, WORD wPos)
{
   fp fDur = (fp)(dwDurationPrev + dwDuration) / 2.0; // count penalty against half of each
   fDur = max(fDur, 1);
   fDur /= (fp)TTSDEMIPHONES; // since duration of demiphones count

   fDur = SCORECALCWEIGHTBOUNDARY * (fp)SRSAMPLESPERSEC / fDur;

   // if either of these are plosives, then (hack) scale up the boundary weight
   // to encourage plosives sticking next to vowels
   if ((wPosPrev & 0x10) || (wPos & 0x10))
      fDur *= SCORECALCWEIGHTBOUNDARY_SCALEPLOSIVE;
   else if (!(wPosPrev & 0x20) && !(wPos & 0x20))
      fDur *= SCORECALCWEIGHTBOUNDARY_SCALEUNVOICED;

   return fDur;
}
#endif

/*************************************************************************************
ScoreCalcWeightBoundary - Calculates the weight boundary

inputs
   fp             fEnergyPrev - Energy of the previous unit
   fp             fEnergyNew - Energy of the new unit
   PCMTTS         pTTS - 
returns
   fp - Weighting
*/
__inline fp ScoreCalcWeightBoundary (fp fEnergyPrev, fp fEnergyNew, DWORD dwUnits)
{
   // basically, weight is .2 of a second * SRSAMPLESPERSEC, maxed with energy
   return max(fEnergyPrev, fEnergyNew) * JOINHALFWINDOWSIZE(dwUnits);
      // use JOINHALFWINDOWSIZE because really doing a tent-shape of JOINHALFWINDOWSIZE for each triangle,
      // and area under tent is JOINHALFWINDOWSIZE
//   return max(fEnergyPrev, fEnergyNew) * SCORECALCWEIGHTBOUNDARY * SRSAMPLESPERSEC;
}

/*************************************************************************************
ScoreCalcWeightSelf - Calculates the weight of self

inputs
   fp             fEnergy - Energy that this is supposed to be
   DWORD          dwDuration - Duration in SRSAMPLEs
returns
   fp - Weighting
*/
__inline fp ScoreCalcWeightSelf (fp fEnergy, DWORD dwDuration)
{
   return fEnergy * (fp)dwDuration / TTSDEMIPHONES;   // BUGFIX - reduce since several demiphones per
}

#if 0 // no longer used. Was hack attempt, but probably not a good idea
/*************************************************************************************
CMTTS::ScoreCalcContiguous - Score bonus for contiguous units.

inputs
   PCMTTSTriPhoneAudio     ptNew - The Triphone in question
   PBYTE                   pbPhone - Phonemes in the future, [0] is current phoneme
   DWORD                   dwPhoneValid - Number of phonemes valid. Thus, if only[0] is valid then this is 1
returns
   fp - score.
*/

__inline fp CMTTS::ScoreCalcContiguous (PCMTTSTriPhoneAudio ptNew, PBYTE pbPhone, DWORD dwPhoneValid)
{
   //if MAXNOTCONTGIOUSNUM==0 then do nothing
   if (!MAXNOTCONTGIOUSNUM)
      return 0;

   if (dwPhoneValid < 2)
      return HYPCONTIGUOUSUNITS * MAXNOTCONTGIOUSNUM;
   
   dwPhoneValid--;
   pbPhone++;

   DWORD i;
   for (i = 0; (i < NUMPHONECONTIGOUS) && dwPhoneValid; i++, pbPhone++, dwPhoneValid++) {
      if (!ptNew->m_abPhoneContiguous[i])
         break;   // end of data

      // if no match then stop
      if (ptNew->m_abPhoneContiguous[i] != (pbPhone[0]+1))
         break;

      // else, continue
      continue;
   } // i

#if 0 // def _DEBUG
   if (i >= 5)
      pbPhone -= i;
#endif
   
   int iScore = (int)MAXNOTCONTGIOUSNUM - i;
   iScore = max(iScore, 0);
   return (fp) iScore * HYPCONTIGUOUSUNITS;
}
#endif // 0
   
/*************************************************************************************
CMTTS::ScoreCalcBoundary - Calculate effects of the boundary on the score

inputs
   DWORD                   dwTime - Time. Most important is dwTime % TTSDEMIPHONES
   PCMTTSTriPhoneAudio     ptNew - The Triphone in question
   DWORD                   *pdwWord - Word history
   PBYTE                   pbWordPos - Word position flags hisotry
   PBYTE                   pbPhone - Phoneme history
   PBYTE                   pbTPhoneNoStress - Phoneme history, without stresses
   PBYTE                   pbTPhoneGroup - Phoneme group history
   BYTE                    bSilence - Silence phoneme
   DWORD                   *pdwPrevious - Used for the first option, if calling from GenerateUNITOPTION.
                                          NULL otherwise
   PVOID                   pahFrom - Used if called from HypExpand
   PCMTTSTriPhoneAudio     *paPCMTTSTriPhoneAudio - Used by HypExpand, from ph.aPCMTTSTriPhoneAudio
returns
   fp - score
*/
__inline fp CMTTS::ScoreCalcBoundary (DWORD dwTime, PCMTTSTriPhoneAudio ptNew, DWORD *pdwWord, PBYTE pbWordPos,
                                         PBYTE pbPhone, PBYTE pbTPhoneNoStress, PBYTE pbTPhoneGroup, BYTE bSilence,
                                         DWORD *pdwPrevious,
                                         PVOID pahFrom, PCMTTSTriPhoneAudio *paPCMTTSTriPhoneAudio)
{
   fp fScore = 0;
   int iPrevPhone = (ATSTARTOFPHONE ? -1 : 0);

#if 0 // no longer used because hackish code
   // BUGFIX - take syllable start and end into account
   BOOL fWordStart = /* ATSTARTOFPHONE && */ (*pbWordPos & 0x01);
   BOOL fSylStart = /* ATSTARTOFPHONE && */ (*pbWordPos & 0x04);

   fp fScoreLeft = 0;
      

   // less penalty for error at boundary
   fScoreLeft = CROSSWORDLESSPENALTYLEFT(fScoreLeft);


   // BUGFIX - take syllable start and end into account
   BOOL fWordEnd = /*ATENDOFPHONE &&*/ (*pbWordPos & 0x02);
   BOOL fSylEnd = /*ATENDOFPHONE &&*/ (*pbWordPos & 0x08);
   fp fScoreRight = 0;

   // NOTE - not sure about the fNotContiguous
   // was doing this unless exact match of phonemes
   BOOL fNotContiguous = TRUE;
   if (fNotContiguous) {
      fPlosive = (*pbWordPos & 0x10) || (pbWordPos[1] & 0x10);
      fScoreRight += HYPNOTCONTIGUOUS;
   }

   // less penalty for cross word at right
   fScoreRight = CROSSWORDLESSPENALTYRIGHT(fScoreRight);

   // weight the scores depending upon left/right
   fp fWeightRight, fWeightLeft;
   if (TTSDEMIPHONES <= 1)
      fWeightRight = fWeightLeft = 1;
   else {
      fWeightRight = dwTime % TTSDEMIPHONES;
      fWeightLeft = (TTSDEMIPHONES-1) - fWeightRight;
      fWeightRight += TTSDEMIPHONES/2;   // so that some weight given to opposite
      fWeightLeft += TTSDEMIPHONES/2; // so that some weight given to opposite
   }

   fScore = (fScoreLeft * fWeightLeft + fScoreRight * fWeightRight) * 2.0 / (fWeightLeft + fWeightRight);
#endif // 0

   PCMLexicon pLex = Lexicon();

   // BOOL fRightVoiced = pbWordPos[0] & 0x20;
   // BOOL fRightPlosive = pbWordPos[0] & 0x10;
   // BOOL fLeftVoiced = pbWordPos[iPrevPhone] & 0x20;
   // BOOL fLeftPlosive = pbWordPos[iPrevPhone] & 0x10;

   // non-contiguous penalty
   //BOOL fPlosive = (*pbWordPos & 0x10) || (pbWordPos[iPrevPhone] & 0x10);
      // BUGFIX - Look at both this one and previous one
   // BOOL fVoiced = (*pbWordPos & 0x20) && (pbWordPos[iPrevPhone] & 0x20);   // do && since using !fVoiced for HYPNOTCONTIGUOUS
   BOOL fUseScore = FALSE;
   if (ATSTARTOFPHONE) {
      if (pdwPrevious) { // calling from GenerateUNITOPTION
         if (ptNew->m_wOrigPhone != (WORD)-1) {
            DWORD dwIndex = MAKELONG ((WORD)((int)ptNew->m_wOrigPhone+iPrevPhone), ptNew->m_wOrigWave);
            if (*pdwPrevious != dwIndex)
               fUseScore = TRUE;  // match, but not contiguous
#ifdef _DEBUG
            else
               fScore += 0;
#endif
         }
      }
      else { // calling from HypExpand
         PCMTTSTriPhoneAudio pLeft;
         if (pLeft = paPCMTTSTriPhoneAudio[PHONEHISTORY-2-(dwTime%TTSDEMIPHONES)]) {
            if ((pLeft->m_wOrigWave != ptNew->m_wOrigWave) || ((int)pLeft->m_wOrigPhone+1 != (int)ptNew->m_wOrigPhone))
               fUseScore = TRUE;  // match, but not contiguous
         }
         // else, phoneme matches exactly what expected
            // NOTE: Assumes NUMTRIPHONEGROUP == 3
      }
   }
   else {
      // BUGFIX - If already in a unit, make sure that contiguous with existing unit
      if (pdwPrevious) { // calling from GenerateUNITOPTION
         DWORD dwIndex = MAKELONG (ptNew->m_wOrigPhone, ptNew->m_wOrigWave);
         if (*pdwPrevious != dwIndex)
            fUseScore = TRUE;  // match, but not contiguous
#ifdef _DEBUG
            else
               fScore += 0;
#endif
      }
      else { // calling from HypExpand
         if (paPCMTTSTriPhoneAudio[PHONEHISTORY-2] != ptNew)
            fUseScore = TRUE;  // match, but not contiguous
      }
   }

   // BUGFIX - Unit scores include some UnitScoreScoreLRMismatch() as well as UnitScoreScoreNonContiguous
   if (fUseScore)
      fScore += UnitScoreJoinEstimate (this, pbPhone[iPrevPhone], pbPhone[0], !iPrevPhone, pLex) / (fp)JOINHALFWINDOWSIZE(m_dwUnits);
         // BUGFIX - Was doing /(JOINHALFWINDOWSIZE*2), but this was wrong mathematically since UnitScoreJoinEstimate()
         // is already windowed by a tent of 2*JOINHALFWINDOWSIZE, so the average would be divided by
         // the area under the tent = 1/2 * (2*JOINHALFWINDOWSIZE)

   return fScore;
}


/*************************************************************************************
CMTTS::ScoreCalcSelfA and ScoreCalcSelfB - Calculate the score of the unit in relation to itself.
This ignores the units to the left/right.

ScoreCalcSelfA - Independent of exact triphone (tpPrev) that occurred before.
ScoreCalcSelfB - Dependent on exact triphone (tpPrev) that occurred before.

Thus, one can be precalced. Make sure to sume the values together.

inputs
   DWORD                   dwTime - Time. Most important is dwTime % TTSDEMIPHONES
   PCMTTSTriPhoneAudio     ptNew - The Triphone in question
   PCMTTSTriPhoneAudio     ptPrev - Previous triphone (by demitriphone), NULL if silence
   BOOL                    fUsePitch - Set to TRUE if phoneme has a pitch
   int         *paiPitchF0Extra - Array of TPAPITCHPOINTS F0Extra pitch points, log()/log(2) * 1000
                  for the ENTIRE phoneme (not just the demiphone)
   int         *paiPitchF0Fuji - Like paiPitchF0Extra, but fuji
   DWORD                   *pdwWord - Word history
   PBYTE                   pbWordSylPos - Word position flags hisotry. WORDPOS_XXX
   DWORD                   dwDuration - Duration in SRFEATURE units
   fp                      fEnergy - Energy of the unit, compared against the tri-phone's ideal
   fp                      fAccent - Accent value of the unit, from -1 to 1
   DWORD                   dwFuncWordGroup - How much of a function word looking for, 0..NUMFUNCWORDGROUP (inclusive)
   PBYTE                   pbPhone - Phoneme history
   PBYTE                   pbTPhoneNoStress - Phoneme history, without stresses
   PBYTE                   pbTPhoneGroup - Phoneme group history
   BYTE                    bSilence - Silence phoneme
   DWORD                   *pdwPrevious - Used for the first option, if calling from GenerateUNITOPTION.
                                          NULL otherwise
   PVOID                   pahFrom - Used if called from HypExpand
   BOOL                    fDisablePCM - Set to TRUE if PCM is temporarily disabled (because user has
                           selected not to use it)
return
   fp - Scpre
*/


__inline fp CMTTS::ScoreCalcSelfA (DWORD dwTime, PCMTTSTriPhoneAudio ptNew, PCMTTSTriPhoneAudio ptPrev, BOOL fUsePitch,
                               int *paiPitchF0Extra, int *paiPitchF0Fuji,
                               DWORD dwDuration, fp fEnergy, fp fAccent,
                               DWORD dwFuncWordGroup,
                               DWORD *pdwWord, PBYTE pbWordSylPos,
                               PBYTE pbPhone, PBYTE pbTPhoneNoStress, PBYTE pbTPhoneGroup, BYTE bSilence,
                               DWORD *pdwPrevious, PVOID pahFrom,
                               BOOL fDisablePCM)
{
   fp fScoreLeft = 0, fScoreRight = 0;
   BOOL fWordStart = /* ATSTARTOFPHONE && */ (*pbWordSylPos & WORDPOS_WORDSTART);
   BOOL fWordEnd = /*ATENDOFPHONE &&*/ (*pbWordSylPos & WORDPOS_WORDEND);
   //BOOL fPlosive = ((*pbWordPos) & 0x10);
   //BOOL fVoiced = ((*pbWordPos) & 0x20);

   PCMLexicon pLex = Lexicon();

   // left
   BOOL fMismatch = FALSE;
// BUGFIX - disable this #define USEBROADMATCH      // dont use
#ifdef USEBROADMATCH
   DWORD dwBroadMatch = 0;    // number of phonemes (left or right) that match broad category
#endif
   // if it's the start of a word, going from silence is ok
   // BUGFIX - Can only use this trick if the previous one ended in silence
   if (!pdwPrevious && (ptNew->m_bPhoneLeft == bSilence) && fWordStart && (pbPhone[-1] != bSilence) && ((PPHONEHYP)pahFrom)->fEndSilence) {
      // BUGFIX - Changed CROSSWORDLESSPENALTYLEFT (HYPINSERTSILENCE) to HYPINSERTSILENCE
      // since CROSSWORDLESSPENALTYLEFT would always multiply by 2/3
      fScoreLeft += HYPINSERTSILENCE;
      fMismatch = TRUE;
   }
   // if it's the start of a word, going from silence is ok
   // BUGFIX - Can only use this trick if the previous one ended in silence
   // various pentalities for how different the left and right groups are
   else if (LexPhoneGroupToMega(pbTPhoneGroup[-1]) != LexPhoneGroupToMega((BYTE)ptNew->m_awTriPhone[0])) {
      fScoreLeft += UnitScoreScoreLRMismatch (this, pbPhone[0], pbPhone[-1], pLex, FALSE, 5);
      fMismatch = TRUE;
   }
   else if (pbTPhoneGroup[-1] != (BYTE)ptNew->m_awTriPhone[0]) {
      // BUGFIX - Changed CROSSWORDLESSPENALTYLEFT (HYPBADGROUP) to just HYPBADGROUP
      // since was tending to change to bad unit at the start/end of word
      fScoreLeft += UnitScoreScoreLRMismatch (this, pbPhone[0], pbPhone[-1], pLex, FALSE, 4);
      fMismatch = TRUE;
   }
   else if (pbTPhoneNoStress[-1] != (BYTE)ptNew->m_awTriPhone[1]) { // right group, wrong phone
      fScoreLeft += UnitScoreScoreLRMismatch (this, pbPhone[0], pbPhone[-1], pLex, FALSE, 3);
      fMismatch = TRUE;
#ifdef USEBROADMATCH
      dwBroadMatch++;
#endif
   }
   else if (pbPhone[-1] != (BYTE)ptNew->m_awTriPhone[2]) {// right phone, wrong stress
      fMismatch = TRUE;
      fScoreLeft += UnitScoreScoreLRMismatch (this, pbPhone[0], pbPhone[-1], pLex, FALSE, 2);
#ifdef USEBROADMATCH
      dwBroadMatch++;
#endif
   }
#ifdef USEBROADMATCH
   else // perfect triphone match, so no error
      dwBroadMatch++;
#endif


   // if it's the end of a word, going to silence is ok
   // int iNextPhone = (ATENDOFPHONE ? 1 : 0);
   // BOOL fNotContiguous = TRUE;
   if ((ptNew->m_bPhoneRight == bSilence) && fWordEnd && (pbPhone[1] != bSilence)) {
      // BUGFIX - Changed CROSSWORDLESSPENALTYRIGHT (HYPINSERTSILENCE) to HYPINSERTSILENCE
      // since CROSSWORDLESSPENALTYRIGHT would always multiply by 2/3
      fScoreRight += HYPINSERTSILENCE;
      fMismatch = TRUE;
   }
   else if (LexPhoneGroupToMega(pbTPhoneGroup[1]) != LexPhoneGroupToMega((BYTE)(ptNew->m_awTriPhone[0]>>8))) {
      fScoreRight += UnitScoreScoreLRMismatch (this, pbPhone[0], pbPhone[1], pLex, TRUE, 5);
      fMismatch = TRUE;
   }
   else if (pbTPhoneGroup[1] != (BYTE)(ptNew->m_awTriPhone[0]>>8)) {
      // BUGFIX - Changed CROSSWORDLESSPENALTYRIGHT (HYPBADGROUP) to just HYPBADGROUP
      // since was tending to change to bad unit at the start/end of word
      fScoreRight += UnitScoreScoreLRMismatch (this, pbPhone[0], pbPhone[1], pLex, TRUE, 4);
      fMismatch = TRUE;
   }
   else if (pbTPhoneNoStress[1] != (BYTE)(ptNew->m_awTriPhone[1]>>8)) {
      fScoreRight += UnitScoreScoreLRMismatch (this, pbPhone[0], pbPhone[1], pLex, TRUE, 3);
      fMismatch = TRUE;
#ifdef USEBROADMATCH
      dwBroadMatch++;
#endif
   }
   else if (pbPhone[1] != (BYTE)(ptNew->m_awTriPhone[2]>>8)) {
      fScoreRight += UnitScoreScoreLRMismatch (this, pbPhone[0], pbPhone[1], pLex, TRUE, 2);
      fMismatch = TRUE;
#ifdef USEBROADMATCH
      dwBroadMatch++;
#endif
   }
#ifdef USEBROADMATCH
   else // perfect triphone match, so no error
      dwBroadMatch++;
#endif

   // if any of the units mismatched then see if can find exact match in m_aMMISpecific, and use that
   DWORD i;
   DWORD dwDemi = dwTime % TTSDEMIPHONES;
   if (fMismatch) {
      BYTE bLeft, bRight;
      fp fLeftHack, fRightHack;
      switch (ptNew->m_dwMismatchAccuracy) {
         case 0:  // by group
            bLeft = pbTPhoneGroup[-1];
            bRight = pbTPhoneGroup[1];

            // since not really sure how much of a mismatch in stress/group there
            // is, add half
            fLeftHack = UnitScoreScoreLRMismatch (this, pbPhone[0], pbPhone[-1], pLex, FALSE, 3) / 2.0;
            fRightHack = UnitScoreScoreLRMismatch (this, pbPhone[0], pbPhone[1], pLex, TRUE, 3) / 2.0;
            break;
         case 1:  // by stressed
            bLeft = pbTPhoneNoStress[-1];
            bRight = pbTPhoneNoStress[1];

            // since not really sure how much of a mismatch in stress/group there
            // is, add half
            fLeftHack = UnitScoreScoreLRMismatch (this, pbPhone[0], pbPhone[-1], pLex, FALSE, 2) / 2.0;
            fRightHack = UnitScoreScoreLRMismatch (this, pbPhone[0], pbPhone[1], pLex, TRUE, 2) / 2.0;
            break;
         case 2:  // exact
         default:
            bLeft = pbPhone[-1];
            bRight = pbPhone[1];

            fLeftHack = fRightHack = 0;
            break;
      } // switch

      PMISMATCHINFO pmmi = &ptNew->m_aMMISpecific[0];
      for (i = 0; i < TRIPHONESPECIFICMISMATCH; i++, pmmi++)
         if ((pmmi->bLeft == bLeft) && (pmmi->bRight == bRight) && pmmi->abRank) {
            // override the guestimated mismatch with the real thing
            fScoreLeft = fScoreRight = ((fp)pmmi->abRank[dwDemi] - (fp) RANKDBOFFSET) / (fp) RANKDBSCALE / 2.0;
            // BUGBUG - in the future, might look for parial matches of m_aMMISpecific that are still more
            // accurate than the main match, and then offset those. Not worth it at the moment

            fScoreLeft += fLeftHack;
            fScoreRight += fRightHack;
            break;
         }
   }

#if 0 // no longer used
   // if either
   // if it's not the proper word, and haven't incurred any penalty from mismatch,
   // then incorporate bad word mismatch
   BOOL fWordMismatch = ((*pdwWord != -1) && (*pdwWord != ptNew->m_dwWord));
   if (fWordMismatch) {
      fScoreLeft += HYPBADWORD;
      fScoreRight += HYPBADWORD;
   }
#endif


   // if mismatch in phoneme at start/end then add penalty
   // BUGFIX - Was doing all bits in bWordPos, but only want to do word start/end
   fScoreLeft += UnitScoreMismatchedWordPos (this,
      (*pbWordSylPos) & (WORDPOS_WORDSTART | WORDPOS_SYLSTART),
      ptNew->m_wWordSylPos & (WORDPOS_WORDSTART | WORDPOS_SYLSTART));
   fScoreRight += UnitScoreMismatchedWordPos (this,
      (*pbWordSylPos) & (WORDPOS_WORDEND | WORDPOS_SYLEND),
      ptNew->m_wWordSylPos & (WORDPOS_WORDEND | WORDPOS_SYLEND));

   // weight the scores depending upon left/right
   fp fWeightRight, fWeightLeft;
   if (TTSDEMIPHONES <= 1)
      fWeightRight = fWeightLeft = 1;
   else {
      fWeightRight = dwDemi;
      fWeightLeft = (TTSDEMIPHONES-1) - fWeightRight;
      fWeightRight += TTSDEMIPHONES/2;   // so that some weight given to opposite
      fWeightLeft += TTSDEMIPHONES/2; // so that some weight given to opposite
   }

   fp fScore = (fScoreLeft * fWeightLeft + fScoreRight * fWeightRight) * 2.0 / (fWeightLeft + fWeightRight);


   // error for pitch not being good match
   fp fErrorPitch;
   BOOL fFullPCM = fDisablePCM ? FALSE : m_fFullPCM;
#ifdef NOMODS_DISABLEPCM
      fFullPCM = FALSE;
#endif

   // BUGFIX - Phoneme always has a pitch: if (fUsePitch) {
   // BUGFIX - more accurate pitch calculations for phoneme
   fp afPhonemePitchF0ExtraDiff[TPAPITCHPOINTS], afPhonemePitchF0FujiDiff[TPAPITCHPOINTS];
   // NOTE: When comparing F0Extra, sum with fujis, especially important for psola
   DWORD dwPPStart = dwDemi * TPAPITCHPOINTS / TTSDEMIPHONES;
   DWORD dwPPEnd = ((dwDemi+1) * TPAPITCHPOINTS + TTSDEMIPHONES-1) / TTSDEMIPHONES;
   dwPPStart = min(dwPPStart, TPAPITCHPOINTS-2);
   dwPPEnd = max(dwPPEnd, dwPPStart+2);
   dwPPEnd = min(dwPPEnd, TPAPITCHPOINTS);
   for (i = dwPPStart; i < dwPPEnd; i++) {
      afPhonemePitchF0ExtraDiff[i] = ptNew->m_aiPitchF0Extra[i] + ptNew->m_aiPitchF0Fuji[i] -
         paiPitchF0Extra[i] - paiPitchF0Fuji[i];

      // BUGFIX - When doing the difference due to fuji, ACTUALLy do the negative different for pitch
      // extra, since already including fuji in afPhonemePitchF0ExtraDiff
      // afPhonemePitchF0FujiDiff[i] = ptNew->m_aiPitchF0Fuji[i] - paiPitchF0Fuji[i]; // assume fuji doesn't change much
      afPhonemePitchF0FujiDiff[i] = paiPitchF0Extra[i] - ptNew->m_aiPitchF0Extra[i]; // assume fuji doesn't change much
   } // i
   fp fPPScale = 1.0 / (fp)(dwPPEnd - dwPPStart - 1);
   fp fScorePitch = 0.0;
   for (i = dwPPStart; i+1 < dwPPEnd; i++) {
      DWORD dwLeft = i;
      DWORD dwRight = i+1;

      // difference in the average
      fp fPitchDiff = (afPhonemePitchF0ExtraDiff[dwLeft] + afPhonemePitchF0ExtraDiff[dwRight]) / 2.0;
      fErrorPitch = abs(fPitchDiff);
      fErrorPitch = min(fErrorPitch, 1500); // never more than 1.5 octaves, so not too much worse
      fErrorPitch *= UnitScorePitch(this, pbPhone[0], pLex, fPitchDiff >= 0, fFullPCM,
         ptNew->m_afPitchConfidence[dwDemi]) / 1000.0;
      fErrorPitch = max (0, fErrorPitch - HYPPITCHPENALTYPEROCTAVEFORGIVE);   // forgive if close
      fScorePitch += fErrorPitch * fPPScale;

      // difference in slope
      fPitchDiff = afPhonemePitchF0ExtraDiff[dwLeft] - afPhonemePitchF0ExtraDiff[dwRight];
      fErrorPitch = abs(fPitchDiff);
      fErrorPitch = min(fErrorPitch, 1500); // never more than 1.5 octaves, so not too much worse
      fErrorPitch *= UnitScorePitch(this, pbPhone[0], pLex, fPitchDiff >= 0, fFullPCM,
         ptNew->m_afPitchConfidence[dwDemi]) / 1000.0;
      fErrorPitch = max (0, fErrorPitch - HYPPITCHPENALTYPEROCTAVEFORGIVE);   // forgive if close
      fScorePitch += fErrorPitch * fPPScale;

      // differnce in fuji
      fPitchDiff = (afPhonemePitchF0FujiDiff[dwLeft] + afPhonemePitchF0FujiDiff[dwRight]) / 2.0;
      fErrorPitch = abs(fPitchDiff);
      fErrorPitch = min(fErrorPitch, 1500); // never more than 1.5 octaves, so not too much worse
      fErrorPitch *= UnitScorePitch(this, pbPhone[0], pLex, fPitchDiff >= 0, FALSE,
         ptNew->m_afPitchConfidence[dwDemi]) / 1000.0;
            // NOTE: When testing fujisaki pitch, always FALSE for full pcm
      fErrorPitch = max (0, fErrorPitch - HYPPITCHPENALTYPEROCTAVEFORGIVE);   // forgive if close
      fScorePitch += fErrorPitch * fPPScale;
   }

#if 0 // old way of calculating
   // loop over 3 pitch points in demiphone
   for (i = 0; i < 3; i++) {
      fp fAlpha = ((fp)i / 2.0 + (fp)dwDemi) / (fp)TTSDEMIPHONES * 2.0;
      DWORD dwLeft = (DWORD)fAlpha;
      fAlpha -= (fp)dwLeft;
      DWORD dwRight = min(dwLeft+1, 3 - 1);

      fp fPitchDiff = (1.0 - fAlpha) * afPhonemePitchDiff[dwLeft] + fAlpha * afPhonemePitchDiff[dwRight];

      fErrorPitch = abs(fPitchDiff);
      fErrorPitch = min(fErrorPitch, 1500); // never more than 1.5 octaves, so not too much worse
      fErrorPitch *= UnitScorePitch(this, pbPhone[0], pLex, fPitchDiff >= 0, fFullPCM) / 1000.0;
      fErrorPitch = max (0, fErrorPitch - HYPPITCHPENALTYPEROCTAVEFORGIVE);   // forgive if close
      fScore += fErrorPitch / 3.0;
   }
#endif

   //fErrorPitch = abs(ptNew->m_iPitchLeft - iPitchLeft);
   //fErrorPitch = min(fErrorPitch, 1500);  // never more than 1.5 octaves, so not too much worse
   //fErrorPitch = fErrorPitch * UnitScorePitch(pbPhone[0], pLex, iPitchLeft > ptNew->m_iPitchLeft, fFullPCM) / 1000.0;
   //fErrorPitch = max (0, fErrorPitch - HYPPITCHPENALTYPEROCTAVEFORGIVE);   // forgive if close
   //fScoreLeft += fErrorPitch * 0.66666;
      // BUGFIX - Multiply by 0.6666 sinc also including center at 0.33333

   //fErrorPitch = abs(ptNew->m_iPitchRight - iPitchRight);
   //fErrorPitch = min(fErrorPitch, 1500);  // never more than 1.5 octaves, so not too much worse
   //fErrorPitch = fErrorPitch * UnitScorePitch(pbPhone[0], pLex, iPitchRight > ptNew->m_iPitchRight, fFullPCM) / 1000.0;
   //fErrorPitch = max (0, fErrorPitch - HYPPITCHPENALTYPEROCTAVEFORGIVE);   // forgive if close
   //fScoreRight += fErrorPitch * 0.66666;
      // BUGFIX - Multiply by 0.6666 sinc also including center at 0.33333

   // BUFIX - Pitch at center too
   //fErrorPitch = abs(ptNew->m_iPitchCenter - iPitchCenter);
   //fErrorPitch = min(fErrorPitch, 1500);  // never more than 1.5 octaves, so not too much worse
   //fErrorPitch = fErrorPitch * UnitScorePitch(pbPhone[0], pLex, iPitchCenter > ptNew->m_iPitchCenter, fFullPCM) / 1000.0;
   //fErrorPitch = max (0, fErrorPitch - HYPPITCHPENALTYPEROCTAVEFORGIVE);   // forgive if close
   //fScoreLeft += fErrorPitch * 0.33333;
   //fScoreRight += fErrorPitch * 0.33333;
   // BUGFIX - Phoneme always has a pitch}


   // add basic phone rank
   fScore += ((fp) ptNew->m_abRank[dwDemi] - (fp) RANKDBOFFSET) / (fp) RANKDBSCALE;
      // BUGFIX - Add RANKDBOFFSET so can include some negative scores

   // error for duration
   fp fScoreDuration = 0.0;
   if (dwDuration && (ptNew->m_dwFeatureEnd - ptNew->m_dwFeatureStart)) { // BUGFIX - Add ptNew->m_dwNumSRFEATURE to prevent divide by zero
      fp fRelDur = log((fp)dwDuration / (fp)(ptNew->m_dwFeatureEnd - ptNew->m_dwFeatureStart)) / log(2.0);
      fScoreDuration += fabs(fRelDur) * UnitScoreDuration (this, pbPhone[0], pLex, fRelDur >= 0.0, fFullPCM,
         ptNew->m_afPitchConfidence[dwDemi]);
      //DWORD dwDurWant = dwDuration; // BUGFIX - Pick duration with same length: fPlosive ? dwDuration : (dwDuration * 3 / 2);
         // BUGFIX - Try to pick units that are 50% longer than
         // the real thing since sounds better to shorten a long unit than lengthen a short one
         // NOTE: There is an equivalent calcualation in CTTSWork AnalysisTriPhone
      //DWORD dwDur = max(ptNew->m_dwNumSRFEATURE, dwDurWant) * 16 / min(ptNew->m_dwNumSRFEATURE, dwDurWant);
      //dwDur -= 16;   // so smame duration goes to 0
      
      // BUGFIX - Allow to be longer by a bit
      //if (ptNew->m_dwNumSRFEATURE > dwDurWant) {
      //   if (dwDur > HYPDURATIONLONGERLESS(16))
      //      dwDur -= HYPDURATIONLONGERLESS(16);
      //   else
      //      dwDur = 0;  // no penality is slightly longer
      //}
      //fScore += dwDur *
      //   (fPlosive ? HYPDURATIONPENALTYPLOSIVE(HYPSCOREDURDELTA) :
      //      (fVoiced ? HYPSCOREDURDELTA : HYPDURATIONPENALTYUNVOICED(HYPSCOREDURDELTA)  )) / 16.0;
   }

   // energy
   fp fScoreEnergy = 0.0;
   if (fEnergy && ptNew->m_fEnergyAvg) {
      fp fRelEnergy = log(fEnergy / ptNew->m_fEnergyAvg) / log(2.0);
      fScoreEnergy += fabs(fRelEnergy) * UnitScoreEnergy (this, pbPhone[0], pLex, fRelEnergy >= 0);
   }

   fp fRelAccent = fAccent - ptNew->m_fAccent;
   fScore += fabs(fRelAccent) * UnitScoreAccent (this, pbPhone[0], pLex, fRelAccent >= 0);
      // NOTE: Accent already has UnitScoreCombined() removed, so don't need to include

#ifdef USEBROADMATCH
   // BUGFIX - If neither left or right matches any of the broad phoneme category
   // then make a really bad score. Want at least one of them to match
   if (!dwBroadMatch)
      fScore += MAXBORDERERROR;
#endif

   fp fScoreFunc = 0.0;
#ifndef NOMODS_DISABLEFUNCWORDGROUP
   fScoreFunc += UnitScoreFunc (this, pbPhone[0], pLex, ptNew->m_dwFuncWordGroup, dwFuncWordGroup);
#endif

   fScore += UnitScoreCombine (fScorePitch, fScoreDuration, fScoreEnergy, fScoreFunc);

   return fScore;
}

__inline fp CMTTS::ScoreCalcSelfB (DWORD dwTime, PCMTTSTriPhoneAudio ptNew, PCMTTSTriPhoneAudio ptPrev, BOOL fUsePitch,
                               int *paiPitchF0Extra, int *paiPitchF0Fuji,
                               DWORD dwDuration, fp fEnergy, fp fAccent,
                               DWORD dwFuncWordGroup,
                               DWORD *pdwWord, PBYTE pbWordSylPos,
                               PBYTE pbPhone, PBYTE pbTPhoneNoStress, PBYTE pbTPhoneGroup, BYTE bSilence,
                               DWORD *pdwPrevious, PVOID pahFrom,
                               BOOL fDisablePCM)
{
   fp fScore = 0;
   fp fErrorPitch;
   BOOL fFullPCM = fDisablePCM ? FALSE : m_fFullPCM;
#ifdef NOMODS_DISABLEPCM
   fFullPCM = FALSE;
#endif


   PCMLexicon pLex = Lexicon();

#if 0 // Wrong way to do it. Moved UnitScoreScoreLRMismatch to ScoreCalcBoundary
   //BOOL fPlosive = ((*pbWordPos) & 0x10);
   //BOOL fVoiced = ((*pbWordPos) & 0x20);

   // determine if contiguous triphones
   BOOL fContiguousTriphones = FALSE;
   if (ptPrev) {
      if (dwTime % TTSDEMIPHONES) {
         if (ptPrev == ptNew)
            fContiguousTriphones = TRUE;  // not on the first demiphone, and they're a match, so contiguous
         else
            fContiguousTriphones = FALSE; // not a match, so not contiguous
      }
      else {
         if ((ptPrev->m_wOrigWave == ptNew->m_wOrigWave) && (ptPrev->m_wOrigPhone+1 == ptNew->m_wOrigPhone))
            fContiguousTriphones = TRUE;  // crossing phonemes, but sequential
         else
            fContiguousTriphones = FALSE; // crossing phonemes and not sequential
      }
   }
   else {
      // no prior triphone, so is silence. As long as this triphone starts
      // with silence, then mark as contiguous
      if (ptNew->m_bPhoneLeft == bSilence)
         fContiguousTriphones = TRUE; // supposed to start silence, so contigous
      else
         fContiguousTriphones = FALSE; // not supposed to start silence, so assume not contiguous
   }

   if (!fContiguousTriphones) // BUGFIX - Was fContiguousTriphones
      fScore += (
         UnitScoreScoreLRMismatch (pbPhone[0], pbPhone[((dwTime+1)%TTSDEMIPHONES) ? 1 : 0], pLex, FALSE, 1) +
         UnitScoreScoreLRMismatch (pbPhone[0], pbPhone[(dwTime%TTSDEMIPHONES) ? 0 : -1], pLex, TRUE, 1)
         ) / 2.0 * TTSDEMIPHONES;
#endif // 0



#ifndef NOMODS_DISABLEUNITCONTINUITY
   // NOTE: I tried to get rid of this, but sounds better with it on, even though it's
   // a bit of a hack
   if (ptPrev && ((ptPrev != ptNew) || !(dwTime % TTSDEMIPHONES)) ) {
         // BUGFIX - Wasn't checking for valid ptPrev, which meant that would be comparing against 0
         // energy and pitch, with eroneous values
      // use above test, because will have 0 difference it (ptPrev == pNew) && (dwTime % TTSEMIPHONES)

      // BUGFIX - continuity so that will try to pick sequential units that
      // if not actually contiguous, will be reasonably close
      float fLeftEnergy;
      float fLeftPitchF0Extra, fLeftPitchF0Fuji;
      DWORD dwLeftDuration;
      // PBYTE pbLeftWordSylPos = (dwTime % TTSDEMIPHONES) ? pbWordSylPos : (pbWordSylPos - 1);   // whatever was to left
      // want whatever the left is
      if (ptPrev) {
         fLeftEnergy = ptPrev->m_fCenterEnergy;
         fLeftPitchF0Extra = ptPrev->m_afPitchF0Extra[(TPAPITCHPOINTS-1)/2];
         fLeftPitchF0Fuji = ptPrev->m_afPitchF0Fuji[(TPAPITCHPOINTS-1)/2];
         dwLeftDuration = ptPrev->m_dwFeatureEnd - ptPrev->m_dwFeatureStart;
      }
      else {
         // silence
         fLeftEnergy = fLeftPitchF0Extra = fLeftPitchF0Fuji = 0;
         dwLeftDuration = 0;
      }

      float fLeftEnergyWant, fLeftPitchF0ExtraWant, fLeftPitchF0FujiWant;
      DWORD dwLeftDurationWant;
      if (dwTime % TTSDEMIPHONES) {
         // part way through phoneme, so what want for previous = this
         fLeftEnergyWant = ptNew->m_fCenterEnergy;
         fLeftPitchF0ExtraWant = ptNew->m_afPitchF0Extra[(TPAPITCHPOINTS-1)/2];
         fLeftPitchF0FujiWant = ptNew->m_afPitchF0Fuji[(TPAPITCHPOINTS-1)/2];
         dwLeftDurationWant = ptNew->m_dwFeatureEnd - ptNew->m_dwFeatureStart;
      }
      else {
         fLeftEnergyWant = ptNew->m_fLeftEnergy;
         fLeftPitchF0ExtraWant = ptNew->m_fLeftPitchF0Extra;
         fLeftPitchF0FujiWant = ptNew->m_fOrigPitchF0Fuji;
         dwLeftDurationWant = ptNew->m_dwLeftDuration;
      }

      // BOOL fLeftPlosive = ((*pbLeftWordPos) & 0x10);
      // BOOL fLeftVoiced = ((*pbLeftWordPos) & 0x20);

      fp fScorePitch = 0.0;
      if (fUsePitch) {
         // calculate pitch
         fErrorPitch = log(max(fLeftPitchF0Extra * fLeftPitchF0Fuji,CLOSE) /
            max(fLeftPitchF0ExtraWant * fLeftPitchF0FujiWant, CLOSE)) / log(2.0);
         fErrorPitch = max(fErrorPitch, -2.0); // dont allow too much
         fErrorPitch = min(fErrorPitch, 2.0);
         fScorePitch += fabs(fErrorPitch) * UnitScorePitch (this, pbPhone[-1], pLex, fErrorPitch > 0, FALSE, // BUGFIX - was passing in: fFullPCM,
            -1.0);

         // NOTE: Not worrying about slope of pitch here. Don't think will be much of an issue

         // fuji pitch, don't use fullpcm flag
         // BUGFIX - As did with other compare, not really comparing fuji. Comparing how
         // much extra is above fuji, inverted
         //fErrorPitch = log(max(fLeftPitchF0Fuji,CLOSE) /
         //   max(fLeftPitchF0FujiWant, CLOSE)) / log(2.0);
         fErrorPitch = log(max(fLeftPitchF0ExtraWant,CLOSE) /
            max(fLeftPitchF0Extra, CLOSE)) / log(2.0);
         fErrorPitch = max(fErrorPitch, -2.0); // dont allow too much
         fErrorPitch = min(fErrorPitch, 2.0);
         fScorePitch += fabs(fErrorPitch) * UnitScorePitch (this, pbPhone[-1], pLex, fErrorPitch > 0, FALSE,
            -1.0);
      }

      // calculate energy
      fp fScoreEnergy = 0.0;
      fErrorPitch = log(max(fLeftEnergy,CLOSE) / max(fLeftEnergyWant, CLOSE)) / log(2.0);
      fErrorPitch = max(fErrorPitch, -2.0); // dont allow too much
      fErrorPitch = min(fErrorPitch, 2.0);
      fScoreEnergy += fabs(fErrorPitch) * UnitScoreEnergy (this, pbPhone[-1], pLex, fErrorPitch > 0);
      
      // calculate duration
      fp fScoreDuration = 0.0;
      fErrorPitch = log((fp) max(dwLeftDuration,1) / (fp)max(dwLeftDurationWant, 1)) / log(2.0);
      fErrorPitch = max(fErrorPitch, -2.0); // dont allow too much
      fErrorPitch = min(fErrorPitch, 2.0);
      fScoreDuration += fabs(fErrorPitch) * UnitScoreDuration (this, pbPhone[-1], pLex, fErrorPitch > 0, FALSE, // BUGFIX - Was passing in fFullPCM,
         -1.0);

      // include error discontinuity in the score
      fScore += UnitScoreCombine (fScorePitch, fScoreDuration, fScoreEnergy, 0.0) * SCORECALCDISCONTINUITYSCALE;
   } // if ptPrev != ptNew
#endif // NOMODS_DISABLEUNITCONTINUITY

   return fScore;
}

// EMTCERRORCACHECALC
typedef struct {
   // on all EMTCxxx
   DWORD          dwStart;       // start count
   DWORD          dwEnd;         // end count
   DWORD          dwType;        // type

   // used by both prelim score calc and boundary error calc
   DWORD          dwTime;

   // used by prelime score calc
   PCMTTSTriPhoneAudio *pptp;
   PCListFixed    plUNITOPTION;
   fp             fEnergy;
   fp             fAccent;
   DWORD          dwDurationX;
   DWORD          dwFuncWordGroup;
   fp             fEnergyPrev;
   BOOL           fUsePitch;
   int            *paiPitchF0Extra;
   int            *paiPitchF0Fuji;
   DWORD          *pdwWord;
   PBYTE          pbWordSylPos;
   PBYTE          pbPhone;
   PBYTE          pbTPhoneNoStress;
   PBYTE          pbTPhoneGroup;
   BYTE           bSilence;
   PCMem          pmemFrom;
   DWORD          dwPass;        // 0 for pass based on guestimates, 1 for pass based on previous scores

   // used by boundary error calc
   PUNITOPTION    pUnit;
   PCListFixed    plUNITOPTIONLast;
   fp             *paf;
   BOOL           *pafLastUsed;
   BOOL           *pafIsValidEnergy;
   fp             *pafLastEnergy;
   int            iTTSQuality;
   BOOL           fDisablePCM;
} EMTCERRORCACHECALC, *PEMTCERRORCACHECALC;

// EMTBEAMSEARCH
typedef struct {
   // on all EMTCxxx
   DWORD          dwStart;       // start count
   DWORD          dwEnd;         // end count
   DWORD          dwType;        // type

   PCListFixed    plUNITOPTION;
   PCMem          pmemFrom;
   PCMem          pmemTo;
   DWORD          dwTime;
   DWORD          *pdwWord;
   PBYTE          pbWordSylPos;
   PBYTE          pbPhone;
   PBYTE          pbTPhoneNoStress;
   PBYTE          pbTPhoneGroup;
   BYTE           bSilence;
   fp             *pafSmooth;
   PCListFixed    plUNITOPTIONLast;
   fp             fEnergy;
   fp             fAccent;
   DWORD          dwDurationX;
   DWORD          dwFuncWordGroup;
   fp             fEnergyPrev;
   BOOL           fUsePitch;
   int            *paiPitchF0Extra;
   int            *paiPitchF0Fuji;
   fp             fEnergyAverage;
   DWORD          dwNum;
   double         *pfCutOff;
   DWORD          *padwModulo;
   BOOL           fDisablePCM;
} EMTBEAMSEARCH, *PEMTBEAMSEARCH;

// EMTJOINFINDBEST
typedef struct {
   // on all EMTCxxx
   DWORD          dwStart;       // start count (demiphones)
   DWORD          dwEnd;         // end count
   DWORD          dwType;        // type

   DWORD          dwNum;         // number of phonemes
   DWORD          dwNumDemi;     // number of demiphones in the sentence
   PCMTTSTriPhoneAudio *pptp;    // Triphone audio, dwNumDemi
   PSYNTHUNITJOININFO   paSUJI;  // array of one per demiphone, dwNumDemiu num
   int            iTTSQuality;   // TTS quality to use
   DWORD          *padwSkip;     // skip info
} EMTJOINFINDBEST, *PEMTJOINFINDBEST;


/*************************************************************************************
CMTTS::EscMultiThreadedCallback - Standard call
*/
void CMTTS::EscMultiThreadedCallback (PVOID pParams, DWORD dwParamSize, DWORD dwThread)
{
   DWORD *padw = (DWORD*)pParams;
   DWORD dwStart = padw[0];
   DWORD dwEnd = padw[1];
   DWORD dwType = padw[2];
   DWORD i, j;

   switch (dwType) {
   case 30: // SynthUnit JoinFindBest
      {
         PEMTJOINFINDBEST pe = (PEMTJOINFINDBEST) pParams;

         PSYNTHUNITJOININFO paSUJI = pe->paSUJI;
         int iTTSQuality = pe->iTTSQuality;
         DWORD dwNum = pe->dwNum;
         PCMTTSTriPhoneAudio *pptp = pe->pptp;
         DWORD *padwSkip = pe->padwSkip;

         for (i = pe->dwStart; i < pe->dwEnd; i++) {

            paSUJI[i].dwBestA = paSUJI[i].dwBestB = (DWORD)-1;
            paSUJI[i].fJoinScore = 0;

            DWORD dwDemiPhone = i;
            DWORD dwCurDemiPhone = i;

            PCMTTSTriPhoneAudio pCenter = pptp[dwCurDemiPhone];
            PCMTTSTriPhoneAudio pLeft = dwCurDemiPhone ? pptp[dwCurDemiPhone-1] : NULL;
            PCMTTSTriPhoneAudio pRight = (dwCurDemiPhone+1 < dwNum*TTSDEMIPHONES) ? pptp[dwCurDemiPhone+1] : NULL;
            DWORD dwSkip = padwSkip[dwCurDemiPhone];
            DWORD dwSkipLeft = dwCurDemiPhone ? padwSkip[dwCurDemiPhone-1] : 0;
            DWORD dwSkipRight = (dwCurDemiPhone+1 < dwNum*TTSDEMIPHONES) ? padwSkip[dwCurDemiPhone+1] : 0;

            DWORD dwDemiPhoneMod = dwDemiPhone % TTSDEMIPHONES;

            // where are the joins for the left, center, and right?
            DWORD adwFeatureJoin[3][2];   // [0=left,1=center,2=right][0=left,1=right]
            DWORD adwFeatureJoinIdeal[3][2]; // like adwFeatureJoin, but ideal locations
            SynthUnitCalcFeatureJoins (dwDemiPhoneMod + TTSDEMIPHONES - 1 /* so modulo around */, pLeft, dwSkipLeft,
               &adwFeatureJoin[0][0], &adwFeatureJoin[0][1], &adwFeatureJoinIdeal[0][0], &adwFeatureJoinIdeal[0][1]);
            SynthUnitCalcFeatureJoins (dwDemiPhoneMod, pCenter, dwSkip,
               &adwFeatureJoin[1][0], &adwFeatureJoin[1][1], &adwFeatureJoinIdeal[1][0], &adwFeatureJoinIdeal[1][1]);
            SynthUnitCalcFeatureJoins (dwDemiPhoneMod + 1, pRight, dwSkipRight,
               &adwFeatureJoin[2][0], &adwFeatureJoin[2][1], &adwFeatureJoinIdeal[2][0], &adwFeatureJoinIdeal[2][1]);

            // see where the center unit joins up with the right
            if (!pCenter || !pRight)
               continue;

      #ifdef _DEBUG
            DWORD dwOldA = adwFeatureJoin[1][1];
            DWORD dwOldB = adwFeatureJoin[2][0];
      #endif

            int iBestCompareQuality;
            int iJoinCompareQuality;
            if (iTTSQuality <= 0) {
               iBestCompareQuality = 0; // BUGFIX - Was 1, but too slow on single-core
               iJoinCompareQuality = 0;
            }
            else if (iTTSQuality == 1) {
               iBestCompareQuality = 1;   // BUGFIX - Was 2, but changed to every 3rd feature
               iJoinCompareQuality = 1;
            }
            else if (iTTSQuality == 2) {
               iBestCompareQuality = 2;   // BUGFIX - Was 3, but changed to every other feature
               iJoinCompareQuality = 2;
            }
            else {   // iTTSQUality >= 3
               iBestCompareQuality = 3;
               iJoinCompareQuality = 3;
            }

            // if have center phoneme and right phoneme, then ignore skip (because will get solved eventually)
            // and calculate the right error
            DWORD dwRangeA = (adwFeatureJoin[1][1] - adwFeatureJoin[1][0] + JOINFINDBESTRANGE - 1) / JOINFINDBESTRANGE;  // half a demiphone
            DWORD dwRangeB = (adwFeatureJoin[2][1] - adwFeatureJoin[2][0] + JOINFINDBESTRANGE - 1) / JOINFINDBESTRANGE; // half a demiphone
               // BUGFIX - Instead of half a demiphone range, do 1/3 of a demiphone
            dwRangeA = min(dwRangeA, MAXJOINRANGE(m_dwUnits));
            dwRangeB = min(dwRangeB, MAXJOINRANGE(m_dwUnits));
            DWORD dwHalfWindow = JOINHALFWINDOWSIZE(m_dwUnits);
            fp fJoinScore = JoinFindBest (
               &m_acsTTSWave[pCenter->m_wOrigWave % MAXRAYTHREAD],
               &m_acsTTSWave[pRight->m_wOrigWave % MAXRAYTHREAD],
               pCenter->m_wOrigWave, pRight->m_wOrigWave,
               adwFeatureJoin[1][1], adwFeatureJoin[2][0],
               dwRangeA, dwRangeB,
               adwFeatureJoinIdeal[1][1], adwFeatureJoinIdeal[2][1],
               dwHalfWindow,
               iBestCompareQuality, iBestCompareQuality,
               &paSUJI[i].dwBestA, &paSUJI[i].dwBestB);

            paSUJI[i].fJoinScore = fJoinScore;

         } // i, over all joins
      }
      return;

   case 20: // beam search
      {
#if 0 // def _DEBUG
         DWORD dwStartTime = GetTickCount();
#endif

         PEMTBEAMSEARCH pe = (PEMTBEAMSEARCH) pParams;

         PCListFixed plUNITOPTION = pe->plUNITOPTION;
         PUNITOPTION puo = (PUNITOPTION) plUNITOPTION->Get(0);
         PCMem pmemFrom = pe->pmemFrom;
         PCMem pmemTo = pe->pmemTo;
         DWORD dwTime = pe->dwTime;
         DWORD *pdwWord = pe->pdwWord;
         PBYTE pbWordSylPos = pe->pbWordSylPos;
         PBYTE pbPhone = pe->pbPhone;
         PBYTE pbTPhoneNoStress = pe->pbTPhoneNoStress;
         PBYTE pbTPhoneGroup = pe->pbTPhoneGroup;
         BYTE bSilence = pe->bSilence;
         fp *pafSmooth = pe->pafSmooth;
         PCListFixed plUNITOPTIONLast = pe->plUNITOPTIONLast;
         fp fEnergy = pe->fEnergy;
         fp fAccent = pe->fAccent;
         DWORD dwDuration = pe->dwDurationX;
         DWORD dwFuncWordGroup = pe->dwFuncWordGroup;
         fp fEnergyPrev = pe->fEnergyPrev;
         BOOL fUsePitch = pe->fUsePitch;
         int *paiPitchF0Extra = pe->paiPitchF0Extra;
         int *paiPitchF0Fuji = pe->paiPitchF0Fuji;
         fp fEnergyAverage = pe->fEnergyAverage;
         DWORD dwNum = pe->dwNum;
         double *pfCutOff = pe->pfCutOff;
         DWORD *padwModulo = pe->padwModulo;
         BOOL fDisablePCM = pe->fDisablePCM;

         DWORD dwBest;
         double fBestScore;
         PPHONEHYP pahFrom;
         DWORD dwNumFrom = (DWORD)pmemFrom->m_dwCurPosn / sizeof(PHONEHYP);
         PHONEHYP ph, phBest;
         PCMTTSTriPhoneAudio ptNew;
         // double fCutOff = 1000000000000;
            // NOTE: Because multithreaded, won't cut off as many units, but shouldn't
            // be much of a problem, especially because of speed gain due to multithreaded

         // pou += dwStart
         // for (j = dwStart; j < dwEnd; j++, puo++) {
         DWORD dwModulo;
         DWORD dwLastJ = 0;
         for (dwModulo = dwStart; dwModulo < dwEnd; dwModulo++) {
            j = padwModulo[dwModulo];
            puo += (j - dwLastJ);
            dwLastJ = j;

            // remember the best
            dwBest = (DWORD)-1;
            fBestScore = 0;

            // loop over all existing hypothesis and find the one that results
            // in the best final score
            pahFrom = (PPHONEHYP)pmemFrom->p;
            for (i = 0; i < dwNumFrom; i++, pahFrom++) {
               // fill in new info
               memcpy (ph.aPCMTTSTriPhoneAudio, &pahFrom->aPCMTTSTriPhoneAudio[1], (PHONEHISTORY-1) * sizeof(PCMTTSTriPhoneAudio));
               ph.fScoreWithEnergy = pahFrom->fScoreWithEnergy;
               ph.fScoreNoEnergy = pahFrom->fScoreNoEnergy;

               if (!puo->pTPA) {
               //if (!pptp) {
                  // if silence (or unknown phoneme) then just add and dont calc score
                  ptNew = NULL;
                  ph.fEndSilence = TRUE;
                  goto addit;
               }
               //ptNew = pptp[j];
               ptNew = puo->pTPA;

               // determine the boundary
               double fBoundaryTheoretical = ScoreCalcBoundary (dwTime, ptNew, pdwWord, pbWordSylPos, pbPhone,
                  pbTPhoneNoStress, pbTPhoneGroup, bSilence,
                  NULL, pahFrom, ph.aPCMTTSTriPhoneAudio);
               _ASSERTE (fBoundaryTheoretical < 10000);

               // calculate the blend
               //BOOL fWordStart = (*pbWordSylPos & WORDPOS_WORDSTART);
               //BOOL fSylStart = (*pbWordSylPos & WORDPOS_SYLSTART);
               double fBoundaryCalc = pafSmooth[plUNITOPTIONLast->Num() * j + pahFrom->dwPreviousIndex];
               //fBoundaryCalc = ATSTARTOFPHONE ? CROSSWORDLESSPENALTYLEFT(fBoundaryCalc) : fBoundaryCalc;
                  // BUGFIX - If not at the start of the phoneme, then CROSSWORDLESSPENALTYLEFT

               double fBoundary = SCORECALCWEIGHTTHEORETICALBOUNDARY * fBoundaryTheoretical +
                  (1.0 - SCORECALCWEIGHTTHEORETICALBOUNDARY) * fBoundaryCalc;
               double fWeightBoundary = ScoreCalcWeightBoundary (fEnergyPrev, fEnergy, m_dwUnits);
               double fWeightBoundaryNoEnergy = ScoreCalcWeightBoundary (1.0, 1.0, m_dwUnits);
                  //dwDurationPrev, dwDuration,
                  //(dwTime ? pbWordPos[(dwTime-1)/TTSDEMIPHONES] : 0),
                  //pbWordPos[dwTime/TTSDEMIPHONES] );

               ph.fScoreWithEnergy += fWeightBoundary * fBoundary;
               ph.fScoreNoEnergy += fWeightBoundaryNoEnergy * fBoundary;

               // BUGFIX - partial check to see if eliminate early
               if (ph.fScoreWithEnergy > *pfCutOff) // NOTE - No worry about thread safety since atmoic, and will only go down
                  continue;
               if ((dwBest != (DWORD)-1) && (ph.fScoreWithEnergy >= phBest.fScoreWithEnergy))
                  continue;
              


               // calculate weight for self
               double fScoreCalcWeightSelf = ScoreCalcWeightSelf (fEnergy, dwDuration);
               double fScoreCalcWeightSelfNoEnergy = ScoreCalcWeightSelf (1.0, SRSAMPLESPERSEC / 10);
                  // BUGFIX - Assume average phoneme is 1/10th of a second, so don't end up preferring short sentences
               double fSelfA =
                  ScoreCalcSelfA (dwTime, ptNew, NULL /*ph.aPCMTTSTriPhoneAudio[PHONEHISTORY-2]*/, fUsePitch,
                     paiPitchF0Extra, paiPitchF0Fuji,
                     dwDuration, fEnergy, fAccent, dwFuncWordGroup,
                     pdwWord, pbWordSylPos, pbPhone, pbTPhoneNoStress, pbTPhoneGroup, bSilence, NULL, pahFrom,
                     fDisablePCM);
               _ASSERTE (fSelfA < 10000);
               ph.fScoreWithEnergy += fSelfA * fScoreCalcWeightSelf;
               ph.fScoreNoEnergy += fSelfA * fScoreCalcWeightSelfNoEnergy;
               if (ph.fScoreWithEnergy > *pfCutOff) // NOTE - No worry about thread safety since atmoic, and will only go down
                  continue;
               if ((dwBest != (DWORD)-1) && (ph.fScoreWithEnergy >= phBest.fScoreWithEnergy))
                  continue;

               // second part
               double fSelfB =
                  ScoreCalcSelfB (dwTime, ptNew, ph.aPCMTTSTriPhoneAudio[PHONEHISTORY-2], fUsePitch,
                     paiPitchF0Extra, paiPitchF0Fuji,
                     dwDuration, fEnergy, fAccent, dwFuncWordGroup,
                     pdwWord, pbWordSylPos, pbPhone, pbTPhoneNoStress, pbTPhoneGroup, bSilence, NULL, pahFrom,
                     fDisablePCM);
               _ASSERTE (fSelfB < 10000);
               ph.fScoreWithEnergy += fSelfB * fScoreCalcWeightSelf;
               ph.fScoreNoEnergy += fSelfB * fScoreCalcWeightSelfNoEnergy;



               ph.fEndSilence = ATENDOFPHONE && (ptNew->m_bPhoneRight == bSilence);
                  // BUGFIX - endsielnce only if at end of this phone


      addit:
               // if this isn't the best score then don't bother
               if ((dwBest != (DWORD)-1) && (ph.fScoreWithEnergy >= phBest.fScoreWithEnergy))
                  continue;

               if (ph.fScoreWithEnergy > *pfCutOff) // NOTE - No worry about thread safety since atmoic, and will only go down
                  continue;

               EnterCriticalSection (&m_csSynthBeamSearch);
               // if this score isn't nearly as good as the best score then don't bother
               if (ph.fScoreWithEnergy <= *pfCutOff) {
                  // write this
                  phBest = ph;
                  phBest.aPCMTTSTriPhoneAudio[PHONEHISTORY-1] = ptNew;
                  phBest.dwPreviousIndex = j; // remember the location in this
                  dwBest = j;
                  *pfCutOff = phBest.fScoreWithEnergy + HYPAUTOCULL;
               }
               LeaveCriticalSection (&m_csSynthBeamSearch);
            } // i

            // if best then keep it
            if (dwBest != (DWORD)-1) {
               EnterCriticalSection (&m_csSynthBeamSearch);
               // make sure enough memory
               if (!pmemTo->Required (pmemTo->m_dwCurPosn + dwNum * sizeof(PHONEHYP)))
                  return;  // error
               memcpy ((PBYTE)pmemTo->p + pmemTo->m_dwCurPosn, &phBest, sizeof(phBest));
               pmemTo->m_dwCurPosn += sizeof(phBest);
               LeaveCriticalSection (&m_csSynthBeamSearch);
            }
         } // j, unit options

#if 0 // def _DEBUG
         WCHAR szTemp[64];
         swprintf (szTemp, L"[T %d = %d]", (int)dwStart, (int)(GetTickCount() - dwStartTime));
         OutputDebugStringW (szTemp);
#endif
      }
      return;

   case 9:  // prelim score calc
      {
         PEMTCERRORCACHECALC pe = (PEMTCERRORCACHECALC) pParams;

         PCMTTSTriPhoneAudio *pptp = pe->pptp;
         PCListFixed plUNITOPTION = pe->plUNITOPTION;
         fp fEnergy = pe->fEnergy;
         fp fAccent = pe->fAccent;
         DWORD dwDuration = pe->dwDurationX;
         DWORD dwFuncWordGroup = pe->dwFuncWordGroup;
         fp fEnergyPrev = pe->fEnergyPrev;
         DWORD dwTime = pe->dwTime;
         BOOL fUsePitch = pe->fUsePitch;
         int *paiPitchF0Extra = pe->paiPitchF0Extra;
         int *paiPitchF0Fuji = pe->paiPitchF0Fuji;
         DWORD *pdwWord = pe->pdwWord;
         PBYTE pbWordSylPos = pe->pbWordSylPos;
         PBYTE pbPhone = pe->pbPhone;
         PBYTE pbTPhoneNoStress = pe->pbTPhoneNoStress;
         PBYTE pbTPhoneGroup = pe->pbTPhoneGroup;
         BYTE bSilence = pe->bSilence;
         PCMem pmemFrom = pe->pmemFrom;
         DWORD dwPass = pe->dwPass;
         int iTTSQuality = pe->iTTSQuality;
         BOOL fDisablePCM = pe->fDisablePCM;

         iTTSQuality = max(iTTSQuality, 0);
         iTTSQuality = min(iTTSQuality, NUMJOINQUALITY-1);

         UNITOPTION uo;
         PCMTTSTriPhoneAudio ptNew;
         DWORD dwNumPPHLast = (DWORD)pmemFrom->m_dwCurPosn / sizeof(PHONEHYP);
         PPHONEHYP pPPHLast = (PPHONEHYP) pmemFrom->p;

         for (j = dwStart; j < dwEnd; j++) {
            memset (&uo, 0, sizeof(uo));

            if (!pptp) {
               // if silence (or unknown phoneme) then just add and dont calc score
               // add empty UO
               EnterCriticalSection (&m_csSynthBeamSearch);
               plUNITOPTION->Add (&uo);
               LeaveCriticalSection (&m_csSynthBeamSearch);
               continue;
            }
            ptNew = pptp[j];

            DWORD dwLastPhone = 0;


            double fScoreCalcWeightSelf = ScoreCalcWeightSelf (fEnergy, dwDuration);
            double fWeightBoundary = ScoreCalcWeightBoundary (fEnergyPrev, fEnergy, m_dwUnits);
               //dwDurationPrev, dwDuration,
               //(dwTime ? pbWordPos[(dwTime-1)/TTSDEMIPHONES] : 0),
               //pbWordPos[dwTime/TTSDEMIPHONES] );

            // can precalc this part
            double fSelfA = ScoreCalcSelfA (dwTime, ptNew, NULL, fUsePitch,
               paiPitchF0Extra, paiPitchF0Fuji,
               dwDuration, fEnergy, fAccent, dwFuncWordGroup,
               pdwWord, pbWordSylPos, pbPhone, pbTPhoneNoStress, pbTPhoneGroup, bSilence, &dwLastPhone, NULL,
               fDisablePCM);
            _ASSERTE (fSelfA < 10000);
            
            // loop over all existing hypothesis and see which ones would have the best score
            DWORD k;
            double fScoreBest = 0;
            double fScore;
            DWORD dwBest = (DWORD)-1;
            for (k = 0; k < dwNumPPHLast; k++) {
               fScore = pPPHLast[k].fScoreWithEnergy + fSelfA * fScoreCalcWeightSelf;

               // BUGFIX - Do ScoreCalcBoundary() first because faster than ScoreCalcSelfB, and produces larger values
               PCMTTSTriPhoneAudio pUnitKpTPA = pPPHLast[k].aPCMTTSTriPhoneAudio[PHONEHISTORY-1];
               if (pUnitKpTPA)
                  dwLastPhone = MAKELONG (pUnitKpTPA->m_wOrigPhone, pUnitKpTPA->m_wOrigWave);
               else
                  dwLastPhone = (DWORD)-1;

               double fBoundary;
               if (!pUnitKpTPA || !ptNew)
                  fBoundary = 0; // since silence
               else if ((dwTime % TTSDEMIPHONES) && (pUnitKpTPA == ptNew))
                  fBoundary = 0; // since same unit connecting to self
               else if (!(dwTime % TTSDEMIPHONES) &&
                  (ptNew->m_wOrigWave == pUnitKpTPA->m_wOrigWave) &&
                  (ptNew->m_wOrigPhone == pUnitKpTPA->m_wOrigPhone+1) )
                     fBoundary = 0; // since same unit connecting to self
               else if (dwPass) {
                  fp fValue;
                  // BUGFIX - Reversed order that passed to ConnectErrorCacheGet()
                  if (ptNew->ConnectErrorCacheGet((DWORD)iTTSQuality, pUnitKpTPA, (dwTime % TTSDEMIPHONES), &fValue))
                     fBoundary = fValue;
                  else // shouldnt happen
                     fBoundary = ScoreCalcBoundary (dwTime, ptNew, pdwWord, pbWordSylPos, pbPhone, pbTPhoneNoStress,
                        pbTPhoneGroup, bSilence, &dwLastPhone, NULL, NULL);
               }
               else
                  // first pass, so guestimate
                  fBoundary = ScoreCalcBoundary (dwTime, ptNew, pdwWord, pbWordSylPos, pbPhone, pbTPhoneNoStress,
                     pbTPhoneGroup, bSilence, &dwLastPhone, NULL, NULL);
               _ASSERTE (fBoundary < 10000);

               fScore += fWeightBoundary * fBoundary;

               // BUGFIX - fast eliminate
               if ((dwBest != (DWORD)-1) && (fScore >= fScoreBest))
                  continue;



               // BUGFIX - Because fSelf depends on hypothesized last triphone, moving ScoreCalcSelf() into
               // this loop
               double fSelfB = ScoreCalcSelfB (dwTime, ptNew, pUnitKpTPA, fUsePitch, 
                  paiPitchF0Extra, paiPitchF0Fuji,
                  dwDuration, fEnergy, fAccent, dwFuncWordGroup,
                  pdwWord, pbWordSylPos, pbPhone, pbTPhoneNoStress, pbTPhoneGroup, bSilence, &dwLastPhone, NULL,
                  fDisablePCM);
                        // NOTE: Exact value for dwLastPhone doesn't matter for ScoreCalcSelf
               _ASSERTE (fSelfB < 10000);
               fScore += fSelfB * fScoreCalcWeightSelf;


               // if this is better than the best score, then remember
               if ((dwBest == (DWORD)-1) || (fScore < fScoreBest)) {
                  dwBest = k;
                  fScoreBest = fScore;
               }
            } // k



            // remember the best of all possible scores
            uo.fScore = (dwBest != (DWORD)-1) ? fScoreBest : 1000000000000;

            // add this
            uo.pTPA = ptNew;

            EnterCriticalSection (&m_csSynthBeamSearch);
            plUNITOPTION->Add (&uo);
            LeaveCriticalSection (&m_csSynthBeamSearch);
         } // j      
      }
      return;

   case 10: // error-calc from GenerateUnitOption
      {
         PEMTCERRORCACHECALC pe = (PEMTCERRORCACHECALC) pParams;

         PUNITOPTION pUnit = pe->pUnit;
         PUNITOPTION pUnitI, pUnitLastJ = NULL;
         PCMTTSTriPhoneAudio pUnitIpTPA, pUnitLastJpTPA;
         DWORD dwTime = pe->dwTime;
         PCListFixed plUNITOPTIONLast = pe->plUNITOPTIONLast;
         fp *paf = pe->paf + dwStart * plUNITOPTIONLast->Num();
         BOOL *pafLastUsed = pe->pafLastUsed;
         BOOL *pafIsValidEnergy = pe->pafIsValidEnergy;
         fp *pafLastEnergy = pe->pafLastEnergy;
         int iTTSQuality = pe->iTTSQuality;
         DWORD dwPass = pe->dwPass;
         PCMem pmemFrom = pe->pmemFrom;
         DWORD dwNumPPHLast = (DWORD)pmemFrom->m_dwCurPosn / sizeof(PHONEHYP);
         PPHONEHYP pPPHLast = (PPHONEHYP) pmemFrom->p;

         int iBestCompareQuality;
         int iJoinCompareQuality;
         iTTSQuality = max(iTTSQuality, 0);
         iTTSQuality = min(iTTSQuality, NUMJOINQUALITY-1);
         if (iTTSQuality <= 0) {
            iBestCompareQuality = 0;
            iJoinCompareQuality = 0;
         }
         else if (iTTSQuality == 1) {
            iBestCompareQuality = 0;
            iJoinCompareQuality = 1;
         }
         else if (iTTSQuality == 2) {
            iBestCompareQuality = 1;
            iJoinCompareQuality = 1;
         }
         else {   // iTTSQUality >= 3
            iBestCompareQuality = 2;
            iJoinCompareQuality = 2;
         }

         //PSRFEATURESMALL apSRF[4];
         // fp afEnergy[4], afRealEnergy[4], afDB[4];
         CListFixed lTPA, lTPAScore;
         //SRFEATURESMALL srfSilence;
         //memset (&srfSilence, 0, sizeof(srfSilence));
         //for (i = 0; i < SRDATAPOINTSSMALL; i++) {
         //   srfSilence.acNoiseEnergyMain[i] = SRNOISEFLOOR;
         //   srfSilence.acVoiceEnergyMain[i] = SRABSOLUTESILENCE;
         //   srfSilence.acNoiseEnergyDelta[i] = SRABSOLUTESILENCE;
         //   srfSilence.acVoiceEnergyDelta[i] = SRABSOLUTESILENCE;
         //} // i
         //fp fSilenceEnergy = SRFEATUREEnergySmall (TRUE, &srfSilence, FALSE, TRUE);
         //SRFEATUREScale (&srfSilence, PHONESAMPLENORMALIZED / fSilenceEnergy);
         PUNITOPTION pUnitLast = (PUNITOPTION)plUNITOPTIONLast->Get(0);
         //DWORD k;
         //double fError;

         for (i = dwStart; i < dwEnd; i++) {
            pUnitI = &pUnit[i];
            pUnitIpTPA = pUnitI->pTPA;

            DWORD dwStartIndex = (dwTime % TTSDEMIPHONES)*2;
            // determine the comparison and energies
//            if (pUnitIpTPA) {
//               // remember two bits from start
//               apSRF[0] = &pUnitIpTPA->m_aSRFeatBoundary[dwStartIndex+0];
//               apSRF[1] = &pUnitIpTPA->m_aSRFeatBoundary[dwStartIndex+1];
//               afEnergy[0] = pUnitIpTPA->m_afSRFeatBoundary[dwStartIndex+0];
//               afEnergy[1] = pUnitIpTPA->m_afSRFeatBoundary[dwStartIndex+1];
//            }
//            else {
//               // this is silence, so nothing
//               apSRF[0] = apSRF[1] = &srfSilence;
//               afEnergy[0] = afEnergy[1] = fSilenceEnergy;
//            }

            // calculate the real energy for better tests. should
            // be close to PHONESAMPLENORMALIZED
//            afRealEnergy[0] = SRFEATUREEnergySmall (TRUE, apSRF[0], FALSE, TRUE);
//            afRealEnergy[1] = SRFEATUREEnergySmall (TRUE, apSRF[1], FALSE, TRUE);
//            afDB[0] = AmplitudeToDb(afEnergy[0] / THEORETICALMAXENERGY * (fp)0x8000);
//            afDB[1] = AmplitudeToDb(afEnergy[1] / THEORETICALMAXENERGY * (fp)0x8000);

            lTPA.Init (sizeof(PCMTTSTriPhoneAudio));
            lTPAScore.Init (sizeof(fp));

            // loop over the previous options
            DWORD dwMaxJ = dwPass ? plUNITOPTIONLast->Num() : dwNumPPHLast;
            for (j = 0; j < dwMaxJ; j++, paf++) {
               if (dwPass) {
                  // if not used then skip
                  if (!pafLastUsed[j])
                     continue;

                  pUnitLastJ = &pUnitLast[j];
                  pUnitLastJpTPA = pUnitLastJ->pTPA;
               }
               else
                  pUnitLastJpTPA = pPPHLast[j].aPCMTTSTriPhoneAudio[PHONEHISTORY-1];


               // BUGFIX - If missing either pUnitIpTPA or  pUnitLastJpTPA, then assume 0 error
               if (!pUnitIpTPA || !pUnitLastJpTPA) {
                  if (dwPass)
                     paf[0] = 0;
                  continue;
               }

               // if these are the same wave then error = 0
               if ((dwTime % TTSDEMIPHONES) && (pUnitIpTPA == pUnitLastJpTPA)) {
                  // optimiziation since will match against itself, so should be no error
                  if (dwPass)
                     paf[0] = 0;
                  continue;
               }
               else if (!(dwTime % TTSDEMIPHONES) && pUnitIpTPA && pUnitLastJpTPA &&
                  (pUnitIpTPA->m_wOrigWave == pUnitLastJpTPA->m_wOrigWave) &&
                  (pUnitIpTPA->m_wOrigPhone == pUnitLastJpTPA->m_wOrigPhone+1) ) {

                  // optimizations so score for contiguous is 0
                  if (dwPass)
                     paf[0] = 0;
                  continue;
               }


               fp fValue;
               // BUGFIX - Reversed order that passed to ConnectErrorCacheGet()
               if (pUnitIpTPA->ConnectErrorCacheGet((DWORD)iTTSQuality, pUnitLastJpTPA, (dwTime % TTSDEMIPHONES), &fValue)) {
                  // if not second pass then MUST add this in to ensure it's not flushed
                  if (!dwPass)
                     goto addin;

                  paf[0] = fValue;  // BUGFIX - unintentionally deleted this
                  continue;   // already cached
               }

               // find the join locations
               DWORD adwFeatureJoin[2][2];   // [0=left phone,1=right phone][0=left,1=right]
               DWORD adwFeatureJoinIdeal[2][2]; // like adwFeatureJoin, but ideal locations
               SynthUnitCalcFeatureJoins (dwTime + TTSDEMIPHONES - 1 /*left*/, pUnitLastJpTPA, 0 /* skip none*/,
                  &adwFeatureJoin[0][0], &adwFeatureJoin[0][1], &adwFeatureJoinIdeal[0][0], &adwFeatureJoinIdeal[0][1]);
               SynthUnitCalcFeatureJoins (dwTime /*right*/, pUnitIpTPA, 0 /* skip none*/,
                  &adwFeatureJoin[1][0], &adwFeatureJoin[1][1], &adwFeatureJoinIdeal[1][0], &adwFeatureJoinIdeal[1][1]);

               // how much to scan
               DWORD dwBestA, dwBestB;
               DWORD dwRangeA = (adwFeatureJoin[0][1] - adwFeatureJoin[0][0] + JOINFINDBESTRANGE - 1) / JOINFINDBESTRANGE;  // half a demiphone
               DWORD dwRangeB = (adwFeatureJoin[1][1] - adwFeatureJoin[1][0] + JOINFINDBESTRANGE - 1) / JOINFINDBESTRANGE; // half a demiphone
               dwRangeA = min(dwRangeA, MAXJOINRANGE(m_dwUnits));
               dwRangeB = min(dwRangeB, MAXJOINRANGE(m_dwUnits));
               DWORD dwHalfWindow = JOINHALFWINDOWSIZE(m_dwUnits);

               // calculate the difference
               fp fScoreBest = JoinFindBest (
                  &m_acsTTSWave[pUnitLastJpTPA->m_wOrigWave % MAXRAYTHREAD],
                  &m_acsTTSWave[pUnitIpTPA->m_wOrigWave % MAXRAYTHREAD],
                  pUnitLastJpTPA->m_wOrigWave, pUnitIpTPA->m_wOrigWave,
                  adwFeatureJoin[0][1], adwFeatureJoin[1][0],
                  dwRangeA, dwRangeB,
                  adwFeatureJoinIdeal[0][1], adwFeatureJoinIdeal[1][1],
                  dwHalfWindow,
                  iBestCompareQuality, iJoinCompareQuality,
                  &dwBestA, &dwBestB);

               fScoreBest /= (fp)JOINHALFWINDOWSIZE(m_dwUnits);  // because will be multiplied by this soon

               // write this
               if (dwPass)
                  paf[0] = fScoreBest;


#if 0 // old code for comparison, overruled by new JoinFindBest
               DWORD dwPrevIndex = (dwTime % TTSDEMIPHONES) ? dwStartIndex : (TTSDEMIPHONES * 2);


               // determine comparison and energies
               if (pUnitLastJpTPA) {
                  // remember two bits from start
                  apSRF[2] = &pUnitLastJpTPA->m_aSRFeatBoundary[dwPrevIndex+0];
                  apSRF[3] = &pUnitLastJpTPA->m_aSRFeatBoundary[dwPrevIndex+1];
                  afEnergy[2] = pUnitLastJpTPA->m_afSRFeatBoundary[dwPrevIndex+0];
                  afEnergy[3] = pUnitLastJpTPA->m_afSRFeatBoundary[dwPrevIndex+1];
               }
               else {
                  // this is silence, so nothing
                  apSRF[2] = apSRF[3] = &srfSilence;
                  afEnergy[2] = afEnergy[3] = fSilenceEnergy;
               }

               // calculate the real energy for better tests. should
               // be close to PHONESAMPLENORMALIZED
               // BUGFIX - Was testing for i, should be testing for valid energy
               EnterCriticalSection (&m_csSynthBeamSearch);
               if (pafIsValidEnergy[j]) {
                  afRealEnergy[2] = pafLastEnergy[j*4+0];
                  afRealEnergy[3] = pafLastEnergy[j*4+1];
                  afDB[2] = pafLastEnergy[j*4+2];
                  afDB[3] = pafLastEnergy[j*4+3];
               }
               else {
                  pafLastEnergy[j*4+0] = afRealEnergy[2] = SRFEATUREEnergySmall (TRUE, apSRF[2], FALSE, TRUE);
                  pafLastEnergy[j*4+1] = afRealEnergy[3] = SRFEATUREEnergySmall (TRUE, apSRF[3], FALSE, TRUE);
                  pafLastEnergy[j*4+2] = afDB[2] = AmplitudeToDb(afEnergy[2] / THEORETICALMAXENERGY * (fp)0x8000);
                  pafLastEnergy[j*4+3] = afDB[3] = AmplitudeToDb(afEnergy[3] / THEORETICALMAXENERGY * (fp)0x8000);
                  pafIsValidEnergy[j] = TRUE;
               }
               LeaveCriticalSection (&m_csSynthBeamSearch);

               fError = 0;

               for (k = 0; k < 2; k++) {
                  // compare the two
                  DWORD dwA = k+0;
                  DWORD dwB = k+2;
      #if 0 // def _DEBUG // to test and make sure energy calculation is right
                  afRealEnergy[dwA] = SRFEATUREEnergy(apSRF[dwA]);
                  afRealEnergy[dwB] = SRFEATUREEnergy(apSRF[dwB]);
      #endif
                  fp fCompare = SRFEATURECompareSmall (TRUE, apSRF[dwA], afRealEnergy[dwA], apSRF[dwB], afRealEnergy[dwB]);
                  
                  // BUGFIX - Just in case decides to get really wild, which does if very large energies
                  fCompare = max (fCompare, 0.0);
                  fCompare = min (fCompare, 1.0);
                  
                  // find the higher number of db
                  fp fMaxDb = max(afDB[dwA], afDB[dwB]);
                  fMaxDb = 1 + fMaxDb/((fp)-SRNOISEFLOOR);   // so that at -60 decibles max, fCompare won't count for anything
                  fMaxDb = max(fMaxDb, 0);
                  fMaxDb = min(fMaxDb, 1);

                  // total error
                  fCompare = (fCompare * SRCOMPAREWEIGHT + fabs(afDB[dwA] - afDB[dwB])) * fMaxDb;
                     // penalty for difference in volume only counts half
                  fError += fCompare;
               } // k

               // fit it into a byte
               fError *= BORDERDBSCALE;   // so average these two
               //fError = max(fError, 0);
               //fError = min(fError, 255);
               paf[0] = fError;
#endif // 0
               fValue = fScoreBest;
   
addin:
               // store in cache
               if (pUnitIpTPA) {
                  lTPA.Add (&pUnitLastJpTPA);
                  lTPAScore.Add (&fValue);
                  // pUnitIpTPA->ConnectErrorCacheSet(pUnitLastJpTPA, (dwTime % TTSDEMIPHONES), paf[0]);
               }
            } // j

            // add them all at once
            if (pUnitIpTPA && lTPA.Num()) {
               // figure out cache size
               DWORD dwMaxUnitOptionFirst, dwMaxUnitOptionSecond;
               UnitCacheSize (iTTSQuality + 1 /* since subtracted before sent */, &dwMaxUnitOptionFirst, &dwMaxUnitOptionSecond);
               DWORD dwCacheSize;
               if (dwPass)
                  dwCacheSize = dwMaxUnitOptionSecond;
               else
                  dwCacheSize = dwMaxUnitOptionFirst;

               // BUGFIX - Disable since using too much memory on high quality large voices
               // dwCacheSize = dwCacheSize * ((DWORD)max(iTTSQuality,0)+1); // so quality 1 is same as before
                  // since always using 1 less quality when passed in to iTTSQUality

               pUnitIpTPA->ConnectErrorCacheSet((DWORD)iTTSQuality, (PCMTTSTriPhoneAudio*)lTPA.Get(0), (fp*)lTPAScore.Get(0), lTPA.Num(),
                  (dwTime % TTSDEMIPHONES),
                  dwCacheSize );
                     // BUGFIX - Was /100, but quickly allocated too much memory with 260K units
                     // because o(n2)
            }
         } // i

      }
      return;
   } // switch
}


/*************************************************************************************
CMTTS::UnitCacheSize - Calculates the unit cache size.

inputs
   int            iTTSQuality - Text to speech qualuty
   DWORD          *pdwFirstPass - Filled with frst pass size
   DWORD          *pdwSecondPass - Filled with second pass size.
returns
   none
*/
void CMTTS::UnitCacheSize (int iTTSQuality, DWORD *pdwFirstPass, DWORD *pdwSecondPass)
{

   // OPTIMIZATION: find out where the best error, and only allow to exceed by so much
   // BUGFIX - MAXUNITOPTION depends on number of units
   fp fMaxUnitOption = sqrt((double)m_dwUnits / (double)OPTIMALNUMUNITS) * (double)MAXUNITOPTION;
   int iTTSQualityLimited = min (max(iTTSQuality, 0), 3);
   // BUGFIX - Greater difference in quality
   fMaxUnitOption *= (0.33 + 0.66 * (fp)iTTSQualityLimited / 3.0);
   *pdwSecondPass = min ((DWORD)(fMaxUnitOption + 0.5) + 1, MAXUNITOPTIONTOTAL);

   // if this is the first pass then more units
   // ttsquality affects this
   // was if (!dwPass)...
   DWORD dwScale = (DWORD) max(iTTSQuality, 1);
   dwScale = min(dwScale, NUMJOINQUALITY-1);
   dwScale++;
   dwScale = dwScale * FIRSTPASSMAXUNITOPTIONSSCALE / NUMJOINQUALITY;
   *pdwFirstPass = (*pdwSecondPass) * dwScale;
}

/*************************************************************************************
CMTTS::GenerateUNITOPTION - Fills a list in with UNITOPTION structures, which
are used to limit the search for HypExpand.

inputs
   DWORD          dwTime - Use dwTime % TTSDEMIPHONES to determine the demophone
   PCMem          pmemFrom - Memory with list of PHONEHYP. m_dwCurPosn = memory where last one is
   DWORD          dwPhoneValid - Number of valid phonemes including and after pbPhone[0]. Thus, is pbPhone[0]
                  and pbPhone[1] are valid, this is 2.
   BYTE           *pbPhone - Phoneme to use at [0]. Also need [-1] and [1] to be valid
   BYTE           *pbWordSylPos - Word psotion (start, end or word)
                  0x01 = start of word, WORDPOS_WORDSTART
                  0x02 = end of word, WORDPOS_WORDEND
                  0x04 = start of syllable, WORDPOS_SYLSTART
                  0x08 = end of syllable, WORDPOS_SYLEND
                  0x10 = plosive, WORDPOS_EXTRA_PLOSIVE
                  0x20 = voiced, WORDPOS_EXTRA_VOICED
   BYTE        *pbTPhoneGroup - Phoneme into group of 16, also need [-1] and [1] to be valid
   BYTE        *pbTPhoneNoStress - Phoneme without stress, also need [-1] and [1] to be valid
         // NOTE: For optimization reasons this function assumes NUMTRIPHONEGROUP == 3
   DWORD       *pdwWord - Word number using, or -1 if not known word
   int         *paiPitchF0Extra - Array of TPAPITCHPOINTS F0Extra pitch points, log()/log(2) * 1000
                  for the ENTIRE phoneme (not just the demiphone)
   int         *paiPitchF0Fuji - Like paiPitchF0Extra, but fuji
   // DWORD       dwDurationPrev - Duration of the previous half phone, or 0 if unknown
   DWORD       dwDuration - Duration of the phone, or 0 if unknown
   fp          fEnergy - Energy of the destination unit, so can choose appropraiote units
   fp          fEnergyPrev - Energy of the previous unit, for boundary weight
   fp          fAccent - How much of an accent looking for, from -1 to 1
   BYTE        bSilence - What phoneme is silence
   DWORD       dwFuncWordGroup - Which function-word-group this is in, 0..NUMFUNCWORDGROUP(inclusive)
   fp          fEnergyAverage - Average energy over all requested phonemes. Used for score calc

   PCListFixed    plUNITOPTION - Initialized to sizeof(UNITOPTION) and filled in
   PCListFixed    plUNITOPTIONLast - Last unit option list. Can be filled with just silence if there
                     was none
   PCMem          pMemInter - Filled with an array of plUNITOPTIONLast.Num() x plUNITOPTION.Num() fp
                     indicating a pentalty to add to the score based on how well the units blend
                     together.
   int               iTTSQuality - TTS quality. 0 for fast, 1 for normal, 2 for slow, 3 for best, 4 for multipass
   BOOL              fDisablePCM - Set to TRUE if PSOLA has been temporarily disabled
*/

static int _cdecl UNITOPTIONSort (const void *elem1, const void *elem2)
{
   UNITOPTION *pdw1, *pdw2;
   pdw1 = (UNITOPTION*) elem1;
   pdw2 = (UNITOPTION*) elem2;

   if (pdw1->fScore > pdw2->fScore)
      return 1;
   else if (pdw1->fScore < pdw2->fScore)
      return -1;
   else
      return 0;
   //return (int)pdw1->dwScore - (int)pdw2->dwScore;
}

void CMTTS::GenerateUNITOPTION (DWORD dwTime, PCMem pmemFrom, DWORD dwPhoneValid, BYTE *pbPhone,
                       BYTE *pbWordSylPos, BYTE *pbTPhoneGroup, BYTE *pbTPhoneNoStress,
                       DWORD *pdwWord,
                       int* paiPitchF0Extra, int *paiPitchF0Fuji,
                       /*DWORD dwDurationPrev,*/ DWORD dwDuration, fp fEnergy, fp fEnergyPrev,
                       fp fAccent, BYTE bSilence,
                       DWORD dwFuncWordGroup,
                       fp fEnergyAverage,
                       PCListFixed plUNITOPTION, PCListFixed plUNITOPTIONLast,
                       PCMem pMemInter,
                       int iTTSQuality, BOOL fDisablePCM)
{
#if 0 // def _DEBUG
   DWORD dwStartTime = GetTickCount();
#endif

   // generate quickfollow and previous
   DWORD i;
   DWORD dwMax = 0;
   //CHashDWORD hPrevious;
   //hPrevious.Init (sizeof(PUNITOPTION));
   PUNITOPTION pUnit; //  = (PUNITOPTION) plUNITOPTIONLast->Get(0);
   //for (i = 0; i < plUNITOPTIONLast->Num(); i++, pUnit++) {
   //   if (!pUnit->pTPA)
   //      continue;

   //   // hash
   //   dwMax = MAKELONG (pUnit->pTPA->m_wOrigPhone, pUnit->pTPA->m_wOrigWave);
   //   hPrevious.Add (dwMax, &pUnit);
   //} // i

   // determine which phoneme units need to look through
   PCMLexicon pLex = Lexicon();
   BYTE bPhoneTemp = PhonemeBackoff(*pbPhone, pLex, bSilence);
   PCListFixed pl = (bPhoneTemp != bSilence) ? (m_palPCMTTSTriPhoneAudio ? m_palPCMTTSTriPhoneAudio[bPhoneTemp] : NULL) : NULL;
   PCMTTSTriPhoneAudio *pptp;
   CListFixed lPCMTTSTriPhoneAudio;
   lPCMTTSTriPhoneAudio.Init (sizeof(PCMTTSTriPhoneAudio));
   DWORD dwNum;
   if (!pl || !pl->Num()) {
      // serious problem because have selected phoneme but no recordsing, therefore
      // just set to NULL pptp
      pptp = NULL;
      dwNum = 1;
   }
   else {
      pptp = (PCMTTSTriPhoneAudio*) pl->Get(0);
      dwNum = pl->Num();
   }

   // ignore pitch
   BOOL fUsePitch = FALSE;
   if (pl && pLex) {
      PLEXPHONE plp = pLex->PhonemeGetUnsort(bPhoneTemp);
      PLEXENGLISHPHONE pe = plp ? MLexiconEnglishPhoneGet(plp->bEnglishPhone) : NULL;
      if (pe) // BUGFIX - Use pitch even for unvoiced  && (pe->dwCategory & PIC_VOICED))
         fUsePitch = TRUE;
   }

   // figure out cache size
   DWORD dwMaxUnitOptionFirst, dwMaxUnitOptionSecond;
   UnitCacheSize (iTTSQuality, &dwMaxUnitOptionFirst, &dwMaxUnitOptionSecond);
   DWORD dwMaxUnitOptionFirstHalf = max(dwMaxUnitOptionFirst/2, 1);

   // potentially reduce the size of the list
   // only do this if not the best quality
   // must be first or last time slice in phoneme
   CListFixed lTTPAReduced;
   DWORD dwTimeSlice = dwTime % TTSDEMIPHONES;
   if (pl && (iTTSQuality <= 2) && (TTSDEMIPHONES >= 2) &&
      (dwNum >= dwMaxUnitOptionFirstHalf) && // if don't have enough units in the first place this pass won't matter
      (!dwTimeSlice || (dwTimeSlice == (TTSDEMIPHONES-1)) ) ) {

      lTTPAReduced.Init (sizeof(PCMTTSTriPhoneAudio));
      lTTPAReduced.Required (dwNum);

      BYTE bGroupThis = pbTPhoneGroup[dwTimeSlice ? 1 : -1];

      BYTE bCompare;
      for (i = 0; i < dwNum; i++) {
         if (dwTimeSlice)
            bCompare = (BYTE) (pptp[i]->m_awTriPhone[0] >> 8); // right triphone
         else
            bCompare = (BYTE) pptp[i]->m_awTriPhone[0]; // left triphone
         
         if (bCompare == bGroupThis)
            lTTPAReduced.Add (&pptp[i]);
      } // i

      // if there are enough entries after this trim down, then use those
      // and speed up the whole process
      if (lTTPAReduced.Num() >= dwMaxUnitOptionFirstHalf) {
         pl = &lTTPAReduced;
         pptp = (PCMTTSTriPhoneAudio*) pl->Get(0);
         dwNum = pl->Num();
      }

   }

   // DWORD j;
   // loop over all the units
   // UNITOPTION uo;
   // PCMTTSTriPhoneAudio ptNew;
   PPHONEHYP pPPHLast = (PPHONEHYP) pmemFrom->p;
   DWORD dwNumPPHLast = (DWORD)pmemFrom->m_dwCurPosn / sizeof(PHONEHYP);

   // multithreaded callback
   EMTCERRORCACHECALC em;

   // limit TTS quality
   iTTSQuality = max(iTTSQuality, 0);
   iTTSQuality = min(iTTSQuality, NUMJOINQUALITY-1);

   DWORD dwPass;
   for (dwPass = (iTTSQuality >= QUICKJOINTESTQUALITYDELTA) ? 0 : 1; dwPass < 2; dwPass++) {
      // clear the list
      plUNITOPTION->Init (sizeof(UNITOPTION));

      // fill multithreaded structure
      memset (&em, 0, sizeof(em));
      em.dwType = 9;
      em.dwTime = dwTime;
      em.pptp = pptp;
      em.plUNITOPTION = plUNITOPTION;
      em.fEnergy = fEnergy;
      em.fAccent = fAccent;
      em.dwDurationX = dwDuration;
      em.dwFuncWordGroup = dwFuncWordGroup;
      em.fEnergyPrev = fEnergyPrev;
      em.fUsePitch = fUsePitch;
      em.paiPitchF0Extra = paiPitchF0Extra;
      em.paiPitchF0Fuji = paiPitchF0Fuji;
      em.pdwWord = pdwWord;
      em.pbWordSylPos = pbWordSylPos;
      em.pbPhone = pbPhone;
      em.pbTPhoneNoStress = pbTPhoneNoStress;
      em.pbTPhoneGroup = pbTPhoneGroup;
      em.bSilence = bSilence;
      em.pmemFrom = pmemFrom;
      em.iTTSQuality = iTTSQuality - (int)QUICKJOINTESTQUALITYDELTA;   // always do -1 here
      em.fDisablePCM = fDisablePCM;
      em.dwPass = (iTTSQuality >= QUICKJOINTESTQUALITYDELTA) ? dwPass : 0;  // if very low TTS quality then treat as first pass

      ThreadLoop (0, dwNum, 1, &em, sizeof(em), NULL);

   #if 0 // replaced by multithreaded callback
      for (j = 0; j < dwNum; j++) {
         memset (&uo, 0, sizeof(uo));

         if (!pptp) {
            // if silence (or unknown phoneme) then just add and dont calc score
            // add empty UO
            plUNITOPTION->Add (&uo);
            continue;
         }
         ptNew = pptp[j];

         DWORD dwLastPhone = 0;


         double fScoreCalcWeightSelf = ScoreCalcWeightSelf (fEnergy, dwDuration);
         double fWeightBoundary = ScoreCalcWeightBoundary (fEnergyPrev, fEnergy);
            //dwDurationPrev, dwDuration,
            //(dwTime ? pbWordPos[(dwTime-1)/TTSDEMIPHONES] : 0),
            //pbWordPos[dwTime/TTSDEMIPHONES] );

         // can precalc this part
         double fSelfA = ScoreCalcSelfA (dwTime, ptNew, NULL, fUsePitch, iPitchLeft, iPitchCenter, iPitchRight, dwDuration, fEnergy,
            pdwWord, pbWordPos, pbPhone, pbTPhoneNoStress, pbTPhoneGroup, bSilence, &dwLastPhone, NULL);
         
         // loop over all existing hypothesis and see which ones would have the best score
         DWORD k;
         double fScoreBest = 0;
         double fScore;
         DWORD dwBest = (DWORD)-1;
         for (k = 0; k < dwNumPPHLast; k++) {
            fScore = pPPHLast[k].fScore + fSelfA * fScoreCalcWeightSelf;

            // BUGFIX - Do ScoreCalcBoundary() first because faster than ScoreCalcSelfB, and produces larger values
            if (pUnitKpTPA)
               dwLastPhone = MAKELONG (
                  pUnitKpTPA->m_wOrigPhone,
                  pUnitKpTPA->m_wOrigWave);
            else
               dwLastPhone = (DWORD)-1;

            double fBoundary = ScoreCalcBoundary (dwTime, ptNew, pdwWord, pbWordPos, pbPhone, pbTPhoneNoStress,
               pbTPhoneGroup, bSilence, &dwLastPhone, NULL, NULL);
            _ASSERTE (fBoundary < 10000);

            fScore += fWeightBoundary * fBoundary;

            // BUGFIX - fast eliminate
            if ((dwBest != (DWORD)-1) && (fScore >= fScoreBest))
               continue;



            // BUGFIX - Because fSelf depends on hypothesized last triphone, moving ScoreCalcSelf() into
            // this loop
            double fSelfB = ScoreCalcSelfB (dwTime, ptNew, pUnitKpTPA, fUsePitch, iPitchLeft, iPitchCenter, iPitchRight, dwDuration, fEnergy,
               pdwWord, pbWordPos, pbPhone, pbTPhoneNoStress, pbTPhoneGroup, bSilence, &dwLastPhone, NULL);
                     // NOTE: Exact value for dwLastPhone doesn't matter for ScoreCalcSelf
            fScore += fSelfB * fScoreCalcWeightSelf;


            // if this is better than the best score, then remember
            if ((dwBest == (DWORD)-1) || (fScore < fScoreBest)) {
               dwBest = k;
               fScoreBest = fScore;
            }
         } // k



         // remember the best of all possible scores
         uo.fScore = (dwBest != (DWORD)-1) ? fScoreBest : 1000000000000;

         // add this
         uo.pTPA = ptNew;
         plUNITOPTION->Add (&uo);
      } // j
   #endif // 0 - replaced by multithreaded callback

   #if 0 // def _DEBUG
      WCHAR szTemp[64];
      swprintf (szTemp, L"\r\nIn GenerateUNITOPTIONe=%d\r\n\tWeightCalc time=%d",
         (int)dwTime, (int)(GetTickCount() - dwStartTime));
      OutputDebugStringW (szTemp);
      dwStartTime = GetTickCount();
   #endif

      // need to sort the list
      pUnit = (PUNITOPTION)plUNITOPTION->Get(0);
      qsort (pUnit, plUNITOPTION->Num(), sizeof(UNITOPTION), UNITOPTIONSort);
      
      DWORD dwMaxUnitOption = dwPass ? dwMaxUnitOptionSecond : dwMaxUnitOptionFirst;

      double fBestScore = plUNITOPTION->Num() ? pUnit[0].fScore : 0.0;
      for (i = 0; i < min(dwMaxUnitOption, plUNITOPTION->Num()); i++)
         if (pUnit[i].fScore > fBestScore + HYPAUTOCULL) // BUGFIX - Was MAXBORDERERROR
            break;
            // NOTE: +MAXBORDERERROR used so that on large voice, i hits the border at between 50 and 100,
            // with dwMaxUnitOption currently being 50. On smaller voices, hits the border about
            // half that value

   #if 0 // def _DEBUG
      WCHAR szTemp[128];
      swprintf (szTemp, L"\r\nUnitOption, best=%d, at MAXUNITOPTION=%d, cutoff=%d",
         (int)dwBestScore,
         (plUNITOPTION->Num() >= dwMaxUnitOption) ? (int)pUnit[dwMaxUnitOption-1].dwScore : -1,
         (int) i);
      OutputDebugStringW (szTemp);
   #endif

      // if there are too many units then cut off
      plUNITOPTION->Truncate (min(i, dwMaxUnitOption));
      pUnit = (PUNITOPTION)plUNITOPTION->Get(0);
      

      // remember this for next time around
      if (!dwPass) {
         lPCMTTSTriPhoneAudio.Init (sizeof(PCMTTSTriPhoneAudio));
         lPCMTTSTriPhoneAudio.Required (plUNITOPTION->Num());
         for (i = 0; i < plUNITOPTION->Num(); i++)
            if (pUnit[i].pTPA)
               lPCMTTSTriPhoneAudio.Add (&pUnit[i].pTPA);

         if (lPCMTTSTriPhoneAudio.Num()) {
            pptp = (PCMTTSTriPhoneAudio*) lPCMTTSTriPhoneAudio.Get(0);
            dwNum = lPCMTTSTriPhoneAudio.Num();
         }
         else {
            // silence
            pptp = NULL;
            dwNum = 1;
         }
      } // !dwPass

      // figure out which current plUNITOPTIONLast are actually used
      CMem memLastUsed;
      DWORD dwNeed = plUNITOPTIONLast->Num() * (sizeof(BOOL) + sizeof(BOOL) + 4 * sizeof(fp));
      if (!memLastUsed.Required (dwNeed))
         return;  // shouldnt happen
      BOOL *pafLastUsed = (BOOL*)memLastUsed.p;
      BOOL *pafIsValidEnergy = pafLastUsed + plUNITOPTIONLast->Num();
      fp *pafLastEnergy = (fp*) (pafIsValidEnergy + plUNITOPTIONLast->Num());
      memset (pafLastUsed, 0, dwNeed);
      for (i = 0; i < dwNumPPHLast; i++, pPPHLast++)
         pafLastUsed[pPPHLast->dwPreviousIndex] = TRUE;
      pPPHLast = (PPHONEHYP) pmemFrom->p; // since modified in loop

      // will need to calculate quality of unit intersections between old and new
      // and store away
      //SRFEATURESMALL srfSilence;
      //memset (&srfSilence, 0, sizeof(srfSilence));
      //for (i = 0; i < SRDATAPOINTSSMALL; i++) {
      //   srfSilence.acNoiseEnergy[i] = SRNOISEFLOOR;
      //   srfSilence.acVoiceEnergy[i] = SRABSOLUTESILENCE;
      //} // i
      //fp fSilenceEnergy = SRFEATUREEnergy (&srfSilence, FALSE);
      //SRFEATUREScale (&srfSilence, PHONESAMPLENORMALIZED / fSilenceEnergy);
      dwNeed = plUNITOPTION->Num() * plUNITOPTIONLast->Num() * sizeof(fp);
      if (!pMemInter->Required (dwNeed))
         return;  // shouldnt happen
      fp *paf = (fp*)pMemInter->p;
      memset (paf, 0, dwNeed);   // default to 0
      // PSRFEATURESMALL apSRF[4];
      // fp afEnergy[4], afRealEnergy[4], afDB[4];
      // PUNITOPTION pUnitLast = (PUNITOPTION)plUNITOPTIONLast->Get(0);
      // DWORD k;
      // double fError;
      // CListFixed lTPA, lTPAScore;

      // EMTCERRORCACHECALC em;
      memset (&em, 0, sizeof(em));
      em.dwType = 10;
      em.pUnit = pUnit;
      em.dwTime = dwTime;
      em.plUNITOPTIONLast = plUNITOPTIONLast;
      em.iTTSQuality = iTTSQuality - (dwPass ? 0 : QUICKJOINTESTQUALITYDELTA);
      em.paf = paf;
      em.pafLastUsed = pafLastUsed;
      em.pafIsValidEnergy = pafIsValidEnergy;
      em.pafLastEnergy = pafLastEnergy;
      em.dwPass = dwPass;  // pass in proper pass number
      em.pmemFrom = pmemFrom;

      ThreadLoop (0, plUNITOPTION->Num(), 1, &em, sizeof(em), NULL);



   #if 0 // moved into multihreaded
      for (i = 0; i < plUNITOPTION->Num(); i++) {
         DWORD dwStartIndex = (dwTime % TTSDEMIPHONES)*2;
         // determine the comparison and energies
         if (pUnit[i].pTPA) {
            // remember two bits from start
            apSRF[0] = &pUnit[i].pTPA->m_aSRFeatBoundary[dwStartIndex+0];
            apSRF[1] = &pUnit[i].pTPA->m_aSRFeatBoundary[dwStartIndex+1];
            afEnergy[0] = pUnit[i].pTPA->m_afSRFeatBoundary[dwStartIndex+0];
            afEnergy[1] = pUnit[i].pTPA->m_afSRFeatBoundary[dwStartIndex+1];
         }
         else {
            // this is silence, so nothing
            apSRF[0] = apSRF[1] = &srfSilence;
            afEnergy[0] = afEnergy[1] = fSilenceEnergy;
         }

         // calculate the real energy for better tests. should
         // be close to PHONESAMPLENORMALIZED
         afRealEnergy[0] = SRFEATUREEnergy (apSRF[0], FALSE);
         afRealEnergy[1] = SRFEATUREEnergy (apSRF[1], FALSE);
         afDB[0] = AmplitudeToDb(afEnergy[0] / THEORETICALMAXENERGY * (fp)0x8000);
         afDB[1] = AmplitudeToDb(afEnergy[1] / THEORETICALMAXENERGY * (fp)0x8000);

         lTPA.Init (sizeof(PCMTTSTriPhoneAudio));
         lTPAScore.Init (sizeof(fp));

         // loop over the previous options
         for (j = 0; j < plUNITOPTIONLast->Num(); j++, paf++) {
            // if not used then skip
            if (!pafLastUsed[j])
               continue;

            // if these are the same wave then error = 0
            if ((dwTime % TTSDEMIPHONES) && (pUnit[i].pTPA == pUnitLast[j].pTPA)) {
               // optimiziation since will match against itself, so should be no error
               paf[0] = 0;
               continue;
            }
            else if (!(dwTime % TTSDEMIPHONES) && pUnit[i].pTPA && pUnitLast[j].pTPA &&
               (pUnit[i].pTPA->m_wOrigWave == pUnitLast[j].pTPA->m_wOrigWave) &&
               (pUnit[i].pTPA->m_wOrigPhone == pUnitLast[j].pTPA->m_wOrigPhone+1) ) {

               // optimizations so score for contiguous is 0
               paf[0] = 0;
               continue;
            }

            if (pUnit[i].pTPA && pUnit[i].pTPA->ConnectErrorCacheGet(pUnitLast[j].pTPA, (dwTime % TTSDEMIPHONES), paf))
               continue;   // already cached

            DWORD dwPrevIndex = (dwTime % TTSDEMIPHONES) ? dwStartIndex : (TTSDEMIPHONES * 2);


            // determine comparison and energies
            if (pUnitLast[j].pTPA) {
               // remember two bits from start
               apSRF[2] = &pUnitLast[j].pTPA->m_aSRFeatBoundary[dwPrevIndex+0];
               apSRF[3] = &pUnitLast[j].pTPA->m_aSRFeatBoundary[dwPrevIndex+1];
               afEnergy[2] = pUnitLast[j].pTPA->m_afSRFeatBoundary[dwPrevIndex+0];
               afEnergy[3] = pUnitLast[j].pTPA->m_afSRFeatBoundary[dwPrevIndex+1];
            }
            else {
               // this is silence, so nothing
               apSRF[2] = apSRF[3] = &srfSilence;
               afEnergy[2] = afEnergy[3] = fSilenceEnergy;
            }

            // calculate the real energy for better tests. should
            // be close to PHONESAMPLENORMALIZED
            // BUGFIX - Was testing for i, should be testing for valid energy
            if (pafIsValidEnergy[j]) {
               afRealEnergy[2] = pafLastEnergy[j*4+0];
               afRealEnergy[3] = pafLastEnergy[j*4+1];
               afDB[2] = pafLastEnergy[j*4+2];
               afDB[3] = pafLastEnergy[j*4+3];
            }
            else {
               pafLastEnergy[j*4+0] = afRealEnergy[2] = SRFEATUREEnergy (apSRF[2], FALSE);
               pafLastEnergy[j*4+1] = afRealEnergy[3] = SRFEATUREEnergy (apSRF[3], FALSE);
               pafLastEnergy[j*4+2] = afDB[2] = AmplitudeToDb(afEnergy[2] / THEORETICALMAXENERGY * (fp)0x8000);
               pafLastEnergy[j*4+3] = afDB[3] = AmplitudeToDb(afEnergy[3] / THEORETICALMAXENERGY * (fp)0x8000);
               pafIsValidEnergy[j] = TRUE;
            }

            fError = 0;

            for (k = 0; k < 2; k++) {
               // compare the two
               DWORD dwA = k+0;
               DWORD dwB = k+2;
   #if 0 // def _DEBUG // to test and make sure energy calculation is right
               afRealEnergy[dwA] = SRFEATUREEnergy(apSRF[dwA]);
               afRealEnergy[dwB] = SRFEATUREEnergy(apSRF[dwB]);
   #endif
               fp fCompare = SRFEATURECompare (apSRF[dwA], afRealEnergy[dwA], apSRF[dwB], afRealEnergy[dwB]);
               
               // BUGFIX - Just in case decides to get really wild, which does if very large energies
               fCompare = max (fCompare, 0.0);
               fCompare = min (fCompare, 1.0);
               
               // find the higher number of db
               fp fMaxDb = max(afDB[dwA], afDB[dwB]);
               fMaxDb = 1 + fMaxDb/((fp)-SRNOISEFLOOR);   // so that at -60 decibles max, fCompare won't count for anything
               fMaxDb = max(fMaxDb, 0);
               fMaxDb = min(fMaxDb, 1);

               // total error
               fCompare = (fCompare * SRCOMPAREWEIGHT + fabs(afDB[dwA] - afDB[dwB])) * fMaxDb;
                  // penalty for difference in volume only counts half
               fError += fCompare;
            } // k

            // fit it into a byte
            fError *= BORDERDBSCALE;   // so average these two
            //fError = max(fError, 0);
            //fError = min(fError, 255);
            paf[0] = fError;

            // store in cache
            if (pUnit[i].pTPA) {
               lTPA.Add (&pUnitLast[j].pTPA);
               lTPAScore.Add (&paf[0]);
               // pUnit[i].pTPA->ConnectErrorCacheSet(pUnitLast[j].pTPA, (dwTime % TTSDEMIPHONES), paf[0]);
            }
         } // j

         // add them all at once
         if (pUnit[i].pTPA)
            pUnit[i].pTPA->ConnectErrorCacheSet((PCMTTSTriPhoneAudio*)lTPA.Get(0), (fp*)lTPAScore.Get(0), lTPA.Num(), (dwTime % TTSDEMIPHONES) );
      } // i
   #endif // 0
   } // dwPass

#if 0 // def _DEBUG
   swprintf (szTemp, L"\r\n\tConnectErrortime=%d", (int)(GetTickCount() - dwStartTime));
   OutputDebugStringW (szTemp);
   dwStartTime = GetTickCount();
#endif


   // done
}


/*************************************************************************************
CMTTS::HypExpand - Expand all the hypothesis in one list into another, using the
new phoneme.

inputs
   DWORD       dwTime - Time index. Important part is dwTime % TTSDEMIPHONES
   PCMem       pmemFrom - Memory with list of PHONEHYP. m_dwCurPosn = memory where last one is
   PCMem       pmemTo - Meory to will with PHONEHYP. m_dwCurPosn = memory where to add to
   DWORD          dwPhoneValid - Number of valid phonemes including and after pbPhone[0]. Thus, is pbPhone[0]
                  and pbPhone[1] are valid, this is 2.
   BYTE        *pbPhone - Phoneme to use, also need [-1] and [1] to be valid
   BYTE        *pbWordPos - Word position (start, end or word)
                  0x01 = start of word, WORDPOS_WORDSTART
                  0x02 = end of word, WORDPOS_WORDEND
                  0x04 = start of syllable, WORDPOS_SYLSTART
                  0x08 = end of syllable, WORDPOS_SYLEND
                  0x10 = plosive, WORDPOS_EXTRA_PLOSIVE
                  0x20 = voiced, WORDPOS_EXTRA_VOICED
   BYTE        *pbTPhoneGroup - Phoneme into group of 16, also need [-1] and [1] to be valid
   BYTE        *pbTPhoneNoStress - Phoneme without stress, also need [-1] and [1] to be valid
         // NOTE: For optimization reasons this function assumes NUMTRIPHONEGROUP == 3
   DWORD       *pdwWord - Word number using, or -1 if not known word
   int         *paiPitchF0Extra - Array of TPAPITCHPOINTS F0Extra pitch points, log()/log(2) * 1000
                  for the ENTIRE phoneme (not just the demiphone)
   int         *paiPitchF0Fuji - Like paiPitchF0Extra, but fuji
   // DWORD       dwDurationPrev - Duration of the previous half phone, or 0 if unknown
   DWORD       dwDuration - Duration of the phone, or 0 if unknown
   fp          fEnergy - Energy of the phone, so can select units based on energy
   fp          fEnergyPrev - Energy of the previous phone, for unit weighting
   fp          fAccent - How much of an accent looking for, from -1.0 to 1.0
   BYTE        bSilence - What phoneme is silence
   DWORD       dwFuncWordGroup - Which function-word-group this is in, 0..NUMFUNCWORDGROUP(inclusive)
   fp          fEnergyAverage - Average energy over all requested phonemes. Used for score calc
   int               iTTSQuality - TTS quality. 0 for fast, 1 for normal, 2 for slow, 3 for best, 4 for multipass
   BOOL        fDisablePCM - Set to TRUE if PSOLA is temporarily disabled

   PCListFixed plUNITOPTIONLast - Unit options from the last list. If there wasn't one, then
               leave this with 0 elements and it will automatically be filled with silence
   PCListFixed plUNITOPTION - Filled with the current unit options
returns
   none
*/

void CMTTS::HypExpand (DWORD dwTime, PCMem pmemFrom, PCMem pmemTo, DWORD dwPhoneValid, BYTE *pbPhone,
                       BYTE *pbWordSylPos, BYTE *pbTPhoneGroup, BYTE *pbTPhoneNoStress,
                       DWORD *pdwWord,
                       int* paiPitchF0Extra, int *paiPitchF0Fuji,
                       /*DWORD dwDurationPrev,*/ DWORD dwDuration, fp fEnergy, fp fEnergyPrev,
                       fp fAccent, BYTE bSilence,
                       DWORD dwFuncWordGroup,
                       fp fEnergyAverage, int iTTSQuality, BOOL fDisablePCM,
                       PCListFixed plUNITOPTIONLast, PCListFixed plUNITOPTION)
{
   // make sure there's a last
   if (!plUNITOPTIONLast->Num()) {
      // fill with silence
      UNITOPTION uo;
      memset (&uo, 0, sizeof(uo));
      plUNITOPTIONLast->Init (sizeof(uo), &uo, 1);
   }

#if 0 // def _DEBUG
   DWORD dwTimeStart = GetTickCount();
   WCHAR szTemp[64];
#endif

   // fill in this list
   CMem memSmooth;
   GenerateUNITOPTION (dwTime, pmemFrom, dwPhoneValid, pbPhone, pbWordSylPos, pbTPhoneGroup, pbTPhoneNoStress, pdwWord,
      paiPitchF0Extra, paiPitchF0Fuji,
      /*dwDurationPrev,*/ dwDuration, fEnergy, fEnergyPrev, fAccent,
      bSilence, dwFuncWordGroup, fEnergyAverage,
      plUNITOPTION, plUNITOPTIONLast,
      &memSmooth, iTTSQuality, fDisablePCM);
   fp *pafSmooth = (fp*)memSmooth.p;

#if 0 // def _DEBUG
   swprintf (szTemp, L"\r\nHyp time: %d\r\n\tGenerateUNITOPTION time = %d",
      (int)dwTime, (int)(GetTickCount() - dwTimeStart));
   OutputDebugStringW (szTemp);
#endif

   // determine which phoneme units need to look through
   PCMLexicon pLex = Lexicon();
   BYTE bPhoneTemp = PhonemeBackoff(*pbPhone, pLex, bSilence);
   //PCListFixed pl = (bPhoneTemp != bSilence) ? m_palPCMTTSTriPhoneAudio[bPhoneTemp] : NULL;
   //PCMTTSTriPhoneAudio *pptp;
   DWORD dwNum;
   //if (!pl || !pl->Num()) {
   //   // serious problem because have selected phoneme but no recordsing, therefore
   //   // just set to NULL pptp
   //   pptp = NULL;
   //   dwNum = 1;
   //}
   //else {
   //   pptp = (PCMTTSTriPhoneAudio*) pl->Get(0);
   //   dwNum = pl->Num();
   //}
   dwNum = plUNITOPTION->Num();

   // ignore pitch
   BOOL fUsePitch = FALSE;
   if ((bPhoneTemp != bSilence) && pLex) {   // BUGFIX - Was check for pl, but using pl anymore
      PLEXPHONE plp = pLex->PhonemeGetUnsort(bPhoneTemp);
      PLEXENGLISHPHONE pe = plp ? MLexiconEnglishPhoneGet(plp->bEnglishPhone) : NULL;
      if (pe) // BUGFIX - Use pitch even for unvoiced && (pe->dwCategory & PIC_VOICED))
         fUsePitch = TRUE;
   }

   // loop over existing hypothesis and expand

#if 0 // def _DEBUG
   dwTimeStart = GetTickCount();
#endif

   // loop over existing unit options
   // can reverse the i and j loops (and make faster) because the context for new phones
   // only depends on the phone immediately before
   double fCutOff = 1000000000000;

   // modulo index so each thread has a bit of the top and will
   // find a cutoff faster
   CMem memModulo;
   if (!memModulo.Required (sizeof(DWORD)*dwNum))
      return;
   DWORD *padwModulo = (DWORD*)memModulo.p;
   DWORD dwThreads = HowManyProcessors();
   DWORD dwLoops = dwThreads; // since passing 1 into ThreadLoop()
   DWORD dwLoopSize = (dwNum + (dwLoops-1)) / dwLoops;
   DWORD dwCur = 0, dwOffset;
   DWORD i;
   for (dwOffset = 0; dwOffset < dwLoopSize; dwOffset++)
      for (i = 0; i < dwLoops; i++) {
         // figure out where it should be going to
         DWORD dwIndex = i * dwLoopSize + dwOffset;
         if (dwIndex >= dwNum)
            continue;   // too large

         // if have reached maximum then stop
         if (dwCur >= dwNum)
            break;

         // write it in
         padwModulo[dwIndex] = dwCur;
         dwCur++;
      }

   EMTBEAMSEARCH em;
   memset (&em, 0, sizeof(em));
   em.dwType = 20;
   em.plUNITOPTION = plUNITOPTION;
   em.pmemFrom = pmemFrom;
   em.pmemTo = pmemTo;
   em.dwTime = dwTime;
   em.pdwWord = pdwWord;
   em.pbWordSylPos = pbWordSylPos;
   em.pbPhone = pbPhone;
   em.pbTPhoneNoStress = pbTPhoneNoStress;
   em.pbTPhoneGroup = pbTPhoneGroup;
   em.bSilence = bSilence;
   em.pafSmooth = pafSmooth;
   em.plUNITOPTIONLast = plUNITOPTIONLast;
   em.fEnergy = fEnergy;
   em.fAccent = fAccent;
   em.dwDurationX = dwDuration;
   em.dwFuncWordGroup = dwFuncWordGroup;
   em.fEnergyPrev = fEnergyPrev;
   em.fUsePitch = fUsePitch;
   em.paiPitchF0Extra = paiPitchF0Extra;
   em.paiPitchF0Fuji = paiPitchF0Fuji;
   em.fEnergyAverage = fEnergyAverage;
   em.dwNum = dwNum;
   em.pfCutOff = &fCutOff;
   em.padwModulo = padwModulo;
   em.fDisablePCM = fDisablePCM;

   ThreadLoop (0, dwNum, 1, &em, sizeof(em), NULL);

#if 0 // replaced by multithreaded
   PUNITOPTION puo;
   puo = (PUNITOPTION) plUNITOPTION->Get(0);
   PPHONEHYP pahFrom = (PPHONEHYP)pmemFrom->p;
   DWORD dwNumFrom = (DWORD)pmemFrom->m_dwCurPosn / sizeof(PHONEHYP);
   DWORD i, j;
   // fp fScoreOrig, fScore;
   fp fBestNewScore = 1000000000;
   double fCutOff = 1000000000000;
   PHONEHYP ph, phBest;
   DWORD dwBest;
   double fBestScore;
   PCMTTSTriPhoneAudio ptNew;
   for (j = 0; j < dwNum; j++, puo++) {

      // remember the best
      dwBest = (DWORD)-1;
      fBestScore = 0;

      // loop over all existing hypothesis and find the one that results
      // in the best final score
      pahFrom = (PPHONEHYP)pmemFrom->p;
      for (i = 0; i < dwNumFrom; i++, pahFrom++) {
         // fill in new info
         memcpy (ph.aPCMTTSTriPhoneAudio, &pahFrom->aPCMTTSTriPhoneAudio[1], (PHONEHISTORY-1) * sizeof(PCMTTSTriPhoneAudio));
         ph.fScore = pahFrom->fScore;

         if (!puo->pTPA) {
         //if (!pptp) {
            // if silence (or unknown phoneme) then just add and dont calc score
            ptNew = NULL;
            ph.fEndSilence = TRUE;
            goto addit;
         }
         //ptNew = pptp[j];
         ptNew = puo->pTPA;

         // determine the boundary
         double fBoundaryTheoretical = ScoreCalcBoundary (dwTime, ptNew, pdwWord, pbWordPos, pbPhone,
            pbTPhoneNoStress, pbTPhoneGroup, bSilence,
            NULL, pahFrom, ph.aPCMTTSTriPhoneAudio);
         _ASSERTE (fBoundaryTheoretical < 10000);

         // calculate the blend
         BOOL fWordStart = (*pbWordPos & 0x01);
         BOOL fSylStart = (*pbWordPos & 0x04);
         double fBoundaryCalc = pafSmooth[plUNITOPTIONLast->Num() * j + pahFrom->dwPreviousIndex];
         //fBoundaryCalc = ATSTARTOFPHONE ? CROSSWORDLESSPENALTYLEFT(fBoundaryCalc) : fBoundaryCalc;
            // BUGFIX - If not at the start of the phoneme, then CROSSWORDLESSPENALTYLEFT

         double fBoundary = SCORECALCWEIGHTTHEORETICALBOUNDARY * fBoundaryTheoretical +
            (1.0 - SCORECALCWEIGHTTHEORETICALBOUNDARY) * fBoundaryCalc;
         double fWeightBoundary = ScoreCalcWeightBoundary (fEnergyPrev, fEnergy);
            //dwDurationPrev, dwDuration,
            //(dwTime ? pbWordPos[(dwTime-1)/TTSDEMIPHONES] : 0),
            //pbWordPos[dwTime/TTSDEMIPHONES] );

         ph.fScore += fWeightBoundary * fBoundary;

         // BUGFIX - partial check to see if eliminate early
         if (ph.fScore > fCutOff)
            continue;
         if ((dwBest != (DWORD)-1) && (ph.fScore >= phBest.fScore))
            continue;
        


         // calculate weight for self
         double fScoreCalcWeightSelf = ScoreCalcWeightSelf (fEnergy, dwDuration);
         double fSelfA =
            ScoreCalcSelfA (dwTime, ptNew, NULL /*ph.aPCMTTSTriPhoneAudio[PHONEHISTORY-2]*/, fUsePitch, iPitchLeft, iPitchCenter, iPitchRight, dwDuration, fEnergy,
               pdwWord, pbWordPos, pbPhone, pbTPhoneNoStress, pbTPhoneGroup, bSilence, NULL, pahFrom);
         ph.fScore += fSelfA * fScoreCalcWeightSelf;
         if (ph.fScore > fCutOff)
            continue;
         if ((dwBest != (DWORD)-1) && (ph.fScore >= phBest.fScore))
            continue;

         // second part
         double fSelfB =
            ScoreCalcSelfB (dwTime, ptNew, ph.aPCMTTSTriPhoneAudio[PHONEHISTORY-2], fUsePitch, iPitchLeft, iPitchCenter, iPitchRight, dwDuration, fEnergy,
               pdwWord, pbWordPos, pbPhone, pbTPhoneNoStress, pbTPhoneGroup, bSilence, NULL, pahFrom);
         ph.fScore += fSelfB * fScoreCalcWeightSelf;



         ph.fEndSilence = ATENDOFPHONE && (ptNew->m_bPhoneRight == bSilence);
            // BUGFIX - endsielnce only if at end of this phone


addit:
         // if this score isn't nearly as good as the best score then don't bother
         if (ph.fScore > fCutOff)
            continue;

         // if this isn't the best score then don't bother
         if ((dwBest != (DWORD)-1) && (ph.fScore >= phBest.fScore))
            continue;

         // write this
         phBest = ph;
         phBest.aPCMTTSTriPhoneAudio[PHONEHISTORY-1] = ptNew;
         phBest.dwPreviousIndex = j; // remember the location in this
         dwBest = j;
         fCutOff = phBest.fScore + HYPAUTOCULL;
      } // i

      // if best then keep it
      if (dwBest != (DWORD)-1) {
         // make sure enough memory
         if (!pmemTo->Required (pmemTo->m_dwCurPosn + dwNum * sizeof(PHONEHYP)))
            return;  // error
         memcpy ((PBYTE)pmemTo->p + pmemTo->m_dwCurPosn, &phBest, sizeof(phBest));
         pmemTo->m_dwCurPosn += sizeof(phBest);
      }
   } // j, unit options
#endif // 0, replaced by multithreaded

#if 0 // def _DEBUG
   swprintf (szTemp, L"\r\n\tBeam search time = %d",
      (int)(GetTickCount() - dwTimeStart));
   OutputDebugStringW (szTemp);
#endif

   return;
}



/*************************************************************************************
CMTTS::HypKeepBestScore - Searches through all the hypothesis and figure out which
has the highest score. Then, discard all hypothesis that don't begin with the
best scoring one's first word.

In the process, this also sorts by the triphone memory and discards duplicates
with a worse score.

inputs
   PCMem       pmem - Memory with list of PHONEHYP. m_dwCurPosn = memory where last one is
   fp          fEnergyAverage - Average energy over all requested phonemes. Used for score calc
returns
   PCMTTSTriPhoneAudio - Best triphone.
*/
static int _cdecl PHONEHYPSort1 (const void *elem1, const void *elem2)
{
   PHONEHYP *pdw1, *pdw2;
   pdw1 = (PHONEHYP*) elem1;
   pdw2 = (PHONEHYP*) elem2;

   // BUGFIX - Since have demiphones now, need to do memcompare of the last TTSDEMIPHONES elements
   int iRet = memcmp (
      pdw1->aPCMTTSTriPhoneAudio + (PHONEHISTORY-TTSDEMIPHONES),
      pdw2->aPCMTTSTriPhoneAudio + (PHONEHISTORY-TTSDEMIPHONES),
      sizeof(pdw1->aPCMTTSTriPhoneAudio[0]) * TTSDEMIPHONES);
   if (iRet)
      return iRet;
   //if (pdw1->aPCMTTSTriPhoneAudio[PHONEHISTORY-1] < pdw2->aPCMTTSTriPhoneAudio[PHONEHISTORY-1])
   //   return 1;
   //else if (pdw1->aPCMTTSTriPhoneAudio[PHONEHISTORY-1] > pdw2->aPCMTTSTriPhoneAudio[PHONEHISTORY-1])
   //   return -1;

   // BUGFIX - The following was a bad way to do this
   //int iRet = memcmp (pdw1->aPCMTTSTriPhoneAudio, pdw2->aPCMTTSTriPhoneAudio, sizeof(pdw1->aPCMTTSTriPhoneAudio));
   //if (iRet)
   //   return iRet;

   // else by score
   if (pdw1->fScoreWithEnergy > pdw2->fScoreWithEnergy)
      return 1;
   else if (pdw1->fScoreWithEnergy < pdw2->fScoreWithEnergy)
      return -1;
   else
      return 0;
   // return (int)pdw1->dwScore - (int)pdw2->dwScore;
}

PCMTTSTriPhoneAudio CMTTS::HypKeepBestScore (PCMem pMem, fp fEnergyAverage)
{
   // find the best
   DWORD dwBest = -1;
   fp fBestScore = 1000000000000;
   DWORD dwNum = (DWORD)pMem->m_dwCurPosn / sizeof(PHONEHYP);
   PPHONEHYP pph = (PPHONEHYP) pMem->p;
   DWORD i;
   for (i = 0; i < dwNum; i++, pph++)
      if (pph->fScoreWithEnergy < fBestScore) {
         dwBest = i;
         fBestScore = pph->fScoreWithEnergy;
      }

   // what's the return for this
   if (dwBest == -1)
      return NULL;   // shouldnt happen
   pph = (PPHONEHYP) pMem->p;
   PCMTTSTriPhoneAudio pRet = pph[dwBest].aPCMTTSTriPhoneAudio[0];

   // remove all triphones that do not match the best
   DWORD j;
   fp fScoreElim = fBestScore + HYPAUTOCULL;
   for (i = 0, j = 0; i < dwNum; i++) {
      if (pph[i].aPCMTTSTriPhoneAudio[0] != pRet)
         continue;   // want to skip this

      // also, if the score is much worse than the best score then eliminate
      if (pph[i].fScoreWithEnergy > fScoreElim)
         continue;

      if (i != j)
         memcpy (pph + j, pph + i, sizeof(PHONEHYP));
      j++;
   } // i
   pMem->m_dwCurPosn = (dwNum = j) * sizeof(PHONEHYP);

   // sort by type, and then by score, so can remove duplicates
   qsort (pph, dwNum, sizeof(PHONEHYP), PHONEHYPSort1);

   // loop through and find duplicates
   DWORD k;
#ifdef _DEBUG
   DWORD dwDuplicates = 0;
#endif
   for (i = 0, j = 0; i < dwNum; /*i++*/) {
      // see how far can go until no longer have match
      for (k = i+1; k < dwNum; k++)
         // BUGFIX - Because using demiphones, need longer compare
         if (memcmp (
            pph[i].aPCMTTSTriPhoneAudio + (PHONEHISTORY-TTSDEMIPHONES),
            pph[k].aPCMTTSTriPhoneAudio + (PHONEHISTORY-TTSDEMIPHONES),
            sizeof(pph[k].aPCMTTSTriPhoneAudio[0]) * TTSDEMIPHONES))
         //if (pph[i].aPCMTTSTriPhoneAudio[PHONEHISTORY-1] != pph[k].aPCMTTSTriPhoneAudio[PHONEHISTORY-1])
               break;

      // copy over i
      if (i != j)
         memcpy (pph + j, pph + i, sizeof(PHONEHYP));
      j++;

#ifdef _DEBUG
      if (k != i+1)
         dwDuplicates++;
#endif

      // move to next
      i = k;
   } // i
   pMem->m_dwCurPosn = (dwNum = j) * sizeof(PHONEHYP);

   return pRet;
}

/*************************************************************************************
CMTTS::HypEliminateLowScores - Eliminates and low scores.
In the process, the list will be sorted by highest score first.


inputs
   PCMem       pmem - Memory with list of PHONEHYP. m_dwCurPosn = memory where last one is
   DWORD       dwMaxNum - Maximum number of hypothesis that can sit around
returns
   none
*/
static int _cdecl PHONEHYPSort2 (const void *elem1, const void *elem2)
{
   PHONEHYP *pdw1, *pdw2;
   pdw1 = (PHONEHYP*) elem1;
   pdw2 = (PHONEHYP*) elem2;

   // else by score
   if (pdw1->fScoreWithEnergy > pdw2->fScoreWithEnergy)
      return 1;
   else if (pdw1->fScoreWithEnergy < pdw2->fScoreWithEnergy)
      return -1;
   else
      return 0;
   //return (int)pdw1->dwScore - (int)pdw2->dwScore;
}
void CMTTS::HypEliminateLowScores (PCMem pMem, DWORD dwMaxNum)
{
   DWORD dwNum = (DWORD)pMem->m_dwCurPosn / sizeof(PHONEHYP);
   PPHONEHYP pph = (PPHONEHYP) pMem->p;


   // NOTE: Always sort since makes next step faster
   // sort by score so can keep best scores
   qsort (pph, dwNum, sizeof(PHONEHYP), PHONEHYPSort2);
      // BUGFIX - Replaced PHONEHYPSort1 with PHONEHYPSort2

   if (dwNum > dwMaxNum)
      pMem->m_dwCurPosn = dwMaxNum * sizeof(PHONEHYP);
}



/*************************************************************************************
CMTTS::SynthDetermineTriPhoneAudio - Given a list of phonemes and their duration, this
determines which units are to be used for each triphone.

inputs
   fp          fAvgPitchF0All - Average pitch. Usually pass in m_fAvgPitch, but if this voice
               is modified, then pass in a higher value
   fp          fAvgPitchF0Fuji - Fuji pitch
   DWORD       *padwPhone - Pointer to an array of phonemes. Each phoneme is the
               unsorted phone number. The high byte (<< 24) contains 0 if the phone
               is in the middle of a word, 1 on left side, 2 on right side, 3 word by itself
   DWORD       *padwWord - Word (from m_pLexWords) associated with each phoneme, or -1 if no
               known word is associated
   DWORD       *padwDur - Array of phoneme durations, in SRFEATURE (1/100th sec) units
   fp          *pafVol - Array of volumes, one per phoneme. See fVolAbsolute
   BOOL        fVolAbsoluate - If TRUE pafVol is the absolute volume to use (so can get this
               from transplanted prosody); in which case it's the CalcSREnergyRange() of
               the phoneme. If FALSE, relative volume.
   fp          *pafAccent - Array of accent values (-1 to 1), per phoneme.
   PCListVariable plPitchF0Extra - This list has one element per phoneme to indicate the
               fundamental pitch of the voice (in Hz, divided by fujisaki pitch). Each element is N * sizeof(fp),
               where N is from 0 to whatever. It indicates the number of pitch points
               stored per phoneme.
   PCListVariable plPitchF0Fuji - Like plPitchF0Extra, but fujisaki pitch in Hz
   DWORD       dwNum - Number of phonemes
   int               iTTSQuality - TTS quality. 0 for fast, 1 for normal, 2 for slow, 3 for best, 4 for multipass
   BOOL        fDisablePCM - Set to TRUE if PCM temporarily disabled
   PCListFixed plPCMTTSTriPhoneAudio - Pointer to a list that is initialized to sizeof(PCMTTSTriPhoneAudio)
               and filled with a list of triphones, one of each phone x TTSDEMIPHONES.
   fp          *pfBestScore - Filled with the acoustic score.
   PCProgressSocket pProgress - Progress
returns
   BOOl - TRUE if success
*/

#define SUPERTPAPITCHPOINTS      (4 * TPAPITCHPOINTS) // so have enough points per phoneme

BOOL CMTTS::SynthDetermineTriPhoneAudio (fp fAvgPitchF0All, fp fAvgPitchF0Fuji, DWORD *padwPhone, DWORD *padwWord,
                                         DWORD *padwDur, fp *pafVol, BOOL fVolAbsolute,
                                         fp *pafAccent,
                                         PCListVariable plPitchF0Extra, PCListVariable plPitchF0Fuji,
                                         DWORD dwNum,
                                         int iTTSQuality, BOOL fDisablePCM,
                                         PCListFixed plPCMTTSTriPhoneAudio, fp *pfBestScore,
                                         PCProgressSocket pProgress)
{
   *pfBestScore = 0; // to blank out

   // if this is a derived voice pass on down
   if (m_fIsDerived) {
      if (!m_pTTSMaster)
         return FALSE;
      return m_pTTSMaster->SynthDetermineTriPhoneAudio (fAvgPitchF0All, fAvgPitchF0Fuji,
         padwPhone, padwWord, padwDur, pafVol, fVolAbsolute, pafAccent,
         plPitchF0Extra, plPitchF0Fuji, dwNum, iTTSQuality, fDisablePCM,
         plPCMTTSTriPhoneAudio, pfBestScore, pProgress);
   }

   // get silence
   PCMLexicon pLex = Lexicon();
   if (!pLex)
      return FALSE;
   BYTE bSilence = pLex->PhonemeFindUnsort (pLex->PhonemeSilence());

   // fill in volumes
   CMem memVol;
   if (!memVol.Required (dwNum * sizeof(fp) * 2))
      return FALSE;
   fp *pafVolTheory = (fp*)memVol.p;
   fp *pafVolWant = pafVolTheory + dwNum;
   DWORD i, j;
   fp fAvgTheory = 0;
   fp fAvgWant = 0;
   for (i = 0; i < dwNum; i++) {
      // theoretical
      PCMTTSTriPhonePros ptpp = SynthDetermineTriPhonePros (
         (BYTE)padwPhone[i], padwPhone[i] >> 24,
         i ? (BYTE)padwPhone[i-1] : bSilence,
         (i+1 < dwNum) ? (BYTE)padwPhone[i+1] : bSilence);

      pafVolTheory[i] = ptpp ? ptpp->m_fEnergyAvg : 0.0;
      fAvgTheory += pafVolTheory[i];

      // what want
      if (fVolAbsolute && pafVol)
         pafVolWant[i] = pafVol[i];
      else
         pafVolWant[i] = (pafVol ? pafVol[i] : 1.0) * pafVolTheory[i];
      fAvgWant += pafVolWant[i];
   } // i

   // normalize so that fAvgWant becomes vAvgTheory
   // figure out average energy too
   fp fScale = fAvgTheory / max(fAvgWant,1.0);
   fp fEnergyAverage = 0;
   DWORD dwEnergyAverageCount = 0;
   for (i = 0; i < dwNum; i++) {
      pafVolWant[i] *= fScale;

      if ((BYTE)padwPhone[i] != bSilence) {
         fEnergyAverage += pafVolWant[i] * (fp) padwDur[i];
         dwEnergyAverageCount += padwDur[i];
      }
   }
   if (dwEnergyAverageCount)
      fEnergyAverage /= (fp)dwEnergyAverageCount;
   fEnergyAverage = max(fEnergyAverage, CLOSE);  // so have something


   // create some memory where can store information, such as extra border around
   // phonemes
   DWORD dwNewNum = dwNum+2;
   CMem mem;
   if (!mem.Required (dwNewNum * (
      (2 + NUMTRIPHONEGROUP) * sizeof(BYTE) + 2*TPAPITCHPOINTS * sizeof(int) + 2*sizeof(DWORD) + 2*SUPERTPAPITCHPOINTS*sizeof(fp) )))
      return FALSE;
   DWORD *padwNewWord = (DWORD*)mem.p;
   DWORD *padwNewFuncWordGroup = padwNewWord + dwNewNum;
   int *paiPitchF0ExtraLRC = (int*)(padwNewFuncWordGroup + dwNewNum);
   int *paiPitchF0FujiLRC = paiPitchF0ExtraLRC + TPAPITCHPOINTS * dwNewNum;
   fp *pafSuperTPAPitchPointsF0Extra = (fp*)(paiPitchF0FujiLRC + TPAPITCHPOINTS * dwNewNum);
   fp *pafSuperTPAPitchPointsF0Fuji = (fp*)(pafSuperTPAPitchPointsF0Extra + SUPERTPAPITCHPOINTS * dwNewNum);
   BYTE *pabNewPhone = (BYTE*)(pafSuperTPAPitchPointsF0Fuji + SUPERTPAPITCHPOINTS * dwNewNum);
   BYTE *pabNewWordPos = pabNewPhone + dwNewNum;
   PBYTE apbTriPhone[NUMTRIPHONEGROUP];
   PBYTE pbCur;
   for (i = 0, pbCur = pabNewWordPos; i < NUMTRIPHONEGROUP; i++)
      pbCur = apbTriPhone[i] = pbCur + dwNewNum;

   // fill this in with information
   pabNewPhone[0] = pabNewPhone[dwNewNum-1] = bSilence;
   pabNewWordPos[0] = pabNewWordPos[dwNewNum-1] = 0;
   padwNewWord[0] = padwNewWord[dwNewNum-1] = -1;
   padwNewFuncWordGroup[0] = padwNewFuncWordGroup[dwNewNum-1] = 0;
   WCHAR szWord[256];
   DWORD dwFuncWordGroup;
   for (i = 0; i < dwNum; i++) {
      pabNewPhone[i+1] = (BYTE)padwPhone[i];
      pabNewWordPos[i+1] = (BYTE)(padwPhone[i] >> 24);
      
      // determine if it's a plosive
      PLEXPHONE plpThis = pLex->PhonemeGetUnsort (PhonemeBackoff(pabNewPhone[i+1], pLex, bSilence));
      PLEXENGLISHPHONE pe = plpThis ? MLexiconEnglishPhoneGet(plpThis->bEnglishPhone) : NULL;
      if (pe && (pe->dwCategory & PIC_PLOSIVE))
         pabNewWordPos[i+1] |= WORDPOS_EXTRA_PLOSIVE;   // note that it's plosive
      if (pe && (pe->dwCategory & PIC_VOICED))
         pabNewWordPos[i+1] |= WORDPOS_EXTRA_VOICED;   // note that it's voiced
      
      padwNewWord[i+1] = padwWord[i];

      // determine which function word it is
      szWord[0] = 0;
      dwFuncWordGroup = NUMFUNCWORDGROUP; // backoff
      if (m_pLexWords && (padwWord[i] != (DWORD)-1) )
         m_pLexWords->WordGet (padwWord[i], szWord, sizeof(szWord), NULL);
      if (szWord[0]) {
         for (j = 0; j < NUMFUNCWORDGROUP; j++) {
            if (!m_apLexFuncWord[j])
               continue;

            if (m_apLexFuncWord[j]->WordExists (szWord)) {
               dwFuncWordGroup = j;
               break;
            }
         } // j
      } // if word
      padwNewFuncWordGroup[i+1] = dwFuncWordGroup;
   } // i

   // find the syllable boundaries
   CListFixed lTempPron, lBoundary;
   lTempPron.Init (sizeof(BYTE));
   lBoundary.Init (sizeof(DWORD));
   DWORD k;
   BYTE bTemp;
   for (i = 0; i < dwNum; i++) {
      if (pabNewPhone[i+1] == bSilence)
         continue;
      if (!(pabNewWordPos[i+1] & 0x01))
         continue;   // no word start

      // loop until word end
      for (j = i; j < dwNum; j++) {
         if (pabNewPhone[j+1] == bSilence)
            break;
         if (pabNewWordPos[j+1] & 0x02) {
            j++;
            break;   // word end
         }
      }

      if (j <= i+1) {
         // single phoneme word = single syllable word
         pabNewWordPos[i+1] |= 0x4 | 0x08;   // note that it's plosive
         continue;
      }

      // else, it's multiphoneme, so guess syllables
      lTempPron.Clear();
      if (j > i)
         lTempPron.Required (j - i);
      for (k = i; k < j; k++) {
         bTemp = pabNewPhone[k+1] + 1;
         lTempPron.Add (&bTemp);
      } // k
      bTemp = 0;
      lTempPron.Add (&bTemp);

      lBoundary.Clear();
      pLex->WordSyllables ((PBYTE)lTempPron.Get(0), NULL, &lBoundary);
         // NOTE: Not passing in word string, which may eventually cause problems for syllables

      // put in syllable markers
      pabNewWordPos[i+1] |= WORDPOS_SYLSTART;   // start syllable
      pabNewWordPos[j+1-1] |= WORDPOS_SYLEND;   // end syllable
      DWORD *padwBoundry = (DWORD*)lBoundary.Get(0);
      for (k = 0; k < lBoundary.Num(); k++) {
         DWORD dwBound = (WORD)padwBoundry[k];
         if (dwBound)
            pabNewWordPos[i+1+dwBound-1] |= WORDPOS_SYLEND;   // start end
         if (i+1+dwBound < dwNum)
            pabNewWordPos[i+1+dwBound] |= WORDPOS_SYLSTART;   // start syllable
      } // k

      i = j-1; // skip ahead
   } // i

   for (i = 0; i < dwNewNum; i++) for (j = 0; j < NUMTRIPHONEGROUP; j++)
      (apbTriPhone[j])[i] = (BYTE)PhoneToTriPhoneNumber(pabNewPhone[i], pabNewPhone[i], pLex, j); 

   // fill in an array of pitches
   DWORD dwFuji;
   CMem memPitches;
   fp *paf;
   PCListVariable plPitch;
   for (i = 0; i < dwNewNum * SUPERTPAPITCHPOINTS; i++)
      pafSuperTPAPitchPointsF0Extra[i] = pafSuperTPAPitchPointsF0Fuji[i] = -1.0; // so will interp between later
   for (dwFuji = 0; dwFuji < 2; dwFuji++) {
      paf = dwFuji ? pafSuperTPAPitchPointsF0Fuji : pafSuperTPAPitchPointsF0Extra;
      // BUGFIX - Need to counteract the pitch values sent in here if doing voice mod,
      // to choose a pitch counteracts the voicemod pitch up/down, so get better TTS
      fp fPitchScaleToCounteractVoiceMod = dwFuji ? (m_fAvgPitch / fAvgPitchF0All) : 1.0;
      plPitch = dwFuji ? plPitchF0Fuji : plPitchF0Extra;
      for (i = 0; i < dwNewNum; i++) {
         int iThis = (int)i - 1;
         fp *pfThis = (plPitch && (iThis >= 0)) ? (fp*) plPitch->Get((DWORD)iThis) : NULL;
         DWORD dwThisSize = pfThis ? ((DWORD)plPitch->Size((DWORD)iThis) / sizeof(fp)) : 0;

         // fill in individual points
         for (j = 0; j < dwThisSize; j++)
            paf[i * SUPERTPAPITCHPOINTS + (j * SUPERTPAPITCHPOINTS + SUPERTPAPITCHPOINTS/2) / dwThisSize] =
               pfThis[j] * fPitchScaleToCounteractVoiceMod;
      } // i, over dwNewNum

      // run pitchlowpass to wipe out all the -1's and fill them in
      PitchLowPass (paf, dwNewNum * SUPERTPAPITCHPOINTS, 0, dwFuji ? fAvgPitchF0Fuji : 1.0);
   } // dwFuji

   // fill in the pitches
   fp fTemp;
   for (dwFuji = 0; dwFuji < 2; dwFuji++) {
      int *pai = dwFuji ? paiPitchF0FujiLRC : paiPitchF0ExtraLRC;
      paf = dwFuji ? pafSuperTPAPitchPointsF0Fuji : pafSuperTPAPitchPointsF0Extra;
      plPitch = dwFuji ? plPitchF0Fuji : plPitchF0Extra;
      for (i = 0; i < dwNewNum; i++, pai += TPAPITCHPOINTS) {
         for (j = 0; j < TPAPITCHPOINTS; j++) {
            fTemp = paf[i*SUPERTPAPITCHPOINTS + (j * SUPERTPAPITCHPOINTS + SUPERTPAPITCHPOINTS/2) / TPAPITCHPOINTS];

            // convert to log
            if (dwFuji)
               fTemp = log(fTemp / (fp)SRBASEPITCH) / log(2.0) * 1000.0;
            else
               fTemp = log(fTemp) / log(2.0) * 1000.0;

            pai[j] = (int)fTemp;
         } // j
      } // i
   } // dwFuji

   // clear desintation
   plPCMTTSTriPhoneAudio->Init (sizeof(PCMTTSTriPhoneAudio));

   // start out with initial hypothesis
   PHONEHYP ph;
   CMem amemPHONEHYP[2];
   if (!amemPHONEHYP[0].Required (sizeof(PHONEHYP)))
      return FALSE; // error
   memset (&ph, 0, sizeof(ph));
   memcpy (amemPHONEHYP[0].p, &ph, sizeof(ph));
   amemPHONEHYP[0].m_dwCurPosn = sizeof(ph);

#ifdef _DEBUG
   DWORD dwStart = GetTickCount();
#endif

   PCMTTSTriPhoneAudio ptpBest = NULL;
   DWORD dwFrom = 0, dwTo = 1;
   DWORD dwMaxHyp = 0;
   CListFixed alUNITOPTION[2];   // use as ping-pong buffer
   // BUGFIX - MAXPHONEHYP dependent on number of units
   // DWORD dwMaxPhoneHyp = (DWORD)(sqrt((double)m_dwUnits / (double)OPTIMALNUMUNITS) * (double)MAXPHONEHYP) + 1;
   fp fMaxPhoneHyp = sqrt((double)m_dwUnits / (double)OPTIMALNUMUNITS) * (double)MAXPHONEHYP;
   int iTTSQualityLimited = min (max(iTTSQuality, 0), 3);
   // BUGFIX - Greatter difference in quality
   fMaxPhoneHyp *= (0.33 + 0.66 * (fp)iTTSQualityLimited / 3.0);
   DWORD dwMaxPhoneHyp = (DWORD)(fMaxPhoneHyp + 0.5) + 1;

   for (i = 0; i < dwNum * TTSDEMIPHONES; i++) {
      if (!(i%4) && pProgress)
         pProgress->Update ((fp)i / (fp)(dwNum * TTSDEMIPHONES));

      // clear the to-list
      dwFrom = i % 2;
      dwTo = (i+1)%2;
      amemPHONEHYP[dwTo].m_dwCurPosn = 0;;

      DWORD dwPhoneIndex = i / TTSDEMIPHONES + 1;
      DWORD dwPhoneIndexPrev = (i + TTSDEMIPHONES - 1) / TTSDEMIPHONES;

      // expand
      HypExpand (i, amemPHONEHYP + dwFrom, amemPHONEHYP + dwTo,
         dwNum - (dwPhoneIndex-1),
         pabNewPhone + dwPhoneIndex, pabNewWordPos + dwPhoneIndex,
         (apbTriPhone[0]) + dwPhoneIndex, (apbTriPhone[1]) + dwPhoneIndex,
         padwNewWord + dwPhoneIndex,
         paiPitchF0ExtraLRC + dwPhoneIndex * TPAPITCHPOINTS,
               // BUGFIX - Was doing dwPhoneIndex*2 instead of dwPhoneINdex*3
         paiPitchF0FujiLRC + dwPhoneIndex * TPAPITCHPOINTS,
         // (padwDur && (dwPhoneIndexPrev >= 1)) ? padwDur[dwPhoneIndexPrev-1] : 0,
         padwDur ? padwDur[dwPhoneIndex-1] : 0,
         pafVolWant[dwPhoneIndex-1],
         dwPhoneIndexPrev ? pafVolWant[dwPhoneIndexPrev-1] : 0,
         pafAccent[dwPhoneIndex-1],
         bSilence,
         padwNewFuncWordGroup[dwPhoneIndex],
         fEnergyAverage, iTTSQuality, fDisablePCM,
         &alUNITOPTION[i%2], &alUNITOPTION[(i+1)%2]);

      // keep the best score
      ptpBest = HypKeepBestScore (&amemPHONEHYP[dwTo], fEnergyAverage);
      plPCMTTSTriPhoneAudio->Add (&ptpBest);

      dwMaxHyp = max(dwMaxHyp, (DWORD)amemPHONEHYP[dwTo].m_dwCurPosn / sizeof(PHONEHYP));

      // eliminate the lowest scores if too many
      HypEliminateLowScores (&amemPHONEHYP[dwTo], dwMaxPhoneHyp); // BUGFIX - Went from 10000 to 1000
         // BUGFIX - Went from 1000 to 100
   } // i

#ifdef _DEBUG
   WCHAR szTemp[128];
   swprintf (szTemp, L"\r\nHypExpand time = %d", (int)(GetTickCount() - dwStart));
   OutputDebugStringW (szTemp);
#endif

   // loop through and find the best one. make sure to add the remaining phonemes
   DWORD dwBest = -1;
   fp fBestScore = 1000000000000;
   fp fBestScoreNoEnergy = 1000000000000;
   dwNewNum = (DWORD)amemPHONEHYP[dwTo].m_dwCurPosn / sizeof(PHONEHYP);
   PPHONEHYP pph = (PPHONEHYP)amemPHONEHYP[dwTo].p;
   for (i = 0; i < dwNewNum; i++)
      if (pph[i].fScoreWithEnergy < fBestScore) {
         fBestScore = pph[i].fScoreWithEnergy;
         fBestScoreNoEnergy = pph[i].fScoreNoEnergy;
         dwBest = i;
      }
   if (dwBest != -1) for (i = 1; i < PHONEHISTORY; i++)
      plPCMTTSTriPhoneAudio->Add (&pph[dwBest].aPCMTTSTriPhoneAudio[i]);
   // remove the first few since they're fake silences
   for (i = 0; i < PHONEHISTORY-1; i++)
      plPCMTTSTriPhoneAudio->Remove (0);

   // remember the best score
   *pfBestScore = fBestScoreNoEnergy;

   return TRUE;
}


/*************************************************************************************
CMTTS::SynthDetermineTriPhonePros - This determines the best prosody triphone to
use.

inputs
   BYTE        bPhone - Phone
   WORD        wWordSylPos - Bits to indicate word pos. WORDPOS_XXX
   BYTE        bLeft - Phone to the left
   BYTE        bRight - Phone to right
returns
   PCMTTSTriPhonePros - Prosody triphone
*/
PCMTTSTriPhonePros CMTTS::SynthDetermineTriPhonePros (BYTE bPhone, WORD wWordSylPos,
                                                      BYTE bLeft, BYTE bRight)
{
   if (bPhone >= m_dwNumPhone)
      return NULL; // not a real phone

   PCMLexicon pLexicon = Lexicon();
   PCMLexicon pLex = pLexicon;
   BYTE bSilence = pLex->PhonemeFindUnsort (pLex->PhonemeSilence());
   PCListFixed pl = m_palPCMTTSTriPhonePros[PhonemeBackoff(bPhone, pLex, bSilence)];
   if (!pl)
      return NULL;

   PLEXPHONE plp = pLex->PhonemeGetUnsort (PhonemeBackoff(bPhone, pLex, bSilence));
   PLEXENGLISHPHONE ple = plp ? MLexiconEnglishPhoneGet(plp->bEnglishPhone) : NULL;

   BOOL fVoiced = FALSE, fPlosive = FALSE;
   if (ple) {
      fVoiced = (ple->dwCategory & PIC_VOICED) ? TRUE : FALSE;
      fPlosive = (ple->dwCategory & PIC_PLOSIVE) ? TRUE : FALSE;
   }

   PCMTTSTriPhonePros *pptp = (PCMTTSTriPhonePros*)pl->Get(0);

   // determine triphone group
   DWORD j;
   WORD awPhoneGroup[NUMTRIPHONEGROUP];
   for (j = 0; j < NUMTRIPHONEGROUP; j++)
      awPhoneGroup[j] = PhoneToTriPhoneNumber (bLeft, bRight, pLexicon, j);

   // loop through all possibilities
   PCMTTSTriPhonePros pBest = NULL;
   fp fBestScore = 1000000000;
   for (j = 0; j < pl->Num(); j++) {
      PCMTTSTriPhonePros ptp = pptp[j];

      fp fScore = 0;

      // left
      if (LexPhoneGroupToMega((BYTE)awPhoneGroup[0]) != LexPhoneGroupToMega((BYTE)ptp->m_awTriPhone[0]))
         fScore += UnitScoreScoreLRMismatch (this, bPhone, bLeft, pLex, FALSE, 5);
      else if ((BYTE)awPhoneGroup[0] != (BYTE)ptp->m_awTriPhone[0])
         // BUGFIX - Changed CROSSWORDLESSPENALTYLEFT (HYPBADGROUP) to just HYPBADGROUP
         // since was tending to change to bad unit at the start/end of word
         fScore += UnitScoreScoreLRMismatch (this, bPhone, bLeft, pLex, FALSE, 4);
      else if ( /*(m_dwTriPhoneGroup >= 2) &&*/ ((BYTE)awPhoneGroup[1] != (BYTE)ptp->m_awTriPhone[1])) // right group, wrong phone
         fScore += UnitScoreScoreLRMismatch (this, bPhone, bLeft, pLex, FALSE, 3);
      else if ( /*(m_dwTriPhoneGroup >= 3) &&*/ ((BYTE)awPhoneGroup[2] != (BYTE)ptp->m_awTriPhone[2]))// right phone, wrong stress
         fScore += UnitScoreScoreLRMismatch (this, bPhone, bLeft, pLex, FALSE, 2);
      // else perfect triphone match, so no error
      // NOTE: Not calling UnitScoreLRMismatch (...., 0 or 1) because irrelevant

      // left
      //if ((BYTE)awPhoneGroup[0] != (BYTE)ptp->m_awTriPhone[0])
      //   dwScore += HYPBADGROUP;
      //else if (((BYTE)awPhoneGroup[1] != (BYTE)ptp->m_awTriPhone[1]) && (m_dwTriPhoneGroup >= 2))
            // BUGFIX - The (m_dwTriPhoneGroup >= 2) check was unintentionally left out
      //   dwScore += HYPBADSTRESS;
      // else, must be equal so no penalty
      // BUGFIX - Test for  && (m_dwTriPhoneGroup >= 2), so if triphone isn't supposed to be conscious of stress

      if (LexPhoneGroupToMega((BYTE)(awPhoneGroup[0]>>8)) != LexPhoneGroupToMega((BYTE)(ptp->m_awTriPhone[0]>>8)))
         fScore += UnitScoreScoreLRMismatch (this, bPhone, bRight, pLex, TRUE, 5);
      else if ((BYTE)(awPhoneGroup[0]>>8) != (BYTE)(ptp->m_awTriPhone[0]>>8))
         // BUGFIX - Changed CROSSWORDLESSPENALTYLEFT (HYPBADGROUP) to just HYPBADGROUP
         // since was tending to change to bad unit at the start/end of word
         fScore += UnitScoreScoreLRMismatch (this, bPhone, bRight, pLex, TRUE, 4);
      else if (/*(m_dwTriPhoneGroup >= 2) &&*/ ((BYTE)(awPhoneGroup[1]>>8) != (BYTE)(ptp->m_awTriPhone[1]>>8))) // right group, wrong phone
         fScore += UnitScoreScoreLRMismatch (this, bPhone, bRight, pLex, TRUE, 3);
      else if (/*(m_dwTriPhoneGroup >= 3) &&*/ ((BYTE)(awPhoneGroup[2]>>8) != (BYTE)(ptp->m_awTriPhone[2]>>8)))// right phone, wrong stress
         fScore += UnitScoreScoreLRMismatch (this, bPhone, bRight, pLex, TRUE, 2);
      // NOTE: Not calling UnitScoreLRMismatch (...., 0 or 1) because irrelevant
      // else perfect triphone match, so no error
      // right
      //if ((BYTE)(awPhoneGroup[0] >> 8) != (BYTE)(ptp->m_awTriPhone[0] >> 8))
      //   dwScore += HYPBADGROUP;
      //else if ((BYTE)(awPhoneGroup[1] >> 8) != (BYTE)(ptp->m_awTriPhone[1] >> 8) && (m_dwTriPhoneGroup >= 2))
      //   dwScore += HYPBADSTRESS;
      // else, must be equal so no penalty
      // BUGFIX - Test for  && (m_dwTriPhoneGroup >= 2), so if triphone isn't supposed to be conscious of stress

      // if mismatch in phoneme at start/end then add penalty
      fScore += UnitScoreMismatchedWordPos (this, wWordSylPos, ptp->m_wWordSylPos);

      // best score
      if (fScore < fBestScore) {
         fBestScore = fScore;
         pBest = ptp;
      }
   } // j

   return pBest;
}


/*************************************************************************************
CMTTS::SynthDetermineTriPhonePros - Given a list of phonemes and their duration, this
determines which units are to be used for each triphone.

inputs
   DWORD       *padwPhone - Pointer to an array of phonemes. Each phoneme is the
               unsorted phone number. The high byte (<< 24) contains 0 if the phone
               is in the middle of a word, 1 on left side, 2 on right side, 3 word by itself,
               AND other WORDPOS_XXX flags for syllables.
   DWORD       dwNum - Number of phonemes
   PCListFixed plPCMTTSTriPhonePros - Pointer to a list that is initialized to sizeof(PCMTTSTriPhonePros)
               and filled with a list of triphones, one of each phone (NOT times TTSDEMIPHONES).
returns
   BOOl - TRUE if success
*/

BOOL CMTTS::SynthDetermineTriPhonePros (DWORD *padwPhone, DWORD dwNum,
                                    PCListFixed plPCMTTSTriPhonePros)
{
   // if this is a derived voice pass on down
   if (m_fIsDerived) {
      if (!m_pTTSMaster)
         return FALSE;
      return m_pTTSMaster->SynthDetermineTriPhonePros (padwPhone, dwNum,
         plPCMTTSTriPhonePros);
   }

   // get silence
   PCMLexicon pLex = Lexicon();
   if (!pLex)
      return FALSE;
   BYTE bSilence = pLex->PhonemeFindUnsort (pLex->PhonemeSilence());

   // clear desintation
   plPCMTTSTriPhonePros->Init (sizeof(PCMTTSTriPhonePros));

   // loop
   DWORD i;
   plPCMTTSTriPhonePros->Required (dwNum);
   for (i = 0; i < dwNum; i++) {
      DWORD dwLeft = i ? padwPhone[i-1] : bSilence;
      DWORD dwRight = (i+1 < dwNum) ? padwPhone[i+1] : bSilence;

      PCMTTSTriPhonePros ptp = SynthDetermineTriPhonePros ((BYTE)padwPhone[i],
         (WORD)(padwPhone[0] >> 24), (BYTE)dwLeft, (BYTE)dwRight);
      plPCMTTSTriPhonePros->Add (&ptp);
   } // i

   return TRUE;
}


/*************************************************************************************
PitchLowPass - Does a low-pass filter on the pitch/volume settings. THis also
looks for values that are < 0 and ramps between them.

inputs
   fp             *pafVal - Pointer to array of pitch values
   DWORD          dwNum - Number
   DWORD          dwHalfSize - Size (in units) of half the low-pass window.
                     The window is triangular
   fp             fDefault - Default value if can't find left/right to ramp between
returns
   none
*/
void PitchLowPass (fp *pafVal, DWORD dwNum, DWORD dwHalfSize, fp fDefault)
{
   // look for places that need to ramp
   DWORD i, j;
   DWORD dwLast = 0;
   while (TRUE) {
      // find first < 0
      for (i = dwLast; i < dwNum; i++)
         if (pafVal[i] < 0)
            break;
      if (i >= dwNum)
         break;   // no more

      // find first > 0
      for (j = i+1; j < dwNum; j++)
         if (pafVal[j] >= 0)
            break;

      // left and right value
      fp fLeft = (i ? pafVal[i-1] : -1);
      fp fRight = ((j < dwNum) ? pafVal[j] : -1);
      if (fLeft < 0)
         fLeft = (fRight >= 0) ? fRight : fDefault;
      if (fRight < 0)
         fRight = (fLeft >= 0) ? fLeft : fDefault;

      // interp amount
      fp fDelta = (fRight - fLeft) / (fp)(j - i);
      fp fCur = fLeft + fDelta/2;
      for (; i < j; i++, fCur += fDelta)
         pafVal[i] = fCur;
      dwLast = j + 1;
   }

#ifdef NOMODS_LOWPASS
   return;
#endif

   // now low pass filter...

   // copy over
   CMem mem;
   if (!mem.Required (sizeof(fp) * dwNum))
      return;
   fp *pfOrig = (fp*)mem.p;
   memcpy (pfOrig, pafVal, sizeof(fp)*dwNum);

   // low pass
   fp fSum;
   int iDist, iCur;
   DWORD dwCount, dwStrength;
   for (i = 0; i < dwNum; i++) {
      fSum = 0;
      dwCount = 0;

      for (iDist = -(int)dwHalfSize; iDist <= (int)dwHalfSize; iDist++) {
         iCur = iDist + (int)i;
         if ((iCur < 0) || (iCur >= (int)dwNum))
            continue;

         dwStrength = dwHalfSize + 1 - (DWORD)abs(iDist);
         fSum += pfOrig[iCur] * (fp)dwStrength;
         dwCount += dwStrength;
      } // iDist
      if (dwCount)
         fSum /= (fp)dwCount;

      pafVal[i] = fSum;
   } // i

   // done
}


/*************************************************************************************
CMTTS::LogSpoken - Log what's spoken to the log file.

inputs
   PCM3DWave         pWave - Wave with the words in it that should be logged
returns
   BOOL - TRUE if success
*/
BOOL CMTTS::LogSpoken (PCM3DWave pWave)
{
   if (!m_fKeepLog)
      return FALSE;

   // creat the string
   CMem memW;
   MemZero (&memW);
   MemCat (&memW, L"\r\n");   // so have new line no matter what
   DWORD i;
   for (i = 0; i < pWave->m_lWVWORD.Num(); i++) {
      PWVWORD pw = (PWVWORD)pWave->m_lWVWORD.Get(i);
      PWSTR psz = (PWSTR)(pw+1);
      if (!psz[0] || psz[0] == L' ')
         continue;   // ignore spaces

      // add
      MemCat (&memW, psz);
      MemCat (&memW, L" ");   // so space separates elements
   }

   // convert this to ANSI in case need
   CMem memA;
   if (!memA.Required (wcslen((PWSTR)memW.p)*2+2))
      return FALSE;
   WideCharToMultiByte (CP_ACP, 0, (PWSTR)memW.p, -1, (char*)memA.p, (DWORD)memA.m_dwAllocated, 0, 0);

   // file name with text extension
   char szFile[256];
   WideCharToMultiByte (CP_ACP, 0, m_szFile, -1, szFile, sizeof(szFile), 0, 0);
   DWORD dwLen = (DWORD)strlen(szFile);
   if ((dwLen < 4) || (szFile[dwLen-4] != '.'))
      return FALSE;
   strcpy (szFile + (dwLen-3), "txt");

   // open existing
   OUTPUTDEBUGFILE (szFile);
   FILE *f = fopen (szFile, "r+b");
   BOOL fUnicode = TRUE;
   if (f) {
      WCHAR wUnicode = 0;
      fread (&wUnicode, sizeof(wUnicode), 1, f);
      if (wUnicode != 0xfeff)
         fUnicode = FALSE;

      fseek (f, 0, SEEK_END);
      if (ftell(f) > 10000000) {
         // file would be too large so dont add
         fclose (f);
         return FALSE;
      }
   }
   else {
      OUTPUTDEBUGFILE (szFile);
      f = fopen (szFile, "wb");
      if (!f)
         return FALSE;

      // write out incode header
      WCHAR wUnicode = 0xfeff;
      fwrite (&wUnicode, sizeof(wUnicode), 1, f);
   }

   // write out
   if (fUnicode)
      fwrite (memW.p, sizeof(WCHAR), wcslen((PWSTR)memW.p), f);
   else
      fwrite (memA.p, sizeof(char), strlen((char*)memA.p), f);

   fclose (f);
   return TRUE;
}


/*************************************************************************************
CMTTS::SynthGenFeatures - Given the phonemes, their durations, and the words they're
in, this fills in the wave's SRFEATUREs, phoneme, and word structures.

IMPORTANT: You must call:
   // will need to compress all units
   m_fTTSWaveDisableCompress = FALSE;
   TTSWaveCompress ();
Sometime after SynthGenFeatures() to re-enable background unit analysis and to
compress all the decompressed waves. DON'T call until after the audio has been synthesized.


inputs
   DWORD          dwCandidates - Number of PSYNGENFEATURESCANDIDATE
   PSYNGENFEATURESCANDIDATE paCandidates - List of candidates to try and synthesize. The candidate
                  with the best acoustic score is synthesized.

                  NOTE: The duration of the final wave might be expanded/contracted to ensure
                  that no time shift takes place

   PCM3DWave      pWave - To Synthesize to
   PCListFixed       plSRDETAILEDPHASE - Initialized and filled in with sRDETAILEDPHASE,
                        one per pWave->m_dwSRSamples.
   PTTSVOICEMOD   pVoiceMod - Parameters controlling how the voice is modified by
                  the original. Generally, pass in NULL. Only internal functions
                  will pass in non-null.
   int               iTTSQuality - TTS quality. 0 for fast, 1 for normal, 2 for slow, 3 for best, 4 for multipass
   BOOL              fDisablePCM - Set to TRUE if the player has temporarily disabled PSOLA
   PCListFixed    plPSOLASTRUCT - List pre-intialized to sizoef(PSOLASTRUCT), that will be appended to
                  with PSOLA information in case wish to synthesize PSOLA. Can be NULL.
   DWORD          *pdwBestCandidate - Filled in with the best candidate
   PCProgressSocket pProgress - Progress
returns
   BOOL - TRUE if success
*/


// BLENDPHASEINFO
typedef struct {
   DWORD             dwTime;        // time (in samples) into the wave where blend
   DWORD             dwSkip;        // skip value. 0x04 means to include phase from left, 0x08 includes phase from right
} BLENDPHASEINFO, *PBLENDPHASEINFO;

BOOL CMTTS::SynthGenFeatures (DWORD dwCandidates, PSYNGENFEATURESCANDIDATE paCandidates,
                              PCM3DWave pWave, PCListFixed plSRDETAILEDPHASE,
                              PTTSVOICEMOD pVoiceMod, int iTTSQuality, BOOL fDisablePCM, PCListFixed plPSOLASTRUCT,
                              DWORD *pdwBestCandidate, PCProgressSocket pProgress)
{
   *pdwBestCandidate = (DWORD)-1; // error

   // flag so can't check anything in background wise
   // BUGFIX - Go backwards so don't end up chasing background thread through
   // 16 different sentences
   DWORD i;
   for (i = 0; i < MAXRAYTHREAD; i++)
      EnterCriticalSection (&m_acsTTSWave[MAXRAYTHREAD - 1 - i]);
   m_fTTSWaveDisableCompress = TRUE;
      // NOTE: When set this to FALSE later don't need to worry about CS
   for (i = 0; i < MAXRAYTHREAD; i++)
      LeaveCriticalSection (&m_acsTTSWave[i]);

   plSRDETAILEDPHASE->Init (sizeof(SRDETAILEDPHASE));

   CListFixed lPCMTTSTriPhoneAudio, lPCMTTSTriPhoneAudioTemp, lPCMTTSTriPhonePros;
   if (pProgress)
      pProgress->Push(0.0, 0.5);
   
   // loop over all the candidates
   DWORD dwCand;
   DWORD dwBestCand = (DWORD)-1;
   fp fBestScore = 0;
   for (dwCand = 0; dwCand < dwCandidates; dwCand++) {
      if (pProgress)
         pProgress->Push ((fp)dwCand / (fp)dwCandidates, (fp)(dwCand+1) / (fp)dwCandidates);

      fp fScoreTemp;

      if (!SynthDetermineTriPhoneAudio (
         pVoiceMod->pSubVoice->m_fAvgPitch,
         pVoiceMod->pSubVoice->m_fAvgPitch / m_fAvgPitch * m_fAvgPitchF0Fuji,
         paCandidates[dwCand].padwPhone, paCandidates[dwCand].padwWord, paCandidates[dwCand].padwDur,
         paCandidates[dwCand].pafVol, paCandidates[dwCand].fVolAbsolute,
         paCandidates[dwCand].pafAccent,
         paCandidates[dwCand].plPitchF0Extra, paCandidates[dwCand].plPitchF0Fuji,
         paCandidates[dwCand].dwNum, iTTSQuality, fDisablePCM, &lPCMTTSTriPhoneAudioTemp, &fScoreTemp, pProgress)) {

         if (pProgress) {
            pProgress->Pop();
            pProgress->Pop();
         }
         m_fTTSWaveDisableCompress = FALSE;
         return FALSE;
      }

#ifdef _DEBUG
      WCHAR szTemp[128];
      swprintf (szTemp, L"\r\nSynthGenFeatures candidate %d scores %g", (int)dwCand, (double)fScoreTemp);
      OutputDebugStringW (szTemp);
#endif

      // take the best one
      if ((dwBestCand == (DWORD)-1) || (fScoreTemp < fBestScore)) {
         dwBestCand = dwCand;
         fBestScore = fScoreTemp;
         lPCMTTSTriPhoneAudio.Init (sizeof(PCMTTSTriPhoneAudio), lPCMTTSTriPhoneAudioTemp.Get(0), lPCMTTSTriPhoneAudioTemp.Num());
      }


      if (pProgress)
         pProgress->Pop();
   }
   if (pProgress)
      pProgress->Pop();

   // remember the best andidate
   *pdwBestCandidate = dwBestCand;
   DWORD *padwPhone = paCandidates[dwBestCand].padwPhone;
   DWORD *padwDur = paCandidates[dwBestCand].padwDur;
   DWORD *padwWord = paCandidates[dwBestCand].padwWord;
   DWORD dwNum = paCandidates[dwBestCand].dwNum;
   PCListVariable plPitchF0Extra = paCandidates[dwBestCand].plPitchF0Extra;
   PCListVariable plPitchF0Fuji = paCandidates[dwBestCand].plPitchF0Fuji;
   PWSTR *papszWord = paCandidates[dwBestCand].papszWord;
   BOOL fAbsPitch = paCandidates[dwBestCand].fAbsPitch;
   PCListFixed plBookmarkIndex = paCandidates[dwBestCand].plBookmarkIndex;
   PCListVariable plBookmarkString = paCandidates[dwBestCand].plBookmarkString;


   if (!SynthDetermineTriPhonePros (padwPhone, dwNum, &lPCMTTSTriPhonePros)) {
      m_fTTSWaveDisableCompress = FALSE;
      return FALSE;
   }
   PCMLexicon pLex = pVoiceMod->pLex;
   if (!pLex)
      pLex = Lexicon();
   if (!pLex) {
      m_fTTSWaveDisableCompress = FALSE;
      return FALSE;
   }

   PCMTTSTriPhoneAudio *pptp = (PCMTTSTriPhoneAudio*) lPCMTTSTriPhoneAudio.Get(0);
   PCMTTSTriPhonePros *pptpPros = (PCMTTSTriPhonePros*) lPCMTTSTriPhonePros.Get(0);
   

   BYTE bSilence = (BYTE)pLex->PhonemeFindUnsort(pLex->PhonemeSilence());
#ifdef _DEBUG
   // output the phonemes
   OutputDebugString ("\r\n:TTS Phone:");
   WORD wLastWave = (WORD)-1;
   WORD wLastPhone = (WORD)-1;
   WCHAR szTemp[64];
   for (i = 0 ; i < lPCMTTSTriPhoneAudio.Num(); i++) {
      PCMTTSTriPhoneAudio ptp = pptp[i];
      PLEXPHONE plp = pLex->PhonemeGetUnsort (PhonemeBackoff((BYTE)padwPhone[i/TTSDEMIPHONES], pLex, bSilence));

      if (ptp && ((ptp->m_wOrigWave != wLastWave) || (ptp->m_wOrigPhone != wLastPhone+((i%TTSDEMIPHONES) ? 0 : 1) ))) {
         // display wave and phone
         swprintf (szTemp, L" (%d:%d)", (int)ptp->m_wOrigWave, (int)ptp->m_wOrigPhone);
         OutputDebugStringW (szTemp);
      }

      if (ptp) {
         wLastWave = ptp->m_wOrigWave;
         wLastPhone = ptp->m_wOrigPhone;

         // display errors in triphones
         // mismatch in left phoneme
         BYTE bPrevPhone = (i >= TTSDEMIPHONES) ? (BYTE)padwPhone[i/TTSDEMIPHONES-1] : bSilence;
         if ((bPrevPhone != ptp->m_bPhoneLeft) && (!i || (pptp[i-1] != ptp)) ){
            PLEXPHONE plp2 = pLex->PhonemeGetUnsort (ptp->m_bPhoneLeft);
            if (plp2) {
               swprintf (szTemp, L" [!%s-]", plp2->szPhoneLong);
               OutputDebugStringW (szTemp);
            }
         }
      }

      // display this phoneme
      if (plp) {
         OutputDebugStringW (L" ");
         OutputDebugStringW (plp->szPhoneLong);
      }

      if (ptp) {
         // display errors in triphones
         BYTE bNextPhone = (i + TTSDEMIPHONES < lPCMTTSTriPhoneAudio.Num()) ? (BYTE)padwPhone[i/TTSDEMIPHONES+1] : bSilence;
         if ( (bNextPhone != ptp->m_bPhoneRight) && ((i+1 >= lPCMTTSTriPhoneAudio.Num()) || (pptp[i+1] != ptp)) ) {
            PLEXPHONE plp2 = pLex->PhonemeGetUnsort (ptp->m_bPhoneRight);
            if (plp2) {
               swprintf (szTemp, L" [!-%s]", plp2->szPhoneLong);
               OutputDebugStringW (szTemp);
            }
         }
      }

   } // i
   OutputDebugString ("\r\n");
#endif

   // figure out total duration
   DWORD dwTotalPreMod = 0;
   for (i = 0; i < dwNum; i++)
      dwTotalPreMod += padwDur[i];

   // make sure wave large enough
   pWave->m_dwSRSAMPLESPERSEC = SRSAMPLESPERSEC;
   DWORD dwSamples = pWave->m_dwSamplesPerSec / pWave->m_dwSRSAMPLESPERSEC;
   dwSamples = dwTotalPreMod * dwSamples;
   if (!pWave->BlankWaveToSize (dwSamples, TRUE)) {
      m_fTTSWaveDisableCompress = FALSE;
      return FALSE;
   }
   DWORD dwCur;

   // fill in the phases
   SRDETAILEDPHASE SDP;
   memset (&SDP, 0, sizeof(SDP));
   plSRDETAILEDPHASE->Required (pWave->m_dwSRSamples);
   for (i = 0; i < pWave->m_dwSRSamples; i++)
      plSRDETAILEDPHASE->Add (&SDP);
   PSRDETAILEDPHASE paSDP = (PSRDETAILEDPHASE) plSRDETAILEDPHASE->Get(0);

   DWORD dwNumCurve = pWave->m_adwPitchSamples[PITCH_F0];

   // make original pitch
   CListFixed lPitchOrig;
   lPitchOrig.Init (sizeof(fp));
   lPitchOrig.Required (dwNumCurve);
   fp fUnk = -1;
   for (i = 0; i < dwNumCurve; i++)
      lPitchOrig.Add (&fUnk);
   fp *pfPitchOrig = (fp*) lPitchOrig.Get(0);





   // will need to decompress all units
   // BUGFIX - Don't need to do because act of getting will decompress
   //for (i = 0; i < lPCMTTSTriPhoneAudio.Num(); i++)
   //   if (pptp[i])
   //      pptp[i]->Decompress();

   // precalculate can skip
   CListFixed lSkip;
   lSkip.Init (sizeof(DWORD));
   DWORD dwDemiPhone, dwCurDemiPhone;
   DWORD dwSkip;
   lSkip.Required (dwNum * TTSDEMIPHONES);
   for (i = 0; i < dwNum; i++) for (dwDemiPhone = 0; dwDemiPhone < TTSDEMIPHONES; dwDemiPhone++) {
      dwCurDemiPhone = i*TTSDEMIPHONES+dwDemiPhone;

      dwSkip = SynthUnitCanSkip (
         dwDemiPhone,
         pptp[dwCurDemiPhone],
         i ? pptp[(i-1) * TTSDEMIPHONES + (TTSDEMIPHONES-1)] : NULL,   // BUGFIX - Was dwCurDemiPhone-1
         dwCurDemiPhone ? pptp[dwCurDemiPhone-1] : NULL,
         (i+1 < dwNum) ? pptp[(i+1)*TTSDEMIPHONES] : NULL,
         (BYTE)padwPhone[i],
         i ? (BYTE) padwPhone[i-1] : bSilence,
         (i+1 < dwNum) ? (BYTE)padwPhone[i+1] : bSilence);

      lSkip.Add (&dwSkip);
   } // i

   // figure out how much to snap to pitch
   //fp fUnits = max(m_dwUnits,1);
   //fUnits = log(fUnits) - log(10000.0);   // so if 10,000 units then 0.0
   //fUnits /= (log(100000.0) - log(10000.0)); // so if 100,0000 units, then 1.0
   //fUnits = max(fUnits, 0.0); // limit
   //fUnits = min(fUnits, 1.0); // limit

   int   iSnapToPitch = 0; // 0 => use algorithsm, 1 = always snap to original, -1 no change (used desired pitch)
   BOOL fFullPCM = fDisablePCM ? FALSE : m_fFullPCM;

#ifdef NOMODS_DISABLEPCM
   fFullPCM = FALSE;
#endif

#ifdef NOMODS_SNAPTOPITCH
   iSnapToPitch = -1;
#endif
#ifdef NOMODS_ORIGPITCH
   iSnapToPitch = 1;
#endif

   // if specified absolute pitch then no snap-to-pitch
   if (fAbsPitch)
      iSnapToPitch = -1;
   DWORD *padwSkip = (DWORD*)lSkip.Get(0);

   // precalculate the joins
   CMem memSUJI;
   if (!memSUJI.Required (dwNum * TTSDEMIPHONES * sizeof(SYNTHUNITJOININFO))) {
      m_fTTSWaveDisableCompress = FALSE;
      return FALSE;
   }
   EMTJOINFINDBEST em;
   memset (&em, 0, sizeof(em));
   em.dwType = 30;
   em.dwNum = dwNum;
   em.dwNumDemi = dwNum * TTSDEMIPHONES;
   em.pptp = pptp;
   em.paSUJI = (PSYNTHUNITJOININFO) memSUJI.p;
   em.iTTSQuality = iTTSQuality;
   em.padwSkip = padwSkip;
   if (pProgress)
      pProgress->Push(0.5, 0.9);
   ThreadLoop (0, em.dwNumDemi, 1, &em, sizeof(em), pProgress);
   if (pProgress)
      pProgress->Pop ();

   // BUGFIX - Moved filling the wave in until after the pitch points
   // so can normalize the voice-box according to pitch
   // fill it in...
   CListFixed lBlendPhase;
   lBlendPhase.Init (sizeof(BLENDPHASEINFO));
   dwCur = 0;
   PCMTTSTriPhoneAudio apTPAHistory[2];
   DWORD adwHistory[3];
   memset (apTPAHistory, 0, sizeof(apTPAHistory));
   memset (adwHistory, 0, sizeof(adwHistory));
   if (pProgress)
      pProgress->Push(0.9, 1.0);

   // how many units, and how much allowed to hard-mod to the original
   fp fModAmount = log((fp)m_dwUnits) - log((fp)MODDURATION_UNITS_START);
   fModAmount /= (log((fp)MODDURATION_UNITS_STOP) - log((fp)MODDURATION_UNITS_START));
   fModAmount = max(fModAmount, 0.0);
   fModAmount = min(fModAmount, 1.0);
   fp fModDuration = fModAmount;
   fp fModPitch = fModAmount;


   int iDurationMod;
   DWORD j;
   DWORD *padwIndex = (DWORD*)plBookmarkIndex->Get(0);

   for (i = 0; i < dwNum; dwCur += padwDur[i++]) {
      if (!(i%4) && pProgress)
         pProgress->Update ((fp)i / (fp)dwNum);

      // if is unvoiced then don't snap
      // fp fSnapToPitchThis = fSnapToPitch;
      BOOL fHasPitch = TRUE;
      PLEXPHONE plp = pLex ? pLex->PhonemeGetUnsort ((BYTE)padwPhone[i]) : NULL;
      PLEXENGLISHPHONE ple = plp ? MLexiconEnglishPhoneGet(plp->bEnglishPhone) : NULL;
      if (!ple || !(ple->dwCategory & PIC_VOICED))
         fHasPitch = FALSE;
         // fSnapToPitchThis = 0;

      // precalculate the duration per individual demiphone
      DWORD adwDurPerDemiphone[TTSDEMIPHONES];
      for (dwDemiPhone = 0; dwDemiPhone < TTSDEMIPHONES; dwDemiPhone++) {
         DWORD dwLen = padwDur[i];
         adwDurPerDemiphone[dwDemiPhone] = dwLen * (dwDemiPhone+1) / TTSDEMIPHONES - dwLen * dwDemiPhone / TTSDEMIPHONES;
      }

      DWORD dwCurDemiPhoneOffset = dwCur;
      for (dwDemiPhone = 0; dwDemiPhone < TTSDEMIPHONES; dwCurDemiPhoneOffset += adwDurPerDemiphone[dwDemiPhone++]) {
         dwCurDemiPhone = i*TTSDEMIPHONES+dwDemiPhone;

         DWORD dwStart = dwCurDemiPhoneOffset; // dwLen * dwDemiPhone / TTSDEMIPHONES + dwCur;
         DWORD dwEnd = dwStart + adwDurPerDemiphone[dwDemiPhone]; // dwLen * (dwDemiPhone+1) / TTSDEMIPHONES + dwCur;
         if (dwEnd <= dwStart)
            continue;


         // sytnhesize
         if (!SynthUnit (fModDuration,
            dwCurDemiPhone, pWave, plSRDETAILEDPHASE,
            &lPitchOrig, // fSnapToPitchThis,
               // BUGFIX - Was using (fHasPitch ? pfPitchOrig : NULL), but just use pfPitchOrig for ALL
            pptp[dwCurDemiPhone], pptpPros[i],
            dwStart, dwEnd,
            dwCurDemiPhone ? pptp[dwCurDemiPhone-1] : NULL,
            i ? pptpPros[i-1] : NULL,
            (dwCurDemiPhone+1 < dwNum*TTSDEMIPHONES) ? pptp[dwCurDemiPhone+1] : NULL,
            (i+1 < dwNum) ? pptpPros[i+1] : NULL,
            pVoiceMod,
            padwSkip[dwCurDemiPhone],
            dwCurDemiPhone ? padwSkip[dwCurDemiPhone-1] : 0,
            (dwCurDemiPhone+1 < dwNum*TTSDEMIPHONES) ? padwSkip[dwCurDemiPhone+1] : 0,
            &apTPAHistory[0], &apTPAHistory[1],
            &adwHistory[0], &adwHistory[1], &adwHistory[2],
            iTTSQuality,
            (PSYNTHUNITJOININFO) memSUJI.p + dwCurDemiPhone,
            plPSOLASTRUCT,
            &iDurationMod)) {

               m_fTTSWaveDisableCompress = FALSE;
               return FALSE;
            }

         // some values may have been changed
         paSDP = (PSRDETAILEDPHASE) plSRDETAILEDPHASE->Get(0);
         pfPitchOrig = (fp*) lPitchOrig.Get(0);
         dwNumCurve = (DWORD)((int)dwNumCurve + iDurationMod);
         adwDurPerDemiphone[dwDemiPhone] = (DWORD)((int)adwDurPerDemiphone[dwDemiPhone] + iDurationMod);

         // if can skip the left then have a boundary that need to remember
         if (padwSkip[dwCurDemiPhone] & (0x04 | 0x08)) {
            BLENDPHASEINFO bpi;
            bpi.dwSkip = padwSkip[dwCurDemiPhone];
            bpi.dwTime = dwStart;
            lBlendPhase.Add (&bpi);
         }
      } // dwDemiPhone

      // since SynthUnit() may have modified the duration, go back and store
      // the new duration
      padwDur[i] = 0;
      for (dwDemiPhone = 0; dwDemiPhone < TTSDEMIPHONES; dwDemiPhone++)
         padwDur[i] += adwDurPerDemiphone[dwDemiPhone];

      // get the phoneme name
      plp = pLex->PhonemeGetUnsort (PhonemeBackoff((BYTE)padwPhone[i], pLex, bSilence));

      // add the phoneme
      WVPHONEME wp;
      memset (&wp, 0, sizeof(wp));
      if (plp)
         memcpy (wp.awcNameLong, plp->szPhoneLong, min((wcslen(plp->szPhoneLong)+1)*sizeof(WCHAR), sizeof(wp.awcNameLong)));
      wp.dwEnglishPhone = plp ? plp->bEnglishPhone : 0;
      wp.dwSample = dwCur * pWave->m_dwSRSkip;
      pWave->m_lWVPHONEME.Add (&wp);

      // add the bookmarks that match
      padwIndex = (DWORD*)plBookmarkIndex->Get(0);
      for (j = 0; j < plBookmarkIndex->Num(); j++, padwIndex++)
         if (padwIndex[0] == i) {
            DWORD dwSample = dwCur * pWave->m_dwSRSkip;
            pWave->m_lBookmarkIndex.Add (&dwSample);
            pWave->m_lBookmarkString.Add (plBookmarkString->Get(j), plBookmarkString->Size(j));
         }

      // add the word?
      if (papszWord) {
         if ((i == 0) || papszWord[i] != papszWord[i-1]) {
            BYTE abHuge[512];
            PWVWORD pww = (PWVWORD)abHuge;
            PWSTR psz = (PWSTR)(pww+1);
            psz[0] = 0;
            if (papszWord[i] && (wcslen(papszWord[i])*sizeof(WCHAR) < sizeof(abHuge)-sizeof(WVWORD)-1))
               wcscpy (psz, papszWord[i]);

            pww->dwSample = dwCur * pWave->m_dwSRSkip;

            pWave->m_lWVWORD.Add (abHuge, sizeof(WVWORD) + (wcslen(psz)+1)*sizeof(WCHAR));
         }
      }
      else {   // no words
         if ((i==0) || (padwWord[i] != padwWord[i-1]) ) {
            BYTE abHuge[512];
            PWVWORD pww = (PWVWORD)abHuge;
            PWSTR psz = (PWSTR)(pww+1);
            pww->dwSample = dwCur * pWave->m_dwSRSkip;
            psz[0] = 0;
            if (padwWord[i] != -1)
               m_pLexWords->WordGet (padwWord[i], psz, sizeof(abHuge)-sizeof(WVWORD), NULL);
            pWave->m_lWVWORD.Add (abHuge, sizeof(WVWORD) + (wcslen(psz)+1)*sizeof(WCHAR));
         }
      }
   } // i
   if (pProgress)
      pProgress->Pop();

   // final bookmark
   padwIndex = (DWORD*)plBookmarkIndex->Get(0);
   for (j = 0; j < plBookmarkIndex->Num(); j++, padwIndex++)
      if (padwIndex[0] >= dwNum) {
         DWORD dwSample = dwCur * pWave->m_dwSRSkip;
         pWave->m_lBookmarkIndex.Add (&dwSample);
         pWave->m_lBookmarkString.Add (plBookmarkString->Get(j), plBookmarkString->Size(j));
      }

   //for (i = 0; i < lPCMTTSTriPhoneAudio.Num(); i++)
   //   if (pptp[i])
   //      pptp[i]->Compress();

   // clear out any remaining audio with silence
   if (dwCur < pWave->m_dwSRSamples) {
      SynthUnit (fModDuration, 0, pWave, plSRDETAILEDPHASE, &lPitchOrig /*pfPitchOrig, fSnapToPitch*/, NULL, NULL, dwCur, pWave->m_dwSRSamples,
         NULL, NULL, NULL, NULL, pVoiceMod, 0, 0, 0,
         NULL, NULL, NULL, NULL, NULL,
         iTTSQuality, NULL, plPSOLASTRUCT, &iDurationMod);

      paSDP = (PSRDETAILEDPHASE) plSRDETAILEDPHASE->Get(0);
      pfPitchOrig = (fp*) lPitchOrig.Get(0);
      dwNumCurve = (DWORD)((int)dwNumCurve + iDurationMod);
   }



   // figure out the pitch points
   // DWORD j;
   CListFixed lPitchF0ExtraCurve, lPitchF0FujiCurve, lPhoneAtPitchCurve, lPitchConfidenceAtPitchCurve;
   lPitchF0ExtraCurve.Init (sizeof(fp));   // BUGFIX - Changed pitch curve to filtered, not spline
   lPitchF0FujiCurve.Init (sizeof(fp));   // BUGFIX - Changed pitch curve to filtered, not spline
   lPhoneAtPitchCurve.Init (sizeof(DWORD));
   lPitchConfidenceAtPitchCurve.Init (sizeof(fp));
   DWORD dwFuji;
   fp fNegativeOne = -1.0;
   for (dwFuji = 0; dwFuji < 2; dwFuji++) {
      dwCur = 0;
      PCListVariable plPitch = dwFuji ? plPitchF0Fuji : plPitchF0Extra;
      PCListFixed plPitchCurve = dwFuji ? &lPitchF0FujiCurve : &lPitchF0ExtraCurve;

      for (i = 0; i < plPitch->Num(); dwCur += padwDur[i++]) {
         DWORD dwSize = (DWORD)plPitch->Size(i);
         dwSize /= sizeof(fp);
         if (!dwSize)
            continue;   // no data
         fp *pfPitch = (fp*)plPitch->Get(i);

   #if 0 // dont need this anymore def NOMODS_ORIGPITCH
         fp afPitchTemp[4];
         afPitchTemp[0] = afPitchTemp[1] = pptp[i*TTSDEMIPHONES+0] ? pptp[i*TTSDEMIPHONES+0]->m_fOrigPitch : 50;
         afPitchTemp[2] = afPitchTemp[3] = pptp[i*TTSDEMIPHONES+1] ? pptp[i*TTSDEMIPHONES+1]->m_fOrigPitch : 50;
         pfPitch = afPitchTemp;
         dwSize = sizeof(afPitchTemp) / sizeof(fp);
   #endif

         // loop over sub-points
         for (j = 0; j < dwSize; j++) {
            DWORD dwDemi = j * TTSDEMIPHONES / dwSize;
            DWORD dwDemiCur = i * TTSDEMIPHONES + dwDemi;
            fp fNegativeOne = -1.0;

            fp fTime = (fp)dwCur + (fp)padwDur[i] / (fp)(dwSize+1) * (fp)(j+1);
            fp fVal = pfPitch[j];
            int iTime = (int)(fTime + .5);

            // while pitch curve doesn't include enough points, add -1 values
            // to list so that will be filled in later
            if (iTime >= 0) {
               plPitchCurve->Required ((DWORD)iTime+1);
               if (!dwFuji) {
                  lPhoneAtPitchCurve.Required ((DWORD)iTime+1);
                  lPitchConfidenceAtPitchCurve.Required ((DWORD)iTime+1);
               }
            }
            while (iTime > (int)plPitchCurve->Num()) {
               plPitchCurve->Add (&fUnk);
               if (!dwFuji) {
                  lPhoneAtPitchCurve.Add (&padwPhone[i]);

                  fp f = pptp[dwDemiCur] ? pptp[dwDemiCur]->m_afPitchConfidence[dwDemi] : fNegativeOne;
                  lPitchConfidenceAtPitchCurve.Add (&f);
               }
            }

            // add final one, which is point
            if (iTime <= (int)plPitchCurve->Num()) {
               // BUGFIX - If pitch <= 0 then add Unknown
               plPitchCurve->Add ((fVal <= 0) ? &fUnk : &fVal);
               if (!dwFuji) {
                  lPhoneAtPitchCurve.Add (&padwPhone[i]);
                  fp f = pptp[dwDemiCur] ? pptp[dwDemiCur]->m_afPitchConfidence[dwDemi] : fNegativeOne;
                  lPitchConfidenceAtPitchCurve.Add (&f);
               }
            }
         } // j
      } // i

      // extend the pitch curve so it's as large as pWave->m_adwPitchSamples[PITCH_F0]
      while (plPitchCurve->Num() < dwNumCurve) {
         plPitchCurve->Add (&fUnk);

         DWORD dw = bSilence;
         if (!dwFuji) {
            lPhoneAtPitchCurve.Add (&dw);
            lPitchConfidenceAtPitchCurve.Add (&fNegativeOne);
         }
      }
   } // dwFuji
   fp *pfPitchF0Extra = (fp*) lPitchF0ExtraCurve.Get(0);
   fp *pfPitchF0Fuji = (fp*) lPitchF0FujiCurve.Get(0);
   DWORD *padwPhoneAtPitchCurve = (DWORD*) lPhoneAtPitchCurve.Get(0);
   fp *pafPitchConfidenceAtPitchCurve = (fp*) lPitchConfidenceAtPitchCurve.Get(0);



      

   // smooth out the pitch curves
   PitchLowPass (pfPitchF0Extra, dwNumCurve,
         0,
         1.0 /* since related to F0Fuji */);
      // BUGFIX - Changed from SRSAMPLESPERSEC/20 to 0
   PitchLowPass (pfPitchF0Fuji, dwNumCurve,
         SRSAMPLESPERSEC/16,
         pVoiceMod->pSubVoice->m_fAvgPitch / m_fAvgPitch * m_fAvgPitchF0Fuji);

   // if there are any blanks in the original unit that aren't in the new one then
   // fill in
   for (i = 0; i < dwNumCurve; i++)
      if ((pfPitchOrig[i] < 0) && (pfPitchF0Extra[i] * pfPitchF0Fuji[i] >= 0))
         pfPitchOrig[i] = pfPitchF0Extra[i] * pfPitchF0Fuji[i];
   PitchLowPass (pfPitchOrig, dwNumCurve,
         0,
         pVoiceMod->pSubVoice->m_fAvgPitch);

   // weight
   fp fOrigPitch = pVoiceMod->pSubVoice->m_fAvgPitch;

   // BUGFIX - If derived voice, then IGNORE snap-to-pitch since won't
   // make any difference anyhow
   if ((fOrigPitch / m_fAvgPitch < 0.98) || (fOrigPitch / m_fAvgPitch > 1.02)) {
      // large variation
      // BUGFIX - Leave snap-to-pitch on even if have disorted the voice. the snap-to-pitch
      // tends to make phonemes match their pitch angle/delta, which should improve quality
      //if (iSnapToPitch == 0)
      //   iSnapToPitch = -1;
   }
   else {
      if ((iSnapToPitch == 0) && (fModPitch > 0.90) )
         iSnapToPitch = 1; // always go right to the original
         // NOTE: This might mess up transplanted prosody with absolute pitch
   }


   // snap to the original pitch
   for (i = 0; i < dwNumCurve; i++) {
      fp fPitchThis = pfPitchF0Extra[i] * pfPitchF0Fuji[i]; // BUGFIX - Was = pfPitchOrig[i]

      // if voice is permanently pitch shifted then take this into account
      fPitchThis = fPitchThis * max(m_fAvgPitch, 1.0) / max(fOrigPitch, 1.0);

      // so can get related to original pitch
      fPitchThis /= pfPitchOrig[i]; // BUGFIX - Was pfPitch[i]

      fp fLogPitchThis = log(fPitchThis) / log(2.0);
      fp fPerOctave = UnitScorePitch (this, (BYTE)padwPhoneAtPitchCurve[i], pLex, fLogPitchThis >= 0, fFullPCM,
         pafPitchConfidenceAtPitchCurve[i]);
      fp fScalePow = fabs(fLogPitchThis) * max(fPerOctave, 0.0)  / (fp)SNAPTOSCALEPITCH;
      fScalePow *= SNAPTOSCALEBYUNITS; // so more units, the more it snaps to
      fp fScale = pow ((fp)0.5, fScalePow);
      if (iSnapToPitch > 0)
         fScale = 0; // always go to original pitch
      else if (iSnapToPitch < 0)
         fScale = 1.0;  // always go to asked-for pitch

      pfPitchF0Extra[i] = pow ((fp)2.0, fScale * fLogPitchThis) * pfPitchOrig[i];  // BUGFIX - Was * pfPitch[i]

      // reapply voice mods
      pfPitchF0Extra[i] /= (max(m_fAvgPitch, 1.0) / max(fOrigPitch, 1.0));  // BUGFIX - Put this back in

      // NOTE: Intentionally NOT adding Fuji back in!
      // After this point pfPitchF0Extra is in Hz
   }

   PBLENDPHASEINFO paBlendPhaseAt;
   int iBlurCenter;
#ifndef NOMODS_ORIGPITCH
   // AWLAYS do blending at unit borders
   DWORD dwBlendHalf = pWave->m_dwSamplesPerSec / pWave->m_dwSRSkip / 40;  // 1/40th sec
   dwBlendHalf = max(dwBlendHalf, 1);

   // blend only the borders of non-contiguous units
   paBlendPhaseAt = (PBLENDPHASEINFO)lBlendPhase.Get(0);
   for (i = 0; i < lBlendPhase.Num(); i++, paBlendPhaseAt++) {
      iBlurCenter = (int) paBlendPhaseAt->dwTime;
      if ((iBlurCenter < 1) || (iBlurCenter >= (int)dwNumCurve))
         continue;   // cant blur here because at the edge already

      fp fAverage = (pfPitchF0Extra[iBlurCenter-1] + pfPitchF0Extra[iBlurCenter]) / 2.0;

      int iOffset, iCur;
      for (iOffset = -(int)dwBlendHalf; iOffset < (int)dwBlendHalf; iOffset++) {
         iCur = iOffset + iBlurCenter;
         if ((iCur < 0) || (iCur >= (int)dwNumCurve))
            continue;

         fp fAlpha = (iOffset >= 0) ? ((fp)iOffset+0.5) : (-iOffset - 0.5);
         fAlpha /= (fp)dwBlendHalf;

         pfPitchF0Extra[iCur] = fAlpha * pfPitchF0Extra[iCur] + (1.0 - fAlpha) * fAverage;
      } // iOffset
   } // i

   // sometimes do even more blending
   DWORD dwLowPass = SRSAMPLESPERSEC/40;
   dwLowPass = (DWORD)floor((1.0 - fModPitch) * (fp)dwLowPass + 0.0 * fModPitch + 0.5);
   if (dwLowPass)
      // blend everything
      // because doing fSnapToPitch, must to a bit of low-pass filtering
      PitchLowPass (pfPitchF0Extra, dwNumCurve,
            dwLowPass,
            pVoiceMod->pSubVoice->m_fAvgPitch);
         // BUGFIX - Changed from SRSAMPLESPERSEC/20 to 0
#endif

   // loop through all the pitch points
   DWORD dwCurCurve = 0;
   for (i = 0; i < pWave->m_adwPitchSamples[PITCH_F0]; i++) {
      fp fPitch = pfPitchF0Extra[i];
      fPitch = max(fPitch, 1);   // always at least some frequency..
      for (j = 0; j < pWave->m_dwChannels; j++) {
         pWave->m_apPitch[PITCH_F0][i*pWave->m_dwChannels+j].fFreq = fPitch;
         pWave->m_apPitch[PITCH_F0][i*pWave->m_dwChannels+j].fStrength = 1;

         pWave->m_apPitch[PITCH_SUB][i*pWave->m_dwChannels+j].fFreq = max(pfPitchF0Fuji[i], 1.0);
         pWave->m_apPitch[PITCH_SUB][i*pWave->m_dwChannels+j].fStrength = 1;

      } // j
   } // i
   pWave->m_afPitchMaxStrength[PITCH_F0] = 1;
   pWave->m_afPitchMaxStrength[PITCH_SUB] = 10.0;   // so can see if is synth prosody

   // add a final silence...
   // BYTE bSilence = (BYTE)pLex->PhonemeFindUnsort(pLex->PhonemeSilence());
   if (dwNum && ((BYTE)padwPhone[dwNum-1] != bSilence)) {
            // BUGFIX - Was testing padwPhone[dwNum] - which caused occasional crashes
      PLEXPHONE plp = pLex->PhonemeGetUnsort (bSilence);

      WVPHONEME wp;
      memset (&wp, 0, sizeof(wp));
      if (plp)
         memcpy (wp.awcNameLong, plp->szPhoneLong, min((wcslen(plp->szPhoneLong)+1)*sizeof(WCHAR), sizeof(wp.awcNameLong)));
      wp.dwEnglishPhone = plp ? plp->bEnglishPhone : 0;
      wp.dwSample = dwCur * pWave->m_dwSRSkip;
      pWave->m_lWVPHONEME.Add (&wp);
      
      // final word
      BYTE abHuge[512];
      PWVWORD pww = (PWVWORD)abHuge;
      PWSTR psz = (PWSTR)(pww+1);
      pww->dwSample = dwCur * pWave->m_dwSRSkip;
      psz[0] = 0;
      pWave->m_lWVWORD.Add (abHuge, sizeof(WVWORD) + (wcslen(psz)+1)*sizeof(WCHAR));

      // NOTE: DON'T need to check for adding final bookmark
   }

#ifdef NOMODS_SRDETAILEDPHASE
   plSRDETAILEDPHASE = NULL;
#endif

#ifndef NOMODS_ALIGNPCM
   // BUGFIX - Only do if full PCM because cause wierd boundaries at times
   if (fFullPCM && !(plPSOLASTRUCT && plPSOLASTRUCT->Num()) && !PhaseModelGet(0,FALSE) )
      pWave->SRFEATUREAlignPCM (fFullPCM, pLex, plSRDETAILEDPHASE);
#endif


#if 0 // test with no phase-per-octave
   for (i = 0; i < pWave->m_dwSRSamples; i++)
      memset (paSDP[i].afVoicedPhase, 0, sizeof(paSDP[i].afVoicedPhase));
#endif // 0

#if 0 // test with no phase-per-harmonic
   for (i = 0; i < pWave->m_dwSRSamples; i++)
      memset (paSDP[i].afHarmPhase, 0, sizeof(paSDP[i].afHarmPhase));
#endif // 0

   DWORD dwPhaseBlurDistance = (DWORD) ((fp)pWave->m_dwSRSAMPLESPERSEC * PHASEBLURWIDTH_BASE + 0.5);    // 1/32th of a second to left and right, for lowest
      // BUGFIX - Was doing /36, but * 0.3 is basically the same
   DWORD dwPhaseBlurDistanceTop = (DWORD) ((fp)pWave->m_dwSRSAMPLESPERSEC * PHASEBLURWIDTH_TOP + 0.5);
   paBlendPhaseAt = (PBLENDPHASEINFO)lBlendPhase.Get(0);
   for (i = 0; i < lBlendPhase.Num(); i++, paBlendPhaseAt++) {
      iBlurCenter = (int) paBlendPhaseAt->dwTime;
      if ((iBlurCenter < 1) || (iBlurCenter >= (int)pWave->m_dwSRSamples))
         continue;   // cant blur here because at the edge already

      BOOL fShift = TRUE, fBlend = TRUE;
#ifdef NOMODS_BLURPHASEDETAILED
      fBlend = FALSE;
#endif
#ifdef NOMODS_BENDPHASEDETAILED
      fShift = FALSE;
#endif
      SRDETAILEDPHASEShiftAndBlend (paSDP, pWave->m_dwSRSamples, (DWORD)iBlurCenter,
                                   dwPhaseBlurDistance, dwPhaseBlurDistanceTop, fShift, fBlend);

   } // i

   // blend in phases where units change
#define NUMPHASEBLUR       (SRPHASENUM/2)          // since dont need to blur all the way up 
   // BUGFIX - Was /4, but upped to /2
#ifndef NOMODS_BLURPHASE
   // BUGFIX - Only have one blur angle over the whole boundary and see how well works
   // since trying half for each side caused more problems
   fp afBlurAngle[NUMPHASEBLUR][2];
   fp afBlurAngleDetailed[NUMPHASEBLUR][2];  // used for detailed phase
   DWORD dwCount, dwWeightScale;
   DWORD dwPhaseBlurDistance = (DWORD) ((fp)pWave->m_dwSRSAMPLESPERSEC * PHASEBLURWIDTH_BASE + 0.5);    // 1/32th of a second to left and right, for lowest
      // BUGFIX - Was doing /36, but * 0.3 is basically the same
   DWORD dwPhaseBlurDistanceTop = (DWORD) ((fp)pWave->m_dwSRSAMPLESPERSEC * PHASEBLURWIDTH_TOP + 0.5);
      // BUGFIX - Up to 1/16th of a second since still getting phase problems
      // BUGFIX - 1/16th too large. Go to 1/24th
      // BUGFIX - Go to 1/36th (from 1/24th) of a second since put in SRFEATUREAlignPCM
   PBLENDPHASEINFO paBlendPhaseAt = (PBLENDPHASEINFO)lBlendPhase.Get(0);
   for (i = 0; i < lBlendPhase.Num(); i++, paBlendPhaseAt++) {
      int iBlurCenter = (int) paBlendPhaseAt->dwTime;
      if ((iBlurCenter < 1) || (iBlurCenter >= (int)pWave->m_dwSRSamples))
         continue;   // cant blur here because at the edge already

      // determine the angles
      DWORD dwSide, dwOffset,dwPhaseBlurDistThis, dwWeight;
      fp fAngle, fX, fY, fXDetailed, fYDetailed;
      int iPhaseCur;
      for (j = 0; j < NUMPHASEBLUR; j++) {
         dwPhaseBlurDistThis = (dwPhaseBlurDistance * (NUMPHASEBLUR-j) + dwPhaseBlurDistanceTop*j) / NUMPHASEBLUR;
         dwPhaseBlurDistThis = max(dwPhaseBlurDistThis, 1);

         fX = fY = fXDetailed = fYDetailed = 0;
         dwCount = 0;

         for (dwSide = 0; dwSide < 2; dwSide++) {
            // BUGFIX include wrong sides, but underweight, otherwise too many phase flips
            DWORD dwSkipFlag = dwSide ? 0x04 : 0x08;
            if (!(paBlendPhaseAt->dwSkip & dwSkipFlag))
               dwWeightScale = 1;   // dont use phase from this side
            else
               dwWeightScale = 2;

            for (dwOffset = 0; dwOffset < dwPhaseBlurDistThis; dwOffset++) {
               if (!dwSide)
                  iPhaseCur = iBlurCenter + (int)dwOffset;
               else
                  iPhaseCur = iBlurCenter - (int)dwOffset - 1;
               if ((iPhaseCur < 0) || (iPhaseCur >= (int)pWave->m_dwSRSamples))
                  continue;   // out of range

               dwWeight = (dwPhaseBlurDistThis - dwOffset) * dwWeightScale;

               fAngle = (fp)pWave->m_paSRFeature[iPhaseCur].abPhase[j] / 256.0 * 2.0 * PI;
               fX += sin(fAngle) * (fp)dwWeight;
               fY += cos(fAngle) * (fp)dwWeight;

               fXDetailed += paSDP[iPhaseCur].afHarmPhase[j][0] * (fp)dwWeight;
               fYDetailed += paSDP[iPhaseCur].afHarmPhase[j][1] * (fp)dwWeight;

               dwCount += dwWeight;
            } // dwOffset

         } // dwSide

         if (dwCount) {
            fX /= (fp)dwCount;
            fY /= (fp)dwCount;
            fXDetailed /= (fp)dwCount;
            fYDetailed /= (fp)dwCount;
         }
         afBlurAngle[j][0] = fX;
         afBlurAngle[j][1] = fY;

         afBlurAngleDetailed[j][0] = fXDetailed;
         afBlurAngleDetailed[j][1] = fYDetailed;
      } // j

      // loop over all phases
      for (j = 0; j < NUMPHASEBLUR; j++) {
         dwPhaseBlurDistThis = (dwPhaseBlurDistance * (NUMPHASEBLUR-j) + dwPhaseBlurDistanceTop*j) / NUMPHASEBLUR;
         dwPhaseBlurDistThis = max(dwPhaseBlurDistThis, 1);

         // loop over first left, then right
         for (dwSide = 0; dwSide < 2; dwSide++) {
            // if get here, blend in phase with both sides, even if no pitch

            for (dwOffset = 0; dwOffset < dwPhaseBlurDistThis; dwOffset++) {
               if (!dwSide)
                  iPhaseCur = iBlurCenter + (int)dwOffset;
               else
                  iPhaseCur = iBlurCenter - (int)dwOffset - 1;
               if ((iPhaseCur < 0) || (iPhaseCur >= (int)pWave->m_dwSRSamples))
                  continue;   // out of range

               // not detailed
               fAngle = (fp)pWave->m_paSRFeature[iPhaseCur].abPhase[j] / 256.0 * 2.0 * PI;
               fX = sin(fAngle);
               fY = cos(fAngle);

               fX = (fp)(dwOffset+1) * fX + (fp)(dwPhaseBlurDistThis-dwOffset) * afBlurAngle[j][0];
               fY = (fp)(dwOffset+1) * fY + (fp)(dwPhaseBlurDistThis-dwOffset) * afBlurAngle[j][1];

               fAngle = atan2(fX, fY);
               fAngle = myfmod(fAngle, 2.0 * PI);
               pWave->m_paSRFeature[iPhaseCur].abPhase[j] = (BYTE)(fAngle / 2.0 / PI * 256.0);

               // detailed
               paSDP[iPhaseCur].afHarmPhase[j][0] = 
                  ((fp)(dwOffset+1) * paSDP[iPhaseCur].afHarmPhase[j][0] + (fp)(dwPhaseBlurDistThis-dwOffset) * afBlurAngle[j][0]) /
                  (fp)(dwPhaseBlurDistThis+1);
               paSDP[iPhaseCur].afHarmPhase[j][1] = 
                  ((fp)(dwOffset+1) * paSDP[iPhaseCur].afHarmPhase[j][1] + (fp)(dwPhaseBlurDistThis-dwOffset) * afBlurAngle[j][1]) /
                  (fp)(dwPhaseBlurDistThis+1);
            } // dwOffset
         } // dwSide
      } // j

   // also blur the per-octave phase
#define NUMPHASEBLUROCTAVE       (SRDATAPOINTSDETAILED)          // blur all the way up
      fp afBlurAngleOctave[NUMPHASEBLUROCTAVE][2];  // used for detailed phase
      for (j = 0; j < NUMPHASEBLUROCTAVE; j++) {
         dwPhaseBlurDistThis = (dwPhaseBlurDistance * (NUMPHASEBLUROCTAVE-j) + dwPhaseBlurDistanceTop*j) / NUMPHASEBLUROCTAVE;
         dwPhaseBlurDistThis = max(dwPhaseBlurDistThis, 1);

         fXDetailed = fYDetailed = 0;
         dwCount = 0;

         for (dwSide = 0; dwSide < 2; dwSide++) {
            // BUGFIX include wrong sides, but underweight, otherwise too many phase flips
            DWORD dwSkipFlag = dwSide ? 0x04 : 0x08;
            if (!(paBlendPhaseAt->dwSkip & dwSkipFlag))
               dwWeightScale = 1;   // dont use phase from this side
            else
               dwWeightScale = 2;

            for (dwOffset = 0; dwOffset < dwPhaseBlurDistThis; dwOffset++) {
               if (!dwSide)
                  iPhaseCur = iBlurCenter + (int)dwOffset;
               else
                  iPhaseCur = iBlurCenter - (int)dwOffset - 1;
               if ((iPhaseCur < 0) || (iPhaseCur >= (int)pWave->m_dwSRSamples))
                  continue;   // out of range

               dwWeight = (dwPhaseBlurDistThis - dwOffset) * dwWeightScale;

               fXDetailed += paSDP[iPhaseCur].afVoicedPhase[j][0] * (fp)dwWeight;
               fYDetailed += paSDP[iPhaseCur].afVoicedPhase[j][1] * (fp)dwWeight;

               dwCount += dwWeight;
            } // dwOffset

         } // dwSide

         if (dwCount) {
            fXDetailed /= (fp)dwCount;
            fYDetailed /= (fp)dwCount;
         }

         afBlurAngleOctave[j][0] = fXDetailed;
         afBlurAngleOctave[j][1] = fYDetailed;
      } // j

      // loop over all phases
      for (j = 0; j < NUMPHASEBLUROCTAVE; j++) {
         dwPhaseBlurDistThis = (dwPhaseBlurDistance * (NUMPHASEBLUROCTAVE-j) + dwPhaseBlurDistanceTop*j) / NUMPHASEBLUROCTAVE;
         dwPhaseBlurDistThis = max(dwPhaseBlurDistThis, 1);

         // loop over first left, then right
         for (dwSide = 0; dwSide < 2; dwSide++) {
            // if get here, blend in phase with both sides, even if no pitch

            for (dwOffset = 0; dwOffset < dwPhaseBlurDistThis; dwOffset++) {
               if (!dwSide)
                  iPhaseCur = iBlurCenter + (int)dwOffset;
               else
                  iPhaseCur = iBlurCenter - (int)dwOffset - 1;
               if ((iPhaseCur < 0) || (iPhaseCur >= (int)pWave->m_dwSRSamples))
                  continue;   // out of range

               // detailed
               paSDP[iPhaseCur].afVoicedPhase[j][0] = 
                  ((fp)(dwOffset+1) * paSDP[iPhaseCur].afVoicedPhase[j][0] + (fp)(dwPhaseBlurDistThis-dwOffset) * afBlurAngleOctave[j][0]) /
                  (fp)(dwPhaseBlurDistThis+1);
               paSDP[iPhaseCur].afVoicedPhase[j][1] = 
                  ((fp)(dwOffset+1) * paSDP[iPhaseCur].afVoicedPhase[j][1] + (fp)(dwPhaseBlurDistThis-dwOffset) * afBlurAngleOctave[j][1]) /
                  (fp)(dwPhaseBlurDistThis+1);

            } // dwOffset
         } // dwSide
      } // j


   } // i

   // to test with no phase
   // for (i = 0; i <pWave->m_dwSRSamples; i++)
   //   memset (pWave->m_paSRFeature[i].abPhase, 0, sizeof(pWave->m_paSRFeature[i].abPhase));
#endif // ndef NOMODS_BLURPHASE

   // blur the edges
#define BLURSIZE     max(SRSAMPLESPERSEC/100,1)
      // BUGFIX - Was SRSAMPLESPERSEC/50, but made smaller so that better sounding voice
#if 0 // BUGFIX - remove blurring to try to get crisper consonants. helps slightly in female
   note - this wont work properly because have 3x the number of triphones
   dwCur = 0;
   for (i = 0; i < dwNum; dwCur += padwDur[i++]) {
      SRFEATURE asrTemp[BLURSIZE*2];
      if (!dwCur)
         continue;   // nothing to blur with

      // if either is silence then dont bother blurring
      if (((BYTE)padwPhone[i] == bSilence) || ((BYTE)padwPhone[i-1] == bSilence))
         continue;

      // BUGFIX - if adjacent waves then dont blur in
      PCMTTSTriPhoneAudio pLeft = pptp[i-1];
      PCMTTSTriPhoneAudio pRight = pptp[i];
      if (pLeft && pRight && (pLeft->m_wOrigWave == pRight->m_wOrigWave) && (pLeft->m_wOrigPhone+1 == pRight->m_wOrigPhone))
         continue;

      DWORD j;
      int iCur;
      for (j = 0; j < BLURSIZE*2; j++) {
         iCur = (int)dwCur - BLURSIZE + (int)j;
         if ((iCur < 0) || (iCur >= (int)pWave->m_dwSRSamples))
            continue;
         asrTemp[j] = pWave->m_paSRFeature[iCur];
      }

      // blend in
      for (j = 0; j < BLURSIZE*2; j++) {
         iCur = (int)dwCur - BLURSIZE + (int)j;
         if ((iCur < 0) || (iCur >= (int)pWave->m_dwSRSamples))
            continue;
         fp fWeight;
         if (j < BLURSIZE)
            fWeight = BLURSIZE - 1 - j;
         else
            fWeight = j - BLURSIZE;
         fWeight = (fWeight + BLURSIZE + 1) / (fp)(2*BLURSIZE+1);

         SRFEATUREInterpolate (&asrTemp[j],
            &asrTemp[(j < BLURSIZE) ? BLURSIZE : (BLURSIZE-1)],
            fWeight,
            &pWave->m_paSRFeature[iCur]);
      }

   }
#endif // 0

   // will need to compress all units
   // BUGFIX - Must be done later so that don't lose PCM data for psola
   // m_fTTSWaveDisableCompress = FALSE;
   // TTSWaveCompress ();

   return TRUE;
}


/*************************************************************************************
CMTTS::SynthAdjustVolumeRelative - This goes through a generated wave (using the PCM)
and adjusts the volume levels for each phoneme.

inputs
   PCM3DWave         pWave - Wave
   DWORD       *padwDur - Duration for each phone in SRFEATURE time-units (1/100th sec)
   fp          *pafVol - Volume for each phone, relative. 1.0 = no change, 2.0 = 2x as loud, etc.
   DWPRD       dwNum - Number of phonemes
returns
   BOOL - TRUE if success
*/
BOOL CMTTS::SynthAdjustVolumeRelative (PCM3DWave pWave, DWORD *padwDur, fp *pafVol,
                                       DWORD dwNum)
{
#ifdef NOMODS_VOLUME
   return TRUE;
#endif

   // figure out the inflection points
   DWORD i, j, dwCur;
   dwCur = 0;
   CListFixed lVolCurve;
   lVolCurve.Init (sizeof(fp));    // BUGFIX - Make volume curve interpolation
   fp fUnk = -1;
   for (i = 0; i < dwNum; dwCur += padwDur[i++]) {
      // fill in point
      fp fTime = (fp)dwCur + (fp)padwDur[i] / 2.0;  // mid way
      fp fVal = pafVol[i];
      int iTime = (int)(fTime + 0.5);

      if (iTime >= 0)
         lVolCurve.Required ((DWORD)iTime + 1);

      // add in unknowns until get to time
      while (iTime > (int)lVolCurve.Num())
         lVolCurve.Add (&fUnk);

      // add final one, which is point
      if (iTime <= (int)lVolCurve.Num())
         lVolCurve.Add (&fVal);
   } // i
   // extend the pitch curve so it's as large as pWave->m_adwPitchSamples[PITCH_F0]
   lVolCurve.Required (pWave->m_dwSRSamples+1);
   while (lVolCurve.Num() < pWave->m_dwSRSamples+1)
      lVolCurve.Add (&fUnk);


   // smooth this out...
   fp *pfVol = (fp*) lVolCurve.Get(0);
   DWORD dwNumCurve = lVolCurve.Num();
   PitchLowPass (pfVol, dwNumCurve, SRSAMPLESPERSEC / 20, 1);



   // now go through and adjust the volumes
   // BUGFIX - Change this so it scales the SRFEATUREs, not the wave
   fp *pafScale = pfVol;
   DWORD dwNumReg = dwNumCurve;
   for (i = 0; i < pWave->m_dwSRSamples; i++) {
      // how much to scale?
      DWORD dwLeft = i;
      dwLeft = min(dwLeft, dwNumReg-1);

      fp fScale = pafScale[dwLeft];
      fScale = max(fScale, CLOSE);  // so dont get error in log
      fScale = log10(fScale) * 20.0;
      fScale = max(fScale, -64);
      fScale = min(fScale, 64);
      int iScale = (int) fScale;

      PSRFEATURE psr = pWave->m_paSRFeature + i;
      for (j = 0; j < SRDATAPOINTS; j++) {
         int iCur;

         iCur = psr->acNoiseEnergy[j] + iScale;
         iCur = max(iCur, SRABSOLUTESILENCE);
         iCur = min(iCur, 127);
         psr->acNoiseEnergy[j] = (char)iCur;

         iCur = psr->acVoiceEnergy[j] + iScale;
         iCur = max(iCur, SRABSOLUTESILENCE);
         iCur = min(iCur, 127);
         psr->acVoiceEnergy[j] = (char)iCur;
      } // j
   } // i

   return TRUE;
}


/*************************************************************************************
CMTTS::SynthAdjustEnergyPerVolume - Goes through and adjusts by energy per volume,
to simulate louder/quieter voice.

inputs
   PCM3DWave         pWave - Wave
   DWORD       *padwPhone - Pointer to an array of phonemes. Each phoneme is the
               unsorted phone number. The high byte (<< 24) contains 0 if the phone
               is in the middle of a word, 1 on left side, 2 on right side, 3 word by itself
   DWORD       *padwDur - Duration for each phone in SRFEATURE time-units (1/100th sec)
   fp          *pafVol - Volume for each phone, relative. 1.0 = no change, 2.0 = 2x as loud, etc.
   DWPRD       dwNum - Number of phonemes
returns
   BOOL - TRUE if success

BUGBUG - If ever use this may have to counteract modifications in UnitSynth() with TTSOUTPUTVOLUMESCALE
*/
BOOL CMTTS::SynthAdjustEnergyPerVolume (PCM3DWave pWave, DWORD *padwPhone, DWORD *padwDur,
                                       DWORD dwNum)
{
   // NOTE: DON'T need a NOMODS_XXX since if the NOMODS_ENERGYPERVOLUMEGET is
   // set then this will fail anyway

   // lexicon
   PCMLexicon pLex = Lexicon();
   if (!pLex)
      return FALSE;

   // allocate memory to store the energies of all the voices phonemes
   CMem memEnergy;
   if (!memEnergy.Required (pWave->m_dwSRSamples * sizeof(fp)))
      return FALSE;
   fp *pafEnergy = (fp*)memEnergy.p;
   memset (pafEnergy, 0, pWave->m_dwSRSamples * sizeof(fp));
   DWORD i, j, k, dwCur;
   PLEXPHONE plp;
   PLEXENGLISHPHONE pe;
   double fAvgEnergyForVoiced = 0;
   DWORD dwAEFVCount = 0;
   for (i = 0, dwCur = 0; i < dwNum; i++) {
      // get the phoneme
      plp = pLex->PhonemeGetUnsort ((BYTE)padwPhone[i]);
      pe = plp ? MLexiconEnglishPhoneGet(plp->bEnglishPhone) : NULL;

      // calc energy for each
      for (j = 0; j < padwDur[i]; j++, dwCur++) {
         if (dwCur >= pWave->m_dwSRSamples)
            continue;   // shouldnt happen

         pafEnergy[dwCur] = SRFEATUREEnergy (FALSE, pWave->m_paSRFeature + dwCur);

         // if it's voiced, then include in calculations
         if (pe && (pe->dwCategory & PIC_VOICED)) {
            fAvgEnergyForVoiced += pafEnergy[dwCur];
            dwAEFVCount++;
         }
      } // j
   } // i

   if (!dwAEFVCount)
      return TRUE;   // nothing to do
   fAvgEnergyForVoiced /= (double)dwAEFVCount;

   // BUGBUG - in future, shout and speak quietly should be handled by changing
   // calculated average energy, to get right tweaks for voice spectrum

   // loop over again and adjust energies
   char *pacConvertVolume;
   int iVal;
   for (i = 0, dwCur = 0; i < dwNum; i++) {
      // get the phoneme
      plp = pLex->PhonemeGetUnsort ((BYTE)padwPhone[i]);
      pe = plp ? MLexiconEnglishPhoneGet(plp->bEnglishPhone) : NULL;

      // calc energy for each
      for (j = 0; j < padwDur[i]; j++, dwCur++) {
         // ignore if it's ont voiced
         if (!pe || !(pe->dwCategory & PIC_VOICED))
            continue;

         pacConvertVolume = EnergyPerVolumeGet (pafEnergy[dwCur] / fAvgEnergyForVoiced);
         if (!pacConvertVolume)
            continue;

         if (dwCur >= pWave->m_dwSRSamples)
            continue;   // shouldnt happen
         PSRFEATURE psrOrig = pWave->m_paSRFeature + dwCur;

         for (k = 0; k < SRDATAPOINTS; k++) {
            iVal = (int)psrOrig->acVoiceEnergy[k];
            if (iVal < SRABSOLUTESILENCE+10)
               continue;   // dont allow to change at all
            if (pacConvertVolume)
               iVal += (int)pacConvertVolume[k];
            iVal = max(iVal, SRABSOLUTESILENCE);
            iVal = min(iVal, SRMAXLOUDNESS);
            psrOrig->acVoiceEnergy[k] = (char)iVal;
         } // k

#ifndef NOMODS_ENERGYPERVOLUMETUNE
         // BUGFIX - if the energy has changed after doing the volume-based-energy
         // adjust then rescale
         fp fEnergyNew = SRFEATUREEnergy (psrOrig);
         fEnergyNew = pafEnergy[dwCur] / max(fEnergyNew, CLOSE);
         int iDelta = AmplitudeToDb (fEnergyNew * (fp)0x8000);

         iDelta = max(iDelta, -12); // dont make too much change
         iDelta = min(iDelta, 12);

         if (iDelta) for (k = 0; k < SRDATAPOINTS; k++) {
            iVal = (int)psrOrig->acVoiceEnergy[k];
            if (iVal < SRABSOLUTESILENCE+10)
               continue;   // dont allow to change at all
            iVal += iDelta;
            iVal = max(iVal, SRABSOLUTESILENCE);
            iVal = min(iVal, SRMAXLOUDNESS);
            psrOrig->acVoiceEnergy[k] = (char)iVal;
         } // k
#endif

      } // j
   } // i


   return TRUE;
}


/*************************************************************************************
CMTTS::SynthGenWaveInt - Generates a final wave for synhtesis.
Given the phonemes, their durations, and the words they're
in, this fills in the wave's wave, SRFEATUREs, phoneme, and word structures.

inputs
   DWORD             dwCandidates - Number of candidates to synhthesize. The candidate
                     with the best acoustic score is used.
   PSYNGENFEATURESCANDIDATE   paCandidates - Array of candidates. padwWord is NOT required
                     by SyntheGenWaveInt(), and will be modified by this call.
   DWORD             dwSamplesPerSec - Sampling rate
   int               iTTSQuality - TTS quality. 0 for fast, 1 for normal, 2 for slow, 3 for best, 4 for multipass
   BOOL              fDisablePCM - If TRUE, then PCM is disabled for speakin.
   PCProgressSocket pProgress - To measure progress with
   PTTSVOICEMOD   pVoiceMod - Parameters controlling how the voice is modified by
                  the original. Generally, pass in NULL. Only internal functions
                  will pass in non-null.
   PCProgressWaveTTS pProgressWave - This is where the audio samples are sent out to
returns
   BOOL - TRUE if success
*/

/* Used in PSYNGENFEATURESCANDIDATE
   DWORD       *padwPhone - Pointer to an array of phonemes. Each phoneme is the
               unsorted phone number. The high byte (<< 24) contains 0 if the phone
               is in the middle of a word, 1 on left side, 2 on right side, 3 word by itself
   PWSTR       *paszWord - Word string associated with each phoneme, or NULL if none associated.
               If two or more phonemes are from the same word they should point to
               the same word string.
   DWORD       *padwDur - Duration for each phone in SRFEATURE time-units (1/100th sec)
   PCListVariable plPitch - This list has one element per phoneme to indicate the
               fundamental pitch of the voice (in Hz). Each element is N * sizeof(fp),
               where N is from 0 to whatever. It indicates the number of pitch points
               stored per phoneme.
   fp          *pafVol - Volume for each phone, relative. 1.0 = no change, 2.0 = 2x as loud, etc.
               If this is NULL then no volume change is done
   BOOL        fVolAbsoluate - If TRUE pafVol is the absolute volume to use (so can get this
               from transplanted prosody); in which case it's the CalcSREnergyRange() of
               the phoneme. If FALSE, relative volume.
   BOOL        fAbsPitch - If TRUE absolute pitch. If FALSE, can fudge on pitch and choose one
               closer to unit.
   DWORD       dwNum - Number of phonemes

   PTTSGLOBALSTATE paTTSGS - Global state per phoneme. Can be NULL, but no emotion then.

   no padwWord!!!!
*/

BOOL CMTTS::SynthGenWaveInt (DWORD dwCandidates, PSYNGENFEATURESCANDIDATE paCandidates,
                             DWORD dwSamplesPerSec,
                             int iTTSQuality, BOOL fDisablePCM, PCProgressSocket pProgress,
                              PTTSVOICEMOD pVoiceMod, PCProgressWaveTTS pProgressWave)
{
   // if multipass, can't really decide on a candidate now because might mess up
   // prosody, so just pass on through
   if (dwCandidates >= 2)
      return SynthGenWaveIntNewWave (dwCandidates, paCandidates, dwSamplesPerSec,
         iTTSQuality, fDisablePCM, pProgress, pVoiceMod, pProgressWave);

   // silence
   PCMLexicon pLex = pVoiceMod ? pVoiceMod->pLex : NULL;
   if (!pLex)
      pLex = Lexicon();
   BYTE bSilence = (BYTE) pLex->PhonemeFindUnsort(pLex->PhonemeSilence());

   // else, search for silence
   DWORD dwStart = 0;
   DWORD i, j;
   SYNGENFEATURESCANDIDATE cNew;
   CListVariable lTempF0Extra, lTempF0Fuji, lTempBookmarkString;
   CListFixed lTempBookmarkIndex;
   BOOL fRet;
   BOOL fSilenceAtStart = FALSE;
   while (dwStart < paCandidates->dwNum) {
      // find silence
      BOOL fSilenceAtEnd = FALSE;
      for (i = dwStart; i < paCandidates->dwNum; i++)
         if ((BYTE) paCandidates->padwPhone[i] == bSilence) {
            i++;  // to include the silence
            if (i < paCandidates->dwNum)
               fSilenceAtEnd = TRUE;
            break;
         }

      // if had some silence before then include half the silence in this segment
      // so no sudden pitch shifts
      DWORD dwStartWithSilence = dwStart;
      DWORD dwStartSilenceDur = 0;
      if (fSilenceAtStart) {
         dwStartWithSilence--;

         // cache away
         dwStartSilenceDur = paCandidates->padwDur[dwStartWithSilence];

         // use half the silence
         paCandidates->padwDur[dwStartWithSilence] -= (paCandidates->padwDur[dwStartWithSilence]/2);
      }

      // cache end silence
      DWORD dwEndSilenceDur = 0;
      if (fSilenceAtEnd) {
         // cache
         dwEndSilenceDur = paCandidates->padwDur[i-1];

         // halve this
         paCandidates->padwDur[i-1] /= 2;
      }

      // fill in lTemp
      lTempF0Extra.Clear();
      lTempF0Fuji.Clear();
      for (j = dwStartWithSilence; j < i; j++) {
         lTempF0Extra.Add (paCandidates->plPitchF0Extra->Get(j), paCandidates->plPitchF0Extra->Size(j));
         lTempF0Fuji.Add (paCandidates->plPitchF0Fuji->Get(j), paCandidates->plPitchF0Fuji->Size(j));
      }

      // bookmark index
      lTempBookmarkIndex.Init (sizeof(DWORD));
      lTempBookmarkString.Clear();
      DWORD *padwIndex = (DWORD*)paCandidates->plBookmarkIndex->Get(0);
      for (j = 0; j < paCandidates->plBookmarkIndex->Num(); j++) {
         if (padwIndex[j] < dwStartWithSilence)
            continue;

         // fancy checks to get bookmark at end
         if ( (i < paCandidates->dwNum) && (padwIndex[j] >= i))
            continue;   // not in this one

         DWORD dwNewIndex = padwIndex[j] - dwStartWithSilence;
         lTempBookmarkIndex.Add (&dwNewIndex);
         lTempBookmarkString.Add (paCandidates->plBookmarkString->Get(j), paCandidates->plBookmarkString->Size(j));
      } // j

      // info
      cNew = paCandidates[0];
      cNew.dwNum = i - dwStartWithSilence;
      cNew.padwDur += dwStartWithSilence;
      cNew.padwPhone += dwStartWithSilence;
      cNew.padwWord += dwStartWithSilence;
      cNew.pafVol += dwStartWithSilence;
      cNew.pafAccent += dwStartWithSilence;
      cNew.papszWord += dwStartWithSilence;
      if (cNew.paTTSGS)
         cNew.paTTSGS += dwStartWithSilence;
      cNew.plPitchF0Extra = &lTempF0Extra;
      cNew.plPitchF0Fuji = &lTempF0Fuji;
      cNew.plBookmarkIndex = &lTempBookmarkIndex;
      cNew.plBookmarkString = &lTempBookmarkString;
      

      if (pProgress)
         pProgress->Push ((fp)dwStart / (fp)paCandidates->dwNum, (fp)i / (fp)paCandidates->dwNum);

      // speak this
      fRet = SynthGenWaveIntNewWave (1, &cNew, dwSamplesPerSec, iTTSQuality, fDisablePCM,
         pProgress, pVoiceMod, pProgressWave);

      if (pProgress)
         pProgress->Pop();

      // restore silence at start
      if (fSilenceAtStart)
         paCandidates->padwDur[dwStartWithSilence] = dwStartSilenceDur;

      // restore silence at end
      if (fSilenceAtEnd)
         paCandidates->padwDur[i-1] = dwEndSilenceDur;

      if (!fRet)
         return FALSE;

      if (i >= paCandidates->dwNum)
         break;

      // go onto the ntext bit
      fSilenceAtStart = fSilenceAtEnd;
      dwStart = i;
   } // while start

   return TRUE;
}

/*************************************************************************************
CMTTS::SynthGenWaveIntNewWave - Generates individual segments from SynthGenWaveInt()
so that TTS can speak more quicly.
*/

BOOL CMTTS::SynthGenWaveIntNewWave (DWORD dwCandidates, PSYNGENFEATURESCANDIDATE paCandidates,
                             DWORD dwSamplesPerSec,
                             int iTTSQuality, BOOL fDisablePCM, PCProgressSocket pProgress,
                              PTTSVOICEMOD pVoiceMod, PCProgressWaveTTS pProgressWave)
{
   // keep track of formant shift for PSOLA
   fp fFormantShift = 0;

   TTSVOICEMOD VM;
   if (!pVoiceMod) {
      pVoiceMod = &VM;
      FillInVOICEMOD (pVoiceMod, NULL, NULL);
   }

   // if this is a derived voice pass on down
   if (m_fIsDerived) {
      if (!m_pTTSMaster)
         return FALSE;
      return m_pTTSMaster->SynthGenWaveInt (dwCandidates, paCandidates, dwSamplesPerSec, iTTSQuality, fDisablePCM, pProgress, pVoiceMod, pProgressWave);
   }

   // allocate the wave
   PCM3DWave pWave = new CM3DWave;
   if (!pWave || !pWave->ConvertSamplesAndChannels (dwSamplesPerSec, 1, NULL) ) {
      if (pWave)
         delete pWave;
      return FALSE;
   }

#ifdef _DEBUG
   DWORD dwTimeCur, dwTimeStart = GetTickCount();
   WCHAR szTemp[128];
#endif

   // figure out the word indecies
   CListFixed lWSF, lWSFFFT;
   CListFixed alWords[MAXSYNGENFEATURESCANDIDATES];
   CListFixed lVol;
   DWORD dwCand;
   DWORD i;
   for (dwCand = 0; dwCand < dwCandidates; dwCand++) {
      alWords[dwCand].Init (sizeof(DWORD));
      alWords[dwCand].Required (paCandidates[dwCand].dwNum);
      for (i = 0; i < paCandidates[dwCand].dwNum; i++) {
         DWORD dwFind = paCandidates[dwCand].papszWord[i] ? m_pLexWords->WordFind (paCandidates[dwCand].papszWord[i]) : -1;
         alWords[dwCand].Add (&dwFind);
      }

      paCandidates[dwCand].padwWord = (DWORD*)alWords[dwCand].Get(0);
   } // dwCand

   // fill in the features
   CListFixed lSRDETAILEDPHASE, lPSOLASTRUCT;
   lPSOLASTRUCT.Init (sizeof(PSOLASTRUCT));
   DWORD dwBestCandidate;
   if (pProgress)
      pProgress->Push (0.0, 0.8);
   if (!SynthGenFeatures (dwCandidates, paCandidates, pWave, &lSRDETAILEDPHASE,
      pVoiceMod, iTTSQuality, fDisablePCM, &lPSOLASTRUCT, &dwBestCandidate, pProgress)) {

         if (pProgress)
            pProgress->Pop();
         goto failed;
   }
   if (pProgress)
      pProgress->Pop();
   PSRDETAILEDPHASE paSDP = (PSRDETAILEDPHASE) lSRDETAILEDPHASE.Get(0);
#ifdef NOMODS_SRDETAILEDPHASE
   paSDP = NULL;  // test turning off detailed phase
#endif

#ifdef _DEBUG
   dwTimeCur = GetTickCount();
   swprintf (szTemp, L"\r\nSynthGenFeatures = %d ms", (int)(dwTimeCur - dwTimeStart));
   dwTimeStart = dwTimeCur;
   OutputDebugStringW (szTemp);
#endif

   // remember
   BOOL fVolAbsolute = paCandidates[dwBestCandidate].fVolAbsolute;
   fp *pafVol = paCandidates[dwBestCandidate].pafVol;
   fp *pafAccent = paCandidates[dwBestCandidate].pafAccent;
   DWORD dwNum = paCandidates[dwBestCandidate].dwNum;
   DWORD *padwDur = paCandidates[dwBestCandidate].padwDur;
   DWORD *padwPhone = paCandidates[dwBestCandidate].padwPhone;
   PTTSGLOBALSTATE paTTSGS = paCandidates[dwBestCandidate].paTTSGS;

   // see if need to calculate the volume for each of the phonemes
   lVol.Init (sizeof(fp));
   if (fVolAbsolute && pafVol) {
      DWORD dwCur = 0;
      PCMLexicon pLex = pVoiceMod->pLex;
      if (!pLex)
         pLex = Lexicon();
      BYTE bSilence = (BYTE) pLex->PhonemeFindUnsort(pLex->PhonemeSilence());
      lVol.Required (lVol.Num() + dwNum);
      for (i = 0; i < dwNum; dwCur += padwDur[i++]) {
         fp fEnergy = pWave->CalcSREnergyRange (FALSE, dwCur * pWave->m_dwSRSkip,
            (dwCur + padwDur[i]) * pWave->m_dwSRSkip);
         fEnergy = max(fEnergy, 1);
         fEnergy = max(pafVol[i],1) / fEnergy;  // BUGFIX - put max pafVol too
         
         // put some limits on so dont get really extreme
         if (fEnergy > 3)
            fEnergy = 3;
         else if (fEnergy < 1.0 / 3.0)
            fEnergy = 1.0 / 3.0;

         // BUGFIX - If it's a silence phoneme then don't care about energy
         if ((BYTE)padwPhone[i] == bSilence)
            fEnergy = 1;

         // add
         lVol.Add (&fEnergy);
      } // i
      pafVol = (fp*)lVol.Get(0);
   }

   if (pafVol)
      if (!SynthAdjustVolumeRelative (pWave, padwDur, pafVol, dwNum))
         goto failed;

   // adjust by energypervolume
   SynthAdjustEnergyPerVolume (pWave, padwPhone, padwDur, dwNum);

   // produce the wave
   // NOTE: Shouldnt do pitch adjust here (except for emphasis) because the pitch
   // will have been set in the voice modifications info
   
#ifdef _DEBUG
   dwTimeCur = GetTickCount();
   swprintf (szTemp, L"\r\nSynthAdjustVolumeRelatie = %d ms", (int)(dwTimeCur - dwTimeStart));
   dwTimeStart = dwTimeCur;
   OutputDebugStringW (szTemp);
#endif

#ifndef NOMODS_VOICEDISGUISE
   // modify featrures first
   if (!pVoiceMod->pSubVoice->m_VoiceDisguise.ModifySRFEATUREInWave (pWave, paSDP, &fFormantShift))
      goto failed;
#endif

#ifdef _DEBUG
   dwTimeCur = GetTickCount();
   swprintf (szTemp, L"\r\nModifySRFEATUREInWave = %d ms", (int)(dwTimeCur - dwTimeStart));
   dwTimeStart = dwTimeCur;
   OutputDebugStringW (szTemp);
#endif

   BOOL fWhispering = FALSE;
#ifndef NOMODS_SHOUTWHISPER
   // go through and adjust for shouting or whisper
   DWORD j, k, dwSRFeat;
   PSRFEATURE psr = pWave->m_paSRFeature;
   if (paTTSGS) for (i = dwSRFeat = 0; i < dwNum; i++, paTTSGS++) {
      for (j = 0; j < padwDur[i]; j++, dwSRFeat++, psr++) {
         // make sure not beyoond the edge
         if (dwSRFeat >= pWave->m_dwSRSamples)
            break;   // shouldnt happen

         // if shouting then adjust
         if (paTTSGS->fDerShout) {
            int iDelta = (int) (paTTSGS->fDerShout / (fp)SRDATAPOINTS * 12.0 /*db*/ * (fp)0x10000);
            int iAdd = 0;
            for (k = 0; k < SRDATAPOINTS; k++, iAdd += iDelta) {
               int iCur = (int)psr->acVoiceEnergy[k];
               if (iCur <= -100)
                  continue;   // silence, so ignore

               iCur += (iAdd / 0x10000);
               iCur = max (iCur, SRABSOLUTESILENCE);
               iCur = min (iCur, SRMAXLOUDNESS);
               psr->acVoiceEnergy[k] = (char)iCur;
            } // k
         } // shout
         
         // if whisper then adjust
#define WHISPERSTART    (SRDATAPOINTS - SRPOINTSPEROCTAVE*4)
#define WHISPERSTOP     (SRDATAPOINTS - SRPOINTSPEROCTAVE*2)
         if (paTTSGS->fDerWhisper) {
            paTTSGS->fDerWhisper = max(paTTSGS->fDerWhisper, 0.0);
            paTTSGS->fDerWhisper = min(paTTSGS->fDerWhisper, 1.0);

            fp fOneMinus = 1.0 - paTTSGS->fDerWhisper;

            if (paTTSGS->fDerWhisper > 0.75)
               fWhispering = TRUE;

            for (k = 0; k < SRDATAPOINTS; k++) {
               fp fVoice = DbToAmplitude (psr->acVoiceEnergy[k]);
               fp fNoise = DbToAmplitude (psr->acNoiseEnergy[k]);
               fp fWhisperScale;
               if (k <= WHISPERSTART)
                  fWhisperScale = 0;
               else if (k >= WHISPERSTOP)
                  fWhisperScale = 1;
               else
                  fWhisperScale = (fp)(k - WHISPERSTART) / (fp)(WHISPERSTOP-WHISPERSTART);
               fp fWhisperTransfer = fWhisperScale * paTTSGS->fDerWhisper * fVoice;
               fp fVoiceAfter = fOneMinus * fVoice - fWhisperTransfer;
               fVoiceAfter = max(fVoiceAfter, 0);
               fNoise += fWhisperTransfer;

               psr->acVoiceEnergy[k] = AmplitudeToDb (fVoiceAfter);
               psr->acNoiseEnergy[k] = AmplitudeToDb (fNoise);
            } // k
         } // whisper

      } // j, duration of phoneme
   } // i, over phones
#endif


   // if disabling PCM then clear now
   // disable if whispering
   if (fDisablePCM || fWhispering)
      lPSOLASTRUCT.Clear();

   // get the phase as PCM
   fp fPitchScaleToCounteractVoiceMod = m_fAvgPitch / pVoiceMod->pSubVoice->m_fAvgPitch;
   if (lPSOLASTRUCT.Num() || !PhaseDetermine(pWave, fPitchScaleToCounteractVoiceMod, &lWSF))
      lWSF.Clear();
   // lWSF.Clear();  // uncomment to test old


   // FFT this
   PWAVESEGMENTFLOAT pwsfFFT = NULL;
   if (lWSF.Num() && lWSFFFT.Init (sizeof(WAVESEGMENTFLOAT), lWSF.Get(0), lWSF.Num())) {
      CSinLUT SinLUT;
      CMem memFFTScratch;
      pwsfFFT = (PWAVESEGMENTFLOAT) lWSFFFT.Get(0);
      for (i = 0; i < lWSFFFT.Num(); i++)
         FFTRecurseReal (&pwsfFFT[i].afPCM[0] - 1, SRFEATUREPCM, 1, &SinLUT, &memFFTScratch);
   }


   if (pProgress)
      pProgress->Push (0.8, 1.0);
   if (!pVoiceMod->pSubVoice->m_VoiceDisguise.SynthesizeFromSRFeature (iTTSQuality, pWave,
      lPSOLASTRUCT.Num() ? (PPSOLASTRUCT)lPSOLASTRUCT.Get(0) : NULL, lPSOLASTRUCT.Num(), fFormantShift,
      pwsfFFT ? &lWSFFFT : NULL,
      paSDP, !fWhispering, pProgress, FALSE, TRUE, NULL)) {
      if (pProgress)
         pProgress->Pop();
      goto failed;
   }
   if (pProgress)
      pProgress->Pop();

   // As SynthGenFeatures() specifies
   // will need to compress all units
   m_fTTSWaveDisableCompress = FALSE;
   TTSWaveCompress ();

#ifdef _DEBUG
   dwTimeCur = GetTickCount();
   swprintf (szTemp, L"\r\nSynthesizeFromSRFeature = %d ms", (int)(dwTimeCur - dwTimeStart));
   dwTimeStart = dwTimeCur;
   OutputDebugStringW (szTemp);
#endif

   // fill in PCM with original phase, mostly for test
   PhaseWriteToWave (pWave, &lWSF);

   // clear 10% of the units
   MemoryCacheFree (10.0, 0.0);


   // log this
   LogSpoken (pWave);

   // sent the wave up
   BOOL fRet = pProgressWave->TTSWaveData (pWave);
   delete pWave;

   // done
   return fRet;

failed:
   // will need to compress all units
   m_fTTSWaveDisableCompress = FALSE;
   TTSWaveCompress ();
   if (pWave)
      delete pWave;
   return FALSE;
}


/*************************************************************************************
CMTTS::MemoryCacheFree - Frees the cache of join costs. This is used to gradually
free up memory used by the TTS voice if it isn't being used.

inputs
   fp             fPercentJoin - Percentage of the elements to free. 100.0 = 100%, 1.0 = 1%.
                  NOTE: This is random, so the given percentage may not always be
                  free.
   fp             fPercentWaves - Percentage of SRFEATURESMALL wave segments to free.
returns
   none
*/
void CMTTS::MemoryCacheFree (fp fPercentJoin, fp fPercentWaves)
{
   DWORD i, dwToClear;
   DWORD dwIndex, dwIndex2;
   if (fPercentJoin && m_dwUnits) {
      dwToClear = (fp) m_dwUnits * fPercentJoin / 100.0;
      dwToClear = max(dwToClear, 1);   // always at least one

      if (m_dwNumPhone) for (i = 0; i < dwToClear; i++) {
         dwIndex = rand() % m_dwNumPhone;

         // triphone audio
         PCListFixed pl = m_palPCMTTSTriPhoneAudio ? m_palPCMTTSTriPhoneAudio[dwIndex] : NULL;
         if (pl) {
            dwIndex2 = rand() % pl->Num();
            PCMTTSTriPhoneAudio *pptp = (PCMTTSTriPhoneAudio*)pl->Get(dwIndex2);
            if (pptp)
               pptp[0]->ConnectErrorCacheFree();
         }
      } // i
   } // if fPercentJoin and m_dwUnites

#ifndef TTSWAVECALSRANALBLOCK_CREATETHREADS
   if (fPercentWaves && m_lPCTTSWave.Num()) {
      dwToClear = (fp) m_lPCTTSWave.Num() * fPercentWaves / 100.0;
      dwToClear = max(dwToClear, 1);   // always at least one
      if (m_dwNumPhone) for (i = 0; i < dwToClear; i++) {
         dwIndex = rand() % m_lPCTTSWave.Num();
         PCTTSWave *ppTW = (PCTTSWave*) m_lPCTTSWave.Get(0);
         PCTTSWave pTW = ppTW[i];
         if (pTW)
            pTW->FreeSRANALBLOCK();
      } // i
   } // fPercentWaves
#endif
}


/*************************************************************************************
CMTTS::PhaseWriteToWave - Fills int he wave's SRFEATURE PCM with the list of phases.

inputs
   PCM3DWave         pWave - Wave
   PCListFixed       plWSF - List of WAVESEGMENTFLOAT, was filled in by PhaseDetermine()
returns
   BOOL - TRUE if success
*/
BOOL CMTTS::PhaseWriteToWave (PCM3DWave pWave, PCListFixed plWSF)
{
   if (plWSF->Num() != pWave->m_dwSRSamples)
      return FALSE;

   PWAVESEGMENTFLOAT pWSF = (PWAVESEGMENTFLOAT) plWSF->Get(0);
   PSRFEATURE pSRF = pWave->m_paSRFeature;

   DWORD i, j;
   double fMax;
   for (i = 0; i < pWave->m_dwSRSamples; i++, pWSF++, pSRF++) {
      // figure out the maximum
      fMax = 0.0;
      for (j = 0; j < SRFEATUREPCM; j++)
         fMax = max(fabs(pWSF->afPCM[j]), fMax);
      if (fMax >= EPSILON)
         fMax = 1.0 / fMax;

      // normalize and fill in
#ifdef SRFEATUREINCLUDEPCM_SHORT
      fMax *= 32767;
      for (j = 0; j < SRFEATUREPCM; j++)
         pSRF->asPCM[j] = (short)floor(pWSF->afPCM[j] * fMax + 0.5);
      pSRF->fPCMScale = 1.0 / 256.0 / 256.0;
#else
      fMax *= 127;
      for (j = 0; j < SRFEATUREPCM; j++)
         pSRF->acPCM[j] = (char)floor(pWSF->afPCM[j] * fMax + 0.5);
      pSRF->fPCMScale = 1.0 / 256.0;
#endif
      pSRF->bPCMHarmFadeStart = 0;
      pSRF->bPCMHarmFadeFull = 0;
      pSRF->bPCMHarmNyquist = 255;
   } // i

   return TRUE;
}

/*************************************************************************************
CMTTS::PhaseDetermine - Figure out what phases are to be used for each sample.

inputs
   PCM3DWave         pWave - Wave
   fp fPitchScaleToCounteractVoiceMod - Multiplied by pitch that get from wave so can
                     counteract voice modifications
   PCListFixed       plWSF - List of WAVESEGMENTFLOAT that's filled in per SRFEATURE in pWave
returns
   BOOL - TRUE if success
*/

// PDPHONE - information
typedef struct {
   DWORD          dwPhone;    // phoneme
   DWORD          dwStart;    // start feature
} PDPHONE, *PPDPHONE;

BOOL CMTTS::PhaseDetermine (PCM3DWave pWave, fp fPitchScaleToCounteractVoiceMod, PCListFixed plWSF)
{
   plWSF->Init (sizeof(WAVESEGMENTFLOAT));

   if (!m_lPCPhaseModel.Num())
      return FALSE;

   // blank this
   WAVESEGMENTFLOAT wsf;
   memset (&wsf, 0, sizeof(wsf));
   plWSF->Required (pWave->m_dwSRSamples);
   DWORD i;
   for (i = 0; i < pWave->m_dwSRSamples; i++)
      plWSF->Add (&wsf);
   PWAVESEGMENTFLOAT pwsf = (PWAVESEGMENTFLOAT)plWSF->Get(0);

   // do SR
   CSRAnal SRAnal;
   fp fMaxSpeechWindow;
   PSRANALBLOCK pSAB = SRAnal.Init (pWave->m_paSRFeature, pWave->m_dwSRSamples, FALSE, &fMaxSpeechWindow);
   if (!pSAB)
      return FALSE;  // cant analyze

   // keep track of all the phoneme numbers
   PCMLexicon pLex = Lexicon();
   if (!pLex)
      return FALSE;
   BYTE bSilence = (BYTE)pLex->PhonemeFindUnsort(pLex->PhonemeSilence());
   CListFixed lPhonemes;
   PWVPHONEME pwp = (PWVPHONEME)pWave->m_lWVPHONEME.Get(0);
   lPhonemes.Init (sizeof(PDPHONE));
   WCHAR szTemp[sizeof (pwp->awcNameLong)/sizeof(WCHAR)];
   PDPHONE pdp;
   memset (&pdp, 0, sizeof(pdp));
   for (i = 0; i < pWave->m_lWVPHONEME.Num(); i++, pwp++) {
      memset (szTemp, 0, sizeof(szTemp));
      memcpy (szTemp, pwp->awcNameLong, sizeof(pwp->awcNameLong));
      pdp.dwPhone = pLex->PhonemeFindUnsort (szTemp);
      if (pdp.dwPhone == -1)
         pdp.dwPhone = bSilence;
      pdp.dwStart = pwp->dwSample / pWave->m_dwSRSkip;
      pdp.dwStart = min(pdp.dwStart, pWave->m_dwSRSamples);
      lPhonemes.Add (&pdp);
   }
   // final silence
   pdp.dwPhone = bSilence;
   pdp.dwStart = pWave->m_dwSRSamples;
   lPhonemes.Add (&pdp);

   // loop over the phonemes
   DWORD dwPhoneIndex;
   PPDPHONE ppdp = (PPDPHONE) lPhonemes.Get(0);
   PCPhaseModel pPM, pPMLeft, pPMRight, pPMBlend;
   fp fPitch;
   CListFixed lPWAVESEGMENTFLOAT;
   DWORD j;
   for (dwPhoneIndex = 0; dwPhoneIndex+1 < lPhonemes.Num(); dwPhoneIndex++, ppdp++) {
      pPM = PhaseModelGet (ppdp->dwPhone, FALSE);
      if (!pPM)
         continue;

      pPMLeft = dwPhoneIndex ? PhaseModelGet (ppdp[-1].dwPhone, FALSE) : NULL;
      pPMRight = (dwPhoneIndex+1 < lPhonemes.Num()) ? PhaseModelGet (ppdp[1].dwPhone, FALSE) : NULL;
      
      // loop over the samples
      DWORD dwDur = ppdp[1].dwStart - ppdp[0].dwStart;
      DWORD dwHalf = dwDur / 2;
      for (i = ppdp[0].dwStart; i < ppdp[1].dwStart; i++) {
         fPitch = pWave->PitchAtSample (PITCH_F0, i * pWave->m_dwSRSkip, 0) * fPitchScaleToCounteractVoiceMod;
            // need to adjust adverage pitch by voice mods

         // figure out if should blend in L/R phoneme
         DWORD dwOffset = i - ppdp[0].dwStart;
         fp fBlendWeight;
         if (dwOffset < dwHalf)
            pPMBlend = pPMLeft;
         else
            pPMBlend = pPMRight;
         fBlendWeight = 1.0 - (0.5 + cos ( ((fp)dwOffset / (fp)dwDur - 0.5) * PI ) * 0.5);

         // see if previous entry has a phase
         BOOL fPreviousValid = FALSE;
         if (i) for (j = 0; j < SRFEATUREPCM; j++)
            if (pwsf[i-1].afPCM[j]) {
               fPreviousValid = TRUE;
               break;
            }

         pPM->MatchingExampleFind (&pSAB[i].sr, pSAB[i].fEnergyAfterNormal,
            fPitch, m_fAvgPitch,
            pPMBlend, fBlendWeight,
            pwsf + i, fPreviousValid);
      } // i
   } // dwPhoneINdex

   // blur the phase to the left and right just a bit, just to make
   // sure that smooth
   CListFixed lCopy;
   if (!lCopy.Init (sizeof(WAVESEGMENTFLOAT), plWSF->Get(0), plWSF->Num()))
      return FALSE;
   PWAVESEGMENTFLOAT pwsfCopy = (PWAVESEGMENTFLOAT)lCopy.Get(0);
   memset (pwsf, 0, sizeof(WAVESEGMENTFLOAT) * plWSF->Num());
#define PHASEWINDOW     1
   double afPhaseWindowWeight[PHASEWINDOW*2+1];
   DWORD dwSample;
   for (i = 0; i < plWSF->Num(); i++, pwsf++, pwsfCopy++) {
      int iCur, iOffset;
      double fSum = 0;
      for (iOffset = -PHASEWINDOW; iOffset <= PHASEWINDOW; iOffset++) {
         afPhaseWindowWeight[PHASEWINDOW + iOffset] = PHASEWINDOW + 1 - abs(iOffset);
         fSum += afPhaseWindowWeight[PHASEWINDOW + iOffset];
      }
      fSum = 1.0 / fSum;
      for (iOffset = -PHASEWINDOW; iOffset <= PHASEWINDOW; iOffset++)
         afPhaseWindowWeight[PHASEWINDOW + iOffset] *= fSum;

      for (iOffset = -PHASEWINDOW; iOffset <= PHASEWINDOW; iOffset++) {
         iCur = iOffset + (int)i;
         if ((iCur < 0) || (iCur >= (int)plWSF->Num()))
            continue;

         for (dwSample = 0; dwSample < SRFEATUREPCM; dwSample++)
            pwsf->afPCM[dwSample] += pwsfCopy[iOffset].afPCM[dwSample] *
               afPhaseWindowWeight[PHASEWINDOW + iOffset];
      } // iOffset
   } // i

   return TRUE;
}

/*************************************************************************************
TTSWaveToTransPros - Takes a wave file and analyzes it for transplanted prosody.
The output it the transplanted prosody information that can then be passed into
TTS.

inputs
   PCM3DWave         pWave - Wave to analyze. This MUST have pitch, phone, and word
                     segmentation already done.
   PCMLexicon        pLex - Lexicon to use.
   DWORD             dwPitchSamples - Number of pitch values stored per phoneme.
   PCListFixed       plPhone - Will be initalized to sizeof(DWORD), and filled
                     with a list of phonemes (unsorted number). The high byte (<< 24)
                     will be filled with 0 if the phone is in the middle of the word,
                     1 at the start, 2 at the end, 3 is the entire word
   PCListFixed       plWord - Will be initialized to sizeof(PWSTR), and filled with
                     a pointer to the word for each phone. These pointers point
                     directly to pWave's data structures, so don't change pWave
                     after this or pointers will be invalid.
   PCListFixed       plDur - Initialized to sizeof(DWORD) and will be filled with
                     the duration of the phoneme in SRFEATURE units (1/100th of a sec)
   PCListVariable    plPitchExtra - Will be filled with one entry per phoneme. Each one
                     will contain dwPitchSamples fp's for the pitch of the phoneme. (dividided by fuji)
   PCListVariable    plPitchFuji - Will be filled with one entry per phoneme. Each one
                     will contain dwPitchSamples fp's for the pitch of the phoneme.
   PCListFixed       plVol - Initialized to sizoef(fp) and filled with one entry
                     per phoneme indicating its volume
   PCListFixed       plAccent - Initialized to sizeof(fp) and filled with accent from -1 to 1.
                        NOTE: Because can't really calculate accent, fills with 0.0.
   PCListFixed       plPCMTTSTriPhoneAudio - If not NULL, this will be filled in with
                     the triphones selected for each phoneme.
   PCMTTS            pTTS - Needed only of plPCMTTSTriPhoneAudio is requested
   BOOL              fIncludesFuji. If TRUE, the plPitchExtra will be multiplied by plPitchFuji
returns
   BOOL - TRUE if success
*/
BOOL TTSWaveToTransPros (PCM3DWave pWave, PCMLexicon pLex, DWORD dwPitchSamples,
                         PCListFixed plPhone, PCListFixed plWord, PCListFixed plDur,
                         PCListVariable plPitchF0Extra, PCListVariable plPitchF0Fuji, PCListFixed plVol,
                         PCListFixed plAccent,
                         PCListFixed plPCMTTSTriPhoneAudio, PCMTTS pTTS,
                         BOOL fIncludesFuji)
{
   // initialize lists
   plPhone->Init (sizeof(DWORD));
   plWord->Init (sizeof(PWSTR));
   plDur->Init (sizeof(DWORD));
   plPitchF0Extra->Clear();
   plPitchF0Fuji->Clear();
   plVol->Init (sizeof(fp));
   plAccent->Init (sizeof(fp));

   // if dont have any sr samples error
   if (!pWave->m_dwSRSamples)
      return FALSE;

   // make sure have pitch
   pWave->CalcPitchIfNeeded (WAVECALC_TTS_FULLPCM, NULL);
   if (!pWave->m_adwPitchSamples[PITCH_F0] || !pWave->m_adwPitchSamples[PITCH_SUB])
      return FALSE;

   // make sure dont have too many pitch samples that cant calc all
   fp afPitchF0Extra[8], afPitchF0Fuji[8];
   dwPitchSamples = min(dwPitchSamples, sizeof(afPitchF0Extra)/sizeof(fp));


   // silence
   BYTE bSilence = (BYTE)pLex->PhonemeFindUnsort(pLex->PhonemeSilence());

   // loop through all the phonemes, starting at phoneme -1
   int iPhone;
   DWORD dwNumPhone = pWave->m_lWVPHONEME.Num();
   DWORD j;
   for (iPhone = -1; iPhone < (int)dwNumPhone; iPhone++) {
      // get the phoneme
      PWVPHONEME pwp = (iPhone >= 0) ?
         (PWVPHONEME)pWave->m_lWVPHONEME.Get((DWORD)iPhone) :
         NULL;

      // get the next phoneme
      PWVPHONEME pwp2 = (iPhone+1 < (int)dwNumPhone) ?
         (PWVPHONEME)pWave->m_lWVPHONEME.Get((DWORD)(iPhone+1)) : NULL;

      // figure out the phoneme start and stop
      DWORD dwPhoneStart = pwp ? pwp->dwSample : NULL;
      DWORD dwPhoneEnd = pwp2 ? pwp2->dwSample : pWave->m_dwSamples;

      // if the phoneme end == start (or less than) then skip this phoneme
      if (dwPhoneEnd <= dwPhoneStart)
         continue;

      // figure out the phoneme
      WCHAR szTemp[sizeof (pwp->awcNameLong)/sizeof(WCHAR)];
      memset (szTemp, 0, sizeof(szTemp));
      if (pwp)
         memcpy (szTemp, pwp->awcNameLong, sizeof(pwp->awcNameLong));
      DWORD dwPhone = pLex->PhonemeFindUnsort (szTemp);
      if (dwPhone == -1)
         dwPhone = bSilence;
      dwPhone = (WORD)dwPhone;   // so doesnt interfere
      PLEXPHONE plp = pLex->PhonemeGetUnsort(pTTS ? pTTS->PhonemeBackoff(dwPhone, pLex, bSilence) : dwPhone);
      PLEXENGLISHPHONE pe = NULL;
      if (plp)
         pe = MLexiconEnglishPhoneGet(plp->bEnglishPhone);

      // figure out what word it's in...
      PWSTR pszWord = NULL;
      for (j = 0; j < pWave->m_lWVWORD.Num(); j++) {
         PWVWORD pw = (PWVWORD)pWave->m_lWVWORD.Get(j);
         PWVWORD pw2 = (PWVWORD)pWave->m_lWVWORD.Get(j+1);

         DWORD dwWordStart = pw ? pw->dwSample : 0;
         DWORD dwWordEnd = pw2 ? pw2->dwSample : pWave->m_dwSamples;

         if (dwWordEnd <= dwPhoneStart)
            continue;   // not in this word
         else if (dwWordStart >= dwPhoneEnd)
            break;   // went to far

         // NOTE: NOT filling in WORDPOS_SYLSTART and WORDPOS_SYLEND, but I don't think
         //    it should have much of an effect
         // else in this word
         pszWord = (PWSTR)(pw+1);
         if (dwWordStart == dwPhoneStart)
            dwPhone |= ((WORDPOS_WORDSTART | WORDPOS_SYLSTART) << 24);   // start of word 
         if (dwWordEnd == dwPhoneEnd)
            dwPhone |= ((WORDPOS_WORDEND | WORDPOS_SYLEND) << 24);   // end of word
         break;
      } // j

      // calculate the pitch...
      // BUGFIX - Dont calculate pitch on unvoiced phones
      DWORD dwNumPitch = 0;
      if (((BYTE)dwPhone != bSilence) && pe && (pe->dwCategory & PIC_VOICED)) {
         dwNumPitch = dwPitchSamples;
         for (j = 0; j < dwNumPitch; j++) {
            afPitchF0Fuji[j] = pWave->PitchAtSample (PITCH_SUB, 
               (fp)dwPhoneStart + (fp)(j+1) / (fp)(dwNumPitch+1) * (fp)(dwPhoneEnd - dwPhoneStart),
               0);
            afPitchF0Extra[j] = pWave->PitchAtSample (PITCH_F0, 
               (fp)dwPhoneStart + (fp)(j+1) / (fp)(dwNumPitch+1) * (fp)(dwPhoneEnd - dwPhoneStart),
               0) / afPitchF0Fuji[j];
         }
      }

      // waht's the volume
      fp fVol = pWave->CalcSREnergyRange (FALSE, dwPhoneStart, dwPhoneEnd);
      fp fAccent = 0.0; // HACK - Don't really know, so assume accent of 0

      // what's the duration in terms of sr units...
      dwPhoneStart = (dwPhoneStart + pWave->m_dwSRSkip/2) / pWave->m_dwSRSkip;
      dwPhoneEnd = (dwPhoneEnd + pWave->m_dwSRSkip/2) / pWave->m_dwSRSkip;
      if (dwPhoneEnd <= dwPhoneStart)
         continue;
      dwPhoneEnd -= dwPhoneStart;

      // add info..
      plPhone->Add (&dwPhone);
      plWord->Add (&pszWord);
      plDur->Add (&dwPhoneEnd);
      plPitchF0Extra->Add (afPitchF0Extra, dwNumPitch * sizeof(fp));
      plPitchF0Fuji->Add (afPitchF0Fuji, dwNumPitch * sizeof(fp));
      plVol->Add (&fVol);
      plAccent->Add (&fAccent);
   } // over iPhone

   // triphones
   DWORD i;
   if (plPCMTTSTriPhoneAudio) {
      plPCMTTSTriPhoneAudio->Init (sizeof(PCMTTSTriPhoneAudio));

      // find out the words as appear in tts
      CListFixed lWord2;
      lWord2.Init (sizeof(DWORD));
      PWSTR *ppsz = (PWSTR*)plWord->Get(0);
      DWORD *padwPhone = (DWORD*)plPhone->Get(0);
      DWORD dwNum = plPhone->Num();
      lWord2.Required (dwNum);
      for (i = 0; i < dwNum; i++) {
         DWORD dwWord = -1;
         if (ppsz[i])
            dwWord = pTTS->LexWordsGet()->WordFind (ppsz[i]);
         lWord2.Add (&dwWord);
      } // i
      DWORD *padwWord2 = (DWORD*)lWord2.Get(0);
      fp fBestScore;

      pTTS->SynthDetermineTriPhoneAudio (
         pTTS->AvgPitchGet(FALSE), pTTS->AvgPitchGet(TRUE),
         padwPhone, padwWord2,
         (DWORD*)plDur->Get(0), (fp*)plVol->Get(0), TRUE,
         (fp*)plAccent->Get(0),
         plPitchF0Extra, plPitchF0Fuji,
         dwNum, 1 /*iTTSQuality*/,
         FALSE /*fDisablePCM*/, plPCMTTSTriPhoneAudio, &fBestScore, NULL);
      if (plPCMTTSTriPhoneAudio->Num() != dwNum * TTSDEMIPHONES)
         return 0;
   } // if triphones

   if (fIncludesFuji) {
      // add the fujisaki pitch into the main
      for (i = 0; i < plPitchF0Extra->Num(); i++) {
         fp *pafF0Extra = (fp*)plPitchF0Extra->Get(i);
         fp *pafF0Fuji = (fp*)plPitchF0Fuji->Get(i);
         DWORD dwNum = (DWORD) plPitchF0Extra->Size(i) / sizeof(fp);

         for (j = 0; j < dwNum; j++)
            pafF0Extra[j] *= pafF0Fuji[j];
      }
   }


   // done
   return TRUE;
}


/*************************************************************************************
CMTTS::CalcWordsPerMinute - Calculates the average words per minute given all the
phoneme units.

returns
   DWORD - Guetimated words per minute
*/
DWORD CMTTS::CalcWordsPerMinute (void)
{
   __int64 iSum = 0;
   int iCount = 0;

   DWORD i, j;
   for (i = 0; i < m_dwNumPhone; i++) {
      PCListFixed pl = m_palPCMTTSTriPhonePros[i];
      if (!pl)
         continue;

      PCMTTSTriPhonePros *pptp = (PCMTTSTriPhonePros*) pl->Get(0);
      for (j = 0; j < pl->Num(); j++) {
         iSum += (__int64)pptp[j]->m_wDuration;
         iCount++;
      }
   } // i

   if (!iCount)
      return 0;

   double fDur;
   fDur = (double)iSum / (double)iCount / (double)SRSAMPLESPERSEC;   // average sec per unit
   fDur /= 60.0;  // so know in terms of minutes
   fDur *= 5;  // so know minutes per word, assuming word=5 phonemes
   fDur = 1.0 / fDur;   // so know words per minte
   return (DWORD)fDur;
}



/*************************************************************************************
TTSFileOpenDialog - Dialog box for opening a CTTS

inputs
   HWND           hWnd - To display dialog off of
   PWSTR          pszFile - Pointer to a file name. Must be filled with initial file
   DWORD          dwChars - Number of characters in the file
   BOOL           fSave - If TRUE then saving instead of openeing. if fSave then
                     pszFile contains an initial file name, or empty string
returns
   BOOL - TRUE if pszFile filled in, FALSE if nothing opened
*/
BOOL TTSFileOpenDialog (HWND hWnd, PWSTR pszFile, DWORD dwChars, BOOL fSave)
{
   OPENFILENAME   ofn;
   char  szTemp[256];
   szTemp[0] = 0;
   WideCharToMultiByte (CP_ACP, 0, pszFile, -1, szTemp, sizeof(szTemp), 0, 0);
   memset (&ofn, 0, sizeof(ofn));
   
   // BUGFIX - Set directory
   char szInitial[256];
   strcpy (szInitial, gszAppDir);
   GetLastDirectory(szInitial, sizeof(szInitial)); // BUGFIX - get last dir
   ofn.lpstrInitialDir = szInitial;

   ofn.lStructSize = sizeof(ofn);
   ofn.hwndOwner = hWnd;
   ofn.hInstance = ghInstance;
   ofn.lpstrFilter = "TTS Voice (*.tts)\0*.tts\0\0\0";
   ofn.lpstrFile = szTemp;
   ofn.nMaxFile = sizeof(szTemp);
   ofn.lpstrTitle = fSave ? "Save the TTS Voice" :
      "Open TTS Voice";
   ofn.Flags = fSave ? (OFN_PATHMUSTEXIST | OFN_HIDEREADONLY) :
      (OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY);
   ofn.lpstrDefExt = "tts";
   // nFileExtension 

   if (fSave) {
      if (!GetSaveFileName(&ofn))
         return FALSE;
   }
   else {
      if (!GetOpenFileName(&ofn))
         return FALSE;
   }

   // BUGFIX - Save diretory
   strcpy (szInitial, ofn.lpstrFile);
   szInitial[ofn.nFileOffset] = 0;
   SetLastDirectory(szInitial);

   // copy over
   MultiByteToWideChar (CP_ACP, 0, ofn.lpstrFile, -1, pszFile, dwChars);
   return TRUE;
}


/*************************************************************************************
TTSProsodyFileOpenDialog - Dialog box for opening a CTTSProsody

inputs
   HWND           hWnd - To display dialog off of
   PWSTR          pszFile - Pointer to a file name. Must be filled with initial file
   DWORD          dwChars - Number of characters in the file
   BOOL           fSave - If TRUE then saving instead of openeing. if fSave then
                     pszFile contains an initial file name, or empty string
   BOOL           fIncludeTTS - If TRUE, then also allow for TTS voices, in addition to the
                     TTS prosody.
returns
   BOOL - TRUE if pszFile filled in, FALSE if nothing opened
*/
BOOL TTSProsodyFileOpenDialog (HWND hWnd, PWSTR pszFile, DWORD dwChars, BOOL fSave, BOOL fIncludeTTS)
{
   OPENFILENAME   ofn;
   char  szTemp[256];
   szTemp[0] = 0;
   WideCharToMultiByte (CP_ACP, 0, pszFile, -1, szTemp, sizeof(szTemp), 0, 0);
   memset (&ofn, 0, sizeof(ofn));
   
   // BUGFIX - Set directory
   char szInitial[256];
   strcpy (szInitial, gszAppDir);
   GetLastDirectory(szInitial, sizeof(szInitial)); // BUGFIX - get last dir
   ofn.lpstrInitialDir = szInitial;

   ofn.lStructSize = sizeof(ofn);
   ofn.hwndOwner = hWnd;
   ofn.hInstance = ghInstance;
   ofn.lpstrFilter = (!fIncludeTTS) ?
      "TTS Prosody (*.mpm)\0*.mpm\0\0\0" :
      "All\0*.mpm;*.tts\0TTS Prosody (*.mpm)\0*.mpm\0TTS Voice (*.tts)\0*.tts\0\0\0";
   ofn.lpstrFile = szTemp;
   ofn.nMaxFile = sizeof(szTemp);
   ofn.lpstrTitle = fSave ? "Save the TTS Prosody Model" :
      "Open TTS Prosody Model";
   ofn.Flags = fSave ? (OFN_PATHMUSTEXIST | OFN_HIDEREADONLY) :
      (OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY);
   ofn.lpstrDefExt = "mpm";
   // nFileExtension 

   if (fSave) {
      if (!GetSaveFileName(&ofn))
         return FALSE;
   }
   else {
      if (!GetOpenFileName(&ofn))
         return FALSE;
   }

   // BUGFIX - Save diretory
   strcpy (szInitial, ofn.lpstrFile);
   szInitial[ofn.nFileOffset] = 0;
   SetLastDirectory(szInitial);

   // copy over
   MultiByteToWideChar (CP_ACP, 0, ofn.lpstrFile, -1, pszFile, dwChars);
   return TRUE;
}

#if 0 // dead code - dont use splines anymore since cause to overshoot mark by large amounts
/*************************************************************************************
SplineValue - This takes a list containing an array of TEXTUREPOINT. .h is the
time, and .v is the value. Then, given a time, this returns the value.

inputs
   PCListFixed       plCurve - Curve to look at
   fp                fTime - Time looking for
returns
   fp - Value at the time
*/
static fp SplineValue (PCListFixed plCurve, fp fTime)
{
   PTEXTUREPOINT ptp = (PTEXTUREPOINT) plCurve->Get(0);
   DWORD dwNumCurve = plCurve->Num();

   if (!dwNumCurve)
      return 0;

   // if less than first location then done
   if (fTime <= ptp[0].h)
      return ptp[0].v;

   // of if greater than max time then done
   if (fTime >= ptp[dwNumCurve-1].h)
      return ptp[dwNumCurve-1].v;

   // find location
   DWORD i;
   for (i = 0; i+1 < dwNumCurve; i++)
      if ((fTime >= ptp[i].h) && (fTime <= ptp[i+1].h))
         break;

   if (i+1 >= dwNumCurve)
      return ptp[dwNumCurve-1].v;

   fp t, f1, f2, f3, f4;
   t = (fTime - ptp[i].h) / (ptp[i+1].h - ptp[i].h);

   f1 = i ? ptp[i-1].v : ptp[i].v;
   f2 = ptp[i].v;
   f3 = ptp[i+1].v;
   f4 = (i+2 < dwNumCurve) ? ptp[i+2].v : ptp[i+1].v;

   return HermiteCubic (t, f1, f2, f3, f4);
}
#endif // 0


/*************************************************************************************
CMTTS::TriPhoneEnergy - Calculates the average energy for a triphone. This is useful
for calculating absolute energy.

NOTE: The data MUST be DECOMPRESSEd already

inputs
   PCMTTSTriPhoneAudio       ptr - Triphone. This can be NULL
   DWORD                *pdwCount - If not NULL, this will be filled in with the number of SRFEATUREs
                        typically in the triphone
returns
   fp - Energy. Average SRFEATUREEnergy() per sample.
*/
fp CMTTS::TriPhoneEnergy (PCMTTSTriPhoneAudio pti, DWORD *pdwCount)
{
   // if this is a derived voice pass on down
   if (m_fIsDerived) {
      if (!m_pTTSMaster)
         return FALSE;
      return m_pTTSMaster->TriPhoneEnergy (pti, pdwCount);
   }

   if (pdwCount)
      *pdwCount = 0;
   if (!pti || !(pti->m_dwFeatureEnd - pti->m_dwFeatureStart))  // BUGFIX - Added check for pti->m_pmemSRFEATURE
      return 0;

   fp fSum = 0;
   //if (!pti->Decompress()) // decompress larger
   //   return 0;
   PCTTSWave pTW = TTSFindWave (pti->m_wOrigWave);
   if (!pTW)
      return 0;
   PSRFEATURE psr = pTW->GetSRFEATURE (&m_acsTTSWave[pti->m_wOrigWave % MAXRAYTHREAD], pti->m_dwFeatureStart, pti->m_dwFeatureEnd);
   if (!psr)
      return 0;
   DWORD i;
   for (i = 0; i < (pti->m_dwFeatureEnd - pti->m_dwFeatureStart); i++, psr++) {
      fSum += SRFEATUREEnergy(FALSE, psr, FALSE);
   } // i
   //pti->Compress();  // compress back smaller
   if (pti->m_dwFeatureEnd - pti->m_dwFeatureStart)
      fSum /= (fp)(pti->m_dwFeatureEnd - pti->m_dwFeatureStart);

   // BUGFIX - Scale by m_iFeatureAdd
   fSum *= (fp)DbToAmplitude(pti->m_iFeatureAdd) / (fp)DbToAmplitude(0);

   // BUGFIX - If completely silent phone then white noise
   fSum = max(fSum, CLOSE);

   if (pdwCount)
      *pdwCount = pti->m_dwFeatureEnd - pti->m_dwFeatureStart;
   return fSum;
}

/*************************************************************************************
CMTTS::SynthArtificialProsody - Use this to generate the artifical prosody for
a sentence (or more) given the phonemes.

inputs
   DWORD       *padwPhone - Pointer to an array of phonemes. Each phoneme is the
               unsorted phone number. The high byte (<< 24) contains 0 if the phone
               is in the middle of a word, 1 on left side, 2 on right side, 3 word by itself
               WORDPOS_XXX flags in the high byte
   PWSTR       *paszWord - Word string associated with each phoneme, or NULL if none associated.
               If two or more phonemes are from the same word they should point to
               the same word string.
   PCMMLNode2   *papMMLWord - Pointer to array of dwNum entries pointing back to the
               original word information. If this has prosody info then the
               prosody info is extracted and used. Prosody information would
               have been written by SynthProsodyTagsApply()
   PCPoint     paPhoneMod - Modifications to [0]=pitch, [1]=vol, [2]=dur for each
               phoneme. If NULL then don't modify
   PTTSGLOBALSTATE paTTSGLOBALSTATE - Pointer to an array of dwNum TTSGLOBALSTATE structures
               contaiining the global state information.
   DWORD       dwNum - Number of phonemes
   PCListFixed plDur - Will be initialized to sizeof(DWORD) and filled with dwNum entries
               for the duration in SRFEATURE time units (1/100th of a sec)
   PCListVariable plPitchF0Extra - Will be initialized and filled with the frequency of
               each phoneme, in hz divided by the fuji prosody. Each element is N * sizeof(fp),
               where N is from 0 to whatever. It indicates the number of pitch points
               stored per phoneme.
   PCListVariable plPitchF0Fuji - Like plPitchF0Extra, but with fujisaki pitch in Hz
   PCListFixed plVol - Will be initialized to sizeof (fp) and filled with relative volume,
               where 1.0 is no change, etc.
   PCListFixed plAccent - Will be initialized to sizeof(fp) and filled with accent, from -1 to 1
   PTTSVOICEMOD   pVoiceMod - Parameters controlling how the voice is modified by
                  the original. Generally, pass in NULL. Only internal functions
                  will pass in non-null.
   BOOL        fDisablePCM - Set to TRUE if PSOLA temporarily disabled
   BOOL        *pfAbsPitch - Filled with TRUE if absolute pitch is requested, FALSE if
                  relative. Usually filled with FALSE
returns
   BOOL - TRUE if success
*/
#define MAXTPPITCH      5     // max control points that can have for tp
// PSTRESS - Used to calculate which side of phone stress occurs
typedef struct {
   PCMTTSTriPhonePros      ptp;     // triphone, for prosody
   fp                   fPitchF0Extra;  // pitch points for triphone
   fp                   fPitchF0Fuji;  // fujisaki
   fp                   fPitchF0ExtraLeft; // pitch on left side
   fp                   fPitchF0ExtraRight; // pitch on right side
   fp                   fPitchF0ExtraCenter; // pitch in center
   BOOL                 fVoiced; // true if voiced
   BOOL                 fVowel;  // true is vowel
   WORD                 wStressImportance; // amount of stress, 0=none, 1=SECONDARY,2=PRIMARY,
   WORD                 wFill;   // nothing
   DWORD                dwPhone; // phoneme, including flags for start/end of word
} PSTRESS, *PPSTRESS;

#define VOWELONLY       // turn on so applies pitch to vowels only
   // BUGFIX - Put the vowel only back in since seems just a hair better than no vowel-only
   // and it makes more sense since vowels are pitch targets

BOOL CMTTS::SynthArtificialProsody (DWORD *padwPhone, PWSTR *paszWord, PCMMLNode2 *papMMLWord,
                                    PCPoint paPhoneMod,
                                    PTTSGLOBALSTATE paTTSGLOBALSTATE, DWORD dwNum,
                           PCListFixed plDur, PCListVariable plPitchF0Extra, PCListVariable plPitchF0Fuji, PCListFixed plVol,
                           PCListFixed plAccent,
                           PTTSVOICEMOD pVoiceMod, BOOL fDisablePCM, BOOL *pfAbsPitch)
{
   if (pfAbsPitch)
      *pfAbsPitch = FALSE;

   PCMLexicon pLex = pVoiceMod->pLex;
   if (!pLex)
      pLex = Lexicon();
   if (!pLex)
      return FALSE;

   CListFixed lIsPitchTP;
   lIsPitchTP.Init (sizeof(BOOL));
   BOOL fDoesPitchUseTP = FALSE;
   BOOL fTPTemp;
   plDur->Init (sizeof(DWORD));
   plPitchF0Extra->Clear();
   plPitchF0Fuji->Clear();
   plVol->Init (sizeof(fp));
   plAccent->Init (sizeof(fp));

   CListFixed lStressAll;  // list of variable stress over all words
   lStressAll.Init (sizeof(PSTRESS));

   // list of original durations so can calculate how fast should be
   CListFixed lDurOrig;
   lDurOrig.Init (sizeof(DWORD));

   // figure out the words
   CListFixed lWords;
   lWords.Init (sizeof(DWORD));
   DWORD i;
   lWords.Required (dwNum);
   for (i = 0; i < dwNum; i++) {
      DWORD dwFind = paszWord[i] ? m_pLexWords->WordFind (paszWord[i]) : -1;
      lWords.Add (&dwFind);
   }
   DWORD *padwWords = (DWORD*)lWords.Get(0);

   // determine the triphones
   CListFixed lTriPhone;
   if (!SynthDetermineTriPhonePros (padwPhone, dwNum, &lTriPhone))
      return FALSE;
   PCMTTSTriPhonePros *pptp = (PCMTTSTriPhonePros*)lTriPhone.Get(0);

   // snap to duration
   //fp fUnits = max(m_dwUnits,1);
   //fUnits = log(fUnits) - log(10000.0);   // so if 10,000 units then 0.0
   //fUnits /= (log(100000.0) - log(10000.0)); // so if 100,0000 units, then 1.0
   //fUnits = max(fUnits, 0.0); // limit
   //fUnits = min(fUnits, 1.0); // limit
   int iSnapToDuration = 0;   // 0 => a blend, 1 = always use the original, -1 = always used asked-for (synthesized) duration
   //fp fSnapToDuration = (1.0 - fUnits) * SNAPTODURATION_10000 + fUnits * SNAPTODURATION_100000;
   BOOL fFullPCM = fDisablePCM ? FALSE : m_fFullPCM;

#ifdef NOMODS_DISABLEPCM
   fFullPCM = FALSE;
#endif
#ifdef NOMODS_SNAPTODURATION
   iSnapToDuration = -1;
#endif

   // create one entry per phoneme using defaults
   DWORD dwWordStart, dwWordEnd;
   CListFixed lStress;
   lStress.Init (sizeof(PSTRESS));
   fp fAvgVowelPitch;
   DWORD dwVowelPitchCount;
   CListFixed lCSVPitch;      // pitch derived from CSV
   CListFixed lCSVDur;        // duration derived from CSV
   CListFixed lCSVVol;        // volume derived from CSV
   BYTE bSilence = (BYTE)pLex->PhonemeFindUnsort(pLex->PhonemeSilence());
   for (dwWordStart = 0; dwWordStart < dwNum; dwWordStart = dwWordEnd) {
      // reset detection of min/max pitch
      fAvgVowelPitch  = 0;
      dwVowelPitchCount = 0;

      // figure out end of word
      lStress.Clear();
      for (dwWordEnd = dwWordStart; dwWordEnd < dwNum; dwWordEnd++) {
         if (paszWord[dwWordEnd] != paszWord[dwWordStart])
            break;

         // keep track of stress info
         PSTRESS pt;
         memset (&pt, 0, sizeof(pt));
         pt.dwPhone = padwPhone[dwWordEnd];
         pt.ptp = pptp[dwWordEnd];
         PLEXPHONE plp = pt.ptp ? pLex->PhonemeGetUnsort (PhonemeBackoff((WORD)padwPhone[dwWordEnd], pLex, bSilence)) : NULL;
         PLEXENGLISHPHONE ple = plp ? MLexiconEnglishPhoneGet(plp->bEnglishPhone) : NULL;

         if (ple) {
            pt.fVoiced = (ple->dwCategory & PIC_VOICED) ? TRUE : FALSE;
            pt.fVowel = ((ple->dwCategory & PIC_MAJORTYPE) == PIC_VOWEL);

            // if chinese then special case
            if (pLex->ChineseUse())
               pt.wStressImportance = pt.fVowel ? 2 : 0;
            else switch (plp->bStress) {  // for english
               default:
               case 0:
               //case 3:  // for chinese, shouldnt happen
               //case 4:  // for chinese, shouldnt happen
               //case 5:  // for chinese, shouldnt happen
                  pt.wStressImportance = 0;
                  break;
               case 1:
                  pt.wStressImportance = 2;
                  break;
               case 2:
                  pt.wStressImportance = 1;
                  break;
            }
         } // if ple

         short iPhonePitchF0Extra = pt.ptp ? pt.ptp->m_iPitchF0Extra : 0;
         short iPhonePitchF0Fuji = pt.ptp ? pt.ptp->m_iPitchF0Fuji : 0;
         short iPhonePitchDelta = pt.ptp ? pt.ptp->m_iPitchDelta : 0;
         short iPhonePitchBulge = pt.ptp ? pt.ptp->m_iPitchBulge : 0;

#ifdef NOMODS_DISABLETRIPHONEPITCH
         iPhonePitchF0Extra = iPhonePitchF0Fuji = iPhonePitchDelta = iPhonePitchBulge = 0;
#endif

         // store pitch
         fp fPitchDelta, fPitchBulge;
         if (pt.fVoiced && pt.fVowel) { // BUGFIX - Only pitch on vowel so do chinese
            pt.fPitchF0Extra = pow (2.0,
               pVoiceMod->pSubVoice->m_fPhonePitchAccentuate * (fp)iPhonePitchF0Extra / 1000.0); // keep this relative
            pt.fPitchF0Fuji = pow (2.0, (fp)iPhonePitchF0Fuji / 1000.0);
               // NOTE: not include phonepitchaccentuate in fuji
         }
         else
            pt.fPitchF0Extra = pt.fPitchF0Fuji = 1;

         // BUGFIX - Only pitch on vowel so do chinese
         fPitchDelta = (pt.fVoiced && pt.fVowel) ? sqrt(pow(2.0,
            pVoiceMod->pSubVoice->m_fPhoneRiseAccentuate * (fp)iPhonePitchDelta/ 1000.0)) : 1;
         fPitchBulge = (pt.fVoiced && pt.fVowel) ? pow(2.0,
            pVoiceMod->pSubVoice->m_fPhoneRiseAccentuate * (fp)iPhonePitchBulge/ 1000.0) : 1;

         pt.fPitchF0ExtraLeft = pt.fPitchF0Extra / fPitchDelta / fPitchBulge;
         pt.fPitchF0ExtraRight = pt.fPitchF0Extra * fPitchDelta / fPitchBulge;
            // NOTE: Dividing left/rigth by bulge so that phone will average the same
         pt.fPitchF0ExtraCenter = pt.fPitchF0Extra * fPitchBulge;

#if 0 // disable so that chinese can include phonemes that are higher in pitch
         // keep track of max and min stress odunf
#ifdef VOWELONLY
         if (pt.fVoiced && pt.fVowel) {
#else
         if (pt.fVoiced) { // BUGFIX: include non-vowels too - either && pt.fVowel) {
#endif
            fAvgVowelPitch += pt.fPitch;
            dwVowelPitchCount++;
         }
#endif // 0

         // NOTE: Move the pitch adjustment until AFTER the calulation over average vowel pitch
#ifndef NOMODS_DISABLEPHONEMOD
         if (paPhoneMod) {
            pt.fPitchF0Extra *= paPhoneMod[dwWordEnd].p[0];
            pt.fPitchF0ExtraLeft *= paPhoneMod[dwWordEnd].p[0];
            pt.fPitchF0ExtraRight *= paPhoneMod[dwWordEnd].p[0];
            pt.fPitchF0ExtraCenter *= paPhoneMod[dwWordEnd].p[0];
         }
#endif

         lStress.Add (&pt);
      } // dwWordEnd
      PPSTRESS pps = (PPSTRESS) lStress.Get(0);

      // see if can get CSV pitch for transplanted prosody
      DWORD dwPitchType = 0;
      DWORD dwDurType = 0;
      DWORD dwVolType = 0;
      DWORD j;
      fp *pafTPPitch, *pafTPDur, *pafTPVol;
      PWSTR psz = NULL;
      pafTPPitch = NULL;
      pafTPDur = NULL;
      pafTPVol = NULL;
      if (papMMLWord[dwWordStart]) {
         // try for relative pitch
         psz = papMMLWord[dwWordStart]->AttribGetString (gpszTPPitchRel);
         if (psz) {
            CSVParse (psz, &lCSVPitch);
            dwPitchType = 1;
            pafTPPitch = (fp*) lCSVPitch.Get(0);
            for (j = 0; j < lCSVPitch.Num(); j++)
               if (pafTPPitch[j] != -1234) {
                  // BUGFIX - Pitch expressiveness applied after fuji calculated
                  //if (paTTSGLOBALSTATE[dwWordStart].fDerPitchExpress != 1.0)
                  //   pafTPPitch[j] = pow(pafTPPitch[j], paTTSGLOBALSTATE[dwWordStart].fDerPitchExpress);

                  pafTPPitch[j] *= paTTSGLOBALSTATE[dwWordStart].fDerAvgPitch;
               }
         }

         // try for absolute pitch
         psz = papMMLWord[dwWordStart]->AttribGetString (gpszTPPitchAbs);
         if (psz) {
            CSVParse (psz, &lCSVPitch);
            dwPitchType = 2;
            pafTPPitch = (fp*) lCSVPitch.Get(0);

            // since requested absolute pitch for transpros, note this
            if (pfAbsPitch)
               *pfAbsPitch = TRUE;
         }

         // try for relative duration
         psz = papMMLWord[dwWordStart]->AttribGetString (gpszTPDurRel);
         if (psz) {
            CSVParse (psz, &lCSVDur);
            dwDurType = 1;
            pafTPDur = (fp*) lCSVDur.Get(0);
         }
         psz = papMMLWord[dwWordStart]->AttribGetString (gpszTPDurAbs);
         if (psz) {
            CSVParse (psz, &lCSVDur);
            dwDurType = 2;
            pafTPDur = (fp*) lCSVDur.Get(0);
         }
         if (lCSVDur.Num() < lStress.Num())
            pafTPDur = NULL;  // since not enough

         // also go for volume
         psz = papMMLWord[dwWordStart]->AttribGetString (gpszTPVolRel);
         if (psz) {
            CSVParse (psz, &lCSVVol);
            dwVolType = 1;
            pafTPVol = (fp*) lCSVVol.Get(0);
         }
         psz = papMMLWord[dwWordStart]->AttribGetString (gpszTPVolAbs);
         if (psz) {
            CSVParse (psz, &lCSVVol);
            dwVolType = 2;
            pafTPVol = (fp*) lCSVVol.Get(0);
         }
         if (lCSVVol.Num() < lStress.Num())
            pafTPVol = NULL;  // since not enough
      }

      // calculate the number of pitch points per phoneme
      DWORD dwTPPitchPerPhone;
      dwTPPitchPerPhone = 0;
      if (pafTPPitch && lStress.Num())
         dwTPPitchPerPhone = lCSVPitch.Num() / lStress.Num();

      // BUGFIX - loop over and hack the stress...
      if (dwVowelPitchCount)
         fAvgVowelPitch /= (fp) dwVowelPitchCount;
      else
         fAvgVowelPitch = 1;

      for (i = 0; i < lStress.Num(); i++) {
         pps[i].fPitchF0Extra /= fAvgVowelPitch; // normalize pitch
         pps[i].fPitchF0ExtraLeft /= fAvgVowelPitch; // normalize pitch
         pps[i].fPitchF0ExtraRight /= fAvgVowelPitch; // normalize pitch
         pps[i].fPitchF0ExtraCenter /= fAvgVowelPitch; // normalize pitch
      }

#ifdef VOWELONLY // BUGFIX - doesnt reproduce the fact that pitch changes for non-vowels too
      // loop back over and modify the pitch of non-vowels to the closest vowel
      for (i = 0; i < lStress.Num(); i++) {
         if (!pps[i].fVoiced)
            continue;   // dont care
         if (pps[i].fVowel)
            continue;   // already know

         // look left/right
         int iLeft, iRight, iScoreLeft, iScoreRight;
         WORD wStressLeft, wStressRight;
         for (iLeft = (int)i-1; iLeft >= 0; iLeft--)
            if (pps[iLeft].fVowel && pps[iLeft].fVoiced)
               break;
         for (iRight = (int)i+1; iRight < (int)lStress.Num(); iRight++)
            if (pps[iRight].fVowel && pps[iRight].fVoiced)
               break;
         wStressLeft = wStressRight = 0;
         if (iLeft >= 0) {
            iScoreLeft = -((int)i - iLeft); // distance away
            wStressLeft = pps[iLeft].wStressImportance;
         }
         else
            iScoreLeft = -10000; // so dont choose
         if (iRight < (int)lStress.Num()) {
            iScoreRight = -(iRight - (int)i); // distance away
            wStressRight = pps[iRight].wStressImportance;
         }
         else
            iScoreRight = -10000; // so dont choose

         // choose the closest vowel for the pitch. If there are two close
         // vowels the choose the stressed one
         if (iScoreLeft > iScoreRight)
            pps[i].fPitchF0Extra = pps[iLeft].fPitchF0ExtraRight;
         else if (iScoreRight > iScoreLeft)
            pps[i].fPitchF0Extra = pps[iRight].fPitchF0ExtraLeft;
         else if (wStressLeft > wStressRight)
            pps[i].fPitchF0Extra = pps[iLeft].fPitchF0ExtraRight;
         else if (wStressRight > wStressLeft)
            pps[i].fPitchF0Extra = pps[iRight].fPitchF0ExtraLeft;
         else if (iScoreLeft == -10000)
            pps[i].fPitchF0Extra = 1;   // since dont know
         else
            pps[i].fPitchF0Extra = pps[iLeft].fPitchF0ExtraRight;  // cant decide so take left
         // NOTE: No doing anything with fPitchCenter (from pitchbulge)... but dont think I need to
      } // i
#endif // 0

      lStressAll.Required (lStressAll.Num() + lStress.Num());
      for (i = 0; i < lStress.Num(); i++) {
         lStressAll.Add (pps + i);

         PCMTTSTriPhonePros ptp = pps[i].ptp;
         DWORD dwDur, dwNumPitch;
         fp afPitchF0Extra[3], afPitchF0Fuji[3], fVol, fAccent;
         if (!ptp) {
            PCMMLNode2 pThis = papMMLWord[i+dwWordStart];

            // silence
            dwDur = SRSAMPLESPERSEC / 8;  // default silence for comma
               // BUGFIX - Make comma 1/8 second from 1/4, which increases speed of period
            if (!paszWord[i+dwWordStart])
               dwDur = SRSAMPLESPERSEC / 25; // micropause
                  // BUGFIX - FAster, was 1/16th second
            else if ( ((paszWord[i+dwWordStart])[0] == L'.') ||
               ((paszWord[i+dwWordStart])[0] == L'?') || ((paszWord[i+dwWordStart])[0] == L'?') ||
               ((paszWord[i+dwWordStart])[0] == L';') )
               dwDur *= 2;
               // BUGFIX - Increased the durations for pauses.

            // scale duration by speaking rate
            dwDur = (DWORD)((fp)dwDur / paTTSGLOBALSTATE[i+dwWordStart].fDerWPM * (fp)m_dwWordsPerMinute + 0.5);
            dwDur = max(dwDur, 1);

            // if we have a tag then use that to determine the break
            PWSTR pszDur = NULL;
            if (pThis)
               pszDur = pThis->AttribGetString (gpszDurAbs);
            if (pszDur) {
               fp fDur = _wtof(pszDur);
               if (fDur > 0) {
                  dwDur = (DWORD)(fDur * SRSAMPLESPERSEC);
                  dwDur = max(dwDur, 1);
               }
            }

            // if we have a break then get duration from that
            pszDur = NULL;
            if (pThis)
               pszDur = pThis->NameGet();
            if (pszDur && !_wcsicmp(pszDur, gpszBreak)) {
               pszDur = pThis->AttribGetString(gpszTime);
               if (!pszDur)
                  dwDur = SRSAMPLESPERSEC / 2;
               else if (!_wcsicmp(pszDur, L"x-small"))
                  dwDur = SRSAMPLESPERSEC / 8;
               else if (!_wcsicmp(pszDur, L"small"))
                  dwDur = SRSAMPLESPERSEC / 4;
               else if (!_wcsicmp(pszDur, L"medium"))
                  dwDur = SRSAMPLESPERSEC / 2;
               else if (!_wcsicmp(pszDur, L"large"))
                  dwDur = SRSAMPLESPERSEC;
               else if (!_wcsicmp(pszDur, L"x-large"))
                  dwDur = SRSAMPLESPERSEC * 2;
               else {
                  DWORD dwLen = (DWORD)wcslen(pszDur);
                  fp fDur = _wtof(pszDur);
                  if ((dwLen > 2) &&
                     ((pszDur[dwLen-2] == L'm') || (pszDur[dwLen-2] == L'M')) &&
                     ((pszDur[dwLen-1] == L's') || (pszDur[dwLen-1] == L'S')))
                     fDur /= 1000.0;
                  if (fDur < 0)
                     fDur = .025;   // something

                  dwDur = (DWORD)(fDur * SRSAMPLESPERSEC);
                  dwDur = max(dwDur, 1);
               }
            }

            afPitchF0Extra[0] = 0;
            afPitchF0Fuji[0] = 0;
            fVol = 0;
            fAccent = 0.0;
            plDur->Add (&dwDur);
            lDurOrig.Add (&dwDur);
            plPitchF0Extra->Add (afPitchF0Extra, 0);    // 0 sized so pitch isn't fixed
            plPitchF0Fuji->Add (afPitchF0Fuji, 0);
            fTPTemp = FALSE;
            lIsPitchTP.Add (&fTPTemp); // so know if uses transpros
            plVol->Add (&fVol);
            plAccent->Add (&fAccent);
            continue;
         }

         // else phoneme

         // decompress
         // BUGFIX - Removed ptp->Decompress();

         // figure out how much duration is modified by prosody hacks
         fp fDurDerive = 1;
         pVoiceMod->pSubVoice->m_fDurWordEnd = max(pVoiceMod->pSubVoice->m_fDurWordEnd, .01);
         pVoiceMod->pSubVoice->m_fDurWordStart = max(pVoiceMod->pSubVoice->m_fDurWordStart, .01);
         if ((pps[i].dwPhone & (WORDPOS_WORDSTART << 24)) && (pps[i].dwPhone & (WORDPOS_WORDEND << 24)))
            fDurDerive = sqrt(pVoiceMod->pSubVoice->m_fDurWordEnd * pVoiceMod->pSubVoice->m_fDurWordStart);
         else if (pps[i].dwPhone & (WORDPOS_WORDSTART << 24))
            fDurDerive = pVoiceMod->pSubVoice->m_fDurWordStart;
         else if (pps[i].dwPhone & (WORDPOS_WORDEND << 24))
            fDurDerive = pVoiceMod->pSubVoice->m_fDurWordEnd;

         if (paPhoneMod)
            fDurDerive *= paPhoneMod[i+dwWordStart].p[2];

         if ((DWORD)(BYTE)pps[i].dwPhone < pVoiceMod->pSubVoice->m_lDurPerPhone.Num()) {
            float f = *((float*)pVoiceMod->pSubVoice->m_lDurPerPhone.Get((BYTE)pps[i].dwPhone));
            f = max(f, .01);
            fDurDerive *= f;
         }

         // calculate the original duration, disregarding transplanted prosody and WPM
         dwDur = (DWORD)((fp)ptp->m_wDuration * fDurDerive + 0.5);
         lDurOrig.Add (&dwDur);

         // duration
         int iSnapToDurationThis = iSnapToDuration;
         fp fDur;
         BOOL fDurTP = FALSE;

         if (!pafTPDur) {
            // use original duration, scaled by WPM
            fDur = (fp)ptp->m_wDuration * fDurDerive;
            // BUGFIX - Not applying WPM here. Doing so below so that longer speech
            // will extend vowels, not everything

            //dwDur = (DWORD)((fp)ptp->m_wDuration / paTTSGLOBALSTATE[i+dwWordStart].fDerWPM
            //   * (fp)m_dwWordsPerMinute * fDurDerive + 0.5);
         }
         else if (dwDurType == 2) {
            // it's TP using absolute duration
            // BUGFIX - If absolute duration but has a global state of faster then do
            fDur = pafTPDur[i] * SRSAMPLESPERSEC;

            // NOTE: Don't need to affect snap to duration is TP absolute duration is somewhat flexible
            // iSnapToDurationThis = -1;   // fixed duration TP doesn't snap to

            // take into account speaking speed
            fDur = fDur / paTTSGLOBALSTATE[i+dwWordStart].fDerWPM
               * (fp)m_dwWordsPerMinute;
               // NOTE - On absolute duration NOT being affected by global state

            // NOTE: This causes transpros absolute duration to be somewhat flexible

            // NOTE: Not sure where to put the averaging so that when global speed-up or slow-down
            // occurs affects transpros correctly

            // BUGFIX - Make transplanted prosody absolte duration be a combination
            // of the requested duration, and the synthesized duration for the unit
            fDur = ((fp)ptp->m_wDuration * fDurDerive + fDur) / 2.0;

            fDurTP = TRUE;
         }
         else {
            // it's TP using relative duration
            fDur = (fp)ptp->m_wDuration * pafTPDur[i] / paTTSGLOBALSTATE[i+dwWordStart].fDerWPM
               * (fp)m_dwWordsPerMinute * fDurDerive;
            fDurTP = TRUE;
         }

#ifdef NOMODS_CHANGEDURATION
         iSnapToDurationThis = 1;
#endif

         fp fDurRatio = fDur / (fp)ptp->m_wDuration;
         fp fLogDurThis = log(fDurRatio) / log(2.0);
         fp fPerOctave = UnitScoreDuration (this, (BYTE)pps[i].dwPhone, pLex, fLogDurThis >= 0, fFullPCM, -1.0);
         fp fScalePow = fabs(fLogDurThis) * max(fPerOctave, 0.0)  / (fp)SNAPTOSCALEDURATION;
         fScalePow *= SNAPTOSCALEBYUNITS; // so the more units, the more it snaps to
         fp fScale = pow ((fp)0.5, fScalePow);
         if (iSnapToDurationThis > 0)
            fScale = 0; // always go to original pitch
         else if (iSnapToDurationThis < 0)
            fScale = 1.0;  // always go to asked-for pitch

         fDur = pow ((fp)2.0, fScale * fLogDurThis) * (fp)ptp->m_wDuration;
         // NOTE: This duration might be further modified when combining units together
      

         dwDur = (DWORD) max(fDur + 0.5, 1.0);
         if (fDurTP)
            dwDur = dwDur | 0x80000000;   // so know its tp
         plDur->Add (&dwDur);

         fAccent = 0.0;

         if (!pafTPVol) {
            // BUGFIX - Volume per phoneme
            if (paPhoneMod)
               fVol = paPhoneMod[i+dwWordStart].p[1];
            else
               fVol = 1; // BUGFIX - Since already incorporate elsewhere... paTTSGLOBALSTATE[i+dwWordStart].fVol; // default volume
         }
         else if (dwVolType == 2) {
            // absoluste volume
            fVol = pafTPVol[i];
               // NOTE: Absolute volume NOT affected by global state

            // calculate the triphone volume
            // BUGFIX - Moved energy average into a triphone prosody feature
            fp fSum = ptp->m_fEnergyAvg;
            //fp fSum = 0;
            //if (ptp) {
            //   for (j = 0; j < ptp->m_dwNumSRFEATURE; j++)
            //      fSum += SRFEATUREEnergy ((PSRFEATURE)ptp->m_pmemSRFEATURE->p + j, FALSE);
            //   fSum /= (fp) ptp->m_dwNumSRFEATURE;
            //}
            if (fSum)
               fVol /= fSum;
            else
               fVol = 1;

            fVol *= (fp)SRDATAPOINTS;  // BUGFIX - Scale by SRDATAPOINTS so if change later wont have to change TP
            
            fVol *= -1; // so know its from TP
         }
         else
            fVol = -pafTPVol[i] * paTTSGLOBALSTATE[i+dwWordStart].fDerVol;    // negative volume to indicate it's from TP

         plVol->Add (&fVol);
         plAccent->Add (&fAccent);


         if (dwTPPitchPerPhone) {
            // if we have transplanted prosody info then see how many pitch points
            // we have
            fp *pfCur = pafTPPitch + dwTPPitchPerPhone * i;
            for (dwNumPitch = 0; dwNumPitch < dwTPPitchPerPhone; dwNumPitch++) {
               // if it's a symbol (-1234) indicating that there's no pitch
               if (pfCur[dwNumPitch] == -1234)
                  break;

               // else, flip the sign so know that it's a TP pitch
               pfCur[dwNumPitch] *= -1;
            }

            // a bit of a hack for transplanted prosody. Set extra to hz, and fuji to 1 or -1.
            // will correct later on
            afPitchF0Extra[0] = afPitchF0Extra[1] = afPitchF0Extra[2] = (pfCur[0] < 0.0) ? -1.0 : 1.0;
            plPitchF0Fuji->Add (&afPitchF0Extra[0], 1 * sizeof(afPitchF0Extra[0]));
            plPitchF0Extra->Add (pfCur, dwNumPitch * sizeof(pfCur[0]));

            fTPTemp = fDoesPitchUseTP = TRUE;
            lIsPitchTP.Add (&fTPTemp);
         }
         else {
            // pitch
            afPitchF0Extra[0] = pps[i].fPitchF0ExtraLeft;  // BUGFIX - include typical pitch shift for phoneme
            afPitchF0Extra[1] = pps[i].fPitchF0ExtraCenter;
               // BUGFIX - Was using fPitch, but need to counterage left/right weight
            afPitchF0Extra[2] = pps[i].fPitchF0ExtraRight;
            afPitchF0Fuji[0] = pps[i].fPitchF0Fuji;
            dwNumPitch = 0;
            if (pps[i].fVoiced) {
               dwNumPitch = 3;   // 3 pitch points for voiced
                  // BUGFIX - Was 3 for vowel, 1 for unvoiced

#if 0 // BUGFIX - any voiced will have 3 pitch points per, so can get better prosody
               // NOTE: If not vowel then no pitch inflection by phoneme
               if (!pps[i].fVowel) {
                  afPitchF0Extra[0] = afPitchF0Extra[2] = afPitchF0Extra[1] = pps[i].fPitchF0Extra;  // all set to median pitch
                        // BUGFIX - Was just setting all to afPitch[1], but since changed afPitch[1]
                        // to be pps[i].fPitchCenter, not pps[i].fPitch, need to change this
                  dwNumPitch = 1;   // BUGFIX - So non-vowels don't have as much resolution becase
                                    // no pitch assigned to them
               }
#endif // 0
               //dwNumPitch = pps[i].fVowel ? 3 : 1; // BUGFIX - so vowel more likely to be constant pitch
            }

            plPitchF0Extra->Add (afPitchF0Extra + ((dwNumPitch == 1) ? 1 : 0), dwNumPitch * sizeof(afPitchF0Extra[0]));
            plPitchF0Fuji->Add (afPitchF0Fuji, sizeof(afPitchF0Fuji[0]));
            fTPTemp = FALSE;
            lIsPitchTP.Add (&fTPTemp);
         }

         // compress
         // BUGFIX - Removed ptp->Compress();
      } // i, over lStress
   } // dwWordStart

   // get this
   DWORD *padwDur = (DWORD*)plDur->Get(0);

   DWORD j;

   // average syllable dur
   // BUGBUG - at some point may want a voice customization feature that allows more ephasis
   // on database's syllable duration based on phonemes, or based on time. Likewise, might
   // want option to always use same syllable duration (for poetry)
   fp fAvgSyllableDur = AvgSyllableDurGet(); // whatever is standard for this voice
   if (!fAvgSyllableDur)
      fAvgSyllableDur = 0.2;  // guess

#if 0 // to test
#ifdef _DEBUG
   FILE *file = fopen("c:\\pros.txt", "wt");
#else
   FILE *file = fopen("c:\\prosr.txt", "wt");
#endif
#endif // 0

   // go through all the phonemes and determine what syllable they're on
   PCMMLNode2 pLast = NULL;
   CListFixed lSylDurPhone, /*lSylDurSyl, lSylPitchSweep,*/ lSylVol, lSylPitchF0Extra, lSylPitchF0Fuji, lSylDurSkew;
   CListFixed lSylAccent;
#ifdef USEDURSYL
   CListFixed lSylDurSyl;
#endif
   CListFixed lDWORDDurPhone, lDWORDDurSyl, lDWORDVol, lDWORDPitchF0ExtraArray, lDWORDPitchF0Fuji, lBoundary, lDWORDDurSkew;
   CListFixed lDWORDSylStart, lDWORDSylEnd, lDWORDAccent;
   DWORD *padwBoundary = NULL;
   dwWordStart = 0;
   lDWORDDurPhone.Init (sizeof(DWORD));
   lDWORDDurSyl.Init (sizeof(DWORD));
   lDWORDDurSkew.Init (sizeof(DWORD));
   // lDWORDPitchSweep.Init (sizeof(DWORD));
   // lDWORDPitchBulge.Init (sizeof(DWORD));
   lDWORDVol.Init (sizeof(DWORD));
   lDWORDAccent.Init (sizeof(DWORD));
   lDWORDPitchF0ExtraArray.Init (sizeof(DWORD));
   lDWORDPitchF0Fuji.Init (sizeof(DWORD));
   lBoundary.Init (sizeof(DWORD));
   lSylDurPhone.Init (sizeof(fp));
   lSylDurSkew.Init (sizeof(fp));
#ifdef USEDURSYL
   lSylDurSyl.Init (sizeof(fp));
#endif
   lSylVol.Init (sizeof(fp));
   lSylAccent.Init (sizeof(fp));
   lSylPitchF0Extra.Init (sizeof(fp));
   lSylPitchF0Fuji.Init (sizeof(fp));
   lDWORDSylStart.Init (sizeof(DWORD));
   lDWORDSylEnd.Init (sizeof(DWORD));
   // lSylPitchSweep.Init (sizeof(fp));
#define SYLPITCHDETAIL     3        // so pitch for syllables is more detailed
   DWORD k;
   DWORD *padwDurOrig = (DWORD*)lDurOrig.Get(0);
   fp fNextPitchF0Extra = 0;   // next pitch to use when dont have one
   fp fNextPitchF0Fuji = 0;   // next pitch to use when dont have one
   fp fTemp, fF0Extra, fF0Fuji;
   for (i = 0; i < dwNum; i++) {
      if (pLast != papMMLWord[i]) {
         pLast = papMMLWord[i];
         dwWordStart = i;

         // get the boundaries and duration of each syllable
         if (papMMLWord[i]) {
            AttribGetArray (papMMLWord[i], gpszSyllables, &lBoundary);
            AttribGetArray (papMMLWord[i], gpszSylDurPhone, &lDWORDDurPhone);
            AttribGetArray (papMMLWord[i], gpszSylDurSyl, &lDWORDDurSyl);
            AttribGetArray (papMMLWord[i], gpszSylDurSkew, &lDWORDDurSkew);
            AttribGetArray (papMMLWord[i], gpszSylPitchF0ExtraArray, &lDWORDPitchF0ExtraArray);
            AttribGetArray (papMMLWord[i], gpszSylPitchF0Fuji, &lDWORDPitchF0Fuji);
            // AttribGetArray (papMMLWord[i], gpszSylPitchSweep, &lDWORDPitchSweep);
            // AttribGetArray (papMMLWord[i], gpszSylPitchBulge, &lDWORDPitchBulge);
            AttribGetArray (papMMLWord[i], gpszSylVol, &lDWORDVol);
            AttribGetArray (papMMLWord[i], gpszSylAccent, &lDWORDAccent);
         }
         else {
            lBoundary.Clear();
            lDWORDDurPhone.Clear();
            lDWORDDurSyl.Clear();
            lDWORDDurSkew.Clear();
            // lDWORDPitchSweep.Clear();
            // lDWORDPitchBulge.Clear();
            lDWORDPitchF0ExtraArray.Clear();
            lDWORDPitchF0Fuji.Clear();
            lDWORDVol.Clear();
            lDWORDAccent.Clear();
         }
         padwBoundary = (DWORD*)lBoundary.Get(0);
      } // if new word

      if (!lBoundary.Num()) {
         // no syllables for this word, so hack in 1.0's
         fTemp = 1.0;
         lSylDurPhone.Add (&fTemp);
#ifdef USEDURSYL
         lSylDurSyl.Add (&fTemp);
#endif
         lSylVol.Add (&fTemp);
         for (k = 0; k < SYLPITCHDETAIL; k++) {
            fTemp = fNextPitchF0Extra;
            fNextPitchF0Extra = 0;
            lSylPitchF0Extra.Add (&fTemp);  // BUGFIX - Was always adding 1. should have been 0

            fTemp = fNextPitchF0Fuji;
            fNextPitchF0Fuji = 0;
            lSylPitchF0Fuji.Add (&fTemp);  // BUGFIX - Was always adding 1. should have been 0
         }

#if 0 // to test
         fprintf (file, "\r\ni=%d: No syllables", (int)i);
#endif // 0

         //f = 0;   // so no pitch sweep
         //lSylPitchSweep.Add (&f);

         fTemp = 0.0;
         lSylDurSkew.Add (&fTemp);
         lSylAccent.Add (&fTemp);

         DWORD dwZero = 0;
         lDWORDSylStart.Add (&dwZero);
         lDWORDSylEnd.Add (&dwZero);

         continue;
      } // if no syllables in this word

      // try to find the apropriate syllable
      DWORD dwSyl;
      for (dwSyl = 0; dwSyl < lBoundary.Num(); dwSyl++)
         if (i - dwWordStart < padwBoundary[dwSyl])
            break;
      // NOTE: I think the bode for lBoundary and syllable stresses may mess up somewhat
      // if there's a phoneme substitution, but it shouldnt crash
      if (dwSyl >= lBoundary.Num())
         dwSyl = lBoundary.Num()-1;  // just to make sure dont go beyond edge

      // figure out the number ideal number of phonemes in this syllable
      DWORD dwSylStart = (dwSyl ? padwBoundary[dwSyl-1] : 0) + dwWordStart;
      DWORD dwSylEnd = padwBoundary[dwSyl] + dwWordStart;
      DWORD dwTotalOrigDur = 0, dwTotalOrigDurVowel = 0, dwTotalOrigDurPlosive = 0, dwTotalOrigDurConsonant = 0;
      DWORD j;
      PLEXPHONE plp;
      PLEXENGLISHPHONE ple;
      for (j = dwSylStart; j < dwSylEnd; j++) {
         dwTotalOrigDur += padwDurOrig[j];

         // get info
         plp = pLex->PhonemeGetUnsort (PhonemeBackoff((WORD)padwPhone[j], pLex, bSilence));
         ple = plp ? MLexiconEnglishPhoneGet(plp->bEnglishPhone) : NULL;
         if (ple && ple->dwCategory & PIC_PLOSIVE)
            dwTotalOrigDurPlosive += padwDurOrig[j];
         else if (!ple || ((ple->dwCategory & PIC_MAJORTYPE) == PIC_VOWEL))
            dwTotalOrigDurVowel += padwDurOrig[j];
         else
            dwTotalOrigDurConsonant += padwDurOrig[j];
      }
      if (!dwTotalOrigDur) {
         dwTotalOrigDur = 1;
         dwTotalOrigDurVowel = 1;
      }

      // figure out the voiced section of the syllable starting and ending
      DWORD dwSylStartVoiced = -1;
      DWORD dwSylEndVoiced = 0;  // so have something
      PPSTRESS pps = (PPSTRESS) lStressAll.Get(0);
      for (j = dwSylStart; j < dwSylEnd; j++) {
         if (j >= lStressAll.Num())
            continue;   // error

         // if this is voiced, then remember as end. else skip
         if (!pps[j].fVoiced)
            continue;
         dwSylEndVoiced = j+1;   // since one extra

         if (dwSylStartVoiced == (DWORD)-1)
            dwSylStartVoiced = j;
      } // voiced start/end
      if (dwSylStartVoiced == (DWORD)-1)
         dwSylStartVoiced = dwSylEndVoiced;


      // how much do we want to stretch it?
      //fp fAvgSyllableDur = paTTSGLOBALSTATE[dwSylStart].fDerAvgSyllableDur;
      //if (!fAvgSyllableDur) {
      //   // shouldnt happen, but just in case
      //   fAvgSyllableDur = AvgSyllableDurGet();
      //   if (!fAvgSyllableDur)
      //      fAvgSyllableDur = 0.2;
      //}
      fp fStretch = (fp) *((DWORD*)lDWORDDurSyl.Get(dwSyl)) / (fp)0x10000 * fAvgSyllableDur;
         // which calculates how long this should be (in sec)
      fp fThisDur = (fp)dwTotalOrigDur / (fp)SRSAMPLESPERSEC;
      fStretch /= fThisDur;   // so stretch accordingly

      // write in the values
      fTemp = (fp) *((DWORD*)lDWORDDurPhone.Get(dwSyl)) / (fp)0x10000;
         // BUGFIX - Disable the following line since sounds better with fStretch off
#ifdef USEDURSYL
      fTemp = fTemp * 0.75 + fStretch * 0.25; // average phoneme stretch, over ideal syllable length
         // which means include part phoneme duration, and part timing for syllables
         // BUGBUG - At some point might have an option for this, to get more poetic or not
         // BUGFIX - Only include average syllable length as 1/4, not 1/2
#endif

      // potentially stretch out for slow voice if not in transpros
      if (!(padwDur[i] & 0x80000000))
         fTemp *= (fp)m_dwWordsPerMinute / paTTSGLOBALSTATE[i].fDerWPM;

      // how many units extra to duration?
#define STRETCHVOWEL       1.0
#define STRETCHCONSONANT   0.5
#define STRETCHPLOSIVE     0.25     // BUGFIX - Upped from 0.1 to 0.25
      fp fStretchSum =
         (fp)dwTotalOrigDurVowel * STRETCHVOWEL +
         (fp)dwTotalOrigDurConsonant * STRETCHCONSONANT +
         (fp)dwTotalOrigDurPlosive * STRETCHPLOSIVE;
      DWORD dwThisDur = padwDurOrig[i];
      fp fUnitsExtra = (fTemp - 1.0) * (fp)dwTotalOrigDur;
      plp = pLex->PhonemeGetUnsort (PhonemeBackoff((WORD)padwPhone[i], pLex, bSilence));
      ple = plp ? MLexiconEnglishPhoneGet(plp->bEnglishPhone) : NULL;
      fp fStretchAmt;
      if (ple && ple->dwCategory & PIC_PLOSIVE)
         fStretchAmt = STRETCHPLOSIVE;
      else if (!ple || ((ple->dwCategory & PIC_MAJORTYPE) == PIC_VOWEL))
         fStretchAmt = STRETCHVOWEL;
      else
         fStretchAmt = STRETCHCONSONANT;
      fp fWeightThis = fStretchAmt * (fp)dwThisDur / fStretchSum;
      fThisDur = (fp)dwThisDur + fUnitsExtra * fWeightThis;
      fThisDur = max(fThisDur, 1);  // at least one unit
      fTemp = fThisDur / (fp)dwThisDur; // so know how much to stretch this individual phoneme

      // add it
      lSylDurPhone.Add (&fTemp);

      // add skew
      fTemp = (fp) *((int*)lDWORDDurSkew.Get(dwSyl)) / (fp)0x10000;
      lSylDurSkew.Add (&fTemp);
      lDWORDSylStart.Add (&dwSylStart);
      lDWORDSylEnd.Add (&dwSylEnd);

#ifdef USEDURSYL
      fTemp = (fp) *((DWORD*)lDWORDDurSyl.Get(dwSyl)) / (fp)0x10000;
      lSylDurSyl.Add (&fTemp);
#endif

      fTemp = (fp) *((DWORD*)lDWORDVol.Get(dwSyl)) / (fp)0x10000;
      lSylVol.Add (&fTemp);

      fTemp = (fp) *((DWORD*)lDWORDAccent.Get(dwSyl)) / (fp)0x10000 - 10.0;
      lSylAccent.Add (&fTemp);

      if ((i >= dwSylStartVoiced) && (i < dwSylEndVoiced)) {
         int ik, ikMinMax;

         // sweep over the voiced part of the syllable
         for (ik = -1; ik <= SYLPITCHDETAIL; ik++) {
            // NOTE: Have ik < 0, and ik >= SYLPITCHDETAIL to ensure that pitch sweep runs full range
            // only calculate < 0 if at the start of the voiced part, and have a pitch added already
            if ((ik < 0) && ((i != dwSylStartVoiced) || !lSylPitchF0Extra.Num() || !lSylPitchF0Fuji.Num() ) )
               continue;

            // only calculate == SYLPITCHDETAIL if i+1 == dwSylEndVoiced
            if ((ik >= SYLPITCHDETAIL) && (i+1 < dwSylEndVoiced))
               continue;

            // pitch sweep amount
            ikMinMax = max(min(ik, SYLPITCHDETAIL-1), 0);
            fp fAlpha = (fp)((int)i*SYLPITCHDETAIL + ikMinMax - (int)dwSylStartVoiced * SYLPITCHDETAIL) / 
               (fp)((dwSylEndVoiced - dwSylStartVoiced)*SYLPITCHDETAIL - 1);

            fAlpha *= (fp)SSPITCHPOINTS;
            fAlpha = max(fAlpha, 0.0);
            fAlpha = min(fAlpha, SSPITCHPOINTS-1);
            DWORD dwPointLeft = (DWORD)floor(fAlpha);
            DWORD dwPointRight = min(dwPointLeft + 1, SSPITCHPOINTS-1);
            fAlpha -= (fp)dwPointLeft;

            fp fLeft = (fp) *((int*)lDWORDPitchF0ExtraArray.Get(dwSyl*SSPITCHPOINTS+dwPointLeft)) / (fp)0x10000;
            fp fRight = (fp) *((int*)lDWORDPitchF0ExtraArray.Get(dwSyl*SSPITCHPOINTS+dwPointRight)) / (fp)0x10000;

            fF0Extra = fLeft * (1.0 - fAlpha) + fRight * fAlpha;
            fF0Extra = pow (2.0, fF0Extra);


            // get fuji too
            fF0Fuji = (fp) *((DWORD*)lDWORDPitchF0Fuji.Get(dwSyl)) / (fp)0x10000;

            if (ik < 0) {
               fp *pfLastF0Extra = (fp*)lSylPitchF0Extra.Get(lSylPitchF0Extra.Num()-1);
               if (*pfLastF0Extra == 0)
                  *pfLastF0Extra = fF0Extra;
               fp *pfLastF0Fuji = (fp*)lSylPitchF0Fuji.Get(lSylPitchF0Fuji.Num()-1);
               if (*pfLastF0Fuji == 0)
                  *pfLastF0Fuji = fF0Fuji;
               fNextPitchF0Extra = 0;   // just in case
               fNextPitchF0Fuji = 0;
            }
            else if (ik >= SYLPITCHDETAIL) {
               // remember this for enxt time
               fNextPitchF0Extra = fF0Extra;
               fNextPitchF0Fuji = fF0Fuji;
            }
            else {
               lSylPitchF0Extra.Add (&fF0Extra);
               lSylPitchF0Fuji.Add (&fF0Fuji);
               fNextPitchF0Extra = 0; // just in case
               fNextPitchF0Fuji = 0; // just in case
            }

#if 0 // to test
            fprintf (file, "\r\ni=%d, k=%d: %d %d %.3g", (int)i, (int)ik,
               (int)dwSylStartVoiced, (int)dwSylEndVoiced, (double)f);
#endif // 0

         } // ik
      }
      else {
         // it's not voiced, so dont specify any pitch
         for (k = 0; k < SYLPITCHDETAIL; k++) {
            fTemp = fNextPitchF0Extra;
            fNextPitchF0Extra = 0;
            lSylPitchF0Extra.Add (&fTemp);

            fTemp = fNextPitchF0Fuji;
            fNextPitchF0Fuji = 0;
            lSylPitchF0Fuji.Add (&fTemp);

#if 0 // to test
            fprintf (file, "\r\ni=%d, k=%d: %d %d %.3g", (int)i, (int)k,
               (int)dwSylStartVoiced, (int)dwSylEndVoiced, (double)f);
#endif // 0
         } // k
      }

      //f = (fp) *((int*)lDWORDPitchSweep.Get(dwSyl)) / (fp)0x10000;
      //lSylPitchSweep.Add (&f);
   } // i, over all phonemes
   fp *pafSylDurPhone = (fp*)lSylDurPhone.Get(0);
   fp *pafSylDurSkew = (fp*)lSylDurSkew.Get(0);
   DWORD *padwSylStart = (DWORD*)lDWORDSylStart.Get(0);
   DWORD *padwSylEnd = (DWORD*)lDWORDSylEnd.Get(0);
#ifdef USEDURSYL
   fp *pafSylDurSyl = (fp*)lSylDurSyl.Get(0);
#endif
   fp *pafSylPitchF0Extra = (fp*)lSylPitchF0Extra.Get(0);
   fp *pafSylPitchF0Fuji = (fp*)lSylPitchF0Fuji.Get(0);
   //fp *pafSylPitchSweep = (fp*)lSylPitchSweep.Get(0);
   fp *pafSylVol = (fp*)lSylVol.Get(0);
   fp *pafSylAccent = (fp*)lSylAccent.Get(0);


   // go back through and adjust the phoneme durations
   pLast = NULL;
   fp fErrorAccum = 0;
   fp fDur, fLastDur;
   BOOL fUsingAbsDur = FALSE;
   DWORD dwLastSylStart = (DWORD)-1, dwLastSylEnd = (DWORD)-1;
   fp fScaleSoSkewDoesntLengthen = 1;
   fp fSkewForSyllable = 1;
   DWORD dwMidPhone = 0;
   fp fMidPhonePercent = 0.5;
   fp fSkew;
   for (i = 0; i < dwNum; i++) {
      if (!fUsingAbsDur)
         fLastDur = pafSylDurPhone[i];


      if (pLast != papMMLWord[i]) {
         // PWSTR psz = papMMLWord[i] ? papMMLWord[i]->AttribGetString (gpszDurRel) : NULL;
         // fLastDur = psz ? _wtof (psz) : 1;
         pLast = papMMLWord[i];
         fUsingAbsDur = FALSE;

         // see if there's an absolute duration
         PWSTR psz = papMMLWord[i] ? papMMLWord[i]->AttribGetString (gpszDurAbs) : NULL;
         fp fDurAbs = 0;
         if (psz)
            fDurAbs = _wtof(psz);
         if (fDurAbs >= .01) {
            // loop until word ends and calculate duration
            fp fSum = 0;
            DWORD j;

            // BUGFIX - may want to multiply all the durations of individual phonemes
            // by the syllable info first, and then normalize. That way will be the right
            // duration, but will also create the right inflection. But NOT if TP flag
            for (j = i; (j < dwNum) && (papMMLWord[j] == pLast); j++) {
               if (HIWORD(padwDur[j]))
                  continue;   // transplanted prosody is set so don't adjust

               fp fDuration = (fp)LOWORD(padwDur[j]); // in case TP set
               fDuration *= pafSylDurPhone[j];
               fDuration += 0.5;
               fDuration = max(fDuration, 1);   // at least 1 length
               padwDur[j] = (DWORD)fDuration;   // dont worry about high bit since wouldnt set
            }

            for (j = i; (j < dwNum) && (papMMLWord[j] == pLast); j++)
               fSum += (fp)LOWORD(padwDur[j]);  // BUGFIX - Take LOWORD() just in case TP high bit set
            if (fSum)
               fLastDur = fDurAbs * (fp)SRSAMPLESPERSEC / fSum;
            fUsingAbsDur = TRUE;
         } // if absolute duration
      }

      // if the duration is flagged with the high bit then it's TP, so
      // just use whatever duration is there
      if (padwDur[i] & 0x80000000) {
         padwDur[i] &= ~0x80000000;
         continue;
      }

      // if different syllable then calculate
      if ((dwLastSylStart != padwSylStart[i]) || (dwLastSylEnd != padwSylEnd[i])) {
         dwLastSylStart = padwSylStart[i];
         dwLastSylEnd = padwSylEnd[i];

         // figure out if there's any skewing on this phoneme
         // figure out the theoretical length
         DWORD dwTheorDur = 0;
         fp fActualDur = 0;
         fp fDurTemp;
         PCMTTSTriPhonePros ptp;
         for (j = padwSylStart[i]; j < padwSylEnd[i]; j++) {
            ptp = pptp[j];
            dwTheorDur += ptp ? ptp->m_wDuration : 1;
            fDurTemp = (fp)padwDur[j] * fLastDur;
            fActualDur += fDurTemp;
         } // j

         // find mid-point in theoretical
         DWORD dwTheorDurHalf = dwTheorDur / 2;
         DWORD dwDur = 1;
         for (j = padwSylStart[i]; j < padwSylEnd[i]; j++) {
            ptp = pptp[j];
            dwDur = ptp ? ptp->m_wDuration : 1;
            if (dwTheorDurHalf < dwDur)
               break;

            dwTheorDurHalf -= dwDur;
         }
         dwMidPhone = j;
         fMidPhonePercent = (fp)dwTheorDurHalf / (fp)dwDur;

         // weight
         fSkew = pow((fp)2.0, pafSylDurSkew[i]);

         // find this mid-phoneme in the actual phoneme
         fp fDurAfterSkew = 0;
         for (j = padwSylStart[i]; j < padwSylEnd[i]; j++) {
            fDurTemp = (fp)padwDur[j] * fLastDur;
            if (j < dwMidPhone)
               fDurTemp *= 1.0 / fSkew;
            else if (j > dwMidPhone)
               fDurTemp *= fSkew;
            else
               fDurTemp *= (1.0 - fMidPhonePercent) * fSkew + fMidPhonePercent / fSkew;
            fDurAfterSkew += fDurTemp;
         }
         fScaleSoSkewDoesntLengthen = max(fActualDur,CLOSE) / max(fDurAfterSkew,CLOSE);
         if (dwLastSylStart == dwLastSylEnd)
            fScaleSoSkewDoesntLengthen = fSkew = 1.0;   // since is silence or something

         fSkewForSyllable = fSkew;
      }

      // what percentage of this phone is before the mid-point and after
      fSkew = fSkewForSyllable;
      if (i < dwMidPhone)
         fSkew = 1.0 / fSkew; // to shorten
      else if (i > dwMidPhone)
         fSkew = fSkew; // to keep same elngth
      else
         fSkew = (1.0 - fMidPhonePercent) * fSkew + fMidPhonePercent / fSkew; // part way for each
      fSkew *= fScaleSoSkewDoesntLengthen;   // so keep right length


      // scale the duration
      fDur = (fp)padwDur[i] * fLastDur * fSkew + fErrorAccum; // deal with roundoff error
#ifndef NOMODS_CHANGEDURATION
      // keep the original time
      padwDur[i] = (DWORD)fDur;
#endif
      padwDur[i] = max(padwDur[i], 1); // always at least some duration
      fErrorAccum = fDur - (fp)padwDur[i];
      fErrorAccum = max(fErrorAccum, 0); // BUGFIX - so dont go negative
   } // i



   // make a list of inflection points for the pitch and volume...
   CListFixed lPitchF0ExtraInflect, lPitchF0FujiInflect, lVolInflect, lAccentInflect;
   lPitchF0ExtraInflect.Init (sizeof(fp));   // BUGFIX - Changed pitch and vol to be low-pass, not spline
   lPitchF0FujiInflect.Init (sizeof(fp));   // BUGFIX - Changed pitch and vol to be low-pass, not spline
   lVolInflect.Init (sizeof(fp));
   lAccentInflect.Init (sizeof(fp));
   pLast = NULL;
   DWORD dwCur = 0;
   fp fUnk = -1;
   BOOL fAbsPitch, fAbsVol;
   for (i = 0; i < dwNum; dwCur += padwDur[i++]) {
      // if same as before nothing
      if (pLast == papMMLWord[i])
         continue;
      pLast = papMMLWord[i];
      if (!papMMLWord[i])
         continue;   // cant do

      // get the pitch, volume, and where it inflects
      PWSTR psz;
      fp fPitchF0Extra, fPitchF0Fuji, fVol;
      // int iInflect;
      fPitchF0Extra = fPitchF0Fuji = 1;
      fVol = 1;
      // not used: psz = pLast->AttribGet (gpszInflectPoint);
      // not used: iInflect = psz ? _wtoi(psz) : 0;

      // if there is an absoluate volume then deal with that
      psz = pLast->AttribGetString (gpszVolAbs);
      fp fVolAbs = psz ? _wtof(psz) : 0;
      if (fVolAbs > CLOSE) {
         fp fSum = 0;
         DWORD dwCount = 0;
         // figure out what this volume would be under normal volume

         pptp = (PCMTTSTriPhonePros*)lTriPhone.Get(0); // just in case was changed
         for (j = i; (j < dwNum) && (pLast == papMMLWord[j]); j++) {
            DWORD dwThisCount;

            // BUGFIX - Since keep energy stored in triphone prosody, deal with differently
            fp fThis = pptp[j]->m_fEnergyAvg;
            dwThisCount = pptp[j]->m_wDuration;
            //fp fThis = TriPhoneEnergy (pptp[j], &dwThisCount);

            // BUGFIX - was
            //DWORD dwWord = paszWord[j] ? m_pLexWords->WordFind (paszWord[j]) : -1;
            //fp fThis = TriPhoneEnergy (padwPhone, dwWord, dwNum, j, &dwThisCount);

            fSum += (fp)dwThisCount * fThis;
            dwCount += dwThisCount;
         } // j
         if (dwCount)
            fSum /= (fp)dwCount;

         // now have volume over entire word, so easy to figure out scale
         if (fSum > CLOSE)
            fVol = fVolAbs / fSum;
         fVol *= (fp)SRDATAPOINTS;  // BUGFIX - Scale so if change # of SRDATAPOINTS wont have to change
         fAbsVol = TRUE;
      } // if fVolAbs
      else {
         fVol *= paTTSGLOBALSTATE[i].fDerVol;   // relative volume
         fAbsVol = FALSE;
      }

      // see aboute absolute pitch
      psz = pLast->AttribGetString (gpszPitchAbs);
      if (psz) {
         fAbsPitch = TRUE;
         fPitchF0Extra = _wtof(psz); //  / paTTSGLOBALSTATE[i].fDerAvgPitch;
         fPitchF0Fuji = 1.0; // since don't know what else to do with absolute
         fDoesPitchUseTP = TRUE; // causing it to recalc fuji
      }
      else
         fAbsPitch = FALSE;

      // NOTE: Minor problem that wont fix. if have absolute pitch,
      // and adjust pitch expressiveness then the pitch will be modified
      // by the expressiveness. Not really worth fixing.

      // add in -1's until get to point, indicating pitch is indeterminant
      lPitchF0ExtraInflect.Required (dwCur);
      lPitchF0FujiInflect.Required (dwCur);
      lVolInflect.Required (dwCur);
      lAccentInflect.Required (dwCur);
      while (lPitchF0ExtraInflect.Num() < dwCur) {
         lPitchF0ExtraInflect.Add (&fUnk);
         lPitchF0FujiInflect.Add (&fUnk);
         lVolInflect.Add (&fUnk);
         lAccentInflect.Add (&fUnk);
      }

      // calculate the duration of this word...
      DWORD dwDur = 0;
      for (j = i; j < dwNum; j++) {
         if (pLast != papMMLWord[j])
            break;   // done with word

         dwDur += padwDur[j];

         fp fv = fVol;
         if (!fAbsVol)
            fv *= pafSylVol[j];  // include volume
         fp fAccent = max(pafSylAccent[j], -1.0) + 10.0; // so not negative

         // add in pitch and volume
         if (lPitchF0ExtraInflect.Num() >= dwCur + dwDur)
            continue;   // shouldnt happen
         DWORD dwNumPoints = dwCur + dwDur - lPitchF0ExtraInflect.Num();
         for (k = 0; k < dwNumPoints; k++) {
            // pitch changes
            fF0Extra = fPitchF0Extra;
            fF0Fuji = fPitchF0Fuji;
            fp fAlpha;
            DWORD dwIndex, dwIndex2;
            if (!fAbsPitch) {
               fAlpha = ((fp)j + (fp)k / (fp)dwNumPoints) * SYLPITCHDETAIL - 0.5;
               fAlpha = max(fAlpha, 0);
               fAlpha = min(fAlpha, (fp)(lSylPitchF0Extra.Num()-1));
               dwIndex = (DWORD)fAlpha;
               dwIndex2 = dwIndex+1;
               dwIndex2 = min(dwIndex2, lSylPitchF0Extra.Num()-1);
               fAlpha -= (fp)dwIndex;

               // extra
               if (pafSylPitchF0Extra[dwIndex] && pafSylPitchF0Extra[dwIndex2])
                  fF0Extra *= (1.0 - fAlpha) * pafSylPitchF0Extra[dwIndex] + fAlpha * pafSylPitchF0Extra[dwIndex2];
               else if (pafSylPitchF0Extra[dwIndex])
                  fF0Extra *= pafSylPitchF0Extra[dwIndex];
               else if (pafSylPitchF0Extra[dwIndex2])
                  fF0Extra *= pafSylPitchF0Extra[dwIndex2];
               else
                  fF0Extra = fUnk;

               // Fuji
               if (pafSylPitchF0Fuji[dwIndex] && pafSylPitchF0Fuji[dwIndex2])
                  fF0Fuji *= (1.0 - fAlpha) * pafSylPitchF0Fuji[dwIndex] + fAlpha * pafSylPitchF0Fuji[dwIndex2];
               else if (pafSylPitchF0Fuji[dwIndex])
                  fF0Fuji *= pafSylPitchF0Fuji[dwIndex];
               else if (pafSylPitchF0Fuji[dwIndex2])
                  fF0Fuji *= pafSylPitchF0Fuji[dwIndex2];
               else
                  fF0Fuji = fUnk;
            }
            if (!fAbsPitch && (fF0Fuji != fUnk)) {
               fF0Fuji *= paTTSGLOBALSTATE[i].fDerAvgPitch / m_fAvgPitch * m_fAvgPitchF0Fuji;  // ok to use i, which is beginngng of word
               fF0Fuji = max(fF0Fuji, CLOSE);
            }
            lPitchF0ExtraInflect.Add (&fF0Extra);
            lPitchF0FujiInflect.Add (&fF0Fuji);

#if 0 // to test
            fprintf (file, "\r\ni=%d, j=%d, k=%d: %.3g %.3g %.3g %.3g %.3g %d",
               (int)i, (int)j, (int)k,
               (double)f, (double)fAlpha, (double)pafSylPitch[dwIndex], (double)pafSylPitch[dwIndex2],
               (double)paTTSGLOBALSTATE[i].fDerAvgPitch, (int)dwNumPoints);
#endif // 0

            // volume is the same for each
            lVolInflect.Add (&fv);
            lAccentInflect.Add (&fAccent);
         } // k
      } // j, over indivual phonemes

   } // i

   // add -1 until get to the end of the info
   lPitchF0ExtraInflect.Required (dwCur);
   lPitchF0FujiInflect.Required (dwCur);
   lVolInflect.Required (dwCur);
   lAccentInflect.Required (dwCur);
   while (lPitchF0ExtraInflect.Num() < dwCur) {
      lPitchF0ExtraInflect.Add (&fUnk);
      lPitchF0FujiInflect.Add (&fUnk);
      lVolInflect.Add (&fUnk);
      lAccentInflect.Add (&fUnk);
   }

   // default pitch
   fp fDefPitch = dwNum ? paTTSGLOBALSTATE[0].fDerAvgPitch : pVoiceMod->pSubVoice->m_fAvgPitch;
   fDefPitch = fDefPitch / m_fAvgPitch * m_fAvgPitchF0Fuji;
      // BUGFIX - so fujisaki pitch used for base

   // smooth this out
   fp *pafPitchF0Extra = (fp*)lPitchF0ExtraInflect.Get(0);
   fp *pafPitchF0Fuji = (fp*)lPitchF0FujiInflect.Get(0);
   fp *pafVolIn = (fp*)lVolInflect.Get(0);
   fp *pafAccentIn = (fp*)lAccentInflect.Get(0);
   DWORD dwNumInflect = lPitchF0ExtraInflect.Num();

#if 0 // to test
   fprintf (file, "\r\n\r\n\r\n");
   for (i = 0; i < dwNumInflect; i++)
      fprintf (file, "\r\n%.3g", (double)pafPitch[i]);
#endif // 0

   int iBlur;
   iBlur = (int)(pVoiceMod->pSubVoice->m_fBlurPitch * (fp)SRSAMPLESPERSEC);
   if (!fDoesPitchUseTP && (iBlur > 0)) {
      PitchLowPass (pafPitchF0Extra, dwNumInflect,
         (DWORD)iBlur, 1.0);
      PitchLowPass (pafPitchF0Fuji, dwNumInflect,
         (DWORD)iBlur, fDefPitch);
      // BUGFIX - Reduced from /5 to /10
   }

#if 0 // to test
   fprintf (file, "\r\n\r\n\r\n");
   for (i = 0; i < dwNumInflect; i++)
      fprintf (file, "\r\n%.3g", (double)pafPitch[i]);
#endif // 0

   iBlur = (int)(pVoiceMod->pSubVoice->m_fBlurVolume * (fp)SRSAMPLESPERSEC);
   if (!fDoesPitchUseTP && (iBlur > 0))
      PitchLowPass (pafVolIn, dwNumInflect, (DWORD)iBlur, 1);
      // BUGFIX - Blur volume more because sounds too jumpy now with volume
      // increasing and decreasing too quickly

   // blur accent slightly
   iBlur = (int)(pVoiceMod->pSubVoice->m_fBlurPitch * (fp)SRSAMPLESPERSEC);
   PitchLowPass (pafAccentIn, dwNumInflect, iBlur, 10.0);
   for (i = 0; i < dwNumInflect; i++)
      pafAccentIn[i] -= 10.0;   // since added 10 before

#if 0 // to test
   for (i = 0; i < dwNum; i++) {
      fprintf (file, "\r\nPhone %d : %x, %d, %.3g; ",
         (DWORD) i, padwPhone[i], *((DWORD*)plDur->Get(i)), (double) *((fp*)plVol->Get(i)));

      fp *paf = (fp*)plPitch->Get(i);
      DWORD dwNum2 = plPitch->Size(i) / sizeof(DWORD);
      for (j = 0; j < dwNum2; j++)
         fprintf (file, "%.3g ", (double)paf[j]);
   } // i
#endif // 0


   // adjust the pitch and volume
   dwCur = 0;
   fp fTime;
   int iTime;
   fp *pafVol = (fp*)plVol->Get(0);
   fp *pafAccent = (fp*)plAccent->Get(0);
   fp afPitchF0ExtraOne[3], afPitchF0FujiOne[3];
   for (i = 0; i < dwNum; dwCur += padwDur[i++]) {
      // adjust the pitch
      fp *pfPitchF0Extra = (fp*)plPitchF0Extra->Get(i);
      fp *pfPitchF0Fuji = (fp*)plPitchF0Fuji->Get(i);
      DWORD dwNumPitchF0Extra = (DWORD)plPitchF0Extra->Size(i) / sizeof(fp);
      DWORD dwNumPitchF0Fuji = (DWORD)plPitchF0Fuji->Size(i) / sizeof(fp);

      // BUGFIX - so fujisaki pitch used for base
#if 0 // keep dwNumPitch the same as original was so that don't mess with pitch
      // note: Some concern that disabling this causes problems SynthDetermineTriPhoneAudio because
      // it uses the pitch, but it seems to find the closest left/right one and use that

      // prosody, or lack of it, from phonemes, causing pitch to be attributed
      // to silence and non-spoken

      // make sure have some pitch information so can do accurate prosody bend
      if (dwNumPitch == 0) {
         dwNumPitch = 3;
         afPitchOne[0] = afPitchOne[1] = afPitchOne[2] = 1.0;
         pfPitch = afPitchOne;
      }
      else if (dwNumPitch == 1) {
          dwNumPitch = 3;
         afPitchOne[0] = afPitchOne[1] = afPitchOne[2] = pfPitch[0];
         pfPitch = afPitchOne;
      }
      else if (dwNumPitch == 2) {
         dwNumPitch = 3;
         afPitchOne[0] = pfPitch[0];
         if ( ((pfPitch[0] > 0) && (pfPitch[1] > 0)) || ((pfPitch[0] < 0) && (pfPitch[1] < 0)) )
            afPitchOne[1] = (pfPitch[0] + pfPitch[1]) / 2;
         else
            afPitchOne[1] = 0;   // since cant really guess
         afPitchOne[2] = pfPitch[1];
         pfPitch = afPitchOne;
      }
#endif // 0

      for (j = 0; j < dwNumPitchF0Extra; j++) {
         if (pfPitchF0Extra[j] < 0) {
            // if < 0 it's a transplanted prosofy pitch, so use as absolute
            pfPitchF0Extra[j] *= -1;
            continue;
         }

         // BUGFIX - If pfPitch[j] == 0 then don't bother adjusting, because
         // wont be used later
         if (!pfPitchF0Extra[j])
            continue;

         fTime = (fp)dwCur + (fp)padwDur[i] * (fp)(j+1) / (fp)(dwNumPitchF0Extra+1);
         iTime = (int)(fTime + .5);
         iTime = max(iTime, 0);
         if (iTime >= (int)dwNumInflect-1)  // BUGFIX - should be dwNumInflect-1
            iTime = (int)dwNumInflect-1;
         pfPitchF0Extra[j] = ((iTime > 0) ? pafPitchF0Extra[iTime] : 1.0) * pfPitchF0Extra[j];

#if 0 // to test
         fprintf (file, "\r\n%d = %.3g", (int)iTime, (double)pafPitch[iTime]);
#endif // 0
      } // j


      for (j = 0; j < dwNumPitchF0Fuji; j++) {
         if (pfPitchF0Fuji[j] < 0) {
            pfPitchF0Fuji[j] *= -1;
            continue;
         }

         // BUGFIX - If pfPitch[j] == 0 then don't bother adjusting, because
         // wont be used later
         if (!pfPitchF0Fuji[j])
            continue;

         fTime = (fp)dwCur + (fp)padwDur[i] * (fp)(j+1) / (fp)(dwNumPitchF0Fuji+1);
         iTime = (int)(fTime + .5);
         iTime = max(iTime, 0);
         if (iTime >= (int)dwNumInflect-1)  // BUGFIX - should be dwNumInflect-1
            iTime = (int)dwNumInflect-1;
         pfPitchF0Fuji[j] = ((iTime > 0) ? pafPitchF0Fuji[iTime] : fDefPitch) * pfPitchF0Fuji[j];

#if 0 // to test
         fprintf (file, "\r\n%d = %.3g", (int)iTime, (double)pafPitch[iTime]);
#endif // 0

      } // j

      // set back again
      if (pfPitchF0Extra == afPitchF0ExtraOne)
         plPitchF0Extra->Set (i, pfPitchF0Extra, dwNumPitchF0Extra * sizeof(*pfPitchF0Extra));
      if (pfPitchF0Fuji == afPitchF0FujiOne)
         plPitchF0Fuji->Set (i, pfPitchF0Fuji, dwNumPitchF0Fuji * sizeof(*pfPitchF0Fuji));

      // figure out the time
      fTime = (fp)dwCur + (fp)padwDur[i]/2;
      iTime = (int)(fTime + .5);
      iTime = max(iTime, 0);
      iTime = min(iTime, (int)dwNumInflect);

      // adjust the volume
      if (pafVol[i] < 0)
         pafVol[i] *= -1;  // since negative volume used to indicate TP
      else
         // volume modifies curve
         pafVol[i] *= ((iTime > 0) ? pafVolIn[iTime] : 1);

      pafAccent[i] = ((iTime > 0) ? pafAccentIn[iTime] : 0.0);
   } // i

   // if use transplanted prosody then figure out better fuji values
   if (fDoesPitchUseTP) {
      // make upa  lsit that can pass to the fuji code
      CListFixed lPITCHSUBSYLLABLE;
      lPITCHSUBSYLLABLE.Init (sizeof(PITCHSUBSYLLABLE));
      PITCHSUBSYLLABLE pss;
      PWSTR pszLastWord = NULL;
      DWORD dwWordNum = 0;
      DWORD dwSamplesPerSec = SRSAMPLESPERSEC;
      DWORD dwCur = 0;
      BOOL *pafIsPitchTP = (BOOL*)lIsPitchTP.Get(0);
      for (i = 0; i < dwNum; dwCur += padwDur[i++], pafIsPitchTP++) {
         PLEXPHONE plp = pLex->PhonemeGetUnsort ((BYTE)padwPhone[i]);
         PLEXENGLISHPHONE ple = plp ? MLexiconEnglishPhoneGet(plp->bEnglishPhone) : NULL;

         // what kind of sound is it
         memset (&pss, 0, sizeof(pss));
         if (ple) {
            pss.fSilence = FALSE;
            pss.fVowel = ((ple->dwCategory & PIC_MAJORTYPE) == PIC_VOWEL);
            pss.fVoiced = (ple->dwCategory & PIC_VOICED) ? TRUE : FALSE;
         }
         else {
            pss.fSilence = TRUE;
            pss.fVowel = FALSE;
            pss.fVoiced = FALSE;
         }

         // word number
         if (pszLastWord != paszWord[i]) {
            pszLastWord = paszWord[i];
            dwWordNum++;
         }
         pss.dwWordNum = dwWordNum;

         // time
         pss.dwStart = dwCur;
         pss.dwEnd = dwCur + padwDur[i];

         fp *pfPitchF0Extra = (fp*)plPitchF0Extra->Get(i);
         fp *pfPitchF0Fuji = (fp*)plPitchF0Fuji->Get(i);
         DWORD dwNumPitchF0Extra = (DWORD)plPitchF0Extra->Size(i) / sizeof(fp);
         DWORD dwNumPitchF0Fuji = (DWORD)plPitchF0Fuji->Size(i) / sizeof(fp);

         pss.dwMid = (pss.dwStart + pss.dwEnd)/2;
         if (!dwNumPitchF0Extra)
            pss.fPitchHighest = pss.fPitchLowest = -1000;   // make sure ignore
         else {   // have pitch points
            pss.fPitchHighest = -1000;
            pss.fPitchLowest = 1000;

            for (j = 0; j < dwNumPitchF0Extra; j++) {
               fp fPitch = log(pfPitchF0Extra[j] * pfPitchF0Fuji[0] / SRBASEPITCH) / log(2.0);
               pss.fPitchHighest = max(pss.fPitchHighest, fPitch);
               if (fPitch < pss.fPitchLowest) {
                  pss.fPitchLowest = fPitch;
                  pss.dwMid = (j * (pss.dwEnd - pss.dwStart) + (pss.dwEnd - pss.dwStart)/2) / dwNumPitchF0Extra +
                     pss.dwStart;
               }
            } // j
         }

         // add this
         lPITCHSUBSYLLABLE.Add(&pss);
      } // i


      // figure out fuji
      PPITCHSUBSYLLABLE ppss = (PPITCHSUBSYLLABLE)lPITCHSUBSYLLABLE.Get(0);
      if (!PitchSubCalc (ppss, lPITCHSUBSYLLABLE.Num(), dwSamplesPerSec))
         return FALSE;  // error
      _ASSERTE (dwNum == lPITCHSUBSYLLABLE.Num());

      // replace the fuji
      pafIsPitchTP = (BOOL*)lIsPitchTP.Get(0);
      for (i = 0; i < dwNum; i++, pafIsPitchTP++, ppss++) {
         fp *pfPitchF0Extra = (fp*)plPitchF0Extra->Get(i);
         fp *pfPitchF0Fuji = (fp*)plPitchF0Fuji->Get(i);
         DWORD dwNumPitchF0Extra = (DWORD)plPitchF0Extra->Size(i) / sizeof(fp);
         DWORD dwNumPitchF0Fuji = (DWORD)plPitchF0Fuji->Size(i) / sizeof(fp);

         // BUGFIX - Modify for non-TP also because once modifying one fuji
         // should modify them all, so consistent
         // Also need disabled so handle absolute pitch calls
         // don't bother modify in non-TP
         //if (!(*pafIsPitchTP))
         //   continue;

         if (ppss->fPitchSub < -900)
            continue;   // shouldnt happen

         fp fPitchF0Fuji = pow(2.0, ppss->fPitchSub) * SRBASEPITCH;
         for (j = 0; j < dwNumPitchF0Extra; j++)
            pfPitchF0Extra[j] = pfPitchF0Extra[j] * pfPitchF0Fuji[0] / fPitchF0Fuji;   // for change in fuji
         for (j = 0; j < dwNumPitchF0Fuji; j++)
            pfPitchF0Fuji[j] = fPitchF0Fuji;
      }
   } // fDoesPitchUseTP


   // apply pitch expressiveness - after TP
   for (i = 0; i < dwNum; i++)
      if (paTTSGLOBALSTATE[i].fDerPitchExpress != 1) {
         fp *pfPitchF0Extra = (fp*)plPitchF0Extra->Get(i);
         DWORD dwNumPitchF0Extra = (DWORD)plPitchF0Extra->Size(i) / sizeof(fp);
         for (j = 0; j < dwNumPitchF0Extra; j++)
            pfPitchF0Extra[j] =pow (pfPitchF0Extra[j],  paTTSGLOBALSTATE[i].fDerPitchExpress);
      }

#if 0 // to test
   fprintf (file, "\r\n\r\n\r\n");
   for (i = 0; i < dwNum; i++) {
      fprintf (file, "\r\nPhone %d : %x, %d, %.3g; ",
         (DWORD) i, padwPhone[i], *((DWORD*)plDur->Get(i)), (double) *((fp*)plVol->Get(i)));

      fp *paf = (fp*)plPitch->Get(i);
      DWORD dwNum2 = plPitch->Size(i) / sizeof(DWORD);
      for (j = 0; j < dwNum2; j++)
         fprintf (file, "%.3g ", (double)paf[j]);
   } // i
   fclose (file);
#endif // 0

   // done
   return TRUE;
}

/*************************************************************************************
CMTTS::ApplyAccent - This loops through the list of phonemes, looking for patterns
in the phonemes and applies an accent based on the phoneme sequence.

inputs
   PCListFixed       plPhone - Will be filled in with a list of phonemes (DWORD), with
                              high byte (<< 24) filled in indicating start, middle,
                              end of word
   PCListFixed       plWord - Initialized to sizeof (PWSTR). Filled in with list of
                              pointers to strings (the strings stored in plStrings).
                              These are the words/punctuation at each phoneme.
   PCListFixed       plMMLWord - Initialized to sizeof(PCMMLNode2). Filled in with the
                              node for the word for each phoneme. If a word has several
                              phonemes they all point to the same node.
   PCListFixed       plPhoneMod - Initialized to sizeof (CPoint). FIlled in with the
                              phone-specific [0]=pitch, [1]=vol, [2]=dur for each,
                              phoneme.
   PCListFixed       plTTSGLOBALSTATE - Initialized to sizeof (TTSGLOBALSTATE) and then
                     filled with one TTSGLOBALSTATE entry per phoneme, so know how fast
                     should speak, etc.
   PTTSVOICEMOD   pVoiceMod - Parameters controlling how the voice is modified by
                  the original. Generally, pass in NULL. Only internal functions
                  will pass in non-null.
returns
   BOOL - TRUE if success
*/
BOOL CMTTS::ApplyAccent (PCListFixed plPhone, PCListFixed plWord, PCListFixed plMMLWord,
                         PCListFixed plPhoneMod, PCListFixed plTTSGLOBALSTATE, PTTSVOICEMOD pVoiceMod)
{
   // if no accent to apply just exit here
   if (!pVoiceMod->pSubVoice->m_lTTSACCENTRULE.Num())
      return TRUE;

   PTTSACCENTRULE par = (PTTSACCENTRULE)pVoiceMod->pSubVoice->m_lTTSACCENTRULE.Get(0);
   DWORD dwNum = pVoiceMod->pSubVoice->m_lTTSACCENTRULE.Num();
   
   // loop through all the phonems
   DWORD i, j, k;
   for (i = 0; i < plPhone->Num(); i++) {
      // phonemes
      DWORD *padwPhone = (DWORD*)plPhone->Get(0);

      // see if find match...
      DWORD dwBestMatch = -1;
      DWORD dwMatchLen = 0;
      for (j = 0; j < dwNum; j++) {
         DWORD dwLen = (DWORD)strlen((char*)par[j].abOrig);
         if (dwLen + i > plPhone->Num())
            continue;
         if ((dwBestMatch != -1) && (dwLen <= dwMatchLen))
            continue;   // not long enough anyway

         // look for match, also seeing if word start/end
         for (k = 0; k < dwLen; k++)
            if (par[j].abOrig[k]-1 != (BYTE)padwPhone[i+k])
               break;
         if (k < dwLen)
            continue;   // no match

         // see if word start/end requirement
         if ((par[j].dwLoc == 1) && !(padwPhone[i+0] & (WORDPOS_WORDSTART << 24)))
            continue;   // supposed to be at word start but isnt
         if ((par[j].dwLoc == 2) && !(padwPhone[i+dwLen-1] & (WORDPOS_WORDEND << 24)))
            continue;   // supposed to be at word start but isnt

         // else have match
         if ((dwBestMatch == -1) || (dwLen > dwMatchLen)) {
            dwBestMatch = j;
            dwMatchLen = dwLen;
         }
      } // j

      if (dwBestMatch == -1)
         continue;   // none found

      // fing out if there's a word start/end here
      DWORD dwWordStart = -1, dwWordEnd = -1;
      for (j = 0; j < dwMatchLen; j++) {
         if (padwPhone[i + j] & (WORDPOS_WORDSTART << 24))
            dwWordStart = j;
         else if (padwPhone[i+j] & (WORDPOS_WORDEND << 24))
            dwWordEnd = j;
      } // j

      // insert/remove phonemes? this is somewhat stupid in that it
      // inserts/remove right from the current location
      DWORD dwLenInsert = (DWORD)strlen((char*)par[dwBestMatch].abNew);
      if (dwLenInsert > dwMatchLen) {
         // NOTE - Dont change dwWordStart or dwWord end because not really sure how
         DWORD dwPhoneInsert = 0;
         PWSTR pszWordInsert = *((PWSTR*)plWord->Get(i));
         PCMMLNode2 pszMMLInsert = *((PCMMLNode2*)plMMLWord->Get(i));
         TTSGLOBALSTATE gsInsert = *((PTTSGLOBALSTATE) plTTSGLOBALSTATE->Get(i));
         CPoint pMod;
         pMod.Copy ((PCPoint)plPhoneMod->Get(i));

         for (j = 0; j < dwLenInsert-dwMatchLen; j++) {
            plPhone->Insert (i, &dwPhoneInsert);
            plPhoneMod->Insert (i, &pMod);
            plWord->Insert (i, &pszWordInsert);
            plMMLWord->Insert (i, &pszMMLInsert);
            plTTSGLOBALSTATE->Insert (i, &gsInsert);
         } // j
      }
      else if (dwLenInsert < dwMatchLen) {
         // else remove
         DWORD dwDiff = dwMatchLen - dwLenInsert;
         if (dwWordStart != -1)
            dwWordStart = (dwWordStart >= dwDiff) ? (dwWordStart - dwDiff) : 0;
         if (dwWordEnd != -1)
            dwWordEnd = (dwWordEnd >= dwDiff) ? (dwWordEnd - dwDiff) : 0;
         for (j = 0; j < dwDiff; j++) {
            plPhone->Remove (i);
            plPhoneMod->Remove (i);
            plWord->Remove (i);
            plMMLWord->Remove (i);
            plTTSGLOBALSTATE->Remove (i);
         }
      }

      // reload the phonem pointer because with insertion and deletion may have messed up
      padwPhone = (DWORD*)plPhone->Get(0);

      // write new phonemes
      for (j = 0; j < dwLenInsert; j++) {
         DWORD dwVal = (DWORD)par[dwBestMatch].abNew[j];
         dwVal--; // so take out offset
         if (dwWordStart == j)
            dwVal |= ((WORDPOS_WORDSTART | WORDPOS_SYLSTART) << 24);
         if (dwWordEnd == j)
            dwVal |= ((WORDPOS_WORDEND | WORDPOS_SYLEND) << 24);
         padwPhone[i+j] = dwVal;
      }
      
      // continue after the current changes
      i += dwLenInsert - 1;
   } // i

   return TRUE;
}

   
/*************************************************************************************
CMTTS::SynthWordMMLToPhones - This takes MML from the text parser's call
(such as ParseFromMMLText), then:

1) calls ParseAddPronunciation() just to make sure every word has a pronunciation.
      NOTE: This modifies the original MML
2) Goes through each "word" looking for a punctuation and adds the word to the
      phoneme.
3) If it finds a punctuation it adds silence.
4) Adds tags to indicate what the prosody should be

inputs
   PCMMLNode2         pNode - MML node from ParseFromMMLText() or ilk. This is modified
                              to add pronunciations
   DWORD             dwNodeStart - Where to start processing in this node. Initially pass
                        in 0, but on later passes, use last *pdwNodeEnd.
   PCListFixed       plPhone - Will be filled in with a list of phonemes (DWORD), with
                              high byte (<< 24) filled in indicating start, middle,
                              end of word. WORDPOS_XXX in high byte
   PCListFixed       plWord - Initialized to sizeof (PWSTR). Filled in with list of
                              pointers to strings (the strings stored in plStrings).
                              These are the words/punctuation at each phoneme.
   PCListFixed       plMMLWord - Initialized to sizeof(PCMMLNode2). Filled in with the
                              node for the word for each phoneme. If a word has several
                              phonemes they all point to the same node.
   PCListFixed       plBookmarkIndex - Initialized to sizeof(DWORD) and indicates which phoneme (as
                              per plWord and plMMLWord) a bookmark appears before.
   PCListVariable    plBookmarkString - Associated bookmark string with each index.
   PCListFixed       plPhoneMod - Initialized to sizeof (CPoint). FIlled in with the
                              phone-specific [0]=pitch, [1]=vol, [2]=dur for each,
                              phoneme.
   PCListVariable    plString - Scratch space used to store the strings pointed to
                              by plWords. Don't delete until not using plWords anymore.
   PTTSGLOBALSTATE   pState - Filled with the initial speaking state
   PCListFixed       plTTSGLOBALSTATE - Initialized to sizeof (TTSGLOBALSTATE) and then
                     filled with one TTSGLOBALSTATE entry per phoneme, so know how fast
                     should speak, etc.
   PTTSVOICEMOD   pVoiceMod - Parameters controlling how the voice is modified by
                  the original. Generally, pass in NULL. Only internal functions
                  will pass in non-null.
   int               iTTSQuality - TTS quality. 0 for fast, 1 for normal, 2 for slow, 3 for best, 4 for multipass
   BOOL              fTransPros - Set this to TRUE if speaking transplanted prosody, and will
                     generate syllables more quickly since they info is likely to be thrown
                     out anyway
   DWORD                dwMultiPass - Pass number when generating several different versions of the
                        same sentence, and selecting the best one for TTS. Used to make the
                        random work better.
   DWORD             *pdwNodeEnd - Filled in with the node that got up to processing (exclusive).
                        Pass this into dwNodeStart to process rest of sentence.
   fp                fTempVolume - Temporary adjustments to volume. Pass in 1.0 for no change
   fp                fTempSpeed - Temprorary adjustments to speed. Pass in 1.0 for no change.
returns
   BOOL - TRUE if success, FALSE if no more to produce
*/
BOOL CMTTS::SynthWordMMLToPhones (PCMMLNode2 pNode, DWORD dwNodeStart, PCListFixed plPhone, PCListFixed plWord,
                                  PCListFixed plMMLWord, PCListFixed plBookmarkIndex, PCListVariable plBookmarkString,
                                  PCListFixed plPhoneMod, PCListVariable plString,
                                  PTTSGLOBALSTATE pState, PCListFixed plTTSGLOBALSTATE,
                                  PTTSVOICEMOD pVoiceMod, int iTTSQuality, BOOL fTransPros, DWORD dwMultiPass,
                                  DWORD *pdwNodeEnd, fp fTempVolume, fp fTempSpeed)
{
   *pdwNodeEnd = (DWORD)-1;   // so something set

   plPhone->Init (sizeof(DWORD));
   plPhoneMod->Init (sizeof(CPoint));
   plWord->Init (sizeof(PWSTR));
   plMMLWord->Init (sizeof(PCMMLNode2));
   plTTSGLOBALSTATE->Init (sizeof(TTSGLOBALSTATE));
   plString->Clear();
   plBookmarkIndex->Init (sizeof(DWORD));
   plBookmarkString->Clear();

   // no-op phone mod
   CPoint pPhoneModNOP;
   pPhoneModNOP.Zero();
   pPhoneModNOP.p[0] = pPhoneModNOP.p[1] = pPhoneModNOP.p[2] = 1;

   // keep track of current state
   TTSGLOBALSTATE gsCur = *pState;

   PCMLexicon pLex = pVoiceMod->pLex;
   if (!pLex)
      pLex = Lexicon();
   if (!pLex)
      return FALSE;
   DWORD dwNumPhone = pLex->PhonemeNum();
   //DWORD dwNeedMicro = (dwNumPhone * dwNumPhone + 7) / 8;
   //BOOL fMicroPause = (m_memMicroPause.m_dwCurPosn == dwNeedMicro);
   BYTE bSilence = (BYTE) pLex->PhonemeFindUnsort(pLex->PhonemeSilence());

   // add the proncunaitions
   CTextParse TextParse;
   if (!TextParse.Init (pLex->LangIDGet(), pLex))
      return FALSE;

#ifdef _DEBUG
   DWORD dwStartTime = GetTickCount();
#endif
   if (!dwNodeStart) {
      // BUGFIX - Only add prounciations if the first time through
      if (!TextParse.ParseAddPronunciation (pNode, (iTTSQuality >= 4) ))   // random pronunciations if multipass
         return FALSE;
   }
#ifdef _DEBUG
   WCHAR szTemp[64];
   swprintf (szTemp, L"\r\nParseAddPronunciation time = %d", (int)(GetTickCount() - dwStartTime));
   OutputDebugStringW (szTemp);
   dwStartTime = GetTickCount();
#endif
   // BUGFIX - Moved the prosody from the bottom to the top since need it
   // to set the micropause flag
   // add prosody
   if (!SynthProsodyTagsApply (pNode, dwNodeStart, &TextParse, pVoiceMod, iTTSQuality, fTransPros, dwMultiPass, pdwNodeEnd))
      return FALSE;
#ifdef _DEBUG
   swprintf (szTemp, L"\r\nSynthProsodyTagsApply time = %d", (int)(GetTickCount() - dwStartTime));
   OutputDebugStringW (szTemp);
#endif

   // loop through all the elements in the node
   DWORD i, j;
   PWSTR psz;
   PCMMLNode2 pSub;
   BYTE abPron[256];
   DWORD dwSize;
   DWORD dwPhone;
   DWORD dwLastPhone = -1; // last phoneme added, for micropause info
   for (i = dwNodeStart /*0*/; i < *pdwNodeEnd /*pNode->ContentNum()*/; i++) {
      pSub = NULL;
      psz = NULL;
      pNode->ContentEnum (i, &psz, &pSub);
      if (!pSub)
         continue;   // ignore straight text

      // will need to deal with embedded tags of various sorts
      psz = pSub->NameGet();
      if (!psz)
         continue;

      if (!_wcsicmp(psz, gpszTTSGlobalState)) {
         // get the state
         GlobalStateFromMML (pSub, pVoiceMod->pSubVoice, &gsCur);
         continue;
      }
      else if (!_wcsicmp(psz, gpszBookmark)) {
         // get the mark
         psz = pSub->AttribGetString (L"mark");
         if (!psz)
            continue;   // bookmark without a string

         // add this
         plBookmarkString->Add (psz, (wcslen(psz)+1)*sizeof(WCHAR));
         DWORD dwIndex = plMMLWord->Num();
         plBookmarkIndex->Add (&dwIndex);
         continue;
      }
      else if (!_wcsicmp(psz, TextParse.Word())) {
         // get the pronunciation
         psz = pSub->AttribGetString (TextParse.Pronunciation());
         if (!psz)
            continue;

         // else convert to binary
         dwSize = (DWORD)MMLBinaryFromString (psz, abPron, sizeof(abPron) - 1 /* so can NULL terminate */);
         if (!dwSize || (dwSize > sizeof(abPron)))
            continue;   // either nothing there or too large

         // get the text
         psz = pSub->AttribGetString(TextParse.Text());
         if (psz) {
            plString->Add (psz, (wcslen(psz)+1)*sizeof(WCHAR));
            psz = (PWSTR) plString->Get(plString->Num()-1);
         }

         // see if want to add pause because of Ngram
         int iValue = 0;
         double fdPauseLeft, fDerMicropauses;
         if (!pSub->AttribGetDouble (gpszPauseLeft, &fdPauseLeft))
            fdPauseLeft = 0.0;
         if (!pSub->AttribGetDouble (gpszDerMicropauses, &fDerMicropauses))
            fDerMicropauses = 0.5;

         BYTE abPOS[(TTSPROSNGRAMBIT+TTSPROSNGRAM)*2+1];
         FindSurroundingPOS (&TextParse, pNode, dwNodeStart, *pdwNodeEnd, i, abPOS);  // need for phoneme duration tweaks

#if 0 // old code
         CPoint pNGram;
         BYTE abPOS[(TTSPROSNGRAMBIT+TTSPROSNGRAM)*2+1];
         BOOL fPauseLeft, fPauseNGram;
         won't work because changed to partial parse of pNode
         NGramFindBackoff (&TextParse, pNode, i, &pNGram, &fdPauseLeft, abPOS);
         fPauseNGram = fPauseLeft;
#endif // 0

         // consider adding a micropause
         if ((abPron[0] < dwNumPhone) && (dwLastPhone < dwNumPhone)) {
            DWORD adwPause[2];
            DWORD dwSubPros;
            adwPause[0] = adwPause[1] = 0;
            for (dwSubPros = 0; dwSubPros <= TTSVOICEMODMAXPROSODY; dwSubPros++) {
               PCTTSProsody pPros = (dwSubPros < TTSVOICEMODMAXPROSODY) ? pVoiceMod->apTTSProsody[dwSubPros] : m_pCTTSProsody;
               if (!pPros)
                  continue;

               DWORD *padw = pPros->PhonemePauseGet (dwLastPhone, abPron[0], FALSE, pLex);
               if (padw) {
                  adwPause[0] += padw[0];
                  adwPause[1] += padw[1];
               }
            }

            // if have data points then affect prosody
            if (adwPause[0] >= MINPROSODYSAMPLES) {
               double fPausePhone = (double)adwPause[1] / (double)adwPause[0];
               fPausePhone *= fPausePhone;
                  // HACK - So doesn't pause so much when speaking with my voice.
                  // not technically correct
               fdPauseLeft = fdPauseLeft + fPausePhone - fdPauseLeft * fPausePhone;
            }
         } // if phonemes in range

         // old code
         //if (fMicroPause && (abPron[0] < dwNumPhone) && (dwLastPhone < dwNumPhone)) {
         //   DWORD dwBit = dwLastPhone * dwNumPhone + (DWORD)abPron[0];
         //   PBYTE pb = (PBYTE)m_memMicroPause.p + (dwBit / 8);

         //   if (pb[0] & (1 << (dwBit % 8)))
         //      fPauseLeft = TRUE;
         //}

         // account for dermicropauses
         fdPauseLeft -= (fDerMicropauses - 0.5);
         //fdPauseLeft = pow (fdPauseLeft, pow (2, fDerMicropauses*2.0 - 1.0) );

         // BUGFIX - If flat set in TTS voice to halve pauses then do so
         // use for my voice because of the way I recorded it
         if (m_fPauseLessOften)
            fdPauseLeft /= 2.0;

         BOOL fPauseLeft = (randf(0, 1.0) < fdPauseLeft);
         BOOL fPauseNGram = fPauseLeft;

         // if thinking about adding micropause, don't do this if we have
         // a transplanted prosody word, which measn TPPitchRel, etc.
         if (fPauseLeft) {
            if (pSub->AttribGetString (gpszTPPitchRel))
               fPauseLeft = FALSE;
            else if (pSub->AttribGetString (gpszTPPitchAbs))
               fPauseLeft = FALSE;
            else if (pSub->AttribGetString (gpszTPVolRel))
               fPauseLeft = FALSE;
            else if (pSub->AttribGetString (gpszTPVolAbs))
               fPauseLeft = FALSE;
            else if (pSub->AttribGetString (gpszTPDurRel))
               fPauseLeft = FALSE;
            else if (pSub->AttribGetString (gpszTPDurAbs))
               fPauseLeft = FALSE;
         }


         // add micropauise
         if (fPauseLeft && (abPron[0] < dwNumPhone) && (dwLastPhone < dwNumPhone)) {
            PWSTR pszNull = NULL;
            PCMMLNode2 pNull = NULL;
            dwPhone = bSilence;

            // BUGFIX - Skip microsilences, since too much
#if 0
            plPhone->Add (&dwPhone);
            plPhoneMod->Add (&pPhoneModNOP);
            plWord->Add (&pszNull);
            plMMLWord->Add (&pNull);
            plTTSGLOBALSTATE->Add (&gsCur);
#endif

            // BUGFIX - if pausing for an ngram then add two pauses
            // BUGFIX - because skipping microsilence above, the ngram silence only
            // gets one silence unit, which seems to work quite well
            if (fPauseNGram) {
               plPhone->Add (&dwPhone);
               plPhoneMod->Add (&pPhoneModNOP);
               plWord->Add (&pszNull);
               plMMLWord->Add (&pNull);
               plTTSGLOBALSTATE->Add (&gsCur);
            }
         }

         // determine the syllable boundaries
         for (j = 0; j < dwSize; j++)
            abPron[j]++;
         abPron[dwSize] = 0;
         CListFixed lBoundary;
         lBoundary.Init (sizeof(DWORD));
         if (psz)
            pLex->WordSyllables (abPron, psz, &lBoundary);
         DWORD *padwBoundary = (DWORD*)lBoundary.Get(0);

         // restore
         for (j = 0; j < dwSize; j++)
            abPron[j]--;

         // add all these phonemes
         for (j = 0; j < dwSize; j++) {
            dwPhone = dwLastPhone = abPron[j];

            // see if boundary
            if (j == 0)
               dwPhone |= (WORDPOS_SYLSTART << 24);
            DWORD k;
            for (k = 0; k < lBoundary.Num(); k++) {
               if ((padwBoundary[k] & 0xffff) == j)
                  dwPhone |= (WORDPOS_SYLSTART << 24);
               if ((padwBoundary[k] & 0xffff) == (j+1))
                  dwPhone |= (WORDPOS_SYLEND << 24);
            } // k

            if (j == 0)
               dwPhone |= (WORDPOS_WORDSTART << 24);
            if (j+1 == dwSize)
               dwPhone |= (WORDPOS_WORDEND << 24);
            plPhone->Add (&dwPhone);
            plWord->Add (&psz);
            plMMLWord->Add (&pSub);
            plTTSGLOBALSTATE->Add (&gsCur);

            // adjustment based on location of phoneme in word
            BYTE bPOSL = abPOS[TTSPROSNGRAMBIT+TTSPROSNGRAM-1];
            BYTE bPOSR = abPOS[TTSPROSNGRAMBIT+TTSPROSNGRAM+1];
            bPOSL = min(bPOSL, PHONEPOSBIN-1);
            bPOSR = min(bPOSR, PHONEPOSBIN-1);

            DWORD dwIndexL = j;
            DWORD dwIndexR = dwSize - j - 1;
            BOOL fLeft = (dwIndexL < NUMPHONEEMPH);
            BOOL fRight = (dwIndexR < NUMPHONEEMPH);
            dwIndexR = 2 * NUMPHONEEMPH - dwIndexR - 1;
            CPoint pMod;

            if (fLeft && !fRight)
               pMod.Copy (&m_apPhoneEmph[dwIndexL][bPOSL]);
            else if (fRight && !fLeft)
               pMod.Copy (&m_apPhoneEmph[dwIndexR][bPOSR]);
            else if (fRight && fLeft) {
               // multiply and sqrt
               pMod.p[0] = sqrt(m_apPhoneEmph[dwIndexL][bPOSL].p[0] *
                  m_apPhoneEmph[dwIndexR][bPOSR].p[0]);
               pMod.p[1] = sqrt(m_apPhoneEmph[dwIndexL][bPOSL].p[1] *
                  m_apPhoneEmph[dwIndexR][bPOSR].p[1]);
               pMod.p[2] = sqrt(m_apPhoneEmph[dwIndexL][bPOSL].p[2] *
                  m_apPhoneEmph[dwIndexR][bPOSR].p[2]);
            }
            else
               pMod.Copy (&pPhoneModNOP);

            plPhoneMod->Add (&pMod);
         }

         continue;
      }
      else if (!_wcsicmp(psz, TextParse.Punctuation())) {
         // get the text
         psz = pSub->AttribGetString(TextParse.Text());
         if (psz) {
            plString->Add (psz, (wcslen(psz)+1)*sizeof(WCHAR));
            psz = (PWSTR) plString->Get(plString->Num()-1);
         }

         // put in silence pause for punctuation
         dwPhone = bSilence;
         plPhone->Add (&dwPhone);
         plPhoneMod->Add (&pPhoneModNOP);
         plWord->Add (&psz);
         plMMLWord->Add (&pSub);
         plTTSGLOBALSTATE->Add (&gsCur);

         // because adding silence, not that last phoneme is -1 so
         // dont add another silence for micropause
         dwLastPhone = -1;
         continue;
      }
      else if (!_wcsicmp(psz, gpszBreak)) {
         // if it's none then ignore
         psz = pSub->AttribGetString (gpszTime);
         if (psz && !_wcsicmp(psz, L"none"))
            continue;

         // put in silence pause for the break
         dwPhone = bSilence;
         psz = NULL;
         plPhone->Add (&dwPhone);
         plPhoneMod->Add (&pPhoneModNOP);
         plWord->Add (&psz);
         plMMLWord->Add (&pSub);
         plTTSGLOBALSTATE->Add (&gsCur);

         // because adding silence, not that last phoneme is -1 so
         // dont add another silence for micropause
         dwLastPhone = -1;
         continue;
      }
   } // i

   // if have added a bookmark, but nothing else, then add a very brief silence
   // This ensures that bookmarks at the end get played
   if (plBookmarkIndex->Num() && !plPhone->Num()) {
      // put in silence pause for the break
      dwPhone = bSilence;
      psz = NULL;
      PCMMLNode2 pNull = NULL;
      plPhone->Add (&dwPhone);
      plPhoneMod->Add (&pPhoneModNOP);
      plWord->Add (&psz);
      plMMLWord->Add (&pNull);
      plTTSGLOBALSTATE->Add (&gsCur);
   }

   PTTSGLOBALSTATE pgs = (PTTSGLOBALSTATE) plTTSGLOBALSTATE->Get(0);
   for (i = 0; i < plTTSGLOBALSTATE->Num(); i++, pgs++) {
      pgs->fProsodyVol *= fTempVolume;
      pgs->fProsodyWPM *= fTempSpeed;
      pgs->fDerVol *= fTempVolume;
      pgs->fDerWPM *= fTempSpeed;
   }


   // add the accent
   if (!ApplyAccent (plPhone, plWord, plMMLWord, plPhoneMod, plTTSGLOBALSTATE, pVoiceMod))
      return FALSE;

   return TRUE;
}


/*************************************************************************************
CMTTS::SynthTextToWordMML - This converts from text (either tagged or not) into
the WordMML, which can then be passed into SynthWordMMLToPhones() to generate
all the phones to be spoken.

inputs
   PWSTR          pszText - Text to speak.
   BOOL           fTagged - If it's tagged then it's in MML format, else it's just
                     raw text.
   PCMLexicon        pLex - Lexicon to use. If not specified then will use default lexicon.
returns
   PCMMLNode2 - Node containing the information, which must be freed by the caller.
               Returns NULL if error
*/
PCMMLNode2 CMTTS::SynthTextToWordMML (PWSTR pszText, BOOL fTagged, PCMLexicon pLex)
{
   // convert
   CTextParse TextParse;
   if (!TextParse.Init (pLex->LangIDGet(), pLex))
      return NULL;
   PCMMLNode2 pNode = TextParse.MMLToPreParse (pszText, fTagged);
   if (!pNode)
      return NULL;

   return SynthMMLToWordMML (pNode, pLex);
}


/*************************************************************************************
CMTTS::SynthMMLToWordMML - This takes MML outputted from CTextParse::MMLToPreParse()
   and converts it to MML with words.

inputs
   PCMMLNOde2     pNode - MML to modify. This is modified in place.
   PCMLexicon        pLex - Lexicon to use. If not specified then will use default lexicon.
returns
   PCMMLNode2 - Node containing the information, which must be freed by the caller.
               Returns NULL if error. Will be pNode. If NULL then pNode is deleted
*/
PCMMLNode2 CMTTS::SynthMMLToWordMML (PCMMLNode2 pNode, PCMLexicon pLex)
{
   if (!pLex)
      pLex = Lexicon();
   if (!pLex)
      return NULL;

   // add the proncunaitions
   CTextParse TextParse;
   if (!TextParse.Init (pLex->LangIDGet(), pLex)) {
      delete pNode;
      return NULL;
   }

   if (!TextParse.ParseFromMML (pNode, FALSE, FALSE)) {
      delete pNode;
      return NULL;
   }

   return pNode;
}


/*************************************************************************************
CMTTS::FillInVOICEMOD - This internal function fills in a VOICEMOD structure with
the relevent information

inputs
   PTTSVOICEMOD      pVoiceMod - To be filled in
   PCMTTSSubVoice    pSubVoice - Sub voice to use. If NULL then uses the generic voice,
                        or, if this is a derived voice, the first choice
   PCTTSProsody      *papCTTSProsody - Pointer to an array of TTSVOICEMODMAXPROSODY prosody
                     elements that will be filled in as this makes the subvoice. Some
                     may be left NULL. Should initially be cleared.
                     This is only used if pSubVoice is set.
returns
   none
*/
void CMTTS::FillInVOICEMOD (PTTSVOICEMOD pVoiceMod, PCMTTSSubVoice pSubVoice, PCTTSProsody *papCTTSProsody)
{
   memset (pVoiceMod, 0, sizeof(*pVoiceMod));
   if (pSubVoice) {
      pVoiceMod->pSubVoice = pSubVoice;
      memcpy (pVoiceMod->apTTSProsody, papCTTSProsody, sizeof(pVoiceMod->apTTSProsody));
   }
   else if (m_lPCMTTSSubVoice.Num()) {
      PCMTTSSubVoice *ppsv = (PCMTTSSubVoice*) m_lPCMTTSSubVoice.Get(0);
      pVoiceMod->pSubVoice = ppsv[0];
      pVoiceMod->apTTSProsody[0] = pVoiceMod->pSubVoice->m_pCTTSProsody;
   }
   else {
      // make sure generic has the master's settings for pitch, wmp
      m_SubVoiceGeneric.m_fAvgPitch = m_pTTSMaster ? m_pTTSMaster->AvgPitchGet(FALSE) : AvgPitchGet(FALSE);
      //m_SubVoiceGeneric.m_fAvgSyllableDur = m_pTTSMaster ? m_pTTSMaster->AvgSyllableDurGet() : AvgSyllableDurGet();
      m_SubVoiceGeneric.m_dwWordsPerMinute = m_pTTSMaster ? m_pTTSMaster->WordsPerMinuteGet() : WordsPerMinuteGet();

      pVoiceMod->pSubVoice = &m_SubVoiceGeneric;
      // since generic dont bother
   }

   // if this is derived then fill in the main one too
   DWORD i;
   if (m_fIsDerived) for (i = 0; i < TTSVOICEMODMAXPROSODY; i++)
      if (!pVoiceMod->apTTSProsody[i]) {
         pVoiceMod->apTTSProsody[i] = m_pCTTSProsody;
         break;
      }

   pVoiceMod->pLex = pVoiceMod->pSubVoice->Lexicon(TRUE, this);

   // make sure sub-voice puitch and WPM are ok
   pVoiceMod->pSubVoice->m_fAvgPitch = max(pVoiceMod->pSubVoice->m_fAvgPitch,1);
   //pVoiceMod->pSubVoice->m_fAvgSyllableDur = max(pVoiceMod->pSubVoice->m_fAvgSyllableDur,0.01);
   pVoiceMod->pSubVoice->m_dwWordsPerMinute = max(pVoiceMod->pSubVoice->m_dwWordsPerMinute,1);
}


/*************************************************************************************
CMTTS::SpeakNodeSubDivideText - Sees if text should be broken off.

inputs
   PWSTR             pszText - Text.
   BOOL              *pfFoundSentenceEnd - Set to TRUE if found a sentence end.
returns
   DWORD - Index into text where make the cut. If 0 then accept the whole text and
      keep on going. If wcslen(pszText) then break at the end of the text.
*/
DWORD CMTTS::SpeakNodeSubDivideText (PWSTR pszText, BOOL *pfFoundSentenceEnd)
{
   // look through the string
   BOOL fFoundNonSpace = FALSE;
   BOOL fIsWSpace;
   DWORD i;
   *pfFoundSentenceEnd = FALSE;
   for (i = 0; pszText[i]; i++) {
      // remember if found non-whitespace
      fIsWSpace = iswspace (pszText[i]);
      fFoundNonSpace |= !fIsWSpace;

      // look for periods, etc.
      switch (pszText[i]) {
         case L'\r':
         case L'\n':
            // if we haven't had non-whitespace text then keep
            // processing
            if (!fFoundNonSpace)
               continue;

            // else, found some characters, so break this as a newline
            break;   // same basic code as punctuatuon

         case L'.':
         case L'!':
         case L'?':
            // will need to skip future whitespace
            break;

         default:
            continue;   // nothing interesting
      } // swtich

      // if get here had some sort of punctuation
      // only care about periods if they occur and the end of a block or have whitespace
      // following them, AND they aren't an abbreviation
      if (pszText[i] == L'.') {
         if (pszText[i+1] && !iswspace(pszText[i+1]))
            continue;   // probably a period between numbers, or something

         // if nothing before this then don't look in lex
         if (!i)
            goto countasbreak;
         int iLook;
         for (iLook = (int)i-1; iLook >= 0; iLook--)
            if (iswspace(pszText[iLook]))
               break;
         iLook++;
         if (iLook >= (int)i)
            goto countasbreak;

         // make the string
         CMem mem;
         for (; iLook <= (int)i; iLook++)
            mem.CharCat (pszText[iLook]);
         mem.CharCat (0);

         // make sure not abbreviation
         PCMLexicon pLex = Lexicon();
         if (pLex && pLex->WordExists ((PWSTR)mem.p))
            continue;   // abbreviation, so don't stop here
      } // if period

countasbreak:
      *pfFoundSentenceEnd = TRUE;

      // get rid of whitespace after this
      i++;
      for (; pszText[i] && iswspace(pszText[i]); i++);
      return i;
   } // i

   // if get here, then accept the entire text
   return 0;
}

/*************************************************************************************
CMTTS::SpeakNodeSubDivide - Subdivide the pNode passed into.

inputs
   PCMMLNode2        pNode - Original node, to clone (or partly clone)
   PCMMLNode2        pAppendTo - Append the results to this, as sub-node. This can be NULL, in which
      case a new node is created and filled into *ppClone.
   PCMMLNode2        *ppClone - Where clone of pNode is written into, in case caller cares.
                     Can be NULL.
   BOOL              *pfFoundContent - Originally pass in FALSE. Will be set to TRUE
                     if any of the searches found content.
returns
   BOOL - TRUE if should keep processing further. FALSE if came to a break in the text
      and have finished one pass
*/
BOOL CMTTS::SpeakNodeSubDivide (PCMMLNode2 pNode, PCMMLNode2 pAppendTo,
                                PCMMLNode2 *ppClone, BOOL *pfFoundContent)
{
   // assuming that if this method is called, the header is neutral
   PCMMLNode2 pClone = pNode->CloneHeader ();
   if (ppClone)
      *ppClone = pClone;
   if (!pClone)
      return FALSE;  // shouldnt happen
   if (pAppendTo)
      pAppendTo->ContentAdd (pClone);  // so it's on the add-to list

   // loop through all the children
   DWORD i;
   PWSTR psz;
   PCMMLNode2 pSub, pSubClone;
   BOOL fRet;
   CMem mem;
   for (i = 0; i < pNode->ContentNum(); i++) {
      if (!pNode->ContentEnum (i, &psz, &pSub))
         return TRUE;   // end of list

      // if have string, see how far get with it
      if (psz) {
         // found something that changes the output
         *pfFoundContent = TRUE;

         BOOL fFoundSentenceEnd;
         DWORD dwDivide = SpeakNodeSubDivideText(psz, &fFoundSentenceEnd);
         DWORD dwLen = (DWORD) wcslen(psz);

         if (!dwDivide || (dwDivide >= dwLen)) {
            // entire text gets moved to copy.
            pClone->ContentAdd (psz);
            pNode->ContentRemove (i);
            i--;

            // see if came to breaking point and want to stop this
            // partial cloning
            if (dwDivide >= dwLen)
               return FALSE;

            // else, not a breaking point (end of sentence) so go further
            continue;
         }
         // else (dwDivide < dwLen)

         // add the first part to the clone
         MemZero (&mem);   // need to make copy to make sure memory not rewritten
         MemCat (&mem, psz);
         psz = (PWSTR)mem.p;
         WCHAR cTemp = psz[dwDivide];
         psz[dwDivide] = 0;
         pClone->ContentAdd (psz);
         psz[dwDivide] = cTemp;

         // remove the first part from this
         pNode->ContentInsert (i+1, psz + dwDivide);  // second half of this string
         pNode->ContentRemove (i);  // remove entire version of this string

         // need to process to here
         return FALSE;
      } // if text

      if (!pSub)
         continue;   // shouldn't happen
      psz = pSub->NameGet();
      if (!psz)
         continue;   // shouldn't happen

      // some nodes are copied entirely
         // BUGFIX - Add bookmark to this
      if (!_wcsicmp(psz, gpszBreak) || !_wcsicmp(psz, gpszBookmark) || !_wcsicmp(psz, gpszTransPros) || !_wcsicmp(psz, gpszTTSWave)) {
         // found something that changes the output
         *pfFoundContent = TRUE;

         pNode->ContentRemove (i, FALSE);
         pClone->ContentAdd (pSub);
         i--;

         continue;   // keep processing after a break
      }
      else if (!_wcsicmp(psz, gpszTransPros) || !_wcsicmp(psz, gpszTTSWave)) {
         // found something that changes the output
         *pfFoundContent = TRUE;

         pNode->ContentRemove (i, FALSE);
         pClone->ContentAdd (pSub);
         i--;

         return FALSE;  // stop processing
      }
      else if (!_wcsicmp(psz, gpszSubVoice)) {
         // this affects everything after this, so clone into copy, but also keep
         // in original

         // if already have stuff to speak, and get a sub-voice message then done
         if (*pfFoundContent)
            return FALSE;

         // see if any other subvoices appear in the list. If they done, then erase them
         DWORD j;
         for (j = 0; j < pClone->ContentNum(); j++) {
            pSubClone = NULL;
            pClone->ContentEnum (j, &psz, &pSubClone);

            if (!pSubClone)
               continue;
            psz = pSubClone->NameGet();
            if (_wcsicmp(psz, gpszSubVoice))
               continue;   // not a sub-voice

            // else, it's a sub-voice, so remove
            pClone->ContentRemove (j);
            j--;
         } // j

         // clone this and add
         pSubClone = pSub->Clone();
         if (pSubClone)
            pClone->ContentAdd (pSubClone);

         continue;
      }
      // else
      // default behavior is to parse inside
      // <emotion>, <emphasis>, <prosody>, <phoneme>, <POS>

      // if there's nothing inside then delete these
      if (!pSub->ContentNum()) {
         pNode->ContentRemove (i);
         i--;
         continue;
      }

      fRet = SpeakNodeSubDivide (pSub, pClone, NULL, pfFoundContent);
      if (!fRet)
         return FALSE;  // found break within sub-tag, so continue that up

      // if get here, have "consumed" this node, so delete it
      pNode->ContentRemove (i);
      i--;
      // continue;
   } // i, over content

   // if get here, should keep processing further
   return TRUE;
}


/*************************************************************************************
CMTTS::SynthGenWaveNode - This is the main "speak" call that most apps will use.

inputs
   DWORD          dwSamplesPerSec - Samples per sec to produce for output
   PCMMLNode2     pNode - MML from TextParse::MMLToPreParse(). This may be modified
                  in place, and is ultimatele DELETED by this function.
   int               iTTSQuality - TTS quality. 0 for fast, 1 for normal, 2 for slow, 3 for best, 4 for multipass
   BOOL              fDisablePCM - If TRUE, then PCM is disabled for speakin.
   PCProgressSocket pProgress - To show percent done
   PTTSVOICEMOD   pVoiceMod - Parameters controlling how the voice is modified by
                  the original. Generally, pass in NULL. Only internal functions
                  will pass in non-null.
   PCProgressWaveTTS pProgressWave - This is the callback to paste the audio into.
returns
   BOOL - TRUE if success
*/
BOOL CMTTS::SynthGenWaveNode (DWORD dwSamplesPerSec, PCMMLNode2 pNode, int iTTSQuality, BOOL fDisablePCM,
                          PCProgressSocket pProgress, PTTSVOICEMOD pVoiceMod,
                          PCProgressWaveTTS pProgressWave)
{
   CListFixed lNodes;
   lNodes.Init (sizeof(PCMMLNode2));

   // pull out bits at a time and process those
   PCMMLNode2 pClone;
   BOOL fFoundContent, fRet;
   while (TRUE) {
      // start out with someplace to put copy
      fFoundContent = FALSE;
      pClone = NULL;
      fRet = SpeakNodeSubDivide (pNode, NULL, &pClone, &fFoundContent);

      // if found content, add clone
      if (fFoundContent && pClone)
         lNodes.Add (&pClone);
      else {
         // delete the clone since didn't amount to anything
         if (pClone)
            delete pClone;

         // since nothing found, stop
         break;
      }
   } // while (TRUE)

   // done with the original node
   delete pNode;

   // loop through all the chunks and synthesize them
   DWORD i, j;
   PCMMLNode2 *ppNode = (PCMMLNode2*)lNodes.Get(0);
   fRet = TRUE;
   for (i = 0; i < lNodes.Num(); i++, ppNode++) {
      if (pProgress)
         pProgress->Push ((fp)i / (fp)lNodes.Num(), (fp)(i+1) / (fp)lNodes.Num());

#ifdef _DEBUG
      CMem mem;
      mem.m_dwCurPosn = 0;
      if (MMLToMem (*ppNode, &mem)) {
         mem.CharCat (0);
         OutputDebugStringW (L"\r\nSub-string: ");
         OutputDebugStringW ((PWSTR)mem.p);
      }
#endif

      pNode = *ppNode;

      // For SAPI: Since pNode is determined to be a sentence, search through
      // all the bookmarks looking for "word:X-Y" to find the range of bookmarks
      // for words, so can know the range for the sentence.
      int iSentStart = 1000000;
      int iSentEnd = -1;
      PCMMLNode2 pSub;
      for (j = 0; j < pNode->ContentNum(); j++) {
         PWSTR psz;
         pSub = NULL;
         pNode->ContentEnum (j, &psz, &pSub);
         if (!pSub)
            continue;

         psz = pSub->NameGet ();
         if (!psz || _wcsicmp(psz, gpszBookmark))
            continue;   // not a bookmark

         // get the "mark" attribute
         psz = pSub->AttribGetString (L"mark");
         if (!psz || _wcsnicmp(psz, L"word:", 5))
            continue;   // not right
         psz += 5;   // to skip "word:";
         PWSTR pszDash = wcschr (psz, L'-');
         if (!pszDash)
            continue;   // no match
         pszDash++;

         int iWordStart = _wtoi(psz);
         int iWordEnd = _wtoi(pszDash);
         iSentStart = min(iSentStart, iWordStart);
         iSentEnd = max(iSentEnd, iWordEnd);
      } // j

      // prepend the bookmark
      if (iSentEnd > iSentStart) {
         pSub = new CMMLNode2;
         if (pSub) {
            pSub->NameSet (gpszBookmark);
            WCHAR szTemp[128];
            swprintf (szTemp, L"sentence:%d-%d", (int)iSentStart, (int)iSentEnd);
            pSub->AttribSetString (L"mark", szTemp);
            pNode->ContentInsert (0, pSub);
         }
      }



      fRet = SynthGenWaveWithProgress (dwSamplesPerSec, pNode, iTTSQuality, fDisablePCM,
         pProgress, pVoiceMod, pProgressWave);
      *ppNode = NULL;

      if (pProgress)
         pProgress->Pop();

      if (!fRet)
         break;
   } // i, lNodes.Num()

   // delete all the nodes
   ppNode = (PCMMLNode2*)lNodes.Get(0);
   for (i = 0; i < lNodes.Num(); i++)
      if (ppNode[i])
         delete ppNode[i];

   return fRet;
}

/*************************************************************************************
CMTTS::SynthGenWaveWithProgress - This is the main "speak" call that most apps will use.

inputs
   DWORD          dwSamplesPerSec - Sampling rate
   PCMMLNode2     pNode - MML from TextParse::MMLToPreParse(). This may be modified
                  in place, and is ultimatele DELETED by this function.
   int               iTTSQuality - TTS quality. 0 for fast, 1 for normal, 2 for slow, 3 for best, 4 for multipass
   BOOL              fDisablePCM - If TRUE, then PCM is disabled for speakin.
   PCProgressSocket pProgress - To show percent done
   PTTSVOICEMOD   pVoiceMod - Parameters controlling how the voice is modified by
                  the original. Generally, pass in NULL. Only internal functions
                  will pass in non-null.
   PCProgressWaveTTS pProgressWave - This is the callback to paste the audio into.
returns
   BOOL - TRUE if success
*/
BOOL CMTTS::SynthGenWaveWithProgress (DWORD dwSamplesPerSec, PCMMLNode2 pNode, int iTTSQuality, BOOL fDisablePCM,
                          PCProgressSocket pProgress, PTTSVOICEMOD pVoiceMod,
                          PCProgressWaveTTS pProgressWave)
{
#if 0 // to test def _DEBUG
         int tmp;

         // Get the current bits
         tmp = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);

         // Clear the upper 16 bits and OR in the desired freqency
         //tmp = (tmp & 0x0000FFFF) | _CRTDBG_CHECK_EVERY_16_DF;

         // Set the new bits
         _CrtSetDbgFlag(tmp | _CRTDBG_CHECK_ALWAYS_DF);
         _CrtCheckMemory ();
#endif

   TTSVOICEMOD VM;
   BOOL fRet = FALSE;

   // if this is a derived voice pass on down
   if (m_fIsDerived) {
      if (!m_pTTSMaster) {
         delete pNode;
         return FALSE;
      }

      // potentially fill in voice mod with a mix
      PCMMLNode2 pSubVoiceNode = pVoiceMod ? NULL : FindSubVoice (pNode);
      BOOL fMustDelete = FALSE;
      PCTTSProsody apTTSProsody[TTSVOICEMODMAXPROSODY];
      memset (apTTSProsody, 0, sizeof(apTTSProsody));
      PCMTTSSubVoice pSubVoice = pSubVoiceNode ? MakeSubVoice (pSubVoiceNode, &fMustDelete, apTTSProsody) : NULL;
      if (pSubVoice) {
         // created a sub-voce, and only did that if no voice mod already
         pVoiceMod = &VM;
         FillInVOICEMOD (pVoiceMod, pSubVoice, apTTSProsody);
      }

      fRet = m_pTTSMaster->SynthGenWaveWithProgress (dwSamplesPerSec, pNode, iTTSQuality, fDisablePCM, pProgress, pVoiceMod,
         pProgressWave);

      if (fMustDelete)
         delete pSubVoice;

      return fRet;
   }


   // BUGFIX - For SAPI, see if want to change the speed and volume
   fp fTempSpeed = 1.0;
   fp fTempVolume = 1.0;
   if (pProgressWave) {
      if (!pProgressWave->TTSSpeedVolume (&fTempSpeed, &fTempVolume))
         return FALSE;
   }

#ifdef _DEBUG
   DWORD dwTimeCur, dwTimeStart = GetTickCount();
   WCHAR szTemp[128];
#endif

   if (!pVoiceMod) {
      pVoiceMod = &VM;
      FillInVOICEMOD (pVoiceMod, NULL, NULL);
   }

   DWORD dwCandidates = 1;
   if (iTTSQuality >= 4)
      dwCandidates = MAXSYNGENFEATURESCANDIDATES;
   // BUGFIX - Tried doing multipass on lower-quality levels but made them
   // extrememly slow, and didn't really seem to do much
   //else if (iTTSQuality == 3)
   //   dwCandidates = min(4, MAXSYNGENFEATURESCANDIDATES);
   //else if (iTTSQuality == 2)
   //   dwCandidates = min(2, MAXSYNGENFEATURESCANDIDATES);

   DWORD dwCand;
   DWORD i;
   dwCandidates = min(dwCandidates, MAXSYNGENFEATURESCANDIDATES);
   PCMMLNode2 apNode[MAXSYNGENFEATURESCANDIDATES];
   SYNGENFEATURESCANDIDATE aCandidates[MAXSYNGENFEATURESCANDIDATES];
   memset (apNode, 0, sizeof(apNode));
   memset (aCandidates, 0, sizeof(aCandidates));
   apNode[0] = pNode;

   TTSGLOBALSTATE aState[MAXSYNGENFEATURESCANDIDATES];
   CListFixed alPhone[MAXSYNGENFEATURESCANDIDATES], alWord[MAXSYNGENFEATURESCANDIDATES];
   CListFixed alMMLWord[MAXSYNGENFEATURESCANDIDATES], alTTSGLOBALSTATE[MAXSYNGENFEATURESCANDIDATES], alPhoneMod[MAXSYNGENFEATURESCANDIDATES];
   CListFixed alBookmarkIndex[MAXSYNGENFEATURESCANDIDATES];
   CListVariable alString[MAXSYNGENFEATURESCANDIDATES], alBookmarkString[MAXSYNGENFEATURESCANDIDATES];
   CListFixed alDur[MAXSYNGENFEATURESCANDIDATES], alVol[MAXSYNGENFEATURESCANDIDATES];
   CListFixed alAccent[MAXSYNGENFEATURESCANDIDATES];
   CListVariable alPitchF0Extra[MAXSYNGENFEATURESCANDIDATES];
   CListVariable alPitchF0Fuji[MAXSYNGENFEATURESCANDIDATES];
   BOOL afAbsPitch[MAXSYNGENFEATURESCANDIDATES];

   // need to fill in information for all the candidates, by filling in the first one
   // and cloning to make entries for the candidates
   apNode[0] = SynthMMLToWordMML (apNode[0], pVoiceMod->pLex);
   if (!apNode[0])
      goto done;

   #ifdef _DEBUG
      dwTimeCur = GetTickCount();
      swprintf (szTemp, L"\r\nSynthMMLToWordMML = %d ms", (int)(dwTimeCur - dwTimeStart));
      dwTimeStart = dwTimeCur;
      OutputDebugStringW (szTemp);
   #endif

   // convert some tags
   memset (&aState[0], 0 ,sizeof(aState[0]));
   aState[0].fProsodyAvgPitch = pVoiceMod->pSubVoice->m_fAvgPitch;
   //aState[0].fProsodyAvgSyllableDur = pVoiceMod->pSubVoice->m_fAvgSyllableDur;
   aState[0].fProsodyPitchExpress = 1;
   aState[0].fProsodyVol = 1;
   aState[0].fProsodyWPM = pVoiceMod->pSubVoice->m_dwWordsPerMinute;
   // leave emotions at 0
   // will need to fill in derviced values
   GlobalStateFromMML (NULL, pVoiceMod->pSubVoice, &aState[0]);

   BOOL fTransPros = FALSE;

   if (!SynthTextInterpretTags (apNode[0], &aState[0], pVoiceMod, &fTransPros))
      goto done;

#ifdef _DEBUG
   dwTimeCur = GetTickCount();
   swprintf (szTemp, L"\r\nSynthTextInterpretTags = %d ms", (int)(dwTimeCur - dwTimeStart));
   dwTimeStart = dwTimeCur;
   OutputDebugStringW (szTemp);
#endif
   
   // make copies of the other candidates
   // copy to candidates
   for (dwCand = 1; dwCand < dwCandidates; dwCand++) {
      apNode[dwCand] = apNode[0]->Clone();
      if (!apNode[dwCand])
         goto done;

      aState[dwCand] = aState[0];
   }


   // loop over all nodes
   DWORD dwNodeStart, dwNodeEnd;
   for (dwNodeStart = 0; TRUE /*dwNodeStart < apNode[0]->ContentNum()*/; dwNodeStart = dwNodeEnd) {


      for (dwCand = 0; dwCand < dwCandidates; dwCand++) {

         // clear out the lists
         alPhone[dwCand].Clear();
         alWord[dwCand].Clear();
         alMMLWord[dwCand].Clear();
         alTTSGLOBALSTATE[dwCand].Clear();
         alPhoneMod[dwCand].Clear();
         alString[dwCand].Clear();
         alDur[dwCand].Clear();
         alVol[dwCand].Clear();
         alAccent[dwCand].Clear();
         alPitchF0Extra[dwCand].Clear();
         alPitchF0Fuji[dwCand].Clear();
         alBookmarkString[dwCand].Clear();
         afAbsPitch[dwCand] = FALSE;

         // get phonemes
         DWORD dwNodeEndNew;
         if (!SynthWordMMLToPhones (apNode[dwCand], dwNodeStart, &alPhone[dwCand], &alWord[dwCand], &alMMLWord[dwCand],
            &alBookmarkIndex[dwCand], &alBookmarkString[dwCand], &alPhoneMod[dwCand],
            &alString[dwCand], &aState[dwCand], &alTTSGLOBALSTATE[dwCand], pVoiceMod, iTTSQuality, fTransPros, dwCand,
            dwCand ? &dwNodeEndNew : &dwNodeEnd, fTempVolume, fTempSpeed)) {
            // error

            // if this is in candidate 0, then it's because there's nothing more to produce anywhere, so
            // just break
            if (!dwCand)
               break;

            // else, something bad happened
            goto done;
         }
         if (dwCand && (dwNodeEndNew != dwNodeEnd))
            goto done;

      #ifdef _DEBUG
         dwTimeCur = GetTickCount();
         swprintf (szTemp, L"\r\nSynthWordMMLToPhones = %d ms", (int)(dwTimeCur - dwTimeStart));
         dwTimeStart = dwTimeCur;
         OutputDebugStringW (szTemp);
      #endif

         // synthesize prosody
         if (!SynthArtificialProsody ((DWORD*)alPhone[dwCand].Get(0), (PWSTR*)alWord[dwCand].Get(0),
            (PCMMLNode2*)alMMLWord[dwCand].Get(0), (PCPoint)alPhoneMod[dwCand].Get(0), (PTTSGLOBALSTATE) alTTSGLOBALSTATE[dwCand].Get(0),
            alPhone[dwCand].Num(), &alDur[dwCand],
            &alPitchF0Extra[dwCand], &alPitchF0Fuji[dwCand],
            &alVol[dwCand], &alAccent[dwCand], pVoiceMod, fDisablePCM, &afAbsPitch[dwCand]))

               goto done;

      #ifdef _DEBUG
         dwTimeCur = GetTickCount();
         swprintf (szTemp, L"\r\nSynthArtificialProsody = %d ms", (int)(dwTimeCur - dwTimeStart));
         dwTimeStart = dwTimeCur;
         OutputDebugStringW (szTemp);
      #endif

      #if 0 // to test
      #ifdef _DEBUG
         FILE *file = fopen("c:\\pros.txt", "wt");
      #else
         FILE *file = fopen("c:\\prosr.txt", "wt");
      #endif
         DWORD i, j;
         for (i = 0; i < alPhone[dwCand].Num(); i++) {
            fprintf (file, "\r\nPhone %d : %x, %d, %.3g; ",
               (DWORD) i, *((DWORD*)alPhone[dwCand].Get(i)), *((DWORD*)alDur[dwCand].Get(i)), (double) *((fp*)lVol.Get(i)));

            fp *paf = (fp*)lPitch.Get(i);
            DWORD dwNum = lPitch.Size(i) / sizeof(DWORD);
            for (j = 0; j < dwNum; j++)
               fprintf (file, "%.3g ", (double)paf[j]);
         } // i
         fclose (file);
      #endif // 0

         // remember values
         aCandidates[dwCand].dwNum = alPhone[dwCand].Num();
         aCandidates[dwCand].fAbsPitch = afAbsPitch[dwCand];
         aCandidates[dwCand].fVolAbsolute = FALSE;
         aCandidates[dwCand].padwDur = (DWORD*)alDur[dwCand].Get(0);
         aCandidates[dwCand].padwPhone = (DWORD*)alPhone[dwCand].Get(0);
         aCandidates[dwCand].pafVol = (fp*)alVol[dwCand].Get(0);
         aCandidates[dwCand].pafAccent = (fp*)alAccent[dwCand].Get(0);
         aCandidates[dwCand].papszWord = (PWSTR*)alWord[dwCand].Get(0);
         aCandidates[dwCand].paTTSGS = (PTTSGLOBALSTATE) alTTSGLOBALSTATE[dwCand].Get(0);
         aCandidates[dwCand].plPitchF0Extra = &alPitchF0Extra[dwCand];
         aCandidates[dwCand].plPitchF0Fuji = &alPitchF0Fuji[dwCand];
         aCandidates[dwCand].plBookmarkIndex = &alBookmarkIndex[dwCand];
         aCandidates[dwCand].plBookmarkString = &alBookmarkString[dwCand];
         // intentionally not setting aCandidates[dwCand].padwWord
      } // dwCand

      // if got to the end of the data then stop
      if (dwCand < dwCandidates)
         break;

      // generate the wave
      if (pProgress)
         pProgress->Push ((fp)dwNodeStart / (fp)apNode[0]->ContentNum(),
            (fp)dwNodeEnd / (fp)apNode[0]->ContentNum());

      if (!SynthGenWaveInt (dwCandidates, aCandidates, dwSamplesPerSec, iTTSQuality, fDisablePCM, pProgress, pVoiceMod, pProgressWave)) {
         if (pProgress)
            pProgress->Pop();
         goto done;
      }
      if (pProgress)
         pProgress->Pop();
   } // dwNodeStart

   // if got here then good
   fRet = TRUE;

done:
   // delete all the nodes
   for (i = 0; i < dwCandidates; i++)
      if (apNode[i]) delete apNode[i];
   return fRet;

}





/*************************************************************************************
CMTTS::SynthGenWave - This is the main "speak" call that most apps will use.

inputs
   PCM3DWave      pWave - Wave to generate to. This can be NULL, but then pProgressWave must be set.
   DWORD          dwSamplesPerSec - Sampling rate.
   PWSTR          pszText - Text to speak.
   BOOL           fTagged - If it's tagged then it's in MML format, else it's just
                     raw text.
   int               iTTSQuality - TTS quality. 0 for fast, 1 for normal, 2 for slow, 3 for best, 4 for multipass
   BOOL              fDisablePCM - If TRUE, then PCM is disabled for speakin.
   PCProgressSocket pProgress - To show percent done
   PTTSVOICEMOD   pVoiceMod - Parameters controlling how the voice is modified by
                  the original. Generally, pass in NULL. Only internal functions
                  will pass in non-null.
   PCProgressWaveTTS pProgressWave - Wave callpack. This must be NULL if pWave is not NULL.
returns
   BOOL - TRUE if success
*/
BOOL CMTTS::SynthGenWave (PCM3DWave pWave, DWORD dwSamplesPerSec, PWSTR pszText, BOOL fTagged, int iTTSQuality, BOOL fDisablePCM,
                          PCProgressSocket pProgress, PTTSVOICEMOD pVoiceMod,
                          PCProgressWaveTTS pProgressWave)
{
#ifdef TURNOFFRANDOM
   srand (0); // hack to always get the same results
   WordRankHistory(NULL);  // to clear for test
#endif // _DEBUG

#ifdef _DEBUG
   DWORD dwTimeStart = GetTickCount();
#endif

   if (!pProgressWave && !pWave)
      return FALSE;
   if (pWave && (pWave->m_dwSamplesPerSec != dwSamplesPerSec))
      return FALSE;

   // deal with NULL pProgressWave
   PCProgressWaveTTSToWave pToWave = NULL;
   if (pWave) {
      pToWave = new CProgressWaveTTSToWave;
      if (!pToWave)
         return FALSE;
      pToWave->m_pWave = pWave;
      pWave->BlankWaveToSize (0, TRUE);   // make sure empty
   }

   // convert the text to MML
   // convert
   CTextParse TextParse;
   PCMLexicon pLex = Lexicon();
   if (!TextParse.Init (pLex->LangIDGet(), pLex)) {
      if (pToWave)
         delete pToWave;
      return FALSE;
   }

   PCMMLNode2 pNode = TextParse.MMLToPreParse (pszText, fTagged);
   if (!pNode) {
      if (pToWave)
         delete pToWave;
      return FALSE;
   }

   // speak
   BOOL fRet = SynthGenWaveNode (dwSamplesPerSec, pNode, iTTSQuality, fDisablePCM, pProgress, pVoiceMod,
      pProgressWave ? pProgressWave : pToWave);

   if (pToWave)
      delete pToWave;

#ifdef _DEBUG
   DWORD dwTimeCur = GetTickCount();
   WCHAR szTemp[64];
   swprintf (szTemp, L"\r\nSynthGenWave = %d ms", (int)(dwTimeCur - dwTimeStart));
   dwTimeStart = dwTimeCur;
   OutputDebugStringW (szTemp);
#endif

   // delete text
   // dont delete since already deleted delete pNode;
   return fRet;
}

/****************************************************************************
FindAdjacentWord - Looks through the PCMMLNode2 and looks for a word (or punct) to
the left/right of the current word. If another tag is there it keeps moving
until it finds a match of the end of the data.

inputs
   PCTextParse       pParse - Text parse to use to identify word/punctuation
   PCMMLNode2         pNode; - Node to look through
   DWORD       dwNodeStart - Node where sentence starts. Before that is assumed to be out of sentence
   DWORD       dwNodeEnd - Node where sentnece ends (exclusive). At and after is not included
   BOOL              fRight - If TRUE looking to the right, else left
   BOOL              fPunct - If TRUE accept words of punctuation. If FALSE only words
   DWORD             *pdwCurLoc - This should be filled with the current location (element
                        in pNode). It will be incremented by the function
   PWSTR             *ppszText - If an element is found, this points to the text attribute of it
   BOOL              *pfIsPunct - If an element is found, filled with true if it's punctuation,
                        FALSE if its a word
   BYTE              *pbPOS - Filled in with the part of speech
returns
   BOOL - TRUE if found element, FALSE if went beyond edge
*/
static BOOL FindAdjacentWord (PCTextParse pParse, PCMMLNode2 pNode, DWORD dwNodeStart, DWORD dwNodeEnd, BOOL fRight,
                              BOOL fPunct, DWORD *pdwCurLoc, PWSTR *ppszText, BOOL *pfIsPunct,
                              BYTE *pbPOS = NULL)
{
   for (;; pdwCurLoc[0]++) {
      int iNext = (int)(*pdwCurLoc) + (fRight ? 1 : -1);
      if (iNext < (int)dwNodeStart)
         return FALSE;
      if (iNext >= (int)dwNodeEnd /*pNode->ContentNum()*/)
         return FALSE;

      // get the element
      PWSTR psz;
      PCMMLNode2 pSub;
      pSub = NULL;
      pNode->ContentEnum ((DWORD)iNext, &psz, &pSub);
      if (!pSub)
         continue;

      // get name
      psz = pSub->NameGet();
      if (!psz)
         continue;

      if (!_wcsicmp(psz, pParse->Word())) {
         psz = pSub->AttribGetString(pParse->Text());
         if (!psz)
            continue;   // word, but dont know what it is so ignore

         *ppszText = psz;
         *pfIsPunct = FALSE;

         // pos
         if (pbPOS) {
            psz = pSub->AttribGetString(pParse->POS());
            if (psz)
               pbPOS[0] = (BYTE)_wtoi(psz);
            else
               pbPOS[0] = 0;
         }
         pdwCurLoc[0] = (DWORD)iNext;   // BUGFIX - Increase pointer to next slot
         return TRUE;
      }
      else if (fPunct && !_wcsicmp(psz, pParse->Punctuation())) {
         psz = pSub->AttribGetString(pParse->Text());
         if (!psz)
            continue;   // word, but dont know what it is so ignore

         *ppszText = psz;
         *pfIsPunct = TRUE;
         if (pbPOS)
            *pbPOS = 0; // since punct
         pdwCurLoc[0] = (DWORD)iNext;   // BUGFIX - Increase pointer to next slot
         return TRUE;
      }
   }
}


#if 0 // old prosody

/*************************************************************************************
CMTTS::NGramFind - Given a pointer to an array of bytes (indicating the POS numbers
for the Ngram), this finds a pointer to the ngram. If it doesn't find one with data
it returns NULL.

inputs
   PBYTE       pabPOS - Pointer to an array of (TTSPROSNGRAMBIT+TTSPROSNGRAM)*2+1 POS. TTSPROSNGRAM+TTSPROSNGRAMBIT
                     is the center word
returns
   PTTSNGRAM - Ngram, or NULL if the Ngram doesn't have any relevent info
*/
PTTSNGRAM CMTTS::NGramFind (PBYTE pabPOS)
{
   // if no data then error
   if (!m_memNGram.m_dwCurPosn)
      return NULL;

   // what's the number
   DWORD dwBin = 0;
   DWORD i;
   for (i = 0; i < (TTSPROSNGRAM+TTSPROSNGRAMBIT)*2+1; i++) {
      if (!i || (i == (TTSPROSNGRAMBIT+TTSPROSNGRAM)*2)) {
         // trigram at very start or end, only care about unknown, word, or punctuation
         dwBin *= 3;
         if (!pabPOS[i])
            dwBin += 0;
         else if (pabPOS[i] == POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION))
            dwBin += 2;
         else
            dwBin += 1;
      }
      else {
         // main trigram
         dwBin *= (POS_MAJOR_NUM+1);
         dwBin += pabPOS[i];
      }
   }

   // get
   PTTSNGRAM png = (PTTSNGRAM)m_memNGram.p;
   png += dwBin;

   return png->bPitch ? png : NULL;
}

/*************************************************************************************
CMTTS::NGramFindBackoff - Given a pointer to an array of bytes (indicating the POS numbers
for the Ngram), this finds a pointer to the ngram. If it doesn't find one with data
it returns NULL.

This handles the backoff case.

inputs
   PBYTE       pabPOS - Pointer to an array of (TTSPROSNGRAMBIT+TTSPROSNGRAM)*2+1 POS.
                     TTSPROSNGRAMBIT+TTSPROSNGRAM
                     is the center word. NOTE: This data is modified in place as
                     it backoff.
   PCPoint     pPros - Filled with the prosody information for pitch, duration, and volume.
   BOOL        *pfPauseLeft - Filled with TRUE if should pause to the left because of the
                     N-gram.
*/
void CMTTS::NGramFindBackoff (PBYTE pabPOS, PCPoint pPros, BOOL *pfPauseLeft)
{
   // blank out just in case
   *pfPauseLeft = FALSE;
   pPros->p[0] = pPros->p[1] = pPros->p[2] = 1;

   PTTSNGRAM p1, p2;
   BYTE bTemp;
   p1 = p2 = NULL;

   DWORD i;
   for (i = TTSPROSNGRAM+TTSPROSNGRAMBIT; i <= TTSPROSNGRAM+TTSPROSNGRAMBIT; i--) {
      // find an exact match
      p1 = NGramFind (pabPOS);
      p2 = NULL;
      if (p1)
         break;

      // if on last straw and cant find one then just exit because nothing left
      if (i == 0)
         return;

      // try cutting out from the left or right
      bTemp = pabPOS[TTSPROSNGRAM+TTSPROSNGRAMBIT - i];
      pabPOS[TTSPROSNGRAM+TTSPROSNGRAMBIT - i] = 0; // to indicate backoff
      p1 = NGramFind (pabPOS);

      pabPOS[TTSPROSNGRAM+TTSPROSNGRAMBIT - i] = bTemp; // restore
      bTemp = pabPOS[TTSPROSNGRAM+TTSPROSNGRAMBIT + i];
      pabPOS[TTSPROSNGRAM+TTSPROSNGRAMBIT + i] = 0; // to indicate backoff
      p2 = NGramFind (pabPOS);
#ifdef _DEBUG
      pabPOS[TTSPROSNGRAM+TTSPROSNGRAMBIT + i] = bTemp; // restore
#endif
      if (p1 || p2)
         break;

      // wipe out since failed
      pabPOS[TTSPROSNGRAM+TTSPROSNGRAMBIT - i] = pabPOS[TTSPROSNGRAM+TTSPROSNGRAMBIT + i] = 0;
   }
   if (!p1 && !p2)
      return;

   // get the two weights
   CPoint w1, w2;
#ifdef _DEBUG
   char szTemp[256];
   WCHAR szwTemp[256];
   PWSTR paszPOS[POS_MAJOR_NUM+1] = {L"UNK", L"noun", L"pron", L"adj", L"prep", L"art",
      L"verb", L"adv", L"aux v", L"conj", L"inter", L"PUNCT"};

   szwTemp[0] = 0;
   for (i = 0; i < (TTSPROSNGRAM+TTSPROSNGRAMBIT)*2+1; i++) {
      if (i)
         wcscat (szwTemp, L" ");
      wcscat (szwTemp, paszPOS[pabPOS[i]]);
   }
   wcscat (szwTemp, L"\r\n");
   WideCharToMultiByte (CP_ACP, 0, szwTemp, -1, szTemp, sizeof(szTemp), 0, 0);
   OutputDebugString (szTemp);
#endif
   if (p1) {
      w1.p[0] = (fp)p1->bPitch / 100.0;
      w1.p[1] = (fp)p1->bVol / 100.0;
      w1.p[2] = (fp)p1->bDur / 100.0;

      if (p1->bFlags & 0x01)
         *pfPauseLeft = TRUE;
   }
   if (p2) {
      w2.p[0] = (fp)p2->bPitch / 100.0;
      w2.p[1] = (fp)p2->bVol / 100.0;
      w2.p[2] = (fp)p2->bDur / 100.0;

      if (p2->bFlags & 0x01)
         *pfPauseLeft = TRUE;
   }

   if (p1 && p2) {
      pPros->p[0] = sqrt(w1.p[0]) * sqrt(w2.p[0]);
      pPros->p[1] = sqrt(w1.p[1]) * sqrt(w2.p[1]);
      pPros->p[2] = sqrt(w1.p[2]) * sqrt(w2.p[2]);
#ifdef _DEBUG
   OutputDebugString ("\tCombi\r\n");
#endif
   }
   else if (p1) {
      pPros->Copy (&w1);

#ifdef _DEBUG
   OutputDebugString ("\tLeft or all\r\n");
#endif
   }
   else if (p2) {
      pPros->Copy (&w2);
#ifdef _DEBUG
   OutputDebugString ("\tRight\r\n");
#endif
   }

}


/*************************************************************************************
CMTTS::NGramFindBackoff - Finds the NGram information for the given word

This handles the backoff case.

inputs
   PCTextParse pParse - Text parser
   PCMMLNode2   pNode - Root node for the sentence being spoken
   DWORD       dwIndex - Index into pNode that's the current word
   PCPoint     pPros - Filled with the prosody information for pitch, duration, and volume.
   BOOL        *pfPauseLeft - Filled with TRUE if should pause to the left because of the
                     N-gram.
   BYTE        *pabPOS - If no NULL, (TTSPROSNGRAMBIT+TTSPROSNGRAM)*2+1 are copied to pabPOS with
                     the parts of speech for the surrounding words.
*/
void CMTTS::NGramFindBackoff (PCTextParse pParse, PCMMLNode2 pNode,
                              DWORD dwIndex, PCPoint pPros, BOOL *pfPauseLeft,
                              BYTE *pabPOS)
{
   // zero out info in case
   pPros->p[0] = pPros->p[1] = pPros->p[2] = 1;
   *pfPauseLeft = FALSE;

   BYTE abPOS[(TTSPROSNGRAM+TTSPROSNGRAMBIT)*2+1];
   if (!FindSurroundingPOS (pParse, pNode, dwIndex, abPOS))
      return;

   if (pabPOS)
      memcpy (pabPOS, abPOS, sizeof(abPOS));

   // determine the Ngram score
   NGramFindBackoff (abPOS, pPros, pfPauseLeft);
}
#endif // 0, old prosody


/*************************************************************************************
CMTTS::FindSurroundingPOS - Finds the surrounding POS.

This handles the backoff case.

inputs
   PCTextParse pParse - Text parser
   PCMMLNode2   pNode - Root node for the sentence being spoken
   DWORD       dwNodeStart - Node where sentence starts. Before that is assumed to be out of sentence
   DWORD       dwNodeEnd - Node where sentnece ends (exclusive). At and after is not included
   DWORD       dwIndex - Index into pNode that's the current word
   BYTE        *pabPOS - (TTSPROSNGRAMBIT+TTSPROSNGRAM)*2+1 are copied to pabPOS with
                     the parts of speech for the surrounding words.
returns
   BOOL - TRUE if success
*/
BOOL CMTTS::FindSurroundingPOS (PCTextParse pParse, PCMMLNode2 pNode, DWORD dwNodeStart, DWORD dwNodeEnd,
                              DWORD dwIndex, BYTE *pabPOS)
{
   PCMMLNode2 pSub = NULL;
   PWSTR psz;
   pNode->ContentEnum (dwIndex, &psz, &pSub);
   if (!pSub)
      return FALSE;

   // determine the POS for surrounding words..
   BYTE abPOS[(TTSPROSNGRAM+TTSPROSNGRAMBIT)*2+1];
   DWORD j;
   for (j = 0; j < (TTSPROSNGRAM+TTSPROSNGRAMBIT)*2+1; j++)
      abPOS[j] = POS_MAJOR_NUM;  // assume it's punctuation

   // center POS
   psz = pSub->AttribGetString(pParse->POS());
   abPOS[TTSPROSNGRAM+TTSPROSNGRAMBIT] = (BYTE)POS_MAJOR_EXTRACT(psz ? _wtoi(psz) : POS_MAJOR_NOUN);

   DWORD dwLeft;
   BOOL fIsPunct;
   BYTE bPOS;
   PWSTR pszText;
   for (dwLeft = 0; dwLeft < 2; dwLeft++) {
      DWORD dwCur = dwIndex;
      for (j = 0; j < TTSPROSNGRAM+TTSPROSNGRAMBIT; j++) {
         if (!FindAdjacentWord (pParse, pNode, dwNodeStart, dwNodeEnd, !dwLeft, TRUE, &dwCur, &pszText, &fIsPunct, &bPOS))
            break;   // no more

         // if it's punctuation then stop, since already have elements filled in assuming
         // theyre punctuation
         if (fIsPunct)
            break;

         // index
         DWORD dwIndex2;
         if (dwLeft)
            dwIndex2 = TTSPROSNGRAM+TTSPROSNGRAMBIT - j - 1;
         else
            dwIndex2 = TTSPROSNGRAM+TTSPROSNGRAMBIT + j + 1;
         abPOS[dwIndex2] = POS_MAJOR_EXTRACT(bPOS);
      } // j
   } // dwLeft

   // make sure within limits
   for (j = 0; j < (TTSPROSNGRAM+TTSPROSNGRAMBIT)*2+1; j++) {
      abPOS[j] = min(abPOS[j], abPOS[POS_MAJOR_NUM]); // so not too high
      if (!abPOS[j])
         abPOS[j] = POS_MAJOR_EXTRACT(POS_MAJOR_NOUN);  // assume noun
   }

   memcpy (pabPOS, abPOS, sizeof(abPOS));

   return TRUE;
}



/*************************************************************************************
CMTTS::FillInCOMPARESYLINFO - Fills in COMPARESYLINFO with appropriate values

inputs
   PCOMPARESYLINFO      pInfo - Filled in
returns
   none
*/
void CMTTS::FillInCOMPARESYLINFO (PCOMPARESYLINFO pInfo)
{
   memset (pInfo, 0, sizeof(*pInfo));

   PCMLexicon pLex = Lexicon();

   // determine phoneme for each group
   fp fWeight, fWeightSum;
   DWORD i, j;
   //DWORD adwGroupPhone[PIS_PHONEGROUPNUM];
   //for (i = 0; i < PIS_PHONEGROUPNUM; i++)
   //   adwGroupPhone[i] = pLex->PhonemeGroupToPhone (i, TRUE, NULL); 

   // just average for all phone groups


   // duration
   fWeightSum = 0.0;
   for (i = 0; i < pLex->PhonemeNum(); i++)
      for (j = 0; j < 2; j++) {
         fWeight = 1.0;
         fWeightSum += fWeight;
         pInfo->fDuration += UnitScoreDuration (this, i, pLex, j, FALSE, -1.0) * fWeight;
      }
   pInfo->fDuration /= fWeightSum;
   pInfo->fDurationSkew = pInfo->fDuration;

   // BUGFIX - Want to distribute duration weight among duration tests
   pInfo->fDuration *= 0.666;
   pInfo->fDurationSkew *= 0.333;

   // energy
   fWeightSum = 0.0;
   for (i = 0; i < pLex->PhonemeNum(); i++)
      for (j = 0; j < 2; j++) {
         fWeight = 1.0;
         fWeightSum += fWeight;
         pInfo->fEnergy += UnitScoreEnergy (this, i, pLex, j) * fWeight;
         pInfo->fAccent += UnitScoreAccent (this, i, pLex, j) * fWeight;
      }
   pInfo->fEnergy /= fWeightSum;
   pInfo->fAccent /= fWeightSum;

   // pitch
   fWeightSum = 0.0;
   for (i = 0; i < pLex->PhonemeNum(); i++)
      for (j = 0; j < 2; j++) {
         fWeight = 1.0;
         fWeightSum += fWeight;
         pInfo->fPitchF0Extra += UnitScorePitch (this, i, pLex, j, FALSE, -1.0) * fWeight;
         pInfo->fPitchF0Fuji += UnitScorePitch (this, i, pLex, j, FALSE, -1.0) * fWeight;
      }
   pInfo->fPitchF0Extra /= fWeightSum;
   pInfo->fPitchF0Fuji /= fWeightSum;

   // function word
   fWeightSum = 0.0;
   for (i = 0; i < pLex->PhonemeNum(); i++)
      for (j = 0; j < 2; j++) {
         fWeight = 1.0;
         fWeightSum += fWeight;
         pInfo->fFuncWord += UnitScoreFunc (this, i, pLex, 0, 1) * fWeight;
            // just one step in function word
      }
   pInfo->fFuncWord /= fWeightSum;

   pInfo->fWordPosMismatchStart = UnitScoreMismatchedWordPos (this, WORDPOS_SYLSTART, WORDPOS_WORDSTART | WORDPOS_SYLSTART) / 2.0;
   pInfo->fWordPosMismatchEnd = UnitScoreMismatchedWordPos (this, WORDPOS_SYLEND, WORDPOS_WORDEND | WORDPOS_SYLEND) / 2.0;
      // NOTE - Dividing by 2.0 on the assumption that the average phoneme is two phonemes
      // Ignoring syllable boundaries because comparing syllable by syllable



   // derived from objectively calculated valyes
   pInfo->fWordMismatchPenalty = (pInfo->fWordPosMismatchStart + pInfo->fWordPosMismatchEnd) / 2.0;
   pInfo->fPunctMismatchPenalty = pInfo->fWordMismatchPenalty * 8.0;
   pInfo->fRandomize = pInfo->fWordMismatchPenalty / 2.0; // BUGFIX - Was using * 2.0;, but too much
      // BUGFIX - Added /2.0 to reduce the randomization s abit more
   pInfo->fCrossSentencePenalty = (pInfo->fDuration + pInfo->fEnergy + pInfo->fPitchF0Fuji) / 3.0; // not sure what this should be exactly
      // using fuji value for cross-sentence penalty
   pInfo->fVeryBadScore = BEAMSEARCHHYP_HISTORY * pInfo->fCrossSentencePenalty;
}


/*************************************************************************************
CMTTS::SynthProsodyCreateSyllablesInt - This loops through the sentence and figures
out the prosody based on the CSentenceSyllable information.

NOTE: This is an internal method that assumes the sentence has already been broken up onto
sentences.
inputs
   PCMMLNode2        pNode - List of words and puncutation.
   DWORD             dwNodeStart - Start index into the pNode
   DWORD             dwNodeEnd - End index into pNode
   PCTextParse       pParse - Just used for getting strings out
   PTTSVOICEMOD   pVoiceMod - Parameters controlling how the voice is modified by
                  the original. Generally, pass in NULL. Only internal functions
                  will pass in non-null.
   int               iTTSQuality - TTS quality. 0 for fast, 1 for normal, 2 for slow, 3 for best, 4 for multipass
   BOOL              fTransPros - Set this to TRUE if speaking transplanted prosody, and will
                     generate syllables more quickly since they info is likely to be thrown
                     out anyway
   DWORD                dwMultiPass - Pass number when generating several different versions of the
                        same sentence, and selecting the best one for TTS. Used to make the
                        random work better.
   PCListFixed       plNodeAssociate - This list is initialized to sizeof(PCMMLNode2),
                     and contains one element per syllable in PCSentenceSyllable.
                     Use this to link nodes with the returned object.
returns
   PCSentenceSyllable - An object with all the syllables from the sentence in it.
                     This object will need to be freed.
                     NOTE: bFlags is filled with 0 if no microsilence, 255 if always microsilence
*/
PCSentenceSyllable CMTTS::SynthProsodyCreateSyllablesInt (PCMMLNode2 pNode, DWORD dwNodeStart, DWORD dwNodeEnd, PCTextParse pParse,
                                                       PTTSVOICEMOD pVoiceMod, int iTTSQuality, BOOL fTransPros,
                                                       DWORD dwMultiPass, PCListFixed plNodeAssociate)
{
   PCSentenceSyllable pss = new CSentenceSyllable;
   plNodeAssociate->Init (sizeof(PCMMLNode2));

   PCMLexicon pLex = Lexicon();

   DWORD i, j, k;
   PCMMLNode2 pSub;
   PWSTR psz;
   SENTSYLEMPH Emph;
   CListFixed lBoundary;
   lBoundary.Init (sizeof(DWORD));
   memset (&Emph, 0, sizeof(Emph));
   // NOTE: fDurSkew and fAccent are set to 0 properly
   Emph.fDurPhone = Emph.fDurSyl = Emph.fPitchF0Fuji = Emph.fVolume = 1;
   CListFixed lPARSERULEDEPTH;
   lPARSERULEDEPTH.Init (sizeof(PARSERULEDEPTH));
   PARSERULEDEPTH PRD, PRDOrig;

   for (i = dwNodeStart; i < dwNodeEnd; i++) {
      pSub = NULL;
      pNode->ContentEnum (i, &psz, &pSub);
      if (!pSub)
         continue;
      psz = pSub->NameGet();
      if (!psz)
         continue;

      // see if this is punctuation
      DWORD dwWord;
      if (!_wcsicmp(psz, pParse->Punctuation())) {
         psz = pSub->AttribGetString(pParse->Text());

         if (psz)
            dwWord = m_pLexTrainingWords->WordFind(psz); // get the word ID
         else
            dwWord = (DWORD)-1;  // no text

         // add this
         pss->Add (
            dwWord,
            POS_MAJOR_EXTRACT(POS_MAJOR_PUNCTUATION),
            0, // sylindex
            0,
            &Emph,
            FALSE,
            0, // NOTE: with punctuation must be syllable 0 and no phonemes
            0, // NOTE: With functuation, always in most common "words"
            0,
            0);
         plNodeAssociate->Add (&pSub);

         // parse depth
         psz = pSub->AttribGetString(pParse->ParseRuleDepth());
         memset (&PRD, 0, sizeof(PRD));
         if (psz)
            *((DWORD*)&PRD) = (DWORD)_wtoi(psz);
         lPARSERULEDEPTH.Add (&PRD);

         continue;
      } // if punctuation

      // else, only looking for words
      if (_wcsicmp(psz, pParse->Word()))
         continue;

      // get the prounciation
      PWSTR pszPron = pSub->AttribGetString (pParse->Pronunciation());
      DWORD dwPhonemes = 0;
      BYTE abPron[50];
      if (pszPron) {
         // else convert to binary
         dwPhonemes = (DWORD)MMLBinaryFromString (pszPron, abPron, sizeof(abPron));
         if (dwPhonemes >= sizeof(abPron))
            dwPhonemes = sizeof(abPron)-1;
         abPron[dwPhonemes] = 0; // to null terminate

         for (j = 0; j < dwPhonemes; j++)
            abPron[j]++;   // so that offset properly
      }
      else
         abPron[0] = 0;

      // get the text
      psz = pSub->AttribGetString(pParse->Text());
      PWSTR pszWord = psz;

      // figure out how many syllables
      lBoundary.Clear();
      pLex->WordSyllables (abPron, psz, &lBoundary);
      DWORD *padwBoundry = (DWORD*)lBoundary.Get(0);

      // restore abPron down so can use later
      for (j = 0; j < dwPhonemes; j++)
         abPron[j]--;

      // see if have a word match and use that
      dwWord = psz ? m_pLexTrainingWords->WordFind(psz) : (DWORD)-1; // get the word ID

      // get the part of speech
      BYTE bPOS, bRuleDepthLowDetail;
      psz = pSub->AttribGetString(pParse->POS());
      if (psz)
         bPOS = POS_MAJOR_EXTRACT ((BYTE)_wtoi(psz));
      else
         bPOS = POS_MAJOR_EXTRACT (POS_MAJOR_NOUN);
      psz = pSub->AttribGetString(pParse->RuleDepthLowDetail());
      if (psz)
         bRuleDepthLowDetail = (BYTE)_wtoi(psz);
      else
         bRuleDepthLowDetail = 0;

      // get the word commonality
      DWORD dwWordRank;
      if (!pszWord || (bPOS == POS_MAJOR_EXTRACT (POS_MAJOR_PUNCTUATION)))
         dwWordRank = 0;   // punctation
      else {
         dwWordRank = WordRank (pszWord, TRUE);
         WordRankHistory (pszWord);  // add word to speaking history
      }

      // BUGBUG - at some point in future might want to have <emphasis> tag
      // affect dwWordRank, but not right now

      // go through all the syllables and add them
      DWORD dwSyllablesAdded = 0;
      for (j = 0; j < lBoundary.Num(); j++) {
         DWORD dwStress = ((lBoundary.Num() > 1) || pLex->ChineseUse()) ? (padwBoundry[j] >> 24) : 1;
               // BUGFIX - single-syllable words are always treated as astressed
               // BUGFIX - With chinese, DON'T force stress for single-syllable words
         DWORD dwPhonemes = (padwBoundry[j] & 0xffff) - (j ? (padwBoundry[j-1] & 0xffff) : 0);

         // figure out the phoneme groups
         DWORD dwPhoneGroupBits = pss->PhoneGroupBitsCalc (
            abPron + (j ? (padwBoundry[j-1] & 0xffff) : 0),
            dwPhonemes,
            pLex);

         pss->Add (
            dwWord,
            bPOS,
            (BYTE)j,
            bRuleDepthLowDetail,
            &Emph,
            (BYTE)dwStress,
            dwPhonemes,
            dwWordRank,
            0,
            dwPhoneGroupBits);
         plNodeAssociate->Add (&pSub);
         dwSyllablesAdded++;

         padwBoundry[j] &= 0x00ffffff;   // so remove the high byte
      } // j

      // write the syllable boundaries... Note that the high-byte will have been removed above
      AttribSetArray (pSub, gpszSyllables, lBoundary.Num(), padwBoundry);

      // parse depth
      psz = pSub->AttribGetString(pParse->ParseRuleDepth());
      memset (&PRD, 0, sizeof(PRD));
      if (psz)
         *((DWORD*)&PRD) = (DWORD)_wtoi(psz);
      PRDOrig = PRD;
      // add a word entry for each syllable
      for (k = 0; k < dwSyllablesAdded; k++) {
         PRD = PRDOrig;
         if (dwSyllablesAdded >= 2) {
            if (k)
               PRD.bBefore = PRD.bDuring;
            if (k+1 < dwSyllablesAdded)
               PRD.bAfter = PRD.bDuring;
         }
         lPARSERULEDEPTH.Add (&PRD);
      } // k

   } // i

   // remember the parse rule depths
   pss->SetPARSERULEDEPTH ((PPARSERULEDEPTH) lPARSERULEDEPTH.Get(0), lPARSERULEDEPTH.Num());
   
   // BUGFIX - make sure there are no "unknown" parts of speech in the sentence syllable
   // because wont match. Particular problem with chinese, but would have been
   // a sizable (but unnoticed) problem with english
   pss->ReplaceUnknownPOSWithNoun ();

   // go through and figure out the best combination of templates to use
   // figure out a slot to put this voice in
   for (i = 0; i < TTSVOICEMODMAXPROSODY; i++)
      if (!pVoiceMod->apTTSProsody[i]) {
         pVoiceMod->apTTSProsody[i] = m_pCTTSProsody;
         break;
      }

   // how accuracy do we want this
   fp fUnits = max(m_dwUnits, 1);
   int iTTSQualityLimited = min (max(iTTSQuality, 0), 3);
   // BUGFIX - Greater difference in qualoty
   fUnits *= (0.33 + 0.66 * (fp)iTTSQualityLimited / 3.0); // for accuracy simulation
   fp fAccuracy = (log((double)fUnits) - log((double)10000)) / (log((double)100000) - log((double)10000));
   fAccuracy = pow((double)2.0, (double)fAccuracy);

   int iTTSQualityProsody = iTTSQuality;
   if (fTransPros) {
      iTTSQualityProsody--;
      fAccuracy /= 2.0; // half the accuracy if transplanted prosody
   }

   COMPARESYLINFO CSI;
   FillInCOMPARESYLINFO (&CSI);
   m_pCTTSProsody->FindBestMatch (
      (m_fFujiRangeHigh + m_fFujiRangeMed) / 2.0,  // BUGFIX - range was being a bit too much, so limit
      (m_fFujiRangeMed + m_fFujiRangeLow) / 2.0,
      m_fFujiRangeLow * 0.8,
      pLex, &CSI, pss, m_pLexTrainingWords, pVoiceMod->apTTSProsody, i+1, iTTSQualityProsody, fAccuracy,
      (iTTSQuality >= 4) ? 2 : 1, dwMultiPass);  // randomness increased on multipass
            // BUGFIX - Was iTTSQuality >= 4, but increase randomness on higher quality (but slow) too
            // since now doing multipass for everything
            // BUGFIX - Changed bacd to (iTTSQuality >= 4) from (iTTSQuality >= 3) because multipass
            // is extrememly slow and didn't seem to change the quality

   // restore
   if (i < TTSVOICEMODMAXPROSODY)
      pVoiceMod->apTTSProsody[i] = NULL;

   // BUGFIX - Loop through all the syllables and pitch normalize
   // NOTE: Cant determine syllable duration here
   double fPitchF0ExtraSum = 0;
   double fPitchF0FujiSum = 0;
   double fPitchCount = 0;
   fp fPitchF0Extra, fPitchF0Fuji, fDur;
   for (i = 0; i < pss->m_dwNum; i++) {
      fPitchF0Fuji = (fp)pss->m_paSyl[i].bPitchF0Fuji / 100.0;
      if (!fPitchF0Fuji)
         continue;

      fPitchF0Fuji = log(fPitchF0Fuji) / log((fp)2);

      // guestimate of duration
      fDur = pow((fp)pss->m_paSyl[i].bDurPhone * (fp)pss->m_paSyl[i].bDurSyl / 100.0 / 100.0, 0.5 * DURPHONEPOW);
         // BUGFIX - Was sqrt() to average the two, but also need to include DURPHONEPOW

      DWORD dwPitchPoint;
      for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++) {
         fPitchF0Extra = (fp)pss->m_paSyl[i].acPitchF0Extra[dwPitchPoint] / 100.0 / (fp)SSPITCHPOINTS;
         fPitchF0ExtraSum += fPitchF0Extra * fDur;
      }
      fPitchF0FujiSum += fPitchF0Fuji * fDur;
      fPitchCount += fDur;
   } // i
   if (fPitchCount) {
      fPitchF0ExtraSum /= fPitchCount;
      fPitchF0FujiSum /= fPitchCount;
   }
   fPitchF0ExtraSum = pow(2.0, fPitchF0ExtraSum);
   fPitchF0FujiSum = pow(2.0, fPitchF0FujiSum);

#ifdef _DEBUG
   WCHAR szTemp[128];
   swprintf (szTemp, L"\r\nPitch error = %g extra, %g fuji", (double)fPitchF0ExtraSum, (double)fPitchF0FujiSum);
   OutputDebugStringW (szTemp);
#endif
   
   // actually scale, and hack to make difficult to go really low
   // NOTE: only normalizing Fuji
#define HACKTOOLOW         0.5   // BUGFIX - Used to be 0.9, but don't do with fuji
   // NOTE: Shouldnt have much of an effect anymore with other tuning to fujipitch
   for (i = 0; i < pss->m_dwNum; i++) {
      fPitchF0Fuji = (fp)pss->m_paSyl[i].bPitchF0Fuji / 100.0;
      fPitchF0Fuji /= fPitchF0FujiSum;

      // if too low then fix
      if (fPitchF0Fuji < HACKTOOLOW) {
         fPitchF0Fuji /= HACKTOOLOW;   // so that threshhold is 1
         fPitchF0Fuji = sqrt(fPitchF0Fuji);
         fPitchF0Fuji *= HACKTOOLOW;   // to restore
      }
      fPitchF0Fuji = fPitchF0Fuji * 100.0 + 0.5;
      fPitchF0Fuji = max(fPitchF0Fuji, 0);
      fPitchF0Fuji = min(fPitchF0Fuji, 255);
      pss->m_paSyl[i].bPitchF0Fuji = (BYTE)fPitchF0Fuji;
   } // i

   return pss;
}



/*************************************************************************************
CMTTS::SynthProsodyCreateSyllables - This loops through the sentence and figures
out the prosody based on the CSentenceSyllable information.


inputs
   PCMMLNode2        pNode - List of words and puncutation.
   DWORD             dwNodeStart - Starting node. Originally pass in 0, but then pass in last dwNode End
   PCTextParse       pParse - Just used for getting strings out
   PTTSVOICEMOD   pVoiceMod - Parameters controlling how the voice is modified by
                  the original. Generally, pass in NULL. Only internal functions
                  will pass in non-null.
   int               iTTSQuality - TTS quality. 0 for fast, 1 for normal, 2 for slow, 3 for best, 4 for multipass
   BOOL              fTransPros - Set this to TRUE if speaking transplanted prosody, and will
                     generate syllables more quickly since they info is likely to be thrown
                     out anyway
   DWORD                dwMultiPass - Pass number when generating several different versions of the
                        same sentence, and selecting the best one for TTS. Used to make the
                        random work better.
   PCListFixed       plNodeAssociate - This list is initialized to sizeof(PCMMLNode2),
                     and contains one element per syllable in PCSentenceSyllable.
                     Use this to link nodes with the returned object.
   DWORD             *pdwNodeEnd - Filled with the next node to start at.
returns
   PCSentenceSyllable - An object with all the syllables from the sentence in it.
                     This object will need to be freed.
                     NOTE: bFlags is filled with 0 if no microsilence, 255 if always microsilence
*/
PCSentenceSyllable CMTTS::SynthProsodyCreateSyllables (PCMMLNode2 pNode, DWORD dwNodeStart, PCTextParse pParse,
                                                       PTTSVOICEMOD pVoiceMod, int iTTSQuality, BOOL fTransPros,
                                                       DWORD dwMultiPass, PCListFixed plNodeAssociate, DWORD *pdwNodeEnd)
{
   // set, sjut in case
   *pdwNodeEnd = (DWORD)-1;

   if (dwNodeStart >= pNode->ContentNum())
      return NULL;   // no more data

   PCSentenceSyllable pss; // = new CSentenceSyllable;
   plNodeAssociate->Init (sizeof(PCMMLNode2));

   // split this into multiple sentences and pass those to individual prosody generation units
   // DWORD i;
   DWORD dwNodeEnd;
   // CListFixed lNodeAssociateTemp;
   // PCSentenceSyllable pssTemp;
   PCMMLNode2 pSub;
   // PCMMLNode2 *ppNode;
   PWSTR psz;
   //for (dwNodeStart = 0; dwNodeStart < pNode->ContentNum(); dwNodeStart = dwNodeEnd) {
   for (dwNodeEnd = dwNodeStart+1; dwNodeEnd < pNode->ContentNum(); dwNodeEnd++) {
      pSub = NULL;
      pNode->ContentEnum (dwNodeEnd, &psz, &pSub);
      if (!pSub)
         continue;
      psz = pSub->NameGet();
      if (!psz)
         continue;

      // only care about punctuation
      if (_wcsicmp(psz, pParse->Punctuation()))
         continue;

      psz = pSub->AttribGetString(pParse->Text());
      if (!psz)
         continue;   // error

      // if it's a period, exclamation point, or question mark then stop
      if (!_wcsicmp(psz, L".") || !_wcsicmp(psz, L"!") || !_wcsicmp(psz, L"?")) {
         // found it, so include this
         dwNodeEnd++;
         break;
      }

      // else, comma or seomthing that's part of a sentence
   } // dwNodeEnd

   *pdwNodeEnd = dwNodeEnd;

   // when get here, have complete sentence
   pss = SynthProsodyCreateSyllablesInt (pNode, dwNodeStart, dwNodeEnd,
      pParse, pVoiceMod, iTTSQuality, fTransPros, dwMultiPass, plNodeAssociate);
   if (!pss)
      return NULL;   // error

   // combine
   //ppNode = (PCMMLNode2*)lNodeAssociateTemp.Get(0);
   //for (i = 0; i < lNodeAssociateTemp.Num(); i++)
   //   plNodeAssociate->Add (ppNode + i);

   //pss->Append (pssTemp);
   //delete pssTemp;

//   } // while have data

   return pss;
}

/*************************************************************************************
CMTTS::SynthProsodyTagsApply - This loops through all the words in PCMMLNode2 (generated
from CTextParse::ParseFromMMLText (or equivalent). If there are no prosody
tags it fills some in, automatically generating prosody.

It writes tags for gpszPitchRel, gpszVolRel, gpszDurRel. This also sets gpszInflectPoint
to 1 if the punctuation is immediately to the right, or -1 if it's immediate to the left.

inputs
   PCMMLNode2         pNode - List of words and punctuation. This is modified in place.
   DWORD             dwNodeStart - Starting index into pNode. Initially pass in 0, but on
                        later passes pass last *pdwNodeEnd.
   PCTextParse       pParse - Just used for getting strings out
   PTTSVOICEMOD   pVoiceMod - Parameters controlling how the voice is modified by
                  the original. Generally, pass in NULL. Only internal functions
                  will pass in non-null.
   int               iTTSQuality - TTS quality. 0 for fast, 1 for normal, 2 for slow, 3 for best, 4 for multipass
   BOOL              fTransPros - Set this to TRUE if speaking transplanted prosody, and will
                     generate syllables more quickly since they info is likely to be thrown
                     out anyway
   DWORD                dwMultiPass - Pass number when generating several different versions of the
                        same sentence, and selecting the best one for TTS. Used to make the
                        random work better.
   DWORD             *pdwNodeEnd - Filled in with node that processed up to. Pass this back into
                     the next dwNodeStart.
returns
   BOOL - TRUE if succes, FALSE if no more to speak
*/

BOOL CMTTS::SynthProsodyTagsApply (PCMMLNode2 pNode, DWORD dwNodeStart, PCTextParse pParse, PTTSVOICEMOD pVoiceMod, int iTTSQuality,
                                   BOOL fTransPros, DWORD dwMultiPass, DWORD *pdwNodeEnd)
{
   *pdwNodeEnd = (DWORD)-1;

#if 0 // old prosody
   PCTTSPunctPros *ppp = (PCTTSPunctPros*) m_lPCTTSPunctPros.Get(0);
   DWORD dwNumPunct = m_lPCTTSPunctPros.Num();
#endif // 0, old prosody

   // determine syllable boundaries
   CListFixed lNodeAssociate;
   PCSentenceSyllable pss = SynthProsodyCreateSyllables (pNode, dwNodeStart, pParse, pVoiceMod,
      iTTSQuality, fTransPros, dwMultiPass, &lNodeAssociate, pdwNodeEnd);
   if (!pss)
      return FALSE;
   PCMMLNode2 *ppNA = (PCMMLNode2*)lNodeAssociate.Get(0);

   // will need to initialize globalflags settings
   // convert some tags
   TTSGLOBALSTATE State;
   memset (&State, 0 ,sizeof(State));
   State.fProsodyAvgPitch = pVoiceMod->pSubVoice->m_fAvgPitch;
   //State.fProsodyAvgSyllableDur = pVoiceMod->pSubVoice->m_fAvgSyllableDur;
   State.fProsodyPitchExpress = 1;
   State.fProsodyVol = 1;
   State.fProsodyWPM = pVoiceMod->pSubVoice->m_dwWordsPerMinute;
   // leave emotions at 0
   // will need to fill in derviced values
   GlobalStateFromMML (NULL, pVoiceMod->pSubVoice, &State);


   DWORD i, j, k, dwSylStart, dwSylEnd, dwNumSyl, dw;
   // DWORD *padwBoundary;
   // CListFixed lBoundary;
   CListFixed lProsody, lDWORD;
   PCMMLNode2 pSub;
   PWSTR psz;
   SENTSYLEMPH Emph;
   PSENTSYLEMPH pProsody;
   lDWORD.Init (sizeof(DWORD));
   lProsody.Init (sizeof(SENTSYLEMPH));
   for (i = dwNodeStart /*0*/; i < *pdwNodeEnd /*pNode->ContentNum()*/; i++) {
      pSub = NULL;
      pNode->ContentEnum (i, &psz, &pSub);
      if (!pSub)
         continue;
      psz = pSub->NameGet();
      if (!psz)
         continue;

      // global state
      if (!_wcsicmp(psz, gpszTTSGlobalState)) {
         // get the state
         GlobalStateFromMML (pSub, pVoiceMod->pSubVoice, &State);
         continue;
      }
      
      // only care about word from here on out
      if (_wcsicmp(psz, pParse->Word()))
         continue;   // only accept words

      // get the word text
      PWSTR pszText = pSub->AttribGetString (pParse->Text());
      if (!pszText)
         continue;

      // find which syllable should be associated with
      for (dwSylStart = 0; dwSylStart < lNodeAssociate.Num(); dwSylStart++)
         if (ppNA[dwSylStart] == pSub)
            break;
      if (dwSylStart >= lNodeAssociate.Num())
         continue;   // couldnt find. This shouldnt happen
      for (dwSylEnd = dwSylStart+1; dwSylEnd < lNodeAssociate.Num(); dwSylEnd++)
         if (ppNA[dwSylEnd] != pSub)
            break;
      dwNumSyl = dwSylEnd - dwSylStart;

      // BUGFIX - remove because dont need
      // get the syllable boundaries
      //if (!AttribGetArray (pSub, gpszSyllables, &lBoundary))
      //   continue; // shouldnt happen
      //if (dwNumSyl != lBoundary.Num())
      //   continue;   // shouldnt happen
      //padwBoundary = (DWORD*) lBoundary.Get(0);

      // create lists for pitch, volume, and duration for each syllable
      lProsody.Clear();
      lProsody.Required (dwNumSyl);
      for (j = 0; j < dwNumSyl; j++) {
         DWORD dwPitchPoint;
         for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
            Emph.afPitchF0Extra[dwPitchPoint] = (fp) pss->m_paSyl[dwSylStart + j].acPitchF0Extra[dwPitchPoint] / 100.0 * PITCHPOW;
         Emph.fPitchF0Fuji = pow ((fp) pss->m_paSyl[dwSylStart + j].bPitchF0Fuji / 100.0, PITCHPOW);
         Emph.fPitchF0FujiDelta = (fp) pss->m_paSyl[dwSylStart + j].cPitchF0FujiDelta / 100.0 * PITCHPOW;
         //Emph.fPitchSweep =  (fp) pss->m_paSyl[dwSylStart + j].cPitchSweep / 100.0 * PITCHPOW;
         //Emph.fPitchBulge =  (fp) pss->m_paSyl[dwSylStart + j].cPitchBulge / 100.0 * PITCHPOW;
         Emph.fDurPhone = pow ((fp) pss->m_paSyl[dwSylStart + j].bDurPhone / 100.0, DURPHONEPOW);
         Emph.fDurSyl = pow ((fp) pss->m_paSyl[dwSylStart + j].bDurSyl / 100.0, DURPHONEPOW);
         Emph.fVolume = pow ((fp) pss->m_paSyl[dwSylStart + j].bVol / 100.0, VOLUMEPOW);
         Emph.fAccent = (fp) pss->m_paSyl[dwSylStart + j].cAccent / 100.0;
         Emph.fDurSkew =  (fp) pss->m_paSyl[dwSylStart + j].cDurSkew / 100.0 * DURPHONEPOW;

         // apply voice mods
         for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++)
            Emph.afPitchF0Extra[dwPitchPoint] *= pVoiceMod->pSubVoice->m_pPOSAccentuate.p[0];
         // Disable: Emph.fPitchF0Fuji = pow (Emph.fPitchF0Fuji, pVoiceMod->pSubVoice->m_pPOSAccentuate.p[0]);
         //Emph.fPitchSweep *= pVoiceMod->pSubVoice->m_pPOSAccentuate.p[0];
         //Emph.fPitchBulge *= pVoiceMod->pSubVoice->m_pPOSAccentuate.p[0];
         Emph.fVolume = pow (Emph.fVolume, pVoiceMod->pSubVoice->m_pPOSAccentuate.p[1] * State.fDerVolumeExpress);
         // NOTE: Nothing for Emph.fAccent
         Emph.fDurPhone = pow (Emph.fDurPhone, pVoiceMod->pSubVoice->m_pPOSAccentuate.p[2] * State.fDerDurationExpress);
         Emph.fDurSyl = pow (Emph.fDurSyl, pVoiceMod->pSubVoice->m_pPOSAccentuate.p[2] * State.fDerDurationExpress);
         Emph.fDurSkew *= pVoiceMod->pSubVoice->m_pPOSAccentuate.p[2] * State.fDerDurationExpress;

         lProsody.Add (&Emph);
      } // j
      pProsody = (PSENTSYLEMPH)lProsody.Get(0);

      // note if have micropause
      // write out pause left
      pSub->AttribSetDouble (gpszPauseLeft, (fp)pss->m_paSyl[dwSylStart].bPauseProb / 15.0);

      // write out micropauses. Note: affected by WPM
      PCMTTS pMaster = TTSMasterGet2 ();
      if (!pMaster)
         pMaster = this;
      fp fSpeedUp = pMaster->m_dwWordsPerMinute ? 
         log(State.fDerWPM / (fp) pMaster->m_dwWordsPerMinute) / log((fp)2) * 0.5 : 0;
            // BUGFIX - was 50, which was totall incorrect. should have been 0.5
            // BUGIFX - Changed to 0.25 so less of an effect
            // BUGFIX - Changed back to 0.5 to greater effect.
      pSub->AttribSetDouble (gpszDerMicropauses, State.fDerMicropauses + fSpeedUp);

      // get the current tags
      CPoint pEmph;
      pEmph.Zero();
      pEmph.p[0] = pEmph.p[1] = pEmph.p[2] = 1;
      psz = pSub->AttribGetString (gpszPitchRel);
      if (psz)
         pEmph.p[0] = _wtof(psz);
      psz = pSub->AttribGetString (gpszVolRel);
      if (psz)
         pEmph.p[1] = _wtof(psz);
      psz = pSub->AttribGetString (gpszDurRel);
      if (psz)
         pEmph.p[2] = _wtof(psz);

      // write out the per-syllable pitch, volume, and duration
      for (j = 0; j < 7; j++) {
         lDWORD.Clear();
         lDWORD.Required (dwNumSyl);

         for (k = 0; k < dwNumSyl; k++) {
            // value
            fp f;
            switch (j) {
            case 0:  // pitchF0Extra
               // See below: f = pProsody[k].fPitchF0Extra;
               // moved to fuji: f *= pEmph.p[0];
               break;
            // NOTE: Not writing fPitchF0FujiDelta because not needed
            case 1:  // volume
               f = pProsody[k].fVolume;
               f *= pEmph.p[1];
               break;
            case 2:  // duration, phone
               f = pProsody[k].fDurPhone;
               f *= pEmph.p[2];
               break;
            case 3:  // duration, syllable
               f = pProsody[k].fDurSyl;
               f *= pEmph.p[2];
               break;
            case 4:  // duration, skew
               f = pProsody[k].fDurSkew;
               f *= pEmph.p[2];
               break;
            case 5:  // pitchF0Fuji
               f = pProsody[k].fPitchF0Fuji;
               f *= pEmph.p[0];
               break;
            case 6:  // accent
               f = max(pProsody[k].fAccent, -1.0) + 10.0;  // add 10.0 since dont want going negative
               // NOTE: No emphasis
               break;
            } // j
               // this is a change from some of the old code for relative duraion, etc.

            if (j == 0) {
               // special case for pitch extra since is an array
               DWORD dwPitchPoint;
               for (dwPitchPoint = 0; dwPitchPoint < SSPITCHPOINTS; dwPitchPoint++) {
                  dw = (DWORD)(int)(pProsody[k].afPitchF0Extra[dwPitchPoint] * (fp)0x10000);
                  lDWORD.Add (&dw);
               } // dwPitchPoint
            }
            else {
               if (j == 4)
                  dw = (DWORD)(int)(f * (fp)0x10000);
               else
                  dw = (DWORD)(f * (fp)0x10000);

               lDWORD.Add (&dw);
            }
         } // k, all points

         // save
         PWSTR pszArray;
         switch (j) {
         case 0:  // pitch
            pszArray = gpszSylPitchF0ExtraArray;
            break;
         case 1:  // volume
            pszArray = gpszSylVol;
            break;
         case 2:  // duration, phone
            pszArray = gpszSylDurPhone;
            break;
         case 3:  // duration, syllable
            pszArray = gpszSylDurSyl;
            break;
         case 4:
            pszArray = gpszSylDurSkew;
            break;
         case 5:  // pitch
            pszArray = gpszSylPitchF0Fuji;
            break;
         case 6:  // accent
            pszArray = gpszSylAccent;
            break;
         } // j

         AttribSetArray (pSub, pszArray, lDWORD.Num(), (DWORD*)lDWORD.Get(0));
      } // j


      // BUGFIX - the following code is now obsolete
#if 0 // old prosody
      // get N-gram
      CPoint pNGram;
      won't work anymore because of the way that handle pNode bit by bit
      NGramFindBackoff (pParse, pNode, i, &pNGram, &fPauseLeft, NULL);
      pEmph.p[0] *= pow (pNGram.p[0], pVoiceMod->pPOSAccentuate.p[0]);
      pEmph.p[1] *= pow (pNGram.p[1], pVoiceMod->pPOSAccentuate.p[1]);
      pEmph.p[2] *= pow (pNGram.p[2], pVoiceMod->pPOSAccentuate.p[2]);


      // figure out what category this word is in
      //BOOL fException;
      DWORD j, dwLeft;
      BOOL fIsPunct;
      for (j = 0; j < NUMLEXWORDEMPH; j++)
         if (m_apLexWordEmph[j] && (-1 != m_apLexWordEmph[j]->WordFind(pszText)))
            break;
      // BUGFIX - Since changed the way fundamentally works, don't use exceptions
      // fException = (-1 != m_pLexWords->WordFind(pszText));
      pEmph.p[0] *= m_apLexWordEmphScale[j].p[0];
      //if (!fException) {
         // only do this for words that aren't in the hardwired list, since already
         // have volume and duration taken care of
         pEmph.p[1] *= m_apLexWordEmphScale[j].p[1];
         pEmph.p[2] *= m_apLexWordEmphScale[j].p[2];
      //}

      // apply scaling by word length
      // get the pronunciation
      PWSTR pszPron = pSub->AttribGetString (pParse->Pronunciation());
      DWORD dwPhonemes = 0;
      if (pszPron) {
         // else convert to binary
         BYTE abPron[50];
         dwPhonemes = MMLBinaryFromString (pszPron, abPron, sizeof(abPron));
      }
      if (dwPhonemes) {
         dwPhonemes--;
         if (dwPhonemes >= NUMPROSWORDLENGTH)
            dwPhonemes = NUMPROSWORDLENGTH-1;
         pEmph.p[0] *= m_apProsWordEmphFromWordLength[dwPhonemes].p[0];
         pEmph.p[1] *= m_apProsWordEmphFromWordLength[dwPhonemes].p[1];
         pEmph.p[2] *= m_apProsWordEmphFromWordLength[dwPhonemes].p[2];
      }

      // look to the left and right for words that are function words
      for (dwLeft = 0; dwLeft < 2; dwLeft++) {
         DWORD dwCur = i;
         for (j = 0; j < PUNCTPROS; j++) {
            won't work anymore because of the way that handle pNode bit by bit
            if (!FindAdjacentWord (pParse, pNode, !dwLeft, FALSE, &dwCur, &pszText, &fIsPunct))
               break;   // no more

            // see if this is a function word
            WORD wFuncWord = (WORD)m_pLexFuncWords->WordFind (pszText);
            if (wFuncWord == -1)
               continue;   // not a function word

            // try to find it
            for (k = 0; k < dwNumPunct; k++)
               if ((ppp[k]->m_wFuncWord == wFuncWord) && !(ppp[k]->m_wPunct))
                  break;
            if (k >= dwNumPunct)
               break;   // not actually found, BUGFIX - Was conintue

            // figure out scaling...
            PCPoint pScale = dwLeft ? &ppp[k]->m_apAfter[j] : &ppp[k]->m_apBefore[j];
            pEmph.p[0] *= pow (pScale->p[0], pVoiceMod->pPunctAccentuate.p[0]);
            pEmph.p[1] *= pow (pScale->p[1], pVoiceMod->pPunctAccentuate.p[1]);
            pEmph.p[2] *= pow (pScale->p[2], pVoiceMod->pPunctAccentuate.p[2]);
         } // j, over PUNCTPROS
      } // dwLeft

      // now, look for punctuation...
      for (dwLeft = 0; dwLeft < 2; dwLeft++) {
         DWORD dwCur = i;
         DWORD dwWordSkip = 0;
         WCHAR wPunct = L'.';
         for (j = 0; dwWordSkip < PUNCTPROS; j++) {
            won't work anymore because of the way that handle pNode bit by bit
            if (!FindAdjacentWord (pParse, pNode, !dwLeft, TRUE, &dwCur, &pszText, &fIsPunct))
               goto assumepunct;   // no more

            // if it isn't punctuation then increase word skip and continue
            if (!fIsPunct) {
               dwWordSkip++;
               continue;
            }

            if (pszText[0])
               wPunct = pszText[0];

assumepunct:
            // if this punctuation is right next to the word then set a flag
            // so the inflection point of the prosody will be at the start/end
            // of the word rather than middle
            if ((dwWordSkip == 0) && !pSub->AttribGetString(gpszInflectPoint))
               pSub->AttribSetString (gpszInflectPoint, dwLeft ? L"-1" : L"1");

            // try to find it
            for (k = 0; k < dwNumPunct; k++)
               if (ppp[k]->m_wPunct == wPunct)
                  break;
            if (k >= dwNumPunct)
               break;   // not actually found

            // figure out scaling...
            PCPoint pScale = dwLeft ? &ppp[k]->m_apAfter[dwWordSkip] : &ppp[k]->m_apBefore[dwWordSkip];
            pEmph.p[0] *= pow (pScale->p[0], pVoiceMod->pPunctAccentuate.p[0]);
            pEmph.p[1] *= pow (pScale->p[1], pVoiceMod->pPunctAccentuate.p[1]);
            pEmph.p[2] *= pow (pScale->p[2], pVoiceMod->pPunctAccentuate.p[2]);

            // since was punctuation should breka
            break;
         } // j, over PUNCTPROS
      } // dwLeft

      // write out the tags
      // BUGFIX - Only write out tags if they're not already there
      WCHAR szTemp[32];
      if (!pSub->AttribGetString (gpszPitchRel)) {
         MMLDoubleToString (pEmph.p[0], szTemp);
         pSub->AttribSetString (gpszPitchRel, szTemp);
      }
      if (!pSub->AttribGetString (gpszVolRel)) {
         MMLDoubleToString (pEmph.p[1], szTemp);
         pSub->AttribSetString (gpszVolRel, szTemp);
      }
      if (!pSub->AttribGetString (gpszDurRel)) {
         MMLDoubleToString (pEmph.p[2], szTemp);
         pSub->AttribSetString (gpszDurRel, szTemp);
      }
#endif // 0, disabled because old code

   }

   // free up memory
   delete pss;

   return TRUE;
}


/*************************************************************************************
ProsValueParse - Parses a prosody value...

inputs
   PWSTR       psz - Input string
   DWORD       *pdwValMean - Filled with the value meaning...
                  0 - absolute change, just a number
                  1 - Number with "Hz" following it
                  2 - Percentage
                  3 - + or - a certain amount
                  4 - + or - semitones (followed by st)
                  5 - + or - a certain percent
                  6 - Unknown/error
returns
   fp - Value.
*/
fp ProsValueParse (PWSTR psz, DWORD *pdwValMean)
{
   *pdwValMean = 6;  // just in case need to breal

   // see if preceded by plus or minus
   int iPlus = 0;
   if (psz[0] == L'+') {
      iPlus = 1;
      psz++;
   }
   else if (psz[0] == L'-') {
      iPlus = -1;
      psz++;
   }

   // make sure a number
   if (!iswdigit(psz[0]) && (psz[0] != L'.'))
      return 0;

   // get the value
   fp fVal = _wtof (psz);

   // look at the end
   DWORD dwLen = (DWORD)wcslen(psz);

   if (dwLen && (psz[dwLen-1] == L'%')) {
      *pdwValMean = (iPlus ? 5 : 2);
      return fVal * ((iPlus == -1) ? -1 : 1);
   }

   if ((dwLen >= 2) && !_wcsnicmp(psz + (dwLen-2), L"hz", 2)) {
      *pdwValMean = (iPlus ? 3 : 1);
      return fVal * ((iPlus == -1) ? -1 : 1);
   }

   if ((dwLen >= 2) && !_wcsnicmp(psz + (dwLen-2), L"st", 2)) {
      *pdwValMean = 4;
      return fVal * ((iPlus == -1) ? -1 : 1);
   }

   // else, no ending
   *pdwValMean = (iPlus ? 3 : 0);
   return fVal * ((iPlus == -1) ? -1 : 1);
}


/*************************************************************************************
CMTTS::EmotionToGlobalState - Takes a prosody tag and interprets all the attributes
to figure out how the global state is modified.

inputs
   PCMMLNode2         pNode - Node whtat is a "prosody" tag
   PTTSGLOBALSTATE   pState - Initially filled in with valid values, and modified in place by tag
   PTTSVOICEMOD   pVoiceMod - Parameters controlling how the voice is modified by
                  the original. Generally, pass in NULL. Only internal functions
                  will pass in non-null.
returns
   none
*/
void CMTTS::EmotionToGlobalState (PCMMLNode2 pNode, PTTSGLOBALSTATE pState, PTTSVOICEMOD pVoiceMod)
{
   PWSTR psz;

   // emotions
   if (psz = pNode->AttribGetString (gpszWhisper))
      pState->fEmotionWhisper = _wtof (psz);
   if (psz = pNode->AttribGetString (gpszShout))
      pState->fEmotionShout = _wtof (psz);
   if (psz = pNode->AttribGetString (gpszQuiet))
      pState->fEmotionQuiet = _wtof (psz);
   if (psz = pNode->AttribGetString (gpszHappy))
      pState->fEmotionHappy = _wtof (psz);
   if (psz = pNode->AttribGetString (gpszSad))
      pState->fEmotionSad = _wtof (psz);
   if (psz = pNode->AttribGetString (gpszAfraid))
      pState->fEmotionAfraid = _wtof (psz);
   if (psz = pNode->AttribGetString (gpszDrunk))
      pState->fEmotionDrunk = _wtof (psz);
}

/*************************************************************************************
CMTTS::ProsodyToGlobalState - Takes a prosody tag and interprets all the attributes
to figure out how the global state is modified.

inputs
   PCMMLNode2         pNode - Node whtat is a "prosody" tag
   PTTSGLOBALSTATE   pState - Initially filled in with valid values, and modified in place by tag
   PTTSVOICEMOD   pVoiceMod - Parameters controlling how the voice is modified by
                  the original. Generally, pass in NULL. Only internal functions
                  will pass in non-null.
returns
   none
*/
void CMTTS::ProsodyToGlobalState (PCMMLNode2 pNode, PTTSGLOBALSTATE pState, PTTSVOICEMOD pVoiceMod)
{
   fp fVal;
   DWORD dwMean;
   PWSTR psz;

   // pitch
   psz = pNode->AttribGetString (gpszPitch);
   if (psz) {
      if (!_wcsicmp(psz, L"x-high"))
         pState->fProsodyAvgPitch = pVoiceMod->pSubVoice->m_fAvgPitch * 1.5;
      else if (!_wcsicmp(psz, L"high"))
         pState->fProsodyAvgPitch = pVoiceMod->pSubVoice->m_fAvgPitch * sqrt(1.5);
      else if (!_wcsicmp(psz, L"medium") || !_wcsicmp(psz, L"default"))
         pState->fProsodyAvgPitch = pVoiceMod->pSubVoice->m_fAvgPitch;
      else if (!_wcsicmp(psz, L"low"))
         pState->fProsodyAvgPitch = pVoiceMod->pSubVoice->m_fAvgPitch / sqrt(1.5);
      else if (!_wcsicmp(psz, L"x-low"))
         pState->fProsodyAvgPitch = pVoiceMod->pSubVoice->m_fAvgPitch / 1.5;
      else {
         fVal = ProsValueParse (psz, &dwMean);
         switch (dwMean) {
         case 0:  // absolute
         case 1:  // number followd by hz
            pState->fProsodyAvgPitch = fVal;
            break;

         case 2:  // percentage
            pState->fProsodyAvgPitch = fVal / 100.0 * pVoiceMod->pSubVoice->m_fAvgPitch;
            break;

         case 3:  // +/- certain amount
            pState->fProsodyAvgPitch += fVal;
            break;

         case 4:  // +/- semitones
            pState->fProsodyAvgPitch *= pow(2, fVal / 12.0);
            break;

         case 5:  // +/- percent
            pState->fProsodyAvgPitch += fVal / 100.0 * pState->fProsodyAvgPitch;
            break;

         case 6:  // error
            break;
         }
         pState->fProsodyAvgPitch = max(pState->fProsodyAvgPitch, 1);
      }
   } // if pitch

   psz = pNode->AttribGetString (gpszRange);
   if (psz) {
      if (!_wcsicmp(psz, L"x-high"))
         pState->fProsodyPitchExpress = 2;
      else if (!_wcsicmp(psz, L"high"))
         pState->fProsodyPitchExpress = 1.5;
      else if (!_wcsicmp(psz, L"medium") || !_wcsicmp(psz, L"default"))
         pState->fProsodyPitchExpress = 1;
      else if (!_wcsicmp(psz, L"low"))
         pState->fProsodyPitchExpress = 0.5;
      else if (!_wcsicmp(psz, L"x-low"))
         pState->fProsodyPitchExpress = 0;
      else {
         fVal = ProsValueParse (psz, &dwMean);
         switch (dwMean) {
         case 2:  // percentage
            pState->fProsodyPitchExpress = fVal / 100.0;
            break;

         case 5:  // +/- percent
            pState->fProsodyPitchExpress += fVal / 100.0;
            break;

         case 0:  // absolute
         case 1:  // number followd by hz
         case 3:  // +/- certain amount
         case 4:  // +/- semitones
         case 6:  // error
            break;
         }
      }
   } // if range

   // rate
   psz = pNode->AttribGetString (gpszRate);
   if (psz) {
      if (!_wcsicmp(psz, L"x-fast"))
         pState->fProsodyWPM = (fp)pVoiceMod->pSubVoice->m_dwWordsPerMinute * 2;
      else if (!_wcsicmp(psz, L"fast"))
         pState->fProsodyWPM = (fp)pVoiceMod->pSubVoice->m_dwWordsPerMinute * sqrt((fp)2);
      else if (!_wcsicmp(psz, L"medium") || !_wcsicmp(psz, L"default"))
         pState->fProsodyWPM = (fp)pVoiceMod->pSubVoice->m_dwWordsPerMinute;
      else if (!_wcsicmp(psz, L"slow"))
         pState->fProsodyWPM = (fp)pVoiceMod->pSubVoice->m_dwWordsPerMinute / sqrt((fp)2);
      else if (!_wcsicmp(psz, L"x-slow"))
         pState->fProsodyWPM = (fp)pVoiceMod->pSubVoice->m_dwWordsPerMinute / 2;
      else {
         fVal = ProsValueParse (psz, &dwMean);
         switch (dwMean) {
         case 0:  // absolute
            pState->fProsodyWPM = fVal;
            break;

         case 2:  // percentage
            pState->fProsodyWPM = fVal / 100.0 * (fp)pVoiceMod->pSubVoice->m_dwWordsPerMinute;
            break;

         case 3:  // +/- certain amount
            pState->fProsodyWPM += fVal;
            break;

         case 5:  // +/- percent
            pState->fProsodyWPM += fVal / 100.0 * pState->fProsodyWPM;
            break;

         case 1:  // number followd by hz
         case 4:  // +/- semitones
         case 6:  // error
            break;
         }
         pState->fProsodyWPM = max(pState->fProsodyWPM, 1);
      }
   } // if rate

   // volume
   psz = pNode->AttribGetString (gpszVolume);
   if (psz) {
      if (!_wcsicmp(psz, L"x-loud"))
         pState->fProsodyVol = 4;
      else if (!_wcsicmp(psz, L"loud"))
         pState->fProsodyVol = 2;
      else if (!_wcsicmp(psz, L"medium") || !_wcsicmp(psz, L"default"))
         pState->fProsodyVol = 1;
      else if (!_wcsicmp(psz, L"soft"))
         pState->fProsodyVol = 1.0 / 2;
      else if (!_wcsicmp(psz, L"x-soft"))
         pState->fProsodyVol = 1.0 / 4;
      else if (!_wcsicmp(psz, L"silent"))
         pState->fProsodyVol = 0;
      else {
         fVal = ProsValueParse (psz, &dwMean);
         switch (dwMean) {
         case 0:  // absolute
         case 2:  // percentage
            pState->fProsodyVol = fVal / 100.0;
            break;

         case 3:  // +/- certain amount
            pState->fProsodyVol += fVal / 100.0;
            break;

         case 5:  // +/- percent
            pState->fProsodyVol += fVal / 100.0 * pState->fProsodyVol;
            break;

         case 1:  // number followd by hz
         case 4:  // +/- semitones
         case 6:  // error
            break;
         }
         pState->fProsodyVol = max(pState->fProsodyVol, 0);
      }
   } // if rate

}


/*************************************************************************************
CMTTS::SynthTextInterpretTags - Loops through the MML contains various tags and
interprets some of them. Namely,
   <Phoneme>
   <Emphasis>
   <TransPros>

This also inserts gpszTTSGlobalState mml if the state is changed by the prosody
call.

inputs
   PCMMLNode2         pNode - node
   PTTSGLOBALSTATE   pState - Filled with the current state. This way prosody tags
                     can be relative
   PTTSVOICEMOD   pVoiceMod - Parameters controlling how the voice is modified by
                  the original. Generally, pass in NULL. Only internal functions
                  will pass in non-null.
   BOOL              *pfTransPros - Initially fill this in with FALSE. It will be
                     modified to TRUE if any transplanted prosody is found.
                     If transplanted prosody is found, prosody generation algorithms
                     are run in a faster mode.
returns
   BOOL - TRUE if sueccs
*/
BOOL CMTTS::SynthTextInterpretTags (PCMMLNode2 pNode, PTTSGLOBALSTATE pState,
                                    PTTSVOICEMOD pVoiceMod, BOOL *pfTransPros)
{
   PCMLexicon pLex = pVoiceMod->pLex;
   if (!pLex)
      pLex = Lexicon();
   if (!pLex)
      return FALSE;
   CTextParse TextParse;
   if (!TextParse.Init (pLex->LangIDGet(), pLex))
      return FALSE;

   DWORD i, j;
   PWSTR psz;
   PCMMLNode2 pSub;
   for (i = 0; i < pNode->ContentNum(); i++) {
      pSub = NULL;
      pNode->ContentEnum (i, &psz, &pSub);
      if (!pSub)
         continue;
      psz = pSub->NameGet();
      if (!psz)
         continue;

      if (!_wcsicmp(psz, gpszProsody) || !_wcsicmp(psz, gpszEmotion) ) {
         TTSGLOBALSTATE gsNew = *pState;
         if (!_wcsicmp(psz, gpszProsody))
            ProsodyToGlobalState (pSub, &gsNew, pVoiceMod);
         else
            EmotionToGlobalState (pSub, &gsNew, pVoiceMod);

         // insert tag so will return to current state
         PCMMLNode2 pNewState = GlobalStateToMML (pState);
         if (pNewState)
            pNode->ContentInsert (i+1, pNewState);


         // parse text
         if (!TextParse.ParseFromMML (pSub, FALSE, FALSE))
            return FALSE;

         // parse the contents of this
         if (!SynthTextInterpretTags (pSub, &gsNew, pVoiceMod, pfTransPros))
            return FALSE;

         // loop through and pull them out
         PCMMLNode2 pEmph;
         while (pSub->ContentNum()) {
            pEmph = NULL;
            psz = NULL;
            DWORD dwNum = pSub->ContentNum()-1;
            pSub->ContentEnum (dwNum, &psz, &pEmph);
            if (psz) {
               pNode->ContentInsert (i+1, psz);
               pSub->ContentRemove (dwNum);
               continue;
            }

            // if it's doesn't have a name or is origtext then just remove
            psz = pEmph->NameGet();
            if (!psz || !_wcsicmp(psz,  gpszOrigText)) {
               pSub->ContentRemove (dwNum);
               continue;
            }

            // remove from original but dont actually delete
            pSub->ContentRemove (dwNum, FALSE);

            // add
            pNode->ContentInsert (i+1, pEmph);
         } // while have sub elements

         // finally, remove this one
         pNode->ContentRemove (i);
         // dont need to do i-- since new node will have been evalatuated already

         // finally, insert tag so that will begin new state
         pNewState = GlobalStateToMML (&gsNew);
         if (pNewState)
            pNode->ContentInsert (i, pNewState);
         continue;
      }
      else if (!_wcsicmp(psz, L"phoneme")) {
         // get the pronuncaition
         PWSTR pszPhoneme = pSub->AttribGetString (gpszPh);
         BYTE abPhone[128];   // phonemes
         DWORD dwBadPhone;
         WCHAR szPron[sizeof(abPhone)*2+1];
         if (!pszPhoneme)
            continue;   // ignore this
         if (!pLex->PronunciationFromText (pszPhoneme, abPhone, sizeof(abPhone), &dwBadPhone))
            continue;   // error, so ignore

         // find the length
         DWORD dwLen = (DWORD)strlen((char*)abPhone);
         if (!dwLen)
            continue;

         // subtract one to each
         for (j = 0; j < dwLen; j++)
            abPhone[j]--;

         // create a new word
         PCMMLNode2 pNew = new CMMLNode2;
         if (!pNew)
            continue;
         MMLBinaryToString (abPhone, dwLen, szPron);
         pNew->NameSet (TextParse.Word());
         pNew->AttribSetString (TextParse.Pronunciation(), szPron);

         // BUBUG - may have to do bookmark with this

         // text of word?
         PCMMLNode2 pJunk;
         psz = NULL;
         pSub->ContentEnum (0, &psz, &pJunk);
         pNew->AttribSetString (TextParse.Text(), psz ? psz : L"unknown word");

         // insert it...
         pNode->ContentInsert (i, pNew);

         // remove this
         pNode->ContentRemove (i+1);
         continue;
      }
      else if (!_wcsicmp(psz, gpszEmphasis)) {
         // figure what kind of emphasis...
         fp fPitchAbs = 0, fVolAbs = 0, fDurAbs = 0, fPitchRel = 0, fVolRel = 0, fDurRel = 0;

         // default to moderate...
         if (!pSub->AttribNum()) {
            fVolRel = fDurRel = 1.2;
            fPitchRel = 1.1;
         }
         
         // see if have level
         PWSTR psz;
         psz = pSub->AttribGetString (gpszLevel);
         if (psz && !_wcsicmp(psz, gpszStrong)) {
            fVolRel = fDurRel = 1.4;
            fPitchRel = 1.2;
         }
         else if (psz && !_wcsicmp(psz, gpszNone))
            fPitchRel = fVolRel = fDurRel = 1.0;
         else if (psz && !_wcsicmp(psz, gpszReduced)) {
            fVolRel = fDurRel = 0.7;
            fPitchRel = 0.9;
         }
         else if (psz) { // or if moderate && !_wcsicmp(psz, gpszModerate))
            fVolRel = fDurRel = 1.2;
            fPitchRel = 1.1;
         }

         // see if other numbers set
         psz = pSub->AttribGetString (gpszPitchAbs);
         if (psz)
            fPitchAbs = _wtof(psz);
         psz = pSub->AttribGetString (gpszVolAbs);
         if (psz)
            fVolAbs = _wtof(psz);
         psz = pSub->AttribGetString (gpszDurAbs);
         if (psz)
            fDurAbs = _wtof(psz);
         psz = pSub->AttribGetString (gpszPitchRel);
         if (psz)
            fPitchRel = _wtof(psz);
         psz = pSub->AttribGetString (gpszVolRel);
         if (psz)
            fVolRel = _wtof(psz);
         psz = pSub->AttribGetString (gpszDurRel);
         if (psz)
            fDurRel = _wtof(psz);

         // figure out the attribute strings to set
         WCHAR aszTemp[3][32];
         PWSTR paszSet[3];
         memset (paszSet, 0, sizeof(paszSet));
         if (fPitchAbs > CLOSE) {
            MMLDoubleToString (fPitchAbs, aszTemp[0]);
            paszSet[0] = gpszPitchAbs;
         }
         else if (fPitchRel > CLOSE) {
            MMLDoubleToString (fPitchRel, aszTemp[0]);
            paszSet[0] = gpszPitchRel;
         }
         if (fVolAbs > CLOSE) {
            MMLDoubleToString (fVolAbs, aszTemp[1]);
            paszSet[1] = gpszVolAbs;
         }
         else if (fVolRel > CLOSE) {
            MMLDoubleToString (fVolRel, aszTemp[1]);
            paszSet[1] = gpszVolRel;
         }
         if (fDurAbs > CLOSE) {
            MMLDoubleToString (fDurAbs, aszTemp[2]);
            paszSet[2] = gpszDurAbs;
         }
         else if (fDurRel > CLOSE) {
            MMLDoubleToString (fDurRel, aszTemp[2]);
            paszSet[2] = gpszDurRel;
         }

         // parse text
         if (!TextParse.ParseFromMML (pSub, FALSE, FALSE))
            return FALSE;

         // parse the contents of this
         if (!SynthTextInterpretTags (pSub, pState, pVoiceMod, pfTransPros))
            return FALSE;

         // loop through and pull them out
         PCMMLNode2 pEmph;
         while (pSub->ContentNum()) {
            pEmph = NULL;
            psz = NULL;
            DWORD dwNum = pSub->ContentNum()-1;
            pSub->ContentEnum (dwNum, &psz, &pEmph);
            if (psz) {
               pNode->ContentInsert (i+1, psz);
               pSub->ContentRemove (dwNum);
               continue;
            }

            // else, remove from original but dont actually delete
            pSub->ContentRemove (dwNum, FALSE);

            // set the emphasis strings
            DWORD k;
            for (k = 0; k < 3; k++)
               if (paszSet[k])
                  pEmph->AttribSetString (paszSet[k], aszTemp[k]);

            // add
            pNode->ContentInsert (i+1, pEmph);
         } // while have sub elements

         // finally, remove this one
         pNode->ContentRemove (i);
         // dont need to do i-- since new node will have been evalatuated already

         continue;
      }
      else if (!_wcsicmp(psz, gpszPOS)) {
         // figure what kind of POS
         DWORD dwPOS = -1;
         PWSTR psz = pSub->AttribGetString (gpszMajor);
         DWORD j, k;
         if (psz) for (j = 0; j < POS_MAJOR_NUM; j++) for (k = 0; k < 2; k++) {
            PWSTR pszComp = (PWSTR)pLex->POSToString (POS_MAJOR_MAKE(j), k ? TRUE : FALSE);
            if (!pszComp)
               continue;
            if (!_wcsicmp(pszComp, psz)) {
               dwPOS = POS_MAJOR_MAKE(j);
               break;
            }
         }

         // make string to set
         WCHAR szTemp[16];
         _itow ((int)dwPOS, szTemp, 10);

         // parse text
         if (!TextParse.ParseFromMML (pSub, FALSE, FALSE))
            return FALSE;

         // parse the contents of this
         if (!SynthTextInterpretTags (pSub, pState, pVoiceMod, pfTransPros))
            return FALSE;

         // loop through and pull them out
         PCMMLNode2 pEmph;
         while (pSub->ContentNum()) {
            pEmph = NULL;
            psz = NULL;
            DWORD dwNum = pSub->ContentNum()-1;
            pSub->ContentEnum (dwNum, &psz, &pEmph);
            if (psz) {
               pNode->ContentInsert (i+1, psz);
               pSub->ContentRemove (dwNum);
               continue;
            }

            // else, remove from original but dont actually delete
            pSub->ContentRemove (dwNum, FALSE);

            psz = pEmph->NameGet();
            if (psz && (dwPOS != -1) && !_wcsicmp(psz,TextParse.Word()))
               pEmph->AttribSetString (TextParse.POS(), szTemp);


            // add
            pNode->ContentInsert (i+1, pEmph);
         } // while have sub elements

         // finally, remove this one
         pNode->ContentRemove (i);
         // dont need to do i-- since new node will have been evalatuated already

         continue;
      }
      else if (!_wcsicmp(psz, gpszTransPros)) {
         // parse text
         if (!TextParse.ParseFromMML (pSub, FALSE, FALSE))
            return FALSE;

         // found transplanted prosody
         *pfTransPros = TRUE;

         // parse the contents of this
         if (!SynthTextInterpretTags (pSub, pState, pVoiceMod, pfTransPros))
            return FALSE;

         // loop through and pull them out
         PCMMLNode2 pEmph;
         while (pSub->ContentNum()) {
            pEmph = NULL;
            psz = NULL;
            DWORD dwNum = pSub->ContentNum()-1;
            pSub->ContentEnum (dwNum, &psz, &pEmph);
            if (psz) {
               pNode->ContentInsert (i+1, psz);
               pSub->ContentRemove (dwNum);
               continue;
            }

            // if it's doesn't have a name or is origtext then just remove
            psz = pEmph->NameGet();
            if (!psz || !_wcsicmp(psz,  gpszOrigText)) {
               pSub->ContentRemove (dwNum);
               continue;
            }

            // if it's a word then look for a phoneme tag
            if (!_wcsicmp(psz, TextParse.Word())) {
               PWSTR pszPhoneme = pEmph->AttribGetString (gpszPh);
               BYTE abPhone[128];   // phonemes
               DWORD dwBadPhone;
               WCHAR szPron[sizeof(abPhone)*2+1];
               if (!pszPhoneme)
                  goto nophone;   // ignore this
               if (!pLex->PronunciationFromText (pszPhoneme, abPhone, sizeof(abPhone), &dwBadPhone))
                  goto nophone;   // error, so ignore

               // find the length
               DWORD dwLen = (DWORD)strlen((char*)abPhone);
               if (!dwLen)
                  goto nophone;

               // subtract one to each
               for (j = 0; j < dwLen; j++)
                  abPhone[j]--;

               // create a new word
               MMLBinaryToString (abPhone, dwLen, szPron);
               pEmph->AttribSetString (TextParse.Pronunciation(), szPron);
            } // if word look for phoneme
nophone:

            // else, remove from original but dont actually delete
            pSub->ContentRemove (dwNum, FALSE);

            // add
            pNode->ContentInsert (i+1, pEmph);
         } // while have sub elements

         // finally, remove this one
         pNode->ContentRemove (i);
         // dont need to do i-- since new node will have been evalatuated already

         continue;
      }
   } // i


   return TRUE;
}



/*************************************************************************************
CMTTS::XXXGet/Set - These functions access member variables of the tts voice
that might be remapped if use a derived tts voice.
*/
BOOL CMTTS::TriPhoneGroupGet (void)
{
   // if this is a derived voice pass on down
   if (m_fIsDerived) {
      if (!m_pTTSMaster)
         return FALSE;
      return m_pTTSMaster->TriPhoneGroupGet();
   }

   return m_dwTriPhoneGroup;
}

void CMTTS::TriPhoneGroupSet (DWORD dwTriPhoneGroup)
{
   if (m_fIsDerived)
      return;

   m_dwTriPhoneGroup = dwTriPhoneGroup;
}

BOOL CMTTS::KeepLogGet (void)
{
   // if this is a derived voice pass on down
   if (m_fIsDerived) {
      if (!m_pTTSMaster)
         return FALSE;
      return m_pTTSMaster->KeepLogGet();
   }
   return m_fKeepLog;
}

void CMTTS::KeepLogSet (BOOL fKeepLog)
{
   // if this is a derived voice pass on down
   if (m_fIsDerived)
      return;

   m_fKeepLog = fKeepLog;
}

BOOL CMTTS::FullPCMGet (void)
{
   // if this is a derived voice pass on down
   if (m_fIsDerived) {
      if (!m_pTTSMaster)
         return FALSE;
      return m_pTTSMaster->FullPCMGet();
   }

#ifdef NOMODS_DISABLEPCM
   return FALSE;
#endif

   return m_fFullPCM;
}


void CMTTS::TTSTARGETCOSTSSet (PTTSTARGETCOSTS pTarget)
{
   // if this is a derived voice pass on down
   if (m_fIsDerived)
      return;

   if (!m_memTTSTARGETCOSTS.Required (sizeof(TTSTARGETCOSTS)))
      return;
   m_memTTSTARGETCOSTS.m_dwCurPosn = sizeof(TTSTARGETCOSTS);
   memcpy (m_memTTSTARGETCOSTS.p, pTarget, sizeof(TTSTARGETCOSTS));
}


void CMTTS::FullPCMSet (BOOL fFullPCM)
{
   // if this is a derived voice pass on down
   if (m_fIsDerived)
      return;

   m_fFullPCM = fFullPCM;
}

fp CMTTS::AvgPitchGet (BOOL fFuji)
{
   if (m_lPCMTTSSubVoice.Num()) {
      // ask average pitch with sub-voices
      PCMTTSSubVoice *ppsv = (PCMTTSSubVoice*) m_lPCMTTSSubVoice.Get(0);
      fp fPitch = ppsv[0]->m_fAvgPitch;
      if (fFuji)
         fPitch = fPitch / m_fAvgPitch * m_fAvgPitchF0Fuji; // guess
      return fPitch;
   }

   return fFuji ? m_fAvgPitchF0Fuji : m_fAvgPitch;
}

fp CMTTS::AvgSyllableDurGet (void)
{
   // if this is a derived voice pass on down
   if (m_fIsDerived) {
      if (!m_pTTSMaster)
         return 0;
      return m_pTTSMaster->AvgSyllableDurGet();
   }

   return m_fAvgSyllableDur;
}


/*************************************************************************************
CMTTS::EnergyPerPitchSet - Takes an array of ENERGYPERPITCHNUM x SRDATAPOINT db values
for how much louder higher/lower pitch voiced SRFEATUREs are than one at the
average pitch.

inputs
   char           *pacEnergyPerPitch - As above
returns
   none
*/
void CMTTS::EnergyPerPitchSet (char *pacEnergyPerPitch)
{
   DWORD dwNeed = ENERGYPERPITCHNUM * SRDATAPOINTS;
   if (!m_memEnergyPerPitch.Required (dwNeed))
      return;

   memcpy (m_memEnergyPerPitch.p, pacEnergyPerPitch, dwNeed);
   m_memEnergyPerPitch.m_dwCurPosn = dwNeed;
}


/*************************************************************************************
CMTTS::EnergyPerPitchGet - Given a pitch (in hz), this returns a pointer to
SRDATAPOINTS chars indicating the number of dB to increase/decrease the pitch
by.

inputs
   fp       fPitch - Pitch in db
returns
   char * - Array of SRDATAPOINTS chars, or NULL if error
*/
char *CMTTS::EnergyPerPitchGet (fp fPitch)
{
#ifdef NOMODS_ENERGYPERPITCHGET
   return NULL;
#endif

   // if this is a derived voice pass on down
   if (m_fIsDerived) {
      if (!m_pTTSMaster)
         return NULL;
      return m_pTTSMaster->EnergyPerPitchGet(fPitch);
   }

   if (fPitch <= 0)
      return NULL;   // error

   fPitch = log(fPitch / (fp)ENERGYPERPITCHBASE) / log((fp)2) * (fp)ENERGYPERPITCHPOINTSPEROCTAVE + 0.5;
   fPitch = max(fPitch, 0);
   fPitch = min(fPitch, (fp)ENERGYPERPITCHNUM-1);
   DWORD dwIndex = (DWORD) fPitch;

   if (((dwIndex + 1) * SRDATAPOINTS) > m_memEnergyPerPitch.m_dwCurPosn)
      return NULL;   // out of range

   // else
   return (char*)m_memEnergyPerPitch.p + dwIndex * SRDATAPOINTS;
}


/*************************************************************************************
CMTTS::EnergyPerVolumeSet - Takes an array of ENERGYPERVOLUMENUM x SRDATAPOINT db values
for how much louder higher/lower volume voiced SRFEATUREs are than one at the
average volume.

inputs
   char           *pacEnergyPerVolume - As above
returns
   none
*/
void CMTTS::EnergyPerVolumeSet (char *pacEnergyPerVolume)
{
   DWORD dwNeed = ENERGYPERVOLUMENUM * SRDATAPOINTS;
   if (!m_memEnergyPerVolume.Required (dwNeed))
      return;

   memcpy (m_memEnergyPerVolume.p, pacEnergyPerVolume, dwNeed);
   m_memEnergyPerVolume.m_dwCurPosn = dwNeed;
}


/*************************************************************************************
CMTTS::EnergyPerVolumeGet - Given a volume (in hz), this returns a pointer to
SRDATAPOINTS chars indicating the number of dB to increase/decrease the volume
by.

inputs
   fp       fEnergyRatio - Ratio of how loud this unit is compared to the average
returns
   char * - Array of SRDATAPOINTS chars, or NULL if error
*/
char *CMTTS::EnergyPerVolumeGet (fp fEnergyRatio)
{
#ifdef NOMODS_ENERGYPERVOLUMEGET
   return NULL;
#endif

   // if this is a derived voice pass on down
   if (m_fIsDerived) {
      if (!m_pTTSMaster)
         return NULL;
      return m_pTTSMaster->EnergyPerVolumeGet(fEnergyRatio);
   }

   fEnergyRatio = max(fEnergyRatio, CLOSE);

   fEnergyRatio = log(fEnergyRatio) / log((fp)2) * (fp)ENERGYPERVOLUMEPOINTSPEROCTAVE + 0.5;
   fEnergyRatio += (fp)ENERGYPERVOLUMECENTER;
   fEnergyRatio = max(fEnergyRatio, 0.0);
   fEnergyRatio = min(fEnergyRatio, (fp)ENERGYPERVOLUMENUM-1);
   DWORD dwIndex = (DWORD) fEnergyRatio;

   if (((dwIndex + 1) * SRDATAPOINTS) > m_memEnergyPerVolume.m_dwCurPosn)
      return NULL;   // out of range

   // else
   return (char*)m_memEnergyPerVolume.p + dwIndex * SRDATAPOINTS;
}


void CMTTS::AvgPitchSet (fp fAvgPitch, fp fAvgPitchF0Fuji,
                         fp fFujiRangeLow, fp fFujiRangeMed, fp fFujiRangeHigh)
{
   m_fAvgPitch = max(fAvgPitch, 1);
   m_fAvgPitchF0Fuji = max(fAvgPitchF0Fuji, 1);
   m_fFujiRangeLow = fFujiRangeLow;
   m_fFujiRangeMed = fFujiRangeMed;
   m_fFujiRangeHigh = fFujiRangeHigh;
}

void CMTTS::LexFuncWordsSet (PCMLexicon pLex, DWORD dwBin)
{
   if (m_fIsDerived)
      return;

   if (dwBin >= NUMFUNCWORDGROUP)
      return;

   if (m_apLexFuncWord[dwBin])
      delete m_apLexFuncWord[dwBin];
   m_apLexFuncWord[dwBin] = pLex ? pLex->Clone() : NULL;
}

void CMTTS::AvgSyllableDurSet (fp fAvgSyllableDur)
{
   m_fAvgSyllableDur = max(fAvgSyllableDur, .01);
}

DWORD CMTTS::WordsPerMinuteGet (void)
{
   return m_dwWordsPerMinute;
}

void CMTTS::WordsPerMinuteSet (DWORD dwWordsPerMinute)
{
   m_dwWordsPerMinute = max (dwWordsPerMinute, 1);
}

PCMLexicon CMTTS::LexWordsGet (void)
{
   // if this is a derived voice pass on down
   if (m_fIsDerived) {
      if (!m_pTTSMaster)
         return NULL;
      return m_pTTSMaster->LexWordsGet();
   }

   return m_pLexWords;
}

PCMLexicon CMTTS::LexTrainingWordsGet (void)
{
   // if this is a derived voice pass on down
   if (m_fIsDerived) {
      if (!m_pTTSMaster)
         return NULL;
      return m_pTTSMaster->LexTrainingWordsGet();
   }

   return m_pLexTrainingWords;
}

BOOL CMTTS::LexWordsSet (PCMLexicon pNew)
{
   if (m_fIsDerived)
      return FALSE;

   if (m_pLexWords)
      delete m_pLexWords;
   m_pLexWords = pNew->Clone();

   return m_pLexWords ? TRUE : FALSE;
}

#if 0 // old prosody
BOOL CMTTS::LexWordEmphSet (PCMLexicon paLexWordEmph[NUMLEXWORDEMPH])
{
   if (m_fIsDerived)
      return FALSE;

   // transfer over the most common words for scaling
   DWORD i;
   for (i = 0; i < NUMLEXWORDEMPH; i++) {
      if (m_apLexWordEmph[i])
         delete m_apLexWordEmph[i];
      m_apLexWordEmph[i] = NULL;
      if (paLexWordEmph[i])
         m_apLexWordEmph[i] = paLexWordEmph[i]->Clone();
   }// i

   return TRUE;
}


BOOL CMTTS::LexFuncWordsSet (PCMLexicon pLexFuncWords)
{
   if (m_fIsDerived)
      return FALSE;

   if (m_pLexFuncWords)
      delete m_pLexFuncWords;
   m_pLexFuncWords = pLexFuncWords ? pLexFuncWords->Clone() : NULL;

   return TRUE;
}

BOOL CMTTS::LexWordEmphScaleSet (CPoint paLexWordEmphScale[NUMLEXWORDEMPH+1])
{
   if (m_fIsDerived)
      return FALSE;

   memcpy (m_apLexWordEmphScale, paLexWordEmphScale, sizeof(m_apLexWordEmphScale));
   return TRUE;
}

BOOL CMTTS::ProsWordEmphFromWordLength (CPoint paWordEmphFromWordLength[NUMPROSWORDLENGTH])
{
   memcpy (m_apProsWordEmphFromWordLength, paWordEmphFromWordLength, sizeof(m_apProsWordEmphFromWordLength));
   return TRUE;
}
#endif // 0

BOOL CMTTS::ProsPhoneEmph (WRDEMPH paPhoneEmph[NUMPHONEEMPH*2][PHONEPOSBIN])
{
   memcpy (m_apPhoneEmph, paPhoneEmph, sizeof(m_apPhoneEmph));
   return TRUE;
}


#if 0 // micropauses now in prosody model
PCMem CMTTS::MemMicroPauseGet (void)
{
   if (m_fIsDerived)
      return FALSE;

   return &m_memMicroPause;
}
#endif // 0


#if 0 // old prosody
PCMem CMTTS::MemNGramGet (void)
{
   if (m_fIsDerived)
      return FALSE;

   return &m_memNGram;
}
#endif // 0


PCTTSProsody CMTTS::TTSProsodyGet (void)
{
   // BUGFIX - Allow to get prosody, so can edit derived voice
   // if (m_fIsDerived)
   //   return NULL;

   return m_pCTTSProsody;
}



// NOTE: pAdd is kept by the TTS engine
#if 0 // old prosody
BOOL CMTTS::PunctProsAdd (PCTTSPunctPros pAdd)
{
   if (m_fIsDerived)
      return FALSE;

   m_lPCTTSPunctPros.Add (&pAdd);
   return TRUE;
}
#endif // 0, old prosody

BOOL CMTTS::IsDerivedGet (void)
{
   return m_fIsDerived;
}

void CMTTS::IsDerivedSet (BOOL fDerived)
{
   m_fIsDerived = fDerived;
}


PWSTR CMTTS::TTSMasterGet (void)
{
   if (!m_fIsDerived)
      return NULL;

   return m_szTTSMaster;
}

CMTTS * CMTTS::TTSMasterGet2 (void)
{
   return m_pTTSMaster;
}

// TTSMasterSet - Note: Tries to load the new master. If it cant then errors
// out. If it can, but is a derived voice then errors out.
BOOL CMTTS::TTSMasterSet (PWSTR pszMaster)
{
   if (!m_fIsDerived)
      return FALSE;

   PCMTTS pMaster = TTSCacheOpen (pszMaster, TRUE, m_fThreadSafe);
   if (!pMaster)
      return FALSE;
   if (pMaster->m_fIsDerived) {
      TTSCacheClose (pMaster);
      return FALSE;
   }

   // keep
   wcscpy (m_szTTSMaster, pszMaster);
   if (m_pTTSMaster)
      TTSCacheClose (m_pTTSMaster);
   m_pTTSMaster = pMaster;

   // remember new wpm and pitch
   m_dwWordsPerMinute = pMaster->m_dwWordsPerMinute;
   m_fAvgPitch = pMaster->m_fAvgPitch;
   m_fAvgSyllableDur = pMaster->m_fAvgSyllableDur;

   return TRUE;
}




/*************************************************************************************
CMTTS::GlottalPulseChange - This adjusts the voiced energy based on guestimates
for how it will change as the synthesized pitch is higher or lower than the recorded
pitch.

NOTE: These numbers were gotten experimenting with recordings of my voice.
As such, its not learned, but it should be prettly close for other voices.
At Pitch=0.66 of orig, need fund(1x) x .6, 2x x .8, 4x x 1, 8x x .6, 16x x .3, 32x x .2

BUGFIX - Put this in to hopeful improve the tts voice. It's noticable if
make it since, but it can't really be heard in speech, although some
changes can be seen in the energy spectrum.

inputs
   PSRFEATURE        psr - Feature to modify (voiced only). Modified in place
   fp                fOrigPitch - Original pitch (in Hz)
   fp                fNewPitch - New pitch (in Hz)
   BOOL              fIsVoiced - Set to TRUE if is voiced phoneme
returns
   none
*/
void CMTTS::GlottalPulseChange (PSRFEATURE psr, fp fOrigPitch, fp fNewPitch, BOOL fIsVoiced)
{
   // if it's not voiced then ignore
   if (!fIsVoiced)
      return;
   // what's the change
   fOrigPitch = max(fOrigPitch, 1);
   fNewPitch = max(fNewPitch, 1);
   fp fChange;

   // need average pitch in terms of this voice
   fChange = fNewPitch / fOrigPitch * m_fAvgPitch;
   char *pac = EnergyPerPitchGet (fChange);
   if (!pac)
      return;
   DWORD i;
   int iVal;
   for (i = 0; i < SRDATAPOINTS; i++) {
      iVal = (int)psr->acVoiceEnergy[i] + (int)pac[i];
      iVal = max(iVal, SRABSOLUTESILENCE);
      iVal = min(iVal, SRMAXLOUDNESS);
      psr->acVoiceEnergy[i] = (char)iVal;
   } // i

#if 0 // old code
   fChange = log(fNewPitch / fOrigPitch) / log(.66);

#define NUMPULSEBIN     6
   fp afScale[NUMPULSEBIN];
   afScale[0] = pow(.6, fChange);
   afScale[1] = pow(.8, fChange);
   afScale[2] = pow(1, fChange);
   afScale[3] = pow(.6, fChange);
   afScale[4] = pow(.3, fChange);
   afScale[5] = pow(.2, fChange);

   // normalize the scale so dont make it too loud/quiet
   DWORD i;
   fp fSum = 0;
   for (i = 0; i < NUMPULSEBIN; i++)
      fSum += afScale[i];
   fSum /= (fp)NUMPULSEBIN;
   if (fChange >= 0)
      fSum = 1;   // when go lower don't end up increasing volume
   for (i = 0; i < NUMPULSEBIN; i++) {
      afScale[i] /= fSum;

      // convert this to db
      afScale[i] = max(afScale[i], 0.001);
      afScale[i] = log10(afScale[i]) * 20.0;
   }

   // figure out frequencies where these occur
   fp afFreq[NUMPULSEBIN];
   for (i = 0; i < NUMPULSEBIN; i++) {
      afFreq[i] = fNewPitch * (fp)(1 << i);

      // convert into points on the SRFEATURE scale
      afFreq[i] = log(afFreq[i] / SRBASEPITCH) / log(2) * (fp)SRPOINTSPEROCTAVE;
   }

   // loop over the srdatapoints and interpolate
   DWORD dwLast = 0;
   for (i = 0; i < SRDATAPOINTS; i++) {
      int iScale;

      if ((fp)i <= afFreq[0])
         iScale = (int)afScale[0];
      else if ((fp)i >= afFreq[NUMPULSEBIN-1])
         iScale = (int)afScale[NUMPULSEBIN-1];
      else {
         while ((dwLast+1 < NUMPULSEBIN) && ((fp)i >= afFreq[dwLast+1]))
            dwLast++;

         fp fAlpha = ((fp)i - afFreq[dwLast]) / (afFreq[dwLast+1]-afFreq[dwLast]);
         iScale = (int)((1.0 - fAlpha) * afScale[dwLast] + fAlpha * afScale[dwLast+1]);
      }

      iScale += (int)psr->acVoiceEnergy[i];
      iScale = max(iScale, -127);
      iScale = min(iScale, 127);
      psr->acVoiceEnergy[i] = (char)iScale;
   } // i
#endif // 0
}


/*************************************************************************************
CMTTS::WordRank - This looks in m_apLexFuncWord's and figures out the word
rank for purposes of the prosody model.

inputs
   PWSTR          pszWord - Word
   BOOL           fUseHistory - If TRUE, also look in the history of recent words
                     as called by WordRankHistory(), to determine the rank
returns
   DWORD - RAnk, from 0 to NUMPROSODYMODELCOMMON-1. 0 being the most common.
*/
DWORD CMTTS::WordRank (PWSTR pszWord, BOOL fUseHistory)
{
   if (m_fIsDerived && m_pTTSMaster)
      return m_pTTSMaster->WordRank (pszWord, fUseHistory);

   // BUGFIX - If NULL word then return uncommon
   if (!pszWord)
      return NUMPROSODYMODELCOMMON-1;

   // look in each of the lexicons and determine a rank
   DWORD i;
   for (i = 0; i < NUMFUNCWORDGROUP; i++) {
      if (!m_apLexFuncWord[i])
         continue;

      if (m_apLexFuncWord[i]->WordExists (pszWord))
         return i / FUNCWORDGROUPPERPROSODYMODELCOMMON;
   } // i

   if (!fUseHistory)
      return NUMPROSODYMODELCOMMON-1;

   // else, didn't find, so look in the history
   for (i = 0; i < m_lWordRankHistory.Num(); i++)
      if (!_wcsicmp(pszWord, (PWSTR)m_lWordRankHistory.Get(i)))
         return NUMPROSODYMODELCOMMON-2;  // since spoke recently assume a slightly lower ranking

   // else, highest rank
   return NUMPROSODYMODELCOMMON-1;
}


/*************************************************************************************
CMTTS::WordRankHistory - Adds a word to the history of words recently spoken.
Any word that's recently spoken will be deemphasized.

inputs
   PWSTR          pszWord - Word. If this is a NULL string then clear the list
returns
   none
*/
#define NUMWORDRANKHISTORY    25       // number of non-function words kept in the word history
void CMTTS::WordRankHistory (PWSTR pszWord)
{
   if (m_fIsDerived && m_pTTSMaster)
      return m_pTTSMaster->WordRankHistory (pszWord);

   if (!pszWord) {
      m_lWordRankHistory.Clear();
      return;
   }

   // if this is already a common word then ignore
   if (WordRank (pszWord, FALSE) < NUMPROSODYMODELCOMMON-1)
      return;

   // if too many clear out
   while (m_lWordRankHistory.Num() >= NUMWORDRANKHISTORY)
      m_lWordRankHistory.Remove (0);

   // add this word
   m_lWordRankHistory.Add (pszWord, (wcslen(pszWord)+1)*sizeof(WCHAR));
}

/****************************************************************************
ProsodyModelInitPage - Call on init page to set text

inputs
   PCEscPage      pPage - Page
   WCHAR          aszProsodyTTS[NUMPROSODYTTS][256] - Prosody strings
*/
void ProsodyModelInitPage (PCEscPage pPage, WCHAR aszProsodyTTS[NUMPROSODYTTS][256])
{
   WCHAR szTemp[64];
   DWORD i;
   PCEscControl pControl;
   for (i = 0; i < NUMPROSODYTTS; i++) {
      swprintf (szTemp, L"prosodytts%d", (int) i);
      if (pControl = pPage->ControlFind (szTemp))
         pControl->AttribSet (Text(), aszProsodyTTS[i]);
   }

}

/****************************************************************************
ProsodyModelEdit - Trap button press calls for the prosody model.
Call this when get ESCN_BUTTONPRESS.

inputs
   PCMLexicon     pLexTTS - Lexicon with phonemes in it
   PCEscPage      pPage - Page
   PESCNBUTTONPRESS p - info
   WCHAR          aszProsodyTTS[NUMPROSODYTTS][256] - Prosody strings
   PCTTSProsody   pTTSProsody - To modify
returns
   BOOL - TRUE if trap, FALSE if should handle elsewhere
*/
BOOL ProsodyModelEdit (PCMLexicon pLexTTS, PCEscPage pPage, PESCNBUTTONPRESS p, WCHAR aszProsodyTTS[NUMPROSODYTTS][256],
                       PCTTSProsody pTTSProsody)
{
   PWSTR psz = p->pControl->m_pszName;
   if (!psz)
      return FALSE;

   PWSTR pszProsodyTTSOpen = L"prosodyttsopen";
   DWORD dwProsodyTTSOpenLen = (DWORD)wcslen(pszProsodyTTSOpen);
   if (_wcsnicmp(psz, pszProsodyTTSOpen, dwProsodyTTSOpenLen))
      return FALSE;  // not this

   DWORD dwNum = _wtoi(psz + dwProsodyTTSOpenLen);
   WCHAR szTemp[256];
   wcscpy (szTemp, aszProsodyTTS[dwNum]);

   if (TTSProsodyFileOpenDialog (pPage->m_pWindow->m_hWnd, szTemp, sizeof(szTemp)/sizeof(WCHAR), FALSE, TRUE))
      wcscpy (aszProsodyTTS[dwNum], szTemp);
   else
      aszProsodyTTS[dwNum][0] = 0;

   swprintf (szTemp, L"prosodytts%d", (int)dwNum);
   PCEscControl pControl;
   if (pControl = pPage->ControlFind (szTemp))
      pControl->AttribSet (Text(), aszProsodyTTS[dwNum]);

   // read and rebuild
   pTTSProsody->Clear();
   CMTTS TTSTemp;
   CTTSProsody TTSProsodyTemp;
   // TTSProsodyTemp.LexiconSet (pTTSProsody->LexiconGet());
   DWORD i;
   for (i = 0; i < NUMPROSODYTTS; i++) {
      psz = aszProsodyTTS[i];
      if (!psz[0])
         continue;   // empty

      BOOL fIsTTS = FALSE;
      DWORD dwLen = (DWORD)wcslen(psz);
      if ((dwLen >= 4) && !_wcsicmp(psz + (dwLen-4), L".tts"))
         fIsTTS = TRUE;

      PCTTSProsody pTemp = NULL;
      if (fIsTTS) {
         // get from tts voice
         if (TTSTemp.Open(psz, FALSE))
            pTemp = TTSTemp.TTSProsodyGet();
      }
      else {
         // get from prosody model
         if (TTSProsodyTemp.Open (psz))
            pTemp = &TTSProsodyTemp;
      }

      if (!pTemp)
         pPage->MBWarning ( psz,
            L"The TTS voice (or prosody model) used for additional prosody couldn't be opened.",
            MB_ICONEXCLAMATION | MB_OK);
      else
         pTTSProsody->Merge (pLexTTS, pTemp);
   } // i

   return TRUE;
}



/****************************************************************************
TTSModifiedMainPage
*/
static BOOL TTSModifiedMainPage (PCEscPage pPage, DWORD dwMessage, PVOID pParam)
{
   PTTSMMP pTTSMP = (PTTSMMP) pPage->m_pUserData;
   PCMTTS pVF = pTTSMP->pTTS;

   switch (dwMessage) {
   case ESCM_INITPAGE:
      {
         // SAPI settings
         PCEscControl pControl;
         ComboBoxSet (pPage, L"age", pVF->m_dwAge);
         ComboBoxSet (pPage, L"gender", pVF->m_dwGender);
         ComboBoxSet (pPage, L"langid", pVF->m_LangID);
         DoubleToControl (pPage, L"versionmajor", (double)pVF->m_dwVersionMajor);
         DoubleToControl (pPage, L"versionminor", (double)pVF->m_dwVersionMinor);
         DoubleToControl (pPage, L"versionbuild", (double)pVF->m_dwVersionBuild);
         if (pControl = pPage->ControlFind (L"sapitoken"))
            pControl->AttribSet (Text(), (PWSTR)pVF->m_memSAPIToken.p);
         if (pControl = pPage->ControlFind (L"sapiname"))
            pControl->AttribSet (Text(), (PWSTR)pVF->m_memSAPIName.p);
         if (pControl = pPage->ControlFind (L"sapivendor"))
            pControl->AttribSet (Text(), (PWSTR)pVF->m_memSAPIVendor.p);

         // enable/disable controls
         BOOL fHaveMain = (pVF->TTSMasterGet2() ? TRUE : FALSE);
         if (pControl = pPage->ControlFind (L"synth"))
            pControl->Enable (fHaveMain);

         // set some sample text
         pControl = pPage->ControlFind (L"testspeak");
         if (pControl)
            pControl->AttribSet (Text(), L"This is a test.");

         ProsodyModelInitPage (pPage, pVF->m_aszProsodyTTS);
      }
      break;

   case ESCN_COMBOBOXSELCHANGE:
      {
         PESCNCOMBOBOXSELCHANGE p = (PESCNCOMBOBOXSELCHANGE) pParam;
         if (!p->pControl || !p->pControl->m_pszName)
            break;
         PWSTR psz;
         DWORD dwVal;
         psz = p->pControl->m_pszName;
         dwVal = p->pszName ? _wtoi(p->pszName) : 0;

         // SAPI combo boxes
         if (!_wcsicmp(psz, L"age")) {
            if (pVF->m_dwAge != dwVal)
               pVF->m_dwAge = dwVal;
            return TRUE;
         }
         if (!_wcsicmp(psz, L"gender")) {
            if (pVF->m_dwGender != dwVal)
               pVF->m_dwGender = dwVal;
            return TRUE;
         }
         if (!_wcsicmp(psz, L"langid")) {
            if (pVF->m_LangID != (LANGID) dwVal)
               pVF->m_LangID = (LANGID) dwVal;
            return TRUE;
         }
         break;
      }

   case ESCN_EDITCHANGE:
      {
         PESCNEDITCHANGE p = (PESCNEDITCHANGE) pParam;
         if (!p->pControl || !p->pControl->m_pszName)
            break;
         PWSTR psz = p->pControl->m_pszName;

         // SAPI edit fields
         if (!_wcsicmp(psz, L"sapitoken")) {
            WCHAR szTemp[256];
            DWORD dwNeeded;
            szTemp[0] = 0;
            p->pControl->AttribGet (Text(), szTemp, sizeof(szTemp), &dwNeeded);
            MemZero (&pVF->m_memSAPIToken);
            MemCat (&pVF->m_memSAPIToken, szTemp);
            return TRUE;
         }
         if (!_wcsicmp(psz, L"sapiname")) {
            WCHAR szTemp[256];
            DWORD dwNeeded;
            szTemp[0] = 0;
            p->pControl->AttribGet (Text(), szTemp, sizeof(szTemp), &dwNeeded);
            MemZero (&pVF->m_memSAPIName);
            MemCat (&pVF->m_memSAPIName, szTemp);
            return TRUE;
         }
         if (!_wcsicmp(psz, L"sapivendor")) {
            WCHAR szTemp[256];
            DWORD dwNeeded;
            szTemp[0] = 0;
            p->pControl->AttribGet (Text(), szTemp, sizeof(szTemp), &dwNeeded);
            MemZero (&pVF->m_memSAPIVendor);
            MemCat (&pVF->m_memSAPIVendor, szTemp);
            return TRUE;
         }
         if (!_wcsicmp(psz, L"versionmajor")) {
            pVF->m_dwVersionMajor = (DWORD)DoubleFromControl (pPage, L"versionmajor");
            return TRUE;
         }
         if (!_wcsicmp(psz, L"versionminor")) {
            pVF->m_dwVersionMinor = (DWORD)DoubleFromControl (pPage, L"versionminor");
            return TRUE;
         }
         if (!_wcsicmp(psz, L"versionbuild")) {
            pVF->m_dwVersionBuild = (DWORD)DoubleFromControl (pPage, L"versionbuild");
            return TRUE;
         }
      }
      break;

   case ESCN_BUTTONPRESS:
      {
         PESCNBUTTONPRESS p = (PESCNBUTTONPRESS) pParam;

         if (ProsodyModelEdit (pVF->Lexicon(), pPage, p, pVF->m_aszProsodyTTS, pVF->TTSProsodyGet()))
            return TRUE;

         if (!p->pControl || !p->pControl->m_pszName)
            break;
         PWSTR psz;
         psz = p->pControl->m_pszName;

         PWSTR pszTestPlay = L"testplay", pszMoveUp = L"moveup", pszMoveDown = L"movedown",
            pszCopy = L"copy", pszDelete = L"delete", pszEditVoice = L"editvoice";
         DWORD dwTestPlayLen = (DWORD)wcslen(pszTestPlay), dwMoveUpLen = (DWORD)wcslen(pszMoveUp),
            dwMoveDownLen = (DWORD)wcslen(pszMoveDown), dwCopyLen = (DWORD)wcslen(pszCopy),
            dwDeleteLen = (DWORD)wcslen(pszDelete), dwEditVoiceLen = (DWORD)wcslen (pszEditVoice);

         if (!_wcsnicmp (psz, pszTestPlay, dwTestPlayLen)) {
            if (!pVF->TTSMasterGet2()) {
               pPage->MBWarning (L"You don't have a master TTS voice selected yet.");
               return TRUE;
            }

            DWORD dwNum = (DWORD)_wtoi(psz + dwTestPlayLen);

            WCHAR szTemp[256];
            szTemp[0] = 0;
            DWORD dwNeed;
            PCEscControl pControl = pPage->ControlFind (L"testspeak");
            if (!pControl)
               return TRUE;
            pControl->AttribGet (Text(), szTemp, sizeof(szTemp), &dwNeed);

            CProgress Progress;
            Progress.Start (pPage->m_pWindow->m_hWnd, "Synthesizing...");
            pTTSMP->pWave->QuickPlayStop();

            TTSVOICEMOD vm;
            PCMTTSSubVoice *ppsv = (PCMTTSSubVoice*) pVF->m_lPCMTTSSubVoice.Get(0);
            PCTTSProsody apTTSProsody[TTSVOICEMODMAXPROSODY];
            memset (apTTSProsody, 0, sizeof(apTTSProsody));
            apTTSProsody[0] = ppsv[dwNum]->m_pCTTSProsody;
            pVF->FillInVOICEMOD (&vm, ppsv[dwNum], apTTSProsody); // so uses current mod
            pVF->SynthGenWave (pTTSMP->pWave, pTTSMP->pWave->m_dwSamplesPerSec, szTemp, FALSE, 1 /*iTTSQuality*/, TRUE /* fDisablePCM */, &Progress, &vm);

            pTTSMP->pWave->QuickPlay();

            return TRUE;
         }

         if (!_wcsnicmp (psz, pszEditVoice, dwEditVoiceLen)) {
            if (!pVF->TTSMasterGet2()) {
               pPage->MBWarning (L"You don't have a master TTS voice selected yet.");
               return TRUE;
            }

            // allow to pass through
            pPage->Exit (psz);

            return TRUE;
         }

         if (!_wcsnicmp (psz, pszMoveUp, dwMoveUpLen)) {
            DWORD dwNum = (DWORD)_wtoi(psz + dwMoveUpLen);
            if (!dwNum)
               return TRUE;   // shouldnt happen

            PCMTTSSubVoice *ppsv = (PCMTTSSubVoice*) pVF->m_lPCMTTSSubVoice.Get(0);
            PCMTTSSubVoice pTemp = ppsv[dwNum-1];
            ppsv[dwNum-1] = ppsv[dwNum];
            ppsv[dwNum] = pTemp;

            pPage->Exit (RedoSamePage());
            return TRUE;
         }

         if (!_wcsnicmp (psz, pszMoveDown, dwMoveDownLen)) {
            DWORD dwNum = (DWORD)_wtoi(psz + dwMoveDownLen);
            if (dwNum+1 >= pVF->m_lPCMTTSSubVoice.Num())
               return TRUE;   // shouldnt happen

            PCMTTSSubVoice *ppsv = (PCMTTSSubVoice*) pVF->m_lPCMTTSSubVoice.Get(0);
            PCMTTSSubVoice pTemp = ppsv[dwNum+1];
            ppsv[dwNum+1] = ppsv[dwNum];
            ppsv[dwNum] = pTemp;

            pPage->Exit (RedoSamePage());
            return TRUE;
         }

         if (!_wcsnicmp (psz, pszCopy, dwCopyLen)) {
            if (pVF->m_lPCMTTSSubVoice.Num() >= MAXSUBVOICES) {
               pPage->MBInformation (L"You can't add any more sub voices.");
               return TRUE;
            }

            DWORD dwNum = (DWORD)_wtoi(psz + dwCopyLen);

            PCMTTSSubVoice *ppsv = (PCMTTSSubVoice*) pVF->m_lPCMTTSSubVoice.Get(0);
            PCMTTSSubVoice pTemp = ppsv[dwNum]->Clone();
            if (!pTemp)
               return TRUE;   // shouldnt happen
            pVF->m_lPCMTTSSubVoice.Add (&pTemp);

            pPage->Exit (RedoSamePage());
            return TRUE;
         }

         if (!_wcsnicmp (psz, pszDelete, dwDeleteLen)) {
            DWORD dwNum = (DWORD)_wtoi(psz + dwDeleteLen);

            if (IDYES != pPage->MBYesNo (L"Are you sure you wish to delete this sub-voice?",
               L"It will be permanently deleted."))
               return TRUE;

            PCMTTSSubVoice *ppsv = (PCMTTSSubVoice*) pVF->m_lPCMTTSSubVoice.Get(0);
            delete ppsv[dwNum];
            pVF->m_lPCMTTSSubVoice.Remove (dwNum);

            pPage->Exit (RedoSamePage());
            return TRUE;
         }

         if (!_wcsicmp(psz, L"sapiregister")) {
            if (!pVF->SAPIRegister()) {
               pPage->MBError (L"Couldn't register the voice with SAPI.",
                  L"This may be because you haven't filled in all the fields above. "
                  L"Or, it may have failed because you didn't run this application "
                  L"with \"Run as administrator\".");
               return TRUE;
            }

            // else, notify done
            pPage->MBInformation (L"Registered.");

            return TRUE;
         }

         if (!_wcsicmp(psz, L"newtts")) {
            WCHAR szTemp[256];
            wcscpy (szTemp, pVF->m_szFile);

            if (!TTSFileOpenDialog (pPage->m_pWindow->m_hWnd, szTemp, sizeof(szTemp)/sizeof(WCHAR), FALSE))
               return TRUE;

            // try setting it
            if (!pVF->TTSMasterSet (szTemp))
               pPage->MBWarning (L"The TTS voice couldn't be used.",
                  L"It may not be a master lexicon.");
            else
               pPage->Exit (RedoSamePage());
            return TRUE;
         }

         if (!_wcsicmp (psz, L"add")) {
            if (!pVF->TTSMasterGet2()) {
               pPage->MBWarning (L"You don't have a master TTS voice selected yet.");
               return TRUE;
            }

            if (pVF->m_lPCMTTSSubVoice.Num() >= MAXSUBVOICES) {
               pPage->MBInformation (L"You can't add any more sub voices.");
               return TRUE;
            }

            // make sure generic has the master's settings for pitch, wmp
            pVF->m_SubVoiceGeneric.m_fAvgPitch = pVF->TTSMasterGet2() ? pVF->TTSMasterGet2()->AvgPitchGet(FALSE) : pVF->AvgPitchGet(FALSE);
            //pVF->m_SubVoiceGeneric.m_fAvgSyllableDur = pVF->TTSMasterGet2() ? pVF->TTSMasterGet2()->AvgSyllableDurGet() : pVF->AvgSyllableDurGet();
            pVF->m_SubVoiceGeneric.m_dwWordsPerMinute = pVF->TTSMasterGet2() ? pVF->TTSMasterGet2()->WordsPerMinuteGet() : pVF->WordsPerMinuteGet();

            PCMTTSSubVoice pTemp = pVF->m_SubVoiceGeneric.Clone();
            MemZero (&pTemp->m_memName);
            MemCat (&pTemp->m_memName, L"New subvoice");
            pVF->m_lPCMTTSSubVoice.Add (&pTemp);

            pPage->Exit (RedoSamePage());
            return TRUE;
         }
         else if (!_wcsicmp (psz, L"import")) {
            // make sure have a master voice
            if (!pVF->TTSMasterGet2()) {
               pPage->MBWarning (L"You don't have a master TTS voice selected yet.");
               return TRUE;
            }

            pTTSMP->szTTSImport[0] = 0;
            if (pTTSMP->pTTSImport)
               delete pTTSMP->pTTSImport;
            if (!TTSFileOpenDialog (pPage->m_pWindow->m_hWnd, pTTSMP->szTTSImport, sizeof(pTTSMP->szTTSImport)/sizeof(WCHAR), FALSE))
               return TRUE;

            // see if exists...
            pTTSMP->pTTSImport = new CMTTS;
            if (!pTTSMP->pTTSImport)
               return TRUE;
            if (!pTTSMP->pTTSImport->Open (pTTSMP->szTTSImport, FALSE)) {
               delete pTTSMP->pTTSImport;
               pTTSMP->pTTSImport = NULL;
               pPage->MBWarning (L"The TTS voice file couldn't be opened.",
                  L"It may not be a proper TTS voice file.");
               return TRUE;
            }

            if (!pTTSMP->pTTSImport->IsDerivedGet()) {
               delete pTTSMP->pTTSImport;
               pTTSMP->pTTSImport = NULL;
               pPage->MBWarning (L"This isn't a modified TTS voice.",
                  L"It is a master TTS voice and can't be edited.");
               return TRUE;
            }

            // exit with import
            pPage->Exit (L"import");

            return TRUE;
         }

      }
      break;

   case ESCM_SUBSTITUTION:
      {
         PESCMSUBSTITUTION p = (PESCMSUBSTITUTION) pParam;

         if (!_wcsicmp(p->pszSubName, L"PAGETITLE")) {
            p->pszSubString = L"Modified TTS Voice main page";
            return TRUE;
         }
         else if (!_wcsicmp(p->pszSubName, L"SRFILE")) {
            p->pszSubString = pVF->m_szFile;
            return TRUE;
         }
         else if (!_wcsicmp(p->pszSubName, L"MASTERTTS")) {
            PWSTR psz = pVF->TTSMasterGet();
            p->pszSubString = (psz && psz[0] && pVF->TTSMasterGet2()) ? psz : L"NO MASTER TTS (you MUST choose one)";
            return TRUE;
         }
         else if (!_wcsicmp(p->pszSubName, L"SUBVOICES")) {
            MemZero (&gMemTemp);

            PCMTTSSubVoice *ppsv = (PCMTTSSubVoice*) pVF->m_lPCMTTSSubVoice.Get(0);
            PWSTR psz;
            DWORD i;
            for (i = 0; i < pVF->m_lPCMTTSSubVoice.Num(); i++) {
               PCMTTSSubVoice psv = ppsv[i];

               MemCat (&gMemTemp, L"<tr><td width=3><xChoiceButton name=editvoice");
               MemCat (&gMemTemp, (int)i);
               MemCat (&gMemTemp, L"><bold>Modify subvoice #");
               MemCat (&gMemTemp, (int)i+1);
               MemCat (&gMemTemp, L", ");
               psz = (PWSTR)psv->m_memName.p;
               MemCatSanitize (&gMemTemp, psz[0] ? psz : L"Unnamed");
               MemCat (&gMemTemp, L"</bold><br/>Press this to modify the sub-voice.</xChoiceButton>"
                  L"</td><td width=1 align=center>");
               if (i) {
                  MemCat (&gMemTemp, L"<button style=uptriangle name=moveup");
                  MemCat (&gMemTemp, (int)i);
                  MemCat (&gMemTemp, L"><bold>Move up</bold></button><br/>");
               }
               if (i+1 < pVF->m_lPCMTTSSubVoice.Num()) {
                  MemCat (&gMemTemp, L"<button style=downtriangle name=movedown");
                  MemCat (&gMemTemp, (int)i);
                  MemCat (&gMemTemp, L"><bold>Move down</bold></button><br/>");
               }
               MemCat (&gMemTemp, L"<button name=testplay");
               MemCat (&gMemTemp, (int)i);
               MemCat (&gMemTemp, L"><bold>Test speak</bold></button><br/><button name=copy");
               MemCat (&gMemTemp, (int)i);
               MemCat (&gMemTemp, L"><bold>Duplicate</bold></button><br/><button style=box color=#ff0000 name=delete");
               MemCat (&gMemTemp, (int)i);
               MemCat (&gMemTemp, L"><bold>Delete</bold></button></td></tr>");
            } // i

            if (!i)
               MemCat (&gMemTemp, L"<tr><td>You need to add one or more sub-voices.</td></tr>");


            p->pszSubString = (PWSTR)gMemTemp.p;
            return TRUE;
         }
      }
      break;



   };


   return DefPage (pPage, dwMessage, pParam);
}



/****************************************************************************
TTSModifiedImportPage
*/
static BOOL TTSModifiedImportPage (PCEscPage pPage, DWORD dwMessage, PVOID pParam)
{
   PTTSMMP pTTSMP = (PTTSMMP) pPage->m_pUserData;
   PCMTTS pVF = pTTSMP->pTTS;
   PCMTTS pVF2 = pTTSMP->pTTSImport;

   switch (dwMessage) {
   case ESCN_BUTTONPRESS:
      {
         PESCNBUTTONPRESS p = (PESCNBUTTONPRESS) pParam;

         if (!p->pControl || !p->pControl->m_pszName)
            break;
         PWSTR psz;
         psz = p->pControl->m_pszName;

         if (!_wcsicmp(psz, L"import")) {
            // make sure have a master for this
            PCMTTS pTTSMaster = pVF->TTSMasterGet2();
            if (!pTTSMaster) {
               // shouldnt happen
               pPage->MBWarning (L"You can't import because there's no master TTS voice.");
               return TRUE;
            }

            // make sure the imported voice has a master
            PCMTTS pTTSMaster2 = pVF2->TTSMasterGet2();
            if (!pTTSMaster2) {
               pPage->MBWarning (L"You can't import because there's no master TTS voice for the imported voices.");
               return TRUE;
            }

            // loop through all the possible sub-voices to import
            DWORD i;
            PCEscControl pControl;
            WCHAR szTemp[64];
            PCMTTSSubVoice *ppsv2 = (PCMTTSSubVoice*) pVF2->m_lPCMTTSSubVoice.Get(0);
            for (i = 0; i < pVF2->m_lPCMTTSSubVoice.Num(); i++) {
               PCMTTSSubVoice psv2 = ppsv2[i];

               // see what the combo box says
               swprintf (szTemp, L"to:%d", (int)i);
               pControl = pPage->ControlFind (szTemp);
               if (!pControl)
                  continue;   // shouldnt happen
               DWORD dwComboSel = pControl->AttribGetInt (CurSel());
               if (dwComboSel >= pVF->m_lPCMTTSSubVoice.Num()+2)
                  continue;   // shouldnt happen
               if (!dwComboSel)
                  continue;   // dont want to impoirt

               // if want to add, but not enough spaces then error
               if (dwComboSel < 2) {
                  if (pVF->m_lPCMTTSSubVoice.Num() >= MAXSUBVOICES) {
                     pPage->MBWarning (L"A voice wasn't added because there aren't any free slots.",
                        (PWSTR)psv2->m_memName.p);
                     continue;
                  }

                  // make sure generic has the master's settings for pitch, wmp
                  pVF->m_SubVoiceGeneric.m_fAvgPitch = pVF->TTSMasterGet2() ? pVF->TTSMasterGet2()->AvgPitchGet(FALSE) : pVF->AvgPitchGet(FALSE);
                  //pVF->m_SubVoiceGeneric.m_fAvgSyllableDur = pVF->TTSMasterGet2() ? pVF->TTSMasterGet2()->AvgSyllableDurGet() : pVF->AvgSyllableDurGet();
                  pVF->m_SubVoiceGeneric.m_dwWordsPerMinute = pVF->TTSMasterGet2() ? pVF->TTSMasterGet2()->WordsPerMinuteGet() : pVF->WordsPerMinuteGet();

                  PCMTTSSubVoice pTemp = pVF->m_SubVoiceGeneric.Clone();
                  MemZero (&pTemp->m_memName);
                  MemCat (&pTemp->m_memName, (PWSTR) psv2->m_memName.p);
                  pVF->m_lPCMTTSSubVoice.Add (&pTemp);

                  dwComboSel = pVF->m_lPCMTTSSubVoice.Num()-1;

               }
               else
                  dwComboSel -= 2;  // so have number
               PCMTTSSubVoice *ppsv = (PCMTTSSubVoice*) pVF->m_lPCMTTSSubVoice.Get(0);
               PCMTTSSubVoice psv = ppsv[dwComboSel];

               // get the checkboxes
               BOOL fCopySynth, fCopyProsody, fCopyPron;
               swprintf (szTemp, L"css:%d", (int)i);
               pControl = pPage->ControlFind (szTemp);
               fCopySynth = (pControl ? pControl->AttribGetBOOL(Checked()) : FALSE);
               swprintf (szTemp, L"cpr:%d", (int)i);
               pControl = pPage->ControlFind (szTemp);
               fCopyProsody = (pControl ? pControl->AttribGetBOOL(Checked()) : FALSE);
               swprintf (szTemp, L"cpu:%d", (int)i);
               pControl = pPage->ControlFind (szTemp);
               fCopyPron = (pControl ? pControl->AttribGetBOOL(Checked()) : FALSE);

               // copy over
               if (fCopySynth) {
                  psv2->m_VoiceDisguise.CloneTo (&psv->m_VoiceDisguise);

                  // make sure no pitch changes here
                  psv->m_VoiceDisguise.m_fPitchOrig = pTTSMaster->AvgPitchGet(FALSE);
                  psv->m_VoiceDisguise.m_fPitchScale = 1.0;
               }

               if (fCopyProsody) {
                  psv->m_fBlurPitch = psv2->m_fBlurPitch;
                  psv->m_fBlurVolume = psv2->m_fBlurVolume;
                  psv->m_fPhoneRiseAccentuate = psv2->m_fPhoneRiseAccentuate;
                  psv->m_fPhonePitchAccentuate = psv2->m_fPhonePitchAccentuate;
                  psv->m_pPOSAccentuate.Copy (&psv2->m_pPOSAccentuate);
                  psv->m_fDurWordStart = psv2->m_fDurWordStart;
                  psv->m_fDurWordEnd = psv2->m_fDurWordEnd;
                  psv->m_fMicroPauseThreshhold = psv2->m_fMicroPauseThreshhold;
                  psv->m_lDurPerPhone.Init (sizeof(float), psv2->m_lDurPerPhone.Get(0), psv2->m_lDurPerPhone.Num());
                  psv->m_fAvgPitch = psv2->m_fAvgPitch / pTTSMaster2->AvgPitchGet(FALSE) * pTTSMaster->AvgPitchGet(FALSE);
                  //psv->m_fAvgSyllableDur = m_fAvgSyllableDur;
                  psv->m_dwWordsPerMinute = (DWORD)((fp)psv2->m_dwWordsPerMinute / (fp)pTTSMaster2->WordsPerMinuteGet() *
                     (fp)pTTSMaster->WordsPerMinuteGet() + 0.5);

                  DWORD j;
                  for (j = 0; j < NUMPROSODYTTS; j++)
                     wcscpy (psv->m_aszProsodyTTS[j], psv2->m_aszProsodyTTS[j]);
                  psv2->m_pCTTSProsody->CloneTo (psv->m_pCTTSProsody);

               }

               if (fCopyPron) {
                  psv->LexiconSet (psv2->m_szLexicon);
                  psv->m_lTTSACCENTRULE.Init (sizeof(TTSACCENTRULE), psv2->m_lTTSACCENTRULE.Get(0), psv2->m_lTTSACCENTRULE.Num());
               }

            } // i

            pPage->Exit (L"import");
            return TRUE;
         } // import

      }
      break;

   case ESCM_SUBSTITUTION:
      {
         PESCMSUBSTITUTION p = (PESCMSUBSTITUTION) pParam;

         if (!_wcsicmp(p->pszSubName, L"PAGETITLE")) {
            p->pszSubString = L"Import sub-voices";
            return TRUE;
         }
         else if (!_wcsicmp(p->pszSubName, L"SRFILE")) {
            p->pszSubString = pVF2->m_szFile;
            return TRUE;
         }
         else if (!_wcsicmp(p->pszSubName, L"COMBOBOXSUB")) {
            MemZero (&gMemTemp);

            DWORD i;
            PCMTTSSubVoice *ppsv = (PCMTTSSubVoice*) pVF->m_lPCMTTSSubVoice.Get(0);
            for (i = 0; i < pVF->m_lPCMTTSSubVoice.Num(); i++) {
               PCMTTSSubVoice psv = ppsv[i];

               MemCat (&gMemTemp, L"<elem name=");
               MemCat (&gMemTemp, (int)i + 2);
               MemCat (&gMemTemp, L">");
               PWSTR psz = (PWSTR)psv->m_memName.p;
               MemCatSanitize (&gMemTemp, (psz && psz[0]) ? psz : L"Unnamed");
               MemCat (&gMemTemp, L" (");
               MemCat (&gMemTemp, (int)i + 1);
               MemCat (&gMemTemp, L")");
               MemCat (&gMemTemp, L"</elem>");
            } // i

            p->pszSubString = (PWSTR)gMemTemp.p;
            return TRUE;
         }
         else if (!_wcsicmp(p->pszSubName, L"SUBVOICES")) {
            MemZero (&gMemTemp);

            PCMTTSSubVoice *ppsv = (PCMTTSSubVoice*) pVF2->m_lPCMTTSSubVoice.Get(0);
            DWORD i;
            for (i = 0; i < pVF2->m_lPCMTTSSubVoice.Num(); i++) {
               PCMTTSSubVoice psv = ppsv[i];

               MemCat (&gMemTemp, L"<tr>");

               // name
               MemCat (&gMemTemp, L"<td>");
               PWSTR psz = (PWSTR)psv->m_memName.p;
               MemCatSanitize (&gMemTemp, (psz && psz[0]) ? psz : L"Unnamed");
               MemCat (&gMemTemp, L" (");
               MemCat (&gMemTemp, (int)i + 1);
               MemCat (&gMemTemp, L")");
               MemCat (&gMemTemp, L"</td>");

               // copy to
               MemCat (&gMemTemp, L"<td><xComboBoxSub width=100% name=to:");
               MemCat (&gMemTemp, (int)i);
               MemCat (&gMemTemp, L"/></td>");

               // check box for copy synth settings
               MemCat (&gMemTemp, L"<td align=center><button style=x checkbox=true checked=true name=css:");
               MemCat (&gMemTemp, (int)i);
               MemCat (&gMemTemp, L"/></td>");

               // check box for copy prosody
               MemCat (&gMemTemp, L"<td align=center><button style=x checkbox=true checked=true name=cpr:");
               MemCat (&gMemTemp, (int)i);
               MemCat (&gMemTemp, L"/></td>");

               // check box for copy punct
               MemCat (&gMemTemp, L"<td align=center><button style=x checkbox=true checked=true name=cpu:");
               MemCat (&gMemTemp, (int)i);
               MemCat (&gMemTemp, L"/></td>");

               MemCat (&gMemTemp, L"</tr>");

            } // i

            if (!i)
               MemCat (&gMemTemp, L"<tr><td>This modified voice doesn't have any sub-voices.</td></tr>");


            p->pszSubString = (PWSTR)gMemTemp.p;
            return TRUE;
         }
      }
      break;



   };


   return DefPage (pPage, dwMessage, pParam);
}


/*************************************************************************************
CMTTS::DialogDerivedMain - Brings up the derived-voice editing dialog

inputs
   PCEscWindow          pWindow - window to use
returns
   BOOL - TRUE if pressed back, FALSE if close
*/
BOOL CMTTS::DialogDerivedMain (PCEscWindow pWindow)
{
   BOOL fRet = TRUE;
   if (!m_fIsDerived)
      return FALSE;

   PWSTR pszRet;

   CM3DWave Wave;
   Wave.ConvertSamplesAndChannels (22050, 1, NULL);

   PWSTR pszEditVoice = L"editvoice";
   DWORD dwEditVoiceLen = (DWORD)wcslen(pszEditVoice);

   TTSMMP tm;
   memset (&tm, 0, sizeof(tm));
   tm.pTTS = this;
   tm.pWave = &Wave;

redo:
   pszRet = pWindow->PageDialog (ghInstance, IDR_MMLTTSMODIFIEDMAIN, TTSModifiedMainPage, &tm);
   if (!pszRet) {
      fRet = FALSE;
      goto done;
   }
   if (!_wcsicmp(pszRet, Back())) {
      fRet = TRUE;
      goto done;
   }
   else if (!_wcsicmp(pszRet, RedoSamePage()))
      goto redo;
   else if (!_wcsnicmp (pszRet, pszEditVoice, dwEditVoiceLen)) {
      DWORD dwNum = (DWORD)_wtoi (pszRet + dwEditVoiceLen);
      PCMTTSSubVoice *ppsv = (PCMTTSSubVoice*) m_lPCMTTSSubVoice.Get(0);

      if (ppsv[dwNum]->Dialog (pWindow, this))
         goto redo;
      else {
         fRet = FALSE;
         goto done;
      }
   }
   else if (!_wcsicmp (pszRet, L"import")) {
      pszRet = pWindow->PageDialog (ghInstance, IDR_MMLTTSMODIFIEDIMPORT, TTSModifiedImportPage, &tm);
      if (!pszRet) {
         fRet = FALSE;
         goto done;
      }
      else if (!_wcsicmp(pszRet, L"import"))
         goto redo;
      else {
         fRet = FALSE;
         goto done;
      }
   }

   fRet = FALSE;
   // fall through

done:
   if (tm.pTTSImport)
      delete tm.pTTSImport;

   return fRet;
}

/*************************************************************************************
CMTTS::MakeSubVoice - Given a PCMMLNode2 for the <SubVoice> node, this makes
up a mixed voice object that combines all the given voices.

inputs
   PCMMLNode2        pNode - SubVoice node. Can be NULL.
   BOOL              *pfMustDelete - Set to TRUE if the caller must delete the
                     subvoice object when it's done
   PCTTSProsody      *papCTTSProsody - Pointer to an array of TTSVOICEMODMAXPROSODY prosody
                     elements that will be filled in as this makes the subvoice. Some
                     may be left NULL. Should initially be cleared
returns
   PCMTTSSubVoice - Subvoice to use. If *pfMustDelete is TRUE then it must be deleted.
                     NOTE: This always returns something.

*/
PCMTTSSubVoice CMTTS::MakeSubVoice (PCMMLNode2 pNode, BOOL *pfMustDelete, PCTTSProsody *papCTTSProsody)
{
   *pfMustDelete = FALSE; // for now

   // if there are no sub-voices then return general
   DWORD dwNum = m_lPCMTTSSubVoice.Num();
   PCMTTSSubVoice *ppsv = (PCMTTSSubVoice*) m_lPCMTTSSubVoice.Get(0);
   if (!dwNum) {
      m_SubVoiceGeneric.m_fAvgPitch = m_pTTSMaster ? m_pTTSMaster->AvgPitchGet(FALSE) : AvgPitchGet(FALSE);
      //m_SubVoiceGeneric.m_fAvgSyllableDur = m_pTTSMaster ? m_pTTSMaster->AvgSyllableDurGet() : AvgSyllableDurGet();
      m_SubVoiceGeneric.m_dwWordsPerMinute = m_pTTSMaster ? m_pTTSMaster->WordsPerMinuteGet() : WordsPerMinuteGet();
      // NOTE: Dont bother setting papCTTSProsody since using generic subvoice
      return &m_SubVoiceGeneric;
   }

   // if there's only one then return that
   if (dwNum < 2) {
      papCTTSProsody[0] = ppsv[0]->m_pCTTSProsody;
      return ppsv[0];
   }

   // see if specified a sub-voice
   int iSubVoice;
   if (pNode->AttribGetInt (L"subvoice", &iSubVoice)) {
      DWORD dw = (DWORD)(iSubVoice-1) % dwNum;
      papCTTSProsody[0] = ppsv[dw]->m_pCTTSProsody;

      // modulo if too hight
      return ppsv[dw];
   }

   // else, get the other values
   PWSTR apszMix[3] = {L"mixvoice", L"mixpros", L"mixpron"};
   DWORD dwMix;
   CListFixed alMix[3], alMixWeight[3];
   DWORD dwProsMix = 0;
   for (dwMix = 0; dwMix < 3; dwMix++) {
      alMix[dwMix].Init (sizeof(PCMTTSSubVoice));
      alMixWeight[dwMix].Init (sizeof(fp));

      // get the value
      if (!pNode->AttribGetInt (apszMix[dwMix], &iSubVoice))
         iSubVoice = 0; // default
      DWORD dwValue = (DWORD) iSubVoice;
      if (!dwValue)
         dwValue = 1;
      alMix[dwMix].Init (sizeof(PCMTTSSubVoice));

      // repeat, adding while have mix
      DWORD dwCount = 0;
      fp *pf;
      DWORD i;
      for (; dwValue; dwValue /= 10) {
         DWORD dwVoice = ((dwValue%10)-1) % dwNum;
         PCMTTSSubVoice pVoice = ppsv[dwVoice];

         // if mixing prosody, and not still not too many subvoices, then add
         if ((dwMix == 1) && (dwProsMix < TTSVOICEMODMAXPROSODY)) {
            papCTTSProsody[dwProsMix] = pVoice->m_pCTTSProsody;
            dwProsMix++;
         }

         // see if it's already on the list
         PCMTTSSubVoice *ppSV = (PCMTTSSubVoice*) alMix[dwMix].Get(0);
         for (i = 0; i < alMix[dwMix].Num(); i++)
            if (ppSV[i] == pVoice)
               break;
         if (i < alMix[dwMix].Num()) {
            // already found, just increase the weight and go
            pf = (fp*) alMixWeight[dwMix].Get(i);
            *pf = *pf + 1;
            dwCount++;
            continue;
         }

         // else, add
         alMix[dwMix].Add (&pVoice);
         fp f = 1;
         alMixWeight[dwMix].Add (&f);
         dwCount++;
      }  // loop over digits

      // normalize
      pf = (fp*) alMixWeight[dwMix].Get(0);
      for (i = 0; i < alMixWeight[dwMix].Num(); i++)
         pf[i] /= (fp)dwCount;
   } // dwMix

   // create a new one
   PCMTTSSubVoice pNew = new CMTTSSubVoice;
   if (!pNew)
      return &m_SubVoiceGeneric; // error, so just return generic
   if (!pNew->MixTogether (alMix[0].Num(), (PCMTTSSubVoice*)alMix[0].Get(0), (fp*)alMixWeight[0].Get(0),
      alMix[1].Num(), (PCMTTSSubVoice*)alMix[1].Get(0), (fp*)alMixWeight[1].Get(0),
      alMix[2].Num(), (PCMTTSSubVoice*)alMix[2].Get(0), (fp*)alMixWeight[2].Get(0))) {
         delete pNew;
         return &m_SubVoiceGeneric; // error, so just return generic
      }

   // else, done
   *pfMustDelete = TRUE;
   return pNew;
}



/*************************************************************************************
CMTTS::FindSubVoice - Given a PCMMLNode2, this finds a <subvoice> node hidden within.

inputs
   PCMMLNode2        pNode - Node
returns
   PCMMLNode2 - Subvoice, or NULL if cant find
*/
PCMMLNode2 CMTTS::FindSubVoice (PCMMLNode2 pNode)
{
   DWORD i;
   PWSTR psz;
   PCMMLNode2 pSub;
   for (i = 0; i < pNode->ContentNum(); i++) {
      pSub = NULL;
      pNode->ContentEnum (i, &psz, &pSub);
      if (!pSub)
         continue;

      psz = pSub->NameGet();
      if (psz && !_wcsicmp(psz, L"subvoice"))
         return pSub;   // found

      // else, recurse
      pSub = FindSubVoice (pSub);
      if (pSub)
         return pSub;
   } // i

   // else, nothing
   return NULL;
}




/*************************************************************************************
CMTTS::JoinCompare - Compares two audio segments to see how well they join.

inputs
   PSRANALBLOCK            pAComp - A block, compressed points
   PSRANALBLOCK            pBComp - B block, compressed points
   DWORD                   dwCompressA - Amount of compression in A. 1 for every sample, 2 for skipping every other, etc.
   DWORD                   dwCompressB - COmpression in B.
   DWORD                   dwNumAComp - Number of entries in the A block, compressed points
   DWORD                   dwNumBComp - Number of entries in the B block, compressed points
   DWORD                   dwCenterAUncomp - Center point in the A block, uncompressed points
   DWORD                   dwCenterBUncomp - Center point in the B block, uncompressed points
   DWORD                   dwHalfWindow - Number of SRFEATURES in a half window, uncompressed points
   int                     iTTSQuality - 0 (or below) will compare only two features,
                              1 compares every third feature,
                              2 will compare every other feature,
                              3 (and above) will compare every feature
                              // not used because no real difference 4 (and above) will compare every feature AND allow spectrum stretches
returns
   fp - Score. Lower is better match. Has units of time (but not energy).
*/
fp CMTTS::JoinCompare (PSRANALBLOCK pAComp, PSRANALBLOCK pBComp,
                       DWORD dwCompressA, DWORD dwCompressB, DWORD dwNumAComp, DWORD dwNumBComp,
                       DWORD dwCenterAUncomp, DWORD dwCenterBUncomp, DWORD dwHalfWindow,
                       int iJoinCompareQuality)
{
   // if same then no error
   if ((pAComp == pBComp) && (dwCenterAUncomp == dwCenterBUncomp))
      return 0.0;

   DWORD dwSkip;
   if (iJoinCompareQuality <= 0)
      dwSkip = dwHalfWindow;
   else if (iJoinCompareQuality == 1)
      dwSkip = 3;
   else if (iJoinCompareQuality == 2)
      dwSkip = 2;
   else
      dwSkip = 1;
//   BOOL fStretch = (iJoinCompareQuality >= 4);

   // memory
   int iMemoryA = -100, iMemoryB = -100;
   fp fMemoryCompare = 0;

   // loop
   DWORD dwPass, dwRight, dwOffset;
   int iCurA, iCurB;
   fp fWeight;
   fp fEnergySumA = 0.0, fEnergySumB = 0.0;
   fp fWeightSum = 0.0;
   fp fScoreSum = 0.0;
   fp fMainScalePenalty;
   fp fScaleBToA, fEnergyBScaledToA, fEnergyA, fCompare, fDbA, fDbB, fMaxDb;
   for (dwPass = 0; dwPass < 2; dwPass++) {
      for (dwRight = 0; dwRight < 2; dwRight++) {
         for (dwOffset = 0; dwOffset < dwHalfWindow; dwOffset += dwSkip) {
            if (dwRight) {
               iCurA = ((int)dwCenterAUncomp + (int)dwOffset + (int)dwCompressA / 2) / (int)dwCompressA;
               iCurA = min(iCurA, (int)dwNumAComp-1);  // so if beyond edge, use last one
               iCurB = ((int)dwCenterBUncomp + (int)dwOffset + (int)dwCompressB / 2) / (int)dwCompressB;
               iCurB = min(iCurB, (int)dwNumBComp-1);  // so if beyond edge, use last one
            }
            else {
               iCurA = ((int)dwCenterAUncomp - (int)dwOffset - 1 + (int)dwCompressA / 2) / (int)dwCompressA;
               iCurA = max(iCurA, 0);  // so if beyond edge, use first one
               iCurB = ((int)dwCenterBUncomp - (int)dwOffset - 1 + (int)dwCompressB / 2) / (int)dwCompressB;
               iCurB = max(iCurB, 0);  // so if beyond edge, use first one
            }

            fWeight = (fp)(dwHalfWindow - dwOffset) / (fp)dwHalfWindow;

            if (dwPass) {
               // if already calculated this then reuse the calculation
               if ((iCurA == iMemoryA) && (iCurB == iMemoryB)) {
                  fCompare = fMemoryCompare;
               }
               else {
                  // need to calc

                  // first, figure out energy difference
                  fEnergyA = pAComp[iCurA].fEnergy;
                  fEnergyBScaledToA = pBComp[iCurB].fEnergy * fScaleBToA;
                  fEnergyA = max(fEnergyA, CLOSE);
                  fEnergyBScaledToA = max(fEnergyBScaledToA, CLOSE);

                  // comparing individual frames
                  fDbA = AmplitudeToDb(fEnergyA / THEORETICALMAXENERGY * (fp)0x8000);
                  fDbB = AmplitudeToDb(fEnergyBScaledToA / THEORETICALMAXENERGY * (fp)0x8000);

   //               if (fStretch)
   //                  fCompare = SRFEATUREDistortCompare (TRUE,
   //                     &pA[iCurA].sr, pA[iCurA].fEnergyAfterNormal,
   //                     &pB[iCurB].sr, pB[iCurB].fEnergyAfterNormal);
   //               else
                     fCompare = SRFEATURECompareSmall (TRUE,
                        &pAComp[iCurA].sr, /* pAComp[iCurA].fEnergyAfterNormal, */
                        &pBComp[iCurB].sr/*,  pBComp[iCurB].fEnergyAfterNormal */ );

                  // BUGFIX - Just in case decides to get really wild, which does if very large energies
                  fCompare = max (fCompare, 0.0);
                  fCompare = min (fCompare, 1.0);
                  
                  // penalty for dB error
                  fMaxDb = max(fDbA, fDbB);
                  fMaxDb = 1 + fMaxDb/((fp)-SRNOISEFLOOR);   // so that at -60 decibles max, fCompare won't count for anything
                  fMaxDb = max(fMaxDb, 0);
                  fMaxDb = min(fMaxDb, 1);
                  
                  fCompare = (fCompare * SRCOMPAREWEIGHT + fabs(fDbA - fDbB)) * fMaxDb;
                  fCompare += fMainScalePenalty;

                  // remember this
                  fMemoryCompare = fCompare;
                  iMemoryA = iCurA;
                  iMemoryB = iCurB;
               }

               fScoreSum += fCompare * fWeight;
            }
            else {
               // calculating the average energy
               fWeightSum += fWeight;
               fEnergySumA += pAComp[iCurA].fEnergy * fWeight;
               fEnergySumB += pBComp[iCurB].fEnergy * fWeight;
            }
         } // dwOffset
      } // dwRight

      if (dwPass) {
         fScoreSum /= fWeightSum;
      }
      else {
         // calculating the average energy
         fEnergySumA /= fWeightSum;
         fEnergySumB /= fWeightSum;
         fEnergySumA = max(fEnergySumA, CLOSE);
         fEnergySumB = max(fEnergySumB, CLOSE);

         fScaleBToA = fEnergySumA / fEnergySumB;

         // overall penalty by scaling from A to B
         fMainScalePenalty = fabs(log10(fScaleBToA) * 20.0) / 2.0;
            // halve so not as much penality when overall are quieter or louder
      }
   } // dwPass

   // done
   return fScoreSum * (fp)dwHalfWindow;   // only doing one half window since supposed to represent triangle
                                          // with ampltiude of 1 at the center point
}


/*************************************************************************************
CMTTS::JoinFindBest - Finds the best joins between two wave segments.

inputs
   CRITICAL_SECTION  *pCSA, *pCSB - Critical section to use for each sentence. Can be NULL.
   DWORD             dwSentenceNumA - A sentence
   DWORD             dwSentenceNumB - B sentence
   DWORD             dwFeatureCenterA - Where join is centered in A
   DWORD             dwFeatureCenterB - Where join is centered in B
   DWORD             dwRangeA - Amount to left/right of join to look for a match
   DWORD             dwRangeB - Amount to left/right of a join to look for a match
   DWORD             dwIdealA - Theoretical ideal join point for A - from trimming ends of unit
   DWORD             dwIdealB - Theoretical ideal join point for B - from trimming ends of unit
   DWORD             dwHalfWindow - Number of units in half a window.
   int               iBestCompareQuality - 0 (or below) then only compares dwIdealA and dwIdealB
                        1 - Compares every 3rd feature
                        2 - Compare every other feature
                        3+ - Compare all features
   int               iJoinCompareQuality - Passed into JoinCompare().
   DWORD             *pdwBestA - Filled with the best join for A
   DWORD             *pdwBestB - Filled with the best join for B
returns
   fp - Best score, as per JoinCompare().
*/
fp CMTTS::JoinFindBest (CRITICAL_SECTION *pCSA, CRITICAL_SECTION *pCSB, DWORD dwSentenceNumA, DWORD dwSentenceNumB,
                        DWORD dwFeatureCenterA, DWORD dwFeatureCenterB,
                        DWORD dwRangeA, DWORD dwRangeB, DWORD dwIdealA, DWORD dwIdealB, DWORD dwHalfWindow,
                        int iBestCompareQuality, int iJoinCompareQuality,
                        DWORD *pdwBestA, DWORD *pdwBestB)
{
   *pdwBestA = dwFeatureCenterA; // as defaults
   *pdwBestB = dwFeatureCenterB;
   // if they're the same wave and same centers then always use that
   if ((dwSentenceNumA == dwSentenceNumB) && (dwFeatureCenterA == dwFeatureCenterB))
      return 0.0;

   // get both features
   DWORD dwB;
   PSRANALBLOCK apABComp[2];
   DWORD adwNumUncomp[2], adwNumComp[2], adwCompress[2];
   int aiRange[2][2];   // range [dwB][0 for left, 1 for right, exclusive]
   int aiIdeal[2];
   int aiDelta[2];

   for (dwB = 0; dwB < 2; dwB++) {
      PCTTSWave pTW = TTSFindWave (dwB ? dwSentenceNumB : dwSentenceNumA);
      if (!pTW)
         return 0.0; // cant find the wave

      DWORD dwCenter = dwB ? dwFeatureCenterB : dwFeatureCenterA;
      DWORD dwPrior, dwAfter, dwCompress, dwPriorComp, dwAfterComp, dwTotalComp;

      int iStart = (int)dwCenter - (dwB ? 0 : 1);
      iStart = max(iStart, 0);

      PSRANALBLOCK pAB = pTW->GetSRANALBLOCK (dwB ? pCSB : pCSA, (DWORD)iStart, (DWORD)iStart+1, &dwPrior, &dwAfter, NULL,
         &dwCompress, &dwPriorComp, &dwAfterComp, &dwTotalComp);
      if (!pAB)
         return 0.0; // can't get this

      aiDelta[dwB] = iStart - (int)dwPrior;

      // else, have them, so fill info
      apABComp[dwB] = pAB - dwPriorComp;
      adwNumUncomp[dwB] = dwPrior + dwAfter + 1;   // since asked for dwCenter to dwCenter+1
      adwNumComp[dwB] = dwTotalComp;
      adwCompress[dwB] = dwCompress;

      aiRange[dwB][0] = (int)(dwB ? dwFeatureCenterB : dwFeatureCenterA) - (int)(dwB ? dwRangeB : dwRangeA) - aiDelta[dwB];
      aiRange[dwB][0] = max(aiRange[dwB][0], 0);

      aiRange[dwB][1] = (int)(dwB ? dwFeatureCenterB : dwFeatureCenterA) + (int)(dwB ? dwRangeB : dwRangeA) + 1 - aiDelta[dwB];
      aiRange[dwB][1] = min(aiRange[dwB][1], (int)adwNumUncomp[dwB]);

      // BUGFIX - Must have at least 1
      if ((aiRange[dwB][1] <= aiRange[dwB][0]) && aiRange[dwB][0])
         aiRange[dwB][0]--;

      aiIdeal[dwB] = (int)(dwB ? dwIdealB : dwIdealA) - aiDelta[dwB];
      aiIdeal[dwB] = max(aiIdeal[dwB], aiRange[dwB][0]);
      aiIdeal[dwB] = min(aiIdeal[dwB], aiRange[dwB][1] - 1);

      // if fast compare quality then just to the ideal
      if (iBestCompareQuality <= 0)
         aiRange[dwB][0] = max(aiRange[dwB][0], aiIdeal[dwB]);
   } // dwB

   // how much to jump
   int iDelta;
   if (iBestCompareQuality <= 0)
      iDelta = 10000;   // a large number
   else if (iBestCompareQuality == 1)
      iDelta = 3;
   else if (iBestCompareQuality == 2)
      iDelta = 2;
   else
      iDelta = 1;

   // loop
   int iA, iB;
   BOOL fFoundBest = FALSE;
   fp fRet = 0;
   fp fScore;
   for (iA = aiRange[0][0]; iA <= aiRange[0][1]; iA += iDelta)
      for (iB = aiRange[1][0]; iB <= aiRange[1][1]; iB += iDelta) {
         fScore = JoinCompare (apABComp[0], apABComp[1], adwCompress[0], adwCompress[1],
            adwNumComp[0], adwNumComp[1], iA, iB, dwHalfWindow, iJoinCompareQuality);

         if (!fFoundBest || (fScore < fRet)) {
            fRet = fScore;
            *pdwBestA = (DWORD)(iA + aiDelta[0]);
            *pdwBestB = (DWORD)(iB + aiDelta[1]);
            fFoundBest = TRUE;
         }
      } // iA, iB

   // if didn't find best then use defaults
   if (!fFoundBest) {
      *pdwBestA = (DWORD)((int)dwIdealA + aiDelta[0]);
      *pdwBestB = (DWORD)((int)dwIdealB + aiDelta[1]);
   }

   return fRet;
}


                        
/*************************************************************************************
CMTTS::SAPIRegister - Registers this voice for SAPI.

NOTE: This registers in both the 32-bit and 64-bit registry

inputs
   none
returns
   BOOL - TRUE if success, FALSE if error
*/
BOOL CMTTS::SAPIRegister (void)
{
   HKEY  hKey;
   DWORD dwDisp;
   WCHAR szLangID[16];
   PWSTR psz;

   swprintf (szLangID, L"%x", (int)m_LangID);

   // make sure have SAPIToken and SAPIName
   if (!((PWSTR)m_memSAPIToken.p)[0] || !((PWSTR)m_memSAPIName.p)[0] || !m_szFile[0])
      return FALSE;

   CMem memBase;
   // see if running on 64-bit
   DWORD dwMemBits = TTSWorkEnoughMemory (1000000);
   DWORD dw64 = ((sizeof(PVOID) > sizeof(DWORD)) || !(dwMemBits & 0x01)) ? 2 : 1;
   DWORD dwWinVersion;

   // want to register on both 32 bit and 64 bit registries
   for (dwWinVersion = 0; dwWinVersion < dw64; dwWinVersion++) {
      REGSAM rs = 0;
      if (dw64 >= 2)
         rs |= (dwWinVersion ? KEY_WOW64_64KEY : KEY_WOW64_32KEY);

      MemZero (&memBase);
      MemCat (&memBase, L"SOFTWARE\\Microsoft\\Speech\\Voices\\Tokens");

      // make sure this is created
      hKey = NULL;
      if (ERROR_SUCCESS != RegCreateKeyExW (HKEY_LOCAL_MACHINE, (PWSTR)memBase.p, 0, 0, REG_OPTION_NON_VOLATILE,
         KEY_READ | KEY_WRITE | rs , NULL, &hKey, &dwDisp))
         return FALSE;
      RegCloseKey (hKey);

      // create the token
      MemCat (&memBase, L"\\");
      MemCat (&memBase, (PWSTR)m_memSAPIToken.p);
      hKey = NULL;
      if (ERROR_SUCCESS != RegCreateKeyExW (HKEY_LOCAL_MACHINE, (PWSTR)memBase.p, 0, 0, REG_OPTION_NON_VOLATILE,
         KEY_READ | KEY_WRITE | rs, NULL, &hKey, &dwDisp))
         return FALSE;

      // write the name
      if (ERROR_SUCCESS != RegSetValueExW (hKey, NULL, 0, REG_SZ,
            (BYTE*) m_memSAPIName.p, (DWORD) (wcslen((PWSTR)m_memSAPIName.p)+1) * sizeof(WCHAR) )) {
         RegCloseKey (hKey);
         return FALSE;
      }

      // and again here
      if (ERROR_SUCCESS != RegSetValueExW (hKey, szLangID, 0, REG_SZ,
            (BYTE*) m_memSAPIName.p, (DWORD) (wcslen((PWSTR)m_memSAPIName.p)+1) * sizeof(WCHAR) )) {
         RegCloseKey (hKey);
         return FALSE;
      }

      // voice name
      if (ERROR_SUCCESS != RegSetValueExW (hKey, L"VoiceName", 0, REG_SZ,
            (BYTE*) m_memSAPIToken.p, (DWORD) (wcslen((PWSTR)m_memSAPIToken.p)+1) * sizeof(WCHAR) )) {
         RegCloseKey (hKey);
         return FALSE;
      }

      // path
      if (ERROR_SUCCESS != RegSetValueExW (hKey, L"VoiceData", 0, REG_SZ,
            (BYTE*) m_szFile, (DWORD) (wcslen(m_szFile)+1) * sizeof(WCHAR) )) {
         RegCloseKey (hKey);
         return FALSE;
      }

      // CircumReality class GUID
      psz = L"{76D2054C-8841-4fe0-93BA-4EBA0C2C4D00}";
      if (ERROR_SUCCESS != RegSetValueExW (hKey, L"CLSID", 0, REG_SZ,
            (BYTE*) psz, (DWORD) (wcslen(psz)+1) * sizeof(WCHAR) )) {
         RegCloseKey (hKey);
         return FALSE;
      }

      // close this key
      RegCloseKey (hKey);


      // attribute sub-key
      MemCat (&memBase, L"\\Attributes");
      hKey = NULL;
      if (ERROR_SUCCESS != RegCreateKeyExW (HKEY_LOCAL_MACHINE, (PWSTR)memBase.p, 0, 0, REG_OPTION_NON_VOLATILE,
         KEY_READ | KEY_WRITE | rs, NULL, &hKey, &dwDisp))
         return FALSE;

      // age
      switch (m_dwAge) {
         case 0:
            psz = L"Child";
            break;
         case 1:
            psz = L"Teen";
            break;
         default:
         case 2:
            psz = L"Adult";
            break;
         case 3:
            psz = L"Senior";
            break;
      }
      if (ERROR_SUCCESS != RegSetValueExW (hKey, L"Age", 0, REG_SZ,
            (BYTE*) psz, (DWORD) (wcslen(psz)+1) * sizeof(WCHAR) )) {
         RegCloseKey (hKey);
         return FALSE;
      }

      // vendor
      psz = (PWSTR)m_memSAPIVendor.p;
      if (!psz)
         psz = L"Enthusiast";
      if (ERROR_SUCCESS != RegSetValueExW (hKey, L"Vendor", 0, REG_SZ,
            (BYTE*) psz, (DWORD) (wcslen(psz)+1) * sizeof(WCHAR) )) {
         RegCloseKey (hKey);
         return FALSE;
      }

      // language
      if (ERROR_SUCCESS != RegSetValueExW (hKey, L"Language", 0, REG_SZ,
            (BYTE*) szLangID, (DWORD) (wcslen(szLangID)+1) * sizeof(WCHAR) )) {
         RegCloseKey (hKey);
         return FALSE;
      }

      // gender
      switch (m_dwGender) {
         default:
         case 0:
            psz = L"Male";
            break;
         case 1:
            psz = L"Female";
            break;
      }
      if (ERROR_SUCCESS != RegSetValueExW (hKey, L"Gender", 0, REG_SZ,
            (BYTE*) psz, (DWORD) (wcslen(psz)+1) * sizeof(WCHAR) )) {
         RegCloseKey (hKey);
         return FALSE;
      }

      // and again here
      if (ERROR_SUCCESS != RegSetValueExW (hKey, L"Name", 0, REG_SZ,
            (BYTE*) m_memSAPIToken.p, (DWORD) (wcslen((PWSTR)m_memSAPIToken.p)+1) * sizeof(WCHAR) )) {
         RegCloseKey (hKey);
         return FALSE;
      }


      // version
      WCHAR szTemp[64];
      swprintf (szTemp, L"%d.%d.%d", (int)m_dwVersionMajor, (int)m_dwVersionMinor, (int)m_dwVersionBuild);
      if (ERROR_SUCCESS != RegSetValueExW (hKey, L"Version", 0, REG_SZ,
            (BYTE*) szTemp, (DWORD) (wcslen(szTemp)+1) * sizeof(WCHAR) )) {
         RegCloseKey (hKey);
         return FALSE;
      }

      // close this key
      RegCloseKey (hKey);
   } // dwWinVersion



   return TRUE;
}


/*************************************************************************************
CMTTSSubVoice::Constructor and destructor
*/
CMTTSSubVoice::CMTTSSubVoice (void)
{
   MemZero (&m_memName);

   m_lTTSACCENTRULE.Init (sizeof(TTSACCENTRULE));
   m_fBlurPitch = 0.1;
   m_fBlurVolume = 0.25;
   m_fPhoneRiseAccentuate = m_fPhonePitchAccentuate = 1;
   m_pPOSAccentuate.Zero();
   m_pPOSAccentuate.p[0] = m_pPOSAccentuate.p[1] = m_pPOSAccentuate.p[2] = 1;
   m_fDurWordStart = m_fDurWordEnd = 1;
   m_fMicroPauseThreshhold = 0.5;
   m_lDurPerPhone.Init (sizeof(float));
   m_fAvgPitch = 100;   // BUGFIX - set to 100 so have something
   //m_fAvgSyllableDur = 100;   // set to 100 so have something
   m_dwWordsPerMinute = 100;  // BUGFIX - Set to 100 so have something

   m_szLexicon[0] = 0;
   m_pLexMain = NULL;

   DWORD i;
   for (i = 0; i < NUMPROSODYTTS; i++)
      m_aszProsodyTTS[i][0] = 0;
   m_pCTTSProsody = new CTTSProsody;

}

CMTTSSubVoice::~CMTTSSubVoice (void)
{
   if (m_pLexMain)
      MLexiconCacheClose (m_pLexMain);
   m_pLexMain = NULL;

   if (m_pCTTSProsody)
      delete m_pCTTSProsody;
   m_pCTTSProsody = NULL;
}


/*************************************************************************************
CMTTSSubVoice::Clone - Standard API
*/
CMTTSSubVoice *CMTTSSubVoice::Clone (void)
{
   PCMTTSSubVoice pNew = new CMTTSSubVoice;
   if (!pNew)
      return FALSE;

   if (!CloneTo (pNew)) {
      delete pNew;
      return NULL;
   }

   return pNew;
}


/*************************************************************************************
CMTTSSubVoice::CloneTo - Standard API
*/
BOOL CMTTSSubVoice::CloneTo (CMTTSSubVoice *pTo)
{
   if (pTo->m_pLexMain)
      MLexiconCacheClose (pTo->m_pLexMain);
   pTo->m_pLexMain = NULL;

   MemZero (&pTo->m_memName);
   MemCat (&pTo->m_memName, (PWSTR)m_memName.p);

   m_VoiceDisguise.CloneTo (&pTo->m_VoiceDisguise);

   pTo->m_lTTSACCENTRULE.Init (sizeof(TTSACCENTRULE), m_lTTSACCENTRULE.Get(0), m_lTTSACCENTRULE.Num());

   pTo->m_fBlurPitch = m_fBlurPitch;
   pTo->m_fBlurVolume = m_fBlurVolume;
   pTo->m_fPhoneRiseAccentuate = m_fPhoneRiseAccentuate;
   pTo->m_fPhonePitchAccentuate = m_fPhonePitchAccentuate;
   pTo->m_pPOSAccentuate.Copy (&m_pPOSAccentuate);

#if 0 // old prosody
   pTo->m_pPunctAccentuate.Copy (&m_pPunctAccentuate);
#endif // 0

   pTo->m_fDurWordStart = m_fDurWordStart;
   pTo->m_fDurWordEnd = m_fDurWordEnd;
   pTo->m_fMicroPauseThreshhold = m_fMicroPauseThreshhold;
   pTo->m_lDurPerPhone.Init (sizeof(float), m_lDurPerPhone.Get(0), m_lDurPerPhone.Num());
   pTo->m_fAvgPitch = m_fAvgPitch;
   //pTo->m_fAvgSyllableDur = m_fAvgSyllableDur;
   pTo->m_dwWordsPerMinute = m_dwWordsPerMinute;

   wcscpy (pTo->m_szLexicon, m_szLexicon);
   pTo->m_pLexMain = m_pLexMain;
   if (pTo->m_pLexMain)
      MLexiconCacheAddRef (pTo->m_pLexMain);

   DWORD i;
   for (i = 0; i < NUMPROSODYTTS; i++)
      wcscpy (pTo->m_aszProsodyTTS[i], m_aszProsodyTTS[i]);
   m_pCTTSProsody->CloneTo (pTo->m_pCTTSProsody);

   return TRUE;
}


static PWSTR gpszTTSSubVoice = L"TTSSubVoice";
static PWSTR gpszName = L"Name";
static PWSTR gpszMicroPauseThreshhold = L"MicroPauseThreshhold";

/*************************************************************************************
CMTTSSubVoice::MMLTo - Standard API
*/
PCMMLNode2 CMTTSSubVoice::MMLTo (void)
{
   PCMMLNode2 pNode = new CMMLNode2;
   if (!pNode)
      return NULL;
   pNode->NameSet (gpszTTSSubVoice);

   PWSTR psz = (PWSTR)m_memName.p;
   if (psz[0])
      MMLValueSet (pNode, gpszName, psz);


   PCMMLNode2 pSub = m_VoiceDisguise.MMLTo ();
   if (pSub) {
      pSub->NameSet (gpszVoiceSynth);
      pNode->ContentAdd (pSub);
   }

   if (m_lTTSACCENTRULE.Num())
      MMLValueSet (pNode, gpszTTSAccentRule, (PBYTE)m_lTTSACCENTRULE.Get(0),
         m_lTTSACCENTRULE.Num() * sizeof(TTSACCENTRULE));

   MMLValueSet (pNode, gpszBlurPitch, m_fBlurPitch);
   MMLValueSet (pNode, gpszBlurVolume, m_fBlurVolume);
   MMLValueSet (pNode, gpszPhoneRiseAccentuate, m_fPhoneRiseAccentuate);
   MMLValueSet (pNode, gpszPhonePitchAccentuate, m_fPhonePitchAccentuate);
   MMLValueSet (pNode, gpszPOSAccentuate, &m_pPOSAccentuate);

#if 0 // old prosody
   MMLValueSet (pNode, gpszPunctAccentuate, &m_pPunctAccentuate);
#endif // 0

   MMLValueSet (pNode, gpszDurWordStart, m_fDurWordStart);
   MMLValueSet (pNode, gpszDurWordEnd, m_fDurWordEnd);
   MMLValueSet (pNode, gpszMicroPauseThreshhold, m_fMicroPauseThreshhold);
   if (m_lDurPerPhone.Num())
      MMLValueSet (pNode, gpszDurPerPhone, (PBYTE)m_lDurPerPhone.Get(0),
         m_lDurPerPhone.Num() * sizeof(float));

   MMLValueSet (pNode, gpszAvgPitch, m_fAvgPitch);
   //MMLValueSet (pNode, gpszAvgSyllableDur, m_fAvgSyllableDur);
   MMLValueSet (pNode, gpszWordsPerMinute, (int)m_dwWordsPerMinute);

   if (m_szLexicon[0])
      MMLValueSet (pNode, gpszLexicon, m_szLexicon);

   DWORD i;
   WCHAR szTemp[64];
   for (i = 0; i < NUMPROSODYTTS; i++) {
      if (!m_aszProsodyTTS[i][0])
         continue;
      swprintf (szTemp, L"ProsodyTTS%d", (int)i);
      MMLValueSet (pNode, szTemp, m_aszProsodyTTS[i]);
   } // i

   // convert to sentence syllables
   pSub = m_pCTTSProsody->MMLTo();
   if (pSub) {
      pSub->NameSet (gpszTTSProsody);
      pNode->ContentAdd (pSub);
   }

   return pNode;
}


/*************************************************************************************
CMTTSSubVoice::MMLFrom - Standard API
*/
BOOL CMTTSSubVoice::MMLFrom (PCMMLNode2 pNode, PWSTR pszSrcFile)
{
   MemZero (&m_memName);
   PWSTR psz = MMLValueGet (pNode, gpszName);
   if (psz)
      MemCat (&m_memName, psz);

   DWORD i;
   for (i = 0; i < NUMPROSODYTTS; i++)
      m_aszProsodyTTS[i][0] = 0;
   m_pCTTSProsody->Clear();

   // clear out what have
   m_lTTSACCENTRULE.Clear();
   m_lDurPerPhone.Clear();

   if (m_pLexMain)
      MLexiconCacheClose (m_pLexMain);
   m_pLexMain = NULL;


   // get the words...
   PCMMLNode2 pSub;

   // voice synthesis
   pSub = NULL;
   pNode->ContentEnum (pNode->ContentFind (gpszVoiceSynth), &psz, &pSub);
   if (pSub) {
      MemZero (&m_VoiceDisguise.m_memSourceTTSFile);
      if (pszSrcFile)
         MemCat (&m_VoiceDisguise.m_memSourceTTSFile, pszSrcFile);
      m_VoiceDisguise.MMLFrom (pSub);
   }

   // tts accent
   CMem memRLE, mem;
   // BUGFIX - Use MMLBinaryGet
   MMLValueGetBinary (pNode, gpszTTSAccentRule, &memRLE);
   //psz = MMLValueGet (pNode, gpszTTSAccentRule);
   if (memRLE.m_dwCurPosn /*psz*/) {
      //if (!memRLE.Required (wcslen(psz)/2))
      //   return FALSE;
      PBYTE pb = (PBYTE)memRLE.p;
      size_t dwSize = memRLE.m_dwCurPosn;
      //DWORD dwSize = MMLBinaryFromString (psz, pb, memRLE.m_dwAllocated);

      m_lTTSACCENTRULE.Init (sizeof(TTSACCENTRULE), pb, (DWORD)dwSize / sizeof(TTSACCENTRULE));
   }

   m_fBlurPitch = MMLValueGetDouble (pNode, gpszBlurPitch, 0.1);
   m_fBlurVolume = MMLValueGetDouble (pNode, gpszBlurVolume, 0.25);
   m_fPhoneRiseAccentuate = MMLValueGetDouble (pNode, gpszPhoneRiseAccentuate, 1);
   m_fPhonePitchAccentuate = MMLValueGetDouble (pNode, gpszPhonePitchAccentuate, 1);
   m_pPOSAccentuate.p[0] = m_pPOSAccentuate.p[1] = m_pPOSAccentuate.p[2] = 1;

#if 0 // old prosody
   m_pPunctAccentuate.Copy (&m_pPOSAccentuate);
   MMLValueGetPoint (pNode, gpszPunctAccentuate, &m_pPunctAccentuate);
#endif // 0

   MMLValueGetPoint (pNode, gpszPOSAccentuate, &m_pPOSAccentuate);
   m_fDurWordStart = MMLValueGetDouble (pNode, gpszDurWordStart, 1);
   m_fDurWordEnd = MMLValueGetDouble (pNode, gpszDurWordEnd, 1);
   m_fMicroPauseThreshhold = MMLValueGetDouble (pNode, gpszMicroPauseThreshhold, 0.5);

   // BUGFIX - Use new binary MMLValueGet
   MMLValueGetBinary (pNode, gpszDurPerPhone, &memRLE);
   //psz = MMLValueGet (pNode, gpszDurPerPhone);
   if (memRLE.m_dwCurPosn /*psz*/) {
      //if (!memRLE.Required (wcslen(psz)/2))
      //   return FALSE;
      PBYTE pb = (PBYTE)memRLE.p;
      //DWORD dwSize = MMLBinaryFromString (psz, pb, memRLE.m_dwAllocated);
      size_t dwSize = memRLE.m_dwCurPosn;


      m_lDurPerPhone.Init (sizeof(float), pb, (DWORD)dwSize / sizeof(float));
   }

   m_fAvgPitch = MMLValueGetDouble (pNode, gpszAvgPitch, 0);
   //m_fAvgSyllableDur = MMLValueGetDouble (pNode, gpszAvgSyllableDur, 0);
   m_dwWordsPerMinute = (DWORD)MMLValueGetInt (pNode, gpszWordsPerMinute, (int)0);
   m_dwWordsPerMinute = max(m_dwWordsPerMinute, 1);

   WCHAR szTemp[64];
   for (i = 0; i < NUMPROSODYTTS; i++) {
      swprintf (szTemp, L"ProsodyTTS%d", (int)i);
      psz = MMLValueGet (pNode, szTemp);
      if (psz)
         wcscpy (m_aszProsodyTTS[i], psz);
      else
         m_aszProsodyTTS[i][0] = 0;
   } // i

   // find misc stuff
   for (i = 0; i < pNode->ContentNum(); i++) {
      pSub = NULL;
      pNode->ContentEnum (i, &psz, &pSub);
      if (!pSub)
         continue;
      psz = pSub->NameGet();
      if (!psz)
         continue;

      if (!_wcsicmp(psz, gpszTTSProsody)) {
         m_pCTTSProsody->MMLFrom (pSub, pszSrcFile);
         continue;
      }
   }


   psz = MMLValueGet (pNode, gpszLexicon);
   if (psz)
      wcscpy (m_szLexicon, psz);
   else
      m_szLexicon[0] = 0;
   if (m_szLexicon[0])
      LexiconExists (m_szLexicon, pszSrcFile);
   LexiconRequired();

   // m_pCTTSProsody->LexiconSet (Lexicon (TRUE, NULL));

   return TRUE;
}





/****************************************************************************
TTSModifiedProsPage
*/
static BOOL TTSModifiedProsPage (PCEscPage pPage, DWORD dwMessage, PVOID pParam)
{
   PTTSMMP pTTSMP = (PTTSMMP) pPage->m_pUserData;
   PCMTTSSubVoice pVF = pTTSMP->pSubVoice;
   PCMTTS pTTS = pTTSMP->pTTS;

   switch (dwMessage) {
   case ESCM_INITPAGE:
      {
         // set some sample text
         PCEscControl pControl;
         pControl = pPage->ControlFind (L"testspeak");
         if (pControl)
            pControl->AttribSet (Text(), L"This is a test.");

         // set the controls
         DoubleToControl (pPage, L"avgpitch", pVF->m_fAvgPitch);
         DoubleToControl (pPage, L"wpm", (double)pVF->m_dwWordsPerMinute);

         DoubleToControl (pPage, L"posacc0", pVF->m_pPOSAccentuate.p[0]);
         DoubleToControl (pPage, L"posacc1", pVF->m_pPOSAccentuate.p[1]);
         DoubleToControl (pPage, L"posacc2", pVF->m_pPOSAccentuate.p[2]);

#if 0 // old prosody
         DoubleToControl (pPage, L"punctacc0", pVF->m_pPunctAccentuate.p[0]);
         DoubleToControl (pPage, L"punctacc1", pVF->m_pPunctAccentuate.p[1]);
         DoubleToControl (pPage, L"punctacc2", pVF->m_pPunctAccentuate.p[2]);
#endif // 0

         DoubleToControl (pPage, L"blurpitch", pVF->m_fBlurPitch);
         DoubleToControl (pPage, L"blurvolume", pVF->m_fBlurVolume);

         DoubleToControl (pPage, L"phonepitch", pVF->m_fPhonePitchAccentuate);
         DoubleToControl (pPage, L"phonerise", pVF->m_fPhoneRiseAccentuate);

         DoubleToControl (pPage, L"durwordstart", pVF->m_fDurWordStart);
         DoubleToControl (pPage, L"durwordend", pVF->m_fDurWordEnd);
         DoubleToControl (pPage, L"micropausethreshhold", pVF->m_fMicroPauseThreshhold);

         // write out the scores
         DWORD i;
         WCHAR szTemp[32];
         PCMLexicon pLex = pTTS->Lexicon();
         if (!pLex)
            break;
         float *paf = (float*)pVF->m_lDurPerPhone.Get(0);
         for (i = 0; i < pLex->PhonemeNum(); i++) {
            float f = (i < pVF->m_lDurPerPhone.Num()) ? paf[i] : 1;
            swprintf (szTemp, L"ep:%d", (int)i);
            DoubleToControl (pPage, szTemp, f);
         } // i

         ProsodyModelInitPage (pPage, pVF->m_aszProsodyTTS);
      }
      break;

   case ESCN_EDITCHANGE:
      {
         PESCNEDITCHANGE p = (PESCNEDITCHANGE) pParam;

         if (!p->pControl || !p->pControl->m_pszName)
            break;
         PWSTR psz = p->pControl->m_pszName;

         if ((psz[0] == L'e') && (psz[1] == L'p') && (psz[2] == L':')) {
            DWORD dwPhone= (DWORD)_wtoi(psz + 3);
            float f1 = 1;
            float f = DoubleFromControl(pPage, psz);
            f = max(f, .01);

            // put 1s in up to the point of modification
            while (pVF->m_lDurPerPhone.Num() <= dwPhone)
               pVF->m_lDurPerPhone.Add (&f1);

            // set value
            float *pf = (float*)pVF->m_lDurPerPhone.Get(dwPhone);
            *pf = f;
            return TRUE;
         }

         // just get all the controls
         pVF->m_fAvgPitch = DoubleFromControl (pPage, L"avgpitch");
         pVF->m_fAvgPitch = max(pVF->m_fAvgPitch, 1);
         pVF->m_dwWordsPerMinute = (DWORD)DoubleFromControl (pPage, L"wpm");
         pVF->m_dwWordsPerMinute = max(pVF->m_dwWordsPerMinute, 1);

         pVF->m_pPOSAccentuate.p[0] = DoubleFromControl (pPage, L"posacc0");
         pVF->m_pPOSAccentuate.p[1] = DoubleFromControl (pPage, L"posacc1");
         pVF->m_pPOSAccentuate.p[2] = DoubleFromControl (pPage, L"posacc2");

#if 0 // old prosody
         pVF->m_pPunctAccentuate.p[0] = DoubleFromControl (pPage, L"punctacc0");
         pVF->m_pPunctAccentuate.p[1] = DoubleFromControl (pPage, L"punctacc1");
         pVF->m_pPunctAccentuate.p[2] = DoubleFromControl (pPage, L"punctacc2");
#endif // 0

         pVF->m_fBlurPitch = DoubleFromControl (pPage, L"blurpitch");
         pVF->m_fBlurVolume = DoubleFromControl (pPage, L"blurvolume");

         pVF->m_fPhonePitchAccentuate = DoubleFromControl (pPage, L"phonepitch");
         pVF->m_fPhoneRiseAccentuate = DoubleFromControl (pPage, L"phonerise");

         pVF->m_fDurWordStart = DoubleFromControl (pPage, L"durwordstart");
         pVF->m_fDurWordStart = max(pVF->m_fDurWordStart, .01);

         pVF->m_fDurWordEnd = DoubleFromControl (pPage, L"durwordend");
         pVF->m_fDurWordEnd = max(pVF->m_fDurWordEnd, .01);

         pVF->m_fMicroPauseThreshhold = DoubleFromControl (pPage, L"micropausethreshhold");
      }
      break;

   case ESCN_BUTTONPRESS:
      {
         PESCNBUTTONPRESS p = (PESCNBUTTONPRESS) pParam;

         if (ProsodyModelEdit (pTTS->Lexicon(), pPage, p, pVF->m_aszProsodyTTS, pVF->m_pCTTSProsody))
            return TRUE;

         if (!p->pControl || !p->pControl->m_pszName)
            break;
         PWSTR psz;
         psz = p->pControl->m_pszName;

         if (!_wcsicmp(psz, L"test")) {
            if (!pTTS->TTSMasterGet2()) {
               pPage->MBWarning (L"You don't have a master TTS voice selected yet.");
               return TRUE;
            }

            WCHAR szTemp[256];
            szTemp[0] = 0;
            DWORD dwNeed;
            PCEscControl pControl = pPage->ControlFind (L"testspeak");
            if (!pControl)
               return TRUE;
            pControl->AttribGet (Text(), szTemp, sizeof(szTemp), &dwNeed);

            CProgress Progress;
            Progress.Start (pPage->m_pWindow->m_hWnd, "Synthesizing...");
            pTTSMP->pWave->QuickPlayStop();
            TTSVOICEMOD vm;
            PCTTSProsody apTTSProsody[TTSVOICEMODMAXPROSODY];
            memset (apTTSProsody, 0, sizeof(apTTSProsody));
            apTTSProsody[0] = pVF->m_pCTTSProsody;
            pTTS->FillInVOICEMOD (&vm, pVF, apTTSProsody); // so uses current mod
            pTTS->SynthGenWave (pTTSMP->pWave, pTTSMP->pWave->m_dwSamplesPerSec, szTemp, FALSE, 1 /*iTTSQuality*/, TRUE /* fDisablePCM */, &Progress, &vm);
            pTTSMP->pWave->QuickPlay();

            return TRUE;
         }
      }
      break;

   case ESCM_SUBSTITUTION:
      {
         PESCMSUBSTITUTION p = (PESCMSUBSTITUTION) pParam;

         if (!_wcsicmp(p->pszSubName, L"PAGETITLE")) {
            p->pszSubString = L"Modify prosody";
            return TRUE;
         }
         else if (!_wcsicmp(p->pszSubName, L"SRFILE")) {
            p->pszSubString = pTTS->m_szFile;
            return TRUE;
         }
         else if (!_wcsicmp(p->pszSubName, L"SUBVOICE")) {
            PWSTR psz = (PWSTR) pVF->m_memName.p;
            p->pszSubString = psz ? psz : L"Unnamed";
            return TRUE;
         }
         else if (!_wcsicmp(p->pszSubName, L"PHONEDUR")) {
            MemZero (&gMemTemp);
            PCMLexicon pLex = pTTS->Lexicon();
            if (!pLex)
               return FALSE;

            DWORD dwSilence = pLex->PhonemeFindUnsort(pLex->PhonemeSilence());

#define PHONECOL           2     // number of columns of phonemes

            // loop
            DWORD i, j;
            DWORD dwRows = (pLex->PhonemeNum() + PHONECOL-1)/PHONECOL;
            for (i = 0; i < dwRows; i++) {
               MemCat (&gMemTemp, L"<tr>");

               for (j = 0; j < PHONECOL; j++) {
                  MemCat (&gMemTemp, L"<td>");
                  
                  PLEXPHONE plp = pLex->PhonemeGet (i + j*dwRows);
                  DWORD dwIndex = plp ? pLex->PhonemeFindUnsort (plp->szPhoneLong) : -1;
                  if (dwIndex == -1)
                     plp = NULL;

                  if (plp) {
                     MemCat (&gMemTemp, L"<bold>");
                     MemCatSanitize (&gMemTemp, plp->szPhoneLong);
                     MemCat (&gMemTemp, L"</bold> - ");
                     MemCatSanitize (&gMemTemp, plp->szSampleWord);
                  }

                  MemCat (&gMemTemp, L"</td>");

                  // edit control
                  MemCat (&gMemTemp, L"<td>");
                  if (plp && (dwIndex != dwSilence)) {
                     MemCat (&gMemTemp, L"<bold><edit maxchars=16 name=ep:");
                     MemCat (&gMemTemp, (int)dwIndex);
                     MemCat (&gMemTemp, L"/></bold>");
                  }
                  MemCat (&gMemTemp, L"</td>");
               } // j
               
               MemCat (&gMemTemp, L"</tr>");
            } // i

            p->pszSubString = (PWSTR)gMemTemp.p;
            return TRUE;
         }
      }
      break;



   };


   return DefPage (pPage, dwMessage, pParam);
}



/****************************************************************************
TTSModifiedPronPage
*/
static BOOL TTSModifiedPronPage (PCEscPage pPage, DWORD dwMessage, PVOID pParam)
{
   PTTSMMP pTTSMP = (PTTSMMP) pPage->m_pUserData;
   PCMTTSSubVoice pVF = pTTSMP->pSubVoice;
   PCMTTS pTTS = pTTSMP->pTTS;

   switch (dwMessage) {
   case ESCM_INITPAGE:
      {
         // set some sample text
         PCEscControl pControl;
         pControl = pPage->ControlFind (L"testspeak");
         if (pControl)
            pControl->AttribSet (Text(), L"This is a test.");

         // fill in the list box
         pPage->Message (ESCM_USER+83);
      }
      break;

   case ESCM_USER+83:   // fill in list box
      {
         PCEscControl pControl = pPage->ControlFind (L"accent");
         if (!pControl)
            return TRUE;
         pControl->Message (ESCM_LISTBOXRESETCONTENT);

         MemZero (&gMemTemp);

         // get the lexicon
         PCMLexicon pLex = pVF->Lexicon(TRUE, pTTS);
         if (!pLex)
            return TRUE;

         // write in elements
         PTTSACCENTRULE par = (PTTSACCENTRULE) pVF->m_lTTSACCENTRULE.Get(0);
         DWORD dwNum = pVF->m_lTTSACCENTRULE.Num();
         DWORD i;
         for (i = 0; i < dwNum; i++, par++) {
            WCHAR szTemp[128];

            MemCat (&gMemTemp, L"<elem name=");
            MemCat (&gMemTemp, (int)i);
            MemCat (&gMemTemp, L">Convert <bold>");

            pLex->PronunciationToText (par->abOrig, szTemp, sizeof(szTemp)/sizeof(WCHAR));
            MemCatSanitize (&gMemTemp, szTemp);

            MemCat (&gMemTemp, L"</bold> to <bold>");

            pLex->PronunciationToText (par->abNew, szTemp, sizeof(szTemp)/sizeof(WCHAR));
            MemCatSanitize (&gMemTemp, szTemp);

            MemCat (&gMemTemp, L"</bold> (");

            switch (par->dwLoc) {
            case 0:
            default:
               MemCat (&gMemTemp, L"anywhere");
               break;
            case 1:
               MemCat (&gMemTemp, L"start of the word");
               break;
            case 2:
               MemCat (&gMemTemp, L"end of the word");
               break;
            }

            MemCat (&gMemTemp, L")</elem>");
         } // i

         // add this to the list box
         ESCMLISTBOXADD lba;
         memset (&lba, 0, sizeof(lba));
         lba.dwInsertBefore = -1;
         lba.pszMML = (PWSTR) gMemTemp.p;
         pControl->Message (ESCM_LISTBOXADD, &lba);

         // set the selection
         pControl->AttribSetInt (CurSel(), dwNum ? (dwNum-1) : 0);

      }
      return TRUE;

   case ESCN_BUTTONPRESS:
      {
         PESCNBUTTONPRESS p = (PESCNBUTTONPRESS) pParam;

         if (!p->pControl || !p->pControl->m_pszName)
            break;
         PWSTR psz;
         psz = p->pControl->m_pszName;

         if (!_wcsicmp(psz, L"delaccent")) {
            PCEscControl pControl = pPage->ControlFind (L"accent");
            if (!pControl)
               return TRUE;
            DWORD dwSel = (DWORD) pControl->AttribGetInt (CurSel());
            if (dwSel >= pVF->m_lTTSACCENTRULE.Num()) {
               pPage->MBWarning (L"You haven't selected a rule to delete.");
               return TRUE;
            }

            // remove
            pVF->m_lTTSACCENTRULE.Remove (dwSel);
            pPage->Message (ESCM_USER+83);

            return TRUE;
         }
         else if (!_wcsicmp(psz, L"addaccent")) {
            // get the strings and combo
            WCHAR szOrig[128], szNew[128];
            DWORD dwNeed, dwLoc;
            PCEscControl pControl;
            szOrig[0] = szNew[0] = 0;
            dwLoc = 0;

            pControl = pPage->ControlFind (L"origphone");
            if (pControl)
               pControl->AttribGet (Text(), szOrig, sizeof(szOrig), &dwNeed);

            pControl = pPage->ControlFind (L"newphone");
            if (pControl)
               pControl->AttribGet (Text(), szNew, sizeof(szNew), &dwNeed);

            // get the where
            pControl = pPage->ControlFind (L"wherephone");
            if (pControl)
               dwLoc = (DWORD) pControl->AttribGetInt (CurSel());

            // get the lexicon
            PCMLexicon pLex = pVF->Lexicon(TRUE, pTTS);
            if (!pLex)
               return TRUE;

            // convert to phomemes
            BYTE abOrig[32], abNew[32];
            if (!pLex->PronunciationFromText (szOrig, abOrig, sizeof(abOrig)-1, &dwNeed)) {
               pPage->MBWarning (L"There is a mis-typed phoneme in the original phonemes.");
               return TRUE;
            }
            if (!pLex->PronunciationFromText (szNew, abNew, sizeof(abNew)-1, &dwNeed)) {
               pPage->MBWarning (L"There is a mis-typed phoneme in the new phonemes.");
               return TRUE;
            }
            DWORD dwLen;
            dwLen = (DWORD)strlen((char*)abOrig);
            if (dwLen > 7) {
               pPage->MBWarning (L"The original phoneme sequence cannot be any more than 7 phonemes.");
               return TRUE;
            }
            if (!dwLen) {
               pPage->MBWarning (L"You must have at least one phoneme in the original phoneme sequence.");
               return TRUE;
            }
            dwLen = (DWORD)strlen((char*)abNew);
            if (dwLen > 7) {
               pPage->MBWarning (L"The new phoneme sequence cannot be any more than 7 phonemes.");
               return TRUE;
            }

            TTSACCENTRULE tar;
            memset (&tar, 0, sizeof(tar));
            strcpy ((char*)tar.abNew, (char*)abNew);
            strcpy ((char*)tar.abOrig, (char*)abOrig);
            tar.dwLoc = dwLoc;

            // add
            pVF->m_lTTSACCENTRULE.Add (&tar);
            pPage->Message (ESCM_USER+83);

            return TRUE;
         }
         else if (!_wcsicmp(psz, L"newlex")) {
            WCHAR szTemp[256];
            PCMLexicon pLex = pVF->Lexicon(FALSE, NULL);
            wcscpy (szTemp, pLex ? pLex->m_szFile : L"");

            if (!MLexiconOpenDialog (pPage->m_pWindow->m_hWnd, szTemp, sizeof(szTemp)/sizeof(WCHAR), FALSE))
               szTemp[0] = 0;

            // try setting it
            pVF->LexiconSet (szTemp);

            pPage->Exit (RedoSamePage());
            return TRUE;
         }
         else if (!_wcsicmp(psz, L"test")) {
            if (!pTTS->TTSMasterGet2()) {
               pPage->MBWarning (L"You don't have a master TTS voice selected yet.");
               return TRUE;
            }

            WCHAR szTemp[256];
            szTemp[0] = 0;
            DWORD dwNeed;
            PCEscControl pControl = pPage->ControlFind (L"testspeak");
            if (!pControl)
               return TRUE;
            pControl->AttribGet (Text(), szTemp, sizeof(szTemp), &dwNeed);

            CProgress Progress;
            Progress.Start (pPage->m_pWindow->m_hWnd, "Synthesizing...");
            pTTSMP->pWave->QuickPlayStop();

            TTSVOICEMOD vm;
            PCTTSProsody apTTSProsody[TTSVOICEMODMAXPROSODY];
            memset (apTTSProsody, 0, sizeof(apTTSProsody));
            apTTSProsody[0] = pVF->m_pCTTSProsody;
            pTTS->FillInVOICEMOD (&vm, pVF, apTTSProsody); // so uses current mod
            pTTS->SynthGenWave (pTTSMP->pWave, pTTSMP->pWave->m_dwSamplesPerSec, szTemp, FALSE, 1 /*iTTSQuality*/, TRUE /* fDisablePCM */, &Progress, &vm);
            pTTSMP->pWave->QuickPlay();

            return TRUE;
         }
      }
      break;

   case ESCM_SUBSTITUTION:
      {
         PESCMSUBSTITUTION p = (PESCMSUBSTITUTION) pParam;

         if (!_wcsicmp(p->pszSubName, L"PAGETITLE")) {
            p->pszSubString = L"Modify pronunciations";
            return TRUE;
         }
         else if (!_wcsicmp(p->pszSubName, L"SRFILE")) {
            p->pszSubString = pTTS->m_szFile;
            return TRUE;
         }
         else if (!_wcsicmp(p->pszSubName, L"SUBVOICE")) {
            PWSTR psz = (PWSTR) pVF->m_memName.p;
            p->pszSubString = psz ? psz : L"Unnamed";
            return TRUE;
         }
         else if (!_wcsicmp(p->pszSubName, L"MASTERLEX")) {
            PCMLexicon pLex = pVF->Lexicon(FALSE, NULL);
            PWSTR psz = pLex ? pLex->m_szFile : NULL;
            p->pszSubString = (psz && psz[0]) ? psz : L"USE LEXICON FROM MASTER VOICE";
            return TRUE;
         }
         else if (!_wcsicmp(p->pszSubName, L"PHONELIST")) {
            PCMLexicon pLex = pVF->Lexicon(TRUE, pTTS);
            p->pszSubString = pLex ? LexPhoneList(pLex) : NULL;
            return TRUE;
         }
      }
      break;



   };


   return DefPage (pPage, dwMessage, pParam);
}



/****************************************************************************
TTSModifiedSubVoicePage
*/
static BOOL TTSModifiedSubVoicePage (PCEscPage pPage, DWORD dwMessage, PVOID pParam)
{
   PTTSMMP pTTSMP = (PTTSMMP) pPage->m_pUserData;
   PCMTTSSubVoice pVF = pTTSMP->pSubVoice;
   PCMTTS pTTS = pTTSMP->pTTS;

   switch (dwMessage) {
   case ESCM_INITPAGE:
      {
         // set some sample text
         PCEscControl pControl = pPage->ControlFind (L"testspeak");
         if (pControl)
            pControl->AttribSet (Text(), L"This is a test.");

         if (pControl = pPage->ControlFind (L"name"))
            pControl->AttribSet (Text(), (PWSTR)pVF->m_memName.p);
      }
      break;

   case ESCN_EDITCHANGE:
      {
         PESCNEDITCHANGE p = (PESCNEDITCHANGE) pParam;
         if (!p->pControl || !p->pControl->m_pszName)
            break;
         PWSTR psz = p->pControl->m_pszName;

         if (!_wcsicmp (psz, L"name")) {
            WCHAR szTemp[128];
            DWORD dwNeeded;
            szTemp[0] = 0;
            p->pControl->AttribGet (Text(), szTemp, sizeof(szTemp), &dwNeeded);

            MemZero (&pVF->m_memName);
            MemCat (&pVF->m_memName, szTemp);
            return TRUE;
         }
      }
      break;

   case ESCN_BUTTONPRESS:
      {
         PESCNBUTTONPRESS p = (PESCNBUTTONPRESS) pParam;

         if (!p->pControl || !p->pControl->m_pszName)
            break;
         PWSTR psz;
         psz = p->pControl->m_pszName;

         if (!_wcsicmp(psz, L"test")) {
            if (!pTTS->TTSMasterGet2()) {
               pPage->MBWarning (L"You don't have a master TTS voice selected yet.");
               return TRUE;
            }

            WCHAR szTemp[256];
            szTemp[0] = 0;
            DWORD dwNeed;
            PCEscControl pControl = pPage->ControlFind (L"testspeak");
            if (!pControl)
               return TRUE;
            pControl->AttribGet (Text(), szTemp, sizeof(szTemp), &dwNeed);

            CProgress Progress;
            Progress.Start (pPage->m_pWindow->m_hWnd, "Synthesizing...");
            pTTSMP->pWave->QuickPlayStop();
            TTSVOICEMOD vm;
            PCTTSProsody apTTSProsody[TTSVOICEMODMAXPROSODY];
            memset (apTTSProsody, 0, sizeof(apTTSProsody));
            apTTSProsody[0] = pVF->m_pCTTSProsody;
            pTTS->FillInVOICEMOD (&vm, pVF, apTTSProsody); // so uses current mod
            pTTS->SynthGenWave (pTTSMP->pWave, pTTSMP->pWave->m_dwSamplesPerSec, szTemp, FALSE, 1 /*iTTSQuality*/, TRUE /* fDisablePCM */, &Progress, &vm);
            pTTSMP->pWave->QuickPlay();

            return TRUE;
         }
      }
      break;

   case ESCM_SUBSTITUTION:
      {
         PESCMSUBSTITUTION p = (PESCMSUBSTITUTION) pParam;

         if (!_wcsicmp(p->pszSubName, L"PAGETITLE")) {
            p->pszSubString = L"Modify sub-voice page";
            return TRUE;
         }
         else if (!_wcsicmp(p->pszSubName, L"SRFILE")) {
            p->pszSubString = pTTS->m_szFile;
            return TRUE;
         }
         else if (!_wcsicmp(p->pszSubName, L"SUBVOICE")) {
            PWSTR psz = (PWSTR) pVF->m_memName.p;
            p->pszSubString = psz ? psz : L"Unnamed";
            return TRUE;
         }
      }
      break;



   };


   return DefPage (pPage, dwMessage, pParam);
}



/*************************************************************************************
CMTTSSubVoice::Dialog - Brings up the derived-voice editing dialog

inputs
   PCEscWindow          pWindow - window to use
   PCMTTS               pTTS - TTS object
returns
   BOOL - TRUE if pressed back, FALSE if close
*/
BOOL CMTTSSubVoice::Dialog (PCEscWindow pWindow, PCMTTS pTTS)
{
   PWSTR pszRet;

   CM3DWave Wave;
   Wave.ConvertSamplesAndChannels (22050, 1, NULL);

   TTSMMP tm;
   memset (&tm, 0, sizeof(tm));
   tm.pTTS = pTTS;
   tm.pWave = &Wave;
   tm.pSubVoice = this;

redo:
   pszRet = pWindow->PageDialog (ghInstance, IDR_MMLTTSMODIFIEDSUBVOICE, TTSModifiedSubVoicePage, &tm);
   if (!pszRet)
      return FALSE;
   if (!_wcsicmp(pszRet, Back()))
      return TRUE;
   else if (!_wcsicmp(pszRet, RedoSamePage()))
      goto redo;
   else if (!_wcsicmp(pszRet, L"synth")) {
      // if there's a pitch orig then rewrite it with current settings
      m_fAvgPitch = max(m_fAvgPitch, 1);
      if (m_VoiceDisguise.m_fPitchOrig) {
         m_VoiceDisguise.m_fPitchOrig = pTTS->TTSMasterGet2()->AvgPitchGet(FALSE);
         m_VoiceDisguise.m_fPitchScale = m_fAvgPitch / m_VoiceDisguise.m_fPitchOrig;
      }

      BOOL fRet = tm.pSubVoice->m_VoiceDisguise.Dialog (pWindow, pTTS->m_pTTSMaster, 0, NULL, 0x01);
         // BUGFIX - pass in master voice

      // readjust average pitch
      if (m_VoiceDisguise.m_fPitchOrig) {
         fp fNew = m_VoiceDisguise.m_fPitchOrig * m_VoiceDisguise.m_fPitchScale;
         if (fNew > 0)
            m_fAvgPitch = fNew;
         
         // reset so wont adjust pitch (except for extended prosody)
         m_VoiceDisguise.m_fPitchScale = 1;
         m_VoiceDisguise.m_fPitchOrig = m_fAvgPitch;
      }

      if (fRet)
         goto redo;
      else
         return FALSE;
   }
   else if (!_wcsicmp(pszRet, L"pronunciation")) {
redopron:
      pszRet = pWindow->PageDialog (ghInstance, IDR_MMLTTSMODIFIEDPRON, TTSModifiedPronPage, &tm);
      if (!pszRet)
         return FALSE;
      else if (!_wcsicmp(pszRet, Back()))
         goto redo;
      else if (!_wcsicmp(pszRet, RedoSamePage()))
         goto redopron;
      else
         return FALSE;
   }
   else if (!_wcsicmp(pszRet, L"prosody")) {
      pszRet = pWindow->PageDialog (ghInstance, IDR_MMLTTSMODIFIEDPROS, TTSModifiedProsPage, &tm);
      if (!pszRet)
         return FALSE;
      else if (!_wcsicmp(pszRet, Back()))
         goto redo;
      else
         return FALSE;
   }

   return FALSE;
}



/*************************************************************************************
CMTTSSubVoice::LexiconRequired - Loads the lexicon if it isn't already loaded.
Returns TRUE if success, FALSE if error
*/
BOOL CMTTSSubVoice::LexiconRequired (void)
{
   if (m_pLexMain)
      return TRUE;

   if (!m_szLexicon[0])
      return FALSE;

   m_pLexMain = MLexiconCacheOpen(m_szLexicon, FALSE);
   return (m_pLexMain ? TRUE : FALSE);
}

/*************************************************************************************
CMTTSSubVoice::LexiconSet - Sets a new lexicon to use

inputs
   PWSTR          pszLexicon - Lexicon
returns
   BOOL - TRUE if was able to open, FALSE if not
*/
BOOL CMTTSSubVoice::LexiconSet (PWSTR pszLexicon)
{
   if (m_pLexMain)
      MLexiconCacheClose (m_pLexMain);
   m_pLexMain = NULL;
   wcscpy (m_szLexicon, pszLexicon);

   BOOL fRet = LexiconRequired ();


   return fRet;
}

/*************************************************************************************
CMTTSSubVoice::LexiconGet - Returns a pointer to the lexicon string. DO NOT modify it.
*/
PWSTR CMTTSSubVoice::LexiconGet (void)
{
   return m_szLexicon;
}

/*************************************************************************************
CMTTSSubVoice::Lexicon - Returns a pointer to the lexicon to use for SR. NULL if cant open

inputs
   BOOL        fBackoffToMaster - If TRUE then backs off to the master lexicon if this
               happens to be a derived voice and has no lexicon of its own
   PCMTTS      pMaster - Master TTS
*/
PCMLexicon CMTTSSubVoice::Lexicon (BOOL fBackoffToMaster, PCMTTS pMaster)
{
   LexiconRequired ();

   // BUGFIX - If this is a derived TTS voice and no lexicon of its own, use
   // its master
   if (!m_pLexMain && pMaster && fBackoffToMaster)
      return pMaster->Lexicon(fBackoffToMaster);

   return m_pLexMain;
}



/*********************************************************************************
CMTTSSubVoice::MixTogether - Mixes a number of sub-voices together into
this sub-voice.

inputs
   DWORD             dwNumVoice - Number
   PCMTTSSubVoice    *papSVVoice - List of sub-voices to mix
   fp                *pafWeightVoice - Pointer to an array of dwNum voice disuise weights. Should
                        sum to 1.0. Affects the voice
   DWORD             dwNumPros - Number
   PCMTTSSubVoice    *papSVPros - List of sub-voices to mix
   fp                *pafWeightPros - Affects the prosody
   DWORD             dwNumPron - Number
                        NOTE: Only one pronunciation mix is possible at the moment
   PCMTTSSubVoice    *papSVPron - List of sub-voices to mix
   fp                *pafWeightPron - Affects the pronunciation
returns
   BOOL - TRUE if success
*/
BOOL CMTTSSubVoice::MixTogether (DWORD dwNumVoice, CMTTSSubVoice **papSVVoice, fp *pafWeightVoice,
                                 DWORD dwNumPros, CMTTSSubVoice **papSVPros, fp *pafWeightPros,
                                 DWORD dwNumPron, CMTTSSubVoice **papSVPron, fp *pafWeightPron)
{
   // clone the first one in the prounciations to this, since is easiest
   if (!papSVPron[0]->CloneTo (this))
      return FALSE;

   // mix together the voice, including the average pitch in it
   CListFixed lDV;
   DWORD i, j;
   lDV.Init (sizeof(PCVoiceDisguise), papSVVoice, dwNumVoice);
   PCVoiceDisguise *ppDV = (PCVoiceDisguise*) lDV.Get(0);
   for (i = 0; i < lDV.Num(); i++)
      ppDV[i] = &papSVVoice[i]->m_VoiceDisguise;
   if (!m_VoiceDisguise.MixTogether (lDV.Num(), ppDV, pafWeightVoice))
      return FALSE;
   m_fAvgPitch = 0;
   //m_fAvgSyllableDur = 0;
   for (i = 0; i < dwNumVoice; i++) {
      m_fAvgPitch += papSVVoice[i]->m_fAvgPitch * pafWeightVoice[i];
      //m_fAvgSyllableDur += papSVVoice[i]->m_fAvgSyllableDur * pafWeightVoice[i];
   }

   
   // do prosody, first cleaing out what have
   m_fBlurPitch = 0;
   m_fBlurVolume = 0;
   m_fPhoneRiseAccentuate = 0;
   m_fPhonePitchAccentuate = 0;
   m_fDurWordStart = 0;
   m_fDurWordEnd = 0;
   m_fMicroPauseThreshhold = 0;
   m_dwWordsPerMinute = 0;
   m_pPOSAccentuate.Zero();

   // make sure full phone
   float f = 1.0;
   DWORD dwPhone = 0;
   for (i = 0; i < dwNumPros; i++)
      dwPhone = max(dwPhone, papSVPros[i]->m_lDurPerPhone.Num());
   m_lDurPerPhone.Required (dwPhone);
   while (m_lDurPerPhone.Num() < dwPhone)
      m_lDurPerPhone.Add (&f);
   float *pafDurPerPhoneOrig = (float*)m_lDurPerPhone.Get(0);
   for (j = 0; j < m_lDurPerPhone.Num(); j++)
      pafDurPerPhoneOrig[j] = 0;

   // average in
   CPoint pTemp;
   for (i = 0; i < dwNumPros; i++) {
      fp fScale = pafWeightPros[i];

      m_fBlurPitch += papSVPros[i]->m_fBlurPitch * fScale;
      m_fBlurVolume += papSVPros[i]->m_fBlurVolume * fScale;
      m_fPhoneRiseAccentuate += papSVPros[i]->m_fPhoneRiseAccentuate * fScale;
      m_fPhonePitchAccentuate += papSVPros[i]->m_fPhonePitchAccentuate * fScale;
      m_fDurWordStart += papSVPros[i]->m_fDurWordStart * fScale;
      m_fDurWordEnd += papSVPros[i]->m_fDurWordEnd * fScale;
      m_fMicroPauseThreshhold += papSVPros[i]->m_fMicroPauseThreshhold * fScale;
      m_dwWordsPerMinute += (DWORD)(papSVPros[i]->m_dwWordsPerMinute * fScale);
      pTemp.Copy (&papSVPros[i]->m_pPOSAccentuate);
      pTemp.Scale (fScale);
      m_pPOSAccentuate.Add (&pTemp);

      float *pafDurPerPhoneCur = (float*)papSVPros[i]->m_lDurPerPhone.Get(0);
      for (j = 0; j < papSVPros[i]->m_lDurPerPhone.Num(); j++)
         pafDurPerPhoneOrig[j] += pafDurPerPhoneCur[j] * fScale;
      for (; j < m_lDurPerPhone.Num(); j++)
         pafDurPerPhoneOrig[j] += fScale; // if not there
   } // i


   // done
   return TRUE;
}


// BUGFIX - Getting internal compiler error. Try to work around
// #pragma optimize ("", off)

/*************************************************************************************
SubSRFEATURECompare - Compares a portion of a fp-version of a SRFEATURE.

inputs
   DWORD          dwNumDataPoints - Number of datapoints in each, either SRDATAPOINTS or SRDATAPOINTSSMALL
   fp             *pafVoicedAMain - SRDATAPOINTS voiced elements
   fp             *pafNoiseAMain - Noise
   fp             *pafVoicedADelta - SRDATAPOINTS voiced elements
   fp             *pafNoiseADelta - Noise
   int            iStartA - Start A
   int            iEndA - End A

   fp             *pafVoicedBMain - Voiced B
   fp             *pafNoiseBMain - Noise from B
   fp             *pafVoicedBDelta - Voiced B
   fp             *pafNoiseBDelta - Noise from B
   int            iStartB - Start B
   int            iEndB - End B... iEndB - iStartB == iEndA - iStartA

   fp             *pfEnergyA - Filled with the energy of A
   fp             *pfEnergyB - Filled with the energy of B
returns
   fp - 1.0 if exact match, 0.0 if mismatch
*/
#ifndef SRACCURACY_PSYCHOWEIGHTS_DELTA_000
__inline fp SubSRFEATURECompare (DWORD dwNumDataPoints,
                        fp *pafVoicedAMain, fp *pafNoiseAMain, fp *pafVoicedADelta, fp *pafNoiseADelta, int iStartA, int iEndA,
                        fp *pafVoicedBMain, fp *pafNoiseBMain, fp *pafVoicedBDelta, fp *pafNoiseBDelta, int iStartB, int iEndB,
                        fp *pfEnergyA, fp *pfEnergyB)
#else
__inline fp SubSRFEATURECompare (DWORD dwNumDataPoints,
                        fp *pafVoicedAMain, fp *pafNoiseAMain, int iStartA, int iEndA,
                        fp *pafVoicedBMain, fp *pafNoiseBMain, int iStartB, int iEndB,
                        fp *pfEnergyA, fp *pfEnergyB)
#endif
{
   // Note: COmbinig voice and noise energy into one, but shouldnt be too mich of an issue

   // dot product
   fp fDot = 0, fEnergyA = 0, fEnergyB = 0;
   fp fA, fB;
   for (; iStartA < iEndA; iStartA++, iStartB++) {
      // determine values
      if ((iStartA >= 0) && (iStartA < (int) dwNumDataPoints))
         fA = pafVoicedAMain[iStartA] + pafNoiseAMain[iStartA]
#ifndef SRACCURACY_PSYCHOWEIGHTS_DELTA_000
            + pafVoicedADelta[iStartA] + pafNoiseADelta[iStartA]
#endif
            ;
      else
         fA = 0;

      if ((iStartB >= 0) && (iStartB < (int) dwNumDataPoints))
         fB = pafVoicedBMain[iStartB] + pafNoiseBMain[iStartB]
#ifndef SRACCURACY_PSYCHOWEIGHTS_DELTA_000
            + pafVoicedBDelta[iStartB] + pafNoiseBDelta[iStartB]
#endif
            ;
      else
         fB = 0;

      fEnergyA += fA * fA;
      fEnergyB += fB * fB;
      fDot += fA * fB;
   } // i

   // fill inthe energies
   fA = sqrt(fEnergyA);
   fB = sqrt(fEnergyB);
   *pfEnergyA = fA;
   *pfEnergyB = fB;

   // cos(theta) = A dot B / |A||B|
   fp fLen = fA * fB;
   if (fLen < CLOSE)
      return 0;   // no match

   fDot /= fLen;
   fDot = min(fDot, 1.0);  // just in case

   return fDot;
}
// #pragma optimize ("", on)


/*************************************************************************************
CTTSWaveSegment::Constructor and destructor
*/
CTTSWaveSegment::CTTSWaveSegment (void)
{
   m_dwFeatureStart = 0;
   m_dwFeatureEnd = 0;
   m_wFlags = 0;
   m_pSRAnal = NULL;
   m_fSRAnalCalc = FALSE;
   m_pSRANALBLOCK = NULL;
   m_fMaxSpeechWindow = NULL;
   m_pmemSRFEATURE = NULL;
   m_pmemTTSFEATURECOMPEXTRA = NULL;
   m_pmemPCM = NULL;
   m_dwSamplesPerSecOrig = m_dwSamplesPerSecUp = 0;
   m_dwSRSkipOrig = m_dwSRSkipUp = 0;
   m_dwSRFEATURECompress = 1;
   m_dwSRFEATURESMALLCompress = 1;
   m_dwNumSRANALBLOCK = 0;
}

CTTSWaveSegment::~CTTSWaveSegment (void)
{
   if (m_pmemSRFEATURE)
      delete m_pmemSRFEATURE;
   if (m_pmemTTSFEATURECOMPEXTRA)
      delete m_pmemTTSFEATURECOMPEXTRA;
   if (m_pSRAnal)
      delete m_pSRAnal;
   if (m_pmemPCM)
      delete m_pmemPCM;
}



/*************************************************************************************
CTTSWaveSegment::MemoryTouch - Ensure that TTS stays in memory

NOTE: This MUST be called within the critical section so don't cause GP faults
*/
DWORD CTTSWaveSegment::MemoryTouch (void)
{
   DWORD dwRet = 0;

   DWORD dwIndex;
   DWORD *pdw;
   // NOTE: Don't bother with compressed since don't actually use it for ordinary TTS,
   // until (perhaps) generate the wave
   //if (m_memCompressed.m_dwCurPosn / sizeof(DWORD)) {
   //   dwIndex = (DWORD)rand() % (DWORD)(m_memCompressed.m_dwCurPosn / sizeof(DWORD));
   //   pdw = (DWORD*)m_memCompressed.p;
   //   dwRet += pdw[dwIndex];
   //}

   if (m_pmemSRFEATURE && (m_pmemSRFEATURE->m_dwAllocated / sizeof(DWORD))) {
      dwIndex = (DWORD)rand() % (DWORD)(m_pmemSRFEATURE->m_dwAllocated / sizeof(DWORD));
      pdw = (DWORD*)m_pmemSRFEATURE->p;
      dwRet += pdw[dwIndex];
   }

   if (m_pmemTTSFEATURECOMPEXTRA && (m_pmemTTSFEATURECOMPEXTRA->m_dwAllocated / sizeof(DWORD))) {
      dwIndex = (DWORD)rand() % (DWORD)(m_pmemTTSFEATURECOMPEXTRA->m_dwAllocated / sizeof(DWORD));
      pdw = (DWORD*)m_pmemTTSFEATURECOMPEXTRA->p;
      dwRet += pdw[dwIndex];
   }

   // NOTE: DON'T bother with PCM since can be cached out anyway

   if (m_pSRAnal && (m_pSRAnal->m_mem.m_dwAllocated / sizeof(DWORD))) {
      dwIndex = (DWORD)rand() % (DWORD)(m_pSRAnal->m_mem.m_dwAllocated / sizeof(DWORD));
      pdw = (DWORD*)m_pSRAnal->m_mem.p;
      dwRet += pdw[dwIndex];
   }

   // just make sure the compressed block is around.
   // don't

   return dwRet;
}


/*************************************************************************************
CTTSWaveSegment::NeedToCompress - Returns TRUE if these wave segments need compressing

*/
BOOL CTTSWaveSegment::NeedToCompress (void)
{
   if (m_pmemSRFEATURE || m_pmemTTSFEATURECOMPEXTRA || m_pmemPCM)
      return TRUE;

   return FALSE;
}

/*************************************************************************************
CTTSWaveSegment::Compress - Compresses the memory in m_pmemSRFEATURE into m_memCompressed.
It then deletes m_pmemSRFEATURE. NOTE: This will NOT do anything if m_memCompressed
already has a m_dwCurPosn.

Also uses m_pmemTTSFEATURECOMPEXTRA and m_pmemPCM.

returns
   BOOL - TRUE if success, FALSE if fail
*/
BOOL CTTSWaveSegment::Compress (void)
{
   if (!m_pmemSRFEATURE || !m_pmemTTSFEATURECOMPEXTRA)
      return TRUE;

   if (m_memCompressed.m_dwCurPosn) {
      // already data
      delete m_pmemSRFEATURE;
      m_pmemSRFEATURE = NULL;
      
      delete m_pmemTTSFEATURECOMPEXTRA;
      m_pmemTTSFEATURECOMPEXTRA = NULL;

      if (m_pmemPCM)
         delete m_pmemPCM;
      m_pmemPCM = NULL;

      return TRUE;
   }

   // compression settings
   // BOOL fTimeCompress = (m_wFlags & TPMML_TIMECOMPRESS) ? TRUE : FALSE;
   // BOOL fTimePhaseCompress = FALSE;
   BOOL fFreqCompress;
   BOOL fIncludePCM;
   switch (m_wFlags & TPMML_FREQCOMPRESSMASK) {
   default:
   case TPMML_FREQCOMPRESS_0: // none
      fFreqCompress = FALSE;
      fIncludePCM = TRUE;
      break;
   case TPMML_FREQCOMPRESS_1: // some
      fFreqCompress = FALSE;
      fIncludePCM = FALSE;
      break;
   case TPMML_FREQCOMPRESS_2: // max
      fFreqCompress = TRUE;
      fIncludePCM = FALSE;
      break;
   } // switch

   DWORD dwIncludeOriginalPCM = 0;
   switch (m_wFlags & TPMML_PCMCOMPRESSMASK) {
   case TPMML_PCMCOMPRESS_1:  // ADPCM
      dwIncludeOriginalPCM = 1;
      break;
   case TPMML_PCMCOMPRESS_2:  // PCM
      dwIncludeOriginalPCM = 2;
      break;
   case TPMML_PCMCOMPRESS_0:
   default:
      break;
   }

   // NOTE - Using m_dwSRSkipOrig won't be a problem for compress because,
   // only really matters when call for compress from TTS voice build,
   // and won't have upsampled

   // if no actual audio then make up silence
   DWORD dwNumSRFEATURE = m_dwFeatureEnd - m_dwFeatureStart;
   if (dwIncludeOriginalPCM && !m_pmemPCM) {
      m_pmemPCM = new CMem;
      if (!m_pmemPCM)
         return FALSE;
      _ASSERTE (m_dwSRSkipOrig == m_dwSRSkipUp);
      if (!m_pmemPCM->Required (dwNumSRFEATURE * m_dwSRSkipOrig * sizeof(short)))
         return FALSE;
      // leave the data uninitialized so it'll be obious
   }

   // allocate a block large enough to compress to... which is basically
   // the size of the original data
   CMem mem;
   DWORD dwNeed = dwNumSRFEATURE * (sizeof(SRFEATURE) + sizeof(TTSFEATURECOMPPCM) + sizeof(TTSFEATURECOMPEXTRA));
   if (dwIncludeOriginalPCM) {
      _ASSERTE (m_dwSRSkipOrig == m_dwSRSkipUp);
      dwNeed += dwNumSRFEATURE * m_dwSRSkipOrig * sizeof(short) + 256;   // add 256 in caase ADPCM, just to cover headers
   }
   if (!mem.Required(dwNeed))
      return FALSE;

   // write out PCM
   if (dwIncludeOriginalPCM == 1) {
      _ASSERTE (m_dwSRSkipOrig == m_dwSRSkipUp);
      ADPCMCompress ((short*)m_pmemPCM->p, dwNumSRFEATURE * m_dwSRSkipOrig, &mem);
   }
   else if (dwIncludeOriginalPCM == 2) {
      // full PCM
      _ASSERTE (m_dwSRSkipOrig == m_dwSRSkipUp);
      memcpy ((PBYTE)mem.p + mem.m_dwCurPosn, m_pmemPCM->p, dwNumSRFEATURE * m_dwSRSkipOrig * sizeof(short));
      mem.m_dwCurPosn += dwNumSRFEATURE * m_dwSRSkipOrig * sizeof(short);
   }

   // BUGFIX - More compression options
   DWORD dwTimeSkip = 1, dwTimePhaseSkip = 1;
   switch (m_wFlags & TPMML_TIMECOMPRESSMASK) {
   case TPMML_TIMECOMPRESS_0: // none
      dwTimeSkip = 1;
      dwTimePhaseSkip = 1;
      break;
   case TPMML_TIMECOMPRESS_1: // minimal
      dwTimeSkip = 2;
      dwTimePhaseSkip = 2;
      break;
   case TPMML_TIMECOMPRESS_2: // more
      dwTimeSkip = 3;
      dwTimePhaseSkip = 3;
      break;
   case TPMML_TIMECOMPRESS_3: // most
      dwTimeSkip = 4;
      dwTimePhaseSkip = 4;
      break;
   } // switch
   // dwTimePhaseSkip = min(dwTimePhaseSkip, 2);   // this code can't handle a phase skip > 2

   // how much time
   DWORD dwTimeMax = TimeMaxCalc (dwTimeSkip, dwNumSRFEATURE);
   DWORD dwTimePhaseMax = TimeMaxCalc (dwTimePhaseSkip, dwNumSRFEATURE);

   // loop through all frequencies of voiced
   DWORD dwFreq, dwSize, dwTime;
   PSRFEATURE psr;
   char cAverage;
   dwFreq = fFreqCompress ? 3 : 0;
   while (dwFreq < SRDATAPOINTS) {
      // increase counter
      if (!fFreqCompress)
         dwSize = 1;   // no compression
      else if (dwFreq < SRDATAPOINTS/3)
         dwSize = 3;   // skip heaps in low frequencies
      else if (dwFreq < SRDATAPOINTS*2/3)
         dwSize = 2;   // skip some
      else
         dwSize = 1;   // high resolution

      // loop over all time
      for (dwTime = 0; dwTime < dwTimeMax; dwTime++) {
         psr = (PSRFEATURE) m_pmemSRFEATURE->p + ((dwTimeSkip>1) ?
            min(dwTime*dwTimeSkip, dwNumSRFEATURE-1) : dwTime);

         // get value
         switch (dwSize) {
         case 1:
            cAverage = psr->acVoiceEnergy[dwFreq];
            break;
         case 2:
            cAverage = AmplitudeToDb ((DbToAmplitude(psr->acVoiceEnergy[dwFreq-1]) + DbToAmplitude(psr->acVoiceEnergy[dwFreq])) / 2.0);
            break;
         case 3:
            cAverage = AmplitudeToDb ((DbToAmplitude(psr->acVoiceEnergy[dwFreq-1]) + DbToAmplitude(psr->acVoiceEnergy[dwFreq]) + 
               DbToAmplitude(psr->acVoiceEnergy[dwFreq+1])) / 3.0);
            break;
         }

         // write the value out
         ((char*)mem.p)[mem.m_dwCurPosn++] = cAverage;
      } // dwTime

      // increase counter
      dwFreq += dwSize;
   } // while dwFreq < SRDATAPOINTS

   // loop through all frequencies of unvoiced
   dwFreq = fFreqCompress ? 4 : 1;
   while (dwFreq < SRDATAPOINTS) {
      // increase counter
      if (!fFreqCompress)
         dwSize = 2;   // not much compression
      else if (dwFreq < SRDATAPOINTS/3)
         dwSize = 4;   // skip heaps in low frequencies
      else if (dwFreq < SRDATAPOINTS*2/3)
         dwSize = 3;   // skip some
      else
         dwSize = 2;   // high resolution

      // loop over all time
      for (dwTime = 0; dwTime < dwTimeMax; dwTime++) {
         psr = (PSRFEATURE) m_pmemSRFEATURE->p + ((dwTimeSkip>1) ?
            min(dwTime*dwTimeSkip, dwNumSRFEATURE-1) : dwTime);

         // get value
         switch (dwSize) {
         case 2:
            cAverage = AmplitudeToDb ((DbToAmplitude(psr->acNoiseEnergy[dwFreq-1]) + DbToAmplitude(psr->acNoiseEnergy[dwFreq])) / 2.0);
            break;
         case 3:
            cAverage = AmplitudeToDb ((DbToAmplitude(psr->acNoiseEnergy[dwFreq-1]) + DbToAmplitude(psr->acNoiseEnergy[dwFreq]) + 
               DbToAmplitude(psr->acNoiseEnergy[dwFreq+1])) / 3.0);
            break;
         case 4:
            cAverage = AmplitudeToDb ((DbToAmplitude(psr->acNoiseEnergy[dwFreq-2]) + DbToAmplitude(psr->acNoiseEnergy[dwFreq-1]) +
               DbToAmplitude(psr->acNoiseEnergy[dwFreq]) + DbToAmplitude(psr->acNoiseEnergy[dwFreq+1])) / 4.0);
            break;
         }

         // write the value out
         ((char*)mem.p)[mem.m_dwCurPosn++] = cAverage;
      } // dwTime

      // increase counter
      dwFreq += dwSize;
   } // while dwFreq < SRDATAPOINTS

   // loop through all frequencies of phase
   DWORD dwFreqMax = fFreqCompress ? (SRPHASENUM/2) : SRPHASENUM;
   for (dwFreq = 0; dwFreq < dwFreqMax; dwFreq++) {
      // loop over all time
      for (dwTime = 0; dwTime < dwTimePhaseMax; dwTime++) {
         psr = (PSRFEATURE) m_pmemSRFEATURE->p + ((dwTimePhaseSkip > 1) ?
            min(dwTime*dwTimePhaseSkip, dwNumSRFEATURE-1) : dwTime);

         // write the value out
         ((BYTE*)mem.p)[mem.m_dwCurPosn++] = psr->abPhase[dwFreq];
      } // dwTime
   } // dwFreq

   // write the pitch and PCM
   // NOTE: Keeping accurate pitch no matter what (which is good for PSOLA)
   // NOTE: Keeping accurate pitch-sync PCM no matter what, which doesn't really matter
   TTSFEATURECOMPEXTRA tce;
   TTSFEATURECOMPPCM tcp;
   memset (&tce, 0, sizeof(tce));
   memset (&tcp, 0, sizeof(tcp));
   for (dwTime = 0; dwTime < dwTimePhaseMax; dwTime++) {
      DWORD dwIndex = ((dwTimePhaseSkip > 1) ?
         min(dwTime*dwTimePhaseSkip, dwNumSRFEATURE-1) : dwTime);
      psr = (PSRFEATURE) m_pmemSRFEATURE->p + dwIndex;

      // write the pitch out
      tce.fPitchF0All = ((PTTSFEATURECOMPEXTRA) m_pmemTTSFEATURECOMPEXTRA->p)[dwIndex].fPitchF0All;
      tce.fPitchF0Fuji = ((PTTSFEATURECOMPEXTRA) m_pmemTTSFEATURECOMPEXTRA->p)[dwIndex].fPitchF0Fuji;
      memcpy ((BYTE*)mem.p + mem.m_dwCurPosn, &tce, sizeof(tce));
      mem.m_dwCurPosn += sizeof(tce);

      // write out the PCM
      if (fIncludePCM) {
         tcp.bPCMHarmFadeStart = psr->bPCMHarmFadeStart;
         tcp.bPCMHarmFadeFull = psr->bPCMHarmFadeFull;
         tcp.bPCMHarmNyquist = psr->bPCMHarmNyquist;
         tcp.bPCMFill = psr->bPCMFill;
         tcp.fPCMScale = psr->fPCMScale;
#ifdef SRFEATUREINCLUDEPCM_SHORT
         // NOTE: Not tested
         memcpy (tcp.asPCM, psr->asPCM, sizeof(tcp.asPCM));
#else
         memcpy (tcp.acPCM, psr->acPCM, sizeof(tcp.acPCM));
#endif

         memcpy ((BYTE*)mem.p + mem.m_dwCurPosn, &tcp, sizeof(tcp));
         mem.m_dwCurPosn += sizeof(tcp);
      }
   } // dwTime


   // compress
   m_memCompressed.m_dwCurPosn = 0;
   if (RLEEncode ((PBYTE)mem.p, mem.m_dwCurPosn, 1, &m_memCompressed))
      return FALSE;

   delete m_pmemSRFEATURE;
   m_pmemSRFEATURE = NULL;
   delete m_pmemTTSFEATURECOMPEXTRA;
   m_pmemTTSFEATURECOMPEXTRA = NULL;
   if (m_pmemPCM)
      delete m_pmemPCM;
   m_pmemPCM = NULL;
   return TRUE;
}


/*************************************************************************************
Lanczos - Lanczos function

inputs
   fp       fX - X value
   fp       fA - A value
returns
   fp - Value
*/
__inline fp Lanczos (fp fX, fp fA)
{
   if (fX < EPSILON)
      return 1.0;

   if (fabs(fX) > fabs(fA))
      return 0.0;

   return fA * sin(PI * fX) * sin(PI / fA * fX) / (PI * PI * fX * fX);
}

#define LANCZOSWINDOW         4     // how large to make the lanczos window for upsampling

/*************************************************************************************
CTTSWaveSegment::Decompress - Decompresses the memory in m_memCompressed into m_pmemSRFEATURE.
NOTE: This will NOT do anything if m_pmemSRFEATURE already has anything.

Also fils in m_pmemTTSFEATURECOMPEXTRA.


returns
   BOOL - TRUE if success, FALSE if fail
*/
BOOL CTTSWaveSegment::Decompress (void)
{
   if (m_pmemSRFEATURE || m_pmemTTSFEATURECOMPEXTRA)
      return TRUE;   // already data

   // create new memory
   m_pmemSRFEATURE = new CMem;
   if (!m_pmemSRFEATURE)
      return FALSE;

   m_pmemTTSFEATURECOMPEXTRA = new CMem;
   if (!m_pmemTTSFEATURECOMPEXTRA)
      return FALSE;

   if (!m_memCompressed.m_dwCurPosn)
      return TRUE;   // no data

   // allocate memory for srfeature
   DWORD dwNumSRFEATURE = m_dwFeatureEnd - m_dwFeatureStart;
   if (!m_pmemSRFEATURE->Required (dwNumSRFEATURE * (sizeof(SRFEATURE) + sizeof(TTSFEATURECOMPPCM) + sizeof(TTSFEATURECOMPEXTRA) ) ))
      return FALSE;  // error
   
   if (!m_pmemTTSFEATURECOMPEXTRA->Required (dwNumSRFEATURE * sizeof(TTSFEATURECOMPEXTRA)))
      return FALSE;


   // compression settings
   // BOOL fTimeCompress = (m_wFlags & TPMML_TIMECOMPRESS) ? TRUE : FALSE;
   // BOOL fTimePhaseCompress = FALSE;
   BOOL fFreqCompress;
   BOOL fIncludePCM;
   switch (m_wFlags & TPMML_FREQCOMPRESSMASK) {
   default:
   case TPMML_FREQCOMPRESS_0: // none
      fFreqCompress = FALSE;
      fIncludePCM = TRUE;
      break;
   case TPMML_FREQCOMPRESS_1: // some
      fFreqCompress = FALSE;
      fIncludePCM = FALSE;
      break;
   case TPMML_FREQCOMPRESS_2: // max
      fFreqCompress = TRUE;
      fIncludePCM = FALSE;
      break;
   } // switch

   DWORD dwIncludeOriginalPCM = 0;
   switch (m_wFlags & TPMML_PCMCOMPRESSMASK) {
   case TPMML_PCMCOMPRESS_1:  // ADPCM
      dwIncludeOriginalPCM = 1;
      break;
   case TPMML_PCMCOMPRESS_2:  // PCM
      dwIncludeOriginalPCM = 2;
      break;
   case TPMML_PCMCOMPRESS_0:
   default:
      break;
   }

   DWORD dwNeedPCM = 0;
   if (dwIncludeOriginalPCM) {
      if (!m_pmemPCM)
         m_pmemPCM = new CMem;
      if (!m_pmemPCM)
         return FALSE;
      dwNeedPCM = dwNumSRFEATURE * m_dwSRSkipOrig * sizeof(short);
      if (!m_pmemPCM->Required (dwNeedPCM))
         return FALSE;
   }

   // BUGFIX - More compression options
   DWORD dwTimeSkip = 1, dwTimePhaseSkip = 1;
   switch (m_wFlags & TPMML_TIMECOMPRESSMASK) {
   case TPMML_TIMECOMPRESS_0: // none
      dwTimeSkip = 1;
      dwTimePhaseSkip = 1;
      break;
   case TPMML_TIMECOMPRESS_1: // minimal
      dwTimeSkip = 2;
      dwTimePhaseSkip = 2;
      break;
   case TPMML_TIMECOMPRESS_2: // more
      dwTimeSkip = 3;
      dwTimePhaseSkip = 3;
      break;
   case TPMML_TIMECOMPRESS_3: // most
      dwTimeSkip = 4;
      dwTimePhaseSkip = 4;
      break;
   } // switch
   // dwTimePhaseSkip = min(dwTimePhaseSkip, 2);   // this code can't handle a phase skip > 2

   m_dwSRFEATURECompress = dwTimeSkip;


   // create a remap table for voiced and unvoiced
   DWORD adwRemapVoiced[SRDATAPOINTS], adwRemapUnvoiced[SRDATAPOINTS];
   memset (adwRemapVoiced, 0, sizeof(adwRemapVoiced));
   memset (adwRemapUnvoiced, 0, sizeof(adwRemapUnvoiced));
   DWORD dwFreq, dwSize, dwVoiced, dwUnvoiced;
   dwFreq = fFreqCompress ? 3 : 0;
   dwVoiced = 0;
   while (dwFreq < SRDATAPOINTS) {
      // increase counter
      if (!fFreqCompress)
         dwSize = 1;   // no compression
      else if (dwFreq < SRDATAPOINTS/3)
         dwSize = 3;   // skip heaps in low frequencies
      else if (dwFreq < SRDATAPOINTS*2/3)
         dwSize = 2;   // skip some
      else
         dwSize = 1;   // high resolution

      adwRemapVoiced[dwVoiced++] = dwFreq;
      dwFreq += dwSize;
   }

   dwFreq = fFreqCompress ? 4 : 1;
   dwUnvoiced = 0;
   while (dwFreq < SRDATAPOINTS) {
      // increase counter
      if (!fFreqCompress)
         dwSize = 2;   // not much compression
      else if (dwFreq < SRDATAPOINTS/3)
         dwSize = 4;   // skip heaps in low frequencies
      else if (dwFreq < SRDATAPOINTS*2/3)
         dwSize = 3;   // skip some
      else
         dwSize = 2;   // high resolution

      adwRemapUnvoiced[dwUnvoiced++] = dwFreq;
      dwFreq += dwSize;
   }

   DWORD dwTimeMax = TimeMaxCalc (dwTimeSkip, dwNumSRFEATURE);
   DWORD dwTimePhaseMax = TimeMaxCalc (dwTimePhaseSkip, dwNumSRFEATURE);

   DWORD dwFreqMax = fFreqCompress ? (SRPHASENUM/2) : SRPHASENUM;

   // run-length decode
   size_t dwUsed;
   CMem mem;
   if (RLEDecode ((PBYTE)m_memCompressed.p, m_memCompressed.m_dwCurPosn, 1, &mem, &dwUsed))
      return FALSE;

   // try to get the PCM, since variable
   // PCM first
   char *pc = (char*)mem.p;
   BOOL fUpsample = FALSE;
   if (dwIncludeOriginalPCM == 1) {
      // decompressing PCM
      m_pmemPCM->m_dwCurPosn = 0;
      dwNeedPCM = ADPCMDecompress (pc, (DWORD) mem.m_dwCurPosn, m_pmemPCM);
      pc += dwNeedPCM;
      
      // if 64-bit amd more than 4 cores then upsample
      if ((sizeof(PVOID) > sizeof(DWORD)) && (HowManyProcessors() > 4))
         fUpsample = TRUE;
   }
   else if (dwIncludeOriginalPCM == 2) {
      // raw PCM
      if (mem.m_dwCurPosn >= dwNeedPCM)
         memcpy (m_pmemPCM->p, pc, dwNeedPCM);
      pc += dwNeedPCM;

      // note that need to upsample
      fUpsample = TRUE;
   }

#if 0 // def _DEBUG // to test
   fUpsample = TRUE;
#endif

   if (!m_pmemPCM)
      fUpsample = FALSE;

   // reset these just in case
   m_dwSRSkipUp = m_dwSRSkipOrig;
   m_dwSamplesPerSecUp = m_dwSamplesPerSecOrig;

   if (fUpsample) {
      DWORD dwSamples = dwNumSRFEATURE * m_dwSRSkipOrig * 2;   // use dwSRSkipOrig since havn't filled in up yet
      if (!m_pmemPCM->Required (dwSamples * sizeof(short)))
         goto skipupsample;

      m_dwSRSkipUp = m_dwSRSkipOrig * 2;
      m_dwSamplesPerSecUp = m_dwSamplesPerSecOrig * 2;

      // copy out the upsamples points
      DWORD i;
      short *pasPCM = (short*)m_pmemPCM->p;
      for (i = dwSamples-2; i < dwSamples; i--)
         pasPCM[i] = pasPCM[i/2];


      // window
      fp afLancsozWindow[LANCZOSWINDOW];
#ifdef _DEBUG // to test zero offset
      for (i = 0; i < LANCZOSWINDOW; i++)
         afLancsozWindow[i] = Lanczos ((fp)i, (fp)LANCZOSWINDOW);
#endif
      fp fLancsozSum = 0.0;
      for (i = 0; i < LANCZOSWINDOW; i++) {
         afLancsozWindow[i] = Lanczos ((fp)i + 0.5, (fp)LANCZOSWINDOW);
         fLancsozSum += afLancsozWindow[i];
      }
      fLancsozSum *= 2.0;
      for (i = 0; i < LANCZOSWINDOW; i++)
         afLancsozWindow[i] /= fLancsozSum;  // just in case math slightly wrong

      // fill in-between values since even values should be OK
      for (i = 1; i < dwSamples; i += 2) {
         fp fSum = 0;
         int iOffset, iCur;
         for (iOffset = -(int)LANCZOSWINDOW; iOffset < (int)LANCZOSWINDOW; iOffset++) {
            iCur = (int)i + 1 + iOffset * 2;
            if ((iCur < 0) || (iCur >= (int)dwSamples))
               continue;   // out of range

            fSum += (fp)pasPCM[iCur] * afLancsozWindow[(iOffset >= 0) ? iOffset : (-iOffset - 1)];
         } // iOffset

         // write it out
         fSum = max(fSum, -32767.0);
         fSum = min(fSum, 32767.0);
         pasPCM[i] = (short)(fSum + 0.5); // BUGFIX - Round
      }
   }

skipupsample:


   if (mem.m_dwCurPosn != dwNeedPCM + dwTimeMax * (dwVoiced + dwUnvoiced) + dwTimePhaseMax * (dwFreqMax + sizeof(TTSFEATURECOMPEXTRA) + (fIncludePCM ? sizeof(TTSFEATURECOMPPCM) : 0)) ) {
      delete m_pmemSRFEATURE;
      m_pmemSRFEATURE = NULL;
      delete m_pmemTTSFEATURECOMPEXTRA;
      m_pmemTTSFEATURECOMPEXTRA = NULL;
      if (m_pmemPCM)
         delete m_pmemPCM;
      m_pmemPCM = NULL;
      return FALSE;
   }


   // setup for loops
   PSRFEATURE psr = (PSRFEATURE)m_pmemSRFEATURE->p;
   DWORD i;

   // decompress voiced frequency wise
   DWORD dwTime, dwLast, dwThis, dwMap;
   for (dwFreq = 0; dwFreq < dwVoiced; dwFreq++) {
      dwLast = -1;
      for (dwTime = 0; dwTime < dwTimeMax; dwTime++) {
         dwThis = ((dwTimeSkip>1) ? min(dwTime*dwTimeSkip, dwNumSRFEATURE-1) : dwTime);
         psr = (PSRFEATURE) m_pmemSRFEATURE->p + dwThis;

         // write the value out
         dwMap = adwRemapVoiced[dwFreq];
         psr->acVoiceEnergy[dwMap] = *pc;

         // see if skipped entries
         if ((dwLast != (DWORD)-1) && (dwLast+1 < dwThis)) {
            DWORD dwDelta = dwThis - dwLast;
            for (i = 1; i < dwDelta; i++)
               psr[(int)i - (int)dwDelta].acVoiceEnergy[dwMap] = AmplitudeToDb (
                  DbToAmplitude(psr[-(int)dwDelta].acVoiceEnergy[dwMap]) * (fp)(dwDelta - i) / (fp)dwDelta +
                  DbToAmplitude(*pc) * (fp)i / (fp)dwDelta);
         }

         dwLast = dwThis;
         pc++;
      } // dwTime
   } // dwFreq

   // because decompress unvoiced may not fill in the highest value, do so
   for (dwTime = 0; dwTime < dwTimeMax; dwTime++) {
      dwThis = ((dwTimeSkip>1) ? min(dwTime*dwTimeSkip, dwNumSRFEATURE-1) : dwTime);
      psr = (PSRFEATURE) m_pmemSRFEATURE->p + dwThis;
      psr->acNoiseEnergy[SRDATAPOINTS-1] = 111;   // special code
   }
   // decompress unvoiced frequency wise
   for (dwFreq = 0; dwFreq < dwUnvoiced; dwFreq++) {
      dwLast = -1;
      for (dwTime = 0; dwTime < dwTimeMax; dwTime++) {
         dwThis = ((dwTimeSkip>1) ? min(dwTime*dwTimeSkip, dwNumSRFEATURE-1) : dwTime);
         psr = (PSRFEATURE) m_pmemSRFEATURE->p + dwThis;

         // write the value out
         dwMap = adwRemapUnvoiced[dwFreq];
         psr->acNoiseEnergy[dwMap] = *pc;

         // see if skipped entries
         if ((dwLast != (DWORD)-1) && (dwLast+1 < dwThis)) {
            DWORD dwDelta = dwThis - dwLast;
            for (i = 1; i < dwDelta; i++)
               psr[(int)i - (int)dwDelta].acNoiseEnergy[dwMap] = AmplitudeToDb (
                  DbToAmplitude(psr[-(int)dwDelta].acNoiseEnergy[dwMap]) * (fp)(dwDelta - i) / (fp)dwDelta +
                  DbToAmplitude(*pc) * (fp)i / (fp)dwDelta);
         }

         dwLast = dwThis;
         pc++;
      } // dwTime
   } // dwFreq

   // check last value
   for (dwTime = 0; dwTime < dwTimeMax; dwTime++) {
      dwThis = ((dwTimeSkip>1) ? min(dwTime*dwTimeSkip, dwNumSRFEATURE-1) : dwTime);
      psr = (PSRFEATURE) m_pmemSRFEATURE->p + dwThis;
      if (psr->acNoiseEnergy[SRDATAPOINTS-1] == 111)  // special code
         psr->acNoiseEnergy[SRDATAPOINTS-1] = psr->acNoiseEnergy[SRDATAPOINTS-2];
      else
         break;   // if -111 case didn't get called then never weill
   }

   // decompress phase frequency wise
   psr = (PSRFEATURE) m_pmemSRFEATURE->p;
   for (dwTime = 0; dwTime < dwNumSRFEATURE; dwTime++, psr++) {
      // BUGFIX - randomize phase instead of zeroing i
      //memset (psr->abPhase, 0, sizeof(psr->abPhase));
      psr->abPhase[0] = 0; // always

      for (dwFreq = 1; dwFreq < SRPHASENUM; dwFreq++)
         psr->abPhase[dwFreq] = (BYTE)rand();

   } // dwTime
   DWORD dwBack;
   for (dwFreq = 0; dwFreq < dwFreqMax; dwFreq++) {
      dwLast = -1;
      for (dwTime = 0; dwTime < dwTimePhaseMax; dwTime++) {
         dwThis = ((dwTimePhaseSkip > 1) ? min(dwTime*dwTimePhaseSkip, dwNumSRFEATURE-1) : dwTime);
         psr = (PSRFEATURE) m_pmemSRFEATURE->p + dwThis;

         // write the value out
         psr->abPhase[dwFreq] = (BYTE)(*pc);

         // if last+2 == dwThis then skipped an entry, so write that in too
         if ((dwLast != (DWORD)-1) && (dwLast+1 != dwThis))
            for (dwBack = dwLast+1; dwBack < dwThis; dwBack++)
               SRFEATUREPhaseInterp (dwFreq,
                  (fp)(dwBack - dwLast) / (fp) (dwThis - dwLast),
                  &psr[(int)dwLast - (int)dwThis],
                  &psr[0],
                  &psr[(int)dwBack - (int)dwThis]);

         dwLast = dwThis;
         pc++;
      } // dwTime
   } // dwFreq

   // read the pitch and PCM
   PTTSFEATURECOMPEXTRA ptce;
   PTTSFEATURECOMPPCM ptcp;
   dwLast = (DWORD)-1;
   for (dwTime = 0; dwTime < dwTimePhaseMax; dwTime++) {
      dwThis = ((dwTimePhaseSkip > 1) ? min(dwTime*dwTimePhaseSkip, dwNumSRFEATURE-1) : dwTime);
      psr = (PSRFEATURE) m_pmemSRFEATURE->p + dwThis;

      // pitch
      ptce = (PTTSFEATURECOMPEXTRA)pc;
      pc += sizeof(*ptce);

      PTTSFEATURECOMPEXTRA ptceThis = ((PTTSFEATURECOMPEXTRA) m_pmemTTSFEATURECOMPEXTRA->p) + dwThis;
      *ptceThis = *ptce;

      if (fIncludePCM) {
         // PCM
         ptcp = (PTTSFEATURECOMPPCM)pc;
         pc += sizeof(*ptcp);

         psr->bPCMHarmFadeStart = ptcp->bPCMHarmFadeStart;
         psr->bPCMHarmFadeFull = ptcp->bPCMHarmFadeFull;
         psr->bPCMHarmNyquist = ptcp->bPCMHarmNyquist;
         psr->bPCMFill = ptcp->bPCMFill;
         psr->fPCMScale = ptcp->fPCMScale;
#ifdef SRFEATUREINCLUDEPCM_SHORT
         // NOTE: Not tested
         memcpy (psr->asPCM, ptcp->asPCM, sizeof(psr->asPCM));
#else
         memcpy (psr->acPCM, ptcp->acPCM, sizeof(psr->acPCM));
#endif
      }
      else {
         psr->bPCMFill = psr->bPCMHarmFadeFull = psr->bPCMHarmFadeStart = psr->bPCMHarmNyquist = 0;
         psr->fPCMScale = 0;
#ifdef SRFEATUREINCLUDEPCM_SHORT
         memset (psr->asPCM, 0, sizeof(psr->asPCM));
#else
         memset (psr->acPCM, 0, sizeof(psr->acPCM));
#endif
      }

      // if last+2 == dwThis then skipped an entry, so write that in too
      if ((dwLast != (DWORD)-1) && (dwLast+1 != dwThis))
         for (dwBack = dwLast+1; dwBack < dwThis; dwBack++) {
            int iLast = (int)dwLast - (int)dwThis;
            int iBack = (int)dwBack - (int)dwThis;

            // just copy the PCM
            psr[iBack].bPCMHarmFadeStart = psr[iLast].bPCMHarmFadeStart;
            psr[iBack].bPCMHarmFadeFull = psr[iLast].bPCMHarmFadeFull;
            psr[iBack].bPCMHarmNyquist = psr[iLast].bPCMHarmNyquist;
            psr[iBack].bPCMFill = psr[iLast].bPCMFill;
            psr[iBack].fPCMScale = psr[iLast].fPCMScale;
#ifdef SRFEATUREINCLUDEPCM_SHORT
            // NOTE: Not tested
            memcpy (psr[iBack].asPCM, psr[iLast].asPCM, sizeof(psr[-1].asPCM));
#else
            memcpy (psr[iBack].acPCM, psr[iLast].acPCM, sizeof(psr[-1].acPCM));
#endif

            // interpolate pitch
            fp fAlpha = (fp)(dwBack - dwLast) / (fp)(dwThis - dwLast);
            ptceThis[iBack] = ptceThis[iLast]; // in case anything other than pitch
            ptceThis[iBack].fPitchF0All = (1.0 - fAlpha) * ptceThis[iLast].fPitchF0All + fAlpha * ptceThis[0].fPitchF0All;
            ptceThis[iBack].fPitchF0Fuji = (1.0 - fAlpha) * ptceThis[iLast].fPitchF0Fuji + fAlpha * ptceThis[0].fPitchF0Fuji;
         }

      dwLast = dwThis;
   } // dwTime


   // fill in gaps between frequencies
   psr = (PSRFEATURE) m_pmemSRFEATURE->p;
   int iLastValue, iThisValue;
   for (dwTime = 0; dwTime < dwNumSRFEATURE; dwTime++, psr++) {
      for (dwVoiced = 0; dwVoiced < 2; dwVoiced++) {
         DWORD *padwMap = dwVoiced ? adwRemapVoiced : adwRemapUnvoiced;
         char *pac = dwVoiced ? psr->acVoiceEnergy : psr->acNoiseEnergy;

         DWORD dwLast = (DWORD)-1, dwCur;
         iLastValue = -100;   // to silence
         for (dwMap = 0; dwMap < SRDATAPOINTS; dwMap++, dwLast = dwCur) {
            dwCur = padwMap[dwMap];
            if (dwCur <= dwLast+1)
               break;   // end of mapping, or already on 1-step so nothing to interpolate

            iThisValue = pac[dwCur];

            switch (dwCur - dwLast) {
            case 2:  // skipped one
               pac[dwCur-1] = AmplitudeToDb ((DbToAmplitude(iLastValue) + DbToAmplitude(iThisValue))/2.0);
               break;
            case 3:  // skipped two
               pac[dwCur-2] = AmplitudeToDb ((DbToAmplitude(iLastValue)*2 + DbToAmplitude(iThisValue))/3.0);
               pac[dwCur-1] = AmplitudeToDb ((DbToAmplitude(iLastValue) + DbToAmplitude(iThisValue)*2)/3.0);
               break;
            case 4:  // skipped three
               pac[dwCur-3] = AmplitudeToDb ((DbToAmplitude(iLastValue)*3 + DbToAmplitude(iThisValue)*1)/4.0);
               pac[dwCur-2] = AmplitudeToDb ((DbToAmplitude(iLastValue)*2 + DbToAmplitude(iThisValue)*2)/4.0);
               pac[dwCur-1] = AmplitudeToDb ((DbToAmplitude(iLastValue)*1 + DbToAmplitude(iThisValue)*3)/4.0);
               break;
            case 5:  // skipped four
               pac[dwCur-4] = AmplitudeToDb ((DbToAmplitude(iLastValue)*4 + DbToAmplitude(iThisValue)*1)/5.0);
               pac[dwCur-3] = AmplitudeToDb ((DbToAmplitude(iLastValue)*3 + DbToAmplitude(iThisValue)*2)/5.0);
               pac[dwCur-2] = AmplitudeToDb ((DbToAmplitude(iLastValue)*2 + DbToAmplitude(iThisValue)*3)/5.0);
               pac[dwCur-1] = AmplitudeToDb ((DbToAmplitude(iLastValue)*1 + DbToAmplitude(iThisValue)*4)/5.0);
               break;
            } // switch

            iLastValue = iThisValue;
         } // dwMap
      } // dwVoiced
   } // dwTime

   return TRUE;
}

/*************************************************************************************
CTTSWaveSegment::MMLToBinary - Like MMLTo, except this fills in a binary buffer
with information.

inputs
   PCMem       pmem - Memory to write to. Start at m_dwCurPosn and add on. Should
                     update m_dwCurPosn in the process
returns
   DWORD - Size of memory added, or 0 if error
*/
size_t CTTSWaveSegment::MMLToBinary (PCMem pmem)
{
   // make sure it's compressed
   if (!Compress())
      return FALSE;

   size_t dwNeed = sizeof(WAVESEGMML) + m_memCompressed.m_dwCurPosn;
   if (!pmem->Required (pmem->m_dwCurPosn + dwNeed))
      return 0;
   PWAVESEGMML ptp = (PWAVESEGMML) ((PBYTE)pmem->p + pmem->m_dwCurPosn);
   PVOID psr = (PSRFEATURE) (ptp+1);
   pmem->m_dwCurPosn += dwNeed;

   memset (ptp, 0, sizeof(*ptp));
   ptp->dwFeatureStart = m_dwFeatureStart;
   ptp->dwFeatureEnd = m_dwFeatureEnd;
   ptp->wFlags = m_wFlags;
   ptp->dwSamplesPerSec = m_dwSamplesPerSecOrig;
   ptp->wSRSkip = (WORD)m_dwSRSkipOrig;

   memcpy (psr, m_memCompressed.p, m_memCompressed.m_dwCurPosn);
   
   return dwNeed;
}

/*************************************************************************************
CTTSWaveSegment::MMLFromBinary - Reads triphone information from binary memory.

inputs
   PVOID             pvMem - Memory to read from
   DWORD             dwSize - Number of bytes. This is the same value as returned from MMLToBinary()
returns
   BOOL - TRUE if success
*/
BOOL CTTSWaveSegment::MMLFromBinary (PVOID pMem, DWORD dwSize)
{
   // delete existing SR feature
   if (m_pmemSRFEATURE) {
      delete m_pmemSRFEATURE;
      m_pmemSRFEATURE = NULL;
   }
   if (m_pmemTTSFEATURECOMPEXTRA) {
      delete m_pmemTTSFEATURECOMPEXTRA;
      m_pmemTTSFEATURECOMPEXTRA = NULL;
   }
   if (m_pmemPCM) {
      delete m_pmemPCM;
      m_pmemPCM = NULL;
   }
   if (m_pSRAnal)
      delete m_pSRAnal;
   m_fSRAnalCalc = FALSE;

   PWAVESEGMML ptp = (PWAVESEGMML) pMem;
   PVOID psr = (PSRFEATURE) (ptp+1);

   m_dwFeatureStart = ptp->dwFeatureStart;
   m_dwFeatureEnd = ptp->dwFeatureEnd;
   m_wFlags = ptp->wFlags;
   m_dwSamplesPerSecOrig = m_dwSamplesPerSecUp = ptp->dwSamplesPerSec;
   m_dwSRSkipOrig = m_dwSRSkipUp = ptp->wSRSkip;

   // copy over remaining
   dwSize -= sizeof(*ptp);

   if (!m_memCompressed.Required (dwSize))
      return FALSE;
   memcpy (m_memCompressed.p, psr, dwSize);
   m_memCompressed.m_dwCurPosn = dwSize;

   return TRUE;
}


/*************************************************************************************
CTTSWaveSegment::Init - Initializes to some PCM.

inputs
   WORD              wFlags - Flags to use for compression.
   PCM3DWave         pWave - Wave. Must have PCM loaded if this is
                     to save PCM, (wFlags had TPMML_PCMCOMPRESSMASK).
   DWORD             dwFeatureStart - Start feature in pWave
   DWORD             dwFeatureEnd - End feature (exclusive)
   PSRFEATURE        pSRFToUse - If this is supplied, the SRFEATURE will come from here instead of
                     pWave. Assumes pSRFToUse start's at feature m_dwFeatureStart and has
                     (m_dwFeatureEnd - m_dwFeatureStart) features. If NULL, then it comes directly from pWave.
returns
   BOOL - TRUE if success
*/
BOOL CTTSWaveSegment::Init (WORD wFlags, PCM3DWave pWave, DWORD dwFeatureStart, DWORD dwFeatureEnd, PSRFEATURE pSRFToUse)
{
   // blank out
   m_memCompressed.m_dwCurPosn = 0;
   if (m_pmemSRFEATURE) {
      delete m_pmemSRFEATURE;
      m_pmemSRFEATURE = NULL;
   }
   if (m_pmemTTSFEATURECOMPEXTRA) {
      delete m_pmemTTSFEATURECOMPEXTRA;
      m_pmemTTSFEATURECOMPEXTRA = NULL;
   }
   if (m_pmemPCM) {
      delete m_pmemPCM;
      m_pmemPCM = NULL;
   }
   if (m_pSRAnal) {
      delete m_pSRAnal;
      m_pSRAnal = NULL;
      m_fSRAnalCalc = FALSE;
   }

   m_dwFeatureStart = dwFeatureStart;
   m_dwFeatureEnd = dwFeatureEnd;
   m_wFlags = wFlags;
   m_dwSamplesPerSecOrig = m_dwSamplesPerSecUp = pWave->m_dwSamplesPerSec;
   m_dwSRSkipOrig = m_dwSRSkipUp = pWave->m_dwSRSkip;

   // if NULL then get data from wave
   if (!pSRFToUse) {
      pSRFToUse = pWave->CacheSRFeatures (m_dwFeatureStart, m_dwFeatureEnd);
      if (!pSRFToUse)
         return FALSE;
      // NOTE: Not calling ReleaseSRFeatures()
   }

   DWORD dwNumSRFEATURE = m_dwFeatureEnd - m_dwFeatureStart;

   // allocate memory
   m_pmemSRFEATURE = new CMem;
   if (!m_pmemSRFEATURE)
      return FALSE;
   m_pmemTTSFEATURECOMPEXTRA = new CMem;
   if (!m_pmemTTSFEATURECOMPEXTRA)
      return FALSE;
   if (!m_pmemSRFEATURE->Required (dwNumSRFEATURE * sizeof(SRFEATURE)))
      return FALSE;
   if (!m_pmemTTSFEATURECOMPEXTRA->Required (dwNumSRFEATURE * sizeof(TTSFEATURECOMPEXTRA)))
      return FALSE;

   // copy over
   memcpy (m_pmemSRFEATURE->p, pSRFToUse, dwNumSRFEATURE * sizeof(SRFEATURE));
   DWORD i;
   memset (m_pmemTTSFEATURECOMPEXTRA->p, 0, dwNumSRFEATURE * sizeof(TTSFEATURECOMPEXTRA));
   PTTSFEATURECOMPEXTRA pTCA = (PTTSFEATURECOMPEXTRA) m_pmemTTSFEATURECOMPEXTRA->p;
   for (i = 0; i < dwNumSRFEATURE; i++, pTCA++) {
      pTCA->fPitchF0All = pWave->PitchAtSample (PITCH_F0, (m_dwFeatureStart + i) * pWave->m_dwSRSkip, 0);
      pTCA->fPitchF0Fuji = pWave->PitchAtSample (PITCH_SUB, (m_dwFeatureStart + i) * pWave->m_dwSRSkip, 0);
   }

   // make sure there's PCM if needed
   if (wFlags & TPMML_PCMCOMPRESSMASK) {
      if (!pWave->m_psWave || (pWave->m_dwChannels != 1))
         return FALSE;

      m_pmemPCM = new CMem;
      if (!m_pmemPCM)
         return FALSE;
      if (!m_pmemPCM->Required (dwNumSRFEATURE * m_dwSRSkipOrig * sizeof(short)))
         return FALSE;
      memset (m_pmemPCM->p, 0, dwNumSRFEATURE * m_dwSRSkipOrig * sizeof(short));

      if (pWave->m_dwSamples >= m_dwFeatureStart * m_dwSRSkipOrig)
         memcpy (m_pmemPCM->p, pWave->m_psWave + (m_dwFeatureStart * m_dwSRSkipOrig),
            min(pWave->m_dwSamples - m_dwFeatureStart * m_dwSRSkipOrig, dwNumSRFEATURE * m_dwSRSkipOrig) * sizeof(short));
   }

   return TRUE;
}


/*************************************************************************************
CTTSWaveSegment::Merge - Merges the data in from a second CTTSWaveSegment. If the
merge fails (because there's no overlap, etc.) then just returns FALSE.

inputs
   PCTTSWaveSegment        *pWith - Merge with
returns
   BOOL - TRUE if they merge properly, FALSE if not
*/
BOOL CTTSWaveSegment::Merge (CTTSWaveSegment *pWith)
{
   // find the earliest one
   PCTTSWaveSegment pEarliest = (m_dwFeatureStart < pWith->m_dwFeatureStart) ? this : pWith;
   PCTTSWaveSegment pLatest = (pEarliest == this) ? pWith : this;
   if (pEarliest->m_dwFeatureEnd != pLatest->m_dwFeatureStart)
      return FALSE;  // can only merge end to end

   pEarliest->Decompress();
   pLatest->Decompress();

   if (!m_pmemSRFEATURE || !m_pmemTTSFEATURECOMPEXTRA)
      return FALSE;  // error

   // make sure sampling rates match
   if ((pWith->m_pmemPCM && !m_pmemPCM) || (m_pmemPCM && !pWith->m_pmemPCM))
      return FALSE;
   if (m_pmemPCM)
      if ((m_dwSamplesPerSecOrig != pWith->m_dwSamplesPerSecOrig) || (m_dwSRSkipOrig != pWith->m_dwSRSkipOrig))
         return FALSE;

   DWORD dwNumSRFEATURE = pLatest->m_dwFeatureEnd - pEarliest->m_dwFeatureStart;
   DWORD dwNumSRFEATUREEarliest = pEarliest->m_dwFeatureEnd - pEarliest->m_dwFeatureStart;
   DWORD dwNumSRFEATURELatest = pLatest->m_dwFeatureEnd - pLatest->m_dwFeatureStart;

   // blank out
   m_memCompressed.m_dwCurPosn = 0;
   if (m_pSRAnal) {
      delete m_pSRAnal;
      m_pSRAnal = NULL;
      m_fSRAnalCalc = FALSE;
   }

   // enough memory
   if (!m_pmemSRFEATURE->Required (dwNumSRFEATURE * sizeof(SRFEATURE)))
      return FALSE;
   if (!m_pmemTTSFEATURECOMPEXTRA->Required (dwNumSRFEATURE * sizeof(TTSFEATURECOMPEXTRA)))
      return FALSE;
   if (m_pmemPCM && !m_pmemPCM->Required(dwNumSRFEATURE * m_dwSRSkipOrig * sizeof(short)))
      return FALSE;

   // move
   if (pEarliest == this) {
      // this is at the beginning, so copy the other onto the end
      memcpy (
         (PSRFEATURE)m_pmemSRFEATURE->p + dwNumSRFEATUREEarliest,
         pLatest->m_pmemSRFEATURE->p,
         dwNumSRFEATURELatest * sizeof(SRFEATURE));
      memcpy (
         (PTTSFEATURECOMPEXTRA)m_pmemTTSFEATURECOMPEXTRA->p + dwNumSRFEATUREEarliest,
         pLatest->m_pmemTTSFEATURECOMPEXTRA->p,
         dwNumSRFEATURELatest * sizeof(TTSFEATURECOMPEXTRA));
      if (m_pmemPCM)
         memcpy (
            (short*)m_pmemPCM->p + m_dwSRSkipOrig * dwNumSRFEATUREEarliest,
            pLatest->m_pmemPCM->p,
            m_dwSRSkipOrig * dwNumSRFEATURELatest * sizeof(short));
   }
   else {
      // move this up
      memmove (
         (PSRFEATURE)m_pmemSRFEATURE->p + dwNumSRFEATUREEarliest,
         m_pmemSRFEATURE->p,
         dwNumSRFEATURELatest * sizeof(SRFEATURE));
      memmove (
         (PTTSFEATURECOMPEXTRA)m_pmemTTSFEATURECOMPEXTRA->p + dwNumSRFEATUREEarliest,
         m_pmemTTSFEATURECOMPEXTRA->p,
         dwNumSRFEATURELatest * sizeof(TTSFEATURECOMPEXTRA));
      if (m_pmemPCM)
         memmove (
            (short*)m_pmemPCM->p + m_dwSRSkipOrig * dwNumSRFEATUREEarliest,
            m_pmemPCM->p,
            m_dwSRSkipOrig * dwNumSRFEATURELatest * sizeof(short));

      // copy new one to top
      memcpy (
         m_pmemSRFEATURE->p,
         pEarliest->m_pmemSRFEATURE->p,
         dwNumSRFEATUREEarliest * sizeof(SRFEATURE));
      memcpy (
         m_pmemTTSFEATURECOMPEXTRA->p,
         pEarliest->m_pmemTTSFEATURECOMPEXTRA->p,
         dwNumSRFEATUREEarliest * sizeof(TTSFEATURECOMPEXTRA));
      if (m_pmemPCM)
          memcpy (
            m_pmemPCM->p,
            pEarliest->m_pmemPCM->p,
            m_dwSRSkipOrig * dwNumSRFEATUREEarliest * sizeof(short));
  }

   // note the new start and end times
   m_dwFeatureStart = pEarliest->m_dwFeatureStart;
   m_dwFeatureEnd = pLatest->m_dwFeatureEnd;

   // done
   return TRUE;

}



/*************************************************************************************
CTTSWaveSegment::Exists - Tests to see if this wave segment covers
the desired features.

inputs
   DWORD                dwFeatureStart - Start feature that want. If this can't support the range then returns an error
   DWORD                dwFeatureEnd - End feature that want
returns
   BOOL - TRUE if covers the range, FALSE if not
*/
BOOL CTTSWaveSegment::Exists (DWORD dwFeatureStart, DWORD dwFeatureEnd)
{
   if ((dwFeatureStart < m_dwFeatureStart) || (dwFeatureStart >= m_dwFeatureEnd) ||
      (dwFeatureEnd <= m_dwFeatureStart) || (dwFeatureEnd > m_dwFeatureEnd))
      return FALSE;

   // else success
   return TRUE;
}


/*************************************************************************************
CTTSWaveSegment::Clone - Clone this
*/
CTTSWaveSegment *CTTSWaveSegment::Clone (void)
{
   // NOTE: Not tested!

   PCTTSWaveSegment pNew = new CTTSWaveSegment;
   if (!pNew)
      return NULL;

   pNew->m_dwFeatureStart = m_dwFeatureStart;
   pNew->m_dwFeatureEnd = m_dwFeatureEnd;
   pNew->m_wFlags = m_wFlags;
   pNew->m_dwSamplesPerSecOrig = m_dwSamplesPerSecOrig;
   pNew->m_dwSamplesPerSecUp = m_dwSamplesPerSecUp;
   pNew->m_dwSRSkipOrig = m_dwSRSkipOrig;
   pNew->m_dwSRSkipUp = m_dwSRSkipUp;

   DWORD dwNumSRFEATURE = m_dwFeatureEnd - m_dwFeatureStart;

   // compressed
   if (m_memCompressed.m_dwCurPosn) {
      if (!pNew->m_memCompressed.Required (m_memCompressed.m_dwCurPosn)) {
         delete pNew;
         return NULL;
      }
      memcpy (pNew->m_memCompressed.p, m_memCompressed.p, m_memCompressed.m_dwCurPosn);
      pNew->m_memCompressed.m_dwCurPosn = m_memCompressed.m_dwCurPosn;
   }

   if (m_pmemSRFEATURE) {
      pNew->m_pmemSRFEATURE = new CMem;
      if (!pNew->m_pmemSRFEATURE) {
         delete pNew;
         return NULL;
      }
      if (!pNew->m_pmemSRFEATURE->Required(dwNumSRFEATURE * sizeof(SRFEATURE))) {
         delete pNew;
         return NULL;
      }
      memcpy (pNew->m_pmemSRFEATURE->p, m_pmemSRFEATURE->p, dwNumSRFEATURE * sizeof(SRFEATURE));
   }

   if (m_pmemTTSFEATURECOMPEXTRA) {
      pNew->m_pmemTTSFEATURECOMPEXTRA = new CMem;
      if (!pNew->m_pmemTTSFEATURECOMPEXTRA) {
         delete pNew;
         return NULL;
      }
      if (!pNew->m_pmemTTSFEATURECOMPEXTRA->Required(dwNumSRFEATURE * sizeof(TTSFEATURECOMPEXTRA))) {
         delete pNew;
         return NULL;
      }
      memcpy (pNew->m_pmemTTSFEATURECOMPEXTRA->p, m_pmemTTSFEATURECOMPEXTRA->p, dwNumSRFEATURE * sizeof(TTSFEATURECOMPEXTRA));
   }

   if (m_pmemPCM) {
      pNew->m_pmemPCM = new CMem;
      if (!pNew->m_pmemPCM) {
         delete pNew;
         return NULL;
      }
      if (!pNew->m_pmemPCM->Required(dwNumSRFEATURE * m_dwSRSkipOrig * sizeof(short))) {
         delete pNew;
         return NULL;
      }
      memcpy (pNew->m_pmemPCM->p, m_pmemPCM->p, dwNumSRFEATURE * m_dwSRSkipOrig * sizeof(short));
   }

   return pNew;
}

/*************************************************************************************
CTTSWaveSegment::GetSRFEATURE - Gets the SRFEATUREs.

inputs
   CRITICAL_SECTION     *pCS - Critical section that will be used to ensure that
                        not bumping over one another multithreaded. If NULL then don't use
   DWORD                dwFeatureStart - Start feature that want. If this can't support the range then returns an error
   DWORD                dwFeatureEnd - End feature that want
   DWORD                *pdwPrior - Filled with number of features available prior to dwFeatureStart. Can be NULL.
   DWORD                *pdwAfter - Filled with number of features available after dwFeatureEnd. Can be NULL.
   PTTSFEATURECOMPEXTRA *ppTFCE - Filled with a pointer to the TTSFEATURECOMPEXTRA at dwFeatureStart. Can be NULL.
   short                **ppaiPCM - Filled in with a pointer to the PCM at dwFeatureStart. If no PCM then fills in with NULL.
returns
   PSRFEATURE - SRFEATURE for dwFeatureStart.
*/
PSRFEATURE CTTSWaveSegment::GetSRFEATURE (CRITICAL_SECTION *pCS, DWORD dwFeatureStart, DWORD dwFeatureEnd,
                                          DWORD *pdwPrior, DWORD *pdwAfter, PTTSFEATURECOMPEXTRA *ppTFCE,
                                          short **ppaiPCM)
{
   if (!Exists (dwFeatureStart, dwFeatureEnd))
      return FALSE;

   // critical section
   if (pCS)
      EnterCriticalSection (pCS);

   if (!Decompress ()) {
      if (pCS)
         LeaveCriticalSection (pCS);
      return NULL;
   }
   if (!m_pmemSRFEATURE || !m_pmemSRFEATURE->p) {
      if (pCS)
         LeaveCriticalSection (pCS);
      return NULL;
   }

   DWORD dwPrior = dwFeatureStart - m_dwFeatureStart;
   PSRFEATURE pRet = (PSRFEATURE)m_pmemSRFEATURE->p + (dwPrior);
   if (pdwPrior)
      *pdwPrior = dwPrior;
   if (pdwAfter)
      *pdwAfter = m_dwFeatureEnd - dwFeatureEnd;
   if (ppTFCE)
      *ppTFCE = (PTTSFEATURECOMPEXTRA)m_pmemTTSFEATURECOMPEXTRA->p + dwPrior;
   if (ppaiPCM)
      *ppaiPCM = (m_pmemPCM && m_pmemPCM->p) ? ((short*)m_pmemPCM->p + dwPrior * m_dwSRSkipUp) : NULL;

   if (pCS)
      LeaveCriticalSection (pCS);

   return pRet;
}



/*************************************************************************************
CTTSWaveSegment::FreeSRANALBLOCK - Frees the SRANALBLOCK.

NOTE: This MUST be called in a thread-safe location so that none of the memory
that's going to be freed will be remembered
*/
void CTTSWaveSegment::FreeSRANALBLOCK (void)
{
   if (m_pSRAnal) {
      delete m_pSRAnal;
      m_pSRAnal = NULL;
   }
}

/*************************************************************************************
CTTSWaveSegment::GetSRANALBLOCK - Gets the SRANALBLOCK.

inputs
   CRITICAL_SECTION     *pCS - Critical section that will be used to ensure that
                        not bumping over one another multithreaded. If NULL then don't use
   DWORD                dwFeatureStart - Start feature that want. If this can't support the range then returns an error
   DWORD                dwFeatureEnd - End feature that want
   DWORD                *pdwPrior - Filled with number of features available prior to dwFeatureStart. Can be NULL.
   DWORD                *pdwAfter - Filled with number of features available after dwFeatureStart. Can be NULL.
   fp                   *pfMaxSpeechWindow - Filled with maxSpeechWindow from CSRAnal.Init()
   DWORD                *pdwCompress - How much the wave segment analyzed SRFEATUREs have been compressed.
                        1 for none. 2 for every other one. 3 for every third. 4 for every fourth.
   DWORD                *pdwPriorComp - Prior, but using *pdwCompress values
   DWORD                *pdwAfterComp - After, but using *pdwCompress values, not necessarily accurate if
                           dwFeatureEnd - dwFeatureStart isn't a multiple of *pdwCompress
   DWORD                *pdwTotalComp - Total number of data points in wave segment
returns
   PSRANALBLOCK - SRANALBLOCK for dwFeatureStart.
*/
PSRANALBLOCK CTTSWaveSegment::GetSRANALBLOCK (CRITICAL_SECTION *pCS, DWORD dwFeatureStart, DWORD dwFeatureEnd,
                                          DWORD *pdwPrior, DWORD *pdwAfter, fp *pfMaxSpeechWindow,
                                          DWORD *pdwCompress, DWORD *pdwPriorComp, DWORD *pdwAfterComp, DWORD *pdwTotalComp)
{
   if (!Exists (dwFeatureStart, dwFeatureEnd))
      return FALSE;

   if (pCS && m_fSRAnalCalc)
      pCS = NULL; // if already calculated then don't bother with CS

   // critical section
   if (pCS)
      EnterCriticalSection (pCS);

   DWORD i;

   // if no pSRAnal then create one
   if (!m_pSRAnal) {
      PSRFEATURE pSRF = GetSRFEATURE (NULL, m_dwFeatureStart, m_dwFeatureEnd);
      if (!pSRF) {
         if (pCS)
            LeaveCriticalSection (pCS);
         return NULL;
      }

      m_pSRAnal = new CSRAnal;
      if (!m_pSRAnal) {
         if (pCS)
            LeaveCriticalSection (pCS);
         return NULL;
      }

      // remember the compression amount
      m_dwSRFEATURESMALLCompress = m_dwSRFEATURECompress;
      CMem memTemp;
      if (m_dwSRFEATURESMALLCompress > 1) {
         // allocate temporary memory and copy over bits and pieces
         m_dwNumSRANALBLOCK = (m_dwFeatureEnd - m_dwFeatureStart + m_dwSRFEATURESMALLCompress - 1) / m_dwSRFEATURESMALLCompress + 1;
         if (!memTemp.Required (m_dwNumSRANALBLOCK * sizeof(SRFEATURE))) {
            if (pCS)
               LeaveCriticalSection (pCS);
            return NULL;
         }
         PSRFEATURE pSRFOrig = pSRF;
         pSRF = (PSRFEATURE) memTemp.p;

         for (i = 0; i < m_dwNumSRANALBLOCK; i++)
            pSRF[i] = pSRFOrig[min(i * m_dwSRFEATURESMALLCompress, m_dwFeatureEnd - m_dwFeatureStart - 1)];
      }
      else
         m_dwNumSRANALBLOCK = m_dwFeatureEnd - m_dwFeatureStart;

      m_pSRANALBLOCK = m_pSRAnal->Init (pSRF, m_dwNumSRANALBLOCK, FALSE, &m_fMaxSpeechWindow);
      if (!m_pSRANALBLOCK) {
         delete m_pSRAnal;
         m_pSRAnal = NULL;
      }

      // BUGFIX - Disable this because used this flag to indicate that
      // would no longer need to get to other memory, including srfeatures.
      // BUT, since occasionally wipe out some of this memory to reduce memory footprint,
      // need the critical seciton active all the time
      // m_fSRAnalCalc = TRUE;
   } // if pSRAnal

   // if m_pSRAnal is set then the other values must be valid
   PSRANALBLOCK pRet;
   if (m_pSRAnal) {
      DWORD dwIndex = dwFeatureStart - m_dwFeatureStart;
      DWORD dwIndexComp = dwIndex / m_dwSRFEATURESMALLCompress;
      pRet = m_pSRANALBLOCK + dwIndexComp;
      if (pdwPrior)
         *pdwPrior = dwIndex;
      if (pdwPriorComp)
         *pdwPriorComp = dwIndexComp;
      if (pdwAfter)
         *pdwAfter = m_dwFeatureEnd - dwFeatureEnd;
      if (pdwAfterComp)
         *pdwAfterComp = m_dwNumSRANALBLOCK - (dwIndexComp + (dwFeatureEnd - dwFeatureStart) / m_dwSRFEATURESMALLCompress);
      if (pdwCompress)
         *pdwCompress = m_dwSRFEATURESMALLCompress;
      if (pdwTotalComp)
         *pdwTotalComp = m_dwNumSRANALBLOCK;
      if (pfMaxSpeechWindow)
         *pfMaxSpeechWindow = m_fMaxSpeechWindow;
   }
   else
      pRet = NULL;

   if (pCS)
      LeaveCriticalSection (pCS);

   return pRet;
}


/*************************************************************************************
CTTSWave::Constructor and destructor
*/
CTTSWave::CTTSWave (void)
{
   m_lPCTTSWaveSegment.Init (sizeof(PCTTSWaveSegment));
   m_dwSentenceNum = 0;
}

CTTSWave::~CTTSWave (void)
{
   PCTTSWaveSegment *ppWS = (PCTTSWaveSegment*)m_lPCTTSWaveSegment.Get(0);
   DWORD i;
   for (i = 0; i < m_lPCTTSWaveSegment.Num(); i++)
      delete ppWS[i];
   m_lPCTTSWaveSegment.Clear();
}



/*************************************************************************************
CTTSWave::MemoryTouch - Ensure that TTS stays in memory

NOTE: This MUST be called within the TTS critical seciton so don't cause GP faults
*/
DWORD CTTSWave::MemoryTouch (void)
{
   if (!m_lPCTTSWaveSegment.Num())
      return 0;

   // pick a random wave segment
   DWORD dwIndex = (DWORD)rand() % m_lPCTTSWaveSegment.Num();
   PCTTSWaveSegment *ppWS = (PCTTSWaveSegment*)m_lPCTTSWaveSegment.Get(dwIndex);
   return (ppWS && ppWS[0]) ? ppWS[0]->MemoryTouch() : 0;
}

/*************************************************************************************
CTTSWave::Compress - Compresses all the wave segments.
*/
BOOL CTTSWave::Compress (void)
{
   DWORD i;
   PCTTSWaveSegment *ppWS = (PCTTSWaveSegment*)m_lPCTTSWaveSegment.Get(0);
   for (i = 0; i < m_lPCTTSWaveSegment.Num(); i++)
      ppWS[i]->Compress();

   return TRUE;
}



/*************************************************************************************
CTTSWave::NeedToCompress - Returns TRUE if any wave segments needs compressing.
*/
BOOL CTTSWave::NeedToCompress (void)
{
   DWORD i;
   PCTTSWaveSegment *ppWS = (PCTTSWaveSegment*)m_lPCTTSWaveSegment.Get(0);
   for (i = 0; i < m_lPCTTSWaveSegment.Num(); i++)
      if (ppWS[i]->NeedToCompress())
         return TRUE;

   return FALSE;
}


/*************************************************************************************
CTTSWave::CalcSRANALBLOCK - Calculates all the analysis blocks

inputs
   CRITICAL_SECTION     *pCS - Critical section to use. Can be null
*/
void CTTSWave::CalcSRANALBLOCK (CRITICAL_SECTION *pCS)
{
   PCTTSWaveSegment *ppWS = (PCTTSWaveSegment*)m_lPCTTSWaveSegment.Get(0);
   DWORD i;
   for (i = 0; i < m_lPCTTSWaveSegment.Num(); i++)
      ppWS[i]->GetSRANALBLOCK (pCS, ppWS[i]->m_dwFeatureStart, ppWS[i]->m_dwFeatureEnd,
         NULL, NULL, NULL, NULL, NULL, NULL, NULL);
}

/*************************************************************************************
CTTSWave::MMLToBinary - Like MMLTo, except this fills in a binary buffer
with information.

inputs
   PCMem       pmem - Memory to write to. Start at m_dwCurPosn and add on. Should
                     update m_dwCurPosn in the process
returns
   DWORD - Size of memory added, or 0 if error
*/
size_t CTTSWave::MMLToBinary (PCMem pmem)
{
   // write the number of objects total
   size_t dwSizeAt = pmem->m_dwCurPosn;
   size_t dwSizeOrig = dwSizeAt;
   if (!pmem->Required (pmem->m_dwCurPosn + 2*sizeof(DWORD)))
      return 0;
   pmem->m_dwCurPosn += 2*sizeof(DWORD);
   DWORD *pdw = (DWORD*)((PBYTE)pmem->p + dwSizeAt);
   pdw[0] = m_lPCTTSWaveSegment.Num();
   pdw[1] = m_dwSentenceNum;

   DWORD i;
   PCTTSWaveSegment *ppWS = (PCTTSWaveSegment*)m_lPCTTSWaveSegment.Get(0);
   for (i = 0; i < m_lPCTTSWaveSegment.Num(); i++) {
      // space for size
      dwSizeAt = pmem->m_dwCurPosn;
      if (!pmem->Required (pmem->m_dwCurPosn + sizeof(DWORD)))
         return 0;
      pmem->m_dwCurPosn += sizeof(DWORD);


      // to binary for sub-object
      size_t dwSize = ppWS[i]->MMLToBinary (pmem);
      if (!dwSize)
         return 0;   // error

      // write the size
      DWORD *pdw = (DWORD*)((PBYTE)pmem->p + dwSizeAt);
      *pdw = (DWORD) dwSize;
   } // i

   return pmem->m_dwCurPosn - dwSizeOrig;
}


/*************************************************************************************
CTTSWave::MMLFromBinary - Reads triphone information from binary memory.

inputs
   PVOID             pvMem - Memory to read from
   DWORD             dwSize - Number of bytes. This is the same value as returned from MMLToBinary()
returns
   BOOL - TRUE if success
*/
BOOL CTTSWave::MMLFromBinary (PVOID pMem, DWORD dwSize)
{
   // clear existing
   PCTTSWaveSegment *ppWS = (PCTTSWaveSegment*)m_lPCTTSWaveSegment.Get(0);
   DWORD i;
   for (i = 0; i < m_lPCTTSWaveSegment.Num(); i++)
      delete ppWS[i];
   m_lPCTTSWaveSegment.Clear();

   // get the number
   if (dwSize < 2*sizeof(DWORD))
      return FALSE;
   DWORD *pdw = (DWORD*)pMem;
   DWORD dwNum = pdw[0];
   m_dwSentenceNum = pdw[1];
   pMem = (PBYTE)pMem + 2*sizeof(DWORD);
   dwSize -= 2*sizeof(DWORD);

   // read in all the copies
   for (i = 0; i < dwNum; i++) {
      // get size
      if (dwSize < sizeof(DWORD))
         return FALSE;
      pdw = (DWORD*)pMem;
      DWORD dwSizeThis = *pdw;
      pMem = (PBYTE)pMem + sizeof(DWORD);
      dwSize -= sizeof(DWORD);

      // make sure large enough
      if (dwSize < dwSizeThis)
         return FALSE;

      // get the semgnet
      PCTTSWaveSegment pSeg = new CTTSWaveSegment;
      if (!pSeg)
         return FALSE;  // error
      if (!pSeg->MMLFromBinary (pMem, dwSizeThis)) {
         delete pSeg;
         return FALSE;
      }
      
      m_lPCTTSWaveSegment.Add (&pSeg);

      pMem = (PBYTE)pMem + dwSizeThis;
      dwSize -= dwSizeThis;

   } // i

   // if anything left over then error
   if (dwSize)
      return FALSE;

   return TRUE;
}



/*************************************************************************************
CTTSWave::Find - Finds a wave segment that covers the given feature range.

inputs
   DWORD          dwFeatureStart - Starting feature, in SRSAMPLES
   DWORD          dwFeatureEnd - Ending feature, in SRSAMPLES
*/
PCTTSWaveSegment CTTSWave::Find (DWORD dwFeatureStart, DWORD dwFeatureEnd)
{
   PCTTSWaveSegment *ppWS = (PCTTSWaveSegment*)m_lPCTTSWaveSegment.Get(0);
   DWORD i;
   for (i = 0; i < m_lPCTTSWaveSegment.Num(); i++)
      if (ppWS[i]->Exists (dwFeatureStart, dwFeatureEnd))
         return ppWS[i];

   return NULL;
}



/*************************************************************************************
CTTSWave::Add - Adds audio to the wave segment.

inputs
   WORD           wFlags - Compression flags, to be stored in CTTSWaveSegment.m_wFlags
   PCM3DWave      pWave - Wave to use. Must have valid pitch and SRFEATUREs.
                  If the wave is to store away PCM (wFlags contains TPMML_PCMCOMPRESSMASK)
                  then this wave must contain PCM, and can't merely be cached
   DWORD          dwFeatureStart - Feature to use
   DWORD          dwFeatureEnd - Up to this feature (exclusive)
   PSRFEATURE     pSRFToUse - If NULL then get features from pWave. If not, use features
                  from this, assuming that pSRFToUse[0] is from dwFeatureStart.
returns
   BOOL - TRUE if success of if already exists
*/
BOOL CTTSWave::Add (WORD wFlags, PCM3DWave pWave, DWORD dwFeatureStart, DWORD dwFeatureEnd, PSRFEATURE pSRFToUse)
{
   // if already exists then do nothing
   if (Find(dwFeatureStart, dwFeatureEnd))
      return TRUE;

   // else, create a new one
   PCTTSWaveSegment pWS = new CTTSWaveSegment;
   if (!pWS)
      return FALSE;
   if (!pWS->Init (wFlags, pWave, dwFeatureStart, dwFeatureEnd, pSRFToUse)) {
      delete pWS;
      return FALSE;
   }

   // add
   m_lPCTTSWaveSegment.Add (&pWS);

   // try to merge
   DWORD i, j;
mergeagain:
   PCTTSWaveSegment *ppWS = (PCTTSWaveSegment*)m_lPCTTSWaveSegment.Get(0);
   for (i = 0; i < m_lPCTTSWaveSegment.Num(); i++)
      for (j = i+1; j < m_lPCTTSWaveSegment.Num(); j++)
         if (ppWS[i]->Merge (ppWS[j])) {
            // successfully merged
            delete ppWS[j];
            m_lPCTTSWaveSegment.Remove (j);
            goto mergeagain;  // start from beginning and see what else can merge
         }

   return TRUE;
}


/*************************************************************************************
CTTSWave::GetSRFEATURE - Gets the SRFEATUREs.

inputs
   CRITICAL_SECTION     *pCS - Critical section that will be used to ensure that
                        not bumping over one another multithreaded. If NULL then don't use
   DWORD                dwFeatureStart - Start feature that want. If this can't support the range then returns an error
   DWORD                dwFeatureEnd - End feature that want
   DWORD                *pdwPrior - Filled with number of features available prior to dwFeatureStart. Can be NULL.
   DWORD                *pdwAfter - Filled with number of features available after dwFeatureEnd. Can be NULL.
   PTTSFEATURECOMPEXTRA *ppTFCE - Filled with a pointer to the TTSFEATURECOMPEXTRA at dwFeatureStart. Can be NULL.
   short                **ppaiPCM - Filled in with a pointer to the PCM at dwFeatureStart. If no PCM then fills in with NULL.
returns
   PSRFEATURE - SRFEATURE for dwFeatureStart.
*/
PSRFEATURE CTTSWave::GetSRFEATURE (CRITICAL_SECTION *pCS, DWORD dwFeatureStart, DWORD dwFeatureEnd,
                                          DWORD *pdwPrior, DWORD *pdwAfter, PTTSFEATURECOMPEXTRA *ppTFCE,
                                          short **ppaiPCM)
{
   PSRFEATURE pRet = NULL;
   PCTTSWaveSegment *ppWS = (PCTTSWaveSegment*)m_lPCTTSWaveSegment.Get(0);
   DWORD i;
   for (i = 0; i < m_lPCTTSWaveSegment.Num(); i++) {
      pRet = ppWS[i]->GetSRFEATURE (pCS, dwFeatureStart, dwFeatureEnd, pdwPrior, pdwAfter, ppTFCE, ppaiPCM);
      if (pRet)
         return pRet;
   }

   return NULL;
}


/*************************************************************************************
CTTSWave::FreeSRANALBLOCK - Frees the SRANALBLOCK.

NOTE: This MUST be called in a thread-safe location so that none of the memory
that's going to be freed will be remembered
*/
void CTTSWave::FreeSRANALBLOCK (void)
{
   PCTTSWaveSegment *ppWS = (PCTTSWaveSegment*)m_lPCTTSWaveSegment.Get(0);
   DWORD i;
   for (i = 0; i < m_lPCTTSWaveSegment.Num(); i++)
      ppWS[i]->FreeSRANALBLOCK ();
}


/*************************************************************************************
CTTSWave::GetSRANALBLOCK - Gets the SRANALBLOCK.

inputs
   CRITICAL_SECTION     *pCS - Critical section that will be used to ensure that
                        not bumping over one another multithreaded. If NULL then don't use
   DWORD                dwFeatureStart - Start feature that want. If this can't support the range then returns an error
   DWORD                dwFeatureEnd - End feature that want
   DWORD                *pdwPrior - Filled with number of features available prior to dwFeatureStart. Can be NULL.
   DWORD                *pdwAfter - Filled with number of features available after dwFeatureStart. Can be NULL.
   fp                   *pfMaxSpeechWindow - Filled with maxSpeechWindow from CSRAnal.Init()
   DWORD                *pdwCompress - How much the wave segment analyzed SRFEATUREs have been compressed.
                        1 for none. 2 for every other one. 3 for every third. 4 for every fourth.
   DWORD                *pdwPriorComp - Prior, but using *pdwCompress values
   DWORD                *pdwAfterComp - After, but using *pdwCompress values, not necessarily accurate if
                           dwFeatureEnd - dwFeatureStart isn't a multiple of *pdwCompress
   DWORD                *pdwTotalComp - Total number of data points in wave segment
returns
   PSRANALBLOCK - SRANALBLOCK for dwFeatureStart.
*/
PSRANALBLOCK CTTSWave::GetSRANALBLOCK (CRITICAL_SECTION *pCS, DWORD dwFeatureStart, DWORD dwFeatureEnd,
                                          DWORD *pdwPrior, DWORD *pdwAfter, fp *pfMaxSpeechWindow,
                                          DWORD *pdwCompress, DWORD *pdwPriorComp, DWORD *pdwAfterComp, DWORD *pdwTotalComp)
{
   PSRANALBLOCK pRet = NULL;
   PCTTSWaveSegment *ppWS = (PCTTSWaveSegment*)m_lPCTTSWaveSegment.Get(0);
   DWORD i;
   for (i = 0; i < m_lPCTTSWaveSegment.Num(); i++) {
      pRet = ppWS[i]->GetSRANALBLOCK (pCS, dwFeatureStart, dwFeatureEnd, pdwPrior, pdwAfter, pfMaxSpeechWindow,
         pdwCompress, pdwPriorComp, pdwAfterComp, pdwTotalComp);
      if (pRet)
         return pRet;
   }

   return NULL;
}


/*************************************************************************************
CTTSWave::Clone - Clones this
*/
CTTSWave *CTTSWave::Clone (void)
{
   // NOT TESTED!
   PCTTSWave pNew = new CTTSWave;
   if (!pNew)
      return NULL;

   pNew->m_dwSentenceNum = m_dwSentenceNum;
   pNew->m_lPCTTSWaveSegment.Init (sizeof(PCTTSWaveSegment), m_lPCTTSWaveSegment.Get(0), m_lPCTTSWaveSegment.Num());

   DWORD i;
   PCTTSWaveSegment *ppWS = (PCTTSWaveSegment*)pNew->m_lPCTTSWaveSegment.Get(0);
   for (i = 0; i < pNew->m_lPCTTSWaveSegment.Num(); i++)
      ppWS[i] = ppWS[i]->Clone();

   return pNew;
}

// BUGBUG - When use pitchabs, doesn't always work well because have later code that
// automatically fixes F0Fuji so it's not too high/low

// BUGBUG - When use X-high, X-low pitch doesn't always work because have code
// that fits phoneme pitch to nearest recorded audio, which doesn't exist for x-high
