ManipulationEditor.cpp 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272
  1. /* ManipulationEditor.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 "ManipulationEditor.h"
  19. #include "PitchTier_to_PointProcess.h"
  20. #include "Sound_to_PointProcess.h"
  21. #include "Sound_to_Pitch.h"
  22. #include "Pitch_to_PitchTier.h"
  23. #include "Pitch_to_PointProcess.h"
  24. #include "EditorM.h"
  25. #include "enums_getText.h"
  26. #include "ManipulationEditor_enums.h"
  27. #include "enums_getValue.h"
  28. #include "ManipulationEditor_enums.h"
  29. Thing_implement (ManipulationEditor, FunctionEditor, 0);
  30. #include "prefs_define.h"
  31. #include "ManipulationEditor_prefs.h"
  32. #include "prefs_install.h"
  33. #include "ManipulationEditor_prefs.h"
  34. #include "prefs_copyToInstance.h"
  35. #include "ManipulationEditor_prefs.h"
  36. /*
  37. * How to add a synthesis method (in an interruptable order):
  38. * 1. add an Manipulation_ #define in Manipulation.h;
  39. * 2. add a synthesize_ routine in Manipulation.cpp, and a reference to it in Manipulation_to_Sound;
  40. * 3. add a button in ManipulationEditor.h;
  41. * 4. add a cb_Synth_ callback.
  42. * 5. create the button in createMenus and update updateMenus;
  43. */
  44. static const conststring32 units_strings [] = { 0, U"Hz", U"st" };
  45. static int prefs_synthesisMethod = Manipulation_OVERLAPADD; /* Remembered across editor creations, not across Praat sessions. */
  46. /* BUG: 25 should be fmin */
  47. #define YLIN(freq) (my p_pitch_units == kManipulationEditor_pitchUnits::HERTZ ? ((freq) < 25 ? 25 : (freq)) : NUMhertzToSemitones ((freq) < 25 ? 25 : (freq)))
  48. #define YLININV(freq) (my p_pitch_units == kManipulationEditor_pitchUnits::HERTZ ? (freq) : NUMsemitonesToHertz (freq))
  49. static void updateMenus (ManipulationEditor me) {
  50. Melder_assert (my synthPulsesButton);
  51. GuiMenuItem_check (my synthPulsesButton, my synthesisMethod == Manipulation_PULSES);
  52. Melder_assert (my synthPulsesHumButton);
  53. GuiMenuItem_check (my synthPulsesHumButton, my synthesisMethod == Manipulation_PULSES_HUM);
  54. Melder_assert (my synthPulsesLpcButton);
  55. GuiMenuItem_check (my synthPulsesLpcButton, my synthesisMethod == Manipulation_PULSES_LPC);
  56. Melder_assert (my synthPitchButton);
  57. GuiMenuItem_check (my synthPitchButton, my synthesisMethod == Manipulation_PITCH);
  58. Melder_assert (my synthPitchHumButton);
  59. GuiMenuItem_check (my synthPitchHumButton, my synthesisMethod == Manipulation_PITCH_HUM);
  60. Melder_assert (my synthPulsesPitchButton);
  61. GuiMenuItem_check (my synthPulsesPitchButton, my synthesisMethod == Manipulation_PULSES_PITCH);
  62. Melder_assert (my synthPulsesPitchHumButton);
  63. GuiMenuItem_check (my synthPulsesPitchHumButton, my synthesisMethod == Manipulation_PULSES_PITCH_HUM);
  64. Melder_assert (my synthOverlapAddButton);
  65. GuiMenuItem_check (my synthOverlapAddButton, my synthesisMethod == Manipulation_OVERLAPADD);
  66. Melder_assert (my synthPitchLpcButton);
  67. GuiMenuItem_check (my synthPitchLpcButton, my synthesisMethod == Manipulation_PITCH_LPC);
  68. }
  69. /*
  70. * The "sound area" contains the original sound and the pulses.
  71. */
  72. static bool getSoundArea (ManipulationEditor me, double *ymin, double *ymax) {
  73. Manipulation ana = (Manipulation) my data;
  74. *ymin = 0.66;
  75. *ymax = 1.00;
  76. return ana -> sound || ana -> pulses;
  77. }
  78. /*
  79. * The "pitch area" contains the grey pitch analysis based on the pulses, and the blue pitch tier.
  80. */
  81. static bool getPitchArea (ManipulationEditor me, double *ymin, double *ymax) {
  82. Manipulation ana = (Manipulation) my data;
  83. *ymin = ana -> duration ? 0.16 : 0.00;
  84. *ymax = 0.65;
  85. return ana -> pulses || ana -> pitch;
  86. }
  87. static bool getDurationArea (ManipulationEditor me, double *ymin, double *ymax) {
  88. Manipulation ana = (Manipulation) my data;
  89. if (! ana -> duration) return false;
  90. *ymin = 0.00;
  91. *ymax = 0.15;
  92. return true;
  93. }
  94. /********** MENU COMMANDS **********/
  95. /***** FILE MENU *****/
  96. static void menu_cb_extractOriginalSound (ManipulationEditor me, EDITOR_ARGS_DIRECT) {
  97. Manipulation ana = (Manipulation) my data;
  98. if (! ana -> sound) return;
  99. autoSound publish = Data_copy (ana -> sound.get());
  100. Editor_broadcastPublication (me, publish.move());
  101. }
  102. static void menu_cb_extractPulses (ManipulationEditor me, EDITOR_ARGS_DIRECT) {
  103. Manipulation ana = (Manipulation) my data;
  104. if (! ana -> pulses) return;
  105. autoPointProcess publish = Data_copy (ana -> pulses.get());
  106. Editor_broadcastPublication (me, publish.move());
  107. }
  108. static void menu_cb_extractPitchTier (ManipulationEditor me, EDITOR_ARGS_DIRECT) {
  109. Manipulation ana = (Manipulation) my data;
  110. if (! ana -> pitch) return;
  111. autoPitchTier publish = Data_copy (ana -> pitch.get());
  112. Editor_broadcastPublication (me, publish.move());
  113. }
  114. static void menu_cb_extractDurationTier (ManipulationEditor me, EDITOR_ARGS_DIRECT) {
  115. Manipulation ana = (Manipulation) my data;
  116. if (! ana -> duration) return;
  117. autoDurationTier publish = Data_copy (ana -> duration.get());
  118. Editor_broadcastPublication (me, publish.move());
  119. }
  120. static void menu_cb_extractManipulatedSound (ManipulationEditor me, EDITOR_ARGS_DIRECT) {
  121. Manipulation ana = (Manipulation) my data;
  122. autoSound publish = Manipulation_to_Sound (ana, my synthesisMethod);
  123. Editor_broadcastPublication (me, publish.move());
  124. }
  125. /***** EDIT MENU *****/
  126. void structManipulationEditor :: v_saveData () {
  127. Manipulation ana = (Manipulation) our data;
  128. if (ana -> pulses) our previousPulses = Data_copy (ana -> pulses.get());
  129. if (ana -> pitch) our previousPitch = Data_copy (ana -> pitch.get());
  130. if (ana -> duration) our previousDuration = Data_copy (ana -> duration.get());
  131. }
  132. void structManipulationEditor :: v_restoreData () {
  133. Manipulation ana = (Manipulation) our data;
  134. autoPointProcess dummy1 = ana -> pulses.move(); ana -> pulses = our previousPulses.move(); our previousPulses = dummy1.move();
  135. autoPitchTier dummy2 = ana -> pitch.move(); ana -> pitch = our previousPitch.move(); our previousPitch = dummy2.move();
  136. autoDurationTier dummy3 = ana -> duration.move(); ana -> duration = our previousDuration.move(); our previousDuration = dummy3.move();
  137. }
  138. /***** PULSES MENU *****/
  139. static void menu_cb_removePulses (ManipulationEditor me, EDITOR_ARGS_DIRECT) {
  140. Manipulation ana = (Manipulation) my data;
  141. if (! ana -> pulses) return;
  142. Editor_save (me, U"Remove pulse(s)");
  143. if (my startSelection == my endSelection)
  144. PointProcess_removePointNear (ana -> pulses.get(), my startSelection);
  145. else
  146. PointProcess_removePointsBetween (ana -> pulses.get(), my startSelection, my endSelection);
  147. FunctionEditor_redraw (me);
  148. Editor_broadcastDataChanged (me);
  149. }
  150. static void menu_cb_addPulseAtCursor (ManipulationEditor me, EDITOR_ARGS_DIRECT) {
  151. Manipulation ana = (Manipulation) my data;
  152. if (! ana -> pulses) return;
  153. Editor_save (me, U"Add pulse");
  154. PointProcess_addPoint (ana -> pulses.get(), 0.5 * (my startSelection + my endSelection));
  155. FunctionEditor_redraw (me);
  156. Editor_broadcastDataChanged (me);
  157. }
  158. static void menu_cb_addPulseAt (ManipulationEditor me, EDITOR_ARGS_FORM) {
  159. EDITOR_FORM (U"Add pulse", nullptr)
  160. REAL (position, U"Position (s)", U"0.0")
  161. EDITOR_OK
  162. SET_REAL (position, 0.5 * (my startSelection + my endSelection))
  163. EDITOR_DO
  164. Manipulation ana = (Manipulation) my data;
  165. if (! ana -> pulses) return;
  166. Editor_save (me, U"Add pulse");
  167. PointProcess_addPoint (ana -> pulses.get(), position);
  168. FunctionEditor_redraw (me);
  169. Editor_broadcastDataChanged (me);
  170. EDITOR_END
  171. }
  172. /***** PITCH MENU *****/
  173. static void menu_cb_removePitchPoints (ManipulationEditor me, EDITOR_ARGS_DIRECT) {
  174. Manipulation ana = (Manipulation) my data;
  175. if (! ana -> pitch) return;
  176. Editor_save (me, U"Remove pitch point(s)");
  177. if (my startSelection == my endSelection)
  178. AnyTier_removePointNear (ana -> pitch.get()->asAnyTier(), my startSelection);
  179. else
  180. AnyTier_removePointsBetween (ana -> pitch.get()->asAnyTier(), my startSelection, my endSelection);
  181. FunctionEditor_redraw (me);
  182. Editor_broadcastDataChanged (me);
  183. }
  184. static void menu_cb_addPitchPointAtCursor (ManipulationEditor me, EDITOR_ARGS_DIRECT) {
  185. Manipulation ana = (Manipulation) my data;
  186. if (! ana -> pitch) return;
  187. Editor_save (me, U"Add pitch point");
  188. RealTier_addPoint (ana -> pitch.get(), 0.5 * (my startSelection + my endSelection), YLININV (my pitchTier.cursor));
  189. FunctionEditor_redraw (me);
  190. Editor_broadcastDataChanged (me);
  191. }
  192. static void menu_cb_addPitchPointAtSlice (ManipulationEditor me, EDITOR_ARGS_DIRECT) {
  193. Manipulation ana = (Manipulation) my data;
  194. PointProcess pulses = ana -> pulses.get();
  195. if (! pulses) Melder_throw (U"There are no pulses.");
  196. if (! ana -> pitch) return;
  197. integer ileft = PointProcess_getLowIndex (pulses, 0.5 * (my startSelection + my endSelection)), iright = ileft + 1, nt = pulses -> nt;
  198. double *t = pulses -> t.at;
  199. double f = my pitchTier.cursor; // default
  200. Editor_save (me, U"Add pitch point");
  201. if (nt <= 1) {
  202. /* Ignore. */
  203. } else if (ileft <= 0) {
  204. double tright = t [2] - t [1];
  205. if (tright > 0.0 && tright <= 0.02) f = YLIN (1.0 / tright);
  206. } else if (iright > nt) {
  207. double tleft = t [nt] - t [nt - 1];
  208. if (tleft > 0.0 && tleft <= 0.02) f = YLIN (1.0 / tleft);
  209. } else { /* Three-period median. */
  210. double tmid = t [iright] - t [ileft], tleft = 0.0, tright = 0.0;
  211. if (ileft > 1) tleft = t [ileft] - t [ileft - 1];
  212. if (iright < nt) tright = t [iright + 1] - t [iright];
  213. if (tleft > 0.02) tleft = 0;
  214. if (tmid > 0.02) tmid = 0;
  215. if (tright > 0.02) tright = 0;
  216. /* Bubble-sort. */
  217. if (tmid < tleft) { double dum = tmid; tmid = tleft; tleft = dum; }
  218. if (tright < tleft) { double dum = tright; tright = tleft; tleft = dum; }
  219. if (tright < tmid) { double dum = tright; tright = tmid; tmid = dum; }
  220. if (tleft != 0.0) f = YLIN (1 / tmid); // median of 3
  221. else if (tmid != 0.0) f = YLIN (2 / (tmid + tright)); // median of 2
  222. else if (tright != 0.0) f = YLIN (1 / tright); // median of 1
  223. }
  224. RealTier_addPoint (ana -> pitch.get(), 0.5 * (my startSelection + my endSelection), YLININV (f));
  225. FunctionEditor_redraw (me);
  226. Editor_broadcastDataChanged (me);
  227. }
  228. static void menu_cb_addPitchPointAt (ManipulationEditor me, EDITOR_ARGS_FORM) {
  229. EDITOR_FORM (U"Add pitch point", nullptr)
  230. REAL (time, U"Time (s)", U"0.0")
  231. REAL (frequency, U"Frequency (Hz or st)", U"100.0")
  232. EDITOR_OK
  233. SET_REAL (time, 0.5 * (my startSelection + my endSelection))
  234. SET_REAL (frequency, my pitchTier.cursor)
  235. EDITOR_DO
  236. Manipulation ana = (Manipulation) my data;
  237. if (! ana -> pitch) return;
  238. Editor_save (me, U"Add pitch point");
  239. RealTier_addPoint (ana -> pitch.get(), time, YLININV (frequency));
  240. FunctionEditor_redraw (me);
  241. Editor_broadcastDataChanged (me);
  242. EDITOR_END
  243. }
  244. static void menu_cb_stylizePitch (ManipulationEditor me, EDITOR_ARGS_FORM) {
  245. EDITOR_FORM (U"Stylize pitch", U"PitchTier: Stylize...")
  246. REAL (frequencyResolution, U"Frequency resolution", my default_pitch_stylize_frequencyResolution ())
  247. RADIO (units, U"Units", my default_pitch_stylize_useSemitones () + 1)
  248. RADIOBUTTON (U"Hertz")
  249. RADIOBUTTON (U"semitones")
  250. EDITOR_OK
  251. SET_REAL (frequencyResolution, my p_pitch_stylize_frequencyResolution)
  252. SET_OPTION (units, my p_pitch_stylize_useSemitones + 1)
  253. EDITOR_DO
  254. Manipulation ana = (Manipulation) my data;
  255. if (! ana -> pitch) return;
  256. Editor_save (me, U"Stylize pitch");
  257. PitchTier_stylize (ana -> pitch.get(),
  258. my pref_pitch_stylize_frequencyResolution () = my p_pitch_stylize_frequencyResolution = frequencyResolution,
  259. my pref_pitch_stylize_useSemitones () = my p_pitch_stylize_useSemitones = units - 1);
  260. FunctionEditor_redraw (me);
  261. Editor_broadcastDataChanged (me);
  262. EDITOR_END
  263. }
  264. static void menu_cb_stylizePitch_2st (ManipulationEditor me, EDITOR_ARGS_DIRECT) {
  265. Manipulation ana = (Manipulation) my data;
  266. if (! ana -> pitch) return;
  267. Editor_save (me, U"Stylize pitch");
  268. PitchTier_stylize (ana -> pitch.get(), 2.0, true);
  269. FunctionEditor_redraw (me);
  270. Editor_broadcastDataChanged (me);
  271. }
  272. static void menu_cb_interpolateQuadratically (ManipulationEditor me, EDITOR_ARGS_FORM) {
  273. EDITOR_FORM (U"Interpolate quadratically", nullptr)
  274. NATURAL (numberOfPointsPerParabola, U"Number of points per parabola", my default_pitch_interpolateQuadratically_numberOfPointsPerParabola ())
  275. EDITOR_OK
  276. SET_INTEGER (numberOfPointsPerParabola, my p_pitch_interpolateQuadratically_numberOfPointsPerParabola)
  277. EDITOR_DO
  278. Manipulation ana = (Manipulation) my data;
  279. if (! ana -> pitch) return;
  280. Editor_save (me, U"Interpolate quadratically");
  281. RealTier_interpolateQuadratically (ana -> pitch.get(),
  282. my pref_pitch_interpolateQuadratically_numberOfPointsPerParabola () = my p_pitch_interpolateQuadratically_numberOfPointsPerParabola = numberOfPointsPerParabola,
  283. my p_pitch_units == kManipulationEditor_pitchUnits::SEMITONES);
  284. FunctionEditor_redraw (me);
  285. Editor_broadcastDataChanged (me);
  286. EDITOR_END
  287. }
  288. static void menu_cb_interpolateQuadratically_4pts (ManipulationEditor me, EDITOR_ARGS_DIRECT) {
  289. Manipulation ana = (Manipulation) my data;
  290. if (! ana -> pitch) return;
  291. Editor_save (me, U"Interpolate quadratically");
  292. RealTier_interpolateQuadratically (ana -> pitch.get(), 4, my p_pitch_units == kManipulationEditor_pitchUnits::SEMITONES);
  293. FunctionEditor_redraw (me);
  294. Editor_broadcastDataChanged (me);
  295. }
  296. static void menu_cb_shiftPitchFrequencies (ManipulationEditor me, EDITOR_ARGS_FORM) {
  297. EDITOR_FORM (U"Shift pitch frequencies", nullptr)
  298. REAL (frequencyShift, U"Frequency shift", U"-20.0")
  299. OPTIONMENU (unit_i, U"Unit", 1)
  300. OPTION (U"Hertz")
  301. OPTION (U"mel")
  302. OPTION (U"logHertz")
  303. OPTION (U"semitones")
  304. OPTION (U"ERB")
  305. EDITOR_OK
  306. EDITOR_DO
  307. Manipulation ana = (Manipulation) my data;
  308. kPitch_unit unit =
  309. unit_i == 1 ? kPitch_unit::HERTZ :
  310. unit_i == 2 ? kPitch_unit::MEL :
  311. unit_i == 3 ? kPitch_unit::LOG_HERTZ :
  312. unit_i == 4 ? kPitch_unit::SEMITONES_1 :
  313. kPitch_unit::ERB;
  314. if (! ana -> pitch) return;
  315. Editor_save (me, U"Shift pitch frequencies");
  316. try {
  317. PitchTier_shiftFrequencies (ana -> pitch.get(), my startSelection, my endSelection, frequencyShift, unit);
  318. FunctionEditor_redraw (me);
  319. Editor_broadcastDataChanged (me);
  320. } catch (MelderError) {
  321. // the PitchTier may have partially changed
  322. FunctionEditor_redraw (me);
  323. Editor_broadcastDataChanged (me);
  324. throw;
  325. }
  326. EDITOR_END
  327. }
  328. static void menu_cb_multiplyPitchFrequencies (ManipulationEditor me, EDITOR_ARGS_FORM) {
  329. EDITOR_FORM (U"Multiply pitch frequencies", nullptr)
  330. POSITIVE (factor, U"Factor", U"1.2")
  331. LABEL (U"The multiplication is always done in hertz.")
  332. EDITOR_OK
  333. EDITOR_DO
  334. Manipulation ana = (Manipulation) my data;
  335. if (! ana -> pitch) return;
  336. Editor_save (me, U"Multiply pitch frequencies");
  337. PitchTier_multiplyFrequencies (ana -> pitch.get(), my startSelection, my endSelection, factor);
  338. FunctionEditor_redraw (me);
  339. Editor_broadcastDataChanged (me);
  340. EDITOR_END
  341. }
  342. static void menu_cb_setPitchRange (ManipulationEditor me, EDITOR_ARGS_FORM) {
  343. EDITOR_FORM (U"Set pitch range", nullptr)
  344. /* BUG: should include Minimum */
  345. REAL (maximum, U"Maximum (Hz or st)", my default_pitch_maximum ())
  346. EDITOR_OK
  347. SET_REAL (maximum, my p_pitch_maximum)
  348. EDITOR_DO
  349. if (maximum <= my pitchTier.minPeriodic)
  350. Melder_throw (U"Maximum pitch must be greater than ",
  351. Melder_half (my pitchTier.minPeriodic), U" ", units_strings [(int) my p_pitch_units], U".");
  352. my pref_pitch_maximum () = my p_pitch_maximum = maximum;
  353. FunctionEditor_redraw (me);
  354. EDITOR_END
  355. }
  356. static void menu_cb_setPitchUnits (ManipulationEditor me, EDITOR_ARGS_FORM) {
  357. EDITOR_FORM (U"Set pitch units", nullptr)
  358. RADIO_ENUM (kManipulationEditor_pitchUnits, pitchUnits,
  359. U"Pitch units", my default_pitch_units ())
  360. EDITOR_OK
  361. SET_ENUM (pitchUnits, kManipulationEditor_pitchUnits, my p_pitch_units)
  362. EDITOR_DO
  363. enum kManipulationEditor_pitchUnits oldPitchUnits = my p_pitch_units;
  364. my pref_pitch_units () = my p_pitch_units = pitchUnits;
  365. if (my p_pitch_units == oldPitchUnits) return;
  366. if (my p_pitch_units == kManipulationEditor_pitchUnits::HERTZ) {
  367. my p_pitch_minimum = 25.0;
  368. my pitchTier.minPeriodic = 50.0;
  369. my pref_pitch_maximum () = my p_pitch_maximum = NUMsemitonesToHertz (my p_pitch_maximum);
  370. my pitchTier.cursor = NUMsemitonesToHertz (my pitchTier.cursor);
  371. } else {
  372. my p_pitch_minimum = -24.0;
  373. my pitchTier.minPeriodic = -12.0;
  374. my pref_pitch_maximum () = my p_pitch_maximum = NUMhertzToSemitones (my p_pitch_maximum);
  375. my pitchTier.cursor = NUMhertzToSemitones (my pitchTier.cursor);
  376. }
  377. FunctionEditor_redraw (me);
  378. EDITOR_END
  379. }
  380. /***** DURATION MENU *****/
  381. static void menu_cb_setDurationRange (ManipulationEditor me, EDITOR_ARGS_FORM) {
  382. EDITOR_FORM (U"Set duration range", nullptr)
  383. REAL (minimum, U"Minimum", my default_duration_minimum ())
  384. REAL (maximum, U"Maximum", my default_duration_maximum ())
  385. EDITOR_OK
  386. SET_REAL (minimum, my p_duration_minimum)
  387. SET_REAL (maximum, my p_duration_maximum)
  388. EDITOR_DO
  389. Manipulation ana = (Manipulation) my data;
  390. double minimumValue = ana -> duration ? RealTier_getMinimumValue (ana -> duration.get()) : undefined;
  391. double maximumValue = ana -> duration ? RealTier_getMaximumValue (ana -> duration.get()) : undefined;
  392. if (minimum > 1) Melder_throw (U"Minimum relative duration must not be greater than 1.");
  393. if (maximum < 1) Melder_throw (U"Maximum relative duration must not be less than 1.");
  394. if (minimum >= maximum) Melder_throw (U"Maximum relative duration must be greater than minimum.");
  395. if (isdefined (minimumValue) && minimum > minimumValue)
  396. Melder_throw (U"Minimum relative duration must not be greater than the minimum value present, "
  397. U"which is ", Melder_half (minimumValue), U".");
  398. if (isdefined (maximumValue) && maximum < maximumValue)
  399. Melder_throw (U"Maximum relative duration must not be less than the maximum value present, "
  400. U"which is ", Melder_half (maximumValue), U".");
  401. my pref_duration_minimum () = my p_duration_minimum = minimum;
  402. my pref_duration_maximum () = my p_duration_maximum = maximum;
  403. FunctionEditor_redraw (me);
  404. EDITOR_END
  405. }
  406. static void menu_cb_setDraggingStrategy (ManipulationEditor me, EDITOR_ARGS_FORM) {
  407. EDITOR_FORM (U"Set dragging strategy", U"ManipulationEditor")
  408. RADIO_ENUM (kManipulationEditor_draggingStrategy, draggingStrategy,
  409. U"Dragging strategy", my default_pitch_draggingStrategy ())
  410. EDITOR_OK
  411. SET_ENUM (draggingStrategy, kManipulationEditor_draggingStrategy, my p_pitch_draggingStrategy)
  412. EDITOR_DO
  413. my pref_pitch_draggingStrategy () = my p_pitch_draggingStrategy = draggingStrategy;
  414. EDITOR_END
  415. }
  416. static void menu_cb_removeDurationPoints (ManipulationEditor me, EDITOR_ARGS_DIRECT) {
  417. Manipulation ana = (Manipulation) my data;
  418. if (! ana -> duration) return;
  419. Editor_save (me, U"Remove duration point(s)");
  420. if (my startSelection == my endSelection)
  421. AnyTier_removePointNear (ana -> duration.get()->asAnyTier(), 0.5 * (my startSelection + my endSelection));
  422. else
  423. AnyTier_removePointsBetween (ana -> duration.get()->asAnyTier(), my startSelection, my endSelection);
  424. FunctionEditor_redraw (me);
  425. Editor_broadcastDataChanged (me);
  426. }
  427. static void menu_cb_addDurationPointAtCursor (ManipulationEditor me, EDITOR_ARGS_DIRECT) {
  428. Manipulation ana = (Manipulation) my data;
  429. if (! ana -> duration) return;
  430. Editor_save (me, U"Add duration point");
  431. RealTier_addPoint (ana -> duration.get(), 0.5 * (my startSelection + my endSelection), my duration.cursor);
  432. FunctionEditor_redraw (me);
  433. Editor_broadcastDataChanged (me);
  434. }
  435. static void menu_cb_addDurationPointAt (ManipulationEditor me, EDITOR_ARGS_FORM) {
  436. EDITOR_FORM (U"Add duration point", nullptr)
  437. REAL (time, U"Time (s)", U"0.0");
  438. REAL (relativeDuration, U"Relative duration", U"1.0");
  439. EDITOR_OK
  440. SET_REAL (time, 0.5 * (my startSelection + my endSelection))
  441. EDITOR_DO
  442. Manipulation ana = (Manipulation) my data;
  443. if (! ana -> duration) return;
  444. Editor_save (me, U"Add duration point");
  445. RealTier_addPoint (ana -> duration.get(), time, relativeDuration);
  446. FunctionEditor_redraw (me);
  447. Editor_broadcastDataChanged (me);
  448. EDITOR_END
  449. }
  450. static void menu_cb_newDuration (ManipulationEditor me, EDITOR_ARGS_DIRECT) {
  451. Manipulation ana = (Manipulation) my data;
  452. Editor_save (me, U"New duration");
  453. ana -> duration = DurationTier_create (ana -> xmin, ana -> xmax);
  454. FunctionEditor_redraw (me);
  455. Editor_broadcastDataChanged (me);
  456. }
  457. static void menu_cb_forgetDuration (ManipulationEditor me, EDITOR_ARGS_DIRECT) {
  458. Manipulation ana = (Manipulation) my data;
  459. ana -> duration = autoDurationTier();
  460. FunctionEditor_redraw (me);
  461. Editor_broadcastDataChanged (me);
  462. }
  463. static void menu_cb_ManipulationEditorHelp (ManipulationEditor, EDITOR_ARGS_DIRECT) { Melder_help (U"ManipulationEditor"); }
  464. static void menu_cb_ManipulationHelp (ManipulationEditor, EDITOR_ARGS_DIRECT) { Melder_help (U"Manipulation"); }
  465. #define menu_cb_Synth_common(menu_cb,meth) \
  466. static void menu_cb (ManipulationEditor me, EDITOR_ARGS_DIRECT) { \
  467. prefs_synthesisMethod = my synthesisMethod = meth; \
  468. updateMenus (me); \
  469. }
  470. menu_cb_Synth_common (menu_cb_Synth_Pulses, Manipulation_PULSES)
  471. menu_cb_Synth_common (menu_cb_Synth_Pulses_hum, Manipulation_PULSES_HUM)
  472. menu_cb_Synth_common (menu_cb_Synth_Pulses_Lpc, Manipulation_PULSES_LPC)
  473. menu_cb_Synth_common (menu_cb_Synth_Pitch, Manipulation_PITCH)
  474. menu_cb_Synth_common (menu_cb_Synth_Pitch_hum, Manipulation_PITCH_HUM)
  475. menu_cb_Synth_common (menu_cb_Synth_Pulses_Pitch, Manipulation_PULSES_PITCH)
  476. menu_cb_Synth_common (menu_cb_Synth_Pulses_Pitch_hum, Manipulation_PULSES_PITCH_HUM)
  477. menu_cb_Synth_common (menu_cb_Synth_OverlapAdd_nodur, Manipulation_OVERLAPADD_NODUR)
  478. menu_cb_Synth_common (menu_cb_Synth_OverlapAdd, Manipulation_OVERLAPADD)
  479. menu_cb_Synth_common (menu_cb_Synth_Pitch_Lpc, Manipulation_PITCH_LPC)
  480. void structManipulationEditor :: v_createMenus () {
  481. ManipulationEditor_Parent :: v_createMenus ();
  482. Editor_addCommand (this, U"File", U"Extract original sound", 0, menu_cb_extractOriginalSound);
  483. Editor_addCommand (this, U"File", U"Extract pulses", 0, menu_cb_extractPulses);
  484. Editor_addCommand (this, U"File", U"Extract pitch tier", 0, menu_cb_extractPitchTier);
  485. Editor_addCommand (this, U"File", U"Extract duration tier", 0, menu_cb_extractDurationTier);
  486. Editor_addCommand (this, U"File", U"Publish resynthesis", 0, menu_cb_extractManipulatedSound);
  487. Editor_addCommand (this, U"File", U"-- close --", 0, nullptr);
  488. Editor_addMenu (this, U"Pulse", 0);
  489. Editor_addCommand (this, U"Pulse", U"Add pulse at cursor", 'P', menu_cb_addPulseAtCursor);
  490. Editor_addCommand (this, U"Pulse", U"Add pulse at...", 0, menu_cb_addPulseAt);
  491. Editor_addCommand (this, U"Pulse", U"-- remove pulses --", 0, nullptr);
  492. Editor_addCommand (this, U"Pulse", U"Remove pulse(s)", GuiMenu_OPTION | 'P', menu_cb_removePulses);
  493. Editor_addMenu (this, U"Pitch", 0);
  494. Editor_addCommand (this, U"Pitch", U"Add pitch point at cursor", 'T', menu_cb_addPitchPointAtCursor);
  495. Editor_addCommand (this, U"Pitch", U"Add pitch point at time slice", 0, menu_cb_addPitchPointAtSlice);
  496. Editor_addCommand (this, U"Pitch", U"Add pitch point at...", 0, menu_cb_addPitchPointAt);
  497. Editor_addCommand (this, U"Pitch", U"-- remove pitch --", 0, nullptr);
  498. Editor_addCommand (this, U"Pitch", U"Remove pitch point(s)", GuiMenu_OPTION | 'T', menu_cb_removePitchPoints);
  499. Editor_addCommand (this, U"Pitch", U"-- pitch prefs --", 0, nullptr);
  500. Editor_addCommand (this, U"Pitch", U"Set pitch range...", 0, menu_cb_setPitchRange);
  501. Editor_addCommand (this, U"Pitch", U"Set pitch units...", 0, menu_cb_setPitchUnits);
  502. Editor_addCommand (this, U"Pitch", U"Set pitch dragging strategy...", 0, menu_cb_setDraggingStrategy);
  503. Editor_addCommand (this, U"Pitch", U"-- modify pitch --", 0, nullptr);
  504. Editor_addCommand (this, U"Pitch", U"Shift pitch frequencies...", 0, menu_cb_shiftPitchFrequencies);
  505. Editor_addCommand (this, U"Pitch", U"Multiply pitch frequencies...", 0, menu_cb_multiplyPitchFrequencies);
  506. Editor_addCommand (this, U"Pitch", U"All:", GuiMenu_INSENSITIVE, menu_cb_stylizePitch);
  507. Editor_addCommand (this, U"Pitch", U"Stylize pitch...", 0, menu_cb_stylizePitch);
  508. Editor_addCommand (this, U"Pitch", U"Stylize pitch (2 st)", '2', menu_cb_stylizePitch_2st);
  509. Editor_addCommand (this, U"Pitch", U"Interpolate quadratically...", 0, menu_cb_interpolateQuadratically);
  510. Editor_addCommand (this, U"Pitch", U"Interpolate quadratically (4 pts)", '4', menu_cb_interpolateQuadratically_4pts);
  511. Editor_addMenu (this, U"Dur", 0);
  512. Editor_addCommand (this, U"Dur", U"Add duration point at cursor", 'D', menu_cb_addDurationPointAtCursor);
  513. Editor_addCommand (this, U"Dur", U"Add duration point at...", 0, menu_cb_addDurationPointAt);
  514. Editor_addCommand (this, U"Dur", U"-- remove duration --", 0, nullptr);
  515. Editor_addCommand (this, U"Dur", U"Remove duration point(s)", GuiMenu_OPTION | 'D', menu_cb_removeDurationPoints);
  516. Editor_addCommand (this, U"Dur", U"-- duration prefs --", 0, nullptr);
  517. Editor_addCommand (this, U"Dur", U"Set duration range...", 0, menu_cb_setDurationRange);
  518. Editor_addCommand (this, U"Dur", U"-- refresh duration --", 0, nullptr);
  519. Editor_addCommand (this, U"Dur", U"New duration", 0, menu_cb_newDuration);
  520. Editor_addCommand (this, U"Dur", U"Forget duration", 0, menu_cb_forgetDuration);
  521. Editor_addMenu (this, U"Synth", 0);
  522. our synthPulsesButton = Editor_addCommand (this, U"Synth", U"Pulses --", GuiMenu_RADIO_FIRST, menu_cb_Synth_Pulses);
  523. our synthPulsesHumButton = Editor_addCommand (this, U"Synth", U"Pulses (hum) --", GuiMenu_RADIO_NEXT, menu_cb_Synth_Pulses_hum);
  524. our synthPulsesLpcButton = Editor_addCommand (this, U"Synth", U"Pulses & LPC -- (\"LPC resynthesis\")", GuiMenu_RADIO_NEXT, menu_cb_Synth_Pulses_Lpc);
  525. Editor_addCommand (this, U"Synth", U"-- pitch resynth --", 0, nullptr);
  526. our synthPitchButton = Editor_addCommand (this, U"Synth", U" -- Pitch", GuiMenu_RADIO_NEXT, menu_cb_Synth_Pitch);
  527. our synthPitchHumButton = Editor_addCommand (this, U"Synth", U" -- Pitch (hum)", GuiMenu_RADIO_NEXT, menu_cb_Synth_Pitch_hum);
  528. our synthPulsesPitchButton = Editor_addCommand (this, U"Synth", U"Pulses -- Pitch", GuiMenu_RADIO_NEXT, menu_cb_Synth_Pulses_Pitch);
  529. our synthPulsesPitchHumButton = Editor_addCommand (this, U"Synth", U"Pulses -- Pitch (hum)", GuiMenu_RADIO_NEXT, menu_cb_Synth_Pulses_Pitch_hum);
  530. Editor_addCommand (this, U"Synth", U"-- full resynth --", 0, nullptr);
  531. our synthOverlapAddButton = Editor_addCommand (this, U"Synth", U"Sound & Pulses -- Pitch & Duration (\"Overlap-add manipulation\")", GuiMenu_RADIO_NEXT | GuiMenu_TOGGLE_ON, menu_cb_Synth_OverlapAdd);
  532. our synthPitchLpcButton = Editor_addCommand (this, U"Synth", U"LPC -- Pitch (\"LPC pitch manipulation\")", GuiMenu_RADIO_NEXT, menu_cb_Synth_Pitch_Lpc);
  533. }
  534. void structManipulationEditor :: v_createHelpMenuItems (EditorMenu menu) {
  535. ManipulationEditor_Parent :: v_createHelpMenuItems (menu);
  536. EditorMenu_addCommand (menu, U"ManipulationEditor help", '?', menu_cb_ManipulationEditorHelp);
  537. EditorMenu_addCommand (menu, U"Manipulation help", 0, menu_cb_ManipulationHelp);
  538. }
  539. /********** DRAWING AREA **********/
  540. static void drawSoundArea (ManipulationEditor me, double ymin, double ymax) {
  541. Manipulation ana = (Manipulation) my data;
  542. Sound sound = ana -> sound.get();
  543. PointProcess pulses = ana -> pulses.get();
  544. Graphics_Viewport viewport = Graphics_insetViewport (my graphics.get(), 0.0, 1.0, ymin, ymax);
  545. Graphics_setWindow (my graphics.get(), 0.0, 1.0, 0.0, 1.0);
  546. Graphics_setColour (my graphics.get(), Graphics_WHITE);
  547. Graphics_fillRectangle (my graphics.get(), 0.0, 1.0, 0.0, 1.0);
  548. Graphics_setColour (my graphics.get(), Graphics_BLACK);
  549. Graphics_rectangle (my graphics.get(), 0.0, 1.0, 0.0, 1.0);
  550. Graphics_setTextAlignment (my graphics.get(), Graphics_RIGHT, Graphics_TOP);
  551. Graphics_setFont (my graphics.get(), kGraphics_font::TIMES);
  552. Graphics_text (my graphics.get(), 1.0, 1.0, U"%%Sound");
  553. Graphics_setColour (my graphics.get(), Graphics_BLUE);
  554. Graphics_text (my graphics.get(), 1.0, 1.0 - Graphics_dyMMtoWC (my graphics.get(), 3), U"%%Pulses");
  555. Graphics_setFont (my graphics.get(), kGraphics_font::HELVETICA);
  556. /*
  557. * Draw blue pulses.
  558. */
  559. if (pulses) {
  560. Graphics_setWindow (my graphics.get(), my startWindow, my endWindow, 0.0, 1.0);
  561. Graphics_setColour (my graphics.get(), Graphics_BLUE);
  562. for (integer i = 1; i <= pulses -> nt; i ++) {
  563. double t = pulses -> t [i];
  564. if (t >= my startWindow && t <= my endWindow)
  565. Graphics_line (my graphics.get(), t, 0.05, t, 0.95);
  566. }
  567. }
  568. /*
  569. * Draw sound.
  570. */
  571. integer first, last;
  572. if (sound && Sampled_getWindowSamples (sound, my startWindow, my endWindow, & first, & last) > 1) {
  573. double minimum, maximum, scaleMin, scaleMax;
  574. Matrix_getWindowExtrema (sound, first, last, 1, 1, & minimum, & maximum);
  575. if (minimum == maximum) minimum = -0.5, maximum = +0.5;
  576. /*
  577. * Scaling.
  578. */
  579. scaleMin = 0.83 * minimum + 0.17 * my soundmin;
  580. scaleMax = 0.83 * maximum + 0.17 * my soundmax;
  581. Graphics_setWindow (my graphics.get(), my startWindow, my endWindow, scaleMin, scaleMax);
  582. FunctionEditor_drawRangeMark (me, scaleMin, Melder_float (Melder_half (scaleMin)), U"", Graphics_BOTTOM);
  583. FunctionEditor_drawRangeMark (me, scaleMax, Melder_float (Melder_half (scaleMax)), U"", Graphics_TOP);
  584. /*
  585. * Draw dotted zero line.
  586. */
  587. if (minimum < 0.0 && maximum > 0.0) {
  588. Graphics_setColour (my graphics.get(), Graphics_CYAN);
  589. Graphics_setLineType (my graphics.get(), Graphics_DOTTED);
  590. Graphics_line (my graphics.get(), my startWindow, 0.0, my endWindow, 0.0);
  591. Graphics_setLineType (my graphics.get(), Graphics_DRAWN);
  592. }
  593. /*
  594. * Draw samples.
  595. */
  596. Graphics_setColour (my graphics.get(), Graphics_BLACK);
  597. Graphics_function (my graphics.get(), & sound -> z [1] [0], first, last,
  598. Sampled_indexToX (sound, first), Sampled_indexToX (sound, last));
  599. }
  600. Graphics_resetViewport (my graphics.get(), viewport);
  601. }
  602. static void drawPitchArea (ManipulationEditor me, double ymin, double ymax) {
  603. Manipulation ana = (Manipulation) my data;
  604. PointProcess pulses = ana -> pulses.get();
  605. PitchTier pitch = ana -> pitch.get();
  606. integer ifirstSelected, ilastSelected, n = pitch ? pitch -> points.size : 0, imin, imax, i;
  607. int cursorVisible = my startSelection == my endSelection && my startSelection >= my startWindow && my startSelection <= my endWindow;
  608. double minimumFrequency = YLIN (50);
  609. int rangePrecisions [] = { 0, 1, 2 };
  610. static const conststring32 rangeUnits [] = { U"", U" Hz", U" st" };
  611. /*
  612. * Pitch contours.
  613. */
  614. Graphics_Viewport viewport = Graphics_insetViewport (my graphics.get(), 0, 1, ymin, ymax);
  615. Graphics_setWindow (my graphics.get(), 0.0, 1.0, 0.0, 1.0);
  616. Graphics_setColour (my graphics.get(), Graphics_WHITE);
  617. Graphics_fillRectangle (my graphics.get(), 0.0, 1.0, 0.0, 1.0);
  618. Graphics_setColour (my graphics.get(), Graphics_BLACK);
  619. Graphics_rectangle (my graphics.get(), 0.0, 1.0, 0.0, 1.0);
  620. Graphics_setColour (my graphics.get(), Graphics_GREEN);
  621. Graphics_setFont (my graphics.get(), kGraphics_font::TIMES);
  622. Graphics_setTextAlignment (my graphics.get(), Graphics_RIGHT, Graphics_TOP);
  623. Graphics_text (my graphics.get(), 1.0, 1.0, U"%%Pitch manip");
  624. Graphics_setGrey (my graphics.get(), 0.7);
  625. Graphics_text (my graphics.get(), 1.0, 1.0 - Graphics_dyMMtoWC (my graphics.get(), 3), U"%%Pitch from pulses");
  626. Graphics_setFont (my graphics.get(), kGraphics_font::HELVETICA);
  627. Graphics_setWindow (my graphics.get(), my startWindow, my endWindow, my p_pitch_minimum, my p_pitch_maximum);
  628. /*
  629. * Draw pitch contour based on pulses.
  630. */
  631. Graphics_setGrey (my graphics.get(), 0.7);
  632. if (pulses) for (i = 1; i < pulses -> nt; i ++) {
  633. double tleft = pulses -> t [i], tright = pulses -> t [i + 1], t = 0.5 * (tleft + tright);
  634. if (t >= my startWindow && t <= my endWindow) {
  635. if (tleft != tright) {
  636. double f = YLIN (1 / (tright - tleft));
  637. if (f >= my pitchTier.minPeriodic && f <= my p_pitch_maximum) {
  638. Graphics_fillCircle_mm (my graphics.get(), t, f, 1);
  639. }
  640. }
  641. }
  642. }
  643. Graphics_setGrey (my graphics.get(), 0.0);
  644. FunctionEditor_drawGridLine (me, minimumFrequency);
  645. FunctionEditor_drawRangeMark (me, my p_pitch_maximum,
  646. Melder_fixed (my p_pitch_maximum, rangePrecisions [(int) my p_pitch_units]), rangeUnits [(int) my p_pitch_units], Graphics_TOP);
  647. FunctionEditor_drawRangeMark (me, my p_pitch_minimum,
  648. Melder_fixed (my p_pitch_minimum, rangePrecisions [(int) my p_pitch_units]), rangeUnits [(int) my p_pitch_units], Graphics_BOTTOM);
  649. if (my startSelection == my endSelection && my pitchTier.cursor >= my p_pitch_minimum && my pitchTier.cursor <= my p_pitch_maximum)
  650. FunctionEditor_drawHorizontalHair (me, my pitchTier.cursor,
  651. Melder_fixed (my pitchTier.cursor, rangePrecisions [(int) my p_pitch_units]), rangeUnits [(int) my p_pitch_units]);
  652. if (cursorVisible && n > 0) {
  653. double y = YLIN (RealTier_getValueAtTime (pitch, my startSelection));
  654. FunctionEditor_insertCursorFunctionValue (me, y,
  655. Melder_fixed (y, rangePrecisions [(int) my p_pitch_units]), rangeUnits [(int) my p_pitch_units],
  656. my p_pitch_minimum, my p_pitch_maximum);
  657. }
  658. if (pitch) {
  659. ifirstSelected = AnyTier_timeToHighIndex (pitch->asAnyTier(), my startSelection);
  660. ilastSelected = AnyTier_timeToLowIndex (pitch->asAnyTier(), my endSelection);
  661. imin = AnyTier_timeToHighIndex (pitch->asAnyTier(), my startWindow);
  662. imax = AnyTier_timeToLowIndex (pitch->asAnyTier(), my endWindow);
  663. }
  664. Graphics_setLineWidth (my graphics.get(), 2.0);
  665. if (n == 0) {
  666. Graphics_setTextAlignment (my graphics.get(), Graphics_CENTRE, Graphics_HALF);
  667. Graphics_setColour (my graphics.get(), Graphics_BLACK);
  668. Graphics_text (my graphics.get(), 0.5 * (my startWindow + my endWindow), 0.5 * (my p_pitch_minimum + my p_pitch_maximum), U"(no pitch points)");
  669. } else if (imax < imin) {
  670. double fleft = YLIN (RealTier_getValueAtTime (pitch, my startWindow));
  671. double fright = YLIN (RealTier_getValueAtTime (pitch, my endWindow));
  672. Graphics_setColour (my graphics.get(), Graphics_GREEN);
  673. Graphics_line (my graphics.get(), my startWindow, fleft, my endWindow, fright);
  674. } else {
  675. for (i = imin; i <= imax; i ++) {
  676. RealPoint point = pitch -> points.at [i];
  677. double t = point -> number, f = YLIN (point -> value);
  678. Graphics_setColour (my graphics.get(), Graphics_GREEN);
  679. if (i == 1)
  680. Graphics_line (my graphics.get(), my startWindow, f, t, f);
  681. else if (i == imin)
  682. Graphics_line (my graphics.get(), t, f, my startWindow, YLIN (RealTier_getValueAtTime (pitch, my startWindow)));
  683. if (i == n)
  684. Graphics_line (my graphics.get(), t, f, my endWindow, f);
  685. else if (i == imax)
  686. Graphics_line (my graphics.get(), t, f, my endWindow, YLIN (RealTier_getValueAtTime (pitch, my endWindow)));
  687. else {
  688. RealPoint pointRight = pitch -> points.at [i + 1];
  689. Graphics_line (my graphics.get(), t, f, pointRight -> number, YLIN (pointRight -> value));
  690. }
  691. }
  692. for (i = imin; i <= imax; i ++) {
  693. RealPoint point = pitch -> points.at [i];
  694. double t = point -> number, f = YLIN (point -> value);
  695. if (i >= ifirstSelected && i <= ilastSelected)
  696. Graphics_setColour (my graphics.get(), Graphics_RED);
  697. else
  698. Graphics_setColour (my graphics.get(), Graphics_GREEN);
  699. Graphics_fillCircle_mm (my graphics.get(), t, f, 3.0);
  700. }
  701. }
  702. Graphics_setLineWidth (my graphics.get(), 1.0);
  703. Graphics_setColour (my graphics.get(), Graphics_BLACK);
  704. Graphics_resetViewport (my graphics.get(), viewport);
  705. }
  706. static void drawDurationArea (ManipulationEditor me, double ymin, double ymax) {
  707. Manipulation ana = (Manipulation) my data;
  708. DurationTier duration = ana -> duration.get();
  709. integer ifirstSelected, ilastSelected, n = duration ? duration -> points.size : 0, imin, imax, i;
  710. int cursorVisible = my startSelection == my endSelection && my startSelection >= my startWindow && my startSelection <= my endWindow;
  711. /*
  712. * Duration contours.
  713. */
  714. Graphics_Viewport viewport = Graphics_insetViewport (my graphics.get(), 0.0, 1.0, ymin, ymax);
  715. Graphics_setWindow (my graphics.get(), 0.0, 1.0, 0.0, 1.0);
  716. Graphics_setColour (my graphics.get(), Graphics_WHITE);
  717. Graphics_fillRectangle (my graphics.get(), 0.0, 1.0, 0.0, 1.0);
  718. Graphics_setColour (my graphics.get(), Graphics_BLACK);
  719. Graphics_rectangle (my graphics.get(), 0.0, 1.0, 0.0, 1.0);
  720. Graphics_setColour (my graphics.get(), Graphics_GREEN);
  721. Graphics_setFont (my graphics.get(), kGraphics_font::TIMES);
  722. Graphics_setTextAlignment (my graphics.get(), Graphics_RIGHT, Graphics_TOP);
  723. Graphics_text (my graphics.get(), 1.0, 1.0, U"%%Duration manip");
  724. Graphics_setFont (my graphics.get(), kGraphics_font::HELVETICA);
  725. Graphics_setWindow (my graphics.get(), my startWindow, my endWindow, my p_duration_minimum, my p_duration_maximum);
  726. FunctionEditor_drawGridLine (me, 1.0);
  727. FunctionEditor_drawRangeMark (me, my p_duration_maximum, Melder_fixed (my p_duration_maximum, 3), U"", Graphics_TOP);
  728. FunctionEditor_drawRangeMark (me, my p_duration_minimum, Melder_fixed (my p_duration_minimum, 3), U"", Graphics_BOTTOM);
  729. if (my startSelection == my endSelection && my duration.cursor >= my p_duration_minimum && my duration.cursor <= my p_duration_maximum)
  730. FunctionEditor_drawHorizontalHair (me, my duration.cursor, Melder_fixed (my duration.cursor, 3), U"");
  731. if (cursorVisible && n > 0) {
  732. double y = RealTier_getValueAtTime (duration, my startSelection);
  733. FunctionEditor_insertCursorFunctionValue (me, y, Melder_fixed (y, 3), U"", my p_duration_minimum, my p_duration_maximum);
  734. }
  735. /*
  736. * Draw duration tier.
  737. */
  738. if (duration) {
  739. ifirstSelected = AnyTier_timeToHighIndex (duration->asAnyTier(), my startSelection);
  740. ilastSelected = AnyTier_timeToLowIndex (duration->asAnyTier(), my endSelection);
  741. imin = AnyTier_timeToHighIndex (duration->asAnyTier(), my startWindow);
  742. imax = AnyTier_timeToLowIndex (duration->asAnyTier(), my endWindow);
  743. }
  744. Graphics_setLineWidth (my graphics.get(), 2.0);
  745. if (n == 0) {
  746. Graphics_setColour (my graphics.get(), Graphics_BLACK);
  747. Graphics_setTextAlignment (my graphics.get(), Graphics_CENTRE, Graphics_HALF);
  748. Graphics_text (my graphics.get(), 0.5 * (my startWindow + my endWindow),
  749. 0.5 * (my p_duration_minimum + my p_duration_maximum), U"(no duration points)");
  750. } else if (imax < imin) {
  751. double fleft = RealTier_getValueAtTime (duration, my startWindow);
  752. double fright = RealTier_getValueAtTime (duration, my endWindow);
  753. Graphics_setColour (my graphics.get(), Graphics_GREEN);
  754. Graphics_line (my graphics.get(), my startWindow, fleft, my endWindow, fright);
  755. } else {
  756. for (i = imin; i <= imax; i ++) {
  757. RealPoint point = duration -> points.at [i];
  758. double t = point -> number, dur = point -> value;
  759. Graphics_setColour (my graphics.get(), Graphics_GREEN);
  760. if (i == 1)
  761. Graphics_line (my graphics.get(), my startWindow, dur, t, dur);
  762. else if (i == imin)
  763. Graphics_line (my graphics.get(), t, dur, my startWindow, RealTier_getValueAtTime (duration, my startWindow));
  764. if (i == n)
  765. Graphics_line (my graphics.get(), t, dur, my endWindow, dur);
  766. else if (i == imax)
  767. Graphics_line (my graphics.get(), t, dur, my endWindow, RealTier_getValueAtTime (duration, my endWindow));
  768. else {
  769. RealPoint pointRight = duration -> points.at [i + 1];
  770. Graphics_line (my graphics.get(), t, dur, pointRight -> number, pointRight -> value);
  771. }
  772. }
  773. for (i = imin; i <= imax; i ++) {
  774. RealPoint point = duration -> points.at [i];
  775. double t = point -> number, dur = point -> value;
  776. if (i >= ifirstSelected && i <= ilastSelected)
  777. Graphics_setColour (my graphics.get(), Graphics_RED);
  778. else
  779. Graphics_setColour (my graphics.get(), Graphics_GREEN);
  780. Graphics_fillCircle_mm (my graphics.get(), t, dur, 3.0);
  781. }
  782. }
  783. Graphics_setLineWidth (my graphics.get(), 1.0);
  784. Graphics_setColour (my graphics.get(), Graphics_BLACK);
  785. Graphics_resetViewport (my graphics.get(), viewport);
  786. }
  787. void structManipulationEditor :: v_draw () {
  788. double ysoundmin, ysoundmax;
  789. double ypitchmin, ypitchmax, ydurationmin, ydurationmax;
  790. int hasSoundArea = getSoundArea (this, & ysoundmin, & ysoundmax);
  791. int hasPitchArea = getPitchArea (this, & ypitchmin, & ypitchmax);
  792. int hasDurationArea = getDurationArea (this, & ydurationmin, & ydurationmax);
  793. if (hasSoundArea) drawSoundArea (this, ysoundmin, ysoundmax);
  794. if (hasPitchArea) drawPitchArea (this, ypitchmin, ypitchmax);
  795. if (hasDurationArea) drawDurationArea (this, ydurationmin, ydurationmax);
  796. Graphics_setWindow (our graphics.get(), 0.0, 1.0, 0.0, 1.0);
  797. Graphics_setGrey (our graphics.get(), 0.85);
  798. Graphics_fillRectangle (our graphics.get(), -0.001, 1.001, ypitchmax, ysoundmin);
  799. Graphics_setGrey (our graphics.get(), 0.00);
  800. Graphics_line (our graphics.get(), 0.0, ysoundmin, 1.0, ysoundmin);
  801. Graphics_line (our graphics.get(), 0.0, ypitchmax, 1.0, ypitchmax);
  802. if (hasDurationArea) {
  803. Graphics_setGrey (our graphics.get(), 0.85);
  804. Graphics_fillRectangle (our graphics.get(), -0.001, 1.001, ydurationmax, ypitchmin);
  805. Graphics_setGrey (our graphics.get(), 0.00);
  806. Graphics_line (our graphics.get(), 0, ypitchmin, 1, ypitchmin);
  807. Graphics_line (our graphics.get(), 0, ydurationmax, 1, ydurationmax);
  808. }
  809. updateMenus (this);
  810. }
  811. static void drawWhileDragging (ManipulationEditor me, double xWC, double yWC, integer first, integer last, double dt, double df) {
  812. Manipulation ana = (Manipulation) my data;
  813. PitchTier pitch = ana -> pitch.get();
  814. (void) xWC;
  815. (void) yWC;
  816. /*
  817. * Draw all selected pitch points as magenta empty circles, if inside the window.
  818. */
  819. for (integer i = first; i <= last; i ++) {
  820. RealPoint point = pitch -> points.at [i];
  821. double t = point -> number + dt, f = YLIN (point -> value) + df;
  822. if (t >= my startWindow && t <= my endWindow)
  823. Graphics_circle_mm (my graphics.get(), t,
  824. f < my pitchTier.minPeriodic ? my pitchTier.minPeriodic : f > my p_pitch_maximum ? my p_pitch_maximum : f, 3.0);
  825. }
  826. if (last == first) {
  827. /*
  828. * Draw a crosshair with time and frequency.
  829. */
  830. RealPoint point = pitch -> points.at [first];
  831. double t = point -> number + dt, fWC = YLIN (point -> value) + df;
  832. Graphics_line (my graphics.get(), t, my p_pitch_minimum, t, my p_pitch_maximum - Graphics_dyMMtoWC (my graphics.get(), 4.0));
  833. Graphics_setTextAlignment (my graphics.get(), Graphics_CENTRE, Graphics_TOP);
  834. Graphics_text (my graphics.get(), t, my p_pitch_maximum, Melder_fixed (t, 6));
  835. Graphics_line (my graphics.get(), my startWindow, fWC, my endWindow, fWC);
  836. Graphics_setTextAlignment (my graphics.get(), Graphics_LEFT, Graphics_BOTTOM);
  837. Graphics_text (my graphics.get(), my startWindow, fWC, Melder_fixed (fWC, 5));
  838. }
  839. }
  840. static bool clickPitch (ManipulationEditor me, double xWC, double yWC, bool shiftKeyPressed) {
  841. Manipulation ana = (Manipulation) my data;
  842. PitchTier pitch = ana -> pitch.get();
  843. integer inearestPoint, ifirstSelected, ilastSelected, i;
  844. RealPoint nearestPoint;
  845. double dt = 0, df = 0;
  846. int draggingSelection, dragHorizontal, dragVertical;
  847. my pitchTier.cursor = my p_pitch_minimum + yWC * (my p_pitch_maximum - my p_pitch_minimum);
  848. if (! pitch) {
  849. Graphics_resetViewport (my graphics.get(), my inset);
  850. return my ManipulationEditor_Parent :: v_click (xWC, yWC, shiftKeyPressed);
  851. }
  852. Graphics_setWindow (my graphics.get(), my startWindow, my endWindow, my p_pitch_minimum, my p_pitch_maximum);
  853. yWC = my pitchTier.cursor;
  854. /*
  855. * Clicked on a pitch point?
  856. */
  857. inearestPoint = AnyTier_timeToNearestIndex (pitch->asAnyTier(), xWC);
  858. if (inearestPoint == 0) {
  859. Graphics_resetViewport (my graphics.get(), my inset);
  860. return my ManipulationEditor_Parent :: v_click (xWC, yWC, shiftKeyPressed);
  861. }
  862. nearestPoint = pitch -> points.at [inearestPoint];
  863. if (Graphics_distanceWCtoMM (my graphics.get(), xWC, yWC, nearestPoint -> number, YLIN (nearestPoint -> value)) > 1.5) {
  864. Graphics_resetViewport (my graphics.get(), my inset);
  865. return my ManipulationEditor_Parent :: v_click (xWC, yWC, shiftKeyPressed);
  866. }
  867. /*
  868. * Clicked on a selected pitch point?
  869. */
  870. draggingSelection = shiftKeyPressed &&
  871. nearestPoint -> number > my startSelection && nearestPoint -> number < my endSelection;
  872. if (draggingSelection) {
  873. ifirstSelected = AnyTier_timeToHighIndex (pitch->asAnyTier(), my startSelection);
  874. ilastSelected = AnyTier_timeToLowIndex (pitch->asAnyTier(), my endSelection);
  875. Editor_save (me, U"Drag pitch points");
  876. } else {
  877. ifirstSelected = ilastSelected = inearestPoint;
  878. Editor_save (me, U"Drag pitch point");
  879. }
  880. /*
  881. * Drag.
  882. */
  883. /*
  884. * Draw at the old location once.
  885. * Since some systems do double buffering,
  886. * the undrawing at the old position and redrawing at the new have to be bracketed by Graphics_mouseStillDown ().
  887. */
  888. Graphics_xorOn (my graphics.get(), Graphics_MAROON);
  889. drawWhileDragging (me, xWC, yWC, ifirstSelected, ilastSelected, dt, df);
  890. dragHorizontal = my p_pitch_draggingStrategy != kManipulationEditor_draggingStrategy::VERTICAL &&
  891. (! shiftKeyPressed || my p_pitch_draggingStrategy != kManipulationEditor_draggingStrategy::HYBRID);
  892. dragVertical = my p_pitch_draggingStrategy != kManipulationEditor_draggingStrategy::HORIZONTAL;
  893. while (Graphics_mouseStillDown (my graphics.get())) {
  894. double xWC_new, yWC_new;
  895. Graphics_getMouseLocation (my graphics.get(), & xWC_new, & yWC_new);
  896. if (xWC_new != xWC || yWC_new != yWC) {
  897. drawWhileDragging (me, xWC, yWC, ifirstSelected, ilastSelected, dt, df);
  898. if (dragHorizontal)
  899. dt += xWC_new - xWC;
  900. if (dragVertical)
  901. df += yWC_new - yWC;
  902. xWC = xWC_new;
  903. yWC = yWC_new;
  904. drawWhileDragging (me, xWC, yWC, ifirstSelected, ilastSelected, dt, df);
  905. }
  906. }
  907. Graphics_xorOff (my graphics.get());
  908. /*
  909. * Dragged inside window?
  910. */
  911. if (xWC < my startWindow || xWC > my endWindow)
  912. return 1;
  913. /*
  914. * Points not dragged past neighbours?
  915. */
  916. {
  917. double newTime = pitch -> points.at [ifirstSelected] -> number + dt;
  918. if (newTime < my tmin) return 1; // outside domain
  919. if (ifirstSelected > 1 && newTime <= pitch -> points.at [ifirstSelected - 1] -> number)
  920. return 1; /* Past left neighbour. */
  921. newTime = pitch -> points.at [ilastSelected] -> number + dt;
  922. if (newTime > my tmax) return 1; // outside domain
  923. if (ilastSelected < pitch -> points.size && newTime >= pitch -> points.at [ilastSelected + 1] -> number)
  924. return FunctionEditor_UPDATE_NEEDED; // past right neighbour
  925. }
  926. /*
  927. * Drop.
  928. */
  929. for (i = ifirstSelected; i <= ilastSelected; i ++) {
  930. RealPoint point = pitch -> points.at [i];
  931. point -> number += dt;
  932. point -> value = YLININV (YLIN (point -> value) + df);
  933. if (point -> value < 50.0)
  934. point -> value = 50.0;
  935. if (point -> value > YLININV (my p_pitch_maximum))
  936. point -> value = YLININV (my p_pitch_maximum);
  937. }
  938. /*
  939. * Make sure that the same pitch points are still selected (a problem with Undo...).
  940. */
  941. if (draggingSelection) {
  942. my startSelection += dt;
  943. my endSelection += dt;
  944. }
  945. if (my startSelection == my endSelection) {
  946. RealPoint point = pitch -> points.at [ifirstSelected];
  947. my startSelection = my endSelection = point -> number;
  948. my pitchTier.cursor = YLIN (point -> value);
  949. }
  950. Editor_broadcastDataChanged (me);
  951. return FunctionEditor_UPDATE_NEEDED;
  952. }
  953. static void drawDurationWhileDragging (ManipulationEditor me, double /* xWC */, double /* yWC */, integer first, integer last, double dt, double df) {
  954. Manipulation ana = (Manipulation) my data;
  955. DurationTier duration = ana -> duration.get();
  956. /*
  957. * Draw all selected duration points as magenta empty circles, if inside the window.
  958. */
  959. for (integer i = first; i <= last; i ++) {
  960. RealPoint point = duration -> points.at [i];
  961. double t = point -> number + dt, dur = point -> value + df;
  962. if (t >= my startWindow && t <= my endWindow)
  963. Graphics_circle_mm (my graphics.get(), t, dur < my p_duration_minimum ? my p_duration_minimum :
  964. dur > my p_duration_maximum ? my p_duration_maximum : dur, 3.0);
  965. }
  966. if (last == first) {
  967. /*
  968. * Draw a crosshair with time and duration.
  969. */
  970. RealPoint point = duration -> points.at [first];
  971. double t = point -> number + dt, durWC = point -> value + df;
  972. Graphics_line (my graphics.get(), t, my p_duration_minimum, t, my p_duration_maximum - Graphics_dyMMtoWC (my graphics.get(), 4.0));
  973. Graphics_setTextAlignment (my graphics.get(), Graphics_CENTRE, Graphics_TOP);
  974. Graphics_text (my graphics.get(), t, my p_duration_maximum, Melder_fixed (t, 6));
  975. Graphics_line (my graphics.get(), my startWindow, durWC, my endWindow, durWC);
  976. Graphics_setTextAlignment (my graphics.get(), Graphics_LEFT, Graphics_BOTTOM);
  977. Graphics_text (my graphics.get(), my startWindow, durWC, Melder_fixed (durWC, 2));
  978. }
  979. }
  980. static bool clickDuration (ManipulationEditor me, double xWC, double yWC, int shiftKeyPressed) {
  981. Manipulation ana = (Manipulation) my data;
  982. DurationTier duration = ana -> duration.get();
  983. integer inearestPoint, ifirstSelected, ilastSelected;
  984. RealPoint nearestPoint;
  985. double dt = 0, df = 0;
  986. int draggingSelection;
  987. /*
  988. * Convert from FunctionEditor's [0, 1] coordinates to world coordinates.
  989. */
  990. yWC = my p_duration_minimum + yWC * (my p_duration_maximum - my p_duration_minimum);
  991. /*
  992. * Move horizontal hair to clicked position.
  993. */
  994. my duration.cursor = yWC;
  995. if (! duration) {
  996. Graphics_resetViewport (my graphics.get(), my inset);
  997. return my ManipulationEditor_Parent :: v_click (xWC, yWC, shiftKeyPressed);
  998. }
  999. Graphics_setWindow (my graphics.get(), my startWindow, my endWindow, my p_duration_minimum, my p_duration_maximum);
  1000. /*
  1001. * Clicked on a duration point?
  1002. */
  1003. inearestPoint = AnyTier_timeToNearestIndex (duration->asAnyTier(), xWC);
  1004. if (inearestPoint == 0) {
  1005. Graphics_resetViewport (my graphics.get(), my inset);
  1006. return my ManipulationEditor_Parent :: v_click (xWC, yWC, shiftKeyPressed);
  1007. }
  1008. nearestPoint = duration -> points.at [inearestPoint];
  1009. if (Graphics_distanceWCtoMM (my graphics.get(), xWC, yWC, nearestPoint -> number, nearestPoint -> value) > 1.5) {
  1010. Graphics_resetViewport (my graphics.get(), my inset);
  1011. return my ManipulationEditor_Parent :: v_click (xWC, yWC, shiftKeyPressed);
  1012. }
  1013. /*
  1014. * Clicked on a selected duration point?
  1015. */
  1016. draggingSelection = shiftKeyPressed &&
  1017. nearestPoint -> number > my startSelection && nearestPoint -> number < my endSelection;
  1018. if (draggingSelection) {
  1019. ifirstSelected = AnyTier_timeToHighIndex (duration->asAnyTier(), my startSelection);
  1020. ilastSelected = AnyTier_timeToLowIndex (duration->asAnyTier(), my endSelection);
  1021. Editor_save (me, U"Drag duration points");
  1022. } else {
  1023. ifirstSelected = ilastSelected = inearestPoint;
  1024. Editor_save (me, U"Drag duration point");
  1025. }
  1026. /*
  1027. * Drag.
  1028. */
  1029. Graphics_xorOn (my graphics.get(), Graphics_MAROON);
  1030. drawDurationWhileDragging (me, xWC, yWC, ifirstSelected, ilastSelected, dt, df);
  1031. while (Graphics_mouseStillDown (my graphics.get())) {
  1032. double xWC_new, yWC_new;
  1033. Graphics_getMouseLocation (my graphics.get(), & xWC_new, & yWC_new);
  1034. if (xWC_new != xWC || yWC_new != yWC) {
  1035. drawDurationWhileDragging (me, xWC, yWC, ifirstSelected, ilastSelected, dt, df);
  1036. dt += xWC_new - xWC, xWC = xWC_new;
  1037. df += yWC_new - yWC, yWC = yWC_new;
  1038. drawDurationWhileDragging (me, xWC_new, yWC_new, ifirstSelected, ilastSelected, dt, df);
  1039. }
  1040. }
  1041. Graphics_xorOff (my graphics.get());
  1042. /*
  1043. * Dragged inside window?
  1044. */
  1045. if (xWC < my startWindow || xWC > my endWindow) return 1;
  1046. /*
  1047. * Points not dragged past neighbours?
  1048. */
  1049. {
  1050. double newTime = duration -> points.at [ifirstSelected] -> number + dt;
  1051. if (newTime < my tmin) return 1; // outside domain
  1052. if (ifirstSelected > 1 && newTime <= duration -> points.at [ifirstSelected - 1] -> number)
  1053. return 1; /* Past left neighbour. */
  1054. newTime = duration -> points.at [ilastSelected] -> number + dt;
  1055. if (newTime > my tmax) return 1; // outside domain
  1056. if (ilastSelected < duration -> points.size && newTime >= duration -> points.at [ilastSelected + 1] -> number)
  1057. return 1; // past right neighbour
  1058. }
  1059. /*
  1060. * Drop.
  1061. */
  1062. for (integer i = ifirstSelected; i <= ilastSelected; i ++) {
  1063. RealPoint point = duration -> points.at [i];
  1064. point -> number += dt;
  1065. point -> value += df;
  1066. if (point -> value < my p_duration_minimum) point -> value = my p_duration_minimum;
  1067. if (point -> value > my p_duration_maximum) point -> value = my p_duration_maximum;
  1068. }
  1069. /*
  1070. * Make sure that the same duration points are still selected (a problem with Undo...).
  1071. */
  1072. if (draggingSelection) my startSelection += dt, my endSelection += dt;
  1073. if (my startSelection == my endSelection) {
  1074. RealPoint point = duration -> points.at [ifirstSelected];
  1075. my startSelection = my endSelection = point -> number;
  1076. my duration.cursor = point -> value;
  1077. }
  1078. Editor_broadcastDataChanged (me);
  1079. return FunctionEditor_UPDATE_NEEDED;
  1080. }
  1081. bool structManipulationEditor :: v_click (double xWC, double yWC, bool shiftKeyPressed) {
  1082. double ypitchmin, ypitchmax, ydurationmin, ydurationmax;
  1083. int hasPitchArea = getPitchArea (this, & ypitchmin, & ypitchmax);
  1084. int hasDurationArea = getDurationArea (this, & ydurationmin, & ydurationmax);
  1085. /*
  1086. * Dispatch click to clicked area.
  1087. */
  1088. if (hasPitchArea && yWC > ypitchmin && yWC < ypitchmax) { // clicked in pitch area?
  1089. inset = Graphics_insetViewport (our graphics.get(), 0.0, 1.0, ypitchmin, ypitchmax);
  1090. return clickPitch (this, xWC, (yWC - ypitchmin) / (ypitchmax - ypitchmin), shiftKeyPressed);
  1091. } else if (hasDurationArea && yWC > ydurationmin && yWC < ydurationmax) { // clicked in duration area?
  1092. inset = Graphics_insetViewport (our graphics.get(), 0.0, 1.0, ydurationmin, ydurationmax);
  1093. return clickDuration (this, xWC, (yWC - ydurationmin) / (ydurationmax - ydurationmin), shiftKeyPressed);
  1094. }
  1095. /*
  1096. * Perform the default action: move cursor or drag selection.
  1097. */
  1098. return our ManipulationEditor_Parent :: v_click (xWC, yWC, shiftKeyPressed);
  1099. }
  1100. void structManipulationEditor :: v_play (double a_tmin, double a_tmax) {
  1101. Manipulation ana = (Manipulation) our data;
  1102. if (our shiftKeyPressed) {
  1103. if (ana -> sound)
  1104. Sound_playPart (ana -> sound.get(), a_tmin, a_tmax, theFunctionEditor_playCallback, this);
  1105. } else {
  1106. Manipulation_playPart (ana, a_tmin, a_tmax, our synthesisMethod);
  1107. }
  1108. }
  1109. autoManipulationEditor ManipulationEditor_create (conststring32 title, Manipulation ana) {
  1110. try {
  1111. autoManipulationEditor me = Thing_new (ManipulationEditor);
  1112. FunctionEditor_init (me.get(), title, ana);
  1113. double maximumPitchValue = RealTier_getMaximumValue (ana -> pitch.get());
  1114. if (my p_pitch_units == kManipulationEditor_pitchUnits::HERTZ) {
  1115. my p_pitch_minimum = 25.0;
  1116. my pitchTier.minPeriodic = 50.0;
  1117. my p_pitch_maximum = maximumPitchValue;
  1118. my pitchTier.cursor = my p_pitch_maximum * 0.8;
  1119. my p_pitch_maximum *= 1.2;
  1120. } else {
  1121. my p_pitch_minimum = -24.0;
  1122. my pitchTier.minPeriodic = -12.0;
  1123. my p_pitch_maximum = ( isdefined (maximumPitchValue) ? NUMhertzToSemitones (maximumPitchValue) : undefined );
  1124. my pitchTier.cursor = my p_pitch_maximum - 4.0;
  1125. my p_pitch_maximum += 3.0;
  1126. }
  1127. if (isundef (my p_pitch_maximum) || my p_pitch_maximum < my pref_pitch_maximum ())
  1128. my p_pitch_maximum = my pref_pitch_maximum ();
  1129. double minimumDurationValue = ( ana -> duration ? RealTier_getMinimumValue (ana -> duration.get()) : undefined );
  1130. my p_duration_minimum = ( isdefined (minimumDurationValue) ? minimumDurationValue : 1.0 );
  1131. if (my pref_duration_minimum () > 1)
  1132. my pref_duration_minimum () = Melder_atof (my default_duration_minimum ());
  1133. if (my p_duration_minimum > my pref_duration_minimum ())
  1134. my p_duration_minimum = my pref_duration_minimum ();
  1135. double maximumDurationValue = ( ana -> duration ? RealTier_getMaximumValue (ana -> duration.get()) : undefined );
  1136. my p_duration_maximum = ( isdefined (maximumDurationValue) ? maximumDurationValue : 1.0 );
  1137. if (my pref_duration_maximum () < 1)
  1138. my pref_duration_maximum () = Melder_atof (my default_duration_maximum ());
  1139. if (my pref_duration_maximum () <= my pref_duration_minimum ()) {
  1140. my pref_duration_minimum () = Melder_atof (my default_duration_minimum ());
  1141. my pref_duration_maximum () = Melder_atof (my default_duration_maximum ());
  1142. }
  1143. if (my p_duration_maximum < my pref_duration_maximum ())
  1144. my p_duration_maximum = my pref_duration_maximum ();
  1145. my duration.cursor = 1.0;
  1146. my synthesisMethod = prefs_synthesisMethod;
  1147. if (ana -> sound)
  1148. Matrix_getWindowExtrema (ana -> sound.get(), 0, 0, 0, 0, & my soundmin, & my soundmax);
  1149. if (my soundmin == my soundmax) my soundmin = -1.0, my soundmax = +1.0;
  1150. updateMenus (me.get());
  1151. return me;
  1152. } catch (MelderError) {
  1153. Melder_throw (U"Manipulation window not created.");
  1154. }
  1155. }
  1156. /* End of file ManipulationEditor.cpp */