1 /* Sound_audio.cpp
2  *
3  * Copyright (C) 1992-2020 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 <errno.h>
20 
21 #ifdef linux
22 	#define DEV_AUDIO  "/dev/dsp"
23 #else
24 	#define DEV_AUDIO  "/dev/audio"
25 #endif
26 
27 #include "Sound.h"
28 #include "Preferences.h"
29 #include "../external/portaudio/portaudio.h"
30 
31 #if defined (macintosh)
32 	#include "macport_on.h"
33 	#include "pa_mac_core.h"
34 	#include "macport_off.h"
35 #elif defined (_WIN32)
36 	#include "winport_on.h"
37 	#include <windows.h>
38 	#include <mmsystem.h>
39 	#include "winport_off.h"
40 #elif defined (linux)
41 	#include <fcntl.h>
42 	#if ! defined (NO_AUDIO)
43 		#if defined (__OpenBSD__) || defined (__NetBSD__)
44 			#include <soundcard.h>
45 		#else
46 			#include <sys/soundcard.h>
47 		#endif
48 	#endif
49 	#include <sys/ioctl.h>   /* ioctl */
50 	#include <unistd.h>   /* open write close read */
51 #else
52 	#include <fcntl.h>
53 #endif
54 
55 struct Sound_recordFixedTime_Info {
56 	integer numberOfSamples, numberOfSamplesRead;
57 	short *buffer;
58 };
getNumberOfSamplesRead(volatile struct Sound_recordFixedTime_Info * info)59 static integer getNumberOfSamplesRead (volatile struct Sound_recordFixedTime_Info *info) {
60 	volatile integer numberOfSamplesRead = info -> numberOfSamplesRead;
61 	return numberOfSamplesRead;
62 }
63 
portaudioStreamCallback(const void * input,void *,unsigned long frameCount,const PaStreamCallbackTimeInfo *,PaStreamCallbackFlags,void * void_info)64 static int portaudioStreamCallback (
65     const void *input, void * /*output*/,
66     unsigned long frameCount,
67     const PaStreamCallbackTimeInfo *  /*timeInfo*/,
68     PaStreamCallbackFlags /*statusFlags*/,
69     void *void_info)
70 {
71 	struct Sound_recordFixedTime_Info *info = (struct Sound_recordFixedTime_Info *) void_info;
72 	integer samplesLeft = info -> numberOfSamples - info -> numberOfSamplesRead;
73 	if (samplesLeft > 0) {
74 		integer dsamples = std::min (samplesLeft, uinteger_to_integer (frameCount));
75 		memcpy (info -> buffer + info -> numberOfSamplesRead, input, integer_to_uinteger (2 * dsamples));
76 		info -> numberOfSamplesRead += dsamples;
77 		const short *input2 = (const short *) input;
78 		//Melder_casual (U"read ", dsamples, U" samples: ", input2 [0], U", ", input2 [1], U", ", input2 [3], U"...");
79 		if (info -> numberOfSamplesRead >= info -> numberOfSamples)
80 			return paComplete;
81 	} else /*if (info -> numberOfSamplesRead >= info -> numberOfSamples)*/ {
82 		info -> numberOfSamplesRead = info -> numberOfSamples;
83 		return paComplete;
84 	}
85 	return paContinue;
86 }
87 
Sound_record_fixedTime(int inputSource,double gain,double balance,double sampleRate,double duration)88 autoSound Sound_record_fixedTime (int inputSource, double gain, double balance, double sampleRate, double duration) {
89 	bool inputUsesPortAudio =
90 		#if defined (_WIN32)
91 			MelderAudio_getInputSoundSystem () == kMelder_inputSoundSystem::MME_VIA_PORTAUDIO;
92 		#elif defined (macintosh)
93 			MelderAudio_getInputSoundSystem () == kMelder_inputSoundSystem::COREAUDIO_VIA_PORTAUDIO;
94 		#elif defined (raspberrypi)
95 			MelderAudio_getInputSoundSystem () == kMelder_inputSoundSystem::JACK_VIA_PORTAUDIO;
96 		#else
97 			MelderAudio_getInputSoundSystem () == kMelder_inputSoundSystem::ALSA_VIA_PORTAUDIO;
98 		#endif
99 	PaStream *portaudioStream = nullptr;
100 	#if defined (macintosh)
101 	#elif defined (_WIN32)
102 		HWAVEIN hWaveIn = 0;
103 	#else
104 		int fd = -1;   // other systems use stream I/O with a file descriptor
105 		int fd_mixer = -1;
106 	#endif
107 	try {
108 		integer numberOfSamples, i;
109 
110 		/*
111 			Declare platform-dependent data structures.
112 		*/
113 		volatile struct Sound_recordFixedTime_Info info = { 0 };
114 		PaStreamParameters streamParameters = { 0 };
115 		#if defined (macintosh)
116 			(void) gain;
117 			(void) balance;
118 		#elif defined (_WIN32)
119 			WAVEFORMATEX waveFormat;
120 			WAVEHDR waveHeader;
121 			MMRESULT err;
122 			(void) inputSource;
123 			(void) gain;
124 			(void) balance;
125 		#elif defined (linux)
126 			int dev_mask;
127 			int val;
128 		#endif
129 
130 		/*
131 			Check representation of shorts.
132 		*/
133 		if (sizeof (short) != 2)
134 			Melder_throw (U"Cannot record a sound on this computer.");
135 
136 		/*
137 			Check sampling frequency.
138 		*/
139 		bool supportsSamplingFrequency = true;
140 		if (inputUsesPortAudio) {
141 			#if defined (macintosh)
142 				if (sampleRate != 44100 && sampleRate != 48000 && sampleRate != 96000)
143 					supportsSamplingFrequency = false;
144 			#endif
145 		} else {
146 			#if defined (macintosh)
147 				if (sampleRate != 44100)
148 					supportsSamplingFrequency = false;
149 			#elif defined (linux)
150 				if (sampleRate != 8000 && sampleRate != 11025 &&
151 						sampleRate != 16000 && sampleRate != 22050 &&
152 						sampleRate != 32000 && sampleRate != 44100 &&
153 						sampleRate != 48000)
154 					supportsSamplingFrequency = false;
155 			#elif defined (_WIN32)
156 				if (sampleRate != 8000 && sampleRate != 11025 &&
157 						sampleRate != 16000 && sampleRate != 22050 &&
158 						sampleRate != 32000 && sampleRate != 44100 &&
159 						sampleRate != 48000 && sampleRate != 96000)
160 					supportsSamplingFrequency = false;
161 			#endif
162 		}
163 		if (! supportsSamplingFrequency)
164 			Melder_throw (U"Your audio hardware does not support a sampling frequency of ", sampleRate, U" Hz.");
165 
166 		/*
167 			Open phase 1.
168 			On some platforms, the info is filled in before the audio port is opened.
169 			On other platforms, the info is filled in after the port is opened.
170 		*/
171 		if (inputUsesPortAudio) {
172 			if (! MelderAudio_hasBeenInitialized) {
173 				PaError err = Pa_Initialize ();
174 				if (err)
175 					Melder_throw (U"Pa_Initialize: ", Melder_peek8to32 (Pa_GetErrorText (err)));
176 				MelderAudio_hasBeenInitialized = true;
177 			}
178 		} else {
179 			#if defined (macintosh)
180 			#elif defined (_WIN32)
181 			#elif ! defined (NO_AUDIO)
182 				/*
183 					We must open the port now, because we use an ioctl to set the info to an open port.
184 				*/
185 				fd = open (DEV_AUDIO, O_RDONLY);
186 				if (fd == -1) {
187 					if (errno == EBUSY)
188 						Melder_throw (U"Audio device in use by another program.");
189 					else
190 						#ifdef linux
191 							Melder_throw (U"Cannot open audio device.\nPlease switch on PortAudio in the Sound Recording Preferences.");
192 						#else
193 							Melder_throw (U"Cannot open audio device.");
194 						#endif
195 				}
196 				/*
197 					The device immediately started recording into its buffer,
198 					but probably at the wrong rate etc.
199 					Pause and flush this rubbish.
200 				*/
201 				#if defined (linux)
202 					ioctl (fd, SNDCTL_DSP_RESET, nullptr);
203 				#endif
204 			#endif
205 		}
206 
207 		/*
208 			Set the input source; the default is the microphone.
209 		*/
210 		if (inputUsesPortAudio) {
211 			if (inputSource < 1 || inputSource > Pa_GetDeviceCount ())
212 				Melder_throw (U"Unknown device #", inputSource, U".");
213 			/*
214 				Saying
215 					streamParameters. device = inputSource - 1;
216 				would presuppose that the input devices are listed before the output devices.
217 				TODO: cycle through all devices, and determine which of them are input devices
218 			*/
219 			streamParameters. device = Pa_GetDefaultInputDevice ();
220 			Melder_casual (U"streamParameters. device: ", (integer) streamParameters. device);
221 			const PaDeviceInfo *paDeviceInfo = Pa_GetDeviceInfo (streamParameters. device);
222 			Melder_casual (U"Name: ", Melder_peek8to32 (paDeviceInfo -> name));
223 		} else {
224 			#if defined (macintosh)
225 			#elif defined (linux) && ! defined (NO_AUDIO)
226 				fd_mixer = open ("/dev/mixer", O_WRONLY);
227 				if (fd_mixer == -1)
228 					Melder_throw (U"Cannot open /dev/mixer.");
229 				dev_mask = inputSource == 1 ? SOUND_MASK_MIC : SOUND_MASK_LINE;
230 				if (ioctl (fd_mixer, SOUND_MIXER_WRITE_RECSRC, & dev_mask) == -1)
231 					Melder_throw (U"Cannot set recording device in mixer");
232 			#endif
233 		}
234 
235 		/*
236 			Set gain and balance.
237 		*/
238 		if (inputUsesPortAudio) {
239 			/* Taken from Audio Control Panel. */
240 		} else {
241 			#if defined (macintosh) || defined (_WIN32)
242 				/* Taken from Audio Control Panel. */
243 			#elif defined (linux) && ! defined (NO_AUDIO)
244 				val = ( gain <= 0.0 ? 0 : gain >= 1.0 ? 100 : Melder_iround (gain * 100) );
245 				balance = ( balance <= 0.0 ? 0 : balance >= 1 ? 1 : balance );
246 				if (balance >= 0.5) {
247 					val = (int)(((int)(val*balance/(1-balance)) << 8) | val);
248 				} else {
249 					val = (int)(val | ((int)(val*(1-balance)/balance) << 8));
250 				}
251 				val = (int)((std::min(2.0-2.0*balance,1.0))*val) | ((int)((std::min(2.0*balance,1.0))*val) << 8);
252 				if (inputSource == 1) {
253 					/* MIC */
254 					if (ioctl (fd_mixer, MIXER_WRITE (SOUND_MIXER_MIC), & val) == -1)
255 						Melder_throw (U"Cannot set gain and balance.");
256 				} else {
257 					/* LINE */
258 					if (ioctl (fd_mixer, MIXER_WRITE (SOUND_MIXER_LINE), & val) == -1)
259 						Melder_throw (U"Cannot set gain and balance.");
260 				}
261 				close (fd_mixer);
262 				fd_mixer = -1;
263 			#endif
264 		}
265 
266 		/*
267 			Set the sampling frequency.
268 		*/
269 		if (inputUsesPortAudio) {
270 			// Set while opening.
271 		} else {
272 			#if defined (macintosh)
273 			#elif defined (linux) && ! defined (NO_AUDIO)
274 				int sampleRate_int = (int) sampleRate;
275 				if (ioctl (fd, SNDCTL_DSP_SPEED, & sampleRate_int) == -1)
276 					Melder_throw (U"Cannot set sampling frequency to ", sampleRate, U" Hz.");
277 			#elif defined (_WIN32)
278 				waveFormat. nSamplesPerSec = sampleRate;
279 			#endif
280 		}
281 
282 		/*
283 			Set the number of channels to 1 (mono), if possible.
284 		*/
285 		if (inputUsesPortAudio) {
286 			streamParameters. channelCount = 1;
287 		} else {
288 			#if defined (macintosh)
289 			#elif defined (linux) && ! defined (NO_AUDIO)
290 				val = 1;
291 				if (ioctl (fd, SNDCTL_DSP_CHANNELS, & val) == -1)
292 					Melder_throw (U"Cannot set to mono.");
293 			#elif defined (_WIN32)
294 				waveFormat. nChannels = 1;
295 			#endif
296 		}
297 
298 		/*
299 			Set the encoding to 16-bit linear (or to 8-bit linear, if 16-bit is not available).
300 		*/
301 		if (inputUsesPortAudio) {
302 			streamParameters. sampleFormat = paInt16;
303 		} else {
304 			#if defined (macintosh)
305 			#elif defined (linux) && ! defined (NO_AUDIO)
306 				#if __BYTE_ORDER == __BIG_ENDIAN
307 					val = AFMT_S16_BE;
308 				#else
309 					val = AFMT_S16_LE;
310 				#endif
311 				if (ioctl (fd, SNDCTL_DSP_SETFMT, & val) == -1)
312 					Melder_throw (U"Cannot set 16-bit linear.");
313 			#elif defined (_WIN32)
314 				waveFormat. wFormatTag = WAVE_FORMAT_PCM;
315 				waveFormat. wBitsPerSample = 16;
316 				waveFormat. nBlockAlign = waveFormat. nChannels * waveFormat. wBitsPerSample / 8;
317 				waveFormat. nAvgBytesPerSec = waveFormat. nBlockAlign * waveFormat. nSamplesPerSec;
318 			#endif
319 		}
320 
321 		/*
322 			Create a buffer for recording, and the resulting sound.
323 		*/
324 		numberOfSamples = Melder_iround (sampleRate * duration);
325 		if (numberOfSamples < 1)
326 			Melder_throw (U"Duration too short.");
327 		autovector<short> buffer = newvectorzero <short> (numberOfSamples);
328 		autoSound me = Sound_createSimple (1, numberOfSamples / sampleRate, sampleRate);
329 		Melder_assert (my nx == numberOfSamples);
330 
331 		/*
332 			Open phase 2.
333 			This starts recording now.
334 		*/
335 		if (inputUsesPortAudio) {
336 			streamParameters. suggestedLatency = Pa_GetDeviceInfo (streamParameters. device) -> defaultLowInputLatency;
337 			#if defined (macintosh)
338 				PaMacCoreStreamInfo macCoreStreamInfo = { 0 };
339 				macCoreStreamInfo. size = sizeof (PaMacCoreStreamInfo);
340 				macCoreStreamInfo. hostApiType = paCoreAudio;
341 				macCoreStreamInfo. version = 0x01;
342 				macCoreStreamInfo. flags = paMacCoreChangeDeviceParameters | paMacCoreFailIfConversionRequired;
343 				macCoreStreamInfo. channelMap = nullptr;
344 				macCoreStreamInfo. channelMapSize = 0;
345 				streamParameters. hostApiSpecificStreamInfo = & macCoreStreamInfo;
346 			#endif
347 			info. numberOfSamples = numberOfSamples;
348 			info. numberOfSamplesRead = 0;
349 			info. buffer = buffer.asArgumentToFunctionThatExpectsZeroBasedArray();
350 			PaError err = Pa_OpenStream (& portaudioStream, & streamParameters, nullptr,
351 				sampleRate,
352 				0,   // this gives the default of 64 samples per buffer on Paul's 2018 MacBook Pro (checked 20200813)
353 				paNoFlag, portaudioStreamCallback, (void *) & info);
354 			if (err)
355 				Melder_throw (U"open ", Melder_peek8to32 (Pa_GetErrorText (err)));
356 			Pa_StartStream (portaudioStream);
357 			if (err)
358 				Melder_throw (U"start ", Melder_peek8to32 (Pa_GetErrorText (err)));
359 		} else {
360 			#if defined (macintosh)
361 			#elif defined (_WIN32)
362 				waveFormat. cbSize = 0;
363 				err = waveInOpen (& hWaveIn, WAVE_MAPPER, & waveFormat, 0, 0, CALLBACK_NULL);
364 				if (err != MMSYSERR_NOERROR)
365 					Melder_throw (U"Error ", err, U" while opening.");
366 			#endif
367 		}
368 for (i = 1; i <= numberOfSamples; i ++) trace (U"Started ", buffer [i]);
369 
370 		/*
371 			Read the sound into the buffer.
372 		*/
373 		if (inputUsesPortAudio) {
374 			// The callback will do this. Just wait.
375 			while (/*getNumberOfSamplesRead (& info)*/ info. numberOfSamplesRead < numberOfSamples) {
376 				//Pa_Sleep (1);
377 				trace (U"filled ", getNumberOfSamplesRead (& info), U"/", numberOfSamples);
378 			}
379 for (i = 1; i <= numberOfSamples; i ++) trace (U"Recorded ", buffer [i]);
380 		} else {
381 			#if defined (macintosh)
382 			#elif defined (_WIN32)
383 				waveHeader. dwFlags = 0;
384 				waveHeader. lpData = (char *) buffer.asArgumentToFunctionThatExpectsZeroBasedArray();
385 				waveHeader. dwBufferLength = numberOfSamples * 2;
386 				waveHeader. dwLoops = 0;
387 				waveHeader. lpNext = nullptr;
388 				waveHeader. reserved = 0;
389 				err = waveInPrepareHeader (hWaveIn, & waveHeader, sizeof (WAVEHDR));
390 				if (err != MMSYSERR_NOERROR)
391 					Melder_throw (U"Error ", err, U" while preparing header.");
392 				err = waveInAddBuffer (hWaveIn, & waveHeader, sizeof (WAVEHDR));
393 				if (err != MMSYSERR_NOERROR)
394 					Melder_throw (U"Error ", err, U" while listening.");
395 				err = waveInStart (hWaveIn);
396 				if (err != MMSYSERR_NOERROR)
397 					Melder_throw (U"Error ", err, U" while starting.");
398 					while (! (waveHeader. dwFlags & WHDR_DONE)) { Pa_Sleep (1); }
399 				err = waveInUnprepareHeader (hWaveIn, & waveHeader, sizeof (WAVEHDR));
400 				if (err != MMSYSERR_NOERROR)
401 					Melder_throw (U"Error ", err, U" while unpreparing header.");
402 			#else
403 				integer bytesLeft = 2 * numberOfSamples, dbytes, bytesRead = 0;
404 				while (bytesLeft) {
405 					dbytes = read (fd, (char *) buffer.asArgumentToFunctionThatExpectsZeroBasedArray() + bytesRead, std::min (bytesLeft, 4000_integer));
406 					if (dbytes <= 0)
407 						break;
408 					bytesLeft -= dbytes;
409 					bytesRead += dbytes;
410 				};
411 			#endif
412 		}
413 
414 		/*
415 			Copy the buffered data to the sound object, and discard the buffer.
416 		*/
417 		for (i = 1; i <= numberOfSamples; i ++)
418 			my z [1] [i] = buffer [i] * (1.0 / 32768);
419 
420 		/*
421 			Close the audio device.
422 		*/
423 		if (inputUsesPortAudio) {
424 			Pa_StopStream (portaudioStream);
425 			Pa_CloseStream (portaudioStream);
426 		} else {
427 			#if defined (macintosh)
428 			#elif defined (_WIN32)
429 				err = waveInClose (hWaveIn);
430 				if (err != MMSYSERR_NOERROR)
431 					Melder_throw (U"Error ", err, U" while closing.");
432 			#else
433 				close (fd);
434 			#endif
435 		}
436 
437 		/*
438 			Hand the resulting sound to the caller.
439 		*/
440 		return me;
441 	} catch (MelderError) {
442 		if (inputUsesPortAudio) {
443 			if (portaudioStream)
444 				Pa_StopStream (portaudioStream);
445 			if (portaudioStream)
446 				Pa_CloseStream (portaudioStream);
447 		} else {
448 			#if defined (macintosh)
449 			#elif defined (_WIN32)
450 				if (hWaveIn != 0)
451 					waveInClose (hWaveIn);
452 			#else
453 				if (fd_mixer != -1)
454 					close (fd_mixer);
455 				if (fd != -1)
456 					close (fd);
457 			#endif
458 		}
459 		Melder_throw (U"Sound not recorded.");
460 	}
461 }
462 
463 /********** PLAYING A SOUND **********/
464 
465 static struct SoundPlay {
466 	integer numberOfSamples, i1, i2, silenceBefore, silenceAfter;
467 	double tmin, tmax, dt, t1;
468 	Sound_PlayCallback callback;
469 	Thing boss;
470 	autovector <int16> outputBuffer;
471 } thePlayingSound;
472 
melderPlayCallback(void * closure,integer samplesPlayed)473 static bool melderPlayCallback (void *closure, integer samplesPlayed) {
474 	struct SoundPlay *me = (struct SoundPlay *) closure;
475 	int phase = 2;
476 	double t = ( samplesPlayed <= my silenceBefore ? my tmin :
477 			samplesPlayed >= my silenceBefore + my numberOfSamples ? my tmax :
478 			my t1 + (my i1 - 1.5 + samplesPlayed - my silenceBefore) * my dt );
479 	if (! MelderAudio_isPlaying) {
480 		my outputBuffer.reset();   // get a bit of privacy
481 		phase = 3;
482 	}
483 	if (my callback)
484 		return my callback (my boss, phase, my tmin, my tmax, t);
485 	return true;
486 }
487 
Sound_playPart(Sound me,double tmin,double tmax,Sound_PlayCallback callback,Thing boss)488 void Sound_playPart (Sound me, double tmin, double tmax, Sound_PlayCallback callback, Thing boss)
489 {
490 	try {
491 		integer ifsamp = Melder_iround (1.0 / my dx), bestSampleRate = MelderAudio_getOutputBestSampleRate (ifsamp);
492 		if (ifsamp == bestSampleRate) {
493 			struct SoundPlay *thee = (struct SoundPlay *) & thePlayingSound;
494 			double *fromLeft = & my z [1] [0], *fromRight = ( my ny > 1 ? & my z [2] [0] : nullptr );
495 			MelderAudio_stopPlaying (MelderAudio_IMPLICIT);
496 			integer i1, i2;
497 			if ((thy numberOfSamples = Matrix_getWindowSamplesX (me, tmin, tmax, & i1, & i2)) < 1)
498 				return;
499 			thy tmin = tmin;
500 			thy tmax = tmax;
501 			thy dt = my dx;
502 			thy t1 = my x1;
503 			thy callback = callback;
504 			thy boss = boss;
505 			thy silenceBefore = Melder_iroundTowardsZero (ifsamp * MelderAudio_getOutputSilenceBefore ());
506 			thy silenceAfter = Melder_iroundTowardsZero (ifsamp * MelderAudio_getOutputSilenceAfter ());
507 			integer numberOfChannels = my ny;
508 			thy outputBuffer = newvectorzero <int16> ((i2 - i1 + 1 + thy silenceBefore + thy silenceAfter) * numberOfChannels);
509 			thy i1 = i1;
510 			thy i2 = i2;
511 			int16 *to = & thy outputBuffer [0] + thy silenceBefore * numberOfChannels;
512 			if (numberOfChannels > 2) {
513 				for (integer i = i1; i <= i2; i ++) {
514 					for (integer chan = 1; chan <= my ny; chan ++) {
515 						integer value = Melder_iround_tieDown (my z [chan] [i] * 32768.0);
516 						* ++ to = (int16) Melder_clipped (-32768_integer, value, +32767_integer);
517 					}
518 				}
519 			} else if (numberOfChannels == 2) {
520 				for (integer i = i1; i <= i2; i ++) {
521 					integer valueLeft = Melder_iround_tieDown (fromLeft [i] * 32768.0);
522 					* ++ to = (int16) Melder_clipped (-32768_integer, valueLeft, +32767_integer);
523 					integer valueRight = Melder_iround_tieDown (fromRight [i] * 32768.0);
524 					* ++ to = (int16) Melder_clipped (-32768_integer, valueRight, +32767_integer);
525 				}
526 			} else {
527 				for (integer i = i1; i <= i2; i ++) {
528 					integer value = Melder_iround_tieDown (fromLeft [i] * 32768.0);
529 					* ++ to = (int16) Melder_clipped (-32768_integer, value, +32767_integer);
530 				}
531 			}
532 			if (thy callback)
533 				thy callback (thy boss, 1, tmin, tmax, tmin);
534 			MelderAudio_play16 (thy outputBuffer.asArgumentToFunctionThatExpectsZeroBasedArray(), ifsamp,
535 				thy silenceBefore + thy numberOfSamples + thy silenceAfter, numberOfChannels, melderPlayCallback, thee);
536 		} else {
537 			autoSound part = Sound_extractPart (me, tmin, tmax, kSound_windowShape::RECTANGULAR, 1.0, true);
538 			autoSound resampled = Sound_resample (part.get(), bestSampleRate, 1);
539 			Sound_playPart (resampled.get(), tmin, tmax, callback, boss);   // recursively
540 		}
541 	} catch (MelderError) {
542 		Melder_throw (me, U": not played.");
543 	}
544 }
545 
Sound_play(Sound me,Sound_PlayCallback playCallback,Thing playClosure)546 void Sound_play (Sound me, Sound_PlayCallback playCallback, Thing playClosure)
547 {
548 	Sound_playPart (me, my xmin, my xmax, playCallback, playClosure);
549 }
550 
551 /* End of file Sound_audio.cpp */
552