1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    The code included in this file is provided under the terms of the ISC license
11    http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12    To use, copy, modify, and/or distribute this software for any purpose with or
13    without fee is hereby granted provided that the above copyright notice and
14    this permission notice appear in all copies.
15 
16    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18    DISCLAIMED.
19 
20   ==============================================================================
21 */
22 
23 namespace juce
24 {
25 
26 namespace
27 {
28 
29 #ifndef JUCE_ALSA_LOGGING
30  #define JUCE_ALSA_LOGGING 0
31 #endif
32 
33 #if JUCE_ALSA_LOGGING
34  #define JUCE_ALSA_LOG(dbgtext)   { juce::String tempDbgBuf ("ALSA: "); tempDbgBuf << dbgtext; Logger::writeToLog (tempDbgBuf); DBG (tempDbgBuf); }
35  #define JUCE_CHECKED_RESULT(x)   (logErrorMessage (x, __LINE__))
36 
logErrorMessage(int err,int lineNum)37  static int logErrorMessage (int err, int lineNum)
38  {
39     if (err < 0)
40         JUCE_ALSA_LOG ("Error: line " << lineNum << ": code " << err << " (" << snd_strerror (err) << ")");
41 
42     return err;
43  }
44 #else
45  #define JUCE_ALSA_LOG(x)         {}
46  #define JUCE_CHECKED_RESULT(x)   (x)
47 #endif
48 
49 #define JUCE_ALSA_FAILED(x)  failed (x)
50 
getDeviceSampleRates(snd_pcm_t * handle,Array<double> & rates)51 static void getDeviceSampleRates (snd_pcm_t* handle, Array<double>& rates)
52 {
53     const int ratesToTry[] = { 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000, 0 };
54 
55     snd_pcm_hw_params_t* hwParams;
56     snd_pcm_hw_params_alloca (&hwParams);
57 
58     for (int i = 0; ratesToTry[i] != 0; ++i)
59     {
60         if (snd_pcm_hw_params_any (handle, hwParams) >= 0
61              && snd_pcm_hw_params_test_rate (handle, hwParams, (unsigned int) ratesToTry[i], 0) == 0)
62         {
63             rates.addIfNotAlreadyThere ((double) ratesToTry[i]);
64         }
65     }
66 }
67 
getDeviceNumChannels(snd_pcm_t * handle,unsigned int * minChans,unsigned int * maxChans)68 static void getDeviceNumChannels (snd_pcm_t* handle, unsigned int* minChans, unsigned int* maxChans)
69 {
70     snd_pcm_hw_params_t *params;
71     snd_pcm_hw_params_alloca (&params);
72 
73     if (snd_pcm_hw_params_any (handle, params) >= 0)
74     {
75         snd_pcm_hw_params_get_channels_min (params, minChans);
76         snd_pcm_hw_params_get_channels_max (params, maxChans);
77 
78         JUCE_ALSA_LOG ("getDeviceNumChannels: " << (int) *minChans << " " << (int) *maxChans);
79 
80         // some virtual devices (dmix for example) report 10000 channels , we have to clamp these values
81         *maxChans = jmin (*maxChans, 256u);
82         *minChans = jmin (*minChans, *maxChans);
83     }
84     else
85     {
86         JUCE_ALSA_LOG ("getDeviceNumChannels failed");
87     }
88 }
89 
getDeviceProperties(const String & deviceID,unsigned int & minChansOut,unsigned int & maxChansOut,unsigned int & minChansIn,unsigned int & maxChansIn,Array<double> & rates,bool testOutput,bool testInput)90 static void getDeviceProperties (const String& deviceID,
91                                  unsigned int& minChansOut,
92                                  unsigned int& maxChansOut,
93                                  unsigned int& minChansIn,
94                                  unsigned int& maxChansIn,
95                                  Array<double>& rates,
96                                  bool testOutput,
97                                  bool testInput)
98 {
99     minChansOut = maxChansOut = minChansIn = maxChansIn = 0;
100 
101     if (deviceID.isEmpty())
102         return;
103 
104     JUCE_ALSA_LOG ("getDeviceProperties(" << deviceID.toUTF8().getAddress() << ")");
105 
106     snd_pcm_info_t* info;
107     snd_pcm_info_alloca (&info);
108 
109     if (testOutput)
110     {
111         snd_pcm_t* pcmHandle;
112 
113         if (JUCE_CHECKED_RESULT (snd_pcm_open (&pcmHandle, deviceID.toUTF8().getAddress(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) >= 0)
114         {
115             getDeviceNumChannels (pcmHandle, &minChansOut, &maxChansOut);
116             getDeviceSampleRates (pcmHandle, rates);
117 
118             snd_pcm_close (pcmHandle);
119         }
120     }
121 
122     if (testInput)
123     {
124         snd_pcm_t* pcmHandle;
125 
126         if (JUCE_CHECKED_RESULT (snd_pcm_open (&pcmHandle, deviceID.toUTF8(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK) >= 0))
127         {
128             getDeviceNumChannels (pcmHandle, &minChansIn, &maxChansIn);
129 
130             if (rates.size() == 0)
131                 getDeviceSampleRates (pcmHandle, rates);
132 
133             snd_pcm_close (pcmHandle);
134         }
135     }
136 }
137 
ensureMinimumNumBitsSet(BigInteger & chans,int minNumChans)138 static void ensureMinimumNumBitsSet (BigInteger& chans, int minNumChans)
139 {
140     int i = 0;
141 
142     while (chans.countNumberOfSetBits() < minNumChans)
143         chans.setBit (i++);
144 }
145 
silentErrorHandler(const char *,int,const char *,int,const char *,...)146 static void silentErrorHandler (const char*, int, const char*, int, const char*,...) {}
147 
148 //==============================================================================
149 class ALSADevice
150 {
151 public:
ALSADevice(const String & devID,bool forInput)152     ALSADevice (const String& devID, bool forInput)
153         : handle (nullptr),
154           bitDepth (16),
155           numChannelsRunning (0),
156           latency (0),
157           deviceID (devID),
158           isInput (forInput),
159           isInterleaved (true)
160     {
161         JUCE_ALSA_LOG ("snd_pcm_open (" << deviceID.toUTF8().getAddress() << ", forInput=" << (int) forInput << ")");
162 
163         int err = snd_pcm_open (&handle, deviceID.toUTF8(),
164                                 forInput ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,
165                                 SND_PCM_ASYNC);
166         if (err < 0)
167         {
168             if (-err == EBUSY)
169                 error << "The device \"" << deviceID << "\" is busy (another application is using it).";
170             else if (-err == ENOENT)
171                 error << "The device \"" << deviceID << "\" is not available.";
172             else
173                 error << "Could not open " << (forInput ? "input" : "output") << " device \"" << deviceID
174                       << "\": " << snd_strerror(err) << " (" << err << ")";
175 
176             JUCE_ALSA_LOG ("snd_pcm_open failed; " << error);
177         }
178     }
179 
~ALSADevice()180     ~ALSADevice()
181     {
182         closeNow();
183     }
184 
closeNow()185     void closeNow()
186     {
187         if (handle != nullptr)
188         {
189             snd_pcm_close (handle);
190             handle = nullptr;
191         }
192     }
193 
setParameters(unsigned int sampleRate,int numChannels,int bufferSize)194     bool setParameters (unsigned int sampleRate, int numChannels, int bufferSize)
195     {
196         if (handle == nullptr)
197             return false;
198 
199         JUCE_ALSA_LOG ("ALSADevice::setParameters(" << deviceID << ", "
200                          << (int) sampleRate << ", " << numChannels << ", " << bufferSize << ")");
201 
202         snd_pcm_hw_params_t* hwParams;
203         snd_pcm_hw_params_alloca (&hwParams);
204 
205         if (snd_pcm_hw_params_any (handle, hwParams) < 0)
206         {
207             // this is the error message that aplay returns when an error happens here,
208             // it is a bit more explicit that "Invalid parameter"
209             error = "Broken configuration for this PCM: no configurations available";
210             return false;
211         }
212 
213         if (snd_pcm_hw_params_set_access (handle, hwParams, SND_PCM_ACCESS_RW_INTERLEAVED) >= 0) // works better for plughw..
214             isInterleaved = true;
215         else if (snd_pcm_hw_params_set_access (handle, hwParams, SND_PCM_ACCESS_RW_NONINTERLEAVED) >= 0)
216             isInterleaved = false;
217         else
218         {
219             jassertfalse;
220             return false;
221         }
222 
223         enum { isFloatBit = 1 << 16, isLittleEndianBit = 1 << 17, onlyUseLower24Bits = 1 << 18 };
224 
225         const int formatsToTry[] = { SND_PCM_FORMAT_FLOAT_LE,   32 | isFloatBit | isLittleEndianBit,
226                                      SND_PCM_FORMAT_FLOAT_BE,   32 | isFloatBit,
227                                      SND_PCM_FORMAT_S32_LE,     32 | isLittleEndianBit,
228                                      SND_PCM_FORMAT_S32_BE,     32,
229                                      SND_PCM_FORMAT_S24_3LE,    24 | isLittleEndianBit,
230                                      SND_PCM_FORMAT_S24_3BE,    24,
231                                      SND_PCM_FORMAT_S24_LE,     32 | isLittleEndianBit | onlyUseLower24Bits,
232                                      SND_PCM_FORMAT_S16_LE,     16 | isLittleEndianBit,
233                                      SND_PCM_FORMAT_S16_BE,     16 };
234         bitDepth = 0;
235 
236         for (int i = 0; i < numElementsInArray (formatsToTry); i += 2)
237         {
238             if (snd_pcm_hw_params_set_format (handle, hwParams, (_snd_pcm_format) formatsToTry [i]) >= 0)
239             {
240                 const int type = formatsToTry [i + 1];
241                 bitDepth = type & 255;
242 
243                 converter.reset (createConverter (isInput, bitDepth,
244                                                   (type & isFloatBit) != 0,
245                                                   (type & isLittleEndianBit) != 0,
246                                                   (type & onlyUseLower24Bits) != 0,
247                                                   numChannels,
248                                                   isInterleaved));
249                 break;
250             }
251         }
252 
253         if (bitDepth == 0)
254         {
255             error = "device doesn't support a compatible PCM format";
256             JUCE_ALSA_LOG ("Error: " + error);
257             return false;
258         }
259 
260         int dir = 0;
261         unsigned int periods = 4;
262         snd_pcm_uframes_t samplesPerPeriod = (snd_pcm_uframes_t) bufferSize;
263 
264         if (JUCE_ALSA_FAILED (snd_pcm_hw_params_set_rate_near (handle, hwParams, &sampleRate, nullptr))
265             || JUCE_ALSA_FAILED (snd_pcm_hw_params_set_channels (handle, hwParams, (unsigned int ) numChannels))
266             || JUCE_ALSA_FAILED (snd_pcm_hw_params_set_periods_near (handle, hwParams, &periods, &dir))
267             || JUCE_ALSA_FAILED (snd_pcm_hw_params_set_period_size_near (handle, hwParams, &samplesPerPeriod, &dir))
268             || JUCE_ALSA_FAILED (snd_pcm_hw_params (handle, hwParams)))
269         {
270             return false;
271         }
272 
273         snd_pcm_uframes_t frames = 0;
274 
275         if (JUCE_ALSA_FAILED (snd_pcm_hw_params_get_period_size (hwParams, &frames, &dir))
276              || JUCE_ALSA_FAILED (snd_pcm_hw_params_get_periods (hwParams, &periods, &dir)))
277             latency = 0;
278         else
279             latency = (int) frames * ((int) periods - 1); // (this is the method JACK uses to guess the latency..)
280 
281         JUCE_ALSA_LOG ("frames: " << (int) frames << ", periods: " << (int) periods
282                           << ", samplesPerPeriod: " << (int) samplesPerPeriod);
283 
284         snd_pcm_sw_params_t* swParams;
285         snd_pcm_sw_params_alloca (&swParams);
286         snd_pcm_uframes_t boundary;
287 
288         if (JUCE_ALSA_FAILED (snd_pcm_sw_params_current (handle, swParams))
289             || JUCE_ALSA_FAILED (snd_pcm_sw_params_get_boundary (swParams, &boundary))
290             || JUCE_ALSA_FAILED (snd_pcm_sw_params_set_silence_threshold (handle, swParams, 0))
291             || JUCE_ALSA_FAILED (snd_pcm_sw_params_set_silence_size (handle, swParams, boundary))
292             || JUCE_ALSA_FAILED (snd_pcm_sw_params_set_start_threshold (handle, swParams, samplesPerPeriod))
293             || JUCE_ALSA_FAILED (snd_pcm_sw_params_set_stop_threshold (handle, swParams, boundary))
294             || JUCE_ALSA_FAILED (snd_pcm_sw_params (handle, swParams)))
295         {
296             return false;
297         }
298 
299        #if JUCE_ALSA_LOGGING
300         // enable this to dump the config of the devices that get opened
301         snd_output_t* out;
302         snd_output_stdio_attach (&out, stderr, 0);
303         snd_pcm_hw_params_dump (hwParams, out);
304         snd_pcm_sw_params_dump (swParams, out);
305        #endif
306 
307         numChannelsRunning = numChannels;
308 
309         return true;
310     }
311 
312     //==============================================================================
writeToOutputDevice(AudioBuffer<float> & outputChannelBuffer,const int numSamples)313     bool writeToOutputDevice (AudioBuffer<float>& outputChannelBuffer, const int numSamples)
314     {
315         jassert (numChannelsRunning <= outputChannelBuffer.getNumChannels());
316         float* const* const data = outputChannelBuffer.getArrayOfWritePointers();
317         snd_pcm_sframes_t numDone = 0;
318 
319         if (isInterleaved)
320         {
321             scratch.ensureSize ((size_t) ((int) sizeof (float) * numSamples * numChannelsRunning), false);
322 
323             for (int i = 0; i < numChannelsRunning; ++i)
324                 converter->convertSamples (scratch.getData(), i, data[i], 0, numSamples);
325 
326             numDone = snd_pcm_writei (handle, scratch.getData(), (snd_pcm_uframes_t) numSamples);
327         }
328         else
329         {
330             for (int i = 0; i < numChannelsRunning; ++i)
331                 converter->convertSamples (data[i], data[i], numSamples);
332 
333             numDone = snd_pcm_writen (handle, (void**) data, (snd_pcm_uframes_t) numSamples);
334         }
335 
336         if (numDone < 0)
337         {
338             if (numDone == -(EPIPE))
339                 underrunCount++;
340 
341             if (JUCE_ALSA_FAILED (snd_pcm_recover (handle, (int) numDone, 1 /* silent */)))
342                 return false;
343         }
344 
345         if (numDone < numSamples)
346             JUCE_ALSA_LOG ("Did not write all samples: numDone: " << numDone << ", numSamples: " << numSamples);
347 
348         return true;
349     }
350 
readFromInputDevice(AudioBuffer<float> & inputChannelBuffer,const int numSamples)351     bool readFromInputDevice (AudioBuffer<float>& inputChannelBuffer, const int numSamples)
352     {
353         jassert (numChannelsRunning <= inputChannelBuffer.getNumChannels());
354         float* const* const data = inputChannelBuffer.getArrayOfWritePointers();
355 
356         if (isInterleaved)
357         {
358             scratch.ensureSize ((size_t) ((int) sizeof (float) * numSamples * numChannelsRunning), false);
359             scratch.fillWith (0); // (not clearing this data causes warnings in valgrind)
360 
361             auto num = snd_pcm_readi (handle, scratch.getData(), (snd_pcm_uframes_t) numSamples);
362 
363             if (num < 0)
364             {
365                 if (num == -(EPIPE))
366                     overrunCount++;
367 
368                 if (JUCE_ALSA_FAILED (snd_pcm_recover (handle, (int) num, 1 /* silent */)))
369                     return false;
370             }
371 
372 
373             if (num < numSamples)
374                 JUCE_ALSA_LOG ("Did not read all samples: num: " << num << ", numSamples: " << numSamples);
375 
376             for (int i = 0; i < numChannelsRunning; ++i)
377                 converter->convertSamples (data[i], 0, scratch.getData(), i, numSamples);
378         }
379         else
380         {
381             auto num = snd_pcm_readn (handle, (void**) data, (snd_pcm_uframes_t) numSamples);
382 
383             if (num < 0)
384             {
385                 if (num == -(EPIPE))
386                     overrunCount++;
387 
388                 if (JUCE_ALSA_FAILED (snd_pcm_recover (handle, (int) num, 1 /* silent */)))
389                     return false;
390             }
391 
392             if (num < numSamples)
393                 JUCE_ALSA_LOG ("Did not read all samples: num: " << num << ", numSamples: " << numSamples);
394 
395             for (int i = 0; i < numChannelsRunning; ++i)
396                 converter->convertSamples (data[i], data[i], numSamples);
397         }
398 
399         return true;
400     }
401 
402     //==============================================================================
403     snd_pcm_t* handle;
404     String error;
405     int bitDepth, numChannelsRunning, latency;
406     int underrunCount = 0, overrunCount = 0;
407 
408 private:
409     //==============================================================================
410     String deviceID;
411     const bool isInput;
412     bool isInterleaved;
413     MemoryBlock scratch;
414     std::unique_ptr<AudioData::Converter> converter;
415 
416     //==============================================================================
417     template <class SampleType>
418     struct ConverterHelper
419     {
createConverterjuce::__anon1075f57d0111::ALSADevice::ConverterHelper420         static AudioData::Converter* createConverter (const bool forInput, const bool isLittleEndian, const int numInterleavedChannels, bool interleaved)
421         {
422             if (interleaved)
423                 return create<AudioData::Interleaved> (forInput, isLittleEndian, numInterleavedChannels);
424 
425             return create<AudioData::NonInterleaved> (forInput, isLittleEndian, numInterleavedChannels);
426         }
427 
428     private:
429         template <class InterleavedType>
createjuce::__anon1075f57d0111::ALSADevice::ConverterHelper430         static AudioData::Converter* create (const bool forInput, const bool isLittleEndian, const int numInterleavedChannels)
431         {
432             if (forInput)
433             {
434                 using DestType = AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst>;
435 
436                 if (isLittleEndian)
437                     return new AudioData::ConverterInstance <AudioData::Pointer <SampleType, AudioData::LittleEndian, InterleavedType, AudioData::Const>, DestType> (numInterleavedChannels, 1);
438 
439                 return new AudioData::ConverterInstance <AudioData::Pointer <SampleType, AudioData::BigEndian, InterleavedType, AudioData::Const>, DestType> (numInterleavedChannels, 1);
440             }
441 
442             using SourceType = AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const>;
443 
444             if (isLittleEndian)
445                 return new AudioData::ConverterInstance <SourceType, AudioData::Pointer <SampleType, AudioData::LittleEndian, InterleavedType, AudioData::NonConst>> (1, numInterleavedChannels);
446 
447             return new AudioData::ConverterInstance <SourceType, AudioData::Pointer <SampleType, AudioData::BigEndian, InterleavedType, AudioData::NonConst>> (1, numInterleavedChannels);
448         }
449     };
450 
createConverter(bool forInput,int bitDepth,bool isFloat,bool isLittleEndian,bool useOnlyLower24Bits,int numInterleavedChannels,bool interleaved)451     static AudioData::Converter* createConverter (bool forInput, int bitDepth,
452                                                   bool isFloat, bool isLittleEndian, bool useOnlyLower24Bits,
453                                                   int numInterleavedChannels,
454                                                   bool interleaved)
455     {
456         JUCE_ALSA_LOG ("format: bitDepth=" << bitDepth << ", isFloat=" << (int) isFloat
457                         << ", isLittleEndian=" << (int) isLittleEndian << ", numChannels=" << numInterleavedChannels);
458 
459         if (isFloat)         return ConverterHelper <AudioData::Float32>::createConverter (forInput, isLittleEndian, numInterleavedChannels, interleaved);
460         if (bitDepth == 16)  return ConverterHelper <AudioData::Int16>  ::createConverter (forInput, isLittleEndian, numInterleavedChannels, interleaved);
461         if (bitDepth == 24)  return ConverterHelper <AudioData::Int24>  ::createConverter (forInput, isLittleEndian, numInterleavedChannels, interleaved);
462 
463         jassert (bitDepth == 32);
464 
465         if (useOnlyLower24Bits)
466             return ConverterHelper <AudioData::Int24in32>::createConverter (forInput, isLittleEndian, numInterleavedChannels, interleaved);
467 
468         return ConverterHelper <AudioData::Int32>::createConverter (forInput, isLittleEndian, numInterleavedChannels, interleaved);
469     }
470 
471     //==============================================================================
failed(const int errorNum)472     bool failed (const int errorNum)
473     {
474         if (errorNum >= 0)
475             return false;
476 
477         error = snd_strerror (errorNum);
478         JUCE_ALSA_LOG ("ALSA error: " << error);
479         return true;
480     }
481 
482     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ALSADevice)
483 };
484 
485 //==============================================================================
486 class ALSAThread  : public Thread
487 {
488 public:
ALSAThread(const String & inputDeviceID,const String & outputDeviceID)489     ALSAThread (const String& inputDeviceID, const String& outputDeviceID)
490         : Thread ("JUCE ALSA"),
491           inputId (inputDeviceID),
492           outputId (outputDeviceID)
493     {
494         initialiseRatesAndChannels();
495     }
496 
~ALSAThread()497     ~ALSAThread() override
498     {
499         close();
500     }
501 
open(BigInteger inputChannels,BigInteger outputChannels,double newSampleRate,int newBufferSize)502     void open (BigInteger inputChannels,
503                BigInteger outputChannels,
504                double newSampleRate,
505                int newBufferSize)
506     {
507         close();
508 
509         error.clear();
510         sampleRate = newSampleRate;
511         bufferSize = newBufferSize;
512 
513         int maxInputsRequested = inputChannels.getHighestBit() + 1;
514         maxInputsRequested = jmax ((int) minChansIn, jmin ((int) maxChansIn, maxInputsRequested));
515 
516         inputChannelBuffer.setSize (maxInputsRequested, bufferSize);
517         inputChannelBuffer.clear();
518         inputChannelDataForCallback.clear();
519         currentInputChans.clear();
520 
521         if (inputChannels.getHighestBit() >= 0)
522         {
523             for (int i = 0; i < maxInputsRequested; ++i)
524             {
525                 if (inputChannels[i])
526                 {
527                     inputChannelDataForCallback.add (inputChannelBuffer.getReadPointer (i));
528                     currentInputChans.setBit (i);
529                 }
530             }
531         }
532 
533         ensureMinimumNumBitsSet (outputChannels, (int) minChansOut);
534 
535         int maxOutputsRequested = outputChannels.getHighestBit() + 1;
536         maxOutputsRequested = jmax ((int) minChansOut, jmin ((int) maxChansOut, maxOutputsRequested));
537 
538         outputChannelBuffer.setSize (maxOutputsRequested, bufferSize);
539         outputChannelBuffer.clear();
540         outputChannelDataForCallback.clear();
541         currentOutputChans.clear();
542 
543         // Note that the input device is opened before an output, because we've heard
544         // of drivers where doing it in the reverse order mysteriously fails.. If this
545         // order also causes problems, let us know and we'll see if we can find a compromise!
546 
547         if (inputChannelDataForCallback.size() > 0 && inputId.isNotEmpty())
548         {
549             inputDevice.reset (new ALSADevice (inputId, true));
550 
551             if (inputDevice->error.isNotEmpty())
552             {
553                 error = inputDevice->error;
554                 inputDevice.reset();
555                 return;
556             }
557 
558             ensureMinimumNumBitsSet (currentInputChans, (int) minChansIn);
559 
560             if (! inputDevice->setParameters ((unsigned int) sampleRate,
561                                               jlimit ((int) minChansIn, (int) maxChansIn, currentInputChans.getHighestBit() + 1),
562                                               bufferSize))
563             {
564                 error = inputDevice->error;
565                 inputDevice.reset();
566                 return;
567             }
568 
569             inputLatency = inputDevice->latency;
570         }
571 
572         if (outputChannels.getHighestBit() >= 0)
573         {
574             for (int i = 0; i < maxOutputsRequested; ++i)
575             {
576                 if (outputChannels[i])
577                 {
578                     outputChannelDataForCallback.add (outputChannelBuffer.getWritePointer (i));
579                     currentOutputChans.setBit (i);
580                 }
581             }
582         }
583 
584         if (outputChannelDataForCallback.size() > 0 && outputId.isNotEmpty())
585         {
586             outputDevice.reset (new ALSADevice (outputId, false));
587 
588             if (outputDevice->error.isNotEmpty())
589             {
590                 error = outputDevice->error;
591                 outputDevice.reset();
592                 return;
593             }
594 
595             if (! outputDevice->setParameters ((unsigned int) sampleRate,
596                                                jlimit ((int) minChansOut, (int) maxChansOut,
597                                                        currentOutputChans.getHighestBit() + 1),
598                                                bufferSize))
599             {
600                 error = outputDevice->error;
601                 outputDevice.reset();
602                 return;
603             }
604 
605             outputLatency = outputDevice->latency;
606         }
607 
608         if (outputDevice == nullptr && inputDevice == nullptr)
609         {
610             error = "no channels";
611             return;
612         }
613 
614         if (outputDevice != nullptr && inputDevice != nullptr)
615             snd_pcm_link (outputDevice->handle, inputDevice->handle);
616 
617         if (inputDevice != nullptr && JUCE_ALSA_FAILED (snd_pcm_prepare (inputDevice->handle)))
618             return;
619 
620         if (outputDevice != nullptr && JUCE_ALSA_FAILED (snd_pcm_prepare (outputDevice->handle)))
621             return;
622 
623         startThread (9);
624 
625         int count = 1000;
626 
627         while (numCallbacks == 0)
628         {
629             sleep (5);
630 
631             if (--count < 0 || ! isThreadRunning())
632             {
633                 error = "device didn't start";
634                 break;
635             }
636         }
637     }
638 
close()639     void close()
640     {
641         if (isThreadRunning())
642         {
643             // problem: when pulseaudio is suspended (with pasuspend) , the ALSAThread::run is just stuck in
644             // snd_pcm_writei -- no error, no nothing it just stays stuck. So the only way I found to exit "nicely"
645             // (that is without the "killing thread by force" of stopThread) , is to just call snd_pcm_close from
646             // here which will cause the thread to resume, and exit
647             signalThreadShouldExit();
648 
649             const int callbacksToStop = numCallbacks;
650 
651             if ((! waitForThreadToExit (400)) && audioIoInProgress && numCallbacks == callbacksToStop)
652             {
653                 JUCE_ALSA_LOG ("Thread is stuck in i/o.. Is pulseaudio suspended?");
654 
655                 if (outputDevice != nullptr) outputDevice->closeNow();
656                 if (inputDevice != nullptr) inputDevice->closeNow();
657             }
658         }
659 
660         stopThread (6000);
661 
662         inputDevice.reset();
663         outputDevice.reset();
664 
665         inputChannelBuffer.setSize (1, 1);
666         outputChannelBuffer.setSize (1, 1);
667 
668         numCallbacks = 0;
669     }
670 
setCallback(AudioIODeviceCallback * const newCallback)671     void setCallback (AudioIODeviceCallback* const newCallback) noexcept
672     {
673         const ScopedLock sl (callbackLock);
674         callback = newCallback;
675     }
676 
run()677     void run() override
678     {
679         while (! threadShouldExit())
680         {
681             if (inputDevice != nullptr && inputDevice->handle != nullptr)
682             {
683                 if (outputDevice == nullptr || outputDevice->handle == nullptr)
684                 {
685                     JUCE_ALSA_FAILED (snd_pcm_wait (inputDevice->handle, 2000));
686 
687                     if (threadShouldExit())
688                         break;
689 
690                     auto avail = snd_pcm_avail_update (inputDevice->handle);
691 
692                     if (avail < 0)
693                         JUCE_ALSA_FAILED (snd_pcm_recover (inputDevice->handle, (int) avail, 0));
694                 }
695 
696                 audioIoInProgress = true;
697 
698                 if (! inputDevice->readFromInputDevice (inputChannelBuffer, bufferSize))
699                 {
700                     JUCE_ALSA_LOG ("Read failure");
701                     break;
702                 }
703 
704                 audioIoInProgress = false;
705             }
706 
707             if (threadShouldExit())
708                 break;
709 
710             {
711                 const ScopedLock sl (callbackLock);
712                 ++numCallbacks;
713 
714                 if (callback != nullptr)
715                 {
716                     callback->audioDeviceIOCallback (inputChannelDataForCallback.getRawDataPointer(),
717                                                      inputChannelDataForCallback.size(),
718                                                      outputChannelDataForCallback.getRawDataPointer(),
719                                                      outputChannelDataForCallback.size(),
720                                                      bufferSize);
721                 }
722                 else
723                 {
724                     for (int i = 0; i < outputChannelDataForCallback.size(); ++i)
725                         zeromem (outputChannelDataForCallback[i], (size_t) bufferSize * sizeof (float));
726                 }
727             }
728 
729             if (outputDevice != nullptr && outputDevice->handle != nullptr)
730             {
731                 JUCE_ALSA_FAILED (snd_pcm_wait (outputDevice->handle, 2000));
732 
733                 if (threadShouldExit())
734                     break;
735 
736                 auto avail = snd_pcm_avail_update (outputDevice->handle);
737 
738                 if (avail < 0)
739                     JUCE_ALSA_FAILED (snd_pcm_recover (outputDevice->handle, (int) avail, 0));
740 
741                 audioIoInProgress = true;
742 
743                 if (! outputDevice->writeToOutputDevice (outputChannelBuffer, bufferSize))
744                 {
745                     JUCE_ALSA_LOG ("write failure");
746                     break;
747                 }
748 
749                 audioIoInProgress = false;
750             }
751         }
752 
753         audioIoInProgress = false;
754     }
755 
getBitDepth() const756     int getBitDepth() const noexcept
757     {
758         if (outputDevice != nullptr)
759             return outputDevice->bitDepth;
760 
761         if (inputDevice != nullptr)
762             return inputDevice->bitDepth;
763 
764         return 16;
765     }
766 
getXRunCount() const767     int getXRunCount() const noexcept
768     {
769         int result = 0;
770 
771         if (outputDevice != nullptr)
772             result += outputDevice->underrunCount;
773 
774         if (inputDevice != nullptr)
775             result += inputDevice->overrunCount;
776 
777         return result;
778     }
779 
780     //==============================================================================
781     String error;
782     double sampleRate = 0;
783     int bufferSize = 0, outputLatency = 0, inputLatency = 0;
784     BigInteger currentInputChans, currentOutputChans;
785 
786     Array<double> sampleRates;
787     StringArray channelNamesOut, channelNamesIn;
788     AudioIODeviceCallback* callback = nullptr;
789 
790 private:
791     //==============================================================================
792     const String inputId, outputId;
793     std::unique_ptr<ALSADevice> outputDevice, inputDevice;
794     std::atomic<int> numCallbacks { 0 };
795     bool audioIoInProgress = false;
796 
797     CriticalSection callbackLock;
798 
799     AudioBuffer<float> inputChannelBuffer, outputChannelBuffer;
800     Array<const float*> inputChannelDataForCallback;
801     Array<float*> outputChannelDataForCallback;
802 
803     unsigned int minChansOut = 0, maxChansOut = 0;
804     unsigned int minChansIn = 0, maxChansIn = 0;
805 
failed(const int errorNum)806     bool failed (const int errorNum)
807     {
808         if (errorNum >= 0)
809             return false;
810 
811         error = snd_strerror (errorNum);
812         JUCE_ALSA_LOG ("ALSA error: " << error);
813         return true;
814     }
815 
initialiseRatesAndChannels()816     void initialiseRatesAndChannels()
817     {
818         sampleRates.clear();
819         channelNamesOut.clear();
820         channelNamesIn.clear();
821         minChansOut = 0;
822         maxChansOut = 0;
823         minChansIn = 0;
824         maxChansIn = 0;
825         unsigned int dummy = 0;
826 
827         getDeviceProperties (inputId, dummy, dummy, minChansIn, maxChansIn, sampleRates, false, true);
828         getDeviceProperties (outputId, minChansOut, maxChansOut, dummy, dummy, sampleRates, true, false);
829 
830         for (unsigned int i = 0; i < maxChansOut; ++i)
831             channelNamesOut.add ("channel " + String ((int) i + 1));
832 
833         for (unsigned int i = 0; i < maxChansIn; ++i)
834             channelNamesIn.add ("channel " + String ((int) i + 1));
835     }
836 
837     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ALSAThread)
838 };
839 
840 
841 //==============================================================================
842 class ALSAAudioIODevice   : public AudioIODevice
843 {
844 public:
ALSAAudioIODevice(const String & deviceName,const String & deviceTypeName,const String & inputDeviceID,const String & outputDeviceID)845     ALSAAudioIODevice (const String& deviceName,
846                        const String& deviceTypeName,
847                        const String& inputDeviceID,
848                        const String& outputDeviceID)
849         : AudioIODevice (deviceName, deviceTypeName),
850           inputId (inputDeviceID),
851           outputId (outputDeviceID),
852           internal (inputDeviceID, outputDeviceID)
853     {
854     }
855 
~ALSAAudioIODevice()856     ~ALSAAudioIODevice() override
857     {
858         close();
859     }
860 
getOutputChannelNames()861     StringArray getOutputChannelNames() override            { return internal.channelNamesOut; }
getInputChannelNames()862     StringArray getInputChannelNames() override             { return internal.channelNamesIn; }
863 
getAvailableSampleRates()864     Array<double> getAvailableSampleRates() override        { return internal.sampleRates; }
865 
getAvailableBufferSizes()866     Array<int> getAvailableBufferSizes() override
867     {
868         Array<int> r;
869         int n = 16;
870 
871         for (int i = 0; i < 50; ++i)
872         {
873             r.add (n);
874             n += n < 64 ? 16
875                         : (n < 512 ? 32
876                                    : (n < 1024 ? 64
877                                                : (n < 2048 ? 128 : 256)));
878         }
879 
880         return r;
881     }
882 
getDefaultBufferSize()883     int getDefaultBufferSize() override                      { return 512; }
884 
open(const BigInteger & inputChannels,const BigInteger & outputChannels,double sampleRate,int bufferSizeSamples)885     String open (const BigInteger& inputChannels,
886                  const BigInteger& outputChannels,
887                  double sampleRate,
888                  int bufferSizeSamples) override
889     {
890         close();
891 
892         if (bufferSizeSamples <= 0)
893             bufferSizeSamples = getDefaultBufferSize();
894 
895         if (sampleRate <= 0)
896         {
897             for (int i = 0; i < internal.sampleRates.size(); ++i)
898             {
899                 double rate = internal.sampleRates[i];
900 
901                 if (rate >= 44100)
902                 {
903                     sampleRate = rate;
904                     break;
905                 }
906             }
907         }
908 
909         internal.open (inputChannels, outputChannels,
910                        sampleRate, bufferSizeSamples);
911 
912         isOpen_ = internal.error.isEmpty();
913         return internal.error;
914     }
915 
close()916     void close() override
917     {
918         stop();
919         internal.close();
920         isOpen_ = false;
921     }
922 
isOpen()923     bool isOpen() override                           { return isOpen_; }
isPlaying()924     bool isPlaying() override                        { return isStarted && internal.error.isEmpty(); }
getLastError()925     String getLastError() override                   { return internal.error; }
926 
getCurrentBufferSizeSamples()927     int getCurrentBufferSizeSamples() override       { return internal.bufferSize; }
getCurrentSampleRate()928     double getCurrentSampleRate() override           { return internal.sampleRate; }
getCurrentBitDepth()929     int getCurrentBitDepth() override                { return internal.getBitDepth(); }
930 
getActiveOutputChannels() const931     BigInteger getActiveOutputChannels() const override    { return internal.currentOutputChans; }
getActiveInputChannels() const932     BigInteger getActiveInputChannels() const override     { return internal.currentInputChans; }
933 
getOutputLatencyInSamples()934     int getOutputLatencyInSamples() override         { return internal.outputLatency; }
getInputLatencyInSamples()935     int getInputLatencyInSamples() override          { return internal.inputLatency; }
936 
getXRunCount() const937     int getXRunCount() const noexcept override       { return internal.getXRunCount(); }
938 
start(AudioIODeviceCallback * callback)939     void start (AudioIODeviceCallback* callback) override
940     {
941         if (! isOpen_)
942             callback = nullptr;
943 
944         if (callback != nullptr)
945             callback->audioDeviceAboutToStart (this);
946 
947         internal.setCallback (callback);
948 
949         isStarted = (callback != nullptr);
950     }
951 
stop()952     void stop() override
953     {
954         auto oldCallback = internal.callback;
955 
956         start (nullptr);
957 
958         if (oldCallback != nullptr)
959             oldCallback->audioDeviceStopped();
960     }
961 
962     String inputId, outputId;
963 
964 private:
965     bool isOpen_ = false, isStarted = false;
966     ALSAThread internal;
967 };
968 
969 
970 //==============================================================================
971 class ALSAAudioIODeviceType  : public AudioIODeviceType
972 {
973 public:
ALSAAudioIODeviceType(bool onlySoundcards,const String & deviceTypeName)974     ALSAAudioIODeviceType (bool onlySoundcards, const String& deviceTypeName)
975         : AudioIODeviceType (deviceTypeName),
976           listOnlySoundcards (onlySoundcards)
977     {
978        #if ! JUCE_ALSA_LOGGING
979         snd_lib_error_set_handler (&silentErrorHandler);
980        #endif
981     }
982 
~ALSAAudioIODeviceType()983     ~ALSAAudioIODeviceType()
984     {
985        #if ! JUCE_ALSA_LOGGING
986         snd_lib_error_set_handler (nullptr);
987        #endif
988 
989         snd_config_update_free_global(); // prevent valgrind from screaming about alsa leaks
990     }
991 
992     //==============================================================================
scanForDevices()993     void scanForDevices()
994     {
995         if (hasScanned)
996             return;
997 
998         hasScanned = true;
999         inputNames.clear();
1000         inputIds.clear();
1001         outputNames.clear();
1002         outputIds.clear();
1003 
1004         JUCE_ALSA_LOG ("scanForDevices()");
1005 
1006         if (listOnlySoundcards)
1007             enumerateAlsaSoundcards();
1008         else
1009             enumerateAlsaPCMDevices();
1010 
1011         inputNames.appendNumbersToDuplicates (false, true);
1012         outputNames.appendNumbersToDuplicates (false, true);
1013     }
1014 
getDeviceNames(bool wantInputNames) const1015     StringArray getDeviceNames (bool wantInputNames) const
1016     {
1017         jassert (hasScanned); // need to call scanForDevices() before doing this
1018 
1019         return wantInputNames ? inputNames : outputNames;
1020     }
1021 
getDefaultDeviceIndex(bool forInput) const1022     int getDefaultDeviceIndex (bool forInput) const
1023     {
1024         jassert (hasScanned); // need to call scanForDevices() before doing this
1025 
1026         auto idx = (forInput ? inputIds : outputIds).indexOf ("default");
1027         return idx >= 0 ? idx : 0;
1028     }
1029 
hasSeparateInputsAndOutputs() const1030     bool hasSeparateInputsAndOutputs() const    { return true; }
1031 
getIndexOfDevice(AudioIODevice * device,bool asInput) const1032     int getIndexOfDevice (AudioIODevice* device, bool asInput) const
1033     {
1034         jassert (hasScanned); // need to call scanForDevices() before doing this
1035 
1036         if (auto* d = dynamic_cast<ALSAAudioIODevice*> (device))
1037             return asInput ? inputIds.indexOf (d->inputId)
1038                            : outputIds.indexOf (d->outputId);
1039 
1040         return -1;
1041     }
1042 
createDevice(const String & outputDeviceName,const String & inputDeviceName)1043     AudioIODevice* createDevice (const String& outputDeviceName,
1044                                  const String& inputDeviceName)
1045     {
1046         jassert (hasScanned); // need to call scanForDevices() before doing this
1047 
1048         auto inputIndex = inputNames.indexOf (inputDeviceName);
1049         auto outputIndex = outputNames.indexOf (outputDeviceName);
1050 
1051         String deviceName (outputIndex >= 0 ? outputDeviceName
1052                                             : inputDeviceName);
1053 
1054         if (inputIndex >= 0 || outputIndex >= 0)
1055             return new ALSAAudioIODevice (deviceName, getTypeName(),
1056                                           inputIds [inputIndex],
1057                                           outputIds [outputIndex]);
1058 
1059         return nullptr;
1060     }
1061 
1062 private:
1063     //==============================================================================
1064     StringArray inputNames, outputNames, inputIds, outputIds;
1065     bool hasScanned = false;
1066     const bool listOnlySoundcards;
1067 
testDevice(const String & id,const String & outputName,const String & inputName)1068     bool testDevice (const String& id, const String& outputName, const String& inputName)
1069     {
1070         unsigned int minChansOut = 0, maxChansOut = 0;
1071         unsigned int minChansIn = 0, maxChansIn = 0;
1072         Array<double> rates;
1073 
1074         bool isInput = inputName.isNotEmpty(), isOutput = outputName.isNotEmpty();
1075         getDeviceProperties (id, minChansOut, maxChansOut, minChansIn, maxChansIn, rates, isOutput, isInput);
1076 
1077         isInput  = maxChansIn > 0;
1078         isOutput = maxChansOut > 0;
1079 
1080         if ((isInput || isOutput) && rates.size() > 0)
1081         {
1082             JUCE_ALSA_LOG ("testDevice: '" << id.toUTF8().getAddress() << "' -> isInput: "
1083                             << (int) isInput << ", isOutput: " << (int) isOutput);
1084 
1085             if (isInput)
1086             {
1087                 inputNames.add (inputName);
1088                 inputIds.add (id);
1089             }
1090 
1091             if (isOutput)
1092             {
1093                 outputNames.add (outputName);
1094                 outputIds.add (id);
1095             }
1096 
1097             return isInput || isOutput;
1098         }
1099 
1100         return false;
1101     }
1102 
enumerateAlsaSoundcards()1103     void enumerateAlsaSoundcards()
1104     {
1105         snd_ctl_t* handle = nullptr;
1106         snd_ctl_card_info_t* info = nullptr;
1107         snd_ctl_card_info_alloca (&info);
1108 
1109         int cardNum = -1;
1110 
1111         while (outputIds.size() + inputIds.size() <= 64)
1112         {
1113             snd_card_next (&cardNum);
1114 
1115             if (cardNum < 0)
1116                 break;
1117 
1118             if (JUCE_CHECKED_RESULT (snd_ctl_open (&handle, ("hw:" + String (cardNum)).toRawUTF8(), SND_CTL_NONBLOCK)) >= 0)
1119             {
1120                 if (JUCE_CHECKED_RESULT (snd_ctl_card_info (handle, info)) >= 0)
1121                 {
1122                     String cardId (snd_ctl_card_info_get_id (info));
1123 
1124                     if (cardId.removeCharacters ("0123456789").isEmpty())
1125                         cardId = String (cardNum);
1126 
1127                     String cardName = snd_ctl_card_info_get_name (info);
1128 
1129                     if (cardName.isEmpty())
1130                         cardName = cardId;
1131 
1132                     int device = -1;
1133 
1134                     snd_pcm_info_t* pcmInfo;
1135                     snd_pcm_info_alloca (&pcmInfo);
1136 
1137                     for (;;)
1138                     {
1139                         if (snd_ctl_pcm_next_device (handle, &device) < 0 || device < 0)
1140                             break;
1141 
1142                         snd_pcm_info_set_device (pcmInfo, (unsigned int) device);
1143 
1144                         for (unsigned int subDevice = 0, nbSubDevice = 1; subDevice < nbSubDevice; ++subDevice)
1145                         {
1146                             snd_pcm_info_set_subdevice (pcmInfo, subDevice);
1147                             snd_pcm_info_set_stream (pcmInfo, SND_PCM_STREAM_CAPTURE);
1148                             const bool isInput = (snd_ctl_pcm_info (handle, pcmInfo) >= 0);
1149 
1150                             snd_pcm_info_set_stream (pcmInfo, SND_PCM_STREAM_PLAYBACK);
1151                             const bool isOutput = (snd_ctl_pcm_info (handle, pcmInfo) >= 0);
1152 
1153                             if (! (isInput || isOutput))
1154                                 continue;
1155 
1156                             if (nbSubDevice == 1)
1157                                 nbSubDevice = snd_pcm_info_get_subdevices_count (pcmInfo);
1158 
1159                             String id, name;
1160 
1161                             if (nbSubDevice == 1)
1162                             {
1163                                 id << "hw:" << cardId << "," << device;
1164                                 name << cardName << ", " << snd_pcm_info_get_name (pcmInfo);
1165                             }
1166                             else
1167                             {
1168                                 id << "hw:" << cardId << "," << device << "," << (int) subDevice;
1169                                 name << cardName << ", " << snd_pcm_info_get_name (pcmInfo)
1170                                      << " {" <<  snd_pcm_info_get_subdevice_name (pcmInfo) << "}";
1171                             }
1172 
1173                             JUCE_ALSA_LOG ("Soundcard ID: " << id << ", name: '" << name
1174                                             << ", isInput:"  << (int) isInput
1175                                             << ", isOutput:" << (int) isOutput << "\n");
1176 
1177                             if (isInput)
1178                             {
1179                                 inputNames.add (name);
1180                                 inputIds.add (id);
1181                             }
1182 
1183                             if (isOutput)
1184                             {
1185                                 outputNames.add (name);
1186                                 outputIds.add (id);
1187                             }
1188                         }
1189                     }
1190                 }
1191 
1192                 JUCE_CHECKED_RESULT (snd_ctl_close (handle));
1193             }
1194         }
1195     }
1196 
1197     /* Enumerates all ALSA output devices (as output by the command aplay -L)
1198        Does not try to open the devices (with "testDevice" for example),
1199        so that it also finds devices that are busy and not yet available.
1200     */
enumerateAlsaPCMDevices()1201     void enumerateAlsaPCMDevices()
1202     {
1203         void** hints = nullptr;
1204 
1205         if (JUCE_CHECKED_RESULT (snd_device_name_hint (-1, "pcm", &hints)) == 0)
1206         {
1207             for (char** h = (char**) hints; *h; ++h)
1208             {
1209                 const String id (hintToString (*h, "NAME"));
1210                 const String description (hintToString (*h, "DESC"));
1211                 const String ioid (hintToString (*h, "IOID"));
1212 
1213                 JUCE_ALSA_LOG ("ID: " << id << "; desc: " << description << "; ioid: " << ioid);
1214 
1215                 String ss = id.fromFirstOccurrenceOf ("=", false, false)
1216                               .upToFirstOccurrenceOf (",", false, false);
1217 
1218                 if (id.isEmpty()
1219                      || id.startsWith ("default:") || id.startsWith ("sysdefault:")
1220                      || id.startsWith ("plughw:") || id == "null")
1221                     continue;
1222 
1223                 String name (description.replace ("\n", "; "));
1224 
1225                 if (name.isEmpty())
1226                     name = id;
1227 
1228                 bool isOutput = (ioid != "Input");
1229                 bool isInput  = (ioid != "Output");
1230 
1231                 // alsa is stupid here, it advertises dmix and dsnoop as input/output devices, but
1232                 // opening dmix as input, or dsnoop as output will trigger errors..
1233                 isInput  = isInput  && ! id.startsWith ("dmix");
1234                 isOutput = isOutput && ! id.startsWith ("dsnoop");
1235 
1236                 if (isInput)
1237                 {
1238                     inputNames.add (name);
1239                     inputIds.add (id);
1240                 }
1241 
1242                 if (isOutput)
1243                 {
1244                     outputNames.add (name);
1245                     outputIds.add (id);
1246                 }
1247             }
1248 
1249             snd_device_name_free_hint (hints);
1250         }
1251 
1252         // sometimes the "default" device is not listed, but it is nice to see it explicitly in the list
1253         if (! outputIds.contains ("default"))
1254             testDevice ("default", "Default ALSA Output", "Default ALSA Input");
1255 
1256         // same for the pulseaudio plugin
1257         if (! outputIds.contains ("pulse"))
1258             testDevice ("pulse", "Pulseaudio output", "Pulseaudio input");
1259 
1260         // make sure the default device is listed first, and followed by the pulse device (if present)
1261         auto idx = outputIds.indexOf ("pulse");
1262         outputIds.move (idx, 0);
1263         outputNames.move (idx, 0);
1264 
1265         idx = inputIds.indexOf ("pulse");
1266         inputIds.move (idx, 0);
1267         inputNames.move (idx, 0);
1268 
1269         idx = outputIds.indexOf ("default");
1270         outputIds.move (idx, 0);
1271         outputNames.move (idx, 0);
1272 
1273         idx = inputIds.indexOf ("default");
1274         inputIds.move (idx, 0);
1275         inputNames.move (idx, 0);
1276     }
1277 
hintToString(const void * hints,const char * type)1278     static String hintToString (const void* hints, const char* type)
1279     {
1280         char* hint = snd_device_name_get_hint (hints, type);
1281         auto s = String::fromUTF8 (hint);
1282         ::free (hint);
1283         return s;
1284     }
1285 
1286     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ALSAAudioIODeviceType)
1287 };
1288 
1289 }
1290 
1291 //==============================================================================
createAudioIODeviceType_ALSA_Soundcards()1292 AudioIODeviceType* createAudioIODeviceType_ALSA_Soundcards()
1293 {
1294     return new ALSAAudioIODeviceType (true, "ALSA HW");
1295 }
1296 
createAudioIODeviceType_ALSA_PCMDevices()1297 AudioIODeviceType* createAudioIODeviceType_ALSA_PCMDevices()
1298 {
1299     return new ALSAAudioIODeviceType (false, "ALSA");
1300 }
1301 
1302 } // namespace juce
1303