This reverts commit ff1bec4635.
Signed-off-by: falkTX <falktx@gmail.com>
tags/v2.1-alpha1-winvst
| @@ -0,0 +1,125 @@ | |||||
| #!/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) | |||||
| # ---------------------------------------------------------------------------------------------------------------------------- | |||||
| @@ -0,0 +1,532 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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<AudioIODeviceType>& 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<AudioIODeviceType>& 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<AudioIODeviceType> availableDeviceTypes; | |||||
| OwnedArray<AudioDeviceSetup> lastDeviceTypeConfigs; | |||||
| AudioDeviceSetup currentSetup; | |||||
| ScopedPointer<AudioIODevice> currentAudioDevice; | |||||
| Array<AudioIODeviceCallback*> callbacks; | |||||
| int numInputChansNeeded, numOutputChansNeeded; | |||||
| String currentDeviceType; | |||||
| BigInteger inputChannels, outputChannels; | |||||
| ScopedPointer<XmlElement> lastExplicitSettings; | |||||
| mutable bool listNeedsScanning; | |||||
| AudioSampleBuffer tempBuffer; | |||||
| struct MidiCallbackInfo | |||||
| { | |||||
| String deviceName; | |||||
| MidiInputCallback* callback; | |||||
| }; | |||||
| StringArray midiInsFromXml; | |||||
| OwnedArray<MidiInput> enabledMidiInputs; | |||||
| Array<MidiCallbackInfo> midiCallbacks; | |||||
| String defaultMidiOutputName; | |||||
| ScopedPointer<MidiOutput> defaultMidiOutput; | |||||
| CriticalSection audioCallbackLock, midiCallbackLock; | |||||
| ScopedPointer<AudioSampleBuffer> 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<int> enabled; | |||||
| double level; | |||||
| }; | |||||
| LevelMeter inputLevelMeter, outputLevelMeter; | |||||
| //============================================================================== | |||||
| class CallbackHandler; | |||||
| friend class CallbackHandler; | |||||
| friend struct ContainerDeletePolicy<CallbackHandler>; | |||||
| ScopedPointer<CallbackHandler> 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 | |||||
| @@ -0,0 +1,45 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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 | |||||
| @@ -0,0 +1,319 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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<double> getAvailableSampleRates() = 0; | |||||
| /** Returns the set of buffer sizes that are available. | |||||
| @see getCurrentBufferSizeSamples, getDefaultBufferSize | |||||
| */ | |||||
| virtual Array<int> 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 | |||||
| @@ -0,0 +1,81 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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 | |||||
| @@ -0,0 +1,178 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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<AudioIODeviceType> 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<Listener> listeners; | |||||
| JUCE_DECLARE_NON_COPYABLE (AudioIODeviceType) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,57 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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 | |||||
| @@ -0,0 +1,225 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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 <CoreAudio/AudioHardware.h> | |||||
| #import <CoreMIDI/MIDIServices.h> | |||||
| #import <AudioToolbox/AudioServices.h> | |||||
| #undef Point | |||||
| #undef Component | |||||
| #elif JUCE_IOS | |||||
| #import <AudioToolbox/AudioToolbox.h> | |||||
| #import <AVFoundation/AVFoundation.h> | |||||
| #import <CoreMIDI/MIDIServices.h> | |||||
| #if TARGET_OS_SIMULATOR | |||||
| #import <CoreMIDI/MIDINetworkSession.h> | |||||
| #endif | |||||
| //============================================================================== | |||||
| #elif JUCE_WINDOWS | |||||
| #if JUCE_WASAPI | |||||
| #include <mmreg.h> | |||||
| #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 <windows.devices.h> | |||||
| #include <windows.devices.midi.h> | |||||
| #include <windows.devices.enumeration.h> | |||||
| #include <wrl/event.h> | |||||
| #if JUCE_MSVC | |||||
| #pragma warning (push) | |||||
| #pragma warning (disable: 4467) | |||||
| #endif | |||||
| #include <robuffer.h> | |||||
| #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 <iasiodrv.h> | |||||
| #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 <alsa/asoundlib.h> | |||||
| #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 <jack/jack.h> | |||||
| #endif | |||||
| #undef SIZEOF | |||||
| //============================================================================== | |||||
| #elif JUCE_ANDROID | |||||
| #if JUCE_USE_ANDROID_OPENSLES | |||||
| #include <SLES/OpenSLES.h> | |||||
| #include <SLES/OpenSLES_Android.h> | |||||
| #include <SLES/OpenSLES_AndroidConfiguration.h> | |||||
| #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 | |||||
| @@ -0,0 +1,155 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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 <juce_events/juce_events.h> | |||||
| #include <juce_audio_basics/juce_audio_basics.h> | |||||
| #if JUCE_MODULE_AVAILABLE_juce_graphics | |||||
| #include <juce_graphics/juce_graphics.h> | |||||
| #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 | |||||
| @@ -0,0 +1,176 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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 | |||||
| @@ -0,0 +1,158 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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 | |||||
| @@ -0,0 +1,103 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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 | |||||
| @@ -0,0 +1,176 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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<PendingMessage> 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 | |||||
| @@ -0,0 +1,143 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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 | |||||
| @@ -0,0 +1,191 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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 <typename UserDataType, typename CallbackType> | |||||
| void pushMidiData (const void* inputData, int numBytes, double time, | |||||
| UserDataType* input, CallbackType& callback) | |||||
| { | |||||
| const uint8* d = static_cast<const uint8*> (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 <typename UserDataType, typename CallbackType> | |||||
| 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<uint8*> (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 | |||||
| @@ -0,0 +1,497 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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, "<init>", "(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, "<init>", "(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<double> getAvailableSampleRates() override | |||||
| { | |||||
| Array<double> r; | |||||
| r.add ((double) sampleRate); | |||||
| return r; | |||||
| } | |||||
| Array<int> getAvailableBufferSizes() override | |||||
| { | |||||
| Array<int> 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<int> (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<int> (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 <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> d (inputChannelBuffer.getWritePointer (chan)); | |||||
| if (chan < numDeviceInputChannels) | |||||
| { | |||||
| AudioData::Pointer <AudioData::Int16, AudioData::NativeEndian, AudioData::Interleaved, AudioData::Const> 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 <AudioData::Int16, AudioData::NativeEndian, AudioData::Interleaved, AudioData::NonConst> d (dest + chan, numDeviceOutputChannels); | |||||
| const float* const sourceChanData = outputChannelBuffer.getReadPointer (jmin (chan, outputChannelBuffer.getNumChannels() - 1)); | |||||
| AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> 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<AndroidAudioIODevice> 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 | |||||
| @@ -0,0 +1,364 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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<uint8> buffer (static_cast<size_t> (len)); | |||||
| std::memcpy (buffer.get(), data + offset, static_cast<size_t> (len)); | |||||
| midiConcatenator.pushMidiData (buffer.get(), | |||||
| len, static_cast<double> (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<AndroidMidiInput*> (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<jstring> string ((jstring) getEnv()->CallObjectMethod (dm, MidiDeviceManager.getInputPortNameForJuceIndex, idx)); | |||||
| return juceString (string); | |||||
| } | |||||
| return {}; | |||||
| } | |||||
| String getOutputPortNameForJuceIndex (int idx) | |||||
| { | |||||
| if (jobject dm = deviceManager.get()) | |||||
| { | |||||
| LocalRef<jstring> 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<jobjectArray> devices (jDevices); | |||||
| return javaStringArrayToJuce (devices); | |||||
| } | |||||
| return {}; | |||||
| } | |||||
| AndroidMidiInput* openMidiInputPortWithIndex (int idx, MidiInput* juceMidiInput, juce::MidiInputCallback* callback) | |||||
| { | |||||
| if (jobject dm = deviceManager.get()) | |||||
| { | |||||
| ScopedPointer<AndroidMidiInput> 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<jstring> 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<AndroidMidiOutput*> (internal); | |||||
| } | |||||
| void MidiOutput::sendMessageNow (const MidiMessage& message) | |||||
| { | |||||
| if (AndroidMidiOutput* androidMidi = reinterpret_cast<AndroidMidiOutput*>(internal)) | |||||
| { | |||||
| JNIEnv* env = getEnv(); | |||||
| const int messageSize = message.getRawDataSize(); | |||||
| LocalRef<jbyteArray> messageContent = LocalRef<jbyteArray> (env->NewByteArray (messageSize)); | |||||
| jbyteArray content = messageContent.get(); | |||||
| jbyte* rawBytes = env->GetByteArrayElements (content, nullptr); | |||||
| std::memcpy (rawBytes, message.getRawData(), static_cast<size_t> (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> 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<AndroidMidiInput*> (internal)) | |||||
| mi->start(); | |||||
| } | |||||
| void MidiInput::stop() | |||||
| { | |||||
| if (AndroidMidiInput* mi = reinterpret_cast<AndroidMidiInput*> (internal)) | |||||
| mi->stop(); | |||||
| } | |||||
| MidiInput::~MidiInput() | |||||
| { | |||||
| delete reinterpret_cast<AndroidMidiInput*> (internal); | |||||
| } | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,93 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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<double> getAvailableSampleRates() override; | |||||
| Array<int> 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> pimpl; | |||||
| JUCE_DECLARE_NON_COPYABLE (iOSAudioIODevice) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,624 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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<JackAudioIODeviceType*> 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<double> getAvailableSampleRates() override | |||||
| { | |||||
| Array<double> rates; | |||||
| if (client != nullptr) | |||||
| rates.add (juce::jack_get_sample_rate (client)); | |||||
| return rates; | |||||
| } | |||||
| Array<int> getAvailableBufferSizes() override | |||||
| { | |||||
| Array<int> 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<const float**> (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<JackAudioIODevice*> (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<float*> inChans, outChans; | |||||
| int totalNumberOfInputChannels; | |||||
| int totalNumberOfOutputChannels; | |||||
| Array<void*> 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<JackAudioIODevice*> (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 | |||||
| @@ -0,0 +1,617 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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<AlsaClient> 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<Port> ports; | |||||
| Atomic<int> activeCallbacks; | |||||
| CriticalSection callbackLock; | |||||
| static AlsaClient* instance; | |||||
| //============================================================================== | |||||
| friend class ReferenceCountedObjectPtr<AlsaClient>; | |||||
| friend struct ContainerDeletePolicy<AlsaClient>; | |||||
| 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<pollfd> pfd ((size_t) numPfds); | |||||
| snd_seq_poll_descriptors (seqHandle, pfd, (unsigned int) numPfds, POLLIN); | |||||
| HeapBlock<uint8> 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<MidiInputThread> 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<AlsaClient::Port*> (internal)); | |||||
| } | |||||
| void MidiOutput::sendMessageNow (const MidiMessage& message) | |||||
| { | |||||
| static_cast<AlsaClient::Port*> (internal)->sendMessageNow (message); | |||||
| } | |||||
| //============================================================================== | |||||
| MidiInput::MidiInput (const String& nm) | |||||
| : name (nm), internal (nullptr) | |||||
| { | |||||
| } | |||||
| MidiInput::~MidiInput() | |||||
| { | |||||
| stop(); | |||||
| AlsaClient::Ptr client (AlsaClient::getInstance()); | |||||
| client->deletePort (static_cast<AlsaClient::Port*> (internal)); | |||||
| } | |||||
| void MidiInput::start() | |||||
| { | |||||
| static_cast<AlsaClient::Port*> (internal)->enableCallback (true); | |||||
| } | |||||
| void MidiInput::stop() | |||||
| { | |||||
| static_cast<AlsaClient::Port*> (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 | |||||
| @@ -0,0 +1,562 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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<const SInt32*> (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<MIDIEndpointRef> (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 = "<error>"; | |||||
| 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<MidiPortAndCallback*> 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<MidiPortAndEndpoint> portAndEndpoint; | |||||
| volatile bool active; | |||||
| private: | |||||
| MidiInputCallback& callback; | |||||
| MidiDataConcatenator concatenator; | |||||
| }; | |||||
| static void midiInputProc (const MIDIPacketList* pktlist, void* readProcRefCon, void* /*srcConnRefCon*/) | |||||
| { | |||||
| static_cast<MidiPortAndCallback*> (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<CoreMidiHelpers::MidiPortAndEndpoint*> (internal); | |||||
| } | |||||
| void MidiOutput::sendMessageNow (const MidiMessage& message) | |||||
| { | |||||
| #if JUCE_IOS | |||||
| const MIDITimeStamp timeStamp = mach_absolute_time(); | |||||
| #else | |||||
| const MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); | |||||
| #endif | |||||
| HeapBlock<MIDIPacketList> 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<CoreMidiHelpers::MidiPortAndEndpoint*> (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<MidiPortAndCallback> 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<MidiPortAndCallback> 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<CoreMidiHelpers::MidiPortAndCallback*> (internal); | |||||
| } | |||||
| void MidiInput::start() | |||||
| { | |||||
| const ScopedLock sl (CoreMidiHelpers::callbackLock); | |||||
| static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal)->active = true; | |||||
| } | |||||
| void MidiInput::stop() | |||||
| { | |||||
| const ScopedLock sl (CoreMidiHelpers::callbackLock); | |||||
| static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal)->active = false; | |||||
| } | |||||
| #undef CHECK_ERROR | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,185 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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 | |||||
| @@ -0,0 +1,111 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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 | |||||
| @@ -0,0 +1,286 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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<ResamplingAudioSource> oldResamplerSource (resamplerSource); | |||||
| ScopedPointer<BufferingAudioSource> 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 | |||||
| @@ -0,0 +1,176 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| 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 | |||||