/* * Copyright (C) 2005 to 2014 by Jonathan Duddington * email: jonsd@users.sourceforge.net * Copyright (C) 2013-2017 Reece H. Dunn * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see: . */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" // for GetFileLength, strncpy0, ...c #include "error.h" // for create_file_error_context #include "mnemonics.h" // for LookupMnemName, MNEM_TAB #include "phoneme.h" // for PHONEME_TAB, PHONEME_TAB_LIST #include "spect.h" // for SpectFrame, peak_t, SpectSeq #include "speech.h" // for path_home, GetFileLength #include "synthdata.h" // for LoadPhData #include "synthesize.h" // for TUNE, frame_t, CONDITION_IS_OTHER #include "translate.h" // for utf8_out, utf8_in #include "voice.h" // for LoadVoice, voice #include "wavegen.h" // for WavegenInit, WavegenSetVoice static int CalculateSample(unsigned char c3, int c1); #define N_ITEM_STRING 256 typedef struct { unsigned int value; char *name; } NAMETAB; typedef struct { const char *mnem; int type; int data; } keywtab_t; #define k_AND 1 #define k_OR 2 #define k_THEN 3 #define k_NOT 4 #define kTHISSTRESS 0x800 // keyword types enum { tPHONEME_TYPE = 1, tPHONEME_FLAG, tTRANSITION, tSTATEMENT, tINSTRN1, tWHICH_PHONEME, tTEST, }; static const keywtab_t k_conditions[] = { { "AND", 0, k_AND }, { "OR", 0, k_OR }, { "THEN", 0, k_THEN }, { "NOT", 0, k_NOT }, { "prevPh", tWHICH_PHONEME, 0 }, { "thisPh", tWHICH_PHONEME, 1 }, { "nextPh", tWHICH_PHONEME, 2 }, { "next2Ph", tWHICH_PHONEME, 3 }, { "nextPhW", tWHICH_PHONEME, 4 }, { "prevPhW", tWHICH_PHONEME, 5 }, { "next2PhW", tWHICH_PHONEME, 6 }, { "nextVowel", tWHICH_PHONEME, 7 }, { "prevVowel", tWHICH_PHONEME, 8 }, { "next3PhW", tWHICH_PHONEME, 9 }, { "prev2PhW", tWHICH_PHONEME, 10 }, { "PreVoicing", tTEST, 0xf01 }, { "KlattSynth", tTEST, 0xf02 }, { "MbrolaSynth", tTEST, 0xf03 }, { NULL, 0, 0 } }; static const keywtab_t k_properties[] = { { "isPause", 0, CONDITION_IS_PHONEME_TYPE | phPAUSE }, { "isVowel", 0, CONDITION_IS_PHONEME_TYPE | phVOWEL }, { "isNasal", 0, CONDITION_IS_PHONEME_TYPE | phNASAL }, { "isLiquid", 0, CONDITION_IS_PHONEME_TYPE | phLIQUID }, { "isUStop", 0, CONDITION_IS_PHONEME_TYPE | phSTOP }, { "isVStop", 0, CONDITION_IS_PHONEME_TYPE | phVSTOP }, { "isVFricative", 0, CONDITION_IS_PHONEME_TYPE | phVFRICATIVE }, { "isPalatal", 0, CONDITION_IS_PHFLAG_SET | phFLAGBIT_PALATAL }, { "isLong", 0, CONDITION_IS_PHFLAG_SET | phFLAGBIT_LONG }, { "isRhotic", 0, CONDITION_IS_PHFLAG_SET | phFLAGBIT_RHOTIC }, { "isSibilant", 0, CONDITION_IS_PHFLAG_SET | phFLAGBIT_SIBILANT }, { "isFlag1", 0, CONDITION_IS_PHFLAG_SET | phFLAGBIT_FLAG1 }, { "isFlag2", 0, CONDITION_IS_PHFLAG_SET | phFLAGBIT_FLAG2 }, { "isVelar", 0, CONDITION_IS_PLACE_OF_ARTICULATION | phPLACE_VELAR }, { "isDiminished", 0, CONDITION_IS_OTHER | STRESS_IS_DIMINISHED }, { "isUnstressed", 0, CONDITION_IS_OTHER | STRESS_IS_UNSTRESSED }, { "isNotStressed", 0, CONDITION_IS_OTHER | STRESS_IS_NOT_STRESSED }, { "isStressed", 0, CONDITION_IS_OTHER | STRESS_IS_SECONDARY }, { "isMaxStress", 0, CONDITION_IS_OTHER | STRESS_IS_PRIMARY }, { "isPause2", 0, CONDITION_IS_OTHER | isBreak }, { "isWordStart", 0, CONDITION_IS_OTHER | isWordStart }, { "isWordEnd", 0, CONDITION_IS_OTHER | isWordEnd }, { "isAfterStress", 0, CONDITION_IS_OTHER | isAfterStress }, { "isNotVowel", 0, CONDITION_IS_OTHER | isNotVowel }, { "isFinalVowel", 0, CONDITION_IS_OTHER | isFinalVowel }, { "isVoiced", 0, CONDITION_IS_OTHER | isVoiced }, { "isFirstVowel", 0, CONDITION_IS_OTHER | isFirstVowel }, { "isSecondVowel", 0, CONDITION_IS_OTHER | isSecondVowel }, { "isTranslationGiven", 0, CONDITION_IS_OTHER | isTranslationGiven }, { NULL, 0, 0 } }; enum { kPHONEMESTART = 1, kUTF8_BOM, kPROCEDURE, kENDPHONEME, kENDPROCEDURE, kPHONEMETABLE, kINCLUDE, kIMPORT_PH, kSTARTTYPE, kENDTYPE, kSTRESSTYPE, kVOICINGSWITCH, kIF, kELSE, kELIF, kENDIF, kCALLPH, kSWITCH_PREVVOWEL, kSWITCH_NEXTVOWEL, kENDSWITCH, kFMT, kWAV, kVOWELSTART, kVOWELENDING, kANDWAV, kVOWELIN, kVOWELOUT, kTONESPEC, kRETURN, kCONTINUE, }; enum { kTUNE = 1, kENDTUNE, kTUNE_PREHEAD, kTUNE_ONSET, kTUNE_HEAD, kTUNE_HEADENV, kTUNE_HEADEXTEND, kTUNE_HEADLAST, kTUNE_NUCLEUS0, kTUNE_NUCLEUS1, kTUNE_SPLIT, }; static const unsigned char utf8_bom[] = { 0xef, 0xbb, 0xbf, 0 }; static const keywtab_t k_intonation[] = { { "tune", 0, kTUNE }, { "endtune", 0, kENDTUNE }, { "prehead", 0, kTUNE_PREHEAD }, { "onset", 0, kTUNE_ONSET }, { "head", 0, kTUNE_HEAD }, { "headenv", 0, kTUNE_HEADENV }, { "headextend", 0, kTUNE_HEADEXTEND }, { "headlast", 0, kTUNE_HEADLAST }, { "nucleus0", 0, kTUNE_NUCLEUS0 }, { "nucleus", 0, kTUNE_NUCLEUS1 }, { "split", 0, kTUNE_SPLIT }, { NULL, 0, -1 } }; static const keywtab_t keywords[] = { { "liquid", tPHONEME_TYPE, phLIQUID }, { "pause", tPHONEME_TYPE, phPAUSE }, { "stress", tPHONEME_TYPE, phSTRESS }, { "virtual", tPHONEME_TYPE, phVIRTUAL }, { "delete_phoneme", tPHONEME_TYPE, phDELETED }, // keywords { "phonemetable", tSTATEMENT, kPHONEMETABLE }, { "include", tSTATEMENT, kINCLUDE }, { (const char *)utf8_bom, tSTATEMENT, kUTF8_BOM }, { "phoneme", tSTATEMENT, kPHONEMESTART }, { "procedure", tSTATEMENT, kPROCEDURE }, { "endphoneme", tSTATEMENT, kENDPHONEME }, { "endprocedure", tSTATEMENT, kENDPROCEDURE }, { "import_phoneme", tSTATEMENT, kIMPORT_PH }, { "stress_type", tSTATEMENT, kSTRESSTYPE }, { "starttype", tSTATEMENT, kSTARTTYPE }, { "endtype", tSTATEMENT, kENDTYPE }, { "voicingswitch", tSTATEMENT, kVOICINGSWITCH }, { "IF", tSTATEMENT, kIF }, { "ELSE", tSTATEMENT, kELSE }, { "ELIF", tSTATEMENT, kELIF }, { "ELSEIF", tSTATEMENT, kELIF }, // same as ELIF { "ENDIF", tSTATEMENT, kENDIF }, { "CALL", tSTATEMENT, kCALLPH }, { "RETURN", tSTATEMENT, kRETURN }, { "PrevVowelEndings", tSTATEMENT, kSWITCH_PREVVOWEL }, { "NextVowelStarts", tSTATEMENT, kSWITCH_NEXTVOWEL }, { "EndSwitch", tSTATEMENT, kENDSWITCH }, { "Tone", tSTATEMENT, kTONESPEC }, { "FMT", tSTATEMENT, kFMT }, { "WAV", tSTATEMENT, kWAV }, { "VowelStart", tSTATEMENT, kVOWELSTART }, { "VowelEnding", tSTATEMENT, kVOWELENDING }, { "addWav", tSTATEMENT, kANDWAV }, { "Vowelin", tSTATEMENT, kVOWELIN }, { "Vowelout", tSTATEMENT, kVOWELOUT }, { "Continue", tSTATEMENT, kCONTINUE }, { "ChangePhoneme", tINSTRN1, i_CHANGE_PHONEME }, { "ChangeNextPhoneme", tINSTRN1, i_REPLACE_NEXT_PHONEME }, { "InsertPhoneme", tINSTRN1, i_INSERT_PHONEME }, { "AppendPhoneme", tINSTRN1, i_APPEND_PHONEME }, { "IfNextVowelAppend", tINSTRN1, i_APPEND_IFNEXTVOWEL }, { "ChangeIfDiminished", tINSTRN1, i_CHANGE_IF | STRESS_IS_DIMINISHED }, { "ChangeIfUnstressed", tINSTRN1, i_CHANGE_IF | STRESS_IS_UNSTRESSED }, { "ChangeIfNotStressed", tINSTRN1, i_CHANGE_IF | STRESS_IS_NOT_STRESSED }, { "ChangeIfStressed", tINSTRN1, i_CHANGE_IF | STRESS_IS_SECONDARY }, { "ChangeIfStressed", tINSTRN1, i_CHANGE_IF | STRESS_IS_PRIMARY }, { "PauseBefore", tINSTRN1, i_PAUSE_BEFORE }, { "PauseAfter", tINSTRN1, i_PAUSE_AFTER }, { "length", tINSTRN1, i_SET_LENGTH }, { "LongLength", tINSTRN1, i_LONG_LENGTH }, { "LengthAdd", tINSTRN1, i_ADD_LENGTH }, { "lengthmod", tINSTRN1, i_LENGTH_MOD }, { "ipa", tINSTRN1, i_IPA_NAME }, // flags { "unstressed", tPHONEME_FLAG, phUNSTRESSED }, { "nolink", tPHONEME_FLAG, phNOLINK }, { "brkafter", tPHONEME_FLAG, phBRKAFTER }, { "rhotic", tPHONEME_FLAG, phRHOTIC }, { "lengthenstop", tPHONEME_FLAG, phLENGTHENSTOP }, { "nopause", tPHONEME_FLAG, phNOPAUSE }, { "prevoice", tPHONEME_FLAG, phPREVOICE }, { "flag1", tPHONEME_FLAG, phFLAG1 }, { "flag2", tPHONEME_FLAG, phFLAG2 }, // vowel transition attributes { "len=", tTRANSITION, 1 }, { "rms=", tTRANSITION, 2 }, { "f1=", tTRANSITION, 3 }, { "f2=", tTRANSITION, 4 }, { "f3=", tTRANSITION, 5 }, { "brk", tTRANSITION, 6 }, { "rate", tTRANSITION, 7 }, { "glstop", tTRANSITION, 8 }, { "lenadd", tTRANSITION, 9 }, { "f4", tTRANSITION, 10 }, { "gpaus", tTRANSITION, 11 }, { "colr=", tTRANSITION, 12 }, { "amp=", tTRANSITION, 13 }, // set rms of 1st frame as fraction of rms of 2nd frame (1/30ths) { NULL, 0, -1 } }; static const keywtab_t *keyword_tabs[] = { keywords, k_conditions, k_properties, k_intonation }; typedef struct { void *link; int value; int ph_mnemonic; short ph_table; char string[1]; } REF_HASH_TAB; typedef struct { FILE *file; int linenum; char fname[80]; } STACK; typedef struct { unsigned short *p_then; unsigned short *p_else; bool returned; } IF_STACK; enum { tENDFILE = 1, tSTRING, tNUMBER, tSIGNEDNUMBER, tPHONEMEMNEM, tOPENBRACKET, tKEYWORD, tCONDITION, tPROPERTIES, tINTONATION, }; typedef struct CompileContext { PHONEME_TAB *phoneme_out; int n_phcodes_list[N_PHONEME_TABS]; PHONEME_TAB_LIST phoneme_tab_list2[N_PHONEME_TABS]; PHONEME_TAB *phoneme_tab2; int phoneme_flags; #define N_PROCS 50 int n_procs; int proc_addr[N_PROCS]; char proc_names[N_ITEM_STRING+1][N_PROCS]; #define MAX_PROG_BUF 2000 unsigned short *prog_out; unsigned short *prog_out_max; unsigned short prog_buf[MAX_PROG_BUF+20]; int n_phoneme_tabs; int n_phcodes; // outout files FILE *f_phdata; FILE *f_phindex; FILE *f_phtab; FILE *f_phcontents; FILE *f_errors; FILE *f_prog_log; FILE *f_in; int f_in_linenum; int f_in_displ; int linenum; int count_references; int duplicate_references; int count_frames; int error_count; int then_count; bool after_if; char current_fname[80]; REF_HASH_TAB *ref_hash_tab[256]; #define N_STACK 12 int stack_ix; STACK stack[N_STACK]; #define N_IF_STACK 12 int if_level; IF_STACK if_stack[N_IF_STACK]; int item_type; int item_terminator; char item_string[N_ITEM_STRING]; NAMETAB *manifest; int n_manifest; char phsrc[sizeof(path_home)+40]; // Source: path to the 'phonemes' source file. } CompileContext; static void clean_context(CompileContext *ctx) { for (int i = 0; i < 256; i++) { REF_HASH_TAB *p; while ((p = ctx->ref_hash_tab[i])) { ctx->ref_hash_tab[i] = (REF_HASH_TAB*)p->link; free(p); } } for (int i = 0; i < ctx->n_manifest; i++) { free(ctx->manifest[i].name); } free(ctx->manifest); free(ctx); } static void error(CompileContext *ctx, const char *format, ...) { va_list args; va_start(args, format); fprintf(ctx->f_errors, "%s(%d): ", ctx->current_fname, ctx->linenum-1); vfprintf(ctx->f_errors, format, args); fprintf(ctx->f_errors, "\n"); ctx->error_count++; va_end(args); } static void error_from_status(CompileContext *ctx, espeak_ng_STATUS status, const char *context) { char message[512]; espeak_ng_GetStatusCodeMessage(status, message, sizeof(message)); if (context) error(ctx, "%s: '%s'.", message, context); else error(ctx, "%s.", message); } static espeak_ng_STATUS ReadPhondataManifest(CompileContext *ctx, espeak_ng_ERROR_CONTEXT *context) { // Read the phondata-manifest file FILE *f; int n_lines = 0; char *p; unsigned int value; char buf[sizeof(path_home)+40]; char name[120]; sprintf(buf, "%s%c%s", path_home, PATHSEP, "phondata-manifest"); if ((f = fopen(buf, "r")) == NULL) return create_file_error_context(context, errno, buf); while (fgets(buf, sizeof(buf), f) != NULL) n_lines++; rewind(f); if (ctx->manifest != NULL) { for (int ix = 0; ix < ctx->n_manifest; ix++) free(ctx->manifest[ix].name); } if (n_lines == 0) { fclose(f); return ENS_EMPTY_PHONEME_MANIFEST; } NAMETAB *new_manifest = (NAMETAB *)realloc(ctx->manifest, n_lines * sizeof(NAMETAB)); if (new_manifest == NULL) { fclose(f); free(ctx->manifest); return ENOMEM; } else ctx->manifest = new_manifest; ctx->n_manifest = 0; while (fgets(buf, sizeof(buf), f) != NULL) { if (!isalpha(buf[0])) continue; if (sscanf(&buf[2], "%x %s", &value, name) == 2) { if ((p = (char *)malloc(strlen(name)+1)) != NULL) { strcpy(p, name); ctx->manifest[ctx->n_manifest].value = value; ctx->manifest[ctx->n_manifest].name = p; ctx->n_manifest++; } } } fclose(f); return ENS_OK; } static const MNEM_TAB reserved_phonemes[] = { { "_\001", phonCONTROL }, // NOT USED { "%", phonSTRESS_U }, { "%%", phonSTRESS_D }, { ",", phonSTRESS_2 }, { ",,", phonSTRESS_3 }, { "'", phonSTRESS_P }, { "''", phonSTRESS_P2 }, { "=", phonSTRESS_PREV }, // stress previous syllable { "_:", phonPAUSE }, // pause { "_", phonPAUSE_SHORT }, // short pause { "_!", phonPAUSE_NOLINK }, // short pause, no link { ":", phonLENGTHEN }, { "@", phonSCHWA }, { "@-", phonSCHWA_SHORT }, { "||", phonEND_WORD }, { "1", phonDEFAULTTONE }, // (numeral 1) default tone (for tone language) { "#X1", phonCAPITAL }, // capital letter indication { "?", phonGLOTTALSTOP }, // glottal stop { "-", phonSYLLABIC }, // syllabic consonant { "_^_", phonSWITCH }, // Change language { "_X1", phonX1 }, // a language specific action { "_|", phonPAUSE_VSHORT }, // very short pause { "_::", phonPAUSE_LONG }, // long pause { "t#", phonT_REDUCED }, // reduced version of [t] { "'!", phonSTRESS_TONIC }, // stress - emphasized { "_;_", phonPAUSE_CLAUSE }, // clause pause { "#@", phonVOWELTYPES }, // vowel type groups, these must be consecutive { "#a", phonVOWELTYPES+1 }, { "#e", phonVOWELTYPES+2 }, { "#i", phonVOWELTYPES+3 }, { "#o", phonVOWELTYPES+4 }, { "#u", phonVOWELTYPES+5 }, { NULL, 0 } }; static void ReservePhCodes(CompileContext *ctx) { // Reserve phoneme codes which have fixed numbers so that they can be // referred to from the program code. const MNEM_TAB *p; p = reserved_phonemes; while (p->mnem != NULL) { ctx->phoneme_tab2[p->value].mnemonic = StringToWord(p->mnem); ctx->phoneme_tab2[p->value].code = p->value; if (ctx->n_phcodes <= p->value) ctx->n_phcodes = p->value+1; p++; } } static int LookupPhoneme(CompileContext *ctx, const char *string, int control) { // control = 0 explicit declaration // control = 1 declare phoneme if not found // control = 2 start looking after control & stress phonemes int ix; int start; int use; unsigned int word; if (strcmp(string, "NULL") == 0) return 1; ix = strlen(string); if ((ix == 0) || (ix > 4)) error(ctx, "Bad phoneme name '%s'", string); word = StringToWord(string); // don't use phoneme number 0, reserved for string terminator start = 1; if (control == 2) { // don't look for control and stress phonemes (allows these characters to be // used for other purposes) start = 8; } use = 0; for (ix = start; ix < ctx->n_phcodes; ix++) { if (ctx->phoneme_tab2[ix].mnemonic == word) return ix; if ((use == 0) && (ctx->phoneme_tab2[ix].mnemonic == 0)) use = ix; } if (use == 0) { if (control == 0) return -1; if (ctx->n_phcodes >= N_PHONEME_TAB-1) return -1; // phoneme table is full use = ctx->n_phcodes++; } // add this phoneme to the phoneme table ctx->phoneme_tab2[use].mnemonic = word; ctx->phoneme_tab2[use].type = phINVALID; ctx->phoneme_tab2[use].program = ctx->linenum; // for error report if the phoneme remains undeclared return use; } static unsigned int get_char(CompileContext *ctx) { unsigned int c; c = fgetc(ctx->f_in); if (c == '\n') ctx->linenum++; return c; } static void unget_char(CompileContext *ctx, unsigned int c) { ungetc(c, ctx->f_in); if (c == '\n') ctx->linenum--; } static int CheckNextChar(CompileContext *ctx) { int c; while (((c = get_char(ctx)) == ' ') || (c == '\t')) ; unget_char(ctx, c); return c; } static int NextItem(CompileContext *ctx, int type) { unsigned char c = 0; unsigned char c2; int ix; const keywtab_t *pk; ctx->item_type = -1; ctx->f_in_displ = ftell(ctx->f_in); ctx->f_in_linenum = ctx->linenum; while (!feof(ctx->f_in)) { c = get_char(ctx); if (c == '/') { if ((c2 = get_char(ctx)) == '/') { // comment, ignore to end of line while (!feof(ctx->f_in) && ((c = get_char(ctx)) != '\n')) ; } else unget_char(ctx, c2); } if (!isspace(c)) break; } if (feof(ctx->f_in)) return -2; if (c == '(') { if (type == tOPENBRACKET) return 1; return -1; } ix = 0; while (!feof(ctx->f_in) && !isspace(c) && (c != '(') && (c != ')') && (c != ',')) { if (c == '\\') c = get_char(ctx); ctx->item_string[ix++] = c; c = get_char(ctx); if (feof(ctx->f_in)) break; if (ctx->item_string[ix-1] == '=') break; } ctx->item_string[ix] = 0; while (isspace(c)) c = get_char(ctx); ctx->item_terminator = ' '; if ((c == ')') || (c == '(') || (c == ',')) ctx->item_terminator = c; if (!feof(ctx->f_in) && !(c == ')' || c == ',')) unget_char(ctx, c); if (type == tSTRING) return 0; if ((type == tNUMBER) || (type == tSIGNEDNUMBER)) { int acc = 0; int sign = 1; char *p; p = ctx->item_string; if ((*p == '-') && (type == tSIGNEDNUMBER)) { sign = -1; p++; } if (!isdigit(*p)) { if ((type == tNUMBER) && (*p == '-')) error(ctx, "Expected an unsigned number"); else error(ctx, "Expected a number"); } while (isdigit(*p)) { acc *= 10; acc += (*p - '0'); p++; } return acc * sign; } if ((type >= tKEYWORD) && (type <= tINTONATION)) { pk = keyword_tabs[type-tKEYWORD]; while (pk->mnem != NULL) { if (strcmp(ctx->item_string, pk->mnem) == 0) { ctx->item_type = pk->type; return pk->data; } pk++; } ctx->item_type = -1; return -1; // keyword not found } if (type == tPHONEMEMNEM) return LookupPhoneme(ctx, ctx->item_string, 2); return -1; } static int NextItemMax(CompileContext *ctx, int max) { // Get a number, but restrict value to max int value; value = NextItem(ctx, tNUMBER); if (value > max) { error(ctx, "Value %d is greater than maximum %d", value, max); value = max; } return value; } static int NextItemBrackets(CompileContext *ctx, int type, int control) { // Expect a parameter inside parentheses // control: bit 0 0= need ( // bit 1 1= allow comma int value; if ((control & 1) == 0) { if (!NextItem(ctx, tOPENBRACKET)) error(ctx, "Expected '('"); } value = NextItem(ctx, type); if ((control & 2) && (ctx->item_terminator == ',')) return value; if (ctx->item_terminator != ')') error(ctx, "Expected ')'"); return value; } static void UngetItem(CompileContext *ctx) { fseek(ctx->f_in, ctx->f_in_displ, SEEK_SET); ctx->linenum = ctx->f_in_linenum; } static int Range(int value, int divide, int min, int max) { if (value < 0) value -= divide/2; else value += divide/2; value = value / divide; if (value > max) value = max; if (value < min) value = min; return value - min; } static int CompileVowelTransition(CompileContext *ctx, int which) { // Compile a vowel transition int len = 0; int rms = 0; int f1 = 0; int f2 = 0; int f2_min = 0; int f2_max = 0; int f3_adj = 0; int f3_amp = 0; int flags = 0; int vcolour = 0; int x; int instn = i_VOWELIN; int word1; int word2; if (which == 1) { len = 50 / 2; // defaults for transition into vowel rms = 25 / 2; if (ctx->phoneme_out->type == phSTOP) { len = 42 / 2; // defaults for transition into vowel rms = 30 / 2; } } else if (which == 2) { instn = i_VOWELOUT; len = 36 / 2; // defaults for transition out of vowel rms = 16 / 2; } for (;;) { int key = NextItem(ctx, tKEYWORD); if (ctx->item_type != tTRANSITION) { UngetItem(ctx); break; } switch (key & 0xf) { case 1: len = Range(NextItem(ctx, tNUMBER), 2, 0, 63) & 0x3f; flags |= 1; break; case 2: rms = Range(NextItem(ctx, tNUMBER), 2, 0, 31) & 0x1f; flags |= 1; break; case 3: f1 = NextItem(ctx, tNUMBER); break; case 4: f2 = Range(NextItem(ctx, tNUMBER), 50, 0, 63) & 0x3f; f2_min = Range(NextItem(ctx, tSIGNEDNUMBER), 50, -15, 15) & 0x1f; f2_max = Range(NextItem(ctx, tSIGNEDNUMBER), 50, -15, 15) & 0x1f; if (f2_min > f2_max) { x = f2_min; f2_min = f2_max; f2_max = x; } break; case 5: f3_adj = Range(NextItem(ctx, tSIGNEDNUMBER), 50, -15, 15) & 0x1f; f3_amp = Range(NextItem(ctx, tNUMBER), 8, 0, 15) & 0x1f; break; case 6: flags |= 2; // break break; case 7: flags |= 4; // rate break; case 8: flags |= 8; // glstop break; case 9: flags |= 16; // lenadd break; case 10: flags |= 32; // f4 break; case 11: flags |= 64; // pause break; case 12: vcolour = NextItem(ctx, tNUMBER); break; case 13: // set rms of 1st frame as fraction of rms of 2nd frame (1/30ths) rms = (Range(NextItem(ctx, tNUMBER), 1, 0, 31) & 0x1f) | 0x20; flags |= 1; break; } } word1 = len + (rms << 6) + (flags << 12); word2 = f2 + (f2_min << 6) + (f2_max << 11) + (f3_adj << 16) + (f3_amp << 21) + (f1 << 26) + (vcolour << 29); ctx->prog_out[0] = instn + ((word1 >> 16) & 0xff); ctx->prog_out[1] = word1; ctx->prog_out[2] = word2 >> 16; ctx->prog_out[3] = word2; ctx->prog_out += 4; return 0; } static espeak_ng_STATUS LoadSpect(CompileContext *ctx, const char *path, int control, int *addr) { SpectSeq *spectseq; int peak; int frame; int n_frames; int ix; int x, x2; int rms; float total; float pkheight; int marker1_set = 0; int frame_vowelbreak = 0; int klatt_flag = 0; SpectFrame *fr; frame_t *fr_out; char filename[sizeof(path_home)+20]; SPECT_SEQ seq_out; SPECT_SEQK seqk_out; // create SpectSeq and import data spectseq = SpectSeqCreate(); if (spectseq == NULL) return ENOMEM; snprintf(filename, sizeof(filename), "%s/%s", ctx->phsrc, path); espeak_ng_STATUS status = LoadSpectSeq(spectseq, filename); if (status != ENS_OK) { error(ctx, "Bad vowel file: '%s'", path); SpectSeqDestroy(spectseq); return status; } // do we need additional klatt data ? for (frame = 0; frame < spectseq->numframes; frame++) { for (ix = 5; ix < N_KLATTP2; ix++) { if (spectseq->frames[frame]->klatt_param[ix] != 0) klatt_flag = FRFLAG_KLATT; } } *addr = ftell(ctx->f_phdata); seq_out.n_frames = 0; seq_out.sqflags = 0; seq_out.length_total = 0; total = 0; for (frame = 0; frame < spectseq->numframes; frame++) { if (spectseq->frames[frame]->keyframe) { if (seq_out.n_frames == 1) frame_vowelbreak = frame; if (spectseq->frames[frame]->markers & 0x2) { // marker 1 is set marker1_set = 1; } seq_out.n_frames++; if (frame > 0) total += spectseq->frames[frame-1]->length; } } seq_out.length_total = (int)total; if ((control & 1) && (marker1_set == 0)) { // This is a vowel, but no Vowel Break marker is set // set a marker flag for the second frame of a vowel spectseq->frames[frame_vowelbreak]->markers |= FRFLAG_VOWEL_CENTRE; } n_frames = 0; for (frame = 0; frame < spectseq->numframes; frame++) { fr = spectseq->frames[frame]; if (fr->keyframe) { if (klatt_flag) fr_out = &seqk_out.frame[n_frames]; else fr_out = (frame_t *)&seq_out.frame[n_frames]; x = (int)(fr->length + 0.5); // round to nearest mS if (x > 255) x = 255; fr_out->length = x; fr_out->frflags = fr->markers | klatt_flag; rms = (int)GetFrameRms(fr, spectseq->amplitude); if (rms > 255) rms = 255; fr_out->rms = rms; if (n_frames == (seq_out.n_frames-1)) fr_out->length = 0; // give last frame zero length // write: peak data ctx->count_frames++; for (peak = 0; peak < 8; peak++) { if (peak < 7) fr_out->ffreq[peak] = fr->peaks[peak].pkfreq; pkheight = spectseq->amplitude * fr->amp_adjust * fr->peaks[peak].pkheight; pkheight = pkheight/640000; if (pkheight > 255) pkheight = 255; fr_out->fheight[peak] = (int)pkheight; if (peak < 6) { x = fr->peaks[peak].pkwidth/4; if (x > 255) x = 255; fr_out->fwidth[peak] = x; if (peak < 3) { x2 = fr->peaks[peak].pkright/4; if (x2 > 255) x2 = 255; fr_out->fright[peak] = x2; } } if (peak < 4) { x = fr->peaks[peak].klt_bw / 2; if (x > 255) x = 255; fr_out->bw[peak] = x; } } for (ix = 0; ix < 5; ix++) { fr_out->klattp[ix] = fr->klatt_param[ix]; fr_out->klattp[KLATT_FNZ] = fr->klatt_param[KLATT_FNZ] / 2; } if (klatt_flag) { // additional klatt parameters for (ix = 0; ix < 5; ix++) fr_out->klattp2[ix] = fr->klatt_param[ix+5]; for (peak = 0; peak < 7; peak++) { fr_out->klatt_ap[peak] = fr->peaks[peak].klt_ap; x = fr->peaks[peak].klt_bp / 2; if (x > 255) x = 255; fr_out->klatt_bp[peak] = x; } fr_out->spare = 0; } if (fr_out->bw[1] == 0) { fr_out->bw[0] = 89 / 2; fr_out->bw[1] = 90 / 2; fr_out->bw[2] = 140 / 2; fr_out->bw[3] = 260 / 2; } n_frames++; } } if (klatt_flag) { seqk_out.n_frames = seq_out.n_frames; seqk_out.sqflags = seq_out.sqflags; seqk_out.length_total = seq_out.length_total; ix = (char *)(&seqk_out.frame[seqk_out.n_frames]) - (char *)(&seqk_out); fwrite(&seqk_out, ix, 1, ctx->f_phdata); while (ix & 3) { // round up to multiple of 4 bytes fputc(0, ctx->f_phdata); ix++; } } else { ix = (char *)(&seq_out.frame[seq_out.n_frames]) - (char *)(&seq_out); fwrite(&seq_out, ix, 1, ctx->f_phdata); while (ix & 3) { // round up to multiple of 4 bytes fputc(0, ctx->f_phdata); ix++; } } SpectSeqDestroy(spectseq); return ENS_OK; } static int LoadWavefile(CompileContext *ctx, FILE *f, const char *fname) { int displ; unsigned char c1; int sample; int sample2; float x; int max = 0; int length; int sr1, sr2; int scale_factor = 0; fseek(f, 24, SEEK_SET); sr1 = Read4Bytes(f); sr2 = Read4Bytes(f); fseek(f, 40, SEEK_SET); if ((sr1 != samplerate) || (sr2 != sr1*2)) { if (sr1 != samplerate) error(ctx, "Can't resample (%d to %d): %s", sr1, samplerate, fname); else error(ctx, "WAV file is not mono: %s", fname); return 0; } displ = ftell(ctx->f_phdata); // data contains: 4 bytes of length (n_samples * 2), followed by 2-byte samples (lsb byte first) length = Read4Bytes(f); while (true) { int c; if ((c = fgetc(f)) == EOF) break; c1 = (unsigned char)c; if ((c = fgetc(f)) == EOF) break; sample = CalculateSample((unsigned char) c, c1); if (sample > max) max = sample; else if (sample < -max) max = -sample; } scale_factor = (max / 127) + 1; #define MIN_FACTOR -1 // was 6, disable use of 16 bit samples if (scale_factor > MIN_FACTOR) { length = length/2 + (scale_factor << 16); } Write4Bytes(ctx->f_phdata, length); fseek(f, 44, SEEK_SET); while (!feof(f)) { c1 = fgetc(f); unsigned char c3 = fgetc(f); sample = CalculateSample(c3, c1); if (feof(f)) break; if (scale_factor <= MIN_FACTOR) { fputc(sample & 0xff, ctx->f_phdata); fputc(sample >> 8, ctx->f_phdata); } else { x = ((float)sample / scale_factor) + 0.5; sample2 = (int)x; if (sample2 > 127) sample2 = 127; if (sample2 < -128) sample2 = -128; fputc(sample2, ctx->f_phdata); } } length = ftell(ctx->f_phdata); while ((length & 3) != 0) { // pad to a multiple of 4 bytes fputc(0, ctx->f_phdata); length++; } return displ | 0x800000; // set bit 23 to indicate a wave file rather than a spectrum } static espeak_ng_STATUS LoadEnvelope(CompileContext *ctx, FILE *f, int *displ) { char buf[128]; if (displ) *displ = ftell(ctx->f_phdata); if (fseek(f, 12, SEEK_SET) == -1) return errno; if (fread(buf, 128, 1, f) != 128) return errno; fwrite(buf, 128, 1, ctx->f_phdata); return ENS_OK; } // Generate a hash code from the specified string static int Hash8(const char *string) { int c; int chars = 0; int hash = 0; while ((c = *string++) != 0) { c = tolower(c) - 'a'; hash = hash * 8 + c; hash = (hash & 0x1ff) ^ (hash >> 8); // exclusive or chars++; } return (hash+chars) & 0xff; } static int LoadEnvelope2(CompileContext *ctx, FILE *f) { int displ; int n_points; char line_buf[128]; float env_x[20]; float env_y[20]; int env_lin[20]; unsigned char env[ENV_LEN]; n_points = 0; if (fgets(line_buf, sizeof(line_buf), f) != NULL) { ; // skip first line, then loop while (!feof(f)) { if (fgets(line_buf, sizeof(line_buf), f) == NULL) break; env_lin[n_points] = 0; int n = sscanf(line_buf, "%f %f %d", &env_x[n_points], &env_y[n_points], &env_lin[n_points]); if (n >= 2) { env_x[n_points] *= (float)1.28; // convert range 0-100 to 0-128 n_points++; } } } if (n_points > 0) { env_x[n_points] = env_x[n_points-1]; env_y[n_points] = env_y[n_points-1]; int ix = 0; int ix2 = 0; for (int x = 0; x < ENV_LEN; x++) { if (n_points > 3 && x > env_x[ix+3]) ix++; if (n_points > 2 && x >= env_x[ix2+1]) ix2++; int y; if (env_lin[ix2] > 0) { y = (env_y[ix2] + (env_y[ix2+1] - env_y[ix2]) * ((float)x - env_x[ix2]) / (env_x[ix2+1] - env_x[ix2])) * 2.55; } else if (n_points > 3) y = (int)(polint(&env_x[ix], &env_y[ix], 4, x) * 255 / 100); // convert to range 0-255 else y = (int)(polint(&env_x[ix], &env_y[ix], 3, x) * 255 / 100); if (y < 0) y = 0; if (y > 255) y = 255; env[x] = y; } } displ = ftell(ctx->f_phdata); fwrite(env, 1, ENV_LEN, ctx->f_phdata); return displ; } static espeak_ng_STATUS LoadDataFile(CompileContext *ctx, const char *path, int control, int *addr) { // load spectrum sequence or sample data from a file. // return index into spect or sample data area. bit 23=1 if a sample int hash; REF_HASH_TAB *p, *p2; if (strcmp(path, "NULL") == 0) return ENS_OK; if (strcmp(path, "DFT") == 0) { *addr = 1; return ENS_OK; } ctx->count_references++; hash = Hash8(path); p = ctx->ref_hash_tab[hash]; while (p != NULL) { if (strcmp(path, p->string) == 0) { ctx->duplicate_references++; *addr = p->value; // already loaded this data break; } p = (REF_HASH_TAB *)p->link; } if (*addr == 0) { char buf[sizeof(path_home)+150]; sprintf(buf, "%s/%s", ctx->phsrc, path); FILE *f; if ((f = fopen(buf, "rb")) == NULL) { sprintf(buf, "%s/%s.wav", ctx->phsrc, path); if ((f = fopen(buf, "rb")) == NULL) { error(ctx, "Can't read file: %s", path); return errno; } } int id = Read4Bytes(f); rewind(f); espeak_ng_STATUS status = ENS_OK; int type_code = ' '; if (id == 0x43455053) { status = LoadSpect(ctx, path, control, addr); type_code = 'S'; } else if (id == 0x46464952) { *addr = LoadWavefile(ctx, f, path); type_code = 'W'; } else if (id == 0x43544950) { status = LoadEnvelope(ctx, f, addr); type_code = 'E'; } else if (id == 0x45564E45) { *addr = LoadEnvelope2(ctx, f); type_code = 'E'; } else { error(ctx, "File not SPEC or RIFF: %s", path); *addr = -1; status = ENS_UNSUPPORTED_PHON_FORMAT; } fclose(f); if (status != ENS_OK) return status; if (*addr > 0) fprintf(ctx->f_phcontents, "%c 0x%.5x %s\n", type_code, *addr & 0x7fffff, path); } // add this item to the hash table if (*addr > 0) { p = ctx->ref_hash_tab[hash]; p2 = (REF_HASH_TAB *)malloc(sizeof(REF_HASH_TAB)+strlen(path)+1); if (p2 == NULL) return ENOMEM; p2->value = *addr; p2->ph_mnemonic = ctx->phoneme_out->mnemonic; // phoneme which uses this file p2->ph_table = ctx->n_phoneme_tabs-1; strcpy(p2->string, path); p2->link = (char *)p; ctx->ref_hash_tab[hash] = p2; } return ENS_OK; } static void CompileToneSpec(CompileContext *ctx) { int pitch1 = 0; int pitch2 = 0; int pitch_env = 0; int amp_env = 0; pitch1 = NextItemBrackets(ctx, tNUMBER, 2); pitch2 = NextItemBrackets(ctx, tNUMBER, 3); if (ctx->item_terminator == ',') { NextItemBrackets(ctx, tSTRING, 3); LoadDataFile(ctx, ctx->item_string, 0, &pitch_env); } if (ctx->item_terminator == ',') { NextItemBrackets(ctx, tSTRING, 1); LoadDataFile(ctx, ctx->item_string, 0, &_env); } if (pitch1 < pitch2) { ctx->phoneme_out->start_type = pitch1; ctx->phoneme_out->end_type = pitch2; } else { ctx->phoneme_out->start_type = pitch2; ctx->phoneme_out->end_type = pitch1; } if (pitch_env != 0) { *ctx->prog_out++ = i_PITCHENV + ((pitch_env >> 16) & 0xf); *ctx->prog_out++ = pitch_env; } if (amp_env != 0) { *ctx->prog_out++ = i_AMPENV + ((amp_env >> 16) & 0xf); *ctx->prog_out++ = amp_env; } } static void CompileSound(CompileContext *ctx, int keyword, int isvowel) { int addr = 0; int value = 0; char path[N_ITEM_STRING]; static const int sound_instns[] = { i_FMT, i_WAV, i_VWLSTART, i_VWLENDING, i_WAVADD }; NextItemBrackets(ctx, tSTRING, 2); strcpy(path, ctx->item_string); if (ctx->item_terminator == ',') { if ((keyword == kVOWELSTART) || (keyword == kVOWELENDING)) { value = NextItemBrackets(ctx, tSIGNEDNUMBER, 1); if (value > 127) { value = 127; error(ctx, "Parameter > 127"); } if (value < -128) { value = -128; error(ctx, "Parameter < -128"); } } else { value = NextItemBrackets(ctx, tNUMBER, 1); if (value > 255) { value = 255; error(ctx, "Parameter > 255"); } } } LoadDataFile(ctx, path, isvowel, &addr); addr = addr / 4; // addr is words not bytes *ctx->prog_out++ = sound_instns[keyword-kFMT] + ((value & 0xff) << 4) + ((addr >> 16) & 0xf); *ctx->prog_out++ = addr & 0xffff; } /* Condition bits 14,15 1 bit 13 1 = AND, 0 = OR bit 12 spare bit 8-11 =0-3 p,t,n,n2 data=phoneme code =4-7 p,t,n,n2 data=(bits5-7: phtype, place, property, special) (bits0-4: data) =8 data = stress bitmap =9 special tests */ static int CompileIf(CompileContext *ctx, int elif) { bool finish = false; int word = 0; int data; int bitmap; int brackets; unsigned short *prog_last_if = NULL; ctx->then_count = 2; ctx->after_if = true; while (!finish) { bool not_flag = false; int word2 = 0; if (ctx->prog_out >= ctx->prog_out_max) { error(ctx, "Phoneme program too large"); return 0; } int key; if ((key = NextItem(ctx, tCONDITION)) < 0) error(ctx, "Expected a condition, not '%s'", ctx->item_string); if ((ctx->item_type == 0) && (key == k_NOT)) { not_flag = true; if ((key = NextItem(ctx, tCONDITION)) < 0) error(ctx, "Expected a condition, not '%s'", ctx->item_string); } if (ctx->item_type == tWHICH_PHONEME) { // prevPh(), thisPh(), nextPh(), next2Ph() etc if (key >= 6) { // put the 'which' code in the next instruction word2 = key; key = 6; } key = key << 8; data = NextItemBrackets(ctx, tPROPERTIES, 0); if (data >= 0) word = key + data + 0x700; else { data = LookupPhoneme(ctx, ctx->item_string, 2); word = key + data; } } else if (ctx->item_type == tTEST) { if (key == kTHISSTRESS) { bitmap = 0; brackets = 2; do { data = NextItemBrackets(ctx, tNUMBER, brackets); if (data > 7) error(ctx, "Expected list of stress levels"); bitmap |= (1 << data); brackets = 3; } while (ctx->item_terminator == ','); word = i_StressLevel | bitmap; } else word = key; } else { error(ctx, "Unexpected keyword '%s'", ctx->item_string); if ((strcmp(ctx->item_string, "phoneme") == 0) || (strcmp(ctx->item_string, "endphoneme") == 0)) return -1; } // output the word prog_last_if = ctx->prog_out; *ctx->prog_out++ = word | i_CONDITION; if (word2 != 0) *ctx->prog_out++ = word2; if (not_flag) *ctx->prog_out++ = i_NOT; // expect AND, OR, THEN switch (NextItem(ctx, tCONDITION)) { case k_AND: break; case k_OR: if (prog_last_if != NULL) *prog_last_if |= i_OR; break; case k_THEN: finish = true; break; default: error(ctx, "Expected AND, OR, THEN"); break; } } if (elif == 0) { ctx->if_level++; ctx->if_stack[ctx->if_level].p_else = NULL; } ctx->if_stack[ctx->if_level].returned = false; ctx->if_stack[ctx->if_level].p_then = ctx->prog_out; *ctx->prog_out++ = i_JUMP_FALSE; return 0; } static void FillThen(CompileContext *ctx, int add) { unsigned short *p; p = ctx->if_stack[ctx->if_level].p_then; if (p != NULL) { int offset = ctx->prog_out - p + add; if ((ctx->then_count == 1) && (ctx->if_level == 1)) { // The THEN part only contains one statement, we can remove the THEN jump // and the interpreter will implicitly skip the statement. while (p < ctx->prog_out) { p[0] = p[1]; p++; } ctx->prog_out--; } else { if (offset > MAX_JUMP) error(ctx, "IF block is too long"); *p = i_JUMP_FALSE + offset; } ctx->if_stack[ctx->if_level].p_then = NULL; } ctx->then_count = 0; } static int CompileElse(CompileContext *ctx) { unsigned short *ref; if (ctx->if_level < 1) { error(ctx, "ELSE not expected"); return 0; } if (ctx->if_stack[ctx->if_level].returned == false) FillThen(ctx, 1); else FillThen(ctx, 0); if (ctx->if_stack[ctx->if_level].returned == false) { ref = ctx->prog_out; *ctx->prog_out++ = 0; unsigned short *p; if ((p = ctx->if_stack[ctx->if_level].p_else) != NULL) *ref = ref - p; // backwards offset to the previous else ctx->if_stack[ctx->if_level].p_else = ref; } return 0; } static int CompileElif(CompileContext *ctx) { if (ctx->if_level < 1) { error(ctx, "ELIF not expected"); return 0; } CompileElse(ctx); CompileIf(ctx, 1); return 0; } static int CompileEndif(CompileContext *ctx) { unsigned short *p; if (ctx->if_level < 1) { error(ctx, "ENDIF not expected"); return 0; } FillThen(ctx, 0); if ((p = ctx->if_stack[ctx->if_level].p_else) != NULL) { int chain; do { chain = *p; // a chain of previous else links int offset = ctx->prog_out - p; if (offset > MAX_JUMP) error(ctx, "IF block is too long"); *p = i_JUMP + offset; p -= chain; } while (chain > 0); } ctx->if_level--; return 0; } static int CompileSwitch(CompileContext *ctx, int type) { // Type 0: EndSwitch // 1: SwitchPrevVowelType // 2: SwitchNextVowelType if (type == 0) { // check the instructions in the Switch return 0; } if (type == 1) *ctx->prog_out++ = i_SWITCH_PREVVOWEL+6; if (type == 2) *ctx->prog_out++ = i_SWITCH_NEXTVOWEL+6; return 0; } static PHONEME_TAB_LIST *FindPhonemeTable(CompileContext *ctx, const char *string) { int ix; for (ix = 0; ix < ctx->n_phoneme_tabs; ix++) { if (strcmp(ctx->phoneme_tab_list2[ix].name, string) == 0) return &ctx->phoneme_tab_list2[ix]; } error(ctx, "compile: unknown phoneme table: '%s'", string); return NULL; } static PHONEME_TAB *FindPhoneme(CompileContext *ctx, const char *string) { PHONEME_TAB_LIST *phtab = NULL; int ix; unsigned int mnem; char *phname; char buf[200]; // is this the name of a phoneme which is in scope if ((strlen(string) <= 4) && ((ix = LookupPhoneme(ctx, string, 0)) != -1)) return &ctx->phoneme_tab2[ix]; // no, treat the name as phonemetable/phoneme strcpy(buf, string); if ((phname = strchr(buf, '/')) != 0) *phname++ = 0; phtab = FindPhonemeTable(ctx, buf); if (phtab == NULL) return NULL; // phoneme table not found mnem = StringToWord(phname); for (ix = 1; ix < 256; ix++) { if (mnem == phtab->phoneme_tab_ptr[ix].mnemonic) return &phtab->phoneme_tab_ptr[ix]; } error(ctx, "Phoneme reference not found: '%s'", string); return NULL; } static void ImportPhoneme(CompileContext *ctx) { unsigned int ph_mnem; unsigned int ph_code; PHONEME_TAB *ph; NextItem(ctx, tSTRING); if ((ph = FindPhoneme(ctx, ctx->item_string)) == NULL) { error(ctx, "Cannot find phoneme '%s' to import.", ctx->item_string); return; } if (ctx->phoneme_out->phflags != 0 || ctx->phoneme_out->type != phINVALID || ctx->phoneme_out->start_type != 0 || ctx->phoneme_out->end_type != 0 || ctx->phoneme_out->std_length != 0 || ctx->phoneme_out->length_mod != 0) { error(ctx, "Phoneme import will override set properties."); } ph_mnem = ctx->phoneme_out->mnemonic; ph_code = ctx->phoneme_out->code; memcpy(ctx->phoneme_out, ph, sizeof(PHONEME_TAB)); ctx->phoneme_out->mnemonic = ph_mnem; ctx->phoneme_out->code = ph_code; if (ctx->phoneme_out->type != phVOWEL) ctx->phoneme_out->end_type = 0; // voicingswitch, this must be set later to refer to a local phoneme } static void CallPhoneme(CompileContext *ctx) { PHONEME_TAB *ph; int ix; int addr = 0; NextItem(ctx, tSTRING); // first look for a procedure name for (ix = 0; ix < ctx->n_procs; ix++) { if (strcmp(ctx->proc_names[ix], ctx->item_string) == 0) { addr = ctx->proc_addr[ix]; break; } } if (ix == ctx->n_procs) { // procedure not found, try a phoneme name if ((ph = FindPhoneme(ctx, ctx->item_string)) == NULL) return; addr = ph->program; if (ctx->phoneme_out->type == phINVALID) { // Phoneme type has not been set. Copy it from the called phoneme ctx->phoneme_out->type = ph->type; ctx->phoneme_out->start_type = ph->start_type; ctx->phoneme_out->end_type = ph->end_type; ctx->phoneme_out->std_length = ph->std_length; ctx->phoneme_out->length_mod = ph->length_mod; ctx->phoneme_flags = ph->phflags & ~phARTICULATION; } } *ctx->prog_out++ = i_CALLPH + (addr >> 16); *ctx->prog_out++ = addr; } static void DecThenCount(CompileContext *ctx) { if (ctx->then_count > 0) ctx->then_count--; } static int CompilePhoneme(CompileContext *ctx, int compile_phoneme) { int endphoneme = 0; int value; int phcode = 0; int flags; int ix; int start; int count; int c; char *p; int vowel_length_factor = 100; // for testing char number_buf[12]; char ipa_buf[N_ITEM_STRING+1]; PHONEME_TAB phoneme_out2; PHONEME_PROG_LOG phoneme_prog_log; ctx->prog_out = ctx->prog_buf; ctx->prog_out_max = &ctx->prog_buf[MAX_PROG_BUF-1]; ctx->if_level = 0; ctx->if_stack[0].returned = false; ctx->after_if = false; ctx->phoneme_flags = 0; NextItem(ctx, tSTRING); if (compile_phoneme) { phcode = LookupPhoneme(ctx, ctx->item_string, 1); // declare phoneme if not already there if (phcode == -1) return 0; ctx->phoneme_out = &ctx->phoneme_tab2[phcode]; } else { // declare a procedure if (ctx->n_procs >= N_PROCS) { error(ctx, "Too many procedures"); return 0; } strcpy(ctx->proc_names[ctx->n_procs], ctx->item_string); ctx->phoneme_out = &phoneme_out2; sprintf(number_buf, "%.3dP", ctx->n_procs); ctx->phoneme_out->mnemonic = StringToWord(number_buf); } ctx->phoneme_out->code = phcode; ctx->phoneme_out->program = 0; ctx->phoneme_out->type = phINVALID; ctx->phoneme_out->std_length = 0; ctx->phoneme_out->start_type = 0; ctx->phoneme_out->end_type = 0; ctx->phoneme_out->length_mod = 0; ctx->phoneme_out->phflags = 0; while (!endphoneme && !feof(ctx->f_in)) { int keyword; if ((keyword = NextItem(ctx, tKEYWORD)) < 0) { if (keyword == -2) { error(ctx, "Missing 'endphoneme' before end-of-file"); // end of file break; } phoneme_feature_t feature = phoneme_feature_from_string(ctx->item_string); espeak_ng_STATUS status = phoneme_add_feature(ctx->phoneme_out, feature); if (status == ENS_OK) continue; error_from_status(ctx, status, ctx->item_string); continue; } switch (ctx->item_type) { case tPHONEME_TYPE: if (ctx->phoneme_out->type != phINVALID) { if (ctx->phoneme_out->type == phFRICATIVE && keyword == phLIQUID) ; // apr liquid => ok else error(ctx, "More than one phoneme type: %s", ctx->item_string); } ctx->phoneme_out->type = keyword; break; case tPHONEME_FLAG: ctx->phoneme_flags |= keyword; break; case tINSTRN1: // instruction group 0, with 8 bit operands which set data in PHONEME_DATA switch (keyword) { case i_CHANGE_PHONEME: case i_APPEND_PHONEME: case i_APPEND_IFNEXTVOWEL: case i_INSERT_PHONEME: case i_REPLACE_NEXT_PHONEME: case i_VOICING_SWITCH: case i_CHANGE_IF | STRESS_IS_DIMINISHED: case i_CHANGE_IF | STRESS_IS_UNSTRESSED: case i_CHANGE_IF | STRESS_IS_NOT_STRESSED: case i_CHANGE_IF | STRESS_IS_SECONDARY: case i_CHANGE_IF | STRESS_IS_PRIMARY: value = NextItemBrackets(ctx, tPHONEMEMNEM, 0); *ctx->prog_out++ = (keyword << 8) + value; DecThenCount(ctx); break; case i_PAUSE_BEFORE: value = NextItemMax(ctx, 255); *ctx->prog_out++ = (i_PAUSE_BEFORE << 8) + value; DecThenCount(ctx); break; case i_PAUSE_AFTER: value = NextItemMax(ctx, 255); *ctx->prog_out++ = (i_PAUSE_AFTER << 8) + value; DecThenCount(ctx); break; case i_SET_LENGTH: value = NextItemMax(ctx, 511); if (ctx->phoneme_out->type == phVOWEL) value = (value * vowel_length_factor)/100; if (ctx->after_if == false) ctx->phoneme_out->std_length = value/2; else { *ctx->prog_out++ = (i_SET_LENGTH << 8) + value/2; DecThenCount(ctx); } break; case i_ADD_LENGTH: value = NextItem(ctx, tSIGNEDNUMBER) / 2; *ctx->prog_out++ = (i_ADD_LENGTH << 8) + (value & 0xff); DecThenCount(ctx); break; case i_LENGTH_MOD: value = NextItem(ctx, tNUMBER); ctx->phoneme_out->length_mod = value; break; case i_IPA_NAME: NextItem(ctx, tSTRING); if (strcmp(ctx->item_string, "NULL") == 0) strcpy(ctx->item_string, " "); // copy the string, recognize characters in the form U+9999 flags = 0; count = 0; ix = 1; for (p = ctx->item_string; *p != 0;) { p += utf8_in(&c, p); if ((c == '|') && (count > 0)) { // '|' means don't allow a tie or joiner before this letter flags |= (1 << (count -1)); } else if ((c == 'U') && (p[0] == '+')) { int j; // U+9999 p++; memcpy(number_buf, p, 4); // U+ should be followed by 4 hex digits number_buf[4] = 0; c = '#'; sscanf(number_buf, "%x", (unsigned int *)&c); // move past the 4 hexdecimal digits for (j = 0; j < 4; j++) { if (!isalnum(*p)) break; p++; } ix += utf8_out(c, &ipa_buf[ix]); count++; } else { ix += utf8_out(c, &ipa_buf[ix]); count++; } } ipa_buf[0] = flags; ipa_buf[ix] = 0; start = 1; if (flags != 0) start = 0; // only include the flags byte if bits are set value = strlen(&ipa_buf[start]); // number of UTF-8 bytes *ctx->prog_out++ = (i_IPA_NAME << 8) + value; for (ix = 0; ix < value; ix += 2) *ctx->prog_out++ = (ipa_buf[ix+start] << 8) + (ipa_buf[ix+start+1] & 0xff); DecThenCount(ctx); break; } break; case tSTATEMENT: switch (keyword) { case kIMPORT_PH: ImportPhoneme(ctx); ctx->phoneme_flags = ctx->phoneme_out->phflags; break; case kSTARTTYPE: phcode = NextItem(ctx, tPHONEMEMNEM); if (phcode == -1) phcode = LookupPhoneme(ctx, ctx->item_string, 1); ctx->phoneme_out->start_type = phcode; if (ctx->phoneme_out->type == phINVALID) error(ctx, "a phoneme type or manner of articulation must be specified before starttype"); break; case kENDTYPE: phcode = NextItem(ctx, tPHONEMEMNEM); if (phcode == -1) phcode = LookupPhoneme(ctx, ctx->item_string, 1); if (ctx->phoneme_out->type == phINVALID) error(ctx, "a phoneme type or manner of articulation must be specified before endtype"); else if (ctx->phoneme_out->type == phVOWEL) ctx->phoneme_out->end_type = phcode; else if (phcode != ctx->phoneme_out->start_type) error(ctx, "endtype must equal starttype for consonants"); break; case kVOICINGSWITCH: phcode = NextItem(ctx, tPHONEMEMNEM); if (phcode == -1) phcode = LookupPhoneme(ctx, ctx->item_string, 1); if (ctx->phoneme_out->type == phVOWEL) error(ctx, "voicingswitch cannot be used on vowels"); else ctx->phoneme_out->end_type = phcode; // use end_type field for consonants as voicing_switch break; case kSTRESSTYPE: value = NextItem(ctx, tNUMBER); ctx->phoneme_out->std_length = value; if (ctx->prog_out > ctx->prog_buf) { error(ctx, "stress phonemes can't contain program instructions"); ctx->prog_out = ctx->prog_buf; } break; case kIF: endphoneme = CompileIf(ctx, 0); break; case kELSE: endphoneme = CompileElse(ctx); break; case kELIF: endphoneme = CompileElif(ctx); break; case kENDIF: endphoneme = CompileEndif(ctx); break; case kENDSWITCH: break; case kSWITCH_PREVVOWEL: endphoneme = CompileSwitch(ctx, 1); break; case kSWITCH_NEXTVOWEL: endphoneme = CompileSwitch(ctx, 2); break; case kCALLPH: CallPhoneme(ctx); DecThenCount(ctx); break; case kFMT: ctx->if_stack[ctx->if_level].returned = true; DecThenCount(ctx); if (ctx->phoneme_out->type == phVOWEL) CompileSound(ctx, keyword, 1); else CompileSound(ctx, keyword, 0); break; case kWAV: ctx->if_stack[ctx->if_level].returned = true; // fallthrough: case kVOWELSTART: case kVOWELENDING: case kANDWAV: DecThenCount(ctx); CompileSound(ctx, keyword, 0); break; case kVOWELIN: DecThenCount(ctx); endphoneme = CompileVowelTransition(ctx, 1); break; case kVOWELOUT: DecThenCount(ctx); endphoneme = CompileVowelTransition(ctx, 2); break; case kTONESPEC: DecThenCount(ctx); CompileToneSpec(ctx); break; case kCONTINUE: *ctx->prog_out++ = INSTN_CONTINUE; DecThenCount(ctx); break; case kRETURN: *ctx->prog_out++ = INSTN_RETURN; DecThenCount(ctx); break; case kINCLUDE: case kPHONEMETABLE: error(ctx, "Missing 'endphoneme' before '%s'", ctx->item_string); // drop through to endphoneme // fallthrough: case kENDPHONEME: case kENDPROCEDURE: endphoneme = 1; if (ctx->if_level > 0) error(ctx, "Missing ENDIF"); if ((ctx->prog_out > ctx->prog_buf) && (ctx->if_stack[0].returned == false)) *ctx->prog_out++ = INSTN_RETURN; break; } break; } } if (endphoneme != 1) error(ctx, "'endphoneme' not expected here"); if (compile_phoneme) { if (ctx->phoneme_out->type == phINVALID) { error(ctx, "Phoneme type is missing"); ctx->phoneme_out->type = 0; } ctx->phoneme_out->phflags |= ctx->phoneme_flags; if (ctx->phoneme_out->phflags & phVOICED) { if (ctx->phoneme_out->type == phSTOP) ctx->phoneme_out->type = phVSTOP; else if (ctx->phoneme_out->type == phFRICATIVE) ctx->phoneme_out->type = phVFRICATIVE; } if (ctx->phoneme_out->std_length == 0) { if (ctx->phoneme_out->type == phVOWEL) ctx->phoneme_out->std_length = 180/2; // default length for vowel } ctx->phoneme_out->phflags |= phLOCAL; // declared in this phoneme table if (ctx->phoneme_out->type == phDELETED) ctx->phoneme_out->mnemonic = 0x01; // will not be recognised } if (ctx->prog_out > ctx->prog_buf) { // write out the program for this phoneme fflush(ctx->f_phindex); ctx->phoneme_out->program = ftell(ctx->f_phindex) / sizeof(unsigned short); if (ctx->f_prog_log != NULL) { phoneme_prog_log.addr = ctx->phoneme_out->program; phoneme_prog_log.length = ctx->prog_out - ctx->prog_buf; fwrite(&phoneme_prog_log, 1, sizeof(phoneme_prog_log), ctx->f_prog_log); } if (compile_phoneme == 0) ctx->proc_addr[ctx->n_procs++] = ftell(ctx->f_phindex) / sizeof(unsigned short); fwrite(ctx->prog_buf, sizeof(unsigned short), ctx->prog_out - ctx->prog_buf, ctx->f_phindex); } return 0; } static void WritePhonemeTables(CompileContext *ctx) { int ix; int j; int value; PHONEME_TAB *p; value = ctx->n_phoneme_tabs; fputc(value, ctx->f_phtab); fputc(0, ctx->f_phtab); fputc(0, ctx->f_phtab); fputc(0, ctx->f_phtab); for (ix = 0; ix < ctx->n_phoneme_tabs; ix++) { p = ctx->phoneme_tab_list2[ix].phoneme_tab_ptr; int n = ctx->n_phcodes_list[ix]; memset(&p[n], 0, sizeof(p[n])); p[n].mnemonic = 0; // terminate the phoneme table // count number of locally declared phonemes int count = 0; for (j = 0; j < n; j++) { if (ix == 0) p[j].phflags |= phLOCAL; // write all phonemes in the base phoneme table if (p[j].phflags & phLOCAL) count++; } ctx->phoneme_tab_list2[ix].n_phonemes = count+1; fputc(count+1, ctx->f_phtab); fputc(ctx->phoneme_tab_list2[ix].includes, ctx->f_phtab); fputc(0, ctx->f_phtab); fputc(0, ctx->f_phtab); fwrite(ctx->phoneme_tab_list2[ix].name, 1, N_PHONEME_TAB_NAME, ctx->f_phtab); for (j = 0; j < n; j++) { if (p[j].phflags & phLOCAL) { // this bit is set temporarily to incidate a local phoneme, declared in // in the current phoneme file p[j].phflags &= ~phLOCAL; fwrite(&p[j], sizeof(PHONEME_TAB), 1, ctx->f_phtab); } } fwrite(&p[n], sizeof(PHONEME_TAB), 1, ctx->f_phtab); // include the extra list-terminator phoneme entry free(p); } } static void EndPhonemeTable(CompileContext *ctx) { int ix; char buf[5]; if (ctx->n_phoneme_tabs == 0) return; // check that all referenced phonemes have been declared for (ix = 0; ix < ctx->n_phcodes; ix++) { if (ctx->phoneme_tab2[ix].type == phINVALID) { error(ctx, "Phoneme [%s] not declared, referenced at line %d", WordToString(buf, ctx->phoneme_tab2[ix].mnemonic), (int)(ctx->phoneme_tab2[ix].program)); ctx->error_count++; ctx->phoneme_tab2[ix].type = 0; // prevent the error message repeating } } ctx->n_phcodes_list[ctx->n_phoneme_tabs-1] = ctx->n_phcodes; } static void StartPhonemeTable(CompileContext *ctx, const char *name) { PHONEME_TAB *p; if (ctx->n_phoneme_tabs >= N_PHONEME_TABS-1) { error(ctx, "Too many phonemetables"); return; } p = (PHONEME_TAB *)calloc(sizeof(PHONEME_TAB), N_PHONEME_TAB); if (p == NULL) { error(ctx, "Out of memory"); return; } memset(&ctx->phoneme_tab_list2[ctx->n_phoneme_tabs], 0, sizeof(PHONEME_TAB_LIST)); ctx->phoneme_tab_list2[ctx->n_phoneme_tabs].phoneme_tab_ptr = ctx->phoneme_tab2 = p; memset(ctx->phoneme_tab_list2[ctx->n_phoneme_tabs].name, 0, sizeof(ctx->phoneme_tab_list2[ctx->n_phoneme_tabs].name)); strncpy0(ctx->phoneme_tab_list2[ctx->n_phoneme_tabs].name, name, N_PHONEME_TAB_NAME); ctx->n_phcodes = 1; ctx->phoneme_tab_list2[ctx->n_phoneme_tabs].includes = 0; if (ctx->n_phoneme_tabs > 0) { NextItem(ctx, tSTRING); // name of base phoneme table int ix; for (ix = 0; ix < ctx->n_phoneme_tabs; ix++) { if (strcmp(ctx->item_string, ctx->phoneme_tab_list2[ix].name) == 0) { ctx->phoneme_tab_list2[ctx->n_phoneme_tabs].includes = ix+1; // initialise the new phoneme table with the contents of this one memcpy(ctx->phoneme_tab2, ctx->phoneme_tab_list2[ix].phoneme_tab_ptr, sizeof(PHONEME_TAB)*N_PHONEME_TAB); ctx->n_phcodes = ctx->n_phcodes_list[ix]; // clear "local phoneme" bit" int j; for (j = 0; j < ctx->n_phcodes; j++) ctx->phoneme_tab2[j].phflags &= ~phLOCAL; break; } } if (ix == ctx->n_phoneme_tabs && strcmp(ctx->item_string, "_") != 0) error(ctx, "Can't find base phonemetable '%s'", ctx->item_string); } else ReservePhCodes(ctx); ctx->n_phoneme_tabs++; } static void CompilePhonemeFiles(CompileContext *ctx) { FILE *f; char buf[sizeof(path_home)+120]; ctx->linenum = 1; ctx->count_references = 0; ctx->duplicate_references = 0; ctx->count_frames = 0; ctx->n_procs = 0; for (;;) { if (feof(ctx->f_in)) { // end of file, go back to previous from, from which this was included if (ctx->stack_ix == 0) break; // end of top level, finished fclose(ctx->f_in); ctx->f_in = ctx->stack[--ctx->stack_ix].file; strcpy(ctx->current_fname, ctx->stack[ctx->stack_ix].fname); ctx->linenum = ctx->stack[ctx->stack_ix].linenum; } int item = NextItem(ctx, tKEYWORD); switch (item) { case kUTF8_BOM: break; // ignore bytes 0xef 0xbb 0xbf case kINCLUDE: NextItem(ctx, tSTRING); sprintf(buf, "%s/%s", ctx->phsrc, ctx->item_string); if ((ctx->stack_ix < N_STACK) && (f = fopen(buf, "rb")) != NULL) { ctx->stack[ctx->stack_ix].linenum = ctx->linenum; strcpy(ctx->stack[ctx->stack_ix].fname, ctx->current_fname); ctx->stack[ctx->stack_ix++].file = ctx->f_in; ctx->f_in = f; strncpy0(ctx->current_fname, ctx->item_string, sizeof(ctx->current_fname)); ctx->linenum = 1; } else error(ctx, "Missing file: %s", ctx->item_string); break; case kPHONEMETABLE: EndPhonemeTable(ctx); NextItem(ctx, tSTRING); // name of the new phoneme table StartPhonemeTable(ctx, ctx->item_string); break; case kPHONEMESTART: if (ctx->n_phoneme_tabs == 0) { error(ctx, "phonemetable is missing"); return; } CompilePhoneme(ctx, 1); break; case kPROCEDURE: CompilePhoneme(ctx, 0); break; default: if (!feof(ctx->f_in)) error(ctx, "Keyword 'phoneme' expected"); break; } } memset(&ctx->phoneme_tab2[ctx->n_phcodes+1], 0, sizeof(ctx->phoneme_tab2[ctx->n_phcodes+1])); ctx->phoneme_tab2[ctx->n_phcodes+1].mnemonic = 0; // terminator } #pragma GCC visibility push(default) espeak_ng_STATUS espeak_ng_CompilePhonemeData(long rate, FILE *log, espeak_ng_ERROR_CONTEXT *context) { return espeak_ng_CompilePhonemeDataPath(rate, NULL, NULL, log, context); } espeak_ng_STATUS espeak_ng_CompilePhonemeDataPath(long rate, const char *source_path, const char *destination_path, FILE *log, espeak_ng_ERROR_CONTEXT *context) { if (!log) log = stderr; char fname[sizeof(path_home)+40]; char phdst[sizeof(path_home)+40]; // Destination: path to the phondata/phontab/phonindex output files. CompileContext *ctx = calloc(1, sizeof(CompileContext)); if (!ctx) return ENOMEM; if (source_path) { sprintf(ctx->phsrc, "%s", source_path); } else { sprintf(ctx->phsrc, "%s/../phsource", path_home); } if (destination_path) { sprintf(phdst, "%s", destination_path); } else { sprintf(phdst, "%s", path_home); } samplerate = rate; LoadPhData(NULL, NULL); if (LoadVoice("", 8/*compiling phonemes*/) == NULL) { clean_context(ctx); return ENS_VOICE_NOT_FOUND; } WavegenInit(rate, 0); WavegenSetVoice(voice); ctx->error_count = 0; ctx->f_errors = log; strncpy0(ctx->current_fname, "phonemes", sizeof(ctx->current_fname)); sprintf(fname, "%s/phonemes", ctx->phsrc); fprintf(log, "Compiling phoneme data: %s\n", fname); ctx->f_in = fopen(fname, "rb"); if (ctx->f_in == NULL) { clean_context(ctx); return create_file_error_context(context, errno, fname); } sprintf(fname, "%s/%s", phdst, "phondata-manifest"); if ((ctx->f_phcontents = fopen(fname, "w")) == NULL) ctx->f_phcontents = stderr; fprintf(ctx->f_phcontents, "# This file lists the type of data that has been compiled into the\n" "# phondata file\n" "#\n" "# The first character of a line indicates the type of data:\n" "# S - A SPECT_SEQ structure\n" "# W - A wavefile segment\n" "# E - An envelope\n" "#\n" "# Address is the displacement within phondata of this item\n" "#\n" "# Address Data file\n" "# ------- ---------\n"); sprintf(fname, "%s/%s", phdst, "phondata"); ctx->f_phdata = fopen(fname, "wb"); if (ctx->f_phdata == NULL) { int error = errno; fclose(ctx->f_in); fclose(ctx->f_phcontents); clean_context(ctx); return create_file_error_context(context, error, fname); } sprintf(fname, "%s/%s", phdst, "phonindex"); ctx->f_phindex = fopen(fname, "wb"); if (ctx->f_phindex == NULL) { int error = errno; fclose(ctx->f_in); fclose(ctx->f_phcontents); fclose(ctx->f_phdata); clean_context(ctx); return create_file_error_context(context, error, fname); } sprintf(fname, "%s/%s", phdst, "phontab"); ctx->f_phtab = fopen(fname, "wb"); if (ctx->f_phtab == NULL) { int error = errno; fclose(ctx->f_in); fclose(ctx->f_phcontents); fclose(ctx->f_phdata); fclose(ctx->f_phindex); clean_context(ctx); return create_file_error_context(context, error, fname); } sprintf(fname, "%s/compile_prog_log", ctx->phsrc); ctx->f_prog_log = fopen(fname, "wb"); // write a word so that further data doesn't start at displ=0 Write4Bytes(ctx->f_phdata, version_phdata); Write4Bytes(ctx->f_phdata, samplerate); Write4Bytes(ctx->f_phindex, version_phdata); memset(ctx->ref_hash_tab, 0, sizeof(ctx->ref_hash_tab)); ctx->n_phoneme_tabs = 0; MAKE_MEM_UNDEFINED(ctx->n_phcodes_list, sizeof(ctx->n_phcodes_list)); MAKE_MEM_UNDEFINED(ctx->phoneme_tab_list2, sizeof(ctx->phoneme_tab_list2)); ctx->stack_ix = 0; MAKE_MEM_UNDEFINED(ctx->stack, sizeof(ctx->stack)); StartPhonemeTable(ctx, "base"); CompilePhonemeFiles(ctx); EndPhonemeTable(ctx); WritePhonemeTables(ctx); fprintf(ctx->f_errors, "\nRefs %d, Reused %d\n", ctx->count_references, ctx->duplicate_references); fclose(ctx->f_in); fclose(ctx->f_phcontents); fclose(ctx->f_phdata); fclose(ctx->f_phindex); fclose(ctx->f_phtab); if (ctx->f_prog_log != NULL) fclose(ctx->f_prog_log); LoadPhData(NULL, NULL); WavegenFini(); fprintf(log, "Compiled phonemes: %d errors.\n", ctx->error_count); if (ctx->f_errors != stderr && ctx->f_errors != stdout) fclose(ctx->f_errors); espeak_ng_STATUS status = ReadPhondataManifest(ctx, context); int res = ctx->error_count > 0 ? ENS_COMPILE_ERROR : ENS_OK; clean_context(ctx); return (status != ENS_OK) ? status : res; } #pragma GCC visibility pop static const char *preset_tune_names[] = { "s1", "c1", "q1", "e1", NULL }; static const TUNE default_tune = { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 40, 24, 8, 0, 0, 0, 0 }, 46, 57, PITCHfall, 16, 0, 0, 255, 78, 50, 255, 3, 5, { -7, -7, -7 }, { -7, -7, -7 }, PITCHfall, 64, 8, PITCHfall, 70, 18, 24, 12, PITCHfall, 70, 18, 24, 12, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }, 0 }; #define N_TUNE_NAMES 100 static const MNEM_TAB envelope_names[] = { { "fall", 0 }, { "rise", 2 }, { "fall-rise", 4 }, { "fall-rise2", 6 }, { "rise-fall", 8 }, { "fall-rise3", 10 }, { "fall-rise4", 12 }, { "fall2", 14 }, { "rise2", 16 }, { "rise-fall-rise", 18 }, { NULL, -1 } }; static int LookupEnvelopeName(const char *name) { return LookupMnem(envelope_names, name); } #pragma GCC visibility push(default) espeak_ng_STATUS espeak_ng_CompileIntonation(FILE *log, espeak_ng_ERROR_CONTEXT *context) { return espeak_ng_CompileIntonationPath(NULL, NULL, log, context); } espeak_ng_STATUS espeak_ng_CompileIntonationPath(const char *source_path, const char *destination_path, FILE *log, espeak_ng_ERROR_CONTEXT *context ) { if (!log) log = stderr; if (!source_path) source_path = path_home; if (!destination_path) destination_path = path_home; int ix; char *p; char c; int n_tune_names = 0; bool done_split = false; bool done_onset = false; bool done_last = false; int n_preset_tunes = 0; int found = 0; int tune_number = 0; FILE *f_out; TUNE *tune_data; TUNE new_tune; char name[12]; char tune_names[N_TUNE_NAMES][12]; char buf[sizeof(path_home)+150]; CompileContext *ctx = calloc(1, sizeof(CompileContext)); if (!ctx) return ENOMEM; ctx->error_count = 0; ctx->f_errors = log; sprintf(buf, "%s/../phsource/intonation.txt", source_path); if ((ctx->f_in = fopen(buf, "r")) == NULL) { sprintf(buf, "%s/../phsource/intonation", source_path); if ((ctx->f_in = fopen(buf, "r")) == NULL) { int error = errno; fclose(ctx->f_errors); clean_context(ctx); return create_file_error_context(context, error, buf); } } for (ix = 0; preset_tune_names[ix] != NULL; ix++) strcpy(tune_names[ix], preset_tune_names[ix]); n_tune_names = ix; n_preset_tunes = ix; // make a list of the tune names while (!feof(ctx->f_in)) { if (fgets(buf, sizeof(buf), ctx->f_in) == NULL) break; if ((memcmp(buf, "tune", 4) == 0) && isspace(buf[4])) { p = &buf[5]; while (isspace(*p)) p++; ix = 0; while ((ix < (int)(sizeof(name) - 1)) && !isspace(*p)) name[ix++] = *p++; name[ix] = 0; found = 0; for (ix = 0; ix < n_tune_names; ix++) { if (strcmp(name, tune_names[ix]) == 0) { found = 1; break; } } if (found == 0) { strncpy0(tune_names[n_tune_names++], name, sizeof(name)); if (n_tune_names >= N_TUNE_NAMES) break; } } } rewind(ctx->f_in); ctx->linenum = 1; tune_data = (n_tune_names == 0) ? NULL : (TUNE *)calloc(n_tune_names, sizeof(TUNE)); if (tune_data == NULL) { fclose(ctx->f_in); fclose(ctx->f_errors); clean_context(ctx); return ENOMEM; } sprintf(buf, "%s/intonations", destination_path); f_out = fopen(buf, "wb"); if (f_out == NULL) { int error = errno; fclose(ctx->f_in); fclose(ctx->f_errors); free(tune_data); clean_context(ctx); return create_file_error_context(context, error, buf); } while (!feof(ctx->f_in)) { int keyword = NextItem(ctx, tINTONATION); switch (keyword) { case kTUNE: done_split = false; memcpy(&new_tune, &default_tune, sizeof(TUNE)); NextItem(ctx, tSTRING); strncpy0(new_tune.name, ctx->item_string, sizeof(new_tune.name)); found = 0; tune_number = 0; for (ix = 0; ix < n_tune_names; ix++) { if (strcmp(new_tune.name, tune_names[ix]) == 0) { found = 1; tune_number = ix; if (tune_data[ix].name[0] != 0) found = 2; break; } } if (found == 2) error(ctx, "Duplicate tune name: '%s'", new_tune.name); if (found == 0) error(ctx, "Bad tune name: '%s;", new_tune.name); break; case kENDTUNE: if (!found) continue; if (done_onset == false) { new_tune.unstr_start[0] = new_tune.unstr_start[1]; new_tune.unstr_end[0] = new_tune.unstr_end[1]; } if (done_last == false) { new_tune.unstr_start[2] = new_tune.unstr_start[1]; new_tune.unstr_end[2] = new_tune.unstr_end[1]; } memcpy(&tune_data[tune_number], &new_tune, sizeof(TUNE)); break; case kTUNE_PREHEAD: new_tune.prehead_start = NextItem(ctx, tNUMBER); new_tune.prehead_end = NextItem(ctx, tNUMBER); break; case kTUNE_ONSET: new_tune.onset = NextItem(ctx, tNUMBER); new_tune.unstr_start[0] = NextItem(ctx, tSIGNEDNUMBER); new_tune.unstr_end[0] = NextItem(ctx, tSIGNEDNUMBER); done_onset = true; break; case kTUNE_HEADLAST: new_tune.head_last = NextItem(ctx, tNUMBER); new_tune.unstr_start[2] = NextItem(ctx, tSIGNEDNUMBER); new_tune.unstr_end[2] = NextItem(ctx, tSIGNEDNUMBER); done_last = true; break; case kTUNE_HEADENV: NextItem(ctx, tSTRING); if ((ix = LookupEnvelopeName(ctx->item_string)) < 0) error(ctx, "Bad envelope name: '%s'", ctx->item_string); else new_tune.stressed_env = ix; new_tune.stressed_drop = NextItem(ctx, tNUMBER); break; case kTUNE_HEAD: new_tune.head_max_steps = NextItem(ctx, tNUMBER); new_tune.head_start = NextItem(ctx, tNUMBER); new_tune.head_end = NextItem(ctx, tNUMBER); new_tune.unstr_start[1] = NextItem(ctx, tSIGNEDNUMBER); new_tune.unstr_end[1] = NextItem(ctx, tSIGNEDNUMBER); break; case kTUNE_HEADEXTEND: // up to 8 numbers for (ix = 0; ix < (int)(sizeof(new_tune.head_extend)); ix++) { if (!isdigit(c = CheckNextChar(ctx)) && (c != '-')) break; new_tune.head_extend[ix] = (NextItem(ctx, tSIGNEDNUMBER) * 64) / 100; // convert from percentage to 64ths } new_tune.n_head_extend = ix; // number of values break; case kTUNE_NUCLEUS0: NextItem(ctx, tSTRING); if ((ix = LookupEnvelopeName(ctx->item_string)) < 0) { error(ctx, "Bad envelope name: '%s'", ctx->item_string); break; } new_tune.nucleus0_env = ix; new_tune.nucleus0_max = NextItem(ctx, tNUMBER); new_tune.nucleus0_min = NextItem(ctx, tNUMBER); break; case kTUNE_NUCLEUS1: NextItem(ctx, tSTRING); if ((ix = LookupEnvelopeName(ctx->item_string)) < 0) { error(ctx, "Bad envelope name: '%s'", ctx->item_string); break; } new_tune.nucleus1_env = ix; new_tune.nucleus1_max = NextItem(ctx, tNUMBER); new_tune.nucleus1_min = NextItem(ctx, tNUMBER); new_tune.tail_start = NextItem(ctx, tNUMBER); new_tune.tail_end = NextItem(ctx, tNUMBER); if (!done_split) { // also this as the default setting for 'split' new_tune.split_nucleus_env = ix; new_tune.split_nucleus_max = new_tune.nucleus1_max; new_tune.split_nucleus_min = new_tune.nucleus1_min; new_tune.split_tail_start = new_tune.tail_start; new_tune.split_tail_end = new_tune.tail_end; } break; case kTUNE_SPLIT: NextItem(ctx, tSTRING); if ((ix = LookupEnvelopeName(ctx->item_string)) < 0) { error(ctx, "Bad envelope name: '%s'", ctx->item_string); break; } done_split = true; new_tune.split_nucleus_env = ix; new_tune.split_nucleus_max = NextItem(ctx, tNUMBER); new_tune.split_nucleus_min = NextItem(ctx, tNUMBER); new_tune.split_tail_start = NextItem(ctx, tNUMBER); new_tune.split_tail_end = NextItem(ctx, tNUMBER); NextItem(ctx, tSTRING); ctx->item_string[12] = 0; for (ix = 0; ix < n_tune_names; ix++) { if (strcmp(ctx->item_string, tune_names[ix]) == 0) break; } if (ix == n_tune_names) error(ctx, "Tune '%s' not found", ctx->item_string); else new_tune.split_tune = ix; break; default: error(ctx, "Unexpected: '%s'", ctx->item_string); break; } } for (ix = 0; ix < n_preset_tunes; ix++) { if (tune_data[ix].name[0] == 0) error(ctx, "Tune '%s' not defined", preset_tune_names[ix]); } fwrite(tune_data, n_tune_names, sizeof(TUNE), f_out); free(tune_data); fclose(ctx->f_in); fclose(f_out); fprintf(log, "Compiled %d intonation tunes: %d errors.\n", n_tune_names, ctx->error_count); LoadPhData(NULL, NULL); int res = ctx->error_count > 0 ? ENS_COMPILE_ERROR : ENS_OK; clean_context(ctx); return res; } #pragma GCC visibility pop static int CalculateSample(unsigned char c3, int c1) { int c2 = c3 << 24; c2 = c2 >> 16; // sign extend return (c1 & 0xff) + c2; }