/* * 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 "error.h" #include "speech.h" #include "phoneme.h" #include "synthesize.h" #include "voice.h" #include "spect.h" typedef struct { unsigned int value; char *name; } NAMETAB; NAMETAB *manifest = NULL; int n_manifest; char phsrc[sizeof(path_home)+40]; // Source: path to the 'phonemes' source file. extern ESPEAK_NG_API int utf8_in(int *c, const char *buf); extern int utf8_out(unsigned int c, char *buf); 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 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 keywtab_t k_properties[] = { { "isPause", 0, phPAUSE }, { "isVowel", 0, phVOWEL }, { "isNasal", 0, phNASAL }, { "isLiquid", 0, phLIQUID }, { "isUStop", 0, phSTOP }, { "isVStop", 0, phVSTOP }, { "isVFricative", 0, phVFRICATIVE }, { "isPalatal", 0, i_isPalatal }, { "isLong", 0, i_isLong }, { "isRhotic", 0, i_isRhotic }, { "isSibilant", 0, i_isSibilant }, { "isFlag1", 0, i_isFlag1 }, { "isFlag2", 0, i_isFlag2 }, { "isFlag3", 0, i_isFlag3 }, { "isVel", 0, i_isVel }, { "isDiminished", 0, i_isDiminished }, { "isUnstressed", 0, i_isUnstressed }, { "isNotStressed", 0, i_isNotStressed }, { "isStressed", 0, i_isStressed }, { "isMaxStress", 0, i_isMaxStress }, { "isBreak", 0, i_isBreak }, { "isPause2", 0, i_isBreak }, // synonym for isBreak { "isWordStart", 0, i_isWordStart }, { "notWordStart", 0, i_notWordStart }, { "isWordEnd", 0, i_isWordEnd }, { "isAfterStress", 0, i_isAfterStress }, { "isNotVowel", 0, i_isNotVowel }, { "isFinalVowel", 0, i_isFinalVowel }, { "isVoiced", 0, i_isVoiced }, // voiced consonant, or vowel { "isFirstVowel", 0, i_isFirstVowel }, { "isSecondVowel", 0, i_isSecondVowel }, { "isTranslationGiven", 0, i_IsTranslationGiven }, // phoneme translation given in **_list or as [[...]] { NULL, 0, 0 } }; enum { kPHONEMESTART = 1, kUTF8_BOM, kPROCEDURE, kENDPHONEME, kENDPROCEDURE, kEQUIVALENTS, 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 unsigned const char utf8_bom[] = { 0xef, 0xbb, 0xbf, 0 }; static 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 keywtab_t keywords[] = { { "vowel", tPHONEME_TYPE, phVOWEL }, // TODO (deprecated): use 'vwl' instead { "liquid", tPHONEME_TYPE, phLIQUID }, { "pause", tPHONEME_TYPE, phPAUSE }, { "stress", tPHONEME_TYPE, phSTRESS }, { "virtual", tPHONEME_TYPE, phVIRTUAL }, { "delete_phoneme", tPHONEME_TYPE, phDELETED }, // type of consonant { "stop", tPHONEME_TYPE, phSTOP }, // TODO (deprecated): use 'stp' instead { "nasal", tPHONEME_TYPE, phNASAL }, // TODO (deprecated): use 'nas' instead // 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 }, { "equivalents", tSTATEMENT, kEQUIVALENTS }, { "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 + 0 }, { "ChangeIfUnstressed", tINSTRN1, i_CHANGE_IF + 1 }, { "ChangeIfNotStressed", tINSTRN1, i_CHANGE_IF + 2 }, { "ChangeIfStressed", tINSTRN1, i_CHANGE_IF + 3 }, { "PauseBefore", tINSTRN1, i_PAUSE_BEFORE }, { "PauseAfter", tINSTRN1, i_PAUSE_AFTER }, { "Length", tINSTRN1, i_SET_LENGTH }, // TODO (deprecated): use 'length' instead { "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 }, { "sibilant", tPHONEME_FLAG, phSIBILANT }, // TODO (deprecated): use 'sib' instead { "nolink", tPHONEME_FLAG, phNOLINK }, { "trill", tPHONEME_FLAG, phTRILL }, // TODO (deprecated): use 'trl' instead { "palatal", tPHONEME_FLAG, phPALATAL }, { "long", tPHONEME_FLAG, phLONG }, { "dontlist", tPHONEME_FLAG, phDONTLIST }, { "brkafter", tPHONEME_FLAG, phBRKAFTER }, { "rhotic", tPHONEME_FLAG, phRHOTIC }, { "nonsyllabic", tPHONEME_FLAG, phNONSYLLABIC }, { "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 keywtab_t *keyword_tabs[] = { keywords, k_conditions, k_properties, k_intonation }; static PHONEME_TAB *phoneme_out; static int n_phcodes_list[N_PHONEME_TABS]; static PHONEME_TAB_LIST phoneme_tab_list2[N_PHONEME_TABS]; static PHONEME_TAB *phoneme_tab2; static char *p_equivalence; static char equivalence_buf[20000]; #define N_PROCS 50 int n_procs; int proc_addr[N_PROCS]; char proc_names[40][N_PROCS]; #define MAX_PROG_BUF 2000 USHORT *prog_out; USHORT *prog_out_max; USHORT prog_buf[MAX_PROG_BUF+20]; static espeak_ng_STATUS ReadPhondataManifest(espeak_ng_ERROR_CONTEXT *context) { // Read the phondata-manifest file FILE *f; int n_lines = 0; int ix; 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 (manifest != NULL) { for (ix = 0; ix < n_manifest; ix++) free(manifest[ix].name); } if (n_lines == 0) { fclose(f); return ENS_EMPTY_PHONEME_MANIFEST; } NAMETAB *new_manifest = (NAMETAB *)realloc(manifest, n_lines * sizeof(NAMETAB)); if (new_manifest == NULL) { fclose(f); free(manifest); return ENOMEM; } else manifest = new_manifest; 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); manifest[n_manifest].value = value; manifest[n_manifest].name = p; n_manifest++; } } } fclose(f); return ENS_OK; } static int n_phoneme_tabs; static int n_phcodes; // outout files static FILE *f_phdata; static FILE *f_phindex; static FILE *f_phtab; static FILE *f_phcontents; static FILE *f_errors = NULL; static FILE *f_prog_log = NULL; static FILE *f_report; static FILE *f_in; static int f_in_linenum; static int f_in_displ; static int linenum; static int count_references = 0; static int duplicate_references = 0; static int count_frames = 0; static int error_count = 0; static int resample_count = 0; static int resample_fails = 0; static int then_count = 0; static int after_if = 0; static char current_fname[80]; static int markers_used[8]; typedef struct { void *link; int value; int ph_mnemonic; short ph_table; char string[1]; } REF_HASH_TAB; static REF_HASH_TAB *ref_hash_tab[256]; #define N_ENVELOPES 30 int n_envelopes = 0; char envelope_paths[N_ENVELOPES][80]; unsigned char envelope_dat[N_ENVELOPES][ENV_LEN]; typedef struct { FILE *file; int linenum; char fname[80]; } STACK; #define N_STACK 12 int stack_ix; STACK stack[N_STACK]; #define N_IF_STACK 12 int if_level; typedef struct { USHORT *p_then; USHORT *p_else; int returned; } IF_STACK; IF_STACK if_stack[N_IF_STACK]; enum { tENDFILE = 1, tSTRING, tNUMBER, tSIGNEDNUMBER, tPHONEMEMNEM, tOPENBRACKET, tKEYWORD, tCONDITION, tPROPERTIES, tINTONATION, }; int item_type; int item_terminator; #define N_ITEM_STRING 256 char item_string[N_ITEM_STRING]; static int ref_sorter(char **a, char **b) { int ix; REF_HASH_TAB *p1 = (REF_HASH_TAB *)(*a); REF_HASH_TAB *p2 = (REF_HASH_TAB *)(*b); ix = strcoll(p1->string, p2->string); if (ix != 0) return ix; ix = p1->ph_table - p2->ph_table; if (ix != 0) return ix; return p1->ph_mnemonic - p2->ph_mnemonic; } static void CompileReport(void) { int ix; int hash; int n; REF_HASH_TAB *p; REF_HASH_TAB **list; const char *data_path; int prev_table; int procedure_num; int prev_mnemonic; if (f_report == NULL) return; // make a list of all the references and sort it list = (REF_HASH_TAB **)malloc((count_references)* sizeof(REF_HASH_TAB *)); if (list == NULL) return; fprintf(f_report, "\n%d phoneme tables\n", n_phoneme_tabs); fprintf(f_report, " new total\n"); for (ix = 0; ix < n_phoneme_tabs; ix++) fprintf(f_report, "%8s %3d %4d\n", phoneme_tab_list2[ix].name, phoneme_tab_list2[ix].n_phonemes, n_phcodes_list[ix]+1); fputc('\n', f_report); fprintf(f_report, "Data file Used by\n"); ix = 0; for (hash = 0; (hash < 256) && (ix < count_references); hash++) { p = ref_hash_tab[hash]; while (p != NULL) { list[ix++] = p; p = (REF_HASH_TAB *)(p->link); } } n = ix; qsort((void *)list, n, sizeof(REF_HASH_TAB *), (int (*)(const void *, const void *))ref_sorter); data_path = ""; prev_mnemonic = 0; prev_table = 0; for (ix = 0; ix < n; ix++) { int j = 0; if (strcmp(list[ix]->string, data_path) != 0) { data_path = list[ix]->string; j = strlen(data_path); fprintf(f_report, "%s", data_path); } else if ((list[ix]->ph_table == prev_table) && (list[ix]->ph_mnemonic == prev_mnemonic)) continue; // same phoneme, don't list twice while (j < 14) { fputc(' ', f_report); // pad filename with spaces j++; } prev_mnemonic = list[ix]->ph_mnemonic; if ((prev_mnemonic >> 24) == 'P') { // a procedure, not a phoneme procedure_num = atoi(WordToString(prev_mnemonic)); fprintf(f_report, " %s %s", phoneme_tab_list2[prev_table = list[ix]->ph_table].name, proc_names[procedure_num]); } else fprintf(f_report, " [%s] %s", WordToString(prev_mnemonic), phoneme_tab_list2[prev_table = list[ix]->ph_table].name); fputc('\n', f_report); } for (ix = 0; ix < n; ix++) { free(list[ix]); list[ix] = NULL; } free(list); list = NULL; } static void error(const char *format, ...) { va_list args; va_start(args, format); fprintf(f_errors, "%s(%d): ", current_fname, linenum-1); vfprintf(f_errors, format, args); fprintf(f_errors, "\n"); error_count++; va_end(args); } static unsigned int StringToWord(const char *string) { // Pack 4 characters into a word int ix; unsigned char c; unsigned int word; if (string == NULL) return 0; word = 0; for (ix = 0; ix < 4; ix++) { if (string[ix] == 0) break; c = string[ix]; word |= (c << (ix*8)); } return word; } static 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() { // Reserve phoneme codes which have fixed numbers so that they can be // referred to from the program code. unsigned int word; MNEM_TAB *p; p = reserved_phonemes; while (p->mnem != NULL) { word = StringToWord(p->mnem); phoneme_tab2[p->value].mnemonic = word; phoneme_tab2[p->value].code = p->value; if (n_phcodes <= p->value) n_phcodes = p->value+1; p++; } } static int LookupPhoneme(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("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 < n_phcodes; ix++) { if (phoneme_tab2[ix].mnemonic == word) return ix; if ((use == 0) && (phoneme_tab2[ix].mnemonic == 0)) use = ix; } if (use == 0) { if (control == 0) return -1; if (n_phcodes >= N_PHONEME_TAB-1) return -1; // phoneme table is full use = n_phcodes++; } // add this phoneme to the phoneme table phoneme_tab2[use].mnemonic = word; phoneme_tab2[use].type = phINVALID; phoneme_tab2[use].program = linenum; // for error report if the phoneme remains undeclared return use; } static unsigned int get_char() { unsigned int c; c = fgetc(f_in); if (c == '\n') linenum++; return c; } static void unget_char(unsigned int c) { ungetc(c, f_in); if (c == '\n') linenum--; } int CheckNextChar() { int c; while (((c = get_char()) == ' ') || (c == '\t')) ; unget_char(c); return c; } static int NextItem(int type) { int acc; unsigned char c = 0; unsigned char c2; int ix; int sign; char *p; keywtab_t *pk; item_type = -1; f_in_displ = ftell(f_in); f_in_linenum = linenum; while (!feof(f_in)) { c = get_char(); if (c == '/') { if ((c2 = get_char()) == '/') { // comment, ignore to end of line while (!feof(f_in) && ((c = get_char()) != '\n')) ; } else unget_char(c2); } if (!isspace(c)) break; } if (feof(f_in)) return -2; if (c == '(') { if (type == tOPENBRACKET) return 1; return -1; } ix = 0; while (!feof(f_in) && !isspace(c) && (c != '(') && (c != ')') && (c != ',')) { if (c == '\\') c = get_char(); item_string[ix++] = c; c = get_char(); if (feof(f_in)) break; if (item_string[ix-1] == '=') break; } item_string[ix] = 0; while (isspace(c)) c = get_char(); item_terminator = ' '; if ((c == ')') || (c == '(') || (c == ',')) item_terminator = c; if ((c == ')') || (c == ',')) c = ' '; if (!feof(f_in)) unget_char(c); if (type == tSTRING) return 0; if ((type == tNUMBER) || (type == tSIGNEDNUMBER)) { acc = 0; sign = 1; p = item_string; if ((*p == '-') && (type == tSIGNEDNUMBER)) { sign = -1; p++; } if (!isdigit(*p)) { if ((type == tNUMBER) && (*p == '-')) error("Expected an unsigned number"); else error("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(item_string, pk->mnem) == 0) { item_type = pk->type; return pk->data; } pk++; } item_type = -1; return -1; // keyword not found } if (type == tPHONEMEMNEM) return LookupPhoneme(item_string, 2); return -1; } static int NextItemMax(int max) { // Get a number, but restrict value to max int value; value = NextItem(tNUMBER); if (value > max) { error("Value %d is greater than maximum %d", value, max); value = max; } return value; } static int NextItemBrackets(int type, int control) { // Expect a parameter inside parantheses // control: bit 0 0= need ( // bit 1 1= allow comma int value; if ((control & 1) == 0) { if (!NextItem(tOPENBRACKET)) error("Expected '('"); } value = NextItem(type); if ((control & 2) && (item_terminator == ',')) return value; if (item_terminator != ')') error("Expected ')'"); return value; } static void UngetItem() { fseek(f_in, f_in_displ, SEEK_SET); linenum = 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; } int CompileVowelTransition(int which) { // Compile a vowel transition int key; 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 (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 (;;) { key = NextItem(tKEYWORD); if (item_type != tTRANSITION) { UngetItem(); break; } switch (key & 0xf) { case 1: len = Range(NextItem(tNUMBER), 2, 0, 63) & 0x3f; flags |= 1; break; case 2: rms = Range(NextItem(tNUMBER), 2, 0, 31) & 0x1f; flags |= 1; break; case 3: f1 = NextItem(tNUMBER); break; case 4: f2 = Range(NextItem(tNUMBER), 50, 0, 63) & 0x3f; f2_min = Range(NextItem(tSIGNEDNUMBER), 50, -15, 15) & 0x1f; f2_max = Range(NextItem(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(tSIGNEDNUMBER), 50, -15, 15) & 0x1f; f3_amp = Range(NextItem(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(tNUMBER); break; case 13: // set rms of 1st frame as fraction of rms of 2nd frame (1/30ths) rms = (Range(NextItem(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); prog_out[0] = instn + ((word1 >> 16) & 0xff); prog_out[1] = word1; prog_out[2] = word2 >> 16; prog_out[3] = word2; prog_out += 4; return 0; } espeak_ng_STATUS LoadSpect(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", phsrc, path); espeak_ng_STATUS status = LoadSpectSeq(spectseq, filename); if (status != ENS_OK) { error("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(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 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, f_phdata); while (ix & 3) { // round up to multiple of 4 bytes fputc(0, f_phdata); ix++; } } else { ix = (char *)(&seq_out.frame[seq_out.n_frames]) - (char *)(&seq_out); fwrite(&seq_out, ix, 1, f_phdata); while (ix & 3) { // round up to multiple of 4 bytes fputc(0, f_phdata); ix++; } } SpectSeqDestroy(spectseq); return ENS_OK; } static int LoadWavefile(FILE *f, const char *fname) { int displ; unsigned char c1; unsigned char c3; int c2; int sample; int sample2; float x; int max = 0; int length; int sr1, sr2; int failed; int len; int resample_wav = 0; const char *fname2; char fname_temp[100]; char msg[120]; int scale_factor = 0; fseek(f, 24, SEEK_SET); sr1 = Read4Bytes(f); sr2 = Read4Bytes(f); fseek(f, 40, SEEK_SET); if ((sr1 != samplerate_native) || (sr2 != sr1*2)) { int fd_temp; char command[sizeof(path_home)+250]; failed = 0; #ifdef HAVE_MKSTEMP strcpy(fname_temp, "/tmp/espeakXXXXXX"); if ((fd_temp = mkstemp(fname_temp)) >= 0) close(fd_temp); #else strcpy(fname_temp, tmpnam(NULL)); #endif fname2 = fname; len = strlen(fname); if (strcmp(&fname[len-4], ".wav") == 0) { strcpy(msg, fname); msg[len-4] = 0; fname2 = msg; } sprintf(command, "sox \"%s/%s.wav\" -r %d -c1 -t wav %s\n", phsrc, fname2, samplerate_native, fname_temp); if (system(command) != 0) failed = 1; if (failed || (GetFileLength(fname_temp) <= 0)) { if (resample_fails < 2) error("Resample command failed: %s", command); resample_fails++; if (sr1 != samplerate_native) error("Can't resample (%d to %d): %s", sr1, samplerate_native, fname); else error("WAV file is not mono: %s", fname); remove(fname_temp); return 0; } f = fopen(fname_temp, "rb"); if (f == NULL) { error("Can't read temp file: %s", fname_temp); return 0; } if (f_report != NULL) fprintf(f_report, "resampled %s\n", fname); resample_count++; resample_wav = 1; fseek(f, 40, SEEK_SET); // skip past the WAV header, up to before "data length" } displ = ftell(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; c3 = (unsigned char)c; c2 = c3 << 24; c2 = c2 >> 16; // sign extend sample = (c1 & 0xff) + c2; 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(f_phdata, length); fseek(f, 44, SEEK_SET); while (!feof(f)) { c1 = fgetc(f); c3 = fgetc(f); c2 = c3 << 24; c2 = c2 >> 16; // sign extend sample = (c1 & 0xff) + c2; if (feof(f)) break; if (scale_factor <= MIN_FACTOR) { fputc(sample & 0xff, f_phdata); fputc(sample >> 8, 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, f_phdata); } } length = ftell(f_phdata); while ((length & 3) != 0) { // pad to a multiple of 4 bytes fputc(0, f_phdata); length++; } if (resample_wav != 0) { fclose(f); remove(fname_temp); } return displ | 0x800000; // set bit 23 to indicate a wave file rather than a spectrum } static espeak_ng_STATUS LoadEnvelope(FILE *f, const char *fname, int *displ) { char buf[128]; if (displ) *displ = ftell(f_phdata); if (fseek(f, 12, SEEK_SET) == -1) return errno; if (fread(buf, 128, 1, f) != 128) return errno; fwrite(buf, 128, 1, f_phdata); if (n_envelopes < N_ENVELOPES) { strncpy0(envelope_paths[n_envelopes], fname, sizeof(envelope_paths[0])); memcpy(envelope_dat[n_envelopes], buf, sizeof(envelope_dat[0])); n_envelopes++; } 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(FILE *f, const char *fname) { int ix, ix2; int n; int x, y; int displ; int n_points; double yy; char line_buf[128]; float env_x[20]; float env_y[20]; int env_lin[20]; unsigned char env[ENV_LEN]; n_points = 0; fgets(line_buf, sizeof(line_buf), f); // skip first line while (!feof(f)) { if (fgets(line_buf, sizeof(line_buf), f) == NULL) break; env_lin[n_points] = 0; 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++; } } env_x[n_points] = env_x[n_points-1]; env_y[n_points] = env_y[n_points-1]; ix = -1; ix2 = 0; for (x = 0; x < ENV_LEN; x++) { if (x > env_x[ix+4]) ix++; if (x >= env_x[ix2+1]) ix2++; if (env_lin[ix2] > 0) { yy = env_y[ix2] + (env_y[ix2+1] - env_y[ix2]) * ((float)x - env_x[ix2]) / (env_x[ix2+1] - env_x[ix2]); y = (int)(yy * 2.55); } else if (n_points > 3) y = (int)(polint(&env_x[ix], &env_y[ix], 4, x) * 2.55); // convert to range 0-255 else y = (int)(polint(&env_x[ix], &env_y[ix], 3, x) * 2.55); if (y < 0) y = 0; if (y > 255) y = 255; env[x] = y; } if (n_envelopes < N_ENVELOPES) { strncpy0(envelope_paths[n_envelopes], fname, sizeof(envelope_paths[0])); memcpy(envelope_dat[n_envelopes], env, ENV_LEN); n_envelopes++; } displ = ftell(f_phdata); fwrite(env, 1, 128, f_phdata); return displ; } static espeak_ng_STATUS LoadDataFile(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 FILE *f; int id; int hash; int type_code = ' '; REF_HASH_TAB *p, *p2; char buf[sizeof(path_home)+150]; if (strcmp(path, "NULL") == 0) return ENS_OK; if (strcmp(path, "DFT") == 0) { *addr = 1; return ENS_OK; } count_references++; hash = Hash8(path); p = ref_hash_tab[hash]; while (p != NULL) { if (strcmp(path, p->string) == 0) { duplicate_references++; *addr = p->value; // already loaded this data break; } p = (REF_HASH_TAB *)p->link; } if (*addr == 0) { sprintf(buf, "%s/%s", phsrc, path); if ((f = fopen(buf, "rb")) == NULL) { sprintf(buf, "%s/%s.wav", phsrc, path); if ((f = fopen(buf, "rb")) == NULL) { error("Can't read file: %s", path); return errno; } } id = Read4Bytes(f); rewind(f); espeak_ng_STATUS status = ENS_OK; if (id == 0x43455053) { status = LoadSpect(path, control, addr); type_code = 'S'; } else if (id == 0x46464952) { *addr = LoadWavefile(f, path); type_code = 'W'; } else if (id == 0x43544950) { status = LoadEnvelope(f, path, addr); type_code = 'E'; } else if (id == 0x45564E45) { *addr = LoadEnvelope2(f, path); type_code = 'E'; } else { error("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(f_phcontents, "%c 0x%.5x %s\n", type_code, *addr & 0x7fffff, path); } // add this item to the hash table if (*addr > 0) { p = 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 = phoneme_out->mnemonic; // phoneme which uses this file p2->ph_table = n_phoneme_tabs-1; strcpy(p2->string, path); p2->link = (char *)p; ref_hash_tab[hash] = p2; } return ENS_OK; } static void CompileToneSpec(void) { int pitch1 = 0; int pitch2 = 0; int pitch_env = 0; int amp_env = 0; pitch1 = NextItemBrackets(tNUMBER, 2); pitch2 = NextItemBrackets(tNUMBER, 3); if (item_terminator == ',') { NextItemBrackets(tSTRING, 3); LoadDataFile(item_string, 0, &pitch_env); } if (item_terminator == ',') { NextItemBrackets(tSTRING, 1); LoadDataFile(item_string, 0, &_env); } if (pitch1 < pitch2) { phoneme_out->start_type = pitch1; phoneme_out->end_type = pitch2; } else { phoneme_out->start_type = pitch2; phoneme_out->end_type = pitch1; } if (pitch_env != 0) { *prog_out++ = i_PITCHENV + ((pitch_env >> 16) & 0xf); *prog_out++ = pitch_env; } if (amp_env != 0) { *prog_out++ = i_AMPENV + ((amp_env >> 16) & 0xf); *prog_out++ = amp_env; } } static void CompileSound(int keyword, int isvowel) { int addr = 0; int value = 0; char path[N_ITEM_STRING]; static int sound_instns[] = { i_FMT, i_WAV, i_VWLSTART, i_VWLENDING, i_WAVADD }; NextItemBrackets(tSTRING, 2); strcpy(path, item_string); if (item_terminator == ',') { if ((keyword == kVOWELSTART) || (keyword == kVOWELENDING)) { value = NextItemBrackets(tSIGNEDNUMBER, 1); if (value > 127) { value = 127; error("Parameter > 127"); } if (value < -128) { value = -128; error("Parameter < -128"); } } else { value = NextItemBrackets(tNUMBER, 1); if (value > 255) { value = 255; error("Parameter > 255"); } } } LoadDataFile(path, isvowel, &addr); addr = addr / 4; // addr is words not bytes *prog_out++ = sound_instns[keyword-kFMT] + ((value & 0xff) << 4) + ((addr >> 16) & 0xf); *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 */ int CompileIf(int elif) { int key; int finish = 0; int word = 0; int word2; int data; int bitmap; int brackets; int not_flag; USHORT *prog_last_if = NULL; then_count = 2; after_if = 1; while (!finish) { not_flag = 0; word2 = 0; if (prog_out >= prog_out_max) { error("Phoneme program too large"); return 0; } if ((key = NextItem(tCONDITION)) < 0) error("Expected a condition, not '%s'", item_string); if ((item_type == 0) && (key == k_NOT)) { not_flag = 1; if ((key = NextItem(tCONDITION)) < 0) error("Expected a condition, not '%s'", item_string); } if (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(tPROPERTIES, 0); if (data >= 0) word = key + data + 0x700; else { data = LookupPhoneme(item_string, 2); word = key + data; } } else if (item_type == tTEST) { if (key == kTHISSTRESS) { bitmap = 0; brackets = 2; do { data = NextItemBrackets(tNUMBER, brackets); if (data > 7) error("Expected list of stress levels"); bitmap |= (1 << data); brackets = 3; } while (item_terminator == ','); word = i_StressLevel | bitmap; } else word = key; } else { error("Unexpected keyword '%s'", item_string); if ((strcmp(item_string, "phoneme") == 0) || (strcmp(item_string, "endphoneme") == 0)) return -1; } // output the word prog_last_if = prog_out; *prog_out++ = word | i_CONDITION; if (word2 != 0) *prog_out++ = word2; if (not_flag) *prog_out++ = i_NOT; // expect AND, OR, THEN switch (NextItem(tCONDITION)) { case k_AND: break; case k_OR: if (prog_last_if != NULL) *prog_last_if |= i_OR; break; case k_THEN: finish = 1; break; default: error("Expected AND, OR, THEN"); break; } } if (elif == 0) { if_level++; if_stack[if_level].p_else = NULL; } if_stack[if_level].returned = 0; if_stack[if_level].p_then = prog_out; *prog_out++ = i_JUMP_FALSE; return 0; } void FillThen(int add) { USHORT *p; int offset; p = if_stack[if_level].p_then; if (p != NULL) { offset = prog_out - p + add; if ((then_count == 1) && (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 < prog_out) { p[0] = p[1]; p++; } prog_out--; } else { if (offset > MAX_JUMP) error("IF block is too long"); *p = i_JUMP_FALSE + offset; } if_stack[if_level].p_then = NULL; } then_count = 0; } int CompileElse(void) { USHORT *ref; USHORT *p; if (if_level < 1) { error("ELSE not expected"); return 0; } if (if_stack[if_level].returned == 0) FillThen(1); else FillThen(0); if (if_stack[if_level].returned == 0) { ref = prog_out; *prog_out++ = 0; if ((p = if_stack[if_level].p_else) != NULL) *ref = ref - p; // backwards offset to the previous else if_stack[if_level].p_else = ref; } return 0; } int CompileElif(void) { if (if_level < 1) { error("ELIF not expected"); return 0; } CompileElse(); CompileIf(1); return 0; } int CompileEndif(void) { USHORT *p; int chain; int offset; if (if_level < 1) { error("ENDIF not expected"); return 0; } FillThen(0); if ((p = if_stack[if_level].p_else) != NULL) { do { chain = *p; // a chain of previous else links offset = prog_out - p; if (offset > MAX_JUMP) error("IF block is too long"); *p = i_JUMP + offset; p -= chain; } while (chain > 0); } if_level--; return 0; } static int CompileSwitch(int type) { // Type 0: EndSwitch // 1: SwitchPrevVowelType // 2: SwitchNextVowelType if (type == 0) { // check the instructions in the Switch return 0; } if (type == 1) *prog_out++ = i_SWITCH_PREVVOWEL+6; if (type == 2) *prog_out++ = i_SWITCH_NEXTVOWEL+6; return 0; } static PHONEME_TAB_LIST *FindPhonemeTable(const char *string) { int ix; for (ix = 0; ix < n_phoneme_tabs; ix++) { if (strcmp(phoneme_tab_list2[ix].name, string) == 0) return &phoneme_tab_list2[ix]; } error("Unknown phoneme table: '%s'", string); return NULL; } static PHONEME_TAB *FindPhoneme(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(string, 0)) != -1)) return &phoneme_tab2[ix]; // no, treat the name as phonemetable/phoneme strcpy(buf, string); if ((phname = strchr(buf, '/')) != 0) *phname++ = 0; phtab = FindPhonemeTable(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("Phoneme reference not found: '%s'", string); return NULL; } static void ImportPhoneme(void) { unsigned int ph_mnem; unsigned int ph_code; PHONEME_TAB *ph; NextItem(tSTRING); if ((ph = FindPhoneme(item_string)) == NULL) return; ph_mnem = phoneme_out->mnemonic; ph_code = phoneme_out->code; memcpy(phoneme_out, ph, sizeof(PHONEME_TAB)); phoneme_out->mnemonic = ph_mnem; phoneme_out->code = ph_code; if (phoneme_out->type != phVOWEL) phoneme_out->end_type = 0; // voicingswitch, this must be set later to refer to a local phoneme } static void CallPhoneme(void) { PHONEME_TAB *ph; int ix; int addr = 0; NextItem(tSTRING); // first look for a procedure name for (ix = 0; ix < n_procs; ix++) { if (strcmp(proc_names[ix], item_string) == 0) { addr = proc_addr[ix]; break; } } if (ix == n_procs) { // procedure not found, try a phoneme name if ((ph = FindPhoneme(item_string)) == NULL) return; addr = ph->program; if (phoneme_out->type == phINVALID) { // Phoneme type has not been set. Copy it from the called phoneme phoneme_out->type = ph->type; phoneme_out->start_type = ph->start_type; phoneme_out->end_type = ph->end_type; phoneme_out->std_length = ph->std_length; phoneme_out->length_mod = ph->length_mod; } } *prog_out++ = i_CALLPH + (addr >> 16); *prog_out++ = addr; } static void DecThenCount() { if (then_count > 0) then_count--; } static void InstnPlusPhoneme(int instn) { int phcode; phcode = NextItemBrackets(tPHONEMEMNEM, 0); *prog_out++ = instn + phcode; } int CompilePhoneme(int compile_phoneme) { int endphoneme = 0; int keyword; 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; prog_out = prog_buf; prog_out_max = &prog_buf[MAX_PROG_BUF-1]; if_level = 0; if_stack[0].returned = 0; after_if = 0; int phoneme_flags = 0; NextItem(tSTRING); if (compile_phoneme) { phcode = LookupPhoneme(item_string, 1); // declare phoneme if not already there if (phcode == -1) return 0; phoneme_out = &phoneme_tab2[phcode]; } else { // declare a procedure if (n_procs >= N_PROCS) { error("Too many procedures"); return 0; } strcpy(proc_names[n_procs], item_string); phoneme_out = &phoneme_out2; sprintf(number_buf, "%.3dP", n_procs); phoneme_out->mnemonic = StringToWord(number_buf); } phoneme_out->code = phcode; phoneme_out->program = 0; phoneme_out->type = phINVALID; phoneme_out->std_length = 0; phoneme_out->start_type = 0; phoneme_out->end_type = 0; phoneme_out->length_mod = 0; phoneme_out->phflags = 0; while (!endphoneme && !feof(f_in)) { if ((keyword = NextItem(tKEYWORD)) < 0) { if (keyword == -2) { error("Missing 'endphoneme' before end-of-file"); // end of file break; } if (phoneme_add_feature(phoneme_out, item_string, NULL) == ENS_OK) continue; error("Bad keyword in phoneme definition '%s'", item_string); continue; } switch (item_type) { case tPHONEME_TYPE: if (phoneme_out->type != phINVALID) error("More than one phoneme type: %s", item_string); phoneme_out->type = keyword; break; case tPHONEME_FLAG: 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+0: case i_CHANGE_IF+1: case i_CHANGE_IF+2: case i_CHANGE_IF+3: InstnPlusPhoneme(keyword << 8); DecThenCount(); break; case i_PAUSE_BEFORE: value = NextItemMax(255); *prog_out++ = (i_PAUSE_BEFORE << 8) + value; DecThenCount(); break; case i_PAUSE_AFTER: value = NextItemMax(255); *prog_out++ = (i_PAUSE_AFTER << 8) + value; DecThenCount(); break; case i_SET_LENGTH: value = NextItemMax(511); if (phoneme_out->type == phVOWEL) value = (value * vowel_length_factor)/100; if (after_if == 0) phoneme_out->std_length = value/2; else { *prog_out++ = (i_SET_LENGTH << 8) + value/2; DecThenCount(); } break; case i_ADD_LENGTH: value = NextItem(tSIGNEDNUMBER) / 2; *prog_out++ = (i_ADD_LENGTH << 8) + (value & 0xff); DecThenCount(); break; case i_LENGTH_MOD: value = NextItem(tNUMBER); phoneme_out->length_mod = value; break; case i_IPA_NAME: NextItem(tSTRING); if (strcmp(item_string, "NULL") == 0) strcpy(item_string, " "); // copy the string, recognize characters in the form U+9999 flags = 0; count = 0; ix = 1; for (p = 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 *prog_out++ = (i_IPA_NAME << 8) + value; for (ix = 0; ix < value; ix += 2) *prog_out++ = (ipa_buf[ix+start] << 8) + (ipa_buf[ix+start+1] & 0xff); DecThenCount(); break; } break; case tSTATEMENT: switch (keyword) { case kIMPORT_PH: ImportPhoneme(); phoneme_flags = phoneme_out->phflags; break; case kSTARTTYPE: phcode = NextItem(tPHONEMEMNEM); if (phcode == -1) phcode = LookupPhoneme(item_string, 1); phoneme_out->start_type = phcode; break; case kENDTYPE: phcode = NextItem(tPHONEMEMNEM); if (phcode == -1) phcode = LookupPhoneme(item_string, 1); if (phoneme_out->type == phVOWEL) phoneme_out->end_type = phcode; else if (phcode != phoneme_out->start_type) error("endtype must equal starttype for consonants"); break; case kVOICINGSWITCH: phcode = NextItem(tPHONEMEMNEM); if (phcode == -1) phcode = LookupPhoneme(item_string, 1); phoneme_out->end_type = phcode; // use end_type field for consonants as voicing_switch break; case kSTRESSTYPE: value = NextItem(tNUMBER); phoneme_out->std_length = value; if (prog_out > prog_buf) { error("stress phonemes can't contain program instructions"); prog_out = prog_buf; } break; case kIF: endphoneme = CompileIf(0); break; case kELSE: endphoneme = CompileElse(); break; case kELIF: endphoneme = CompileElif(); break; case kENDIF: endphoneme = CompileEndif(); break; case kENDSWITCH: break; case kSWITCH_PREVVOWEL: endphoneme = CompileSwitch(1); break; case kSWITCH_NEXTVOWEL: endphoneme = CompileSwitch(2); break; case kCALLPH: CallPhoneme(); DecThenCount(); break; case kFMT: if_stack[if_level].returned = 1; DecThenCount(); if (phoneme_out->type == phVOWEL) CompileSound(keyword, 1); else CompileSound(keyword, 0); break; case kWAV: if_stack[if_level].returned = 1; // fallthrough: case kVOWELSTART: case kVOWELENDING: case kANDWAV: DecThenCount(); CompileSound(keyword, 0); break; case kVOWELIN: DecThenCount(); endphoneme = CompileVowelTransition(1); break; case kVOWELOUT: DecThenCount(); endphoneme = CompileVowelTransition(2); break; case kTONESPEC: DecThenCount(); CompileToneSpec(); break; case kCONTINUE: *prog_out++ = i_CONTINUE; DecThenCount(); break; case kRETURN: *prog_out++ = i_RETURN; DecThenCount(); break; case kINCLUDE: case kPHONEMETABLE: error("Missing 'endphoneme' before '%s'", item_string); // drop through to endphoneme // fallthrough: case kENDPHONEME: case kENDPROCEDURE: endphoneme = 1; if (if_level > 0) error("Missing ENDIF"); if ((prog_out > prog_buf) && (if_stack[0].returned == 0)) *prog_out++ = i_RETURN; break; } break; } } if (endphoneme != 1) error("'endphoneme' not expected here"); if (compile_phoneme) { if (phoneme_out->type == phINVALID) { error("Phoneme type is missing"); phoneme_out->type = 0; } phoneme_out->phflags |= phoneme_flags; if (phoneme_out->phflags & phVOICED) { if (phoneme_out->type == phSTOP) phoneme_out->type = phVSTOP; else if (phoneme_out->type == phFRICATIVE) phoneme_out->type = phVFRICATIVE; } if (phoneme_out->std_length == 0) { if (phoneme_out->type == phVOWEL) phoneme_out->std_length = 180/2; // default length for vowel } phoneme_out->phflags |= phLOCAL; // declared in this phoneme table if (phoneme_out->type == phDELETED) phoneme_out->mnemonic = 0x01; // will not be recognised } if (prog_out > prog_buf) { // write out the program for this phoneme fflush(f_phindex); phoneme_out->program = ftell(f_phindex) / sizeof(USHORT); if (f_prog_log != NULL) { phoneme_prog_log.addr = phoneme_out->program; phoneme_prog_log.length = prog_out - prog_buf; fwrite(&phoneme_prog_log, 1, sizeof(phoneme_prog_log), f_prog_log); } if (compile_phoneme == 0) proc_addr[n_procs++] = ftell(f_phindex) / sizeof(USHORT); fwrite(prog_buf, sizeof(USHORT), prog_out - prog_buf, f_phindex); } return 0; } static void WritePhonemeTables() { int ix; int j; int n; int value; int count; PHONEME_TAB *p; value = n_phoneme_tabs; fputc(value, f_phtab); fputc(0, f_phtab); fputc(0, f_phtab); fputc(0, f_phtab); for (ix = 0; ix < n_phoneme_tabs; ix++) { p = phoneme_tab_list2[ix].phoneme_tab_ptr; n = 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 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++; } phoneme_tab_list2[ix].n_phonemes = count+1; fputc(count+1, f_phtab); fputc(phoneme_tab_list2[ix].includes, f_phtab); fputc(0, f_phtab); fputc(0, f_phtab); Write4Bytes(f_phtab, phoneme_tab_list2[ix].equivalence_tables); // byte index into phondata for equivalence tables fwrite(phoneme_tab_list2[ix].name, 1, N_PHONEME_TAB_NAME, 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, f_phtab); } } fwrite(&p[n], sizeof(PHONEME_TAB), 1, f_phtab); // include the extra list-terminator phoneme entry free(p); } } static void EndPhonemeTable() { int ix; int *pw; int length; if (n_phoneme_tabs == 0) return; // check that all referenced phonemes have been declared for (ix = 0; ix < n_phcodes; ix++) { if (phoneme_tab2[ix].type == phINVALID) { error("Phoneme [%s] not declared, referenced at line %d", WordToString(phoneme_tab2[ix].mnemonic), (int)(phoneme_tab2[ix].program)); error_count++; phoneme_tab2[ix].type = 0; // prevent the error message repeating } } n_phcodes_list[n_phoneme_tabs-1] = n_phcodes; if ((length = p_equivalence - equivalence_buf) > 0) { // terminate the list of phoneme equivalence tables pw = (int *)p_equivalence; pw[0] = 0; // write the equivalence data into phondata, and remember it's address ix = ftell(f_phdata); fprintf(f_phcontents, "Q 0x%.5x %s\n", ix, phoneme_tab_list2[n_phoneme_tabs-1].name); phoneme_tab_list2[n_phoneme_tabs-1].equivalence_tables = ix; fwrite(equivalence_buf, length+4, 1, f_phdata); } } static void StartPhonemeTable(const char *name) { int ix; int j; PHONEME_TAB *p; if (n_phoneme_tabs >= N_PHONEME_TABS-1) { error("Too many phonemetables"); return; } p = (PHONEME_TAB *)calloc(sizeof(PHONEME_TAB), N_PHONEME_TAB); if (p == NULL) { error("Out of memory"); return; } memset(&phoneme_tab_list2[n_phoneme_tabs], 0, sizeof(PHONEME_TAB_LIST)); phoneme_tab_list2[n_phoneme_tabs].phoneme_tab_ptr = phoneme_tab2 = p; memset(phoneme_tab_list2[n_phoneme_tabs].name, 0, sizeof(phoneme_tab_list2[n_phoneme_tabs].name)); strncpy0(phoneme_tab_list2[n_phoneme_tabs].name, name, N_PHONEME_TAB_NAME); n_phcodes = 1; phoneme_tab_list2[n_phoneme_tabs].includes = 0; p_equivalence = equivalence_buf; if (n_phoneme_tabs > 0) { NextItem(tSTRING); // name of base phoneme table for (ix = 0; ix < n_phoneme_tabs; ix++) { if (strcmp(item_string, phoneme_tab_list2[ix].name) == 0) { phoneme_tab_list2[n_phoneme_tabs].includes = ix+1; // initialise the new phoneme table with the contents of this one memcpy(phoneme_tab2, phoneme_tab_list2[ix].phoneme_tab_ptr, sizeof(PHONEME_TAB)*N_PHONEME_TAB); n_phcodes = n_phcodes_list[ix]; // clear "local phoneme" bit" for (j = 0; j < n_phcodes; j++) phoneme_tab2[j].phflags &= ~phLOCAL; break; } } if (ix == n_phoneme_tabs) error("Can't find base phonemetable '%s'", item_string); } else ReservePhCodes(); n_phoneme_tabs++; } static void CompileEquivalents() { // a list of phonemes in another language and the equivalent phoneme strings in this language int ix; int n_names; int n_bytes; int foreign_error = 0; int remove_stress = 0; char *p_start; char *p; int foreign_table; char foreign_table_name[40]; char line_buf[80]; char names[6][80]; char phcode[7]; NextItem(tSTRING); strcpy(foreign_table_name, item_string); if ((foreign_table = SelectPhonemeTableName(foreign_table_name)) < 0) { if (strcmp(foreign_table_name, "NULL") != 0) error("Unknown phoneme table '%s'", foreign_table_name); foreign_error = 1; } p_start = p_equivalence; p_equivalence += 8; p_start[0] = foreign_table; linenum--; while (!feof(f_in)) { linenum++; if (fgets(line_buf, sizeof(line_buf), f_in) == NULL) break; if ((p = strstr(line_buf, "//")) != NULL) *p = 0; for (ix = 0; ix < 6; ix++) names[ix][0] = 0; n_names = sscanf(line_buf, "%s %s %s %s %s %s", names[0], names[1], names[2], names[3], names[4], names[5]); if (n_names < 1) continue; if (strcmp(names[0], "endphoneme") == 0) break; if (foreign_error) continue; if (strcmp(names[0], "remove_stress") == 0) { remove_stress = 1; continue; } if (p_equivalence > &equivalence_buf[sizeof(equivalence_buf) - 16]) { error("'equivalents' tables are too large"); break; } if (foreign_error == 0) { phcode[0] = LookupPhonemeString(names[0]); if (phcode[0] == 0) { sprintf(line_buf, "%s/%s", foreign_table_name, names[0]); error("Unknown phoneme '%s'", line_buf); } } for (ix = 1; ix < n_names; ix++) phcode[ix] = LookupPhoneme(names[ix], 1); // only write a translation if it has an effect if ((n_names > 2) || (phcode[0] != phcode[1])) { // write: foreign phoneme number, then a string of local phoneme numbers memcpy(p_equivalence, phcode, n_names); p_equivalence += n_names; *p_equivalence++ = 0; } } *p_equivalence++ = 0; p_equivalence = (char *)((intptr_t)(p_equivalence + 3) & ~0x3); // align to word boundary n_bytes = p_equivalence - p_start; p_start[1] = remove_stress; n_bytes = n_bytes / 4; p_start[2] = n_bytes >> 8; // index of next table p_start[3] = n_bytes; } static void CompilePhonemeFiles() { int item; FILE *f; char buf[sizeof(path_home)+120]; linenum = 1; count_references = 0; duplicate_references = 0; count_frames = 0; n_procs = 0; for (;;) { if (feof(f_in)) { // end of file, go back to previous from, from which this was included if (stack_ix == 0) break; // end of top level, finished fclose(f_in); f_in = stack[--stack_ix].file; strcpy(current_fname, stack[stack_ix].fname); linenum = stack[stack_ix].linenum; } item = NextItem(tKEYWORD); switch (item) { case kUTF8_BOM: break; // ignore bytes 0xef 0xbb 0xbf case kINCLUDE: NextItem(tSTRING); sprintf(buf, "%s/%s", phsrc, item_string); if ((stack_ix < N_STACK) && (f = fopen(buf, "rb")) != NULL) { stack[stack_ix].linenum = linenum; strcpy(stack[stack_ix].fname, current_fname); stack[stack_ix++].file = f_in; f_in = f; strncpy0(current_fname, item_string, sizeof(current_fname)); linenum = 1; } else error("Missing file: %s", item_string); break; case kPHONEMETABLE: EndPhonemeTable(); NextItem(tSTRING); // name of the new phoneme table StartPhonemeTable(item_string); break; case kPHONEMESTART: if (n_phoneme_tabs == 0) { error("phonemetable is missing"); return; } CompilePhoneme(1); break; case kPROCEDURE: CompilePhoneme(0); break; case kEQUIVALENTS: CompileEquivalents(); break; default: if (!feof(f_in)) error("Keyword 'phoneme' expected"); break; } } memset(&phoneme_tab2[n_phcodes+1], 0, sizeof(phoneme_tab2[n_phcodes+1])); phoneme_tab2[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. if (source_path) { sprintf(phsrc, "%s", source_path); } else { sprintf(phsrc, "%s/../phsource", path_home); } if (destination_path) { sprintf(phdst, "%s", destination_path); } else { sprintf(phdst, "%s", path_home); } samplerate_native = samplerate = rate; LoadPhData(NULL, NULL); if (LoadVoice("", 0) == NULL) return ENS_VOICE_NOT_FOUND; WavegenInit(rate, 0); WavegenSetVoice(voice); n_envelopes = 0; error_count = 0; resample_count = 0; memset(markers_used, 0, sizeof(markers_used)); f_errors = log; strncpy0(current_fname, "phonemes", sizeof(current_fname)); sprintf(fname, "%s/phonemes", phsrc); fprintf(log, "Compiling phoneme data: %s\n", fname); f_in = fopen(fname, "rb"); if (f_in == NULL) return create_file_error_context(context, errno, fname); sprintf(fname, "%s/%s", phsrc, "compile_report"); f_report = fopen(fname, "w"); if (f_report == NULL) { int error = errno; fclose(f_in); return create_file_error_context(context, error, fname); } sprintf(fname, "%s/%s", phdst, "phondata-manifest"); if ((f_phcontents = fopen(fname, "w")) == NULL) f_phcontents = stderr; fprintf(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" "# Q - Phoneme equivalence tables\n" "#\n" "# Address is the displacement within phondata of this item\n" "#\n" "# Address Data file\n" "# ------- ---------\n"); sprintf(fname, "%s/%s", phdst, "phondata"); f_phdata = fopen(fname, "wb"); if (f_phdata == NULL) { int error = errno; fclose(f_in); fclose(f_report); fclose(f_phcontents); return create_file_error_context(context, error, fname); } sprintf(fname, "%s/%s", phdst, "phonindex"); f_phindex = fopen(fname, "wb"); if (f_phindex == NULL) { int error = errno; fclose(f_in); fclose(f_report); fclose(f_phcontents); fclose(f_phdata); return create_file_error_context(context, error, fname); } sprintf(fname, "%s/%s", phdst, "phontab"); f_phtab = fopen(fname, "wb"); if (f_phtab == NULL) { int error = errno; fclose(f_in); fclose(f_report); fclose(f_phcontents); fclose(f_phdata); fclose(f_phindex); return create_file_error_context(context, error, fname); } sprintf(fname, "%s/compile_prog_log", phsrc); f_prog_log = fopen(fname, "wb"); // write a word so that further data doesn't start at displ=0 Write4Bytes(f_phdata, version_phdata); Write4Bytes(f_phdata, samplerate_native); Write4Bytes(f_phindex, version_phdata); memset(ref_hash_tab, 0, sizeof(ref_hash_tab)); n_phoneme_tabs = 0; stack_ix = 0; StartPhonemeTable("base"); CompilePhonemeFiles(); EndPhonemeTable(); WritePhonemeTables(); fprintf(f_errors, "\nRefs %d, Reused %d\n", count_references, duplicate_references); fclose(f_in); fclose(f_phcontents); fclose(f_phdata); fclose(f_phindex); fclose(f_phtab); if (f_prog_log != NULL) fclose(f_prog_log); LoadPhData(NULL, NULL); CompileReport(); fclose(f_report); if (resample_count > 0) { fprintf(f_errors, "\n%d WAV files resampled to %d Hz\n", resample_count, samplerate_native); fprintf(log, "Compiled phonemes: %d errors, %d files resampled to %d Hz.\n", error_count, resample_count, samplerate_native); } else fprintf(log, "Compiled phonemes: %d errors.\n", error_count); if (f_errors != stderr && f_errors != stdout) fclose(f_errors); espeak_ng_STATUS status = ReadPhondataManifest(context); if (status != ENS_OK) return status; return error_count > 0 ? ENS_COMPILE_ERROR : ENS_OK; } #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 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 } }; 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) { if (!log) log = stderr; int ix; char *p; char c; int keyword; int n_tune_names = 0; int done_split = 0; int done_onset = 0; int done_last = 0; int n_preset_tunes = 0; int found; 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]; error_count = 0; f_errors = log; sprintf(buf, "%s/../phsource/intonation.txt", path_home); if ((f_in = fopen(buf, "r")) == NULL) { sprintf(buf, "%s/../phsource/intonation", path_home); if ((f_in = fopen(buf, "r")) == NULL) { int error = errno; fclose(f_errors); 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(f_in)) { if (fgets(buf, sizeof(buf), 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(f_in); linenum = 1; tune_data = (n_tune_names == 0) ? NULL : (TUNE *)calloc(n_tune_names, sizeof(TUNE)); if (tune_data == NULL) { fclose(f_in); fclose(f_errors); return ENOMEM; } sprintf(buf, "%s/intonations", path_home); f_out = fopen(buf, "wb"); if (f_out == NULL) { int error = errno; fclose(f_in); fclose(f_errors); free(tune_data); return create_file_error_context(context, error, buf); } while (!feof(f_in)) { keyword = NextItem(tINTONATION); switch (keyword) { case kTUNE: done_split = 0; memcpy(&new_tune, &default_tune, sizeof(TUNE)); NextItem(tSTRING); strncpy0(new_tune.name, 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("Duplicate tune name: '%s'", new_tune.name); if (found == 0) error("Bad tune name: '%s;", new_tune.name); break; case kENDTUNE: if (done_onset == 0) { new_tune.unstr_start[0] = new_tune.unstr_start[1]; new_tune.unstr_end[0] = new_tune.unstr_end[1]; } if (done_last == 0) { 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(tNUMBER); new_tune.prehead_end = NextItem(tNUMBER); break; case kTUNE_ONSET: new_tune.onset = NextItem(tNUMBER); new_tune.unstr_start[0] = NextItem(tSIGNEDNUMBER); new_tune.unstr_end[0] = NextItem(tSIGNEDNUMBER); done_onset = 1; break; case kTUNE_HEADLAST: new_tune.head_last = NextItem(tNUMBER); new_tune.unstr_start[2] = NextItem(tSIGNEDNUMBER); new_tune.unstr_end[2] = NextItem(tSIGNEDNUMBER); done_last = 1; break; case kTUNE_HEADENV: NextItem(tSTRING); if ((ix = LookupEnvelopeName(item_string)) < 0) error("Bad envelope name: '%s'", item_string); else new_tune.stressed_env = ix; new_tune.stressed_drop = NextItem(tNUMBER); break; case kTUNE_HEAD: new_tune.head_max_steps = NextItem(tNUMBER); new_tune.head_start = NextItem(tNUMBER); new_tune.head_end = NextItem(tNUMBER); new_tune.unstr_start[1] = NextItem(tSIGNEDNUMBER); new_tune.unstr_end[1] = NextItem(tSIGNEDNUMBER); break; case kTUNE_HEADEXTEND: // up to 8 numbers for (ix = 0; ix < (int)(sizeof(new_tune.head_extend)); ix++) { if (!isdigit(c = CheckNextChar()) && (c != '-')) break; new_tune.head_extend[ix] = (NextItem(tSIGNEDNUMBER) * 64) / 100; // convert from percentage to 64ths } new_tune.n_head_extend = ix; // number of values break; case kTUNE_NUCLEUS0: NextItem(tSTRING); if ((ix = LookupEnvelopeName(item_string)) < 0) { error("Bad envelope name: '%s'", item_string); break; } new_tune.nucleus0_env = ix; new_tune.nucleus0_max = NextItem(tNUMBER); new_tune.nucleus0_min = NextItem(tNUMBER); break; case kTUNE_NUCLEUS1: NextItem(tSTRING); if ((ix = LookupEnvelopeName(item_string)) < 0) { error("Bad envelope name: '%s'", item_string); break; } new_tune.nucleus1_env = ix; new_tune.nucleus1_max = NextItem(tNUMBER); new_tune.nucleus1_min = NextItem(tNUMBER); new_tune.tail_start = NextItem(tNUMBER); new_tune.tail_end = NextItem(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(tSTRING); if ((ix = LookupEnvelopeName(item_string)) < 0) { error("Bad envelope name: '%s'", item_string); break; } done_split = 1; new_tune.split_nucleus_env = ix; new_tune.split_nucleus_max = NextItem(tNUMBER); new_tune.split_nucleus_min = NextItem(tNUMBER); new_tune.split_tail_start = NextItem(tNUMBER); new_tune.split_tail_end = NextItem(tNUMBER); NextItem(tSTRING); item_string[12] = 0; for (ix = 0; ix < n_tune_names; ix++) { if (strcmp(item_string, tune_names[ix]) == 0) break; } if (ix == n_tune_names) error("Tune '%s' not found", item_string); else new_tune.split_tune = ix; break; default: error("Unexpected: '%s'", item_string); break; } } for (ix = 0; ix < n_preset_tunes; ix++) { if (tune_data[ix].name[0] == 0) error("Tune '%s' not defined", preset_tune_names[ix]); } fwrite(tune_data, n_tune_names, sizeof(TUNE), f_out); free(tune_data); fclose(f_in); fclose(f_out); fprintf(log, "Compiled %d intonation tunes: %d errors.\n", n_tune_names, error_count); LoadPhData(NULL, NULL); return error_count > 0 ? ENS_COMPILE_ERROR : ENS_OK; } #pragma GCC visibility pop