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