@@ -0,0 +1,619 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-9 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online at www.gnu.org/licenses. | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
// (This file gets included by juce_linux_NativeCode.cpp, rather than being | |||||
// compiled on its own). | |||||
#ifdef JUCE_INCLUDED_FILE | |||||
#if JUCE_JACK | |||||
//============================================================================== | |||||
static void* juce_libjack_handle = 0; | |||||
void* juce_load_jack_function (const char* const name) | |||||
{ | |||||
if (juce_libjack_handle == 0) | |||||
return 0; | |||||
return dlsym (juce_libjack_handle, name); | |||||
} | |||||
//============================================================================== | |||||
#define JUCE_DECL_JACK_FUNCTION(return_type, fn_name, argument_types, arguments) \ | |||||
typedef return_type (*fn_name##_ptr_t)argument_types; \ | |||||
return_type fn_name argument_types { \ | |||||
static fn_name##_ptr_t fn = 0; \ | |||||
if (fn == 0) { fn = (fn_name##_ptr_t)juce_load_jack_function(#fn_name); } \ | |||||
if (fn) return (*fn)arguments; \ | |||||
else return 0; \ | |||||
} | |||||
#define JUCE_DECL_VOID_JACK_FUNCTION(fn_name, argument_types, arguments) \ | |||||
typedef void (*fn_name##_ptr_t)argument_types; \ | |||||
void fn_name argument_types { \ | |||||
static fn_name##_ptr_t fn = 0; \ | |||||
if (fn == 0) { fn = (fn_name##_ptr_t)juce_load_jack_function(#fn_name); } \ | |||||
if (fn) (*fn)arguments; \ | |||||
} | |||||
//============================================================================== | |||||
JUCE_DECL_JACK_FUNCTION (jack_client_t*, jack_client_open, (const char* client_name, jack_options_t options, jack_status_t* status), (client_name, options, status)); | |||||
JUCE_DECL_JACK_FUNCTION (int, jack_client_close, (jack_client_t *client), (client)); | |||||
JUCE_DECL_JACK_FUNCTION (int, jack_activate, (jack_client_t* client), (client)); | |||||
JUCE_DECL_JACK_FUNCTION (int, jack_deactivate, (jack_client_t* client), (client)); | |||||
JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_get_buffer_size, (jack_client_t* client), (client)); | |||||
JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_get_sample_rate, (jack_client_t* client), (client)); | |||||
JUCE_DECL_VOID_JACK_FUNCTION (jack_on_shutdown, (jack_client_t* client, void (*function)(void* arg), void* arg), (client, function, arg)); | |||||
JUCE_DECL_JACK_FUNCTION (void* , jack_port_get_buffer, (jack_port_t* port, jack_nframes_t nframes), (port, nframes)); | |||||
JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_port_get_total_latency, (jack_client_t* client, jack_port_t* port), (client, port)); | |||||
JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_register, (jack_client_t* client, const char* port_name, const char* port_type, unsigned long flags, unsigned long buffer_size), (client, port_name, port_type, flags, buffer_size)); | |||||
JUCE_DECL_VOID_JACK_FUNCTION (jack_set_error_function, (void (*func)(const char*)), (func)); | |||||
JUCE_DECL_JACK_FUNCTION (int, jack_set_process_callback, (jack_client_t* client, JackProcessCallback process_callback, void* arg), (client, process_callback, arg)); | |||||
JUCE_DECL_JACK_FUNCTION (const char**, jack_get_ports, (jack_client_t* client, const char* port_name_pattern, const char* type_name_pattern, unsigned long flags), (client, port_name_pattern, type_name_pattern, flags)); | |||||
JUCE_DECL_JACK_FUNCTION (int, jack_connect, (jack_client_t* client, const char* source_port, const char* destination_port), (client, source_port, destination_port)); | |||||
JUCE_DECL_JACK_FUNCTION (const char*, jack_port_name, (const jack_port_t* port), (port)); | |||||
JUCE_DECL_JACK_FUNCTION (int, jack_set_port_connect_callback, (jack_client_t* client, JackPortConnectCallback connect_callback, void* arg), (client, connect_callback, arg)); | |||||
JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_by_id, (jack_client_t* client, jack_port_id_t port_id), (client, port_id)); | |||||
JUCE_DECL_JACK_FUNCTION (int, jack_port_connected, (const jack_port_t* port), (port)); | |||||
JUCE_DECL_JACK_FUNCTION (int, jack_port_connected_to, (const jack_port_t* port, const char* port_name), (port, port_name)); | |||||
#if JUCE_DEBUG | |||||
#define JACK_LOGGING_ENABLED 1 | |||||
#endif | |||||
#if JACK_LOGGING_ENABLED | |||||
static void jack_Log (const String& s) | |||||
{ | |||||
puts (s); | |||||
} | |||||
static void dumpJackErrorMessage (const jack_status_t status) throw() | |||||
{ | |||||
if (status & JackServerFailed || status & JackServerError) | |||||
jack_Log ("Unable to connect to JACK server"); | |||||
if (status & JackVersionError) | |||||
jack_Log ("Client's protocol version does not match"); | |||||
if (status & JackInvalidOption) | |||||
jack_Log ("The operation contained an invalid or unsupported option"); | |||||
if (status & JackNameNotUnique) | |||||
jack_Log ("The desired client name was not unique"); | |||||
if (status & JackNoSuchClient) | |||||
jack_Log ("Requested client does not exist"); | |||||
if (status & JackInitFailure) | |||||
jack_Log ("Unable to initialize client"); | |||||
} | |||||
#else | |||||
#define dumpJackErrorMessage(a) {} | |||||
#define jack_Log(...) {} | |||||
#endif | |||||
//============================================================================== | |||||
#ifndef JUCE_JACK_CLIENT_NAME | |||||
#define JUCE_JACK_CLIENT_NAME "JuceJack" | |||||
#endif | |||||
//============================================================================== | |||||
class JackAudioIODevice : public AudioIODevice | |||||
{ | |||||
public: | |||||
JackAudioIODevice (const String& deviceName, | |||||
const String& inputId_, | |||||
const String& outputId_) | |||||
: AudioIODevice (deviceName, T("JACK")), | |||||
inputId (inputId_), | |||||
outputId (outputId_), | |||||
isOpen_ (false), | |||||
callback (0), | |||||
inChans (0), | |||||
outChans (0), | |||||
totalNumberOfInputChannels (0), | |||||
totalNumberOfOutputChannels (0) | |||||
{ | |||||
jassert (deviceName.isNotEmpty()); | |||||
jack_status_t status; | |||||
client = juce::jack_client_open (JUCE_JACK_CLIENT_NAME, JackNoStartServer, &status); | |||||
if (client == 0) | |||||
{ | |||||
dumpJackErrorMessage (status); | |||||
} | |||||
else | |||||
{ | |||||
juce::jack_set_error_function (errorCallback); | |||||
// open input ports | |||||
const StringArray inputChannels (getInputChannelNames()); | |||||
for (int i = 0; i < inputChannels.size(); i++) | |||||
{ | |||||
String inputName; | |||||
inputName << "in_" << (++totalNumberOfInputChannels); | |||||
inputPorts.add (juce::jack_port_register (client, (const char*) inputName, | |||||
JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0)); | |||||
} | |||||
// open output ports | |||||
const StringArray outputChannels (getOutputChannelNames()); | |||||
for (int i = 0; i < outputChannels.size (); i++) | |||||
{ | |||||
String outputName; | |||||
outputName << "out_" << (++totalNumberOfOutputChannels); | |||||
outputPorts.add (juce::jack_port_register (client, (const char*) outputName, | |||||
JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0)); | |||||
} | |||||
inChans = (float**) juce_calloc (sizeof (float*) * (totalNumberOfInputChannels + 2)); | |||||
outChans = (float**) juce_calloc (sizeof (float*) * (totalNumberOfOutputChannels + 2)); | |||||
} | |||||
} | |||||
~JackAudioIODevice() | |||||
{ | |||||
close(); | |||||
if (client != 0) | |||||
{ | |||||
juce::jack_client_close (client); | |||||
client = 0; | |||||
} | |||||
juce_free (inChans); | |||||
juce_free (outChans); | |||||
} | |||||
const StringArray getChannelNames (bool forInput) const | |||||
{ | |||||
StringArray names; | |||||
const char** const ports = juce::jack_get_ports (client, 0, 0, /* JackPortIsPhysical | */ | |||||
forInput ? JackPortIsInput : JackPortIsOutput); | |||||
if (ports != 0) | |||||
{ | |||||
int j = 0; | |||||
while (ports[j] != 0) | |||||
{ | |||||
const String portName (ports [j++]); | |||||
if (portName.upToFirstOccurrenceOf (T(":"), false, false) == getName()) | |||||
names.add (portName.fromFirstOccurrenceOf (T(":"), false, false)); | |||||
} | |||||
free (ports); | |||||
} | |||||
return names; | |||||
} | |||||
const StringArray getOutputChannelNames() { return getChannelNames (false); } | |||||
const StringArray getInputChannelNames() { return getChannelNames (true); } | |||||
int getNumSampleRates() { return client != 0 ? 1 : 0; } | |||||
double getSampleRate (int index) { return client != 0 ? juce::jack_get_sample_rate (client) : 0; } | |||||
int getNumBufferSizesAvailable() { return client != 0 ? 1 : 0; } | |||||
int getBufferSizeSamples (int index) { return getDefaultBufferSize(); } | |||||
int getDefaultBufferSize() { return client != 0 ? juce::jack_get_buffer_size (client) : 0; } | |||||
const String open (const BitArray& inputChannels, const BitArray& outputChannels, | |||||
double sampleRate, int bufferSizeSamples) | |||||
{ | |||||
if (client == 0) | |||||
{ | |||||
lastError = T("No JACK client running"); | |||||
return lastError; | |||||
} | |||||
lastError = String::empty; | |||||
close(); | |||||
juce::jack_set_process_callback (client, processCallback, this); | |||||
juce::jack_on_shutdown (client, shutdownCallback, this); | |||||
juce::jack_activate (client); | |||||
isOpen_ = true; | |||||
if (! inputChannels.isEmpty()) | |||||
{ | |||||
const char** const ports = juce::jack_get_ports (client, 0, 0, /* JackPortIsPhysical | */ JackPortIsOutput); | |||||
if (ports != 0) | |||||
{ | |||||
const int numInputChannels = inputChannels.getHighestBit () + 1; | |||||
for (int i = 0; i < numInputChannels; ++i) | |||||
{ | |||||
const String portName (ports[i]); | |||||
if (inputChannels[i] && portName.upToFirstOccurrenceOf (T(":"), false, false) == getName()) | |||||
{ | |||||
int error = juce::jack_connect (client, ports[i], juce::jack_port_name ((jack_port_t*) inputPorts[i])); | |||||
if (error != 0) | |||||
jack_Log ("Cannot connect input port " + String (i) + " (" + String (ports[i]) + "), error " + String (error)); | |||||
} | |||||
} | |||||
free (ports); | |||||
} | |||||
} | |||||
if (! outputChannels.isEmpty()) | |||||
{ | |||||
const char** const ports = juce::jack_get_ports (client, 0, 0, /* JackPortIsPhysical | */ JackPortIsInput); | |||||
if (ports != 0) | |||||
{ | |||||
const int numOutputChannels = outputChannels.getHighestBit () + 1; | |||||
for (int i = 0; i < numOutputChannels; ++i) | |||||
{ | |||||
const String portName (ports[i]); | |||||
if (outputChannels[i] && portName.upToFirstOccurrenceOf (T(":"), false, false) == getName()) | |||||
{ | |||||
int error = juce::jack_connect (client, juce::jack_port_name ((jack_port_t*) outputPorts[i]), ports[i]); | |||||
if (error != 0) | |||||
jack_Log ("Cannot connect output port " + String (i) + " (" + String (ports[i]) + "), error " + String (error)); | |||||
} | |||||
} | |||||
free (ports); | |||||
} | |||||
} | |||||
return lastError; | |||||
} | |||||
void close() | |||||
{ | |||||
stop(); | |||||
if (client != 0) | |||||
{ | |||||
juce::jack_deactivate (client); | |||||
juce::jack_set_process_callback (client, processCallback, 0); | |||||
juce::jack_on_shutdown (client, shutdownCallback, 0); | |||||
} | |||||
isOpen_ = false; | |||||
} | |||||
void start (AudioIODeviceCallback* newCallback) | |||||
{ | |||||
if (isOpen_ && newCallback != callback) | |||||
{ | |||||
if (newCallback != 0) | |||||
newCallback->audioDeviceAboutToStart (this); | |||||
AudioIODeviceCallback* const oldCallback = callback; | |||||
{ | |||||
const ScopedLock sl (callbackLock); | |||||
callback = newCallback; | |||||
} | |||||
if (oldCallback != 0) | |||||
oldCallback->audioDeviceStopped(); | |||||
} | |||||
} | |||||
void stop() | |||||
{ | |||||
start (0); | |||||
} | |||||
bool isOpen() { return isOpen_; } | |||||
bool isPlaying() { return callback != 0; } | |||||
int getCurrentBufferSizeSamples() { return getBufferSizeSamples (0); } | |||||
double getCurrentSampleRate() { return getSampleRate (0); } | |||||
int getCurrentBitDepth() { return 32; } | |||||
const String getLastError() { return lastError; } | |||||
const BitArray getActiveOutputChannels() const | |||||
{ | |||||
BitArray outputBits; | |||||
for (int i = 0; i < outputPorts.size(); i++) | |||||
if (juce::jack_port_connected ((jack_port_t*) outputPorts [i])) | |||||
outputBits.setBit (i); | |||||
return outputBits; | |||||
} | |||||
const BitArray getActiveInputChannels() const | |||||
{ | |||||
BitArray inputBits; | |||||
for (int i = 0; i < inputPorts.size(); i++) | |||||
if (juce::jack_port_connected ((jack_port_t*) inputPorts [i])) | |||||
inputBits.setBit (i); | |||||
return inputBits; | |||||
} | |||||
int getOutputLatencyInSamples() | |||||
{ | |||||
int latency = 0; | |||||
for (int i = 0; i < outputPorts.size(); i++) | |||||
latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, (jack_port_t*) outputPorts [i])); | |||||
return latency; | |||||
} | |||||
int getInputLatencyInSamples() | |||||
{ | |||||
int latency = 0; | |||||
for (int i = 0; i < inputPorts.size(); i++) | |||||
latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, (jack_port_t*) inputPorts [i])); | |||||
return latency; | |||||
} | |||||
String inputId, outputId; | |||||
private: | |||||
void process (const int numSamples) | |||||
{ | |||||
int i, numActiveInChans = 0, numActiveOutChans = 0; | |||||
for (i = 0; i < totalNumberOfInputChannels; ++i) | |||||
{ | |||||
jack_default_audio_sample_t* in | |||||
= (jack_default_audio_sample_t*) juce::jack_port_get_buffer ((jack_port_t*) inputPorts.getUnchecked(i), numSamples); | |||||
if (in != 0) | |||||
inChans [numActiveInChans++] = (float*) in; | |||||
} | |||||
for (i = 0; i < totalNumberOfOutputChannels; ++i) | |||||
{ | |||||
jack_default_audio_sample_t* out | |||||
= (jack_default_audio_sample_t*) juce::jack_port_get_buffer ((jack_port_t*) outputPorts.getUnchecked(i), numSamples); | |||||
if (out != 0) | |||||
outChans [numActiveOutChans++] = (float*) out; | |||||
} | |||||
const ScopedLock sl (callbackLock); | |||||
if (callback != 0) | |||||
{ | |||||
callback->audioDeviceIOCallback ((const float**) inChans, numActiveInChans, | |||||
outChans, numActiveOutChans, numSamples); | |||||
} | |||||
else | |||||
{ | |||||
for (i = 0; i < numActiveOutChans; ++i) | |||||
zeromem (outChans[i], sizeof (float) * numSamples); | |||||
} | |||||
} | |||||
static int processCallback (jack_nframes_t nframes, void* callbackArgument) | |||||
{ | |||||
if (callbackArgument != 0) | |||||
((JackAudioIODevice*) callbackArgument)->process (nframes); | |||||
return 0; | |||||
} | |||||
static void threadInitCallback (void* callbackArgument) | |||||
{ | |||||
jack_Log ("JackAudioIODevice::initialise"); | |||||
} | |||||
static void shutdownCallback (void* callbackArgument) | |||||
{ | |||||
jack_Log ("JackAudioIODevice::shutdown"); | |||||
JackAudioIODevice* device = (JackAudioIODevice*) callbackArgument; | |||||
if (device != 0) | |||||
{ | |||||
device->client = 0; | |||||
device->close(); | |||||
} | |||||
} | |||||
static void errorCallback (const char* msg) | |||||
{ | |||||
jack_Log ("JackAudioIODevice::errorCallback " + String (msg)); | |||||
} | |||||
bool isOpen_; | |||||
jack_client_t* client; | |||||
String lastError; | |||||
AudioIODeviceCallback* callback; | |||||
CriticalSection callbackLock; | |||||
float** inChans; | |||||
float** outChans; | |||||
int totalNumberOfInputChannels; | |||||
int totalNumberOfOutputChannels; | |||||
VoidArray inputPorts, outputPorts; | |||||
}; | |||||
//============================================================================== | |||||
class JackAudioIODeviceType : public AudioIODeviceType | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
JackAudioIODeviceType() | |||||
: AudioIODeviceType (T("JACK")), | |||||
hasScanned (false) | |||||
{ | |||||
} | |||||
~JackAudioIODeviceType() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
void scanForDevices() | |||||
{ | |||||
hasScanned = true; | |||||
inputNames.clear(); | |||||
inputIds.clear(); | |||||
outputNames.clear(); | |||||
outputIds.clear(); | |||||
if (juce_libjack_handle == 0) | |||||
{ | |||||
juce_libjack_handle = dlopen ("libjack.so", RTLD_LAZY); | |||||
if (juce_libjack_handle == 0) | |||||
return; | |||||
} | |||||
// open a dummy client | |||||
jack_status_t status; | |||||
jack_client_t* client = juce::jack_client_open ("JuceJackDummy", JackNoStartServer, &status); | |||||
if (client == 0) | |||||
{ | |||||
dumpJackErrorMessage (status); | |||||
} | |||||
else | |||||
{ | |||||
// scan for output devices | |||||
const char** ports = juce::jack_get_ports (client, 0, 0, /* JackPortIsPhysical | */ JackPortIsOutput); | |||||
if (ports != 0) | |||||
{ | |||||
int j = 0; | |||||
while (ports[j] != 0) | |||||
{ | |||||
String clientName (ports[j]); | |||||
clientName = clientName.upToFirstOccurrenceOf (T(":"), false, false); | |||||
if (clientName != String (JUCE_JACK_CLIENT_NAME) | |||||
&& ! inputNames.contains (clientName)) | |||||
{ | |||||
inputNames.add (clientName); | |||||
inputIds.add (ports [j]); | |||||
} | |||||
++j; | |||||
} | |||||
free (ports); | |||||
} | |||||
// scan for input devices | |||||
ports = juce::jack_get_ports (client, 0, 0, /* JackPortIsPhysical | */ JackPortIsInput); | |||||
if (ports != 0) | |||||
{ | |||||
int j = 0; | |||||
while (ports[j] != 0) | |||||
{ | |||||
String clientName (ports[j]); | |||||
clientName = clientName.upToFirstOccurrenceOf (T(":"), false, false); | |||||
if (clientName != String (JUCE_JACK_CLIENT_NAME) | |||||
&& ! outputNames.contains (clientName)) | |||||
{ | |||||
outputNames.add (clientName); | |||||
outputIds.add (ports [j]); | |||||
} | |||||
++j; | |||||
} | |||||
free (ports); | |||||
} | |||||
juce::jack_client_close (client); | |||||
} | |||||
} | |||||
const StringArray getDeviceNames (const bool wantInputNames) const | |||||
{ | |||||
jassert (hasScanned); // need to call scanForDevices() before doing this | |||||
return wantInputNames ? inputNames : outputNames; | |||||
} | |||||
int getDefaultDeviceIndex (const bool forInput) const | |||||
{ | |||||
jassert (hasScanned); // need to call scanForDevices() before doing this | |||||
return 0; | |||||
} | |||||
bool hasSeparateInputsAndOutputs() const { return true; } | |||||
int getIndexOfDevice (AudioIODevice* device, const bool asInput) const | |||||
{ | |||||
jassert (hasScanned); // need to call scanForDevices() before doing this | |||||
JackAudioIODevice* const d = dynamic_cast <JackAudioIODevice*> (device); | |||||
if (d == 0) | |||||
return -1; | |||||
return asInput ? inputIds.indexOf (d->inputId) | |||||
: outputIds.indexOf (d->outputId); | |||||
} | |||||
AudioIODevice* createDevice (const String& outputDeviceName, | |||||
const String& inputDeviceName) | |||||
{ | |||||
jassert (hasScanned); // need to call scanForDevices() before doing this | |||||
const int inputIndex = inputNames.indexOf (inputDeviceName); | |||||
const int outputIndex = outputNames.indexOf (outputDeviceName); | |||||
if (inputIndex >= 0 || outputIndex >= 0) | |||||
return new JackAudioIODevice (outputIndex >= 0 ? outputDeviceName | |||||
: inputDeviceName, | |||||
inputIds [inputIndex], | |||||
outputIds [outputIndex]); | |||||
return 0; | |||||
} | |||||
//============================================================================== | |||||
juce_UseDebuggingNewOperator | |||||
private: | |||||
StringArray inputNames, outputNames, inputIds, outputIds; | |||||
bool hasScanned; | |||||
JackAudioIODeviceType (const JackAudioIODeviceType&); | |||||
const JackAudioIODeviceType& operator= (const JackAudioIODeviceType&); | |||||
}; | |||||
//============================================================================== | |||||
AudioIODeviceType* juce_createAudioIODeviceType_JACK() | |||||
{ | |||||
return new JackAudioIODeviceType(); | |||||
} | |||||
//============================================================================== | |||||
#else // if JACK is turned off.. | |||||
AudioIODeviceType* juce_createAudioIODeviceType_JACK() { return 0; } | |||||
#endif | |||||
#endif |
@@ -0,0 +1,513 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library - "Jules' Utility Class Extensions" | |||||
Copyright 2004-9 by Raw Material Software Ltd. | |||||
------------------------------------------------------------------------------ | |||||
JUCE can be redistributed and/or modified under the terms of the GNU General | |||||
Public License (Version 2), as published by the Free Software Foundation. | |||||
A copy of the license is included in the JUCE distribution, or can be found | |||||
online at www.gnu.org/licenses. | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.rawmaterialsoftware.com/juce for more information. | |||||
============================================================================== | |||||
*/ | |||||
// (This file gets included by juce_mac_NativeCode.mm, rather than being | |||||
// compiled on its own). | |||||
#if JUCE_INCLUDED_FILE | |||||
//============================================================================== | |||||
class CoreGraphicsContext : public LowLevelGraphicsContext | |||||
{ | |||||
public: | |||||
CoreGraphicsContext (CGContextRef context_, const float flipHeight_) | |||||
: context (context_), | |||||
flipHeight (flipHeight_) | |||||
{ | |||||
CGContextRetain (context); | |||||
} | |||||
~CoreGraphicsContext() | |||||
{ | |||||
CGContextRelease (context); | |||||
} | |||||
//============================================================================== | |||||
bool isVectorDevice() const { return false; } | |||||
void setOrigin (int x, int y) | |||||
{ | |||||
CGContextTranslateCTM (context, x, -y); | |||||
} | |||||
bool reduceClipRegion (int x, int y, int w, int h) | |||||
{ | |||||
CGContextClipToRect (context, CGRectMake (x, flipHeight - (y + h), w, h)); | |||||
return ! isClipEmpty(); | |||||
} | |||||
bool reduceClipRegion (const RectangleList& clipRegion) | |||||
{ | |||||
const int numRects = clipRegion.getNumRectangles(); | |||||
CGRect* const rects = new CGRect [numRects]; | |||||
for (int i = 0; i < numRects; ++i) | |||||
{ | |||||
const Rectangle& r = clipRegion.getRectangle(i); | |||||
rects[i] = CGRectMake (r.getX(), flipHeight - r.getBottom(), r.getWidth(), r.getHeight()); | |||||
} | |||||
CGContextClipToRects (context, rects, numRects); | |||||
delete[] rects; | |||||
return ! isClipEmpty(); | |||||
} | |||||
void excludeClipRegion (int x, int y, int w, int h) | |||||
{ | |||||
RectangleList r (getClipBounds()); | |||||
r.subtract (Rectangle (x, y, w, h)); | |||||
reduceClipRegion (r); | |||||
} | |||||
void saveState() | |||||
{ | |||||
CGContextSaveGState (context); | |||||
} | |||||
void restoreState() | |||||
{ | |||||
CGContextRestoreGState (context); | |||||
} | |||||
bool clipRegionIntersects (int x, int y, int w, int h) | |||||
{ | |||||
return getClipBounds().intersects (Rectangle (x, y, w, h)); | |||||
} | |||||
const Rectangle getClipBounds() const | |||||
{ | |||||
CGRect bounds = CGRectIntegral (CGContextGetClipBoundingBox (context)); | |||||
return Rectangle (roundFloatToInt (bounds.origin.x), | |||||
roundFloatToInt (flipHeight - (bounds.origin.y + bounds.size.height)), | |||||
roundFloatToInt (bounds.size.width), | |||||
roundFloatToInt (bounds.size.height)); | |||||
} | |||||
bool isClipEmpty() const | |||||
{ | |||||
return CGRectIsEmpty (CGContextGetClipBoundingBox (context)); | |||||
} | |||||
//============================================================================== | |||||
void fillRectWithColour (int x, int y, int w, int h, const Colour& colour, const bool replaceExistingContents) | |||||
{ | |||||
CGContextSetBlendMode (context, replaceExistingContents ? kCGBlendModeCopy : kCGBlendModeNormal); | |||||
CGContextSetAlpha (context, 1.0f); | |||||
setColour (colour); | |||||
CGContextFillRect (context, CGRectMake (x, flipHeight - (y + h), w, h)); | |||||
} | |||||
void fillRectWithGradient (int x, int y, int w, int h, const ColourGradient& gradient) | |||||
{ | |||||
CGContextSaveGState (context); | |||||
CGContextClipToRect (context, CGRectMake (x, flipHeight - (y + h), w, h)); | |||||
flip(); | |||||
drawGradient (gradient); | |||||
CGContextRestoreGState (context); | |||||
} | |||||
void fillPathWithColour (const Path& path, const AffineTransform& transform, const Colour& colour, EdgeTable::OversamplingLevel /*quality*/) | |||||
{ | |||||
CGContextSetAlpha (context, 1.0f); | |||||
CGContextSaveGState (context); | |||||
flip(); | |||||
applyTransform (transform); | |||||
createPath (path); | |||||
setColour (colour); | |||||
CGContextSetBlendMode (context, kCGBlendModeNormal); | |||||
CGContextFillPath (context); | |||||
CGContextRestoreGState (context); | |||||
} | |||||
void fillPathWithGradient (const Path& path, const AffineTransform& transform, const ColourGradient& gradient, EdgeTable::OversamplingLevel quality) | |||||
{ | |||||
CGContextSaveGState (context); | |||||
createPath (path, transform); | |||||
CGContextClip (context); | |||||
flip(); | |||||
applyTransform (gradient.transform); | |||||
drawGradient (gradient); | |||||
CGContextRestoreGState (context); | |||||
} | |||||
void fillPathWithImage (const Path& path, const AffineTransform& transform, | |||||
const Image& image, int imageX, int imageY, float alpha, EdgeTable::OversamplingLevel /*quality*/) | |||||
{ | |||||
CGContextSaveGState (context); | |||||
createPath (path, transform); | |||||
CGContextClip (context); | |||||
blendImage (image, imageX, imageY, image.getWidth(), image.getHeight(), 0, 0, alpha); | |||||
CGContextRestoreGState (context); | |||||
} | |||||
void fillAlphaChannelWithColour (const Image& alphaImage, int alphaImageX, int alphaImageY, const Colour& colour) | |||||
{ | |||||
Image* singleChannelImage = createAlphaChannelImage (alphaImage); | |||||
CGImageRef image = createImage (*singleChannelImage, true); | |||||
CGContextSetAlpha (context, 1.0f); | |||||
CGContextSaveGState (context); | |||||
CGRect r = CGRectMake (alphaImageX, flipHeight - (alphaImageY + alphaImage.getHeight()), | |||||
alphaImage.getWidth(), alphaImage.getHeight()); | |||||
CGContextClipToMask (context, r, image); | |||||
setColour (colour); | |||||
CGContextSetBlendMode (context, kCGBlendModeNormal); | |||||
CGContextFillRect (context, r); | |||||
CGContextRestoreGState (context); | |||||
CGImageRelease (image); | |||||
deleteAlphaChannelImage (alphaImage, singleChannelImage); | |||||
} | |||||
void fillAlphaChannelWithGradient (const Image& alphaImage, int alphaImageX, int alphaImageY, const ColourGradient& gradient) | |||||
{ | |||||
Image* singleChannelImage = createAlphaChannelImage (alphaImage); | |||||
CGImageRef image = createImage (*singleChannelImage, true); | |||||
CGContextSaveGState (context); | |||||
CGRect r = CGRectMake (alphaImageX, flipHeight - (alphaImageY + alphaImage.getHeight()), | |||||
alphaImage.getWidth(), alphaImage.getHeight()); | |||||
CGContextClipToMask (context, r, image); | |||||
flip(); | |||||
drawGradient (gradient); | |||||
CGContextRestoreGState (context); | |||||
CGImageRelease (image); | |||||
deleteAlphaChannelImage (alphaImage, singleChannelImage); | |||||
} | |||||
void fillAlphaChannelWithImage (const Image& alphaImage, int alphaImageX, int alphaImageY, | |||||
const Image& fillerImage, int fillerImageX, int fillerImageY, float alpha) | |||||
{ | |||||
Image* singleChannelImage = createAlphaChannelImage (alphaImage); | |||||
CGImageRef image = createImage (*singleChannelImage, true); | |||||
CGContextSaveGState (context); | |||||
CGRect r = CGRectMake (alphaImageX, flipHeight - (alphaImageY + alphaImage.getHeight()), | |||||
alphaImage.getWidth(), alphaImage.getHeight()); | |||||
CGContextClipToMask (context, r, image); | |||||
blendImage (fillerImage, fillerImageX, fillerImageY, | |||||
fillerImage.getWidth(), fillerImage.getHeight(), | |||||
0, 0, alpha); | |||||
CGContextRestoreGState (context); | |||||
CGImageRelease (image); | |||||
deleteAlphaChannelImage (alphaImage, singleChannelImage); | |||||
} | |||||
//============================================================================== | |||||
void blendImage (const Image& sourceImage, | |||||
int destX, int destY, int destW, int destH, int sourceX, int sourceY, | |||||
float alpha) | |||||
{ | |||||
CGContextSetBlendMode (context, kCGBlendModeNormal); | |||||
CGImageRef image = createImage (sourceImage, false); | |||||
CGContextSaveGState (context); | |||||
CGContextClipToRect (context, CGRectMake (destX, flipHeight - (destY + destH), destW, destH)); | |||||
CGContextSetAlpha (context, alpha); | |||||
CGContextDrawImage (context, CGRectMake (destX - sourceX, | |||||
flipHeight - ((destY - sourceY) + sourceImage.getHeight()), | |||||
sourceImage.getWidth(), | |||||
sourceImage.getHeight()), image); | |||||
CGContextRestoreGState (context); | |||||
CGImageRelease (image); | |||||
} | |||||
void blendImageRescaling (const Image& sourceImage, | |||||
int dx, int dy, int dw, int dh, | |||||
int sx, int sy, int sw, int sh, | |||||
float alpha, const Graphics::ResamplingQuality quality) | |||||
{ | |||||
if (sw > 0 && sh > 0) | |||||
{ | |||||
if (sw == dw && sh == dh) | |||||
{ | |||||
blendImage (sourceImage, | |||||
dx, dy, dw, dh, | |||||
sx, sy, alpha); | |||||
} | |||||
else | |||||
{ | |||||
blendImageWarping (sourceImage, | |||||
sx, sy, sw, sh, | |||||
AffineTransform::translation ((float) -sx, | |||||
(float) -sy) | |||||
.scaled (dw / (float) sw, | |||||
dh / (float) sh) | |||||
.translated ((float) dx, | |||||
(float) dy), | |||||
alpha, | |||||
quality); | |||||
} | |||||
} | |||||
} | |||||
void blendImageWarping (const Image& sourceImage, | |||||
int srcClipX, int srcClipY, int srcClipW, int srcClipH, | |||||
const AffineTransform& transform, | |||||
float alpha, const Graphics::ResamplingQuality quality) | |||||
{ | |||||
CGContextSetBlendMode (context, kCGBlendModeNormal); | |||||
CGImageRef fullImage = createImage (sourceImage, false); | |||||
CGImageRef image = CGImageCreateWithImageInRect (fullImage, CGRectMake (srcClipX, sourceImage.getHeight() - (srcClipY + srcClipH), | |||||
srcClipW, srcClipH)); | |||||
CGImageRelease (fullImage); | |||||
CGContextSaveGState (context); | |||||
CGContextSetAlpha (context, alpha); | |||||
flip(); | |||||
applyTransform (AffineTransform::scale (1.0f, -1.0f).translated (0, sourceImage.getHeight()).followedBy (transform)); | |||||
CGContextSetInterpolationQuality (context, quality == Graphics::lowResamplingQuality | |||||
? kCGInterpolationLow | |||||
: kCGInterpolationHigh); | |||||
CGContextDrawImage (context, CGRectMake (0, 0, sourceImage.getWidth(), | |||||
sourceImage.getHeight()), image); | |||||
CGImageRelease (image); | |||||
CGContextRestoreGState (context); | |||||
} | |||||
//============================================================================== | |||||
void drawLine (double x1, double y1, double x2, double y2, const Colour& colour) | |||||
{ | |||||
CGContextSetAlpha (context, 1.0f); | |||||
CGContextSetRGBStrokeColor (context, colour.getFloatRed(), colour.getFloatGreen(), | |||||
colour.getFloatBlue(), colour.getFloatAlpha()); | |||||
CGContextSetLineCap (context, kCGLineCapSquare); | |||||
CGContextSetLineWidth (context, 1.0f); | |||||
CGPoint line[] = { { x1 + 0.5f, flipHeight - (y1 + 0.5f) }, | |||||
{ x2 + 0.5f, flipHeight - (y2 + 0.5f) } }; | |||||
CGContextStrokeLineSegments (context, line, 1); | |||||
} | |||||
void drawVerticalLine (const int x, double top, double bottom, const Colour& colour) | |||||
{ | |||||
setColour (colour); | |||||
CGContextSetBlendMode (context, kCGBlendModeNormal); | |||||
CGContextFillRect (context, CGRectMake (x, flipHeight - bottom, 1.0f, bottom - top)); | |||||
} | |||||
void drawHorizontalLine (const int y, double left, double right, const Colour& colour) | |||||
{ | |||||
setColour (colour); | |||||
CGContextSetBlendMode (context, kCGBlendModeNormal); | |||||
CGContextFillRect (context, CGRectMake (left, y, right - left, 1.0f)); | |||||
} | |||||
private: | |||||
CGContextRef context; | |||||
const float flipHeight; | |||||
void setColour (const Colour& colour) const throw() | |||||
{ | |||||
CGContextSetRGBFillColor (context, | |||||
colour.getFloatRed(), colour.getFloatGreen(), | |||||
colour.getFloatBlue(), colour.getFloatAlpha()); | |||||
} | |||||
static void gradientCallback (void* info, const CGFloat* inData, CGFloat* outData) | |||||
{ | |||||
const ColourGradient* const g = (const ColourGradient*) info; | |||||
const Colour c (g->getColourAtPosition (inData[0])); | |||||
outData[0] = c.getFloatRed(); | |||||
outData[1] = c.getFloatGreen(); | |||||
outData[2] = c.getFloatBlue(); | |||||
outData[3] = c.getFloatAlpha(); | |||||
} | |||||
CGShadingRef createGradient (const ColourGradient& gradient) const throw() | |||||
{ | |||||
CGShadingRef result = 0; | |||||
CGColorSpaceRef colourSpace = CGColorSpaceCreateDeviceRGB(); | |||||
CGFunctionCallbacks callbacks = { 0, gradientCallback, 0 }; | |||||
CGFunctionRef function = CGFunctionCreate ((void*) &gradient, 1, 0, 4, 0, &callbacks); | |||||
CGPoint p1 = CGPointMake (gradient.x1, gradient.y1); | |||||
if (gradient.isRadial) | |||||
{ | |||||
result = CGShadingCreateRadial (colourSpace, | |||||
p1, 0, | |||||
p1, hypotf (gradient.x1 - gradient.x2, gradient.y1 - gradient.y2), | |||||
function, true, true); | |||||
} | |||||
else | |||||
{ | |||||
result = CGShadingCreateAxial (colourSpace, p1, | |||||
CGPointMake (gradient.x2, gradient.y2), | |||||
function, true, true); | |||||
} | |||||
CGColorSpaceRelease (colourSpace); | |||||
CGFunctionRelease (function); | |||||
return result; | |||||
} | |||||
void drawGradient (const ColourGradient& gradient) const throw() | |||||
{ | |||||
CGContextSetBlendMode (context, kCGBlendModeNormal); | |||||
CGContextSetAlpha (context, 1.0f); | |||||
CGShadingRef shading = createGradient (gradient); | |||||
CGContextDrawShading (context, shading); | |||||
CGShadingRelease (shading); | |||||
} | |||||
void createPath (const Path& path) const throw() | |||||
{ | |||||
CGContextBeginPath (context); | |||||
Path::Iterator i (path); | |||||
while (i.next()) | |||||
{ | |||||
switch (i.elementType) | |||||
{ | |||||
case Path::Iterator::startNewSubPath: | |||||
CGContextMoveToPoint (context, i.x1, i.y1); | |||||
break; | |||||
case Path::Iterator::lineTo: | |||||
CGContextAddLineToPoint (context, i.x1, i.y1); | |||||
break; | |||||
case Path::Iterator::quadraticTo: | |||||
CGContextAddQuadCurveToPoint (context, i.x1, i.y1, i.x2, i.y2); | |||||
break; | |||||
case Path::Iterator::cubicTo: | |||||
CGContextAddCurveToPoint (context, i.x1, i.y1, i.x2, i.y2, i.x3, i.y3); | |||||
break; | |||||
case Path::Iterator::closePath: | |||||
CGContextClosePath (context); break; | |||||
default: | |||||
jassertfalse | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
void createPath (const Path& path, const AffineTransform& transform) const throw() | |||||
{ | |||||
CGContextBeginPath (context); | |||||
Path::Iterator i (path); | |||||
while (i.next()) | |||||
{ | |||||
switch (i.elementType) | |||||
{ | |||||
case Path::Iterator::startNewSubPath: | |||||
transform.transformPoint (i.x1, i.y1); | |||||
CGContextMoveToPoint (context, i.x1, flipHeight - i.y1); | |||||
break; | |||||
case Path::Iterator::lineTo: | |||||
transform.transformPoint (i.x1, i.y1); | |||||
CGContextAddLineToPoint (context, i.x1, flipHeight - i.y1); | |||||
break; | |||||
case Path::Iterator::quadraticTo: | |||||
transform.transformPoint (i.x1, i.y1); | |||||
transform.transformPoint (i.x2, i.y2); | |||||
CGContextAddQuadCurveToPoint (context, i.x1, flipHeight - i.y1, i.x2, flipHeight - i.y2); | |||||
break; | |||||
case Path::Iterator::cubicTo: | |||||
transform.transformPoint (i.x1, i.y1); | |||||
transform.transformPoint (i.x2, i.y2); | |||||
transform.transformPoint (i.x3, i.y3); | |||||
CGContextAddCurveToPoint (context, i.x1, flipHeight - i.y1, i.x2, flipHeight - i.y2, i.x3, flipHeight - i.y3); | |||||
break; | |||||
case Path::Iterator::closePath: | |||||
CGContextClosePath (context); break; | |||||
default: | |||||
jassertfalse | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
CGImageRef createImage (const Image& juceImage, const bool forAlpha) const throw() | |||||
{ | |||||
int lineStride = 0; | |||||
int pixelStride = 0; | |||||
const uint8* imageData = juceImage.lockPixelDataReadOnly (0, 0, juceImage.getWidth(), juceImage.getHeight(), | |||||
lineStride, pixelStride); | |||||
CGDataProviderRef provider = CGDataProviderCreateWithData (0, imageData, lineStride * pixelStride, 0); | |||||
CGColorSpaceRef colourSpace = forAlpha ? CGColorSpaceCreateDeviceGray() | |||||
: CGColorSpaceCreateDeviceRGB(); | |||||
CGImageRef imageRef = CGImageCreate (juceImage.getWidth(), juceImage.getHeight(), | |||||
8, pixelStride * 8, lineStride, | |||||
colourSpace, | |||||
(juceImage.hasAlphaChannel() && ! forAlpha) | |||||
? (kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little) | |||||
: kCGBitmapByteOrderDefault, | |||||
provider, | |||||
0, true, kCGRenderingIntentDefault); | |||||
CGColorSpaceRelease (colourSpace); | |||||
CGDataProviderRelease (provider); | |||||
juceImage.releasePixelDataReadOnly (imageData); | |||||
return imageRef; | |||||
} | |||||
static Image* createAlphaChannelImage (const Image& im) throw() | |||||
{ | |||||
if (im.getFormat() == Image::SingleChannel) | |||||
return const_cast <Image*> (&im); | |||||
return im.createCopyOfAlphaChannel(); | |||||
} | |||||
static void deleteAlphaChannelImage (const Image& im, Image* const alphaIm) throw() | |||||
{ | |||||
if (im.getFormat() != Image::SingleChannel) | |||||
delete alphaIm; | |||||
} | |||||
void flip() const throw() | |||||
{ | |||||
CGContextConcatCTM (context, CGAffineTransformMake (1, 0, 0, -1, 0, flipHeight)); | |||||
} | |||||
void applyTransform (const AffineTransform& transform) const throw() | |||||
{ | |||||
CGAffineTransform t; | |||||
t.a = transform.mat00; | |||||
t.b = transform.mat10; | |||||
t.c = transform.mat01; | |||||
t.d = transform.mat11; | |||||
t.tx = transform.mat02; | |||||
t.ty = transform.mat12; | |||||
CGContextConcatCTM (context, t); | |||||
} | |||||
}; | |||||
#endif |