DTW.cpp 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552
  1. /* DTW.cpp
  2. *
  3. * Copyright (C) 1993-2013, 2015-2016 David Weenink, 2017 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. See the GNU
  13. * General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * ilong with this work. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. /*
  19. djmw 20020813 GPL header
  20. djmw 20050302 Path finder modified with sakoe-chiba band
  21. djmw 20050305 DTW_to_Polygon
  22. djmw 20050306 DTW_swapAxes
  23. djmw 20050530 Added Matrices_to_DTW
  24. djmw 20060909 DTW_getPathY linear behaviour outside domains.
  25. djmw 20061205 Pitches_to_DTW
  26. djmw 20061214 Changed info to Melder_writeLine<x> format.
  27. djmw 20071012 Added: o_CAN_WRITE_AS_ENCODING.h
  28. djmw 20071016 To Melder_error<n>
  29. djmw 20071022 Extra comments + possible bug correction in DTW_Path_recode.
  30. djmw 20071201 Melder_warning<n>.
  31. djmw 20071204 DTW_and_Sounds_draw.
  32. djmw 20080122 float -> double
  33. djmw 20081123 DTW_and_Sounds_checkDomains did not swap sounds correctly.
  34. djmw 20091009 Removed a bug in DTW_Path_recode that could cause two identical x and y times in succesion at the end.
  35. djmw 20100504 extra check in DTW_Path_makeIndex
  36. djmw 20110304 Thing_new
  37. */
  38. #include "DTW.h"
  39. #include "Sound_extensions.h"
  40. #include "NUM2.h"
  41. #include "NUMmachar.h"
  42. #include "oo_DESTROY.h"
  43. #include "DTW_def.h"
  44. #include "oo_COPY.h"
  45. #include "DTW_def.h"
  46. #include "oo_EQUAL.h"
  47. #include "DTW_def.h"
  48. #include "oo_CAN_WRITE_AS_ENCODING.h"
  49. #include "DTW_def.h"
  50. #include "oo_WRITE_TEXT.h"
  51. #include "DTW_def.h"
  52. #include "oo_WRITE_BINARY.h"
  53. #include "DTW_def.h"
  54. #include "oo_READ_TEXT.h"
  55. #include "DTW_def.h"
  56. #include "oo_READ_BINARY.h"
  57. #include "DTW_def.h"
  58. #include "oo_DESCRIPTION.h"
  59. #include "DTW_def.h"
  60. Thing_implement (DTW, Matrix, 2);
  61. #define DTW_BIG 1e308
  62. void structDTW :: v_info () {
  63. structDaata :: v_info ();
  64. MelderInfo_writeLine (U"Domain of prototype: ", ymin, U" to ", ymax, U" (s)."); // ppgb: Wat is een domain prototype?
  65. MelderInfo_writeLine (U"Domain of candidate: ", xmin, U" to ", xmax, U" (s)."); // ppgb: Wat is een domain candidate?
  66. MelderInfo_writeLine (U"Number of frames in prototype: ", ny);
  67. MelderInfo_writeLine (U"Number of frames in candidate: ", nx);
  68. MelderInfo_writeLine (U"Path length (frames): ", pathLength);
  69. MelderInfo_writeLine (U"Global warped distance: ", weightedDistance);
  70. if (nx == ny) {
  71. double dd = 0;
  72. for (integer i = 1; i <= nx; i ++) {
  73. dd += z [i] [i];
  74. }
  75. MelderInfo_writeLine (U"Distance ilong diagonal: ", dd / nx);
  76. }
  77. }
  78. static void DTW_drawPath_raw (DTW me, Graphics g, double xmin, double xmax, double ymin, double ymax, bool garnish, bool inset);
  79. static void DTW_paintDistances_raw (DTW me, Graphics g, double xmin, double xmax, double ymin, double ymax, double minimum, double maximum, bool garnish, bool inset);
  80. static double _DTW_Sounds_getPartY (Graphics g, double dtw_part_x);
  81. static void DTW_findPath_special (DTW me, bool matchStart, bool matchEnd, int slope, autoMatrix *cumulativeDists);
  82. /*
  83. Two 'slope lines, lh and ll, start in the lower left corner, the upper/lower has the maximum/minimum allowed slope.
  84. Two other lines, ru and rl, end in the upper-right corner. The upper/lower line have minimum/maximum slope.
  85. 1. the four lines with the two intersections determine a diamond
  86. For vertical line at x we return the upper and lower y of the two intersections
  87. 1. When we don't have a diamond, we return an error and the vertical distance between the slopelines in ylow.
  88. */
  89. /*
  90. The actual path will be interpolated as follows:
  91. The distance matrix has cells of dimensions dx by dy.
  92. The optimal path connects these cells with one another in the following way:
  93. In a diagonal ''path'' segment, i.e. when cells have no side in common,
  94. the interpolated path runs from the lowerleft corner to the upperright corner.
  95. If a path segment is horizontal or vertical the path also runs from lowerleft to upperright.
  96. It is only when a horizontal and a vertical segment have a cell in common that we have to make
  97. some adjustments to the path in the common cell.
  98. This will be done as follows:
  99. Let nx and ny be the number of horizontal and vertical cells in this path segment.
  100. The path will always start at the lowerleft of the leftmost block and end
  101. at the upperright of the rightmost block.
  102. In the common cell we have to introduce an extra point in the path at (x1,y1)
  103. where the line from the horizontal segment and the line from the vertical segment intersect.
  104. Let the lowerleft and upperright point of the common cell be (0,0) and (dx,dy),
  105. then the coordinates (x,y) of the intersection can be calculated as follows:
  106. If we have two lines y = a1*x+b1 and y = a2*x+b2 they intersect at
  107. (x,y) = (b2-b1, a1*b2-a2*b1)/(a1-a2)
  108. For horizontal-vertical (hv) cells:
  109. a1 = dy/(nx*dx), a2 = ny*dy/dx
  110. b1 = dy*(nx-1)/nx, b2 = 0
  111. Then:
  112. (x,y) = (dx, dy*ny ) * (nx-1)/(nx*ny-1)
  113. For vertical-horizontal (vh) cells:
  114. a1 = ny*dy/dx, a2 = dy/(nx*dx)
  115. b1 = -(ny-1)*dy b2 = 0
  116. Then:
  117. (x,y) = (nx*dx, dy) * (ny-1)/(nx*ny-1)
  118. */
  119. /* DTW_getXTime (DTW me, (DTW_getYTime (DTW me, double tx)) == tx */
  120. double DTW_getYTimeFromXTime (DTW me, double tx) {
  121. // Catch cases where tier would give constant extrapolation
  122. if (tx < my xmin) {
  123. return my ymin - (my xmin - tx);
  124. }
  125. if (tx > my xmax) {
  126. return my ymax + (tx - my xmax);
  127. }
  128. DTW_Path_Query thee = & my pathQuery;
  129. return RealTier_getValueAtTime(thy yfromx.get(), tx);
  130. }
  131. double DTW_getXTimeFromYTime (DTW me, double ty) {
  132. // Catch cases where tier would give constant extrapolation
  133. if (ty < my ymin) {
  134. return my ymin - (my ymin - ty);
  135. }
  136. if (ty > my ymax) {
  137. return my ymax + (ty - my ymax);
  138. }
  139. DTW_Path_Query thee = & my pathQuery;
  140. return RealTier_getValueAtTime(thy xfromy.get(), ty);
  141. }
  142. void DTW_Path_Query_init (DTW_Path_Query me, integer ny, integer nx) {
  143. Melder_assert (ny > 0 && nx > 0);
  144. my ny = ny;
  145. my nx = nx;
  146. my nxy = 2 * (ny > nx ? ny : nx) + 2; // maximum number of points
  147. my xfromy = Thing_new (RealTier);
  148. my yfromx = Thing_new (RealTier);
  149. }
  150. /* Recode the path from a chain of cells to a piecewise linear path. */
  151. void DTW_Path_recode (DTW me) {
  152. try {
  153. DTW_Path_Query thee = & my pathQuery;
  154. integer nxy; // current number of elements in recoded path
  155. integer nsc_x = 1; // current number of successive horizontal cells in the cells chain
  156. integer nsc_y = 1; // current number of successive vertical cells in the cells chain
  157. integer nd = 0; // current number of successive diagonal cells in the cells chain
  158. bool yDirection = false; // previous segment in the original path was vertical
  159. bool xDirection = false; // previous segment in the original path was horizontal
  160. integer ixp = 0, iyp = 0; // previous cell
  161. struct structPoint {
  162. double x,y;
  163. };
  164. /* 1. Starting point always at origin */
  165. integer nxymax = thy nx + thy ny + 2;
  166. autoNUMvector<struct structPoint> xytimes (1, nxymax);
  167. xytimes [1]. x = my xmin;
  168. xytimes [1]. y = my ymin;
  169. /* 2. next point lower left of first cell */
  170. nsc_x = my path [1]. x;
  171. ixp = nsc_x - 1;
  172. xytimes [2]. x = my x1 + (nsc_x - 1 - 0.5) * my dx;
  173. nsc_y = my path [1]. y;
  174. iyp = nsc_y - 1;
  175. xytimes [2]. y = my y1 + (nsc_y - 1 - 0.5) * my dy;
  176. /* 3. follow all cells. implicit: my x1 - 0.5 * my dx > my xmin && my y1 - 0.5 * my dy > my ymin */
  177. nxy = 2;
  178. for (integer j = 1; j <= my pathLength; j ++) {
  179. integer index; // where are we in the new path?
  180. integer ix = my path [j]. x, iy = my path [j]. y;
  181. double xright = my x1 + (ix - 1 + 0.5) * my dx;
  182. double x, y, f, ytop = my y1 + (iy - 1 + 0.5) * my dy;
  183. if (iy == iyp) { // horizontal path?
  184. xDirection = true;
  185. if (yDirection) { // we came from vertical direction?
  186. // We came from a vertical direction so this is the second horizontal cell in a row.
  187. // The statement after this "if" updates nsc_x to 2.
  188. nsc_x = 1;
  189. yDirection = false;
  190. }
  191. nsc_x ++;
  192. if (nsc_y > 1 || nd > 1) {
  193. // Previous segment was diagonal or vertical: modify intersection
  194. // The vh intersection (x,y) = (nsc_x*dx, dy) * (nsc_y-1)/(nsc_x*nsc_y-1)
  195. // A diagonal segment has nsc_y = 1.
  196. f = (nsc_y - 1.0) / (nsc_x * nsc_y - 1);
  197. x = xright - nsc_x * my dx + nsc_x * my dx * f;
  198. y = ytop - my dy + my dy * f;
  199. index = nxy - 1;
  200. if (nsc_x == 2) {
  201. index = nxy;
  202. nxy ++;
  203. }
  204. xytimes [index]. x = x;
  205. xytimes [index]. y = y;
  206. }
  207. nd = 0;
  208. } else if (ix == ixp) { // vertical
  209. yDirection = true;
  210. if (xDirection) {
  211. nsc_y = 1;
  212. xDirection = false;
  213. }
  214. nsc_y ++;
  215. if (nsc_x > 1 || nd > 1) {
  216. // The hv intersection (x,y) = (dx, dy*nsc_y ) * (nsc_x-1)/(nsc_x*nsc_y-1)
  217. f = (nsc_x - 1.0) / (nsc_x * nsc_y - 1);
  218. x = xright - my dx + my dx * f;
  219. y = ytop - nsc_y * my dy + nsc_y * my dy * f;
  220. index = nxy - 1;
  221. if (nsc_y == 2) {
  222. index = nxy;
  223. nxy ++;
  224. }
  225. xytimes [index]. x = x;
  226. xytimes [index]. y = y;
  227. }
  228. nd = 0;
  229. } else if (ix == ixp + 1 && iy == iyp + 1) { // diagonal
  230. nd ++;
  231. if (nd == 1) {
  232. nxy ++;
  233. }
  234. nsc_x = nsc_y = 1;
  235. } else {
  236. Melder_throw (U"The path goes back in time.");
  237. }
  238. // update
  239. xytimes [nxy]. x = xright;
  240. xytimes [nxy]. y = ytop;
  241. ixp = ix;
  242. iyp = iy;
  243. }
  244. if (my xmax > xytimes [nxy].x || my ymax > xytimes [nxy].y) {
  245. nxy ++;
  246. xytimes [nxy].x = my xmax;
  247. xytimes [nxy].y = my ymax;
  248. }
  249. Melder_assert (nxy <= 2 * (my ny > my nx ? my ny : my nx) + 2);
  250. thy nxy = nxy;
  251. thy yfromx = RealTier_create (my xmin, my xmax);
  252. thy xfromy = RealTier_create (my ymin, my ymax);
  253. for (integer i = 1; i <= nxy; i ++) {
  254. RealTier_addPoint (thy yfromx.get(), xytimes [i]. x, xytimes [i]. y);
  255. RealTier_addPoint (thy xfromy.get(), xytimes [i]. y, xytimes [i]. x);
  256. }
  257. //DTW_Path_makeIndex (me, DTW_X);
  258. //DTW_Path_makeIndex (me, DTW_Y);
  259. } catch (MelderError) {
  260. Melder_throw (me, U": not recoded.");
  261. }
  262. }
  263. #if 0
  264. void DTW_Path_recode (DTW me) {
  265. DTW_Path_Query thee = & my pathQuery;
  266. integer nxy; // current number of elements in recoded path
  267. integer nx = 1; // current number of successive horizontal cells in the cells chain
  268. integer ny = 1; // current number of successive vertical cells in the cells chain
  269. integer nd = 0; // current number of successive diagonal cells in the cells chain
  270. int isv = 0; // previous segment in the original path was vertical
  271. int ish = 0; // previous segment in the original path was horizontal
  272. integer ixp = 0, iyp = 0; // previous cell
  273. /* Algorithm
  274. 1: get all the points in the path
  275. 2. get the x and y indices
  276. */
  277. /* 1. Starting point always at origin */
  278. thy xytimes [1]. x = my xmin;
  279. thy xytimes [1]. y = my ymin;
  280. nx = my path [1]. x;
  281. ixp = nx - 1;
  282. thy xytimes [2]. x = my x1 + (nx - 1 - 0.5) * my dx;
  283. ny = my path [1]. y;
  284. iyp = ny - 1;
  285. thy xytimes [2]. y = my y1 + (ny - 1 - 0.5) * my dy;
  286. // implicit: my x1 - 0.5 * my dx > my xmin && my y1 - 0.5 * my dy > my ymin
  287. nxy = 2;
  288. for (integer j = 1; j <= my pathLength; j ++) {
  289. integer index; // where are we in the new path?
  290. integer ix = my path [j]. x, iy = my path [j]. y;
  291. double xright = my x1 + (ix - 1 + 0.5) * my dx;
  292. double x, y, f, ytop = my y1 + (iy - 1 + 0.5) * my dy;
  293. if (iy == iyp) { // horizontal path?
  294. ish = 1;
  295. if (isv) { // we came from vertical direction?
  296. // We came from a vertical direction so this is the second horizontal cell in a row.
  297. // The statement after this "if" updates nx to 2.
  298. nx = 1; isv = 0;
  299. }
  300. nx ++;
  301. if (ny > 1 || nd > 1) {
  302. // Previous segment was diagonal or vertical: modify intersection
  303. // The vh intersection (x,y) = (nx*dx, dy) * (ny-1)/(nx*ny-1)
  304. // A diagonal segment has ny = 1.
  305. f = (ny - 1.0) / (nx * ny - 1);
  306. x = xright - nx * my dx + nx * my dx * f;
  307. y = ytop - my dy + my dy * f;
  308. index = nxy - 1;
  309. if (nx == 2) {
  310. index = nxy;
  311. nxy ++;
  312. }
  313. thy xytimes [index]. x = x;
  314. thy xytimes [index]. y = y;
  315. }
  316. nd = 0;
  317. } else if (ix == ixp) { // vertical
  318. isv = 1;
  319. if (ish) {
  320. ny = 1;
  321. ish = 0;
  322. }
  323. ny ++;
  324. if (nx > 1 || nd > 1) {
  325. // The hv intersection (x,y) = (dx, dy*ny ) * (nx-1)/(nx*ny-1)
  326. f = (nx - 1.0) / (nx * ny - 1);
  327. x = xright - my dx + my dx * f;
  328. y = ytop - ny * my dy + ny * my dy * f;
  329. index = nxy - 1;
  330. if (ny == 2) {
  331. index = nxy;
  332. nxy ++;
  333. }
  334. thy xytimes [index]. x = x;
  335. thy xytimes [index]. y = y;
  336. }
  337. nd = 0;
  338. } else if (ix == ixp + 1 && iy == iyp + 1) { // diagonal
  339. nd ++;
  340. if (nd == 1) {
  341. nxy ++;
  342. }
  343. nx = ny = 1;
  344. } else {
  345. Melder_throw (U"The path goes back in time.");
  346. }
  347. // update
  348. thy xytimes [nxy]. x = xright;
  349. thy xytimes [nxy]. y = ytop;
  350. ixp = ix;
  351. iyp = iy;
  352. }
  353. if (my xmax > thy xytimes [nxy]. x || my ymax > thy xytimes [nxy]. y) {
  354. nxy ++;
  355. thy xytimes [nxy]. x = my xmax;
  356. thy xytimes [nxy]. y = my ymax;
  357. }
  358. Melder_assert (nxy <= 2 * (my ny > my nx ? my ny : my nx) + 2);
  359. thy nxy = nxy;
  360. DTW_Path_makeIndex (me, DTW_X);
  361. DTW_Path_makeIndex (me, DTW_Y);
  362. }
  363. #endif
  364. void DTW_pathRemoveRedundantNodes (DTW me) {
  365. integer i = 1, skip = 0;
  366. for (integer j = 2; j <= my pathLength; j ++) {
  367. if ( (my path [j]. y == my path [i]. y) || my path [j]. x == my path [i]. x) {
  368. skip ++;
  369. } else {
  370. /* if (j-1)^th was the last of a series: store it */
  371. if (skip > 0) {
  372. my path [++ i] = my path [j - 1];
  373. }
  374. /* same check again */
  375. skip = 0;
  376. if ( (my path [j]. y == my path [i]. y) || my path [j]. x == my path [i]. x) {
  377. skip ++;
  378. } else {
  379. my path [++ i] = my path [j];
  380. }
  381. }
  382. }
  383. if (skip > 0) {
  384. my path [++ i] = my path [my pathLength];
  385. }
  386. my pathLength = i;
  387. }
  388. /* Prototype must be on y-axis and test on x-axis */
  389. autoDTW DTW_create (double tminp, double tmaxp, integer ntp, double dtp, double t1p, double tminc, double tmaxc, integer ntc, double dtc, double t1c) {
  390. try {
  391. autoDTW me = Thing_new (DTW);
  392. Matrix_init (me.get(), tminc, tmaxc, ntc, dtc, t1c, tminp, tmaxp, ntp, dtp, t1p);
  393. my path = NUMvector<structDTW_Path> (1, ntc + ntp - 1);
  394. DTW_Path_Query_init (& my pathQuery, ntp, ntc);
  395. my wx = 1;
  396. my wy = 1;
  397. my wd = 2;
  398. return me;
  399. } catch (MelderError) {
  400. Melder_throw (U"DTW not created.");
  401. }
  402. }
  403. void DTW_setWeights (DTW me, double wx, double wy, double wd) {
  404. my wx = wx;
  405. my wy = wy;
  406. my wd = wd;
  407. }
  408. autoDTW DTW_swapAxes (DTW me) {
  409. try {
  410. autoDTW thee = DTW_create (my xmin, my xmax, my nx, my dx, my x1, my ymin, my ymax, my ny, my dy, my y1);
  411. for (integer x = 1; x <= my nx; x ++) {
  412. for (integer y = 1; y <= my ny; y ++) {
  413. thy z [x] [y] = my z [y] [x];
  414. }
  415. }
  416. thy pathLength = my pathLength;
  417. for (integer i = 1; i <= my pathLength; i ++) {
  418. thy path [i]. x = my path [i]. y;
  419. thy path [i]. y = my path [i]. x;
  420. }
  421. return thee;
  422. } catch (MelderError) {
  423. Melder_throw (me, U": axes not swapped.");
  424. }
  425. }
  426. double DTW_getPathY (DTW me, double tx) {
  427. // Assume linear behaviour outside domains
  428. // Other option: scale with x_domain/y_domain
  429. if (tx < my xmin) {
  430. return my ymin - (my xmin - tx);
  431. }
  432. if (tx > my xmax) {
  433. return my ymax + (tx - my xmax);
  434. }
  435. // Find column in DTW matrix
  436. integer ix = Melder_ifloor ((tx - my x1) / my dx) + 1;
  437. if (ix < 1) {
  438. ix = 1;
  439. }
  440. if (ix > my nx) {
  441. ix = my nx;
  442. }
  443. // Find index in the path and the row number (iy)
  444. integer i = ix + my path [1]. x - 1;
  445. while (i <= my pathLength && my path [i]. x != ix) {
  446. i ++;
  447. }
  448. if (i > my pathLength) {
  449. return undefined;
  450. }
  451. integer iy = my path [i]. y; /* row */
  452. /*
  453. We like to have a "continuous" interpretation of time for the quantized x and y times.
  454. A time block is specified by lower left (x,y) and upper right (x+dx,y+dy).
  455. The path is interpreted as piecewise linear.
  456. Two special cases:
  457. 1. the local path is horizontal, i.e. two or more path elements with the same path.y value.
  458. We calculate the y-time from the line that connects the lower-left position of the leftmost
  459. horizontal time block to the upper-right position of the rightmost time horizontal block.
  460. (lowest column number to highest column number)
  461. For the first column and the last column we need to do something special if
  462. 2. the local path is vertical, i.e. two or path elements with the same path.x value.
  463. We calculate the y-time from the line that connects the lower-left position of the bottommost
  464. vertical time block to the upper-right position of the topmost time horizontal block.
  465. (lowest row number to highest row number)
  466. */
  467. // Horizontal path? Find left and right positions.
  468. integer ileft = i - 1;
  469. while (ileft >= 1 && my path [ileft]. y == iy) {
  470. ileft --;
  471. }
  472. ileft ++;
  473. if (ileft == 1 && ix > 1 && my path [ileft]. y > 1) {
  474. ileft ++;
  475. }
  476. integer iright = i + 1;
  477. while (iright <= my pathLength && my path [iright]. y == iy) {
  478. iright ++;
  479. }
  480. iright --;
  481. if (iright == my pathLength && ix < my nx && my path [iright]. y < my ny) {
  482. iright --;
  483. }
  484. integer nxx = iright - ileft + 1;
  485. // Vertical path? Only if not already horizontal.
  486. integer ibottom = i;
  487. integer itop = i;
  488. if (nxx == 1) {
  489. ibottom --;
  490. while (ibottom >= 1 && my path [ibottom]. x == ix) {
  491. ibottom --;
  492. }
  493. ibottom ++;
  494. itop ++;
  495. while (itop <= 1 && my path [itop]. x == ix) {
  496. itop --;
  497. }
  498. itop ++;
  499. }
  500. integer nyy = itop - ibottom + 1;
  501. double boxx = nxx * my dx;
  502. double boxy = nyy * my dy;
  503. double ty;
  504. // Corrections at extreme left and right if path [1].x=1 && path [1].y>1
  505. if (ix == my nx) {
  506. boxx = my xmax - (my x1 + (ix - 1) * my dx - my dx / 2.0);
  507. boxy = my ymax - (my y1 + (iy - 1) * my dy - my dy / 2.0);
  508. ty = my ymax - (boxy - (boxx - (my xmax - tx)) * boxy / boxx);
  509. } else if (ix == 1) {
  510. boxx = my x1 + my dx / 2.0 - my xmin;
  511. boxy = my y1 + (itop - 1) * my dy + my dy / 2.0 - my ymin;
  512. ty = (tx - my xmin) * boxy / boxx + my ymin;
  513. } else {
  514. // Diagonal interpolation in a box with lower left (0,0) and upper right (nxx*dx, nyy*dy).
  515. double ty0 = (tx - (my x1 + (my path [ileft]. x - 1.0) * my dx - my dx / 2.0)) * boxy / boxx;
  516. ty = my y1 + (my path [ibottom]. y - 1.0) * my dy - my dy / 2.0 + ty0;
  517. }
  518. return ty;
  519. }
  520. integer DTW_getMaximumConsecutiveSteps (DTW me, int direction) {
  521. integer nglobal = 1, nlocal = 1;
  522. for (integer i = 2; i <= my pathLength; i ++) {
  523. int localdirection;
  524. if (my path [i].y == my path [i - 1]. y) {
  525. localdirection = DTW_X;
  526. } else if (my path [i]. x == my path [i - 1]. x) {
  527. localdirection = DTW_Y;
  528. } else {
  529. localdirection = DTW_XANDY;
  530. }
  531. if (localdirection == direction) {
  532. nlocal += 1;
  533. }
  534. if (direction != localdirection || i == my pathLength) {
  535. if (nlocal > nglobal) {
  536. nglobal = nlocal;
  537. }
  538. nlocal = 1;
  539. }
  540. }
  541. return nglobal;
  542. }
  543. static void DTW_paintDistances_raw (DTW me, Graphics g, double xmin, double xmax, double ymin,
  544. double ymax, double minimum, double maximum, bool garnish, bool inset) {
  545. integer ixmin, ixmax, iymin, iymax;
  546. if (xmax <= xmin) {
  547. xmin = my xmin;
  548. xmax = my xmax;
  549. }
  550. if (ymax <= ymin) {
  551. ymin = my ymin;
  552. ymax = my ymax;
  553. }
  554. (void) Matrix_getWindowSamplesX (me, xmin - 0.49999 * my dx, xmax + 0.49999 * my dx, & ixmin, & ixmax);
  555. (void) Matrix_getWindowSamplesY (me, ymin - 0.49999 * my dy, ymax + 0.49999 * my dy, & iymin, & iymax);
  556. if (maximum <= minimum) {
  557. (void) Matrix_getWindowExtrema (me, ixmin, ixmax, iymin, iymax, & minimum, & maximum);
  558. }
  559. if (maximum <= minimum) {
  560. minimum -= 1.0;
  561. maximum += 1.0;
  562. }
  563. if (xmin >= xmax || ymin >= ymax)
  564. return;
  565. if (inset)
  566. Graphics_setInner (g);
  567. Graphics_setWindow (g, xmin, xmax, ymin, ymax);
  568. Graphics_cellArray (g, my z.at,
  569. ixmin, ixmax, Matrix_columnToX (me, ixmin - 0.5), Matrix_columnToX (me, ixmax + 0.5),
  570. iymin, iymax, Matrix_rowToY (me, iymin - 0.5), Matrix_rowToY (me, iymax + 0.5),
  571. minimum, maximum);
  572. Graphics_rectangle (g, xmin, xmax, ymin, ymax);
  573. if (inset)
  574. Graphics_unsetInner (g);
  575. if (garnish) {
  576. Graphics_marksBottom (g, 2, true, true, false);
  577. Graphics_marksLeft (g, 2, true, true, false);
  578. }
  579. }
  580. void DTW_paintDistances (DTW me, Graphics g, double xmin, double xmax, double ymin, double ymax, double minimum, double maximum, bool garnish) {
  581. DTW_paintDistances_raw (me, g, xmin, xmax, ymin, ymax, minimum, maximum, garnish, true);
  582. }
  583. static double RealTier_getXAtIndex (RealTier me, integer point) {
  584. double x = undefined;
  585. if (point > 0 && point <= my points.size) {
  586. x = my points.at [point] -> number;
  587. }
  588. return x;
  589. }
  590. static void DTW_drawPath_raw (DTW me, Graphics g, double xmin, double xmax, double ymin, double ymax, bool garnish, bool inset) {
  591. DTW_Path_Query thee = & my pathQuery;
  592. if (xmin >= xmax) {
  593. xmin = my xmin; xmax = my xmax;
  594. }
  595. if (ymin >= ymax) {
  596. ymin = my ymin; ymax = my ymax;
  597. }
  598. if (inset) {
  599. Graphics_setInner (g);
  600. }
  601. Graphics_setWindow (g, xmin, xmax, ymin, ymax);
  602. double x1 = RealTier_getXAtIndex (thy yfromx.get(), 1);
  603. double y1 = RealTier_getValueAtIndex (thy yfromx.get(), 1);
  604. for (integer i = 2; i <= thy yfromx -> points.size; i ++) {
  605. double x2 = RealTier_getXAtIndex (thy yfromx.get(), i);
  606. double y2 = RealTier_getValueAtIndex (thy yfromx.get(), i);
  607. double xc1, yc1, xc2, yc2;
  608. if (NUMclipLineWithinRectangle (x1, y1, x2, y2, xmin, ymin, xmax, ymax, & xc1, & yc1, & xc2, & yc2)) {
  609. Graphics_line (g, xc1, yc1, xc2, yc2);
  610. }
  611. x1 = x2;
  612. y1 = y2;
  613. }
  614. if (inset) {
  615. Graphics_unsetInner (g);
  616. }
  617. if (garnish) {
  618. Graphics_drawInnerBox (g);
  619. Graphics_marksBottom (g, 2, true, true, false);
  620. Graphics_marksLeft (g, 2, true, true, false);
  621. }
  622. }
  623. void DTW_drawPath (DTW me, Graphics g, double xmin, double xmax, double ymin, double ymax, bool garnish) {
  624. DTW_drawPath_raw (me, g, xmin, xmax, ymin, ymax, garnish, true);
  625. }
  626. static void DTW_drawWarp_raw (DTW me, Graphics g, double xmin, double xmax, double ymin, double ymax, double t, bool garnish, bool inset, bool warpX) {
  627. double tx = warpX ? t : DTW_getXTimeFromYTime (me, t);
  628. double ty = warpX ? DTW_getYTimeFromXTime (me, t) : t;
  629. int lineType = Graphics_inqLineType (g);
  630. if (xmin >= xmax) {
  631. xmin = my xmin; xmax = my xmax;
  632. }
  633. if (ymin >= ymax) {
  634. ymin = my ymin; ymax = my ymax;
  635. }
  636. if (inset) {
  637. Graphics_setInner (g);
  638. }
  639. Graphics_setWindow (g, xmin, xmax, ymin, ymax);
  640. Graphics_setLineType (g, Graphics_DOTTED);
  641. if (ty <= ymax) {
  642. Graphics_line (g, tx, ymin, tx, ty);
  643. Graphics_line (g, tx, ty, xmin, ty);
  644. } else {
  645. Graphics_line (g, tx, ymin, tx, ymax);
  646. }
  647. Graphics_setLineType (g, lineType);
  648. if (inset) {
  649. Graphics_unsetInner (g);
  650. }
  651. if (garnish) {
  652. Graphics_markBottom (g, tx, true, true, false, nullptr);
  653. if (ty <= ymax) {
  654. Graphics_markLeft (g, ty, true, true, false, nullptr);
  655. }
  656. }
  657. }
  658. void DTW_drawWarpX (DTW me, Graphics g, double xmin, double xmax, double ymin, double ymax, double tx, bool garnish) {
  659. DTW_drawWarp_raw (me, g, xmin, xmax, ymin, ymax, tx, garnish, true, true);
  660. }
  661. void DTW_drawWarpY (DTW me, Graphics g, double xmin, double xmax, double ymin, double ymax, double ty, bool garnish) {
  662. DTW_drawWarp_raw (me, g, xmin, xmax, ymin, ymax, ty, garnish, true, false);
  663. }
  664. static void DTW_Sounds_checkDomains (DTW me, Sound *y, Sound *x, double *xmin, double *xmax, double *ymin, double *ymax) {
  665. Melder_require ((my ymin == (*y) -> xmin && my ymax == (*y) -> xmax &&
  666. my xmin == (*x) -> xmin && my xmax == (*x) -> xmax) ||
  667. (my ymin == (*x) -> xmin && my ymax == (*x) -> xmax &&
  668. my xmin == (*y) -> xmin && my xmax == (*y) -> xmax), U"The domains of the DTW and the sound(s) should be equal.");
  669. if (*xmin >= *xmax) {
  670. *xmin = my xmin;
  671. *xmax = my xmax;
  672. }
  673. if (*ymin >= *ymax) {
  674. *ymin = my ymin;
  675. *ymax = my ymax;
  676. }
  677. }
  678. static void drawBox (Graphics g) {
  679. double x1WC, x2WC, y1WC, y2WC;
  680. double lineWidth = Graphics_inqLineWidth (g);
  681. Graphics_inqWindow (g, & x1WC, & x2WC, & y1WC, & y2WC);
  682. Graphics_setLineWidth (g, 2.0 * lineWidth);
  683. Graphics_rectangle (g, x1WC, x2WC, y1WC, y2WC);
  684. Graphics_setLineWidth (g, lineWidth);
  685. }
  686. /*
  687. In a picture with a DTW and a left and bottom Sound, we want "width" of the vertical sound
  688. and the "height" of the horizontal Sound t be equal.
  689. Given the horizontal fraction of the DTW-part, this routine returns the vertical part.
  690. */
  691. static double _DTW_Sounds_getPartY (Graphics g, double dtw_part_x) {
  692. double x1NDC, x2NDC, y1NDC, y2NDC;
  693. Graphics_inqViewport (g, & x1NDC, & x2NDC, & y1NDC, & y2NDC);
  694. return 1.0 - ((1.0 - dtw_part_x) * (x2NDC - x1NDC)) / (y2NDC - y1NDC);
  695. }
  696. void DTW_Sounds_draw (DTW me, Sound y, Sound x, Graphics g, double xmin, double xmax, double ymin, double ymax, bool garnish)
  697. {
  698. DTW_Sounds_checkDomains (me, & y, & x, & xmin, & xmax, & ymin, & ymax);
  699. Graphics_setInner (g);
  700. Graphics_Viewport ovp = g -> outerViewport; // save for unsetInner
  701. double dtw_part_x = 0.85;
  702. double dtw_part_y = _DTW_Sounds_getPartY (g, dtw_part_x);
  703. // DTW
  704. Graphics_Viewport vp = Graphics_insetViewport (g, 1.0 - dtw_part_x, 1.0, 1.0 - dtw_part_y, 1.0);
  705. DTW_paintDistances_raw (me, g, xmin, xmax, ymin, ymax, 0.0, 0.0, false, false);
  706. DTW_drawPath_raw (me, g, xmin, xmax, ymin, ymax, false, false);
  707. drawBox (g);
  708. Graphics_resetViewport (g, vp);
  709. // Sound y
  710. vp = Graphics_insetViewport (g, 0.0, 1.0 - dtw_part_x, 1.0 - dtw_part_y, 1.0);
  711. Sound_draw_btlr (y, g, ymin, ymax, -1.0, 1.0, FROM_BOTTOM_TO_TOP, 0);
  712. if (garnish)
  713. drawBox (g);
  714. Graphics_resetViewport (g, vp);
  715. // Sound x
  716. vp = Graphics_insetViewport (g, 1 - dtw_part_x, 1, 0, 1 - dtw_part_y);
  717. Sound_draw_btlr (x, g, xmin, xmax, -1.0, 1.0, FROM_LEFT_TO_RIGHT, 0);
  718. if (garnish)
  719. drawBox (g);
  720. Graphics_resetViewport (g, vp);
  721. // Set window coordinates so that margins will work, i.e. extend time domains
  722. double xmin3 = xmax - (xmax - xmin) / dtw_part_x;
  723. double ymin3 = ymax - (ymax - ymin) / dtw_part_y;
  724. Graphics_setWindow (g, xmin3, xmax, ymin3, ymax);
  725. g -> outerViewport = ovp; // restore from _setInner
  726. Graphics_unsetInner (g);
  727. if (garnish) {
  728. Graphics_markLeft (g, ymin, true, true, false, nullptr);
  729. Graphics_markLeft (g, ymax, true, true, false, nullptr);
  730. Graphics_markBottom (g, xmin, true, true, false, nullptr);
  731. Graphics_markBottom (g, xmax, true, true, false, nullptr);
  732. }
  733. }
  734. void DTW_Sounds_drawWarpX (DTW me, Sound yy, Sound xx, Graphics g, double xmin, double xmax, double ymin, double ymax, double tx, bool garnish)
  735. {
  736. Sound y = yy, x = xx;
  737. int lineType = Graphics_inqLineType (g);
  738. DTW_Sounds_checkDomains (me, & y, & x, & xmin, & xmax, & ymin, & ymax);
  739. Graphics_setInner (g);
  740. double dtw_part_x = 0.85;
  741. double dtw_part_y = _DTW_Sounds_getPartY (g, dtw_part_x);
  742. xmin = xmax - (xmax - xmin) / dtw_part_x;
  743. ymin = ymax - (ymax - ymin) / dtw_part_y;
  744. Graphics_setWindow (g, xmin, xmax, ymin, ymax);
  745. double ty = DTW_getYTimeFromXTime (me, tx);
  746. Graphics_setLineType (g, Graphics_DOTTED);
  747. Graphics_line (g, tx, ymin, tx, ty);
  748. Graphics_line (g, tx, ty, xmin, ty);
  749. Graphics_setLineType (g, lineType);
  750. Graphics_unsetInner (g);
  751. if (garnish) {
  752. Graphics_markBottom (g, tx, true, true, false, nullptr);
  753. Graphics_markLeft (g, ty, true, true, false, nullptr);
  754. }
  755. }
  756. autoMatrix DTW_to_Matrix_distances (DTW me) {
  757. try {
  758. autoMatrix thee = Matrix_create (my xmin, my xmax, my nx, my dx, my x1, my ymin, my ymax, my ny, my dy, my y1);
  759. matrixcopy_preallocated (thy z.get(), my z.get());
  760. return thee;
  761. } catch (MelderError) {
  762. Melder_throw (me, U": distances not converted to Matrix.");
  763. }
  764. }
  765. /* nog aanpassen, dl = sqrt (dx^2 + dy^2) */
  766. void DTW_drawDistancesAlongPath (DTW me, Graphics g, double xmin, double xmax, double dmin, double dmax, bool garnish) {
  767. if (xmin >= xmax) {
  768. xmin = my xmin;
  769. xmax = my xmax;
  770. }
  771. integer ixmax, ixmin;
  772. if (Matrix_getWindowSamplesX (me, xmin, xmax, & ixmin, & ixmax) == 0)
  773. return;
  774. integer ii = 1;
  775. while (ii < my pathLength && my path [ii]. x < ixmin)
  776. ii ++;
  777. ixmin = ii;
  778. while (ii <= my pathLength && my path [ii]. x < ixmax)
  779. ii ++;
  780. ixmax = ii;
  781. autoNUMvector<double> d (ixmin, ixmax);
  782. for (integer i = ixmin; i <= ixmax; i ++)
  783. d [i] = my z [my path [i]. y] [i];
  784. if (dmin >= dmax) {
  785. NUMvector_extrema (d.peek(), ixmin, ixmax, & dmin, & dmax);
  786. } else {
  787. for (integer i = ixmin; i <= ixmax; i ++) {
  788. if (d [i] > dmax)
  789. d [i] = dmax;
  790. else if (d [i] < dmin)
  791. d [i] = dmin;
  792. }
  793. }
  794. Graphics_setInner (g);
  795. Graphics_setWindow (g, xmin, xmax, dmin, dmax);
  796. Graphics_function (g, d.peek(), ixmin, ixmax, xmin, xmax);
  797. Graphics_unsetInner (g);
  798. if (garnish) {
  799. Graphics_drawInnerBox (g);
  800. Graphics_textLeft (g, true, U"distance");
  801. Graphics_marksBottom (g, 2, true, true, false);
  802. Graphics_marksLeft (g, 2, true, true, false);
  803. }
  804. }
  805. /*
  806. metric = 1...n (sum (a_i^n))^(1/n)
  807. */
  808. autoDTW Matrices_to_DTW (Matrix me, Matrix thee, bool matchStart, bool matchEnd, int slope, double metric) {
  809. try {
  810. Melder_require (thy ny == my ny, U"Column sizes should be equal.");
  811. autoDTW him = DTW_create (my xmin, my xmax, my nx, my dx, my x1, thy xmin, thy xmax, thy nx, thy dx, thy x1);
  812. autoMelderProgress progess (U"Calculate distances");
  813. for (integer i = 1; i <= my nx; i ++) {
  814. for (integer j = 1; j <= thy nx; j ++) {
  815. /*
  816. First divide distance by maximum to prevent overflow when metric
  817. is a large number.
  818. d = (x^n)^(1/n) may overflow if x>1 & n >>1 even if d would not overflow!
  819. */
  820. double dmax = 0.0, d = 0.0;
  821. for (integer k = 1; k <= my ny; k ++) {
  822. double dtmp = fabs (my z [k] [i] - thy z [k] [j]);
  823. if (dtmp > dmax) {
  824. dmax = dtmp;
  825. }
  826. }
  827. if (dmax > 0) {
  828. for (integer k = 1; k <= my ny; k ++) {
  829. double dtmp = fabs (my z [k] [i] - thy z [k] [j]) / dmax;
  830. d += pow (dtmp, metric);
  831. }
  832. }
  833. d = dmax * pow (d, 1.0 / metric);
  834. his z [i] [j] = d / my ny; // == d * dy / ymax
  835. }
  836. if ((i % 10) == 1) {
  837. Melder_progress (0.999 * i / my nx, U"Calculate distances: column ", i, U" from ", my nx, U".");
  838. }
  839. }
  840. DTW_findPath (him.get(), matchStart, matchEnd, slope);
  841. return him;
  842. } catch (MelderError) {
  843. Melder_throw (U"DTW not created from matrices.");
  844. }
  845. }
  846. autoDTW Spectrograms_to_DTW (Spectrogram me, Spectrogram thee, bool matchStart, bool matchEnd, int slope, double metric) {
  847. try {
  848. Melder_require (my xmin == thy xmin && my ymax == thy ymax && my ny == thy ny, U"The number of frequencies and/or frequency ranges should be equal.");
  849. autoMatrix m1 = Spectrogram_to_Matrix (me);
  850. autoMatrix m2 = Spectrogram_to_Matrix (thee);
  851. // Take log10 for dB's (4e-10 scaling not necessary)
  852. for (integer i = 1; i <= my ny; i ++) {
  853. for (integer j = 1; j <= my nx; j ++) {
  854. m1 -> z [i] [j] = 10 * log10 (m1 -> z [i] [j]);
  855. }
  856. }
  857. for (integer i = 1; i <= thy ny; i ++) {
  858. for (integer j = 1; j <= thy nx; j ++) {
  859. m2 -> z [i] [j] = 10 * log10 (m2 -> z [i] [j]);
  860. }
  861. }
  862. autoDTW him = Matrices_to_DTW (m1.get(), m2.get(), matchStart, matchEnd, slope, metric);
  863. return him;
  864. } catch (MelderError) {
  865. Melder_throw (U"DTW not created from Spectrograms.");
  866. }
  867. }
  868. static int Pitch_findFirstAndLastVoicedFrame (Pitch me, integer *first, integer *last) {
  869. *first = 1;
  870. while (*first <= my nx && ! Pitch_isVoiced_i (me, *first)) {
  871. (*first) ++;
  872. }
  873. *last = my nx;
  874. while (*last >= *first && ! Pitch_isVoiced_i (me, *last)) {
  875. (*last) --;
  876. }
  877. return *first <= my nx && *last >= 1;
  878. }
  879. autoDTW Pitches_to_DTW_sgc (Pitch me, Pitch thee, double vuv_costs, double time_weight, bool matchStart, bool matchEnd, int slope);
  880. autoDTW Pitches_to_DTW_sgc (Pitch me, Pitch thee, double vuv_costs, double time_weight, bool matchStart, bool matchEnd, int slope) { // vuv_costs=24, time_weight=10 ?
  881. try {
  882. Melder_require (vuv_costs >= 0.0, U"Voiced-unvoiced costs should not be negative.");
  883. Melder_require (time_weight >= 0.0, U"Time costs weight should not be negative.");
  884. integer myfirst, mylast, thyfirst, thylast;
  885. Melder_require (Pitch_findFirstAndLastVoicedFrame (me, & myfirst, & mylast) && Pitch_findFirstAndLastVoicedFrame (thee, & thyfirst, & thylast), U"No voiced frames.");
  886. /*
  887. We do not want the silences before the first voiced frame and after the last voiced frame
  888. to determine the distances.
  889. We create paths from (1,1)...(thyfirst,myfirst) and (thylast,mylast)...(thy nx,my nx)
  890. by making the other cell's distances very large.
  891. */
  892. autoDTW him = DTW_create (my xmin, my xmax, my nx, my dx, my x1, thy xmin, thy xmax, thy nx, thy dx, thy x1);
  893. autoNUMvector<double> pitchx (1, thy nx);
  894. kPitch_unit unit = kPitch_unit::SEMITONES_100;
  895. for (integer j = 1; j <= thy nx; j ++) {
  896. pitchx [j] = Sampled_getValueAtSample (thee, j, Pitch_LEVEL_FREQUENCY, (int) unit);
  897. }
  898. for (integer i = 1; i <= my nx; i ++) {
  899. double pitchy = Sampled_getValueAtSample (me, i, Pitch_LEVEL_FREQUENCY, (int) unit);
  900. double t1 = my x1 + (i - 1) * my dx;
  901. for (integer j = 1; j <= thy nx; j ++) {
  902. double t2 = thy x1 + (j - 1) * thy dx;
  903. double dist_f = 0.0; // based on pitch difference
  904. double dist_t = fabs (t1 - t2);
  905. if (isundef (pitchy)) {
  906. if (isdefined (pitchx [j])) {
  907. dist_f = vuv_costs;
  908. }
  909. } else if (isundef (pitchx [j])) {
  910. dist_f = vuv_costs;
  911. } else {
  912. dist_f = fabs (pitchy - pitchx [j]);
  913. }
  914. his z [i] [j] = sqrt (dist_f * dist_f + time_weight * dist_t * dist_t);
  915. }
  916. }
  917. DTW_findPath (him.get(), matchStart, matchEnd, slope);
  918. return him;
  919. } catch (MelderError) {
  920. Melder_throw (U"DTW not created from Pitches.");
  921. }
  922. }
  923. autoDTW Pitches_to_DTW (Pitch me, Pitch thee, double vuv_costs, double time_weight, bool matchStart, bool matchEnd, int slope) { // vuv_costs=24, time_weight=10 ?
  924. try {
  925. Melder_require (vuv_costs >= 0.0, U"Voiced-unvoiced costs should not be negative.");
  926. Melder_require (time_weight >= 0.0, U"Time costs weight should not be negative.");
  927. autoDTW him = DTW_create (my xmin, my xmax, my nx, my dx, my x1, thy xmin, thy xmax, thy nx, thy dx, thy x1);
  928. autoNUMvector<double> pitchx (1, thy nx);
  929. kPitch_unit unit = kPitch_unit::SEMITONES_100;
  930. for (integer j = 1; j <= thy nx; j ++) {
  931. pitchx [j] = Sampled_getValueAtSample (thee, j, Pitch_LEVEL_FREQUENCY, (int) unit);
  932. }
  933. for (integer i = 1; i <= my nx; i ++) {
  934. double pitchy = Sampled_getValueAtSample (me, i, Pitch_LEVEL_FREQUENCY, (int) unit);
  935. double t1 = my x1 + (i - 1) * my dx;
  936. for (integer j = 1; j <= thy nx; j ++) {
  937. double t2 = thy x1 + (j - 1) * thy dx;
  938. double dist_f = 0; // based on pitch difference
  939. double dist_t = fabs (t1 - t2);
  940. if (isundef (pitchy)) {
  941. if (isdefined (pitchx [j])) {
  942. dist_f = vuv_costs;
  943. }
  944. } else if (isundef (pitchx [j])) {
  945. dist_f = vuv_costs;
  946. } else {
  947. dist_f = fabs (pitchy - pitchx [j]);
  948. }
  949. his z [i] [j] = sqrt (dist_f * dist_f + time_weight * dist_t * dist_t);
  950. }
  951. }
  952. DTW_findPath (him.get(), matchStart, matchEnd, slope);
  953. return him;
  954. } catch (MelderError) {
  955. Melder_throw (U"DTW not created from Pitches.");
  956. }
  957. }
  958. autoDurationTier DTW_to_DurationTier (DTW /* me */) {
  959. return autoDurationTier();
  960. }
  961. void DTW_Matrix_replace (DTW me, Matrix thee) {
  962. try {
  963. Melder_require (my xmin == thy xmin && my xmax == thy xmax && my ymin == thy ymin && my ymax == thy ymax,
  964. U"The X and Y domains of the matrix and the DTW must be equal.");
  965. Melder_require (my nx == thy nx && my dx == thy dx && my ny == thy ny && my dy == thy dy,
  966. U"The sampling of the matrix and the DTW should be equal.");
  967. double minimum, maximum;
  968. Matrix_getWindowExtrema (me, 0, 0, 0, 0, & minimum, & maximum);
  969. Melder_require (minimum >= 0.0,
  970. U"Distances should not be negative.");
  971. matrixcopy_preallocated (my z.get(), thy z.get()); // from thee to me!
  972. } catch (MelderError) {
  973. Melder_throw (me, U": distances not replaced.");
  974. }
  975. }
  976. /****************** new implementation ********/
  977. void DTW_findPath (DTW me, bool matchStart, bool matchEnd, int slope) {
  978. DTW_findPath_special (me, matchStart, matchEnd, slope, nullptr);
  979. }
  980. autoMatrix DTW_to_Matrix_cumulativeDistances (DTW me, double sakoeChibaBand, int slope) {
  981. try {
  982. autoMatrix cumulativeDistances;
  983. DTW_findPath_bandAndSlope (me, sakoeChibaBand, slope, & cumulativeDistances);
  984. return cumulativeDistances;
  985. } catch (MelderError) {
  986. Melder_throw (me, U": cumulative costs matrix not created.");
  987. }
  988. }
  989. static void DTW_relaxConstraints (DTW me, double band, int slope, double *relaxedBand, int *relaxedSlope) {
  990. (void) slope;
  991. double dtw_slope = (my ymax - my ymin - band) / (my xmax - my xmin - band);
  992. dtw_slope = dtw_slope+1.0; // fake instruction t avoid compiler warning
  993. *relaxedBand = 0.0;
  994. *relaxedSlope = 1;
  995. }
  996. static void DTW_checkSlopeConstraints (DTW me, double band, int slope) {
  997. try {
  998. double slopes [5] = { DTW_BIG, DTW_BIG, 3.0, 2.0, 1.5 } ;
  999. double dtw_slope = (my ymax - my ymin - band) / (my xmax - my xmin - band);
  1000. Melder_require (slope > 0 && slope < 5, U"Invalid slope constraint.");
  1001. Melder_require (! (dtw_slope == 0.0 && slope != 1), U"Band too wide.");
  1002. if (dtw_slope < 1.0) {
  1003. dtw_slope = 1.0 / dtw_slope;
  1004. }
  1005. Melder_require (dtw_slope <= slopes [slope], U"There is a conflict between the chosen slope constraint and the relative duration. The duration ratio of the integerest and the shortest object is ", dtw_slope, U". This implies that the largest slope in the constraint must have a value greater or equal to this ratio.");
  1006. } catch (MelderError) {
  1007. Melder_throw (U"Slope constraints cannot be met.");
  1008. }
  1009. }
  1010. static void DTW_Polygon_setUnreachableParts (DTW me, Polygon thee, integer **psi) {
  1011. try {
  1012. double eps = my dx / 100.0; // safe enough
  1013. double dtw_slope = (my ymax - my ymin) / (my xmax - my xmin);
  1014. double xmin, xmax, ymin, ymax;
  1015. Polygon_getExtrema (thee, & xmin, & xmax, & ymin, & ymax);
  1016. // if the Polygon and the DTW don't overlap everything is unreachable!
  1017. if (xmax <= my xmin || xmin >= my xmax || ymax <= my ymin || ymin >= my ymax) {
  1018. Melder_throw (U"DTW and Polygon don't overlap.");
  1019. }
  1020. // find border "above" polygon
  1021. for (integer ix = 1; ix <= my nx; ix ++) {
  1022. double x = my x1 + (ix - 1) * my dx;
  1023. integer iystart = Melder_ifloor (dtw_slope * ix * (my dx / my dy)) + 1;
  1024. for (integer iy = iystart + 1; iy <= my ny; iy ++) {
  1025. double y = my y1 + (iy - 1) * my dy;
  1026. if (Polygon_getLocationOfPoint (thee, x, y, eps) == Polygon_OUTSIDE) {
  1027. for (integer k = iy; k <= my ny; k ++) {
  1028. psi [k] [ix] = DTW_UNREACHABLE;
  1029. }
  1030. break;
  1031. }
  1032. }
  1033. }
  1034. // find border "below" polygon
  1035. for (integer ix = 2; ix <= my nx; ix ++) {
  1036. double x = my x1 + (ix - 1) * my dx;
  1037. integer iystart = Melder_ifloor (dtw_slope * ix * (my dx / my dy)); // start 1 lower
  1038. if (iystart > my ny) iystart = my ny;
  1039. for (integer iy = iystart - 1; iy >= 1; iy --) {
  1040. double y = my y1 + (iy - 1) * my dy;
  1041. if (Polygon_getLocationOfPoint (thee, x, y, eps) == Polygon_OUTSIDE) {
  1042. for (integer k = iy; k >= 1; k --) {
  1043. psi [k] [ix] = DTW_UNREACHABLE;
  1044. }
  1045. break;
  1046. }
  1047. }
  1048. }
  1049. } catch (MelderError) {
  1050. Melder_throw (me, U" cannot set unreachable parts.");
  1051. }
  1052. }
  1053. #define DTW_ISREACHABLE(y,x) ((psi [y] [x] != DTW_UNREACHABLE) && (psi [y] [x] != DTW_FORBIDDEN))
  1054. static void DTW_findPath_special (DTW me, bool matchStart, bool matchEnd, int slope, autoMatrix *cumulativeDists) {
  1055. (void) matchStart;
  1056. (void) matchEnd;
  1057. try {
  1058. autoPolygon thee = DTW_to_Polygon (me, 0.0, slope);
  1059. DTW_Polygon_findPathInside (me, thee.get(), slope, cumulativeDists);
  1060. } catch (MelderError) {
  1061. Melder_throw (me, U": cannot find path.");
  1062. }
  1063. }
  1064. // Intersection of two straight lines y = a [i]*x+b [i], where a [2] = 1 / a [1]. Point (x1,y1) is on first line,
  1065. // point (x2,y2) is on second line.
  1066. static void getIntersectionPoint (double x1, double y1, double x2, double y2, double a, double *x3, double *y3) {
  1067. *x3 = (y2 - y1 + a * x1 - x2 / a) / (a - 1.0 / a);
  1068. *y3 = a * *x3 + y1 - a * x1;
  1069. }
  1070. autoPolygon DTW_to_Polygon (DTW me, double band, int slope) {
  1071. try {
  1072. try {
  1073. DTW_checkSlopeConstraints (me, band, slope);
  1074. } catch (MelderError) {
  1075. DTW_relaxConstraints (me, band, slope, & band, & slope);
  1076. Melder_flushError ();
  1077. }
  1078. double slopes [5] = { DTW_BIG, DTW_BIG, 3.0, 2.0, 1.5 } ;
  1079. if (band <= 0) {
  1080. if (slope == 1) {
  1081. autoPolygon thee = Polygon_create (4);
  1082. thy x [1] = my xmin;
  1083. thy y [1] = my ymin;
  1084. thy x [2] = my xmin;
  1085. thy y [2] = my ymax;
  1086. thy x [3] = my xmax;
  1087. thy y [3] = my ymax;
  1088. thy x [4] = my xmax;
  1089. thy y [4] = my ymin;
  1090. return thee;
  1091. } else {
  1092. autoPolygon thee = Polygon_create (4);
  1093. thy x [1] = my xmin;
  1094. thy y [1] = my ymin;
  1095. thy x [3] = my xmax;
  1096. thy y [3] = my ymax;
  1097. double x, y;
  1098. getIntersectionPoint (my xmin, my ymin, my xmax, my ymax, slopes [slope], & x, & y);
  1099. if (x < my xmin) x = my xmin;
  1100. if (x > my xmax) x = my xmax;
  1101. if (y < my ymin) y = my ymin;
  1102. if (y > my ymax) y = my ymax;
  1103. thy x [2] = x;
  1104. thy y [2] = y;
  1105. getIntersectionPoint (my xmin, my ymin, my xmax, my ymax, 1.0 / slopes [slope], & x, & y);
  1106. if (x < my xmin) x = my xmin;
  1107. if (x > my xmax) x = my xmax;
  1108. if (y < my ymin) y = my ymin;
  1109. if (y > my ymax) y = my ymax;
  1110. thy x [4] = x;
  1111. thy y [4] = y;
  1112. return thee;
  1113. }
  1114. } else {
  1115. if (slope == 1) {
  1116. autoPolygon thee = Polygon_create (6);
  1117. thy x [1] = my xmin;
  1118. thy y [1] = my ymin;
  1119. thy x [2] = my xmin;
  1120. thy y [2] = my ymin + band;
  1121. thy x [3] = my xmax - band;
  1122. thy y [3] = my ymax;
  1123. thy x [4] = my xmax;
  1124. thy y [4] = my ymax;
  1125. thy x [5] = my xmax;
  1126. thy y [5] = my ymax - band;
  1127. thy x [6] = my xmin + band;
  1128. thy y [6] = my ymin;
  1129. return thee;
  1130. } else {
  1131. autoPolygon thee = Polygon_create (8);
  1132. double x, y;
  1133. thy x [1] = my xmin;
  1134. thy y [1] = my ymin;
  1135. thy x [2] = my xmin;
  1136. thy y [2] = my ymin + band;
  1137. getIntersectionPoint (my xmin, my ymin + band, my xmax - band, my ymax, slopes [slope], & x, & y);
  1138. if (x < my xmin) x = my xmin;
  1139. if (x > my xmax) x = my xmax;
  1140. if (y < my ymin) y = my ymin;
  1141. if (y > my ymax) y = my ymax;
  1142. thy x [3] = x;
  1143. thy y [3] = y;
  1144. thy x [4] = my xmax - band;
  1145. thy y [4] = my ymax;
  1146. thy x [5] = my xmax;
  1147. thy y [5]= my ymax;
  1148. thy x [6] = my xmax;
  1149. thy y [6] = my ymax - band;
  1150. getIntersectionPoint (my xmin + band, my ymin, my xmax, my ymax - band, 1.0 / slopes [slope], & x, & y);
  1151. if (x < my xmin) x = my xmin;
  1152. if (x > my xmax) x = my xmax;
  1153. if (y < my ymin) y = my ymin;
  1154. if (y > my ymax) y = my ymax;
  1155. thy x [7] = x;
  1156. thy y [7] = y;
  1157. thy x [8] = my xmin + band;
  1158. thy y [8] = my ymin;
  1159. return thee;
  1160. }
  1161. }
  1162. } catch (MelderError) {
  1163. Melder_throw (me, U" no Polygon created.");
  1164. }
  1165. }
  1166. autoMatrix DTW_Polygon_to_Matrix_cumulativeDistances (DTW me, Polygon thee, int localSlope) {
  1167. try {
  1168. autoMatrix cumulativeDistances;
  1169. DTW_Polygon_findPathInside (me, thee, localSlope, & cumulativeDistances);
  1170. return cumulativeDistances;
  1171. } catch (MelderError) {
  1172. Melder_throw (me, U": cumulative costs matrix not created from DTW and Polygon.");
  1173. }
  1174. }
  1175. void DTW_findPath_bandAndSlope (DTW me, double sakoeChibaBand, int localSlope, autoMatrix *cumulativeDists) {
  1176. try {
  1177. autoPolygon thee = DTW_to_Polygon (me, sakoeChibaBand, localSlope);
  1178. DTW_Polygon_findPathInside (me, thee.get(), localSlope, cumulativeDists);
  1179. } catch (MelderError) {
  1180. Melder_throw (me, U" cannot determine the path.");
  1181. }
  1182. }
  1183. void DTW_Polygon_findPathInside (DTW me, Polygon thee, int localSlope, autoMatrix *cumulativeDists) {
  1184. try {
  1185. double slopes [5] = { DTW_BIG, DTW_BIG, 3.0, 2.0, 1.5 };
  1186. // if localSlope == 1 start of path is within 10% of minimum duration. Starts farther away
  1187. integer delta_xy = (my nx < my ny ? my nx : my ny) / 10; // if localSlope == 1 start within 10% of
  1188. if (localSlope < 1 || localSlope > 4) {
  1189. Melder_throw (U"Local slope parameter is illegal.");
  1190. }
  1191. autoNUMmatrix<double> delta (-2, my ny, -2, my nx);
  1192. autoNUMmatrix<integer> psi (-2, my ny, -2, my nx);
  1193. for (integer i = 1; i <= my ny; i ++) {
  1194. for (integer j = 1; j <= my nx; j ++) {
  1195. delta [i] [j] = my z [i] [j];
  1196. }
  1197. }
  1198. // start by making the outside unreachable
  1199. for (integer k = -2; k <= 1; k ++) {
  1200. for (integer j = -2; j <= my nx; j ++) {
  1201. // delta [k] [j] = DTW_BIG;
  1202. psi [k] [j] = DTW_UNREACHABLE;
  1203. }
  1204. for (integer i = 1; i <= my ny; i ++) {
  1205. // delta [i] [k] = DTW_BIG;
  1206. psi [i] [k] = DTW_UNREACHABLE;
  1207. }
  1208. }
  1209. // Make begin part of first column reachable
  1210. integer rowto = delta_xy;
  1211. if (localSlope != 1) {
  1212. rowto = Melder_ifloor (slopes [localSlope]) + 1;
  1213. }
  1214. for (integer iy = 2; iy <= rowto; iy ++) {
  1215. if (localSlope != 1) {
  1216. delta [iy] [1] = delta [iy - 1] [1] + my z [iy] [1];
  1217. psi [iy] [1] = DTW_Y;
  1218. } else {
  1219. psi [iy] [1] = DTW_START; // will be adapted by DTW_Polygon_setUnreachableParts
  1220. }
  1221. }
  1222. // Make begin part of first row reachable
  1223. integer colto = delta_xy;
  1224. if (localSlope != 1) {
  1225. colto = Melder_ifloor (slopes [localSlope]) + 1;
  1226. }
  1227. for (integer ix = 2; ix <= colto; ix ++) {
  1228. if (localSlope != 1) {
  1229. delta [1] [ix] = delta [1] [ix -1] + my z [1] [ix];
  1230. psi [1] [ix] = DTW_X;
  1231. } else {
  1232. psi [1] [ix] = DTW_START; // will be adapted by DTW_Polygon_setUnreachableParts
  1233. }
  1234. }
  1235. // Now we can set the unreachable parts from the Polygon
  1236. DTW_Polygon_setUnreachableParts (me, thee, psi.peek());
  1237. // Forward pass.
  1238. integer numberOfIsolatedPoints = 0;
  1239. autoMelderProgress progress (U"Find path");
  1240. for (integer j = 2; j <= my nx; j ++) {
  1241. for (integer i = 2; i <= my ny; i ++) {
  1242. if (! DTW_ISREACHABLE (i, j)) continue;
  1243. double g, gmin = DTW_BIG;
  1244. integer direction = 0;
  1245. if (DTW_ISREACHABLE (i - 1, j - 1)) {
  1246. gmin = delta [i - 1] [j - 1] + 2.0 * my z [i] [j];
  1247. direction = DTW_XANDY;
  1248. } else if (DTW_ISREACHABLE (i, j - 1)) {
  1249. gmin = delta [i] [j - 1] + my z [i] [j];
  1250. direction = DTW_X;
  1251. } else if (DTW_ISREACHABLE (i - 1, j)) {
  1252. gmin = delta [i - 1] [j] + my z [i] [j];
  1253. direction = DTW_Y;
  1254. } else {
  1255. numberOfIsolatedPoints ++;
  1256. continue;
  1257. }
  1258. switch (localSlope) {
  1259. case 1: { // no restriction
  1260. if (DTW_ISREACHABLE (i, j - 1) && ((g = delta [i] [j - 1] + my z [i] [j]) < gmin)) {
  1261. gmin = g;
  1262. direction = DTW_X;
  1263. }
  1264. if (DTW_ISREACHABLE (i - 1, j) && ((g = delta [i - 1] [j] + my z [i] [j]) < gmin)) {
  1265. gmin = g;
  1266. direction = DTW_Y;
  1267. }
  1268. }
  1269. break;
  1270. // P = 1/2
  1271. case 2: { // P = 1/2
  1272. if (DTW_ISREACHABLE (i - 1, j - 3) && psi [i] [j - 1] == DTW_X && psi [i] [j - 2] == DTW_XANDY &&
  1273. (g = delta [i-1] [j-3] + 2.0 * my z [i] [j-2] + my z [i] [j-1] + my z [i] [j]) < gmin) {
  1274. gmin = g;
  1275. direction = DTW_X;
  1276. }
  1277. if (DTW_ISREACHABLE (i - 1, j - 2) && psi [i] [j - 1] == DTW_XANDY &&
  1278. (g = delta [i - 1] [j - 2] + 2.0 * my z [i] [j - 1] + my z [i] [j]) < gmin) {
  1279. gmin = g;
  1280. direction = DTW_X;
  1281. }
  1282. if (DTW_ISREACHABLE (i - 2, j - 1) && psi [i - 1] [j] == DTW_XANDY &&
  1283. (g = delta [i - 2] [j - 1] + 2.0 * my z [i - 1] [j] + my z [i] [j]) < gmin) {
  1284. gmin = g;
  1285. direction = DTW_Y;
  1286. }
  1287. if (DTW_ISREACHABLE (i - 3, j - 1) && psi [i - 1] [j] == DTW_Y && psi [i - 2] [j] == DTW_XANDY &&
  1288. (g = delta [i-3] [j-1] + 2.0 * my z [i-2] [j] + my z [i-1] [j] + my z [i] [j]) < gmin) {
  1289. gmin = g;
  1290. direction = DTW_Y;
  1291. }
  1292. }
  1293. break;
  1294. // P = 1
  1295. case 3: {
  1296. if (DTW_ISREACHABLE (i - 1, j - 2) && psi [i] [j - 1] == DTW_XANDY &&
  1297. (g = delta [i - 1] [j - 2] + 2.0 * my z [i] [j - 1] + my z [i] [j]) < gmin) {
  1298. gmin = g;
  1299. direction = DTW_X;
  1300. }
  1301. if (DTW_ISREACHABLE (i - 2, j - 1) && psi [i - 1] [j] == DTW_XANDY &&
  1302. (g = delta [i - 2] [j - 1] + 2.0 * my z [i - 1] [j] + my z [i] [j]) < gmin) {
  1303. gmin = g;
  1304. direction = DTW_Y;
  1305. }
  1306. }
  1307. break;
  1308. // P = 2
  1309. case 4: {
  1310. if (DTW_ISREACHABLE (i - 2, j - 3) && psi [i] [j - 1] == DTW_XANDY && psi [i - 1] [j - 2] == DTW_XANDY &&
  1311. (g = delta [i-2] [j-3] + 2.0 * my z [i-1] [j-2] + 2.0 * my z [i] [j-1] + my z [i] [j]) < gmin) {
  1312. gmin = g;
  1313. direction = DTW_X;
  1314. }
  1315. if (DTW_ISREACHABLE (i - 3, j - 2) && psi [i - 1] [j] == DTW_XANDY && psi [i - 2] [j - 1] == DTW_XANDY &&
  1316. (g = delta [i-3] [j-2] + 2.0 * my z [i-2] [j-1] + 2.0 * my z [i-1] [j] + my z [i] [j]) < gmin) {
  1317. gmin = g;
  1318. direction = DTW_Y;
  1319. }
  1320. }
  1321. break;
  1322. default:
  1323. break;
  1324. }
  1325. Melder_assert (direction != 0);
  1326. psi [i] [j] = direction;
  1327. delta [i] [j] = gmin;
  1328. }
  1329. if ((j % 10) == 2) {
  1330. Melder_progress (0.999 * j / my nx, U"Calculate time warp: frame ", j, U" from ", my nx, U".");
  1331. }
  1332. }
  1333. // Find minimum at end of path and trace back.
  1334. integer iy = my ny;
  1335. double minimum = delta [iy] [my nx];
  1336. for (integer i = my ny - 1; i > 0; i --) {
  1337. if (! DTW_ISREACHABLE (i, my nx)) {
  1338. break; // we're in unreachable places
  1339. } else if (delta [i] [my nx] < minimum) {
  1340. minimum = delta [iy = i] [my nx];
  1341. }
  1342. }
  1343. integer pathIndex = my nx + my ny - 1; // maximum path length
  1344. my weightedDistance = minimum / (my nx + my ny);
  1345. my path [pathIndex]. y = iy;
  1346. integer ix = my path [pathIndex]. x = my nx;
  1347. // Fill path backwards.
  1348. while (ix > 1) {
  1349. if (psi [iy] [ix] == DTW_XANDY) {
  1350. ix --;
  1351. iy --;
  1352. } else if (psi [iy] [ix] == DTW_X) {
  1353. ix --;
  1354. } else if (psi [iy] [ix] == DTW_Y) {
  1355. iy --;
  1356. } else if (psi [iy] [ix] == DTW_START) {
  1357. break;
  1358. }
  1359. if (pathIndex < 2 || iy < 1) break;
  1360. //Melder_assert (pathIndex > 1 && iy > 0);
  1361. my path [-- pathIndex]. x = ix;
  1362. my path [pathIndex]. y = iy;
  1363. }
  1364. my pathLength = my nx + my ny - 1 - pathIndex + 1;
  1365. if (pathIndex > 1) {
  1366. for (integer j = 1; j <= my pathLength; j ++) {
  1367. my path [j] = my path [pathIndex ++];
  1368. }
  1369. }
  1370. DTW_Path_recode (me);
  1371. if (cumulativeDists) {
  1372. autoMatrix him = Matrix_create (my xmin, my xmax, my nx, my dx, my x1,
  1373. my ymin, my ymax, my ny, my dy, my y1);
  1374. for (integer i = 1; i <= my ny; i ++) {
  1375. for (integer j = 1; j <= my nx; j ++) {
  1376. his z [i] [j] = delta [i] [j];
  1377. }
  1378. }
  1379. *cumulativeDists = him.move();
  1380. }
  1381. } catch (MelderError) {
  1382. Melder_throw (me, U": cannot find path.");
  1383. }
  1384. }
  1385. /* End of file DTW.cpp */