1 /* 2 * DSFML - The Simple and Fast Multimedia Library for D 3 * 4 * Copyright (c) 2013 - 2018 Jeremy DeHaan (dehaan.jeremiah@gmail.com) 5 * 6 * This software is provided 'as-is', without any express or implied warranty. 7 * In no event will the authors be held liable for any damages arising from the 8 * use of this software. 9 * 10 * Permission is granted to anyone to use this software for any purpose, 11 * including commercial applications, and to alter it and redistribute it 12 * freely, subject to the following restrictions: 13 * 14 * 1. The origin of this software must not be misrepresented; you must not claim 15 * that you wrote the original software. If you use this software in a product, 16 * an acknowledgment in the product documentation would be appreciated but is 17 * not required. 18 * 19 * 2. Altered source versions must be plainly marked as such, and must not be 20 * misrepresented as being the original software. 21 * 22 * 3. This notice may not be removed or altered from any source distribution 23 * 24 * 25 * DSFML is based on SFML (Copyright Laurent Gomila) 26 */ 27 28 /** 29 * 30 * $(U SoundRecorder) provides a simple interface to access the audio recording 31 * capabilities of the computer (the microphone). 32 * 33 * As an abstract base class, it only cares about capturing sound samples, the 34 * task of making something useful with them is left to the derived class. Note 35 * that DSFML provides a built-in specialization for saving the captured data to 36 * a sound buffer (see $(SOUNDBUFFERRECORDER_LINK)). 37 * 38 * A derived class has only one virtual function to override: 39 * $(UL $(LI onProcessSamples provides the new chunks of audio samples while the 40 * capture happens)) 41 * 42 * $(PARA Moreover, two additionnal virtual functions can be overriden as well 43 * if necessary:) 44 * $(UL 45 * $(LI onStart is called before the capture happens, to perform custom 46 * initializations) 47 * $(LI onStop is called after the capture ends, to perform custom cleanup)) 48 * 49 * $(PARA 50 * A derived class can also control the frequency of the onProcessSamples calls, 51 * with the setProcessingInterval protected function. The default interval is 52 * chosen so that recording thread doesn't consume too much CPU, but it can be 53 * changed to a smaller value if you need to process the recorded data in real 54 * time, for example. 55 * 56 * The audio capture feature may not be supported or activated on every 57 * platform, thus it is recommended to check its availability with the 58 * `isAvailable()` function. If it returns false, then any attempt to use an 59 * audio recorder will fail. 60 * 61 * If you have multiple sound input devices connected to your computer (for 62 * example: microphone, external soundcard, webcam mic, ...) you can get a list 63 * of all available devices through the `getAvailableDevices()` function. You 64 * can then select a device by calling `setDevice()` with the appropriate 65 * device. Otherwise the default capturing device will be used. 66 * 67 * By default the recording is in 16-bit mono. Using the setChannelCount method 68 * you can change the number of channels used by the audio capture device to 69 * record. Note that you have to decide whether you want to record in mono or 70 * stereo before starting the recording. 71 * 72 * It is important to note that the audio capture happens in a separate thread, 73 * so that it doesn't block the rest of the program. In particular, the 74 * `onProcessSamples` and `onStop` virtual functions (but not `onStart`) will be 75 * called from this separate thread. It is important to keep this in mind, 76 * because you may have to take care of synchronization issues if you share data 77 * between threads.) 78 * 79 * Example: 80 * --- 81 * class CustomRecorder : SoundRecorder 82 * { 83 * ~this() 84 * { 85 * // Make sure to stop the recording thread 86 * stop(); 87 * } 88 * 89 * override bool onStart() // optional 90 * { 91 * // Initialize whatever has to be done before the capture starts 92 * ... 93 * 94 * // Return true to start playing 95 * return true; 96 * } 97 * 98 * bool onProcessSamples(const(short)[] samples) 99 * { 100 * // Do something with the new chunk of samples (store them, send them, ...) 101 * ... 102 * 103 * // Return true to continue playing 104 * return true; 105 * } 106 * 107 * override void onStop() // optional 108 * { 109 * // Clean up whatever has to be done after the capture ends 110 * ... 111 * } 112 * } 113 * 114 * // Usage 115 * if (CustomRecorder.isAvailable()) 116 * { 117 * auto recorder = new CustomRecorder(); 118 * 119 * if (!recorder.start()) 120 * return -1; 121 * 122 * ... 123 * recorder.stop(); 124 * } 125 * --- 126 * 127 * See_Also: 128 * $(SOUNDBUFFERRECORDER_LINK) 129 */ 130 module dsfml.audio.soundrecorder; 131 132 import core.thread; 133 import dsfml.system..string; 134 import dsfml.system.err; 135 136 /** 137 * Abstract base class for capturing sound data. 138 */ 139 class SoundRecorder 140 { 141 package sfSoundRecorder* sfPtr; 142 private SoundRecorderCallBacks callBacks; 143 144 /// Default constructor. 145 protected this() 146 { 147 callBacks = new SoundRecorderCallBacks(this); 148 sfPtr = sfSoundRecorder_construct(callBacks); 149 150 //Fix for some strange bug that I can't seem to track down. 151 //This bug causes the array in SoundBufferRecorder to segfault if 152 //its length reaches 1024, but creating an array of this size before capturing happens 153 //seems to fix it. This fix should allow other implementations to not segfault as well. 154 //I will look into the cause when I have more time, but this at least renders it usable. 155 short[] temp; 156 temp.length = 1024; 157 temp.length =0; 158 } 159 160 /// Destructor. 161 ~this() 162 { 163 import dsfml.system.config; 164 mixin(destructorOutput); 165 sfSoundRecorder_destroy(sfPtr); 166 } 167 168 /** 169 * Start the capture. 170 * 171 * The sampleRate parameter defines the number of audio samples captured per 172 * second. The higher, the better the quality (for example, 44100 173 * samples/sec is CD quality). This function uses its own thread so that it 174 * doesn't block the rest of the program while the capture runs. Please note 175 * that only one capture can happen at the same time. 176 * 177 * Params: 178 * theSampleRate = Desired capture rate, in number of samples per second 179 */ 180 void start(uint theSampleRate = 44100) 181 { 182 sfSoundRecorder_start(sfPtr, theSampleRate); 183 } 184 185 /// Stop the capture. 186 void stop() 187 { 188 sfSoundRecorder_stop(sfPtr); 189 } 190 191 @property 192 { 193 /** 194 * Get the sample rate in samples per second. 195 * 196 * The sample rate defines the number of audio samples captured per second. 197 * The higher, the better the quality (for example, 44100 samples/sec is CD 198 * quality). 199 */ 200 uint sampleRate() const 201 { 202 return sfSoundRecorder_getSampleRate(sfPtr); 203 } 204 } 205 206 /** 207 * Get the name of the current audio capture device. 208 * 209 * Returns: The name of the current audio capture device. 210 */ 211 string getDevice() const { 212 return .toString(sfSoundRecorder_getDevice(sfPtr)); 213 } 214 215 /** 216 * Set the audio capture device. 217 * 218 * This function sets the audio capture device to the device with the given 219 * name. It can be called on the fly (i.e: while recording). If you do so 220 * while recording and opening the device fails, it stops the recording. 221 * 222 * Params: 223 * name = The name of the audio capture device 224 * 225 * Returns: true, if it was able to set the requested device. 226 * 227 * See_Also: 228 * `getAvailableDevices`, `getDefaultDevice` 229 */ 230 bool setDevice (const(char)[] name) 231 { 232 return sfSoundRecorder_setDevice(sfPtr, name.ptr, name.length); 233 } 234 235 /** 236 * Get a list of the names of all available audio capture devices. 237 * 238 * This function returns an array of strings, containing the names of all 239 * available audio capture devices. 240 * 241 * Returns: An array of strings containing the names. 242 */ 243 static const(string)[] getAvailableDevices() 244 { 245 //stores all available devices after the first call 246 static string[] availableDevices; 247 248 //if getAvailableDevices hasn't been called yet 249 if(availableDevices.length == 0) 250 { 251 const (char)** devices; 252 size_t counts; 253 254 devices = sfSoundRecorder_getAvailableDevices(&counts); 255 256 //calculate real length 257 availableDevices.length = counts; 258 259 //populate availableDevices 260 for(uint i = 0; i < counts; i++) 261 { 262 availableDevices[i] = .toString(devices[i]); 263 } 264 } 265 266 return availableDevices; 267 } 268 269 /** 270 * Get the name of the default audio capture device. 271 * 272 * This function returns the name of the default audio capture device. If 273 * none is available, an empty string is returned. 274 * 275 * Returns: The name of the default audio capture device. 276 */ 277 static string getDefaultDevice() { 278 return .toString(sfSoundRecorder_getDefaultDevice()); 279 } 280 281 /** 282 * Check if the system supports audio capture. 283 * 284 * This function should always be called before using the audio capture 285 * features. If it returns false, then any attempt to use SoundRecorder or 286 * one of its derived classes will fail. 287 * 288 * Returns: true if audio capture is supported, false otherwise. 289 */ 290 static bool isAvailable() 291 { 292 return sfSoundRecorder_isAvailable(); 293 } 294 295 protected 296 { 297 /** 298 * Set the processing interval. 299 * 300 * The processing interval controls the period between calls to the 301 * onProcessSamples function. You may want to use a small interval if 302 * you want to process the recorded data in real time, for example. 303 * 304 * Note: this is only a hint, the actual period may vary. So don't rely 305 * on this parameter to implement precise timing. 306 * 307 * The default processing interval is 100 ms. 308 * 309 * Params: 310 * interval = Processing interval 311 */ 312 void setProcessingInterval (Duration interval) { 313 sfSoundRecorder_setProcessingInterval(sfPtr, 314 interval.total!"usecs"); 315 } 316 317 /** 318 * Start capturing audio data. 319 * 320 * This virtual function may be overriden by a derived class if 321 * something has to be done every time a new capture starts. If not, 322 * this function can be ignored; the default implementation does 323 * nothing. 324 * 325 * Returns: true to the start the capture, or false to abort it. 326 */ 327 bool onStart() 328 { 329 return true; 330 } 331 332 /** 333 * Process a new chunk of recorded samples. 334 * 335 * This virtual function is called every time a new chunk of recorded 336 * data is available. The derived class can then do whatever it wants 337 * with it (storing it, playing it, sending it over the network, etc.). 338 * 339 * Params: 340 * samples = Array of the new chunk of recorded samples 341 * 342 * Returns: true to continue the capture, or false to stop it. 343 */ 344 abstract bool onProcessSamples(const(short)[] samples); 345 346 /** 347 * Stop capturing audio data. 348 * 349 * This virtual function may be overriden by a derived class if 350 * something has to be done every time the capture ends. If not, this 351 * function can be ignored; the default implementation does nothing. 352 */ 353 void onStop() 354 { 355 } 356 } 357 } 358 359 private: 360 361 extern(C++) interface sfmlSoundRecorderCallBacks 362 { 363 bool onStart(); 364 bool onProcessSamples(const(short)* samples, size_t sampleCount); 365 void onStop(); 366 } 367 368 class SoundRecorderCallBacks: sfmlSoundRecorderCallBacks 369 { 370 import std.stdio; 371 372 SoundRecorder m_recorder; 373 374 this(SoundRecorder recorder) 375 { 376 m_recorder = recorder; 377 } 378 379 extern(C++) bool onStart() 380 { 381 return m_recorder.onStart(); 382 } 383 extern(C++) bool onProcessSamples(const(short)* samples, size_t sampleCount) 384 { 385 return m_recorder.onProcessSamples(samples[0..sampleCount]); 386 } 387 extern(C++) void onStop() 388 { 389 m_recorder.onStop(); 390 } 391 } 392 393 private extern(C): 394 395 struct sfSoundRecorder; 396 397 sfSoundRecorder* sfSoundRecorder_construct(sfmlSoundRecorderCallBacks newCallBacks); 398 399 void sfSoundRecorder_destroy(sfSoundRecorder* soundRecorder); 400 401 void sfSoundRecorder_start(sfSoundRecorder* soundRecorder, uint sampleRate); 402 403 void sfSoundRecorder_stop(sfSoundRecorder* soundRecorder); 404 405 uint sfSoundRecorder_getSampleRate(const sfSoundRecorder* soundRecorder); 406 407 bool sfSoundRecorder_setDevice(sfSoundRecorder* soundRecorder, const(char)* name, size_t length); 408 409 const(char)* sfSoundRecorder_getDevice(const (sfSoundRecorder)* soundRecorder); 410 411 const(char)** sfSoundRecorder_getAvailableDevices(size_t* count); 412 413 const(char)* sfSoundRecorder_getDefaultDevice(); 414 415 bool sfSoundRecorder_isAvailable(); 416 417 void sfSoundRecorder_setProcessingInterval(sfSoundRecorder* soundRecorder, ulong time);