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