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 lib.DSFML.soundrecorder; 131 132 import bindbc.sfml.audio; 133 import bindbc.sfml.system; 134 135 import core.thread; 136 import nudsfml.system.string; 137 import nudsfml.system.err; 138 139 /** 140 * Abstract base class for capturing sound data. 141 */ 142 class SoundRecorder 143 { 144 package sfSoundRecorder* sfPtr; 145 private SoundRecorderCallBacks callBacks; 146 147 short[] m_samples; 148 149 /// Default constructor. 150 protected this() 151 { 152 callBacks = new SoundRecorderCallBacks(this); 153 154 sfPtr = sfSoundRecorder_create(callBacks.onProcessSamples, callBacks.onStart, callBacks.onStop,cast(void*) m_samples.ptr); 155 //sfPtr = sfSoundRecorder_construct(callBacks); 156 157 //Fix for some strange bug that I can't seem to track down. 158 //This bug causes the array in SoundBufferRecorder to segfault if 159 //its length reaches 1024, but creating an array of this size before capturing happens 160 //seems to fix it. This fix should allow other implementations to not segfault as well. 161 //I will look into the cause when I have more time, but this at least renders it usable. 162 short[] temp; 163 temp.length = 1024; 164 temp.length = 0; 165 } 166 167 /// Destructor. 168 ~this() 169 { 170 import nudsfml.system.config; 171 mixin(destructorOutput); 172 sfSoundRecorder_destroy(sfPtr); 173 } 174 175 /** 176 * Start the capture. 177 * 178 * The sampleRate parameter defines the number of audio samples captured per 179 * second. The higher, the better the quality (for example, 44100 180 * samples/sec is CD quality). This function uses its own thread so that it 181 * doesn't block the rest of the program while the capture runs. Please note 182 * that only one capture can happen at the same time. 183 * 184 * Params: 185 * theSampleRate = Desired capture rate, in number of samples per second 186 */ 187 void start(uint theSampleRate = 44100) 188 { 189 sfSoundRecorder_start(sfPtr, theSampleRate); 190 } 191 192 /// Stop the capture. 193 void stop() 194 { 195 sfSoundRecorder_stop(sfPtr); 196 } 197 198 @property 199 { 200 /** 201 * Get the sample rate in samples per second. 202 * 203 * The sample rate defines the number of audio samples captured per second. 204 * The higher, the better the quality (for example, 44100 samples/sec is CD 205 * quality). 206 */ 207 uint sampleRate() const 208 { 209 return sfSoundRecorder_getSampleRate(sfPtr); 210 } 211 } 212 213 /** 214 * Get the name of the current audio capture device. 215 * 216 * Returns: The name of the current audio capture device. 217 */ 218 string getDevice() const { 219 return .toString(sfSoundRecorder_getDevice(sfPtr)); 220 } 221 222 /** 223 * Set the audio capture device. 224 * 225 * This function sets the audio capture device to the device with the given 226 * name. It can be called on the fly (i.e: while recording). If you do so 227 * while recording and opening the device fails, it stops the recording. 228 * 229 * Params: 230 * name = The name of the audio capture device 231 * 232 * Returns: true, if it was able to set the requested device. 233 * 234 * See_Also: 235 * `getAvailableDevices`, `getDefaultDevice` 236 */ 237 bool setDevice (const(char)[] name) 238 { 239 return sfSoundRecorder_setDevice(sfPtr, name.ptr, name.length); 240 } 241 242 /** 243 * Get a list of the names of all available audio capture devices. 244 * 245 * This function returns an array of strings, containing the names of all 246 * available audio capture devices. 247 * 248 * Returns: An array of strings containing the names. 249 */ 250 static const(string)[] getAvailableDevices() 251 { 252 //stores all available devices after the first call 253 static string[] availableDevices; 254 255 //if getAvailableDevices hasn't been called yet 256 if(availableDevices.length == 0) 257 { 258 const (char)** devices; 259 size_t counts; 260 261 devices = sfSoundRecorder_getAvailableDevices(&counts); 262 263 //calculate real length 264 availableDevices.length = counts; 265 266 //populate availableDevices 267 for(uint i = 0; i < counts; i++) 268 { 269 availableDevices[i] = .toString(devices[i]); 270 } 271 } 272 273 return availableDevices; 274 } 275 276 /** 277 * Get the name of the default audio capture device. 278 * 279 * This function returns the name of the default audio capture device. If 280 * none is available, an empty string is returned. 281 * 282 * Returns: The name of the default audio capture device. 283 */ 284 static string getDefaultDevice() { 285 return .toString(sfSoundRecorder_getDefaultDevice()); 286 } 287 288 /** 289 * Check if the system supports audio capture. 290 * 291 * This function should always be called before using the audio capture 292 * features. If it returns false, then any attempt to use SoundRecorder or 293 * one of its derived classes will fail. 294 * 295 * Returns: true if audio capture is supported, false otherwise. 296 */ 297 static bool isAvailable() 298 { 299 return sfSoundRecorder_isAvailable(); 300 } 301 302 protected 303 { 304 /** 305 * Set the processing interval. 306 * 307 * The processing interval controls the period between calls to the 308 * onProcessSamples function. You may want to use a small interval if 309 * you want to process the recorded data in real time, for example. 310 * 311 * Note: this is only a hint, the actual period may vary. So don't rely 312 * on this parameter to implement precise timing. 313 * 314 * The default processing interval is 100 ms. 315 * 316 * Params: 317 * interval = Processing interval 318 */ 319 void setProcessingInterval (Duration interval) { 320 sfSoundRecorder_setProcessingInterval(sfPtr, 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 bool onStart(); 370 bool onProcessSamples(const(short)* samples, size_t sampleCount); 371 void onStop(); 372 } 373 374 class SoundRecorderCallBacks: sfmlSoundRecorderCallBacks 375 { 376 import std.stdio; 377 378 SoundRecorder m_recorder; 379 380 this(SoundRecorder recorder) 381 { 382 m_recorder = recorder; 383 } 384 385 extern(C++) bool onStart() 386 { 387 return m_recorder.onStart(); 388 } 389 extern(C++) bool onProcessSamples(const(short)* samples, size_t sampleCount) 390 { 391 return m_recorder.onProcessSamples(samples[0..sampleCount]); 392 } 393 extern(C++) void onStop() 394 { 395 m_recorder.onStop(); 396 } 397 } 398