/* insert boundary at time t and merge/delete intervals after this time */ static void IntervalTier_insertBoundaryAndMergeIntervalsAfter (IntervalTier me, double t) { if (t <= my xmin || t >= my xmax) { return; } long intervalNumber = IntervalTier_timeToLowIndex (me, t); while (my intervals.size > intervalNumber + 1) { my intervals. removeItem (my intervals.size); } // there can be maximally one interval left to the right of intervalNumber TextInterval ti = my intervals.at [intervalNumber]; if (ti -> xmin == t) { // if t happens to be on a boundary: remove the next interval if it exists if (my intervals.size > intervalNumber) { my intervals. removeItem (my intervals .size); } ti -> xmax = my xmax; TextInterval_setText (ti, U""); } else { ti -> xmax = t; TextInterval last = my intervals.at [my intervals.size]; last -> xmin = t; last -> xmax = my xmax; TextInterval_setText (last, U""); } }
static void IntervalTier_insertIntervalDestructively (IntervalTier me, double tmin, double tmax) { Melder_assert (tmin < tmax); Melder_assert (tmin >= my xmin); Melder_assert (tmax <= my xmax); /* * Make sure that the tier has boundaries at the edges of the interval. */ long firstIntervalNumber = IntervalTier_hasTime (me, tmin); if (! firstIntervalNumber) { long intervalNumber = IntervalTier_timeToIndex (me, tmin); if (intervalNumber == 0) Melder_throw (U"Cannot add a boundary at ", Melder_fixed (tmin, 6), U" seconds, because this is outside the time domain of the intervals."); TextInterval interval = my intervals.at [intervalNumber]; /* * Move the text to the left of the boundary. */ autoTextInterval newInterval = TextInterval_create (tmin, interval -> xmax, U""); interval -> xmax = tmin; my intervals. addItem_move (newInterval.move()); firstIntervalNumber = IntervalTier_hasTime (me, interval -> xmin); } Melder_assert (firstIntervalNumber >= 1 && firstIntervalNumber <= my intervals.size); long lastIntervalNumber = IntervalTier_hasTime (me, tmax); if (! lastIntervalNumber) { long intervalNumber = IntervalTier_timeToIndex (me, tmax); if (intervalNumber == 0) Melder_throw (U"Cannot add a boundary at ", Melder_fixed (tmin, 6), U" seconds, because this is outside the time domain of the intervals."); TextInterval interval = my intervals.at [intervalNumber]; /* * Move the text to the right of the boundary. */ autoTextInterval newInterval = TextInterval_create (interval -> xmin, tmax, U""); interval -> xmin = tmax; my intervals. addItem_move (newInterval.move()); lastIntervalNumber = IntervalTier_hasTime (me, interval -> xmax); } Melder_assert (lastIntervalNumber >= 1 && lastIntervalNumber <= my intervals.size); /* * Empty the interval in the word tier. */ trace (U"Empty interval %ld down to ", lastIntervalNumber, U".", firstIntervalNumber); for (long iinterval = lastIntervalNumber; iinterval >= firstIntervalNumber; iinterval --) { TextInterval interval = my intervals.at [iinterval]; if (interval -> xmin > tmin && interval -> xmin < tmax) { Melder_assert (iinterval > 1); TextInterval previous = my intervals.at [iinterval - 1]; previous -> xmax = tmax; // collapse left and right intervals into left interval TextInterval_setText (previous, U""); my intervals. removeItem (iinterval); // remove right interval } if (interval -> xmax == tmax) { TextInterval_setText (interval, U""); } } }
static void IntervalTier_add (IntervalTier me, double xmin, double xmax, const char32 *label) { long i = IntervalTier_timeToIndex (me, xmin); // xmin is in interval i if (i < 1) { Melder_throw (U"Index too low."); } autoTextInterval newti = TextInterval_create (xmin, xmax, label); TextInterval interval = (TextInterval) my intervals -> item[i]; double xmaxi = interval -> xmax; if (xmax > xmaxi) { Melder_throw (U"Don't know what to do"); // Don't know what to do } if (xmin == interval -> xmin) { if (xmax == interval -> xmax) { // interval already present TextInterval_setText (interval, label); return; } // split interval interval -> xmin = xmax; Collection_addItem (my intervals, newti.transfer()); return; } interval -> xmax = xmin; Collection_addItem (my intervals, newti.transfer()); // extra interval when xmax's are not the same if (xmax < xmaxi) { autoTextInterval newti2 = TextInterval_create (xmax, xmaxi, interval -> text); Collection_addItem (my intervals, newti2.transfer()); } }
static void IntervalTier_addBoundaryUnsorted (IntervalTier me, long iinterval, double time, const char32 *leftLabel) { if (time <= my xmin || time >= my xmax) { Melder_throw (U"Time is outside interval."); } // Find interval to split if (iinterval <= 0) { iinterval = IntervalTier_timeToLowIndex (me, time); } // Modify end time of left label TextInterval ti = (TextInterval) my intervals -> item[iinterval]; ti -> xmax = time; TextInterval_setText (ti, leftLabel); autoTextInterval ti_new = TextInterval_create (time, my xmax, U""); Sorted_addItem_unsorted (my intervals, ti_new.transfer()); }
static void IntervalTier_addBoundaryUnsorted (IntervalTier me, long iinterval, double time, const char32 *newLabel, bool isNewleftLabel) { if (time <= my xmin || time >= my xmax) { Melder_throw (U"Time is outside interval domains."); } // Find interval to split if (iinterval <= 0) { iinterval = IntervalTier_timeToLowIndex (me, time); } // Modify end time of left label TextInterval ti = my intervals.at [iinterval]; ti -> xmax = time; if (isNewleftLabel) TextInterval_setText (ti, newLabel); autoTextInterval ti_new = TextInterval_create (time, my xmax, (! isNewleftLabel ? newLabel : U"" )); my intervals. addItem_unsorted_move (ti_new.move()); }
static void IntervalTier_mergeSpecialIntervals (IntervalTier me) { long intervalIndex = my intervals.size; TextInterval right = my intervals.at [intervalIndex]; long labelLength_right = TextInterval_labelLength (right); bool isEmptyInterval_right = labelLength_right == 0 || (labelLength_right == 1 && Melder_equ (right -> text, U"\001")); while (intervalIndex > 1) { TextInterval left = my intervals.at [intervalIndex - 1]; long labelLength_left = TextInterval_labelLength (left); bool isEmptyInterval_left = labelLength_left == 0 || (labelLength_left == 1 && Melder_equ (left -> text, U"\001")); if (isEmptyInterval_right && isEmptyInterval_left) { // remove right interval and empty left interval left -> xmax = right -> xmax; TextInterval_setText (left, U""); my intervals. removeItem (intervalIndex); } right = left; isEmptyInterval_right = isEmptyInterval_left; intervalIndex --; } }
TextGrid TextGrid_readFromTIMITLabelFile (MelderFile file, int phnFile) { try { double dt = 1.0 / 16000; /* 1 / (TIMIT samplingFrequency) */ double xmax = dt; autofile f = Melder_fopen (file, "r"); // Ending time will only be known after all labels have been read. // We start with a sufficiently long duration (one hour) and correct this later. autoTextGrid me = TextGrid_create (0, 3600, U"wrd", 0); IntervalTier timit = (IntervalTier) my tiers -> item[1]; long linesRead = 0; char line[200], label[200]; while (fgets (line, 199, f)) { long it1, it2; linesRead++; if (sscanf (line, "%ld%ld%s", &it1, &it2, label) != 3) { Melder_throw (U"Incorrect number of items."); } if (it1 < 0 || it2 <= it1) { Melder_throw (U"Incorrect time at line ", linesRead); } xmax = it2 * dt; double xmin = it1 * dt; long ni = timit -> intervals -> size - 1; if (ni < 1) { ni = 1; // Some files do not start with a first line "0 <number2> h#". // Instead they start with "<number1> <number2> h#", where number1 > 0. // We override number1 with 0. */ if (xmin > 0 && phnFile) { xmin = 0; } } TextInterval interval = (TextInterval) timit -> intervals -> item[ni]; if (xmin < interval -> xmax && linesRead > 1) { xmin = interval -> xmax; Melder_warning (U"File \"", MelderFile_messageName (file), U"\": Start time set to previous end " U"time for label at line ", linesRead, U"."); } // standard: new TextInterval const char *labelstring = (strncmp (label, "h#", 2) ? label : TIMIT_DELIMITER); IntervalTier_add (timit, xmin, xmax, Melder_peek8to32 (labelstring)); } // Now correct the end times, based on last read interval. // (end time was set to large value!) if (timit -> intervals -> size < 2) { Melder_throw (U"Empty TextGrid"); } Collection_removeItem (timit -> intervals, timit -> intervals -> size); TextInterval interval = (TextInterval) timit -> intervals -> item[timit -> intervals -> size]; timit -> xmax = interval -> xmax; my xmax = xmax; if (phnFile) { // Create tier 2 with IPA symbols autoIntervalTier ipa = Data_copy (timit); Thing_setName (ipa.peek(), U"ipa"); // First change the data in ipa for (long i = 1; i <= ipa -> intervals -> size; i++) { interval = (TextInterval) timit -> intervals -> item[i]; TextInterval_setText ( (TextInterval) ipa -> intervals -> item[i], Melder_peek8to32 (timitLabelToIpaLabel (Melder_peek32to8 (interval -> text)))); } Collection_addItem (my tiers, ipa.transfer()); // Then: add to collection Thing_setName (timit, U"phn"); // rename wrd } f.close (file); return me.transfer(); } catch (MelderError) { Melder_throw (U"TextGrid not read from file ", file, U"."); } }
TextGrid Intensity_to_TextGrid_detectSilences (Intensity me, double silenceThreshold_dB, double minSilenceDuration, double minSoundingDuration, const char32 *silenceLabel, const char32 *soundingLabel) { try { double duration = my xmax - my xmin, time; if (silenceThreshold_dB >= 0) { Melder_throw (U"The silence threshold w.r.t. the maximum intensity should be a negative number."); } autoTextGrid thee = TextGrid_create (my xmin, my xmax, U"silences", U""); IntervalTier it = (IntervalTier) thy tiers -> item[1]; TextInterval_setText ( (TextInterval) it -> intervals -> item[1], soundingLabel); if (minSilenceDuration > duration) { return thee.transfer(); } double intensity_max_db, intensity_min_db, xOfMaximum, xOfMinimum; Vector_getMaximumAndX (me, 0, 0, 1, NUM_PEAK_INTERPOLATE_PARABOLIC, &intensity_max_db, &xOfMaximum); Vector_getMinimumAndX (me, 0, 0, 1, NUM_PEAK_INTERPOLATE_PARABOLIC, &intensity_min_db, &xOfMinimum); double intensity_dbRange = intensity_max_db - intensity_min_db; if (intensity_dbRange < 10) Melder_warning (U"The loudest and softest part in your sound only differ by ", intensity_dbRange, U" dB."); double intensityThreshold = intensity_max_db - fabs (silenceThreshold_dB); if (minSilenceDuration > duration || intensityThreshold < intensity_min_db) { return thee.transfer(); } int inSilenceInterval = my z[1][1] < intensityThreshold; long iinterval = 1; const char32 *label; for (long i = 2; i <= my nx; i++) { int addBoundary = 0; if (my z[1][i] < intensityThreshold) { if (!inSilenceInterval) { // Start of silence addBoundary = 1; inSilenceInterval = 1; label = soundingLabel; } } else { if (inSilenceInterval) { // End of silence addBoundary = 1; inSilenceInterval = 0; label = silenceLabel; } } if (addBoundary) { time = my x1 + (i - 1) * my dx; IntervalTier_addBoundaryUnsorted (it, iinterval, time, label); iinterval++; } } // (re)label last interval */ label = inSilenceInterval ? silenceLabel : soundingLabel; TextInterval_setText ( (TextInterval) it -> intervals -> item[iinterval], label); Sorted_sort (it -> intervals); // First remove short non-silence intervals in-between silence intervals and // then remove the remaining short silence intervals. // This works much better than first removing short silence intervals and // then short non-silence intervals. IntervalTier_cutIntervals_minimumDuration (it, soundingLabel, minSoundingDuration); IntervalTier_cutIntervalsOnLabelMatch (it, silenceLabel); IntervalTier_cutIntervals_minimumDuration (it, silenceLabel, minSilenceDuration); IntervalTier_cutIntervalsOnLabelMatch (it, soundingLabel); return thee.transfer(); } catch (MelderError) { Melder_throw (me, U": TextGrid not created."); } }
void TextGrid_anySound_alignInterval (TextGrid me, Function anySound, long tierNumber, long intervalNumber, const char32 *languageName, bool includeWords, bool includePhonemes) { try { IntervalTier headTier = TextGrid_checkSpecifiedTierIsIntervalTier (me, tierNumber); if (intervalNumber < 1 || intervalNumber > headTier -> intervals.size) Melder_throw (U"Interval ", intervalNumber, U" does not exist."); TextInterval interval = headTier -> intervals.at [intervalNumber]; if (! includeWords && ! includePhonemes) Melder_throw (U"Nothing to be done, because you asked neither for word alignment nor for phoneme alignment."); if (str32str (headTier -> name, U"/") ) Melder_throw (U"The current tier already has a slash (\"/\") in its name. Cannot create a word or phoneme tier from it."); autoSound part = anySound -> classInfo == classLongSound ? LongSound_extractPart (static_cast <LongSound> (anySound), interval -> xmin, interval -> xmax, true) : Sound_extractPart (static_cast <Sound> (anySound), interval -> xmin, interval -> xmax, kSound_windowShape_RECTANGULAR, 1.0, true); autoSpeechSynthesizer synthesizer = SpeechSynthesizer_create (languageName, U"default"); double silenceThreshold = -35, minSilenceDuration = 0.1, minSoundingDuration = 0.1; autoTextGrid analysis; if (! Melder_equ (interval -> text, U"")) { try { analysis = SpeechSynthesizer_and_Sound_and_TextInterval_align (synthesizer.get(), part.get(), interval, silenceThreshold, minSilenceDuration, minSoundingDuration); } catch (MelderError) { Melder_clearError (); // ignore all error messages from DTW and the like } } if (analysis) { /* * Clean up the analysis. */ Melder_assert (analysis -> xmin == interval -> xmin); Melder_assert (analysis -> xmax == interval -> xmax); Melder_assert (analysis -> tiers->size == 4); Thing_cast (IntervalTier, analysisWordTier, analysis -> tiers->at [3]); if (! IntervalTier_check (analysisWordTier)) Melder_throw (U"Analysis word tier out of order."); IntervalTier_removeEmptyIntervals (analysisWordTier, nullptr); Melder_assert (analysisWordTier -> xmax == analysis -> xmax); Melder_assert (analysisWordTier -> intervals.size >= 1); TextInterval firstInterval = analysisWordTier -> intervals.at [1]; TextInterval lastInterval = analysisWordTier -> intervals.at [analysisWordTier -> intervals.size]; firstInterval -> xmin = analysis -> xmin; lastInterval -> xmax = analysis -> xmax; if (lastInterval -> xmax != analysis -> xmax) Melder_fatal (U"analysis ends at ", analysis -> xmax, U", but last interval at ", lastInterval -> xmax, U" seconds"); if (! IntervalTier_check (analysisWordTier)) Melder_throw (U"Analysis word tier out of order (2)."); Thing_cast (IntervalTier, analysisPhonemeTier, analysis -> tiers->at [4]); if (! IntervalTier_check (analysisPhonemeTier)) Melder_throw (U"Analysis phoneme tier out of order."); IntervalTier_removeEmptyIntervals (analysisPhonemeTier, analysisWordTier); Melder_assert (analysisPhonemeTier -> xmax == analysis -> xmax); Melder_assert (analysisPhonemeTier -> intervals.size >= 1); firstInterval = analysisPhonemeTier -> intervals.at [1]; lastInterval = analysisPhonemeTier -> intervals.at [analysisPhonemeTier -> intervals.size]; firstInterval -> xmin = analysis -> xmin; lastInterval -> xmax = analysis -> xmax; Melder_assert (lastInterval -> xmax == analysis -> xmax); if (! IntervalTier_check (analysisPhonemeTier)) Melder_throw (U"Analysis phoneme tier out of order (2)."); } long wordTierNumber = 0, phonemeTierNumber = 0; IntervalTier wordTier = nullptr, phonemeTier = nullptr; /* * Include a word tier. */ if (includeWords) { /* * Make sure that the word tier exists. */ autoMelderString newWordTierName; MelderString_copy (& newWordTierName, headTier -> name, U"/word"); for (long itier = 1; itier <= my tiers->size; itier ++) { IntervalTier tier = static_cast <IntervalTier> (my tiers->at [itier]); if (Melder_equ (newWordTierName.string, tier -> name)) { if (tier -> classInfo != classIntervalTier) Melder_throw (U"A tier with the prospective word tier name (", tier -> name, U") already exists, but it is not an interval tier." U"\nPlease change its name or remove it."); wordTierNumber = itier; break; } } if (! wordTierNumber) { autoIntervalTier newWordTier = IntervalTier_create (my xmin, my xmax); Thing_setName (newWordTier.get(), newWordTierName.string); my tiers -> addItemAtPosition_move (newWordTier.move(), wordTierNumber = tierNumber + 1); } Melder_assert (wordTierNumber >= 1 && wordTierNumber <= my tiers->size); wordTier = static_cast <IntervalTier> (my tiers->at [wordTierNumber]); /* * Make sure that the word tier has boundaries at the edges of the interval. */ IntervalTier_insertIntervalDestructively (wordTier, interval -> xmin, interval -> xmax); /* * Copy the contents of the word analysis into the interval in the word tier. */ long wordIntervalNumber = IntervalTier_hasTime (wordTier, interval -> xmin); Melder_assert (wordIntervalNumber != 0); if (analysis) { Thing_cast (IntervalTier, analysisWordTier, analysis -> tiers->at [3]); if (! IntervalTier_check (analysisWordTier)) Melder_throw (U"Analysis word tier out of order (3)."); if (! IntervalTier_check (wordTier)) Melder_throw (U"Word tier out of order (3)."); for (long ianalysisInterval = 1; ianalysisInterval <= analysisWordTier -> intervals.size; ianalysisInterval ++) { TextInterval analysisInterval = analysisWordTier -> intervals.at [ianalysisInterval]; TextInterval wordInterval = nullptr; double tmin = analysisInterval -> xmin, tmax = analysisInterval -> xmax; if (tmax == analysis -> xmax) { wordInterval = wordTier -> intervals.at [wordIntervalNumber]; TextInterval_setText (wordInterval, analysisInterval -> text); } else { wordInterval = wordTier -> intervals.at [wordIntervalNumber]; autoTextInterval newInterval = TextInterval_create (tmin, tmax, analysisInterval -> text); wordInterval -> xmin = tmax; wordTier -> intervals. addItem_move (newInterval.move()); wordIntervalNumber ++; } } if (! IntervalTier_check (analysisWordTier)) Melder_throw (U"Analysis word tier out of order (4)."); if (! IntervalTier_check (wordTier)) Melder_throw (U"Word tier out of order (4)."); } } /* * Include a phoneme tier. */ if (includePhonemes) { /* * Make sure that the phoneme tier exists. */ autoMelderString newPhonemeTierName; MelderString_copy (& newPhonemeTierName, headTier -> name, U"/phon"); for (long itier = 1; itier <= my tiers->size; itier ++) { IntervalTier tier = (IntervalTier) my tiers->at [itier]; if (Melder_equ (newPhonemeTierName.string, tier -> name)) { if (tier -> classInfo != classIntervalTier) Melder_throw (U"A tier with the prospective phoneme tier name (", tier -> name, U") already exists, but it is not an interval tier." U"\nPlease change its name or remove it."); phonemeTierNumber = itier; break; } } if (! phonemeTierNumber) { autoIntervalTier newPhonemeTier = IntervalTier_create (my xmin, my xmax); Thing_setName (newPhonemeTier.get(), newPhonemeTierName.string); my tiers -> addItemAtPosition_move (newPhonemeTier.move(), phonemeTierNumber = wordTierNumber ? wordTierNumber + 1 : tierNumber + 1); } Melder_assert (phonemeTierNumber >= 1 && phonemeTierNumber <= my tiers->size); phonemeTier = static_cast <IntervalTier> (my tiers->at [phonemeTierNumber]); /* * Make sure that the phoneme tier has boundaries at the edges of the interval. */ IntervalTier_insertIntervalDestructively (phonemeTier, interval -> xmin, interval -> xmax); /* * Copy the contents of the phoneme analysis into the interval in the phoneme tier. */ long phonemeIntervalNumber = IntervalTier_hasTime (phonemeTier, interval -> xmin); Melder_assert (phonemeIntervalNumber != 0); if (analysis.get()) { Thing_cast (IntervalTier, analysisPhonemeTier, analysis -> tiers->at [4]); for (long ianalysisInterval = 1; ianalysisInterval <= analysisPhonemeTier -> intervals.size; ianalysisInterval ++) { TextInterval analysisInterval = analysisPhonemeTier -> intervals.at [ianalysisInterval]; TextInterval phonemeInterval = nullptr; double tmin = analysisInterval -> xmin, tmax = analysisInterval -> xmax; if (tmax == analysis -> xmax) { phonemeInterval = phonemeTier -> intervals.at [phonemeIntervalNumber]; TextInterval_setText (phonemeInterval, analysisInterval -> text); } else { phonemeInterval = phonemeTier -> intervals.at [phonemeIntervalNumber]; autoTextInterval newInterval = TextInterval_create (tmin, tmax, analysisInterval -> text); phonemeInterval -> xmin = tmax; phonemeTier -> intervals. addItem_move (newInterval.move()); phonemeIntervalNumber ++; } } } if (includeWords) { /* * Synchronize the boundaries between the word tier and the phoneme tier. */ //for (long iinterval = 1; iinterval <= } } } catch (MelderError) { Melder_throw (me, U" & ", anySound, U": interval not aligned."); } }
// Cut parts from me marked by labels in thee autoIntervalTier IntervalTier_and_IntervalTier_cutPartsMatchingLabel (IntervalTier me, IntervalTier thee, const char32 *label, double precision) { try { if (my xmin != thy xmin || my xmax != thy xmax) { Melder_throw (U"Domains must be equal."); } autoNUMvector<double> durations (1, my intervals.size); for (long i = 1; i <= my intervals.size; i ++) { TextInterval ti = my intervals.at [i]; durations[i] = ti -> xmax - ti -> xmin; } long myInterval = 1; for (long j = 1; j <= thy intervals.size; j ++) { TextInterval cut = thy intervals.at [j]; if (Melder_equ (cut -> text, label)) { // trim while (myInterval <= my intervals.size) { TextInterval ti = my intervals.at [myInterval]; if (ti -> xmin > cut -> xmin - precision && ti -> xmax < cut -> xmax + precision) { // 1. interval completely within cut durations[myInterval] = 0; myInterval++; } else if (ti -> xmin < cut -> xmin + precision && cut -> xmin < ti -> xmax + precision) { // 2. cut start is within interval if (cut -> xmax > ti -> xmax - precision) { // interval end is in cut, interval start before durations[myInterval] -= ti -> xmax - cut -> xmin; myInterval++; } else { // 3. cut completely within interval durations[myInterval] -= cut -> xmax - cut -> xmin; break; } } else if (cut -> xmax > ti -> xmin - precision && cut -> xmin < ti -> xmax + precision) { // +1+2 : cut end is within interval, cut start before durations[myInterval] -= cut -> xmax - ti -> xmin; break; } else if (ti -> xmax < cut -> xmin + precision) { myInterval++; } } } } double totalDuration = 0; for (long i = 1; i <= my intervals.size; i ++) { if (durations[i] < precision) { durations[i] = 0; } totalDuration += durations[i]; } autoIntervalTier him = IntervalTier_create (0, totalDuration); double time = 0; long hisInterval = 1; for (long i = 1; i <= my intervals.size; i ++) { if (durations[i] <= 0) continue; TextInterval ti = my intervals.at [i]; time += durations[i]; if (fabs (time - totalDuration) > precision) { IntervalTier_splitInterval (him.peek(), time, ti -> text, hisInterval, precision); hisInterval++; } else { // last interval TextInterval histi = his intervals.at [hisInterval]; TextInterval_setText (histi, ti -> text); } } return him; } catch (MelderError) { Melder_throw (me, U": parts not cut."); } }