SoundRecorder.cpp 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189
  1. /* SoundRecorder.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. /* Linux code originally by Darryl Purnell, Pretoria */
  19. /* GTK conversion includes work by Franz Brauße */
  20. /* This source file describes interactive sound recorders for the following systems:
  21. * MacOS
  22. * Linux
  23. * Windows
  24. * Because the behaviour of these sound recorders is partly similar, partly different,
  25. * this would seem a good candidate for object-oriented programming
  26. * (one audio manager and several audio drivers).
  27. * However, the places where sound recorders are similar and where they are different,
  28. * are hard to predict. For this reason, everything is done with platform #ifdefs.
  29. */
  30. #include <errno.h>
  31. #include "SoundRecorder.h"
  32. #include "Sound_and_Spectrum.h"
  33. #include "machine.h"
  34. #include "EditorM.h"
  35. #if defined (macintosh)
  36. #include "pa_mac_core.h"
  37. #endif
  38. #include "enums_getText.h"
  39. #include "SoundRecorder_enums.h"
  40. #include "enums_getValue.h"
  41. #include "SoundRecorder_enums.h"
  42. Thing_implement (SoundRecorder, Editor, 0);
  43. #include "prefs_define.h"
  44. #include "SoundRecorder_prefs.h"
  45. #include "prefs_install.h"
  46. #include "SoundRecorder_prefs.h"
  47. #include "prefs_copyToInstance.h"
  48. #include "SoundRecorder_prefs.h"
  49. static struct {
  50. int bufferSizeInMegabytes;
  51. } preferences;
  52. void SoundRecorder_preferences () {
  53. Preferences_addInt (U"SoundRecorder.bufferSizeInMegabytes", & preferences.bufferSizeInMegabytes, 60);
  54. }
  55. int SoundRecorder_getBufferSizePref_MB () { return preferences.bufferSizeInMegabytes; }
  56. void SoundRecorder_setBufferSizePref_MB (int size) { preferences.bufferSizeInMegabytes = size < 1 ? 1 : size > 1000 ? 1000: size; }
  57. #define step 1000
  58. /* For those systems that do not have a pollable audio control panel, */
  59. /* the settings are saved only here, so that they are remembered across */
  60. /* subsequent creations of a SoundRecorder. Also, this is then the way */
  61. /* in which two simultaneously open SoundRecorders would communicate. */
  62. static struct {
  63. int inputSource; // 1 = microphone, 2 = line, 3 = digital
  64. int leftGain, rightGain; // 0..255
  65. double sampleRate;
  66. } theControlPanel =
  67. #if defined (linux)
  68. { 1, 200, 200, 44100 };
  69. #elif defined (macintosh)
  70. { 1, 26, 26, 44100 };
  71. #else
  72. { 1, 26, 26, 44100 };
  73. #endif
  74. /********** ERROR HANDLING **********/
  75. #if defined (_WIN32)
  76. static void win_fillFormat (SoundRecorder me) {
  77. my waveFormat. nSamplesPerSec = (int) theControlPanel. sampleRate;
  78. my waveFormat. nChannels = my numberOfChannels;
  79. my waveFormat. wFormatTag = WAVE_FORMAT_PCM;
  80. my waveFormat. wBitsPerSample = 16;
  81. my waveFormat. nBlockAlign = my waveFormat. nChannels * my waveFormat. wBitsPerSample / 8;
  82. my waveFormat. nAvgBytesPerSec = my waveFormat. nBlockAlign * my waveFormat. nSamplesPerSec;
  83. my waveFormat. cbSize = 0;
  84. }
  85. static void win_fillHeader (SoundRecorder me, int which) {
  86. my waveHeader [which]. dwFlags = 0;
  87. my waveHeader [which]. lpData = which == 0 ? (char *) my buffer : which == 1 ? (char *) my buffertje1: (char *) my buffertje2;
  88. my waveHeader [which]. dwBufferLength = which == 0 ? my nmax * my waveFormat. nChannels * 2 : 1000 * my waveFormat. nChannels * 2;
  89. my waveHeader [which]. dwLoops = 0;
  90. my waveHeader [which]. lpNext = nullptr;
  91. my waveHeader [which]. reserved = 0;
  92. }
  93. static void win_waveInCheck (SoundRecorder me) {
  94. wchar_t messageText [MAXERRORLENGTH];
  95. MMRESULT err;
  96. if (my err == MMSYSERR_NOERROR) return;
  97. err = waveInGetErrorText (my err, messageText, MAXERRORLENGTH);
  98. if (err == MMSYSERR_NOERROR) Melder_throw (Melder_peekWto32 (messageText));
  99. else if (err == MMSYSERR_BADERRNUM) Melder_throw (U"Error number ", my err, U" out of range.");
  100. else if (err == MMSYSERR_NODRIVER) Melder_throw (U"No sound driver present.");
  101. else if (err == MMSYSERR_NOMEM) Melder_throw (U"Out of memory.");
  102. else Melder_throw (U"Unknown sound error.");
  103. }
  104. static void win_waveInOpen (SoundRecorder me) {
  105. try {
  106. my err = waveInOpen (& my hWaveIn, WAVE_MAPPER, & my waveFormat, 0, 0, CALLBACK_NULL);
  107. win_waveInCheck (me);
  108. if (Melder_debug != 8) waveInReset (my hWaveIn);
  109. } catch (MelderError) {
  110. Melder_throw (U"Audio input not opened.");
  111. }
  112. }
  113. static void win_waveInPrepareHeader (SoundRecorder me, int which) {
  114. try {
  115. my err = waveInPrepareHeader (my hWaveIn, & my waveHeader [which], sizeof (WAVEHDR));
  116. win_waveInCheck (me);
  117. } catch (MelderError) {
  118. Melder_throw (U"Audio input: cannot prepare header.\nQuit some other programs or go to \"Sound input prefs\" in the Preferences menu.");
  119. }
  120. }
  121. static void win_waveInAddBuffer (SoundRecorder me, int which) {
  122. try {
  123. my err = waveInAddBuffer (my hWaveIn, & my waveHeader [which], sizeof (WAVEHDR));
  124. win_waveInCheck (me);
  125. } catch (MelderError) {
  126. Melder_throw (U"Audio input: cannot add buffer.");
  127. }
  128. }
  129. static void win_waveInStart (SoundRecorder me) {
  130. try {
  131. my err = waveInStart (my hWaveIn); // asynchronous
  132. win_waveInCheck (me);
  133. } catch (MelderError) {
  134. Melder_throw (U"Audio input not started.");
  135. }
  136. }
  137. static void win_waveInStop (SoundRecorder me) {
  138. try {
  139. my err = waveInStop (my hWaveIn);
  140. win_waveInCheck (me);
  141. } catch (MelderError) {
  142. Melder_throw (U"Audio input not stopped.");
  143. }
  144. }
  145. static void win_waveInReset (SoundRecorder me) {
  146. try {
  147. my err = waveInReset (my hWaveIn);
  148. win_waveInCheck (me);
  149. } catch (MelderError) {
  150. Melder_throw (U"Audio input not reset.");
  151. }
  152. }
  153. static void win_waveInUnprepareHeader (SoundRecorder me, int which) {
  154. try {
  155. my err = waveInUnprepareHeader (my hWaveIn, & my waveHeader [which], sizeof (WAVEHDR));
  156. win_waveInCheck (me);
  157. } catch (MelderError) {
  158. Melder_throw (U"Audio input: cannot unprepare header.");
  159. }
  160. }
  161. static void win_waveInClose (SoundRecorder me) {
  162. try {
  163. my err = waveInClose (my hWaveIn);
  164. my hWaveIn = 0;
  165. win_waveInCheck (me);
  166. } catch (MelderError) {
  167. Melder_throw (U"Audio input not closed.");
  168. }
  169. }
  170. #endif
  171. static void stopRecording (SoundRecorder me) {
  172. if (! my recording) return;
  173. try {
  174. my recording = false;
  175. if (! my synchronous) {
  176. if (my inputUsesPortAudio) {
  177. Pa_StopStream (my portaudioStream);
  178. Pa_CloseStream (my portaudioStream);
  179. my portaudioStream = nullptr;
  180. } else {
  181. #if defined (_WIN32)
  182. /*
  183. * On newer systems, waveInStop waits until the buffer is full.
  184. * Wrong behaviour!
  185. * Therefore, we call waveInReset instead.
  186. * But on these same newer systems, waveInReset causes the dwBytesRecorded
  187. * attribute to go to zero, so we cannot do
  188. * my nsamp = my waveHeader [0]. dwBytesRecorded / (sizeof (short) * my numberOfChannels);
  189. */
  190. MMTIME mmtime;
  191. mmtime. wType = TIME_BYTES;
  192. my nsamp = 0;
  193. if (waveInGetPosition (my hWaveIn, & mmtime, sizeof (MMTIME)) == MMSYSERR_NOERROR)
  194. my nsamp = mmtime. u.cb / (sizeof (short) * my numberOfChannels);
  195. win_waveInReset (me);
  196. if (my nsamp == 0)
  197. my nsamp = my waveHeader [0]. dwBytesRecorded / (sizeof (short) * my numberOfChannels);
  198. if (my nsamp > my nmax)
  199. my nsamp = my nmax;
  200. win_waveInUnprepareHeader (me, 0);
  201. win_waveInClose (me);
  202. #endif
  203. }
  204. }
  205. } catch (MelderError) {
  206. Melder_flushError (U"Cannot stop recording.");
  207. }
  208. Graphics_setWindow (my graphics.get(), 0.0, 1.0, 0.0, 1.0);
  209. Graphics_setColour (my graphics.get(), Graphics_WHITE);
  210. Graphics_fillRectangle (my graphics.get(), 0.0, 1.0, 0.0, 1.0);
  211. }
  212. void structSoundRecorder :: v_destroy () noexcept {
  213. stopRecording (this); // must occur before freeing our buffer
  214. MelderAudio_stopPlaying (MelderAudio_IMPLICIT); // must also occur before freeing our buffer
  215. #if cocoa
  216. if (our d_cocoaTimer) CFRunLoopTimerInvalidate (our d_cocoaTimer);
  217. #elif gtk
  218. g_idle_remove_by_data (this);
  219. #elif motif
  220. if (our workProcId) XtRemoveWorkProc (our workProcId);
  221. #endif
  222. NUMvector_free (our buffer, 0);
  223. if (our inputUsesPortAudio) {
  224. if (our portaudioStream) Pa_StopStream (our portaudioStream);
  225. if (our portaudioStream) Pa_CloseStream (our portaudioStream);
  226. } else {
  227. #if defined (_WIN32)
  228. if (our hWaveIn != 0) {
  229. waveInReset (our hWaveIn);
  230. waveInUnprepareHeader (our hWaveIn, & our waveHeader [0], sizeof (WAVEHDR));
  231. waveInClose (our hWaveIn);
  232. }
  233. #elif defined (macintosh)
  234. #elif defined (UNIX)
  235. if (our fd != -1) close (our fd);
  236. #endif
  237. }
  238. our SoundRecorder_Parent :: v_destroy ();
  239. }
  240. static void showMaximum (SoundRecorder me, int channel, double maximum) {
  241. maximum /= 32768.0;
  242. Graphics_setWindow (my graphics.get(),
  243. my numberOfChannels == 1 || channel == 1 ? 0.0 : -1.0,
  244. my numberOfChannels == 1 || channel == 2 ? 1.0 : 2.0,
  245. -0.1, 1.1);
  246. Graphics_setGrey (my graphics.get(), 0.9);
  247. Graphics_fillRectangle (my graphics.get(), 0.0, 1.0, maximum, 1.0);
  248. Graphics_setColour (my graphics.get(), Graphics_GREEN);
  249. if (maximum < 0.75) {
  250. Graphics_fillRectangle (my graphics.get(), 0.0, 1.0, 0.0, maximum);
  251. } else {
  252. Graphics_fillRectangle (my graphics.get(), 0.0, 1.0, 0.0, 0.75);
  253. Graphics_setColour (my graphics.get(), Graphics_YELLOW);
  254. if (maximum < 0.92) {
  255. Graphics_fillRectangle (my graphics.get(), 0.0, 1.0, 0.75, maximum);
  256. } else {
  257. Graphics_fillRectangle (my graphics.get(), 0.0, 1.0, 0.75, 0.92);
  258. Graphics_setColour (my graphics.get(), Graphics_RED);
  259. Graphics_fillRectangle (my graphics.get(), 0.0, 1.0, 0.92, maximum);
  260. }
  261. }
  262. }
  263. static void showMeter (SoundRecorder me, short *buffer, integer nsamp) {
  264. Melder_assert (my graphics);
  265. if (nsamp < 1) {
  266. Graphics_setWindow (my graphics.get(), 0.0, 1.0, 0.0, 1.0);
  267. #if defined (macintosh)
  268. Graphics_setColour (my graphics.get(), Graphics_WHITE);
  269. Graphics_fillRectangle (my graphics.get(), 0.2, 0.8, 0.3, 0.7);
  270. #endif
  271. Graphics_setTextAlignment (my graphics.get(), Graphics_CENTRE, Graphics_HALF);
  272. Graphics_setColour (my graphics.get(), Graphics_BLACK);
  273. Graphics_text (my graphics.get(), 0.5, 0.5, U"Not recording.");
  274. return;
  275. }
  276. if (my p_meter_which == kSoundRecorder_meter::INTENSITY) {
  277. int leftMaximum = 0, rightMaximum = 0;
  278. if (my numberOfChannels == 1) {
  279. for (integer i = 0; i < nsamp; i ++) {
  280. int value = buffer [i];
  281. if (abs (value) > leftMaximum) leftMaximum = abs (value);
  282. }
  283. } else {
  284. for (integer i = 0; i < nsamp; i ++) {
  285. int left = buffer [i+i], right = buffer [i+i+1];
  286. if (abs (left) > leftMaximum) leftMaximum = abs (left);
  287. if (abs (right) > rightMaximum) rightMaximum = abs (right);
  288. }
  289. }
  290. if (my lastLeftMaximum > 30000) {
  291. int leak = my lastLeftMaximum - Melder_ifloor (2000000.0 / theControlPanel. sampleRate);
  292. if (leftMaximum < leak) leftMaximum = leak;
  293. }
  294. showMaximum (me, 1, leftMaximum);
  295. my lastLeftMaximum = leftMaximum;
  296. if (my numberOfChannels == 2) {
  297. if (my lastRightMaximum > 30000) {
  298. int leak = my lastRightMaximum - Melder_ifloor (2000000.0 / theControlPanel. sampleRate);
  299. if (rightMaximum < leak) rightMaximum = leak;
  300. }
  301. showMaximum (me, 2, rightMaximum);
  302. my lastRightMaximum = rightMaximum;
  303. }
  304. } else if (my p_meter_which == kSoundRecorder_meter::CENTRE_OF_GRAVITY_VERSUS_INTENSITY) {
  305. autoSound sound = Sound_create (my numberOfChannels,
  306. 0.0, nsamp / theControlPanel. sampleRate,
  307. nsamp, 1.0 / theControlPanel. sampleRate, 0.5 / theControlPanel. sampleRate);
  308. short *p = & buffer [0];
  309. for (integer isamp = 1; isamp <= nsamp; isamp ++) {
  310. for (integer ichan = 1; ichan <= my numberOfChannels; ichan ++) {
  311. sound -> z [ichan] [isamp] = * (p ++) / 32768.0;
  312. }
  313. }
  314. Sound_multiplyByWindow (sound.get(), kSound_windowShape::KAISER_2);
  315. double intensity = Sound_getIntensity_dB (sound.get());
  316. autoSpectrum spectrum = Sound_to_Spectrum (sound.get(), true);
  317. double centreOfGravity = Spectrum_getCentreOfGravity (spectrum.get(), 1.0);
  318. trace (nsamp, U" samples, intensity ", intensity, U" dB, centre of gravity ", centreOfGravity, U" Hz");
  319. Graphics_setWindow (my graphics.get(),
  320. my p_meter_centreOfGravity_minimum, my p_meter_centreOfGravity_maximum,
  321. my p_meter_intensity_minimum, my p_meter_intensity_maximum);
  322. Graphics_setColour (my graphics.get(), Graphics_WHITE);
  323. Graphics_fillRectangle (my graphics.get(),
  324. my p_meter_centreOfGravity_minimum, my p_meter_centreOfGravity_maximum,
  325. my p_meter_intensity_minimum, my p_meter_intensity_maximum);
  326. Graphics_setColour (my graphics.get(), Graphics_BLACK);
  327. Graphics_fillCircle_mm (my graphics.get(), centreOfGravity, intensity, 3.0);
  328. }
  329. Graphics_flushWs (my graphics.get());
  330. }
  331. static bool tooManySamplesInBufferToReturnToGui (SoundRecorder me) {
  332. (void) me;
  333. return false;
  334. }
  335. static integer getMyNsamp (SoundRecorder me) {
  336. volatile integer nsamp = my nsamp; // Prevent inlining.
  337. return nsamp;
  338. }
  339. #if cocoa
  340. #define WORKPROC_RETURN void
  341. #define WORKPROC_ARGS CFRunLoopTimerRef /*timer*/, void *void_SoundRecorder
  342. #elif gtk
  343. #define WORKPROC_RETURN gboolean
  344. #define WORKPROC_ARGS void *void_SoundRecorder
  345. #else
  346. #define WORKPROC_RETURN bool
  347. #define WORKPROC_ARGS void *void_SoundRecorder
  348. #endif
  349. static WORKPROC_RETURN workProc (WORKPROC_ARGS) {
  350. SoundRecorder me = static_cast <SoundRecorder> (void_SoundRecorder);
  351. try {
  352. short buffertje [step*2];
  353. int stepje = 0;
  354. #if defined (linux)
  355. #define min(a,b) a > b ? b : a
  356. #endif
  357. /* Determine global audio parameters (may have been changed by an external control panel):
  358. * 1. input source;
  359. * 2. left and right gain;
  360. * 3. sampling frequency.
  361. */
  362. if (my inputUsesPortAudio) {
  363. } else {
  364. }
  365. /* Set the buttons according to the audio parameters. */
  366. if (my recordButton) GuiThing_setSensitive (my recordButton, ! my recording);
  367. if (my stopButton) GuiThing_setSensitive (my stopButton, my recording);
  368. if (my playButton) GuiThing_setSensitive (my playButton, ! my recording && my nsamp > 0);
  369. if (my applyButton) GuiThing_setSensitive (my applyButton, ! my recording && my nsamp > 0);
  370. if (my okButton) GuiThing_setSensitive (my okButton, ! my recording && my nsamp > 0);
  371. if (my monoButton && my numberOfChannels == 1) GuiRadioButton_set (my monoButton);
  372. if (my stereoButton && my numberOfChannels == 2) GuiRadioButton_set (my stereoButton);
  373. for (integer i = 1; i <= SoundRecorder_IFSAMP_MAX; i ++)
  374. if (my fsamps [i]. button && theControlPanel. sampleRate == my fsamps [i]. fsamp)
  375. GuiRadioButton_set (my fsamps [i]. button);
  376. if (my devices [theControlPanel. inputSource]. button)
  377. GuiRadioButton_set (my devices [theControlPanel. inputSource]. button);
  378. if (my monoButton) GuiThing_setSensitive (my monoButton, ! my recording);
  379. if (my stereoButton) GuiThing_setSensitive (my stereoButton, ! my recording);
  380. for (integer i = 1; i <= SoundRecorder_IFSAMP_MAX; i ++)
  381. if (my fsamps [i]. button) {
  382. GuiThing_setSensitive (my fsamps [i]. button, ! my recording);
  383. }
  384. for (integer i = 1; i <= SoundRecorder_IDEVICE_MAX; i ++)
  385. if (my devices [i]. button)
  386. GuiThing_setSensitive (my devices [i]. button, ! my recording);
  387. /*Graphics_setGrey (my graphics, 0.9);
  388. Graphics_fillRectangle (my graphics, 0.0, 1.0, 0.0, 32768.0);
  389. Graphics_setGrey (my graphics, 0.9);
  390. Graphics_fillRectangle (my graphics, 0.0, 1.0, 0.0, 32768.0);*/
  391. if (my synchronous) {
  392. /*
  393. * Read some samples into 'buffertje'.
  394. */
  395. do {
  396. if (my inputUsesPortAudio) {
  397. /*
  398. * Asynchronous recording: do nothing.
  399. */
  400. } else {
  401. #if defined (macintosh) || defined (_WIN32)
  402. /*
  403. * Asynchronous recording on these systems: do nothing.
  404. */
  405. #else
  406. // linux
  407. if (my fd != -1)
  408. stepje = read (my fd, (void *) buffertje, step * (sizeof (short) * my numberOfChannels)) / (sizeof (short) * my numberOfChannels);
  409. #endif
  410. }
  411. if (my recording) {
  412. memcpy (my buffer + my nsamp * my numberOfChannels, buffertje, stepje * (sizeof (short) * my numberOfChannels));
  413. }
  414. showMeter (me, buffertje, stepje);
  415. if (my recording) {
  416. my nsamp += stepje;
  417. if (my nsamp > my nmax - step) my recording = false;
  418. GuiScale_setValue (my progressScale, 1000.0 * ((double) my nsamp / (double) my nmax));
  419. }
  420. } while (my recording && tooManySamplesInBufferToReturnToGui (me));
  421. } else {
  422. if (my recording) {
  423. /*
  424. * We have to know how far the buffer has been filled.
  425. * However, the buffer may be filled at interrupt time,
  426. * so that the buffer may be being filled during this workproc.
  427. * So we ask for the buffer filling just once, namely here at the beginning.
  428. */
  429. integer lastSample = 0;
  430. if (my inputUsesPortAudio) {
  431. /*
  432. * The buffer filling is contained in my nsamp,
  433. * which has been set during interrupt time and may again be updated behind our backs during this workproc.
  434. * So we do it in such a way that the compiler cannot ask for my nsamp twice.
  435. */
  436. lastSample = getMyNsamp (me);
  437. Pa_Sleep (10);
  438. } else {
  439. #if defined (_WIN32)
  440. MMTIME mmtime;
  441. mmtime. wType = TIME_BYTES;
  442. if (waveInGetPosition (my hWaveIn, & mmtime, sizeof (MMTIME)) == MMSYSERR_NOERROR)
  443. lastSample = mmtime. u.cb / (sizeof (short) * my numberOfChannels);
  444. #elif defined (macintosh)
  445. #endif
  446. }
  447. integer firstSample = lastSample - 3000;
  448. if (firstSample < 0) firstSample = 0;
  449. showMeter (me, my buffer + firstSample * my numberOfChannels, lastSample - firstSample);
  450. GuiScale_setValue (my progressScale, 1000.0 * ((double) lastSample / (double) my nmax));
  451. } else {
  452. showMeter (me, nullptr, 0);
  453. }
  454. }
  455. } catch (MelderError) {
  456. Melder_flushError ();
  457. }
  458. #if cocoa
  459. return;
  460. #elif gtk
  461. return true;
  462. #else
  463. return false;
  464. #endif
  465. }
  466. static int portaudioStreamCallback (
  467. const void *input, void * /* output */,
  468. unsigned long frameCount,
  469. const PaStreamCallbackTimeInfo* /* timeInfo */,
  470. PaStreamCallbackFlags /* statusFlags */,
  471. void *void_SoundRecorder)
  472. {
  473. /*
  474. * This procedure may be called at interrupt time.
  475. * It therefore accesses only data that is constant during recording,
  476. * namely me, my buffer, my numberOfChannels, and my nmax.
  477. * The only thing it changes is my nsamp;
  478. * the workProc will therefore have to take some care in accessing my nsamp (see there).
  479. */
  480. SoundRecorder me = static_cast <SoundRecorder> (void_SoundRecorder);
  481. if (Melder_debug == 20)
  482. Melder_casual (U"The PortAudio stream callback receives ", frameCount, U" frames.");
  483. Melder_assert (my nsamp <= my nmax);
  484. uinteger samplesLeft = my nmax - my nsamp;
  485. if (samplesLeft > 0) {
  486. uinteger dsamples = samplesLeft > frameCount ? frameCount : samplesLeft;
  487. if (Melder_debug == 20)
  488. Melder_casual (U"play ", dsamples, U" ", Pa_GetStreamCpuLoad (my portaudioStream));
  489. memcpy (my buffer + my nsamp * my numberOfChannels, input, 2 * dsamples * my numberOfChannels);
  490. my nsamp += dsamples;
  491. if (my nsamp >= my nmax) return paComplete;
  492. } else /*if (my nsamp >= my nmax)*/ {
  493. my nsamp = my nmax;
  494. return paComplete;
  495. }
  496. return paContinue;
  497. }
  498. static void gui_button_cb_record (SoundRecorder me, GuiButtonEvent /* event */) {
  499. try {
  500. if (my recording) return;
  501. my nsamp = 0;
  502. my recording = true;
  503. my lastLeftMaximum = 0;
  504. my lastRightMaximum = 0;
  505. if (! my synchronous) {
  506. if (my inputUsesPortAudio) {
  507. PaStreamParameters streamParameters = { };
  508. streamParameters. device = my deviceIndices [theControlPanel. inputSource];
  509. streamParameters. channelCount = my numberOfChannels;
  510. streamParameters. sampleFormat = paInt16;
  511. streamParameters. suggestedLatency = my deviceInfos [theControlPanel. inputSource] -> defaultLowInputLatency;
  512. #if defined (macintosh)
  513. PaMacCoreStreamInfo macCoreStreamInfo = { };
  514. macCoreStreamInfo. size = sizeof (PaMacCoreStreamInfo);
  515. macCoreStreamInfo. hostApiType = paCoreAudio;
  516. macCoreStreamInfo. version = 0x01;
  517. macCoreStreamInfo. flags = paMacCoreChangeDeviceParameters | paMacCoreFailIfConversionRequired;
  518. streamParameters. hostApiSpecificStreamInfo = & macCoreStreamInfo;
  519. #endif
  520. if (Melder_debug == 20)
  521. Melder_casual (U"Before Pa_OpenStream");
  522. PaError err = Pa_OpenStream (& my portaudioStream, & streamParameters, nullptr,
  523. theControlPanel. sampleRate, 0, paNoFlag, portaudioStreamCallback, (void *) me);
  524. if (Melder_debug == 20)
  525. Melder_casual (U"Pa_OpenStream returns ", (int) err);
  526. if (err)
  527. Melder_throw (U"open ", Melder_peek8to32 (Pa_GetErrorText (err)));
  528. Pa_StartStream (my portaudioStream);
  529. if (Melder_debug == 20)
  530. Melder_casual (U"Pa_StartStream returns ", (int) err);
  531. if (err)
  532. Melder_throw (U"start ", Melder_peek8to32 (Pa_GetErrorText (err)));
  533. } else {
  534. #if defined (_WIN32)
  535. win_fillFormat (me);
  536. win_fillHeader (me, 0);
  537. win_waveInOpen (me);
  538. win_waveInPrepareHeader (me, 0);
  539. win_waveInAddBuffer (me, 0);
  540. win_waveInStart (me);
  541. #elif defined (macintosh)
  542. #endif
  543. }
  544. }
  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. } catch (MelderError) {
  549. Graphics_setWindow (my graphics.get(), 0.0, 1.0, 0.0, 1.0);
  550. Graphics_setColour (my graphics.get(), Graphics_WHITE);
  551. Graphics_fillRectangle (my graphics.get(), 0.0, 1.0, 0.0, 1.0);
  552. my recording = false;
  553. Melder_flushError (U"Cannot record.");
  554. }
  555. }
  556. static void gui_button_cb_stop (SoundRecorder me, GuiButtonEvent /* event */) {
  557. stopRecording (me);
  558. }
  559. static void gui_button_cb_play (SoundRecorder me, GuiButtonEvent /* event */) {
  560. if (my recording || my nsamp == 0) return;
  561. MelderAudio_play16 (my buffer, theControlPanel. sampleRate, my fakeMono ? my nsamp / 2 : my nsamp, my fakeMono ? 2 : my numberOfChannels, nullptr, nullptr);
  562. }
  563. static void publish (SoundRecorder me) {
  564. autoSound sound;
  565. integer nsamp = my fakeMono ? my nsamp / 2 : my nsamp;
  566. if (my nsamp == 0) return;
  567. double fsamp = theControlPanel. sampleRate;
  568. if (fsamp <= 0.0) fsamp = 44100.0; // safe
  569. try {
  570. sound = Sound_createSimple (my numberOfChannels, (double) nsamp / fsamp, fsamp);
  571. } catch (MelderError) {
  572. Melder_flushError (U"You can still save to file.");
  573. return;
  574. }
  575. if (my fakeMono) {
  576. for (integer i = 1; i <= nsamp; i ++)
  577. sound -> z [1] [i] = (my buffer [i + i - 2] + my buffer [i + i - 1]) * (1.0 / 65536);
  578. } else if (my numberOfChannels == 1) {
  579. for (integer i = 1; i <= nsamp; i ++)
  580. sound -> z [1] [i] = my buffer [i - 1] * (1.0 / 32768);
  581. } else {
  582. for (integer i = 1; i <= nsamp; i ++) {
  583. sound -> z [1] [i] = my buffer [i + i - 2] * (1.0 / 32768);
  584. sound -> z [2] [i] = my buffer [i + i - 1] * (1.0 / 32768);
  585. }
  586. }
  587. if (my soundName) {
  588. autostring32 name = GuiText_getString (my soundName);
  589. Thing_setName (sound.get(), name.get());
  590. }
  591. Editor_broadcastPublication (me, sound.move());
  592. }
  593. static void gui_button_cb_cancel (SoundRecorder me, GuiButtonEvent /* event */) {
  594. stopRecording (me);
  595. forget (me);
  596. }
  597. static void gui_button_cb_apply (SoundRecorder me, GuiButtonEvent /* event */) {
  598. stopRecording (me);
  599. publish (me);
  600. }
  601. static void gui_button_cb_ok (SoundRecorder me, GuiButtonEvent /* event */) {
  602. stopRecording (me);
  603. publish (me);
  604. forget (me);
  605. }
  606. static void initialize (SoundRecorder me) {
  607. try {
  608. if (my inputUsesPortAudio) {
  609. #if defined (macintoshXXX)
  610. my fsamps [SoundRecorder_IFSAMP_8000]. canDo = false;
  611. my fsamps [SoundRecorder_IFSAMP_11025]. canDo = false;
  612. my fsamps [SoundRecorder_IFSAMP_12000]. canDo = false;
  613. my fsamps [SoundRecorder_IFSAMP_16000]. canDo = false;
  614. my fsamps [SoundRecorder_IFSAMP_22050]. canDo = false;
  615. my fsamps [SoundRecorder_IFSAMP_24000]. canDo = false;
  616. my fsamps [SoundRecorder_IFSAMP_32000]. canDo = false;
  617. my fsamps [SoundRecorder_IFSAMP_64000]. canDo = false;
  618. #else
  619. // Accept all standard sample rates.
  620. (void) me;
  621. #endif
  622. } else {
  623. #if defined (macintosh)
  624. #elif defined (_WIN32)
  625. (void) me;
  626. #elif defined (linux) && ! defined (NO_AUDIO)
  627. int sampleRate = (int) theControlPanel. sampleRate, sampleSize = 16;
  628. int channels = my numberOfChannels, stereo = ( my numberOfChannels == 2 ), val;
  629. #if __BYTE_ORDER == __BIG_ENDIAN
  630. int format = AFMT_S16_BE;
  631. #else
  632. int format = AFMT_S16_LE;
  633. #endif
  634. int fd_mixer;
  635. my fd = open ("/dev/dsp", O_RDONLY);
  636. if (my fd == -1) {
  637. if (errno == EBUSY)
  638. Melder_throw (U"Audio device already in use.");
  639. else
  640. Melder_throw (U"Cannot open audio device.\n"
  641. U"Please switch on PortAudio in the Sound Recording Preferences.");
  642. }
  643. ioctl (my fd, SNDCTL_DSP_RESET, nullptr);
  644. ioctl (my fd, SNDCTL_DSP_SPEED, & sampleRate);
  645. ioctl (my fd, SNDCTL_DSP_SAMPLESIZE, & sampleSize);
  646. ioctl (my fd, SNDCTL_DSP_CHANNELS, (val = channels, & val));
  647. if (channels == 1 && val == 2) {
  648. close (my fd);
  649. Melder_throw (U"This sound card does not support mono.");
  650. }
  651. ioctl (my fd, SNDCTL_DSP_STEREO, & stereo);
  652. ioctl (my fd, SNDCTL_DSP_SETFMT, & format);
  653. fd_mixer = open ("/dev/mixer", O_WRONLY);
  654. if (fd_mixer == -1) {
  655. Melder_throw (U"Cannot open /dev/mixer.");
  656. } else {
  657. int dev_mask = theControlPanel. inputSource == 2 ? SOUND_MASK_LINE : SOUND_MASK_MIC;
  658. if (ioctl (fd_mixer, SOUND_MIXER_WRITE_RECSRC, & dev_mask) == -1) {
  659. close (fd_mixer);
  660. Melder_throw (U"Can't set recording device in mixer.");
  661. }
  662. close (fd_mixer);
  663. }
  664. #endif
  665. }
  666. } catch (MelderError) {
  667. Melder_throw (U"16-bit audio recording not initialized.");
  668. }
  669. }
  670. static void gui_radiobutton_cb_input (SoundRecorder me, GuiRadioButtonEvent event) {
  671. Melder_casual (U"SoundRecorder:"
  672. U" setting the input source from ", theControlPanel. inputSource,
  673. U" to ", event -> position, U".");
  674. theControlPanel. inputSource = event -> position;
  675. /* Set system's input source. */
  676. if (my inputUsesPortAudio) {
  677. // deferred to the start of recording
  678. } else {
  679. #if defined (_WIN32)
  680. // deferred to the start of recording
  681. #elif defined (macintosh)
  682. //SPBCloseDevice (my refNum);
  683. try {
  684. initialize (me);
  685. } catch (MelderError) {
  686. Melder_flushError ();
  687. }
  688. #elif defined (linux) && ! defined (NO_AUDIO)
  689. int fd_mixer = open ("/dev/mixer", O_WRONLY);
  690. if (fd_mixer == -1) {
  691. Melder_flushError (U"(Sound_record:) Cannot open /dev/mixer.");
  692. }
  693. int dev_mask = theControlPanel.inputSource == 2 ? SOUND_MASK_LINE : SOUND_MASK_MIC;
  694. if (ioctl (fd_mixer, SOUND_MIXER_WRITE_RECSRC, & dev_mask) == -1)
  695. Melder_flushError (U"(Sound_record:) Can't set recording device in mixer");
  696. close (fd_mixer);
  697. #endif
  698. }
  699. }
  700. static void gui_radiobutton_cb_fsamp (SoundRecorder me, GuiRadioButtonEvent event) {
  701. if (my recording) return;
  702. try {
  703. double fsamp = undefined;
  704. for (integer i = 1; i <= SoundRecorder_IFSAMP_MAX; i ++)
  705. if (event -> toggle == my fsamps [i]. button)
  706. fsamp = my fsamps [i]. fsamp;
  707. Melder_assert (isdefined (fsamp));
  708. /*
  709. * If we push the 48000 button while the sampling frequency is 22050,
  710. * we first get a message that the 22050 button has changed,
  711. * and then we get a message that the 48000 button has changed.
  712. * So the following will work (it used to be different with old Motif versions on Linux):
  713. */
  714. Melder_casual (U"SoundRecorder:"
  715. U" setting the sample rate from ", (integer) theControlPanel. sampleRate,
  716. U" to ", (integer) fsamp, U" Hz.");
  717. if (fsamp == theControlPanel. sampleRate) return;
  718. /*
  719. * Now we know, hopefully, that the message is from the button that was clicked,
  720. * not the one that was unset by the radio box, so we can take action.
  721. */
  722. theControlPanel. sampleRate = fsamp;
  723. /*
  724. * Set the system's sampling frequency.
  725. * On some systems, we cannot do this without closing the audio device,
  726. * and reopening it with a new sampling frequency.
  727. */
  728. if (my inputUsesPortAudio) {
  729. // deferred to the start of recording
  730. } else {
  731. #if defined (_WIN32)
  732. // deferred to the start of recording
  733. #elif defined (macintosh)
  734. //SPBCloseDevice (my refNum);
  735. initialize (me);
  736. #elif defined (linux) && ! defined (NO_AUDIO)
  737. close (my fd);
  738. initialize (me);
  739. #endif
  740. }
  741. } catch (MelderError) {
  742. Melder_throw (U"Sampling frequency not changed.");
  743. }
  744. }
  745. static void gui_drawingarea_cb_resize (SoundRecorder me, GuiDrawingArea_ResizeEvent event) {
  746. if (! my graphics) return; // could be the case in the very beginning
  747. Graphics_setWsViewport (my graphics.get(), 0, event -> width, 0, event -> height);
  748. Graphics_setWsWindow (my graphics.get(), 0, event -> width, 0, event -> height);
  749. Graphics_setViewport (my graphics.get(), 0, event -> width, 0, event -> height);
  750. Graphics_updateWs (my graphics.get());
  751. }
  752. void structSoundRecorder :: v_createChildren ()
  753. {
  754. /* Channels */
  755. integer y = 20 + Machine_getMenuBarHeight ();
  756. GuiLabel_createShown (our windowForm, 10, 160, y, y + Gui_LABEL_HEIGHT, U"Channels:", 0);
  757. GuiRadioGroup_begin ();
  758. y += Gui_RADIOBUTTON_HEIGHT + Gui_RADIOBUTTON_SPACING;
  759. our monoButton = GuiRadioButton_createShown (our windowForm, 20, 170, y, y + Gui_RADIOBUTTON_HEIGHT,
  760. U"Mono", nullptr, nullptr, 0);
  761. y += Gui_RADIOBUTTON_HEIGHT + Gui_RADIOBUTTON_SPACING;
  762. our stereoButton = GuiRadioButton_createShown (our windowForm, 20, 170, y, y + Gui_RADIOBUTTON_HEIGHT,
  763. U"Stereo", nullptr, nullptr, 0);
  764. GuiRadioGroup_end ();
  765. /* Input source */
  766. y = 140 + Machine_getMenuBarHeight ();
  767. #if defined (_WIN32)
  768. GuiLabel_createShown (our windowForm, 10, 170, y, y + Gui_LABEL_HEIGHT, U"(use Windows mixer", 0);
  769. y += Gui_LABEL_HEIGHT + 10;
  770. GuiLabel_createShown (our windowForm, 10, 170, y, y + Gui_LABEL_HEIGHT, U" without meters)", 0);
  771. #else
  772. GuiLabel_createShown (our windowForm, 10, 170, y, y + Gui_LABEL_HEIGHT, U"Input source:", 0);
  773. GuiRadioGroup_begin ();
  774. for (integer i = 1; i <= SoundRecorder_IDEVICE_MAX; i ++) {
  775. if (our devices [i]. canDo) {
  776. y += Gui_RADIOBUTTON_HEIGHT + Gui_RADIOBUTTON_SPACING;
  777. our devices [i]. button = GuiRadioButton_createShown (our windowForm, 20, 170, y, y + Gui_RADIOBUTTON_HEIGHT,
  778. our devices [i]. name, gui_radiobutton_cb_input, this, 0);
  779. }
  780. }
  781. GuiRadioGroup_end ();
  782. #endif
  783. /* Meter box */
  784. y = 20 + Machine_getMenuBarHeight ();
  785. GuiLabel_createShown (our windowForm, 170, -170, y, y + Gui_LABEL_HEIGHT, U"Meter", GuiLabel_CENTRE);
  786. y += Gui_LABEL_HEIGHT;
  787. our meter = GuiDrawingArea_createShown (our windowForm, 170, -170, y, -150,
  788. nullptr, nullptr, nullptr, gui_drawingarea_cb_resize, this, GuiDrawingArea_BORDER);
  789. /* Sampling frequency */
  790. y = 20 + Machine_getMenuBarHeight ();
  791. GuiLabel_createShown (our windowForm, -160, -10, y, y + Gui_LABEL_HEIGHT, U"Sampling frequency:", 0);
  792. GuiRadioGroup_begin ();
  793. for (integer i = 1; i <= SoundRecorder_IFSAMP_MAX; i ++) {
  794. if (our fsamps [i]. canDo) {
  795. double fsamp = our fsamps [i]. fsamp;
  796. y += Gui_RADIOBUTTON_HEIGHT + Gui_RADIOBUTTON_SPACING;
  797. our fsamps [i]. button = GuiRadioButton_createShown (our windowForm,
  798. -150, -10, y, y + Gui_RADIOBUTTON_HEIGHT,
  799. Melder_cat (fsamp == Melder_roundDown (fsamp) ? Melder_integer ((integer) fsamp) : Melder_fixed (fsamp, 5), U" Hz"),
  800. gui_radiobutton_cb_fsamp, this, fsamp == theControlPanel. sampleRate ? GuiRadioButton_SET : 0);
  801. }
  802. }
  803. GuiRadioGroup_end ();
  804. our progressScale = GuiScale_createShown (our windowForm,
  805. 10, 350, -130, -90,
  806. 0, 1000, 0, 0);
  807. y = 60;
  808. our recordButton = GuiButton_createShown (our windowForm, 20, 90, -y - Gui_PUSHBUTTON_HEIGHT, -y,
  809. U"Record", gui_button_cb_record, this, 0);
  810. our stopButton = GuiButton_createShown (our windowForm, 100, 170, -y - Gui_PUSHBUTTON_HEIGHT, -y,
  811. U"Stop", gui_button_cb_stop, this, 0);
  812. if (inputUsesPortAudio) {
  813. our playButton = GuiButton_createShown (our windowForm, 180, 250, -y - Gui_PUSHBUTTON_HEIGHT, -y,
  814. U"Play", gui_button_cb_play, this, 0);
  815. } else {
  816. #if defined (_WIN32) || defined (macintosh)
  817. our playButton = GuiButton_createShown (our windowForm, 180, 250, -y - Gui_PUSHBUTTON_HEIGHT, -y,
  818. U"Play", gui_button_cb_play, this, 0);
  819. #endif
  820. }
  821. GuiLabel_createShown (our windowForm, -200, -130, -y - 2 - Gui_TEXTFIELD_HEIGHT, -y - 2, U"Name:", GuiLabel_RIGHT);
  822. our soundName = GuiText_createShown (our windowForm, -120, -20, -y - 2 - Gui_TEXTFIELD_HEIGHT, -y - 2, 0);
  823. GuiText_setString (our soundName, U"untitled");
  824. y = 20;
  825. our cancelButton = GuiButton_createShown (our windowForm, -350, -280, -y - Gui_PUSHBUTTON_HEIGHT, -y,
  826. U"Close", gui_button_cb_cancel, this, 0);
  827. our applyButton = GuiButton_createShown (our windowForm, -270, -170, -y - Gui_PUSHBUTTON_HEIGHT, -y,
  828. U"Save to list", gui_button_cb_apply, this, GuiButton_DEFAULT);
  829. our okButton = GuiButton_createShown (our windowForm, -160, -20, -y - Gui_PUSHBUTTON_HEIGHT, -y,
  830. U"Save to list & Close", gui_button_cb_ok, this, 0);
  831. }
  832. static void writeFakeMonoFile (SoundRecorder me, MelderFile file, int audioFileType) {
  833. integer nsamp = my nsamp / 2;
  834. autoMelderFile mfile = MelderFile_create (file);
  835. MelderFile_writeAudioFileHeader (file, audioFileType, Melder_iround (theControlPanel. sampleRate), nsamp, 1, 16);
  836. if (Melder_defaultAudioFileEncoding (audioFileType, 16) == Melder_LINEAR_16_BIG_ENDIAN) {
  837. for (integer i = 0; i < nsamp; i ++)
  838. binputi16 ((my buffer [i + i - 2] + my buffer [i + i - 1]) / 2, file -> filePointer);
  839. } else {
  840. for (integer i = 0; i < nsamp; i ++)
  841. binputi16LE ((my buffer [i + i - 2] + my buffer [i + i - 1]) / 2, file -> filePointer);
  842. }
  843. MelderFile_writeAudioFileTrailer (file, audioFileType, Melder_iround (theControlPanel. sampleRate), nsamp, 1, 16);
  844. mfile.close ();
  845. }
  846. static void writeAudioFile (SoundRecorder me, MelderFile file, int audioFileType) {
  847. try {
  848. if (my fakeMono) {
  849. writeFakeMonoFile (me, file, audioFileType);
  850. } else {
  851. MelderFile_writeAudioFile (file, audioFileType, my buffer, Melder_iround (theControlPanel. sampleRate), my nsamp, my numberOfChannels, 16);
  852. }
  853. } catch (MelderError) {
  854. Melder_throw (U"Audio file not written.");
  855. }
  856. }
  857. static void menu_cb_writeWav (SoundRecorder me, EDITOR_ARGS_FORM) {
  858. EDITOR_FORM_SAVE (U"Save as WAV file", nullptr)
  859. autostring32 name = GuiText_getString (my soundName);
  860. Melder_sprint (defaultName,300, name.get(), U".wav");
  861. EDITOR_DO_SAVE
  862. writeAudioFile (me, file, Melder_WAV);
  863. EDITOR_END
  864. }
  865. static void menu_cb_writeAifc (SoundRecorder me, EDITOR_ARGS_FORM) {
  866. EDITOR_FORM_SAVE (U"Save as AIFC file", nullptr)
  867. autostring32 name = GuiText_getString (my soundName);
  868. Melder_sprint (defaultName,300, name.get(), U".aifc");
  869. EDITOR_DO_SAVE
  870. writeAudioFile (me, file, Melder_AIFC);
  871. EDITOR_END
  872. }
  873. static void menu_cb_writeNextSun (SoundRecorder me, EDITOR_ARGS_FORM) {
  874. EDITOR_FORM_SAVE (U"Save as NeXT/Sun file", nullptr)
  875. autostring32 name = GuiText_getString (my soundName);
  876. Melder_sprint (defaultName,300, name.get(), U".au");
  877. EDITOR_DO_SAVE
  878. writeAudioFile (me, file, Melder_NEXT_SUN);
  879. EDITOR_END
  880. }
  881. static void menu_cb_writeNist (SoundRecorder me, EDITOR_ARGS_FORM) {
  882. EDITOR_FORM_SAVE (U"Save as NIST file", nullptr)
  883. autostring32 name = GuiText_getString (my soundName);
  884. Melder_sprint (defaultName,300, name.get(), U".nist");
  885. EDITOR_DO_SAVE
  886. writeAudioFile (me, file, Melder_NIST);
  887. EDITOR_END
  888. }
  889. static void updateMenus (SoundRecorder me) {
  890. GuiMenuItem_check (my meterIntensityButton,
  891. my p_meter_which == kSoundRecorder_meter::INTENSITY);
  892. GuiMenuItem_check (my meterCentreOfGravityVersusIntensityButton,
  893. my p_meter_which == kSoundRecorder_meter::CENTRE_OF_GRAVITY_VERSUS_INTENSITY);
  894. }
  895. static void menu_cb_intensity (SoundRecorder me, EDITOR_ARGS_DIRECT) {
  896. my pref_meter_which () = my p_meter_which = kSoundRecorder_meter::INTENSITY;
  897. updateMenus (me);
  898. }
  899. static void menu_cb_centreOfGravityVersusIntensity (SoundRecorder me, EDITOR_ARGS_DIRECT) {
  900. my pref_meter_which () = my p_meter_which = kSoundRecorder_meter::CENTRE_OF_GRAVITY_VERSUS_INTENSITY;
  901. updateMenus (me);
  902. }
  903. static void menu_cb_SoundRecorder_help (SoundRecorder, EDITOR_ARGS_DIRECT) { Melder_help (U"SoundRecorder"); }
  904. void structSoundRecorder :: v_createMenus () {
  905. SoundRecorder_Parent :: v_createMenus ();
  906. Editor_addCommand (this, U"File", U"Save as WAV file...", 0, menu_cb_writeWav);
  907. Editor_addCommand (this, U"File", U"Save as AIFC file...", 0, menu_cb_writeAifc);
  908. Editor_addCommand (this, U"File", U"Save as NeXT/Sun file...", 0, menu_cb_writeNextSun);
  909. Editor_addCommand (this, U"File", U"Save as NIST file...", 0, menu_cb_writeNist);
  910. Editor_addCommand (this, U"File", U"-- write --", 0, nullptr);
  911. Editor_addMenu (this, U"Meter", 0);
  912. our meterIntensityButton =
  913. Editor_addCommand (this, U"Meter", U"Intensity", GuiMenu_RADIO_FIRST, menu_cb_intensity);
  914. our meterCentreOfGravityVersusIntensityButton =
  915. Editor_addCommand (this, U"Meter", U"Centre of gravity ~ intensity", GuiMenu_RADIO_NEXT, menu_cb_centreOfGravityVersusIntensity);
  916. }
  917. void structSoundRecorder :: v_createHelpMenuItems (EditorMenu menu) {
  918. SoundRecorder_Parent :: v_createHelpMenuItems (menu);
  919. EditorMenu_addCommand (menu, U"SoundRecorder help", '?', menu_cb_SoundRecorder_help);
  920. }
  921. autoSoundRecorder SoundRecorder_create (int numberOfChannels) {
  922. try {
  923. autoSoundRecorder me = Thing_new (SoundRecorder);
  924. my inputUsesPortAudio =
  925. #if defined (_WIN32)
  926. MelderAudio_getInputSoundSystem () == kMelder_inputSoundSystem::MME_VIA_PORTAUDIO;
  927. #elif defined (macintosh)
  928. MelderAudio_getInputSoundSystem () == kMelder_inputSoundSystem::COREAUDIO_VIA_PORTAUDIO;
  929. #else
  930. MelderAudio_getInputSoundSystem () == kMelder_inputSoundSystem::ALSA_VIA_PORTAUDIO;
  931. #endif
  932. if (my inputUsesPortAudio) {
  933. } else {
  934. #if defined (_WIN32)
  935. UINT numberOfDevices = waveInGetNumDevs ();
  936. if (numberOfDevices == 0)
  937. Melder_throw (U"No sound input devices available.");
  938. WAVEINCAPS caps;
  939. MMRESULT err = waveInGetDevCaps (WAVE_MAPPER, & caps, sizeof (WAVEINCAPS));
  940. if (numberOfChannels == 2 && caps. wChannels < 2)
  941. Melder_throw (U"Your computer does not support stereo sound input.");
  942. /* BUG: should we ask whether 16 bit is supported? */
  943. for (UINT i = 0; i < numberOfDevices; i ++) {
  944. waveInGetDevCaps (i, & caps, sizeof (WAVEINCAPS));
  945. /*Melder_casual (U"Name of device ", i, U": ", Melder_peek16to32 (aps. szPname));*/
  946. }
  947. #elif defined (macintosh)
  948. SInt32 soundFeatures;
  949. if (Gestalt (gestaltSoundAttr, & soundFeatures) ||
  950. ! (soundFeatures & (1 << gestaltSoundIOMgrPresent)) ||
  951. ! (soundFeatures & (1 << gestaltBuiltInSoundInput)) ||
  952. ! (soundFeatures & (1 << gestaltHasSoundInputDevice)))
  953. Melder_throw (U"Your computer does not support sound input.");
  954. if (! (soundFeatures & (1 << gestalt16BitSoundIO)) || // hardware
  955. ! (soundFeatures & (1 << gestaltStereoInput)) || // hardware
  956. ! (soundFeatures & (1 << gestalt16BitAudioSupport))) // software
  957. Melder_throw (U"Your computer does not support stereo sound input.");
  958. #endif
  959. }
  960. my numberOfChannels = numberOfChannels;
  961. if (sizeof (short) != 2)
  962. Melder_throw (U"Long shorts!!!!!");
  963. if (my inputUsesPortAudio) {
  964. my synchronous = false;
  965. } else {
  966. #if defined (macintosh) || defined (_WIN32)
  967. my synchronous = false;
  968. #else
  969. my synchronous = true;
  970. #endif
  971. }
  972. /*
  973. * Allocate the maximum buffer.
  974. */
  975. if (preferences.bufferSizeInMegabytes < 1) preferences.bufferSizeInMegabytes = 1; // validate preferences
  976. if (preferences.bufferSizeInMegabytes > 1000) preferences.bufferSizeInMegabytes = 1000;
  977. if (! my buffer) {
  978. integer nmax_bytes_pref = preferences.bufferSizeInMegabytes * 1000000;
  979. integer nmax_bytes = my inputUsesPortAudio ? nmax_bytes_pref :
  980. #if defined (_WIN32)
  981. 66150000; // the maximum physical buffer on Windows XP; shorter than in Windows 98, alas.
  982. #else
  983. nmax_bytes_pref;
  984. #endif
  985. my nmax = nmax_bytes / (sizeof (short) * numberOfChannels);
  986. for (;;) {
  987. try {
  988. my buffer = NUMvector <short> (0, my nmax * numberOfChannels - 1);
  989. break; // success
  990. } catch (MelderError) {
  991. if (my nmax < 100000) {
  992. throw; // failure, with error message
  993. } else {
  994. Melder_clearError ();
  995. my nmax /= 2; // retry with less application memory
  996. }
  997. }
  998. }
  999. }
  1000. Melder_assert (my buffer);
  1001. /*
  1002. * Count the number of input devices and sources.
  1003. */
  1004. if (my inputUsesPortAudio) {
  1005. static bool paInitialized = false;
  1006. if (! paInitialized) {
  1007. PaError err = Pa_Initialize ();
  1008. if (Melder_debug == 20)
  1009. Melder_casual (U"init ", Melder_peek8to32 (Pa_GetErrorText (err)));
  1010. paInitialized = true;
  1011. if (Melder_debug == 20) {
  1012. PaHostApiIndex hostApiCount = Pa_GetHostApiCount ();
  1013. Melder_casual (U"host API count ", hostApiCount);
  1014. for (PaHostApiIndex iHostApi = 0; iHostApi < hostApiCount; iHostApi ++) {
  1015. const PaHostApiInfo *hostApiInfo = Pa_GetHostApiInfo (iHostApi);
  1016. PaHostApiTypeId type = hostApiInfo -> type;
  1017. Melder_casual (U"host API ", iHostApi, U": ", type, U", \"", Melder_peek8to32 (hostApiInfo -> name), U"\" ", hostApiInfo -> deviceCount);
  1018. }
  1019. PaHostApiIndex defaultHostApi = Pa_GetDefaultHostApi ();
  1020. Melder_casual (U"default host API ", defaultHostApi);
  1021. PaDeviceIndex deviceCount = Pa_GetDeviceCount ();
  1022. Melder_casual (U"device count ", deviceCount);
  1023. }
  1024. }
  1025. PaDeviceIndex deviceCount = Pa_GetDeviceCount ();
  1026. for (PaDeviceIndex idevice = 0; idevice < deviceCount; idevice ++) {
  1027. const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo (idevice);
  1028. if (Melder_debug == 20)
  1029. Melder_casual (
  1030. U"Device \"", Melder_peek8to32 (deviceInfo -> name),
  1031. U"\", input ", deviceInfo -> maxInputChannels,
  1032. U", output ", deviceInfo -> maxOutputChannels,
  1033. U", sample rate ", deviceInfo -> defaultSampleRate
  1034. );
  1035. if (deviceInfo -> maxInputChannels > 0 && my numberOfInputDevices < SoundRecorder_IDEVICE_MAX) {
  1036. my devices [++ my numberOfInputDevices]. canDo = true;
  1037. str32ncpy (my devices [my numberOfInputDevices]. name, Melder_peek8to32 (deviceInfo -> name), 40);
  1038. my devices [my numberOfInputDevices]. name [40] = U'\0';
  1039. my deviceInfos [my numberOfInputDevices] = deviceInfo;
  1040. my deviceIndices [my numberOfInputDevices] = idevice;
  1041. }
  1042. }
  1043. if (my numberOfInputDevices == 0)
  1044. Melder_throw (U"No input devices available.");
  1045. } else {
  1046. #if defined (macintosh)
  1047. #elif defined (_WIN32)
  1048. // No device info: use Windows mixer.
  1049. #else
  1050. my devices [1]. canDo = true;
  1051. str32cpy (my devices [1]. name, U"Microphone");
  1052. my devices [2]. canDo = true;
  1053. str32cpy (my devices [2]. name, U"Line");
  1054. #endif
  1055. }
  1056. /*
  1057. * Sampling frequency constants.
  1058. */
  1059. my fsamps [SoundRecorder_IFSAMP_8000]. fsamp = 8000.0;
  1060. my fsamps [SoundRecorder_IFSAMP_9800]. fsamp = 9800.0;
  1061. my fsamps [SoundRecorder_IFSAMP_11025]. fsamp = 11025.0;
  1062. my fsamps [SoundRecorder_IFSAMP_12000]. fsamp = 12000.0;
  1063. my fsamps [SoundRecorder_IFSAMP_16000]. fsamp = 16000.0;
  1064. my fsamps [SoundRecorder_IFSAMP_22050]. fsamp = 22050.0;
  1065. my fsamps [SoundRecorder_IFSAMP_22254]. fsamp = 22254.54545;
  1066. my fsamps [SoundRecorder_IFSAMP_24000]. fsamp = 24000.0;
  1067. my fsamps [SoundRecorder_IFSAMP_32000]. fsamp = 32000.0;
  1068. my fsamps [SoundRecorder_IFSAMP_44100]. fsamp = 44100.0;
  1069. my fsamps [SoundRecorder_IFSAMP_48000]. fsamp = 48000.0;
  1070. my fsamps [SoundRecorder_IFSAMP_64000]. fsamp = 64000.0;
  1071. my fsamps [SoundRecorder_IFSAMP_96000]. fsamp = 96000.0;
  1072. my fsamps [SoundRecorder_IFSAMP_192000]. fsamp = 192000.0;
  1073. /*
  1074. * The default set of possible sampling frequencies, to be modified in the initialize () procedure.
  1075. */
  1076. for (integer i = 1; i <= SoundRecorder_IFSAMP_MAX; i ++) my fsamps [i]. canDo = true; // optimistic: can do all, except two:
  1077. my fsamps [SoundRecorder_IFSAMP_9800]. canDo = false; // sgi only
  1078. my fsamps [SoundRecorder_IFSAMP_22254]. canDo = false; // old Mac only
  1079. /*
  1080. * Initialize system-dependent structures.
  1081. * On all systems: stereo 16-bit linear encoding.
  1082. * Some systems take initial values from the system control panel
  1083. * (automatically in the workProc), other systems from theControlPanel.
  1084. */
  1085. initialize (me.get());
  1086. Editor_init (me.get(), 100, 100, 600, 500, U"SoundRecorder", nullptr);
  1087. my graphics = Graphics_create_xmdrawingarea (my meter);
  1088. Melder_assert (my graphics);
  1089. Graphics_setWindow (my graphics.get(), 0.0, 1.0, 0.0, 1.0);
  1090. Graphics_setColour (my graphics.get(), Graphics_WHITE);
  1091. Graphics_fillRectangle (my graphics.get(), 0.0, 1.0, 0.0, 1.0);
  1092. struct structGuiDrawingArea_ResizeEvent event { my meter, 0 };
  1093. event. width = GuiControl_getWidth (my meter);
  1094. event. height = GuiControl_getHeight (my meter);
  1095. gui_drawingarea_cb_resize (me.get(), & event);
  1096. #if cocoa
  1097. CFRunLoopTimerContext context = { 0, me.get(), nullptr, nullptr, nullptr };
  1098. my d_cocoaTimer = CFRunLoopTimerCreate (nullptr, CFAbsoluteTimeGetCurrent () + 0.02,
  1099. 0.02, 0, 0, workProc, & context);
  1100. CFRunLoopAddTimer (CFRunLoopGetCurrent (), my d_cocoaTimer, kCFRunLoopCommonModes);
  1101. #elif gtk
  1102. g_idle_add (workProc, me.get());
  1103. #elif motif
  1104. my workProcId = GuiAddWorkProc (workProc, me.get());
  1105. #endif
  1106. updateMenus (me.get());
  1107. return me;
  1108. } catch (MelderError) {
  1109. Melder_throw (U"SoundRecorder not created.");
  1110. }
  1111. }
  1112. /* End of file SoundRecorder.cpp */