1 /* TimeSoundEditor.cpp
2 *
3 * Copyright (C) 1992-2021 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
19 #include "NUM2.h"
20 #include "TimeSoundEditor.h"
21 #include "EditorM.h"
22 #include "../kar/UnicodeData.h"
23
24 #include "enums_getText.h"
25 #include "TimeSoundEditor_enums.h"
26 #include "enums_getValue.h"
27 #include "TimeSoundEditor_enums.h"
28
29 Thing_implement (TimeSoundEditor, FunctionEditor, 0);
30
31 #include "prefs_define.h"
32 #include "TimeSoundEditor_prefs.h"
33 #include "prefs_install.h"
34 #include "TimeSoundEditor_prefs.h"
35 #include "prefs_copyToInstance.h"
36 #include "TimeSoundEditor_prefs.h"
37
38 /********** Thing methods **********/
39
v_destroy()40 void structTimeSoundEditor :: v_destroy () noexcept {
41 if (our d_ownSound)
42 forget (our d_sound.data);
43 TimeSoundEditor_Parent :: v_destroy ();
44 }
45
v_info()46 void structTimeSoundEditor :: v_info () {
47 TimeSoundEditor_Parent :: v_info ();
48 /* Sound flags: */
49 MelderInfo_writeLine (U"Sound scaling strategy: ", kTimeSoundEditor_scalingStrategy_getText (our p_sound_scalingStrategy));
50 }
51
52 enum {
53 TimeSoundEditor_PART_CURSOR = 1,
54 TimeSoundEditor_PART_SELECTION = 2
55 };
56
makeQueriable(TimeSoundEditor me,bool allowCursor,double * tmin,double * tmax)57 static int makeQueriable (TimeSoundEditor me, bool allowCursor, double *tmin, double *tmax) {
58 if (my startSelection == my endSelection) {
59 if (allowCursor) {
60 *tmin = *tmax = my startSelection;
61 return TimeSoundEditor_PART_CURSOR;
62 } else {
63 Melder_throw (U"Make a selection first.");
64 }
65 } else if (my startSelection < my startWindow || my endSelection > my endWindow) {
66 Melder_throw (U"Command ambiguous: a part of the selection (", my startSelection, U", ", my endSelection, U") "
67 U"is outside of the window (", my startWindow, U", ", my endWindow, U"). "
68 U"Either zoom or re-select.");
69 }
70 *tmin = my startSelection;
71 *tmax = my endSelection;
72 return TimeSoundEditor_PART_SELECTION;
73 }
74
75 /***** FILE MENU *****/
76
menu_cb_DrawVisibleSound(TimeSoundEditor me,EDITOR_ARGS_FORM)77 static void menu_cb_DrawVisibleSound (TimeSoundEditor me, EDITOR_ARGS_FORM) {
78 EDITOR_FORM (U"Draw visible sound", nullptr)
79 my v_form_pictureWindow (cmd);
80 LABEL (U"Sound:")
81 BOOLEAN (preserveTimes, U"Preserve times", my default_picture_preserveTimes ());
82 REAL (bottom, U"left Vertical range", my default_picture_bottom ())
83 REAL (top, U"right Vertical range", my default_picture_top ())
84 my v_form_pictureMargins (cmd);
85 my v_form_pictureSelection (cmd);
86 BOOLEAN (garnish, U"Garnish", my default_picture_garnish ());
87 EDITOR_OK
88 my v_ok_pictureWindow (cmd);
89 SET_BOOLEAN (preserveTimes, my pref_picture_preserveTimes ())
90 SET_REAL (bottom, my pref_picture_bottom ())
91 SET_REAL (top, my pref_picture_top ())
92 my v_ok_pictureMargins (cmd);
93 my v_ok_pictureSelection (cmd);
94 SET_BOOLEAN (garnish, my pref_picture_garnish ())
95 EDITOR_DO
96 my v_do_pictureWindow (cmd);
97 my pref_picture_preserveTimes () = preserveTimes;
98 my pref_picture_bottom () = bottom;
99 my pref_picture_top () = top;
100 my v_do_pictureMargins (cmd);
101 my v_do_pictureSelection (cmd);
102 my pref_picture_garnish () = garnish;
103 if (! my d_longSound.data && ! my d_sound.data)
104 Melder_throw (U"There is no sound to draw.");
105 autoSound publish = my d_longSound.data ?
106 LongSound_extractPart (my d_longSound.data, my startWindow, my endWindow, my pref_picture_preserveTimes ()) :
107 Sound_extractPart (my d_sound.data, my startWindow, my endWindow, kSound_windowShape::RECTANGULAR, 1.0, my pref_picture_preserveTimes ());
108 Editor_openPraatPicture (me);
109 Sound_draw (publish.get(), my pictureGraphics, 0.0, 0.0, my pref_picture_bottom (), my pref_picture_top (),
110 my pref_picture_garnish (), U"Curve");
111 FunctionEditor_garnish (me);
112 Editor_closePraatPicture (me);
113 EDITOR_END
114 }
115
menu_cb_DrawSelectedSound(TimeSoundEditor me,EDITOR_ARGS_FORM)116 static void menu_cb_DrawSelectedSound (TimeSoundEditor me, EDITOR_ARGS_FORM) {
117 EDITOR_FORM (U"Draw selected sound", nullptr)
118 my v_form_pictureWindow (cmd);
119 LABEL (U"Sound:")
120 BOOLEAN (preserveTimes, U"Preserve times", my default_picture_preserveTimes ());
121 REAL (bottom, U"left Vertical range", my default_picture_bottom ());
122 REAL (top, U"right Vertical range", my default_picture_top ());
123 my v_form_pictureMargins (cmd);
124 BOOLEAN (garnish, U"Garnish", my default_picture_garnish ());
125 EDITOR_OK
126 my v_ok_pictureWindow (cmd);
127 SET_BOOLEAN (preserveTimes, my pref_picture_preserveTimes ());
128 SET_REAL (bottom, my pref_picture_bottom ());
129 SET_REAL (top, my pref_picture_top ());
130 my v_ok_pictureMargins (cmd);
131 SET_BOOLEAN (garnish, my pref_picture_garnish ());
132 EDITOR_DO
133 my v_do_pictureWindow (cmd);
134 my pref_picture_preserveTimes () = preserveTimes;
135 my pref_picture_bottom () = bottom;
136 my pref_picture_top () = top;
137 my v_do_pictureMargins (cmd);
138 my pref_picture_garnish () = garnish;
139 if (! my d_longSound.data && ! my d_sound.data)
140 Melder_throw (U"There is no sound to draw.");
141 autoSound publish = my d_longSound.data ?
142 LongSound_extractPart (my d_longSound.data, my startSelection, my endSelection, my pref_picture_preserveTimes ()) :
143 Sound_extractPart (my d_sound.data, my startSelection, my endSelection,
144 kSound_windowShape::RECTANGULAR, 1.0, my pref_picture_preserveTimes ());
145 Editor_openPraatPicture (me);
146 Sound_draw (publish.get(), my pictureGraphics, 0.0, 0.0, my pref_picture_bottom (), my pref_picture_top (),
147 my pref_picture_garnish (), U"Curve");
148 Editor_closePraatPicture (me);
149 EDITOR_END
150 }
151
do_ExtractSelectedSound(TimeSoundEditor me,bool preserveTimes)152 static autoSound do_ExtractSelectedSound (TimeSoundEditor me, bool preserveTimes) {
153 if (my endSelection <= my startSelection)
154 Melder_throw (U"No selection.");
155 if (my d_longSound.data)
156 return LongSound_extractPart (my d_longSound.data, my startSelection, my endSelection, preserveTimes);
157 else if (my d_sound.data)
158 return Sound_extractPart (my d_sound.data, my startSelection, my endSelection, kSound_windowShape::RECTANGULAR, 1.0, preserveTimes);
159 Melder_fatal (U"No Sound or LongSound available.");
160 return autoSound(); // never reached
161 }
162
CONVERT_DATA_TO_ONE__ExtractSelectedSound_timeFromZero(TimeSoundEditor me,EDITOR_ARGS_DIRECT_WITH_OUTPUT)163 static void CONVERT_DATA_TO_ONE__ExtractSelectedSound_timeFromZero (TimeSoundEditor me, EDITOR_ARGS_DIRECT_WITH_OUTPUT) {
164 CONVERT_DATA_TO_ONE
165 autoSound result = do_ExtractSelectedSound (me, false);
166 CONVERT_DATA_TO_ONE_END (U"untitled")
167 }
168
CONVERT_DATA_TO_ONE__ExtractSelectedSound_preserveTimes(TimeSoundEditor me,EDITOR_ARGS_DIRECT_WITH_OUTPUT)169 static void CONVERT_DATA_TO_ONE__ExtractSelectedSound_preserveTimes (TimeSoundEditor me, EDITOR_ARGS_DIRECT_WITH_OUTPUT) {
170 CONVERT_DATA_TO_ONE
171 autoSound result = do_ExtractSelectedSound (me, true);
172 CONVERT_DATA_TO_ONE_END (U"untitled")
173 }
174
CONVERT_DATA_TO_ONE__ExtractSelectedSound_windowed(TimeSoundEditor me,EDITOR_ARGS_FORM)175 static void CONVERT_DATA_TO_ONE__ExtractSelectedSound_windowed (TimeSoundEditor me, EDITOR_ARGS_FORM) {
176 EDITOR_FORM (U"Extract selected sound (windowed)", nullptr)
177 WORD (name, U"Name", U"slice")
178 OPTIONMENU_ENUM (kSound_windowShape, windowShape, U"Window shape", my default_extract_windowShape ())
179 POSITIVE (relativeWidth, U"Relative width", my default_extract_relativeWidth ())
180 BOOLEAN (preserveTimes, U"Preserve times", my default_extract_preserveTimes ())
181 EDITOR_OK
182 SET_ENUM (windowShape, kSound_windowShape, my pref_extract_windowShape ())
183 SET_REAL (relativeWidth, my pref_extract_relativeWidth ())
184 SET_BOOLEAN (preserveTimes, my pref_extract_preserveTimes ())
185 EDITOR_DO
186 Sound sound = my d_sound.data;
187 Melder_assert (sound);
188 CONVERT_DATA_TO_ONE
189 my pref_extract_windowShape () = windowShape;
190 my pref_extract_relativeWidth () = relativeWidth;
191 my pref_extract_preserveTimes () = preserveTimes;
192 autoSound result = Sound_extractPart (sound, my startSelection, my endSelection, my pref_extract_windowShape (),
193 my pref_extract_relativeWidth (), my pref_extract_preserveTimes ());
194 CONVERT_DATA_TO_ONE_END (name)
195 EDITOR_END
196 }
197
CONVERT_DATA_TO_ONE__ExtractSelectedSoundForOverlap(TimeSoundEditor me,EDITOR_ARGS_FORM)198 static void CONVERT_DATA_TO_ONE__ExtractSelectedSoundForOverlap (TimeSoundEditor me, EDITOR_ARGS_FORM) {
199 EDITOR_FORM (U"Extract selected sound for overlap)", nullptr)
200 WORD (name, U"Name", U"slice")
201 POSITIVE (overlap, U"Overlap (s)", my default_extract_overlap ())
202 EDITOR_OK
203 SET_REAL (overlap, my pref_extract_overlap ())
204 EDITOR_DO
205 Sound sound = my d_sound.data;
206 Melder_assert (sound);
207 CONVERT_DATA_TO_ONE
208 my pref_extract_overlap () = overlap;
209 autoSound result = Sound_extractPartForOverlap (sound, my startSelection, my endSelection,
210 my pref_extract_overlap ());
211 CONVERT_DATA_TO_ONE_END (name)
212 EDITOR_END
213 }
214
do_write(TimeSoundEditor me,MelderFile file,int format,int numberOfBitsPerSamplePoint)215 static void do_write (TimeSoundEditor me, MelderFile file, int format, int numberOfBitsPerSamplePoint) {
216 if (my startSelection >= my endSelection)
217 Melder_throw (U"No samples selected.");
218 if (my d_longSound.data) {
219 LongSound_savePartAsAudioFile (my d_longSound.data, format, my startSelection, my endSelection, file, numberOfBitsPerSamplePoint);
220 } else if (my d_sound.data) {
221 Sound sound = my d_sound.data;
222 double margin = 0.0;
223 integer nmargin = Melder_ifloor (margin / sound -> dx);
224 integer first, last, numberOfSamples = Sampled_getWindowSamples (sound,
225 my startSelection, my endSelection, & first, & last) + nmargin * 2;
226 first -= nmargin;
227 last += nmargin;
228 if (numberOfSamples) {
229 autoSound save = Sound_create (sound -> ny, 0.0, numberOfSamples * sound -> dx, numberOfSamples, sound -> dx, 0.5 * sound -> dx);
230 integer offset = first - 1;
231 if (first < 1)
232 first = 1;
233 if (last > sound -> nx)
234 last = sound -> nx;
235 for (integer channel = 1; channel <= sound -> ny; channel ++) {
236 for (integer i = first; i <= last; i ++)
237 save -> z [channel] [i - offset] = sound -> z [channel] [i];
238 }
239 Sound_saveAsAudioFile (save.get(), file, format, numberOfBitsPerSamplePoint);
240 }
241 }
242 }
243
menu_cb_WriteWav(TimeSoundEditor me,EDITOR_ARGS_FORM)244 static void menu_cb_WriteWav (TimeSoundEditor me, EDITOR_ARGS_FORM) {
245 EDITOR_FORM_SAVE (U"Save selected sound as WAV file", nullptr)
246 Melder_sprint (defaultName,300, my d_longSound.data ? my d_longSound.data -> name.get() : my d_sound.data -> name.get(), U".wav");
247 EDITOR_DO_SAVE
248 do_write (me, file, Melder_WAV, 16);
249 EDITOR_END
250 }
251
menu_cb_SaveAs24BitWav(TimeSoundEditor me,EDITOR_ARGS_FORM)252 static void menu_cb_SaveAs24BitWav (TimeSoundEditor me, EDITOR_ARGS_FORM) {
253 EDITOR_FORM_SAVE (U"Save selected sound as 24-bit WAV file", nullptr)
254 Melder_assert (! my d_longSound.data && my d_sound.data);
255 Melder_sprint (defaultName,300, my d_sound.data -> name.get(), U".wav");
256 EDITOR_DO_SAVE
257 do_write (me, file, Melder_WAV, 24);
258 EDITOR_END
259 }
260
menu_cb_SaveAs32BitWav(TimeSoundEditor me,EDITOR_ARGS_FORM)261 static void menu_cb_SaveAs32BitWav (TimeSoundEditor me, EDITOR_ARGS_FORM) {
262 EDITOR_FORM_SAVE (U"Save selected sound as 32-bit WAV file", nullptr)
263 Melder_assert (! my d_longSound.data && my d_sound.data);
264 Melder_sprint (defaultName,300, my d_sound.data -> name.get(), U".wav");
265 EDITOR_DO_SAVE
266 do_write (me, file, Melder_WAV, 32);
267 EDITOR_END
268 }
269
menu_cb_WriteAiff(TimeSoundEditor me,EDITOR_ARGS_FORM)270 static void menu_cb_WriteAiff (TimeSoundEditor me, EDITOR_ARGS_FORM) {
271 EDITOR_FORM_SAVE (U"Save selected sound as AIFF file", nullptr)
272 Melder_sprint (defaultName,300, my d_longSound.data ? my d_longSound.data -> name.get() : my d_sound.data -> name.get(), U".aiff");
273 EDITOR_DO_SAVE
274 do_write (me, file, Melder_AIFF, 16);
275 EDITOR_END
276 }
277
menu_cb_WriteAifc(TimeSoundEditor me,EDITOR_ARGS_FORM)278 static void menu_cb_WriteAifc (TimeSoundEditor me, EDITOR_ARGS_FORM) {
279 EDITOR_FORM_SAVE (U"Save selected sound as AIFC file", nullptr)
280 Melder_sprint (defaultName,300, my d_longSound.data ? my d_longSound.data -> name.get() : my d_sound.data -> name.get(), U".aifc");
281 EDITOR_DO_SAVE
282 do_write (me, file, Melder_AIFC, 16);
283 EDITOR_END
284 }
285
menu_cb_WriteNextSun(TimeSoundEditor me,EDITOR_ARGS_FORM)286 static void menu_cb_WriteNextSun (TimeSoundEditor me, EDITOR_ARGS_FORM) {
287 EDITOR_FORM_SAVE (U"Save selected sound as NeXT/Sun file", nullptr)
288 Melder_sprint (defaultName,300, my d_longSound.data ? my d_longSound.data -> name.get() : my d_sound.data -> name.get(), U".au");
289 EDITOR_DO_SAVE
290 do_write (me, file, Melder_NEXT_SUN, 16);
291 EDITOR_END
292 }
293
menu_cb_WriteNist(TimeSoundEditor me,EDITOR_ARGS_FORM)294 static void menu_cb_WriteNist (TimeSoundEditor me, EDITOR_ARGS_FORM) {
295 EDITOR_FORM_SAVE (U"Save selected sound as NIST file", nullptr)
296 Melder_sprint (defaultName,300, my d_longSound.data ? my d_longSound.data -> name.get() : my d_sound.data -> name.get(), U".nist");
297 EDITOR_DO_SAVE
298 do_write (me, file, Melder_NIST, 16);
299 EDITOR_END
300 }
301
menu_cb_WriteFlac(TimeSoundEditor me,EDITOR_ARGS_FORM)302 static void menu_cb_WriteFlac (TimeSoundEditor me, EDITOR_ARGS_FORM) {
303 EDITOR_FORM_SAVE (U"Save selected sound as FLAC file", nullptr)
304 Melder_sprint (defaultName,300, my d_longSound.data ? my d_longSound.data -> name.get() : my d_sound.data -> name.get(), U".flac");
305 EDITOR_DO_SAVE
306 do_write (me, file, Melder_FLAC, 16);
307 EDITOR_END
308 }
309
v_createMenuItems_file_draw(EditorMenu menu)310 void structTimeSoundEditor :: v_createMenuItems_file_draw (EditorMenu menu) {
311 EditorMenu_addCommand (menu, U"Draw to picture window:", GuiMenu_INSENSITIVE, menu_cb_DrawVisibleSound /* dummy */);
312 if (our d_sound.data || our d_longSound.data) {
313 EditorMenu_addCommand (menu, U"Draw visible sound...", 0, menu_cb_DrawVisibleSound);
314 our drawButton = EditorMenu_addCommand (menu, U"Draw selected sound...", 0, menu_cb_DrawSelectedSound);
315 }
316 }
317
v_createMenuItems_file_extract(EditorMenu menu)318 void structTimeSoundEditor :: v_createMenuItems_file_extract (EditorMenu menu) {
319 EditorMenu_addCommand (menu, U"Extract to objects window:", GuiMenu_INSENSITIVE,
320 CONVERT_DATA_TO_ONE__ExtractSelectedSound_preserveTimes /* dummy */);
321 if (our d_sound.data || our d_longSound.data) {
322 our publishPreserveButton = EditorMenu_addCommand (menu, U"Extract selected sound (preserve times)", 0,
323 CONVERT_DATA_TO_ONE__ExtractSelectedSound_preserveTimes);
324 EditorMenu_addCommand (menu, U"Extract sound selection (preserve times)", Editor_HIDDEN,
325 CONVERT_DATA_TO_ONE__ExtractSelectedSound_preserveTimes);
326 EditorMenu_addCommand (menu, U"Extract selection (preserve times)", Editor_HIDDEN,
327 CONVERT_DATA_TO_ONE__ExtractSelectedSound_preserveTimes);
328 our publishButton = EditorMenu_addCommand (menu, U"Extract selected sound (time from 0)", 0,
329 CONVERT_DATA_TO_ONE__ExtractSelectedSound_timeFromZero);
330 EditorMenu_addCommand (menu, U"Extract sound selection (time from 0)", Editor_HIDDEN,
331 CONVERT_DATA_TO_ONE__ExtractSelectedSound_timeFromZero);
332 EditorMenu_addCommand (menu, U"Extract selection (time from 0)", Editor_HIDDEN,
333 CONVERT_DATA_TO_ONE__ExtractSelectedSound_timeFromZero);
334 EditorMenu_addCommand (menu, U"Extract selection", Editor_HIDDEN,
335 CONVERT_DATA_TO_ONE__ExtractSelectedSound_timeFromZero);
336 if (our d_sound.data) {
337 our publishWindowButton = EditorMenu_addCommand (menu, U"Extract selected sound (windowed)...", 0,
338 CONVERT_DATA_TO_ONE__ExtractSelectedSound_windowed);
339 EditorMenu_addCommand (menu, U"Extract windowed sound selection...", Editor_HIDDEN,
340 CONVERT_DATA_TO_ONE__ExtractSelectedSound_windowed);
341 EditorMenu_addCommand (menu, U"Extract windowed selection...", Editor_HIDDEN,
342 CONVERT_DATA_TO_ONE__ExtractSelectedSound_windowed);
343 our publishOverlapButton = EditorMenu_addCommand (menu, U"Extract selected sound for overlap...", 0,
344 CONVERT_DATA_TO_ONE__ExtractSelectedSoundForOverlap);
345 }
346 }
347 }
348
v_createMenuItems_file_write(EditorMenu menu)349 void structTimeSoundEditor :: v_createMenuItems_file_write (EditorMenu menu) {
350 EditorMenu_addCommand (menu, U"Save to disk:", GuiMenu_INSENSITIVE, menu_cb_WriteWav /* dummy */);
351 if (our d_sound.data || our d_longSound.data) {
352 our writeWavButton = EditorMenu_addCommand (menu, U"Save selected sound as WAV file...", 0, menu_cb_WriteWav);
353 EditorMenu_addCommand (menu, U"Write selected sound to WAV file...", Editor_HIDDEN, menu_cb_WriteWav);
354 EditorMenu_addCommand (menu, U"Write sound selection to WAV file...", Editor_HIDDEN, menu_cb_WriteWav);
355 EditorMenu_addCommand (menu, U"Write selection to WAV file...", Editor_HIDDEN, menu_cb_WriteWav);
356 if (our d_sound.data) {
357 our saveAs24BitWavButton = EditorMenu_addCommand (menu, U"Save selected sound as 24-bit WAV file...", 0, menu_cb_SaveAs24BitWav);
358 our saveAs32BitWavButton = EditorMenu_addCommand (menu, U"Save selected sound as 32-bit WAV file...", 0, menu_cb_SaveAs32BitWav);
359 }
360 our writeAiffButton = EditorMenu_addCommand (menu, U"Save selected sound as AIFF file...", 0, menu_cb_WriteAiff);
361 EditorMenu_addCommand (menu, U"Write selected sound to AIFF file...", Editor_HIDDEN, menu_cb_WriteAiff);
362 EditorMenu_addCommand (menu, U"Write sound selection to AIFF file...", Editor_HIDDEN, menu_cb_WriteAiff);
363 EditorMenu_addCommand (menu, U"Write selection to AIFF file...", Editor_HIDDEN, menu_cb_WriteAiff);
364 our writeAifcButton = EditorMenu_addCommand (menu, U"Save selected sound as AIFC file...", 0, menu_cb_WriteAifc);
365 EditorMenu_addCommand (menu, U"Write selected sound to AIFC file...", Editor_HIDDEN, menu_cb_WriteAifc);
366 EditorMenu_addCommand (menu, U"Write sound selection to AIFC file...", Editor_HIDDEN, menu_cb_WriteAifc);
367 EditorMenu_addCommand (menu, U"Write selection to AIFC file...", Editor_HIDDEN, menu_cb_WriteAifc);
368 our writeNextSunButton = EditorMenu_addCommand (menu, U"Save selected sound as NeXT/Sun file...", 0, menu_cb_WriteNextSun);
369 EditorMenu_addCommand (menu, U"Write selected sound to NeXT/Sun file...", Editor_HIDDEN, menu_cb_WriteNextSun);
370 EditorMenu_addCommand (menu, U"Write sound selection to NeXT/Sun file...", Editor_HIDDEN, menu_cb_WriteNextSun);
371 EditorMenu_addCommand (menu, U"Write selection to NeXT/Sun file...", Editor_HIDDEN, menu_cb_WriteNextSun);
372 our writeNistButton = EditorMenu_addCommand (menu, U"Save selected sound as NIST file...", 0, menu_cb_WriteNist);
373 EditorMenu_addCommand (menu, U"Write selected sound to NIST file...", Editor_HIDDEN, menu_cb_WriteNist);
374 EditorMenu_addCommand (menu, U"Write sound selection to NIST file...", Editor_HIDDEN, menu_cb_WriteNist);
375 EditorMenu_addCommand (menu, U"Write selection to NIST file...", Editor_HIDDEN, menu_cb_WriteNist);
376 our writeFlacButton = EditorMenu_addCommand (menu, U"Save selected sound as FLAC file...", 0, menu_cb_WriteFlac);
377 EditorMenu_addCommand (menu, U"Write selected sound to FLAC file...", Editor_HIDDEN, menu_cb_WriteFlac);
378 EditorMenu_addCommand (menu, U"Write sound selection to FLAC file...", Editor_HIDDEN, menu_cb_WriteFlac);
379 }
380 }
381
v_createMenuItems_file(EditorMenu menu)382 void structTimeSoundEditor :: v_createMenuItems_file (EditorMenu menu) {
383 our TimeSoundEditor_Parent :: v_createMenuItems_file (menu);
384 our v_createMenuItems_file_draw (menu);
385 EditorMenu_addCommand (menu, U"-- after file draw --", 0, nullptr);
386 our v_createMenuItems_file_extract (menu);
387 EditorMenu_addCommand (menu, U"-- after file extract --", 0, nullptr);
388 our v_createMenuItems_file_write (menu);
389 EditorMenu_addCommand (menu, U"-- after file write --", 0, nullptr);
390 }
391
392 /********** QUERY MENU **********/
393
INFO_DATA__SoundInfo(TimeSoundEditor me,EDITOR_ARGS_DIRECT_WITH_OUTPUT)394 static void INFO_DATA__SoundInfo (TimeSoundEditor me, EDITOR_ARGS_DIRECT_WITH_OUTPUT) {
395 INFO_DATA
396 Thing_info (my d_sound.data);
397 INFO_DATA_END
398 }
399
INFO_DATA__LongSoundInfo(TimeSoundEditor me,EDITOR_ARGS_DIRECT_WITH_OUTPUT)400 static void INFO_DATA__LongSoundInfo (TimeSoundEditor me, EDITOR_ARGS_DIRECT_WITH_OUTPUT) {
401 INFO_DATA
402 Thing_info (my d_longSound.data);
403 INFO_DATA_END
404 }
405
INFO_DATA__getAmplitudes(TimeSoundEditor me,EDITOR_ARGS_DIRECT_WITH_OUTPUT)406 static void INFO_DATA__getAmplitudes (TimeSoundEditor me, EDITOR_ARGS_DIRECT_WITH_OUTPUT) {
407 INFO_DATA
408 double tmin, tmax;
409 const int part = makeQueriable (me, true, & tmin, & tmax);
410 if (! my d_sound.data)
411 Melder_throw (U"No Sound object is visible (a LongSound cannot be queried).");
412 MelderInfo_open ();
413 if (part == TimeSoundEditor_PART_CURSOR)
414 for (integer ichan = 1; ichan <= my d_sound.data -> ny; ichan ++)
415 MelderInfo_writeLine (Vector_getValueAtX (my d_sound.data, 0.5 * (my startSelection + my endSelection), ichan, kVector_valueInterpolation :: SINC70),
416 U" (interpolated amplitude at CURSOR in channel ", ichan, U")");
417 else
418 for (integer ichan = 1; ichan <= my d_sound.data -> ny; ichan ++)
419 MelderInfo_writeLine (Sampled_getMean (my d_sound.data, my startSelection, my endSelection, ichan, 0, true),
420 U" (mean amplitude in SELECTION in channel ", ichan, U")");
421 MelderInfo_close ();
422 INFO_DATA_END
423 }
424
v_createMenuItems_query_info(EditorMenu menu)425 void structTimeSoundEditor :: v_createMenuItems_query_info (EditorMenu menu) {
426 TimeSoundEditor_Parent :: v_createMenuItems_query_info (menu);
427 if (our d_sound.data && our d_sound.data != data) {
428 EditorMenu_addCommand (menu, U"Sound info", 0, INFO_DATA__SoundInfo);
429 } else if (our d_longSound.data && our d_longSound.data != data) {
430 EditorMenu_addCommand (menu, U"LongSound info", 0, INFO_DATA__LongSoundInfo);
431 }
432 if (our d_sound.data) {
433 EditorMenu_addCommand (menu, U"-- sound query --", 0, nullptr);
434 EditorMenu_addCommand (menu, U"Get amplitude(s)", 0, INFO_DATA__getAmplitudes);
435 }
436 }
437
438 /********** VIEW MENU **********/
439
menu_cb_soundScaling(TimeSoundEditor me,EDITOR_ARGS_FORM)440 static void menu_cb_soundScaling (TimeSoundEditor me, EDITOR_ARGS_FORM) {
441 EDITOR_FORM (U"Sound scaling", nullptr)
442 OPTIONMENU_ENUM (kTimeSoundEditor_scalingStrategy, scalingStrategy,
443 U"Scaling strategy", my default_sound_scalingStrategy ())
444 LABEL (U"For \"fixed height\":")
445 POSITIVE (height, U"Height", my default_sound_scaling_height ())
446 LABEL (U"For \"fixed range\":")
447 REAL (minimum, U"Minimum", my default_sound_scaling_minimum ())
448 REAL (maximum, U"Maximum", my default_sound_scaling_maximum ())
449 EDITOR_OK
450 SET_ENUM (scalingStrategy, kTimeSoundEditor_scalingStrategy, my p_sound_scalingStrategy)
451 SET_REAL (height, my p_sound_scaling_height)
452 SET_REAL (minimum, my p_sound_scaling_minimum)
453 SET_REAL (maximum, my p_sound_scaling_maximum)
454 EDITOR_DO
455 my pref_sound_scalingStrategy () = my p_sound_scalingStrategy = scalingStrategy;
456 my pref_sound_scaling_height () = my p_sound_scaling_height = height;
457 my pref_sound_scaling_minimum () = my p_sound_scaling_minimum = minimum;
458 my pref_sound_scaling_maximum () = my p_sound_scaling_maximum = maximum;
459 FunctionEditor_redraw (me);
460 EDITOR_END
461 }
462
menu_cb_soundMuteChannels(TimeSoundEditor me,EDITOR_ARGS_FORM)463 static void menu_cb_soundMuteChannels (TimeSoundEditor me, EDITOR_ARGS_FORM) {
464 EDITOR_FORM (U"Mute channels", nullptr)
465 NATURALVECTOR (channels, U"Channels to mute", WHITESPACE_SEPARATED_, U"2")
466 EDITOR_OK
467 EDITOR_DO
468 const integer numberOfChannels = ( my d_longSound.data ? my d_longSound.data -> numberOfChannels : my d_sound.data -> ny );
469 Melder_assert (my d_sound.muteChannels.size == numberOfChannels);
470 for (integer ichan = 1; ichan <= numberOfChannels; ichan ++)
471 my d_sound.muteChannels [ichan] = false;
472 for (integer ichan = 1; ichan <= channels.size; ichan ++)
473 if (channels [ichan] >= 1 && channels [ichan] <= numberOfChannels)
474 my d_sound.muteChannels [channels [ichan]] = true;
475 FunctionEditor_redraw (me);
476 EDITOR_END
477 }
478
v_createMenuItems_view(EditorMenu menu)479 void structTimeSoundEditor :: v_createMenuItems_view (EditorMenu menu) {
480 if (our d_sound.data || our d_longSound.data)
481 our v_createMenuItems_view_sound (menu);
482 TimeSoundEditor_Parent :: v_createMenuItems_view (menu);
483 }
484
v_createMenuItems_view_sound(EditorMenu menu)485 void structTimeSoundEditor :: v_createMenuItems_view_sound (EditorMenu menu) {
486 EditorMenu_addCommand (menu, U"Sound scaling...", 0, menu_cb_soundScaling);
487 EditorMenu_addCommand (menu, U"Mute channels...", 0, menu_cb_soundMuteChannels);
488 }
489
v_updateMenuItems_file()490 void structTimeSoundEditor :: v_updateMenuItems_file () {
491 Sampled sound;
492 if (our d_sound.data) // cannot do this with "?:", because d_sound.data and d_longSound.data have different types
493 sound = our d_sound.data;
494 else
495 sound = our d_longSound.data;
496 if (! sound)
497 return;
498 integer first, last, selectedSamples = Sampled_getWindowSamples (sound, our startSelection, our endSelection, & first, & last);
499 if (our drawButton) {
500 GuiThing_setSensitive (our drawButton, selectedSamples != 0);
501 GuiThing_setSensitive (our publishButton, selectedSamples != 0);
502 GuiThing_setSensitive (our publishPreserveButton, selectedSamples != 0);
503 if (our publishWindowButton)
504 GuiThing_setSensitive (our publishWindowButton, selectedSamples != 0);
505 if (our publishOverlapButton)
506 GuiThing_setSensitive (our publishOverlapButton, selectedSamples != 0);
507 }
508 GuiThing_setSensitive (our writeWavButton, selectedSamples != 0);
509 if (our saveAs24BitWavButton)
510 GuiThing_setSensitive (our saveAs24BitWavButton, selectedSamples != 0);
511 if (our saveAs32BitWavButton)
512 GuiThing_setSensitive (our saveAs32BitWavButton, selectedSamples != 0);
513 GuiThing_setSensitive (our writeAiffButton, selectedSamples != 0);
514 GuiThing_setSensitive (our writeAifcButton, selectedSamples != 0);
515 GuiThing_setSensitive (our writeNextSunButton, selectedSamples != 0);
516 GuiThing_setSensitive (our writeNistButton, selectedSamples != 0);
517 GuiThing_setSensitive (our writeFlacButton, selectedSamples != 0);
518 }
519
TimeSoundEditor_drawSound(TimeSoundEditor me,double globalMinimum,double globalMaximum)520 void TimeSoundEditor_drawSound (TimeSoundEditor me, double globalMinimum, double globalMaximum) {
521 Sound sound = my d_sound.data;
522 LongSound longSound = my d_longSound.data;
523 Melder_assert (!! sound != !! longSound);
524 const integer numberOfChannels = ( sound ? sound -> ny : longSound -> numberOfChannels );
525 const bool cursorVisible = ( my startSelection == my endSelection && my startSelection >= my startWindow && my startSelection <= my endWindow );
526 Graphics_setColour (my graphics.get(), Melder_BLACK);
527 bool fits;
528 try {
529 fits = ( sound ? true : LongSound_haveWindow (longSound, my startWindow, my endWindow) );
530 } catch (MelderError) {
531 const bool outOfMemory = !! str32str (Melder_getError (), U"memory");
532 if (Melder_debug == 9)
533 Melder_flushError ();
534 else
535 Melder_clearError ();
536 Graphics_setWindow (my graphics.get(), 0.0, 1.0, 0.0, 1.0);
537 Graphics_setTextAlignment (my graphics.get(), Graphics_CENTRE, Graphics_HALF);
538 Graphics_text (my graphics.get(), 0.5, 0.5, outOfMemory ? U"(out of memory)" : U"(cannot read sound file)");
539 return;
540 }
541 if (! fits) {
542 Graphics_setWindow (my graphics.get(), 0.0, 1.0, 0.0, 1.0);
543 Graphics_setTextAlignment (my graphics.get(), Graphics_CENTRE, Graphics_HALF);
544 Graphics_text (my graphics.get(), 0.5, 0.5, U"(window too large; zoom in to see the data)");
545 return;
546 }
547 integer first, last;
548 if (Sampled_getWindowSamples (sound ? (Sampled) sound : (Sampled) longSound, my startWindow, my endWindow, & first, & last) <= 1) {
549 Graphics_setWindow (my graphics.get(), 0.0, 1.0, 0.0, 1.0);
550 Graphics_setTextAlignment (my graphics.get(), Graphics_CENTRE, Graphics_HALF);
551 Graphics_text (my graphics.get(), 0.5, 0.5, U"(zoom out to see the data)");
552 return;
553 }
554 const integer numberOfVisibleChannels = Melder_clippedRight (numberOfChannels, 8_integer);
555 const integer firstVisibleChannel = my d_sound.channelOffset + 1;
556 const integer lastVisibleChannel = Melder_clippedRight (my d_sound.channelOffset + numberOfVisibleChannels, numberOfChannels);
557 double maximumExtent = 0.0, visibleMinimum = 0.0, visibleMaximum = 0.0;
558 if (my p_sound_scalingStrategy == kTimeSoundEditor_scalingStrategy::BY_WINDOW) {
559 if (longSound)
560 LongSound_getWindowExtrema (longSound, my startWindow, my endWindow, firstVisibleChannel, & visibleMinimum, & visibleMaximum);
561 else
562 Matrix_getWindowExtrema (sound, first, last, firstVisibleChannel, firstVisibleChannel, & visibleMinimum, & visibleMaximum);
563 for (integer ichan = firstVisibleChannel + 1; ichan <= lastVisibleChannel; ichan ++) {
564 double visibleChannelMinimum, visibleChannelMaximum;
565 if (longSound)
566 LongSound_getWindowExtrema (longSound, my startWindow, my endWindow, ichan, & visibleChannelMinimum, & visibleChannelMaximum);
567 else
568 Matrix_getWindowExtrema (sound, first, last, ichan, ichan, & visibleChannelMinimum, & visibleChannelMaximum);
569 if (visibleChannelMinimum < visibleMinimum)
570 visibleMinimum = visibleChannelMinimum;
571 if (visibleChannelMaximum > visibleMaximum)
572 visibleMaximum = visibleChannelMaximum;
573 }
574 maximumExtent = visibleMaximum - visibleMinimum;
575 }
576 for (integer ichan = firstVisibleChannel; ichan <= lastVisibleChannel; ichan ++) {
577 const double cursorFunctionValue = ( longSound ? 0.0 :
578 Vector_getValueAtX (sound, 0.5 * (my startSelection + my endSelection), ichan, kVector_valueInterpolation :: SINC70) );
579 const double ymin = (double) (numberOfVisibleChannels - ichan + my d_sound.channelOffset) / numberOfVisibleChannels;
580 const double ymax = (double) (numberOfVisibleChannels + 1 - ichan + my d_sound.channelOffset) / numberOfVisibleChannels;
581 Graphics_Viewport vp = Graphics_insetViewport (my graphics.get(), 0.0, 1.0, ymin, ymax);
582 bool horizontal = false;
583 double minimum = ( sound ? globalMinimum : -1.0 ), maximum = ( sound ? globalMaximum : 1.0 );
584 if (my p_sound_scalingStrategy == kTimeSoundEditor_scalingStrategy::BY_WINDOW) {
585 if (numberOfChannels > 2) {
586 if (longSound)
587 LongSound_getWindowExtrema (longSound, my startWindow, my endWindow, ichan, & minimum, & maximum);
588 else
589 Matrix_getWindowExtrema (sound, first, last, ichan, ichan, & minimum, & maximum);
590 if (maximumExtent > 0.0) {
591 const double middle = 0.5 * (minimum + maximum);
592 minimum = middle - 0.5 * maximumExtent;
593 maximum = middle + 0.5 * maximumExtent;
594 }
595 } else {
596 minimum = visibleMinimum;
597 maximum = visibleMaximum;
598 }
599 } else if (my p_sound_scalingStrategy == kTimeSoundEditor_scalingStrategy::BY_WINDOW_AND_CHANNEL) {
600 if (longSound)
601 LongSound_getWindowExtrema (longSound, my startWindow, my endWindow, ichan, & minimum, & maximum);
602 else
603 Matrix_getWindowExtrema (sound, first, last, ichan, ichan, & minimum, & maximum);
604 } else if (my p_sound_scalingStrategy == kTimeSoundEditor_scalingStrategy::FIXED_HEIGHT) {
605 if (longSound)
606 LongSound_getWindowExtrema (longSound, my startWindow, my endWindow, ichan, & minimum, & maximum);
607 else
608 Matrix_getWindowExtrema (sound, first, last, ichan, ichan, & minimum, & maximum);
609 const double channelExtent = my p_sound_scaling_height;
610 const double middle = 0.5 * (minimum + maximum);
611 minimum = middle - 0.5 * channelExtent;
612 maximum = middle + 0.5 * channelExtent;
613 } else if (my p_sound_scalingStrategy == kTimeSoundEditor_scalingStrategy::FIXED_RANGE) {
614 minimum = my p_sound_scaling_minimum;
615 maximum = my p_sound_scaling_maximum;
616 }
617 if (minimum == maximum) {
618 horizontal = true;
619 minimum -= 1.0;
620 maximum += 1.0;
621 }
622 Graphics_setWindow (my graphics.get(), my startWindow, my endWindow, minimum, maximum);
623 if (horizontal) {
624 Graphics_setTextAlignment (my graphics.get(), Graphics_RIGHT, Graphics_HALF);
625 const double mid = 0.5 * (minimum + maximum);
626 Graphics_text (my graphics.get(), my startWindow, mid, Melder_float (Melder_half (mid)));
627 } else {
628 if (! cursorVisible || isundef (cursorFunctionValue) || Graphics_dyWCtoMM (my graphics.get(), cursorFunctionValue - minimum) > 5.0) {
629 Graphics_setTextAlignment (my graphics.get(), Graphics_RIGHT, Graphics_BOTTOM);
630 Graphics_text (my graphics.get(), my startWindow, minimum, Melder_float (Melder_half (minimum)));
631 }
632 if (! cursorVisible || isundef (cursorFunctionValue) || Graphics_dyWCtoMM (my graphics.get(), maximum - cursorFunctionValue) > 5.0) {
633 Graphics_setTextAlignment (my graphics.get(), Graphics_RIGHT, Graphics_TOP);
634 Graphics_text (my graphics.get(), my startWindow, maximum, Melder_float (Melder_half (maximum)));
635 }
636 }
637 if (minimum < 0 && maximum > 0 && ! horizontal) {
638 Graphics_setWindow (my graphics.get(), 0.0, 1.0, minimum, maximum);
639 if (! cursorVisible || isundef (cursorFunctionValue) || fabs (Graphics_dyWCtoMM (my graphics.get(), cursorFunctionValue - 0.0)) > 3.0) {
640 Graphics_setTextAlignment (my graphics.get(), Graphics_RIGHT, Graphics_HALF);
641 Graphics_text (my graphics.get(), 0.0, 0.0, U"0");
642 }
643 Graphics_setColour (my graphics.get(), Melder_CYAN);
644 Graphics_setLineType (my graphics.get(), Graphics_DOTTED);
645 Graphics_line (my graphics.get(), 0.0, 0.0, 1.0, 0.0);
646 Graphics_setLineType (my graphics.get(), Graphics_DRAWN);
647 }
648 /*
649 Garnish the drawing area of each channel.
650 */
651 Graphics_setWindow (my graphics.get(), 0.0, 1.0, 0.0, 1.0);
652 Graphics_setColour (my graphics.get(), Melder_CYAN);
653 Graphics_innerRectangle (my graphics.get(), 0.0, 1.0, 0.0, 1.0);
654 Graphics_setColour (my graphics.get(), Melder_BLACK);
655 if (numberOfChannels > 1) {
656 Graphics_setTextAlignment (my graphics.get(), Graphics_LEFT, Graphics_HALF);
657 Graphics_setTextAlignment (my graphics.get(), Graphics_LEFT, Graphics_HALF);
658 conststring32 channelName = my v_getChannelName (ichan);
659 static MelderString channelLabel;
660 MelderString_copy (& channelLabel, ( channelName ? U"ch" : U"Ch " ), ichan);
661 if (channelName)
662 MelderString_append (& channelLabel, U": ", channelName);
663 MelderString_append (& channelLabel, U" ",
664 ( my d_sound.muteChannels [ichan] ? UNITEXT_SPEAKER_WITH_CANCELLATION_STROKE : UNITEXT_SPEAKER ));
665 if (ichan > 8 && ichan - my d_sound.channelOffset == 1)
666 MelderString_append (& channelLabel, U" " UNITEXT_UPWARDS_ARROW);
667 else if (numberOfChannels >= 8 && ichan - my d_sound.channelOffset == 8 && ichan < numberOfChannels)
668 MelderString_append (& channelLabel, U" " UNITEXT_DOWNWARDS_ARROW);
669 Graphics_text (my graphics.get(), 1.0, 0.5, channelLabel.string);
670 }
671 /*
672 Draw a very thin separator line underneath.
673 */
674 if (ichan < numberOfChannels) {
675 /*Graphics_setColour (my graphics.get(), Melder_BLACK);*/
676 Graphics_line (my graphics.get(), 0.0, 0.0, 1.0, 0.0);
677 }
678 /*
679 Draw the samples.
680 */
681 /*if (ichan == 1) FunctionEditor_SoundAnalysis_drawPulses (this);*/
682 if (sound) {
683 Graphics_setWindow (my graphics.get(), my startWindow, my endWindow, minimum, maximum);
684 if (cursorVisible && isdefined (cursorFunctionValue))
685 FunctionEditor_drawCursorFunctionValue (me, cursorFunctionValue, Melder_float (Melder_half (cursorFunctionValue)), U"");
686 Graphics_setColour (my graphics.get(), Melder_BLACK);
687 Graphics_function (my graphics.get(), & sound -> z [ichan] [0], first, last,
688 Sampled_indexToX (sound, first), Sampled_indexToX (sound, last));
689 } else {
690 Graphics_setWindow (my graphics.get(), my startWindow, my endWindow, minimum * 32768, maximum * 32768);
691 Graphics_function16 (my graphics.get(),
692 longSound -> buffer.asArgumentToFunctionThatExpectsZeroBasedArray() - longSound -> imin * numberOfChannels + (ichan - 1),
693 numberOfChannels, first, last, Sampled_indexToX (longSound, first), Sampled_indexToX (longSound, last));
694 }
695 Graphics_resetViewport (my graphics.get(), vp);
696 }
697 Graphics_setWindow (my graphics.get(), 0.0, 1.0, 0.0, 1.0);
698 Graphics_rectangle (my graphics.get(), 0.0, 1.0, 0.0, 1.0);
699 }
700
v_mouseInWideDataView(GuiDrawingArea_MouseEvent event,double x_world,double y_fraction)701 bool structTimeSoundEditor :: v_mouseInWideDataView (GuiDrawingArea_MouseEvent event, double x_world, double y_fraction) {
702 if (event -> isClick()) {
703 Sound sound = our d_sound.data;
704 LongSound longSound = our d_longSound.data;
705 if (!! sound != !! longSound) {
706 y_fraction = (y_fraction - v_getBottomOfSoundArea ()) / (1.0 - v_getBottomOfSoundArea ());
707 const integer numberOfChannels = ( sound ? sound -> ny : longSound -> numberOfChannels );
708 if (event -> commandKeyPressed) {
709 if (numberOfChannels > 1) {
710 const integer numberOfVisibleChannels = Melder_clippedRight (numberOfChannels, 8_integer);
711 Melder_assert (numberOfVisibleChannels >= 1); // for Melder_clipped
712 const integer clickedChannel = our d_sound.channelOffset +
713 Melder_clipped (1_integer, Melder_ifloor ((1.0 - y_fraction) * numberOfVisibleChannels + 1), numberOfVisibleChannels);
714 const integer firstVisibleChannel = our d_sound.channelOffset + 1;
715 const integer lastVisibleChannel = Melder_clippedRight (our d_sound.channelOffset + numberOfVisibleChannels, numberOfChannels);
716 if (clickedChannel >= firstVisibleChannel && clickedChannel <= lastVisibleChannel) {
717 our d_sound.muteChannels [clickedChannel] = ! our d_sound.muteChannels [clickedChannel];
718 return FunctionEditor_UPDATE_NEEDED;
719 }
720 }
721 } else {
722 if (numberOfChannels > 8) {
723 if (x_world >= our endWindow && y_fraction > 0.875 && y_fraction <= 1.000 && our d_sound.channelOffset > 0) {
724 our d_sound.channelOffset -= 8;
725 return FunctionEditor_UPDATE_NEEDED;
726 }
727 if (x_world >= our endWindow && y_fraction > 0.000 && y_fraction <= 0.125 && our d_sound.channelOffset < numberOfChannels - 8) {
728 our d_sound.channelOffset += 8;
729 return FunctionEditor_UPDATE_NEEDED;
730 }
731 }
732 }
733 }
734 }
735 return TimeSoundEditor_Parent :: v_mouseInWideDataView (event, x_world, y_fraction);
736 }
737
TimeSoundEditor_init(TimeSoundEditor me,conststring32 title,Function data,Sampled sound,bool ownSound)738 void TimeSoundEditor_init (TimeSoundEditor me, conststring32 title, Function data, Sampled sound, bool ownSound) {
739 my d_ownSound = ownSound;
740 if (sound) {
741 integer numberOfChannels = 1;
742 if (ownSound) {
743 Melder_assert (Thing_isa (sound, classSound));
744 my d_sound.data = Data_copy ((Sound) sound).releaseToAmbiguousOwner(); // deep copy; ownership transferred
745 Matrix_getWindowExtrema (my d_sound.data, 1, my d_sound.data -> nx, 1, my d_sound.data -> ny, & my d_sound.minimum, & my d_sound.maximum);
746 numberOfChannels = my d_sound.data -> ny;
747 } else if (Thing_isa (sound, classSound)) {
748 my d_sound.data = (Sound) sound; // reference copy; ownership not transferred
749 Matrix_getWindowExtrema (my d_sound.data, 1, my d_sound.data -> nx, 1, my d_sound.data -> ny, & my d_sound.minimum, & my d_sound.maximum);
750 numberOfChannels = my d_sound.data -> ny;
751 } else if (Thing_isa (sound, classLongSound)) {
752 my d_longSound.data = (LongSound) sound;
753 my d_sound.minimum = -1.0;
754 my d_sound.maximum = 1.0;
755 numberOfChannels = my d_longSound.data -> numberOfChannels;
756 } else {
757 Melder_fatal (U"Invalid sound class in TimeSoundEditor::init.");
758 }
759 my d_sound.muteChannels = zero_BOOLVEC (numberOfChannels);
760 }
761 FunctionEditor_init (me, title, data);
762 }
763
764 /* End of file TimeSoundEditor.cpp */
765