fix macOS build (following Projucer changes made in Windows, which removed /Applications/JUCE/modules from its headers). move JUCE headers under source control, so that Windows and macOS can both build against same version of JUCE. remove AUv3 target (I think it's an iOS thing, so it will never work with this macOS fluidsynth dylib).

This commit is contained in:
Alex Birch
2018-06-17 13:34:53 +01:00
parent a2be47c887
commit dff4d13a1d
1563 changed files with 601601 additions and 3466 deletions

View File

@ -0,0 +1,509 @@
/*
==============================================================================
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.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-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) \
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 (AndroidBluetoothManager, JUCE_ANDROID_ACTIVITY_CLASSPATH "$BluetoothManager");
#undef JNI_CLASS_MEMBERS
//==============================================================================
struct AndroidBluetoothMidiInterface
{
static void startStopScan (bool startScanning)
{
JNIEnv* env = getEnv();
LocalRef<jobject> btManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager));
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 (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager));
// 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 (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager));
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 (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager));
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 (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager));
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 (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager));
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 * height;
const float deviceNameWidth = 0.6f * width;
g.setFont (fontHeight);
g.setColour (getDeviceNameFontColour (device.connectionStatus));
g.drawText (device.name,
Rectangle<float> (xmargin, ymargin, deviceNameWidth - (2.0f * xmargin), height - (2.0f * ymargin)),
Justification::topLeft, true);
g.setColour (getDeviceStatusFontColour (device.connectionStatus));
g.drawText (statusString,
Rectangle<float> (deviceNameWidth + xmargin, ymargin,
width - deviceNameWidth - (2.0f * xmargin), height - (2.0f * ymargin)),
Justification::topRight, true);
g.setColour (Colours::grey);
g.drawHorizontalLine (height - 1, xmargin, 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;
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()
{
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);
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()
{
jobject btManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager));
return btManager != nullptr;
}
} // namespace juce

View File

@ -0,0 +1,149 @@
/*
==============================================================================
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.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-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 (! bounds.isEmpty());
controller = [[CABTMIDICentralViewController alloc] init];
nativeSelectorComponent.setView ([controller view]);
addAndMakeVisible (nativeSelectorComponent);
enterModalState (true, exitCallback.release(), true);
}
~BluetoothMidiSelectorOverlay()
{
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 ([NSString stringWithUTF8String: "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

View File

@ -0,0 +1,84 @@
/*
==============================================================================
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.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-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

View File

@ -0,0 +1,46 @@
/*
==============================================================================
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.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-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

View File

@ -0,0 +1,469 @@
/*
==============================================================================
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.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-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");
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
addMethod (@selector (initWithAudioSourceHolder:), initWithAudioSourceHolder, "@@:^v");
addMethod (@selector (verifyDataForTrack:intoBuffer:length:atAddress:blockSize:ioFlags:),
produceDataForTrack, "I@:@^cIQI^I");
#pragma clang diagnostic pop
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 (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 (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;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
NSObject* producer = [cls.createInstance() performSelector: @selector (initWithAudioSourceHolder:)
withObject: (id) new AudioTrackProducerClass::AudioSourceHolder (source, numFrames)];
#pragma clang diagnostic pop
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 : public 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()
{
stopTimer();
}
void timerCallback() override
{
const DiskState state = getDiskState();
if (state != lastState)
{
lastState = state;
owner.sendChangeMessage();
}
}
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:
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";
}
}

View File

@ -0,0 +1,267 @@
/*
==============================================================================
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.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-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)
{
forEachXmlChildElementWithTagName (xml, child, "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";
forEachXmlChildElement (*trackArray, track)
{
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 (0, "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 (FileInputStream* const in = tracks [track].createInputStream())
{
BufferedInputStream* const bin = new BufferedInputStream (in, 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

View File

@ -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.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-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);
// Do not call this on OSX. 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;
}
} // namespace juce

View File

@ -0,0 +1,418 @@
/*
==============================================================================
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.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,46 @@
/*
==============================================================================
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.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-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