diff --git a/source/modules/juce_audio_devices/Makefile b/source/modules/juce_audio_devices/Makefile deleted file mode 100644 index 4910fd058..000000000 --- a/source/modules/juce_audio_devices/Makefile +++ /dev/null @@ -1,125 +0,0 @@ -#!/usr/bin/make -f -# Makefile for juce_audio_devices # -# ------------------------------- # -# Created by falkTX -# - -CWD=../.. -MODULENAME=juce_audio_devices -include ../Makefile.mk - -# ---------------------------------------------------------------------------------------------------------------------------- - -BUILD_CXX_FLAGS += $(JUCE_AUDIO_DEVICES_FLAGS) -I.. - -ifeq ($(WIN32),true) -BUILD_CXX_FLAGS += -Wno-missing-field-initializers -# BUILD_CXX_FLAGS += -I$(CWD)/includes/asio -BUILD_CXX_FLAGS += -I$(CWD)/modules/rtaudio/include -endif - -# ---------------------------------------------------------------------------------------------------------------------------- - -ifeq ($(MACOS),true) -OBJS = $(OBJDIR)/$(MODULENAME).mm.o -OBJS_posix32 = $(OBJDIR)/$(MODULENAME).mm.posix32.o -OBJS_posix64 = $(OBJDIR)/$(MODULENAME).mm.posix64.o -else -OBJS = $(OBJDIR)/$(MODULENAME).cpp.o -OBJS_posix32 = $(OBJDIR)/$(MODULENAME).cpp.posix32.o -OBJS_posix64 = $(OBJDIR)/$(MODULENAME).cpp.posix64.o -endif -OBJS_win32 = $(OBJDIR)/$(MODULENAME).cpp.win32.o -OBJS_win64 = $(OBJDIR)/$(MODULENAME).cpp.win64.o - -# ---------------------------------------------------------------------------------------------------------------------------- - -all: $(MODULEDIR)/$(MODULENAME).a -posix32: $(MODULEDIR)/$(MODULENAME).posix32.a -posix64: $(MODULEDIR)/$(MODULENAME).posix64.a -win32: $(MODULEDIR)/$(MODULENAME).win32.a -win64: $(MODULEDIR)/$(MODULENAME).win64.a - -# ---------------------------------------------------------------------------------------------------------------------------- - -clean: - rm -f $(OBJDIR)/*.o $(MODULEDIR)/$(MODULENAME)*.a - -debug: - $(MAKE) DEBUG=true - -# ---------------------------------------------------------------------------------------------------------------------------- - -$(MODULEDIR)/$(MODULENAME).a: $(OBJS) - -@mkdir -p $(MODULEDIR) - @echo "Creating $(MODULENAME).a" - @rm -f $@ - @$(AR) crs $@ $^ - -$(MODULEDIR)/$(MODULENAME).posix32.a: $(OBJS_posix32) - -@mkdir -p $(MODULEDIR) - @echo "Creating $(MODULENAME).posix32.a" - @rm -f $@ - @$(AR) crs $@ $^ - -$(MODULEDIR)/$(MODULENAME).posix64.a: $(OBJS_posix64) - -@mkdir -p $(MODULEDIR) - @echo "Creating $(MODULENAME).posix64.a" - @rm -f $@ - @$(AR) crs $@ $^ - -$(MODULEDIR)/$(MODULENAME).win32.a: $(OBJS_win32) - -@mkdir -p $(MODULEDIR) - @echo "Creating $(MODULENAME).win32.a" - @rm -f $@ - @$(AR) crs $@ $^ - -$(MODULEDIR)/$(MODULENAME).win64.a: $(OBJS_win64) - -@mkdir -p $(MODULEDIR) - @echo "Creating $(MODULENAME).win64.a" - @rm -f $@ - @$(AR) crs $@ $^ - -# ---------------------------------------------------------------------------------------------------------------------------- - -$(OBJDIR)/$(MODULENAME).cpp.o: $(MODULENAME).cpp - -@mkdir -p $(OBJDIR) - @echo "Compiling $<" - @$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ - -$(OBJDIR)/$(MODULENAME).cpp.%32.o: $(MODULENAME).cpp - -@mkdir -p $(OBJDIR) - @echo "Compiling $< (32bit)" - @$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -c -o $@ - -$(OBJDIR)/$(MODULENAME).cpp.%64.o: $(MODULENAME).cpp - -@mkdir -p $(OBJDIR) - @echo "Compiling $< (64bit)" - @$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -c -o $@ - -# ---------------------------------------------------------------------------------------------------------------------------- - -$(OBJDIR)/$(MODULENAME).mm.o: $(MODULENAME).cpp - -@mkdir -p $(OBJDIR) - @echo "Compiling $<" - @$(CXX) $< $(BUILD_CXX_FLAGS) -ObjC++ -c -o $@ - -$(OBJDIR)/$(MODULENAME).mm.%32.o: $(MODULENAME).cpp - -@mkdir -p $(OBJDIR) - @echo "Compiling $< (32bit)" - @$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -ObjC++ -c -o $@ - -$(OBJDIR)/$(MODULENAME).mm.%64.o: $(MODULENAME).cpp - -@mkdir -p $(OBJDIR) - @echo "Compiling $< (64bit)" - @$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -ObjC++ -c -o $@ - -# ---------------------------------------------------------------------------------------------------------------------------- - --include $(OBJS:%.o=%.d) --include $(OBJS_posix32:%.o=%.d) --include $(OBJS_posix64:%.o=%.d) --include $(OBJS_win32:%.o=%.d) --include $(OBJS_win64:%.o=%.d) - -# ---------------------------------------------------------------------------------------------------------------------------- diff --git a/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp b/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp deleted file mode 100644 index 1cc237e58..000000000 --- a/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp +++ /dev/null @@ -1,1018 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -AudioDeviceManager::AudioDeviceSetup::AudioDeviceSetup() - : sampleRate (0), - bufferSize (0), - useDefaultInputChannels (true), - useDefaultOutputChannels (true) -{ -} - -bool AudioDeviceManager::AudioDeviceSetup::operator== (const AudioDeviceManager::AudioDeviceSetup& other) const -{ - return outputDeviceName == other.outputDeviceName - && inputDeviceName == other.inputDeviceName - && sampleRate == other.sampleRate - && bufferSize == other.bufferSize - && inputChannels == other.inputChannels - && useDefaultInputChannels == other.useDefaultInputChannels - && outputChannels == other.outputChannels - && useDefaultOutputChannels == other.useDefaultOutputChannels; -} - -//============================================================================== -class AudioDeviceManager::CallbackHandler : public AudioIODeviceCallback, - public MidiInputCallback, - public AudioIODeviceType::Listener -{ -public: - CallbackHandler (AudioDeviceManager& adm) noexcept : owner (adm) {} - -private: - void audioDeviceIOCallback (const float** ins, int numIns, float** outs, int numOuts, int numSamples) override - { - owner.audioDeviceIOCallbackInt (ins, numIns, outs, numOuts, numSamples); - } - - void audioDeviceAboutToStart (AudioIODevice* device) override - { - owner.audioDeviceAboutToStartInt (device); - } - - void audioDeviceStopped() override - { - owner.audioDeviceStoppedInt(); - } - - void audioDeviceError (const String& message) override - { - owner.audioDeviceErrorInt (message); - } - - void handleIncomingMidiMessage (MidiInput* source, const MidiMessage& message) override - { - owner.handleIncomingMidiMessageInt (source, message); - } - - void audioDeviceListChanged() override - { - owner.audioDeviceListChanged(); - } - - AudioDeviceManager& owner; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CallbackHandler) -}; - -//============================================================================== -AudioDeviceManager::AudioDeviceManager() - : numInputChansNeeded (0), - numOutputChansNeeded (2), - listNeedsScanning (true), - testSoundPosition (0), - cpuUsageMs (0), - timeToCpuScale (0) -{ - callbackHandler = new CallbackHandler (*this); -} - -AudioDeviceManager::~AudioDeviceManager() -{ - currentAudioDevice = nullptr; - defaultMidiOutput = nullptr; -} - -//============================================================================== -void AudioDeviceManager::createDeviceTypesIfNeeded() -{ - if (availableDeviceTypes.size() == 0) - { - OwnedArray types; - createAudioDeviceTypes (types); - - for (int i = 0; i < types.size(); ++i) - addAudioDeviceType (types.getUnchecked(i)); - - types.clear (false); - - if (AudioIODeviceType* first = availableDeviceTypes.getFirst()) - currentDeviceType = first->getTypeName(); - } -} - -const OwnedArray& AudioDeviceManager::getAvailableDeviceTypes() -{ - scanDevicesIfNeeded(); - return availableDeviceTypes; -} - -void AudioDeviceManager::audioDeviceListChanged() -{ - if (currentAudioDevice != nullptr) - { - currentSetup.sampleRate = currentAudioDevice->getCurrentSampleRate(); - currentSetup.bufferSize = currentAudioDevice->getCurrentBufferSizeSamples(); - currentSetup.inputChannels = currentAudioDevice->getActiveInputChannels(); - currentSetup.outputChannels = currentAudioDevice->getActiveOutputChannels(); - } - - sendChangeMessage(); -} - -//============================================================================== -static void addIfNotNull (OwnedArray& list, AudioIODeviceType* const device) -{ - if (device != nullptr) - list.add (device); -} - -void AudioDeviceManager::createAudioDeviceTypes (OwnedArray& list) -{ - addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_WASAPI (false)); - addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_WASAPI (true)); - addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_DirectSound()); - addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_ASIO()); - addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_CoreAudio()); - addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_iOSAudio()); - addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_JACK()); - addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_ALSA()); - addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_OpenSLES()); - addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_Android()); -} - -void AudioDeviceManager::addAudioDeviceType (AudioIODeviceType* newDeviceType) -{ - if (newDeviceType != nullptr) - { - jassert (lastDeviceTypeConfigs.size() == availableDeviceTypes.size()); - availableDeviceTypes.add (newDeviceType); - lastDeviceTypeConfigs.add (new AudioDeviceSetup()); - - newDeviceType->addListener (callbackHandler); - } -} - -static bool deviceListContains (AudioIODeviceType* type, bool isInput, const String& name) -{ - StringArray devices (type->getDeviceNames (isInput)); - - for (int i = devices.size(); --i >= 0;) - if (devices[i].trim().equalsIgnoreCase (name.trim())) - return true; - - return false; -} - -//============================================================================== -String AudioDeviceManager::initialise (const int numInputChannelsNeeded, - const int numOutputChannelsNeeded, - const XmlElement* const xml, - const bool selectDefaultDeviceOnFailure, - const String& preferredDefaultDeviceName, - const AudioDeviceSetup* preferredSetupOptions) -{ - scanDevicesIfNeeded(); - - numInputChansNeeded = numInputChannelsNeeded; - numOutputChansNeeded = numOutputChannelsNeeded; - - if (xml != nullptr && xml->hasTagName ("DEVICESETUP")) - return initialiseFromXML (*xml, selectDefaultDeviceOnFailure, - preferredDefaultDeviceName, preferredSetupOptions); - - return initialiseDefault (preferredDefaultDeviceName, preferredSetupOptions); -} - -String AudioDeviceManager::initialiseDefault (const String& preferredDefaultDeviceName, - const AudioDeviceSetup* preferredSetupOptions) -{ - AudioDeviceSetup setup; - - if (preferredSetupOptions != nullptr) - { - setup = *preferredSetupOptions; - } - else if (preferredDefaultDeviceName.isNotEmpty()) - { - for (int j = availableDeviceTypes.size(); --j >= 0;) - { - AudioIODeviceType* const type = availableDeviceTypes.getUnchecked(j); - - const StringArray outs (type->getDeviceNames (false)); - - for (int i = 0; i < outs.size(); ++i) - { - if (outs[i].matchesWildcard (preferredDefaultDeviceName, true)) - { - setup.outputDeviceName = outs[i]; - break; - } - } - - const StringArray ins (type->getDeviceNames (true)); - - for (int i = 0; i < ins.size(); ++i) - { - if (ins[i].matchesWildcard (preferredDefaultDeviceName, true)) - { - setup.inputDeviceName = ins[i]; - break; - } - } - } - } - - insertDefaultDeviceNames (setup); - return setAudioDeviceSetup (setup, false); -} - -String AudioDeviceManager::initialiseFromXML (const XmlElement& xml, - const bool selectDefaultDeviceOnFailure, - const String& preferredDefaultDeviceName, - const AudioDeviceSetup* preferredSetupOptions) -{ - lastExplicitSettings = new XmlElement (xml); - - String error; - AudioDeviceSetup setup; - - if (preferredSetupOptions != nullptr) - setup = *preferredSetupOptions; - - if (xml.getStringAttribute ("audioDeviceName").isNotEmpty()) - { - setup.inputDeviceName = setup.outputDeviceName - = xml.getStringAttribute ("audioDeviceName"); - } - else - { - setup.inputDeviceName = xml.getStringAttribute ("audioInputDeviceName"); - setup.outputDeviceName = xml.getStringAttribute ("audioOutputDeviceName"); - } - - currentDeviceType = xml.getStringAttribute ("deviceType"); - - if (findType (currentDeviceType) == nullptr) - { - if (AudioIODeviceType* const type = findType (setup.inputDeviceName, setup.outputDeviceName)) - currentDeviceType = type->getTypeName(); - else if (availableDeviceTypes.size() > 0) - currentDeviceType = availableDeviceTypes.getUnchecked(0)->getTypeName(); - } - - setup.bufferSize = xml.getIntAttribute ("audioDeviceBufferSize", setup.bufferSize); - setup.sampleRate = xml.getDoubleAttribute ("audioDeviceRate", setup.sampleRate); - - setup.inputChannels .parseString (xml.getStringAttribute ("audioDeviceInChans", "11"), 2); - setup.outputChannels.parseString (xml.getStringAttribute ("audioDeviceOutChans", "11"), 2); - - setup.useDefaultInputChannels = ! xml.hasAttribute ("audioDeviceInChans"); - setup.useDefaultOutputChannels = ! xml.hasAttribute ("audioDeviceOutChans"); - - error = setAudioDeviceSetup (setup, true); - - midiInsFromXml.clear(); - - forEachXmlChildElementWithTagName (xml, c, "MIDIINPUT") - midiInsFromXml.add (c->getStringAttribute ("name")); - - const StringArray allMidiIns (MidiInput::getDevices()); - - for (int i = allMidiIns.size(); --i >= 0;) - setMidiInputEnabled (allMidiIns[i], midiInsFromXml.contains (allMidiIns[i])); - - if (error.isNotEmpty() && selectDefaultDeviceOnFailure) - error = initialise (numInputChansNeeded, numOutputChansNeeded, - nullptr, false, preferredDefaultDeviceName); - - setDefaultMidiOutput (xml.getStringAttribute ("defaultMidiOutput")); - - return error; -} - -String AudioDeviceManager::initialiseWithDefaultDevices (int numInputChannelsNeeded, - int numOutputChannelsNeeded) -{ - lastExplicitSettings = nullptr; - - return initialise (numInputChannelsNeeded, numOutputChannelsNeeded, - nullptr, false, String(), nullptr); -} - -void AudioDeviceManager::insertDefaultDeviceNames (AudioDeviceSetup& setup) const -{ - if (AudioIODeviceType* type = getCurrentDeviceTypeObject()) - { - if (setup.outputDeviceName.isEmpty()) - setup.outputDeviceName = type->getDeviceNames (false) [type->getDefaultDeviceIndex (false)]; - - if (setup.inputDeviceName.isEmpty()) - setup.inputDeviceName = type->getDeviceNames (true) [type->getDefaultDeviceIndex (true)]; - } -} - -XmlElement* AudioDeviceManager::createStateXml() const -{ - return lastExplicitSettings.createCopy(); -} - -//============================================================================== -void AudioDeviceManager::scanDevicesIfNeeded() -{ - if (listNeedsScanning) - { - listNeedsScanning = false; - - createDeviceTypesIfNeeded(); - - for (int i = availableDeviceTypes.size(); --i >= 0;) - availableDeviceTypes.getUnchecked(i)->scanForDevices(); - } -} - -AudioIODeviceType* AudioDeviceManager::findType (const String& typeName) -{ - scanDevicesIfNeeded(); - - for (int i = availableDeviceTypes.size(); --i >= 0;) - if (availableDeviceTypes.getUnchecked(i)->getTypeName() == typeName) - return availableDeviceTypes.getUnchecked(i); - - return nullptr; -} - -AudioIODeviceType* AudioDeviceManager::findType (const String& inputName, const String& outputName) -{ - scanDevicesIfNeeded(); - - for (int i = availableDeviceTypes.size(); --i >= 0;) - { - AudioIODeviceType* const type = availableDeviceTypes.getUnchecked(i); - - if ((inputName.isNotEmpty() && deviceListContains (type, true, inputName)) - || (outputName.isNotEmpty() && deviceListContains (type, false, outputName))) - { - return type; - } - } - - return nullptr; -} - -void AudioDeviceManager::getAudioDeviceSetup (AudioDeviceSetup& setup) const -{ - setup = currentSetup; -} - -void AudioDeviceManager::deleteCurrentDevice() -{ - currentAudioDevice = nullptr; - currentSetup.inputDeviceName.clear(); - currentSetup.outputDeviceName.clear(); -} - -void AudioDeviceManager::setCurrentAudioDeviceType (const String& type, - const bool treatAsChosenDevice) -{ - for (int i = 0; i < availableDeviceTypes.size(); ++i) - { - if (availableDeviceTypes.getUnchecked(i)->getTypeName() == type - && currentDeviceType != type) - { - if (currentAudioDevice != nullptr) - { - closeAudioDevice(); - Thread::sleep (1500); // allow a moment for OS devices to sort themselves out, to help - // avoid things like DirectSound/ASIO clashes - } - - currentDeviceType = type; - - AudioDeviceSetup s (*lastDeviceTypeConfigs.getUnchecked(i)); - insertDefaultDeviceNames (s); - - setAudioDeviceSetup (s, treatAsChosenDevice); - - sendChangeMessage(); - break; - } - } -} - -AudioIODeviceType* AudioDeviceManager::getCurrentDeviceTypeObject() const -{ - for (int i = 0; i < availableDeviceTypes.size(); ++i) - if (availableDeviceTypes.getUnchecked(i)->getTypeName() == currentDeviceType) - return availableDeviceTypes.getUnchecked(i); - - return availableDeviceTypes[0]; -} - -String AudioDeviceManager::setAudioDeviceSetup (const AudioDeviceSetup& newSetup, - const bool treatAsChosenDevice) -{ - jassert (&newSetup != ¤tSetup); // this will have no effect - - if (newSetup == currentSetup && currentAudioDevice != nullptr) - return {}; - - if (! (newSetup == currentSetup)) - sendChangeMessage(); - - stopDevice(); - - const String newInputDeviceName (numInputChansNeeded == 0 ? String() : newSetup.inputDeviceName); - const String newOutputDeviceName (numOutputChansNeeded == 0 ? String() : newSetup.outputDeviceName); - - String error; - AudioIODeviceType* type = getCurrentDeviceTypeObject(); - - if (type == nullptr || (newInputDeviceName.isEmpty() && newOutputDeviceName.isEmpty())) - { - deleteCurrentDevice(); - - if (treatAsChosenDevice) - updateXml(); - - return {}; - } - - if (currentSetup.inputDeviceName != newInputDeviceName - || currentSetup.outputDeviceName != newOutputDeviceName - || currentAudioDevice == nullptr) - { - deleteCurrentDevice(); - scanDevicesIfNeeded(); - - if (newOutputDeviceName.isNotEmpty() && ! deviceListContains (type, false, newOutputDeviceName)) - return "No such device: " + newOutputDeviceName; - - if (newInputDeviceName.isNotEmpty() && ! deviceListContains (type, true, newInputDeviceName)) - return "No such device: " + newInputDeviceName; - - currentAudioDevice = type->createDevice (newOutputDeviceName, newInputDeviceName); - - if (currentAudioDevice == nullptr) - error = "Can't open the audio device!\n\n" - "This may be because another application is currently using the same device - " - "if so, you should close any other applications and try again!"; - else - error = currentAudioDevice->getLastError(); - - if (error.isNotEmpty()) - { - deleteCurrentDevice(); - return error; - } - - if (newSetup.useDefaultInputChannels) - { - inputChannels.clear(); - inputChannels.setRange (0, numInputChansNeeded, true); - } - - if (newSetup.useDefaultOutputChannels) - { - outputChannels.clear(); - outputChannels.setRange (0, numOutputChansNeeded, true); - } - - if (newInputDeviceName.isEmpty()) inputChannels.clear(); - if (newOutputDeviceName.isEmpty()) outputChannels.clear(); - } - - if (! newSetup.useDefaultInputChannels) inputChannels = newSetup.inputChannels; - if (! newSetup.useDefaultOutputChannels) outputChannels = newSetup.outputChannels; - - currentSetup = newSetup; - - currentSetup.sampleRate = chooseBestSampleRate (newSetup.sampleRate); - currentSetup.bufferSize = chooseBestBufferSize (newSetup.bufferSize); - - error = currentAudioDevice->open (inputChannels, - outputChannels, - currentSetup.sampleRate, - currentSetup.bufferSize); - - if (error.isEmpty()) - { - currentDeviceType = currentAudioDevice->getTypeName(); - - currentAudioDevice->start (callbackHandler); - - currentSetup.sampleRate = currentAudioDevice->getCurrentSampleRate(); - currentSetup.bufferSize = currentAudioDevice->getCurrentBufferSizeSamples(); - currentSetup.inputChannels = currentAudioDevice->getActiveInputChannels(); - currentSetup.outputChannels = currentAudioDevice->getActiveOutputChannels(); - - for (int i = 0; i < availableDeviceTypes.size(); ++i) - if (availableDeviceTypes.getUnchecked (i)->getTypeName() == currentDeviceType) - *(lastDeviceTypeConfigs.getUnchecked (i)) = currentSetup; - - if (treatAsChosenDevice) - updateXml(); - } - else - { - deleteCurrentDevice(); - } - - return error; -} - -double AudioDeviceManager::chooseBestSampleRate (double rate) const -{ - jassert (currentAudioDevice != nullptr); - - const Array rates (currentAudioDevice->getAvailableSampleRates()); - - if (rate > 0 && rates.contains (rate)) - return rate; - - rate = currentAudioDevice->getCurrentSampleRate(); - - if (rate > 0 && rates.contains (rate)) - return rate; - - double lowestAbove44 = 0.0; - - for (int i = rates.size(); --i >= 0;) - { - const double sr = rates[i]; - - if (sr >= 44100.0 && (lowestAbove44 < 1.0 || sr < lowestAbove44)) - lowestAbove44 = sr; - } - - if (lowestAbove44 > 0.0) - return lowestAbove44; - - return rates[0]; -} - -int AudioDeviceManager::chooseBestBufferSize (int bufferSize) const -{ - jassert (currentAudioDevice != nullptr); - - if (bufferSize > 0 && currentAudioDevice->getAvailableBufferSizes().contains (bufferSize)) - return bufferSize; - - return currentAudioDevice->getDefaultBufferSize(); -} - -void AudioDeviceManager::stopDevice() -{ - if (currentAudioDevice != nullptr) - currentAudioDevice->stop(); - - testSound = nullptr; -} - -void AudioDeviceManager::closeAudioDevice() -{ - stopDevice(); - currentAudioDevice = nullptr; -} - -void AudioDeviceManager::restartLastAudioDevice() -{ - if (currentAudioDevice == nullptr) - { - if (currentSetup.inputDeviceName.isEmpty() - && currentSetup.outputDeviceName.isEmpty()) - { - // This method will only reload the last device that was running - // before closeAudioDevice() was called - you need to actually open - // one first, with setAudioDevice(). - jassertfalse; - return; - } - - AudioDeviceSetup s (currentSetup); - setAudioDeviceSetup (s, false); - } -} - -void AudioDeviceManager::updateXml() -{ - lastExplicitSettings = new XmlElement ("DEVICESETUP"); - - lastExplicitSettings->setAttribute ("deviceType", currentDeviceType); - lastExplicitSettings->setAttribute ("audioOutputDeviceName", currentSetup.outputDeviceName); - lastExplicitSettings->setAttribute ("audioInputDeviceName", currentSetup.inputDeviceName); - - if (currentAudioDevice != nullptr) - { - lastExplicitSettings->setAttribute ("audioDeviceRate", currentAudioDevice->getCurrentSampleRate()); - - if (currentAudioDevice->getDefaultBufferSize() != currentAudioDevice->getCurrentBufferSizeSamples()) - lastExplicitSettings->setAttribute ("audioDeviceBufferSize", currentAudioDevice->getCurrentBufferSizeSamples()); - - if (! currentSetup.useDefaultInputChannels) - lastExplicitSettings->setAttribute ("audioDeviceInChans", currentSetup.inputChannels.toString (2)); - - if (! currentSetup.useDefaultOutputChannels) - lastExplicitSettings->setAttribute ("audioDeviceOutChans", currentSetup.outputChannels.toString (2)); - } - - for (int i = 0; i < enabledMidiInputs.size(); ++i) - lastExplicitSettings->createNewChildElement ("MIDIINPUT") - ->setAttribute ("name", enabledMidiInputs[i]->getName()); - - if (midiInsFromXml.size() > 0) - { - // Add any midi devices that have been enabled before, but which aren't currently - // open because the device has been disconnected. - const StringArray availableMidiDevices (MidiInput::getDevices()); - - for (int i = 0; i < midiInsFromXml.size(); ++i) - if (! availableMidiDevices.contains (midiInsFromXml[i], true)) - lastExplicitSettings->createNewChildElement ("MIDIINPUT") - ->setAttribute ("name", midiInsFromXml[i]); - } - - if (defaultMidiOutputName.isNotEmpty()) - lastExplicitSettings->setAttribute ("defaultMidiOutput", defaultMidiOutputName); -} - -//============================================================================== -void AudioDeviceManager::addAudioCallback (AudioIODeviceCallback* newCallback) -{ - { - const ScopedLock sl (audioCallbackLock); - if (callbacks.contains (newCallback)) - return; - } - - if (currentAudioDevice != nullptr && newCallback != nullptr) - newCallback->audioDeviceAboutToStart (currentAudioDevice); - - const ScopedLock sl (audioCallbackLock); - callbacks.add (newCallback); -} - -void AudioDeviceManager::removeAudioCallback (AudioIODeviceCallback* callbackToRemove) -{ - if (callbackToRemove != nullptr) - { - bool needsDeinitialising = currentAudioDevice != nullptr; - - { - const ScopedLock sl (audioCallbackLock); - - needsDeinitialising = needsDeinitialising && callbacks.contains (callbackToRemove); - callbacks.removeFirstMatchingValue (callbackToRemove); - } - - if (needsDeinitialising) - callbackToRemove->audioDeviceStopped(); - } -} - -void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelData, - int numInputChannels, - float** outputChannelData, - int numOutputChannels, - int numSamples) -{ - const ScopedLock sl (audioCallbackLock); - - inputLevelMeter.updateLevel (inputChannelData, numInputChannels, numSamples); - outputLevelMeter.updateLevel (const_cast (outputChannelData), numOutputChannels, numSamples); - - if (callbacks.size() > 0) - { - const double callbackStartTime = Time::getMillisecondCounterHiRes(); - - tempBuffer.setSize (jmax (1, numOutputChannels), jmax (1, numSamples), false, false, true); - - callbacks.getUnchecked(0)->audioDeviceIOCallback (inputChannelData, numInputChannels, - outputChannelData, numOutputChannels, numSamples); - - float** const tempChans = tempBuffer.getArrayOfWritePointers(); - - for (int i = callbacks.size(); --i > 0;) - { - callbacks.getUnchecked(i)->audioDeviceIOCallback (inputChannelData, numInputChannels, - tempChans, numOutputChannels, numSamples); - - for (int chan = 0; chan < numOutputChannels; ++chan) - { - if (const float* const src = tempChans [chan]) - if (float* const dst = outputChannelData [chan]) - for (int j = 0; j < numSamples; ++j) - dst[j] += src[j]; - } - } - - const double msTaken = Time::getMillisecondCounterHiRes() - callbackStartTime; - const double filterAmount = 0.2; - cpuUsageMs += filterAmount * (msTaken - cpuUsageMs); - - if (msTaken > msPerBlock) - xruns++; - } - else - { - for (int i = 0; i < numOutputChannels; ++i) - zeromem (outputChannelData[i], sizeof (float) * (size_t) numSamples); - } - - if (testSound != nullptr) - { - const int numSamps = jmin (numSamples, testSound->getNumSamples() - testSoundPosition); - const float* const src = testSound->getReadPointer (0, testSoundPosition); - - for (int i = 0; i < numOutputChannels; ++i) - for (int j = 0; j < numSamps; ++j) - outputChannelData [i][j] += src[j]; - - testSoundPosition += numSamps; - if (testSoundPosition >= testSound->getNumSamples()) - testSound = nullptr; - } -} - -void AudioDeviceManager::audioDeviceAboutToStartInt (AudioIODevice* const device) -{ - cpuUsageMs = 0; - xruns = 0; - - const double sampleRate = device->getCurrentSampleRate(); - const int blockSize = device->getCurrentBufferSizeSamples(); - - if (sampleRate > 0.0 && blockSize > 0) - { - msPerBlock = 1000.0 * blockSize / sampleRate; - timeToCpuScale = (msPerBlock > 0.0) ? (1.0 / msPerBlock) : 0.0; - } - - { - const ScopedLock sl (audioCallbackLock); - for (int i = callbacks.size(); --i >= 0;) - callbacks.getUnchecked(i)->audioDeviceAboutToStart (device); - } - - sendChangeMessage(); -} - -void AudioDeviceManager::audioDeviceStoppedInt() -{ - cpuUsageMs = 0; - timeToCpuScale = 0; - xruns = 0; - sendChangeMessage(); - - const ScopedLock sl (audioCallbackLock); - for (int i = callbacks.size(); --i >= 0;) - callbacks.getUnchecked(i)->audioDeviceStopped(); -} - -void AudioDeviceManager::audioDeviceErrorInt (const String& message) -{ - const ScopedLock sl (audioCallbackLock); - for (int i = callbacks.size(); --i >= 0;) - callbacks.getUnchecked(i)->audioDeviceError (message); -} - -double AudioDeviceManager::getCpuUsage() const -{ - return jlimit (0.0, 1.0, timeToCpuScale * cpuUsageMs); -} - -//============================================================================== -void AudioDeviceManager::setMidiInputEnabled (const String& name, const bool enabled) -{ - if (enabled != isMidiInputEnabled (name)) - { - if (enabled) - { - const int index = MidiInput::getDevices().indexOf (name); - - if (index >= 0) - { - if (MidiInput* const midiIn = MidiInput::openDevice (index, callbackHandler)) - { - enabledMidiInputs.add (midiIn); - midiIn->start(); - } - } - } - else - { - for (int i = enabledMidiInputs.size(); --i >= 0;) - if (enabledMidiInputs[i]->getName() == name) - enabledMidiInputs.remove (i); - } - - updateXml(); - sendChangeMessage(); - } -} - -bool AudioDeviceManager::isMidiInputEnabled (const String& name) const -{ - for (int i = enabledMidiInputs.size(); --i >= 0;) - if (enabledMidiInputs[i]->getName() == name) - return true; - - return false; -} - -void AudioDeviceManager::addMidiInputCallback (const String& name, MidiInputCallback* callbackToAdd) -{ - removeMidiInputCallback (name, callbackToAdd); - - if (name.isEmpty() || isMidiInputEnabled (name)) - { - const ScopedLock sl (midiCallbackLock); - - MidiCallbackInfo mc; - mc.deviceName = name; - mc.callback = callbackToAdd; - midiCallbacks.add (mc); - } -} - -void AudioDeviceManager::removeMidiInputCallback (const String& name, MidiInputCallback* callbackToRemove) -{ - for (int i = midiCallbacks.size(); --i >= 0;) - { - const MidiCallbackInfo& mc = midiCallbacks.getReference(i); - - if (mc.callback == callbackToRemove && mc.deviceName == name) - { - const ScopedLock sl (midiCallbackLock); - midiCallbacks.remove (i); - } - } -} - -void AudioDeviceManager::handleIncomingMidiMessageInt (MidiInput* source, const MidiMessage& message) -{ - if (! message.isActiveSense()) - { - const ScopedLock sl (midiCallbackLock); - - for (int i = 0; i < midiCallbacks.size(); ++i) - { - const MidiCallbackInfo& mc = midiCallbacks.getReference(i); - - if (mc.deviceName.isEmpty() || mc.deviceName == source->getName()) - mc.callback->handleIncomingMidiMessage (source, message); - } - } -} - -//============================================================================== -void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName) -{ - if (defaultMidiOutputName != deviceName) - { - Array oldCallbacks; - - { - const ScopedLock sl (audioCallbackLock); - oldCallbacks.swapWith (callbacks); - } - - if (currentAudioDevice != nullptr) - for (int i = oldCallbacks.size(); --i >= 0;) - oldCallbacks.getUnchecked(i)->audioDeviceStopped(); - - defaultMidiOutput = nullptr; - defaultMidiOutputName = deviceName; - - if (deviceName.isNotEmpty()) - defaultMidiOutput = MidiOutput::openDevice (MidiOutput::getDevices().indexOf (deviceName)); - - if (currentAudioDevice != nullptr) - for (int i = oldCallbacks.size(); --i >= 0;) - oldCallbacks.getUnchecked(i)->audioDeviceAboutToStart (currentAudioDevice); - - { - const ScopedLock sl (audioCallbackLock); - oldCallbacks.swapWith (callbacks); - } - - updateXml(); - sendChangeMessage(); - } -} - -//============================================================================== -AudioDeviceManager::LevelMeter::LevelMeter() noexcept : level() {} - -void AudioDeviceManager::LevelMeter::updateLevel (const float* const* channelData, int numChannels, int numSamples) noexcept -{ - if (enabled.get() != 0 && numChannels > 0) - { - for (int j = 0; j < numSamples; ++j) - { - float s = 0; - - for (int i = 0; i < numChannels; ++i) - s += std::abs (channelData[i][j]); - - s /= (float) numChannels; - - const double decayFactor = 0.99992; - - if (s > level) - level = s; - else if (level > 0.001f) - level *= decayFactor; - else - level = 0; - } - } - else - { - level = 0; - } -} - -void AudioDeviceManager::LevelMeter::setEnabled (bool shouldBeEnabled) noexcept -{ - enabled.set (shouldBeEnabled ? 1 : 0); - level = 0; -} - -double AudioDeviceManager::LevelMeter::getCurrentLevel() const noexcept -{ - jassert (enabled.get() != 0); // you need to call setEnabled (true) before using this! - return level; -} - -void AudioDeviceManager::playTestSound() -{ - { // cunningly nested to swap, unlock and delete in that order. - ScopedPointer oldSound; - - { - const ScopedLock sl (audioCallbackLock); - oldSound = testSound; - } - } - - testSoundPosition = 0; - - if (currentAudioDevice != nullptr) - { - const double sampleRate = currentAudioDevice->getCurrentSampleRate(); - const int soundLength = (int) sampleRate; - - const double frequency = 440.0; - const float amplitude = 0.5f; - - const double phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); - - AudioSampleBuffer* const newSound = new AudioSampleBuffer (1, soundLength); - - for (int i = 0; i < soundLength; ++i) - newSound->setSample (0, i, amplitude * (float) std::sin (i * phasePerSample)); - - newSound->applyGainRamp (0, 0, soundLength / 10, 0.0f, 1.0f); - newSound->applyGainRamp (0, soundLength - soundLength / 4, soundLength / 4, 1.0f, 0.0f); - - const ScopedLock sl (audioCallbackLock); - testSound = newSound; - } -} - -int AudioDeviceManager::getXRunCount() const noexcept -{ - auto deviceXRuns = (currentAudioDevice != nullptr ? currentAudioDevice->getXRunCount() : -1); - return (deviceXRuns >= 0 ? deviceXRuns : xruns); -} - -double AudioDeviceManager::getCurrentInputLevel() const noexcept { return inputLevelMeter.getCurrentLevel(); } -double AudioDeviceManager::getCurrentOutputLevel() const noexcept { return outputLevelMeter.getCurrentLevel(); } - -void AudioDeviceManager::enableInputLevelMeasurement (bool enable) noexcept { inputLevelMeter.setEnabled (enable); } -void AudioDeviceManager::enableOutputLevelMeasurement (bool enable) noexcept { outputLevelMeter.setEnabled (enable); } - -} // namespace juce diff --git a/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h b/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h deleted file mode 100644 index 6e5d68254..000000000 --- a/source/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.h +++ /dev/null @@ -1,532 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -//============================================================================== -/** - Manages the state of some audio and midi i/o devices. - - This class keeps tracks of a currently-selected audio device, through - with which it continuously streams data from an audio callback, as well as - one or more midi inputs. - - The idea is that your application will create one global instance of this object, - and let it take care of creating and deleting specific types of audio devices - internally. So when the device is changed, your callbacks will just keep running - without having to worry about this. - - The manager can save and reload all of its device settings as XML, which - makes it very easy for you to save and reload the audio setup of your - application. - - And to make it easy to let the user change its settings, there's a component - to do just that - the AudioDeviceSelectorComponent class, which contains a set of - device selection/sample-rate/latency controls. - - To use an AudioDeviceManager, create one, and use initialise() to set it up. Then - call addAudioCallback() to register your audio callback with it, and use that to process - your audio data. - - The manager also acts as a handy hub for incoming midi messages, allowing a - listener to register for messages from either a specific midi device, or from whatever - the current default midi input device is. The listener then doesn't have to worry about - re-registering with different midi devices if they are changed or deleted. - - And yet another neat trick is that amount of CPU time being used is measured and - available with the getCpuUsage() method. - - The AudioDeviceManager is a ChangeBroadcaster, and will send a change message to - listeners whenever one of its settings is changed. - - @see AudioDeviceSelectorComponent, AudioIODevice, AudioIODeviceType -*/ -class JUCE_API AudioDeviceManager : public ChangeBroadcaster -{ -public: - //============================================================================== - /** Creates a default AudioDeviceManager. - - Initially no audio device will be selected. You should call the initialise() method - and register an audio callback with setAudioCallback() before it'll be able to - actually make any noise. - */ - AudioDeviceManager(); - - /** Destructor. */ - ~AudioDeviceManager(); - - //============================================================================== - /** - This structure holds a set of properties describing the current audio setup. - - An AudioDeviceManager uses this class to save/load its current settings, and to - specify your preferred options when opening a device. - - @see AudioDeviceManager::setAudioDeviceSetup(), AudioDeviceManager::initialise() - */ - struct JUCE_API AudioDeviceSetup - { - /** Creates an AudioDeviceSetup object. - - The default constructor sets all the member variables to indicate default values. - You can then fill-in any values you want to before passing the object to - AudioDeviceManager::initialise(). - */ - AudioDeviceSetup(); - - bool operator== (const AudioDeviceSetup& other) const; - - /** The name of the audio device used for output. - The name has to be one of the ones listed by the AudioDeviceManager's currently - selected device type. - This may be the same as the input device. - An empty string indicates the default device. - */ - String outputDeviceName; - - /** The name of the audio device used for input. - This may be the same as the output device. - An empty string indicates the default device. - */ - String inputDeviceName; - - /** The current sample rate. - This rate is used for both the input and output devices. - A value of 0 indicates that you don't care what rate is used, and the - device will choose a sensible rate for you. - */ - double sampleRate; - - /** The buffer size, in samples. - This buffer size is used for both the input and output devices. - A value of 0 indicates the default buffer size. - */ - int bufferSize; - - /** The set of active input channels. - The bits that are set in this array indicate the channels of the - input device that are active. - If useDefaultInputChannels is true, this value is ignored. - */ - BigInteger inputChannels; - - /** If this is true, it indicates that the inputChannels array - should be ignored, and instead, the device's default channels - should be used. - */ - bool useDefaultInputChannels; - - /** The set of active output channels. - The bits that are set in this array indicate the channels of the - input device that are active. - If useDefaultOutputChannels is true, this value is ignored. - */ - BigInteger outputChannels; - - /** If this is true, it indicates that the outputChannels array - should be ignored, and instead, the device's default channels - should be used. - */ - bool useDefaultOutputChannels; - }; - - - //============================================================================== - /** Opens a set of audio devices ready for use. - - This will attempt to open either a default audio device, or one that was - previously saved as XML. - - @param numInputChannelsNeeded the maximum number of input channels your app would like to - use (the actual number of channels opened may be less than - the number requested) - @param numOutputChannelsNeeded the maximum number of output channels your app would like to - use (the actual number of channels opened may be less than - the number requested) - @param savedState either a previously-saved state that was produced - by createStateXml(), or nullptr if you want the manager - to choose the best device to open. - @param selectDefaultDeviceOnFailure if true, then if the device specified in the XML - fails to open, then a default device will be used - instead. If false, then on failure, no device is - opened. - @param preferredDefaultDeviceName if this is not empty, and there's a device with this - name, then that will be used as the default device - (assuming that there wasn't one specified in the XML). - The string can actually be a simple wildcard, containing "*" - and "?" characters - @param preferredSetupOptions if this is non-null, the structure will be used as the - set of preferred settings when opening the device. If you - use this parameter, the preferredDefaultDeviceName - field will be ignored - - @returns an error message if anything went wrong, or an empty string if it worked ok. - */ - String initialise (int numInputChannelsNeeded, - int numOutputChannelsNeeded, - const XmlElement* savedState, - bool selectDefaultDeviceOnFailure, - const String& preferredDefaultDeviceName = String(), - const AudioDeviceSetup* preferredSetupOptions = nullptr); - - /** Resets everything to a default device setup, clearing any stored settings. */ - String initialiseWithDefaultDevices (int numInputChannelsNeeded, - int numOutputChannelsNeeded); - - /** Returns some XML representing the current state of the manager. - - This stores the current device, its samplerate, block size, etc, and - can be restored later with initialise(). - - Note that this can return a null pointer if no settings have been explicitly changed - (i.e. if the device manager has just been left in its default state). - */ - XmlElement* createStateXml() const; - - //============================================================================== - /** Returns the current device properties that are in use. - @see setAudioDeviceSetup - */ - void getAudioDeviceSetup (AudioDeviceSetup& result) const; - - /** Changes the current device or its settings. - - If you want to change a device property, like the current sample rate or - block size, you can call getAudioDeviceSetup() to retrieve the current - settings, then tweak the appropriate fields in the AudioDeviceSetup structure, - and pass it back into this method to apply the new settings. - - @param newSetup the settings that you'd like to use - @param treatAsChosenDevice if this is true and if the device opens correctly, these new - settings will be taken as having been explicitly chosen by the - user, and the next time createStateXml() is called, these settings - will be returned. If it's false, then the device is treated as a - temporary or default device, and a call to createStateXml() will - return either the last settings that were made with treatAsChosenDevice - as true, or the last XML settings that were passed into initialise(). - @returns an error message if anything went wrong, or an empty string if it worked ok. - - @see getAudioDeviceSetup - */ - String setAudioDeviceSetup (const AudioDeviceSetup& newSetup, - bool treatAsChosenDevice); - - - /** Returns the currently-active audio device. */ - AudioIODevice* getCurrentAudioDevice() const noexcept { return currentAudioDevice; } - - /** Returns the type of audio device currently in use. - @see setCurrentAudioDeviceType - */ - String getCurrentAudioDeviceType() const { return currentDeviceType; } - - /** Returns the currently active audio device type object. - Don't keep a copy of this pointer - it's owned by the device manager and could - change at any time. - */ - AudioIODeviceType* getCurrentDeviceTypeObject() const; - - /** Changes the class of audio device being used. - - This switches between, e.g. ASIO and DirectSound. On the Mac you probably won't ever call - this because there's only one type: CoreAudio. - - For a list of types, see getAvailableDeviceTypes(). - */ - void setCurrentAudioDeviceType (const String& type, - bool treatAsChosenDevice); - - /** Closes the currently-open device. - You can call restartLastAudioDevice() later to reopen it in the same state - that it was just in. - */ - void closeAudioDevice(); - - /** Tries to reload the last audio device that was running. - - Note that this only reloads the last device that was running before - closeAudioDevice() was called - it doesn't reload any kind of saved-state, - and can only be called after a device has been opened with SetAudioDevice(). - - If a device is already open, this call will do nothing. - */ - void restartLastAudioDevice(); - - //============================================================================== - /** Registers an audio callback to be used. - - The manager will redirect callbacks from whatever audio device is currently - in use to all registered callback objects. If more than one callback is - active, they will all be given the same input data, and their outputs will - be summed. - - If necessary, this method will invoke audioDeviceAboutToStart() on the callback - object before returning. - - To remove a callback, use removeAudioCallback(). - */ - void addAudioCallback (AudioIODeviceCallback* newCallback); - - /** Deregisters a previously added callback. - - If necessary, this method will invoke audioDeviceStopped() on the callback - object before returning. - - @see addAudioCallback - */ - void removeAudioCallback (AudioIODeviceCallback* callback); - - //============================================================================== - /** Returns the average proportion of available CPU being spent inside the audio callbacks. - @returns A value between 0 and 1.0 to indicate the approximate proportion of CPU - time spent in the callbacks. - */ - double getCpuUsage() const; - - //============================================================================== - /** Enables or disables a midi input device. - - The list of devices can be obtained with the MidiInput::getDevices() method. - - Any incoming messages from enabled input devices will be forwarded on to all the - listeners that have been registered with the addMidiInputCallback() method. They - can either register for messages from a particular device, or from just the - "default" midi input. - - Routing the midi input via an AudioDeviceManager means that when a listener - registers for the default midi input, this default device can be changed by the - manager without the listeners having to know about it or re-register. - - It also means that a listener can stay registered for a midi input that is disabled - or not present, so that when the input is re-enabled, the listener will start - receiving messages again. - - @see addMidiInputCallback, isMidiInputEnabled - */ - void setMidiInputEnabled (const String& midiInputDeviceName, bool enabled); - - /** Returns true if a given midi input device is being used. - @see setMidiInputEnabled - */ - bool isMidiInputEnabled (const String& midiInputDeviceName) const; - - /** Registers a listener for callbacks when midi events arrive from a midi input. - - The device name can be empty to indicate that it wants to receive all incoming - events from all the enabled MIDI inputs. Or it can be the name of one of the - MIDI input devices if it just wants the events from that device. (see - MidiInput::getDevices() for the list of device names). - - Only devices which are enabled (see the setMidiInputEnabled() method) will have their - events forwarded on to listeners. - */ - void addMidiInputCallback (const String& midiInputDeviceName, - MidiInputCallback* callback); - - /** Removes a listener that was previously registered with addMidiInputCallback(). */ - void removeMidiInputCallback (const String& midiInputDeviceName, - MidiInputCallback* callback); - - //============================================================================== - /** Sets a midi output device to use as the default. - - The list of devices can be obtained with the MidiOutput::getDevices() method. - - The specified device will be opened automatically and can be retrieved with the - getDefaultMidiOutput() method. - - Pass in an empty string to deselect all devices. For the default device, you - can use MidiOutput::getDevices() [MidiOutput::getDefaultDeviceIndex()]. - - @see getDefaultMidiOutput, getDefaultMidiOutputName - */ - void setDefaultMidiOutput (const String& deviceName); - - /** Returns the name of the default midi output. - @see setDefaultMidiOutput, getDefaultMidiOutput - */ - const String& getDefaultMidiOutputName() const noexcept { return defaultMidiOutputName; } - - /** Returns the current default midi output device. - If no device has been selected, or the device can't be opened, this will return nullptr. - @see getDefaultMidiOutputName - */ - MidiOutput* getDefaultMidiOutput() const noexcept { return defaultMidiOutput; } - - /** Returns a list of the types of device supported. */ - const OwnedArray& getAvailableDeviceTypes(); - - //============================================================================== - /** Creates a list of available types. - - This will add a set of new AudioIODeviceType objects to the specified list, to - represent each available types of device. - - You can override this if your app needs to do something specific, like avoid - using DirectSound devices, etc. - */ - virtual void createAudioDeviceTypes (OwnedArray& types); - - /** Adds a new device type to the list of types. - The manager will take ownership of the object that is passed-in. - */ - void addAudioDeviceType (AudioIODeviceType* newDeviceType); - - //============================================================================== - /** Plays a beep through the current audio device. - - This is here to allow the audio setup UI panels to easily include a "test" - button so that the user can check where the audio is coming from. - */ - void playTestSound(); - - //============================================================================== - /** Turns on level-measuring for input channels. - @see getCurrentInputLevel() - */ - void enableInputLevelMeasurement (bool enableMeasurement) noexcept; - - /** Turns on level-measuring for output channels. - @see getCurrentOutputLevel() - */ - void enableOutputLevelMeasurement (bool enableMeasurement) noexcept; - - /** Returns the current input level. - To use this, you must first enable it by calling enableInputLevelMeasurement(). - @see enableInputLevelMeasurement() - */ - double getCurrentInputLevel() const noexcept; - - /** Returns the current output level. - To use this, you must first enable it by calling enableOutputLevelMeasurement(). - @see enableOutputLevelMeasurement() - */ - double getCurrentOutputLevel() const noexcept; - - /** Returns the a lock that can be used to synchronise access to the audio callback. - Obviously while this is locked, you're blocking the audio thread from running, so - it must only be used for very brief periods when absolutely necessary. - */ - CriticalSection& getAudioCallbackLock() noexcept { return audioCallbackLock; } - - /** Returns the a lock that can be used to synchronise access to the midi callback. - Obviously while this is locked, you're blocking the midi system from running, so - it must only be used for very brief periods when absolutely necessary. - */ - CriticalSection& getMidiCallbackLock() noexcept { return midiCallbackLock; } - - //============================================================================== - /** Returns the number of under- or over runs reported. - - This method will use the underlying device's native getXRunCount if it supports - it. Otherwise it will estimate the number of under-/overruns by measuring the - time it spent in the audio callback. - */ - int getXRunCount() const noexcept; - -private: - //============================================================================== - OwnedArray availableDeviceTypes; - OwnedArray lastDeviceTypeConfigs; - - AudioDeviceSetup currentSetup; - ScopedPointer currentAudioDevice; - Array callbacks; - int numInputChansNeeded, numOutputChansNeeded; - String currentDeviceType; - BigInteger inputChannels, outputChannels; - ScopedPointer lastExplicitSettings; - mutable bool listNeedsScanning; - AudioSampleBuffer tempBuffer; - - struct MidiCallbackInfo - { - String deviceName; - MidiInputCallback* callback; - }; - - StringArray midiInsFromXml; - OwnedArray enabledMidiInputs; - Array midiCallbacks; - - String defaultMidiOutputName; - ScopedPointer defaultMidiOutput; - CriticalSection audioCallbackLock, midiCallbackLock; - - ScopedPointer testSound; - int testSoundPosition; - - double cpuUsageMs, timeToCpuScale, msPerBlock; - int xruns; - - struct LevelMeter - { - LevelMeter() noexcept; - void updateLevel (const float* const*, int numChannels, int numSamples) noexcept; - void setEnabled (bool) noexcept; - double getCurrentLevel() const noexcept; - - Atomic enabled; - double level; - }; - - LevelMeter inputLevelMeter, outputLevelMeter; - - //============================================================================== - class CallbackHandler; - friend class CallbackHandler; - friend struct ContainerDeletePolicy; - ScopedPointer callbackHandler; - - void audioDeviceIOCallbackInt (const float** inputChannelData, int totalNumInputChannels, - float** outputChannelData, int totalNumOutputChannels, int numSamples); - void audioDeviceAboutToStartInt (AudioIODevice*); - void audioDeviceStoppedInt(); - void audioDeviceErrorInt (const String&); - void handleIncomingMidiMessageInt (MidiInput*, const MidiMessage&); - void audioDeviceListChanged(); - - String restartDevice (int blockSizeToUse, double sampleRateToUse, - const BigInteger& ins, const BigInteger& outs); - void stopDevice(); - - void updateXml(); - - void createDeviceTypesIfNeeded(); - void scanDevicesIfNeeded(); - void deleteCurrentDevice(); - double chooseBestSampleRate (double preferred) const; - int chooseBestBufferSize (int preferred) const; - void insertDefaultDeviceNames (AudioDeviceSetup&) const; - String initialiseDefault (const String& preferredDefaultDeviceName, const AudioDeviceSetup*); - String initialiseFromXML (const XmlElement&, bool selectDefaultDeviceOnFailure, - const String& preferredDefaultDeviceName, const AudioDeviceSetup*); - - AudioIODeviceType* findType (const String& inputName, const String& outputName); - AudioIODeviceType* findType (const String& typeName); - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioDeviceManager) -}; - -} // namespace juce diff --git a/source/modules/juce_audio_devices/audio_io/juce_AudioIODevice.cpp b/source/modules/juce_audio_devices/audio_io/juce_AudioIODevice.cpp deleted file mode 100644 index 935de58d0..000000000 --- a/source/modules/juce_audio_devices/audio_io/juce_AudioIODevice.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -AudioIODevice::AudioIODevice (const String& deviceName, const String& deviceTypeName) - : name (deviceName), typeName (deviceTypeName) -{ -} - -AudioIODevice::~AudioIODevice() {} - -void AudioIODeviceCallback::audioDeviceError (const String&) {} -bool AudioIODevice::setAudioPreprocessingEnabled (bool) { return false; } -bool AudioIODevice::hasControlPanel() const { return false; } -int AudioIODevice::getXRunCount() const noexcept { return -1; } - -bool AudioIODevice::showControlPanel() -{ - jassertfalse; // this should only be called for devices which return true from - // their hasControlPanel() method. - return false; -} - -} // namespace juce diff --git a/source/modules/juce_audio_devices/audio_io/juce_AudioIODevice.h b/source/modules/juce_audio_devices/audio_io/juce_AudioIODevice.h deleted file mode 100644 index a1599f4d2..000000000 --- a/source/modules/juce_audio_devices/audio_io/juce_AudioIODevice.h +++ /dev/null @@ -1,319 +0,0 @@ -/* - ============================================================================== - - 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 AudioIODevice; - - -//============================================================================== -/** - One of these is passed to an AudioIODevice object to stream the audio data - in and out. - - The AudioIODevice will repeatedly call this class's audioDeviceIOCallback() - method on its own high-priority audio thread, when it needs to send or receive - the next block of data. - - @see AudioIODevice, AudioDeviceManager -*/ -class JUCE_API AudioIODeviceCallback -{ -public: - /** Destructor. */ - virtual ~AudioIODeviceCallback() {} - - /** Processes a block of incoming and outgoing audio data. - - The subclass's implementation should use the incoming audio for whatever - purposes it needs to, and must fill all the output channels with the next - block of output data before returning. - - The channel data is arranged with the same array indices as the channel name - array returned by AudioIODevice::getOutputChannelNames(), but those channels - that aren't specified in AudioIODevice::open() will have a null pointer for their - associated channel, so remember to check for this. - - @param inputChannelData a set of arrays containing the audio data for each - incoming channel - this data is valid until the function - returns. There will be one channel of data for each input - channel that was enabled when the audio device was opened - (see AudioIODevice::open()) - @param numInputChannels the number of pointers to channel data in the - inputChannelData array. - @param outputChannelData a set of arrays which need to be filled with the data - that should be sent to each outgoing channel of the device. - There will be one channel of data for each output channel - that was enabled when the audio device was opened (see - AudioIODevice::open()) - The initial contents of the array is undefined, so the - callback function must fill all the channels with zeros if - its output is silence. Failing to do this could cause quite - an unpleasant noise! - @param numOutputChannels the number of pointers to channel data in the - outputChannelData array. - @param numSamples the number of samples in each channel of the input and - output arrays. The number of samples will depend on the - audio device's buffer size and will usually remain constant, - although this isn't guaranteed, so make sure your code can - cope with reasonable changes in the buffer size from one - callback to the next. - */ - virtual void audioDeviceIOCallback (const float** inputChannelData, - int numInputChannels, - float** outputChannelData, - int numOutputChannels, - int numSamples) = 0; - - /** Called to indicate that the device is about to start calling back. - - This will be called just before the audio callbacks begin, either when this - callback has just been added to an audio device, or after the device has been - restarted because of a sample-rate or block-size change. - - You can use this opportunity to find out the sample rate and block size - that the device is going to use by calling the AudioIODevice::getCurrentSampleRate() - and AudioIODevice::getCurrentBufferSizeSamples() on the supplied pointer. - - @param device the audio IO device that will be used to drive the callback. - Note that if you're going to store this this pointer, it is - only valid until the next time that audioDeviceStopped is called. - */ - virtual void audioDeviceAboutToStart (AudioIODevice* device) = 0; - - /** Called to indicate that the device has stopped. */ - virtual void audioDeviceStopped() = 0; - - /** This can be overridden to be told if the device generates an error while operating. - Be aware that this could be called by any thread! And not all devices perform - this callback. - */ - virtual void audioDeviceError (const String& errorMessage); -}; - - -//============================================================================== -/** - Base class for an audio device with synchronised input and output channels. - - Subclasses of this are used to implement different protocols such as DirectSound, - ASIO, CoreAudio, etc. - - To create one of these, you'll need to use the AudioIODeviceType class - see the - documentation for that class for more info. - - For an easier way of managing audio devices and their settings, have a look at the - AudioDeviceManager class. - - @see AudioIODeviceType, AudioDeviceManager -*/ -class JUCE_API AudioIODevice -{ -public: - /** Destructor. */ - virtual ~AudioIODevice(); - - //============================================================================== - /** Returns the device's name, (as set in the constructor). */ - const String& getName() const noexcept { return name; } - - /** Returns the type of the device. - - E.g. "CoreAudio", "ASIO", etc. - this comes from the AudioIODeviceType that created it. - */ - const String& getTypeName() const noexcept { return typeName; } - - //============================================================================== - /** Returns the names of all the available output channels on this device. - To find out which of these are currently in use, call getActiveOutputChannels(). - */ - virtual StringArray getOutputChannelNames() = 0; - - /** Returns the names of all the available input channels on this device. - To find out which of these are currently in use, call getActiveInputChannels(). - */ - virtual StringArray getInputChannelNames() = 0; - - //============================================================================== - /** Returns the set of sample-rates this device supports. - @see getCurrentSampleRate - */ - virtual Array getAvailableSampleRates() = 0; - - /** Returns the set of buffer sizes that are available. - @see getCurrentBufferSizeSamples, getDefaultBufferSize - */ - virtual Array getAvailableBufferSizes() = 0; - - /** Returns the default buffer-size to use. - @returns a number of samples - @see getAvailableBufferSizes - */ - virtual int getDefaultBufferSize() = 0; - - //============================================================================== - /** Tries to open the device ready to play. - - @param inputChannels a BigInteger in which a set bit indicates that the corresponding - input channel should be enabled - @param outputChannels a BigInteger in which a set bit indicates that the corresponding - output channel should be enabled - @param sampleRate the sample rate to try to use - to find out which rates are - available, see getAvailableSampleRates() - @param bufferSizeSamples the size of i/o buffer to use - to find out the available buffer - sizes, see getAvailableBufferSizes() - @returns an error description if there's a problem, or an empty string if it succeeds in - opening the device - @see close - */ - virtual String open (const BigInteger& inputChannels, - const BigInteger& outputChannels, - double sampleRate, - int bufferSizeSamples) = 0; - - /** Closes and releases the device if it's open. */ - virtual void close() = 0; - - /** Returns true if the device is still open. - - A device might spontaneously close itself if something goes wrong, so this checks if - it's still open. - */ - virtual bool isOpen() = 0; - - /** Starts the device actually playing. - - This must be called after the device has been opened. - - @param callback the callback to use for streaming the data. - @see AudioIODeviceCallback, open - */ - virtual void start (AudioIODeviceCallback* callback) = 0; - - /** Stops the device playing. - - Once a device has been started, this will stop it. Any pending calls to the - callback class will be flushed before this method returns. - */ - virtual void stop() = 0; - - /** Returns true if the device is still calling back. - - The device might mysteriously stop, so this checks whether it's - still playing. - */ - virtual bool isPlaying() = 0; - - /** Returns the last error that happened if anything went wrong. */ - virtual String getLastError() = 0; - - //============================================================================== - /** Returns the buffer size that the device is currently using. - - If the device isn't actually open, this value doesn't really mean much. - */ - virtual int getCurrentBufferSizeSamples() = 0; - - /** Returns the sample rate that the device is currently using. - - If the device isn't actually open, this value doesn't really mean much. - */ - virtual double getCurrentSampleRate() = 0; - - /** Returns the device's current physical bit-depth. - - If the device isn't actually open, this value doesn't really mean much. - */ - virtual int getCurrentBitDepth() = 0; - - /** Returns a mask showing which of the available output channels are currently - enabled. - @see getOutputChannelNames - */ - virtual BigInteger getActiveOutputChannels() const = 0; - - /** Returns a mask showing which of the available input channels are currently - enabled. - @see getInputChannelNames - */ - virtual BigInteger getActiveInputChannels() const = 0; - - /** Returns the device's output latency. - - This is the delay in samples between a callback getting a block of data, and - that data actually getting played. - */ - virtual int getOutputLatencyInSamples() = 0; - - /** Returns the device's input latency. - - This is the delay in samples between some audio actually arriving at the soundcard, - and the callback getting passed this block of data. - */ - virtual int getInputLatencyInSamples() = 0; - - - //============================================================================== - /** True if this device can show a pop-up control panel for editing its settings. - - This is generally just true of ASIO devices. If true, you can call showControlPanel() - to display it. - */ - virtual bool hasControlPanel() const; - - /** Shows a device-specific control panel if there is one. - - This should only be called for devices which return true from hasControlPanel(). - */ - virtual bool showControlPanel(); - - /** On devices which support it, this allows automatic gain control or other - mic processing to be disabled. - If the device doesn't support this operation, it'll return false. - */ - virtual bool setAudioPreprocessingEnabled (bool shouldBeEnabled); - - //============================================================================== - /** Returns the number of under- or over runs reported by the OS since - playback/recording has started. - - This number may be different than determining the Xrun count manually (by - measuring the time spent in the audio callback) as the OS may be doing - some buffering internally - especially on mobile devices. - - Returns -1 if playback/recording has not started yet or if getting the underrun - count is not supported for this device (Android SDK 23 and lower). - */ - virtual int getXRunCount() const noexcept; - - //============================================================================== -protected: - /** Creates a device, setting its name and type member variables. */ - AudioIODevice (const String& deviceName, - const String& typeName); - - /** @internal */ - String name, typeName; -}; - -} // namespace juce diff --git a/source/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.cpp b/source/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.cpp deleted file mode 100644 index 838aaa654..000000000 --- a/source/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -AudioIODeviceType::AudioIODeviceType (const String& name) - : typeName (name) -{ -} - -AudioIODeviceType::~AudioIODeviceType() -{ -} - -//============================================================================== -void AudioIODeviceType::addListener (Listener* l) { listeners.add (l); } -void AudioIODeviceType::removeListener (Listener* l) { listeners.remove (l); } - -void AudioIODeviceType::callDeviceChangeListeners() -{ - listeners.call (&AudioIODeviceType::Listener::audioDeviceListChanged); -} - -//============================================================================== -#if ! JUCE_MAC -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio() { return nullptr; } -#endif - -#if ! JUCE_IOS -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() { return nullptr; } -#endif - -#if ! (JUCE_WINDOWS && JUCE_WASAPI) -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (bool) { return nullptr; } -#endif - -#if ! (JUCE_WINDOWS && JUCE_DIRECTSOUND) -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_DirectSound() { return nullptr; } -#endif - -#if ! (JUCE_WINDOWS && JUCE_ASIO) -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ASIO() { return nullptr; } -#endif - -#if ! (JUCE_LINUX && JUCE_ALSA) -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA() { return nullptr; } -#endif - -#if ! (JUCE_LINUX && JUCE_JACK) -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() { return nullptr; } -#endif - -#if ! JUCE_ANDROID -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android() { return nullptr; } -#endif - -#if ! (JUCE_ANDROID && JUCE_USE_ANDROID_OPENSLES) -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES() { return nullptr; } -#endif - -} // namespace juce diff --git a/source/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h b/source/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h deleted file mode 100644 index c11f711a4..000000000 --- a/source/modules/juce_audio_devices/audio_io/juce_AudioIODeviceType.h +++ /dev/null @@ -1,178 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -//============================================================================== -/** - Represents a type of audio driver, such as DirectSound, ASIO, CoreAudio, etc. - - To get a list of available audio driver types, use the AudioDeviceManager::createAudioDeviceTypes() - method. Each of the objects returned can then be used to list the available - devices of that type. E.g. - @code - OwnedArray types; - myAudioDeviceManager.createAudioDeviceTypes (types); - - for (int i = 0; i < types.size(); ++i) - { - String typeName (types[i]->getTypeName()); // This will be things like "DirectSound", "CoreAudio", etc. - - types[i]->scanForDevices(); // This must be called before getting the list of devices - - StringArray deviceNames (types[i]->getDeviceNames()); // This will now return a list of available devices of this type - - for (int j = 0; j < deviceNames.size(); ++j) - { - AudioIODevice* device = types[i]->createDevice (deviceNames [j]); - - ... - } - } - @endcode - - For an easier way of managing audio devices and their settings, have a look at the - AudioDeviceManager class. - - @see AudioIODevice, AudioDeviceManager -*/ -class JUCE_API AudioIODeviceType -{ -public: - //============================================================================== - /** Returns the name of this type of driver that this object manages. - - This will be something like "DirectSound", "ASIO", "CoreAudio", "ALSA", etc. - */ - const String& getTypeName() const noexcept { return typeName; } - - //============================================================================== - /** Refreshes the object's cached list of known devices. - - This must be called at least once before calling getDeviceNames() or any of - the other device creation methods. - */ - virtual void scanForDevices() = 0; - - /** Returns the list of available devices of this type. - - The scanForDevices() method must have been called to create this list. - - @param wantInputNames only really used by DirectSound where devices are split up - into inputs and outputs, this indicates whether to use - the input or output name to refer to a pair of devices. - */ - virtual StringArray getDeviceNames (bool wantInputNames = false) const = 0; - - /** Returns the name of the default device. - - This will be one of the names from the getDeviceNames() list. - - @param forInput if true, this means that a default input device should be - returned; if false, it should return the default output - */ - virtual int getDefaultDeviceIndex (bool forInput) const = 0; - - /** Returns the index of a given device in the list of device names. - If asInput is true, it shows the index in the inputs list, otherwise it - looks for it in the outputs list. - */ - virtual int getIndexOfDevice (AudioIODevice* device, bool asInput) const = 0; - - /** Returns true if two different devices can be used for the input and output. - */ - virtual bool hasSeparateInputsAndOutputs() const = 0; - - /** Creates one of the devices of this type. - - The deviceName must be one of the strings returned by getDeviceNames(), and - scanForDevices() must have been called before this method is used. - */ - virtual AudioIODevice* createDevice (const String& outputDeviceName, - const String& inputDeviceName) = 0; - - //============================================================================== - /** - A class for receiving events when audio devices are inserted or removed. - - You can register an AudioIODeviceType::Listener with an~AudioIODeviceType object - using the AudioIODeviceType::addListener() method, and it will be called when - devices of that type are added or removed. - - @see AudioIODeviceType::addListener, AudioIODeviceType::removeListener - */ - class Listener - { - public: - virtual ~Listener() {} - - /** Called when the list of available audio devices changes. */ - virtual void audioDeviceListChanged() = 0; - }; - - /** Adds a listener that will be called when this type of device is added or - removed from the system. - */ - void addListener (Listener* listener); - - /** Removes a listener that was previously added with addListener(). */ - void removeListener (Listener* listener); - - //============================================================================== - /** Destructor. */ - virtual ~AudioIODeviceType(); - - //============================================================================== - /** Creates a CoreAudio device type if it's available on this platform, or returns null. */ - static AudioIODeviceType* createAudioIODeviceType_CoreAudio(); - /** Creates an iOS device type if it's available on this platform, or returns null. */ - static AudioIODeviceType* createAudioIODeviceType_iOSAudio(); - /** Creates a WASAPI device type if it's available on this platform, or returns null. */ - static AudioIODeviceType* createAudioIODeviceType_WASAPI (bool exclusiveMode); - /** Creates a DirectSound device type if it's available on this platform, or returns null. */ - static AudioIODeviceType* createAudioIODeviceType_DirectSound(); - /** Creates an ASIO device type if it's available on this platform, or returns null. */ - static AudioIODeviceType* createAudioIODeviceType_ASIO(); - /** Creates an ALSA device type if it's available on this platform, or returns null. */ - static AudioIODeviceType* createAudioIODeviceType_ALSA(); - /** Creates a JACK device type if it's available on this platform, or returns null. */ - static AudioIODeviceType* createAudioIODeviceType_JACK(); - /** Creates an Android device type if it's available on this platform, or returns null. */ - static AudioIODeviceType* createAudioIODeviceType_Android(); - /** Creates an Android OpenSLES device type if it's available on this platform, or returns null. */ - static AudioIODeviceType* createAudioIODeviceType_OpenSLES(); - -protected: - explicit AudioIODeviceType (const String& typeName); - - /** Synchronously calls all the registered device list change listeners. */ - void callDeviceChangeListeners(); - -private: - String typeName; - ListenerList listeners; - - JUCE_DECLARE_NON_COPYABLE (AudioIODeviceType) -}; - -} // namespace juce diff --git a/source/modules/juce_audio_devices/audio_io/juce_SystemAudioVolume.h b/source/modules/juce_audio_devices/audio_io/juce_SystemAudioVolume.h deleted file mode 100644 index bf5c44c6a..000000000 --- a/source/modules/juce_audio_devices/audio_io/juce_SystemAudioVolume.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -//============================================================================== -/** - Contains functions to control the system's master volume. -*/ -class JUCE_API SystemAudioVolume -{ -public: - //============================================================================== - /** Returns the operating system's current volume level in the range 0 to 1.0 */ - static float JUCE_CALLTYPE getGain(); - - /** Attempts to set the operating system's current volume level. - @param newGain the level, between 0 and 1.0 - @returns true if the operation succeeds - */ - static bool JUCE_CALLTYPE setGain (float newGain); - - /** Returns true if the system's audio output is currently muted. */ - static bool JUCE_CALLTYPE isMuted(); - - /** Attempts to mute the operating system's audio output. - @param shouldBeMuted true if you want it to be muted - @returns true if the operation succeeds - */ - static bool JUCE_CALLTYPE setMuted (bool shouldBeMuted); - -private: - SystemAudioVolume(); // Don't instantiate this class, just call its static fns. - JUCE_DECLARE_NON_COPYABLE (SystemAudioVolume) -}; - -} // namespace juce diff --git a/source/modules/juce_audio_devices/juce_audio_devices.cpp b/source/modules/juce_audio_devices/juce_audio_devices.cpp deleted file mode 100644 index 6bdd8fe06..000000000 --- a/source/modules/juce_audio_devices/juce_audio_devices.cpp +++ /dev/null @@ -1,225 +0,0 @@ -/* - ============================================================================== - - 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. - - ============================================================================== -*/ - -#ifdef JUCE_AUDIO_DEVICES_H_INCLUDED - /* When you add this cpp file to your project, you mustn't include it in a file where you've - already included any other headers - just put it inside a file on its own, possibly with your config - flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix - header files that the compiler may be using. - */ - #error "Incorrect use of JUCE cpp file" -#endif - -#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 -#define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 -#define JUCE_CORE_INCLUDE_JNI_HELPERS 1 -#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 -#define JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW 1 - -#ifndef JUCE_USE_WINRT_MIDI - #define JUCE_USE_WINRT_MIDI 0 -#endif - -#if JUCE_USE_WINRT_MIDI - #define JUCE_EVENTS_INCLUDE_WINRT_WRAPPER 1 -#endif - -#include "juce_audio_devices.h" - -//============================================================================== -#if JUCE_MAC - #define Point CarbonDummyPointName - #define Component CarbonDummyCompName - #import - #import - #import - #undef Point - #undef Component - -#elif JUCE_IOS - #import - #import - #import - - #if TARGET_OS_SIMULATOR - #import - #endif - -//============================================================================== -#elif JUCE_WINDOWS - #if JUCE_WASAPI - #include - #endif - - #if JUCE_USE_WINRT_MIDI - /* If you cannot find any of the header files below then you are probably - attempting to use the Windows 10 Bluetooth Low Energy API. For this to work you - need to install version 10.0.14393.0 of the Windows Standalone SDK and add the - path to the WinRT headers to your build system. This path should have the form - "C:\Program Files (x86)\Windows Kits\10\Include\10.0.14393.0\winrt". - - Also please note that Microsoft's Bluetooth MIDI stack has multiple issues, so - this API is EXPERIMENTAL - use at your own risk! - */ - #include - #include - #include - #include - #if JUCE_MSVC - #pragma warning (push) - #pragma warning (disable: 4467) - #endif - #include - #if JUCE_MSVC - #pragma warning (pop) - #endif - #endif - - #if JUCE_ASIO - /* This is very frustrating - we only need to use a handful of definitions from - a couple of the header files in Steinberg's ASIO SDK, and it'd be easy to copy - about 30 lines of code into this cpp file to create a fully stand-alone ASIO - implementation... - - ..unfortunately that would break Steinberg's license agreement for use of - their SDK, so I'm not allowed to do this. - - This means that anyone who wants to use JUCE's ASIO abilities will have to: - - 1) Agree to Steinberg's licensing terms and download the ASIO SDK - (see http://www.steinberg.net/en/company/developers.html). - - 2) Enable this code with a global definition #define JUCE_ASIO 1. - - 3) Make sure that your header search path contains the iasiodrv.h file that - comes with the SDK. (Only about a handful of the SDK header files are actually - needed - so to simplify things, you could just copy these into your JUCE directory). - */ - #include - #endif - -//============================================================================== -#elif JUCE_LINUX - #if JUCE_ALSA - /* Got an include error here? If so, you've either not got ALSA installed, or you've - not got your paths set up correctly to find its header files. - - The package you need to install to get ASLA support is "libasound2-dev". - - If you don't have the ALSA library and don't want to build Juce with audio support, - just set the JUCE_ALSA flag to 0. - */ - #include - #endif - - #if JUCE_JACK - /* Got an include error here? If so, you've either not got jack-audio-connection-kit - installed, or you've not got your paths set up correctly to find its header files. - - The package you need to install to get JACK support is "libjack-dev". - - If you don't have the jack-audio-connection-kit library and don't want to build - Juce with low latency audio support, just set the JUCE_JACK flag to 0. - */ - #include - #endif - #undef SIZEOF - -//============================================================================== -#elif JUCE_ANDROID - - #if JUCE_USE_ANDROID_OPENSLES - #include - #include - #include - #endif - -#endif - -#include "audio_io/juce_AudioDeviceManager.cpp" -#include "audio_io/juce_AudioIODevice.cpp" -#include "audio_io/juce_AudioIODeviceType.cpp" -#include "midi_io/juce_MidiMessageCollector.cpp" -#include "midi_io/juce_MidiOutput.cpp" -#include "sources/juce_AudioSourcePlayer.cpp" -#include "sources/juce_AudioTransportSource.cpp" -#include "native/juce_MidiDataConcatenator.h" - -//============================================================================== -#if JUCE_MAC - #include "native/juce_mac_CoreAudio.cpp" - #include "native/juce_mac_CoreMidi.cpp" - -//============================================================================== -#elif JUCE_IOS - #include "native/juce_ios_Audio.cpp" - #include "native/juce_mac_CoreMidi.cpp" - -//============================================================================== -#elif JUCE_WINDOWS - - #if JUCE_WASAPI - #include "native/juce_win32_WASAPI.cpp" - #endif - - #if JUCE_DIRECTSOUND - #include "native/juce_win32_DirectSound.cpp" - #endif - - #include "native/juce_win32_Midi.cpp" - - #if JUCE_ASIO - #include "native/juce_win32_ASIO.cpp" - #endif - -//============================================================================== -#elif JUCE_LINUX - #if JUCE_ALSA - #include "native/juce_linux_ALSA.cpp" - #endif - - #include "native/juce_linux_Midi.cpp" - - #if JUCE_JACK - #include "native/juce_linux_JackAudio.cpp" - #endif - -//============================================================================== -#elif JUCE_ANDROID - #include "native/juce_android_Audio.cpp" - #include "native/juce_android_Midi.cpp" - - #if JUCE_USE_ANDROID_OPENSLES - #include "native/juce_android_OpenSL.cpp" - #endif -#endif - -#if ! JUCE_SYSTEMAUDIOVOL_IMPLEMENTED -namespace juce -{ - // None of these methods are available. (On Windows you might need to enable WASAPI for this) - float JUCE_CALLTYPE SystemAudioVolume::getGain() { jassertfalse; return 0.0f; } - bool JUCE_CALLTYPE SystemAudioVolume::setGain (float) { jassertfalse; return false; } - bool JUCE_CALLTYPE SystemAudioVolume::isMuted() { jassertfalse; return false; } - bool JUCE_CALLTYPE SystemAudioVolume::setMuted (bool) { jassertfalse; return false; } -} -#endif diff --git a/source/modules/juce_audio_devices/juce_audio_devices.h b/source/modules/juce_audio_devices/juce_audio_devices.h deleted file mode 100644 index 9b26405da..000000000 --- a/source/modules/juce_audio_devices/juce_audio_devices.h +++ /dev/null @@ -1,155 +0,0 @@ -/* - ============================================================================== - - 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. - - ============================================================================== -*/ - -/******************************************************************************* - The block below describes the properties of this module, and is read by - the Projucer to automatically generate project code that uses it. - For details about the syntax and how to create or use a module, see the - JUCE Module Format.txt file. - - - BEGIN_JUCE_MODULE_DECLARATION - - ID: juce_audio_devices - vendor: juce - version: 5.1.2 - name: JUCE audio and MIDI I/O device classes - description: Classes to play and record from audio and MIDI I/O devices - website: http://www.juce.com/juce - license: ISC - - dependencies: juce_audio_basics, juce_events - OSXFrameworks: CoreAudio CoreMIDI AudioToolbox - iOSFrameworks: CoreAudio CoreMIDI AudioToolbox AVFoundation - linuxPackages: alsa - mingwLibs: winmm - - END_JUCE_MODULE_DECLARATION - -*******************************************************************************/ - - -#pragma once -#define JUCE_AUDIO_DEVICES_H_INCLUDED - -#include -#include - -#if JUCE_MODULE_AVAILABLE_juce_graphics -#include -#endif - -//============================================================================== -/** Config: JUCE_ASIO - Enables ASIO audio devices (MS Windows only). - Turning this on means that you'll need to have the Steinberg ASIO SDK installed - on your Windows build machine. - - See the comments in the ASIOAudioIODevice class's header file for more - info about this. -*/ -#ifndef JUCE_ASIO - #define JUCE_ASIO 0 -#endif - -/** Config: JUCE_WASAPI - Enables WASAPI audio devices (Windows Vista and above). See also the - JUCE_WASAPI_EXCLUSIVE flag. -*/ -#ifndef JUCE_WASAPI - #define JUCE_WASAPI 1 -#endif - -/** Config: JUCE_WASAPI_EXCLUSIVE - Enables WASAPI audio devices in exclusive mode (Windows Vista and above). -*/ -#ifndef JUCE_WASAPI_EXCLUSIVE - #define JUCE_WASAPI_EXCLUSIVE 0 -#endif - - -/** Config: JUCE_DIRECTSOUND - Enables DirectSound audio (MS Windows only). -*/ -#ifndef JUCE_DIRECTSOUND - #define JUCE_DIRECTSOUND 1 -#endif - -/** Config: JUCE_ALSA - Enables ALSA audio devices (Linux only). -*/ -#ifndef JUCE_ALSA - #define JUCE_ALSA 1 -#endif - -/** Config: JUCE_JACK - Enables JACK audio devices (Linux only). -*/ -#ifndef JUCE_JACK - #define JUCE_JACK 0 -#endif - -/** Config: JUCE_USE_ANDROID_OPENSLES - Enables OpenSLES devices (Android only). -*/ -#ifndef JUCE_USE_ANDROID_OPENSLES - #if JUCE_ANDROID_API_VERSION > 8 - #define JUCE_USE_ANDROID_OPENSLES 1 - #else - #define JUCE_USE_ANDROID_OPENSLES 0 - #endif -#endif - -/** Config: JUCE_USE_WINRT_MIDI - *** - EXPERIMENTAL - Microsoft's Bluetooth MIDI stack has multiple issues, - use at your own risk! - *** - - Enables the use of the Windows Runtime API for MIDI, which supports - Bluetooth Low Energy connections on computers with the Anniversary Update - of Windows 10. - - To compile with this flag requires version 10.0.14393.0 of the Windows - Standalone SDK and you must add the path to the WinRT headers. This path - should be something similar to - "C:\Program Files (x86)\Windows Kits\10\Include\10.0.14393.0\winrt". -*/ -#ifndef JUCE_USE_WINRT_MIDI - #define JUCE_USE_WINRT_MIDI 0 -#endif - - -//============================================================================== -#include "midi_io/juce_MidiInput.h" -#include "midi_io/juce_MidiMessageCollector.h" -#include "midi_io/juce_MidiOutput.h" -#include "audio_io/juce_AudioIODevice.h" -#include "audio_io/juce_AudioIODeviceType.h" -#include "audio_io/juce_SystemAudioVolume.h" -#include "sources/juce_AudioSourcePlayer.h" -#include "sources/juce_AudioTransportSource.h" -#include "audio_io/juce_AudioDeviceManager.h" - -#if JUCE_IOS - #include "native/juce_ios_Audio.h" -#endif diff --git a/source/modules/juce_audio_devices/midi_io/juce_MidiInput.h b/source/modules/juce_audio_devices/midi_io/juce_MidiInput.h deleted file mode 100644 index 52edd9fe0..000000000 --- a/source/modules/juce_audio_devices/midi_io/juce_MidiInput.h +++ /dev/null @@ -1,176 +0,0 @@ -/* - ============================================================================== - - 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 MidiInput; - - -//============================================================================== -/** - Receives incoming messages from a physical MIDI input device. - - This class is overridden to handle incoming midi messages. See the MidiInput - class for more details. - - @see MidiInput -*/ -class JUCE_API MidiInputCallback -{ -public: - /** Destructor. */ - virtual ~MidiInputCallback() {} - - - /** Receives an incoming message. - - A MidiInput object will call this method when a midi event arrives. It'll be - called on a high-priority system thread, so avoid doing anything time-consuming - in here, and avoid making any UI calls. You might find the MidiBuffer class helpful - for queueing incoming messages for use later. - - @param source the MidiInput object that generated the message - @param message the incoming message. The message's timestamp is set to a value - equivalent to (Time::getMillisecondCounter() / 1000.0) to specify the - time when the message arrived. - */ - virtual void handleIncomingMidiMessage (MidiInput* source, - const MidiMessage& message) = 0; - - /** Notification sent each time a packet of a multi-packet sysex message arrives. - - If a long sysex message is broken up into multiple packets, this callback is made - for each packet that arrives until the message is finished, at which point - the normal handleIncomingMidiMessage() callback will be made with the entire - message. - - The message passed in will contain the start of a sysex, but won't be finished - with the terminating 0xf7 byte. - */ - virtual void handlePartialSysexMessage (MidiInput* source, - const uint8* messageData, - int numBytesSoFar, - double timestamp) - { - ignoreUnused (source, messageData, numBytesSoFar, timestamp); - } -}; - -//============================================================================== -/** - Represents a midi input device. - - To create one of these, use the static getDevices() method to find out what inputs are - available, and then use the openDevice() method to try to open one. - - @see MidiOutput -*/ -class JUCE_API MidiInput -{ -public: - //============================================================================== - /** Returns a list of the available midi input devices. - - You can open one of the devices by passing its index into the - openDevice() method. - - @see getDefaultDeviceIndex, openDevice - */ - static StringArray getDevices(); - - /** Returns the index of the default midi input device to use. - - This refers to the index in the list returned by getDevices(). - */ - static int getDefaultDeviceIndex(); - - /** Tries to open one of the midi input devices. - - This will return a MidiInput object if it manages to open it. You can then - call start() and stop() on this device, and delete it when no longer needed. - - If the device can't be opened, this will return a null pointer. - - @param deviceIndex the index of a device from the list returned by getDevices() - @param callback the object that will receive the midi messages from this device. - - @see MidiInputCallback, getDevices - */ - static MidiInput* openDevice (int deviceIndex, - MidiInputCallback* callback); - - #if JUCE_LINUX || JUCE_MAC || JUCE_IOS || DOXYGEN - /** This will try to create a new midi input device (Not available on Windows). - - This will attempt to create a new midi input device with the specified name, - for other apps to connect to. - - Returns nullptr if a device can't be created. - - @param deviceName the name to use for the new device - @param callback the object that will receive the midi messages from this device. - */ - static MidiInput* createNewDevice (const String& deviceName, - MidiInputCallback* callback); - #endif - - //============================================================================== - /** Destructor. */ - ~MidiInput(); - - /** Returns the name of this device. */ - const String& getName() const noexcept { return name; } - - /** Allows you to set a custom name for the device, in case you don't like the name - it was given when created. - */ - void setName (const String& newName) noexcept { name = newName; } - - //============================================================================== - /** Starts the device running. - - After calling this, the device will start sending midi messages to the - MidiInputCallback object that was specified when the openDevice() method - was called. - - @see stop - */ - void start(); - - /** Stops the device running. - @see start - */ - void stop(); - -private: - //============================================================================== - String name; - void* internal = nullptr; - - // The input objects are created with the openDevice() method. - explicit MidiInput (const String&); - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInput) -}; - -} // namespace juce diff --git a/source/modules/juce_audio_devices/midi_io/juce_MidiMessageCollector.cpp b/source/modules/juce_audio_devices/midi_io/juce_MidiMessageCollector.cpp deleted file mode 100644 index 50ca20569..000000000 --- a/source/modules/juce_audio_devices/midi_io/juce_MidiMessageCollector.cpp +++ /dev/null @@ -1,158 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -MidiMessageCollector::MidiMessageCollector() -{ -} - -MidiMessageCollector::~MidiMessageCollector() -{ -} - -//============================================================================== -void MidiMessageCollector::reset (const double newSampleRate) -{ - jassert (newSampleRate > 0); - - const ScopedLock sl (midiCallbackLock); - #if JUCE_DEBUG - hasCalledReset = true; - #endif - sampleRate = newSampleRate; - incomingMessages.clear(); - lastCallbackTime = Time::getMillisecondCounterHiRes(); -} - -void MidiMessageCollector::addMessageToQueue (const MidiMessage& message) -{ - #if JUCE_DEBUG - jassert (hasCalledReset); // you need to call reset() to set the correct sample rate before using this object - #endif - - // the messages that come in here need to be time-stamped correctly - see MidiInput - // for details of what the number should be. - jassert (message.getTimeStamp() != 0); - - const ScopedLock sl (midiCallbackLock); - - auto sampleNumber = (int) ((message.getTimeStamp() - 0.001 * lastCallbackTime) * sampleRate); - - incomingMessages.addEvent (message, sampleNumber); - - // if the messages don't get used for over a second, we'd better - // get rid of any old ones to avoid the queue getting too big - if (sampleNumber > sampleRate) - incomingMessages.clear (0, sampleNumber - (int) sampleRate); -} - -void MidiMessageCollector::removeNextBlockOfMessages (MidiBuffer& destBuffer, - const int numSamples) -{ - #if JUCE_DEBUG - jassert (hasCalledReset); // you need to call reset() to set the correct sample rate before using this object - #endif - - jassert (numSamples > 0); - - auto timeNow = Time::getMillisecondCounterHiRes(); - auto msElapsed = timeNow - lastCallbackTime; - - const ScopedLock sl (midiCallbackLock); - lastCallbackTime = timeNow; - - if (! incomingMessages.isEmpty()) - { - int numSourceSamples = jmax (1, roundToInt (msElapsed * 0.001 * sampleRate)); - int startSample = 0; - int scale = 1 << 16; - - const uint8* midiData; - int numBytes, samplePosition; - - MidiBuffer::Iterator iter (incomingMessages); - - if (numSourceSamples > numSamples) - { - // if our list of events is longer than the buffer we're being - // asked for, scale them down to squeeze them all in.. - const int maxBlockLengthToUse = numSamples << 5; - - if (numSourceSamples > maxBlockLengthToUse) - { - startSample = numSourceSamples - maxBlockLengthToUse; - numSourceSamples = maxBlockLengthToUse; - iter.setNextSamplePosition (startSample); - } - - scale = (numSamples << 10) / numSourceSamples; - - while (iter.getNextEvent (midiData, numBytes, samplePosition)) - { - samplePosition = ((samplePosition - startSample) * scale) >> 10; - - destBuffer.addEvent (midiData, numBytes, - jlimit (0, numSamples - 1, samplePosition)); - } - } - else - { - // if our event list is shorter than the number we need, put them - // towards the end of the buffer - startSample = numSamples - numSourceSamples; - - while (iter.getNextEvent (midiData, numBytes, samplePosition)) - { - destBuffer.addEvent (midiData, numBytes, - jlimit (0, numSamples - 1, samplePosition + startSample)); - } - } - - incomingMessages.clear(); - } -} - -//============================================================================== -void MidiMessageCollector::handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) -{ - MidiMessage m (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity)); - m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001); - - addMessageToQueue (m); -} - -void MidiMessageCollector::handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) -{ - MidiMessage m (MidiMessage::noteOff (midiChannel, midiNoteNumber, velocity)); - m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001); - - addMessageToQueue (m); -} - -void MidiMessageCollector::handleIncomingMidiMessage (MidiInput*, const MidiMessage& message) -{ - addMessageToQueue (message); -} - -} // namespace juce diff --git a/source/modules/juce_audio_devices/midi_io/juce_MidiMessageCollector.h b/source/modules/juce_audio_devices/midi_io/juce_MidiMessageCollector.h deleted file mode 100644 index aa8bcdbeb..000000000 --- a/source/modules/juce_audio_devices/midi_io/juce_MidiMessageCollector.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -//============================================================================== -/** - Collects incoming realtime MIDI messages and turns them into blocks suitable for - processing by a block-based audio callback. - - The class can also be used as either a MidiKeyboardStateListener or a MidiInputCallback - so it can easily use a midi input or keyboard component as its source. - - @see MidiMessage, MidiInput -*/ -class JUCE_API MidiMessageCollector : public MidiKeyboardStateListener, - public MidiInputCallback -{ -public: - //============================================================================== - /** Creates a MidiMessageCollector. */ - MidiMessageCollector(); - - /** Destructor. */ - ~MidiMessageCollector(); - - //============================================================================== - /** Clears any messages from the queue. - - You need to call this method before starting to use the collector, so that - it knows the correct sample rate to use. - */ - void reset (double sampleRate); - - /** Takes an incoming real-time message and adds it to the queue. - - The message's timestamp is taken, and it will be ready for retrieval as part - of the block returned by the next call to removeNextBlockOfMessages(). - - This method is fully thread-safe when overlapping calls are made with - removeNextBlockOfMessages(). - */ - void addMessageToQueue (const MidiMessage& message); - - /** Removes all the pending messages from the queue as a buffer. - - This will also correct the messages' timestamps to make sure they're in - the range 0 to numSamples - 1. - - This call should be made regularly by something like an audio processing - callback, because the time that it happens is used in calculating the - midi event positions. - - This method is fully thread-safe when overlapping calls are made with - addMessageToQueue(). - - Precondition: numSamples must be greater than 0. - */ - void removeNextBlockOfMessages (MidiBuffer& destBuffer, int numSamples); - - - //============================================================================== - /** @internal */ - void handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override; - /** @internal */ - void handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override; - /** @internal */ - void handleIncomingMidiMessage (MidiInput*, const MidiMessage&) override; - -private: - //============================================================================== - double lastCallbackTime = 0; - CriticalSection midiCallbackLock; - MidiBuffer incomingMessages; - double sampleRate = 44100.0; - #if JUCE_DEBUG - bool hasCalledReset = false; - #endif - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiMessageCollector) -}; - -} // namespace juce diff --git a/source/modules/juce_audio_devices/midi_io/juce_MidiOutput.cpp b/source/modules/juce_audio_devices/midi_io/juce_MidiOutput.cpp deleted file mode 100644 index 36b7cf640..000000000 --- a/source/modules/juce_audio_devices/midi_io/juce_MidiOutput.cpp +++ /dev/null @@ -1,176 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -struct MidiOutput::PendingMessage -{ - PendingMessage (const void* const data, const int len, const double timeStamp) - : message (data, len, timeStamp) - {} - - MidiMessage message; - PendingMessage* next; -}; - -MidiOutput::MidiOutput (const String& midiName) - : Thread ("midi out"), - internal (nullptr), - firstMessage (nullptr), - name (midiName) -{ -} - -void MidiOutput::sendBlockOfMessagesNow (const MidiBuffer& buffer) -{ - MidiBuffer::Iterator i (buffer); - MidiMessage message; - int samplePosition; // Note: not actually used, so no need to initialise. - - while (i.getNextEvent (message, samplePosition)) - sendMessageNow (message); -} - -void MidiOutput::sendBlockOfMessages (const MidiBuffer& buffer, - const double millisecondCounterToStartAt, - double samplesPerSecondForBuffer) -{ - // You've got to call startBackgroundThread() for this to actually work.. - jassert (isThreadRunning()); - - // this needs to be a value in the future - RTFM for this method! - jassert (millisecondCounterToStartAt > 0); - - const double timeScaleFactor = 1000.0 / samplesPerSecondForBuffer; - - MidiBuffer::Iterator i (buffer); - - const uint8* data; - int len, time; - - while (i.getNextEvent (data, len, time)) - { - const double eventTime = millisecondCounterToStartAt + timeScaleFactor * time; - - PendingMessage* const m = new PendingMessage (data, len, eventTime); - - const ScopedLock sl (lock); - - if (firstMessage == nullptr || firstMessage->message.getTimeStamp() > eventTime) - { - m->next = firstMessage; - firstMessage = m; - } - else - { - PendingMessage* mm = firstMessage; - - while (mm->next != nullptr && mm->next->message.getTimeStamp() <= eventTime) - mm = mm->next; - - m->next = mm->next; - mm->next = m; - } - } - - notify(); -} - -void MidiOutput::clearAllPendingMessages() -{ - const ScopedLock sl (lock); - - while (firstMessage != nullptr) - { - PendingMessage* const m = firstMessage; - firstMessage = firstMessage->next; - delete m; - } -} - -void MidiOutput::startBackgroundThread() -{ - startThread (9); -} - -void MidiOutput::stopBackgroundThread() -{ - stopThread (5000); -} - -void MidiOutput::run() -{ - while (! threadShouldExit()) - { - uint32 now = Time::getMillisecondCounter(); - uint32 eventTime = 0; - uint32 timeToWait = 500; - - PendingMessage* message; - - { - const ScopedLock sl (lock); - message = firstMessage; - - if (message != nullptr) - { - eventTime = (uint32) roundToInt (message->message.getTimeStamp()); - - if (eventTime > now + 20) - { - timeToWait = eventTime - (now + 20); - message = nullptr; - } - else - { - firstMessage = message->next; - } - } - } - - if (message != nullptr) - { - const ScopedPointer messageDeleter (message); - - if (eventTime > now) - { - Time::waitForMillisecondCounter (eventTime); - - if (threadShouldExit()) - break; - } - - if (eventTime > now - 200) - sendMessageNow (message->message); - } - else - { - jassert (timeToWait < 1000 * 30); - wait ((int) timeToWait); - } - } - - clearAllPendingMessages(); -} - -} // namespace juce diff --git a/source/modules/juce_audio_devices/midi_io/juce_MidiOutput.h b/source/modules/juce_audio_devices/midi_io/juce_MidiOutput.h deleted file mode 100644 index f05dc32f1..000000000 --- a/source/modules/juce_audio_devices/midi_io/juce_MidiOutput.h +++ /dev/null @@ -1,143 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -//============================================================================== -/** - Controls a physical MIDI output device. - - To create one of these, use the static getDevices() method to get a list of the - available output devices, then use the openDevice() method to try to open one. - - @see MidiInput -*/ -class JUCE_API MidiOutput : private Thread -{ -public: - //============================================================================== - /** Returns a list of the available midi output devices. - - You can open one of the devices by passing its index into the - openDevice() method. - - @see getDefaultDeviceIndex, openDevice - */ - static StringArray getDevices(); - - /** Returns the index of the default midi output device to use. - - This refers to the index in the list returned by getDevices(). - */ - static int getDefaultDeviceIndex(); - - /** Tries to open one of the midi output devices. - - This will return a MidiOutput object if it manages to open it. You can then - send messages to this device, and delete it when no longer needed. - - If the device can't be opened, this will return a null pointer. - - @param deviceIndex the index of a device from the list returned by getDevices() - @see getDevices - */ - static MidiOutput* openDevice (int deviceIndex); - - - #if JUCE_LINUX || JUCE_MAC || JUCE_IOS || DOXYGEN - /** This will try to create a new midi output device (Not available on Windows). - - This will attempt to create a new midi output device that other apps can connect - to and use as their midi input. - - Returns nullptr if a device can't be created. - - @param deviceName the name to use for the new device - */ - static MidiOutput* createNewDevice (const String& deviceName); - #endif - - //============================================================================== - /** Destructor. */ - ~MidiOutput(); - - /** Returns the name of this device. */ - const String& getName() const noexcept { return name; } - - /** Sends out a MIDI message immediately. */ - void sendMessageNow (const MidiMessage& message); - - /** Sends out a sequence of MIDI messages immediately. */ - void sendBlockOfMessagesNow (const MidiBuffer& buffer); - - //============================================================================== - /** This lets you supply a block of messages that will be sent out at some point - in the future. - - The MidiOutput class has an internal thread that can send out timestamped - messages - this appends a set of messages to its internal buffer, ready for - sending. - - This will only work if you've already started the thread with startBackgroundThread(). - - A time is specified, at which the block of messages should be sent. This time uses - the same time base as Time::getMillisecondCounter(), and must be in the future. - - The samplesPerSecondForBuffer parameter indicates the number of samples per second - used by the MidiBuffer. Each event in a MidiBuffer has a sample position, and the - samplesPerSecondForBuffer value is needed to convert this sample position to a - real time. - */ - void sendBlockOfMessages (const MidiBuffer& buffer, - double millisecondCounterToStartAt, - double samplesPerSecondForBuffer); - - /** Gets rid of any midi messages that had been added by sendBlockOfMessages(). */ - void clearAllPendingMessages(); - - /** Starts up a background thread so that the device can send blocks of data. - Call this to get the device ready, before using sendBlockOfMessages(). - */ - void startBackgroundThread(); - - /** Stops the background thread, and clears any pending midi events. - @see startBackgroundThread - */ - void stopBackgroundThread(); - - -private: - //============================================================================== - void* internal = nullptr; - CriticalSection lock; - struct PendingMessage; - PendingMessage* firstMessage; - String name; - - MidiOutput (const String& midiName); // These objects are created with the openDevice() method. - void run() override; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutput) -}; - -} // namespace juce diff --git a/source/modules/juce_audio_devices/native/juce_MidiDataConcatenator.h b/source/modules/juce_audio_devices/native/juce_MidiDataConcatenator.h deleted file mode 100644 index 1df491240..000000000 --- a/source/modules/juce_audio_devices/native/juce_MidiDataConcatenator.h +++ /dev/null @@ -1,191 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -//============================================================================== -/** - Helper class that takes chunks of incoming midi bytes, packages them into - messages, and dispatches them to a midi callback. -*/ -class MidiDataConcatenator -{ -public: - //============================================================================== - MidiDataConcatenator (int initialBufferSize) - : pendingData ((size_t) initialBufferSize) - { - } - - void reset() - { - pendingBytes = 0; - runningStatus = 0; - pendingDataTime = 0; - } - - template - void pushMidiData (const void* inputData, int numBytes, double time, - UserDataType* input, CallbackType& callback) - { - const uint8* d = static_cast (inputData); - - while (numBytes > 0) - { - if (pendingBytes > 0 || d[0] == 0xf0) - { - processSysex (d, numBytes, time, input, callback); - runningStatus = 0; - } - else - { - int len = 0; - uint8 data[3]; - - while (numBytes > 0) - { - // If there's a realtime message embedded in the middle of - // the normal message, handle it now.. - if (*d >= 0xf8 && *d <= 0xfe) - { - callback.handleIncomingMidiMessage (input, MidiMessage (*d++, time)); - --numBytes; - } - else - { - if (len == 0 && *d < 0x80 && runningStatus >= 0x80) - data[len++] = runningStatus; - - data[len++] = *d++; - --numBytes; - - const uint8 firstByte = data[0]; - - if (firstByte < 0x80 || firstByte == 0xf7) - { - len = 0; - break; // ignore this malformed MIDI message.. - } - - if (len >= MidiMessage::getMessageLengthFromFirstByte (firstByte)) - break; - } - } - - if (len > 0) - { - int used = 0; - const MidiMessage m (data, len, used, 0, time); - - if (used <= 0) - break; // malformed message.. - - jassert (used == len); - callback.handleIncomingMidiMessage (input, m); - runningStatus = data[0]; - } - } - } - } - -private: - template - void processSysex (const uint8*& d, int& numBytes, double time, - UserDataType* input, CallbackType& callback) - { - if (*d == 0xf0) - { - pendingBytes = 0; - pendingDataTime = time; - } - - pendingData.ensureSize ((size_t) (pendingBytes + numBytes), false); - uint8* totalMessage = static_cast (pendingData.getData()); - uint8* dest = totalMessage + pendingBytes; - - do - { - if (pendingBytes > 0 && *d >= 0x80) - { - if (*d == 0xf7) - { - *dest++ = *d++; - ++pendingBytes; - --numBytes; - break; - } - - if (*d >= 0xfa || *d == 0xf8) - { - callback.handleIncomingMidiMessage (input, MidiMessage (*d, time)); - ++d; - --numBytes; - } - else - { - pendingBytes = 0; - int used = 0; - const MidiMessage m (d, numBytes, used, 0, time); - - if (used > 0) - { - callback.handleIncomingMidiMessage (input, m); - numBytes -= used; - d += used; - } - - break; - } - } - else - { - *dest++ = *d++; - ++pendingBytes; - --numBytes; - } - } - while (numBytes > 0); - - if (pendingBytes > 0) - { - if (totalMessage [pendingBytes - 1] == 0xf7) - { - callback.handleIncomingMidiMessage (input, MidiMessage (totalMessage, pendingBytes, pendingDataTime)); - pendingBytes = 0; - } - else - { - callback.handlePartialSysexMessage (input, totalMessage, pendingBytes, pendingDataTime); - } - } - } - - MemoryBlock pendingData; - double pendingDataTime = 0; - int pendingBytes = 0; - uint8 runningStatus = 0; - - JUCE_DECLARE_NON_COPYABLE (MidiDataConcatenator) -}; - -} // namespace juce diff --git a/source/modules/juce_audio_devices/native/juce_android_Audio.cpp b/source/modules/juce_audio_devices/native/juce_android_Audio.cpp deleted file mode 100644 index 42f6fdd98..000000000 --- a/source/modules/juce_audio_devices/native/juce_android_Audio.cpp +++ /dev/null @@ -1,497 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - STATICMETHOD (getMinBufferSize, "getMinBufferSize", "(III)I") \ - STATICMETHOD (getNativeOutputSampleRate, "getNativeOutputSampleRate", "(I)I") \ - METHOD (constructor, "", "(IIIIII)V") \ - METHOD (getState, "getState", "()I") \ - METHOD (play, "play", "()V") \ - METHOD (stop, "stop", "()V") \ - METHOD (release, "release", "()V") \ - METHOD (flush, "flush", "()V") \ - METHOD (write, "write", "([SII)I") \ - -DECLARE_JNI_CLASS (AudioTrack, "android/media/AudioTrack"); -#undef JNI_CLASS_MEMBERS - -//============================================================================== -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - STATICMETHOD (getMinBufferSize, "getMinBufferSize", "(III)I") \ - METHOD (constructor, "", "(IIIII)V") \ - METHOD (getState, "getState", "()I") \ - METHOD (startRecording, "startRecording", "()V") \ - METHOD (stop, "stop", "()V") \ - METHOD (read, "read", "([SII)I") \ - METHOD (release, "release", "()V") \ - -DECLARE_JNI_CLASS (AudioRecord, "android/media/AudioRecord"); -#undef JNI_CLASS_MEMBERS - -//============================================================================== -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - STATICFIELD (SDK_INT, "SDK_INT", "I") \ - - DECLARE_JNI_CLASS (AndroidBuildVersion, "android/os/Build$VERSION"); -#undef JNI_CLASS_MEMBERS - -//============================================================================== -enum -{ - CHANNEL_OUT_STEREO = 12, - CHANNEL_IN_STEREO = 12, - CHANNEL_IN_MONO = 16, - ENCODING_PCM_16BIT = 2, - STREAM_MUSIC = 3, - MODE_STREAM = 1, - STATE_UNINITIALIZED = 0 -}; - -const char* const javaAudioTypeName = "Android Audio"; - -//============================================================================== -class AndroidAudioIODevice : public AudioIODevice, - public Thread -{ -public: - //============================================================================== - AndroidAudioIODevice (const String& deviceName) - : AudioIODevice (deviceName, javaAudioTypeName), - Thread ("audio"), - minBufferSizeOut (0), minBufferSizeIn (0), callback (0), sampleRate (0), - numClientInputChannels (0), numDeviceInputChannels (0), numDeviceInputChannelsAvailable (2), - numClientOutputChannels (0), numDeviceOutputChannels (0), - actualBufferSize (0), isRunning (false), - inputChannelBuffer (1, 1), - outputChannelBuffer (1, 1) - { - JNIEnv* env = getEnv(); - sampleRate = env->CallStaticIntMethod (AudioTrack, AudioTrack.getNativeOutputSampleRate, MODE_STREAM); - - minBufferSizeOut = (int) env->CallStaticIntMethod (AudioTrack, AudioTrack.getMinBufferSize, sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT); - minBufferSizeIn = (int) env->CallStaticIntMethod (AudioRecord, AudioRecord.getMinBufferSize, sampleRate, CHANNEL_IN_STEREO, ENCODING_PCM_16BIT); - - if (minBufferSizeIn <= 0) - { - minBufferSizeIn = env->CallStaticIntMethod (AudioRecord, AudioRecord.getMinBufferSize, sampleRate, CHANNEL_IN_MONO, ENCODING_PCM_16BIT); - - if (minBufferSizeIn > 0) - numDeviceInputChannelsAvailable = 1; - else - numDeviceInputChannelsAvailable = 0; - } - - DBG ("Audio device - min buffers: " << minBufferSizeOut << ", " << minBufferSizeIn << "; " - << sampleRate << " Hz; input chans: " << numDeviceInputChannelsAvailable); - } - - ~AndroidAudioIODevice() - { - close(); - } - - StringArray getOutputChannelNames() override - { - StringArray s; - s.add ("Left"); - s.add ("Right"); - return s; - } - - StringArray getInputChannelNames() override - { - StringArray s; - - if (numDeviceInputChannelsAvailable == 2) - { - s.add ("Left"); - s.add ("Right"); - } - else if (numDeviceInputChannelsAvailable == 1) - { - s.add ("Audio Input"); - } - - return s; - } - - Array getAvailableSampleRates() override - { - Array r; - r.add ((double) sampleRate); - return r; - } - - Array getAvailableBufferSizes() override - { - Array b; - int n = 16; - - for (int i = 0; i < 50; ++i) - { - b.add (n); - n += n < 64 ? 16 - : (n < 512 ? 32 - : (n < 1024 ? 64 - : (n < 2048 ? 128 : 256))); - } - - return b; - } - - int getDefaultBufferSize() override { return 2048; } - - String open (const BigInteger& inputChannels, - const BigInteger& outputChannels, - double requestedSampleRate, - int bufferSize) override - { - close(); - - if (sampleRate != (int) requestedSampleRate) - return "Sample rate not allowed"; - - lastError.clear(); - int preferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize; - - numDeviceInputChannels = 0; - numDeviceOutputChannels = 0; - - activeOutputChans = outputChannels; - activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false); - numClientOutputChannels = activeOutputChans.countNumberOfSetBits(); - - activeInputChans = inputChannels; - activeInputChans.setRange (2, activeInputChans.getHighestBit(), false); - numClientInputChannels = activeInputChans.countNumberOfSetBits(); - - actualBufferSize = preferredBufferSize; - inputChannelBuffer.setSize (2, actualBufferSize); - inputChannelBuffer.clear(); - outputChannelBuffer.setSize (2, actualBufferSize); - outputChannelBuffer.clear(); - - JNIEnv* env = getEnv(); - - if (numClientOutputChannels > 0) - { - numDeviceOutputChannels = 2; - outputDevice = GlobalRef (env->NewObject (AudioTrack, AudioTrack.constructor, - STREAM_MUSIC, sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT, - (jint) (minBufferSizeOut * numDeviceOutputChannels * static_cast (sizeof (int16))), MODE_STREAM)); - - const bool supportsUnderrunCount = (getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT) >= 24); - getUnderrunCount = supportsUnderrunCount ? env->GetMethodID (AudioTrack, "getUnderrunCount", "()I") : 0; - - int outputDeviceState = env->CallIntMethod (outputDevice, AudioTrack.getState); - if (outputDeviceState > 0) - { - isRunning = true; - } - else - { - // failed to open the device - outputDevice.clear(); - lastError = "Error opening audio output device: android.media.AudioTrack failed with state = " + String (outputDeviceState); - } - } - - if (numClientInputChannels > 0 && numDeviceInputChannelsAvailable > 0) - { - if (! RuntimePermissions::isGranted (RuntimePermissions::recordAudio)) - { - // If you hit this assert, you probably forgot to get RuntimePermissions::recordAudio - // before trying to open an audio input device. This is not going to work! - jassertfalse; - - inputDevice.clear(); - lastError = "Error opening audio input device: the app was not granted android.permission.RECORD_AUDIO"; - } - else - { - numDeviceInputChannels = jmin (numClientInputChannels, numDeviceInputChannelsAvailable); - inputDevice = GlobalRef (env->NewObject (AudioRecord, AudioRecord.constructor, - 0 /* (default audio source) */, sampleRate, - numDeviceInputChannelsAvailable > 1 ? CHANNEL_IN_STEREO : CHANNEL_IN_MONO, - ENCODING_PCM_16BIT, - (jint) (minBufferSizeIn * numDeviceInputChannels * static_cast (sizeof (int16))))); - - int inputDeviceState = env->CallIntMethod (inputDevice, AudioRecord.getState); - if (inputDeviceState > 0) - { - isRunning = true; - } - else - { - // failed to open the device - inputDevice.clear(); - lastError = "Error opening audio input device: android.media.AudioRecord failed with state = " + String (inputDeviceState); - } - } - } - - if (isRunning) - { - if (outputDevice != nullptr) - env->CallVoidMethod (outputDevice, AudioTrack.play); - - if (inputDevice != nullptr) - env->CallVoidMethod (inputDevice, AudioRecord.startRecording); - - startThread (8); - } - else - { - closeDevices(); - } - - return lastError; - } - - void close() override - { - if (isRunning) - { - stopThread (2000); - isRunning = false; - closeDevices(); - } - } - - int getOutputLatencyInSamples() override { return (minBufferSizeOut * 3) / 4; } - int getInputLatencyInSamples() override { return (minBufferSizeIn * 3) / 4; } - bool isOpen() override { return isRunning; } - int getCurrentBufferSizeSamples() override { return actualBufferSize; } - int getCurrentBitDepth() override { return 16; } - double getCurrentSampleRate() override { return sampleRate; } - BigInteger getActiveOutputChannels() const override { return activeOutputChans; } - BigInteger getActiveInputChannels() const override { return activeInputChans; } - String getLastError() override { return lastError; } - bool isPlaying() override { return isRunning && callback != 0; } - - int getXRunCount() const noexcept override - { - if (outputDevice != nullptr && getUnderrunCount != 0) - return getEnv()->CallIntMethod (outputDevice, getUnderrunCount); - - return -1; - } - - void start (AudioIODeviceCallback* newCallback) override - { - if (isRunning && callback != newCallback) - { - if (newCallback != nullptr) - newCallback->audioDeviceAboutToStart (this); - - const ScopedLock sl (callbackLock); - callback = newCallback; - } - } - - void stop() override - { - if (isRunning) - { - AudioIODeviceCallback* lastCallback; - - { - const ScopedLock sl (callbackLock); - lastCallback = callback; - callback = nullptr; - } - - if (lastCallback != nullptr) - lastCallback->audioDeviceStopped(); - } - } - - void run() override - { - JNIEnv* env = getEnv(); - jshortArray audioBuffer = env->NewShortArray (actualBufferSize * jmax (numDeviceOutputChannels, numDeviceInputChannels)); - - while (! threadShouldExit()) - { - if (inputDevice != nullptr) - { - jint numRead = env->CallIntMethod (inputDevice, AudioRecord.read, audioBuffer, 0, actualBufferSize * numDeviceInputChannels); - - if (numRead < actualBufferSize * numDeviceInputChannels) - { - DBG ("Audio read under-run! " << numRead); - } - - jshort* const src = env->GetShortArrayElements (audioBuffer, 0); - - for (int chan = 0; chan < inputChannelBuffer.getNumChannels(); ++chan) - { - AudioData::Pointer d (inputChannelBuffer.getWritePointer (chan)); - - if (chan < numDeviceInputChannels) - { - AudioData::Pointer s (src + chan, numDeviceInputChannels); - d.convertSamples (s, actualBufferSize); - } - else - { - d.clearSamples (actualBufferSize); - } - } - - env->ReleaseShortArrayElements (audioBuffer, src, 0); - } - - if (threadShouldExit()) - break; - - { - const ScopedLock sl (callbackLock); - - if (callback != nullptr) - { - callback->audioDeviceIOCallback (inputChannelBuffer.getArrayOfReadPointers(), numClientInputChannels, - outputChannelBuffer.getArrayOfWritePointers(), numClientOutputChannels, - actualBufferSize); - } - else - { - outputChannelBuffer.clear(); - } - } - - if (outputDevice != nullptr) - { - if (threadShouldExit()) - break; - - jshort* const dest = env->GetShortArrayElements (audioBuffer, 0); - - for (int chan = 0; chan < numDeviceOutputChannels; ++chan) - { - AudioData::Pointer d (dest + chan, numDeviceOutputChannels); - - const float* const sourceChanData = outputChannelBuffer.getReadPointer (jmin (chan, outputChannelBuffer.getNumChannels() - 1)); - AudioData::Pointer s (sourceChanData); - d.convertSamples (s, actualBufferSize); - } - - env->ReleaseShortArrayElements (audioBuffer, dest, 0); - jint numWritten = env->CallIntMethod (outputDevice, AudioTrack.write, audioBuffer, 0, actualBufferSize * numDeviceOutputChannels); - - if (numWritten < actualBufferSize * numDeviceOutputChannels) - { - DBG ("Audio write underrun! " << numWritten); - } - } - } - } - - int minBufferSizeOut, minBufferSizeIn; - -private: - //============================================================================== - CriticalSection callbackLock; - AudioIODeviceCallback* callback; - jint sampleRate; - int numClientInputChannels, numDeviceInputChannels, numDeviceInputChannelsAvailable; - int numClientOutputChannels, numDeviceOutputChannels; - int actualBufferSize; - bool isRunning; - String lastError; - BigInteger activeOutputChans, activeInputChans; - GlobalRef outputDevice, inputDevice; - AudioSampleBuffer inputChannelBuffer, outputChannelBuffer; - jmethodID getUnderrunCount = 0; - - void closeDevices() - { - if (outputDevice != nullptr) - { - outputDevice.callVoidMethod (AudioTrack.stop); - outputDevice.callVoidMethod (AudioTrack.release); - outputDevice.clear(); - } - - if (inputDevice != nullptr) - { - inputDevice.callVoidMethod (AudioRecord.stop); - inputDevice.callVoidMethod (AudioRecord.release); - inputDevice.clear(); - } - } - - JUCE_DECLARE_NON_COPYABLE (AndroidAudioIODevice) -}; - -//============================================================================== -class AndroidAudioIODeviceType : public AudioIODeviceType -{ -public: - AndroidAudioIODeviceType() : AudioIODeviceType (javaAudioTypeName) {} - - //============================================================================== - void scanForDevices() {} - StringArray getDeviceNames (bool) const { return StringArray (javaAudioTypeName); } - int getDefaultDeviceIndex (bool) const { return 0; } - int getIndexOfDevice (AudioIODevice* device, bool) const { return device != nullptr ? 0 : -1; } - bool hasSeparateInputsAndOutputs() const { return false; } - - AudioIODevice* createDevice (const String& outputDeviceName, - const String& inputDeviceName) - { - ScopedPointer dev; - - if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty()) - { - dev = new AndroidAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName - : inputDeviceName); - - if (dev->getCurrentSampleRate() <= 0 || dev->getDefaultBufferSize() <= 0) - dev = nullptr; - } - - return dev.release(); - } - -private: - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidAudioIODeviceType) -}; - - -//============================================================================== -extern bool isOpenSLAvailable(); - -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android() -{ - #if JUCE_USE_ANDROID_OPENSLES - if (isOpenSLAvailable()) - return nullptr; - #endif - - return new AndroidAudioIODeviceType(); -} - -} // namespace juce diff --git a/source/modules/juce_audio_devices/native/juce_android_Midi.cpp b/source/modules/juce_audio_devices/native/juce_android_Midi.cpp deleted file mode 100644 index 5afe6562c..000000000 --- a/source/modules/juce_audio_devices/native/juce_android_Midi.cpp +++ /dev/null @@ -1,364 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (getJuceAndroidMidiInputDevices, "getJuceAndroidMidiInputDevices", "()[Ljava/lang/String;") \ - METHOD (getJuceAndroidMidiOutputDevices, "getJuceAndroidMidiOutputDevices", "()[Ljava/lang/String;") \ - METHOD (openMidiInputPortWithJuceIndex, "openMidiInputPortWithJuceIndex", "(IJ)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort;") \ - METHOD (openMidiOutputPortWithJuceIndex, "openMidiOutputPortWithJuceIndex", "(I)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort;") \ - METHOD (getInputPortNameForJuceIndex, "getInputPortNameForJuceIndex", "(I)Ljava/lang/String;") \ - METHOD (getOutputPortNameForJuceIndex, "getOutputPortNameForJuceIndex", "(I)Ljava/lang/String;") - DECLARE_JNI_CLASS (MidiDeviceManager, JUCE_ANDROID_ACTIVITY_CLASSPATH "$MidiDeviceManager") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ - METHOD (start, "start", "()V" )\ - METHOD (stop, "stop", "()V") \ - METHOD (close, "close", "()V") \ - METHOD (sendMidi, "sendMidi", "([BII)V") - DECLARE_JNI_CLASS (JuceMidiPort, JUCE_ANDROID_ACTIVITY_CLASSPATH "$JuceMidiPort") -#undef JNI_CLASS_MEMBERS - - -//============================================================================== -class AndroidMidiInput -{ -public: - AndroidMidiInput (MidiInput* midiInput, int portIdx, - juce::MidiInputCallback* midiInputCallback, jobject deviceManager) - : juceMidiInput (midiInput), - callback (midiInputCallback), - midiConcatenator (2048), - javaMidiDevice (getEnv()->CallObjectMethod (deviceManager, - MidiDeviceManager.openMidiInputPortWithJuceIndex, - (jint) portIdx, - (jlong) this)) - { - } - - ~AndroidMidiInput() - { - if (jobject d = javaMidiDevice.get()) - { - getEnv()->CallVoidMethod (d, JuceMidiPort.close); - javaMidiDevice.clear(); - } - } - - bool isOpen() const noexcept - { - return javaMidiDevice != nullptr; - } - - void start() - { - if (jobject d = javaMidiDevice.get()) - getEnv()->CallVoidMethod (d, JuceMidiPort.start); - } - - void stop() - { - if (jobject d = javaMidiDevice.get()) - getEnv()->CallVoidMethod (d, JuceMidiPort.stop); - - callback = nullptr; - } - - void receive (jbyteArray byteArray, jlong offset, jint len, jlong timestamp) - { - jassert (byteArray != nullptr); - jbyte* data = getEnv()->GetByteArrayElements (byteArray, nullptr); - - HeapBlock buffer (static_cast (len)); - std::memcpy (buffer.get(), data + offset, static_cast (len)); - - midiConcatenator.pushMidiData (buffer.get(), - len, static_cast (timestamp) * 1.0e-9, - juceMidiInput, *callback); - - getEnv()->ReleaseByteArrayElements (byteArray, data, 0); - } - -private: - MidiInput* juceMidiInput; - MidiInputCallback* callback; - MidiDataConcatenator midiConcatenator; - GlobalRef javaMidiDevice; -}; - -//============================================================================== -class AndroidMidiOutput -{ -public: - AndroidMidiOutput (jobject midiDevice) - : javaMidiDevice (midiDevice) - { - } - - ~AndroidMidiOutput() - { - if (jobject d = javaMidiDevice.get()) - { - getEnv()->CallVoidMethod (d, JuceMidiPort.close); - javaMidiDevice.clear(); - } - } - - void send (jbyteArray byteArray, jint offset, jint len) - { - if (jobject d = javaMidiDevice.get()) - getEnv()->CallVoidMethod (d, - JuceMidiPort.sendMidi, - byteArray, offset, len); - } - -private: - GlobalRef javaMidiDevice; -}; - -JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024JuceMidiInputPort), handleReceive, - void, (JNIEnv* env, jobject, jlong host, jbyteArray byteArray, - jint offset, jint count, jlong timestamp)) -{ - // Java may create a Midi thread which JUCE doesn't know about and this callback may be - // received on this thread. Java will have already created a JNI Env for this new thread, - // which we need to tell Juce about - setEnv (env); - - reinterpret_cast (host)->receive (byteArray, offset, count, timestamp); -} - -//============================================================================== -class AndroidMidiDeviceManager -{ -public: - AndroidMidiDeviceManager() - : deviceManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidMidiDeviceManager)) - { - } - - String getInputPortNameForJuceIndex (int idx) - { - if (jobject dm = deviceManager.get()) - { - LocalRef string ((jstring) getEnv()->CallObjectMethod (dm, MidiDeviceManager.getInputPortNameForJuceIndex, idx)); - return juceString (string); - } - - return {}; - } - - String getOutputPortNameForJuceIndex (int idx) - { - if (jobject dm = deviceManager.get()) - { - LocalRef string ((jstring) getEnv()->CallObjectMethod (dm, MidiDeviceManager.getOutputPortNameForJuceIndex, idx)); - return juceString (string); - } - - return {}; - } - - StringArray getDevices (bool input) - { - if (jobject dm = deviceManager.get()) - { - jobjectArray jDevices - = (jobjectArray) getEnv()->CallObjectMethod (dm, input ? MidiDeviceManager.getJuceAndroidMidiInputDevices - : MidiDeviceManager.getJuceAndroidMidiOutputDevices); - - // Create a local reference as converting this - // to a JUCE string will call into JNI - LocalRef devices (jDevices); - return javaStringArrayToJuce (devices); - } - - return {}; - } - - AndroidMidiInput* openMidiInputPortWithIndex (int idx, MidiInput* juceMidiInput, juce::MidiInputCallback* callback) - { - if (jobject dm = deviceManager.get()) - { - ScopedPointer androidMidiInput (new AndroidMidiInput (juceMidiInput, idx, callback, dm)); - - if (androidMidiInput->isOpen()) - return androidMidiInput.release(); - } - - return nullptr; - } - - AndroidMidiOutput* openMidiOutputPortWithIndex (int idx) - { - if (jobject dm = deviceManager.get()) - if (jobject javaMidiPort = getEnv()->CallObjectMethod (dm, MidiDeviceManager.openMidiOutputPortWithJuceIndex, (jint) idx)) - return new AndroidMidiOutput (javaMidiPort); - - return nullptr; - } - -private: - static StringArray javaStringArrayToJuce (jobjectArray jStrings) - { - StringArray retval; - - JNIEnv* env = getEnv(); - const int count = env->GetArrayLength (jStrings); - - for (int i = 0; i < count; ++i) - { - LocalRef string ((jstring) env->GetObjectArrayElement (jStrings, i)); - retval.add (juceString (string)); - } - - return retval; - } - - GlobalRef deviceManager; -}; - -//============================================================================== -StringArray MidiOutput::getDevices() -{ - AndroidMidiDeviceManager manager; - return manager.getDevices (false); -} - -int MidiOutput::getDefaultDeviceIndex() -{ - return 0; -} - -MidiOutput* MidiOutput::openDevice (int index) -{ - if (index < 0) - return nullptr; - - AndroidMidiDeviceManager manager; - - String midiOutputName = manager.getOutputPortNameForJuceIndex (index); - - if (midiOutputName.isEmpty()) - { - // you supplied an invalid device index! - jassertfalse; - return nullptr; - } - - if (AndroidMidiOutput* midiOutput = manager.openMidiOutputPortWithIndex (index)) - { - MidiOutput* retval = new MidiOutput (midiOutputName); - retval->internal = midiOutput; - - return retval; - } - - return nullptr; -} - -MidiOutput::~MidiOutput() -{ - stopBackgroundThread(); - - delete reinterpret_cast (internal); -} - -void MidiOutput::sendMessageNow (const MidiMessage& message) -{ - if (AndroidMidiOutput* androidMidi = reinterpret_cast(internal)) - { - JNIEnv* env = getEnv(); - const int messageSize = message.getRawDataSize(); - - LocalRef messageContent = LocalRef (env->NewByteArray (messageSize)); - jbyteArray content = messageContent.get(); - - jbyte* rawBytes = env->GetByteArrayElements (content, nullptr); - std::memcpy (rawBytes, message.getRawData(), static_cast (messageSize)); - env->ReleaseByteArrayElements (content, rawBytes, 0); - - androidMidi->send (content, (jint) 0, (jint) messageSize); - } -} - -//============================================================================== -MidiInput::MidiInput (const String& nm) : name (nm) -{ -} - -StringArray MidiInput::getDevices() -{ - AndroidMidiDeviceManager manager; - return manager.getDevices (true); -} - -int MidiInput::getDefaultDeviceIndex() -{ - return 0; -} - -MidiInput* MidiInput::openDevice (int index, juce::MidiInputCallback* callback) -{ - if (index < 0) - return nullptr; - - AndroidMidiDeviceManager manager; - - String midiInputName = manager.getInputPortNameForJuceIndex (index); - - if (midiInputName.isEmpty()) - { - // you supplied an invalid device index! - jassertfalse; - return nullptr; - } - - ScopedPointer midiInput (new MidiInput (midiInputName)); - - midiInput->internal = manager.openMidiInputPortWithIndex (index, midiInput, callback); - - return midiInput->internal != nullptr ? midiInput.release() - : nullptr; -} - -void MidiInput::start() -{ - if (AndroidMidiInput* mi = reinterpret_cast (internal)) - mi->start(); -} - -void MidiInput::stop() -{ - if (AndroidMidiInput* mi = reinterpret_cast (internal)) - mi->stop(); -} - -MidiInput::~MidiInput() -{ - delete reinterpret_cast (internal); -} - -} // namespace juce diff --git a/source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp b/source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp deleted file mode 100644 index 48cd2fe91..000000000 --- a/source/modules/juce_audio_devices/native/juce_android_OpenSL.cpp +++ /dev/null @@ -1,1321 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -//============================================================================== -#ifndef SL_ANDROID_DATAFORMAT_PCM_EX - #define SL_ANDROID_DATAFORMAT_PCM_EX ((SLuint32) 0x00000004) -#endif - -#ifndef SL_ANDROID_PCM_REPRESENTATION_FLOAT - #define SL_ANDROID_PCM_REPRESENTATION_FLOAT ((SLuint32) 0x00000003) -#endif - -#ifndef SL_ANDROID_RECORDING_PRESET_UNPROCESSED - #define SL_ANDROID_RECORDING_PRESET_UNPROCESSED ((SLuint32) 0x00000005) -#endif - -//============================================================================== -struct PCMDataFormatEx : SLDataFormat_PCM -{ - SLuint32 representation; -}; - -//============================================================================== -template struct IntfIID; -template <> struct IntfIID { static SLInterfaceID_ iid; }; -template <> struct IntfIID { static SLInterfaceID_ iid; }; -template <> struct IntfIID { static SLInterfaceID_ iid; }; -template <> struct IntfIID { static SLInterfaceID_ iid; }; -template <> struct IntfIID { static SLInterfaceID_ iid; }; -template <> struct IntfIID { static SLInterfaceID_ iid; }; -template <> struct IntfIID { static SLInterfaceID_ iid; }; - -SLInterfaceID_ IntfIID::iid = { 0x79216360, 0xddd7, 0x11db, 0xac16, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b} }; -SLInterfaceID_ IntfIID::iid = { 0x8d97c260, 0xddd4, 0x11db, 0x958f, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b} }; -SLInterfaceID_ IntfIID::iid = { 0x97750f60, 0xddd7, 0x11db, 0x92b1, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b} }; -SLInterfaceID_ IntfIID::iid = { 0xef0bd9c0, 0xddd7, 0x11db, 0xbf49, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b} }; -SLInterfaceID_ IntfIID::iid = { 0xc5657aa0, 0xdddb, 0x11db, 0x82f7, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b} }; -SLInterfaceID_ IntfIID::iid = { 0x198e4940, 0xc5d7, 0x11df, 0xa2a6, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b} }; -SLInterfaceID_ IntfIID::iid = { 0x89f6a7e0, 0xbeac, 0x11df, 0x8b5c, {0x00, 0x02, 0xa5, 0xd5, 0xc5, 0x1b} }; - -//============================================================================== -// Some life-time and type management of OpenSL objects -class SlObjectRef -{ -public: - //============================================================================== - SlObjectRef() noexcept {} - SlObjectRef (const SlObjectRef& obj) noexcept : cb (obj.cb) {} - SlObjectRef (SlObjectRef&& obj) noexcept : cb (static_cast&&> (obj.cb)) { obj.cb = nullptr; } - explicit SlObjectRef (SLObjectItf o) : cb (new ControlBlock (o)) {} - - //============================================================================== - SlObjectRef& operator=(const SlObjectRef& r) noexcept { cb = r.cb; return *this; } - SlObjectRef& operator=(SlObjectRef&& r) noexcept { cb = static_cast&&> (r.cb); r.cb = nullptr; return *this; } - SlObjectRef& operator=(std::nullptr_t) noexcept { cb = nullptr; return *this; } - - //============================================================================== - const SLObjectItf_* const operator*() noexcept { return *cb->ptr.get(); } - SLObjectItf operator->() noexcept { return (cb == nullptr ? nullptr : cb->ptr.get()); } - operator SLObjectItf() noexcept { return (cb == nullptr ? nullptr : cb->ptr.get()); } - - //============================================================================== - bool operator== (nullptr_t) const noexcept { return (cb == nullptr || cb->ptr == nullptr); } - bool operator!= (nullptr_t) const noexcept { return (cb != nullptr && cb->ptr != nullptr); } -private: - - //============================================================================== - struct ControlBlock : ReferenceCountedObject { ScopedPointer ptr; ControlBlock() {} ControlBlock (SLObjectItf o) : ptr (o) {} }; - ReferenceCountedObjectPtr cb; -}; - -template -class SlRef : public SlObjectRef -{ -public: - //============================================================================== - SlRef() noexcept : type (nullptr) {} - SlRef (SlRef& r) noexcept : SlObjectRef (r), type (r.type) {} - SlRef (SlRef&& r) noexcept : SlObjectRef (static_cast (r)), type (r.type) { r.type = nullptr; } - - //============================================================================== - SlRef& operator= (const SlRef& r) noexcept { SlObjectRef::operator= (r); type = r.type; return *this; } - SlRef& operator= (SlRef&& r) noexcept { SlObjectRef::operator= (static_cast (r)); type = r.type; r.type = nullptr; return *this; } - SlRef& operator= (std::nullptr_t) noexcept { SlObjectRef::operator= (nullptr); type = nullptr; return *this; } - - //============================================================================== - T* const operator*() noexcept { return *type; } - T* const * operator->() noexcept { return type; } - operator T* const *() noexcept { return type; } - - //============================================================================== - static SlRef cast (SlObjectRef& base) { return SlRef (base); } - static SlRef cast (SlObjectRef&& base) { return SlRef (static_cast (base)); } -private: - //============================================================================== - SlRef (SlObjectRef& base) : SlObjectRef (base) - { - SLObjectItf obj = SlObjectRef::operator->(); - SLresult err = (*obj)->GetInterface (obj, &IntfIID::iid, &type); - if (type == nullptr || err != SL_RESULT_SUCCESS) - *this = nullptr; - } - - SlRef (SlObjectRef&& base) : SlObjectRef (static_cast (base)) - { - SLObjectItf obj = SlObjectRef::operator->(); - SLresult err = (*obj)->GetInterface (obj, &IntfIID::iid, &type); - base = nullptr; - - if (type == nullptr || err != SL_RESULT_SUCCESS) - *this = nullptr; - } - T* const * type; -}; - -template <> -struct ContainerDeletePolicy -{ - static void destroy (SLObjectItf object) - { - if (object != nullptr) - (*object)->Destroy (object); - } -}; - -//============================================================================== -template struct BufferHelpers {}; - -template <> -struct BufferHelpers -{ - enum { isFloatingPoint = 0 }; - - static void initPCMDataFormat (PCMDataFormatEx& dataFormat, int numChannels, double sampleRate) - { - dataFormat.formatType = SL_DATAFORMAT_PCM; - dataFormat.numChannels = (SLuint32) numChannels; - dataFormat.samplesPerSec = (SLuint32) (sampleRate * 1000); - dataFormat.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; - dataFormat.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; - dataFormat.channelMask = (numChannels == 1) ? SL_SPEAKER_FRONT_CENTER : - (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT); - dataFormat.endianness = SL_BYTEORDER_LITTLEENDIAN; - dataFormat.representation = 0; - } - - static void prepareCallbackBuffer (AudioSampleBuffer&, int16*) {} - - static void convertFromOpenSL (const int16* srcInterleaved, AudioSampleBuffer& audioBuffer) - { - for (int i = 0; i < audioBuffer.getNumChannels(); ++i) - { - typedef AudioData::Pointer DstSampleType; - typedef AudioData::Pointer SrcSampleType; - - DstSampleType dstData (audioBuffer.getWritePointer (i)); - SrcSampleType srcData (srcInterleaved + i, audioBuffer.getNumChannels()); - dstData.convertSamples (srcData, audioBuffer.getNumSamples()); - } - } - - static void convertToOpenSL (const AudioSampleBuffer& audioBuffer, int16* dstInterleaved) - { - for (int i = 0; i < audioBuffer.getNumChannels(); ++i) - { - typedef AudioData::Pointer DstSampleType; - typedef AudioData::Pointer SrcSampleType; - - DstSampleType dstData (dstInterleaved + i, audioBuffer.getNumChannels()); - SrcSampleType srcData (audioBuffer.getReadPointer (i)); - - dstData.convertSamples (srcData, audioBuffer.getNumSamples()); - } - } - -}; - -template <> -struct BufferHelpers -{ - enum { isFloatingPoint = 1 }; - - static void initPCMDataFormat (PCMDataFormatEx& dataFormat, int numChannels, double sampleRate) - { - dataFormat.formatType = SL_ANDROID_DATAFORMAT_PCM_EX; - dataFormat.numChannels = (SLuint32) numChannels; - dataFormat.samplesPerSec = (SLuint32) (sampleRate * 1000); - dataFormat.bitsPerSample = 32; - dataFormat.containerSize = 32; - dataFormat.channelMask = (numChannels == 1) ? SL_SPEAKER_FRONT_CENTER : - (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT); - dataFormat.endianness = SL_BYTEORDER_LITTLEENDIAN; - dataFormat.representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT; - } - - static void prepareCallbackBuffer (AudioSampleBuffer& audioBuffer, float* native) - { - if (audioBuffer.getNumChannels() == 1) - audioBuffer.setDataToReferTo (&native, 1, audioBuffer.getNumSamples()); - } - - static void convertFromOpenSL (const float* srcInterleaved, AudioSampleBuffer& audioBuffer) - { - if (audioBuffer.getNumChannels() == 1) - { - jassert (srcInterleaved == audioBuffer.getWritePointer (0)); - return; - } - - for (int i = 0; i < audioBuffer.getNumChannels(); ++i) - { - typedef AudioData::Pointer DstSampleType; - typedef AudioData::Pointer SrcSampleType; - - DstSampleType dstData (audioBuffer.getWritePointer (i)); - SrcSampleType srcData (srcInterleaved + i, audioBuffer.getNumChannels()); - dstData.convertSamples (srcData, audioBuffer.getNumSamples()); - } - } - - static void convertToOpenSL (const AudioSampleBuffer& audioBuffer, float* dstInterleaved) - { - if (audioBuffer.getNumChannels() == 1) - { - jassert (dstInterleaved == audioBuffer.getReadPointer (0)); - return; - } - - for (int i = 0; i < audioBuffer.getNumChannels(); ++i) - { - typedef AudioData::Pointer DstSampleType; - typedef AudioData::Pointer SrcSampleType; - - DstSampleType dstData (dstInterleaved + i, audioBuffer.getNumChannels()); - SrcSampleType srcData (audioBuffer.getReadPointer (i)); - - dstData.convertSamples (srcData, audioBuffer.getNumSamples()); - } - } -}; - -class SLRealtimeThread; - -//============================================================================== -class OpenSLAudioIODevice : public AudioIODevice -{ -public: - //============================================================================== - template - class OpenSLSessionT; - - //============================================================================== - // CRTP - template - struct OpenSLQueueRunner - { - OpenSLQueueRunner (OpenSLSessionT& sessionToUse, int numChannelsToUse) - : owner (sessionToUse), - numChannels (numChannelsToUse), - nativeBuffer (static_cast (numChannels * owner.bufferSize * owner.numBuffers)), - scratchBuffer (numChannelsToUse, owner.bufferSize), - sampleBuffer (scratchBuffer.getArrayOfWritePointers(), numChannelsToUse, owner.bufferSize), - nextBlock (0), numBlocksOut (0) - {} - - ~OpenSLQueueRunner() - { - if (config != nullptr && javaProxy != nullptr) - { - javaProxy.clear(); - (*config)->ReleaseJavaProxy (config, SL_ANDROID_JAVA_PROXY_ROUTING); - } - } - - bool init() - { - runner = crtp().createPlayerOrRecorder(); - if (runner == nullptr) - return false; - - const bool supportsJavaProxy = (getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT) >= 24); - - if (supportsJavaProxy) - { - // may return nullptr on some platforms - that's ok - config = SlRef::cast (runner); - - if (config != nullptr) - { - jobject audioRoutingJni; - auto status = (*config)->AcquireJavaProxy (config, SL_ANDROID_JAVA_PROXY_ROUTING, - &audioRoutingJni); - - if (status == SL_RESULT_SUCCESS && audioRoutingJni != 0) - javaProxy = GlobalRef (audioRoutingJni); - } - } - - queue = SlRef::cast (runner); - if (queue == nullptr) - return false; - - return ((*queue)->RegisterCallback (queue, staticFinished, this) == SL_RESULT_SUCCESS); - } - - - void clear() - { - nextBlock.set (0); - numBlocksOut.set (0); - - zeromem (nativeBuffer.get(), static_cast (owner.bufferSize * numChannels * owner.numBuffers) * sizeof (T)); - scratchBuffer.clear(); - (*queue)->Clear (queue); - } - - void enqueueBuffer() - { - (*queue)->Enqueue (queue, getCurrentBuffer(), static_cast (getBufferSizeInSamples() * sizeof (T))); - ++numBlocksOut; - } - - bool isBufferAvailable() const { return (numBlocksOut.get() < owner.numBuffers); } - T* getNextBuffer() { nextBlock.set((nextBlock.get() + 1) % owner.numBuffers); return getCurrentBuffer(); } - T* getCurrentBuffer() { return nativeBuffer.get() + (static_cast (nextBlock.get()) * getBufferSizeInSamples()); } - size_t getBufferSizeInSamples() const { return static_cast (owner.bufferSize * numChannels); } - - void finished (SLAndroidSimpleBufferQueueItf) - { - attachAndroidJNI(); - - --numBlocksOut; - owner.doSomeWorkOnAudioThread(); - } - - static void staticFinished (SLAndroidSimpleBufferQueueItf caller, void *pContext) - { - reinterpret_cast (pContext)->finished (caller); - } - - // get the "this" pointer for CRTP - Child& crtp() { return * ((Child*) this); } - const Child& crtp() const { return * ((Child*) this); } - - OpenSLSessionT& owner; - - SlRef runner; - SlRef queue; - SlRef config; - GlobalRef javaProxy; - - int numChannels; - - HeapBlock nativeBuffer; - AudioSampleBuffer scratchBuffer, sampleBuffer; - - Atomic nextBlock, numBlocksOut; - }; - - //============================================================================== - template - struct OpenSLQueueRunnerPlayer : OpenSLQueueRunner, SLPlayItf_> - { - typedef OpenSLQueueRunner, SLPlayItf_> Base; - - enum { isPlayer = 1 }; - - OpenSLQueueRunnerPlayer (OpenSLSessionT& sessionToUse, int numChannelsToUse) - : Base (sessionToUse, numChannelsToUse) - {} - - SlRef createPlayerOrRecorder() - { - SLDataLocator_AndroidSimpleBufferQueue queueLocator = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, static_cast (Base::owner.numBuffers)}; - SLDataLocator_OutputMix outputMix = {SL_DATALOCATOR_OUTPUTMIX, Base::owner.outputMix}; - - PCMDataFormatEx dataFormat; - BufferHelpers::initPCMDataFormat (dataFormat, Base::numChannels, Base::owner.sampleRate); - - SLDataSource source = {&queueLocator, &dataFormat}; - SLDataSink sink = {&outputMix, nullptr}; - - SLInterfaceID queueInterfaces[] = { &IntfIID::iid, &IntfIID::iid }; - SLboolean interfaceRequired[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE}; - - SLObjectItf obj = nullptr; - - SLresult status = (*Base::owner.engine)->CreateAudioPlayer (Base::owner.engine, &obj, &source, &sink, 2, queueInterfaces, interfaceRequired); - if (status != SL_RESULT_SUCCESS || obj == nullptr || (*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS) - { - if (obj != nullptr) - (*obj)->Destroy (obj); - - return SlRef(); - } - - return SlRef::cast (SlObjectRef (obj)); - } - - void setState (bool running) { (*Base::runner)->SetPlayState (Base::runner, running ? SL_PLAYSTATE_PLAYING : SL_PLAYSTATE_STOPPED); } - }; - - template - struct OpenSLQueueRunnerRecorder : OpenSLQueueRunner, SLRecordItf_> - { - typedef OpenSLQueueRunner, SLRecordItf_> Base; - - enum { isPlayer = 0 }; - - OpenSLQueueRunnerRecorder (OpenSLSessionT& sessionToUse, int numChannelsToUse) - : Base (sessionToUse, numChannelsToUse) - {} - - SlRef createPlayerOrRecorder() - { - SLDataLocator_IODevice ioDeviceLocator = {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, nullptr}; - SLDataLocator_AndroidSimpleBufferQueue queueLocator = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, static_cast (Base::owner.numBuffers)}; - - PCMDataFormatEx dataFormat; - BufferHelpers::initPCMDataFormat (dataFormat, Base::numChannels, Base::owner.sampleRate); - - SLDataSource source = {&ioDeviceLocator, nullptr}; - SLDataSink sink = {&queueLocator, &dataFormat}; - - SLInterfaceID queueInterfaces[] = { &IntfIID::iid, &IntfIID::iid }; - SLboolean interfaceRequired[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE}; - - SLObjectItf obj = nullptr; - - SLresult status = (*Base::owner.engine)->CreateAudioRecorder (Base::owner.engine, &obj, &source, &sink, 2, queueInterfaces, interfaceRequired); - if (status != SL_RESULT_SUCCESS || obj == nullptr || (*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS) - { - if (obj != nullptr) - (*obj)->Destroy (obj); - - return SlRef(); - } - - SlRef recorder = SlRef::cast (SlObjectRef (obj)); - - return recorder; - } - - bool setAudioPreprocessingEnabled (bool shouldEnable) - { - if (Base::config != nullptr) - { - const bool supportsUnprocessed = (getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT) >= 25); - const SLuint32 recordingPresetValue - = (shouldEnable ? SL_ANDROID_RECORDING_PRESET_GENERIC - : (supportsUnprocessed ? SL_ANDROID_RECORDING_PRESET_UNPROCESSED - : SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION)); - - SLresult status = (*Base::config)->SetConfiguration (Base::config, SL_ANDROID_KEY_RECORDING_PRESET, - &recordingPresetValue, sizeof (recordingPresetValue)); - - return (status == SL_RESULT_SUCCESS); - } - - return false; - } - - void setState (bool running) { (*Base::runner)->SetRecordState (Base::runner, running ? SL_RECORDSTATE_RECORDING : SL_RECORDSTATE_STOPPED); } - }; - - //============================================================================== - class OpenSLSession - { - public: - OpenSLSession (DynamicLibrary& slLibraryToUse, - int numInputChannels, int numOutputChannels, - double samleRateToUse, int bufferSizeToUse, - int numBuffersToUse) - : inputChannels (numInputChannels), outputChannels (numOutputChannels), - sampleRate (samleRateToUse), bufferSize (bufferSizeToUse), numBuffers (numBuffersToUse), - running (false), audioProcessingEnabled (true), callback (nullptr) - { - jassert (numInputChannels > 0 || numOutputChannels > 0); - - if (CreateEngineFunc createEngine = (CreateEngineFunc) slLibraryToUse.getFunction ("slCreateEngine")) - { - SLObjectItf obj = nullptr; - - SLresult err = createEngine (&obj, 0, nullptr, 0, nullptr, nullptr); - if (err != SL_RESULT_SUCCESS || obj == nullptr || (*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS) - { - if (obj != nullptr) - (*obj)->Destroy (obj); - - return; - } - - engine = SlRef::cast (SlObjectRef (obj)); - } - - if (outputChannels > 0) - { - SLObjectItf obj = nullptr; - - SLresult err = (*engine)->CreateOutputMix (engine, &obj, 0, nullptr, nullptr); - if (err != SL_RESULT_SUCCESS || obj == nullptr || (*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS) - { - if (obj != nullptr) - (*obj)->Destroy (obj); - - return; - } - - outputMix = SlRef::cast (SlObjectRef (obj)); - } - } - - virtual ~OpenSLSession() {} - - virtual bool openedOK() const { return (engine != nullptr && (outputChannels == 0 || (outputMix != nullptr))); } - virtual void start() { stop(); jassert (callback.get() != nullptr); running = true; } - virtual void stop() { running = false; } - virtual bool setAudioPreprocessingEnabled (bool shouldEnable) = 0; - virtual bool supportsFloatingPoint() const noexcept = 0; - virtual int getXRunCount() const noexcept = 0; - - void setCallback (AudioIODeviceCallback* callbackToUse) - { - if (! running) - { - callback.set (callbackToUse); - return; - } - - // don't set callback to null! stop the playback instead! - jassert (callbackToUse != nullptr); - - // spin-lock until we can set the callback - while (true) - { - AudioIODeviceCallback* old = callback.get(); - if (old == callbackToUse) - break; - - if (callback.compareAndSetBool (callbackToUse, old)) - break; - - Thread::sleep (1); - } - } - - void process (const float** inputChannelData, float** outputChannelData) - { - if (AudioIODeviceCallback* cb = callback.exchange(nullptr)) - { - cb->audioDeviceIOCallback (inputChannelData, inputChannels, outputChannelData, outputChannels, bufferSize); - callback.set (cb); - } - else - { - for (int i = 0; i < outputChannels; ++i) - zeromem (outputChannelData[i], sizeof(float) * static_cast (bufferSize)); - } - } - - static OpenSLSession* create (DynamicLibrary& slLibrary, - int numInputChannels, int numOutputChannels, - double samleRateToUse, int bufferSizeToUse, - int numBuffersToUse); - - //============================================================================== - typedef SLresult (*CreateEngineFunc)(SLObjectItf*,SLuint32,const SLEngineOption*,SLuint32,const SLInterfaceID*,const SLboolean*); - - //============================================================================== - int inputChannels, outputChannels; - double sampleRate; - int bufferSize, numBuffers; - - bool running, audioProcessingEnabled; - - SlRef engine; - SlRef outputMix; - - Atomic callback; - }; - - template - class OpenSLSessionT : public OpenSLSession - { - public: - OpenSLSessionT (DynamicLibrary& slLibraryToUse, - int numInputChannels, int numOutputChannels, - double samleRateToUse, int bufferSizeToUse, - int numBuffersToUse) - : OpenSLSession (slLibraryToUse, numInputChannels, numOutputChannels, samleRateToUse, bufferSizeToUse, numBuffersToUse) - { - jassert (numInputChannels > 0 || numOutputChannels > 0); - - if (OpenSLSession::openedOK()) - { - if (inputChannels > 0) - { - recorder = new OpenSLQueueRunnerRecorder(*this, inputChannels); - - if (! recorder->init()) - { - recorder = nullptr; - return; - } - } - - if (outputChannels > 0) - { - player = new OpenSLQueueRunnerPlayer(*this, outputChannels); - - if (! player->init()) - { - player = nullptr; - return; - } - - const bool supportsUnderrunCount = (getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT) >= 24); - getUnderrunCount = supportsUnderrunCount ? getEnv()->GetMethodID (AudioTrack, "getUnderrunCount", "()I") : 0; - } - } - } - - bool openedOK() const override - { - return (OpenSLSession::openedOK() && (inputChannels == 0 || recorder != nullptr) - && (outputChannels == 0 || player != nullptr)); - } - - void start() override - { - OpenSLSession::start(); - - guard.set (0); - - if (inputChannels > 0) - recorder->clear(); - - if (outputChannels > 0) - player->clear(); - - // first enqueue all buffers - for (int i = 0; i < numBuffers; ++i) - doSomeWorkOnAudioThread(); - - if (inputChannels > 0) - recorder->setState (true); - - if (outputChannels > 0) - player->setState (true); - } - - void stop() override - { - OpenSLSession::stop(); - - while (! guard.compareAndSetBool (1, 0)) - Thread::sleep (1); - - if (inputChannels > 0) - recorder->setState (false); - - if (outputChannels > 0) - player->setState (false); - - guard.set (0); - } - - bool setAudioPreprocessingEnabled (bool shouldEnable) override - { - if (shouldEnable != audioProcessingEnabled) - { - audioProcessingEnabled = shouldEnable; - - if (recorder != nullptr) - return recorder->setAudioPreprocessingEnabled (audioProcessingEnabled); - } - - return true; - } - - int getXRunCount() const noexcept override - { - if (player != nullptr && player->javaProxy != nullptr && getUnderrunCount != 0) - return getEnv()->CallIntMethod (player->javaProxy, getUnderrunCount); - - return -1; - } - - bool supportsFloatingPoint() const noexcept override { return (BufferHelpers::isFloatingPoint != 0); } - - void doSomeWorkOnAudioThread() - { - // only the player or the recorder should enter this section at any time - if (guard.compareAndSetBool (1, 0)) - { - // are there enough buffers avaialable to process some audio - if ((inputChannels == 0 || recorder->isBufferAvailable()) && (outputChannels == 0 || player->isBufferAvailable())) - { - T* recorderBuffer = (inputChannels > 0 ? recorder->getNextBuffer() : nullptr); - T* playerBuffer = (outputChannels > 0 ? player->getNextBuffer() : nullptr); - - const float** inputChannelData = nullptr; - float** outputChannelData = nullptr; - - if (recorderBuffer != nullptr) - { - BufferHelpers::prepareCallbackBuffer (recorder->sampleBuffer, recorderBuffer); - BufferHelpers::convertFromOpenSL (recorderBuffer, recorder->sampleBuffer); - - inputChannelData = recorder->sampleBuffer.getArrayOfReadPointers(); - } - - if (playerBuffer != nullptr) - { - BufferHelpers::prepareCallbackBuffer (player->sampleBuffer, playerBuffer); - outputChannelData = player->sampleBuffer.getArrayOfWritePointers(); - } - - process (inputChannelData, outputChannelData); - - if (recorderBuffer != nullptr) - recorder->enqueueBuffer(); - - if (playerBuffer != nullptr) - { - BufferHelpers::convertToOpenSL (player->sampleBuffer, playerBuffer); - player->enqueueBuffer(); - } - } - - guard.set (0); - } - } - - //============================================================================== - ScopedPointer > player; - ScopedPointer > recorder; - Atomic guard; - jmethodID getUnderrunCount = 0; - }; - - //============================================================================== - OpenSLAudioIODevice (const String& deviceName) - : AudioIODevice (deviceName, openSLTypeName), - actualBufferSize (0), sampleRate (0), - audioProcessingEnabled (true), - callback (nullptr) - { - // OpenSL has piss-poor support for determining latency, so the only way I can find to - // get a number for this is by asking the AudioTrack/AudioRecord classes.. - AndroidAudioIODevice javaDevice (deviceName); - - // this is a total guess about how to calculate the latency, but seems to vaguely agree - // with the devices I've tested.. YMMV - inputLatency = (javaDevice.minBufferSizeIn * 2) / 3; - outputLatency = (javaDevice.minBufferSizeOut * 2) / 3; - - const int64 longestLatency = jmax (inputLatency, outputLatency); - const int64 totalLatency = inputLatency + outputLatency; - inputLatency = (int) ((longestLatency * inputLatency) / totalLatency) & ~15; - outputLatency = (int) ((longestLatency * outputLatency) / totalLatency) & ~15; - - bool success = slLibrary.open ("libOpenSLES.so"); - - // You can only create this class if you are sure that your hardware supports OpenSL - jassert (success); - ignoreUnused (success); - } - - ~OpenSLAudioIODevice() - { - close(); - } - - bool openedOk() const { return session != nullptr; } - - StringArray getOutputChannelNames() override - { - StringArray s; - s.add ("Left"); - s.add ("Right"); - return s; - } - - StringArray getInputChannelNames() override - { - StringArray s; - s.add ("Audio Input"); - return s; - } - - Array getAvailableSampleRates() override - { - //see https://developer.android.com/ndk/guides/audio/opensl-for-android.html - - static const double rates[] = { 8000.0, 11025.0, 12000.0, 16000.0, - 22050.0, 24000.0, 32000.0, 44100.0, 48000.0 }; - Array retval (rates, numElementsInArray (rates)); - - // make sure the native sample rate is pafrt of the list - double native = getNativeSampleRate(); - if (native != 0.0 && ! retval.contains (native)) - retval.add (native); - - return retval; - } - - Array getAvailableBufferSizes() override - { - // we need to offer the lowest possible buffer size which - // is the native buffer size - const int defaultNumMultiples = 8; - const int nativeBufferSize = getNativeBufferSize(); - - Array retval; - for (int i = 1; i < defaultNumMultiples; ++i) - retval.add (i * nativeBufferSize); - - return retval; - } - - String open (const BigInteger& inputChannels, - const BigInteger& outputChannels, - double requestedSampleRate, - int bufferSize) override - { - close(); - - lastError.clear(); - sampleRate = (int) requestedSampleRate; - - int preferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize; - - activeOutputChans = outputChannels; - activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false); - int numOutputChannels = activeOutputChans.countNumberOfSetBits(); - - activeInputChans = inputChannels; - activeInputChans.setRange (1, activeInputChans.getHighestBit(), false); - int numInputChannels = activeInputChans.countNumberOfSetBits(); - - actualBufferSize = preferredBufferSize; - - const int audioBuffersToEnqueue = hasLowLatencyAudioPath() ? buffersToEnqueueForLowLatency - : buffersToEnqueueSlowAudio; - - if (numInputChannels > 0 && (! RuntimePermissions::isGranted (RuntimePermissions::recordAudio))) - { - // If you hit this assert, you probably forgot to get RuntimePermissions::recordAudio - // before trying to open an audio input device. This is not going to work! - jassertfalse; - lastError = "Error opening OpenSL input device: the app was not granted android.permission.RECORD_AUDIO"; - } - - session = OpenSLSession::create (slLibrary, numInputChannels, numOutputChannels, - sampleRate, actualBufferSize, audioBuffersToEnqueue); - if (session != nullptr) - session->setAudioPreprocessingEnabled (audioProcessingEnabled); - else - { - if (numInputChannels > 0 && numOutputChannels > 0 && RuntimePermissions::isGranted (RuntimePermissions::recordAudio)) - { - // New versions of the Android emulator do not seem to support audio input anymore on OS X - activeInputChans = BigInteger(0); - numInputChannels = 0; - - session = OpenSLSession::create(slLibrary, numInputChannels, numOutputChannels, - sampleRate, actualBufferSize, audioBuffersToEnqueue); - } - } - - DBG ("OpenSL: numInputChannels = " << numInputChannels - << ", numOutputChannels = " << numOutputChannels - << ", nativeBufferSize = " << getNativeBufferSize() - << ", nativeSampleRate = " << getNativeSampleRate() - << ", actualBufferSize = " << actualBufferSize - << ", audioBuffersToEnqueue = " << audioBuffersToEnqueue - << ", sampleRate = " << sampleRate - << ", supportsFloatingPoint = " << (session != nullptr && session->supportsFloatingPoint() ? "true" : "false")); - - if (session == nullptr) - lastError = "Unknown error initializing opensl session"; - - deviceOpen = (session != nullptr); - return lastError; - } - - void close() override - { - stop(); - session = nullptr; - callback = nullptr; - } - - int getOutputLatencyInSamples() override { return outputLatency; } - int getInputLatencyInSamples() override { return inputLatency; } - bool isOpen() override { return deviceOpen; } - int getCurrentBufferSizeSamples() override { return actualBufferSize; } - int getCurrentBitDepth() override { return (session != nullptr && session->supportsFloatingPoint() ? 32 : 16); } - BigInteger getActiveOutputChannels() const override { return activeOutputChans; } - BigInteger getActiveInputChannels() const override { return activeInputChans; } - String getLastError() override { return lastError; } - bool isPlaying() override { return callback != nullptr; } - int getXRunCount() const noexcept override { return (session != nullptr ? session->getXRunCount() : -1); } - - int getDefaultBufferSize() override - { - // Only on a Pro-Audio device will we set the lowest possible buffer size - // by default. We need to be more conservative on other devices - // as they may be low-latency, but still have a crappy CPU. - return (isProAudioDevice() ? 1 : 6) - * defaultBufferSizeIsMultipleOfNative * getNativeBufferSize(); - } - - double getCurrentSampleRate() override - { - return (sampleRate == 0.0 ? getNativeSampleRate() : sampleRate); - } - - void start (AudioIODeviceCallback* newCallback) override - { - if (session != nullptr && callback != newCallback) - { - AudioIODeviceCallback* oldCallback = callback; - - if (newCallback != nullptr) - newCallback->audioDeviceAboutToStart (this); - - if (oldCallback != nullptr) - { - // already running - if (newCallback == nullptr) - stop(); - else - session->setCallback (newCallback); - - oldCallback->audioDeviceStopped(); - } - else - { - jassert (newCallback != nullptr); - - // session hasn't started yet - session->setCallback (newCallback); - session->start(); - } - - callback = newCallback; - } - } - - void stop() override - { - if (session != nullptr && callback != nullptr) - { - callback = nullptr; - session->stop(); - session->setCallback (nullptr); - } - } - - bool setAudioPreprocessingEnabled (bool shouldAudioProcessingBeEnabled) override - { - audioProcessingEnabled = shouldAudioProcessingBeEnabled; - - if (session != nullptr) - session->setAudioPreprocessingEnabled (audioProcessingEnabled); - - return true; - } - - static const char* const openSLTypeName; - -private: - //============================================================================== - friend class SLRealtimeThread; - - //============================================================================== - DynamicLibrary slLibrary; - int actualBufferSize, sampleRate; - int inputLatency, outputLatency; - bool deviceOpen, audioProcessingEnabled; - String lastError; - BigInteger activeOutputChans, activeInputChans; - AudioIODeviceCallback* callback; - - ScopedPointer session; - - enum - { - // The number of buffers to enqueue needs to be at least two for the audio to use the low-latency - // audio path (see "Performance" section in ndk/docs/Additional_library_docs/opensles/index.html) - buffersToEnqueueForLowLatency = 4, - buffersToEnqueueSlowAudio = 8, - defaultBufferSizeIsMultipleOfNative = 1 - }; - - //============================================================================== - static String audioManagerGetProperty (const String& property) - { - const LocalRef jProperty (javaString (property)); - const LocalRef text ((jstring) android.activity.callObjectMethod (JuceAppActivity.audioManagerGetProperty, - jProperty.get())); - if (text.get() != 0) - return juceString (text); - - return {}; - } - - static bool androidHasSystemFeature (const String& property) - { - const LocalRef jProperty (javaString (property)); - return android.activity.callBooleanMethod (JuceAppActivity.hasSystemFeature, jProperty.get()); - } - - static double getNativeSampleRate() - { - return audioManagerGetProperty ("android.media.property.OUTPUT_SAMPLE_RATE").getDoubleValue(); - } - - static int getNativeBufferSize() - { - const int val = audioManagerGetProperty ("android.media.property.OUTPUT_FRAMES_PER_BUFFER").getIntValue(); - return val > 0 ? val : 512; - } - - static bool isProAudioDevice() - { - return androidHasSystemFeature ("android.hardware.audio.pro"); - } - - static bool hasLowLatencyAudioPath() - { - return androidHasSystemFeature ("android.hardware.audio.low_latency"); - } - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenSLAudioIODevice) -}; - -OpenSLAudioIODevice::OpenSLSession* OpenSLAudioIODevice::OpenSLSession::create (DynamicLibrary& slLibrary, - int numInputChannels, int numOutputChannels, - double samleRateToUse, int bufferSizeToUse, - int numBuffersToUse) -{ - ScopedPointer retval; - auto sdkVersion = getEnv()->GetStaticIntField (AndroidBuildVersion, AndroidBuildVersion.SDK_INT); - - // SDK versions 21 and higher should natively support floating point... - if (sdkVersion >= 21) - { - retval = new OpenSLSessionT (slLibrary, numInputChannels, numOutputChannels, samleRateToUse, - bufferSizeToUse, numBuffersToUse); - - // ...however, some devices lie so re-try without floating point - if (retval != nullptr && (! retval->openedOK())) - retval = nullptr; - } - - if (retval == nullptr) - { - retval = new OpenSLSessionT (slLibrary, numInputChannels, numOutputChannels, samleRateToUse, - bufferSizeToUse, numBuffersToUse); - - if (retval != nullptr && (! retval->openedOK())) - retval = nullptr; - } - - return retval.release(); -} - -//============================================================================== -class OpenSLAudioDeviceType : public AudioIODeviceType -{ -public: - OpenSLAudioDeviceType() : AudioIODeviceType (OpenSLAudioIODevice::openSLTypeName) {} - - //============================================================================== - void scanForDevices() override {} - - StringArray getDeviceNames (bool) const override { return StringArray (OpenSLAudioIODevice::openSLTypeName); } - 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& outputDeviceName, - const String& inputDeviceName) override - { - ScopedPointer dev; - - if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty()) - dev = new OpenSLAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName - : inputDeviceName); - - return dev.release(); - } - - static bool isOpenSLAvailable() - { - DynamicLibrary library; - return library.open ("libOpenSLES.so"); - } - -private: - - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OpenSLAudioDeviceType) -}; - -const char* const OpenSLAudioIODevice::openSLTypeName = "Android OpenSL"; - - -//============================================================================== -bool isOpenSLAvailable() { return OpenSLAudioDeviceType::isOpenSLAvailable(); } - -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES() -{ - return isOpenSLAvailable() ? new OpenSLAudioDeviceType() : nullptr; -} - -//============================================================================== -class SLRealtimeThread -{ -public: - static constexpr int numBuffers = 4; - - SLRealtimeThread() - { - if (auto createEngine = (OpenSLAudioIODevice::OpenSLSession::CreateEngineFunc) slLibrary.getFunction ("slCreateEngine")) - { - SLObjectItf obj = nullptr; - auto err = createEngine (&obj, 0, nullptr, 0, nullptr, nullptr); - - if (err != SL_RESULT_SUCCESS || obj == nullptr) - return; - - if ((*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS) - { - (*obj)->Destroy (obj); - return; - } - - engine = SlRef::cast (SlObjectRef (obj)); - - if (engine == nullptr) - { - (*obj)->Destroy (obj); - return; - } - - obj = nullptr; - err = (*engine)->CreateOutputMix (engine, &obj, 0, nullptr, nullptr); - - if (err != SL_RESULT_SUCCESS || obj == nullptr || (*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS) - { - (*obj)->Destroy (obj); - return; - } - - outputMix = SlRef::cast (SlObjectRef (obj)); - - if (outputMix == nullptr) - { - (*obj)->Destroy (obj); - return; - } - - SLDataLocator_AndroidSimpleBufferQueue queueLocator = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, static_cast (numBuffers)}; - SLDataLocator_OutputMix outputMixLocator = {SL_DATALOCATOR_OUTPUTMIX, outputMix}; - - PCMDataFormatEx dataFormat; - BufferHelpers::initPCMDataFormat (dataFormat, 1, OpenSLAudioIODevice::getNativeSampleRate()); - - SLDataSource source = { &queueLocator, &dataFormat }; - SLDataSink sink = { &outputMixLocator, nullptr }; - - SLInterfaceID queueInterfaces[] = { &IntfIID::iid }; - SLboolean trueFlag = SL_BOOLEAN_TRUE; - - obj = nullptr; - err = (*engine)->CreateAudioPlayer (engine, &obj, &source, &sink, 1, queueInterfaces, &trueFlag); - - if (err != SL_RESULT_SUCCESS || obj == nullptr) - return; - - if ((*obj)->Realize (obj, 0) != SL_RESULT_SUCCESS) - { - (*obj)->Destroy (obj); - return; - } - - player = SlRef::cast (SlObjectRef (obj)); - - if (player == nullptr) - { - (*obj)->Destroy (obj); - return; - } - - queue = SlRef::cast (player); - if (queue == nullptr) - return; - - if ((*queue)->RegisterCallback (queue, staticFinished, this) != SL_RESULT_SUCCESS) - { - queue = nullptr; - return; - } - - pthread_cond_init (&threadReady, nullptr); - pthread_mutex_init (&threadReadyMutex, nullptr); - } - } - - bool isOK() const { return queue != nullptr; } - - pthread_t startThread (void* (*entry) (void*), void* userPtr) - { - memset (buffer.get(), 0, static_cast (sizeof (int16) * static_cast (bufferSize * numBuffers))); - - for (int i = 0; i < numBuffers; ++i) - { - int16* dst = buffer.get() + (bufferSize * i); - (*queue)->Enqueue (queue, dst, static_cast (static_cast (bufferSize) * sizeof (int16))); - } - - pthread_mutex_lock (&threadReadyMutex); - - threadEntryProc = entry; - threadUserPtr = userPtr; - - (*player)->SetPlayState (player, SL_PLAYSTATE_PLAYING); - - pthread_cond_wait (&threadReady, &threadReadyMutex); - pthread_mutex_unlock (&threadReadyMutex); - - return threadID; - } - - void finished() - { - if (threadEntryProc != nullptr) - { - pthread_mutex_lock (&threadReadyMutex); - - threadID = pthread_self(); - - pthread_cond_signal (&threadReady); - pthread_mutex_unlock (&threadReadyMutex); - - threadEntryProc (threadUserPtr); - threadEntryProc = nullptr; - - (*player)->SetPlayState (player, SL_PLAYSTATE_STOPPED); - MessageManager::callAsync ([this] () { delete this; }); - } - } - -private: - //============================================================================= - static void staticFinished (SLAndroidSimpleBufferQueueItf, void* context) - { - static_cast (context)->finished(); - } - - //============================================================================= - DynamicLibrary slLibrary { "libOpenSLES.so" }; - - SlRef engine; - SlRef outputMix; - SlRef player; - SlRef queue; - - int bufferSize = OpenSLAudioIODevice::getNativeBufferSize(); - HeapBlock buffer { HeapBlock (static_cast (1 * bufferSize * numBuffers)) }; - - void* (*threadEntryProc) (void*) = nullptr; - void* threadUserPtr = nullptr; - - pthread_cond_t threadReady; - pthread_mutex_t threadReadyMutex; - pthread_t threadID; -}; - -pthread_t juce_createRealtimeAudioThread (void* (*entry) (void*), void* userPtr) -{ - ScopedPointer thread (new SLRealtimeThread); - - if (! thread->isOK()) - return 0; - - pthread_t threadID = thread->startThread (entry, userPtr); - - // the thread will de-allocate itself - thread.release(); - - return threadID; -} - -} // namespace juce diff --git a/source/modules/juce_audio_devices/native/juce_ios_Audio.cpp b/source/modules/juce_audio_devices/native/juce_ios_Audio.cpp deleted file mode 100644 index 432bb228b..000000000 --- a/source/modules/juce_audio_devices/native/juce_ios_Audio.cpp +++ /dev/null @@ -1,1206 +0,0 @@ -/* - ============================================================================== - - 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 iOSAudioIODevice; - -static const char* const iOSAudioDeviceName = "iOS Audio"; - -//============================================================================== -struct AudioSessionHolder : public AsyncUpdater -{ - AudioSessionHolder(); - ~AudioSessionHolder(); - - void handleAsyncUpdate() override; - - void handleStatusChange (bool enabled, const char* reason) const; - void handleRouteChange (const char* reason); - - CriticalSection routeChangeLock; - String lastRouteChangeReason; - Array activeDevices; - - id nativeSession; -}; - -static const char* getRoutingChangeReason (AVAudioSessionRouteChangeReason reason) noexcept -{ - switch (reason) - { - case AVAudioSessionRouteChangeReasonNewDeviceAvailable: return "New device available"; - case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: return "Old device unavailable"; - case AVAudioSessionRouteChangeReasonCategoryChange: return "Category change"; - case AVAudioSessionRouteChangeReasonOverride: return "Override"; - case AVAudioSessionRouteChangeReasonWakeFromSleep: return "Wake from sleep"; - case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory: return "No suitable route for category"; - case AVAudioSessionRouteChangeReasonRouteConfigurationChange: return "Route configuration change"; - case AVAudioSessionRouteChangeReasonUnknown: - default: return "Unknown"; - } -} - -bool getNotificationValueForKey (NSNotification* notification, NSString* key, NSUInteger& value) noexcept -{ - if (notification != nil) - { - if (NSDictionary* userInfo = [notification userInfo]) - { - if (NSNumber* number = [userInfo objectForKey: key]) - { - value = [number unsignedIntegerValue]; - return true; - } - } - } - - jassertfalse; - return false; -} - -} // juce namespace - -//============================================================================== -@interface iOSAudioSessionNative : NSObject -{ -@private - juce::AudioSessionHolder* audioSessionHolder; -}; - -- (id) init: (juce::AudioSessionHolder*) holder; -- (void) dealloc; - -- (void) audioSessionChangedInterruptionType: (NSNotification*) notification; -- (void) handleMediaServicesReset; -- (void) handleMediaServicesLost; -- (void) handleRouteChange: (NSNotification*) notification; -@end - -@implementation iOSAudioSessionNative - -- (id) init: (juce::AudioSessionHolder*) holder -{ - self = [super init]; - - if (self != nil) - { - audioSessionHolder = holder; - - auto session = [AVAudioSession sharedInstance]; - auto centre = [NSNotificationCenter defaultCenter]; - - [centre addObserver: self - selector: @selector (audioSessionChangedInterruptionType:) - name: AVAudioSessionInterruptionNotification - object: session]; - - [centre addObserver: self - selector: @selector (handleMediaServicesLost) - name: AVAudioSessionMediaServicesWereLostNotification - object: session]; - - [centre addObserver: self - selector: @selector (handleMediaServicesReset) - name: AVAudioSessionMediaServicesWereResetNotification - object: session]; - - [centre addObserver: self - selector: @selector (handleRouteChange:) - name: AVAudioSessionRouteChangeNotification - object: session]; - } - else - { - jassertfalse; - } - - return self; -} - -- (void) dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver: self]; - [super dealloc]; -} - -- (void) audioSessionChangedInterruptionType: (NSNotification*) notification -{ - NSUInteger value; - - if (juce::getNotificationValueForKey (notification, AVAudioSessionInterruptionTypeKey, value)) - { - switch ((AVAudioSessionInterruptionType) value) - { - case AVAudioSessionInterruptionTypeBegan: - audioSessionHolder->handleStatusChange (false, "AVAudioSessionInterruptionTypeBegan"); - break; - - case AVAudioSessionInterruptionTypeEnded: - audioSessionHolder->handleStatusChange (true, "AVAudioSessionInterruptionTypeEnded"); - break; - - // No default so the code doesn't compile if this enum is extended. - } - } -} - -- (void) handleMediaServicesReset -{ - audioSessionHolder->handleStatusChange (true, "AVAudioSessionMediaServicesWereResetNotification"); -} - -- (void) handleMediaServicesLost -{ - audioSessionHolder->handleStatusChange (false, "AVAudioSessionMediaServicesWereLostNotification"); -} - -- (void) handleRouteChange: (NSNotification*) notification -{ - NSUInteger value; - - if (juce::getNotificationValueForKey (notification, AVAudioSessionRouteChangeReasonKey, value)) - audioSessionHolder->handleRouteChange (juce::getRoutingChangeReason ((AVAudioSessionRouteChangeReason) value)); -} - -@end - -//============================================================================== -#if JUCE_MODULE_AVAILABLE_juce_graphics - #include -#endif - -namespace juce { - -#ifndef JUCE_IOS_AUDIO_LOGGING - #define JUCE_IOS_AUDIO_LOGGING 0 -#endif - -#if JUCE_IOS_AUDIO_LOGGING - #define JUCE_IOS_AUDIO_LOG(x) DBG(x) -#else - #define JUCE_IOS_AUDIO_LOG(x) -#endif - -static void logNSError (NSError* e) -{ - if (e != nil) - { - JUCE_IOS_AUDIO_LOG ("iOS Audio error: " << [e.localizedDescription UTF8String]); - jassertfalse; - } -} - -#define JUCE_NSERROR_CHECK(X) { NSError* error = nil; X; logNSError (error); } - -//============================================================================== -struct iOSAudioIODevice::Pimpl : public AudioPlayHead, - public AsyncUpdater -{ - Pimpl (iOSAudioIODevice& ioDevice) - : owner (ioDevice) - { - sessionHolder->activeDevices.add (&owner); - - updateSampleRateAndAudioInput(); - } - - ~Pimpl() - { - sessionHolder->activeDevices.removeFirstMatchingValue (&owner); - - close(); - } - - static void setAudioSessionActive (bool enabled) - { - JUCE_NSERROR_CHECK ([[AVAudioSession sharedInstance] setActive: enabled - error: &error]); - } - - static double trySampleRate (double rate) - { - auto session = [AVAudioSession sharedInstance]; - JUCE_NSERROR_CHECK ([session setPreferredSampleRate: rate - error: &error]); - return session.sampleRate; - } - - Array getAvailableSampleRates() - { - const ScopedLock sl (callbackLock); - - Array rates; - - // Important: the supported audio sample rates change on the iPhone 6S - // depending on whether the headphones are plugged in or not! - setAudioSessionActive (true); - - AudioUnitRemovePropertyListenerWithUserData (audioUnit, - kAudioUnitProperty_StreamFormat, - dispatchAudioUnitPropertyChange, - this); - - const double lowestRate = trySampleRate (4000); - const double highestRate = trySampleRate (192000); - - for (double rate = lowestRate; rate <= highestRate; rate += 1000) - { - const double supportedRate = trySampleRate (rate); - - if (rates.addIfNotAlreadyThere (supportedRate)) - JUCE_IOS_AUDIO_LOG ("available rate = " + String (supportedRate, 0) + "Hz"); - - rate = jmax (rate, supportedRate); - } - - trySampleRate (sampleRate); - updateCurrentBufferSize(); - - AudioUnitAddPropertyListener (audioUnit, - kAudioUnitProperty_StreamFormat, - dispatchAudioUnitPropertyChange, - this); - - return rates; - } - - Array getAvailableBufferSizes() - { - Array r; - - for (int i = 6; i < 13; ++i) - r.add (1 << i); - - return r; - } - - void updateSampleRateAndAudioInput() - { - auto session = [AVAudioSession sharedInstance]; - sampleRate = session.sampleRate; - audioInputIsAvailable = session.isInputAvailable; - actualBufferSize = roundToInt (sampleRate * session.IOBufferDuration); - - JUCE_IOS_AUDIO_LOG ("AVAudioSession: sampleRate: " << sampleRate - << " Hz, audioInputAvailable: " << (int) audioInputIsAvailable - << ", buffer size: " << actualBufferSize); - } - - String open (const BigInteger& inputChannelsWanted, - const BigInteger& outputChannelsWanted, - double targetSampleRate, int bufferSize) - { - close(); - - firstHostTime = true; - lastNumFrames = 0; - xrun = 0; - lastError.clear(); - preferredBufferSize = bufferSize <= 0 ? defaultBufferSize : bufferSize; - - // xxx set up channel mapping - - activeOutputChans = outputChannelsWanted; - activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false); - numOutputChannels = activeOutputChans.countNumberOfSetBits(); - monoOutputChannelNumber = activeOutputChans.findNextSetBit (0); - - activeInputChans = inputChannelsWanted; - activeInputChans.setRange (2, activeInputChans.getHighestBit(), false); - numInputChannels = activeInputChans.countNumberOfSetBits(); - monoInputChannelNumber = activeInputChans.findNextSetBit (0); - - setAudioSessionActive (true); - - // Set the session category & options: - auto session = [AVAudioSession sharedInstance]; - - const bool useInputs = (numInputChannels > 0 && audioInputIsAvailable); - - NSString* category = (useInputs ? AVAudioSessionCategoryPlayAndRecord : AVAudioSessionCategoryPlayback); - - NSUInteger options = AVAudioSessionCategoryOptionMixWithOthers; // Alternatively AVAudioSessionCategoryOptionDuckOthers - if (useInputs) // These options are only valid for category = PlayAndRecord - options |= (AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionAllowBluetooth); - - JUCE_NSERROR_CHECK ([session setCategory: category - withOptions: options - error: &error]); - - fixAudioRouteIfSetToReceiver(); - - // Set the sample rate - trySampleRate (targetSampleRate); - updateSampleRateAndAudioInput(); - updateCurrentBufferSize(); - - prepareFloatBuffers (actualBufferSize); - - isRunning = true; - handleRouteChange ("Started AudioUnit"); - - lastError = (audioUnit != 0 ? "" : "Couldn't open the device"); - - setAudioSessionActive (true); - - return lastError; - } - - void close() - { - if (isRunning) - { - isRunning = false; - - if (audioUnit != 0) - { - AudioOutputUnitStart (audioUnit); - AudioComponentInstanceDispose (audioUnit); - audioUnit = 0; - } - - setAudioSessionActive (false); - } - } - - void start (AudioIODeviceCallback* newCallback) - { - if (isRunning && callback != newCallback) - { - if (newCallback != nullptr) - newCallback->audioDeviceAboutToStart (&owner); - - const ScopedLock sl (callbackLock); - callback = newCallback; - } - } - - void stop() - { - if (isRunning) - { - AudioIODeviceCallback* lastCallback; - - { - const ScopedLock sl (callbackLock); - lastCallback = callback; - callback = nullptr; - } - - if (lastCallback != nullptr) - lastCallback->audioDeviceStopped(); - } - } - - bool setAudioPreprocessingEnabled (bool enable) - { - auto session = [AVAudioSession sharedInstance]; - - NSString* mode = (enable ? AVAudioSessionModeMeasurement - : AVAudioSessionModeDefault); - - JUCE_NSERROR_CHECK ([session setMode: mode - error: &error]); - - return session.mode == mode; - } - - //============================================================================== - bool canControlTransport() override { return interAppAudioConnected; } - - void transportPlay (bool shouldSartPlaying) override - { - if (! canControlTransport()) - return; - - HostCallbackInfo callbackInfo; - fillHostCallbackInfo (callbackInfo); - - Boolean hostIsPlaying = NO; - OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData, - &hostIsPlaying, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr); - - ignoreUnused (err); - jassert (err == noErr); - - if (hostIsPlaying != shouldSartPlaying) - handleAudioTransportEvent (kAudioUnitRemoteControlEvent_TogglePlayPause); - } - - void transportRecord (bool shouldStartRecording) override - { - if (! canControlTransport()) - return; - - HostCallbackInfo callbackInfo; - fillHostCallbackInfo (callbackInfo); - - Boolean hostIsRecording = NO; - OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData, - nullptr, - &hostIsRecording, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr); - ignoreUnused (err); - jassert (err == noErr); - - if (hostIsRecording != shouldStartRecording) - handleAudioTransportEvent (kAudioUnitRemoteControlEvent_ToggleRecord); - } - - void transportRewind() override - { - if (canControlTransport()) - handleAudioTransportEvent (kAudioUnitRemoteControlEvent_Rewind); - } - - bool getCurrentPosition (CurrentPositionInfo& result) override - { - if (! canControlTransport()) - return false; - - zerostruct (result); - - HostCallbackInfo callbackInfo; - fillHostCallbackInfo (callbackInfo); - - if (callbackInfo.hostUserData == nullptr) - return false; - - Boolean hostIsPlaying = NO; - Boolean hostIsRecording = NO; - Float64 hostCurrentSampleInTimeLine = 0; - Boolean hostIsCycling = NO; - Float64 hostCycleStartBeat = 0; - Float64 hostCycleEndBeat = 0; - OSStatus err = callbackInfo.transportStateProc2 (callbackInfo.hostUserData, - &hostIsPlaying, - &hostIsRecording, - nullptr, - &hostCurrentSampleInTimeLine, - &hostIsCycling, - &hostCycleStartBeat, - &hostCycleEndBeat); - if (err == kAUGraphErr_CannotDoInCurrentContext) - return false; - - jassert (err == noErr); - - result.timeInSamples = (int64) hostCurrentSampleInTimeLine; - result.isPlaying = hostIsPlaying; - result.isRecording = hostIsRecording; - result.isLooping = hostIsCycling; - result.ppqLoopStart = hostCycleStartBeat; - result.ppqLoopEnd = hostCycleEndBeat; - - result.timeInSeconds = result.timeInSamples / sampleRate; - - Float64 hostBeat = 0; - Float64 hostTempo = 0; - err = callbackInfo.beatAndTempoProc (callbackInfo.hostUserData, - &hostBeat, - &hostTempo); - jassert (err == noErr); - - result.ppqPosition = hostBeat; - result.bpm = hostTempo; - - Float32 hostTimeSigNumerator = 0; - UInt32 hostTimeSigDenominator = 0; - Float64 hostCurrentMeasureDownBeat = 0; - err = callbackInfo.musicalTimeLocationProc (callbackInfo.hostUserData, - nullptr, - &hostTimeSigNumerator, - &hostTimeSigDenominator, - &hostCurrentMeasureDownBeat); - jassert (err == noErr); - - result.ppqPositionOfLastBarStart = hostCurrentMeasureDownBeat; - result.timeSigNumerator = (int) hostTimeSigNumerator; - result.timeSigDenominator = (int) hostTimeSigDenominator; - - result.frameRate = AudioPlayHead::fpsUnknown; - - return true; - } - - //============================================================================== - #if JUCE_MODULE_AVAILABLE_juce_graphics - Image getIcon (int size) - { - if (interAppAudioConnected) - { - UIImage* hostUIImage = AudioOutputUnitGetHostIcon (audioUnit, size); - if (hostUIImage != nullptr) - return juce_createImageFromUIImage (hostUIImage); - } - return Image(); - } - #endif - - void switchApplication() - { - if (! interAppAudioConnected) - return; - - CFURLRef hostUrl; - UInt32 dataSize = sizeof (hostUrl); - OSStatus err = AudioUnitGetProperty(audioUnit, - kAudioUnitProperty_PeerURL, - kAudioUnitScope_Global, - 0, - &hostUrl, - &dataSize); - if (err == noErr) - { - #if (! defined __IPHONE_OS_VERSION_MIN_REQUIRED) || (! defined __IPHONE_10_0) || (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_10_0) - [[UIApplication sharedApplication] openURL: (NSURL*)hostUrl]; - #else - [[UIApplication sharedApplication] openURL: (NSURL*)hostUrl options: @{} completionHandler: nil]; - #endif - } - } - - //============================================================================== - void invokeAudioDeviceErrorCallback (const String& reason) - { - const ScopedLock sl (callbackLock); - - if (callback != nullptr) - callback->audioDeviceError (reason); - } - - void handleStatusChange (bool enabled, const char* reason) - { - const ScopedLock myScopedLock (callbackLock); - - JUCE_IOS_AUDIO_LOG ("handleStatusChange: enabled: " << (int) enabled << ", reason: " << reason); - - isRunning = enabled; - setAudioSessionActive (enabled); - - if (enabled) - AudioOutputUnitStart (audioUnit); - else - AudioOutputUnitStop (audioUnit); - - if (! enabled) - invokeAudioDeviceErrorCallback (reason); - } - - void handleRouteChange (const char* reason) - { - const ScopedLock myScopedLock (callbackLock); - - JUCE_IOS_AUDIO_LOG ("handleRouteChange: reason: " << reason); - - fixAudioRouteIfSetToReceiver(); - - if (isRunning) - invokeAudioDeviceErrorCallback (reason); - - restart(); - } - - void handleAudioUnitPropertyChange (AudioUnit, - AudioUnitPropertyID propertyID, - AudioUnitScope scope, - AudioUnitElement element) - { - JUCE_IOS_AUDIO_LOG ("handleAudioUnitPropertyChange: propertyID: " << String (propertyID) - << " scope: " << String (scope) - << " element: " << String (element)); - - switch (propertyID) - { - case kAudioUnitProperty_IsInterAppConnected: - handleInterAppAudioConnectionChange(); - return; - case kAudioUnitProperty_StreamFormat: - if (scope == kAudioUnitScope_Output && element == 0) - handleStreamFormatChange(); - - return; - default: - jassertfalse; - return; - } - } - - void handleInterAppAudioConnectionChange() - { - UInt32 connected; - UInt32 dataSize = sizeof (connected); - OSStatus err = AudioUnitGetProperty (audioUnit, kAudioUnitProperty_IsInterAppConnected, - kAudioUnitScope_Global, 0, &connected, &dataSize); - ignoreUnused (err); - jassert (err == noErr); - - JUCE_IOS_AUDIO_LOG ("handleInterAppAudioConnectionChange: " << (connected ? "connected" - : "disconnected")); - - if (connected != interAppAudioConnected) - { - const ScopedLock myScopedLock (callbackLock); - - interAppAudioConnected = connected; - - UIApplicationState appstate = [UIApplication sharedApplication].applicationState; - bool inForeground = (appstate != UIApplicationStateBackground); - - if (interAppAudioConnected || inForeground) - { - setAudioSessionActive (true); - AudioOutputUnitStart (audioUnit); - - if (callback != nullptr) - callback->audioDeviceAboutToStart (&owner); - } - else if (! inForeground) - { - AudioOutputUnitStop (audioUnit); - setAudioSessionActive (false); - } - } - } - - //============================================================================== - void prepareFloatBuffers (int bufferSize) - { - if (numInputChannels + numOutputChannels > 0) - { - floatData.setSize (numInputChannels + numOutputChannels, bufferSize); - zeromem (inputChannels, sizeof (inputChannels)); - zeromem (outputChannels, sizeof (outputChannels)); - - for (int i = 0; i < numInputChannels; ++i) - inputChannels[i] = floatData.getWritePointer (i); - - for (int i = 0; i < numOutputChannels; ++i) - outputChannels[i] = floatData.getWritePointer (i + numInputChannels); - } - } - - //============================================================================== - OSStatus process (AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time, - const UInt32 numFrames, AudioBufferList* data) - { - OSStatus err = noErr; - - recordXruns (time, numFrames); - - if (audioInputIsAvailable && numInputChannels > 0) - err = AudioUnitRender (audioUnit, flags, time, 1, numFrames, data); - - const ScopedTryLock stl (callbackLock); - - if (stl.isLocked() && callback != nullptr) - { - if ((int) numFrames > floatData.getNumSamples()) - prepareFloatBuffers ((int) numFrames); - - if (audioInputIsAvailable && numInputChannels > 0) - { - short* shortData = (short*) data->mBuffers[0].mData; - - if (numInputChannels >= 2) - { - for (UInt32 i = 0; i < numFrames; ++i) - { - inputChannels[0][i] = *shortData++ * (1.0f / 32768.0f); - inputChannels[1][i] = *shortData++ * (1.0f / 32768.0f); - } - } - else - { - if (monoInputChannelNumber > 0) - ++shortData; - - for (UInt32 i = 0; i < numFrames; ++i) - { - inputChannels[0][i] = *shortData++ * (1.0f / 32768.0f); - ++shortData; - } - } - } - else - { - for (int i = numInputChannels; --i >= 0;) - zeromem (inputChannels[i], sizeof (float) * numFrames); - } - - callback->audioDeviceIOCallback ((const float**) inputChannels, numInputChannels, - outputChannels, numOutputChannels, (int) numFrames); - - short* const shortData = (short*) data->mBuffers[0].mData; - int n = 0; - - if (numOutputChannels >= 2) - { - for (UInt32 i = 0; i < numFrames; ++i) - { - shortData [n++] = (short) (outputChannels[0][i] * 32767.0f); - shortData [n++] = (short) (outputChannels[1][i] * 32767.0f); - } - } - else if (numOutputChannels == 1) - { - for (UInt32 i = 0; i < numFrames; ++i) - { - const short s = (short) (outputChannels[monoOutputChannelNumber][i] * 32767.0f); - shortData [n++] = s; - shortData [n++] = s; - } - } - else - { - zeromem (data->mBuffers[0].mData, 2 * sizeof (short) * numFrames); - } - } - else - { - zeromem (data->mBuffers[0].mData, 2 * sizeof (short) * numFrames); - } - - return err; - } - - void updateCurrentBufferSize() - { - NSTimeInterval bufferDuration = sampleRate > 0 ? (NSTimeInterval) ((preferredBufferSize + 1) / sampleRate) : 0.0; - - JUCE_NSERROR_CHECK ([[AVAudioSession sharedInstance] setPreferredIOBufferDuration: bufferDuration - error: &error]); - updateSampleRateAndAudioInput(); - } - - void recordXruns (const AudioTimeStamp* time, UInt32 numFrames) - { - if (time != nullptr && (time->mFlags & kAudioTimeStampSampleTimeValid) != 0) - { - if (! firstHostTime) - { - if ((time->mSampleTime - lastSampleTime) != lastNumFrames) - xrun++; - } - else - firstHostTime = false; - - lastSampleTime = time->mSampleTime; - } - else - firstHostTime = true; - - lastNumFrames = numFrames; - } - - //============================================================================== - static OSStatus processStatic (void* client, AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time, - UInt32 /*busNumber*/, UInt32 numFrames, AudioBufferList* data) - { - - return static_cast (client)->process (flags, time, numFrames, data); - } - - //============================================================================== - void resetFormat (const int numChannels) noexcept - { - zerostruct (format); - format.mFormatID = kAudioFormatLinearPCM; - format.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked | kAudioFormatFlagsNativeEndian; - format.mBitsPerChannel = 8 * sizeof (short); - format.mChannelsPerFrame = (UInt32) numChannels; - format.mFramesPerPacket = 1; - format.mBytesPerFrame = format.mBytesPerPacket = (UInt32) numChannels * sizeof (short); - } - - bool createAudioUnit() - { - if (audioUnit != 0) - { - AudioComponentInstanceDispose (audioUnit); - audioUnit = 0; - } - - resetFormat (2); - - AudioComponentDescription desc; - desc.componentType = kAudioUnitType_Output; - desc.componentSubType = kAudioUnitSubType_RemoteIO; - desc.componentManufacturer = kAudioUnitManufacturer_Apple; - desc.componentFlags = 0; - desc.componentFlagsMask = 0; - - AudioComponent comp = AudioComponentFindNext (0, &desc); - AudioComponentInstanceNew (comp, &audioUnit); - - if (audioUnit == 0) - return false; - - #if JucePlugin_Enable_IAA - AudioComponentDescription appDesc; - appDesc.componentType = JucePlugin_IAAType; - appDesc.componentSubType = JucePlugin_IAASubType; - appDesc.componentManufacturer = JucePlugin_ManufacturerCode; - appDesc.componentFlags = 0; - appDesc.componentFlagsMask = 0; - OSStatus err = AudioOutputUnitPublish (&appDesc, - CFSTR(JucePlugin_IAAName), - JucePlugin_VersionCode, - audioUnit); - - // This assert will be hit if the Inter-App Audio entitlement has not - // been enabled, or the description being published with - // AudioOutputUnitPublish is different from any in the AudioComponents - // array in this application's .plist file. - jassert (err == noErr); - - err = AudioUnitAddPropertyListener (audioUnit, - kAudioUnitProperty_IsInterAppConnected, - dispatchAudioUnitPropertyChange, - this); - jassert (err == noErr); - #endif - - if (numInputChannels > 0) - { - const UInt32 one = 1; - AudioUnitSetProperty (audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &one, sizeof (one)); - } - - { - AudioChannelLayout layout; - layout.mChannelBitmap = 0; - layout.mNumberChannelDescriptions = 0; - layout.mChannelLayoutTag = kAudioChannelLayoutTag_Stereo; - AudioUnitSetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Input, 0, &layout, sizeof (layout)); - AudioUnitSetProperty (audioUnit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Output, 0, &layout, sizeof (layout)); - } - - { - AURenderCallbackStruct inputProc; - inputProc.inputProc = processStatic; - inputProc.inputProcRefCon = this; - AudioUnitSetProperty (audioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &inputProc, sizeof (inputProc)); - } - - AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof (format)); - AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, sizeof (format)); - - UInt32 framesPerSlice; - UInt32 dataSize = sizeof (framesPerSlice); - - AudioUnitInitialize (audioUnit); - - updateCurrentBufferSize(); - - if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, - kAudioUnitScope_Global, 0, &framesPerSlice, &dataSize) == noErr - && dataSize == sizeof (framesPerSlice) && static_cast (framesPerSlice) != actualBufferSize) - { - prepareFloatBuffers (static_cast (framesPerSlice)); - } - - AudioUnitAddPropertyListener (audioUnit, kAudioUnitProperty_StreamFormat, dispatchAudioUnitPropertyChange, this); - - return true; - } - - void fillHostCallbackInfo (HostCallbackInfo& callbackInfo) - { - zerostruct (callbackInfo); - UInt32 dataSize = sizeof (HostCallbackInfo); - OSStatus err = AudioUnitGetProperty (audioUnit, - kAudioUnitProperty_HostCallbacks, - kAudioUnitScope_Global, - 0, - &callbackInfo, - &dataSize); - ignoreUnused (err); - jassert (err == noErr); - } - - void handleAudioTransportEvent (AudioUnitRemoteControlEvent event) - { - OSStatus err = AudioUnitSetProperty (audioUnit, kAudioOutputUnitProperty_RemoteControlToHost, - kAudioUnitScope_Global, 0, &event, sizeof (event)); - ignoreUnused (err); - jassert (err == noErr); - } - - // If the routing is set to go through the receiver (i.e. the speaker, but quiet), this re-routes it - // to make it loud. Needed because by default when using an input + output, the output is kept quiet. - static void fixAudioRouteIfSetToReceiver() - { - auto session = [AVAudioSession sharedInstance]; - auto route = session.currentRoute; - - for (AVAudioSessionPortDescription* port in route.inputs) - { - ignoreUnused (port); - JUCE_IOS_AUDIO_LOG ("AVAudioSession: input: " << [port.description UTF8String]); - } - - for (AVAudioSessionPortDescription* port in route.outputs) - { - JUCE_IOS_AUDIO_LOG ("AVAudioSession: output: " << [port.description UTF8String]); - - if ([port.portName isEqualToString: @"Receiver"]) - { - JUCE_NSERROR_CHECK ([session overrideOutputAudioPort: AVAudioSessionPortOverrideSpeaker - error: &error]); - setAudioSessionActive (true); - } - } - } - - void restart() - { - if (isRunning) - { - updateSampleRateAndAudioInput(); - updateCurrentBufferSize(); - createAudioUnit(); - - setAudioSessionActive (true); - - if (audioUnit != 0) - { - UInt32 formatSize = sizeof (format); - AudioUnitGetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, &formatSize); - AudioOutputUnitStart (audioUnit); - } - - if (callback != nullptr) - { - callback->audioDeviceStopped(); - callback->audioDeviceAboutToStart (&owner); - } - } - } - - void handleAsyncUpdate() override - { - restart(); - } - - void handleStreamFormatChange() - { - AudioStreamBasicDescription desc; - zerostruct (desc); - UInt32 dataSize = sizeof (desc); - AudioUnitGetProperty(audioUnit, - kAudioUnitProperty_StreamFormat, - kAudioUnitScope_Output, - 0, - &desc, - &dataSize); - - if (desc.mSampleRate != sampleRate) - { - JUCE_IOS_AUDIO_LOG ("handleStreamFormatChange: sample rate " << desc.mSampleRate); - triggerAsyncUpdate(); - } - } - - static void dispatchAudioUnitPropertyChange (void* data, AudioUnit unit, AudioUnitPropertyID propertyID, - AudioUnitScope scope, AudioUnitElement element) - { - static_cast (data)->handleAudioUnitPropertyChange (unit, propertyID, scope, element); - } - - void handleMidiMessage (MidiMessage msg) - { - if (messageCollector != nullptr) - messageCollector->addMessageToQueue (msg); - } - - static void midiEventCallback (void *client, UInt32 status, UInt32 data1, UInt32 data2, UInt32) - { - return static_cast (client)->handleMidiMessage (MidiMessage ((int) status, - (int) data1, - (int) data2, - Time::getMillisecondCounter() / 1000.0)); - } - - bool isRunning = false; - AudioIODeviceCallback* callback = nullptr; - - String lastError; - - bool audioInputIsAvailable = false; - - const int defaultBufferSize = - #if TARGET_IPHONE_SIMULATOR - 512; - #else - 256; - #endif - double sampleRate = 0; - int numInputChannels = 2, numOutputChannels = 2; - int preferredBufferSize = 0, actualBufferSize = 0; - - bool interAppAudioConnected = false; - - BigInteger activeOutputChans, activeInputChans; - - MidiMessageCollector* messageCollector = nullptr; - - iOSAudioIODevice& owner; - SharedResourcePointer sessionHolder; - CriticalSection callbackLock; - - AudioStreamBasicDescription format; - AudioUnit audioUnit {}; - - AudioSampleBuffer floatData; - float* inputChannels[3]; - float* outputChannels[3]; - bool monoInputChannelNumber, monoOutputChannelNumber; - - bool firstHostTime; - Float64 lastSampleTime; - unsigned int lastNumFrames; - int xrun; - - JUCE_DECLARE_NON_COPYABLE (Pimpl) -}; - - -//============================================================================== -iOSAudioIODevice::iOSAudioIODevice (const String& deviceName) - : AudioIODevice (deviceName, iOSAudioDeviceName), - pimpl (new Pimpl (*this)) -{} - -//============================================================================== -String iOSAudioIODevice::open (const BigInteger& inChans, const BigInteger& outChans, - double requestedSampleRate, int requestedBufferSize) -{ - return pimpl->open (inChans, outChans, requestedSampleRate, requestedBufferSize); -} -void iOSAudioIODevice::close() { pimpl->close(); } - -void iOSAudioIODevice::start (AudioIODeviceCallback* callbackToUse) { pimpl->start (callbackToUse); } -void iOSAudioIODevice::stop() { pimpl->stop(); } - -Array iOSAudioIODevice::getAvailableSampleRates() { return pimpl->getAvailableSampleRates(); } -Array iOSAudioIODevice::getAvailableBufferSizes() { return pimpl->getAvailableBufferSizes(); } - -bool iOSAudioIODevice::setAudioPreprocessingEnabled (bool enabled) { return pimpl->setAudioPreprocessingEnabled (enabled); } - -bool iOSAudioIODevice::isPlaying() { return pimpl->isRunning && pimpl->callback != nullptr; } -bool iOSAudioIODevice::isOpen() { return pimpl->isRunning; } -String iOSAudioIODevice::getLastError() { return pimpl->lastError; } - -StringArray iOSAudioIODevice::getOutputChannelNames() { return { "Left", "Right" }; } -StringArray iOSAudioIODevice::getInputChannelNames() { return pimpl->audioInputIsAvailable ? getOutputChannelNames() : StringArray(); } - -int iOSAudioIODevice::getDefaultBufferSize() { return pimpl->defaultBufferSize; } -int iOSAudioIODevice::getCurrentBufferSizeSamples() { return pimpl->actualBufferSize; } - -double iOSAudioIODevice::getCurrentSampleRate() { return pimpl->sampleRate; } - -int iOSAudioIODevice::getCurrentBitDepth() { return 16; } - -BigInteger iOSAudioIODevice::getActiveOutputChannels() const { return pimpl->activeOutputChans; } -BigInteger iOSAudioIODevice::getActiveInputChannels() const { return pimpl->activeInputChans; } - -int iOSAudioIODevice::getOutputLatencyInSamples() { return roundToInt (pimpl->sampleRate * [AVAudioSession sharedInstance].outputLatency); } -int iOSAudioIODevice::getInputLatencyInSamples() { return roundToInt (pimpl->sampleRate * [AVAudioSession sharedInstance].inputLatency); } -int iOSAudioIODevice::getXRunCount() const noexcept { return pimpl->xrun; } - -void iOSAudioIODevice::setMidiMessageCollector (MidiMessageCollector* collector) { pimpl->messageCollector = collector; } -AudioPlayHead* iOSAudioIODevice::getAudioPlayHead() const { return pimpl; } - -bool iOSAudioIODevice::isInterAppAudioConnected() const { return pimpl->interAppAudioConnected; } -#if JUCE_MODULE_AVAILABLE_juce_graphics -Image iOSAudioIODevice::getIcon (int size) { return pimpl->getIcon (size); } -#endif -void iOSAudioIODevice::switchApplication() { return pimpl->switchApplication(); } - -//============================================================================== -struct iOSAudioIODeviceType : public AudioIODeviceType -{ - iOSAudioIODeviceType() : AudioIODeviceType (iOSAudioDeviceName) {} - - void scanForDevices() {} - StringArray getDeviceNames (bool /*wantInputNames*/) const { return StringArray (iOSAudioDeviceName); } - int getDefaultDeviceIndex (bool /*forInput*/) const { return 0; } - int getIndexOfDevice (AudioIODevice* d, bool /*asInput*/) const { return d != nullptr ? 0 : -1; } - bool hasSeparateInputsAndOutputs() const { return false; } - - AudioIODevice* createDevice (const String& outputDeviceName, const String& inputDeviceName) - { - if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty()) - return new iOSAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName : inputDeviceName); - - return nullptr; - } - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSAudioIODeviceType) -}; - -//============================================================================== -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() -{ - return new iOSAudioIODeviceType(); -} - -//============================================================================== -AudioSessionHolder::AudioSessionHolder() { nativeSession = [[iOSAudioSessionNative alloc] init: this]; } -AudioSessionHolder::~AudioSessionHolder() { [nativeSession release]; } - -void AudioSessionHolder::handleAsyncUpdate() -{ - const ScopedLock sl (routeChangeLock); - for (auto device: activeDevices) - device->pimpl->handleRouteChange (lastRouteChangeReason.toRawUTF8()); -} - -void AudioSessionHolder::handleStatusChange (bool enabled, const char* reason) const -{ - for (auto device: activeDevices) - device->pimpl->handleStatusChange (enabled, reason); -} - -void AudioSessionHolder::handleRouteChange (const char* reason) -{ - const ScopedLock sl (routeChangeLock); - lastRouteChangeReason = reason; - triggerAsyncUpdate(); -} - -#undef JUCE_NSERROR_CHECK - -} // namespace juce diff --git a/source/modules/juce_audio_devices/native/juce_ios_Audio.h b/source/modules/juce_audio_devices/native/juce_ios_Audio.h deleted file mode 100644 index b06be4d97..000000000 --- a/source/modules/juce_audio_devices/native/juce_ios_Audio.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -struct iOSAudioIODeviceType; - -class iOSAudioIODevice : public AudioIODevice -{ -public: - //============================================================================== - String open (const BigInteger&, const BigInteger&, double, int) override; - void close() override; - - void start (AudioIODeviceCallback*) override; - void stop() override; - - Array getAvailableSampleRates() override; - Array getAvailableBufferSizes() override; - - bool setAudioPreprocessingEnabled (bool) override; - - //============================================================================== - bool isPlaying() override; - bool isOpen() override; - String getLastError() override; - - //============================================================================== - StringArray getOutputChannelNames() override; - StringArray getInputChannelNames() override; - - int getDefaultBufferSize() override; - int getCurrentBufferSizeSamples() override; - - double getCurrentSampleRate() override; - - int getCurrentBitDepth() override; - - BigInteger getActiveOutputChannels() const override; - BigInteger getActiveInputChannels() const override; - - int getOutputLatencyInSamples() override; - int getInputLatencyInSamples() override; - - int getXRunCount() const noexcept override; - - //============================================================================== - void setMidiMessageCollector (MidiMessageCollector*); - AudioPlayHead* getAudioPlayHead() const; - - //============================================================================== - bool isInterAppAudioConnected() const; - #if JUCE_MODULE_AVAILABLE_juce_graphics - Image getIcon (int size); - #endif - void switchApplication(); - -private: - //============================================================================== - iOSAudioIODevice (const String&); - - //============================================================================== - friend struct iOSAudioIODeviceType; - friend struct AudioSessionHolder; - - struct Pimpl; - friend struct Pimpl; - ScopedPointer pimpl; - - JUCE_DECLARE_NON_COPYABLE (iOSAudioIODevice) -}; - -} // namespace juce diff --git a/source/modules/juce_audio_devices/native/juce_linux_ALSA.cpp b/source/modules/juce_audio_devices/native/juce_linux_ALSA.cpp deleted file mode 100644 index 7a521f468..000000000 --- a/source/modules/juce_audio_devices/native/juce_linux_ALSA.cpp +++ /dev/null @@ -1,1314 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -namespace -{ - -#ifndef JUCE_ALSA_LOGGING - #define JUCE_ALSA_LOGGING 0 -#endif - -#if JUCE_ALSA_LOGGING - #define JUCE_ALSA_LOG(dbgtext) { juce::String tempDbgBuf ("ALSA: "); tempDbgBuf << dbgtext; Logger::writeToLog (tempDbgBuf); DBG (tempDbgBuf); } - #define JUCE_CHECKED_RESULT(x) (logErrorMessage (x, __LINE__)) - - static int logErrorMessage (int err, int lineNum) - { - if (err < 0) - JUCE_ALSA_LOG ("Error: line " << lineNum << ": code " << err << " (" << snd_strerror (err) << ")"); - - return err; - } -#else - #define JUCE_ALSA_LOG(x) {} - #define JUCE_CHECKED_RESULT(x) (x) -#endif - -#define JUCE_ALSA_FAILED(x) failed (x) - -static void getDeviceSampleRates (snd_pcm_t* handle, Array& rates) -{ - const int ratesToTry[] = { 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000, 0 }; - - snd_pcm_hw_params_t* hwParams; - snd_pcm_hw_params_alloca (&hwParams); - - for (int i = 0; ratesToTry[i] != 0; ++i) - { - if (snd_pcm_hw_params_any (handle, hwParams) >= 0 - && snd_pcm_hw_params_test_rate (handle, hwParams, (unsigned int) ratesToTry[i], 0) == 0) - { - rates.addIfNotAlreadyThere ((double) ratesToTry[i]); - } - } -} - -static void getDeviceNumChannels (snd_pcm_t* handle, unsigned int* minChans, unsigned int* maxChans) -{ - snd_pcm_hw_params_t *params; - snd_pcm_hw_params_alloca (¶ms); - - if (snd_pcm_hw_params_any (handle, params) >= 0) - { - snd_pcm_hw_params_get_channels_min (params, minChans); - snd_pcm_hw_params_get_channels_max (params, maxChans); - - JUCE_ALSA_LOG ("getDeviceNumChannels: " << (int) *minChans << " " << (int) *maxChans); - - // some virtual devices (dmix for example) report 10000 channels , we have to clamp these values - *maxChans = jmin (*maxChans, 256u); - *minChans = jmin (*minChans, *maxChans); - } - else - { - JUCE_ALSA_LOG ("getDeviceNumChannels failed"); - } -} - -static void getDeviceProperties (const String& deviceID, - unsigned int& minChansOut, - unsigned int& maxChansOut, - unsigned int& minChansIn, - unsigned int& maxChansIn, - Array& rates, - bool testOutput, - bool testInput) -{ - minChansOut = maxChansOut = minChansIn = maxChansIn = 0; - - if (deviceID.isEmpty()) - return; - - JUCE_ALSA_LOG ("getDeviceProperties(" << deviceID.toUTF8().getAddress() << ")"); - - snd_pcm_info_t* info; - snd_pcm_info_alloca (&info); - - if (testOutput) - { - snd_pcm_t* pcmHandle; - - if (JUCE_CHECKED_RESULT (snd_pcm_open (&pcmHandle, deviceID.toUTF8().getAddress(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) >= 0) - { - getDeviceNumChannels (pcmHandle, &minChansOut, &maxChansOut); - getDeviceSampleRates (pcmHandle, rates); - - snd_pcm_close (pcmHandle); - } - } - - if (testInput) - { - snd_pcm_t* pcmHandle; - - if (JUCE_CHECKED_RESULT (snd_pcm_open (&pcmHandle, deviceID.toUTF8(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK) >= 0)) - { - getDeviceNumChannels (pcmHandle, &minChansIn, &maxChansIn); - - if (rates.size() == 0) - getDeviceSampleRates (pcmHandle, rates); - - snd_pcm_close (pcmHandle); - } - } -} - -static void ensureMinimumNumBitsSet (BigInteger& chans, int minNumChans) -{ - int i = 0; - - while (chans.countNumberOfSetBits() < minNumChans) - chans.setBit (i++); -} - -static void silentErrorHandler (const char*, int, const char*, int, const char*,...) {} - -//============================================================================== -class ALSADevice -{ -public: - ALSADevice (const String& devID, bool forInput) - : handle (nullptr), - bitDepth (16), - numChannelsRunning (0), - latency (0), - deviceID (devID), - isInput (forInput), - isInterleaved (true) - { - JUCE_ALSA_LOG ("snd_pcm_open (" << deviceID.toUTF8().getAddress() << ", forInput=" << (int) forInput << ")"); - - int err = snd_pcm_open (&handle, deviceID.toUTF8(), - forInput ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, - SND_PCM_ASYNC); - if (err < 0) - { - if (-err == EBUSY) - error << "The device \"" << deviceID << "\" is busy (another application is using it)."; - else if (-err == ENOENT) - error << "The device \"" << deviceID << "\" is not available."; - else - error << "Could not open " << (forInput ? "input" : "output") << " device \"" << deviceID - << "\": " << snd_strerror(err) << " (" << err << ")"; - - JUCE_ALSA_LOG ("snd_pcm_open failed; " << error); - } - } - - ~ALSADevice() - { - closeNow(); - } - - void closeNow() - { - if (handle != nullptr) - { - snd_pcm_close (handle); - handle = nullptr; - } - } - - bool setParameters (unsigned int sampleRate, int numChannels, int bufferSize) - { - if (handle == nullptr) - return false; - - JUCE_ALSA_LOG ("ALSADevice::setParameters(" << deviceID << ", " - << (int) sampleRate << ", " << numChannels << ", " << bufferSize << ")"); - - snd_pcm_hw_params_t* hwParams; - snd_pcm_hw_params_alloca (&hwParams); - - if (snd_pcm_hw_params_any (handle, hwParams) < 0) - { - // this is the error message that aplay returns when an error happens here, - // it is a bit more explicit that "Invalid parameter" - error = "Broken configuration for this PCM: no configurations available"; - return false; - } - - if (snd_pcm_hw_params_set_access (handle, hwParams, SND_PCM_ACCESS_RW_INTERLEAVED) >= 0) // works better for plughw.. - isInterleaved = true; - else if (snd_pcm_hw_params_set_access (handle, hwParams, SND_PCM_ACCESS_RW_NONINTERLEAVED) >= 0) - isInterleaved = false; - else - { - jassertfalse; - return false; - } - - enum { isFloatBit = 1 << 16, isLittleEndianBit = 1 << 17, onlyUseLower24Bits = 1 << 18 }; - - const int formatsToTry[] = { SND_PCM_FORMAT_FLOAT_LE, 32 | isFloatBit | isLittleEndianBit, - SND_PCM_FORMAT_FLOAT_BE, 32 | isFloatBit, - SND_PCM_FORMAT_S32_LE, 32 | isLittleEndianBit, - SND_PCM_FORMAT_S32_BE, 32, - SND_PCM_FORMAT_S24_3LE, 24 | isLittleEndianBit, - SND_PCM_FORMAT_S24_3BE, 24, - SND_PCM_FORMAT_S24_LE, 32 | isLittleEndianBit | onlyUseLower24Bits, - SND_PCM_FORMAT_S16_LE, 16 | isLittleEndianBit, - SND_PCM_FORMAT_S16_BE, 16 }; - bitDepth = 0; - - for (int i = 0; i < numElementsInArray (formatsToTry); i += 2) - { - if (snd_pcm_hw_params_set_format (handle, hwParams, (_snd_pcm_format) formatsToTry [i]) >= 0) - { - const int type = formatsToTry [i + 1]; - bitDepth = type & 255; - - converter = createConverter (isInput, bitDepth, - (type & isFloatBit) != 0, - (type & isLittleEndianBit) != 0, - (type & onlyUseLower24Bits) != 0, - numChannels, - isInterleaved); - break; - } - } - - if (bitDepth == 0) - { - error = "device doesn't support a compatible PCM format"; - JUCE_ALSA_LOG ("Error: " + error); - return false; - } - - int dir = 0; - unsigned int periods = 4; - snd_pcm_uframes_t samplesPerPeriod = (snd_pcm_uframes_t) bufferSize; - - if (JUCE_ALSA_FAILED (snd_pcm_hw_params_set_rate_near (handle, hwParams, &sampleRate, 0)) - || JUCE_ALSA_FAILED (snd_pcm_hw_params_set_channels (handle, hwParams, (unsigned int ) numChannels)) - || JUCE_ALSA_FAILED (snd_pcm_hw_params_set_periods_near (handle, hwParams, &periods, &dir)) - || JUCE_ALSA_FAILED (snd_pcm_hw_params_set_period_size_near (handle, hwParams, &samplesPerPeriod, &dir)) - || JUCE_ALSA_FAILED (snd_pcm_hw_params (handle, hwParams))) - { - return false; - } - - snd_pcm_uframes_t frames = 0; - - if (JUCE_ALSA_FAILED (snd_pcm_hw_params_get_period_size (hwParams, &frames, &dir)) - || JUCE_ALSA_FAILED (snd_pcm_hw_params_get_periods (hwParams, &periods, &dir))) - latency = 0; - else - latency = (int) frames * ((int) periods - 1); // (this is the method JACK uses to guess the latency..) - - JUCE_ALSA_LOG ("frames: " << (int) frames << ", periods: " << (int) periods - << ", samplesPerPeriod: " << (int) samplesPerPeriod); - - snd_pcm_sw_params_t* swParams; - snd_pcm_sw_params_alloca (&swParams); - snd_pcm_uframes_t boundary; - - if (JUCE_ALSA_FAILED (snd_pcm_sw_params_current (handle, swParams)) - || JUCE_ALSA_FAILED (snd_pcm_sw_params_get_boundary (swParams, &boundary)) - || JUCE_ALSA_FAILED (snd_pcm_sw_params_set_silence_threshold (handle, swParams, 0)) - || JUCE_ALSA_FAILED (snd_pcm_sw_params_set_silence_size (handle, swParams, boundary)) - || JUCE_ALSA_FAILED (snd_pcm_sw_params_set_start_threshold (handle, swParams, samplesPerPeriod)) - || JUCE_ALSA_FAILED (snd_pcm_sw_params_set_stop_threshold (handle, swParams, boundary)) - || JUCE_ALSA_FAILED (snd_pcm_sw_params (handle, swParams))) - { - return false; - } - - #if JUCE_ALSA_LOGGING - // enable this to dump the config of the devices that get opened - snd_output_t* out; - snd_output_stdio_attach (&out, stderr, 0); - snd_pcm_hw_params_dump (hwParams, out); - snd_pcm_sw_params_dump (swParams, out); - #endif - - numChannelsRunning = numChannels; - - return true; - } - - //============================================================================== - bool writeToOutputDevice (AudioSampleBuffer& outputChannelBuffer, const int numSamples) - { - jassert (numChannelsRunning <= outputChannelBuffer.getNumChannels()); - float* const* const data = outputChannelBuffer.getArrayOfWritePointers(); - snd_pcm_sframes_t numDone = 0; - - if (isInterleaved) - { - scratch.ensureSize ((size_t) ((int) sizeof (float) * numSamples * numChannelsRunning), false); - - for (int i = 0; i < numChannelsRunning; ++i) - converter->convertSamples (scratch.getData(), i, data[i], 0, numSamples); - - numDone = snd_pcm_writei (handle, scratch.getData(), (snd_pcm_uframes_t) numSamples); - } - else - { - for (int i = 0; i < numChannelsRunning; ++i) - converter->convertSamples (data[i], data[i], numSamples); - - numDone = snd_pcm_writen (handle, (void**) data, (snd_pcm_uframes_t) numSamples); - } - - if (numDone < 0) - { - if (numDone == -(EPIPE)) - underrunCount++; - - if (JUCE_ALSA_FAILED (snd_pcm_recover (handle, (int) numDone, 1 /* silent */))) - return false; - } - - if (numDone < numSamples) - JUCE_ALSA_LOG ("Did not write all samples: numDone: " << numDone << ", numSamples: " << numSamples); - - return true; - } - - bool readFromInputDevice (AudioSampleBuffer& inputChannelBuffer, const int numSamples) - { - jassert (numChannelsRunning <= inputChannelBuffer.getNumChannels()); - float* const* const data = inputChannelBuffer.getArrayOfWritePointers(); - - if (isInterleaved) - { - scratch.ensureSize ((size_t) ((int) sizeof (float) * numSamples * numChannelsRunning), false); - scratch.fillWith (0); // (not clearing this data causes warnings in valgrind) - - snd_pcm_sframes_t num = snd_pcm_readi (handle, scratch.getData(), (snd_pcm_uframes_t) numSamples); - - if (num < 0) - { - if (num == -(EPIPE)) - overrunCount++; - - if (JUCE_ALSA_FAILED (snd_pcm_recover (handle, (int) num, 1 /* silent */))) - return false; - } - - - if (num < numSamples) - JUCE_ALSA_LOG ("Did not read all samples: num: " << num << ", numSamples: " << numSamples); - - for (int i = 0; i < numChannelsRunning; ++i) - converter->convertSamples (data[i], 0, scratch.getData(), i, numSamples); - } - else - { - snd_pcm_sframes_t num = snd_pcm_readn (handle, (void**) data, (snd_pcm_uframes_t) numSamples); - - if (num < 0) - { - if (num == -(EPIPE)) - overrunCount++; - - if (JUCE_ALSA_FAILED (snd_pcm_recover (handle, (int) num, 1 /* silent */))) - return false; - } - - if (num < numSamples) - JUCE_ALSA_LOG ("Did not read all samples: num: " << num << ", numSamples: " << numSamples); - - for (int i = 0; i < numChannelsRunning; ++i) - converter->convertSamples (data[i], data[i], numSamples); - } - - return true; - } - - //============================================================================== - snd_pcm_t* handle; - String error; - int bitDepth, numChannelsRunning, latency; - int underrunCount = 0, overrunCount = 0; - -private: - //============================================================================== - String deviceID; - const bool isInput; - bool isInterleaved; - MemoryBlock scratch; - ScopedPointer converter; - - //============================================================================== - template - struct ConverterHelper - { - static AudioData::Converter* createConverter (const bool forInput, const bool isLittleEndian, const int numInterleavedChannels, bool interleaved) - { - if (interleaved) - return create (forInput, isLittleEndian, numInterleavedChannels); - - return create (forInput, isLittleEndian, numInterleavedChannels); - } - - private: - template - static AudioData::Converter* create (const bool forInput, const bool isLittleEndian, const int numInterleavedChannels) - { - if (forInput) - { - typedef AudioData::Pointer DestType; - - if (isLittleEndian) - return new AudioData::ConverterInstance , DestType> (numInterleavedChannels, 1); - - return new AudioData::ConverterInstance , DestType> (numInterleavedChannels, 1); - } - - typedef AudioData::Pointer SourceType; - - if (isLittleEndian) - return new AudioData::ConverterInstance > (1, numInterleavedChannels); - - return new AudioData::ConverterInstance > (1, numInterleavedChannels); - } - }; - - static AudioData::Converter* createConverter (bool forInput, int bitDepth, - bool isFloat, bool isLittleEndian, bool useOnlyLower24Bits, - int numInterleavedChannels, - bool interleaved) - { - JUCE_ALSA_LOG ("format: bitDepth=" << bitDepth << ", isFloat=" << (int) isFloat - << ", isLittleEndian=" << (int) isLittleEndian << ", numChannels=" << numInterleavedChannels); - - if (isFloat) return ConverterHelper ::createConverter (forInput, isLittleEndian, numInterleavedChannels, interleaved); - if (bitDepth == 16) return ConverterHelper ::createConverter (forInput, isLittleEndian, numInterleavedChannels, interleaved); - if (bitDepth == 24) return ConverterHelper ::createConverter (forInput, isLittleEndian, numInterleavedChannels, interleaved); - - jassert (bitDepth == 32); - - if (useOnlyLower24Bits) - return ConverterHelper ::createConverter (forInput, isLittleEndian, numInterleavedChannels, interleaved); - - return ConverterHelper ::createConverter (forInput, isLittleEndian, numInterleavedChannels, interleaved); - } - - //============================================================================== - bool failed (const int errorNum) - { - if (errorNum >= 0) - return false; - - error = snd_strerror (errorNum); - JUCE_ALSA_LOG ("ALSA error: " << error); - return true; - } - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ALSADevice) -}; - -//============================================================================== -class ALSAThread : public Thread -{ -public: - ALSAThread (const String& inputDeviceID, const String& outputDeviceID) - : Thread ("Juce ALSA"), - sampleRate (0), - bufferSize (0), - outputLatency (0), - inputLatency (0), - callback (0), - inputId (inputDeviceID), - outputId (outputDeviceID), - numCallbacks (0), - audioIoInProgress (false), - inputChannelBuffer (1, 1), - outputChannelBuffer (1, 1) - { - initialiseRatesAndChannels(); - } - - ~ALSAThread() - { - close(); - } - - void open (BigInteger inputChannels, - BigInteger outputChannels, - const double newSampleRate, - const int newBufferSize) - { - close(); - - error.clear(); - sampleRate = newSampleRate; - bufferSize = newBufferSize; - - int maxInputsRequested = inputChannels.getHighestBit() + 1; - maxInputsRequested = jmax ((int) minChansIn, jmin ((int) maxChansIn, maxInputsRequested)); - - inputChannelBuffer.setSize (maxInputsRequested, bufferSize); - inputChannelBuffer.clear(); - inputChannelDataForCallback.clear(); - currentInputChans.clear(); - - if (inputChannels.getHighestBit() >= 0) - { - for (int i = 0; i < maxInputsRequested; ++i) - { - if (inputChannels[i]) - { - inputChannelDataForCallback.add (inputChannelBuffer.getReadPointer (i)); - currentInputChans.setBit (i); - } - } - } - - ensureMinimumNumBitsSet (outputChannels, (int) minChansOut); - - int maxOutputsRequested = outputChannels.getHighestBit() + 1; - maxOutputsRequested = jmax ((int) minChansOut, jmin ((int) maxChansOut, maxOutputsRequested)); - - outputChannelBuffer.setSize (maxOutputsRequested, bufferSize); - outputChannelBuffer.clear(); - outputChannelDataForCallback.clear(); - currentOutputChans.clear(); - - if (outputChannels.getHighestBit() >= 0) - { - for (int i = 0; i < maxOutputsRequested; ++i) - { - if (outputChannels[i]) - { - outputChannelDataForCallback.add (outputChannelBuffer.getWritePointer (i)); - currentOutputChans.setBit (i); - } - } - } - - if (outputChannelDataForCallback.size() > 0 && outputId.isNotEmpty()) - { - outputDevice = new ALSADevice (outputId, false); - - if (outputDevice->error.isNotEmpty()) - { - error = outputDevice->error; - outputDevice = nullptr; - return; - } - - if (! outputDevice->setParameters ((unsigned int) sampleRate, - jlimit ((int) minChansOut, (int) maxChansOut, - currentOutputChans.getHighestBit() + 1), - bufferSize)) - { - error = outputDevice->error; - outputDevice = nullptr; - return; - } - - outputLatency = outputDevice->latency; - } - - if (inputChannelDataForCallback.size() > 0 && inputId.isNotEmpty()) - { - inputDevice = new ALSADevice (inputId, true); - - if (inputDevice->error.isNotEmpty()) - { - error = inputDevice->error; - inputDevice = nullptr; - return; - } - - ensureMinimumNumBitsSet (currentInputChans, (int) minChansIn); - - if (! inputDevice->setParameters ((unsigned int) sampleRate, - jlimit ((int) minChansIn, (int) maxChansIn, currentInputChans.getHighestBit() + 1), - bufferSize)) - { - error = inputDevice->error; - inputDevice = nullptr; - return; - } - - inputLatency = inputDevice->latency; - } - - if (outputDevice == nullptr && inputDevice == nullptr) - { - error = "no channels"; - return; - } - - if (outputDevice != nullptr && inputDevice != nullptr) - snd_pcm_link (outputDevice->handle, inputDevice->handle); - - if (inputDevice != nullptr && JUCE_ALSA_FAILED (snd_pcm_prepare (inputDevice->handle))) - return; - - if (outputDevice != nullptr && JUCE_ALSA_FAILED (snd_pcm_prepare (outputDevice->handle))) - return; - - startThread (9); - - int count = 1000; - - while (numCallbacks == 0) - { - sleep (5); - - if (--count < 0 || ! isThreadRunning()) - { - error = "device didn't start"; - break; - } - } - } - - void close() - { - if (isThreadRunning()) - { - // problem: when pulseaudio is suspended (with pasuspend) , the ALSAThread::run is just stuck in - // snd_pcm_writei -- no error, no nothing it just stays stuck. So the only way I found to exit "nicely" - // (that is without the "killing thread by force" of stopThread) , is to just call snd_pcm_close from - // here which will cause the thread to resume, and exit - signalThreadShouldExit(); - - const int callbacksToStop = numCallbacks; - - if ((! waitForThreadToExit (400)) && audioIoInProgress && numCallbacks == callbacksToStop) - { - JUCE_ALSA_LOG ("Thread is stuck in i/o.. Is pulseaudio suspended?"); - - if (outputDevice != nullptr) outputDevice->closeNow(); - if (inputDevice != nullptr) inputDevice->closeNow(); - } - } - - stopThread (6000); - - inputDevice = nullptr; - outputDevice = nullptr; - - inputChannelBuffer.setSize (1, 1); - outputChannelBuffer.setSize (1, 1); - - numCallbacks = 0; - } - - void setCallback (AudioIODeviceCallback* const newCallback) noexcept - { - const ScopedLock sl (callbackLock); - callback = newCallback; - } - - void run() override - { - while (! threadShouldExit()) - { - if (inputDevice != nullptr && inputDevice->handle != nullptr) - { - if (outputDevice == nullptr || outputDevice->handle == nullptr) - { - JUCE_ALSA_FAILED (snd_pcm_wait (inputDevice->handle, 2000)); - - if (threadShouldExit()) - break; - - snd_pcm_sframes_t avail = snd_pcm_avail_update (inputDevice->handle); - - if (avail < 0) - JUCE_ALSA_FAILED (snd_pcm_recover (inputDevice->handle, (int) avail, 0)); - } - - audioIoInProgress = true; - - if (! inputDevice->readFromInputDevice (inputChannelBuffer, bufferSize)) - { - JUCE_ALSA_LOG ("Read failure"); - break; - } - - audioIoInProgress = false; - } - - if (threadShouldExit()) - break; - - { - const ScopedLock sl (callbackLock); - ++numCallbacks; - - if (callback != nullptr) - { - callback->audioDeviceIOCallback (inputChannelDataForCallback.getRawDataPointer(), - inputChannelDataForCallback.size(), - outputChannelDataForCallback.getRawDataPointer(), - outputChannelDataForCallback.size(), - bufferSize); - } - else - { - for (int i = 0; i < outputChannelDataForCallback.size(); ++i) - zeromem (outputChannelDataForCallback[i], sizeof (float) * (size_t) bufferSize); - } - } - - if (outputDevice != nullptr && outputDevice->handle != nullptr) - { - JUCE_ALSA_FAILED (snd_pcm_wait (outputDevice->handle, 2000)); - - if (threadShouldExit()) - break; - - snd_pcm_sframes_t avail = snd_pcm_avail_update (outputDevice->handle); - - if (avail < 0) - JUCE_ALSA_FAILED (snd_pcm_recover (outputDevice->handle, (int) avail, 0)); - - audioIoInProgress = true; - - if (! outputDevice->writeToOutputDevice (outputChannelBuffer, bufferSize)) - { - JUCE_ALSA_LOG ("write failure"); - break; - } - - audioIoInProgress = false; - } - } - - audioIoInProgress = false; - } - - int getBitDepth() const noexcept - { - if (outputDevice != nullptr) - return outputDevice->bitDepth; - - if (inputDevice != nullptr) - return inputDevice->bitDepth; - - return 16; - } - - int getXRunCount() const noexcept - { - int result = 0; - - if (outputDevice != nullptr) - result += outputDevice->underrunCount; - - if (inputDevice != nullptr) - result += inputDevice->overrunCount; - - return result; - } - - //============================================================================== - String error; - double sampleRate; - int bufferSize, outputLatency, inputLatency; - BigInteger currentInputChans, currentOutputChans; - - Array sampleRates; - StringArray channelNamesOut, channelNamesIn; - AudioIODeviceCallback* callback; - -private: - //============================================================================== - const String inputId, outputId; - ScopedPointer outputDevice, inputDevice; - int numCallbacks; - bool audioIoInProgress; - - CriticalSection callbackLock; - - AudioSampleBuffer inputChannelBuffer, outputChannelBuffer; - Array inputChannelDataForCallback; - Array outputChannelDataForCallback; - - unsigned int minChansOut, maxChansOut; - unsigned int minChansIn, maxChansIn; - - bool failed (const int errorNum) - { - if (errorNum >= 0) - return false; - - error = snd_strerror (errorNum); - JUCE_ALSA_LOG ("ALSA error: " << error); - return true; - } - - void initialiseRatesAndChannels() - { - sampleRates.clear(); - channelNamesOut.clear(); - channelNamesIn.clear(); - minChansOut = 0; - maxChansOut = 0; - minChansIn = 0; - maxChansIn = 0; - unsigned int dummy = 0; - - getDeviceProperties (inputId, dummy, dummy, minChansIn, maxChansIn, sampleRates, false, true); - getDeviceProperties (outputId, minChansOut, maxChansOut, dummy, dummy, sampleRates, true, false); - - for (unsigned int i = 0; i < maxChansOut; ++i) - channelNamesOut.add ("channel " + String ((int) i + 1)); - - for (unsigned int i = 0; i < maxChansIn; ++i) - channelNamesIn.add ("channel " + String ((int) i + 1)); - } - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ALSAThread) -}; - - -//============================================================================== -class ALSAAudioIODevice : public AudioIODevice -{ -public: - ALSAAudioIODevice (const String& deviceName, - const String& deviceTypeName, - const String& inputDeviceID, - const String& outputDeviceID) - : AudioIODevice (deviceName, deviceTypeName), - inputId (inputDeviceID), - outputId (outputDeviceID), - isOpen_ (false), - isStarted (false), - internal (inputDeviceID, outputDeviceID) - { - } - - ~ALSAAudioIODevice() - { - close(); - } - - StringArray getOutputChannelNames() override { return internal.channelNamesOut; } - StringArray getInputChannelNames() override { return internal.channelNamesIn; } - - Array getAvailableSampleRates() override { return internal.sampleRates; } - - Array getAvailableBufferSizes() override - { - Array r; - int n = 16; - - for (int i = 0; i < 50; ++i) - { - r.add (n); - n += n < 64 ? 16 - : (n < 512 ? 32 - : (n < 1024 ? 64 - : (n < 2048 ? 128 : 256))); - } - - return r; - } - - int getDefaultBufferSize() override { return 512; } - - String open (const BigInteger& inputChannels, - const BigInteger& outputChannels, - double sampleRate, - int bufferSizeSamples) override - { - close(); - - if (bufferSizeSamples <= 0) - bufferSizeSamples = getDefaultBufferSize(); - - if (sampleRate <= 0) - { - for (int i = 0; i < internal.sampleRates.size(); ++i) - { - double rate = internal.sampleRates[i]; - - if (rate >= 44100) - { - sampleRate = rate; - break; - } - } - } - - internal.open (inputChannels, outputChannels, - sampleRate, bufferSizeSamples); - - isOpen_ = internal.error.isEmpty(); - return internal.error; - } - - void close() override - { - stop(); - internal.close(); - isOpen_ = false; - } - - bool isOpen() override { return isOpen_; } - bool isPlaying() override { return isStarted && internal.error.isEmpty(); } - String getLastError() override { return internal.error; } - - int getCurrentBufferSizeSamples() override { return internal.bufferSize; } - double getCurrentSampleRate() override { return internal.sampleRate; } - int getCurrentBitDepth() override { return internal.getBitDepth(); } - - BigInteger getActiveOutputChannels() const override { return internal.currentOutputChans; } - BigInteger getActiveInputChannels() const override { return internal.currentInputChans; } - - int getOutputLatencyInSamples() override { return internal.outputLatency; } - int getInputLatencyInSamples() override { return internal.inputLatency; } - - int getXRunCount() const noexcept override { return internal.getXRunCount(); } - - void start (AudioIODeviceCallback* callback) override - { - if (! isOpen_) - callback = nullptr; - - if (callback != nullptr) - callback->audioDeviceAboutToStart (this); - - internal.setCallback (callback); - - isStarted = (callback != nullptr); - } - - void stop() override - { - AudioIODeviceCallback* const oldCallback = internal.callback; - - start (nullptr); - - if (oldCallback != nullptr) - oldCallback->audioDeviceStopped(); - } - - String inputId, outputId; - -private: - bool isOpen_, isStarted; - ALSAThread internal; -}; - - -//============================================================================== -class ALSAAudioIODeviceType : public AudioIODeviceType -{ -public: - ALSAAudioIODeviceType (bool onlySoundcards, const String &deviceTypeName) - : AudioIODeviceType (deviceTypeName), - hasScanned (false), - listOnlySoundcards (onlySoundcards) - { - #if ! JUCE_ALSA_LOGGING - snd_lib_error_set_handler (&silentErrorHandler); - #endif - } - - ~ALSAAudioIODeviceType() - { - #if ! JUCE_ALSA_LOGGING - snd_lib_error_set_handler (nullptr); - #endif - - snd_config_update_free_global(); // prevent valgrind from screaming about alsa leaks - } - - //============================================================================== - void scanForDevices() - { - if (hasScanned) - return; - - hasScanned = true; - inputNames.clear(); - inputIds.clear(); - outputNames.clear(); - outputIds.clear(); - - JUCE_ALSA_LOG ("scanForDevices()"); - - if (listOnlySoundcards) - enumerateAlsaSoundcards(); - else - enumerateAlsaPCMDevices(); - - inputNames.appendNumbersToDuplicates (false, true); - outputNames.appendNumbersToDuplicates (false, true); - } - - StringArray getDeviceNames (bool wantInputNames) const - { - jassert (hasScanned); // need to call scanForDevices() before doing this - - return wantInputNames ? inputNames : outputNames; - } - - int getDefaultDeviceIndex (bool forInput) const - { - jassert (hasScanned); // need to call scanForDevices() before doing this - - const int idx = (forInput ? inputIds : outputIds).indexOf ("default"); - return idx >= 0 ? idx : 0; - } - - bool hasSeparateInputsAndOutputs() const { return true; } - - int getIndexOfDevice (AudioIODevice* device, bool asInput) const - { - jassert (hasScanned); // need to call scanForDevices() before doing this - - if (ALSAAudioIODevice* d = dynamic_cast (device)) - return asInput ? inputIds.indexOf (d->inputId) - : outputIds.indexOf (d->outputId); - - return -1; - } - - AudioIODevice* createDevice (const String& outputDeviceName, - const String& inputDeviceName) - { - jassert (hasScanned); // need to call scanForDevices() before doing this - - const int inputIndex = inputNames.indexOf (inputDeviceName); - const int outputIndex = outputNames.indexOf (outputDeviceName); - - String deviceName (outputIndex >= 0 ? outputDeviceName - : inputDeviceName); - - if (inputIndex >= 0 || outputIndex >= 0) - return new ALSAAudioIODevice (deviceName, getTypeName(), - inputIds [inputIndex], - outputIds [outputIndex]); - - return nullptr; - } - -private: - //============================================================================== - StringArray inputNames, outputNames, inputIds, outputIds; - bool hasScanned, listOnlySoundcards; - - bool testDevice (const String &id, const String &outputName, const String &inputName) - { - unsigned int minChansOut = 0, maxChansOut = 0; - unsigned int minChansIn = 0, maxChansIn = 0; - Array rates; - - bool isInput = inputName.isNotEmpty(), isOutput = outputName.isNotEmpty(); - getDeviceProperties (id, minChansOut, maxChansOut, minChansIn, maxChansIn, rates, isOutput, isInput); - - isInput = maxChansIn > 0; - isOutput = maxChansOut > 0; - - if ((isInput || isOutput) && rates.size() > 0) - { - JUCE_ALSA_LOG ("testDevice: '" << id.toUTF8().getAddress() << "' -> isInput: " - << (int) isInput << ", isOutput: " << (int) isOutput); - - if (isInput) - { - inputNames.add (inputName); - inputIds.add (id); - } - - if (isOutput) - { - outputNames.add (outputName); - outputIds.add (id); - } - - return isInput || isOutput; - } - - return false; - } - - void enumerateAlsaSoundcards() - { - snd_ctl_t* handle = nullptr; - snd_ctl_card_info_t* info = nullptr; - snd_ctl_card_info_alloca (&info); - - int cardNum = -1; - - while (outputIds.size() + inputIds.size() <= 64) - { - snd_card_next (&cardNum); - - if (cardNum < 0) - break; - - if (JUCE_CHECKED_RESULT (snd_ctl_open (&handle, ("hw:" + String (cardNum)).toUTF8(), SND_CTL_NONBLOCK)) >= 0) - { - if (JUCE_CHECKED_RESULT (snd_ctl_card_info (handle, info)) >= 0) - { - String cardId (snd_ctl_card_info_get_id (info)); - - if (cardId.removeCharacters ("0123456789").isEmpty()) - cardId = String (cardNum); - - String cardName = snd_ctl_card_info_get_name (info); - - if (cardName.isEmpty()) - cardName = cardId; - - int device = -1; - - snd_pcm_info_t* pcmInfo; - snd_pcm_info_alloca (&pcmInfo); - - for (;;) - { - if (snd_ctl_pcm_next_device (handle, &device) < 0 || device < 0) - break; - - snd_pcm_info_set_device (pcmInfo, (unsigned int) device); - - for (unsigned int subDevice = 0, nbSubDevice = 1; subDevice < nbSubDevice; ++subDevice) - { - snd_pcm_info_set_subdevice (pcmInfo, subDevice); - snd_pcm_info_set_stream (pcmInfo, SND_PCM_STREAM_CAPTURE); - const bool isInput = (snd_ctl_pcm_info (handle, pcmInfo) >= 0); - - snd_pcm_info_set_stream (pcmInfo, SND_PCM_STREAM_PLAYBACK); - const bool isOutput = (snd_ctl_pcm_info (handle, pcmInfo) >= 0); - - if (! (isInput || isOutput)) - continue; - - if (nbSubDevice == 1) - nbSubDevice = snd_pcm_info_get_subdevices_count (pcmInfo); - - String id, name; - - if (nbSubDevice == 1) - { - id << "hw:" << cardId << "," << device; - name << cardName << ", " << snd_pcm_info_get_name (pcmInfo); - } - else - { - id << "hw:" << cardId << "," << device << "," << (int) subDevice; - name << cardName << ", " << snd_pcm_info_get_name (pcmInfo) - << " {" << snd_pcm_info_get_subdevice_name (pcmInfo) << "}"; - } - - JUCE_ALSA_LOG ("Soundcard ID: " << id << ", name: '" << name - << ", isInput:" << (int) isInput - << ", isOutput:" << (int) isOutput << "\n"); - - if (isInput) - { - inputNames.add (name); - inputIds.add (id); - } - - if (isOutput) - { - outputNames.add (name); - outputIds.add (id); - } - } - } - } - - JUCE_CHECKED_RESULT (snd_ctl_close (handle)); - } - } - } - - /* Enumerates all ALSA output devices (as output by the command aplay -L) - Does not try to open the devices (with "testDevice" for example), - so that it also finds devices that are busy and not yet available. - */ - void enumerateAlsaPCMDevices() - { - void** hints = nullptr; - - if (JUCE_CHECKED_RESULT (snd_device_name_hint (-1, "pcm", &hints)) == 0) - { - for (char** h = (char**) hints; *h; ++h) - { - const String id (hintToString (*h, "NAME")); - const String description (hintToString (*h, "DESC")); - const String ioid (hintToString (*h, "IOID")); - - JUCE_ALSA_LOG ("ID: " << id << "; desc: " << description << "; ioid: " << ioid); - - String ss = id.fromFirstOccurrenceOf ("=", false, false) - .upToFirstOccurrenceOf (",", false, false); - - if (id.isEmpty() - || id.startsWith ("default:") || id.startsWith ("sysdefault:") - || id.startsWith ("plughw:") || id == "null") - continue; - - String name (description.replace ("\n", "; ")); - - if (name.isEmpty()) - name = id; - - bool isOutput = (ioid != "Input"); - bool isInput = (ioid != "Output"); - - // alsa is stupid here, it advertises dmix and dsnoop as input/output devices, but - // opening dmix as input, or dsnoop as output will trigger errors.. - isInput = isInput && ! id.startsWith ("dmix"); - isOutput = isOutput && ! id.startsWith ("dsnoop"); - - if (isInput) - { - inputNames.add (name); - inputIds.add (id); - } - - if (isOutput) - { - outputNames.add (name); - outputIds.add (id); - } - } - - snd_device_name_free_hint (hints); - } - - // sometimes the "default" device is not listed, but it is nice to see it explicitely in the list - if (! outputIds.contains ("default")) - testDevice ("default", "Default ALSA Output", "Default ALSA Input"); - - // same for the pulseaudio plugin - if (! outputIds.contains ("pulse")) - testDevice ("pulse", "Pulseaudio output", "Pulseaudio input"); - - // make sure the default device is listed first, and followed by the pulse device (if present) - int idx = outputIds.indexOf ("pulse"); - outputIds.move (idx, 0); - outputNames.move (idx, 0); - - idx = inputIds.indexOf ("pulse"); - inputIds.move (idx, 0); - inputNames.move (idx, 0); - - idx = outputIds.indexOf ("default"); - outputIds.move (idx, 0); - outputNames.move (idx, 0); - - idx = inputIds.indexOf ("default"); - inputIds.move (idx, 0); - inputNames.move (idx, 0); - } - - static String hintToString (const void* hints, const char* type) - { - char* const hint = snd_device_name_get_hint (hints, type); - const String s (String::fromUTF8 (hint)); - ::free (hint); - return s; - } - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ALSAAudioIODeviceType) -}; - -} - -//============================================================================== -AudioIODeviceType* createAudioIODeviceType_ALSA_Soundcards() -{ - return new ALSAAudioIODeviceType (true, "ALSA HW"); -} - -AudioIODeviceType* createAudioIODeviceType_ALSA_PCMDevices() -{ - return new ALSAAudioIODeviceType (false, "ALSA"); -} - -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA() -{ - return createAudioIODeviceType_ALSA_PCMDevices(); -} - -} // namespace juce diff --git a/source/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp b/source/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp deleted file mode 100644 index 35302a316..000000000 --- a/source/modules/juce_audio_devices/native/juce_linux_JackAudio.cpp +++ /dev/null @@ -1,624 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -static void* juce_libjackHandle = nullptr; - -static void* juce_loadJackFunction (const char* const name) -{ - if (juce_libjackHandle == nullptr) - return nullptr; - - return dlsym (juce_libjackHandle, name); -} - -#define JUCE_DECL_JACK_FUNCTION(return_type, fn_name, argument_types, arguments) \ - return_type fn_name argument_types \ - { \ - typedef return_type (*fn_type) argument_types; \ - static fn_type fn = (fn_type) juce_loadJackFunction (#fn_name); \ - return (fn != nullptr) ? ((*fn) arguments) : (return_type) 0; \ - } - -#define JUCE_DECL_VOID_JACK_FUNCTION(fn_name, argument_types, arguments) \ - void fn_name argument_types \ - { \ - typedef void (*fn_type) argument_types; \ - static fn_type fn = (fn_type) juce_loadJackFunction (#fn_name); \ - if (fn != nullptr) (*fn) arguments; \ - } - -//============================================================================== -JUCE_DECL_JACK_FUNCTION (jack_client_t*, jack_client_open, (const char* client_name, jack_options_t options, jack_status_t* status, ...), (client_name, options, status)); -JUCE_DECL_JACK_FUNCTION (int, jack_client_close, (jack_client_t *client), (client)); -JUCE_DECL_JACK_FUNCTION (int, jack_activate, (jack_client_t* client), (client)); -JUCE_DECL_JACK_FUNCTION (int, jack_deactivate, (jack_client_t* client), (client)); -JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_get_buffer_size, (jack_client_t* client), (client)); -JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_get_sample_rate, (jack_client_t* client), (client)); -JUCE_DECL_VOID_JACK_FUNCTION (jack_on_shutdown, (jack_client_t* client, void (*function)(void* arg), void* arg), (client, function, arg)); -JUCE_DECL_JACK_FUNCTION (void* , jack_port_get_buffer, (jack_port_t* port, jack_nframes_t nframes), (port, nframes)); -JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_port_get_total_latency, (jack_client_t* client, jack_port_t* port), (client, port)); -JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_register, (jack_client_t* client, const char* port_name, const char* port_type, unsigned long flags, unsigned long buffer_size), (client, port_name, port_type, flags, buffer_size)); -JUCE_DECL_VOID_JACK_FUNCTION (jack_set_error_function, (void (*func)(const char*)), (func)); -JUCE_DECL_JACK_FUNCTION (int, jack_set_process_callback, (jack_client_t* client, JackProcessCallback process_callback, void* arg), (client, process_callback, arg)); -JUCE_DECL_JACK_FUNCTION (const char**, jack_get_ports, (jack_client_t* client, const char* port_name_pattern, const char* type_name_pattern, unsigned long flags), (client, port_name_pattern, type_name_pattern, flags)); -JUCE_DECL_JACK_FUNCTION (int, jack_connect, (jack_client_t* client, const char* source_port, const char* destination_port), (client, source_port, destination_port)); -JUCE_DECL_JACK_FUNCTION (const char*, jack_port_name, (const jack_port_t* port), (port)); -JUCE_DECL_JACK_FUNCTION (void*, jack_set_port_connect_callback, (jack_client_t* client, JackPortConnectCallback connect_callback, void* arg), (client, connect_callback, arg)); -JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_by_id, (jack_client_t* client, jack_port_id_t port_id), (client, port_id)); -JUCE_DECL_JACK_FUNCTION (int, jack_port_connected, (const jack_port_t* port), (port)); -JUCE_DECL_JACK_FUNCTION (int, jack_port_connected_to, (const jack_port_t* port, const char* port_name), (port, port_name)); -JUCE_DECL_JACK_FUNCTION (int, jack_set_xrun_callback, (jack_client_t* client, JackXRunCallback xrun_callback, void* arg), (client, xrun_callback, arg)); - -#if JUCE_DEBUG - #define JACK_LOGGING_ENABLED 1 -#endif - -#if JACK_LOGGING_ENABLED -namespace -{ - void jack_Log (const String& s) - { - std::cerr << s << std::endl; - } - - const char* getJackErrorMessage (const jack_status_t status) - { - if (status & JackServerFailed - || status & JackServerError) return "Unable to connect to JACK server"; - if (status & JackVersionError) return "Client's protocol version does not match"; - if (status & JackInvalidOption) return "The operation contained an invalid or unsupported option"; - if (status & JackNameNotUnique) return "The desired client name was not unique"; - if (status & JackNoSuchClient) return "Requested client does not exist"; - if (status & JackInitFailure) return "Unable to initialize client"; - return nullptr; - } -} - #define JUCE_JACK_LOG_STATUS(x) { if (const char* m = getJackErrorMessage (x)) jack_Log (m); } - #define JUCE_JACK_LOG(x) jack_Log(x) -#else - #define JUCE_JACK_LOG_STATUS(x) {} - #define JUCE_JACK_LOG(x) {} -#endif - - -//============================================================================== -#ifndef JUCE_JACK_CLIENT_NAME - #define JUCE_JACK_CLIENT_NAME "JUCEJack" -#endif - -struct JackPortIterator -{ - JackPortIterator (jack_client_t* const client, const bool forInput) - : ports (nullptr), index (-1) - { - if (client != nullptr) - ports = juce::jack_get_ports (client, nullptr, nullptr, - forInput ? JackPortIsOutput : JackPortIsInput); - // (NB: This looks like it's the wrong way round, but it is correct!) - } - - ~JackPortIterator() - { - ::free (ports); - } - - bool next() - { - if (ports == nullptr || ports [index + 1] == nullptr) - return false; - - name = CharPointer_UTF8 (ports[++index]); - clientName = name.upToFirstOccurrenceOf (":", false, false); - return true; - } - - const char** ports; - int index; - String name; - String clientName; -}; - -class JackAudioIODeviceType; -static Array activeDeviceTypes; - -//============================================================================== -class JackAudioIODevice : public AudioIODevice -{ -public: - JackAudioIODevice (const String& deviceName, - const String& inId, - const String& outId) - : AudioIODevice (deviceName, "JACK"), - inputId (inId), - outputId (outId), - deviceIsOpen (false), - callback (nullptr), - totalNumberOfInputChannels (0), - totalNumberOfOutputChannels (0) - { - jassert (deviceName.isNotEmpty()); - - jack_status_t status; - client = juce::jack_client_open (JUCE_JACK_CLIENT_NAME, JackNoStartServer, &status); - - if (client == nullptr) - { - JUCE_JACK_LOG_STATUS (status); - } - else - { - juce::jack_set_error_function (errorCallback); - - // open input ports - const StringArray inputChannels (getInputChannelNames()); - for (int i = 0; i < inputChannels.size(); ++i) - { - String inputName; - inputName << "in_" << ++totalNumberOfInputChannels; - - inputPorts.add (juce::jack_port_register (client, inputName.toUTF8(), - JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0)); - } - - // open output ports - const StringArray outputChannels (getOutputChannelNames()); - for (int i = 0; i < outputChannels.size(); ++i) - { - String outputName; - outputName << "out_" << ++totalNumberOfOutputChannels; - - outputPorts.add (juce::jack_port_register (client, outputName.toUTF8(), - JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0)); - } - - inChans.calloc (totalNumberOfInputChannels + 2); - outChans.calloc (totalNumberOfOutputChannels + 2); - } - } - - ~JackAudioIODevice() - { - close(); - if (client != nullptr) - { - juce::jack_client_close (client); - client = nullptr; - } - } - - StringArray getChannelNames (bool forInput) const - { - StringArray names; - - for (JackPortIterator i (client, forInput); i.next();) - if (i.clientName == getName()) - names.add (i.name.fromFirstOccurrenceOf (":", false, false)); - - return names; - } - - StringArray getOutputChannelNames() override { return getChannelNames (false); } - StringArray getInputChannelNames() override { return getChannelNames (true); } - - Array getAvailableSampleRates() override - { - Array rates; - - if (client != nullptr) - rates.add (juce::jack_get_sample_rate (client)); - - return rates; - } - - Array getAvailableBufferSizes() override - { - Array sizes; - - if (client != nullptr) - sizes.add (juce::jack_get_buffer_size (client)); - - return sizes; - } - - int getDefaultBufferSize() override { return getCurrentBufferSizeSamples(); } - int getCurrentBufferSizeSamples() override { return client != nullptr ? juce::jack_get_buffer_size (client) : 0; } - double getCurrentSampleRate() override { return client != nullptr ? juce::jack_get_sample_rate (client) : 0; } - - - String open (const BigInteger& inputChannels, const BigInteger& outputChannels, - double /* sampleRate */, int /* bufferSizeSamples */) override - { - if (client == nullptr) - { - lastError = "No JACK client running"; - return lastError; - } - - lastError.clear(); - close(); - - xruns = 0; - juce::jack_set_process_callback (client, processCallback, this); - juce::jack_set_port_connect_callback (client, portConnectCallback, this); - juce::jack_on_shutdown (client, shutdownCallback, this); - juce::jack_set_xrun_callback (client, xrunCallback, this); - juce::jack_activate (client); - deviceIsOpen = true; - - if (! inputChannels.isZero()) - { - for (JackPortIterator i (client, true); i.next();) - { - if (inputChannels [i.index] && i.clientName == getName()) - { - int error = juce::jack_connect (client, i.ports[i.index], juce::jack_port_name ((jack_port_t*) inputPorts[i.index])); - if (error != 0) - JUCE_JACK_LOG ("Cannot connect input port " + String (i.index) + " (" + i.name + "), error " + String (error)); - } - } - } - - if (! outputChannels.isZero()) - { - for (JackPortIterator i (client, false); i.next();) - { - if (outputChannels [i.index] && i.clientName == getName()) - { - int error = juce::jack_connect (client, juce::jack_port_name ((jack_port_t*) outputPorts[i.index]), i.ports[i.index]); - if (error != 0) - JUCE_JACK_LOG ("Cannot connect output port " + String (i.index) + " (" + i.name + "), error " + String (error)); - } - } - } - - updateActivePorts(); - - return lastError; - } - - void close() override - { - stop(); - - if (client != nullptr) - { - juce::jack_deactivate (client); - - juce::jack_set_xrun_callback (client, xrunCallback, nullptr); - juce::jack_set_process_callback (client, processCallback, nullptr); - juce::jack_set_port_connect_callback (client, portConnectCallback, nullptr); - juce::jack_on_shutdown (client, shutdownCallback, nullptr); - } - - deviceIsOpen = false; - } - - void start (AudioIODeviceCallback* newCallback) override - { - if (deviceIsOpen && newCallback != callback) - { - if (newCallback != nullptr) - newCallback->audioDeviceAboutToStart (this); - - AudioIODeviceCallback* const oldCallback = callback; - - { - const ScopedLock sl (callbackLock); - callback = newCallback; - } - - if (oldCallback != nullptr) - oldCallback->audioDeviceStopped(); - } - } - - void stop() override - { - start (nullptr); - } - - bool isOpen() override { return deviceIsOpen; } - bool isPlaying() override { return callback != nullptr; } - int getCurrentBitDepth() override { return 32; } - String getLastError() override { return lastError; } - int getXRunCount() const noexcept override { return xruns; } - - BigInteger getActiveOutputChannels() const override { return activeOutputChannels; } - BigInteger getActiveInputChannels() const override { return activeInputChannels; } - - int getOutputLatencyInSamples() override - { - int latency = 0; - - for (int i = 0; i < outputPorts.size(); i++) - latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, (jack_port_t*) outputPorts [i])); - - return latency; - } - - int getInputLatencyInSamples() override - { - int latency = 0; - - for (int i = 0; i < inputPorts.size(); i++) - latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, (jack_port_t*) inputPorts [i])); - - return latency; - } - - String inputId, outputId; - -private: - void process (const int numSamples) - { - int numActiveInChans = 0, numActiveOutChans = 0; - - for (int i = 0; i < totalNumberOfInputChannels; ++i) - { - if (activeInputChannels[i]) - if (jack_default_audio_sample_t* in - = (jack_default_audio_sample_t*) juce::jack_port_get_buffer ((jack_port_t*) inputPorts.getUnchecked(i), numSamples)) - inChans [numActiveInChans++] = (float*) in; - } - - for (int i = 0; i < totalNumberOfOutputChannels; ++i) - { - if (activeOutputChannels[i]) - if (jack_default_audio_sample_t* out - = (jack_default_audio_sample_t*) juce::jack_port_get_buffer ((jack_port_t*) outputPorts.getUnchecked(i), numSamples)) - outChans [numActiveOutChans++] = (float*) out; - } - - const ScopedLock sl (callbackLock); - - if (callback != nullptr) - { - if ((numActiveInChans + numActiveOutChans) > 0) - callback->audioDeviceIOCallback (const_cast (inChans.getData()), numActiveInChans, - outChans, numActiveOutChans, numSamples); - } - else - { - for (int i = 0; i < numActiveOutChans; ++i) - zeromem (outChans[i], sizeof (float) * numSamples); - } - } - - static int processCallback (jack_nframes_t nframes, void* callbackArgument) - { - if (callbackArgument != nullptr) - ((JackAudioIODevice*) callbackArgument)->process (nframes); - - return 0; - } - - static int xrunCallback (void* callbackArgument) - { - if (callbackArgument != nullptr) - ((JackAudioIODevice*) callbackArgument)->xruns++; - - return 0; - } - - void updateActivePorts() - { - BigInteger newOutputChannels, newInputChannels; - - for (int i = 0; i < outputPorts.size(); ++i) - if (juce::jack_port_connected ((jack_port_t*) outputPorts.getUnchecked(i))) - newOutputChannels.setBit (i); - - for (int i = 0; i < inputPorts.size(); ++i) - if (juce::jack_port_connected ((jack_port_t*) inputPorts.getUnchecked(i))) - newInputChannels.setBit (i); - - if (newOutputChannels != activeOutputChannels - || newInputChannels != activeInputChannels) - { - AudioIODeviceCallback* const oldCallback = callback; - - stop(); - - activeOutputChannels = newOutputChannels; - activeInputChannels = newInputChannels; - - if (oldCallback != nullptr) - start (oldCallback); - - sendDeviceChangedCallback(); - } - } - - static void portConnectCallback (jack_port_id_t, jack_port_id_t, int, void* arg) - { - if (JackAudioIODevice* device = static_cast (arg)) - device->updateActivePorts(); - } - - static void threadInitCallback (void* /* callbackArgument */) - { - JUCE_JACK_LOG ("JackAudioIODevice::initialise"); - } - - static void shutdownCallback (void* callbackArgument) - { - JUCE_JACK_LOG ("JackAudioIODevice::shutdown"); - - if (JackAudioIODevice* device = (JackAudioIODevice*) callbackArgument) - { - device->client = nullptr; - device->close(); - } - } - - static void errorCallback (const char* msg) - { - JUCE_JACK_LOG ("JackAudioIODevice::errorCallback " + String (msg)); - } - - static void sendDeviceChangedCallback(); - - bool deviceIsOpen; - jack_client_t* client; - String lastError; - AudioIODeviceCallback* callback; - CriticalSection callbackLock; - - HeapBlock inChans, outChans; - int totalNumberOfInputChannels; - int totalNumberOfOutputChannels; - Array inputPorts, outputPorts; - BigInteger activeInputChannels, activeOutputChannels; - - int xruns; -}; - - -//============================================================================== -class JackAudioIODeviceType : public AudioIODeviceType -{ -public: - JackAudioIODeviceType() - : AudioIODeviceType ("JACK"), - hasScanned (false) - { - activeDeviceTypes.add (this); - } - - ~JackAudioIODeviceType() - { - activeDeviceTypes.removeFirstMatchingValue (this); - } - - void scanForDevices() - { - hasScanned = true; - inputNames.clear(); - inputIds.clear(); - outputNames.clear(); - outputIds.clear(); - - if (juce_libjackHandle == nullptr) juce_libjackHandle = dlopen ("libjack.so.0", RTLD_LAZY); - if (juce_libjackHandle == nullptr) juce_libjackHandle = dlopen ("libjack.so", RTLD_LAZY); - if (juce_libjackHandle == nullptr) return; - - jack_status_t status; - - // open a dummy client - if (jack_client_t* const client = juce::jack_client_open ("JuceJackDummy", JackNoStartServer, &status)) - { - // scan for output devices - for (JackPortIterator i (client, false); i.next();) - { - if (i.clientName != (JUCE_JACK_CLIENT_NAME) && ! inputNames.contains (i.clientName)) - { - inputNames.add (i.clientName); - inputIds.add (i.ports [i.index]); - } - } - - // scan for input devices - for (JackPortIterator i (client, true); i.next();) - { - if (i.clientName != (JUCE_JACK_CLIENT_NAME) && ! outputNames.contains (i.clientName)) - { - outputNames.add (i.clientName); - outputIds.add (i.ports [i.index]); - } - } - - juce::jack_client_close (client); - } - else - { - JUCE_JACK_LOG_STATUS (status); - } - } - - StringArray getDeviceNames (bool wantInputNames) const - { - jassert (hasScanned); // need to call scanForDevices() before doing this - return wantInputNames ? inputNames : outputNames; - } - - int getDefaultDeviceIndex (bool /* forInput */) const - { - jassert (hasScanned); // need to call scanForDevices() before doing this - return 0; - } - - bool hasSeparateInputsAndOutputs() const { return true; } - - int getIndexOfDevice (AudioIODevice* device, bool asInput) const - { - jassert (hasScanned); // need to call scanForDevices() before doing this - - if (JackAudioIODevice* d = dynamic_cast (device)) - return asInput ? inputIds.indexOf (d->inputId) - : outputIds.indexOf (d->outputId); - - return -1; - } - - AudioIODevice* createDevice (const String& outputDeviceName, - const String& inputDeviceName) - { - jassert (hasScanned); // need to call scanForDevices() before doing this - - const int inputIndex = inputNames.indexOf (inputDeviceName); - const int outputIndex = outputNames.indexOf (outputDeviceName); - - if (inputIndex >= 0 || outputIndex >= 0) - return new JackAudioIODevice (outputIndex >= 0 ? outputDeviceName - : inputDeviceName, - inputIds [inputIndex], - outputIds [outputIndex]); - - return nullptr; - } - - void portConnectionChange() { callDeviceChangeListeners(); } - -private: - StringArray inputNames, outputNames, inputIds, outputIds; - bool hasScanned; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JackAudioIODeviceType) -}; - -void JackAudioIODevice::sendDeviceChangedCallback() -{ - for (int i = activeDeviceTypes.size(); --i >= 0;) - if (JackAudioIODeviceType* d = activeDeviceTypes[i]) - d->portConnectionChange(); -} - -//============================================================================== -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() -{ - return new JackAudioIODeviceType(); -} - -} // namespace juce diff --git a/source/modules/juce_audio_devices/native/juce_linux_Midi.cpp b/source/modules/juce_audio_devices/native/juce_linux_Midi.cpp deleted file mode 100644 index ef4dfade8..000000000 --- a/source/modules/juce_audio_devices/native/juce_linux_Midi.cpp +++ /dev/null @@ -1,617 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -#if JUCE_ALSA - -// You can define these strings in your app if you want to override the default names: -#ifndef JUCE_ALSA_MIDI_NAME - #define JUCE_ALSA_MIDI_NAME JUCEApplicationBase::getInstance()->getApplicationName().toUTF8() -#endif - -//============================================================================== -namespace -{ - -//============================================================================== -class AlsaClient : public ReferenceCountedObject -{ -public: - typedef ReferenceCountedObjectPtr Ptr; - - //============================================================================== - // represents an input or output port of the supplied AlsaClient - class Port - { - public: - Port (AlsaClient& c, bool forInput) noexcept - : portId (-1), - callbackEnabled (false), - client (c), - isInput (forInput), - callback (nullptr), - maxEventSize (4 * 1024), - midiInput (nullptr) - {} - - ~Port() - { - if (isValid()) - { - if (isInput) - enableCallback (false); - else - snd_midi_event_free (midiParser); - - snd_seq_delete_simple_port (client.get(), portId); - } - } - - void connectWith (int sourceClient, int sourcePort) const noexcept - { - if (isInput) - snd_seq_connect_from (client.get(), portId, sourceClient, sourcePort); - else - snd_seq_connect_to (client.get(), portId, sourceClient, sourcePort); - } - - bool isValid() const noexcept - { - return client.get() != nullptr && portId >= 0; - } - - void setupInput(MidiInput* input, MidiInputCallback* cb) - { - jassert (cb && input); - - callback = cb; - midiInput = input; - } - - void setupOutput() - { - jassert (! isInput); - - snd_midi_event_new ((size_t) maxEventSize, &midiParser); - } - - void enableCallback (bool enable) - { - if (callbackEnabled != enable) - { - callbackEnabled = enable; - - if (enable) - client.registerCallback(); - else - client.unregisterCallback(); - } - } - - bool sendMessageNow (const MidiMessage& message) - { - if (message.getRawDataSize() > maxEventSize) - { - maxEventSize = message.getRawDataSize(); - snd_midi_event_free (midiParser); - snd_midi_event_new ((size_t) maxEventSize, &midiParser); - } - - snd_seq_event_t event; - snd_seq_ev_clear (&event); - - long numBytes = (long) message.getRawDataSize(); - const uint8* data = message.getRawData(); - - snd_seq_t* seqHandle = client.get(); - bool success = true; - - while (numBytes > 0) - { - const long numSent = snd_midi_event_encode (midiParser, data, numBytes, &event); - - if (numSent <= 0) - { - success = numSent == 0; - break; - } - - numBytes -= numSent; - data += numSent; - - snd_seq_ev_set_source (&event, (unsigned char) portId); - snd_seq_ev_set_subs (&event); - snd_seq_ev_set_direct (&event); - - if (snd_seq_event_output_direct (seqHandle, &event) < 0) - { - success = false; - break; - } - } - - snd_midi_event_reset_encode (midiParser); - return success; - } - - - bool operator== (const Port& lhs) const noexcept - { - return portId != -1 && portId == lhs.portId; - } - - int portId; - bool callbackEnabled; - - private: - friend class AlsaClient; - - AlsaClient& client; - bool isInput; - MidiInputCallback* callback; - snd_midi_event_t* midiParser; - int maxEventSize; - MidiInput* midiInput; - - void createPort (const String& name, bool enableSubscription) - { - if (snd_seq_t* seqHandle = client.get()) - { - const unsigned int caps = - isInput - ? (SND_SEQ_PORT_CAP_WRITE | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_WRITE : 0)) - : (SND_SEQ_PORT_CAP_WRITE | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_READ : 0)); - portId = snd_seq_create_simple_port (seqHandle, name.toUTF8(), caps, - SND_SEQ_PORT_TYPE_MIDI_GENERIC | - SND_SEQ_PORT_TYPE_APPLICATION); - } - } - - void handleIncomingMidiMessage (const MidiMessage& message) const - { - callback->handleIncomingMidiMessage (midiInput, message); - } - - void handlePartialSysexMessage (const uint8* messageData, int numBytesSoFar, double timeStamp) - { - callback->handlePartialSysexMessage (midiInput, messageData, numBytesSoFar, timeStamp); - } - }; - - static Ptr getInstance() - { - if (instance == nullptr) - instance = new AlsaClient(); - - return instance; - } - - void registerCallback() - { - if (inputThread == nullptr) - inputThread = new MidiInputThread (*this); - - if (++activeCallbacks - 1 == 0) - inputThread->startThread(); - } - - void unregisterCallback() - { - jassert (activeCallbacks.get() > 0); - if (--activeCallbacks == 0 && inputThread->isThreadRunning()) - inputThread->signalThreadShouldExit(); - } - - void handleIncomingMidiMessage (snd_seq_event* event, const MidiMessage& message) - { - if (event->dest.port < ports.size() - && ports[event->dest.port]->callbackEnabled) - ports[event->dest.port]->handleIncomingMidiMessage (message); - } - - void handlePartialSysexMessage (snd_seq_event* event, const uint8* messageData, int numBytesSoFar, double timeStamp) - { - if (event->dest.port < ports.size() - && ports[event->dest.port]->callbackEnabled) - ports[event->dest.port]->handlePartialSysexMessage (messageData, numBytesSoFar, timeStamp); - } - - snd_seq_t* get() const noexcept { return handle; } - int getId() const noexcept { return clientId; } - - Port* createPort (const String& name, bool forInput, bool enableSubscription) - { - Port* port = new Port (*this, forInput); - port->createPort (name, enableSubscription); - ports.set (port->portId, port); - incReferenceCount(); - return port; - } - - void deletePort (Port* port) - { - ports.remove (port->portId); - decReferenceCount(); - } - -private: - snd_seq_t* handle; - int clientId; - OwnedArray ports; - Atomic activeCallbacks; - CriticalSection callbackLock; - - static AlsaClient* instance; - - //============================================================================== - friend class ReferenceCountedObjectPtr; - friend struct ContainerDeletePolicy; - - AlsaClient() - : handle (nullptr), - inputThread (nullptr) - { - jassert (instance == nullptr); - - snd_seq_open (&handle, "default", SND_SEQ_OPEN_DUPLEX, 0); - snd_seq_nonblock (handle, SND_SEQ_NONBLOCK); - snd_seq_set_client_name (handle, JUCE_ALSA_MIDI_NAME); - clientId = snd_seq_client_id(handle); - - // It's good idea to pre-allocate a good number of elements - ports.ensureStorageAllocated (32); - } - - ~AlsaClient() - { - jassert (instance != nullptr); - - instance = nullptr; - - if (handle != nullptr) - snd_seq_close (handle); - - jassert (activeCallbacks.get() == 0); - - if (inputThread) - inputThread->stopThread (3000); - } - - //============================================================================== - class MidiInputThread : public Thread - { - public: - MidiInputThread (AlsaClient& c) - : Thread ("Juce MIDI Input"), client (c), concatenator (2048) - { - jassert (client.get() != nullptr); - } - - void run() override - { - const int maxEventSize = 16 * 1024; - snd_midi_event_t* midiParser; - snd_seq_t* seqHandle = client.get(); - - if (snd_midi_event_new (maxEventSize, &midiParser) >= 0) - { - const int numPfds = snd_seq_poll_descriptors_count (seqHandle, POLLIN); - HeapBlock pfd ((size_t) numPfds); - snd_seq_poll_descriptors (seqHandle, pfd, (unsigned int) numPfds, POLLIN); - - HeapBlock buffer (maxEventSize); - - while (! threadShouldExit()) - { - if (poll (pfd, (nfds_t) numPfds, 100) > 0) // there was a "500" here which is a bit long when we exit the program and have to wait for a timeout on this poll call - { - if (threadShouldExit()) - break; - - do - { - snd_seq_event_t* inputEvent = nullptr; - - if (snd_seq_event_input (seqHandle, &inputEvent) >= 0) - { - // xxx what about SYSEXes that are too big for the buffer? - const long numBytes = snd_midi_event_decode (midiParser, buffer, - maxEventSize, inputEvent); - - snd_midi_event_reset_decode (midiParser); - - concatenator.pushMidiData (buffer, (int) numBytes, - Time::getMillisecondCounter() * 0.001, - inputEvent, client); - - snd_seq_free_event (inputEvent); - } - } - while (snd_seq_event_input_pending (seqHandle, 0) > 0); - } - } - - snd_midi_event_free (midiParser); - } - } - - private: - AlsaClient& client; - MidiDataConcatenator concatenator; - }; - - ScopedPointer inputThread; -}; - -AlsaClient* AlsaClient::instance = nullptr; - -//============================================================================== -static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client, - snd_seq_client_info_t* clientInfo, - const bool forInput, - StringArray& deviceNamesFound, - const int deviceIndexToOpen) -{ - AlsaClient::Port* port = nullptr; - - snd_seq_t* seqHandle = client->get(); - snd_seq_port_info_t* portInfo = nullptr; - - snd_seq_port_info_alloca (&portInfo); - jassert (portInfo); - int numPorts = snd_seq_client_info_get_num_ports (clientInfo); - const int sourceClient = snd_seq_client_info_get_client (clientInfo); - - snd_seq_port_info_set_client (portInfo, sourceClient); - snd_seq_port_info_set_port (portInfo, -1); - - while (--numPorts >= 0) - { - if (snd_seq_query_next_port (seqHandle, portInfo) == 0 - && (snd_seq_port_info_get_capability (portInfo) - & (forInput ? SND_SEQ_PORT_CAP_SUBS_WRITE : SND_SEQ_PORT_CAP_SUBS_READ)) != 0) - { - const String portName = snd_seq_port_info_get_name(portInfo); - - deviceNamesFound.add (portName); - - if (deviceNamesFound.size() == deviceIndexToOpen + 1) - { - const int sourcePort = snd_seq_port_info_get_port (portInfo); - if (sourcePort != -1) - { - port = client->createPort (portName, forInput, false); - jassert (port->isValid()); - port->connectWith (sourceClient, sourcePort); - break; - } - } - } - } - - return port; -} - -static AlsaClient::Port* iterateMidiDevices (const bool forInput, - StringArray& deviceNamesFound, - const int deviceIndexToOpen) -{ - AlsaClient::Port* port = nullptr; - const AlsaClient::Ptr client (AlsaClient::getInstance()); - - if (snd_seq_t* const seqHandle = client->get()) - { - snd_seq_system_info_t* systemInfo = nullptr; - snd_seq_client_info_t* clientInfo = nullptr; - - snd_seq_system_info_alloca (&systemInfo); - jassert(systemInfo); - if (snd_seq_system_info (seqHandle, systemInfo) == 0) - { - snd_seq_client_info_alloca (&clientInfo); - jassert(clientInfo); - int numClients = snd_seq_system_info_get_cur_clients (systemInfo); - - while (--numClients >= 0) - { - if (snd_seq_query_next_client (seqHandle, clientInfo) == 0) - { - const int sourceClient = snd_seq_client_info_get_client (clientInfo); - if (sourceClient != client->getId() - && sourceClient != SND_SEQ_CLIENT_SYSTEM) - { - port = iterateMidiClient (client, clientInfo, forInput, - deviceNamesFound, deviceIndexToOpen); - if (port) - break; - } - } - } - } - } - - deviceNamesFound.appendNumbersToDuplicates (true, true); - - return port; -} - -} // namespace - -StringArray MidiOutput::getDevices() -{ - StringArray devices; - iterateMidiDevices (false, devices, -1); - return devices; -} - -int MidiOutput::getDefaultDeviceIndex() -{ - return 0; -} - -MidiOutput* MidiOutput::openDevice (int deviceIndex) -{ - MidiOutput* newDevice = nullptr; - - StringArray devices; - AlsaClient::Port* port = iterateMidiDevices (false, devices, deviceIndex); - - if (port == nullptr) - return nullptr; - - jassert (port->isValid()); - - newDevice = new MidiOutput (devices [deviceIndex]); - port->setupOutput(); - newDevice->internal = port; - - return newDevice; -} - -MidiOutput* MidiOutput::createNewDevice (const String& deviceName) -{ - MidiOutput* newDevice = nullptr; - - const AlsaClient::Ptr client (AlsaClient::getInstance()); - - AlsaClient::Port* port = client->createPort (deviceName, false, true); - - jassert (port->isValid()); - - newDevice = new MidiOutput (deviceName); - port->setupOutput(); - newDevice->internal = port; - - return newDevice; -} - -MidiOutput::~MidiOutput() -{ - stopBackgroundThread(); - - AlsaClient::Ptr client (AlsaClient::getInstance()); - client->deletePort (static_cast (internal)); -} - -void MidiOutput::sendMessageNow (const MidiMessage& message) -{ - static_cast (internal)->sendMessageNow (message); -} - -//============================================================================== -MidiInput::MidiInput (const String& nm) - : name (nm), internal (nullptr) -{ -} - -MidiInput::~MidiInput() -{ - stop(); - AlsaClient::Ptr client (AlsaClient::getInstance()); - client->deletePort (static_cast (internal)); -} - -void MidiInput::start() -{ - static_cast (internal)->enableCallback (true); -} - -void MidiInput::stop() -{ - static_cast (internal)->enableCallback (false); -} - -int MidiInput::getDefaultDeviceIndex() -{ - return 0; -} - -StringArray MidiInput::getDevices() -{ - StringArray devices; - iterateMidiDevices (true, devices, -1); - return devices; -} - -MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback) -{ - MidiInput* newDevice = nullptr; - - StringArray devices; - AlsaClient::Port* port = iterateMidiDevices (true, devices, deviceIndex); - - if (port == nullptr) - return nullptr; - - jassert (port->isValid()); - - newDevice = new MidiInput (devices [deviceIndex]); - port->setupInput (newDevice, callback); - newDevice->internal = port; - - return newDevice; -} - -MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) -{ - MidiInput* newDevice = nullptr; - - AlsaClient::Ptr client (AlsaClient::getInstance()); - - AlsaClient::Port* port = client->createPort (deviceName, true, true); - - jassert (port->isValid()); - - newDevice = new MidiInput (deviceName); - port->setupInput (newDevice, callback); - newDevice->internal = port; - - return newDevice; -} - - -//============================================================================== -#else - -// (These are just stub functions if ALSA is unavailable...) - -StringArray MidiOutput::getDevices() { return {}; } -int MidiOutput::getDefaultDeviceIndex() { return 0; } -MidiOutput* MidiOutput::openDevice (int) { return nullptr; } -MidiOutput* MidiOutput::createNewDevice (const String&) { return nullptr; } -MidiOutput::~MidiOutput() {} -void MidiOutput::sendMessageNow (const MidiMessage&) {} - -MidiInput::MidiInput (const String& nm) : name (nm), internal (nullptr) {} -MidiInput::~MidiInput() {} -void MidiInput::start() {} -void MidiInput::stop() {} -int MidiInput::getDefaultDeviceIndex() { return 0; } -StringArray MidiInput::getDevices() { return {}; } -MidiInput* MidiInput::openDevice (int, MidiInputCallback*) { return nullptr; } -MidiInput* MidiInput::createNewDevice (const String&, MidiInputCallback*) { return nullptr; } - -#endif - -} // namespace juce diff --git a/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp b/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp deleted file mode 100644 index 0bd8f3fbc..000000000 --- a/source/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp +++ /dev/null @@ -1,2073 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -#if JUCE_COREAUDIO_LOGGING_ENABLED - #define JUCE_COREAUDIOLOG(a) { String camsg ("CoreAudio: "); camsg << a; Logger::writeToLog (camsg); } -#else - #define JUCE_COREAUDIOLOG(a) -#endif - -#ifdef __clang__ - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wnonnull" // aovid some spurious 10.11 SDK warnings - - // The AudioHardwareService stuff was deprecated in 10.11 but there's no replacement yet, - // so we'll have to silence the warnings here and revisit it in a future OS version.. - #if ((defined (MAC_OS_X_VERSION_10_12) && MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_12) \ - || (defined (MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_11)) - #pragma clang diagnostic ignored "-Wdeprecated-declarations" - #endif -#endif - -//============================================================================== -struct SystemVol -{ - SystemVol (AudioObjectPropertySelector selector) noexcept - : outputDeviceID (kAudioObjectUnknown) - { - addr.mScope = kAudioObjectPropertyScopeGlobal; - addr.mElement = kAudioObjectPropertyElementMaster; - addr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; - - if (AudioHardwareServiceHasProperty (kAudioObjectSystemObject, &addr)) - { - UInt32 deviceIDSize = sizeof (outputDeviceID); - OSStatus status = AudioHardwareServiceGetPropertyData (kAudioObjectSystemObject, &addr, 0, - nullptr, &deviceIDSize, &outputDeviceID); - - if (status == noErr) - { - addr.mElement = kAudioObjectPropertyElementMaster; - addr.mSelector = selector; - addr.mScope = kAudioDevicePropertyScopeOutput; - - if (! AudioHardwareServiceHasProperty (outputDeviceID, &addr)) - outputDeviceID = kAudioObjectUnknown; - } - } - } - - float getGain() const noexcept - { - Float32 gain = 0; - - if (outputDeviceID != kAudioObjectUnknown) - { - UInt32 size = sizeof (gain); - AudioHardwareServiceGetPropertyData (outputDeviceID, &addr, - 0, nullptr, &size, &gain); - } - - return (float) gain; - } - - bool setGain (float gain) const noexcept - { - if (outputDeviceID != kAudioObjectUnknown && canSetVolume()) - { - Float32 newVolume = gain; - UInt32 size = sizeof (newVolume); - - return AudioHardwareServiceSetPropertyData (outputDeviceID, &addr, 0, nullptr, - size, &newVolume) == noErr; - } - - return false; - } - - bool isMuted() const noexcept - { - UInt32 muted = 0; - - if (outputDeviceID != kAudioObjectUnknown) - { - UInt32 size = sizeof (muted); - AudioHardwareServiceGetPropertyData (outputDeviceID, &addr, - 0, nullptr, &size, &muted); - } - - return muted != 0; - } - - bool setMuted (bool mute) const noexcept - { - if (outputDeviceID != kAudioObjectUnknown && canSetVolume()) - { - UInt32 newMute = mute ? 1 : 0; - UInt32 size = sizeof (newMute); - - return AudioHardwareServiceSetPropertyData (outputDeviceID, &addr, 0, nullptr, - size, &newMute) == noErr; - } - - return false; - } - -private: - AudioDeviceID outputDeviceID; - AudioObjectPropertyAddress addr; - - bool canSetVolume() const noexcept - { - Boolean isSettable = NO; - return AudioHardwareServiceIsPropertySettable (outputDeviceID, &addr, &isSettable) == noErr - && isSettable; - } -}; - -#ifdef __clang__ - #pragma clang diagnostic pop -#endif - -#define JUCE_SYSTEMAUDIOVOL_IMPLEMENTED 1 -float JUCE_CALLTYPE SystemAudioVolume::getGain() { return SystemVol (kAudioHardwareServiceDeviceProperty_VirtualMasterVolume).getGain(); } -bool JUCE_CALLTYPE SystemAudioVolume::setGain (float gain) { return SystemVol (kAudioHardwareServiceDeviceProperty_VirtualMasterVolume).setGain (gain); } -bool JUCE_CALLTYPE SystemAudioVolume::isMuted() { return SystemVol (kAudioDevicePropertyMute).isMuted(); } -bool JUCE_CALLTYPE SystemAudioVolume::setMuted (bool mute) { return SystemVol (kAudioDevicePropertyMute).setMuted (mute); } - -//============================================================================== -struct CoreAudioClasses -{ - -class CoreAudioIODeviceType; -class CoreAudioIODevice; - -//============================================================================== -class CoreAudioInternal : private Timer -{ -public: - CoreAudioInternal (CoreAudioIODevice& d, AudioDeviceID id, bool input, bool output) - : owner (d), - deviceID (id), - isInputDevice (input), - isOutputDevice (output) - { - jassert (deviceID != 0); - - updateDetailsFromDevice(); - - AudioObjectPropertyAddress pa; - pa.mSelector = kAudioObjectPropertySelectorWildcard; - pa.mScope = kAudioObjectPropertyScopeWildcard; - pa.mElement = kAudioObjectPropertyElementWildcard; - - AudioObjectAddPropertyListener (deviceID, &pa, deviceListenerProc, this); - } - - ~CoreAudioInternal() - { - AudioObjectPropertyAddress pa; - pa.mSelector = kAudioObjectPropertySelectorWildcard; - pa.mScope = kAudioObjectPropertyScopeWildcard; - pa.mElement = kAudioObjectPropertyElementWildcard; - - AudioObjectRemovePropertyListener (deviceID, &pa, deviceListenerProc, this); - - stop (false); - } - - void allocateTempBuffers() - { - const int tempBufSize = bufferSize + 4; - audioBuffer.calloc ((size_t) ((numInputChans + numOutputChans) * tempBufSize)); - - tempInputBuffers.calloc ((size_t) numInputChans + 2); - tempOutputBuffers.calloc ((size_t) numOutputChans + 2); - - int count = 0; - for (int i = 0; i < numInputChans; ++i) tempInputBuffers[i] = audioBuffer + count++ * tempBufSize; - for (int i = 0; i < numOutputChans; ++i) tempOutputBuffers[i] = audioBuffer + count++ * tempBufSize; - } - - struct CallbackDetailsForChannel - { - int streamNum; - int dataOffsetSamples; - int dataStrideSamples; - }; - - // returns the number of actual available channels - StringArray getChannelInfo (const bool input, Array& newChannelInfo) const - { - StringArray newNames; - int chanNum = 0; - UInt32 size; - - AudioObjectPropertyAddress pa; - pa.mSelector = kAudioDevicePropertyStreamConfiguration; - pa.mScope = input ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; - pa.mElement = kAudioObjectPropertyElementMaster; - - if (OK (AudioObjectGetPropertyDataSize (deviceID, &pa, 0, nullptr, &size))) - { - HeapBlock bufList; - bufList.calloc (size, 1); - - if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, bufList))) - { - const int numStreams = (int) bufList->mNumberBuffers; - - for (int i = 0; i < numStreams; ++i) - { - auto& b = bufList->mBuffers[i]; - - for (unsigned int j = 0; j < b.mNumberChannels; ++j) - { - String name; - NSString* nameNSString = nil; - size = sizeof (nameNSString); - - pa.mSelector = kAudioObjectPropertyElementName; - pa.mElement = (AudioObjectPropertyElement) chanNum + 1; - - if (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &nameNSString) == noErr) - { - name = nsStringToJuce (nameNSString); - [nameNSString release]; - } - - if ((input ? activeInputChans : activeOutputChans) [chanNum]) - { - CallbackDetailsForChannel info = { i, (int) j, (int) b.mNumberChannels }; - newChannelInfo.add (info); - } - - if (name.isEmpty()) - name << (input ? "Input " : "Output ") << (chanNum + 1); - - newNames.add (name); - ++chanNum; - } - } - } - } - - return newNames; - } - - Array getSampleRatesFromDevice() const - { - Array newSampleRates; - - AudioObjectPropertyAddress pa; - pa.mScope = kAudioObjectPropertyScopeWildcard; - pa.mElement = kAudioObjectPropertyElementMaster; - pa.mSelector = kAudioDevicePropertyAvailableNominalSampleRates; - UInt32 size = 0; - - if (OK (AudioObjectGetPropertyDataSize (deviceID, &pa, 0, nullptr, &size))) - { - HeapBlock ranges; - ranges.calloc (size, 1); - - if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, ranges))) - { - static const double possibleRates[] = { 44100.0, 48000.0, 88200.0, 96000.0, 176400.0, 192000.0, 384000.0 }; - - for (int i = 0; i < numElementsInArray (possibleRates); ++i) - { - for (int j = size / (int) sizeof (AudioValueRange); --j >= 0;) - { - if (possibleRates[i] >= ranges[j].mMinimum - 2 && possibleRates[i] <= ranges[j].mMaximum + 2) - { - newSampleRates.add (possibleRates[i]); - break; - } - } - } - } - } - - if (newSampleRates.size() == 0 && sampleRate > 0) - newSampleRates.add (sampleRate); - - return newSampleRates; - } - - Array getBufferSizesFromDevice() const - { - Array newBufferSizes; - - AudioObjectPropertyAddress pa; - pa.mScope = kAudioObjectPropertyScopeWildcard; - pa.mElement = kAudioObjectPropertyElementMaster; - pa.mSelector = kAudioDevicePropertyBufferFrameSizeRange; - UInt32 size = 0; - - if (OK (AudioObjectGetPropertyDataSize (deviceID, &pa, 0, nullptr, &size))) - { - HeapBlock ranges; - ranges.calloc (size, 1); - - if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, ranges))) - { - newBufferSizes.add ((int) (ranges[0].mMinimum + 15) & ~15); - - for (int i = 32; i <= 2048; i += 32) - { - for (int j = size / (int) sizeof (AudioValueRange); --j >= 0;) - { - if (i >= ranges[j].mMinimum && i <= ranges[j].mMaximum) - { - newBufferSizes.addIfNotAlreadyThere (i); - break; - } - } - } - - if (bufferSize > 0) - newBufferSizes.addIfNotAlreadyThere (bufferSize); - } - } - - if (newBufferSizes.size() == 0 && bufferSize > 0) - newBufferSizes.add (bufferSize); - - return newBufferSizes; - } - - int getLatencyFromDevice (AudioObjectPropertyScope scope) const - { - UInt32 latency = 0; - UInt32 size = sizeof (latency); - AudioObjectPropertyAddress pa; - pa.mElement = kAudioObjectPropertyElementMaster; - pa.mSelector = kAudioDevicePropertyLatency; - pa.mScope = scope; - AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &latency); - - UInt32 safetyOffset = 0; - size = sizeof (safetyOffset); - pa.mSelector = kAudioDevicePropertySafetyOffset; - AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &safetyOffset); - - return (int) (latency + safetyOffset); - } - - int getBitDepthFromDevice (AudioObjectPropertyScope scope) const - { - AudioObjectPropertyAddress pa; - pa.mElement = kAudioObjectPropertyElementMaster; - pa.mSelector = kAudioStreamPropertyPhysicalFormat; - pa.mScope = scope; - - AudioStreamBasicDescription asbd; - UInt32 size = sizeof (asbd); - - if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &asbd))) - return (int) asbd.mBitsPerChannel; - - return 0; - } - - int getFrameSizeFromDevice() const - { - AudioObjectPropertyAddress pa; - pa.mScope = kAudioObjectPropertyScopeWildcard; - pa.mElement = kAudioObjectPropertyElementMaster; - pa.mSelector = kAudioDevicePropertyBufferFrameSize; - - UInt32 framesPerBuf = (UInt32) bufferSize; - UInt32 size = sizeof (framesPerBuf); - AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &framesPerBuf); - return (int) framesPerBuf; - } - - bool isDeviceAlive() const - { - AudioObjectPropertyAddress pa; - pa.mScope = kAudioObjectPropertyScopeWildcard; - pa.mElement = kAudioObjectPropertyElementMaster; - pa.mSelector = kAudioDevicePropertyDeviceIsAlive; - - UInt32 isAlive = 0; - UInt32 size = sizeof (isAlive); - return deviceID != 0 - && OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &isAlive)) - && isAlive != 0; - } - - bool updateDetailsFromDevice() - { - stopTimer(); - - if (! isDeviceAlive()) - return false; - - // this collects all the new details from the device without any locking, then - // locks + swaps them afterwards. - - const double newSampleRate = getNominalSampleRate(); - const int newBufferSize = getFrameSizeFromDevice(); - - Array newBufferSizes = getBufferSizesFromDevice(); - Array newSampleRates = getSampleRatesFromDevice(); - - const int newInputLatency = getLatencyFromDevice (kAudioDevicePropertyScopeInput); - const int newOutputLatency = getLatencyFromDevice (kAudioDevicePropertyScopeOutput); - - Array newInChans, newOutChans; - auto newInNames = isInputDevice ? getChannelInfo (true, newInChans) : StringArray(); - auto newOutNames = isOutputDevice ? getChannelInfo (false, newOutChans) : StringArray(); - - const int newBitDepth = jmax (getBitDepthFromDevice (kAudioDevicePropertyScopeInput), - getBitDepthFromDevice (kAudioDevicePropertyScopeOutput)); - - { - const ScopedLock sl (callbackLock); - - bitDepth = newBitDepth > 0 ? newBitDepth : 32; - - if (newSampleRate > 0) - sampleRate = newSampleRate; - - inputLatency = newInputLatency; - outputLatency = newOutputLatency; - bufferSize = newBufferSize; - - sampleRates.swapWith (newSampleRates); - bufferSizes.swapWith (newBufferSizes); - - inChanNames.swapWith (newInNames); - outChanNames.swapWith (newOutNames); - - inputChannelInfo.swapWith (newInChans); - outputChannelInfo.swapWith (newOutChans); - - allocateTempBuffers(); - } - - return true; - } - - //============================================================================== - StringArray getSources (bool input) - { - StringArray s; - HeapBlock types; - const int num = getAllDataSourcesForDevice (deviceID, types); - - for (int i = 0; i < num; ++i) - { - AudioValueTranslation avt; - char buffer[256]; - - avt.mInputData = &(types[i]); - avt.mInputDataSize = sizeof (UInt32); - avt.mOutputData = buffer; - avt.mOutputDataSize = 256; - - UInt32 transSize = sizeof (avt); - - AudioObjectPropertyAddress pa; - pa.mSelector = kAudioDevicePropertyDataSourceNameForID; - pa.mScope = input ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; - pa.mElement = kAudioObjectPropertyElementMaster; - - if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &transSize, &avt))) - s.add (buffer); - } - - return s; - } - - int getCurrentSourceIndex (bool input) const - { - OSType currentSourceID = 0; - UInt32 size = sizeof (currentSourceID); - int result = -1; - - AudioObjectPropertyAddress pa; - pa.mSelector = kAudioDevicePropertyDataSource; - pa.mScope = input ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; - pa.mElement = kAudioObjectPropertyElementMaster; - - if (deviceID != 0) - { - if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, ¤tSourceID))) - { - HeapBlock types; - const int num = getAllDataSourcesForDevice (deviceID, types); - - for (int i = 0; i < num; ++i) - { - if (types[num] == currentSourceID) - { - result = i; - break; - } - } - } - } - - return result; - } - - void setCurrentSourceIndex (int index, bool input) - { - if (deviceID != 0) - { - HeapBlock types; - const int num = getAllDataSourcesForDevice (deviceID, types); - - if (isPositiveAndBelow (index, num)) - { - AudioObjectPropertyAddress pa; - pa.mSelector = kAudioDevicePropertyDataSource; - pa.mScope = input ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; - pa.mElement = kAudioObjectPropertyElementMaster; - - OSType typeId = types[index]; - - OK (AudioObjectSetPropertyData (deviceID, &pa, 0, 0, sizeof (typeId), &typeId)); - } - } - } - - double getNominalSampleRate() const - { - AudioObjectPropertyAddress pa; - pa.mSelector = kAudioDevicePropertyNominalSampleRate; - pa.mScope = kAudioObjectPropertyScopeGlobal; - pa.mElement = kAudioObjectPropertyElementMaster; - Float64 sr = 0; - UInt32 size = (UInt32) sizeof (sr); - return OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &sr)) ? (double) sr : 0.0; - } - - bool setNominalSampleRate (double newSampleRate) const - { - if (std::abs (getNominalSampleRate() - newSampleRate) < 1.0) - return true; - - AudioObjectPropertyAddress pa; - pa.mSelector = kAudioDevicePropertyNominalSampleRate; - pa.mScope = kAudioObjectPropertyScopeGlobal; - pa.mElement = kAudioObjectPropertyElementMaster; - Float64 sr = newSampleRate; - return OK (AudioObjectSetPropertyData (deviceID, &pa, 0, 0, sizeof (sr), &sr)); - } - - //============================================================================== - String reopen (const BigInteger& inputChannels, - const BigInteger& outputChannels, - double newSampleRate, int bufferSizeSamples) - { - String error; - callbacksAllowed = false; - stopTimer(); - - stop (false); - - updateDetailsFromDevice(); - - activeInputChans = inputChannels; - activeInputChans.setRange (inChanNames.size(), - activeInputChans.getHighestBit() + 1 - inChanNames.size(), - false); - - activeOutputChans = outputChannels; - activeOutputChans.setRange (outChanNames.size(), - activeOutputChans.getHighestBit() + 1 - outChanNames.size(), - false); - - numInputChans = activeInputChans.countNumberOfSetBits(); - numOutputChans = activeOutputChans.countNumberOfSetBits(); - - if (! setNominalSampleRate (newSampleRate)) - { - updateDetailsFromDevice(); - error = "Couldn't change sample rate"; - } - else - { - // change buffer size - AudioObjectPropertyAddress pa; - pa.mSelector = kAudioDevicePropertyBufferFrameSize; - pa.mScope = kAudioObjectPropertyScopeGlobal; - pa.mElement = kAudioObjectPropertyElementMaster; - UInt32 framesPerBuf = (UInt32) bufferSizeSamples; - - if (! OK (AudioObjectSetPropertyData (deviceID, &pa, 0, 0, sizeof (framesPerBuf), &framesPerBuf))) - { - updateDetailsFromDevice(); - error = "Couldn't change buffer size"; - } - else - { - // Annoyingly, after changing the rate and buffer size, some devices fail to - // correctly report their new settings until some random time in the future, so - // after calling updateDetailsFromDevice, we need to manually bodge these values - // to make sure we're using the correct numbers.. - updateDetailsFromDevice(); - sampleRate = newSampleRate; - bufferSize = bufferSizeSamples; - - if (sampleRates.size() == 0) - error = "Device has no available sample-rates"; - else if (bufferSizes.size() == 0) - error = "Device has no available buffer-sizes"; - } - } - - callbacksAllowed = true; - return error; - } - - bool start() - { - if (! started) - { - callback = nullptr; - - if (deviceID != 0) - { - if (OK (AudioDeviceCreateIOProcID (deviceID, audioIOProc, this, &audioProcID))) - { - if (OK (AudioDeviceStart (deviceID, audioIOProc))) - { - started = true; - } - else - { - OK (AudioDeviceDestroyIOProcID (deviceID, audioProcID)); - audioProcID = 0; - } - } - } - } - - return started; - } - - void setCallback (AudioIODeviceCallback* cb) - { - const ScopedLock sl (callbackLock); - callback = cb; - } - - void stop (bool leaveInterruptRunning) - { - { - const ScopedLock sl (callbackLock); - callback = nullptr; - } - - if (started - && (deviceID != 0) - && ! leaveInterruptRunning) - { - OK (AudioDeviceStop (deviceID, audioIOProc)); - OK (AudioDeviceDestroyIOProcID (deviceID, audioProcID)); - audioProcID = 0; - - started = false; - - { const ScopedLock sl (callbackLock); } - - // wait until it's definitely stopped calling back.. - for (int i = 40; --i >= 0;) - { - Thread::sleep (50); - - UInt32 running = 0; - UInt32 size = sizeof (running); - - AudioObjectPropertyAddress pa; - pa.mSelector = kAudioDevicePropertyDeviceIsRunning; - pa.mScope = kAudioObjectPropertyScopeWildcard; - pa.mElement = kAudioObjectPropertyElementMaster; - - OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &running)); - - if (running == 0) - break; - } - - const ScopedLock sl (callbackLock); - } - } - - double getSampleRate() const { return sampleRate; } - int getBufferSize() const { return bufferSize; } - - void audioCallback (const AudioBufferList* inInputData, - AudioBufferList* outOutputData) - { - const ScopedLock sl (callbackLock); - - if (callback != nullptr) - { - for (int i = numInputChans; --i >= 0;) - { - const CallbackDetailsForChannel& info = inputChannelInfo.getReference(i); - float* dest = tempInputBuffers [i]; - const float* src = ((const float*) inInputData->mBuffers[info.streamNum].mData) - + info.dataOffsetSamples; - const int stride = info.dataStrideSamples; - - if (stride != 0) // if this is zero, info is invalid - { - for (int j = bufferSize; --j >= 0;) - { - *dest++ = *src; - src += stride; - } - } - } - - callback->audioDeviceIOCallback (const_cast (tempInputBuffers.get()), - numInputChans, - tempOutputBuffers, - numOutputChans, - bufferSize); - - for (int i = numOutputChans; --i >= 0;) - { - const CallbackDetailsForChannel& info = outputChannelInfo.getReference(i); - const float* src = tempOutputBuffers [i]; - float* dest = ((float*) outOutputData->mBuffers[info.streamNum].mData) - + info.dataOffsetSamples; - const int stride = info.dataStrideSamples; - - if (stride != 0) // if this is zero, info is invalid - { - for (int j = bufferSize; --j >= 0;) - { - *dest = *src++; - dest += stride; - } - } - } - } - else - { - for (UInt32 i = 0; i < outOutputData->mNumberBuffers; ++i) - zeromem (outOutputData->mBuffers[i].mData, - outOutputData->mBuffers[i].mDataByteSize); - } - } - - // called by callbacks - void deviceDetailsChanged() - { - if (callbacksAllowed) - startTimer (100); - } - - void timerCallback() override - { - JUCE_COREAUDIOLOG ("Device changed"); - - stopTimer(); - const double oldSampleRate = sampleRate; - const int oldBufferSize = bufferSize; - - if (! updateDetailsFromDevice()) - owner.stop(); - else if (oldBufferSize != bufferSize || oldSampleRate != sampleRate) - owner.restart(); - } - - //============================================================================== - CoreAudioIODevice& owner; - int inputLatency = 0; - int outputLatency = 0; - int bitDepth = 32; - int xruns = 0; - BigInteger activeInputChans, activeOutputChans; - StringArray inChanNames, outChanNames; - Array sampleRates; - Array bufferSizes; - AudioIODeviceCallback* callback = nullptr; - AudioDeviceIOProcID audioProcID = 0; - -private: - CriticalSection callbackLock; - AudioDeviceID deviceID; - bool started = false; - double sampleRate = 0; - int bufferSize = 512; - HeapBlock audioBuffer; - int numInputChans = 0; - int numOutputChans = 0; - bool callbacksAllowed = true; - const bool isInputDevice, isOutputDevice; - - Array inputChannelInfo, outputChannelInfo; - HeapBlock tempInputBuffers, tempOutputBuffers; - - //============================================================================== - static OSStatus audioIOProc (AudioDeviceID /*inDevice*/, - const AudioTimeStamp* /*inNow*/, - const AudioBufferList* inInputData, - const AudioTimeStamp* /*inInputTime*/, - AudioBufferList* outOutputData, - const AudioTimeStamp* /*inOutputTime*/, - void* device) - { - static_cast (device)->audioCallback (inInputData, outOutputData); - return noErr; - } - - static OSStatus deviceListenerProc (AudioDeviceID /*inDevice*/, UInt32 /*inLine*/, const AudioObjectPropertyAddress* pa, void* inClientData) - { - CoreAudioInternal* const intern = static_cast (inClientData); - - switch (pa->mSelector) - { - case kAudioDeviceProcessorOverload: - intern->xruns++; - break; - case kAudioDevicePropertyBufferSize: - case kAudioDevicePropertyBufferFrameSize: - case kAudioDevicePropertyNominalSampleRate: - case kAudioDevicePropertyStreamFormat: - case kAudioDevicePropertyDeviceIsAlive: - case kAudioStreamPropertyPhysicalFormat: - intern->deviceDetailsChanged(); - break; - - case kAudioObjectPropertyOwnedObjects: - intern->stop (false); - intern->owner.deviceType.triggerAsyncAudioDeviceListChange(); - break; - - case kAudioDevicePropertyBufferSizeRange: - case kAudioDevicePropertyVolumeScalar: - case kAudioDevicePropertyMute: - case kAudioDevicePropertyPlayThru: - case kAudioDevicePropertyDataSource: - case kAudioDevicePropertyDeviceIsRunning: - break; - } - - return noErr; - } - - //============================================================================== - static int getAllDataSourcesForDevice (AudioDeviceID deviceID, HeapBlock& types) - { - AudioObjectPropertyAddress pa; - pa.mSelector = kAudioDevicePropertyDataSources; - pa.mScope = kAudioObjectPropertyScopeWildcard; - pa.mElement = kAudioObjectPropertyElementMaster; - UInt32 size = 0; - - if (deviceID != 0 - && AudioObjectGetPropertyDataSize (deviceID, &pa, 0, nullptr, &size) == noErr) - { - types.calloc (size, 1); - - if (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, types) == noErr) - return size / (int) sizeof (OSType); - } - - return 0; - } - - bool OK (const OSStatus errorCode) const - { - if (errorCode == noErr) - return true; - - const String errorMessage ("CoreAudio error: " + String::toHexString ((int) errorCode)); - JUCE_COREAUDIOLOG (errorMessage); - - if (callback != nullptr) - callback->audioDeviceError (errorMessage); - - return false; - } - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreAudioInternal) -}; - - -//============================================================================== -class CoreAudioIODevice : public AudioIODevice -{ -public: - CoreAudioIODevice (CoreAudioIODeviceType& dt, - const String& deviceName, - AudioDeviceID inputDeviceId, const int inputIndex_, - AudioDeviceID outputDeviceId, const int outputIndex_) - : AudioIODevice (deviceName, "CoreAudio"), - deviceType (dt), - inputIndex (inputIndex_), - outputIndex (outputIndex_), - isOpen_ (false), - isStarted (false) - { - CoreAudioInternal* device = nullptr; - if (outputDeviceId == 0 || outputDeviceId == inputDeviceId) - { - jassert (inputDeviceId != 0); - device = new CoreAudioInternal (*this, inputDeviceId, true, outputDeviceId != 0); - } - else - { - device = new CoreAudioInternal (*this, outputDeviceId, false, true); - } - jassert (device != nullptr); - - internal = device; - - AudioObjectPropertyAddress pa; - pa.mSelector = kAudioObjectPropertySelectorWildcard; - pa.mScope = kAudioObjectPropertyScopeWildcard; - pa.mElement = kAudioObjectPropertyElementWildcard; - - AudioObjectAddPropertyListener (kAudioObjectSystemObject, &pa, hardwareListenerProc, internal); - } - - ~CoreAudioIODevice() - { - close(); - - AudioObjectPropertyAddress pa; - pa.mSelector = kAudioObjectPropertySelectorWildcard; - pa.mScope = kAudioObjectPropertyScopeWildcard; - pa.mElement = kAudioObjectPropertyElementWildcard; - - AudioObjectRemovePropertyListener (kAudioObjectSystemObject, &pa, hardwareListenerProc, internal); - } - - StringArray getOutputChannelNames() override { return internal->outChanNames; } - StringArray getInputChannelNames() override { return internal->inChanNames; } - - bool isOpen() override { return isOpen_; } - - Array getAvailableSampleRates() override { return internal->sampleRates; } - Array getAvailableBufferSizes() override { return internal->bufferSizes; } - - double getCurrentSampleRate() override { return internal->getSampleRate(); } - int getCurrentBitDepth() override { return internal->bitDepth; } - int getCurrentBufferSizeSamples() override { return internal->getBufferSize(); } - int getXRunCount() const noexcept override { return internal->xruns; } - - int getDefaultBufferSize() override - { - int best = 0; - - for (int i = 0; best < 512 && i < internal->bufferSizes.size(); ++i) - best = internal->bufferSizes.getUnchecked(i); - - if (best == 0) - best = 512; - - return best; - } - - String open (const BigInteger& inputChannels, - const BigInteger& outputChannels, - double sampleRate, int bufferSizeSamples) override - { - isOpen_ = true; - - internal->xruns = 0; - if (bufferSizeSamples <= 0) - bufferSizeSamples = getDefaultBufferSize(); - - lastError = internal->reopen (inputChannels, outputChannels, sampleRate, bufferSizeSamples); - - JUCE_COREAUDIOLOG ("Opened: " << getName()); - JUCE_COREAUDIOLOG ("Latencies: " << getInputLatencyInSamples() << ' ' << getOutputLatencyInSamples()); - - isOpen_ = lastError.isEmpty(); - return lastError; - } - - void close() override - { - isOpen_ = false; - internal->stop (false); - } - - BigInteger getActiveOutputChannels() const override { return internal->activeOutputChans; } - BigInteger getActiveInputChannels() const override { return internal->activeInputChans; } - - int getOutputLatencyInSamples() override - { - // this seems like a good guess at getting the latency right - comparing - // this with a round-trip measurement, it gets it to within a few millisecs - // for the built-in mac soundcard - return internal->outputLatency; - } - - int getInputLatencyInSamples() override - { - return internal->inputLatency; - } - - void start (AudioIODeviceCallback* callback) override - { - if (! isStarted) - { - if (callback != nullptr) - callback->audioDeviceAboutToStart (this); - - isStarted = internal->start(); - - if (isStarted) - internal->setCallback (callback); - } - } - - void stop() override - { - if (isStarted) - { - AudioIODeviceCallback* const lastCallback = internal->callback; - - isStarted = false; - internal->stop (true); - - if (lastCallback != nullptr) - lastCallback->audioDeviceStopped(); - } - } - - bool isPlaying() override - { - if (internal->callback == nullptr) - isStarted = false; - - return isStarted; - } - - String getLastError() override - { - return lastError; - } - - void audioDeviceListChanged() - { - deviceType.audioDeviceListChanged(); - } - - void restart() - { - JUCE_COREAUDIOLOG ("Restarting"); - AudioIODeviceCallback* oldCallback = internal->callback; - stop(); - start (oldCallback); - } - - bool setCurrentSampleRate (double newSampleRate) - { - return internal->setNominalSampleRate (newSampleRate); - } - - CoreAudioIODeviceType& deviceType; - int inputIndex, outputIndex; - -private: - ScopedPointer internal; - bool isOpen_, isStarted; - String lastError; - - static OSStatus hardwareListenerProc (AudioDeviceID /*inDevice*/, UInt32 /*inLine*/, const AudioObjectPropertyAddress* pa, void* inClientData) - { - switch (pa->mSelector) - { - case kAudioHardwarePropertyDevices: - static_cast (inClientData)->deviceDetailsChanged(); - break; - - case kAudioHardwarePropertyDefaultOutputDevice: - case kAudioHardwarePropertyDefaultInputDevice: - case kAudioHardwarePropertyDefaultSystemOutputDevice: - break; - } - - return noErr; - } - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreAudioIODevice) -}; - -//============================================================================== -class AudioIODeviceCombiner : public AudioIODevice, - private Thread -{ -public: - AudioIODeviceCombiner (const String& deviceName, CoreAudioIODeviceType& deviceType) - : AudioIODevice (deviceName, "CoreAudio"), - Thread (deviceName), - owner (deviceType) - { - } - - ~AudioIODeviceCombiner() - { - close(); - devices.clear(); - } - - void addDevice (CoreAudioIODevice* device, bool useInputs, bool useOutputs) - { - jassert (device != nullptr); - jassert (! isOpen()); - jassert (! device->isOpen()); - devices.add (new DeviceWrapper (*this, device, useInputs, useOutputs)); - - if (currentSampleRate == 0) - currentSampleRate = device->getCurrentSampleRate(); - - if (currentBufferSize == 0) - currentBufferSize = device->getCurrentBufferSizeSamples(); - } - - Array getDevices() const - { - Array devs; - - for (int i = 0; i < devices.size(); ++i) - devs.add (devices.getUnchecked(i)->device); - - return devs; - } - - StringArray getOutputChannelNames() override - { - StringArray names; - - for (int i = 0; i < devices.size(); ++i) - names.addArray (devices.getUnchecked(i)->getOutputChannelNames()); - - names.appendNumbersToDuplicates (false, true); - return names; - } - - StringArray getInputChannelNames() override - { - StringArray names; - - for (int i = 0; i < devices.size(); ++i) - names.addArray (devices.getUnchecked(i)->getInputChannelNames()); - - names.appendNumbersToDuplicates (false, true); - return names; - } - - Array getAvailableSampleRates() override - { - Array commonRates; - - for (int i = 0; i < devices.size(); ++i) - { - Array rates (devices.getUnchecked(i)->device->getAvailableSampleRates()); - - if (i == 0) - commonRates = rates; - else - commonRates.removeValuesNotIn (rates); - } - - return commonRates; - } - - Array getAvailableBufferSizes() override - { - Array commonSizes; - - for (int i = 0; i < devices.size(); ++i) - { - Array sizes (devices.getUnchecked(i)->device->getAvailableBufferSizes()); - - if (i == 0) - commonSizes = sizes; - else - commonSizes.removeValuesNotIn (sizes); - } - - return commonSizes; - } - - bool isOpen() override { return active; } - bool isPlaying() override { return callback != nullptr; } - double getCurrentSampleRate() override { return currentSampleRate; } - int getCurrentBufferSizeSamples() override { return currentBufferSize; } - - int getCurrentBitDepth() override - { - int depth = 32; - - for (int i = 0; i < devices.size(); ++i) - depth = jmin (depth, devices.getUnchecked(i)->device->getCurrentBitDepth()); - - return depth; - } - - int getDefaultBufferSize() override - { - int size = 0; - - for (int i = 0; i < devices.size(); ++i) - size = jmax (size, devices.getUnchecked(i)->device->getDefaultBufferSize()); - - return size; - } - - String open (const BigInteger& inputChannels, - const BigInteger& outputChannels, - double sampleRate, int bufferSize) override - { - close(); - active = true; - - if (bufferSize <= 0) - bufferSize = getDefaultBufferSize(); - - if (sampleRate <= 0) - { - Array rates (getAvailableSampleRates()); - - for (int i = 0; i < rates.size() && sampleRate < 44100.0; ++i) - sampleRate = rates.getUnchecked(i); - } - - currentSampleRate = sampleRate; - currentBufferSize = bufferSize; - - const int fifoSize = bufferSize * 3 + 1; - int totalInputChanIndex = 0, totalOutputChanIndex = 0; - int chanIndex = 0; - - for (int i = 0; i < devices.size(); ++i) - { - DeviceWrapper& d = *devices.getUnchecked(i); - - BigInteger ins (inputChannels >> totalInputChanIndex); - BigInteger outs (outputChannels >> totalOutputChanIndex); - - int numIns = d.getInputChannelNames().size(); - int numOuts = d.getOutputChannelNames().size(); - - totalInputChanIndex += numIns; - totalOutputChanIndex += numOuts; - - String err = d.open (ins, outs, sampleRate, bufferSize, - chanIndex, fifoSize); - - if (err.isNotEmpty()) - { - close(); - lastError = err; - return err; - } - - chanIndex += d.numInputChans + d.numOutputChans; - } - - fifos.setSize (chanIndex, fifoSize); - fifos.clear(); - startThread (9); - - return {}; - } - - void close() override - { - stop(); - stopThread (10000); - fifos.clear(); - active = false; - - for (int i = 0; i < devices.size(); ++i) - devices.getUnchecked(i)->close(); - } - - BigInteger getActiveOutputChannels() const override - { - BigInteger chans; - int start = 0; - - for (int i = 0; i < devices.size(); ++i) - { - const int numChans = devices.getUnchecked(i)->getOutputChannelNames().size(); - - if (numChans > 0) - { - chans |= (devices.getUnchecked(i)->device->getActiveOutputChannels() << start); - start += numChans; - } - } - - return chans; - } - - BigInteger getActiveInputChannels() const override - { - BigInteger chans; - int start = 0; - - for (int i = 0; i < devices.size(); ++i) - { - const int numChans = devices.getUnchecked(i)->getInputChannelNames().size(); - - if (numChans > 0) - { - chans |= (devices.getUnchecked(i)->device->getActiveInputChannels() << start); - start += numChans; - } - } - - return chans; - } - - int getOutputLatencyInSamples() override - { - int lat = 0; - - for (int i = 0; i < devices.size(); ++i) - lat = jmax (lat, devices.getUnchecked(i)->device->getOutputLatencyInSamples()); - - return lat + currentBufferSize * 2; - } - - int getInputLatencyInSamples() override - { - int lat = 0; - - for (int i = 0; i < devices.size(); ++i) - lat = jmax (lat, devices.getUnchecked(i)->device->getInputLatencyInSamples()); - - return lat + currentBufferSize * 2; - } - - void start (AudioIODeviceCallback* newCallback) override - { - if (callback != newCallback) - { - stop(); - fifos.clear(); - - for (int i = 0; i < devices.size(); ++i) - devices.getUnchecked(i)->start(); - - if (newCallback != nullptr) - newCallback->audioDeviceAboutToStart (this); - - const ScopedLock sl (callbackLock); - callback = newCallback; - } - } - - void stop() override { shutdown ({}); } - - String getLastError() override - { - return lastError; - } - -private: - CoreAudioIODeviceType& owner; - CriticalSection callbackLock; - AudioIODeviceCallback* callback = nullptr; - double currentSampleRate = 0; - int currentBufferSize = 0; - bool active = false; - String lastError; - - AudioSampleBuffer fifos; - - void run() override - { - const int numSamples = currentBufferSize; - - AudioSampleBuffer buffer (fifos.getNumChannels(), numSamples); - buffer.clear(); - - Array inputChans; - Array outputChans; - - for (int i = 0; i < devices.size(); ++i) - { - DeviceWrapper& d = *devices.getUnchecked(i); - - for (int j = 0; j < d.numInputChans; ++j) inputChans.add (buffer.getReadPointer (d.inputIndex + j)); - for (int j = 0; j < d.numOutputChans; ++j) outputChans.add (buffer.getWritePointer (d.outputIndex + j)); - } - - const int numInputChans = inputChans.size(); - const int numOutputChans = outputChans.size(); - - inputChans.add (nullptr); - outputChans.add (nullptr); - - const int blockSizeMs = jmax (1, (int) (1000 * numSamples / currentSampleRate)); - - jassert (numInputChans + numOutputChans == buffer.getNumChannels()); - - while (! threadShouldExit()) - { - readInput (buffer, numSamples, blockSizeMs); - - bool didCallback = true; - - { - const ScopedLock sl (callbackLock); - - if (callback != nullptr) - callback->audioDeviceIOCallback ((const float**) inputChans.getRawDataPointer(), numInputChans, - outputChans.getRawDataPointer(), numOutputChans, numSamples); - else - didCallback = false; - } - - if (didCallback) - { - pushOutputData (buffer, numSamples, blockSizeMs); - } - else - { - for (int i = 0; i < numOutputChans; ++i) - FloatVectorOperations::clear (outputChans[i], numSamples); - - reset(); - } - } - } - - void shutdown (const String& error) - { - AudioIODeviceCallback* lastCallback = nullptr; - - { - const ScopedLock sl (callbackLock); - std::swap (callback, lastCallback); - } - - for (int i = 0; i < devices.size(); ++i) - devices.getUnchecked(i)->device->stop(); - - if (lastCallback != nullptr) - { - if (error.isNotEmpty()) - lastCallback->audioDeviceError (error); - else - lastCallback->audioDeviceStopped(); - } - } - - void reset() - { - for (int i = 0; i < devices.size(); ++i) - devices.getUnchecked(i)->reset(); - } - - void underrun() - { - } - - void readInput (AudioSampleBuffer& buffer, const int numSamples, const int blockSizeMs) - { - for (int i = 0; i < devices.size(); ++i) - { - DeviceWrapper& d = *devices.getUnchecked(i); - d.done = (d.numInputChans == 0); - } - - for (int tries = 5;;) - { - bool anyRemaining = false; - - for (int i = 0; i < devices.size(); ++i) - { - DeviceWrapper& d = *devices.getUnchecked(i); - - if (! d.done) - { - if (d.isInputReady (numSamples)) - { - d.readInput (buffer, numSamples); - d.done = true; - } - else - anyRemaining = true; - } - } - - if (! anyRemaining) - return; - - if (--tries == 0) - break; - - wait (blockSizeMs); - } - - for (int j = 0; j < devices.size(); ++j) - { - DeviceWrapper& d = *devices.getUnchecked(j); - - if (! d.done) - for (int i = 0; i < d.numInputChans; ++i) - buffer.clear (d.inputIndex + i, 0, numSamples); - } - } - - void pushOutputData (AudioSampleBuffer& buffer, const int numSamples, const int blockSizeMs) - { - for (int i = 0; i < devices.size(); ++i) - { - DeviceWrapper& d = *devices.getUnchecked(i); - d.done = (d.numOutputChans == 0); - } - - for (int tries = 5;;) - { - bool anyRemaining = false; - - for (int i = 0; i < devices.size(); ++i) - { - DeviceWrapper& d = *devices.getUnchecked(i); - - if (! d.done) - { - if (d.isOutputReady (numSamples)) - { - d.pushOutputData (buffer, numSamples); - d.done = true; - } - else - anyRemaining = true; - } - } - - if ((! anyRemaining) || --tries == 0) - return; - - wait (blockSizeMs); - } - } - - void handleAudioDeviceAboutToStart (AudioIODevice* device) - { - const ScopedLock sl (callbackLock); - - auto newSampleRate = device->getCurrentSampleRate(); - auto commonRates = getAvailableSampleRates(); - if (! commonRates.contains (newSampleRate)) - { - commonRates.sort(); - if (newSampleRate < commonRates.getFirst() || newSampleRate > commonRates.getLast()) - newSampleRate = jlimit (commonRates.getFirst(), commonRates.getLast(), newSampleRate); - else - for (auto it = commonRates.begin(); it < commonRates.end() - 1; ++it) - if (it[0] < newSampleRate && it[1] > newSampleRate) - { - newSampleRate = newSampleRate - it[0] < it[1] - newSampleRate ? it[0] : it[1]; - break; - } - } - currentSampleRate = newSampleRate; - - bool anySampleRateChanges = false; - for (int i = 0; i < devices.size(); ++i) - if (devices.getUnchecked(i)->getCurrentSampleRate() != currentSampleRate) - { - devices.getUnchecked(i)->setCurrentSampleRate (currentSampleRate); - anySampleRateChanges = true; - } - - if (anySampleRateChanges) - owner.audioDeviceListChanged(); - - if (callback != nullptr) - callback->audioDeviceAboutToStart (device); - } - - void handleAudioDeviceStopped() { shutdown ({}); } - void handleAudioDeviceError (const String& errorMessage) { shutdown (errorMessage.isNotEmpty() ? errorMessage : String ("unknown")); } - - //============================================================================== - struct DeviceWrapper : private AudioIODeviceCallback - { - DeviceWrapper (AudioIODeviceCombiner& cd, CoreAudioIODevice* d, bool useIns, bool useOuts) - : owner (cd), device (d), inputIndex (0), outputIndex (0), - useInputs (useIns), useOutputs (useOuts), - inputFifo (32), outputFifo (32), done (false) - { - } - - ~DeviceWrapper() - { - close(); - } - - String open (const BigInteger& inputChannels, const BigInteger& outputChannels, - double sampleRate, int bufferSize, - int channelIndex, - int fifoSize) - { - inputFifo.setTotalSize (fifoSize); - outputFifo.setTotalSize (fifoSize); - inputFifo.reset(); - outputFifo.reset(); - - String err (device->open (useInputs ? inputChannels : BigInteger(), - useOutputs ? outputChannels : BigInteger(), - sampleRate, bufferSize)); - - numInputChans = useInputs ? device->getActiveInputChannels().countNumberOfSetBits() : 0; - numOutputChans = useOutputs ? device->getActiveOutputChannels().countNumberOfSetBits() : 0; - - inputIndex = channelIndex; - outputIndex = channelIndex + numInputChans; - - return err; - } - - void close() - { - device->close(); - } - - void start() - { - reset(); - device->start (this); - } - - void reset() - { - inputFifo.reset(); - outputFifo.reset(); - } - - StringArray getOutputChannelNames() const { return useOutputs ? device->getOutputChannelNames() : StringArray(); } - StringArray getInputChannelNames() const { return useInputs ? device->getInputChannelNames() : StringArray(); } - - bool isInputReady (int numSamples) const noexcept - { - return numInputChans == 0 || inputFifo.getNumReady() >= numSamples; - } - - void readInput (AudioSampleBuffer& destBuffer, int numSamples) - { - if (numInputChans == 0) - return; - - int start1, size1, start2, size2; - inputFifo.prepareToRead (numSamples, start1, size1, start2, size2); - - for (int i = 0; i < numInputChans; ++i) - { - const int index = inputIndex + i; - float* const dest = destBuffer.getWritePointer (index); - const float* const src = owner.fifos.getReadPointer (index); - - if (size1 > 0) FloatVectorOperations::copy (dest, src + start1, size1); - if (size2 > 0) FloatVectorOperations::copy (dest + size1, src + start2, size2); - } - - inputFifo.finishedRead (size1 + size2); - } - - bool isOutputReady (int numSamples) const noexcept - { - return numOutputChans == 0 || outputFifo.getFreeSpace() >= numSamples; - } - - void pushOutputData (AudioSampleBuffer& srcBuffer, int numSamples) - { - if (numOutputChans == 0) - return; - - int start1, size1, start2, size2; - outputFifo.prepareToWrite (numSamples, start1, size1, start2, size2); - - for (int i = 0; i < numOutputChans; ++i) - { - const int index = outputIndex + i; - float* const dest = owner.fifos.getWritePointer (index); - const float* const src = srcBuffer.getReadPointer (index); - - if (size1 > 0) FloatVectorOperations::copy (dest + start1, src, size1); - if (size2 > 0) FloatVectorOperations::copy (dest + start2, src + size1, size2); - } - - outputFifo.finishedWrite (size1 + size2); - } - - void audioDeviceIOCallback (const float** inputChannelData, int numInputChannels, - float** outputChannelData, int numOutputChannels, - int numSamples) override - { - AudioSampleBuffer& buf = owner.fifos; - - if (numInputChannels > 0) - { - int start1, size1, start2, size2; - inputFifo.prepareToWrite (numSamples, start1, size1, start2, size2); - - if (size1 + size2 < numSamples) - { - inputFifo.reset(); - inputFifo.prepareToWrite (numSamples, start1, size1, start2, size2); - } - - for (int i = 0; i < numInputChannels; ++i) - { - float* const dest = buf.getWritePointer (inputIndex + i); - const float* const src = inputChannelData[i]; - - if (size1 > 0) FloatVectorOperations::copy (dest + start1, src, size1); - if (size2 > 0) FloatVectorOperations::copy (dest + start2, src + size1, size2); - } - - inputFifo.finishedWrite (size1 + size2); - - if (numSamples > size1 + size2) - { - for (int i = 0; i < numInputChans; ++i) - buf.clear (inputIndex + i, size1 + size2, numSamples - (size1 + size2)); - - owner.underrun(); - } - } - - if (numOutputChannels > 0) - { - int start1, size1, start2, size2; - outputFifo.prepareToRead (numSamples, start1, size1, start2, size2); - - if (size1 + size2 < numSamples) - { - Thread::sleep (1); - outputFifo.prepareToRead (numSamples, start1, size1, start2, size2); - } - - for (int i = 0; i < numOutputChannels; ++i) - { - float* const dest = outputChannelData[i]; - const float* const src = buf.getReadPointer (outputIndex + i); - - if (size1 > 0) FloatVectorOperations::copy (dest, src + start1, size1); - if (size2 > 0) FloatVectorOperations::copy (dest + size1, src + start2, size2); - } - - outputFifo.finishedRead (size1 + size2); - - if (numSamples > size1 + size2) - { - for (int i = 0; i < numOutputChannels; ++i) - FloatVectorOperations::clear (outputChannelData[i] + (size1 + size2), numSamples - (size1 + size2)); - - owner.underrun(); - } - } - - owner.notify(); - } - - double getCurrentSampleRate() { return device->getCurrentSampleRate(); } - bool setCurrentSampleRate (double newSampleRate) { return device->setCurrentSampleRate (newSampleRate); } - - void audioDeviceAboutToStart (AudioIODevice* d) override { owner.handleAudioDeviceAboutToStart (d); } - void audioDeviceStopped() override { owner.handleAudioDeviceStopped(); } - void audioDeviceError (const String& errorMessage) override { owner.handleAudioDeviceError (errorMessage); } - - AudioIODeviceCombiner& owner; - ScopedPointer device; - int inputIndex, numInputChans, outputIndex, numOutputChans; - bool useInputs, useOutputs; - AbstractFifo inputFifo, outputFifo; - bool done; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DeviceWrapper) - }; - - OwnedArray devices; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioIODeviceCombiner) -}; - - -//============================================================================== -class CoreAudioIODeviceType : public AudioIODeviceType, - private AsyncUpdater -{ -public: - CoreAudioIODeviceType() - : AudioIODeviceType ("CoreAudio"), - hasScanned (false) - { - AudioObjectPropertyAddress pa; - pa.mSelector = kAudioHardwarePropertyDevices; - pa.mScope = kAudioObjectPropertyScopeWildcard; - pa.mElement = kAudioObjectPropertyElementWildcard; - - AudioObjectAddPropertyListener (kAudioObjectSystemObject, &pa, hardwareListenerProc, this); - } - - ~CoreAudioIODeviceType() - { - AudioObjectPropertyAddress pa; - pa.mSelector = kAudioHardwarePropertyDevices; - pa.mScope = kAudioObjectPropertyScopeWildcard; - pa.mElement = kAudioObjectPropertyElementWildcard; - - AudioObjectRemovePropertyListener (kAudioObjectSystemObject, &pa, hardwareListenerProc, this); - } - - //============================================================================== - void scanForDevices() override - { - hasScanned = true; - - inputDeviceNames.clear(); - outputDeviceNames.clear(); - inputIds.clear(); - outputIds.clear(); - - UInt32 size; - - AudioObjectPropertyAddress pa; - pa.mSelector = kAudioHardwarePropertyDevices; - pa.mScope = kAudioObjectPropertyScopeWildcard; - pa.mElement = kAudioObjectPropertyElementMaster; - - if (AudioObjectGetPropertyDataSize (kAudioObjectSystemObject, &pa, 0, nullptr, &size) == noErr) - { - HeapBlock devs; - devs.calloc (size, 1); - - if (AudioObjectGetPropertyData (kAudioObjectSystemObject, &pa, 0, nullptr, &size, devs) == noErr) - { - const int num = size / (int) sizeof (AudioDeviceID); - for (int i = 0; i < num; ++i) - { - char name [1024]; - size = sizeof (name); - pa.mSelector = kAudioDevicePropertyDeviceName; - - if (AudioObjectGetPropertyData (devs[i], &pa, 0, nullptr, &size, name) == noErr) - { - const String nameString (String::fromUTF8 (name, (int) strlen (name))); - const int numIns = getNumChannels (devs[i], true); - const int numOuts = getNumChannels (devs[i], false); - - if (numIns > 0) - { - inputDeviceNames.add (nameString); - inputIds.add (devs[i]); - } - - if (numOuts > 0) - { - outputDeviceNames.add (nameString); - outputIds.add (devs[i]); - } - } - } - } - } - - inputDeviceNames.appendNumbersToDuplicates (false, true); - outputDeviceNames.appendNumbersToDuplicates (false, true); - } - - StringArray getDeviceNames (bool wantInputNames) const override - { - jassert (hasScanned); // need to call scanForDevices() before doing this - - return wantInputNames ? inputDeviceNames - : outputDeviceNames; - } - - int getDefaultDeviceIndex (bool forInput) const override - { - jassert (hasScanned); // need to call scanForDevices() before doing this - - AudioDeviceID deviceID; - UInt32 size = sizeof (deviceID); - - // if they're asking for any input channels at all, use the default input, so we - // get the built-in mic rather than the built-in output with no inputs.. - - AudioObjectPropertyAddress pa; - pa.mSelector = forInput ? kAudioHardwarePropertyDefaultInputDevice - : kAudioHardwarePropertyDefaultOutputDevice; - pa.mScope = kAudioObjectPropertyScopeWildcard; - pa.mElement = kAudioObjectPropertyElementMaster; - - if (AudioObjectGetPropertyData (kAudioObjectSystemObject, &pa, 0, nullptr, &size, &deviceID) == noErr) - { - if (forInput) - { - for (int i = inputIds.size(); --i >= 0;) - if (inputIds[i] == deviceID) - return i; - } - else - { - for (int i = outputIds.size(); --i >= 0;) - if (outputIds[i] == deviceID) - return i; - } - } - - return 0; - } - - int getIndexOfDevice (AudioIODevice* device, bool asInput) const override - { - jassert (hasScanned); // need to call scanForDevices() before doing this - - if (CoreAudioIODevice* const d = dynamic_cast (device)) - return asInput ? d->inputIndex - : d->outputIndex; - - if (AudioIODeviceCombiner* const d = dynamic_cast (device)) - { - const Array devs (d->getDevices()); - - for (int i = 0; i < devs.size(); ++i) - { - const int index = getIndexOfDevice (devs.getUnchecked(i), asInput); - - if (index >= 0) - return index; - } - } - - return -1; - } - - bool hasSeparateInputsAndOutputs() const override { return true; } - - AudioIODevice* createDevice (const String& outputDeviceName, - const String& inputDeviceName) override - { - jassert (hasScanned); // need to call scanForDevices() before doing this - - const int inputIndex = inputDeviceNames.indexOf (inputDeviceName); - const int outputIndex = outputDeviceNames.indexOf (outputDeviceName); - - AudioDeviceID inputDeviceID = inputIds [inputIndex]; - AudioDeviceID outputDeviceID = outputIds [outputIndex]; - - if (inputDeviceID == 0 && outputDeviceID == 0) - return nullptr; - - String combinedName (outputDeviceName.isEmpty() ? inputDeviceName : outputDeviceName); - - if (inputDeviceID == outputDeviceID) - return new CoreAudioIODevice (*this, combinedName, inputDeviceID, inputIndex, outputDeviceID, outputIndex); - - ScopedPointer in, out; - - if (inputDeviceID != 0) - in = new CoreAudioIODevice (*this, inputDeviceName, inputDeviceID, inputIndex, 0, -1); - - if (outputDeviceID != 0) - out = new CoreAudioIODevice (*this, outputDeviceName, 0, -1, outputDeviceID, outputIndex); - - if (in == nullptr) return out.release(); - if (out == nullptr) return in.release(); - - ScopedPointer combo (new AudioIODeviceCombiner (combinedName, *this)); - combo->addDevice (in.release(), true, false); - combo->addDevice (out.release(), false, true); - return combo.release(); - } - - void audioDeviceListChanged() - { - scanForDevices(); - callDeviceChangeListeners(); - } - - void triggerAsyncAudioDeviceListChange() - { - triggerAsyncUpdate(); - } - - //============================================================================== -private: - StringArray inputDeviceNames, outputDeviceNames; - Array inputIds, outputIds; - - bool hasScanned; - - static int getNumChannels (AudioDeviceID deviceID, bool input) - { - int total = 0; - UInt32 size; - - AudioObjectPropertyAddress pa; - pa.mSelector = kAudioDevicePropertyStreamConfiguration; - pa.mScope = input ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; - pa.mElement = kAudioObjectPropertyElementMaster; - - if (AudioObjectGetPropertyDataSize (deviceID, &pa, 0, nullptr, &size) == noErr) - { - HeapBlock bufList; - bufList.calloc (size, 1); - - if (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, bufList) == noErr) - { - const int numStreams = (int) bufList->mNumberBuffers; - - for (int i = 0; i < numStreams; ++i) - { - const ::AudioBuffer& b = bufList->mBuffers[i]; - total += b.mNumberChannels; - } - } - } - - return total; - } - - static OSStatus hardwareListenerProc (AudioDeviceID, UInt32, const AudioObjectPropertyAddress*, void* clientData) - { - static_cast (clientData)->triggerAsyncAudioDeviceListChange(); - return noErr; - } - - void handleAsyncUpdate() override - { - audioDeviceListChanged(); - } - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreAudioIODeviceType) -}; - -}; - -//============================================================================== -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio() -{ - return new CoreAudioClasses::CoreAudioIODeviceType(); -} - -#undef JUCE_COREAUDIOLOG - -} // namespace juce diff --git a/source/modules/juce_audio_devices/native/juce_mac_CoreMidi.cpp b/source/modules/juce_audio_devices/native/juce_mac_CoreMidi.cpp deleted file mode 100644 index 9637792a7..000000000 --- a/source/modules/juce_audio_devices/native/juce_mac_CoreMidi.cpp +++ /dev/null @@ -1,562 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -#ifndef JUCE_LOG_COREMIDI_ERRORS - #define JUCE_LOG_COREMIDI_ERRORS 1 -#endif - -namespace CoreMidiHelpers -{ - static bool checkError (const OSStatus err, const int lineNum) - { - if (err == noErr) - return true; - - #if JUCE_LOG_COREMIDI_ERRORS - Logger::writeToLog ("CoreMIDI error: " + String (lineNum) + " - " + String::toHexString ((int) err)); - #endif - - ignoreUnused (lineNum); - return false; - } - - #undef CHECK_ERROR - #define CHECK_ERROR(a) CoreMidiHelpers::checkError (a, __LINE__) - - //============================================================================== - struct ScopedCFString - { - ScopedCFString() noexcept : cfString (nullptr) {} - ~ScopedCFString() noexcept { if (cfString != nullptr) CFRelease (cfString); } - - CFStringRef cfString; - }; - - static String getMidiObjectName (MIDIObjectRef entity) - { - String result; - CFStringRef str = nullptr; - MIDIObjectGetStringProperty (entity, kMIDIPropertyName, &str); - - if (str != nullptr) - { - result = String::fromCFString (str); - CFRelease (str); - } - - return result; - } - - void enableSimulatorMidiSession() - { - #if TARGET_OS_SIMULATOR - static bool hasEnabledNetworkSession = false; - - if (! hasEnabledNetworkSession) - { - MIDINetworkSession* session = [MIDINetworkSession defaultSession]; - session.enabled = YES; - session.connectionPolicy = MIDINetworkConnectionPolicy_Anyone; - - hasEnabledNetworkSession = true; - } - #endif - } - - static String getEndpointName (MIDIEndpointRef endpoint, bool isExternal) - { - String result (getMidiObjectName (endpoint)); - - MIDIEntityRef entity = 0; // NB: don't attempt to use nullptr for refs - it fails in some types of build. - MIDIEndpointGetEntity (endpoint, &entity); - - if (entity == 0) - return result; // probably virtual - - if (result.isEmpty()) - result = getMidiObjectName (entity); // endpoint name is empty - try the entity - - // now consider the device's name - MIDIDeviceRef device = 0; - MIDIEntityGetDevice (entity, &device); - - if (device != 0) - { - const String deviceName (getMidiObjectName (device)); - - if (deviceName.isNotEmpty()) - { - // if an external device has only one entity, throw away - // the endpoint name and just use the device name - if (isExternal && MIDIDeviceGetNumberOfEntities (device) < 2) - { - result = deviceName; - } - else if (! result.startsWithIgnoreCase (deviceName)) - { - // prepend the device name to the entity name - result = (deviceName + " " + result).trimEnd(); - } - } - } - - return result; - } - - static String getConnectedEndpointName (MIDIEndpointRef endpoint) - { - String result; - - // Does the endpoint have connections? - CFDataRef connections = nullptr; - int numConnections = 0; - - MIDIObjectGetDataProperty (endpoint, kMIDIPropertyConnectionUniqueID, &connections); - - if (connections != nullptr) - { - numConnections = ((int) CFDataGetLength (connections)) / (int) sizeof (MIDIUniqueID); - - if (numConnections > 0) - { - const SInt32* pid = reinterpret_cast (CFDataGetBytePtr (connections)); - - for (int i = 0; i < numConnections; ++i, ++pid) - { - MIDIUniqueID uid = (MIDIUniqueID) ByteOrder::swapIfLittleEndian ((uint32) *pid); - MIDIObjectRef connObject; - MIDIObjectType connObjectType; - OSStatus err = MIDIObjectFindByUniqueID (uid, &connObject, &connObjectType); - - if (err == noErr) - { - String s; - - if (connObjectType == kMIDIObjectType_ExternalSource - || connObjectType == kMIDIObjectType_ExternalDestination) - { - // Connected to an external device's endpoint (10.3 and later). - s = getEndpointName (static_cast (connObject), true); - } - else - { - // Connected to an external device (10.2) (or something else, catch-all) - s = getMidiObjectName (connObject); - } - - if (s.isNotEmpty()) - { - if (result.isNotEmpty()) - result += ", "; - - result += s; - } - } - } - } - - CFRelease (connections); - } - - if (result.isEmpty()) // Here, either the endpoint had no connections, or we failed to obtain names for them. - result = getEndpointName (endpoint, false); - - return result; - } - - static StringArray findDevices (const bool forInput) - { - // It seems that OSX can be a bit picky about the thread that's first used to - // search for devices. It's safest to use the message thread for calling this. - jassert (MessageManager::getInstance()->isThisTheMessageThread()); - - enableSimulatorMidiSession(); - - const ItemCount num = forInput ? MIDIGetNumberOfSources() - : MIDIGetNumberOfDestinations(); - StringArray s; - - for (ItemCount i = 0; i < num; ++i) - { - MIDIEndpointRef dest = forInput ? MIDIGetSource (i) - : MIDIGetDestination (i); - String name; - - if (dest != 0) - name = getConnectedEndpointName (dest); - - if (name.isEmpty()) - name = ""; - - s.add (name); - } - - return s; - } - - static void globalSystemChangeCallback (const MIDINotification*, void*) - { - // TODO.. Should pass-on this notification.. - } - - static String getGlobalMidiClientName() - { - if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance()) - return app->getApplicationName(); - - return "JUCE"; - } - - static MIDIClientRef getGlobalMidiClient() - { - static MIDIClientRef globalMidiClient = 0; - - if (globalMidiClient == 0) - { - // Since OSX 10.6, the MIDIClientCreate function will only work - // correctly when called from the message thread! - jassert (MessageManager::getInstance()->isThisTheMessageThread()); - - enableSimulatorMidiSession(); - - CoreMidiHelpers::ScopedCFString name; - name.cfString = getGlobalMidiClientName().toCFString(); - CHECK_ERROR (MIDIClientCreate (name.cfString, &globalSystemChangeCallback, nullptr, &globalMidiClient)); - } - - return globalMidiClient; - } - - //============================================================================== - class MidiPortAndEndpoint - { - public: - MidiPortAndEndpoint (MIDIPortRef p, MIDIEndpointRef ep) noexcept - : port (p), endPoint (ep) - { - } - - ~MidiPortAndEndpoint() noexcept - { - if (port != 0) - MIDIPortDispose (port); - - if (port == 0 && endPoint != 0) // if port == nullptr, it means we created the endpoint, so it's safe to delete it - MIDIEndpointDispose (endPoint); - } - - void send (const MIDIPacketList* const packets) noexcept - { - if (port != 0) - MIDISend (port, endPoint, packets); - else - MIDIReceived (endPoint, packets); - } - - MIDIPortRef port; - MIDIEndpointRef endPoint; - }; - - //============================================================================== - class MidiPortAndCallback; - CriticalSection callbackLock; - Array activeCallbacks; - - class MidiPortAndCallback - { - public: - MidiPortAndCallback (MidiInputCallback& cb) - : input (nullptr), active (false), callback (cb), concatenator (2048) - { - } - - ~MidiPortAndCallback() - { - active = false; - - { - const ScopedLock sl (callbackLock); - activeCallbacks.removeFirstMatchingValue (this); - } - - if (portAndEndpoint != 0 && portAndEndpoint->port != 0) - CHECK_ERROR (MIDIPortDisconnectSource (portAndEndpoint->port, portAndEndpoint->endPoint)); - } - - void handlePackets (const MIDIPacketList* const pktlist) - { - const double time = Time::getMillisecondCounterHiRes() * 0.001; - - const ScopedLock sl (callbackLock); - if (activeCallbacks.contains (this) && active) - { - const MIDIPacket* packet = &pktlist->packet[0]; - - for (unsigned int i = 0; i < pktlist->numPackets; ++i) - { - concatenator.pushMidiData (packet->data, (int) packet->length, time, - input, callback); - - packet = MIDIPacketNext (packet); - } - } - } - - MidiInput* input; - ScopedPointer portAndEndpoint; - volatile bool active; - - private: - MidiInputCallback& callback; - MidiDataConcatenator concatenator; - }; - - static void midiInputProc (const MIDIPacketList* pktlist, void* readProcRefCon, void* /*srcConnRefCon*/) - { - static_cast (readProcRefCon)->handlePackets (pktlist); - } -} - -//============================================================================== -StringArray MidiOutput::getDevices() { return CoreMidiHelpers::findDevices (false); } -int MidiOutput::getDefaultDeviceIndex() { return 0; } - -MidiOutput* MidiOutput::openDevice (int index) -{ - MidiOutput* mo = nullptr; - - if (isPositiveAndBelow (index, MIDIGetNumberOfDestinations())) - { - MIDIEndpointRef endPoint = MIDIGetDestination ((ItemCount) index); - - CoreMidiHelpers::ScopedCFString pname; - - if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname.cfString))) - { - MIDIClientRef client = CoreMidiHelpers::getGlobalMidiClient(); - MIDIPortRef port; - String deviceName = CoreMidiHelpers::getConnectedEndpointName (endPoint); - - if (client != 0 && CHECK_ERROR (MIDIOutputPortCreate (client, pname.cfString, &port))) - { - mo = new MidiOutput (deviceName); - mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (port, endPoint); - } - } - } - - return mo; -} - -MidiOutput* MidiOutput::createNewDevice (const String& deviceName) -{ - MIDIClientRef client = CoreMidiHelpers::getGlobalMidiClient(); - MIDIEndpointRef endPoint; - - CoreMidiHelpers::ScopedCFString name; - name.cfString = deviceName.toCFString(); - - if (client != 0 && CHECK_ERROR (MIDISourceCreate (client, name.cfString, &endPoint))) - { - MidiOutput* mo = new MidiOutput (deviceName); - mo->internal = new CoreMidiHelpers::MidiPortAndEndpoint (0, endPoint); - return mo; - } - - return nullptr; -} - -MidiOutput::~MidiOutput() -{ - stopBackgroundThread(); - - delete static_cast (internal); -} - -void MidiOutput::sendMessageNow (const MidiMessage& message) -{ - #if JUCE_IOS - const MIDITimeStamp timeStamp = mach_absolute_time(); - #else - const MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); - #endif - - HeapBlock allocatedPackets; - MIDIPacketList stackPacket; - MIDIPacketList* packetToSend = &stackPacket; - const size_t dataSize = (size_t) message.getRawDataSize(); - - if (message.isSysEx()) - { - const int maxPacketSize = 256; - int pos = 0, bytesLeft = (int) dataSize; - const int numPackets = (bytesLeft + maxPacketSize - 1) / maxPacketSize; - allocatedPackets.malloc ((size_t) (32 * (size_t) numPackets + dataSize), 1); - packetToSend = allocatedPackets; - packetToSend->numPackets = (UInt32) numPackets; - - MIDIPacket* p = packetToSend->packet; - - for (int i = 0; i < numPackets; ++i) - { - p->timeStamp = timeStamp; - p->length = (UInt16) jmin (maxPacketSize, bytesLeft); - memcpy (p->data, message.getRawData() + pos, p->length); - pos += p->length; - bytesLeft -= p->length; - p = MIDIPacketNext (p); - } - } - else if (dataSize < 65536) // max packet size - { - const size_t stackCapacity = sizeof (stackPacket.packet->data); - - if (dataSize > stackCapacity) - { - allocatedPackets.malloc ((sizeof (MIDIPacketList) - stackCapacity) + dataSize, 1); - packetToSend = allocatedPackets; - } - - packetToSend->numPackets = 1; - MIDIPacket& p = *(packetToSend->packet); - p.timeStamp = timeStamp; - p.length = (UInt16) dataSize; - memcpy (p.data, message.getRawData(), dataSize); - } - else - { - jassertfalse; // packet too large to send! - return; - } - - static_cast (internal)->send (packetToSend); -} - -//============================================================================== -StringArray MidiInput::getDevices() { return CoreMidiHelpers::findDevices (true); } -int MidiInput::getDefaultDeviceIndex() { return 0; } - -MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) -{ - jassert (callback != nullptr); - - using namespace CoreMidiHelpers; - MidiInput* newInput = nullptr; - - if (isPositiveAndBelow (index, MIDIGetNumberOfSources())) - { - if (MIDIEndpointRef endPoint = MIDIGetSource ((ItemCount) index)) - { - ScopedCFString name; - - if (CHECK_ERROR (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &name.cfString))) - { - if (MIDIClientRef client = getGlobalMidiClient()) - { - MIDIPortRef port; - ScopedPointer mpc (new MidiPortAndCallback (*callback)); - - if (CHECK_ERROR (MIDIInputPortCreate (client, name.cfString, midiInputProc, mpc, &port))) - { - if (CHECK_ERROR (MIDIPortConnectSource (port, endPoint, nullptr))) - { - mpc->portAndEndpoint = new MidiPortAndEndpoint (port, endPoint); - - newInput = new MidiInput (getDevices() [index]); - mpc->input = newInput; - newInput->internal = mpc; - - const ScopedLock sl (callbackLock); - activeCallbacks.add (mpc.release()); - } - else - { - CHECK_ERROR (MIDIPortDispose (port)); - } - } - } - } - } - } - - return newInput; -} - -MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) -{ - jassert (callback != nullptr); - - using namespace CoreMidiHelpers; - MidiInput* mi = nullptr; - - if (MIDIClientRef client = getGlobalMidiClient()) - { - ScopedPointer mpc (new MidiPortAndCallback (*callback)); - mpc->active = false; - - MIDIEndpointRef endPoint; - ScopedCFString name; - name.cfString = deviceName.toCFString(); - - if (CHECK_ERROR (MIDIDestinationCreate (client, name.cfString, midiInputProc, mpc, &endPoint))) - { - mpc->portAndEndpoint = new MidiPortAndEndpoint (0, endPoint); - - mi = new MidiInput (deviceName); - mpc->input = mi; - mi->internal = mpc; - - const ScopedLock sl (callbackLock); - activeCallbacks.add (mpc.release()); - } - } - - return mi; -} - -MidiInput::MidiInput (const String& nm) : name (nm) -{ -} - -MidiInput::~MidiInput() -{ - delete static_cast (internal); -} - -void MidiInput::start() -{ - const ScopedLock sl (CoreMidiHelpers::callbackLock); - static_cast (internal)->active = true; -} - -void MidiInput::stop() -{ - const ScopedLock sl (CoreMidiHelpers::callbackLock); - static_cast (internal)->active = false; -} - -#undef CHECK_ERROR - -} // namespace juce diff --git a/source/modules/juce_audio_devices/native/juce_win32_ASIO.cpp b/source/modules/juce_audio_devices/native/juce_win32_ASIO.cpp deleted file mode 100644 index 3a0c6409d..000000000 --- a/source/modules/juce_audio_devices/native/juce_win32_ASIO.cpp +++ /dev/null @@ -1,1649 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -#undef WINDOWS - -/* The ASIO SDK *should* declare its callback functions as being __cdecl, but different versions seem - to be pretty random about whether or not they do this. If you hit an error using these functions - it'll be because you're trying to build using __stdcall, in which case you'd need to either get hold of - an ASIO SDK which correctly specifies __cdecl, or add the __cdecl keyword to its functions yourself. -*/ -#define JUCE_ASIOCALLBACK __cdecl - -//============================================================================== -namespace ASIODebugging -{ - #if JUCE_ASIO_DEBUGGING - #define JUCE_ASIO_LOG(msg) ASIODebugging::logMessage (msg) - #define JUCE_ASIO_LOG_ERROR(msg, errNum) ASIODebugging::logError ((msg), (errNum)) - - static void logMessage (String message) - { - message = "ASIO: " + message; - DBG (message); - Logger::writeToLog (message); - } - - static void logError (const String& context, long error) - { - const char* err = "Unknown error"; - - switch (error) - { - case ASE_OK: return; - case ASE_NotPresent: err = "Not Present"; break; - case ASE_HWMalfunction: err = "Hardware Malfunction"; break; - case ASE_InvalidParameter: err = "Invalid Parameter"; break; - case ASE_InvalidMode: err = "Invalid Mode"; break; - case ASE_SPNotAdvancing: err = "Sample position not advancing"; break; - case ASE_NoClock: err = "No Clock"; break; - case ASE_NoMemory: err = "Out of memory"; break; - default: break; - } - - logMessage ("error: " + context + " - " + err); - } - #else - static void dummyLog() {} - #define JUCE_ASIO_LOG(msg) ASIODebugging::dummyLog() - #define JUCE_ASIO_LOG_ERROR(msg, errNum) ignoreUnused (errNum); ASIODebugging::dummyLog() - #endif -} - -//============================================================================== -struct ASIOSampleFormat -{ - ASIOSampleFormat() noexcept {} - - ASIOSampleFormat (const long type) noexcept - : bitDepth (24), - littleEndian (true), - formatIsFloat (false), - byteStride (4) - { - switch (type) - { - case ASIOSTInt16MSB: byteStride = 2; littleEndian = false; bitDepth = 16; break; - case ASIOSTInt24MSB: byteStride = 3; littleEndian = false; break; - case ASIOSTInt32MSB: bitDepth = 32; littleEndian = false; break; - case ASIOSTFloat32MSB: bitDepth = 32; littleEndian = false; formatIsFloat = true; break; - case ASIOSTFloat64MSB: bitDepth = 64; byteStride = 8; littleEndian = false; break; - case ASIOSTInt32MSB16: bitDepth = 16; littleEndian = false; break; - case ASIOSTInt32MSB18: littleEndian = false; break; - case ASIOSTInt32MSB20: littleEndian = false; break; - case ASIOSTInt32MSB24: littleEndian = false; break; - case ASIOSTInt16LSB: byteStride = 2; bitDepth = 16; break; - case ASIOSTInt24LSB: byteStride = 3; break; - case ASIOSTInt32LSB: bitDepth = 32; break; - case ASIOSTFloat32LSB: bitDepth = 32; formatIsFloat = true; break; - case ASIOSTFloat64LSB: bitDepth = 64; byteStride = 8; break; - case ASIOSTInt32LSB16: bitDepth = 16; break; - case ASIOSTInt32LSB18: break; // (unhandled) - case ASIOSTInt32LSB20: break; // (unhandled) - case ASIOSTInt32LSB24: break; - - case ASIOSTDSDInt8LSB1: break; // (unhandled) - case ASIOSTDSDInt8MSB1: break; // (unhandled) - case ASIOSTDSDInt8NER8: break; // (unhandled) - - default: - jassertfalse; // (not a valid format code..) - break; - } - } - - void convertToFloat (const void* const src, float* const dst, const int samps) const noexcept - { - if (formatIsFloat) - { - memcpy (dst, src, samps * sizeof (float)); - } - else - { - switch (bitDepth) - { - case 16: convertInt16ToFloat (static_cast (src), dst, byteStride, samps, littleEndian); break; - case 24: convertInt24ToFloat (static_cast (src), dst, byteStride, samps, littleEndian); break; - case 32: convertInt32ToFloat (static_cast (src), dst, byteStride, samps, littleEndian); break; - default: jassertfalse; break; - } - } - } - - void convertFromFloat (const float* const src, void* const dst, const int samps) const noexcept - { - if (formatIsFloat) - { - memcpy (dst, src, samps * sizeof (float)); - } - else - { - switch (bitDepth) - { - case 16: convertFloatToInt16 (src, static_cast (dst), byteStride, samps, littleEndian); break; - case 24: convertFloatToInt24 (src, static_cast (dst), byteStride, samps, littleEndian); break; - case 32: convertFloatToInt32 (src, static_cast (dst), byteStride, samps, littleEndian); break; - default: jassertfalse; break; - } - } - } - - void clear (void* dst, const int numSamps) noexcept - { - if (dst != nullptr) - zeromem (dst, numSamps * byteStride); - } - - int bitDepth, byteStride; - bool formatIsFloat, littleEndian; - -private: - static void convertInt16ToFloat (const char* src, float* dest, const int srcStrideBytes, - int numSamples, const bool littleEndian) noexcept - { - const double g = 1.0 / 32768.0; - - if (littleEndian) - { - while (--numSamples >= 0) - { - *dest++ = (float) (g * (short) ByteOrder::littleEndianShort (src)); - src += srcStrideBytes; - } - } - else - { - while (--numSamples >= 0) - { - *dest++ = (float) (g * (short) ByteOrder::bigEndianShort (src)); - src += srcStrideBytes; - } - } - } - - static void convertFloatToInt16 (const float* src, char* dest, const int dstStrideBytes, - int numSamples, const bool littleEndian) noexcept - { - const double maxVal = (double) 0x7fff; - - if (littleEndian) - { - while (--numSamples >= 0) - { - *(uint16*) dest = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * *src++))); - dest += dstStrideBytes; - } - } - else - { - while (--numSamples >= 0) - { - *(uint16*) dest = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * *src++))); - dest += dstStrideBytes; - } - } - } - - static void convertInt24ToFloat (const char* src, float* dest, const int srcStrideBytes, - int numSamples, const bool littleEndian) noexcept - { - const double g = 1.0 / 0x7fffff; - - if (littleEndian) - { - while (--numSamples >= 0) - { - *dest++ = (float) (g * ByteOrder::littleEndian24Bit (src)); - src += srcStrideBytes; - } - } - else - { - while (--numSamples >= 0) - { - *dest++ = (float) (g * ByteOrder::bigEndian24Bit (src)); - src += srcStrideBytes; - } - } - } - - static void convertFloatToInt24 (const float* src, char* dest, const int dstStrideBytes, - int numSamples, const bool littleEndian) noexcept - { - const double maxVal = (double) 0x7fffff; - - if (littleEndian) - { - while (--numSamples >= 0) - { - ByteOrder::littleEndian24BitToChars ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * *src++)), dest); - dest += dstStrideBytes; - } - } - else - { - while (--numSamples >= 0) - { - ByteOrder::bigEndian24BitToChars ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * *src++)), dest); - dest += dstStrideBytes; - } - } - } - - static void convertInt32ToFloat (const char* src, float* dest, const int srcStrideBytes, - int numSamples, const bool littleEndian) noexcept - { - const double g = 1.0 / 0x7fffffff; - - if (littleEndian) - { - while (--numSamples >= 0) - { - *dest++ = (float) (g * (int) ByteOrder::littleEndianInt (src)); - src += srcStrideBytes; - } - } - else - { - while (--numSamples >= 0) - { - *dest++ = (float) (g * (int) ByteOrder::bigEndianInt (src)); - src += srcStrideBytes; - } - } - } - - static void convertFloatToInt32 (const float* src, char* dest, const int dstStrideBytes, - int numSamples, const bool littleEndian) noexcept - { - const double maxVal = (double) 0x7fffffff; - - if (littleEndian) - { - while (--numSamples >= 0) - { - *(uint32*) dest = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * *src++))); - dest += dstStrideBytes; - } - } - else - { - while (--numSamples >= 0) - { - *(uint32*) dest = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * *src++))); - dest += dstStrideBytes; - } - } - } -}; - -//============================================================================== -class ASIOAudioIODevice; -static ASIOAudioIODevice* volatile currentASIODev[16] = { 0 }; - -extern HWND juce_messageWindowHandle; - -class ASIOAudioIODeviceType; -static void sendASIODeviceChangeToListeners (ASIOAudioIODeviceType*); - -//============================================================================== -class ASIOAudioIODevice : public AudioIODevice, - private Timer -{ -public: - ASIOAudioIODevice (ASIOAudioIODeviceType* ownerType, const String& devName, - const CLSID clsID, const int slotNumber) - : AudioIODevice (devName, "ASIO"), - owner (ownerType), - asioObject (nullptr), - classId (clsID), - inputLatency (0), - outputLatency (0), - minBufferSize (0), maxBufferSize (0), - preferredBufferSize (0), - bufferGranularity (0), - numClockSources (0), - currentBlockSizeSamples (0), - currentBitDepth (16), - currentSampleRate (0), - currentCallback (nullptr), - bufferIndex (0), - numActiveInputChans (0), - numActiveOutputChans (0), - deviceIsOpen (false), - isStarted (false), - buffersCreated (false), - calledback (false), - littleEndian (false), - postOutput (true), - needToReset (false), - insideControlPanelModalLoop (false), - shouldUsePreferredSize (false) - { - ::CoInitialize (nullptr); - - name = devName; - inBuffers.calloc (4); - outBuffers.calloc (4); - - jassert (currentASIODev [slotNumber] == nullptr); - currentASIODev [slotNumber] = this; - - openDevice(); - } - - ~ASIOAudioIODevice() - { - for (int i = 0; i < numElementsInArray (currentASIODev); ++i) - if (currentASIODev[i] == this) - currentASIODev[i] = nullptr; - - close(); - JUCE_ASIO_LOG ("closed"); - removeCurrentDriver(); - } - - void updateSampleRates() - { - // find a list of sample rates.. - Array newRates; - - if (asioObject != nullptr) - { - const int possibleSampleRates[] = { 32000, 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000 }; - - for (int index = 0; index < numElementsInArray (possibleSampleRates); ++index) - if (asioObject->canSampleRate ((double) possibleSampleRates[index]) == 0) - newRates.add ((double) possibleSampleRates[index]); - } - - if (newRates.isEmpty()) - { - double cr = getSampleRate(); - JUCE_ASIO_LOG ("No sample rates supported - current rate: " + String ((int) cr)); - - if (cr > 0) - newRates.add ((int) cr); - } - - if (sampleRates != newRates) - { - sampleRates.swapWith (newRates); - - #if JUCE_ASIO_DEBUGGING - StringArray s; - for (int i = 0; i < sampleRates.size(); ++i) - s.add (String (sampleRates.getUnchecked(i))); - - JUCE_ASIO_LOG ("Rates: " + s.joinIntoString (" ")); - #endif - } - } - - StringArray getOutputChannelNames() override { return outputChannelNames; } - StringArray getInputChannelNames() override { return inputChannelNames; } - - Array getAvailableSampleRates() override { return sampleRates; } - Array getAvailableBufferSizes() override { return bufferSizes; } - int getDefaultBufferSize() override { return preferredBufferSize; } - - int getXRunCount() const noexcept override { return xruns; } - - String open (const BigInteger& inputChannels, - const BigInteger& outputChannels, - double sr, int bufferSizeSamples) override - { - if (isOpen()) - close(); - - jassert (currentCallback == nullptr); - - if (bufferSizeSamples < 8 || bufferSizeSamples > 16384) - shouldUsePreferredSize = true; - - if (asioObject == nullptr) - { - const String openingError (openDevice()); - - if (asioObject == nullptr) - return openingError; - } - - isStarted = false; - bufferIndex = -1; - - long err = asioObject->getChannels (&totalNumInputChans, &totalNumOutputChans); - jassert (err == ASE_OK); - - bufferSizeSamples = readBufferSizes (bufferSizeSamples); - - double sampleRate = sr; - currentSampleRate = sampleRate; - currentBlockSizeSamples = bufferSizeSamples; - currentChansOut.clear(); - currentChansIn.clear(); - - updateSampleRates(); - - if (sampleRate == 0 || (sampleRates.size() > 0 && ! sampleRates.contains (sampleRate))) - sampleRate = sampleRates[0]; - - jassert (sampleRate != 0); - if (sampleRate == 0) - sampleRate = 44100.0; - - updateClockSources(); - currentSampleRate = getSampleRate(); - - error.clear(); - buffersCreated = false; - - setSampleRate (sampleRate); - - // (need to get this again in case a sample rate change affected the channel count) - err = asioObject->getChannels (&totalNumInputChans, &totalNumOutputChans); - jassert (err == ASE_OK); - - if (asioObject->future (kAsioCanReportOverload, nullptr) != ASE_OK) - xruns = -1; - - inBuffers.calloc (totalNumInputChans + 8); - outBuffers.calloc (totalNumOutputChans + 8); - - if (needToReset) - { - JUCE_ASIO_LOG (" Resetting"); - removeCurrentDriver(); - - loadDriver(); - String initError = initDriver(); - - if (initError.isNotEmpty()) - JUCE_ASIO_LOG ("ASIOInit: " + initError); - - needToReset = false; - } - - const int totalBuffers = resetBuffers (inputChannels, outputChannels); - - setCallbackFunctions(); - - JUCE_ASIO_LOG ("disposing buffers"); - err = asioObject->disposeBuffers(); - - JUCE_ASIO_LOG ("creating buffers: " + String (totalBuffers) + ", " + String (currentBlockSizeSamples)); - err = asioObject->createBuffers (bufferInfos, totalBuffers, currentBlockSizeSamples, &callbacks); - - if (err != ASE_OK) - { - currentBlockSizeSamples = preferredBufferSize; - JUCE_ASIO_LOG_ERROR ("create buffers 2", err); - - asioObject->disposeBuffers(); - err = asioObject->createBuffers (bufferInfos, totalBuffers, currentBlockSizeSamples, &callbacks); - } - - if (err == ASE_OK) - { - buffersCreated = true; - - tempBuffer.calloc (totalBuffers * currentBlockSizeSamples + 32); - - int n = 0; - Array types; - currentBitDepth = 16; - - for (int i = 0; i < (int) totalNumInputChans; ++i) - { - if (inputChannels[i]) - { - inBuffers[n] = tempBuffer + (currentBlockSizeSamples * n); - - ASIOChannelInfo channelInfo = { 0 }; - channelInfo.channel = i; - channelInfo.isInput = 1; - asioObject->getChannelInfo (&channelInfo); - - types.addIfNotAlreadyThere (channelInfo.type); - inputFormat[n] = ASIOSampleFormat (channelInfo.type); - - currentBitDepth = jmax (currentBitDepth, inputFormat[n].bitDepth); - ++n; - } - } - - jassert (numActiveInputChans == n); - n = 0; - - for (int i = 0; i < (int) totalNumOutputChans; ++i) - { - if (outputChannels[i]) - { - outBuffers[n] = tempBuffer + (currentBlockSizeSamples * (numActiveInputChans + n)); - - ASIOChannelInfo channelInfo = { 0 }; - channelInfo.channel = i; - channelInfo.isInput = 0; - asioObject->getChannelInfo (&channelInfo); - - types.addIfNotAlreadyThere (channelInfo.type); - outputFormat[n] = ASIOSampleFormat (channelInfo.type); - - currentBitDepth = jmax (currentBitDepth, outputFormat[n].bitDepth); - ++n; - } - } - - jassert (numActiveOutputChans == n); - - for (int i = types.size(); --i >= 0;) - JUCE_ASIO_LOG ("channel format: " + String (types[i])); - - jassert (n <= totalBuffers); - - for (int i = 0; i < numActiveOutputChans; ++i) - { - outputFormat[i].clear (bufferInfos [numActiveInputChans + i].buffers[0], currentBlockSizeSamples); - outputFormat[i].clear (bufferInfos [numActiveInputChans + i].buffers[1], currentBlockSizeSamples); - } - - readLatencies(); - refreshBufferSizes(); - deviceIsOpen = true; - - JUCE_ASIO_LOG ("starting"); - calledback = false; - err = asioObject->start(); - - if (err != 0) - { - deviceIsOpen = false; - JUCE_ASIO_LOG ("stop on failure"); - Thread::sleep (10); - asioObject->stop(); - error = "Can't start device"; - Thread::sleep (10); - } - else - { - int count = 300; - while (--count > 0 && ! calledback) - Thread::sleep (10); - - isStarted = true; - - if (! calledback) - { - error = "Device didn't start correctly"; - JUCE_ASIO_LOG ("no callbacks - stopping.."); - asioObject->stop(); - } - } - } - else - { - error = "Can't create i/o buffers"; - } - - if (error.isNotEmpty()) - { - JUCE_ASIO_LOG_ERROR (error, err); - disposeBuffers(); - - Thread::sleep (20); - isStarted = false; - deviceIsOpen = false; - - const String errorCopy (error); - close(); // (this resets the error string) - error = errorCopy; - } - - needToReset = false; - return error; - } - - void close() override - { - error.clear(); - stopTimer(); - stop(); - - if (asioObject != nullptr && deviceIsOpen) - { - const ScopedLock sl (callbackLock); - - deviceIsOpen = false; - isStarted = false; - needToReset = false; - - JUCE_ASIO_LOG ("stopping"); - - if (asioObject != nullptr) - { - Thread::sleep (20); - asioObject->stop(); - Thread::sleep (10); - disposeBuffers(); - } - - Thread::sleep (10); - } - } - - bool isOpen() override { return deviceIsOpen || insideControlPanelModalLoop; } - bool isPlaying() override { return asioObject != nullptr && currentCallback != nullptr; } - - int getCurrentBufferSizeSamples() override { return currentBlockSizeSamples; } - double getCurrentSampleRate() override { return currentSampleRate; } - int getCurrentBitDepth() override { return currentBitDepth; } - - BigInteger getActiveOutputChannels() const override { return currentChansOut; } - BigInteger getActiveInputChannels() const override { return currentChansIn; } - - int getOutputLatencyInSamples() override { return outputLatency + currentBlockSizeSamples / 4; } - int getInputLatencyInSamples() override { return inputLatency + currentBlockSizeSamples / 4; } - - void start (AudioIODeviceCallback* callback) override - { - if (callback != nullptr) - { - callback->audioDeviceAboutToStart (this); - - const ScopedLock sl (callbackLock); - currentCallback = callback; - } - } - - void stop() override - { - AudioIODeviceCallback* const lastCallback = currentCallback; - - { - const ScopedLock sl (callbackLock); - currentCallback = nullptr; - } - - if (lastCallback != nullptr) - lastCallback->audioDeviceStopped(); - } - - String getLastError() { return error; } - bool hasControlPanel() const { return true; } - - bool showControlPanel() - { - JUCE_ASIO_LOG ("showing control panel"); - - bool done = false; - insideControlPanelModalLoop = true; - - const uint32 started = Time::getMillisecondCounter(); - - if (asioObject != nullptr) - { - asioObject->controlPanel(); - - const int spent = (int) Time::getMillisecondCounter() - (int) started; - - JUCE_ASIO_LOG ("spent: " + String (spent)); - - if (spent > 300) - { - shouldUsePreferredSize = true; - done = true; - } - } - - insideControlPanelModalLoop = false; - return done; - } - - void resetRequest() noexcept - { - startTimer (500); - } - - void timerCallback() override - { - if (! insideControlPanelModalLoop) - { - stopTimer(); - - JUCE_ASIO_LOG ("restart request!"); - - AudioIODeviceCallback* const oldCallback = currentCallback; - - close(); - - needToReset = true; - open (BigInteger (currentChansIn), BigInteger (currentChansOut), - currentSampleRate, currentBlockSizeSamples); - - reloadChannelNames(); - - if (oldCallback != nullptr) - start (oldCallback); - - sendASIODeviceChangeToListeners (owner); - } - else - { - startTimer (100); - } - } - -private: - //============================================================================== - WeakReference owner; - IASIO* volatile asioObject; - ASIOCallbacks callbacks; - - CLSID classId; - String error; - - long totalNumInputChans, totalNumOutputChans; - StringArray inputChannelNames, outputChannelNames; - - Array sampleRates; - Array bufferSizes; - long inputLatency, outputLatency; - long minBufferSize, maxBufferSize, preferredBufferSize, bufferGranularity; - ASIOClockSource clocks[32]; - int numClockSources; - - int volatile currentBlockSizeSamples; - int volatile currentBitDepth; - double volatile currentSampleRate; - BigInteger currentChansOut, currentChansIn; - AudioIODeviceCallback* volatile currentCallback; - CriticalSection callbackLock; - - HeapBlock bufferInfos; - HeapBlock inBuffers, outBuffers; - HeapBlock inputFormat, outputFormat; - - WaitableEvent event1; - HeapBlock tempBuffer; - int volatile bufferIndex, numActiveInputChans, numActiveOutputChans; - - bool deviceIsOpen, isStarted, buffersCreated; - bool volatile calledback; - bool volatile littleEndian, postOutput, needToReset; - bool volatile insideControlPanelModalLoop; - bool volatile shouldUsePreferredSize; - int xruns = 0; - - //============================================================================== - static String convertASIOString (char* const text, int length) - { - if (CharPointer_UTF8::isValidString (text, length)) - return String::fromUTF8 (text, length); - - WCHAR wideVersion [64] = { 0 }; - MultiByteToWideChar (CP_ACP, 0, text, length, wideVersion, numElementsInArray (wideVersion)); - return wideVersion; - } - - String getChannelName (int index, bool isInput) const - { - ASIOChannelInfo channelInfo = { 0 }; - channelInfo.channel = index; - channelInfo.isInput = isInput ? 1 : 0; - asioObject->getChannelInfo (&channelInfo); - - return convertASIOString (channelInfo.name, sizeof (channelInfo.name)); - } - - void reloadChannelNames() - { - if (asioObject != nullptr - && asioObject->getChannels (&totalNumInputChans, &totalNumOutputChans) == ASE_OK) - { - inputChannelNames.clear(); - outputChannelNames.clear(); - - for (int i = 0; i < totalNumInputChans; ++i) - inputChannelNames.add (getChannelName (i, true)); - - for (int i = 0; i < totalNumOutputChans; ++i) - outputChannelNames.add (getChannelName (i, false)); - - outputChannelNames.trim(); - inputChannelNames.trim(); - outputChannelNames.appendNumbersToDuplicates (false, true); - inputChannelNames.appendNumbersToDuplicates (false, true); - } - } - - long refreshBufferSizes() - { - return asioObject->getBufferSize (&minBufferSize, &maxBufferSize, &preferredBufferSize, &bufferGranularity); - } - - int readBufferSizes (int bufferSizeSamples) - { - minBufferSize = 0; - maxBufferSize = 0; - bufferGranularity = 0; - long newPreferredSize = 0; - - if (asioObject->getBufferSize (&minBufferSize, &maxBufferSize, &newPreferredSize, &bufferGranularity) == ASE_OK) - { - if (preferredBufferSize != 0 && newPreferredSize != 0 && newPreferredSize != preferredBufferSize) - shouldUsePreferredSize = true; - - if (bufferSizeSamples < minBufferSize || bufferSizeSamples > maxBufferSize) - shouldUsePreferredSize = true; - - preferredBufferSize = newPreferredSize; - } - - // unfortunate workaround for certain drivers which crash if you make - // dynamic changes to the buffer size... - shouldUsePreferredSize = shouldUsePreferredSize || getName().containsIgnoreCase ("Digidesign"); - - if (shouldUsePreferredSize) - { - JUCE_ASIO_LOG ("Using preferred size for buffer.."); - long err = refreshBufferSizes(); - - if (err == ASE_OK) - { - bufferSizeSamples = (int) preferredBufferSize; - } - else - { - bufferSizeSamples = 1024; - JUCE_ASIO_LOG_ERROR ("getBufferSize1", err); - } - - shouldUsePreferredSize = false; - } - - return bufferSizeSamples; - } - - int resetBuffers (const BigInteger& inputChannels, - const BigInteger& outputChannels) - { - numActiveInputChans = 0; - numActiveOutputChans = 0; - - ASIOBufferInfo* info = bufferInfos; - for (int i = 0; i < totalNumInputChans; ++i) - { - if (inputChannels[i]) - { - currentChansIn.setBit (i); - info->isInput = 1; - info->channelNum = i; - info->buffers[0] = info->buffers[1] = nullptr; - ++info; - ++numActiveInputChans; - } - } - - for (int i = 0; i < totalNumOutputChans; ++i) - { - if (outputChannels[i]) - { - currentChansOut.setBit (i); - info->isInput = 0; - info->channelNum = i; - info->buffers[0] = info->buffers[1] = nullptr; - ++info; - ++numActiveOutputChans; - } - } - - return numActiveInputChans + numActiveOutputChans; - } - - void addBufferSizes (long minSize, long maxSize, long preferredSize, long granularity) - { - // find a list of buffer sizes.. - JUCE_ASIO_LOG (String ((int) minSize) + "->" + String ((int) maxSize) + ", " - + String ((int) preferredSize) + ", " + String ((int) granularity)); - - if (granularity >= 0) - { - granularity = jmax (16, (int) granularity); - - for (int i = jmax ((int) (minSize + 15) & ~15, (int) granularity); i < jmin (6400, (int) maxSize); i += granularity) - bufferSizes.addIfNotAlreadyThere (granularity * (i / granularity)); - } - else if (granularity < 0) - { - for (int i = 0; i < 18; ++i) - { - const int s = (1 << i); - - if (s >= minSize && s <= maxSize) - bufferSizes.add (s); - } - } - - bufferSizes.addIfNotAlreadyThere (preferredSize); - bufferSizes.sort(); - } - - double getSampleRate() const - { - double cr = 0; - long err = asioObject->getSampleRate (&cr); - JUCE_ASIO_LOG_ERROR ("getSampleRate", err); - return cr; - } - - void setSampleRate (double newRate) - { - if (currentSampleRate != newRate) - { - JUCE_ASIO_LOG ("rate change: " + String (currentSampleRate) + " to " + String (newRate)); - long err = asioObject->setSampleRate (newRate); - - if (err == ASE_NoClock && numClockSources > 0) - { - JUCE_ASIO_LOG ("trying to set a clock source.."); - Thread::sleep (10); - err = asioObject->setClockSource (clocks[0].index); - JUCE_ASIO_LOG_ERROR ("setClockSource2", err); - - Thread::sleep (10); - err = asioObject->setSampleRate (newRate); - } - - if (err == 0) - currentSampleRate = newRate; - - // on fail, ignore the attempt to change rate, and run with the current one.. - } - } - - void updateClockSources() - { - zeromem (clocks, sizeof (clocks)); - long numSources = numElementsInArray (clocks); - asioObject->getClockSources (clocks, &numSources); - numClockSources = (int) numSources; - - bool isSourceSet = false; - - // careful not to remove this loop because it does more than just logging! - for (int i = 0; i < numClockSources; ++i) - { - String s ("clock: "); - s += clocks[i].name; - - if (clocks[i].isCurrentSource) - { - isSourceSet = true; - s << " (cur)"; - } - - JUCE_ASIO_LOG (s); - } - - if (numClockSources > 1 && ! isSourceSet) - { - JUCE_ASIO_LOG ("setting clock source"); - long err = asioObject->setClockSource (clocks[0].index); - JUCE_ASIO_LOG_ERROR ("setClockSource1", err); - Thread::sleep (20); - } - else - { - if (numClockSources == 0) - JUCE_ASIO_LOG ("no clock sources!"); - } - } - - void readLatencies() - { - inputLatency = outputLatency = 0; - - if (asioObject->getLatencies (&inputLatency, &outputLatency) != 0) - JUCE_ASIO_LOG ("getLatencies() failed"); - else - JUCE_ASIO_LOG ("Latencies: in = " + String ((int) inputLatency) + ", out = " + String ((int) outputLatency)); - } - - void createDummyBuffers (long preferredSize) - { - numActiveInputChans = 0; - numActiveOutputChans = 0; - - ASIOBufferInfo* info = bufferInfos; - int numChans = 0; - - for (int i = 0; i < jmin (2, (int) totalNumInputChans); ++i) - { - info->isInput = 1; - info->channelNum = i; - info->buffers[0] = info->buffers[1] = nullptr; - ++info; - ++numChans; - } - - const int outputBufferIndex = numChans; - - for (int i = 0; i < jmin (2, (int) totalNumOutputChans); ++i) - { - info->isInput = 0; - info->channelNum = i; - info->buffers[0] = info->buffers[1] = nullptr; - ++info; - ++numChans; - } - - setCallbackFunctions(); - - JUCE_ASIO_LOG ("creating buffers (dummy): " + String (numChans) + ", " + String ((int) preferredSize)); - - if (preferredSize > 0) - { - long err = asioObject->createBuffers (bufferInfos, numChans, preferredSize, &callbacks); - JUCE_ASIO_LOG_ERROR ("dummy buffers", err); - } - - long newInps = 0, newOuts = 0; - asioObject->getChannels (&newInps, &newOuts); - - if (totalNumInputChans != newInps || totalNumOutputChans != newOuts) - { - totalNumInputChans = newInps; - totalNumOutputChans = newOuts; - - JUCE_ASIO_LOG (String ((int) totalNumInputChans) + " in; " + String ((int) totalNumOutputChans) + " out"); - } - - updateSampleRates(); - reloadChannelNames(); - - for (int i = 0; i < totalNumOutputChans; ++i) - { - ASIOChannelInfo channelInfo = { 0 }; - channelInfo.channel = i; - channelInfo.isInput = 0; - asioObject->getChannelInfo (&channelInfo); - - outputFormat[i] = ASIOSampleFormat (channelInfo.type); - - if (i < 2) - { - // clear the channels that are used with the dummy stuff - outputFormat[i].clear (bufferInfos [outputBufferIndex + i].buffers[0], preferredBufferSize); - outputFormat[i].clear (bufferInfos [outputBufferIndex + i].buffers[1], preferredBufferSize); - } - } - } - - void removeCurrentDriver() - { - if (asioObject != nullptr) - { - asioObject->Release(); - asioObject = nullptr; - } - } - - bool loadDriver() - { - removeCurrentDriver(); - - bool crashed = false; - bool ok = tryCreatingDriver (crashed); - - if (crashed) - JUCE_ASIO_LOG ("** Driver crashed while being opened"); - - return ok; - } - - bool tryCreatingDriver (bool& crashed) - { - #if ! JUCE_MINGW - __try - #endif - { - return CoCreateInstance (classId, 0, CLSCTX_INPROC_SERVER, - classId, (void**) &asioObject) == S_OK; - } - #if ! JUCE_MINGW - __except (EXCEPTION_EXECUTE_HANDLER) { crashed = true; } - return false; - #endif - } - - String getLastDriverError() const - { - jassert (asioObject != nullptr); - char buffer [512] = { 0 }; - asioObject->getErrorMessage (buffer); - return String (buffer, sizeof (buffer) - 1); - } - - String initDriver() - { - if (asioObject == nullptr) - return "No Driver"; - - const bool initOk = !! asioObject->init (juce_messageWindowHandle); - String driverError; - - // Get error message if init() failed, or if it's a buggy Denon driver, - // which returns true from init() even when it fails. - if ((! initOk) || getName().containsIgnoreCase ("denon dj")) - driverError = getLastDriverError(); - - if ((! initOk) && driverError.isEmpty()) - driverError = "Driver failed to initialise"; - - if (driverError.isEmpty()) - { - char buffer [512]; - asioObject->getDriverName (buffer); // just in case any flimsy drivers expect this to be called.. - } - - return driverError; - } - - String openDevice() - { - // open the device and get its info.. - JUCE_ASIO_LOG ("opening device: " + getName()); - - needToReset = false; - outputChannelNames.clear(); - inputChannelNames.clear(); - bufferSizes.clear(); - sampleRates.clear(); - deviceIsOpen = false; - totalNumInputChans = 0; - totalNumOutputChans = 0; - numActiveInputChans = 0; - numActiveOutputChans = 0; - xruns = 0; - currentCallback = nullptr; - - error.clear(); - - if (getName().isEmpty()) - return error; - - long err = 0; - - if (loadDriver()) - { - if ((error = initDriver()).isEmpty()) - { - numActiveInputChans = 0; - numActiveOutputChans = 0; - totalNumInputChans = 0; - totalNumOutputChans = 0; - - if (asioObject != nullptr - && (err = asioObject->getChannels (&totalNumInputChans, &totalNumOutputChans)) == 0) - { - JUCE_ASIO_LOG (String ((int) totalNumInputChans) + " in, " + String ((int) totalNumOutputChans) + " out"); - - const int chansToAllocate = totalNumInputChans + totalNumOutputChans + 4; - bufferInfos.calloc (chansToAllocate); - inBuffers.calloc (chansToAllocate); - outBuffers.calloc (chansToAllocate); - inputFormat.calloc (chansToAllocate); - outputFormat.calloc (chansToAllocate); - - if ((err = refreshBufferSizes()) == 0) - { - addBufferSizes (minBufferSize, maxBufferSize, preferredBufferSize, bufferGranularity); - - double currentRate = getSampleRate(); - - if (currentRate < 1.0 || currentRate > 192001.0) - { - JUCE_ASIO_LOG ("setting default sample rate"); - err = asioObject->setSampleRate (44100.0); - JUCE_ASIO_LOG_ERROR ("setting sample rate", err); - - currentRate = getSampleRate(); - } - - currentSampleRate = currentRate; - - postOutput = (asioObject->outputReady() == 0); - if (postOutput) - JUCE_ASIO_LOG ("outputReady true"); - - updateSampleRates(); - - readLatencies(); // ..doing these steps because cubase does so at this stage - createDummyBuffers (preferredBufferSize); // in initialisation, and some devices fail if we don't. - readLatencies(); - - // start and stop because cubase does it.. - err = asioObject->start(); - // ignore an error here, as it might start later after setting other stuff up - JUCE_ASIO_LOG_ERROR ("start", err); - - Thread::sleep (80); - asioObject->stop(); - } - else - { - error = "Can't detect buffer sizes"; - } - } - else - { - error = "Can't detect asio channels"; - } - } - } - else - { - error = "No such device"; - } - - if (error.isNotEmpty()) - { - JUCE_ASIO_LOG_ERROR (error, err); - disposeBuffers(); - removeCurrentDriver(); - } - else - { - JUCE_ASIO_LOG ("device open"); - } - - deviceIsOpen = false; - needToReset = false; - stopTimer(); - return error; - } - - void disposeBuffers() - { - if (asioObject != nullptr && buffersCreated) - { - buffersCreated = false; - asioObject->disposeBuffers(); - } - } - - //============================================================================== - void JUCE_ASIOCALLBACK callback (const long index) - { - if (isStarted) - { - bufferIndex = index; - processBuffer(); - } - else - { - if (postOutput && (asioObject != nullptr)) - asioObject->outputReady(); - } - - calledback = true; - } - - void processBuffer() - { - const ASIOBufferInfo* const infos = bufferInfos; - const int bi = bufferIndex; - - const ScopedLock sl (callbackLock); - - if (bi >= 0) - { - const int samps = currentBlockSizeSamples; - - if (currentCallback != nullptr) - { - for (int i = 0; i < numActiveInputChans; ++i) - { - jassert (inBuffers[i] != nullptr); - inputFormat[i].convertToFloat (infos[i].buffers[bi], inBuffers[i], samps); - } - - currentCallback->audioDeviceIOCallback (const_cast (inBuffers.getData()), numActiveInputChans, - outBuffers, numActiveOutputChans, samps); - - for (int i = 0; i < numActiveOutputChans; ++i) - { - jassert (outBuffers[i] != nullptr); - outputFormat[i].convertFromFloat (outBuffers[i], infos [numActiveInputChans + i].buffers[bi], samps); - } - } - else - { - for (int i = 0; i < numActiveOutputChans; ++i) - outputFormat[i].clear (infos[numActiveInputChans + i].buffers[bi], samps); - } - } - - if (postOutput) - asioObject->outputReady(); - } - - long asioMessagesCallback (long selector, long value) - { - switch (selector) - { - case kAsioSelectorSupported: - if (value == kAsioResetRequest || value == kAsioEngineVersion || value == kAsioResyncRequest - || value == kAsioLatenciesChanged || value == kAsioSupportsInputMonitor || value == kAsioOverload) - return 1; - break; - - case kAsioBufferSizeChange: JUCE_ASIO_LOG ("kAsioBufferSizeChange"); resetRequest(); return 1; - case kAsioResetRequest: JUCE_ASIO_LOG ("kAsioResetRequest"); resetRequest(); return 1; - case kAsioResyncRequest: JUCE_ASIO_LOG ("kAsioResyncRequest"); resetRequest(); return 1; - case kAsioLatenciesChanged: JUCE_ASIO_LOG ("kAsioLatenciesChanged"); return 1; - case kAsioEngineVersion: return 2; - - case kAsioSupportsTimeInfo: - case kAsioSupportsTimeCode: return 0; - case kAsioOverload: xruns++; return 1; - } - - return 0; - } - - //============================================================================== - template - struct ASIOCallbackFunctions - { - static ASIOTime* JUCE_ASIOCALLBACK bufferSwitchTimeInfoCallback (ASIOTime*, long index, long) - { - if (currentASIODev[deviceIndex] != nullptr) - currentASIODev[deviceIndex]->callback (index); - - return nullptr; - } - - static void JUCE_ASIOCALLBACK bufferSwitchCallback (long index, long) - { - if (currentASIODev[deviceIndex] != nullptr) - currentASIODev[deviceIndex]->callback (index); - } - - static long JUCE_ASIOCALLBACK asioMessagesCallback (long selector, long value, void*, double*) - { - return currentASIODev[deviceIndex] != nullptr - ? currentASIODev[deviceIndex]->asioMessagesCallback (selector, value) - : 0; - } - - static void JUCE_ASIOCALLBACK sampleRateChangedCallback (ASIOSampleRate) - { - if (currentASIODev[deviceIndex] != nullptr) - currentASIODev[deviceIndex]->resetRequest(); - } - - static void setCallbacks (ASIOCallbacks& callbacks) noexcept - { - callbacks.bufferSwitch = &bufferSwitchCallback; - callbacks.asioMessage = &asioMessagesCallback; - callbacks.bufferSwitchTimeInfo = &bufferSwitchTimeInfoCallback; - callbacks.sampleRateDidChange = &sampleRateChangedCallback; - } - - static void setCallbacksForDevice (ASIOCallbacks& callbacks, ASIOAudioIODevice* device) noexcept - { - if (currentASIODev[deviceIndex] == device) - setCallbacks (callbacks); - else - ASIOCallbackFunctions::setCallbacksForDevice (callbacks, device); - } - }; - - void setCallbackFunctions() noexcept - { - /**/ if (currentASIODev[0] == this) ASIOCallbackFunctions<0>::setCallbacks (callbacks); - else if (currentASIODev[1] == this) ASIOCallbackFunctions<1>::setCallbacks (callbacks); - else if (currentASIODev[2] == this) ASIOCallbackFunctions<2>::setCallbacks (callbacks); - else if (currentASIODev[3] == this) ASIOCallbackFunctions<3>::setCallbacks (callbacks); - else jassertfalse; - } - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ASIOAudioIODevice) -}; - -template <> -struct ASIOAudioIODevice::ASIOCallbackFunctions -{ - static void setCallbacksForDevice (ASIOCallbacks&, ASIOAudioIODevice*) noexcept {} -}; - -//============================================================================== -class ASIOAudioIODeviceType : public AudioIODeviceType -{ -public: - ASIOAudioIODeviceType() : AudioIODeviceType ("ASIO") {} - - //============================================================================== - void scanForDevices() - { - hasScanned = true; - - deviceNames.clear(); - classIds.clear(); - - HKEY hk = 0; - int index = 0; - - if (RegOpenKey (HKEY_LOCAL_MACHINE, _T("software\\asio"), &hk) == ERROR_SUCCESS) - { - TCHAR name [256]; - - while (RegEnumKey (hk, index++, name, numElementsInArray (name)) == ERROR_SUCCESS) - addDriverInfo (name, hk); - - RegCloseKey (hk); - } - } - - StringArray getDeviceNames (bool /*wantInputNames*/) const - { - jassert (hasScanned); // need to call scanForDevices() before doing this - - return deviceNames; - } - - int getDefaultDeviceIndex (bool) const - { - jassert (hasScanned); // need to call scanForDevices() before doing this - - for (int i = deviceNames.size(); --i >= 0;) - if (deviceNames[i].containsIgnoreCase ("asio4all")) - return i; // asio4all is a safe choice for a default.. - - #if JUCE_DEBUG - if (deviceNames.size() > 1 && deviceNames[0].containsIgnoreCase ("digidesign")) - return 1; // (the digi m-box driver crashes the app when you run - // it in the debugger, which can be a bit annoying) - #endif - - return 0; - } - - static int findFreeSlot() - { - for (int i = 0; i < numElementsInArray (currentASIODev); ++i) - if (currentASIODev[i] == 0) - return i; - - jassertfalse; // unfortunately you can only have a finite number - // of ASIO devices open at the same time.. - return -1; - } - - int getIndexOfDevice (AudioIODevice* d, bool /*asInput*/) const - { - jassert (hasScanned); // need to call scanForDevices() before doing this - - return d == nullptr ? -1 : deviceNames.indexOf (d->getName()); - } - - bool hasSeparateInputsAndOutputs() const { return false; } - - AudioIODevice* createDevice (const String& outputDeviceName, - const String& inputDeviceName) - { - // ASIO can't open two different devices for input and output - they must be the same one. - jassert (inputDeviceName == outputDeviceName || outputDeviceName.isEmpty() || inputDeviceName.isEmpty()); - jassert (hasScanned); // need to call scanForDevices() before doing this - - const String deviceName (outputDeviceName.isNotEmpty() ? outputDeviceName - : inputDeviceName); - const int index = deviceNames.indexOf (deviceName); - - if (index >= 0) - { - const int freeSlot = findFreeSlot(); - - if (freeSlot >= 0) - return new ASIOAudioIODevice (this, deviceName, - classIds.getReference (index), freeSlot); - } - - return nullptr; - } - - void sendDeviceChangeToListeners() - { - callDeviceChangeListeners(); - } - - JUCE_DECLARE_WEAK_REFERENCEABLE (ASIOAudioIODeviceType) - -private: - StringArray deviceNames; - Array classIds; - - bool hasScanned = false; - - //============================================================================== - static bool checkClassIsOk (const String& classId) - { - HKEY hk = 0; - bool ok = false; - - if (RegOpenKey (HKEY_CLASSES_ROOT, _T("clsid"), &hk) == ERROR_SUCCESS) - { - int index = 0; - TCHAR name [512]; - - while (RegEnumKey (hk, index++, name, numElementsInArray (name)) == ERROR_SUCCESS) - { - if (classId.equalsIgnoreCase (name)) - { - HKEY subKey, pathKey; - - if (RegOpenKeyEx (hk, name, 0, KEY_READ, &subKey) == ERROR_SUCCESS) - { - if (RegOpenKeyEx (subKey, _T("InprocServer32"), 0, KEY_READ, &pathKey) == ERROR_SUCCESS) - { - TCHAR pathName [1024] = { 0 }; - DWORD dtype = REG_SZ; - DWORD dsize = sizeof (pathName); - - if (RegQueryValueEx (pathKey, 0, 0, &dtype, (LPBYTE) pathName, &dsize) == ERROR_SUCCESS) - // In older code, this used to check for the existence of the file, but there are situations - // where our process doesn't have access to it, but where the driver still loads ok.. - ok = (pathName[0] != 0); - - RegCloseKey (pathKey); - } - - RegCloseKey (subKey); - } - - break; - } - } - - RegCloseKey (hk); - } - - return ok; - } - - //============================================================================== - void addDriverInfo (const String& keyName, HKEY hk) - { - HKEY subKey; - - if (RegOpenKeyEx (hk, keyName.toWideCharPointer(), 0, KEY_READ, &subKey) == ERROR_SUCCESS) - { - TCHAR buf [256] = { 0 }; - DWORD dtype = REG_SZ; - DWORD dsize = sizeof (buf); - - if (RegQueryValueEx (subKey, _T("clsid"), 0, &dtype, (LPBYTE) buf, &dsize) == ERROR_SUCCESS) - { - if (dsize > 0 && checkClassIsOk (buf)) - { - CLSID classId; - if (CLSIDFromString ((LPOLESTR) buf, &classId) == S_OK) - { - dtype = REG_SZ; - dsize = sizeof (buf); - String deviceName; - - if (RegQueryValueEx (subKey, _T("description"), 0, &dtype, (LPBYTE) buf, &dsize) == ERROR_SUCCESS) - deviceName = buf; - else - deviceName = keyName; - - JUCE_ASIO_LOG ("found " + deviceName); - deviceNames.add (deviceName); - classIds.add (classId); - } - } - - RegCloseKey (subKey); - } - } - } - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ASIOAudioIODeviceType) -}; - -void sendASIODeviceChangeToListeners (ASIOAudioIODeviceType* type) -{ - if (type != nullptr) - type->sendDeviceChangeToListeners(); -} - -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ASIO() -{ - return new ASIOAudioIODeviceType(); -} - -} // namespace juce diff --git a/source/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp b/source/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp deleted file mode 100644 index 10ea41aba..000000000 --- a/source/modules/juce_audio_devices/native/juce_win32_DirectSound.cpp +++ /dev/null @@ -1,1301 +0,0 @@ -/* - ============================================================================== - - 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. - - ============================================================================== -*/ - -extern "C" -{ - // Declare just the minimum number of interfaces for the DSound objects that we need.. - typedef struct typeDSBUFFERDESC - { - DWORD dwSize; - DWORD dwFlags; - DWORD dwBufferBytes; - DWORD dwReserved; - LPWAVEFORMATEX lpwfxFormat; - GUID guid3DAlgorithm; - } DSBUFFERDESC; - - struct IDirectSoundBuffer; - - #undef INTERFACE - #define INTERFACE IDirectSound - DECLARE_INTERFACE_(IDirectSound, IUnknown) - { - STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID*) PURE; - STDMETHOD_(ULONG,AddRef) (THIS) PURE; - STDMETHOD_(ULONG,Release) (THIS) PURE; - STDMETHOD(CreateSoundBuffer) (THIS_ DSBUFFERDESC*, IDirectSoundBuffer**, LPUNKNOWN) PURE; - STDMETHOD(GetCaps) (THIS_ void*) PURE; - STDMETHOD(DuplicateSoundBuffer) (THIS_ IDirectSoundBuffer*, IDirectSoundBuffer**) PURE; - STDMETHOD(SetCooperativeLevel) (THIS_ HWND, DWORD) PURE; - STDMETHOD(Compact) (THIS) PURE; - STDMETHOD(GetSpeakerConfig) (THIS_ LPDWORD) PURE; - STDMETHOD(SetSpeakerConfig) (THIS_ DWORD) PURE; - STDMETHOD(Initialize) (THIS_ const GUID*) PURE; - }; - - #undef INTERFACE - #define INTERFACE IDirectSoundBuffer - DECLARE_INTERFACE_(IDirectSoundBuffer, IUnknown) - { - STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID*) PURE; - STDMETHOD_(ULONG,AddRef) (THIS) PURE; - STDMETHOD_(ULONG,Release) (THIS) PURE; - STDMETHOD(GetCaps) (THIS_ void*) PURE; - STDMETHOD(GetCurrentPosition) (THIS_ LPDWORD, LPDWORD) PURE; - STDMETHOD(GetFormat) (THIS_ LPWAVEFORMATEX, DWORD, LPDWORD) PURE; - STDMETHOD(GetVolume) (THIS_ LPLONG) PURE; - STDMETHOD(GetPan) (THIS_ LPLONG) PURE; - STDMETHOD(GetFrequency) (THIS_ LPDWORD) PURE; - STDMETHOD(GetStatus) (THIS_ LPDWORD) PURE; - STDMETHOD(Initialize) (THIS_ IDirectSound*, DSBUFFERDESC*) PURE; - STDMETHOD(Lock) (THIS_ DWORD, DWORD, LPVOID*, LPDWORD, LPVOID*, LPDWORD, DWORD) PURE; - STDMETHOD(Play) (THIS_ DWORD, DWORD, DWORD) PURE; - STDMETHOD(SetCurrentPosition) (THIS_ DWORD) PURE; - STDMETHOD(SetFormat) (THIS_ const WAVEFORMATEX*) PURE; - STDMETHOD(SetVolume) (THIS_ LONG) PURE; - STDMETHOD(SetPan) (THIS_ LONG) PURE; - STDMETHOD(SetFrequency) (THIS_ DWORD) PURE; - STDMETHOD(Stop) (THIS) PURE; - STDMETHOD(Unlock) (THIS_ LPVOID, DWORD, LPVOID, DWORD) PURE; - STDMETHOD(Restore) (THIS) PURE; - }; - - //============================================================================== - typedef struct typeDSCBUFFERDESC - { - DWORD dwSize; - DWORD dwFlags; - DWORD dwBufferBytes; - DWORD dwReserved; - LPWAVEFORMATEX lpwfxFormat; - } DSCBUFFERDESC; - - struct IDirectSoundCaptureBuffer; - - #undef INTERFACE - #define INTERFACE IDirectSoundCapture - DECLARE_INTERFACE_(IDirectSoundCapture, IUnknown) - { - STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID*) PURE; - STDMETHOD_(ULONG,AddRef) (THIS) PURE; - STDMETHOD_(ULONG,Release) (THIS) PURE; - STDMETHOD(CreateCaptureBuffer) (THIS_ DSCBUFFERDESC*, IDirectSoundCaptureBuffer**, LPUNKNOWN) PURE; - STDMETHOD(GetCaps) (THIS_ void*) PURE; - STDMETHOD(Initialize) (THIS_ const GUID*) PURE; - }; - - #undef INTERFACE - #define INTERFACE IDirectSoundCaptureBuffer - DECLARE_INTERFACE_(IDirectSoundCaptureBuffer, IUnknown) - { - STDMETHOD(QueryInterface) (THIS_ REFIID, LPVOID*) PURE; - STDMETHOD_(ULONG,AddRef) (THIS) PURE; - STDMETHOD_(ULONG,Release) (THIS) PURE; - STDMETHOD(GetCaps) (THIS_ void*) PURE; - STDMETHOD(GetCurrentPosition) (THIS_ LPDWORD, LPDWORD) PURE; - STDMETHOD(GetFormat) (THIS_ LPWAVEFORMATEX, DWORD, LPDWORD) PURE; - STDMETHOD(GetStatus) (THIS_ LPDWORD) PURE; - STDMETHOD(Initialize) (THIS_ IDirectSoundCapture*, DSCBUFFERDESC*) PURE; - STDMETHOD(Lock) (THIS_ DWORD, DWORD, LPVOID*, LPDWORD, LPVOID*, LPDWORD, DWORD) PURE; - STDMETHOD(Start) (THIS_ DWORD) PURE; - STDMETHOD(Stop) (THIS) PURE; - STDMETHOD(Unlock) (THIS_ LPVOID, DWORD, LPVOID, DWORD) PURE; - }; - - #undef INTERFACE -} - -namespace juce -{ - -//============================================================================== -namespace DSoundLogging -{ - String getErrorMessage (HRESULT hr) - { - const char* result = nullptr; - - switch (hr) - { - case MAKE_HRESULT(1, 0x878, 10): result = "Device already allocated"; break; - case MAKE_HRESULT(1, 0x878, 30): result = "Control unavailable"; break; - case E_INVALIDARG: result = "Invalid parameter"; break; - case MAKE_HRESULT(1, 0x878, 50): result = "Invalid call"; break; - case E_FAIL: result = "Generic error"; break; - case MAKE_HRESULT(1, 0x878, 70): result = "Priority level error"; break; - case E_OUTOFMEMORY: result = "Out of memory"; break; - case MAKE_HRESULT(1, 0x878, 100): result = "Bad format"; break; - case E_NOTIMPL: result = "Unsupported function"; break; - case MAKE_HRESULT(1, 0x878, 120): result = "No driver"; break; - case MAKE_HRESULT(1, 0x878, 130): result = "Already initialised"; break; - case CLASS_E_NOAGGREGATION: result = "No aggregation"; break; - case MAKE_HRESULT(1, 0x878, 150): result = "Buffer lost"; break; - case MAKE_HRESULT(1, 0x878, 160): result = "Another app has priority"; break; - case MAKE_HRESULT(1, 0x878, 170): result = "Uninitialised"; break; - case E_NOINTERFACE: result = "No interface"; break; - case S_OK: result = "No error"; break; - default: return "Unknown error: " + String ((int) hr); - } - - return result; - } - - //============================================================================== - #if JUCE_DIRECTSOUND_LOGGING - static void logMessage (String message) - { - message = "DSOUND: " + message; - DBG (message); - Logger::writeToLog (message); - } - - static void logError (HRESULT hr, int lineNum) - { - if (FAILED (hr)) - { - String error ("Error at line "); - error << lineNum << ": " << getErrorMessage (hr); - logMessage (error); - } - } - - #define JUCE_DS_LOG(a) DSoundLogging::logMessage(a); - #define JUCE_DS_LOG_ERROR(a) DSoundLogging::logError(a, __LINE__); - #else - #define JUCE_DS_LOG(a) - #define JUCE_DS_LOG_ERROR(a) - #endif -} - -//============================================================================== -namespace -{ - #define DSOUND_FUNCTION(functionName, params) \ - typedef HRESULT (WINAPI *type##functionName) params; \ - static type##functionName ds##functionName = nullptr; - - #define DSOUND_FUNCTION_LOAD(functionName) \ - ds##functionName = (type##functionName) GetProcAddress (h, #functionName); \ - jassert (ds##functionName != nullptr); - - typedef BOOL (CALLBACK *LPDSENUMCALLBACKW) (LPGUID, LPCWSTR, LPCWSTR, LPVOID); - typedef BOOL (CALLBACK *LPDSENUMCALLBACKA) (LPGUID, LPCSTR, LPCSTR, LPVOID); - - DSOUND_FUNCTION (DirectSoundCreate, (const GUID*, IDirectSound**, LPUNKNOWN)) - DSOUND_FUNCTION (DirectSoundCaptureCreate, (const GUID*, IDirectSoundCapture**, LPUNKNOWN)) - DSOUND_FUNCTION (DirectSoundEnumerateW, (LPDSENUMCALLBACKW, LPVOID)) - DSOUND_FUNCTION (DirectSoundCaptureEnumerateW, (LPDSENUMCALLBACKW, LPVOID)) - - void initialiseDSoundFunctions() - { - if (dsDirectSoundCreate == nullptr) - { - HMODULE h = LoadLibraryA ("dsound.dll"); - - DSOUND_FUNCTION_LOAD (DirectSoundCreate) - DSOUND_FUNCTION_LOAD (DirectSoundCaptureCreate) - DSOUND_FUNCTION_LOAD (DirectSoundEnumerateW) - DSOUND_FUNCTION_LOAD (DirectSoundCaptureEnumerateW) - } - } - - // the overall size of buffer used is this value x the block size - enum { blocksPerOverallBuffer = 16 }; -} - -//============================================================================== -class DSoundInternalOutChannel -{ -public: - DSoundInternalOutChannel (const String& name_, const GUID& guid_, int rate, - int bufferSize, float* left, float* right) - : bitDepth (16), name (name_), guid (guid_), sampleRate (rate), - bufferSizeSamples (bufferSize), leftBuffer (left), rightBuffer (right), - pDirectSound (nullptr), pOutputBuffer (nullptr) - { - } - - ~DSoundInternalOutChannel() - { - close(); - } - - void close() - { - if (pOutputBuffer != nullptr) - { - JUCE_DS_LOG ("closing output: " + name); - HRESULT hr = pOutputBuffer->Stop(); - JUCE_DS_LOG_ERROR (hr); ignoreUnused (hr); - - pOutputBuffer->Release(); - pOutputBuffer = nullptr; - } - - if (pDirectSound != nullptr) - { - pDirectSound->Release(); - pDirectSound = nullptr; - } - } - - String open() - { - JUCE_DS_LOG ("opening output: " + name + " rate=" + String (sampleRate) - + " bits=" + String (bitDepth) + " buf=" + String (bufferSizeSamples)); - - pDirectSound = nullptr; - pOutputBuffer = nullptr; - writeOffset = 0; - xruns = 0; - - firstPlayTime = true; - lastPlayTime = 0; - - String error; - HRESULT hr = E_NOINTERFACE; - - if (dsDirectSoundCreate != nullptr) - hr = dsDirectSoundCreate (&guid, &pDirectSound, nullptr); - - if (SUCCEEDED (hr)) - { - bytesPerBuffer = (bufferSizeSamples * (bitDepth >> 2)) & ~15; - ticksPerBuffer = bytesPerBuffer * Time::getHighResolutionTicksPerSecond() / (sampleRate * (bitDepth >> 2)); - totalBytesPerBuffer = (blocksPerOverallBuffer * bytesPerBuffer) & ~15; - const int numChannels = 2; - - hr = pDirectSound->SetCooperativeLevel (GetDesktopWindow(), 2 /* DSSCL_PRIORITY */); - JUCE_DS_LOG_ERROR (hr); - - if (SUCCEEDED (hr)) - { - IDirectSoundBuffer* pPrimaryBuffer; - - DSBUFFERDESC primaryDesc = { 0 }; - primaryDesc.dwSize = sizeof (DSBUFFERDESC); - primaryDesc.dwFlags = 1 /* DSBCAPS_PRIMARYBUFFER */; - primaryDesc.dwBufferBytes = 0; - primaryDesc.lpwfxFormat = 0; - - JUCE_DS_LOG ("co-op level set"); - hr = pDirectSound->CreateSoundBuffer (&primaryDesc, &pPrimaryBuffer, 0); - JUCE_DS_LOG_ERROR (hr); - - if (SUCCEEDED (hr)) - { - WAVEFORMATEX wfFormat; - wfFormat.wFormatTag = WAVE_FORMAT_PCM; - wfFormat.nChannels = (unsigned short) numChannels; - wfFormat.nSamplesPerSec = (DWORD) sampleRate; - wfFormat.wBitsPerSample = (unsigned short) bitDepth; - wfFormat.nBlockAlign = (unsigned short) (wfFormat.nChannels * wfFormat.wBitsPerSample / 8); - wfFormat.nAvgBytesPerSec = wfFormat.nSamplesPerSec * wfFormat.nBlockAlign; - wfFormat.cbSize = 0; - - hr = pPrimaryBuffer->SetFormat (&wfFormat); - JUCE_DS_LOG_ERROR (hr); - - if (SUCCEEDED (hr)) - { - DSBUFFERDESC secondaryDesc = { 0 }; - secondaryDesc.dwSize = sizeof (DSBUFFERDESC); - secondaryDesc.dwFlags = 0x8000 /* DSBCAPS_GLOBALFOCUS */ - | 0x10000 /* DSBCAPS_GETCURRENTPOSITION2 */; - secondaryDesc.dwBufferBytes = (DWORD) totalBytesPerBuffer; - secondaryDesc.lpwfxFormat = &wfFormat; - - hr = pDirectSound->CreateSoundBuffer (&secondaryDesc, &pOutputBuffer, 0); - JUCE_DS_LOG_ERROR (hr); - - if (SUCCEEDED (hr)) - { - JUCE_DS_LOG ("buffer created"); - - DWORD dwDataLen; - unsigned char* pDSBuffData; - - hr = pOutputBuffer->Lock (0, (DWORD) totalBytesPerBuffer, - (LPVOID*) &pDSBuffData, &dwDataLen, 0, 0, 0); - JUCE_DS_LOG_ERROR (hr); - - if (SUCCEEDED (hr)) - { - zeromem (pDSBuffData, dwDataLen); - - hr = pOutputBuffer->Unlock (pDSBuffData, dwDataLen, 0, 0); - - if (SUCCEEDED (hr)) - { - hr = pOutputBuffer->SetCurrentPosition (0); - - if (SUCCEEDED (hr)) - { - hr = pOutputBuffer->Play (0, 0, 1 /* DSBPLAY_LOOPING */); - - if (SUCCEEDED (hr)) - return {}; - } - } - } - } - } - } - } - } - - error = DSoundLogging::getErrorMessage (hr); - close(); - return error; - } - - void synchronisePosition() - { - if (pOutputBuffer != nullptr) - { - DWORD playCursor; - pOutputBuffer->GetCurrentPosition (&playCursor, &writeOffset); - } - } - - bool service() - { - if (pOutputBuffer == 0) - return true; - - DWORD playCursor, writeCursor; - - for (;;) - { - HRESULT hr = pOutputBuffer->GetCurrentPosition (&playCursor, &writeCursor); - - if (hr == MAKE_HRESULT (1, 0x878, 150)) // DSERR_BUFFERLOST - { - pOutputBuffer->Restore(); - continue; - } - - if (SUCCEEDED (hr)) - break; - - JUCE_DS_LOG_ERROR (hr); - jassertfalse; - return true; - } - - auto currentPlayTime = Time::getHighResolutionTicks (); - if (! firstPlayTime) - { - auto expectedBuffers = (currentPlayTime - lastPlayTime) / ticksPerBuffer; - - playCursor += static_cast (expectedBuffers * bytesPerBuffer); - } - else - firstPlayTime = false; - - lastPlayTime = currentPlayTime; - - int playWriteGap = (int) (writeCursor - playCursor); - if (playWriteGap < 0) - playWriteGap += totalBytesPerBuffer; - - int bytesEmpty = (int) (playCursor - writeOffset); - if (bytesEmpty < 0) - bytesEmpty += totalBytesPerBuffer; - - if (bytesEmpty > (totalBytesPerBuffer - playWriteGap)) - { - writeOffset = writeCursor; - bytesEmpty = totalBytesPerBuffer - playWriteGap; - - // buffer underflow - xruns++; - } - - if (bytesEmpty >= bytesPerBuffer) - { - int* buf1 = nullptr; - int* buf2 = nullptr; - DWORD dwSize1 = 0; - DWORD dwSize2 = 0; - - HRESULT hr = pOutputBuffer->Lock (writeOffset, (DWORD) bytesPerBuffer, - (void**) &buf1, &dwSize1, - (void**) &buf2, &dwSize2, 0); - - if (hr == MAKE_HRESULT (1, 0x878, 150)) // DSERR_BUFFERLOST - { - pOutputBuffer->Restore(); - - hr = pOutputBuffer->Lock (writeOffset, (DWORD) bytesPerBuffer, - (void**) &buf1, &dwSize1, - (void**) &buf2, &dwSize2, 0); - } - - if (SUCCEEDED (hr)) - { - if (bitDepth == 16) - { - const float* left = leftBuffer; - const float* right = rightBuffer; - int samples1 = (int) (dwSize1 >> 2); - int samples2 = (int) (dwSize2 >> 2); - - if (left == nullptr) - { - for (int* dest = buf1; --samples1 >= 0;) *dest++ = convertInputValues (0, *right++); - for (int* dest = buf2; --samples2 >= 0;) *dest++ = convertInputValues (0, *right++); - } - else if (right == nullptr) - { - for (int* dest = buf1; --samples1 >= 0;) *dest++ = convertInputValues (*left++, 0); - for (int* dest = buf2; --samples2 >= 0;) *dest++ = convertInputValues (*left++, 0); - } - else - { - for (int* dest = buf1; --samples1 >= 0;) *dest++ = convertInputValues (*left++, *right++); - for (int* dest = buf2; --samples2 >= 0;) *dest++ = convertInputValues (*left++, *right++); - } - } - else - { - jassertfalse; - } - - writeOffset = (writeOffset + dwSize1 + dwSize2) % totalBytesPerBuffer; - - pOutputBuffer->Unlock (buf1, dwSize1, buf2, dwSize2); - } - else - { - jassertfalse; - JUCE_DS_LOG_ERROR (hr); - } - - bytesEmpty -= bytesPerBuffer; - return true; - } - else - { - return false; - } - } - - int bitDepth, xruns; - bool doneFlag; - -private: - String name; - GUID guid; - int sampleRate, bufferSizeSamples; - float* leftBuffer; - float* rightBuffer; - - IDirectSound* pDirectSound; - IDirectSoundBuffer* pOutputBuffer; - DWORD writeOffset; - int totalBytesPerBuffer, bytesPerBuffer; - unsigned int lastPlayCursor; - - bool firstPlayTime; - int64 lastPlayTime, ticksPerBuffer; - - static inline int convertInputValues (const float l, const float r) noexcept - { - return jlimit (-32768, 32767, roundToInt (32767.0f * r)) << 16 - | (0xffff & jlimit (-32768, 32767, roundToInt (32767.0f * l))); - } - - JUCE_DECLARE_NON_COPYABLE (DSoundInternalOutChannel) -}; - -//============================================================================== -struct DSoundInternalInChannel -{ -public: - DSoundInternalInChannel (const String& name_, const GUID& guid_, int rate, - int bufferSize, float* left, float* right) - : bitDepth (16), name (name_), guid (guid_), sampleRate (rate), - bufferSizeSamples (bufferSize), leftBuffer (left), rightBuffer (right), - pDirectSound (nullptr), pDirectSoundCapture (nullptr), pInputBuffer (nullptr) - { - } - - ~DSoundInternalInChannel() - { - close(); - } - - void close() - { - if (pInputBuffer != nullptr) - { - JUCE_DS_LOG ("closing input: " + name); - HRESULT hr = pInputBuffer->Stop(); - JUCE_DS_LOG_ERROR (hr); ignoreUnused (hr); - - pInputBuffer->Release(); - pInputBuffer = nullptr; - } - - if (pDirectSoundCapture != nullptr) - { - pDirectSoundCapture->Release(); - pDirectSoundCapture = nullptr; - } - - if (pDirectSound != nullptr) - { - pDirectSound->Release(); - pDirectSound = nullptr; - } - } - - String open() - { - JUCE_DS_LOG ("opening input: " + name - + " rate=" + String (sampleRate) + " bits=" + String (bitDepth) + " buf=" + String (bufferSizeSamples)); - - pDirectSound = nullptr; - pDirectSoundCapture = nullptr; - pInputBuffer = nullptr; - readOffset = 0; - totalBytesPerBuffer = 0; - - HRESULT hr = dsDirectSoundCaptureCreate != nullptr - ? dsDirectSoundCaptureCreate (&guid, &pDirectSoundCapture, nullptr) - : E_NOINTERFACE; - - if (SUCCEEDED (hr)) - { - const int numChannels = 2; - bytesPerBuffer = (bufferSizeSamples * (bitDepth >> 2)) & ~15; - totalBytesPerBuffer = (blocksPerOverallBuffer * bytesPerBuffer) & ~15; - - WAVEFORMATEX wfFormat; - wfFormat.wFormatTag = WAVE_FORMAT_PCM; - wfFormat.nChannels = (unsigned short)numChannels; - wfFormat.nSamplesPerSec = (DWORD) sampleRate; - wfFormat.wBitsPerSample = (unsigned short) bitDepth; - wfFormat.nBlockAlign = (unsigned short) (wfFormat.nChannels * (wfFormat.wBitsPerSample / 8)); - wfFormat.nAvgBytesPerSec = wfFormat.nSamplesPerSec * wfFormat.nBlockAlign; - wfFormat.cbSize = 0; - - DSCBUFFERDESC captureDesc = { 0 }; - captureDesc.dwSize = sizeof (DSCBUFFERDESC); - captureDesc.dwFlags = 0; - captureDesc.dwBufferBytes = (DWORD) totalBytesPerBuffer; - captureDesc.lpwfxFormat = &wfFormat; - - JUCE_DS_LOG ("object created"); - hr = pDirectSoundCapture->CreateCaptureBuffer (&captureDesc, &pInputBuffer, 0); - - if (SUCCEEDED (hr)) - { - hr = pInputBuffer->Start (1 /* DSCBSTART_LOOPING */); - - if (SUCCEEDED (hr)) - return {}; - } - } - - JUCE_DS_LOG_ERROR (hr); - const String error (DSoundLogging::getErrorMessage (hr)); - close(); - - return error; - } - - void synchronisePosition() - { - if (pInputBuffer != nullptr) - { - DWORD capturePos; - pInputBuffer->GetCurrentPosition (&capturePos, (DWORD*) &readOffset); - } - } - - bool service() - { - if (pInputBuffer == 0) - return true; - - DWORD capturePos, readPos; - HRESULT hr = pInputBuffer->GetCurrentPosition (&capturePos, &readPos); - JUCE_DS_LOG_ERROR (hr); - - if (FAILED (hr)) - return true; - - int bytesFilled = (int) (readPos - readOffset); - if (bytesFilled < 0) - bytesFilled += totalBytesPerBuffer; - - if (bytesFilled >= bytesPerBuffer) - { - short* buf1 = nullptr; - short* buf2 = nullptr; - DWORD dwsize1 = 0; - DWORD dwsize2 = 0; - - hr = pInputBuffer->Lock ((DWORD) readOffset, (DWORD) bytesPerBuffer, - (void**) &buf1, &dwsize1, - (void**) &buf2, &dwsize2, 0); - - if (SUCCEEDED (hr)) - { - if (bitDepth == 16) - { - const float g = 1.0f / 32768.0f; - - float* destL = leftBuffer; - float* destR = rightBuffer; - int samples1 = (int) (dwsize1 >> 2); - int samples2 = (int) (dwsize2 >> 2); - - if (destL == nullptr) - { - for (const short* src = buf1; --samples1 >= 0;) { ++src; *destR++ = *src++ * g; } - for (const short* src = buf2; --samples2 >= 0;) { ++src; *destR++ = *src++ * g; } - } - else if (destR == nullptr) - { - for (const short* src = buf1; --samples1 >= 0;) { *destL++ = *src++ * g; ++src; } - for (const short* src = buf2; --samples2 >= 0;) { *destL++ = *src++ * g; ++src; } - } - else - { - for (const short* src = buf1; --samples1 >= 0;) { *destL++ = *src++ * g; *destR++ = *src++ * g; } - for (const short* src = buf2; --samples2 >= 0;) { *destL++ = *src++ * g; *destR++ = *src++ * g; } - } - } - else - { - jassertfalse; - } - - readOffset = (readOffset + dwsize1 + dwsize2) % totalBytesPerBuffer; - - pInputBuffer->Unlock (buf1, dwsize1, buf2, dwsize2); - } - else - { - JUCE_DS_LOG_ERROR (hr); - jassertfalse; - } - - bytesFilled -= bytesPerBuffer; - - return true; - } - else - { - return false; - } - } - - unsigned int readOffset; - int bytesPerBuffer, totalBytesPerBuffer; - int bitDepth; - bool doneFlag; - -private: - String name; - GUID guid; - int sampleRate, bufferSizeSamples; - float* leftBuffer; - float* rightBuffer; - - IDirectSound* pDirectSound; - IDirectSoundCapture* pDirectSoundCapture; - IDirectSoundCaptureBuffer* pInputBuffer; - - JUCE_DECLARE_NON_COPYABLE (DSoundInternalInChannel) -}; - -//============================================================================== -class DSoundAudioIODevice : public AudioIODevice, - public Thread -{ -public: - DSoundAudioIODevice (const String& deviceName, - const int outputDeviceIndex_, - const int inputDeviceIndex_) - : AudioIODevice (deviceName, "DirectSound"), - Thread ("Juce DSound"), - outputDeviceIndex (outputDeviceIndex_), - inputDeviceIndex (inputDeviceIndex_), - isOpen_ (false), - isStarted (false), - bufferSizeSamples (0), - sampleRate (0.0), - callback (nullptr) - { - if (outputDeviceIndex_ >= 0) - { - outChannels.add (TRANS("Left")); - outChannels.add (TRANS("Right")); - } - - if (inputDeviceIndex_ >= 0) - { - inChannels.add (TRANS("Left")); - inChannels.add (TRANS("Right")); - } - } - - ~DSoundAudioIODevice() - { - close(); - } - - String open (const BigInteger& inputChannels, - const BigInteger& outputChannels, - double newSampleRate, int newBufferSize) override - { - lastError = openDevice (inputChannels, outputChannels, newSampleRate, newBufferSize); - isOpen_ = lastError.isEmpty(); - - return lastError; - } - - void close() override - { - stop(); - - if (isOpen_) - { - closeDevice(); - isOpen_ = false; - } - } - - bool isOpen() override { return isOpen_ && isThreadRunning(); } - int getCurrentBufferSizeSamples() override { return bufferSizeSamples; } - double getCurrentSampleRate() override { return sampleRate; } - BigInteger getActiveOutputChannels() const override { return enabledOutputs; } - BigInteger getActiveInputChannels() const override { return enabledInputs; } - int getOutputLatencyInSamples() override { return (int) (getCurrentBufferSizeSamples() * 1.5); } - int getInputLatencyInSamples() override { return getOutputLatencyInSamples(); } - StringArray getOutputChannelNames() override { return outChannels; } - StringArray getInputChannelNames() override { return inChannels; } - - Array getAvailableSampleRates() override - { - static const double rates[] = { 44100.0, 48000.0, 88200.0, 96000.0 }; - return Array (rates, numElementsInArray (rates)); - } - - Array getAvailableBufferSizes() override - { - Array r; - int n = 64; - - for (int i = 0; i < 50; ++i) - { - r.add (n); - n += (n < 512) ? 32 - : ((n < 1024) ? 64 - : ((n < 2048) ? 128 : 256)); - } - - return r; - } - - int getDefaultBufferSize() override { return 2560; } - - int getCurrentBitDepth() override - { - int bits = 256; - - for (int i = inChans.size(); --i >= 0;) - bits = jmin (bits, inChans[i]->bitDepth); - - for (int i = outChans.size(); --i >= 0;) - bits = jmin (bits, outChans[i]->bitDepth); - - if (bits > 32) - bits = 16; - - return bits; - } - - void start (AudioIODeviceCallback* call) override - { - if (isOpen_ && call != nullptr && ! isStarted) - { - if (! isThreadRunning()) - { - // something gone wrong and the thread's stopped.. - isOpen_ = false; - return; - } - - call->audioDeviceAboutToStart (this); - - const ScopedLock sl (startStopLock); - callback = call; - isStarted = true; - } - } - - void stop() override - { - if (isStarted) - { - AudioIODeviceCallback* const callbackLocal = callback; - - { - const ScopedLock sl (startStopLock); - isStarted = false; - } - - if (callbackLocal != nullptr) - callbackLocal->audioDeviceStopped(); - } - } - - bool isPlaying() override { return isStarted && isOpen_ && isThreadRunning(); } - String getLastError() override { return lastError; } - - int getXRunCount () const noexcept override - { - return (outChans[0] != nullptr ? outChans[0]->xruns : -1); - } - - //============================================================================== - StringArray inChannels, outChannels; - int outputDeviceIndex, inputDeviceIndex; - -private: - bool isOpen_; - bool isStarted; - String lastError; - - OwnedArray inChans; - OwnedArray outChans; - WaitableEvent startEvent; - - int bufferSizeSamples; - double sampleRate; - BigInteger enabledInputs, enabledOutputs; - AudioSampleBuffer inputBuffers, outputBuffers; - - AudioIODeviceCallback* callback; - CriticalSection startStopLock; - - String openDevice (const BigInteger& inputChannels, - const BigInteger& outputChannels, - double sampleRate_, int bufferSizeSamples_); - - void closeDevice() - { - isStarted = false; - stopThread (5000); - - inChans.clear(); - outChans.clear(); - inputBuffers.setSize (1, 1); - outputBuffers.setSize (1, 1); - } - - void resync() - { - if (! threadShouldExit()) - { - sleep (5); - - for (int i = 0; i < outChans.size(); ++i) - outChans.getUnchecked(i)->synchronisePosition(); - - for (int i = 0; i < inChans.size(); ++i) - inChans.getUnchecked(i)->synchronisePosition(); - } - } - -public: - void run() override - { - while (! threadShouldExit()) - { - if (wait (100)) - break; - } - - const int latencyMs = (int) (bufferSizeSamples * 1000.0 / sampleRate); - const int maxTimeMS = jmax (5, 3 * latencyMs); - - while (! threadShouldExit()) - { - int numToDo = 0; - uint32 startTime = Time::getMillisecondCounter(); - - for (int i = inChans.size(); --i >= 0;) - { - inChans.getUnchecked(i)->doneFlag = false; - ++numToDo; - } - - for (int i = outChans.size(); --i >= 0;) - { - outChans.getUnchecked(i)->doneFlag = false; - ++numToDo; - } - - if (numToDo > 0) - { - const int maxCount = 3; - int count = maxCount; - - for (;;) - { - for (int i = inChans.size(); --i >= 0;) - { - DSoundInternalInChannel* const in = inChans.getUnchecked(i); - - if ((! in->doneFlag) && in->service()) - { - in->doneFlag = true; - --numToDo; - } - } - - for (int i = outChans.size(); --i >= 0;) - { - DSoundInternalOutChannel* const out = outChans.getUnchecked(i); - - if ((! out->doneFlag) && out->service()) - { - out->doneFlag = true; - --numToDo; - } - } - - if (numToDo <= 0) - break; - - if (Time::getMillisecondCounter() > startTime + maxTimeMS) - { - resync(); - break; - } - - if (--count <= 0) - { - Sleep (1); - count = maxCount; - } - - if (threadShouldExit()) - return; - } - } - else - { - sleep (1); - } - - const ScopedLock sl (startStopLock); - - if (isStarted) - { - callback->audioDeviceIOCallback (inputBuffers.getArrayOfReadPointers(), inputBuffers.getNumChannels(), - outputBuffers.getArrayOfWritePointers(), outputBuffers.getNumChannels(), - bufferSizeSamples); - } - else - { - outputBuffers.clear(); - sleep (1); - } - } - } - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DSoundAudioIODevice) -}; - -//============================================================================== -struct DSoundDeviceList -{ - StringArray outputDeviceNames, inputDeviceNames; - Array outputGuids, inputGuids; - - void scan() - { - outputDeviceNames.clear(); - inputDeviceNames.clear(); - outputGuids.clear(); - inputGuids.clear(); - - if (dsDirectSoundEnumerateW != 0) - { - dsDirectSoundEnumerateW (outputEnumProcW, this); - dsDirectSoundCaptureEnumerateW (inputEnumProcW, this); - } - } - - bool operator!= (const DSoundDeviceList& other) const noexcept - { - return outputDeviceNames != other.outputDeviceNames - || inputDeviceNames != other.inputDeviceNames - || outputGuids != other.outputGuids - || inputGuids != other.inputGuids; - } - -private: - static BOOL enumProc (LPGUID lpGUID, String desc, StringArray& names, Array& guids) - { - desc = desc.trim(); - - if (desc.isNotEmpty()) - { - const String origDesc (desc); - - int n = 2; - while (names.contains (desc)) - desc = origDesc + " (" + String (n++) + ")"; - - names.add (desc); - guids.add (lpGUID != nullptr ? *lpGUID : GUID()); - } - - return TRUE; - } - - BOOL outputEnumProc (LPGUID guid, LPCWSTR desc) { return enumProc (guid, desc, outputDeviceNames, outputGuids); } - BOOL inputEnumProc (LPGUID guid, LPCWSTR desc) { return enumProc (guid, desc, inputDeviceNames, inputGuids); } - - static BOOL CALLBACK outputEnumProcW (LPGUID lpGUID, LPCWSTR description, LPCWSTR, LPVOID object) - { - return static_cast (object)->outputEnumProc (lpGUID, description); - } - - static BOOL CALLBACK inputEnumProcW (LPGUID lpGUID, LPCWSTR description, LPCWSTR, LPVOID object) - { - return static_cast (object)->inputEnumProc (lpGUID, description); - } -}; - -//============================================================================== -String DSoundAudioIODevice::openDevice (const BigInteger& inputChannels, - const BigInteger& outputChannels, - double sampleRate_, int bufferSizeSamples_) -{ - closeDevice(); - - sampleRate = sampleRate_; - - if (bufferSizeSamples_ <= 0) - bufferSizeSamples_ = 960; // use as a default size if none is set. - - bufferSizeSamples = bufferSizeSamples_ & ~7; - - DSoundDeviceList dlh; - dlh.scan(); - - enabledInputs = inputChannels; - enabledInputs.setRange (inChannels.size(), - enabledInputs.getHighestBit() + 1 - inChannels.size(), - false); - - inputBuffers.setSize (jmax (1, enabledInputs.countNumberOfSetBits()), bufferSizeSamples); - inputBuffers.clear(); - int numIns = 0; - - for (int i = 0; i <= enabledInputs.getHighestBit(); i += 2) - { - float* left = enabledInputs[i] ? inputBuffers.getWritePointer (numIns++) : nullptr; - float* right = enabledInputs[i + 1] ? inputBuffers.getWritePointer (numIns++) : nullptr; - - if (left != nullptr || right != nullptr) - inChans.add (new DSoundInternalInChannel (dlh.inputDeviceNames [inputDeviceIndex], - dlh.inputGuids [inputDeviceIndex], - (int) sampleRate, bufferSizeSamples, - left, right)); - } - - enabledOutputs = outputChannels; - enabledOutputs.setRange (outChannels.size(), - enabledOutputs.getHighestBit() + 1 - outChannels.size(), - false); - - outputBuffers.setSize (jmax (1, enabledOutputs.countNumberOfSetBits()), bufferSizeSamples); - outputBuffers.clear(); - int numOuts = 0; - - for (int i = 0; i <= enabledOutputs.getHighestBit(); i += 2) - { - float* left = enabledOutputs[i] ? outputBuffers.getWritePointer (numOuts++) : nullptr; - float* right = enabledOutputs[i + 1] ? outputBuffers.getWritePointer (numOuts++) : nullptr; - - if (left != nullptr || right != nullptr) - outChans.add (new DSoundInternalOutChannel (dlh.outputDeviceNames[outputDeviceIndex], - dlh.outputGuids [outputDeviceIndex], - (int) sampleRate, bufferSizeSamples, - left, right)); - } - - String error; - - // boost our priority while opening the devices to try to get better sync between them - const int oldThreadPri = GetThreadPriority (GetCurrentThread()); - const DWORD oldProcPri = GetPriorityClass (GetCurrentProcess()); - SetThreadPriority (GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); - SetPriorityClass (GetCurrentProcess(), REALTIME_PRIORITY_CLASS); - - for (int i = 0; i < outChans.size(); ++i) - { - error = outChans[i]->open(); - - if (error.isNotEmpty()) - { - error = "Error opening " + dlh.outputDeviceNames[i] + ": \"" + error + "\""; - break; - } - } - - if (error.isEmpty()) - { - for (int i = 0; i < inChans.size(); ++i) - { - error = inChans[i]->open(); - - if (error.isNotEmpty()) - { - error = "Error opening " + dlh.inputDeviceNames[i] + ": \"" + error + "\""; - break; - } - } - } - - if (error.isEmpty()) - { - for (int i = 0; i < outChans.size(); ++i) - outChans.getUnchecked(i)->synchronisePosition(); - - for (int i = 0; i < inChans.size(); ++i) - inChans.getUnchecked(i)->synchronisePosition(); - - startThread (9); - sleep (10); - - notify(); - } - else - { - JUCE_DS_LOG ("Opening failed: " + error); - } - - SetThreadPriority (GetCurrentThread(), oldThreadPri); - SetPriorityClass (GetCurrentProcess(), oldProcPri); - - return error; -} - -//============================================================================== -class DSoundAudioIODeviceType : public AudioIODeviceType, - private DeviceChangeDetector -{ -public: - DSoundAudioIODeviceType() - : AudioIODeviceType ("DirectSound"), - DeviceChangeDetector (L"DirectSound"), - hasScanned (false) - { - initialiseDSoundFunctions(); - } - - void scanForDevices() - { - hasScanned = true; - deviceList.scan(); - } - - StringArray getDeviceNames (bool wantInputNames) const - { - jassert (hasScanned); // need to call scanForDevices() before doing this - - return wantInputNames ? deviceList.inputDeviceNames - : deviceList.outputDeviceNames; - } - - int getDefaultDeviceIndex (bool /*forInput*/) const - { - jassert (hasScanned); // need to call scanForDevices() before doing this - return 0; - } - - int getIndexOfDevice (AudioIODevice* device, bool asInput) const - { - jassert (hasScanned); // need to call scanForDevices() before doing this - - if (DSoundAudioIODevice* const d = dynamic_cast (device)) - return asInput ? d->inputDeviceIndex - : d->outputDeviceIndex; - - return -1; - } - - bool hasSeparateInputsAndOutputs() const { return true; } - - AudioIODevice* createDevice (const String& outputDeviceName, - const String& inputDeviceName) - { - jassert (hasScanned); // need to call scanForDevices() before doing this - - const int outputIndex = deviceList.outputDeviceNames.indexOf (outputDeviceName); - const int inputIndex = deviceList.inputDeviceNames.indexOf (inputDeviceName); - - if (outputIndex >= 0 || inputIndex >= 0) - return new DSoundAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName - : inputDeviceName, - outputIndex, inputIndex); - - return nullptr; - } - -private: - DSoundDeviceList deviceList; - bool hasScanned; - - void systemDeviceChanged() override - { - DSoundDeviceList newList; - newList.scan(); - - if (newList != deviceList) - { - deviceList = newList; - callDeviceChangeListeners(); - } - } - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DSoundAudioIODeviceType) -}; - -//============================================================================== -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_DirectSound() -{ - return new DSoundAudioIODeviceType(); -} - -} // namespace juce diff --git a/source/modules/juce_audio_devices/native/juce_win32_Midi.cpp b/source/modules/juce_audio_devices/native/juce_win32_Midi.cpp deleted file mode 100644 index c45ab2dc5..000000000 --- a/source/modules/juce_audio_devices/native/juce_win32_Midi.cpp +++ /dev/null @@ -1,1232 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -struct MidiServiceType -{ - struct InputWrapper - { - virtual ~InputWrapper() {} - - virtual String getDeviceName() = 0; - virtual void start() = 0; - virtual void stop() = 0; - }; - - struct OutputWrapper - { - virtual ~OutputWrapper() {} - - virtual String getDeviceName() = 0; - virtual void sendMessageNow (const MidiMessage&) = 0; - }; - - MidiServiceType() {} - virtual ~MidiServiceType() {} - - virtual StringArray getDevices (bool) = 0; - virtual int getDefaultDeviceIndex (bool) = 0; - - virtual InputWrapper* createInputWrapper (MidiInput*, int, MidiInputCallback*) = 0; - virtual OutputWrapper* createOutputWrapper (int) = 0; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiServiceType) -}; - -//============================================================================== -class WindowsMidiService : public MidiServiceType -{ -private: - struct WindowsInputWrapper : public InputWrapper - { - struct MidiInCollector - { - MidiInCollector (WindowsMidiService& s, - MidiInput* const inputDevice, - MidiInputCallback& cb) - : midiService (s), - input (inputDevice), - callback (cb) - { - } - - ~MidiInCollector() - { - stop(); - - if (deviceHandle != 0) - { - for (int count = 5; --count >= 0;) - { - if (midiInClose (deviceHandle) == MMSYSERR_NOERROR) - break; - - Sleep (20); - } - } - } - - void handleMessage (const uint8* bytes, const uint32 timeStamp) - { - if (bytes[0] >= 0x80 && isStarted) - { - concatenator.pushMidiData (bytes, - MidiMessage::getMessageLengthFromFirstByte (bytes[0]), - convertTimeStamp (timeStamp), - input, - callback); - writeFinishedBlocks(); - } - } - - void handleSysEx (MIDIHDR* const hdr, const uint32 timeStamp) - { - if (isStarted && hdr->dwBytesRecorded > 0) - { - concatenator.pushMidiData (hdr->lpData, (int) hdr->dwBytesRecorded, - convertTimeStamp (timeStamp), input, callback); - writeFinishedBlocks(); - } - } - - void start() - { - if (deviceHandle != 0 && ! isStarted) - { - midiService.activeMidiCollectors.addIfNotAlreadyThere (this); - - for (int i = 0; i < (int) numHeaders; ++i) - { - headers[i].prepare (deviceHandle); - headers[i].write (deviceHandle); - } - - startTime = Time::getMillisecondCounterHiRes(); - MMRESULT res = midiInStart (deviceHandle); - - if (res == MMSYSERR_NOERROR) - { - concatenator.reset(); - isStarted = true; - } - else - { - unprepareAllHeaders(); - } - } - } - - void stop() - { - if (isStarted) - { - isStarted = false; - midiInReset (deviceHandle); - midiInStop (deviceHandle); - midiService.activeMidiCollectors.removeFirstMatchingValue (this); - unprepareAllHeaders(); - concatenator.reset(); - } - } - - static void CALLBACK midiInCallback (HMIDIIN, UINT uMsg, DWORD_PTR dwInstance, - DWORD_PTR midiMessage, DWORD_PTR timeStamp) - { - auto* collector = reinterpret_cast (dwInstance); - - if (collector->midiService.activeMidiCollectors.contains (collector)) - { - if (uMsg == MIM_DATA) - collector->handleMessage ((const uint8*) &midiMessage, (uint32) timeStamp); - else if (uMsg == MIM_LONGDATA) - collector->handleSysEx ((MIDIHDR*) midiMessage, (uint32) timeStamp); - } - } - - HMIDIIN deviceHandle = 0; - - private: - WindowsMidiService& midiService; - MidiInput* input; - MidiInputCallback& callback; - MidiDataConcatenator concatenator { 4096 }; - bool volatile isStarted = false; - double startTime = 0; - - struct MidiHeader - { - MidiHeader() {} - - void prepare (HMIDIIN device) - { - zerostruct (hdr); - hdr.lpData = data; - hdr.dwBufferLength = (DWORD) numElementsInArray (data); - - midiInPrepareHeader (device, &hdr, sizeof (hdr)); - } - - void unprepare (HMIDIIN device) - { - if ((hdr.dwFlags & WHDR_DONE) != 0) - { - int c = 10; - while (--c >= 0 && midiInUnprepareHeader (device, &hdr, sizeof (hdr)) == MIDIERR_STILLPLAYING) - Thread::sleep (20); - - jassert (c >= 0); - } - } - - void write (HMIDIIN device) - { - hdr.dwBytesRecorded = 0; - midiInAddBuffer (device, &hdr, sizeof (hdr)); - } - - void writeIfFinished (HMIDIIN device) - { - if ((hdr.dwFlags & WHDR_DONE) != 0) - write (device); - } - - MIDIHDR hdr; - char data [256]; - - JUCE_DECLARE_NON_COPYABLE (MidiHeader) - }; - - enum { numHeaders = 32 }; - MidiHeader headers [numHeaders]; - - void writeFinishedBlocks() - { - for (int i = 0; i < (int) numHeaders; ++i) - headers[i].writeIfFinished (deviceHandle); - } - - void unprepareAllHeaders() - { - for (int i = 0; i < (int) numHeaders; ++i) - headers[i].unprepare (deviceHandle); - } - - double convertTimeStamp (uint32 timeStamp) - { - auto t = startTime + timeStamp; - auto now = Time::getMillisecondCounterHiRes(); - - if (t > now) - { - if (t > now + 2.0) - startTime -= 1.0; - - t = now; - } - - return t * 0.001; - } - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInCollector) - }; - - //============================================================================== - WindowsInputWrapper (WindowsMidiService& parentService, - MidiInput* const input, - const int index, - MidiInputCallback* const callback) - { - auto names = getDevices(); - UINT deviceId = MIDI_MAPPER; - - if (isPositiveAndBelow (index, names.size())) - { - deviceName = names[index]; - deviceId = index; - } - - collector = new MidiInCollector (parentService, input, *callback); - - HMIDIIN h; - MMRESULT err = midiInOpen (&h, deviceId, - (DWORD_PTR) &MidiInCollector::midiInCallback, - (DWORD_PTR) (MidiInCollector*) collector.get(), - CALLBACK_FUNCTION); - - if (err != MMSYSERR_NOERROR) - throw std::runtime_error ("Failed to create Windows input device wrapper"); - - collector->deviceHandle = h; - } - - ~WindowsInputWrapper() {} - - static StringArray getDevices() - { - StringArray s; - const UINT num = midiInGetNumDevs(); - - for (UINT i = 0; i < num; ++i) - { - MIDIINCAPS mc = { 0 }; - - if (midiInGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) - s.add (String (mc.szPname, (size_t) numElementsInArray (mc.szPname))); - } - - s.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 ("")); - return s; - } - - static int getDefaultDeviceIndex() - { - return 0; - } - - void start() override { collector->start(); } - void stop() override { collector->stop(); } - - String getDeviceName() override - { - return deviceName; - } - - String deviceName; - ScopedPointer collector; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsInputWrapper) - }; - - //============================================================================== - struct WindowsOutputWrapper : public OutputWrapper - { - struct MidiOutHandle - { - int refCount; - UINT deviceId; - HMIDIOUT handle; - - JUCE_LEAK_DETECTOR (MidiOutHandle) - }; - - WindowsOutputWrapper (WindowsMidiService& p, int index) : parent (p) - { - auto names = getDevices(); - UINT deviceId = MIDI_MAPPER; - - if (isPositiveAndBelow (index, names.size())) - { - deviceName = names[index]; - deviceId = index; - } - - if (deviceId == MIDI_MAPPER) - { - // use the microsoft sw synth as a default - best not to allow deviceId - // to be MIDI_MAPPER, or else device sharing breaks - for (int i = 0; i < names.size(); ++i) - if (names[i].containsIgnoreCase ("microsoft")) - deviceId = (UINT) i; - } - - for (int i = parent.activeOutputHandles.size(); --i >= 0;) - { - auto* activeHandle = parent.activeOutputHandles.getUnchecked (i); - - if (activeHandle->deviceId == deviceId) - { - activeHandle->refCount++; - han = activeHandle; - return; - } - } - - for (int i = 4; --i >= 0;) - { - HMIDIOUT h = 0; - MMRESULT res = midiOutOpen (&h, deviceId, 0, 0, CALLBACK_NULL); - - if (res == MMSYSERR_NOERROR) - { - han = new MidiOutHandle(); - han->deviceId = deviceId; - han->refCount = 1; - han->handle = h; - parent.activeOutputHandles.add (han); - return; - } - - if (res == MMSYSERR_ALLOCATED) - Sleep (100); - else - break; - } - - throw std::runtime_error ("Failed to create Windows output device wrapper"); - } - - ~WindowsOutputWrapper() - { - if (parent.activeOutputHandles.contains (han.get()) && --(han->refCount) == 0) - { - midiOutClose (han->handle); - parent.activeOutputHandles.removeFirstMatchingValue (han.get()); - } - } - - void sendMessageNow (const MidiMessage& message) override - { - if (message.getRawDataSize() > 3 || message.isSysEx()) - { - MIDIHDR h = { 0 }; - - h.lpData = (char*) message.getRawData(); - h.dwBytesRecorded = h.dwBufferLength = (DWORD) message.getRawDataSize(); - - if (midiOutPrepareHeader (han->handle, &h, sizeof (MIDIHDR)) == MMSYSERR_NOERROR) - { - MMRESULT res = midiOutLongMsg (han->handle, &h, sizeof (MIDIHDR)); - - if (res == MMSYSERR_NOERROR) - { - while ((h.dwFlags & MHDR_DONE) == 0) - Sleep (1); - - int count = 500; // 1 sec timeout - - while (--count >= 0) - { - res = midiOutUnprepareHeader (han->handle, &h, sizeof (MIDIHDR)); - - if (res == MIDIERR_STILLPLAYING) - Sleep (2); - else - break; - } - } - } - } - else - { - for (int i = 0; i < 50; ++i) - { - if (midiOutShortMsg (han->handle, *(unsigned int*) message.getRawData()) != MIDIERR_NOTREADY) - break; - - Sleep (1); - } - } - } - - static Array getDeviceCaps() - { - Array devices; - const UINT num = midiOutGetNumDevs(); - - for (UINT i = 0; i < num; ++i) - { - MIDIOUTCAPS mc = { 0 }; - - if (midiOutGetDevCaps (i, &mc, sizeof (mc)) == MMSYSERR_NOERROR) - devices.add (mc); - } - - return devices; - } - - static StringArray getDevices() - { - StringArray s; - - for (auto& mc : getDeviceCaps()) - s.add (String (mc.szPname, (size_t) numElementsInArray (mc.szPname))); - - s.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 ("")); - return s; - } - - static int getDefaultDeviceIndex() - { - int n = 0; - - for (auto& mc : getDeviceCaps()) - { - if ((mc.wTechnology & MOD_MAPPER) != 0) - return n; - - ++n; - } - - return 0; - } - - String getDeviceName() override - { - return deviceName; - } - - WindowsMidiService& parent; - String deviceName; - - ScopedPointer han; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsOutputWrapper) - }; - -public: - WindowsMidiService() {} - - StringArray getDevices (bool isInput) override - { - return isInput ? WindowsInputWrapper::getDevices() - : WindowsOutputWrapper::getDevices(); - } - - int getDefaultDeviceIndex (bool isInput) override - { - return isInput ? WindowsInputWrapper::getDefaultDeviceIndex() - : WindowsOutputWrapper::getDefaultDeviceIndex(); - } - - InputWrapper* createInputWrapper (MidiInput* input, int index, MidiInputCallback* callback) override - { - return new WindowsInputWrapper (*this, input, index, callback); - } - - OutputWrapper* createOutputWrapper (int index) override - { - return new WindowsOutputWrapper (*this, index); - } - -private: - Array activeMidiCollectors; - Array activeOutputHandles; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WindowsMidiService) -}; - -//============================================================================== -#if JUCE_USE_WINRT_MIDI - -using namespace Microsoft::WRL; - -using namespace ABI::Windows::Foundation; -using namespace ABI::Windows::Devices::Midi; -using namespace ABI::Windows::Devices::Enumeration; -using namespace ABI::Windows::Storage::Streams; - -class WinRTMidiService : public MidiServiceType -{ -private: - template - struct MidiIODeviceWatcher - { - struct DeviceInfo - { - String name; - String id; - bool isDefault = false; - }; - - MidiIODeviceWatcher (ComSmartPtr& comFactory) - : factory (comFactory) - { - } - - ~MidiIODeviceWatcher() - { - stop(); - } - - bool start() - { - HSTRING deviceSelector; - HRESULT hr = factory->GetDeviceSelector (&deviceSelector); - if (FAILED (hr)) - return false; - - auto deviceInformationFactory = WinRTWrapper::getInstance()->getWRLFactory (&RuntimeClass_Windows_Devices_Enumeration_DeviceInformation[0]); - if (deviceInformationFactory == nullptr) - return false; - - hr = deviceInformationFactory->CreateWatcherAqsFilter (deviceSelector, watcher.resetAndGetPointerAddress()); - if (FAILED (hr)) - return false; - - class DeviceEnumerationThread : public Thread - { - public: - DeviceEnumerationThread (String threadName, MidiIODeviceWatcher& p) - : Thread (threadName), parent (p) - {} - - void run() override - { - auto parentPtr = &parent; - - parent.watcher->add_Added ( - Callback> ( - [parentPtr](IDeviceWatcher*, IDeviceInformation* info) { return parentPtr->addDevice (info); } - ).Get(), - &parent.deviceAddedToken); - - parent.watcher->add_Removed ( - Callback> ( - [parentPtr](IDeviceWatcher*, IDeviceInformationUpdate* info) { return parentPtr->removeDevice (info); } - ).Get(), - &parent.deviceRemovedToken); - - EventRegistrationToken deviceEnumerationCompletedToken { 0 }; - parent.watcher->add_EnumerationCompleted ( - Callback> ( - [this](IDeviceWatcher*, IInspectable*) { enumerationCompleted.signal(); return S_OK; } - ).Get(), - &deviceEnumerationCompletedToken); - - parent.watcher->Start(); - enumerationCompleted.wait(); - - if (deviceEnumerationCompletedToken.value != 0) - parent.watcher->remove_EnumerationCompleted (deviceEnumerationCompletedToken); - } - - private: - MidiIODeviceWatcher& parent; - WaitableEvent enumerationCompleted; - }; - - DeviceEnumerationThread enumerationThread ("WinRT Device Enumeration Thread", *this); - enumerationThread.startThread(); - enumerationThread.waitForThreadToExit (4000); - - return true; - } - - bool stop() - { - if (watcher == nullptr) - return true; - - if (deviceAddedToken.value != 0) - { - HRESULT hr = watcher->remove_Added (deviceAddedToken); - if (FAILED (hr)) - return false; - - deviceAddedToken.value = 0; - } - - if (deviceRemovedToken.value != 0) - { - HRESULT hr = watcher->remove_Removed (deviceRemovedToken); - if (FAILED (hr)) - return false; - - deviceRemovedToken.value = 0; - } - - HRESULT hr = watcher->Stop(); - if (FAILED (hr)) - return false; - - watcher = nullptr; - return true; - } - - HRESULT addDevice (IDeviceInformation* addedDeviceInfo) - { - boolean isEnabled; - HRESULT hr = addedDeviceInfo->get_IsEnabled (&isEnabled); - if (FAILED (hr)) - return S_OK; - - if (! isEnabled) - return S_OK; - - const ScopedLock lock (deviceChanges); - - DeviceInfo info; - - HSTRING name; - hr = addedDeviceInfo->get_Name (&name); - if (FAILED (hr)) - return S_OK; - - info.name = WinRTWrapper::getInstance()->hStringToString (name); - - HSTRING id; - hr = addedDeviceInfo->get_Id (&id); - if (FAILED (hr)) - return S_OK; - - info.id = WinRTWrapper::getInstance()->hStringToString (id); - - boolean isDefault; - hr = addedDeviceInfo->get_IsDefault (&isDefault); - if (FAILED (hr)) - return S_OK; - - info.isDefault = isDefault != 0; - - connectedDevices.add (info); - - return S_OK; - } - - HRESULT removeDevice (IDeviceInformationUpdate* removedDeviceInfo) - { - const ScopedLock lock (deviceChanges); - - HSTRING removedDeviceIdHstr; - removedDeviceInfo->get_Id (&removedDeviceIdHstr); - String removedDeviceId = WinRTWrapper::getInstance()->hStringToString (removedDeviceIdHstr); - - for (int i = 0; i < connectedDevices.size(); ++i) - { - if (connectedDevices[i].id == removedDeviceId) - { - connectedDevices.remove (i); - break; - } - } - - return S_OK; - } - - StringArray getDevices() - { - { - const ScopedLock lock (deviceChanges); - lastQueriedConnectedDevices = connectedDevices; - } - - StringArray result; - for (auto info : lastQueriedConnectedDevices.get()) - result.add (info.name); - - return result; - } - - int getDefaultDeviceIndex() - { - auto& lastDevices = lastQueriedConnectedDevices.get(); - for (int i = 0; i < lastDevices.size(); ++i) - if (lastDevices[i].isDefault) - return i; - - return 0; - } - - String getDeviceNameFromIndex (const int index) - { - if (isPositiveAndBelow (index, lastQueriedConnectedDevices.get().size())) - return lastQueriedConnectedDevices.get()[index].name; - - return {}; - } - - String getDeviceID (const String name) - { - const ScopedLock lock (deviceChanges); - - for (auto info : connectedDevices) - if (info.name == name) - return info.id; - - return {}; - } - - ComSmartPtr& factory; - - EventRegistrationToken deviceAddedToken { 0 }, - deviceRemovedToken { 0 }; - - ComSmartPtr watcher; - - Array connectedDevices; - CriticalSection deviceChanges; - ThreadLocalValue> lastQueriedConnectedDevices; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiIODeviceWatcher); - }; - - template - class OpenMidiPortThread : public Thread - { - public: - OpenMidiPortThread (String threadName, - String midiDeviceId, - ComSmartPtr& comFactory, - ComSmartPtr& comPort) - : Thread (threadName), - deviceId (midiDeviceId), - factory (comFactory), - port (comPort) - { - } - - ~OpenMidiPortThread() - { - } - - void run() override - { - WinRTWrapper::ScopedHString hDeviceId (deviceId); - ComSmartPtr> asyncOp; - HRESULT hr = factory->FromIdAsync (hDeviceId.get(), asyncOp.resetAndGetPointerAddress()); - if (FAILED (hr)) - return; - - hr = asyncOp->put_Completed (Callback> ( - [this] (IAsyncOperation* asyncOpPtr, AsyncStatus) - { - if (asyncOpPtr == nullptr) - return E_ABORT; - - HRESULT hr = asyncOpPtr->GetResults (port.resetAndGetPointerAddress()); - if (FAILED (hr)) - return hr; - - portOpened.signal(); - return S_OK; - } - ).Get()); - - // When using Bluetooth the asynchronous port opening operation will occasionally - // hang, so we use a timeout. We will be able to remove this when Microsoft - // improves the Bluetooth MIDI stack. - portOpened.wait (2000); - } - - const String deviceId; - ComSmartPtr& factory; - ComSmartPtr& port; - - WaitableEvent portOpened { true }; - }; - - struct WinRTInputWrapper : public InputWrapper - { - WinRTInputWrapper (WinRTMidiService& service, - MidiInput* const input, - const int index, - MidiInputCallback& cb) - : inputDevice (input), - callback (cb), - concatenator (4096) - { - const ScopedLock lock (service.inputDeviceWatcher->deviceChanges); - - deviceName = service.inputDeviceWatcher->getDeviceNameFromIndex (index); - if (deviceName.isEmpty()) - throw std::runtime_error ("Invalid device index"); - - const auto deviceID = service.inputDeviceWatcher->getDeviceID (deviceName); - if (deviceID.isEmpty()) - throw std::runtime_error ("Device unavailable"); - - OpenMidiPortThread portThread ("Open WinRT MIDI input port", - deviceID, - service.midiInFactory, - midiInPort); - portThread.startThread(); - portThread.waitForThreadToExit (-1); - if (midiInPort == nullptr) - throw std::runtime_error ("Timed out waiting for midi input port creation"); - - startTime = Time::getMillisecondCounterHiRes(); - - HRESULT hr = midiInPort->add_MessageReceived ( - Callback> ( - [this] (IMidiInPort*, IMidiMessageReceivedEventArgs* args) { return midiInMessageReceived (args); } - ).Get(), - &midiInMessageToken); - if (FAILED (hr)) - throw std::runtime_error ("Failed to set midi input callback"); - } - - ~WinRTInputWrapper() - { - if (midiInMessageToken.value != 0) - midiInPort->remove_MessageReceived (midiInMessageToken); - - midiInPort = nullptr; - } - - void start() override - { - if (!isStarted) - { - concatenator.reset(); - isStarted = true; - } - } - - void stop() override - { - if (isStarted) - { - isStarted = false; - concatenator.reset(); - } - } - - String getDeviceName() override - { - return deviceName; - } - - HRESULT midiInMessageReceived (IMidiMessageReceivedEventArgs* args) - { - if (! isStarted) - return S_OK; - - ComSmartPtr message; - HRESULT hr = args->get_Message (message.resetAndGetPointerAddress()); - if (FAILED (hr)) - return hr; - - ComSmartPtr buffer; - hr = message->get_RawData (buffer.resetAndGetPointerAddress()); - if (FAILED (hr)) - return hr; - - ComSmartPtr bufferByteAccess; - hr = buffer->QueryInterface (bufferByteAccess.resetAndGetPointerAddress()); - if (FAILED (hr)) - return hr; - - uint8_t* bufferData = nullptr; - hr = bufferByteAccess->Buffer (&bufferData); - if (FAILED (hr)) - return hr; - - uint32_t numBytes = 0; - hr = buffer->get_Length (&numBytes); - if (FAILED (hr)) - return hr; - - ABI::Windows::Foundation::TimeSpan timespan; - hr = message->get_Timestamp (×pan); - if (FAILED (hr)) - return hr; - - concatenator.pushMidiData (bufferData, - numBytes, - convertTimeStamp (timespan.Duration), - inputDevice, - callback); - - return S_OK; - } - - double convertTimeStamp (int64 timestamp) - { - const auto millisecondsSinceStart = static_cast (timestamp) / 10000.0; - double t = startTime + millisecondsSinceStart; - - const double now = Time::getMillisecondCounterHiRes(); - if (t > now) - { - if (t > now + 2.0) - startTime -= 1.0; - - t = now; - } - - return t * 0.001; - } - - MidiInput* inputDevice; - MidiInputCallback& callback; - String deviceName; - MidiDataConcatenator concatenator; - ComSmartPtr midiInPort; - EventRegistrationToken midiInMessageToken { 0 }; - - double startTime = 0; - bool isStarted = false; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTInputWrapper); - }; - - struct WinRTOutputWrapper : public OutputWrapper - { - WinRTOutputWrapper (WinRTMidiService& service, const int index) - { - const ScopedLock lock (service.outputDeviceWatcher->deviceChanges); - - deviceName = service.outputDeviceWatcher->getDeviceNameFromIndex (index); - if (deviceName.isEmpty()) - throw std::runtime_error ("Invalid device index"); - - const auto deviceID = service.outputDeviceWatcher->getDeviceID (deviceName); - if (deviceID.isEmpty()) - throw std::runtime_error ("Device unavailable"); - - OpenMidiPortThread portThread ("Open WinRT MIDI output port", - deviceID, - service.midiOutFactory, - midiOutPort); - portThread.startThread(); - portThread.waitForThreadToExit (-1); - if (midiOutPort == nullptr) - throw std::runtime_error ("Timed out waiting for midi output port creation"); - - auto bufferFactory = WinRTWrapper::getInstance()->getWRLFactory (&RuntimeClass_Windows_Storage_Streams_Buffer[0]); - if (bufferFactory == nullptr) - throw std::runtime_error ("Failed to create output buffer factory"); - - HRESULT hr = bufferFactory->Create (static_cast (65536), buffer.resetAndGetPointerAddress()); - if (FAILED (hr)) - throw std::runtime_error ("Failed to create output buffer"); - - hr = buffer->QueryInterface (bufferByteAccess.resetAndGetPointerAddress()); - if (FAILED (hr)) - throw std::runtime_error ("Failed to get buffer byte access"); - - hr = bufferByteAccess->Buffer (&bufferData); - if (FAILED (hr)) - throw std::runtime_error ("Failed to get buffer data pointer"); - } - - ~WinRTOutputWrapper() {} - - void sendMessageNow (const MidiMessage& message) override - { - const UINT32 numBytes = message.getRawDataSize(); - HRESULT hr = buffer->put_Length (numBytes); - if (FAILED (hr)) - jassertfalse; - - memcpy_s (bufferData, numBytes, message.getRawData(), numBytes); - - midiOutPort->SendBuffer (buffer); - } - - String getDeviceName() override - { - return deviceName; - } - - String deviceName; - ComSmartPtr midiOutPort; - ComSmartPtr buffer; - ComSmartPtr bufferByteAccess; - uint8_t* bufferData = nullptr; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTOutputWrapper); - }; - -public: - WinRTMidiService() - { - if (! WinRTWrapper::getInstance()->isInitialised()) - throw std::runtime_error ("Failed to initialise the WinRT wrapper"); - - midiInFactory = WinRTWrapper::getInstance()->getWRLFactory (&RuntimeClass_Windows_Devices_Midi_MidiInPort[0]); - if (midiInFactory == nullptr) - throw std::runtime_error ("Failed to create midi in factory"); - - midiOutFactory = WinRTWrapper::getInstance()->getWRLFactory (&RuntimeClass_Windows_Devices_Midi_MidiOutPort[0]); - if (midiOutFactory == nullptr) - throw std::runtime_error ("Failed to create midi out factory"); - - inputDeviceWatcher = new MidiIODeviceWatcher (midiInFactory); - if (! inputDeviceWatcher->start()) - throw std::runtime_error ("Failed to start midi input device watcher"); - - outputDeviceWatcher = new MidiIODeviceWatcher (midiOutFactory); - if (! outputDeviceWatcher->start()) - throw std::runtime_error ("Failed to start midi output device watcher"); - } - - ~WinRTMidiService() - { - } - - StringArray getDevices (bool isInput) override - { - return isInput ? inputDeviceWatcher ->getDevices() - : outputDeviceWatcher->getDevices(); - } - - int getDefaultDeviceIndex (bool isInput) override - { - return isInput ? inputDeviceWatcher ->getDefaultDeviceIndex() - : outputDeviceWatcher->getDefaultDeviceIndex(); - } - - InputWrapper* createInputWrapper (MidiInput* input, int index, MidiInputCallback* callback) override - { - return new WinRTInputWrapper (*this, input, index, *callback); - } - - OutputWrapper* createOutputWrapper (int index) override - { - return new WinRTOutputWrapper (*this, index); - } - - ComSmartPtr midiInFactory; - ComSmartPtr midiOutFactory; - - ScopedPointer> inputDeviceWatcher; - ScopedPointer> outputDeviceWatcher; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WinRTMidiService) -}; - -#endif // JUCE_USE_WINRT_MIDI - -//============================================================================== -class MidiService : public DeletedAtShutdown -{ -public: - ~MidiService(); - - MidiServiceType* getService(); - - juce_DeclareSingleton (MidiService, false) - -private: - MidiService(); - - ScopedPointer internal; -}; - -juce_ImplementSingleton (MidiService) - -MidiService::~MidiService() -{ - clearSingletonInstance(); -} - -MidiServiceType* MidiService::getService() -{ - return internal.get(); -} - -MidiService::MidiService() -{ - #if JUCE_USE_WINRT_MIDI - try - { - internal = new WinRTMidiService(); - return; - } - catch (std::runtime_error&) - { - } - #endif - - internal = new WindowsMidiService(); -} - -//============================================================================== -StringArray MidiInput::getDevices() -{ - return MidiService::getInstance()->getService()->getDevices (true); -} - -int MidiInput::getDefaultDeviceIndex() -{ - return MidiService::getInstance()->getService()->getDefaultDeviceIndex (true); -} - -MidiInput::MidiInput (const String& deviceName) - : name (deviceName) -{ -} - -MidiInput* MidiInput::openDevice (const int index, MidiInputCallback* const callback) -{ - if (callback == nullptr) - return nullptr; - - ScopedPointer in (new MidiInput ({})); - ScopedPointer wrapper; - - try - { - wrapper = MidiService::getInstance()->getService()->createInputWrapper (in, index, callback); - } - catch (std::runtime_error&) - { - return nullptr; - } - - in->setName (wrapper->getDeviceName()); - in->internal = wrapper.release(); - return in.release(); -} - -MidiInput::~MidiInput() -{ - delete static_cast (internal); -} - -void MidiInput::start() { static_cast (internal)->start(); } -void MidiInput::stop() { static_cast (internal)->stop(); } - -//============================================================================== -StringArray MidiOutput::getDevices() -{ - return MidiService::getInstance()->getService()->getDevices (false); -} - -int MidiOutput::getDefaultDeviceIndex() -{ - return MidiService::getInstance()->getService()->getDefaultDeviceIndex (false); -} - -MidiOutput* MidiOutput::openDevice (const int index) -{ - ScopedPointer wrapper; - - try - { - wrapper = MidiService::getInstance()->getService()->createOutputWrapper (index); - } - catch (std::runtime_error&) - { - return nullptr; - } - - ScopedPointer out (new MidiOutput (wrapper->getDeviceName())); - out->internal = wrapper.release(); - return out.release(); -} - -MidiOutput::~MidiOutput() -{ - stopBackgroundThread(); - delete static_cast (internal); -} - -void MidiOutput::sendMessageNow (const MidiMessage& message) -{ - static_cast (internal)->sendMessageNow (message); -} - -} // namespace juce diff --git a/source/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp b/source/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp deleted file mode 100644 index d0380d789..000000000 --- a/source/modules/juce_audio_devices/native/juce_win32_WASAPI.cpp +++ /dev/null @@ -1,1731 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -#ifndef JUCE_WASAPI_LOGGING - #define JUCE_WASAPI_LOGGING 0 -#endif - -//============================================================================== -namespace WasapiClasses -{ - -void logFailure (HRESULT hr) -{ - ignoreUnused (hr); - jassert (hr != (HRESULT) 0x800401f0); // If you hit this, it means you're trying to call from - // a thread which hasn't been initialised with CoInitialize(). - - #if JUCE_WASAPI_LOGGING - if (FAILED (hr)) - { - const char* m = nullptr; - - switch (hr) - { - case E_POINTER: m = "E_POINTER"; break; - case E_INVALIDARG: m = "E_INVALIDARG"; break; - case E_NOINTERFACE: m = "E_NOINTERFACE"; break; - - #define JUCE_WASAPI_ERR(desc, n) \ - case MAKE_HRESULT(1, 0x889, n): m = #desc; break; - - JUCE_WASAPI_ERR (AUDCLNT_E_NOT_INITIALIZED, 0x001) - JUCE_WASAPI_ERR (AUDCLNT_E_ALREADY_INITIALIZED, 0x002) - JUCE_WASAPI_ERR (AUDCLNT_E_WRONG_ENDPOINT_TYPE, 0x003) - JUCE_WASAPI_ERR (AUDCLNT_E_DEVICE_INVALIDATED, 0x004) - JUCE_WASAPI_ERR (AUDCLNT_E_NOT_STOPPED, 0x005) - JUCE_WASAPI_ERR (AUDCLNT_E_BUFFER_TOO_LARGE, 0x006) - JUCE_WASAPI_ERR (AUDCLNT_E_OUT_OF_ORDER, 0x007) - JUCE_WASAPI_ERR (AUDCLNT_E_UNSUPPORTED_FORMAT, 0x008) - JUCE_WASAPI_ERR (AUDCLNT_E_INVALID_SIZE, 0x009) - JUCE_WASAPI_ERR (AUDCLNT_E_DEVICE_IN_USE, 0x00a) - JUCE_WASAPI_ERR (AUDCLNT_E_BUFFER_OPERATION_PENDING, 0x00b) - JUCE_WASAPI_ERR (AUDCLNT_E_THREAD_NOT_REGISTERED, 0x00c) - JUCE_WASAPI_ERR (AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED, 0x00e) - JUCE_WASAPI_ERR (AUDCLNT_E_ENDPOINT_CREATE_FAILED, 0x00f) - JUCE_WASAPI_ERR (AUDCLNT_E_SERVICE_NOT_RUNNING, 0x010) - JUCE_WASAPI_ERR (AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED, 0x011) - JUCE_WASAPI_ERR (AUDCLNT_E_EXCLUSIVE_MODE_ONLY, 0x012) - JUCE_WASAPI_ERR (AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL, 0x013) - JUCE_WASAPI_ERR (AUDCLNT_E_EVENTHANDLE_NOT_SET, 0x014) - JUCE_WASAPI_ERR (AUDCLNT_E_INCORRECT_BUFFER_SIZE, 0x015) - JUCE_WASAPI_ERR (AUDCLNT_E_BUFFER_SIZE_ERROR, 0x016) - JUCE_WASAPI_ERR (AUDCLNT_E_CPUUSAGE_EXCEEDED, 0x017) - JUCE_WASAPI_ERR (AUDCLNT_E_BUFFER_ERROR, 0x018) - JUCE_WASAPI_ERR (AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED, 0x019) - JUCE_WASAPI_ERR (AUDCLNT_E_INVALID_DEVICE_PERIOD, 0x020) - default: break; - } - - Logger::writeToLog ("WASAPI error: " + (m != nullptr ? String (m) - : String::toHexString ((int) hr))); - } - #endif -} - -#undef check - -bool check (HRESULT hr) -{ - logFailure (hr); - return SUCCEEDED (hr); -} - -//============================================================================== -} - -#if JUCE_MINGW - struct PROPERTYKEY - { - GUID fmtid; - DWORD pid; - }; - - WINOLEAPI PropVariantClear (PROPVARIANT*); -#endif - -#if JUCE_MINGW && defined (KSDATAFORMAT_SUBTYPE_PCM) - #undef KSDATAFORMAT_SUBTYPE_PCM - #undef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT -#endif - -#ifndef KSDATAFORMAT_SUBTYPE_PCM - #define KSDATAFORMAT_SUBTYPE_PCM uuidFromString ("00000001-0000-0010-8000-00aa00389b71") - #define KSDATAFORMAT_SUBTYPE_IEEE_FLOAT uuidFromString ("00000003-0000-0010-8000-00aa00389b71") -#endif - -#define JUCE_IUNKNOWNCLASS(name, guid) JUCE_COMCLASS(name, guid) : public IUnknown -#define JUCE_COMCALL virtual HRESULT STDMETHODCALLTYPE - -enum EDataFlow -{ - eRender = 0, - eCapture = (eRender + 1), - eAll = (eCapture + 1) -}; - -enum -{ - DEVICE_STATE_ACTIVE = 1 -}; - -enum -{ - AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY = 1, - AUDCLNT_BUFFERFLAGS_SILENT = 2 -}; - -JUCE_IUNKNOWNCLASS (IPropertyStore, "886d8eeb-8cf2-4446-8d02-cdba1dbdcf99") -{ - JUCE_COMCALL GetCount (DWORD*) = 0; - JUCE_COMCALL GetAt (DWORD, PROPERTYKEY*) = 0; - JUCE_COMCALL GetValue (const PROPERTYKEY&, PROPVARIANT*) = 0; - JUCE_COMCALL SetValue (const PROPERTYKEY&, const PROPVARIANT&) = 0; - JUCE_COMCALL Commit() = 0; -}; - -JUCE_IUNKNOWNCLASS (IMMDevice, "D666063F-1587-4E43-81F1-B948E807363F") -{ - JUCE_COMCALL Activate (REFIID, DWORD, PROPVARIANT*, void**) = 0; - JUCE_COMCALL OpenPropertyStore (DWORD, IPropertyStore**) = 0; - JUCE_COMCALL GetId (LPWSTR*) = 0; - JUCE_COMCALL GetState (DWORD*) = 0; -}; - -JUCE_IUNKNOWNCLASS (IMMEndpoint, "1BE09788-6894-4089-8586-9A2A6C265AC5") -{ - JUCE_COMCALL GetDataFlow (EDataFlow*) = 0; -}; - -struct IMMDeviceCollection : public IUnknown -{ - JUCE_COMCALL GetCount (UINT*) = 0; - JUCE_COMCALL Item (UINT, IMMDevice**) = 0; -}; - -enum ERole -{ - eConsole = 0, - eMultimedia = (eConsole + 1), - eCommunications = (eMultimedia + 1) -}; - -JUCE_IUNKNOWNCLASS (IMMNotificationClient, "7991EEC9-7E89-4D85-8390-6C703CEC60C0") -{ - JUCE_COMCALL OnDeviceStateChanged (LPCWSTR, DWORD) = 0; - JUCE_COMCALL OnDeviceAdded (LPCWSTR) = 0; - JUCE_COMCALL OnDeviceRemoved (LPCWSTR) = 0; - JUCE_COMCALL OnDefaultDeviceChanged (EDataFlow, ERole, LPCWSTR) = 0; - JUCE_COMCALL OnPropertyValueChanged (LPCWSTR, const PROPERTYKEY) = 0; -}; - -JUCE_IUNKNOWNCLASS (IMMDeviceEnumerator, "A95664D2-9614-4F35-A746-DE8DB63617E6") -{ - JUCE_COMCALL EnumAudioEndpoints (EDataFlow, DWORD, IMMDeviceCollection**) = 0; - JUCE_COMCALL GetDefaultAudioEndpoint (EDataFlow, ERole, IMMDevice**) = 0; - JUCE_COMCALL GetDevice (LPCWSTR, IMMDevice**) = 0; - JUCE_COMCALL RegisterEndpointNotificationCallback (IMMNotificationClient*) = 0; - JUCE_COMCALL UnregisterEndpointNotificationCallback (IMMNotificationClient*) = 0; -}; - -JUCE_COMCLASS (MMDeviceEnumerator, "BCDE0395-E52F-467C-8E3D-C4579291692E"); - -typedef LONGLONG REFERENCE_TIME; - -enum AVRT_PRIORITY -{ - AVRT_PRIORITY_LOW = -1, - AVRT_PRIORITY_NORMAL, - AVRT_PRIORITY_HIGH, - AVRT_PRIORITY_CRITICAL -}; - -enum AUDCLNT_SHAREMODE -{ - AUDCLNT_SHAREMODE_SHARED, - AUDCLNT_SHAREMODE_EXCLUSIVE -}; - -JUCE_IUNKNOWNCLASS (IAudioClient, "1CB9AD4C-DBFA-4c32-B178-C2F568A703B2") -{ - JUCE_COMCALL Initialize (AUDCLNT_SHAREMODE, DWORD, REFERENCE_TIME, REFERENCE_TIME, const WAVEFORMATEX*, LPCGUID) = 0; - JUCE_COMCALL GetBufferSize (UINT32*) = 0; - JUCE_COMCALL GetStreamLatency (REFERENCE_TIME*) = 0; - JUCE_COMCALL GetCurrentPadding (UINT32*) = 0; - JUCE_COMCALL IsFormatSupported (AUDCLNT_SHAREMODE, const WAVEFORMATEX*, WAVEFORMATEX**) = 0; - JUCE_COMCALL GetMixFormat (WAVEFORMATEX**) = 0; - JUCE_COMCALL GetDevicePeriod (REFERENCE_TIME*, REFERENCE_TIME*) = 0; - JUCE_COMCALL Start() = 0; - JUCE_COMCALL Stop() = 0; - JUCE_COMCALL Reset() = 0; - JUCE_COMCALL SetEventHandle (HANDLE) = 0; - JUCE_COMCALL GetService (REFIID, void**) = 0; -}; - -JUCE_IUNKNOWNCLASS (IAudioCaptureClient, "C8ADBD64-E71E-48a0-A4DE-185C395CD317") -{ - JUCE_COMCALL GetBuffer (BYTE**, UINT32*, DWORD*, UINT64*, UINT64*) = 0; - JUCE_COMCALL ReleaseBuffer (UINT32) = 0; - JUCE_COMCALL GetNextPacketSize (UINT32*) = 0; -}; - -JUCE_IUNKNOWNCLASS (IAudioRenderClient, "F294ACFC-3146-4483-A7BF-ADDCA7C260E2") -{ - JUCE_COMCALL GetBuffer (UINT32, BYTE**) = 0; - JUCE_COMCALL ReleaseBuffer (UINT32, DWORD) = 0; -}; - -JUCE_IUNKNOWNCLASS (IAudioEndpointVolume, "5CDF2C82-841E-4546-9722-0CF74078229A") -{ - JUCE_COMCALL RegisterControlChangeNotify (void*) = 0; - JUCE_COMCALL UnregisterControlChangeNotify (void*) = 0; - JUCE_COMCALL GetChannelCount (UINT*) = 0; - JUCE_COMCALL SetMasterVolumeLevel (float, LPCGUID) = 0; - JUCE_COMCALL SetMasterVolumeLevelScalar (float, LPCGUID) = 0; - JUCE_COMCALL GetMasterVolumeLevel (float*) = 0; - JUCE_COMCALL GetMasterVolumeLevelScalar (float*) = 0; - JUCE_COMCALL SetChannelVolumeLevel (UINT, float, LPCGUID) = 0; - JUCE_COMCALL SetChannelVolumeLevelScalar (UINT, float, LPCGUID) = 0; - JUCE_COMCALL GetChannelVolumeLevel (UINT, float*) = 0; - JUCE_COMCALL GetChannelVolumeLevelScalar (UINT, float*) = 0; - JUCE_COMCALL SetMute (BOOL, LPCGUID) = 0; - JUCE_COMCALL GetMute (BOOL*) = 0; - JUCE_COMCALL GetVolumeStepInfo (UINT*, UINT*) = 0; - JUCE_COMCALL VolumeStepUp (LPCGUID) = 0; - JUCE_COMCALL VolumeStepDown (LPCGUID) = 0; - JUCE_COMCALL QueryHardwareSupport (DWORD*) = 0; - JUCE_COMCALL GetVolumeRange (float*, float*, float*) = 0; -}; - -enum AudioSessionDisconnectReason -{ - DisconnectReasonDeviceRemoval = 0, - DisconnectReasonServerShutdown = 1, - DisconnectReasonFormatChanged = 2, - DisconnectReasonSessionLogoff = 3, - DisconnectReasonSessionDisconnected = 4, - DisconnectReasonExclusiveModeOverride = 5 -}; - -enum AudioSessionState -{ - AudioSessionStateInactive = 0, - AudioSessionStateActive = 1, - AudioSessionStateExpired = 2 -}; - -JUCE_IUNKNOWNCLASS (IAudioSessionEvents, "24918ACC-64B3-37C1-8CA9-74A66E9957A8") -{ - JUCE_COMCALL OnDisplayNameChanged (LPCWSTR, LPCGUID) = 0; - JUCE_COMCALL OnIconPathChanged (LPCWSTR, LPCGUID) = 0; - JUCE_COMCALL OnSimpleVolumeChanged (float, BOOL, LPCGUID) = 0; - JUCE_COMCALL OnChannelVolumeChanged (DWORD, float*, DWORD, LPCGUID) = 0; - JUCE_COMCALL OnGroupingParamChanged (LPCGUID, LPCGUID) = 0; - JUCE_COMCALL OnStateChanged (AudioSessionState) = 0; - JUCE_COMCALL OnSessionDisconnected (AudioSessionDisconnectReason) = 0; -}; - -JUCE_IUNKNOWNCLASS (IAudioSessionControl, "F4B1A599-7266-4319-A8CA-E70ACB11E8CD") -{ - JUCE_COMCALL GetState (AudioSessionState*) = 0; - JUCE_COMCALL GetDisplayName (LPWSTR*) = 0; - JUCE_COMCALL SetDisplayName (LPCWSTR, LPCGUID) = 0; - JUCE_COMCALL GetIconPath (LPWSTR*) = 0; - JUCE_COMCALL SetIconPath (LPCWSTR, LPCGUID) = 0; - JUCE_COMCALL GetGroupingParam (GUID*) = 0; - JUCE_COMCALL SetGroupingParam (LPCGUID, LPCGUID) = 0; - JUCE_COMCALL RegisterAudioSessionNotification (IAudioSessionEvents*) = 0; - JUCE_COMCALL UnregisterAudioSessionNotification (IAudioSessionEvents*) = 0; -}; - -#undef JUCE_COMCALL -#undef JUCE_COMCLASS -#undef JUCE_IUNKNOWNCLASS - -//============================================================================== -namespace WasapiClasses -{ - -String getDeviceID (IMMDevice* const device) -{ - String s; - WCHAR* deviceId = nullptr; - - if (check (device->GetId (&deviceId))) - { - s = String (deviceId); - CoTaskMemFree (deviceId); - } - - return s; -} - -EDataFlow getDataFlow (const ComSmartPtr& device) -{ - EDataFlow flow = eRender; - ComSmartPtr endPoint; - if (check (device.QueryInterface (endPoint))) - (void) check (endPoint->GetDataFlow (&flow)); - - return flow; -} - -int refTimeToSamples (const REFERENCE_TIME& t, const double sampleRate) noexcept -{ - return roundToInt (sampleRate * ((double) t) * 0.0000001); -} - -REFERENCE_TIME samplesToRefTime (const int numSamples, const double sampleRate) noexcept -{ - return (REFERENCE_TIME) ((numSamples * 10000.0 * 1000.0 / sampleRate) + 0.5); -} - -void copyWavFormat (WAVEFORMATEXTENSIBLE& dest, const WAVEFORMATEX* const src) noexcept -{ - memcpy (&dest, src, src->wFormatTag == WAVE_FORMAT_EXTENSIBLE ? sizeof (WAVEFORMATEXTENSIBLE) - : sizeof (WAVEFORMATEX)); -} - -//============================================================================== -class WASAPIDeviceBase -{ -public: - WASAPIDeviceBase (const ComSmartPtr& d, const bool exclusiveMode) - : device (d), - sampleRate (0), - defaultSampleRate (0), - numChannels (0), - actualNumChannels (0), - minBufferSize (0), - defaultBufferSize (0), - latencySamples (0), - useExclusiveMode (exclusiveMode), - actualBufferSize (0), - bytesPerSample (0), - bytesPerFrame (0), - sampleRateHasChanged (false), - shouldClose (false) - { - clientEvent = CreateEvent (nullptr, false, false, nullptr); - - ComSmartPtr tempClient (createClient()); - if (tempClient == nullptr) - return; - - REFERENCE_TIME defaultPeriod, minPeriod; - if (! check (tempClient->GetDevicePeriod (&defaultPeriod, &minPeriod))) - return; - - WAVEFORMATEX* mixFormat = nullptr; - if (! check (tempClient->GetMixFormat (&mixFormat))) - return; - - WAVEFORMATEXTENSIBLE format; - copyWavFormat (format, mixFormat); - CoTaskMemFree (mixFormat); - - actualNumChannels = numChannels = format.Format.nChannels; - defaultSampleRate = format.Format.nSamplesPerSec; - minBufferSize = refTimeToSamples (minPeriod, defaultSampleRate); - defaultBufferSize = refTimeToSamples (defaultPeriod, defaultSampleRate); - mixFormatChannelMask = format.dwChannelMask; - - rates.addUsingDefaultSort (defaultSampleRate); - - if (useExclusiveMode - && findSupportedFormat (tempClient, defaultSampleRate, format.dwChannelMask, format)) - { - // Got a format that is supported by the device so we can ask what sample rates are supported (in whatever format) - } - - static const int ratesToTest[] = { 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000 }; - - for (int i = 0; i < numElementsInArray (ratesToTest); ++i) - { - if (rates.contains (ratesToTest[i])) - continue; - - format.Format.nSamplesPerSec = (DWORD) ratesToTest[i]; - format.Format.nAvgBytesPerSec = (DWORD) (format.Format.nSamplesPerSec * format.Format.nChannels * format.Format.wBitsPerSample / 8); - - if (SUCCEEDED (tempClient->IsFormatSupported (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE - : AUDCLNT_SHAREMODE_SHARED, - (WAVEFORMATEX*) &format, 0))) - if (! rates.contains (ratesToTest[i])) - rates.addUsingDefaultSort (ratesToTest[i]); - } - } - - virtual ~WASAPIDeviceBase() - { - device = nullptr; - CloseHandle (clientEvent); - } - - bool isOk() const noexcept { return defaultBufferSize > 0 && defaultSampleRate > 0; } - - bool openClient (const double newSampleRate, const BigInteger& newChannels, const int bufferSizeSamples) - { - sampleRate = newSampleRate; - channels = newChannels; - channels.setRange (actualNumChannels, channels.getHighestBit() + 1 - actualNumChannels, false); - numChannels = channels.getHighestBit() + 1; - - if (numChannels == 0) - return true; - - client = createClient(); - - if (client != nullptr - && tryInitialisingWithBufferSize (bufferSizeSamples)) - { - sampleRateHasChanged = false; - - channelMaps.clear(); - for (int i = 0; i <= channels.getHighestBit(); ++i) - if (channels[i]) - channelMaps.add (i); - - REFERENCE_TIME latency; - if (check (client->GetStreamLatency (&latency))) - latencySamples = refTimeToSamples (latency, sampleRate); - - (void) check (client->GetBufferSize (&actualBufferSize)); - - createSessionEventCallback(); - - return check (client->SetEventHandle (clientEvent)); - } - - return false; - } - - void closeClient() - { - if (client != nullptr) - client->Stop(); - - deleteSessionEventCallback(); - client = nullptr; - ResetEvent (clientEvent); - } - - void deviceSampleRateChanged() - { - sampleRateHasChanged = true; - } - - void deviceBecameInactive() - { - shouldClose = true; - } - - //============================================================================== - ComSmartPtr device; - ComSmartPtr client; - double sampleRate, defaultSampleRate; - int numChannels, actualNumChannels; - int minBufferSize, defaultBufferSize, latencySamples; - DWORD mixFormatChannelMask; - const bool useExclusiveMode; - Array rates; - HANDLE clientEvent; - BigInteger channels; - Array channelMaps; - UINT32 actualBufferSize; - int bytesPerSample, bytesPerFrame; - bool sampleRateHasChanged, shouldClose; - - virtual void updateFormat (bool isFloat) = 0; - -private: - //============================================================================== - class SessionEventCallback : public ComBaseClassHelper - { - public: - SessionEventCallback (WASAPIDeviceBase& d) : owner (d) {} - - JUCE_COMRESULT OnDisplayNameChanged (LPCWSTR, LPCGUID) { return S_OK; } - JUCE_COMRESULT OnIconPathChanged (LPCWSTR, LPCGUID) { return S_OK; } - JUCE_COMRESULT OnSimpleVolumeChanged (float, BOOL, LPCGUID) { return S_OK; } - JUCE_COMRESULT OnChannelVolumeChanged (DWORD, float*, DWORD, LPCGUID) { return S_OK; } - JUCE_COMRESULT OnGroupingParamChanged (LPCGUID, LPCGUID) { return S_OK; } - JUCE_COMRESULT OnStateChanged(AudioSessionState state) - { - if (state == AudioSessionStateInactive || state == AudioSessionStateExpired) - owner.deviceBecameInactive(); - - return S_OK; - } - - JUCE_COMRESULT OnSessionDisconnected (AudioSessionDisconnectReason reason) - { - Logger::writeToLog("OnSessionDisconnected"); - if (reason == DisconnectReasonFormatChanged) - owner.deviceSampleRateChanged(); - - return S_OK; - } - - private: - WASAPIDeviceBase& owner; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SessionEventCallback) - }; - - ComSmartPtr audioSessionControl; - ComSmartPtr sessionEventCallback; - - void createSessionEventCallback() - { - deleteSessionEventCallback(); - client->GetService (__uuidof (IAudioSessionControl), - (void**) audioSessionControl.resetAndGetPointerAddress()); - - if (audioSessionControl != nullptr) - { - sessionEventCallback = new SessionEventCallback (*this); - audioSessionControl->RegisterAudioSessionNotification (sessionEventCallback); - sessionEventCallback->Release(); // (required because ComBaseClassHelper objects are constructed with a ref count of 1) - } - } - - void deleteSessionEventCallback() - { - if (audioSessionControl != nullptr && sessionEventCallback != nullptr) - audioSessionControl->UnregisterAudioSessionNotification (sessionEventCallback); - - audioSessionControl = nullptr; - sessionEventCallback = nullptr; - } - - //============================================================================== - ComSmartPtr createClient() - { - ComSmartPtr newClient; - - if (device != nullptr) - logFailure (device->Activate (__uuidof (IAudioClient), CLSCTX_INPROC_SERVER, - nullptr, (void**) newClient.resetAndGetPointerAddress())); - - return newClient; - } - - struct AudioSampleFormat - { - bool useFloat; - int bitsPerSampleToTry; - int bytesPerSampleContainer; - }; - - bool tryFormat (const AudioSampleFormat sampleFormat, IAudioClient* clientToUse, double newSampleRate, - DWORD newMixFormatChannelMask, WAVEFORMATEXTENSIBLE& format) const - { - zerostruct (format); - - if (numChannels <= 2 && sampleFormat.bitsPerSampleToTry <= 16) - { - format.Format.wFormatTag = WAVE_FORMAT_PCM; - } - else - { - format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; - format.Format.cbSize = sizeof (WAVEFORMATEXTENSIBLE) - sizeof (WAVEFORMATEX); - } - - format.Format.nSamplesPerSec = (DWORD) newSampleRate; - format.Format.nChannels = (WORD) numChannels; - format.Format.wBitsPerSample = (WORD) (8 * sampleFormat.bytesPerSampleContainer); - format.Samples.wValidBitsPerSample = (WORD) (sampleFormat.bitsPerSampleToTry); - format.Format.nBlockAlign = (WORD) (format.Format.nChannels * format.Format.wBitsPerSample / 8); - format.Format.nAvgBytesPerSec = (DWORD) (format.Format.nSamplesPerSec * format.Format.nBlockAlign); - format.SubFormat = sampleFormat.useFloat ? KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : KSDATAFORMAT_SUBTYPE_PCM; - format.dwChannelMask = newMixFormatChannelMask; - - WAVEFORMATEXTENSIBLE* nearestFormat = nullptr; - - HRESULT hr = clientToUse->IsFormatSupported (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE - : AUDCLNT_SHAREMODE_SHARED, - (WAVEFORMATEX*) &format, - useExclusiveMode ? nullptr : (WAVEFORMATEX**) &nearestFormat); - logFailure (hr); - - if (hr == S_FALSE && format.Format.nSamplesPerSec == nearestFormat->Format.nSamplesPerSec) - { - copyWavFormat (format, (const WAVEFORMATEX*) nearestFormat); - hr = S_OK; - } - - CoTaskMemFree (nearestFormat); - return check (hr); - } - - bool findSupportedFormat (IAudioClient* clientToUse, double newSampleRate, - DWORD newMixFormatChannelMask, WAVEFORMATEXTENSIBLE& format) const - { - static const AudioSampleFormat formats[] = - { - { true, 32, 4 }, - { false, 32, 4 }, - { false, 24, 4 }, - { false, 24, 3 }, - { false, 20, 4 }, - { false, 20, 3 }, - { false, 16, 2 } - }; - - for (int i = 0; i < numElementsInArray (formats); ++i) - if (tryFormat (formats[i], clientToUse, newSampleRate, newMixFormatChannelMask, format)) - return true; - - return false; - } - - bool tryInitialisingWithBufferSize (const int bufferSizeSamples) - { - WAVEFORMATEXTENSIBLE format; - - if (findSupportedFormat (client, sampleRate, mixFormatChannelMask, format)) - { - REFERENCE_TIME defaultPeriod = 0, minPeriod = 0; - - check (client->GetDevicePeriod (&defaultPeriod, &minPeriod)); - - if (useExclusiveMode && bufferSizeSamples > 0) - defaultPeriod = jmax (minPeriod, samplesToRefTime (bufferSizeSamples, format.Format.nSamplesPerSec)); - - for (;;) - { - GUID session; - HRESULT hr = client->Initialize (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED, - 0x40000 /*AUDCLNT_STREAMFLAGS_EVENTCALLBACK*/, - defaultPeriod, useExclusiveMode ? defaultPeriod : 0, (WAVEFORMATEX*) &format, &session); - - if (check (hr)) - { - actualNumChannels = format.Format.nChannels; - const bool isFloat = format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && format.SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - bytesPerSample = format.Format.wBitsPerSample / 8; - bytesPerFrame = format.Format.nBlockAlign; - - updateFormat (isFloat); - return true; - } - - // Handle the "alignment dance" : http://msdn.microsoft.com/en-us/library/windows/desktop/dd370875(v=vs.85).aspx (see Remarks) - if (hr != MAKE_HRESULT (1, 0x889, 0x19)) // AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED - break; - - UINT32 numFrames = 0; - if (! check (client->GetBufferSize (&numFrames))) - break; - - // Recreate client - client = nullptr; - client = createClient(); - - defaultPeriod = samplesToRefTime (numFrames, format.Format.nSamplesPerSec); - } - } - - return false; - } - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIDeviceBase) -}; - -//============================================================================== -class WASAPIInputDevice : public WASAPIDeviceBase -{ -public: - WASAPIInputDevice (const ComSmartPtr& d, const bool exclusiveMode) - : WASAPIDeviceBase (d, exclusiveMode), - reservoir (1, 1) - { - } - - ~WASAPIInputDevice() - { - close(); - } - - bool open (const double newSampleRate, const BigInteger& newChannels, int bufferSizeSamples) - { - return openClient (newSampleRate, newChannels, bufferSizeSamples) - && (numChannels == 0 || check (client->GetService (__uuidof (IAudioCaptureClient), - (void**) captureClient.resetAndGetPointerAddress()))); - } - - void close() - { - closeClient(); - captureClient = nullptr; - reservoir.reset(); - reservoirReadPos = reservoirWritePos = 0; - } - - template - void updateFormatWithType (SourceType*) noexcept - { - typedef AudioData::Pointer NativeType; - converter = new AudioData::ConverterInstance, NativeType> (actualNumChannels, 1); - } - - void updateFormat (bool isFloat) override - { - if (isFloat) updateFormatWithType ((AudioData::Float32*) nullptr); - else if (bytesPerSample == 4) updateFormatWithType ((AudioData::Int32*) nullptr); - else if (bytesPerSample == 3) updateFormatWithType ((AudioData::Int24*) nullptr); - else updateFormatWithType ((AudioData::Int16*) nullptr); - } - - bool start (const int userBufferSize) - { - reservoirSize = actualBufferSize + userBufferSize; - reservoirMask = nextPowerOfTwo (reservoirSize) - 1; - reservoir.setSize ((reservoirMask + 1) * bytesPerFrame, true); - reservoirReadPos = reservoirWritePos = 0; - xruns = 0; - - if (! check (client->Start())) - return false; - - purgeInputBuffers(); - return true; - } - - void purgeInputBuffers() - { - uint8* inputData; - UINT32 numSamplesAvailable; - DWORD flags; - - while (captureClient->GetBuffer (&inputData, &numSamplesAvailable, &flags, nullptr, nullptr) - != MAKE_HRESULT (0, 0x889, 0x1) /* AUDCLNT_S_BUFFER_EMPTY */) - captureClient->ReleaseBuffer (numSamplesAvailable); - } - - int getNumSamplesInReservoir() const noexcept { return reservoirWritePos - reservoirReadPos; } - - void handleDeviceBuffer() - { - if (numChannels <= 0) - return; - - uint8* inputData; - UINT32 numSamplesAvailable; - DWORD flags; - - while (check (captureClient->GetBuffer (&inputData, &numSamplesAvailable, &flags, nullptr, nullptr)) && numSamplesAvailable > 0) - { - if ((flags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) != 0) - xruns++; - - int samplesLeft = (int) numSamplesAvailable; - - while (samplesLeft > 0) - { - const int localWrite = reservoirWritePos & reservoirMask; - const int samplesToDo = jmin (samplesLeft, reservoirMask + 1 - localWrite); - const int samplesToDoBytes = samplesToDo * bytesPerFrame; - - void* reservoirPtr = addBytesToPointer (reservoir.getData(), localWrite * bytesPerFrame); - - if ((flags & AUDCLNT_BUFFERFLAGS_SILENT) != 0) - zeromem (reservoirPtr, samplesToDoBytes); - else - memcpy (reservoirPtr, inputData, samplesToDoBytes); - - reservoirWritePos += samplesToDo; - inputData += samplesToDoBytes; - samplesLeft -= samplesToDo; - } - - if (getNumSamplesInReservoir() > reservoirSize) - reservoirReadPos = reservoirWritePos - reservoirSize; - - captureClient->ReleaseBuffer (numSamplesAvailable); - } - } - - void copyBuffersFromReservoir (float** destBuffers, int numDestBuffers, int bufferSize) - { - if ((numChannels <= 0 && bufferSize == 0) || reservoir.getSize() == 0) - return; - - int offset = jmax (0, bufferSize - getNumSamplesInReservoir()); - - if (offset > 0) - { - for (int i = 0; i < numDestBuffers; ++i) - zeromem (destBuffers[i], offset * sizeof (float)); - - bufferSize -= offset; - reservoirReadPos -= offset / 2; - } - - while (bufferSize > 0) - { - const int localRead = reservoirReadPos & reservoirMask; - - const int samplesToDo = jmin (bufferSize, getNumSamplesInReservoir(), reservoirMask + 1 - localRead); - if (samplesToDo <= 0) - break; - - const int reservoirOffset = localRead * bytesPerFrame; - - for (int i = 0; i < numDestBuffers; ++i) - converter->convertSamples (destBuffers[i] + offset, 0, addBytesToPointer (reservoir.getData(), reservoirOffset), channelMaps.getUnchecked(i), samplesToDo); - - bufferSize -= samplesToDo; - offset += samplesToDo; - reservoirReadPos += samplesToDo; - } - } - - ComSmartPtr captureClient; - MemoryBlock reservoir; - int reservoirSize, reservoirMask, xruns; - volatile int reservoirReadPos, reservoirWritePos; - - ScopedPointer converter; - -private: - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIInputDevice) -}; - -//============================================================================== -class WASAPIOutputDevice : public WASAPIDeviceBase -{ -public: - WASAPIOutputDevice (const ComSmartPtr& d, const bool exclusiveMode) - : WASAPIDeviceBase (d, exclusiveMode) - { - } - - ~WASAPIOutputDevice() - { - close(); - } - - bool open (const double newSampleRate, const BigInteger& newChannels, int bufferSizeSamples) - { - return openClient (newSampleRate, newChannels, bufferSizeSamples) - && (numChannels == 0 || check (client->GetService (__uuidof (IAudioRenderClient), - (void**) renderClient.resetAndGetPointerAddress()))); - } - - void close() - { - closeClient(); - renderClient = nullptr; - } - - template - void updateFormatWithType (DestType*) - { - typedef AudioData::Pointer NativeType; - converter = new AudioData::ConverterInstance > (1, actualNumChannels); - } - - void updateFormat (bool isFloat) override - { - if (isFloat) updateFormatWithType ((AudioData::Float32*) nullptr); - else if (bytesPerSample == 4) updateFormatWithType ((AudioData::Int32*) nullptr); - else if (bytesPerSample == 3) updateFormatWithType ((AudioData::Int24*) nullptr); - else updateFormatWithType ((AudioData::Int16*) nullptr); - } - - bool start() - { - int samplesToDo = getNumSamplesAvailableToCopy(); - uint8* outputData; - - if (check (renderClient->GetBuffer (samplesToDo, &outputData))) - renderClient->ReleaseBuffer (samplesToDo, AUDCLNT_BUFFERFLAGS_SILENT); - - return check (client->Start()); - } - - int getNumSamplesAvailableToCopy() const - { - if (numChannels <= 0) - return 0; - - if (! useExclusiveMode) - { - UINT32 padding = 0; - if (check (client->GetCurrentPadding (&padding))) - return actualBufferSize - (int) padding; - } - - return actualBufferSize; - } - - void copyBuffers (const float** const srcBuffers, const int numSrcBuffers, int bufferSize, - WASAPIInputDevice* inputDevice, Thread& thread) - { - if (numChannels <= 0) - return; - - int offset = 0; - - while (bufferSize > 0) - { - // This is needed in order not to drop any input data if the output device endpoint buffer was full - if ((! useExclusiveMode) && inputDevice != nullptr - && WaitForSingleObject (inputDevice->clientEvent, 0) == WAIT_OBJECT_0) - inputDevice->handleDeviceBuffer(); - - int samplesToDo = jmin (getNumSamplesAvailableToCopy(), bufferSize); - - if (samplesToDo == 0) - { - // This can ONLY occur in non-exclusive mode - if (! thread.threadShouldExit() && WaitForSingleObject (clientEvent, 1000) == WAIT_OBJECT_0) - continue; - - break; - } - - if (useExclusiveMode && WaitForSingleObject (clientEvent, 1000) == WAIT_TIMEOUT) - break; - - uint8* outputData = nullptr; - if (check (renderClient->GetBuffer ((UINT32) samplesToDo, &outputData))) - { - for (int i = 0; i < numSrcBuffers; ++i) - converter->convertSamples (outputData, channelMaps.getUnchecked(i), srcBuffers[i] + offset, 0, samplesToDo); - - renderClient->ReleaseBuffer ((UINT32) samplesToDo, 0); - } - - bufferSize -= samplesToDo; - offset += samplesToDo; - } - } - - ComSmartPtr renderClient; - ScopedPointer converter; - -private: - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIOutputDevice) -}; - -//============================================================================== -class WASAPIAudioIODevice : public AudioIODevice, - public Thread, - private AsyncUpdater -{ -public: - WASAPIAudioIODevice (const String& deviceName, - const String& typeName, - const String& outputDeviceID, - const String& inputDeviceID, - const bool exclusiveMode) - : AudioIODevice (deviceName, typeName), - Thread ("Juce WASAPI"), - outputDeviceId (outputDeviceID), - inputDeviceId (inputDeviceID), - useExclusiveMode (exclusiveMode), - isOpen_ (false), - isStarted (false), - currentBufferSizeSamples (0), - currentSampleRate (0), - callback (nullptr), - deviceBecameInactive (false) - { - } - - ~WASAPIAudioIODevice() - { - close(); - } - - bool initialise() - { - latencyIn = latencyOut = 0; - Array ratesIn, ratesOut; - - if (createDevices()) - { - jassert (inputDevice != nullptr || outputDevice != nullptr); - - if (inputDevice != nullptr && outputDevice != nullptr) - { - defaultSampleRate = jmin (inputDevice->defaultSampleRate, outputDevice->defaultSampleRate); - minBufferSize = jmin (inputDevice->minBufferSize, outputDevice->minBufferSize); - defaultBufferSize = jmax (inputDevice->defaultBufferSize, outputDevice->defaultBufferSize); - sampleRates = inputDevice->rates; - sampleRates.removeValuesNotIn (outputDevice->rates); - } - else - { - WASAPIDeviceBase* d = inputDevice != nullptr ? static_cast (inputDevice) - : static_cast (outputDevice); - defaultSampleRate = d->defaultSampleRate; - minBufferSize = d->minBufferSize; - defaultBufferSize = d->defaultBufferSize; - sampleRates = d->rates; - } - - bufferSizes.addUsingDefaultSort (defaultBufferSize); - if (minBufferSize != defaultBufferSize) - bufferSizes.addUsingDefaultSort (minBufferSize); - - int n = 64; - for (int i = 0; i < 40; ++i) - { - if (n >= minBufferSize && n <= 2048 && ! bufferSizes.contains (n)) - bufferSizes.addUsingDefaultSort (n); - - n += (n < 512) ? 32 : (n < 1024 ? 64 : 128); - } - - return true; - } - - return false; - } - - StringArray getOutputChannelNames() override - { - StringArray outChannels; - - if (outputDevice != nullptr) - for (int i = 1; i <= outputDevice->actualNumChannels; ++i) - outChannels.add ("Output channel " + String (i)); - - return outChannels; - } - - StringArray getInputChannelNames() override - { - StringArray inChannels; - - if (inputDevice != nullptr) - for (int i = 1; i <= inputDevice->actualNumChannels; ++i) - inChannels.add ("Input channel " + String (i)); - - return inChannels; - } - - Array getAvailableSampleRates() override { return sampleRates; } - Array getAvailableBufferSizes() override { return bufferSizes; } - int getDefaultBufferSize() override { return defaultBufferSize; } - - int getCurrentBufferSizeSamples() override { return currentBufferSizeSamples; } - double getCurrentSampleRate() override { return currentSampleRate; } - int getCurrentBitDepth() override { return 32; } - int getOutputLatencyInSamples() override { return latencyOut; } - int getInputLatencyInSamples() override { return latencyIn; } - BigInteger getActiveOutputChannels() const override { return outputDevice != nullptr ? outputDevice->channels : BigInteger(); } - BigInteger getActiveInputChannels() const override { return inputDevice != nullptr ? inputDevice->channels : BigInteger(); } - String getLastError() override { return lastError; } - int getXRunCount () const noexcept override { return inputDevice != nullptr ? inputDevice->xruns : -1; } - - String open (const BigInteger& inputChannels, const BigInteger& outputChannels, - double sampleRate, int bufferSizeSamples) override - { - close(); - lastError.clear(); - - if (sampleRates.size() == 0 && inputDevice != nullptr && outputDevice != nullptr) - { - lastError = TRANS("The input and output devices don't share a common sample rate!"); - return lastError; - } - - currentBufferSizeSamples = bufferSizeSamples <= 0 ? defaultBufferSize : jmax (bufferSizeSamples, minBufferSize); - currentSampleRate = sampleRate > 0 ? sampleRate : defaultSampleRate; - lastKnownInputChannels = inputChannels; - lastKnownOutputChannels = outputChannels; - - if (inputDevice != nullptr && ! inputDevice->open (currentSampleRate, inputChannels, bufferSizeSamples)) - { - lastError = TRANS("Couldn't open the input device!"); - return lastError; - } - - if (outputDevice != nullptr && ! outputDevice->open (currentSampleRate, outputChannels, bufferSizeSamples)) - { - close(); - lastError = TRANS("Couldn't open the output device!"); - return lastError; - } - - if (useExclusiveMode) - { - // This is to make sure that the callback uses actualBufferSize in case of exclusive mode - if (inputDevice != nullptr && outputDevice != nullptr && inputDevice->actualBufferSize != outputDevice->actualBufferSize) - { - close(); - lastError = TRANS("Couldn't open the output device (buffer size mismatch)"); - return lastError; - } - - currentBufferSizeSamples = outputDevice != nullptr ? outputDevice->actualBufferSize - : inputDevice->actualBufferSize; - } - - if (inputDevice != nullptr) ResetEvent (inputDevice->clientEvent); - if (outputDevice != nullptr) ResetEvent (outputDevice->clientEvent); - - deviceBecameInactive = false; - - startThread (8); - Thread::sleep (5); - - if (inputDevice != nullptr && inputDevice->client != nullptr) - { - latencyIn = (int) (inputDevice->latencySamples + currentBufferSizeSamples); - - if (! inputDevice->start (currentBufferSizeSamples)) - { - close(); - lastError = TRANS("Couldn't start the input device!"); - return lastError; - } - } - - if (outputDevice != nullptr && outputDevice->client != nullptr) - { - latencyOut = (int) (outputDevice->latencySamples + currentBufferSizeSamples); - - if (! outputDevice->start()) - { - close(); - lastError = TRANS("Couldn't start the output device!"); - return lastError; - } - } - - isOpen_ = true; - return lastError; - } - - void close() override - { - stop(); - signalThreadShouldExit(); - - if (inputDevice != nullptr) SetEvent (inputDevice->clientEvent); - if (outputDevice != nullptr) SetEvent (outputDevice->clientEvent); - - stopThread (5000); - - if (inputDevice != nullptr) inputDevice->close(); - if (outputDevice != nullptr) outputDevice->close(); - - isOpen_ = false; - } - - bool isOpen() override { return isOpen_ && isThreadRunning(); } - bool isPlaying() override { return isStarted && isOpen_ && isThreadRunning(); } - - void start (AudioIODeviceCallback* call) override - { - if (isOpen_ && call != nullptr && ! isStarted) - { - if (! isThreadRunning()) - { - // something's gone wrong and the thread's stopped.. - isOpen_ = false; - return; - } - - call->audioDeviceAboutToStart (this); - - const ScopedLock sl (startStopLock); - callback = call; - isStarted = true; - } - } - - void stop() override - { - if (isStarted) - { - AudioIODeviceCallback* const callbackLocal = callback; - - { - const ScopedLock sl (startStopLock); - isStarted = false; - } - - if (callbackLocal != nullptr) - callbackLocal->audioDeviceStopped(); - } - } - - void setMMThreadPriority() - { - DynamicLibrary dll ("avrt.dll"); - JUCE_LOAD_WINAPI_FUNCTION (dll, AvSetMmThreadCharacteristicsW, avSetMmThreadCharacteristics, HANDLE, (LPCWSTR, LPDWORD)) - JUCE_LOAD_WINAPI_FUNCTION (dll, AvSetMmThreadPriority, avSetMmThreadPriority, HANDLE, (HANDLE, AVRT_PRIORITY)) - - if (avSetMmThreadCharacteristics != 0 && avSetMmThreadPriority != 0) - { - DWORD dummy = 0; - HANDLE h = avSetMmThreadCharacteristics (L"Pro Audio", &dummy); - - if (h != 0) - avSetMmThreadPriority (h, AVRT_PRIORITY_NORMAL); - } - } - - void run() override - { - setMMThreadPriority(); - - const int bufferSize = currentBufferSizeSamples; - const int numInputBuffers = getActiveInputChannels().countNumberOfSetBits(); - const int numOutputBuffers = getActiveOutputChannels().countNumberOfSetBits(); - bool sampleRateHasChanged = false; - - AudioSampleBuffer ins (jmax (1, numInputBuffers), bufferSize + 32); - AudioSampleBuffer outs (jmax (1, numOutputBuffers), bufferSize + 32); - float** const inputBuffers = ins.getArrayOfWritePointers(); - float** const outputBuffers = outs.getArrayOfWritePointers(); - ins.clear(); - outs.clear(); - - while (! threadShouldExit()) - { - if (outputDevice != nullptr && outputDevice->shouldClose) - deviceBecameInactive = true; - - if (inputDevice != nullptr && ! deviceBecameInactive) - { - if (inputDevice->shouldClose) - deviceBecameInactive = true; - - if (outputDevice == nullptr) - { - if (WaitForSingleObject (inputDevice->clientEvent, 1000) == WAIT_TIMEOUT) - break; - - inputDevice->handleDeviceBuffer(); - - if (inputDevice->getNumSamplesInReservoir() < bufferSize) - continue; - } - else - { - if (useExclusiveMode && WaitForSingleObject (inputDevice->clientEvent, 0) == WAIT_OBJECT_0) - inputDevice->handleDeviceBuffer(); - } - - inputDevice->copyBuffersFromReservoir (inputBuffers, numInputBuffers, bufferSize); - - if (inputDevice->sampleRateHasChanged) - { - sampleRateHasChanged = true; - sampleRateChangedByOutput = false; - } - } - - if (! deviceBecameInactive) - { - const ScopedTryLock sl (startStopLock); - - if (sl.isLocked() && isStarted) - callback->audioDeviceIOCallback (const_cast (inputBuffers), numInputBuffers, - outputBuffers, numOutputBuffers, bufferSize); - else - outs.clear(); - } - - if (outputDevice != nullptr && !deviceBecameInactive) - { - // Note that this function is handed the input device so it can check for the event and make sure - // the input reservoir is filled up correctly even when bufferSize > device actualBufferSize - outputDevice->copyBuffers (const_cast (outputBuffers), numOutputBuffers, bufferSize, inputDevice, *this); - - if (outputDevice->sampleRateHasChanged) - { - sampleRateHasChanged = true; - sampleRateChangedByOutput = true; - } - } - - if (sampleRateHasChanged || deviceBecameInactive) - { - triggerAsyncUpdate(); - break; // Quit the thread... will restart it later! - } - } - } - - //============================================================================== - String outputDeviceId, inputDeviceId; - String lastError; - -private: - // Device stats... - ScopedPointer inputDevice; - ScopedPointer outputDevice; - const bool useExclusiveMode; - double defaultSampleRate; - int minBufferSize, defaultBufferSize; - int latencyIn, latencyOut; - Array sampleRates; - Array bufferSizes; - - // Active state... - bool isOpen_, isStarted; - int currentBufferSizeSamples; - double currentSampleRate; - bool sampleRateChangedByOutput, deviceBecameInactive; - - AudioIODeviceCallback* callback; - CriticalSection startStopLock; - - BigInteger lastKnownInputChannels, lastKnownOutputChannels; - - //============================================================================== - bool createDevices() - { - ComSmartPtr enumerator; - if (! check (enumerator.CoCreateInstance (__uuidof (MMDeviceEnumerator)))) - return false; - - ComSmartPtr deviceCollection; - if (! check (enumerator->EnumAudioEndpoints (eAll, DEVICE_STATE_ACTIVE, deviceCollection.resetAndGetPointerAddress()))) - return false; - - UINT32 numDevices = 0; - if (! check (deviceCollection->GetCount (&numDevices))) - return false; - - for (UINT32 i = 0; i < numDevices; ++i) - { - ComSmartPtr device; - if (! check (deviceCollection->Item (i, device.resetAndGetPointerAddress()))) - continue; - - const String deviceId (getDeviceID (device)); - if (deviceId.isEmpty()) - continue; - - const EDataFlow flow = getDataFlow (device); - - if (deviceId == inputDeviceId && flow == eCapture) - inputDevice = new WASAPIInputDevice (device, useExclusiveMode); - else if (deviceId == outputDeviceId && flow == eRender) - outputDevice = new WASAPIOutputDevice (device, useExclusiveMode); - } - - return (outputDeviceId.isEmpty() || (outputDevice != nullptr && outputDevice->isOk())) - && (inputDeviceId.isEmpty() || (inputDevice != nullptr && inputDevice->isOk())); - } - - //============================================================================== - void handleAsyncUpdate() override - { - stop(); - - outputDevice = nullptr; - inputDevice = nullptr; - - // sample rate change - if (! deviceBecameInactive) - { - initialise(); - - open (lastKnownInputChannels, lastKnownOutputChannels, - getChangedSampleRate(), currentBufferSizeSamples); - - start (callback); - } - } - - double getChangedSampleRate() const - { - if (outputDevice != nullptr && sampleRateChangedByOutput) - return outputDevice->defaultSampleRate; - - if (inputDevice != nullptr && ! sampleRateChangedByOutput) - return inputDevice->defaultSampleRate; - - return 0.0; - } - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIAudioIODevice) -}; - - -//============================================================================== -class WASAPIAudioIODeviceType : public AudioIODeviceType, - private DeviceChangeDetector -{ -public: - WASAPIAudioIODeviceType (bool exclusive) - : AudioIODeviceType (exclusive ? "Windows Audio (Exclusive Mode)" : "Windows Audio"), - DeviceChangeDetector (L"Windows Audio"), - exclusiveMode (exclusive), - hasScanned (false) - { - } - - ~WASAPIAudioIODeviceType() - { - if (notifyClient != nullptr) - enumerator->UnregisterEndpointNotificationCallback (notifyClient); - } - - //============================================================================== - void scanForDevices() - { - hasScanned = true; - - outputDeviceNames.clear(); - inputDeviceNames.clear(); - outputDeviceIds.clear(); - inputDeviceIds.clear(); - - scan (outputDeviceNames, inputDeviceNames, - outputDeviceIds, inputDeviceIds); - } - - StringArray getDeviceNames (bool wantInputNames) const - { - jassert (hasScanned); // need to call scanForDevices() before doing this - - return wantInputNames ? inputDeviceNames - : outputDeviceNames; - } - - int getDefaultDeviceIndex (bool /*forInput*/) const - { - jassert (hasScanned); // need to call scanForDevices() before doing this - return 0; - } - - int getIndexOfDevice (AudioIODevice* device, bool asInput) const - { - jassert (hasScanned); // need to call scanForDevices() before doing this - - if (WASAPIAudioIODevice* const d = dynamic_cast (device)) - return asInput ? inputDeviceIds.indexOf (d->inputDeviceId) - : outputDeviceIds.indexOf (d->outputDeviceId); - - return -1; - } - - bool hasSeparateInputsAndOutputs() const { return true; } - - AudioIODevice* createDevice (const String& outputDeviceName, - const String& inputDeviceName) - { - jassert (hasScanned); // need to call scanForDevices() before doing this - - ScopedPointer device; - - const int outputIndex = outputDeviceNames.indexOf (outputDeviceName); - const int inputIndex = inputDeviceNames.indexOf (inputDeviceName); - - if (outputIndex >= 0 || inputIndex >= 0) - { - device = new WASAPIAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName - : inputDeviceName, - getTypeName(), - outputDeviceIds [outputIndex], - inputDeviceIds [inputIndex], - exclusiveMode); - - if (! device->initialise()) - device = nullptr; - } - - return device.release(); - } - - //============================================================================== - StringArray outputDeviceNames, outputDeviceIds; - StringArray inputDeviceNames, inputDeviceIds; - -private: - bool exclusiveMode, hasScanned; - ComSmartPtr enumerator; - - //============================================================================== - class ChangeNotificationClient : public ComBaseClassHelper - { - public: - ChangeNotificationClient (WASAPIAudioIODeviceType& d) - : ComBaseClassHelper (0), device (d) {} - - HRESULT STDMETHODCALLTYPE OnDeviceAdded (LPCWSTR) { return notify(); } - HRESULT STDMETHODCALLTYPE OnDeviceRemoved (LPCWSTR) { return notify(); } - HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR, DWORD) { return notify(); } - HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged (EDataFlow, ERole, LPCWSTR) { return notify(); } - HRESULT STDMETHODCALLTYPE OnPropertyValueChanged (LPCWSTR, const PROPERTYKEY) { return notify(); } - - private: - WASAPIAudioIODeviceType& device; - - HRESULT notify() { device.triggerAsyncDeviceChangeCallback(); return S_OK; } - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChangeNotificationClient) - }; - - ComSmartPtr notifyClient; - - //============================================================================== - static String getDefaultEndpoint (IMMDeviceEnumerator* const enumerator, const bool forCapture) - { - String s; - IMMDevice* dev = nullptr; - - if (check (enumerator->GetDefaultAudioEndpoint (forCapture ? eCapture : eRender, - eMultimedia, &dev))) - { - WCHAR* deviceId = nullptr; - - if (check (dev->GetId (&deviceId))) - { - s = deviceId; - CoTaskMemFree (deviceId); - } - - dev->Release(); - } - - return s; - } - - //============================================================================== - void scan (StringArray& outDeviceNames, - StringArray& inDeviceNames, - StringArray& outDeviceIds, - StringArray& inDeviceIds) - { - if (enumerator == nullptr) - { - if (! check (enumerator.CoCreateInstance (__uuidof (MMDeviceEnumerator)))) - return; - - notifyClient = new ChangeNotificationClient (*this); - enumerator->RegisterEndpointNotificationCallback (notifyClient); - } - - const String defaultRenderer (getDefaultEndpoint (enumerator, false)); - const String defaultCapture (getDefaultEndpoint (enumerator, true)); - - ComSmartPtr deviceCollection; - UINT32 numDevices = 0; - - if (! (check (enumerator->EnumAudioEndpoints (eAll, DEVICE_STATE_ACTIVE, deviceCollection.resetAndGetPointerAddress())) - && check (deviceCollection->GetCount (&numDevices)))) - return; - - for (UINT32 i = 0; i < numDevices; ++i) - { - ComSmartPtr device; - if (! check (deviceCollection->Item (i, device.resetAndGetPointerAddress()))) - continue; - - DWORD state = 0; - if (! (check (device->GetState (&state)) && state == DEVICE_STATE_ACTIVE)) - continue; - - const String deviceId (getDeviceID (device)); - String name; - - { - ComSmartPtr properties; - if (! check (device->OpenPropertyStore (STGM_READ, properties.resetAndGetPointerAddress()))) - continue; - - PROPVARIANT value; - zerostruct (value); - - const PROPERTYKEY PKEY_Device_FriendlyName - = { { 0xa45c254e, 0xdf1c, 0x4efd, { 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0 } }, 14 }; - - if (check (properties->GetValue (PKEY_Device_FriendlyName, &value))) - name = value.pwszVal; - - PropVariantClear (&value); - } - - const EDataFlow flow = getDataFlow (device); - - if (flow == eRender) - { - const int index = (deviceId == defaultRenderer) ? 0 : -1; - outDeviceIds.insert (index, deviceId); - outDeviceNames.insert (index, name); - } - else if (flow == eCapture) - { - const int index = (deviceId == defaultCapture) ? 0 : -1; - inDeviceIds.insert (index, deviceId); - inDeviceNames.insert (index, name); - } - } - - inDeviceNames.appendNumbersToDuplicates (false, false); - outDeviceNames.appendNumbersToDuplicates (false, false); - } - - //============================================================================== - void systemDeviceChanged() override - { - StringArray newOutNames, newInNames, newOutIds, newInIds; - scan (newOutNames, newInNames, newOutIds, newInIds); - - if (newOutNames != outputDeviceNames - || newInNames != inputDeviceNames - || newOutIds != outputDeviceIds - || newInIds != inputDeviceIds) - { - hasScanned = true; - outputDeviceNames = newOutNames; - inputDeviceNames = newInNames; - outputDeviceIds = newOutIds; - inputDeviceIds = newInIds; - } - - callDeviceChangeListeners(); - } - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIAudioIODeviceType) -}; - -//============================================================================== -struct MMDeviceMasterVolume -{ - MMDeviceMasterVolume() - { - ComSmartPtr enumerator; - if (check (enumerator.CoCreateInstance (__uuidof (MMDeviceEnumerator)))) - { - ComSmartPtr device; - if (check (enumerator->GetDefaultAudioEndpoint (eRender, eConsole, device.resetAndGetPointerAddress()))) - check (device->Activate (__uuidof (IAudioEndpointVolume), CLSCTX_INPROC_SERVER, nullptr, - (void**) endpointVolume.resetAndGetPointerAddress())); - } - } - - float getGain() const - { - float vol = 0.0f; - if (endpointVolume != nullptr) - check (endpointVolume->GetMasterVolumeLevelScalar (&vol)); - - return vol; - } - - bool setGain (float newGain) const - { - return endpointVolume != nullptr - && check (endpointVolume->SetMasterVolumeLevelScalar (jlimit (0.0f, 1.0f, newGain), nullptr)); - } - - bool isMuted() const - { - BOOL mute = 0; - return endpointVolume != nullptr - && check (endpointVolume->GetMute (&mute)) && mute != 0; - } - - bool setMuted (bool shouldMute) const - { - return endpointVolume != nullptr - && check (endpointVolume->SetMute (shouldMute, nullptr)); - } - - ComSmartPtr endpointVolume; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MMDeviceMasterVolume) -}; - -} - -//============================================================================== -AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (bool exclusiveMode) -{ - #if ! JUCE_WASAPI_EXCLUSIVE - if (exclusiveMode) - return nullptr; - #endif - - return SystemStats::getOperatingSystemType() >= SystemStats::WinVista - ? new WasapiClasses::WASAPIAudioIODeviceType (exclusiveMode) - : nullptr; -} - -//============================================================================== -#define JUCE_SYSTEMAUDIOVOL_IMPLEMENTED 1 -float JUCE_CALLTYPE SystemAudioVolume::getGain() { return WasapiClasses::MMDeviceMasterVolume().getGain(); } -bool JUCE_CALLTYPE SystemAudioVolume::setGain (float gain) { return WasapiClasses::MMDeviceMasterVolume().setGain (gain); } -bool JUCE_CALLTYPE SystemAudioVolume::isMuted() { return WasapiClasses::MMDeviceMasterVolume().isMuted(); } -bool JUCE_CALLTYPE SystemAudioVolume::setMuted (bool mute) { return WasapiClasses::MMDeviceMasterVolume().setMuted (mute); } - -} // namespace juce diff --git a/source/modules/juce_audio_devices/sources/juce_AudioSourcePlayer.cpp b/source/modules/juce_audio_devices/sources/juce_AudioSourcePlayer.cpp deleted file mode 100644 index b03ca9a93..000000000 --- a/source/modules/juce_audio_devices/sources/juce_AudioSourcePlayer.cpp +++ /dev/null @@ -1,185 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -AudioSourcePlayer::AudioSourcePlayer() - : source (nullptr), - sampleRate (0), - bufferSize (0), - lastGain (1.0f), - gain (1.0f) -{ -} - -AudioSourcePlayer::~AudioSourcePlayer() -{ - setSource (nullptr); -} - -void AudioSourcePlayer::setSource (AudioSource* newSource) -{ - if (source != newSource) - { - AudioSource* const oldSource = source; - - if (newSource != nullptr && bufferSize > 0 && sampleRate > 0) - newSource->prepareToPlay (bufferSize, sampleRate); - - { - const ScopedLock sl (readLock); - source = newSource; - } - - if (oldSource != nullptr) - oldSource->releaseResources(); - } -} - -void AudioSourcePlayer::setGain (const float newGain) noexcept -{ - gain = newGain; -} - -void AudioSourcePlayer::audioDeviceIOCallback (const float** inputChannelData, - int totalNumInputChannels, - float** outputChannelData, - int totalNumOutputChannels, - int numSamples) -{ - // these should have been prepared by audioDeviceAboutToStart()... - jassert (sampleRate > 0 && bufferSize > 0); - - const ScopedLock sl (readLock); - - if (source != nullptr) - { - int numActiveChans = 0, numInputs = 0, numOutputs = 0; - - // messy stuff needed to compact the channels down into an array - // of non-zero pointers.. - for (int i = 0; i < totalNumInputChannels; ++i) - { - if (inputChannelData[i] != nullptr) - { - inputChans [numInputs++] = inputChannelData[i]; - if (numInputs >= numElementsInArray (inputChans)) - break; - } - } - - for (int i = 0; i < totalNumOutputChannels; ++i) - { - if (outputChannelData[i] != nullptr) - { - outputChans [numOutputs++] = outputChannelData[i]; - if (numOutputs >= numElementsInArray (outputChans)) - break; - } - } - - if (numInputs > numOutputs) - { - // if there aren't enough output channels for the number of - // inputs, we need to create some temporary extra ones (can't - // use the input data in case it gets written to) - tempBuffer.setSize (numInputs - numOutputs, numSamples, - false, false, true); - - for (int i = 0; i < numOutputs; ++i) - { - channels[numActiveChans] = outputChans[i]; - memcpy (channels[numActiveChans], inputChans[i], sizeof (float) * (size_t) numSamples); - ++numActiveChans; - } - - for (int i = numOutputs; i < numInputs; ++i) - { - channels[numActiveChans] = tempBuffer.getWritePointer (i - numOutputs); - memcpy (channels[numActiveChans], inputChans[i], sizeof (float) * (size_t) numSamples); - ++numActiveChans; - } - } - else - { - for (int i = 0; i < numInputs; ++i) - { - channels[numActiveChans] = outputChans[i]; - memcpy (channels[numActiveChans], inputChans[i], sizeof (float) * (size_t) numSamples); - ++numActiveChans; - } - - for (int i = numInputs; i < numOutputs; ++i) - { - channels[numActiveChans] = outputChans[i]; - zeromem (channels[numActiveChans], sizeof (float) * (size_t) numSamples); - ++numActiveChans; - } - } - - AudioSampleBuffer buffer (channels, numActiveChans, numSamples); - - AudioSourceChannelInfo info (&buffer, 0, numSamples); - source->getNextAudioBlock (info); - - for (int i = info.buffer->getNumChannels(); --i >= 0;) - buffer.applyGainRamp (i, info.startSample, info.numSamples, lastGain, gain); - - lastGain = gain; - } - else - { - for (int i = 0; i < totalNumOutputChannels; ++i) - if (outputChannelData[i] != nullptr) - zeromem (outputChannelData[i], sizeof (float) * (size_t) numSamples); - } -} - -void AudioSourcePlayer::audioDeviceAboutToStart (AudioIODevice* device) -{ - prepareToPlay (device->getCurrentSampleRate(), - device->getCurrentBufferSizeSamples()); -} - -void AudioSourcePlayer::prepareToPlay (double newSampleRate, int newBufferSize) -{ - sampleRate = newSampleRate; - bufferSize = newBufferSize; - zeromem (channels, sizeof (channels)); - - if (source != nullptr) - source->prepareToPlay (bufferSize, sampleRate); -} - -void AudioSourcePlayer::audioDeviceStopped() -{ - if (source != nullptr) - source->releaseResources(); - - sampleRate = 0.0; - bufferSize = 0; - - tempBuffer.setSize (2, 8); -} - -} // namespace juce diff --git a/source/modules/juce_audio_devices/sources/juce_AudioSourcePlayer.h b/source/modules/juce_audio_devices/sources/juce_AudioSourcePlayer.h deleted file mode 100644 index d624b0a57..000000000 --- a/source/modules/juce_audio_devices/sources/juce_AudioSourcePlayer.h +++ /dev/null @@ -1,111 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -//============================================================================== -/** - Wrapper class to continuously stream audio from an audio source to an - AudioIODevice. - - This object acts as an AudioIODeviceCallback, so can be attached to an - output device, and will stream audio from an AudioSource. -*/ -class JUCE_API AudioSourcePlayer : public AudioIODeviceCallback -{ -public: - //============================================================================== - /** Creates an empty AudioSourcePlayer. */ - AudioSourcePlayer(); - - /** Destructor. - - Make sure this object isn't still being used by an AudioIODevice before - deleting it! - */ - virtual ~AudioSourcePlayer(); - - //============================================================================== - /** Changes the current audio source to play from. - - If the source passed in is already being used, this method will do nothing. - If the source is not null, its prepareToPlay() method will be called - before it starts being used for playback. - - If there's another source currently playing, its releaseResources() method - will be called after it has been swapped for the new one. - - @param newSource the new source to use - this will NOT be deleted - by this object when no longer needed, so it's the - caller's responsibility to manage it. - */ - void setSource (AudioSource* newSource); - - /** Returns the source that's playing. - May return nullptr if there's no source. - */ - AudioSource* getCurrentSource() const noexcept { return source; } - - /** Sets a gain to apply to the audio data. - @see getGain - */ - void setGain (float newGain) noexcept; - - /** Returns the current gain. - @see setGain - */ - float getGain() const noexcept { return gain; } - - //============================================================================== - /** Implementation of the AudioIODeviceCallback method. */ - void audioDeviceIOCallback (const float** inputChannelData, - int totalNumInputChannels, - float** outputChannelData, - int totalNumOutputChannels, - int numSamples) override; - - /** Implementation of the AudioIODeviceCallback method. */ - void audioDeviceAboutToStart (AudioIODevice* device) override; - - /** Implementation of the AudioIODeviceCallback method. */ - void audioDeviceStopped() override; - - /** An alternative method for initialising the source without an AudioIODevice. */ - void prepareToPlay (double sampleRate, int blockSize); - -private: - //============================================================================== - CriticalSection readLock; - AudioSource* source; - double sampleRate; - int bufferSize; - float* channels [128]; - float* outputChans [128]; - const float* inputChans [128]; - AudioSampleBuffer tempBuffer; - float lastGain, gain; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSourcePlayer) -}; - -} // namespace juce diff --git a/source/modules/juce_audio_devices/sources/juce_AudioTransportSource.cpp b/source/modules/juce_audio_devices/sources/juce_AudioTransportSource.cpp deleted file mode 100644 index bbc3d8ae4..000000000 --- a/source/modules/juce_audio_devices/sources/juce_AudioTransportSource.cpp +++ /dev/null @@ -1,286 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -AudioTransportSource::AudioTransportSource() -{ -} - -AudioTransportSource::~AudioTransportSource() -{ - setSource (nullptr); - releaseMasterResources(); -} - -void AudioTransportSource::setSource (PositionableAudioSource* const newSource, - int readAheadSize, TimeSliceThread* readAheadThread, - double sourceSampleRateToCorrectFor, int maxNumChannels) -{ - if (source == newSource) - { - if (source == nullptr) - return; - - setSource (nullptr, 0, nullptr); // deselect and reselect to avoid releasing resources wrongly - } - - readAheadBufferSize = readAheadSize; - sourceSampleRate = sourceSampleRateToCorrectFor; - - ResamplingAudioSource* newResamplerSource = nullptr; - BufferingAudioSource* newBufferingSource = nullptr; - PositionableAudioSource* newPositionableSource = nullptr; - AudioSource* newMasterSource = nullptr; - - ScopedPointer oldResamplerSource (resamplerSource); - ScopedPointer oldBufferingSource (bufferingSource); - AudioSource* oldMasterSource = masterSource; - - if (newSource != nullptr) - { - newPositionableSource = newSource; - - if (readAheadSize > 0) - { - // If you want to use a read-ahead buffer, you must also provide a TimeSliceThread - // for it to use! - jassert (readAheadThread != nullptr); - - newPositionableSource = newBufferingSource - = new BufferingAudioSource (newPositionableSource, *readAheadThread, - false, readAheadSize, maxNumChannels); - } - - newPositionableSource->setNextReadPosition (0); - - if (sourceSampleRateToCorrectFor > 0) - newMasterSource = newResamplerSource - = new ResamplingAudioSource (newPositionableSource, false, maxNumChannels); - else - newMasterSource = newPositionableSource; - - if (isPrepared) - { - if (newResamplerSource != nullptr && sourceSampleRate > 0 && sampleRate > 0) - newResamplerSource->setResamplingRatio (sourceSampleRate / sampleRate); - - newMasterSource->prepareToPlay (blockSize, sampleRate); - } - } - - { - const ScopedLock sl (callbackLock); - - source = newSource; - resamplerSource = newResamplerSource; - bufferingSource = newBufferingSource; - masterSource = newMasterSource; - positionableSource = newPositionableSource; - - inputStreamEOF = false; - playing = false; - } - - if (oldMasterSource != nullptr) - oldMasterSource->releaseResources(); -} - -void AudioTransportSource::start() -{ - if ((! playing) && masterSource != nullptr) - { - { - const ScopedLock sl (callbackLock); - playing = true; - stopped = false; - inputStreamEOF = false; - } - - sendChangeMessage(); - } -} - -void AudioTransportSource::stop() -{ - if (playing) - { - { - const ScopedLock sl (callbackLock); - playing = false; - } - - int n = 500; - while (--n >= 0 && ! stopped) - Thread::sleep (2); - - sendChangeMessage(); - } -} - -void AudioTransportSource::setPosition (double newPosition) -{ - if (sampleRate > 0.0) - setNextReadPosition ((int64) (newPosition * sampleRate)); -} - -double AudioTransportSource::getCurrentPosition() const -{ - if (sampleRate > 0.0) - return (double) getNextReadPosition() / sampleRate; - - return 0.0; -} - -double AudioTransportSource::getLengthInSeconds() const -{ - if (sampleRate > 0.0) - return (double) getTotalLength() / sampleRate; - - return 0.0; -} - -void AudioTransportSource::setNextReadPosition (int64 newPosition) -{ - if (positionableSource != nullptr) - { - if (sampleRate > 0 && sourceSampleRate > 0) - newPosition = (int64) ((double) newPosition * sourceSampleRate / sampleRate); - - positionableSource->setNextReadPosition (newPosition); - - if (resamplerSource != nullptr) - resamplerSource->flushBuffers(); - - inputStreamEOF = false; - } -} - -int64 AudioTransportSource::getNextReadPosition() const -{ - if (positionableSource != nullptr) - { - const double ratio = (sampleRate > 0 && sourceSampleRate > 0) ? sampleRate / sourceSampleRate : 1.0; - return (int64) ((double) positionableSource->getNextReadPosition() * ratio); - } - - return 0; -} - -int64 AudioTransportSource::getTotalLength() const -{ - const ScopedLock sl (callbackLock); - - if (positionableSource != nullptr) - { - const double ratio = (sampleRate > 0 && sourceSampleRate > 0) ? sampleRate / sourceSampleRate : 1.0; - return (int64) ((double) positionableSource->getTotalLength() * ratio); - } - - return 0; -} - -bool AudioTransportSource::isLooping() const -{ - const ScopedLock sl (callbackLock); - return positionableSource != nullptr && positionableSource->isLooping(); -} - -void AudioTransportSource::setGain (const float newGain) noexcept -{ - gain = newGain; -} - -void AudioTransportSource::prepareToPlay (int samplesPerBlockExpected, double newSampleRate) -{ - const ScopedLock sl (callbackLock); - - sampleRate = newSampleRate; - blockSize = samplesPerBlockExpected; - - if (masterSource != nullptr) - masterSource->prepareToPlay (samplesPerBlockExpected, sampleRate); - - if (resamplerSource != nullptr && sourceSampleRate > 0) - resamplerSource->setResamplingRatio (sourceSampleRate / sampleRate); - - inputStreamEOF = false; - isPrepared = true; -} - -void AudioTransportSource::releaseMasterResources() -{ - const ScopedLock sl (callbackLock); - - if (masterSource != nullptr) - masterSource->releaseResources(); - - isPrepared = false; -} - -void AudioTransportSource::releaseResources() -{ - releaseMasterResources(); -} - -void AudioTransportSource::getNextAudioBlock (const AudioSourceChannelInfo& info) -{ - const ScopedLock sl (callbackLock); - - if (masterSource != nullptr && ! stopped) - { - masterSource->getNextAudioBlock (info); - - if (! playing) - { - // just stopped playing, so fade out the last block.. - for (int i = info.buffer->getNumChannels(); --i >= 0;) - info.buffer->applyGainRamp (i, info.startSample, jmin (256, info.numSamples), 1.0f, 0.0f); - - if (info.numSamples > 256) - info.buffer->clear (info.startSample + 256, info.numSamples - 256); - } - - if (positionableSource->getNextReadPosition() > positionableSource->getTotalLength() + 1 - && ! positionableSource->isLooping()) - { - playing = false; - inputStreamEOF = true; - sendChangeMessage(); - } - - stopped = ! playing; - - for (int i = info.buffer->getNumChannels(); --i >= 0;) - info.buffer->applyGainRamp (i, info.startSample, info.numSamples, lastGain, gain); - } - else - { - info.clearActiveBufferRegion(); - stopped = true; - } - - lastGain = gain; -} - -} // namespace juce diff --git a/source/modules/juce_audio_devices/sources/juce_AudioTransportSource.h b/source/modules/juce_audio_devices/sources/juce_AudioTransportSource.h deleted file mode 100644 index 90103dad6..000000000 --- a/source/modules/juce_audio_devices/sources/juce_AudioTransportSource.h +++ /dev/null @@ -1,176 +0,0 @@ -/* - ============================================================================== - - 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 -{ - -//============================================================================== -/** - An AudioSource that takes a PositionableAudioSource and allows it to be - played, stopped, started, etc. - - This can also be told use a buffer and background thread to read ahead, and - if can correct for different sample-rates. - - You may want to use one of these along with an AudioSourcePlayer and AudioIODevice - to control playback of an audio file. - - @see AudioSource, AudioSourcePlayer -*/ -class JUCE_API AudioTransportSource : public PositionableAudioSource, - public ChangeBroadcaster -{ -public: - //============================================================================== - /** Creates an AudioTransportSource. - After creating one of these, use the setSource() method to select an input source. - */ - AudioTransportSource(); - - /** Destructor. */ - ~AudioTransportSource(); - - //============================================================================== - /** Sets the reader that is being used as the input source. - - This will stop playback, reset the position to 0 and change to the new reader. - - The source passed in will not be deleted by this object, so must be managed by - the caller. - - @param newSource the new input source to use. This may be a nullptr - @param readAheadBufferSize a size of buffer to use for reading ahead. If this - is zero, no reading ahead will be done; if it's - greater than zero, a BufferingAudioSource will be used - to do the reading-ahead. If you set a non-zero value here, - you'll also need to set the readAheadThread parameter. - @param readAheadThread if you set readAheadBufferSize to a non-zero value, then - you'll also need to supply this TimeSliceThread object for - the background reader to use. The thread object must not be - deleted while the AudioTransport source is still using it. - @param sourceSampleRateToCorrectFor if this is non-zero, it specifies the sample - rate of the source, and playback will be sample-rate - adjusted to maintain playback at the correct pitch. If - this is 0, no sample-rate adjustment will be performed - @param maxNumChannels the maximum number of channels that may need to be played - */ - void setSource (PositionableAudioSource* newSource, - int readAheadBufferSize = 0, - TimeSliceThread* readAheadThread = nullptr, - double sourceSampleRateToCorrectFor = 0.0, - int maxNumChannels = 2); - - //============================================================================== - /** Changes the current playback position in the source stream. - - The next time the getNextAudioBlock() method is called, this - is the time from which it'll read data. - - @see getPosition - */ - void setPosition (double newPosition); - - /** Returns the position that the next data block will be read from - This is a time in seconds. - */ - double getCurrentPosition() const; - - /** Returns the stream's length in seconds. */ - double getLengthInSeconds() const; - - /** Returns true if the player has stopped because its input stream ran out of data. */ - bool hasStreamFinished() const noexcept { return inputStreamEOF; } - - //============================================================================== - /** Starts playing (if a source has been selected). - - If it starts playing, this will send a message to any ChangeListeners - that are registered with this object. - */ - void start(); - - /** Stops playing. - - If it's actually playing, this will send a message to any ChangeListeners - that are registered with this object. - */ - void stop(); - - /** Returns true if it's currently playing. */ - bool isPlaying() const noexcept { return playing; } - - //============================================================================== - /** Changes the gain to apply to the output. - @param newGain a factor by which to multiply the outgoing samples, - so 1.0 = 0dB, 0.5 = -6dB, 2.0 = 6dB, etc. - */ - void setGain (float newGain) noexcept; - - /** Returns the current gain setting. - @see setGain - */ - float getGain() const noexcept { return gain; } - - //============================================================================== - /** Implementation of the AudioSource method. */ - void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; - - /** Implementation of the AudioSource method. */ - void releaseResources() override; - - /** Implementation of the AudioSource method. */ - void getNextAudioBlock (const AudioSourceChannelInfo&) override; - - //============================================================================== - /** Implements the PositionableAudioSource method. */ - void setNextReadPosition (int64 newPosition) override; - - /** Implements the PositionableAudioSource method. */ - int64 getNextReadPosition() const override; - - /** Implements the PositionableAudioSource method. */ - int64 getTotalLength() const override; - - /** Implements the PositionableAudioSource method. */ - bool isLooping() const override; - -private: - //============================================================================== - PositionableAudioSource* source = nullptr; - ResamplingAudioSource* resamplerSource = nullptr; - BufferingAudioSource* bufferingSource = nullptr; - PositionableAudioSource* positionableSource = nullptr; - AudioSource* masterSource = nullptr; - - CriticalSection callbackLock; - float volatile gain = 1.0f, lastGain = 1.0f; - bool volatile playing = false, stopped = true; - double sampleRate = 44100.0, sourceSampleRate = 0; - int blockSize = 128, readAheadBufferSize = 0; - bool volatile isPrepared = false, inputStreamEOF = false; - - void releaseMasterResources(); - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioTransportSource) -}; - -} // namespace juce