| @@ -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 | |||