/* * DISTRHO Cardinal Plugin * Copyright (C) 2021-2022 Filipe Coelho * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 3 of * the License, or any later version. * * This program 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. * * For a full copy of the GNU General Public License see the LICENSE file. */ #include #define createPlugin createStaticPlugin #include "src/DistrhoPluginInternal.hpp" #include "src/DistrhoUIInternal.hpp" START_NAMESPACE_DISTRHO // -------------------------------------------------------------------------------------------------------------------- class ParameterFromDPF : public juce::AudioProcessorParameter { PluginExporter& plugin; const ParameterEnumerationValues& enumValues; const ParameterRanges& ranges; const uint32_t hints; const uint index; bool* const updatedPtr; mutable juce::StringArray dpfValueStrings; public: ParameterFromDPF(PluginExporter& plugin_, const uint index_, bool* const updatedPtr_) : plugin(plugin_), enumValues(plugin_.getParameterEnumValues(index_)), ranges(plugin_.getParameterRanges(index_)), hints(plugin_.getParameterHints(index_)), index(index_), updatedPtr(updatedPtr_) {} void setValueNotifyingHostFromDPF(const float newValue) { setValueNotifyingHost(ranges.getNormalizedValue(newValue)); *updatedPtr = false; } protected: float getValue() const override { return ranges.getNormalizedValue(plugin.getParameterValue(index)); } void setValue(const float newValue) override { *updatedPtr = true; plugin.setParameterValue(index, ranges.getUnnormalizedValue(newValue)); } float getDefaultValue() const override { return ranges.getNormalizedValue(plugin.getParameterDefault(index)); } juce::String getName(const int maximumStringLength) const override { if (maximumStringLength <= 0) return juce::String(plugin.getParameterName(index).buffer()); return juce::String(plugin.getParameterName(index).buffer(), static_cast(maximumStringLength)); } juce::String getLabel() const override { return plugin.getParameterUnit(index).buffer(); } int getNumSteps() const override { if (hints & kParameterIsBoolean) return 2; if (enumValues.restrictedMode) return enumValues.count; if (hints & kParameterIsInteger) return ranges.max - ranges.min; return juce::AudioProcessorParameter::getNumSteps(); } bool isDiscrete() const override { if (hints & (kParameterIsBoolean|kParameterIsInteger)) return true; if (enumValues.restrictedMode) return true; return false; } bool isBoolean() const override { return (hints & kParameterIsBoolean) != 0x0; } juce::String getText(const float normalizedValue, const int maximumStringLength) const override { float value = ranges.getUnnormalizedValue(normalizedValue); if (hints & kParameterIsBoolean) { const float midRange = ranges.min + (ranges.max - ranges.min) * 0.5f; value = value > midRange ? ranges.max : ranges.min; } else if (hints & kParameterIsInteger) { value = std::round(value); } if (enumValues.restrictedMode) { for (uint32_t i=0; i < enumValues.count; ++i) { if (d_isEqual(enumValues.values[i].value, value)) { if (maximumStringLength <= 0) return juce::String(enumValues.values[i].label); return juce::String(enumValues.values[i].label, static_cast(maximumStringLength)); } } } juce::String text; if (hints & kParameterIsInteger) text = juce::String(static_cast(value)); else text = juce::String(value); if (maximumStringLength <= 0) return text; return juce::String(text.toRawUTF8(), static_cast(maximumStringLength)); } float getValueForText(const juce::String& text) const override { if (enumValues.restrictedMode) { for (uint32_t i=0; i < enumValues.count; ++i) { if (text == enumValues.values[i].label.buffer()) return ranges.getNormalizedValue(enumValues.values[i].value); } } float value; if (hints & kParameterIsInteger) value = std::atoi(text.toRawUTF8()); else value = std::atof(text.toRawUTF8()); return ranges.getFixedAndNormalizedValue(value); } bool isAutomatable() const override { return (hints & kParameterIsAutomatable) != 0x0; } juce::String getCurrentValueAsText() const override { const float value = plugin.getParameterValue(index); if (enumValues.restrictedMode) { for (uint32_t i=0; i < enumValues.count; ++i) { if (d_isEqual(enumValues.values[i].value, value)) return juce::String(enumValues.values[i].label); } } if (hints & kParameterIsInteger) return juce::String(static_cast(value)); return juce::String(value); } juce::StringArray getAllValueStrings() const override { if (dpfValueStrings.size() != 0) return dpfValueStrings; if (enumValues.restrictedMode) { for (uint32_t i=0; i < enumValues.count; ++i) dpfValueStrings.add(enumValues.values[i].label.buffer()); return dpfValueStrings; } if (hints & kParameterIsBoolean) { if (hints & kParameterIsInteger) { dpfValueStrings.add(juce::String(static_cast(ranges.min))); dpfValueStrings.add(juce::String(static_cast(ranges.max))); } else { dpfValueStrings.add(juce::String(ranges.min)); dpfValueStrings.add(juce::String(ranges.max)); } } else if (hints & kParameterIsInteger) { const int imin = static_cast(ranges.min); const int imax = static_cast(ranges.max); for (int i=imin; i<=imax; ++i) dpfValueStrings.add(juce::String(i)); } return dpfValueStrings; } }; // -------------------------------------------------------------------------------------------------------------------- // unused in cardinal static constexpr const requestParameterValueChangeFunc nullRequestParameterValueChangeFunc = nullptr; // only needed for headless builds, which this wrapper never builds for static constexpr const updateStateValueFunc nullUpdateStateValueFunc = nullptr; // DSP/processor implementation class CardinalWrapperProcessor : public juce::AudioProcessor { friend class CardinalWrapperEditor; PluginExporter plugin; MidiEvent midiEvents[kMaxMidiEvents]; TimePosition timePosition; const uint32_t parameterCount; juce::AudioProcessorParameter* bypassParameter; juce::MidiBuffer* currentMidiMessages; bool* updatedParameters; public: CardinalWrapperProcessor() : plugin(this, writeMidiFunc, nullRequestParameterValueChangeFunc, nullUpdateStateValueFunc), parameterCount(plugin.getParameterCount()), bypassParameter(nullptr), currentMidiMessages(nullptr), updatedParameters(nullptr) { if (const double sampleRate = getSampleRate()) if (sampleRate > 0.0) plugin.setSampleRate(sampleRate); if (const int samplesPerBlock = getBlockSize()) if (samplesPerBlock > 0) plugin.setBufferSize(static_cast(samplesPerBlock)); if (parameterCount != 0) { updatedParameters = new bool[parameterCount]; std::memset(updatedParameters, 0, sizeof(bool)*parameterCount); for (uint i=0; i 0,); plugin.deactivateIfNeeded(); plugin.setSampleRate(sampleRate); plugin.setBufferSize(static_cast(samplesPerBlock)); plugin.activate(); } void releaseResources() override { plugin.deactivateIfNeeded(); } void processBlock(juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages) override { const int numSamples = buffer.getNumSamples(); DISTRHO_SAFE_ASSERT_INT_RETURN(numSamples > 0, numSamples, midiMessages.clear()); uint32_t midiEventCount = 0; for (const juce::MidiMessageMetadata midiMessage : midiMessages) { DISTRHO_SAFE_ASSERT_CONTINUE(midiMessage.numBytes > 0); DISTRHO_SAFE_ASSERT_CONTINUE(midiMessage.samplePosition >= 0); if (midiMessage.numBytes > static_cast(MidiEvent::kDataSize)) continue; MidiEvent& midiEvent(midiEvents[midiEventCount++]); midiEvent.frame = static_cast(midiMessage.samplePosition); midiEvent.size = (static_cast(midiMessage.numBytes)); std::memcpy(midiEvent.data, midiMessage.data, midiEvent.size); if (midiEventCount == kMaxMidiEvents) break; } midiMessages.clear(); const juce::ScopedValueSetter cvs(currentMidiMessages, &midiMessages, nullptr); juce::AudioPlayHead* const playhead = getPlayHead(); juce::AudioPlayHead::CurrentPositionInfo posInfo; if (playhead != nullptr && playhead->getCurrentPosition(posInfo)) { timePosition.playing = posInfo.isPlaying; timePosition.bbt.valid = true; // ticksPerBeat is not possible with JUCE timePosition.bbt.ticksPerBeat = 1920.0; if (posInfo.timeInSamples >= 0) timePosition.frame = static_cast(posInfo.timeInSamples); else timePosition.frame = 0; timePosition.bbt.beatsPerMinute = posInfo.bpm; const double ppqPos = std::abs(posInfo.ppqPosition); const int ppqPerBar = posInfo.timeSigNumerator * 4 / posInfo.timeSigDenominator; const double barBeats = (std::fmod(ppqPos, ppqPerBar) / ppqPerBar) * posInfo.timeSigNumerator; const double rest = std::fmod(barBeats, 1.0); timePosition.bbt.bar = static_cast(ppqPos) / ppqPerBar + 1; timePosition.bbt.beat = static_cast(barBeats - rest + 0.5) + 1; timePosition.bbt.tick = rest * timePosition.bbt.ticksPerBeat; timePosition.bbt.beatsPerBar = posInfo.timeSigNumerator; timePosition.bbt.beatType = posInfo.timeSigDenominator; if (posInfo.ppqPosition < 0.0) { --timePosition.bbt.bar; timePosition.bbt.beat = posInfo.timeSigNumerator - timePosition.bbt.beat + 1; timePosition.bbt.tick = timePosition.bbt.ticksPerBeat - timePosition.bbt.tick - 1; } timePosition.bbt.barStartTick = timePosition.bbt.ticksPerBeat* timePosition.bbt.beatsPerBar* (timePosition.bbt.bar-1); } else { timePosition.frame = 0; timePosition.playing = false; timePosition.bbt.valid = false; } plugin.setTimePosition(timePosition); DISTRHO_SAFE_ASSERT_RETURN(buffer.getNumChannels() == 2,); const float* audioBufferIn[2]; float* audioBufferOut[2]; audioBufferIn[0] = buffer.getReadPointer(0); audioBufferIn[1] = buffer.getReadPointer(1); audioBufferOut[0] = buffer.getWritePointer(0); audioBufferOut[1] = buffer.getWritePointer(1); plugin.run(audioBufferIn, audioBufferOut, static_cast(numSamples), midiEvents, midiEventCount); } // fix compiler warning void processBlock(juce::AudioBuffer&, juce::MidiBuffer&) override {} double getTailLengthSeconds() const override { return 0.0; } bool acceptsMidi() const override { return true; } bool producesMidi() const override { return true; } juce::AudioProcessorParameter* getBypassParameter() const override { return bypassParameter; } juce::AudioProcessorEditor* createEditor() override; bool hasEditor() const override { return true; } int getNumPrograms() override { return 1; } int getCurrentProgram() override { return 0; } void setCurrentProgram(int) override { } const juce::String getProgramName(int) override { return "Default"; } void changeProgramName(int, const juce::String&) override { } void getStateInformation(juce::MemoryBlock& destData) override { juce::XmlElement xmlState("CardinalState"); for (uint32_t i=0; i xmlState(getXmlFromBinary(data, sizeInBytes)); DISTRHO_SAFE_ASSERT_RETURN(xmlState.get() != nullptr,); const juce::Array& parameters(getParameters()); for (uint32_t i=0; igetDoubleAttribute(plugin.getParameterSymbol(i).buffer(), plugin.getParameterDefault(i)); const float normalizedValue = plugin.getParameterRanges(i).getFixedAndNormalizedValue(value); parameters.getUnchecked(static_cast(i))->setValueNotifyingHost(normalizedValue); } for (uint32_t i=0, stateCount=plugin.getStateCount(); igetStringAttribute(key.buffer(), plugin.getStateDefaultValue(i).buffer()); plugin.setState(key, value.toRawUTF8()); } } private: static bool writeMidiFunc(void* const ptr, const MidiEvent& midiEvent) { CardinalWrapperProcessor* const processor = static_cast(ptr); DISTRHO_SAFE_ASSERT_RETURN(processor != nullptr, false); juce::MidiBuffer* const currentMidiMessages = processor->currentMidiMessages; DISTRHO_SAFE_ASSERT_RETURN(currentMidiMessages != nullptr, false); const uint8_t* const data = midiEvent.size > MidiEvent::kDataSize ? midiEvent.dataExt : midiEvent.data; return currentMidiMessages->addEvent(data, static_cast(midiEvent.size), static_cast(midiEvent.frame)); } }; // -------------------------------------------------------------------------------------------------------------------- // unused in cardinal static constexpr const sendNoteFunc nullSendNoteFunc = nullptr; // unwanted, juce file dialogs are ugly static constexpr const fileRequestFunc nullFileRequestFunc = nullptr; // UI/editor implementation class CardinalWrapperEditor : public juce::AudioProcessorEditor, private juce::Timer { CardinalWrapperProcessor& cardinalProcessor; UIExporter* ui; void* const dspPtr; public: CardinalWrapperEditor(CardinalWrapperProcessor& cardinalProc) : juce::AudioProcessorEditor(cardinalProc), cardinalProcessor(cardinalProc), ui(nullptr), dspPtr(cardinalProc.plugin.getInstancePointer()) { setOpaque(true); setResizable(true, false); // setResizeLimits(648, 538, -1, -1); setSize(1228, 666); startTimer(1000.0 / 60.0); } ~CardinalWrapperEditor() override { stopTimer(); delete ui; } protected: void timerCallback() override { if (ui == nullptr) return; for (uint32_t i=0; iparameterChanged(i, cardinalProcessor.plugin.getParameterValue(i)); } } repaint(); } void paint(juce::Graphics&) override { if (ui == nullptr) { juce::ComponentPeer* const peer = getPeer(); DISTRHO_SAFE_ASSERT_RETURN(peer != nullptr,); void* const nativeHandle = peer->getNativeHandle(); DISTRHO_SAFE_ASSERT_RETURN(nativeHandle != nullptr,); ui = new UIExporter(this, (uintptr_t)nativeHandle, cardinalProcessor.getSampleRate(), editParamFunc, setParamFunc, setStateFunc, nullSendNoteFunc, setSizeFunc, nullFileRequestFunc, nullptr, // bundlePath dspPtr, 0.0 // scaleFactor ); if (cardinalProcessor.wrapperType == juce::AudioProcessor::wrapperType_Standalone) { const double scaleFactor = ui->getScaleFactor(); ui->setWindowOffset(4 * scaleFactor, 30 * scaleFactor); } } ui->plugin_idle(); } private: static void editParamFunc(void* const ptr, const uint32_t index, const bool started) { CardinalWrapperEditor* const editor = static_cast(ptr); DISTRHO_SAFE_ASSERT_RETURN(editor != nullptr,); CardinalWrapperProcessor& cardinalProcessor(editor->cardinalProcessor); if (started) cardinalProcessor.getParameters().getUnchecked(static_cast(index))->beginChangeGesture(); else cardinalProcessor.getParameters().getUnchecked(static_cast(index))->endChangeGesture(); } static void setParamFunc(void* const ptr, const uint32_t index, const float value) { CardinalWrapperEditor* const editor = static_cast(ptr); DISTRHO_SAFE_ASSERT_RETURN(editor != nullptr,); CardinalWrapperProcessor& cardinalProcessor(editor->cardinalProcessor); const juce::Array& parameters(cardinalProcessor.getParameters()); juce::AudioProcessorParameter* const parameter = parameters.getUnchecked(static_cast(index)); static_cast(parameter)->setValueNotifyingHostFromDPF(value); } static void setStateFunc(void* const ptr, const char* const key, const char* const value) { CardinalWrapperEditor* const editor = static_cast(ptr); DISTRHO_SAFE_ASSERT_RETURN(editor != nullptr,); CardinalWrapperProcessor& cardinalProcessor(editor->cardinalProcessor); cardinalProcessor.plugin.setState(key, value); } static void setSizeFunc(void* const ptr, uint width, uint height) { CardinalWrapperEditor* const editor = static_cast(ptr); DISTRHO_SAFE_ASSERT_RETURN(editor != nullptr,); #ifdef DISTRHO_OS_MAC UIExporter* const ui = editor->ui; DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); const double scaleFactor = ui->getScaleFactor(); width /= scaleFactor; height /= scaleFactor; #endif editor->setSize(static_cast(width), static_cast(height)); } }; juce::AudioProcessorEditor* CardinalWrapperProcessor::createEditor() { return new CardinalWrapperEditor(*this); } // -------------------------------------------------------------------------------------------------------------------- END_NAMESPACE_DISTRHO // -------------------------------------------------------------------------------------------------------------------- juce::AudioProcessor* createPluginFilter() { // set valid but dummy values d_nextBufferSize = 512; d_nextSampleRate = 48000.0; return new DISTRHO_NAMESPACE::CardinalWrapperProcessor; } // --------------------------------------------------------------------------------------------------------------------