TextGrid_Sound.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682
  1. /* TextGrid_Sound.cpp
  2. *
  3. * Copyright (C) 1992-2018 Paul Boersma
  4. *
  5. * This code is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or (at
  8. * your option) any later version.
  9. *
  10. * This code is distributed in the hope that it will be useful, but
  11. * WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  13. * See the GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this work. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. #include "TextGrid_Sound.h"
  19. #include "Pitch_to_PitchTier.h"
  20. #include "SpeechSynthesizer_and_TextGrid.h"
  21. #include "LongSound.h"
  22. static bool IntervalTier_check (IntervalTier me) {
  23. for (integer iinterval = 1; iinterval <= my intervals.size; iinterval ++) {
  24. TextInterval interval = my intervals.at [iinterval];
  25. if (interval -> xmin >= interval -> xmax) {
  26. Melder_casual (U"Interval ", iinterval, U" starts at ", interval -> xmin,
  27. U" but ends at ", interval -> xmax, U" seconds.");
  28. return false;
  29. }
  30. }
  31. if (my intervals.size < 2) return true;
  32. for (integer iinterval = 1; iinterval < my intervals.size; iinterval ++) {
  33. TextInterval thisInterval = my intervals.at [iinterval];
  34. TextInterval nextInterval = my intervals.at [iinterval + 1];
  35. if (thisInterval -> xmax != nextInterval -> xmin) {
  36. Melder_casual (U"Interval ", iinterval, U" ends at ", thisInterval -> xmax,
  37. U" but the next interval starts at ", nextInterval -> xmin, U" seconds.");
  38. return false;
  39. }
  40. }
  41. return true;
  42. }
  43. static void IntervalTier_insertIntervalDestructively (IntervalTier me, double tmin, double tmax) {
  44. Melder_assert (tmin < tmax);
  45. Melder_assert (tmin >= my xmin);
  46. Melder_assert (tmax <= my xmax);
  47. /*
  48. * Make sure that the tier has boundaries at the edges of the interval.
  49. */
  50. integer firstIntervalNumber = IntervalTier_hasTime (me, tmin);
  51. if (! firstIntervalNumber) {
  52. integer intervalNumber = IntervalTier_timeToIndex (me, tmin);
  53. if (intervalNumber == 0)
  54. Melder_throw (U"Cannot add a boundary at ", Melder_fixed (tmin, 6), U" seconds, because this is outside the time domain of the intervals.");
  55. TextInterval interval = my intervals.at [intervalNumber];
  56. /*
  57. * Move the text to the left of the boundary.
  58. */
  59. autoTextInterval newInterval = TextInterval_create (tmin, interval -> xmax, U"");
  60. interval -> xmax = tmin;
  61. my intervals. addItem_move (newInterval.move());
  62. firstIntervalNumber = IntervalTier_hasTime (me, interval -> xmin);
  63. }
  64. Melder_assert (firstIntervalNumber >= 1 && firstIntervalNumber <= my intervals.size);
  65. integer lastIntervalNumber = IntervalTier_hasTime (me, tmax);
  66. if (! lastIntervalNumber) {
  67. integer intervalNumber = IntervalTier_timeToIndex (me, tmax);
  68. if (intervalNumber == 0)
  69. Melder_throw (U"Cannot add a boundary at ", Melder_fixed (tmin, 6), U" seconds, because this is outside the time domain of the intervals.");
  70. TextInterval interval = my intervals.at [intervalNumber];
  71. /*
  72. * Move the text to the right of the boundary.
  73. */
  74. autoTextInterval newInterval = TextInterval_create (interval -> xmin, tmax, U"");
  75. interval -> xmin = tmax;
  76. my intervals. addItem_move (newInterval.move());
  77. lastIntervalNumber = IntervalTier_hasTime (me, interval -> xmax);
  78. }
  79. Melder_assert (lastIntervalNumber >= 1 && lastIntervalNumber <= my intervals.size);
  80. /*
  81. * Empty the interval in the word tier.
  82. */
  83. trace (U"Empty interval ", lastIntervalNumber, U" down to ", U".", firstIntervalNumber);
  84. for (integer iinterval = lastIntervalNumber; iinterval >= firstIntervalNumber; iinterval --) {
  85. TextInterval interval = my intervals.at [iinterval];
  86. if (interval -> xmin > tmin && interval -> xmin < tmax) {
  87. Melder_assert (iinterval > 1);
  88. TextInterval previous = my intervals.at [iinterval - 1];
  89. previous -> xmax = tmax; // collapse left and right intervals into left interval
  90. TextInterval_setText (previous, U"");
  91. my intervals. removeItem (iinterval); // remove right interval
  92. }
  93. if (interval -> xmax == tmax) {
  94. TextInterval_setText (interval, U"");
  95. }
  96. }
  97. }
  98. static double IntervalTier_boundaryTimeClosestTo (IntervalTier me, double tmin, double tmax) {
  99. integer intervalNumber = IntervalTier_timeToLowIndex (me, tmax);
  100. if (intervalNumber != 0) {
  101. TextInterval interval = my intervals.at [intervalNumber];
  102. if (interval -> xmin > tmin && interval -> xmin < tmax) {
  103. return interval -> xmin;
  104. }
  105. }
  106. return 0.5 * (tmin + tmax);
  107. }
  108. static void IntervalTier_removeEmptyIntervals (IntervalTier me, IntervalTier boss) {
  109. IntervalTier_removeBoundariesBetweenIdenticallyLabeledIntervals (me, U"");
  110. if (my intervals.size < 2) return;
  111. TextInterval firstInterval = my intervals.at [1];
  112. if (Melder_equ (firstInterval -> text.get(), U"")) {
  113. IntervalTier_removeLeftBoundary (me, 2);
  114. }
  115. if (my intervals.size < 2) return;
  116. TextInterval lastInterval = my intervals.at [my intervals.size];
  117. if (Melder_equ (lastInterval -> text.get(), U"")) {
  118. IntervalTier_removeLeftBoundary (me, my intervals.size);
  119. }
  120. if (my intervals.size < 3) return;
  121. for (integer iinterval = my intervals.size - 1; iinterval >= 2; iinterval --) {
  122. TextInterval interval = my intervals.at [iinterval];
  123. if (Melder_equ (interval -> text.get(), U"")) {
  124. /*
  125. * Distribute the empty interval between its neigbours.
  126. */
  127. double newBoundaryTime =
  128. boss ?
  129. IntervalTier_boundaryTimeClosestTo (boss, interval -> xmin, interval -> xmax) :
  130. 0.5 * (interval -> xmin + interval -> xmax);
  131. TextInterval previous = my intervals.at [iinterval - 1];
  132. TextInterval next = my intervals.at [iinterval + 1];
  133. previous -> xmax = newBoundaryTime;
  134. next -> xmin = newBoundaryTime;
  135. my intervals. removeItem (iinterval);
  136. }
  137. }
  138. }
  139. void TextGrid_anySound_alignInterval (TextGrid me, Function anySound, integer tierNumber, integer intervalNumber, conststring32 languageName, bool includeWords, bool includePhonemes) {
  140. try {
  141. IntervalTier headTier = TextGrid_checkSpecifiedTierIsIntervalTier (me, tierNumber);
  142. if (intervalNumber < 1 || intervalNumber > headTier -> intervals.size)
  143. Melder_throw (U"Interval ", intervalNumber, U" does not exist.");
  144. TextInterval interval = headTier -> intervals.at [intervalNumber];
  145. if (! includeWords && ! includePhonemes)
  146. Melder_throw (U"Nothing to be done, because you asked neither for word alignment nor for phoneme alignment.");
  147. if (str32str (headTier -> name.get(), U"/") )
  148. Melder_throw (U"The current tier already has a slash (\"/\") in its name. Cannot create a word or phoneme tier from it.");
  149. autoSound part =
  150. anySound -> classInfo == classLongSound ?
  151. LongSound_extractPart (static_cast <LongSound> (anySound), interval -> xmin, interval -> xmax, true) :
  152. Sound_extractPart (static_cast <Sound> (anySound), interval -> xmin, interval -> xmax, kSound_windowShape::RECTANGULAR, 1.0, true);
  153. autoSpeechSynthesizer synthesizer = SpeechSynthesizer_create (languageName, U"Female1");
  154. synthesizer -> d_samplingFrequency = round (
  155. anySound -> classInfo == classLongSound ?
  156. static_cast <LongSound> (anySound) -> sampleRate :
  157. 1.0 / static_cast <Sound> (anySound) -> dx
  158. );
  159. double silenceThreshold = -30.0, minSilenceDuration = 0.1, minSoundingDuration = 0.1;
  160. autoTextGrid analysis;
  161. if (! Melder_equ (interval -> text.get(), U"")) {
  162. analysis = SpeechSynthesizer_Sound_TextInterval_align
  163. (synthesizer.get(), part.get(), interval, silenceThreshold, minSilenceDuration, minSoundingDuration);
  164. }
  165. if (analysis) {
  166. /*
  167. * Clean up the analysis.
  168. */
  169. Melder_assert (fabs (analysis -> xmin - interval -> xmin) < 1e-12);
  170. if (analysis -> xmax != interval -> xmax) {
  171. //Melder_fatal (U"Analysis ends at ", analysis -> xmax, U" but interval at ", interval -> xmax, U"seconds.");
  172. analysis -> xmax = interval -> xmax;
  173. analysis -> intervalTier_cast (1) -> xmax = interval -> xmax;
  174. analysis -> intervalTier_cast (2) -> xmax = interval -> xmax;
  175. analysis -> intervalTier_cast (3) -> xmax = interval -> xmax;
  176. analysis -> intervalTier_cast (4) -> xmax = interval -> xmax;
  177. analysis -> intervalTier_cast (1) -> intervals.at [analysis -> intervalTier_cast (1) -> intervals.size] -> xmax = interval -> xmax;
  178. analysis -> intervalTier_cast (2) -> intervals.at [analysis -> intervalTier_cast (2) -> intervals.size] -> xmax = interval -> xmax;
  179. analysis -> intervalTier_cast (3) -> intervals.at [analysis -> intervalTier_cast (3) -> intervals.size] -> xmax = interval -> xmax;
  180. analysis -> intervalTier_cast (4) -> intervals.at [analysis -> intervalTier_cast (4) -> intervals.size] -> xmax = interval -> xmax;
  181. }
  182. Melder_assert (analysis -> tiers->size == 4);
  183. IntervalTier analysisWordTier = analysis -> intervalTier_cast (3);
  184. if (! IntervalTier_check (analysisWordTier))
  185. Melder_throw (U"Analysis word tier out of order.");
  186. IntervalTier_removeEmptyIntervals (analysisWordTier, nullptr);
  187. Melder_assert (analysisWordTier -> xmax == analysis -> xmax);
  188. Melder_assert (analysisWordTier -> intervals.size >= 1);
  189. TextInterval firstInterval = analysisWordTier -> intervals.at [1];
  190. TextInterval lastInterval = analysisWordTier -> intervals.at [analysisWordTier -> intervals.size];
  191. firstInterval -> xmin = analysis -> xmin;
  192. lastInterval -> xmax = analysis -> xmax;
  193. if (lastInterval -> xmax != analysis -> xmax)
  194. Melder_fatal (U"analysis ends at ", analysis -> xmax, U", but last interval at ", lastInterval -> xmax, U" seconds");
  195. if (! IntervalTier_check (analysisWordTier))
  196. Melder_throw (U"Analysis word tier out of order (2).");
  197. IntervalTier analysisPhonemeTier = analysis -> intervalTier_cast (4);
  198. if (! IntervalTier_check (analysisPhonemeTier))
  199. Melder_throw (U"Analysis phoneme tier out of order.");
  200. IntervalTier_removeEmptyIntervals (analysisPhonemeTier, analysisWordTier);
  201. Melder_assert (analysisPhonemeTier -> xmax == analysis -> xmax);
  202. Melder_assert (analysisPhonemeTier -> intervals.size >= 1);
  203. firstInterval = analysisPhonemeTier -> intervals.at [1];
  204. lastInterval = analysisPhonemeTier -> intervals.at [analysisPhonemeTier -> intervals.size];
  205. firstInterval -> xmin = analysis -> xmin;
  206. lastInterval -> xmax = analysis -> xmax;
  207. Melder_assert (lastInterval -> xmax == analysis -> xmax);
  208. if (! IntervalTier_check (analysisPhonemeTier))
  209. Melder_throw (U"Analysis phoneme tier out of order (2).");
  210. }
  211. integer wordTierNumber = 0, phonemeTierNumber = 0;
  212. IntervalTier wordTier = nullptr, phonemeTier = nullptr;
  213. /*
  214. * Include a word tier.
  215. */
  216. if (includeWords) {
  217. /*
  218. * Make sure that the word tier exists.
  219. */
  220. autoMelderString newWordTierName;
  221. MelderString_copy (& newWordTierName, headTier -> name.get(), U"/word");
  222. for (integer itier = 1; itier <= my tiers->size; itier ++) {
  223. Function tier = my tiers->at [itier];
  224. if (Melder_equ (newWordTierName.string, tier -> name.get())) {
  225. if (tier -> classInfo != classIntervalTier)
  226. Melder_throw (U"A tier with the prospective word tier name (", tier -> name.get(), U") already exists, but it is not an interval tier."
  227. U"\nPlease change its name or remove it.");
  228. wordTierNumber = itier;
  229. break;
  230. }
  231. }
  232. if (! wordTierNumber) {
  233. autoIntervalTier newWordTier = IntervalTier_create (my xmin, my xmax);
  234. Thing_setName (newWordTier.get(), newWordTierName.string);
  235. my tiers -> addItemAtPosition_move (newWordTier.move(), wordTierNumber = tierNumber + 1);
  236. }
  237. Melder_assert (wordTierNumber >= 1 && wordTierNumber <= my tiers->size);
  238. wordTier = static_cast <IntervalTier> (my tiers->at [wordTierNumber]);
  239. /*
  240. * Make sure that the word tier has boundaries at the edges of the interval.
  241. */
  242. IntervalTier_insertIntervalDestructively (wordTier, interval -> xmin, interval -> xmax);
  243. /*
  244. * Copy the contents of the word analysis into the interval in the word tier.
  245. */
  246. integer wordIntervalNumber = IntervalTier_hasTime (wordTier, interval -> xmin);
  247. Melder_assert (wordIntervalNumber != 0);
  248. if (analysis) {
  249. IntervalTier analysisWordTier = analysis -> intervalTier_cast (3);
  250. if (! IntervalTier_check (analysisWordTier))
  251. Melder_throw (U"Analysis word tier out of order (3).");
  252. if (! IntervalTier_check (wordTier))
  253. Melder_throw (U"Word tier out of order (3).");
  254. for (integer ianalysisInterval = 1; ianalysisInterval <= analysisWordTier -> intervals.size; ianalysisInterval ++) {
  255. TextInterval analysisInterval = analysisWordTier -> intervals.at [ianalysisInterval];
  256. TextInterval wordInterval = nullptr;
  257. double tmin = analysisInterval -> xmin, tmax = analysisInterval -> xmax;
  258. if (tmax == analysis -> xmax) {
  259. wordInterval = wordTier -> intervals.at [wordIntervalNumber];
  260. TextInterval_setText (wordInterval, analysisInterval -> text.get());
  261. } else {
  262. wordInterval = wordTier -> intervals.at [wordIntervalNumber];
  263. autoTextInterval newInterval = TextInterval_create (tmin, tmax, analysisInterval -> text.get());
  264. wordInterval -> xmin = tmax;
  265. wordTier -> intervals. addItem_move (newInterval.move());
  266. wordIntervalNumber ++;
  267. }
  268. }
  269. if (! IntervalTier_check (analysisWordTier))
  270. Melder_throw (U"Analysis word tier out of order (4).");
  271. if (! IntervalTier_check (wordTier))
  272. Melder_throw (U"Word tier out of order (4).");
  273. }
  274. }
  275. /*
  276. * Include a phoneme tier.
  277. */
  278. if (includePhonemes) {
  279. /*
  280. * Make sure that the phoneme tier exists.
  281. */
  282. autoMelderString newPhonemeTierName;
  283. MelderString_copy (& newPhonemeTierName, headTier -> name.get(), U"/phon");
  284. for (integer itier = 1; itier <= my tiers->size; itier ++) {
  285. Function tier = my tiers->at [itier];
  286. if (Melder_equ (newPhonemeTierName.string, tier -> name.get())) {
  287. if (tier -> classInfo != classIntervalTier)
  288. Melder_throw (U"A tier with the prospective phoneme tier name (", tier -> name.get(), U") already exists, but it is not an interval tier."
  289. U"\nPlease change its name or remove it.");
  290. phonemeTierNumber = itier;
  291. break;
  292. }
  293. }
  294. if (! phonemeTierNumber) {
  295. autoIntervalTier newPhonemeTier = IntervalTier_create (my xmin, my xmax);
  296. Thing_setName (newPhonemeTier.get(), newPhonemeTierName.string);
  297. my tiers -> addItemAtPosition_move (newPhonemeTier.move(),
  298. phonemeTierNumber = wordTierNumber ? wordTierNumber + 1 : tierNumber + 1);
  299. }
  300. Melder_assert (phonemeTierNumber >= 1 && phonemeTierNumber <= my tiers->size);
  301. phonemeTier = my intervalTier_cast (phonemeTierNumber);
  302. /*
  303. * Make sure that the phoneme tier has boundaries at the edges of the interval.
  304. */
  305. IntervalTier_insertIntervalDestructively (phonemeTier, interval -> xmin, interval -> xmax);
  306. /*
  307. * Copy the contents of the phoneme analysis into the interval in the phoneme tier.
  308. */
  309. integer phonemeIntervalNumber = IntervalTier_hasTime (phonemeTier, interval -> xmin);
  310. Melder_assert (phonemeIntervalNumber != 0);
  311. if (analysis) {
  312. IntervalTier analysisPhonemeTier = analysis -> intervalTier_cast (4);
  313. for (integer ianalysisInterval = 1; ianalysisInterval <= analysisPhonemeTier -> intervals.size; ianalysisInterval ++) {
  314. TextInterval analysisInterval = analysisPhonemeTier -> intervals.at [ianalysisInterval];
  315. TextInterval phonemeInterval = nullptr;
  316. double tmin = analysisInterval -> xmin, tmax = analysisInterval -> xmax;
  317. if (tmax == analysis -> xmax) {
  318. phonemeInterval = phonemeTier -> intervals.at [phonemeIntervalNumber];
  319. TextInterval_setText (phonemeInterval, analysisInterval -> text.get());
  320. } else {
  321. phonemeInterval = phonemeTier -> intervals.at [phonemeIntervalNumber];
  322. autoTextInterval newInterval = TextInterval_create (tmin, tmax, analysisInterval -> text.get());
  323. phonemeInterval -> xmin = tmax;
  324. phonemeTier -> intervals. addItem_move (newInterval.move());
  325. phonemeIntervalNumber ++;
  326. }
  327. }
  328. }
  329. if (includeWords) {
  330. /*
  331. * Synchronize the boundaries between the word tier and the phoneme tier.
  332. */
  333. //for (integer iinterval = 1; iinterval <=
  334. }
  335. }
  336. } catch (MelderError) {
  337. Melder_throw (me, U" & ", anySound, U": interval not aligned.");
  338. }
  339. }
  340. void TextGrid_Sound_draw (TextGrid me, Sound sound, Graphics g, double tmin, double tmax,
  341. bool showBoundaries, bool useTextStyles, bool garnish) // STEREO BUG
  342. {
  343. integer numberOfTiers = my tiers->size;
  344. /*
  345. * Automatic windowing:
  346. */
  347. if (tmax <= tmin) tmin = my xmin, tmax = my xmax;
  348. Graphics_setInner (g);
  349. Graphics_setWindow (g, tmin, tmax, -1.0 - 0.5 * numberOfTiers, 1.0);
  350. /*
  351. * Draw sound in upper part.
  352. */
  353. integer first, last;
  354. if (sound && Sampled_getWindowSamples (sound, tmin, tmax, & first, & last) > 1) {
  355. Graphics_setLineType (g, Graphics_DOTTED);
  356. Graphics_line (g, tmin, 0.0, tmax, 0.0);
  357. Graphics_setLineType (g, Graphics_DRAWN);
  358. Graphics_function (g, sound -> z [1], first, last,
  359. Sampled_indexToX (sound, first), Sampled_indexToX (sound, last));
  360. }
  361. /*
  362. * Draw labels in lower part.
  363. */
  364. Graphics_setTextAlignment (g, Graphics_CENTRE, Graphics_HALF);
  365. Graphics_setPercentSignIsItalic (g, useTextStyles);
  366. Graphics_setNumberSignIsBold (g, useTextStyles);
  367. Graphics_setCircumflexIsSuperscript (g, useTextStyles);
  368. Graphics_setUnderscoreIsSubscript (g, useTextStyles);
  369. for (integer itier = 1; itier <= numberOfTiers; itier ++) {
  370. Function anyTier = my tiers->at [itier];
  371. double ymin = -1.0 - 0.5 * itier, ymax = ymin + 0.5;
  372. Graphics_rectangle (g, tmin, tmax, ymin, ymax);
  373. if (anyTier -> classInfo == classIntervalTier) {
  374. IntervalTier tier = static_cast <IntervalTier> (anyTier);
  375. integer ninterval = tier -> intervals.size;
  376. for (integer iinterval = 1; iinterval <= ninterval; iinterval ++) {
  377. TextInterval interval = tier -> intervals.at [iinterval];
  378. double intmin = interval -> xmin, intmax = interval -> xmax;
  379. if (intmin < tmin) intmin = tmin;
  380. if (intmax > tmax) intmax = tmax;
  381. if (intmin >= intmax) continue;
  382. if (showBoundaries && intmin > tmin && intmin < tmax) {
  383. Graphics_setLineType (g, Graphics_DOTTED);
  384. Graphics_line (g, intmin, -1.0, intmin, 1.0); // in sound part
  385. Graphics_setLineType (g, Graphics_DRAWN);
  386. }
  387. /* Draw left boundary. */
  388. if (intmin > tmin && intmin < tmax) Graphics_line (g, intmin, ymin, intmin, ymax);
  389. /* Draw label text. */
  390. if (interval -> text && intmax >= tmin && intmin <= tmax) {
  391. double t1 = tmin > intmin ? tmin : intmin;
  392. double t2 = tmax < intmax ? tmax : intmax;
  393. Graphics_text (g, 0.5 * (t1 + t2), 0.5 * (ymin + ymax), interval -> text.get());
  394. }
  395. }
  396. } else {
  397. TextTier tier = static_cast <TextTier> (anyTier);
  398. integer numberOfPoints = tier -> points.size;
  399. for (integer ipoint = 1; ipoint <= numberOfPoints; ipoint ++) {
  400. TextPoint point = tier -> points.at [ipoint];
  401. double t = point -> number;
  402. if (t > tmin && t < tmax) {
  403. if (showBoundaries) {
  404. Graphics_setLineType (g, Graphics_DOTTED);
  405. Graphics_line (g, t, -1.0, t, 1.0); // in sound part
  406. Graphics_setLineType (g, Graphics_DRAWN);
  407. }
  408. Graphics_line (g, t, ymin, t, 0.8 * ymin + 0.2 * ymax);
  409. Graphics_line (g, t, 0.2 * ymin + 0.8 * ymax, t, ymax);
  410. if (point -> mark)
  411. Graphics_text (g, t, 0.5 * (ymin + ymax), point -> mark.get());
  412. }
  413. }
  414. }
  415. }
  416. Graphics_setPercentSignIsItalic (g, true);
  417. Graphics_setNumberSignIsBold (g, true);
  418. Graphics_setCircumflexIsSuperscript (g, true);
  419. Graphics_setUnderscoreIsSubscript (g, true);
  420. Graphics_unsetInner (g);
  421. if (garnish) {
  422. Graphics_drawInnerBox (g);
  423. Graphics_textBottom (g, true, U"Time (s)");
  424. Graphics_marksBottom (g, 2, true, true, false);
  425. }
  426. }
  427. autoSoundList TextGrid_Sound_extractAllIntervals (TextGrid me, Sound sound, integer tierNumber, bool preserveTimes) {
  428. try {
  429. IntervalTier tier = TextGrid_checkSpecifiedTierIsIntervalTier (me, tierNumber);
  430. autoSoundList list = SoundList_create ();
  431. for (integer iseg = 1; iseg <= tier -> intervals.size; iseg ++) {
  432. TextInterval segment = tier -> intervals.at [iseg];
  433. autoSound interval = Sound_extractPart (sound, segment -> xmin, segment -> xmax, kSound_windowShape::RECTANGULAR, 1.0, preserveTimes);
  434. Thing_setName (interval.get(), segment -> text ? segment -> text.get() : U"untitled");
  435. list -> addItem_move (interval.move());
  436. }
  437. return list;
  438. } catch (MelderError) {
  439. Melder_throw (me, U" & ", sound, U": intervals not extracted.");
  440. }
  441. }
  442. Thing_implement (SoundList, Ordered, 0);
  443. autoSoundList TextGrid_Sound_extractNonemptyIntervals (TextGrid me, Sound sound, integer tierNumber, bool preserveTimes) {
  444. try {
  445. IntervalTier tier = TextGrid_checkSpecifiedTierIsIntervalTier (me, tierNumber);
  446. autoSoundList list = SoundList_create ();
  447. for (integer iseg = 1; iseg <= tier -> intervals.size; iseg ++) {
  448. TextInterval segment = tier -> intervals.at [iseg];
  449. if (segment -> text && segment -> text [0] != U'\0') {
  450. autoSound interval = Sound_extractPart (sound, segment -> xmin, segment -> xmax, kSound_windowShape::RECTANGULAR, 1.0, preserveTimes);
  451. Thing_setName (interval.get(), segment -> text ? segment -> text.get() : U"untitled");
  452. list -> addItem_move (interval.move());
  453. }
  454. }
  455. if (list->size == 0) Melder_warning (U"No non-empty intervals were found.");
  456. return list;
  457. } catch (MelderError) {
  458. Melder_throw (me, U" & ", sound, U": non-empty intervals not extracted.");
  459. }
  460. }
  461. autoSoundList TextGrid_Sound_extractIntervalsWhere (TextGrid me, Sound sound, integer tierNumber,
  462. kMelder_string which, conststring32 text, bool preserveTimes)
  463. {
  464. try {
  465. IntervalTier tier = TextGrid_checkSpecifiedTierIsIntervalTier (me, tierNumber);
  466. autoSoundList list = SoundList_create ();
  467. integer count = 0;
  468. for (integer iseg = 1; iseg <= tier -> intervals.size; iseg ++) {
  469. TextInterval segment = tier -> intervals.at [iseg];
  470. if (Melder_stringMatchesCriterion (segment -> text.get(), which, text, true)) {
  471. autoSound interval = Sound_extractPart (sound, segment -> xmin, segment -> xmax, kSound_windowShape::RECTANGULAR, 1.0, preserveTimes);
  472. Thing_setName (interval.get(), Melder_cat (sound -> name ? sound -> name.get() : U"", U"_", text, U"_", ++ count));
  473. list -> addItem_move (interval.move());
  474. }
  475. }
  476. if (list->size == 0)
  477. Melder_warning (U"No label that ", kMelder_string_getText (which), U" the text \"", text, U"\" was found.");
  478. return list;
  479. } catch (MelderError) {
  480. Melder_throw (me, U" & ", sound, U": intervals not extracted.");
  481. }
  482. }
  483. static void autoMarks (Graphics g, double ymin, double ymax, bool haveDottedLines) {
  484. double dy = ymax - ymin;
  485. if (dy < 26.0) {
  486. integer imin = Melder_iroundUp ((ymin + 2.0) / 5.0), imax = Melder_ifloor ((ymax - 2.0) / 5.0);
  487. for (integer i = imin; i <= imax; i ++)
  488. Graphics_markLeft (g, i * 5.0, true, true, haveDottedLines, nullptr);
  489. } else if (dy < 110.0) {
  490. integer imin = Melder_iroundUp ((ymin + 8.0) / 20.0), imax = Melder_ifloor ((ymax - 8.0) / 20.0);
  491. for (integer i = imin; i <= imax; i ++)
  492. Graphics_markLeft (g, i * 20.0, true, true, haveDottedLines, nullptr);
  493. } else if (dy < 260.0) {
  494. integer imin = Melder_iroundUp ((ymin + 20.0) / 50.0), imax = Melder_ifloor ((ymax - 20.0) / 50.0);
  495. for (integer i = imin; i <= imax; i ++)
  496. Graphics_markLeft (g, i * 50.0, true, true, haveDottedLines, nullptr);
  497. } else if (dy < 510.0) {
  498. integer imin = Melder_iroundUp ((ymin + 40.0) / 100.0), imax = Melder_ifloor ((ymax - 40.0) / 100.0);
  499. for (integer i = imin; i <= imax; i ++)
  500. Graphics_markLeft (g, i * 100.0, true, true, haveDottedLines, nullptr);
  501. }
  502. }
  503. static void autoMarks_logarithmic (Graphics g, double ymin, double ymax, bool haveDottedLines) {
  504. double fy = ymax / ymin;
  505. for (int i = -12; i <= 12; i ++) {
  506. double power = pow (10, i), y = power;
  507. if (y > ymin * 1.2 && y < ymax / 1.2)
  508. Graphics_markLeftLogarithmic (g, y, true, true, haveDottedLines, nullptr);
  509. if (fy > 2100) {
  510. ; /* Enough. */
  511. } else if (fy > 210) {
  512. y = 3.0 * power;
  513. if (y > ymin * 1.2 && y < ymax / 1.2)
  514. Graphics_markLeftLogarithmic (g, y, true, true, haveDottedLines, nullptr);
  515. } else {
  516. y = 2.0 * power;
  517. if (y > ymin * 1.2 && y < ymax / 1.2)
  518. Graphics_markLeftLogarithmic (g, y, true, true, haveDottedLines, nullptr);
  519. y = 5.0 * power;
  520. if (y > ymin * 1.2 && y < ymax / 1.2)
  521. Graphics_markLeftLogarithmic (g, y, true, true, haveDottedLines, nullptr);
  522. if (fy < 21) {
  523. y = 3.0 * power;
  524. if (y > ymin * 1.2 && y < ymax / 1.2)
  525. Graphics_markLeftLogarithmic (g, y, true, true, haveDottedLines, nullptr);
  526. y = 7.0 * power;
  527. if (y > ymin * 1.2 && y < ymax / 1.2)
  528. Graphics_markLeftLogarithmic (g, y, true, true, haveDottedLines, nullptr);
  529. }
  530. if (fy < 4.1) {
  531. y = 1.5 * power;
  532. if (y > ymin * 1.2 && y < ymax / 1.2)
  533. Graphics_markLeftLogarithmic (g, y, true, true, haveDottedLines, nullptr);
  534. y = 4.0 * power;
  535. if (y > ymin * 1.2 && y < ymax / 1.2)
  536. Graphics_markLeftLogarithmic (g, y, true, true, haveDottedLines, nullptr);
  537. }
  538. }
  539. }
  540. }
  541. static void autoMarks_semitones (Graphics g, double ymin, double ymax, bool haveDottedLines) {
  542. double dy = ymax - ymin;
  543. if (dy < 16.0) {
  544. integer imin = Melder_iroundUp ((ymin + 1.2) / 3.0), imax = Melder_ifloor ((ymax - 1.2) / 3.0);
  545. for (integer i = imin; i <= imax; i ++)
  546. Graphics_markLeft (g, i * 3.0, true, true, haveDottedLines, nullptr);
  547. } else if (dy < 32.0) {
  548. integer imin = Melder_iroundUp ((ymin + 2.4) / 6.0), imax = Melder_ifloor ((ymax - 2.4) / 6.0);
  549. for (integer i = imin; i <= imax; i ++)
  550. Graphics_markLeft (g, i * 6.0, true, true, haveDottedLines, nullptr);
  551. } else if (dy < 64.0) {
  552. integer imin = Melder_iroundUp ((ymin + 4.8) / 12.0), imax = Melder_ifloor ((ymax - 4.8) / 12.0);
  553. for (integer i = imin; i <= imax; i ++)
  554. Graphics_markLeft (g, i * 12.0, true, true, haveDottedLines, nullptr);
  555. } else if (dy < 128.0) {
  556. integer imin = Melder_iroundUp ((ymin + 9.6) / 24.0), imax = Melder_ifloor ((ymax - 9.6) / 24.0);
  557. for (integer i = imin; i <= imax; i ++)
  558. Graphics_markLeft (g, i * 24.0, true, true, haveDottedLines, nullptr);
  559. }
  560. }
  561. void TextGrid_Pitch_drawSeparately (TextGrid grid, Pitch pitch, Graphics g, double tmin, double tmax,
  562. double fmin, double fmax, bool showBoundaries, bool useTextStyles, bool garnish, bool speckle, kPitch_unit unit)
  563. {
  564. integer numberOfTiers = grid -> tiers->size;
  565. if (tmax <= tmin) tmin = grid -> xmin, tmax = grid -> xmax;
  566. if (Function_isUnitLogarithmic (pitch, Pitch_LEVEL_FREQUENCY, (int) unit)) {
  567. fmin = Function_convertStandardToSpecialUnit (pitch, fmin, Pitch_LEVEL_FREQUENCY, (int) unit);
  568. fmax = Function_convertStandardToSpecialUnit (pitch, fmax, Pitch_LEVEL_FREQUENCY, (int) unit);
  569. }
  570. if (unit == kPitch_unit::HERTZ_LOGARITHMIC)
  571. Pitch_draw (pitch, g, tmin, tmax, pow (10.0, fmin - 0.25 * (fmax - fmin) * numberOfTiers), pow (10.0, fmax), false, speckle, unit);
  572. else
  573. Pitch_draw (pitch, g, tmin, tmax, fmin - 0.25 * (fmax - fmin) * numberOfTiers, fmax, false, speckle, unit);
  574. TextGrid_Sound_draw (grid, nullptr, g, tmin, tmax, showBoundaries, useTextStyles, false);
  575. /*
  576. * Restore window for the sake of margin drawing.
  577. */
  578. Graphics_setWindow (g, tmin, tmax, fmin - 0.25 * (fmax - fmin) * numberOfTiers, fmax);
  579. if (unit == kPitch_unit::HERTZ_LOGARITHMIC)
  580. fmin = pow (10, fmin), fmax = pow (10.0, fmax);
  581. if (garnish) {
  582. Graphics_drawInnerBox (g);
  583. if (unit == kPitch_unit::HERTZ_LOGARITHMIC) {
  584. Graphics_markLeftLogarithmic (g, fmin, true, true, false, nullptr);
  585. Graphics_markLeftLogarithmic (g, fmax, true, true, false, nullptr);
  586. autoMarks_logarithmic (g, fmin, fmax, false);
  587. } else if (unit == kPitch_unit::SEMITONES_100) {
  588. Graphics_markLeft (g, fmin, true, true, false, nullptr);
  589. Graphics_markLeft (g, fmax, true, true, false, nullptr);
  590. autoMarks_semitones (g, fmin, fmax, false);
  591. } else {
  592. Graphics_markLeft (g, fmin, true, true, false, nullptr);
  593. Graphics_markLeft (g, fmax, true, true, false, nullptr);
  594. autoMarks (g, fmin, fmax, false);
  595. }
  596. Graphics_textLeft (g, true, Melder_cat (U"Pitch (", Function_getUnitText (pitch, Pitch_LEVEL_FREQUENCY, (int) unit, Function_UNIT_TEXT_GRAPHICAL), U")"));
  597. Graphics_textBottom (g, true, U"Time (s)");
  598. Graphics_marksBottom (g, 2, true, true, false);
  599. }
  600. }
  601. void TextGrid_Pitch_draw (TextGrid grid, Pitch pitch, Graphics g,
  602. integer tierNumber, double tmin, double tmax, double fmin, double fmax,
  603. double fontSize, bool useTextStyles, int horizontalAlignment, bool garnish, bool speckle, kPitch_unit unit)
  604. {
  605. try {
  606. Function anyTier = TextGrid_checkSpecifiedTierNumberWithinRange (grid, tierNumber);
  607. double oldFontSize = Graphics_inqFontSize (g);
  608. Pitch_draw (pitch, g, tmin, tmax, fmin, fmax, garnish, speckle, unit);
  609. if (tmax <= tmin) tmin = grid -> xmin, tmax = grid -> xmax;
  610. autoPitchTier pitchTier = Pitch_to_PitchTier (pitch);
  611. if (Function_isUnitLogarithmic (pitch, Pitch_LEVEL_FREQUENCY, (int) unit)) {
  612. fmin = Function_convertStandardToSpecialUnit (pitch, fmin, Pitch_LEVEL_FREQUENCY, (int) unit);
  613. fmax = Function_convertStandardToSpecialUnit (pitch, fmax, Pitch_LEVEL_FREQUENCY, (int) unit);
  614. }
  615. Graphics_setTextAlignment (g, (kGraphics_horizontalAlignment) horizontalAlignment, Graphics_BOTTOM);
  616. Graphics_setInner (g);
  617. Graphics_setFontSize (g, fontSize);
  618. Graphics_setPercentSignIsItalic (g, useTextStyles);
  619. Graphics_setNumberSignIsBold (g, useTextStyles);
  620. Graphics_setCircumflexIsSuperscript (g, useTextStyles);
  621. Graphics_setUnderscoreIsSubscript (g, useTextStyles);
  622. if (anyTier -> classInfo == classIntervalTier) {
  623. IntervalTier tier = static_cast <IntervalTier> (anyTier);
  624. for (integer i = 1; i <= tier -> intervals.size; i ++) {
  625. TextInterval interval = tier -> intervals.at [i];
  626. double tleft = interval -> xmin, tright = interval -> xmax, tmid, f0;
  627. if (! interval -> text || ! interval -> text [0]) continue;
  628. if (tleft < pitch -> xmin) tleft = pitch -> xmin;
  629. if (tright > pitch -> xmax) tright = pitch -> xmax;
  630. tmid = (tleft + tright) / 2;
  631. if (tmid < tmin || tmid > tmax) continue;
  632. f0 = Function_convertStandardToSpecialUnit (pitch, RealTier_getValueAtTime (pitchTier.get(), tmid), Pitch_LEVEL_FREQUENCY, (int) unit);
  633. if (f0 < fmin || f0 > fmax) continue;
  634. Graphics_text (g,
  635. horizontalAlignment == (int) Graphics_LEFT ? tleft : horizontalAlignment == (int) Graphics_RIGHT ? tright : tmid,
  636. f0, interval -> text.get());
  637. }
  638. } else {
  639. TextTier tier = static_cast <TextTier> (anyTier);
  640. for (integer i = 1; i <= tier -> points.size; i ++) {
  641. TextPoint point = tier -> points.at [i];
  642. double t = point -> number;
  643. if (! point -> mark || ! point -> mark [0]) continue;
  644. if (t < tmin || t > tmax) continue;
  645. double f0 = Function_convertStandardToSpecialUnit (pitch, RealTier_getValueAtTime (pitchTier.get(), t), Pitch_LEVEL_FREQUENCY, (int) unit);
  646. if (f0 < fmin || f0 > fmax) continue;
  647. Graphics_text (g, t, f0, point -> mark.get());
  648. }
  649. }
  650. Graphics_setPercentSignIsItalic (g, true);
  651. Graphics_setNumberSignIsBold (g, true);
  652. Graphics_setCircumflexIsSuperscript (g, true);
  653. Graphics_setUnderscoreIsSubscript (g, true);
  654. Graphics_setFontSize (g, oldFontSize);
  655. Graphics_unsetInner (g);
  656. } catch (MelderError) {
  657. Melder_throw (grid, U" & ", pitch, U": not drawn.");
  658. }
  659. }
  660. /* End of file TextGrid_Sound.cpp */