diff --git a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp index 732c058d10..4685e43509 100644 --- a/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp +++ b/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp @@ -158,6 +158,7 @@ void AudioDeviceManager::createAudioDeviceTypes (OwnedArray& addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_iOSAudio()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_ALSA()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_JACK()); + addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_Bela()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_Oboe()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_OpenSLES()); addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_Android()); diff --git a/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.cpp b/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.cpp index cb609d592f..63f9e7fd5a 100644 --- a/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.cpp +++ b/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.cpp @@ -70,6 +70,10 @@ AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA() AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() { return nullptr; } #endif +#if ! (JUCE_LINUX && JUCE_BELA) +AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Bela() { return nullptr; } +#endif + #if ! JUCE_ANDROID AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android() { return nullptr; } #endif diff --git a/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h b/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h index a3d2d73ac1..f7b3236f63 100644 --- a/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h +++ b/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h @@ -165,6 +165,8 @@ public: static AudioIODeviceType* createAudioIODeviceType_OpenSLES(); /** Creates an Oboe device type if it's available on this platform, or returns null. */ static AudioIODeviceType* createAudioIODeviceType_Oboe(); + /** Creates a Bela device type if it's available on this platform, or returns null. */ + static AudioIODeviceType* createAudioIODeviceType_Bela(); protected: explicit AudioIODeviceType (const String& typeName); diff --git a/modules/juce_audio_devices/juce_audio_devices.cpp b/modules/juce_audio_devices/juce_audio_devices.cpp index 731a63ebeb..67f36912dd 100644 --- a/modules/juce_audio_devices/juce_audio_devices.cpp +++ b/modules/juce_audio_devices/juce_audio_devices.cpp @@ -142,6 +142,15 @@ */ #include #endif + + #if JUCE_BELA + /* Got an include error here? If so, you've either not got the bela headers + installed, or you've not got your paths set up correctly to find its header + files. + */ + #include + #endif + #undef SIZEOF //============================================================================== @@ -207,6 +216,10 @@ #include "native/juce_linux_JackAudio.cpp" #endif + #if JUCE_BELA + #include "native/juce_linux_Bela.cpp" + #endif + //============================================================================== #elif JUCE_ANDROID #include "native/juce_android_Audio.cpp" diff --git a/modules/juce_audio_devices/juce_audio_devices.h b/modules/juce_audio_devices/juce_audio_devices.h index bf18e02efa..fd7555dc81 100644 --- a/modules/juce_audio_devices/juce_audio_devices.h +++ b/modules/juce_audio_devices/juce_audio_devices.h @@ -108,6 +108,13 @@ #define JUCE_JACK 0 #endif +/** Config: JUCE_BELA + Enables Bela audio devices on Bela boards. +*/ +#ifndef JUCE_BELA + #define JUCE_BELA 0 +#endif + /** Config: JUCE_USE_ANDROID_OBOE *** DEVELOPER PREVIEW - Oboe is currently in developer preview and diff --git a/modules/juce_audio_devices/native/juce_linux_Bela.cpp b/modules/juce_audio_devices/native/juce_linux_Bela.cpp new file mode 100644 index 0000000000..cfe8dc891f --- /dev/null +++ b/modules/juce_audio_devices/native/juce_linux_Bela.cpp @@ -0,0 +1,373 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +class BelaAudioIODevice : public AudioIODevice +{ +public: + BelaAudioIODevice() : AudioIODevice (BelaAudioIODevice::belaTypeName, + BelaAudioIODevice::belaTypeName) + { + Bela_defaultSettings (&defaultSettings); + } + + ~BelaAudioIODevice() {} + + //============================================================================== + StringArray getOutputChannelNames() override { return { "Out #1", "Out #2" }; } + StringArray getInputChannelNames() override { return { "In #1", "In #2" }; } + Array getAvailableSampleRates() override { return { 44100.0 }; } + Array getAvailableBufferSizes() override { /* TODO: */ return { getDefaultBufferSize() }; } + int getDefaultBufferSize() override { return defaultSettings.periodSize; } + + //============================================================================== + String open (const BigInteger& inputChannels, + const BigInteger& outputChannels, + double sampleRate, + int bufferSizeSamples) override + { + if (sampleRate != 44100.0 && sampleRate != 0.0) + { + lastError = "Bela audio outputs only support 44.1 kHz sample rate"; + return lastError; + } + + settings = defaultSettings; + + auto numIns = getNumContiguousSetBits (inputChannels); + auto numOuts = getNumContiguousSetBits (outputChannels); + + settings.useAnalog = 0; + settings.useDigital = 0; + settings.numAudioInChannels = numIns; + settings.numAudioOutChannels = numOuts; + settings.detectUnderruns = 1; + settings.setup = setupCallback; + settings.render = renderCallback; + settings.cleanup = cleanupCallback; + settings.interleave = 1; + + if (bufferSizeSamples > 0) + settings.periodSize = bufferSizeSamples; + + isBelaOpen = false; + isRunning = false; + callback = nullptr; + underruns = 0; + + if (Bela_initAudio (&settings, this) != 0 || ! isBelaOpen) + { + lastError = "Bela_initAutio failed"; + return lastError; + } + + actualNumberOfInputs = jmin (numIns, actualNumberOfInputs); + actualNumberOfOutputs = jmin (numOuts, actualNumberOfOutputs); + + audioInBuffer.setSize (actualNumberOfInputs, actualBufferSize); + channelInBuffer.calloc (actualNumberOfInputs); + + audioOutBuffer.setSize (actualNumberOfOutputs, actualBufferSize); + channelOutBuffer.calloc (actualNumberOfOutputs); + + return {}; + } + + void close() override + { + stop(); + + if (isBelaOpen) + { + Bela_cleanupAudio(); + + isBelaOpen = false; + callback = nullptr; + underruns = 0; + + actualBufferSize = 0; + actualNumberOfInputs = 0; + actualNumberOfOutputs = 0; + + audioInBuffer.setSize (0, 0); + channelInBuffer.free(); + + audioOutBuffer.setSize (0, 0); + channelOutBuffer.free(); + } + } + + bool isOpen() override { return isBelaOpen; } + + void start (AudioIODeviceCallback* newCallback) override + { + if (! isBelaOpen) + return; + + if (isRunning) + { + if (newCallback != callback) + { + if (newCallback != nullptr) + newCallback->audioDeviceAboutToStart (this); + + { + ScopedLock lock (callbackLock); + std::swap (callback, newCallback); + } + + if (newCallback != nullptr) + newCallback->audioDeviceStopped(); + } + } + else + { + audioInBuffer.clear(); + audioOutBuffer.clear(); + + callback = newCallback; + isRunning = (Bela_startAudio() == 0); + + if (callback != nullptr) + { + if (isRunning) + { + callback->audioDeviceAboutToStart (this); + } + else + { + lastError = "Bela_StartAudio failed"; + callback->audioDeviceError (lastError); + } + } + } + } + + void stop() override + { + AudioIODeviceCallback* oldCallback = nullptr; + + if (callback != nullptr) + { + ScopedLock lock (callbackLock); + std::swap (callback, oldCallback); + } + + isRunning = false; + Bela_stopAudio(); + + if (oldCallback != nullptr) + oldCallback->audioDeviceStopped(); + } + + bool isPlaying() override { return isRunning; } + String getLastError() override { return lastError; } + + //============================================================================== + int getCurrentBufferSizeSamples() override { return actualBufferSize; } + double getCurrentSampleRate() override { return 44100.0; } + int getCurrentBitDepth() override { return 24; } + BigInteger getActiveOutputChannels() const override { BigInteger b; b.setRange (0, actualNumberOfOutputs, true); return b; } + BigInteger getActiveInputChannels() const override { BigInteger b; b.setRange (0, actualNumberOfInputs, true); return b; } + int getOutputLatencyInSamples() override { /* TODO */ return 0; } + int getInputLatencyInSamples() override { /* TODO */ return 0; } + int getXRunCount() const noexcept { return underruns; } + + //============================================================================== + static const char* const belaTypeName; + +private: + //============================================================================== + bool setup (BelaContext& context) + { + actualBufferSize = context.audioFrames; + actualNumberOfInputs = context.audioInChannels; + actualNumberOfOutputs = context.audioOutChannels; + isBelaOpen = true; + firstCallback = true; + + ScopedLock lock (callbackLock); + + if (callback != nullptr) + callback->audioDeviceAboutToStart (this); + + return true; + } + + void render (BelaContext& context) + { + // check for xruns + calculateXruns (context.audioFramesElapsed, context.audioFrames); + + ScopedLock lock (callbackLock); + + if (callback != nullptr) + { + jassert (context.audioFrames <= actualBufferSize); + auto numSamples = jmin (context.audioFrames, actualBufferSize); + auto interleaved = ((context.flags & BELA_FLAG_INTERLEAVED) != 0); + auto numIns = jmin (actualNumberOfInputs, (int) context.audioInChannels); + auto numOuts = jmin (actualNumberOfOutputs, (int) context.audioOutChannels); + + int ch; + + if (interleaved && context.audioInChannels > 1) + { + for (ch = 0; ch < numIns; ++ch) + { + using DstSampleType = AudioData::Pointer; + using SrcSampleType = AudioData::Pointer; + + channelInBuffer[ch] = audioInBuffer.getWritePointer (ch); + DstSampleType dstData (audioInBuffer.getWritePointer (ch)); + SrcSampleType srcData (context.audioIn + ch, context.audioInChannels); + dstData.convertSamples (srcData, numSamples); + } + } + else + { + for (ch = 0; ch < numIns; ++ch) + channelInBuffer[ch] = context.audioIn + (ch * numSamples); + } + + for (; ch < actualNumberOfInputs; ++ch) + { + channelInBuffer[ch] = audioInBuffer.getWritePointer(ch); + zeromem (audioInBuffer.getWritePointer (ch), sizeof (float) * numSamples); + } + + for (int i = 0; i < actualNumberOfOutputs; ++i) + channelOutBuffer[i] = ((interleaved && context.audioOutChannels > 1) || i >= context.audioOutChannels ? audioOutBuffer.getWritePointer (i) + : context.audioOut + (i * numSamples)); + + callback->audioDeviceIOCallback (channelInBuffer.getData(), actualNumberOfInputs, + channelOutBuffer.getData(), actualNumberOfOutputs, + numSamples); + + if (interleaved && context.audioOutChannels > 1) + { + for (int i = 0; i < numOuts; ++i) + { + using DstSampleType = AudioData::Pointer; + using SrcSampleType = AudioData::Pointer; + + SrcSampleType srcData (channelOutBuffer[i]); + DstSampleType dstData (context.audioOut + i, context.audioOutChannels); + + dstData.convertSamples (srcData, numSamples); + } + } + } + } + + void cleanup (BelaContext&) + { + ScopedLock lock (callbackLock); + + if (callback != nullptr) + callback->audioDeviceStopped(); + } + + + //============================================================================== + uint64_t expectedElapsedAudioSamples = 0; + int underruns = 0; + bool firstCallback = false; + + void calculateXruns (uint64_t audioFramesElapsed, uint32_t numSamples) + { + if (audioFramesElapsed > expectedElapsedAudioSamples && ! firstCallback) + ++underruns; + + firstCallback = false; + expectedElapsedAudioSamples = audioFramesElapsed + numSamples; + } + + //============================================================================== + static int getNumContiguousSetBits (const BigInteger& value) noexcept + { + int bit = 0; + + while (value[bit]) + ++bit; + + return bit; + } + + //============================================================================== + static bool setupCallback (BelaContext* context, void* userData) noexcept { return static_cast (userData)->setup (*context); } + static void renderCallback (BelaContext* context, void* userData) noexcept { static_cast (userData)->render (*context); } + static void cleanupCallback (BelaContext* context, void* userData) noexcept { static_cast (userData)->cleanup (*context); } + + //============================================================================== + BelaInitSettings defaultSettings, settings; + bool isBelaOpen = false, isRunning = false; + + CriticalSection callbackLock; + AudioIODeviceCallback* callback = nullptr; + + String lastError; + uint32_t actualBufferSize = 0; + int actualNumberOfInputs = 0, actualNumberOfOutputs = 0; + + AudioBuffer audioInBuffer, audioOutBuffer; + HeapBlock channelInBuffer; + HeapBlock channelOutBuffer; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BelaAudioIODevice) +}; + +const char* const BelaAudioIODevice::belaTypeName = "Bela Analog"; + +//============================================================================== +struct BelaAudioIODeviceType : public AudioIODeviceType +{ + BelaAudioIODeviceType() : AudioIODeviceType ("Bela") {} + + // TODO: support analog outputs + StringArray getDeviceNames (bool) const override { return StringArray (BelaAudioIODevice::belaTypeName); } + void scanForDevices() override {} + int getDefaultDeviceIndex (bool) const override { return 0; } + int getIndexOfDevice (AudioIODevice* device, bool) const override { return device != nullptr ? 0 : -1; } + bool hasSeparateInputsAndOutputs() const override { return false; } + + AudioIODevice* createDevice (const String& outputName, const String& inputName) override + { + if (outputName == BelaAudioIODevice::belaTypeName || inputName == BelaAudioIODevice::belaTypeName) + return new BelaAudioIODevice(); + + return nullptr; + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BelaAudioIODeviceType) +}; + +//============================================================================== +AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Bela() +{ + return new BelaAudioIODeviceType(); +} + +} // namespace juce