SoundEditor.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. /* SoundEditor.cpp
  2. *
  3. * Copyright (C) 1992-2018 Paul Boersma, 2007 Erez Volk (FLAC support)
  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 "Sound_and_MixingMatrix.h"
  19. #include "SoundEditor.h"
  20. #include "Sound_and_Spectrogram.h"
  21. #include "Pitch.h"
  22. #include "Preferences.h"
  23. #include "EditorM.h"
  24. Thing_implement (SoundEditor, TimeSoundAnalysisEditor, 0);
  25. /********** METHODS **********/
  26. void structSoundEditor :: v_dataChanged () {
  27. Sound sound = (Sound) data;
  28. Melder_assert (sound);
  29. if (sound -> classInfo == classSound) // LongSound editors can get spurious v_dataChanged messages (e.g. in a TextGrid editor)
  30. Matrix_getWindowExtrema (sound, 1, sound -> nx, 1, sound -> ny, & d_sound.minimum, & d_sound.maximum); // BUG unreadable
  31. v_reset_analysis ();
  32. SoundEditor_Parent :: v_dataChanged ();
  33. }
  34. /***** EDIT MENU *****/
  35. static void menu_cb_Copy (SoundEditor me, EDITOR_ARGS_DIRECT) {
  36. try {
  37. Sound_clipboard = my d_longSound.data ? LongSound_extractPart ((LongSound) my data, my startSelection, my endSelection, false) :
  38. Sound_extractPart ((Sound) my data, my startSelection, my endSelection, kSound_windowShape::RECTANGULAR, 1.0, false);
  39. } catch (MelderError) {
  40. Melder_throw (U"Sound selection not copied to clipboard.");
  41. }
  42. }
  43. static void menu_cb_Cut (SoundEditor me, EDITOR_ARGS_DIRECT) {
  44. try {
  45. Sound sound = (Sound) my data;
  46. integer first, last, selectionNumberOfSamples = Sampled_getWindowSamples (sound,
  47. my startSelection, my endSelection, & first, & last);
  48. integer oldNumberOfSamples = sound -> nx;
  49. integer newNumberOfSamples = oldNumberOfSamples - selectionNumberOfSamples;
  50. if (newNumberOfSamples < 1)
  51. Melder_throw (U"You cannot cut all of the signal away,\n"
  52. U"because you cannot create a Sound with 0 samples.\n"
  53. U"You could consider using Copy instead.");
  54. if (selectionNumberOfSamples) {
  55. /*
  56. Create without change.
  57. */
  58. autoSound publish = Sound_create (sound -> ny, 0.0, selectionNumberOfSamples * sound -> dx,
  59. selectionNumberOfSamples, sound -> dx, 0.5 * sound -> dx);
  60. for (integer channel = 1; channel <= sound -> ny; channel ++) {
  61. integer j = 0;
  62. for (integer i = first; i <= last; i ++)
  63. publish -> z [channel] [++ j] = sound -> z [channel] [i];
  64. }
  65. autoMAT newData = MATraw (sound -> ny, newNumberOfSamples);
  66. for (integer channel = 1; channel <= sound -> ny; channel ++) {
  67. integer j = 0;
  68. for (integer i = 1; i < first; i ++)
  69. newData [channel] [++ j] = sound -> z [channel] [i];
  70. for (integer i = last + 1; i <= oldNumberOfSamples; i ++)
  71. newData [channel] [++ j] = sound -> z [channel] [i];
  72. Melder_assert (j == newData.ncol);
  73. }
  74. Editor_save (me, U"Cut");
  75. /*
  76. Change without error.
  77. */
  78. sound -> xmin = 0.0;
  79. sound -> xmax = newNumberOfSamples * sound -> dx;
  80. sound -> nx = newNumberOfSamples;
  81. sound -> x1 = 0.5 * sound -> dx;
  82. sound -> z = newData.move();
  83. Sound_clipboard = publish.move();
  84. /* Start updating the markers of the FunctionEditor, respecting the invariants. */
  85. my tmin = sound -> xmin;
  86. my tmax = sound -> xmax;
  87. /* Collapse the selection, */
  88. /* so that the Cut operation can immediately be undone by a Paste. */
  89. /* The exact position will be half-way in between two samples. */
  90. my startSelection = my endSelection = sound -> xmin + (first - 1) * sound -> dx;
  91. /* Update the window. */
  92. {
  93. double t1 = (first - 1) * sound -> dx;
  94. double t2 = last * sound -> dx;
  95. double windowLength = my endWindow - my startWindow; // > 0
  96. if (t1 > my startWindow)
  97. if (t2 < my endWindow)
  98. my startWindow -= 0.5 * (t2 - t1);
  99. else
  100. (void) 0;
  101. else if (t2 < my endWindow)
  102. my startWindow -= t2 - t1;
  103. else /* Cut overlaps entire window: centre. */
  104. my startWindow = my startSelection - 0.5 * windowLength;
  105. my endWindow = my startWindow + windowLength; // first try
  106. if (my endWindow > my tmax) {
  107. my startWindow -= my endWindow - my tmax; // second try
  108. if (my startWindow < my tmin)
  109. my startWindow = my tmin; // third try
  110. my endWindow = my tmax; // second try
  111. } else if (my startWindow < my tmin) {
  112. my endWindow -= my startWindow - my tmin; // second try
  113. if (my endWindow > my tmax)
  114. my endWindow = my tmax; // third try
  115. my startWindow = my tmin; // second try
  116. }
  117. }
  118. /* Force FunctionEditor to show changes. */
  119. Matrix_getWindowExtrema (sound, 1, sound -> nx, 1, sound -> ny, & my d_sound.minimum, & my d_sound.maximum);
  120. my v_reset_analysis ();
  121. FunctionEditor_ungroup (me);
  122. FunctionEditor_marksChanged (me, false);
  123. Editor_broadcastDataChanged (me);
  124. } else {
  125. Melder_warning (U"No samples selected.");
  126. }
  127. } catch (MelderError) {
  128. Melder_throw (U"Sound selection not cut to clipboard.");
  129. }
  130. }
  131. static void menu_cb_Paste (SoundEditor me, EDITOR_ARGS_DIRECT) {
  132. Sound sound = (Sound) my data;
  133. integer leftSample = Sampled_xToLowIndex (sound, my endSelection);
  134. integer oldNumberOfSamples = sound -> nx, newNumberOfSamples;
  135. if (! Sound_clipboard) {
  136. Melder_warning (U"Clipboard is empty; nothing pasted.");
  137. return;
  138. }
  139. if (Sound_clipboard -> ny != sound -> ny)
  140. Melder_throw (U"Cannot paste, because\n"
  141. U"the number of channels of the clipboard is not equal to\n"
  142. U"the number of channels of the edited sound.");
  143. if (Sound_clipboard -> dx != sound -> dx)
  144. Melder_throw (U"Cannot paste, because\n"
  145. U"the sampling frequency of the clipboard is not equal to\n"
  146. U"the sampling frequency of the edited sound.");
  147. if (leftSample < 0) leftSample = 0;
  148. if (leftSample > oldNumberOfSamples) leftSample = oldNumberOfSamples;
  149. newNumberOfSamples = oldNumberOfSamples + Sound_clipboard -> nx;
  150. /*
  151. Check without change.
  152. */
  153. autoMAT newData = MATraw (sound -> ny, newNumberOfSamples);
  154. for (integer channel = 1; channel <= sound -> ny; channel ++) {
  155. integer j = 0;
  156. for (integer i = 1; i <= leftSample; i ++)
  157. newData [channel] [++ j] = sound -> z [channel] [i];
  158. for (integer i = 1; i <= Sound_clipboard -> nx; i ++)
  159. newData [channel] [++ j] = Sound_clipboard -> z [channel] [i];
  160. for (integer i = leftSample + 1; i <= oldNumberOfSamples; i ++)
  161. newData [channel] [++ j] = sound -> z [channel] [i];
  162. Melder_assert (j == newData.ncol);
  163. }
  164. Editor_save (me, U"Paste");
  165. /*
  166. Change without error.
  167. */
  168. sound -> xmin = 0.0;
  169. sound -> xmax = newNumberOfSamples * sound -> dx;
  170. sound -> nx = newNumberOfSamples;
  171. sound -> x1 = 0.5 * sound -> dx;
  172. sound -> z = newData.move();
  173. /* Start updating the markers of the FunctionEditor, respecting the invariants. */
  174. my tmin = sound -> xmin;
  175. my tmax = sound -> xmax;
  176. my startSelection = leftSample * sound -> dx;
  177. my endSelection = (leftSample + Sound_clipboard -> nx) * sound -> dx;
  178. /* Force FunctionEditor to show changes. */
  179. Matrix_getWindowExtrema (sound, 1, sound -> nx, 1, sound -> ny, & my d_sound.minimum, & my d_sound.maximum);
  180. my v_reset_analysis ();
  181. FunctionEditor_ungroup (me);
  182. FunctionEditor_marksChanged (me, false);
  183. Editor_broadcastDataChanged (me);
  184. }
  185. static void menu_cb_SetSelectionToZero (SoundEditor me, EDITOR_ARGS_DIRECT) {
  186. Sound sound = (Sound) my data;
  187. integer first, last;
  188. Sampled_getWindowSamples (sound, my startSelection, my endSelection, & first, & last);
  189. Editor_save (me, U"Set to zero");
  190. for (integer channel = 1; channel <= sound -> ny; channel ++) {
  191. for (integer i = first; i <= last; i ++) {
  192. sound -> z [channel] [i] = 0.0;
  193. }
  194. }
  195. my v_reset_analysis ();
  196. FunctionEditor_redraw (me);
  197. Editor_broadcastDataChanged (me);
  198. }
  199. static void menu_cb_ReverseSelection (SoundEditor me, EDITOR_ARGS_DIRECT) {
  200. Editor_save (me, U"Reverse selection");
  201. Sound_reverse ((Sound) my data, my startSelection, my endSelection);
  202. my v_reset_analysis ();
  203. FunctionEditor_redraw (me);
  204. Editor_broadcastDataChanged (me);
  205. }
  206. /***** SELECT MENU *****/
  207. static void menu_cb_MoveCursorToZero (SoundEditor me, EDITOR_ARGS_DIRECT) {
  208. double zero = Sound_getNearestZeroCrossing ((Sound) my data, 0.5 * (my startSelection + my endSelection), 1); // STEREO BUG
  209. if (isdefined (zero)) {
  210. my startSelection = my endSelection = zero;
  211. FunctionEditor_marksChanged (me, true);
  212. }
  213. }
  214. static void menu_cb_MoveBtoZero (SoundEditor me, EDITOR_ARGS_DIRECT) {
  215. double zero = Sound_getNearestZeroCrossing ((Sound) my data, my startSelection, 1); // STEREO BUG
  216. if (isdefined (zero)) {
  217. my startSelection = zero;
  218. if (my startSelection > my endSelection) {
  219. double dummy = my startSelection;
  220. my startSelection = my endSelection;
  221. my endSelection = dummy;
  222. }
  223. FunctionEditor_marksChanged (me, true);
  224. }
  225. }
  226. static void menu_cb_MoveEtoZero (SoundEditor me, EDITOR_ARGS_DIRECT) {
  227. double zero = Sound_getNearestZeroCrossing ((Sound) my data, my endSelection, 1); // STEREO BUG
  228. if (isdefined (zero)) {
  229. my endSelection = zero;
  230. if (my startSelection > my endSelection) {
  231. double dummy = my startSelection;
  232. my startSelection = my endSelection;
  233. my endSelection = dummy;
  234. }
  235. FunctionEditor_marksChanged (me, true);
  236. }
  237. }
  238. /***** HELP MENU *****/
  239. static void menu_cb_SoundEditorHelp (SoundEditor, EDITOR_ARGS_DIRECT) { Melder_help (U"SoundEditor"); }
  240. static void menu_cb_LongSoundEditorHelp (SoundEditor, EDITOR_ARGS_DIRECT) { Melder_help (U"LongSoundEditor"); }
  241. void structSoundEditor :: v_createMenus () {
  242. SoundEditor_Parent :: v_createMenus ();
  243. Melder_assert (data);
  244. Melder_assert (d_sound.data || d_longSound.data);
  245. Editor_addCommand (this, U"Edit", U"-- cut copy paste --", 0, nullptr);
  246. if (d_sound.data) cutButton = Editor_addCommand (this, U"Edit", U"Cut", 'X', menu_cb_Cut);
  247. copyButton = Editor_addCommand (this, U"Edit", U"Copy selection to Sound clipboard", 'C', menu_cb_Copy);
  248. if (d_sound.data) pasteButton = Editor_addCommand (this, U"Edit", U"Paste after selection", 'V', menu_cb_Paste);
  249. if (d_sound.data) {
  250. Editor_addCommand (this, U"Edit", U"-- zero --", 0, nullptr);
  251. zeroButton = Editor_addCommand (this, U"Edit", U"Set selection to zero", 0, menu_cb_SetSelectionToZero);
  252. reverseButton = Editor_addCommand (this, U"Edit", U"Reverse selection", 'R', menu_cb_ReverseSelection);
  253. }
  254. if (d_sound.data) {
  255. Editor_addCommand (this, U"Select", U"-- move to zero --", 0, 0);
  256. Editor_addCommand (this, U"Select", U"Move start of selection to nearest zero crossing", ',', menu_cb_MoveBtoZero);
  257. Editor_addCommand (this, U"Select", U"Move begin of selection to nearest zero crossing", Editor_HIDDEN, menu_cb_MoveBtoZero);
  258. Editor_addCommand (this, U"Select", U"Move cursor to nearest zero crossing", '0', menu_cb_MoveCursorToZero);
  259. Editor_addCommand (this, U"Select", U"Move end of selection to nearest zero crossing", '.', menu_cb_MoveEtoZero);
  260. }
  261. v_createMenus_analysis ();
  262. }
  263. void structSoundEditor :: v_createHelpMenuItems (EditorMenu menu) {
  264. SoundEditor_Parent :: v_createHelpMenuItems (menu);
  265. EditorMenu_addCommand (menu, U"SoundEditor help", '?', menu_cb_SoundEditorHelp);
  266. EditorMenu_addCommand (menu, U"LongSoundEditor help", 0, menu_cb_LongSoundEditorHelp);
  267. }
  268. /********** UPDATE **********/
  269. void structSoundEditor :: v_prepareDraw () {
  270. if (d_longSound.data) {
  271. try {
  272. LongSound_haveWindow (our d_longSound.data, our startWindow, our endWindow);
  273. } catch (MelderError) {
  274. Melder_clearError ();
  275. }
  276. }
  277. }
  278. void structSoundEditor :: v_draw () {
  279. Sampled data = (Sampled) our data;
  280. Graphics_Viewport viewport;
  281. bool showAnalysis = our p_spectrogram_show || our p_pitch_show || our p_intensity_show || our p_formant_show;
  282. Melder_assert (data);
  283. Melder_assert (our d_sound.data || our d_longSound.data);
  284. /*
  285. * We check beforehand whether the window fits the LongSound buffer.
  286. */
  287. if (our d_longSound.data && our endWindow - our startWindow > our d_longSound.data -> bufferLength) {
  288. Graphics_setColour (our graphics.get(), Graphics_WHITE);
  289. Graphics_setWindow (our graphics.get(), 0.0, 1.0, 0.0, 1.0);
  290. Graphics_fillRectangle (our graphics.get(), 0.0, 1.0, 0.0, 1.0);
  291. Graphics_setColour (our graphics.get(), Graphics_BLACK);
  292. Graphics_setTextAlignment (our graphics.get(), Graphics_CENTRE, Graphics_BOTTOM);
  293. Graphics_text (our graphics.get(), 0.5, 0.5, U"(window longer than ", Melder_float (Melder_single (our d_longSound.data -> bufferLength)), U" seconds)");
  294. Graphics_setTextAlignment (our graphics.get(), Graphics_CENTRE, Graphics_TOP);
  295. Graphics_text (our graphics.get(), 0.5, 0.5, U"(zoom in to see the samples)");
  296. return;
  297. }
  298. /* Draw sound. */
  299. if (showAnalysis)
  300. viewport = Graphics_insetViewport (our graphics.get(), 0.0, 1.0, 0.5, 1.0);
  301. Graphics_setColour (our graphics.get(), Graphics_WHITE);
  302. Graphics_setWindow (our graphics.get(), 0.0, 1.0, 0.0, 1.0);
  303. Graphics_fillRectangle (our graphics.get(), 0.0, 1.0, 0.0, 1.0);
  304. TimeSoundEditor_drawSound (this, our d_sound.minimum, our d_sound.maximum);
  305. //Graphics_flushWs (our graphics.get());
  306. if (showAnalysis)
  307. Graphics_resetViewport (our graphics.get(), viewport);
  308. /* Draw analyses. */
  309. if (showAnalysis) {
  310. /* Draw spectrogram, pitch, formants. */
  311. viewport = Graphics_insetViewport (our graphics.get(), 0.0, 1.0, 0.0, 0.5);
  312. v_draw_analysis ();
  313. //Graphics_flushWs (our graphics.get());
  314. Graphics_resetViewport (our graphics.get(), viewport);
  315. }
  316. /* Draw pulses. */
  317. if (p_pulses_show) {
  318. if (showAnalysis)
  319. viewport = Graphics_insetViewport (our graphics.get(), 0.0, 1.0, 0.5, 1.0);
  320. v_draw_analysis_pulses ();
  321. TimeSoundEditor_drawSound (this, our d_sound.minimum, our d_sound.maximum); // second time, partially across the pulses
  322. //Graphics_flushWs (our graphics.get());
  323. if (showAnalysis)
  324. Graphics_resetViewport (our graphics.get(), viewport);
  325. }
  326. /* Update buttons. */
  327. integer first, last;
  328. integer selectedSamples = Sampled_getWindowSamples (data, our startSelection, our endSelection, & first, & last);
  329. v_updateMenuItems_file ();
  330. if (our d_sound.data) {
  331. GuiThing_setSensitive (cutButton , selectedSamples != 0 && selectedSamples < our d_sound.data -> nx);
  332. GuiThing_setSensitive (copyButton , selectedSamples != 0);
  333. GuiThing_setSensitive (zeroButton , selectedSamples != 0);
  334. GuiThing_setSensitive (reverseButton , selectedSamples != 0);
  335. }
  336. }
  337. void structSoundEditor :: v_play (double a_tmin, double a_tmax) {
  338. integer numberOfChannels = our d_longSound.data ? our d_longSound.data -> numberOfChannels : our d_sound.data -> ny;
  339. integer numberOfMuteChannels = 0;
  340. bool *muteChannels = our d_sound. muteChannels;
  341. for (integer i = 1; i <= numberOfChannels; i ++) {
  342. if (muteChannels [i]) {
  343. numberOfMuteChannels ++;
  344. }
  345. }
  346. integer numberOfChannelsToPlay = numberOfChannels - numberOfMuteChannels;
  347. Melder_require (numberOfChannelsToPlay > 0, U"Please select at least one channel to play.");
  348. if (our d_longSound.data) {
  349. if (numberOfMuteChannels > 0) {
  350. autoSound part = LongSound_extractPart (our d_longSound.data, a_tmin, a_tmax, 1);
  351. autoMixingMatrix thee = MixingMatrix_create (numberOfChannelsToPlay, numberOfChannels);
  352. MixingMatrix_muteAndActivateChannels (thee.get(), muteChannels);
  353. Sound_MixingMatrix_playPart (part.get(), thee.get(), a_tmin, a_tmax, theFunctionEditor_playCallback, this);
  354. } else {
  355. LongSound_playPart (our d_longSound.data, a_tmin, a_tmax, theFunctionEditor_playCallback, this);
  356. }
  357. } else {
  358. if (numberOfMuteChannels > 0) {
  359. autoMixingMatrix thee = MixingMatrix_create (numberOfChannelsToPlay, numberOfChannels);
  360. MixingMatrix_muteAndActivateChannels (thee.get(), muteChannels);
  361. Sound_MixingMatrix_playPart (our d_sound.data, thee.get(), a_tmin, a_tmax, theFunctionEditor_playCallback, this);
  362. } else {
  363. Sound_playPart (our d_sound.data, a_tmin, a_tmax, theFunctionEditor_playCallback, this);
  364. }
  365. }
  366. }
  367. bool structSoundEditor :: v_click (double xWC, double yWC, bool shiftKeyPressed) {
  368. if ((our p_spectrogram_show || our p_formant_show) && yWC < 0.5 && xWC > our startWindow && xWC < our endWindow) {
  369. our d_spectrogram_cursor = our p_spectrogram_viewFrom +
  370. 2.0 * yWC * (our p_spectrogram_viewTo - our p_spectrogram_viewFrom);
  371. }
  372. return SoundEditor_Parent :: v_click (xWC, yWC, shiftKeyPressed); // drag & update
  373. }
  374. void structSoundEditor :: v_highlightSelection (double left, double right, double bottom, double top) {
  375. if (our p_spectrogram_show)
  376. Graphics_highlight (our graphics.get(), left, right, 0.5 * (bottom + top), top);
  377. else
  378. Graphics_highlight (our graphics.get(), left, right, bottom, top);
  379. }
  380. void structSoundEditor :: v_unhighlightSelection (double left, double right, double bottom, double top) {
  381. if (our p_spectrogram_show)
  382. Graphics_unhighlight (our graphics.get(), left, right, 0.5 * (bottom + top), top);
  383. else
  384. Graphics_unhighlight (our graphics.get(), left, right, bottom, top);
  385. }
  386. void SoundEditor_init (SoundEditor me, conststring32 title, Sampled data) {
  387. /*
  388. * my longSound.data or my sound.data have to be set before we call FunctionEditor_init,
  389. * because createMenus expects that one of them is not null.
  390. */
  391. TimeSoundAnalysisEditor_init (me, title, data, data, false);
  392. if (my d_longSound.data && my endWindow - my startWindow > 30.0) {
  393. my endWindow = my startWindow + 30.0;
  394. if (my startWindow == my tmin)
  395. my startSelection = my endSelection = 0.5 * (my startWindow + my endWindow);
  396. FunctionEditor_marksChanged (me, false);
  397. }
  398. }
  399. autoSoundEditor SoundEditor_create (conststring32 title, Sampled data) {
  400. Melder_assert (data);
  401. try {
  402. autoSoundEditor me = Thing_new (SoundEditor);
  403. SoundEditor_init (me.get(), title, data);
  404. return me;
  405. } catch (MelderError) {
  406. Melder_throw (U"Sound window not created.");
  407. }
  408. }
  409. /* End of file SoundEditor.cpp */