git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce
subrepo: subdir: "deps/juce" merged: "b13f9084e" upstream: origin: "https://github.com/essej/JUCE.git" branch: "sono6good" commit: "b13f9084e" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596"
This commit is contained in:
524
deps/juce/modules/juce_audio_utils/native/juce_android_BluetoothMidiDevicePairingDialogue.cpp
vendored
Normal file
524
deps/juce/modules/juce_audio_utils/native/juce_android_BluetoothMidiDevicePairingDialogue.cpp
vendored
Normal file
@ -0,0 +1,524 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
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, CALLBACK) \
|
||||
STATICMETHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$BluetoothManager;")
|
||||
|
||||
DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidJuceMidiSupport, "com/rmsl/juce/JuceMidiSupport", 23)
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
|
||||
METHOD (getMidiBluetoothAddresses, "getMidiBluetoothAddresses", "()[Ljava/lang/String;") \
|
||||
METHOD (pairBluetoothMidiDevice, "pairBluetoothMidiDevice", "(Ljava/lang/String;)Z") \
|
||||
METHOD (unpairBluetoothMidiDevice, "unpairBluetoothMidiDevice", "(Ljava/lang/String;)V") \
|
||||
METHOD (getHumanReadableStringForBluetoothAddress, "getHumanReadableStringForBluetoothAddress", "(Ljava/lang/String;)Ljava/lang/String;") \
|
||||
METHOD (getBluetoothDeviceStatus, "getBluetoothDeviceStatus", "(Ljava/lang/String;)I") \
|
||||
METHOD (startStopScan, "startStopScan", "(Z)V")
|
||||
|
||||
DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidBluetoothManager, "com/rmsl/juce/JuceMidiSupport$BluetoothManager", 23)
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
||||
//==============================================================================
|
||||
struct AndroidBluetoothMidiInterface
|
||||
{
|
||||
static void startStopScan (bool startScanning)
|
||||
{
|
||||
JNIEnv* env = getEnv();
|
||||
LocalRef<jobject> btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get()));
|
||||
|
||||
if (btManager.get() != nullptr)
|
||||
env->CallVoidMethod (btManager.get(), AndroidBluetoothManager.startStopScan, (jboolean) (startScanning ? 1 : 0));
|
||||
}
|
||||
|
||||
static StringArray getBluetoothMidiDevicesNearby()
|
||||
{
|
||||
StringArray retval;
|
||||
|
||||
JNIEnv* env = getEnv();
|
||||
|
||||
LocalRef<jobject> btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get()));
|
||||
|
||||
// if this is null then bluetooth is not enabled
|
||||
if (btManager.get() == nullptr)
|
||||
return {};
|
||||
|
||||
jobjectArray jDevices = (jobjectArray) env->CallObjectMethod (btManager.get(),
|
||||
AndroidBluetoothManager.getMidiBluetoothAddresses);
|
||||
LocalRef<jobjectArray> devices (jDevices);
|
||||
|
||||
const int count = env->GetArrayLength (devices.get());
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
LocalRef<jstring> string ((jstring) env->GetObjectArrayElement (devices.get(), i));
|
||||
retval.add (juceString (string));
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static bool pairBluetoothMidiDevice (const String& bluetoothAddress)
|
||||
{
|
||||
JNIEnv* env = getEnv();
|
||||
|
||||
LocalRef<jobject> btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get()));
|
||||
if (btManager.get() == nullptr)
|
||||
return false;
|
||||
|
||||
jboolean result = env->CallBooleanMethod (btManager.get(), AndroidBluetoothManager.pairBluetoothMidiDevice,
|
||||
javaString (bluetoothAddress).get());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void unpairBluetoothMidiDevice (const String& bluetoothAddress)
|
||||
{
|
||||
JNIEnv* env = getEnv();
|
||||
|
||||
LocalRef<jobject> btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get()));
|
||||
|
||||
if (btManager.get() != nullptr)
|
||||
env->CallVoidMethod (btManager.get(), AndroidBluetoothManager.unpairBluetoothMidiDevice,
|
||||
javaString (bluetoothAddress).get());
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static String getHumanReadableStringForBluetoothAddress (const String& address)
|
||||
{
|
||||
JNIEnv* env = getEnv();
|
||||
|
||||
LocalRef<jobject> btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get()));
|
||||
|
||||
if (btManager.get() == nullptr)
|
||||
return address;
|
||||
|
||||
LocalRef<jstring> string ((jstring) env->CallObjectMethod (btManager.get(),
|
||||
AndroidBluetoothManager.getHumanReadableStringForBluetoothAddress,
|
||||
javaString (address).get()));
|
||||
|
||||
|
||||
if (string.get() == nullptr)
|
||||
return address;
|
||||
|
||||
return juceString (string);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
enum PairStatus
|
||||
{
|
||||
unpaired = 0,
|
||||
paired = 1,
|
||||
pairing = 2
|
||||
};
|
||||
|
||||
static PairStatus isBluetoothDevicePaired (const String& address)
|
||||
{
|
||||
JNIEnv* env = getEnv();
|
||||
|
||||
LocalRef<jobject> btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get()));
|
||||
|
||||
if (btManager.get() == nullptr)
|
||||
return unpaired;
|
||||
|
||||
return static_cast<PairStatus> (env->CallIntMethod (btManager.get(), AndroidBluetoothManager.getBluetoothDeviceStatus,
|
||||
javaString (address).get()));
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct AndroidBluetoothMidiDevice
|
||||
{
|
||||
enum ConnectionStatus
|
||||
{
|
||||
offline,
|
||||
connected,
|
||||
disconnected,
|
||||
connecting,
|
||||
disconnecting
|
||||
};
|
||||
|
||||
AndroidBluetoothMidiDevice (String deviceName, String address, ConnectionStatus status)
|
||||
: name (deviceName), bluetoothAddress (address), connectionStatus (status)
|
||||
{
|
||||
// can't create a device without a valid name and bluetooth address!
|
||||
jassert (! name.isEmpty());
|
||||
jassert (! bluetoothAddress.isEmpty());
|
||||
}
|
||||
|
||||
bool operator== (const AndroidBluetoothMidiDevice& other) const noexcept
|
||||
{
|
||||
return bluetoothAddress == other.bluetoothAddress;
|
||||
}
|
||||
|
||||
bool operator!= (const AndroidBluetoothMidiDevice& other) const noexcept
|
||||
{
|
||||
return ! operator== (other);
|
||||
}
|
||||
|
||||
const String name, bluetoothAddress;
|
||||
ConnectionStatus connectionStatus;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class AndroidBluetoothMidiDevicesListBox : public ListBox,
|
||||
private ListBoxModel,
|
||||
private Timer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
AndroidBluetoothMidiDevicesListBox()
|
||||
: timerPeriodInMs (1000)
|
||||
{
|
||||
setRowHeight (40);
|
||||
setModel (this);
|
||||
setOutlineThickness (1);
|
||||
startTimer (timerPeriodInMs);
|
||||
}
|
||||
|
||||
void pairDeviceThreadFinished() // callback from PairDeviceThread
|
||||
{
|
||||
updateDeviceList();
|
||||
startTimer (timerPeriodInMs);
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
typedef AndroidBluetoothMidiDevice::ConnectionStatus DeviceStatus;
|
||||
|
||||
int getNumRows() override
|
||||
{
|
||||
return devices.size();
|
||||
}
|
||||
|
||||
void paintListBoxItem (int rowNumber, Graphics& g,
|
||||
int width, int height, bool) override
|
||||
{
|
||||
if (isPositiveAndBelow (rowNumber, devices.size()))
|
||||
{
|
||||
const AndroidBluetoothMidiDevice& device = devices.getReference (rowNumber);
|
||||
const String statusString (getDeviceStatusString (device.connectionStatus));
|
||||
|
||||
g.fillAll (Colours::white);
|
||||
|
||||
const float xmargin = 3.0f;
|
||||
const float ymargin = 3.0f;
|
||||
const float fontHeight = 0.4f * (float) height;
|
||||
const float deviceNameWidth = 0.6f * (float) width;
|
||||
|
||||
g.setFont (fontHeight);
|
||||
|
||||
g.setColour (getDeviceNameFontColour (device.connectionStatus));
|
||||
g.drawText (device.name,
|
||||
Rectangle<float> (xmargin, ymargin, deviceNameWidth - (2.0f * xmargin), (float) height - (2.0f * ymargin)),
|
||||
Justification::topLeft, true);
|
||||
|
||||
g.setColour (getDeviceStatusFontColour (device.connectionStatus));
|
||||
g.drawText (statusString,
|
||||
Rectangle<float> (deviceNameWidth + xmargin, ymargin,
|
||||
(float) width - deviceNameWidth - (2.0f * xmargin), (float) height - (2.0f * ymargin)),
|
||||
Justification::topRight, true);
|
||||
|
||||
g.setColour (Colours::grey);
|
||||
g.drawHorizontalLine (height - 1, xmargin, (float) width);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
static Colour getDeviceNameFontColour (DeviceStatus deviceStatus) noexcept
|
||||
{
|
||||
if (deviceStatus == AndroidBluetoothMidiDevice::offline)
|
||||
return Colours::grey;
|
||||
|
||||
return Colours::black;
|
||||
}
|
||||
|
||||
static Colour getDeviceStatusFontColour (DeviceStatus deviceStatus) noexcept
|
||||
{
|
||||
if (deviceStatus == AndroidBluetoothMidiDevice::offline
|
||||
|| deviceStatus == AndroidBluetoothMidiDevice::connecting
|
||||
|| deviceStatus == AndroidBluetoothMidiDevice::disconnecting)
|
||||
return Colours::grey;
|
||||
|
||||
if (deviceStatus == AndroidBluetoothMidiDevice::connected)
|
||||
return Colours::green;
|
||||
|
||||
return Colours::black;
|
||||
}
|
||||
|
||||
static String getDeviceStatusString (DeviceStatus deviceStatus) noexcept
|
||||
{
|
||||
if (deviceStatus == AndroidBluetoothMidiDevice::offline) return "Offline";
|
||||
if (deviceStatus == AndroidBluetoothMidiDevice::connected) return "Connected";
|
||||
if (deviceStatus == AndroidBluetoothMidiDevice::disconnected) return "Not connected";
|
||||
if (deviceStatus == AndroidBluetoothMidiDevice::connecting) return "Connecting...";
|
||||
if (deviceStatus == AndroidBluetoothMidiDevice::disconnecting) return "Disconnecting...";
|
||||
|
||||
// unknown device state!
|
||||
jassertfalse;
|
||||
return "Status unknown";
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void listBoxItemClicked (int row, const MouseEvent&) override
|
||||
{
|
||||
const AndroidBluetoothMidiDevice& device = devices.getReference (row);
|
||||
|
||||
if (device.connectionStatus == AndroidBluetoothMidiDevice::disconnected)
|
||||
disconnectedDeviceClicked (row);
|
||||
|
||||
else if (device.connectionStatus == AndroidBluetoothMidiDevice::connected)
|
||||
connectedDeviceClicked (row);
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
updateDeviceList();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct PairDeviceThread : public Thread,
|
||||
private AsyncUpdater
|
||||
{
|
||||
PairDeviceThread (const String& bluetoothAddressOfDeviceToPair,
|
||||
AndroidBluetoothMidiDevicesListBox& ownerListBox)
|
||||
: Thread ("JUCE Bluetooth MIDI Device Pairing Thread"),
|
||||
bluetoothAddress (bluetoothAddressOfDeviceToPair),
|
||||
owner (&ownerListBox)
|
||||
{
|
||||
startThread();
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
AndroidBluetoothMidiInterface::pairBluetoothMidiDevice (bluetoothAddress);
|
||||
triggerAsyncUpdate();
|
||||
}
|
||||
|
||||
void handleAsyncUpdate() override
|
||||
{
|
||||
if (owner != nullptr)
|
||||
owner->pairDeviceThreadFinished();
|
||||
|
||||
delete this;
|
||||
}
|
||||
|
||||
private:
|
||||
String bluetoothAddress;
|
||||
Component::SafePointer<AndroidBluetoothMidiDevicesListBox> owner;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
void disconnectedDeviceClicked (int row)
|
||||
{
|
||||
stopTimer();
|
||||
|
||||
AndroidBluetoothMidiDevice& device = devices.getReference (row);
|
||||
device.connectionStatus = AndroidBluetoothMidiDevice::connecting;
|
||||
updateContent();
|
||||
repaint();
|
||||
|
||||
new PairDeviceThread (device.bluetoothAddress, *this);
|
||||
}
|
||||
|
||||
void connectedDeviceClicked (int row)
|
||||
{
|
||||
AndroidBluetoothMidiDevice& device = devices.getReference (row);
|
||||
device.connectionStatus = AndroidBluetoothMidiDevice::disconnecting;
|
||||
updateContent();
|
||||
repaint();
|
||||
AndroidBluetoothMidiInterface::unpairBluetoothMidiDevice (device.bluetoothAddress);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void updateDeviceList()
|
||||
{
|
||||
StringArray bluetoothAddresses = AndroidBluetoothMidiInterface::getBluetoothMidiDevicesNearby();
|
||||
|
||||
Array<AndroidBluetoothMidiDevice> newDevices;
|
||||
|
||||
for (String* address = bluetoothAddresses.begin();
|
||||
address != bluetoothAddresses.end(); ++address)
|
||||
{
|
||||
String name = AndroidBluetoothMidiInterface::getHumanReadableStringForBluetoothAddress (*address);
|
||||
|
||||
DeviceStatus status;
|
||||
switch (AndroidBluetoothMidiInterface::isBluetoothDevicePaired (*address))
|
||||
{
|
||||
case AndroidBluetoothMidiInterface::pairing:
|
||||
status = AndroidBluetoothMidiDevice::connecting;
|
||||
break;
|
||||
case AndroidBluetoothMidiInterface::paired:
|
||||
status = AndroidBluetoothMidiDevice::connected;
|
||||
break;
|
||||
case AndroidBluetoothMidiInterface::unpaired:
|
||||
default:
|
||||
status = AndroidBluetoothMidiDevice::disconnected;
|
||||
}
|
||||
|
||||
newDevices.add (AndroidBluetoothMidiDevice (name, *address, status));
|
||||
}
|
||||
|
||||
devices.swapWith (newDevices);
|
||||
updateContent();
|
||||
repaint();
|
||||
}
|
||||
|
||||
Array<AndroidBluetoothMidiDevice> devices;
|
||||
const int timerPeriodInMs;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class BluetoothMidiSelectorOverlay : public Component
|
||||
{
|
||||
public:
|
||||
BluetoothMidiSelectorOverlay (ModalComponentManager::Callback* exitCallbackToUse,
|
||||
const Rectangle<int>& boundsToUse)
|
||||
: bounds (boundsToUse)
|
||||
{
|
||||
std::unique_ptr<ModalComponentManager::Callback> exitCallback (exitCallbackToUse);
|
||||
|
||||
AndroidBluetoothMidiInterface::startStopScan (true);
|
||||
|
||||
setAlwaysOnTop (true);
|
||||
setVisible (true);
|
||||
addToDesktop (ComponentPeer::windowHasDropShadow);
|
||||
|
||||
if (bounds.isEmpty())
|
||||
setBounds (0, 0, getParentWidth(), getParentHeight());
|
||||
else
|
||||
setBounds (bounds);
|
||||
|
||||
toFront (true);
|
||||
setOpaque (! bounds.isEmpty());
|
||||
|
||||
addAndMakeVisible (bluetoothDevicesList);
|
||||
enterModalState (true, exitCallback.release(), true);
|
||||
}
|
||||
|
||||
~BluetoothMidiSelectorOverlay() override
|
||||
{
|
||||
AndroidBluetoothMidiInterface::startStopScan (false);
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (bounds.isEmpty() ? Colours::black.withAlpha (0.6f) : Colours::black);
|
||||
|
||||
g.setColour (Colour (0xffdfdfdf));
|
||||
Rectangle<int> overlayBounds = getOverlayBounds();
|
||||
g.fillRect (overlayBounds);
|
||||
|
||||
g.setColour (Colours::black);
|
||||
g.setFont (16);
|
||||
g.drawText ("Bluetooth MIDI Devices",
|
||||
overlayBounds.removeFromTop (20).reduced (3, 3),
|
||||
Justification::topLeft, true);
|
||||
|
||||
overlayBounds.removeFromTop (2);
|
||||
|
||||
g.setFont (12);
|
||||
g.drawText ("tap to connect/disconnect",
|
||||
overlayBounds.removeFromTop (18).reduced (3, 3),
|
||||
Justification::topLeft, true);
|
||||
}
|
||||
|
||||
void inputAttemptWhenModal() override { exitModalState (0); }
|
||||
void mouseDrag (const MouseEvent&) override {}
|
||||
void mouseDown (const MouseEvent&) override { exitModalState (0); }
|
||||
void resized() override { update(); }
|
||||
void parentSizeChanged() override { update(); }
|
||||
|
||||
private:
|
||||
Rectangle<int> bounds;
|
||||
|
||||
void update()
|
||||
{
|
||||
if (bounds.isEmpty())
|
||||
setBounds (0, 0, getParentWidth(), getParentHeight());
|
||||
else
|
||||
setBounds (bounds);
|
||||
|
||||
bluetoothDevicesList.setBounds (getOverlayBounds().withTrimmedTop (40));
|
||||
}
|
||||
|
||||
Rectangle<int> getOverlayBounds() const noexcept
|
||||
{
|
||||
if (bounds.isEmpty())
|
||||
{
|
||||
const int pw = getParentWidth();
|
||||
const int ph = getParentHeight();
|
||||
|
||||
return Rectangle<int> (pw, ph).withSizeKeepingCentre (jmin (400, pw - 14),
|
||||
jmin (300, ph - 40));
|
||||
}
|
||||
|
||||
return bounds.withZeroOrigin();
|
||||
}
|
||||
|
||||
AndroidBluetoothMidiDevicesListBox bluetoothDevicesList;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BluetoothMidiSelectorOverlay)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* exitCallbackPtr,
|
||||
Rectangle<int>* btBounds)
|
||||
{
|
||||
std::unique_ptr<ModalComponentManager::Callback> exitCallback (exitCallbackPtr);
|
||||
|
||||
if (getAndroidSDKVersion() < 23)
|
||||
return false;
|
||||
|
||||
auto boundsToUse = (btBounds != nullptr ? *btBounds : Rectangle<int> {});
|
||||
|
||||
if (! RuntimePermissions::isGranted (RuntimePermissions::bluetoothMidi))
|
||||
{
|
||||
// If you hit this assert, you probably forgot to get RuntimePermissions::bluetoothMidi.
|
||||
// This is not going to work, boo! The pairing dialogue won't be able to scan for or
|
||||
// find any devices, it will just display an empty list, so don't bother opening it.
|
||||
jassertfalse;
|
||||
return false;
|
||||
}
|
||||
|
||||
new BluetoothMidiSelectorOverlay (exitCallback.release(), boundsToUse);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BluetoothMidiDevicePairingDialogue::isAvailable()
|
||||
{
|
||||
if (getAndroidSDKVersion() < 23)
|
||||
return false;
|
||||
|
||||
auto* env = getEnv();
|
||||
|
||||
LocalRef<jobject> btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get()));
|
||||
return btManager != nullptr;
|
||||
}
|
||||
|
||||
} // namespace juce
|
148
deps/juce/modules/juce_audio_utils/native/juce_ios_BluetoothMidiDevicePairingDialogue.mm
vendored
Normal file
148
deps/juce/modules/juce_audio_utils/native/juce_ios_BluetoothMidiDevicePairingDialogue.mm
vendored
Normal file
@ -0,0 +1,148 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#if ! TARGET_IPHONE_SIMULATOR
|
||||
|
||||
#include <CoreAudioKit/CoreAudioKit.h>
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
class BluetoothMidiSelectorOverlay : public Component
|
||||
{
|
||||
public:
|
||||
BluetoothMidiSelectorOverlay (ModalComponentManager::Callback* exitCallbackToUse,
|
||||
const Rectangle<int>& boundsToUse)
|
||||
: bounds (boundsToUse)
|
||||
{
|
||||
std::unique_ptr<ModalComponentManager::Callback> exitCallback (exitCallbackToUse);
|
||||
|
||||
setAlwaysOnTop (true);
|
||||
setVisible (true);
|
||||
addToDesktop (ComponentPeer::windowHasDropShadow);
|
||||
|
||||
if (bounds.isEmpty())
|
||||
setBounds (0, 0, getParentWidth(), getParentHeight());
|
||||
else
|
||||
setBounds (bounds);
|
||||
|
||||
toFront (true);
|
||||
setOpaque (true);
|
||||
|
||||
controller = [[CABTMIDICentralViewController alloc] init];
|
||||
nativeSelectorComponent.setView ([controller view]);
|
||||
|
||||
addAndMakeVisible (nativeSelectorComponent);
|
||||
|
||||
enterModalState (true, exitCallback.release(), true);
|
||||
}
|
||||
|
||||
~BluetoothMidiSelectorOverlay() override
|
||||
{
|
||||
nativeSelectorComponent.setView (nullptr);
|
||||
[controller release];
|
||||
}
|
||||
|
||||
void paint (Graphics& g) override
|
||||
{
|
||||
g.fillAll (bounds.isEmpty() ? Colours::black.withAlpha (0.5f) : Colours::black);
|
||||
}
|
||||
|
||||
void inputAttemptWhenModal() override { close(); }
|
||||
void mouseDrag (const MouseEvent&) override {}
|
||||
void mouseDown (const MouseEvent&) override { close(); }
|
||||
void resized() override { update(); }
|
||||
void parentSizeChanged() override { update(); }
|
||||
|
||||
private:
|
||||
void update()
|
||||
{
|
||||
if (bounds.isEmpty())
|
||||
{
|
||||
const int pw = getParentWidth();
|
||||
const int ph = getParentHeight();
|
||||
|
||||
nativeSelectorComponent.setBounds (Rectangle<int> (pw, ph)
|
||||
.withSizeKeepingCentre (jmin (400, pw),
|
||||
jmin (450, ph - 40)));
|
||||
}
|
||||
else
|
||||
{
|
||||
nativeSelectorComponent.setBounds (bounds.withZeroOrigin());
|
||||
}
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
exitModalState (0);
|
||||
setVisible (false);
|
||||
}
|
||||
|
||||
CABTMIDICentralViewController* controller;
|
||||
UIViewComponent nativeSelectorComponent;
|
||||
Rectangle<int> bounds;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BluetoothMidiSelectorOverlay)
|
||||
};
|
||||
|
||||
bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* exitCallback,
|
||||
Rectangle<int>* btBounds)
|
||||
{
|
||||
std::unique_ptr<ModalComponentManager::Callback> cb (exitCallback);
|
||||
auto boundsToUse = (btBounds != nullptr ? *btBounds : Rectangle<int> {});
|
||||
|
||||
if (isAvailable())
|
||||
{
|
||||
new BluetoothMidiSelectorOverlay (cb.release(), boundsToUse);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BluetoothMidiDevicePairingDialogue::isAvailable()
|
||||
{
|
||||
return NSClassFromString (@"CABTMIDICentralViewController") != nil;
|
||||
}
|
||||
|
||||
} // namespace juce
|
||||
|
||||
//==============================================================================
|
||||
#else
|
||||
|
||||
namespace juce
|
||||
{
|
||||
bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* exitCallback,
|
||||
Rectangle<int>*)
|
||||
{
|
||||
std::unique_ptr<ModalComponentManager::Callback> cb (exitCallback);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BluetoothMidiDevicePairingDialogue::isAvailable() { return false; }
|
||||
}
|
||||
|
||||
#endif
|
83
deps/juce/modules/juce_audio_utils/native/juce_linux_AudioCDReader.cpp
vendored
Normal file
83
deps/juce/modules/juce_audio_utils/native/juce_linux_AudioCDReader.cpp
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
AudioCDReader::AudioCDReader()
|
||||
: AudioFormatReader (0, "CD Audio")
|
||||
{
|
||||
}
|
||||
|
||||
StringArray AudioCDReader::getAvailableCDNames()
|
||||
{
|
||||
StringArray names;
|
||||
return names;
|
||||
}
|
||||
|
||||
AudioCDReader* AudioCDReader::createReaderForCD (const int)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AudioCDReader::~AudioCDReader()
|
||||
{
|
||||
}
|
||||
|
||||
void AudioCDReader::refreshTrackLengths()
|
||||
{
|
||||
}
|
||||
|
||||
bool AudioCDReader::readSamples (int**, int, int,
|
||||
int64, int)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioCDReader::isCDStillPresent() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AudioCDReader::isTrackAudio (int) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void AudioCDReader::enableIndexScanning (bool)
|
||||
{
|
||||
}
|
||||
|
||||
int AudioCDReader::getLastIndex() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
Array<int> AudioCDReader::findIndexesInTrack (const int)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace juce
|
45
deps/juce/modules/juce_audio_utils/native/juce_linux_BluetoothMidiDevicePairingDialogue.cpp
vendored
Normal file
45
deps/juce/modules/juce_audio_utils/native/juce_linux_BluetoothMidiDevicePairingDialogue.cpp
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* exitCallback,
|
||||
Rectangle<int>*)
|
||||
{
|
||||
std::unique_ptr<ModalComponentManager::Callback> cb (exitCallback);
|
||||
// not implemented on Linux yet!
|
||||
// You should check whether the dialogue is available on your system
|
||||
// using isAvailable() before calling open().
|
||||
jassertfalse;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BluetoothMidiDevicePairingDialogue::isAvailable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace juce
|
466
deps/juce/modules/juce_audio_utils/native/juce_mac_AudioCDBurner.mm
vendored
Normal file
466
deps/juce/modules/juce_audio_utils/native/juce_mac_AudioCDBurner.mm
vendored
Normal file
@ -0,0 +1,466 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
const int kilobytesPerSecond1x = 176;
|
||||
|
||||
struct AudioTrackProducerClass : public ObjCClass<NSObject>
|
||||
{
|
||||
AudioTrackProducerClass() : ObjCClass<NSObject> ("JUCEAudioTrackProducer_")
|
||||
{
|
||||
addIvar<AudioSourceHolder*> ("source");
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
addMethod (@selector (initWithAudioSourceHolder:), initWithAudioSourceHolder, "@@:^v");
|
||||
addMethod (@selector (verifyDataForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:),
|
||||
produceDataForTrack, "I@:@^cIQI^I");
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
addMethod (@selector (cleanupTrackAfterBurn:), cleanupTrackAfterBurn, "v@:@");
|
||||
addMethod (@selector (cleanupTrackAfterVerification:), cleanupTrackAfterVerification, "c@:@");
|
||||
addMethod (@selector (estimateLengthOfTrack:), estimateLengthOfTrack, "Q@:@");
|
||||
addMethod (@selector (prepareTrack:forBurn:toMedia:), prepareTrack, "c@:@@@");
|
||||
addMethod (@selector (prepareTrackForVerification:), prepareTrackForVerification, "c@:@");
|
||||
addMethod (@selector (produceDataForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:),
|
||||
produceDataForTrack, "I@:@^cIQI^I");
|
||||
addMethod (@selector (producePreGapForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:),
|
||||
produceDataForTrack, "I@:@^cIQI^I");
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
struct AudioSourceHolder
|
||||
{
|
||||
AudioSourceHolder (AudioSource* s, int numFrames)
|
||||
: source (s), readPosition (0), lengthInFrames (numFrames)
|
||||
{
|
||||
}
|
||||
|
||||
~AudioSourceHolder()
|
||||
{
|
||||
if (source != nullptr)
|
||||
source->releaseResources();
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioSource> source;
|
||||
int readPosition, lengthInFrames;
|
||||
};
|
||||
|
||||
private:
|
||||
static id initWithAudioSourceHolder (id self, SEL, AudioSourceHolder* source)
|
||||
{
|
||||
self = sendSuperclassMessage<id> (self, @selector (init));
|
||||
object_setInstanceVariable (self, "source", source);
|
||||
return self;
|
||||
}
|
||||
|
||||
static AudioSourceHolder* getSource (id self)
|
||||
{
|
||||
return getIvar<AudioSourceHolder*> (self, "source");
|
||||
}
|
||||
|
||||
static void dealloc (id self, SEL)
|
||||
{
|
||||
delete getSource (self);
|
||||
sendSuperclassMessage<void> (self, @selector (dealloc));
|
||||
}
|
||||
|
||||
static void cleanupTrackAfterBurn (id, SEL, DRTrack*) {}
|
||||
static BOOL cleanupTrackAfterVerification (id, SEL, DRTrack*) { return true; }
|
||||
|
||||
static uint64_t estimateLengthOfTrack (id self, SEL, DRTrack*)
|
||||
{
|
||||
return static_cast<uint64_t> (getSource (self)->lengthInFrames);
|
||||
}
|
||||
|
||||
static BOOL prepareTrack (id self, SEL, DRTrack*, DRBurn*, NSDictionary*)
|
||||
{
|
||||
if (AudioSourceHolder* const source = getSource (self))
|
||||
{
|
||||
source->source->prepareToPlay (44100 / 75, 44100);
|
||||
source->readPosition = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static BOOL prepareTrackForVerification (id self, SEL, DRTrack*)
|
||||
{
|
||||
if (AudioSourceHolder* const source = getSource (self))
|
||||
source->source->prepareToPlay (44100 / 75, 44100);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint32_t produceDataForTrack (id self, SEL, DRTrack*, char* buffer,
|
||||
uint32_t bufferLength, uint64_t /*address*/,
|
||||
uint32_t /*blockSize*/, uint32_t* /*flags*/)
|
||||
{
|
||||
if (AudioSourceHolder* const source = getSource (self))
|
||||
{
|
||||
const int numSamples = jmin ((int) bufferLength / 4,
|
||||
(source->lengthInFrames * (44100 / 75)) - source->readPosition);
|
||||
|
||||
if (numSamples > 0)
|
||||
{
|
||||
AudioBuffer<float> tempBuffer (2, numSamples);
|
||||
AudioSourceChannelInfo info (tempBuffer);
|
||||
|
||||
source->source->getNextAudioBlock (info);
|
||||
|
||||
typedef AudioData::Pointer <AudioData::Int16, AudioData::LittleEndian, AudioData::Interleaved, AudioData::NonConst> CDSampleFormat;
|
||||
typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> SourceSampleFormat;
|
||||
|
||||
CDSampleFormat left (buffer, 2);
|
||||
left.convertSamples (SourceSampleFormat (tempBuffer.getReadPointer (0)), numSamples);
|
||||
CDSampleFormat right (buffer + 2, 2);
|
||||
right.convertSamples (SourceSampleFormat (tempBuffer.getReadPointer (1)), numSamples);
|
||||
|
||||
source->readPosition += numSamples;
|
||||
}
|
||||
|
||||
return static_cast<uint32_t> (numSamples * 4);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t producePreGapForTrack (id, SEL, DRTrack*, char* buffer,
|
||||
uint32_t bufferLength, uint64_t /*address*/,
|
||||
uint32_t /*blockSize*/, uint32_t* /*flags*/)
|
||||
{
|
||||
zeromem (buffer, bufferLength);
|
||||
return bufferLength;
|
||||
}
|
||||
|
||||
static BOOL verifyDataForTrack (id, SEL, DRTrack*, const char*,
|
||||
uint32_t /*bufferLength*/, uint64_t /*address*/,
|
||||
uint32_t /*blockSize*/, uint32_t* /*flags*/)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct OpenDiskDevice
|
||||
{
|
||||
OpenDiskDevice (DRDevice* d)
|
||||
: device (d),
|
||||
tracks ([[NSMutableArray alloc] init]),
|
||||
underrunProtection (true)
|
||||
{
|
||||
}
|
||||
|
||||
~OpenDiskDevice()
|
||||
{
|
||||
[tracks release];
|
||||
}
|
||||
|
||||
void addSourceTrack (AudioSource* source, int numSamples)
|
||||
{
|
||||
if (source != nullptr)
|
||||
{
|
||||
const int numFrames = (numSamples + 587) / 588;
|
||||
|
||||
static AudioTrackProducerClass cls;
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
NSObject* producer = [cls.createInstance() performSelector: @selector (initWithAudioSourceHolder:)
|
||||
withObject: (id) new AudioTrackProducerClass::AudioSourceHolder (source, numFrames)];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
DRTrack* track = [[DRTrack alloc] initWithProducer: producer];
|
||||
|
||||
{
|
||||
NSMutableDictionary* p = [[track properties] mutableCopy];
|
||||
[p setObject: [DRMSF msfWithFrames: static_cast<UInt32> (numFrames)] forKey: DRTrackLengthKey];
|
||||
[p setObject: [NSNumber numberWithUnsignedShort: 2352] forKey: DRBlockSizeKey];
|
||||
[p setObject: [NSNumber numberWithInt: 0] forKey: DRDataFormKey];
|
||||
[p setObject: [NSNumber numberWithInt: 0] forKey: DRBlockTypeKey];
|
||||
[p setObject: [NSNumber numberWithInt: 0] forKey: DRTrackModeKey];
|
||||
[p setObject: [NSNumber numberWithInt: 0] forKey: DRSessionFormatKey];
|
||||
[track setProperties: p];
|
||||
[p release];
|
||||
}
|
||||
|
||||
[tracks addObject: track];
|
||||
|
||||
[track release];
|
||||
[producer release];
|
||||
}
|
||||
}
|
||||
|
||||
String burn (AudioCDBurner::BurnProgressListener* listener,
|
||||
bool shouldEject, bool peformFakeBurnForTesting, int burnSpeed)
|
||||
{
|
||||
DRBurn* burn = [DRBurn burnForDevice: device];
|
||||
|
||||
if (! [device acquireExclusiveAccess])
|
||||
return "Couldn't open or write to the CD device";
|
||||
|
||||
[device acquireMediaReservation];
|
||||
|
||||
NSMutableDictionary* d = [[burn properties] mutableCopy];
|
||||
[d autorelease];
|
||||
[d setObject: [NSNumber numberWithBool: peformFakeBurnForTesting] forKey: DRBurnTestingKey];
|
||||
[d setObject: [NSNumber numberWithBool: false] forKey: DRBurnVerifyDiscKey];
|
||||
[d setObject: (shouldEject ? DRBurnCompletionActionEject : DRBurnCompletionActionMount) forKey: DRBurnCompletionActionKey];
|
||||
|
||||
if (burnSpeed > 0)
|
||||
[d setObject: [NSNumber numberWithFloat: burnSpeed * kilobytesPerSecond1x] forKey: DRBurnRequestedSpeedKey];
|
||||
|
||||
if (! underrunProtection)
|
||||
[d setObject: [NSNumber numberWithBool: false] forKey: DRBurnUnderrunProtectionKey];
|
||||
|
||||
[burn setProperties: d];
|
||||
|
||||
[burn writeLayout: tracks];
|
||||
|
||||
for (;;)
|
||||
{
|
||||
Thread::sleep (300);
|
||||
float progress = [[[burn status] objectForKey: DRStatusPercentCompleteKey] floatValue];
|
||||
|
||||
if (listener != nullptr && listener->audioCDBurnProgress (progress))
|
||||
{
|
||||
[burn abort];
|
||||
return "User cancelled the write operation";
|
||||
}
|
||||
|
||||
if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateFailed])
|
||||
return "Write operation failed";
|
||||
|
||||
if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateDone])
|
||||
break;
|
||||
|
||||
NSString* err = (NSString*) [[[burn status] objectForKey: DRErrorStatusKey]
|
||||
objectForKey: DRErrorStatusErrorStringKey];
|
||||
if ([err length] > 0)
|
||||
return nsStringToJuce (err);
|
||||
}
|
||||
|
||||
[device releaseMediaReservation];
|
||||
[device releaseExclusiveAccess];
|
||||
return {};
|
||||
}
|
||||
|
||||
DRDevice* device;
|
||||
NSMutableArray* tracks;
|
||||
bool underrunProtection;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class AudioCDBurner::Pimpl : private Timer
|
||||
{
|
||||
public:
|
||||
Pimpl (AudioCDBurner& b, int deviceIndex) : owner (b)
|
||||
{
|
||||
if (DRDevice* dev = [[DRDevice devices] objectAtIndex: static_cast<NSUInteger> (deviceIndex)])
|
||||
{
|
||||
device.reset (new OpenDiskDevice (dev));
|
||||
lastState = getDiskState();
|
||||
startTimer (1000);
|
||||
}
|
||||
}
|
||||
|
||||
~Pimpl() override
|
||||
{
|
||||
stopTimer();
|
||||
}
|
||||
|
||||
DiskState getDiskState() const
|
||||
{
|
||||
if ([device->device isValid])
|
||||
{
|
||||
NSDictionary* status = [device->device status];
|
||||
NSString* state = [status objectForKey: DRDeviceMediaStateKey];
|
||||
|
||||
if ([state isEqualTo: DRDeviceMediaStateNone])
|
||||
{
|
||||
if ([[status objectForKey: DRDeviceIsTrayOpenKey] boolValue])
|
||||
return trayOpen;
|
||||
|
||||
return noDisc;
|
||||
}
|
||||
|
||||
if ([state isEqualTo: DRDeviceMediaStateMediaPresent])
|
||||
{
|
||||
if ([[[status objectForKey: DRDeviceMediaInfoKey] objectForKey: DRDeviceMediaBlocksFreeKey] intValue] > 0)
|
||||
return writableDiskPresent;
|
||||
|
||||
return readOnlyDiskPresent;
|
||||
}
|
||||
}
|
||||
|
||||
return unknown;
|
||||
}
|
||||
|
||||
bool openTray() { return [device->device isValid] && [device->device ejectMedia]; }
|
||||
|
||||
Array<int> getAvailableWriteSpeeds() const
|
||||
{
|
||||
Array<int> results;
|
||||
|
||||
if ([device->device isValid])
|
||||
for (id kbPerSec in [[[device->device status] objectForKey: DRDeviceMediaInfoKey] objectForKey: DRDeviceBurnSpeedsKey])
|
||||
results.add ([kbPerSec intValue] / kilobytesPerSecond1x);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
bool setBufferUnderrunProtection (const bool shouldBeEnabled)
|
||||
{
|
||||
if ([device->device isValid])
|
||||
{
|
||||
device->underrunProtection = shouldBeEnabled;
|
||||
return shouldBeEnabled && [[[device->device status] objectForKey: DRDeviceCanUnderrunProtectCDKey] boolValue];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int getNumAvailableAudioBlocks() const
|
||||
{
|
||||
return [[[[device->device status] objectForKey: DRDeviceMediaInfoKey]
|
||||
objectForKey: DRDeviceMediaBlocksFreeKey] intValue];
|
||||
}
|
||||
|
||||
std::unique_ptr<OpenDiskDevice> device;
|
||||
|
||||
private:
|
||||
void timerCallback() override
|
||||
{
|
||||
const DiskState state = getDiskState();
|
||||
|
||||
if (state != lastState)
|
||||
{
|
||||
lastState = state;
|
||||
owner.sendChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
DiskState lastState;
|
||||
AudioCDBurner& owner;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
AudioCDBurner::AudioCDBurner (const int deviceIndex)
|
||||
{
|
||||
pimpl.reset (new Pimpl (*this, deviceIndex));
|
||||
}
|
||||
|
||||
AudioCDBurner::~AudioCDBurner()
|
||||
{
|
||||
}
|
||||
|
||||
AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex)
|
||||
{
|
||||
std::unique_ptr<AudioCDBurner> b (new AudioCDBurner (deviceIndex));
|
||||
|
||||
if (b->pimpl->device == nil)
|
||||
b = nullptr;
|
||||
|
||||
return b.release();
|
||||
}
|
||||
|
||||
StringArray AudioCDBurner::findAvailableDevices()
|
||||
{
|
||||
StringArray s;
|
||||
|
||||
for (NSDictionary* dic in [DRDevice devices])
|
||||
if (NSString* name = [dic valueForKey: DRDeviceProductNameKey])
|
||||
s.add (nsStringToJuce (name));
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
AudioCDBurner::DiskState AudioCDBurner::getDiskState() const
|
||||
{
|
||||
return pimpl->getDiskState();
|
||||
}
|
||||
|
||||
bool AudioCDBurner::isDiskPresent() const
|
||||
{
|
||||
return getDiskState() == writableDiskPresent;
|
||||
}
|
||||
|
||||
bool AudioCDBurner::openTray()
|
||||
{
|
||||
return pimpl->openTray();
|
||||
}
|
||||
|
||||
AudioCDBurner::DiskState AudioCDBurner::waitUntilStateChange (int timeOutMilliseconds)
|
||||
{
|
||||
const int64 timeout = Time::currentTimeMillis() + timeOutMilliseconds;
|
||||
DiskState oldState = getDiskState();
|
||||
DiskState newState = oldState;
|
||||
|
||||
while (newState == oldState && Time::currentTimeMillis() < timeout)
|
||||
{
|
||||
newState = getDiskState();
|
||||
Thread::sleep (100);
|
||||
}
|
||||
|
||||
return newState;
|
||||
}
|
||||
|
||||
Array<int> AudioCDBurner::getAvailableWriteSpeeds() const
|
||||
{
|
||||
return pimpl->getAvailableWriteSpeeds();
|
||||
}
|
||||
|
||||
bool AudioCDBurner::setBufferUnderrunProtection (const bool shouldBeEnabled)
|
||||
{
|
||||
return pimpl->setBufferUnderrunProtection (shouldBeEnabled);
|
||||
}
|
||||
|
||||
int AudioCDBurner::getNumAvailableAudioBlocks() const
|
||||
{
|
||||
return pimpl->getNumAvailableAudioBlocks();
|
||||
}
|
||||
|
||||
bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamps)
|
||||
{
|
||||
if ([pimpl->device->device isValid])
|
||||
{
|
||||
pimpl->device->addSourceTrack (source, numSamps);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
String AudioCDBurner::burn (AudioCDBurner::BurnProgressListener* listener,
|
||||
bool ejectDiscAfterwards,
|
||||
bool performFakeBurnForTesting,
|
||||
int writeSpeed)
|
||||
{
|
||||
if ([pimpl->device->device isValid])
|
||||
return pimpl->device->burn (listener, ejectDiscAfterwards, performFakeBurnForTesting, writeSpeed);
|
||||
|
||||
return "Couldn't open or write to the CD device";
|
||||
}
|
||||
|
||||
}
|
266
deps/juce/modules/juce_audio_utils/native/juce_mac_AudioCDReader.mm
vendored
Normal file
266
deps/juce/modules/juce_audio_utils/native/juce_mac_AudioCDReader.mm
vendored
Normal file
@ -0,0 +1,266 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
namespace CDReaderHelpers
|
||||
{
|
||||
inline const XmlElement* getElementForKey (const XmlElement& xml, const String& key)
|
||||
{
|
||||
for (auto* child : xml.getChildWithTagNameIterator ("key"))
|
||||
if (child->getAllSubText().trim() == key)
|
||||
return child->getNextElement();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static int getIntValueForKey (const XmlElement& xml, const String& key, int defaultValue = -1)
|
||||
{
|
||||
const XmlElement* const block = getElementForKey (xml, key);
|
||||
return block != nullptr ? block->getAllSubText().trim().getIntValue() : defaultValue;
|
||||
}
|
||||
|
||||
// Get the track offsets for a CD given an XmlElement representing its TOC.Plist.
|
||||
// Returns NULL on success, otherwise a const char* representing an error.
|
||||
static const char* getTrackOffsets (XmlDocument& xmlDocument, Array<int>& offsets)
|
||||
{
|
||||
const std::unique_ptr<XmlElement> xml (xmlDocument.getDocumentElement());
|
||||
if (xml == nullptr)
|
||||
return "Couldn't parse XML in file";
|
||||
|
||||
const XmlElement* const dict = xml->getChildByName ("dict");
|
||||
if (dict == nullptr)
|
||||
return "Couldn't get top level dictionary";
|
||||
|
||||
const XmlElement* const sessions = getElementForKey (*dict, "Sessions");
|
||||
if (sessions == nullptr)
|
||||
return "Couldn't find sessions key";
|
||||
|
||||
const XmlElement* const session = sessions->getFirstChildElement();
|
||||
if (session == nullptr)
|
||||
return "Couldn't find first session";
|
||||
|
||||
const int leadOut = getIntValueForKey (*session, "Leadout Block");
|
||||
if (leadOut < 0)
|
||||
return "Couldn't find Leadout Block";
|
||||
|
||||
const XmlElement* const trackArray = getElementForKey (*session, "Track Array");
|
||||
if (trackArray == nullptr)
|
||||
return "Couldn't find Track Array";
|
||||
|
||||
for (auto* track : trackArray->getChildIterator())
|
||||
{
|
||||
const int trackValue = getIntValueForKey (*track, "Start Block");
|
||||
if (trackValue < 0)
|
||||
return "Couldn't find Start Block in the track";
|
||||
|
||||
offsets.add (trackValue * AudioCDReader::samplesPerFrame - 88200);
|
||||
}
|
||||
|
||||
offsets.add (leadOut * AudioCDReader::samplesPerFrame - 88200);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void findDevices (Array<File>& cds)
|
||||
{
|
||||
File volumes ("/Volumes");
|
||||
volumes.findChildFiles (cds, File::findDirectories, false);
|
||||
|
||||
for (int i = cds.size(); --i >= 0;)
|
||||
if (! cds.getReference(i).getChildFile (".TOC.plist").exists())
|
||||
cds.remove (i);
|
||||
}
|
||||
|
||||
struct TrackSorter
|
||||
{
|
||||
static int getCDTrackNumber (const File& file)
|
||||
{
|
||||
return file.getFileName().initialSectionContainingOnly ("0123456789").getIntValue();
|
||||
}
|
||||
|
||||
static int compareElements (const File& first, const File& second)
|
||||
{
|
||||
const int firstTrack = getCDTrackNumber (first);
|
||||
const int secondTrack = getCDTrackNumber (second);
|
||||
|
||||
jassert (firstTrack > 0 && secondTrack > 0);
|
||||
|
||||
return firstTrack - secondTrack;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
StringArray AudioCDReader::getAvailableCDNames()
|
||||
{
|
||||
Array<File> cds;
|
||||
CDReaderHelpers::findDevices (cds);
|
||||
|
||||
StringArray names;
|
||||
|
||||
for (int i = 0; i < cds.size(); ++i)
|
||||
names.add (cds.getReference(i).getFileName());
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
AudioCDReader* AudioCDReader::createReaderForCD (const int index)
|
||||
{
|
||||
Array<File> cds;
|
||||
CDReaderHelpers::findDevices (cds);
|
||||
|
||||
if (cds[index].exists())
|
||||
return new AudioCDReader (cds[index]);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AudioCDReader::AudioCDReader (const File& volume)
|
||||
: AudioFormatReader (nullptr, "CD Audio"),
|
||||
volumeDir (volume),
|
||||
currentReaderTrack (-1)
|
||||
{
|
||||
sampleRate = 44100.0;
|
||||
bitsPerSample = 16;
|
||||
numChannels = 2;
|
||||
usesFloatingPointData = false;
|
||||
|
||||
refreshTrackLengths();
|
||||
}
|
||||
|
||||
AudioCDReader::~AudioCDReader()
|
||||
{
|
||||
}
|
||||
|
||||
void AudioCDReader::refreshTrackLengths()
|
||||
{
|
||||
tracks.clear();
|
||||
trackStartSamples.clear();
|
||||
lengthInSamples = 0;
|
||||
|
||||
volumeDir.findChildFiles (tracks, File::findFiles | File::ignoreHiddenFiles, false, "*.aiff");
|
||||
|
||||
CDReaderHelpers::TrackSorter sorter;
|
||||
tracks.sort (sorter);
|
||||
|
||||
const File toc (volumeDir.getChildFile (".TOC.plist"));
|
||||
|
||||
if (toc.exists())
|
||||
{
|
||||
XmlDocument doc (toc);
|
||||
const char* error = CDReaderHelpers::getTrackOffsets (doc, trackStartSamples);
|
||||
ignoreUnused (error); // could be logged..
|
||||
|
||||
lengthInSamples = trackStartSamples.getLast() - trackStartSamples.getFirst();
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioCDReader::readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
|
||||
int64 startSampleInFile, int numSamples)
|
||||
{
|
||||
while (numSamples > 0)
|
||||
{
|
||||
int track = -1;
|
||||
|
||||
for (int i = 0; i < trackStartSamples.size() - 1; ++i)
|
||||
{
|
||||
if (startSampleInFile < trackStartSamples.getUnchecked (i + 1))
|
||||
{
|
||||
track = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (track < 0)
|
||||
return false;
|
||||
|
||||
if (track != currentReaderTrack)
|
||||
{
|
||||
reader = nullptr;
|
||||
|
||||
if (auto in = tracks [track].createInputStream())
|
||||
{
|
||||
BufferedInputStream* const bin = new BufferedInputStream (in.release(), 65536, true);
|
||||
|
||||
AiffAudioFormat format;
|
||||
reader.reset (format.createReaderFor (bin, true));
|
||||
|
||||
if (reader == nullptr)
|
||||
currentReaderTrack = -1;
|
||||
else
|
||||
currentReaderTrack = track;
|
||||
}
|
||||
}
|
||||
|
||||
if (reader == nullptr)
|
||||
return false;
|
||||
|
||||
const int startPos = (int) (startSampleInFile - trackStartSamples.getUnchecked (track));
|
||||
const int numAvailable = (int) jmin ((int64) numSamples, reader->lengthInSamples - startPos);
|
||||
|
||||
reader->readSamples (destSamples, numDestChannels, startOffsetInDestBuffer, startPos, numAvailable);
|
||||
|
||||
numSamples -= numAvailable;
|
||||
startSampleInFile += numAvailable;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioCDReader::isCDStillPresent() const
|
||||
{
|
||||
return volumeDir.exists();
|
||||
}
|
||||
|
||||
void AudioCDReader::ejectDisk()
|
||||
{
|
||||
JUCE_AUTORELEASEPOOL
|
||||
{
|
||||
[[NSWorkspace sharedWorkspace] unmountAndEjectDeviceAtPath: juceStringToNS (volumeDir.getFullPathName())];
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioCDReader::isTrackAudio (int trackNum) const
|
||||
{
|
||||
return tracks [trackNum].hasFileExtension (".aiff");
|
||||
}
|
||||
|
||||
void AudioCDReader::enableIndexScanning (bool)
|
||||
{
|
||||
// any way to do this on a Mac??
|
||||
}
|
||||
|
||||
int AudioCDReader::getLastIndex() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
Array<int> AudioCDReader::findIndexesInTrack (const int /*trackNumber*/)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace juce
|
191
deps/juce/modules/juce_audio_utils/native/juce_mac_BluetoothMidiDevicePairingDialogue.mm
vendored
Normal file
191
deps/juce/modules/juce_audio_utils/native/juce_mac_BluetoothMidiDevicePairingDialogue.mm
vendored
Normal file
@ -0,0 +1,191 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
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 defined (MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11
|
||||
|
||||
//==============================================================================
|
||||
class BluetoothMidiPairingWindowClass : public ObjCClass<NSObject>
|
||||
{
|
||||
public:
|
||||
struct Callbacks
|
||||
{
|
||||
std::unique_ptr<ModalComponentManager::Callback> modalExit;
|
||||
std::function<void()> windowClosed;
|
||||
};
|
||||
|
||||
BluetoothMidiPairingWindowClass() : ObjCClass<NSObject> ("JUCEBluetoothMidiPairingWindowClass_")
|
||||
{
|
||||
addIvar<Callbacks*> ("callbacks");
|
||||
addIvar<CABTLEMIDIWindowController*> ("controller");
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
addMethod (@selector (initWithCallbacks:), initWithCallbacks, "@@:^v");
|
||||
addMethod (@selector (show:), show, "v@:^v");
|
||||
addMethod (@selector (receivedWindowWillClose:), receivedWindowWillClose, "v@:^v");
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
addMethod (@selector (dealloc), dealloc, "v@:");
|
||||
|
||||
registerClass();
|
||||
}
|
||||
|
||||
private:
|
||||
static CABTLEMIDIWindowController* getController (id self)
|
||||
{
|
||||
return getIvar<CABTLEMIDIWindowController*> (self, "controller");
|
||||
}
|
||||
|
||||
static id initWithCallbacks (id self, SEL, Callbacks* cbs)
|
||||
{
|
||||
self = sendSuperclassMessage<id> (self, @selector (init));
|
||||
|
||||
object_setInstanceVariable (self, "callbacks", cbs);
|
||||
object_setInstanceVariable (self, "controller", [CABTLEMIDIWindowController new]);
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
[[NSNotificationCenter defaultCenter] addObserver: self
|
||||
selector: @selector (receivedWindowWillClose:)
|
||||
name: @"NSWindowWillCloseNotification"
|
||||
object: [getController (self) window]];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
static void dealloc (id self, SEL)
|
||||
{
|
||||
[getController (self) release];
|
||||
sendSuperclassMessage<void> (self, @selector (dealloc));
|
||||
}
|
||||
|
||||
static void show (id self, SEL, Rectangle<int>* bounds)
|
||||
{
|
||||
if (bounds != nullptr)
|
||||
{
|
||||
auto nsBounds = makeNSRect (*bounds);
|
||||
|
||||
auto mainScreenHeight = []
|
||||
{
|
||||
if ([[NSScreen screens] count] == 0)
|
||||
return (CGFloat) 0.0f;
|
||||
|
||||
return [[[NSScreen screens] objectAtIndex: 0] frame].size.height;
|
||||
}();
|
||||
|
||||
nsBounds.origin.y = mainScreenHeight - (nsBounds.origin.y + nsBounds.size.height);
|
||||
|
||||
[getController (self).window setFrame: nsBounds
|
||||
display: YES];
|
||||
}
|
||||
|
||||
[getController (self) showWindow: nil];
|
||||
}
|
||||
|
||||
static void receivedWindowWillClose (id self, SEL, NSNotification*)
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver: self];
|
||||
|
||||
auto* cbs = getIvar<Callbacks*> (self, "callbacks");
|
||||
|
||||
if (cbs->modalExit != nullptr)
|
||||
cbs->modalExit->modalStateFinished (0);
|
||||
|
||||
cbs->windowClosed();
|
||||
}
|
||||
};
|
||||
|
||||
class BluetoothMidiSelectorWindowHelper : public DeletedAtShutdown
|
||||
{
|
||||
public:
|
||||
BluetoothMidiSelectorWindowHelper (ModalComponentManager::Callback* exitCallback,
|
||||
Rectangle<int>* bounds)
|
||||
{
|
||||
std::unique_ptr<ModalComponentManager::Callback> exitCB (exitCallback);
|
||||
|
||||
static BluetoothMidiPairingWindowClass cls;
|
||||
window.reset (cls.createInstance());
|
||||
|
||||
auto deletionCB = [safeThis = WeakReference<BluetoothMidiSelectorWindowHelper> { this }]
|
||||
{
|
||||
if (safeThis != nullptr)
|
||||
delete safeThis.get();
|
||||
};
|
||||
|
||||
callbacks.reset (new BluetoothMidiPairingWindowClass::Callbacks { std::move (exitCB),
|
||||
std::move (deletionCB) });
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
|
||||
[window.get() performSelector: @selector (initWithCallbacks:)
|
||||
withObject: (id) callbacks.get()];
|
||||
[window.get() performSelector: @selector (show:)
|
||||
withObject: (id) bounds];
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<NSObject, NSObjectDeleter> window;
|
||||
std::unique_ptr<BluetoothMidiPairingWindowClass::Callbacks> callbacks;
|
||||
|
||||
JUCE_DECLARE_WEAK_REFERENCEABLE (BluetoothMidiSelectorWindowHelper)
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BluetoothMidiSelectorWindowHelper)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* exitCallback,
|
||||
Rectangle<int>* bounds)
|
||||
{
|
||||
new BluetoothMidiSelectorWindowHelper (exitCallback, bounds);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BluetoothMidiDevicePairingDialogue::isAvailable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* exitCallback,
|
||||
Rectangle<int>*)
|
||||
{
|
||||
std::unique_ptr<ModalComponentManager::Callback> cb (exitCallback);
|
||||
// This functionality is unavailable when targetting OSX < 10.11. Instead,
|
||||
// you should pair Bluetooth MIDI devices using the "Audio MIDI Setup" app
|
||||
// (located in /Applications/Utilities).
|
||||
jassertfalse;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BluetoothMidiDevicePairingDialogue::isAvailable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace juce
|
417
deps/juce/modules/juce_audio_utils/native/juce_win32_AudioCDBurner.cpp
vendored
Normal file
417
deps/juce/modules/juce_audio_utils/native/juce_win32_AudioCDBurner.cpp
vendored
Normal file
@ -0,0 +1,417 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
namespace CDBurnerHelpers
|
||||
{
|
||||
IDiscRecorder* enumCDBurners (StringArray* list, int indexToOpen, IDiscMaster** master)
|
||||
{
|
||||
CoInitialize (0);
|
||||
|
||||
IDiscMaster* dm;
|
||||
IDiscRecorder* result = nullptr;
|
||||
|
||||
if (SUCCEEDED (CoCreateInstance (CLSID_MSDiscMasterObj, 0,
|
||||
CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,
|
||||
IID_IDiscMaster,
|
||||
(void**) &dm)))
|
||||
{
|
||||
if (SUCCEEDED (dm->Open()))
|
||||
{
|
||||
IEnumDiscRecorders* drEnum = nullptr;
|
||||
|
||||
if (SUCCEEDED (dm->EnumDiscRecorders (&drEnum)))
|
||||
{
|
||||
IDiscRecorder* dr = nullptr;
|
||||
DWORD dummy;
|
||||
int index = 0;
|
||||
|
||||
while (drEnum->Next (1, &dr, &dummy) == S_OK)
|
||||
{
|
||||
if (indexToOpen == index)
|
||||
{
|
||||
result = dr;
|
||||
break;
|
||||
}
|
||||
else if (list != nullptr)
|
||||
{
|
||||
BSTR path;
|
||||
|
||||
if (SUCCEEDED (dr->GetPath (&path)))
|
||||
list->add ((const WCHAR*) path);
|
||||
}
|
||||
|
||||
++index;
|
||||
dr->Release();
|
||||
}
|
||||
|
||||
drEnum->Release();
|
||||
}
|
||||
|
||||
if (master == 0)
|
||||
dm->Close();
|
||||
}
|
||||
|
||||
if (master != nullptr)
|
||||
*master = dm;
|
||||
else
|
||||
dm->Release();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class AudioCDBurner::Pimpl : public ComBaseClassHelper <IDiscMasterProgressEvents>,
|
||||
public Timer
|
||||
{
|
||||
public:
|
||||
Pimpl (AudioCDBurner& owner_, IDiscMaster* discMaster_, IDiscRecorder* discRecorder_)
|
||||
: owner (owner_), discMaster (discMaster_), discRecorder (discRecorder_), redbook (0),
|
||||
listener (0), progress (0), shouldCancel (false)
|
||||
{
|
||||
HRESULT hr = discMaster->SetActiveDiscMasterFormat (IID_IRedbookDiscMaster, (void**) &redbook);
|
||||
jassert (SUCCEEDED (hr));
|
||||
hr = discMaster->SetActiveDiscRecorder (discRecorder);
|
||||
//jassert (SUCCEEDED (hr));
|
||||
|
||||
lastState = getDiskState();
|
||||
startTimer (2000);
|
||||
}
|
||||
|
||||
~Pimpl() {}
|
||||
|
||||
void releaseObjects()
|
||||
{
|
||||
discRecorder->Close();
|
||||
if (redbook != nullptr)
|
||||
redbook->Release();
|
||||
discRecorder->Release();
|
||||
discMaster->Release();
|
||||
Release();
|
||||
}
|
||||
|
||||
JUCE_COMRESULT QueryCancel (boolean* pbCancel)
|
||||
{
|
||||
if (listener != nullptr && ! shouldCancel)
|
||||
shouldCancel = listener->audioCDBurnProgress (progress);
|
||||
|
||||
*pbCancel = shouldCancel;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT NotifyBlockProgress (long nCompleted, long nTotal)
|
||||
{
|
||||
progress = nCompleted / (float) nTotal;
|
||||
shouldCancel = listener != nullptr && listener->audioCDBurnProgress (progress);
|
||||
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
JUCE_COMRESULT NotifyPnPActivity (void) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT NotifyAddProgress (long /*nCompletedSteps*/, long /*nTotalSteps*/) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT NotifyTrackProgress (long /*nCurrentTrack*/, long /*nTotalTracks*/) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT NotifyPreparingBurn (long /*nEstimatedSeconds*/) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT NotifyClosingDisc (long /*nEstimatedSeconds*/) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT NotifyBurnComplete (HRESULT /*status*/) { return E_NOTIMPL; }
|
||||
JUCE_COMRESULT NotifyEraseComplete (HRESULT /*status*/) { return E_NOTIMPL; }
|
||||
|
||||
class ScopedDiscOpener
|
||||
{
|
||||
public:
|
||||
ScopedDiscOpener (Pimpl& p) : pimpl (p) { pimpl.discRecorder->OpenExclusive(); }
|
||||
~ScopedDiscOpener() { pimpl.discRecorder->Close(); }
|
||||
|
||||
private:
|
||||
Pimpl& pimpl;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (ScopedDiscOpener)
|
||||
};
|
||||
|
||||
DiskState getDiskState()
|
||||
{
|
||||
const ScopedDiscOpener opener (*this);
|
||||
|
||||
long type, flags;
|
||||
HRESULT hr = discRecorder->QueryMediaType (&type, &flags);
|
||||
|
||||
if (FAILED (hr))
|
||||
return unknown;
|
||||
|
||||
if (type != 0 && (flags & MEDIA_WRITABLE) != 0)
|
||||
return writableDiskPresent;
|
||||
|
||||
if (type == 0)
|
||||
return noDisc;
|
||||
|
||||
return readOnlyDiskPresent;
|
||||
}
|
||||
|
||||
int getIntProperty (const LPOLESTR name, const int defaultReturn) const
|
||||
{
|
||||
ComSmartPtr<IPropertyStorage> prop;
|
||||
if (FAILED (discRecorder->GetRecorderProperties (prop.resetAndGetPointerAddress())))
|
||||
return defaultReturn;
|
||||
|
||||
PROPSPEC iPropSpec;
|
||||
iPropSpec.ulKind = PRSPEC_LPWSTR;
|
||||
iPropSpec.lpwstr = name;
|
||||
|
||||
PROPVARIANT iPropVariant;
|
||||
return FAILED (prop->ReadMultiple (1, &iPropSpec, &iPropVariant))
|
||||
? defaultReturn : (int) iPropVariant.lVal;
|
||||
}
|
||||
|
||||
bool setIntProperty (const LPOLESTR name, const int value) const
|
||||
{
|
||||
ComSmartPtr<IPropertyStorage> prop;
|
||||
if (FAILED (discRecorder->GetRecorderProperties (prop.resetAndGetPointerAddress())))
|
||||
return false;
|
||||
|
||||
PROPSPEC iPropSpec;
|
||||
iPropSpec.ulKind = PRSPEC_LPWSTR;
|
||||
iPropSpec.lpwstr = name;
|
||||
|
||||
PROPVARIANT iPropVariant;
|
||||
if (FAILED (prop->ReadMultiple (1, &iPropSpec, &iPropVariant)))
|
||||
return false;
|
||||
|
||||
iPropVariant.lVal = (long) value;
|
||||
return SUCCEEDED (prop->WriteMultiple (1, &iPropSpec, &iPropVariant, iPropVariant.vt))
|
||||
&& SUCCEEDED (discRecorder->SetRecorderProperties (prop));
|
||||
}
|
||||
|
||||
void timerCallback() override
|
||||
{
|
||||
const DiskState state = getDiskState();
|
||||
|
||||
if (state != lastState)
|
||||
{
|
||||
lastState = state;
|
||||
owner.sendChangeMessage();
|
||||
}
|
||||
}
|
||||
|
||||
AudioCDBurner& owner;
|
||||
DiskState lastState;
|
||||
IDiscMaster* discMaster;
|
||||
IDiscRecorder* discRecorder;
|
||||
IRedbookDiscMaster* redbook;
|
||||
AudioCDBurner::BurnProgressListener* listener;
|
||||
float progress;
|
||||
bool shouldCancel;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
AudioCDBurner::AudioCDBurner (const int deviceIndex)
|
||||
{
|
||||
IDiscMaster* discMaster = nullptr;
|
||||
IDiscRecorder* discRecorder = CDBurnerHelpers::enumCDBurners (0, deviceIndex, &discMaster);
|
||||
|
||||
if (discRecorder != nullptr)
|
||||
pimpl.reset (new Pimpl (*this, discMaster, discRecorder));
|
||||
}
|
||||
|
||||
AudioCDBurner::~AudioCDBurner()
|
||||
{
|
||||
if (pimpl != nullptr)
|
||||
pimpl.release()->releaseObjects();
|
||||
}
|
||||
|
||||
StringArray AudioCDBurner::findAvailableDevices()
|
||||
{
|
||||
StringArray devs;
|
||||
CDBurnerHelpers::enumCDBurners (&devs, -1, 0);
|
||||
return devs;
|
||||
}
|
||||
|
||||
AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex)
|
||||
{
|
||||
std::unique_ptr<AudioCDBurner> b (new AudioCDBurner (deviceIndex));
|
||||
|
||||
if (b->pimpl == 0)
|
||||
b = nullptr;
|
||||
|
||||
return b.release();
|
||||
}
|
||||
|
||||
AudioCDBurner::DiskState AudioCDBurner::getDiskState() const
|
||||
{
|
||||
return pimpl->getDiskState();
|
||||
}
|
||||
|
||||
bool AudioCDBurner::isDiskPresent() const
|
||||
{
|
||||
return getDiskState() == writableDiskPresent;
|
||||
}
|
||||
|
||||
bool AudioCDBurner::openTray()
|
||||
{
|
||||
const Pimpl::ScopedDiscOpener opener (*pimpl);
|
||||
return SUCCEEDED (pimpl->discRecorder->Eject());
|
||||
}
|
||||
|
||||
AudioCDBurner::DiskState AudioCDBurner::waitUntilStateChange (int timeOutMilliseconds)
|
||||
{
|
||||
const int64 timeout = Time::currentTimeMillis() + timeOutMilliseconds;
|
||||
DiskState oldState = getDiskState();
|
||||
DiskState newState = oldState;
|
||||
|
||||
while (newState == oldState && Time::currentTimeMillis() < timeout)
|
||||
{
|
||||
newState = getDiskState();
|
||||
Thread::sleep (jmin (250, (int) (timeout - Time::currentTimeMillis())));
|
||||
}
|
||||
|
||||
return newState;
|
||||
}
|
||||
|
||||
Array<int> AudioCDBurner::getAvailableWriteSpeeds() const
|
||||
{
|
||||
Array<int> results;
|
||||
const int maxSpeed = pimpl->getIntProperty (L"MaxWriteSpeed", 1);
|
||||
const int speeds[] = { 1, 2, 4, 8, 12, 16, 20, 24, 32, 40, 64, 80 };
|
||||
|
||||
for (int i = 0; i < numElementsInArray (speeds); ++i)
|
||||
if (speeds[i] <= maxSpeed)
|
||||
results.add (speeds[i]);
|
||||
|
||||
results.addIfNotAlreadyThere (maxSpeed);
|
||||
return results;
|
||||
}
|
||||
|
||||
bool AudioCDBurner::setBufferUnderrunProtection (const bool shouldBeEnabled)
|
||||
{
|
||||
if (pimpl->getIntProperty (L"BufferUnderrunFreeCapable", 0) == 0)
|
||||
return false;
|
||||
|
||||
pimpl->setIntProperty (L"EnableBufferUnderrunFree", shouldBeEnabled ? -1 : 0);
|
||||
return pimpl->getIntProperty (L"EnableBufferUnderrunFree", 0) != 0;
|
||||
}
|
||||
|
||||
int AudioCDBurner::getNumAvailableAudioBlocks() const
|
||||
{
|
||||
long blocksFree = 0;
|
||||
pimpl->redbook->GetAvailableAudioTrackBlocks (&blocksFree);
|
||||
return blocksFree;
|
||||
}
|
||||
|
||||
String AudioCDBurner::burn (AudioCDBurner::BurnProgressListener* listener, bool ejectDiscAfterwards,
|
||||
bool performFakeBurnForTesting, int writeSpeed)
|
||||
{
|
||||
pimpl->setIntProperty (L"WriteSpeed", writeSpeed > 0 ? writeSpeed : -1);
|
||||
|
||||
pimpl->listener = listener;
|
||||
pimpl->progress = 0;
|
||||
pimpl->shouldCancel = false;
|
||||
|
||||
UINT_PTR cookie;
|
||||
HRESULT hr = pimpl->discMaster->ProgressAdvise ((AudioCDBurner::Pimpl*) pimpl.get(), &cookie);
|
||||
|
||||
hr = pimpl->discMaster->RecordDisc (performFakeBurnForTesting,
|
||||
ejectDiscAfterwards);
|
||||
|
||||
String error;
|
||||
if (hr != S_OK)
|
||||
{
|
||||
const char* e = "Couldn't open or write to the CD device";
|
||||
|
||||
if (hr == IMAPI_E_USERABORT)
|
||||
e = "User cancelled the write operation";
|
||||
else if (hr == IMAPI_E_MEDIUM_NOTPRESENT || hr == IMAPI_E_TRACKOPEN)
|
||||
e = "No Disk present";
|
||||
|
||||
error = e;
|
||||
}
|
||||
|
||||
pimpl->discMaster->ProgressUnadvise (cookie);
|
||||
pimpl->listener = 0;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
bool AudioCDBurner::addAudioTrack (AudioSource* audioSource, int numSamples)
|
||||
{
|
||||
if (audioSource == 0)
|
||||
return false;
|
||||
|
||||
std::unique_ptr<AudioSource> source (audioSource);
|
||||
|
||||
long bytesPerBlock;
|
||||
HRESULT hr = pimpl->redbook->GetAudioBlockSize (&bytesPerBlock);
|
||||
|
||||
const int samplesPerBlock = bytesPerBlock / 4;
|
||||
bool ok = true;
|
||||
|
||||
hr = pimpl->redbook->CreateAudioTrack ((long) numSamples / (bytesPerBlock * 4));
|
||||
|
||||
HeapBlock<byte> buffer (bytesPerBlock);
|
||||
AudioBuffer<float> sourceBuffer (2, samplesPerBlock);
|
||||
int samplesDone = 0;
|
||||
|
||||
source->prepareToPlay (samplesPerBlock, 44100.0);
|
||||
|
||||
while (ok)
|
||||
{
|
||||
{
|
||||
AudioSourceChannelInfo info (&sourceBuffer, 0, samplesPerBlock);
|
||||
sourceBuffer.clear();
|
||||
|
||||
source->getNextAudioBlock (info);
|
||||
}
|
||||
|
||||
buffer.clear (bytesPerBlock);
|
||||
|
||||
typedef AudioData::Pointer <AudioData::Int16, AudioData::LittleEndian,
|
||||
AudioData::Interleaved, AudioData::NonConst> CDSampleFormat;
|
||||
|
||||
typedef AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian,
|
||||
AudioData::NonInterleaved, AudioData::Const> SourceSampleFormat;
|
||||
|
||||
CDSampleFormat left (buffer, 2);
|
||||
left.convertSamples (SourceSampleFormat (sourceBuffer.getReadPointer (0)), samplesPerBlock);
|
||||
CDSampleFormat right (buffer + 2, 2);
|
||||
right.convertSamples (SourceSampleFormat (sourceBuffer.getReadPointer (1)), samplesPerBlock);
|
||||
|
||||
hr = pimpl->redbook->AddAudioTrackBlocks (buffer, bytesPerBlock);
|
||||
|
||||
if (FAILED (hr))
|
||||
ok = false;
|
||||
|
||||
samplesDone += samplesPerBlock;
|
||||
|
||||
if (samplesDone >= numSamples)
|
||||
break;
|
||||
}
|
||||
|
||||
hr = pimpl->redbook->CloseAudioTrack();
|
||||
return ok && hr == S_OK;
|
||||
}
|
||||
|
||||
} // namespace juce
|
1315
deps/juce/modules/juce_audio_utils/native/juce_win32_AudioCDReader.cpp
vendored
Normal file
1315
deps/juce/modules/juce_audio_utils/native/juce_win32_AudioCDReader.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
45
deps/juce/modules/juce_audio_utils/native/juce_win_BluetoothMidiDevicePairingDialogue.cpp
vendored
Normal file
45
deps/juce/modules/juce_audio_utils/native/juce_win_BluetoothMidiDevicePairingDialogue.cpp
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* exitCallback,
|
||||
Rectangle<int>*)
|
||||
{
|
||||
std::unique_ptr<ModalComponentManager::Callback> cb (exitCallback);
|
||||
// not implemented on Windows yet!
|
||||
// You should check whether the dialogue is available on your system
|
||||
// using isAvailable() before calling open().
|
||||
jassertfalse;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BluetoothMidiDevicePairingDialogue::isAvailable()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace juce
|
Reference in New Issue
Block a user