DPF

DISTRHO Plugin Framework
Log | Files | Refs | Submodules | README | LICENSE

commit ac545ac660cd2fcdd6a598c57111766e378ee445
parent 86a621bfd86922a49ce593fec2a618a1e0cc6ef3
Author: Filipe Coelho <falktx@falktx.com>
Date:   Mon,  8 Aug 2022 22:27:48 +0100

Merge pull request #381 from DISTRHO/develop

Merge develop into main
Diffstat:
M.github/workflows/cmake.yml | 3++-
M.github/workflows/example-plugins.yml | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
A.github/workflows/irc.yml | 20++++++++++++++++++++
M.github/workflows/makefile.yml | 4++--
M.gitignore | 1+
AFEATURES.md | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ALICENSING.md | 47+++++++++++++++++++++++++++++++++++++++++++++++
MMakefile | 7+++----
MMakefile.base.mk | 354++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
MMakefile.plugins.mk | 346++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
MREADME.md | 8+++++++-
Mcmake/DPF-plugin.cmake | 181++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mdgl/Application.hpp | 30+++++++++++++++++++++++++++---
Mdgl/Base.hpp | 86++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mdgl/Cairo.hpp | 6+++---
Mdgl/EventHandlers.hpp | 13+++++++++++--
Adgl/FileBrowserDialog.hpp | 28++++++++++++++++++++++++++++
Mdgl/ImageBase.hpp | 2+-
Mdgl/ImageBaseWidgets.hpp | 51++++++++++++++++++++++++++++++++++++++++++++++++---
Mdgl/Makefile | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Mdgl/NanoVG.hpp | 51++++++++++++++++++++++++++++++++++++++++++++-------
Adgl/OpenGL-include.hpp | 112+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdgl/OpenGL.hpp | 111++++++++++++-------------------------------------------------------------------
Mdgl/StandaloneWindow.hpp | 1+
Mdgl/SubWidget.hpp | 2+-
Mdgl/TopLevelWidget.hpp | 11++++++++---
Mdgl/Widget.hpp | 103+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mdgl/Window.hpp | 189+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mdgl/src/Application.cpp | 34+++++++++++++++++++++++++++++++++-
Mdgl/src/ApplicationPrivateData.cpp | 24++++++++++++++++++++----
Mdgl/src/ApplicationPrivateData.hpp | 15+++++++++++++++
Mdgl/src/Cairo.cpp | 13++++++++++---
Mdgl/src/Color.cpp | 4++--
Mdgl/src/Geometry.cpp | 8+++++---
Mdgl/src/ImageBaseWidgets.cpp | 8++++----
Mdgl/src/NanoVG.cpp | 122++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mdgl/src/OpenGL.cpp | 131+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdgl/src/SubWidget.cpp | 19+++++++++++++++----
Mdgl/src/TopLevelWidget.cpp | 49+++++++++++++++++++++++++++++++++++--------------
Mdgl/src/TopLevelWidgetPrivateData.cpp | 34----------------------------------
Mdgl/src/TopLevelWidgetPrivateData.hpp | 3+--
Mdgl/src/Vulkan.cpp | 7+++++++
Mdgl/src/Widget.cpp | 5-----
Mdgl/src/WidgetPrivateData.cpp | 51++++++++++++---------------------------------------
Mdgl/src/WidgetPrivateData.hpp | 1-
Mdgl/src/Window.cpp | 196+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Mdgl/src/WindowPrivateData.cpp | 525+++++++++++++++++++++++++++++++++++++++----------------------------------------
Mdgl/src/WindowPrivateData.hpp | 159+++++++++++++------------------------------------------------------------------
Mdgl/src/nanovg/fontstash.h | 297+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mdgl/src/nanovg/nanovg.c | 332+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mdgl/src/nanovg/nanovg.h | 27+++++++++++++++++++--------
Mdgl/src/nanovg/nanovg_gl.h | 241+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mdgl/src/nanovg/stb_image.h | 2492+++++++++++++++++++++++--------------------------------------------------------
Mdgl/src/pugl.cpp | 625+++++++++++++++++++++++++++++++------------------------------------------------
Mdgl/src/pugl.hpp | 129++++++++++++++++++++++++-------------------------------------------------------
Ddgl/src/sofd/libsofd.c | 2472-------------------------------------------------------------------------------
Mdistrho/DistrhoInfo.hpp | 303+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mdistrho/DistrhoPlugin.hpp | 176+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mdistrho/DistrhoPluginMain.cpp | 16+++++++++++++++-
Mdistrho/DistrhoPluginUtils.hpp | 66+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Adistrho/DistrhoStandaloneUtils.hpp | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdistrho/DistrhoUI.hpp | 70+++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mdistrho/DistrhoUIMain.cpp | 13++++++++++++-
Mdistrho/DistrhoUI_macOS.mm | 43+++++++++++++++++++------------------------
Mdistrho/DistrhoUtils.hpp | 119++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mdistrho/extra/ExternalWindow.hpp | 37++++++++++++++++++++++++++++---------
Adistrho/extra/FileBrowserDialog.hpp | 28++++++++++++++++++++++++++++
Adistrho/extra/FileBrowserDialogImpl.cpp | 844+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adistrho/extra/FileBrowserDialogImpl.hpp | 121+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdistrho/extra/LeakDetector.hpp | 20+++++++++++++++++++-
Mdistrho/extra/RingBuffer.hpp | 25+++++++++++++++++++++++--
Adistrho/extra/Runner.hpp | 251+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdistrho/extra/ScopedPointer.hpp | 20+++++++++++++++++++-
Mdistrho/extra/String.hpp | 40++++++++++++++++++++++++++++++++++++++--
Mdistrho/extra/Thread.hpp | 4++++
Adistrho/extra/sofd/libsofd.c | 2482+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rdgl/src/sofd/libsofd.h -> distrho/extra/sofd/libsofd.h | 0
Mdistrho/src/DistrhoDefines.h | 79++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Mdistrho/src/DistrhoPlugin.cpp | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mdistrho/src/DistrhoPluginCarla.cpp | 44++++++++++++++++++++------------------------
Mdistrho/src/DistrhoPluginChecks.h | 52++++++++++++++++++++++++++++++++++------------------
Mdistrho/src/DistrhoPluginInternal.hpp | 244++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mdistrho/src/DistrhoPluginJACK.cpp | 306++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mdistrho/src/DistrhoPluginLADSPA+DSSI.cpp | 20+++++++++++---------
Mdistrho/src/DistrhoPluginLV2.cpp | 275+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mdistrho/src/DistrhoPluginLV2export.cpp | 422++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Adistrho/src/DistrhoPluginVST.hpp | 421+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdistrho/src/DistrhoPluginVST2.cpp | 343++++++++++++++++++++++++++++++++-----------------------------------------------
Mdistrho/src/DistrhoPluginVST3.cpp | 4928+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mdistrho/src/DistrhoUI.cpp | 152++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Mdistrho/src/DistrhoUIInternal.hpp | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mdistrho/src/DistrhoUILV2.cpp | 61+++++++++++++++++++++++++++++++++++++++++++++++++------------
Mdistrho/src/DistrhoUIPrivateData.hpp | 142++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Adistrho/src/DistrhoUIVST3.cpp | 1571+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adistrho/src/DistrhoUtils.cpp | 161+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdistrho/src/dssi/seq_event-compat.h | 10++++++++++
Mdistrho/src/jackbridge/JackBridge.cpp | 501+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mdistrho/src/jackbridge/JackBridge.hpp | 19++-----------------
Ddistrho/src/jackbridge/Makefile | 257-------------------------------------------------------------------------------
Adistrho/src/jackbridge/NativeBridge.hpp | 300+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdistrho/src/jackbridge/RtAudioBridge.hpp | 438+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Adistrho/src/jackbridge/SDL2Bridge.hpp | 250+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adistrho/src/jackbridge/WebBridge.hpp | 475+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adistrho/src/jackbridge/rtmidi/LICENSE | 27+++++++++++++++++++++++++++
Adistrho/src/jackbridge/rtmidi/RtMidi.cpp | 3930+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adistrho/src/jackbridge/rtmidi/RtMidi.h | 658+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdistrho/src/lv2/atom-forge.h | 107++++++++++++++++++++++++++-----------------------------------------------------
Ddistrho/src/lv2/atom-helpers.h | 249-------------------------------------------------------------------------------
Mdistrho/src/lv2/atom-util.h | 130+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Mdistrho/src/lv2/atom.h | 88++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Mdistrho/src/lv2/buf-size.h | 33+++++++++++++++++++++++----------
Mdistrho/src/lv2/data-access.h | 19++++++++++++-------
Mdistrho/src/lv2/dynmanifest.h | 17++++++++++++-----
Mdistrho/src/lv2/event-helpers.h | 33++++++++++++++++-----------------
Mdistrho/src/lv2/event.h | 54++++++++++++++++++++++++++----------------------------
Mdistrho/src/lv2/instance-access.h | 29++++++++++++++---------------
Mdistrho/src/lv2/log.h | 36+++++++++++++++++++++++-------------
Mdistrho/src/lv2/logger.h | 54++++++++++++++++++++++++++++++++++--------------------
Ddistrho/src/lv2/lv2-midifunctions.h | 98-------------------------------------------------------------------------------
Ddistrho/src/lv2/lv2-miditype.h | 175-------------------------------------------------------------------------------
Mdistrho/src/lv2/lv2.h | 218+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mdistrho/src/lv2/lv2_kxstudio_properties.h | 4++--
Mdistrho/src/lv2/midi.h | 106++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mdistrho/src/lv2/morph.h | 34+++++++++++++++++++++-------------
Mdistrho/src/lv2/options.h | 29+++++++++++++++++++++--------
Mdistrho/src/lv2/parameters.h | 71++++++++++++++++++++++++++++++++++++++++++-----------------------------
Mdistrho/src/lv2/patch.h | 74++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mdistrho/src/lv2/port-groups.h | 87+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mdistrho/src/lv2/port-props.h | 44+++++++++++++++++++++++++-------------------
Mdistrho/src/lv2/presets.h | 25++++++++++++++++---------
Mdistrho/src/lv2/resize-port.h | 36+++++++++++++++++++++++++-----------
Mdistrho/src/lv2/state.h | 151+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mdistrho/src/lv2/time.h | 56++++++++++++++++++++++++++++++--------------------------
Mdistrho/src/lv2/units.h | 85+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mdistrho/src/lv2/uri-map.h | 16++++++++++++----
Mdistrho/src/lv2/urid.h | 31+++++++++++++++++++------------
Mdistrho/src/lv2/worker.h | 55+++++++++++++++++++++++++++++++++++++------------------
Mdistrho/src/travesty/README.txt | 6+++---
Mdistrho/src/travesty/align_pop.h | 19+++++++++++++++++--
Mdistrho/src/travesty/align_push.h | 15+++++++++++----
Mdistrho/src/travesty/audio_processor.h | 185+++++++++++++++++++++++++++++++++++++++----------------------------------------
Mdistrho/src/travesty/base.h | 204+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Mdistrho/src/travesty/bstream.h | 21+++++++++------------
Mdistrho/src/travesty/component.h | 118+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mdistrho/src/travesty/edit_controller.h | 130+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Mdistrho/src/travesty/events.h | 56+++++++++++++++++++++++++-------------------------------
Mdistrho/src/travesty/factory.h | 88++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Adistrho/src/travesty/host.h | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Adistrho/src/travesty/message.h | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adistrho/src/travesty/unit.h | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdistrho/src/travesty/view.h | 170+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Mdpf.doxygen | 1+
Mexamples/CVPort/ExamplePluginCVPort.cpp | 6+++---
Mexamples/CVPort/Makefile | 3+--
Mexamples/CairoUI/CairoExamplePlugin.cpp | 13+++++++++++++
Mexamples/CairoUI/DistrhoPluginInfo.h | 1+
Mexamples/CairoUI/Makefile | 3++-
Mexamples/EmbedExternalUI/DistrhoPluginInfo.h | 1+
Mexamples/EmbedExternalUI/EmbedExternalExamplePlugin.cpp | 19++++++++++++++++---
Mexamples/EmbedExternalUI/EmbedExternalExampleUI.cpp | 40+++++++++++++++++++++++++++-------------
Mexamples/EmbedExternalUI/Makefile | 1+
Mexamples/ExternalUI/DistrhoPluginInfo.h | 1+
Mexamples/ExternalUI/ExternalExamplePlugin.cpp | 4++--
Mexamples/FileHandling/DistrhoPluginInfo.h | 2+-
Mexamples/FileHandling/FileHandlingPlugin.cpp | 47+++++++++++++++++++++++------------------------
Mexamples/FileHandling/Makefile | 3++-
Dexamples/ImguiSimpleGain/CParamSmooth.hpp | 41-----------------------------------------
Dexamples/ImguiSimpleGain/DistrhoPluginInfo.h | 145-------------------------------------------------------------------------------
Dexamples/ImguiSimpleGain/ImGuiSrc.cpp | 35-----------------------------------
Dexamples/ImguiSimpleGain/ImGuiUI.cpp | 314-------------------------------------------------------------------------------
Dexamples/ImguiSimpleGain/ImGuiUI.hpp | 56--------------------------------------------------------
Dexamples/ImguiSimpleGain/Makefile | 52----------------------------------------------------
Dexamples/ImguiSimpleGain/PluginSimpleGain.cpp | 161-------------------------------------------------------------------------------
Dexamples/ImguiSimpleGain/PluginSimpleGain.hpp | 161-------------------------------------------------------------------------------
Dexamples/ImguiSimpleGain/UISimpleGain.cpp | 130-------------------------------------------------------------------------------
Dexamples/ImguiSimpleGain/UISimpleGain.hpp | 55-------------------------------------------------------
Mexamples/Info/CMakeLists.txt | 2+-
Mexamples/Info/DistrhoPluginInfo.h | 1+
Mexamples/Info/InfoExamplePlugin.cpp | 17+++++++++++++++--
Mexamples/Info/Makefile | 9++++-----
Mexamples/Info/ResizeHandle.hpp | 55+++++++++++++++++++++++++++++++++++++++++++------------
Mexamples/Latency/CMakeLists.txt | 2+-
Mexamples/Latency/LatencyExamplePlugin.cpp | 17+++++++++++++++--
Mexamples/Latency/Makefile | 5+++--
Mexamples/Meters/CMakeLists.txt | 2+-
Mexamples/Meters/DistrhoPluginInfo.h | 1+
Mexamples/Meters/ExamplePluginMeters.cpp | 19++++++++++++++++---
Mexamples/Meters/Makefile | 3++-
Mexamples/Metronome/ExamplePluginMetronome.cpp | 17++++++++++++++++-
Mexamples/Metronome/Makefile | 3++-
Mexamples/MidiThrough/CMakeLists.txt | 2+-
Mexamples/MidiThrough/Makefile | 3++-
Mexamples/Parameters/CMakeLists.txt | 2+-
Mexamples/Parameters/DistrhoPluginInfo.h | 1+
Mexamples/Parameters/ExamplePluginParameters.cpp | 17+++++++++++++++--
Mexamples/SendNote/DistrhoPluginInfo.h | 1+
Mexamples/SendNote/Makefile | 7+++----
Mexamples/SendNote/SendNoteExamplePlugin.cpp | 17+++++++++++++----
Mexamples/States/CMakeLists.txt | 2+-
Mexamples/States/DistrhoPluginInfo.h | 4++++
Mexamples/States/ExamplePluginStates.cpp | 57++++++++++++++++++++++++++++++++++-----------------------
Mexamples/States/Makefile | 15++-------------
Mtests/Demo.cpp | 194+++++++++++--------------------------------------------------------------------
Mtests/FileBrowserDialog.cpp | 22++++++++++++++++------
Mtests/Makefile | 31+++++++++++++++++++++++++++----
Atests/NanoImage.cpp | 227+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtests/widgets/ExampleTextWidget.hpp | 31+++++++++++++++++++++++++++----
Atests/widgets/ResizeHandle.hpp | 207+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mutils/generate-vst-bundles.sh | 3+++
Mutils/lv2-ttl-generator/GNUmakefile | 8++++----
Mutils/lv2-ttl-generator/lv2_ttl_generator.c | 7+++++++
Mutils/package-osx-bundles.sh | 16+++++++++++++---
Autils/plugin.app/Contents/Info.plist | 20++++++++++++++++++++
Mutils/plugin.pkg/package.xml.in | 6+++++-
Mutils/plugin.vst/Contents/Info.plist | 2+-
Dutils/plugin.vst/Contents/MacOS/deleteme | 1-
Autils/symbols/dssi.def | 3+++
Autils/symbols/dssi.exp | 2++
Autils/symbols/dssi.version | 4++++
Autils/symbols/ladspa.def | 2++
Autils/symbols/ladspa.exp | 1+
Autils/symbols/ladspa.version | 4++++
Autils/symbols/lv2-dsp.def | 3+++
Autils/symbols/lv2-dsp.exp | 2++
Autils/symbols/lv2-dsp.version | 4++++
Autils/symbols/lv2-ui.def | 2++
Autils/symbols/lv2-ui.exp | 1+
Autils/symbols/lv2-ui.version | 4++++
Autils/symbols/lv2.def | 4++++
Autils/symbols/lv2.exp | 3+++
Autils/symbols/lv2.version | 4++++
Autils/symbols/shared.def | 2++
Autils/symbols/shared.exp | 1+
Autils/symbols/shared.version | 4++++
Autils/symbols/vst2.def | 3+++
Autils/symbols/vst2.exp | 1+
Autils/symbols/vst2.version | 4++++
Autils/symbols/vst3.def | 4++++
Autils/symbols/vst3.exp | 3+++
Autils/symbols/vst3.version | 4++++
Autils/valgrind-dpf.supp | 48++++++++++++++++++++++++++++++++++++++++++++++++
241 files changed, 26374 insertions(+), 10762 deletions(-)

diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml @@ -30,6 +30,7 @@ jobs: liblo-dev \ libgl-dev \ libcairo2-dev \ + libdbus-1-dev \ libx11-dev - name: Create Build Environment shell: bash @@ -55,7 +56,7 @@ jobs: path: ${{runner.workspace}}/build/bin/ cmake_macos: - runs-on: macos-10.15 + runs-on: macos-11 steps: - uses: actions/checkout@v2 with: diff --git a/.github/workflows/example-plugins.yml b/.github/workflows/example-plugins.yml @@ -26,7 +26,7 @@ jobs: echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports bionic-updates main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ports-arm64.list echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports bionic-backports main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ports-arm64.list sudo apt-get update -qq - sudo apt-get install -yq g++-aarch64-linux-gnu libasound2-dev:arm64 libcairo2-dev:arm64 libgl1-mesa-dev:arm64 liblo-dev:arm64 libpulse-dev:arm64 libx11-dev:arm64 libxcursor-dev:arm64 libxext-dev:arm64 libxrandr-dev:arm64 qemu-user-static + sudo apt-get install -yq g++-aarch64-linux-gnu libasound2-dev:arm64 libcairo2-dev:arm64 libdbus-1-dev:arm64 libgl1-mesa-dev:arm64 liblo-dev:arm64 libpulse-dev:arm64 libx11-dev:arm64 libxcursor-dev:arm64 libxext-dev:arm64 libxrandr-dev:arm64 qemu-user-static # fix broken Ubuntu packages missing pkg-config file in multi-arch package sudo apt-get install -yq libasound2-dev libgl1-mesa-dev liblo-dev libpulse-dev libxcursor-dev libxrandr-dev sudo ln -s /usr/lib/aarch64-linux-gnu/liblo.so.7 /usr/lib/aarch64-linux-gnu/liblo.so @@ -63,7 +63,7 @@ jobs: echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports bionic-updates main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ports-armhf.list echo "deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports bionic-backports main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ports-armhf.list sudo apt-get update -qq - sudo apt-get install -yq g++-arm-linux-gnueabihf libasound2-dev:armhf libcairo2-dev:armhf libgl1-mesa-dev:armhf liblo-dev:armhf libpulse-dev:armhf libx11-dev:armhf libxcursor-dev:armhf libxext-dev:armhf libxrandr-dev:armhf qemu-user-static + sudo apt-get install -yq g++-arm-linux-gnueabihf libasound2-dev:armhf libcairo2-dev:armhf libdbus-1-dev:armhf libgl1-mesa-dev:armhf liblo-dev:armhf libpulse-dev:armhf libx11-dev:armhf libxcursor-dev:armhf libxext-dev:armhf libxrandr-dev:armhf qemu-user-static # fix broken Ubuntu packages missing pkg-config file in multi-arch package sudo apt-get install -yq libasound2-dev libgl1-mesa-dev liblo-dev libpulse-dev libxcursor-dev libxrandr-dev sudo ln -s /usr/lib/arm-linux-gnueabihf/liblo.so.7 /usr/lib/arm-linux-gnueabihf/liblo.so @@ -96,7 +96,7 @@ jobs: run: | sudo dpkg --add-architecture i386 sudo apt-get update -qq - sudo apt-get install -yq g++-multilib libasound2-dev:i386 libcairo2-dev:i386 libgl1-mesa-dev:i386 liblo-dev:i386 libpulse-dev:i386 libx11-dev:i386 libxcursor-dev:i386 libxext-dev:i386 libxrandr-dev:i386 + sudo apt-get install -yq g++-multilib libasound2-dev:i386 libcairo2-dev:i386 libdbus-1-dev:i386 libgl1-mesa-dev:i386 liblo-dev:i386 libpulse-dev:i386 libx11-dev:i386 libxcursor-dev:i386 libxext-dev:i386 libxrandr-dev:i386 - name: Build linux x86 env: CFLAGS: -m32 @@ -124,7 +124,7 @@ jobs: - name: Set up dependencies run: | sudo apt-get update -qq - sudo apt-get install -yq libasound2-dev libcairo2-dev libgl1-mesa-dev liblo-dev libpulse-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev + sudo apt-get install -yq libasound2-dev libcairo2-dev libdbus-1-dev libgl1-mesa-dev liblo-dev libpulse-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev - name: Build linux x86_64 env: LDFLAGS: -static-libgcc -static-libstdc++ @@ -141,15 +141,11 @@ jobs: bin/* macos-universal: - runs-on: macos-10.15 + runs-on: macos-11 steps: - uses: actions/checkout@v2 with: submodules: recursive - - name: Fix up Xcode - run: | - sudo rm -Rf /Library/Developer/CommandLineTools/SDKs/* - sudo xcode-select -s "/Applications/Xcode_12.3.app" - name: Build macOS universal env: CFLAGS: -arch x86_64 -arch arm64 -DMAC_OS_X_VERSION_MAX_ALLOWED=MAC_OS_X_VERSION_10_12 -mmacosx-version-min=10.12 -mtune=generic -msse -msse2 @@ -157,7 +153,7 @@ jobs: LDFLAGS: -arch x86_64 -arch arm64 -mmacosx-version-min=10.12 run: | make features - make NOOPT=true -j $(sysctl -n hw.logicalcpu) + make HAVE_CAIRO=false NOOPT=true -j $(sysctl -n hw.logicalcpu) ./utils/package-osx-bundles.sh - name: Set sha8 id: slug @@ -172,9 +168,10 @@ jobs: !bin/*-dssi.dylib !bin/lv2 !bin/vst2 + !bin/vst3 win32: - runs-on: ubuntu-20.04 + runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 with: @@ -235,3 +232,100 @@ jobs: bin/* !bin/*-ladspa.dll !bin/*-dssi.dll + + plugin-validation: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Set up dependencies + run: | + # custom repos + wget https://launchpad.net/~kxstudio-debian/+archive/kxstudio/+files/kxstudio-repos_10.0.3_all.deb + sudo dpkg -i kxstudio-repos_10.0.3_all.deb + sudo apt-get update -qq + # build-deps + sudo apt-get install -yq libasound2-dev libcairo2-dev libdbus-1-dev libgl1-mesa-dev liblo-dev libpulse-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev + # runtime testing + sudo apt-get install -yq carla-git lilv-utils lv2-dev lv2lint valgrind + - name: Build plugins + env: + CFLAGS: -g + CXXFLAGS: -g -DDPF_ABORT_ON_ERROR + LDFLAGS: -static-libgcc -static-libstdc++ + run: | + make features + make NOOPT=true SKIP_STRIPPING=true -j $(nproc) + - name: Validate LV2 ttl syntax + run: | + lv2_validate \ + /usr/lib/lv2/mod.lv2/*.ttl \ + /usr/lib/lv2/kx-meta/*.ttl \ + /usr/lib/lv2/kx-control-input-port-change-request.lv2/*.ttl \ + /usr/lib/lv2/kx-programs.lv2/*.ttl \ + ./bin/*.lv2/*.ttl + - name: Validate LV2 metadata and binaries + run: | + export LV2_PATH=/tmp/lv2-path + mkdir ${LV2_PATH} + cp -r bin/*.lv2 \ + /usr/lib/lv2/{atom,buf-size,core,data-access,kx-control-input-port-change-request,kx-programs,instance-access,midi,parameters,port-groups,port-props,options,patch,presets,resize-port,state,time,ui,units,urid,worker}.lv2 \ + ${LV2_PATH} + lv2lint -s lv2_generate_ttl -l ld-linux-x86-64.so.2 -M nopack $(lv2ls) + - name: Test LADSPA plugins + run: | + for p in $(ls bin/ | grep ladspa.so); do \ + env CARLA_BRIDGE_DUMMY=1 CARLA_BRIDGE_TESTING=native \ + valgrind \ + --error-exitcode=255 \ + --leak-check=full \ + --track-origins=yes \ + --suppressions=./utils/valgrind-dpf.supp \ + /usr/lib/carla/carla-bridge-native ladspa ./bin/${p} "" 1>/dev/null; \ + done + - name: Test DSSI plugins + run: | + for p in $(ls bin/ | grep dssi.so); do \ + env CARLA_BRIDGE_DUMMY=1 CARLA_BRIDGE_TESTING=native \ + valgrind \ + --error-exitcode=255 \ + --leak-check=full \ + --track-origins=yes \ + --suppressions=./utils/valgrind-dpf.supp \ + /usr/lib/carla/carla-bridge-native dssi ./bin/${p} "" 1>/dev/null; \ + done + - name: Test LV2 plugins + run: | + export LV2_PATH=/tmp/lv2-path + for p in $(lv2ls); do \ + env CARLA_BRIDGE_DUMMY=1 CARLA_BRIDGE_TESTING=native \ + valgrind \ + --error-exitcode=255 \ + --leak-check=full \ + --track-origins=yes \ + --suppressions=./utils/valgrind-dpf.supp \ + /usr/lib/carla/carla-bridge-native lv2 "" ${p} 1>/dev/null; \ + done + - name: Test VST2 plugins + run: | + for p in $(ls bin/ | grep vst.so); do \ + env CARLA_BRIDGE_DUMMY=1 CARLA_BRIDGE_TESTING=native \ + valgrind \ + --error-exitcode=255 \ + --leak-check=full \ + --track-origins=yes \ + --suppressions=./utils/valgrind-dpf.supp \ + /usr/lib/carla/carla-bridge-native vst2 ./bin/${p} "" 1>/dev/null; \ + done + - name: Test VST3 plugins + run: | + for p in $(ls bin/ | grep vst3); do \ + env CARLA_BRIDGE_DUMMY=1 CARLA_BRIDGE_TESTING=native \ + valgrind \ + --error-exitcode=255 \ + --leak-check=full \ + --track-origins=yes \ + --suppressions=./utils/valgrind-dpf.supp \ + /usr/lib/carla/carla-bridge-native vst3 ./bin/${p} "" 1>/dev/null; \ + done diff --git a/.github/workflows/irc.yml b/.github/workflows/irc.yml @@ -0,0 +1,20 @@ +name: irc + +on: [push] + +jobs: + notification: + runs-on: ubuntu-latest + name: IRC notification + steps: + - name: Format message + id: message + run: | + message="${{ github.actor }} pushed $(echo '${{ github.event.commits[0].message }}' | head -n 1) ${{ github.event.commits[0].url }}" + echo ::set-output name=message::"${message}" + - name: IRC notification + uses: Gottox/irc-message-action@v2 + with: + channel: '#kxstudio' + nickname: kxstudio-bot + message: ${{ steps.message.outputs.message }} diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml @@ -20,7 +20,7 @@ jobs: - name: Set up dependencies run: | sudo apt-get update -qq - sudo apt-get install -yq libasound2-dev libcairo2-dev libgl1-mesa-dev liblo-dev libpulse-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev xvfb + sudo apt-get install -yq libasound2-dev libcairo2-dev libdbus-1-dev libgl1-mesa-dev liblo-dev libpulse-dev libx11-dev libxcursor-dev libxext-dev libxrandr-dev xvfb - name: Without any warnings env: CFLAGS: -Werror @@ -40,7 +40,7 @@ jobs: CXXFLAGS: -Werror -std=gnu++98 run: | make clean >/dev/null - make -j $(nproc) VST3_FILENAME= + make -j $(nproc) - name: No namespace env: CFLAGS: -Werror diff --git a/.gitignore b/.gitignore @@ -17,3 +17,4 @@ bin/ build/ docs/ utils/lv2_ttl_generator +utils/lv2_ttl_generator.dSYM/ diff --git a/FEATURES.md b/FEATURES.md @@ -0,0 +1,93 @@ +# DPF - DISTRHO Plugin Framework + +This file describes the available features for each plugin format. +The limitations could be due to the plugin format itself or within DPF. +If the limitation is within DPF, a link is provided to a description below on the reason for it. + +| Feature | JACK/Standalone | LADSPA | DSSI | LV2 | VST2 | VST3 | Feature | +|---------------------|---------------------------------------|-------------------------|---------------------|-------------------------------|--------------------------------|----------------------------------|---------------------| +| Audio port groups | [Yes*](#jack-audio-port-groups) | No | No | Yes | No | [No*](#vst3-is-work-in-progress) | Audio port groups | +| Audio port as CV | Yes | No | No | Yes | No | [No*](#vst3-is-work-in-progress) | Audio port as CV | +| Audio sidechan | Yes | No | No | Yes | [No*](#vst2-potential-support) | [No*](#vst3-is-work-in-progress) | Audio sidechan | +| Bypass control | No | No | No | Yes | [No*](#vst2-potential-support) | [No*](#vst3-is-work-in-progress) | Bypass control | +| MIDI input | Yes | No | Yes | Yes | Yes | Yes | MIDI input | +| MIDI output | Yes | No | No | Yes | Yes | Yes | MIDI output | +| Parameter changes | Yes | No | No | [No*](#lv2-parameter-changes) | Yes | Yes | Parameter changes | +| Parameter groups | No | No | No | Yes | Yes | [No*](#vst3-is-work-in-progress) | Parameter groups | +| Parameter outputs | No | No | No | Yes | No | [No*](#vst3-is-work-in-progress) | Parameter outputs | +| Parameter triggers | Yes | No | No | Yes | [No*](#parameter-triggers) | [No*](#parameter-triggers) | Parameter triggers | +| Programs | [Yes*](#jack-parameters-and-programs) | [No*](#ladspa-programs) | [Yes*](#dssi-state) | Yes | [No*](#vst2-programs) | Yes | Programs | +| States | Yes | No | [Yes*](#dssi-state) | Yes | Yes | Yes | States | +| Full/internal state | Yes | No | No | Yes | Yes | Yes | Full/internal state | +| Time position | Yes | No | No | Yes | Yes | Yes | Time position | +| UI | [Yes*](#jack-custom-ui-only) | No | External only | Yes | Embed only | Embed only | UI | +| UI bg/fg colors | No | No | No | Yes | No | No? | UI bg/fg colors | +| UI direct access | Yes | No | No | Yes | Yes | Yes | UI direct access | +| UI host-filebrowser | No | No | No | Yes | [No*](#vst2-potential-support) | [No*](#vst3-is-work-in-progress) | UI host-filebrowser | +| UI host-resize | Yes | No | Yes | Yes | No | [No*](#vst3-is-work-in-progress) | UI host-resize | +| UI remote control | No | No | Yes | Yes | No | Yes | UI remote control | +| UI send midi note | Yes | No | Yes | Yes | Yes | Yes | UI send midi note | + +For things that could be unclear: + +- "States" refers to DPF API support, supporting key-value string pairs for internal state saving +- "Full state" refers to plugins updating their state internally without outside intervention (like host or UI) +- "UI direct access" means `DISTRHO_PLUGIN_WANT_DIRECT_ACCESS` is possible, that is, running DSP and UI on the same process +- "UI remote control" means running the UI on a separate machine (for example over the network) +- An external UI on this table means that it cannot be embed into the host window, but the plugin can still provide one + +# Special notes + +## Parameter triggers + +Trigger-style parameters (parameters which value is reset to its default every run) are only supported in JACK and LV2. +For all other plugin formats DPF will simulate the behaviour through a parameter change request. + +## JACK audio port groups + +DPF will set JACK metadata information for grouping audio ports. +This is not supported by most JACK applications at the moment. + +## JACK parameters and programs + +Under JACK/Stanlone mode, MIDI input events will trigger program and parameter changes. +MIDI program change events work as expected (that is, MIDI program change 0 will load 1st plugin program). +MIDI CCs are used for parameter changes (matching the `midiCC` value you set on each parameter). + +## JACK custom UI only + +There is no generic plugin editor view. +If your plugin has no custom UI, the standalone executable will run but not show any window. + +## LADSPA programs + +Programs for LADSPA could be done via LRDF but this is not supported in DPF. + +## DSSI State + +DSSI only supports state changes when called via UI, no "full state" possible. +This also makes it impossibe to use programs and state at the same time with DSSI, +because in DPF changing programs can lead to state changes but there is no way to fetch this information on DSSI plugins. + +To make it simpler to understand, think of DSSI programs and states as UI properties. +Because in DPF changing the state happens from UI to DSP side, regular DSSI can be supported. +But if we involve programs, they would need to pass through the UI in order to work. Which goes against DPF's design. + +## LV2 parameter changes + +Although this is already implemented in DPF (through a custom extension), this is not implemented on most hosts. +So for now you can pretty much treat it as if not supported. + +## VST2 potential support + +Not supported in DPF at the moment. +It could eventually be, but likely not due to VST2 being phased out by Steinberg. +Contact DPF authors if you require such a feature. + +## VST2 programs + +VST2 program support requires saving state of all programs in memory, which is very expensive and thus not done in DPF. + +## VST3 is work in progress + +Feature is possible, just not implemented yet in DPF. diff --git a/LICENSING.md b/LICENSING.md @@ -0,0 +1,47 @@ +# DPF - DISTRHO Plugin Framework + +Even though DPF is quite liberally licensed, not all plugin formats follow the same ideals. +This is usually due to plugin APIs/headers being tied to a specific license or having commercial restrictions. +This file describes the licensing that applies to each individual plugin format as a way to make it clear what is possible and compatible. +Note that if you are making GPLv2+ licensed plugins this does not apply to you, as so far everything is GPLv2+ compatible. + +Regardless of target format, DPF itself needs to be mentioned in attribution. +See the [LICENSE](LICENSE) file for copyright details. + +| Target | License(s) | License restrictions | Additional attribution | +|-----------------|----------------------|-----------------------|------------------------| +| JACK/Standalone | MIT (RtAudio) | Copyright attribution | **RtAudio**: 2001-2019 Gary P. Scavone | +| LADSPA | LGPLv2.1+ | ??? (*) | 2000-2002 Richard W. E. Furse, Paul Barton-Davis, Stefan Westerfeld | +| DSSI | LGPLv2.1+ | ??? (*) | **DSSI**: 2004, 2009 Chris Cannam, Steve Harris and Sean Bolton;<br/> **ALSA**: 1998-2001 Jaroslav Kysela, Abramo Bagnara, Takashi Iwai | +| LV2 | ISC | Copyright attribution | 2006-2020 Steve Harris, David Robillard;<br/> 2000-2002 Richard W.E. Furse, Paul Barton-Davis, Stefan Westerfeld | +| VST2 | GPLv2+ or commercial | Must be GPLv2+ compatible or alternatively use Steinberg VST2 SDK (no longer available for new plugins) | GPLv2+ compatible license or custom agreement with Steinberg | +| VST3 | ISC | Copyright attribution | (none, only DPF files used) | + +### LADSPA and DSSI special note + +The header files on LADSPA and DSSI are LGPLv2.1+ licensed, which is unusual for pure APIs without libraries. +LADSPA authors mention this on ladspa.org homepage: + +> LADSPA has been released under LGPL (GNU Lesser General Public License). +> This is not intended to be the final license for LADSPA. +> In the long term it is hoped that LADSPA will have a public license that is even less restrictive, so that commercial applications can use it (in a protected way) without having to use a derived LGPL library. +> It may be that LGPL is already free enough for this, but we aren't sure. + +So the situation for LADSPA/DSSI plugins is unclear for commercial plugins. +These formats are very limited and not much used anymore anyway, feel free to skip them if this situation is a potential issue for you. + +### VST2 special note + +By default DPF uses the free reverse-engineered [vestige header](distrho/src/vestige/vestige.h) file. +This file is GPLv2+ licensed, so that applies to plugins built with it as well. +You can alternatively build DPF-based VST2 plugins using the official Steinberg VST2 SDK, +simply set the `VESTIGE_HEADER` compiler macro to `0` during build. +You will need to provide your own VST2 SDK files then, as DPF does not ship with them. +Note there are legal issues surrounding releasing new VST2 plugins using the official SDK, as that is no longer supported by Steinberg. + +### VST3 special note + +Contrary to most plugins, DPF does not use the official VST3 SDK. +Instead, the API definitions are provided by the [travesty](distrho/src/travesty/) sub-project, licensed in the same way as DPF. +This allows us to freely build plugins without being encumbered by restrictive licensing deals. +It makes the internal implementation much harder for DPF, but this is not an issue for external developers. diff --git a/Makefile b/Makefile @@ -21,7 +21,6 @@ dgl: examples: dgl $(MAKE) all -C examples/CVPort - $(MAKE) all -C examples/EmbedExternalUI $(MAKE) all -C examples/FileHandling $(MAKE) all -C examples/Info $(MAKE) all -C examples/Latency @@ -34,13 +33,13 @@ examples: dgl ifeq ($(HAVE_CAIRO),true) $(MAKE) all -C examples/CairoUI endif +ifeq ($(HAVE_DGL),true) + $(MAKE) all -C examples/EmbedExternalUI +endif ifeq ($(CAN_GENERATE_TTL),true) gen: examples utils/lv2_ttl_generator @$(CURDIR)/utils/generate-ttl.sh -ifeq ($(MACOS),true) - @$(CURDIR)/utils/generate-vst-bundles.sh -endif utils/lv2_ttl_generator: $(MAKE) -C utils/lv2-ttl-generator diff --git a/Makefile.base.mk b/Makefile.base.mk @@ -25,35 +25,36 @@ ifneq ($(HAIKU),true) ifneq ($(HURD),true) ifneq ($(LINUX),true) ifneq ($(MACOS),true) +ifneq ($(WASM),true) ifneq ($(WINDOWS),true) ifneq (,$(findstring bsd,$(TARGET_MACHINE))) -BSD=true -endif -ifneq (,$(findstring haiku,$(TARGET_MACHINE))) -HAIKU=true -endif -ifneq (,$(findstring linux,$(TARGET_MACHINE))) -LINUX=true +BSD = true +else ifneq (,$(findstring haiku,$(TARGET_MACHINE))) +HAIKU = true +else ifneq (,$(findstring linux,$(TARGET_MACHINE))) +LINUX = true else ifneq (,$(findstring gnu,$(TARGET_MACHINE))) -HURD=true -endif -ifneq (,$(findstring apple,$(TARGET_MACHINE))) -MACOS=true -endif -ifneq (,$(findstring mingw,$(TARGET_MACHINE))) -WINDOWS=true -endif -ifneq (,$(findstring windows,$(TARGET_MACHINE))) -WINDOWS=true -endif - -endif -endif -endif -endif -endif -endif +HURD = true +else ifneq (,$(findstring apple,$(TARGET_MACHINE))) +MACOS = true +else ifneq (,$(findstring mingw,$(TARGET_MACHINE))) +WINDOWS = true +else ifneq (,$(findstring msys,$(TARGET_MACHINE))) +WINDOWS = true +else ifneq (,$(findstring wasm,$(TARGET_MACHINE))) +WASM = true +else ifneq (,$(findstring windows,$(TARGET_MACHINE))) +WINDOWS = true +endif + +endif # WINDOWS +endif # WASM +endif # MACOS +endif # LINUX +endif # HURD +endif # HAIKU +endif # BSD # --------------------------------------------------------------------------------------------------------------------- # Auto-detect the processor @@ -61,30 +62,37 @@ endif TARGET_PROCESSOR := $(firstword $(subst -, ,$(TARGET_MACHINE))) ifneq (,$(filter i%86,$(TARGET_PROCESSOR))) -CPU_I386=true -CPU_I386_OR_X86_64=true +CPU_I386 = true +CPU_I386_OR_X86_64 = true +endif +ifneq (,$(filter wasm32,$(TARGET_PROCESSOR))) +CPU_I386 = true +CPU_I386_OR_X86_64 = true endif ifneq (,$(filter x86_64,$(TARGET_PROCESSOR))) -CPU_X86_64=true -CPU_I386_OR_X86_64=true +CPU_X86_64 = true +CPU_I386_OR_X86_64 = true endif ifneq (,$(filter arm%,$(TARGET_PROCESSOR))) -CPU_ARM=true -CPU_ARM_OR_AARCH64=true +CPU_ARM = true +CPU_ARM_OR_AARCH64 = true endif ifneq (,$(filter arm64%,$(TARGET_PROCESSOR))) -CPU_ARM64=true -CPU_ARM_OR_AARCH64=true +CPU_ARM64 = true +CPU_ARM_OR_AARCH64 = true endif ifneq (,$(filter aarch64%,$(TARGET_PROCESSOR))) -CPU_AARCH64=true -CPU_ARM_OR_AARCH64=true +CPU_AARCH64 = true +CPU_ARM_OR_AARCH64 = true endif # --------------------------------------------------------------------------------------------------------------------- # Set PKG_CONFIG (can be overridden by environment variable) -ifeq ($(WINDOWS),true) +ifeq ($(WASM),true) +# Skip on wasm by default +PKG_CONFIG ?= false +else ifeq ($(WINDOWS),true) # Build statically on Windows by default PKG_CONFIG ?= pkg-config --static else @@ -92,50 +100,67 @@ PKG_CONFIG ?= pkg-config endif # --------------------------------------------------------------------------------------------------------------------- +# Set cross compiling flag + +ifeq ($(WASM),true) +CROSS_COMPILING = true +endif + +# --------------------------------------------------------------------------------------------------------------------- # Set LINUX_OR_MACOS ifeq ($(LINUX),true) -LINUX_OR_MACOS=true +LINUX_OR_MACOS = true endif ifeq ($(MACOS),true) -LINUX_OR_MACOS=true +LINUX_OR_MACOS = true endif # --------------------------------------------------------------------------------------------------------------------- -# Set MACOS_OR_WINDOWS and HAIKU_OR_MACOS_OR_WINDOWS +# Set MACOS_OR_WINDOWS, MACOS_OR_WASM_OR_WINDOWS, HAIKU_OR_MACOS_OR_WINDOWS and HAIKU_OR_MACOS_OR_WASM_OR_WINDOWS ifeq ($(HAIKU),true) -HAIKU_OR_MACOS_OR_WINDOWS=true +HAIKU_OR_MACOS_OR_WASM_OR_WINDOWS = true +HAIKU_OR_MACOS_OR_WINDOWS = true endif ifeq ($(MACOS),true) -MACOS_OR_WINDOWS=true -HAIKU_OR_MACOS_OR_WINDOWS=true +HAIKU_OR_MACOS_OR_WASM_OR_WINDOWS = true +HAIKU_OR_MACOS_OR_WINDOWS = true +MACOS_OR_WASM_OR_WINDOWS = true +MACOS_OR_WINDOWS = true +endif + +ifeq ($(WASM),true) +HAIKU_OR_MACOS_OR_WASM_OR_WINDOWS = true +MACOS_OR_WASM_OR_WINDOWS = true endif ifeq ($(WINDOWS),true) -MACOS_OR_WINDOWS=true -HAIKU_OR_MACOS_OR_WINDOWS=true +HAIKU_OR_MACOS_OR_WASM_OR_WINDOWS = true +HAIKU_OR_MACOS_OR_WINDOWS = true +MACOS_OR_WASM_OR_WINDOWS = true +MACOS_OR_WINDOWS = true endif # --------------------------------------------------------------------------------------------------------------------- # Set UNIX ifeq ($(BSD),true) -UNIX=true +UNIX = true endif ifeq ($(HURD),true) -UNIX=true +UNIX = true endif ifeq ($(LINUX),true) -UNIX=true +UNIX = true endif ifeq ($(MACOS),true) -UNIX=true +UNIX = true endif # --------------------------------------------------------------------------------------------------------------------- @@ -145,7 +170,12 @@ BASE_FLAGS = -Wall -Wextra -pipe -MD -MP BASE_OPTS = -O3 -ffast-math -fdata-sections -ffunction-sections ifeq ($(CPU_I386_OR_X86_64),true) -BASE_OPTS += -mtune=generic -msse -msse2 -mfpmath=sse +BASE_OPTS += -mtune=generic +ifeq ($(WASM),true) +BASE_OPTS += -msse -msse2 -msse3 -msimd128 +else +BASE_OPTS += -msse -msse2 -mfpmath=sse +endif endif ifeq ($(CPU_ARM),true) @@ -155,27 +185,49 @@ endif endif ifeq ($(MACOS),true) + # MacOS linker flags -LINK_OPTS = -fdata-sections -ffunction-sections -Wl,-dead_strip -Wl,-dead_strip_dylibs +LINK_OPTS = -fdata-sections -ffunction-sections -Wl,-dead_strip,-dead_strip_dylibs ifneq ($(SKIP_STRIPPING),true) LINK_OPTS += -Wl,-x endif + else + # Common linker flags -LINK_OPTS = -fdata-sections -ffunction-sections -Wl,--gc-sections -Wl,-O1 -Wl,--as-needed +LINK_OPTS = -fdata-sections -ffunction-sections -Wl,-O1,--gc-sections +ifeq ($(WASM),true) +LINK_OPTS += -O3 +LINK_OPTS += -sAGGRESSIVE_VARIABLE_ELIMINATION=1 +else +LINK_OPTS += -Wl,--as-needed ifneq ($(SKIP_STRIPPING),true) LINK_OPTS += -Wl,--strip-all endif endif +endif + +ifeq ($(SKIP_STRIPPING),true) +BASE_FLAGS += -g +endif + ifeq ($(NOOPT),true) # Non-CPU-specific optimization flags BASE_OPTS = -O2 -ffast-math -fdata-sections -ffunction-sections endif +ifneq ($(MACOS_OR_WASM_OR_WINDOWS),true) +ifneq ($(BSD),true) +BASE_FLAGS += -fno-gnu-unique +endif +endif + ifeq ($(WINDOWS),true) +# Assume we want posix +BASE_FLAGS += -posix -D__STDC_FORMAT_MACROS=1 -D__USE_MINGW_ANSI_STDIO=1 # Needed for windows, see https://github.com/falkTX/Carla/issues/855 -BASE_OPTS += -mstackrealign +BASE_FLAGS += -mstackrealign else # Not needed for Windows BASE_FLAGS += -fPIC -DPIC @@ -184,24 +236,50 @@ endif ifeq ($(DEBUG),true) BASE_FLAGS += -DDEBUG -O0 -g LINK_OPTS = +ifeq ($(WASM),true) +LINK_OPTS += -sASSERTIONS=1 +endif else BASE_FLAGS += -DNDEBUG $(BASE_OPTS) -fvisibility=hidden CXXFLAGS += -fvisibility-inlines-hidden endif +ifeq ($(STATIC_BUILD),true) +BASE_FLAGS += -DSTATIC_BUILD +# LINK_OPTS += -static +endif + +ifeq ($(WITH_LTO),true) +BASE_FLAGS += -fno-strict-aliasing -flto +LINK_OPTS += -fno-strict-aliasing -flto -Werror=odr -Werror=lto-type-mismatch +endif + BUILD_C_FLAGS = $(BASE_FLAGS) -std=gnu99 $(CFLAGS) BUILD_CXX_FLAGS = $(BASE_FLAGS) -std=gnu++11 $(CXXFLAGS) LINK_FLAGS = $(LINK_OPTS) $(LDFLAGS) -ifneq ($(MACOS),true) +ifeq ($(WASM),true) +# Special flag for emscripten +LINK_FLAGS += -sENVIRONMENT=web -sLLD_REPORT_UNDEFINED +else ifneq ($(MACOS),true) # Not available on MacOS -LINK_FLAGS += -Wl,--no-undefined +LINK_FLAGS += -Wl,--no-undefined endif ifeq ($(MACOS_OLD),true) BUILD_CXX_FLAGS = $(BASE_FLAGS) $(CXXFLAGS) -DHAVE_CPP11_SUPPORT=0 endif +ifeq ($(WASM_CLIPBOARD),true) +BUILD_CXX_FLAGS += -DPUGL_WASM_ASYNC_CLIPBOARD +LINK_FLAGS += -sASYNCIFY -sASYNCIFY_IMPORTS=puglGetAsyncClipboardData +endif + +ifeq ($(WASM_EXCEPTIONS),true) +BUILD_CXX_FLAGS += -fexceptions +LINK_FLAGS += -fexceptions +endif + ifeq ($(WINDOWS),true) # Always build statically on windows LINK_FLAGS += -static -static-libgcc -static-libstdc++ @@ -235,33 +313,36 @@ endif HAVE_CAIRO = $(shell $(PKG_CONFIG) --exists cairo && echo true) -# Vulkan is not supported yet -# HAVE_VULKAN = $(shell $(PKG_CONFIG) --exists vulkan && echo true) - -ifeq ($(MACOS_OR_WINDOWS),true) +ifeq ($(MACOS_OR_WASM_OR_WINDOWS),true) HAVE_OPENGL = true else -HAVE_OPENGL = $(shell $(PKG_CONFIG) --exists gl && echo true) -ifneq ($(HAIKU),true) +HAVE_OPENGL = $(shell $(PKG_CONFIG) --exists gl && echo true) +HAVE_DBUS = $(shell $(PKG_CONFIG) --exists dbus-1 && echo true) HAVE_X11 = $(shell $(PKG_CONFIG) --exists x11 && echo true) HAVE_XCURSOR = $(shell $(PKG_CONFIG) --exists xcursor && echo true) HAVE_XEXT = $(shell $(PKG_CONFIG) --exists xext && echo true) HAVE_XRANDR = $(shell $(PKG_CONFIG) --exists xrandr && echo true) endif -endif + +# Vulkan is not supported yet +# HAVE_VULKAN = $(shell $(PKG_CONFIG) --exists vulkan && echo true) # --------------------------------------------------------------------------------------------------------------------- # Check for optional libraries HAVE_LIBLO = $(shell $(PKG_CONFIG) --exists liblo && echo true) +ifneq ($(SKIP_NATIVE_AUDIO_FALLBACK),true) +ifneq ($(SKIP_RTAUDIO_FALLBACK),true) + ifeq ($(MACOS),true) HAVE_RTAUDIO = true else ifeq ($(WINDOWS),true) HAVE_RTAUDIO = true -else ifneq ($(HAIKU),true) +else HAVE_ALSA = $(shell $(PKG_CONFIG) --exists alsa && echo true) HAVE_PULSEAUDIO = $(shell $(PKG_CONFIG) --exists libpulse-simple && echo true) +HAVE_SDL2 = $(shell $(PKG_CONFIG) --exists sdl2 && echo true) ifeq ($(HAVE_ALSA),true) HAVE_RTAUDIO = true else ifeq ($(HAVE_PULSEAUDIO),true) @@ -269,31 +350,39 @@ HAVE_RTAUDIO = true endif endif -# backwards compat +endif +endif + +# backwards compat, always available/enabled +ifneq ($(FORCE_NATIVE_AUDIO_FALLBACK),true) +ifeq ($(STATIC_BUILD),true) +HAVE_JACK = $(shell $(PKG_CONFIG) --exists jack && echo true) +else HAVE_JACK = true +endif +endif # --------------------------------------------------------------------------------------------------------------------- # Set Generic DGL stuff ifeq ($(HAIKU),true) DGL_SYSTEM_LIBS += -lbe -endif - -ifeq ($(MACOS),true) +else ifeq ($(MACOS),true) DGL_SYSTEM_LIBS += -framework Cocoa -framework CoreVideo -endif - -ifeq ($(WINDOWS),true) +else ifeq ($(WASM),true) +else ifeq ($(WINDOWS),true) DGL_SYSTEM_LIBS += -lgdi32 -lcomdlg32 +# -lole32 +else +ifeq ($(HAVE_DBUS),true) +DGL_FLAGS += $(shell $(PKG_CONFIG) --cflags dbus-1) -DHAVE_DBUS +DGL_SYSTEM_LIBS += $(shell $(PKG_CONFIG) --libs dbus-1) endif - -ifneq ($(HAIKU_OR_MACOS_OR_WINDOWS),true) ifeq ($(HAVE_X11),true) DGL_FLAGS += $(shell $(PKG_CONFIG) --cflags x11) -DHAVE_X11 DGL_SYSTEM_LIBS += $(shell $(PKG_CONFIG) --libs x11) ifeq ($(HAVE_XCURSOR),true) -# TODO -DHAVE_XCURSOR -DGL_FLAGS += $(shell $(PKG_CONFIG) --cflags xcursor) +DGL_FLAGS += $(shell $(PKG_CONFIG) --cflags xcursor) -DHAVE_XCURSOR DGL_SYSTEM_LIBS += $(shell $(PKG_CONFIG) --libs xcursor) endif ifeq ($(HAVE_XEXT),true) @@ -331,18 +420,20 @@ DGL_FLAGS += -DHAVE_OPENGL ifeq ($(HAIKU),true) OPENGL_FLAGS = $(shell $(PKG_CONFIG) --cflags gl) OPENGL_LIBS = $(shell $(PKG_CONFIG) --libs gl) -endif - -ifeq ($(MACOS),true) +else ifeq ($(MACOS),true) OPENGL_FLAGS = -DGL_SILENCE_DEPRECATION=1 -Wno-deprecated-declarations OPENGL_LIBS = -framework OpenGL +else ifeq ($(WASM),true) +ifeq ($(USE_GLES2),true) +OPENGL_LIBS = -sMIN_WEBGL_VERSION=2 -sMAX_WEBGL_VERSION=2 +else +ifneq ($(USE_GLES3),true) +OPENGL_LIBS = -sLEGACY_GL_EMULATION -sGL_UNSAFE_OPTS=0 endif - -ifeq ($(WINDOWS),true) -OPENGL_LIBS = -lopengl32 endif - -ifneq ($(HAIKU_OR_MACOS_OR_WINDOWS),true) +else ifeq ($(WINDOWS),true) +OPENGL_LIBS = -lopengl32 +else OPENGL_FLAGS = $(shell $(PKG_CONFIG) --cflags gl x11) OPENGL_LIBS = $(shell $(PKG_CONFIG) --libs gl x11) endif @@ -354,7 +445,7 @@ endif # --------------------------------------------------------------------------------------------------------------------- # Set Stub specific stuff -ifeq ($(HAIKU_OR_MACOS_OR_WINDOWS),true) +ifeq ($(MACOS_OR_WASM_OR_WINDOWS),true) HAVE_STUB = true else HAVE_STUB = $(HAVE_X11) @@ -394,41 +485,105 @@ PULSEAUDIO_FLAGS = $(shell $(PKG_CONFIG) --cflags libpulse-simple) PULSEAUDIO_LIBS = $(shell $(PKG_CONFIG) --libs libpulse-simple) endif -ifneq ($(HAIKU_OR_MACOS_OR_WINDOWS),true) +ifeq ($(HAVE_SDL2),true) +SDL2_FLAGS = $(shell $(PKG_CONFIG) --cflags sdl2) +SDL2_LIBS = $(shell $(PKG_CONFIG) --libs sdl2) +endif + +ifeq ($(HAVE_JACK),true) +ifeq ($(STATIC_BUILD),true) +JACK_FLAGS = $(shell $(PKG_CONFIG) --cflags jack) +JACK_LIBS = $(shell $(PKG_CONFIG) --libs jack) +endif +endif + +ifneq ($(HAIKU_OR_MACOS_OR_WASM_OR_WINDOWS),true) SHARED_MEMORY_LIBS = -lrt endif # --------------------------------------------------------------------------------------------------------------------- # Backwards-compatible HAVE_DGL -ifeq ($(MACOS_OR_WINDOWS),true) +ifeq ($(MACOS_OR_WASM_OR_WINDOWS),true) HAVE_DGL = true else ifeq ($(HAVE_OPENGL),true) -ifeq ($(HAIKU),true) -HAVE_DGL = true -else HAVE_DGL = $(HAVE_X11) endif + +# --------------------------------------------------------------------------------------------------------------------- +# Namespace flags + +ifneq ($(DISTRHO_NAMESPACE),) +BUILD_CXX_FLAGS += -DDISTRHO_NAMESPACE=$(DISTRHO_NAMESPACE) +endif + +ifneq ($(DGL_NAMESPACE),) +BUILD_CXX_FLAGS += -DDGL_NAMESPACE=$(DGL_NAMESPACE) +endif + +# --------------------------------------------------------------------------------------------------------------------- +# Optional flags + +ifeq ($(NVG_DISABLE_SKIPPING_WHITESPACE),true) +BUILD_CXX_FLAGS += -DNVG_DISABLE_SKIPPING_WHITESPACE +endif + +ifneq ($(NVG_FONT_TEXTURE_FLAGS),) +BUILD_CXX_FLAGS += -DNVG_FONT_TEXTURE_FLAGS=$(NVG_FONT_TEXTURE_FLAGS) +endif + +ifeq ($(FILE_BROWSER_DISABLED),true) +BUILD_CXX_FLAGS += -DDGL_FILE_BROWSER_DISABLED +endif + +ifneq ($(WINDOWS_ICON_ID),) +BUILD_CXX_FLAGS += -DDGL_WINDOWS_ICON_ID=$(WINDOWS_ICON_ID) +endif + +ifeq ($(USE_GLES2),true) +BUILD_CXX_FLAGS += -DDGL_USE_GLES -DDGL_USE_GLES2 +endif + +ifeq ($(USE_GLES3),true) +BUILD_CXX_FLAGS += -DDGL_USE_GLES -DDGL_USE_GLES3 +endif + +ifeq ($(USE_OPENGL3),true) +BUILD_CXX_FLAGS += -DDGL_USE_OPENGL3 +endif + +ifeq ($(USE_NANOVG_FBO),true) +BUILD_CXX_FLAGS += -DDGL_USE_NANOVG_FBO +endif + +ifeq ($(USE_NANOVG_FREETYPE),true) +BUILD_CXX_FLAGS += -DFONS_USE_FREETYPE $(shell $(PKG_CONFIG) --cflags freetype2) +endif + +ifeq ($(USE_RGBA),true) +BUILD_CXX_FLAGS += -DDGL_USE_RGBA endif # --------------------------------------------------------------------------------------------------------------------- # Set app extension -ifeq ($(WINDOWS),true) +ifeq ($(WASM),true) +APP_EXT = .html +else ifeq ($(WINDOWS),true) APP_EXT = .exe endif # --------------------------------------------------------------------------------------------------------------------- # Set shared lib extension -LIB_EXT = .so - ifeq ($(MACOS),true) LIB_EXT = .dylib -endif - -ifeq ($(WINDOWS),true) +else ifeq ($(WASM),true) +LIB_EXT = .wasm +else ifeq ($(WINDOWS),true) LIB_EXT = .dll +else +LIB_EXT = .so endif # --------------------------------------------------------------------------------------------------------------------- @@ -436,6 +591,8 @@ endif ifeq ($(MACOS),true) SHARED = -dynamiclib +else ifeq ($(WASM),true) +SHARED = -sSIDE_MODULE=2 else SHARED = -shared endif @@ -443,8 +600,12 @@ endif # --------------------------------------------------------------------------------------------------------------------- # Handle the verbosity switch -ifeq ($(VERBOSE),true) SILENT = + +ifeq ($(VERBOSE),1) +else ifeq ($(VERBOSE),y) +else ifeq ($(VERBOSE),yes) +else ifeq ($(VERBOSE),true) else SILENT = @ endif @@ -473,19 +634,24 @@ features: $(call print_available,HURD) $(call print_available,LINUX) $(call print_available,MACOS) + $(call print_available,WASM) $(call print_available,WINDOWS) + $(call print_available,HAIKU_OR_MACOS_OR_WASM_OR_WINDOWS) $(call print_available,HAIKU_OR_MACOS_OR_WINDOWS) $(call print_available,LINUX_OR_MACOS) + $(call print_available,MACOS_OR_WASM_OR_WINDOWS) $(call print_available,MACOS_OR_WINDOWS) $(call print_available,UNIX) @echo === Detected features $(call print_available,HAVE_ALSA) + $(call print_available,HAVE_DBUS) $(call print_available,HAVE_CAIRO) $(call print_available,HAVE_DGL) $(call print_available,HAVE_LIBLO) $(call print_available,HAVE_OPENGL) $(call print_available,HAVE_PULSEAUDIO) $(call print_available,HAVE_RTAUDIO) + $(call print_available,HAVE_SDL2) $(call print_available,HAVE_STUB) $(call print_available,HAVE_VULKAN) $(call print_available,HAVE_X11) diff --git a/Makefile.plugins.mk b/Makefile.plugins.mk @@ -38,6 +38,10 @@ ifeq ($(HAVE_ALSA),true) BASE_FLAGS += -DHAVE_ALSA endif +ifeq ($(HAVE_JACK),true) +BASE_FLAGS += -DHAVE_JACK +endif + ifeq ($(HAVE_LIBLO),true) BASE_FLAGS += -DHAVE_LIBLO endif @@ -46,41 +50,66 @@ ifeq ($(HAVE_PULSEAUDIO),true) BASE_FLAGS += -DHAVE_PULSEAUDIO endif +ifeq ($(HAVE_RTAUDIO),true) +BASE_FLAGS += -DHAVE_RTAUDIO +endif + +ifeq ($(HAVE_SDL2),true) +BASE_FLAGS += -DHAVE_SDL2 +endif + +# always needed +ifneq ($(HAIKU_OR_MACOS_OR_WASM_OR_WINDOWS),true) +ifneq ($(STATIC_BUILD),true) +LINK_FLAGS += -ldl +endif +endif + +# --------------------------------------------------------------------------------------------------------------------- +# JACK/Standalone setup + +ifeq ($(WASM),true) + +JACK_FLAGS += -sUSE_SDL=2 +JACK_LIBS += -sUSE_SDL=2 +JACK_LIBS += -sMAIN_MODULE -ldl + +ifneq ($(FILE_BROWSER_DISABLED),true) +JACK_LIBS += -sEXPORTED_RUNTIME_METHODS=FS,cwrap +endif + +else ifneq ($(SKIP_RTAUDIO_FALLBACK),true) + ifeq ($(MACOS),true) -JACK_LIBS += -framework CoreAudio -framework CoreFoundation +JACK_LIBS += -framework CoreAudio -framework CoreFoundation -framework CoreMIDI else ifeq ($(WINDOWS),true) -JACK_LIBS += -lksuser -lmfplat -lmfuuid -lole32 -lwinmm -lwmcodecdspuuid -else ifneq ($(HAIKU),true) -JACK_LIBS = -ldl +JACK_LIBS += -lole32 -lwinmm +# DirectSound +JACK_LIBS += -ldsound +# WASAPI +# JACK_LIBS += -lksuser -lmfplat -lmfuuid -lwmcodecdspuuid +else +ifeq ($(HAVE_PULSEAUDIO),true) +JACK_FLAGS += $(PULSEAUDIO_FLAGS) +JACK_LIBS += $(PULSEAUDIO_LIBS) +endif ifeq ($(HAVE_ALSA),true) JACK_FLAGS += $(ALSA_FLAGS) JACK_LIBS += $(ALSA_LIBS) endif -ifeq ($(HAVE_PULSEAUDIO),true) -JACK_FLAGS += $(PULSEAUDIO_FLAGS) -JACK_LIBS += $(PULSEAUDIO_LIBS) endif + ifeq ($(HAVE_RTAUDIO),true) +ifneq ($(HAIKU),true) JACK_LIBS += -lpthread -endif # !HAIKU -endif - -# backwards compat -BASE_FLAGS += -DHAVE_JACK - -# --------------------------------------------------------------------------------------------------------------------- -# Set VST3 filename, see https://vst3sdk-doc.diatonic.jp/doc/vstinterfaces/vst3loc.html - -ifeq ($(LINUX),true) -VST3_FILENAME = $(TARGET_PROCESSOR)-linux/$(NAME).so endif -ifeq ($(MACOS),true) -ifneq ($(MACOS_OLD),true) -VST3_FILENAME = MacOS/$(NAME) endif + +ifeq ($(HAVE_SDL2),true) +JACK_FLAGS += $(SDL2_FLAGS) +JACK_LIBS += $(SDL2_LIBS) endif -ifeq ($(WINDOWS),true) -VST3_FILENAME = $(TARGET_PROCESSOR)-win/$(NAME).vst3 + endif # --------------------------------------------------------------------------------------------------------------------- @@ -94,33 +123,6 @@ OBJS_UI += $(BUILD_DIR)/DistrhoUI_macOS_$(NAME).mm.o endif # --------------------------------------------------------------------------------------------------------------------- -# Set plugin binary file targets - -jack = $(TARGET_DIR)/$(NAME)$(APP_EXT) -ladspa_dsp = $(TARGET_DIR)/$(NAME)-ladspa$(LIB_EXT) -dssi_dsp = $(TARGET_DIR)/$(NAME)-dssi$(LIB_EXT) -dssi_ui = $(TARGET_DIR)/$(NAME)-dssi/$(NAME)_ui$(APP_EXT) -lv2 = $(TARGET_DIR)/$(NAME).lv2/$(NAME)$(LIB_EXT) -lv2_dsp = $(TARGET_DIR)/$(NAME).lv2/$(NAME)_dsp$(LIB_EXT) -lv2_ui = $(TARGET_DIR)/$(NAME).lv2/$(NAME)_ui$(LIB_EXT) -vst2 = $(TARGET_DIR)/$(NAME)-vst$(LIB_EXT) -ifneq ($(VST3_FILENAME),) -vst3 = $(TARGET_DIR)/$(NAME).vst3/Contents/$(VST3_FILENAME) -endif - -# --------------------------------------------------------------------------------------------------------------------- -# Set plugin symbols to export - -ifeq ($(MACOS),true) -SYMBOLS_LADSPA = -Wl,-exported_symbol,_ladspa_descriptor -SYMBOLS_DSSI = -Wl,-exported_symbol,_ladspa_descriptor -Wl,-exported_symbol,_dssi_descriptor -SYMBOLS_LV2 = -Wl,-exported_symbol,_lv2_descriptor -Wl,-exported_symbol,_lv2_generate_ttl -SYMBOLS_LV2UI = -Wl,-exported_symbol,_lv2ui_descriptor -SYMBOLS_VST2 = -Wl,-exported_symbol,_VSTPluginMain -SYMBOLS_VST3 = -Wl,-exported_symbol,_GetPluginFactory -Wl,-exported_symbol,_bundleEntry -Wl,-exported_symbol,_bundleExit -endif - -# --------------------------------------------------------------------------------------------------------------------- # Handle UI stuff, disable UI support automatically ifeq ($(FILES_UI),) @@ -164,6 +166,18 @@ HAVE_DGL = false endif endif +ifeq ($(UI_TYPE),opengl3) +ifeq ($(HAVE_OPENGL),true) +DGL_FLAGS += -DDGL_OPENGL -DDGL_USE_OPENGL3 -DHAVE_DGL +DGL_FLAGS += $(OPENGL_FLAGS) +DGL_LIBS += $(OPENGL_LIBS) +DGL_LIB = $(DPF_PATH)/build/libdgl-opengl3.a +HAVE_DGL = true +else +HAVE_DGL = false +endif +endif + ifeq ($(UI_TYPE),vulkan) ifeq ($(HAVE_VULKAN),true) DGL_FLAGS += -DDGL_VULKAN -DHAVE_DGL @@ -192,6 +206,76 @@ endif DGL_LIBS += $(DGL_SYSTEM_LIBS) -lm +# TODO split dsp and ui object build flags +BASE_FLAGS += $(DGL_FLAGS) + +# --------------------------------------------------------------------------------------------------------------------- +# Set VST2 filename, either single binary or inside a bundle + +ifeq ($(MACOS),true) +VST2_CONTENTS = $(NAME).vst/Contents +VST2_FILENAME = $(VST2_CONTENTS)/MacOS/$(NAME) +else ifeq ($(USE_VST2_BUNDLE),true) +VST2_FILENAME = $(NAME).vst/$(NAME)$(LIB_EXT) +else +VST2_FILENAME = $(NAME)-vst$(LIB_EXT) +endif + +# --------------------------------------------------------------------------------------------------------------------- +# Set VST3 filename, see https://vst3sdk-doc.diatonic.jp/doc/vstinterfaces/vst3loc.html + +ifeq ($(LINUX),true) +VST3_FILENAME = $(NAME).vst3/Contents/$(TARGET_PROCESSOR)-linux/$(NAME).so +else ifeq ($(MACOS),true) +VST3_CONTENTS = $(NAME).vst3/Contents +VST3_FILENAME = $(VST3_CONTENTS)/MacOS/$(NAME) +else ifeq ($(WASM),true) +VST3_FILENAME = $(NAME).vst3/Contents/wasm/$(NAME).vst3 +else ifeq ($(WINDOWS),true) +ifeq ($(CPU_I386),true) +VST3_FILENAME = $(NAME).vst3/Contents/x86-win/$(NAME).vst3 +else ifeq ($(CPU_X86_64),true) +VST3_FILENAME = $(NAME).vst3/Contents/x86_64-win/$(NAME).vst3 +endif +endif + +# --------------------------------------------------------------------------------------------------------------------- +# Set plugin binary file targets + +ifeq ($(MACOS),true) +ifeq ($(HAVE_DGL),true) +MACOS_APP_BUNDLE = true +endif +endif + +ifeq ($(MACOS_APP_BUNDLE),true) +jack = $(TARGET_DIR)/$(NAME).app/Contents/MacOS/$(NAME) +jackfiles = $(TARGET_DIR)/$(NAME).app/Contents/Info.plist +else +jack = $(TARGET_DIR)/$(NAME)$(APP_EXT) +endif +ladspa_dsp = $(TARGET_DIR)/$(NAME)-ladspa$(LIB_EXT) +dssi_dsp = $(TARGET_DIR)/$(NAME)-dssi$(LIB_EXT) +dssi_ui = $(TARGET_DIR)/$(NAME)-dssi/$(NAME)_ui$(APP_EXT) +lv2 = $(TARGET_DIR)/$(NAME).lv2/$(NAME)$(LIB_EXT) +lv2_dsp = $(TARGET_DIR)/$(NAME).lv2/$(NAME)_dsp$(LIB_EXT) +lv2_ui = $(TARGET_DIR)/$(NAME).lv2/$(NAME)_ui$(LIB_EXT) +vst2 = $(TARGET_DIR)/$(VST2_FILENAME) +ifneq ($(VST3_FILENAME),) +vst3 = $(TARGET_DIR)/$(VST3_FILENAME) +endif +shared = $(TARGET_DIR)/$(NAME)$(LIB_EXT) +static = $(TARGET_DIR)/$(NAME).a + +ifeq ($(MACOS),true) +vst2files += $(TARGET_DIR)/$(VST2_CONTENTS)/Info.plist +vst2files += $(TARGET_DIR)/$(VST2_CONTENTS)/PkgInfo +vst2files += $(TARGET_DIR)/$(VST2_CONTENTS)/Resources/empty.lproj +vst3files += $(TARGET_DIR)/$(VST3_CONTENTS)/Info.plist +vst3files += $(TARGET_DIR)/$(VST3_CONTENTS)/PkgInfo +vst3files += $(TARGET_DIR)/$(VST3_CONTENTS)/Resources/empty.lproj +endif + ifneq ($(HAVE_DGL),true) dssi_ui = lv2_ui = @@ -203,8 +287,53 @@ ifneq ($(HAVE_LIBLO),true) dssi_ui = endif -# TODO split dsp and ui object build flags -BASE_FLAGS += $(DGL_FLAGS) +# --------------------------------------------------------------------------------------------------------------------- +# Set plugin symbols to export + +ifeq ($(MACOS),true) +SYMBOLS_LADSPA = -Wl,-exported_symbols_list,$(DPF_PATH)/utils/symbols/ladspa.exp +SYMBOLS_DSSI = -Wl,-exported_symbols_list,$(DPF_PATH)/utils/symbols/dssi.exp +SYMBOLS_LV2DSP = -Wl,-exported_symbols_list,$(DPF_PATH)/utils/symbols/lv2-dsp.exp +SYMBOLS_LV2UI = -Wl,-exported_symbols_list,$(DPF_PATH)/utils/symbols/lv2-ui.exp +SYMBOLS_LV2 = -Wl,-exported_symbols_list,$(DPF_PATH)/utils/symbols/lv2.exp +SYMBOLS_VST2 = -Wl,-exported_symbols_list,$(DPF_PATH)/utils/symbols/vst2.exp +SYMBOLS_VST3 = -Wl,-exported_symbols_list,$(DPF_PATH)/utils/symbols/vst3.exp +SYMBOLS_SHARED = -Wl,-exported_symbols_list,$(DPF_PATH)/utils/symbols/shared.exp +else ifeq ($(WASM),true) +SYMBOLS_LADSPA = -sEXPORTED_FUNCTIONS="['ladspa_descriptor']" +SYMBOLS_DSSI = -sEXPORTED_FUNCTIONS="['ladspa_descriptor','dssi_descriptor']" +SYMBOLS_LV2DSP = -sEXPORTED_FUNCTIONS="['lv2_descriptor','lv2_generate_ttl']" +SYMBOLS_LV2UI = -sEXPORTED_FUNCTIONS="['lv2ui_descriptor']" +SYMBOLS_LV2 = -sEXPORTED_FUNCTIONS="['lv2_descriptor','lv2_generate_ttl','lv2ui_descriptor']" +SYMBOLS_VST2 = -sEXPORTED_FUNCTIONS="['VSTPluginMain']" +SYMBOLS_VST3 = -sEXPORTED_FUNCTIONS="['GetPluginFactory','ModuleEntry','ModuleExit']" +SYMBOLS_SHARED = -sEXPORTED_FUNCTIONS="['createSharedPlugin']" +else ifeq ($(WINDOWS),true) +SYMBOLS_LADSPA = $(DPF_PATH)/utils/symbols/ladspa.def +SYMBOLS_DSSI = $(DPF_PATH)/utils/symbols/dssi.def +SYMBOLS_LV2DSP = $(DPF_PATH)/utils/symbols/lv2-dsp.def +SYMBOLS_LV2UI = $(DPF_PATH)/utils/symbols/lv2-ui.def +SYMBOLS_LV2 = $(DPF_PATH)/utils/symbols/lv2.def +SYMBOLS_VST2 = $(DPF_PATH)/utils/symbols/vst2.def +SYMBOLS_VST3 = $(DPF_PATH)/utils/symbols/vst3.def +SYMBOLS_SHARED = $(DPF_PATH)/utils/symbols/shared.def +else ifneq ($(DEBUG),true) +SYMBOLS_LADSPA = -Wl,--version-script=$(DPF_PATH)/utils/symbols/ladspa.version +SYMBOLS_DSSI = -Wl,--version-script=$(DPF_PATH)/utils/symbols/dssi.version +SYMBOLS_LV2DSP = -Wl,--version-script=$(DPF_PATH)/utils/symbols/lv2-dsp.version +SYMBOLS_LV2UI = -Wl,--version-script=$(DPF_PATH)/utils/symbols/lv2-ui.version +SYMBOLS_LV2 = -Wl,--version-script=$(DPF_PATH)/utils/symbols/lv2.version +SYMBOLS_VST2 = -Wl,--version-script=$(DPF_PATH)/utils/symbols/vst2.version +SYMBOLS_VST3 = -Wl,--version-script=$(DPF_PATH)/utils/symbols/vst3.version +SYMBOLS_SHARED = -Wl,--version-script=$(DPF_PATH)/utils/symbols/shared.version +endif + +# --------------------------------------------------------------------------------------------------------------------- +# Runtime test build + +ifeq ($(DPF_RUNTIME_TESTING),true) +BUILD_CXX_FLAGS += -DDPF_RUNTIME_TESTING -Wno-pmf-conversions +endif # --------------------------------------------------------------------------------------------------------------------- # all needs to be first @@ -234,9 +363,23 @@ $(BUILD_DIR)/%.cpp.o: %.cpp @echo "Compiling $<" $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ +$(BUILD_DIR)/%.m.o: %.m + -@mkdir -p "$(shell dirname $(BUILD_DIR)/$<)" + @echo "Compiling $<" + $(SILENT)$(CC) $< $(BUILD_C_FLAGS) -ObjC -c -o $@ + +$(BUILD_DIR)/%.mm.o: %.mm + -@mkdir -p "$(shell dirname $(BUILD_DIR)/$<)" + @echo "Compiling $<" + $(SILENT)$(CC) $< $(BUILD_CXX_FLAGS) -ObjC++ -c -o $@ + clean: rm -rf $(BUILD_DIR) - rm -rf $(TARGET_DIR)/$(NAME) $(TARGET_DIR)/$(NAME)-* $(TARGET_DIR)/$(NAME).lv2 + rm -rf $(TARGET_DIR)/$(NAME) + rm -rf $(TARGET_DIR)/$(NAME)-* + rm -rf $(TARGET_DIR)/$(NAME).lv2 + rm -rf $(TARGET_DIR)/$(NAME).vst + rm -rf $(TARGET_DIR)/$(NAME).vst3 # --------------------------------------------------------------------------------------------------------------------- # DGL @@ -247,6 +390,9 @@ $(DPF_PATH)/build/libdgl-cairo.a: $(DPF_PATH)/build/libdgl-opengl.a: $(MAKE) -C $(DPF_PATH)/dgl opengl +$(DPF_PATH)/build/libdgl-opengl3.a: + $(MAKE) -C $(DPF_PATH)/dgl opengl3 + $(DPF_PATH)/build/libdgl-stub.a: $(MAKE) -C $(DPF_PATH)/dgl stub @@ -255,37 +401,35 @@ $(DPF_PATH)/build/libdgl-vulkan.a: # --------------------------------------------------------------------------------------------------------------------- -AS_PUGL_NAMESPACE = $(subst -,_,$(1)) - -$(BUILD_DIR)/DistrhoPluginMain_%.cpp.o: $(DPF_PATH)/distrho/DistrhoPluginMain.cpp +$(BUILD_DIR)/DistrhoPluginMain_%.cpp.o: $(DPF_PATH)/distrho/DistrhoPluginMain.cpp $(EXTRA_DEPENDENCIES) -@mkdir -p $(BUILD_DIR) @echo "Compiling DistrhoPluginMain.cpp ($*)" $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -DDISTRHO_PLUGIN_TARGET_$* -c -o $@ -$(BUILD_DIR)/DistrhoUIMain_%.cpp.o: $(DPF_PATH)/distrho/DistrhoUIMain.cpp +$(BUILD_DIR)/DistrhoUIMain_%.cpp.o: $(DPF_PATH)/distrho/DistrhoUIMain.cpp $(EXTRA_DEPENDENCIES) -@mkdir -p $(BUILD_DIR) @echo "Compiling DistrhoUIMain.cpp ($*)" $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -DDISTRHO_PLUGIN_TARGET_$* -c -o $@ -$(BUILD_DIR)/DistrhoUI_macOS_%.mm.o: $(DPF_PATH)/distrho/DistrhoUI_macOS.mm +$(BUILD_DIR)/DistrhoUI_macOS_%.mm.o: $(DPF_PATH)/distrho/DistrhoUI_macOS.mm $(EXTRA_DEPENDENCIES) -@mkdir -p $(BUILD_DIR) @echo "Compiling DistrhoUI_macOS.mm ($*)" - $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -DPUGL_NAMESPACE=$(call AS_PUGL_NAMESPACE,$*) -DGL_SILENCE_DEPRECATION -Wno-deprecated-declarations -I$(DPF_PATH)/dgl/src -I$(DPF_PATH)/dgl/src/pugl-upstream/include -ObjC++ -c -o $@ + $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -ObjC++ -c -o $@ -$(BUILD_DIR)/DistrhoPluginMain_JACK.cpp.o: $(DPF_PATH)/distrho/DistrhoPluginMain.cpp +$(BUILD_DIR)/DistrhoPluginMain_JACK.cpp.o: $(DPF_PATH)/distrho/DistrhoPluginMain.cpp $(EXTRA_DEPENDENCIES) -@mkdir -p $(BUILD_DIR) @echo "Compiling DistrhoPluginMain.cpp (JACK)" $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -DDISTRHO_PLUGIN_TARGET_JACK $(JACK_FLAGS) -c -o $@ -$(BUILD_DIR)/DistrhoUIMain_DSSI.cpp.o: $(DPF_PATH)/distrho/DistrhoUIMain.cpp +$(BUILD_DIR)/DistrhoUIMain_DSSI.cpp.o: $(DPF_PATH)/distrho/DistrhoUIMain.cpp $(EXTRA_DEPENDENCIES) -@mkdir -p $(BUILD_DIR) @echo "Compiling DistrhoUIMain.cpp (DSSI)" - $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(LIBLO_FLAGS) -DDISTRHO_PLUGIN_TARGET_DSSI -c -o $@ + $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) -DDISTRHO_PLUGIN_TARGET_DSSI $(LIBLO_FLAGS) -c -o $@ # --------------------------------------------------------------------------------------------------------------------- # JACK -jack: $(jack) +jack: $(jack) $(jackfiles) ifeq ($(HAVE_DGL),true) $(jack): $(OBJS_DSP) $(OBJS_UI) $(BUILD_DIR)/DistrhoPluginMain_JACK.cpp.o $(BUILD_DIR)/DistrhoUIMain_JACK.cpp.o $(DGL_LIB) @@ -294,7 +438,7 @@ $(jack): $(OBJS_DSP) $(BUILD_DIR)/DistrhoPluginMain_JACK.cpp.o endif -@mkdir -p $(shell dirname $@) @echo "Creating JACK standalone for $(NAME)" - $(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(DGL_LIBS) $(JACK_LIBS) -o $@ + $(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(EXTRA_LIBS) $(DGL_LIBS) $(JACK_LIBS) -o $@ # --------------------------------------------------------------------------------------------------------------------- # LADSPA @@ -337,22 +481,22 @@ $(lv2): $(OBJS_DSP) $(OBJS_UI) $(BUILD_DIR)/DistrhoPluginMain_LV2.cpp.o endif -@mkdir -p $(shell dirname $@) @echo "Creating LV2 plugin for $(NAME)" - $(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(DGL_LIBS) $(SHARED) $(SYMBOLS_LV2) $(SYMBOLS_LV2UI) -o $@ + $(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(EXTRA_LIBS) $(DGL_LIBS) $(SHARED) $(SYMBOLS_LV2) -o $@ $(lv2_dsp): $(OBJS_DSP) $(BUILD_DIR)/DistrhoPluginMain_LV2.cpp.o -@mkdir -p $(shell dirname $@) @echo "Creating LV2 plugin library for $(NAME)" - $(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(SHARED) $(SYMBOLS_LV2) -o $@ + $(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(EXTRA_LIBS) $(SHARED) $(SYMBOLS_LV2DSP) -o $@ $(lv2_ui): $(OBJS_UI) $(BUILD_DIR)/DistrhoUIMain_LV2.cpp.o $(DGL_LIB) -@mkdir -p $(shell dirname $@) @echo "Creating LV2 plugin UI for $(NAME)" - $(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(DGL_LIBS) $(SHARED) $(SYMBOLS_LV2UI) -o $@ + $(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(EXTRA_LIBS) $(DGL_LIBS) $(SHARED) $(SYMBOLS_LV2UI) -o $@ # --------------------------------------------------------------------------------------------------------------------- # VST2 -vst2 vst: $(vst2) +vst2 vst: $(vst2) $(vst2files) ifeq ($(HAVE_DGL),true) $(vst2): $(OBJS_DSP) $(OBJS_UI) $(BUILD_DIR)/DistrhoPluginMain_VST2.cpp.o $(BUILD_DIR)/DistrhoUIMain_VST2.cpp.o $(DGL_LIB) @@ -361,17 +505,73 @@ $(vst2): $(OBJS_DSP) $(BUILD_DIR)/DistrhoPluginMain_VST2.cpp.o endif -@mkdir -p $(shell dirname $@) @echo "Creating VST2 plugin for $(NAME)" - $(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(DGL_LIBS) $(SHARED) $(SYMBOLS_VST2) -o $@ + $(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(EXTRA_LIBS) $(DGL_LIBS) $(SHARED) $(SYMBOLS_VST2) -o $@ # --------------------------------------------------------------------------------------------------------------------- # VST3 -vst3: $(vst3) +vst3: $(vst3) $(vst3files) +ifeq ($(HAVE_DGL),true) +$(vst3): $(OBJS_DSP) $(OBJS_UI) $(BUILD_DIR)/DistrhoPluginMain_VST3.cpp.o $(BUILD_DIR)/DistrhoUIMain_VST3.cpp.o $(DGL_LIB) +else $(vst3): $(OBJS_DSP) $(BUILD_DIR)/DistrhoPluginMain_VST3.cpp.o +endif -@mkdir -p $(shell dirname $@) @echo "Creating VST3 plugin for $(NAME)" - $(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(DGL_LIBS) $(SHARED) $(SYMBOLS_VST3) -o $@ + $(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(EXTRA_LIBS) $(DGL_LIBS) $(SHARED) $(SYMBOLS_VST3) -o $@ + +# --------------------------------------------------------------------------------------------------------------------- +# Shared + +shared: $(shared) + +ifeq ($(HAVE_DGL),true) +$(shared): $(OBJS_DSP) $(OBJS_UI) $(BUILD_DIR)/DistrhoPluginMain_SHARED.cpp.o $(BUILD_DIR)/DistrhoUIMain_SHARED.cpp.o $(DGL_LIB) +else +$(shared): $(OBJS_DSP) $(BUILD_DIR)/DistrhoPluginMain_SHARED.cpp.o +endif + -@mkdir -p $(shell dirname $@) + @echo "Creating shared library for $(NAME)" + $(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(EXTRA_LIBS) $(DGL_LIBS) $(SHARED) $(SYMBOLS_SHARED) -o $@ + +# --------------------------------------------------------------------------------------------------------------------- +# Static + +static: $(static) + +ifeq ($(HAVE_DGL),true) +$(static): $(OBJS_DSP) $(OBJS_UI) $(BUILD_DIR)/DistrhoPluginMain_STATIC.cpp.o $(BUILD_DIR)/DistrhoUIMain_STATIC.cpp.o +else +$(static): $(OBJS_DSP) $(BUILD_DIR)/DistrhoPluginMain_STATIC.cpp.o +endif + -@mkdir -p $(shell dirname $@) + @echo "Creating static library for $(NAME)" + $(SILENT)rm -f $@ + $(SILENT)$(AR) crs $@ $^ + +# --------------------------------------------------------------------------------------------------------------------- +# macOS files + +$(TARGET_DIR)/%.app/Contents/Info.plist: $(DPF_PATH)/utils/plugin.app/Contents/Info.plist + -@mkdir -p $(shell dirname $@) + $(SILENT)sed -e "s/@INFO_PLIST_PROJECT_NAME@/$(NAME)/" $< > $@ + +$(TARGET_DIR)/%.vst/Contents/Info.plist: $(DPF_PATH)/utils/plugin.vst/Contents/Info.plist + -@mkdir -p $(shell dirname $@) + $(SILENT)sed -e "s/@INFO_PLIST_PROJECT_NAME@/$(NAME)/" $< > $@ + +$(TARGET_DIR)/%.vst3/Contents/Info.plist: $(DPF_PATH)/utils/plugin.vst/Contents/Info.plist + -@mkdir -p $(shell dirname $@) + $(SILENT)sed -e "s/@INFO_PLIST_PROJECT_NAME@/$(NAME)/" $< > $@ + +$(TARGET_DIR)/%/Contents/PkgInfo: $(DPF_PATH)/utils/plugin.vst/Contents/PkgInfo + -@mkdir -p $(shell dirname $@) + $(SILENT)cp $< $@ + +$(TARGET_DIR)/%/Resources/empty.lproj: $(DPF_PATH)/utils/plugin.vst/Contents/Resources/empty.lproj + -@mkdir -p $(shell dirname $@) + $(SILENT)cp $< $@ # --------------------------------------------------------------------------------------------------------------------- @@ -386,11 +586,15 @@ endif -include $(BUILD_DIR)/DistrhoPluginMain_LV2.cpp.d -include $(BUILD_DIR)/DistrhoPluginMain_VST2.cpp.d -include $(BUILD_DIR)/DistrhoPluginMain_VST3.cpp.d +-include $(BUILD_DIR)/DistrhoPluginMain_SHARED.cpp.d +-include $(BUILD_DIR)/DistrhoPluginMain_STATIC.cpp.d -include $(BUILD_DIR)/DistrhoUIMain_JACK.cpp.d -include $(BUILD_DIR)/DistrhoUIMain_DSSI.cpp.d -include $(BUILD_DIR)/DistrhoUIMain_LV2.cpp.d -include $(BUILD_DIR)/DistrhoUIMain_VST2.cpp.d -include $(BUILD_DIR)/DistrhoUIMain_VST3.cpp.d +-include $(BUILD_DIR)/DistrhoUIMain_SHARED.cpp.d +-include $(BUILD_DIR)/DistrhoUIMain_STATIC.cpp.d # --------------------------------------------------------------------------------------------------------------------- diff --git a/README.md b/README.md @@ -7,7 +7,7 @@ DPF is designed to make development of new plugins an easy and enjoyable task.<b It allows developers to create plugins with custom UIs using a simple C++ API.<br/> The framework facilitates exporting various different plugin formats from the same code-base.<br/> -DPF can build for LADSPA, DSSI, LV2 and VST formats.<br/> +DPF can build for LADSPA, DSSI, LV2, VST2 and VST3 formats.<br/> All current plugin format implementations are complete.<br/> A JACK/Standalone mode is also available, allowing you to quickly test plugins.<br/> @@ -19,6 +19,12 @@ Getting time information from the host is possible.<br/> It uses the same format as the JACK Transport API, making porting some code easier.<br/> +## Licensing + +DPF is released under ISC, which basically means you can do whatever you want as long as you credit the original authors. +Some plugin formats may have additional restrictions, see [LICENSING.md](LICENSING.md) for details. + + ## Help and documentation Bug reports happen on the [DPF github project](https://github.com/DISTRHO/DPF/issues). diff --git a/cmake/DPF-plugin.cmake b/cmake/DPF-plugin.cmake @@ -22,7 +22,7 @@ # add_subdirectory(DPF) # # dpf_add_plugin(MyPlugin -# TARGETS lv2 vst2 +# TARGETS lv2 vst2 vst3 # UI_TYPE opengl # FILES_DSP # src/MyPlugin.cpp @@ -71,7 +71,7 @@ include(CMakeParseArguments) # # `TARGETS` <tgt1>...<tgtN> # a list of one of more of the following target types: -# `jack`, `ladspa`, `dssi`, `lv2`, `vst2` +# `jack`, `ladspa`, `dssi`, `lv2`, `vst2`, `vst3` # # `UI_TYPE` <type> # the user interface type: `opengl` (default), `cairo` @@ -122,6 +122,10 @@ function(dpf_add_plugin NAME) target_include_directories("${NAME}" PUBLIC "${DPF_ROOT_DIR}/distrho") + if((NOT WIN32) AND (NOT APPLE) AND (NOT HAIKU)) + target_link_libraries("${NAME}" PRIVATE "dl") + endif() + if(_dgl_library) # make sure that all code will see DGL_* definitions target_link_libraries("${NAME}" PUBLIC @@ -135,7 +139,10 @@ function(dpf_add_plugin NAME) if(_dgl_library) dpf__add_static_library("${NAME}-ui" ${_dpf_plugin_FILES_UI}) target_link_libraries("${NAME}-ui" PUBLIC "${NAME}" ${_dgl_library}) - # add the files containing Objective-C classes, recompiled under namespace + if((NOT WIN32) AND (NOT APPLE) AND (NOT HAIKU)) + target_link_libraries("${NAME}-ui" PRIVATE "dl") + endif() + # add the files containing Objective-C classes dpf__add_plugin_specific_ui_sources("${NAME}-ui") else() add_library("${NAME}-ui" INTERFACE) @@ -153,6 +160,8 @@ function(dpf_add_plugin NAME) dpf__build_lv2("${NAME}" "${_dgl_library}" "${_dpf_plugin_MONOLITHIC}") elseif(_target STREQUAL "vst2") dpf__build_vst2("${NAME}" "${_dgl_library}") + elseif(_target STREQUAL "vst3") + dpf__build_vst3("${NAME}" "${_dgl_library}") else() message(FATAL_ERROR "Unrecognized target type for plugin: ${_target}") endif() @@ -183,11 +192,6 @@ function(dpf__build_jack NAME DGL_LIBRARY) RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin/$<0:>" OUTPUT_NAME "${NAME}") - # Note: libjack will be linked at runtime - if((NOT WIN32) AND (NOT APPLE) AND (NOT HAIKU)) - target_link_libraries("${NAME}-jack" PRIVATE "dl") - endif() - # for RtAudio native fallback if(APPLE) find_library(APPLE_COREAUDIO_FRAMEWORK "CoreAudio") @@ -199,13 +203,14 @@ endfunction() # dpf__build_ladspa # ------------------------------------------------------------------------------ # -# Add build rules for a DSSI plugin. +# Add build rules for a LADSPA plugin. # function(dpf__build_ladspa NAME) dpf__create_dummy_source_list(_no_srcs) dpf__add_module("${NAME}-ladspa" ${_no_srcs}) dpf__add_plugin_main("${NAME}-ladspa" "ladspa") + dpf__set_module_export_list("${NAME}-ladspa" "ladspa") target_link_libraries("${NAME}-ladspa" PRIVATE "${NAME}-dsp") set_target_properties("${NAME}-ladspa" PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin/$<0:>" @@ -234,6 +239,7 @@ function(dpf__build_dssi NAME DGL_LIBRARY) dpf__add_module("${NAME}-dssi" ${_no_srcs}) dpf__add_plugin_main("${NAME}-dssi" "dssi") + dpf__set_module_export_list("${NAME}-dssi" "dssi") target_link_libraries("${NAME}-dssi" PRIVATE "${NAME}-dsp") set_target_properties("${NAME}-dssi" PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin/$<0:>" @@ -257,13 +263,18 @@ endfunction() # dpf__build_lv2 # ------------------------------------------------------------------------------ # -# Add build rules for a LV2 plugin. +# Add build rules for an LV2 plugin. # function(dpf__build_lv2 NAME DGL_LIBRARY MONOLITHIC) dpf__create_dummy_source_list(_no_srcs) dpf__add_module("${NAME}-lv2" ${_no_srcs}) dpf__add_plugin_main("${NAME}-lv2" "lv2") + if(DGL_LIBRARY AND MONOLITHIC) + dpf__set_module_export_list("${NAME}-lv2" "lv2") + else() + dpf__set_module_export_list("${NAME}-lv2" "lv2-dsp") + endif() target_link_libraries("${NAME}-lv2" PRIVATE "${NAME}-dsp") set_target_properties("${NAME}-lv2" PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin/${NAME}.lv2/$<0:>" @@ -280,6 +291,7 @@ function(dpf__build_lv2 NAME DGL_LIBRARY MONOLITHIC) else() dpf__add_module("${NAME}-lv2-ui" ${_no_srcs}) dpf__add_ui_main("${NAME}-lv2-ui" "lv2" "${DGL_LIBRARY}") + dpf__set_module_export_list("${NAME}-lv2-ui" "lv2-ui") target_link_libraries("${NAME}-lv2-ui" PRIVATE "${NAME}-ui") set_target_properties("${NAME}-lv2-ui" PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin/${NAME}.lv2/$<0:>" @@ -293,7 +305,7 @@ function(dpf__build_lv2 NAME DGL_LIBRARY MONOLITHIC) add_dependencies("${NAME}-lv2" lv2_ttl_generator) add_custom_command(TARGET "${NAME}-lv2" POST_BUILD - COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR} + COMMAND "$<TARGET_FILE:lv2_ttl_generator>" "$<TARGET_FILE:${NAME}-lv2>" WORKING_DIRECTORY "${PROJECT_BINARY_DIR}/bin/${NAME}.lv2" @@ -311,6 +323,7 @@ function(dpf__build_vst2 NAME DGL_LIBRARY) dpf__add_module("${NAME}-vst2" ${_no_srcs}) dpf__add_plugin_main("${NAME}-vst2" "vst2") dpf__add_ui_main("${NAME}-vst2" "vst2" "${DGL_LIBRARY}") + dpf__set_module_export_list("${NAME}-vst2" "vst2") target_link_libraries("${NAME}-vst2" PRIVATE "${NAME}-dsp" "${NAME}-ui") set_target_properties("${NAME}-vst2" PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin/$<0:>" @@ -330,6 +343,95 @@ function(dpf__build_vst2 NAME DGL_LIBRARY) endif() endfunction() +# dpf__determine_vst3_package_architecture +# ------------------------------------------------------------------------------ +# +# Determines the package architecture for a VST3 plugin target. +# +function(dpf__determine_vst3_package_architecture OUTPUT_VARIABLE) + # if set by variable, override the detection + if(DPF_VST3_ARCHITECTURE) + set("${OUTPUT_VARIABLE}" "${DPF_VST3_ARCHITECTURE}" PARENT_SCOPE) + return() + endif() + + # not used on Apple, which supports universal binary + if(APPLE) + set("${OUTPUT_VARIABLE}" "universal" PARENT_SCOPE) + return() + endif() + + # identify the target processor (special case of MSVC, problematic sometimes) + if(MSVC) + set(vst3_system_arch "${MSVC_CXX_ARCHITECTURE_ID}") + else() + set(vst3_system_arch "${CMAKE_SYSTEM_PROCESSOR}") + endif() + + # transform the processor name to a format that VST3 recognizes + if(vst3_system_arch MATCHES "^(x86_64|amd64|AMD64|x64|X64)$") + set(vst3_package_arch "x86_64") + elseif(vst3_system_arch MATCHES "^(i.86|x86|X86)$") + if(WIN32) + set(vst3_package_arch "x86") + else() + set(vst3_package_arch "i386") + endif() + elseif(vst3_system_arch MATCHES "^(armv[3-8][a-z]*)$") + set(vst3_package_arch "${vst3_system_arch}") + elseif(vst3_system_arch MATCHES "^(aarch64)$") + set(vst3_package_arch "aarch64") + else() + message(FATAL_ERROR "We don't know this architecture for VST3: ${vst3_system_arch}.") + endif() + + # TODO: the detections for Windows arm/arm64 when supported + + set("${OUTPUT_VARIABLE}" "${vst3_package_arch}" PARENT_SCOPE) +endfunction() + +# dpf__build_vst3 +# ------------------------------------------------------------------------------ +# +# Add build rules for a VST3 plugin. +# +function(dpf__build_vst3 NAME DGL_LIBRARY) + dpf__determine_vst3_package_architecture(vst3_arch) + + dpf__create_dummy_source_list(_no_srcs) + + dpf__add_module("${NAME}-vst3" ${_no_srcs}) + dpf__add_plugin_main("${NAME}-vst3" "vst3") + dpf__add_ui_main("${NAME}-vst3" "vst3" "${DGL_LIBRARY}") + dpf__set_module_export_list("${NAME}-vst3" "vst3") + target_link_libraries("${NAME}-vst3" PRIVATE "${NAME}-dsp" "${NAME}-ui") + set_target_properties("${NAME}-vst3" PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/obj/vst3/$<0:>" + OUTPUT_NAME "${NAME}" + PREFIX "") + + if(APPLE) + set_target_properties("${NAME}-vst3" PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin/${NAME}.vst3/Contents/MacOS/$<0:>" + SUFFIX "") + elseif(WIN32) + set_target_properties("${NAME}-vst3" PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin/${NAME}.vst3/Contents/${vst3_arch}-win/$<0:>" SUFFIX ".vst3") + else() + set_target_properties("${NAME}-vst3" PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin/${NAME}.vst3/Contents/${vst3_arch}-linux/$<0:>") + endif() + + if(APPLE) + # Uses the same macOS bundle template as VST2 + set(INFO_PLIST_PROJECT_NAME "${NAME}") + configure_file("${DPF_ROOT_DIR}/utils/plugin.vst/Contents/Info.plist" + "${PROJECT_BINARY_DIR}/bin/${NAME}.vst3/Contents/Info.plist" @ONLY) + file(COPY "${DPF_ROOT_DIR}/utils/plugin.vst/Contents/PkgInfo" + DESTINATION "${PROJECT_BINARY_DIR}/bin/${NAME}.vst3/Contents") + endif() +endfunction() + # dpf__add_dgl_cairo # ------------------------------------------------------------------------------ # @@ -366,9 +468,9 @@ function(dpf__add_dgl_cairo) if(NOT APPLE) target_sources(dgl-cairo PRIVATE "${DPF_ROOT_DIR}/dgl/src/pugl.cpp") - else() # Note: macOS pugl will be built as part of DistrhoUI_macOS.mm - #target_sources(dgl-opengl PRIVATE - # "${DPF_ROOT_DIR}/dgl/src/pugl.mm") + else() + target_sources(dgl-opengl PRIVATE + "${DPF_ROOT_DIR}/dgl/src/pugl.mm") endif() target_include_directories(dgl-cairo PUBLIC "${DPF_ROOT_DIR}/dgl") @@ -428,9 +530,9 @@ function(dpf__add_dgl_opengl) if(NOT APPLE) target_sources(dgl-opengl PRIVATE "${DPF_ROOT_DIR}/dgl/src/pugl.cpp") - else() # Note: macOS pugl will be built as part of DistrhoUI_macOS.mm - #target_sources(dgl-opengl PRIVATE - # "${DPF_ROOT_DIR}/dgl/src/pugl.mm") + else() + target_sources(dgl-opengl PRIVATE + "${DPF_ROOT_DIR}/dgl/src/pugl.mm") endif() target_include_directories(dgl-opengl PUBLIC "${DPF_ROOT_DIR}/dgl") @@ -454,19 +556,12 @@ endfunction() # dpf__add_plugin_specific_ui_sources # ------------------------------------------------------------------------------ # -# Compile plugin-specific UI sources into the target designated by the given -# name. There are some special considerations here: -# - On most platforms, sources can be compiled only once, as part of DGL; -# - On macOS, for any sources which define Objective-C interfaces, these must -# be recompiled for each plugin under a unique namespace. In this case, the -# name must be a plugin-specific identifier, and it will be used for computing -# the unique ID along with the project version. +# Compile system specific files, for now it is just Objective-C code +# function(dpf__add_plugin_specific_ui_sources NAME) if(APPLE) target_sources("${NAME}" PRIVATE "${DPF_ROOT_DIR}/distrho/DistrhoUI_macOS.mm") - string(SHA256 _hash "${NAME}:${PROJECT_VERSION}") - target_compile_definitions("${NAME}" PUBLIC "PUGL_NAMESPACE=${_hash}") endif() endfunction() @@ -494,22 +589,22 @@ function(dpf__add_dgl_system_libs) target_include_directories(dgl-system-libs INTERFACE "${X11_INCLUDE_DIR}") target_link_libraries(dgl-system-libs INTERFACE "${X11_X11_LIB}") target_compile_definitions(dgl-system-libs-definitions INTERFACE "HAVE_X11") + if(X11_Xcursor_FOUND) + target_link_libraries(dgl-system-libs INTERFACE "${X11_Xcursor_LIB}") + target_compile_definitions(dgl-system-libs-definitions INTERFACE "HAVE_XCURSOR") + endif() if(X11_Xext_FOUND) target_link_libraries(dgl-system-libs INTERFACE "${X11_Xext_LIB}") target_compile_definitions(dgl-system-libs-definitions INTERFACE "HAVE_XEXT") endif() - if(X11_XSync_FOUND) - target_link_libraries(dgl-system-libs INTERFACE "${X11_XSync_LIB}") - target_compile_definitions(dgl-system-libs-definitions INTERFACE "HAVE_XSYNC") - endif() if(X11_Xrandr_FOUND) target_link_libraries(dgl-system-libs INTERFACE "${X11_Xrandr_LIB}") target_compile_definitions(dgl-system-libs-definitions INTERFACE "HAVE_XRANDR") endif() - #if(X11_Xcursor_FOUND) - # target_link_libraries(dgl-system-libs INTERFACE "${X11_Xcursor_LIB}") - # target_compile_definitions(dgl-system-libs-definitions INTERFACE "HAVE_XCURSOR") - #endif() + if(X11_XSync_FOUND) + target_link_libraries(dgl-system-libs INTERFACE "${X11_XSync_LIB}") + target_compile_definitions(dgl-system-libs-definitions INTERFACE "HAVE_XSYNC") + endif() endif() if(MSVC) @@ -566,6 +661,24 @@ function(dpf__add_static_library NAME) dpf__set_target_defaults("${NAME}") endfunction() +# dpf__set_module_export_list +# ------------------------------------------------------------------------------ +# +# Applies a list of exported symbols to the module target. +# +function(dpf__set_module_export_list NAME EXPORTS) + if(WIN32) + target_sources("${NAME}" PRIVATE "${DPF_ROOT_DIR}/utils/symbols/${EXPORTS}.def") + elseif(APPLE) + set_property(TARGET "${NAME}" APPEND PROPERTY LINK_OPTIONS + "-Xlinker" "-exported_symbols_list" + "-Xlinker" "${DPF_ROOT_DIR}/utils/symbols/${EXPORTS}.exp") + else() + set_property(TARGET "${NAME}" APPEND PROPERTY LINK_OPTIONS + "-Xlinker" "--version-script=${DPF_ROOT_DIR}/utils/symbols/${EXPORTS}.version") + endif() +endfunction() + # dpf__set_target_defaults # ------------------------------------------------------------------------------ # diff --git a/dgl/Application.hpp b/dgl/Application.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -19,6 +19,12 @@ #include "Base.hpp" +#ifdef DISTRHO_NAMESPACE +START_NAMESPACE_DISTRHO +class PluginApplication; +END_NAMESPACE_DISTRHO +#endif + START_NAMESPACE_DGL // -------------------------------------------------------------------------------------------------------------------- @@ -33,7 +39,7 @@ START_NAMESPACE_DGL Unless stated otherwise, functions within this class are not thread-safe. */ -class Application +class DISTRHO_API Application { public: /** @@ -81,6 +87,15 @@ public: bool isStandalone() const noexcept; /** + Return the time in seconds. + + This is a monotonically increasing clock with high resolution.@n + The returned time is only useful to compare against other times returned by this function, + its absolute value has no meaning. + */ + double getTime() const; + + /** Add a callback function to be triggered on every idle cycle. You can add more than one, and remove them at anytime with removeIdleCallback(). Idle callbacks trigger right after OS event handling and Window idle events (within the same cycle). @@ -94,7 +109,7 @@ public: void removeIdleCallback(IdleCallback* callback); /** - Set the class name of the application. + Get the class name of the application. This is a stable identifier for the application, used as the window class/instance name on X11 and Windows. It is not displayed to the user, but can be used in scripts and by window managers, @@ -102,12 +117,21 @@ public: Plugins created with DPF have their class name automatically set based on DGL_NAMESPACE and plugin name. */ + const char* getClassName() const noexcept; + + /** + Set the class name of the application. + @see getClassName + */ void setClassName(const char* name); private: struct PrivateData; PrivateData* const pData; friend class Window; + #ifdef DISTRHO_NAMESPACE + friend class DISTRHO_NAMESPACE::PluginApplication; + #endif DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Application) }; diff --git a/dgl/Base.hpp b/dgl/Base.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -49,16 +49,16 @@ enum Modifier { /** Keyboard key codepoints. - All keys are identified by a Unicode code point in PuglEventKey::key. This - enumeration defines constants for special keys that do not have a standard - code point, and some convenience constants for control characters. Note - that all keys are handled in the same way, this enumeration is just for + All keys are identified by a Unicode code point in Widget::KeyboardEvent::key. + This enumeration defines constants for special keys that do not have a standard + code point, and some convenience constants for control characters. + Note that all keys are handled in the same way, this enumeration is just for convenience when writing hard-coded key bindings. Keys that do not have a standard code point use values in the Private Use - Area in the Basic Multilingual Plane (`U+E000` to `U+F8FF`). Applications - must take care to not interpret these values beyond key detection, the - mapping used here is arbitrary and specific to DPF. + Area in the Basic Multilingual Plane (`U+E000` to `U+F8FF`). + Applications must take care to not interpret these values beyond key detection, + the mapping used here is arbitrary and specific to DPF. */ enum Key { // Convenience symbols for ASCII control characters @@ -116,7 +116,7 @@ enum Key { /** Common flags for all events. */ -enum Flag { +enum EventFlag { kFlagSendEvent = 1, ///< Event is synthetic kFlagIsHint = 2 ///< Event is a hint (not direct user input) }; @@ -131,6 +131,46 @@ enum CrossingMode { }; /** + A mouse button. + + Mouse button numbers start from 1, and are ordered: primary, secondary, middle. + So, on a typical right-handed mouse, the button numbers are: + + Left: 1 + Right: 2 + Middle (often a wheel): 3 + + Higher button numbers are reported in the same order they are represented on the system. + There is no universal standard here, but buttons 4 and 5 are typically a pair of buttons or a rocker, + which are usually bound to "back" and "forward" operations. + + Note that these numbers may differ from those used on the underlying + platform, since they are manipulated to provide a consistent portable API. +*/ +enum MouseButton { + kMouseButtonLeft = 1, + kMouseButtonRight, + kMouseButtonMiddle, +}; + +/** + A mouse cursor type. + + This is a portable subset of mouse cursors that exist on X11, MacOS, and Windows. +*/ +enum MouseCursor { + kMouseCursorArrow, ///< Default pointing arrow + kMouseCursorCaret, ///< Caret (I-Beam) for text entry + kMouseCursorCrosshair, ///< Cross-hair + kMouseCursorHand, ///< Hand with a pointing finger + kMouseCursorNotAllowed, ///< Operation not allowed + kMouseCursorLeftRight, ///< Left/right arrow for horizontal resize + kMouseCursorUpDown, ///< Up/down arrow for vertical resize + kMouseCursorDiagonal, ///< Top-left to bottom-right arrow for diagonal resize + kMouseCursorAntiDiagonal ///< Bottom-left to top-right arrow for diagonal resize +}; + +/** Scroll direction. Describes the direction of a scroll event along with whether the scroll is a "smooth" scroll. @@ -138,11 +178,29 @@ enum CrossingMode { while a smooth scroll is for those with arbitrary scroll direction freedom, like some touchpads. */ enum ScrollDirection { - kScrollUp, ///< Scroll up - kScrollDown, ///< Scroll down - kScrollLeft, ///< Scroll left - kScrollRight, ///< Scroll right - kScrollSmooth ///< Smooth scroll in any direction + kScrollUp, ///< Scroll up + kScrollDown, ///< Scroll down + kScrollLeft, ///< Scroll left + kScrollRight, ///< Scroll right + kScrollSmooth ///< Smooth scroll in any direction +}; + +/** + A clipboard data offer. + @see Window::onClipboardDataOffer +*/ +struct ClipboardDataOffer { + /** + The id of this data offer. + @note The value 0 is reserved for null/invalid. + */ + uint32_t id; + + /** + The type of this data offer. + Usually a MIME type, but may also be another platform-specific type identifier. + */ + const char* type; }; // -------------------------------------------------------------------------------------------------------------------- diff --git a/dgl/Cairo.hpp b/dgl/Cairo.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -20,7 +20,7 @@ #include "ImageBase.hpp" #include "ImageBaseWidgets.hpp" -#include <cairo/cairo.h> +#include <cairo.h> START_NAMESPACE_DGL @@ -151,7 +151,7 @@ public: /** Destructor. */ - virtual ~CairoBaseWidget() {} + ~CairoBaseWidget() override {} protected: /** diff --git a/dgl/EventHandlers.hpp b/dgl/EventHandlers.hpp @@ -52,7 +52,7 @@ public: }; explicit ButtonEventHandler(SubWidget* self); - ~ButtonEventHandler(); + virtual ~ButtonEventHandler(); bool isActive() noexcept; void setActive(bool active, bool sendCallback) noexcept; @@ -117,7 +117,7 @@ public: explicit KnobEventHandler(SubWidget* self); explicit KnobEventHandler(SubWidget* self, const KnobEventHandler& other); KnobEventHandler& operator=(const KnobEventHandler& other); - ~KnobEventHandler(); + virtual ~KnobEventHandler(); // returns raw value, is assumed to be scaled if using log float getValue() const noexcept; @@ -154,6 +154,15 @@ private: struct PrivateData; PrivateData* const pData; + /* not for use */ +#ifdef DISTRHO_PROPER_CPP11_SUPPORT + KnobEventHandler(KnobEventHandler& other) = delete; + KnobEventHandler(const KnobEventHandler& other) = delete; +#else + KnobEventHandler(KnobEventHandler& other); + KnobEventHandler(const KnobEventHandler& other); +#endif + DISTRHO_LEAK_DETECTOR(KnobEventHandler) }; diff --git a/dgl/FileBrowserDialog.hpp b/dgl/FileBrowserDialog.hpp @@ -0,0 +1,28 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DGL_FILE_BROWSER_DIALOG_HPP_INCLUDED +#define DGL_FILE_BROWSER_DIALOG_HPP_INCLUDED + +#include "Base.hpp" + +START_NAMESPACE_DGL + +#include "../distrho/extra/FileBrowserDialogImpl.hpp" + +END_NAMESPACE_DGL + +#endif // DGL_FILE_BROWSER_DIALOG_HPP_INCLUDED diff --git a/dgl/ImageBase.hpp b/dgl/ImageBase.hpp @@ -39,7 +39,7 @@ enum ImageFormat { It is an abstract class that provides the common methods to build on top. Cairo and OpenGL Image classes are based upon this one. - @see Image + @see CairoImage, OpenGLImage */ class ImageBase { diff --git a/dgl/ImageBaseWidgets.hpp b/dgl/ImageBaseWidgets.hpp @@ -25,13 +25,36 @@ START_NAMESPACE_DGL // -------------------------------------------------------------------------------------------------------------------- +/** + DGL Image About Window class. + + This is a Window attached (transient) to another Window that simply shows an Image as its content. + It is typically used for "about this project" style pop-up Windows. + + Pressing 'Esc' or clicking anywhere on the window will automatically close it. + + @see CairoImageAboutWindow, OpenGLImageAboutWindow, Window::runAsModal(bool) + */ template <class ImageType> class ImageBaseAboutWindow : public StandaloneWindow { public: - explicit ImageBaseAboutWindow(Window& parentWindow, const ImageType& image = ImageType()); - explicit ImageBaseAboutWindow(TopLevelWidget* parentTopLevelWidget, const ImageType& image = ImageType()); - + /** + Constructor taking an existing Window as the parent transient window and an optional image. + If @a image is valid, the about window size will match the image size. + */ + explicit ImageBaseAboutWindow(Window& transientParentWindow, const ImageType& image = ImageType()); + + /** + Constructor taking a top-level-widget's Window as the parent transient window and an optional image. + If @a image is valid, the about window size will match the image size. + */ + explicit ImageBaseAboutWindow(TopLevelWidget* topLevelWidget, const ImageType& image = ImageType()); + + /** + Set a new image to use as background for this window. + Window size will adjust to match the image size. + */ void setImage(const ImageType& image); protected: @@ -47,6 +70,16 @@ private: // -------------------------------------------------------------------------------------------------------------------- +/** + DGL Image Button class. + + This is a typical button, where the drawing comes from a pregenerated set of images. + The button can be under "normal", "hover" and "down" states, with one separate image possible for each. + + The event logic for this button comes from the ButtonEventHandler class. + + @see CairoImageButton, OpenGLImageButton + */ template <class ImageType> class ImageBaseButton : public SubWidget, public ButtonEventHandler @@ -81,6 +114,18 @@ private: // -------------------------------------------------------------------------------------------------------------------- +/** + DGL Image Knob class. + + This is a typical knob/dial, where the drawing comes from a pregenerated image "filmstrip". + The knob's "filmstrip" image can be either horizontal or vertical, + with the number of steps automatically based on the largest value (ie, horizontal if width>height, vertical if height>width). + There are no different images for "hover" or "down" states. + + The event logic for this knob comes from the KnobEventHandler class. + + @see CairoImageKnob, OpenGLImageKnob + */ template <class ImageType> class ImageBaseKnob : public SubWidget, public KnobEventHandler diff --git a/dgl/Makefile b/dgl/Makefile @@ -13,14 +13,10 @@ BUILD_CXX_FLAGS += $(DGL_FLAGS) -I. -Isrc -DDONT_SET_USING_DGL_NAMESPACE -Wno-un BUILD_CXX_FLAGS += -Isrc/pugl-upstream/include LINK_FLAGS += $(DGL_LIBS) -ifeq ($(USE_OPENGL3),true) -BUILD_CXX_FLAGS += -DDGL_USE_OPENGL3 -endif - -# TODO fix these after pugl-upstream is done -BUILD_CXX_FLAGS += -Wno-attributes -Wno-extra -Wno-missing-field-initializers -ifneq ($(MACOS),true) -BUILD_CXX_FLAGS += -Wno-narrowing +ifeq ($(MACOS),true) +BUILD_CXX_FLAGS += -Wno-deprecated-declarations +else +PUGL_EXTRA_FLAGS = -Wno-extra -Wmissing-field-initializers endif # ifneq ($(MACOS_OLD),true) @@ -73,6 +69,18 @@ endif # --------------------------------------------------------------------------------------------------------------------- +OBJS_opengl3 = $(OBJS_common) \ + ../build/dgl/OpenGL.cpp.opengl3.o \ + ../build/dgl/NanoVG.cpp.opengl3.o + +ifeq ($(MACOS),true) +OBJS_opengl3 += ../build/dgl/pugl.mm.opengl3.o +else +OBJS_opengl3 += ../build/dgl/pugl.cpp.opengl3.o +endif + +# --------------------------------------------------------------------------------------------------------------------- + OBJS_stub = $(OBJS_common) ifeq ($(MACOS),true) @@ -116,10 +124,11 @@ endif all: $(TARGETS) -cairo: ../build/libdgl-cairo.a -opengl: ../build/libdgl-opengl.a -stub: ../build/libdgl-stub.a -vulkan: ../build/libdgl-vulkan.a +cairo: ../build/libdgl-cairo.a +opengl: ../build/libdgl-opengl.a +opengl3: ../build/libdgl-opengl3.a +stub: ../build/libdgl-stub.a +vulkan: ../build/libdgl-vulkan.a # --------------------------------------------------------------------------------------------------------------------- @@ -135,6 +144,12 @@ vulkan: ../build/libdgl-vulkan.a $(SILENT)rm -f $@ $(SILENT)$(AR) crs $@ $^ +../build/libdgl-opengl3.a: $(OBJS_opengl3) + -@mkdir -p ../build + @echo "Creating libdgl-opengl3.a" + $(SILENT)rm -f $@ + $(SILENT)$(AR) crs $@ $^ + ../build/libdgl-stub.a: $(OBJS_stub) -@mkdir -p ../build @echo "Creating libdgl-stub.a" @@ -171,39 +186,63 @@ vulkan: ../build/libdgl-vulkan.a # --------------------------------------------------------------------------------------------------------------------- +../build/dgl/pugl.cpp.o: src/pugl.cpp + -@mkdir -p ../build/dgl + @echo "Compiling $<" + $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(PUGL_EXTRA_FLAGS) -c -o $@ + +../build/dgl/pugl.mm.o: src/pugl.mm + -@mkdir -p ../build/dgl + @echo "Compiling $<" + $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(PUGL_EXTRA_FLAGS) -c -ObjC++ -o $@ + +# --------------------------------------------------------------------------------------------------------------------- + ../build/dgl/%.cpp.cairo.o: src/%.cpp -@mkdir -p ../build/dgl @echo "Compiling $< (Cairo variant)" - $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(CAIRO_FLAGS) -DDGL_CAIRO -c -o $@ + $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(PUGL_EXTRA_FLAGS) $(CAIRO_FLAGS) -DDGL_CAIRO -c -o $@ ../build/dgl/%.mm.cairo.o: src/%.mm -@mkdir -p ../build/dgl @echo "Compiling $< (Cairo variant)" - $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(CAIRO_FLAGS) -DDGL_CAIRO -c -ObjC++ -o $@ + $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(PUGL_EXTRA_FLAGS) $(CAIRO_FLAGS) -DDGL_CAIRO -c -ObjC++ -o $@ # --------------------------------------------------------------------------------------------------------------------- ../build/dgl/%.cpp.opengl.o: src/%.cpp -@mkdir -p ../build/dgl @echo "Compiling $< (OpenGL variant)" - $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(OPENGL_FLAGS) -DDGL_OPENGL -c -o $@ + $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(PUGL_EXTRA_FLAGS) $(OPENGL_FLAGS) -DDGL_OPENGL -c -o $@ ../build/dgl/%.mm.opengl.o: src/%.mm -@mkdir -p ../build/dgl @echo "Compiling $< (OpenGL variant)" - $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(OPENGL_FLAGS) -DDGL_OPENGL -c -ObjC++ -o $@ + $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(PUGL_EXTRA_FLAGS) $(OPENGL_FLAGS) -DDGL_OPENGL -c -ObjC++ -o $@ + +# --------------------------------------------------------------------------------------------------------------------- + +../build/dgl/%.cpp.opengl3.o: src/%.cpp + -@mkdir -p ../build/dgl + @echo "Compiling $< (OpenGL3 variant)" + $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(PUGL_EXTRA_FLAGS) $(OPENGL_FLAGS) -DDGL_OPENGL -DDGL_USE_OPENGL3 -c -o $@ + +../build/dgl/%.mm.opengl3.o: src/%.mm + -@mkdir -p ../build/dgl + @echo "Compiling $< (OpenGL3 variant)" + $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(PUGL_EXTRA_FLAGS) $(OPENGL_FLAGS) -DDGL_OPENGL -DDGL_USE_OPENGL3 -c -ObjC++ -o $@ # --------------------------------------------------------------------------------------------------------------------- ../build/dgl/%.cpp.vulkan.o: src/%.cpp -@mkdir -p ../build/dgl @echo "Compiling $< (Vulkan variant)" - $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(VULKAN_FLAGS) -DDGL_VULKAN -c -o $@ + $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(PUGL_EXTRA_FLAGS) $(VULKAN_FLAGS) -DDGL_VULKAN -c -o $@ ../build/dgl/%.mm.vulkan.o: src/%.mm -@mkdir -p ../build/dgl @echo "Compiling $< (Vulkan variant)" - $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(VULKAN_FLAGS) -DDGL_VULKAN -c -ObjC++ -o $@ + $(SILENT)$(CXX) $< $(BUILD_CXX_FLAGS) $(PUGL_EXTRA_FLAGS) $(VULKAN_FLAGS) -DDGL_VULKAN -c -ObjC++ -o $@ # --------------------------------------------------------------------------------------------------------------------- @@ -218,6 +257,7 @@ debug: -include $(OBJS_common:%.o=%.d) -include $(OBJS_cairo:%.o=%.d) -include $(OBJS_opengl:%.o=%.d) +-include $(OBJS_opengl3:%.o=%.d) -include $(OBJS_stub:%.o=%.d) -include $(OBJS_vulkan:%.o=%.d) diff --git a/dgl/NanoVG.hpp b/dgl/NanoVG.hpp @@ -23,6 +23,11 @@ #include "TopLevelWidget.hpp" #include "StandaloneWindow.hpp" +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable:4661) /* instantiated template classes whose methods are defined elsewhere */ +#endif + #ifndef DGL_NO_SHARED_RESOURCES # define NANOVG_DEJAVU_SANS_TTF "__dpf_dejavusans_ttf__" #endif @@ -38,6 +43,15 @@ START_NAMESPACE_DGL class NanoVG; // ----------------------------------------------------------------------- +// Helper methods + +/** + Create a NanoVG context using the DPF-provided NanoVG library. + On Windows this will load a few extra OpenGL functions required for NanoVG to work. + */ +NVGcontext* nvgCreateGL(int flags); + +// ----------------------------------------------------------------------- // NanoImage /** @@ -439,6 +453,11 @@ public: */ void globalAlpha(float alpha); + /** + Sets the color tint applied to all rendered shapes. + */ + void globalTint(Color tint); + /* -------------------------------------------------------------------- * Transforms */ @@ -582,12 +601,26 @@ public: NanoImage::Handle createImageFromMemory(uchar* data, uint dataSize, int imageFlags); /** - Creates image from specified image data. + Creates image from specified raw format image data. + */ + NanoImage::Handle createImageFromRawMemory(uint w, uint h, const uchar* data, + ImageFlags imageFlags, ImageFormat format); + + /** + Creates image from specified raw format image data. + Overloaded function for convenience. + @see ImageFlags + */ + NanoImage::Handle createImageFromRawMemory(uint w, uint h, const uchar* data, + int imageFlags, ImageFormat format); + + /** + Creates image from specified RGBA image data. */ NanoImage::Handle createImageFromRGBA(uint w, uint h, const uchar* data, ImageFlags imageFlags); /** - Creates image from specified image data. + Creates image from specified RGBA image data. Overloaded function for convenience. @see ImageFlags */ @@ -884,7 +917,7 @@ public: Constructor for a NanoSubWidget. @see CreateFlags */ - explicit NanoBaseWidget(Widget* const parentGroupWidget, int flags = CREATE_ANTIALIAS); + explicit NanoBaseWidget(Widget* parentGroupWidget, int flags = CREATE_ANTIALIAS); /** Constructor for a NanoTopLevelWidget. @@ -893,21 +926,21 @@ public: explicit NanoBaseWidget(Window& windowToMapTo, int flags = CREATE_ANTIALIAS); /** - Constructor for a NanoStandaloneWindow without parent window. + Constructor for a NanoStandaloneWindow without transient parent window. @see CreateFlags */ explicit NanoBaseWidget(Application& app, int flags = CREATE_ANTIALIAS); /** - Constructor for a NanoStandaloneWindow with parent window. + Constructor for a NanoStandaloneWindow with transient parent window. @see CreateFlags */ - explicit NanoBaseWidget(Application& app, Window& parentWindow, int flags = CREATE_ANTIALIAS); + explicit NanoBaseWidget(Application& app, Window& transientParentWindow, int flags = CREATE_ANTIALIAS); /** Destructor. */ - virtual ~NanoBaseWidget() {} + ~NanoBaseWidget() override {} protected: /** @@ -950,4 +983,8 @@ typedef NanoSubWidget NanoWidget; END_NAMESPACE_DGL +#ifdef _MSC_VER +# pragma warning(pop) +#endif + #endif // DGL_NANO_WIDGET_HPP_INCLUDED diff --git a/dgl/OpenGL-include.hpp b/dgl/OpenGL-include.hpp @@ -0,0 +1,112 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DGL_OPENGL_INCLUDE_HPP_INCLUDED +#define DGL_OPENGL_INCLUDE_HPP_INCLUDED + +#include "../distrho/src/DistrhoDefines.h" + +// -------------------------------------------------------------------------------------------------------------------- +// Fix OpenGL includes for Windows, based on glfw code (part 1) + +#undef DGL_CALLBACK_DEFINED +#undef DGL_WINGDIAPI_DEFINED + +#ifdef DISTRHO_OS_WINDOWS + +#ifndef APIENTRY +# define APIENTRY __stdcall +#endif // APIENTRY + +/* We need WINGDIAPI defined */ +#ifndef WINGDIAPI +# if defined(_MSC_VER) || defined(__BORLANDC__) || defined(__POCC__) +# define WINGDIAPI __declspec(dllimport) +# elif defined(__LCC__) +# define WINGDIAPI __stdcall +# else +# define WINGDIAPI extern +# endif +# define DGL_WINGDIAPI_DEFINED +#endif // WINGDIAPI + +/* Some <GL/glu.h> files also need CALLBACK defined */ +#ifndef CALLBACK +# if defined(_MSC_VER) +# if (defined(_M_MRX000) || defined(_M_IX86) || defined(_M_ALPHA) || defined(_M_PPC)) && !defined(MIDL_PASS) +# define CALLBACK __stdcall +# else +# define CALLBACK +# endif +# else +# define CALLBACK __stdcall +# endif +# define DGL_CALLBACK_DEFINED +#endif // CALLBACK + +#endif // DISTRHO_OS_WINDOWS + +// -------------------------------------------------------------------------------------------------------------------- +// OpenGL includes + +#ifdef DISTRHO_OS_MAC +# ifdef DGL_USE_OPENGL3 +# include <OpenGL/gl3.h> +# include <OpenGL/gl3ext.h> +# else +# include <OpenGL/gl.h> +# endif +#else +# ifndef DISTRHO_OS_WINDOWS +# define GL_GLEXT_PROTOTYPES +# endif +# ifndef __GLEW_H__ +# include <GL/gl.h> +# include <GL/glext.h> +# endif +#endif + +// -------------------------------------------------------------------------------------------------------------------- +// Missing OpenGL defines + +#if defined(GL_BGR_EXT) && !defined(GL_BGR) +# define GL_BGR GL_BGR_EXT +#endif + +#if defined(GL_BGRA_EXT) && !defined(GL_BGRA) +# define GL_BGRA GL_BGRA_EXT +#endif + +#ifndef GL_CLAMP_TO_BORDER +# define GL_CLAMP_TO_BORDER 0x812D +#endif + +// -------------------------------------------------------------------------------------------------------------------- +// Fix OpenGL includes for Windows, based on glfw code (part 2) + +#ifdef DGL_CALLBACK_DEFINED +# undef CALLBACK +# undef DGL_CALLBACK_DEFINED +#endif + +#ifdef DGL_WINGDIAPI_DEFINED +# undef WINGDIAPI +# undef DGL_WINGDIAPI_DEFINED +#endif + +// -------------------------------------------------------------------------------------------------------------------- + +#endif diff --git a/dgl/OpenGL.hpp b/dgl/OpenGL.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -20,96 +20,7 @@ #include "ImageBase.hpp" #include "ImageBaseWidgets.hpp" -// ----------------------------------------------------------------------- -// Fix OpenGL includes for Windows, based on glfw code (part 1) - -#undef DGL_CALLBACK_DEFINED -#undef DGL_WINGDIAPI_DEFINED - -#ifdef DISTRHO_OS_WINDOWS - -#ifndef APIENTRY -# define APIENTRY __stdcall -#endif // APIENTRY - -/* We need WINGDIAPI defined */ -#ifndef WINGDIAPI -# if defined(_MSC_VER) || defined(__BORLANDC__) || defined(__POCC__) -# define WINGDIAPI __declspec(dllimport) -# elif defined(__LCC__) -# define WINGDIAPI __stdcall -# else -# define WINGDIAPI extern -# endif -# define DGL_WINGDIAPI_DEFINED -#endif // WINGDIAPI - -/* Some <GL/glu.h> files also need CALLBACK defined */ -#ifndef CALLBACK -# if defined(_MSC_VER) -# if (defined(_M_MRX000) || defined(_M_IX86) || defined(_M_ALPHA) || defined(_M_PPC)) && !defined(MIDL_PASS) -# define CALLBACK __stdcall -# else -# define CALLBACK -# endif -# else -# define CALLBACK __stdcall -# endif -# define DGL_CALLBACK_DEFINED -#endif // CALLBACK - -/* Most GL/glu.h variants on Windows need wchar_t */ -#include <cstddef> - -#endif // DISTRHO_OS_WINDOWS - -// ----------------------------------------------------------------------- -// OpenGL includes - -#ifdef DISTRHO_OS_MAC -# ifdef DGL_USE_OPENGL3 -# include <OpenGL/gl3.h> -# include <OpenGL/gl3ext.h> -# else -# include <OpenGL/gl.h> -# endif -#else -# ifndef DISTRHO_OS_WINDOWS -# define GL_GLEXT_PROTOTYPES -# endif -# ifndef __GLEW_H__ -# include <GL/gl.h> -# include <GL/glext.h> -# endif -#endif - -// ----------------------------------------------------------------------- -// Missing OpenGL defines - -#if defined(GL_BGR_EXT) && !defined(GL_BGR) -# define GL_BGR GL_BGR_EXT -#endif - -#if defined(GL_BGRA_EXT) && !defined(GL_BGRA) -# define GL_BGRA GL_BGRA_EXT -#endif - -#ifndef GL_CLAMP_TO_BORDER -# define GL_CLAMP_TO_BORDER 0x812D -#endif - -// ----------------------------------------------------------------------- -// Fix OpenGL includes for Windows, based on glfw code (part 2) - -#ifdef DGL_CALLBACK_DEFINED -# undef CALLBACK -# undef DGL_CALLBACK_DEFINED -#endif - -#ifdef DGL_WINGDIAPI_DEFINED -# undef WINGDIAPI -# undef DGL_WINGDIAPI_DEFINED -#endif +#include "OpenGL-include.hpp" START_NAMESPACE_DGL @@ -120,6 +31,8 @@ START_NAMESPACE_DGL */ struct OpenGLGraphicsContext : GraphicsContext { +#ifdef DGL_USE_OPENGL3 +#endif }; // ----------------------------------------------------------------------- @@ -129,7 +42,11 @@ ImageFormat asDISTRHOImageFormat(const GLenum format) { switch (format) { +#ifdef DGL_USE_OPENGL3 + case GL_RED: +#else case GL_LUMINANCE: +#endif return kImageFormatGrayscale; case GL_BGR: return kImageFormatBGR; @@ -152,7 +69,11 @@ GLenum asOpenGLImageFormat(const ImageFormat format) case kImageFormatNull: break; case kImageFormatGrayscale: +#ifdef DGL_USE_OPENGL3 + return GL_RED; +#else return GL_LUMINANCE; +#endif case kImageFormatBGR: return GL_BGR; case kImageFormatBGRA: @@ -230,11 +151,11 @@ public: // FIXME this should not be needed inline void loadFromMemory(const char* rdata, uint w, uint h, ImageFormat fmt = kImageFormatBGRA) - { loadFromMemory(rdata, Size<uint>(w, h), fmt); }; + { loadFromMemory(rdata, Size<uint>(w, h), fmt); } inline void draw(const GraphicsContext& context) - { drawAt(context, Point<int>(0, 0)); }; + { drawAt(context, Point<int>(0, 0)); } inline void drawAt(const GraphicsContext& context, int x, int y) - { drawAt(context, Point<int>(x, y)); }; + { drawAt(context, Point<int>(x, y)); } /** Constructor using raw image data, specifying an OpenGL image format. @@ -297,4 +218,4 @@ typedef ImageBaseSwitch<OpenGLImage> OpenGLImageSwitch; END_NAMESPACE_DGL -#endif +#endif // DGL_OPENGL_HPP_INCLUDED diff --git a/dgl/StandaloneWindow.hpp b/dgl/StandaloneWindow.hpp @@ -71,6 +71,7 @@ public: bool addIdleCallback(IdleCallback* callback, uint timerFrequencyInMs = 0) { return Window::addIdleCallback(callback, timerFrequencyInMs); } bool removeIdleCallback(IdleCallback* callback) { return Window::removeIdleCallback(callback); } + Application& getApp() const noexcept { return Window::getApp(); } const GraphicsContext& getGraphicsContext() const noexcept { return Window::getGraphicsContext(); } double getScaleFactor() const noexcept { return Window::getScaleFactor(); } void setGeometryConstraints(uint minimumWidth, uint minimumHeight, diff --git a/dgl/SubWidget.hpp b/dgl/SubWidget.hpp @@ -47,7 +47,7 @@ public: /** Destructor. */ - virtual ~SubWidget(); + ~SubWidget() override; /** Check if this widget contains the point defined by @a x and @a y. diff --git a/dgl/TopLevelWidget.hpp b/dgl/TopLevelWidget.hpp @@ -54,7 +54,7 @@ public: /** Destructor. */ - virtual ~TopLevelWidget(); + ~TopLevelWidget() override; /** Get the application associated with this top-level widget's window. @@ -101,13 +101,17 @@ public: void repaint(const Rectangle<uint>& rect) noexcept; // TODO group stuff after here, convenience functions present in Window class + const void* getClipboard(size_t& dataSize); + bool setClipboard(const char* mimeType, const void* data, size_t dataSize); + bool setCursor(MouseCursor cursor); bool addIdleCallback(IdleCallback* callback, uint timerFrequencyInMs = 0); bool removeIdleCallback(IdleCallback* callback); double getScaleFactor() const noexcept; void setGeometryConstraints(uint minimumWidth, uint minimumHeight, bool keepAspectRatio = false, - bool automaticallyScale = false); + bool automaticallyScale = false, + bool resizeNowIfAutoScaling = true); DISTRHO_DEPRECATED_BY("getApp()") Application& getParentApp() const noexcept { return getApp(); } @@ -117,7 +121,6 @@ public: protected: bool onKeyboard(const KeyboardEvent&) override; - bool onSpecial(const SpecialEvent&) override; bool onCharacterInput(const CharacterInputEvent&) override; bool onMouse(const MouseEvent&) override; bool onMotion(const MotionEvent&) override; @@ -130,6 +133,8 @@ private: #ifdef DISTRHO_DEFINES_H_INCLUDED friend class DISTRHO_NAMESPACE::UI; #endif + /** @internal */ + virtual void requestSizeChange(uint width, uint height); DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TopLevelWidget) }; diff --git a/dgl/Widget.hpp b/dgl/Widget.hpp @@ -56,17 +56,16 @@ public: /** Base event data. These are the fields present on all Widget events. - - @a mod Currently active keyboard modifiers, @see Modifier. - @a mod Event flags, @see Flag. - @a time Event timestamp (if any). */ struct BaseEvent { + /** Currently active keyboard modifiers. @see Modifier */ uint mod; + /** Event flags. @see EventFlag */ uint flags; + /** Event timestamp (if any). */ uint time; - /** Constructor */ + /** Constructor for default/null values */ BaseEvent() noexcept : mod(0x0), flags(0x0), time(0) {} /** Destuctor */ virtual ~BaseEvent() noexcept {} @@ -86,17 +85,17 @@ public: Alternatively, the raw @a keycode can be used to work directly with physical keys, but note that this value is not portable and differs between platforms and hardware. - @a press True if the key was pressed, false if released. - @a key Unicode point of the key pressed. - @a keycode Raw keycode. @see onKeyboard */ struct KeyboardEvent : BaseEvent { + /** True if the key was pressed, false if released. */ bool press; + /** Unicode point of the key pressed. */ uint key; + /** Raw keycode. */ uint keycode; - /** Constructor */ + /** Constructor for default/null values */ KeyboardEvent() noexcept : BaseEvent(), press(false), @@ -107,18 +106,14 @@ public: /** Special keyboard event. - This event allows the use of keys that do not have unicode points. - Note that some are non-printable keys. - - @a press True if the key was pressed, false if released. - @a key The key pressed. - @see onSpecial + DEPRECATED This used to be part of DPF due to pugl, but now deprecated and simply non-functional. + All events go through KeyboardEvent or CharacterInputEvent, use those instead. */ - struct SpecialEvent : BaseEvent { + struct DISTRHO_DEPRECATED_BY("KeyboardEvent") SpecialEvent : BaseEvent { bool press; - Key key; + Key key; - /** Constructor */ + /** Constructor for default/null values */ SpecialEvent() noexcept : BaseEvent(), press(false), @@ -135,17 +130,17 @@ public: so there is not necessarily a direct correspondence between text events and physical key presses. For example, with some input methods a sequence of several key presses will generate a single character. - @a keycode Raw key code. - @a character Unicode character code. - @a string UTF-8 string. @see onCharacterInput */ struct CharacterInputEvent : BaseEvent { + /** Raw key code. */ uint keycode; + /** Unicode character code. */ uint character; + /** UTF-8 string. */ char string[8]; - /** Constructor */ + /** Constructor for default/null values */ CharacterInputEvent() noexcept : BaseEvent(), keycode(0), @@ -159,20 +154,19 @@ public: /** Mouse press or release event. - - @a button The button number starting from 1 (1 = left, 2 = middle, 3 = right). - @a press True if the button was pressed, false if released. - @a pos The widget-relative coordinates of the pointer. - @a absolutePos The absolute coordinates of the pointer. @see onMouse */ struct MouseEvent : BaseEvent { + /** The button number starting from 1. @see MouseButton */ uint button; + /** True if the button was pressed, false if released. */ bool press; + /** The widget-relative coordinates of the pointer. */ Point<double> pos; + /** The absolute coordinates of the pointer. */ Point<double> absolutePos; - /** Constructor */ + /** Constructor for default/null values */ MouseEvent() noexcept : BaseEvent(), button(0), @@ -183,16 +177,15 @@ public: /** Mouse motion event. - - @a pos The widget-relative coordinates of the pointer. - @a absolutePos The absolute coordinates of the pointer. @see onMotion */ struct MotionEvent : BaseEvent { + /** The widget-relative coordinates of the pointer. */ Point<double> pos; + /** The absolute coordinates of the pointer. */ Point<double> absolutePos; - /** Constructor */ + /** Constructor for default/null values */ MotionEvent() noexcept : BaseEvent(), pos(0.0, 0.0), @@ -208,19 +201,19 @@ public: Some systems and devices support finer resolution and/or higher values for fast scrolls, so programs should handle any value gracefully. - @a pos The widget-relative coordinates of the pointer. - @a absolutePos The absolute coordinates of the pointer. - @a delta The scroll distance. - @a direction The direction of the scroll or "smooth". @see onScroll */ struct ScrollEvent : BaseEvent { + /** The widget-relative coordinates of the pointer. */ Point<double> pos; + /** The absolute coordinates of the pointer. */ Point<double> absolutePos; + /** The scroll distance. */ Point<double> delta; + /** The direction of the scroll or "smooth". */ ScrollDirection direction; - /** Constructor */ + /** Constructor for default/null values */ ScrollEvent() noexcept : BaseEvent(), pos(0.0, 0.0), @@ -231,15 +224,15 @@ public: /** Resize event. - @a size The new widget size. - @a oldSize The previous size, may be null. @see onResize */ struct ResizeEvent { + /** The new widget size. */ Size<uint> size; + /** The previous size, can be null. */ Size<uint> oldSize; - /** Constructor */ + /** Constructor for default/null values */ ResizeEvent() noexcept : size(0, 0), oldSize(0, 0) {} @@ -247,15 +240,15 @@ public: /** Widget position changed event. - @a pos The new absolute position of the widget. - @a oldPos The previous absolute position of the widget. @see onPositionChanged */ struct PositionChangedEvent { + /** The new absolute position of the widget. */ Point<int> pos; + /** The previous absolute position of the widget. */ Point<int> oldPos; - /** Constructor */ + /** Constructor for default/null values */ PositionChangedEvent() noexcept : pos(0, 0), oldPos(0, 0) {} @@ -399,12 +392,6 @@ protected: virtual bool onKeyboard(const KeyboardEvent&); /** - A function called when a special key is pressed or released. - @return True to stop event propagation, false otherwise. - */ - virtual bool onSpecial(const SpecialEvent&); - - /** A function called when an UTF-8 character is received. @return True to stop event propagation, false otherwise. */ @@ -433,6 +420,24 @@ protected: */ virtual void onResize(const ResizeEvent&); + /** + A function called when a special key is pressed or released. + DEPRECATED use onKeyboard or onCharacterInput + */ +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-declarations" +#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 460 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + virtual bool onSpecial(const SpecialEvent&) { return false; } +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 460 +# pragma GCC diagnostic pop +#endif + private: struct PrivateData; PrivateData* const pData; diff --git a/dgl/Window.hpp b/dgl/Window.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -19,6 +19,18 @@ #include "Geometry.hpp" +#ifndef DGL_FILE_BROWSER_DISABLED +# include "FileBrowserDialog.hpp" +#endif + +#include <vector> + +#ifdef DISTRHO_NAMESPACE +START_NAMESPACE_DISTRHO +class PluginWindow; +END_NAMESPACE_DISTRHO +#endif + START_NAMESPACE_DGL class Application; @@ -47,60 +59,11 @@ class TopLevelWidget; ... */ -class Window +class DISTRHO_API Window { struct PrivateData; public: -#ifndef DGL_FILE_BROWSER_DISABLED - /** - File browser options. - @see Window::openFileBrowser - */ - struct FileBrowserOptions { - /** - File browser button state. - This allows to customize the behaviour of the file browse dialog buttons. - Note these are merely hints, not all systems support them. - */ - enum ButtonState { - kButtonInvisible, - kButtonVisibleUnchecked, - kButtonVisibleChecked, - }; - - /** Start directory, uses current working directory if null */ - const char* startDir; - /** File browser dialog window title, uses "FileBrowser" if null */ - const char* title; - // TODO file filter - - /** - File browser buttons. - */ - struct Buttons { - /** Whether to list all files vs only those with matching file extension */ - ButtonState listAllFiles; - /** Whether to show hidden files */ - ButtonState showHidden; - /** Whether to show list of places (bookmarks) */ - ButtonState showPlaces; - - /** Constructor for default values */ - Buttons() - : listAllFiles(kButtonVisibleChecked), - showHidden(kButtonVisibleUnchecked), - showPlaces(kButtonVisibleChecked) {} - } buttons; - - /** Constructor for default values */ - FileBrowserOptions() - : startDir(nullptr), - title(nullptr), - buttons() {} - }; -#endif // DGL_FILE_BROWSER_DISABLED - /** Window graphics context as a scoped struct. This class gives graphics context drawing time to a window's widgets. @@ -157,10 +120,10 @@ public: explicit Window(Application& app); /** - Constructor for a modal window, by having another window as its parent. + Constructor for a modal window, by having another window as its transient parent. The Application instance must be the same between the 2 windows. */ - explicit Window(Application& app, Window& parent); + explicit Window(Application& app, Window& transientParentWindow); /** Constructor for an embed Window without known size, @@ -246,6 +209,41 @@ public: void setResizable(bool resizable); /** + Get X offset, typically 0. + */ + int getOffsetX() const noexcept; + + /** + Get Y offset, typically 0. + */ + int getOffsetY() const noexcept; + + /** + Get offset. + */ + Point<int> getOffset() const noexcept; + + /** + Set X offset. + */ + void setOffsetX(int x); + + /** + Set Y offset. + */ + void setOffsetY(int y); + + /** + Set offset using @a x and @a y values. + */ + void setOffset(int x, int y); + + /** + Set offset. + */ + void setOffset(const Point<int>& offset); + + /** Get width. */ uint getWidth() const noexcept; @@ -303,6 +301,39 @@ public: void setIgnoringKeyRepeat(bool ignore) noexcept; /** + Get the clipboard contents. + + This gets the system clipboard contents, + which may have been set with setClipboard() or copied from another application. + + Returns the clipboard contents, or null. + + @note By default only "text/plain" mimetype is supported and returned. + Override onClipboardDataOffer for supporting other types. + */ + const void* getClipboard(size_t& dataSize); + + /** + Set the clipboard contents. + + This sets the system clipboard contents, + which can be retrieved with getClipboard() or pasted into other applications. + + If using a string, the use of a null terminator is required (and must be part of dataSize).@n + The MIME type of the data "text/plain" is assumed if null is used. + */ + bool setClipboard(const char* mimeType, const void* data, size_t dataSize); + + /** + Set the mouse cursor. + + This changes the system cursor that is displayed when the pointer is inside the window. + May fail if setting the cursor is not supported on this system, + for example if compiled on X11 without Xcursor support. + */ + bool setCursor(MouseCursor cursor); + + /** Add a callback function to be triggered on every idle cycle or on a specific timer frequency. You can add more than one, and remove them at anytime with removeIdleCallback(). This can be used to perform some action at a regular interval with relatively low frequency. @@ -361,7 +392,7 @@ public: #ifndef DGL_FILE_BROWSER_DISABLED /** - Open a file browser dialog with this window as parent. + Open a file browser dialog with this window as transient parent. A few options can be specified to setup the dialog. If a path is selected, onFileSelected() will be called with the user chosen path. @@ -369,7 +400,7 @@ public: This function does not block the event loop. */ - bool openFileBrowser(const FileBrowserOptions& options); + bool openFileBrowser(const DGL_NAMESPACE::FileBrowserOptions& options = FileBrowserOptions()); #endif /** @@ -383,6 +414,13 @@ public: void repaint(const Rectangle<uint>& rect) noexcept; /** + Render this window's content into a picture file, specified by @a filename. + Window must be visible and on screen. + Written picture format is PPM. + */ + void renderToPicture(const char* filename); + + /** Run this window as a modal, blocking input events from the parent. Only valid for windows that have been created with another window as parent (as passed in the constructor). Can optionally block-wait, but such option is only available if the application is running as standalone. @@ -390,12 +428,27 @@ public: void runAsModal(bool blockWait = false); /** + Get the geometry constraints set for the Window. + @see setGeometryConstraints + */ + Size<uint> getGeometryConstraints(bool& keepAspectRatio); + + /** Set geometry constraints for the Window when resized by the user, and optionally scale contents automatically. */ void setGeometryConstraints(uint minimumWidth, uint minimumHeight, bool keepAspectRatio = false, - bool automaticallyScale = false); + bool automaticallyScale = false, + bool resizeNowIfAutoScaling = true); + + /** + Set the transient parent of the window. + + Set this for transient children like dialogs, to have them properly associated with their parent window. + This should be not be called for embed windows, or after making the window visible. + */ + void setTransientParent(uintptr_t transientParentWindowHandle); /** DEPRECATED Use isIgnoringKeyRepeat(). */ DISTRHO_DEPRECATED_BY("isIgnoringKeyRepeat()") @@ -411,6 +464,23 @@ public: protected: /** + Get the types available for the data in a clipboard. + Must only be called within the context of onClipboardDataOffer. + */ + std::vector<ClipboardDataOffer> getClipboardDataOfferTypes(); + + /** + A function called when clipboard has data present, possibly with several datatypes. + While handling this event, the data types can be investigated with getClipboardDataOfferTypes() to decide whether to accept the offer. + + Reimplement and return a non-zero id to accept the clipboard data offer for a particular type. + Applications must ignore any type they do not recognize. + + The default implementation accepts the "text/plain" mimetype. + */ + virtual uint32_t onClipboardDataOffer(); + + /** A function called when the window is attempted to be closed. Returning true closes the window, which is the default behaviour. Override this method and return false to prevent the window from being closed by the user. @@ -459,8 +529,10 @@ protected: private: PrivateData* const pData; friend class Application; - friend class PluginWindow; friend class TopLevelWidget; + #ifdef DISTRHO_NAMESPACE + friend class DISTRHO_NAMESPACE::PluginWindow; + #endif /** @internal */ explicit Window(Application& app, @@ -469,9 +541,10 @@ private: uint height, double scaleFactor, bool resizable, + bool isVST3, bool doPostInit); - DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Window); + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Window) }; // ----------------------------------------------------------------------- diff --git a/dgl/src/Application.cpp b/dgl/src/Application.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -16,10 +16,23 @@ #include "ApplicationPrivateData.hpp" +#if defined(__EMSCRIPTEN__) +# include <emscripten/emscripten.h> +#elif defined(DISTRHO_OS_MAC) +# include <CoreFoundation/CoreFoundation.h> +#endif + START_NAMESPACE_DGL // -------------------------------------------------------------------------------------------------------------------- +#ifdef __EMSCRIPTEN__ +static void app_idle(void* const app) +{ + static_cast<Application*>(app)->idle(); +} +#endif + Application::Application(const bool isStandalone) : pData(new PrivateData(isStandalone)) {} @@ -37,8 +50,22 @@ void Application::exec(const uint idleTimeInMs) { DISTRHO_SAFE_ASSERT_RETURN(pData->isStandalone,); +#if defined(__EMSCRIPTEN__) + emscripten_set_main_loop_arg(app_idle, this, 0, true); +#elif defined(DISTRHO_OS_MAC) + const CFTimeInterval idleTimeInSecs = static_cast<CFTimeInterval>(idleTimeInMs) / 1000; + + while (! pData->isQuitting) + { + pData->idle(0); + + if (CFRunLoopRunInMode(kCFRunLoopDefaultMode, idleTimeInSecs, true) == kCFRunLoopRunFinished) + break; + } +#else while (! pData->isQuitting) pData->idle(idleTimeInMs); +#endif } void Application::quit() @@ -56,6 +83,11 @@ bool Application::isStandalone() const noexcept return pData->isStandalone; } +double Application::getTime() const +{ + return pData->getTime(); +} + void Application::addIdleCallback(IdleCallback* const callback) { DISTRHO_SAFE_ASSERT_RETURN(callback != nullptr,) diff --git a/dgl/src/ApplicationPrivateData.cpp b/dgl/src/ApplicationPrivateData.cpp @@ -45,6 +45,13 @@ static bool isThisTheMainThread(const d_ThreadHandle mainThreadHandle) noexcept // -------------------------------------------------------------------------------------------------------------------- +const char* Application::getClassName() const noexcept +{ + return puglGetClassName(pData->world); +} + +// -------------------------------------------------------------------------------------------------------------------- + Application::PrivateData::PrivateData(const bool standalone) : world(puglNewWorld(standalone ? PUGL_PROGRAM : PUGL_MODULE, standalone ? PUGL_WORLD_THREADS : 0x0)), @@ -60,11 +67,8 @@ Application::PrivateData::PrivateData(const bool standalone) DISTRHO_SAFE_ASSERT_RETURN(world != nullptr,); puglSetWorldHandle(world, this); +#ifndef __EMSCRIPTEN__ puglSetClassName(world, DISTRHO_MACRO_AS_STRING(DGL_NAMESPACE)); - -#ifdef DISTRHO_OS_MAC - if (standalone) - puglMacOSActivateApp(); #endif } @@ -118,6 +122,11 @@ void Application::PrivateData::idle(const uint timeoutInMs) puglUpdate(world, timeoutInSeconds); } + triggerIdleCallbacks(); +} + +void Application::PrivateData::triggerIdleCallbacks() +{ for (std::list<IdleCallback*>::iterator it = idleCallbacks.begin(), ite = idleCallbacks.end(); it != ite; ++it) { IdleCallback* const idleCallback(*it); @@ -147,6 +156,13 @@ void Application::PrivateData::quit() #endif } +double Application::PrivateData::getTime() const +{ + DISTRHO_SAFE_ASSERT_RETURN(world != nullptr, 0.0); + + return puglGetTime(world); +} + void Application::PrivateData::setClassName(const char* const name) { DISTRHO_SAFE_ASSERT_RETURN(world != nullptr,); diff --git a/dgl/src/ApplicationPrivateData.hpp b/dgl/src/ApplicationPrivateData.hpp @@ -22,6 +22,9 @@ #include <list> #ifdef DISTRHO_OS_WINDOWS +# ifndef NOMINMAX +# define NOMINMAX +# endif # include <winsock2.h> # include <windows.h> typedef HANDLE d_ThreadHandle; @@ -30,12 +33,18 @@ typedef HANDLE d_ThreadHandle; typedef pthread_t d_ThreadHandle; #endif +#ifdef DISTRHO_OS_MAC typedef struct PuglWorldImpl PuglWorld; +#endif START_NAMESPACE_DGL class Window; +#ifndef DISTRHO_OS_MAC +typedef struct PuglWorldImpl PuglWorld; +#endif + // -------------------------------------------------------------------------------------------------------------------- struct Application::PrivateData { @@ -84,10 +93,16 @@ struct Application::PrivateData { /** Run Pugl world update for @a timeoutInMs, and then each idle callback in order of registration. */ void idle(uint timeoutInMs); + /** Run each idle callback without updating pugl world. */ + void triggerIdleCallbacks(); + /** Set flag indicating application is quitting, and close all windows in reverse order of registration. For standalone mode only. */ void quit(); + /** Get time via pugl */ + double getTime() const; + /** Set pugl world class name. */ void setClassName(const char* name); diff --git a/dgl/src/Cairo.cpp b/dgl/src/Cairo.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * Copyright (C) 2019-2021 Jean Pierre Cimalando <jp-dev@inbox.ru> * * Permission to use, copy, modify, and/or distribute this software for any purpose with @@ -384,8 +384,8 @@ void CairoImage::loadFromMemory(const char* const rdata, const Size<uint>& s, co cairo_surface_t* const newsurface = cairo_image_surface_create_for_data(newdata, cairoformat, width, height, stride); DISTRHO_SAFE_ASSERT_RETURN(newsurface != nullptr,); - DISTRHO_SAFE_ASSERT_RETURN(s.getWidth() == cairo_image_surface_get_width(newsurface),); - DISTRHO_SAFE_ASSERT_RETURN(s.getHeight() == cairo_image_surface_get_height(newsurface),); + DISTRHO_SAFE_ASSERT_RETURN(static_cast<int>(s.getWidth()) == cairo_image_surface_get_width(newsurface),); + DISTRHO_SAFE_ASSERT_RETURN(static_cast<int>(s.getHeight()) == cairo_image_surface_get_height(newsurface),); cairo_surface_destroy(surface); @@ -808,6 +808,13 @@ void TopLevelWidget::PrivateData::display() // ----------------------------------------------------------------------- +void Window::PrivateData::renderToPicture(const char*, const GraphicsContext&, uint, uint) +{ + notImplemented("Window::PrivateData::renderToPicture"); +} + +// ----------------------------------------------------------------------- + const GraphicsContext& Window::PrivateData::getGraphicsContext() const noexcept { GraphicsContext& context((GraphicsContext&)graphicsContext); diff --git a/dgl/src/Color.cpp b/dgl/src/Color.cpp @@ -114,10 +114,10 @@ Color::Color(const Color& color1, const Color& color2, const float u) noexcept interpolate(color2, u); } -Color Color::withAlpha(const float alpha) noexcept +Color Color::withAlpha(const float alpha2) noexcept { Color color(*this); - color.alpha = alpha; + color.alpha = alpha2; return color; } diff --git a/dgl/src/Geometry.cpp b/dgl/src/Geometry.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2019 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -15,8 +15,10 @@ */ #ifdef _MSC_VER -// instantiated template classes whose methods are defined elsewhere -# pragma warning(disable:4661) +# pragma warning(disable:4661) /* instantiated template classes whose methods are defined elsewhere */ +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wconversion" #endif #include "../Geometry.hpp" diff --git a/dgl/src/ImageBaseWidgets.cpp b/dgl/src/ImageBaseWidgets.cpp @@ -22,8 +22,8 @@ START_NAMESPACE_DGL // -------------------------------------------------------------------------------------------------------------------- template <class ImageType> -ImageBaseAboutWindow<ImageType>::ImageBaseAboutWindow(Window& parentWindow, const ImageType& image) - : StandaloneWindow(parentWindow.getApp(), parentWindow), +ImageBaseAboutWindow<ImageType>::ImageBaseAboutWindow(Window& transientParentWindow, const ImageType& image) + : StandaloneWindow(transientParentWindow.getApp(), transientParentWindow), img(image) { setResizable(false); @@ -39,8 +39,8 @@ ImageBaseAboutWindow<ImageType>::ImageBaseAboutWindow(Window& parentWindow, cons } template <class ImageType> -ImageBaseAboutWindow<ImageType>::ImageBaseAboutWindow(TopLevelWidget* const parentTopLevelWidget, const ImageType& image) - : StandaloneWindow(parentTopLevelWidget->getApp(), parentTopLevelWidget->getWindow()), +ImageBaseAboutWindow<ImageType>::ImageBaseAboutWindow(TopLevelWidget* const topLevelWidget, const ImageType& image) + : StandaloneWindow(topLevelWidget->getApp(), topLevelWidget->getWindow()), img(image) { setResizable(false); diff --git a/dgl/src/NanoVG.cpp b/dgl/src/NanoVG.cpp @@ -54,6 +54,18 @@ DGL_EXT(PFNGLUNIFORM4FVPROC, glUniform4fv) DGL_EXT(PFNGLUSEPROGRAMPROC, glUseProgram) DGL_EXT(PFNGLVERTEXATTRIBPOINTERPROC, glVertexAttribPointer) DGL_EXT(PFNGLBLENDFUNCSEPARATEPROC, glBlendFuncSeparate) +# ifdef DGL_USE_NANOVG_FBO +DGL_EXT(PFNGLCHECKFRAMEBUFFERSTATUSPROC, glCheckFramebufferStatus) +DGL_EXT(PFNGLBINDFRAMEBUFFERPROC, glBindFramebuffer) +DGL_EXT(PFNGLBINDRENDERBUFFERPROC, glBindRenderbuffer) +DGL_EXT(PFNGLDELETEFRAMEBUFFERSPROC, glDeleteFramebuffers) +DGL_EXT(PFNGLDELETERENDERBUFFERSPROC, glDeleteRenderbuffers) +DGL_EXT(PFNGLFRAMEBUFFERTEXTURE2DPROC, glFramebufferTexture2D) +DGL_EXT(PFNGLFRAMEBUFFERRENDERBUFFERPROC, glFramebufferRenderbuffer) +DGL_EXT(PFNGLGENFRAMEBUFFERSPROC, glGenFramebuffers) +DGL_EXT(PFNGLGENRENDERBUFFERSPROC, glGenRenderbuffers) +DGL_EXT(PFNGLRENDERBUFFERSTORAGEPROC, glRenderbufferStorage) +# endif # ifdef DGL_USE_OPENGL3 DGL_EXT(PFNGLBINDBUFFERRANGEPROC, glBindBufferRange) DGL_EXT(PFNGLBINDVERTEXARRAYPROC, glBindVertexArray) @@ -70,13 +82,15 @@ DGL_EXT(PFNGLUNIFORMBLOCKBINDINGPROC, glUniformBlockBinding) // Include NanoVG OpenGL implementation //#define STB_IMAGE_STATIC -#ifdef DGL_USE_OPENGL3 +#if defined(DGL_USE_GLES2) +# define NANOVG_GLES2_IMPLEMENTATION +#elif defined(DGL_USE_OPENGL3) # define NANOVG_GL3_IMPLEMENTATION #else # define NANOVG_GL2_IMPLEMENTATION #endif -#if defined(DISTRHO_OS_MAC) && defined(NANOVG_GL3_IMPLEMENTATION) +#if defined(DISTRHO_OS_MAC) && defined(NANOVG_GL2_IMPLEMENTATION) # define glBindVertexArray glBindVertexArrayAPPLE # define glDeleteVertexArrays glDeleteVertexArraysAPPLE # define glGenVertexArrays glGenVertexArraysAPPLE @@ -84,29 +98,38 @@ DGL_EXT(PFNGLUNIFORMBLOCKBINDINGPROC, glUniformBlockBinding) #include "nanovg/nanovg_gl.h" +#ifdef DGL_USE_NANOVG_FBO +# define NANOVG_FBO_VALID 1 +# include "nanovg/nanovg_gl_utils.h" +#endif + #if defined(NANOVG_GL2) -# define nvgCreateGL nvgCreateGL2 +# define nvgCreateGLfn nvgCreateGL2 # define nvgDeleteGL nvgDeleteGL2 # define nvglCreateImageFromHandle nvglCreateImageFromHandleGL2 # define nvglImageHandle nvglImageHandleGL2 #elif defined(NANOVG_GL3) -# define nvgCreateGL nvgCreateGL3 +# define nvgCreateGLfn nvgCreateGL3 # define nvgDeleteGL nvgDeleteGL3 # define nvglCreateImageFromHandle nvglCreateImageFromHandleGL3 # define nvglImageHandle nvglImageHandleGL3 #elif defined(NANOVG_GLES2) -# define nvgCreateGL nvgCreateGLES2 +# define nvgCreateGLfn nvgCreateGLES2 # define nvgDeleteGL nvgDeleteGLES2 # define nvglCreateImageFromHandle nvglCreateImageFromHandleGLES2 # define nvglImageHandle nvglImageHandleGLES2 #elif defined(NANOVG_GLES3) -# define nvgCreateGL nvgCreateGLES3 +# define nvgCreateGLfn nvgCreateGLES3 # define nvgDeleteGL nvgDeleteGLES3 # define nvglCreateImageFromHandle nvglCreateImageFromHandleGLES3 # define nvglImageHandle nvglImageHandleGLES3 #endif -static NVGcontext* nvgCreateGL_helper(int flags) +// ----------------------------------------------------------------------- + +START_NAMESPACE_DGL + +NVGcontext* nvgCreateGL(int flags) { #if defined(DISTRHO_OS_WINDOWS) # if defined(__GNUC__) && (__GNUC__ >= 9) @@ -117,6 +140,11 @@ static NVGcontext* nvgCreateGL_helper(int flags) # define DGL_EXT(PROC, func) \ if (needsInit) func = (PROC) wglGetProcAddress ( #func ); \ DISTRHO_SAFE_ASSERT_RETURN(func != nullptr, nullptr); +# define DGL_EXT2(PROC, func, fallback) \ + if (needsInit) { \ + func = (PROC) wglGetProcAddress ( #func ); \ + if (func == nullptr) func = (PROC) wglGetProcAddress ( #fallback ); \ + } DISTRHO_SAFE_ASSERT_RETURN(func != nullptr, nullptr); DGL_EXT(PFNGLACTIVETEXTUREPROC, glActiveTexture) DGL_EXT(PFNGLATTACHSHADERPROC, glAttachShader) DGL_EXT(PFNGLBINDATTRIBLOCATIONPROC, glBindAttribLocation) @@ -145,6 +173,18 @@ DGL_EXT(PFNGLUNIFORM4FVPROC, glUniform4fv) DGL_EXT(PFNGLUSEPROGRAMPROC, glUseProgram) DGL_EXT(PFNGLVERTEXATTRIBPOINTERPROC, glVertexAttribPointer) DGL_EXT(PFNGLBLENDFUNCSEPARATEPROC, glBlendFuncSeparate) +# ifdef DGL_USE_NANOVG_FBO +DGL_EXT(PFNGLCHECKFRAMEBUFFERSTATUSPROC, glCheckFramebufferStatus) +DGL_EXT2(PFNGLBINDFRAMEBUFFERPROC, glBindFramebuffer, glBindFramebufferEXT) +DGL_EXT2(PFNGLBINDRENDERBUFFERPROC, glBindRenderbuffer, glBindRenderbufferEXT) +DGL_EXT2(PFNGLDELETEFRAMEBUFFERSPROC, glDeleteFramebuffers, glDeleteFramebuffersEXT) +DGL_EXT2(PFNGLDELETERENDERBUFFERSPROC, glDeleteRenderbuffers, glDeleteRenderbuffersEXT) +DGL_EXT2(PFNGLFRAMEBUFFERTEXTURE2DPROC, glFramebufferTexture2D, glFramebufferTexture2DEXT) +DGL_EXT2(PFNGLFRAMEBUFFERRENDERBUFFERPROC, glFramebufferRenderbuffer, glFramebufferRenderbufferEXT) +DGL_EXT2(PFNGLGENFRAMEBUFFERSPROC, glGenFramebuffers, glGenFramebuffersEXT) +DGL_EXT2(PFNGLGENRENDERBUFFERSPROC, glGenRenderbuffers, glGenRenderbuffersEXT) +DGL_EXT2(PFNGLRENDERBUFFERSTORAGEPROC, glRenderbufferStorage, glRenderbufferStorageEXT) +# endif # ifdef DGL_USE_OPENGL3 DGL_EXT(PFNGLBINDBUFFERRANGEPROC, glBindBufferRange) DGL_EXT(PFNGLBINDVERTEXARRAYPROC, glBindVertexArray) @@ -155,19 +195,16 @@ DGL_EXT(PFNGLGENVERTEXARRAYSPROC, glGenVertexArrays) DGL_EXT(PFNGLUNIFORMBLOCKBINDINGPROC, glUniformBlockBinding) # endif # undef DGL_EXT +# undef DGL_EXT2 needsInit = false; # if defined(__GNUC__) && (__GNUC__ >= 9) # pragma GCC diagnostic pop # endif #endif - return nvgCreateGL(flags); + return nvgCreateGLfn(flags); } // ----------------------------------------------------------------------- - -START_NAMESPACE_DGL - -// ----------------------------------------------------------------------- // DGL Color class conversion Color::Color(const NVGcolor& c) noexcept @@ -215,6 +252,7 @@ NanoImage& NanoImage::operator=(const Handle& handle) fHandle.context = handle.context; fHandle.imageId = handle.imageId; + _updateSize(); return *this; } @@ -282,13 +320,16 @@ NanoVG::Paint::operator NVGpaint() const noexcept // NanoVG NanoVG::NanoVG(int flags) - : fContext(nvgCreateGL_helper(flags)), + : fContext(nvgCreateGL(flags)), fInFrame(false), - fIsSubWidget(false) {} + fIsSubWidget(false) +{ + DISTRHO_CUSTOM_SAFE_ASSERT("Failed to create NanoVG context, expect a black screen", fContext != nullptr); +} NanoVG::~NanoVG() { - DISTRHO_SAFE_ASSERT(! fInFrame); + DISTRHO_CUSTOM_SAFE_ASSERT("Destroying NanoVG context with still active frame", ! fInFrame); if (fContext != nullptr && ! fIsSubWidget) nvgDeleteGL(fContext); @@ -483,6 +524,12 @@ void NanoVG::globalAlpha(float alpha) nvgGlobalAlpha(fContext, alpha); } +void NanoVG::globalTint(Color tint) +{ + if (fContext != nullptr) + nvgGlobalTint(fContext, tint); +} + // ----------------------------------------------------------------------- // Transforms @@ -631,6 +678,45 @@ NanoImage::Handle NanoVG::createImageFromMemory(uchar* data, uint dataSize, int return NanoImage::Handle(fContext, nvgCreateImageMem(fContext, imageFlags, data,static_cast<int>(dataSize))); } +NanoImage::Handle NanoVG::createImageFromRawMemory(uint w, uint h, const uchar* data, + ImageFlags imageFlags, ImageFormat format) +{ + return createImageFromRawMemory(w, h, data, static_cast<int>(imageFlags), format); +} + +NanoImage::Handle NanoVG::createImageFromRawMemory(uint w, uint h, const uchar* data, + int imageFlags, ImageFormat format) +{ + if (fContext == nullptr) return NanoImage::Handle(); + DISTRHO_SAFE_ASSERT_RETURN(data != nullptr, NanoImage::Handle()); + + NVGtexture nvgformat; + switch (format) + { + case kImageFormatGrayscale: + nvgformat = NVG_TEXTURE_ALPHA; + break; + case kImageFormatBGR: + nvgformat = NVG_TEXTURE_BGR; + break; + case kImageFormatBGRA: + nvgformat = NVG_TEXTURE_BGRA; + break; + case kImageFormatRGB: + nvgformat = NVG_TEXTURE_RGB; + break; + case kImageFormatRGBA: + nvgformat = NVG_TEXTURE_RGBA; + break; + default: + return NanoImage::Handle(); + } + + return NanoImage::Handle(fContext, nvgCreateImageRaw(fContext, + static_cast<int>(w), + static_cast<int>(h), imageFlags, nvgformat, data)); +} + NanoImage::Handle NanoVG::createImageFromRGBA(uint w, uint h, const uchar* data, ImageFlags imageFlags) { return createImageFromRGBA(w, h, data, static_cast<int>(imageFlags)); @@ -646,12 +732,14 @@ NanoImage::Handle NanoVG::createImageFromRGBA(uint w, uint h, const uchar* data, static_cast<int>(h), imageFlags, data)); } -NanoImage::Handle NanoVG::createImageFromTextureHandle(GLuint textureId, uint w, uint h, ImageFlags imageFlags, bool deleteTexture) +NanoImage::Handle NanoVG::createImageFromTextureHandle(GLuint textureId, uint w, uint h, + ImageFlags imageFlags, bool deleteTexture) { return createImageFromTextureHandle(textureId, w, h, static_cast<int>(imageFlags), deleteTexture); } -NanoImage::Handle NanoVG::createImageFromTextureHandle(GLuint textureId, uint w, uint h, int imageFlags, bool deleteTexture) +NanoImage::Handle NanoVG::createImageFromTextureHandle(GLuint textureId, uint w, uint h, + int imageFlags, bool deleteTexture) { if (fContext == nullptr) return NanoImage::Handle(); DISTRHO_SAFE_ASSERT_RETURN(textureId != 0, NanoImage::Handle()); diff --git a/dgl/src/OpenGL.cpp b/dgl/src/OpenGL.cpp @@ -34,19 +34,47 @@ START_NAMESPACE_DGL // ----------------------------------------------------------------------- + +#if defined(DGL_USE_GLES2) +static void notImplemented(const char* const name) +{ +// d_stderr2("GLES2 function not implemented: %s", name); +} +#elif defined(DGL_USE_GLES3) +static void notImplemented(const char* const name) +{ + d_stderr2("GLES3 function not implemented: %s", name); +} +#elif defined(DGL_USE_OPENGL3) +static void notImplemented(const char* const name) +{ + d_stderr2("OpenGL3 function not implemented: %s", name); +} +#else +# define DGL_USE_COMPAT_OPENGL +#endif + +// ----------------------------------------------------------------------- // Color void Color::setFor(const GraphicsContext&, const bool includeAlpha) { +#ifdef DGL_USE_COMPAT_OPENGL if (includeAlpha) glColor4f(red, green, blue, alpha); else glColor3f(red, green, blue); +#else + notImplemented("Color::setFor"); + // unused + (void)includeAlpha; +#endif } // ----------------------------------------------------------------------- // Line +#ifdef DGL_USE_COMPAT_OPENGL template<typename T> static void drawLine(const Point<T>& posStart, const Point<T>& posEnd) { @@ -61,21 +89,30 @@ static void drawLine(const Point<T>& posStart, const Point<T>& posEnd) glEnd(); } +#endif template<typename T> void Line<T>::draw(const GraphicsContext&, const T width) { +#ifdef DGL_USE_COMPAT_OPENGL DISTRHO_SAFE_ASSERT_RETURN(width != 0,); glLineWidth(static_cast<GLfloat>(width)); drawLine<T>(posStart, posEnd); +#else + notImplemented("Line::draw"); +#endif } // deprecated calls template<typename T> void Line<T>::draw() { +#ifdef DGL_USE_COMPAT_OPENGL drawLine<T>(posStart, posEnd); +#else + notImplemented("Line::draw"); +#endif } template class Line<double>; @@ -88,6 +125,7 @@ template class Line<ushort>; // ----------------------------------------------------------------------- // Circle +#ifdef DGL_USE_COMPAT_OPENGL template<typename T> static void drawCircle(const Point<T>& pos, const uint numSegments, @@ -115,11 +153,16 @@ static void drawCircle(const Point<T>& pos, glEnd(); } +#endif template<typename T> void Circle<T>::draw(const GraphicsContext&) { +#ifdef DGL_USE_COMPAT_OPENGL drawCircle<T>(fPos, fNumSegments, fSize, fSin, fCos, false); +#else + notImplemented("Circle::draw"); +#endif } template<typename T> @@ -128,20 +171,32 @@ void Circle<T>::drawOutline(const GraphicsContext&, const T lineWidth) DISTRHO_SAFE_ASSERT_RETURN(lineWidth != 0,); glLineWidth(static_cast<GLfloat>(lineWidth)); +#ifdef DGL_USE_COMPAT_OPENGL drawCircle<T>(fPos, fNumSegments, fSize, fSin, fCos, true); +#else + notImplemented("Circle::drawOutline"); +#endif } // deprecated calls template<typename T> void Circle<T>::draw() { +#ifdef DGL_USE_COMPAT_OPENGL drawCircle<T>(fPos, fNumSegments, fSize, fSin, fCos, false); +#else + notImplemented("Circle::draw"); +#endif } template<typename T> void Circle<T>::drawOutline() { +#ifdef DGL_USE_COMPAT_OPENGL drawCircle<T>(fPos, fNumSegments, fSize, fSin, fCos, true); +#else + notImplemented("Circle::drawOutline"); +#endif } template class Circle<double>; @@ -154,6 +209,7 @@ template class Circle<ushort>; // ----------------------------------------------------------------------- // Triangle +#ifdef DGL_USE_COMPAT_OPENGL template<typename T> static void drawTriangle(const Point<T>& pos1, const Point<T>& pos2, @@ -172,11 +228,16 @@ static void drawTriangle(const Point<T>& pos1, glEnd(); } +#endif template<typename T> void Triangle<T>::draw(const GraphicsContext&) { +#ifdef DGL_USE_COMPAT_OPENGL drawTriangle<T>(pos1, pos2, pos3, false); +#else + notImplemented("Triangle::draw"); +#endif } template<typename T> @@ -185,20 +246,32 @@ void Triangle<T>::drawOutline(const GraphicsContext&, const T lineWidth) DISTRHO_SAFE_ASSERT_RETURN(lineWidth != 0,); glLineWidth(static_cast<GLfloat>(lineWidth)); +#ifdef DGL_USE_COMPAT_OPENGL drawTriangle<T>(pos1, pos2, pos3, true); +#else + notImplemented("Triangle::drawOutline"); +#endif } // deprecated calls template<typename T> void Triangle<T>::draw() { +#ifdef DGL_USE_COMPAT_OPENGL drawTriangle<T>(pos1, pos2, pos3, false); +#else + notImplemented("Triangle::draw"); +#endif } template<typename T> void Triangle<T>::drawOutline() { +#ifdef DGL_USE_COMPAT_OPENGL drawTriangle<T>(pos1, pos2, pos3, true); +#else + notImplemented("Triangle::drawOutline"); +#endif } template class Triangle<double>; @@ -211,6 +284,7 @@ template class Triangle<ushort>; // ----------------------------------------------------------------------- // Rectangle +#ifdef DGL_USE_COMPAT_OPENGL template<typename T> static void drawRectangle(const Rectangle<T>& rect, const bool outline) { @@ -239,11 +313,16 @@ static void drawRectangle(const Rectangle<T>& rect, const bool outline) glEnd(); } +#endif template<typename T> void Rectangle<T>::draw(const GraphicsContext&) { +#ifdef DGL_USE_COMPAT_OPENGL drawRectangle<T>(*this, false); +#else + notImplemented("Rectangle::draw"); +#endif } template<typename T> @@ -252,20 +331,32 @@ void Rectangle<T>::drawOutline(const GraphicsContext&, const T lineWidth) DISTRHO_SAFE_ASSERT_RETURN(lineWidth != 0,); glLineWidth(static_cast<GLfloat>(lineWidth)); +#ifdef DGL_USE_COMPAT_OPENGL drawRectangle<T>(*this, true); +#else + notImplemented("Rectangle::drawOutline"); +#endif } // deprecated calls template<typename T> void Rectangle<T>::draw() { +#ifdef DGL_USE_COMPAT_OPENGL drawRectangle<T>(*this, false); +#else + notImplemented("Rectangle::draw"); +#endif } template<typename T> void Rectangle<T>::drawOutline() { +#ifdef DGL_USE_COMPAT_OPENGL drawRectangle<T>(*this, true); +#else + notImplemented("Rectangle::drawOutline"); +#endif } template class Rectangle<double>; @@ -316,11 +407,14 @@ static void drawOpenGLImage(const OpenGLImage& image, const Point<int>& pos, con setupCalled = true; } +#ifdef DGL_USE_COMPAT_OPENGL glColor4f(1.0f, 1.0f, 1.0f, 1.0f); +#endif glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, textureId); +#ifdef DGL_USE_COMPAT_OPENGL glBegin(GL_QUADS); { @@ -343,6 +437,7 @@ static void drawOpenGLImage(const OpenGLImage& image, const Point<int>& pos, con } glEnd(); +#endif glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D); @@ -533,17 +628,23 @@ void ImageBaseKnob<OpenGLImage>::onDisplay() if (pData->rotationAngle != 0) { +#ifdef DGL_USE_COMPAT_OPENGL glPushMatrix(); +#endif const int w2 = w/2; const int h2 = h/2; +#ifdef DGL_USE_COMPAT_OPENGL glTranslatef(static_cast<float>(w2), static_cast<float>(h2), 0.0f); glRotatef(normValue*static_cast<float>(pData->rotationAngle), 0.0f, 0.0f, 1.0f); +#endif Rectangle<int>(-w2, -h2, w, h).draw(context); +#ifdef DGL_USE_COMPAT_OPENGL glPopMatrix(); +#endif } else { @@ -667,6 +768,36 @@ void TopLevelWidget::PrivateData::display() // ----------------------------------------------------------------------- +void Window::PrivateData::renderToPicture(const char* const filename, + const GraphicsContext&, + const uint width, + const uint height) +{ + FILE* const f = fopen(filename, "w"); + DISTRHO_SAFE_ASSERT_RETURN(f != nullptr,); + + GLubyte* const pixels = new GLubyte[width * height * 3 * sizeof(GLubyte)]; + + glFlush(); + glReadPixels(0, 0, static_cast<GLsizei>(width), static_cast<GLsizei>(height), GL_RGB, GL_UNSIGNED_BYTE, pixels); + + fprintf(f, "P3\n%d %d\n255\n", width, height); + for (uint y = 0; y < height; y++) + { + for (uint i, x = 0; x < width; x++) + { + i = 3 * ((height - y - 1) * width + x); + fprintf(f, "%3d %3d %3d ", pixels[i], pixels[i+1], pixels[i+2]); + } + fprintf(f, "\n"); + } + + delete[] pixels; + fclose(f); +} + +// ----------------------------------------------------------------------- + const GraphicsContext& Window::PrivateData::getGraphicsContext() const noexcept { return (const GraphicsContext&)graphicsContext; diff --git a/dgl/src/SubWidget.cpp b/dgl/src/SubWidget.cpp @@ -34,7 +34,9 @@ SubWidget::~SubWidget() template<typename T> bool SubWidget::contains(const T x, const T y) const noexcept { - return Rectangle<double>(0, 0, getWidth()-pData->margin.getX(), getHeight()-pData->margin.getY()).contains(x, y); + return Rectangle<double>(0, 0, + static_cast<double>(getWidth()), + static_cast<double>(getHeight())).contains(x, y); } template<typename T> @@ -65,9 +67,18 @@ Rectangle<int> SubWidget::getAbsoluteArea() const noexcept Rectangle<uint> SubWidget::getConstrainedAbsoluteArea() const noexcept { - return Rectangle<uint>(static_cast<uint>(std::max(0, getAbsoluteX())), - static_cast<uint>(std::max(0, getAbsoluteY())), - getSize()); + const int x = getAbsoluteX(); + const int y = getAbsoluteY(); + + if (x >= 0 && y >= 0) + return Rectangle<uint>(x, y, getSize()); + + const int xOffset = std::min(0, x); + const int yOffset = std::min(0, y); + const int width = std::max(0, static_cast<int>(getWidth()) + xOffset); + const int height = std::max(0, static_cast<int>(getHeight()) + yOffset); + + return Rectangle<uint>(0, 0, static_cast<uint>(width), static_cast<uint>(height)); } void SubWidget::setAbsoluteX(const int x) noexcept diff --git a/dgl/src/TopLevelWidget.cpp b/dgl/src/TopLevelWidget.cpp @@ -60,6 +60,21 @@ void TopLevelWidget::setSize(const Size<uint>& size) pData->window.setSize(size); } +const void* TopLevelWidget::getClipboard(size_t& dataSize) +{ + return pData->window.getClipboard(dataSize); +} + +bool TopLevelWidget::setClipboard(const char* const mimeType, const void* const data, const size_t dataSize) +{ + return pData->window.setClipboard(mimeType, data, dataSize); +} + +bool TopLevelWidget::setCursor(const MouseCursor cursor) +{ + return pData->window.setCursor(cursor); +} + bool TopLevelWidget::addIdleCallback(IdleCallback* const callback, const uint timerFrequencyInMs) { return pData->window.addIdleCallback(callback, timerFrequencyInMs); @@ -88,41 +103,47 @@ void TopLevelWidget::repaint(const Rectangle<uint>& rect) noexcept void TopLevelWidget::setGeometryConstraints(const uint minimumWidth, const uint minimumHeight, const bool keepAspectRatio, - const bool automaticallyScale) + const bool automaticallyScale, + const bool resizeNowIfAutoScaling) { - pData->window.setGeometryConstraints(minimumWidth, minimumHeight, keepAspectRatio, automaticallyScale); + pData->window.setGeometryConstraints(minimumWidth, + minimumHeight, + keepAspectRatio, + automaticallyScale, + resizeNowIfAutoScaling); } // -------------------------------------------------------------------------------------------------------------------- -bool TopLevelWidget::onKeyboard(const KeyboardEvent&) +bool TopLevelWidget::onKeyboard(const KeyboardEvent& ev) { - return false; + return pData->keyboardEvent(ev); } -bool TopLevelWidget::onSpecial(const SpecialEvent&) +bool TopLevelWidget::onCharacterInput(const CharacterInputEvent& ev) { - return false; + return pData->characterInputEvent(ev); } -bool TopLevelWidget::onCharacterInput(const CharacterInputEvent&) +bool TopLevelWidget::onMouse(const MouseEvent& ev) { - return false; + return pData->mouseEvent(ev); } -bool TopLevelWidget::onMouse(const MouseEvent&) +bool TopLevelWidget::onMotion(const MotionEvent& ev) { - return false; + return pData->motionEvent(ev); } -bool TopLevelWidget::onMotion(const MotionEvent&) +bool TopLevelWidget::onScroll(const ScrollEvent& ev) { - return false; + return pData->scrollEvent(ev); } -bool TopLevelWidget::onScroll(const ScrollEvent&) +// -------------------------------------------------------------------------------------------------------------------- + +void TopLevelWidget::requestSizeChange(uint, uint) { - return false; } // -------------------------------------------------------------------------------------------------------------------- diff --git a/dgl/src/TopLevelWidgetPrivateData.cpp b/dgl/src/TopLevelWidgetPrivateData.cpp @@ -42,38 +42,16 @@ bool TopLevelWidget::PrivateData::keyboardEvent(const KeyboardEvent& ev) if (! selfw->pData->visible) return false; - // give top-level widget chance to catch this event first - if (self->onKeyboard(ev)) - return true; - // propagate event to all subwidgets recursively return selfw->pData->giveKeyboardEventForSubWidgets(ev); } -bool TopLevelWidget::PrivateData::specialEvent(const SpecialEvent& ev) -{ - // ignore event if we are not visible - if (! selfw->pData->visible) - return false; - - // give top-level widget chance to catch this event first - if (self->onSpecial(ev)) - return true; - - // propagate event to all subwidgets recursively - return selfw->pData->giveSpecialEventForSubWidgets(ev); -} - bool TopLevelWidget::PrivateData::characterInputEvent(const CharacterInputEvent& ev) { // ignore event if we are not visible if (! selfw->pData->visible) return false; - // give top-level widget chance to catch this event first - if (self->onCharacterInput(ev)) - return true; - // propagate event to all subwidgets recursively return selfw->pData->giveCharacterInputEventForSubWidgets(ev); } @@ -96,10 +74,6 @@ bool TopLevelWidget::PrivateData::mouseEvent(const MouseEvent& ev) rev.absolutePos.setY(ev.absolutePos.getY() / autoScaleFactor); } - // give top-level widget chance to catch this event first - if (self->onMouse(ev)) - return true; - // propagate event to all subwidgets recursively return selfw->pData->giveMouseEventForSubWidgets(rev); } @@ -122,10 +96,6 @@ bool TopLevelWidget::PrivateData::motionEvent(const MotionEvent& ev) rev.absolutePos.setY(ev.absolutePos.getY() / autoScaleFactor); } - // give top-level widget chance to catch this event first - if (self->onMotion(ev)) - return true; - // propagate event to all subwidgets recursively return selfw->pData->giveMotionEventForSubWidgets(rev); } @@ -150,10 +120,6 @@ bool TopLevelWidget::PrivateData::scrollEvent(const ScrollEvent& ev) rev.delta.setY(ev.delta.getY() / autoScaleFactor); } - // give top-level widget chance to catch this event first - if (self->onScroll(ev)) - return true; - // propagate event to all subwidgets recursively return selfw->pData->giveScrollEventForSubWidgets(rev); } diff --git a/dgl/src/TopLevelWidgetPrivateData.hpp b/dgl/src/TopLevelWidgetPrivateData.hpp @@ -30,11 +30,10 @@ struct TopLevelWidget::PrivateData { Widget* const selfw; Window& window; - explicit PrivateData(TopLevelWidget* const s, Window& w); + explicit PrivateData(TopLevelWidget* self, Window& window); ~PrivateData(); void display(); bool keyboardEvent(const KeyboardEvent& ev); - bool specialEvent(const SpecialEvent& ev); bool characterInputEvent(const CharacterInputEvent& ev); bool mouseEvent(const MouseEvent& ev); bool motionEvent(const MotionEvent& ev); diff --git a/dgl/src/Vulkan.cpp b/dgl/src/Vulkan.cpp @@ -231,6 +231,13 @@ void TopLevelWidget::PrivateData::display() // ----------------------------------------------------------------------- +void Window::PrivateData::renderToPicture(const char*, const GraphicsContext&, uint, uint) +{ + notImplemented("Window::PrivateData::renderToPicture"); +} + +// ----------------------------------------------------------------------- + const GraphicsContext& Window::PrivateData::getGraphicsContext() const noexcept { return (const GraphicsContext&)graphicsContext; diff --git a/dgl/src/Widget.cpp b/dgl/src/Widget.cpp @@ -167,11 +167,6 @@ bool Widget::onKeyboard(const KeyboardEvent& ev) return pData->giveKeyboardEventForSubWidgets(ev); } -bool Widget::onSpecial(const SpecialEvent& ev) -{ - return pData->giveSpecialEventForSubWidgets(ev); -} - bool Widget::onCharacterInput(const CharacterInputEvent& ev) { return pData->giveCharacterInputEventForSubWidgets(ev); diff --git a/dgl/src/WidgetPrivateData.cpp b/dgl/src/WidgetPrivateData.cpp @@ -87,24 +87,6 @@ bool Widget::PrivateData::giveKeyboardEventForSubWidgets(const KeyboardEvent& ev return false; } -bool Widget::PrivateData::giveSpecialEventForSubWidgets(const SpecialEvent& ev) -{ - if (! visible) - return false; - if (subWidgets.size() == 0) - return false; - - FOR_EACH_SUBWIDGET_INV(rit) - { - SubWidget* const widget(*rit); - - if (widget->isVisible() && widget->onSpecial(ev)) - return true; - } - - return false; -} - bool Widget::PrivateData::giveCharacterInputEventForSubWidgets(const CharacterInputEvent& ev) { if (! visible) @@ -130,18 +112,15 @@ bool Widget::PrivateData::giveMouseEventForSubWidgets(MouseEvent& ev) if (subWidgets.size() == 0) return false; - double x = ev.absolutePos.getX(); - double y = ev.absolutePos.getY(); + const double x = ev.absolutePos.getX(); + const double y = ev.absolutePos.getY(); if (SubWidget* const selfw = dynamic_cast<SubWidget*>(self)) { if (selfw->pData->needsViewportScaling) { - x -= selfw->getAbsoluteX(); - y -= selfw->getAbsoluteY(); - - ev.absolutePos.setX(x); - ev.absolutePos.setY(y); + ev.absolutePos.setX(x - selfw->getAbsoluteX() + selfw->getMargin().getX()); + ev.absolutePos.setY(y - selfw->getAbsoluteY() + selfw->getMargin().getY()); } } @@ -169,18 +148,15 @@ bool Widget::PrivateData::giveMotionEventForSubWidgets(MotionEvent& ev) if (subWidgets.size() == 0) return false; - double x = ev.absolutePos.getX(); - double y = ev.absolutePos.getY(); + const double x = ev.absolutePos.getX(); + const double y = ev.absolutePos.getY(); if (SubWidget* const selfw = dynamic_cast<SubWidget*>(self)) { if (selfw->pData->needsViewportScaling) { - x -= selfw->getAbsoluteX(); - y -= selfw->getAbsoluteY(); - - ev.absolutePos.setX(x); - ev.absolutePos.setY(y); + ev.absolutePos.setX(x - selfw->getAbsoluteX() + selfw->getMargin().getX()); + ev.absolutePos.setY(y - selfw->getAbsoluteY() + selfw->getMargin().getY()); } } @@ -208,18 +184,15 @@ bool Widget::PrivateData::giveScrollEventForSubWidgets(ScrollEvent& ev) if (subWidgets.size() == 0) return false; - double x = ev.absolutePos.getX(); - double y = ev.absolutePos.getY(); + const double x = ev.absolutePos.getX(); + const double y = ev.absolutePos.getY(); if (SubWidget* const selfw = dynamic_cast<SubWidget*>(self)) { if (selfw->pData->needsViewportScaling) { - x -= selfw->getAbsoluteX(); - y -= selfw->getAbsoluteY(); - - ev.absolutePos.setX(x); - ev.absolutePos.setY(y); + ev.absolutePos.setX(x - selfw->getAbsoluteX() + selfw->getMargin().getX()); + ev.absolutePos.setY(y - selfw->getAbsoluteY() + selfw->getMargin().getY()); } } diff --git a/dgl/src/WidgetPrivateData.hpp b/dgl/src/WidgetPrivateData.hpp @@ -44,7 +44,6 @@ struct Widget::PrivateData { void displaySubWidgets(uint width, uint height, double autoScaleFactor); bool giveKeyboardEventForSubWidgets(const KeyboardEvent& ev); - bool giveSpecialEventForSubWidgets(const SpecialEvent& ev); bool giveCharacterInputEventForSubWidgets(const CharacterInputEvent& ev); bool giveMouseEventForSubWidgets(MouseEvent& ev); bool giveMotionEventForSubWidgets(MotionEvent& ev); diff --git a/dgl/src/Window.cpp b/dgl/src/Window.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -15,6 +15,7 @@ */ #include "WindowPrivateData.hpp" +#include "../TopLevelWidget.hpp" #include "pugl.hpp" @@ -66,8 +67,8 @@ Window::Window(Application& app) pData->initPost(); } -Window::Window(Application& app, Window& parent) - : pData(new PrivateData(app, this, parent.pData)) +Window::Window(Application& app, Window& transientParentWindow) + : pData(new PrivateData(app, this, transientParentWindow.pData)) { pData->initPost(); } @@ -87,7 +88,7 @@ Window::Window(Application& app, const uint height, const double scaleFactor, const bool resizable) - : pData(new PrivateData(app, this, parentWindowHandle, width, height, scaleFactor, resizable)) + : pData(new PrivateData(app, this, parentWindowHandle, width, height, scaleFactor, resizable, false)) { pData->initPost(); } @@ -98,8 +99,9 @@ Window::Window(Application& app, const uint height, const double scaleFactor, const bool resizable, + const bool isVST3, const bool doPostInit) - : pData(new PrivateData(app, this, parentWindowHandle, width, height, scaleFactor, resizable)) + : pData(new PrivateData(app, this, parentWindowHandle, width, height, scaleFactor, resizable, isVST3)) { if (doPostInit) pData->initPost(); @@ -153,6 +155,48 @@ void Window::setResizable(const bool resizable) pData->setResizable(resizable); } +int Window::getOffsetX() const noexcept +{ + DISTRHO_SAFE_ASSERT_RETURN(pData->view != nullptr, 0); + + return puglGetFrame(pData->view).x; +} + +int Window::getOffsetY() const noexcept +{ + DISTRHO_SAFE_ASSERT_RETURN(pData->view != nullptr, 0); + + return puglGetFrame(pData->view).y; +} + +Point<int> Window::getOffset() const noexcept +{ + DISTRHO_SAFE_ASSERT_RETURN(pData->view != nullptr, Point<int>()); + + const PuglRect rect = puglGetFrame(pData->view); + return Point<int>(rect.x, rect.y); +} + +void Window::setOffsetX(const int x) +{ + setOffset(x, getOffsetY()); +} + +void Window::setOffsetY(const int y) +{ + setOffset(getOffsetX(), y); +} + +void Window::setOffset(const int x, const int y) +{ + puglSetPosition(pData->view, x, y); +} + +void Window::setOffset(const Point<int>& offset) +{ + setOffset(offset.getX(), offset.getY()); +} + uint Window::getWidth() const noexcept { DISTRHO_SAFE_ASSERT_RETURN(pData->view != nullptr, 0); @@ -199,8 +243,14 @@ void Window::setSize(uint width, uint height) if (pData->isEmbed) { const double scaleFactor = pData->scaleFactor; - const uint minWidth = static_cast<uint>(pData->minWidth * scaleFactor + 0.5); - const uint minHeight = static_cast<uint>(pData->minHeight * scaleFactor + 0.5); + uint minWidth = pData->minWidth; + uint minHeight = pData->minHeight; + + if (pData->autoScaling && scaleFactor != 1.0) + { + minWidth *= scaleFactor; + minHeight *= scaleFactor; + } // handle geometry constraints here if (width < minWidth) @@ -220,15 +270,27 @@ void Window::setSize(uint width, uint height) { // fix width if (reqRatio > ratio) - width = height * ratio; + width = static_cast<uint>(height * ratio + 0.5); // fix height else - height = width / ratio; + height = static_cast<uint>(static_cast<double>(width) / ratio + 0.5); } } } - puglSetWindowSize(pData->view, width, height); + if (pData->usesSizeRequest) + { + DISTRHO_SAFE_ASSERT_RETURN(pData->topLevelWidgets.size() != 0,); + + TopLevelWidget* const topLevelWidget = pData->topLevelWidgets.front(); + DISTRHO_SAFE_ASSERT_RETURN(topLevelWidget != nullptr,); + + topLevelWidget->requestSizeChange(width, height); + } + else + { + puglSetSizeAndDefault(pData->view, width, height); + } } void Window::setSize(const Size<uint>& size) @@ -257,6 +319,21 @@ void Window::setIgnoringKeyRepeat(const bool ignore) noexcept puglSetViewHint(pData->view, PUGL_IGNORE_KEY_REPEAT, ignore); } +const void* Window::getClipboard(size_t& dataSize) +{ + return pData->getClipboard(dataSize); +} + +bool Window::setClipboard(const char* const mimeType, const void* const data, const size_t dataSize) +{ + return puglSetClipboard(pData->view, mimeType != nullptr ? mimeType : "text/plain", data, dataSize) == PUGL_SUCCESS; +} + +bool Window::setCursor(const MouseCursor cursor) +{ + return puglSetCursor(pData->view, static_cast<PuglCursor>(cursor)) == PUGL_SUCCESS; +} + bool Window::addIdleCallback(IdleCallback* const callback, const uint timerFrequencyInMs) { DISTRHO_SAFE_ASSERT_RETURN(callback != nullptr, false) @@ -285,7 +362,7 @@ const GraphicsContext& Window::getGraphicsContext() const noexcept uintptr_t Window::getNativeWindowHandle() const noexcept { - return puglGetNativeWindow(pData->view); + return puglGetNativeView(pData->view); } double Window::getScaleFactor() const noexcept @@ -319,10 +396,10 @@ void Window::repaint(const Rectangle<uint>& rect) noexcept return; PuglRect prect = { - static_cast<double>(rect.getX()), - static_cast<double>(rect.getY()), - static_cast<double>(rect.getWidth()), - static_cast<double>(rect.getHeight()), + static_cast<PuglCoord>(rect.getX()), + static_cast<PuglCoord>(rect.getY()), + static_cast<PuglSpan>(rect.getWidth()), + static_cast<PuglSpan>(rect.getHeight()), }; if (pData->autoScaling) { @@ -336,15 +413,27 @@ void Window::repaint(const Rectangle<uint>& rect) noexcept puglPostRedisplayRect(pData->view, prect); } +void Window::renderToPicture(const char* const filename) +{ + pData->filenameToRenderInto = strdup(filename); +} + void Window::runAsModal(bool blockWait) { pData->runAsModal(blockWait); } -void Window::setGeometryConstraints(const uint minimumWidth, - const uint minimumHeight, +Size<uint> Window::getGeometryConstraints(bool& keepAspectRatio) +{ + keepAspectRatio = pData->keepAspectRatio; + return Size<uint>(pData->minWidth, pData->minHeight); +} + +void Window::setGeometryConstraints(uint minimumWidth, + uint minimumHeight, const bool keepAspectRatio, - const bool automaticallyScale) + const bool automaticallyScale, + const bool resizeNowIfAutoScaling) { DISTRHO_SAFE_ASSERT_RETURN(minimumWidth > 0,); DISTRHO_SAFE_ASSERT_RETURN(minimumHeight > 0,); @@ -359,12 +448,15 @@ void Window::setGeometryConstraints(const uint minimumWidth, const double scaleFactor = pData->scaleFactor; - puglSetGeometryConstraints(pData->view, - static_cast<uint>(minimumWidth * scaleFactor + 0.5), - static_cast<uint>(minimumHeight * scaleFactor + 0.5), - keepAspectRatio); + if (automaticallyScale && scaleFactor != 1.0) + { + minimumWidth *= scaleFactor; + minimumHeight *= scaleFactor; + } - if (scaleFactor != 1.0) + puglSetGeometryConstraints(pData->view, minimumWidth, minimumHeight, keepAspectRatio); + + if (scaleFactor != 1.0 && automaticallyScale && resizeNowIfAutoScaling) { const Size<uint> size(getSize()); @@ -373,50 +465,64 @@ void Window::setGeometryConstraints(const uint minimumWidth, } } -bool Window::onClose() +void Window::setTransientParent(const uintptr_t transientParentWindowHandle) { - return true; + puglSetTransientParent(pData->view, transientParentWindowHandle); } -void Window::onFocus(bool, CrossingMode) +std::vector<ClipboardDataOffer> Window::getClipboardDataOfferTypes() { + std::vector<ClipboardDataOffer> offerTypes; + + if (const uint32_t numTypes = puglGetNumClipboardTypes(pData->view)) + { + offerTypes.reserve(numTypes); + + for (uint32_t i=0; i<numTypes; ++i) + { + const ClipboardDataOffer offer = { i + 1, puglGetClipboardType(pData->view, i) }; + offerTypes.push_back(offer); + } + } + + return offerTypes; } -void Window::onReshape(uint, uint) +uint32_t Window::onClipboardDataOffer() { - puglFallbackOnResize(pData->view); + std::vector<ClipboardDataOffer> offers(getClipboardDataOfferTypes()); + + for (std::vector<ClipboardDataOffer>::iterator it=offers.begin(), end=offers.end(); it != end;++it) + { + const ClipboardDataOffer offer = *it; + if (std::strcmp(offer.type, "text/plain") == 0) + return offer.id; + } + + return 0; } -void Window::onScaleFactorChanged(double) +bool Window::onClose() { + return true; } -#ifndef DGL_FILE_BROWSER_DISABLED -void Window::onFileSelected(const char*) +void Window::onFocus(bool, CrossingMode) { } -#endif -#if 0 -void Window::setTransientWinId(const uintptr_t winId) +void Window::onReshape(uint, uint) { - puglSetTransientFor(pData->view, winId); + puglFallbackOnResize(pData->view); } -// ----------------------------------------------------------------------- - -bool Window::handlePluginKeyboard(const bool press, const uint key) +void Window::onScaleFactorChanged(double) { - // TODO - return false; - // return pData->handlePluginKeyboard(press, key); } -bool Window::handlePluginSpecial(const bool press, const Key key) +#ifndef DGL_FILE_BROWSER_DISABLED +void Window::onFileSelected(const char*) { - // TODO - return false; - // return pData->handlePluginSpecial(press, key); } #endif diff --git a/dgl/src/WindowPrivateData.cpp b/dgl/src/WindowPrivateData.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -19,26 +19,19 @@ #include "pugl.hpp" -#include "../../distrho/extra/String.hpp" - -#ifdef DISTRHO_OS_WINDOWS -# include <direct.h> -# include <winsock2.h> -# include <windows.h> -# include <vector> -#else -# include <unistd.h> -#endif - -#define DGL_DEBUG_EVENTS +// #define DGL_DEBUG_EVENTS #if defined(DEBUG) && defined(DGL_DEBUG_EVENTS) -# include <cinttypes> +# ifdef DISTRHO_PROPER_CPP11_SUPPORT +# include <cinttypes> +# else +# include <inttypes.h> +# endif #endif START_NAMESPACE_DGL -#if defined(DEBUG) && defined(DGL_DEBUG_EVENTS) +#ifdef DGL_DEBUG_EVENTS # define DGL_DBG(msg) std::fprintf(stderr, "%s", msg); # define DGL_DBGp(...) std::fprintf(stderr, __VA_ARGS__); # define DGL_DBGF std::fflush(stderr); @@ -59,44 +52,69 @@ START_NAMESPACE_DGL // ----------------------------------------------------------------------- -#ifdef DISTRHO_OS_WINDOWS -// static pointer used for direct comparisons -static const char* const kWin32SelectedFileCancelled = "__dpf_cancelled__"; -#endif - -static double getDesktopScaleFactor(const PuglView* const view) +static double getScaleFactorFromParent(const PuglView* const view) { // allow custom scale for testing if (const char* const scale = getenv("DPF_SCALE_FACTOR")) return std::max(1.0, std::atof(scale)); if (view != nullptr) - return puglGetDesktopScaleFactor(view); + return puglGetScaleFactorFromParent(view); return 1.0; } +static PuglView* puglNewViewWithTransientParent(PuglWorld* const world, PuglView* const transientParentView) +{ + DISTRHO_SAFE_ASSERT_RETURN(world != nullptr, nullptr); + + if (PuglView* const view = puglNewView(world)) + { + puglSetTransientParent(view, puglGetNativeView(transientParentView)); + return view; + } + + return nullptr; +} + +static PuglView* puglNewViewWithParentWindow(PuglWorld* const world, const uintptr_t parentWindowHandle) +{ + DISTRHO_SAFE_ASSERT_RETURN(world != nullptr, nullptr); + + if (PuglView* const view = puglNewView(world)) + { + puglSetParentWindow(view, parentWindowHandle); + return view; + } + + return nullptr; +} + // ----------------------------------------------------------------------- Window::PrivateData::PrivateData(Application& a, Window* const s) : app(a), appData(a.pData), self(s), - view(puglNewView(appData->world)), - transientParentView(nullptr), + view(appData->world != nullptr ? puglNewView(appData->world) : nullptr), topLevelWidgets(), isClosed(true), isVisible(false), isEmbed(false), - scaleFactor(getDesktopScaleFactor(view)), + usesSizeRequest(false), + scaleFactor(getScaleFactorFromParent(view)), autoScaling(false), autoScaleFactor(1.0), minWidth(0), minHeight(0), keepAspectRatio(false), ignoreIdleCallbacks(false), -#ifdef DISTRHO_OS_WINDOWS - win32SelectedFile(nullptr), + waitingForClipboardData(false), + waitingForClipboardEvents(false), + clipboardTypeId(0), + filenameToRenderInto(nullptr), +#ifndef DGL_FILE_BROWSER_DISABLED + fileBrowserHandle(nullptr), #endif modal() { @@ -107,12 +125,12 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, PrivateData* c : app(a), appData(a.pData), self(s), - view(puglNewView(appData->world)), - transientParentView(ppData->view), + view(puglNewViewWithTransientParent(appData->world, ppData->view)), topLevelWidgets(), isClosed(true), isVisible(false), isEmbed(false), + usesSizeRequest(false), scaleFactor(ppData->scaleFactor), autoScaling(false), autoScaleFactor(1.0), @@ -120,13 +138,15 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, PrivateData* c minHeight(0), keepAspectRatio(false), ignoreIdleCallbacks(false), -#ifdef DISTRHO_OS_WINDOWS - win32SelectedFile(nullptr), + waitingForClipboardData(false), + waitingForClipboardEvents(false), + clipboardTypeId(0), + filenameToRenderInto(nullptr), +#ifndef DGL_FILE_BROWSER_DISABLED + fileBrowserHandle(nullptr), #endif modal(ppData) { - puglSetTransientFor(view, puglGetNativeWindow(transientParentView)); - initPre(DEFAULT_WIDTH, DEFAULT_HEIGHT, false); } @@ -136,52 +156,57 @@ Window::PrivateData::PrivateData(Application& a, Window* const s, : app(a), appData(a.pData), self(s), - view(puglNewView(appData->world)), - transientParentView(nullptr), + view(puglNewViewWithParentWindow(appData->world, parentWindowHandle)), topLevelWidgets(), isClosed(parentWindowHandle == 0), isVisible(parentWindowHandle != 0), isEmbed(parentWindowHandle != 0), - scaleFactor(scale != 0.0 ? scale : getDesktopScaleFactor(view)), + usesSizeRequest(false), + scaleFactor(scale != 0.0 ? scale : getScaleFactorFromParent(view)), autoScaling(false), autoScaleFactor(1.0), minWidth(0), minHeight(0), keepAspectRatio(false), ignoreIdleCallbacks(false), -#ifdef DISTRHO_OS_WINDOWS - win32SelectedFile(nullptr), + waitingForClipboardData(false), + waitingForClipboardEvents(false), + clipboardTypeId(0), + filenameToRenderInto(nullptr), +#ifndef DGL_FILE_BROWSER_DISABLED + fileBrowserHandle(nullptr), #endif modal() { - if (isEmbed) - puglSetParentWindow(view, parentWindowHandle); - initPre(DEFAULT_WIDTH, DEFAULT_HEIGHT, resizable); } Window::PrivateData::PrivateData(Application& a, Window* const s, const uintptr_t parentWindowHandle, const uint width, const uint height, - const double scale, const bool resizable) + const double scale, const bool resizable, const bool isVST3) : app(a), appData(a.pData), self(s), - view(appData->world != nullptr ? puglNewView(appData->world) : nullptr), - transientParentView(nullptr), + view(puglNewViewWithParentWindow(appData->world, parentWindowHandle)), topLevelWidgets(), isClosed(parentWindowHandle == 0), isVisible(parentWindowHandle != 0 && view != nullptr), isEmbed(parentWindowHandle != 0), - scaleFactor(scale != 0.0 ? scale : getDesktopScaleFactor(view)), + usesSizeRequest(isVST3), + scaleFactor(scale != 0.0 ? scale : getScaleFactorFromParent(view)), autoScaling(false), autoScaleFactor(1.0), minWidth(0), minHeight(0), keepAspectRatio(false), ignoreIdleCallbacks(false), -#ifdef DISTRHO_OS_WINDOWS - win32SelectedFile(nullptr), + waitingForClipboardData(false), + waitingForClipboardEvents(false), + clipboardTypeId(0), + filenameToRenderInto(nullptr), +#ifndef DGL_FILE_BROWSER_DISABLED + fileBrowserHandle(nullptr), #endif modal() { @@ -195,14 +220,16 @@ Window::PrivateData::~PrivateData() { appData->idleCallbacks.remove(this); appData->windows.remove(self); + std::free(filenameToRenderInto); if (view == nullptr) return; if (isEmbed) { -#ifdef HAVE_X11 - sofdFileDialogClose(); +#ifndef DGL_FILE_BROWSER_DISABLED + if (fileBrowserHandle != nullptr) + fileBrowserClose(fileBrowserHandle); #endif puglHide(view); appData->oneWindowClosed(); @@ -210,11 +237,6 @@ Window::PrivateData::~PrivateData() isVisible = false; } -#ifdef DISTRHO_OS_WINDOWS - if (win32SelectedFile != nullptr && win32SelectedFile != kWin32SelectedFileCancelled) - std::free(const_cast<char*>(win32SelectedFile)); -#endif - puglFreeView(view); } @@ -233,21 +255,33 @@ void Window::PrivateData::initPre(const uint width, const uint height, const boo } puglSetMatchingBackendForCurrentBuild(view); - - puglClearMinSize(view); - puglSetWindowSize(view, width, height); - puglSetHandle(view, this); + puglSetViewHint(view, PUGL_RESIZABLE, resizable ? PUGL_TRUE : PUGL_FALSE); puglSetViewHint(view, PUGL_IGNORE_KEY_REPEAT, PUGL_FALSE); +#if DGL_USE_RGBA + puglSetViewHint(view, PUGL_DEPTH_BITS, 24); +#else puglSetViewHint(view, PUGL_DEPTH_BITS, 16); +#endif puglSetViewHint(view, PUGL_STENCIL_BITS, 8); -#ifdef DGL_USE_OPENGL3 + +#if defined(DGL_USE_OPENGL3) || defined(DGL_USE_GLES3) puglSetViewHint(view, PUGL_USE_COMPAT_PROFILE, PUGL_FALSE); puglSetViewHint(view, PUGL_CONTEXT_VERSION_MAJOR, 3); +#elif defined(DGL_USE_GLES2) + puglSetViewHint(view, PUGL_USE_COMPAT_PROFILE, PUGL_FALSE); + puglSetViewHint(view, PUGL_CONTEXT_VERSION_MAJOR, 2); +#else + puglSetViewHint(view, PUGL_USE_COMPAT_PROFILE, PUGL_TRUE); + puglSetViewHint(view, PUGL_CONTEXT_VERSION_MAJOR, 2); #endif + // PUGL_SAMPLES ?? puglSetEventFunc(view, puglEventCallback); + + // setting default size triggers system-level calls, do it last + puglSetSizeHint(view, PUGL_DEFAULT_SIZE, width, height); } bool Window::PrivateData::initPost() @@ -313,8 +347,8 @@ void Window::PrivateData::show() appData->oneWindowShown(); // FIXME - PuglRect rect = puglGetFrame(view); - puglSetWindowSize(view, static_cast<uint>(rect.width), static_cast<uint>(rect.height)); +// PuglRect rect = puglGetFrame(view); +// puglSetWindowSize(view, static_cast<uint>(rect.width), static_cast<uint>(rect.height)); #if defined(DISTRHO_OS_WINDOWS) puglWin32ShowCentered(view); @@ -354,9 +388,14 @@ void Window::PrivateData::hide() if (modal.enabled) stopModal(); -#ifdef HAVE_X11 - sofdFileDialogClose(); +#ifndef DGL_FILE_BROWSER_DISABLED + if (fileBrowserHandle != nullptr) + { + fileBrowserClose(fileBrowserHandle); + fileBrowserHandle = nullptr; + } #endif + puglHide(view); isVisible = false; @@ -372,11 +411,7 @@ void Window::PrivateData::focus() if (! isEmbed) puglRaiseWindow(view); -#ifdef HAVE_X11 - puglX11GrabFocus(view); -#else puglGrabFocus(view); -#endif } // ----------------------------------------------------------------------- @@ -387,10 +422,7 @@ void Window::PrivateData::setResizable(const bool resizable) DGL_DBG("Window setResizable called\n"); - puglSetViewHint(view, PUGL_RESIZABLE, resizable ? PUGL_TRUE : PUGL_FALSE); -#ifdef DISTRHO_OS_WINDOWS - puglWin32SetWindowResizable(view, resizable); -#endif + puglSetResizable(view, resizable); } // ----------------------------------------------------------------------- @@ -398,20 +430,12 @@ void Window::PrivateData::setResizable(const bool resizable) void Window::PrivateData::idleCallback() { #ifndef DGL_FILE_BROWSER_DISABLED -# ifdef DISTRHO_OS_WINDOWS - if (const char* path = win32SelectedFile) + if (fileBrowserHandle != nullptr && fileBrowserIdle(fileBrowserHandle)) { - win32SelectedFile = nullptr; - if (path == kWin32SelectedFileCancelled) - path = nullptr; - self->onFileSelected(path); - std::free(const_cast<char*>(path)); + self->onFileSelected(fileBrowserGetPath(fileBrowserHandle)); + fileBrowserClose(fileBrowserHandle); + fileBrowserHandle = nullptr; } -# endif -# ifdef HAVE_X11 - if (sofdFileDialogIdle(view)) - self->onFileSelected(sofdFileDialogGetPath()); -# endif #endif } @@ -451,164 +475,23 @@ bool Window::PrivateData::removeIdleCallback(IdleCallback* const callback) // ----------------------------------------------------------------------- // file handling -bool Window::PrivateData::openFileBrowser(const Window::FileBrowserOptions& options) +bool Window::PrivateData::openFileBrowser(const FileBrowserOptions& options) { - using DISTRHO_NAMESPACE::String; - - // -------------------------------------------------------------------------- - // configure start dir - - // TODO: get abspath if needed - // TODO: cross-platform - - String startDir(options.startDir); - - if (startDir.isEmpty()) - { - // TESTING verify this whole thing... -# ifdef DISTRHO_OS_WINDOWS - if (char* const cwd = _getcwd(nullptr, 0)) - { - startDir = cwd; - std::free(cwd); - } -# else - if (char* const cwd = getcwd(nullptr, 0)) - { - startDir = cwd; - std::free(cwd); - } -# endif - } - - DISTRHO_SAFE_ASSERT_RETURN(startDir.isNotEmpty(), false); - - if (! startDir.endsWith(DISTRHO_OS_SEP)) - startDir += DISTRHO_OS_SEP_STR; - - // -------------------------------------------------------------------------- - // configure title - - String title(options.title); - - if (title.isEmpty()) - { - title = puglGetWindowTitle(view); - - if (title.isEmpty()) - title = "FileBrowser"; - } + if (fileBrowserHandle != nullptr) + fileBrowserClose(fileBrowserHandle); - // -------------------------------------------------------------------------- - // show + FileBrowserOptions options2 = options; -# ifdef DISTRHO_OS_MAC - uint flags = 0x0; + if (options2.title == nullptr) + options2.title = puglGetWindowTitle(view); - if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked) - flags |= 0x001; - else if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleUnchecked) - flags |= 0x002; + fileBrowserHandle = fileBrowserCreate(isEmbed, + puglGetNativeView(view), + autoScaling ? autoScaleFactor : scaleFactor, + options2); - if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked) - flags |= 0x010; - else if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleUnchecked) - flags |= 0x020; - - if (options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleChecked) - flags |= 0x100; - else if (options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleUnchecked) - flags |= 0x200; - - return puglMacOSFilePanelOpen(view, startDir, title, flags, openPanelCallback); -# endif - -# ifdef DISTRHO_OS_WINDOWS - // the old and compatible dialog API - OPENFILENAMEW ofn; - memset(&ofn, 0, sizeof(ofn)); - if (win32SelectedFile != nullptr && win32SelectedFile != kWin32SelectedFileCancelled) - std::free(const_cast<char*>(win32SelectedFile)); - win32SelectedFile = nullptr; - - ofn.lStructSize = sizeof(ofn); - ofn.hwndOwner = (HWND)puglGetNativeWindow(view); - - // set start directory in UTF-16 encoding - std::vector<WCHAR> startDirW; - startDirW.resize(startDir.length() + 1); - if (MultiByteToWideChar(CP_UTF8, 0, startDir.buffer(), -1, startDirW.data(), startDirW.size())) - ofn.lpstrInitialDir = startDirW.data(); - - // set title in UTF-16 encoding - std::vector<WCHAR> titleW; - titleW.resize(title.length() + 1); - if (MultiByteToWideChar(CP_UTF8, 0, title.buffer(), -1, titleW.data(), titleW.size())) - ofn.lpstrTitle = titleW.data(); - - // prepare a buffer to receive the result - std::vector<WCHAR> fileNameW(32768); // the Unicode maximum - ofn.lpstrFile = fileNameW.data(); - ofn.nMaxFile = (DWORD)fileNameW.size(); - - // flags - ofn.Flags = OFN_PATHMUSTEXIST; - if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked) - ofn.Flags |= OFN_FORCESHOWHIDDEN; - if (options.buttons.showPlaces == FileBrowserOptions::kButtonInvisible) - ofn.FlagsEx |= OFN_EX_NOPLACESBAR; - - // TODO synchronous only, can't do better with WinAPI native dialogs. - // threading might work, if someone is motivated to risk it. - if (GetOpenFileNameW(&ofn)) - { - // back to UTF-8 - std::vector<char> fileNameA(4 * 32768); - if (WideCharToMultiByte(CP_UTF8, 0, fileNameW.data(), -1, - fileNameA.data(), (int)fileNameA.size(), - nullptr, nullptr)) - { - // handle it during the next idle cycle (fake async) - win32SelectedFile = strdup(fileNameA.data()); - } - } - - if (win32SelectedFile == nullptr) - win32SelectedFile = kWin32SelectedFileCancelled; - - return true; -# endif - -# ifdef HAVE_X11 - uint flags = 0x0; - - if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked) - flags |= 0x001; - else if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleUnchecked) - flags |= 0x002; - - if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked) - flags |= 0x010; - else if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleUnchecked) - flags |= 0x020; - - if (options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleChecked) - flags |= 0x100; - else if (options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleUnchecked) - flags |= 0x200; - - return sofdFileDialogShow(view, startDir, title, flags, autoScaling ? autoScaleFactor : scaleFactor); -# endif - - return false; -} - -# ifdef DISTRHO_OS_MAC -void Window::PrivateData::openPanelCallback(PuglView* const view, const char* const path) -{ - ((Window::PrivateData*)puglGetHandle(view))->self->onFileSelected(path); + return fileBrowserHandle != nullptr; } -# endif #endif // ! DGL_FILE_BROWSER_DISABLED // ----------------------------------------------------------------------- @@ -731,7 +614,7 @@ void Window::PrivateData::onPuglConfigure(const double width, const double heigh void Window::PrivateData::onPuglExpose() { - DGL_DBGp("PUGL: onPuglExpose\n"); + // DGL_DBG("PUGL: onPuglExpose\n"); puglOnDisplayPrepare(view); @@ -743,6 +626,14 @@ void Window::PrivateData::onPuglExpose() if (widget->isVisible()) widget->pData->display(); } + + if (char* const filename = filenameToRenderInto) + { + const PuglRect rect = puglGetFrame(view); + filenameToRenderInto = nullptr; + renderToPicture(filename, getGraphicsContext(), static_cast<uint>(rect.width), static_cast<uint>(rect.height)); + std::free(filename); + } #endif } @@ -801,15 +692,15 @@ void Window::PrivateData::onPuglKey(const Widget::KeyboardEvent& ev) { TopLevelWidget* const widget(*rit); - if (widget->isVisible() && widget->pData->keyboardEvent(ev)) + if (widget->isVisible() && widget->onKeyboard(ev)) break; } #endif } -void Window::PrivateData::onPuglSpecial(const Widget::SpecialEvent& ev) +void Window::PrivateData::onPuglText(const Widget::CharacterInputEvent& ev) { - DGL_DBGp("onPuglSpecial : %i %u\n", ev.press, ev.key); + DGL_DBGp("onPuglText : %u %u %s\n", ev.keycode, ev.character, ev.string); if (modal.child != nullptr) return modal.child->focus(); @@ -819,15 +710,15 @@ void Window::PrivateData::onPuglSpecial(const Widget::SpecialEvent& ev) { TopLevelWidget* const widget(*rit); - if (widget->isVisible() && widget->pData->specialEvent(ev)) + if (widget->isVisible() && widget->onCharacterInput(ev)) break; } #endif } -void Window::PrivateData::onPuglText(const Widget::CharacterInputEvent& ev) +void Window::PrivateData::onPuglMouse(const Widget::MouseEvent& ev) { - DGL_DBGp("onPuglText : %u %u %s\n", ev.keycode, ev.character, ev.string); + DGL_DBGp("onPuglMouse : %i %i %f %f\n", ev.button, ev.press, ev.pos.getX(), ev.pos.getY()); if (modal.child != nullptr) return modal.child->focus(); @@ -837,15 +728,15 @@ void Window::PrivateData::onPuglText(const Widget::CharacterInputEvent& ev) { TopLevelWidget* const widget(*rit); - if (widget->isVisible() && widget->pData->characterInputEvent(ev)) + if (widget->isVisible() && widget->onMouse(ev)) break; } #endif } -void Window::PrivateData::onPuglMouse(const Widget::MouseEvent& ev) +void Window::PrivateData::onPuglMotion(const Widget::MotionEvent& ev) { - DGL_DBGp("onPuglMouse : %i %i %f %f\n", ev.button, ev.press, ev.pos.getX(), ev.pos.getY()); + DGL_DBGp("onPuglMotion : %f %f\n", ev.pos.getX(), ev.pos.getY()); if (modal.child != nullptr) return modal.child->focus(); @@ -855,15 +746,15 @@ void Window::PrivateData::onPuglMouse(const Widget::MouseEvent& ev) { TopLevelWidget* const widget(*rit); - if (widget->isVisible() && widget->pData->mouseEvent(ev)) + if (widget->isVisible() && widget->onMotion(ev)) break; } #endif } -void Window::PrivateData::onPuglMotion(const Widget::MotionEvent& ev) +void Window::PrivateData::onPuglScroll(const Widget::ScrollEvent& ev) { - DGL_DBGp("onPuglMotion : %f %f\n", ev.pos.getX(), ev.pos.getY()); + DGL_DBGp("onPuglScroll : %f %f %f %f\n", ev.pos.getX(), ev.pos.getY(), ev.delta.getX(), ev.delta.getY()); if (modal.child != nullptr) return modal.child->focus(); @@ -873,28 +764,82 @@ void Window::PrivateData::onPuglMotion(const Widget::MotionEvent& ev) { TopLevelWidget* const widget(*rit); - if (widget->isVisible() && widget->pData->motionEvent(ev)) + if (widget->isVisible() && widget->onScroll(ev)) break; } #endif } -void Window::PrivateData::onPuglScroll(const Widget::ScrollEvent& ev) +const void* Window::PrivateData::getClipboard(size_t& dataSize) { - DGL_DBGp("onPuglScroll : %f %f %f %f\n", ev.pos.getX(), ev.pos.getY(), ev.delta.getX(), ev.delta.getY()); + clipboardTypeId = 0; + waitingForClipboardData = true, + waitingForClipboardEvents = true; - if (modal.child != nullptr) - return modal.child->focus(); + // begin clipboard dance here + if (puglPaste(view) != PUGL_SUCCESS) + { + dataSize = 0; + waitingForClipboardEvents = false; + return nullptr; + } -#ifndef DPF_TEST_WINDOW_CPP - FOR_EACH_TOP_LEVEL_WIDGET_INV(rit) + #ifdef DGL_USING_X11 + // wait for type request, clipboardTypeId must be != 0 to be valid + int retry = static_cast<int>(2 / 0.03); + while (clipboardTypeId == 0 && waitingForClipboardData && --retry >= 0) { - TopLevelWidget* const widget(*rit); + if (puglX11UpdateWithoutExposures(appData->world) != PUGL_SUCCESS) + break; + } + #endif + + if (clipboardTypeId == 0) + { + dataSize = 0; + waitingForClipboardEvents = false; + return nullptr; + } - if (widget->isVisible() && widget->pData->scrollEvent(ev)) + #ifdef DGL_USING_X11 + // wait for actual data (assumes offer was accepted) + retry = static_cast<int>(2 / 0.03); + while (waitingForClipboardData && --retry >= 0) + { + if (puglX11UpdateWithoutExposures(appData->world) != PUGL_SUCCESS) break; } -#endif + #endif + + if (clipboardTypeId == 0) + { + dataSize = 0; + waitingForClipboardEvents = false; + return nullptr; + } + + waitingForClipboardEvents = false; + return puglGetClipboard(view, clipboardTypeId - 1, &dataSize); +} + +uint32_t Window::PrivateData::onClipboardDataOffer() +{ + DGL_DBG("onClipboardDataOffer\n"); + + if ((clipboardTypeId = self->onClipboardDataOffer()) != 0) + return clipboardTypeId; + + // stop waiting for data, it was rejected + waitingForClipboardData = false; + return 0; +} + +void Window::PrivateData::onClipboardData(const uint32_t typeId) +{ + if (clipboardTypeId != typeId) + clipboardTypeId = 0; + + waitingForClipboardData = false; } #if defined(DEBUG) && defined(DGL_DEBUG_EVENTS) @@ -905,9 +850,41 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu { Window::PrivateData* const pData = (Window::PrivateData*)puglGetHandle(view); #if defined(DEBUG) && defined(DGL_DEBUG_EVENTS) - printEvent(event, "pugl event: ", true); + if (event->type != PUGL_TIMER) { + printEvent(event, "pugl event: ", true); + } #endif + if (pData->waitingForClipboardEvents) + { + switch (event->type) + { + case PUGL_UPDATE: + case PUGL_EXPOSE: + case PUGL_FOCUS_IN: + case PUGL_FOCUS_OUT: + case PUGL_KEY_PRESS: + case PUGL_KEY_RELEASE: + case PUGL_TEXT: + case PUGL_POINTER_IN: + case PUGL_POINTER_OUT: + case PUGL_BUTTON_PRESS: + case PUGL_BUTTON_RELEASE: + case PUGL_MOTION: + case PUGL_SCROLL: + case PUGL_TIMER: + case PUGL_LOOP_ENTER: + case PUGL_LOOP_LEAVE: + return PUGL_SUCCESS; + case PUGL_DATA_OFFER: + case PUGL_DATA: + break; + default: + d_stdout("Got event %d while waitingForClipboardEvents", event->type); + break; + } + } + switch (event->type) { ///< No event @@ -916,10 +893,10 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu ///< View created, a #PuglEventCreate case PUGL_CREATE: -#ifdef HAVE_X11 + #ifdef DGL_USING_X11 if (! pData->isEmbed) - puglX11SetWindowTypeAndPID(view); -#endif + puglX11SetWindowTypeAndPID(view, pData->appData->isStandalone); + #endif break; ///< View destroyed, a #PuglEventDestroy @@ -969,7 +946,6 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu case PUGL_KEY_RELEASE: { // unused x, y, xRoot, yRoot (double) - // TODO special keys? Widget::KeyboardEvent ev; ev.mod = event->key.state; ev.flags = event->key.flags; @@ -977,8 +953,14 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu ev.press = event->type == PUGL_KEY_PRESS; ev.key = event->key.key; ev.keycode = event->key.keycode; - if ((ev.mod & kModifierShift) != 0 && ev.key >= 'a' && ev.key <= 'z') - ev.key -= 'a' - 'A'; // a-z -> A-Z + + // keyboard events must always be lowercase + if (ev.key >= 'A' && ev.key <= 'Z') + { + ev.key += 'a' - 'A'; // A-Z -> a-z + ev.mod |= kModifierShift; + } + pData->onPuglKey(ev); break; } @@ -1014,7 +996,7 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu ev.mod = event->button.state; ev.flags = event->button.flags; ev.time = static_cast<uint>(event->button.time * 1000.0 + 0.5); - ev.button = event->button.button; + ev.button = event->button.button + 1; ev.press = event->type == PUGL_BUTTON_PRESS; ev.pos = Point<double>(event->button.x, event->button.y); ev.absolutePos = ev.pos; @@ -1067,6 +1049,17 @@ PuglStatus Window::PrivateData::puglEventCallback(PuglView* const view, const Pu ///< Recursive loop left, a #PuglEventLoopLeave case PUGL_LOOP_LEAVE: break; + + ///< Data offered from clipboard, a #PuglDataOfferEvent + case PUGL_DATA_OFFER: + if (const uint32_t offerTypeId = pData->onClipboardDataOffer()) + puglAcceptOffer(view, &event->offer, offerTypeId - 1); + break; + + ///< Data available from clipboard, a #PuglDataEvent + case PUGL_DATA: + pData->onClipboardData(event->data.typeIndex + 1); + break; } return PUGL_SUCCESS; diff --git a/dgl/src/WindowPrivateData.hpp b/dgl/src/WindowPrivateData.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -44,9 +44,6 @@ struct Window::PrivateData : IdleCallback { /** Pugl view instance. */ PuglView* view; - /** Pugl view instance of the transient parent window. */ - PuglView* const transientParentView; - /** Reserved space for graphics context. */ mutable uint8_t graphicsContext[sizeof(void*)]; @@ -63,6 +60,9 @@ struct Window::PrivateData : IdleCallback { /** Whether this Window is embed into another (usually not DGL-controlled) Window. */ const bool isEmbed; + /** Whether to ignore resize requests and feed them into the host instead. used for VST3 */ + const bool usesSizeRequest; + /** Scale factor to report to widgets on request, purely informational. */ double scaleFactor; @@ -77,9 +77,19 @@ struct Window::PrivateData : IdleCallback { /** Whether to ignore idle callback requests, useful for temporary windows. */ bool ignoreIdleCallbacks; -#ifdef DISTRHO_OS_WINDOWS - /** Selected file for openFileBrowser on windows, stored for fake async operation. */ - const char* win32SelectedFile; + /** Whether we are waiting to receive clipboard data, ignoring some events in the process. */ + bool waitingForClipboardData; + bool waitingForClipboardEvents; + + /** The type id returned by the last onClipboardDataOffer call. */ + uint32_t clipboardTypeId; + + /** Render to a picture file when non-null, automatically free+unset after saving. */ + char* filenameToRenderInto; + +#ifndef DGL_FILE_BROWSER_DISABLED + /** Handle for file browser dialog operations. */ + DGL_NAMESPACE::FileBrowserHandle fileBrowserHandle; #endif /** Modal window setup. */ @@ -121,7 +131,7 @@ struct Window::PrivateData : IdleCallback { /** Constructor for an embed Window, with a few extra hints from the host side. */ explicit PrivateData(Application& app, Window* self, uintptr_t parentWindowHandle, - uint width, uint height, double scaling, bool resizable); + uint width, uint height, double scaling, bool resizable, bool isVST3); /** Destructor. */ ~PrivateData() override; @@ -156,12 +166,11 @@ struct Window::PrivateData : IdleCallback { #ifndef DGL_FILE_BROWSER_DISABLED // file handling - bool openFileBrowser(const Window::FileBrowserOptions& options); -# ifdef DISTRHO_OS_MAC - static void openPanelCallback(PuglView* view, const char* path); -# endif + bool openFileBrowser(const DGL_NAMESPACE::FileBrowserOptions& options); #endif + static void renderToPicture(const char* filename, const GraphicsContext& context, uint width, uint height); + // modal handling void startModal(); void stopModal(); @@ -173,12 +182,16 @@ struct Window::PrivateData : IdleCallback { void onPuglClose(); void onPuglFocus(bool focus, CrossingMode mode); void onPuglKey(const Widget::KeyboardEvent& ev); - void onPuglSpecial(const Widget::SpecialEvent& ev); void onPuglText(const Widget::CharacterInputEvent& ev); void onPuglMouse(const Widget::MouseEvent& ev); void onPuglMotion(const Widget::MotionEvent& ev); void onPuglScroll(const Widget::ScrollEvent& ev); + // clipboard related handling + const void* getClipboard(size_t& dataSize); + uint32_t onClipboardDataOffer(); + void onClipboardData(uint32_t typeId); + // Pugl event handling entry point static PuglStatus puglEventCallback(PuglView* view, const PuglEvent* event); @@ -189,124 +202,4 @@ struct Window::PrivateData : IdleCallback { END_NAMESPACE_DGL -#if 0 -// #if defined(DISTRHO_OS_HAIKU) -// BApplication* bApplication; -// BView* bView; -// BWindow* bWindow; -#if defined(DISTRHO_OS_MAC) -// NSView<PuglGenericView>* mView; -// id mWindow; -// id mParentWindow; -# ifndef DGL_FILE_BROWSER_DISABLED - NSOpenPanel* fOpenFilePanel; - id fFilePanelDelegate; -# endif -#elif defined(DISTRHO_OS_WINDOWS) -// HWND hwnd; -// HWND hwndParent; -# ifndef DGL_FILE_BROWSER_DISABLED - String fSelectedFile; -# endif -#endif -#endif - -#if 0 -// ----------------------------------------------------------------------- -// Window Private - -struct Window::PrivateData { - // ------------------------------------------------------------------- - - bool handlePluginSpecial(const bool press, const Key key) - { - DBGp("PUGL: handlePluginSpecial : %i %i\n", press, key); - - if (fModal.childFocus != nullptr) - { - fModal.childFocus->focus(); - return true; - } - - int mods = 0x0; - - switch (key) - { - case kKeyShift: - mods |= kModifierShift; - break; - case kKeyControl: - mods |= kModifierControl; - break; - case kKeyAlt: - mods |= kModifierAlt; - break; - default: - break; - } - - if (mods != 0x0) - { - if (press) - fView->mods |= mods; - else - fView->mods &= ~(mods); - } - - Widget::SpecialEvent ev; - ev.press = press; - ev.key = key; - ev.mod = static_cast<Modifier>(fView->mods); - ev.time = 0; - - FOR_EACH_WIDGET_INV(rit) - { - Widget* const widget(*rit); - - if (widget->isVisible() && widget->onSpecial(ev)) - return true; - } - - return false; - } - -#if defined(DISTRHO_OS_MAC) && !defined(DGL_FILE_BROWSER_DISABLED) - static void openPanelDidEnd(NSOpenPanel* panel, int returnCode, void *userData) - { - PrivateData* pData = (PrivateData*)userData; - - if (returnCode == NSOKButton) - { - NSArray* urls = [panel URLs]; - NSURL* fileUrl = nullptr; - - for (NSUInteger i = 0, n = [urls count]; i < n && !fileUrl; ++i) - { - NSURL* url = (NSURL*)[urls objectAtIndex:i]; - if ([url isFileURL]) - fileUrl = url; - } - - if (fileUrl) - { - PuglView* view = pData->fView; - if (view->fileSelectedFunc) - { - const char* fileName = [fileUrl.path UTF8String]; - view->fileSelectedFunc(view, fileName); - } - } - } - - [pData->fOpenFilePanel release]; - pData->fOpenFilePanel = nullptr; - } -#endif - - // ------------------------------------------------------------------- - - DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PrivateData) -}; -#endif - #endif // DGL_WINDOW_PRIVATE_DATA_HPP_INCLUDED diff --git a/dgl/src/nanovg/fontstash.h b/dgl/src/nanovg/fontstash.h @@ -157,31 +157,170 @@ struct FONSttFontImpl { }; typedef struct FONSttFontImpl FONSttFontImpl; -static FT_Library ftLibrary; +#else + +#define STB_TRUETYPE_IMPLEMENTATION +static void* fons__tmpalloc(size_t size, void* up); +static void fons__tmpfree(void* ptr, void* up); +#define STBTT_malloc(x,u) fons__tmpalloc(x,u) +#define STBTT_free(x,u) fons__tmpfree(x,u) +#include "stb_truetype.h" + +struct FONSttFontImpl { + stbtt_fontinfo font; +}; +typedef struct FONSttFontImpl FONSttFontImpl; + +#endif + +#ifndef FONS_SCRATCH_BUF_SIZE +# define FONS_SCRATCH_BUF_SIZE 96000 +#endif +#ifndef FONS_HASH_LUT_SIZE +# define FONS_HASH_LUT_SIZE 256 +#endif +#ifndef FONS_INIT_FONTS +# define FONS_INIT_FONTS 4 +#endif +#ifndef FONS_INIT_GLYPHS +# define FONS_INIT_GLYPHS 256 +#endif +#ifndef FONS_INIT_ATLAS_NODES +# define FONS_INIT_ATLAS_NODES 256 +#endif +#ifndef FONS_VERTEX_COUNT +# define FONS_VERTEX_COUNT 1024 +#endif +#ifndef FONS_MAX_STATES +# define FONS_MAX_STATES 20 +#endif +#ifndef FONS_MAX_FALLBACKS +# define FONS_MAX_FALLBACKS 20 +#endif + +static unsigned int fons__hashint(unsigned int a) +{ + a += ~(a<<15); + a ^= (a>>10); + a += (a<<3); + a ^= (a>>6); + a += ~(a<<11); + a ^= (a>>16); + return a; +} + +static int fons__mini(int a, int b) +{ + return a < b ? a : b; +} + +static int fons__maxi(int a, int b) +{ + return a > b ? a : b; +} + +struct FONSglyph +{ + unsigned int codepoint; + int index; + int next; + short size, blur; + short x0,y0,x1,y1; + short xadv,xoff,yoff; +}; +typedef struct FONSglyph FONSglyph; + +struct FONSfont +{ + FONSttFontImpl font; + char name[64]; + unsigned char* data; + int dataSize; + unsigned char freeData; + float ascender; + float descender; + float lineh; + FONSglyph* glyphs; + int cglyphs; + int nglyphs; + int lut[FONS_HASH_LUT_SIZE]; + int fallbacks[FONS_MAX_FALLBACKS]; + int nfallbacks; +}; +typedef struct FONSfont FONSfont; + +struct FONSstate +{ + int font; + int align; + float size; + unsigned int color; + float blur; + float spacing; +}; +typedef struct FONSstate FONSstate; + +struct FONSatlasNode { + short x, y, width; +}; +typedef struct FONSatlasNode FONSatlasNode; + +struct FONSatlas +{ + int width, height; + FONSatlasNode* nodes; + int nnodes; + int cnodes; +}; +typedef struct FONSatlas FONSatlas; + +struct FONScontext +{ + FONSparams params; + float itw,ith; + unsigned char* texData; + int dirtyRect[4]; + FONSfont** fonts; + FONSatlas* atlas; + int cfonts; + int nfonts; + float verts[FONS_VERTEX_COUNT*2]; + float tcoords[FONS_VERTEX_COUNT*2]; + unsigned int colors[FONS_VERTEX_COUNT]; + int nverts; + unsigned char* scratch; + int nscratch; + FONSstate states[FONS_MAX_STATES]; + int nstates; + void (*handleError)(void* uptr, int error, int val); + void* errorUptr; +#ifdef FONS_USE_FREETYPE + FT_Library ftLibrary; +#endif +}; + +#ifdef FONS_USE_FREETYPE int fons__tt_init(FONScontext *context) { FT_Error ftError; - FONS_NOTUSED(context); - ftError = FT_Init_FreeType(&ftLibrary); + ftError = FT_Init_FreeType(&context->ftLibrary); return ftError == 0; } int fons__tt_done(FONScontext *context) { FT_Error ftError; - FONS_NOTUSED(context); - ftError = FT_Done_FreeType(ftLibrary); + ftError = FT_Done_FreeType(context->ftLibrary); return ftError == 0; } int fons__tt_loadFont(FONScontext *context, FONSttFontImpl *font, unsigned char *data, int dataSize, int fontIndex) { FT_Error ftError; - FONS_NOTUSED(context); //font->font.userdata = stash; - ftError = FT_New_Memory_Face(ftLibrary, (const FT_Byte*)data, dataSize, fontIndex, &font->font); + ftError = FT_New_Memory_Face(context->ftLibrary, (const FT_Byte*)data, dataSize, fontIndex, &font->font); return ftError == 0; } @@ -269,18 +408,6 @@ int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2) #else -#define STB_TRUETYPE_IMPLEMENTATION -static void* fons__tmpalloc(size_t size, void* up); -static void fons__tmpfree(void* ptr, void* up); -#define STBTT_malloc(x,u) fons__tmpalloc(x,u) -#define STBTT_free(x,u) fons__tmpfree(x,u) -#include "stb_truetype.h" - -struct FONSttFontImpl { - stbtt_fontinfo font; -}; -typedef struct FONSttFontImpl FONSttFontImpl; - int fons__tt_init(FONScontext *context) { FONS_NOTUSED(context); @@ -350,129 +477,6 @@ int fons__tt_getGlyphKernAdvance(FONSttFontImpl *font, int glyph1, int glyph2) #endif -#ifndef FONS_SCRATCH_BUF_SIZE -# define FONS_SCRATCH_BUF_SIZE 96000 -#endif -#ifndef FONS_HASH_LUT_SIZE -# define FONS_HASH_LUT_SIZE 256 -#endif -#ifndef FONS_INIT_FONTS -# define FONS_INIT_FONTS 4 -#endif -#ifndef FONS_INIT_GLYPHS -# define FONS_INIT_GLYPHS 256 -#endif -#ifndef FONS_INIT_ATLAS_NODES -# define FONS_INIT_ATLAS_NODES 256 -#endif -#ifndef FONS_VERTEX_COUNT -# define FONS_VERTEX_COUNT 1024 -#endif -#ifndef FONS_MAX_STATES -# define FONS_MAX_STATES 20 -#endif -#ifndef FONS_MAX_FALLBACKS -# define FONS_MAX_FALLBACKS 20 -#endif - -static unsigned int fons__hashint(unsigned int a) -{ - a += ~(a<<15); - a ^= (a>>10); - a += (a<<3); - a ^= (a>>6); - a += ~(a<<11); - a ^= (a>>16); - return a; -} - -static int fons__mini(int a, int b) -{ - return a < b ? a : b; -} - -static int fons__maxi(int a, int b) -{ - return a > b ? a : b; -} - -struct FONSglyph -{ - unsigned int codepoint; - int index; - int next; - short size, blur; - short x0,y0,x1,y1; - short xadv,xoff,yoff; -}; -typedef struct FONSglyph FONSglyph; - -struct FONSfont -{ - FONSttFontImpl font; - char name[64]; - unsigned char* data; - int dataSize; - unsigned char freeData; - float ascender; - float descender; - float lineh; - FONSglyph* glyphs; - int cglyphs; - int nglyphs; - int lut[FONS_HASH_LUT_SIZE]; - int fallbacks[FONS_MAX_FALLBACKS]; - int nfallbacks; -}; -typedef struct FONSfont FONSfont; - -struct FONSstate -{ - int font; - int align; - float size; - unsigned int color; - float blur; - float spacing; -}; -typedef struct FONSstate FONSstate; - -struct FONSatlasNode { - short x, y, width; -}; -typedef struct FONSatlasNode FONSatlasNode; - -struct FONSatlas -{ - int width, height; - FONSatlasNode* nodes; - int nnodes; - int cnodes; -}; -typedef struct FONSatlas FONSatlas; - -struct FONScontext -{ - FONSparams params; - float itw,ith; - unsigned char* texData; - int dirtyRect[4]; - FONSfont** fonts; - FONSatlas* atlas; - int cfonts; - int nfonts; - float verts[FONS_VERTEX_COUNT*2]; - float tcoords[FONS_VERTEX_COUNT*2]; - unsigned int colors[FONS_VERTEX_COUNT]; - int nverts; - unsigned char* scratch; - int nscratch; - FONSstate states[FONS_MAX_STATES]; - int nstates; - void (*handleError)(void* uptr, int error, int val); - void* errorUptr; -}; - #ifdef STB_TRUETYPE_IMPLEMENTATION static void* fons__tmpalloc(size_t size, void* up) @@ -907,6 +911,8 @@ static int fons__allocFont(FONScontext* stash) stash->fonts = (FONSfont**)realloc(stash->fonts, sizeof(FONSfont*) * stash->cfonts); if (stash->fonts == NULL) return -1; + for (int i=stash->nfonts; i<stash->cfonts; ++i) + stash->fonts[i] = NULL; } font = (FONSfont*)malloc(sizeof(FONSfont)); if (font == NULL) goto error; @@ -961,7 +967,10 @@ int fonsAddFontMem(FONScontext* stash, const char* name, unsigned char* data, in int idx = fons__allocFont(stash); if (idx == FONS_INVALID) + { + if (freeData && data) free(data); return FONS_INVALID; + } font = stash->fonts[idx]; @@ -1015,6 +1024,8 @@ static FONSglyph* fons__allocGlyph(FONSfont* font) font->cglyphs = font->cglyphs == 0 ? 8 : font->cglyphs * 2; font->glyphs = (FONSglyph*)realloc(font->glyphs, sizeof(FONSglyph) * font->cglyphs); if (font->glyphs == NULL) return NULL; + for (int i=font->nglyphs; i<font->cglyphs; ++i) + memset(&font->glyphs[i], 0, sizeof(*font->glyphs)); } font->nglyphs++; return &font->glyphs[font->nglyphs-1]; @@ -1680,8 +1691,8 @@ void fonsDeleteInternal(FONScontext* stash) if (stash->fonts) free(stash->fonts); if (stash->texData) free(stash->texData); if (stash->scratch) free(stash->scratch); - free(stash); fons__tt_done(stash); + free(stash); } void fonsSetErrorCallback(FONScontext* stash, void (*callback)(void* uptr, int error, int val), void* uptr) diff --git a/dgl/src/nanovg/nanovg.c b/dgl/src/nanovg/nanovg.c @@ -24,8 +24,21 @@ #include "nanovg.h" #define FONTSTASH_IMPLEMENTATION #include "fontstash.h" + +#ifndef NVG_NO_STB #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" +#endif + +#ifdef NVG_DISABLE_SKIPPING_WHITESPACE +#define NVG_SKIPPED_CHAR NVG_SPACE +#else +#define NVG_SKIPPED_CHAR NVG_CHAR +#endif + +#ifndef NVG_FONT_TEXTURE_FLAGS +#define NVG_FONT_TEXTURE_FLAGS 0 +#endif #ifdef _MSC_VER #pragma warning(disable: 4100) // unreferenced formal parameter @@ -74,7 +87,7 @@ struct NVGstate { float miterLimit; int lineJoin; int lineCap; - float alpha; + NVGcolor tint; float xform[6]; NVGscissor scissor; float fontSize; @@ -109,6 +122,14 @@ struct NVGpathCache { }; typedef struct NVGpathCache NVGpathCache; +struct NVGfontContext { // Fontstash context plus font images; shared between shared NanoVG contexts. + int refCount; + struct FONScontext* fs; + int fontImages[NVG_MAX_FONTIMAGES]; + int fontImageIdx; +}; +typedef struct NVGfontContext NVGfontContext; + struct NVGcontext { NVGparams params; float* commands; @@ -122,9 +143,7 @@ struct NVGcontext { float distTol; float fringeWidth; float devicePxRatio; - struct FONScontext* fs; - int fontImages[NVG_MAX_FONTIMAGES]; - int fontImageIdx; + NVGfontContext* fontContext; int drawCallCount; int fillTriCount; int strokeTriCount; @@ -283,7 +302,7 @@ static NVGstate* nvg__getState(NVGcontext* ctx) return &ctx->states[ctx->nstates-1]; } -NVGcontext* nvgCreateInternal(NVGparams* params) +NVGcontext* nvgCreateInternal(NVGparams* params, NVGcontext* other) // Share the fonts and images of 'other' if it's non-NULL. { FONSparams fontParams; NVGcontext* ctx = (NVGcontext*)malloc(sizeof(NVGcontext)); @@ -292,8 +311,16 @@ NVGcontext* nvgCreateInternal(NVGparams* params) memset(ctx, 0, sizeof(NVGcontext)); ctx->params = *params; - for (i = 0; i < NVG_MAX_FONTIMAGES; i++) - ctx->fontImages[i] = 0; + if (other) { + ctx->fontContext = other->fontContext; + ctx->fontContext->refCount++; + } else { + ctx->fontContext = (NVGfontContext*)malloc(sizeof(NVGfontContext)); + if (ctx->fontContext == NULL) goto error; + for (i = 0; i < NVG_MAX_FONTIMAGES; i++) + ctx->fontContext->fontImages[i] = 0; + ctx->fontContext->refCount = 1; + } ctx->commands = (float*)malloc(sizeof(float)*NVG_INIT_COMMANDS_SIZE); if (!ctx->commands) goto error; @@ -308,25 +335,32 @@ NVGcontext* nvgCreateInternal(NVGparams* params) nvg__setDevicePixelRatio(ctx, 1.0f); - if (ctx->params.renderCreate(ctx->params.userPtr) == 0) goto error; + if (ctx->params.renderCreate(ctx->params.userPtr, other ? other->params.userPtr : NULL) == 0) goto error; // Init font rendering - memset(&fontParams, 0, sizeof(fontParams)); - fontParams.width = NVG_INIT_FONTIMAGE_SIZE; - fontParams.height = NVG_INIT_FONTIMAGE_SIZE; - fontParams.flags = FONS_ZERO_TOPLEFT; - fontParams.renderCreate = NULL; - fontParams.renderUpdate = NULL; - fontParams.renderDraw = NULL; - fontParams.renderDelete = NULL; - fontParams.userPtr = NULL; - ctx->fs = fonsCreateInternal(&fontParams); - if (ctx->fs == NULL) goto error; - - // Create font texture - ctx->fontImages[0] = ctx->params.renderCreateTexture(ctx->params.userPtr, NVG_TEXTURE_ALPHA, fontParams.width, fontParams.height, 0, NULL); - if (ctx->fontImages[0] == 0) goto error; - ctx->fontImageIdx = 0; + if (!other) { + memset(&fontParams, 0, sizeof(fontParams)); + fontParams.width = NVG_INIT_FONTIMAGE_SIZE; + fontParams.height = NVG_INIT_FONTIMAGE_SIZE; + fontParams.flags = FONS_ZERO_TOPLEFT; + fontParams.renderCreate = NULL; + fontParams.renderUpdate = NULL; + fontParams.renderDraw = NULL; + fontParams.renderDelete = NULL; + fontParams.userPtr = NULL; + ctx->fontContext->fs = fonsCreateInternal(&fontParams); + if (ctx->fontContext->fs == NULL) goto error; + + // Create font texture + ctx->fontContext->fontImages[0] = ctx->params.renderCreateTexture(ctx->params.userPtr, + NVG_TEXTURE_ALPHA, + fontParams.width, + fontParams.height, + NVG_FONT_TEXTURE_FLAGS, + NULL); + if (ctx->fontContext->fontImages[0] == 0) goto error; + ctx->fontContext->fontImageIdx = 0; + } return ctx; @@ -347,14 +381,18 @@ void nvgDeleteInternal(NVGcontext* ctx) if (ctx->commands != NULL) free(ctx->commands); if (ctx->cache != NULL) nvg__deletePathCache(ctx->cache); - if (ctx->fs) - fonsDeleteInternal(ctx->fs); + if (ctx->fontContext != NULL && --ctx->fontContext->refCount == 0) { + if (ctx->fontContext->fs) + fonsDeleteInternal(ctx->fontContext->fs); - for (i = 0; i < NVG_MAX_FONTIMAGES; i++) { - if (ctx->fontImages[i] != 0) { - nvgDeleteImage(ctx, ctx->fontImages[i]); - ctx->fontImages[i] = 0; + for (i = 0; i < NVG_MAX_FONTIMAGES; i++) { + if (ctx->fontContext->fontImages[i] != 0) { + nvgDeleteImage(ctx, ctx->fontContext->fontImages[i]); + ctx->fontContext->fontImages[i] = 0; + } } + + free(ctx->fontContext); } if (ctx->params.renderDelete != NULL) @@ -391,30 +429,30 @@ void nvgCancelFrame(NVGcontext* ctx) void nvgEndFrame(NVGcontext* ctx) { ctx->params.renderFlush(ctx->params.userPtr); - if (ctx->fontImageIdx != 0) { - int fontImage = ctx->fontImages[ctx->fontImageIdx]; + if (ctx->fontContext->fontImageIdx != 0) { + int fontImage = ctx->fontContext->fontImages[ctx->fontContext->fontImageIdx]; int i, j, iw, ih; // delete images that smaller than current one if (fontImage == 0) return; nvgImageSize(ctx, fontImage, &iw, &ih); - for (i = j = 0; i < ctx->fontImageIdx; i++) { - if (ctx->fontImages[i] != 0) { + for (i = j = 0; i < ctx->fontContext->fontImageIdx; i++) { + if (ctx->fontContext->fontImages[i] != 0) { int nw, nh; - nvgImageSize(ctx, ctx->fontImages[i], &nw, &nh); + nvgImageSize(ctx, ctx->fontContext->fontImages[i], &nw, &nh); if (nw < iw || nh < ih) - nvgDeleteImage(ctx, ctx->fontImages[i]); + nvgDeleteImage(ctx, ctx->fontContext->fontImages[i]); else - ctx->fontImages[j++] = ctx->fontImages[i]; + ctx->fontContext->fontImages[j++] = ctx->fontContext->fontImages[i]; } } // make current font image to first - ctx->fontImages[j++] = ctx->fontImages[0]; - ctx->fontImages[0] = fontImage; - ctx->fontImageIdx = 0; + ctx->fontContext->fontImages[j++] = ctx->fontContext->fontImages[0]; + ctx->fontContext->fontImages[0] = fontImage; + ctx->fontContext->fontImageIdx = 0; // clear all images after j for (i = j; i < NVG_MAX_FONTIMAGES; i++) - ctx->fontImages[i] = 0; + ctx->fontContext->fontImages[i] = 0; } } @@ -651,7 +689,7 @@ void nvgReset(NVGcontext* ctx) state->miterLimit = 10.0f; state->lineCap = NVG_BUTT; state->lineJoin = NVG_MITER; - state->alpha = 1.0f; + state->tint = nvgRGBAf(1, 1, 1, 1); nvgTransformIdentity(state->xform); state->scissor.extent[0] = -1.0f; @@ -699,7 +737,33 @@ void nvgLineJoin(NVGcontext* ctx, int join) void nvgGlobalAlpha(NVGcontext* ctx, float alpha) { NVGstate* state = nvg__getState(ctx); - state->alpha = alpha; + state->tint.a = alpha; +} + +void nvgGlobalTint(NVGcontext* ctx, NVGcolor tint) +{ + NVGstate* state = nvg__getState(ctx); + state->tint = tint; +} + +NVGcolor nvgGetGlobalTint(NVGcontext* ctx) +{ + NVGstate* state = nvg__getState(ctx); + return state->tint; +} + +void nvgAlpha(NVGcontext* ctx, float alpha) +{ + NVGstate* state = nvg__getState(ctx); + state->tint.a *= alpha; +} + +void nvgTint(NVGcontext* ctx, NVGcolor tint) +{ + NVGstate* state = nvg__getState(ctx); + int i; + for (i = 0; i < 4; i++) + state->tint.rgba[i] *= tint.rgba[i]; } void nvgTransform(NVGcontext* ctx, float a, float b, float c, float d, float e, float f) @@ -788,6 +852,7 @@ void nvgFillPaint(NVGcontext* ctx, NVGpaint paint) nvgTransformMultiply(state->fill.xform, state->xform); } +#ifndef NVG_NO_STB int nvgCreateImage(NVGcontext* ctx, const char* filename, int imageFlags) { int w, h, n, image; @@ -816,10 +881,16 @@ int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, unsigned char* data, int stbi_image_free(img); return image; } +#endif + +int nvgCreateImageRaw(NVGcontext* ctx, int w, int h, int imageFlags, NVGtexture format, const unsigned char* data) +{ + return ctx->params.renderCreateTexture(ctx->params.userPtr, format, w, h, imageFlags, data); +} int nvgCreateImageRGBA(NVGcontext* ctx, int w, int h, int imageFlags, const unsigned char* data) { - return ctx->params.renderCreateTexture(ctx->params.userPtr, NVG_TEXTURE_RGBA, w, h, imageFlags, data); + return nvgCreateImageRaw(ctx, w, h, imageFlags, NVG_TEXTURE_RGBA, data); } void nvgUpdateImage(NVGcontext* ctx, int image, const unsigned char* data) @@ -2229,9 +2300,11 @@ void nvgFill(NVGcontext* ctx) else nvg__expandFill(ctx, 0.0f, NVG_MITER, 2.4f); - // Apply global alpha - fillPaint.innerColor.a *= state->alpha; - fillPaint.outerColor.a *= state->alpha; + // Apply global tint + for (i = 0; i < 4; i++) { + fillPaint.innerColor.rgba[i] *= state->tint.rgba[i]; + fillPaint.outerColor.rgba[i] *= state->tint.rgba[i]; + } ctx->params.renderFill(ctx->params.userPtr, &fillPaint, state->compositeOperation, &state->scissor, ctx->fringeWidth, ctx->cache->bounds, ctx->cache->paths, ctx->cache->npaths); @@ -2264,9 +2337,11 @@ void nvgStroke(NVGcontext* ctx) strokeWidth = ctx->fringeWidth; } - // Apply global alpha - strokePaint.innerColor.a *= state->alpha; - strokePaint.outerColor.a *= state->alpha; + // Apply global tint + for (i = 0; i < 4; i++) { + strokePaint.innerColor.rgba[i] *= state->tint.rgba[i]; + strokePaint.outerColor.rgba[i] *= state->tint.rgba[i]; + } nvg__flattenPaths(ctx); @@ -2289,35 +2364,35 @@ void nvgStroke(NVGcontext* ctx) // Add fonts int nvgCreateFont(NVGcontext* ctx, const char* name, const char* filename) { - return fonsAddFont(ctx->fs, name, filename, 0); + return fonsAddFont(ctx->fontContext->fs, name, filename, 0); } int nvgCreateFontAtIndex(NVGcontext* ctx, const char* name, const char* filename, const int fontIndex) { - return fonsAddFont(ctx->fs, name, filename, fontIndex); + return fonsAddFont(ctx->fontContext->fs, name, filename, fontIndex); } int nvgCreateFontMem(NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData) { - return fonsAddFontMem(ctx->fs, name, data, ndata, freeData, 0); + return fonsAddFontMem(ctx->fontContext->fs, name, data, ndata, freeData, 0); } int nvgCreateFontMemAtIndex(NVGcontext* ctx, const char* name, unsigned char* data, int ndata, int freeData, const int fontIndex) { - return fonsAddFontMem(ctx->fs, name, data, ndata, freeData, fontIndex); + return fonsAddFontMem(ctx->fontContext->fs, name, data, ndata, freeData, fontIndex); } int nvgFindFont(NVGcontext* ctx, const char* name) { if (name == NULL) return -1; - return fonsGetFontByName(ctx->fs, name); + return fonsGetFontByName(ctx->fontContext->fs, name); } int nvgAddFallbackFontId(NVGcontext* ctx, int baseFont, int fallbackFont) { if(baseFont == -1 || fallbackFont == -1) return 0; - return fonsAddFallbackFont(ctx->fs, baseFont, fallbackFont); + return fonsAddFallbackFont(ctx->fontContext->fs, baseFont, fallbackFont); } int nvgAddFallbackFont(NVGcontext* ctx, const char* baseFont, const char* fallbackFont) @@ -2327,7 +2402,7 @@ int nvgAddFallbackFont(NVGcontext* ctx, const char* baseFont, const char* fallba void nvgResetFallbackFontsId(NVGcontext* ctx, int baseFont) { - fonsResetFallbackFont(ctx->fs, baseFont); + fonsResetFallbackFont(ctx->fontContext->fs, baseFont); } void nvgResetFallbackFonts(NVGcontext* ctx, const char* baseFont) @@ -2375,7 +2450,7 @@ void nvgFontFaceId(NVGcontext* ctx, int font) void nvgFontFace(NVGcontext* ctx, const char* font) { NVGstate* state = nvg__getState(ctx); - state->fontId = fonsGetFontByName(ctx->fs, font); + state->fontId = fonsGetFontByName(ctx->fontContext->fs, font); } static float nvg__quantize(float a, float d) @@ -2392,12 +2467,12 @@ static void nvg__flushTextTexture(NVGcontext* ctx) { int dirty[4]; - if (fonsValidateTexture(ctx->fs, dirty)) { - int fontImage = ctx->fontImages[ctx->fontImageIdx]; + if (fonsValidateTexture(ctx->fontContext->fs, dirty)) { + int fontImage = ctx->fontContext->fontImages[ctx->fontContext->fontImageIdx]; // Update texture if (fontImage != 0) { int iw, ih; - const unsigned char* data = fonsGetTextureData(ctx->fs, &iw, &ih); + const unsigned char* data = fonsGetTextureData(ctx->fontContext->fs, &iw, &ih); int x = dirty[0]; int y = dirty[1]; int w = dirty[2] - dirty[0]; @@ -2411,37 +2486,42 @@ static int nvg__allocTextAtlas(NVGcontext* ctx) { int iw, ih; nvg__flushTextTexture(ctx); - if (ctx->fontImageIdx >= NVG_MAX_FONTIMAGES-1) + if (ctx->fontContext->fontImageIdx >= NVG_MAX_FONTIMAGES-1) return 0; // if next fontImage already have a texture - if (ctx->fontImages[ctx->fontImageIdx+1] != 0) - nvgImageSize(ctx, ctx->fontImages[ctx->fontImageIdx+1], &iw, &ih); + if (ctx->fontContext->fontImages[ctx->fontContext->fontImageIdx+1] != 0) + nvgImageSize(ctx, ctx->fontContext->fontImages[ctx->fontContext->fontImageIdx+1], &iw, &ih); else { // calculate the new font image size and create it. - nvgImageSize(ctx, ctx->fontImages[ctx->fontImageIdx], &iw, &ih); + nvgImageSize(ctx, ctx->fontContext->fontImages[ctx->fontContext->fontImageIdx], &iw, &ih); if (iw > ih) ih *= 2; else iw *= 2; if (iw > NVG_MAX_FONTIMAGE_SIZE || ih > NVG_MAX_FONTIMAGE_SIZE) iw = ih = NVG_MAX_FONTIMAGE_SIZE; - ctx->fontImages[ctx->fontImageIdx+1] = ctx->params.renderCreateTexture(ctx->params.userPtr, NVG_TEXTURE_ALPHA, iw, ih, 0, NULL); + ctx->fontContext->fontImages[ctx->fontContext->fontImageIdx+1] + = ctx->params.renderCreateTexture(ctx->params.userPtr, + NVG_TEXTURE_ALPHA, iw, ih, NVG_FONT_TEXTURE_FLAGS, NULL); } - ++ctx->fontImageIdx; - fonsResetAtlas(ctx->fs, iw, ih); + ++ctx->fontContext->fontImageIdx; + fonsResetAtlas(ctx->fontContext->fs, iw, ih); return 1; } static void nvg__renderText(NVGcontext* ctx, NVGvertex* verts, int nverts) { + int i; NVGstate* state = nvg__getState(ctx); NVGpaint paint = state->fill; // Render triangles. - paint.image = ctx->fontImages[ctx->fontImageIdx]; + paint.image = ctx->fontContext->fontImages[ctx->fontContext->fontImageIdx]; - // Apply global alpha - paint.innerColor.a *= state->alpha; - paint.outerColor.a *= state->alpha; + // Apply global tint + for (i = 0; i < 4; i++) { + paint.innerColor.rgba[i] *= state->tint.rgba[i]; + paint.outerColor.rgba[i] *= state->tint.rgba[i]; + } ctx->params.renderTriangles(ctx->params.userPtr, &paint, state->compositeOperation, &state->scissor, verts, nverts, ctx->fringeWidth); @@ -2449,6 +2529,12 @@ static void nvg__renderText(NVGcontext* ctx, NVGvertex* verts, int nverts) ctx->textTriCount += nverts/3; } +static int nvg__isTransformFlipped(const float *xform) +{ + float det = xform[0] * xform[3] - xform[2] * xform[1]; + return( det < 0); +} + float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char* end) { NVGstate* state = nvg__getState(ctx); @@ -2459,25 +2545,26 @@ float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char* float invscale = 1.0f / scale; int cverts = 0; int nverts = 0; + int isFlipped = nvg__isTransformFlipped(state->xform); if (end == NULL) end = string + strlen(string); if (state->fontId == FONS_INVALID) return x; - fonsSetSize(ctx->fs, state->fontSize*scale); - fonsSetSpacing(ctx->fs, state->letterSpacing*scale); - fonsSetBlur(ctx->fs, state->fontBlur*scale); - fonsSetAlign(ctx->fs, state->textAlign); - fonsSetFont(ctx->fs, state->fontId); + fonsSetSize(ctx->fontContext->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fontContext->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fontContext->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fontContext->fs, state->textAlign); + fonsSetFont(ctx->fontContext->fs, state->fontId); cverts = nvg__maxi(2, (int)(end - string)) * 6; // conservative estimate. verts = nvg__allocTempVerts(ctx, cverts); if (verts == NULL) return x; - fonsTextIterInit(ctx->fs, &iter, x*scale, y*scale, string, end, FONS_GLYPH_BITMAP_REQUIRED); + fonsTextIterInit(ctx->fontContext->fs, &iter, x*scale, y*scale, string, end, FONS_GLYPH_BITMAP_REQUIRED); prevIter = iter; - while (fonsTextIterNext(ctx->fs, &iter, &q)) { + while (fonsTextIterNext(ctx->fontContext->fs, &iter, &q)) { float c[4*2]; if (iter.prevGlyphIndex == -1) { // can not retrieve glyph? if (nverts != 0) { @@ -2487,11 +2574,17 @@ float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char* if (!nvg__allocTextAtlas(ctx)) break; // no memory :( iter = prevIter; - fonsTextIterNext(ctx->fs, &iter, &q); // try again + fonsTextIterNext(ctx->fontContext->fs, &iter, &q); // try again if (iter.prevGlyphIndex == -1) // still can not find glyph? break; } prevIter = iter; + if(isFlipped) { + float tmp; + + tmp = q.y0; q.y0 = q.y1; q.y1 = tmp; + tmp = q.t0; q.t0 = q.t1; q.t1 = tmp; + } // Transform corners. nvgTransformPoint(&c[0],&c[1], state->xform, q.x0*invscale, q.y0*invscale); nvgTransformPoint(&c[2],&c[3], state->xform, q.x1*invscale, q.y0*invscale); @@ -2499,6 +2592,11 @@ float nvgText(NVGcontext* ctx, float x, float y, const char* string, const char* nvgTransformPoint(&c[6],&c[7], state->xform, q.x0*invscale, q.y1*invscale); // Create triangles if (nverts+6 <= cverts) { +#if NVG_FONT_TEXTURE_FLAGS + // align font kerning to integer pixel positions + for (int i = 0; i < 8; ++i) + c[i] = (int)(c[i] + 0.5f); +#endif nvg__vset(&verts[nverts], c[0], c[1], q.s0, q.t0); nverts++; nvg__vset(&verts[nverts], c[4], c[5], q.s1, q.t1); nverts++; nvg__vset(&verts[nverts], c[2], c[3], q.s1, q.t0); nverts++; @@ -2566,18 +2664,18 @@ int nvgTextGlyphPositions(NVGcontext* ctx, float x, float y, const char* string, if (string == end) return 0; - fonsSetSize(ctx->fs, state->fontSize*scale); - fonsSetSpacing(ctx->fs, state->letterSpacing*scale); - fonsSetBlur(ctx->fs, state->fontBlur*scale); - fonsSetAlign(ctx->fs, state->textAlign); - fonsSetFont(ctx->fs, state->fontId); + fonsSetSize(ctx->fontContext->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fontContext->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fontContext->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fontContext->fs, state->textAlign); + fonsSetFont(ctx->fontContext->fs, state->fontId); - fonsTextIterInit(ctx->fs, &iter, x*scale, y*scale, string, end, FONS_GLYPH_BITMAP_OPTIONAL); + fonsTextIterInit(ctx->fontContext->fs, &iter, x*scale, y*scale, string, end, FONS_GLYPH_BITMAP_OPTIONAL); prevIter = iter; - while (fonsTextIterNext(ctx->fs, &iter, &q)) { + while (fonsTextIterNext(ctx->fontContext->fs, &iter, &q)) { if (iter.prevGlyphIndex < 0 && nvg__allocTextAtlas(ctx)) { // can not retrieve glyph? iter = prevIter; - fonsTextIterNext(ctx->fs, &iter, &q); // try again + fonsTextIterNext(ctx->fontContext->fs, &iter, &q); // try again } prevIter = iter; positions[npos].str = iter.str; @@ -2630,20 +2728,20 @@ int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, floa if (string == end) return 0; - fonsSetSize(ctx->fs, state->fontSize*scale); - fonsSetSpacing(ctx->fs, state->letterSpacing*scale); - fonsSetBlur(ctx->fs, state->fontBlur*scale); - fonsSetAlign(ctx->fs, state->textAlign); - fonsSetFont(ctx->fs, state->fontId); + fonsSetSize(ctx->fontContext->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fontContext->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fontContext->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fontContext->fs, state->textAlign); + fonsSetFont(ctx->fontContext->fs, state->fontId); breakRowWidth *= scale; - fonsTextIterInit(ctx->fs, &iter, 0, 0, string, end, FONS_GLYPH_BITMAP_OPTIONAL); + fonsTextIterInit(ctx->fontContext->fs, &iter, 0, 0, string, end, FONS_GLYPH_BITMAP_OPTIONAL); prevIter = iter; - while (fonsTextIterNext(ctx->fs, &iter, &q)) { + while (fonsTextIterNext(ctx->fontContext->fs, &iter, &q)) { if (iter.prevGlyphIndex < 0 && nvg__allocTextAtlas(ctx)) { // can not retrieve glyph? iter = prevIter; - fonsTextIterNext(ctx->fs, &iter, &q); // try again + fonsTextIterNext(ctx->fontContext->fs, &iter, &q); // try again } prevIter = iter; switch (iter.codepoint) { @@ -2699,7 +2797,7 @@ int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, floa } else { if (rowStart == NULL) { // Skip white space until the beginning of the line - if (type == NVG_CHAR || type == NVG_CJK_CHAR) { + if (type == NVG_CHAR || type == NVG_CJK_CHAR || type == NVG_SKIPPED_CHAR) { // The current char is the row so far rowStartX = iter.x; rowStart = iter.str; @@ -2719,7 +2817,7 @@ int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, floa float nextWidth = iter.nextx - rowStartX; // track last non-white space character - if (type == NVG_CHAR || type == NVG_CJK_CHAR) { + if (type == NVG_CHAR || type == NVG_CJK_CHAR || type == NVG_SKIPPED_CHAR) { rowEnd = iter.next; rowWidth = iter.nextx - rowStartX; rowMaxX = q.x1 - rowStartX; @@ -2814,16 +2912,16 @@ float nvgTextBounds(NVGcontext* ctx, float x, float y, const char* string, const if (state->fontId == FONS_INVALID) return 0; - fonsSetSize(ctx->fs, state->fontSize*scale); - fonsSetSpacing(ctx->fs, state->letterSpacing*scale); - fonsSetBlur(ctx->fs, state->fontBlur*scale); - fonsSetAlign(ctx->fs, state->textAlign); - fonsSetFont(ctx->fs, state->fontId); + fonsSetSize(ctx->fontContext->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fontContext->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fontContext->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fontContext->fs, state->textAlign); + fonsSetFont(ctx->fontContext->fs, state->fontId); - width = fonsTextBounds(ctx->fs, x*scale, y*scale, string, end, bounds); + width = fonsTextBounds(ctx->fontContext->fs, x*scale, y*scale, string, end, bounds); if (bounds != NULL) { // Use line bounds for height. - fonsLineBounds(ctx->fs, y*scale, &bounds[1], &bounds[3]); + fonsLineBounds(ctx->fontContext->fs, y*scale, &bounds[1], &bounds[3]); bounds[0] *= invscale; bounds[1] *= invscale; bounds[2] *= invscale; @@ -2858,12 +2956,12 @@ void nvgTextBoxBounds(NVGcontext* ctx, float x, float y, float breakRowWidth, co minx = maxx = x; miny = maxy = y; - fonsSetSize(ctx->fs, state->fontSize*scale); - fonsSetSpacing(ctx->fs, state->letterSpacing*scale); - fonsSetBlur(ctx->fs, state->fontBlur*scale); - fonsSetAlign(ctx->fs, state->textAlign); - fonsSetFont(ctx->fs, state->fontId); - fonsLineBounds(ctx->fs, 0, &rminy, &rmaxy); + fonsSetSize(ctx->fontContext->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fontContext->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fontContext->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fontContext->fs, state->textAlign); + fonsSetFont(ctx->fontContext->fs, state->fontId); + fonsLineBounds(ctx->fontContext->fs, 0, &rminy, &rmaxy); rminy *= invscale; rmaxy *= invscale; @@ -2909,13 +3007,13 @@ void nvgTextMetrics(NVGcontext* ctx, float* ascender, float* descender, float* l if (state->fontId == FONS_INVALID) return; - fonsSetSize(ctx->fs, state->fontSize*scale); - fonsSetSpacing(ctx->fs, state->letterSpacing*scale); - fonsSetBlur(ctx->fs, state->fontBlur*scale); - fonsSetAlign(ctx->fs, state->textAlign); - fonsSetFont(ctx->fs, state->fontId); + fonsSetSize(ctx->fontContext->fs, state->fontSize*scale); + fonsSetSpacing(ctx->fontContext->fs, state->letterSpacing*scale); + fonsSetBlur(ctx->fontContext->fs, state->fontBlur*scale); + fonsSetAlign(ctx->fontContext->fs, state->textAlign); + fonsSetFont(ctx->fontContext->fs, state->fontId); - fonsVertMetrics(ctx->fs, ascender, descender, lineh); + fonsVertMetrics(ctx->fontContext->fs, ascender, descender, lineh); if (ascender != NULL) *ascender *= invscale; if (descender != NULL) diff --git a/dgl/src/nanovg/nanovg.h b/dgl/src/nanovg/nanovg.h @@ -144,6 +144,14 @@ enum NVGimageFlags { NVG_IMAGE_NEAREST = 1<<5, // Image interpolation is Nearest instead Linear }; +enum NVGtexture { + NVG_TEXTURE_ALPHA, + NVG_TEXTURE_BGR, + NVG_TEXTURE_BGRA, + NVG_TEXTURE_RGB, + NVG_TEXTURE_RGBA, +}; + // Begin drawing a new frame // Calls to nanovg drawing API should be wrapped in nvgBeginFrame() & nvgEndFrame() // nvgBeginFrame() defines the size of the window to render to in relation currently @@ -271,6 +279,10 @@ void nvgLineJoin(NVGcontext* ctx, int join); // Sets the transparency applied to all rendered shapes. // Already transparent paths will get proportionally more transparent as well. void nvgGlobalAlpha(NVGcontext* ctx, float alpha); +void nvgGlobalTint(NVGcontext* ctx, NVGcolor tint); +NVGcolor nvgGetGlobalTint(NVGcontext* ctx); +void nvgAlpha(NVGcontext* ctx, float alpha); +void nvgTint(NVGcontext* ctx, NVGcolor tint); // // Transforms @@ -375,6 +387,10 @@ int nvgCreateImage(NVGcontext* ctx, const char* filename, int imageFlags); // Returns handle to the image. int nvgCreateImageMem(NVGcontext* ctx, int imageFlags, unsigned char* data, int ndata); +// Creates image from specified image data and texture format. +// Returns handle to the image. +int nvgCreateImageRaw(NVGcontext* ctx, int w, int h, int imageFlags, enum NVGtexture format, const unsigned char* data); + // Creates image from specified image data. // Returns handle to the image. int nvgCreateImageRGBA(NVGcontext* ctx, int w, int h, int imageFlags, const unsigned char* data); @@ -414,7 +430,7 @@ NVGpaint nvgBoxGradient(NVGcontext* ctx, float x, float y, float w, float h, NVGpaint nvgRadialGradient(NVGcontext* ctx, float cx, float cy, float inr, float outr, NVGcolor icol, NVGcolor ocol); -// Creates and returns an image patter. Parameters (ox,oy) specify the left-top location of the image pattern, +// Creates and returns an image pattern. Parameters (ox,oy) specify the left-top location of the image pattern, // (ex,ey) the size of one image, angle rotation around the top-left corner, image is handle to the image to render. // The gradient is transformed by the current transform when it is passed to nvgFillPaint() or nvgStrokePaint(). NVGpaint nvgImagePattern(NVGcontext* ctx, float ox, float oy, float ex, float ey, @@ -627,11 +643,6 @@ int nvgTextBreakLines(NVGcontext* ctx, const char* string, const char* end, floa // // Internal Render API // -enum NVGtexture { - NVG_TEXTURE_ALPHA = 0x01, - NVG_TEXTURE_RGBA = 0x02, -}; - struct NVGscissor { float xform[6]; float extent[2]; @@ -660,7 +671,7 @@ typedef struct NVGpath NVGpath; struct NVGparams { void* userPtr; int edgeAntiAlias; - int (*renderCreate)(void* uptr); + int (*renderCreate)(void* uptr, void* otherUptr); int (*renderCreateTexture)(void* uptr, int type, int w, int h, int imageFlags, const unsigned char* data); int (*renderDeleteTexture)(void* uptr, int image); int (*renderUpdateTexture)(void* uptr, int image, int x, int y, int w, int h, const unsigned char* data); @@ -676,7 +687,7 @@ struct NVGparams { typedef struct NVGparams NVGparams; // Constructor and destructor, called by the render back-end. -NVGcontext* nvgCreateInternal(NVGparams* params); +NVGcontext* nvgCreateInternal(NVGparams* params, NVGcontext* other); void nvgDeleteInternal(NVGcontext* ctx); NVGparams* nvgInternalParams(NVGcontext* ctx); diff --git a/dgl/src/nanovg/nanovg_gl.h b/dgl/src/nanovg/nanovg_gl.h @@ -18,6 +18,28 @@ #ifndef NANOVG_GL_H #define NANOVG_GL_H +#if defined NANOVG_GL2_FORCED +# undef NANOVG_GL3 +# undef NANOVG_GLES2 +# undef NANOVG_GLES3 +# define NANOVG_GL2 1 +#elif defined NANOVG_GL3_FORCED +# undef NANOVG_GL2 +# undef NANOVG_GLES2 +# undef NANOVG_GLES3 +# define NANOVG_GL3 1 +#elif defined NANOVG_GLES2_FORCED +# undef NANOVG_GL2 +# undef NANOVG_GL3 +# undef NANOVG_GLES3 +# define NANOVG_GLES2 1 +#elif defined NANOVG_GLES3_FORCED +# undef NANOVG_GL2 +# undef NANOVG_GL3 +# undef NANOVG_GLES2 +# define NANOVG_GLES3 1 +#endif + #ifdef __cplusplus extern "C" { #endif @@ -40,9 +62,7 @@ enum NVGcreateFlags { #elif defined NANOVG_GL3_IMPLEMENTATION # define NANOVG_GL3 1 # define NANOVG_GL_IMPLEMENTATION 1 -# ifndef __APPLE__ -# define NANOVG_GL_USE_UNIFORMBUFFER 1 -# endif +# define NANOVG_GL_USE_UNIFORMBUFFER 1 #elif defined NANOVG_GLES2_IMPLEMENTATION # define NANOVG_GLES2 1 # define NANOVG_GL_IMPLEMENTATION 1 @@ -59,6 +79,7 @@ enum NVGcreateFlags { #if defined NANOVG_GL2 NVGcontext* nvgCreateGL2(int flags); +NVGcontext* nvgCreateSharedGL2(NVGcontext* other, int flags); void nvgDeleteGL2(NVGcontext* ctx); int nvglCreateImageFromHandleGL2(NVGcontext* ctx, GLuint textureId, int w, int h, int flags); @@ -69,6 +90,7 @@ GLuint nvglImageHandleGL2(NVGcontext* ctx, int image); #if defined NANOVG_GL3 NVGcontext* nvgCreateGL3(int flags); +NVGcontext* nvgCreateSharedGL3(NVGcontext* other, int flags); void nvgDeleteGL3(NVGcontext* ctx); int nvglCreateImageFromHandleGL3(NVGcontext* ctx, GLuint textureId, int w, int h, int flags); @@ -79,6 +101,7 @@ GLuint nvglImageHandleGL3(NVGcontext* ctx, int image); #if defined NANOVG_GLES2 NVGcontext* nvgCreateGLES2(int flags); +NVGcontext* nvgCreateSharedGLES2(NVGcontext* other, int flags); void nvgDeleteGLES2(NVGcontext* ctx); int nvglCreateImageFromHandleGLES2(NVGcontext* ctx, GLuint textureId, int w, int h, int flags); @@ -89,6 +112,7 @@ GLuint nvglImageHandleGLES2(NVGcontext* ctx, int image); #if defined NANOVG_GLES3 NVGcontext* nvgCreateGLES3(int flags); +NVGcontext* nvgCreateSharedGLES3(NVGcontext* other, int flags); void nvgDeleteGLES3(NVGcontext* ctx); int nvglCreateImageFromHandleGLES3(NVGcontext* ctx, GLuint textureId, int w, int h, int flags); @@ -149,6 +173,9 @@ struct GLNVGtexture { int width, height; int type; int flags; +#if defined NANOVG_GLES2 + unsigned char* data; +#endif }; typedef struct GLNVGtexture GLNVGtexture; @@ -230,13 +257,19 @@ struct GLNVGfragUniforms { }; typedef struct GLNVGfragUniforms GLNVGfragUniforms; -struct GLNVGcontext { - GLNVGshader shader; +struct GLNVGtextureContext { // Textures; shared between shared NanoVG contexts. + int refCount; GLNVGtexture* textures; - float view[2]; int ntextures; int ctextures; int textureId; +}; +typedef struct GLNVGtextureContext GLNVGtextureContext; + +struct GLNVGcontext { + GLNVGshader shader; + GLNVGtextureContext* textureContext; + float view[2]; GLuint vertBuf; #if defined NANOVG_GL3 GLuint vertArr; @@ -352,26 +385,26 @@ static GLNVGtexture* glnvg__allocTexture(GLNVGcontext* gl) GLNVGtexture* tex = NULL; int i; - for (i = 0; i < gl->ntextures; i++) { - if (gl->textures[i].id == 0) { - tex = &gl->textures[i]; + for (i = 0; i < gl->textureContext->ntextures; i++) { + if (gl->textureContext->textures[i].id == 0) { + tex = &gl->textureContext->textures[i]; break; } } if (tex == NULL) { - if (gl->ntextures+1 > gl->ctextures) { + if (gl->textureContext->ntextures+1 > gl->textureContext->ctextures) { GLNVGtexture* textures; - int ctextures = glnvg__maxi(gl->ntextures+1, 4) + gl->ctextures/2; // 1.5x Overallocate - textures = (GLNVGtexture*)realloc(gl->textures, sizeof(GLNVGtexture)*ctextures); + int ctextures = glnvg__maxi(gl->textureContext->ntextures+1, 4) + gl->textureContext->ctextures/2; // 1.5x Overallocate + textures = (GLNVGtexture*)realloc(gl->textureContext->textures, sizeof(GLNVGtexture)*ctextures); if (textures == NULL) return NULL; - gl->textures = textures; - gl->ctextures = ctextures; + gl->textureContext->textures = textures; + gl->textureContext->ctextures = ctextures; } - tex = &gl->textures[gl->ntextures++]; + tex = &gl->textureContext->textures[gl->textureContext->ntextures++]; } memset(tex, 0, sizeof(*tex)); - tex->id = ++gl->textureId; + tex->id = ++gl->textureContext->textureId; return tex; } @@ -379,20 +412,25 @@ static GLNVGtexture* glnvg__allocTexture(GLNVGcontext* gl) static GLNVGtexture* glnvg__findTexture(GLNVGcontext* gl, int id) { int i; - for (i = 0; i < gl->ntextures; i++) - if (gl->textures[i].id == id) - return &gl->textures[i]; + for (i = 0; i < gl->textureContext->ntextures; i++) + if (gl->textureContext->textures[i].id == id) + return &gl->textureContext->textures[i]; return NULL; } static int glnvg__deleteTexture(GLNVGcontext* gl, int id) { int i; - for (i = 0; i < gl->ntextures; i++) { - if (gl->textures[i].id == id) { - if (gl->textures[i].tex != 0 && (gl->textures[i].flags & NVG_IMAGE_NODELETE) == 0) - glDeleteTextures(1, &gl->textures[i].tex); - memset(&gl->textures[i], 0, sizeof(gl->textures[i])); + for (i = 0; i < gl->textureContext->ntextures; i++) { + if (gl->textureContext->textures[i].id == id) { + if (gl->textureContext->textures[i].tex != 0 && (gl->textureContext->textures[i].flags & NVG_IMAGE_NODELETE) == 0) + { + glDeleteTextures(1, &gl->textureContext->textures[i].tex); +#if defined NANOVG_GLES2 + free(gl->textureContext->textures[i].data); +#endif + } + memset(&gl->textureContext->textures[i], 0, sizeof(gl->textureContext->textures[i])); return 1; } } @@ -506,9 +544,20 @@ static void glnvg__getUniforms(GLNVGshader* shader) static int glnvg__renderCreateTexture(void* uptr, int type, int w, int h, int imageFlags, const unsigned char* data); -static int glnvg__renderCreate(void* uptr) +static int glnvg__renderCreate(void* uptr, void* otherUptr) // Share the textures of GLNVGcontext 'otherUptr' if it's non-NULL. { GLNVGcontext* gl = (GLNVGcontext*)uptr; + + if (otherUptr) { + GLNVGcontext* other = (GLNVGcontext*)otherUptr; + gl->textureContext = other->textureContext; + gl->textureContext->refCount++; + } else { + gl->textureContext = (GLNVGtextureContext*)malloc(sizeof(GLNVGtextureContext)); + memset(gl->textureContext, 0, sizeof(GLNVGtextureContext)); + gl->textureContext->refCount = 1; + } + int align = 4; // TODO: mediump float may not be enough for GLES2 in iOS. @@ -734,7 +783,7 @@ static int glnvg__renderCreateTexture(void* uptr, int type, int w, int h, int im } // No mips. if (imageFlags & NVG_IMAGE_GENERATE_MIPMAPS) { - printf("Mip-maps is not support for non power-of-two textures (%d x %d)\n", w, h); + printf("Mip-maps is not supported for non power-of-two textures (%d x %d)\n", w, h); imageFlags &= ~NVG_IMAGE_GENERATE_MIPMAPS; } } @@ -761,9 +810,48 @@ static int glnvg__renderCreateTexture(void* uptr, int type, int w, int h, int im } #endif - if (type == NVG_TEXTURE_RGBA) + switch (type) + { + case NVG_TEXTURE_BGR: +#if NANOVG_GLES2 + // GLES2 cannot handle GL_BGR, do local conversion to GL_RGB + tex->data = (uint8_t*)malloc(sizeof(uint8_t) * 3 * w * h); + for (uint32_t i=0; i<w*h; ++i) + { + tex->data[i*3+0] = data[i*3+2]; + tex->data[i*3+1] = data[i*3+1]; + tex->data[i*3+2] = data[i*3+0]; + } + data = tex->data; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, data); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_BGR, GL_UNSIGNED_BYTE, data); +#endif + break; + case NVG_TEXTURE_BGRA: +#if NANOVG_GLES2 + // GLES2 cannot handle GL_BGRA, do local conversion to GL_RGBA + tex->data = (uint8_t*)malloc(sizeof(uint8_t) * 4 * w * h); + for (uint32_t i=0; i<w*h; ++i) + { + tex->data[i*3+0] = data[i*3+3]; + tex->data[i*3+1] = data[i*3+2]; + tex->data[i*3+2] = data[i*3+1]; + tex->data[i*3+3] = data[i*3+0]; + } + data = tex->data; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, data); +#else + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, data); +#endif + break; + case NVG_TEXTURE_RGB: + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, data); + break; + case NVG_TEXTURE_RGBA: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); - else + break; + default: #if defined(NANOVG_GLES2) || defined (NANOVG_GL2) glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, w, h, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data); #elif defined(NANOVG_GLES3) @@ -771,6 +859,8 @@ static int glnvg__renderCreateTexture(void* uptr, int type, int w, int h, int im #else glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, data); #endif + break; + } if (imageFlags & NVG_IMAGE_GENERATE_MIPMAPS) { if (imageFlags & NVG_IMAGE_NEAREST) { @@ -845,22 +935,50 @@ static int glnvg__renderUpdateTexture(void* uptr, int image, int x, int y, int w glPixelStorei(GL_UNPACK_SKIP_ROWS, y); #else // No support for all of skip, need to update a whole row at a time. - if (tex->type == NVG_TEXTURE_RGBA) + switch (tex->type) + { + case NVG_TEXTURE_BGR: + data += y*tex->width*3; + break; + case NVG_TEXTURE_BGRA: data += y*tex->width*4; - else + break; + case NVG_TEXTURE_RGB: + data += y*tex->width*3; + break; + case NVG_TEXTURE_RGBA: + data += y*tex->width*4; + break; + default: data += y*tex->width; + break; + } x = 0; w = tex->width; #endif - if (tex->type == NVG_TEXTURE_RGBA) + switch (tex->type) + { + case NVG_TEXTURE_BGR: + glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_BGR, GL_UNSIGNED_BYTE, data); + break; + case NVG_TEXTURE_BGRA: + glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_BGRA, GL_UNSIGNED_BYTE, data); + break; + case NVG_TEXTURE_RGB: + glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_RGB, GL_UNSIGNED_BYTE, data); + break; + case NVG_TEXTURE_RGBA: glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_RGBA, GL_UNSIGNED_BYTE, data); - else + break; + default: #if defined(NANOVG_GLES2) || defined(NANOVG_GL2) glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_LUMINANCE, GL_UNSIGNED_BYTE, data); #else glTexSubImage2D(GL_TEXTURE_2D, 0, x,y, w,h, GL_RED, GL_UNSIGNED_BYTE, data); #endif + break; + } glPixelStorei(GL_UNPACK_ALIGNMENT, 4); #ifndef NANOVG_GLES2 @@ -956,15 +1074,31 @@ static int glnvg__convertPaint(GLNVGcontext* gl, GLNVGfragUniforms* frag, NVGpai frag->type = NSVG_SHADER_FILLIMG; #if NANOVG_GL_USE_UNIFORMBUFFER - if (tex->type == NVG_TEXTURE_RGBA) + switch (tex->type) + { + case NVG_TEXTURE_BGR: + case NVG_TEXTURE_BGRA: + case NVG_TEXTURE_RGB: + case NVG_TEXTURE_RGBA: frag->texType = (tex->flags & NVG_IMAGE_PREMULTIPLIED) ? 0 : 1; - else + break; + default: frag->texType = 2; + break; + } #else - if (tex->type == NVG_TEXTURE_RGBA) + switch (tex->type) + { + case NVG_TEXTURE_BGR: + case NVG_TEXTURE_BGRA: + case NVG_TEXTURE_RGB: + case NVG_TEXTURE_RGBA: frag->texType = (tex->flags & NVG_IMAGE_PREMULTIPLIED) ? 0.0f : 1.0f; - else + break; + default: frag->texType = 2.0f; + break; + } #endif // printf("frag->texType = %d\n", frag->texType); } else { @@ -1547,11 +1681,14 @@ static void glnvg__renderDelete(void* uptr) if (gl->vertBuf != 0) glDeleteBuffers(1, &gl->vertBuf); - for (i = 0; i < gl->ntextures; i++) { - if (gl->textures[i].tex != 0 && (gl->textures[i].flags & NVG_IMAGE_NODELETE) == 0) - glDeleteTextures(1, &gl->textures[i].tex); + if (gl->textureContext != NULL && --gl->textureContext->refCount == 0) { + for (i = 0; i < gl->textureContext->ntextures; i++) { + if (gl->textureContext->textures[i].tex != 0 && (gl->textureContext->textures[i].flags & NVG_IMAGE_NODELETE) == 0) + glDeleteTextures(1, &gl->textureContext->textures[i].tex); + } + free(gl->textureContext->textures); + free(gl->textureContext); } - free(gl->textures); free(gl->paths); free(gl->verts); @@ -1572,6 +1709,28 @@ NVGcontext* nvgCreateGLES2(int flags) NVGcontext* nvgCreateGLES3(int flags) #endif { +#if defined NANOVG_GL2 + return nvgCreateSharedGL2(NULL, flags); +#elif defined NANOVG_GL3 + return nvgCreateSharedGL3(NULL, flags); +#elif defined NANOVG_GLES2 + return nvgCreateSharedGLES2(NULL, flags); +#elif defined NANOVG_GLES3 + return nvgCreateSharedGLES3(NULL, flags); +#endif +} + +// Share the fonts and textures of 'other' if it's non-NULL. +#if defined NANOVG_GL2 +NVGcontext* nvgCreateSharedGL2(NVGcontext* other, int flags) +#elif defined NANOVG_GL3 +NVGcontext* nvgCreateSharedGL3(NVGcontext* other, int flags) +#elif defined NANOVG_GLES2 +NVGcontext* nvgCreateSharedGLES2(NVGcontext* other, int flags) +#elif defined NANOVG_GLES3 +NVGcontext* nvgCreateSharedGLES3(NVGcontext* other, int flags) +#endif +{ NVGparams params; NVGcontext* ctx = NULL; GLNVGcontext* gl = (GLNVGcontext*)malloc(sizeof(GLNVGcontext)); @@ -1596,7 +1755,7 @@ NVGcontext* nvgCreateGLES3(int flags) gl->flags = flags; - ctx = nvgCreateInternal(&params); + ctx = nvgCreateInternal(&params, other); if (ctx == NULL) goto error; return ctx; diff --git a/dgl/src/nanovg/stb_image.h b/dgl/src/nanovg/stb_image.h @@ -1,5 +1,5 @@ -/* stb_image - v2.25 - public domain image loader - http://nothings.org/stb - no warranty implied; use at your own risk +/* stb_image - v2.10 - public domain image loader - http://nothings.org/stb_image.h + no warranty implied; use at your own risk Do this: #define STB_IMAGE_IMPLEMENTATION @@ -21,7 +21,7 @@ avoid problematic images and only need the trivial interface JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) - PNG 1/2/4/8/16-bit-per-channel + PNG 1/2/4/8-bit-per-channel (16 bpc not supported) TGA (not sure what subset, if a subset) BMP non-1bpp, non-RLE @@ -42,32 +42,135 @@ Full documentation under "DOCUMENTATION" below. -LICENSE - - See end of file for license information. - -RECENT REVISION HISTORY: - - 2.25 (2020-02-02) fix warnings - 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically - 2.23 (2019-08-11) fix clang static analysis warning - 2.22 (2019-03-04) gif fixes, fix warnings - 2.21 (2019-02-25) fix typo in comment - 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs - 2.19 (2018-02-11) fix warning - 2.18 (2018-01-30) fix warnings - 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings - 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes - 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC - 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs - 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes - 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes - 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 - RGB-format JPEG; remove white matting in PSD; - allocate large structures on the stack; - correct channel count for PNG & BMP + Revision 2.00 release notes: + + - Progressive JPEG is now supported. + + - PPM and PGM binary formats are now supported, thanks to Ken Miller. + + - x86 platforms now make use of SSE2 SIMD instructions for + JPEG decoding, and ARM platforms can use NEON SIMD if requested. + This work was done by Fabian "ryg" Giesen. SSE2 is used by + default, but NEON must be enabled explicitly; see docs. + + With other JPEG optimizations included in this version, we see + 2x speedup on a JPEG on an x86 machine, and a 1.5x speedup + on a JPEG on an ARM machine, relative to previous versions of this + library. The same results will not obtain for all JPGs and for all + x86/ARM machines. (Note that progressive JPEGs are significantly + slower to decode than regular JPEGs.) This doesn't mean that this + is the fastest JPEG decoder in the land; rather, it brings it + closer to parity with standard libraries. If you want the fastest + decode, look elsewhere. (See "Philosophy" section of docs below.) + + See final bullet items below for more info on SIMD. + + - Added STBI_MALLOC, STBI_REALLOC, and STBI_FREE macros for replacing + the memory allocator. Unlike other STBI libraries, these macros don't + support a context parameter, so if you need to pass a context in to + the allocator, you'll have to store it in a global or a thread-local + variable. + + - Split existing STBI_NO_HDR flag into two flags, STBI_NO_HDR and + STBI_NO_LINEAR. + STBI_NO_HDR: suppress implementation of .hdr reader format + STBI_NO_LINEAR: suppress high-dynamic-range light-linear float API + + - You can suppress implementation of any of the decoders to reduce + your code footprint by #defining one or more of the following + symbols before creating the implementation. + + STBI_NO_JPEG + STBI_NO_PNG + STBI_NO_BMP + STBI_NO_PSD + STBI_NO_TGA + STBI_NO_GIF + STBI_NO_HDR + STBI_NO_PIC + STBI_NO_PNM (.ppm and .pgm) + + - You can request *only* certain decoders and suppress all other ones + (this will be more forward-compatible, as addition of new decoders + doesn't require you to disable them explicitly): + + STBI_ONLY_JPEG + STBI_ONLY_PNG + STBI_ONLY_BMP + STBI_ONLY_PSD + STBI_ONLY_TGA + STBI_ONLY_GIF + STBI_ONLY_HDR + STBI_ONLY_PIC + STBI_ONLY_PNM (.ppm and .pgm) + + Note that you can define multiples of these, and you will get all + of them ("only x" and "only y" is interpreted to mean "only x&y"). + + - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still + want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB + + - Compilation of all SIMD code can be suppressed with + #define STBI_NO_SIMD + It should not be necessary to disable SIMD unless you have issues + compiling (e.g. using an x86 compiler which doesn't support SSE + intrinsics or that doesn't support the method used to detect + SSE2 support at run-time), and even those can be reported as + bugs so I can refine the built-in compile-time checking to be + smarter. + + - The old STBI_SIMD system which allowed installing a user-defined + IDCT etc. has been removed. If you need this, don't upgrade. My + assumption is that almost nobody was doing this, and those who + were will find the built-in SIMD more satisfactory anyway. + + - RGB values computed for JPEG images are slightly different from + previous versions of stb_image. (This is due to using less + integer precision in SIMD.) The C code has been adjusted so + that the same RGB values will be computed regardless of whether + SIMD support is available, so your app should always produce + consistent results. But these results are slightly different from + previous versions. (Specifically, about 3% of available YCbCr values + will compute different RGB results from pre-1.49 versions by +-1; + most of the deviating values are one smaller in the G channel.) + + - If you must produce consistent results with previous versions of + stb_image, #define STBI_JPEG_OLD and you will get the same results + you used to; however, you will not get the SIMD speedups for + the YCbCr-to-RGB conversion step (although you should still see + significant JPEG speedup from the other changes). + + Please note that STBI_JPEG_OLD is a temporary feature; it will be + removed in future versions of the library. It is only intended for + near-term back-compatibility use. + + + Latest revision history: 2.10 (2016-01-22) avoid warning introduced in 2.09 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) partial animated GIF support + limited 16-bit PSD support + minor bugs, code cleanup, and compiler warnings + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) additional corruption checking + stbi_set_flip_vertically_on_load + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPEG, including x86 SSE2 & ARM NEON SIMD + progressive JPEG + PGM/PPM support + STBI_MALLOC,STBI_REALLOC,STBI_FREE + STBI_NO_*, STBI_ONLY_* + GIF bugfix + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support (both grayscale and paletted) + optimize PNG + fix bug in interlaced PNG with user-specified channel count See end of file for full revision history. @@ -82,33 +185,34 @@ RECENT REVISION HISTORY: Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) - github:urraka (animated gif) Junggon Kim (PNM comments) - Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) - socks-the-fox (16-bit PNG) - Jeremy Sawicki (handle all ImageNet JPGs) - Optimizations & bugfixes Mikhail Morozov (1-bit BMP) - Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) + urraka@github (animated gif) Junggon Kim (PNM comments) + Daniel Gibson (16-bit TGA) + + Optimizations & bugfixes + Fabian "ryg" Giesen Arseny Kapoulkine - John-Mark Allen - Carmelo J Fdez-Aguera Bug & warning fixes Marc LeBlanc David Woo Guillaume George Martins Mozeiko - Christpher Lloyd Jerry Jansson Joseph Thomson Phil Jordan - Dave Moore Roy Eltham Hayaki Saito Nathan Reed - Won Chun Luke Graham Johan Duparc Nick Verigakis - the Horde3D community Thomas Ruf Ronny Chevalier github:rlyeh - Janez Zemva John Bartholomew Michal Cichon github:romigrou - Jonathan Blow Ken Hamada Tero Hanninen github:svdijk - Laurent Gomila Cort Stratton Sergio Gonzalez github:snagar - Aruelien Pocheville Thibault Reuille Cass Everitt github:Zelex - Ryamond Barbiero Paul Du Bois Engin Manap github:grim210 - Aldo Culquicondor Philipp Wiesemann Dale Weiler github:sammyhw - Oriol Ferrer Mesia Josh Tobin Matthew Gregan github:phprus - Julian Raschke Gregory Mullen Baldur Karlsson github:poppolopoppo - Christian Floisand Kevin Schmidt JR Smith github:darealshinji - Brad Weinberger Matvey Cherevko github:Michaelangel007 - Blazej Dariusz Roszkowski Alexander Veselov + Christpher Lloyd Martin Golini Jerry Jansson Joseph Thomson + Dave Moore Roy Eltham Hayaki Saito Phil Jordan + Won Chun Luke Graham Johan Duparc Nathan Reed + the Horde3D community Thomas Ruf Ronny Chevalier Nick Verigakis + Janez Zemva John Bartholomew Michal Cichon svdijk@github + Jonathan Blow Ken Hamada Tero Hanninen Baldur Karlsson + Laurent Gomila Cort Stratton Sergio Gonzalez romigrou@github + Aruelien Pocheville Thibault Reuille Cass Everitt + Ryamond Barbiero Paul Du Bois Engin Manap + Blazej Dariusz Roszkowski + Michaelangel007@github + + +LICENSE + +This software is in the public domain. Where that dedication is not +recognized, you are granted a perpetual, irrevocable license to copy, +distribute, and modify this file as you see fit. + */ #ifndef STBI_INCLUDE_STB_IMAGE_H @@ -117,8 +221,10 @@ RECENT REVISION HISTORY: // DOCUMENTATION // // Limitations: +// - no 16-bit-per-channel PNG // - no 12-bit-per-channel JPEG // - no JPEGs with arithmetic coding +// - no 1-bit BMP // - GIF always returns *comp=4 // // Basic usage (see HDR discussion below for HDR usage): @@ -131,10 +237,10 @@ RECENT REVISION HISTORY: // stbi_image_free(data) // // Standard parameters: -// int *x -- outputs image width in pixels -// int *y -- outputs image height in pixels -// int *channels_in_file -- outputs # of image components in image file -// int desired_channels -- if non-zero, # of image components requested in result +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *comp -- outputs # of image components in image file +// int req_comp -- if non-zero, # of image components requested in result // // The return value from an image loader is an 'unsigned char *' which points // to the pixel data, or NULL on an allocation failure or if the image is @@ -142,12 +248,11 @@ RECENT REVISION HISTORY: // with each pixel consisting of N interleaved 8-bit components; the first // pixel pointed to is top-left-most in the image. There is no padding between // image scanlines or between pixels, regardless of format. The number of -// components N is 'desired_channels' if desired_channels is non-zero, or -// *channels_in_file otherwise. If desired_channels is non-zero, -// *channels_in_file has the number of components that _would_ have been -// output otherwise. E.g. if you set desired_channels to 4, you will always -// get RGBA output, but you can check *channels_in_file to see if it's trivially -// opaque because e.g. there were only 3 channels in the source image. +// components N is 'req_comp' if req_comp is non-zero, or *comp otherwise. +// If req_comp is non-zero, *comp has the number of components that _would_ +// have been output otherwise. E.g. if you set req_comp to 4, you will always +// get RGBA output, but you can check *comp to see if it's trivially opaque +// because e.g. there were only 3 channels in the source image. // // An output image with N components has the following components interleaved // in this order in each pixel: @@ -159,26 +264,16 @@ RECENT REVISION HISTORY: // 4 red, green, blue, alpha // // If image loading fails for any reason, the return value will be NULL, -// and *x, *y, *channels_in_file will be unchanged. The function -// stbi_failure_reason() can be queried for an extremely brief, end-user -// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS -// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// and *x, *y, *comp will be unchanged. The function stbi_failure_reason() +// can be queried for an extremely brief, end-user unfriendly explanation +// of why the load failed. Define STBI_NO_FAILURE_STRINGS to avoid +// compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly // more user-friendly ones. // // Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. // // =========================================================================== // -// UNICODE: -// -// If compiling for Windows and you wish to use Unicode filenames, compile -// with -// #define STBI_WINDOWS_UTF8 -// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert -// Windows wchar_t filenames to utf8. -// -// =========================================================================== -// // Philosophy // // stb libraries are designed with the following priorities: @@ -189,15 +284,15 @@ RECENT REVISION HISTORY: // // Sometimes I let "good performance" creep up in priority over "easy to maintain", // and for best performance I may provide less-easy-to-use APIs that give higher -// performance, in addition to the easy-to-use ones. Nevertheless, it's important +// performance, in addition to the easy to use ones. Nevertheless, it's important // to keep in mind that from the standpoint of you, a client of this library, -// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. +// all you care about is #1 and #3, and stb libraries do not emphasize #3 above all. // // Some secondary priorities arise directly from the first two, some of which -// provide more explicit reasons why performance can't be emphasized. +// make more explicit reasons why performance can't be emphasized. // // - Portable ("ease of use") -// - Small source code footprint ("easy to maintain") +// - Small footprint ("easy to maintain") // - No dependencies ("ease of use") // // =========================================================================== @@ -229,6 +324,13 @@ RECENT REVISION HISTORY: // (at least this is true for iOS and Android). Therefore, the NEON support is // toggled by a build flag: define STBI_NEON to get NEON loops. // +// The output of the JPEG decoder is slightly different from versions where +// SIMD support was introduced (that is, for versions before 1.49). The +// difference is only +-1 in the 8-bit RGB channels, and only on a small +// fraction of pixels. You can force the pre-1.49 behavior by defining +// STBI_JPEG_OLD, but this will disable some of the SIMD decoding path +// and hence cost some performance. +// // If for some reason you do not want to use any of SIMD code, or if // you have issues compiling it, you can disable it entirely by // defining STBI_NO_SIMD. @@ -237,10 +339,11 @@ RECENT REVISION HISTORY: // // HDR image support (disable by defining STBI_NO_HDR) // -// stb_image supports loading HDR images in general, and currently the Radiance -// .HDR file format specifically. You can still load any file through the existing -// interface; if you attempt to load an HDR file, it will be automatically remapped -// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// stb_image now supports loading HDR images in general, and currently +// the Radiance .HDR file format, although the support is provided +// generically. You can still load any file through the existing interface; +// if you attempt to load an HDR file, it will be automatically remapped to +// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; // both of these constants can be reconfigured through this interface: // // stbi_hdr_to_ldr_gamma(2.2f); @@ -274,7 +377,7 @@ RECENT REVISION HISTORY: // // By default we convert iphone-formatted PNGs back to RGB, even though // they are internally encoded differently. You can disable this conversion -// by calling stbi_convert_iphone_png_to_rgb(0), in which case +// by by calling stbi_convert_iphone_png_to_rgb(0), in which case // you will always just get the native iphone "format" through (which // is BGR stored in RGB). // @@ -283,41 +386,6 @@ RECENT REVISION HISTORY: // says there's premultiplied data (currently only happens in iPhone images, // and only if iPhone convert-to-rgb processing is on). // -// =========================================================================== -// -// ADDITIONAL CONFIGURATION -// -// - You can suppress implementation of any of the decoders to reduce -// your code footprint by #defining one or more of the following -// symbols before creating the implementation. -// -// STBI_NO_JPEG -// STBI_NO_PNG -// STBI_NO_BMP -// STBI_NO_PSD -// STBI_NO_TGA -// STBI_NO_GIF -// STBI_NO_HDR -// STBI_NO_PIC -// STBI_NO_PNM (.ppm and .pgm) -// -// - You can request *only* certain decoders and suppress all other ones -// (this will be more forward-compatible, as addition of new decoders -// doesn't require you to disable them explicitly): -// -// STBI_ONLY_JPEG -// STBI_ONLY_PNG -// STBI_ONLY_BMP -// STBI_ONLY_PSD -// STBI_ONLY_TGA -// STBI_ONLY_GIF -// STBI_ONLY_HDR -// STBI_ONLY_PIC -// STBI_ONLY_PNM (.ppm and .pgm) -// -// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still -// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB -// #ifndef STBI_NO_STDIO @@ -328,7 +396,7 @@ RECENT REVISION HISTORY: enum { - STBI_default = 0, // only used for desired_channels + STBI_default = 0, // only used for req_comp STBI_grey = 1, STBI_grey_alpha = 2, @@ -336,21 +404,17 @@ enum STBI_rgb_alpha = 4 }; -#include <stdlib.h> typedef unsigned char stbi_uc; -typedef unsigned short stbi_us; #ifdef __cplusplus extern "C" { #endif -#ifndef STBIDEF #ifdef STB_IMAGE_STATIC #define STBIDEF static #else #define STBIDEF extern #endif -#endif ////////////////////////////////////////////////////////////////////////////// // @@ -368,52 +432,22 @@ typedef struct int (*eof) (void *user); // returns nonzero if we are at end of file/data } stbi_io_callbacks; -//////////////////////////////////// -// -// 8-bits-per-channel interface -// - -STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp); +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *comp, int req_comp); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *comp, int req_comp); #ifndef STBI_NO_STDIO -STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); // for stbi_load_from_file, file pointer is left pointing immediately after image #endif -#ifndef STBI_NO_GIF -STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); -#endif - -#ifdef STBI_WINDOWS_UTF8 -STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); -#endif - -//////////////////////////////////// -// -// 16-bits-per-channel interface -// - -STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); - -#ifndef STBI_NO_STDIO -STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); -STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); -#endif - -//////////////////////////////////// -// -// float-per-channel interface -// #ifndef STBI_NO_LINEAR - STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); - STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *comp, int req_comp); + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); #ifndef STBI_NO_STDIO - STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); - STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); #endif #endif @@ -437,7 +471,7 @@ STBIDEF int stbi_is_hdr_from_file(FILE *f); // get a VERY brief reason for failure -// on most compilers (and ALL modern mainstream compilers) this is threadsafe +// NOT THREADSAFE STBIDEF const char *stbi_failure_reason (void); // free the loaded image -- this is just free() @@ -446,14 +480,11 @@ STBIDEF void stbi_image_free (void *retval_from_stbi_load); // get image dimensions & components without fully decoding STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); -STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); -STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); #ifndef STBI_NO_STDIO -STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); -STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); -STBIDEF int stbi_is_16_bit (char const *filename); -STBIDEF int stbi_is_16_bit_from_file(FILE *f); +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); + #endif @@ -470,11 +501,6 @@ STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); // flip the image vertically, so the first pixel in the output array is the bottom left STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); -// as above, but only applies to images loaded on the thread that calls the function -// this function is only available if your compiler supports thread-local variables; -// calling it will fail to link if your compiler doesn't -STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); - // ZLIB client - used by PNG, available for other purposes STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); @@ -539,10 +565,9 @@ STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const ch #include <stddef.h> // ptrdiff_t on osx #include <stdlib.h> #include <string.h> -#include <limits.h> #if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) -#include <math.h> // ldexp, pow +#include <math.h> // ldexp #endif #ifndef STBI_NO_STDIO @@ -554,12 +579,6 @@ STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const ch #define STBI_ASSERT(x) assert(x) #endif -#ifdef __cplusplus -#define STBI_EXTERN extern "C" -#else -#define STBI_EXTERN extern -#endif - #ifndef _MSC_VER #ifdef __cplusplus @@ -571,17 +590,6 @@ STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const ch #define stbi_inline __forceinline #endif -#ifndef STBI_NO_THREAD_LOCALS - #if defined(__cplusplus) && __cplusplus >= 201103L - #define STBI_THREAD_LOCAL thread_local - #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L - #define STBI_THREAD_LOCAL _Thread_local - #elif defined(__GNUC__) - #define STBI_THREAD_LOCAL __thread - #elif defined(_MSC_VER) - #define STBI_THREAD_LOCAL __declspec(thread) -#endif -#endif #ifdef _MSC_VER typedef unsigned short stbi__uint16; @@ -640,14 +648,12 @@ typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; #define STBI__X86_TARGET #endif -#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +#if defined(__GNUC__) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// NOTE: not clear do we actually need this for the 64-bit path? // gcc doesn't support sse2 intrinsics unless you compile with -msse2, -// which in turn means it gets to use SSE2 everywhere. This is unfortunate, -// but previous attempts to provide the SSE2 functions with runtime -// detection caused numerous issues. The way architecture extensions are -// exposed in GCC/Clang is, sadly, not really suited for one-file libs. -// New behavior: if compiled with -msse2, we use SSE2 without any -// detection; if not, we don't use it at all. +// (but compiling with -msse2 allows the compiler to use SSE2 everywhere; +// this is just broken and gcc are jerks for not fixing it properly +// http://www.virtualdub.org/blog/pivot/entry.php?id=363 ) #define STBI_NO_SIMD #endif @@ -666,7 +672,7 @@ typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; #define STBI_NO_SIMD #endif -#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#if !defined(STBI_NO_SIMD) && defined(STBI__X86_TARGET) #define STBI_SSE2 #include <emmintrin.h> @@ -695,27 +701,25 @@ static int stbi__cpuid3(void) #define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name -#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) -static int stbi__sse2_available(void) +static int stbi__sse2_available() { int info3 = stbi__cpuid3(); return ((info3 >> 26) & 1) != 0; } -#endif - #else // assume GCC-style if not VC++ #define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) -#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) -static int stbi__sse2_available(void) +static int stbi__sse2_available() { - // If we're even attempting to compile this on GCC/Clang, that means - // -msse2 is on, which means the compiler is allowed to use SSE2 - // instructions at will, and so are we. - return 1; -} +#if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 408 // GCC 4.8 or later + // GCC 4.8+ has a nice way to do this + return __builtin_cpu_supports("sse2"); +#else + // portable way to do this, preferably without using GCC inline ASM? + // just bail for now. + return 0; #endif - +} #endif #endif @@ -822,179 +826,79 @@ static void stbi__rewind(stbi__context *s) s->img_buffer_end = s->img_buffer_original_end; } -enum -{ - STBI_ORDER_RGB, - STBI_ORDER_BGR -}; - -typedef struct -{ - int bits_per_channel; - int num_channels; - int channel_order; -} stbi__result_info; - #ifndef STBI_NO_JPEG static int stbi__jpeg_test(stbi__context *s); -static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static stbi_uc *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); #endif #ifndef STBI_NO_PNG static int stbi__png_test(stbi__context *s); -static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static stbi_uc *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); -static int stbi__png_is16(stbi__context *s); #endif #ifndef STBI_NO_BMP static int stbi__bmp_test(stbi__context *s); -static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); #endif #ifndef STBI_NO_TGA static int stbi__tga_test(stbi__context *s); -static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); #endif #ifndef STBI_NO_PSD static int stbi__psd_test(stbi__context *s); -static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); -static int stbi__psd_is16(stbi__context *s); #endif #ifndef STBI_NO_HDR static int stbi__hdr_test(stbi__context *s); -static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); #endif #ifndef STBI_NO_PIC static int stbi__pic_test(stbi__context *s); -static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static stbi_uc *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); #endif #ifndef STBI_NO_GIF static int stbi__gif_test(stbi__context *s); -static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); -static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); #endif #ifndef STBI_NO_PNM static int stbi__pnm_test(stbi__context *s); -static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static stbi_uc *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); #endif -static -#ifdef STBI_THREAD_LOCAL -STBI_THREAD_LOCAL -#endif -const char *stbi__g_failure_reason; +// this is not threadsafe +static const char *stbi__g_failure_reason; STBIDEF const char *stbi_failure_reason(void) { return stbi__g_failure_reason; } -#ifndef STBI_NO_FAILURE_STRINGS static int stbi__err(const char *str) { stbi__g_failure_reason = str; return 0; } -#endif static void *stbi__malloc(size_t size) { return STBI_MALLOC(size); } -// stb_image uses ints pervasively, including for offset calculations. -// therefore the largest decoded image size we can support with the -// current code, even on 64-bit targets, is INT_MAX. this is not a -// significant limitation for the intended use case. -// -// we do, however, need to make sure our size calculations don't -// overflow. hence a few helper functions for size calculations that -// multiply integers together, making sure that they're non-negative -// and no overflow occurs. - -// return 1 if the sum is valid, 0 on overflow. -// negative terms are considered invalid. -static int stbi__addsizes_valid(int a, int b) -{ - if (b < 0) return 0; - // now 0 <= b <= INT_MAX, hence also - // 0 <= INT_MAX - b <= INTMAX. - // And "a + b <= INT_MAX" (which might overflow) is the - // same as a <= INT_MAX - b (no overflow) - return a <= INT_MAX - b; -} - -// returns 1 if the product is valid, 0 on overflow. -// negative factors are considered invalid. -static int stbi__mul2sizes_valid(int a, int b) -{ - if (a < 0 || b < 0) return 0; - if (b == 0) return 1; // mul-by-0 is always safe - // portable way to check for no overflows in a*b - return a <= INT_MAX/b; -} - -#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) -// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow -static int stbi__mad2sizes_valid(int a, int b, int add) -{ - return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); -} -#endif - -// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow -static int stbi__mad3sizes_valid(int a, int b, int c, int add) -{ - return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && - stbi__addsizes_valid(a*b*c, add); -} - -// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow -#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) -static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) -{ - return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && - stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); -} -#endif - -#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) -// mallocs with size overflow checking -static void *stbi__malloc_mad2(int a, int b, int add) -{ - if (!stbi__mad2sizes_valid(a, b, add)) return NULL; - return stbi__malloc(a*b + add); -} -#endif - -static void *stbi__malloc_mad3(int a, int b, int c, int add) -{ - if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; - return stbi__malloc(a*b*c + add); -} - -#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) -static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) -{ - if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; - return stbi__malloc(a*b*c*d + add); -} -#endif - // stbi__err - error // stbi__errpf - error returning pointer to float // stbi__errpuc - error returning pointer to unsigned char @@ -1023,63 +927,40 @@ static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); #endif -static int stbi__vertically_flip_on_load_global = 0; +static int stbi__vertically_flip_on_load = 0; STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) { - stbi__vertically_flip_on_load_global = flag_true_if_should_flip; + stbi__vertically_flip_on_load = flag_true_if_should_flip; } -#ifndef STBI_THREAD_LOCAL -#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global -#else -static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set; - -STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip) +static unsigned char *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) { - stbi__vertically_flip_on_load_local = flag_true_if_should_flip; - stbi__vertically_flip_on_load_set = 1; -} - -#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \ - ? stbi__vertically_flip_on_load_local \ - : stbi__vertically_flip_on_load_global) -#endif // STBI_THREAD_LOCAL - -static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) -{ - memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields - ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed - ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order - ri->num_channels = 0; - #ifndef STBI_NO_JPEG - if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp); #endif #ifndef STBI_NO_PNG - if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp); #endif #ifndef STBI_NO_BMP - if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp); #endif #ifndef STBI_NO_GIF - if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp); #endif #ifndef STBI_NO_PSD - if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); - #else - STBI_NOTUSED(bpc); + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp); #endif #ifndef STBI_NO_PIC - if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp); #endif #ifndef STBI_NO_PNM - if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp); #endif #ifndef STBI_NO_HDR if (stbi__hdr_test(s)) { - float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp); return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); } #endif @@ -1087,175 +968,66 @@ static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int re #ifndef STBI_NO_TGA // test tga last because it's a crappy test! if (stbi__tga_test(s)) - return stbi__tga_load(s,x,y,comp,req_comp, ri); + return stbi__tga_load(s,x,y,comp,req_comp); #endif return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); } -static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +static unsigned char *stbi__load_flip(stbi__context *s, int *x, int *y, int *comp, int req_comp) { - int i; - int img_len = w * h * channels; - stbi_uc *reduced; - - reduced = (stbi_uc *) stbi__malloc(img_len); - if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); - - for (i = 0; i < img_len; ++i) - reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling - - STBI_FREE(orig); - return reduced; -} + unsigned char *result = stbi__load_main(s, x, y, comp, req_comp); -static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) -{ - int i; - int img_len = w * h * channels; - stbi__uint16 *enlarged; - - enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); - if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); - - for (i = 0; i < img_len; ++i) - enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff - - STBI_FREE(orig); - return enlarged; -} - -static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) -{ - int row; - size_t bytes_per_row = (size_t)w * bytes_per_pixel; - stbi_uc temp[2048]; - stbi_uc *bytes = (stbi_uc *)image; - - for (row = 0; row < (h>>1); row++) { - stbi_uc *row0 = bytes + row*bytes_per_row; - stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; - // swap row0 with row1 - size_t bytes_left = bytes_per_row; - while (bytes_left) { - size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); - memcpy(temp, row0, bytes_copy); - memcpy(row0, row1, bytes_copy); - memcpy(row1, temp, bytes_copy); - row0 += bytes_copy; - row1 += bytes_copy; - bytes_left -= bytes_copy; + if (stbi__vertically_flip_on_load && result != NULL) { + int w = *x, h = *y; + int depth = req_comp ? req_comp : *comp; + int row,col,z; + stbi_uc temp; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < depth; z++) { + temp = result[(row * w + col) * depth + z]; + result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; + result[((h - row - 1) * w + col) * depth + z] = temp; + } + } } } -} - -#ifndef STBI_NO_GIF -static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) -{ - int slice; - int slice_size = w * h * bytes_per_pixel; - - stbi_uc *bytes = (stbi_uc *)image; - for (slice = 0; slice < z; ++slice) { - stbi__vertical_flip(bytes, w, h, bytes_per_pixel); - bytes += slice_size; - } -} -#endif - -static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi__result_info ri; - void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); - - if (result == NULL) - return NULL; - if (ri.bits_per_channel != 8) { - STBI_ASSERT(ri.bits_per_channel == 16); - result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); - ri.bits_per_channel = 8; - } - - // @TODO: move stbi__convert_format to here - - if (stbi__vertically_flip_on_load) { - int channels = req_comp ? req_comp : *comp; - stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); - } - - return (unsigned char *) result; -} - -static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) -{ - stbi__result_info ri; - void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); - - if (result == NULL) - return NULL; - - if (ri.bits_per_channel != 16) { - STBI_ASSERT(ri.bits_per_channel == 8); - result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); - ri.bits_per_channel = 16; - } - - // @TODO: move stbi__convert_format16 to here - // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision - - if (stbi__vertically_flip_on_load) { - int channels = req_comp ? req_comp : *comp; - stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); - } - - return (stbi__uint16 *) result; + return result; } -#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) +#ifndef STBI_NO_HDR static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) { if (stbi__vertically_flip_on_load && result != NULL) { - int channels = req_comp ? req_comp : *comp; - stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); + int w = *x, h = *y; + int depth = req_comp ? req_comp : *comp; + int row,col,z; + float temp; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < depth; z++) { + temp = result[(row * w + col) * depth + z]; + result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; + result[((h - row - 1) * w + col) * depth + z] = temp; + } + } + } } } #endif #ifndef STBI_NO_STDIO -#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) -STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); -STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); -#endif - -#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) -STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) -{ - return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); -} -#endif - static FILE *stbi__fopen(char const *filename, char const *mode) { FILE *f; -#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) - wchar_t wMode[64]; - wchar_t wFilename[1024]; - if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename))) - return 0; - - if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode))) - return 0; - -#if _MSC_VER >= 1400 - if (0 != _wfopen_s(&f, wFilename, wMode)) - f = 0; -#else - f = _wfopen(wFilename, wMode); -#endif - -#elif defined(_MSC_VER) && _MSC_VER >= 1400 +#if defined(_MSC_VER) && _MSC_VER >= 1400 if (0 != fopen_s(&f, filename, mode)) f=0; #else @@ -1280,83 +1052,28 @@ STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req unsigned char *result; stbi__context s; stbi__start_file(&s,f); - result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + result = stbi__load_flip(&s,x,y,comp,req_comp); if (result) { // need to 'unget' all the characters in the IO buffer fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); } return result; } - -STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) -{ - stbi__uint16 *result; - stbi__context s; - stbi__start_file(&s,f); - result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); - if (result) { - // need to 'unget' all the characters in the IO buffer - fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); - } - return result; -} - -STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) -{ - FILE *f = stbi__fopen(filename, "rb"); - stbi__uint16 *result; - if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); - result = stbi_load_from_file_16(f,x,y,comp,req_comp); - fclose(f); - return result; -} - - #endif //!STBI_NO_STDIO -STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); -} - -STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); - return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); -} - STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) { stbi__context s; stbi__start_mem(&s,buffer,len); - return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + return stbi__load_flip(&s,x,y,comp,req_comp); } STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) { stbi__context s; stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); -} - -#ifndef STBI_NO_GIF -STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) -{ - unsigned char *result; - stbi__context s; - stbi__start_mem(&s,buffer,len); - - result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); - if (stbi__vertically_flip_on_load) { - stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); - } - - return result; + return stbi__load_flip(&s,x,y,comp,req_comp); } -#endif #ifndef STBI_NO_LINEAR static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) @@ -1364,14 +1081,13 @@ static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int unsigned char *data; #ifndef STBI_NO_HDR if (stbi__hdr_test(s)) { - stbi__result_info ri; - float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp); if (hdr_data) stbi__float_postprocess(hdr_data,x,y,comp,req_comp); return hdr_data; } #endif - data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + data = stbi__load_flip(s, x, y, comp, req_comp); if (data) return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); @@ -1441,16 +1157,12 @@ STBIDEF int stbi_is_hdr (char const *filename) return result; } -STBIDEF int stbi_is_hdr_from_file(FILE *f) +STBIDEF int stbi_is_hdr_from_file(FILE *f) { #ifndef STBI_NO_HDR - long pos = ftell(f); - int res; stbi__context s; stbi__start_file(&s,f); - res = stbi__hdr_test(&s); - fseek(f, pos, SEEK_SET); - return res; + return stbi__hdr_test(&s); #else STBI_NOTUSED(f); return 0; @@ -1523,9 +1235,6 @@ stbi_inline static stbi_uc stbi__get8(stbi__context *s) return 0; } -#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) -// nothing -#else stbi_inline static int stbi__at_eof(stbi__context *s) { if (s->io.read) { @@ -1537,11 +1246,7 @@ stbi_inline static int stbi__at_eof(stbi__context *s) return s->img_buffer >= s->img_buffer_end; } -#endif -#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) -// nothing -#else static void stbi__skip(stbi__context *s, int n) { if (n < 0) { @@ -1558,11 +1263,7 @@ static void stbi__skip(stbi__context *s, int n) } s->img_buffer += n; } -#endif -#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM) -// nothing -#else static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) { if (s->io.read) { @@ -1586,27 +1287,18 @@ static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) } else return 0; } -#endif -#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) -// nothing -#else static int stbi__get16be(stbi__context *s) { int z = stbi__get8(s); return (z << 8) + stbi__get8(s); } -#endif -#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) -// nothing -#else static stbi__uint32 stbi__get32be(stbi__context *s) { stbi__uint32 z = stbi__get16be(s); return (z << 16) + stbi__get16be(s); } -#endif #if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) // nothing @@ -1628,9 +1320,7 @@ static stbi__uint32 stbi__get32le(stbi__context *s) #define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings -#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) -// nothing -#else + ////////////////////////////////////////////////////////////////////////////// // // generic converter from built-in img_n to req_comp @@ -1646,11 +1336,7 @@ static stbi_uc stbi__compute_y(int r, int g, int b) { return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); } -#endif -#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) -// nothing -#else static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) { int i,j; @@ -1659,7 +1345,7 @@ static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int r if (req_comp == img_n) return data; STBI_ASSERT(req_comp >= 1 && req_comp <= 4); - good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + good = (unsigned char *) stbi__malloc(req_comp * x * y); if (good == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); @@ -1669,97 +1355,37 @@ static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int r unsigned char *src = data + j * x * img_n ; unsigned char *dest = good + j * x * req_comp; - #define STBI__COMBO(a,b) ((a)*8+(b)) - #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + #define COMBO(a,b) ((a)*8+(b)) + #define CASE(a,b) case COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) // convert source image with img_n components to one with req_comp components; // avoid switch per pixel, so use switch per scanline and massive macros - switch (STBI__COMBO(img_n, req_comp)) { - STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; - STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; - STBI__CASE(2,1) { dest[0]=src[0]; } break; - STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; - STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; - STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; - STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; - STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; - STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; - STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + switch (COMBO(img_n, req_comp)) { + CASE(1,2) dest[0]=src[0], dest[1]=255; break; + CASE(1,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(1,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; break; + CASE(2,1) dest[0]=src[0]; break; + CASE(2,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(2,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; break; + CASE(3,4) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; break; + CASE(3,1) dest[0]=stbi__compute_y(src[0],src[1],src[2]); break; + CASE(3,2) dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; break; + CASE(4,1) dest[0]=stbi__compute_y(src[0],src[1],src[2]); break; + CASE(4,2) dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; break; + CASE(4,3) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; break; default: STBI_ASSERT(0); } - #undef STBI__CASE + #undef CASE } STBI_FREE(data); return good; } -#endif - -#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) -// nothing -#else -static stbi__uint16 stbi__compute_y_16(int r, int g, int b) -{ - return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); -} -#endif - -#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) -// nothing -#else -static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) -{ - int i,j; - stbi__uint16 *good; - - if (req_comp == img_n) return data; - STBI_ASSERT(req_comp >= 1 && req_comp <= 4); - - good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); - if (good == NULL) { - STBI_FREE(data); - return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); - } - - for (j=0; j < (int) y; ++j) { - stbi__uint16 *src = data + j * x * img_n ; - stbi__uint16 *dest = good + j * x * req_comp; - - #define STBI__COMBO(a,b) ((a)*8+(b)) - #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) - // convert source image with img_n components to one with req_comp components; - // avoid switch per pixel, so use switch per scanline and massive macros - switch (STBI__COMBO(img_n, req_comp)) { - STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; - STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; - STBI__CASE(2,1) { dest[0]=src[0]; } break; - STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; - STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; - STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; - STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; - STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; - STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; - STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; - STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; - default: STBI_ASSERT(0); - } - #undef STBI__CASE - } - - STBI_FREE(data); - return good; -} -#endif #ifndef STBI_NO_LINEAR static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) { int i,k,n; - float *output; - if (!data) return NULL; - output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + float *output = (float *) stbi__malloc(x * y * comp * sizeof(float)); if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } // compute number of non-alpha components if (comp & 1) n = comp; else n = comp-1; @@ -1767,11 +1393,7 @@ static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) for (k=0; k < n; ++k) { output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); } - } - if (n < comp) { - for (i=0; i < x*y; ++i) { - output[i*comp + n] = data[i*comp + n]/255.0f; - } + if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; } STBI_FREE(data); return output; @@ -1783,9 +1405,7 @@ static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) { int i,k,n; - stbi_uc *output; - if (!data) return NULL; - output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + stbi_uc *output = (stbi_uc *) stbi__malloc(x * y * comp); if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } // compute number of non-alpha components if (comp & 1) n = comp; else n = comp-1; @@ -1850,7 +1470,7 @@ typedef struct stbi__context *s; stbi__huffman huff_dc[4]; stbi__huffman huff_ac[4]; - stbi__uint16 dequant[4][64]; + stbi_uc dequant[4][64]; stbi__int16 fast_ac[4][1 << FAST_BITS]; // sizes for components, interleaved MCUs @@ -1886,9 +1506,6 @@ typedef struct int succ_high; int succ_low; int eob_run; - int jfif; - int app14_color_transform; // Adobe APP14 tag - int rgb; int scan_n, order[4]; int restart_interval, todo; @@ -1901,8 +1518,7 @@ typedef struct static int stbi__build_huffman(stbi__huffman *h, int *count) { - int i,j,k=0; - unsigned int code; + int i,j,k=0,code; // build size list for each symbol (from JPEG spec) for (i=0; i < 16; ++i) for (j=0; j < count[i]; ++j) @@ -1918,7 +1534,7 @@ static int stbi__build_huffman(stbi__huffman *h, int *count) if (h->size[k] == j) { while (h->size[k] == j) h->code[k++] = (stbi__uint16) (code++); - if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + if (code-1 >= (1 << j)) return stbi__err("bad code lengths","Corrupt JPEG"); } // compute largest code + 1 for this size, preshifted as needed later h->maxcode[j] = code << (16-j); @@ -1959,10 +1575,10 @@ static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) // magnitude code followed by receive_extend code int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); int m = 1 << (magbits - 1); - if (k < m) k += (~0U << magbits) + 1; + if (k < m) k += (-1 << magbits) + 1; // if the result is small enough, we can fit it in fast_ac table if (k >= -128 && k <= 127) - fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); + fast_ac[i] = (stbi__int16) ((k << 8) + (run << 4) + (len + magbits)); } } } @@ -1971,10 +1587,9 @@ static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) static void stbi__grow_buffer_unsafe(stbi__jpeg *j) { do { - unsigned int b = j->nomore ? 0 : stbi__get8(j->s); + int b = j->nomore ? 0 : stbi__get8(j->s); if (b == 0xff) { int c = stbi__get8(j->s); - while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes if (c != 0) { j->marker = (unsigned char) c; j->nomore = 1; @@ -1987,7 +1602,7 @@ static void stbi__grow_buffer_unsafe(stbi__jpeg *j) } // (1 << n) - 1 -static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; +static stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; // decode a jpeg huffman value from the bitstream stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) @@ -2040,7 +1655,7 @@ stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) } // bias[n] = (-1<<n) + 1 -static const int stbi__jbias[16] = {0,-1,-3,-7,-15,-31,-63,-127,-255,-511,-1023,-2047,-4095,-8191,-16383,-32767}; +static int const stbi__jbias[16] = {0,-1,-3,-7,-15,-31,-63,-127,-255,-511,-1023,-2047,-4095,-8191,-16383,-32767}; // combined JPEG 'receive' and JPEG 'extend', since baseline // always extends everything it receives. @@ -2083,7 +1698,7 @@ stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) // given a value that's at position X in the zigzag stream, // where does it appear in the 8x8 matrix coded as row-major? -static const stbi_uc stbi__jpeg_dezigzag[64+15] = +static stbi_uc stbi__jpeg_dezigzag[64+15] = { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, @@ -2099,7 +1714,7 @@ static const stbi_uc stbi__jpeg_dezigzag[64+15] = }; // decode one 64-entry block-- -static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi_uc *dequant) { int diff,dc,k; int t; @@ -2309,7 +1924,7 @@ stbi_inline static stbi_uc stbi__clamp(int x) } #define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) -#define stbi__fsh(x) ((x) * 4096) +#define stbi__fsh(x) ((x) << 12) // derived from jidctint -- DCT_ISLOW #define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ @@ -2364,7 +1979,7 @@ static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) // (1|2|3|4|5|6|7)==0 0 seconds // all separate -0.047 seconds // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds - int dcterm = d[0]*4; + int dcterm = d[0] << 2; v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; } else { STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) @@ -2808,7 +2423,7 @@ static stbi_uc stbi__get_marker(stbi__jpeg *j) x = stbi__get8(j->s); if (x != 0xff) return STBI__MARKER_none; while (x == 0xff) - x = stbi__get8(j->s); // consume repeated 0xff fill bytes + x = stbi__get8(j->s); return x; } @@ -2823,7 +2438,7 @@ static void stbi__jpeg_reset(stbi__jpeg *j) j->code_bits = 0; j->code_buffer = 0; j->nomore = 0; - j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = 0; j->marker = STBI__MARKER_none; j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; j->eob_run = 0; @@ -2955,7 +2570,7 @@ static int stbi__parse_entropy_coded_data(stbi__jpeg *z) } } -static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) +static void stbi__jpeg_dequantize(short *data, stbi_uc *dequant) { int i; for (i=0; i < 64; ++i) @@ -2997,14 +2612,13 @@ static int stbi__process_marker(stbi__jpeg *z, int m) L = stbi__get16be(z->s)-2; while (L > 0) { int q = stbi__get8(z->s); - int p = q >> 4, sixteen = (p != 0); + int p = q >> 4; int t = q & 15,i; - if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); + if (p != 0) return stbi__err("bad DQT type","Corrupt JPEG"); if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); - for (i=0; i < 64; ++i) - z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); - L -= (sixteen ? 129 : 65); + z->dequant[t][stbi__jpeg_dezigzag[i]] = stbi__get8(z->s); + L -= 65; } return L==0; @@ -3037,50 +2651,12 @@ static int stbi__process_marker(stbi__jpeg *z, int m) } return L==0; } - // check for comment block or APP blocks if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { - L = stbi__get16be(z->s); - if (L < 2) { - if (m == 0xFE) - return stbi__err("bad COM len","Corrupt JPEG"); - else - return stbi__err("bad APP len","Corrupt JPEG"); - } - L -= 2; - - if (m == 0xE0 && L >= 5) { // JFIF APP0 segment - static const unsigned char tag[5] = {'J','F','I','F','\0'}; - int ok = 1; - int i; - for (i=0; i < 5; ++i) - if (stbi__get8(z->s) != tag[i]) - ok = 0; - L -= 5; - if (ok) - z->jfif = 1; - } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment - static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; - int ok = 1; - int i; - for (i=0; i < 6; ++i) - if (stbi__get8(z->s) != tag[i]) - ok = 0; - L -= 6; - if (ok) { - stbi__get8(z->s); // version - stbi__get16be(z->s); // flags0 - stbi__get16be(z->s); // flags1 - z->app14_color_transform = stbi__get8(z->s); // color transform - L -= 6; - } - } - - stbi__skip(z->s, L); + stbi__skip(z->s, stbi__get16be(z->s)-2); return 1; } - - return stbi__err("unknown marker","Corrupt JPEG"); + return 0; } // after we see SOS @@ -3123,28 +2699,6 @@ static int stbi__process_scan_header(stbi__jpeg *z) return 1; } -static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) -{ - int i; - for (i=0; i < ncomp; ++i) { - if (z->img_comp[i].raw_data) { - STBI_FREE(z->img_comp[i].raw_data); - z->img_comp[i].raw_data = NULL; - z->img_comp[i].data = NULL; - } - if (z->img_comp[i].raw_coeff) { - STBI_FREE(z->img_comp[i].raw_coeff); - z->img_comp[i].raw_coeff = 0; - z->img_comp[i].coeff = 0; - } - if (z->img_comp[i].linebuf) { - STBI_FREE(z->img_comp[i].linebuf); - z->img_comp[i].linebuf = NULL; - } - } - return why; -} - static int stbi__process_frame_header(stbi__jpeg *z, int scan) { stbi__context *s = z->s; @@ -3154,7 +2708,7 @@ static int stbi__process_frame_header(stbi__jpeg *z, int scan) s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires c = stbi__get8(s); - if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); + if (c != 3 && c != 1) return stbi__err("bad component count","Corrupt JPEG"); // JFIF requires s->img_n = c; for (i=0; i < c; ++i) { z->img_comp[i].data = NULL; @@ -3163,12 +2717,11 @@ static int stbi__process_frame_header(stbi__jpeg *z, int scan) if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); - z->rgb = 0; for (i=0; i < s->img_n; ++i) { - static const unsigned char rgb[3] = { 'R', 'G', 'B' }; z->img_comp[i].id = stbi__get8(s); - if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) - ++z->rgb; + if (z->img_comp[i].id != i+1) // JFIF requires + if (z->img_comp[i].id != i) // some version of jpegtran outputs non-JFIF-compliant files! + return stbi__err("bad component ID","Corrupt JPEG"); q = stbi__get8(s); z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); @@ -3177,7 +2730,7 @@ static int stbi__process_frame_header(stbi__jpeg *z, int scan) if (scan != STBI__SCAN_load) return 1; - if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); for (i=0; i < s->img_n; ++i) { if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; @@ -3189,7 +2742,6 @@ static int stbi__process_frame_header(stbi__jpeg *z, int scan) z->img_v_max = v_max; z->img_mcu_w = h_max * 8; z->img_mcu_h = v_max * 8; - // these sizes can't be more than 17 bits z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; @@ -3201,27 +2753,28 @@ static int stbi__process_frame_header(stbi__jpeg *z, int scan) // the bogus oversized data from using interleaved MCUs and their // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't // discard the extra data until colorspace conversion - // - // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) - // so these muls can't overflow with 32-bit ints (which we require) z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; - z->img_comp[i].coeff = 0; - z->img_comp[i].raw_coeff = 0; - z->img_comp[i].linebuf = NULL; - z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); - if (z->img_comp[i].raw_data == NULL) - return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].raw_data = stbi__malloc(z->img_comp[i].w2 * z->img_comp[i].h2+15); + + if (z->img_comp[i].raw_data == NULL) { + for(--i; i >= 0; --i) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + } + return stbi__err("outofmem", "Out of memory"); + } // align blocks for idct using mmx/sse z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + z->img_comp[i].linebuf = NULL; if (z->progressive) { - // w2, h2 are multiples of 8 (see above) - z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; - z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; - z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); - if (z->img_comp[i].raw_coeff == NULL) - return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff_w = (z->img_comp[i].w2 + 7) >> 3; + z->img_comp[i].coeff_h = (z->img_comp[i].h2 + 7) >> 3; + z->img_comp[i].raw_coeff = STBI_MALLOC(z->img_comp[i].coeff_w * z->img_comp[i].coeff_h * 64 * sizeof(short) + 15); z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } else { + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; } } @@ -3240,8 +2793,6 @@ static int stbi__process_frame_header(stbi__jpeg *z, int scan) static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) { int m; - z->jfif = 0; - z->app14_color_transform = -1; // valid values are 0,1,2 z->marker = STBI__MARKER_none; // initialize cached marker to empty m = stbi__get_marker(z); if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); @@ -3283,15 +2834,12 @@ static int stbi__decode_jpeg_image(stbi__jpeg *j) if (x == 255) { j->marker = stbi__get8(j->s); break; + } else if (x != 0) { + return stbi__err("junk before marker", "Corrupt JPEG"); } } // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 } - } else if (stbi__DNL(m)) { - int Ld = stbi__get16be(j->s); - stbi__uint32 NL = stbi__get16be(j->s); - if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); - if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); } else { if (!stbi__process_marker(j, m)) return 0; } @@ -3510,9 +3058,38 @@ static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_ return out; } +#ifdef STBI_JPEG_OLD +// this is the same YCbCr-to-RGB calculation that stb_image has used +// historically before the algorithm changes in 1.49 +#define float2fixed(x) ((int) ((x) * 65536 + 0.5)) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 16) + 32768; // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr*float2fixed(1.40200f); + g = y_fixed - cr*float2fixed(0.71414f) - cb*float2fixed(0.34414f); + b = y_fixed + cb*float2fixed(1.77200f); + r >>= 16; + g >>= 16; + b >>= 16; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#else // this is a reduced-precision calculation of YCbCr-to-RGB introduced // to make sure the code produces the same results in both SIMD and scalar -#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +#define float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) { int i; @@ -3521,9 +3098,9 @@ static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc int r,g,b; int cr = pcr[i] - 128; int cb = pcb[i] - 128; - r = y_fixed + cr* stbi__float2fixed(1.40200f); - g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); - b = y_fixed + cb* stbi__float2fixed(1.77200f); + r = y_fixed + cr* float2fixed(1.40200f); + g = y_fixed + (cr*-float2fixed(0.71414f)) + ((cb*-float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* float2fixed(1.77200f); r >>= 20; g >>= 20; b >>= 20; @@ -3537,6 +3114,7 @@ static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc out += step; } } +#endif #if defined(STBI_SSE2) || defined(STBI_NEON) static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) @@ -3655,9 +3233,9 @@ static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc cons int r,g,b; int cr = pcr[i] - 128; int cb = pcb[i] - 128; - r = y_fixed + cr* stbi__float2fixed(1.40200f); - g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); - b = y_fixed + cb* stbi__float2fixed(1.77200f); + r = y_fixed + cr* float2fixed(1.40200f); + g = y_fixed + cr*-float2fixed(0.71414f) + ((cb*-float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* float2fixed(1.77200f); r >>= 20; g >>= 20; b >>= 20; @@ -3683,14 +3261,18 @@ static void stbi__setup_jpeg(stbi__jpeg *j) #ifdef STBI_SSE2 if (stbi__sse2_available()) { j->idct_block_kernel = stbi__idct_simd; + #ifndef STBI_JPEG_OLD j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + #endif j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; } #endif #ifdef STBI_NEON j->idct_block_kernel = stbi__idct_simd; + #ifndef STBI_JPEG_OLD j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + #endif j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; #endif } @@ -3698,7 +3280,23 @@ static void stbi__setup_jpeg(stbi__jpeg *j) // clean up the temporary component buffers static void stbi__cleanup_jpeg(stbi__jpeg *j) { - stbi__free_jpeg_components(j, j->s->img_n, 0); + int i; + for (i=0; i < j->s->img_n; ++i) { + if (j->img_comp[i].raw_data) { + STBI_FREE(j->img_comp[i].raw_data); + j->img_comp[i].raw_data = NULL; + j->img_comp[i].data = NULL; + } + if (j->img_comp[i].raw_coeff) { + STBI_FREE(j->img_comp[i].raw_coeff); + j->img_comp[i].raw_coeff = 0; + j->img_comp[i].coeff = 0; + } + if (j->img_comp[i].linebuf) { + STBI_FREE(j->img_comp[i].linebuf); + j->img_comp[i].linebuf = NULL; + } + } } typedef struct @@ -3711,16 +3309,9 @@ typedef struct int ypos; // which pre-expansion row we're on } stbi__resample; -// fast 0..255 * 0..255 => 0..255 rounded multiplication -static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) -{ - unsigned int t = x*y + 128; - return (stbi_uc) ((t + (t >>8)) >> 8); -} - static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) { - int n, decode_n, is_rgb; + int n, decode_n; z->s->img_n = 0; // make stbi__cleanup_jpeg safe // validate req_comp @@ -3730,11 +3321,9 @@ static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } // determine actual number of components to generate - n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; - - is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); + n = req_comp ? req_comp : z->s->img_n; - if (z->s->img_n == 3 && n < 3 && !is_rgb) + if (z->s->img_n == 3 && n < 3) decode_n = 1; else decode_n = z->s->img_n; @@ -3744,7 +3333,7 @@ static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp int k; unsigned int i,j; stbi_uc *output; - stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; + stbi_uc *coutput[4]; stbi__resample res_comp[4]; @@ -3771,7 +3360,7 @@ static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp } // can't error after this so, this is safe - output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + output = (stbi_uc *) stbi__malloc(n * z->s->img_x * z->s->img_y + 1); if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } // now go ahead and resample @@ -3794,39 +3383,7 @@ static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp if (n >= 3) { stbi_uc *y = coutput[0]; if (z->s->img_n == 3) { - if (is_rgb) { - for (i=0; i < z->s->img_x; ++i) { - out[0] = y[i]; - out[1] = coutput[1][i]; - out[2] = coutput[2][i]; - out[3] = 255; - out += n; - } - } else { - z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); - } - } else if (z->s->img_n == 4) { - if (z->app14_color_transform == 0) { // CMYK - for (i=0; i < z->s->img_x; ++i) { - stbi_uc m = coutput[3][i]; - out[0] = stbi__blinn_8x8(coutput[0][i], m); - out[1] = stbi__blinn_8x8(coutput[1][i], m); - out[2] = stbi__blinn_8x8(coutput[2][i], m); - out[3] = 255; - out += n; - } - } else if (z->app14_color_transform == 2) { // YCCK - z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); - for (i=0; i < z->s->img_x; ++i) { - stbi_uc m = coutput[3][i]; - out[0] = stbi__blinn_8x8(255 - out[0], m); - out[1] = stbi__blinn_8x8(255 - out[1], m); - out[2] = stbi__blinn_8x8(255 - out[2], m); - out += n; - } - } else { // YCbCr + alpha? Ignore the fourth channel for now - z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); - } + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); } else for (i=0; i < z->s->img_x; ++i) { out[0] = out[1] = out[2] = y[i]; @@ -3834,70 +3391,37 @@ static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp out += n; } } else { - if (is_rgb) { - if (n == 1) - for (i=0; i < z->s->img_x; ++i) - *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); - else { - for (i=0; i < z->s->img_x; ++i, out += 2) { - out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); - out[1] = 255; - } - } - } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { - for (i=0; i < z->s->img_x; ++i) { - stbi_uc m = coutput[3][i]; - stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); - stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); - stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); - out[0] = stbi__compute_y(r, g, b); - out[1] = 255; - out += n; - } - } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { - for (i=0; i < z->s->img_x; ++i) { - out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); - out[1] = 255; - out += n; - } - } else { - stbi_uc *y = coutput[0]; - if (n == 1) - for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; - else - for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } - } + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255; } } stbi__cleanup_jpeg(z); *out_x = z->s->img_x; *out_y = z->s->img_y; - if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output + if (comp) *comp = z->s->img_n; // report original components, not output return output; } } -static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +static unsigned char *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) { - unsigned char* result; - stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); - STBI_NOTUSED(ri); - j->s = s; - stbi__setup_jpeg(j); - result = load_jpeg_image(j, x,y,comp,req_comp); - STBI_FREE(j); - return result; + stbi__jpeg j; + j.s = s; + stbi__setup_jpeg(&j); + return load_jpeg_image(&j, x,y,comp,req_comp); } static int stbi__jpeg_test(stbi__context *s) { int r; - stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); - j->s = s; - stbi__setup_jpeg(j); - r = stbi__decode_jpeg_header(j, STBI__SCAN_type); + stbi__jpeg j; + j.s = s; + stbi__setup_jpeg(&j); + r = stbi__decode_jpeg_header(&j, STBI__SCAN_type); stbi__rewind(s); - STBI_FREE(j); return r; } @@ -3909,18 +3433,15 @@ static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) } if (x) *x = j->s->img_x; if (y) *y = j->s->img_y; - if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; + if (comp) *comp = j->s->img_n; return 1; } static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) { - int result; - stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); - j->s = s; - result = stbi__jpeg_info_raw(j, x, y, comp); - STBI_FREE(j); - return result; + stbi__jpeg j; + j.s = s; + return stbi__jpeg_info_raw(&j, x, y, comp); } #endif @@ -3966,7 +3487,7 @@ stbi_inline static int stbi__bit_reverse(int v, int bits) return stbi__bitreverse16(v) >> (16-bits); } -static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) +static int stbi__zbuild_huffman(stbi__zhuffman *z, stbi_uc *sizelist, int num) { int i,k=0; int code, next_code[16], sizes[17]; @@ -4109,18 +3630,18 @@ static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room return 1; } -static const int stbi__zlength_base[31] = { +static int stbi__zlength_base[31] = { 3,4,5,6,7,8,9,10,11,13, 15,17,19,23,27,31,35,43,51,59, 67,83,99,115,131,163,195,227,258,0,0 }; -static const int stbi__zlength_extra[31]= +static int stbi__zlength_extra[31]= { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; -static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +static int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; -static const int stbi__zdist_extra[32] = +static int stbi__zdist_extra[32] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; static int stbi__parse_huffman_block(stbi__zbuf *a) @@ -4167,7 +3688,7 @@ static int stbi__parse_huffman_block(stbi__zbuf *a) static int stbi__compute_huffman_codes(stbi__zbuf *a) { - static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + static stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; stbi__zhuffman z_codelength; stbi_uc lencodes[286+32+137];//padding for maximum single op stbi_uc codelength_sizes[19]; @@ -4176,7 +3697,6 @@ static int stbi__compute_huffman_codes(stbi__zbuf *a) int hlit = stbi__zreceive(a,5) + 257; int hdist = stbi__zreceive(a,5) + 1; int hclen = stbi__zreceive(a,4) + 4; - int ntot = hlit + hdist; memset(codelength_sizes, 0, sizeof(codelength_sizes)); for (i=0; i < hclen; ++i) { @@ -4186,35 +3706,33 @@ static int stbi__compute_huffman_codes(stbi__zbuf *a) if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; n = 0; - while (n < ntot) { + while (n < hlit + hdist) { int c = stbi__zhuffman_decode(a, &z_codelength); if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); if (c < 16) lencodes[n++] = (stbi_uc) c; - else { - stbi_uc fill = 0; - if (c == 16) { - c = stbi__zreceive(a,2)+3; - if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); - fill = lencodes[n-1]; - } else if (c == 17) - c = stbi__zreceive(a,3)+3; - else { - STBI_ASSERT(c == 18); - c = stbi__zreceive(a,7)+11; - } - if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); - memset(lencodes+n, fill, c); + else if (c == 16) { + c = stbi__zreceive(a,2)+3; + memset(lencodes+n, lencodes[n-1], c); + n += c; + } else if (c == 17) { + c = stbi__zreceive(a,3)+3; + memset(lencodes+n, 0, c); + n += c; + } else { + STBI_ASSERT(c == 18); + c = stbi__zreceive(a,7)+11; + memset(lencodes+n, 0, c); n += c; } } - if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (n != hlit+hdist) return stbi__err("bad codelengths","Corrupt PNG"); if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; return 1; } -static int stbi__parse_uncompressed_block(stbi__zbuf *a) +static int stbi__parse_uncomperssed_block(stbi__zbuf *a) { stbi_uc header[4]; int len,nlen,k; @@ -4252,28 +3770,13 @@ static int stbi__parse_zlib_header(stbi__zbuf *a) if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png - // window = 1 << (8 + cinfo)... but who cares, we fully buffer output - return 1; -} - -static const stbi_uc stbi__zdefault_length[288] = -{ - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, - 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 -}; -static const stbi_uc stbi__zdefault_distance[32] = -{ - 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 -}; -/* -Init algorithm: + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +// @TODO: should statically initialize these for optimal thread safety +static stbi_uc stbi__zdefault_length[288], stbi__zdefault_distance[32]; +static void stbi__init_zdefaults(void) { int i; // use <= to match clearly with spec for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; @@ -4283,7 +3786,6 @@ Init algorithm: for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; } -*/ static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) { @@ -4296,12 +3798,13 @@ static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) final = stbi__zreceive(a,1); type = stbi__zreceive(a,2); if (type == 0) { - if (!stbi__parse_uncompressed_block(a)) return 0; + if (!stbi__parse_uncomperssed_block(a)) return 0; } else if (type == 3) { return 0; } else { if (type == 1) { // use fixed code lengths + if (!stbi__zdefault_distance[31]) stbi__init_zdefaults(); if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0; if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; } else { @@ -4426,7 +3929,7 @@ static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) static int stbi__check_png_header(stbi__context *s) { - static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + static stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; int i; for (i=0; i < 8; ++i) if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); @@ -4437,7 +3940,6 @@ typedef struct { stbi__context *s; stbi_uc *idata, *expanded, *out; - int depth; } stbi__png; @@ -4472,40 +3974,35 @@ static int stbi__paeth(int a, int b, int c) return c; } -static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; +static stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; // create the png data from post-deflated data static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) { - int bytes = (depth == 16? 2 : 1); stbi__context *s = a->s; - stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 i,j,stride = x*out_n; stbi__uint32 img_len, img_width_bytes; int k; int img_n = s->img_n; // copy it into a local for later - int output_bytes = out_n*bytes; - int filter_bytes = img_n*bytes; - int width = x; - STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); - a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + a->out = (stbi_uc *) stbi__malloc(x * y * out_n); // extra bytes to write off the end into if (!a->out) return stbi__err("outofmem", "Out of memory"); - if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); img_width_bytes = (((img_n * x * depth) + 7) >> 3); img_len = (img_width_bytes + 1) * y; - - // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, - // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), - // so just check for raw_len < img_len always. - if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + if (s->img_x == x && s->img_y == y) { + if (raw_len != img_len) return stbi__err("not enough pixels","Corrupt PNG"); + } else { // interlaced: + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + } for (j=0; j < y; ++j) { stbi_uc *cur = a->out + stride*j; - stbi_uc *prior; + stbi_uc *prior = cur - stride; int filter = *raw++; - + int filter_bytes = img_n; + int width = x; if (filter > 4) return stbi__err("invalid filter","Corrupt PNG"); @@ -4515,7 +4012,6 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r filter_bytes = 1; width = img_width_bytes; } - prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above // if first row, use special filter that doesn't sample previous row if (j == 0) filter = first_row_filter[filter]; @@ -4539,14 +4035,6 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r raw += img_n; cur += out_n; prior += out_n; - } else if (depth == 16) { - if (img_n != out_n) { - cur[filter_bytes] = 255; // first pixel top byte - cur[filter_bytes+1] = 255; // first pixel bottom byte - } - raw += filter_bytes; - cur += output_bytes; - prior += output_bytes; } else { raw += 1; cur += 1; @@ -4555,47 +4043,38 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r // this is a little gross, so that we don't switch per-pixel or per-component if (depth < 8 || img_n == out_n) { - int nk = (width - 1)*filter_bytes; - #define STBI__CASE(f) \ + int nk = (width - 1)*img_n; + #define CASE(f) \ case f: \ for (k=0; k < nk; ++k) switch (filter) { // "none" filter turns into a memcpy here; make that explicit. case STBI__F_none: memcpy(cur, raw, nk); break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; + CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); break; + CASE(STBI__F_up) cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); break; + CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); break; + CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); break; + CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); break; } - #undef STBI__CASE + #undef CASE raw += nk; } else { STBI_ASSERT(img_n+1 == out_n); - #define STBI__CASE(f) \ + #define CASE(f) \ case f: \ - for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ - for (k=0; k < filter_bytes; ++k) + for (i=x-1; i >= 1; --i, cur[img_n]=255,raw+=img_n,cur+=out_n,prior+=out_n) \ + for (k=0; k < img_n; ++k) switch (filter) { - STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; - STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; - STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; - STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; - STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; - STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; - STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; - } - #undef STBI__CASE - - // the loop above sets the high byte of the pixels' alpha, but for - // 16 bit png files we also need the low byte set. we'll do that here. - if (depth == 16) { - cur = a->out + stride*j; // start at the beginning of the row again - for (i=0; i < x; ++i,cur+=output_bytes) { - cur[filter_bytes+1] = 255; - } + CASE(STBI__F_none) cur[k] = raw[k]; break; + CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k-out_n]); break; + CASE(STBI__F_up) cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-out_n])>>1)); break; + CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-out_n],prior[k],prior[k-out_n])); break; + CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k-out_n] >> 1)); break; + CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-out_n],0,0)); break; } + #undef CASE } } @@ -4671,17 +4150,6 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r } } } - } else if (depth == 16) { - // force the image data from big-endian to platform-native. - // this is done in a separate pass due to the decoding relying - // on the data being untouched, but could probably be done - // per-line during decode if care is taken. - stbi_uc *cur = a->out; - stbi__uint16 *cur16 = (stbi__uint16*)cur; - - for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { - *cur16 = (cur[0] << 8) | cur[1]; - } } return 1; @@ -4689,15 +4157,13 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) { - int bytes = (depth == 16 ? 2 : 1); - int out_bytes = out_n * bytes; stbi_uc *final; int p; if (!interlaced) return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); // de-interlacing - final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + final = (stbi_uc *) stbi__malloc(a->s->img_x * a->s->img_y * out_n); for (p=0; p < 7; ++p) { int xorig[] = { 0,4,0,2,0,1,0 }; int yorig[] = { 0,0,4,0,2,0,1 }; @@ -4717,8 +4183,8 @@ static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint3 for (i=0; i < x; ++i) { int out_y = j*yspc[p]+yorig[p]; int out_x = i*xspc[p]+xorig[p]; - memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, - a->out + (j*x+i)*out_bytes, out_bytes); + memcpy(final + out_y*a->s->img_x*out_n + out_x*out_n, + a->out + (j*x+i)*out_n, out_n); } } STBI_FREE(a->out); @@ -4756,37 +4222,12 @@ static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) return 1; } -static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) -{ - stbi__context *s = z->s; - stbi__uint32 i, pixel_count = s->img_x * s->img_y; - stbi__uint16 *p = (stbi__uint16*) z->out; - - // compute color-based transparency, assuming we've - // already got 65535 as the alpha value in the output - STBI_ASSERT(out_n == 2 || out_n == 4); - - if (out_n == 2) { - for (i = 0; i < pixel_count; ++i) { - p[1] = (p[0] == tc[0] ? 0 : 65535); - p += 2; - } - } else { - for (i = 0; i < pixel_count; ++i) { - if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) - p[3] = 0; - p += 4; - } - } - return 1; -} - static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) { stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; stbi_uc *p, *temp_out, *orig = a->out; - p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + p = (stbi_uc *) stbi__malloc(pixel_count * pal_img_n); if (p == NULL) return stbi__err("outofmem", "Out of memory"); // between here and free(out) below, exitting would leak @@ -4852,10 +4293,9 @@ static void stbi__de_iphone(stbi__png *z) stbi_uc a = p[3]; stbi_uc t = p[0]; if (a) { - stbi_uc half = a / 2; - p[0] = (p[2] * 255 + half) / a; - p[1] = (p[1] * 255 + half) / a; - p[2] = ( t * 255 + half) / a; + p[0] = p[2] * 255 / a; + p[1] = p[1] * 255 / a; + p[2] = t * 255 / a; } else { p[0] = p[2]; p[2] = t; @@ -4874,15 +4314,14 @@ static void stbi__de_iphone(stbi__png *z) } } -#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) +#define STBI__PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) { stbi_uc palette[1024], pal_img_n=0; - stbi_uc has_trans=0, tc[3]={0}; - stbi__uint16 tc16[3]; + stbi_uc has_trans=0, tc[3]; stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; - int first=1,k,interlace=0, color=0, is_iphone=0; + int first=1,k,interlace=0, color=0, depth=0, is_iphone=0; stbi__context *s = z->s; z->expanded = NULL; @@ -4907,9 +4346,8 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); - z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + depth = stbi__get8(s); if (depth != 1 && depth != 2 && depth != 4 && depth != 8) return stbi__err("1/2/4/8-bit only","PNG not supported: 1/2/4/8-bit only"); color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); - if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); @@ -4957,11 +4395,8 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); has_trans = 1; - if (z->depth == 16) { - for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is - } else { - for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger - } + for (k=0; k < s->img_n; ++k) + tc[k] = (stbi_uc) (stbi__get16be(s) & 255) * stbi__depth_scale_table[depth]; // non 8-bit images will be larger } break; } @@ -4992,7 +4427,7 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) if (scan != STBI__SCAN_load) return 1; if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); // initial guess for decoded data size to avoid unnecessary reallocs - bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + bpl = (s->img_x * depth + 7) / 8; // bytes per line, per component raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); if (z->expanded == NULL) return 0; // zlib should set error @@ -5001,14 +4436,9 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) s->img_out_n = s->img_n+1; else s->img_out_n = s->img_n; - if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; - if (has_trans) { - if (z->depth == 16) { - if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; - } else { - if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; - } - } + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, depth, color, interlace)) return 0; + if (has_trans) + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) stbi__de_iphone(z); if (pal_img_n) { @@ -5018,13 +4448,8 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) if (req_comp >= 3) s->img_out_n = req_comp; if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) return 0; - } else if (has_trans) { - // non-paletted image with tRNS -> source image has (constant) alpha - ++s->img_n; } STBI_FREE(z->expanded); z->expanded = NULL; - // end of PNG chunk, read and skip CRC - stbi__get32be(s); return 1; } @@ -5050,28 +4475,21 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) } } -static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +static unsigned char *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp) { - void *result=NULL; + unsigned char *result=NULL; if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { - if (p->depth < 8) - ri->bits_per_channel = 8; - else - ri->bits_per_channel = p->depth; result = p->out; p->out = NULL; if (req_comp && req_comp != p->s->img_out_n) { - if (ri->bits_per_channel == 8) - result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); - else - result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + result = stbi__convert_format(result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); p->s->img_out_n = req_comp; if (result == NULL) return result; } *x = p->s->img_x; *y = p->s->img_y; - if (n) *n = p->s->img_n; + if (n) *n = p->s->img_out_n; } STBI_FREE(p->out); p->out = NULL; STBI_FREE(p->expanded); p->expanded = NULL; @@ -5080,11 +4498,11 @@ static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, st return result; } -static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +static unsigned char *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) { stbi__png p; p.s = s; - return stbi__do_png(&p, x,y,comp,req_comp, ri); + return stbi__do_png(&p, x,y,comp,req_comp); } static int stbi__png_test(stbi__context *s) @@ -5113,19 +4531,6 @@ static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) p.s = s; return stbi__png_info_raw(&p, x, y, comp); } - -static int stbi__png_is16(stbi__context *s) -{ - stbi__png p; - p.s = s; - if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) - return 0; - if (p.depth != 16) { - stbi__rewind(p.s); - return 0; - } - return 1; -} #endif // Microsoft/Windows BMP image @@ -5159,11 +4564,11 @@ static int stbi__high_bit(unsigned int z) { int n=0; if (z == 0) return -1; - if (z >= 0x10000) { n += 16; z >>= 16; } - if (z >= 0x00100) { n += 8; z >>= 8; } - if (z >= 0x00010) { n += 4; z >>= 4; } - if (z >= 0x00004) { n += 2; z >>= 2; } - if (z >= 0x00002) { n += 1;/* >>= 1;*/ } + if (z >= 0x10000) n += 16, z >>= 16; + if (z >= 0x00100) n += 8, z >>= 8; + if (z >= 0x00010) n += 4, z >>= 4; + if (z >= 0x00004) n += 2, z >>= 2; + if (z >= 0x00002) n += 1, z >>= 1; return n; } @@ -5177,34 +4582,27 @@ static int stbi__bitcount(unsigned int a) return a & 0xff; } -// extract an arbitrarily-aligned N-bit value (N=bits) -// from v, and then make it 8-bits long and fractionally -// extend it to full full range. -static int stbi__shiftsigned(unsigned int v, int shift, int bits) -{ - static unsigned int mul_table[9] = { - 0, - 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, - 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, - }; - static unsigned int shift_table[9] = { - 0, 0,0,1,0,2,4,6,0, - }; - if (shift < 0) - v <<= -shift; - else - v >>= shift; - STBI_ASSERT(v < 256); - v >>= (8-bits); - STBI_ASSERT(bits >= 0 && bits <= 8); - return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; +static int stbi__shiftsigned(int v, int shift, int bits) +{ + int result; + int z=0; + + if (shift < 0) v <<= -shift; + else v >>= shift; + result = v; + + z = bits; + while (z < 8) { + result += v >> z; + z += bits; + } + return result; } typedef struct { int bpp, offset, hsz; unsigned int mr,mg,mb,ma, all_a; - int extra_read; } stbi__bmp_data; static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) @@ -5216,9 +4614,7 @@ static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) stbi__get16le(s); // discard reserved info->offset = stbi__get32le(s); info->hsz = hsz = stbi__get32le(s); - info->mr = info->mg = info->mb = info->ma = 0; - info->extra_read = 14; - + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); if (hsz == 12) { s->img_x = stbi__get16le(s); @@ -5229,6 +4625,7 @@ static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) } if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); info->bpp = stbi__get16le(s); + if (info->bpp == 1) return stbi__errpuc("monochrome", "BMP type not supported: 1-bit"); if (hsz != 12) { int compress = stbi__get32le(s); if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); @@ -5245,6 +4642,7 @@ static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) stbi__get32le(s); } if (info->bpp == 16 || info->bpp == 32) { + info->mr = info->mg = info->mb = 0; if (compress == 0) { if (info->bpp == 32) { info->mr = 0xffu << 16; @@ -5261,7 +4659,6 @@ static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) info->mr = stbi__get32le(s); info->mg = stbi__get32le(s); info->mb = stbi__get32le(s); - info->extra_read += 12; // not documented, but generated by photoshop and handled by mspaint if (info->mr == info->mg && info->mg == info->mb) { // ?!?!? @@ -5293,7 +4690,7 @@ static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) } -static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) { stbi_uc *out; unsigned int mr=0,mg=0,mb=0,ma=0, all_a; @@ -5301,9 +4698,8 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req int psize=0,i,j,width; int flip_vertically, pad, target; stbi__bmp_data info; - STBI_NOTUSED(ri); - info.all_a = 255; + info.all_a = 255; if (stbi__bmp_parse_header(s, &info) == NULL) return NULL; // error code already set @@ -5318,29 +4714,19 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req if (info.hsz == 12) { if (info.bpp < 24) - psize = (info.offset - info.extra_read - 24) / 3; + psize = (info.offset - 14 - 24) / 3; } else { if (info.bpp < 16) - psize = (info.offset - info.extra_read - info.hsz) >> 2; - } - if (psize == 0) { - STBI_ASSERT(info.offset == (s->img_buffer - s->buffer_start)); + psize = (info.offset - 14 - info.hsz) >> 2; } - if (info.bpp == 24 && ma == 0xff000000) - s->img_n = 3; - else - s->img_n = ma ? 4 : 3; + s->img_n = ma ? 4 : 3; if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 target = req_comp; else target = s->img_n; // if they want monochrome, we'll post-convert - // sanity-check size - if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) - return stbi__errpuc("too large", "Corrupt BMP"); - - out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + out = (stbi_uc *) stbi__malloc(target * s->img_x * s->img_y); if (!out) return stbi__errpuc("outofmem", "Out of memory"); if (info.bpp < 16) { int z=0; @@ -5352,56 +4738,36 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req if (info.hsz != 12) stbi__get8(s); pal[i][3] = 255; } - stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); - if (info.bpp == 1) width = (s->img_x + 7) >> 3; - else if (info.bpp == 4) width = (s->img_x + 1) >> 1; + stbi__skip(s, info.offset - 14 - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 4) width = (s->img_x + 1) >> 1; else if (info.bpp == 8) width = s->img_x; else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } pad = (-width)&3; - if (info.bpp == 1) { - for (j=0; j < (int) s->img_y; ++j) { - int bit_offset = 7, v = stbi__get8(s); - for (i=0; i < (int) s->img_x; ++i) { - int color = (v>>bit_offset)&0x1; - out[z++] = pal[color][0]; - out[z++] = pal[color][1]; - out[z++] = pal[color][2]; - if (target == 4) out[z++] = 255; - if (i+1 == (int) s->img_x) break; - if((--bit_offset) < 0) { - bit_offset = 7; - v = stbi__get8(s); - } - } - stbi__skip(s, pad); - } - } else { - for (j=0; j < (int) s->img_y; ++j) { - for (i=0; i < (int) s->img_x; i += 2) { - int v=stbi__get8(s),v2=0; - if (info.bpp == 4) { - v2 = v & 15; - v >>= 4; - } - out[z++] = pal[v][0]; - out[z++] = pal[v][1]; - out[z++] = pal[v][2]; - if (target == 4) out[z++] = 255; - if (i+1 == (int) s->img_x) break; - v = (info.bpp == 8) ? stbi__get8(s) : v2; - out[z++] = pal[v][0]; - out[z++] = pal[v][1]; - out[z++] = pal[v][2]; - if (target == 4) out[z++] = 255; + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; } - stbi__skip(s, pad); + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; } + stbi__skip(s, pad); } } else { int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; int z = 0; int easy=0; - stbi__skip(s, info.offset - info.extra_read - info.hsz); + stbi__skip(s, info.offset - 14 - info.hsz); if (info.bpp == 24) width = 3 * s->img_x; else if (info.bpp == 16) width = 2*s->img_x; else /* bpp = 32 and pad = 0 */ width=0; @@ -5436,7 +4802,7 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req int bpp = info.bpp; for (i=0; i < (int) s->img_x; ++i) { stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); - unsigned int a; + int a; out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); @@ -5448,7 +4814,7 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req stbi__skip(s, pad); } } - + // if alpha channel is all 0s, replace with all 255s if (target == 4 && all_a == 0) for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) @@ -5460,7 +4826,7 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req stbi_uc *p1 = out + j *s->img_x*target; stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; for (i=0; i < (int) s->img_x*target; ++i) { - t = p1[i]; p1[i] = p2[i]; p2[i] = t; + t = p1[i], p1[i] = p2[i], p2[i] = t; } } } @@ -5484,14 +4850,14 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) { // only RGB or RGBA (incl. 16bit) or grey allowed - if (is_rgb16) *is_rgb16 = 0; + if(is_rgb16) *is_rgb16 = 0; switch(bits_per_pixel) { case 8: return STBI_grey; case 16: if(is_grey) return STBI_grey_alpha; - // fallthrough + // else: fall-through case 15: if(is_rgb16) *is_rgb16 = 1; - return STBI_rgb; - case 24: // fallthrough + return STBI_rgb; + case 24: // fall-through case 32: return bits_per_pixel/8; default: return 0; } @@ -5594,18 +4960,18 @@ errorEnd: } // read 16bit value and convert to 24bit RGB -static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) { - stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 px = stbi__get16le(s); stbi__uint16 fiveBitMask = 31; // we have 3 channels with 5bits each int r = (px >> 10) & fiveBitMask; int g = (px >> 5) & fiveBitMask; int b = px & fiveBitMask; // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later - out[0] = (stbi_uc)((r * 255)/31); - out[1] = (stbi_uc)((g * 255)/31); - out[2] = (stbi_uc)((b * 255)/31); + out[0] = (r * 255)/31; + out[1] = (g * 255)/31; + out[2] = (b * 255)/31; // some people claim that the most significant bit might be used for alpha // (possibly if an alpha-bit is set in the "image descriptor byte") @@ -5613,7 +4979,7 @@ static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) // so let's treat all 15 and 16bit TGAs as RGB with no alpha. } -static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) { // read in the TGA header stuff int tga_offset = stbi__get8(s); @@ -5635,13 +5001,10 @@ static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req unsigned char *tga_data; unsigned char *tga_palette = NULL; int i, j; - unsigned char raw_data[4] = {0}; + unsigned char raw_data[4]; int RLE_count = 0; int RLE_repeating = 0; int read_next_pixel = 1; - STBI_NOTUSED(ri); - STBI_NOTUSED(tga_x_origin); // @TODO - STBI_NOTUSED(tga_y_origin); // @TODO // do a tiny bit of precessing if ( tga_image_type >= 8 ) @@ -5663,10 +5026,7 @@ static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req *y = tga_height; if (comp) *comp = tga_comp; - if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) - return stbi__errpuc("too large", "Corrupt TGA"); - - tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + tga_data = (unsigned char*)stbi__malloc( (size_t)tga_width * tga_height * tga_comp ); if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); // skip to the data's starting position (offset usually = 0) @@ -5685,7 +5045,7 @@ static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req // any data to skip? (offset usually = 0) stbi__skip(s, tga_palette_start ); // load the palette - tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + tga_palette = (unsigned char*)stbi__malloc( tga_palette_len * tga_comp ); if (!tga_palette) { STBI_FREE(tga_data); return stbi__errpuc("outofmem", "Out of memory"); @@ -5805,7 +5165,6 @@ static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req // Microsoft's C compilers happy... [8^( tga_palette_start = tga_palette_len = tga_palette_bits = tga_x_origin = tga_y_origin = 0; - STBI_NOTUSED(tga_palette_start); // OK, done return tga_data; } @@ -5822,53 +5181,14 @@ static int stbi__psd_test(stbi__context *s) return r; } -static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) -{ - int count, nleft, len; - - count = 0; - while ((nleft = pixelCount - count) > 0) { - len = stbi__get8(s); - if (len == 128) { - // No-op. - } else if (len < 128) { - // Copy next len+1 bytes literally. - len++; - if (len > nleft) return 0; // corrupt data - count += len; - while (len) { - *p = stbi__get8(s); - p += 4; - len--; - } - } else if (len > 128) { - stbi_uc val; - // Next -len+1 bytes in the dest are replicated from next source byte. - // (Interpret len as a negative 8-bit int.) - len = 257 - len; - if (len > nleft) return 0; // corrupt data - val = stbi__get8(s); - count += len; - while (len) { - *p = val; - p += 4; - len--; - } - } - } - - return 1; -} - -static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) { - int pixelCount; + int pixelCount; int channelCount, compression; - int channel, i; + int channel, i, count, len; int bitdepth; int w,h; stbi_uc *out; - STBI_NOTUSED(ri); // Check identifier if (stbi__get32be(s) != 0x38425053) // "8BPS" @@ -5925,18 +5245,8 @@ static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req if (compression > 1) return stbi__errpuc("bad compression", "PSD has an unknown compression format"); - // Check size - if (!stbi__mad3sizes_valid(4, w, h, 0)) - return stbi__errpuc("too large", "Corrupt PSD"); - // Create the destination image. - - if (!compression && bitdepth == 16 && bpc == 16) { - out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); - ri->bits_per_channel = 16; - } else - out = (stbi_uc *) stbi__malloc(4 * w*h); - + out = (stbi_uc *) stbi__malloc(4 * w*h); if (!out) return stbi__errpuc("outofmem", "Out of memory"); pixelCount = w*h; @@ -5953,7 +5263,7 @@ static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req // Else if n is 128, noop. // Endloop - // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, + // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, // which we're going to just skip. stbi__skip(s, h * channelCount * 2 ); @@ -5968,86 +5278,67 @@ static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req *p = (channel == 3 ? 255 : 0); } else { // Read the RLE data. - if (!stbi__psd_decode_rle(s, p, pixelCount)) { - STBI_FREE(out); - return stbi__errpuc("corrupt", "bad RLE data"); + count = 0; + while (count < pixelCount) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len ^= 0x0FF; + len += 2; + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } } } } } else { // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) - // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + // where each channel consists of an 8-bit value for each pixel in the image. // Read the data by channel. for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out + channel; if (channel >= channelCount) { // Fill this channel with default data. - if (bitdepth == 16 && bpc == 16) { - stbi__uint16 *q = ((stbi__uint16 *) out) + channel; - stbi__uint16 val = channel == 3 ? 65535 : 0; - for (i = 0; i < pixelCount; i++, q += 4) - *q = val; - } else { - stbi_uc *p = out+channel; - stbi_uc val = channel == 3 ? 255 : 0; - for (i = 0; i < pixelCount; i++, p += 4) - *p = val; - } + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; } else { - if (ri->bits_per_channel == 16) { // output bpc - stbi__uint16 *q = ((stbi__uint16 *) out) + channel; - for (i = 0; i < pixelCount; i++, q += 4) - *q = (stbi__uint16) stbi__get16be(s); + // Read the data. + if (bitdepth == 16) { + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); } else { - stbi_uc *p = out+channel; - if (bitdepth == 16) { // input bpc - for (i = 0; i < pixelCount; i++, p += 4) - *p = (stbi_uc) (stbi__get16be(s) >> 8); - } else { - for (i = 0; i < pixelCount; i++, p += 4) - *p = stbi__get8(s); - } - } - } - } - } - - // remove weird white matte from PSD - if (channelCount >= 4) { - if (ri->bits_per_channel == 16) { - for (i=0; i < w*h; ++i) { - stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; - if (pixel[3] != 0 && pixel[3] != 65535) { - float a = pixel[3] / 65535.0f; - float ra = 1.0f / a; - float inv_a = 65535.0f * (1 - ra); - pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); - pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); - pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); - } - } - } else { - for (i=0; i < w*h; ++i) { - unsigned char *pixel = out + 4*i; - if (pixel[3] != 0 && pixel[3] != 255) { - float a = pixel[3] / 255.0f; - float ra = 1.0f / a; - float inv_a = 255.0f * (1 - ra); - pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); - pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); - pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); } } } } - // convert to desired output format if (req_comp && req_comp != 4) { - if (ri->bits_per_channel == 16) - out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); - else - out = stbi__convert_format(out, 4, req_comp, w, h); + out = stbi__convert_format(out, 4, req_comp, w, h); if (out == NULL) return out; // stbi__convert_format frees input on failure } @@ -6231,13 +5522,10 @@ static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *c return result; } -static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +static stbi_uc *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp) { stbi_uc *result; - int i, x,y, internal_comp; - STBI_NOTUSED(ri); - - if (!comp) comp = &internal_comp; + int i, x,y; for (i=0; i<92; ++i) stbi__get8(s); @@ -6245,14 +5533,14 @@ static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_c x = stbi__get16be(s); y = stbi__get16be(s); if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); - if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + if ((1 << 28) / x < y) return stbi__errpuc("too large", "Image too large to decode"); stbi__get32be(s); //skip `ratio' stbi__get16be(s); //skip `fields' stbi__get16be(s); //skip `pad' // intermediate buffer is RGBA - result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + result = (stbi_uc *) stbi__malloc(x*y*4); memset(result, 0xff, x*y*4); if (!stbi__pic_load_core(s,x,y,comp, result)) { @@ -6289,13 +5577,11 @@ typedef struct typedef struct { int w,h; - stbi_uc *out; // output buffer (always 4 components) - stbi_uc *background; // The current "background" as far as a gif is concerned - stbi_uc *history; - int flags, bgindex, ratio, transparent, eflags; + stbi_uc *out, *old_out; // output buffer (always 4 components) + int flags, bgindex, ratio, transparent, eflags, delay; stbi_uc pal[256][4]; stbi_uc lpal[256][4]; - stbi__gif_lzw codes[8192]; + stbi__gif_lzw codes[4096]; stbi_uc *color_table; int parse, step; int lflags; @@ -6303,7 +5589,6 @@ typedef struct int max_x, max_y; int cur_x, cur_y; int line_size; - int delay; } stbi__gif; static int stbi__gif_test_raw(stbi__context *s) @@ -6364,22 +5649,19 @@ static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_in static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) { - stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); - if (!stbi__gif_header(s, g, comp, 1)) { - STBI_FREE(g); + stbi__gif g; + if (!stbi__gif_header(s, &g, comp, 1)) { stbi__rewind( s ); return 0; } - if (x) *x = g->w; - if (y) *y = g->h; - STBI_FREE(g); + if (x) *x = g.w; + if (y) *y = g.h; return 1; } static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) { stbi_uc *p, *c; - int idx; // recurse to decode the prefixes, since the linked-list is backwards, // and working backwards through an interleaved image would be nasty @@ -6388,12 +5670,10 @@ static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) if (g->cur_y >= g->max_y) return; - idx = g->cur_x + g->cur_y; - p = &g->out[idx]; - g->history[idx / 4] = 1; - + p = &g->out[g->cur_x + g->cur_y]; c = &g->color_table[g->codes[code].suffix * 4]; - if (c[3] > 128) { // don't render transparent pixels; + + if (c[3] >= 128) { p[0] = c[2]; p[1] = c[1]; p[2] = c[0]; @@ -6467,16 +5747,11 @@ static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) stbi__skip(s,len); return g->out; } else if (code <= avail) { - if (first) { - return stbi__errpuc("no clear code", "Corrupt GIF"); - } + if (first) return stbi__errpuc("no clear code", "Corrupt GIF"); if (oldcode >= 0) { p = &g->codes[avail++]; - if (avail > 8192) { - return stbi__errpuc("too many codes", "Corrupt GIF"); - } - + if (avail > 4096) return stbi__errpuc("too many codes", "Corrupt GIF"); p->prefix = (stbi__int16) oldcode; p->first = g->codes[oldcode].first; p->suffix = (code == avail) ? p->first : g->codes[code].first; @@ -6498,77 +5773,59 @@ static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) } } +static void stbi__fill_gif_background(stbi__gif *g, int x0, int y0, int x1, int y1) +{ + int x, y; + stbi_uc *c = g->pal[g->bgindex]; + for (y = y0; y < y1; y += 4 * g->w) { + for (x = x0; x < x1; x += 4) { + stbi_uc *p = &g->out[y + x]; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = 0; + } + } +} + // this function is designed to support animated gifs, although stb_image doesn't support it -// two back is the image from two frames ago, used for a very specific disposal format -static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp) { - int dispose; - int first_frame; - int pi; - int pcount; - STBI_NOTUSED(req_comp); + int i; + stbi_uc *prev_out = 0; - // on first frame, any non-written pixels get the background colour (non-transparent) - first_frame = 0; - if (g->out == 0) { - if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header - if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) - return stbi__errpuc("too large", "GIF image is too large"); - pcount = g->w * g->h; - g->out = (stbi_uc *) stbi__malloc(4 * pcount); - g->background = (stbi_uc *) stbi__malloc(4 * pcount); - g->history = (stbi_uc *) stbi__malloc(pcount); - if (!g->out || !g->background || !g->history) - return stbi__errpuc("outofmem", "Out of memory"); - - // image is treated as "transparent" at the start - ie, nothing overwrites the current background; - // background colour is only used for pixels that are not rendered first frame, after that "background" - // color refers to the color that was there the previous frame. - memset(g->out, 0x00, 4 * pcount); - memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) - memset(g->history, 0x00, pcount); // pixels that were affected previous frame - first_frame = 1; - } else { - // second frame - how do we dispoase of the previous one? - dispose = (g->eflags & 0x1C) >> 2; - pcount = g->w * g->h; + if (g->out == 0 && !stbi__gif_header(s, g, comp,0)) + return 0; // stbi__g_failure_reason set by stbi__gif_header - if ((dispose == 3) && (two_back == 0)) { - dispose = 2; // if I don't have an image to revert back to, default to the old background - } + prev_out = g->out; + g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h); + if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); - if (dispose == 3) { // use previous graphic - for (pi = 0; pi < pcount; ++pi) { - if (g->history[pi]) { - memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); - } - } - } else if (dispose == 2) { - // restore what was changed last frame to background before that frame; - for (pi = 0; pi < pcount; ++pi) { - if (g->history[pi]) { - memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); - } + switch ((g->eflags & 0x1C) >> 2) { + case 0: // unspecified (also always used on 1st frame) + stbi__fill_gif_background(g, 0, 0, 4 * g->w, 4 * g->w * g->h); + break; + case 1: // do not dispose + if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); + g->old_out = prev_out; + break; + case 2: // dispose to background + if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); + stbi__fill_gif_background(g, g->start_x, g->start_y, g->max_x, g->max_y); + break; + case 3: // dispose to previous + if (g->old_out) { + for (i = g->start_y; i < g->max_y; i += 4 * g->w) + memcpy(&g->out[i + g->start_x], &g->old_out[i + g->start_x], g->max_x - g->start_x); } - } else { - // This is a non-disposal case eithe way, so just - // leave the pixels as is, and they will become the new background - // 1: do not dispose - // 0: not specified. - } - - // background is what out is after the undoing of the previou frame; - memcpy( g->background, g->out, 4 * g->w * g->h ); + break; } - // clear my history; - memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame - for (;;) { - int tag = stbi__get8(s); - switch (tag) { + switch (stbi__get8(s)) { case 0x2C: /* Image Descriptor */ { + int prev_trans = -1; stbi__int32 x, y, w, h; stbi_uc *o; @@ -6587,13 +5844,6 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i g->cur_x = g->start_x; g->cur_y = g->start_y; - // if the width of the specified rectangle is 0, that means - // we may not see *any* pixels or the image is malformed; - // to make sure this is caught, move the current y down to - // max_y (which is what out_gif_code checks). - if (w == 0) - g->cur_y = g->max_y; - g->lflags = stbi__get8(s); if (g->lflags & 0x40) { @@ -6608,24 +5858,19 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); g->color_table = (stbi_uc *) g->lpal; } else if (g->flags & 0x80) { + if (g->transparent >= 0 && (g->eflags & 0x01)) { + prev_trans = g->pal[g->transparent][3]; + g->pal[g->transparent][3] = 0; + } g->color_table = (stbi_uc *) g->pal; } else return stbi__errpuc("missing color table", "Corrupt GIF"); o = stbi__process_gif_raster(s, g); - if (!o) return NULL; - - // if this was the first frame, - pcount = g->w * g->h; - if (first_frame && (g->bgindex > 0)) { - // if first frame, any pixel not drawn to gets the background color - for (pi = 0; pi < pcount; ++pi) { - if (g->history[pi] == 0) { - g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; - memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); - } - } - } + if (o == NULL) return NULL; + + if (prev_trans != -1) + g->pal[g->transparent][3] = (stbi_uc) prev_trans; return o; } @@ -6633,35 +5878,19 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i case 0x21: // Comment Extension. { int len; - int ext = stbi__get8(s); - if (ext == 0xF9) { // Graphic Control Extension. + if (stbi__get8(s) == 0xF9) { // Graphic Control Extension. len = stbi__get8(s); if (len == 4) { g->eflags = stbi__get8(s); - g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. - - // unset old transparent - if (g->transparent >= 0) { - g->pal[g->transparent][3] = 255; - } - if (g->eflags & 0x01) { - g->transparent = stbi__get8(s); - if (g->transparent >= 0) { - g->pal[g->transparent][3] = 0; - } - } else { - // don't need transparent - stbi__skip(s, 1); - g->transparent = -1; - } + g->delay = stbi__get16le(s); + g->transparent = stbi__get8(s); } else { stbi__skip(s, len); break; } } - while ((len = stbi__get8(s)) != 0) { + while ((len = stbi__get8(s)) != 0) stbi__skip(s, len); - } break; } @@ -6672,103 +5901,26 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i return stbi__errpuc("unknown code", "Corrupt GIF"); } } -} - -static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) -{ - if (stbi__gif_test(s)) { - int layers = 0; - stbi_uc *u = 0; - stbi_uc *out = 0; - stbi_uc *two_back = 0; - stbi__gif g; - int stride; - memset(&g, 0, sizeof(g)); - if (delays) { - *delays = 0; - } - - do { - u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); - if (u == (stbi_uc *) s) u = 0; // end of animated gif marker - - if (u) { - *x = g.w; - *y = g.h; - ++layers; - stride = g.w * g.h * 4; - - if (out) { - void *tmp = (stbi_uc*) STBI_REALLOC( out, layers * stride ); - if (NULL == tmp) { - STBI_FREE(g.out); - STBI_FREE(g.history); - STBI_FREE(g.background); - return stbi__errpuc("outofmem", "Out of memory"); - } - else - out = (stbi_uc*) tmp; - if (delays) { - *delays = (int*) STBI_REALLOC( *delays, sizeof(int) * layers ); - } - } else { - out = (stbi_uc*)stbi__malloc( layers * stride ); - if (delays) { - *delays = (int*) stbi__malloc( layers * sizeof(int) ); - } - } - memcpy( out + ((layers - 1) * stride), u, stride ); - if (layers >= 2) { - two_back = out - 2 * stride; - } - - if (delays) { - (*delays)[layers - 1U] = g.delay; - } - } - } while (u != 0); - - // free temp buffer; - STBI_FREE(g.out); - STBI_FREE(g.history); - STBI_FREE(g.background); - - // do the final conversion after loading everything; - if (req_comp && req_comp != 4) - out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); - *z = layers; - return out; - } else { - return stbi__errpuc("not GIF", "Image was not as a gif type."); - } + STBI_NOTUSED(req_comp); } -static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) { stbi_uc *u = 0; stbi__gif g; memset(&g, 0, sizeof(g)); - STBI_NOTUSED(ri); - u = stbi__gif_load_next(s, &g, comp, req_comp, 0); + u = stbi__gif_load_next(s, &g, comp, req_comp); if (u == (stbi_uc *) s) u = 0; // end of animated gif marker if (u) { *x = g.w; *y = g.h; - - // moved conversion to after successful load so that the same - // can be done for multiple frames. if (req_comp && req_comp != 4) u = stbi__convert_format(u, 4, req_comp, g.w, g.h); - } else if (g.out) { - // if there was an error and we allocated an image buffer, free it! - STBI_FREE(g.out); } - - // free buffers needed for multiple frame loading; - STBI_FREE(g.history); - STBI_FREE(g.background); + else if (g.out) + STBI_FREE(g.out); return u; } @@ -6783,24 +5935,20 @@ static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) // Radiance RGBE HDR loader // originally by Nicolas Schulz #ifndef STBI_NO_HDR -static int stbi__hdr_test_core(stbi__context *s, const char *signature) +static int stbi__hdr_test_core(stbi__context *s) { + const char *signature = "#?RADIANCE\n"; int i; for (i=0; signature[i]; ++i) if (stbi__get8(s) != signature[i]) - return 0; - stbi__rewind(s); + return 0; return 1; } static int stbi__hdr_test(stbi__context* s) { - int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + int r = stbi__hdr_test_core(s); stbi__rewind(s); - if(!r) { - r = stbi__hdr_test_core(s, "#?RGBE\n"); - stbi__rewind(s); - } return r; } @@ -6854,7 +6002,7 @@ static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) } } -static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) { char buffer[STBI__HDR_BUFLEN]; char *token; @@ -6865,12 +6013,10 @@ static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int re int len; unsigned char count, value; int i, j, k, c1,c2, z; - const char *headerToken; - STBI_NOTUSED(ri); + // Check identifier - headerToken = stbi__hdr_gettoken(s,buffer); - if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + if (strcmp(stbi__hdr_gettoken(s,buffer), "#?RADIANCE") != 0) return stbi__errpf("not HDR", "Corrupt HDR image"); // Parse header @@ -6899,13 +6045,8 @@ static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int re if (comp) *comp = 3; if (req_comp == 0) req_comp = 3; - if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) - return stbi__errpf("too large", "HDR image is too large"); - // Read data - hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); - if (!hdr_data) - return stbi__errpf("outofmem", "Out of memory"); + hdr_data = (float *) stbi__malloc(height * width * req_comp * sizeof(float)); // Load image data // image data is stored as some number of sca @@ -6944,29 +6085,20 @@ static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int re len <<= 8; len |= stbi__get8(s); if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } - if (scanline == NULL) { - scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); - if (!scanline) { - STBI_FREE(hdr_data); - return stbi__errpf("outofmem", "Out of memory"); - } - } + if (scanline == NULL) scanline = (stbi_uc *) stbi__malloc(width * 4); for (k = 0; k < 4; ++k) { - int nleft; i = 0; - while ((nleft = width - i) > 0) { + while (i < width) { count = stbi__get8(s); if (count > 128) { // Run value = stbi__get8(s); count -= 128; - if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } for (z = 0; z < count; ++z) scanline[i++ * 4 + k] = value; } else { // Dump - if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } for (z = 0; z < count; ++z) scanline[i++ * 4 + k] = stbi__get8(s); } @@ -6975,8 +6107,7 @@ static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int re for (i=0; i < width; ++i) stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); } - if (scanline) - STBI_FREE(scanline); + STBI_FREE(scanline); } return hdr_data; @@ -6987,11 +6118,6 @@ static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) char buffer[STBI__HDR_BUFLEN]; char *token; int valid = 0; - int dummy; - - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; if (stbi__hdr_test(s) == 0) { stbi__rewind( s ); @@ -7033,19 +6159,14 @@ static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) void *p; stbi__bmp_data info; - info.all_a = 255; + info.all_a = 255; p = stbi__bmp_parse_header(s, &info); stbi__rewind( s ); if (p == NULL) return 0; - if (x) *x = s->img_x; - if (y) *y = s->img_y; - if (comp) { - if (info.bpp == 24 && info.ma == 0xff000000) - *comp = 3; - else - *comp = info.ma ? 4 : 3; - } + *x = s->img_x; + *y = s->img_y; + *comp = info.ma ? 4 : 3; return 1; } #endif @@ -7053,10 +6174,7 @@ static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) #ifndef STBI_NO_PSD static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) { - int channelCount, dummy, depth; - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; + int channelCount; if (stbi__get32be(s) != 0x38425053) { stbi__rewind( s ); return 0; @@ -7073,8 +6191,7 @@ static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) } *y = stbi__get32be(s); *x = stbi__get32be(s); - depth = stbi__get16be(s); - if (depth != 8 && depth != 16) { + if (stbi__get16be(s) != 8) { stbi__rewind( s ); return 0; } @@ -7085,45 +6202,14 @@ static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) *comp = 4; return 1; } - -static int stbi__psd_is16(stbi__context *s) -{ - int channelCount, depth; - if (stbi__get32be(s) != 0x38425053) { - stbi__rewind( s ); - return 0; - } - if (stbi__get16be(s) != 1) { - stbi__rewind( s ); - return 0; - } - stbi__skip(s, 6); - channelCount = stbi__get16be(s); - if (channelCount < 0 || channelCount > 16) { - stbi__rewind( s ); - return 0; - } - (void) stbi__get32be(s); - (void) stbi__get32be(s); - depth = stbi__get16be(s); - if (depth != 16) { - stbi__rewind( s ); - return 0; - } - return 1; -} #endif #ifndef STBI_NO_PIC static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) { - int act_comp=0,num_packets=0,chained,dummy; + int act_comp=0,num_packets=0,chained; stbi__pic_packet packets[10]; - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { stbi__rewind(s); return 0; @@ -7199,22 +6285,16 @@ static int stbi__pnm_test(stbi__context *s) return 1; } -static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +static stbi_uc *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) { stbi_uc *out; - STBI_NOTUSED(ri); - if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) return 0; - *x = s->img_x; *y = s->img_y; - if (comp) *comp = s->img_n; + *comp = s->img_n; - if (!stbi__mad3sizes_valid(s->img_n, s->img_x, s->img_y, 0)) - return stbi__errpuc("too large", "PNM too large"); - - out = (stbi_uc *) stbi__malloc_mad3(s->img_n, s->img_x, s->img_y, 0); + out = (stbi_uc *) stbi__malloc(s->img_n * s->img_x * s->img_y); if (!out) return stbi__errpuc("outofmem", "Out of memory"); stbi__getn(s, out, s->img_n * s->img_x * s->img_y); @@ -7263,20 +6343,16 @@ static int stbi__pnm_getinteger(stbi__context *s, char *c) static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) { - int maxv, dummy; + int maxv; char c, p, t; - if (!x) x = &dummy; - if (!y) y = &dummy; - if (!comp) comp = &dummy; - - stbi__rewind(s); + stbi__rewind( s ); // Get identifier p = (char) stbi__get8(s); t = (char) stbi__get8(s); if (p != 'P' || (t != '5' && t != '6')) { - stbi__rewind(s); + stbi__rewind( s ); return 0; } @@ -7342,19 +6418,6 @@ static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) return stbi__err("unknown image type", "Image not of any known type, or corrupt"); } -static int stbi__is_16_main(stbi__context *s) -{ - #ifndef STBI_NO_PNG - if (stbi__png_is16(s)) return 1; - #endif - - #ifndef STBI_NO_PSD - if (stbi__psd_is16(s)) return 1; - #endif - - return 0; -} - #ifndef STBI_NO_STDIO STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) { @@ -7376,27 +6439,6 @@ STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) fseek(f,pos,SEEK_SET); return r; } - -STBIDEF int stbi_is_16_bit(char const *filename) -{ - FILE *f = stbi__fopen(filename, "rb"); - int result; - if (!f) return stbi__err("can't fopen", "Unable to open file"); - result = stbi_is_16_bit_from_file(f); - fclose(f); - return result; -} - -STBIDEF int stbi_is_16_bit_from_file(FILE *f) -{ - int r; - stbi__context s; - long pos = ftell(f); - stbi__start_file(&s, f); - r = stbi__is_16_main(&s); - fseek(f,pos,SEEK_SET); - return r; -} #endif // !STBI_NO_STDIO STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) @@ -7413,51 +6455,10 @@ STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int return stbi__info_main(&s,x,y,comp); } -STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) -{ - stbi__context s; - stbi__start_mem(&s,buffer,len); - return stbi__is_16_main(&s); -} - -STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) -{ - stbi__context s; - stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); - return stbi__is_16_main(&s); -} - #endif // STB_IMAGE_IMPLEMENTATION /* revision history: - 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs - 2.19 (2018-02-11) fix warning - 2.18 (2018-01-30) fix warnings - 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug - 1-bit BMP - *_is_16_bit api - avoid warnings - 2.16 (2017-07-23) all functions have 16-bit variants; - STBI_NO_STDIO works again; - compilation fixes; - fix rounding in unpremultiply; - optimize vertical flip; - disable raw_len validation; - documentation fixes - 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; - warning fixes; disable run-time SSE detection on gcc; - uniform handling of optional "return" values; - thread-safe initialization of zlib tables - 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs - 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now - 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes - 2.11 (2016-04-02) allocate large structures on the stack - remove white matting for transparent PSD - fix reported channel count for PNG & BMP - re-enable SSE2 in non-gcc 64-bit - support RGB-formatted JPEG - read 16-bit PNGs (only as 8-bit) 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED 2.09 (2016-01-16) allow comments in PNM files 16-bit-per-pixel TGA (not bit-per-component) @@ -7611,46 +6612,3 @@ STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user 0.50 (2006-11-19) first released version */ - - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/dgl/src/pugl.cpp b/dgl/src/pugl.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -16,20 +16,32 @@ #include "pugl.hpp" +// -------------------------------------------------------------------------------------------------------------------- +// include base headers + +#ifdef DGL_CAIRO +# include <cairo.h> +#endif +#ifdef DGL_OPENGL +# include "../OpenGL-include.hpp" +#endif +#ifdef DGL_VULKAN +# include <vulkan/vulkan_core.h> +#endif + /* we will include all header files used in pugl in their C++ friendly form, then pugl stuff in custom namespace */ #include <cassert> #include <cmath> #include <cstdlib> +#include <cstdio> #include <cstring> #include <ctime> -#if defined(DISTRHO_OS_HAIKU) -#elif defined(DISTRHO_OS_MAC) +#if defined(DISTRHO_OS_MAC) # import <Cocoa/Cocoa.h> # include <dlfcn.h> # include <mach/mach_time.h> # ifdef DGL_CAIRO -# include <cairo.h> # include <cairo-quartz.h> # endif # ifdef DGL_OPENGL @@ -37,15 +49,20 @@ # endif # ifdef DGL_VULKAN # import <QuartzCore/CAMetalLayer.h> -# include <vulkan/vulkan_core.h> # include <vulkan/vulkan_macos.h> # endif +#elif defined(DISTRHO_OS_WASM) +# include <emscripten/emscripten.h> +# include <emscripten/html5.h> +# ifdef DGL_OPENGL +# include <EGL/egl.h> +# endif #elif defined(DISTRHO_OS_WINDOWS) # include <wctype.h> +# include <winsock2.h> # include <windows.h> # include <windowsx.h> # ifdef DGL_CAIRO -# include <cairo.h> # include <cairo-win32.h> # endif # ifdef DGL_OPENGL @@ -55,10 +72,12 @@ # include <vulkan/vulkan.h> # include <vulkan/vulkan_win32.h> # endif -#else +#elif defined(HAVE_X11) # include <dlfcn.h> +# include <limits.h> +# include <unistd.h> # include <sys/select.h> -# include <sys/time.h> +// # include <sys/time.h> # include <X11/X.h> # include <X11/Xatom.h> # include <X11/Xlib.h> @@ -67,7 +86,7 @@ # include <X11/keysym.h> # ifdef HAVE_XCURSOR # include <X11/Xcursor/Xcursor.h> -# include <X11/cursorfont.h> +// # include <X11/cursorfont.h> # endif # ifdef HAVE_XRANDR # include <X11/extensions/Xrandr.h> @@ -77,23 +96,24 @@ # include <X11/extensions/syncconst.h> # endif # ifdef DGL_CAIRO -# include <cairo.h> # include <cairo-xlib.h> # endif # ifdef DGL_OPENGL -# include <GL/gl.h> # include <GL/glx.h> # endif # ifdef DGL_VULKAN -# include <vulkan/vulkan_core.h> # include <vulkan/vulkan_xlib.h> # endif #endif -#ifdef HAVE_X11 -# define DBLCLKTME 400 -# include "sofd/libsofd.h" -# include "sofd/libsofd.c" +#ifndef DGL_FILE_BROWSER_DISABLED +# define FILE_BROWSER_DIALOG_DGL_NAMESPACE +# define FILE_BROWSER_DIALOG_NAMESPACE DGL_NAMESPACE +# define DGL_FILE_BROWSER_DIALOG_HPP_INCLUDED +START_NAMESPACE_DGL +# include "../../distrho/extra/FileBrowserDialogImpl.hpp" +END_NAMESPACE_DGL +# include "../../distrho/extra/FileBrowserDialogImpl.cpp" #endif #ifndef DISTRHO_OS_MAC @@ -102,17 +122,17 @@ START_NAMESPACE_DGL // -------------------------------------------------------------------------------------------------------------------- -#if defined(DISTRHO_OS_HAIKU) -#elif defined(DISTRHO_OS_MAC) +#if defined(DISTRHO_OS_MAC) # ifndef DISTRHO_MACOS_NAMESPACE_MACRO # define DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(NS, SEP, INTERFACE) NS ## SEP ## INTERFACE # define DISTRHO_MACOS_NAMESPACE_MACRO(NS, INTERFACE) DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(NS, _, INTERFACE) -# define PuglStubView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglStubView) -# define PuglWrapperView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglWrapperView) -# define PuglWindow DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglWindow) -# endif -# ifndef __MAC_10_9 -# define NSModalResponseOK NSOKButton +# define PuglCairoView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglCairoView) +# define PuglOpenGLView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglOpenGLView) +# define PuglStubView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglStubView) +# define PuglVulkanView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglVulkanView) +# define PuglWindow DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglWindow) +# define PuglWindowDelegate DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglWindowDelegate) +# define PuglWrapperView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PuglWrapperView) # endif # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -128,6 +148,12 @@ START_NAMESPACE_DGL # import "pugl-upstream/src/mac_vulkan.m" # endif # pragma clang diagnostic pop +#elif defined(DISTRHO_OS_WASM) +# include "pugl-upstream/src/wasm.c" +# include "pugl-upstream/src/wasm_stub.c" +# ifdef DGL_OPENGL +# include "pugl-upstream/src/wasm_gl.c" +# endif #elif defined(DISTRHO_OS_WINDOWS) # include "pugl-upstream/src/win.c" # include "pugl-upstream/src/win_stub.c" @@ -140,7 +166,7 @@ START_NAMESPACE_DGL # ifdef DGL_VULKAN # include "pugl-upstream/src/win_vulkan.c" # endif -#else +#elif defined(HAVE_X11) # include "pugl-upstream/src/x11.c" # include "pugl-upstream/src/x11_stub.c" # ifdef DGL_CAIRO @@ -154,119 +180,41 @@ START_NAMESPACE_DGL # endif #endif -#include "pugl-upstream/src/implementation.c" +#include "pugl-upstream/src/common.c" +#include "pugl-upstream/src/internal.c" // -------------------------------------------------------------------------------------------------------------------- -// expose backend enter +// DGL specific, expose backend enter bool puglBackendEnter(PuglView* const view) { - return view->backend->enter(view, NULL) == PUGL_SUCCESS; + return view->backend->enter(view, nullptr) == PUGL_SUCCESS; } // -------------------------------------------------------------------------------------------------------------------- -// expose backend leave +// DGL specific, expose backend leave -void puglBackendLeave(PuglView* const view) +bool puglBackendLeave(PuglView* const view) { - view->backend->leave(view, NULL); + return view->backend->leave(view, nullptr) == PUGL_SUCCESS; } // -------------------------------------------------------------------------------------------------------------------- -// clear minimum size to 0 - -void puglClearMinSize(PuglView* const view) -{ - view->minWidth = 0; - view->minHeight = 0; -} +// DGL specific, assigns backend that matches current DGL build -// -------------------------------------------------------------------------------------------------------------------- -// missing in pugl, directly returns transient parent - -PuglNativeView puglGetTransientParent(const PuglView* const view) -{ - return view->transientParent; -} - -// -------------------------------------------------------------------------------------------------------------------- -// missing in pugl, directly returns title char* pointer - -const char* puglGetWindowTitle(const PuglView* const view) -{ - return view->title; -} - -// -------------------------------------------------------------------------------------------------------------------- -// get global scale factor - -double puglGetDesktopScaleFactor(const PuglView* const view) +void puglSetMatchingBackendForCurrentBuild(PuglView* const view) { -#if defined(DISTRHO_OS_MAC) - if (NSWindow* const window = view->impl->window ? view->impl->window - : [view->impl->wrapperView window]) - return [window screen].backingScaleFactor; - return [NSScreen mainScreen].backingScaleFactor; -#elif defined(DISTRHO_OS_WINDOWS) - if (const HMODULE Shcore = LoadLibraryA("Shcore.dll")) - { - typedef HRESULT(WINAPI* PFN_GetProcessDpiAwareness)(HANDLE, DWORD*); - typedef HRESULT(WINAPI* PFN_GetScaleFactorForMonitor)(HMONITOR, DWORD*); - -# if defined(__GNUC__) && (__GNUC__ >= 9) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-function-type" -# endif - const PFN_GetProcessDpiAwareness GetProcessDpiAwareness - = (PFN_GetProcessDpiAwareness)GetProcAddress(Shcore, "GetProcessDpiAwareness"); - const PFN_GetScaleFactorForMonitor GetScaleFactorForMonitor - = (PFN_GetScaleFactorForMonitor)GetProcAddress(Shcore, "GetScaleFactorForMonitor"); -# if defined(__GNUC__) && (__GNUC__ >= 9) -# pragma GCC diagnostic pop -# endif - - DWORD dpiAware = 0; - if (GetProcessDpiAwareness && GetScaleFactorForMonitor - && GetProcessDpiAwareness(NULL, &dpiAware) == 0 && dpiAware != 0) - { - const HMONITOR hMon = MonitorFromWindow(view->impl->hwnd, MONITOR_DEFAULTTOPRIMARY); - - DWORD scaleFactor = 0; - if (GetScaleFactorForMonitor(hMon, &scaleFactor) == 0 && scaleFactor != 0) - { - FreeLibrary(Shcore); - return static_cast<double>(scaleFactor) / 100.0; - } - } - - FreeLibrary(Shcore); - } -#elif defined(HAVE_X11) - XrmInitialize(); - - if (char* const rms = XResourceManagerString(view->world->impl->display)) - { - if (const XrmDatabase sdb = XrmGetStringDatabase(rms)) - { - char* type = nullptr; - XrmValue ret; - - if (XrmGetResource(sdb, "Xft.dpi", "String", &type, &ret) - && ret.addr != nullptr - && type != nullptr - && std::strncmp("String", type, 6) == 0) - { - if (const double dpi = std::atof(ret.addr)) - return dpi / 96; - } - } - } -#else - // unused - (void)view; +#ifdef DGL_CAIRO + puglSetBackend(view, puglCairoBackend()); #endif - - return 1.0; +#ifdef DGL_OPENGL + puglSetBackend(view, puglGlBackend()); +#endif +#ifdef DGL_VULKAN + puglSetBackend(view, puglVulkanBackend()); +#endif + if (view->backend == nullptr) + puglSetBackend(view, puglStubBackend()); } // -------------------------------------------------------------------------------------------------------------------- @@ -274,141 +222,173 @@ double puglGetDesktopScaleFactor(const PuglView* const view) void puglRaiseWindow(PuglView* const view) { -#if defined(DISTRHO_OS_HAIKU) - // nothing here yet -#elif defined(DISTRHO_OS_MAC) +#if defined(DISTRHO_OS_MAC) if (NSWindow* const window = view->impl->window ? view->impl->window : [view->impl->wrapperView window]) [window orderFrontRegardless]; +#elif defined(DISTRHO_OS_WASM) + // nothing #elif defined(DISTRHO_OS_WINDOWS) SetForegroundWindow(view->impl->hwnd); SetActiveWindow(view->impl->hwnd); -#else - XRaiseWindow(view->impl->display, view->impl->win); +#elif defined(HAVE_X11) + XRaiseWindow(view->world->impl->display, view->impl->win); #endif } // -------------------------------------------------------------------------------------------------------------------- -// set backend that matches current build +// get scale factor from parent window if possible, fallback to puglGetScaleFactor -void puglSetMatchingBackendForCurrentBuild(PuglView* const view) +double puglGetScaleFactorFromParent(const PuglView* const view) { -#ifdef DGL_CAIRO - puglSetBackend(view, puglCairoBackend()); -#endif -#ifdef DGL_OPENGL - puglSetBackend(view, puglGlBackend()); -#endif -#ifdef DGL_VULKAN - puglSetBackend(view, puglVulkanBackend()); + const PuglNativeView parent = view->parent ? view->parent : view->transientParent ? view->transientParent : 0; +#if defined(DISTRHO_OS_MAC) + // some of these can return 0 as backingScaleFactor, pick the most relevant valid one + const NSWindow* possibleWindows[] = { + parent != 0 ? [(NSView*)parent window] : nullptr, + view->impl->window, + [view->impl->wrapperView window] + }; + for (size_t i=0; i<ARRAY_SIZE(possibleWindows); ++i) + { + if (possibleWindows[i] == nullptr) + continue; + if (const double scaleFactor = [[possibleWindows[i] screen] backingScaleFactor]) + return scaleFactor; + } + return [[NSScreen mainScreen] backingScaleFactor]; +#elif defined(DISTRHO_OS_WINDOWS) + const HWND hwnd = parent != 0 ? (HWND)parent : view->impl->hwnd; + return puglWinGetViewScaleFactor(hwnd); +#else + return puglGetScaleFactor(view); + // unused + (void)parent; #endif - if (view->backend == nullptr) - puglSetBackend(view, puglStubBackend()); } // -------------------------------------------------------------------------------------------------------------------- -// Combine puglSetMinSize and puglSetAspectRatio +// Combined puglSetSizeHint using PUGL_MIN_SIZE and PUGL_FIXED_ASPECT PuglStatus puglSetGeometryConstraints(PuglView* const view, const uint width, const uint height, const bool aspect) { - view->minWidth = (int)width; - view->minHeight = (int)height; - - if (aspect) { - view->minAspectX = (int)width; - view->minAspectY = (int)height; - view->maxAspectX = (int)width; - view->maxAspectY = (int)height; + view->sizeHints[PUGL_MIN_SIZE].width = width; + view->sizeHints[PUGL_MIN_SIZE].height = height; + + if (aspect) + { + view->sizeHints[PUGL_FIXED_ASPECT].width = width; + view->sizeHints[PUGL_FIXED_ASPECT].height = height; } -#if defined(DISTRHO_OS_HAIKU) - // nothing? -#elif defined(DISTRHO_OS_MAC) - /* +#if defined(DISTRHO_OS_MAC) if (view->impl->window) { - [view->impl->window setContentMinSize:sizePoints(view, view->minWidth, view->minHeight)]; + PuglStatus status; + + if ((status = updateSizeHint(view, PUGL_MIN_SIZE)) != PUGL_SUCCESS) + return status; - if (aspect) - [view->impl->window setContentAspectRatio:sizePoints(view, view->minAspectX, view->minAspectY)]; + if (aspect && (status = updateSizeHint(view, PUGL_FIXED_ASPECT)) != PUGL_SUCCESS) + return status; } - */ - puglSetMinSize(view, width, height); - puglSetAspectRatio(view, width, height, width, height); +#elif defined(DISTRHO_OS_WASM) + // nothing #elif defined(DISTRHO_OS_WINDOWS) // nothing -#else +#elif defined(HAVE_X11) if (const PuglStatus status = updateSizeHints(view)) return status; - XFlush(view->impl->display); + XFlush(view->world->impl->display); #endif return PUGL_SUCCESS; } // -------------------------------------------------------------------------------------------------------------------- -// set window size with default size and without changing frame x/y position +// set view as resizable (or not) during runtime -PuglStatus puglSetWindowSize(PuglView* const view, const uint width, const uint height) +void puglSetResizable(PuglView* const view, const bool resizable) { - view->defaultWidth = width; - view->defaultHeight = height; + puglSetViewHint(view, PUGL_RESIZABLE, resizable ? PUGL_TRUE : PUGL_FALSE); -#if defined(DISTRHO_OS_HAIKU) || defined(DISTRHO_OS_MAC) - // replace the 2 views setFrame with setFrameSize - const PuglRect frame = { view->frame.x, view->frame.y, (double)width, (double)height }; - PuglInternals* const impl = view->impl; +#if defined(DISTRHO_OS_MAC) + if (PuglWindow* const window = view->impl->window) + { + const uint style = (NSClosableWindowMask | NSTitledWindowMask | NSMiniaturizableWindowMask) + | (resizable ? NSResizableWindowMask : 0x0); + [window setStyleMask:style]; + } + // FIXME use [view setAutoresizingMask:NSViewNotSizable] ? +#elif defined(DISTRHO_OS_WASM) + // nothing +#elif defined(DISTRHO_OS_WINDOWS) + if (const HWND hwnd = view->impl->hwnd) + { + const uint winFlags = resizable ? GetWindowLong(hwnd, GWL_STYLE) | (WS_SIZEBOX | WS_MAXIMIZEBOX) + : GetWindowLong(hwnd, GWL_STYLE) & ~(WS_SIZEBOX | WS_MAXIMIZEBOX); + SetWindowLong(hwnd, GWL_STYLE, winFlags); + } +#elif defined(HAVE_X11) + updateSizeHints(view); +#endif +} - // Update view frame to exactly the requested frame in Pugl coordinates - view->frame = frame; +// -------------------------------------------------------------------------------------------------------------------- +// set window size while also changing default +PuglStatus puglSetSizeAndDefault(PuglView* view, uint width, uint height) +{ + if (width > INT16_MAX || height > INT16_MAX) + return PUGL_BAD_PARAMETER; + + view->sizeHints[PUGL_DEFAULT_SIZE].width = view->frame.width = static_cast<PuglSpan>(width); + view->sizeHints[PUGL_DEFAULT_SIZE].height = view->frame.height = static_cast<PuglSpan>(height); + +#if defined(DISTRHO_OS_MAC) + // mostly matches upstream pugl, simplified + PuglInternals* const impl = view->impl; + + const PuglRect frame = view->frame; const NSRect framePx = rectToNsRect(frame); const NSRect framePt = nsRectToPoints(view, framePx); - if (impl->window) + + if (PuglWindow* const window = view->impl->window) { - // Resize window to fit new content rect const NSRect screenPt = rectToScreen(viewScreen(view), framePt); - const NSRect winFrame = [impl->window frameRectForContentRect:screenPt]; - - [impl->window setFrame:winFrame display:NO]; + const NSRect winFrame = [window frameRectForContentRect:screenPt]; + [window setFrame:winFrame display:NO]; } - // Resize views const NSSize sizePx = NSMakeSize(frame.width, frame.height); const NSSize sizePt = [impl->drawView convertSizeFromBacking:sizePx]; - - [impl->wrapperView setFrameSize:(impl->window ? sizePt : framePt.size)]; + [impl->wrapperView setFrameSize:sizePt]; [impl->drawView setFrameSize:sizePt]; +#elif defined(DISTRHO_OS_WASM) + d_stdout("className is %s", view->world->className); + emscripten_set_canvas_element_size(view->world->className, width, height); #elif defined(DISTRHO_OS_WINDOWS) - // matches upstream pugl, except we add SWP_NOMOVE flag - if (view->impl->hwnd) + // matches upstream pugl, except we re-enter context after resize + if (const HWND hwnd = view->impl->hwnd) { - const PuglRect frame = view->frame; - - RECT rect = { (long)frame.x, - (long)frame.y, - (long)frame.x + (long)frame.width, - (long)frame.y + (long)frame.height }; - - AdjustWindowRectEx(&rect, puglWinGetWindowFlags(view), FALSE, puglWinGetWindowExFlags(view)); - - if (! SetWindowPos(view->impl->hwnd, - HWND_TOP, - rect.left, - rect.top, - rect.right - rect.left, - rect.bottom - rect.top, - SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER)) + const RECT rect = adjustedWindowRect(view, view->frame.x, view->frame.y, + static_cast<long>(width), static_cast<long>(height)); + + if (!SetWindowPos(hwnd, HWND_TOP, 0, 0, rect.right - rect.left, rect.bottom - rect.top, + SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOMOVE)) return PUGL_UNKNOWN_ERROR; + + // make sure to return context back to ourselves + puglBackendEnter(view); } -#else - // matches upstream pugl, except we use XResizeWindow instead of XMoveResizeWindow - if (view->impl->win) +#elif defined(HAVE_X11) + // matches upstream pugl, all in one + if (const Window window = view->impl->win) { - Display* const display = view->impl->display; + Display* const display = view->world->impl->display; - if (! XResizeWindow(display, view->impl->win, width, height)) + if (! XResizeWindow(display, window, width, height)) return PUGL_UNKNOWN_ERROR; if (const PuglStatus status = updateSizeHints(view)) @@ -418,8 +398,6 @@ PuglStatus puglSetWindowSize(PuglView* const view, const uint width, const uint } #endif - view->frame.width = width; - view->frame.height = height; return PUGL_SUCCESS; } @@ -430,7 +408,9 @@ void puglOnDisplayPrepare(PuglView*) { #ifdef DGL_OPENGL glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +# ifndef DGL_USE_GLES glLoadIdentity(); +# endif #endif } @@ -442,24 +422,26 @@ void puglFallbackOnResize(PuglView* const view) #ifdef DGL_OPENGL glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +# ifndef DGL_USE_GLES glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0, static_cast<GLdouble>(view->frame.width), static_cast<GLdouble>(view->frame.height), 0.0, 0.0, 1.0); glViewport(0, 0, static_cast<GLsizei>(view->frame.width), static_cast<GLsizei>(view->frame.height)); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); +# else + glViewport(0, 0, static_cast<GLsizei>(view->frame.width), static_cast<GLsizei>(view->frame.height)); +# endif +#else + return; + // unused + (void)view; #endif } -#ifdef DISTRHO_OS_MAC // -------------------------------------------------------------------------------------------------------------------- -// macOS specific, allow standalone window to gain focus -void puglMacOSActivateApp() -{ - [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; - [NSApp activateIgnoringOtherApps:YES]; -} +#if defined(DISTRHO_OS_MAC) // -------------------------------------------------------------------------------------------------------------------- // macOS specific, add another view's window as child @@ -517,8 +499,8 @@ void puglMacOSShowCentered(PuglView* const view) const NSRect ourFrame = [view->impl->window frame]; const NSRect transientFrame = [transientWindow frame]; - const int x = transientFrame.origin.x + transientFrame.size.width / 2 - ourFrame.size.width / 2; - const int y = transientFrame.origin.y + transientFrame.size.height / 2 + ourFrame.size.height / 2; + const int x = transientFrame.origin.x + (transientFrame.size.width - ourFrame.size.width) / 2; + const int y = transientFrame.origin.y + (transientFrame.size.height - ourFrame.size.height) / 2; [view->impl->window setFrameTopLeftPoint:NSMakePoint(x, y)]; } @@ -529,53 +511,9 @@ void puglMacOSShowCentered(PuglView* const view) } // -------------------------------------------------------------------------------------------------------------------- -// macOS specific, setup file browser dialog - -bool puglMacOSFilePanelOpen(PuglView* const view, - const char* const startDir, const char* const title, const uint flags, - openPanelCallback callback) -{ - PuglInternals* impl = view->impl; - - NSOpenPanel* const panel = [NSOpenPanel openPanel]; - - [panel setAllowsMultipleSelection:NO]; - [panel setCanChooseFiles:YES]; - [panel setCanChooseDirectories:NO]; - [panel setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:startDir]]]; - - // TODO file filter using allowedContentTypes: [UTType] - - if (flags & 0x001) - [panel setAllowsOtherFileTypes:YES]; - if (flags & 0x010) - [panel setShowsHiddenFiles:YES]; - - NSString* titleString = [[NSString alloc] - initWithBytes:title - length:strlen(title) - encoding:NSUTF8StringEncoding]; - [panel setTitle:titleString]; - [panel beginSheetModalForWindow:(impl->window ? impl->window : [view->impl->wrapperView window]) - completionHandler:^(NSInteger result) - { - if (result == NSModalResponseOK && [[panel URL] isFileURL]) - { - NSString* const path = [[panel URL] path]; - callback(view, [path UTF8String]); - } - else - { - callback(view, nullptr); - } - }]; - - return true; -} -#endif +#elif defined(DISTRHO_OS_WINDOWS) -#ifdef DISTRHO_OS_WINDOWS // -------------------------------------------------------------------------------------------------------------------- // win32 specific, call ShowWindow with SW_RESTORE @@ -609,165 +547,92 @@ void puglWin32ShowCentered(PuglView* const view) } else { - ShowWindow(impl->hwnd, SW_SHOWNORMAL); - } +#ifdef DGL_WINDOWS_ICON_ID + WNDCLASSEX wClass; + std::memset(&wClass, 0, sizeof(wClass)); - SetFocus(impl->hwnd); -} + const HINSTANCE hInstance = GetModuleHandle(nullptr); -// -------------------------------------------------------------------------------------------------------------------- -// win32 specific, set or unset WS_SIZEBOX style flag - -void puglWin32SetWindowResizable(PuglView* const view, const bool resizable) -{ - PuglInternals* impl = view->impl; - DISTRHO_SAFE_ASSERT_RETURN(impl->hwnd != nullptr,); - - const int winFlags = resizable ? GetWindowLong(impl->hwnd, GWL_STYLE) | WS_SIZEBOX - : GetWindowLong(impl->hwnd, GWL_STYLE) & ~WS_SIZEBOX; - SetWindowLong(impl->hwnd, GWL_STYLE, winFlags); -} + if (GetClassInfoEx(hInstance, view->world->className, &wClass)) + wClass.hIcon = LoadIcon(nullptr, MAKEINTRESOURCE(DGL_WINDOWS_ICON_ID)); -// -------------------------------------------------------------------------------------------------------------------- + SetClassLongPtr(impl->hwnd, GCLP_HICON, (LONG_PTR) LoadIcon(hInstance, MAKEINTRESOURCE(DGL_WINDOWS_ICON_ID))); #endif -#ifdef HAVE_X11 -// -------------------------------------------------------------------------------------------------------------------- -// X11 specific, safer way to grab focus - -PuglStatus puglX11GrabFocus(const PuglView* const view) -{ - const PuglInternals* const impl = view->impl; - - XWindowAttributes wa; - std::memset(&wa, 0, sizeof(wa)); - - DISTRHO_SAFE_ASSERT_RETURN(XGetWindowAttributes(impl->display, impl->win, &wa), PUGL_UNKNOWN_ERROR); + MONITORINFO mInfo; + std::memset(&mInfo, 0, sizeof(mInfo)); + mInfo.cbSize = sizeof(mInfo); - if (wa.map_state == IsViewable) - { - XRaiseWindow(impl->display, impl->win); - XSetInputFocus(impl->display, impl->win, RevertToPointerRoot, CurrentTime); - XSync(impl->display, False); + if (GetMonitorInfo(MonitorFromWindow(impl->hwnd, MONITOR_DEFAULTTOPRIMARY), &mInfo)) + SetWindowPos(impl->hwnd, + HWND_TOP, + mInfo.rcWork.left + (mInfo.rcWork.right - mInfo.rcWork.left - view->frame.width) / 2, + mInfo.rcWork.top + (mInfo.rcWork.bottom - mInfo.rcWork.top - view->frame.height) / 2, + 0, 0, SWP_SHOWWINDOW|SWP_NOSIZE); + else + ShowWindow(impl->hwnd, SW_NORMAL); } - return PUGL_SUCCESS; + SetFocus(impl->hwnd); } // -------------------------------------------------------------------------------------------------------------------- -// X11 specific, set dialog window type and pid hints -void puglX11SetWindowTypeAndPID(const PuglView* const view) -{ - const PuglInternals* const impl = view->impl; +#elif defined(DISTRHO_OS_WASM) - const pid_t pid = getpid(); - const Atom _nwp = XInternAtom(impl->display, "_NET_WM_PID", False); - XChangeProperty(impl->display, impl->win, _nwp, XA_CARDINAL, 32, PropModeReplace, (const uchar*)&pid, 1); - - const Atom _wt = XInternAtom(impl->display, "_NET_WM_WINDOW_TYPE", False); - - // Setting the window to both dialog and normal will produce a decorated floating dialog - // Order is important: DIALOG needs to come before NORMAL - const Atom _wts[2] = { - XInternAtom(impl->display, "_NET_WM_WINDOW_TYPE_DIALOG", False), - XInternAtom(impl->display, "_NET_WM_WINDOW_TYPE_NORMAL", False) - }; - - XChangeProperty(impl->display, impl->win, _wt, XA_ATOM, 32, PropModeReplace, (const uchar*)&_wts, 2); -} +// nothing here yet // -------------------------------------------------------------------------------------------------------------------- -// X11 specific stuff for sofd - -static Display* sofd_display; -static char* sofd_filename; -// -------------------------------------------------------------------------------------------------------------------- -// X11 specific, show file dialog via sofd +#elif defined(HAVE_X11) -bool sofdFileDialogShow(PuglView* const view, - const char* const startDir, const char* const title, - const uint flags, const double scaleFactor) +PuglStatus puglX11UpdateWithoutExposures(PuglWorld* const world) { - // only one possible at a time - DISTRHO_SAFE_ASSERT_RETURN(sofd_display == nullptr, false); + const bool wasDispatchingEvents = world->impl->dispatchingEvents; + world->impl->dispatchingEvents = true; + PuglStatus st = PUGL_SUCCESS; - sofd_display = XOpenDisplay(nullptr); - DISTRHO_SAFE_ASSERT_RETURN(sofd_display != nullptr, false); + const double startTime = puglGetTime(world); + const double endTime = startTime + 0.03; - DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(0, startDir) == 0, false); - DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(1, title) == 0, false); - - x_fib_cfg_buttons(3, flags & 0x001 ? 1 : flags & 0x002 ? 0 : -1); - x_fib_cfg_buttons(1, flags & 0x010 ? 1 : flags & 0x020 ? 0 : -1); - x_fib_cfg_buttons(2, flags & 0x100 ? 1 : flags & 0x200 ? 0 : -1); + for (double t = startTime; !st && t < endTime; t = puglGetTime(world)) + { + pollX11Socket(world, endTime - t); + st = dispatchX11Events(world); + } - return (x_fib_show(sofd_display, view->impl->win, 0, 0, scaleFactor + 0.5) == 0); + world->impl->dispatchingEvents = wasDispatchingEvents; + return st; } // -------------------------------------------------------------------------------------------------------------------- -// X11 specific, idle sofd file dialog, returns true if dialog was closed (with or without a file selection) +// X11 specific, set dialog window type and pid hints -bool sofdFileDialogIdle(PuglView* const view) +void puglX11SetWindowTypeAndPID(const PuglView* const view, const bool isStandalone) { - if (sofd_display == nullptr) - return false; + const PuglInternals* const impl = view->impl; + Display* const display = view->world->impl->display; - XEvent event; - while (XPending(sofd_display) > 0) - { - XNextEvent(sofd_display, &event); - - if (x_fib_handle_events(sofd_display, &event) == 0) - continue; - - if (sofd_filename != nullptr) - std::free(sofd_filename); - - if (x_fib_status() > 0) - sofd_filename = x_fib_filename(); - else - sofd_filename = nullptr; + const pid_t pid = getpid(); + const Atom _nwp = XInternAtom(display, "_NET_WM_PID", False); + XChangeProperty(display, impl->win, _nwp, XA_CARDINAL, 32, PropModeReplace, (const uchar*)&pid, 1); - x_fib_close(sofd_display); - XCloseDisplay(sofd_display); - sofd_display = nullptr; - return true; - } + const Atom _wt = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False); - return false; -} + Atom _wts[2]; + int numAtoms = 0; -// -------------------------------------------------------------------------------------------------------------------- -// X11 specific, close sofd file dialog + if (! isStandalone) + _wts[numAtoms++] = XInternAtom(display, "_NET_WM_WINDOW_TYPE_DIALOG", False); -void sofdFileDialogClose() -{ - if (sofd_display != nullptr) - { - x_fib_close(sofd_display); - XCloseDisplay(sofd_display); - sofd_display = nullptr; - } + _wts[numAtoms++] = XInternAtom(display, "_NET_WM_WINDOW_TYPE_NORMAL", False); - if (sofd_filename != nullptr) - { - std::free(sofd_filename); - sofd_filename = nullptr; - } + XChangeProperty(display, impl->win, _wt, XA_ATOM, 32, PropModeReplace, (const uchar*)&_wts, numAtoms); } // -------------------------------------------------------------------------------------------------------------------- -// X11 specific, get path chosen via sofd file dialog - -const char* sofdFileDialogGetPath() -{ - return sofd_filename; -} -#endif -// -------------------------------------------------------------------------------------------------------------------- +#endif // HAVE_X11 #ifndef DISTRHO_OS_MAC END_NAMESPACE_DGL diff --git a/dgl/src/pugl.hpp b/dgl/src/pugl.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -19,7 +19,7 @@ #include "../Base.hpp" -/* we will include all header files used in pugl in their C++ friendly form, then pugl stuff in custom namespace */ +/* we will include all header files used in pugl.h in their C++ friendly form, then pugl stuff in custom namespace */ #include <cstddef> #ifdef DISTRHO_PROPER_CPP11_SUPPORT # include <cstdbool> @@ -29,136 +29,85 @@ # include <stdint.h> #endif +// hidden api #define PUGL_API #define PUGL_DISABLE_DEPRECATED +#define PUGL_NO_INCLUDE_GL_H #define PUGL_NO_INCLUDE_GLU_H -// -------------------------------------------------------------------------------------------------------------------- - #ifndef DISTRHO_OS_MAC START_NAMESPACE_DGL -#else -USE_NAMESPACE_DGL #endif #include "pugl-upstream/include/pugl/pugl.h" // -------------------------------------------------------------------------------------------------------------------- -PUGL_BEGIN_DECLS - -// expose backend enter -PUGL_API bool -puglBackendEnter(PuglView* view); - -// expose backend leave -PUGL_API void -puglBackendLeave(PuglView* view); +// DGL specific, expose backend enter +bool puglBackendEnter(PuglView* view); -// clear minimum size to 0 -PUGL_API void -puglClearMinSize(PuglView* view); +// DGL specific, expose backend leave +bool puglBackendLeave(PuglView* view); -// missing in pugl, directly returns transient parent -PUGL_API PuglNativeView -puglGetTransientParent(const PuglView* view); - -// missing in pugl, directly returns title char* pointer -PUGL_API const char* -puglGetWindowTitle(const PuglView* view); - -// get global scale factor -PUGL_API double -puglGetDesktopScaleFactor(const PuglView* view); +// DGL specific, assigns backend that matches current DGL build +void puglSetMatchingBackendForCurrentBuild(PuglView* view); // bring view window into the foreground, aka "raise" window -PUGL_API void -puglRaiseWindow(PuglView* view); +void puglRaiseWindow(PuglView* view); -// DGL specific, assigns backend that matches current DGL build -PUGL_API void -puglSetMatchingBackendForCurrentBuild(PuglView* view); +// get scale factor from parent window if possible, fallback to puglGetScaleFactor +double puglGetScaleFactorFromParent(const PuglView* view); + +// combined puglSetSizeHint using PUGL_MIN_SIZE, PUGL_MIN_ASPECT and PUGL_MAX_ASPECT +PuglStatus puglSetGeometryConstraints(PuglView* view, uint width, uint height, bool aspect); -// Combine puglSetMinSize and puglSetAspectRatio -PUGL_API PuglStatus -puglSetGeometryConstraints(PuglView* view, unsigned int width, unsigned int height, bool aspect); +// set view as resizable (or not) during runtime +void puglSetResizable(PuglView* view, bool resizable); -// set window size with default size and without changing frame x/y position -PUGL_API PuglStatus -puglSetWindowSize(PuglView* view, unsigned int width, unsigned int height); +// set window size while also changing default +PuglStatus puglSetSizeAndDefault(PuglView* view, uint width, uint height); // DGL specific, build-specific drawing prepare -PUGL_API void -puglOnDisplayPrepare(PuglView* view); +void puglOnDisplayPrepare(PuglView* view); // DGL specific, build-specific fallback resize -PUGL_API void -puglFallbackOnResize(PuglView* view); +void puglFallbackOnResize(PuglView* view); -#ifdef DISTRHO_OS_MAC -// macOS specific, allow standalone window to gain focus -PUGL_API void -puglMacOSActivateApp(); +#if defined(DISTRHO_OS_MAC) // macOS specific, add another view's window as child -PUGL_API PuglStatus -puglMacOSAddChildWindow(PuglView* view, PuglView* child); +PuglStatus puglMacOSAddChildWindow(PuglView* view, PuglView* child); // macOS specific, remove another view's window as child -PUGL_API PuglStatus -puglMacOSRemoveChildWindow(PuglView* view, PuglView* child); +PuglStatus puglMacOSRemoveChildWindow(PuglView* view, PuglView* child); // macOS specific, center view based on parent coordinates (if there is one) -PUGL_API void -puglMacOSShowCentered(PuglView* view); +void puglMacOSShowCentered(PuglView* view); -// macOS specific, setup file browser dialog -typedef void (*openPanelCallback)(PuglView* view, const char* path); -bool puglMacOSFilePanelOpen(PuglView* view, const char* startDir, const char* title, uint flags, openPanelCallback callback); -#endif +#elif defined(DISTRHO_OS_WASM) -#ifdef DISTRHO_OS_WINDOWS -// win32 specific, call ShowWindow with SW_RESTORE -PUGL_API void -puglWin32RestoreWindow(PuglView* view); +// nothing here yet -// win32 specific, center view based on parent coordinates (if there is one) -PUGL_API void -puglWin32ShowCentered(PuglView* view); +#elif defined(DISTRHO_OS_WINDOWS) -// win32 specific, set or unset WS_SIZEBOX style flag -PUGL_API void -puglWin32SetWindowResizable(PuglView* view, bool resizable); -#endif +// win32 specific, call ShowWindow with SW_RESTORE +void puglWin32RestoreWindow(PuglView* view); -#ifdef HAVE_X11 -// X11 specific, safer way to grab focus -PUGL_API PuglStatus -puglX11GrabFocus(const PuglView* view); +// win32 specific, center view based on parent coordinates (if there is one) +void puglWin32ShowCentered(PuglView* view); -// X11 specific, set dialog window type and pid hints -PUGL_API void -puglX11SetWindowTypeAndPID(const PuglView* view); +#elif defined(HAVE_X11) -// X11 specific, show file dialog via sofd -PUGL_API bool -sofdFileDialogShow(PuglView* view, const char* startDir, const char* title, uint flags, double scaleFactor); +#define DGL_USING_X11 -// X11 specific, idle sofd file dialog, returns true if dialog was closed (with or without a file selection) -PUGL_API bool -sofdFileDialogIdle(PuglView* const view); +// X11 specific, update world without triggering exposure evente +PuglStatus puglX11UpdateWithoutExposures(PuglWorld* world); -// X11 specific, close sofd file dialog -PUGL_API void -sofdFileDialogClose(); +// X11 specific, set dialog window type and pid hints +void puglX11SetWindowTypeAndPID(const PuglView* view, bool isStandalone); -// X11 specific, get path chosen via sofd file dialog -PUGL_API const char* -sofdFileDialogGetPath(); #endif -PUGL_END_DECLS - // -------------------------------------------------------------------------------------------------------------------- #ifndef DISTRHO_OS_MAC diff --git a/dgl/src/sofd/libsofd.c b/dgl/src/sofd/libsofd.c @@ -1,2472 +0,0 @@ -/* libSOFD - Simple Open File Dialog [for X11 without toolkit] - * - * Copyright (C) 2014 Robin Gareus <robin@gareus.org> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -/* Test and example: - * gcc -Wall -D SOFD_TEST -g -o sofd libsofd.c -lX11 - * - * public API documentation and example code at the bottom of this file - * - * This small lib may one day include openGL rendering and - * wayland window support, but not today. Today we celebrate - * 30 years of X11. - */ - -#ifdef SOFD_TEST -#define HAVE_X11 -#include "libsofd.h" -#endif - -#include <stdio.h> -#include <stdint.h> -#include <string.h> -#include <stdlib.h> -#include <unistd.h> -#include <libgen.h> -#include <time.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <assert.h> - -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wnarrowing" -#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wnarrowing" -#endif - -// shared 'recently used' implementation -// sadly, xbel does not qualify as simple. -// hence we use a simple format alike the -// gtk-bookmark list (one file per line) - -#define MAX_RECENT_ENTRIES 24 -#define MAX_RECENT_AGE (15552000) // 180 days (in sec) - -typedef struct { - char path[1024]; - time_t atime; -} FibRecentFile; - -static FibRecentFile *_recentlist = NULL; -static unsigned int _recentcnt = 0; -static uint8_t _recentlock = 0; - -static int fib_isxdigit (const char x) { - if ( - (x >= '0' && x <= '9') - || - (x >= 'a' && x <= 'f') - || - (x >= 'A' && x <= 'F') - ) return 1; - return 0; -} - -static void decode_3986 (char *str) { - int len = strlen (str); - int idx = 0; - while (idx + 2 < len) { - char *in = &str[idx]; - if (('%' == *in) && fib_isxdigit (in[1]) && fib_isxdigit (in[2])) { - char hexstr[3]; - hexstr[0] = in[1]; - hexstr[1] = in[2]; - hexstr[2] = 0; - long hex = strtol (hexstr, NULL, 16); - *in = hex; - memmove (&str[idx+1], &str[idx + 3], len - idx - 2); - len -= 2; - } - ++idx; - } -} - -static char *encode_3986 (const char *str) { - size_t alloc, newlen; - char *ns = NULL; - unsigned char in; - size_t i = 0; - size_t length; - - if (!str) return strdup (""); - - alloc = strlen (str) + 1; - newlen = alloc; - - ns = (char*) malloc (alloc); - - length = alloc; - while (--length) { - in = *str; - - switch (in) { - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - case 'a': case 'b': case 'c': case 'd': case 'e': - case 'f': case 'g': case 'h': case 'i': case 'j': - case 'k': case 'l': case 'm': case 'n': case 'o': - case 'p': case 'q': case 'r': case 's': case 't': - case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': - case 'A': case 'B': case 'C': case 'D': case 'E': - case 'F': case 'G': case 'H': case 'I': case 'J': - case 'K': case 'L': case 'M': case 'N': case 'O': - case 'P': case 'Q': case 'R': case 'S': case 'T': - case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': - case '_': case '~': case '.': case '-': - case '/': case ',': // XXX not in RFC3986 - ns[i++] = in; - break; - default: - newlen += 2; /* this'll become a %XX */ - if (newlen > alloc) { - alloc *= 2; - ns = (char*) realloc (ns, alloc); - } - snprintf (&ns[i], 4, "%%%02X", in); - i += 3; - break; - } - ++str; - } - ns[i] = 0; - return ns; -} - -void x_fib_free_recent () { - free (_recentlist); - _recentlist = NULL; - _recentcnt = 0; -} - -static int cmp_recent (const void *p1, const void *p2) { - FibRecentFile *a = (FibRecentFile*) p1; - FibRecentFile *b = (FibRecentFile*) p2; - if (a->atime == b->atime) return 0; - return a->atime < b->atime; -} - -int x_fib_add_recent (const char *path, time_t atime) { - unsigned int i; - struct stat fs; - if (_recentlock) { return -1; } - if (access (path, R_OK)) { - return -1; - } - if (stat (path, &fs)) { - return -1; - } - if (!S_ISREG (fs.st_mode)) { - return -1; - } - if (atime == 0) atime = time (NULL); - if (MAX_RECENT_AGE > 0 && atime + MAX_RECENT_AGE < time (NULL)) { - return -1; - } - - for (i = 0; i < _recentcnt; ++i) { - if (!strcmp (_recentlist[i].path, path)) { - if (_recentlist[i].atime < atime) { - _recentlist[i].atime = atime; - } - qsort (_recentlist, _recentcnt, sizeof(FibRecentFile), cmp_recent); - return _recentcnt; - } - } - _recentlist = (FibRecentFile*)realloc (_recentlist, (_recentcnt + 1) * sizeof(FibRecentFile)); - _recentlist[_recentcnt].atime = atime; - strcpy (_recentlist[_recentcnt].path, path); - qsort (_recentlist, _recentcnt + 1, sizeof(FibRecentFile), cmp_recent); - - if (_recentcnt >= MAX_RECENT_ENTRIES) { - return (_recentcnt); - } - return (++_recentcnt); -} - -#ifdef PATHSEP -#undef PATHSEP -#endif - -#ifdef PLATFORM_WINDOWS -#define DIRSEP '\\' -#else -#define DIRSEP '/' -#endif - -static void mkpath(const char *dir) { - char tmp[1024]; - char *p; - size_t len; - - snprintf (tmp, sizeof(tmp), "%s", dir); - len = strlen(tmp); - if (tmp[len - 1] == '/') - tmp[len - 1] = 0; - for (p = tmp + 1; *p; ++p) - if(*p == DIRSEP) { - *p = 0; -#ifdef PLATFORM_WINDOWS - mkdir(tmp); -#else - mkdir(tmp, 0755); -#endif - *p = DIRSEP; - } -#ifdef PLATFORM_WINDOWS - mkdir(tmp); -#else - mkdir(tmp, 0755); -#endif -} - -int x_fib_save_recent (const char *fn) { - if (_recentlock) { return -1; } - if (!fn) { return -1; } - if (_recentcnt < 1 || !_recentlist) { return -1; } - unsigned int i; - char *dn = strdup (fn); - mkpath (dirname (dn)); - free (dn); - - FILE *rf = fopen (fn, "w"); - if (!rf) return -1; - - qsort (_recentlist, _recentcnt, sizeof(FibRecentFile), cmp_recent); - for (i = 0; i < _recentcnt; ++i) { - char *n = encode_3986 (_recentlist[i].path); - fprintf (rf, "%s %lu\n", n, _recentlist[i].atime); - free (n); - } - fclose (rf); - return 0; -} - -int x_fib_load_recent (const char *fn) { - char tmp[1024]; - if (_recentlock) { return -1; } - if (!fn) { return -1; } - x_fib_free_recent (); - if (access (fn, R_OK)) { - return -1; - } - FILE *rf = fopen (fn, "r"); - if (!rf) return -1; - while (fgets (tmp, sizeof(tmp), rf) - && strlen (tmp) > 1 - && strlen (tmp) < sizeof(tmp)) - { - char *s; - tmp[strlen (tmp) - 1] = '\0'; // strip newline - if (!(s = strchr (tmp, ' '))) { // find name <> atime sep - continue; - } - *s = '\0'; - time_t t = atol (++s); - decode_3986 (tmp); - x_fib_add_recent (tmp, t); - } - fclose (rf); - return 0; -} - -unsigned int x_fib_recent_count () { - return _recentcnt; -} - -const char *x_fib_recent_at (unsigned int i) { - if (i >= _recentcnt) - return NULL; - return _recentlist[i].path; -} - -#ifdef PLATFORM_WINDOWS -#define PATHSEP "\\" -#else -#define PATHSEP "/" -#endif - -const char *x_fib_recent_file(const char *appname) { - static char recent_file[1024]; - assert(!strchr(appname, '/')); - const char *xdg = getenv("XDG_DATA_HOME"); - if (xdg && (strlen(xdg) + strlen(appname) + 10) < sizeof(recent_file)) { - sprintf(recent_file, "%s" PATHSEP "%s" PATHSEP "recent", xdg, appname); - return recent_file; - } -#ifdef PLATFORM_WINDOWS - const char * homedrive = getenv("HOMEDRIVE"); - const char * homepath = getenv("HOMEPATH"); - if (homedrive && homepath && (strlen(homedrive) + strlen(homepath) + strlen(appname) + 29) < PATH_MAX) { - sprintf(recent_file, "%s%s" PATHSEP "Application Data" PATHSEP "%s" PATHSEP "recent.txt", homedrive, homepath, appname); - return recent_file; - } -#elif defined PLATFORM_OSX - const char *home = getenv("HOME"); - if (home && (strlen(home) + strlen(appname) + 29) < sizeof(recent_file)) { - sprintf(recent_file, "%s/Library/Preferences/%s/recent", home, appname); - return recent_file; - } -#else - const char *home = getenv("HOME"); - if (home && (strlen(home) + strlen(appname) + 22) < sizeof(recent_file)) { - sprintf(recent_file, "%s/.local/share/%s/recent", home, appname); - return recent_file; - } -#endif - return NULL; -} - -#ifdef HAVE_X11 -#include <mntent.h> -#include <dirent.h> - -#include <X11/Xlib.h> -#include <X11/Xatom.h> -#include <X11/Xutil.h> -#include <X11/keysym.h> -#include <X11/Xos.h> - -#ifndef MIN -#define MIN(A,B) ( (A) < (B) ? (A) : (B) ) -#endif - -#ifndef MAX -#define MAX(A,B) ( (A) < (B) ? (B) : (A) ) -#endif - -static Window _fib_win = 0; -static GC _fib_gc = 0; -static XColor _c_gray0, _c_gray1, _c_gray2, _c_gray3, _c_gray4, _c_gray5; -static Font _fibfont = 0; -static Pixmap _pixbuffer = None; - -static int _fib_width = 100; -static int _fib_height = 100; -static int _btn_w = 0; -static int _btn_span = 0; -static double _scalefactor = 1; - -static int _fib_font_height = 0; -static int _fib_dir_indent = 0; -static int _fib_spc_norm = 0; -static int _fib_font_ascent = 0; -static int _fib_font_vsep = 0; -static int _fib_font_size_width = 0; -static int _fib_font_time_width = 0; -static int _fib_place_width = 0; - -static int _scrl_f = 0; -static int _scrl_y0 = -1; -static int _scrl_y1 = -1; -static int _scrl_my = -1; -static int _scrl_mf = -1; -static int _view_p = -1; - -static int _fsel = -1; -static int _hov_b = -1; -static int _hov_f = -1; -static int _hov_p = -1; -static int _hov_h = -1; -static int _hov_l = -1; -static int _hov_s = -1; -static int _sort = 0; -static int _columns = 0; -static int _fib_filter_fn = 1; -static int _fib_hidden_fn = 0; -static int _fib_show_places = 0; - -static uint8_t _fib_mapped = 0; -static uint8_t _fib_resized = 0; -static unsigned long _dblclk = 0; - -static int _status = -2; -static char _rv_open[1024] = ""; - -static char _fib_cfg_custom_places[1024] = ""; -static char _fib_cfg_custom_font[256] = ""; -static char _fib_cfg_title[128] = "xjadeo - Open Video File"; - -typedef struct { - char name[256]; - int x0; - int xw; -} FibPathButton; - -typedef struct { - char name[256]; - char strtime[32]; - char strsize[32]; - int ssizew; - off_t size; - time_t mtime; - uint8_t flags; // 2: selected, 4: isdir 8: recent-entry - FibRecentFile *rfp; -} FibFileEntry; - -typedef struct { - char text[24]; - uint8_t flags; // 2: selected, 4: toggle, 8 disable - int x0; - int tw; - int xw; - void (*callback)(Display*); -} FibButton; - -typedef struct { - char name[256]; - char path[1024]; - uint8_t flags; // 1: hover, 2: selected, 4:add sep -} FibPlace; - -static char _cur_path[1024] = ""; -static FibFileEntry *_dirlist = NULL; -static FibPathButton *_pathbtn = NULL; -static FibPlace *_placelist = NULL; -static int _dircount = 0; -static int _pathparts = 0; -static int _placecnt = 0; - -static FibButton _btn_ok; -static FibButton _btn_cancel; -static FibButton _btn_filter; -static FibButton _btn_places; -static FibButton _btn_hidden; -static FibButton *_btns[] = {&_btn_places, &_btn_filter, &_btn_hidden, &_btn_cancel, &_btn_ok}; - -static int (*_fib_filter_function)(const char *filename); - -/* hardcoded layout */ -#define DSEP 6 // px; horiz space beween elements, also l+r margin for file-list -#define PSEP 4 // px; horiz space beween paths -#define FILECOLUMN (17 * _fib_dir_indent) //px; min width of file-column -#define LISTTOP 2.7 //em; top of the file-browser list -#define LISTBOT 4.75 //em; bottom of the file-browers list -#define BTNBTMMARGIN 0.75 //em; height/margin of the button row -#define BTNPADDING 2 // px - only used for open/cancel buttons -#define SCROLLBARW (3 + (_fib_spc_norm&~1)) //px; - should be SCROLLBARW = (N * 2 + 3) -#define SCROLLBOXH 10 //px; arrow box top+bottom -#define PLACESW _fib_place_width //px; -#define PLACESWMAX (15 *_fib_spc_norm) //px; -#define PATHBTNTOP _fib_font_vsep //px; offset by (_fib_font_ascent); -#define FAREAMRGB 3 //px; base L+R margin -#define FAREAMRGR (FAREAMRGB + 1) //px; right margin of file-area + 1 (line width) -#define FAREAMRGL (_fib_show_places ? PLACESW / _scalefactor + FAREAMRGB : FAREAMRGB) //px; left margin of file-area -#define TEXTSEP 4 //px; -#define FAREATEXTL (FAREAMRGL + TEXTSEP) //px; filename text-left FAREAMRGL + TEXTSEP -#define SORTBTNOFF -10 //px; - -#ifndef DBLCLKTME -#define DBLCLKTME 200 //msec; double click time -#endif - -#define DRAW_OUTLINE -#define DOUBLE_BUFFER -#define LIST_ENTRY_HOVER - -static int query_font_geometry (Display *dpy, GC gc, const char *txt, int *w, int *h, int *a, int *d) { - XCharStruct text_structure; - int font_direction, font_ascent, font_descent; - XFontStruct *fontinfo = XQueryFont (dpy, XGContextFromGC (gc)); - - if (!fontinfo) { return -1; } - XTextExtents (fontinfo, txt, strlen (txt), &font_direction, &font_ascent, &font_descent, &text_structure); - if (w) *w = XTextWidth (fontinfo, txt, strlen (txt)); - if (h) *h = text_structure.ascent + text_structure.descent; - if (a) *a = text_structure.ascent; - if (d) *d = text_structure.descent; - XFreeFontInfo (NULL, fontinfo, 1); - return 0; -} - -static void VDrawRectangle (Display *dpy, Drawable d, GC gc, int x, int y, unsigned int w, unsigned int h) { -#ifdef DRAW_OUTLINE - XSetForeground (dpy, gc, _c_gray5.pixel); - XDrawLine (dpy, d, gc, x + 1, y + h, x + w, y + h); - XDrawLine (dpy, d, gc, x + w, y + 1, x + w, y + h); - XDrawLine (dpy, d, gc, x + 1, y, x + w, y); - XDrawLine (dpy, d, gc, x, y + 1, x, y + h); -#else - const unsigned long blackColor = BlackPixel (dpy, DefaultScreen (dpy)); - XSetForeground (dpy, _fib_gc, blackColor); - XDrawRectangle (dpy, d, gc, x, y, w, h); -#endif -} - -static void fib_expose (Display *dpy, Window realwin) { - int i; - XID win; - if (!_fib_mapped) return; - - if (_fib_resized -#ifdef DOUBLE_BUFFER - || !_pixbuffer -#endif - ) - { -#ifdef DOUBLE_BUFFER - unsigned int w = 0, h = 0; - if (_pixbuffer != None) { - Window ignored_w; - int ignored_i; - unsigned int ignored_u; - XGetGeometry(dpy, _pixbuffer, &ignored_w, &ignored_i, &ignored_i, &w, &h, &ignored_u, &ignored_u); - if (_fib_width != (int)w || _fib_height != (int)h) { - XFreePixmap (dpy, _pixbuffer); - _pixbuffer = None; - } - } - if (_pixbuffer == None) { - XWindowAttributes wa; - XGetWindowAttributes (dpy, realwin, &wa); - _pixbuffer = XCreatePixmap (dpy, realwin, _fib_width, _fib_height, wa.depth); - } -#endif - if (_pixbuffer != None) { - XSetForeground (dpy, _fib_gc, _c_gray1.pixel); - XFillRectangle (dpy, _pixbuffer, _fib_gc, 0, 0, _fib_width, _fib_height); - } else { - XSetForeground (dpy, _fib_gc, _c_gray1.pixel); - XFillRectangle (dpy, realwin, _fib_gc, 0, 0, _fib_width, _fib_height); - } - _fib_resized = 0; - } - - if (_pixbuffer == None) { - win = realwin; - } else { - win = _pixbuffer; - } - - // Top Row: dirs and up navigation - - int ppw = 0; - int ppx = FAREAMRGB * _scalefactor; - - for (i = _pathparts - 1; i >= 0; --i) { - ppw += _pathbtn[i].xw + PSEP * _scalefactor; - if (ppw >= _fib_width - PSEP * _scalefactor - _pathbtn[0].xw - FAREAMRGB * _scalefactor) break; // XXX, first change is from "/" to "<", NOOP - } - ++i; - // border-less "<" parent/up, IFF space is limited - if (i > 0) { - if (0 == _hov_p || (_hov_p > 0 && _hov_p < _pathparts - 1)) { - XSetForeground (dpy, _fib_gc, _c_gray4.pixel); - } else { - XSetForeground (dpy, _fib_gc, _c_gray0.pixel); - } - XDrawString (dpy, win, _fib_gc, ppx, PATHBTNTOP, "<", 1); - ppx += _pathbtn[0].xw + PSEP * _scalefactor; - if (i == _pathparts) --i; - } - - _view_p = i; - - while (i < _pathparts) { - if (i == _hov_p) { - XSetForeground (dpy, _fib_gc, _c_gray0.pixel); - } else { - XSetForeground (dpy, _fib_gc, _c_gray2.pixel); - } - XFillRectangle (dpy, win, _fib_gc, - ppx + 1, PATHBTNTOP - _fib_font_ascent, - _pathbtn[i].xw - 1, _fib_font_height); - VDrawRectangle (dpy, win, _fib_gc, - ppx, PATHBTNTOP - _fib_font_ascent, - _pathbtn[i].xw, _fib_font_height); - XSetForeground (dpy, _fib_gc, _c_gray4.pixel); - XDrawString (dpy, win, _fib_gc, ppx + 1 + BTNPADDING, PATHBTNTOP, - _pathbtn[i].name, strlen (_pathbtn[i].name)); - _pathbtn[i].x0 = ppx; // current position - ppx += _pathbtn[i].xw + PSEP * _scalefactor; - ++i; - } - - // middle, scroll list of file names - const int ltop = LISTTOP * _fib_font_vsep; - const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; - const int fsel_height = 4 * _scalefactor + llen * _fib_font_vsep; - const int fsel_width = _fib_width - (FAREAMRGL + FAREAMRGR) * _scalefactor - (llen < _dircount ? SCROLLBARW * _scalefactor : 0); - const int t_x = FAREATEXTL * _scalefactor; - int t_s = FAREATEXTL * _scalefactor + fsel_width; - int t_t = FAREATEXTL * _scalefactor + fsel_width; - - // check which colums can be visible - // depending on available width of window. - _columns = 0; - if (fsel_width > FILECOLUMN + _fib_font_size_width + _fib_font_time_width) { - _columns |= 2; - t_s = FAREAMRGL * _scalefactor + fsel_width - _fib_font_time_width - TEXTSEP * _scalefactor; - } - if (fsel_width > FILECOLUMN + _fib_font_size_width) { - _columns |= 1; - t_t = t_s - _fib_font_size_width - TEXTSEP * _scalefactor; - } - - int fstop = _scrl_f; // first entry in scroll position - const int ttop = ltop - _fib_font_height + _fib_font_ascent; - - if (fstop > 0 && fstop + llen > _dircount) { - fstop = MAX (0, _dircount - llen); - _scrl_f = fstop; - } - - // list header - XSetForeground (dpy, _fib_gc, _c_gray3.pixel); - XFillRectangle (dpy, win, _fib_gc, FAREAMRGL * _scalefactor, ltop - _fib_font_vsep, fsel_width, _fib_font_vsep); - - // draw background of file list - XSetForeground (dpy, _fib_gc, _c_gray2.pixel); - XFillRectangle (dpy, win, _fib_gc, FAREAMRGL * _scalefactor, ltop, fsel_width, fsel_height); - -#ifdef DRAW_OUTLINE - VDrawRectangle (dpy, win, _fib_gc, - FAREAMRGL * _scalefactor, - ltop - _fib_font_vsep - 1, - _fib_width - (FAREAMRGL + FAREAMRGR) * _scalefactor, - fsel_height + _fib_font_vsep + 1); -#endif - - switch (_hov_h) { - case 1: - XSetForeground (dpy, _fib_gc, _c_gray0.pixel); - XFillRectangle (dpy, win, _fib_gc, - t_x + _fib_dir_indent - (TEXTSEP - 1) * _scalefactor, - ltop - _fib_font_vsep, - t_t - t_x - _fib_dir_indent - 1 * _scalefactor, - _fib_font_vsep); - break; - case 2: - XSetForeground (dpy, _fib_gc, _c_gray0.pixel); - XFillRectangle (dpy, win, _fib_gc, - t_t - (TEXTSEP - 1) * _scalefactor, - ltop - _fib_font_vsep, - _fib_font_size_width + (TEXTSEP - 1) * _scalefactor, - _fib_font_vsep); - break; - case 3: - XSetForeground (dpy, _fib_gc, _c_gray0.pixel); - XFillRectangle (dpy, win, _fib_gc, - t_s - (TEXTSEP - 1) * _scalefactor, - ltop - _fib_font_vsep, - TEXTSEP * 2 * _scalefactor + _fib_font_time_width - 1 * _scalefactor, - _fib_font_vsep); - break; - default: - break; - } - - // column headings and sort order - int arp = MAX (2 * _scalefactor, _fib_font_height / 5); // arrow scale - const int trioff = _fib_font_height - _fib_font_ascent - arp + 1 * _scalefactor; - XPoint ptri[4] = { {0, ttop - trioff }, {arp, -arp - arp - 1 * _scalefactor}, {-arp - arp, 0}, {arp, arp + arp + 1 * _scalefactor}}; - if (_sort & 1) { - ptri[0].y = ttop -arp - arp - 1 * _scalefactor; - ptri[1].y *= -1; - ptri[3].y *= -1; - } - switch (_sort) { - case 0: - case 1: - ptri[0].x = t_t + (SORTBTNOFF + 2) * _scalefactor - arp; - XSetForeground (dpy, _fib_gc, _c_gray4.pixel); - XFillPolygon (dpy, win, _fib_gc, ptri, 3, Convex, CoordModePrevious); - XDrawLines (dpy, win, _fib_gc, ptri, 4, CoordModePrevious); - break; - case 2: - case 3: - if (_columns & 1) { - ptri[0].x = t_s + (SORTBTNOFF + 2) * _scalefactor - arp; - XSetForeground (dpy, _fib_gc, _c_gray4.pixel); - XFillPolygon (dpy, win, _fib_gc, ptri, 3, Convex, CoordModePrevious); - XDrawLines (dpy, win, _fib_gc, ptri, 4, CoordModePrevious); - } - break; - case 4: - case 5: - if (_columns & 2) { - ptri[0].x = FAREATEXTL * _scalefactor + fsel_width + (SORTBTNOFF + 2) * _scalefactor - arp; - XSetForeground (dpy, _fib_gc, _c_gray4.pixel); - XFillPolygon (dpy, win, _fib_gc, ptri, 3, Convex, CoordModePrevious); - XDrawLines (dpy, win, _fib_gc, ptri, 4, CoordModePrevious); - } - break; - } - -#if 0 // bottom header bottom border - XSetForeground (dpy, _fib_gc, _c_gray5.pixel); - XSetLineAttributes (dpy, _fib_gc, 1, LineOnOffDash, CapButt, JoinMiter); - XDrawLine (dpy, win, _fib_gc, - FAREAMRGL + 1, ltop, - FAREAMRGL + fsel_width, ltop); - XSetLineAttributes (dpy, _fib_gc, 1, LineSolid, CapButt, JoinMiter); -#endif - - XSetForeground (dpy, _fib_gc, _c_gray2.pixel); - XDrawLine (dpy, win, _fib_gc, - t_x + _fib_dir_indent - TEXTSEP * _scalefactor, ltop - _fib_font_vsep + 3 * _scalefactor, - t_x + _fib_dir_indent - TEXTSEP * _scalefactor, ltop - 3 * _scalefactor); - - XSetForeground (dpy, _fib_gc, _c_gray4.pixel); - XDrawString (dpy, win, _fib_gc, t_x + _fib_dir_indent, ttop, "Name", 4); - - if (_columns & 1) { - XSetForeground (dpy, _fib_gc, _c_gray2.pixel); - XDrawLine (dpy, win, _fib_gc, - t_t - TEXTSEP * _scalefactor, ltop - _fib_font_vsep + 3 * _scalefactor, - t_t - TEXTSEP * _scalefactor, ltop - 3 * _scalefactor); - XSetForeground (dpy, _fib_gc, _c_gray4.pixel); - XDrawString (dpy, win, _fib_gc, t_t, ttop, "Size", 4); - } - - if (_columns & 2) { - XSetForeground (dpy, _fib_gc, _c_gray2.pixel); - XDrawLine (dpy, win, _fib_gc, - t_s - TEXTSEP * _scalefactor, ltop - _fib_font_vsep + 3 * _scalefactor, - t_s - TEXTSEP * _scalefactor, ltop - 3 * _scalefactor); - XSetForeground (dpy, _fib_gc, _c_gray4.pixel); - if (_pathparts > 0) - XDrawString (dpy, win, _fib_gc, t_s, ttop, "Last Modified", 13); - else - XDrawString (dpy, win, _fib_gc, t_s, ttop, "Last Used", 9); - } - - // scrollbar sep - if (llen < _dircount) { - const int sx0 = _fib_width - (SCROLLBARW + FAREAMRGR) * _scalefactor; - XSetForeground (dpy, _fib_gc, _c_gray2.pixel); - XDrawLine (dpy, win, _fib_gc, - sx0 - 1, ltop - _fib_font_vsep, -#ifdef DRAW_OUTLINE - sx0 - 1, ltop + fsel_height -#else - sx0 - 1, ltop - 1 -#endif - ); - } - - // clip area for file-name - XRectangle clp = {(FAREAMRGL + 1) * _scalefactor, ltop, - t_t - (FAREAMRGL + TEXTSEP * 2 + 1) * _scalefactor, fsel_height}; - - // list files in view - for (i = 0; i < llen; ++i) { - const int j = i + fstop; - if (j >= _dircount) break; - - const int t_y = ltop + (i+1) * _fib_font_vsep - 4; - - if (_dirlist[j].flags & 2) { - XSetForeground (dpy, _fib_gc, _c_gray5.pixel); - XFillRectangle (dpy, win, _fib_gc, - FAREAMRGL * _scalefactor, t_y - _fib_font_ascent, fsel_width, _fib_font_height); - } - /* - if (_hov_f == j && !(_dirlist[j].flags & 2)) { - XSetForeground (dpy, _fib_gc, _c_gray4.pixel); - } - */ - if (_dirlist[j].flags & 4) { - XSetForeground (dpy, _fib_gc, (_dirlist[j].flags & 2) ? _c_gray3.pixel : _c_gray5.pixel); - XDrawString (dpy, win, _fib_gc, t_x, t_y, "D", 1); - } - XSetClipRectangles (dpy, _fib_gc, 0, 0, &clp, 1, Unsorted); - XSetForeground (dpy, _fib_gc, _c_gray4.pixel); - XDrawString (dpy, win, _fib_gc, - t_x + _fib_dir_indent, t_y, - _dirlist[j].name, strlen (_dirlist[j].name)); - XSetClipMask (dpy, _fib_gc, None); - - if (_columns & 1) // right-aligned 'size' - XDrawString (dpy, win, _fib_gc, - t_s - (TEXTSEP + 2) * _scalefactor - _dirlist[j].ssizew, t_y, - _dirlist[j].strsize, strlen (_dirlist[j].strsize)); - if (_columns & 2) - XDrawString (dpy, win, _fib_gc, - t_s, t_y, - _dirlist[j].strtime, strlen (_dirlist[j].strtime)); - } - - // scrollbar - if (llen < _dircount) { - float sl = (fsel_height + _fib_font_vsep - (SCROLLBOXH + SCROLLBOXH) * _scalefactor) / (float) _dircount; - sl = MAX ((8. * _scalefactor / llen), sl); // 8px min height of scroller - const int sy1 = llen * sl; - const float mx = (fsel_height + _fib_font_vsep - (SCROLLBOXH + SCROLLBOXH) * _scalefactor - sy1) / (float)(_dircount - llen); - const int sy0 = fstop * mx; - const int sx0 = _fib_width - (SCROLLBARW + FAREAMRGR) * _scalefactor; - const int stop = ltop - _fib_font_vsep; - - _scrl_y0 = stop + SCROLLBOXH * _scalefactor + sy0; - _scrl_y1 = _scrl_y0 + sy1; - - assert (fstop + llen <= _dircount); - // scroll-bar background - XSetForeground (dpy, _fib_gc, _c_gray1.pixel); - XFillRectangle (dpy, win, _fib_gc, sx0, stop, SCROLLBARW * _scalefactor, fsel_height + _fib_font_vsep); - - // scroller - if (_hov_s == 0 || _hov_s == 1 || _hov_s == 2) { - XSetForeground (dpy, _fib_gc, _c_gray4.pixel); - } else { - XSetForeground (dpy, _fib_gc, _c_gray0.pixel); - } - XFillRectangle (dpy, win, _fib_gc, sx0 + 1 * _scalefactor, stop + SCROLLBOXH * _scalefactor + sy0, (SCROLLBARW - 2) * _scalefactor, sy1); - - int scrw = (SCROLLBARW -3) / 2 * _scalefactor; - // arrows top and bottom - if (_hov_s == 1) { - XSetForeground (dpy, _fib_gc, _c_gray4.pixel); - } else { - XSetForeground (dpy, _fib_gc, _c_gray0.pixel); - } - XPoint ptst[4] = { {sx0 + 1 * _scalefactor, stop + 8 * _scalefactor}, {scrw, -7 * _scalefactor}, {scrw, 7 * _scalefactor}, {-2 * scrw, 0}}; - XFillPolygon (dpy, win, _fib_gc, ptst, 3, Convex, CoordModePrevious); - XDrawLines (dpy, win, _fib_gc, ptst, 4, CoordModePrevious); - - if (_hov_s == 2) { - XSetForeground (dpy, _fib_gc, _c_gray4.pixel); - } else { - XSetForeground (dpy, _fib_gc, _c_gray0.pixel); - } - XPoint ptsb[4] = { {sx0 + 1 * _scalefactor, ltop + fsel_height - 9 * _scalefactor}, {2*scrw, 0}, {-scrw, 7 * _scalefactor}, {-scrw, -7 * _scalefactor}}; - XFillPolygon (dpy, win, _fib_gc, ptsb, 3, Convex, CoordModePrevious); - XDrawLines (dpy, win, _fib_gc, ptsb, 4, CoordModePrevious); - } else { - _scrl_y0 = _scrl_y1 = -1; - } - - if (_fib_show_places) { - assert (_placecnt > 0); - - // heading - XSetForeground (dpy, _fib_gc, _c_gray3.pixel); - XFillRectangle (dpy, win, _fib_gc, FAREAMRGB * _scalefactor, ltop - _fib_font_vsep, PLACESW - TEXTSEP * _scalefactor, _fib_font_vsep); - - // body - XSetForeground (dpy, _fib_gc, _c_gray2.pixel); - XFillRectangle (dpy, win, _fib_gc, FAREAMRGB * _scalefactor, ltop, PLACESW - TEXTSEP * _scalefactor, fsel_height); - -#ifdef DRAW_OUTLINE - VDrawRectangle (dpy, win, _fib_gc, FAREAMRGB * _scalefactor, ltop - _fib_font_vsep -1, PLACESW - TEXTSEP * _scalefactor, fsel_height + _fib_font_vsep + 1); -#endif - - XSetForeground (dpy, _fib_gc, _c_gray4.pixel); - XDrawString (dpy, win, _fib_gc, (FAREAMRGB + TEXTSEP) * _scalefactor, ttop, "Places", 6); - - XRectangle pclip = {(FAREAMRGB + 1) * _scalefactor, ltop, PLACESW - (TEXTSEP + 1) * _scalefactor, fsel_height}; - XSetClipRectangles (dpy, _fib_gc, 0, 0, &pclip, 1, Unsorted); - const int plx = (FAREAMRGB + TEXTSEP) * _scalefactor; - for (i = 0; i < llen && i < _placecnt; ++i) { - const int ply = ltop + (i+1) * _fib_font_vsep - 4 * _scalefactor; - if (i == _hov_l) { - XSetForeground (dpy, _fib_gc, _c_gray5.pixel); - XFillRectangle (dpy, win, _fib_gc, FAREAMRGB * _scalefactor, ltop + i * _fib_font_vsep, PLACESW - TEXTSEP * _scalefactor, _fib_font_vsep); - } - XSetForeground (dpy, _fib_gc, _c_gray4.pixel); - XDrawString (dpy, win, _fib_gc, - plx, ply, - _placelist[i].name, strlen (_placelist[i].name)); - if (_placelist[i].flags & 4) { - XSetForeground (dpy, _fib_gc, _c_gray5.pixel); - const int plly = ply - _fib_font_ascent + _fib_font_height + 1 * _scalefactor; - const int pllx0 = FAREAMRGB * _scalefactor; - const int pllx1 = FAREAMRGB * _scalefactor + PLACESW - TEXTSEP * _scalefactor; - XDrawLine (dpy, win, _fib_gc, pllx0, plly, pllx1, plly); - } - } - XSetClipMask (dpy, _fib_gc, None); - - if (_placecnt > llen) { - const int plly = ltop + fsel_height - _fib_font_height + _fib_font_ascent; - const int pllx0 = FAREAMRGB * _scalefactor + (PLACESW - TEXTSEP * _scalefactor) * .75; - const int pllx1 = FAREAMRGB * _scalefactor + PLACESW - TEXTSEP * 2 * _scalefactor; - - XSetForeground (dpy, _fib_gc, _c_gray4.pixel); - XSetLineAttributes (dpy, _fib_gc, 1 * _scalefactor, LineOnOffDash, CapButt, JoinMiter); - XDrawLine (dpy, win, _fib_gc, pllx0, plly, pllx1, plly); - XSetLineAttributes (dpy, _fib_gc, 1 * _scalefactor, LineSolid, CapButt, JoinMiter); - } - } - - // Bottom Buttons - const int numb = sizeof(_btns) / sizeof(FibButton*); - int xtra = _fib_width - _btn_span; - const int cbox = _fib_font_ascent - 2 * _scalefactor; - const int bbase = _fib_height - BTNBTMMARGIN * _fib_font_vsep - BTNPADDING * _scalefactor; - const int cblw = cbox > 20 * _scalefactor - ? 5 * _scalefactor - : (cbox > 9 * _scalefactor ? 3 : 1) * _scalefactor; - - int bx = FAREAMRGB * _scalefactor; - for (i = 0; i < numb; ++i) { - if (_btns[i]->flags & 8) { continue; } - if (_btns[i]->flags & 4) { - // checkbutton - const int cby0 = bbase - cbox + (1 + BTNPADDING) * _scalefactor; - if (i == _hov_b) { - XSetForeground (dpy, _fib_gc, _c_gray0.pixel); - } else { - XSetForeground (dpy, _fib_gc, _c_gray0.pixel); - } - XDrawRectangle (dpy, win, _fib_gc, - bx, cby0 - 1, cbox + 1, cbox + 1); - - XSetForeground (dpy, _fib_gc, _c_gray4.pixel); - XDrawString (dpy, win, _fib_gc, BTNPADDING * _scalefactor + bx + _fib_font_ascent, bbase + (BTNPADDING + 1) * _scalefactor, - _btns[i]->text, strlen (_btns[i]->text)); - - if (i == _hov_b) { - XSetForeground (dpy, _fib_gc, _c_gray0.pixel); - } else { - /* if (_btns[i]->flags & 2) { - XSetForeground (dpy, _fib_gc, _c_gray1.pixel); - } else */ { - XSetForeground (dpy, _fib_gc, _c_gray2.pixel); - } - } - XFillRectangle (dpy, win, _fib_gc, - bx+1, cby0, cbox, cbox); - - if (_btns[i]->flags & 2) { - XSetLineAttributes (dpy, _fib_gc, cblw, LineSolid, CapRound, JoinMiter); - XSetForeground (dpy, _fib_gc, _c_gray4.pixel); - XDrawLine (dpy, win, _fib_gc, - bx + 2, cby0 + 1, - bx + cbox - 1, cby0 + cbox - 2); - XDrawLine (dpy, win, _fib_gc, - bx + cbox - 1, cby0 + 1, - bx + 2, cby0 + cbox - 2); - XSetLineAttributes (dpy, _fib_gc, 1, LineSolid, CapButt, JoinMiter); - } - } else { - if (xtra > 0) { - bx += xtra; - xtra = 0; - } - // pushbutton - - uint8_t can_hover = 1; // special case - if (_btns[i] == &_btn_ok) { - if (_fsel < 0 || _fsel >= _dircount) { - can_hover = 0; - } - } - - if (can_hover && i == _hov_b) { - XSetForeground (dpy, _fib_gc, _c_gray0.pixel); - } else { - XSetForeground (dpy, _fib_gc, _c_gray2.pixel); - } - XFillRectangle (dpy, win, _fib_gc, - bx + 1, bbase - _fib_font_ascent, - _btn_w - 1, _fib_font_height + BTNPADDING * 2 * _scalefactor); - VDrawRectangle (dpy, win, _fib_gc, - bx, bbase - _fib_font_ascent, - _btn_w, _fib_font_height + BTNPADDING * 2 * _scalefactor); - XSetForeground (dpy, _fib_gc, _c_gray4.pixel); - XDrawString (dpy, win, _fib_gc, bx + (_btn_w - _btns[i]->tw) * .5, 1 + bbase + BTNPADDING * _scalefactor, - _btns[i]->text, strlen (_btns[i]->text)); - } - _btns[i]->x0 = bx; - bx += _btns[i]->xw + DSEP * _scalefactor; - } - - if (_pixbuffer != None) { - XCopyArea(dpy, _pixbuffer, realwin, _fib_gc, 0, 0, _fib_width, _fib_height, 0, 0); - } - XFlush (dpy); -} - -static void fib_reset () { - _hov_p = _hov_f = _hov_h = _hov_l = -1; - _scrl_f = 0; - _fib_resized = 1; -} - -static int cmp_n_up (const void *p1, const void *p2) { - FibFileEntry *a = (FibFileEntry*) p1; - FibFileEntry *b = (FibFileEntry*) p2; - if ((a->flags & 4) && !(b->flags & 4)) return -1; - if (!(a->flags & 4) && (b->flags & 4)) return 1; - return strcmp (a->name, b->name); -} - -static int cmp_n_down (const void *p1, const void *p2) { - FibFileEntry *a = (FibFileEntry*) p1; - FibFileEntry *b = (FibFileEntry*) p2; - if ((a->flags & 4) && !(b->flags & 4)) return -1; - if (!(a->flags & 4) && (b->flags & 4)) return 1; - return strcmp (b->name, a->name); -} - -static int cmp_t_up (const void *p1, const void *p2) { - FibFileEntry *a = (FibFileEntry*) p1; - FibFileEntry *b = (FibFileEntry*) p2; - if ((a->flags & 4) && !(b->flags & 4)) return -1; - if (!(a->flags & 4) && (b->flags & 4)) return 1; - if (a->mtime == b->mtime) return 0; - return a->mtime > b->mtime ? -1 : 1; -} - -static int cmp_t_down (const void *p1, const void *p2) { - FibFileEntry *a = (FibFileEntry*) p1; - FibFileEntry *b = (FibFileEntry*) p2; - if ((a->flags & 4) && !(b->flags & 4)) return -1; - if (!(a->flags & 4) && (b->flags & 4)) return 1; - if (a->mtime == b->mtime) return 0; - return a->mtime > b->mtime ? 1 : -1; -} - -static int cmp_s_up (const void *p1, const void *p2) { - FibFileEntry *a = (FibFileEntry*) p1; - FibFileEntry *b = (FibFileEntry*) p2; - if ((a->flags & 4) && (b->flags & 4)) return 0; // dir, no size, retain order - if ((a->flags & 4) && !(b->flags & 4)) return -1; - if (!(a->flags & 4) && (b->flags & 4)) return 1; - if (a->size == b->size) return 0; - return a->size > b->size ? -1 : 1; -} - -static int cmp_s_down (const void *p1, const void *p2) { - FibFileEntry *a = (FibFileEntry*) p1; - FibFileEntry *b = (FibFileEntry*) p2; - if ((a->flags & 4) && (b->flags & 4)) return 0; // dir, no size, retain order - if ((a->flags & 4) && !(b->flags & 4)) return -1; - if (!(a->flags & 4) && (b->flags & 4)) return 1; - if (a->size == b->size) return 0; - return a->size > b->size ? 1 : -1; -} - -static void fmt_size (Display *dpy, FibFileEntry *f) { - if (f->size > 10995116277760) { - sprintf (f->strsize, "%.0f TB", f->size / 1099511627776.f); - } - if (f->size > 1099511627776) { - sprintf (f->strsize, "%.1f TB", f->size / 1099511627776.f); - } - else if (f->size > 10737418240) { - sprintf (f->strsize, "%.0f GB", f->size / 1073741824.f); - } - else if (f->size > 1073741824) { - sprintf (f->strsize, "%.1f GB", f->size / 1073741824.f); - } - else if (f->size > 10485760) { - sprintf (f->strsize, "%.0f MB", f->size / 1048576.f); - } - else if (f->size > 1048576) { - sprintf (f->strsize, "%.1f MB", f->size / 1048576.f); - } - else if (f->size > 10240) { - sprintf (f->strsize, "%.0f KB", f->size / 1024.f); - } - else if (f->size >= 1000) { - sprintf (f->strsize, "%.1f KB", f->size / 1024.f); - } - else { - sprintf (f->strsize, "%.0f B", f->size / 1.f); - } - int sw = 0; - query_font_geometry (dpy, _fib_gc, f->strsize, &sw, NULL, NULL, NULL); - if (sw > _fib_font_size_width) { - _fib_font_size_width = sw; - } - f->ssizew = sw; -} - -static void fmt_time (Display *dpy, FibFileEntry *f) { - struct tm *tmp; - tmp = localtime (&f->mtime); - if (!tmp) { - return; - } - strftime (f->strtime, sizeof(f->strtime), "%F %H:%M", tmp); - - int tw = 0; - query_font_geometry (dpy, _fib_gc, f->strtime, &tw, NULL, NULL, NULL); - if (tw > _fib_font_time_width) { - _fib_font_time_width = tw; - } -} - -static void fib_resort (const char * sel) { - if (_dircount < 1) { return; } - int (*sortfn)(const void *p1, const void *p2); - switch (_sort) { - case 1: sortfn = &cmp_n_down; break; - case 2: sortfn = &cmp_s_down; break; - case 3: sortfn = &cmp_s_up; break; - case 4: sortfn = &cmp_t_down; break; - case 5: sortfn = &cmp_t_up; break; - default: - sortfn = &cmp_n_up; - break; - } - qsort (_dirlist, _dircount, sizeof(_dirlist[0]), sortfn); - int i; - for (i = 0; i < _dircount && sel; ++i) { - if (!strcmp (_dirlist[i].name, sel)) { - _fsel = i; - break; - } - } -} - -static void fib_select (Display *dpy, int item) { - if (_fsel >= 0) { - _dirlist[_fsel].flags &= ~2; - } - _fsel = item; - if (_fsel >= 0 && _fsel < _dircount) { - _dirlist[_fsel].flags |= 2; - const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; - if (_fsel < _scrl_f) { - _scrl_f = _fsel; - } - else if (_fsel >= _scrl_f + llen) { - _scrl_f = 1 + _fsel - llen; - } - } else { - _fsel = -1; - } - - fib_expose (dpy, _fib_win); -} - -static inline int fib_filter (const char *name) { - if (_fib_filter_function) { - return _fib_filter_function (name); - } else { - return 1; - } -} - -static void fib_pre_opendir (Display *dpy) { - if (_dirlist) free (_dirlist); - if (_pathbtn) free (_pathbtn); - _dirlist = NULL; - _pathbtn = NULL; - _dircount = 0; - _pathparts = 0; - query_font_geometry (dpy, _fib_gc, "Size ", &_fib_font_size_width, NULL, NULL, NULL); - fib_reset (); - _fsel = -1; -} - -static void fib_post_opendir (Display *dpy, const char *sel) { - if (_dircount > 0) - _fsel = 0; // select first - else - _fsel = -1; - fib_resort (sel); - - if (_dircount > 0 && _fsel >= 0) { - fib_select (dpy, _fsel); - } else { - fib_expose (dpy, _fib_win); - } -} - -static int fib_dirlistadd (Display *dpy, const int i, const char* path, const char *name, time_t mtime) { - char tp[1024]; - struct stat fs; - if (!_fib_hidden_fn && name[0] == '.') return -1; - if (!strcmp (name, ".")) return -1; - if (!strcmp (name, "..")) return -1; - strcpy (tp, path); - strcat (tp, name); - if (access (tp, R_OK)) { - return -1; - } - if (stat (tp, &fs)) { - return -1; - } - assert (i < _dircount); // could happen if dir changes while we're reading. - if (i >= _dircount) return -1; - if (S_ISDIR (fs.st_mode)) { - _dirlist[i].flags |= 4; - } - else if (S_ISREG (fs.st_mode)) { - if (!fib_filter (name)) return -1; - } -#if 0 // only needed with lstat() - else if (S_ISLNK (fs.st_mode)) { - if (!fib_filter (name)) return -1; - } -#endif - else { - return -1; - } - strcpy (_dirlist[i].name, name); - _dirlist[i].mtime = mtime > 0 ? mtime : fs.st_mtime; - _dirlist[i].size = fs.st_size; - if (!(_dirlist[i].flags & 4)) - fmt_size (dpy, &_dirlist[i]); - fmt_time (dpy, &_dirlist[i]); - return 0; -} - -static int fib_openrecent (Display *dpy, const char *sel) { - int i; - unsigned int j; - assert (_recentcnt > 0); - fib_pre_opendir (dpy); - query_font_geometry (dpy, _fib_gc, "Last Used", &_fib_font_time_width, NULL, NULL, NULL); - _dirlist = (FibFileEntry*) calloc (_recentcnt, sizeof(FibFileEntry)); - _dircount = _recentcnt; - for (j = 0, i = 0; j < _recentcnt; ++j) { - char base[1024]; - char *s = strrchr (_recentlist[j].path, '/'); - if (!s || !*++s) continue; - size_t len = (s - _recentlist[j].path); - strncpy (base, _recentlist[j].path, len); - base[len] = '\0'; - if (!fib_dirlistadd (dpy, i, base, s, _recentlist[j].atime)) { - _dirlist[i].rfp = &_recentlist[j]; - _dirlist[i].flags |= 8; - ++i; - } - } - _dircount = i; - fib_post_opendir (dpy, sel); - return _dircount; -} - -static int fib_opendir (Display *dpy, const char* path, const char *sel) { - char *t0, *t1; - int i; - - assert (path); - - if (strlen (path) == 0 && _recentcnt > 0) { // XXX we should use a better indication for this - strcpy (_cur_path, ""); - return fib_openrecent (dpy, sel); - } - - assert (strlen (path) < sizeof(_cur_path) -1); - assert (strlen (path) > 0); - assert (strstr (path, "//") == NULL); - assert (path[0] == '/'); - - fib_pre_opendir (dpy); - - query_font_geometry (dpy, _fib_gc, "Last Modified", &_fib_font_time_width, NULL, NULL, NULL); - DIR *dir = opendir (path); - if (!dir) { - strcpy (_cur_path, "/"); - } else { - int i; - struct dirent *de; - if (path != _cur_path) - strcpy (_cur_path, path); - - if (_cur_path[strlen (_cur_path) -1] != '/') - strcat (_cur_path, "/"); - - while ((de = readdir (dir))) { - if (!_fib_hidden_fn && de->d_name[0] == '.') continue; - ++_dircount; - } - - if (_dircount > 0) - _dirlist = (FibFileEntry*) calloc (_dircount, sizeof(FibFileEntry)); - - rewinddir (dir); - - i = 0; - while ((de = readdir (dir))) { - if (!fib_dirlistadd (dpy, i, _cur_path, de->d_name, 0)) - ++i; - } - _dircount = i; - closedir (dir); - } - - t0 = _cur_path; - while (*t0 && (t0 = strchr (t0, '/'))) { - ++_pathparts; - ++t0; - } - assert (_pathparts > 0); - _pathbtn = (FibPathButton*) calloc (_pathparts + 1, sizeof(FibPathButton)); - - t1 = _cur_path; - i = 0; - while (*t1 && (t0 = strchr (t1, '/'))) { - if (i == 0) { - strcpy (_pathbtn[i].name, "/"); - } else { - *t0 = 0; - strcpy (_pathbtn[i].name, t1); - } - query_font_geometry (dpy, _fib_gc, _pathbtn[i].name, &_pathbtn[i].xw, NULL, NULL, NULL); - _pathbtn[i].xw += BTNPADDING + BTNPADDING; - *t0 = '/'; - t1 = t0 + 1; - ++i; - } - fib_post_opendir (dpy, sel); - return _dircount; -} - -static int fib_open (Display *dpy, int item) { - char tp[1024]; - if (_dirlist[item].flags & 8) { - assert (_dirlist[item].rfp); - strcpy (_rv_open, _dirlist[item].rfp->path); - _status = 1; - return 0; - } - strcpy (tp, _cur_path); - strcat (tp, _dirlist[item].name); - if (_dirlist[item].flags & 4) { - fib_opendir (dpy, tp, NULL); - return 0; - } else { - _status = 1; - strcpy (_rv_open, tp); - } - return 0; -} - -static void cb_cancel (Display *dpy) { - _status = -1; - - // unused - return; (void)dpy; -} - -static void cb_open (Display *dpy) { - if (_fsel >= 0 && _fsel < _dircount) { - fib_open (dpy, _fsel); - } -} - -static void sync_button_states () { - if (_fib_show_places) - _btn_places.flags |= 2; - else - _btn_places.flags &= ~2; - if (_fib_filter_fn) // inverse -> show all - _btn_filter.flags &= ~2; - else - _btn_filter.flags |= 2; - if (_fib_hidden_fn) - _btn_hidden.flags |= 2; - else - _btn_hidden.flags &= ~2; -} - -static void cb_places (Display *dpy) { - _fib_show_places = ! _fib_show_places; - if (_placecnt < 1) - _fib_show_places = 0; - sync_button_states (); - _fib_resized = 1; - fib_expose (dpy, _fib_win); -} - -static void cb_filter (Display *dpy) { - _fib_filter_fn = ! _fib_filter_fn; - sync_button_states (); - char *sel = _fsel >= 0 ? strdup (_dirlist[_fsel].name) : NULL; - fib_opendir (dpy, _cur_path, sel); - free (sel); -} - -static void cb_hidden (Display *dpy) { - _fib_hidden_fn = ! _fib_hidden_fn; - sync_button_states (); - char *sel = _fsel >= 0 ? strdup (_dirlist[_fsel].name) : NULL; - fib_opendir (dpy, _cur_path, sel); - free (sel); -} - -static int fib_widget_at_pos (Display *dpy, int x, int y, int *it) { - const int btop = _fib_height - BTNBTMMARGIN * _fib_font_vsep - _fib_font_ascent - BTNPADDING * _scalefactor; - const int bbot = btop + _fib_font_height + BTNPADDING * 2 * _scalefactor; - const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; - const int ltop = LISTTOP * _fib_font_vsep; - const int fbot = ltop + 4 * _scalefactor + llen * _fib_font_vsep; - const int ptop = PATHBTNTOP - _fib_font_ascent; - assert (it); - - // paths at top - if (y > ptop && y < ptop + _fib_font_height && _view_p >= 0 && _pathparts > 0) { - int i = _view_p; - *it = -1; - if (i > 0) { // special case '<' - if (x > FAREAMRGB * _scalefactor && x <= FAREAMRGB * _scalefactor + _pathbtn[0].xw) { - *it = _view_p - 1; - i = _pathparts; - } - } - while (i < _pathparts) { - if (x >= _pathbtn[i].x0 && x <= _pathbtn[i].x0 + _pathbtn[i].xw) { - *it = i; - break; - } - ++i; - } - assert (*it < _pathparts); - if (*it >= 0) return 1; - else return 0; - } - - // buttons at bottom - if (y > btop && y < bbot) { - size_t i; - *it = -1; - for (i = 0; i < sizeof(_btns) / sizeof(FibButton*); ++i) { - const int bx = _btns[i]->x0; - if (_btns[i]->flags & 8) { continue; } - if (x > bx && x < bx + _btns[i]->xw) { - *it = i; - } - } - if (*it >= 0) return 3; - else return 0; - } - - // main file area - if (y >= ltop - _fib_font_vsep && y < fbot && x > FAREAMRGL * _scalefactor && x < _fib_width - FAREAMRGR * _scalefactor) { - // scrollbar - if (_scrl_y0 > 0 && x >= _fib_width - (FAREAMRGR + SCROLLBARW) * _scalefactor && x <= _fib_width - FAREAMRGR * _scalefactor) { - if (y >= _scrl_y0 && y < _scrl_y1) { - *it = 0; - } else if (y >= _scrl_y1) { - *it = 2; - } else { - *it = 1; - } - return 4; - } - // file-list - else if (y >= ltop) { - const int item = (y - ltop) / _fib_font_vsep + _scrl_f; - *it = -1; - if (item >= 0 && item < _dircount) { - *it = item; - } - if (*it >= 0) return 2; - else return 0; - } - else { - *it = -1; - const int fsel_width = _fib_width - (FAREAMRGL + FAREAMRGR) * _scalefactor - (llen < _dircount ? SCROLLBARW * _scalefactor : 0); - const int t_s = FAREAMRGL * _scalefactor + fsel_width - _fib_font_time_width - TEXTSEP * 2 * _scalefactor; - const int t_t = FAREAMRGL * _scalefactor + fsel_width - TEXTSEP * _scalefactor - _fib_font_size_width - ((_columns & 2) ? ( _fib_font_time_width + TEXTSEP * 2 * _scalefactor) : 0); - if (x >= fsel_width + FAREAMRGL * _scalefactor) ; - else if ((_columns & 2) && x >= t_s) *it = 3; - else if ((_columns & 1) && x >= t_t) *it = 2; - else if (x >= FAREATEXTL * _scalefactor + _fib_dir_indent - TEXTSEP * _scalefactor) *it = 1; - if (*it >= 0) return 5; - else return 0; - } - } - - // places list - if (_fib_show_places && y >= ltop && y < fbot && x > FAREAMRGB * _scalefactor && x < (FAREAMRGL - FAREAMRGB) * _scalefactor) { - const int item = (y - ltop) / _fib_font_vsep; - *it = -1; - if (item >= 0 && item < _placecnt) { - *it = item; - } - if (*it >= 0) return 6; - else return 0; - } - - return 0; - - // unused - (void)dpy; -} - -static void fib_update_hover (Display *dpy, int need_expose, const int type, const int item) { - int hov_p = -1; - int hov_b = -1; - int hov_h = -1; - int hov_s = -1; -#ifdef LIST_ENTRY_HOVER - int hov_f = -1; - int hov_l = -1; -#endif - - switch (type) { - case 1: hov_p = item; break; - case 3: hov_b = item; break; - case 4: hov_s = item; break; - case 5: hov_h = item; break; -#ifdef LIST_ENTRY_HOVER - case 6: hov_l = item; break; - case 2: hov_f = item; break; -#endif - default: break; - } -#ifdef LIST_ENTRY_HOVER - if (hov_f != _hov_f) { _hov_f = hov_f; need_expose = 1; } - if (hov_l != _hov_l) { _hov_l = hov_l; need_expose = 1; } -#endif - if (hov_b != _hov_b) { _hov_b = hov_b; need_expose = 1; } - if (hov_p != _hov_p) { _hov_p = hov_p; need_expose = 1; } - if (hov_h != _hov_h) { _hov_h = hov_h; need_expose = 1; } - if (hov_s != _hov_s) { _hov_s = hov_s; need_expose = 1; } - - if (need_expose) { - fib_expose (dpy, _fib_win); - } -} - -static void fib_motion (Display *dpy, int x, int y) { - int it = -1; - - if (_scrl_my >= 0) { - const int sdiff = y - _scrl_my; - const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; - const int fsel_height = 4 + llen * _fib_font_vsep; - const float sl = (fsel_height + _fib_font_vsep - (SCROLLBOXH + SCROLLBOXH)) / (float) _dircount; - - int news = _scrl_mf + sdiff / sl; - if (news < 0) news = 0; - if (news >= (_dircount - llen)) news = _dircount - llen; - if (news != _scrl_f) { - _scrl_f = news; - fib_expose (dpy, _fib_win); - } - return; - } - - const int type = fib_widget_at_pos (dpy, x, y, &it); - fib_update_hover (dpy, 0, type, it); -} - -static void fib_mousedown (Display *dpy, int x, int y, int btn, unsigned long time) { - int it; - switch (fib_widget_at_pos (dpy, x, y, &it)) { - case 4: // scrollbar - if (btn == 1) { - _dblclk = 0; - if (it == 0) { - _scrl_my = y; - _scrl_mf = _scrl_f; - } else { - int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; - if (llen < 2) llen = 2; - int news = _scrl_f; - if (it == 1) { - news -= llen - 1; - } else { - news += llen - 1; - } - if (news < 0) news = 0; - if (news >= (_dircount - llen)) news = _dircount - llen; - if (news != _scrl_f && _scrl_y0 >= 0) { - assert (news >=0); - _scrl_f = news; - fib_update_hover (dpy, 1, 4, it); - } - } - } - break; - case 2: // file-list - if (btn == 4 || btn == 5) { - const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; - int news = _scrl_f + ((btn == 4) ? - 1 : 1); - if (news < 0) news = 0; - if (news >= (_dircount - llen)) news = _dircount - llen; - if (news != _scrl_f && _scrl_y0 >= 0) { - assert (news >=0); - _scrl_f = news; - fib_update_hover (dpy, 1, 0, 0); - } - _dblclk = 0; - } - else if (btn == 1 && it >= 0 && it < _dircount) { - if (_fsel == it) { - if (time - _dblclk < DBLCLKTME) { - fib_open (dpy, it); - _dblclk = 0; - } - _dblclk = time; - } else { - fib_select (dpy, it); - _dblclk = time; - } - /* - if (_fsel >= 0) { - if (!(_dirlist[_fsel].flags & 4)); - } - */ - } - break; - case 1: // paths - assert (_fsel < _dircount); - assert (it >= 0 && it < _pathparts); - { - int i = 0; - char path[1024] = "/"; - while (++i <= it) { - strcat (path, _pathbtn[i].name); - strcat (path, "/"); - } - char *sel = NULL; - if (i < _pathparts) - sel = strdup (_pathbtn[i].name); - else if (i == _pathparts && _fsel >= 0) - sel = strdup (_dirlist[_fsel].name); - fib_opendir (dpy, path, sel); - free (sel); - } - break; - case 3: // btn - if (btn == 1 && _btns[it]->callback) { - _btns[it]->callback (dpy); - } - break; - case 5: // sort - if (btn == 1) { - switch (it) { - case 1: if (_sort == 0) _sort = 1; else _sort = 0; break; - case 2: if (_sort == 2) _sort = 3; else _sort = 2; break; - case 3: if (_sort == 4) _sort = 5; else _sort = 4; break; - } - if (_fsel >= 0) { - assert (_dirlist && _dircount >= _fsel); - _dirlist[_fsel].flags &= ~2; - char *sel = strdup (_dirlist[_fsel].name); - fib_resort (sel); - free (sel); - } else { - fib_resort (NULL); - _fsel = -1; - } - fib_reset (); - _hov_h = it; - fib_select (dpy, _fsel); - } - break; - case 6: - if (btn == 1 && it >= 0 && it < _placecnt) { - fib_opendir (dpy, _placelist[it].path, NULL); - } - break; - default: - break; - } -} - -static void fib_mouseup (Display *dpy, int x, int y, int btn, unsigned long time) { - _scrl_my = -1; - - // unused - return; (void)dpy; (void)x; (void)y; (void)btn; (void)time; -} - -static void add_place_raw (Display *dpy, const char *name, const char *path) { - _placelist = (FibPlace*) realloc (_placelist, (_placecnt + 1) * sizeof(FibPlace)); - strcpy (_placelist[_placecnt].path, path); - strcpy (_placelist[_placecnt].name, name); - _placelist[_placecnt].flags = 0; - - int sw = -1; - query_font_geometry (dpy, _fib_gc, name, &sw, NULL, NULL, NULL); - if (sw > _fib_place_width) { - _fib_place_width = sw; - } - ++_placecnt; -} - -static int add_place_places (Display *dpy, const char *name, const char *url) { - char const * path; - struct stat fs; - int i; - if (!url || strlen (url) < 1) return -1; - if (!name || strlen (name) < 1) return -1; - if (url[0] == '/') { - path = url; - } - else if (!strncmp (url, "file:///", 8)) { - path = &url[7]; - } - else { - return -1; - } - - if (access (path, R_OK)) { - return -1; - } - if (stat (path, &fs)) { - return -1; - } - if (!S_ISDIR (fs.st_mode)) { - return -1; - } - - for (i = 0; i < _placecnt; ++i) { - if (!strcmp (path, _placelist[i].path)) { - return -1; - } - } - add_place_raw (dpy, name, path); - return 0; -} - -static int parse_gtk_bookmarks (Display *dpy, const char *fn) { - char tmp[1024]; - if (access (fn, R_OK)) { - return -1; - } - FILE *bm = fopen (fn, "r"); - if (!bm) return -1; - int found = 0; - while (fgets (tmp, sizeof(tmp), bm) - && strlen (tmp) > 1 - && strlen (tmp) < sizeof(tmp)) - { - char *s, *n; - tmp[strlen (tmp) - 1] = '\0'; // strip newline - if ((s = strchr (tmp, ' '))) { - *s = '\0'; - n = strdup (++s); - decode_3986 (tmp); - if (!add_place_places (dpy, n, tmp)) { - ++found; - } - free (n); - } else if ((s = strrchr (tmp, '/'))) { - n = strdup (++s); - decode_3986 (tmp); - if (!add_place_places (dpy, n, tmp)) { - ++found; - } - free (n); - } - } - fclose (bm); - return found; -} - -static const char *ignore_mountpoints[] = { - "/bin", "/boot", "/dev", "/etc", - "/lib", "/live", "/mnt", "/opt", - "/root", "/sbin", "/srv", "/tmp", - "/usr", "/var", "/proc", "/sbin", - "/net", "/sys" -}; - -static const char *ignore_fs[] = { - "auto", "autofs", - "debugfs", "devfs", - "devpts", "ecryptfs", - "fusectl", "kernfs", - "linprocfs", "proc", - "ptyfs", "rootfs", - "selinuxfs", "sysfs", - "tmpfs", "usbfs", - "nfsd", "rpc_pipefs", -}; - -static const char *ignore_devices[] = { - "binfmt_", "devpts", - "gvfs", "none", - "nfsd", "sunrpc", - "/dev/loop", "/dev/vn" -}; - -static int check_mount (const char *mountpoint, const char *fs, const char *device) { - size_t i; - if (!mountpoint || !fs || !device) return -1; - //printf("%s %s %s\n", mountpoint, fs, device); - for (i = 0 ; i < sizeof(ignore_mountpoints) / sizeof(char*); ++i) { - if (!strncmp (mountpoint, ignore_mountpoints[i], strlen (ignore_mountpoints[i]))) { - return 1; - } - } - if (!strncmp (mountpoint, "/home", 5)) { - return 1; - } - for (i = 0 ; i < sizeof(ignore_fs) / sizeof(char*); ++i) { - if (!strncmp (fs, ignore_fs[i], strlen (ignore_fs[i]))) { - return 1; - } - } - for (i = 0 ; i < sizeof(ignore_devices) / sizeof(char*); ++i) { - if (!strncmp (device, ignore_devices[i], strlen (ignore_devices[i]))) { - return 1; - } - } - return 0; -} - -static int read_mtab (Display *dpy, const char *mtab) { - FILE *mt = fopen (mtab, "r"); - if (!mt) return -1; - int found = 0; - struct mntent *mntent; - while ((mntent = getmntent (mt)) != NULL) { - char *s; - if (check_mount (mntent->mnt_dir, mntent->mnt_type, mntent->mnt_fsname)) - continue; - - if ((s = strrchr (mntent->mnt_dir, '/'))) { - ++s; - } else { - s = mntent->mnt_dir; - } - if (!add_place_places (dpy, s, mntent->mnt_dir)) { - ++found; - } - } - fclose (mt); - return found; -} - -static void populate_places (Display *dpy) { - char tmp[1024]; - int spacer = -1; - if (_placecnt > 0) return; - _fib_place_width = 0; - - if (_recentcnt > 0) { - add_place_raw (dpy, "Recently Used", ""); - _placelist[0].flags |= 4; - } - - add_place_places (dpy, "Home", getenv ("HOME")); - - if (getenv ("HOME")) { - strcpy (tmp, getenv ("HOME")); - strcat (tmp, "/Desktop"); - add_place_places (dpy, "Desktop", tmp); - } - - add_place_places (dpy, "Filesystem", "/"); - - if (_placecnt > 0) spacer = _placecnt -1; - - if (strlen (_fib_cfg_custom_places) > 0) { - parse_gtk_bookmarks (dpy, _fib_cfg_custom_places); - } - - if (read_mtab (dpy, "/proc/mounts") < 1) { - read_mtab (dpy, "/etc/mtab"); - } - - int parsed_bookmarks = 0; - if (!parsed_bookmarks && getenv ("HOME")) { - strcpy (tmp, getenv ("HOME")); - strcat (tmp, "/.gtk-bookmarks"); - if (parse_gtk_bookmarks (dpy, tmp) > 0) { - parsed_bookmarks = 1; - } - } - if (!parsed_bookmarks && getenv ("XDG_CONFIG_HOME")) { - strcpy (tmp, getenv ("XDG_CONFIG_HOME")); - strcat (tmp, "/gtk-3.0/bookmarks"); - if (parse_gtk_bookmarks (dpy, tmp) > 0) { - parsed_bookmarks = 1; - } - } - if (!parsed_bookmarks && getenv ("HOME")) { - strcpy (tmp, getenv ("HOME")); - strcat (tmp, "/.config/gtk-3.0/bookmarks"); - if (parse_gtk_bookmarks (dpy, tmp) > 0) { - parsed_bookmarks = 1; - } - } - if (_fib_place_width > 0) { - _fib_place_width = MIN (_fib_place_width + TEXTSEP + _fib_dir_indent /*extra*/ , PLACESWMAX); - } - if (spacer > 0 && spacer < _placecnt -1) { - _placelist[ spacer ].flags |= 4; - } -} - -static uint8_t font_err = 0; -static int x_error_handler (Display *d, XErrorEvent *e) { - font_err = 1; - return 0; - - // unused - (void)d; (void)e; -} - -int x_fib_show (Display *dpy, Window parent, int x, int y, double scalefactor) { - if (_fib_win) { - XSetInputFocus (dpy, _fib_win, RevertToParent, CurrentTime); - return -1; - } - - _status = 0; - _rv_open[0] = '\0'; - - Colormap colormap = DefaultColormap (dpy, DefaultScreen (dpy)); - _c_gray1.flags = DoRed | DoGreen | DoBlue; - _c_gray0.red = _c_gray0.green = _c_gray0.blue = 0x5000; // 95% hover prelight - _c_gray1.red = _c_gray1.green = _c_gray1.blue = 0x1100; // 93% window bg, scrollbar-fg - _c_gray2.red = _c_gray2.green = _c_gray2.blue = 0x1c00; // 83% button & list bg - _c_gray3.red = _c_gray3.green = _c_gray3.blue = 0x0a00; // 75% heading + scrollbar-bg - _c_gray4.red = _c_gray4.green = _c_gray4.blue = 0xd600; // 40% prelight text, sep lines - _c_gray5.red = _c_gray5.green = _c_gray5.blue = 0x3000; // 20% 3D border - - if (!XAllocColor (dpy, colormap, &_c_gray0)) return -1; - if (!XAllocColor (dpy, colormap, &_c_gray1)) return -1; - if (!XAllocColor (dpy, colormap, &_c_gray2)) return -1; - if (!XAllocColor (dpy, colormap, &_c_gray3)) return -1; - if (!XAllocColor (dpy, colormap, &_c_gray4)) return -1; - if (!XAllocColor (dpy, colormap, &_c_gray5)) return -1; - - XSetWindowAttributes attr; - memset (&attr, 0, sizeof(XSetWindowAttributes)); - attr.border_pixel = _c_gray2.pixel; - - attr.event_mask = ExposureMask | KeyPressMask - | ButtonPressMask | ButtonReleaseMask - | ConfigureNotify | StructureNotifyMask - | PointerMotionMask | LeaveWindowMask; - - _fib_win = XCreateWindow ( - dpy, DefaultRootWindow (dpy), - x, y, _fib_width * scalefactor, _fib_height * scalefactor, - 1, CopyFromParent, InputOutput, CopyFromParent, - CWEventMask | CWBorderPixel, &attr); - - _scalefactor = scalefactor; - - if (!_fib_win) { return 1; } - - if (parent) - XSetTransientForHint (dpy, _fib_win, parent); - - XStoreName (dpy, _fib_win, "Select File"); - - Atom wmDelete = XInternAtom (dpy, "WM_DELETE_WINDOW", True); - XSetWMProtocols (dpy, _fib_win, &wmDelete, 1); - - _fib_gc = XCreateGC (dpy, _fib_win, 0, NULL); - XSetLineAttributes (dpy, _fib_gc, 1, LineSolid, CapButt, JoinMiter); - const char dl[1] = {1}; - XSetDashes (dpy, _fib_gc, 0, dl, 1); - - int (*handler)(Display *, XErrorEvent *) = XSetErrorHandler (&x_error_handler); - -#define _XTESTFONT(FN) \ - { \ - font_err = 0; \ - _fibfont = XLoadFont (dpy, FN); \ - XSetFont (dpy, _fib_gc, _fibfont); \ - XSync (dpy, False); \ - } - - font_err = 1; - if (getenv ("XJFONT")) _XTESTFONT (getenv ("XJFONT")); - if (font_err && strlen (_fib_cfg_custom_font) > 0) _XTESTFONT (_fib_cfg_custom_font); - if (scalefactor >= 2.5) { - if (font_err) _XTESTFONT ("-*-helvetica-medium-r-normal-*-18-*-*-*-*-*-*-*"); - if (font_err) _XTESTFONT ("-*-verdana-medium-r-normal-*-18-*-*-*-*-*-*-*"); - if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-20-*-*-*-*-*-*-*"); - if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-18-*-*-*-*-*-*-*"); - } else if (scalefactor >= 2) { - if (font_err) _XTESTFONT ("-*-helvetica-medium-r-normal-*-16-*-*-*-*-*-*-*"); - if (font_err) _XTESTFONT ("-*-verdana-medium-r-normal-*-16-*-*-*-*-*-*-*"); - if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-18-*-*-*-*-*-*-*"); - if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-16-*-*-*-*-*-*-*"); - } else if (scalefactor >= 1.5) { - if (font_err) _XTESTFONT ("-*-helvetica-medium-r-normal-*-14-*-*-*-*-*-*-*"); - if (font_err) _XTESTFONT ("-*-verdana-medium-r-normal-*-14-*-*-*-*-*-*-*"); - if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-15-*-*-*-*-*-*-*"); - if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-14-*-*-*-*-*-*-*"); - } else { - if (font_err) _XTESTFONT ("-*-helvetica-medium-r-normal-*-12-*-*-*-*-*-*-*"); - if (font_err) _XTESTFONT ("-*-verdana-medium-r-normal-*-12-*-*-*-*-*-*-*"); - if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-13-*-*-*-*-*-*-*"); - if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-12-*-*-*-*-*-*-*"); - } - if (font_err) _fibfont = None; - XSync (dpy, False); - XSetErrorHandler (handler); - - if (_fib_font_height == 0) { // 1st time only - query_font_geometry (dpy, _fib_gc, "D ", &_fib_dir_indent, NULL, NULL, NULL); - query_font_geometry (dpy, _fib_gc, "_", &_fib_spc_norm, NULL, NULL, NULL); - if (query_font_geometry (dpy, _fib_gc, "|0Yy", NULL, &_fib_font_height, &_fib_font_ascent, NULL)) { - XFreeGC (dpy, _fib_gc); - XDestroyWindow (dpy, _fib_win); - _fib_win = 0; - return -1; - } - _fib_font_height += 3 * scalefactor; - _fib_font_ascent += 2 * scalefactor; - _fib_font_vsep = _fib_font_height + 2 * scalefactor; - } - - populate_places (dpy); - - strcpy (_btn_ok.text, "Open"); - strcpy (_btn_cancel.text, "Cancel"); - strcpy (_btn_filter.text, "List All Files"); - strcpy (_btn_places.text, "Show Places"); - strcpy (_btn_hidden.text, "Show Hidden"); - - _btn_ok.callback = &cb_open; - _btn_cancel.callback = &cb_cancel; - _btn_filter.callback = &cb_filter; - _btn_places.callback = &cb_places; - _btn_hidden.callback = &cb_hidden; - _btn_filter.flags |= 4; - _btn_places.flags |= 4; - _btn_hidden.flags |= 4; - - if (!_fib_filter_function) { - _btn_filter.flags |= 8; - } - - size_t i; - int btncnt = 0; - _btn_w = 0; - _btn_span = 0; - for (i = 0; i < sizeof(_btns) / sizeof(FibButton*); ++i) { - if (_btns[i]->flags & 8) { continue; } - query_font_geometry (dpy, _fib_gc, _btns[i]->text, &_btns[i]->tw, NULL, NULL, NULL); - if (_btns[i]->flags & 4) { - _btn_span += _btns[i]->tw + _fib_font_ascent + TEXTSEP * scalefactor; - } else { - ++btncnt; - if (_btns[i]->tw > _btn_w) - _btn_w = _btns[i]->tw; - } - } - - _btn_w += (BTNPADDING + BTNPADDING + TEXTSEP + TEXTSEP + TEXTSEP) * scalefactor; - _btn_span += _btn_w * btncnt + DSEP * scalefactor * (i - 1) + (FAREAMRGR + FAREAMRGB) * scalefactor; - - for (i = 0; i < sizeof(_btns) / sizeof(FibButton*); ++i) { - if (_btns[i]->flags & 8) { continue; } - if (_btns[i]->flags & 4) { - _btns[i]->xw = _btns[i]->tw + _fib_font_ascent + TEXTSEP * scalefactor; - } else { - _btns[i]->xw = _btn_w; - } - } - - sync_button_states () ; - - _fib_height = _fib_font_vsep * 15.8 * (1.0 + (scalefactor - 1.0) / 2.0); - _fib_width = MAX (_btn_span, 480 * scalefactor); - - XResizeWindow (dpy, _fib_win, _fib_width, _fib_height); - - XTextProperty x_wname, x_iname; - XSizeHints hints; - XWMHints wmhints; - - hints.flags = PSize | PMinSize; - hints.min_width = _btn_span; - hints.min_height = 8 * _fib_font_vsep; - - char *w_name = & _fib_cfg_title[0]; - - wmhints.input = True; - wmhints.flags = InputHint; - if (XStringListToTextProperty (&w_name, 1, &x_wname) && - XStringListToTextProperty (&w_name, 1, &x_iname)) - { - XSetWMProperties (dpy, _fib_win, &x_wname, &x_iname, NULL, 0, &hints, &wmhints, NULL); - XFree (x_wname.value); - XFree (x_iname.value); - } - - XSetWindowBackground (dpy, _fib_win, _c_gray1.pixel); - - _fib_mapped = 0; - XMapRaised (dpy, _fib_win); - - if (!strlen (_cur_path) || !fib_opendir (dpy, _cur_path, NULL)) { - fib_opendir (dpy, getenv ("HOME") ? getenv ("HOME") : "/", NULL); - } - -#if 0 - XGrabPointer (dpy, _fib_win, True, - ButtonReleaseMask | ButtonPressMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask | StructureNotifyMask, - GrabModeAsync, GrabModeAsync, None, None, CurrentTime); - XGrabKeyboard (dpy, _fib_win, True, GrabModeAsync, GrabModeAsync, CurrentTime); - //XSetInputFocus (dpy, parent, RevertToNone, CurrentTime); -#endif - _recentlock = 1; - return 0; -} - -void x_fib_close (Display *dpy) { - if (!_fib_win) return; - XFreeGC (dpy, _fib_gc); - XDestroyWindow (dpy, _fib_win); - _fib_win = 0; - free (_dirlist); - _dirlist = NULL; - free (_pathbtn); - _pathbtn = NULL; - if (_fibfont != None) XUnloadFont (dpy, _fibfont); - _fibfont = None; - free (_placelist); - _placelist = NULL; - _dircount = 0; - _pathparts = 0; - _placecnt = 0; - if (_pixbuffer != None) XFreePixmap (dpy, _pixbuffer); - _pixbuffer = None; - Colormap colormap = DefaultColormap (dpy, DefaultScreen (dpy)); - XFreeColors (dpy, colormap, &_c_gray0.pixel, 1, 0); - XFreeColors (dpy, colormap, &_c_gray1.pixel, 1, 0); - XFreeColors (dpy, colormap, &_c_gray2.pixel, 1, 0); - XFreeColors (dpy, colormap, &_c_gray3.pixel, 1, 0); - XFreeColors (dpy, colormap, &_c_gray4.pixel, 1, 0); - XFreeColors (dpy, colormap, &_c_gray5.pixel, 1, 0); - _recentlock = 0; -} - -int x_fib_handle_events (Display *dpy, XEvent *event) { - if (!_fib_win) return 0; - if (_status) return 0; - if (event->xany.window != _fib_win) { - return 0; - } - - switch (event->type) { - case MapNotify: - _fib_mapped = 1; - break; - case UnmapNotify: - _fib_mapped = 0; - break; - case LeaveNotify: - fib_update_hover (dpy, 1, 0, 0); - break; - case ClientMessage: - if (!strcmp (XGetAtomName (dpy, event->xclient.message_type), "WM_PROTOCOLS")) { - _status = -1; - } - break; - case ConfigureNotify: - if ( - (event->xconfigure.width > 1 && event->xconfigure.height > 1) - && - (event->xconfigure.width != _fib_width || event->xconfigure.height != _fib_height) - ) - { - _fib_width = event->xconfigure.width; - _fib_height = event->xconfigure.height; - _fib_resized = 1; - } - break; - case Expose: - if (event->xexpose.count == 0) { - fib_expose (dpy, event->xany.window); - } - break; - case MotionNotify: - fib_motion (dpy, event->xmotion.x, event->xmotion.y); - if (event->xmotion.is_hint == NotifyHint) { - XGetMotionEvents (dpy, event->xany.window, CurrentTime, CurrentTime, NULL); - } - break; - case ButtonPress: - fib_mousedown (dpy, event->xbutton.x, event->xbutton.y, event->xbutton.button, event->xbutton.time); - break; - case ButtonRelease: - fib_mouseup (dpy, event->xbutton.x, event->xbutton.y, event->xbutton.button, event->xbutton.time); - break; - case KeyRelease: - break; - case KeyPress: - { - KeySym key; - char buf[100]; - static XComposeStatus stat; - XLookupString (&event->xkey, buf, sizeof(buf), &key, &stat); - switch (key) { - case XK_Escape: - _status = -1; - break; - case XK_Up: - if (_fsel > 0) { - fib_select (dpy, _fsel - 1); - } - break; - case XK_Down: - if (_fsel < _dircount -1) { - fib_select ( dpy, _fsel + 1); - } - break; - case XK_Page_Up: - if (_fsel > 0) { - int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; - if (llen < 1) llen = 1; else --llen; - int fs = MAX (0, _fsel - llen); - fib_select ( dpy, fs); - } - break; - case XK_Page_Down: - if (_fsel < _dircount) { - int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; - if (llen < 1) llen = 1; else --llen; - int fs = MIN (_dircount - 1, _fsel + llen); - fib_select ( dpy, fs); - } - break; - case XK_Left: - if (_pathparts > 1) { - int i = 0; - char path[1024] = "/"; - while (++i < _pathparts - 1) { - strcat (path, _pathbtn[i].name); - strcat (path, "/"); - } - char *sel = strdup (_pathbtn[_pathparts-1].name); - fib_opendir (dpy, path, sel); - free (sel); - } - break; - case XK_Right: - if (_fsel >= 0 && _fsel < _dircount) { - if (_dirlist[_fsel].flags & 4) { - cb_open (dpy); - } - } - break; - case XK_Return: - cb_open (dpy); - break; - default: - if ((key >= XK_a && key <= XK_z) || (key >= XK_0 && key <= XK_9)) { - int i; - for (i = 0; i < _dircount; ++i) { - int j = (_fsel + i + 1) % _dircount; - char kcmp = _dirlist[j].name[0]; - if (kcmp > 0x40 && kcmp <= 0x5A) kcmp |= 0x20; - if (kcmp == (char)key) { - fib_select ( dpy, j); - break; - } - } - } - break; - } - } - break; - } - - if (_status) { - x_fib_close (dpy); - } - return _status; -} - -int x_fib_status () { - return _status; -} - -int x_fib_configure (int k, const char *v) { - if (_fib_win) { return -1; } - switch (k) { - case 0: - if (strlen (v) >= sizeof(_cur_path) -1) return -2; - if (strlen (v) < 1) return -2; - if (v[0] != '/') return -2; - if (strstr (v, "//")) return -2; - strncpy (_cur_path, v, sizeof(_cur_path)); - break; - case 1: - if (strlen (v) >= sizeof(_fib_cfg_title) -1) return -2; - strncpy (_fib_cfg_title, v, sizeof(_fib_cfg_title)); - break; - case 2: - if (strlen (v) >= sizeof(_fib_cfg_custom_font) -1) return -2; - strncpy (_fib_cfg_custom_font, v, sizeof(_fib_cfg_custom_font)); - break; - case 3: - if (strlen (v) >= sizeof(_fib_cfg_custom_places) -1) return -2; - strncpy (_fib_cfg_custom_places, v, sizeof(_fib_cfg_custom_places)); - break; - default: - return -2; - } - return 0; -} - -int x_fib_cfg_buttons (int k, int v) { - if (_fib_win) { return -1; } - switch (k) { - case 1: - if (v < 0) { - _btn_hidden.flags |= 8; - } else { - _btn_hidden.flags &= ~8; - } - if (v == 1) { - _btn_hidden.flags |= 2; - _fib_hidden_fn = 1; - } else if (v == 0) { - _btn_hidden.flags &= 2; - _fib_hidden_fn = 0; - } - break; - case 2: - if (v < 0) { - _btn_places.flags |= 8; - } else { - _btn_places.flags &= ~8; - } - if (v == 1) { - _btn_places.flags |= 2; - _fib_show_places = 1; - } else if (v == 0) { - _btn_places.flags &= ~2; - _fib_show_places = 0; - } - break; - case 3: - // NB. filter button is automatically hidden - // IFF the filter-function is NULL. - if (v < 0) { - _btn_filter.flags |= 8; - } else { - _btn_filter.flags &= ~8; - } - if (v == 1) { - _btn_filter.flags &= ~2; // inverse - 'show all' = !filter - _fib_filter_fn = 1; - } else if (v == 0) { - _btn_filter.flags |= 2; - _fib_filter_fn = 0; - } - break; - default: - return -2; - } - return 0; -} - -int x_fib_cfg_filter_callback (int (*cb)(const char*)) { - if (_fib_win) { return -1; } - _fib_filter_function = cb; - return 0; -} - -char *x_fib_filename () { - if (_status > 0 && !_fib_win) - return strdup (_rv_open); - else - return NULL; -} -#endif // HAVE_X11 - -#if defined(__clang__) -# pragma clang diagnostic pop -#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) -# pragma GCC diagnostic pop -#endif - -/* example usage */ -#ifdef SOFD_TEST - -static int fib_filter_movie_filename (const char *name) { - if (!_fib_filter_fn) return 1; - const int l3 = strlen (name) - 3; - const int l4 = l3 - 1; - const int l5 = l4 - 1; - const int l6 = l5 - 1; - const int l9 = l6 - 3; - if ( - (l4 > 0 && ( - !strcasecmp (&name[l4], ".avi") - || !strcasecmp (&name[l4], ".mov") - || !strcasecmp (&name[l4], ".ogg") - || !strcasecmp (&name[l4], ".ogv") - || !strcasecmp (&name[l4], ".mpg") - || !strcasecmp (&name[l4], ".mov") - || !strcasecmp (&name[l4], ".mp4") - || !strcasecmp (&name[l4], ".mkv") - || !strcasecmp (&name[l4], ".vob") - || !strcasecmp (&name[l4], ".asf") - || !strcasecmp (&name[l4], ".avs") - || !strcasecmp (&name[l4], ".dts") - || !strcasecmp (&name[l4], ".flv") - || !strcasecmp (&name[l4], ".m4v") - )) || - (l5 > 0 && ( - !strcasecmp (&name[l5], ".h264") - || !strcasecmp (&name[l5], ".webm") - )) || - (l6 > 0 && ( - !strcasecmp (&name[l6], ".dirac") - )) || - (l9 > 0 && ( - !strcasecmp (&name[l9], ".matroska") - )) || - (l3 > 0 && ( - !strcasecmp (&name[l3], ".dv") - || !strcasecmp (&name[l3], ".ts") - )) - ) - { - return 1; - } - return 0; -} - -int main (int argc, char **argv) { - Display* dpy = XOpenDisplay (0); - if (!dpy) return -1; - - x_fib_cfg_filter_callback (fib_filter_movie_filename); - x_fib_configure (1, "Open Movie File"); - x_fib_load_recent ("/tmp/sofd.recent"); - x_fib_show (dpy, 0, 300, 300); - - while (1) { - XEvent event; - while (XPending (dpy) > 0) { - XNextEvent (dpy, &event); - if (x_fib_handle_events (dpy, &event)) { - if (x_fib_status () > 0) { - char *fn = x_fib_filename (); - printf ("OPEN '%s'\n", fn); - x_fib_add_recent (fn, time (NULL)); - free (fn); - } - } - } - if (x_fib_status ()) { - break; - } - usleep (80000); - } - x_fib_close (dpy); - - x_fib_save_recent ("/tmp/sofd.recent"); - - x_fib_free_recent (); - XCloseDisplay (dpy); - return 0; -} -#endif diff --git a/distrho/DistrhoInfo.hpp b/distrho/DistrhoInfo.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2019 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -31,23 +31,39 @@ START_NAMESPACE_DISTRHO It allows developers to create plugins with custom UIs using a simple C++ API.@n The framework facilitates exporting various different plugin formats from the same code-base. - DPF can build for LADSPA, DSSI, LV2 and VST2 formats.@n + DPF can build for LADSPA, DSSI, LV2, VST2 and VST3 formats.@n A JACK/Standalone mode is also available, allowing you to quickly test plugins. @section Macros You start by creating a "DistrhoPluginInfo.h" file describing the plugin via macros, see @ref PluginMacros.@n - This file is included in the main DPF code to select which features to activate for each plugin format. + This file is included during compilation of the main DPF code to select which features to activate for each plugin format. For example, a plugin (with %UI) that use states will require LV2 hosts to support Atom and Worker extensions for - message passing from the %UI to the plugin.@n + message passing from the %UI to the (DSP) plugin.@n If your plugin does not make use of states, the Worker extension is not set as a required feature. @section Plugin The next step is to create your plugin code by subclassing DPF's Plugin class.@n You need to pass the number of parameters in the constructor and also the number of programs and states, if any. - Here's an example of an audio plugin that simply mutes the host output: + Do note all of DPF code is within its own C++ namespace (@b DISTRHO for DSP/plugin stuff, @b DGL for UI stuff).@n + You can use @ref START_NAMESPACE_DISTRHO / @ref END_NAMESPACE_DISTRHO combo around your code, or globally set @ref USE_NAMESPACE_DISTRHO.@n + These are defined as compiler macros so that you can override the namespace name during build. When in doubt, just follow the examples. + + @section Examples + Let's begin with some examples.@n + Here is one of a stereo audio plugin that simply mutes the host output: @code + /* Make DPF related classes available for us to use without any extra namespace references */ + USE_NAMESPACE_DISTRHO; + + /** + Our custom plugin class. + Subclassing `Plugin` from DPF is how this all works. + + By default, only information-related functions and `run` are pure virtual (that is, must be reimplemented). + When enabling certain features (such as programs or states, more on that below), a few extra functions also need to be reimplemented. + */ class MutePlugin : public Plugin { public: @@ -107,18 +123,10 @@ START_NAMESPACE_DISTRHO } /* ---------------------------------------------------------------------------------------- - * This example has no parameters, so skip parameter stuff */ - - void initParameter(uint32_t, Parameter&) override {} - float getParameterValue(uint32_t) const override { return 0.0f; } - void setParameterValue(uint32_t, float) override {} - - /* ---------------------------------------------------------------------------------------- * Audio/MIDI Processing */ /** Run/process function for plugins without MIDI input. - NOTE: Some parameters might be null if there are no audio inputs or outputs. */ void run(const float**, float** outputs, uint32_t frames) override { @@ -130,11 +138,20 @@ START_NAMESPACE_DISTRHO std::memset(outL, 0, sizeof(float)*frames); std::memset(outR, 0, sizeof(float)*frames); } - }; + + /** + Create an instance of the Plugin class. + This is the entry point for DPF plugins. + DPF will call this to either create an instance of your plugin for the host or to fetch some initial information for internal caching. + */ + Plugin* createPlugin() + { + return new MutePlugin(); + } @endcode - See the Plugin class for more information and to understand what each function does. + See the Plugin class for more information. @section Parameters A plugin is nothing without parameters.@n @@ -142,12 +159,12 @@ START_NAMESPACE_DISTRHO They have hints to describe how they behave plus a name and a symbol identifying them.@n Parameters also have 'ranges' – a minimum, maximum and default value. - Input parameters are "read-only": the plugin can read them but not change them. - (the exception being when changing programs, more on that below)@n + Input parameters are by default "read-only": the plugin can read them but not change them. + (there are exceptions and possibly a request to the host to change values, more on that below)@n It's the host responsibility to save, restore and set input parameters. Output parameters can be changed at anytime by the plugin.@n - The host will simply read their values and not change them. + The host will simply read their values and never change them. Here's an example of an audio plugin that has 1 input parameter: @code @@ -204,7 +221,7 @@ START_NAMESPACE_DISTRHO { // we only have one parameter so we can skip checking the index - parameter.hints = kParameterIsAutomable; + parameter.hints = kParameterIsAutomatable; parameter.name = "Gain"; parameter.symbol = "gain"; parameter.ranges.min = 0.0f; @@ -257,8 +274,8 @@ START_NAMESPACE_DISTRHO See the Parameter struct for more information about parameters. @section Programs - Programs in DPF refer to plugin-side presets (usually called "factory presets"), - an initial set of presets provided by plugin authors included in the actual plugin. + Programs in DPF refer to plugin-side presets (usually called "factory presets").@n + This is meant as an initial set of presets provided by plugin authors included in the actual plugin. To use programs you must first enable them by setting @ref DISTRHO_PLUGIN_WANT_PROGRAMS to 1 in your DistrhoPluginInfo.h file.@n When enabled you'll need to override 2 new function in your plugin code, @@ -314,7 +331,7 @@ START_NAMESPACE_DISTRHO */ void initParameter(uint32_t index, Parameter& parameter) override { - parameter.hints = kParameterIsAutomable; + parameter.hints = kParameterIsAutomatable; parameter.ranges.min = 0.0f; parameter.ranges.max = 2.0f; parameter.ranges.def = 1.0f; @@ -338,12 +355,9 @@ START_NAMESPACE_DISTRHO */ void initProgramName(uint32_t index, String& programName) { - switch(index) - { - case 0: - programName = "Default"; - break; - } + // we only have one program so we can skip checking the index + + programName = "Default"; } /* ---------------------------------------------------------------------------------------- @@ -384,13 +398,10 @@ START_NAMESPACE_DISTRHO */ void loadProgram(uint32_t index) { - switch(index) - { - case 0: - fGainL = 1.0f; - fGainR = 1.0f; - break; - } + // same as before, ignore index check + + fGainL = 1.0f; + fGainR = 1.0f; } /* ---------------------------------------------------------------------------------------- @@ -508,6 +519,20 @@ START_NAMESPACE_DISTRHO #define DISTRHO_PLUGIN_IS_SYNTH 1 /** + Request the minimum buffer size for the input and output event ports.@n + Currently only used in LV2, with a default value of 2048 if unset. + */ +#define DISTRHO_PLUGIN_MINIMUM_BUFFER_SIZE 2048 + +/** + Whether the plugin has an LV2 modgui. + + This will simply add a "rdfs:seeAlso <modgui.ttl>" on the LV2 manifest.@n + It is up to you to create this file. + */ +#define DISTRHO_PLUGIN_USES_MODGUI 0 + +/** Enable direct access between the %UI and plugin code. @see UI::getPluginInstancePointer() @note DO NOT USE THIS UNLESS STRICTLY NECESSARY!! @@ -613,8 +638,216 @@ START_NAMESPACE_DISTRHO */ #define DISTRHO_UI_URI DISTRHO_PLUGIN_URI "#UI" +/** + Custom LV2 category for the plugin.@n + This can be one of the following values: + + - lv2:Plugin + - lv2:AllpassPlugin + - lv2:AmplifierPlugin + - lv2:AnalyserPlugin + - lv2:BandpassPlugin + - lv2:ChorusPlugin + - lv2:CombPlugin + - lv2:CompressorPlugin + - lv2:ConstantPlugin + - lv2:ConverterPlugin + - lv2:DelayPlugin + - lv2:DistortionPlugin + - lv2:DynamicsPlugin + - lv2:EQPlugin + - lv2:EnvelopePlugin + - lv2:ExpanderPlugin + - lv2:FilterPlugin + - lv2:FlangerPlugin + - lv2:FunctionPlugin + - lv2:GatePlugin + - lv2:GeneratorPlugin + - lv2:HighpassPlugin + - lv2:InstrumentPlugin + - lv2:LimiterPlugin + - lv2:LowpassPlugin + - lv2:MIDIPlugin + - lv2:MixerPlugin + - lv2:ModulatorPlugin + - lv2:MultiEQPlugin + - lv2:OscillatorPlugin + - lv2:ParaEQPlugin + - lv2:PhaserPlugin + - lv2:PitchPlugin + - lv2:ReverbPlugin + - lv2:SimulatorPlugin + - lv2:SpatialPlugin + - lv2:SpectralPlugin + - lv2:UtilityPlugin + - lv2:WaveshaperPlugin + + See http://lv2plug.in/ns/lv2core for more information. + */ +#define DISTRHO_PLUGIN_LV2_CATEGORY "lv2:Plugin" + +/** + Custom VST3 categories for the plugin.@n + This is a list of categories, separated by a @c |. + + Each effect category can be one of the following values: + + - Fx + - Fx|Ambisonics + - Fx|Analyzer + - Fx|Delay + - Fx|Distortion + - Fx|Dynamics + - Fx|EQ + - Fx|Filter + - Fx|Instrument + - Fx|Instrument|External + - Fx|Spatial + - Fx|Generator + - Fx|Mastering + - Fx|Modulation + - Fx|Network + - Fx|Pitch Shift + - Fx|Restoration + - Fx|Reverb + - Fx|Surround + - Fx|Tools + + Each instrument category can be one of the following values: + + - Instrument + - Instrument|Drum + - Instrument|External + - Instrument|Piano + - Instrument|Sampler + - Instrument|Synth + - Instrument|Synth|Sampler + + @note DPF will automatically set Mono and Stereo categories when appropriate. + */ +#define DISTRHO_PLUGIN_VST3_CATEGORIES "Fx" + +/** @} */ + +/* ------------------------------------------------------------------------------------------------------------ + * Plugin Macros */ + +/** + @defgroup ExtraPluginMacros Extra Plugin Macros + + C Macros to customize DPF behaviour. + + These are macros that do not set plugin features or information, but instead change DPF internals. + They are all optional. + + Unless stated otherwise, values are assumed to be a simple/empty define. + @{ + */ + +/** + Whether to enable runtime plugin tests.@n + This will check, during initialization of the plugin, if parameters, programs and states are setup properly.@n + Useful to enable as part of CI, can safely be skipped.@n + Under DPF makefiles this can be enabled by using `make DPF_RUNTIME_TESTING=true`. + + @note Some checks are only available with the GCC compiler, + for detecting if a virtual function has been reimplemented. + */ +#define DPF_RUNTIME_TESTING + +/** + Whether to show parameter outputs in the VST2 plugins.@n + This is disabled (unset) by default, as the VST2 format has no notion of read-only parameters. + */ +#define DPF_VST_SHOW_PARAMETER_OUTPUTS + +/** + Disable all file browser related code.@n + Must be set as compiler macro when building DGL. (e.g. `CXXFLAGS="-DDGL_FILE_BROWSER_DISABLED"`) + */ +#define DGL_FILE_BROWSER_DISABLED + +/** + Disable resource files, like internally used fonts.@n + Must be set as compiler macro when building DGL. (e.g. `CXXFLAGS="-DDGL_NO_SHARED_RESOURCES"`) + */ +#define DGL_NO_SHARED_RESOURCES + +/** + Whether to use OpenGL3 instead of the default OpenGL2 compatility profile. + Under DPF makefiles this can be enabled by using `make USE_OPENGL3=true` on the dgl build step. + + @note This is experimental and incomplete, contributions are welcome and appreciated. + */ +#define DGL_USE_OPENGL3 + +/** + Whether to use the GPLv2+ vestige header instead of the official Steinberg VST2 SDK.@n + This is a boolean, and enabled (set to 1) by default.@n + Set this to 0 in order to create non-GPL binaries. + (but then at your own discretion in regards to Steinberg licensing)@n + When set to 0, DPF will import the VST2 definitions from `"vst/aeffectx.h"` (not shipped with DPF). + */ +#define VESTIGE_HEADER 1 + /** @} */ +/* ------------------------------------------------------------------------------------------------------------ + * Namespace Macros */ + +/** + @defgroup NamespaceMacros Namespace Macros + + C Macros to use and customize DPF namespaces. + + These are macros that serve as helpers around C++ namespaces, and also as a way to set custom namespaces during a build. + @{ + */ + +/** + Compiler macro that sets the C++ namespace for DPF plugins.@n + If unset during build, it will use the name @b DISTRHO by default. + + Unless you know exactly what you are doing, you do need to modify this value.@n + The only probable useful case for customizing it is if you are building a big collection of very similar DPF-based plugins in your application.@n + For example, having 2 different versions of the same plugin that should behave differently but still exist within the same binary. + + On macOS (where due to Objective-C restrictions all code that interacts with Cocoa needs to be in a flat namespace), + DPF will automatically use the plugin name as prefix to flat namespace functions in order to avoid conflicts. + + So, basically, it is DPF's job to make sure plugin binaries are 100% usable as-is.@n + You typically do not need to care about this at all. + */ +#define DISTRHO_NAMESPACE DISTRHO + +/** + Compiler macro that begins the C++ namespace for @b DISTRHO, as needed for (the DSP side of) plugins.@n + All classes in DPF are within this namespace except for UI/graphics stuff. + @see END_NAMESPACE_DISTRHO + */ +#define START_NAMESPACE_DISTRHO namespace DISTRHO_NAMESPACE { + +/** + Close the namespace previously started by @ref START_NAMESPACE_DISTRHO.@n + This doesn't really need to be a macro, it is just prettier/more consistent that way. + */ +#define END_NAMESPACE_DISTRHO } + +/** + Make the @b DISTRHO namespace available in the current function scope.@n + This is not set by default in order to avoid conflicts with commonly used names such as "Parameter" and "Plugin". + */ +#define USE_NAMESPACE_DISTRHO using namespace DISTRHO_NAMESPACE; + +/* TODO + * + * DISTRHO_MACRO_AS_STRING_VALUE + * DISTRHO_MACRO_AS_STRING + * DISTRHO_PROPER_CPP11_SUPPORT + * DONT_SET_USING_DISTRHO_NAMESPACE + * + */ + // ----------------------------------------------------------------------------------------------------------- END_NAMESPACE_DISTRHO diff --git a/distrho/DistrhoPlugin.hpp b/distrho/DistrhoPlugin.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -40,7 +40,9 @@ START_NAMESPACE_DISTRHO static const uint32_t kAudioPortIsCV = 0x1; /** - Audio port should be used as sidechan (LV2 only). + Audio port should be used as sidechan (LV2 and VST3 only). + This hint should not be used with CV style ports. + @note non-sidechain audio ports must exist in the plugin if this flag is set. */ static const uint32_t kAudioPortIsSidechain = 0x2; @@ -84,10 +86,14 @@ static const uint32_t kCVPortHasScaledRange = 0x80; */ /** - Parameter is automable (real-time safe). + Parameter is automatable (real-time safe). @see Plugin::setParameterValue(uint32_t, float) */ -static const uint32_t kParameterIsAutomable = 0x01; +static const uint32_t kParameterIsAutomatable = 0x01; + +/** It was a typo, sorry.. */ +DISTRHO_DEPRECATED_BY("kParameterIsAutomatable") +static const uint32_t kParameterIsAutomable = kParameterIsAutomatable; /** Parameter value is boolean.@n @@ -130,6 +136,52 @@ static const uint32_t kParameterIsTrigger = 0x20 | kParameterIsBoolean; /** @} */ /* ------------------------------------------------------------------------------------------------------------ + * State Hints */ + +/** + @defgroup StateHints State Hints + + Various state hints. + @see State::hints + @{ + */ + +/** + State is visible and readable by hosts that support string-type plugin parameters. + */ +static const uint32_t kStateIsHostReadable = 0x01; + +/** + State is writable by the host, allowing users to arbitrarily change the state.@n + For obvious reasons a writable state is also readable by the host. + */ +static const uint32_t kStateIsHostWritable = 0x02 | kStateIsHostReadable; + +/** + State is a filename path instead of a regular string.@n + The readable and writable hints are required for filenames to work, and thus are automatically set. + */ +static const uint32_t kStateIsFilenamePath = 0x04 | kStateIsHostWritable; + +/** + State is a base64 encoded string. + */ +static const uint32_t kStateIsBase64Blob = 0x08; + +/** + State is for Plugin/DSP side only, meaning there is never a need to notify the UI when it changes. + */ +static const uint32_t kStateIsOnlyForDSP = 0x10; + +/** + State is for UI side only.@n + If the DSP and UI are separate and the UI is not available, this property won't be saved. + */ +static const uint32_t kStateIsOnlyForUI = 0x20; + +/** @} */ + +/* ------------------------------------------------------------------------------------------------------------ * Base Plugin structs */ /** @@ -561,7 +613,7 @@ struct Parameter { case kParameterDesignationNull: break; case kParameterDesignationBypass: - hints = kParameterIsAutomable|kParameterIsBoolean|kParameterIsInteger; + hints = kParameterIsAutomatable|kParameterIsBoolean|kParameterIsInteger; name = "Bypass"; shortName = "Bypass"; symbol = "dpf_bypass"; @@ -584,6 +636,9 @@ struct Parameter { A group can be applied to both inputs and outputs (at the same time). The same group cannot be used in audio ports and parameters. + When both audio and parameter groups are used, audio groups MUST be defined first. + That is, group indexes start with audio ports, then parameters. + An audio port group logically combines ports which should be considered part of the same stream.@n For example, two audio ports in a group may form a stereo stream. @@ -611,6 +666,49 @@ struct PortGroup { }; /** + State. + + In DPF states refer to key:value string pairs, used to store arbitrary non-parameter data.@n + By default states are completely internal to the plugin and not visible by the host.@n + Flags can be set to allow hosts to see and/or change them. + + TODO API under construction + */ +struct State { + /** + Hints describing this state. + @note Changing these hints can break compatibility with previously saved data. + @see StateHints + */ + uint32_t hints; + + /** + The key or "symbol" of this state.@n + A state key is a short restricted name used as a machine and human readable identifier. + @note State keys MUST be unique within a plugin instance. + TODO define rules for allowed characters, must be usable as URI non-encoded parameters + */ + String key; + + /** + The default value of this state.@n + Can be left empty if considered a valid initial state. + */ + String defaultValue; + + /** + String representation of this state. + */ + String label; + + /** + An extensive description/comment about this state. + @note This value is optional and only used for LV2. + */ + String description; +}; + +/** MIDI event. */ struct MidiEvent { @@ -632,6 +730,9 @@ struct MidiEvent { /** MIDI data.@n If size > kDataSize, dataExt is used (otherwise null). + + When dataExt is used, the event holder is responsible for + keeping the pointer valid during the entirety of the run function. */ uint8_t data[kDataSize]; const uint8_t* dataExt; @@ -652,6 +753,8 @@ struct TimePosition { /** Current host transport position in frames. + @note This value is not always monotonic, + with some plugin hosts assigning it based on a source that can accumulate rounding errors. */ uint64_t frame; @@ -832,6 +935,22 @@ public: */ double getSampleRate() const noexcept; + /** + Get the bundle path where the plugin resides. + Can return null if the plugin is not available in a bundle (if it is a single binary). + @see getBinaryFilename + @see getResourcePath + */ + const char* getBundlePath() const noexcept; + + /** + Check if this plugin instance is a "dummy" one used for plugin meta-data/information export.@n + When true no processing will be done, the plugin is created only to extract information.@n + In DPF, LADSPA/DSSI, VST2 and VST3 formats create one global instance per plugin binary + while LV2 creates one when generating turtle meta-data. + */ + bool isDummyInstance() const noexcept; + #if DISTRHO_PLUGIN_WANT_TIMEPOS /** Get the current host transport time position.@n @@ -878,6 +997,19 @@ public: bool requestParameterValueChange(uint32_t index, float value) noexcept; #endif +#if DISTRHO_PLUGIN_WANT_STATE + /** + Set state value and notify the host about the change.@n + This function will call `setState()` and also trigger an update on the UI side as necessary.@n + It must not be called during run.@n + The state must be host readable. + @note this function does nothing on DSSI plugin format, as DSSI only supports UI->DSP messages. + + TODO API under construction + */ + bool updateStateValue(const char* key, const char* value) noexcept; +#endif + protected: /* -------------------------------------------------------------------------------------------------------- * Information */ @@ -943,7 +1075,7 @@ protected: Initialize the parameter @a index.@n This function will be called once, shortly after the plugin is created. */ - virtual void initParameter(uint32_t index, Parameter& parameter) = 0; + virtual void initParameter(uint32_t index, Parameter& parameter); /** Initialize the port group @a groupId.@n @@ -963,18 +1095,17 @@ protected: #if DISTRHO_PLUGIN_WANT_STATE /** - Set the state key and default value of @a index.@n + Initialize the state @a index.@n This function will be called once, shortly after the plugin is created.@n Must be implemented by your plugin class only if DISTRHO_PLUGIN_WANT_STATE is enabled. */ - virtual void initState(uint32_t index, String& stateKey, String& defaultStateValue) = 0; -#endif + virtual void initState(uint32_t index, State& state); -#if DISTRHO_PLUGIN_WANT_STATEFILES - /** - TODO API under construction - */ - virtual bool isStateFile(uint32_t index) = 0; + DISTRHO_DEPRECATED_BY("initState(uint32_t,State&)") + virtual void initState(uint32_t, String&, String&) {} + + DISTRHO_DEPRECATED_BY("initState(uint32_t,State&)") + virtual bool isStateFile(uint32_t) { return false; } #endif /* -------------------------------------------------------------------------------------------------------- @@ -984,15 +1115,15 @@ protected: Get the current value of a parameter.@n The host may call this function from any context, including realtime processing. */ - virtual float getParameterValue(uint32_t index) const = 0; + virtual float getParameterValue(uint32_t index) const; /** Change a parameter value.@n The host may call this function from any context, including realtime processing.@n - When a parameter is marked as automable, you must ensure no non-realtime operations are performed. + When a parameter is marked as automatable, you must ensure no non-realtime operations are performed. @note This function will only be called for parameter inputs. */ - virtual void setParameterValue(uint32_t index, float value) = 0; + virtual void setParameterValue(uint32_t index, float value); #if DISTRHO_PLUGIN_WANT_PROGRAMS /** @@ -1000,7 +1131,7 @@ protected: The host may call this function from any context, including realtime processing.@n Must be implemented by your plugin class only if DISTRHO_PLUGIN_WANT_PROGRAMS is enabled. */ - virtual void loadProgram(uint32_t index) = 0; + virtual void loadProgram(uint32_t index); #endif #if DISTRHO_PLUGIN_WANT_FULL_STATE @@ -1010,7 +1141,7 @@ protected: Must be implemented by your plugin class if DISTRHO_PLUGIN_WANT_FULL_STATE is enabled. @note The use of this function breaks compatibility with the DSSI format. */ - virtual String getState(const char* key) const = 0; + virtual String getState(const char* key) const; #endif #if DISTRHO_PLUGIN_WANT_STATE @@ -1018,7 +1149,7 @@ protected: Change an internal state @a key to @a value.@n Must be implemented by your plugin class only if DISTRHO_PLUGIN_WANT_STATE is enabled. */ - virtual void setState(const char* key, const char* value) = 0; + virtual void setState(const char* key, const char* value); #endif /* -------------------------------------------------------------------------------------------------------- @@ -1089,7 +1220,10 @@ private: */ /** - TODO. + Create an instance of the Plugin class.@n + This is the entry point for DPF plugins.@n + DPF will call this to either create an instance of your plugin for the host + or to fetch some initial information for internal caching. */ extern Plugin* createPlugin(); diff --git a/distrho/DistrhoPluginMain.cpp b/distrho/DistrhoPluginMain.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2016 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -29,6 +29,20 @@ # include "src/DistrhoPluginVST2.cpp" #elif defined(DISTRHO_PLUGIN_TARGET_VST3) # include "src/DistrhoPluginVST3.cpp" +#elif defined(DISTRHO_PLUGIN_TARGET_SHARED) +DISTRHO_PLUGIN_EXPORT DISTRHO_NAMESPACE::Plugin* createSharedPlugin(); +DISTRHO_PLUGIN_EXPORT DISTRHO_NAMESPACE::Plugin* createSharedPlugin() { return DISTRHO_NAMESPACE::createPlugin(); } +#elif defined(DISTRHO_PLUGIN_TARGET_STATIC) +START_NAMESPACE_DISTRHO +Plugin* createStaticPlugin() { return createPlugin(); } +END_NAMESPACE_DISTRHO #else # error unsupported format #endif + +#if defined(DISTRHO_PLUGIN_TARGET_JACK) +# define DISTRHO_IS_STANDALONE 1 +#else +# define DISTRHO_IS_STANDALONE 0 +#endif +#include "src/DistrhoUtils.cpp" diff --git a/distrho/DistrhoPluginUtils.hpp b/distrho/DistrhoPluginUtils.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2019 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -21,9 +21,62 @@ START_NAMESPACE_DISTRHO -// ----------------------------------------------------------------------------------------------------------- +/* ------------------------------------------------------------------------------------------------------------ + * Plugin related utilities */ + +/** + @defgroup PluginRelatedUtilities Plugin related utilities + + @{ + */ + +/** + Get the absolute filename of the plugin DSP/UI binary.@n + Under certain systems or plugin formats the binary will be inside the plugin bundle.@n + Also, in some formats or setups, the DSP and UI binaries are in different files. +*/ +const char* getBinaryFilename(); + +/** + Get a string representation of the current plugin format we are building against.@n + This can be "JACK/Standalone", "LADSPA", "DSSI", "LV2", "VST2" or "VST3".@n + This string is purely informational and must not be used to tweak plugin behaviour. + + @note DO NOT CHANGE PLUGIN BEHAVIOUR BASED ON PLUGIN FORMAT. +*/ +const char* getPluginFormatName() noexcept; /** + Get the path to where resources are stored within the plugin bundle.@n + Requires a valid plugin bundle path. + + Returns a path inside the bundle where the plugin is meant to store its resources in.@n + This path varies between systems and plugin formats, like so: + + - LV2: <bundle>/resources (can be stored anywhere inside the bundle really, DPF just uses this one) + - VST2 macOS: <bundle>/Contents/Resources + - VST2 non-macOS: <bundle>/resources (see note) + + The other non-mentioned formats do not support bundles.@n + + @note For VST2 on non-macOS systems, this assumes you have your plugin inside a dedicated directory + rather than only shipping with the binary (e.g. <myplugin.vst>/myplugin.dll) +*/ +const char* getResourcePath(const char* bundlePath) noexcept; + +/** @} */ + +/* ------------------------------------------------------------------------------------------------------------ + * Plugin helper classes */ + +/** + @defgroup PluginHelperClasses Plugin helper classes + + @{ + */ + +#if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 +/** Handy class to help keep audio buffer in sync with incoming MIDI events. To use it, create a local variable (on the stack) and call nextEvent() until it returns false. @code @@ -44,11 +97,11 @@ START_NAMESPACE_DISTRHO Some important notes when using this class: 1. MidiEvent::frame retains its original value, but it is useless, do not use it. - 2. The class variables names are be the same as the default ones in the run function. + 2. The class variable names are the same as the default ones in the run function. Keep that in mind and try to avoid typos. :) */ -class AudioMidiSyncHelper { -public: +struct AudioMidiSyncHelper +{ /** Parameters from the run function, adjusted for event sync */ float* outputs[DISTRHO_PLUGIN_NUM_OUTPUTS]; uint32_t frames; @@ -151,6 +204,9 @@ private: uint32_t remainingMidiEventCount; uint32_t totalFramesUsed; }; +#endif + +/** @} */ // ----------------------------------------------------------------------------------------------------------- diff --git a/distrho/DistrhoStandaloneUtils.hpp b/distrho/DistrhoStandaloneUtils.hpp @@ -0,0 +1,99 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DISTRHO_STANDALONE_UTILS_HPP_INCLUDED +#define DISTRHO_STANDALONE_UTILS_HPP_INCLUDED + +#include "src/DistrhoDefines.h" + +START_NAMESPACE_DISTRHO + +/* ------------------------------------------------------------------------------------------------------------ + * Standalone plugin related utilities */ + +/** + @defgroup StandalonePluginRelatedUtilities Plugin related utilities + + When the plugin is running as standalone and JACK is not available, a native audio handling is in place. + It is a very simple handling, auto-connecting to the default audio interface for outputs. + + !!EXPERIMENTAL!! + + Still under development and testing. + + @{ + */ + +/** + Check if the current standalone is using native audio methods. + If this function returns false, you MUST NOT use any other function from this group. +*/ +bool isUsingNativeAudio() noexcept; + +/** + Check if the current standalone supports audio input. +*/ +bool supportsAudioInput(); + +/** + Check if the current standalone supports dynamic buffer size changes. +*/ +bool supportsBufferSizeChanges(); + +/** + Check if the current standalone supports MIDI. +*/ +bool supportsMIDI(); + +/** + Check if the current standalone has audio input enabled. +*/ +bool isAudioInputEnabled(); + +/** + Check if the current standalone has MIDI enabled. +*/ +bool isMIDIEnabled(); + +/** + Get the current buffer size. +*/ +uint getBufferSize(); + +/** + Request permissions to use audio input. + Only valid to call if audio input is supported but not currently enabled. +*/ +bool requestAudioInput(); + +/** + Request change to a new buffer size. +*/ +bool requestBufferSizeChange(uint newBufferSize); + +/** + Request permissions to use MIDI. + Only valid to call if MIDI is supported but not currently enabled. +*/ +bool requestMIDI(); + +/** @} */ + +// ----------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO + +#endif // DISTRHO_STANDALONE_UTILS_HPP_INCLUDED diff --git a/distrho/DistrhoUI.hpp b/distrho/DistrhoUI.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -48,12 +48,14 @@ typedef DGL_NAMESPACE::NanoTopLevelWidget UIWidget; typedef DGL_NAMESPACE::TopLevelWidget UIWidget; #endif -START_NAMESPACE_DGL -class PluginWindow; -END_NAMESPACE_DGL +#if DISTRHO_UI_FILE_BROWSER +# include "extra/FileBrowserDialog.hpp" +#endif START_NAMESPACE_DISTRHO +class PluginWindow; + /* ------------------------------------------------------------------------------------------------------------ * DPF UI */ @@ -80,12 +82,12 @@ public: It assumes aspect ratio is meant to be kept. Manually call setGeometryConstraints instead if keeping UI aspect ratio is not required. */ - UI(uint width = 0, uint height = 0, bool automaticallyScale = false); + UI(uint width = 0, uint height = 0, bool automaticallyScaleAndSetAsMinimumSize = false); /** Destructor. */ - virtual ~UI(); + ~UI() override; /* -------------------------------------------------------------------------------------------------------- * Host state */ @@ -132,6 +134,13 @@ public: double getSampleRate() const noexcept; /** + Get the bundle path where the UI resides.@n + Can return null if the UI is not available in a bundle (if it is a single binary). + @see getBinaryFilename + */ + const char* getBundlePath() const noexcept; + + /** editParameter. Touch/pressed-down event. @@ -153,9 +162,7 @@ public: @TODO Document this. */ void setState(const char* key, const char* value); -#endif -#if DISTRHO_PLUGIN_WANT_STATEFILES /** Request a new file from the host, matching the properties of a state key.@n This will use the native host file browser if available, otherwise a DPF built-in file browser is used.@n @@ -176,6 +183,22 @@ public: void sendNote(uint8_t channel, uint8_t note, uint8_t velocity); #endif +#if DISTRHO_UI_FILE_BROWSER + /** + Open a file browser dialog with this window as transient parent.@n + A few options can be specified to setup the dialog. + + If a path is selected, onFileSelected() will be called with the user chosen path. + If the user cancels or does not pick a file, onFileSelected() will be called with nullptr as filename. + + This function does not block the event loop. + + @note This is exactly the same API as provided by the Window class, + but redeclared here so that non-embed/DGL based UIs can still use file browser related functions. + */ + bool openFileBrowser(const DISTRHO_NAMESPACE::FileBrowserOptions& options = FileBrowserOptions()); +#endif + #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS /* -------------------------------------------------------------------------------------------------------- * Direct DSP access - DO NOT USE THIS UNLESS STRICTLY NECESSARY!! */ @@ -272,6 +295,23 @@ protected: #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI /** + Get the types available for the data in a clipboard. + Must only be called within the context of uiClipboardDataOffer. + */ + std::vector<DGL_NAMESPACE::ClipboardDataOffer> getClipboardDataOfferTypes(); + + /** + Window clipboard data offer function, called when clipboard has data present, possibly with several datatypes. + While handling this event, the data types can be investigated with getClipboardDataOfferTypes() to decide whether to accept the offer. + + Reimplement and return a non-zero id to accept the clipboard data offer for a particular type. + UIs must ignore any type they do not recognize. + + The default implementation accepts the "text/plain" mimetype. + */ + virtual uint32_t uiClipboardDataOffer(); + + /** Windows focus function, called when the window gains or loses the keyboard focus. This function is for plugin UIs to be able to override Window::onFocus(bool, CrossingMode). @@ -290,8 +330,9 @@ protected: The most common exception is custom OpenGL setup, but only really needed for custom OpenGL drawing code. */ virtual void uiReshape(uint width, uint height); +#endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI -# ifndef DGL_FILE_BROWSER_DISABLED +#if DISTRHO_UI_FILE_BROWSER /** Window file selected function, called when a path is selected by the user, as triggered by openFileBrowser(). This function is for plugin UIs to be able to override Window::onFileSelected(const char*). @@ -299,11 +340,10 @@ protected: This action happens after the user confirms the action, so the file browser dialog will be closed at this point. The default implementation does nothing. - If you need to use files as plugin state, please setup and use DISTRHO_PLUGIN_WANT_STATEFILES instead. + If you need to use files as plugin state, please setup and use states with kStateIsFilenamePath instead. */ virtual void uiFileBrowserSelected(const char* filename); -# endif -#endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI +#endif /* -------------------------------------------------------------------------------------------------------- * UI Resize Handling, internal */ @@ -329,8 +369,12 @@ protected: private: struct PrivateData; PrivateData* const uiData; - friend class DGL_NAMESPACE::PluginWindow; + friend class PluginWindow; friend class UIExporter; +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + /** @internal */ + void requestSizeChange(uint width, uint height) override; +#endif DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(UI) }; diff --git a/distrho/DistrhoUIMain.cpp b/distrho/DistrhoUIMain.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -27,7 +27,18 @@ #elif defined(DISTRHO_PLUGIN_TARGET_VST2) // nothing #elif defined(DISTRHO_PLUGIN_TARGET_VST3) +# include "src/DistrhoUIVST3.cpp" +#elif defined(DISTRHO_PLUGIN_TARGET_SHARED) || defined(DISTRHO_PLUGIN_TARGET_STATIC) // nothing #else # error unsupported format #endif + +#if !DISTRHO_PLUGIN_WANT_DIRECT_ACCESS && !defined(DISTRHO_PLUGIN_TARGET_CARLA) && !defined(DISTRHO_PLUGIN_TARGET_JACK) && !defined(DISTRHO_PLUGIN_TARGET_VST2) && !defined(DISTRHO_PLUGIN_TARGET_VST3) +# ifdef DISTRHO_PLUGIN_TARGET_DSSI +# define DISTRHO_IS_STANDALONE 1 +# else +# define DISTRHO_IS_STANDALONE 0 +# endif +# include "src/DistrhoUtils.cpp" +#endif diff --git a/distrho/DistrhoUI_macOS.mm b/distrho/DistrhoUI_macOS.mm @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -14,18 +14,28 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifndef PUGL_NAMESPACE -# error PUGL_NAMESPACE must be set when compiling this file -#endif +// A few utils declared in DistrhoUI.cpp but defined here because they use Obj-C #include "src/DistrhoPluginChecks.h" #include "src/DistrhoDefines.h" -#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI -#import <Cocoa/Cocoa.h> -#include <algorithm> -#include <cmath> +#if DISTRHO_UI_FILE_BROWSER || DISTRHO_PLUGIN_HAS_EXTERNAL_UI +# import <Cocoa/Cocoa.h> +#endif + +#if DISTRHO_UI_FILE_BROWSER +# define DISTRHO_FILE_BROWSER_DIALOG_HPP_INCLUDED +# define FILE_BROWSER_DIALOG_NAMESPACE DISTRHO_NAMESPACE +# define FILE_BROWSER_DIALOG_DISTRHO_NAMESPACE +START_NAMESPACE_DISTRHO +# include "extra/FileBrowserDialogImpl.hpp" +END_NAMESPACE_DISTRHO +# include "extra/FileBrowserDialogImpl.cpp" +#endif +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI +# include <algorithm> +# include <cmath> START_NAMESPACE_DISTRHO double getDesktopScaleFactor(const uintptr_t parentWindowHandle) { @@ -40,19 +50,4 @@ double getDesktopScaleFactor(const uintptr_t parentWindowHandle) return [NSScreen mainScreen].backingScaleFactor; } END_NAMESPACE_DISTRHO -#else // DISTRHO_PLUGIN_HAS_EXTERNAL_UI -#include "../dgl/Base.hpp" - -#define DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(DGL_NS, SEP, PUGL_NS, INTERFACE) DGL_NS ## SEP ## PUGL_NS ## SEP ## INTERFACE -#define DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NS, PUGL_NS, INTERFACE) DISTRHO_MACOS_NAMESPACE_MACRO_HELPER(DGL_NS, _, PUGL_NS, INTERFACE) - -#define PuglCairoView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, CairoView) -#define PuglOpenGLView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, OpenGLView) -#define PuglStubView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, StubView) -#define PuglVulkanView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, VulkanView) -#define PuglWindow DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, Window) -#define PuglWindowDelegate DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, WindowDelegate) -#define PuglWrapperView DISTRHO_MACOS_NAMESPACE_MACRO(DGL_NAMESPACE, PUGL_NAMESPACE, WrapperView) - -#import "src/pugl.mm" -#endif // DISTRHO_PLUGIN_HAS_EXTERNAL_UI +#endif diff --git a/distrho/DistrhoUtils.hpp b/distrho/DistrhoUtils.hpp @@ -58,11 +58,18 @@ inline float round(float __x) #define DISTRHO_MACRO_AS_STRING_VALUE(MACRO) #MACRO #define DISTRHO_MACRO_AS_STRING(MACRO) DISTRHO_MACRO_AS_STRING_VALUE(MACRO) -// ----------------------------------------------------------------------- -// misc functions +/* ------------------------------------------------------------------------------------------------------------ + * misc functions */ -/* - * Return a 64-bit number from 4 8-bit numbers. +/** + @defgroup MiscellaneousFunctions Miscellaneous functions + + @{ + */ + +/** + Return a 32-bit number from 4 8-bit numbers.@n + The return type is a int64_t for better compatibility with plugin formats that use such numbers. */ static inline constexpr int64_t d_cconst(const uint8_t a, const uint8_t b, const uint8_t c, const uint8_t d) noexcept @@ -70,8 +77,8 @@ int64_t d_cconst(const uint8_t a, const uint8_t b, const uint8_t c, const uint8_ return (a << 24) | (b << 16) | (c << 8) | (d << 0); } -/* - * Return an hexadecimal representation of a MAJ.MIN.MICRO version number. +/** + Return an hexadecimal representation of a MAJ.MIN.MICRO version number. */ static inline constexpr uint32_t d_version(const uint8_t major, const uint8_t minor, const uint8_t micro) noexcept @@ -79,18 +86,26 @@ uint32_t d_version(const uint8_t major, const uint8_t minor, const uint8_t micro return uint32_t(major << 16) | uint32_t(minor << 8) | (micro << 0); } -/* - * Dummy function. +/** + Dummy, no-op function. */ static inline void d_pass() noexcept {} -// ----------------------------------------------------------------------- -// string print functions +/** @} */ -/* - * Print a string to stdout with newline (gray color). - * Does nothing if DEBUG is not defined. +/* ------------------------------------------------------------------------------------------------------------ + * string print functions */ + +/** + @defgroup StringPrintFunctions String print functions + + @{ + */ + +/** + Print a string to stdout with newline (gray color). + Does nothing if DEBUG is not defined. */ #ifndef DEBUG # define d_debug(...) @@ -109,8 +124,8 @@ void d_debug(const char* const fmt, ...) noexcept } #endif -/* - * Print a string to stdout with newline. +/** + Print a string to stdout with newline. */ static inline void d_stdout(const char* const fmt, ...) noexcept @@ -124,8 +139,8 @@ void d_stdout(const char* const fmt, ...) noexcept } catch (...) {} } -/* - * Print a string to stderr with newline. +/** + Print a string to stderr with newline. */ static inline void d_stderr(const char* const fmt, ...) noexcept @@ -139,8 +154,8 @@ void d_stderr(const char* const fmt, ...) noexcept } catch (...) {} } -/* - * Print a string to stderr with newline (red color). +/** + Print a string to stderr with newline (red color). */ static inline void d_stderr2(const char* const fmt, ...) noexcept @@ -155,8 +170,8 @@ void d_stderr2(const char* const fmt, ...) noexcept } catch (...) {} } -/* - * Print a safe assertion error message. +/** + Print a safe assertion error message. */ static inline void d_safe_assert(const char* const assertion, const char* const file, const int line) noexcept @@ -164,8 +179,8 @@ void d_safe_assert(const char* const assertion, const char* const file, const in d_stderr2("assertion failure: \"%s\" in file %s, line %i", assertion, file, line); } -/* - * Print a safe assertion error message, with 1 extra signed integer value. +/** + Print a safe assertion error message, with 1 extra signed integer value. */ static inline void d_safe_assert_int(const char* const assertion, const char* const file, @@ -174,8 +189,8 @@ void d_safe_assert_int(const char* const assertion, const char* const file, d_stderr2("assertion failure: \"%s\" in file %s, line %i, value %i", assertion, file, line, value); } -/* - * Print a safe assertion error message, with 1 extra unsigned integer value. +/** + Print a safe assertion error message, with 1 extra unsigned integer value. */ static inline void d_safe_assert_uint(const char* const assertion, const char* const file, @@ -184,8 +199,8 @@ void d_safe_assert_uint(const char* const assertion, const char* const file, d_stderr2("assertion failure: \"%s\" in file %s, line %i, value %u", assertion, file, line, value); } -/* - * Print a safe assertion error message, with 2 extra signed integer values. +/** + Print a safe assertion error message, with 2 extra signed integer values. */ static inline void d_safe_assert_int2(const char* const assertion, const char* const file, @@ -194,8 +209,8 @@ void d_safe_assert_int2(const char* const assertion, const char* const file, d_stderr2("assertion failure: \"%s\" in file %s, line %i, v1 %i, v2 %i", assertion, file, line, v1, v2); } -/* - * Print a safe assertion error message, with 2 extra unsigned integer values. +/** + Print a safe assertion error message, with 2 extra unsigned integer values. */ static inline void d_safe_assert_uint2(const char* const assertion, const char* const file, @@ -204,8 +219,8 @@ void d_safe_assert_uint2(const char* const assertion, const char* const file, d_stderr2("assertion failure: \"%s\" in file %s, line %i, v1 %u, v2 %u", assertion, file, line, v1, v2); } -/* - * Print a safe assertion error message, with a custom error message. +/** + Print a safe assertion error message, with a custom error message. */ static inline void d_custom_safe_assert(const char* const message, const char* const assertion, const char* const file, @@ -214,8 +229,8 @@ void d_custom_safe_assert(const char* const message, const char* const assertion d_stderr2("assertion failure: %s, condition \"%s\" in file %s, line %i", message, assertion, file, line); } -/* - * Print a safe exception error message. +/** + Print a safe exception error message. */ static inline void d_safe_exception(const char* const exception, const char* const file, const int line) noexcept @@ -223,12 +238,20 @@ void d_safe_exception(const char* const exception, const char* const file, const d_stderr2("exception caught: \"%s\" in file %s, line %i", exception, file, line); } -// ----------------------------------------------------------------------- -// math functions +/** @} */ -/* - * Safely compare two floating point numbers. - * Returns true if they match. +/* ------------------------------------------------------------------------------------------------------------ + * math functions */ + +/** + @defgroup MathFunctions Math related functions + + @{ + */ + +/** + Safely compare two floating point numbers. + Returns true if they match. */ template<typename T> static inline @@ -237,9 +260,9 @@ bool d_isEqual(const T& v1, const T& v2) return std::abs(v1-v2) < std::numeric_limits<T>::epsilon(); } -/* - * Safely compare two floating point numbers. - * Returns true if they don't match. +/** + Safely compare two floating point numbers. + Returns true if they don't match. */ template<typename T> static inline @@ -248,8 +271,8 @@ bool d_isNotEqual(const T& v1, const T& v2) return std::abs(v1-v2) >= std::numeric_limits<T>::epsilon(); } -/* - * Safely check if a floating point number is zero. +/** + Safely check if a floating point number is zero. */ template<typename T> static inline @@ -258,8 +281,8 @@ bool d_isZero(const T& value) return std::abs(value) < std::numeric_limits<T>::epsilon(); } -/* - * Safely check if a floating point number is not zero. +/** + Safely check if a floating point number is not zero. */ template<typename T> static inline @@ -268,8 +291,8 @@ bool d_isNotZero(const T& value) return std::abs(value) >= std::numeric_limits<T>::epsilon(); } -/* - * Get next power of 2. +/** + Get next power of 2. */ static inline uint32_t d_nextPowerOf2(uint32_t size) noexcept @@ -286,6 +309,8 @@ uint32_t d_nextPowerOf2(uint32_t size) noexcept return ++size; } +/** @} */ + // ----------------------------------------------------------------------- #ifndef DONT_SET_USING_DISTRHO_NAMESPACE diff --git a/distrho/extra/ExternalWindow.hpp b/distrho/extra/ExternalWindow.hpp @@ -295,7 +295,8 @@ public: */ void setSize(uint width, uint height) { - DISTRHO_SAFE_ASSERT_UINT2_RETURN(width > 1 && height > 1, width, height,); + DISTRHO_SAFE_ASSERT_UINT_RETURN(width > 1, width,); + DISTRHO_SAFE_ASSERT_UINT_RETURN(height > 1, height,); if (pData.width == width && pData.height == height) return; @@ -314,10 +315,24 @@ public: { if (pData.title == title) return; + pData.title = title; titleChanged(title); } + /** + Set geometry constraints for the Window when resized by the user. + */ + void setGeometryConstraints(uint minimumWidth, uint minimumHeight, bool keepAspectRatio = false) + { + DISTRHO_SAFE_ASSERT_UINT_RETURN(minimumWidth > 0, minimumWidth,); + DISTRHO_SAFE_ASSERT_UINT_RETURN(minimumHeight > 0, minimumHeight,); + + pData.minWidth = minimumWidth; + pData.minHeight = minimumHeight; + pData.keepAspectRatio = keepAspectRatio; + } + /* -------------------------------------------------------------------------------------------------------- * TopLevelWidget-like calls - actions called by the host */ @@ -339,6 +354,7 @@ public: { if (pData.visible == visible) return; + pData.visible = visible; visibilityChanged(visible); } @@ -351,6 +367,7 @@ public: { if (pData.transientWinId == winId) return; + pData.transientWinId = winId; transientParentWindowChanged(winId); } @@ -388,39 +405,35 @@ protected: A callback for when the window size changes. @note WIP this might need to get fed back into the host somehow. */ - virtual void sizeChanged(uint width, uint height) + virtual void sizeChanged(uint /* width */, uint /* height */) { // unused, meant for custom implementations - return; (void)width; (void)height; } /** A callback for when the window title changes. @note WIP this might need to get fed back into the host somehow. */ - virtual void titleChanged(const char* title) + virtual void titleChanged(const char* /* title */) { // unused, meant for custom implementations - return; (void)title; } /** A callback for when the window visibility changes. @note WIP this might need to get fed back into the host somehow. */ - virtual void visibilityChanged(bool visible) + virtual void visibilityChanged(bool /* visible */) { // unused, meant for custom implementations - return; (void)visible; } /** A callback for when the transient parent window changes. */ - virtual void transientParentWindowChanged(uintptr_t winId) + virtual void transientParentWindowChanged(uintptr_t /* winId */) { // unused, meant for custom implementations - return; (void)winId; } private: @@ -533,6 +546,9 @@ private: uint height; double scaleFactor; String title; + uint minWidth; + uint minHeight; + bool keepAspectRatio; bool isQuitting; bool isStandalone; bool visible; @@ -544,6 +560,9 @@ private: height(1), scaleFactor(1.0), title(), + minWidth(0), + minHeight(0), + keepAspectRatio(false), isQuitting(false), isStandalone(false), visible(false) {} diff --git a/distrho/extra/FileBrowserDialog.hpp b/distrho/extra/FileBrowserDialog.hpp @@ -0,0 +1,28 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DISTRHO_FILE_BROWSER_DIALOG_HPP_INCLUDED +#define DISTRHO_FILE_BROWSER_DIALOG_HPP_INCLUDED + +#include "../DistrhoUtils.hpp" + +START_NAMESPACE_DISTRHO + +#include "FileBrowserDialogImpl.hpp" + +END_NAMESPACE_DISTRHO + +#endif // DISTRHO_FILE_BROWSER_DIALOG_HPP_INCLUDED diff --git a/distrho/extra/FileBrowserDialogImpl.cpp b/distrho/extra/FileBrowserDialogImpl.cpp @@ -0,0 +1,844 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#if !defined(DISTRHO_FILE_BROWSER_DIALOG_HPP_INCLUDED) && !defined(DGL_FILE_BROWSER_DIALOG_HPP_INCLUDED) +# error bad include +#endif +#if !defined(FILE_BROWSER_DIALOG_DISTRHO_NAMESPACE) && !defined(FILE_BROWSER_DIALOG_DGL_NAMESPACE) +# error bad usage +#endif + +#include "ScopedPointer.hpp" +#include "String.hpp" + +#ifdef DISTRHO_OS_MAC +# import <Cocoa/Cocoa.h> +#endif +#ifdef DISTRHO_OS_WASM +# include <emscripten/emscripten.h> +#endif +#ifdef DISTRHO_OS_WINDOWS +# include <direct.h> +# include <process.h> +# include <winsock2.h> +# include <windows.h> +# include <commdlg.h> +# include <vector> +#else +# include <unistd.h> +#endif +#ifdef HAVE_DBUS +# include <dbus/dbus.h> +#endif +#ifdef HAVE_X11 +# define DBLCLKTME 400 +# include "sofd/libsofd.h" +# include "sofd/libsofd.c" +#endif + +#ifdef FILE_BROWSER_DIALOG_DGL_NAMESPACE +START_NAMESPACE_DGL +using DISTRHO_NAMESPACE::ScopedPointer; +using DISTRHO_NAMESPACE::String; +#else +START_NAMESPACE_DISTRHO +#endif + +// -------------------------------------------------------------------------------------------------------------------- + +// static pointer used for signal null/none action taken +static const char* const kSelectedFileCancelled = "__dpf_cancelled__"; + +#ifdef HAVE_DBUS +static constexpr bool isHexChar(const char c) noexcept +{ + return c >= '0' && c <= 'f' && (c <= '9' || (c >= 'A' && c <= 'F') || c >= 'a'); +} + +static constexpr int toHexChar(const char c) noexcept +{ + return c >= '0' && c <= '9' ? c - '0' : (c >= 'A' && c <= 'F' ? c - 'A' : c - 'a') + 10; +} +#endif + +// -------------------------------------------------------------------------------------------------------------------- + +#ifdef DISTRHO_OS_WASM +# define DISTRHO_WASM_NAMESPACE_MACRO_HELPER(NS, SEP, FUNCTION) NS ## SEP ## FUNCTION +# define DISTRHO_WASM_NAMESPACE_MACRO(NS, FUNCTION) DISTRHO_WASM_NAMESPACE_MACRO_HELPER(NS, _, FUNCTION) +# define DISTRHO_WASM_NAMESPACE_HELPER(NS) #NS +# define DISTRHO_WASM_NAMESPACE(NS) DISTRHO_WASM_NAMESPACE_HELPER(NS) +# define fileBrowserSetPathNamespaced DISTRHO_WASM_NAMESPACE_MACRO(FILE_BROWSER_DIALOG_NAMESPACE, fileBrowserSetPath) +# define fileBrowserSetPathFuncName DISTRHO_WASM_NAMESPACE(FILE_BROWSER_DIALOG_NAMESPACE) "_fileBrowserSetPath" + +// FIXME use world class name as prefix +static bool openWebBrowserFileDialog(const char* const funcname, void* const handle) +{ + const char* const nameprefix = DISTRHO_WASM_NAMESPACE(FILE_BROWSER_DIALOG_NAMESPACE); + + return EM_ASM_INT({ + var canvasFileObjName = UTF8ToString($0) + "_file_open"; + var canvasFileOpenElem = document.getElementById(canvasFileObjName); + + var jsfuncname = UTF8ToString($1); + var jsfunc = Module.cwrap(jsfuncname, 'null', ['number', 'string']); + + if (canvasFileOpenElem) { + document.body.removeChild(canvasFileOpenElem); + } + + canvasFileOpenElem = document.createElement('input'); + canvasFileOpenElem.type = 'file'; + canvasFileOpenElem.id = canvasFileObjName; + canvasFileOpenElem.style.display = 'none'; + document.body.appendChild(canvasFileOpenElem); + + canvasFileOpenElem.onchange = function(e) { + if (!canvasFileOpenElem.files) { + jsfunc($2, ""); + return; + } + + var file = canvasFileOpenElem.files[0]; + var filename = '/' + file.name; + var reader = new FileReader(); + + reader.onloadend = function(e) { + var content = new Uint8Array(reader.result); + Module.FS.writeFile(filename, content); + jsfunc($2, filename); + }; + + reader.readAsArrayBuffer(file); + }; + + canvasFileOpenElem.click(); + return 1; + }, nameprefix, funcname, handle) != 0; +} + +static bool downloadWebBrowserFile(const char* const filename) +{ + const char* const nameprefix = DISTRHO_WASM_NAMESPACE(FILE_BROWSER_DIALOG_NAMESPACE); + + return EM_ASM_INT({ + var canvasFileObjName = UTF8ToString($0) + "_file_save"; + var jsfilename = UTF8ToString($1); + + var canvasFileSaveElem = document.getElementById(canvasFileObjName); + if (canvasFileSaveElem) { + // only 1 file save allowed at once + console.warn("One file save operation already in progress, refusing to open another"); + return 0; + } + + canvasFileSaveElem = document.createElement('a'); + canvasFileSaveElem.download = jsfilename; + canvasFileSaveElem.id = canvasFileObjName; + canvasFileSaveElem.style.display = 'none'; + document.body.appendChild(canvasFileSaveElem); + + var content = Module.FS.readFile('/' + jsfilename); + canvasFileSaveElem.href = URL.createObjectURL(new Blob([content])); + canvasFileSaveElem.click(); + + setTimeout(function() { + URL.revokeObjectURL(canvasFileSaveElem.href); + document.body.removeChild(canvasFileSaveElem); + }, 2000); + return 1; + }, nameprefix, filename) != 0; +} +#endif + +// -------------------------------------------------------------------------------------------------------------------- + +struct FileBrowserData { + const char* selectedFile; + +#ifdef DISTRHO_OS_MAC + NSSavePanel* nsBasePanel; + NSOpenPanel* nsOpenPanel; +#endif +#ifdef HAVE_DBUS + DBusConnection* dbuscon; +#endif +#ifdef HAVE_X11 + Display* x11display; +#endif + +#ifdef DISTRHO_OS_WASM + char* defaultName; + bool saving; +#endif + +#ifdef DISTRHO_OS_WINDOWS + OPENFILENAMEW ofn; + volatile bool threadCancelled; + uintptr_t threadHandle; + std::vector<WCHAR> fileNameW; + std::vector<WCHAR> startDirW; + std::vector<WCHAR> titleW; + const bool saving; + bool isEmbed; + + FileBrowserData(const bool save) + : selectedFile(nullptr), + threadCancelled(false), + threadHandle(0), + fileNameW(32768), + saving(save), + isEmbed(false) + { + std::memset(&ofn, 0, sizeof(ofn)); + ofn.lStructSize = sizeof(ofn); + ofn.lpstrFile = fileNameW.data(); + ofn.nMaxFile = (DWORD)fileNameW.size(); + } + + ~FileBrowserData() + { + if (cancelAndStop()) + free(); + } + + void setupAndStart(const bool embed, + const char* const startDir, + const char* const windowTitle, + const uintptr_t winId, + const FileBrowserOptions options) + { + isEmbed = embed; + + ofn.hwndOwner = (HWND)winId; + + ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR; + if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked) + ofn.Flags |= OFN_FORCESHOWHIDDEN; + + ofn.FlagsEx = 0x0; + if (options.buttons.showPlaces == FileBrowserOptions::kButtonInvisible) + ofn.FlagsEx |= OFN_EX_NOPLACESBAR; + + startDirW.resize(std::strlen(startDir) + 1); + if (MultiByteToWideChar(CP_UTF8, 0, startDir, -1, startDirW.data(), static_cast<int>(startDirW.size()))) + ofn.lpstrInitialDir = startDirW.data(); + + titleW.resize(std::strlen(windowTitle) + 1); + if (MultiByteToWideChar(CP_UTF8, 0, windowTitle, -1, titleW.data(), static_cast<int>(titleW.size()))) + ofn.lpstrTitle = titleW.data(); + + uint threadId; + threadCancelled = false; + threadHandle = _beginthreadex(nullptr, 0, _run, this, 0, &threadId); + } + + bool cancelAndStop() + { + threadCancelled = true; + + if (threadHandle == 0) + return true; + + // if previous dialog running, carefully close its window + const HWND owner = isEmbed ? GetParent(ofn.hwndOwner) : ofn.hwndOwner; + + if (owner != nullptr && owner != INVALID_HANDLE_VALUE) + { + const HWND window = GetWindow(owner, GW_HWNDFIRST); + + if (window != nullptr && window != INVALID_HANDLE_VALUE) + { + SendMessage(window, WM_SYSCOMMAND, SC_CLOSE, 0); + SendMessage(window, WM_CLOSE, 0, 0); + WaitForSingleObject((HANDLE)threadHandle, 5000); + } + } + + if (threadHandle == 0) + return true; + + // not good if thread still running, but let's close the handle anyway + CloseHandle((HANDLE)threadHandle); + threadHandle = 0; + return false; + } + + void run() + { + const char* nextFile = nullptr; + + if (saving ? GetSaveFileNameW(&ofn) : GetOpenFileNameW(&ofn)) + { + if (threadCancelled) + { + threadHandle = 0; + return; + } + + // back to UTF-8 + std::vector<char> fileNameA(4 * 32768); + if (WideCharToMultiByte(CP_UTF8, 0, fileNameW.data(), -1, + fileNameA.data(), (int)fileNameA.size(), + nullptr, nullptr)) + { + nextFile = strdup(fileNameA.data()); + } + } + + if (threadCancelled) + { + threadHandle = 0; + return; + } + + if (nextFile == nullptr) + nextFile = kSelectedFileCancelled; + + selectedFile = nextFile; + threadHandle = 0; + } + + static unsigned __stdcall _run(void* const arg) + { + // CoInitializeEx(nullptr, COINIT_MULTITHREADED); + static_cast<FileBrowserData*>(arg)->run(); + // CoUninitialize(); + _endthreadex(0); + return 0; + } +#else // DISTRHO_OS_WINDOWS + FileBrowserData(const bool save) + : selectedFile(nullptr) + { +#ifdef DISTRHO_OS_MAC + if (save) + { + nsOpenPanel = nullptr; + nsBasePanel = [[NSSavePanel savePanel]retain]; + } + else + { + nsOpenPanel = [[NSOpenPanel openPanel]retain]; + nsBasePanel = nsOpenPanel; + } +#endif +#ifdef DISTRHO_OS_WASM + defaultName = nullptr; + saving = save; +#endif +#ifdef HAVE_DBUS + if ((dbuscon = dbus_bus_get(DBUS_BUS_SESSION, nullptr)) != nullptr) + dbus_connection_set_exit_on_disconnect(dbuscon, false); +#endif +#ifdef HAVE_X11 + x11display = XOpenDisplay(nullptr); +#endif + + // maybe unused + return; (void)save; + } + + ~FileBrowserData() + { +#ifdef DISTRHO_OS_MAC + [nsBasePanel release]; +#endif +#ifdef DISTRHO_OS_WASM + std::free(defaultName); +#endif +#ifdef HAVE_DBUS + if (dbuscon != nullptr) + dbus_connection_unref(dbuscon); +#endif +#ifdef HAVE_X11 + if (x11display != nullptr) + XCloseDisplay(x11display); +#endif + + free(); + } +#endif + + void free() + { + if (selectedFile == nullptr) + return; + + if (selectedFile == kSelectedFileCancelled || std::strcmp(selectedFile, kSelectedFileCancelled) == 0) + { + selectedFile = nullptr; + return; + } + + std::free(const_cast<char*>(selectedFile)); + selectedFile = nullptr; + } +}; + +// -------------------------------------------------------------------------------------------------------------------- + +#ifdef DISTRHO_OS_WASM +extern "C" { +EMSCRIPTEN_KEEPALIVE +void fileBrowserSetPathNamespaced(FileBrowserHandle handle, const char* filename) +{ + handle->free(); + + if (filename != nullptr && filename[0] != '\0') + handle->selectedFile = strdup(filename); + else + handle->selectedFile = kSelectedFileCancelled; +} +} +#endif + +FileBrowserHandle fileBrowserCreate(const bool isEmbed, + const uintptr_t windowId, + const double scaleFactor, + const FileBrowserOptions& options) +{ + String startDir(options.startDir); + + if (startDir.isEmpty()) + { +#ifdef DISTRHO_OS_WINDOWS + if (char* const cwd = _getcwd(nullptr, 0)) +#else + if (char* const cwd = getcwd(nullptr, 0)) +#endif + { + startDir = cwd; + std::free(cwd); + } + } + + DISTRHO_SAFE_ASSERT_RETURN(startDir.isNotEmpty(), nullptr); + + if (! startDir.endsWith(DISTRHO_OS_SEP)) + startDir += DISTRHO_OS_SEP_STR; + + String windowTitle(options.title); + + if (windowTitle.isEmpty()) + windowTitle = "FileBrowser"; + + ScopedPointer<FileBrowserData> handle(new FileBrowserData(options.saving)); + +#ifdef DISTRHO_OS_MAC +# if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_8 + // unsupported + d_stderr2("fileBrowserCreate is unsupported on macos < 10.8"); + return nullptr; +# else + NSSavePanel* const nsBasePanel = handle->nsBasePanel; + DISTRHO_SAFE_ASSERT_RETURN(nsBasePanel != nullptr, nullptr); + + if (! options.saving) + { + NSOpenPanel* const nsOpenPanel = handle->nsOpenPanel; + DISTRHO_SAFE_ASSERT_RETURN(nsOpenPanel != nullptr, nullptr); + + [nsOpenPanel setAllowsMultipleSelection:NO]; + [nsOpenPanel setCanChooseDirectories:NO]; + [nsOpenPanel setCanChooseFiles:YES]; + } + + [nsBasePanel setDirectoryURL:[NSURL fileURLWithPath:[NSString stringWithUTF8String:startDir]]]; + + // TODO file filter using allowedContentTypes: [UTType] + + if (options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked) + [nsBasePanel setAllowsOtherFileTypes:YES]; + if (options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked) + [nsBasePanel setShowsHiddenFiles:YES]; + + NSString* const titleString = [[NSString alloc] + initWithBytes:windowTitle + length:strlen(windowTitle) + encoding:NSUTF8StringEncoding]; + [nsBasePanel setTitle:titleString]; + + FileBrowserData* const handleptr = handle.get(); + + dispatch_async(dispatch_get_main_queue(), ^ + { + [nsBasePanel beginSheetModalForWindow:[(NSView*)windowId window] + completionHandler:^(NSModalResponse result) + { + if (result == NSModalResponseOK && [[nsBasePanel URL] isFileURL]) + { + NSString* const path = [[nsBasePanel URL] path]; + handleptr->selectedFile = strdup([path UTF8String]); + } + else + { + handleptr->selectedFile = kSelectedFileCancelled; + } + }]; + }); +# endif +#endif + +#ifdef DISTRHO_OS_WASM + if (options.saving) + { + const size_t len = options.defaultName != nullptr ? strlen(options.defaultName) : 0; + DISTRHO_SAFE_ASSERT_RETURN(len != 0, nullptr); + + char* const filename = static_cast<char*>(malloc(len + 2)); + filename[0] = '/'; + std::memcpy(filename + 1, options.defaultName, len + 1); + + handle->defaultName = strdup(options.defaultName); + handle->selectedFile = filename; + return handle.release(); + } + + const char* const funcname = fileBrowserSetPathFuncName; + if (openWebBrowserFileDialog(funcname, handle.get())) + return handle.release(); + + return nullptr; +#endif + +#ifdef DISTRHO_OS_WINDOWS + handle->setupAndStart(isEmbed, startDir, windowTitle, windowId, options); +#endif + +#ifdef HAVE_DBUS + // optional, can be null + DBusConnection* const dbuscon = handle->dbuscon; + + // https://flatpak.github.io/xdg-desktop-portal/portal-docs.html#gdbus-org.freedesktop.portal.FileChooser + if (dbuscon != nullptr) + { + // if this is the first time we are calling into DBus, check if things are working + static bool checkAvailable = !dbus_bus_name_has_owner(dbuscon, "org.freedesktop.portal.Desktop", nullptr); + + if (checkAvailable) + { + checkAvailable = false; + + if (DBusMessage* const msg = dbus_message_new_method_call("org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.FileChooser", + "version")) + { + if (DBusMessage* const reply = dbus_connection_send_with_reply_and_block(dbuscon, msg, 250, nullptr)) + dbus_message_unref(reply); + + dbus_message_unref(msg); + } + } + + // Any subsquent calls should have this DBus service active + if (dbus_bus_name_has_owner(dbuscon, "org.freedesktop.portal.Desktop", nullptr)) + { + if (DBusMessage* const msg = dbus_message_new_method_call("org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.FileChooser", + options.saving ? "SaveFile" : "OpenFile")) + { + #ifdef HAVE_X11 + char windowIdStr[32]; + memset(windowIdStr, 0, sizeof(windowIdStr)); + snprintf(windowIdStr, sizeof(windowIdStr)-1, "x11:%llx", (ulonglong)windowId); + const char* windowIdStrPtr = windowIdStr; + #endif + + dbus_message_append_args(msg, + #ifdef HAVE_X11 + DBUS_TYPE_STRING, &windowIdStrPtr, + #endif + DBUS_TYPE_STRING, &windowTitle, + DBUS_TYPE_INVALID); + + DBusMessageIter iter, array; + dbus_message_iter_init_append(msg, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &array); + + { + DBusMessageIter dict, variant, variantArray; + const char* const current_folder_key = "current_folder"; + const char* const current_folder_val = startDir.buffer(); + + dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, nullptr, &dict); + dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &current_folder_key); + dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, "ay", &variant); + dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, "y", &variantArray); + dbus_message_iter_append_fixed_array(&variantArray, DBUS_TYPE_BYTE, + &current_folder_val, startDir.length()+1); + dbus_message_iter_close_container(&variant, &variantArray); + dbus_message_iter_close_container(&dict, &variant); + dbus_message_iter_close_container(&array, &dict); + } + + dbus_message_iter_close_container(&iter, &array); + + dbus_connection_send(dbuscon, msg, nullptr); + + dbus_message_unref(msg); + return handle.release(); + } + } + } +#endif + +#ifdef HAVE_X11 + Display* const x11display = handle->x11display; + DISTRHO_SAFE_ASSERT_RETURN(x11display != nullptr, nullptr); + + // unsupported at the moment + if (options.saving) + return nullptr; + + DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(0, startDir) == 0, nullptr); + DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(1, windowTitle) == 0, nullptr); + + const int button1 = options.buttons.showHidden == FileBrowserOptions::kButtonVisibleChecked ? 1 + : options.buttons.showHidden == FileBrowserOptions::kButtonVisibleUnchecked ? 0 : -1; + const int button2 = options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleChecked ? 1 + : options.buttons.showPlaces == FileBrowserOptions::kButtonVisibleUnchecked ? 0 : -1; + const int button3 = options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleChecked ? 1 + : options.buttons.listAllFiles == FileBrowserOptions::kButtonVisibleUnchecked ? 0 : -1; + + x_fib_cfg_buttons(1, button1); + x_fib_cfg_buttons(2, button2); + x_fib_cfg_buttons(3, button3); + + if (x_fib_show(x11display, windowId, 0, 0, scaleFactor + 0.5) != 0) + return nullptr; +#endif + + return handle.release(); + + // might be unused + (void)isEmbed; + (void)windowId; + (void)scaleFactor; +} + +// -------------------------------------------------------------------------------------------------------------------- +// returns true if dialog was closed (with or without a file selection) + +bool fileBrowserIdle(const FileBrowserHandle handle) +{ +#ifdef HAVE_DBUS + if (DBusConnection* dbuscon = handle->dbuscon) + { + while (dbus_connection_dispatch(dbuscon) == DBUS_DISPATCH_DATA_REMAINS) {} + dbus_connection_read_write_dispatch(dbuscon, 0); + + if (DBusMessage* const message = dbus_connection_pop_message(dbuscon)) + { + const char* const interface = dbus_message_get_interface(message); + const char* const member = dbus_message_get_member(message); + + if (interface != nullptr && std::strcmp(interface, "org.freedesktop.portal.Request") == 0 + && member != nullptr && std::strcmp(member, "Response") == 0) + { + do { + DBusMessageIter iter; + dbus_message_iter_init(message, &iter); + + // starts with uint32 for return/exit code + DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_UINT32); + + uint32_t ret = 1; + dbus_message_iter_get_basic(&iter, &ret); + + if (ret != 0) + break; + + // next must be array + dbus_message_iter_next(&iter); + DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_ARRAY); + + // open dict array + DBusMessageIter dictArray; + dbus_message_iter_recurse(&iter, &dictArray); + DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dictArray) == DBUS_TYPE_DICT_ENTRY); + + // open containing dict + DBusMessageIter dict; + dbus_message_iter_recurse(&dictArray, &dict); + + // look for dict with string "uris" + DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_STRING); + + const char* key = nullptr; + dbus_message_iter_get_basic(&dict, &key); + DISTRHO_SAFE_ASSERT_BREAK(key != nullptr); + + // keep going until we find it + while (std::strcmp(key, "uris") != 0) + { + key = nullptr; + dbus_message_iter_next(&dictArray); + DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dictArray) == DBUS_TYPE_DICT_ENTRY); + + dbus_message_iter_recurse(&dictArray, &dict); + DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_STRING); + + dbus_message_iter_get_basic(&dict, &key); + DISTRHO_SAFE_ASSERT_BREAK(key != nullptr); + } + + if (key == nullptr) + break; + + // then comes variant + dbus_message_iter_next(&dict); + DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_VARIANT); + + DBusMessageIter variant; + dbus_message_iter_recurse(&dict, &variant); + DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&variant) == DBUS_TYPE_ARRAY); + + // open variant array (variant type is string) + DBusMessageIter variantArray; + dbus_message_iter_recurse(&variant, &variantArray); + DISTRHO_SAFE_ASSERT_BREAK(dbus_message_iter_get_arg_type(&variantArray) == DBUS_TYPE_STRING); + + const char* value = nullptr; + dbus_message_iter_get_basic(&variantArray, &value); + + // and finally we have our dear value, just make sure it is local + DISTRHO_SAFE_ASSERT_BREAK(value != nullptr); + + if (const char* const localvalue = std::strstr(value, "file:///")) + { + if (char* const decodedvalue = strdup(localvalue + 7)) + { + for (char* s = decodedvalue; (s = std::strchr(s, '%')) != nullptr; ++s) + { + if (! isHexChar(s[1]) || ! isHexChar(s[2])) + continue; + + const int decodedNum = toHexChar(s[1]) * 0x10 + toHexChar(s[2]); + + char replacementChar; + switch (decodedNum) + { + case 0x20: replacementChar = ' '; break; + case 0x22: replacementChar = '\"'; break; + case 0x23: replacementChar = '#'; break; + case 0x25: replacementChar = '%'; break; + case 0x3c: replacementChar = '<'; break; + case 0x3e: replacementChar = '>'; break; + case 0x5b: replacementChar = '['; break; + case 0x5c: replacementChar = '\\'; break; + case 0x5d: replacementChar = ']'; break; + case 0x5e: replacementChar = '^'; break; + case 0x60: replacementChar = '`'; break; + case 0x7b: replacementChar = '{'; break; + case 0x7c: replacementChar = '|'; break; + case 0x7d: replacementChar = '}'; break; + case 0x7e: replacementChar = '~'; break; + default: continue; + } + + s[0] = replacementChar; + std::memmove(s + 1, s + 3, std::strlen(s) - 2); + } + + handle->selectedFile = decodedvalue; + } + } + + } while(false); + + if (handle->selectedFile == nullptr) + handle->selectedFile = kSelectedFileCancelled; + } + } + } +#endif + +#ifdef HAVE_X11 + Display* const x11display = handle->x11display; + + if (x11display == nullptr) + return false; + + XEvent event; + while (XPending(x11display) > 0) + { + XNextEvent(x11display, &event); + + if (x_fib_handle_events(x11display, &event) == 0) + continue; + + if (x_fib_status() > 0) + handle->selectedFile = x_fib_filename(); + else + handle->selectedFile = kSelectedFileCancelled; + + x_fib_close(x11display); + XCloseDisplay(x11display); + handle->x11display = nullptr; + break; + } +#endif + + return handle->selectedFile != nullptr; +} + +// -------------------------------------------------------------------------------------------------------------------- +// close sofd file dialog + +void fileBrowserClose(const FileBrowserHandle handle) +{ +#ifdef DISTRHO_OS_WASM + if (handle->saving && fileBrowserGetPath(handle) != nullptr) + downloadWebBrowserFile(handle->defaultName); +#endif + +#ifdef HAVE_X11 + if (Display* const x11display = handle->x11display) + x_fib_close(x11display); +#endif + + delete handle; +} + +// -------------------------------------------------------------------------------------------------------------------- +// get path chosen via sofd file dialog + +const char* fileBrowserGetPath(const FileBrowserHandle handle) +{ + if (const char* const selectedFile = handle->selectedFile) + if (selectedFile != kSelectedFileCancelled && std::strcmp(selectedFile, kSelectedFileCancelled) != 0) + return selectedFile; + + return nullptr; +} + +// -------------------------------------------------------------------------------------------------------------------- + +#ifdef FILE_BROWSER_DIALOG_DGL_NAMESPACE +END_NAMESPACE_DGL +#else +END_NAMESPACE_DISTRHO +#endif + +#undef FILE_BROWSER_DIALOG_DISTRHO_NAMESPACE +#undef FILE_BROWSER_DIALOG_DGL_NAMESPACE +#undef FILE_BROWSER_DIALOG_NAMESPACE + +#undef fileBrowserSetPathNamespaced +#undef fileBrowserSetPathFuncName diff --git a/distrho/extra/FileBrowserDialogImpl.hpp b/distrho/extra/FileBrowserDialogImpl.hpp @@ -0,0 +1,121 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#if !defined(DISTRHO_FILE_BROWSER_DIALOG_HPP_INCLUDED) && !defined(DGL_FILE_BROWSER_DIALOG_HPP_INCLUDED) +# error bad include +#endif + +// -------------------------------------------------------------------------------------------------------------------- +// File Browser Dialog stuff + +struct FileBrowserData; +typedef FileBrowserData* FileBrowserHandle; + +// -------------------------------------------------------------------------------------------------------------------- + +/** + File browser options, for customizing the file browser dialog.@n + By default the file browser dialog will be work as "open file" in the current working directory. +*/ +struct FileBrowserOptions { + /** Whether we are saving, opening files otherwise (default) */ + bool saving; + + /** Default filename when saving, required in some platforms (basename without path separators) */ + const char* defaultName; + + /** Start directory, uses current working directory if null */ + const char* startDir; + + /** File browser dialog window title, uses "FileBrowser" if null */ + const char* title; + + // TODO file filter + + /** + File browser button state. + This allows to customize the behaviour of the file browse dialog buttons. + Note these are merely hints, not all systems support them. + */ + enum ButtonState { + kButtonInvisible, + kButtonVisibleUnchecked, + kButtonVisibleChecked, + }; + + /** + File browser buttons. + */ + struct Buttons { + /** Whether to list all files vs only those with matching file extension */ + ButtonState listAllFiles; + /** Whether to show hidden files */ + ButtonState showHidden; + /** Whether to show list of places (bookmarks) */ + ButtonState showPlaces; + + /** Constructor for default values */ + Buttons() + : listAllFiles(kButtonVisibleChecked), + showHidden(kButtonVisibleUnchecked), + showPlaces(kButtonVisibleChecked) {} + } buttons; + + /** Constructor for default values */ + FileBrowserOptions() + : saving(false), + defaultName(nullptr), + startDir(nullptr), + title(nullptr), + buttons() {} +}; + +// -------------------------------------------------------------------------------------------------------------------- + +/** + Create a new file browser dialog. + + @p isEmbed: Whether the window this dialog belongs to is an embed/child window (needed to close dialog on Windows) + @p windowId: The native window id to attach this dialog to as transient parent (X11 Window, HWND or NSView*) + @p scaleFactor: Scale factor to use (only used on X11) + @p options: Extra options, optional + By default the file browser dialog will be work as "open file" in the current working directory. +*/ +FileBrowserHandle fileBrowserCreate(bool isEmbed, + uintptr_t windowId, + double scaleFactor, + const FileBrowserOptions& options = FileBrowserOptions()); + +/** + Idle the file browser dialog handle.@n + Returns true if dialog was closed (with or without a file selection), + in which case the handle must not be used afterwards. + You can then call fileBrowserGetPath to know the selected file (or null if cancelled). +*/ +bool fileBrowserIdle(const FileBrowserHandle handle); + +/** + Close the file browser dialog, handle must not be used afterwards. +*/ +void fileBrowserClose(const FileBrowserHandle handle); + +/** + Get the path chosen by the user or null.@n + Should only be called after fileBrowserIdle returns true. +*/ +const char* fileBrowserGetPath(const FileBrowserHandle handle); + +// -------------------------------------------------------------------------------------------------------------------- diff --git a/distrho/extra/LeakDetector.hpp b/distrho/extra/LeakDetector.hpp @@ -23,7 +23,25 @@ START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------- // The following code was based from juce-core LeakDetector class -// Copyright (C) 2013 Raw Material Software Ltd. + +/** + Copyright (C) 2013 Raw Material Software Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. +*/ /** A good old-fashioned C macro concatenation helper. This combines two items (which may themselves be macros) into a single string, diff --git a/distrho/extra/RingBuffer.hpp b/distrho/extra/RingBuffer.hpp @@ -203,11 +203,23 @@ public: /* * Get the size of the data available to read. */ - uint32_t getAvailableDataSize() const noexcept + uint32_t getReadableDataSize() const noexcept { DISTRHO_SAFE_ASSERT_RETURN(buffer != nullptr, 0); - const uint32_t wrap((buffer->tail > buffer->wrtn) ? 0 : buffer->size); + const uint32_t wrap = buffer->head > buffer->tail ? 0 : buffer->size; + + return wrap + buffer->head - buffer->tail; + } + + /* + * Get the size of the data available to write. + */ + uint32_t getWritableDataSize() const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(buffer != nullptr, 0); + + const uint32_t wrap = (buffer->tail > buffer->wrtn) ? 0 : buffer->size; return wrap + buffer->tail - buffer->wrtn; } @@ -724,6 +736,15 @@ public: heapBuffer.size = 0; } + void copyFromAndClearOther(HeapRingBuffer& other) + { + DISTRHO_SAFE_ASSERT_RETURN(other.heapBuffer.size == heapBuffer.size,); + + std::memcpy(&heapBuffer, &other.heapBuffer, sizeof(HeapBuffer) - sizeof(uint8_t*)); + std::memcpy(heapBuffer.buf, other.heapBuffer.buf, sizeof(uint8_t) * heapBuffer.size); + other.clearData(); + } + private: /** The heap buffer used for this class. */ HeapBuffer heapBuffer; diff --git a/distrho/extra/Runner.hpp b/distrho/extra/Runner.hpp @@ -0,0 +1,251 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DISTRHO_RUNNER_HPP_INCLUDED +#define DISTRHO_RUNNER_HPP_INCLUDED + +#include "../DistrhoUtils.hpp" + +#ifndef DISTRHO_OS_WASM +# include "Thread.hpp" +#else +# include "String.hpp" +# include <emscripten/html5.h> +#endif + +START_NAMESPACE_DISTRHO + +#ifdef DISTRHO_RUNNER_INDIRECT_WASM_CALLS +long d_emscripten_set_interval(void (*)(void*), double, void*); +void d_emscripten_clear_interval(long); +#else +# define d_emscripten_set_interval emscripten_set_interval +# define d_emscripten_clear_interval emscripten_clear_interval +#endif + +// ------------------------------------------------------------------------------------------------------------------- +// Runner class + +/** + Runner class for DPF. + + This is a handy class that handles "idle" time in either background or main thread, + whichever is more suitable to the target platform. + Typically background threads on desktop platforms, main thread on web. + + A single function is expected to be implemented by subclasses, + which directly allows it to stop the runner by returning false. + + You can use it for quick operations that do not need to be handled in the main thread if possible. + The target is to spread out execution over many runs, instead of spending a lot of time on a single task. + */ +class Runner +{ +protected: + /* + * Constructor. + */ + Runner(const char* const runnerName = nullptr) noexcept + #ifndef DISTRHO_OS_WASM + : fRunnerThread(this, runnerName), + fTimeInterval(0) + #else + : fRunnerName(runnerName), + fIntervalId(0) + #endif + { + } + + /* + * Destructor. + */ + virtual ~Runner() /*noexcept*/ + { + DISTRHO_SAFE_ASSERT(! isRunnerActive()); + + stopRunner(); + } + + /* + * Virtual function to be implemented by the subclass. + * Return true to keep running, false to stop execution. + */ + virtual bool run() = 0; + + /* + * Check if the runner should stop. + * To be called from inside the runner to know if a stop request has been made. + */ + bool shouldRunnerStop() const noexcept + { + #ifndef DISTRHO_OS_WASM + return fRunnerThread.shouldThreadExit(); + #else + return fIntervalId == 0; + #endif + } + + // --------------------------------------------------------------------------------------------------------------- + +public: + /* + * Check if the runner is active. + */ + bool isRunnerActive() noexcept + { + #ifndef DISTRHO_OS_WASM + return fRunnerThread.isThreadRunning(); + #else + return fIntervalId != 0; + #endif + } + + /* + * Start the thread. + */ + bool startRunner(const uint timeIntervalMilliseconds = 0) noexcept + { + #ifndef DISTRHO_OS_WASM + DISTRHO_SAFE_ASSERT_RETURN(!fRunnerThread.isThreadRunning(), false); + fTimeInterval = timeIntervalMilliseconds; + return fRunnerThread.startThread(); + #else + DISTRHO_SAFE_ASSERT_RETURN(fIntervalId == 0, false); + fIntervalId = d_emscripten_set_interval(_entryPoint, timeIntervalMilliseconds, this); + return true; + #endif + } + + /* + * Stop the runner. + * This will signal the runner to stop if active, and wait until it finishes. + */ + bool stopRunner() noexcept + { + #ifndef DISTRHO_OS_WASM + return fRunnerThread.stopThread(-1); + #else + signalRunnerShouldStop(); + return true; + #endif + } + + /* + * Tell the runner to stop as soon as possible. + */ + void signalRunnerShouldStop() noexcept + { + #ifndef DISTRHO_OS_WASM + fRunnerThread.signalThreadShouldExit(); + #else + if (fIntervalId != 0) + { + d_emscripten_clear_interval(fIntervalId); + fIntervalId = 0; + } + #endif + } + + // --------------------------------------------------------------------------------------------------------------- + + /* + * Returns the name of the runner. + * This is the name that gets set in the constructor. + */ + const String& getRunnerName() const noexcept + { + #ifndef DISTRHO_OS_WASM + return fRunnerThread.getThreadName(); + #else + return fRunnerName; + #endif + } + + // --------------------------------------------------------------------------------------------------------------- + +private: +#ifndef DISTRHO_OS_WASM + class RunnerThread : public Thread + { + Runner* const runner; + + public: + RunnerThread(Runner* const r, const char* const rn) + : Thread(rn), + runner(r) {} + + protected: + void run() override + { + const uint timeInterval = runner->fTimeInterval; + + while (!shouldThreadExit()) + { + bool stillRunning = false; + + try { + stillRunning = runner->run(); + } catch(...) {} + + if (stillRunning && !shouldThreadExit()) + { + if (timeInterval != 0) + d_msleep(timeInterval); + + // FIXME + // pthread_yield(); + continue; + } + + break; + } + } + } fRunnerThread; + + uint fTimeInterval; +#else + const String fRunnerName; + long fIntervalId; + + void _runEntryPoint() noexcept + { + bool stillRunning = false; + + try { + stillRunning = run(); + } catch(...) {} + + if (fIntervalId != 0 && !stillRunning) + { + d_emscripten_clear_interval(fIntervalId); + fIntervalId = 0; + } + } + + static void _entryPoint(void* const userData) noexcept + { + static_cast<Runner*>(userData)->_runEntryPoint(); + } +#endif + + DISTRHO_DECLARE_NON_COPYABLE(Runner) +}; + +// ------------------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO + +#endif // DISTRHO_RUNNER_HPP_INCLUDED diff --git a/distrho/extra/ScopedPointer.hpp b/distrho/extra/ScopedPointer.hpp @@ -25,7 +25,25 @@ START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------- // The following code was based from juce-core ScopedPointer class -// Copyright (C) 2013 Raw Material Software Ltd. + +/** + Copyright (C) 2013 Raw Material Software Ltd. + + Permission is granted to use this software under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license/ + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD + TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE + OF THIS SOFTWARE. +*/ //============================================================================== /** diff --git a/distrho/extra/String.hpp b/distrho/extra/String.hpp @@ -516,7 +516,7 @@ public: */ String& replace(const char before, const char after) noexcept { - DISTRHO_SAFE_ASSERT_RETURN(before != '\0' && after != '\0', *this); + DISTRHO_SAFE_ASSERT_RETURN(before != '\0' /* && after != '\0' */, *this); for (std::size_t i=0; i < fBufferLen; ++i) { @@ -619,6 +619,36 @@ public: } /* + * Create a new string where all non-basic characters are converted to '_'. + * @see toBasic() + */ + String asBasic() const noexcept + { + String s(*this); + return s.toBasic(); + } + + /* + * Create a new string where all ascii characters are converted lowercase. + * @see toLower() + */ + String asLower() const noexcept + { + String s(*this); + return s.toLower(); + } + + /* + * Create a new string where all ascii characters are converted to uppercase. + * @see toUpper() + */ + String asUpper() const noexcept + { + String s(*this); + return s.toUpper(); + } + + /* * Direct access to the string buffer (read-only). */ const char* buffer() const noexcept @@ -831,7 +861,7 @@ public: std::memcpy(newBuf, fBuffer, fBufferLen); std::memcpy(newBuf + fBufferLen, strBuf, strBufLen + 1); - return String(newBuf); + return String(newBuf, false); } String operator+(const String& str) noexcept @@ -839,6 +869,12 @@ public: return operator+(str.fBuffer); } + // needed for std::map compatibility + bool operator<(const String& str) const noexcept + { + return std::strcmp(fBuffer, str.fBuffer) < 0; + } + // ------------------------------------------------------------------- private: diff --git a/distrho/extra/Thread.hpp b/distrho/extra/Thread.hpp @@ -25,6 +25,10 @@ # include <sys/prctl.h> #endif +#ifdef DISTRHO_OS_WASM +# error Threads do not work under wasm! +#endif + START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------- diff --git a/distrho/extra/sofd/libsofd.c b/distrho/extra/sofd/libsofd.c @@ -0,0 +1,2482 @@ +/* libSOFD - Simple Open File Dialog [for X11 without toolkit] + * + * Copyright (C) 2014 Robin Gareus <robin@gareus.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* Test and example: + * gcc -Wall -D SOFD_TEST -g -o sofd libsofd.c -lX11 + * + * public API documentation and example code at the bottom of this file + * + * This small lib may one day include openGL rendering and + * wayland window support, but not today. Today we celebrate + * 30 years of X11. + */ + +#ifdef SOFD_TEST +#define HAVE_X11 +#include "libsofd.h" +#endif + +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <libgen.h> +#include <time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <assert.h> + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnarrowing" +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wnarrowing" +#endif + +// shared 'recently used' implementation +// sadly, xbel does not qualify as simple. +// hence we use a simple format alike the +// gtk-bookmark list (one file per line) + +#define MAX_RECENT_ENTRIES 24 +#define MAX_RECENT_AGE (15552000) // 180 days (in sec) + +typedef struct { + char path[1024]; + time_t atime; +} FibRecentFile; + +static FibRecentFile *_recentlist = NULL; +static unsigned int _recentcnt = 0; +static uint8_t _recentlock = 0; + +static int fib_isxdigit (const char x) { + if ( + (x >= '0' && x <= '9') + || + (x >= 'a' && x <= 'f') + || + (x >= 'A' && x <= 'F') + ) return 1; + return 0; +} + +static void decode_3986 (char *str) { + int len = strlen (str); + int idx = 0; + while (idx + 2 < len) { + char *in = &str[idx]; + if (('%' == *in) && fib_isxdigit (in[1]) && fib_isxdigit (in[2])) { + char hexstr[3]; + hexstr[0] = in[1]; + hexstr[1] = in[2]; + hexstr[2] = 0; + long hex = strtol (hexstr, NULL, 16); + *in = hex; + memmove (&str[idx+1], &str[idx + 3], len - idx - 2); + len -= 2; + } + ++idx; + } +} + +static char *encode_3986 (const char *str) { + size_t alloc, newlen; + char *ns = NULL; + unsigned char in; + size_t i = 0; + size_t length; + + if (!str) return strdup (""); + + alloc = strlen (str) + 1; + newlen = alloc; + + ns = (char*) malloc (alloc); + + length = alloc; + while (--length) { + in = *str; + + switch (in) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': case 'g': case 'h': case 'i': case 'j': + case 'k': case 'l': case 'm': case 'n': case 'o': + case 'p': case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': case 'G': case 'H': case 'I': case 'J': + case 'K': case 'L': case 'M': case 'N': case 'O': + case 'P': case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': + case '_': case '~': case '.': case '-': + case '/': case ',': // XXX not in RFC3986 + ns[i++] = in; + break; + default: + newlen += 2; /* this'll become a %XX */ + if (newlen > alloc) { + alloc *= 2; + ns = (char*) realloc (ns, alloc); + } + snprintf (&ns[i], 4, "%%%02X", in); + i += 3; + break; + } + ++str; + } + ns[i] = 0; + return ns; +} + +void x_fib_free_recent () { + free (_recentlist); + _recentlist = NULL; + _recentcnt = 0; +} + +static int cmp_recent (const void *p1, const void *p2) { + FibRecentFile *a = (FibRecentFile*) p1; + FibRecentFile *b = (FibRecentFile*) p2; + if (a->atime == b->atime) return 0; + return a->atime < b->atime; +} + +int x_fib_add_recent (const char *path, time_t atime) { + unsigned int i; + struct stat fs; + if (_recentlock) { return -1; } + if (access (path, R_OK)) { + return -1; + } + if (stat (path, &fs)) { + return -1; + } + if (!S_ISREG (fs.st_mode)) { + return -1; + } + if (atime == 0) atime = time (NULL); + if (MAX_RECENT_AGE > 0 && atime + MAX_RECENT_AGE < time (NULL)) { + return -1; + } + + for (i = 0; i < _recentcnt; ++i) { + if (!strcmp (_recentlist[i].path, path)) { + if (_recentlist[i].atime < atime) { + _recentlist[i].atime = atime; + } + qsort (_recentlist, _recentcnt, sizeof(FibRecentFile), cmp_recent); + return _recentcnt; + } + } + _recentlist = (FibRecentFile*)realloc (_recentlist, (_recentcnt + 1) * sizeof(FibRecentFile)); + _recentlist[_recentcnt].atime = atime; + strcpy (_recentlist[_recentcnt].path, path); + qsort (_recentlist, _recentcnt + 1, sizeof(FibRecentFile), cmp_recent); + + if (_recentcnt >= MAX_RECENT_ENTRIES) { + return (_recentcnt); + } + return (++_recentcnt); +} + +#ifdef PATHSEP +#undef PATHSEP +#endif + +#ifdef PLATFORM_WINDOWS +#define DIRSEP '\\' +#else +#define DIRSEP '/' +#endif + +static void mkpath(const char *dir) { + char tmp[1024]; + char *p; + size_t len; + + snprintf (tmp, sizeof(tmp), "%s", dir); + len = strlen(tmp); + if (tmp[len - 1] == '/') + tmp[len - 1] = 0; + for (p = tmp + 1; *p; ++p) + if(*p == DIRSEP) { + *p = 0; +#ifdef PLATFORM_WINDOWS + mkdir(tmp); +#else + mkdir(tmp, 0755); +#endif + *p = DIRSEP; + } +#ifdef PLATFORM_WINDOWS + mkdir(tmp); +#else + mkdir(tmp, 0755); +#endif +} + +int x_fib_save_recent (const char *fn) { + if (_recentlock) { return -1; } + if (!fn) { return -1; } + if (_recentcnt < 1 || !_recentlist) { return -1; } + unsigned int i; + char *dn = strdup (fn); + mkpath (dirname (dn)); + free (dn); + + FILE *rf = fopen (fn, "w"); + if (!rf) return -1; + + qsort (_recentlist, _recentcnt, sizeof(FibRecentFile), cmp_recent); + for (i = 0; i < _recentcnt; ++i) { + char *n = encode_3986 (_recentlist[i].path); + fprintf (rf, "%s %lu\n", n, _recentlist[i].atime); + free (n); + } + fclose (rf); + return 0; +} + +int x_fib_load_recent (const char *fn) { + char tmp[1024]; + if (_recentlock) { return -1; } + if (!fn) { return -1; } + x_fib_free_recent (); + if (access (fn, R_OK)) { + return -1; + } + FILE *rf = fopen (fn, "r"); + if (!rf) return -1; + while (fgets (tmp, sizeof(tmp), rf) + && strlen (tmp) > 1 + && strlen (tmp) < sizeof(tmp)) + { + char *s; + tmp[strlen (tmp) - 1] = '\0'; // strip newline + if (!(s = strchr (tmp, ' '))) { // find name <> atime sep + continue; + } + *s = '\0'; + time_t t = atol (++s); + decode_3986 (tmp); + x_fib_add_recent (tmp, t); + } + fclose (rf); + return 0; +} + +unsigned int x_fib_recent_count () { + return _recentcnt; +} + +const char *x_fib_recent_at (unsigned int i) { + if (i >= _recentcnt) + return NULL; + return _recentlist[i].path; +} + +#ifdef PLATFORM_WINDOWS +#define PATHSEP "\\" +#else +#define PATHSEP "/" +#endif + +const char *x_fib_recent_file(const char *appname) { + static char recent_file[1024]; + assert(!strchr(appname, '/')); + const char *xdg = getenv("XDG_DATA_HOME"); + if (xdg && (strlen(xdg) + strlen(appname) + 10) < sizeof(recent_file)) { + sprintf(recent_file, "%s" PATHSEP "%s" PATHSEP "recent", xdg, appname); + return recent_file; + } +#ifdef PLATFORM_WINDOWS + const char * homedrive = getenv("HOMEDRIVE"); + const char * homepath = getenv("HOMEPATH"); + if (homedrive && homepath && (strlen(homedrive) + strlen(homepath) + strlen(appname) + 29) < PATH_MAX) { + sprintf(recent_file, "%s%s" PATHSEP "Application Data" PATHSEP "%s" PATHSEP "recent.txt", homedrive, homepath, appname); + return recent_file; + } +#elif defined PLATFORM_OSX + const char *home = getenv("HOME"); + if (home && (strlen(home) + strlen(appname) + 29) < sizeof(recent_file)) { + sprintf(recent_file, "%s/Library/Preferences/%s/recent", home, appname); + return recent_file; + } +#else + const char *home = getenv("HOME"); + if (home && (strlen(home) + strlen(appname) + 22) < sizeof(recent_file)) { + sprintf(recent_file, "%s/.local/share/%s/recent", home, appname); + return recent_file; + } +#endif + return NULL; +} + +#ifdef HAVE_X11 +#include <dirent.h> + +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <X11/Xutil.h> +#include <X11/keysym.h> +#include <X11/Xos.h> + +#if defined(__linux__) || defined(__linux) +#define HAVE_MNTENT +#include <mntent.h> +#endif + +#ifndef MIN +#define MIN(A,B) ( (A) < (B) ? (A) : (B) ) +#endif + +#ifndef MAX +#define MAX(A,B) ( (A) < (B) ? (B) : (A) ) +#endif + +static Window _fib_win = 0; +static GC _fib_gc = 0; +static XColor _c_gray0, _c_gray1, _c_gray2, _c_gray3, _c_gray4, _c_gray5; +static Font _fibfont = 0; +static Pixmap _pixbuffer = None; + +static int _fib_width = 100; +static int _fib_height = 100; +static int _btn_w = 0; +static int _btn_span = 0; +static double _scalefactor = 1; + +static int _fib_font_height = 0; +static int _fib_dir_indent = 0; +static int _fib_spc_norm = 0; +static int _fib_font_ascent = 0; +static int _fib_font_vsep = 0; +static int _fib_font_size_width = 0; +static int _fib_font_time_width = 0; +static int _fib_place_width = 0; + +static int _scrl_f = 0; +static int _scrl_y0 = -1; +static int _scrl_y1 = -1; +static int _scrl_my = -1; +static int _scrl_mf = -1; +static int _view_p = -1; + +static int _fsel = -1; +static int _hov_b = -1; +static int _hov_f = -1; +static int _hov_p = -1; +static int _hov_h = -1; +static int _hov_l = -1; +static int _hov_s = -1; +static int _sort = 0; +static int _columns = 0; +static int _fib_filter_fn = 1; +static int _fib_hidden_fn = 0; +static int _fib_show_places = 0; + +static uint8_t _fib_mapped = 0; +static uint8_t _fib_resized = 0; +static unsigned long _dblclk = 0; + +static int _status = -2; +static char _rv_open[1024] = ""; + +static char _fib_cfg_custom_places[1024] = ""; +static char _fib_cfg_custom_font[256] = ""; +static char _fib_cfg_title[128] = "xjadeo - Open Video File"; + +typedef struct { + char name[256]; + int x0; + int xw; +} FibPathButton; + +typedef struct { + char name[256]; + char strtime[32]; + char strsize[32]; + int ssizew; + off_t size; + time_t mtime; + uint8_t flags; // 2: selected, 4: isdir 8: recent-entry + FibRecentFile *rfp; +} FibFileEntry; + +typedef struct { + char text[24]; + uint8_t flags; // 2: selected, 4: toggle, 8 disable + int x0; + int tw; + int xw; + void (*callback)(Display*); +} FibButton; + +typedef struct { + char name[256]; + char path[1024]; + uint8_t flags; // 1: hover, 2: selected, 4:add sep +} FibPlace; + +static char _cur_path[1024] = ""; +static FibFileEntry *_dirlist = NULL; +static FibPathButton *_pathbtn = NULL; +static FibPlace *_placelist = NULL; +static int _dircount = 0; +static int _pathparts = 0; +static int _placecnt = 0; + +static FibButton _btn_ok; +static FibButton _btn_cancel; +static FibButton _btn_filter; +static FibButton _btn_places; +static FibButton _btn_hidden; +static FibButton *_btns[] = {&_btn_places, &_btn_filter, &_btn_hidden, &_btn_cancel, &_btn_ok}; + +static int (*_fib_filter_function)(const char *filename); + +/* hardcoded layout */ +#define DSEP 6 // px; horiz space beween elements, also l+r margin for file-list +#define PSEP 4 // px; horiz space beween paths +#define FILECOLUMN (17 * _fib_dir_indent) //px; min width of file-column +#define LISTTOP 2.7 //em; top of the file-browser list +#define LISTBOT 4.75 //em; bottom of the file-browers list +#define BTNBTMMARGIN 0.75 //em; height/margin of the button row +#define BTNPADDING 2 // px - only used for open/cancel buttons +#define SCROLLBARW (3 + (_fib_spc_norm&~1)) //px; - should be SCROLLBARW = (N * 2 + 3) +#define SCROLLBOXH 10 //px; arrow box top+bottom +#define PLACESW _fib_place_width //px; +#define PLACESWMAX (15 *_fib_spc_norm) //px; +#define PATHBTNTOP _fib_font_vsep //px; offset by (_fib_font_ascent); +#define FAREAMRGB 3 //px; base L+R margin +#define FAREAMRGR (FAREAMRGB + 1) //px; right margin of file-area + 1 (line width) +#define FAREAMRGL (_fib_show_places ? PLACESW / _scalefactor + FAREAMRGB : FAREAMRGB) //px; left margin of file-area +#define TEXTSEP 4 //px; +#define FAREATEXTL (FAREAMRGL + TEXTSEP) //px; filename text-left FAREAMRGL + TEXTSEP +#define SORTBTNOFF -10 //px; + +#ifndef DBLCLKTME +#define DBLCLKTME 200 //msec; double click time +#endif + +#define DRAW_OUTLINE +#define DOUBLE_BUFFER +#define LIST_ENTRY_HOVER + +static int query_font_geometry (Display *dpy, GC gc, const char *txt, int *w, int *h, int *a, int *d) { + XCharStruct text_structure; + int font_direction, font_ascent, font_descent; + XFontStruct *fontinfo = XQueryFont (dpy, XGContextFromGC (gc)); + + if (!fontinfo) { return -1; } + XTextExtents (fontinfo, txt, strlen (txt), &font_direction, &font_ascent, &font_descent, &text_structure); + if (w) *w = XTextWidth (fontinfo, txt, strlen (txt)); + if (h) *h = text_structure.ascent + text_structure.descent; + if (a) *a = text_structure.ascent; + if (d) *d = text_structure.descent; +#ifndef DISTRHO_OS_HAIKU // FIXME + XFreeFontInfo (NULL, fontinfo, 1); +#endif + return 0; +} + +static void VDrawRectangle (Display *dpy, Drawable d, GC gc, int x, int y, unsigned int w, unsigned int h) { +#ifdef DRAW_OUTLINE + XSetForeground (dpy, gc, _c_gray5.pixel); + XDrawLine (dpy, d, gc, x + 1, y + h, x + w, y + h); + XDrawLine (dpy, d, gc, x + w, y + 1, x + w, y + h); + XDrawLine (dpy, d, gc, x + 1, y, x + w, y); + XDrawLine (dpy, d, gc, x, y + 1, x, y + h); +#else + const unsigned long blackColor = BlackPixel (dpy, DefaultScreen (dpy)); + XSetForeground (dpy, _fib_gc, blackColor); + XDrawRectangle (dpy, d, gc, x, y, w, h); +#endif +} + +static void fib_expose (Display *dpy, Window realwin) { + int i; + XID win; + if (!_fib_mapped) return; + + if (_fib_resized +#ifdef DOUBLE_BUFFER + || !_pixbuffer +#endif + ) + { +#ifdef DOUBLE_BUFFER + unsigned int w = 0, h = 0; + if (_pixbuffer != None) { + Window ignored_w; + int ignored_i; + unsigned int ignored_u; + XGetGeometry(dpy, _pixbuffer, &ignored_w, &ignored_i, &ignored_i, &w, &h, &ignored_u, &ignored_u); + if (_fib_width != (int)w || _fib_height != (int)h) { + XFreePixmap (dpy, _pixbuffer); + _pixbuffer = None; + } + } + if (_pixbuffer == None) { + XWindowAttributes wa; + XGetWindowAttributes (dpy, realwin, &wa); + _pixbuffer = XCreatePixmap (dpy, realwin, _fib_width, _fib_height, wa.depth); + } +#endif + if (_pixbuffer != None) { + XSetForeground (dpy, _fib_gc, _c_gray1.pixel); + XFillRectangle (dpy, _pixbuffer, _fib_gc, 0, 0, _fib_width, _fib_height); + } else { + XSetForeground (dpy, _fib_gc, _c_gray1.pixel); + XFillRectangle (dpy, realwin, _fib_gc, 0, 0, _fib_width, _fib_height); + } + _fib_resized = 0; + } + + if (_pixbuffer == None) { + win = realwin; + } else { + win = _pixbuffer; + } + + // Top Row: dirs and up navigation + + int ppw = 0; + int ppx = FAREAMRGB * _scalefactor; + + for (i = _pathparts - 1; i >= 0; --i) { + ppw += _pathbtn[i].xw + PSEP * _scalefactor; + if (ppw >= _fib_width - PSEP * _scalefactor - _pathbtn[0].xw - FAREAMRGB * _scalefactor) break; // XXX, first change is from "/" to "<", NOOP + } + ++i; + // border-less "<" parent/up, IFF space is limited + if (i > 0) { + if (0 == _hov_p || (_hov_p > 0 && _hov_p < _pathparts - 1)) { + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + } else { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } + XDrawString (dpy, win, _fib_gc, ppx, PATHBTNTOP, "<", 1); + ppx += _pathbtn[0].xw + PSEP * _scalefactor; + if (i == _pathparts) --i; + } + + _view_p = i; + + while (i < _pathparts) { + if (i == _hov_p) { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } else { + XSetForeground (dpy, _fib_gc, _c_gray2.pixel); + } + XFillRectangle (dpy, win, _fib_gc, + ppx + 1, PATHBTNTOP - _fib_font_ascent, + _pathbtn[i].xw - 1, _fib_font_height); + VDrawRectangle (dpy, win, _fib_gc, + ppx, PATHBTNTOP - _fib_font_ascent, + _pathbtn[i].xw, _fib_font_height); + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XDrawString (dpy, win, _fib_gc, ppx + 1 + BTNPADDING, PATHBTNTOP, + _pathbtn[i].name, strlen (_pathbtn[i].name)); + _pathbtn[i].x0 = ppx; // current position + ppx += _pathbtn[i].xw + PSEP * _scalefactor; + ++i; + } + + // middle, scroll list of file names + const int ltop = LISTTOP * _fib_font_vsep; + const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + const int fsel_height = 4 * _scalefactor + llen * _fib_font_vsep; + const int fsel_width = _fib_width - (FAREAMRGL + FAREAMRGR) * _scalefactor - (llen < _dircount ? SCROLLBARW * _scalefactor : 0); + const int t_x = FAREATEXTL * _scalefactor; + int t_s = FAREATEXTL * _scalefactor + fsel_width; + int t_t = FAREATEXTL * _scalefactor + fsel_width; + + // check which colums can be visible + // depending on available width of window. + _columns = 0; + if (fsel_width > FILECOLUMN + _fib_font_size_width + _fib_font_time_width) { + _columns |= 2; + t_s = FAREAMRGL * _scalefactor + fsel_width - _fib_font_time_width - TEXTSEP * _scalefactor; + } + if (fsel_width > FILECOLUMN + _fib_font_size_width) { + _columns |= 1; + t_t = t_s - _fib_font_size_width - TEXTSEP * _scalefactor; + } + + int fstop = _scrl_f; // first entry in scroll position + const int ttop = ltop - _fib_font_height + _fib_font_ascent; + + if (fstop > 0 && fstop + llen > _dircount) { + fstop = MAX (0, _dircount - llen); + _scrl_f = fstop; + } + + // list header + XSetForeground (dpy, _fib_gc, _c_gray3.pixel); + XFillRectangle (dpy, win, _fib_gc, FAREAMRGL * _scalefactor, ltop - _fib_font_vsep, fsel_width, _fib_font_vsep); + + // draw background of file list + XSetForeground (dpy, _fib_gc, _c_gray2.pixel); + XFillRectangle (dpy, win, _fib_gc, FAREAMRGL * _scalefactor, ltop, fsel_width, fsel_height); + +#ifdef DRAW_OUTLINE + VDrawRectangle (dpy, win, _fib_gc, + FAREAMRGL * _scalefactor, + ltop - _fib_font_vsep - 1, + _fib_width - (FAREAMRGL + FAREAMRGR) * _scalefactor, + fsel_height + _fib_font_vsep + 1); +#endif + + switch (_hov_h) { + case 1: + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + XFillRectangle (dpy, win, _fib_gc, + t_x + _fib_dir_indent - (TEXTSEP - 1) * _scalefactor, + ltop - _fib_font_vsep, + t_t - t_x - _fib_dir_indent - 1 * _scalefactor, + _fib_font_vsep); + break; + case 2: + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + XFillRectangle (dpy, win, _fib_gc, + t_t - (TEXTSEP - 1) * _scalefactor, + ltop - _fib_font_vsep, + _fib_font_size_width + (TEXTSEP - 1) * _scalefactor, + _fib_font_vsep); + break; + case 3: + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + XFillRectangle (dpy, win, _fib_gc, + t_s - (TEXTSEP - 1) * _scalefactor, + ltop - _fib_font_vsep, + TEXTSEP * 2 * _scalefactor + _fib_font_time_width - 1 * _scalefactor, + _fib_font_vsep); + break; + default: + break; + } + + // column headings and sort order + int arp = MAX (2 * _scalefactor, _fib_font_height / 5); // arrow scale + const int trioff = _fib_font_height - _fib_font_ascent - arp + 1 * _scalefactor; + XPoint ptri[4] = { {0, ttop - trioff }, {arp, -arp - arp - 1 * _scalefactor}, {-arp - arp, 0}, {arp, arp + arp + 1 * _scalefactor}}; + if (_sort & 1) { + ptri[0].y = ttop -arp - arp - 1 * _scalefactor; + ptri[1].y *= -1; + ptri[3].y *= -1; + } + switch (_sort) { + case 0: + case 1: + ptri[0].x = t_t + (SORTBTNOFF + 2) * _scalefactor - arp; + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XFillPolygon (dpy, win, _fib_gc, ptri, 3, Convex, CoordModePrevious); + XDrawLines (dpy, win, _fib_gc, ptri, 4, CoordModePrevious); + break; + case 2: + case 3: + if (_columns & 1) { + ptri[0].x = t_s + (SORTBTNOFF + 2) * _scalefactor - arp; + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XFillPolygon (dpy, win, _fib_gc, ptri, 3, Convex, CoordModePrevious); + XDrawLines (dpy, win, _fib_gc, ptri, 4, CoordModePrevious); + } + break; + case 4: + case 5: + if (_columns & 2) { + ptri[0].x = FAREATEXTL * _scalefactor + fsel_width + (SORTBTNOFF + 2) * _scalefactor - arp; + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XFillPolygon (dpy, win, _fib_gc, ptri, 3, Convex, CoordModePrevious); + XDrawLines (dpy, win, _fib_gc, ptri, 4, CoordModePrevious); + } + break; + } + +#if 0 // bottom header bottom border + XSetForeground (dpy, _fib_gc, _c_gray5.pixel); + XSetLineAttributes (dpy, _fib_gc, 1, LineOnOffDash, CapButt, JoinMiter); + XDrawLine (dpy, win, _fib_gc, + FAREAMRGL + 1, ltop, + FAREAMRGL + fsel_width, ltop); + XSetLineAttributes (dpy, _fib_gc, 1, LineSolid, CapButt, JoinMiter); +#endif + + XSetForeground (dpy, _fib_gc, _c_gray2.pixel); + XDrawLine (dpy, win, _fib_gc, + t_x + _fib_dir_indent - TEXTSEP * _scalefactor, ltop - _fib_font_vsep + 3 * _scalefactor, + t_x + _fib_dir_indent - TEXTSEP * _scalefactor, ltop - 3 * _scalefactor); + + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XDrawString (dpy, win, _fib_gc, t_x + _fib_dir_indent, ttop, "Name", 4); + + if (_columns & 1) { + XSetForeground (dpy, _fib_gc, _c_gray2.pixel); + XDrawLine (dpy, win, _fib_gc, + t_t - TEXTSEP * _scalefactor, ltop - _fib_font_vsep + 3 * _scalefactor, + t_t - TEXTSEP * _scalefactor, ltop - 3 * _scalefactor); + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XDrawString (dpy, win, _fib_gc, t_t, ttop, "Size", 4); + } + + if (_columns & 2) { + XSetForeground (dpy, _fib_gc, _c_gray2.pixel); + XDrawLine (dpy, win, _fib_gc, + t_s - TEXTSEP * _scalefactor, ltop - _fib_font_vsep + 3 * _scalefactor, + t_s - TEXTSEP * _scalefactor, ltop - 3 * _scalefactor); + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + if (_pathparts > 0) + XDrawString (dpy, win, _fib_gc, t_s, ttop, "Last Modified", 13); + else + XDrawString (dpy, win, _fib_gc, t_s, ttop, "Last Used", 9); + } + + // scrollbar sep + if (llen < _dircount) { + const int sx0 = _fib_width - (SCROLLBARW + FAREAMRGR) * _scalefactor; + XSetForeground (dpy, _fib_gc, _c_gray2.pixel); + XDrawLine (dpy, win, _fib_gc, + sx0 - 1, ltop - _fib_font_vsep, +#ifdef DRAW_OUTLINE + sx0 - 1, ltop + fsel_height +#else + sx0 - 1, ltop - 1 +#endif + ); + } + + // clip area for file-name + XRectangle clp = {(FAREAMRGL + 1) * _scalefactor, ltop, + t_t - (FAREAMRGL + TEXTSEP * 2 + 1) * _scalefactor, fsel_height}; + + // list files in view + for (i = 0; i < llen; ++i) { + const int j = i + fstop; + if (j >= _dircount) break; + + const int t_y = ltop + (i+1) * _fib_font_vsep - 4; + + if (_dirlist[j].flags & 2) { + XSetForeground (dpy, _fib_gc, _c_gray5.pixel); + XFillRectangle (dpy, win, _fib_gc, + FAREAMRGL * _scalefactor, t_y - _fib_font_ascent, fsel_width, _fib_font_height); + } + /* + if (_hov_f == j && !(_dirlist[j].flags & 2)) { + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + } + */ + if (_dirlist[j].flags & 4) { + XSetForeground (dpy, _fib_gc, (_dirlist[j].flags & 2) ? _c_gray3.pixel : _c_gray5.pixel); + XDrawString (dpy, win, _fib_gc, t_x, t_y, "D", 1); + } + XSetClipRectangles (dpy, _fib_gc, 0, 0, &clp, 1, Unsorted); + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XDrawString (dpy, win, _fib_gc, + t_x + _fib_dir_indent, t_y, + _dirlist[j].name, strlen (_dirlist[j].name)); + XSetClipMask (dpy, _fib_gc, None); + + if (_columns & 1) // right-aligned 'size' + XDrawString (dpy, win, _fib_gc, + t_s - (TEXTSEP + 2) * _scalefactor - _dirlist[j].ssizew, t_y, + _dirlist[j].strsize, strlen (_dirlist[j].strsize)); + if (_columns & 2) + XDrawString (dpy, win, _fib_gc, + t_s, t_y, + _dirlist[j].strtime, strlen (_dirlist[j].strtime)); + } + + // scrollbar + if (llen < _dircount) { + float sl = (fsel_height + _fib_font_vsep - (SCROLLBOXH + SCROLLBOXH) * _scalefactor) / (float) _dircount; + sl = MAX ((8. * _scalefactor / llen), sl); // 8px min height of scroller + const int sy1 = llen * sl; + const float mx = (fsel_height + _fib_font_vsep - (SCROLLBOXH + SCROLLBOXH) * _scalefactor - sy1) / (float)(_dircount - llen); + const int sy0 = fstop * mx; + const int sx0 = _fib_width - (SCROLLBARW + FAREAMRGR) * _scalefactor; + const int stop = ltop - _fib_font_vsep; + + _scrl_y0 = stop + SCROLLBOXH * _scalefactor + sy0; + _scrl_y1 = _scrl_y0 + sy1; + + assert (fstop + llen <= _dircount); + // scroll-bar background + XSetForeground (dpy, _fib_gc, _c_gray1.pixel); + XFillRectangle (dpy, win, _fib_gc, sx0, stop, SCROLLBARW * _scalefactor, fsel_height + _fib_font_vsep); + + // scroller + if (_hov_s == 0 || _hov_s == 1 || _hov_s == 2) { + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + } else { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } + XFillRectangle (dpy, win, _fib_gc, sx0 + 1 * _scalefactor, stop + SCROLLBOXH * _scalefactor + sy0, (SCROLLBARW - 2) * _scalefactor, sy1); + + int scrw = (SCROLLBARW -3) / 2 * _scalefactor; + // arrows top and bottom + if (_hov_s == 1) { + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + } else { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } + XPoint ptst[4] = { {sx0 + 1 * _scalefactor, stop + 8 * _scalefactor}, {scrw, -7 * _scalefactor}, {scrw, 7 * _scalefactor}, {-2 * scrw, 0}}; + XFillPolygon (dpy, win, _fib_gc, ptst, 3, Convex, CoordModePrevious); + XDrawLines (dpy, win, _fib_gc, ptst, 4, CoordModePrevious); + + if (_hov_s == 2) { + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + } else { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } + XPoint ptsb[4] = { {sx0 + 1 * _scalefactor, ltop + fsel_height - 9 * _scalefactor}, {2*scrw, 0}, {-scrw, 7 * _scalefactor}, {-scrw, -7 * _scalefactor}}; + XFillPolygon (dpy, win, _fib_gc, ptsb, 3, Convex, CoordModePrevious); + XDrawLines (dpy, win, _fib_gc, ptsb, 4, CoordModePrevious); + } else { + _scrl_y0 = _scrl_y1 = -1; + } + + if (_fib_show_places) { + assert (_placecnt > 0); + + // heading + XSetForeground (dpy, _fib_gc, _c_gray3.pixel); + XFillRectangle (dpy, win, _fib_gc, FAREAMRGB * _scalefactor, ltop - _fib_font_vsep, PLACESW - TEXTSEP * _scalefactor, _fib_font_vsep); + + // body + XSetForeground (dpy, _fib_gc, _c_gray2.pixel); + XFillRectangle (dpy, win, _fib_gc, FAREAMRGB * _scalefactor, ltop, PLACESW - TEXTSEP * _scalefactor, fsel_height); + +#ifdef DRAW_OUTLINE + VDrawRectangle (dpy, win, _fib_gc, FAREAMRGB * _scalefactor, ltop - _fib_font_vsep -1, PLACESW - TEXTSEP * _scalefactor, fsel_height + _fib_font_vsep + 1); +#endif + + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XDrawString (dpy, win, _fib_gc, (FAREAMRGB + TEXTSEP) * _scalefactor, ttop, "Places", 6); + + XRectangle pclip = {(FAREAMRGB + 1) * _scalefactor, ltop, PLACESW - (TEXTSEP + 1) * _scalefactor, fsel_height}; + XSetClipRectangles (dpy, _fib_gc, 0, 0, &pclip, 1, Unsorted); + const int plx = (FAREAMRGB + TEXTSEP) * _scalefactor; + for (i = 0; i < llen && i < _placecnt; ++i) { + const int ply = ltop + (i+1) * _fib_font_vsep - 4 * _scalefactor; + if (i == _hov_l) { + XSetForeground (dpy, _fib_gc, _c_gray5.pixel); + XFillRectangle (dpy, win, _fib_gc, FAREAMRGB * _scalefactor, ltop + i * _fib_font_vsep, PLACESW - TEXTSEP * _scalefactor, _fib_font_vsep); + } + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XDrawString (dpy, win, _fib_gc, + plx, ply, + _placelist[i].name, strlen (_placelist[i].name)); + if (_placelist[i].flags & 4) { + XSetForeground (dpy, _fib_gc, _c_gray5.pixel); + const int plly = ply - _fib_font_ascent + _fib_font_height + 1 * _scalefactor; + const int pllx0 = FAREAMRGB * _scalefactor; + const int pllx1 = FAREAMRGB * _scalefactor + PLACESW - TEXTSEP * _scalefactor; + XDrawLine (dpy, win, _fib_gc, pllx0, plly, pllx1, plly); + } + } + XSetClipMask (dpy, _fib_gc, None); + + if (_placecnt > llen) { + const int plly = ltop + fsel_height - _fib_font_height + _fib_font_ascent; + const int pllx0 = FAREAMRGB * _scalefactor + (PLACESW - TEXTSEP * _scalefactor) * .75; + const int pllx1 = FAREAMRGB * _scalefactor + PLACESW - TEXTSEP * 2 * _scalefactor; + + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XSetLineAttributes (dpy, _fib_gc, 1 * _scalefactor, LineOnOffDash, CapButt, JoinMiter); + XDrawLine (dpy, win, _fib_gc, pllx0, plly, pllx1, plly); + XSetLineAttributes (dpy, _fib_gc, 1 * _scalefactor, LineSolid, CapButt, JoinMiter); + } + } + + // Bottom Buttons + const int numb = sizeof(_btns) / sizeof(FibButton*); + int xtra = _fib_width - _btn_span; + const int cbox = _fib_font_ascent - 2 * _scalefactor; + const int bbase = _fib_height - BTNBTMMARGIN * _fib_font_vsep - BTNPADDING * _scalefactor; + const int cblw = cbox > 20 * _scalefactor + ? 5 * _scalefactor + : (cbox > 9 * _scalefactor ? 3 : 1) * _scalefactor; + + int bx = FAREAMRGB * _scalefactor; + for (i = 0; i < numb; ++i) { + if (_btns[i]->flags & 8) { continue; } + if (_btns[i]->flags & 4) { + // checkbutton + const int cby0 = bbase - cbox + (1 + BTNPADDING) * _scalefactor; + if (i == _hov_b) { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } else { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } + XDrawRectangle (dpy, win, _fib_gc, + bx, cby0 - 1, cbox + 1, cbox + 1); + + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XDrawString (dpy, win, _fib_gc, BTNPADDING * _scalefactor + bx + _fib_font_ascent, bbase + (BTNPADDING + 1) * _scalefactor, + _btns[i]->text, strlen (_btns[i]->text)); + + if (i == _hov_b) { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } else { + /* if (_btns[i]->flags & 2) { + XSetForeground (dpy, _fib_gc, _c_gray1.pixel); + } else */ { + XSetForeground (dpy, _fib_gc, _c_gray2.pixel); + } + } + XFillRectangle (dpy, win, _fib_gc, + bx+1, cby0, cbox, cbox); + + if (_btns[i]->flags & 2) { + XSetLineAttributes (dpy, _fib_gc, cblw, LineSolid, CapRound, JoinMiter); + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XDrawLine (dpy, win, _fib_gc, + bx + 2, cby0 + 1, + bx + cbox - 1, cby0 + cbox - 2); + XDrawLine (dpy, win, _fib_gc, + bx + cbox - 1, cby0 + 1, + bx + 2, cby0 + cbox - 2); + XSetLineAttributes (dpy, _fib_gc, 1, LineSolid, CapButt, JoinMiter); + } + } else { + if (xtra > 0) { + bx += xtra; + xtra = 0; + } + // pushbutton + + uint8_t can_hover = 1; // special case + if (_btns[i] == &_btn_ok) { + if (_fsel < 0 || _fsel >= _dircount) { + can_hover = 0; + } + } + + if (can_hover && i == _hov_b) { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } else { + XSetForeground (dpy, _fib_gc, _c_gray2.pixel); + } + XFillRectangle (dpy, win, _fib_gc, + bx + 1, bbase - _fib_font_ascent, + _btn_w - 1, _fib_font_height + BTNPADDING * 2 * _scalefactor); + VDrawRectangle (dpy, win, _fib_gc, + bx, bbase - _fib_font_ascent, + _btn_w, _fib_font_height + BTNPADDING * 2 * _scalefactor); + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XDrawString (dpy, win, _fib_gc, bx + (_btn_w - _btns[i]->tw) * .5, 1 + bbase + BTNPADDING * _scalefactor, + _btns[i]->text, strlen (_btns[i]->text)); + } + _btns[i]->x0 = bx; + bx += _btns[i]->xw + DSEP * _scalefactor; + } + + if (_pixbuffer != None) { + XCopyArea(dpy, _pixbuffer, realwin, _fib_gc, 0, 0, _fib_width, _fib_height, 0, 0); + } + XFlush (dpy); +} + +static void fib_reset () { + _hov_p = _hov_f = _hov_h = _hov_l = -1; + _scrl_f = 0; + _fib_resized = 1; +} + +static int cmp_n_up (const void *p1, const void *p2) { + FibFileEntry *a = (FibFileEntry*) p1; + FibFileEntry *b = (FibFileEntry*) p2; + if ((a->flags & 4) && !(b->flags & 4)) return -1; + if (!(a->flags & 4) && (b->flags & 4)) return 1; + return strcmp (a->name, b->name); +} + +static int cmp_n_down (const void *p1, const void *p2) { + FibFileEntry *a = (FibFileEntry*) p1; + FibFileEntry *b = (FibFileEntry*) p2; + if ((a->flags & 4) && !(b->flags & 4)) return -1; + if (!(a->flags & 4) && (b->flags & 4)) return 1; + return strcmp (b->name, a->name); +} + +static int cmp_t_up (const void *p1, const void *p2) { + FibFileEntry *a = (FibFileEntry*) p1; + FibFileEntry *b = (FibFileEntry*) p2; + if ((a->flags & 4) && !(b->flags & 4)) return -1; + if (!(a->flags & 4) && (b->flags & 4)) return 1; + if (a->mtime == b->mtime) return 0; + return a->mtime > b->mtime ? -1 : 1; +} + +static int cmp_t_down (const void *p1, const void *p2) { + FibFileEntry *a = (FibFileEntry*) p1; + FibFileEntry *b = (FibFileEntry*) p2; + if ((a->flags & 4) && !(b->flags & 4)) return -1; + if (!(a->flags & 4) && (b->flags & 4)) return 1; + if (a->mtime == b->mtime) return 0; + return a->mtime > b->mtime ? 1 : -1; +} + +static int cmp_s_up (const void *p1, const void *p2) { + FibFileEntry *a = (FibFileEntry*) p1; + FibFileEntry *b = (FibFileEntry*) p2; + if ((a->flags & 4) && (b->flags & 4)) return 0; // dir, no size, retain order + if ((a->flags & 4) && !(b->flags & 4)) return -1; + if (!(a->flags & 4) && (b->flags & 4)) return 1; + if (a->size == b->size) return 0; + return a->size > b->size ? -1 : 1; +} + +static int cmp_s_down (const void *p1, const void *p2) { + FibFileEntry *a = (FibFileEntry*) p1; + FibFileEntry *b = (FibFileEntry*) p2; + if ((a->flags & 4) && (b->flags & 4)) return 0; // dir, no size, retain order + if ((a->flags & 4) && !(b->flags & 4)) return -1; + if (!(a->flags & 4) && (b->flags & 4)) return 1; + if (a->size == b->size) return 0; + return a->size > b->size ? 1 : -1; +} + +static void fmt_size (Display *dpy, FibFileEntry *f) { + if (f->size > 10995116277760) { + sprintf (f->strsize, "%.0f TB", f->size / 1099511627776.f); + } + if (f->size > 1099511627776) { + sprintf (f->strsize, "%.1f TB", f->size / 1099511627776.f); + } + else if (f->size > 10737418240) { + sprintf (f->strsize, "%.0f GB", f->size / 1073741824.f); + } + else if (f->size > 1073741824) { + sprintf (f->strsize, "%.1f GB", f->size / 1073741824.f); + } + else if (f->size > 10485760) { + sprintf (f->strsize, "%.0f MB", f->size / 1048576.f); + } + else if (f->size > 1048576) { + sprintf (f->strsize, "%.1f MB", f->size / 1048576.f); + } + else if (f->size > 10240) { + sprintf (f->strsize, "%.0f KB", f->size / 1024.f); + } + else if (f->size >= 1000) { + sprintf (f->strsize, "%.1f KB", f->size / 1024.f); + } + else { + sprintf (f->strsize, "%.0f B", f->size / 1.f); + } + int sw = 0; + query_font_geometry (dpy, _fib_gc, f->strsize, &sw, NULL, NULL, NULL); + if (sw > _fib_font_size_width) { + _fib_font_size_width = sw; + } + f->ssizew = sw; +} + +static void fmt_time (Display *dpy, FibFileEntry *f) { + struct tm *tmp; + tmp = localtime (&f->mtime); + if (!tmp) { + return; + } + strftime (f->strtime, sizeof(f->strtime), "%F %H:%M", tmp); + + int tw = 0; + query_font_geometry (dpy, _fib_gc, f->strtime, &tw, NULL, NULL, NULL); + if (tw > _fib_font_time_width) { + _fib_font_time_width = tw; + } +} + +static void fib_resort (const char * sel) { + if (_dircount < 1) { return; } + int (*sortfn)(const void *p1, const void *p2); + switch (_sort) { + case 1: sortfn = &cmp_n_down; break; + case 2: sortfn = &cmp_s_down; break; + case 3: sortfn = &cmp_s_up; break; + case 4: sortfn = &cmp_t_down; break; + case 5: sortfn = &cmp_t_up; break; + default: + sortfn = &cmp_n_up; + break; + } + qsort (_dirlist, _dircount, sizeof(_dirlist[0]), sortfn); + int i; + for (i = 0; i < _dircount && sel; ++i) { + if (!strcmp (_dirlist[i].name, sel)) { + _fsel = i; + break; + } + } +} + +static void fib_select (Display *dpy, int item) { + if (_fsel >= 0) { + _dirlist[_fsel].flags &= ~2; + } + _fsel = item; + if (_fsel >= 0 && _fsel < _dircount) { + _dirlist[_fsel].flags |= 2; + const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + if (_fsel < _scrl_f) { + _scrl_f = _fsel; + } + else if (_fsel >= _scrl_f + llen) { + _scrl_f = 1 + _fsel - llen; + } + } else { + _fsel = -1; + } + + fib_expose (dpy, _fib_win); +} + +static inline int fib_filter (const char *name) { + if (_fib_filter_function) { + return _fib_filter_function (name); + } else { + return 1; + } +} + +static void fib_pre_opendir (Display *dpy) { + if (_dirlist) free (_dirlist); + if (_pathbtn) free (_pathbtn); + _dirlist = NULL; + _pathbtn = NULL; + _dircount = 0; + _pathparts = 0; + query_font_geometry (dpy, _fib_gc, "Size ", &_fib_font_size_width, NULL, NULL, NULL); + fib_reset (); + _fsel = -1; +} + +static void fib_post_opendir (Display *dpy, const char *sel) { + if (_dircount > 0) + _fsel = 0; // select first + else + _fsel = -1; + fib_resort (sel); + + if (_dircount > 0 && _fsel >= 0) { + fib_select (dpy, _fsel); + } else { + fib_expose (dpy, _fib_win); + } +} + +static int fib_dirlistadd (Display *dpy, const int i, const char* path, const char *name, time_t mtime) { + char tp[1024]; + struct stat fs; + if (!_fib_hidden_fn && name[0] == '.') return -1; + if (!strcmp (name, ".")) return -1; + if (!strcmp (name, "..")) return -1; + strcpy (tp, path); + strcat (tp, name); + if (access (tp, R_OK)) { + return -1; + } + if (stat (tp, &fs)) { + return -1; + } + assert (i < _dircount); // could happen if dir changes while we're reading. + if (i >= _dircount) return -1; + if (S_ISDIR (fs.st_mode)) { + _dirlist[i].flags |= 4; + } + else if (S_ISREG (fs.st_mode)) { + if (!fib_filter (name)) return -1; + } +#if 0 // only needed with lstat() + else if (S_ISLNK (fs.st_mode)) { + if (!fib_filter (name)) return -1; + } +#endif + else { + return -1; + } + strcpy (_dirlist[i].name, name); + _dirlist[i].mtime = mtime > 0 ? mtime : fs.st_mtime; + _dirlist[i].size = fs.st_size; + if (!(_dirlist[i].flags & 4)) + fmt_size (dpy, &_dirlist[i]); + fmt_time (dpy, &_dirlist[i]); + return 0; +} + +static int fib_openrecent (Display *dpy, const char *sel) { + int i; + unsigned int j; + assert (_recentcnt > 0); + fib_pre_opendir (dpy); + query_font_geometry (dpy, _fib_gc, "Last Used", &_fib_font_time_width, NULL, NULL, NULL); + _dirlist = (FibFileEntry*) calloc (_recentcnt, sizeof(FibFileEntry)); + _dircount = _recentcnt; + for (j = 0, i = 0; j < _recentcnt; ++j) { + char base[1024]; + char *s = strrchr (_recentlist[j].path, '/'); + if (!s || !*++s) continue; + size_t len = (s - _recentlist[j].path); + strncpy (base, _recentlist[j].path, len); + base[len] = '\0'; + if (!fib_dirlistadd (dpy, i, base, s, _recentlist[j].atime)) { + _dirlist[i].rfp = &_recentlist[j]; + _dirlist[i].flags |= 8; + ++i; + } + } + _dircount = i; + fib_post_opendir (dpy, sel); + return _dircount; +} + +static int fib_opendir (Display *dpy, const char* path, const char *sel) { + char *t0, *t1; + int i; + + assert (path); + + if (strlen (path) == 0 && _recentcnt > 0) { // XXX we should use a better indication for this + strcpy (_cur_path, ""); + return fib_openrecent (dpy, sel); + } + + assert (strlen (path) < sizeof(_cur_path) -1); + assert (strlen (path) > 0); + assert (strstr (path, "//") == NULL); + assert (path[0] == '/'); + + fib_pre_opendir (dpy); + + query_font_geometry (dpy, _fib_gc, "Last Modified", &_fib_font_time_width, NULL, NULL, NULL); + DIR *dir = opendir (path); + if (!dir) { + strcpy (_cur_path, "/"); + } else { + int i; + struct dirent *de; + if (path != _cur_path) + strcpy (_cur_path, path); + + if (_cur_path[strlen (_cur_path) -1] != '/') + strcat (_cur_path, "/"); + + while ((de = readdir (dir))) { + if (!_fib_hidden_fn && de->d_name[0] == '.') continue; + ++_dircount; + } + + if (_dircount > 0) + _dirlist = (FibFileEntry*) calloc (_dircount, sizeof(FibFileEntry)); + + rewinddir (dir); + + i = 0; + while ((de = readdir (dir))) { + if (!fib_dirlistadd (dpy, i, _cur_path, de->d_name, 0)) + ++i; + } + _dircount = i; + closedir (dir); + } + + t0 = _cur_path; + while (*t0 && (t0 = strchr (t0, '/'))) { + ++_pathparts; + ++t0; + } + assert (_pathparts > 0); + _pathbtn = (FibPathButton*) calloc (_pathparts + 1, sizeof(FibPathButton)); + + t1 = _cur_path; + i = 0; + while (*t1 && (t0 = strchr (t1, '/'))) { + if (i == 0) { + strcpy (_pathbtn[i].name, "/"); + } else { + *t0 = 0; + strcpy (_pathbtn[i].name, t1); + } + query_font_geometry (dpy, _fib_gc, _pathbtn[i].name, &_pathbtn[i].xw, NULL, NULL, NULL); + _pathbtn[i].xw += BTNPADDING + BTNPADDING; + *t0 = '/'; + t1 = t0 + 1; + ++i; + } + fib_post_opendir (dpy, sel); + return _dircount; +} + +static int fib_open (Display *dpy, int item) { + char tp[1024]; + if (_dirlist[item].flags & 8) { + assert (_dirlist[item].rfp); + strcpy (_rv_open, _dirlist[item].rfp->path); + _status = 1; + return 0; + } + strcpy (tp, _cur_path); + strcat (tp, _dirlist[item].name); + if (_dirlist[item].flags & 4) { + fib_opendir (dpy, tp, NULL); + return 0; + } else { + _status = 1; + strcpy (_rv_open, tp); + } + return 0; +} + +static void cb_cancel (Display *dpy) { + _status = -1; + + // unused + return; (void)dpy; +} + +static void cb_open (Display *dpy) { + if (_fsel >= 0 && _fsel < _dircount) { + fib_open (dpy, _fsel); + } +} + +static void sync_button_states () { + if (_fib_show_places) + _btn_places.flags |= 2; + else + _btn_places.flags &= ~2; + if (_fib_filter_fn) // inverse -> show all + _btn_filter.flags &= ~2; + else + _btn_filter.flags |= 2; + if (_fib_hidden_fn) + _btn_hidden.flags |= 2; + else + _btn_hidden.flags &= ~2; +} + +static void cb_places (Display *dpy) { + _fib_show_places = ! _fib_show_places; + if (_placecnt < 1) + _fib_show_places = 0; + sync_button_states (); + _fib_resized = 1; + fib_expose (dpy, _fib_win); +} + +static void cb_filter (Display *dpy) { + _fib_filter_fn = ! _fib_filter_fn; + sync_button_states (); + char *sel = _fsel >= 0 ? strdup (_dirlist[_fsel].name) : NULL; + fib_opendir (dpy, _cur_path, sel); + free (sel); +} + +static void cb_hidden (Display *dpy) { + _fib_hidden_fn = ! _fib_hidden_fn; + sync_button_states (); + char *sel = _fsel >= 0 ? strdup (_dirlist[_fsel].name) : NULL; + fib_opendir (dpy, _cur_path, sel); + free (sel); +} + +static int fib_widget_at_pos (Display *dpy, int x, int y, int *it) { + const int btop = _fib_height - BTNBTMMARGIN * _fib_font_vsep - _fib_font_ascent - BTNPADDING * _scalefactor; + const int bbot = btop + _fib_font_height + BTNPADDING * 2 * _scalefactor; + const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + const int ltop = LISTTOP * _fib_font_vsep; + const int fbot = ltop + 4 * _scalefactor + llen * _fib_font_vsep; + const int ptop = PATHBTNTOP - _fib_font_ascent; + assert (it); + + // paths at top + if (y > ptop && y < ptop + _fib_font_height && _view_p >= 0 && _pathparts > 0) { + int i = _view_p; + *it = -1; + if (i > 0) { // special case '<' + if (x > FAREAMRGB * _scalefactor && x <= FAREAMRGB * _scalefactor + _pathbtn[0].xw) { + *it = _view_p - 1; + i = _pathparts; + } + } + while (i < _pathparts) { + if (x >= _pathbtn[i].x0 && x <= _pathbtn[i].x0 + _pathbtn[i].xw) { + *it = i; + break; + } + ++i; + } + assert (*it < _pathparts); + if (*it >= 0) return 1; + else return 0; + } + + // buttons at bottom + if (y > btop && y < bbot) { + size_t i; + *it = -1; + for (i = 0; i < sizeof(_btns) / sizeof(FibButton*); ++i) { + const int bx = _btns[i]->x0; + if (_btns[i]->flags & 8) { continue; } + if (x > bx && x < bx + _btns[i]->xw) { + *it = i; + } + } + if (*it >= 0) return 3; + else return 0; + } + + // main file area + if (y >= ltop - _fib_font_vsep && y < fbot && x > FAREAMRGL * _scalefactor && x < _fib_width - FAREAMRGR * _scalefactor) { + // scrollbar + if (_scrl_y0 > 0 && x >= _fib_width - (FAREAMRGR + SCROLLBARW) * _scalefactor && x <= _fib_width - FAREAMRGR * _scalefactor) { + if (y >= _scrl_y0 && y < _scrl_y1) { + *it = 0; + } else if (y >= _scrl_y1) { + *it = 2; + } else { + *it = 1; + } + return 4; + } + // file-list + else if (y >= ltop) { + const int item = (y - ltop) / _fib_font_vsep + _scrl_f; + *it = -1; + if (item >= 0 && item < _dircount) { + *it = item; + } + if (*it >= 0) return 2; + else return 0; + } + else { + *it = -1; + const int fsel_width = _fib_width - (FAREAMRGL + FAREAMRGR) * _scalefactor - (llen < _dircount ? SCROLLBARW * _scalefactor : 0); + const int t_s = FAREAMRGL * _scalefactor + fsel_width - _fib_font_time_width - TEXTSEP * 2 * _scalefactor; + const int t_t = FAREAMRGL * _scalefactor + fsel_width - TEXTSEP * _scalefactor - _fib_font_size_width - ((_columns & 2) ? ( _fib_font_time_width + TEXTSEP * 2 * _scalefactor) : 0); + if (x >= fsel_width + FAREAMRGL * _scalefactor) ; + else if ((_columns & 2) && x >= t_s) *it = 3; + else if ((_columns & 1) && x >= t_t) *it = 2; + else if (x >= FAREATEXTL * _scalefactor + _fib_dir_indent - TEXTSEP * _scalefactor) *it = 1; + if (*it >= 0) return 5; + else return 0; + } + } + + // places list + if (_fib_show_places && y >= ltop && y < fbot && x > FAREAMRGB * _scalefactor && x < (FAREAMRGL - FAREAMRGB) * _scalefactor) { + const int item = (y - ltop) / _fib_font_vsep; + *it = -1; + if (item >= 0 && item < _placecnt) { + *it = item; + } + if (*it >= 0) return 6; + else return 0; + } + + return 0; + + // unused + (void)dpy; +} + +static void fib_update_hover (Display *dpy, int need_expose, const int type, const int item) { + int hov_p = -1; + int hov_b = -1; + int hov_h = -1; + int hov_s = -1; +#ifdef LIST_ENTRY_HOVER + int hov_f = -1; + int hov_l = -1; +#endif + + switch (type) { + case 1: hov_p = item; break; + case 3: hov_b = item; break; + case 4: hov_s = item; break; + case 5: hov_h = item; break; +#ifdef LIST_ENTRY_HOVER + case 6: hov_l = item; break; + case 2: hov_f = item; break; +#endif + default: break; + } +#ifdef LIST_ENTRY_HOVER + if (hov_f != _hov_f) { _hov_f = hov_f; need_expose = 1; } + if (hov_l != _hov_l) { _hov_l = hov_l; need_expose = 1; } +#endif + if (hov_b != _hov_b) { _hov_b = hov_b; need_expose = 1; } + if (hov_p != _hov_p) { _hov_p = hov_p; need_expose = 1; } + if (hov_h != _hov_h) { _hov_h = hov_h; need_expose = 1; } + if (hov_s != _hov_s) { _hov_s = hov_s; need_expose = 1; } + + if (need_expose) { + fib_expose (dpy, _fib_win); + } +} + +static void fib_motion (Display *dpy, int x, int y) { + int it = -1; + + if (_scrl_my >= 0) { + const int sdiff = y - _scrl_my; + const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + const int fsel_height = 4 + llen * _fib_font_vsep; + const float sl = (fsel_height + _fib_font_vsep - (SCROLLBOXH + SCROLLBOXH)) / (float) _dircount; + + int news = _scrl_mf + sdiff / sl; + if (news < 0) news = 0; + if (news >= (_dircount - llen)) news = _dircount - llen; + if (news != _scrl_f) { + _scrl_f = news; + fib_expose (dpy, _fib_win); + } + return; + } + + const int type = fib_widget_at_pos (dpy, x, y, &it); + fib_update_hover (dpy, 0, type, it); +} + +static void fib_mousedown (Display *dpy, int x, int y, int btn, unsigned long time) { + int it; + switch (fib_widget_at_pos (dpy, x, y, &it)) { + case 4: // scrollbar + if (btn == 1) { + _dblclk = 0; + if (it == 0) { + _scrl_my = y; + _scrl_mf = _scrl_f; + } else { + int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + if (llen < 2) llen = 2; + int news = _scrl_f; + if (it == 1) { + news -= llen - 1; + } else { + news += llen - 1; + } + if (news < 0) news = 0; + if (news >= (_dircount - llen)) news = _dircount - llen; + if (news != _scrl_f && _scrl_y0 >= 0) { + assert (news >=0); + _scrl_f = news; + fib_update_hover (dpy, 1, 4, it); + } + } + } + break; + case 2: // file-list + if (btn == 4 || btn == 5) { + const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + int news = _scrl_f + ((btn == 4) ? - 1 : 1); + if (news < 0) news = 0; + if (news >= (_dircount - llen)) news = _dircount - llen; + if (news != _scrl_f && _scrl_y0 >= 0) { + assert (news >=0); + _scrl_f = news; + fib_update_hover (dpy, 1, 0, 0); + } + _dblclk = 0; + } + else if (btn == 1 && it >= 0 && it < _dircount) { + if (_fsel == it) { + if (time - _dblclk < DBLCLKTME) { + fib_open (dpy, it); + _dblclk = 0; + } + _dblclk = time; + } else { + fib_select (dpy, it); + _dblclk = time; + } + /* + if (_fsel >= 0) { + if (!(_dirlist[_fsel].flags & 4)); + } + */ + } + break; + case 1: // paths + assert (_fsel < _dircount); + assert (it >= 0 && it < _pathparts); + { + int i = 0; + char path[1024] = "/"; + while (++i <= it) { + strcat (path, _pathbtn[i].name); + strcat (path, "/"); + } + char *sel = NULL; + if (i < _pathparts) + sel = strdup (_pathbtn[i].name); + else if (i == _pathparts && _fsel >= 0) + sel = strdup (_dirlist[_fsel].name); + fib_opendir (dpy, path, sel); + free (sel); + } + break; + case 3: // btn + if (btn == 1 && _btns[it]->callback) { + _btns[it]->callback (dpy); + } + break; + case 5: // sort + if (btn == 1) { + switch (it) { + case 1: if (_sort == 0) _sort = 1; else _sort = 0; break; + case 2: if (_sort == 2) _sort = 3; else _sort = 2; break; + case 3: if (_sort == 4) _sort = 5; else _sort = 4; break; + } + if (_fsel >= 0) { + assert (_dirlist && _dircount >= _fsel); + _dirlist[_fsel].flags &= ~2; + char *sel = strdup (_dirlist[_fsel].name); + fib_resort (sel); + free (sel); + } else { + fib_resort (NULL); + _fsel = -1; + } + fib_reset (); + _hov_h = it; + fib_select (dpy, _fsel); + } + break; + case 6: + if (btn == 1 && it >= 0 && it < _placecnt) { + fib_opendir (dpy, _placelist[it].path, NULL); + } + break; + default: + break; + } +} + +static void fib_mouseup (Display *dpy, int x, int y, int btn, unsigned long time) { + _scrl_my = -1; + + // unused + return; (void)dpy; (void)x; (void)y; (void)btn; (void)time; +} + +static void add_place_raw (Display *dpy, const char *name, const char *path) { + _placelist = (FibPlace*) realloc (_placelist, (_placecnt + 1) * sizeof(FibPlace)); + strcpy (_placelist[_placecnt].path, path); + strcpy (_placelist[_placecnt].name, name); + _placelist[_placecnt].flags = 0; + + int sw = -1; + query_font_geometry (dpy, _fib_gc, name, &sw, NULL, NULL, NULL); + if (sw > _fib_place_width) { + _fib_place_width = sw; + } + ++_placecnt; +} + +static int add_place_places (Display *dpy, const char *name, const char *url) { + char const * path; + struct stat fs; + int i; + if (!url || strlen (url) < 1) return -1; + if (!name || strlen (name) < 1) return -1; + if (url[0] == '/') { + path = url; + } + else if (!strncmp (url, "file:///", 8)) { + path = &url[7]; + } + else { + return -1; + } + + if (access (path, R_OK)) { + return -1; + } + if (stat (path, &fs)) { + return -1; + } + if (!S_ISDIR (fs.st_mode)) { + return -1; + } + + for (i = 0; i < _placecnt; ++i) { + if (!strcmp (path, _placelist[i].path)) { + return -1; + } + } + add_place_raw (dpy, name, path); + return 0; +} + +static int parse_gtk_bookmarks (Display *dpy, const char *fn) { + char tmp[1024]; + if (access (fn, R_OK)) { + return -1; + } + FILE *bm = fopen (fn, "r"); + if (!bm) return -1; + int found = 0; + while (fgets (tmp, sizeof(tmp), bm) + && strlen (tmp) > 1 + && strlen (tmp) < sizeof(tmp)) + { + char *s, *n; + tmp[strlen (tmp) - 1] = '\0'; // strip newline + if ((s = strchr (tmp, ' '))) { + *s = '\0'; + n = strdup (++s); + decode_3986 (tmp); + if (!add_place_places (dpy, n, tmp)) { + ++found; + } + free (n); + } else if ((s = strrchr (tmp, '/'))) { + n = strdup (++s); + decode_3986 (tmp); + if (!add_place_places (dpy, n, tmp)) { + ++found; + } + free (n); + } + } + fclose (bm); + return found; +} + +#ifdef HAVE_MNTENT +static const char *ignore_mountpoints[] = { + "/bin", "/boot", "/dev", "/etc", + "/lib", "/live", "/mnt", "/opt", + "/root", "/sbin", "/srv", "/tmp", + "/usr", "/var", "/proc", "/sbin", + "/net", "/sys" +}; + +static const char *ignore_fs[] = { + "auto", "autofs", + "debugfs", "devfs", + "devpts", "ecryptfs", + "fusectl", "kernfs", + "linprocfs", "proc", + "ptyfs", "rootfs", + "selinuxfs", "sysfs", + "tmpfs", "usbfs", + "nfsd", "rpc_pipefs", +}; + +static const char *ignore_devices[] = { + "binfmt_", "devpts", + "gvfs", "none", + "nfsd", "sunrpc", + "/dev/loop", "/dev/vn" +}; + +static int check_mount (const char *mountpoint, const char *fs, const char *device) { + size_t i; + if (!mountpoint || !fs || !device) return -1; + //printf("%s %s %s\n", mountpoint, fs, device); + for (i = 0 ; i < sizeof(ignore_mountpoints) / sizeof(char*); ++i) { + if (!strncmp (mountpoint, ignore_mountpoints[i], strlen (ignore_mountpoints[i]))) { + return 1; + } + } + if (!strncmp (mountpoint, "/home", 5)) { + return 1; + } + for (i = 0 ; i < sizeof(ignore_fs) / sizeof(char*); ++i) { + if (!strncmp (fs, ignore_fs[i], strlen (ignore_fs[i]))) { + return 1; + } + } + for (i = 0 ; i < sizeof(ignore_devices) / sizeof(char*); ++i) { + if (!strncmp (device, ignore_devices[i], strlen (ignore_devices[i]))) { + return 1; + } + } + return 0; +} + +static int read_mtab (Display *dpy, const char *mtab) { + FILE *mt = fopen (mtab, "r"); + if (!mt) return -1; + int found = 0; + struct mntent *mntent; + while ((mntent = getmntent (mt)) != NULL) { + char *s; + if (check_mount (mntent->mnt_dir, mntent->mnt_type, mntent->mnt_fsname)) + continue; + + if ((s = strrchr (mntent->mnt_dir, '/'))) { + ++s; + } else { + s = mntent->mnt_dir; + } + if (!add_place_places (dpy, s, mntent->mnt_dir)) { + ++found; + } + } + fclose (mt); + return found; +} +#endif + +static void populate_places (Display *dpy) { + char tmp[1024]; + int spacer = -1; + if (_placecnt > 0) return; + _fib_place_width = 0; + + if (_recentcnt > 0) { + add_place_raw (dpy, "Recently Used", ""); + _placelist[0].flags |= 4; + } + + add_place_places (dpy, "Home", getenv ("HOME")); + + if (getenv ("HOME")) { + strcpy (tmp, getenv ("HOME")); + strcat (tmp, "/Desktop"); + add_place_places (dpy, "Desktop", tmp); + } + + add_place_places (dpy, "Filesystem", "/"); + + if (_placecnt > 0) spacer = _placecnt -1; + + if (strlen (_fib_cfg_custom_places) > 0) { + parse_gtk_bookmarks (dpy, _fib_cfg_custom_places); + } + +#ifdef HAVE_MNTENT + if (read_mtab (dpy, "/proc/mounts") < 1) { + read_mtab (dpy, "/etc/mtab"); + } +#endif + + int parsed_bookmarks = 0; + if (!parsed_bookmarks && getenv ("HOME")) { + strcpy (tmp, getenv ("HOME")); + strcat (tmp, "/.gtk-bookmarks"); + if (parse_gtk_bookmarks (dpy, tmp) > 0) { + parsed_bookmarks = 1; + } + } + if (!parsed_bookmarks && getenv ("XDG_CONFIG_HOME")) { + strcpy (tmp, getenv ("XDG_CONFIG_HOME")); + strcat (tmp, "/gtk-3.0/bookmarks"); + if (parse_gtk_bookmarks (dpy, tmp) > 0) { + parsed_bookmarks = 1; + } + } + if (!parsed_bookmarks && getenv ("HOME")) { + strcpy (tmp, getenv ("HOME")); + strcat (tmp, "/.config/gtk-3.0/bookmarks"); + if (parse_gtk_bookmarks (dpy, tmp) > 0) { + parsed_bookmarks = 1; + } + } + if (_fib_place_width > 0) { + _fib_place_width = MIN (_fib_place_width + TEXTSEP + _fib_dir_indent /*extra*/ , PLACESWMAX); + } + if (spacer > 0 && spacer < _placecnt -1) { + _placelist[ spacer ].flags |= 4; + } +} + +static uint8_t font_err = 0; +static int x_error_handler (Display *d, XErrorEvent *e) { + font_err = 1; + return 0; + + // unused + (void)d; (void)e; +} + +int x_fib_show (Display *dpy, Window parent, int x, int y, double scalefactor) { + if (_fib_win) { + XSetInputFocus (dpy, _fib_win, RevertToParent, CurrentTime); + return -1; + } + + _status = 0; + _rv_open[0] = '\0'; + + Colormap colormap = DefaultColormap (dpy, DefaultScreen (dpy)); + _c_gray1.flags = DoRed | DoGreen | DoBlue; + _c_gray0.red = _c_gray0.green = _c_gray0.blue = 0x5000; // 95% hover prelight + _c_gray1.red = _c_gray1.green = _c_gray1.blue = 0x1100; // 93% window bg, scrollbar-fg + _c_gray2.red = _c_gray2.green = _c_gray2.blue = 0x1c00; // 83% button & list bg + _c_gray3.red = _c_gray3.green = _c_gray3.blue = 0x0a00; // 75% heading + scrollbar-bg + _c_gray4.red = _c_gray4.green = _c_gray4.blue = 0xd600; // 40% prelight text, sep lines + _c_gray5.red = _c_gray5.green = _c_gray5.blue = 0x3000; // 20% 3D border + + if (!XAllocColor (dpy, colormap, &_c_gray0)) return -1; + if (!XAllocColor (dpy, colormap, &_c_gray1)) return -1; + if (!XAllocColor (dpy, colormap, &_c_gray2)) return -1; + if (!XAllocColor (dpy, colormap, &_c_gray3)) return -1; + if (!XAllocColor (dpy, colormap, &_c_gray4)) return -1; + if (!XAllocColor (dpy, colormap, &_c_gray5)) return -1; + + XSetWindowAttributes attr; + memset (&attr, 0, sizeof(XSetWindowAttributes)); + attr.border_pixel = _c_gray2.pixel; + + attr.event_mask = ExposureMask | KeyPressMask + | ButtonPressMask | ButtonReleaseMask + | ConfigureNotify | StructureNotifyMask + | PointerMotionMask | LeaveWindowMask; + + _fib_win = XCreateWindow ( + dpy, DefaultRootWindow (dpy), + x, y, _fib_width * scalefactor, _fib_height * scalefactor, + 1, CopyFromParent, InputOutput, CopyFromParent, + CWEventMask | CWBorderPixel, &attr); + + _scalefactor = scalefactor; + + if (!_fib_win) { return 1; } + + if (parent) + XSetTransientForHint (dpy, _fib_win, parent); + + XStoreName (dpy, _fib_win, "Select File"); + + Atom wmDelete = XInternAtom (dpy, "WM_DELETE_WINDOW", True); + XSetWMProtocols (dpy, _fib_win, &wmDelete, 1); + + _fib_gc = XCreateGC (dpy, _fib_win, 0, NULL); + XSetLineAttributes (dpy, _fib_gc, 1, LineSolid, CapButt, JoinMiter); + const char dl[1] = {1}; + XSetDashes (dpy, _fib_gc, 0, dl, 1); + + int (*handler)(Display *, XErrorEvent *) = XSetErrorHandler (&x_error_handler); + +#define _XTESTFONT(FN) \ + { \ + font_err = 0; \ + _fibfont = XLoadFont (dpy, FN); \ + XSetFont (dpy, _fib_gc, _fibfont); \ + XSync (dpy, False); \ + } + + font_err = 1; + if (getenv ("XJFONT")) _XTESTFONT (getenv ("XJFONT")); + if (font_err && strlen (_fib_cfg_custom_font) > 0) _XTESTFONT (_fib_cfg_custom_font); + if (scalefactor >= 2.5) { + if (font_err) _XTESTFONT ("-*-helvetica-medium-r-normal-*-18-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-*-verdana-medium-r-normal-*-18-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-20-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-18-*-*-*-*-*-*-*"); + } else if (scalefactor >= 2) { + if (font_err) _XTESTFONT ("-*-helvetica-medium-r-normal-*-16-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-*-verdana-medium-r-normal-*-16-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-18-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-16-*-*-*-*-*-*-*"); + } else if (scalefactor >= 1.5) { + if (font_err) _XTESTFONT ("-*-helvetica-medium-r-normal-*-14-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-*-verdana-medium-r-normal-*-14-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-15-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-14-*-*-*-*-*-*-*"); + } else { + if (font_err) _XTESTFONT ("-*-helvetica-medium-r-normal-*-12-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-*-verdana-medium-r-normal-*-12-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-13-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-12-*-*-*-*-*-*-*"); + } + if (font_err) _fibfont = None; + XSync (dpy, False); + XSetErrorHandler (handler); + + if (_fib_font_height == 0) { // 1st time only + query_font_geometry (dpy, _fib_gc, "D ", &_fib_dir_indent, NULL, NULL, NULL); + query_font_geometry (dpy, _fib_gc, "_", &_fib_spc_norm, NULL, NULL, NULL); + if (query_font_geometry (dpy, _fib_gc, "|0Yy", NULL, &_fib_font_height, &_fib_font_ascent, NULL)) { + XFreeGC (dpy, _fib_gc); + XDestroyWindow (dpy, _fib_win); + _fib_win = 0; + return -1; + } + _fib_font_height += 3 * scalefactor; + _fib_font_ascent += 2 * scalefactor; + _fib_font_vsep = _fib_font_height + 2 * scalefactor; + } + + populate_places (dpy); + + strcpy (_btn_ok.text, "Open"); + strcpy (_btn_cancel.text, "Cancel"); + strcpy (_btn_filter.text, "List All Files"); + strcpy (_btn_places.text, "Show Places"); + strcpy (_btn_hidden.text, "Show Hidden"); + + _btn_ok.callback = &cb_open; + _btn_cancel.callback = &cb_cancel; + _btn_filter.callback = &cb_filter; + _btn_places.callback = &cb_places; + _btn_hidden.callback = &cb_hidden; + _btn_filter.flags |= 4; + _btn_places.flags |= 4; + _btn_hidden.flags |= 4; + + if (!_fib_filter_function) { + _btn_filter.flags |= 8; + } + + size_t i; + int btncnt = 0; + _btn_w = 0; + _btn_span = 0; + for (i = 0; i < sizeof(_btns) / sizeof(FibButton*); ++i) { + if (_btns[i]->flags & 8) { continue; } + query_font_geometry (dpy, _fib_gc, _btns[i]->text, &_btns[i]->tw, NULL, NULL, NULL); + if (_btns[i]->flags & 4) { + _btn_span += _btns[i]->tw + _fib_font_ascent + TEXTSEP * scalefactor; + } else { + ++btncnt; + if (_btns[i]->tw > _btn_w) + _btn_w = _btns[i]->tw; + } + } + + _btn_w += (BTNPADDING + BTNPADDING + TEXTSEP + TEXTSEP + TEXTSEP) * scalefactor; + _btn_span += _btn_w * btncnt + DSEP * scalefactor * (i - 1) + (FAREAMRGR + FAREAMRGB) * scalefactor; + + for (i = 0; i < sizeof(_btns) / sizeof(FibButton*); ++i) { + if (_btns[i]->flags & 8) { continue; } + if (_btns[i]->flags & 4) { + _btns[i]->xw = _btns[i]->tw + _fib_font_ascent + TEXTSEP * scalefactor; + } else { + _btns[i]->xw = _btn_w; + } + } + + sync_button_states () ; + + _fib_height = _fib_font_vsep * 15.8 * (1.0 + (scalefactor - 1.0) / 2.0); + _fib_width = MAX (_btn_span, 480 * scalefactor); + + XResizeWindow (dpy, _fib_win, _fib_width, _fib_height); + + XTextProperty x_wname, x_iname; + XSizeHints hints; + XWMHints wmhints; + + hints.flags = PSize | PMinSize; + hints.min_width = _btn_span; + hints.min_height = 8 * _fib_font_vsep; + + char *w_name = & _fib_cfg_title[0]; + + wmhints.input = True; + wmhints.flags = InputHint; + if (XStringListToTextProperty (&w_name, 1, &x_wname) && + XStringListToTextProperty (&w_name, 1, &x_iname)) + { + XSetWMProperties (dpy, _fib_win, &x_wname, &x_iname, NULL, 0, &hints, &wmhints, NULL); + XFree (x_wname.value); + XFree (x_iname.value); + } + + XSetWindowBackground (dpy, _fib_win, _c_gray1.pixel); + + _fib_mapped = 0; + XMapRaised (dpy, _fib_win); + + if (!strlen (_cur_path) || !fib_opendir (dpy, _cur_path, NULL)) { + fib_opendir (dpy, getenv ("HOME") ? getenv ("HOME") : "/", NULL); + } + +#if 0 + XGrabPointer (dpy, _fib_win, True, + ButtonReleaseMask | ButtonPressMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask | StructureNotifyMask, + GrabModeAsync, GrabModeAsync, None, None, CurrentTime); + XGrabKeyboard (dpy, _fib_win, True, GrabModeAsync, GrabModeAsync, CurrentTime); + //XSetInputFocus (dpy, parent, RevertToNone, CurrentTime); +#endif + _recentlock = 1; + return 0; +} + +void x_fib_close (Display *dpy) { + if (!_fib_win) return; + XFreeGC (dpy, _fib_gc); + XDestroyWindow (dpy, _fib_win); + _fib_win = 0; + free (_dirlist); + _dirlist = NULL; + free (_pathbtn); + _pathbtn = NULL; + if (_fibfont != None) XUnloadFont (dpy, _fibfont); + _fibfont = None; + free (_placelist); + _placelist = NULL; + _dircount = 0; + _pathparts = 0; + _placecnt = 0; + if (_pixbuffer != None) XFreePixmap (dpy, _pixbuffer); + _pixbuffer = None; + Colormap colormap = DefaultColormap (dpy, DefaultScreen (dpy)); + XFreeColors (dpy, colormap, &_c_gray0.pixel, 1, 0); + XFreeColors (dpy, colormap, &_c_gray1.pixel, 1, 0); + XFreeColors (dpy, colormap, &_c_gray2.pixel, 1, 0); + XFreeColors (dpy, colormap, &_c_gray3.pixel, 1, 0); + XFreeColors (dpy, colormap, &_c_gray4.pixel, 1, 0); + XFreeColors (dpy, colormap, &_c_gray5.pixel, 1, 0); + _recentlock = 0; +} + +int x_fib_handle_events (Display *dpy, XEvent *event) { + if (!_fib_win) return 0; + if (_status) return 0; + if (event->xany.window != _fib_win) { + return 0; + } + + switch (event->type) { + case MapNotify: + _fib_mapped = 1; + break; + case UnmapNotify: + _fib_mapped = 0; + break; + case LeaveNotify: + fib_update_hover (dpy, 1, 0, 0); + break; + case ClientMessage: + if (!strcmp (XGetAtomName (dpy, event->xclient.message_type), "WM_PROTOCOLS")) { + _status = -1; + } + break; + case ConfigureNotify: + if ( + (event->xconfigure.width > 1 && event->xconfigure.height > 1) + && + (event->xconfigure.width != _fib_width || event->xconfigure.height != _fib_height) + ) + { + _fib_width = event->xconfigure.width; + _fib_height = event->xconfigure.height; + _fib_resized = 1; + } + break; + case Expose: + if (event->xexpose.count == 0) { + fib_expose (dpy, event->xany.window); + } + break; + case MotionNotify: + fib_motion (dpy, event->xmotion.x, event->xmotion.y); + if (event->xmotion.is_hint == NotifyHint) { + XGetMotionEvents (dpy, event->xany.window, CurrentTime, CurrentTime, NULL); + } + break; + case ButtonPress: + fib_mousedown (dpy, event->xbutton.x, event->xbutton.y, event->xbutton.button, event->xbutton.time); + break; + case ButtonRelease: + fib_mouseup (dpy, event->xbutton.x, event->xbutton.y, event->xbutton.button, event->xbutton.time); + break; + case KeyRelease: + break; + case KeyPress: + { + KeySym key; + char buf[100]; + static XComposeStatus stat; + XLookupString (&event->xkey, buf, sizeof(buf), &key, &stat); + switch (key) { + case XK_Escape: + _status = -1; + break; + case XK_Up: + if (_fsel > 0) { + fib_select (dpy, _fsel - 1); + } + break; + case XK_Down: + if (_fsel < _dircount -1) { + fib_select ( dpy, _fsel + 1); + } + break; + case XK_Page_Up: + if (_fsel > 0) { + int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + if (llen < 1) llen = 1; else --llen; + int fs = MAX (0, _fsel - llen); + fib_select ( dpy, fs); + } + break; + case XK_Page_Down: + if (_fsel < _dircount) { + int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + if (llen < 1) llen = 1; else --llen; + int fs = MIN (_dircount - 1, _fsel + llen); + fib_select ( dpy, fs); + } + break; + case XK_Left: + if (_pathparts > 1) { + int i = 0; + char path[1024] = "/"; + while (++i < _pathparts - 1) { + strcat (path, _pathbtn[i].name); + strcat (path, "/"); + } + char *sel = strdup (_pathbtn[_pathparts-1].name); + fib_opendir (dpy, path, sel); + free (sel); + } + break; + case XK_Right: + if (_fsel >= 0 && _fsel < _dircount) { + if (_dirlist[_fsel].flags & 4) { + cb_open (dpy); + } + } + break; + case XK_Return: + cb_open (dpy); + break; + default: + if ((key >= XK_a && key <= XK_z) || (key >= XK_0 && key <= XK_9)) { + int i; + for (i = 0; i < _dircount; ++i) { + int j = (_fsel + i + 1) % _dircount; + char kcmp = _dirlist[j].name[0]; + if (kcmp > 0x40 && kcmp <= 0x5A) kcmp |= 0x20; + if (kcmp == (char)key) { + fib_select ( dpy, j); + break; + } + } + } + break; + } + } + break; + } + + if (_status) { + x_fib_close (dpy); + } + return _status; +} + +int x_fib_status () { + return _status; +} + +int x_fib_configure (int k, const char *v) { + if (_fib_win) { return -1; } + switch (k) { + case 0: + if (strlen (v) >= sizeof(_cur_path) -1) return -2; + if (strlen (v) < 1) return -2; + if (v[0] != '/') return -2; + if (strstr (v, "//")) return -2; + strncpy (_cur_path, v, sizeof(_cur_path)); + break; + case 1: + if (strlen (v) >= sizeof(_fib_cfg_title) -1) return -2; + strncpy (_fib_cfg_title, v, sizeof(_fib_cfg_title)); + break; + case 2: + if (strlen (v) >= sizeof(_fib_cfg_custom_font) -1) return -2; + strncpy (_fib_cfg_custom_font, v, sizeof(_fib_cfg_custom_font)); + break; + case 3: + if (strlen (v) >= sizeof(_fib_cfg_custom_places) -1) return -2; + strncpy (_fib_cfg_custom_places, v, sizeof(_fib_cfg_custom_places)); + break; + default: + return -2; + } + return 0; +} + +int x_fib_cfg_buttons (int k, int v) { + if (_fib_win) { return -1; } + switch (k) { + case 1: + if (v < 0) { + _btn_hidden.flags |= 8; + } else { + _btn_hidden.flags &= ~8; + } + if (v == 1) { + _btn_hidden.flags |= 2; + _fib_hidden_fn = 1; + } else if (v == 0) { + _btn_hidden.flags &= 2; + _fib_hidden_fn = 0; + } + break; + case 2: + if (v < 0) { + _btn_places.flags |= 8; + } else { + _btn_places.flags &= ~8; + } + if (v == 1) { + _btn_places.flags |= 2; + _fib_show_places = 1; + } else if (v == 0) { + _btn_places.flags &= ~2; + _fib_show_places = 0; + } + break; + case 3: + // NB. filter button is automatically hidden + // IFF the filter-function is NULL. + if (v < 0) { + _btn_filter.flags |= 8; + } else { + _btn_filter.flags &= ~8; + } + if (v == 1) { + _btn_filter.flags &= ~2; // inverse - 'show all' = !filter + _fib_filter_fn = 1; + } else if (v == 0) { + _btn_filter.flags |= 2; + _fib_filter_fn = 0; + } + break; + default: + return -2; + } + return 0; +} + +int x_fib_cfg_filter_callback (int (*cb)(const char*)) { + if (_fib_win) { return -1; } + _fib_filter_function = cb; + return 0; +} + +char *x_fib_filename () { + if (_status > 0 && !_fib_win) + return strdup (_rv_open); + else + return NULL; +} +#endif // HAVE_X11 + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +# pragma GCC diagnostic pop +#endif + +/* example usage */ +#ifdef SOFD_TEST + +static int fib_filter_movie_filename (const char *name) { + if (!_fib_filter_fn) return 1; + const int l3 = strlen (name) - 3; + const int l4 = l3 - 1; + const int l5 = l4 - 1; + const int l6 = l5 - 1; + const int l9 = l6 - 3; + if ( + (l4 > 0 && ( + !strcasecmp (&name[l4], ".avi") + || !strcasecmp (&name[l4], ".mov") + || !strcasecmp (&name[l4], ".ogg") + || !strcasecmp (&name[l4], ".ogv") + || !strcasecmp (&name[l4], ".mpg") + || !strcasecmp (&name[l4], ".mov") + || !strcasecmp (&name[l4], ".mp4") + || !strcasecmp (&name[l4], ".mkv") + || !strcasecmp (&name[l4], ".vob") + || !strcasecmp (&name[l4], ".asf") + || !strcasecmp (&name[l4], ".avs") + || !strcasecmp (&name[l4], ".dts") + || !strcasecmp (&name[l4], ".flv") + || !strcasecmp (&name[l4], ".m4v") + )) || + (l5 > 0 && ( + !strcasecmp (&name[l5], ".h264") + || !strcasecmp (&name[l5], ".webm") + )) || + (l6 > 0 && ( + !strcasecmp (&name[l6], ".dirac") + )) || + (l9 > 0 && ( + !strcasecmp (&name[l9], ".matroska") + )) || + (l3 > 0 && ( + !strcasecmp (&name[l3], ".dv") + || !strcasecmp (&name[l3], ".ts") + )) + ) + { + return 1; + } + return 0; +} + +int main (int argc, char **argv) { + Display* dpy = XOpenDisplay (0); + if (!dpy) return -1; + + x_fib_cfg_filter_callback (fib_filter_movie_filename); + x_fib_configure (1, "Open Movie File"); + x_fib_load_recent ("/tmp/sofd.recent"); + x_fib_show (dpy, 0, 300, 300); + + while (1) { + XEvent event; + while (XPending (dpy) > 0) { + XNextEvent (dpy, &event); + if (x_fib_handle_events (dpy, &event)) { + if (x_fib_status () > 0) { + char *fn = x_fib_filename (); + printf ("OPEN '%s'\n", fn); + x_fib_add_recent (fn, time (NULL)); + free (fn); + } + } + } + if (x_fib_status ()) { + break; + } + usleep (80000); + } + x_fib_close (dpy); + + x_fib_save_recent ("/tmp/sofd.recent"); + + x_fib_free_recent (); + XCloseDisplay (dpy); + return 0; +} +#endif diff --git a/dgl/src/sofd/libsofd.h b/distrho/extra/sofd/libsofd.h diff --git a/distrho/src/DistrhoDefines.h b/distrho/src/DistrhoDefines.h @@ -27,10 +27,12 @@ /* Check OS */ #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) +# define DISTRHO_API # define DISTRHO_PLUGIN_EXPORT extern "C" __declspec (dllexport) # define DISTRHO_OS_WINDOWS 1 # define DISTRHO_DLL_EXTENSION "dll" #else +# define DISTRHO_API # define DISTRHO_PLUGIN_EXPORT extern "C" __attribute__ ((visibility("default"))) # if defined(__APPLE__) # define DISTRHO_OS_MAC 1 @@ -43,6 +45,8 @@ # define DISTRHO_OS_BSD 1 # elif defined(__GNU__) # define DISTRHO_OS_GNU_HURD 1 +# elif defined(__EMSCRIPTEN__) +# define DISTRHO_OS_WASM 1 # endif #endif @@ -71,6 +75,13 @@ # define nullptr NULL #endif +/* Define unlikely */ +#ifdef __GNUC__ +# define unlikely(x) __builtin_expect(x,0) +#else +# define unlikely(x) x +#endif + /* Define DISTRHO_DEPRECATED */ #if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 480 # define DISTRHO_DEPRECATED __attribute__((deprecated)) @@ -81,49 +92,49 @@ #endif /* Define DISTRHO_DEPRECATED_BY */ -#if defined(__clang__) && defined(DISTRHO_PROPER_CPP11_SUPPORT) +#if defined(__clang__) && (__clang_major__ * 100 + __clang_minor__) >= 502 # define DISTRHO_DEPRECATED_BY(other) __attribute__((deprecated("", other))) -#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 480 +#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 408 # define DISTRHO_DEPRECATED_BY(other) __attribute__((deprecated("Use " other))) #else # define DISTRHO_DEPRECATED_BY(other) DISTRHO_DEPRECATED #endif /* Define DISTRHO_SAFE_ASSERT* */ -#define DISTRHO_SAFE_ASSERT(cond) if (! (cond)) d_safe_assert (#cond, __FILE__, __LINE__); -#define DISTRHO_SAFE_ASSERT_INT(cond, value) if (! (cond)) d_safe_assert_int (#cond, __FILE__, __LINE__, static_cast<int>(value)); -#define DISTRHO_SAFE_ASSERT_INT2(cond, v1, v2) if (! (cond)) d_safe_assert_int2 (#cond, __FILE__, __LINE__, static_cast<int>(v1), static_cast<int>(v2)); -#define DISTRHO_SAFE_ASSERT_UINT(cond, value) if (! (cond)) d_safe_assert_uint (#cond, __FILE__, __LINE__, static_cast<uint>(value)); -#define DISTRHO_SAFE_ASSERT_UINT2(cond, v1, v2) if (! (cond)) d_safe_assert_uint2(#cond, __FILE__, __LINE__, static_cast<uint>(v1), static_cast<uint>(v2)); +#define DISTRHO_SAFE_ASSERT(cond) if (unlikely(!(cond))) d_safe_assert (#cond, __FILE__, __LINE__); +#define DISTRHO_SAFE_ASSERT_INT(cond, value) if (unlikely(!(cond))) d_safe_assert_int (#cond, __FILE__, __LINE__, static_cast<int>(value)); +#define DISTRHO_SAFE_ASSERT_INT2(cond, v1, v2) if (unlikely(!(cond))) d_safe_assert_int2 (#cond, __FILE__, __LINE__, static_cast<int>(v1), static_cast<int>(v2)); +#define DISTRHO_SAFE_ASSERT_UINT(cond, value) if (unlikely(!(cond))) d_safe_assert_uint (#cond, __FILE__, __LINE__, static_cast<uint>(value)); +#define DISTRHO_SAFE_ASSERT_UINT2(cond, v1, v2) if (unlikely(!(cond))) d_safe_assert_uint2(#cond, __FILE__, __LINE__, static_cast<uint>(v1), static_cast<uint>(v2)); -#define DISTRHO_SAFE_ASSERT_BREAK(cond) if (! (cond)) { d_safe_assert(#cond, __FILE__, __LINE__); break; } -#define DISTRHO_SAFE_ASSERT_CONTINUE(cond) if (! (cond)) { d_safe_assert(#cond, __FILE__, __LINE__); continue; } -#define DISTRHO_SAFE_ASSERT_RETURN(cond, ret) if (! (cond)) { d_safe_assert(#cond, __FILE__, __LINE__); return ret; } +#define DISTRHO_SAFE_ASSERT_BREAK(cond) if (unlikely(!(cond))) { d_safe_assert(#cond, __FILE__, __LINE__); break; } +#define DISTRHO_SAFE_ASSERT_CONTINUE(cond) if (unlikely(!(cond))) { d_safe_assert(#cond, __FILE__, __LINE__); continue; } +#define DISTRHO_SAFE_ASSERT_RETURN(cond, ret) if (unlikely(!(cond))) { d_safe_assert(#cond, __FILE__, __LINE__); return ret; } -#define DISTRHO_CUSTOM_SAFE_ASSERT(msg, cond) if (! (cond)) d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); -#define DISTRHO_CUSTOM_SAFE_ASSERT_BREAK(msg, cond) if (! (cond)) { d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); break; } -#define DISTRHO_CUSTOM_SAFE_ASSERT_CONTINUE(msg, cond) if (! (cond)) { d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); continue; } -#define DISTRHO_CUSTOM_SAFE_ASSERT_RETURN(msg, cond, ret) if (! (cond)) { d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); return ret; } +#define DISTRHO_CUSTOM_SAFE_ASSERT(msg, cond) if (unlikely(!(cond))) d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); +#define DISTRHO_CUSTOM_SAFE_ASSERT_BREAK(msg, cond) if (unlikely(!(cond))) { d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); break; } +#define DISTRHO_CUSTOM_SAFE_ASSERT_CONTINUE(msg, cond) if (unlikely(!(cond))) { d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); continue; } +#define DISTRHO_CUSTOM_SAFE_ASSERT_RETURN(msg, cond, ret) if (unlikely(!(cond))) { d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); return ret; } -#define DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_BREAK(msg, cond) if (! (cond)) { static bool _p; if (!_p) { _p = true; d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); } break; } -#define DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_CONTINUE(msg, cond) if (! (cond)) { static bool _p; if (!_p) { _p = true; d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); } continue; } -#define DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_RETURN(msg, cond, ret) if (! (cond)) { static bool _p; if (!_p) { _p = true; d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); } return ret; } +#define DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_BREAK(msg, cond) if (unlikely(!(cond))) { static bool _p; if (!_p) { _p = true; d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); } break; } +#define DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_CONTINUE(msg, cond) if (unlikely(!(cond))) { static bool _p; if (!_p) { _p = true; d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); } continue; } +#define DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_RETURN(msg, cond, ret) if (unlikely(!(cond))) { static bool _p; if (!_p) { _p = true; d_custom_safe_assert(msg, #cond, __FILE__, __LINE__); } return ret; } -#define DISTRHO_SAFE_ASSERT_INT_BREAK(cond, value) if (! (cond)) { d_safe_assert_int(#cond, __FILE__, __LINE__, static_cast<int>(value); break; } -#define DISTRHO_SAFE_ASSERT_INT_CONTINUE(cond, value) if (! (cond)) { d_safe_assert_int(#cond, __FILE__, __LINE__, static_cast<int>(value)); continue; } -#define DISTRHO_SAFE_ASSERT_INT_RETURN(cond, value, ret) if (! (cond)) { d_safe_assert_int(#cond, __FILE__, __LINE__, static_cast<int>(value)); return ret; } +#define DISTRHO_SAFE_ASSERT_INT_BREAK(cond, value) if (unlikely(!(cond))) { d_safe_assert_int(#cond, __FILE__, __LINE__, static_cast<int>(value)); break; } +#define DISTRHO_SAFE_ASSERT_INT_CONTINUE(cond, value) if (unlikely(!(cond))) { d_safe_assert_int(#cond, __FILE__, __LINE__, static_cast<int>(value)); continue; } +#define DISTRHO_SAFE_ASSERT_INT_RETURN(cond, value, ret) if (unlikely(!(cond))) { d_safe_assert_int(#cond, __FILE__, __LINE__, static_cast<int>(value)); return ret; } -#define DISTRHO_SAFE_ASSERT_INT2_BREAK(cond, v1, v2) if (! (cond)) { d_safe_assert_int2(#cond, __FILE__, __LINE__, static_cast<int>(v1), static_cast<int>(v2)); break; } -#define DISTRHO_SAFE_ASSERT_INT2_CONTINUE(cond, v1, v2) if (! (cond)) { d_safe_assert_int2(#cond, __FILE__, __LINE__, static_cast<int>(v1), static_cast<int>(v2)); continue; } -#define DISTRHO_SAFE_ASSERT_INT2_RETURN(cond, v1, v2, ret) if (! (cond)) { d_safe_assert_int2(#cond, __FILE__, __LINE__, static_cast<int>(v1), static_cast<int>(v2)); return ret; } +#define DISTRHO_SAFE_ASSERT_INT2_BREAK(cond, v1, v2) if (unlikely(!(cond))) { d_safe_assert_int2(#cond, __FILE__, __LINE__, static_cast<int>(v1), static_cast<int>(v2)); break; } +#define DISTRHO_SAFE_ASSERT_INT2_CONTINUE(cond, v1, v2) if (unlikely(!(cond))) { d_safe_assert_int2(#cond, __FILE__, __LINE__, static_cast<int>(v1), static_cast<int>(v2)); continue; } +#define DISTRHO_SAFE_ASSERT_INT2_RETURN(cond, v1, v2, ret) if (unlikely(!(cond))) { d_safe_assert_int2(#cond, __FILE__, __LINE__, static_cast<int>(v1), static_cast<int>(v2)); return ret; } -#define DISTRHO_SAFE_ASSERT_UINT_BREAK(cond, value) if (! (cond)) { d_safe_assert_uint(#cond, __FILE__, __LINE__, static_cast<uint>(value); break; } -#define DISTRHO_SAFE_ASSERT_UINT_CONTINUE(cond, value) if (! (cond)) { d_safe_assert_uint(#cond, __FILE__, __LINE__, static_cast<uint>(value)); continue; } -#define DISTRHO_SAFE_ASSERT_UINT_RETURN(cond, value, ret) if (! (cond)) { d_safe_assert_uint(#cond, __FILE__, __LINE__, static_cast<uint>(value)); return ret; } +#define DISTRHO_SAFE_ASSERT_UINT_BREAK(cond, value) if (unlikely(!(cond))) { d_safe_assert_uint(#cond, __FILE__, __LINE__, static_cast<uint>(value)); break; } +#define DISTRHO_SAFE_ASSERT_UINT_CONTINUE(cond, value) if (unlikely(!(cond))) { d_safe_assert_uint(#cond, __FILE__, __LINE__, static_cast<uint>(value)); continue; } +#define DISTRHO_SAFE_ASSERT_UINT_RETURN(cond, value, ret) if (unlikely(!(cond))) { d_safe_assert_uint(#cond, __FILE__, __LINE__, static_cast<uint>(value)); return ret; } -#define DISTRHO_SAFE_ASSERT_UINT2_BREAK(cond, v1, v2) if (! (cond)) { d_safe_assert_uint2(#cond, __FILE__, __LINE__, static_cast<uint>(v1), static_cast<uint>(v2)); break; } -#define DISTRHO_SAFE_ASSERT_UINT2_CONTINUE(cond, v1, v2) if (! (cond)) { d_safe_assert_uint2(#cond, __FILE__, __LINE__, static_cast<uint>(v1), static_cast<uint>(v2)); continue; } -#define DISTRHO_SAFE_ASSERT_UINT2_RETURN(cond, v1, v2, ret) if (! (cond)) { d_safe_assert_uint2(#cond, __FILE__, __LINE__, static_cast<uint>(v1), static_cast<uint>(v2)); return ret; } +#define DISTRHO_SAFE_ASSERT_UINT2_BREAK(cond, v1, v2) if (unlikely(!(cond))) { d_safe_assert_uint2(#cond, __FILE__, __LINE__, static_cast<uint>(v1), static_cast<uint>(v2)); break; } +#define DISTRHO_SAFE_ASSERT_UINT2_CONTINUE(cond, v1, v2) if (unlikely(!(cond))) { d_safe_assert_uint2(#cond, __FILE__, __LINE__, static_cast<uint>(v1), static_cast<uint>(v2)); continue; } +#define DISTRHO_SAFE_ASSERT_UINT2_RETURN(cond, v1, v2, ret) if (unlikely(!(cond))) { d_safe_assert_uint2(#cond, __FILE__, __LINE__, static_cast<uint>(v1), static_cast<uint>(v2)); return ret; } /* Define DISTRHO_SAFE_EXCEPTION */ #define DISTRHO_SAFE_EXCEPTION(msg) catch(...) { d_safe_exception(msg, __FILE__, __LINE__); } @@ -193,11 +204,21 @@ private: \ # define DISTRHO_OS_SPLIT_STR ":" #endif +/* MSVC warnings */ +#ifdef _MSC_VER +# define strdup _strdup +# pragma warning(disable:4244) /* possible loss of data */ +#endif + +/* Useful macros */ +#define ARRAY_SIZE(ARRAY) sizeof(ARRAY)/sizeof(ARRAY[0]) + /* Useful typedefs */ typedef unsigned char uchar; typedef unsigned short int ushort; typedef unsigned int uint; typedef unsigned long int ulong; +typedef unsigned long long int ulonglong; /* Deprecated macros */ #define DISTRHO_DECLARE_NON_COPY_CLASS(ClassName) DISTRHO_DECLARE_NON_COPYABLE(ClassName) diff --git a/distrho/src/DistrhoPlugin.cpp b/distrho/src/DistrhoPlugin.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -21,15 +21,17 @@ START_NAMESPACE_DISTRHO /* ------------------------------------------------------------------------------------------------------------ * Static data, see DistrhoPluginInternal.hpp */ -uint32_t d_lastBufferSize = 0; -double d_lastSampleRate = 0.0; -bool d_lastCanRequestParameterValueChanges = false; +uint32_t d_nextBufferSize = 0; +double d_nextSampleRate = 0.0; +const char* d_nextBundlePath = nullptr; +bool d_nextPluginIsDummy = false; +bool d_nextCanRequestParameterValueChanges = false; /* ------------------------------------------------------------------------------------------------------------ * Static fallback data, see DistrhoPluginInternal.hpp */ const String PluginExporter::sFallbackString; -const AudioPort PluginExporter::sFallbackAudioPort; +/* */ AudioPortWithBusId PluginExporter::sFallbackAudioPort; const ParameterRanges PluginExporter::sFallbackRanges; const ParameterEnumerationValues PluginExporter::sFallbackEnumValues; const PortGroupWithId PluginExporter::sFallbackPortGroup; @@ -41,7 +43,13 @@ Plugin::Plugin(uint32_t parameterCount, uint32_t programCount, uint32_t stateCou : pData(new PrivateData()) { #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 - pData->audioPorts = new AudioPort[DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS]; + pData->audioPorts = new AudioPortWithBusId[DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS]; +#endif + +#ifdef DPF_ABORT_ON_ERROR +# define DPF_ABORT abort(); +#else +# define DPF_ABORT #endif if (parameterCount > 0) @@ -50,26 +58,29 @@ Plugin::Plugin(uint32_t parameterCount, uint32_t programCount, uint32_t stateCou pData->parameters = new Parameter[parameterCount]; } -#if DISTRHO_PLUGIN_WANT_PROGRAMS if (programCount > 0) { +#if DISTRHO_PLUGIN_WANT_PROGRAMS pData->programCount = programCount; pData->programNames = new String[programCount]; - } #else - DISTRHO_SAFE_ASSERT(programCount == 0); + d_stderr2("DPF warning: Plugins with programs must define `DISTRHO_PLUGIN_WANT_PROGRAMS` to 1"); + DPF_ABORT #endif + } -#if DISTRHO_PLUGIN_WANT_STATE if (stateCount > 0) { - pData->stateCount = stateCount; - pData->stateKeys = new String[stateCount]; - pData->stateDefValues = new String[stateCount]; - } +#if DISTRHO_PLUGIN_WANT_STATE + pData->stateCount = stateCount; + pData->states = new State[stateCount]; #else - DISTRHO_SAFE_ASSERT(stateCount == 0); + d_stderr2("DPF warning: Plugins with state must define `DISTRHO_PLUGIN_WANT_STATE` to 1"); + DPF_ABORT #endif + } + +#undef DPF_ABORT } Plugin::~Plugin() @@ -90,6 +101,16 @@ double Plugin::getSampleRate() const noexcept return pData->sampleRate; } +const char* Plugin::getBundlePath() const noexcept +{ + return pData->bundlePath; +} + +bool Plugin::isDummyInstance() const noexcept +{ + return pData->isDummy; +} + #if DISTRHO_PLUGIN_WANT_TIMEPOS const TimePosition& Plugin::getTimePosition() const noexcept { @@ -123,6 +144,13 @@ bool Plugin::requestParameterValueChange(const uint32_t index, const float value } #endif +#if DISTRHO_PLUGIN_WANT_STATE +bool Plugin::updateStateValue(const char* const key, const char* const value) noexcept +{ + return pData->updateStateValueCallback(key, value); +} +#endif + /* ------------------------------------------------------------------------------------------------------------ * Init */ @@ -144,16 +172,69 @@ void Plugin::initAudioPort(bool input, uint32_t index, AudioPort& port) } } +void Plugin::initParameter(uint32_t, Parameter&) {} + void Plugin::initPortGroup(const uint32_t groupId, PortGroup& portGroup) { fillInPredefinedPortGroupData(groupId, portGroup); } +#if DISTRHO_PLUGIN_WANT_PROGRAMS +void Plugin::initProgramName(uint32_t, String&) {} +#endif + +#if DISTRHO_PLUGIN_WANT_STATE +void Plugin::initState(const uint32_t index, State& state) +{ + uint hints = 0x0; + String stateKey, defaultStateValue; + + #if defined(__clang__) + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" + #endif + initState(index, stateKey, defaultStateValue); + if (isStateFile(index)) + hints = kStateIsFilenamePath; + #if defined(__clang__) + #pragma clang diagnostic pop + #elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) + #pragma GCC diagnostic pop + #endif + + state.hints = hints; + state.key = stateKey; + state.label = stateKey; + state.defaultValue = defaultStateValue; +} +#endif + +/* ------------------------------------------------------------------------------------------------------------ + * Init */ + +float Plugin::getParameterValue(uint32_t) const { return 0.0f; } +void Plugin::setParameterValue(uint32_t, float) {} + +#if DISTRHO_PLUGIN_WANT_PROGRAMS +void Plugin::loadProgram(uint32_t) {} +#endif + +#if DISTRHO_PLUGIN_WANT_FULL_STATE +String Plugin::getState(const char*) const { return String(); } +#endif + +#if DISTRHO_PLUGIN_WANT_STATE +void Plugin::setState(const char*, const char*) {} +#endif + /* ------------------------------------------------------------------------------------------------------------ * Callbacks (optional) */ void Plugin::bufferSizeChanged(uint32_t) {} -void Plugin::sampleRateChanged(double) {} +void Plugin::sampleRateChanged(double) {} // ----------------------------------------------------------------------------------------------------------- diff --git a/distrho/src/DistrhoPluginCarla.cpp b/distrho/src/DistrhoPluginCarla.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -31,11 +31,13 @@ START_NAMESPACE_DISTRHO #if ! DISTRHO_PLUGIN_WANT_MIDI_OUTPUT -static const writeMidiFunc writeMidiCallback = nullptr; +static constexpr const writeMidiFunc writeMidiCallback = nullptr; #endif #if ! DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST -static const requestParameterValueChangeFunc requestParameterValueChangeCallback = nullptr; +static constexpr const requestParameterValueChangeFunc requestParameterValueChangeCallback = nullptr; #endif +// TODO +static constexpr const updateStateValueFunc updateStateValueCallback = nullptr; #if DISTRHO_PLUGIN_HAS_UI // ----------------------------------------------------------------------- @@ -53,7 +55,12 @@ class UICarla public: UICarla(const NativeHostDescriptor* const host, PluginExporter* const plugin) : fHost(host), - fUI(this, 0, editParameterCallback, setParameterCallback, setStateCallback, sendNoteCallback, setSizeCallback, plugin->getInstancePointer()) + fUI(this, 0, plugin->getSampleRate(), + editParameterCallback, setParameterCallback, setStateCallback, sendNoteCallback, + nullptr, // window size + nullptr, // TODO file request + nullptr, // bundle path + plugin->getInstancePointer()) { fUI.setWindowTitle(host->uiName); @@ -75,7 +82,7 @@ public: bool carla_idle() { - return fUI.idle(); + return fUI.plugin_idle(); } void carla_setParameterValue(const uint32_t index, const float value) @@ -129,11 +136,6 @@ protected: } #endif - void handleSetSize(const uint width, const uint height) - { - fUI.setWindowSize(width, height); - } - // --------------------------------------------- private: @@ -172,11 +174,6 @@ private: } #endif - static void setSizeCallback(void* ptr, uint width, uint height) - { - handlePtr->handleSetSize(width, height); - } - #undef handlePtr CARLA_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(UICarla) @@ -191,7 +188,7 @@ class PluginCarla : public NativePluginClass public: PluginCarla(const NativeHostDescriptor* const host) : NativePluginClass(host), - fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback), + fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback, updateStateValueCallback), fScalePointsCache(nullptr) { #if DISTRHO_PLUGIN_HAS_UI @@ -238,7 +235,7 @@ protected: int nativeParamHints = ::NATIVE_PARAMETER_IS_ENABLED; const uint32_t paramHints = fPlugin.getParameterHints(index); - if (paramHints & kParameterIsAutomable) + if (paramHints & kParameterIsAutomatable) nativeParamHints |= ::NATIVE_PARAMETER_IS_AUTOMABLE; if (paramHints & kParameterIsBoolean) nativeParamHints |= ::NATIVE_PARAMETER_IS_BOOLEAN; @@ -367,7 +364,8 @@ protected: } #if DISTRHO_PLUGIN_WANT_MIDI_INPUT - void process(float** const inBuffer, float** const outBuffer, const uint32_t frames, const NativeMidiEvent* const midiEvents, const uint32_t midiEventCount) override + void process(const float* const* const inBuffer, float** const outBuffer, const uint32_t frames, + const NativeMidiEvent* const midiEvents, const uint32_t midiEventCount) override { MidiEvent realMidiEvents[midiEventCount]; @@ -391,7 +389,8 @@ protected: fPlugin.run(const_cast<const float**>(inBuffer), outBuffer, frames, realMidiEvents, midiEventCount); } #else - void process(float** const inBuffer, float** const outBuffer, const uint32_t frames, const NativeMidiEvent* const, const uint32_t) override + void process(const float* const* const inBuffer, float** const outBuffer, const uint32_t frames, + const NativeMidiEvent* const, const uint32_t) override { fPlugin.run(const_cast<const float**>(inBuffer), outBuffer, frames); } @@ -498,10 +497,7 @@ private: void createUiIfNeeded() { if (fUiPtr == nullptr) - { - d_lastUiSampleRate = getSampleRate(); fUiPtr = new UICarla(getHostHandle(), &fPlugin); - } } #endif @@ -539,8 +535,8 @@ private: public: static NativePluginHandle _instantiate(const NativeHostDescriptor* host) { - d_lastBufferSize = host->get_buffer_size(host->handle); - d_lastSampleRate = host->get_sample_rate(host->handle); + d_nextBufferSize = host->get_buffer_size(host->handle); + d_nextSampleRate = host->get_sample_rate(host->handle); return new PluginCarla(host); } diff --git a/distrho/src/DistrhoPluginChecks.h b/distrho/src/DistrhoPluginChecks.h @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2019 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -81,18 +81,23 @@ # define DISTRHO_PLUGIN_WANT_STATE 0 #endif -#ifndef DISTRHO_PLUGIN_WANT_STATEFILES -# define DISTRHO_PLUGIN_WANT_STATEFILES 0 -#endif - #ifndef DISTRHO_PLUGIN_WANT_FULL_STATE # define DISTRHO_PLUGIN_WANT_FULL_STATE 0 +# define DISTRHO_PLUGIN_WANT_FULL_STATE_WAS_NOT_SET #endif #ifndef DISTRHO_PLUGIN_WANT_TIMEPOS # define DISTRHO_PLUGIN_WANT_TIMEPOS 0 #endif +#ifndef DISTRHO_UI_FILE_BROWSER +# if defined(DGL_FILE_BROWSER_DISABLED) || DISTRHO_PLUGIN_HAS_EXTERNAL_UI +# define DISTRHO_UI_FILE_BROWSER 0 +# else +# define DISTRHO_UI_FILE_BROWSER 1 +# endif +#endif + #ifndef DISTRHO_UI_USER_RESIZABLE # define DISTRHO_UI_USER_RESIZABLE 0 #endif @@ -136,25 +141,37 @@ #endif // ----------------------------------------------------------------------- -// Enable state if plugin wants state files - -#if DISTRHO_PLUGIN_WANT_STATEFILES && ! DISTRHO_PLUGIN_WANT_STATE -# undef DISTRHO_PLUGIN_WANT_STATE -# define DISTRHO_PLUGIN_WANT_STATE 1 +// Enable state if plugin wants state files (deprecated) + +#ifdef DISTRHO_PLUGIN_WANT_STATEFILES +# warning DISTRHO_PLUGIN_WANT_STATEFILES is deprecated +# undef DISTRHO_PLUGIN_WANT_STATEFILES +# if ! DISTRHO_PLUGIN_WANT_STATE +# undef DISTRHO_PLUGIN_WANT_STATE +# define DISTRHO_PLUGIN_WANT_STATE 1 +# endif #endif // ----------------------------------------------------------------------- // Enable full state if plugin exports presets -// FIXME -// #if DISTRHO_PLUGIN_WANT_PROGRAMS && DISTRHO_PLUGIN_WANT_STATE && ! DISTRHO_PLUGIN_WANT_FULL_STATE -// # warning Plugins with programs and state need to implement full state API -// # undef DISTRHO_PLUGIN_WANT_FULL_STATE -// # define DISTRHO_PLUGIN_WANT_FULL_STATE 1 -// #endif +#if DISTRHO_PLUGIN_WANT_PROGRAMS && DISTRHO_PLUGIN_WANT_STATE && defined(DISTRHO_PLUGIN_WANT_FULL_STATE_WAS_NOT_SET) +# warning Plugins with programs and state should implement full state API too +# undef DISTRHO_PLUGIN_WANT_FULL_STATE +# define DISTRHO_PLUGIN_WANT_FULL_STATE 1 +#endif // ----------------------------------------------------------------------- -// Disable UI if DGL or External UI is not available +// Disable file browser if using external UI + +#if DISTRHO_UI_FILE_BROWSER && DISTRHO_PLUGIN_HAS_EXTERNAL_UI +# warning file browser APIs do not work for external UIs +# undef DISTRHO_UI_FILE_BROWSER 0 +# define DISTRHO_UI_FILE_BROWSER 0 +#endif + +// ----------------------------------------------------------------------- +// Disable UI if DGL or external UI is not available #if (defined(DGL_CAIRO) && ! defined(HAVE_CAIRO)) || (defined(DGL_OPENGL) && ! defined(HAVE_OPENGL)) # undef DISTRHO_PLUGIN_HAS_EMBED_UI @@ -173,7 +190,6 @@ # error DISTRHO_UI_IS_STANDALONE must not be defined #endif - // ----------------------------------------------------------------------- #endif // DISTRHO_PLUGIN_CHECKS_H_INCLUDED diff --git a/distrho/src/DistrhoPluginInternal.hpp b/distrho/src/DistrhoPluginInternal.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -19,6 +19,10 @@ #include "../DistrhoPlugin.hpp" +#ifdef DISTRHO_PLUGIN_TARGET_VST3 +# include "DistrhoPluginVST.hpp" +#endif + #include <set> START_NAMESPACE_DISTRHO @@ -31,19 +35,30 @@ static const uint32_t kMaxMidiEvents = 512; // ----------------------------------------------------------------------- // Static data, see DistrhoPlugin.cpp -extern uint32_t d_lastBufferSize; -extern double d_lastSampleRate; -extern bool d_lastCanRequestParameterValueChanges; +extern uint32_t d_nextBufferSize; +extern double d_nextSampleRate; +extern const char* d_nextBundlePath; +extern bool d_nextPluginIsDummy; +extern bool d_nextCanRequestParameterValueChanges; // ----------------------------------------------------------------------- // DSP callbacks typedef bool (*writeMidiFunc) (void* ptr, const MidiEvent& midiEvent); typedef bool (*requestParameterValueChangeFunc) (void* ptr, uint32_t index, float value); +typedef bool (*updateStateValueFunc) (void* ptr, const char* key, const char* value); // ----------------------------------------------------------------------- // Helpers +struct AudioPortWithBusId : AudioPort { + uint32_t busId; + + AudioPortWithBusId() + : AudioPort(), + busId(0) {} +}; + struct PortGroupWithId : PortGroup { uint32_t groupId; @@ -75,10 +90,12 @@ static void fillInPredefinedPortGroupData(const uint32_t groupId, PortGroup& por // Plugin private data struct Plugin::PrivateData { + const bool canRequestParameterValueChanges; + const bool isDummy; bool isProcessing; #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 - AudioPort* audioPorts; + AudioPortWithBusId* audioPorts; #endif uint32_t parameterCount; @@ -95,8 +112,7 @@ struct Plugin::PrivateData { #if DISTRHO_PLUGIN_WANT_STATE uint32_t stateCount; - String* stateKeys; - String* stateDefValues; + State* states; #endif #if DISTRHO_PLUGIN_WANT_LATENCY @@ -111,13 +127,16 @@ struct Plugin::PrivateData { void* callbacksPtr; writeMidiFunc writeMidiCallbackFunc; requestParameterValueChangeFunc requestParameterValueChangeCallbackFunc; + updateStateValueFunc updateStateValueCallbackFunc; uint32_t bufferSize; double sampleRate; - bool canRequestParameterValueChanges; + char* bundlePath; PrivateData() noexcept - : isProcessing(false), + : canRequestParameterValueChanges(d_nextCanRequestParameterValueChanges), + isDummy(d_nextPluginIsDummy), + isProcessing(false), #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 audioPorts(nullptr), #endif @@ -132,8 +151,7 @@ struct Plugin::PrivateData { #endif #if DISTRHO_PLUGIN_WANT_STATE stateCount(0), - stateKeys(nullptr), - stateDefValues(nullptr), + states(nullptr), #endif #if DISTRHO_PLUGIN_WANT_LATENCY latency(0), @@ -141,9 +159,10 @@ struct Plugin::PrivateData { callbacksPtr(nullptr), writeMidiCallbackFunc(nullptr), requestParameterValueChangeCallbackFunc(nullptr), - bufferSize(d_lastBufferSize), - sampleRate(d_lastSampleRate), - canRequestParameterValueChanges(d_lastCanRequestParameterValueChanges) + updateStateValueCallbackFunc(nullptr), + bufferSize(d_nextBufferSize), + sampleRate(d_nextSampleRate), + bundlePath(d_nextBundlePath != nullptr ? strdup(d_nextBundlePath) : nullptr) { DISTRHO_SAFE_ASSERT(bufferSize != 0); DISTRHO_SAFE_ASSERT(d_isNotZero(sampleRate)); @@ -156,13 +175,17 @@ struct Plugin::PrivateData { #endif #ifdef DISTRHO_PLUGIN_TARGET_LV2 -# if (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || DISTRHO_PLUGIN_WANT_STATE) +# if (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_STATE || DISTRHO_PLUGIN_WANT_TIMEPOS) parameterOffset += 1; # endif # if (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE) parameterOffset += 1; # endif #endif + +#ifdef DISTRHO_PLUGIN_TARGET_VST3 + parameterOffset += kVst3InternalParameterCount; +#endif } ~PrivateData() noexcept @@ -196,18 +219,18 @@ struct Plugin::PrivateData { #endif #if DISTRHO_PLUGIN_WANT_STATE - if (stateKeys != nullptr) + if (states != nullptr) { - delete[] stateKeys; - stateKeys = nullptr; + delete[] states; + states = nullptr; } +#endif - if (stateDefValues != nullptr) + if (bundlePath != nullptr) { - delete[] stateDefValues; - stateDefValues = nullptr; + std::free(bundlePath); + bundlePath = nullptr; } -#endif } #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT @@ -229,6 +252,17 @@ struct Plugin::PrivateData { return false; } #endif + +#if DISTRHO_PLUGIN_WANT_STATE + bool updateStateValueCallback(const char* const key, const char* const value) + { + d_stdout("updateStateValueCallback %p", updateStateValueCallbackFunc); + if (updateStateValueCallbackFunc != nullptr) + return updateStateValueCallbackFunc(callbacksPtr, key, value); + + return false; + } +#endif }; // ----------------------------------------------------------------------- @@ -239,7 +273,8 @@ class PluginExporter public: PluginExporter(void* const callbacksPtr, const writeMidiFunc writeMidiCall, - const requestParameterValueChangeFunc requestParameterValueChangeCall) + const requestParameterValueChangeFunc requestParameterValueChangeCall, + const updateStateValueFunc updateStateValueCall) : fPlugin(createPlugin()), fData((fPlugin != nullptr) ? fPlugin->pData : nullptr), fIsActive(false) @@ -247,6 +282,79 @@ public: DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr,); DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr,); +#if defined(DPF_RUNTIME_TESTING) && defined(__GNUC__) && !defined(__clang__) + /* Run-time testing build. + * Verify that virtual functions are overriden if parameters, programs or states are in use. + * This does not work on all compilers, but we use it purely as informational check anyway. */ + if (fData->parameterCount != 0) + { + if ((void*)(fPlugin->*(&Plugin::initParameter)) == (void*)&Plugin::initParameter) + { + d_stderr2("DPF warning: Plugins with parameters must implement `initParameter`"); + abort(); + } + if ((void*)(fPlugin->*(&Plugin::getParameterValue)) == (void*)&Plugin::getParameterValue) + { + d_stderr2("DPF warning: Plugins with parameters must implement `getParameterValue`"); + abort(); + } + if ((void*)(fPlugin->*(&Plugin::setParameterValue)) == (void*)&Plugin::setParameterValue) + { + d_stderr2("DPF warning: Plugins with parameters must implement `setParameterValue`"); + abort(); + } + } + +# if DISTRHO_PLUGIN_WANT_PROGRAMS + if (fData->programCount != 0) + { + if ((void*)(fPlugin->*(&Plugin::initProgramName)) == (void*)&Plugin::initProgramName) + { + d_stderr2("DPF warning: Plugins with programs must implement `initProgramName`"); + abort(); + } + if ((void*)(fPlugin->*(&Plugin::loadProgram)) == (void*)&Plugin::loadProgram) + { + d_stderr2("DPF warning: Plugins with programs must implement `loadProgram`"); + abort(); + } + } +# endif + +# if DISTRHO_PLUGIN_WANT_STATE + if (fData->stateCount != 0) + { + if ((void*)(fPlugin->*(&Plugin::initState)) == (void*)&Plugin::initState) + { + d_stderr2("DPF warning: Plugins with state must implement `initState`"); + abort(); + } + + if ((void*)(fPlugin->*(&Plugin::setState)) == (void*)&Plugin::setState) + { + d_stderr2("DPF warning: Plugins with state must implement `setState`"); + abort(); + } + } +# endif + +# if DISTRHO_PLUGIN_WANT_FULL_STATE + if (fData->stateCount != 0) + { + if ((void*)(fPlugin->*(&Plugin::getState)) == (void*)&Plugin::getState) + { + d_stderr2("DPF warning: Plugins with full state must implement `getState`"); + abort(); + } + } + else + { + d_stderr2("DPF warning: Plugins with full state must have at least 1 state"); + abort(); + } +# endif +#endif + #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 { uint32_t j=0; @@ -276,7 +384,7 @@ public: portGroupIndices.erase(kPortGroupNone); - if (const size_t portGroupSize = portGroupIndices.size()) + if (const uint32_t portGroupSize = static_cast<uint32_t>(portGroupIndices.size())) { fData->portGroups = new PortGroupWithId[portGroupSize]; fData->portGroupCount = portGroupSize; @@ -302,12 +410,13 @@ public: #if DISTRHO_PLUGIN_WANT_STATE for (uint32_t i=0, count=fData->stateCount; i < count; ++i) - fPlugin->initState(i, fData->stateKeys[i], fData->stateDefValues[i]); + fPlugin->initState(i, fData->states[i]); #endif fData->callbacksPtr = callbacksPtr; fData->writeMidiCallbackFunc = writeMidiCall; fData->requestParameterValueChangeCallbackFunc = requestParameterValueChangeCall; + fData->updateStateValueCallbackFunc = updateStateValueCall; } ~PluginExporter() @@ -390,7 +499,7 @@ public: #endif #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 - const AudioPort& getAudioPort(const bool input, const uint32_t index) const noexcept + AudioPortWithBusId& getAudioPort(const bool input, const uint32_t index) const noexcept { DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, sFallbackAudioPort); @@ -409,6 +518,41 @@ public: return fData->audioPorts[index + (input ? 0 : DISTRHO_PLUGIN_NUM_INPUTS)]; } + + uint32_t getAudioPortHints(const bool input, const uint32_t index) const noexcept + { + return getAudioPort(input, index).hints; + } + + uint32_t getAudioPortCountWithGroupId(const bool input, const uint32_t groupId) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, 0); + + uint32_t numPorts = 0; + + if (input) + { + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + for (uint32_t i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i) + { + if (fData->audioPorts[i].groupId == groupId) + ++numPorts; + } + #endif + } + else + { + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + for (uint32_t i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) + { + if (fData->audioPorts[i + DISTRHO_PLUGIN_NUM_INPUTS].groupId == groupId) + ++numPorts; + } + #endif + } + + return numPorts; + } #endif uint32_t getParameterCount() const noexcept @@ -449,6 +593,11 @@ public: return (getParameterHints(index) & kParameterIsOutput) != 0x0; } + bool isParameterTrigger(const uint32_t index) const noexcept + { + return (getParameterHints(index) & kParameterIsTrigger) == kParameterIsTrigger; + } + bool isParameterOutputOrTrigger(const uint32_t index) const noexcept { const uint32_t hints = getParameterHints(index); @@ -524,6 +673,14 @@ public: return fData->parameters[index].groupId; } + float getParameterDefault(const uint32_t index) const + { + DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr, 0.0f); + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->parameterCount, 0.0f); + + return fData->parameters[index].ranges.def; + } + float getParameterValue(const uint32_t index) const { DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr, 0.0f); @@ -608,31 +765,43 @@ public: return fData->stateCount; } + uint32_t getStateHints(const uint32_t index) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, 0x0); + + return fData->states[index].hints; + } + const String& getStateKey(const uint32_t index) const noexcept { DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, sFallbackString); - return fData->stateKeys[index]; + return fData->states[index].key; } const String& getStateDefaultValue(const uint32_t index) const noexcept { DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, sFallbackString); - return fData->stateDefValues[index]; + return fData->states[index].defaultValue; } -# if DISTRHO_PLUGIN_WANT_STATEFILES - bool isStateFile(const uint32_t index) const + const String& getStateLabel(const uint32_t index) const noexcept { - DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, false); + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, sFallbackString); - return fPlugin->isStateFile(index); + return fData->states[index].label; + } + + const String& getStateDescription(const uint32_t index) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->stateCount, sFallbackString); + + return fData->states[index].description; } -# endif # if DISTRHO_PLUGIN_WANT_FULL_STATE - String getState(const char* key) const + String getStateValue(const char* const key) const { DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, sFallbackString); DISTRHO_SAFE_ASSERT_RETURN(key != nullptr && key[0] != '\0', sFallbackString); @@ -657,7 +826,7 @@ public: for (uint32_t i=0; i < fData->stateCount; ++i) { - if (fData->stateKeys[i] == key) + if (fData->states[i].key == key) return true; } @@ -809,15 +978,12 @@ private: // Static fallback data, see DistrhoPlugin.cpp static const String sFallbackString; - static const AudioPort sFallbackAudioPort; + static /* */ AudioPortWithBusId sFallbackAudioPort; static const ParameterRanges sFallbackRanges; static const ParameterEnumerationValues sFallbackEnumValues; static const PortGroupWithId sFallbackPortGroup; DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginExporter) -#ifndef DISTRHO_PLUGIN_TARGET_VST3 /* there is no way around this for VST3 */ - DISTRHO_PREVENT_HEAP_ALLOCATION -#endif }; // ----------------------------------------------------------------------- diff --git a/distrho/src/DistrhoPluginJACK.cpp b/distrho/src/DistrhoPluginJACK.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,6 +16,10 @@ #include "DistrhoPluginInternal.hpp" +#if !defined(DISTRHO_OS_WINDOWS) && !defined(STATIC_BUILD) +# include "../DistrhoPluginUtils.hpp" +#endif + #if DISTRHO_PLUGIN_HAS_UI # include "DistrhoUIInternal.hpp" # include "../extra/RingBuffer.hpp" @@ -23,11 +27,20 @@ # include "../extra/Sleep.hpp" #endif +#ifdef DPF_RUNTIME_TESTING +# include "../extra/Thread.hpp" +#endif + +#if defined(HAVE_JACK) && defined(STATIC_BUILD) && !defined(DISTRHO_OS_WASM) +# define JACKBRIDGE_DIRECT +#endif + #include "jackbridge/JackBridge.cpp" #include "lv2/lv2.h" #ifndef DISTRHO_OS_WINDOWS # include <signal.h> +# include <unistd.h> #endif #ifndef JACK_METADATA_ORDER @@ -110,12 +123,12 @@ class PluginJack #endif { public: - PluginJack(jack_client_t* const client) - : fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback), + PluginJack(jack_client_t* const client, const uintptr_t winId) + : fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback, nullptr), #if DISTRHO_PLUGIN_HAS_UI fUI(this, - 0, // winId - d_lastSampleRate, + winId, + d_nextSampleRate, nullptr, // edit param setParameterValueCallback, setStateCallback, @@ -218,6 +231,9 @@ public: #else while (! gCloseSignalReceived) d_sleep(1); + + // unused + (void)winId; #endif } @@ -422,7 +438,7 @@ protected: for (uint32_t i=0; i < eventCount; ++i) { - if (jackbridge_midi_event_get(&jevent, midiInBuf, i) != 0) + if (! jackbridge_midi_event_get(&jevent, midiInBuf, i)) break; // Check if message is control change on channel 1 @@ -469,7 +485,7 @@ protected: MidiEvent& midiEvent(midiEvents[midiEventCount++]); midiEvent.frame = jevent.time; - midiEvent.size = jevent.size; + midiEvent.size = static_cast<uint32_t>(jevent.size); if (midiEvent.size > MidiEvent::kDataSize) midiEvent.dataExt = jevent.buffer; @@ -735,7 +751,7 @@ private: return jackbridge_midi_event_write(fPortMidiOutBuffer, midiEvent.frame, midiEvent.size > MidiEvent::kDataSize ? midiEvent.dataExt : midiEvent.data, - midiEvent.size) == 0; + midiEvent.size); } static bool writeMidiCallback(void* ptr, const MidiEvent& midiEvent) @@ -747,14 +763,183 @@ private: #undef thisPtr }; +// ----------------------------------------------------------------------- + +#ifdef DPF_RUNTIME_TESTING +class PluginProcessTestingThread : public Thread +{ + PluginExporter& plugin; + +public: + PluginProcessTestingThread(PluginExporter& p) : plugin(p) {} + +protected: + void run() override + { + plugin.setBufferSize(256); + plugin.activate(); + + float buffer[256]; + const float* inputs[DISTRHO_PLUGIN_NUM_INPUTS > 0 ? DISTRHO_PLUGIN_NUM_INPUTS : 1]; + float* outputs[DISTRHO_PLUGIN_NUM_OUTPUTS > 0 ? DISTRHO_PLUGIN_NUM_OUTPUTS : 1]; + for (int i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i) + inputs[i] = buffer; + for (int i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) + outputs[i] = buffer; + + while (! shouldThreadExit()) + { +#if DISTRHO_PLUGIN_WANT_MIDI_INPUT + plugin.run(inputs, outputs, 128, nullptr, 0); +#else + plugin.run(inputs, outputs, 128); +#endif + d_msleep(100); + } + + plugin.deactivate(); + } +}; + +bool runSelfTests() +{ + // simple plugin creation first + { + d_nextBufferSize = 512; + d_nextSampleRate = 44100.0; + PluginExporter plugin(nullptr, nullptr, nullptr); + d_nextBufferSize = 0; + d_nextSampleRate = 0.0; + } + + // keep values for all tests now + d_nextBufferSize = 512; + d_nextSampleRate = 44100.0; + + // simple processing + { + PluginExporter plugin(nullptr, nullptr, nullptr); + plugin.activate(); + plugin.deactivate(); + plugin.setBufferSize(128); + plugin.setSampleRate(48000); + plugin.activate(); + + float buffer[128]; + const float* inputs[DISTRHO_PLUGIN_NUM_INPUTS > 0 ? DISTRHO_PLUGIN_NUM_INPUTS : 1]; + float* outputs[DISTRHO_PLUGIN_NUM_OUTPUTS > 0 ? DISTRHO_PLUGIN_NUM_OUTPUTS : 1]; + for (int i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i) + inputs[i] = buffer; + for (int i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) + outputs[i] = buffer; + +#if DISTRHO_PLUGIN_WANT_MIDI_INPUT + plugin.run(inputs, outputs, 128, nullptr, 0); +#else + plugin.run(inputs, outputs, 128); +#endif + + plugin.deactivate(); + } + + // multi-threaded processing with UI + { + PluginExporter pluginA(nullptr, nullptr, nullptr); + PluginExporter pluginB(nullptr, nullptr, nullptr); + PluginExporter pluginC(nullptr, nullptr, nullptr); + PluginProcessTestingThread procTestA(pluginA); + PluginProcessTestingThread procTestB(pluginB); + PluginProcessTestingThread procTestC(pluginC); + procTestA.startThread(); + procTestB.startThread(); + procTestC.startThread(); + + // wait 2s + d_sleep(2); + + // stop the 2nd instance now + procTestB.stopThread(5000); + +#if DISTRHO_PLUGIN_HAS_UI + // start UI in the middle of this + { + UIExporter uiA(nullptr, 0, pluginA.getSampleRate(), + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + pluginA.getInstancePointer(), 0.0); + UIExporter uiB(nullptr, 0, pluginA.getSampleRate(), + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + pluginB.getInstancePointer(), 0.0); + UIExporter uiC(nullptr, 0, pluginA.getSampleRate(), + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + pluginC.getInstancePointer(), 0.0); + + // show UIs + uiB.showAndFocus(); + uiA.showAndFocus(); + uiC.showAndFocus(); + + // idle for 3s + for (int i=0; i<30; i++) + { + uiC.plugin_idle(); + uiB.plugin_idle(); + uiA.plugin_idle(); + d_msleep(100); + } + } +#endif + + procTestA.stopThread(5000); + procTestC.stopThread(5000); + } + + return true; +} +#endif // DPF_RUNTIME_TESTING + END_NAMESPACE_DISTRHO // ----------------------------------------------------------------------- -int main() +int main(int argc, char* argv[]) { USE_NAMESPACE_DISTRHO; +#ifdef DPF_RUNTIME_TESTING + if (argc == 2 && std::strcmp(argv[1], "selftest") == 0) + return runSelfTests() ? 0 : 1; +#endif + +#if defined(DISTRHO_OS_WINDOWS) && DISTRHO_PLUGIN_HAS_UI + /* the code below is based on + * https://www.tillett.info/2013/05/13/how-to-create-a-windows-program-that-works-as-both-as-a-gui-and-console-application/ + */ + bool hasConsole = false; + + HANDLE consoleHandleOut, consoleHandleError; + + if (AttachConsole(ATTACH_PARENT_PROCESS)) + { + // Redirect unbuffered STDOUT to the console + consoleHandleOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (consoleHandleOut != INVALID_HANDLE_VALUE) + { + freopen("CONOUT$", "w", stdout); + setvbuf(stdout, NULL, _IONBF, 0); + } + + // Redirect unbuffered STDERR to the console + consoleHandleError = GetStdHandle(STD_ERROR_HANDLE); + if (consoleHandleError != INVALID_HANDLE_VALUE) + { + freopen("CONOUT$", "w", stderr); + setvbuf(stderr, NULL, _IONBF, 0); + } + + hasConsole = true; + } +#endif + jack_status_t status = jack_status_t(0x0); jack_client_t* client = jackbridge_client_open(DISTRHO_PLUGIN_NAME, JackNoStartServer, &status); @@ -792,25 +977,112 @@ int main() if (errorString.isNotEmpty()) { errorString[errorString.length()-2] = '.'; - d_stderr("Failed to create jack client, reason was:\n%s", errorString.buffer()); + d_stderr("Failed to create the JACK client, reason was:\n%s", errorString.buffer()); } else - d_stderr("Failed to create jack client, cannot continue!"); + d_stderr("Failed to create the JACK client, cannot continue!"); + + #if defined(DISTRHO_OS_WINDOWS) && DISTRHO_PLUGIN_HAS_UI + // make sure message box is high-dpi aware + if (const HMODULE user32 = LoadLibrary("user32.dll")) + { + typedef BOOL(WINAPI* SPDA)(void); + #if defined(__GNUC__) && (__GNUC__ >= 9) + # pragma GCC diagnostic push + # pragma GCC diagnostic ignored "-Wcast-function-type" + #endif + const SPDA SetProcessDPIAware = (SPDA)GetProcAddress(user32, "SetProcessDPIAware"); + #if defined(__GNUC__) && (__GNUC__ >= 9) + # pragma GCC diagnostic pop + #endif + if (SetProcessDPIAware) + SetProcessDPIAware(); + FreeLibrary(user32); + } + + const String win32error = "Failed to create JACK client, reason was:\n" + errorString; + MessageBoxA(nullptr, win32error.buffer(), "", MB_ICONERROR); + #endif return 1; } - USE_NAMESPACE_DISTRHO; - initSignalHandler(); - d_lastBufferSize = jackbridge_get_buffer_size(client); - d_lastSampleRate = jackbridge_get_sample_rate(client); - d_lastCanRequestParameterValueChanges = true; + d_nextBufferSize = jackbridge_get_buffer_size(client); + d_nextSampleRate = jackbridge_get_sample_rate(client); + d_nextCanRequestParameterValueChanges = true; + + #if !defined(DISTRHO_OS_WINDOWS) && !defined(STATIC_BUILD) + // find plugin bundle + static String bundlePath; + if (bundlePath.isEmpty()) + { + String tmpPath(getBinaryFilename()); + tmpPath.truncate(tmpPath.rfind(DISTRHO_OS_SEP)); + #ifdef DISTRHO_OS_MAC + if (tmpPath.endsWith("/MacOS")) + { + tmpPath.truncate(tmpPath.rfind('/')); + if (tmpPath.endsWith("/Contents")) + { + tmpPath.truncate(tmpPath.rfind('/')); + bundlePath = tmpPath; + d_nextBundlePath = bundlePath.buffer(); + } + } + #else + if (access(tmpPath + DISTRHO_OS_SEP_STR "resources", F_OK) == 0) + { + bundlePath = tmpPath; + d_nextBundlePath = bundlePath.buffer(); + } + #endif + } + #endif + + uintptr_t winId = 0; +#if DISTRHO_PLUGIN_HAS_UI + if (argc == 3 && std::strcmp(argv[1], "embed") == 0) + winId = static_cast<uintptr_t>(std::atoll(argv[2])); +#endif + + const PluginJack p(client, winId); - const PluginJack p(client); +#if defined(DISTRHO_OS_WINDOWS) && DISTRHO_PLUGIN_HAS_UI + /* the code below is based on + * https://www.tillett.info/2013/05/13/how-to-create-a-windows-program-that-works-as-both-as-a-gui-and-console-application/ + */ + + // Send "enter" to release application from the console + // This is a hack, but if not used the console doesn't know the application has + // returned. The "enter" key only sent if the console window is in focus. + if (hasConsole && (GetConsoleWindow() == GetForegroundWindow() || SetFocus(GetConsoleWindow()) != nullptr)) + { + INPUT ip; + // Set up a generic keyboard event. + ip.type = INPUT_KEYBOARD; + ip.ki.wScan = 0; // hardware scan code for key + ip.ki.time = 0; + ip.ki.dwExtraInfo = 0; + + // Send the "Enter" key + ip.ki.wVk = 0x0D; // virtual-key code for the "Enter" key + ip.ki.dwFlags = 0; // 0 for key press + SendInput(1, &ip, sizeof(INPUT)); + + // Release the "Enter" key + ip.ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release + SendInput(1, &ip, sizeof(INPUT)); + } +#endif return 0; + +#ifndef DPF_RUNTIME_TESTING + // unused + (void)argc; (void)argv; +#endif } // ----------------------------------------------------------------------- diff --git a/distrho/src/DistrhoPluginLADSPA+DSSI.cpp b/distrho/src/DistrhoPluginLADSPA+DSSI.cpp @@ -50,7 +50,7 @@ class PluginLadspaDssi { public: PluginLadspaDssi() - : fPlugin(nullptr, nullptr, nullptr), + : fPlugin(nullptr, nullptr, nullptr, nullptr), fPortControls(nullptr), fLastControlValues(nullptr) { @@ -418,9 +418,9 @@ private: static LADSPA_Handle ladspa_instantiate(const LADSPA_Descriptor*, ulong sampleRate) { - if (d_lastBufferSize == 0) - d_lastBufferSize = 2048; - d_lastSampleRate = sampleRate; + if (d_nextBufferSize == 0) + d_nextBufferSize = 2048; + d_nextSampleRate = sampleRate; return new PluginLadspaDssi(); } @@ -551,11 +551,13 @@ static const struct DescriptorInitializer DescriptorInitializer() { // Create dummy plugin to get data from - d_lastBufferSize = 512; - d_lastSampleRate = 44100.0; - const PluginExporter plugin(nullptr, nullptr, nullptr); - d_lastBufferSize = 0; - d_lastSampleRate = 0.0; + d_nextBufferSize = 512; + d_nextSampleRate = 44100.0; + d_nextPluginIsDummy = true; + const PluginExporter plugin(nullptr, nullptr, nullptr, nullptr); + d_nextBufferSize = 0; + d_nextSampleRate = 0.0; + d_nextPluginIsDummy = false; // Get port count, init ulong port = 0; diff --git a/distrho/src/DistrhoPluginLV2.cpp b/distrho/src/DistrhoPluginLV2.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -17,6 +17,7 @@ #include "DistrhoPluginInternal.hpp" #include "lv2/atom.h" +#include "lv2/atom-forge.h" #include "lv2/atom-util.h" #include "lv2/buf-size.h" #include "lv2/data-access.h" @@ -37,10 +38,6 @@ # include "libmodla.h" #endif -#ifdef noexcept -# undef noexcept -#endif - #include <map> #ifndef DISTRHO_PLUGIN_URI @@ -51,8 +48,8 @@ # define DISTRHO_PLUGIN_LV2_STATE_PREFIX "urn:distrho:" #endif -#define DISTRHO_LV2_USE_EVENTS_IN (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI) || DISTRHO_PLUGIN_WANT_STATEFILES) -#define DISTRHO_LV2_USE_EVENTS_OUT (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI)) +#define DISTRHO_LV2_USE_EVENTS_IN (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || DISTRHO_PLUGIN_WANT_STATE) +#define DISTRHO_LV2_USE_EVENTS_OUT (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE) START_NAMESPACE_DISTRHO @@ -65,6 +62,9 @@ static const writeMidiFunc writeMidiCallback = nullptr; #if ! DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST static const requestParameterValueChangeFunc requestParameterValueChangeCallback = nullptr; #endif +#if ! DISTRHO_PLUGIN_WANT_STATE +static const updateStateValueFunc updateStateValueCallback = nullptr; +#endif // ----------------------------------------------------------------------- @@ -76,7 +76,7 @@ public: const LV2_Worker_Schedule* const worker, const LV2_ControlInputPort_Change_Request* const ctrlInPortChangeReq, const bool usingNominal) - : fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback), + : fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback, updateStateValueCallback), fUsingNominal(usingNominal), #ifdef DISTRHO_PLUGIN_LICENSED_FOR_MOD fRunCount(0), @@ -130,29 +130,29 @@ public: #endif #if DISTRHO_PLUGIN_WANT_STATE + std::memset(&fAtomForge, 0, sizeof(fAtomForge)); + lv2_atom_forge_init(&fAtomForge, uridMap); + if (const uint32_t count = fPlugin.getStateCount()) { + fUrids = new LV2_URID[count]; fNeededUiSends = new bool[count]; for (uint32_t i=0; i < count; ++i) { fNeededUiSends[i] = false; - const String& dkey(fPlugin.getStateKey(i)); - fStateMap[dkey] = fPlugin.getStateDefaultValue(i); + const String& statekey(fPlugin.getStateKey(i)); + fStateMap[statekey] = fPlugin.getStateDefaultValue(i); -# if DISTRHO_PLUGIN_WANT_STATEFILES - if (fPlugin.isStateFile(i)) - { - const String dpf_lv2_key(DISTRHO_PLUGIN_URI "#" + dkey); - const LV2_URID urid = uridMap->map(uridMap->handle, dpf_lv2_key.buffer()); - fUridStateFileMap[urid] = dkey; - } -# endif + const String lv2key(DISTRHO_PLUGIN_URI "#" + statekey); + const LV2_URID urid = fUrids[i] = uridMap->map(uridMap->handle, lv2key.buffer()); + fUridStateMap[urid] = statekey; } } else { + fUrids = nullptr; fNeededUiSends = nullptr; } #else @@ -187,6 +187,12 @@ public: fNeededUiSends = nullptr; } + if (fUrids != nullptr) + { + delete[] fUrids; + fUrids = nullptr; + } + fStateMap.clear(); #endif } @@ -548,13 +554,14 @@ public: } #endif - // check for messages from UI or files -#if DISTRHO_PLUGIN_WANT_STATE && (DISTRHO_PLUGIN_HAS_UI || DISTRHO_PLUGIN_WANT_STATEFILES) + // check for messages from UI or host +#if DISTRHO_PLUGIN_WANT_STATE LV2_ATOM_SEQUENCE_FOREACH(fPortEventsIn, event) { if (event == nullptr) break; + #if DISTRHO_PLUGIN_HAS_UI if (event->body.type == fURIDs.dpfKeyValue) { const void* const data = (const void*)(event + 1); @@ -563,7 +570,11 @@ public: if (std::strcmp((const char*)data, "__dpf_ui_data__") == 0) { for (uint32_t i=0, count=fPlugin.getStateCount(); i < count; ++i) + { + if (fPlugin.getStateHints(i) & kStateIsOnlyForDSP) + continue; fNeededUiSends[i] = true; + } } // no, send to DSP as usual else if (fWorker != nullptr) @@ -571,8 +582,9 @@ public: fWorker->schedule_work(fWorker->handle, sizeof(LV2_Atom)+event->body.size, &event->body); } } -# if DISTRHO_PLUGIN_WANT_STATEFILES - else if (event->body.type == fURIDs.atomObject && fWorker != nullptr) + else + #endif + if (event->body.type == fURIDs.atomObject && fWorker != nullptr) { const LV2_Atom_Object* const object = (const LV2_Atom_Object*)&event->body; @@ -581,12 +593,11 @@ public: lv2_atom_object_get(object, fURIDs.patchProperty, &property, fURIDs.patchValue, &value, nullptr); if (property != nullptr && property->type == fURIDs.atomURID && - value != nullptr && value->type == fURIDs.atomPath) + value != nullptr && (value->type == fURIDs.atomPath || value->type == fURIDs.atomString)) { fWorker->schedule_work(fWorker->handle, sizeof(LV2_Atom)+event->body.size, &event->body); } } -# endif } #endif @@ -685,7 +696,7 @@ public: updateParameterOutputsAndTriggers(); -#if DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI +#if DISTRHO_PLUGIN_WANT_STATE fEventsOutData.initIfNeeded(fURIDs.atomSequence); LV2_Atom_Event* aev; @@ -696,6 +707,16 @@ public: if (! fNeededUiSends[i]) continue; + const uint32_t hints = fPlugin.getStateHints(i); + + #if ! DISTRHO_PLUGIN_HAS_UI + if ((hints & kStateIsHostReadable) == 0x0) + { + fNeededUiSends[i] = false; + continue; + } + #endif + const String& curKey(fPlugin.getStateKey(i)); for (StringToStringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) @@ -707,30 +728,71 @@ public: const String& value(cit->second); - // set msg size (key + value + separator + 2x null terminator) - const size_t msgSize = key.length()+value.length()+3; + // set msg size + uint32_t msgSize; + + if (hints & kStateIsHostReadable) + { + // object, prop key, prop urid, value key, value + msgSize = sizeof(LV2_Atom_Object) + + sizeof(LV2_Atom_Property_Body) * 4 + + sizeof(LV2_Atom_URID) * 3 + + sizeof(LV2_Atom_String) + + value.length() + 1; + } + else + { + // key + value + 2x null terminator + separator + msgSize = static_cast<uint32_t>(key.length()+value.length())+3U; + } if (sizeof(LV2_Atom_Event) + msgSize > capacity - fEventsOutData.offset) { - d_stdout("Sending key '%s' to UI failed, out of space", key.buffer()); + d_stdout("Sending key '%s' to UI failed, out of space (needs %u bytes)", + key.buffer(), msgSize); break; } // put data aev = (LV2_Atom_Event*)(LV2_ATOM_CONTENTS(LV2_Atom_Sequence, fEventsOutData.port) + fEventsOutData.offset); aev->time.frames = 0; - aev->body.type = fURIDs.dpfKeyValue; - aev->body.size = msgSize; - uint8_t* const msgBuf = LV2_ATOM_BODY(&aev->body); - std::memset(msgBuf, 0, msgSize); + if (hints & kStateIsHostReadable) + { + uint8_t* const msgBuf = (uint8_t*)&aev->body; + LV2_Atom_Forge atomForge = fAtomForge; + lv2_atom_forge_set_buffer(&atomForge, msgBuf, msgSize); - // write key and value in atom buffer - std::memcpy(msgBuf, key.buffer(), key.length()+1); - std::memcpy(msgBuf+(key.length()+1), value.buffer(), value.length()+1); + LV2_Atom_Forge_Frame forgeFrame; + lv2_atom_forge_object(&atomForge, &forgeFrame, 0, fURIDs.patchSet); - fEventsOutData.growBy(lv2_atom_pad_size(sizeof(LV2_Atom_Event) + msgSize)); + lv2_atom_forge_key(&atomForge, fURIDs.patchProperty); + lv2_atom_forge_urid(&atomForge, fUrids[i]); + + lv2_atom_forge_key(&atomForge, fURIDs.patchValue); + if ((hints & kStateIsFilenamePath) == kStateIsFilenamePath) + lv2_atom_forge_path(&atomForge, value.buffer(), static_cast<uint32_t>(value.length()+1)); + else + lv2_atom_forge_string(&atomForge, value.buffer(), static_cast<uint32_t>(value.length()+1)); + + lv2_atom_forge_pop(&atomForge, &forgeFrame); + + msgSize = ((LV2_Atom*)msgBuf)->size; + } + else + { + aev->body.type = fURIDs.dpfKeyValue; + aev->body.size = msgSize; + + uint8_t* const msgBuf = LV2_ATOM_BODY(&aev->body); + std::memset(msgBuf, 0, msgSize); + // write key and value in atom buffer + std::memcpy(msgBuf, key.buffer(), key.length()+1); + std::memcpy(msgBuf+(key.length()+1), value.buffer(), value.length()+1); + } + + fEventsOutData.growBy(lv2_atom_pad_size(sizeof(LV2_Atom_Event) + msgSize)); fNeededUiSends[i] = false; break; } @@ -838,7 +900,7 @@ public: for (StringToStringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) { const String& key = cit->first; - fStateMap[key] = fPlugin.getState(key); + fStateMap[key] = fPlugin.getStateValue(key); } # endif } @@ -854,11 +916,11 @@ public: for (StringToStringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) { const String& key = cit->first; - fStateMap[key] = fPlugin.getState(key); + fStateMap[key] = fPlugin.getStateValue(key); } # endif - String dpf_lv2_key; + String lv2key; LV2_URID urid; for (uint32_t i=0, count=fPlugin.getStateCount(); i < count; ++i) @@ -872,30 +934,40 @@ public: if (curKey != key) continue; - const String& value(cit->second); + const uint32_t hints = fPlugin.getStateHints(i); + + #if ! DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS + // do not save UI-only messages if there is no UI available + if (hints & kStateIsOnlyForUI) + break; + #endif -# if DISTRHO_PLUGIN_WANT_STATEFILES - if (fPlugin.isStateFile(i)) + if (hints & kStateIsHostReadable) { - dpf_lv2_key = DISTRHO_PLUGIN_URI "#"; - urid = fURIDs.atomPath; + lv2key = DISTRHO_PLUGIN_URI "#"; + urid = (hints & kStateIsFilenamePath) == kStateIsFilenamePath + ? fURIDs.atomPath + : fURIDs.atomString; } else -# endif { - dpf_lv2_key = DISTRHO_PLUGIN_LV2_STATE_PREFIX; + lv2key = DISTRHO_PLUGIN_LV2_STATE_PREFIX; urid = fURIDs.atomString; } - dpf_lv2_key += key; + lv2key += key; + + const String& value(cit->second); // some hosts need +1 for the null terminator, even though the type is string store(handle, - fUridMap->map(fUridMap->handle, dpf_lv2_key.buffer()), + fUridMap->map(fUridMap->handle, lv2key.buffer()), value.buffer(), value.length()+1, urid, LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE); + + break; } } @@ -907,33 +979,35 @@ public: size_t size; uint32_t type, flags; - String dpf_lv2_key; + String lv2key; LV2_URID urid; for (uint32_t i=0, count=fPlugin.getStateCount(); i < count; ++i) { const String& key(fPlugin.getStateKey(i)); -# if DISTRHO_PLUGIN_WANT_STATEFILES - if (fPlugin.isStateFile(i)) + const uint32_t hints = fPlugin.getStateHints(i); + + if (hints & kStateIsHostReadable) { - dpf_lv2_key = DISTRHO_PLUGIN_URI "#"; - urid = fURIDs.atomPath; + lv2key = DISTRHO_PLUGIN_URI "#"; + urid = (hints & kStateIsFilenamePath) == kStateIsFilenamePath + ? fURIDs.atomPath + : fURIDs.atomString; } else -# endif { - dpf_lv2_key = DISTRHO_PLUGIN_LV2_STATE_PREFIX; + lv2key = DISTRHO_PLUGIN_LV2_STATE_PREFIX; urid = fURIDs.atomString; } - dpf_lv2_key += key; + lv2key += key; size = 0; type = 0; flags = LV2_STATE_IS_POD|LV2_STATE_IS_PORTABLE; const void* data = retrieve(handle, - fUridMap->map(fUridMap->handle, dpf_lv2_key.buffer()), + fUridMap->map(fUridMap->handle, lv2key.buffer()), &size, &type, &flags); if (data == nullptr || size == 0) @@ -947,9 +1021,10 @@ public: setState(key, value); -#if DISTRHO_LV2_USE_EVENTS_OUT +#if DISTRHO_PLUGIN_WANT_STATE // signal msg needed for UI - fNeededUiSends[i] = true; + if ((hints & kStateIsOnlyForDSP) == 0x0) + fNeededUiSends[i] = true; #endif } @@ -971,7 +1046,6 @@ public: return LV2_WORKER_SUCCESS; } -# if DISTRHO_PLUGIN_WANT_STATEFILES if (eventBody->type == fURIDs.atomObject) { const LV2_Atom_Object* const object = (const LV2_Atom_Object*)eventBody; @@ -982,7 +1056,8 @@ public: DISTRHO_SAFE_ASSERT_RETURN(property != nullptr, LV2_WORKER_ERR_UNKNOWN); DISTRHO_SAFE_ASSERT_RETURN(property->type == fURIDs.atomURID, LV2_WORKER_ERR_UNKNOWN); DISTRHO_SAFE_ASSERT_RETURN(value != nullptr, LV2_WORKER_ERR_UNKNOWN); - DISTRHO_SAFE_ASSERT_RETURN(value->type == fURIDs.atomPath, LV2_WORKER_ERR_UNKNOWN); + DISTRHO_SAFE_ASSERT_RETURN(value->type == fURIDs.atomPath || + value->type == fURIDs.atomString, LV2_WORKER_ERR_UNKNOWN); const LV2_URID urid = ((const LV2_Atom_URID*)property)->body; const char* const filename = (const char*)(value + 1); @@ -990,8 +1065,8 @@ public: String key; try { - key = fUridStateFileMap[urid]; - } DISTRHO_SAFE_EXCEPTION_RETURN("lv2_work fUridStateFileMap[urid]", LV2_WORKER_ERR_UNKNOWN); + key = fUridStateMap[urid]; + } DISTRHO_SAFE_EXCEPTION_RETURN("lv2_work fUridStateMap[urid]", LV2_WORKER_ERR_UNKNOWN); setState(key, filename); @@ -999,14 +1074,14 @@ public: { if (fPlugin.getStateKey(i) == key) { - fNeededUiSends[i] = true; + if ((fPlugin.getStateHints(i) & kStateIsOnlyForDSP) == 0x0) + fNeededUiSends[i] = true; break; } } return LV2_WORKER_SUCCESS; } -# endif return LV2_WORKER_ERR_UNKNOWN; } @@ -1140,6 +1215,7 @@ private: LV2_URID atomURID; LV2_URID dpfKeyValue; LV2_URID midiEvent; + LV2_URID patchSet; LV2_URID patchProperty; LV2_URID patchValue; LV2_URID timePosition; @@ -1166,6 +1242,7 @@ private: atomURID(map(LV2_ATOM__URID)), dpfKeyValue(map(DISTRHO_PLUGIN_LV2_STATE_PREFIX "KeyValueState")), midiEvent(map(LV2_MIDI__MidiEvent)), + patchSet(map(LV2_PATCH__Set)), patchProperty(map(LV2_PATCH__property)), patchValue(map(LV2_PATCH__value)), timePosition(map(LV2_TIME__Position)), @@ -1192,18 +1269,30 @@ private: const LV2_Worker_Schedule* const fWorker; #if DISTRHO_PLUGIN_WANT_STATE + LV2_Atom_Forge fAtomForge; StringToStringMap fStateMap; + UridToStringMap fUridStateMap; + LV2_URID* fUrids; bool* fNeededUiSends; void setState(const char* const key, const char* const newValue) { fPlugin.setState(key, newValue); - // check if we want to save this key - if (! fPlugin.wantStateKey(key)) - return; + // save this key if necessary + if (fPlugin.wantStateKey(key)) + updateInternalState(key, newValue, false); + } + + bool updateState(const char* const key, const char* const newValue) + { + fPlugin.setState(key, newValue); + return updateInternalState(key, newValue, true); + } - // check if key already exists + bool updateInternalState(const char* const key, const char* const newValue, const bool sendToUI) + { + // key must already exist for (StringToStringMap::iterator it=fStateMap.begin(), ite=fStateMap.end(); it != ite; ++it) { const String& dkey(it->first); @@ -1211,16 +1300,27 @@ private: if (dkey == key) { it->second = newValue; - return; + + if (sendToUI) + { + for (uint32_t i=0, count=fPlugin.getStateCount(); i < count; ++i) + { + if (fPlugin.getStateKey(i) == key) + { + if ((fPlugin.getStateHints(i) & kStateIsOnlyForDSP) == 0x0) + fNeededUiSends[i] = true; + break; + } + } + } + + return true; } } d_stderr("Failed to find plugin state with key \"%s\"", key); + return false; } - -# if DISTRHO_PLUGIN_WANT_STATEFILES - UridToStringMap fUridStateFileMap; -# endif #endif void updateParameterOutputsAndTriggers() @@ -1261,6 +1361,13 @@ private: } #endif +#if DISTRHO_PLUGIN_WANT_STATE + static bool updateStateValueCallback(void* const ptr, const char* const key, const char* const value) + { + return ((PluginLv2*)ptr)->updateState(key, value); + } +#endif + #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT bool writeMidi(const MidiEvent& midiEvent) { @@ -1296,7 +1403,7 @@ private: // ----------------------------------------------------------------------- -static LV2_Handle lv2_instantiate(const LV2_Descriptor*, double sampleRate, const char*, const LV2_Feature* const* features) +static LV2_Handle lv2_instantiate(const LV2_Descriptor*, double sampleRate, const char* bundlePath, const LV2_Feature* const* features) { const LV2_Options_Option* options = nullptr; const LV2_URID_Map* uridMap = nullptr; @@ -1339,7 +1446,7 @@ static LV2_Handle lv2_instantiate(const LV2_Descriptor*, double sampleRate, cons mod_license_check(features, DISTRHO_PLUGIN_URI); #endif - d_lastBufferSize = 0; + d_nextBufferSize = 0; bool usingNominal = false; for (int i=0; options[i].key != 0; ++i) @@ -1348,7 +1455,7 @@ static LV2_Handle lv2_instantiate(const LV2_Descriptor*, double sampleRate, cons { if (options[i].type == uridMap->map(uridMap->handle, LV2_ATOM__Int)) { - d_lastBufferSize = *(const int*)options[i].value; + d_nextBufferSize = *(const int*)options[i].value; usingNominal = true; } else @@ -1361,7 +1468,7 @@ static LV2_Handle lv2_instantiate(const LV2_Descriptor*, double sampleRate, cons if (options[i].key == uridMap->map(uridMap->handle, LV2_BUF_SIZE__maxBlockLength)) { if (options[i].type == uridMap->map(uridMap->handle, LV2_ATOM__Int)) - d_lastBufferSize = *(const int*)options[i].value; + d_nextBufferSize = *(const int*)options[i].value; else d_stderr("Host provides maxBlockLength but has wrong value type"); @@ -1369,14 +1476,18 @@ static LV2_Handle lv2_instantiate(const LV2_Descriptor*, double sampleRate, cons } } - if (d_lastBufferSize == 0) + if (d_nextBufferSize == 0) { d_stderr("Host does not provide nominalBlockLength or maxBlockLength options"); - d_lastBufferSize = 2048; + d_nextBufferSize = 2048; } - d_lastSampleRate = sampleRate; - d_lastCanRequestParameterValueChanges = ctrlInPortChangeReq != nullptr; + d_nextSampleRate = sampleRate; + d_nextBundlePath = bundlePath; + d_nextCanRequestParameterValueChanges = ctrlInPortChangeReq != nullptr; + + if (std::getenv("RUNNING_UNDER_LV2LINT") != nullptr) + d_nextPluginIsDummy = true; return new PluginLv2(sampleRate, uridMap, worker, ctrlInPortChangeReq, usingNominal); } diff --git a/distrho/src/DistrhoPluginLV2export.cpp b/distrho/src/DistrhoPluginLV2export.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -15,6 +15,7 @@ */ #include "DistrhoPluginInternal.hpp" +#include "../DistrhoPluginUtils.hpp" #include "lv2/atom.h" #include "lv2/buf-size.h" @@ -41,6 +42,10 @@ # include "mod-license.h" #endif +#ifndef DISTRHO_OS_WINDOWS +# include <unistd.h> +#endif + #include <fstream> #include <iostream> @@ -74,8 +79,8 @@ # define DISTRHO_LV2_UI_TYPE "UI" #endif -#define DISTRHO_LV2_USE_EVENTS_IN (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI) || DISTRHO_PLUGIN_WANT_STATEFILES) -#define DISTRHO_LV2_USE_EVENTS_OUT (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI)) +#define DISTRHO_LV2_USE_EVENTS_IN (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_TIMEPOS || DISTRHO_PLUGIN_WANT_STATE) +#define DISTRHO_LV2_USE_EVENTS_OUT (DISTRHO_PLUGIN_WANT_MIDI_OUTPUT || DISTRHO_PLUGIN_WANT_STATE) #define DISTRHO_BYPASS_PARAMETER_NAME "lv2_enabled" @@ -147,9 +152,7 @@ static const char* const lv2ManifestUiOptionalFeatures[] = "ui:parent", "ui:touch", #endif -#if DISTRHO_PLUGIN_WANT_STATEFILES "ui:requestValue", -#endif nullptr }; @@ -223,12 +226,28 @@ void lv2_generate_ttl(const char* const basename) { USE_NAMESPACE_DISTRHO + String bundlePath(getBinaryFilename()); + if (bundlePath.isNotEmpty()) + { + bundlePath.truncate(bundlePath.rfind(DISTRHO_OS_SEP)); + } +#ifndef DISTRHO_OS_WINDOWS + else if (char* const cwd = ::getcwd(nullptr, 0)) + { + bundlePath = cwd; + std::free(cwd); + } +#endif + d_nextBundlePath = bundlePath.buffer(); + // Dummy plugin to get data from - d_lastBufferSize = 512; - d_lastSampleRate = 44100.0; - PluginExporter plugin(nullptr, nullptr, nullptr); - d_lastBufferSize = 0; - d_lastSampleRate = 0.0; + d_nextBufferSize = 512; + d_nextSampleRate = 44100.0; + d_nextPluginIsDummy = true; + PluginExporter plugin(nullptr, nullptr, nullptr, nullptr); + d_nextBufferSize = 0; + d_nextSampleRate = 0.0; + d_nextPluginIsDummy = false; const String pluginDLL(basename); const String pluginTTL(pluginDLL + ".ttl"); @@ -332,6 +351,7 @@ void lv2_generate_ttl(const char* const basename) pluginString += "@prefix doap: <http://usefulinc.com/ns/doap#> .\n"; pluginString += "@prefix foaf: <http://xmlns.com/foaf/0.1/> .\n"; pluginString += "@prefix lv2: <" LV2_CORE_PREFIX "> .\n"; + pluginString += "@prefix midi: <" LV2_MIDI_PREFIX "> .\n"; pluginString += "@prefix mod: <http://moddevices.com/ns/mod#> .\n"; pluginString += "@prefix opts: <" LV2_OPTIONS_PREFIX "> .\n"; pluginString += "@prefix pg: <" LV2_PORT_GROUPS_PREFIX "> .\n"; @@ -341,38 +361,54 @@ void lv2_generate_ttl(const char* const basename) #if DISTRHO_LV2_USE_EVENTS_IN || DISTRHO_LV2_USE_EVENTS_OUT pluginString += "@prefix rsz: <" LV2_RESIZE_PORT_PREFIX "> .\n"; #endif + pluginString += "@prefix spdx: <http://spdx.org/rdf/terms#> .\n"; #if DISTRHO_PLUGIN_HAS_UI pluginString += "@prefix ui: <" LV2_UI_PREFIX "> .\n"; #endif pluginString += "@prefix unit: <" LV2_UNITS_PREFIX "> .\n"; pluginString += "\n"; -#if DISTRHO_PLUGIN_WANT_STATEFILES - // define writable states as lv2 parameters - bool hasStateFiles = false; +#if DISTRHO_PLUGIN_WANT_STATE + bool hasHostVisibleState = false; for (uint32_t i=0, count=plugin.getStateCount(); i < count; ++i) { - if (! plugin.isStateFile(i)) + const uint32_t hints = plugin.getStateHints(i); + + if ((hints & kStateIsHostReadable) == 0x0) continue; - const String& key(plugin.getStateKey(i)); - pluginString += "<" DISTRHO_PLUGIN_URI "#" + key + ">\n"; + pluginString += "<" DISTRHO_PLUGIN_URI "#" + plugin.getStateKey(i) + ">\n"; pluginString += " a lv2:Parameter ;\n"; - pluginString += " rdfs:label \"" + key + "\" ;\n"; - pluginString += " rdfs:range atom:Path .\n\n"; - hasStateFiles = true; + pluginString += " rdfs:label \"" + plugin.getStateLabel(i) + "\" ;\n"; + + const String& comment(plugin.getStateDescription(i)); + + if (comment.isNotEmpty()) + { + if (comment.contains('"') || comment.contains('\n')) + pluginString += " rdfs:comment \"\"\"" + comment + "\"\"\" ;\n"; + else + pluginString += " rdfs:comment \"" + comment + "\" ;\n"; + } + + if ((hints & kStateIsFilenamePath) == kStateIsFilenamePath) + pluginString += " rdfs:range atom:Path .\n\n"; + else + pluginString += " rdfs:range atom:String .\n\n"; + + hasHostVisibleState = true; } #endif // plugin pluginString += "<" DISTRHO_PLUGIN_URI ">\n"; #ifdef DISTRHO_PLUGIN_LV2_CATEGORY - pluginString += " a " DISTRHO_PLUGIN_LV2_CATEGORY ", lv2:Plugin ;\n"; + pluginString += " a " DISTRHO_PLUGIN_LV2_CATEGORY ", lv2:Plugin, doap:Project ;\n"; #elif DISTRHO_PLUGIN_IS_SYNTH - pluginString += " a lv2:InstrumentPlugin, lv2:Plugin ;\n"; + pluginString += " a lv2:InstrumentPlugin, lv2:Plugin, doap:Project ;\n"; #else - pluginString += " a lv2:Plugin ;\n"; + pluginString += " a lv2:Plugin, doap:Project ;\n"; #endif pluginString += "\n"; @@ -381,16 +417,22 @@ void lv2_generate_ttl(const char* const basename) addAttribute(pluginString, "lv2:requiredFeature", lv2ManifestPluginRequiredFeatures, 4); addAttribute(pluginString, "opts:supportedOption", lv2ManifestPluginSupportedOptions, 4); -#if DISTRHO_PLUGIN_WANT_STATEFILES - if (hasStateFiles) +#if DISTRHO_PLUGIN_WANT_STATE + if (hasHostVisibleState) { for (uint32_t i=0, count=plugin.getStateCount(); i < count; ++i) { - if (! plugin.isStateFile(i)) + const uint32_t hints = plugin.getStateHints(i); + + if ((hints & kStateIsHostReadable) == 0x0) continue; const String& key(plugin.getStateKey(i)); - pluginString += " patch:writable <" DISTRHO_PLUGIN_URI "#" + key + ">;\n"; + + if ((hints & kStateIsHostWritable) == kStateIsHostWritable) + pluginString += " patch:writable <" DISTRHO_PLUGIN_URI "#" + key + "> ;\n"; + else + pluginString += " patch:readable <" DISTRHO_PLUGIN_URI "#" + key + "> ;\n"; } pluginString += "\n"; } @@ -430,20 +472,23 @@ void lv2_generate_ttl(const char* const basename) if (port.hints & kAudioPortIsSidechain) pluginString += " lv2:portProperty lv2:isSideChain;\n"; - switch (port.groupId) + if (port.groupId != kPortGroupNone) { - case kPortGroupNone: - break; - case kPortGroupMono: - pluginString += " pg:group pg:MonoGroup ;\n"; - break; - case kPortGroupStereo: - pluginString += " pg:group pg:StereoGroup ;\n"; - break; - default: pluginString += " pg:group <" DISTRHO_PLUGIN_URI "#portGroup_" + plugin.getPortGroupSymbolForId(port.groupId) + "> ;\n"; - break; + + switch (port.groupId) + { + case kPortGroupMono: + pluginString += " lv2:designation pg:center ;\n"; + break; + case kPortGroupStereo: + if (i == 1) + pluginString += " lv2:designation pg:right ;\n"; + else + pluginString += " lv2:designation pg:left ;\n"; + break; + } } // set ranges @@ -520,20 +565,23 @@ void lv2_generate_ttl(const char* const basename) if (port.hints & kAudioPortIsSidechain) pluginString += " lv2:portProperty lv2:isSideChain;\n"; - switch (port.groupId) + if (port.groupId != kPortGroupNone) { - case kPortGroupNone: - break; - case kPortGroupMono: - pluginString += " pg:group pg:MonoGroup ;\n"; - break; - case kPortGroupStereo: - pluginString += " pg:group pg:StereoGroup ;\n"; - break; - default: pluginString += " pg:group <" DISTRHO_PLUGIN_URI "#portGroup_" + plugin.getPortGroupSymbolForId(port.groupId) + "> ;\n"; - break; + + switch (port.groupId) + { + case kPortGroupMono: + pluginString += " lv2:designation pg:center ;\n"; + break; + case kPortGroupStereo: + if (i == 1) + pluginString += " lv2:designation pg:right ;\n"; + else + pluginString += " lv2:designation pg:left ;\n"; + break; + } } // set ranges @@ -594,14 +642,21 @@ void lv2_generate_ttl(const char* const basename) pluginString += " rsz:minimumSize " + String(DISTRHO_PLUGIN_MINIMUM_BUFFER_SIZE) + " ;\n"; pluginString += " atom:bufferType atom:Sequence ;\n"; # if (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI) - pluginString += " atom:supports <" LV2_ATOM__String "> ;\n"; + pluginString += " atom:supports atom:String ;\n"; # endif # if DISTRHO_PLUGIN_WANT_MIDI_INPUT - pluginString += " atom:supports <" LV2_MIDI__MidiEvent "> ;\n"; + pluginString += " atom:supports midi:MidiEvent ;\n"; # endif # if DISTRHO_PLUGIN_WANT_TIMEPOS pluginString += " atom:supports <" LV2_TIME__Position "> ;\n"; # endif +# if DISTRHO_PLUGIN_WANT_STATE + if (hasHostVisibleState) + { + pluginString += " atom:supports <" LV2_PATCH__Message "> ;\n"; + pluginString += " lv2:designation lv2:control ;\n"; + } +# endif pluginString += " ] ;\n\n"; ++portIndex; #endif @@ -615,10 +670,17 @@ void lv2_generate_ttl(const char* const basename) pluginString += " rsz:minimumSize " + String(DISTRHO_PLUGIN_MINIMUM_BUFFER_SIZE) + " ;\n"; pluginString += " atom:bufferType atom:Sequence ;\n"; # if (DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI) - pluginString += " atom:supports <" LV2_ATOM__String "> ;\n"; + pluginString += " atom:supports atom:String ;\n"; # endif # if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT - pluginString += " atom:supports <" LV2_MIDI__MidiEvent "> ;\n"; + pluginString += " atom:supports midi:MidiEvent ;\n"; +# endif +# if DISTRHO_PLUGIN_WANT_STATE + if (hasHostVisibleState) + { + pluginString += " atom:supports <" LV2_PATCH__Message "> ;\n"; + pluginString += " lv2:designation lv2:control ;\n"; + } # endif pluginString += " ] ;\n\n"; ++portIndex; @@ -746,12 +808,22 @@ void lv2_generate_ttl(const char* const basename) } if (j+1 == enumValues.count) - pluginString += " ] ;\n\n"; + pluginString += " ] ;\n"; else pluginString += " ] ,\n"; } } + // MIDI CC binding + if (const uint8_t midiCC = plugin.getParameterMidiCC(i)) + { + char midiCCBuf[7]; + snprintf(midiCCBuf, sizeof(midiCCBuf), "B0%02x00", midiCC); + pluginString += " midi:binding \""; + pluginString += midiCCBuf; + pluginString += "\"^^midi:MidiEvent ;\n"; + } + // unit const String& unit(plugin.getParameterUnit(i)); @@ -814,7 +886,7 @@ void lv2_generate_ttl(const char* const basename) } // hints - const uint32_t hints(plugin.getParameterHints(i)); + const uint32_t hints = plugin.getParameterHints(i); if (hints & kParameterIsBoolean) { @@ -826,32 +898,18 @@ void lv2_generate_ttl(const char* const basename) pluginString += " lv2:portProperty lv2:integer ;\n"; if (hints & kParameterIsLogarithmic) pluginString += " lv2:portProperty <" LV2_PORT_PROPS__logarithmic "> ;\n"; - if ((hints & kParameterIsAutomable) == 0 && plugin.isParameterInput(i)) + if ((hints & kParameterIsAutomatable) == 0 && plugin.isParameterInput(i)) { pluginString += " lv2:portProperty <" LV2_PORT_PROPS__expensive "> ,\n"; - pluginString += " <" LV2_KXSTUDIO_PROPERTIES__NonAutomable "> ;\n"; + pluginString += " <" LV2_KXSTUDIO_PROPERTIES__NonAutomatable "> ;\n"; } - // TODO midiCC - // group const uint32_t groupId = plugin.getParameterGroupId(i); - switch (groupId) - { - case kPortGroupNone: - break; - case kPortGroupMono: - pluginString += " pg:group pg:MonoGroup ;\n"; - break; - case kPortGroupStereo: - pluginString += " pg:group pg:StereoGroup ;\n"; - break; - default: + if (groupId != kPortGroupNone) pluginString += " pg:group <" DISTRHO_PLUGIN_URI "#portGroup_" + plugin.getPortGroupSymbolForId(groupId) + "> ;\n"; - break; - } } // ! designated @@ -895,13 +953,156 @@ void lv2_generate_ttl(const char* const basename) { const String license(plugin.getLicense()); - // TODO always convert to URL, do best-guess based on known licenses + // Using URL as license if (license.contains("://")) + { pluginString += " doap:license <" + license + "> ;\n\n"; + } + // String contaning quotes, use as-is else if (license.contains('"')) + { pluginString += " doap:license \"\"\"" + license + "\"\"\" ;\n\n"; + } + // Regular license string, convert to URL as much as we can else - pluginString += " doap:license \"" + license + "\" ;\n\n"; + { + const String uplicense(license.asUpper()); + + // for reference, see https://spdx.org/licenses/ + + // common licenses + /**/ if (uplicense == "AGPL-1.0-ONLY" || + uplicense == "AGPL1" || + uplicense == "AGPLV1") + { + pluginString += " doap:license <http://spdx.org/licenses/AGPL-1.0-only.html> ;\n\n"; + } + else if (uplicense == "AGPL-1.0-OR-LATER" || + uplicense == "AGPL1+" || + uplicense == "AGPLV1+") + { + pluginString += " doap:license <http://spdx.org/licenses/AGPL-1.0-or-later.html> ;\n\n"; + } + else if (uplicense == "AGPL-3.0-ONLY" || + uplicense == "AGPL3" || + uplicense == "AGPLV3") + { + pluginString += " doap:license <http://spdx.org/licenses/AGPL-3.0-only.html> ;\n\n"; + } + else if (uplicense == "AGPL-3.0-OR-LATER" || + uplicense == "AGPL3+" || + uplicense == "AGPLV3+") + { + pluginString += " doap:license <http://spdx.org/licenses/AGPL-3.0-or-later.html> ;\n\n"; + } + else if (uplicense == "APACHE-2.0" || + uplicense == "APACHE2" || + uplicense == "APACHE-2") + { + pluginString += " doap:license <http://spdx.org/licenses/Apache-2.0.html> ;\n\n"; + } + else if (uplicense == "BSD-2-CLAUSE" || + uplicense == "BSD2" || + uplicense == "BSD-2") + { + pluginString += " doap:license <http://spdx.org/licenses/BSD-2-Clause.html> ;\n\n"; + } + else if (uplicense == "BSD-3-CLAUSE" || + uplicense == "BSD3" || + uplicense == "BSD-3") + { + pluginString += " doap:license <http://spdx.org/licenses/BSD-3-Clause.html> ;\n\n"; + } + else if (uplicense == "GPL-2.0-ONLY" || + uplicense == "GPL2" || + uplicense == "GPLV2") + { + pluginString += " doap:license <http://spdx.org/licenses/GPL-2.0-only.html> ;\n\n"; + } + else if (uplicense == "GPL-2.0-OR-LATER" || + uplicense == "GPL2+" || + uplicense == "GPLV2+" || + uplicense == "GPLV2.0+" || + uplicense == "GPL V2+") + { + pluginString += " doap:license <http://spdx.org/licenses/GPL-2.0-or-later.html> ;\n\n"; + } + else if (uplicense == "GPL-3.0-ONLY" || + uplicense == "GPL3" || + uplicense == "GPLV3") + { + pluginString += " doap:license <http://spdx.org/licenses/GPL-3.0-only.html> ;\n\n"; + } + else if (uplicense == "GPL-3.0-OR-LATER" || + uplicense == "GPL3+" || + uplicense == "GPLV3+" || + uplicense == "GPLV3.0+" || + uplicense == "GPL V3+") + { + pluginString += " doap:license <http://spdx.org/licenses/GPL-3.0-or-later.html> ;\n\n"; + } + else if (uplicense == "ISC") + { + pluginString += " doap:license <http://spdx.org/licenses/ISC.html> ;\n\n"; + } + else if (uplicense == "LGPL-2.0-ONLY" || + uplicense == "LGPL2" || + uplicense == "LGPLV2") + { + pluginString += " doap:license <http://spdx.org/licenses/LGPL-2.0-only.html> ;\n\n"; + } + else if (uplicense == "LGPL-2.0-OR-LATER" || + uplicense == "LGPL2+" || + uplicense == "LGPLV2+") + { + pluginString += " doap:license <http://spdx.org/licenses/LGPL-2.0-or-later.html> ;\n\n"; + } + else if (uplicense == "LGPL-2.1-ONLY" || + uplicense == "LGPL2.1" || + uplicense == "LGPLV2.1") + { + pluginString += " doap:license <http://spdx.org/licenses/LGPL-2.1-only.html> ;\n\n"; + } + else if (uplicense == "LGPL-2.1-OR-LATER" || + uplicense == "LGPL2.1+" || + uplicense == "LGPLV2.1+") + { + pluginString += " doap:license <http://spdx.org/licenses/LGPL-2.1-or-later.html> ;\n\n"; + } + else if (uplicense == "LGPL-3.0-ONLY" || + uplicense == "LGPL3" || + uplicense == "LGPLV3") + { + pluginString += " doap:license <http://spdx.org/licenses/LGPL-2.0-only.html> ;\n\n"; + } + else if (uplicense == "LGPL-3.0-OR-LATER" || + uplicense == "LGPL3+" || + uplicense == "LGPLV3+") + { + pluginString += " doap:license <http://spdx.org/licenses/LGPL-3.0-or-later.html> ;\n\n"; + } + else if (uplicense == "MIT") + { + pluginString += " doap:license <http://spdx.org/licenses/MIT.html> ;\n\n"; + } + + // generic fallbacks + else if (uplicense.startsWith("GPL")) + { + pluginString += " doap:license <http://opensource.org/licenses/gpl-license> ;\n\n"; + } + else if (uplicense.startsWith("LGPL")) + { + pluginString += " doap:license <http://opensource.org/licenses/lgpl-license> ;\n\n"; + } + + // unknown or not handled yet, log a warning + else + { + d_stderr("Unknown license string '%s'", license.buffer()); + pluginString += " doap:license \"" + license + "\" ;\n\n"; + } + } } // developer @@ -926,8 +1127,8 @@ void lv2_generate_ttl(const char* const basename) const uint32_t version(plugin.getVersion()); const uint32_t majorVersion = (version & 0xFF0000) >> 16; - const uint32_t microVersion = (version & 0x00FF00) >> 8; - /* */ uint32_t minorVersion = (version & 0x0000FF) >> 0; + /* */ uint32_t minorVersion = (version & 0x00FF00) >> 8; + const uint32_t microVersion = (version & 0x0000FF) >> 0; // NOTE: LV2 ignores 'major' version and says 0 for minor is pre-release/unstable. if (majorVersion > 0) @@ -948,13 +1149,6 @@ void lv2_generate_ttl(const char* const basename) DISTRHO_SAFE_ASSERT_CONTINUE(portGroup.groupId != kPortGroupNone); DISTRHO_SAFE_ASSERT_CONTINUE(portGroup.symbol.isNotEmpty()); - switch (portGroup.groupId) - { - case kPortGroupMono: - case kPortGroupStereo: - continue; - } - pluginString += "\n<" DISTRHO_PLUGIN_URI "#portGroup_" + portGroup.symbol + ">\n"; isInput = isOutput = false; @@ -978,19 +1172,28 @@ void lv2_generate_ttl(const char* const basename) } pluginString += " a "; + if (isInput && !isOutput) pluginString += "pg:InputGroup"; else if (isOutput && !isInput) pluginString += "pg:OutputGroup"; else pluginString += "pg:Group"; + + switch (portGroup.groupId) + { + case kPortGroupMono: + pluginString += " , pg:MonoGroup"; + break; + case kPortGroupStereo: + pluginString += " , pg:StereoGroup"; + break; + } + pluginString += " ;\n"; -#if 0 - pluginString += " rdfs:label \"" + portGroup.name + "\" ;\n"; -#else + // pluginString += " rdfs:label \"" + portGroup.name + "\" ;\n"; pluginString += " lv2:name \"" + portGroup.name + "\" ;\n"; -#endif pluginString += " lv2:symbol \"" + portGroup.symbol + "\" .\n"; } } @@ -1037,7 +1240,10 @@ void lv2_generate_ttl(const char* const basename) presetsString += "@prefix lv2: <" LV2_CORE_PREFIX "> .\n"; presetsString += "@prefix pset: <" LV2_PRESETS_PREFIX "> .\n"; # if DISTRHO_PLUGIN_WANT_STATE + presetsString += "@prefix owl: <http://www.w3.org/2002/07/owl#> .\n"; + presetsString += "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n"; presetsString += "@prefix state: <" LV2_STATE_PREFIX "> .\n"; + presetsString += "@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n"; # endif presetsString += "\n"; @@ -1045,8 +1251,13 @@ void lv2_generate_ttl(const char* const basename) const uint32_t numPrograms = plugin.getProgramCount(); # if DISTRHO_PLUGIN_WANT_FULL_STATE const uint32_t numStates = plugin.getStateCount(); + const bool valid = numParameters != 0 || numStates != 0; +# else + const bool valid = numParameters != 0; # endif + DISTRHO_CUSTOM_SAFE_ASSERT_RETURN("Programs require parameters or full state", valid, presetsFile.close()); + const String presetSeparator(std::strstr(DISTRHO_PLUGIN_URI, "#") != nullptr ? ":" : "#"); char strBuf[0xff+1]; @@ -1054,6 +1265,23 @@ void lv2_generate_ttl(const char* const basename) String presetString; +# if DISTRHO_PLUGIN_WANT_FULL_STATE + for (uint32_t i=0; i<numStates; ++i) + { + if (plugin.getStateHints(i) & kStateIsHostReadable) + continue; + + // readable states are defined as lv2 parameters. + // non-readable states have no definition, but one is needed for presets and ttl validation. + presetString = "<" DISTRHO_PLUGIN_LV2_STATE_PREFIX + plugin.getStateKey(i) + ">\n"; + presetString += " a owl:DatatypeProperty ;\n"; + presetString += " rdfs:label \"Plugin state key-value string pair\" ;\n"; + presetString += " rdfs:domain state:State ;\n"; + presetString += " rdfs:range xsd:string .\n\n"; + presetsString += presetString; + } +# endif + for (uint32_t i=0; i<numPrograms; ++i) { std::snprintf(strBuf, 0xff, "%03i", i+1); @@ -1063,24 +1291,20 @@ void lv2_generate_ttl(const char* const basename) presetString = "<" DISTRHO_PLUGIN_URI + presetSeparator + "preset" + strBuf + ">\n"; # if DISTRHO_PLUGIN_WANT_FULL_STATE - if (numParameters == 0 && numStates == 0) -#else - if (numParameters == 0) -#endif - { - presetString += " ."; - presetsString += presetString; - continue; - } - -# if DISTRHO_PLUGIN_WANT_FULL_STATE presetString += " state:state [\n"; for (uint32_t j=0; j<numStates; ++j) { const String key = plugin.getStateKey(j); - const String value = plugin.getState(key); + const String value = plugin.getStateValue(key); + + presetString += " <"; + + if (plugin.getStateHints(i) & kStateIsHostReadable) + presetString += DISTRHO_PLUGIN_URI "#"; + else + presetString += DISTRHO_PLUGIN_LV2_STATE_PREFIX; - presetString += " <" DISTRHO_PLUGIN_LV2_STATE_PREFIX + key + ">"; + presetString += key + ">"; if (value.length() < 10) presetString += " \"" + value + "\" ;\n"; diff --git a/distrho/src/DistrhoPluginVST.hpp b/distrho/src/DistrhoPluginVST.hpp @@ -0,0 +1,421 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DISTRHO_PLUGIN_VST_HPP_INCLUDED +#define DISTRHO_PLUGIN_VST_HPP_INCLUDED + +#include "DistrhoPluginChecks.h" +#include "../DistrhoUtils.hpp" + +#include <algorithm> +#include <cmath> + +#if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_HAS_EMBED_UI +# undef DISTRHO_PLUGIN_HAS_UI +# define DISTRHO_PLUGIN_HAS_UI 0 +#endif + +#if DISTRHO_PLUGIN_HAS_UI && ! defined(HAVE_DGL) && ! DISTRHO_PLUGIN_HAS_EXTERNAL_UI +# undef DISTRHO_PLUGIN_HAS_UI +# define DISTRHO_PLUGIN_HAS_UI 0 +#endif + +#if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_HAS_EXTERNAL_UI +# include "Base.hpp" +#endif + +#if DISTRHO_PLUGIN_HAS_UI == 1 && DISTRHO_PLUGIN_WANT_DIRECT_ACCESS == 0 +# define DPF_VST3_USES_SEPARATE_CONTROLLER 1 +#else +# define DPF_VST3_USES_SEPARATE_CONTROLLER 0 +#endif + +// -------------------------------------------------------------------------------------------------------------------- + +#ifdef DISTRHO_PROPER_CPP11_SUPPORT +# include <atomic> +#else +// quick and dirty std::atomic replacement for the things we need +namespace std { + struct atomic_int { + volatile int value; + explicit atomic_int(volatile int v) noexcept : value(v) {} + int operator++() volatile noexcept { return __atomic_add_fetch(&value, 1, __ATOMIC_RELAXED); } + int operator--() volatile noexcept { return __atomic_sub_fetch(&value, 1, __ATOMIC_RELAXED); } + operator int() volatile noexcept { return __atomic_load_n(&value, __ATOMIC_RELAXED); } + }; +}; +#endif + +// -------------------------------------------------------------------------------------------------------------------- + +START_NAMESPACE_DISTRHO + +// -------------------------------------------------------------------------------------------------------------------- + +enum Vst3InternalParameters { +#if DPF_VST3_USES_SEPARATE_CONTROLLER + kVst3InternalParameterBufferSize, + kVst3InternalParameterSampleRate, +#endif +#if DISTRHO_PLUGIN_WANT_LATENCY + kVst3InternalParameterLatency, +#endif +#if DISTRHO_PLUGIN_WANT_PROGRAMS + kVst3InternalParameterProgram, +#endif + kVst3InternalParameterBaseCount, +#if DISTRHO_PLUGIN_WANT_MIDI_INPUT + kVst3InternalParameterMidiCC_start = kVst3InternalParameterBaseCount, + kVst3InternalParameterMidiCC_end = kVst3InternalParameterMidiCC_start + 130*16, + kVst3InternalParameterCount = kVst3InternalParameterMidiCC_end +#else + kVst3InternalParameterCount = kVst3InternalParameterBaseCount +#endif +}; + +#if DPF_VST3_USES_SEPARATE_CONTROLLER || DISTRHO_PLUGIN_WANT_LATENCY || DISTRHO_PLUGIN_WANT_PROGRAMS || DISTRHO_PLUGIN_WANT_MIDI_INPUT +# define DPF_VST3_HAS_INTERNAL_PARAMETERS 1 +#else +# define DPF_VST3_HAS_INTERNAL_PARAMETERS 0 +#endif + +#if DPF_VST3_HAS_INTERNAL_PARAMETERS && DISTRHO_PLUGIN_WANT_MIDI_INPUT && \ + !(DPF_VST3_USES_SEPARATE_CONTROLLER || DISTRHO_PLUGIN_WANT_LATENCY || DISTRHO_PLUGIN_WANT_PROGRAMS) +# define DPF_VST3_PURE_MIDI_INTERNAL_PARAMETERS 1 +#else +# define DPF_VST3_PURE_MIDI_INTERNAL_PARAMETERS 0 +#endif + +// -------------------------------------------------------------------------------------------------------------------- + +static inline +bool strcmp_utf16(const int16_t* const str16, const char* const str8) +{ + size_t i = 0; + for (; str8[i] != '\0'; ++i) + { + const uint8_t char8 = static_cast<uint8_t>(str8[i]); + + // skip non-ascii chars, unsupported + if (char8 >= 0x80) + return false; + + if (str16[i] != char8) + return false; + } + + return str16[i] == str8[i]; +} + +// -------------------------------------------------------------------------------------------------------------------- + +static inline +size_t strlen_utf16(const int16_t* const str) +{ + size_t i = 0; + + while (str[i] != 0) + ++i; + + return i; +} + +// -------------------------------------------------------------------------------------------------------------------- + +static inline +void strncpy(char* const dst, const char* const src, const size_t length) +{ + DISTRHO_SAFE_ASSERT_RETURN(length > 0,); + + if (const size_t len = std::min(std::strlen(src), length-1U)) + { + std::memcpy(dst, src, len); + dst[len] = '\0'; + } + else + { + dst[0] = '\0'; + } +} + +static inline +void strncpy_utf8(char* const dst, const int16_t* const src, const size_t length) +{ + DISTRHO_SAFE_ASSERT_RETURN(length > 0,); + + if (const size_t len = std::min(strlen_utf16(src), length-1U)) + { + for (size_t i=0; i<len; ++i) + { + // skip non-ascii chars, unsupported + if (src[i] >= 0x80) + continue; + + dst[i] = src[i]; + } + dst[len] = 0; + } + else + { + dst[0] = 0; + } +} + +static inline +void strncpy_utf16(int16_t* const dst, const char* const src, const size_t length) +{ + DISTRHO_SAFE_ASSERT_RETURN(length > 0,); + + if (const size_t len = std::min(std::strlen(src), length-1U)) + { + for (size_t i=0; i<len; ++i) + { + // skip non-ascii chars, unsupported + if ((uint8_t)src[i] >= 0x80) + continue; + + dst[i] = src[i]; + } + dst[len] = 0; + } + else + { + dst[0] = 0; + } +} + +// -------------------------------------------------------------------------------------------------------------------- + +template<typename T> +static void snprintf_t(char* const dst, const T value, const char* const format, const size_t size) +{ + DISTRHO_SAFE_ASSERT_RETURN(size > 0,); + std::snprintf(dst, size-1, format, value); + dst[size-1] = '\0'; +} + +template<typename T> +static void snprintf_utf16_t(int16_t* const dst, const T value, const char* const format, const size_t size) +{ + DISTRHO_SAFE_ASSERT_RETURN(size > 0,); + + char* const tmpbuf = (char*)std::malloc(size); + DISTRHO_SAFE_ASSERT_RETURN(tmpbuf != nullptr,); + + std::snprintf(tmpbuf, size-1, format, value); + tmpbuf[size-1] = '\0'; + + strncpy_utf16(dst, tmpbuf, size); + std::free(tmpbuf); +} + +static inline +void snprintf_f32(char* const dst, const float value, const size_t size) +{ + return snprintf_t<float>(dst, value, "%f", size); +} + +static inline +void snprintf_i32(char* const dst, const int32_t value, const size_t size) +{ + return snprintf_t<int32_t>(dst, value, "%d", size); +} + +static inline +void snprintf_u32(char* const dst, const uint32_t value, const size_t size) +{ + return snprintf_t<uint32_t>(dst, value, "%u", size); +} + +static inline +void snprintf_f32_utf16(int16_t* const dst, const float value, const size_t size) +{ + return snprintf_utf16_t<float>(dst, value, "%f", size); +} + +static inline +void snprintf_i32_utf16(int16_t* const dst, const int32_t value, const size_t size) +{ + return snprintf_utf16_t<int32_t>(dst, value, "%d", size); +} + +static inline +void snprintf_u32_utf16(int16_t* const dst, const uint32_t value, const size_t size) +{ + return snprintf_utf16_t<uint32_t>(dst, value, "%u", size); +} + +#if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_HAS_EXTERNAL_UI +// -------------------------------------------------------------------------------------------------------------------- +// translate a vstgui-based key character and code to matching values used by DPF + +static inline +uint translateVstKeyCode(bool& special, const int16_t keychar, const int16_t keycode) noexcept +{ + using namespace DGL_NAMESPACE; + + // special stuff first + special = true; + switch (keycode) + { + case 1: return kKeyBackspace; + // 2 \t (handled below) + // 3 clear + // 4 \r (handled below) + case 6: return kKeyEscape; + // 7 space (handled below) + // 8 next + // 17 select + // 18 print + // 19 \n (handled below) + // 20 snapshot + case 22: return kKeyDelete; + // 23 help + // 57 = (handled below) + // numpad stuff follows + // 24 0 (handled below) + // 25 1 (handled below) + // 26 2 (handled below) + // 27 3 (handled below) + // 28 4 (handled below) + // 29 5 (handled below) + // 30 6 (handled below) + // 31 7 (handled below) + // 32 8 (handled below) + // 33 9 (handled below) + // 34 * (handled below) + // 35 + (handled below) + // 36 separator + // 37 - (handled below) + // 38 . (handled below) + // 39 / (handled below) + // handle rest of special keys + /* these special keys are missing: + - kKeySuper + - kKeyCapsLock + - kKeyPrintScreen + */ + case 40: return kKeyF1; + case 41: return kKeyF2; + case 42: return kKeyF3; + case 43: return kKeyF4; + case 44: return kKeyF5; + case 45: return kKeyF6; + case 46: return kKeyF7; + case 47: return kKeyF8; + case 48: return kKeyF9; + case 49: return kKeyF10; + case 50: return kKeyF11; + case 51: return kKeyF12; + case 11: return kKeyLeft; + case 12: return kKeyUp; + case 13: return kKeyRight; + case 14: return kKeyDown; + case 15: return kKeyPageUp; + case 16: return kKeyPageDown; + case 10: return kKeyHome; + case 9: return kKeyEnd; + case 21: return kKeyInsert; + case 54: return kKeyShift; + case 55: return kKeyControl; + case 56: return kKeyAlt; + case 58: return kKeyMenu; + case 52: return kKeyNumLock; + case 53: return kKeyScrollLock; + case 5: return kKeyPause; + } + + // regular keys next + special = false; + switch (keycode) + { + case 2: return '\t'; + case 4: return '\r'; + case 7: return ' '; + case 19: return '\n'; + case 57: return '='; + case 24: return '0'; + case 25: return '1'; + case 26: return '2'; + case 27: return '3'; + case 28: return '4'; + case 29: return '5'; + case 30: return '6'; + case 31: return '7'; + case 32: return '8'; + case 33: return '9'; + case 34: return '*'; + case 35: return '+'; + case 37: return '-'; + case 38: return '.'; + case 39: return '/'; + } + + // fallback + return keychar; +} +#endif + +// -------------------------------------------------------------------------------------------------------------------- +// handy way to create a utf16 string from a utf8 one on the current function scope, used for message strings + +struct ScopedUTF16String { + int16_t* str; + ScopedUTF16String(const char* const s) noexcept + : str(nullptr) + { + const size_t len = std::strlen(s); + str = static_cast<int16_t*>(std::malloc(sizeof(int16_t) * (len + 1))); + DISTRHO_SAFE_ASSERT_RETURN(str != nullptr,); + strncpy_utf16(str, s, len + 1); + } + + ~ScopedUTF16String() noexcept + { + std::free(str); + } + + operator const int16_t*() const noexcept + { + return str; + } +}; + +// -------------------------------------------------------------------------------------------------------------------- +// handy way to create a utf8 string from a utf16 one on the current function scope (limited to 128 chars) + +struct ScopedUTF8String { + char str[128]; + + ScopedUTF8String(const int16_t* const s) noexcept + { + strncpy_utf8(str, s, 128); + } + + operator const char*() const noexcept + { + return str; + } +}; + +// -------------------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO + +// -------------------------------------------------------------------------------------------------------------------- + +#endif // DISTRHO_PLUGIN_VST_HPP_INCLUDED diff --git a/distrho/src/DistrhoPluginVST2.cpp b/distrho/src/DistrhoPluginVST2.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -15,17 +15,10 @@ */ #include "DistrhoPluginInternal.hpp" +#include "DistrhoPluginVST.hpp" +#include "../DistrhoPluginUtils.hpp" #include "../extra/ScopedSafeLocale.hpp" - -#if DISTRHO_PLUGIN_HAS_UI && ! DISTRHO_PLUGIN_HAS_EMBED_UI -# undef DISTRHO_PLUGIN_HAS_UI -# define DISTRHO_PLUGIN_HAS_UI 0 -#endif - -#if DISTRHO_PLUGIN_HAS_UI && ! defined(HAVE_DGL) && ! DISTRHO_PLUGIN_HAS_EXTERNAL_UI -# undef DISTRHO_PLUGIN_HAS_UI -# define DISTRHO_PLUGIN_HAS_UI 0 -#endif +#include "../extra/ScopedPointer.hpp" #if DISTRHO_PLUGIN_HAS_UI # include "DistrhoUIInternal.hpp" @@ -44,6 +37,7 @@ #include <clocale> #include <map> #include <string> +#include <vector> #if VESTIGE_HEADER # include "vestige/vestige.h" @@ -82,37 +76,6 @@ static const requestParameterValueChangeFunc requestParameterValueChangeCallback // ----------------------------------------------------------------------- -void strncpy(char* const dst, const char* const src, const size_t size) -{ - DISTRHO_SAFE_ASSERT_RETURN(size > 0,); - - if (const size_t len = std::min(std::strlen(src), size-1U)) - { - std::memcpy(dst, src, len); - dst[len] = '\0'; - } - else - { - dst[0] = '\0'; - } -} - -void snprintf_param(char* const dst, const float value, const size_t size) -{ - DISTRHO_SAFE_ASSERT_RETURN(size > 0,); - std::snprintf(dst, size-1, "%f", value); - dst[size-1] = '\0'; -} - -void snprintf_iparam(char* const dst, const int32_t value, const size_t size) -{ - DISTRHO_SAFE_ASSERT_RETURN(size > 0,); - std::snprintf(dst, size-1, "%d", value); - dst[size-1] = '\0'; -} - -// ----------------------------------------------------------------------- - struct ParameterAndNotesHelper { float* parameterValues; @@ -187,7 +150,7 @@ public: sendNoteCallback, setSizeCallback, nullptr, // TODO file request - nullptr, + d_nextBundlePath, plugin->getInstancePointer(), scaleFactor) # if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI @@ -254,86 +217,16 @@ public: # endif # if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI - int handlePluginKeyEvent(const bool down, int32_t index, const intptr_t value) + int handlePluginKeyEvent(const bool down, const int32_t index, const intptr_t value) { d_stdout("handlePluginKeyEvent %i %i %li\n", down, index, (long int)value); using namespace DGL_NAMESPACE; - int special = 0; - switch (value) - { - // convert some VST special values to normal keys - case 1: index = kKeyBackspace; break; - case 2: index = '\t'; break; - // 3 clear - case 4: index = '\r'; break; - case 6: index = kKeyEscape; break; - case 7: index = ' '; break; - // 8 next - // 17 select - // 18 print - case 19: index = '\n'; break; - // 20 snapshot - case 22: index = kKeyDelete; break; - // 23 help - case 57: index = '='; break; - - // numpad stuff follows - case 24: index = '0'; break; - case 25: index = '1'; break; - case 26: index = '2'; break; - case 27: index = '3'; break; - case 28: index = '4'; break; - case 29: index = '5'; break; - case 30: index = '6'; break; - case 31: index = '7'; break; - case 32: index = '8'; break; - case 33: index = '9'; break; - case 34: index = '*'; break; - case 35: index = '+'; break; - // 36 separator - case 37: index = '-'; break; - case 38: index = '.'; break; - case 39: index = '/'; break; - - // handle rest of special keys - /* these special keys are missing: - - kKeySuper - - kKeyCapsLock - - kKeyPrintScreen - */ - case 40: special = kKeyF1; break; - case 41: special = kKeyF2; break; - case 42: special = kKeyF3; break; - case 43: special = kKeyF4; break; - case 44: special = kKeyF5; break; - case 45: special = kKeyF6; break; - case 46: special = kKeyF7; break; - case 47: special = kKeyF8; break; - case 48: special = kKeyF9; break; - case 49: special = kKeyF10; break; - case 50: special = kKeyF11; break; - case 51: special = kKeyF12; break; - case 11: special = kKeyLeft; break; - case 12: special = kKeyUp; break; - case 13: special = kKeyRight; break; - case 14: special = kKeyDown; break; - case 15: special = kKeyPageUp; break; - case 16: special = kKeyPageDown; break; - case 10: special = kKeyHome; break; - case 9: special = kKeyEnd; break; - case 21: special = kKeyInsert; break; - case 54: special = kKeyShift; break; - case 55: special = kKeyControl; break; - case 56: special = kKeyAlt; break; - case 58: special = kKeyMenu; break; - case 52: special = kKeyNumLock; break; - case 53: special = kKeyScrollLock; break; - case 5: special = kKeyPause; break; - } + bool special; + const uint key = translateVstKeyCode(special, index, static_cast<int32_t>(value)); - switch (special) + switch (key) { case kKeyShift: if (down) @@ -355,19 +248,9 @@ public: break; } - if (special != 0) - { - fUI.handlePluginSpecial(down, static_cast<Key>(special), fKeyboardModifiers); - return 1; - } - - if (index > 0) - { - fUI.handlePluginKeyboard(down, static_cast<uint>(index), fKeyboardModifiers); - return 1; - } - - return 0; + return fUI.handlePluginKeyboardVST(down, special, key, + value >= 0 ? static_cast<uint>(value) : 0, + fKeyboardModifiers) ? 1 : 0; } # endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI @@ -486,11 +369,11 @@ class PluginVst : public ParameterAndNotesHelper { public: PluginVst(const audioMasterCallback audioMaster, AEffect* const effect) - : fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback), + : fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback, nullptr), fAudioMaster(audioMaster), fEffect(effect) { - std::memset(fProgramName, 0, sizeof(char)*(32+1)); + std::memset(fProgramName, 0, sizeof(fProgramName)); std::strcpy(fProgramName, "Default"); const uint32_t parameterCount = fPlugin.getParameterCount(); @@ -575,7 +458,7 @@ public: case effSetProgramName: if (char* const programName = (char*)ptr) { - DISTRHO_NAMESPACE::strncpy(fProgramName, programName, 32); + strncpy(fProgramName, programName, 32); return 1; } break; @@ -583,7 +466,7 @@ public: case effGetProgramName: if (char* const programName = (char*)ptr) { - DISTRHO_NAMESPACE::strncpy(programName, fProgramName, 24); + strncpy(programName, fProgramName, 24); return 1; } break; @@ -591,7 +474,7 @@ public: case effGetProgramNameIndexed: if (char* const programName = (char*)ptr) { - DISTRHO_NAMESPACE::strncpy(programName, fProgramName, 24); + strncpy(programName, fProgramName, 24); return 1; } break; @@ -621,14 +504,14 @@ public: if (d_isNotEqual(value, enumValues.values[i].value)) continue; - DISTRHO_NAMESPACE::strncpy((char*)ptr, enumValues.values[i].label.buffer(), 24); + strncpy((char*)ptr, enumValues.values[i].label.buffer(), 24); return 1; } if (hints & kParameterIsInteger) - DISTRHO_NAMESPACE::snprintf_iparam((char*)ptr, (int32_t)value, 24); + snprintf_i32((char*)ptr, (int32_t)value, 24); else - DISTRHO_NAMESPACE::snprintf_param((char*)ptr, value, 24); + snprintf_f32((char*)ptr, value, 24); return 1; } @@ -693,7 +576,7 @@ public: else { UIExporter tmpUI(nullptr, 0, fPlugin.getSampleRate(), - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, d_nextBundlePath, fPlugin.getInstancePointer(), fLastScaleFactor); fVstRect.right = tmpUI.getWidth(); fVstRect.bottom = tmpUI.getHeight(); @@ -725,7 +608,7 @@ public: for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) { const String& key = cit->first; - fStateMap[key] = fPlugin.getState(key); + fStateMap[key] = fPlugin.getStateValue(key); } # endif @@ -801,7 +684,7 @@ public: for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) { const String& key = cit->first; - fStateMap[key] = fPlugin.getState(key); + fStateMap[key] = fPlugin.getStateValue(key); } # endif @@ -980,8 +863,8 @@ public: { const uint32_t hints(fPlugin.getParameterHints(index)); - // must be automable, and not output - if ((hints & kParameterIsAutomable) != 0 && (hints & kParameterIsOutput) == 0) + // must be automatable, and not output + if ((hints & kParameterIsAutomatable) != 0 && (hints & kParameterIsOutput) == 0) return 1; } break; @@ -1020,6 +903,8 @@ public: #else return -1; #endif + if (std::strcmp(canDo, "offline") == 0) + return -1; } break; @@ -1055,7 +940,7 @@ public: void vst_setParameter(const int32_t index, const float value) { - const uint32_t hints(fPlugin.getParameterHints(index)); + const uint32_t hints = fPlugin.getParameterHints(index); const ParameterRanges& ranges(fPlugin.getParameterRanges(index)); // TODO figure out how to detect kVstParameterUsesIntegerMinMax host support, and skip normalization @@ -1099,11 +984,10 @@ public: if (const VstTimeInfo* const vstTimeInfo = (const VstTimeInfo*)hostCallback(audioMasterGetTime, 0, kWantVstTimeFlags)) { - fTimePosition.frame = vstTimeInfo->samplePos; - fTimePosition.playing = (vstTimeInfo->flags & kVstTransportPlaying); - fTimePosition.bbt.valid = ((vstTimeInfo->flags & kVstTempoValid) != 0 || (vstTimeInfo->flags & kVstTimeSigValid) != 0); + fTimePosition.frame = vstTimeInfo->samplePos; + fTimePosition.playing = vstTimeInfo->flags & kVstTransportPlaying; - // ticksPerBeat is not possible with VST + // ticksPerBeat is not possible with VST2 fTimePosition.bbt.ticksPerBeat = 1920.0; if (vstTimeInfo->flags & kVstTempoValid) @@ -1118,6 +1002,7 @@ public: const double barBeats = (std::fmod(ppqPos, ppqPerBar) / ppqPerBar) * vstTimeInfo->timeSigNumerator; const double rest = std::fmod(barBeats, 1.0); + fTimePosition.bbt.valid = true; fTimePosition.bbt.bar = static_cast<int32_t>(ppqPos) / ppqPerBar + 1; fTimePosition.bbt.beat = static_cast<int32_t>(barBeats - rest + 0.5) + 1; fTimePosition.bbt.tick = rest * fTimePosition.bbt.ticksPerBeat; @@ -1133,6 +1018,7 @@ public: } else { + fTimePosition.bbt.valid = false; fTimePosition.bbt.bar = 1; fTimePosition.bbt.beat = 1; fTimePosition.bbt.tick = 0.0; @@ -1193,7 +1079,7 @@ private: AEffect* const fEffect; // Temporary data - char fProgramName[32+1]; + char fProgramName[32]; #if DISTRHO_PLUGIN_WANT_MIDI_INPUT uint32_t fMidiEventCount; @@ -1384,31 +1270,38 @@ struct VstObject { #define vstObjectPtr (VstObject*)effect->object #define pluginPtr (vstObjectPtr)->plugin +static ScopedPointer<PluginExporter> sPlugin; + static intptr_t vst_dispatcherCallback(AEffect* effect, int32_t opcode, int32_t index, intptr_t value, void* ptr, float opt) { // first internal init const bool doInternalInit = (opcode == -1729 && index == 0xdead && value == 0xf00d); - if (doInternalInit) - { - // set valid but dummy values - d_lastBufferSize = 512; - d_lastSampleRate = 44100.0; - d_lastCanRequestParameterValueChanges = true; - } - - // Create dummy plugin to get data from - static PluginExporter plugin(nullptr, nullptr, nullptr); - - if (doInternalInit) + if (doInternalInit || opcode == effOpen) { - // unset - d_lastBufferSize = 0; - d_lastSampleRate = 0.0; - d_lastCanRequestParameterValueChanges = false; + if (sPlugin == nullptr) + { + // set valid but dummy values + d_nextBufferSize = 512; + d_nextSampleRate = 44100.0; + d_nextPluginIsDummy = true; + d_nextCanRequestParameterValueChanges = true; + + // Create dummy plugin to get data from + sPlugin = new PluginExporter(nullptr, nullptr, nullptr, nullptr); + + // unset + d_nextBufferSize = 0; + d_nextSampleRate = 0.0; + d_nextPluginIsDummy = false; + d_nextCanRequestParameterValueChanges = false; + } - *(PluginExporter**)ptr = &plugin; - return 0; + if (doInternalInit) + { + *(PluginExporter**)ptr = sPlugin.get(); + return 0; + } } // handle base opcodes @@ -1426,15 +1319,15 @@ static intptr_t vst_dispatcherCallback(AEffect* effect, int32_t opcode, int32_t audioMasterCallback audioMaster = (audioMasterCallback)obj->audioMaster; - d_lastBufferSize = audioMaster(effect, audioMasterGetBlockSize, 0, 0, nullptr, 0.0f); - d_lastSampleRate = audioMaster(effect, audioMasterGetSampleRate, 0, 0, nullptr, 0.0f); - d_lastCanRequestParameterValueChanges = true; + d_nextBufferSize = audioMaster(effect, audioMasterGetBlockSize, 0, 0, nullptr, 0.0f); + d_nextSampleRate = audioMaster(effect, audioMasterGetSampleRate, 0, 0, nullptr, 0.0f); + d_nextCanRequestParameterValueChanges = true; // some hosts are not ready at this point or return 0 buffersize/samplerate - if (d_lastBufferSize == 0) - d_lastBufferSize = 2048; - if (d_lastSampleRate <= 0.0) - d_lastSampleRate = 44100.0; + if (d_nextBufferSize == 0) + d_nextBufferSize = 2048; + if (d_nextSampleRate <= 0.0) + d_nextSampleRate = 44100.0; obj->plugin = new PluginVst(audioMaster, effect); return 1; @@ -1458,53 +1351,54 @@ static intptr_t vst_dispatcherCallback(AEffect* effect, int32_t opcode, int32_t delete obj; #endif + sPlugin = nullptr; return 1; } //delete effect; return 0; case effGetParamLabel: - if (ptr != nullptr && index < static_cast<int32_t>(plugin.getParameterCount())) + if (ptr != nullptr && index < static_cast<int32_t>(sPlugin->getParameterCount())) { - DISTRHO_NAMESPACE::strncpy((char*)ptr, plugin.getParameterUnit(index), 8); + strncpy((char*)ptr, sPlugin->getParameterUnit(index), 8); return 1; } return 0; case effGetParamName: - if (ptr != nullptr && index < static_cast<int32_t>(plugin.getParameterCount())) + if (ptr != nullptr && index < static_cast<int32_t>(sPlugin->getParameterCount())) { - const String& shortName(plugin.getParameterShortName(index)); + const String& shortName(sPlugin->getParameterShortName(index)); if (shortName.isNotEmpty()) - DISTRHO_NAMESPACE::strncpy((char*)ptr, shortName, 16); + strncpy((char*)ptr, shortName, 16); else - DISTRHO_NAMESPACE::strncpy((char*)ptr, plugin.getParameterName(index), 16); + strncpy((char*)ptr, sPlugin->getParameterName(index), 16); return 1; } return 0; case effGetParameterProperties: - if (ptr != nullptr && index < static_cast<int32_t>(plugin.getParameterCount())) + if (ptr != nullptr && index < static_cast<int32_t>(sPlugin->getParameterCount())) { if (VstParameterProperties* const properties = (VstParameterProperties*)ptr) { memset(properties, 0, sizeof(VstParameterProperties)); // full name - DISTRHO_NAMESPACE::strncpy(properties->label, - plugin.getParameterName(index), - sizeof(properties->label)); + strncpy(properties->label, + sPlugin->getParameterName(index), + sizeof(properties->label)); // short name - const String& shortName(plugin.getParameterShortName(index)); + const String& shortName(sPlugin->getParameterShortName(index)); if (shortName.isNotEmpty()) - DISTRHO_NAMESPACE::strncpy(properties->shortLabel, - plugin.getParameterShortName(index), - sizeof(properties->shortLabel)); + strncpy(properties->shortLabel, + sPlugin->getParameterShortName(index), + sizeof(properties->shortLabel)); // parameter hints - const uint32_t hints = plugin.getParameterHints(index); + const uint32_t hints = sPlugin->getParameterHints(index); if (hints & kParameterIsOutput) return 1; @@ -1516,7 +1410,7 @@ static intptr_t vst_dispatcherCallback(AEffect* effect, int32_t opcode, int32_t if (hints & kParameterIsInteger) { - const ParameterRanges& ranges(plugin.getParameterRanges(index)); + const ParameterRanges& ranges(sPlugin->getParameterRanges(index)); properties->flags |= kVstParameterUsesIntegerMinMax; properties->minInteger = static_cast<int32_t>(ranges.min); properties->maxInteger = static_cast<int32_t>(ranges.max); @@ -1528,29 +1422,29 @@ static intptr_t vst_dispatcherCallback(AEffect* effect, int32_t opcode, int32_t } // parameter group (category in vst) - const uint32_t groupId = plugin.getParameterGroupId(index); + const uint32_t groupId = sPlugin->getParameterGroupId(index); if (groupId != kPortGroupNone) { // we can't use groupId directly, so use the index array where this group is stored in - for (uint32_t i=0, count=plugin.getPortGroupCount(); i < count; ++i) + for (uint32_t i=0, count=sPlugin->getPortGroupCount(); i < count; ++i) { - const PortGroupWithId& portGroup(plugin.getPortGroupByIndex(i)); + const PortGroupWithId& portGroup(sPlugin->getPortGroupByIndex(i)); if (portGroup.groupId == groupId) { properties->category = i + 1; - DISTRHO_NAMESPACE::strncpy(properties->categoryLabel, - portGroup.name.buffer(), - sizeof(properties->categoryLabel)); + strncpy(properties->categoryLabel, + portGroup.name.buffer(), + sizeof(properties->categoryLabel)); break; } } if (properties->category != 0) { - for (uint32_t i=0, count=plugin.getParameterCount(); i < count; ++i) - if (plugin.getParameterGroupId(i) == groupId) + for (uint32_t i=0, count=sPlugin->getParameterCount(); i < count; ++i) + if (sPlugin->getParameterGroupId(i) == groupId) ++properties->numParametersInCategory; } } @@ -1570,7 +1464,7 @@ static intptr_t vst_dispatcherCallback(AEffect* effect, int32_t opcode, int32_t case effGetEffectName: if (char* const cptr = (char*)ptr) { - DISTRHO_NAMESPACE::strncpy(cptr, plugin.getName(), 32); + strncpy(cptr, sPlugin->getName(), 32); return 1; } return 0; @@ -1578,7 +1472,7 @@ static intptr_t vst_dispatcherCallback(AEffect* effect, int32_t opcode, int32_t case effGetVendorString: if (char* const cptr = (char*)ptr) { - DISTRHO_NAMESPACE::strncpy(cptr, plugin.getMaker(), 32); + strncpy(cptr, sPlugin->getMaker(), 32); return 1; } return 0; @@ -1586,13 +1480,13 @@ static intptr_t vst_dispatcherCallback(AEffect* effect, int32_t opcode, int32_t case effGetProductString: if (char* const cptr = (char*)ptr) { - DISTRHO_NAMESPACE::strncpy(cptr, plugin.getLabel(), 32); + strncpy(cptr, sPlugin->getLabel(), 32); return 1; } return 0; case effGetVendorVersion: - return plugin.getVersion(); + return sPlugin->getVersion(); case effGetVstVersion: return kVstVersion; @@ -1635,12 +1529,26 @@ static void vst_processReplacingCallback(AEffect* effect, float** inputs, float* #undef validPlugin #undef vstObjectPtr +static struct Cleanup { + std::vector<AEffect*> effects; + + ~Cleanup() + { + for (std::vector<AEffect*>::iterator it = effects.begin(), end = effects.end(); it != end; ++it) + { + AEffect* const effect = *it; + delete (VstObject*)effect->object; + delete effect; + } + } +} sCleanup; + // ----------------------------------------------------------------------- END_NAMESPACE_DISTRHO DISTRHO_PLUGIN_EXPORT -#if DISTRHO_OS_WINDOWS || DISTRHO_OS_MAC +#if DISTRHO_OS_MAC || DISTRHO_OS_WASM || DISTRHO_OS_WINDOWS const AEffect* VSTPluginMain(audioMasterCallback audioMaster); #else const AEffect* VSTPluginMain(audioMasterCallback audioMaster) asm ("main"); @@ -1655,6 +1563,32 @@ const AEffect* VSTPluginMain(audioMasterCallback audioMaster) if (audioMaster(nullptr, audioMasterVersion, 0, 0, nullptr, 0.0f) == 0) return nullptr; + // find plugin bundle + static String bundlePath; + if (bundlePath.isEmpty()) + { + String tmpPath(getBinaryFilename()); + tmpPath.truncate(tmpPath.rfind(DISTRHO_OS_SEP)); +#ifdef DISTRHO_OS_MAC + if (tmpPath.endsWith("/MacOS")) + { + tmpPath.truncate(tmpPath.rfind('/')); + if (tmpPath.endsWith("/Contents")) + { + tmpPath.truncate(tmpPath.rfind('/')); + bundlePath = tmpPath; + d_nextBundlePath = bundlePath.buffer(); + } + } +#else + if (tmpPath.endsWith(".vst")) + { + bundlePath = tmpPath; + d_nextBundlePath = bundlePath.buffer(); + } +#endif + } + // first internal init PluginExporter* plugin = nullptr; vst_dispatcherCallback(nullptr, -1729, 0xdead, 0xf00d, &plugin, 0.0f); @@ -1714,12 +1648,13 @@ const AEffect* VSTPluginMain(audioMasterCallback audioMaster) effect->processReplacing = vst_processReplacingCallback; // pointers - VstObject* const obj(new VstObject()); + VstObject* const obj = new VstObject(); obj->audioMaster = audioMaster; obj->plugin = nullptr; // done effect->object = obj; + sCleanup.effects.push_back(effect); return effect; } diff --git a/distrho/src/DistrhoPluginVST3.cpp b/distrho/src/DistrhoPluginVST3.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -15,282 +15,4804 @@ */ #include "DistrhoPluginInternal.hpp" +#include "../DistrhoPluginUtils.hpp" #include "../extra/ScopedPointer.hpp" +#define DPF_VST3_MAX_BUFFER_SIZE 32768 +#define DPF_VST3_MAX_SAMPLE_RATE 384000 +#define DPF_VST3_MAX_LATENCY DPF_VST3_MAX_SAMPLE_RATE * 10 + +#if DISTRHO_PLUGIN_HAS_UI +# include "../extra/RingBuffer.hpp" +#endif + #include "travesty/audio_processor.h" #include "travesty/component.h" #include "travesty/edit_controller.h" #include "travesty/factory.h" +#include "travesty/host.h" + +#include <map> +#include <string> +#include <vector> + +/* TODO items: + * == parameters + * - test parameter triggers + * - have parameter outputs host-provided UI working in at least 1 host + * - parameter groups via unit ids + * - test parameter changes from DSP (aka requestParameterValueChange) + * - implement getParameterNormalized/setParameterNormalized for MIDI CC params ? + * - fully implemented parameter stuff and verify + * - float to int safe casting + * - verify that latency changes works (with and without DPF_VST3_USES_SEPARATE_CONTROLLER) + * == MIDI + * - MIDI CC changes (need to store value to give to the host?) + * - MIDI program changes + * - MIDI sysex + * == BUSES + * - routing info, do we care? + * == CV + * - cv scaling to -1/+1 + * - test in at least 1 host + * == INFO + * - set factory email (needs new DPF API, useful for LV2 as well) + * - do something with set_io_mode? + */ + +START_NAMESPACE_DISTRHO + +// -------------------------------------------------------------------------------------------------------------------- + +#if ! DISTRHO_PLUGIN_WANT_MIDI_OUTPUT +static constexpr const writeMidiFunc writeMidiCallback = nullptr; +#endif +#if ! DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST +static constexpr const requestParameterValueChangeFunc requestParameterValueChangeCallback = nullptr; +#endif + +typedef std::map<const String, String> StringMap; + +// -------------------------------------------------------------------------------------------------------------------- +// custom v3_tuid compatible type + +typedef uint32_t dpf_tuid[4]; +#ifdef DISTRHO_PROPER_CPP11_SUPPORT +static_assert(sizeof(v3_tuid) == sizeof(dpf_tuid), "uid size mismatch"); +#endif + +// -------------------------------------------------------------------------------------------------------------------- +// custom, constant uids related to DPF + +static constexpr const uint32_t dpf_id_entry = d_cconst('D', 'P', 'F', ' '); +static constexpr const uint32_t dpf_id_clas = d_cconst('c', 'l', 'a', 's'); +static constexpr const uint32_t dpf_id_comp = d_cconst('c', 'o', 'm', 'p'); +static constexpr const uint32_t dpf_id_ctrl = d_cconst('c', 't', 'r', 'l'); +static constexpr const uint32_t dpf_id_proc = d_cconst('p', 'r', 'o', 'c'); +static constexpr const uint32_t dpf_id_view = d_cconst('v', 'i', 'e', 'w'); + +// -------------------------------------------------------------------------------------------------------------------- +// plugin specific uids (values are filled in during plugin init) + +static dpf_tuid dpf_tuid_class = { dpf_id_entry, dpf_id_clas, 0, 0 }; +static dpf_tuid dpf_tuid_component = { dpf_id_entry, dpf_id_comp, 0, 0 }; +static dpf_tuid dpf_tuid_controller = { dpf_id_entry, dpf_id_ctrl, 0, 0 }; +static dpf_tuid dpf_tuid_processor = { dpf_id_entry, dpf_id_proc, 0, 0 }; +static dpf_tuid dpf_tuid_view = { dpf_id_entry, dpf_id_view, 0, 0 }; + +// -------------------------------------------------------------------------------------------------------------------- +// Utility functions + +const char* tuid2str(const v3_tuid iid) +{ + static constexpr const struct { + v3_tuid iid; + const char* name; + } extra_known_iids[] = { + { V3_ID(0x00000000,0x00000000,0x00000000,0x00000000), "(nil)" }, + // edit-controller + { V3_ID(0xF040B4B3,0xA36045EC,0xABCDC045,0xB4D5A2CC), "{v3_component_handler2|NOT}" }, + { V3_ID(0x7F4EFE59,0xF3204967,0xAC27A3AE,0xAFB63038), "{v3_edit_controller2|NOT}" }, + { V3_ID(0x067D02C1,0x5B4E274D,0xA92D90FD,0x6EAF7240), "{v3_component_handler_bus_activation|NOT}" }, + { V3_ID(0xC1271208,0x70594098,0xB9DD34B3,0x6BB0195E), "{v3_edit_controller_host_editing|NOT}" }, + { V3_ID(0xB7F8F859,0x41234872,0x91169581,0x4F3721A3), "{v3_edit_controller_note_expression_controller|NOT}" }, + // units + { V3_ID(0x8683B01F,0x7B354F70,0xA2651DEC,0x353AF4FF), "{v3_program_list_data|NOT}" }, + { V3_ID(0x6C389611,0xD391455D,0xB870B833,0x94A0EFDD), "{v3_unit_data|NOT}" }, + { V3_ID(0x4B5147F8,0x4654486B,0x8DAB30BA,0x163A3C56), "{v3_unit_handler|NOT}" }, + { V3_ID(0xF89F8CDF,0x699E4BA5,0x96AAC9A4,0x81452B01), "{v3_unit_handler2|NOT}" }, + { V3_ID(0x3D4BD6B5,0x913A4FD2,0xA886E768,0xA5EB92C1), "{v3_unit_info|NOT}" }, + // misc + { V3_ID(0x309ECE78,0xEB7D4FAE,0x8B2225D9,0x09FD08B6), "{v3_audio_presentation_latency|NOT}" }, + { V3_ID(0xB4E8287F,0x1BB346AA,0x83A46667,0x68937BAB), "{v3_automation_state|NOT}" }, + { V3_ID(0x0F194781,0x8D984ADA,0xBBA0C1EF,0xC011D8D0), "{v3_info_listener|NOT}" }, + { V3_ID(0x6D21E1DC,0x91199D4B,0xA2A02FEF,0x6C1AE55C), "{v3_parameter_function_name|NOT}" }, + { V3_ID(0x8AE54FDA,0xE93046B9,0xA28555BC,0xDC98E21E), "{v3_prefetchable_support|NOT}" }, + { V3_ID(0xA81A0471,0x48C34DC4,0xAC30C9E1,0x3C8393D5), "{v3_xml_representation_stream|NOT}" }, + /* + // seen in the wild but unknown, related to component + { V3_ID(0x6548D671,0x997A4EA5,0x9B336A6F,0xB3E93B50), "{v3_|NOT}" }, + { V3_ID(0xC2B7896B,0x069844D5,0x8F06E937,0x33A35FF7), "{v3_|NOT}" }, + { V3_ID(0xE123DE93,0xE0F642A4,0xAE53867E,0x53F059EE), "{v3_|NOT}" }, + { V3_ID(0x83850D7B,0xC12011D8,0xA143000A,0x959B31C6), "{v3_|NOT}" }, + { V3_ID(0x9598D418,0xA00448AC,0x9C6D8248,0x065B2E5C), "{v3_|NOT}" }, + { V3_ID(0xBD386132,0x45174BAD,0xA324390B,0xFD297506), "{v3_|NOT}" }, + { V3_ID(0xD7296A84,0x23B1419C,0xAAD0FAA3,0x53BB16B7), "{v3_|NOT}" }, + { V3_ID(0x181A0AF6,0xA10947BA,0x8A6F7C7C,0x3FF37129), "{v3_|NOT}" }, + { V3_ID(0xC2B7896B,0x69A844D5,0x8F06E937,0x33A35FF7), "{v3_|NOT}" }, + // seen in the wild but unknown, related to edit controller + { V3_ID(0x1F2F76D3,0xBFFB4B96,0xB99527A5,0x5EBCCEF4), "{v3_|NOT}" }, + { V3_ID(0x6B2449CC,0x419740B5,0xAB3C79DA,0xC5FE5C86), "{v3_|NOT}" }, + { V3_ID(0x67800560,0x5E784D90,0xB97BAB4C,0x8DC5BAA3), "{v3_|NOT}" }, + { V3_ID(0xDB51DA00,0x8FD5416D,0xB84894D8,0x7FDE73E4), "{v3_|NOT}" }, + { V3_ID(0xE90FC54F,0x76F24235,0x8AF8BD15,0x68C663D6), "{v3_|NOT}" }, + { V3_ID(0x07938E89,0xBA0D4CA8,0x8C7286AB,0xA9DDA95B), "{v3_|NOT}" }, + { V3_ID(0x42879094,0xA2F145ED,0xAC90E82A,0x99458870), "{v3_|NOT}" }, + { V3_ID(0xC3B17BC0,0x2C174494,0x80293402,0xFBC4BBF8), "{v3_|NOT}" }, + { V3_ID(0x31E29A7A,0xE55043AD,0x8B95B9B8,0xDA1FBE1E), "{v3_|NOT}" }, + { V3_ID(0x8E3C292C,0x95924F9D,0xB2590B1E,0x100E4198), "{v3_|NOT}" }, + { V3_ID(0x50553FD9,0x1D2C4C24,0xB410F484,0xC5FB9F3F), "{v3_|NOT}" }, + { V3_ID(0xF185556C,0x5EE24FC7,0x92F28754,0xB7759EA8), "{v3_|NOT}" }, + { V3_ID(0xD2CE9317,0xF24942C9,0x9742E82D,0xB10CCC52), "{v3_|NOT}" }, + { V3_ID(0xDA57E6D1,0x1F3242D1,0xAD9C1A82,0xFDB95695), "{v3_|NOT}" }, + { V3_ID(0x3ABDFC3E,0x4B964A66,0xFCD86F10,0x0D554023), "{v3_|NOT}" }, + // seen in the wild but unknown, related to view + { V3_ID(0xAA3E50FF,0xB78840EE,0xADCD48E8,0x094CEDB7), "{v3_|NOT}" }, + { V3_ID(0x2CAE14DB,0x4DE04C6E,0x8BD2E611,0x1B31A9C2), "{v3_|NOT}" }, + { V3_ID(0xD868D61D,0x20F445F4,0x947D069E,0xC811D1E4), "{v3_|NOT}" }, + { V3_ID(0xEE49E3CA,0x6FCB44FB,0xAEBEE6C3,0x48625122), "{v3_|NOT}" }, + */ + }; + + if (v3_tuid_match(iid, v3_audio_processor_iid)) + return "{v3_audio_processor}"; + if (v3_tuid_match(iid, v3_attribute_list_iid)) + return "{v3_attribute_list_iid}"; + if (v3_tuid_match(iid, v3_bstream_iid)) + return "{v3_bstream}"; + if (v3_tuid_match(iid, v3_component_iid)) + return "{v3_component}"; + if (v3_tuid_match(iid, v3_component_handler_iid)) + return "{v3_component_handler}"; + if (v3_tuid_match(iid, v3_connection_point_iid)) + return "{v3_connection_point_iid}"; + if (v3_tuid_match(iid, v3_edit_controller_iid)) + return "{v3_edit_controller}"; + if (v3_tuid_match(iid, v3_event_handler_iid)) + return "{v3_event_handler_iid}"; + if (v3_tuid_match(iid, v3_event_list_iid)) + return "{v3_event_list}"; + if (v3_tuid_match(iid, v3_funknown_iid)) + return "{v3_funknown}"; + if (v3_tuid_match(iid, v3_host_application_iid)) + return "{v3_host_application_iid}"; + if (v3_tuid_match(iid, v3_message_iid)) + return "{v3_message_iid}"; + if (v3_tuid_match(iid, v3_midi_mapping_iid)) + return "{v3_midi_mapping_iid}"; + if (v3_tuid_match(iid, v3_param_value_queue_iid)) + return "{v3_param_value_queue}"; + if (v3_tuid_match(iid, v3_param_changes_iid)) + return "{v3_param_changes}"; + if (v3_tuid_match(iid, v3_plugin_base_iid)) + return "{v3_plugin_base}"; + if (v3_tuid_match(iid, v3_plugin_factory_iid)) + return "{v3_plugin_factory}"; + if (v3_tuid_match(iid, v3_plugin_factory_2_iid)) + return "{v3_plugin_factory_2}"; + if (v3_tuid_match(iid, v3_plugin_factory_3_iid)) + return "{v3_plugin_factory_3}"; + if (v3_tuid_match(iid, v3_plugin_frame_iid)) + return "{v3_plugin_frame}"; + if (v3_tuid_match(iid, v3_plugin_view_iid)) + return "{v3_plugin_view}"; + if (v3_tuid_match(iid, v3_plugin_view_content_scale_iid)) + return "{v3_plugin_view_content_scale_iid}"; + if (v3_tuid_match(iid, v3_plugin_view_parameter_finder_iid)) + return "{v3_plugin_view_parameter_finder}"; + if (v3_tuid_match(iid, v3_process_context_requirements_iid)) + return "{v3_process_context_requirements}"; + if (v3_tuid_match(iid, v3_run_loop_iid)) + return "{v3_run_loop_iid}"; + if (v3_tuid_match(iid, v3_timer_handler_iid)) + return "{v3_timer_handler_iid}"; + + if (std::memcmp(iid, dpf_tuid_class, sizeof(dpf_tuid)) == 0) + return "{dpf_tuid_class}"; + if (std::memcmp(iid, dpf_tuid_component, sizeof(dpf_tuid)) == 0) + return "{dpf_tuid_component}"; + if (std::memcmp(iid, dpf_tuid_controller, sizeof(dpf_tuid)) == 0) + return "{dpf_tuid_controller}"; + if (std::memcmp(iid, dpf_tuid_processor, sizeof(dpf_tuid)) == 0) + return "{dpf_tuid_processor}"; + if (std::memcmp(iid, dpf_tuid_view, sizeof(dpf_tuid)) == 0) + return "{dpf_tuid_view}"; + + for (size_t i=0; i<ARRAY_SIZE(extra_known_iids); ++i) + { + if (v3_tuid_match(iid, extra_known_iids[i].iid)) + return extra_known_iids[i].name; + } + + static char buf[46]; + std::snprintf(buf, sizeof(buf), "{0x%08X,0x%08X,0x%08X,0x%08X}", + (uint32_t)d_cconst(iid[ 0], iid[ 1], iid[ 2], iid[ 3]), + (uint32_t)d_cconst(iid[ 4], iid[ 5], iid[ 6], iid[ 7]), + (uint32_t)d_cconst(iid[ 8], iid[ 9], iid[10], iid[11]), + (uint32_t)d_cconst(iid[12], iid[13], iid[14], iid[15])); + return buf; +} + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_plugin_view_create (implemented on UI side) + +v3_plugin_view** dpf_plugin_view_create(v3_host_application** host, void* instancePointer, double sampleRate); + +// -------------------------------------------------------------------------------------------------------------------- + +/** + * VST3 DSP class. + * + * All the dynamic things from VST3 get implemented here, free of complex low-level VST3 pointer things. + * This class is created during the "initialize" component event, and destroyed during "terminate". + * + * The low-level VST3 stuff comes after. + */ +class PluginVst3 +{ + /* Buses: count possible buses we can provide to the host, in case they are not yet defined by the developer. + * These values are only used if port groups aren't set. + * + * When port groups are not in use: + * - 1 bus is provided for the main audio (if there is any) + * - 1 for sidechain + * - 1 for each cv port + * So basically: + * Main audio is used as first bus, if available. + * Then sidechain, also if available. + * And finally each CV port individually. + * + * MIDI will have a single bus, nothing special there. + */ + struct BusInfo { + uint8_t audio; // either 0 or 1 + uint8_t sidechain; // either 0 or 1 + uint32_t groups; + uint32_t audioPorts; + uint32_t sidechainPorts; + uint32_t groupPorts; + uint32_t cvPorts; + + BusInfo() + : audio(0), + sidechain(0), + groups(0), + audioPorts(0), + sidechainPorts(0), + groupPorts(0), + cvPorts(0) {} + } inputBuses, outputBuses; + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + /* Handy class for storing and sorting VST3 events and MIDI CC parameters. + * It will only store events for which a MIDI conversion is possible. + */ + struct InputEventList { + enum Type { + NoteOn, + NoteOff, + SysexData, + PolyPressure, + CC_Normal, + CC_ChannelPressure, + CC_Pitchbend, + UI_MIDI // event from UI + }; + struct InputEventStorage { + Type type; + union { + v3_event_note_on noteOn; + v3_event_note_off noteOff; + v3_event_data sysexData; + v3_event_poly_pressure polyPressure; + uint8_t midi[3]; + }; + } eventListStorage[kMaxMidiEvents]; + + struct InputEvent { + int32_t sampleOffset; + const InputEventStorage* storage; + InputEvent* next; + } eventList[kMaxMidiEvents]; + + uint16_t numUsed; + int32_t firstSampleOffset; + int32_t lastSampleOffset; + InputEvent* firstEvent; + InputEvent* lastEvent; + + void init() + { + numUsed = 0; + firstSampleOffset = lastSampleOffset = 0; + firstEvent = nullptr; + } + + uint32_t convert(MidiEvent midiEvents[kMaxMidiEvents]) const noexcept + { + uint32_t count = 0; + + for (const InputEvent* event = firstEvent; event != nullptr; event = event->next) + { + MidiEvent& midiEvent(midiEvents[count++]); + midiEvent.frame = event->sampleOffset; + + const InputEventStorage& eventStorage(*event->storage); + + switch (eventStorage.type) + { + case NoteOn: + midiEvent.size = 3; + midiEvent.data[0] = 0x90 | (eventStorage.noteOn.channel & 0xf); + midiEvent.data[1] = eventStorage.noteOn.pitch; + midiEvent.data[2] = std::max(0, std::min(127, (int)(eventStorage.noteOn.velocity * 127))); + midiEvent.data[3] = 0; + break; + case NoteOff: + midiEvent.size = 3; + midiEvent.data[0] = 0x80 | (eventStorage.noteOff.channel & 0xf); + midiEvent.data[1] = eventStorage.noteOff.pitch; + midiEvent.data[2] = std::max(0, std::min(127, (int)(eventStorage.noteOff.velocity * 127))); + midiEvent.data[3] = 0; + break; + /* TODO + case SysexData: + break; + */ + case PolyPressure: + midiEvent.size = 3; + midiEvent.data[0] = 0xA0 | (eventStorage.polyPressure.channel & 0xf); + midiEvent.data[1] = eventStorage.polyPressure.pitch; + midiEvent.data[2] = std::max(0, std::min(127, (int)(eventStorage.polyPressure.pressure * 127))); + midiEvent.data[3] = 0; + break; + case CC_Normal: + midiEvent.size = 3; + midiEvent.data[0] = 0xB0 | (eventStorage.midi[0] & 0xf); + midiEvent.data[1] = eventStorage.midi[1]; + midiEvent.data[2] = eventStorage.midi[2]; + break; + case CC_ChannelPressure: + midiEvent.size = 2; + midiEvent.data[0] = 0xD0 | (eventStorage.midi[0] & 0xf); + midiEvent.data[1] = eventStorage.midi[1]; + midiEvent.data[2] = 0; + break; + case CC_Pitchbend: + midiEvent.size = 3; + midiEvent.data[0] = 0xE0 | (eventStorage.midi[0] & 0xf); + midiEvent.data[1] = eventStorage.midi[1]; + midiEvent.data[2] = eventStorage.midi[2]; + break; + case UI_MIDI: + midiEvent.size = 3; + midiEvent.data[0] = eventStorage.midi[0]; + midiEvent.data[1] = eventStorage.midi[1]; + midiEvent.data[2] = eventStorage.midi[2]; + break; + default: + midiEvent.size = 0; + break; + } + } + + return count; + } + + bool appendEvent(const v3_event& event) noexcept + { + // only save events that can be converted directly into MIDI + switch (event.type) + { + case V3_EVENT_NOTE_ON: + case V3_EVENT_NOTE_OFF: + // case V3_EVENT_DATA: + case V3_EVENT_POLY_PRESSURE: + break; + default: + return false; + } + + InputEventStorage& eventStorage(eventListStorage[numUsed]); + + switch (event.type) + { + case V3_EVENT_NOTE_ON: + eventStorage.type = NoteOn; + eventStorage.noteOn = event.note_on; + break; + case V3_EVENT_NOTE_OFF: + eventStorage.type = NoteOff; + eventStorage.noteOff = event.note_off; + break; + case V3_EVENT_DATA: + eventStorage.type = SysexData; + eventStorage.sysexData = event.data; + break; + case V3_EVENT_POLY_PRESSURE: + eventStorage.type = PolyPressure; + eventStorage.polyPressure = event.poly_pressure; + break; + default: + return false; + } + + eventList[numUsed].sampleOffset = event.sample_offset; + eventList[numUsed].storage = &eventStorage; + + return placeSorted(event.sample_offset); + } + + bool appendCC(const int32_t sampleOffset, v3_param_id paramId, const double value) noexcept + { + InputEventStorage& eventStorage(eventListStorage[numUsed]); + + paramId -= kVst3InternalParameterMidiCC_start; + + const uint8_t cc = paramId % 130; + + switch (cc) + { + case 128: + eventStorage.type = CC_ChannelPressure; + eventStorage.midi[1] = std::max(0, std::min(127, (int)(value * 127))); + eventStorage.midi[2] = 0; + break; + case 129: + eventStorage.type = CC_Pitchbend; + eventStorage.midi[1] = std::max(0, std::min(16384, (int)(value * 16384))) & 0x7f; + eventStorage.midi[2] = std::max(0, std::min(16384, (int)(value * 16384))) >> 7; + break; + default: + eventStorage.type = CC_Normal; + eventStorage.midi[1] = cc; + eventStorage.midi[2] = std::max(0, std::min(127, (int)(value * 127))); + break; + } + + eventStorage.midi[0] = paramId / 130; + + eventList[numUsed].sampleOffset = sampleOffset; + eventList[numUsed].storage = &eventStorage; + + return placeSorted(sampleOffset); + } + + #if DISTRHO_PLUGIN_HAS_UI + // NOTE always runs first + bool appendFromUI(const uint8_t midiData[3]) + { + InputEventStorage& eventStorage(eventListStorage[numUsed]); + + eventStorage.type = UI_MIDI; + std::memcpy(eventStorage.midi, midiData, sizeof(uint8_t)*3); + + InputEvent* const event = &eventList[numUsed]; + + event->sampleOffset = 0; + event->storage = &eventStorage; + event->next = nullptr; + + if (numUsed == 0) + { + firstEvent = lastEvent = event; + } + else + { + lastEvent->next = event; + lastEvent = event; + } + + return ++numUsed == kMaxMidiEvents; + } + #endif + + private: + bool placeSorted(const int32_t sampleOffset) noexcept + { + InputEvent* const event = &eventList[numUsed]; + + // initialize + if (numUsed == 0) + { + firstSampleOffset = lastSampleOffset = sampleOffset; + firstEvent = lastEvent = event; + event->next = nullptr; + } + // push to the back + else if (sampleOffset >= lastSampleOffset) + { + lastSampleOffset = sampleOffset; + lastEvent->next = event; + lastEvent = event; + event->next = nullptr; + } + // push to the front + else if (sampleOffset < firstSampleOffset) + { + firstSampleOffset = sampleOffset; + event->next = firstEvent; + firstEvent = event; + } + // find place in between events + else + { + // keep reference out of the loop so we can check validity afterwards + InputEvent* event2 = firstEvent; + + // iterate all events + for (; event2 != nullptr; event2 = event2->next) + { + // if offset is higher than iterated event, stop and insert in-between + if (sampleOffset > event2->sampleOffset) + break; + + // if offset matches, find the last event with the same offset so we can push after it + if (sampleOffset == event2->sampleOffset) + { + event2 = event2->next; + for (; event2 != nullptr && sampleOffset == event2->sampleOffset; event2 = event2->next) {} + break; + } + } + + DISTRHO_SAFE_ASSERT_RETURN(event2 != nullptr, true); + + event->next = event2->next; + event2->next = event; + } + + return ++numUsed == kMaxMidiEvents; + } + } inputEventList; + #endif // DISTRHO_PLUGIN_WANT_MIDI_INPUT + +public: + PluginVst3(v3_host_application** const host) + : fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback, nullptr), + fComponentHandler(nullptr), + #if DISTRHO_PLUGIN_HAS_UI + #if DPF_VST3_USES_SEPARATE_CONTROLLER + fConnectionFromCompToCtrl(nullptr), + #endif + fConnectionFromCtrlToView(nullptr), + fHostApplication(host), + #endif + fParameterCount(fPlugin.getParameterCount()), + fVst3ParameterCount(fParameterCount + kVst3InternalParameterCount), + fCachedParameterValues(nullptr), + fDummyAudioBuffer(nullptr), + fParameterValuesChangedDuringProcessing(nullptr) + #if DISTRHO_PLUGIN_HAS_UI + , fParameterValueChangesForUI(nullptr) + , fConnectedToUI(false) + #endif + #if DISTRHO_PLUGIN_WANT_LATENCY + , fLastKnownLatency(fPlugin.getLatency()) + #endif + #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + , fHostEventOutputHandle(nullptr) + #endif + #if DISTRHO_PLUGIN_WANT_PROGRAMS + , fCurrentProgram(0) + , fProgramCountMinusOne(fPlugin.getProgramCount()-1) + #endif + { + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + std::memset(fEnabledInputs, 0, sizeof(fEnabledInputs)); + fillInBusInfoDetails<true>(); + #endif + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + std::memset(fEnabledOutputs, 0, sizeof(fEnabledOutputs)); + fillInBusInfoDetails<false>(); + #endif + + if (const uint32_t extraParameterCount = fParameterCount + kVst3InternalParameterBaseCount) + { + fCachedParameterValues = new float[extraParameterCount]; + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + fCachedParameterValues[kVst3InternalParameterBufferSize] = fPlugin.getBufferSize(); + fCachedParameterValues[kVst3InternalParameterSampleRate] = fPlugin.getSampleRate(); + #endif + #if DISTRHO_PLUGIN_WANT_LATENCY + fCachedParameterValues[kVst3InternalParameterLatency] = fLastKnownLatency; + #endif + #if DISTRHO_PLUGIN_WANT_PROGRAMS + fCachedParameterValues[kVst3InternalParameterProgram] = 0.0f; + #endif + + for (uint32_t i=0; i < fParameterCount; ++i) + fCachedParameterValues[kVst3InternalParameterBaseCount + i] = fPlugin.getParameterDefault(i); + + fParameterValuesChangedDuringProcessing = new bool[extraParameterCount]; + std::memset(fParameterValuesChangedDuringProcessing, 0, sizeof(bool)*extraParameterCount); + + #if DISTRHO_PLUGIN_HAS_UI + fParameterValueChangesForUI = new bool[extraParameterCount]; + std::memset(fParameterValueChangesForUI, 0, sizeof(bool)*extraParameterCount); + #endif + } + + #if DISTRHO_PLUGIN_WANT_STATE + for (uint32_t i=0, count=fPlugin.getStateCount(); i<count; ++i) + { + const String& dkey(fPlugin.getStateKey(i)); + fStateMap[dkey] = fPlugin.getStateDefaultValue(i); + } + #endif + + #if !DISTRHO_PLUGIN_HAS_UI + // unused + return; (void)host; + #endif + } + + ~PluginVst3() + { + if (fCachedParameterValues != nullptr) + { + delete[] fCachedParameterValues; + fCachedParameterValues = nullptr; + } + + if (fDummyAudioBuffer != nullptr) + { + delete[] fDummyAudioBuffer; + fDummyAudioBuffer = nullptr; + } + + if (fParameterValuesChangedDuringProcessing != nullptr) + { + delete[] fParameterValuesChangedDuringProcessing; + fParameterValuesChangedDuringProcessing = nullptr; + } + + #if DISTRHO_PLUGIN_HAS_UI + if (fParameterValueChangesForUI != nullptr) + { + delete[] fParameterValueChangesForUI; + fParameterValueChangesForUI = nullptr; + } + #endif + } + + // ---------------------------------------------------------------------------------------------------------------- + // utilities and common code + + void setNormalizedPluginParameterValue(const uint32_t index, const float normalized) + { + const ParameterRanges& ranges(fPlugin.getParameterRanges(index)); + const uint32_t hints = fPlugin.getParameterHints(index); + float value = ranges.getUnnormalizedValue(normalized); + + if (hints & kParameterIsBoolean) + { + const float midRange = ranges.min + (ranges.max - ranges.min) / 2.0f; + value = value > midRange ? ranges.max : ranges.min; + } + else if (hints & kParameterIsInteger) + { + value = std::round(value); + } + + fCachedParameterValues[kVst3InternalParameterBaseCount + index] = value; + #if DISTRHO_PLUGIN_HAS_UI + fParameterValueChangesForUI[kVst3InternalParameterBaseCount + index] = true; + #endif + fPlugin.setParameterValue(index, value); + } + + // ---------------------------------------------------------------------------------------------------------------- + // stuff called for UI creation + + void* getInstancePointer() const noexcept + { + return fPlugin.getInstancePointer(); + } + + double getSampleRate() const noexcept + { + return fPlugin.getSampleRate(); + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_component interface calls + + int32_t getBusCount(const int32_t mediaType, const int32_t busDirection) const noexcept + { + switch (mediaType) + { + case V3_AUDIO: + if (busDirection == V3_INPUT) + return inputBuses.audio + inputBuses.sidechain + inputBuses.groups + inputBuses.cvPorts; + if (busDirection == V3_OUTPUT) + return outputBuses.audio + outputBuses.sidechain + outputBuses.groups + outputBuses.cvPorts; + break; + case V3_EVENT: + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (busDirection == V3_INPUT) + return 1; + #endif + #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + if (busDirection == V3_OUTPUT) + return 1; + #endif + break; + } + + return 0; + } + + v3_result getBusInfo(const int32_t mediaType, + const int32_t busDirection, + const int32_t busIndex, + v3_bus_info* const info) const + { + DISTRHO_SAFE_ASSERT_INT_RETURN(mediaType == V3_AUDIO || mediaType == V3_EVENT, mediaType, V3_INVALID_ARG); + DISTRHO_SAFE_ASSERT_INT_RETURN(busDirection == V3_INPUT || busDirection == V3_OUTPUT, busDirection, V3_INVALID_ARG); + DISTRHO_SAFE_ASSERT_INT_RETURN(busIndex >= 0, busIndex, V3_INVALID_ARG); + + #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 || DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + const uint32_t busId = static_cast<uint32_t>(busIndex); + #endif + + if (mediaType == V3_AUDIO) + { + #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + if (busDirection == V3_INPUT) + { + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + return getAudioBusInfo<true>(busId, info); + #else + d_stdout("invalid input bus %d", busId); + return V3_INVALID_ARG; + #endif // DISTRHO_PLUGIN_NUM_INPUTS + } + else + { + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + return getAudioBusInfo<false>(busId, info); + #else + d_stdout("invalid output bus %d", busId); + return V3_INVALID_ARG; + #endif // DISTRHO_PLUGIN_NUM_OUTPUTS + } + #else + d_stdout("invalid bus, line %d", __LINE__); + return V3_INVALID_ARG; + #endif // DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS + } + else + { + if (busDirection == V3_INPUT) + { + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + DISTRHO_SAFE_ASSERT_RETURN(busId == 0, V3_INVALID_ARG); + #else + d_stdout("invalid bus, line %d", __LINE__); + return V3_INVALID_ARG; + #endif + } + else + { + #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + DISTRHO_SAFE_ASSERT_RETURN(busId == 0, V3_INVALID_ARG); + #else + d_stdout("invalid bus, line %d", __LINE__); + return V3_INVALID_ARG; + #endif + } + info->media_type = V3_EVENT; + info->direction = busDirection; + info->channel_count = 1; + strncpy_utf16(info->bus_name, busDirection == V3_INPUT ? "Event/MIDI Input" + : "Event/MIDI Output", 128); + info->bus_type = V3_MAIN; + info->flags = V3_DEFAULT_ACTIVE; + return V3_OK; + } + } + + v3_result getRoutingInfo(v3_routing_info*, v3_routing_info*) + { + /* + output->media_type = V3_AUDIO; + output->bus_idx = 0; + output->channel = -1; + d_stdout("getRoutingInfo %s %d %d", + v3_media_type_str(input->media_type), input->bus_idx, input->channel); + */ + return V3_NOT_IMPLEMENTED; + } + + v3_result activateBus(const int32_t mediaType, + const int32_t busDirection, + const int32_t busIndex, + const bool state) noexcept + { + DISTRHO_SAFE_ASSERT_INT_RETURN(busDirection == V3_INPUT || busDirection == V3_OUTPUT, busDirection, V3_INVALID_ARG); + DISTRHO_SAFE_ASSERT_INT_RETURN(busIndex >= 0, busIndex, V3_INVALID_ARG); + + if (mediaType == V3_AUDIO) + { + #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + const uint32_t busId = static_cast<uint32_t>(busIndex); + + if (busDirection == V3_INPUT) + { + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + for (uint32_t i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i) + { + const AudioPortWithBusId& port(fPlugin.getAudioPort(true, i)); + + if (port.busId == busId) + fEnabledInputs[i] = state; + } + #endif + } + else + { + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + for (uint32_t i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) + { + const AudioPortWithBusId& port(fPlugin.getAudioPort(false, i)); + + if (port.busId == busId) + fEnabledOutputs[i] = state; + } + #endif + } + #endif + } + + return V3_OK; + + #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS == 0 + // unused + (void)state; + #endif + } + + v3_result setActive(const bool active) + { + if (active) + fPlugin.activate(); + else + fPlugin.deactivateIfNeeded(); + + return V3_OK; + } + + /* state: we pack pairs of key-value strings each separated by a null/zero byte. + * current-program comes first, then dpf key/value states and then parameters. + * parameters are simply converted to/from strings and floats. + * the parameter symbol is used as the "key", so it is possible to reorder them or even remove and add safely. + * there are markers for begin and end of state and parameters, so they never conflict. + */ + v3_result setState(v3_bstream** const stream) + { + #if DISTRHO_PLUGIN_HAS_UI + const bool connectedToUI = fConnectionFromCtrlToView != nullptr && fConnectedToUI; + #endif + String key, value; + bool hasValue = false; + bool fillingKey = true; // if filling key or value + char queryingType = 'i'; // can be 'n', 's' or 'p' (none, states, parameters) + + char buffer[512], orig; + buffer[sizeof(buffer)-1] = '\xff'; + v3_result res; + + for (int32_t terminated = 0, read; terminated == 0;) + { + read = -1; + res = v3_cpp_obj(stream)->read(stream, buffer, sizeof(buffer)-1, &read); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + DISTRHO_SAFE_ASSERT_INT_RETURN(read > 0, read, V3_INTERNAL_ERR); + + if (read == 0) + return V3_OK; + + for (int32_t i = 0; i < read; ++i) + { + // found terminator, stop here + if (buffer[i] == '\xfe') + { + terminated = 1; + break; + } + + // store character at read position + orig = buffer[read]; + + // place null character to create valid string + buffer[read] = '\0'; + + // append to temporary vars + if (fillingKey) + { + key += buffer + i; + } + else + { + value += buffer + i; + hasValue = true; + } + + // increase buffer offset by length of string + i += std::strlen(buffer + i); + + // restore read character + buffer[read] = orig; + + // if buffer offset points to null, we found the end of a string, lets check + if (buffer[i] == '\0') + { + // special keys + if (key == "__dpf_state_begin__") + { + DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i' || queryingType == 'n', + queryingType, V3_INTERNAL_ERR); + queryingType = 's'; + key.clear(); + value.clear(); + hasValue = false; + continue; + } + if (key == "__dpf_state_end__") + { + DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 's', queryingType, V3_INTERNAL_ERR); + queryingType = 'n'; + key.clear(); + value.clear(); + hasValue = false; + continue; + } + if (key == "__dpf_parameters_begin__") + { + DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i' || queryingType == 'n', + queryingType, V3_INTERNAL_ERR); + queryingType = 'p'; + key.clear(); + value.clear(); + hasValue = false; + continue; + } + if (key == "__dpf_parameters_end__") + { + DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'p', queryingType, V3_INTERNAL_ERR); + queryingType = 'x'; + key.clear(); + value.clear(); + hasValue = false; + continue; + } + + // no special key, swap between reading real key and value + fillingKey = !fillingKey; + + // if there is no value yet keep reading until we have one + if (! hasValue) + continue; + + if (key == "__dpf_program__") + { + DISTRHO_SAFE_ASSERT_INT_RETURN(queryingType == 'i', queryingType, V3_INTERNAL_ERR); + queryingType = 'n'; + + d_stdout("found program '%s'", value.buffer()); + + #if DISTRHO_PLUGIN_WANT_PROGRAMS + const int program = std::atoi(value.buffer()); + DISTRHO_SAFE_ASSERT_CONTINUE(program >= 0); + + fCurrentProgram = static_cast<uint32_t>(program); + fPlugin.loadProgram(fCurrentProgram); + + #if DISTRHO_PLUGIN_HAS_UI + if (connectedToUI) + { + fParameterValueChangesForUI[kVst3InternalParameterProgram] = false; + sendParameterSetToUI(kVst3InternalParameterProgram, program); + } + #endif + #endif + } + else if (queryingType == 's') + { + d_stdout("found state '%s' '%s'", key.buffer(), value.buffer()); + + #if DISTRHO_PLUGIN_WANT_STATE + if (fPlugin.wantStateKey(key)) + { + fStateMap[key] = value; + fPlugin.setState(key, value); + + #if DISTRHO_PLUGIN_HAS_UI + if (connectedToUI) + sendStateSetToUI(key, value); + #endif + } + #endif + } + else if (queryingType == 'p') + { + d_stdout("found parameter '%s' '%s'", key.buffer(), value.buffer()); + float fvalue; + + // find parameter with this symbol, and set its value + for (uint32_t j=0; j < fParameterCount; ++j) + { + if (fPlugin.isParameterOutputOrTrigger(j)) + continue; + if (fPlugin.getParameterSymbol(j) != key) + continue; + + if (fPlugin.getParameterHints(j) & kParameterIsInteger) + fvalue = std::atoi(value.buffer()); + else + fvalue = std::atof(value.buffer()); + + fCachedParameterValues[kVst3InternalParameterBaseCount + j] = fvalue; + #if DISTRHO_PLUGIN_HAS_UI + if (connectedToUI) + { + // UI parameter updates are handled outside the read loop (after host param restart) + fParameterValueChangesForUI[kVst3InternalParameterBaseCount + j] = true; + } + #endif + fPlugin.setParameterValue(j, fvalue); + break; + } + } + + key.clear(); + value.clear(); + hasValue = false; + } + } + } + + if (fComponentHandler != nullptr) + v3_cpp_obj(fComponentHandler)->restart_component(fComponentHandler, V3_RESTART_PARAM_VALUES_CHANGED); + + #if DISTRHO_PLUGIN_HAS_UI + if (connectedToUI) + { + for (uint32_t i=0; i<fParameterCount; ++i) + { + if (fPlugin.isParameterOutputOrTrigger(i)) + continue; + fParameterValueChangesForUI[kVst3InternalParameterBaseCount + i] = false; + sendParameterSetToUI(kVst3InternalParameterBaseCount + i, + fCachedParameterValues[kVst3InternalParameterBaseCount + i]); + } + } + #endif + + return V3_OK; + } + + v3_result getState(v3_bstream** const stream) + { + const uint32_t paramCount = fPlugin.getParameterCount(); + #if DISTRHO_PLUGIN_WANT_STATE + const uint32_t stateCount = fPlugin.getStateCount(); + #else + const uint32_t stateCount = 0; + #endif + + if (stateCount == 0 && paramCount == 0) + { + char buffer = '\0'; + int32_t ignored; + return v3_cpp_obj(stream)->write(stream, &buffer, 1, &ignored); + } + + #if DISTRHO_PLUGIN_WANT_FULL_STATE + // Update current state + for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) + { + const String& key = cit->first; + fStateMap[key] = fPlugin.getStateValue(key); + } + #endif + + String state; + + #if DISTRHO_PLUGIN_WANT_PROGRAMS + { + String tmpStr("__dpf_program__\xff"); + tmpStr += String(fCurrentProgram); + tmpStr += "\xff"; + + state += tmpStr; + } + #endif + + #if DISTRHO_PLUGIN_WANT_STATE + if (stateCount != 0) + { + state += "__dpf_state_begin__\xff"; + + for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) + { + const String& key = cit->first; + const String& value = cit->second; + + // join key and value + String tmpStr; + tmpStr = key; + tmpStr += "\xff"; + tmpStr += value; + tmpStr += "\xff"; + + state += tmpStr; + } + + state += "__dpf_state_end__\xff"; + } + #endif + + if (paramCount != 0) + { + state += "__dpf_parameters_begin__\xff"; + + for (uint32_t i=0; i<paramCount; ++i) + { + if (fPlugin.isParameterOutputOrTrigger(i)) + continue; + + // join key and value + String tmpStr; + tmpStr = fPlugin.getParameterSymbol(i); + tmpStr += "\xff"; + if (fPlugin.getParameterHints(i) & kParameterIsInteger) + tmpStr += String(static_cast<int>(std::round(fPlugin.getParameterValue(i)))); + else + tmpStr += String(fPlugin.getParameterValue(i)); + tmpStr += "\xff"; + + state += tmpStr; + } + + state += "__dpf_parameters_end__\xff"; + } + + // terminator + state += "\xfe"; + + state.replace('\xff', '\0'); + + // now saving state, carefully until host written bytes matches full state size + const char* buffer = state.buffer(); + const int32_t size = static_cast<int32_t>(state.length())+1; + v3_result res; + + for (int32_t wrtntotal = 0, wrtn; wrtntotal < size; wrtntotal += wrtn) + { + wrtn = 0; + res = v3_cpp_obj(stream)->write(stream, const_cast<char*>(buffer), size - wrtntotal, &wrtn); + + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + DISTRHO_SAFE_ASSERT_INT_RETURN(wrtn > 0, wrtn, V3_INTERNAL_ERR); + } + + return V3_OK; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_audio_processor interface calls + + v3_result setBusArrangements(v3_speaker_arrangement* const inputs, const int32_t numInputs, + v3_speaker_arrangement* const outputs, const int32_t numOutputs) + { + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + DISTRHO_SAFE_ASSERT_RETURN(numInputs >= 0, V3_INVALID_ARG); + if (!setAudioBusArrangement<true>(inputs, static_cast<uint32_t>(numInputs))) + return V3_INTERNAL_ERR; + #else + DISTRHO_SAFE_ASSERT_RETURN(numInputs == 0, V3_INVALID_ARG); + // unused + (void)inputs; + #endif + + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + DISTRHO_SAFE_ASSERT_RETURN(numOutputs >= 0, V3_INVALID_ARG); + if (!setAudioBusArrangement<false>(outputs, static_cast<uint32_t>(numOutputs))) + return V3_INTERNAL_ERR; + #else + DISTRHO_SAFE_ASSERT_RETURN(numOutputs == 0, V3_INVALID_ARG); + // unused + (void)outputs; + #endif + + return V3_OK; + } + + v3_result getBusArrangement(const int32_t busDirection, const int32_t busIndex, v3_speaker_arrangement* const speaker) const noexcept + { + DISTRHO_SAFE_ASSERT_INT_RETURN(busDirection == V3_INPUT || busDirection == V3_OUTPUT, busDirection, V3_INVALID_ARG); + DISTRHO_SAFE_ASSERT_INT_RETURN(busIndex >= 0, busIndex, V3_INVALID_ARG); + DISTRHO_SAFE_ASSERT_RETURN(speaker != nullptr, V3_INVALID_ARG); + + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 || DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + const uint32_t busId = static_cast<uint32_t>(busIndex); + #endif + + if (busDirection == V3_INPUT) + { + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + if (getAudioBusArrangement<true>(busId, speaker)) + return V3_OK; + #endif + d_stdout("invalid input bus arrangement %d", busIndex); + return V3_INVALID_ARG; + } + else + { + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + if (getAudioBusArrangement<false>(busId, speaker)) + return V3_OK; + #endif + d_stdout("invalid output bus arrangement %d", busIndex); + return V3_INVALID_ARG; + } + } + + uint32_t getLatencySamples() const noexcept + { + #if DISTRHO_PLUGIN_WANT_LATENCY + return fPlugin.getLatency(); + #else + return 0; + #endif + } + + v3_result setupProcessing(v3_process_setup* const setup) + { + DISTRHO_SAFE_ASSERT_RETURN(setup->symbolic_sample_size == V3_SAMPLE_32, V3_INVALID_ARG); + + const bool active = fPlugin.isActive(); + fPlugin.deactivateIfNeeded(); + + // TODO process_mode can be V3_REALTIME, V3_PREFETCH, V3_OFFLINE + + fPlugin.setSampleRate(setup->sample_rate, true); + fPlugin.setBufferSize(setup->max_block_size, true); + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + fCachedParameterValues[kVst3InternalParameterBufferSize] = setup->max_block_size; + fParameterValuesChangedDuringProcessing[kVst3InternalParameterBufferSize] = true; + + fCachedParameterValues[kVst3InternalParameterSampleRate] = setup->sample_rate; + fParameterValuesChangedDuringProcessing[kVst3InternalParameterSampleRate] = true; + #if DISTRHO_PLUGIN_HAS_UI + fParameterValueChangesForUI[kVst3InternalParameterSampleRate] = true; + #endif + #endif + + if (active) + fPlugin.activate(); + + delete[] fDummyAudioBuffer; + fDummyAudioBuffer = new float[setup->max_block_size]; + + return V3_OK; + } + + v3_result setProcessing(const bool processing) + { + if (processing) + { + if (! fPlugin.isActive()) + fPlugin.activate(); + } + else + { + fPlugin.deactivateIfNeeded(); + } + + return V3_OK; + } + + v3_result process(v3_process_data* const data) + { + DISTRHO_SAFE_ASSERT_RETURN(data->symbolic_sample_size == V3_SAMPLE_32, V3_INVALID_ARG); + // d_stdout("process %i", data->symbolic_sample_size); + + // activate plugin if not done yet + if (! fPlugin.isActive()) + fPlugin.activate(); + + #if DISTRHO_PLUGIN_WANT_TIMEPOS + if (v3_process_context* const ctx = data->ctx) + { + fTimePosition.playing = ctx->state & V3_PROCESS_CTX_PLAYING; + + // ticksPerBeat is not possible with VST3 + fTimePosition.bbt.ticksPerBeat = 1920.0; + + if (ctx->state & V3_PROCESS_CTX_CONT_TIME_VALID) + fTimePosition.frame = ctx->continuous_time_in_samples; + else + fTimePosition.frame = ctx->project_time_in_samples; + + if (ctx->state & V3_PROCESS_CTX_TEMPO_VALID) + fTimePosition.bbt.beatsPerMinute = ctx->bpm; + else + fTimePosition.bbt.beatsPerMinute = 120.0; + + if (ctx->state & (V3_PROCESS_CTX_PROJECT_TIME_VALID|V3_PROCESS_CTX_TIME_SIG_VALID)) + { + const double ppqPos = std::abs(ctx->project_time_quarters); + const int ppqPerBar = ctx->time_sig_numerator * 4 / ctx->time_sig_denom; + const double barBeats = (std::fmod(ppqPos, ppqPerBar) / ppqPerBar) * ctx->time_sig_numerator; + const double rest = std::fmod(barBeats, 1.0); + + fTimePosition.bbt.valid = true; + fTimePosition.bbt.bar = static_cast<int32_t>(ppqPos) / ppqPerBar + 1; + fTimePosition.bbt.beat = static_cast<int32_t>(barBeats - rest + 0.5) + 1; + fTimePosition.bbt.tick = rest * fTimePosition.bbt.ticksPerBeat; + fTimePosition.bbt.beatsPerBar = ctx->time_sig_numerator; + fTimePosition.bbt.beatType = ctx->time_sig_denom; + + if (ctx->project_time_quarters < 0.0) + { + --fTimePosition.bbt.bar; + fTimePosition.bbt.beat = ctx->time_sig_numerator - fTimePosition.bbt.beat + 1; + fTimePosition.bbt.tick = fTimePosition.bbt.ticksPerBeat - fTimePosition.bbt.tick - 1; + } + } + else + { + fTimePosition.bbt.valid = false; + fTimePosition.bbt.bar = 1; + fTimePosition.bbt.beat = 1; + fTimePosition.bbt.tick = 0.0; + fTimePosition.bbt.beatsPerBar = 4.0f; + fTimePosition.bbt.beatType = 4.0f; + } + + fTimePosition.bbt.barStartTick = fTimePosition.bbt.ticksPerBeat* + fTimePosition.bbt.beatsPerBar* + (fTimePosition.bbt.bar-1); + + fPlugin.setTimePosition(fTimePosition); + } + #endif + + if (data->nframes <= 0) + { + updateParametersFromProcessing(data->output_params, 0); + return V3_OK; + } + + const float* inputs[DISTRHO_PLUGIN_NUM_INPUTS != 0 ? DISTRHO_PLUGIN_NUM_INPUTS : 1]; + /* */ float* outputs[DISTRHO_PLUGIN_NUM_OUTPUTS != 0 ? DISTRHO_PLUGIN_NUM_OUTPUTS : 1]; + + std::memset(fDummyAudioBuffer, 0, sizeof(float)*data->nframes); + + { + int32_t i = 0; + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + if (data->inputs != nullptr) + { + for (int32_t j = 0; j < data->inputs->num_channels; ++j) + { + while (!fEnabledInputs[i] && i < DISTRHO_PLUGIN_NUM_INPUTS) + inputs[i++] = fDummyAudioBuffer; + + DISTRHO_SAFE_ASSERT_INT_BREAK(i < DISTRHO_PLUGIN_NUM_INPUTS, i); + inputs[i++] = data->inputs->channel_buffers_32[j]; + } + } + #endif + for (; i < std::max(1, DISTRHO_PLUGIN_NUM_INPUTS); ++i) + inputs[i] = fDummyAudioBuffer; + } + + { + int32_t i = 0; + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + if (data->outputs != nullptr) + { + for (int32_t j = 0; j < data->outputs->num_channels; ++j) + { + while (!fEnabledOutputs[i] && i < DISTRHO_PLUGIN_NUM_OUTPUTS) + outputs[i++] = fDummyAudioBuffer; + + DISTRHO_SAFE_ASSERT_INT_BREAK(i < DISTRHO_PLUGIN_NUM_OUTPUTS, i); + outputs[i++] = data->outputs->channel_buffers_32[j]; + } + } + #endif + for (; i < std::max(1, DISTRHO_PLUGIN_NUM_OUTPUTS); ++i) + outputs[i] = fDummyAudioBuffer; + } + + #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + fHostEventOutputHandle = data->output_events; + #endif + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + bool canAppendMoreEvents = true; + inputEventList.init(); + + #if DISTRHO_PLUGIN_HAS_UI + while (fNotesRingBuffer.isDataAvailableForReading()) + { + uint8_t midiData[3]; + if (! fNotesRingBuffer.readCustomData(midiData, 3)) + break; + + if (inputEventList.appendFromUI(midiData)) + { + canAppendMoreEvents = false; + break; + } + } + #endif + + if (canAppendMoreEvents) + { + if (v3_event_list** const eventptr = data->input_events) + { + v3_event event; + for (uint32_t i = 0, count = v3_cpp_obj(eventptr)->get_event_count(eventptr); i < count; ++i) + { + if (v3_cpp_obj(eventptr)->get_event(eventptr, i, &event) != V3_OK) + break; + + if (inputEventList.appendEvent(event)) + { + canAppendMoreEvents = false; + break; + } + } + } + } + #endif + + if (v3_param_changes** const inparamsptr = data->input_params) + { + int32_t offset; + double value; + + for (int32_t i = 0, count = v3_cpp_obj(inparamsptr)->get_param_count(inparamsptr); i < count; ++i) + { + v3_param_value_queue** const queue = v3_cpp_obj(inparamsptr)->get_param_data(inparamsptr, i); + DISTRHO_SAFE_ASSERT_BREAK(queue != nullptr); + + const v3_param_id rindex = v3_cpp_obj(queue)->get_param_id(queue); + DISTRHO_SAFE_ASSERT_UINT_BREAK(rindex < fVst3ParameterCount, rindex); + + #if DPF_VST3_HAS_INTERNAL_PARAMETERS + if (rindex < kVst3InternalParameterCount) + { + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + // if there are any MIDI CC events as parameter changes, handle them here + if (canAppendMoreEvents && rindex >= kVst3InternalParameterMidiCC_start && rindex <= kVst3InternalParameterMidiCC_end) + { + for (int32_t j = 0, pcount = v3_cpp_obj(queue)->get_point_count(queue); j < pcount; ++j) + { + if (v3_cpp_obj(queue)->get_point(queue, j, &offset, &value) != V3_OK) + break; + + if (inputEventList.appendCC(offset, rindex, value)) + { + canAppendMoreEvents = false; + break; + } + } + } + #endif + continue; + } + #endif + + if (v3_cpp_obj(queue)->get_point_count(queue) <= 0) + continue; + + // if there are any parameter changes at frame 0, handle them here + if (v3_cpp_obj(queue)->get_point(queue, 0, &offset, &value) != V3_OK) + break; + + if (offset != 0) + continue; + + const uint32_t index = rindex - kVst3InternalParameterCount; + setNormalizedPluginParameterValue(index, value); + } + } + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + const uint32_t midiEventCount = inputEventList.convert(fMidiEvents); + fPlugin.run(inputs, outputs, data->nframes, fMidiEvents, midiEventCount); + #else + fPlugin.run(inputs, outputs, data->nframes); + #endif + + #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + fHostEventOutputHandle = nullptr; + #endif + + // if there are any parameter changes after frame 0, set them here + if (v3_param_changes** const inparamsptr = data->input_params) + { + int32_t offset; + double value; + + for (int32_t i = 0, count = v3_cpp_obj(inparamsptr)->get_param_count(inparamsptr); i < count; ++i) + { + v3_param_value_queue** const queue = v3_cpp_obj(inparamsptr)->get_param_data(inparamsptr, i); + DISTRHO_SAFE_ASSERT_BREAK(queue != nullptr); + + const v3_param_id rindex = v3_cpp_obj(queue)->get_param_id(queue); + DISTRHO_SAFE_ASSERT_UINT_BREAK(rindex < fVst3ParameterCount, rindex); + + #if DPF_VST3_HAS_INTERNAL_PARAMETERS + if (rindex < kVst3InternalParameterCount) + continue; + #endif + + const int32_t pcount = v3_cpp_obj(queue)->get_point_count(queue); + + if (pcount <= 0) + continue; + + if (v3_cpp_obj(queue)->get_point(queue, pcount - 1, &offset, &value) != V3_OK) + break; + + if (offset == 0) + continue; + + const uint32_t index = rindex - kVst3InternalParameterCount; + setNormalizedPluginParameterValue(index, value); + } + } + + updateParametersFromProcessing(data->output_params, data->nframes - 1); + return V3_OK; + } + + uint32_t getTailSamples() const noexcept + { + return 0; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_edit_controller interface calls + + int32_t getParameterCount() const noexcept + { + return fVst3ParameterCount; + } + + v3_result getParameterInfo(const int32_t rindex, v3_param_info* const info) const noexcept + { + std::memset(info, 0, sizeof(v3_param_info)); + DISTRHO_SAFE_ASSERT_RETURN(rindex >= 0, V3_INVALID_ARG); + + // TODO hash the parameter symbol + info->param_id = rindex; + + switch (rindex) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER + case kVst3InternalParameterBufferSize: + info->flags = V3_PARAM_READ_ONLY | V3_PARAM_IS_HIDDEN; + info->step_count = DPF_VST3_MAX_BUFFER_SIZE - 1; + strncpy_utf16(info->title, "Buffer Size", 128); + strncpy_utf16(info->short_title, "Buffer Size", 128); + strncpy_utf16(info->units, "frames", 128); + return V3_OK; + case kVst3InternalParameterSampleRate: + info->flags = V3_PARAM_READ_ONLY | V3_PARAM_IS_HIDDEN; + strncpy_utf16(info->title, "Sample Rate", 128); + strncpy_utf16(info->short_title, "Sample Rate", 128); + strncpy_utf16(info->units, "frames", 128); + return V3_OK; + #endif + #if DISTRHO_PLUGIN_WANT_LATENCY + case kVst3InternalParameterLatency: + info->flags = V3_PARAM_READ_ONLY | V3_PARAM_IS_HIDDEN; + strncpy_utf16(info->title, "Latency", 128); + strncpy_utf16(info->short_title, "Latency", 128); + strncpy_utf16(info->units, "frames", 128); + return V3_OK; + #endif + #if DISTRHO_PLUGIN_WANT_PROGRAMS + case kVst3InternalParameterProgram: + info->flags = V3_PARAM_CAN_AUTOMATE | V3_PARAM_IS_LIST | V3_PARAM_PROGRAM_CHANGE | V3_PARAM_IS_HIDDEN; + info->step_count = fProgramCountMinusOne; + strncpy_utf16(info->title, "Current Program", 128); + strncpy_utf16(info->short_title, "Program", 128); + return V3_OK; + #endif + } + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (rindex < kVst3InternalParameterCount) + { + const uint32_t index = static_cast<uint32_t>(rindex - kVst3InternalParameterMidiCC_start); + info->flags = V3_PARAM_CAN_AUTOMATE | V3_PARAM_IS_HIDDEN; + info->step_count = 127; + char ccstr[24]; + snprintf(ccstr, sizeof(ccstr), "MIDI Ch. %d CC %d", static_cast<uint8_t>(index / 130) + 1, index % 130); + strncpy_utf16(info->title, ccstr, 128); + snprintf(ccstr, sizeof(ccstr), "Ch.%d CC%d", index / 130 + 1, index % 130); + strncpy_utf16(info->short_title, ccstr+5, 128); + return V3_OK; + } + #endif + + const uint32_t index = static_cast<uint32_t>(rindex - kVst3InternalParameterCount); + DISTRHO_SAFE_ASSERT_UINT_RETURN(index < fParameterCount, index, V3_INVALID_ARG); + + // set up flags + int32_t flags = 0; + + const ParameterEnumerationValues& enumValues(fPlugin.getParameterEnumValues(index)); + const ParameterRanges& ranges(fPlugin.getParameterRanges(index)); + const uint32_t hints = fPlugin.getParameterHints(index); + + switch (fPlugin.getParameterDesignation(index)) + { + case kParameterDesignationNull: + break; + case kParameterDesignationBypass: + flags |= V3_PARAM_IS_BYPASS; + break; + } + + if (hints & kParameterIsAutomatable) + flags |= V3_PARAM_CAN_AUTOMATE; + if (hints & kParameterIsOutput) + flags |= V3_PARAM_READ_ONLY; + + // set up step_count + int32_t step_count = 0; + + if (hints & kParameterIsBoolean) + step_count = 1; + else if (hints & kParameterIsInteger) + step_count = ranges.max - ranges.min; + + if (enumValues.count >= 2 && enumValues.restrictedMode) + { + flags |= V3_PARAM_IS_LIST; + step_count = enumValues.count - 1; + } + + info->flags = flags; + info->step_count = step_count; + info->default_normalised_value = ranges.getNormalizedValue(ranges.def); + // int32_t unit_id; + strncpy_utf16(info->title, fPlugin.getParameterName(index), 128); + strncpy_utf16(info->short_title, fPlugin.getParameterShortName(index), 128); + strncpy_utf16(info->units, fPlugin.getParameterUnit(index), 128); + return V3_OK; + } + + v3_result getParameterStringForValue(const v3_param_id rindex, const double normalized, v3_str_128 output) + { + DISTRHO_SAFE_ASSERT_RETURN(normalized >= 0.0 && normalized <= 1.0, V3_INVALID_ARG); + + switch (rindex) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER + case kVst3InternalParameterBufferSize: + snprintf_i32_utf16(output, static_cast<int>(normalized * DPF_VST3_MAX_BUFFER_SIZE + 0.5), 128); + return V3_OK; + case kVst3InternalParameterSampleRate: + snprintf_f32_utf16(output, std::round(normalized * DPF_VST3_MAX_SAMPLE_RATE), 128); + return V3_OK; + #endif + #if DISTRHO_PLUGIN_WANT_LATENCY + case kVst3InternalParameterLatency: + snprintf_f32_utf16(output, std::round(normalized * DPF_VST3_MAX_LATENCY), 128); + return V3_OK; + #endif + #if DISTRHO_PLUGIN_WANT_PROGRAMS + case kVst3InternalParameterProgram: + const uint32_t program = std::round(normalized * fProgramCountMinusOne); + strncpy_utf16(output, fPlugin.getProgramName(program), 128); + return V3_OK; + #endif + } + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (rindex < kVst3InternalParameterCount) + { + snprintf_f32_utf16(output, std::round(normalized * 127), 128); + return V3_OK; + } + #endif + + const uint32_t index = static_cast<uint32_t>(rindex - kVst3InternalParameterCount); + DISTRHO_SAFE_ASSERT_UINT_RETURN(index < fParameterCount, index, V3_INVALID_ARG); + + const ParameterEnumerationValues& enumValues(fPlugin.getParameterEnumValues(index)); + const ParameterRanges& ranges(fPlugin.getParameterRanges(index)); + const uint32_t hints = fPlugin.getParameterHints(index); + float value = ranges.getUnnormalizedValue(normalized); + + 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); + } + + for (uint32_t i=0; i < enumValues.count; ++i) + { + if (d_isEqual(enumValues.values[i].value, value)) + { + strncpy_utf16(output, enumValues.values[i].label, 128); + return V3_OK; + } + } + + if (hints & kParameterIsInteger) + snprintf_i32_utf16(output, value, 128); + else + snprintf_f32_utf16(output, value, 128); + + return V3_OK; + } + + v3_result getParameterValueForString(const v3_param_id rindex, int16_t* const input, double* const output) + { + switch (rindex) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER + case kVst3InternalParameterBufferSize: + *output = static_cast<double>(std::atoi(ScopedUTF8String(input))) / DPF_VST3_MAX_BUFFER_SIZE; + return V3_OK; + case kVst3InternalParameterSampleRate: + *output = std::atof(ScopedUTF8String(input)) / DPF_VST3_MAX_SAMPLE_RATE; + return V3_OK; + #endif + #if DISTRHO_PLUGIN_WANT_LATENCY + case kVst3InternalParameterLatency: + *output = std::atof(ScopedUTF8String(input)) / DPF_VST3_MAX_LATENCY; + return V3_OK; + #endif + #if DISTRHO_PLUGIN_WANT_PROGRAMS + case kVst3InternalParameterProgram: + for (uint32_t i=0, count=fPlugin.getProgramCount(); i < count; ++i) + { + if (strcmp_utf16(input, fPlugin.getProgramName(i))) + { + *output = static_cast<double>(i) / static_cast<double>(fProgramCountMinusOne); + return V3_OK; + } + } + return V3_INVALID_ARG; + #endif + } + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (rindex < kVst3InternalParameterCount) + { + // TODO find CC/channel based on name + return V3_NOT_IMPLEMENTED; + } + #endif + + const uint32_t index = static_cast<uint32_t>(rindex - kVst3InternalParameterCount); + DISTRHO_SAFE_ASSERT_UINT_RETURN(index < fParameterCount, index, V3_INVALID_ARG); + + const ParameterEnumerationValues& enumValues(fPlugin.getParameterEnumValues(index)); + const ParameterRanges& ranges(fPlugin.getParameterRanges(index)); + + for (uint32_t i=0; i < enumValues.count; ++i) + { + if (strcmp_utf16(input, enumValues.values[i].label)) + { + *output = ranges.getNormalizedValue(enumValues.values[i].value); + return V3_OK; + } + } + + const ScopedUTF8String input8(input); + + float value; + if (fPlugin.getParameterHints(index) & kParameterIsInteger) + value = std::atoi(input8); + else + value = std::atof(input8); + + *output = ranges.getNormalizedValue(value); + return V3_OK; + } + + double normalizedParameterToPlain(const v3_param_id rindex, const double normalized) + { + DISTRHO_SAFE_ASSERT_RETURN(normalized >= 0.0 && normalized <= 1.0, 0.0); + + switch (rindex) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER + case kVst3InternalParameterBufferSize: + return std::round(normalized * DPF_VST3_MAX_BUFFER_SIZE); + case kVst3InternalParameterSampleRate: + return normalized * DPF_VST3_MAX_SAMPLE_RATE; + #endif + #if DISTRHO_PLUGIN_WANT_LATENCY + case kVst3InternalParameterLatency: + return normalized * DPF_VST3_MAX_LATENCY; + #endif + #if DISTRHO_PLUGIN_WANT_PROGRAMS + case kVst3InternalParameterProgram: + return std::round(normalized * fProgramCountMinusOne); + #endif + } + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (rindex < kVst3InternalParameterCount) + return std::round(normalized * 127); + #endif + + const uint32_t index = static_cast<uint32_t>(rindex - kVst3InternalParameterCount); + DISTRHO_SAFE_ASSERT_UINT2_RETURN(index < fParameterCount, index, fParameterCount, 0.0); + + const ParameterRanges& ranges(fPlugin.getParameterRanges(index)); + const uint32_t hints = fPlugin.getParameterHints(index); + float value = ranges.getUnnormalizedValue(normalized); + + if (hints & kParameterIsBoolean) + { + const float midRange = ranges.min + (ranges.max - ranges.min) / 2.0f; + value = value > midRange ? ranges.max : ranges.min; + } + else if (hints & kParameterIsInteger) + { + value = std::round(value); + } + + return value; + } + + double plainParameterToNormalized(const v3_param_id rindex, const double plain) + { + switch (rindex) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER + case kVst3InternalParameterBufferSize: + return std::max(0.0, std::min(1.0, plain / DPF_VST3_MAX_BUFFER_SIZE)); + case kVst3InternalParameterSampleRate: + return std::max(0.0, std::min(1.0, plain / DPF_VST3_MAX_SAMPLE_RATE)); + #endif + #if DISTRHO_PLUGIN_WANT_LATENCY + case kVst3InternalParameterLatency: + return std::max(0.0, std::min(1.0, plain / DPF_VST3_MAX_LATENCY)); + #endif + #if DISTRHO_PLUGIN_WANT_PROGRAMS + case kVst3InternalParameterProgram: + return std::max(0.0, std::min(1.0, plain / fProgramCountMinusOne)); + #endif + } + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (rindex < kVst3InternalParameterCount) + return std::max(0.0, std::min(1.0, plain / 127)); + #endif + + const uint32_t index = static_cast<uint32_t>(rindex - kVst3InternalParameterCount); + DISTRHO_SAFE_ASSERT_UINT2_RETURN(index < fParameterCount, index, fParameterCount, 0.0); + + const ParameterRanges& ranges(fPlugin.getParameterRanges(index)); + return ranges.getNormalizedValue(plain); + } + + double getParameterNormalized(const v3_param_id rindex) + { + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + // TODO something to do here? + if ( + #if !DPF_VST3_PURE_MIDI_INTERNAL_PARAMETERS + rindex >= kVst3InternalParameterMidiCC_start && + #endif + rindex <= kVst3InternalParameterMidiCC_end) + return 0.0; + #endif + + #if DPF_VST3_USES_SEPARATE_CONTROLLER || DISTRHO_PLUGIN_WANT_LATENCY || DISTRHO_PLUGIN_WANT_PROGRAMS + switch (rindex) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER + case kVst3InternalParameterBufferSize: + case kVst3InternalParameterSampleRate: + #endif + #if DISTRHO_PLUGIN_WANT_LATENCY + case kVst3InternalParameterLatency: + #endif + #if DISTRHO_PLUGIN_WANT_PROGRAMS + case kVst3InternalParameterProgram: + #endif + return plainParameterToNormalized(rindex, fCachedParameterValues[rindex]); + } + #endif + + const uint32_t index = static_cast<uint32_t>(rindex - kVst3InternalParameterCount); + DISTRHO_SAFE_ASSERT_UINT2_RETURN(index < fParameterCount, index, fParameterCount, 0.0); + + const ParameterRanges& ranges(fPlugin.getParameterRanges(index)); + return ranges.getNormalizedValue(fCachedParameterValues[kVst3InternalParameterBaseCount + index]); + } + + v3_result setParameterNormalized(const v3_param_id rindex, const double normalized) + { + DISTRHO_SAFE_ASSERT_RETURN(normalized >= 0.0 && normalized <= 1.0, V3_INVALID_ARG); + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + // TODO something to do here? + if ( + #if !DPF_VST3_PURE_MIDI_INTERNAL_PARAMETERS + rindex >= kVst3InternalParameterMidiCC_start && + #endif + rindex <= kVst3InternalParameterMidiCC_end) + return V3_INVALID_ARG; + #endif + + #if DPF_VST3_HAS_INTERNAL_PARAMETERS && !DPF_VST3_PURE_MIDI_INTERNAL_PARAMETERS + if (rindex < kVst3InternalParameterBaseCount) + { + fCachedParameterValues[rindex] = normalizedParameterToPlain(rindex, normalized); + int flags = 0; + + switch (rindex) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER + case kVst3InternalParameterBufferSize: + fPlugin.setBufferSize(fCachedParameterValues[rindex], true); + break; + case kVst3InternalParameterSampleRate: + fPlugin.setSampleRate(fCachedParameterValues[rindex], true); + break; + #endif + #if DISTRHO_PLUGIN_WANT_LATENCY + case kVst3InternalParameterLatency: + flags = V3_RESTART_LATENCY_CHANGED; + break; + #endif + #if DISTRHO_PLUGIN_WANT_PROGRAMS + case kVst3InternalParameterProgram: + flags = V3_RESTART_PARAM_VALUES_CHANGED; + fCurrentProgram = fCachedParameterValues[rindex]; + fPlugin.loadProgram(fCurrentProgram); + + for (uint32_t i=0; i<fParameterCount; ++i) + { + if (fPlugin.isParameterOutputOrTrigger(i)) + continue; + fCachedParameterValues[kVst3InternalParameterCount + i] = fPlugin.getParameterValue(i); + } + + #if DISTRHO_PLUGIN_HAS_UI + fParameterValueChangesForUI[kVst3InternalParameterProgram] = true; + #endif + break; + #endif + } + + if (fComponentHandler != nullptr && flags != 0) + v3_cpp_obj(fComponentHandler)->restart_component(fComponentHandler, flags); + + return V3_OK; + } + #endif + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + const uint32_t index = static_cast<uint32_t>(rindex - kVst3InternalParameterCount); + DISTRHO_SAFE_ASSERT_UINT2_RETURN(index < fParameterCount, index, fParameterCount, V3_INVALID_ARG); + + setNormalizedPluginParameterValue(index, normalized); + #endif + return V3_OK; + + #if !DPF_VST3_HAS_INTERNAL_PARAMETERS + // unused + (void)rindex; + #endif + } + + v3_result setComponentHandler(v3_component_handler** const handler) noexcept + { + fComponentHandler = handler; + return V3_OK; + } + +#if DISTRHO_PLUGIN_HAS_UI + // ---------------------------------------------------------------------------------------------------------------- + // v3_connection_point interface calls + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + void comp2ctrl_connect(v3_connection_point** const other) + { + fConnectionFromCompToCtrl = other; + } + + void comp2ctrl_disconnect() + { + fConnectionFromCompToCtrl = nullptr; + } + + v3_result comp2ctrl_notify(v3_message** const message) + { + const char* const msgid = v3_cpp_obj(message)->get_message_id(message); + DISTRHO_SAFE_ASSERT_RETURN(msgid != nullptr, V3_INVALID_ARG); + + v3_attribute_list** const attrs = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrs != nullptr, V3_INVALID_ARG); + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (std::strcmp(msgid, "midi") == 0) + return notify_midi(attrs); + #endif + + #if DISTRHO_PLUGIN_WANT_STATE + if (std::strcmp(msgid, "state-set") == 0) + return notify_state(attrs); + #endif + + d_stdout("comp2ctrl_notify received unknown msg '%s'", msgid); + + return V3_NOT_IMPLEMENTED; + } + #endif // DPF_VST3_USES_SEPARATE_CONTROLLER + + // ---------------------------------------------------------------------------------------------------------------- + + void ctrl2view_connect(v3_connection_point** const other) + { + DISTRHO_SAFE_ASSERT(fConnectedToUI == false); + + fConnectionFromCtrlToView = other; + fConnectedToUI = false; + } + + void ctrl2view_disconnect() + { + fConnectedToUI = false; + fConnectionFromCtrlToView = nullptr; + } + + v3_result ctrl2view_notify(v3_message** const message) + { + DISTRHO_SAFE_ASSERT_RETURN(fConnectionFromCtrlToView != nullptr, V3_INTERNAL_ERR); + + const char* const msgid = v3_cpp_obj(message)->get_message_id(message); + DISTRHO_SAFE_ASSERT_RETURN(msgid != nullptr, V3_INVALID_ARG); + + if (std::strcmp(msgid, "init") == 0) + { + fConnectedToUI = true; + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + fParameterValueChangesForUI[kVst3InternalParameterSampleRate] = false; + sendParameterSetToUI(kVst3InternalParameterSampleRate, + fCachedParameterValues[kVst3InternalParameterSampleRate]); + #endif + + #if DISTRHO_PLUGIN_WANT_PROGRAMS + fParameterValueChangesForUI[kVst3InternalParameterProgram] = false; + sendParameterSetToUI(kVst3InternalParameterProgram, fCurrentProgram); + #endif + + #if DISTRHO_PLUGIN_WANT_FULL_STATE + // Update current state from plugin side + for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) + { + const String& key = cit->first; + fStateMap[key] = fPlugin.getStateValue(key); + } + #endif + + #if DISTRHO_PLUGIN_WANT_STATE + // Set state + for (StringMap::const_iterator cit=fStateMap.begin(), cite=fStateMap.end(); cit != cite; ++cit) + { + const String& key = cit->first; + const String& value = cit->second; + + sendStateSetToUI(key, value); + } + #endif + + for (uint32_t i=0; i<fParameterCount; ++i) + { + fParameterValueChangesForUI[kVst3InternalParameterBaseCount + i] = false; + sendParameterSetToUI(kVst3InternalParameterBaseCount + i, + fCachedParameterValues[kVst3InternalParameterBaseCount + i]); + } + + sendReadyToUI(); + return V3_OK; + } + + DISTRHO_SAFE_ASSERT_RETURN(fConnectedToUI, V3_INTERNAL_ERR); + + v3_attribute_list** const attrs = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrs != nullptr, V3_INVALID_ARG); + + if (std::strcmp(msgid, "idle") == 0) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER + if (fParameterValueChangesForUI[kVst3InternalParameterSampleRate]) + { + fParameterValueChangesForUI[kVst3InternalParameterSampleRate] = false; + sendParameterSetToUI(kVst3InternalParameterSampleRate, + fCachedParameterValues[kVst3InternalParameterSampleRate]); + } + #endif + + #if DISTRHO_PLUGIN_WANT_PROGRAMS + if (fParameterValueChangesForUI[kVst3InternalParameterProgram]) + { + fParameterValueChangesForUI[kVst3InternalParameterProgram] = false; + sendParameterSetToUI(kVst3InternalParameterProgram, fCurrentProgram); + } + #endif + + for (uint32_t i=0; i<fParameterCount; ++i) + { + if (! fParameterValueChangesForUI[kVst3InternalParameterBaseCount + i]) + continue; + + fParameterValueChangesForUI[kVst3InternalParameterBaseCount + i] = false; + sendParameterSetToUI(kVst3InternalParameterBaseCount + i, + fCachedParameterValues[kVst3InternalParameterBaseCount + i]); + } + + sendReadyToUI(); + return V3_OK; + } + + if (std::strcmp(msgid, "close") == 0) + { + fConnectedToUI = false; + return V3_OK; + } + + if (std::strcmp(msgid, "parameter-edit") == 0) + { + DISTRHO_SAFE_ASSERT_RETURN(fComponentHandler != nullptr, V3_INTERNAL_ERR); + + int64_t rindex; + int64_t started; + v3_result res; + + res = v3_cpp_obj(attrs)->get_int(attrs, "rindex", &rindex); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + DISTRHO_SAFE_ASSERT_INT2_RETURN(rindex >= kVst3InternalParameterCount, + rindex, fParameterCount, V3_INTERNAL_ERR); + DISTRHO_SAFE_ASSERT_INT2_RETURN(rindex < kVst3InternalParameterCount + fParameterCount, + rindex, fParameterCount, V3_INTERNAL_ERR); + + res = v3_cpp_obj(attrs)->get_int(attrs, "started", &started); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + DISTRHO_SAFE_ASSERT_INT_RETURN(started == 0 || started == 1, started, V3_INTERNAL_ERR); + + return started != 0 ? v3_cpp_obj(fComponentHandler)->begin_edit(fComponentHandler, rindex) + : v3_cpp_obj(fComponentHandler)->end_edit(fComponentHandler, rindex); + } + + if (std::strcmp(msgid, "parameter-set") == 0) + { + DISTRHO_SAFE_ASSERT_RETURN(fComponentHandler != nullptr, V3_INTERNAL_ERR); + + int64_t rindex; + double value; + v3_result res; + + res = v3_cpp_obj(attrs)->get_int(attrs, "rindex", &rindex); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + DISTRHO_SAFE_ASSERT_INT2_RETURN(rindex >= kVst3InternalParameterCount, + rindex, fParameterCount, V3_INTERNAL_ERR); + DISTRHO_SAFE_ASSERT_INT2_RETURN(rindex < kVst3InternalParameterCount + fParameterCount, + rindex, fParameterCount, V3_INTERNAL_ERR); + + res = v3_cpp_obj(attrs)->get_float(attrs, "value", &value); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + + const uint32_t index = rindex - kVst3InternalParameterCount; + const double normalized = fPlugin.getParameterRanges(index).getNormalizedValue(value); + + return v3_cpp_obj(fComponentHandler)->perform_edit(fComponentHandler, rindex, normalized); + } + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (std::strcmp(msgid, "midi") == 0) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER + DISTRHO_SAFE_ASSERT_RETURN(fConnectionFromCompToCtrl != nullptr, V3_INTERNAL_ERR); + return v3_cpp_obj(fConnectionFromCompToCtrl)->notify(fConnectionFromCompToCtrl, message); + #else + return notify_midi(attrs); + #endif + } + #endif + + #if DISTRHO_PLUGIN_WANT_STATE + if (std::strcmp(msgid, "state-set") == 0) + { + const v3_result res = notify_state(attrs); + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + if (res != V3_OK) + return res; + + // notify component of the change + DISTRHO_SAFE_ASSERT_RETURN(fConnectionFromCompToCtrl != nullptr, V3_INTERNAL_ERR); + return v3_cpp_obj(fConnectionFromCompToCtrl)->notify(fConnectionFromCompToCtrl, message); + #else + return res; + #endif + } + #endif + + d_stdout("ctrl2view_notify received unknown msg '%s'", msgid); + + return V3_NOT_IMPLEMENTED; + } + + #if DISTRHO_PLUGIN_WANT_STATE + v3_result notify_state(v3_attribute_list** const attrs) + { + int64_t keyLength = -1; + int64_t valueLength = -1; + v3_result res; + + res = v3_cpp_obj(attrs)->get_int(attrs, "key:length", &keyLength); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + DISTRHO_SAFE_ASSERT_INT_RETURN(keyLength >= 0, keyLength, V3_INTERNAL_ERR); + + res = v3_cpp_obj(attrs)->get_int(attrs, "value:length", &valueLength); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + DISTRHO_SAFE_ASSERT_INT_RETURN(valueLength >= 0, valueLength, V3_INTERNAL_ERR); + + int16_t* const key16 = (int16_t*)std::malloc(sizeof(int16_t)*(keyLength + 1)); + DISTRHO_SAFE_ASSERT_RETURN(key16 != nullptr, V3_NOMEM); + + int16_t* const value16 = (int16_t*)std::malloc(sizeof(int16_t)*(valueLength + 1)); + DISTRHO_SAFE_ASSERT_RETURN(value16 != nullptr, V3_NOMEM); + + res = v3_cpp_obj(attrs)->get_string(attrs, "key", key16, sizeof(int16_t)*(keyLength+1)); + DISTRHO_SAFE_ASSERT_INT2_RETURN(res == V3_OK, res, keyLength, res); + + if (valueLength != 0) + { + res = v3_cpp_obj(attrs)->get_string(attrs, "value", value16, sizeof(int16_t)*(valueLength+1)); + DISTRHO_SAFE_ASSERT_INT2_RETURN(res == V3_OK, res, valueLength, res); + } + + // do cheap inline conversion + char* const key = (char*)key16; + char* const value = (char*)value16; + + for (int64_t i=0; i<keyLength; ++i) + key[i] = key16[i]; + for (int64_t i=0; i<valueLength; ++i) + value[i] = value16[i]; + + key[keyLength] = '\0'; + value[valueLength] = '\0'; + + fPlugin.setState(key, value); + + // save this key as needed + if (fPlugin.wantStateKey(key)) + { + for (StringMap::iterator it=fStateMap.begin(), ite=fStateMap.end(); it != ite; ++it) + { + const String& dkey(it->first); + + if (dkey == key) + { + it->second = value; + std::free(key16); + std::free(value16); + return V3_OK; + } + } + + d_stderr("Failed to find plugin state with key \"%s\"", key); + } + + std::free(key16); + std::free(value16); + return V3_OK; + } + #endif // DISTRHO_PLUGIN_WANT_STATE + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + v3_result notify_midi(v3_attribute_list** const attrs) + { + uint8_t* data; + uint32_t size; + v3_result res; + + res = v3_cpp_obj(attrs)->get_binary(attrs, "data", (const void**)&data, &size); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + + // known maximum size + DISTRHO_SAFE_ASSERT_UINT_RETURN(size == 3, size, V3_INTERNAL_ERR); + + return fNotesRingBuffer.writeCustomData(data, size) && fNotesRingBuffer.commitWrite() ? V3_OK : V3_NOMEM; + } + #endif // DISTRHO_PLUGIN_WANT_MIDI_INPUT +#endif + + // ---------------------------------------------------------------------------------------------------------------- + +private: + // Plugin + PluginExporter fPlugin; + + // VST3 stuff + v3_component_handler** fComponentHandler; + #if DISTRHO_PLUGIN_HAS_UI + #if DPF_VST3_USES_SEPARATE_CONTROLLER + v3_connection_point** fConnectionFromCompToCtrl; + #endif + v3_connection_point** fConnectionFromCtrlToView; + v3_host_application** const fHostApplication; + #endif + + // Temporary data + const uint32_t fParameterCount; + const uint32_t fVst3ParameterCount; // full offset + real + float* fCachedParameterValues; // basic offset + real + float* fDummyAudioBuffer; + bool* fParameterValuesChangedDuringProcessing; // basic offset + real + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + bool fEnabledInputs[DISTRHO_PLUGIN_NUM_INPUTS]; + #endif + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + bool fEnabledOutputs[DISTRHO_PLUGIN_NUM_OUTPUTS]; + #endif + #if DISTRHO_PLUGIN_HAS_UI + bool* fParameterValueChangesForUI; // basic offset + real + bool fConnectedToUI; + #endif + #if DISTRHO_PLUGIN_WANT_LATENCY + uint32_t fLastKnownLatency; + #endif + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + MidiEvent fMidiEvents[kMaxMidiEvents]; + #if DISTRHO_PLUGIN_HAS_UI + SmallStackRingBuffer fNotesRingBuffer; + #endif + #endif + #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + v3_event_list** fHostEventOutputHandle; + #endif + #if DISTRHO_PLUGIN_WANT_PROGRAMS + uint32_t fCurrentProgram; + const uint32_t fProgramCountMinusOne; + #endif + #if DISTRHO_PLUGIN_WANT_STATE + StringMap fStateMap; + #endif + #if DISTRHO_PLUGIN_WANT_TIMEPOS + TimePosition fTimePosition; + #endif + + // ---------------------------------------------------------------------------------------------------------------- + // helper functions for dealing with buses + + #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + template<bool isInput> + void fillInBusInfoDetails() + { + constexpr const uint32_t numPorts = isInput ? DISTRHO_PLUGIN_NUM_INPUTS : DISTRHO_PLUGIN_NUM_OUTPUTS; + BusInfo& busInfo(isInput ? inputBuses : outputBuses); + bool* const enabledPorts = isInput + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + ? fEnabledInputs + #else + ? nullptr + #endif + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + : fEnabledOutputs; + #else + : nullptr; + #endif + + std::vector<uint32_t> visitedPortGroups; + for (uint32_t i=0; i<numPorts; ++i) + { + const AudioPortWithBusId& port(fPlugin.getAudioPort(isInput, i)); + + if (port.groupId != kPortGroupNone) + { + const std::vector<uint32_t>::iterator end = visitedPortGroups.end(); + if (std::find(visitedPortGroups.begin(), end, port.groupId) == end) + { + visitedPortGroups.push_back(port.groupId); + ++busInfo.groups; + } + ++busInfo.groupPorts; + continue; + } + + if (port.hints & kAudioPortIsCV) + ++busInfo.cvPorts; + else if (port.hints & kAudioPortIsSidechain) + ++busInfo.sidechainPorts; + else + ++busInfo.audioPorts; + } + + if (busInfo.audioPorts != 0) + busInfo.audio = 1; + if (busInfo.sidechainPorts != 0) + busInfo.sidechain = 1; + + uint32_t busIdForCV = 0; + const std::vector<uint32_t>::iterator vpgStart = visitedPortGroups.begin(); + const std::vector<uint32_t>::iterator vpgEnd = visitedPortGroups.end(); + + for (uint32_t i=0; i<numPorts; ++i) + { + AudioPortWithBusId& port(fPlugin.getAudioPort(isInput, i)); + + if (port.groupId != kPortGroupNone) + { + port.busId = std::find(vpgStart, vpgEnd, port.groupId) - vpgStart; + + if (busInfo.audio == 0 && (port.hints & kAudioPortIsSidechain) == 0x0) + enabledPorts[i] = true; + } + else + { + if (port.hints & kAudioPortIsCV) + { + port.busId = busInfo.audio + busInfo.sidechain + busIdForCV++; + } + else if (port.hints & kAudioPortIsSidechain) + { + port.busId = busInfo.audio; + } + else + { + port.busId = 0; + enabledPorts[i] = true; + } + + port.busId += busInfo.groups; + } + } + } + + template<bool isInput> + v3_result getAudioBusInfo(uint32_t busId, v3_bus_info* const info) const + { + constexpr const uint32_t numPorts = isInput ? DISTRHO_PLUGIN_NUM_INPUTS : DISTRHO_PLUGIN_NUM_OUTPUTS; + const BusInfo& busInfo(isInput ? inputBuses : outputBuses); + + int32_t numChannels; + uint32_t flags; + v3_bus_types busType; + v3_str_128 busName = {}; + + if (busId < busInfo.groups) + { + numChannels = 0; + + for (uint32_t i=0; i<numPorts; ++i) + { + const AudioPortWithBusId& port(fPlugin.getAudioPort(isInput, i)); + + if (port.busId == busId) + { + const PortGroupWithId& group(fPlugin.getPortGroupById(port.groupId)); + + switch (port.groupId) + { + case kPortGroupStereo: + case kPortGroupMono: + strncpy_utf16(busName, isInput ? "Audio Input" : "Audio Output", 128); + break; + default: + if (group.name.isNotEmpty()) + strncpy_utf16(busName, group.name, 128); + else + strncpy_utf16(busName, port.name, 128); + break; + } + + numChannels = fPlugin.getAudioPortCountWithGroupId(isInput, port.groupId); + + if (busInfo.audio == 0 && (port.hints & kAudioPortIsSidechain) == 0x0) + { + busType = V3_MAIN; + flags = V3_DEFAULT_ACTIVE; + } + else + { + busType = V3_AUX; + flags = 0; + } + break; + } + } + + DISTRHO_SAFE_ASSERT_RETURN(numChannels != 0, V3_INTERNAL_ERR); + } + else + { + busId -= busInfo.groups; + + switch (busId) + { + case 0: + if (busInfo.audio) + { + numChannels = busInfo.audioPorts; + busType = V3_MAIN; + flags = V3_DEFAULT_ACTIVE; + break; + } + // fall-through + case 1: + if (busInfo.sidechain) + { + numChannels = busInfo.sidechainPorts; + busType = V3_AUX; + flags = 0; + break; + } + // fall-through + default: + numChannels = 1; + busType = V3_AUX; + flags = V3_IS_CONTROL_VOLTAGE; + break; + } + + if (busType == V3_MAIN) + { + strncpy_utf16(busName, isInput ? "Audio Input" : "Audio Output", 128); + } + else + { + for (uint32_t i=0; i<numPorts; ++i) + { + const AudioPortWithBusId& port(fPlugin.getAudioPort(isInput, i)); + + if (port.busId == busId) + { + const String& groupName(busInfo.groups ? fPlugin.getPortGroupById(port.groupId).name : port.name); + strncpy_utf16(busName, groupName, 128); + break; + } + } + } + } + + // d_stdout("getAudioBusInfo %d %d %d", (int)isInput, busId, numChannels); + std::memset(info, 0, sizeof(v3_bus_info)); + info->media_type = V3_AUDIO; + info->direction = isInput ? V3_INPUT : V3_OUTPUT; + info->channel_count = numChannels; + std::memcpy(info->bus_name, busName, sizeof(busName)); + info->bus_type = busType; + info->flags = flags; + return V3_OK; + } + + template<bool isInput> + v3_speaker_arrangement getSpeakerArrangementForAudioPort(const BusInfo& busInfo, const uint32_t portGroupId, uint32_t busId) const noexcept + { + switch (portGroupId) + { + case kPortGroupMono: + return V3_SPEAKER_M; + case kPortGroupStereo: + return V3_SPEAKER_L | V3_SPEAKER_R; + } + + v3_speaker_arrangement arr = 0x0; + + if (busId < busInfo.groups) + { + const uint32_t numPortsInBus = fPlugin.getAudioPortCountWithGroupId(isInput, busId); + for (uint32_t j=0; j<numPortsInBus; ++j) + arr |= 1ull << (j + 33ull); + } + else + { + busId -= busInfo.groups; + + if (busInfo.audio != 0 && busId == 0) + { + arr = 0x0; + for (uint32_t j=0; j<busInfo.audioPorts; ++j) + arr |= 1ull << (j + 33ull); + } + else if (busInfo.sidechain != 0 && busId == busInfo.audio) + { + arr = 0x0; + for (uint32_t j=0; j<busInfo.sidechainPorts; ++j) + arr |= 1ull << (busInfo.audioPorts + j + 33ull); + } + else + { + arr = 1ull << (busInfo.audioPorts + busInfo.sidechainPorts + busId + 33ull); + } + } + + return arr; + } + + template<bool isInput> + bool getAudioBusArrangement(uint32_t busId, v3_speaker_arrangement* const speaker) const + { + constexpr const uint32_t numPorts = isInput ? DISTRHO_PLUGIN_NUM_INPUTS : DISTRHO_PLUGIN_NUM_OUTPUTS; + const BusInfo& busInfo(isInput ? inputBuses : outputBuses); + const bool* const enabledPorts = isInput + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + ? fEnabledInputs + #else + ? nullptr + #endif + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + : fEnabledOutputs; + #else + : nullptr; + #endif + + for (uint32_t i=0; i<numPorts; ++i) + { + const AudioPortWithBusId& port(fPlugin.getAudioPort(isInput, i)); + + if (port.busId != busId) + { + // d_stdout("port.busId != busId: %d %d", port.busId, busId); + continue; + } + + if (!enabledPorts[i]) + { + *speaker = 0; + // d_stdout("getAudioBusArrangement %d %d not enabled", i, busId); + return true; + } + + *speaker = getSpeakerArrangementForAudioPort<isInput>(busInfo, port.groupId, busId); + // d_stdout("getAudioBusArrangement %d enabled by value %lx", busId, *speaker); + return true; + } + + return false; + } + + template<bool isInput> + bool setAudioBusArrangement(v3_speaker_arrangement* const speakers, const uint32_t numBuses) + { + constexpr const uint32_t numPorts = isInput ? DISTRHO_PLUGIN_NUM_INPUTS : DISTRHO_PLUGIN_NUM_OUTPUTS; + BusInfo& busInfo(isInput ? inputBuses : outputBuses); + bool* const enabledPorts = isInput + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + ? fEnabledInputs + #else + ? nullptr + #endif + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + : fEnabledOutputs; + #else + : nullptr; + #endif + + bool ok = true; + + for (uint32_t busId=0; busId<numBuses; ++busId) + { + const v3_speaker_arrangement arr = speakers[busId]; + + // d_stdout("setAudioBusArrangement %d %d | %d %lx", (int)isInput, numBuses, busId, arr); + + for (uint32_t i=0; i<numPorts; ++i) + { + AudioPortWithBusId& port(fPlugin.getAudioPort(isInput, i)); + + if (port.busId != busId) + { + // d_stdout("setAudioBusArrangement port.busId != busId: %d %d", port.busId, busId); + continue; + } + + // get the only valid speaker arrangement for this bus, assuming enabled + const v3_speaker_arrangement earr = getSpeakerArrangementForAudioPort<isInput>(busInfo, port.groupId, busId); + + // fail if host tries to map it to anything else + if (earr != arr && arr != 0) + { + ok = false; + continue; + } + + enabledPorts[i] = arr != 0; + } + } + + // disable any buses outside of the requested arrangement + const uint32_t totalBuses = busInfo.audio + busInfo.sidechain + busInfo.groups + busInfo.cvPorts; + + for (uint32_t busId=numBuses; busId<totalBuses; ++busId) + { + for (uint32_t i=0; i<numPorts; ++i) + { + const AudioPortWithBusId& port(fPlugin.getAudioPort(isInput, i)); + + if (port.busId == busId) + { + enabledPorts[i] = false; + break; + } + } + } + + return ok; + } + #endif + + // ---------------------------------------------------------------------------------------------------------------- + // helper functions called during process, cannot block + + void updateParametersFromProcessing(v3_param_changes** const outparamsptr, const int32_t offset) + { + DISTRHO_SAFE_ASSERT_RETURN(outparamsptr != nullptr,); + + v3_param_id paramId; + float curValue; + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + for (v3_param_id i=kVst3InternalParameterBufferSize; i<=kVst3InternalParameterSampleRate; ++i) + { + if (! fParameterValuesChangedDuringProcessing[i]) + continue; + + curValue = plainParameterToNormalized(i, fCachedParameterValues[i]); + fParameterValuesChangedDuringProcessing[i] = false; + addParameterDataToHostOutputEvents(outparamsptr, i, curValue); + } + #endif + + for (uint32_t i=0; i<fParameterCount; ++i) + { + if (fPlugin.isParameterOutput(i)) + { + // NOTE: no output parameter support in VST3, simulate it here + curValue = fPlugin.getParameterValue(i); + + if (d_isEqual(curValue, fCachedParameterValues[kVst3InternalParameterBaseCount + i])) + continue; + } + else if (fPlugin.isParameterTrigger(i)) + { + // NOTE: no trigger support in VST3 parameters, simulate it here + curValue = fPlugin.getParameterValue(i); + + if (d_isEqual(curValue, fPlugin.getParameterDefault(i))) + continue; + + fPlugin.setParameterValue(i, curValue); + } + else if (fParameterValuesChangedDuringProcessing[kVst3InternalParameterBaseCount + i]) + { + fParameterValuesChangedDuringProcessing[kVst3InternalParameterBaseCount + i] = false; + curValue = fPlugin.getParameterValue(i); + } + else + { + continue; + } + + fCachedParameterValues[kVst3InternalParameterBaseCount + i] = curValue; + #if DISTRHO_PLUGIN_HAS_UI + fParameterValueChangesForUI[kVst3InternalParameterBaseCount + i] = true; + #endif + + paramId = kVst3InternalParameterCount + i; + curValue = fPlugin.getParameterRanges(i).getNormalizedValue(curValue); + + if (! addParameterDataToHostOutputEvents(outparamsptr, paramId, curValue, offset)) + break; + } + + #if DISTRHO_PLUGIN_WANT_LATENCY + const uint32_t latency = fPlugin.getLatency(); + + if (fLastKnownLatency != latency) + { + fLastKnownLatency = latency; + + curValue = plainParameterToNormalized(kVst3InternalParameterLatency, + fCachedParameterValues[kVst3InternalParameterLatency]); + addParameterDataToHostOutputEvents(outparamsptr, kVst3InternalParameterLatency, curValue); + } + #endif + } + + bool addParameterDataToHostOutputEvents(v3_param_changes** const outparamsptr, + v3_param_id paramId, + const float curValue, + const int32_t offset = 0) + { + int32_t index = 0; + v3_param_value_queue** const queue = v3_cpp_obj(outparamsptr)->add_param_data(outparamsptr, + &paramId, &index); + DISTRHO_SAFE_ASSERT_RETURN(queue != nullptr, false); + DISTRHO_SAFE_ASSERT_RETURN(v3_cpp_obj(queue)->add_point(queue, 0, curValue, &index) == V3_OK, false); + + if (offset != 0) + v3_cpp_obj(queue)->add_point(queue, offset, curValue, &index); + + return true; + } + + #if DISTRHO_PLUGIN_HAS_UI + // ---------------------------------------------------------------------------------------------------------------- + // helper functions called during message passing, can block + + v3_message** createMessage(const char* const id) const + { + DISTRHO_SAFE_ASSERT_RETURN(fHostApplication != nullptr, nullptr); + + v3_tuid iid; + memcpy(iid, v3_message_iid, sizeof(v3_tuid)); + v3_message** msg = nullptr; + const v3_result res = v3_cpp_obj(fHostApplication)->create_instance(fHostApplication, iid, iid, (void**)&msg); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_TRUE, res, nullptr); + DISTRHO_SAFE_ASSERT_RETURN(msg != nullptr, nullptr); + + v3_cpp_obj(msg)->set_message_id(msg, id); + return msg; + } + + void sendParameterSetToUI(const v3_param_id rindex, const double value) const + { + v3_message** const message = createMessage("parameter-set"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 2); + v3_cpp_obj(attrlist)->set_int(attrlist, "rindex", rindex); + v3_cpp_obj(attrlist)->set_float(attrlist, "value", value); + v3_cpp_obj(fConnectionFromCtrlToView)->notify(fConnectionFromCtrlToView, message); + + v3_cpp_obj_unref(message); + } + + void sendStateSetToUI(const char* const key, const char* const value) const + { + v3_message** const message = createMessage("state-set"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 2); + v3_cpp_obj(attrlist)->set_int(attrlist, "key:length", std::strlen(key)); + v3_cpp_obj(attrlist)->set_int(attrlist, "value:length", std::strlen(value)); + v3_cpp_obj(attrlist)->set_string(attrlist, "key", ScopedUTF16String(key)); + v3_cpp_obj(attrlist)->set_string(attrlist, "value", ScopedUTF16String(value)); + v3_cpp_obj(fConnectionFromCtrlToView)->notify(fConnectionFromCtrlToView, message); + + v3_cpp_obj_unref(message); + } + + void sendReadyToUI() const + { + v3_message** const message = createMessage("ready"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 2); + v3_cpp_obj(fConnectionFromCtrlToView)->notify(fConnectionFromCtrlToView, message); + + v3_cpp_obj_unref(message); + } + #endif + + // ---------------------------------------------------------------------------------------------------------------- + // DPF callbacks + + #if DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST + bool requestParameterValueChange(const uint32_t index, float) + { + fParameterValuesChangedDuringProcessing[kVst3InternalParameterBaseCount + index] = true; + return true; + } + + static bool requestParameterValueChangeCallback(void* const ptr, const uint32_t index, const float value) + { + return ((PluginVst3*)ptr)->requestParameterValueChange(index, value); + } + #endif + + #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + bool writeMidi(const MidiEvent& midiEvent) + { + DISTRHO_CUSTOM_SAFE_ASSERT_ONCE_RETURN("MIDI output unsupported", fHostEventOutputHandle != nullptr, false); + + v3_event event; + std::memset(&event, 0, sizeof(event)); + event.sample_offset = midiEvent.frame; + + const uint8_t* const data = midiEvent.size > MidiEvent::kDataSize ? midiEvent.dataExt : midiEvent.data; + + switch (data[0] & 0xf0) + { + case 0x80: + event.type = V3_EVENT_NOTE_OFF; + event.note_off.channel = data[0] & 0xf; + event.note_off.pitch = data[1]; + event.note_off.velocity = (float)data[2] / 127.0f; + // int32_t note_id; + // float tuning; + break; + case 0x90: + event.type = V3_EVENT_NOTE_ON; + event.note_on.channel = data[0] & 0xf; + event.note_on.pitch = data[1]; + // float tuning; + event.note_on.velocity = (float)data[2] / 127.0f; + // int32_t length; + // int32_t note_id; + break; + case 0xA0: + event.type = V3_EVENT_POLY_PRESSURE; + event.poly_pressure.channel = data[0] & 0xf; + event.poly_pressure.pitch = data[1]; + event.poly_pressure.pressure = (float)data[2] / 127.0f; + // int32_t note_id; + break; + case 0xB0: + event.type = V3_EVENT_LEGACY_MIDI_CC_OUT; + event.midi_cc_out.channel = data[0] & 0xf; + event.midi_cc_out.cc_number = data[1]; + event.midi_cc_out.value = data[2]; + if (midiEvent.size == 4) + event.midi_cc_out.value2 = midiEvent.size == 4; + break; + /* TODO how do we deal with program changes?? + case 0xC0: + break; + */ + case 0xD0: + event.type = V3_EVENT_LEGACY_MIDI_CC_OUT; + event.midi_cc_out.channel = data[0] & 0xf; + event.midi_cc_out.cc_number = 128; + event.midi_cc_out.value = data[1]; + break; + case 0xE0: + event.type = V3_EVENT_LEGACY_MIDI_CC_OUT; + event.midi_cc_out.channel = data[0] & 0xf; + event.midi_cc_out.cc_number = 129; + event.midi_cc_out.value = data[1]; + event.midi_cc_out.value2 = data[2]; + break; + default: + return true; + } + + return v3_cpp_obj(fHostEventOutputHandle)->add_event(fHostEventOutputHandle, &event) == V3_OK; + } + + static bool writeMidiCallback(void* ptr, const MidiEvent& midiEvent) + { + return ((PluginVst3*)ptr)->writeMidi(midiEvent); + } + #endif +}; + +// -------------------------------------------------------------------------------------------------------------------- + +/** + * VST3 low-level pointer thingies follow, proceed with care. + */ + +// -------------------------------------------------------------------------------------------------------------------- +// v3_funknown for static instances + +static uint32_t V3_API dpf_static_ref(void*) { return 1; } +static uint32_t V3_API dpf_static_unref(void*) { return 0; } + +// -------------------------------------------------------------------------------------------------------------------- +// v3_funknown for classes with a single instance + +template<class T> +static uint32_t V3_API dpf_single_instance_ref(void* const self) +{ + return ++(*static_cast<T**>(self))->refcounter; +} + +template<class T> +static uint32_t V3_API dpf_single_instance_unref(void* const self) +{ + return --(*static_cast<T**>(self))->refcounter; +} + +// -------------------------------------------------------------------------------------------------------------------- +// Store components that we can't delete properly, to be cleaned up on module unload + +struct dpf_component; + +static std::vector<dpf_component**> gComponentGarbage; + +static uint32_t handleUncleanComponent(dpf_component** const componentptr) +{ + gComponentGarbage.push_back(componentptr); + return 0; +} + +#if DPF_VST3_USES_SEPARATE_CONTROLLER +// -------------------------------------------------------------------------------------------------------------------- +// Store controllers that we can't delete properly, to be cleaned up on module unload + +struct dpf_edit_controller; + +static std::vector<dpf_edit_controller**> gControllerGarbage; + +static uint32_t handleUncleanController(dpf_edit_controller** const controllerptr) +{ + gControllerGarbage.push_back(controllerptr); + return 0; +} + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_comp2ctrl_connection_point + +struct dpf_comp2ctrl_connection_point : v3_connection_point_cpp { + std::atomic_int refcounter; + ScopedPointer<PluginVst3>& vst3; + v3_connection_point** other; + + dpf_comp2ctrl_connection_point(ScopedPointer<PluginVst3>& v) + : refcounter(1), + vst3(v), + other(nullptr) + { + // v3_funknown, single instance + query_interface = query_interface_connection_point; + ref = dpf_single_instance_ref<dpf_comp2ctrl_connection_point>; + unref = dpf_single_instance_unref<dpf_comp2ctrl_connection_point>; + + // v3_connection_point + point.connect = connect; + point.disconnect = disconnect; + point.notify = notify; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_connection_point(void* const self, const v3_tuid iid, void** const iface) + { + dpf_comp2ctrl_connection_point* const point = *static_cast<dpf_comp2ctrl_connection_point**>(self); + + if (v3_tuid_match(iid, v3_funknown_iid) || + v3_tuid_match(iid, v3_connection_point_iid)) + { + d_stdout("dpf_comp2ctrl_connection_point => %p %s %p | OK", self, tuid2str(iid), iface); + ++point->refcounter; + *iface = self; + return V3_OK; + } + + d_stdout("dpf_comp2ctrl_connection_point => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface); + + *iface = nullptr; + return V3_NO_INTERFACE; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_connection_point + + static v3_result V3_API connect(void* const self, v3_connection_point** const other) + { + d_stdout("dpf_comp2ctrl_connection_point::connect => %p %p", self, other); + dpf_comp2ctrl_connection_point* const point = *static_cast<dpf_comp2ctrl_connection_point**>(self); + DISTRHO_SAFE_ASSERT_RETURN(point->other == nullptr, V3_INVALID_ARG); + DISTRHO_SAFE_ASSERT_RETURN(point->other != other, V3_INVALID_ARG); + + point->other = other; + + if (PluginVst3* const vst3 = point->vst3) + vst3->comp2ctrl_connect(other); + + return V3_OK; + } + + static v3_result V3_API disconnect(void* const self, v3_connection_point** const other) + { + d_stdout("dpf_comp2ctrl_connection_point => %p %p", self, other); + dpf_comp2ctrl_connection_point* const point = *static_cast<dpf_comp2ctrl_connection_point**>(self); + DISTRHO_SAFE_ASSERT_RETURN(point->other != nullptr, V3_INVALID_ARG); + DISTRHO_SAFE_ASSERT_RETURN(point->other == other, V3_INVALID_ARG); + + if (PluginVst3* const vst3 = point->vst3) + vst3->comp2ctrl_disconnect(); + + point->other = nullptr; + + return V3_OK; + } + + static v3_result V3_API notify(void* const self, v3_message** const message) + { + dpf_comp2ctrl_connection_point* const point = *static_cast<dpf_comp2ctrl_connection_point**>(self); + + PluginVst3* const vst3 = point->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + v3_connection_point** const other = point->other; + DISTRHO_SAFE_ASSERT_RETURN(other != nullptr, V3_NOT_INITIALIZED); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr, V3_INVALID_ARG); + + int64_t target = 0; + const v3_result res = v3_cpp_obj(attrlist)->get_int(attrlist, "__dpf_msg_target__", &target); + DISTRHO_SAFE_ASSERT_RETURN(res == V3_OK, res); + DISTRHO_SAFE_ASSERT_INT_RETURN(target == 1, target, V3_INTERNAL_ERR); + + // view -> edit controller -> component + return vst3->comp2ctrl_notify(message); + } +}; +#endif // DPF_VST3_USES_SEPARATE_CONTROLLER + +#if DISTRHO_PLUGIN_HAS_UI +// -------------------------------------------------------------------------------------------------------------------- +// dpf_ctrl2view_connection_point + +struct dpf_ctrl2view_connection_point : v3_connection_point_cpp { + ScopedPointer<PluginVst3>& vst3; + v3_connection_point** other; + + dpf_ctrl2view_connection_point(ScopedPointer<PluginVst3>& v) + : vst3(v), + other(nullptr) + { + // v3_funknown, single instance, used internally + query_interface = nullptr; + ref = nullptr; + unref = nullptr; + + // v3_connection_point + point.connect = connect; + point.disconnect = disconnect; + point.notify = notify; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_connection_point + + static v3_result V3_API connect(void* const self, v3_connection_point** const other) + { + d_stdout("dpf_ctrl2view_connection_point::connect => %p %p", self, other); + dpf_ctrl2view_connection_point* const point = *static_cast<dpf_ctrl2view_connection_point**>(self); + DISTRHO_SAFE_ASSERT_RETURN(point->other == nullptr, V3_INVALID_ARG); + DISTRHO_SAFE_ASSERT_RETURN(point->other != other, V3_INVALID_ARG); + + point->other = other; + + if (PluginVst3* const vst3 = point->vst3) + vst3->ctrl2view_connect(other); + + return V3_OK; + } + + static v3_result V3_API disconnect(void* const self, v3_connection_point** const other) + { + d_stdout("dpf_ctrl2view_connection_point::disconnect => %p %p", self, other); + dpf_ctrl2view_connection_point* const point = *static_cast<dpf_ctrl2view_connection_point**>(self); + DISTRHO_SAFE_ASSERT_RETURN(point->other != nullptr, V3_INVALID_ARG); + DISTRHO_SAFE_ASSERT_RETURN(point->other == other, V3_INVALID_ARG); + + if (PluginVst3* const vst3 = point->vst3) + vst3->ctrl2view_disconnect(); + + v3_cpp_obj_unref(point->other); + point->other = nullptr; + + return V3_OK; + } + + static v3_result V3_API notify(void* const self, v3_message** const message) + { + dpf_ctrl2view_connection_point* const point = *static_cast<dpf_ctrl2view_connection_point**>(self); + + PluginVst3* const vst3 = point->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + v3_connection_point** const other = point->other; + DISTRHO_SAFE_ASSERT_RETURN(other != nullptr, V3_NOT_INITIALIZED); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr, V3_INVALID_ARG); + + int64_t target = 0; + const v3_result res = v3_cpp_obj(attrlist)->get_int(attrlist, "__dpf_msg_target__", &target); + DISTRHO_SAFE_ASSERT_RETURN(res == V3_OK, res); + DISTRHO_SAFE_ASSERT_INT_RETURN(target == 1 || target == 2, target, V3_INTERNAL_ERR); + + if (target == 1) + { + // view -> edit controller + return vst3->ctrl2view_notify(message); + } + else + { + // edit controller -> view + return v3_cpp_obj(other)->notify(other, message); + } + } +}; +#endif // DISTRHO_PLUGIN_HAS_UI + +#if DISTRHO_PLUGIN_WANT_MIDI_INPUT +// -------------------------------------------------------------------------------------------------------------------- +// dpf_midi_mapping + +struct dpf_midi_mapping : v3_midi_mapping_cpp { + dpf_midi_mapping() + { + // v3_funknown, static + query_interface = query_interface_midi_mapping; + ref = dpf_static_ref; + unref = dpf_static_unref; + + // v3_midi_mapping + map.get_midi_controller_assignment = get_midi_controller_assignment; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_midi_mapping(void* const self, const v3_tuid iid, void** const iface) + { + if (v3_tuid_match(iid, v3_funknown_iid) || + v3_tuid_match(iid, v3_midi_mapping_iid)) + { + d_stdout("query_interface_midi_mapping => %p %s %p | OK", self, tuid2str(iid), iface); + *iface = self; + return V3_OK; + } + + d_stdout("query_interface_midi_mapping => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface); + + *iface = nullptr; + return V3_NO_INTERFACE; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_midi_mapping + + static v3_result V3_API get_midi_controller_assignment(void*, const int32_t bus, const int16_t channel, const int16_t cc, v3_param_id* const id) + { + DISTRHO_SAFE_ASSERT_INT_RETURN(bus == 0, bus, V3_FALSE); + DISTRHO_SAFE_ASSERT_INT_RETURN(channel >= 0 && channel < 16, channel, V3_FALSE); + DISTRHO_SAFE_ASSERT_INT_RETURN(cc >= 0 && cc < 130, cc, V3_FALSE); + + *id = kVst3InternalParameterMidiCC_start + channel * 130 + cc; + return V3_TRUE; + } + + DISTRHO_PREVENT_HEAP_ALLOCATION +}; +#endif // DISTRHO_PLUGIN_WANT_MIDI_INPUT + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_edit_controller + +struct dpf_edit_controller : v3_edit_controller_cpp { + std::atomic_int refcounter; + #if DISTRHO_PLUGIN_HAS_UI + ScopedPointer<dpf_ctrl2view_connection_point> connectionCtrl2View; + #endif + #if DPF_VST3_USES_SEPARATE_CONTROLLER + ScopedPointer<dpf_comp2ctrl_connection_point> connectionComp2Ctrl; + ScopedPointer<PluginVst3> vst3; + #else + ScopedPointer<PluginVst3>& vst3; + bool initialized; + #endif + // cached values + v3_component_handler** handler; + v3_host_application** const hostApplicationFromFactory; + #if !DPF_VST3_USES_SEPARATE_CONTROLLER + v3_host_application** const hostApplicationFromComponent; + v3_host_application** hostApplicationFromComponentInitialize; + #endif + v3_host_application** hostApplicationFromInitialize; + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + dpf_edit_controller(v3_host_application** const hostApp) + : refcounter(1), + vst3(nullptr), + #else + dpf_edit_controller(ScopedPointer<PluginVst3>& v, v3_host_application** const hostApp, v3_host_application** const hostComp) + : refcounter(1), + vst3(v), + initialized(false), + #endif + handler(nullptr), + hostApplicationFromFactory(hostApp), + #if !DPF_VST3_USES_SEPARATE_CONTROLLER + hostApplicationFromComponent(hostComp), + hostApplicationFromComponentInitialize(nullptr), + #endif + hostApplicationFromInitialize(nullptr) + { + d_stdout("dpf_edit_controller() with hostApplication %p", hostApplicationFromFactory); + + // make sure host application is valid through out this controller lifetime + if (hostApplicationFromFactory != nullptr) + v3_cpp_obj_ref(hostApplicationFromFactory); + #if !DPF_VST3_USES_SEPARATE_CONTROLLER + if (hostApplicationFromComponent != nullptr) + v3_cpp_obj_ref(hostApplicationFromComponent); + #endif + + // v3_funknown, everything custom + query_interface = query_interface_edit_controller; + ref = ref_edit_controller; + unref = unref_edit_controller; + + // v3_plugin_base + base.initialize = initialize; + base.terminate = terminate; + + // v3_edit_controller + ctrl.set_component_state = set_component_state; + ctrl.set_state = set_state; + ctrl.get_state = get_state; + ctrl.get_parameter_count = get_parameter_count; + ctrl.get_parameter_info = get_parameter_info; + ctrl.get_parameter_string_for_value = get_parameter_string_for_value; + ctrl.get_parameter_value_for_string = get_parameter_value_for_string; + ctrl.normalised_parameter_to_plain = normalised_parameter_to_plain; + ctrl.plain_parameter_to_normalised = plain_parameter_to_normalised; + ctrl.get_parameter_normalised = get_parameter_normalised; + ctrl.set_parameter_normalised = set_parameter_normalised; + ctrl.set_component_handler = set_component_handler; + ctrl.create_view = create_view; + } + + ~dpf_edit_controller() + { + d_stdout("~dpf_edit_controller()"); + #if DISTRHO_PLUGIN_HAS_UI + connectionCtrl2View = nullptr; + #endif + #if DPF_VST3_USES_SEPARATE_CONTROLLER + connectionComp2Ctrl = nullptr; + vst3 = nullptr; + #endif + + #if !DPF_VST3_USES_SEPARATE_CONTROLLER + if (hostApplicationFromComponent != nullptr) + v3_cpp_obj_unref(hostApplicationFromComponent); + #endif + if (hostApplicationFromFactory != nullptr) + v3_cpp_obj_unref(hostApplicationFromFactory); + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_edit_controller(void* const self, const v3_tuid iid, void** const iface) + { + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + if (v3_tuid_match(iid, v3_funknown_iid) || + v3_tuid_match(iid, v3_plugin_base_iid) || + v3_tuid_match(iid, v3_edit_controller_iid)) + { + d_stdout("query_interface_edit_controller => %p %s %p | OK", self, tuid2str(iid), iface); + ++controller->refcounter; + *iface = self; + return V3_OK; + } + + if (v3_tuid_match(iid, v3_midi_mapping_iid)) + { + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + d_stdout("query_interface_edit_controller => %p %s %p | OK convert static", self, tuid2str(iid), iface); + static dpf_midi_mapping midi_mapping; + static dpf_midi_mapping* midi_mapping_ptr = &midi_mapping; + *iface = &midi_mapping_ptr; + return V3_OK; + #else + d_stdout("query_interface_edit_controller => %p %s %p | reject unused", self, tuid2str(iid), iface); + *iface = nullptr; + return V3_NO_INTERFACE; + #endif + } + + if (v3_tuid_match(iid, v3_connection_point_iid)) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER + d_stdout("query_interface_edit_controller => %p %s %p | OK convert %p", + self, tuid2str(iid), iface, controller->connectionComp2Ctrl.get()); + + if (controller->connectionComp2Ctrl == nullptr) + controller->connectionComp2Ctrl = new dpf_comp2ctrl_connection_point(controller->vst3); + else + ++controller->connectionComp2Ctrl->refcounter; + *iface = &controller->connectionComp2Ctrl; + return V3_OK; + #else + d_stdout("query_interface_edit_controller => %p %s %p | reject unwanted", self, tuid2str(iid), iface); + *iface = nullptr; + return V3_NO_INTERFACE; + #endif + } + + d_stdout("query_interface_edit_controller => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface); + *iface = nullptr; + return V3_NO_INTERFACE; + } + + static uint32_t V3_API ref_edit_controller(void* const self) + { + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + const int refcount = ++controller->refcounter; + d_stdout("dpf_edit_controller::ref => %p | refcount %i", self, refcount); + return refcount; + } + + static uint32_t V3_API unref_edit_controller(void* const self) + { + dpf_edit_controller** const controllerptr = static_cast<dpf_edit_controller**>(self); + dpf_edit_controller* const controller = *controllerptr; + + if (const int refcount = --controller->refcounter) + { + d_stdout("dpf_edit_controller::unref => %p | refcount %i", self, refcount); + return refcount; + } + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + /** + * Some hosts will have unclean instances of a few of the controller child classes at this point. + * We check for those here, going through the whole possible chain to see if it is safe to delete. + * If not, we add this controller to the `gControllerGarbage` global which will take care of it during unload. + */ + + bool unclean = false; + + if (dpf_comp2ctrl_connection_point* const point = controller->connectionComp2Ctrl) + { + if (const int refcount = point->refcounter) + { + unclean = true; + d_stderr("DPF warning: asked to delete controller while component connection point still active (refcount %d)", refcount); + } + } + + if (unclean) + return handleUncleanController(controllerptr); + + d_stdout("dpf_edit_controller::unref => %p | refcount is zero, deleting everything now!", self); + + delete controller; + delete controllerptr; + #else + d_stdout("dpf_edit_controller::unref => %p | refcount is zero, deletion will be done by component later", self); + #endif + return 0; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_plugin_base + + static v3_result V3_API initialize(void* const self, v3_funknown** const context) + { + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + // check if already initialized + #if DPF_VST3_USES_SEPARATE_CONTROLLER + DISTRHO_SAFE_ASSERT_RETURN(controller->vst3 == nullptr, V3_INVALID_ARG); + #else + DISTRHO_SAFE_ASSERT_RETURN(! controller->initialized, V3_INVALID_ARG); + #endif + + // query for host application + v3_host_application** hostApplication = nullptr; + if (context != nullptr) + v3_cpp_obj_query_interface(context, v3_host_application_iid, &hostApplication); + + d_stdout("dpf_edit_controller::initialize => %p %p | host %p", self, context, hostApplication); + + // save it for later so we can unref it + controller->hostApplicationFromInitialize = hostApplication; + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + // provide the factory application to the plugin if this new one is missing + if (hostApplication == nullptr) + hostApplication = controller->hostApplicationFromFactory; + + // default early values + if (d_nextBufferSize == 0) + d_nextBufferSize = 1024; + if (d_nextSampleRate <= 0.0) + d_nextSampleRate = 44100.0; + + d_nextCanRequestParameterValueChanges = true; + + // create the actual plugin + controller->vst3 = new PluginVst3(hostApplication); + + // set connection point if needed + if (dpf_comp2ctrl_connection_point* const point = controller->connectionComp2Ctrl) + { + if (point->other != nullptr) + controller->vst3->comp2ctrl_connect(point->other); + } + #else + // mark as initialized + controller->initialized = true; + #endif + + return V3_OK; + } + + static v3_result V3_API terminate(void* self) + { + d_stdout("dpf_edit_controller::terminate => %p", self); + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + // check if already terminated + DISTRHO_SAFE_ASSERT_RETURN(controller->vst3 != nullptr, V3_INVALID_ARG); + + // delete actual plugin + controller->vst3 = nullptr; + #else + // check if already terminated + DISTRHO_SAFE_ASSERT_RETURN(controller->initialized, V3_INVALID_ARG); + + // mark as uninitialzed + controller->initialized = false; + #endif + + // unref host application received during initialize + if (controller->hostApplicationFromInitialize != nullptr) + { + v3_cpp_obj_unref(controller->hostApplicationFromInitialize); + controller->hostApplicationFromInitialize = nullptr; + } + + return V3_OK; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_edit_controller + + static v3_result V3_API set_component_state(void* const self, v3_bstream** const stream) + { + d_stdout("dpf_edit_controller::set_component_state => %p %p", self, stream); + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->setState(stream); + #else + return V3_OK; + + // unused + (void)self; + (void)stream; + #endif + } + + static v3_result V3_API set_state(void* const self, v3_bstream** const stream) + { + d_stdout("dpf_edit_controller::set_state => %p %p", self, stream); + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + DISTRHO_SAFE_ASSERT_RETURN(controller->vst3 != nullptr, V3_NOT_INITIALIZED); + #endif + + return V3_NOT_IMPLEMENTED; + + // maybe unused + (void)self; + (void)stream; + } + + static v3_result V3_API get_state(void* const self, v3_bstream** const stream) + { + d_stdout("dpf_edit_controller::get_state => %p %p", self, stream); + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + DISTRHO_SAFE_ASSERT_RETURN(controller->vst3 != nullptr, V3_NOT_INITIALIZED); + #endif + + return V3_NOT_IMPLEMENTED; + + // maybe unused + (void)self; + (void)stream; + } + + static int32_t V3_API get_parameter_count(void* self) + { + // d_stdout("dpf_edit_controller::get_parameter_count => %p", self); + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->getParameterCount(); + } + + static v3_result V3_API get_parameter_info(void* self, int32_t param_idx, v3_param_info* param_info) + { + // d_stdout("dpf_edit_controller::get_parameter_info => %p %i", self, param_idx); + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->getParameterInfo(param_idx, param_info); + } + + static v3_result V3_API get_parameter_string_for_value(void* self, v3_param_id index, double normalised, v3_str_128 output) + { + // NOTE very noisy, called many times + // d_stdout("dpf_edit_controller::get_parameter_string_for_value => %p %u %f %p", self, index, normalised, output); + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->getParameterStringForValue(index, normalised, output); + } + + static v3_result V3_API get_parameter_value_for_string(void* self, v3_param_id index, int16_t* input, double* output) + { + d_debug("dpf_edit_controller::get_parameter_value_for_string => %p %u %p %p", self, index, input, output); + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->getParameterValueForString(index, input, output); + } + + static double V3_API normalised_parameter_to_plain(void* self, v3_param_id index, double normalised) + { + d_debug("dpf_edit_controller::normalised_parameter_to_plain => %p %u %f", self, index, normalised); + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->normalizedParameterToPlain(index, normalised); + } + + static double V3_API plain_parameter_to_normalised(void* self, v3_param_id index, double plain) + { + d_debug("dpf_edit_controller::plain_parameter_to_normalised => %p %u %f", self, index, plain); + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->plainParameterToNormalized(index, plain); + } + + static double V3_API get_parameter_normalised(void* self, v3_param_id index) + { + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, 0.0); + + return vst3->getParameterNormalized(index); + } + + static v3_result V3_API set_parameter_normalised(void* const self, const v3_param_id index, const double normalised) + { + // d_stdout("dpf_edit_controller::set_parameter_normalised => %p %u %f", self, index, normalised); + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->setParameterNormalized(index, normalised); + } + + static v3_result V3_API set_component_handler(void* self, v3_component_handler** handler) + { + d_stdout("dpf_edit_controller::set_component_handler => %p %p", self, handler); + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + controller->handler = handler; + + if (PluginVst3* const vst3 = controller->vst3) + return vst3->setComponentHandler(handler); + + return V3_NOT_INITIALIZED; + } + + static v3_plugin_view** V3_API create_view(void* self, const char* name) + { + d_stdout("dpf_edit_controller::create_view => %p %s", self, name); + dpf_edit_controller* const controller = *static_cast<dpf_edit_controller**>(self); + + d_stdout("create_view has contexts %p %p", + controller->hostApplicationFromFactory, controller->hostApplicationFromInitialize); + + #if DISTRHO_PLUGIN_HAS_UI + // plugin must be initialized + PluginVst3* const vst3 = controller->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, nullptr); + + d_stdout("dpf_edit_controller::create_view => %p %s | edit-ctrl %p, factory %p", + self, name, + controller->hostApplicationFromInitialize, + controller->hostApplicationFromFactory); + + // we require a host application for message creation + v3_host_application** const host = controller->hostApplicationFromInitialize != nullptr + ? controller->hostApplicationFromInitialize + #if !DPF_VST3_USES_SEPARATE_CONTROLLER + : controller->hostApplicationFromComponent != nullptr + ? controller->hostApplicationFromComponent + : controller->hostApplicationFromComponentInitialize != nullptr + ? controller->hostApplicationFromComponentInitialize + #endif + : controller->hostApplicationFromFactory; + DISTRHO_SAFE_ASSERT_RETURN(host != nullptr, nullptr); + + v3_plugin_view** const view = dpf_plugin_view_create(host, + vst3->getInstancePointer(), + vst3->getSampleRate()); + DISTRHO_SAFE_ASSERT_RETURN(view != nullptr, nullptr); + + v3_connection_point** uiconn = nullptr; + if (v3_cpp_obj_query_interface(view, v3_connection_point_iid, &uiconn) == V3_OK) + { + d_stdout("view connection query ok %p", uiconn); + controller->connectionCtrl2View = new dpf_ctrl2view_connection_point(controller->vst3); + + v3_connection_point** const ctrlconn = (v3_connection_point**)&controller->connectionCtrl2View; + + v3_cpp_obj(uiconn)->connect(uiconn, ctrlconn); + v3_cpp_obj(ctrlconn)->connect(ctrlconn, uiconn); + } + else + { + controller->connectionCtrl2View = nullptr; + } + + return view; + #else + return nullptr; + #endif + } +}; + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_process_context_requirements + +struct dpf_process_context_requirements : v3_process_context_requirements_cpp { + dpf_process_context_requirements() + { + // v3_funknown, static + query_interface = query_interface_process_context_requirements; + ref = dpf_static_ref; + unref = dpf_static_unref; + + // v3_process_context_requirements + req.get_process_context_requirements = get_process_context_requirements; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_process_context_requirements(void* const self, const v3_tuid iid, void** const iface) + { + if (v3_tuid_match(iid, v3_funknown_iid) || + v3_tuid_match(iid, v3_process_context_requirements_iid)) + { + d_stdout("query_interface_process_context_requirements => %p %s %p | OK", self, tuid2str(iid), iface); + *iface = self; + return V3_OK; + } + + d_stdout("query_interface_process_context_requirements => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface); + + *iface = nullptr; + return V3_NO_INTERFACE; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_process_context_requirements + + static uint32_t V3_API get_process_context_requirements(void*) + { + #if DISTRHO_PLUGIN_WANT_TIMEPOS + return 0x0 + | V3_PROCESS_CTX_NEED_CONTINUOUS_TIME // V3_PROCESS_CTX_CONT_TIME_VALID + | V3_PROCESS_CTX_NEED_PROJECT_TIME // V3_PROCESS_CTX_PROJECT_TIME_VALID + | V3_PROCESS_CTX_NEED_TEMPO // V3_PROCESS_CTX_TEMPO_VALID + | V3_PROCESS_CTX_NEED_TIME_SIG // V3_PROCESS_CTX_TIME_SIG_VALID + | V3_PROCESS_CTX_NEED_TRANSPORT_STATE; // V3_PROCESS_CTX_PLAYING + #else + return 0x0; + #endif + } + + DISTRHO_PREVENT_HEAP_ALLOCATION +}; + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_audio_processor + +struct dpf_audio_processor : v3_audio_processor_cpp { + std::atomic_int refcounter; + ScopedPointer<PluginVst3>& vst3; + + dpf_audio_processor(ScopedPointer<PluginVst3>& v) + : refcounter(1), + vst3(v) + { + // v3_funknown, single instance + query_interface = query_interface_audio_processor; + ref = dpf_single_instance_ref<dpf_audio_processor>; + unref = dpf_single_instance_unref<dpf_audio_processor>; + + // v3_audio_processor + proc.set_bus_arrangements = set_bus_arrangements; + proc.get_bus_arrangement = get_bus_arrangement; + proc.can_process_sample_size = can_process_sample_size; + proc.get_latency_samples = get_latency_samples; + proc.setup_processing = setup_processing; + proc.set_processing = set_processing; + proc.process = process; + proc.get_tail_samples = get_tail_samples; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_audio_processor(void* const self, const v3_tuid iid, void** const iface) + { + dpf_audio_processor* const processor = *static_cast<dpf_audio_processor**>(self); + + if (v3_tuid_match(iid, v3_funknown_iid) || + v3_tuid_match(iid, v3_audio_processor_iid)) + { + d_stdout("query_interface_audio_processor => %p %s %p | OK", self, tuid2str(iid), iface); + ++processor->refcounter; + *iface = self; + return V3_OK; + } + + if (v3_tuid_match(iid, v3_process_context_requirements_iid)) + { + d_stdout("query_interface_audio_processor => %p %s %p | OK convert static", self, tuid2str(iid), iface); + static dpf_process_context_requirements context_req; + static dpf_process_context_requirements* context_req_ptr = &context_req; + *iface = &context_req_ptr; + return V3_OK; + } + + d_stdout("query_interface_audio_processor => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface); + + *iface = nullptr; + return V3_NO_INTERFACE; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_audio_processor + + static v3_result V3_API set_bus_arrangements(void* const self, + v3_speaker_arrangement* const inputs, const int32_t num_inputs, + v3_speaker_arrangement* const outputs, const int32_t num_outputs) + { + // NOTE this is called a bunch of times in JUCE hosts + d_stdout("dpf_audio_processor::set_bus_arrangements => %p %p %i %p %i", + self, inputs, num_inputs, outputs, num_outputs); + dpf_audio_processor* const processor = *static_cast<dpf_audio_processor**>(self); + + PluginVst3* const vst3 = processor->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return processor->vst3->setBusArrangements(inputs, num_inputs, outputs, num_outputs); + } + + static v3_result V3_API get_bus_arrangement(void* const self, const int32_t bus_direction, + const int32_t idx, v3_speaker_arrangement* const arr) + { + d_stdout("dpf_audio_processor::get_bus_arrangement => %p %s %i %p", + self, v3_bus_direction_str(bus_direction), idx, arr); + dpf_audio_processor* const processor = *static_cast<dpf_audio_processor**>(self); + + PluginVst3* const vst3 = processor->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return processor->vst3->getBusArrangement(bus_direction, idx, arr); + } + + static v3_result V3_API can_process_sample_size(void*, const int32_t symbolic_sample_size) + { + // NOTE runs during RT + // d_stdout("dpf_audio_processor::can_process_sample_size => %i", symbolic_sample_size); + return symbolic_sample_size == V3_SAMPLE_32 ? V3_OK : V3_NOT_IMPLEMENTED; + } + + static uint32_t V3_API get_latency_samples(void* const self) + { + d_stdout("dpf_audio_processor::get_latency_samples => %p", self); + dpf_audio_processor* const processor = *static_cast<dpf_audio_processor**>(self); + + PluginVst3* const vst3 = processor->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, 0); + + return processor->vst3->getLatencySamples(); + } + + static v3_result V3_API setup_processing(void* const self, v3_process_setup* const setup) + { + d_stdout("dpf_audio_processor::setup_processing => %p %p", self, setup); + dpf_audio_processor* const processor = *static_cast<dpf_audio_processor**>(self); + + PluginVst3* const vst3 = processor->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + d_stdout("dpf_audio_processor::setup_processing => %p %p | %d %f", self, setup, setup->max_block_size, setup->sample_rate); + + d_nextBufferSize = setup->max_block_size; + d_nextSampleRate = setup->sample_rate; + return processor->vst3->setupProcessing(setup); + } + + static v3_result V3_API set_processing(void* const self, const v3_bool state) + { + d_stdout("dpf_audio_processor::set_processing => %p %u", self, state); + dpf_audio_processor* const processor = *static_cast<dpf_audio_processor**>(self); + + PluginVst3* const vst3 = processor->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return processor->vst3->setProcessing(state); + } + + static v3_result V3_API process(void* const self, v3_process_data* const data) + { + // NOTE runs during RT + // d_stdout("dpf_audio_processor::process => %p", self); + dpf_audio_processor* const processor = *static_cast<dpf_audio_processor**>(self); + + PluginVst3* const vst3 = processor->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return processor->vst3->process(data); + } + + static uint32_t V3_API get_tail_samples(void* const self) + { + d_stdout("dpf_audio_processor::get_tail_samples => %p", self); + dpf_audio_processor* const processor = *static_cast<dpf_audio_processor**>(self); + + PluginVst3* const vst3 = processor->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, 0); + + return processor->vst3->getTailSamples(); + } +}; + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_component + +struct dpf_component : v3_component_cpp { + std::atomic_int refcounter; + ScopedPointer<dpf_audio_processor> processor; + #if DPF_VST3_USES_SEPARATE_CONTROLLER + ScopedPointer<dpf_comp2ctrl_connection_point> connectionComp2Ctrl; + #else + ScopedPointer<dpf_edit_controller> controller; + #endif + ScopedPointer<PluginVst3> vst3; + v3_host_application** const hostApplicationFromFactory; + v3_host_application** hostApplicationFromInitialize; -START_NAMESPACE_DISTRHO + dpf_component(v3_host_application** const host) + : refcounter(1), + hostApplicationFromFactory(host), + hostApplicationFromInitialize(nullptr) + { + d_stdout("dpf_component() with hostApplication %p", hostApplicationFromFactory); -#if ! DISTRHO_PLUGIN_WANT_MIDI_OUTPUT -static const writeMidiFunc writeMidiCallback = nullptr; -#endif -#if ! DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST -static const requestParameterValueChangeFunc requestParameterValueChangeCallback = nullptr; -#endif + // make sure host application is valid through out this component lifetime + if (hostApplicationFromFactory != nullptr) + v3_cpp_obj_ref(hostApplicationFromFactory); -// custom v3_tuid compatible type -typedef uint32_t dpf_tuid[4]; -static_assert(sizeof(v3_tuid) == sizeof(dpf_tuid), "uid size mismatch"); + // v3_funknown, everything custom + query_interface = query_interface_component; + ref = ref_component; + unref = unref_component; -// custom uids, fully created during module init -static constexpr const uint32_t dpf_id_entry = d_cconst('D', 'P', 'F', ' '); -static constexpr const uint32_t dpf_id_clas = d_cconst('c', 'l', 'a', 's'); -static constexpr const uint32_t dpf_id_comp = d_cconst('c', 'o', 'm', 'p'); -static constexpr const uint32_t dpf_id_ctrl = d_cconst('c', 't', 'r', 'l'); -static constexpr const uint32_t dpf_id_proc = d_cconst('p', 'r', 'o', 'c'); -static constexpr const uint32_t dpf_id_view = d_cconst('v', 'i', 'e', 'w'); + // v3_plugin_base + base.initialize = initialize; + base.terminate = terminate; -static dpf_tuid dpf_tuid_class = { dpf_id_entry, dpf_id_clas, 0, 0 }; -static dpf_tuid dpf_tuid_component = { dpf_id_entry, dpf_id_comp, 0, 0 }; -static dpf_tuid dpf_tuid_controller = { dpf_id_entry, dpf_id_ctrl, 0, 0 }; -static dpf_tuid dpf_tuid_processor = { dpf_id_entry, dpf_id_proc, 0, 0 }; -static dpf_tuid dpf_tuid_view = { dpf_id_entry, dpf_id_view, 0, 0 }; + // v3_component + comp.get_controller_class_id = get_controller_class_id; + comp.set_io_mode = set_io_mode; + comp.get_bus_count = get_bus_count; + comp.get_bus_info = get_bus_info; + comp.get_routing_info = get_routing_info; + comp.activate_bus = activate_bus; + comp.set_active = set_active; + comp.set_state = set_state; + comp.get_state = get_state; + } -// ----------------------------------------------------------------------- + ~dpf_component() + { + d_stdout("~dpf_component()"); + processor = nullptr; + #if DPF_VST3_USES_SEPARATE_CONTROLLER + connectionComp2Ctrl = nullptr; + #else + controller = nullptr; + #endif + vst3 = nullptr; -void strncpy(char* const dst, const char* const src, const size_t size) -{ - DISTRHO_SAFE_ASSERT_RETURN(size > 0,); + if (hostApplicationFromFactory != nullptr) + v3_cpp_obj_unref(hostApplicationFromFactory); + } - if (const size_t len = std::min(std::strlen(src), size-1U)) + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_component(void* const self, const v3_tuid iid, void** const iface) { - std::memcpy(dst, src, len); - dst[len] = '\0'; + dpf_component* const component = *static_cast<dpf_component**>(self); + + if (v3_tuid_match(iid, v3_funknown_iid) || + v3_tuid_match(iid, v3_plugin_base_iid) || + v3_tuid_match(iid, v3_component_iid)) + { + d_stdout("query_interface_component => %p %s %p | OK", self, tuid2str(iid), iface); + ++component->refcounter; + *iface = self; + return V3_OK; + } + + if (v3_tuid_match(iid, v3_midi_mapping_iid)) + { + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + d_stdout("query_interface_component => %p %s %p | OK convert static", self, tuid2str(iid), iface); + static dpf_midi_mapping midi_mapping; + static dpf_midi_mapping* midi_mapping_ptr = &midi_mapping; + *iface = &midi_mapping_ptr; + return V3_OK; + #else + d_stdout("query_interface_component => %p %s %p | reject unused", self, tuid2str(iid), iface); + *iface = nullptr; + return V3_NO_INTERFACE; + #endif + } + + if (v3_tuid_match(iid, v3_audio_processor_iid)) + { + d_stdout("query_interface_component => %p %s %p | OK convert %p", + self, tuid2str(iid), iface, component->processor.get()); + + if (component->processor == nullptr) + component->processor = new dpf_audio_processor(component->vst3); + else + ++component->processor->refcounter; + *iface = &component->processor; + return V3_OK; + } + + if (v3_tuid_match(iid, v3_connection_point_iid)) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER + d_stdout("query_interface_component => %p %s %p | OK convert %p", + self, tuid2str(iid), iface, component->connectionComp2Ctrl.get()); + + if (component->connectionComp2Ctrl == nullptr) + component->connectionComp2Ctrl = new dpf_comp2ctrl_connection_point(component->vst3); + else + ++component->connectionComp2Ctrl->refcounter; + *iface = &component->connectionComp2Ctrl; + return V3_OK; + #else + d_stdout("query_interface_component => %p %s %p | reject unwanted", self, tuid2str(iid), iface); + *iface = nullptr; + return V3_NO_INTERFACE; + #endif + } + + if (v3_tuid_match(iid, v3_edit_controller_iid)) + { + #if !DPF_VST3_USES_SEPARATE_CONTROLLER + d_stdout("query_interface_component => %p %s %p | OK convert %p", + self, tuid2str(iid), iface, component->controller.get()); + + if (component->controller == nullptr) + component->controller = new dpf_edit_controller(component->vst3, + component->hostApplicationFromFactory, + component->hostApplicationFromInitialize); + else + ++component->controller->refcounter; + *iface = &component->controller; + return V3_OK; + #else + d_stdout("query_interface_component => %p %s %p | reject unwanted", self, tuid2str(iid), iface); + *iface = nullptr; + return V3_NO_INTERFACE; + #endif + } + + d_stdout("query_interface_component => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface); + *iface = nullptr; + return V3_NO_INTERFACE; } - else + + static uint32_t V3_API ref_component(void* const self) { - dst[0] = '\0'; + dpf_component* const component = *static_cast<dpf_component**>(self); + const int refcount = ++component->refcounter; + d_stdout("dpf_component::ref => %p | refcount %i", self, refcount); + return refcount; } -} -// ----------------------------------------------------------------------- + static uint32_t V3_API unref_component(void* const self) + { + dpf_component** const componentptr = static_cast<dpf_component**>(self); + dpf_component* const component = *componentptr; -// v3_funknown, v3_plugin_base, v3_component + if (const int refcount = --component->refcounter) + { + d_stdout("dpf_component::unref => %p | refcount %i", self, refcount); + return refcount; + } -// audio_processor + /** + * Some hosts will have unclean instances of a few of the component child classes at this point. + * We check for those here, going through the whole possible chain to see if it is safe to delete. + * If not, we add this component to the `gComponentGarbage` global which will take care of it during unload. + */ -class PluginVst3 -{ -public: - PluginVst3() - : fPlugin(this, writeMidiCallback, requestParameterValueChangeCallback) + bool unclean = false; + + if (dpf_audio_processor* const proc = component->processor) + { + if (const int refcount = proc->refcounter) + { + unclean = true; + d_stderr("DPF warning: asked to delete component while audio processor still active (refcount %d)", refcount); + } + } + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + if (dpf_comp2ctrl_connection_point* const point = component->connectionComp2Ctrl) + { + if (const int refcount = point->refcounter) + { + unclean = true; + d_stderr("DPF warning: asked to delete component while connection point still active (refcount %d)", refcount); + } + } + #else + if (dpf_edit_controller* const controller = component->controller) + { + if (const int refcount = controller->refcounter) + { + unclean = true; + d_stderr("DPF warning: asked to delete component while edit controller still active (refcount %d)", refcount); + } + } + #endif + + if (unclean) + return handleUncleanComponent(componentptr); + + d_stdout("dpf_component::unref => %p | refcount is zero, deleting everything now!", self); + + delete component; + delete componentptr; + return 0; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_plugin_base + + static v3_result V3_API initialize(void* const self, v3_funknown** const context) + { + dpf_component* const component = *static_cast<dpf_component**>(self); + + // check if already initialized + DISTRHO_SAFE_ASSERT_RETURN(component->vst3 == nullptr, V3_INVALID_ARG); + + // query for host application + v3_host_application** hostApplication = nullptr; + if (context != nullptr) + v3_cpp_obj_query_interface(context, v3_host_application_iid, &hostApplication); + + d_stdout("dpf_component::initialize => %p %p | hostApplication %p", self, context, hostApplication); + + // save it for later so we can unref it + component->hostApplicationFromInitialize = hostApplication; + + #if !DPF_VST3_USES_SEPARATE_CONTROLLER + // save it in edit controller too, needed for some hosts + if (component->controller != nullptr) + component->controller->hostApplicationFromComponentInitialize = hostApplication; + #endif + + // provide the factory application to the plugin if this new one is missing + if (hostApplication == nullptr) + hostApplication = component->hostApplicationFromFactory; + + // default early values + if (d_nextBufferSize == 0) + d_nextBufferSize = 1024; + if (d_nextSampleRate <= 0.0) + d_nextSampleRate = 44100.0; + + d_nextCanRequestParameterValueChanges = true; + + // create the actual plugin + component->vst3 = new PluginVst3(hostApplication); + + #if DPF_VST3_USES_SEPARATE_CONTROLLER + // set connection point if needed + if (dpf_comp2ctrl_connection_point* const point = component->connectionComp2Ctrl) + { + if (point->other != nullptr) + component->vst3->comp2ctrl_connect(point->other); + } + #endif + + return V3_OK; + } + + static v3_result V3_API terminate(void* const self) { + d_stdout("dpf_component::terminate => %p", self); + dpf_component* const component = *static_cast<dpf_component**>(self); + + // check if already terminated + DISTRHO_SAFE_ASSERT_RETURN(component->vst3 != nullptr, V3_INVALID_ARG); + + // delete actual plugin + component->vst3 = nullptr; + + #if !DPF_VST3_USES_SEPARATE_CONTROLLER + // remove previous host application saved during initialize + if (component->controller != nullptr) + component->controller->hostApplicationFromComponentInitialize = nullptr; + #endif + + // unref host application received during initialize + if (component->hostApplicationFromInitialize != nullptr) + { + v3_cpp_obj_unref(component->hostApplicationFromInitialize); + component->hostApplicationFromInitialize = nullptr; + } + + return V3_OK; } -private: - // Plugin - PluginExporter fPlugin; + // ---------------------------------------------------------------------------------------------------------------- + // v3_component - // VST3 stuff - // TODO + static v3_result V3_API get_controller_class_id(void*, v3_tuid class_id) + { + d_stdout("dpf_component::get_controller_class_id => %p", class_id); + + std::memcpy(class_id, dpf_tuid_controller, sizeof(v3_tuid)); + return V3_OK; + } -#if DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST - bool requestParameterValueChange(uint32_t, float) + static v3_result V3_API set_io_mode(void* const self, const int32_t io_mode) { + d_stdout("dpf_component::set_io_mode => %p %i", self, io_mode); + + dpf_component* const component = *static_cast<dpf_component**>(self); + + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + // TODO - return true; + return V3_NOT_IMPLEMENTED; } - static bool requestParameterValueChangeCallback(void* const ptr, const uint32_t index, const float value) + static int32_t V3_API get_bus_count(void* const self, const int32_t media_type, const int32_t bus_direction) { - return ((PluginVst3*)ptr)->requestParameterValueChange(index, value); + // NOTE runs during RT + // d_stdout("dpf_component::get_bus_count => %p %s %s", + // self, v3_media_type_str(media_type), v3_bus_direction_str(bus_direction)); + dpf_component* const component = *static_cast<dpf_component**>(self); + + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + const int32_t ret = vst3->getBusCount(media_type, bus_direction); + // d_stdout("dpf_component::get_bus_count returns %i", ret); + return ret; } -#endif -#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT - bool writeMidi(const MidiEvent& midiEvent) + static v3_result V3_API get_bus_info(void* const self, const int32_t media_type, const int32_t bus_direction, + const int32_t bus_idx, v3_bus_info* const info) { - // TODO - return true; + d_stdout("dpf_component::get_bus_info => %p %s %s %i %p", + self, v3_media_type_str(media_type), v3_bus_direction_str(bus_direction), bus_idx, info); + dpf_component* const component = *static_cast<dpf_component**>(self); + + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->getBusInfo(media_type, bus_direction, bus_idx, info); } - static bool writeMidiCallback(void* ptr, const MidiEvent& midiEvent) + static v3_result V3_API get_routing_info(void* const self, v3_routing_info* const input, v3_routing_info* const output) { - return ((PluginVst3*)ptr)->writeMidi(midiEvent); + d_stdout("dpf_component::get_routing_info => %p %p %p", self, input, output); + dpf_component* const component = *static_cast<dpf_component**>(self); + + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); + + return vst3->getRoutingInfo(input, output); } -#endif -}; + static v3_result V3_API activate_bus(void* const self, const int32_t media_type, const int32_t bus_direction, + const int32_t bus_idx, const v3_bool state) + { + // NOTE this is called a bunch of times + // d_stdout("dpf_component::activate_bus => %p %s %s %i %u", + // self, v3_media_type_str(media_type), v3_bus_direction_str(bus_direction), bus_idx, state); + dpf_component* const component = *static_cast<dpf_component**>(self); -// ----------------------------------------------------------------------- -// WIP this whole section still TODO + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); -struct ControllerComponent; -struct ProcessorComponent; + return vst3->activateBus(media_type, bus_direction, bus_idx, state); + } -struct ComponentAdapter : v3_funknown, v3_plugin_base -{ - // needs atomic refcount, starts at 1 - - ComponentAdapter() - { - static const uint8_t* kSupportedFactories[] = { - v3_funknown_iid, - v3_plugin_base_iid, - /* - v3_component_iid, - v3_edit_controller_iid, - v3_audio_processor_iid - */ - }; + static v3_result V3_API set_active(void* const self, const v3_bool state) + { + d_stdout("dpf_component::set_active => %p %u", self, state); + dpf_component* const component = *static_cast<dpf_component**>(self); - // ------------------------------------------------------------------------------------------------------------ - // v3_funknown + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); - query_interface = []V3_API(void* self, const v3_tuid iid, void** iface) -> v3_result - { - d_stdout("ComponentAdapter::query_interface %p %p %p", self, iid, iface); + return component->vst3->setActive(state); + } - *iface = NULL; - DISTRHO_SAFE_ASSERT_RETURN(self != nullptr, V3_NO_INTERFACE); + static v3_result V3_API set_state(void* const self, v3_bstream** const stream) + { + d_stdout("dpf_component::set_state => %p", self); + dpf_component* const component = *static_cast<dpf_component**>(self); - for (const uint8_t* factory_iid : kSupportedFactories) - { - if (v3_tuid_match(factory_iid, iid)) - { - *iface = self; - return V3_OK; - } - } + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); - return V3_NO_INTERFACE; - }; + return vst3->setState(stream); + } + + static v3_result V3_API get_state(void* const self, v3_bstream** const stream) + { + d_stdout("dpf_component::get_state => %p %p", self, stream); + dpf_component* const component = *static_cast<dpf_component**>(self); + + PluginVst3* const vst3 = component->vst3; + DISTRHO_SAFE_ASSERT_RETURN(vst3 != nullptr, V3_NOT_INITIALIZED); - // TODO use atomic counter - ref = []V3_API(void*) -> uint32_t { return 1; }; - unref = []V3_API(void*) -> uint32_t { return 0; }; + return vst3->getState(stream); } }; -struct ControllerComponent : ComponentAdapter +// -------------------------------------------------------------------------------------------------------------------- +// Dummy plugin to get data from + +static const PluginExporter& _getPluginInfo() { -}; + d_nextBufferSize = 1024; + d_nextSampleRate = 44100.0; + d_nextPluginIsDummy = true; + d_nextCanRequestParameterValueChanges = true; + static const PluginExporter gPluginInfo(nullptr, nullptr, nullptr, nullptr); + d_nextBufferSize = 0; + d_nextSampleRate = 0.0; + d_nextPluginIsDummy = false; + d_nextCanRequestParameterValueChanges = false; + + return gPluginInfo; +} -struct ProcessorComponent : ComponentAdapter +static const PluginExporter& getPluginInfo() { -}; + static const PluginExporter& info(_getPluginInfo()); + return info; +} -// -------------------------------------------------------------------------------------------------------------------- -// Dummy plugin to get data from +static const char* getPluginCategories() +{ + static String categories; + static bool firstInit = true; + + if (firstInit) + { +#ifdef DISTRHO_PLUGIN_VST3_CATEGORIES + categories = DISTRHO_PLUGIN_VST3_CATEGORIES; +#elif DISTRHO_PLUGIN_IS_SYNTH + categories = "Instrument"; +#endif +#if (DISTRHO_PLUGIN_NUM_INPUTS == 0 || DISTRHO_PLUGIN_NUM_INPUTS == 1) && DISTRHO_PLUGIN_NUM_OUTPUTS == 1 + if (categories.isNotEmpty()) + categories += "|"; + categories += "Mono"; +#elif (DISTRHO_PLUGIN_NUM_INPUTS == 0 || DISTRHO_PLUGIN_NUM_INPUTS == 2) && DISTRHO_PLUGIN_NUM_OUTPUTS == 2 + if (categories.isNotEmpty()) + categories += "|"; + categories += "Stereo"; +#endif + firstInit = false; + } -static ScopedPointer<PluginExporter> gPluginInfo; + return categories.buffer(); +} -static void gPluginInit() +static const char* getPluginVersion() { - if (gPluginInfo != nullptr) - return; + static String version; + + if (version.isEmpty()) + { + const uint32_t versionNum = getPluginInfo().getVersion(); - d_lastBufferSize = 512; - d_lastSampleRate = 44100.0; - gPluginInfo = new PluginExporter(nullptr, nullptr, nullptr); - d_lastBufferSize = 0; - d_lastSampleRate = 0.0; + char versionBuf[64]; + std::snprintf(versionBuf, sizeof(versionBuf)-1, "%d.%d.%d", + (versionNum >> 16) & 0xff, + (versionNum >> 8) & 0xff, + (versionNum >> 0) & 0xff); + versionBuf[sizeof(versionBuf)-1] = '\0'; + version = versionBuf; + } - dpf_tuid_class[3] = dpf_tuid_component[3] = dpf_tuid_controller[3] - = dpf_tuid_processor[3] = dpf_tuid_view[3] = gPluginInfo->getUniqueId(); + return version.buffer(); } // -------------------------------------------------------------------------------------------------------------------- // dpf_factory -struct v3_plugin_factory_cpp : v3_funknown, v3_plugin_factory { - v3_plugin_factory_2 v2; - v3_plugin_factory_3 v3; -}; - struct dpf_factory : v3_plugin_factory_cpp { + std::atomic_int refcounter; + + // cached values + v3_funknown** hostContext; + dpf_factory() + : refcounter(1), + hostContext(nullptr) { - static const uint8_t* kSupportedFactories[] = { - v3_funknown_iid, - v3_plugin_factory_iid, - v3_plugin_factory_2_iid - }; + // v3_funknown, static + query_interface = query_interface_factory; + ref = ref_factory; + unref = unref_factory; + + // v3_plugin_factory + v1.get_factory_info = get_factory_info; + v1.num_classes = num_classes; + v1.get_class_info = get_class_info; + v1.create_instance = create_instance; + + // v3_plugin_factory_2 + v2.get_class_info_2 = get_class_info_2; + + // v3_plugin_factory_3 + v3.get_class_info_utf16 = get_class_info_utf16; + v3.set_host_context = set_host_context; + } - // ------------------------------------------------------------------------------------------------------------ - // v3_funknown + ~dpf_factory() + { + // unref old context if there is one + if (hostContext != nullptr) + v3_cpp_obj_unref(hostContext); - query_interface = []V3_API(void* self, const v3_tuid iid, void** iface) -> v3_result + #if DPF_VST3_USES_SEPARATE_CONTROLLER + if (gControllerGarbage.size() != 0) { - *iface = NULL; - DISTRHO_SAFE_ASSERT_RETURN(self != nullptr, V3_NO_INTERFACE); + d_stdout("DPF notice: cleaning up previously undeleted controllers now"); - for (const uint8_t* factory_iid : kSupportedFactories) + for (std::vector<dpf_edit_controller**>::iterator it = gControllerGarbage.begin(); + it != gControllerGarbage.end(); ++it) { - if (v3_tuid_match(factory_iid, iid)) - { - *iface = self; - return V3_OK; - } + dpf_edit_controller** const controllerptr = *it; + dpf_edit_controller* const controller = *controllerptr; + delete controller; + delete controllerptr; } - return V3_NO_INTERFACE; - }; + gControllerGarbage.clear(); + } + #endif + + if (gComponentGarbage.size() != 0) + { + d_stdout("DPF notice: cleaning up previously undeleted components now"); - // we only support 1 plugin per binary, so don't have to care here - ref = []V3_API(void*) -> uint32_t { return 1; }; - unref = []V3_API(void*) -> uint32_t { return 0; }; + for (std::vector<dpf_component**>::iterator it = gComponentGarbage.begin(); + it != gComponentGarbage.end(); ++it) + { + dpf_component** const componentptr = *it; + dpf_component* const component = *componentptr; + delete component; + delete componentptr; + } - // ------------------------------------------------------------------------------------------------------------ - // v3_plugin_factory + gComponentGarbage.clear(); + } + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_factory(void* const self, const v3_tuid iid, void** const iface) + { + dpf_factory* const factory = *static_cast<dpf_factory**>(self); - get_factory_info = []V3_API(void*, struct v3_factory_info* const info) -> v3_result + if (v3_tuid_match(iid, v3_funknown_iid) || + v3_tuid_match(iid, v3_plugin_factory_iid) || + v3_tuid_match(iid, v3_plugin_factory_2_iid) || + v3_tuid_match(iid, v3_plugin_factory_3_iid)) { - DISTRHO_NAMESPACE::strncpy(info->vendor, gPluginInfo->getMaker(), sizeof(info->vendor)); - DISTRHO_NAMESPACE::strncpy(info->url, gPluginInfo->getHomePage(), sizeof(info->url)); - DISTRHO_NAMESPACE::strncpy(info->email, "", sizeof(info->email)); // TODO + d_stdout("query_interface_factory => %p %s %p | OK", self, tuid2str(iid), iface); + ++factory->refcounter; + *iface = self; return V3_OK; - }; + } + + d_stdout("query_interface_factory => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface); + + *iface = nullptr; + return V3_NO_INTERFACE; + } + + static uint32_t V3_API ref_factory(void* const self) + { + dpf_factory* const factory = *static_cast<dpf_factory**>(self); + const int refcount = ++factory->refcounter; + d_stdout("ref_factory::ref => %p | refcount %i", self, refcount); + return refcount; + } + + static uint32_t V3_API unref_factory(void* const self) + { + dpf_factory** const factoryptr = static_cast<dpf_factory**>(self); + dpf_factory* const factory = *factoryptr; - num_classes = []V3_API(void*) -> int32_t + if (const int refcount = --factory->refcounter) { - return 1; - }; + d_stdout("unref_factory::unref => %p | refcount %i", self, refcount); + return refcount; + } + + d_stdout("unref_factory::unref => %p | refcount is zero, deleting factory", self); + + delete factory; + delete factoryptr; + return 0; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_plugin_factory + + static v3_result V3_API get_factory_info(void*, v3_factory_info* const info) + { + d_stdout("dpf_factory::get_factory_info => %p", info); + std::memset(info, 0, sizeof(*info)); + + info->flags = 0x10; // unicode + DISTRHO_NAMESPACE::strncpy(info->vendor, getPluginInfo().getMaker(), ARRAY_SIZE(info->vendor)); + DISTRHO_NAMESPACE::strncpy(info->url, getPluginInfo().getHomePage(), ARRAY_SIZE(info->url)); + // DISTRHO_NAMESPACE::strncpy(info->email, "", ARRAY_SIZE(info->email)); // TODO + return V3_OK; + } + + static int32_t V3_API num_classes(void*) + { + d_stdout("dpf_factory::num_classes"); + #if DPF_VST3_USES_SEPARATE_CONTROLLER + return 2; // factory can create component and edit-controller + #else + return 1; // factory can only create component, edit-controller must be casted + #endif + } + + static v3_result V3_API get_class_info(void*, const int32_t idx, v3_class_info* const info) + { + d_stdout("dpf_factory::get_class_info => %i %p", idx, info); + std::memset(info, 0, sizeof(*info)); + DISTRHO_SAFE_ASSERT_RETURN(idx <= 2, V3_INVALID_ARG); + + info->cardinality = 0x7FFFFFFF; + DISTRHO_NAMESPACE::strncpy(info->name, getPluginInfo().getName(), ARRAY_SIZE(info->name)); - get_class_info = []V3_API(void*, int32_t /*idx*/, struct v3_class_info* const info) -> v3_result + if (idx == 0) { - memcpy(info->class_id, dpf_tuid_class, sizeof(v3_tuid)); - info->cardinality = 0x7FFFFFFF; - DISTRHO_NAMESPACE::strncpy(info->category, "Audio Module Class", sizeof(info->category)); - DISTRHO_NAMESPACE::strncpy(info->name, gPluginInfo->getName(), sizeof(info->name)); + std::memcpy(info->class_id, dpf_tuid_class, sizeof(v3_tuid)); + DISTRHO_NAMESPACE::strncpy(info->category, "Audio Module Class", ARRAY_SIZE(info->category)); + } + else + { + std::memcpy(info->class_id, dpf_tuid_controller, sizeof(v3_tuid)); + DISTRHO_NAMESPACE::strncpy(info->category, "Component Controller Class", ARRAY_SIZE(info->category)); + } + + return V3_OK; + } + + static v3_result V3_API create_instance(void* self, const v3_tuid class_id, const v3_tuid iid, void** const instance) + { + d_stdout("dpf_factory::create_instance => %p %s %s %p", self, tuid2str(class_id), tuid2str(iid), instance); + dpf_factory* const factory = *static_cast<dpf_factory**>(self); + + // query for host application + v3_host_application** hostApplication = nullptr; + if (factory->hostContext != nullptr) + v3_cpp_obj_query_interface(factory->hostContext, v3_host_application_iid, &hostApplication); + + // create component + if (v3_tuid_match(class_id, *(const v3_tuid*)&dpf_tuid_class) && (v3_tuid_match(iid, v3_component_iid) || + v3_tuid_match(iid, v3_funknown_iid))) + { + dpf_component** const componentptr = new dpf_component*; + *componentptr = new dpf_component(hostApplication); + *instance = static_cast<void*>(componentptr); return V3_OK; - }; + } - create_instance = []V3_API(void* self, const v3_tuid class_id, const v3_tuid iid, void** instance) -> v3_result + #if DPF_VST3_USES_SEPARATE_CONTROLLER + // create edit controller + if (v3_tuid_match(class_id, *(const v3_tuid*)&dpf_tuid_controller) && (v3_tuid_match(iid, v3_edit_controller_iid) || + v3_tuid_match(iid, v3_funknown_iid))) { - d_stdout("%s %i %p %p %p %p", __PRETTY_FUNCTION__, __LINE__, self, class_id, iid, instance); - DISTRHO_SAFE_ASSERT_RETURN(v3_tuid_match(class_id, *(v3_tuid*)&dpf_tuid_class) && - v3_tuid_match(iid, v3_component_iid), V3_NO_INTERFACE); + dpf_edit_controller** const controllerptr = new dpf_edit_controller*; + *controllerptr = new dpf_edit_controller(hostApplication); + *instance = static_cast<void*>(controllerptr); + return V3_OK; + } + #endif - *instance = nullptr; // new ComponentAdapter(); - return V3_INTERNAL_ERR; - }; + // unsupported, roll back host application + if (hostApplication != nullptr) + v3_cpp_obj_unref(hostApplication); - // ------------------------------------------------------------------------------------------------------------ - // v3_plugin_factory_2 + return V3_NO_INTERFACE; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_plugin_factory_2 + + static v3_result V3_API get_class_info_2(void*, const int32_t idx, v3_class_info_2* const info) + { + d_stdout("dpf_factory::get_class_info_2 => %i %p", idx, info); + std::memset(info, 0, sizeof(*info)); + DISTRHO_SAFE_ASSERT_RETURN(idx <= 2, V3_INVALID_ARG); + + info->cardinality = 0x7FFFFFFF; + #if DPF_VST3_USES_SEPARATE_CONTROLLER || !DISTRHO_PLUGIN_HAS_UI + info->class_flags = V3_DISTRIBUTABLE; + #endif + DISTRHO_NAMESPACE::strncpy(info->sub_categories, getPluginCategories(), ARRAY_SIZE(info->sub_categories)); + DISTRHO_NAMESPACE::strncpy(info->name, getPluginInfo().getName(), ARRAY_SIZE(info->name)); + DISTRHO_NAMESPACE::strncpy(info->vendor, getPluginInfo().getMaker(), ARRAY_SIZE(info->vendor)); + DISTRHO_NAMESPACE::strncpy(info->version, getPluginVersion(), ARRAY_SIZE(info->version)); + DISTRHO_NAMESPACE::strncpy(info->sdk_version, "Travesty 3.7.4", ARRAY_SIZE(info->sdk_version)); - v2.get_class_info_2 = []V3_API(void*, int32_t /*idx*/, struct v3_class_info_2 *info) -> v3_result + if (idx == 0) { - // get_class_info - memcpy(info->class_id, dpf_tuid_class, sizeof(v3_tuid)); - info->cardinality = 0x7FFFFFFF; - DISTRHO_NAMESPACE::strncpy(info->category, "Audio Module Class", sizeof(info->category)); - DISTRHO_NAMESPACE::strncpy(info->name, gPluginInfo->getName(), sizeof(info->name)); - // get_class_info_2 - info->class_flags = 0; - DISTRHO_NAMESPACE::strncpy(info->sub_categories, "", sizeof(info->sub_categories)); // TODO - DISTRHO_NAMESPACE::strncpy(info->vendor, gPluginInfo->getMaker(), sizeof(info->vendor)); - DISTRHO_NAMESPACE::strncpy(info->version, "", sizeof(info->version)); // TODO - DISTRHO_NAMESPACE::strncpy(info->sdk_version, "Travesty", sizeof(info->sdk_version)); // TESTING use "VST 3.7" ? - return V3_OK; - }; + std::memcpy(info->class_id, dpf_tuid_class, sizeof(v3_tuid)); + DISTRHO_NAMESPACE::strncpy(info->category, "Audio Module Class", ARRAY_SIZE(info->category)); + } + else + { + std::memcpy(info->class_id, dpf_tuid_controller, sizeof(v3_tuid)); + DISTRHO_NAMESPACE::strncpy(info->category, "Component Controller Class", ARRAY_SIZE(info->category)); + } + + return V3_OK; + } + + // ------------------------------------------------------------------------------------------------------------ + // v3_plugin_factory_3 + + static v3_result V3_API get_class_info_utf16(void*, const int32_t idx, v3_class_info_3* const info) + { + d_stdout("dpf_factory::get_class_info_utf16 => %i %p", idx, info); + std::memset(info, 0, sizeof(*info)); + DISTRHO_SAFE_ASSERT_RETURN(idx <= 2, V3_INVALID_ARG); + + info->cardinality = 0x7FFFFFFF; + #if DPF_VST3_USES_SEPARATE_CONTROLLER || !DISTRHO_PLUGIN_HAS_UI + info->class_flags = V3_DISTRIBUTABLE; + #endif + DISTRHO_NAMESPACE::strncpy(info->sub_categories, getPluginCategories(), ARRAY_SIZE(info->sub_categories)); + DISTRHO_NAMESPACE::strncpy_utf16(info->name, getPluginInfo().getName(), ARRAY_SIZE(info->name)); + DISTRHO_NAMESPACE::strncpy_utf16(info->vendor, getPluginInfo().getMaker(), ARRAY_SIZE(info->vendor)); + DISTRHO_NAMESPACE::strncpy_utf16(info->version, getPluginVersion(), ARRAY_SIZE(info->version)); + DISTRHO_NAMESPACE::strncpy_utf16(info->sdk_version, "Travesty 3.7.4", ARRAY_SIZE(info->sdk_version)); + + if (idx == 0) + { + std::memcpy(info->class_id, dpf_tuid_class, sizeof(v3_tuid)); + DISTRHO_NAMESPACE::strncpy(info->category, "Audio Module Class", ARRAY_SIZE(info->category)); + } + else + { + std::memcpy(info->class_id, dpf_tuid_controller, sizeof(v3_tuid)); + DISTRHO_NAMESPACE::strncpy(info->category, "Component Controller Class", ARRAY_SIZE(info->category)); + } + + return V3_OK; } -}; -static const dpf_factory dpf_factory; + static v3_result V3_API set_host_context(void* const self, v3_funknown** const context) + { + d_stdout("dpf_factory::set_host_context => %p %p", self, context); + dpf_factory* const factory = *static_cast<dpf_factory**>(self); + + // unref old context if there is one + if (factory->hostContext != nullptr) + v3_cpp_obj_unref(factory->hostContext); + + // store new context + factory->hostContext = context; + + // make sure the object keeps being valid for a while + if (context != nullptr) + v3_cpp_obj_ref(context); + + return V3_OK; + } +}; END_NAMESPACE_DISTRHO @@ -303,8 +4825,9 @@ const void* GetPluginFactory(void); const void* GetPluginFactory(void) { USE_NAMESPACE_DISTRHO; - static const struct v3_plugin_factory_2* const factory = (v3_plugin_factory_2*)&dpf_factory; - return &factory; + dpf_factory** const factoryptr = new dpf_factory*; + *factoryptr = new dpf_factory; + return static_cast<void*>(factoryptr); } // -------------------------------------------------------------------------------------------------------------------- @@ -312,22 +4835,49 @@ const void* GetPluginFactory(void) #if defined(DISTRHO_OS_MAC) # define ENTRYFNNAME bundleEntry +# define ENTRYFNNAMEARGS void* # define EXITFNNAME bundleExit #elif defined(DISTRHO_OS_WINDOWS) # define ENTRYFNNAME InitDll +# define ENTRYFNNAMEARGS void # define EXITFNNAME ExitDll #else # define ENTRYFNNAME ModuleEntry +# define ENTRYFNNAMEARGS void* # define EXITFNNAME ModuleExit #endif DISTRHO_PLUGIN_EXPORT -bool ENTRYFNNAME(void*); +bool ENTRYFNNAME(ENTRYFNNAMEARGS); -bool ENTRYFNNAME(void*) +bool ENTRYFNNAME(ENTRYFNNAMEARGS) { USE_NAMESPACE_DISTRHO; - gPluginInit(); + + // find plugin bundle + static String bundlePath; + if (bundlePath.isEmpty()) + { + String tmpPath(getBinaryFilename()); + tmpPath.truncate(tmpPath.rfind(DISTRHO_OS_SEP)); + tmpPath.truncate(tmpPath.rfind(DISTRHO_OS_SEP)); + + if (tmpPath.endsWith(DISTRHO_OS_SEP_STR "Contents")) + { + tmpPath.truncate(tmpPath.rfind(DISTRHO_OS_SEP)); + bundlePath = tmpPath; + d_nextBundlePath = bundlePath.buffer(); + } + else + { + bundlePath = "error"; + } + } + + // init dummy plugin and set uniqueId + dpf_tuid_class[2] = dpf_tuid_component[2] = dpf_tuid_controller[2] + = dpf_tuid_processor[2] = dpf_tuid_view[2] = getPluginInfo().getUniqueId(); + return true; } @@ -336,8 +4886,6 @@ bool EXITFNNAME(void); bool EXITFNNAME(void) { - USE_NAMESPACE_DISTRHO; - gPluginInfo = nullptr; return true; } diff --git a/distrho/src/DistrhoUI.cpp b/distrho/src/DistrhoUI.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -15,10 +15,46 @@ */ #include "src/DistrhoPluginChecks.h" +#include "src/DistrhoDefines.h" + +#include <cstddef> + +#ifdef DISTRHO_PROPER_CPP11_SUPPORT +# include <cstdint> +#else +# include <stdint.h> +#endif + +#if DISTRHO_UI_FILE_BROWSER && !defined(DISTRHO_OS_MAC) +# define DISTRHO_PUGL_NAMESPACE_MACRO_HELPER(NS, SEP, FUNCTION) NS ## SEP ## FUNCTION +# define DISTRHO_PUGL_NAMESPACE_MACRO(NS, FUNCTION) DISTRHO_PUGL_NAMESPACE_MACRO_HELPER(NS, _, FUNCTION) +# define x_fib_add_recent DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_add_recent) +# define x_fib_cfg_buttons DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_cfg_buttons) +# define x_fib_cfg_filter_callback DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_cfg_filter_callback) +# define x_fib_close DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_close) +# define x_fib_configure DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_configure) +# define x_fib_filename DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_filename) +# define x_fib_free_recent DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_free_recent) +# define x_fib_handle_events DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_handle_events) +# define x_fib_load_recent DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_load_recent) +# define x_fib_recent_at DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_recent_at) +# define x_fib_recent_count DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_recent_count) +# define x_fib_recent_file DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_recent_file) +# define x_fib_save_recent DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_save_recent) +# define x_fib_show DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_show) +# define x_fib_status DISTRHO_PUGL_NAMESPACE_MACRO(plugin, x_fib_status) +# define DISTRHO_FILE_BROWSER_DIALOG_HPP_INCLUDED +# define FILE_BROWSER_DIALOG_NAMESPACE DISTRHO_NAMESPACE +# define FILE_BROWSER_DIALOG_DISTRHO_NAMESPACE +START_NAMESPACE_DISTRHO +# include "../extra/FileBrowserDialogImpl.hpp" +END_NAMESPACE_DISTRHO +# include "../extra/FileBrowserDialogImpl.cpp" +#endif #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI # if defined(DISTRHO_OS_WINDOWS) -# define WIN32_LEAN_AND_MEAN +# include <winsock2.h> # include <windows.h> # elif defined(HAVE_X11) # include <X11/Xresource.h> @@ -32,14 +68,16 @@ START_NAMESPACE_DISTRHO -#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI /* ------------------------------------------------------------------------------------------------------------ * Static data, see DistrhoUIInternal.hpp */ +const char* g_nextBundlePath = nullptr; +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI uintptr_t g_nextWindowId = 0; double g_nextScaleFactor = 1.0; -const char* g_nextBundlePath = nullptr; +#endif +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI /* ------------------------------------------------------------------------------------------------------------ * get global scale factor */ @@ -71,22 +109,18 @@ static double getDesktopScaleFactor(const uintptr_t parentWindowHandle) # endif DWORD dpiAware = 0; + DWORD scaleFactor = 100; if (GetProcessDpiAwareness && GetScaleFactorForMonitor - && GetProcessDpiAwareness(NULL, &dpiAware) == 0 && dpiAware != 0) + && GetProcessDpiAwareness(nullptr, &dpiAware) == 0 && dpiAware != 0) { const HMONITOR hMon = parentWindowHandle != 0 ? MonitorFromWindow((HWND)parentWindowHandle, MONITOR_DEFAULTTOPRIMARY) : MonitorFromPoint(POINT{0,0}, MONITOR_DEFAULTTOPRIMARY); - - DWORD scaleFactor = 0; - if (GetScaleFactorForMonitor(hMon, &scaleFactor) == 0 && scaleFactor != 0) - { - FreeLibrary(Shcore); - return static_cast<double>(scaleFactor) / 100.0; - } + GetScaleFactorForMonitor(hMon, &scaleFactor); } FreeLibrary(Shcore); + return static_cast<double>(scaleFactor) / 100.0; } #elif defined(HAVE_X11) ::Display* const display = XOpenDisplay(nullptr); @@ -94,28 +128,31 @@ static double getDesktopScaleFactor(const uintptr_t parentWindowHandle) XrmInitialize(); + double dpi = 96.0; if (char* const rms = XResourceManagerString(display)) { - if (const XrmDatabase sdb = XrmGetStringDatabase(rms)) + if (const XrmDatabase db = XrmGetStringDatabase(rms)) { char* type = nullptr; - XrmValue ret; + XrmValue value = {}; - if (XrmGetResource(sdb, "Xft.dpi", "String", &type, &ret) - && ret.addr != nullptr + if (XrmGetResource(db, "Xft.dpi", "Xft.Dpi", &type, &value) && type != nullptr - && std::strncmp("String", type, 6) == 0) + && std::strcmp(type, "String") == 0 + && value.addr != nullptr) { - if (const double dpi = std::atof(ret.addr)) - { - XCloseDisplay(display); - return dpi / 96; - } + char* end = nullptr; + const double xftDpi = std::strtod(value.addr, &end); + if (xftDpi > 0.0 && xftDpi < HUGE_VAL) + dpi = xftDpi; } + + XrmDestroyDatabase(db); } } XCloseDisplay(display); + return dpi / 96; #endif return 1.0; @@ -164,21 +201,21 @@ UI::PrivateData::createNextWindow(UI* const ui, const uint width, const uint hei /* ------------------------------------------------------------------------------------------------------------ * UI */ -UI::UI(const uint width, const uint height, const bool automaticallyScale) +UI::UI(const uint width, const uint height, const bool automaticallyScaleAndSetAsMinimumSize) : UIWidget(UI::PrivateData::createNextWindow(this, width, height)), uiData(UI::PrivateData::s_nextPrivateData) { #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI - if (width > 0 && height > 0) + if (width != 0 && height != 0) { Widget::setSize(width, height); - if (automaticallyScale) - setGeometryConstraints(width, height, true, true); + if (automaticallyScaleAndSetAsMinimumSize) + setGeometryConstraints(width, height, true, true, true); } #else // unused - return; (void)automaticallyScale; + (void)automaticallyScaleAndSetAsMinimumSize; #endif } @@ -217,6 +254,11 @@ double UI::getSampleRate() const noexcept return uiData->sampleRate; } +const char* UI::getBundlePath() const noexcept +{ + return uiData->bundlePath; +} + void UI::editParameter(uint32_t index, bool started) { uiData->editParamCallback(index + uiData->parameterOffset, started); @@ -234,7 +276,7 @@ void UI::setState(const char* key, const char* value) } #endif -#if DISTRHO_PLUGIN_WANT_STATEFILES +#if DISTRHO_PLUGIN_WANT_STATE bool UI::requestStateFile(const char* key) { return uiData->fileRequestCallback(key); @@ -248,6 +290,13 @@ void UI::sendNote(uint8_t channel, uint8_t note, uint8_t velocity) } #endif +#if DISTRHO_UI_FILE_BROWSER +bool UI::openFileBrowser(const FileBrowserOptions& options) +{ + return getWindow().openFileBrowser((DGL_NAMESPACE::FileBrowserOptions&)options); +} +#endif + #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS /* ------------------------------------------------------------------------------------------------------------ * Direct DSP access */ @@ -260,7 +309,7 @@ void* UI::getPluginInstancePointer() const noexcept #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI /* ------------------------------------------------------------------------------------------------------------ - * External UI helpers */ + * External UI helpers (static calls) */ const char* UI::getNextBundlePath() noexcept { @@ -295,6 +344,25 @@ void UI::uiScaleFactorChanged(double) } #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI +std::vector<DGL_NAMESPACE::ClipboardDataOffer> UI::getClipboardDataOfferTypes() +{ + return uiData->window->getClipboardDataOfferTypes(); +} + +uint32_t UI::uiClipboardDataOffer() +{ + std::vector<DGL_NAMESPACE::ClipboardDataOffer> offers(uiData->window->getClipboardDataOfferTypes()); + + for (std::vector<DGL_NAMESPACE::ClipboardDataOffer>::iterator it=offers.begin(), end=offers.end(); it != end;++it) + { + const DGL_NAMESPACE::ClipboardDataOffer offer = *it; + if (std::strcmp(offer.type, "text/plain") == 0) + return offer.id; + } + + return 0; +} + void UI::uiFocus(bool, DGL_NAMESPACE::CrossingMode) { } @@ -304,13 +372,13 @@ void UI::uiReshape(uint, uint) // NOTE this must be the same as Window::onReshape pData->fallbackOnResize(); } +#endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI -# ifndef DGL_FILE_BROWSER_DISABLED +#if DISTRHO_UI_FILE_BROWSER void UI::uiFileBrowserSelected(const char*) { } -# endif -#endif // !DISTRHO_PLUGIN_HAS_EXTERNAL_UI +#endif /* ------------------------------------------------------------------------------------------------------------ * UI Resize Handling, internal */ @@ -327,9 +395,29 @@ void UI::onResize(const ResizeEvent& ev) { UIWidget::onResize(ev); +#ifndef DISTRHO_PLUGIN_TARGET_VST3 + if (uiData->initializing) + return; + const uint width = ev.size.getWidth(); const uint height = ev.size.getHeight(); uiData->setSizeCallback(width, height); +#endif +} + +// NOTE: only used for VST3 +void UI::requestSizeChange(const uint width, const uint height) +{ +# ifdef DISTRHO_PLUGIN_TARGET_VST3 + if (uiData->initializing) + uiData->window->setSizeForVST3(width, height); + else + uiData->setSizeCallback(width, height); +# else + // unused + (void)width; + (void)height; +# endif } #endif diff --git a/distrho/src/DistrhoUIInternal.hpp b/distrho/src/DistrhoUIInternal.hpp @@ -24,10 +24,10 @@ START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------- // Static data, see DistrhoUI.cpp +extern const char* g_nextBundlePath; #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI extern uintptr_t g_nextWindowId; extern double g_nextScaleFactor; -extern const char* g_nextBundlePath; #endif // ----------------------------------------------------------------------- @@ -62,6 +62,7 @@ public: uiData(new UI::PrivateData()) { uiData->sampleRate = sampleRate; + uiData->bundlePath = bundlePath != nullptr ? strdup(bundlePath) : nullptr; uiData->dspPtr = dspPtr; uiData->bgColor = bgColor; @@ -77,27 +78,28 @@ public: uiData->setSizeCallbackFunc = setSizeCall; uiData->fileRequestCallbackFunc = fileRequestCall; + g_nextBundlePath = bundlePath; #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI g_nextWindowId = winId; g_nextScaleFactor = scaleFactor; - g_nextBundlePath = bundlePath; #endif UI::PrivateData::s_nextPrivateData = uiData; UI* const uiPtr = createUI(); + g_nextBundlePath = nullptr; #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI g_nextWindowId = 0; g_nextScaleFactor = 0.0; - g_nextBundlePath = nullptr; #else - // Leave context called in the PluginWindow constructor, see DistrhoUIPrivateData.hpp + // enter context called in the PluginWindow constructor, see DistrhoUIPrivateData.hpp uiData->window->leaveContext(); #endif UI::PrivateData::s_nextPrivateData = nullptr; DISTRHO_SAFE_ASSERT_RETURN(uiPtr != nullptr,); ui = uiPtr; + uiData->initializing = false; #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI // unused @@ -108,6 +110,9 @@ public: ~UIExporter() { quit(); +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + uiData->window->enterContextForDeletion(); +#endif delete ui; delete uiData; } @@ -129,6 +134,23 @@ public: return uiData->window->getScaleFactor(); } + bool getGeometryConstraints(uint& minimumWidth, uint& minimumHeight, bool& keepAspectRatio) const noexcept + { +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI + uiData->window->getGeometryConstraints(minimumWidth, minimumHeight, keepAspectRatio); +#else + const DGL_NAMESPACE::Size<uint> size(uiData->window->getGeometryConstraints(keepAspectRatio)); + minimumWidth = size.getWidth(); + minimumHeight = size.getHeight(); +#endif + return true; + } + + bool isResizable() const noexcept + { + return uiData->window->isResizable(); + } + bool isVisible() const noexcept { return uiData->window->isVisible(); @@ -210,7 +232,14 @@ public: ui->uiIdle(); } -#else + + void showAndFocus() + { + uiData->window->show(); + uiData->window->focus(); + } +#endif + bool plugin_idle() { DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, false); @@ -219,7 +248,6 @@ public: ui->uiIdle(); return ! uiData->app.isQuitting(); } -#endif void focus() { @@ -234,19 +262,62 @@ public: // ------------------------------------------------------------------- +#if defined(DISTRHO_PLUGIN_TARGET_VST3) && (defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WINDOWS)) + void idleForVST3() + { + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); + + uiData->app.triggerIdleCallbacks(); + ui->uiIdle(); + } + +# if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + void addIdleCallbackForVST3(IdleCallback* const cb, const uint timerFrequencyInMs) + { + uiData->window->addIdleCallback(cb, timerFrequencyInMs); + } + + void removeIdleCallbackForVST3(IdleCallback* const cb) + { + uiData->window->removeIdleCallback(cb); + } +# endif +#endif + + // ------------------------------------------------------------------- + + void setWindowOffset(const int x, const int y) + { +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI + // TODO + (void)x; (void)y; +#else + uiData->window->setOffset(x, y); +#endif + } + +#ifdef DISTRHO_PLUGIN_TARGET_VST3 + void setWindowSizeForVST3(const uint width, const uint height) + { +# if DISTRHO_PLUGIN_HAS_EXTERNAL_UI + ui->setSize(width, height); +# else + uiData->window->setSizeForVST3(width, height); +# endif + } +#endif + void setWindowTitle(const char* const uiTitle) { uiData->window->setTitle(uiTitle); } - void setWindowTransientWinId(const uintptr_t winId) + void setWindowTransientWinId(const uintptr_t transientParentWindowHandle) { #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI - ui->setTransientWindowId(winId); -#elif 0 /* TODO */ - glWindow.setTransientWinId(winId); + ui->setTransientWindowId(transientParentWindowHandle); #else - (void)winId; + uiData->window->setTransientParent(transientParentWindowHandle); #endif } @@ -258,23 +329,37 @@ public: } #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI - bool handlePluginKeyboard(const bool press, const uint key, const uint16_t mods) + bool handlePluginKeyboardVST(const bool press, const bool special, const uint keychar, const uint keycode, const uint16_t mods) { - // TODO also trigger Character input event - DGL_NAMESPACE::Widget::KeyboardEvent ev; - ev.press = press; - ev.key = key; - ev.mod = mods; - return ui->onKeyboard(ev); - } + using namespace DGL_NAMESPACE; - bool handlePluginSpecial(const bool press, const DGL_NAMESPACE::Key key, const uint16_t mods) - { - DGL_NAMESPACE::Widget::SpecialEvent ev; - ev.press = press; - ev.key = key; - ev.mod = mods; - return ui->onSpecial(ev); + Widget::KeyboardEvent ev; + ev.mod = mods; + ev.press = press; + ev.key = keychar; + ev.keycode = keycode; + + // keyboard events must always be lowercase + if (ev.key >= 'A' && ev.key <= 'Z') + ev.key += 'a' - 'A'; // A-Z -> a-z + + const bool ret = ui->onKeyboard(ev); + + if (press && !special && (mods & (kModifierControl|kModifierAlt|kModifierSuper)) == 0) + { + Widget::CharacterInputEvent cev; + cev.mod = mods; + cev.character = keychar; + cev.keycode = keycode; + + // if shift modifier is on, convert a-z -> A-Z for character input + if (cev.character >= 'a' && cev.character <= 'z' && (mods & kModifierShift) != 0) + cev.character -= 'a' - 'A'; + + ui->onCharacterInput(cev); + } + + return ret; } #endif @@ -287,6 +372,15 @@ public: ui->uiScaleFactorChanged(scaleFactor); } +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + void notifyFocusChanged(const bool focus) + { + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); + + ui->uiFocus(focus, DGL_NAMESPACE::kCrossingNormal); + } +#endif + void setSampleRate(const double sampleRate, const bool doCallback = false) { DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); diff --git a/distrho/src/DistrhoUILV2.cpp b/distrho/src/DistrhoUILV2.cpp @@ -181,6 +181,33 @@ public: fUI.stateChanged(key, value); } + else if (atom->type == fURIDs.atomObject) + { + /* TODO + const LV2_Atom_Object* const obj = (const LV2_Atom_Object*)LV2_ATOM_BODY_CONST(atom); + + const LV2_Atom* property = nullptr; + const LV2_Atom* avalue = nullptr; + lv2_atom_object_get(obj, fURIDs.patchProperty, &property, fURIDs.patchValue, &avalue, 0); + + DISTRHO_SAFE_ASSERT_RETURN(property != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(avalue != nullptr,); + + DISTRHO_SAFE_ASSERT_RETURN(property->type == fURIDs.atomURID,); + DISTRHO_SAFE_ASSERT_RETURN(avalue->type == fURIDs.atomPath || avalue->type == fURIDs.atomString,); + + if (property != nullptr && property->type == fURIDs.atomURID && + avalue != nullptr && (avalue->type == fURIDs.atomPath || avalue->type == fURIDs.atomString)) + { + const char* const key = (const char*)LV2_ATOM_BODY_CONST(property); + const char* const value = (const char*)LV2_ATOM_BODY_CONST(avalue); + + d_stdout("received atom object '%s' '%s'", key, value); + + fUI.stateChanged(key, value); + } + */ + } else { d_stdout("received atom not dpfKeyValue"); @@ -285,11 +312,13 @@ protected: tmpStr[std::strlen(key)] = '\0'; // set msg size (key + separator + value + null terminator) - const size_t msgSize = tmpStr.length() + 1U; + const uint32_t msgSize = static_cast<uint32_t>(tmpStr.length()) + 1U; // reserve atom space - const size_t atomSize = sizeof(LV2_Atom) + msgSize; + const uint32_t atomSize = sizeof(LV2_Atom) + msgSize; char* const atomBuf = (char*)malloc(atomSize); + DISTRHO_SAFE_ASSERT_RETURN(atomBuf != nullptr,); + std::memset(atomBuf, 0, atomSize); // set atom info @@ -366,15 +395,19 @@ private: // LV2 URIDs const struct URIDs { const LV2_URID_Map* _uridMap; - LV2_URID dpfKeyValue; - LV2_URID atomEventTransfer; - LV2_URID atomFloat; - LV2_URID atomLong; - LV2_URID atomPath; - LV2_URID atomString; - LV2_URID midiEvent; - LV2_URID paramSampleRate; - LV2_URID patchSet; + const LV2_URID dpfKeyValue; + const LV2_URID atomEventTransfer; + const LV2_URID atomFloat; + const LV2_URID atomLong; + const LV2_URID atomObject; + const LV2_URID atomPath; + const LV2_URID atomString; + const LV2_URID atomURID; + const LV2_URID midiEvent; + const LV2_URID paramSampleRate; + const LV2_URID patchProperty; + const LV2_URID patchSet; + const LV2_URID patchValue; URIDs(const LV2_URID_Map* const uridMap) : _uridMap(uridMap), @@ -382,11 +415,15 @@ private: atomEventTransfer(map(LV2_ATOM__eventTransfer)), atomFloat(map(LV2_ATOM__Float)), atomLong(map(LV2_ATOM__Long)), + atomObject(map(LV2_ATOM__Object)), atomPath(map(LV2_ATOM__Path)), atomString(map(LV2_ATOM__String)), + atomURID(map(LV2_ATOM__URID)), midiEvent(map(LV2_MIDI__MidiEvent)), paramSampleRate(map(LV2_PARAMETERS__sampleRate)), - patchSet(map(LV2_PATCH__Set)) {} + patchProperty(map(LV2_PATCH__property)), + patchSet(map(LV2_PATCH__Set)), + patchValue(map(LV2_PATCH__value)) {} inline LV2_URID map(const char* const uri) const { diff --git a/distrho/src/DistrhoUIPrivateData.hpp b/distrho/src/DistrhoUIPrivateData.hpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -19,10 +19,15 @@ #include "../DistrhoUI.hpp" +#ifdef DISTRHO_PLUGIN_TARGET_VST3 +# include "DistrhoPluginVST.hpp" +#endif + #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI # include "../extra/Sleep.hpp" +// TODO import and use file browser here #else -# include "../../dgl/Application.hpp" +# include "../../dgl/src/ApplicationPrivateData.hpp" # include "../../dgl/src/WindowPrivateData.hpp" # include "../../dgl/src/pugl.hpp" #endif @@ -33,18 +38,18 @@ # define DISTRHO_UI_IS_STANDALONE 0 #endif -#if defined(DISTRHO_PLUGIN_TARGET_VST2) || defined(DISTRHO_PLUGIN_TARGET_VST3) +#ifdef DISTRHO_PLUGIN_TARGET_VST3 +# define DISTRHO_UI_IS_VST3 1 +#else +# define DISTRHO_UI_IS_VST3 0 +#endif + +#ifdef DISTRHO_PLUGIN_TARGET_VST2 # undef DISTRHO_UI_USER_RESIZABLE # define DISTRHO_UI_USER_RESIZABLE 0 #endif -// ----------------------------------------------------------------------- - -#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI START_NAMESPACE_DISTRHO -#else -START_NAMESPACE_DGL -#endif // ----------------------------------------------------------------------- // Plugin Application, will set class name based on plugin details @@ -92,16 +97,18 @@ struct PluginApplication // these are not needed void idle() {} void quit() {} + void triggerIdleCallbacks() {} DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginApplication) }; #else -class PluginApplication : public Application +class PluginApplication : public DGL_NAMESPACE::Application { public: explicit PluginApplication() - : Application(DISTRHO_UI_IS_STANDALONE) + : DGL_NAMESPACE::Application(DISTRHO_UI_IS_STANDALONE) { +#ifndef DISTRHO_OS_WASM const char* const className = ( #ifdef DISTRHO_PLUGIN_BRAND DISTRHO_PLUGIN_BRAND @@ -111,6 +118,12 @@ public: "-" DISTRHO_PLUGIN_NAME ); setClassName(className); +#endif + } + + void triggerIdleCallbacks() + { + pData->triggerIdleCallbacks(); } DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginApplication) @@ -146,24 +159,31 @@ public: void setTitle(const char* const title) { ui->setTitle(title); } void setVisible(const bool visible) { ui->setVisible(visible); } uintptr_t getNativeWindowHandle() const noexcept { return ui->getNativeWindowHandle(); } + void getGeometryConstraints(uint& minimumWidth, uint& minimumHeight, bool& keepAspectRatio) const noexcept + { + minimumWidth = ui->pData.minWidth; + minimumHeight = ui->pData.minHeight; + keepAspectRatio = ui->pData.keepAspectRatio; + } DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginWindow) }; #else // DISTRHO_PLUGIN_HAS_EXTERNAL_UI -class PluginWindow : public Window +class PluginWindow : public DGL_NAMESPACE::Window { - DISTRHO_NAMESPACE::UI* const ui; + UI* const ui; bool initializing; bool receivedReshapeDuringInit; public: - explicit PluginWindow(DISTRHO_NAMESPACE::UI* const uiPtr, + explicit PluginWindow(UI* const uiPtr, PluginApplication& app, const uintptr_t parentWindowHandle, const uint width, const uint height, const double scaleFactor) - : Window(app, parentWindowHandle, width, height, scaleFactor, DISTRHO_UI_USER_RESIZABLE, false), + : Window(app, parentWindowHandle, width, height, scaleFactor, + DISTRHO_UI_USER_RESIZABLE, DISTRHO_UI_IS_VST3, false), ui(uiPtr), initializing(true), receivedReshapeDuringInit(false) @@ -171,10 +191,18 @@ public: if (pData->view == nullptr) return; + // this is called just before creating UI, ensuring proper context to it if (pData->initPost()) puglBackendEnter(pData->view); } + ~PluginWindow() override + { + if (pData->view != nullptr) + puglBackendLeave(pData->view); + } + + // called after creating UI, restoring proper context void leaveContext() { if (pData->view == nullptr) @@ -187,12 +215,42 @@ public: puglBackendLeave(pData->view); } + // used for temporary windows (VST2/3 get size without active/visible view) void setIgnoreIdleCallbacks(const bool ignore = true) { pData->ignoreIdleCallbacks = ignore; } + // called right before deleting UI, ensuring correct context + void enterContextForDeletion() + { + if (pData->view != nullptr) + puglBackendEnter(pData->view); + } + + #ifdef DISTRHO_PLUGIN_TARGET_VST3 + void setSizeForVST3(const uint width, const uint height) + { + puglSetSizeAndDefault(pData->view, width, height); + } + #endif + + std::vector<DGL_NAMESPACE::ClipboardDataOffer> getClipboardDataOfferTypes() + { + return Window::getClipboardDataOfferTypes(); + } + protected: + uint32_t onClipboardDataOffer() override + { + DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr, 0); + + if (initializing) + return 0; + + return ui->uiClipboardDataOffer(); + } + void onFocus(const bool focus, const DGL_NAMESPACE::CrossingMode mode) override { DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); @@ -226,7 +284,7 @@ protected: ui->uiScaleFactorChanged(scaleFactor); } -# ifndef DGL_FILE_BROWSER_DISABLED +# if DISTRHO_UI_FILE_BROWSER void onFileSelected(const char* filename) override; # endif @@ -234,21 +292,6 @@ protected: }; #endif // DISTRHO_PLUGIN_HAS_EXTERNAL_UI -#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI -END_NAMESPACE_DISTRHO -#else -END_NAMESPACE_DGL -#endif - -// ----------------------------------------------------------------------- - -START_NAMESPACE_DISTRHO - -#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI -using DGL_NAMESPACE::PluginApplication; -using DGL_NAMESPACE::PluginWindow; -#endif - // ----------------------------------------------------------------------- // UI callbacks @@ -277,9 +320,13 @@ struct UI::PrivateData { uint fgColor; double scaleFactor; uintptr_t winId; -#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI && !defined(DGL_FILE_BROWSER_DISABLED) +#if DISTRHO_UI_FILE_BROWSER && !DISTRHO_PLUGIN_HAS_EXTERNAL_UI char* uiStateFileKeyRequest; #endif + char* bundlePath; + + // Ignore initial resize events while initializing + bool initializing; // Callbacks void* callbacksPtr; @@ -300,9 +347,11 @@ struct UI::PrivateData { fgColor(0xffffffff), scaleFactor(1.0), winId(0), -#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI && !defined(DGL_FILE_BROWSER_DISABLED) +#if DISTRHO_UI_FILE_BROWSER && !DISTRHO_PLUGIN_HAS_EXTERNAL_UI uiStateFileKeyRequest(nullptr), #endif + bundlePath(nullptr), + initializing(true), callbacksPtr(nullptr), editParamCallbackFunc(nullptr), setParamCallbackFunc(nullptr), @@ -326,13 +375,18 @@ struct UI::PrivateData { parameterOffset += 1; # endif #endif + +#ifdef DISTRHO_PLUGIN_TARGET_VST3 + parameterOffset += kVst3InternalParameterCount; +#endif } ~PrivateData() noexcept { -#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI && !defined(DGL_FILE_BROWSER_DISABLED) +#if DISTRHO_UI_FILE_BROWSER && !DISTRHO_PLUGIN_HAS_EXTERNAL_UI std::free(uiStateFileKeyRequest); #endif + std::free(bundlePath); } void editParamCallback(const uint32_t rindex, const bool started) @@ -384,7 +438,7 @@ inline bool UI::PrivateData::fileRequestCallback(const char* const key) if (fileRequestCallbackFunc != nullptr) return fileRequestCallbackFunc(callbacksPtr, key); -#if DISTRHO_PLUGIN_WANT_STATEFILES && !DISTRHO_PLUGIN_HAS_EXTERNAL_UI && !defined(DGL_FILE_BROWSER_DISABLED) +#if DISTRHO_PLUGIN_WANT_STATE && DISTRHO_UI_FILE_BROWSER && !DISTRHO_PLUGIN_HAS_EXTERNAL_UI std::free(uiStateFileKeyRequest); uiStateFileKeyRequest = strdup(key); DISTRHO_SAFE_ASSERT_RETURN(uiStateFileKeyRequest != nullptr, false); @@ -393,7 +447,7 @@ inline bool UI::PrivateData::fileRequestCallback(const char* const key) snprintf(title, sizeof(title)-1u, DISTRHO_PLUGIN_NAME ": %s", key); title[sizeof(title)-1u] = '\0'; - DGL_NAMESPACE::Window::FileBrowserOptions opts; + DGL_NAMESPACE::FileBrowserOptions opts; opts.title = title; return window->openFileBrowser(opts); #endif @@ -401,14 +455,10 @@ inline bool UI::PrivateData::fileRequestCallback(const char* const key) return false; } -END_NAMESPACE_DISTRHO - // ----------------------------------------------------------------------- // PluginWindow onFileSelected that require UI::PrivateData definitions -#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI && !defined(DGL_FILE_BROWSER_DISABLED) -START_NAMESPACE_DGL - +#if DISTRHO_UI_FILE_BROWSER && !DISTRHO_PLUGIN_HAS_EXTERNAL_UI inline void PluginWindow::onFileSelected(const char* const filename) { DISTRHO_SAFE_ASSERT_RETURN(ui != nullptr,); @@ -416,7 +466,7 @@ inline void PluginWindow::onFileSelected(const char* const filename) if (initializing) return; -# if DISTRHO_PLUGIN_WANT_STATEFILES + #if DISTRHO_PLUGIN_WANT_STATE if (char* const key = ui->uiData->uiStateFileKeyRequest) { ui->uiData->uiStateFileKeyRequest = nullptr; @@ -430,14 +480,16 @@ inline void PluginWindow::onFileSelected(const char* const filename) std::free(key); return; } -# endif + #endif + puglBackendEnter(pData->view); ui->uiFileBrowserSelected(filename); + puglBackendLeave(pData->view); } - -END_NAMESPACE_DGL #endif // ----------------------------------------------------------------------- +END_NAMESPACE_DISTRHO + #endif // DISTRHO_UI_PRIVATE_DATA_HPP_INCLUDED diff --git a/distrho/src/DistrhoUIVST3.cpp b/distrho/src/DistrhoUIVST3.cpp @@ -0,0 +1,1571 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "DistrhoUIInternal.hpp" + +#include "travesty/base.h" +#include "travesty/edit_controller.h" +#include "travesty/host.h" +#include "travesty/view.h" + +#if DISTRHO_PLUGIN_HAS_EXTERNAL_UI +# if defined(DISTRHO_OS_MAC) +# include <CoreFoundation/CoreFoundation.h> +# elif defined(DISTRHO_OS_WINDOWS) +# include <winuser.h> +# define DPF_VST3_WIN32_TIMER_ID 1 +# endif +#endif + +/* TODO items: + * - mousewheel event + * - file request? + */ + +#if !(defined(DISTRHO_OS_MAC) || defined(DISTRHO_OS_WINDOWS)) +# define DPF_VST3_USING_HOST_RUN_LOOP 1 +#else +# define DPF_VST3_USING_HOST_RUN_LOOP 0 +#endif + +#ifndef DPF_VST3_TIMER_INTERVAL +# define DPF_VST3_TIMER_INTERVAL 16 /* ~60 fps */ +#endif + +START_NAMESPACE_DISTRHO + +// -------------------------------------------------------------------------------------------------------------------- + +#if ! DISTRHO_PLUGIN_WANT_MIDI_INPUT +static constexpr const sendNoteFunc sendNoteCallback = nullptr; +#endif +#if ! DISTRHO_PLUGIN_WANT_STATE +static constexpr const setStateFunc setStateCallback = nullptr; +#endif + +// -------------------------------------------------------------------------------------------------------------------- +// Static data, see DistrhoPlugin.cpp + +extern const char* d_nextBundlePath; + +// -------------------------------------------------------------------------------------------------------------------- +// Utility functions (defined on plugin side) + +const char* tuid2str(const v3_tuid iid); + +// -------------------------------------------------------------------------------------------------------------------- + +static void applyGeometryConstraints(const uint minimumWidth, + const uint minimumHeight, + const bool keepAspectRatio, + v3_view_rect* const rect) +{ + d_stdout("applyGeometryConstraints %u %u %d {%d,%d,%d,%d} | BEFORE", + minimumWidth, minimumHeight, keepAspectRatio, rect->top, rect->left, rect->right, rect->bottom); + const int32_t minWidth = static_cast<int32_t>(minimumWidth); + const int32_t minHeight = static_cast<int32_t>(minimumHeight); + + if (keepAspectRatio) + { + const double ratio = static_cast<double>(minWidth) / static_cast<double>(minHeight); + const double reqRatio = static_cast<double>(rect->right) / static_cast<double>(rect->bottom); + + if (d_isNotEqual(ratio, reqRatio)) + { + // fix width + if (reqRatio > ratio) + rect->right = static_cast<int32_t>(rect->bottom * ratio + 0.5); + // fix height + else + rect->bottom = static_cast<int32_t>(static_cast<double>(rect->right) / ratio + 0.5); + } + } + + if (minWidth > rect->right) + rect->right = minWidth; + if (minHeight > rect->bottom) + rect->bottom = minHeight; + + d_stdout("applyGeometryConstraints %u %u %d {%d,%d,%d,%d} | AFTER", + minimumWidth, minimumHeight, keepAspectRatio, rect->top, rect->left, rect->right, rect->bottom); +} + +// -------------------------------------------------------------------------------------------------------------------- + +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI +static uint translateVST3Modifiers(const int64_t modifiers) noexcept +{ + using namespace DGL_NAMESPACE; + + uint dglmods = 0; + if (modifiers & (1 << 0)) + dglmods |= kModifierShift; + if (modifiers & (1 << 1)) + dglmods |= kModifierAlt; + #ifdef DISTRHO_OS_MAC + if (modifiers & (1 << 2)) + dglmods |= kModifierSuper; + if (modifiers & (1 << 3)) + dglmods |= kModifierControl; + #else + if (modifiers & (1 << 2)) + dglmods |= kModifierControl; + if (modifiers & (1 << 3)) + dglmods |= kModifierSuper; + #endif + + return dglmods; +} +#endif + +// -------------------------------------------------------------------------------------------------------------------- + +/** + * Helper class for getting a native idle timer, either through pugl or via native APIs. + */ +#if !DPF_VST3_USING_HOST_RUN_LOOP +class NativeIdleCallback : public IdleCallback +{ +public: + NativeIdleCallback(UIExporter& ui) + : fUI(ui), + fCallbackRegistered(false) + #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI + #if defined(DISTRHO_OS_MAC) + , fTimerRef(nullptr) + #elif defined(DISTRHO_OS_WINDOWS) + , fTimerWindow(nullptr) + , fTimerWindowClassName() + #endif + #endif + { + } + + void registerNativeIdleCallback() + { + DISTRHO_SAFE_ASSERT_RETURN(!fCallbackRegistered,); + fCallbackRegistered = true; + + #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + fUI.addIdleCallbackForVST3(this, DPF_VST3_TIMER_INTERVAL); + #elif defined(DISTRHO_OS_MAC) + constexpr const CFTimeInterval interval = DPF_VST3_TIMER_INTERVAL * 0.0001; + + CFRunLoopTimerContext context = {}; + context.info = this; + fTimerRef = CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + interval, interval, 0, 0, + platformIdleTimerCallback, &context); + DISTRHO_SAFE_ASSERT_RETURN(fTimerRef != nullptr,); + + CFRunLoopAddTimer(CFRunLoopGetCurrent(), fTimerRef, kCFRunLoopCommonModes); + #elif defined(DISTRHO_OS_WINDOWS) + /* We cannot assume anything about the native parent window passed as a parameter (winId) to the + * UIVst3 constructor because we do not own it. + * These parent windows have class names like 'reaperPluginHostWrapProc' and 'JUCE_nnnnnn'. + * + * Create invisible window to handle a timer instead. + * There is no need for implementing a window proc because DefWindowProc already calls the + * callback function when processing WM_TIMER messages. + */ + fTimerWindowClassName = ( + #ifdef DISTRHO_PLUGIN_BRAND + DISTRHO_PLUGIN_BRAND + #else + DISTRHO_MACRO_AS_STRING(DISTRHO_NAMESPACE) + #endif + "-" DISTRHO_PLUGIN_NAME "-" + ); + + char suffix[9]; + std::snprintf(suffix, sizeof(suffix), "%08x", std::rand()); + suffix[sizeof(suffix)-1] = '\0'; + fTimerWindowClassName += suffix; + + WNDCLASSEX cls; + ZeroMemory(&cls, sizeof(cls)); + cls.cbSize = sizeof(WNDCLASSEX); + cls.cbWndExtra = sizeof(LONG_PTR); + cls.lpszClassName = fTimerWindowClassName.buffer(); + cls.lpfnWndProc = DefWindowProc; + RegisterClassEx(&cls); + + fTimerWindow = CreateWindowEx(0, cls.lpszClassName, "DPF Timer Helper", + 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, nullptr, nullptr); + DISTRHO_SAFE_ASSERT_RETURN(fTimerWindow != nullptr,); + + SetWindowLongPtr(fTimerWindow, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(static_cast<void*>(this))); + SetTimer(fTimerWindow, DPF_VST3_WIN32_TIMER_ID, DPF_VST3_TIMER_INTERVAL, + static_cast<TIMERPROC>(platformIdleTimerCallback)); + #endif + } + + void unregisterNativeIdleCallback() + { + #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + fUI.removeIdleCallbackForVST3(this); + #elif defined(DISTRHO_OS_MAC) + CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), fTimerRef, kCFRunLoopCommonModes); + CFRelease(fTimerRef); + #elif defined(DISTRHO_OS_WINDOWS) + DISTRHO_SAFE_ASSERT_RETURN(fTimerWindow != nullptr,); + KillTimer(fTimerWindow, DPF_VST3_WIN32_TIMER_ID); + DestroyWindow(fTimerWindow); + UnregisterClass(fTimerWindowClassName, nullptr); + #endif + } + +private: + UIExporter& fUI; + bool fCallbackRegistered; + + #if DISTRHO_PLUGIN_HAS_EXTERNAL_UI + #if defined(DISTRHO_OS_MAC) + CFRunLoopTimerRef fTimerRef; + + static void platformIdleTimerCallback(CFRunLoopTimerRef, void* const info) + { + static_cast<NativeIdleCallback*>(info)->idleCallback(); + } + #elif defined(DISTRHO_OS_WINDOWS) + HWND fTimerWindow; + String fTimerWindowClassName; + + WINAPI static void platformIdleTimerCallback(const HWND hwnd, UINT, UINT_PTR, DWORD) + { + reinterpret_cast<NativeIdleCallback*>(GetWindowLongPtr(hwnd, GWLP_USERDATA))->idleCallback(); + } + #endif + #endif +}; +#endif + +// -------------------------------------------------------------------------------------------------------------------- + +/** + * VST3 UI class. + * + * All the dynamic things from VST3 get implemented here, free of complex low-level VST3 pointer things. + * The UI is created during the "attach" view event, and destroyed during "removed". + * + * The low-level VST3 stuff comes after. + */ +class UIVst3 +#if !DPF_VST3_USING_HOST_RUN_LOOP + : public NativeIdleCallback +#endif +{ +public: + UIVst3(v3_plugin_view** const view, + v3_host_application** const host, + v3_connection_point** const connection, + v3_plugin_frame** const frame, + const intptr_t winId, + const float scaleFactor, + const double sampleRate, + void* const instancePointer, + const bool willResizeFromHost, + const bool needsResizeFromPlugin) + : +#if !DPF_VST3_USING_HOST_RUN_LOOP + NativeIdleCallback(fUI), +#endif + fView(view), + fHostApplication(host), + fConnection(connection), + fFrame(frame), + fScaleFactor(scaleFactor), + fReadyForPluginData(false), + fIsResizingFromPlugin(false), + fIsResizingFromHost(willResizeFromHost), + fNeedsResizeFromPlugin(needsResizeFromPlugin), + fNextPluginRect(), + fUI(this, winId, sampleRate, + editParameterCallback, + setParameterCallback, + setStateCallback, + sendNoteCallback, + setSizeCallback, + nullptr, // TODO file request + d_nextBundlePath, + instancePointer, + scaleFactor) + { + } + + ~UIVst3() + { + #if !DPF_VST3_USING_HOST_RUN_LOOP + unregisterNativeIdleCallback(); + #endif + + if (fConnection != nullptr) + disconnect(); + } + + void postInit(uint32_t nextWidth, uint32_t nextHeight) + { + if (fIsResizingFromHost && nextWidth > 0 && nextHeight > 0) + { + #ifdef DISTRHO_OS_MAC + const double scaleFactor = fUI.getScaleFactor(); + nextWidth *= scaleFactor; + nextHeight *= scaleFactor; + #endif + + if (fUI.getWidth() != nextWidth || fUI.getHeight() != nextHeight) + { + d_stdout("postInit sets new size as %u %u", nextWidth, nextHeight); + fUI.setWindowSizeForVST3(nextWidth, nextHeight); + } + } + else if (fNeedsResizeFromPlugin) + { + d_stdout("postInit forcely sets size from plugin as %u %u", fUI.getWidth(), fUI.getHeight()); + setSize(fUI.getWidth(), fUI.getHeight()); + } + + if (fConnection != nullptr) + connect(fConnection); + + #if !DPF_VST3_USING_HOST_RUN_LOOP + registerNativeIdleCallback(); + #endif + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_plugin_view interface calls + +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + v3_result onWheel(float /*distance*/) + { + // TODO + return V3_NOT_IMPLEMENTED; + } + + v3_result onKeyDown(const int16_t keychar, const int16_t keycode, const int16_t modifiers) + { + DISTRHO_SAFE_ASSERT_INT_RETURN(keychar >= 0 && keychar < 0x7f, keychar, V3_FALSE); + + bool special; + const uint key = translateVstKeyCode(special, keychar, keycode); + d_debug("onKeyDown %d %d %x -> %d %d", keychar, keycode, modifiers, special, key); + + return fUI.handlePluginKeyboardVST(true, special, key, + keycode >= 0 ? static_cast<uint>(keycode) : 0, + translateVST3Modifiers(modifiers)) ? V3_TRUE : V3_FALSE; + } + + v3_result onKeyUp(const int16_t keychar, const int16_t keycode, const int16_t modifiers) + { + DISTRHO_SAFE_ASSERT_INT_RETURN(keychar >= 0 && keychar < 0x7f, keychar, V3_FALSE); + + bool special; + const uint key = translateVstKeyCode(special, keychar, keycode); + d_debug("onKeyUp %d %d %x -> %d %d", keychar, keycode, modifiers, special, key); + + return fUI.handlePluginKeyboardVST(false, special, key, + keycode >= 0 ? static_cast<uint>(keycode) : 0, + translateVST3Modifiers(modifiers)) ? V3_TRUE : V3_FALSE; + } + + v3_result onFocus(const bool state) + { + if (state) + fUI.focus(); + fUI.notifyFocusChanged(state); + return V3_OK; + } +#endif + + v3_result getSize(v3_view_rect* const rect) const noexcept + { + if (fIsResizingFromPlugin) + { + *rect = fNextPluginRect; + } + else + { + rect->left = rect->top = 0; + rect->right = fUI.getWidth(); + rect->bottom = fUI.getHeight(); + #ifdef DISTRHO_OS_MAC + const double scaleFactor = fUI.getScaleFactor(); + rect->right /= scaleFactor; + rect->bottom /= scaleFactor; + #endif + } + + d_stdout("getSize request returning %i %i", rect->right, rect->bottom); + return V3_OK; + } + + v3_result onSize(v3_view_rect* const orect) + { + v3_view_rect rect = *orect; + + #ifdef DISTRHO_OS_MAC + const double scaleFactor = fUI.getScaleFactor(); + rect.top *= scaleFactor; + rect.left *= scaleFactor; + rect.right *= scaleFactor; + rect.bottom *= scaleFactor; + #endif + + if (fIsResizingFromPlugin) + { + d_stdout("host->plugin onSize request %i %i (plugin resize was active, unsetting now)", + rect.right - rect.left, rect.bottom - rect.top); + fIsResizingFromPlugin = false; + } + else + { + d_stdout("host->plugin onSize request %i %i (OK)", rect.right - rect.left, rect.bottom - rect.top); + } + + fIsResizingFromHost = true; + fUI.setWindowSizeForVST3(rect.right - rect.left, rect.bottom - rect.top); + return V3_OK; + } + + v3_result setFrame(v3_plugin_frame** const frame) noexcept + { + fFrame = frame; + return V3_OK; + } + + v3_result canResize() noexcept + { + return fUI.isResizable() ? V3_TRUE : V3_FALSE; + } + + v3_result checkSizeConstraint(v3_view_rect* const rect) + { + uint minimumWidth, minimumHeight; + bool keepAspectRatio; + fUI.getGeometryConstraints(minimumWidth, minimumHeight, keepAspectRatio); + + #ifdef DISTRHO_OS_MAC + const double scaleFactor = fUI.getScaleFactor(); + minimumWidth /= scaleFactor; + minimumHeight /= scaleFactor; + #endif + + applyGeometryConstraints(minimumWidth, minimumHeight, keepAspectRatio, rect); + return V3_TRUE; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_connection_point interface calls + + void connect(v3_connection_point** const point) noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(point != nullptr,); + + fConnection = point; + + d_stdout("requesting current plugin state"); + + v3_message** const message = createMessage("init"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 1); + v3_cpp_obj(fConnection)->notify(fConnection, message); + + v3_cpp_obj_unref(message); + } + + void disconnect() noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fConnection != nullptr,); + + d_stdout("reporting UI closed"); + fReadyForPluginData = false; + + v3_message** const message = createMessage("close"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 1); + v3_cpp_obj(fConnection)->notify(fConnection, message); + + v3_cpp_obj_unref(message); + + fConnection = nullptr; + } + + v3_result notify(v3_message** const message) + { + const char* const msgid = v3_cpp_obj(message)->get_message_id(message); + DISTRHO_SAFE_ASSERT_RETURN(msgid != nullptr, V3_INVALID_ARG); + + v3_attribute_list** const attrs = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrs != nullptr, V3_INVALID_ARG); + + if (std::strcmp(msgid, "ready") == 0) + { + DISTRHO_SAFE_ASSERT_RETURN(! fReadyForPluginData, V3_INTERNAL_ERR); + fReadyForPluginData = true; + return V3_OK; + } + + if (std::strcmp(msgid, "parameter-set") == 0) + { + int64_t rindex; + double value; + v3_result res; + + res = v3_cpp_obj(attrs)->get_int(attrs, "rindex", &rindex); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + + res = v3_cpp_obj(attrs)->get_float(attrs, "value", &value); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + + if (rindex < kVst3InternalParameterBaseCount) + { + switch (rindex) + { + #if DPF_VST3_USES_SEPARATE_CONTROLLER + case kVst3InternalParameterSampleRate: + DISTRHO_SAFE_ASSERT_RETURN(value >= 0.0, V3_INVALID_ARG); + fUI.setSampleRate(value, true); + break; + #endif + #if DISTRHO_PLUGIN_WANT_PROGRAMS + case kVst3InternalParameterProgram: + DISTRHO_SAFE_ASSERT_RETURN(value >= 0.0, V3_INVALID_ARG); + fUI.programLoaded(static_cast<uint32_t>(value + 0.5)); + break; + #endif + } + + return V3_OK; + } + + const uint32_t index = static_cast<uint32_t>(rindex) - kVst3InternalParameterBaseCount; + fUI.parameterChanged(index, value); + return V3_OK; + } + + #if DISTRHO_PLUGIN_WANT_STATE + if (std::strcmp(msgid, "state-set") == 0) + { + int64_t keyLength = -1; + int64_t valueLength = -1; + v3_result res; + + res = v3_cpp_obj(attrs)->get_int(attrs, "key:length", &keyLength); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + DISTRHO_SAFE_ASSERT_INT_RETURN(keyLength >= 0, keyLength, V3_INTERNAL_ERR); + + res = v3_cpp_obj(attrs)->get_int(attrs, "value:length", &valueLength); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_OK, res, res); + DISTRHO_SAFE_ASSERT_INT_RETURN(valueLength >= 0, valueLength, V3_INTERNAL_ERR); + + int16_t* const key16 = (int16_t*)std::malloc(sizeof(int16_t)*(keyLength + 1)); + DISTRHO_SAFE_ASSERT_RETURN(key16 != nullptr, V3_NOMEM); + + int16_t* const value16 = (int16_t*)std::malloc(sizeof(int16_t)*(valueLength + 1)); + DISTRHO_SAFE_ASSERT_RETURN(value16 != nullptr, V3_NOMEM); + + res = v3_cpp_obj(attrs)->get_string(attrs, "key", key16, sizeof(int16_t)*(keyLength+1)); + DISTRHO_SAFE_ASSERT_INT2_RETURN(res == V3_OK, res, keyLength, res); + + if (valueLength != 0) + { + res = v3_cpp_obj(attrs)->get_string(attrs, "value", value16, sizeof(int16_t)*(valueLength+1)); + DISTRHO_SAFE_ASSERT_INT2_RETURN(res == V3_OK, res, valueLength, res); + } + + // do cheap inline conversion + char* const key = (char*)key16; + char* const value = (char*)value16; + + for (int64_t i=0; i<keyLength; ++i) + key[i] = key16[i]; + for (int64_t i=0; i<valueLength; ++i) + value[i] = value16[i]; + + key[keyLength] = '\0'; + value[valueLength] = '\0'; + + fUI.stateChanged(key, value); + + std::free(key16); + std::free(value16); + return V3_OK; + } + #endif + + d_stdout("UIVst3 received unknown msg '%s'", msgid); + + return V3_NOT_IMPLEMENTED; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_plugin_view_content_scale_steinberg interface calls + + v3_result setContentScaleFactor(const float factor) + { + if (d_isEqual(fScaleFactor, factor)) + return V3_OK; + + fScaleFactor = factor; + fUI.notifyScaleFactorChanged(factor); + return V3_OK; + } + + #if DPF_VST3_USING_HOST_RUN_LOOP + // ---------------------------------------------------------------------------------------------------------------- + // v3_timer_handler interface calls + + void onTimer() + { + fUI.plugin_idle(); + doIdleStuff(); + } + #else + // ---------------------------------------------------------------------------------------------------------------- + // special idle callback without v3_timer_handler + + void idleCallback() override + { + fUI.idleForVST3(); + doIdleStuff(); + } + #endif + + void doIdleStuff() + { + if (fReadyForPluginData) + { + fReadyForPluginData = false; + requestMorePluginData(); + } + + if (fNeedsResizeFromPlugin) + { + fNeedsResizeFromPlugin = false; + d_stdout("first resize forced behaviour is now stopped"); + } + + if (fIsResizingFromHost) + { + fIsResizingFromHost = false; + d_stdout("was resizing from host, now stopped"); + } + + if (fIsResizingFromPlugin) + { + fIsResizingFromPlugin = false; + d_stdout("was resizing from plugin, now stopped"); + } + } + + // ---------------------------------------------------------------------------------------------------------------- + +private: + // VST3 stuff + v3_plugin_view** const fView; + v3_host_application** const fHostApplication; + v3_connection_point** fConnection; + v3_plugin_frame** fFrame; + + // Temporary data + float fScaleFactor; + bool fReadyForPluginData; + bool fIsResizingFromPlugin; + bool fIsResizingFromHost; + bool fNeedsResizeFromPlugin; + v3_view_rect fNextPluginRect; // for when plugin requests a new size + + // Plugin UI (after VST3 stuff so the UI can call into us during its constructor) + UIExporter fUI; + + // ---------------------------------------------------------------------------------------------------------------- + // helper functions called during message passing + + v3_message** createMessage(const char* const id) const + { + DISTRHO_SAFE_ASSERT_RETURN(fHostApplication != nullptr, nullptr); + + v3_tuid iid; + std::memcpy(iid, v3_message_iid, sizeof(v3_tuid)); + v3_message** msg = nullptr; + const v3_result res = v3_cpp_obj(fHostApplication)->create_instance(fHostApplication, iid, iid, (void**)&msg); + DISTRHO_SAFE_ASSERT_INT_RETURN(res == V3_TRUE, res, nullptr); + DISTRHO_SAFE_ASSERT_RETURN(msg != nullptr, nullptr); + + v3_cpp_obj(msg)->set_message_id(msg, id); + return msg; + } + + void requestMorePluginData() const + { + DISTRHO_SAFE_ASSERT_RETURN(fConnection != nullptr,); + + v3_message** const message = createMessage("idle"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 1); + v3_cpp_obj(fConnection)->notify(fConnection, message); + + v3_cpp_obj_unref(message); + } + + // ---------------------------------------------------------------------------------------------------------------- + // DPF callbacks + + void editParameter(const uint32_t rindex, const bool started) const + { + DISTRHO_SAFE_ASSERT_RETURN(fConnection != nullptr,); + + v3_message** const message = createMessage("parameter-edit"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 1); + v3_cpp_obj(attrlist)->set_int(attrlist, "rindex", rindex); + v3_cpp_obj(attrlist)->set_int(attrlist, "started", started ? 1 : 0); + v3_cpp_obj(fConnection)->notify(fConnection, message); + + v3_cpp_obj_unref(message); + } + + static void editParameterCallback(void* ptr, uint32_t rindex, bool started) + { + ((UIVst3*)ptr)->editParameter(rindex, started); + } + + void setParameterValue(const uint32_t rindex, const float realValue) + { + DISTRHO_SAFE_ASSERT_RETURN(fConnection != nullptr,); + + v3_message** const message = createMessage("parameter-set"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 1); + v3_cpp_obj(attrlist)->set_int(attrlist, "rindex", rindex); + v3_cpp_obj(attrlist)->set_float(attrlist, "value", realValue); + v3_cpp_obj(fConnection)->notify(fConnection, message); + + v3_cpp_obj_unref(message); + } + + static void setParameterCallback(void* ptr, uint32_t rindex, float value) + { + ((UIVst3*)ptr)->setParameterValue(rindex, value); + } + + void setSize(uint width, uint height) + { + DISTRHO_SAFE_ASSERT_RETURN(fView != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(fFrame != nullptr,); + + #ifdef DISTRHO_OS_MAC + const double scaleFactor = fUI.getScaleFactor(); + width /= scaleFactor; + height /= scaleFactor; + #endif + + if (fIsResizingFromHost) + { + if (fNeedsResizeFromPlugin) + { + d_stdout("plugin->host setSize %u %u (FORCED, exception for first resize)", width, height); + } + else + { + d_stdout("plugin->host setSize %u %u (IGNORED, host resize active)", width, height); + return; + } + } + else + { + d_stdout("plugin->host setSize %u %u (OK)", width, height); + } + + fIsResizingFromPlugin = true; + + v3_view_rect rect; + rect.left = rect.top = 0; + rect.right = width; + rect.bottom = height; + fNextPluginRect = rect; + v3_cpp_obj(fFrame)->resize_view(fFrame, fView, &rect); + } + + static void setSizeCallback(void* ptr, uint width, uint height) + { + ((UIVst3*)ptr)->setSize(width, height); + } + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity) + { + DISTRHO_SAFE_ASSERT_RETURN(fConnection != nullptr,); + + v3_message** const message = createMessage("midi"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + uint8_t midiData[3]; + midiData[0] = (velocity != 0 ? 0x90 : 0x80) | channel; + midiData[1] = note; + midiData[2] = velocity; + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 1); + v3_cpp_obj(attrlist)->set_binary(attrlist, "data", midiData, sizeof(midiData)); + v3_cpp_obj(fConnection)->notify(fConnection, message); + + v3_cpp_obj_unref(message); + } + + static void sendNoteCallback(void* ptr, uint8_t channel, uint8_t note, uint8_t velocity) + { + ((UIVst3*)ptr)->sendNote(channel, note, velocity); + } + #endif + + #if DISTRHO_PLUGIN_WANT_STATE + void setState(const char* const key, const char* const value) + { + DISTRHO_SAFE_ASSERT_RETURN(fConnection != nullptr,); + + v3_message** const message = createMessage("state-set"); + DISTRHO_SAFE_ASSERT_RETURN(message != nullptr,); + + v3_attribute_list** const attrlist = v3_cpp_obj(message)->get_attributes(message); + DISTRHO_SAFE_ASSERT_RETURN(attrlist != nullptr,); + + v3_cpp_obj(attrlist)->set_int(attrlist, "__dpf_msg_target__", 1); + v3_cpp_obj(attrlist)->set_int(attrlist, "key:length", std::strlen(key)); + v3_cpp_obj(attrlist)->set_int(attrlist, "value:length", std::strlen(value)); + v3_cpp_obj(attrlist)->set_string(attrlist, "key", ScopedUTF16String(key)); + v3_cpp_obj(attrlist)->set_string(attrlist, "value", ScopedUTF16String(value)); + v3_cpp_obj(fConnection)->notify(fConnection, message); + + v3_cpp_obj_unref(message); + } + + static void setStateCallback(void* ptr, const char* key, const char* value) + { + ((UIVst3*)ptr)->setState(key, value); + } + #endif +}; + +// -------------------------------------------------------------------------------------------------------------------- + +/** + * VST3 low-level pointer thingies follow, proceed with care. + */ + +// -------------------------------------------------------------------------------------------------------------------- +// v3_funknown for classes with a single instance + +template<class T> +static uint32_t V3_API dpf_single_instance_ref(void* const self) +{ + return ++(*static_cast<T**>(self))->refcounter; +} + +template<class T> +static uint32_t V3_API dpf_single_instance_unref(void* const self) +{ + return --(*static_cast<T**>(self))->refcounter; +} + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_ui_connection_point + +struct dpf_ui_connection_point : v3_connection_point_cpp { + std::atomic_int refcounter; + ScopedPointer<UIVst3>& uivst3; + v3_connection_point** other; + + dpf_ui_connection_point(ScopedPointer<UIVst3>& v) + : refcounter(1), + uivst3(v), + other(nullptr) + { + // v3_funknown, single instance + query_interface = query_interface_connection_point; + ref = dpf_single_instance_ref<dpf_ui_connection_point>; + unref = dpf_single_instance_unref<dpf_ui_connection_point>; + + // v3_connection_point + point.connect = connect; + point.disconnect = disconnect; + point.notify = notify; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_connection_point(void* const self, const v3_tuid iid, void** const iface) + { + dpf_ui_connection_point* const point = *static_cast<dpf_ui_connection_point**>(self); + + if (v3_tuid_match(iid, v3_funknown_iid) || + v3_tuid_match(iid, v3_connection_point_iid)) + { + d_stdout("UI|query_interface_connection_point => %p %s %p | OK", self, tuid2str(iid), iface); + ++point->refcounter; + *iface = self; + return V3_OK; + } + + d_stdout("DSP|query_interface_connection_point => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface); + + *iface = NULL; + return V3_NO_INTERFACE; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_connection_point + + static v3_result V3_API connect(void* const self, v3_connection_point** const other) + { + dpf_ui_connection_point* const point = *static_cast<dpf_ui_connection_point**>(self); + d_stdout("UI|dpf_ui_connection_point::connect => %p %p", self, other); + + DISTRHO_SAFE_ASSERT_RETURN(point->other == nullptr, V3_INVALID_ARG); + + point->other = other; + + if (UIVst3* const uivst3 = point->uivst3) + uivst3->connect(other); + + return V3_OK; + }; + + static v3_result V3_API disconnect(void* const self, v3_connection_point** const other) + { + d_stdout("UI|dpf_ui_connection_point::disconnect => %p %p", self, other); + dpf_ui_connection_point* const point = *static_cast<dpf_ui_connection_point**>(self); + + DISTRHO_SAFE_ASSERT_RETURN(point->other != nullptr, V3_INVALID_ARG); + + point->other = nullptr; + + if (UIVst3* const uivst3 = point->uivst3) + uivst3->disconnect(); + + return V3_OK; + }; + + static v3_result V3_API notify(void* const self, v3_message** const message) + { + dpf_ui_connection_point* const point = *static_cast<dpf_ui_connection_point**>(self); + + UIVst3* const uivst3 = point->uivst3; + DISTRHO_SAFE_ASSERT_RETURN(uivst3 != nullptr, V3_NOT_INITIALIZED); + + return uivst3->notify(message); + } +}; + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_plugin_view_content_scale + +struct dpf_plugin_view_content_scale : v3_plugin_view_content_scale_cpp { + std::atomic_int refcounter; + ScopedPointer<UIVst3>& uivst3; + // cached values + float scaleFactor; + + dpf_plugin_view_content_scale(ScopedPointer<UIVst3>& v) + : refcounter(1), + uivst3(v), + scaleFactor(0.0f) + { + // v3_funknown, single instance + query_interface = query_interface_view_content_scale; + ref = dpf_single_instance_ref<dpf_plugin_view_content_scale>; + unref = dpf_single_instance_unref<dpf_plugin_view_content_scale>; + + // v3_plugin_view_content_scale + scale.set_content_scale_factor = set_content_scale_factor; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_view_content_scale(void* const self, const v3_tuid iid, void** const iface) + { + dpf_plugin_view_content_scale* const scale = *static_cast<dpf_plugin_view_content_scale**>(self); + + if (v3_tuid_match(iid, v3_funknown_iid) || + v3_tuid_match(iid, v3_plugin_view_content_scale_iid)) + { + d_stdout("query_interface_view_content_scale => %p %s %p | OK", self, tuid2str(iid), iface); + ++scale->refcounter; + *iface = self; + return V3_OK; + } + + d_stdout("query_interface_view_content_scale => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface); + + *iface = NULL; + return V3_NO_INTERFACE; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_plugin_view_content_scale + + static v3_result V3_API set_content_scale_factor(void* const self, const float factor) + { + dpf_plugin_view_content_scale* const scale = *static_cast<dpf_plugin_view_content_scale**>(self); + d_stdout("dpf_plugin_view::set_content_scale_factor => %p %f", self, factor); + + scale->scaleFactor = factor; + + if (UIVst3* const uivst3 = scale->uivst3) + return uivst3->setContentScaleFactor(factor); + + return V3_NOT_INITIALIZED; + } +}; + +#if DPF_VST3_USING_HOST_RUN_LOOP +// -------------------------------------------------------------------------------------------------------------------- +// dpf_timer_handler + +struct dpf_timer_handler : v3_timer_handler_cpp { + std::atomic_int refcounter; + ScopedPointer<UIVst3>& uivst3; + bool valid; + + dpf_timer_handler(ScopedPointer<UIVst3>& v) + : refcounter(1), + uivst3(v), + valid(true) + { + // v3_funknown, single instance + query_interface = query_interface_timer_handler; + ref = dpf_single_instance_ref<dpf_timer_handler>; + unref = dpf_single_instance_unref<dpf_timer_handler>; + + // v3_timer_handler + timer.on_timer = on_timer; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_timer_handler(void* self, const v3_tuid iid, void** iface) + { + dpf_timer_handler* const timer = *static_cast<dpf_timer_handler**>(self); + + if (v3_tuid_match(iid, v3_funknown_iid) || + v3_tuid_match(iid, v3_timer_handler_iid)) + { + d_stdout("query_interface_timer_handler => %p %s %p | OK", self, tuid2str(iid), iface); + ++timer->refcounter; + *iface = self; + return V3_OK; + } + + d_stdout("query_interface_timer_handler => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface); + + *iface = NULL; + return V3_NO_INTERFACE; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_timer_handler + + static void V3_API on_timer(void* self) + { + dpf_timer_handler* const timer = *static_cast<dpf_timer_handler**>(self); + + DISTRHO_SAFE_ASSERT_RETURN(timer->valid,); + + timer->uivst3->onTimer(); + } +}; +#endif + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_plugin_view + +static const char* const kSupportedPlatforms[] = { +#if defined(DISTRHO_OS_WINDOWS) + V3_VIEW_PLATFORM_TYPE_HWND, +#elif defined(DISTRHO_OS_MAC) + V3_VIEW_PLATFORM_TYPE_NSVIEW, +#else + V3_VIEW_PLATFORM_TYPE_X11, +#endif +}; + +struct dpf_plugin_view : v3_plugin_view_cpp { + std::atomic_int refcounter; + ScopedPointer<dpf_ui_connection_point> connection; + ScopedPointer<dpf_plugin_view_content_scale> scale; + #if DPF_VST3_USING_HOST_RUN_LOOP + ScopedPointer<dpf_timer_handler> timer; + #endif + ScopedPointer<UIVst3> uivst3; + // cached values + v3_host_application** const hostApplication; + void* const instancePointer; + double sampleRate; + v3_plugin_frame** frame; + v3_run_loop** runloop; + uint32_t nextWidth, nextHeight; + bool sizeRequestedBeforeBeingAttached; + + dpf_plugin_view(v3_host_application** const host, void* const instance, const double sr) + : refcounter(1), + hostApplication(host), + instancePointer(instance), + sampleRate(sr), + frame(nullptr), + runloop(nullptr), + nextWidth(0), + nextHeight(0), + sizeRequestedBeforeBeingAttached(false) + { + d_stdout("dpf_plugin_view() with hostApplication %p", hostApplication); + + // make sure host application is valid through out this view lifetime + if (hostApplication != nullptr) + v3_cpp_obj_ref(hostApplication); + + // v3_funknown, everything custom + query_interface = query_interface_view; + ref = ref_view; + unref = unref_view; + + // v3_plugin_view + view.is_platform_type_supported = is_platform_type_supported; + view.attached = attached; + view.removed = removed; + view.on_wheel = on_wheel; + view.on_key_down = on_key_down; + view.on_key_up = on_key_up; + view.get_size = get_size; + view.on_size = on_size; + view.on_focus = on_focus; + view.set_frame = set_frame; + view.can_resize = can_resize; + view.check_size_constraint = check_size_constraint; + } + + ~dpf_plugin_view() + { + d_stdout("~dpf_plugin_view()"); + + connection = nullptr; + scale = nullptr; + #if DPF_VST3_USING_HOST_RUN_LOOP + timer = nullptr; + #endif + uivst3 = nullptr; + + if (hostApplication != nullptr) + v3_cpp_obj_unref(hostApplication); + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_funknown + + static v3_result V3_API query_interface_view(void* self, const v3_tuid iid, void** iface) + { + dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self); + + if (v3_tuid_match(iid, v3_funknown_iid) || + v3_tuid_match(iid, v3_plugin_view_iid)) + { + d_stdout("query_interface_view => %p %s %p | OK", self, tuid2str(iid), iface); + ++view->refcounter; + *iface = self; + return V3_OK; + } + + if (v3_tuid_match(v3_connection_point_iid, iid)) + { + d_stdout("query_interface_view => %p %s %p | OK convert %p", + self, tuid2str(iid), iface, view->connection.get()); + + if (view->connection == nullptr) + view->connection = new dpf_ui_connection_point(view->uivst3); + else + ++view->connection->refcounter; + *iface = &view->connection; + return V3_OK; + } + + #ifndef DISTRHO_OS_MAC + if (v3_tuid_match(v3_plugin_view_content_scale_iid, iid)) + { + d_stdout("query_interface_view => %p %s %p | OK convert %p", + self, tuid2str(iid), iface, view->scale.get()); + + if (view->scale == nullptr) + view->scale = new dpf_plugin_view_content_scale(view->uivst3); + else + ++view->scale->refcounter; + *iface = &view->scale; + return V3_OK; + } + #endif + + d_stdout("query_interface_view => %p %s %p | WARNING UNSUPPORTED", self, tuid2str(iid), iface); + + *iface = nullptr; + return V3_NO_INTERFACE; + } + + static uint32_t V3_API ref_view(void* self) + { + dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self); + const int refcount = ++view->refcounter; + d_stdout("dpf_plugin_view::ref => %p | refcount %i", self, refcount); + return refcount; + } + + static uint32_t V3_API unref_view(void* self) + { + dpf_plugin_view** const viewptr = static_cast<dpf_plugin_view**>(self); + dpf_plugin_view* const view = *viewptr; + + if (const int refcount = --view->refcounter) + { + d_stdout("dpf_plugin_view::unref => %p | refcount %i", self, refcount); + return refcount; + } + + if (view->connection != nullptr && view->connection->other) + v3_cpp_obj(view->connection->other)->disconnect(view->connection->other, + (v3_connection_point**)&view->connection); + + /** + * Some hosts will have unclean instances of a few of the view child classes at this point. + * We check for those here, going through the whole possible chain to see if it is safe to delete. + * TODO cleanup. + */ + + bool unclean = false; + + if (dpf_ui_connection_point* const conn = view->connection) + { + if (const int refcount = conn->refcounter) + { + unclean = true; + d_stderr("DPF warning: asked to delete view while connection point still active (refcount %d)", refcount); + } + } + + #ifndef DISTRHO_OS_MAC + if (dpf_plugin_view_content_scale* const scale = view->scale) + { + if (const int refcount = scale->refcounter) + { + unclean = true; + d_stderr("DPF warning: asked to delete view while content scale still active (refcount %d)", refcount); + } + } + #endif + + if (unclean) + return 0; + + d_stdout("dpf_plugin_view::unref => %p | refcount is zero, deleting everything now!", self); + + delete view; + delete viewptr; + return 0; + } + + // ---------------------------------------------------------------------------------------------------------------- + // v3_plugin_view + + static v3_result V3_API is_platform_type_supported(void* const self, const char* const platform_type) + { + d_stdout("dpf_plugin_view::is_platform_type_supported => %p %s", self, platform_type); + + for (size_t i=0; i<ARRAY_SIZE(kSupportedPlatforms); ++i) + { + if (std::strcmp(kSupportedPlatforms[i], platform_type) == 0) + return V3_OK; + } + + return V3_NOT_IMPLEMENTED; + } + + static v3_result V3_API attached(void* const self, void* const parent, const char* const platform_type) + { + d_stdout("dpf_plugin_view::attached => %p %p %s", self, parent, platform_type); + dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self); + DISTRHO_SAFE_ASSERT_RETURN(view->uivst3 == nullptr, V3_INVALID_ARG); + + for (size_t i=0; i<ARRAY_SIZE(kSupportedPlatforms); ++i) + { + if (std::strcmp(kSupportedPlatforms[i], platform_type) == 0) + { + #if DPF_VST3_USING_HOST_RUN_LOOP + // find host run loop to plug ourselves into (required on some systems) + DISTRHO_SAFE_ASSERT_RETURN(view->frame != nullptr, V3_INVALID_ARG); + + v3_run_loop** runloop = nullptr; + v3_cpp_obj_query_interface(view->frame, v3_run_loop_iid, &runloop); + DISTRHO_SAFE_ASSERT_RETURN(runloop != nullptr, V3_INVALID_ARG); + + view->runloop = runloop; + #endif + + const float lastScaleFactor = view->scale != nullptr ? view->scale->scaleFactor : 0.0f; + view->uivst3 = new UIVst3((v3_plugin_view**)self, + view->hostApplication, + view->connection != nullptr ? view->connection->other : nullptr, + view->frame, + (uintptr_t)parent, + lastScaleFactor, + view->sampleRate, + view->instancePointer, + view->nextWidth > 0 && view->nextHeight > 0, + view->sizeRequestedBeforeBeingAttached); + + view->uivst3->postInit(view->nextWidth, view->nextHeight); + view->nextWidth = 0; + view->nextHeight = 0; + view->sizeRequestedBeforeBeingAttached = false; + + #if DPF_VST3_USING_HOST_RUN_LOOP + // register a timer host run loop stuff + view->timer = new dpf_timer_handler(view->uivst3); + v3_cpp_obj(runloop)->register_timer(runloop, + (v3_timer_handler**)&view->timer, + DPF_VST3_TIMER_INTERVAL); + #endif + + return V3_OK; + } + } + + return V3_NOT_IMPLEMENTED; + } + + static v3_result V3_API removed(void* const self) + { + d_stdout("dpf_plugin_view::removed => %p", self); + dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self); + DISTRHO_SAFE_ASSERT_RETURN(view->uivst3 != nullptr, V3_INVALID_ARG); + + #if DPF_VST3_USING_HOST_RUN_LOOP + // unregister our timer as needed + if (v3_run_loop** const runloop = view->runloop) + { + if (view->timer != nullptr && view->timer->valid) + { + v3_cpp_obj(runloop)->unregister_timer(runloop, (v3_timer_handler**)&view->timer); + + if (const int refcount = --view->timer->refcounter) + { + view->timer->valid = false; + d_stderr("VST3 warning: Host run loop did not give away timer (refcount %d)", refcount); + } + else + { + view->timer = nullptr; + } + } + + v3_cpp_obj_unref(runloop); + view->runloop = nullptr; + } + #endif + + view->uivst3 = nullptr; + return V3_OK; + } + + static v3_result V3_API on_wheel(void* const self, const float distance) + { +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + d_stdout("dpf_plugin_view::on_wheel => %p %f", self, distance); + dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self); + + UIVst3* const uivst3 = view->uivst3; + DISTRHO_SAFE_ASSERT_RETURN(uivst3 != nullptr, V3_NOT_INITIALIZED); + + return uivst3->onWheel(distance); +#else + return V3_NOT_IMPLEMENTED; + // unused + (void)self; (void)distance; +#endif + } + + static v3_result V3_API on_key_down(void* const self, const int16_t key_char, const int16_t key_code, const int16_t modifiers) + { +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + d_stdout("dpf_plugin_view::on_key_down => %p %i %i %i", self, key_char, key_code, modifiers); + dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self); + + UIVst3* const uivst3 = view->uivst3; + DISTRHO_SAFE_ASSERT_RETURN(uivst3 != nullptr, V3_NOT_INITIALIZED); + + return uivst3->onKeyDown(key_char, key_code, modifiers); +#else + return V3_NOT_IMPLEMENTED; + // unused + (void)self; (void)key_char; (void)key_code; (void)modifiers; +#endif + } + + static v3_result V3_API on_key_up(void* const self, const int16_t key_char, const int16_t key_code, const int16_t modifiers) + { +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + d_stdout("dpf_plugin_view::on_key_up => %p %i %i %i", self, key_char, key_code, modifiers); + dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self); + + UIVst3* const uivst3 = view->uivst3; + DISTRHO_SAFE_ASSERT_RETURN(uivst3 != nullptr, V3_NOT_INITIALIZED); + + return uivst3->onKeyUp(key_char, key_code, modifiers); +#else + return V3_NOT_IMPLEMENTED; + // unused + (void)self; (void)key_char; (void)key_code; (void)modifiers; +#endif + } + + static v3_result V3_API get_size(void* const self, v3_view_rect* const rect) + { + d_stdout("dpf_plugin_view::get_size => %p", self); + dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self); + + if (UIVst3* const uivst3 = view->uivst3) + return uivst3->getSize(rect); + + d_stdout("dpf_plugin_view::get_size => %p | V3_NOT_INITIALIZED", self); + std::memset(rect, 0, sizeof(v3_view_rect)); + view->sizeRequestedBeforeBeingAttached = true; + return V3_NOT_INITIALIZED; + } + + static v3_result V3_API on_size(void* const self, v3_view_rect* const rect) + { + d_stdout("dpf_plugin_view::on_size => %p {%d,%d,%d,%d}", + self, rect->top, rect->left, rect->right, rect->bottom); + DISTRHO_SAFE_ASSERT_INT2_RETURN(rect->right > rect->left, rect->right, rect->left, V3_INVALID_ARG); + DISTRHO_SAFE_ASSERT_INT2_RETURN(rect->bottom > rect->top, rect->bottom, rect->top, V3_INVALID_ARG); + + dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self); + + if (UIVst3* const uivst3 = view->uivst3) + return uivst3->onSize(rect); + + view->nextWidth = static_cast<uint32_t>(rect->right - rect->left); + view->nextHeight = static_cast<uint32_t>(rect->bottom - rect->top); + return V3_OK; + } + + static v3_result V3_API on_focus(void* const self, const v3_bool state) + { +#if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI + d_stdout("dpf_plugin_view::on_focus => %p %u", self, state); + dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self); + + UIVst3* const uivst3 = view->uivst3; + DISTRHO_SAFE_ASSERT_RETURN(uivst3 != nullptr, V3_NOT_INITIALIZED); + + return uivst3->onFocus(state); +#else + return V3_NOT_IMPLEMENTED; + // unused + (void)self; (void)state; +#endif + } + + static v3_result V3_API set_frame(void* const self, v3_plugin_frame** const frame) + { + d_stdout("dpf_plugin_view::set_frame => %p %p", self, frame); + dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self); + + view->frame = frame; + + if (UIVst3* const uivst3 = view->uivst3) + return uivst3->setFrame(frame); + + return V3_OK; + } + + static v3_result V3_API can_resize(void* const self) + { +#if DISTRHO_UI_USER_RESIZABLE + dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self); + + if (UIVst3* const uivst3 = view->uivst3) + return uivst3->canResize(); + + return V3_TRUE; +#else + return V3_FALSE; + + // unused + (void)self; +#endif + } + + static v3_result V3_API check_size_constraint(void* const self, v3_view_rect* const rect) + { + d_stdout("dpf_plugin_view::check_size_constraint => %p {%d,%d,%d,%d}", + self, rect->top, rect->left, rect->right, rect->bottom); + dpf_plugin_view* const view = *static_cast<dpf_plugin_view**>(self); + + if (UIVst3* const uivst3 = view->uivst3) + return uivst3->checkSizeConstraint(rect); + + return V3_NOT_INITIALIZED; + } +}; + +// -------------------------------------------------------------------------------------------------------------------- +// dpf_plugin_view_create (called from plugin side) + +v3_plugin_view** dpf_plugin_view_create(v3_host_application** host, void* instancePointer, double sampleRate); + +v3_plugin_view** dpf_plugin_view_create(v3_host_application** const host, + void* const instancePointer, + const double sampleRate) +{ + dpf_plugin_view** const viewptr = new dpf_plugin_view*; + *viewptr = new dpf_plugin_view(host, instancePointer, sampleRate); + return static_cast<v3_plugin_view**>(static_cast<void*>(viewptr)); +} + +// -------------------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO diff --git a/distrho/src/DistrhoUtils.cpp b/distrho/src/DistrhoUtils.cpp @@ -0,0 +1,161 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DISTRHO_IS_STANDALONE +# error Wrong build configuration +#endif + +#include "../extra/String.hpp" +#include "../DistrhoStandaloneUtils.hpp" + +#ifdef DISTRHO_OS_WINDOWS +# include <windows.h> +#else +# ifndef STATIC_BUILD +# include <dlfcn.h> +# endif +# include <limits.h> +# include <stdlib.h> +#endif + +#if defined(DISTRHO_OS_WINDOWS) && !defined(STATIC_BUILD) && !DISTRHO_IS_STANDALONE +static HINSTANCE hInstance = nullptr; + +DISTRHO_PLUGIN_EXPORT +BOOL WINAPI DllMain(HINSTANCE hInst, DWORD reason, LPVOID) +{ + if (reason == DLL_PROCESS_ATTACH) + hInstance = hInst; + return 1; +} +#endif + +START_NAMESPACE_DISTRHO + +// ----------------------------------------------------------------------- + +const char* getBinaryFilename() +{ + static String filename; + +#ifndef STATIC_BUILD + if (filename.isNotEmpty()) + return filename; + +# ifdef DISTRHO_OS_WINDOWS +# if DISTRHO_IS_STANDALONE + constexpr const HINSTANCE hInstance = nullptr; +# endif + CHAR filenameBuf[MAX_PATH]; + filenameBuf[0] = '\0'; + GetModuleFileNameA(hInstance, filenameBuf, sizeof(filenameBuf)); + filename = filenameBuf; +# else + Dl_info info; + dladdr((void*)getBinaryFilename, &info); + char filenameBuf[PATH_MAX]; + filename = realpath(info.dli_fname, filenameBuf); +# endif +#endif + + return filename; +} + +const char* getPluginFormatName() noexcept +{ +#if defined(DISTRHO_PLUGIN_TARGET_CARLA) + return "Carla"; +#elif defined(DISTRHO_PLUGIN_TARGET_JACK) +# ifdef DISTRHO_OS_WASM + return "Wasm/Standalone"; +# else + return "JACK/Standalone"; +# endif +#elif defined(DISTRHO_PLUGIN_TARGET_LADSPA) + return "LADSPA"; +#elif defined(DISTRHO_PLUGIN_TARGET_DSSI) + return "DSSI"; +#elif defined(DISTRHO_PLUGIN_TARGET_LV2) + return "LV2"; +#elif defined(DISTRHO_PLUGIN_TARGET_VST2) + return "VST2"; +#elif defined(DISTRHO_PLUGIN_TARGET_VST3) + return "VST3"; +#else + return "Unknown"; +#endif +} + +const char* getResourcePath(const char* const bundlePath) noexcept +{ + DISTRHO_SAFE_ASSERT_RETURN(bundlePath != nullptr, nullptr); + +#if defined(DISTRHO_PLUGIN_TARGET_JACK) || defined(DISTRHO_PLUGIN_TARGET_VST2) + static String resourcePath; + + if (resourcePath.isEmpty()) + { + resourcePath = bundlePath; +# ifdef DISTRHO_OS_MAC + resourcePath += "/Contents/Resources"; +# else + resourcePath += DISTRHO_OS_SEP_STR "resources"; +# endif + } + + return resourcePath.buffer(); +#elif defined(DISTRHO_PLUGIN_TARGET_LV2) + static String resourcePath; + + if (resourcePath.isEmpty()) + { + resourcePath = bundlePath; + resourcePath += DISTRHO_OS_SEP_STR "resources"; + } + + return resourcePath.buffer(); +#elif defined(DISTRHO_PLUGIN_TARGET_VST3) + static String resourcePath; + + if (resourcePath.isEmpty()) + { + resourcePath = bundlePath; + resourcePath += "/Contents/Resources"; + } + + return resourcePath.buffer(); +#endif + + return nullptr; +} + +#ifndef DISTRHO_PLUGIN_TARGET_JACK +// all these are null for non-standalone targets +bool isUsingNativeAudio() noexcept { return false; } +bool supportsAudioInput() { return false; } +bool supportsBufferSizeChanges() { return false; } +bool supportsMIDI() { return false; } +bool isAudioInputEnabled() { return false; } +bool isMIDIEnabled() { return false; } +uint getBufferSize() { return 0; } +bool requestAudioInput() { return false; } +bool requestBufferSizeChange(uint) { return false; } +bool requestMIDI() { return false; } +#endif + +// ----------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO diff --git a/distrho/src/dssi/seq_event-compat.h b/distrho/src/dssi/seq_event-compat.h @@ -1,3 +1,13 @@ +/** + * \file include/seq_event.h + * \brief Application interface library for the ALSA driver + * \author Jaroslav Kysela <perex@perex.cz> + * \author Abramo Bagnara <abramo@alsa-project.org> + * \author Takashi Iwai <tiwai@suse.de> + * \date 1998-2001 + * + * Application interface library for the ALSA driver + */ /* * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as diff --git a/distrho/src/jackbridge/JackBridge.cpp b/distrho/src/jackbridge/JackBridge.cpp @@ -1,6 +1,6 @@ /* * JackBridge for DPF - * Copyright (C) 2013-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2013-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -15,6 +15,7 @@ */ #include "JackBridge.hpp" +#include "../../DistrhoStandaloneUtils.hpp" #if ! (defined(JACKBRIDGE_DIRECT) || defined(JACKBRIDGE_DUMMY)) @@ -33,14 +34,49 @@ #endif #include <cerrno> -#include "../../extra/LibraryUtils.hpp" -// in case JACK fails, we fallback to RtAudio's native API -#ifdef DISTRHO_PROPER_CPP11_SUPPORT +#ifdef HAVE_JACK +# include "../../extra/LibraryUtils.hpp" +#else +typedef void* lib_t; +#endif + +// in case JACK fails, we fallback to native bridges simulating JACK API +#include "NativeBridge.hpp" + +#if defined(DISTRHO_OS_WASM) +# include "WebBridge.hpp" +#endif + +#ifndef DISTRHO_PROPER_CPP11_SUPPORT +# undef HAVE_RTAUDIO +#endif + +#ifdef DPF_JACK_STANDALONE_SKIP_RTAUDIO_FALLBACK +# undef HAVE_RTAUDIO +#endif + +#ifdef DPF_JACK_STANDALONE_SKIP_SDL2_FALLBACK +# undef HAVE_SDL2 +#endif + +#if defined(HAVE_RTAUDIO) && DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 +// fix conflict between DGL and macOS names +# define Point CorePoint +# define Size CoreSize # include "RtAudioBridge.hpp" # ifdef RTAUDIO_API_TYPE # include "rtaudio/RtAudio.cpp" # endif +# ifdef RTMIDI_API_TYPE +# include "rtmidi/RtMidi.cpp" +# endif +# undef Point +# undef Size +#endif + +#if defined(HAVE_SDL2) && DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 +# include "SDL2Bridge.hpp" #endif // ----------------------------------------------------------------------------- @@ -308,14 +344,9 @@ struct JackBridge { jacksym_remove_all_properties remove_all_properties_ptr; jacksym_set_property_change_callback set_property_change_callback_ptr; -#ifdef __WINE__ + #ifdef __WINE__ jacksym_set_thread_creator set_thread_creator_ptr; -#endif - - static bool usingRtAudio; -#ifdef RTAUDIO_API_TYPE - static RtAudioBridge rtAudio; -#endif + #endif JackBridge() : lib(nullptr), @@ -411,19 +442,21 @@ struct JackBridge { remove_properties_ptr(nullptr), remove_all_properties_ptr(nullptr), set_property_change_callback_ptr(nullptr) -#ifdef __WINE__ + #ifdef __WINE__ , set_thread_creator_ptr(nullptr) -#endif + #endif { -# if defined(DISTRHO_OS_MAC) - const char* const filename("libjack.dylib"); -# elif defined(DISTRHO_OS_WINDOWS) && defined(_WIN64) - const char* const filename("libjack64.dll"); -# elif defined(DISTRHO_OS_WINDOWS) - const char* const filename("libjack.dll"); -# else - const char* const filename("libjack.so.0"); -# endif + #ifdef HAVE_JACK + #if defined(DISTRHO_OS_MAC) + const char* const filename = "libjack.dylib"; + #elif defined(DISTRHO_OS_WINDOWS) && defined(_WIN64) + const char* const filename = "libjack64.dll"; + #elif defined(DISTRHO_OS_WINDOWS) + const char* const filename = "libjack.dll"; + #else + const char* const filename = "libjack.so.0"; + #endif + USE_NAMESPACE_DISTRHO lib = lib_open(filename); @@ -554,14 +587,16 @@ struct JackBridge { LIB_SYMBOL(remove_all_properties) LIB_SYMBOL(set_property_change_callback) -#ifdef __WINE__ + #ifdef __WINE__ LIB_SYMBOL(set_thread_creator) -#endif + #endif + #endif #undef JOIN #undef LIB_SYMBOL } + #ifdef HAVE_JACK ~JackBridge() noexcept { USE_NAMESPACE_DISTRHO @@ -572,14 +607,14 @@ struct JackBridge { lib = nullptr; } } + #endif DISTRHO_DECLARE_NON_COPYABLE(JackBridge); }; -bool JackBridge::usingRtAudio = false; -#ifdef RTAUDIO_API_TYPE -RtAudioBridge JackBridge::rtAudio; -#endif +static bool usingNativeBridge = false; +static bool usingRealJACK = true; +static NativeBridge* nativeBridge = nullptr; // ----------------------------------------------------------------------------- @@ -808,7 +843,7 @@ bool jackbridge_is_ok() noexcept { #if defined(JACKBRIDGE_DUMMY) return false; -#elif defined(JACKBRIDGE_DIRECT) || defined(RTAUDIO_API_TYPE) +#elif defined(JACKBRIDGE_DIRECT) || defined(DISTRHO_OS_WASM) || defined(RTAUDIO_API_TYPE) return true; #else return (getBridgeInstance().lib != nullptr); @@ -817,7 +852,7 @@ bool jackbridge_is_ok() noexcept void jackbridge_init() { -#if defined(__WINE__) && ! defined(JACKBRIDGE_DIRECT) +#if defined(__WINE__) && !defined(JACKBRIDGE_DIRECT) if (getBridgeInstance().set_thread_creator_ptr != nullptr) getBridgeInstance().set_thread_creator_ptr(WineBridge::thread_creator); #endif @@ -831,9 +866,8 @@ void jackbridge_get_version(int* major_ptr, int* minor_ptr, int* micro_ptr, int* #elif defined(JACKBRIDGE_DIRECT) return jack_get_version(major_ptr, minor_ptr, micro_ptr, proto_ptr); #else - if (! JackBridge::usingRtAudio) - if (getBridgeInstance().get_version_ptr != nullptr) - return getBridgeInstance().get_version_ptr(major_ptr, minor_ptr, micro_ptr, proto_ptr); + if (usingRealJACK && getBridgeInstance().get_version_ptr != nullptr) + return getBridgeInstance().get_version_ptr(major_ptr, minor_ptr, micro_ptr, proto_ptr); #endif if (major_ptr != nullptr) *major_ptr = 0; @@ -851,11 +885,7 @@ const char* jackbridge_get_version_string() #elif defined(JACKBRIDGE_DIRECT) return jack_get_version_string(); #else -# ifdef RTAUDIO_API_TYPE - if (JackBridge::usingRtAudio) - return RTAUDIO_VERSION; -# endif - if (getBridgeInstance().get_version_string_ptr != nullptr) + if (usingRealJACK && getBridgeInstance().get_version_string_ptr != nullptr) return getBridgeInstance().get_version_string_ptr(); #endif return nullptr; @@ -869,17 +899,40 @@ jack_client_t* jackbridge_client_open(const char* client_name, uint32_t options, #elif defined(JACKBRIDGE_DIRECT) return jack_client_open(client_name, static_cast<jack_options_t>(options), status); #else + #ifndef DISTRHO_OS_WASM if (getBridgeInstance().client_open_ptr != nullptr) if (jack_client_t* const client = getBridgeInstance().client_open_ptr(client_name, static_cast<jack_options_t>(options), status)) return client; -# ifdef RTAUDIO_API_TYPE - if (JackBridge::rtAudio.open()) - { - d_stdout("JACK setup failed, using RtAudio instead"); - JackBridge::usingRtAudio = true; - return (jack_client_t*)0x1; // return non-null - } -# endif + #endif + + static jack_client_t* const kValidClient = (jack_client_t*)0x1; + + // maybe unused + (void)kValidClient; + + usingNativeBridge = true; + usingRealJACK = false; + + #ifdef DISTRHO_OS_WASM + nativeBridge = new WebBridge; + if (nativeBridge->open(client_name)) + return kValidClient; + delete nativeBridge; + #endif + + #if defined(HAVE_RTAUDIO) && defined(RTAUDIO_API_TYPE) + nativeBridge = new RtAudioBridge; + if (nativeBridge->open(client_name)) + return kValidClient; + delete nativeBridge; + #endif + + #if defined(HAVE_SDL2) && DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + nativeBridge = new SDL2Bridge; + if (nativeBridge->open(client_name)) + return kValidClient; + delete nativeBridge; + #endif #endif if (status != nullptr) *status = JackServerError; @@ -892,13 +945,18 @@ bool jackbridge_client_close(jack_client_t* client) #elif defined(JACKBRIDGE_DIRECT) return (jack_client_close(client) == 0); #else -# ifdef RTAUDIO_API_TYPE - if (JackBridge::usingRtAudio) + if (usingNativeBridge) { - JackBridge::usingRtAudio = false; - return JackBridge::rtAudio.close(); + if (nativeBridge != nullptr) + { + nativeBridge->close(); + delete nativeBridge; + nativeBridge = nullptr; + } + usingNativeBridge = false; + usingRealJACK = true; + return true; } -# endif if (getBridgeInstance().client_close_ptr != nullptr) return (getBridgeInstance().client_close_ptr(client) == 0); #endif @@ -913,24 +971,20 @@ int jackbridge_client_name_size() #elif defined(JACKBRIDGE_DIRECT) return jack_client_name_size(); #else - if (! JackBridge::usingRtAudio) - if (getBridgeInstance().client_name_size_ptr != nullptr) - return getBridgeInstance().client_name_size_ptr(); + if (usingRealJACK && getBridgeInstance().client_name_size_ptr != nullptr) + return getBridgeInstance().client_name_size_ptr(); #endif return 33; } -char* jackbridge_get_client_name(jack_client_t* client) +const char* jackbridge_get_client_name(jack_client_t* client) { #if defined(JACKBRIDGE_DUMMY) #elif defined(JACKBRIDGE_DIRECT) return jack_get_client_name(client); #else - if (JackBridge::usingRtAudio) - { - char* const name = (char*)DISTRHO_PLUGIN_NAME; - return name; - } + if (usingNativeBridge) + return DISTRHO_PLUGIN_NAME; if (getBridgeInstance().get_client_name_ptr != nullptr) return getBridgeInstance().get_client_name_ptr(client); #endif @@ -945,7 +999,7 @@ char* jackbridge_client_get_uuid(jack_client_t* client) #elif defined(JACKBRIDGE_DIRECT) return jack_client_get_uuid(client); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (const jacksym_client_get_uuid func = getBridgeInstance().client_get_uuid_ptr) return func(client); #endif @@ -958,7 +1012,7 @@ char* jackbridge_get_uuid_for_client_name(jack_client_t* client, const char* nam #elif defined(JACKBRIDGE_DIRECT) return jack_get_uuid_for_client_name(client, name); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().get_uuid_for_client_name_ptr != nullptr) return getBridgeInstance().get_uuid_for_client_name_ptr(client, name); #endif @@ -971,7 +1025,7 @@ char* jackbridge_get_client_name_by_uuid(jack_client_t* client, const char* uuid #elif defined(JACKBRIDGE_DIRECT) return jack_get_client_name_by_uuid(client, uuid); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().get_client_name_by_uuid_ptr != nullptr) return getBridgeInstance().get_client_name_by_uuid_ptr(client, uuid); #endif @@ -986,7 +1040,7 @@ bool jackbridge_uuid_parse(const char* buf, jack_uuid_t* uuid) #elif defined(JACKBRIDGE_DIRECT) return (jack_uuid_parse(buf, uuid) == 0); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (const jacksym_uuid_parse func = getBridgeInstance().uuid_parse_ptr) return (func(buf, uuid) == 0); #endif @@ -999,7 +1053,7 @@ void jackbridge_uuid_unparse(jack_uuid_t uuid, char buf[JACK_UUID_STRING_SIZE]) #elif defined(JACKBRIDGE_DIRECT) jack_uuid_unparse(uuid, buf); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (const jacksym_uuid_unparse func = getBridgeInstance().uuid_unparse_ptr) return func(uuid, buf); #endif @@ -1013,10 +1067,8 @@ bool jackbridge_activate(jack_client_t* client) #elif defined(JACKBRIDGE_DIRECT) return (jack_activate(client) == 0); #else -# ifdef RTAUDIO_API_TYPE - if (JackBridge::usingRtAudio) - return JackBridge::rtAudio.activate(); -# endif + if (usingNativeBridge) + return nativeBridge->activate(); if (getBridgeInstance().activate_ptr != nullptr) return (getBridgeInstance().activate_ptr(client) == 0); #endif @@ -1029,10 +1081,8 @@ bool jackbridge_deactivate(jack_client_t* client) #elif defined(JACKBRIDGE_DIRECT) return (jack_deactivate(client) == 0); #else -# ifdef RTAUDIO_API_TYPE - if (JackBridge::usingRtAudio) - return JackBridge::rtAudio.deactivate(); -# endif + if (usingNativeBridge) + return nativeBridge->deactivate(); if (getBridgeInstance().deactivate_ptr != nullptr) return (getBridgeInstance().deactivate_ptr(client) == 0); #endif @@ -1045,7 +1095,7 @@ bool jackbridge_is_realtime(jack_client_t* client) #elif defined(JACKBRIDGE_DIRECT) return jack_is_realtime(client); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().is_realtime_ptr != nullptr) return getBridgeInstance().is_realtime_ptr(client); #endif @@ -1060,7 +1110,7 @@ bool jackbridge_set_thread_init_callback(jack_client_t* client, JackThreadInitCa #elif defined(JACKBRIDGE_DIRECT) return (jack_set_thread_init_callback(client, thread_init_callback, arg) == 0); #else - if (! JackBridge::usingRtAudio && getBridgeInstance().set_thread_init_callback_ptr != nullptr) + if (usingRealJACK && getBridgeInstance().set_thread_init_callback_ptr != nullptr) { # ifdef __WINE__ WineBridge::getInstance().set_thread_init(thread_init_callback); @@ -1079,7 +1129,7 @@ void jackbridge_on_shutdown(jack_client_t* client, JackShutdownCallback shutdown #elif defined(JACKBRIDGE_DIRECT) jack_on_shutdown(client, shutdown_callback, arg); #else - if (! JackBridge::usingRtAudio && getBridgeInstance().on_shutdown_ptr != nullptr) + if (usingRealJACK && getBridgeInstance().on_shutdown_ptr != nullptr) { # ifdef __WINE__ WineBridge::getInstance().set_shutdown(shutdown_callback); @@ -1097,7 +1147,7 @@ void jackbridge_on_info_shutdown(jack_client_t* client, JackInfoShutdownCallback #elif defined(JACKBRIDGE_DIRECT) jack_on_info_shutdown(client, shutdown_callback, arg); #else - if (! JackBridge::usingRtAudio && getBridgeInstance().on_info_shutdown_ptr != nullptr) + if (usingRealJACK && getBridgeInstance().on_info_shutdown_ptr != nullptr) { # ifdef __WINE__ WineBridge::getInstance().set_info_shutdown(shutdown_callback); @@ -1115,14 +1165,12 @@ bool jackbridge_set_process_callback(jack_client_t* client, JackProcessCallback #elif defined(JACKBRIDGE_DIRECT) return (jack_set_process_callback(client, process_callback, arg) == 0); #else -# ifdef RTAUDIO_API_TYPE - if (JackBridge::usingRtAudio) + if (usingNativeBridge) { - JackBridge::rtAudio.jackProcessCallback = process_callback; - JackBridge::rtAudio.jackProcessArg = arg; + nativeBridge->jackProcessCallback = process_callback; + nativeBridge->jackProcessArg = arg; return true; } -# endif if (getBridgeInstance().set_process_callback_ptr != nullptr) { # ifdef __WINE__ @@ -1142,7 +1190,7 @@ bool jackbridge_set_freewheel_callback(jack_client_t* client, JackFreewheelCallb #elif defined(JACKBRIDGE_DIRECT) return (jack_set_freewheel_callback(client, freewheel_callback, arg) == 0); #else - if (! JackBridge::usingRtAudio && getBridgeInstance().set_freewheel_callback_ptr != nullptr) + if (usingRealJACK && getBridgeInstance().set_freewheel_callback_ptr != nullptr) { # ifdef __WINE__ WineBridge::getInstance().set_freewheel(freewheel_callback); @@ -1161,7 +1209,13 @@ bool jackbridge_set_buffer_size_callback(jack_client_t* client, JackBufferSizeCa #elif defined(JACKBRIDGE_DIRECT) return (jack_set_buffer_size_callback(client, bufsize_callback, arg) == 0); #else - if (! JackBridge::usingRtAudio && getBridgeInstance().set_buffer_size_callback_ptr != nullptr) + if (usingNativeBridge) + { + nativeBridge->bufferSizeCallback = bufsize_callback; + nativeBridge->jackBufferSizeArg = arg; + return true; + } + if (getBridgeInstance().set_buffer_size_callback_ptr != nullptr) { # ifdef __WINE__ WineBridge::getInstance().set_bufsize(bufsize_callback); @@ -1180,7 +1234,7 @@ bool jackbridge_set_sample_rate_callback(jack_client_t* client, JackSampleRateCa #elif defined(JACKBRIDGE_DIRECT) return (jack_set_sample_rate_callback(client, srate_callback, arg) == 0); #else - if (! JackBridge::usingRtAudio && getBridgeInstance().set_sample_rate_callback_ptr != nullptr) + if (usingRealJACK && getBridgeInstance().set_sample_rate_callback_ptr != nullptr) { # ifdef __WINE__ WineBridge::getInstance().set_srate(srate_callback); @@ -1199,7 +1253,7 @@ bool jackbridge_set_client_registration_callback(jack_client_t* client, JackClie #elif defined(JACKBRIDGE_DIRECT) return (jack_set_client_registration_callback(client, registration_callback, arg) == 0); #else - if (! JackBridge::usingRtAudio && getBridgeInstance().set_client_registration_callback_ptr != nullptr) + if (usingRealJACK && getBridgeInstance().set_client_registration_callback_ptr != nullptr) { # ifdef __WINE__ WineBridge::getInstance().set_client_reg(registration_callback); @@ -1218,7 +1272,7 @@ bool jackbridge_set_port_registration_callback(jack_client_t* client, JackPortRe #elif defined(JACKBRIDGE_DIRECT) return (jack_set_port_registration_callback(client, registration_callback, arg) == 0); #else - if (! JackBridge::usingRtAudio && getBridgeInstance().set_port_registration_callback_ptr != nullptr) + if (usingRealJACK && getBridgeInstance().set_port_registration_callback_ptr != nullptr) { # ifdef __WINE__ WineBridge::getInstance().set_port_reg(registration_callback); @@ -1237,7 +1291,7 @@ bool jackbridge_set_port_rename_callback(jack_client_t* client, JackPortRenameCa #elif defined(JACKBRIDGE_DIRECT) return (jack_set_port_rename_callback(client, rename_callback, arg) == 0); #else - if (! JackBridge::usingRtAudio && getBridgeInstance().set_port_rename_callback_ptr != nullptr) + if (usingRealJACK && getBridgeInstance().set_port_rename_callback_ptr != nullptr) { # ifdef __WINE__ WineBridge::getInstance().set_port_rename(rename_callback); @@ -1256,7 +1310,7 @@ bool jackbridge_set_port_connect_callback(jack_client_t* client, JackPortConnect #elif defined(JACKBRIDGE_DIRECT) return (jack_set_port_connect_callback(client, connect_callback, arg) == 0); #else - if (! JackBridge::usingRtAudio && getBridgeInstance().set_port_connect_callback_ptr != nullptr) + if (usingRealJACK && getBridgeInstance().set_port_connect_callback_ptr != nullptr) { # ifdef __WINE__ WineBridge::getInstance().set_port_conn(connect_callback); @@ -1275,7 +1329,7 @@ bool jackbridge_set_graph_order_callback(jack_client_t* client, JackGraphOrderCa #elif defined(JACKBRIDGE_DIRECT) return (jack_set_graph_order_callback(client, graph_callback, arg) == 0); #else - if (! JackBridge::usingRtAudio && getBridgeInstance().set_graph_order_callback_ptr != nullptr) + if (usingRealJACK && getBridgeInstance().set_graph_order_callback_ptr != nullptr) { # ifdef __WINE__ WineBridge::getInstance().set_graph_order(graph_callback); @@ -1294,7 +1348,7 @@ bool jackbridge_set_xrun_callback(jack_client_t* client, JackXRunCallback xrun_c #elif defined(JACKBRIDGE_DIRECT) return (jack_set_xrun_callback(client, xrun_callback, arg) == 0); #else - if (! JackBridge::usingRtAudio && getBridgeInstance().set_xrun_callback_ptr != nullptr) + if (usingRealJACK && getBridgeInstance().set_xrun_callback_ptr != nullptr) { # ifdef __WINE__ WineBridge::getInstance().set_xrun(xrun_callback); @@ -1313,7 +1367,7 @@ bool jackbridge_set_latency_callback(jack_client_t* client, JackLatencyCallback #elif defined(JACKBRIDGE_DIRECT) return (jack_set_latency_callback(client, latency_callback, arg) == 0); #else - if (! JackBridge::usingRtAudio && getBridgeInstance().set_latency_callback_ptr != nullptr) + if (usingRealJACK && getBridgeInstance().set_latency_callback_ptr != nullptr) { # ifdef __WINE__ WineBridge::getInstance().set_latency(latency_callback); @@ -1334,7 +1388,7 @@ bool jackbridge_set_freewheel(jack_client_t* client, bool onoff) #elif defined(JACKBRIDGE_DIRECT) return jack_set_freewheel(client, onoff); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().set_freewheel_ptr != nullptr) return getBridgeInstance().set_freewheel_ptr(client, onoff); #endif @@ -1347,9 +1401,10 @@ bool jackbridge_set_buffer_size(jack_client_t* client, jack_nframes_t nframes) #elif defined(JACKBRIDGE_DIRECT) return jack_set_buffer_size(client, nframes); #else - if (! JackBridge::usingRtAudio) - if (getBridgeInstance().set_buffer_size_ptr != nullptr) - return getBridgeInstance().set_buffer_size_ptr(client, nframes); + if (usingNativeBridge) + return nativeBridge->requestBufferSizeChange(nframes); + if (getBridgeInstance().set_buffer_size_ptr != nullptr) + return getBridgeInstance().set_buffer_size_ptr(client, nframes); #endif return false; } @@ -1362,10 +1417,8 @@ jack_nframes_t jackbridge_get_sample_rate(jack_client_t* client) #elif defined(JACKBRIDGE_DIRECT) return jack_get_sample_rate(client); #else -# ifdef RTAUDIO_API_TYPE - if (JackBridge::usingRtAudio) - return JackBridge::rtAudio.sampleRate; -# endif + if (usingNativeBridge) + return nativeBridge->sampleRate; if (getBridgeInstance().get_sample_rate_ptr != nullptr) return getBridgeInstance().get_sample_rate_ptr(client); #endif @@ -1378,10 +1431,8 @@ jack_nframes_t jackbridge_get_buffer_size(jack_client_t* client) #elif defined(JACKBRIDGE_DIRECT) return jack_get_buffer_size(client); #else -# ifdef RTAUDIO_API_TYPE - if (JackBridge::usingRtAudio) - return JackBridge::rtAudio.bufferSize; -# endif + if (usingNativeBridge) + return nativeBridge->bufferSize; if (getBridgeInstance().get_buffer_size_ptr != nullptr) return getBridgeInstance().get_buffer_size_ptr(client); #endif @@ -1394,7 +1445,7 @@ float jackbridge_cpu_load(jack_client_t* client) #elif defined(JACKBRIDGE_DIRECT) return jack_cpu_load(client); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().cpu_load_ptr != nullptr) return getBridgeInstance().cpu_load_ptr(client); #endif @@ -1409,10 +1460,8 @@ jack_port_t* jackbridge_port_register(jack_client_t* client, const char* port_na #elif defined(JACKBRIDGE_DIRECT) return jack_port_register(client, port_name, type, flags, buffer_size); #else -# ifdef RTAUDIO_API_TYPE - if (JackBridge::usingRtAudio) - return JackBridge::rtAudio.registerPort(type, flags); -# endif + if (usingNativeBridge) + return nativeBridge->registerPort(type, flags); if (getBridgeInstance().port_register_ptr != nullptr) return getBridgeInstance().port_register_ptr(client, port_name, type, static_cast<ulong>(flags), @@ -1427,7 +1476,7 @@ bool jackbridge_port_unregister(jack_client_t* client, jack_port_t* port) #elif defined(JACKBRIDGE_DIRECT) return (jack_port_unregister(client, port) == 0); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_unregister_ptr != nullptr) return (getBridgeInstance().port_unregister_ptr(client, port) == 0); #endif @@ -1440,10 +1489,8 @@ void* jackbridge_port_get_buffer(jack_port_t* port, jack_nframes_t nframes) #elif defined(JACKBRIDGE_DIRECT) return jack_port_get_buffer(port, nframes); #else -# ifdef RTAUDIO_API_TYPE - if (JackBridge::usingRtAudio) - return JackBridge::rtAudio.getPortBuffer(port); -# endif + if (usingNativeBridge) + return nativeBridge->getPortBuffer(port); if (getBridgeInstance().port_get_buffer_ptr != nullptr) return getBridgeInstance().port_get_buffer_ptr(port, nframes); #endif @@ -1458,7 +1505,7 @@ const char* jackbridge_port_name(const jack_port_t* port) #elif defined(JACKBRIDGE_DIRECT) return jack_port_name(port); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_name_ptr != nullptr) return getBridgeInstance().port_name_ptr(port); #endif @@ -1471,7 +1518,7 @@ jack_uuid_t jackbridge_port_uuid(const jack_port_t* port) #elif defined(JACKBRIDGE_DIRECT) return jack_port_uuid(port); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_uuid_ptr != nullptr) return getBridgeInstance().port_uuid_ptr(port); #endif @@ -1484,7 +1531,7 @@ const char* jackbridge_port_short_name(const jack_port_t* port) #elif defined(JACKBRIDGE_DIRECT) return jack_port_short_name(port); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_short_name_ptr != nullptr) return getBridgeInstance().port_short_name_ptr(port); #endif @@ -1497,7 +1544,7 @@ int jackbridge_port_flags(const jack_port_t* port) #elif defined(JACKBRIDGE_DIRECT) return jack_port_flags(port); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_flags_ptr != nullptr) return getBridgeInstance().port_flags_ptr(port); #endif @@ -1510,7 +1557,7 @@ const char* jackbridge_port_type(const jack_port_t* port) #elif defined(JACKBRIDGE_DIRECT) return jack_port_type(port); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_type_ptr != nullptr) return getBridgeInstance().port_type_ptr(port); #endif @@ -1523,7 +1570,7 @@ bool jackbridge_port_is_mine(const jack_client_t* client, const jack_port_t* por #elif defined(JACKBRIDGE_DIRECT) return jack_port_is_mine(client, port); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_is_mine_ptr != nullptr) return getBridgeInstance().port_is_mine_ptr(client, port); #endif @@ -1536,7 +1583,7 @@ int jackbridge_port_connected(const jack_port_t* port) #elif defined(JACKBRIDGE_DIRECT) return jack_port_connected(port); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_connected_ptr != nullptr) return getBridgeInstance().port_connected_ptr(port); #endif @@ -1549,7 +1596,7 @@ bool jackbridge_port_connected_to(const jack_port_t* port, const char* port_name #elif defined(JACKBRIDGE_DIRECT) return jack_port_connected_to(port, port_name); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_connected_to_ptr != nullptr) return getBridgeInstance().port_connected_to_ptr(port, port_name); #endif @@ -1562,7 +1609,7 @@ const char** jackbridge_port_get_connections(const jack_port_t* port) #elif defined(JACKBRIDGE_DIRECT) return jack_port_get_connections(port); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_get_connections_ptr != nullptr) return getBridgeInstance().port_get_connections_ptr(port); #endif @@ -1575,7 +1622,7 @@ const char** jackbridge_port_get_all_connections(const jack_client_t* client, co #elif defined(JACKBRIDGE_DIRECT) return jack_port_get_all_connections(client, port); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_get_all_connections_ptr != nullptr) return getBridgeInstance().port_get_all_connections_ptr(client, port); #endif @@ -1590,7 +1637,7 @@ bool jackbridge_port_rename(jack_client_t* client, jack_port_t* port, const char #elif defined(JACKBRIDGE_DIRECT) return (jack_port_rename(client, port, port_name) == 0); #else - if (JackBridge::usingRtAudio) + if (usingNativeBridge) return false; // Try new API first if (getBridgeInstance().port_rename_ptr != nullptr) @@ -1608,7 +1655,7 @@ bool jackbridge_port_set_alias(jack_port_t* port, const char* alias) #elif defined(JACKBRIDGE_DIRECT) return (jack_port_set_alias(port, alias) == 0); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_set_alias_ptr != nullptr) return (getBridgeInstance().port_set_alias_ptr(port, alias) == 0); #endif @@ -1621,7 +1668,7 @@ bool jackbridge_port_unset_alias(jack_port_t* port, const char* alias) #elif defined(JACKBRIDGE_DIRECT) return (jack_port_unset_alias(port, alias) == 0); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_unset_alias_ptr != nullptr) return (getBridgeInstance().port_unset_alias_ptr(port, alias) == 0); #endif @@ -1634,7 +1681,7 @@ int jackbridge_port_get_aliases(const jack_port_t* port, char* const aliases[2]) #elif defined(JACKBRIDGE_DIRECT) return (jack_port_get_aliases(port, aliases) == 0); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_get_aliases_ptr != nullptr) return getBridgeInstance().port_get_aliases_ptr(port, aliases); #endif @@ -1649,7 +1696,7 @@ bool jackbridge_port_request_monitor(jack_port_t* port, bool onoff) #elif defined(JACKBRIDGE_DIRECT) return (jack_port_request_monitor(port, onoff) == 0); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_request_monitor_ptr != nullptr) return (getBridgeInstance().port_request_monitor_ptr(port, onoff) == 0); #endif @@ -1662,7 +1709,7 @@ bool jackbridge_port_request_monitor_by_name(jack_client_t* client, const char* #elif defined(JACKBRIDGE_DIRECT) return (jack_port_request_monitor_by_name(client, port_name, onoff) == 0); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_request_monitor_by_name_ptr != nullptr) return (getBridgeInstance().port_request_monitor_by_name_ptr(client, port_name, onoff) == 0); #endif @@ -1675,7 +1722,7 @@ bool jackbridge_port_ensure_monitor(jack_port_t* port, bool onoff) #elif defined(JACKBRIDGE_DIRECT) return (jack_port_ensure_monitor(port, onoff) == 0); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_ensure_monitor_ptr != nullptr) return (getBridgeInstance().port_ensure_monitor_ptr(port, onoff) == 0); #endif @@ -1688,7 +1735,7 @@ bool jackbridge_port_monitoring_input(jack_port_t* port) #elif defined(JACKBRIDGE_DIRECT) return jack_port_monitoring_input(port); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_monitoring_input_ptr != nullptr) return getBridgeInstance().port_monitoring_input_ptr(port); #endif @@ -1703,7 +1750,7 @@ bool jackbridge_connect(jack_client_t* client, const char* source_port, const ch #elif defined(JACKBRIDGE_DIRECT) return (jack_connect(client, source_port, destination_port) == 0); #else - if (! JackBridge::usingRtAudio && getBridgeInstance().connect_ptr != nullptr) + if (usingRealJACK && getBridgeInstance().connect_ptr != nullptr) { const int ret = getBridgeInstance().connect_ptr(client, source_port, destination_port); return ret == 0 || ret == EEXIST; @@ -1718,7 +1765,7 @@ bool jackbridge_disconnect(jack_client_t* client, const char* source_port, const #elif defined(JACKBRIDGE_DIRECT) return (jack_disconnect(client, source_port, destination_port) == 0); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().disconnect_ptr != nullptr) return (getBridgeInstance().disconnect_ptr(client, source_port, destination_port) == 0); #endif @@ -1731,7 +1778,7 @@ bool jackbridge_port_disconnect(jack_client_t* client, jack_port_t* port) #elif defined(JACKBRIDGE_DIRECT) return (jack_port_disconnect(client, port) == 0); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_disconnect_ptr != nullptr) return (getBridgeInstance().port_disconnect_ptr(client, port) == 0); #endif @@ -1746,7 +1793,7 @@ int jackbridge_port_name_size() #elif defined(JACKBRIDGE_DIRECT) return jack_port_name_size(); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_name_size_ptr != nullptr) return getBridgeInstance().port_name_size_ptr(); #endif @@ -1759,7 +1806,7 @@ int jackbridge_port_type_size() #elif defined(JACKBRIDGE_DIRECT) return jack_port_type_size(); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_type_size_ptr != nullptr) return getBridgeInstance().port_type_size_ptr(); #endif @@ -1772,7 +1819,7 @@ uint32_t jackbridge_port_type_get_buffer_size(jack_client_t* client, const char* #elif defined(JACKBRIDGE_DIRECT) return static_cast<uint32_t>(jack_port_type_get_buffer_size(client, port_type)); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_type_get_buffer_size_ptr != nullptr) return static_cast<uint32_t>(getBridgeInstance().port_type_get_buffer_size_ptr(client, port_type)); #endif @@ -1787,7 +1834,7 @@ void jackbridge_port_get_latency_range(jack_port_t* port, uint32_t mode, jack_la #elif defined(JACKBRIDGE_DIRECT) return jack_port_get_latency_range(port, static_cast<jack_latency_callback_mode_t>(mode), range); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_get_latency_range_ptr != nullptr) return getBridgeInstance().port_get_latency_range_ptr(port, static_cast<jack_latency_callback_mode_t>(mode), @@ -1803,7 +1850,7 @@ void jackbridge_port_set_latency_range(jack_port_t* port, uint32_t mode, jack_la #elif defined(JACKBRIDGE_DIRECT) jack_port_set_latency_range(port, static_cast<jack_latency_callback_mode_t>(mode), range); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_set_latency_range_ptr != nullptr) getBridgeInstance().port_set_latency_range_ptr(port, static_cast<jack_latency_callback_mode_t>(mode), @@ -1817,7 +1864,7 @@ bool jackbridge_recompute_total_latencies(jack_client_t* client) #elif defined(JACKBRIDGE_DIRECT) return (jack_recompute_total_latencies(client) == 0); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().recompute_total_latencies_ptr != nullptr) return (getBridgeInstance().recompute_total_latencies_ptr(client) == 0); #endif @@ -1832,7 +1879,7 @@ const char** jackbridge_get_ports(jack_client_t* client, const char* port_name_p #elif defined(JACKBRIDGE_DIRECT) return jack_get_ports(client, port_name_pattern, type_name_pattern, flags); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().get_ports_ptr != nullptr) return getBridgeInstance().get_ports_ptr(client, port_name_pattern, type_name_pattern, static_cast<ulong>(flags)); @@ -1846,7 +1893,7 @@ jack_port_t* jackbridge_port_by_name(jack_client_t* client, const char* port_nam #elif defined(JACKBRIDGE_DIRECT) return jack_port_by_name(client, port_name); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_by_name_ptr != nullptr) return getBridgeInstance().port_by_name_ptr(client, port_name); #endif @@ -1859,7 +1906,7 @@ jack_port_t* jackbridge_port_by_id(jack_client_t* client, jack_port_id_t port_id #elif defined(JACKBRIDGE_DIRECT) return jack_port_by_id(client, port_id); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().port_by_id_ptr != nullptr) return getBridgeInstance().port_by_id_ptr(client, port_id); #endif @@ -1874,7 +1921,7 @@ void jackbridge_free(void* ptr) #elif defined(JACKBRIDGE_DIRECT) return jack_free(ptr); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().free_ptr != nullptr) return getBridgeInstance().free_ptr(ptr); @@ -1891,9 +1938,10 @@ uint32_t jackbridge_midi_get_event_count(void* port_buffer) #elif defined(JACKBRIDGE_DIRECT) return jack_midi_get_event_count(port_buffer); #else - if (! JackBridge::usingRtAudio) - if (getBridgeInstance().midi_get_event_count_ptr != nullptr) - return getBridgeInstance().midi_get_event_count_ptr(port_buffer); + if (usingNativeBridge) + return nativeBridge->getEventCount(); + if (getBridgeInstance().midi_get_event_count_ptr != nullptr) + return getBridgeInstance().midi_get_event_count_ptr(port_buffer); #endif return 0; } @@ -1904,9 +1952,10 @@ bool jackbridge_midi_event_get(jack_midi_event_t* event, void* port_buffer, uint #elif defined(JACKBRIDGE_DIRECT) return (jack_midi_event_get(event, port_buffer, event_index) == 0); #else - if (! JackBridge::usingRtAudio) - if (getBridgeInstance().midi_event_get_ptr != nullptr) - return (getBridgeInstance().midi_event_get_ptr(event, port_buffer, event_index) == 0); + if (usingNativeBridge) + return nativeBridge->getEvent(event); + if (getBridgeInstance().midi_event_get_ptr != nullptr) + return (getBridgeInstance().midi_event_get_ptr(event, port_buffer, event_index) == 0); #endif return false; } @@ -1917,9 +1966,10 @@ void jackbridge_midi_clear_buffer(void* port_buffer) #elif defined(JACKBRIDGE_DIRECT) jack_midi_clear_buffer(port_buffer); #else - if (! JackBridge::usingRtAudio) - if (getBridgeInstance().midi_clear_buffer_ptr != nullptr) - getBridgeInstance().midi_clear_buffer_ptr(port_buffer); + if (usingNativeBridge) + return nativeBridge->clearEventBuffer(); + if (getBridgeInstance().midi_clear_buffer_ptr != nullptr) + getBridgeInstance().midi_clear_buffer_ptr(port_buffer); #endif } @@ -1929,9 +1979,10 @@ bool jackbridge_midi_event_write(void* port_buffer, jack_nframes_t time, const j #elif defined(JACKBRIDGE_DIRECT) return (jack_midi_event_write(port_buffer, time, data, data_size) == 0); #else - if (! JackBridge::usingRtAudio) - if (getBridgeInstance().midi_event_write_ptr != nullptr) - return (getBridgeInstance().midi_event_write_ptr(port_buffer, time, data, data_size) == 0); + if (usingNativeBridge) + return nativeBridge->writeEvent(time, data, data_size); + if (getBridgeInstance().midi_event_write_ptr != nullptr) + return (getBridgeInstance().midi_event_write_ptr(port_buffer, time, data, data_size) == 0); #endif return false; } @@ -1942,7 +1993,7 @@ jack_midi_data_t* jackbridge_midi_event_reserve(void* port_buffer, jack_nframes_ #elif defined(JACKBRIDGE_DIRECT) return jack_midi_event_reserve(port_buffer, time, data_size); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().midi_event_reserve_ptr != nullptr) return getBridgeInstance().midi_event_reserve_ptr(port_buffer, time, data_size); #endif @@ -1957,7 +2008,7 @@ bool jackbridge_release_timebase(jack_client_t* client) #elif defined(JACKBRIDGE_DIRECT) return (jack_release_timebase(client) == 0); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().release_timebase_ptr != nullptr) return (getBridgeInstance().release_timebase_ptr(client) == 0); #endif @@ -1970,7 +2021,7 @@ bool jackbridge_set_sync_callback(jack_client_t* client, JackSyncCallback sync_c #elif defined(JACKBRIDGE_DIRECT) return (jack_set_sync_callback(client, sync_callback, arg) == 0); #else - if (! JackBridge::usingRtAudio && getBridgeInstance().set_sync_callback_ptr != nullptr) + if (usingRealJACK && getBridgeInstance().set_sync_callback_ptr != nullptr) { # ifdef __WINE__ WineBridge::getInstance().set_sync(sync_callback); @@ -1989,7 +2040,7 @@ bool jackbridge_set_sync_timeout(jack_client_t* client, jack_time_t timeout) #elif defined(JACKBRIDGE_DIRECT) return (jack_set_sync_timeout(client, timeout) == 0); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().set_sync_timeout_ptr != nullptr) return (getBridgeInstance().set_sync_timeout_ptr(client, timeout) == 0); #endif @@ -2002,7 +2053,7 @@ bool jackbridge_set_timebase_callback(jack_client_t* client, bool conditional, J #elif defined(JACKBRIDGE_DIRECT) return (jack_set_timebase_callback(client, conditional, timebase_callback, arg) == 0); #else - if (! JackBridge::usingRtAudio && getBridgeInstance().set_timebase_callback_ptr != nullptr) + if (usingRealJACK && getBridgeInstance().set_timebase_callback_ptr != nullptr) { # ifdef __WINE__ WineBridge::getInstance().set_timebase(timebase_callback); @@ -2021,7 +2072,7 @@ bool jackbridge_transport_locate(jack_client_t* client, jack_nframes_t frame) #elif defined(JACKBRIDGE_DIRECT) return (jack_transport_locate(client, frame) == 0); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().transport_locate_ptr != nullptr) return (getBridgeInstance().transport_locate_ptr(client, frame) == 0); #endif @@ -2034,7 +2085,7 @@ uint32_t jackbridge_transport_query(const jack_client_t* client, jack_position_t #elif defined(JACKBRIDGE_DIRECT) return jack_transport_query(client, pos); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().transport_query_ptr != nullptr) return getBridgeInstance().transport_query_ptr(client, pos); #endif @@ -2054,7 +2105,7 @@ jack_nframes_t jackbridge_get_current_transport_frame(const jack_client_t* clien #elif defined(JACKBRIDGE_DIRECT) return jack_get_current_transport_frame(client); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().get_current_transport_frame_ptr != nullptr) return getBridgeInstance().get_current_transport_frame_ptr(client); #endif @@ -2067,7 +2118,7 @@ bool jackbridge_transport_reposition(jack_client_t* client, const jack_position_ #elif defined(JACKBRIDGE_DIRECT) return (jack_transport_reposition(client, pos) == 0); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().transport_reposition_ptr != nullptr) return (getBridgeInstance().transport_reposition_ptr(client, pos) == 0); #endif @@ -2080,7 +2131,7 @@ void jackbridge_transport_start(jack_client_t* client) #elif defined(JACKBRIDGE_DIRECT) jack_transport_start(client); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().transport_start_ptr != nullptr) getBridgeInstance().transport_start_ptr(client); #endif @@ -2092,7 +2143,7 @@ void jackbridge_transport_stop(jack_client_t* client) #elif defined(JACKBRIDGE_DIRECT) jack_transport_stop(client); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().transport_stop_ptr != nullptr) getBridgeInstance().transport_stop_ptr(client); #endif @@ -2106,7 +2157,7 @@ bool jackbridge_set_property(jack_client_t* client, jack_uuid_t subject, const c #elif defined(JACKBRIDGE_DIRECT) return (jack_set_property(client, subject, key, value, type) == 0); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().set_property_ptr != nullptr) return (getBridgeInstance().set_property_ptr(client, subject, key, value, type) == 0); #endif @@ -2119,7 +2170,7 @@ bool jackbridge_get_property(jack_uuid_t subject, const char* key, char** value, #elif defined(JACKBRIDGE_DIRECT) return (jack_get_property(subject, key, value, type) == 0); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().get_property_ptr != nullptr) return (getBridgeInstance().get_property_ptr(subject, key, value, type) == 0); #endif @@ -2132,7 +2183,7 @@ void jackbridge_free_description(jack_description_t* desc, bool free_description #elif defined(JACKBRIDGE_DIRECT) jack_free_description(desc, free_description_itself); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().free_description_ptr != nullptr) getBridgeInstance().free_description_ptr(desc, free_description_itself); #endif @@ -2144,7 +2195,7 @@ bool jackbridge_get_properties(jack_uuid_t subject, jack_description_t* desc) #elif defined(JACKBRIDGE_DIRECT) return (jack_get_properties(subject, desc) == 0); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().get_properties_ptr != nullptr) return (getBridgeInstance().get_properties_ptr(subject, desc) == 0); #endif @@ -2157,7 +2208,7 @@ bool jackbridge_get_all_properties(jack_description_t** descs) #elif defined(JACKBRIDGE_DIRECT) return (jack_get_all_properties(descs) == 0); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().get_all_properties_ptr != nullptr) return (getBridgeInstance().get_all_properties_ptr(descs) == 0); #endif @@ -2170,7 +2221,7 @@ bool jackbridge_remove_property(jack_client_t* client, jack_uuid_t subject, cons #elif defined(JACKBRIDGE_DIRECT) return (jack_remove_property(client, subject, key) == 0); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().remove_property_ptr != nullptr) return (getBridgeInstance().remove_property_ptr(client, subject, key) == 0); #endif @@ -2183,7 +2234,7 @@ int jackbridge_remove_properties(jack_client_t* client, jack_uuid_t subject) #elif defined(JACKBRIDGE_DIRECT) return jack_remove_properties(client, subject); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().remove_properties_ptr != nullptr) return getBridgeInstance().remove_properties_ptr(client, subject); #endif @@ -2196,7 +2247,7 @@ bool jackbridge_remove_all_properties(jack_client_t* client) #elif defined(JACKBRIDGE_DIRECT) return (jack_remove_all_properties(client) == 0); #else - if (! JackBridge::usingRtAudio) + if (usingRealJACK) if (getBridgeInstance().remove_all_properties_ptr != nullptr) return (getBridgeInstance().remove_all_properties_ptr(client) == 0); #endif @@ -2209,7 +2260,7 @@ bool jackbridge_set_property_change_callback(jack_client_t* client, JackProperty #elif defined(JACKBRIDGE_DIRECT) return (jack_set_property_change_callback(client, callback, arg) == 0); #else - if (! JackBridge::usingRtAudio && getBridgeInstance().set_property_change_callback_ptr != nullptr) + if (usingRealJACK && getBridgeInstance().set_property_change_callback_ptr != nullptr) { # ifdef __WINE__ WineBridge::getInstance().set_prop_change(callback); @@ -2223,3 +2274,99 @@ bool jackbridge_set_property_change_callback(jack_client_t* client, JackProperty } // ----------------------------------------------------------------------------- + +START_NAMESPACE_DISTRHO + +bool isUsingNativeAudio() noexcept +{ +#if defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT) + return false; +#else + return usingNativeBridge; +#endif +} + +bool supportsAudioInput() +{ +#if !(defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT)) + if (usingNativeBridge) + return nativeBridge->supportsAudioInput(); +#endif + return false; +} + +bool supportsBufferSizeChanges() +{ +#if !(defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT)) + if (usingNativeBridge) + return nativeBridge->supportsBufferSizeChanges(); +#endif + return false; +} + +bool supportsMIDI() +{ +#if !(defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT)) + if (usingNativeBridge) + return nativeBridge->supportsMIDI(); +#endif + return false; +} + +bool isAudioInputEnabled() +{ +#if !(defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT)) + if (usingNativeBridge) + return nativeBridge->isAudioInputEnabled(); +#endif + return false; +} + +bool isMIDIEnabled() +{ +#if !(defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT)) + if (usingNativeBridge) + return nativeBridge->isMIDIEnabled(); +#endif + return false; +} + +uint getBufferSize() +{ +#if !(defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT)) + if (usingNativeBridge) + return nativeBridge->getBufferSize(); +#endif + return 0; +} + +bool requestAudioInput() +{ +#if !(defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT)) + if (usingNativeBridge) + return nativeBridge->requestAudioInput(); +#endif + return false; +} + +bool requestBufferSizeChange(const uint newBufferSize) +{ +#if !(defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT)) + if (usingNativeBridge) + return nativeBridge->requestBufferSizeChange(newBufferSize); +#endif + return false; +} + +bool requestMIDI() +{ +#if !(defined(JACKBRIDGE_DUMMY) || defined(JACKBRIDGE_DIRECT)) + if (usingNativeBridge) + return nativeBridge->requestMIDI(); +#endif + return false; +} + +END_NAMESPACE_DISTRHO + +// ----------------------------------------------------------------------------- diff --git a/distrho/src/jackbridge/JackBridge.hpp b/distrho/src/jackbridge/JackBridge.hpp @@ -300,8 +300,8 @@ JACKBRIDGE_API const char* jackbridge_get_version_string(); JACKBRIDGE_API jack_client_t* jackbridge_client_open(const char* client_name, uint32_t options, jack_status_t* status); JACKBRIDGE_API bool jackbridge_client_close(jack_client_t* client); -JACKBRIDGE_API int jackbridge_client_name_size(); -JACKBRIDGE_API char* jackbridge_get_client_name(jack_client_t* client); +JACKBRIDGE_API int jackbridge_client_name_size(); +JACKBRIDGE_API const char* jackbridge_get_client_name(jack_client_t* client); JACKBRIDGE_API char* jackbridge_client_get_uuid(jack_client_t* client); JACKBRIDGE_API char* jackbridge_get_uuid_for_client_name(jack_client_t* client, const char* name); @@ -408,19 +408,4 @@ JACKBRIDGE_API int jackbridge_remove_properties(jack_client_t* client, jack_uui JACKBRIDGE_API bool jackbridge_remove_all_properties(jack_client_t* client); JACKBRIDGE_API bool jackbridge_set_property_change_callback(jack_client_t* client, JackPropertyChangeCallback callback, void* arg); -JACKBRIDGE_API bool jackbridge_sem_init(void* sem) noexcept; -JACKBRIDGE_API void jackbridge_sem_destroy(void* sem) noexcept; -JACKBRIDGE_API bool jackbridge_sem_connect(void* sem) noexcept; -JACKBRIDGE_API void jackbridge_sem_post(void* sem, bool server) noexcept; -JACKBRIDGE_API bool jackbridge_sem_timedwait(void* sem, uint msecs, bool server) noexcept; - -JACKBRIDGE_API bool jackbridge_shm_is_valid(const void* shm) noexcept; -JACKBRIDGE_API void jackbridge_shm_init(void* shm) noexcept; -JACKBRIDGE_API void jackbridge_shm_attach(void* shm, const char* name) noexcept; -JACKBRIDGE_API void jackbridge_shm_close(void* shm) noexcept; -JACKBRIDGE_API void* jackbridge_shm_map(void* shm, uint64_t size) noexcept; -JACKBRIDGE_API void jackbridge_shm_unmap(void* shm, void* ptr) noexcept; - -JACKBRIDGE_API void jackbridge_parent_deathsig(bool kill) noexcept; - #endif // JACKBRIDGE_HPP_INCLUDED diff --git a/distrho/src/jackbridge/Makefile b/distrho/src/jackbridge/Makefile @@ -1,257 +0,0 @@ -#!/usr/bin/make -f -# Makefile for jackbridge # -# ----------------------- # -# Created by falkTX -# - -CWD=.. -MODULENAME=jackbridge -include ../modules/Makefile.mk - -# --------------------------------------------------------------------------------------------------------------------- - -BUILD_CXX_FLAGS += $(JACKBRIDGE_FLAGS) -LINK_FLAGS += $(JACKBRIDGE_LIBS) - -WINE_32BIT_FLAGS = $(32BIT_FLAGS) -fpermissive -WINE_64BIT_FLAGS = $(64BIT_FLAGS) -fpermissive -WINE_LINK_FLAGS = $(LINK_FLAGS) $(LIBDL_LIBS) -lpthread -lstdc++ - -ifeq ($(JACKBRIDGE_DIRECT),true) -BUILD_CXX_FLAGS += $(JACK_FLAGS) -DJACKBRIDGE_DIRECT -LINK_FLAGS += $(JACK_LIBS) -endif - -ifneq ($(MACOS),true) -WINE_32BIT_FLAGS += -I/usr/include/wine/wine/windows -WINE_32BIT_FLAGS += -I/usr/include/wine-development/windows -WINE_32BIT_FLAGS += -I/opt/wine-devel/include/wine/windows -WINE_32BIT_FLAGS += -L/usr/lib32/wine -WINE_32BIT_FLAGS += -L/usr/lib/wine -WINE_32BIT_FLAGS += -L/usr/lib/i386-linux-gnu/wine -WINE_32BIT_FLAGS += -L/usr/lib/i386-linux-gnu/wine-development -WINE_32BIT_FLAGS += -L/opt/wine-stable/lib -WINE_32BIT_FLAGS += -L/opt/wine-stable/lib/wine -WINE_32BIT_FLAGS += -L/opt/wine-staging/lib -WINE_32BIT_FLAGS += -L/opt/wine-staging/lib/wine - -WINE_64BIT_FLAGS += -I/usr/include/wine/wine/windows -WINE_64BIT_FLAGS += -I/usr/include/wine-development/windows -WINE_64BIT_FLAGS += -I/opt/wine-devel/include/wine/windows -WINE_64BIT_FLAGS += -L/usr/lib64/wine -WINE_64BIT_FLAGS += -L/usr/lib/x86_64-linux-gnu/wine -WINE_64BIT_FLAGS += -L/usr/lib/x86_64-linux-gnu/wine-development -WINE_64BIT_FLAGS += -L/opt/wine-stable/lib64 -WINE_64BIT_FLAGS += -L/opt/wine-stable/lib64/wine -WINE_64BIT_FLAGS += -L/opt/wine-staging/lib64 -WINE_64BIT_FLAGS += -L/opt/wine-staging/lib64/wine - -WINE_LINK_FLAGS += -lrt -endif - -# --------------------------------------------------------------------------------------------------------------------- - -OBJS = $(OBJDIR)/JackBridge1.cpp.o $(OBJDIR)/JackBridge2.cpp.o -OBJS_arm32 = $(OBJDIR)/JackBridge1.cpp.arm32.o $(OBJDIR)/JackBridge2.cpp.arm32.o -OBJS_posix32 = $(OBJDIR)/JackBridge1.cpp.posix32.o $(OBJDIR)/JackBridge2.cpp.posix32.o -OBJS_posix64 = $(OBJDIR)/JackBridge1.cpp.posix64.o $(OBJDIR)/JackBridge2.cpp.posix64.o -OBJS_win32 = $(OBJDIR)/JackBridge1.cpp.win32.o $(OBJDIR)/JackBridge2.cpp.win32.o -OBJS_win64 = $(OBJDIR)/JackBridge1.cpp.win64.o $(OBJDIR)/JackBridge2.cpp.win64.o -OBJS_wine32 = $(OBJDIR)/JackBridge1.cpp.wine32.o $(OBJDIR)/JackBridge2.cpp.wine32.o $(OBJDIR)/JackBridge3.cpp.wine32.o -OBJS_wine64 = $(OBJDIR)/JackBridge1.cpp.wine64.o $(OBJDIR)/JackBridge2.cpp.wine64.o $(OBJDIR)/JackBridge3.cpp.wine64.o - -OBJS_posix32e = $(OBJDIR)/JackBridgeExport.cpp.posix32e.o -OBJS_posix64e = $(OBJDIR)/JackBridgeExport.cpp.posix64e.o -OBJS_win64e = $(OBJDIR)/JackBridgeExport.cpp.win64e.o -OBJS_win32e = $(OBJDIR)/JackBridgeExport.cpp.win32e.o - -# --------------------------------------------------------------------------------------------------------------------- - -all: $(MODULEDIR)/$(MODULENAME).a - -ifeq ($(WIN32),true) -posix32: -posix64: -posix32e: -posix64e: -win32: $(MODULEDIR)/$(MODULENAME).win32.a -win64: $(MODULEDIR)/$(MODULENAME).win64.a -win32e: $(MODULEDIR)/$(MODULENAME).win32e.a -win64e: $(MODULEDIR)/$(MODULENAME).win64e.a -wine32: -wine64: -else -arm32: $(MODULEDIR)/$(MODULENAME).arm32.a -posix32: $(MODULEDIR)/$(MODULENAME).posix32.a -posix64: $(MODULEDIR)/$(MODULENAME).posix64.a -posix32e: $(MODULEDIR)/$(MODULENAME).posix32e.a -posix64e: $(MODULEDIR)/$(MODULENAME).posix64e.a -win32: -win64: -win32e: -win64e: -wine32: $(MODULEDIR)/$(MODULENAME)-wine32.dll$(LIB_EXT) -wine64: $(MODULEDIR)/$(MODULENAME)-wine64.dll$(LIB_EXT) -endif - -# --------------------------------------------------------------------------------------------------------------------- - -clean: - rm -f $(OBJDIR)/*.o $(MODULEDIR)/$(MODULENAME)*.* - -debug: - $(MAKE) DEBUG=true - -# --------------------------------------------------------------------------------------------------------------------- - -$(MODULEDIR)/$(MODULENAME).a: $(OBJS) - -@mkdir -p $(MODULEDIR) - @echo "Creating $(MODULENAME).a" - @rm -f $@ - @$(AR) crs $@ $^ - -$(MODULEDIR)/$(MODULENAME).arm32.a: $(OBJS_arm32) - -@mkdir -p $(MODULEDIR) - @echo "Creating $(MODULENAME).arm32.a" - @rm -f $@ - @$(AR) crs $@ $^ - -$(MODULEDIR)/$(MODULENAME).posix32.a: $(OBJS_posix32) - -@mkdir -p $(MODULEDIR) - @echo "Creating $(MODULENAME).posix32.a" - @rm -f $@ - @$(AR) crs $@ $^ - -$(MODULEDIR)/$(MODULENAME).posix64.a: $(OBJS_posix64) - -@mkdir -p $(MODULEDIR) - @echo "Creating $(MODULENAME).posix64.a" - @rm -f $@ - @$(AR) crs $@ $^ - -$(MODULEDIR)/$(MODULENAME).win32.a: $(OBJS_win32) - -@mkdir -p $(MODULEDIR) - @echo "Creating $(MODULENAME).win32.a" - @rm -f $@ - @$(AR) crs $@ $^ - -$(MODULEDIR)/$(MODULENAME).win64.a: $(OBJS_win64) - -@mkdir -p $(MODULEDIR) - @echo "Creating $(MODULENAME).win64.a" - @rm -f $@ - @$(AR) crs $@ $^ - -# --------------------------------------------------------------------------------------------------------------------- - -$(MODULEDIR)/$(MODULENAME).posix32e.a: $(OBJS_posix32e) - -@mkdir -p $(MODULEDIR) - @echo "Creating $(MODULENAME).posix32e.a" - @rm -f $@ - @$(AR) crs $@ $^ - -$(MODULEDIR)/$(MODULENAME).posix64e.a: $(OBJS_posix64e) - -@mkdir -p $(MODULEDIR) - @echo "Creating $(MODULENAME).posix64e.a" - @rm -f $@ - @$(AR) crs $@ $^ - -$(MODULEDIR)/$(MODULENAME).win32e.a: $(OBJS_win32e) - -@mkdir -p $(MODULEDIR) - @echo "Creating $(MODULENAME).win32e.a" - @rm -f $@ - @$(AR) crs $@ $^ - -$(MODULEDIR)/$(MODULENAME).win64e.a: $(OBJS_win64e) - -@mkdir -p $(MODULEDIR) - @echo "Creating $(MODULENAME).win64e.a" - @rm -f $@ - @$(AR) crs $@ $^ - -# --------------------------------------------------------------------------------------------------------------------- - -$(MODULEDIR)/$(MODULENAME)-wine32.dll$(LIB_EXT): $(OBJS_wine32) JackBridgeExport.def - -@mkdir -p $(MODULEDIR) - @echo "Linking $(MODULENAME)-wine32.dll$(LIB_EXT)" - @$(WINECC) $^ $(WINE_32BIT_FLAGS) $(WINE_LINK_FLAGS) $(SHARED) -o $@ - -$(MODULEDIR)/$(MODULENAME)-wine64.dll$(LIB_EXT): $(OBJS_wine64) JackBridgeExport.def - -@mkdir -p $(MODULEDIR) - @echo "Linking $(MODULENAME)-wine64.dll$(LIB_EXT)" - @$(WINECC) $^ $(WINE_64BIT_FLAGS) $(WINE_LINK_FLAGS) $(SHARED) -o $@ - -# --------------------------------------------------------------------------------------------------------------------- - -$(OBJDIR)/JackBridge1.cpp.o: JackBridge1.cpp - -@mkdir -p $(OBJDIR) - @echo "Compiling JackBridge1.cpp" - @$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ - -$(OBJDIR)/JackBridge2.cpp.o: JackBridge2.cpp - -@mkdir -p $(OBJDIR) - @echo "Compiling JackBridge2.cpp" - @$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ - -# --------------------------------------------------------------------------------------------------------------------- - -$(OBJDIR)/JackBridgeExport.cpp.%32e.o: JackBridgeExport.cpp - -@mkdir -p $(OBJDIR) - @echo "Compiling $<" - @$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -fpermissive -c -o $@ - -$(OBJDIR)/JackBridgeExport.cpp.%64e.o: JackBridgeExport.cpp - -@mkdir -p $(OBJDIR) - @echo "Compiling $<" - @$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -fpermissive -c -o $@ - -# --------------------------------------------------------------------------------------------------------------------- - -$(OBJDIR)/%.cpp.arm32.o: %.cpp - -@mkdir -p $(OBJDIR) - @echo "Compiling $< (arm32)" - @$(CXX) $< $(BUILD_CXX_FLAGS) $(ARM32_FLAGS) -c -o $@ - -$(OBJDIR)/%.cpp.posix32.o: %.cpp - -@mkdir -p $(OBJDIR) - @echo "Compiling $< (posix32)" - @$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -c -o $@ - -$(OBJDIR)/%.cpp.posix64.o: %.cpp - -@mkdir -p $(OBJDIR) - @echo "Compiling $< (posix64)" - @$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -c -o $@ - -$(OBJDIR)/%.cpp.win32.o: %.cpp - -@mkdir -p $(OBJDIR) - @echo "Compiling $< (win32)" - @$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -c -o $@ - -$(OBJDIR)/%.cpp.win64.o: %.cpp - -@mkdir -p $(OBJDIR) - @echo "Compiling $< (win64)" - @$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -c -o $@ - -$(OBJDIR)/%.cpp.wine32.o: %.cpp - -@mkdir -p $(OBJDIR) - @echo "Compiling $< (wine32)" - @$(WINECC) $< $(BUILD_CXX_FLAGS) $(WINE_32BIT_FLAGS) -c -o $@ - -$(OBJDIR)/%.cpp.wine64.o: %.cpp - -@mkdir -p $(OBJDIR) - @echo "Compiling $< (wine64)" - @$(WINECC) $< $(BUILD_CXX_FLAGS) $(WINE_64BIT_FLAGS) -c -o $@ - -# --------------------------------------------------------------------------------------------------------------------- - --include $(OBJS:%.o=%.d) --include $(OBJS_arm32:%.o=%.d) --include $(OBJS_posix32:%.o=%.d) --include $(OBJS_posix32e:%.o=%.d) --include $(OBJS_posix64:%.o=%.d) --include $(OBJS_posix64e:%.o=%.d) --include $(OBJS_win32:%.o=%.d) --include $(OBJS_win32e:%.o=%.d) --include $(OBJS_win64:%.o=%.d) --include $(OBJS_win64e:%.o=%.d) --include $(OBJS_wine32:%.o=%.d) --include $(OBJS_wine64:%.o=%.d) - -# --------------------------------------------------------------------------------------------------------------------- diff --git a/distrho/src/jackbridge/NativeBridge.hpp b/distrho/src/jackbridge/NativeBridge.hpp @@ -0,0 +1,300 @@ +/* + * Native Bridge for DPF + * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef NATIVE_BRIDGE_HPP_INCLUDED +#define NATIVE_BRIDGE_HPP_INCLUDED + +#include "JackBridge.hpp" + +#include "../../extra/RingBuffer.hpp" + +using DISTRHO_NAMESPACE::HeapRingBuffer; + +struct NativeBridge { + // Current status information + uint bufferSize; + uint sampleRate; + + // Port caching information + uint numAudioIns; + uint numAudioOuts; + uint numMidiIns; + uint numMidiOuts; + + // JACK callbacks + JackProcessCallback jackProcessCallback = nullptr; + JackBufferSizeCallback bufferSizeCallback = nullptr; + void* jackProcessArg = nullptr; + void* jackBufferSizeArg = nullptr; + + // Runtime buffers + enum PortMask { + kPortMaskAudio = 0x1000, + kPortMaskMIDI = 0x2000, + kPortMaskInput = 0x4000, + kPortMaskOutput = 0x8000, + kPortMaskInputMIDI = kPortMaskInput|kPortMaskMIDI, + kPortMaskOutputMIDI = kPortMaskOutput|kPortMaskMIDI, + }; +#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + float* audioBuffers[DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS]; + float* audioBufferStorage; +#endif +#if DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + bool midiAvailable; +#endif +#if DISTRHO_PLUGIN_WANT_MIDI_INPUT + static constexpr const uint32_t kMaxMIDIInputMessageSize = 3; + uint8_t midiDataStorage[kMaxMIDIInputMessageSize]; + HeapRingBuffer midiInBufferCurrent; + HeapRingBuffer midiInBufferPending; +#endif +#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + HeapRingBuffer midiOutBuffer; +#endif + + NativeBridge() + : bufferSize(0), + sampleRate(0), + numAudioIns(0), + numAudioOuts(0), + numMidiIns(0), + numMidiOuts(0), + jackProcessCallback(nullptr), + bufferSizeCallback(nullptr), + jackProcessArg(nullptr), + jackBufferSizeArg(nullptr) + #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + , audioBuffers() + , audioBufferStorage(nullptr) + #endif + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + , midiAvailable(false) + #endif + { + #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + std::memset(audioBuffers, 0, sizeof(audioBuffers)); + #endif + } + + virtual ~NativeBridge() {} + virtual bool open(const char* const clientName) = 0; + virtual bool close() = 0; + virtual bool activate() = 0; + virtual bool deactivate() = 0; + + virtual bool supportsAudioInput() const + { + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + return true; + #else + return false; + #endif + } + + virtual bool isAudioInputEnabled() const + { + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + return true; + #else + return false; + #endif + } + + virtual bool supportsBufferSizeChanges() const { return false; } + virtual bool isMIDIEnabled() const { return false; } + virtual bool requestAudioInput() { return false; } + virtual bool requestBufferSizeChange(uint32_t) { return false; } + virtual bool requestMIDI() { return false; } + + uint32_t getBufferSize() const noexcept + { + return bufferSize; + } + + bool supportsMIDI() const noexcept + { + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + return midiAvailable; + #else + return false; + #endif + } + + uint32_t getEventCount() + { + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (midiAvailable) + { + // NOTE: this function is only called once per run + midiInBufferCurrent.copyFromAndClearOther(midiInBufferPending); + return midiInBufferCurrent.getReadableDataSize() / (kMaxMIDIInputMessageSize + 1u); + } + #endif + + return 0; + } + + bool getEvent(jack_midi_event_t* const event) + { + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + // NOTE: this function is called for all events in index succession + if (midiAvailable && midiInBufferCurrent.getReadableDataSize() >= (kMaxMIDIInputMessageSize + 1u)) + { + event->time = 0; // TODO + event->size = midiInBufferCurrent.readByte(); + event->buffer = midiDataStorage; + return midiInBufferCurrent.readCustomData(midiDataStorage, kMaxMIDIInputMessageSize); + } + #endif + return false; + // maybe unused + (void)event; + } + + void clearEventBuffer() + { + #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + if (midiAvailable) + midiOutBuffer.clearData(); + #endif + } + + bool writeEvent(const jack_nframes_t time, const jack_midi_data_t* const data, const uint32_t size) + { + if (size > 3) + return false; + + #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + if (midiAvailable) + { + if (midiOutBuffer.writeByte(size) && midiOutBuffer.writeCustomData(data, size)) + { + bool fail = false; + // align + switch (size) + { + case 1: fail |= !midiOutBuffer.writeByte(0); + // fall-through + case 2: fail |= !midiOutBuffer.writeByte(0); + } + fail |= !midiOutBuffer.writeUInt(time); + midiOutBuffer.commitWrite(); + return !fail; + } + midiOutBuffer.commitWrite(); + } + #endif + + return false; + // maybe unused + (void)data; + (void)time; + } + + void allocBuffers(const bool audio, const bool midi) + { + DISTRHO_SAFE_ASSERT_RETURN(bufferSize != 0,); + + if (audio) + { + #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + audioBufferStorage = new float[bufferSize*(DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS)]; + + for (uint i=0; i<DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) + audioBuffers[i] = audioBufferStorage + (bufferSize * i); + #endif + + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + std::memset(audioBufferStorage, 0, sizeof(float)*bufferSize*DISTRHO_PLUGIN_NUM_INPUTS); + #endif + } + + if (midi) + { + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + midiInBufferCurrent.createBuffer(kMaxMIDIInputMessageSize * 512); + midiInBufferPending.createBuffer(kMaxMIDIInputMessageSize * 512); + #endif + #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + midiOutBuffer.createBuffer(2048); + #endif + } + } + + void freeBuffers() + { + #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + delete[] audioBufferStorage; + audioBufferStorage = nullptr; + #endif + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + midiInBufferCurrent.deleteBuffer(); + midiInBufferPending.deleteBuffer(); + #endif + #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + midiOutBuffer.deleteBuffer(); + #endif + } + + jack_port_t* registerPort(const char* const type, const ulong flags) + { + bool isAudio, isInput; + + /**/ if (std::strcmp(type, JACK_DEFAULT_AUDIO_TYPE) == 0) + isAudio = true; + else if (std::strcmp(type, JACK_DEFAULT_MIDI_TYPE) == 0) + isAudio = false; + else + return nullptr; + + /**/ if (flags & JackPortIsInput) + isInput = true; + else if (flags & JackPortIsOutput) + isInput = false; + else + return nullptr; + + const uintptr_t ret = (isAudio ? kPortMaskAudio : kPortMaskMIDI) + | (isInput ? kPortMaskInput : kPortMaskOutput); + + return (jack_port_t*)(ret + (isAudio ? (isInput ? numAudioIns++ : numAudioOuts++) + : (isInput ? numMidiIns++ : numMidiOuts++))); + } + + void* getPortBuffer(jack_port_t* const port) + { + const uintptr_t portMask = (uintptr_t)port; + DISTRHO_SAFE_ASSERT_RETURN(portMask != 0x0, nullptr); + + #if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + if (portMask & kPortMaskAudio) + return audioBuffers[(portMask & kPortMaskInput ? 0 : DISTRHO_PLUGIN_NUM_INPUTS) + (portMask & 0x0fff)]; + #endif + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if ((portMask & kPortMaskInputMIDI) == kPortMaskInputMIDI) + return (void*)0x1; + #endif + #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + if ((portMask & kPortMaskOutputMIDI) == kPortMaskOutputMIDI) + return (void*)0x2; + #endif + + return nullptr; + } +}; + +#endif // NATIVE_BRIDGE_HPP_INCLUDED diff --git a/distrho/src/jackbridge/RtAudioBridge.hpp b/distrho/src/jackbridge/RtAudioBridge.hpp @@ -1,6 +1,6 @@ /* - * RtAudioBridge for DPF - * Copyright (C) 2021 Filipe Coelho <falktx@falktx.com> + * RtAudio Bridge for DPF + * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -14,111 +14,79 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifndef RTAUDIOBRIDGE_HPP_INCLUDED -#define RTAUDIOBRIDGE_HPP_INCLUDED +#ifndef RTAUDIO_BRIDGE_HPP_INCLUDED +#define RTAUDIO_BRIDGE_HPP_INCLUDED -#include "JackBridge.hpp" +#include "NativeBridge.hpp" + +#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS == 0 +# error RtAudio without audio does not make sense +#endif #if defined(DISTRHO_OS_MAC) # define __MACOSX_CORE__ # define RTAUDIO_API_TYPE MACOSX_CORE +# define RTMIDI_API_TYPE MACOSX_CORE #elif defined(DISTRHO_OS_WINDOWS) && !defined(_MSC_VER) -# define __WINDOWS_WASAPI__ -# define RTAUDIO_API_TYPE WINDOWS_WASAPI -#elif defined(HAVE_PULSEAUDIO) -# define __LINUX_PULSE__ -# define RTAUDIO_API_TYPE LINUX_PULSE -#elif defined(HAVE_ALSA) -# define __LINUX_ALSA__ -# define RTAUDIO_API_TYPE LINUX_ALSA +# define __WINDOWS_DS__ +# define RTAUDIO_API_TYPE WINDOWS_DS +# define RTMIDI_API_TYPE WINDOWS_MM +#else +# if defined(HAVE_PULSEAUDIO) +# define __LINUX_PULSE__ +# define RTAUDIO_API_TYPE LINUX_PULSE +# elif defined(HAVE_ALSA) +# define __LINUX_ALSA__ + # define RTAUDIO_API_TYPE LINUX_ALSA +# endif +# ifdef HAVE_ALSA +# define RTMIDI_API_TYPE LINUX_ALSA +# endif #endif #ifdef RTAUDIO_API_TYPE -# define Point CorePoint /* fix conflict between DGL and macOS Point name */ # include "rtaudio/RtAudio.h" -# undef Point -# include "../../extra/RingBuffer.hpp" +# include "rtmidi/RtMidi.h" # include "../../extra/ScopedPointer.hpp" +# include "../../extra/String.hpp" -using DISTRHO_NAMESPACE::HeapRingBuffer; using DISTRHO_NAMESPACE::ScopedPointer; +using DISTRHO_NAMESPACE::String; -struct RtAudioBridge { +struct RtAudioBridge : NativeBridge { // pointer to RtAudio instance ScopedPointer<RtAudio> handle; - - // RtAudio information - uint bufferSize = 0; - uint sampleRate = 0; - - // Port caching information - uint numAudioIns = 0; - uint numAudioOuts = 0; - uint numMidiIns = 0; - uint numMidiOuts = 0; - - // JACK callbacks - JackProcessCallback jackProcessCallback = nullptr; - void* jackProcessArg = nullptr; - - // Runtime buffers - enum PortMask { - kPortMaskAudio = 0x1000, - kPortMaskMIDI = 0x2000, - kPortMaskInput = 0x4000, - kPortMaskOutput = 0x8000, - kPortMaskInputMIDI = kPortMaskInput|kPortMaskMIDI, - kPortMaskOutputMIDI = kPortMaskOutput|kPortMaskMIDI, - }; -#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 - float* audioBuffers[DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS]; -#endif -#if DISTRHO_PLUGIN_WANT_MIDI_INPUT - HeapRingBuffer midiInBuffer; -#endif -#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT - HeapRingBuffer midiOutBuffer; -#endif - - bool open() + bool captureEnabled = false; + #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_INPUT + std::vector<RtMidiIn> midiIns; + #endif + #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + std::vector<RtMidiOut> midiOuts; + #endif + + // caching + String name; + uint nextBufferSize = 512; + + RtAudioBridge() { - ScopedPointer<RtAudio> rtAudio; - - try { - rtAudio = new RtAudio(RtAudio::RTAUDIO_API_TYPE); - } DISTRHO_SAFE_EXCEPTION_RETURN("new RtAudio()", false); - - uint rtAudioBufferFrames = 512; - - RtAudio::StreamParameters inParams; - inParams.deviceId = rtAudio->getDefaultInputDevice(); - inParams.nChannels = DISTRHO_PLUGIN_NUM_INPUTS; - - RtAudio::StreamParameters outParams; - outParams.deviceId = rtAudio->getDefaultOutputDevice(); - outParams.nChannels = DISTRHO_PLUGIN_NUM_OUTPUTS; - - RtAudio::StreamOptions opts; - opts.flags = RTAUDIO_NONINTERLEAVED | RTAUDIO_MINIMIZE_LATENCY | RTAUDIO_ALSA_USE_DEFAULT; - - try { - rtAudio->openStream(&outParams, &inParams, RTAUDIO_FLOAT32, 48000, &rtAudioBufferFrames, RtAudioCallback, this, &opts, nullptr); - } DISTRHO_SAFE_EXCEPTION_RETURN("rtAudio->openStream()", false); + #if defined(RTMIDI_API_TYPE) && (DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT) + midiAvailable = true; + #endif + } - handle = rtAudio; - bufferSize = rtAudioBufferFrames; - sampleRate = handle->getStreamSampleRate(); + const char* getVersion() const noexcept + { + return RTAUDIO_VERSION; + } -#if DISTRHO_PLUGIN_WANT_MIDI_INPUT - midiInBuffer.createBuffer(128); -#endif -#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT - midiOutBuffer.createBuffer(128); -#endif - return true; + bool open(const char* const clientName) override + { + name = clientName; + return _open(false); } - bool close() + bool close() override { DISTRHO_SAFE_ASSERT_RETURN(handle != nullptr, false); @@ -129,86 +97,259 @@ struct RtAudioBridge { } DISTRHO_SAFE_EXCEPTION("handle->abortStream()"); } + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + freeBuffers(); + #endif handle = nullptr; return true; } - bool activate() + bool activate() override { DISTRHO_SAFE_ASSERT_RETURN(handle != nullptr, false); try { handle->startStream(); - } DISTRHO_SAFE_EXCEPTION("handle->startStream()"); + } DISTRHO_SAFE_EXCEPTION_RETURN("handle->startStream()", false); return true; } - bool deactivate() + bool deactivate() override { DISTRHO_SAFE_ASSERT_RETURN(handle != nullptr, false); try { handle->stopStream(); - } DISTRHO_SAFE_EXCEPTION("handle->stopStream()"); + } DISTRHO_SAFE_EXCEPTION_RETURN("handle->stopStream()", false); return true; } - jack_port_t* registerPort(const char* const type, const ulong flags) + bool isAudioInputEnabled() const override { - bool isAudio, isInput; + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + return captureEnabled; + #else + return false; + #endif + } - if (std::strcmp(type, JACK_DEFAULT_AUDIO_TYPE) == 0) - isAudio = true; - else if (std::strcmp(type, JACK_DEFAULT_MIDI_TYPE) == 0) - isAudio = false; - else - return nullptr; + bool requestAudioInput() override + { + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + // stop audio first + deactivate(); + close(); + + // try to open with capture enabled + const bool ok = _open(true); - if (flags & JackPortIsInput) - isInput = true; - else if (flags & JackPortIsOutput) - isInput = false; + if (ok) + captureEnabled = true; else - return nullptr; + _open(false); - const uintptr_t ret = (isAudio ? kPortMaskAudio : kPortMaskMIDI) - | (isInput ? kPortMaskInput : kPortMaskOutput); + activate(); + return ok; + #else + return false; + #endif + } - return (jack_port_t*)(ret + (isAudio ? (isInput ? numAudioIns++ : numAudioOuts++) - : (isInput ? numMidiIns++ : numMidiOuts++))); + bool isMIDIEnabled() const override + { + d_stdout("%s %d", __PRETTY_FUNCTION__, __LINE__); + #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (!midiIns.empty()) + return true; + #endif + #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + if (!midiOuts.empty()) + return true; + #endif + return false; } - void* getPortBuffer(jack_port_t* const port) + bool requestMIDI() override { - const uintptr_t portMask = (uintptr_t)port; - DISTRHO_SAFE_ASSERT_RETURN(portMask != 0x0, nullptr); + d_stdout("%s %d", __PRETTY_FUNCTION__, __LINE__); + // clear ports in use first + #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (!midiIns.empty()) + { + try { + midiIns.clear(); + } catch (const RtMidiError& err) { + d_safe_exception(err.getMessage().c_str(), __FILE__, __LINE__); + return false; + } DISTRHO_SAFE_EXCEPTION_RETURN("midiIns.clear()", false); + } + #endif + #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + if (!midiOuts.size()) + { + try { + midiOuts.clear(); + } catch (const RtMidiError& err) { + d_safe_exception(err.getMessage().c_str(), __FILE__, __LINE__); + return false; + } DISTRHO_SAFE_EXCEPTION_RETURN("midiOuts.clear()", false); + } + #endif -#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 - if (portMask & kPortMaskAudio) - return audioBuffers[(portMask & kPortMaskInput ? 0 : DISTRHO_PLUGIN_NUM_INPUTS) + (portMask & 0x0fff)]; -#endif -#if DISTRHO_PLUGIN_WANT_MIDI_INPUT - if ((portMask & kPortMaskInputMIDI) == kPortMaskInputMIDI) - return &midiInBuffer; -#endif -#if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT - if ((portMask & kPortMaskOutputMIDI) == kPortMaskOutputMIDI) - return &midiOutBuffer; -#endif + // query port count + #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_INPUT + uint midiInCount; + try { + RtMidiIn midiIn(RtMidi::RTMIDI_API_TYPE, name.buffer()); + midiInCount = midiIn.getPortCount(); + } catch (const RtMidiError& err) { + d_safe_exception(err.getMessage().c_str(), __FILE__, __LINE__); + return false; + } DISTRHO_SAFE_EXCEPTION_RETURN("midiIn.getPortCount()", false); + #endif + #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + uint midiOutCount; + try { + RtMidiOut midiOut(RtMidi::RTMIDI_API_TYPE, name.buffer()); + midiOutCount = midiOut.getPortCount(); + } catch (const RtMidiError& err) { + d_safe_exception(err.getMessage().c_str(), __FILE__, __LINE__); + return false; + } DISTRHO_SAFE_EXCEPTION_RETURN("midiOut.getPortCount()", false); + #endif + + // open all possible ports + #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_INPUT + for (uint i=0; i<midiInCount; ++i) + { + try { + RtMidiIn midiIn(RtMidi::RTMIDI_API_TYPE, name.buffer()); + midiIn.setCallback(RtMidiCallback, this); + midiIn.openPort(i); + midiIns.push_back(std::move(midiIn)); + } catch (const RtMidiError& err) { + d_safe_exception(err.getMessage().c_str(), __FILE__, __LINE__); + } DISTRHO_SAFE_EXCEPTION("midiIn.openPort()"); + } + #endif + #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + for (uint i=0; i<midiOutCount; ++i) + { + try { + RtMidiOut midiOut(RtMidi::RTMIDI_API_TYPE, name.buffer()); + midiOut.openPort(i); + midiOuts.push_back(std::move(midiOut)); + } catch (const RtMidiError& err) { + d_safe_exception(err.getMessage().c_str(), __FILE__, __LINE__); + } DISTRHO_SAFE_EXCEPTION("midiOut.openPort()"); + } + #endif + + return true; + } - return nullptr; + /* RtAudio in macOS uses a different than usual way to handle audio block size, + * where RTAUDIO_MINIMIZE_LATENCY makes CoreAudio use very low latencies (around 15 samples). + * As such, dynamic buffer sizes are meaningless there. + */ + #ifndef DISTRHO_OS_MAC + bool supportsBufferSizeChanges() const override + { + return true; + } + + bool requestBufferSizeChange(const uint32_t newBufferSize) override + { + // stop audio first + deactivate(); + close(); + + // try to open with new buffer size + nextBufferSize = newBufferSize; + + const bool ok = _open(captureEnabled); + + if (!ok) + { + // revert to old buffer size if new one failed + nextBufferSize = bufferSize; + _open(captureEnabled); + } + + if (bufferSizeCallback != nullptr) + bufferSizeCallback(bufferSize, jackBufferSizeArg); + + activate(); + return ok; + } + #endif + + bool _open(const bool withInput) + { + ScopedPointer<RtAudio> rtAudio; + + try { + rtAudio = new RtAudio(RtAudio::RTAUDIO_API_TYPE); + } DISTRHO_SAFE_EXCEPTION_RETURN("new RtAudio()", false); + + uint rtAudioBufferFrames = nextBufferSize; + + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + RtAudio::StreamParameters inParams; + #endif + RtAudio::StreamParameters* inParamsPtr = nullptr; + + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + if (withInput) + { + inParams.deviceId = rtAudio->getDefaultInputDevice(); + inParams.nChannels = DISTRHO_PLUGIN_NUM_INPUTS; + inParamsPtr = &inParams; + } + #endif + + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + RtAudio::StreamParameters outParams; + outParams.deviceId = rtAudio->getDefaultOutputDevice(); + outParams.nChannels = DISTRHO_PLUGIN_NUM_OUTPUTS; + RtAudio::StreamParameters* const outParamsPtr = &outParams; + #else + RtAudio::StreamParameters* const outParamsPtr = nullptr; + #endif + + RtAudio::StreamOptions opts; + opts.flags = RTAUDIO_NONINTERLEAVED | RTAUDIO_MINIMIZE_LATENCY | RTAUDIO_ALSA_USE_DEFAULT; + opts.streamName = name.buffer(); + + try { + rtAudio->openStream(outParamsPtr, inParamsPtr, RTAUDIO_FLOAT32, 48000, &rtAudioBufferFrames, + RtAudioCallback, this, &opts, nullptr); + } catch (const RtAudioError& err) { + d_safe_exception(err.getMessage().c_str(), __FILE__, __LINE__); + return false; + } DISTRHO_SAFE_EXCEPTION_RETURN("rtAudio->openStream()", false); + + handle = rtAudio; + bufferSize = rtAudioBufferFrames; + sampleRate = handle->getStreamSampleRate(); + allocBuffers(!withInput, true); + return true; } static int RtAudioCallback(void* const outputBuffer, + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 void* const inputBuffer, + #else + void*, + #endif const uint numFrames, const double /* streamTime */, const RtAudioStreamStatus /* status */, void* const userData) { - RtAudioBridge* const self = (RtAudioBridge*)userData; + RtAudioBridge* const self = static_cast<RtAudioBridge*>(userData); if (self->jackProcessCallback == nullptr) { @@ -217,35 +358,44 @@ struct RtAudioBridge { return 0; } -#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 - float** const selfAudioBuffers = self->audioBuffers; - - uint i = 0; -# if DISTRHO_PLUGIN_NUM_INPUTS > 0 - if (float* const insPtr = (float*)inputBuffer) + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + if (float* const insPtr = static_cast<float*>(inputBuffer)) { - for (uint j=0; j<DISTRHO_PLUGIN_NUM_INPUTS; ++j, ++i) - selfAudioBuffers[i] = insPtr + (j * numFrames); + for (uint i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i) + self->audioBuffers[i] = insPtr + (i * numFrames); } -# endif -# if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 - if (float* const outsPtr = (float*)outputBuffer) + #endif + + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + if (float* const outsPtr = static_cast<float*>(outputBuffer)) { - for (uint j=0; j<DISTRHO_PLUGIN_NUM_OUTPUTS; ++j, ++i) - selfAudioBuffers[i] = outsPtr + (j * numFrames); + for (uint i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) + self->audioBuffers[DISTRHO_PLUGIN_NUM_INPUTS + i] = outsPtr + (i * numFrames); } -# endif -#endif + #endif self->jackProcessCallback(numFrames, self->jackProcessArg); + return 0; + } -#if DISTRHO_PLUGIN_NUM_INPUTS == 0 - // unused - (void)inputBuffer; -#endif + #if defined(RTMIDI_API_TYPE) && DISTRHO_PLUGIN_WANT_MIDI_INPUT + static void RtMidiCallback(double /*timeStamp*/, std::vector<uchar>* message, void* userData) + { + const size_t len = message->size(); + DISTRHO_SAFE_ASSERT_RETURN(len > 0 && len <= kMaxMIDIInputMessageSize,); + + RtAudioBridge* const self = static_cast<RtAudioBridge*>(userData); + + // TODO timestamp handling + self->midiInBufferPending.writeByte(static_cast<uint8_t>(len)); + self->midiInBufferPending.writeCustomData(message->data(), len); + for (uint8_t i=0; i<len; ++i) + self->midiInBufferPending.writeByte(0); + self->midiInBufferPending.commitWrite(); } + #endif }; #endif // RTAUDIO_API_TYPE -#endif // RTAUDIOBRIDGE_HPP_INCLUDED +#endif // RTAUDIO_BRIDGE_HPP_INCLUDED diff --git a/distrho/src/jackbridge/SDL2Bridge.hpp b/distrho/src/jackbridge/SDL2Bridge.hpp @@ -0,0 +1,250 @@ +/* + * SDL Bridge for DPF + * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef SDL_BRIDGE_HPP_INCLUDED +#define SDL_BRIDGE_HPP_INCLUDED + +#include "NativeBridge.hpp" + +#include <SDL.h> + +#ifndef SDL_HINT_AUDIO_DEVICE_APP_NAME +# define SDL_HINT_AUDIO_DEVICE_APP_NAME "SDL_AUDIO_DEVICE_APP_NAME" +#endif + +#ifndef SDL_HINT_AUDIO_DEVICE_STREAM_NAME +# define SDL_HINT_AUDIO_DEVICE_STREAM_NAME "SDL_AUDIO_DEVICE_STREAM_NAME" +#endif + +#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS == 0 +# error SDL without audio does not make sense +#endif + +struct SDL2Bridge : NativeBridge { + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + SDL_AudioDeviceID captureDeviceId; + #endif + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + SDL_AudioDeviceID playbackDeviceId; + #endif + + SDL2Bridge() + : NativeBridge() + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + , captureDeviceId(0) + #endif + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + , playbackDeviceId(0) + #endif + {} + + bool open(const char* const clientName) override + { + SDL_InitSubSystem(SDL_INIT_AUDIO); + + SDL_AudioSpec requested; + std::memset(&requested, 0, sizeof(requested)); + requested.format = AUDIO_F32SYS; + requested.freq = 48000; + requested.samples = 512; + requested.userdata = this; + + SDL_SetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME, clientName); + // SDL_SetHint(SDL_HINT_AUDIO_RESAMPLING_MODE, "1"); + + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + SDL_SetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME, "Capure"); + requested.channels = DISTRHO_PLUGIN_NUM_INPUTS; + requested.callback = AudioInputCallback; + + SDL_AudioSpec receivedCapture; + captureDeviceId = SDL_OpenAudioDevice(nullptr, 1, &requested, &receivedCapture, + SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_SAMPLES_CHANGE); + if (captureDeviceId == 0) + { + d_stderr2("Failed to open SDL playback device, error was: %s", SDL_GetError()); + return false; + } + + if (receivedCapture.channels != DISTRHO_PLUGIN_NUM_INPUTS) + { + SDL_CloseAudioDevice(captureDeviceId); + captureDeviceId = 0; + d_stderr2("Invalid or missing audio input channels"); + return false; + } + #endif + + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + SDL_AudioSpec receivedPlayback; + SDL_SetHint(SDL_HINT_AUDIO_DEVICE_STREAM_NAME, "Playback"); + requested.channels = DISTRHO_PLUGIN_NUM_OUTPUTS; + requested.callback = AudioOutputCallback; + + playbackDeviceId = SDL_OpenAudioDevice(nullptr, 0, &requested, &receivedPlayback, + SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_SAMPLES_CHANGE); + if (playbackDeviceId == 0) + { + d_stderr2("Failed to open SDL playback device, error was: %s", SDL_GetError()); + return false; + } + + if (receivedPlayback.channels != DISTRHO_PLUGIN_NUM_OUTPUTS) + { + SDL_CloseAudioDevice(playbackDeviceId); + playbackDeviceId = 0; + d_stderr2("Invalid or missing audio output channels"); + return false; + } + #endif + + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 && DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + // if using both input and output, make sure they match + if (receivedCapture.samples != receivedPlayback.samples) + { + SDL_CloseAudioDevice(captureDeviceId); + SDL_CloseAudioDevice(playbackDeviceId); + captureDeviceId = playbackDeviceId = 0; + d_stderr2("Mismatch buffer size %u vs %u", receivedCapture.samples, receivedCapture.samples); + return false; + } + if (receivedCapture.freq != receivedPlayback.freq) + { + SDL_CloseAudioDevice(captureDeviceId); + SDL_CloseAudioDevice(playbackDeviceId); + captureDeviceId = playbackDeviceId = 0; + d_stderr2("Mismatch sample rate %u vs %u", receivedCapture.freq, receivedCapture.freq); + return false; + } + #endif + + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + bufferSize = receivedCapture.samples; + sampleRate = receivedCapture.freq; + #else + bufferSize = receivedPlayback.samples; + sampleRate = receivedPlayback.freq; + #endif + + allocBuffers(true, false); + return true; + } + + bool close() override + { + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + DISTRHO_SAFE_ASSERT_RETURN(captureDeviceId != 0, false); + SDL_CloseAudioDevice(captureDeviceId); + captureDeviceId = 0; + #endif + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + DISTRHO_SAFE_ASSERT_RETURN(playbackDeviceId != 0, false); + SDL_CloseAudioDevice(playbackDeviceId); + playbackDeviceId = 0; + #endif + + freeBuffers(); + return true; + } + + bool activate() override + { + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + DISTRHO_SAFE_ASSERT_RETURN(captureDeviceId != 0, false); + SDL_PauseAudioDevice(captureDeviceId, 0); + #endif + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + DISTRHO_SAFE_ASSERT_RETURN(playbackDeviceId != 0, false); + SDL_PauseAudioDevice(playbackDeviceId, 0); + #endif + return true; + } + + bool deactivate() override + { + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + DISTRHO_SAFE_ASSERT_RETURN(captureDeviceId != 0, false); + SDL_PauseAudioDevice(captureDeviceId, 1); + #endif + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + DISTRHO_SAFE_ASSERT_RETURN(playbackDeviceId != 0, false); + SDL_PauseAudioDevice(playbackDeviceId, 1); + #endif + return true; + } + + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + static void AudioInputCallback(void* const userData, uchar* const stream, const int len) + { + NativeBridge* const self = static_cast<NativeBridge*>(userData); + + // safety checks + DISTRHO_SAFE_ASSERT_RETURN(stream != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(len > 0,); + + if (self->jackProcessCallback == nullptr) + return; + + const uint numFrames = static_cast<uint>(len / sizeof(float) / DISTRHO_PLUGIN_NUM_INPUTS); + DISTRHO_SAFE_ASSERT_UINT2_RETURN(numFrames == self->bufferSize, numFrames, self->bufferSize,); + + const float* const fstream = (const float*)stream; + + for (uint i=0; i<DISTRHO_PLUGIN_NUM_INPUTS; ++i) + { + for (uint j=0; j<numFrames; ++j) + self->audioBuffers[i][j] = fstream[j * DISTRHO_PLUGIN_NUM_INPUTS + i]; + } + + #if DISTRHO_PLUGIN_NUM_OUTPUTS == 0 + // if there are no outputs, run process callback now + self->jackProcessCallback(numFrames, self->jackProcessArg); + #endif + } + #endif + + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + static void AudioOutputCallback(void* const userData, uchar* const stream, const int len) + { + NativeBridge* const self = static_cast<NativeBridge*>(userData); + + // safety checks + DISTRHO_SAFE_ASSERT_RETURN(stream != nullptr,); + DISTRHO_SAFE_ASSERT_RETURN(len > 0,); + + if (self->jackProcessCallback == nullptr) + { + std::memset(stream, 0, len); + return; + } + + const uint numFrames = static_cast<uint>(len / sizeof(float) / DISTRHO_PLUGIN_NUM_OUTPUTS); + DISTRHO_SAFE_ASSERT_UINT2_RETURN(numFrames == self->bufferSize, numFrames, self->bufferSize,); + + self->jackProcessCallback(numFrames, self->jackProcessArg); + + float* const fstream = (float*)stream; + + for (uint i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) + { + for (uint j=0; j < numFrames; ++j) + fstream[j * DISTRHO_PLUGIN_NUM_OUTPUTS + i] = self->audioBuffers[DISTRHO_PLUGIN_NUM_INPUTS + i][j]; + } + } + #endif +}; + +#endif // SDL_BRIDGE_HPP_INCLUDED diff --git a/distrho/src/jackbridge/WebBridge.hpp b/distrho/src/jackbridge/WebBridge.hpp @@ -0,0 +1,475 @@ +/* + * Web Audio + MIDI Bridge for DPF + * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef WEB_BRIDGE_HPP_INCLUDED +#define WEB_BRIDGE_HPP_INCLUDED + +#include "NativeBridge.hpp" + +#include <emscripten/emscripten.h> + +struct WebBridge : NativeBridge { +#if DISTRHO_PLUGIN_NUM_INPUTS > 0 + bool captureAvailable = false; +#endif +#if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + bool playbackAvailable = false; +#endif + bool active = false; + double timestamp = 0; + + WebBridge() + { + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + captureAvailable = EM_ASM_INT({ + if (typeof(navigator.mediaDevices) !== 'undefined' && typeof(navigator.mediaDevices.getUserMedia) !== 'undefined') + return 1; + if (typeof(navigator.webkitGetUserMedia) !== 'undefined') + return 1; + return false; + }) != 0; + #endif + + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + playbackAvailable = EM_ASM_INT({ + if (typeof(AudioContext) !== 'undefined') + return 1; + if (typeof(webkitAudioContext) !== 'undefined') + return 1; + return 0; + }) != 0; + #endif + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + midiAvailable = EM_ASM_INT({ + return typeof(navigator.requestMIDIAccess) === 'function' ? 1 : 0; + }) != 0; + #endif + } + + bool open(const char*) override + { + // early bail out if required features are not supported + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + if (!captureAvailable) + { + #if DISTRHO_PLUGIN_NUM_OUTPUTS == 0 + d_stderr2("Audio capture is not supported"); + return false; + #else + if (!playbackAvailable) + { + d_stderr2("Audio capture and playback are not supported"); + return false; + } + d_stderr2("Audio capture is not supported, but can still use playback"); + #endif + } + #endif + + #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + if (!playbackAvailable) + { + d_stderr2("Audio playback is not supported"); + return false; + } + #endif + + const bool initialized = EM_ASM_INT({ + if (typeof(Module['WebAudioBridge']) === 'undefined') { + Module['WebAudioBridge'] = {}; + } + + var WAB = Module['WebAudioBridge']; + if (!WAB.audioContext) { + if (typeof(AudioContext) !== 'undefined') { + WAB.audioContext = new AudioContext(); + } else if (typeof(webkitAudioContext) !== 'undefined') { + WAB.audioContext = new webkitAudioContext(); + } + } + + return WAB.audioContext === undefined ? 0 : 1; + }) != 0; + + if (!initialized) + { + d_stderr2("Failed to initialize web audio"); + return false; + } + + bufferSize = EM_ASM_INT({ + var WAB = Module['WebAudioBridge']; + return WAB['minimizeBufferSize'] ? 256 : 2048; + }); + sampleRate = EM_ASM_INT_V({ + var WAB = Module['WebAudioBridge']; + return WAB.audioContext.sampleRate; + }); + + allocBuffers(true, true); + + EM_ASM({ + var numInputs = $0; + var numOutputs = $1; + var bufferSize = $2; + var WAB = Module['WebAudioBridge']; + + var realBufferSize = WAB['minimizeBufferSize'] ? 2048 : bufferSize; + var divider = realBufferSize / bufferSize; + + // main processor + WAB.processor = WAB.audioContext['createScriptProcessor'](realBufferSize, numInputs, numOutputs); + WAB.processor['onaudioprocess'] = function (e) { + // var timestamp = performance.now(); + for (var k = 0; k < divider; ++k) { + for (var i = 0; i < numInputs; ++i) { + var buffer = e['inputBuffer']['getChannelData'](i); + for (var j = 0; j < bufferSize; ++j) { + // setValue($3 + ((bufferSize * i) + j) * 4, buffer[j], 'float'); + HEAPF32[$3 + (((bufferSize * i) + j) << 2) >> 2] = buffer[bufferSize * k + j]; + } + } + dynCall('vi', $4, [$5]); + for (var i = 0; i < numOutputs; ++i) { + var buffer = e['outputBuffer']['getChannelData'](i); + var offset = bufferSize * (numInputs + i); + for (var j = 0; j < bufferSize; ++j) { + buffer[bufferSize * k + j] = HEAPF32[$3 + ((offset + j) << 2) >> 2]; + } + } + } + }; + + // connect to output + WAB.processor['connect'](WAB.audioContext['destination']); + + // resume/start playback on first click + document.addEventListener('click', function(e) { + var WAB = Module['WebAudioBridge']; + if (WAB.audioContext.state === 'suspended') + WAB.audioContext.resume(); + }); + }, DISTRHO_PLUGIN_NUM_INPUTS, DISTRHO_PLUGIN_NUM_OUTPUTS, bufferSize, audioBufferStorage, WebAudioCallback, this); + + return true; + } + + bool close() override + { + freeBuffers(); + return true; + } + + bool activate() override + { + active = true; + return true; + } + + bool deactivate() override + { + active = false; + return true; + } + + bool supportsAudioInput() const override + { + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + return captureAvailable; + #else + return false; + #endif + } + + bool isAudioInputEnabled() const override + { + #if DISTRHO_PLUGIN_NUM_INPUTS > 0 + return EM_ASM_INT({ return Module['WebAudioBridge'].captureStreamNode ? 1 : 0 }) != 0; + #else + return false; + #endif + } + + bool requestAudioInput() override + { + DISTRHO_SAFE_ASSERT_RETURN(DISTRHO_PLUGIN_NUM_INPUTS > 0, false); + + EM_ASM({ + var numInputs = $0; + var WAB = Module['WebAudioBridge']; + + var constraints = {}; + // we need to use this weird awkward way for objects, otherwise build fails + constraints['audio'] = true; + constraints['video'] = false; + constraints['autoGainControl'] = {}; + constraints['autoGainControl']['exact'] = false; + constraints['echoCancellation'] = {}; + constraints['echoCancellation']['exact'] = false; + constraints['noiseSuppression'] = {}; + constraints['noiseSuppression']['exact'] = false; + constraints['channelCount'] = {}; + constraints['channelCount']['min'] = 0; + constraints['channelCount']['ideal'] = numInputs; + constraints['latency'] = {}; + constraints['latency']['min'] = 0; + constraints['latency']['ideal'] = 0; + constraints['sampleSize'] = {}; + constraints['sampleSize']['min'] = 8; + constraints['sampleSize']['max'] = 32; + constraints['sampleSize']['ideal'] = 16; + // old property for chrome + constraints['googAutoGainControl'] = false; + + var success = function(stream) { + WAB.captureStreamNode = WAB.audioContext['createMediaStreamSource'](stream); + WAB.captureStreamNode.connect(WAB.processor); + }; + var fail = function() { + }; + + if (navigator.mediaDevices !== undefined && navigator.mediaDevices.getUserMedia !== undefined) { + navigator.mediaDevices.getUserMedia(constraints).then(success).catch(fail); + } else if (navigator.webkitGetUserMedia !== undefined) { + navigator.webkitGetUserMedia(constraints, success, fail); + } + }, DISTRHO_PLUGIN_NUM_INPUTS); + + return true; + } + + bool supportsBufferSizeChanges() const override + { + return true; + } + + bool requestBufferSizeChange(const uint32_t newBufferSize) override + { + // try to create new processor first + bool success = EM_ASM_INT({ + var numInputs = $0; + var numOutputs = $1; + var newBufferSize = $2; + var WAB = Module['WebAudioBridge']; + + try { + WAB.newProcessor = WAB.audioContext['createScriptProcessor'](newBufferSize, numInputs, numOutputs); + } catch (e) { + return 0; + } + + // got new processor, disconnect old one + WAB.processor['disconnect'](WAB.audioContext['destination']); + + if (WAB.captureStreamNode) + WAB.captureStreamNode.disconnect(WAB.processor); + + return 1; + }, DISTRHO_PLUGIN_NUM_INPUTS, DISTRHO_PLUGIN_NUM_OUTPUTS, newBufferSize) != 0; + + if (!success) + return false; + + bufferSize = newBufferSize; + freeBuffers(); + allocBuffers(true, true); + + if (bufferSizeCallback != nullptr) + bufferSizeCallback(newBufferSize, jackBufferSizeArg); + + EM_ASM({ + var numInputs = $0; + var numOutputs = $1; + var bufferSize = $2; + var WAB = Module['WebAudioBridge']; + + // store the new processor + delete WAB.processor; + WAB.processor = WAB.newProcessor; + delete WAB.newProcessor; + + // setup new processor the same way as old one + WAB.processor['onaudioprocess'] = function (e) { + // var timestamp = performance.now(); + for (var i = 0; i < numInputs; ++i) { + var buffer = e['inputBuffer']['getChannelData'](i); + for (var j = 0; j < bufferSize; ++j) { + // setValue($3 + ((bufferSize * i) + j) * 4, buffer[j], 'float'); + HEAPF32[$3 + (((bufferSize * i) + j) << 2) >> 2] = buffer[j]; + } + } + dynCall('vi', $4, [$5]); + for (var i = 0; i < numOutputs; ++i) { + var buffer = e['outputBuffer']['getChannelData'](i); + var offset = bufferSize * (numInputs + i); + for (var j = 0; j < bufferSize; ++j) { + buffer[j] = HEAPF32[$3 + ((offset + j) << 2) >> 2]; + } + } + }; + + // connect to output + WAB.processor['connect'](WAB.audioContext['destination']); + + // and input, if available + if (WAB.captureStreamNode) + WAB.captureStreamNode.connect(WAB.processor); + + }, DISTRHO_PLUGIN_NUM_INPUTS, DISTRHO_PLUGIN_NUM_OUTPUTS, bufferSize, audioBufferStorage, WebAudioCallback, this); + + return true; + } + + bool isMIDIEnabled() const override + { + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + return EM_ASM_INT({ return Module['WebAudioBridge'].midi ? 1 : 0 }) != 0; + #else + return false; + #endif + } + + bool requestMIDI() override + { + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT || DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + if (midiAvailable) + { + EM_ASM({ + var useInput = !!$0; + var useOutput = !!$1; + var maxSize = $2; + var WAB = Module['WebAudioBridge']; + + var offset = Module._malloc(maxSize); + + var inputCallback = function(event) { + if (event.data.length > maxSize) + return; + var buffer = new Uint8Array(Module.HEAPU8.buffer, offset, maxSize); + buffer.set(event.data); + dynCall('viiif', $3, [$4, buffer.byteOffset, event.data.length, event.timeStamp]); + }; + var stateCallback = function(event) { + if (event.port.state === 'connected' && event.port.connection === 'open') { + if (useInput && event.port.type === 'input') { + if (event.port.name.indexOf('Midi Through') < 0) + event.port.onmidimessage = inputCallback; + } else if (useOutput && event.port.type === 'output') { + event.port.open(); + } + } + }; + + var success = function(midi) { + WAB.midi = midi; + midi.onstatechange = stateCallback; + if (useInput) { + midi.inputs.forEach(function(port) { + if (port.name.indexOf('Midi Through') < 0) + port.onmidimessage = inputCallback; + }); + } + if (useOutput) { + midi.outputs.forEach(function(port) { + port.open(); + }); + } + }; + var fail = function(why) { + console.log("midi access failed:", why); + }; + + navigator.requestMIDIAccess().then(success, fail); + }, DISTRHO_PLUGIN_WANT_MIDI_INPUT, DISTRHO_PLUGIN_WANT_MIDI_OUTPUT, kMaxMIDIInputMessageSize, WebMIDICallback, this); + + return true; + } + else + #endif + { + d_stderr2("MIDI is not supported"); + return false; + } + } + + static void WebAudioCallback(void* const userData /* , const double timestamp */) + { + WebBridge* const self = static_cast<WebBridge*>(userData); + // self->timestamp = timestamp; + + const uint numFrames = self->bufferSize; + + if (self->jackProcessCallback != nullptr && self->active) + { + self->jackProcessCallback(numFrames, self->jackProcessArg); + + #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT + if (self->midiAvailable && self->midiOutBuffer.isDataAvailableForReading()) + { + static_assert(kMaxMIDIInputMessageSize + 1u == 4, "change code if bumping this value"); + uint32_t offset = 0; + uint8_t bytes[4] = {}; + double timestamp = EM_ASM_DOUBLE({ return performance.now(); }); + + while (self->midiOutBuffer.isDataAvailableForReading() && + self->midiOutBuffer.readCustomData(bytes, ARRAY_SIZE(bytes))) + { + offset = self->midiOutBuffer.readUInt(); + + EM_ASM({ + var WAB = Module['WebAudioBridge']; + if (WAB.midi) { + var timestamp = $5 + $0; + var size = $1; + WAB.midi.outputs.forEach(function(port) { + if (port.state !== 'disconnected') { + port.send(size == 3 ? [ $2, $3, $4 ] : + size == 2 ? [ $2, $3 ] : + [ $2 ], timestamp); + } + }); + } + }, offset, bytes[0], bytes[1], bytes[2], bytes[3], timestamp); + } + + self->midiOutBuffer.clearData(); + } + #endif + } + else + { + for (uint i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) + std::memset(self->audioBuffers[DISTRHO_PLUGIN_NUM_INPUTS + i], 0, sizeof(float)*numFrames); + } + } + + #if DISTRHO_PLUGIN_WANT_MIDI_INPUT + static void WebMIDICallback(void* const userData, uint8_t* const data, const int len, const double timestamp) + { + DISTRHO_SAFE_ASSERT_RETURN(len > 0 && len <= (int)kMaxMIDIInputMessageSize,); + + WebBridge* const self = static_cast<WebBridge*>(userData); + + // TODO timestamp handling + self->midiInBufferPending.writeByte(static_cast<uint8_t>(len)); + self->midiInBufferPending.writeCustomData(data, kMaxMIDIInputMessageSize); + self->midiInBufferPending.commitWrite(); + } + #endif +}; + +#endif // WEB_BRIDGE_HPP_INCLUDED diff --git a/distrho/src/jackbridge/rtmidi/LICENSE b/distrho/src/jackbridge/rtmidi/LICENSE @@ -0,0 +1,27 @@ + +RtMidi: realtime MIDI i/o C++ classes +Copyright (c) 2003-2021 Gary P. Scavone + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +Any person wishing to distribute modifications to the Software is +asked to send the modifications to the original developer so that +they can be incorporated into the canonical version. This is, +however, not a binding provision of this license. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/distrho/src/jackbridge/rtmidi/RtMidi.cpp b/distrho/src/jackbridge/rtmidi/RtMidi.cpp @@ -0,0 +1,3930 @@ +/**********************************************************************/ +/*! \class RtMidi + \brief An abstract base class for realtime MIDI input/output. + + This class implements some common functionality for the realtime + MIDI input/output subclasses RtMidiIn and RtMidiOut. + + RtMidi GitHub site: https://github.com/thestk/rtmidi + RtMidi WWW site: http://www.music.mcgill.ca/~gary/rtmidi/ + + RtMidi: realtime MIDI i/o C++ classes + Copyright (c) 2003-2021 Gary P. Scavone + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + asked to send the modifications to the original developer so that + they can be incorporated into the canonical version. This is, + however, not a binding provision of this license. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +/**********************************************************************/ + +#include "RtMidi.h" +#include <sstream> +#if defined(__APPLE__) +#include <TargetConditionals.h> +#endif + +#if (TARGET_OS_IPHONE == 1) + + #define AudioGetCurrentHostTime CAHostTimeBase::GetCurrentTime + #define AudioConvertHostTimeToNanos CAHostTimeBase::ConvertToNanos + + #include <mach/mach_time.h> + class CTime2nsFactor + { + public: + CTime2nsFactor() + { + mach_timebase_info_data_t tinfo; + mach_timebase_info(&tinfo); + Factor = (double)tinfo.numer / tinfo.denom; + } + static double Factor; + }; + double CTime2nsFactor::Factor; + static CTime2nsFactor InitTime2nsFactor; + #undef AudioGetCurrentHostTime + #undef AudioConvertHostTimeToNanos + #define AudioGetCurrentHostTime (uint64_t) mach_absolute_time + #define AudioConvertHostTimeToNanos(t) t *CTime2nsFactor::Factor + #define EndianS32_BtoN(n) n + +#endif + +// Default for Windows is to add an identifier to the port names; this +// flag can be defined (e.g. in your project file) to disable this behaviour. +//#define RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES + +// **************************************************************** // +// +// MidiInApi and MidiOutApi subclass prototypes. +// +// **************************************************************** // + +#if !defined(__LINUX_ALSA__) && !defined(__UNIX_JACK__) && !defined(__MACOSX_CORE__) && !defined(__WINDOWS_MM__) && !defined(TARGET_IPHONE_OS) && !defined(__WEB_MIDI_API__) + #define __RTMIDI_DUMMY__ +#endif + +#if defined(__MACOSX_CORE__) +#include <CoreMIDI/CoreMIDI.h> + +class MidiInCore: public MidiInApi +{ + public: + MidiInCore( const std::string &clientName, unsigned int queueSizeLimit ); + ~MidiInCore( void ); + RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; }; + void openPort( unsigned int portNumber, const std::string &portName ); + void openVirtualPort( const std::string &portName ); + void closePort( void ); + void setClientName( const std::string &clientName ); + void setPortName( const std::string &portName ); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + + protected: + MIDIClientRef getCoreMidiClientSingleton(const std::string& clientName) throw(); + void initialize( const std::string& clientName ); +}; + +class MidiOutCore: public MidiOutApi +{ + public: + MidiOutCore( const std::string &clientName ); + ~MidiOutCore( void ); + RtMidi::Api getCurrentApi( void ) { return RtMidi::MACOSX_CORE; }; + void openPort( unsigned int portNumber, const std::string &portName ); + void openVirtualPort( const std::string &portName ); + void closePort( void ); + void setClientName( const std::string &clientName ); + void setPortName( const std::string &portName ); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + void sendMessage( const unsigned char *message, size_t size ); + + protected: + MIDIClientRef getCoreMidiClientSingleton(const std::string& clientName) throw(); + void initialize( const std::string& clientName ); +}; + +#endif + +#if defined(__UNIX_JACK__) + +class MidiInJack: public MidiInApi +{ + public: + MidiInJack( const std::string &clientName, unsigned int queueSizeLimit ); + ~MidiInJack( void ); + RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; }; + void openPort( unsigned int portNumber, const std::string &portName ); + void openVirtualPort( const std::string &portName ); + void closePort( void ); + void setClientName( const std::string &clientName ); + void setPortName( const std::string &portName); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + + protected: + std::string clientName; + + void connect( void ); + void initialize( const std::string& clientName ); +}; + +class MidiOutJack: public MidiOutApi +{ + public: + MidiOutJack( const std::string &clientName ); + ~MidiOutJack( void ); + RtMidi::Api getCurrentApi( void ) { return RtMidi::UNIX_JACK; }; + void openPort( unsigned int portNumber, const std::string &portName ); + void openVirtualPort( const std::string &portName ); + void closePort( void ); + void setClientName( const std::string &clientName ); + void setPortName( const std::string &portName); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + void sendMessage( const unsigned char *message, size_t size ); + + protected: + std::string clientName; + + void connect( void ); + void initialize( const std::string& clientName ); +}; + +#endif + +#if defined(__LINUX_ALSA__) + +class MidiInAlsa: public MidiInApi +{ + public: + MidiInAlsa( const std::string &clientName, unsigned int queueSizeLimit ); + ~MidiInAlsa( void ); + RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; }; + void openPort( unsigned int portNumber, const std::string &portName ); + void openVirtualPort( const std::string &portName ); + void closePort( void ); + void setClientName( const std::string &clientName ); + void setPortName( const std::string &portName); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + + protected: + void initialize( const std::string& clientName ); +}; + +class MidiOutAlsa: public MidiOutApi +{ + public: + MidiOutAlsa( const std::string &clientName ); + ~MidiOutAlsa( void ); + RtMidi::Api getCurrentApi( void ) { return RtMidi::LINUX_ALSA; }; + void openPort( unsigned int portNumber, const std::string &portName ); + void openVirtualPort( const std::string &portName ); + void closePort( void ); + void setClientName( const std::string &clientName ); + void setPortName( const std::string &portName ); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + void sendMessage( const unsigned char *message, size_t size ); + + protected: + void initialize( const std::string& clientName ); +}; + +#endif + +#if defined(__WINDOWS_MM__) + +class MidiInWinMM: public MidiInApi +{ + public: + MidiInWinMM( const std::string &clientName, unsigned int queueSizeLimit ); + ~MidiInWinMM( void ); + RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; }; + void openPort( unsigned int portNumber, const std::string &portName ); + void openVirtualPort( const std::string &portName ); + void closePort( void ); + void setClientName( const std::string &clientName ); + void setPortName( const std::string &portName ); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + + protected: + void initialize( const std::string& clientName ); +}; + +class MidiOutWinMM: public MidiOutApi +{ + public: + MidiOutWinMM( const std::string &clientName ); + ~MidiOutWinMM( void ); + RtMidi::Api getCurrentApi( void ) { return RtMidi::WINDOWS_MM; }; + void openPort( unsigned int portNumber, const std::string &portName ); + void openVirtualPort( const std::string &portName ); + void closePort( void ); + void setClientName( const std::string &clientName ); + void setPortName( const std::string &portName ); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + void sendMessage( const unsigned char *message, size_t size ); + + protected: + void initialize( const std::string& clientName ); +}; + +#endif + +#if defined(__WEB_MIDI_API__) + +class MidiInWeb : public MidiInApi +{ + std::string client_name{}; + std::string web_midi_id{}; + int open_port_number{-1}; + + public: + MidiInWeb(const std::string &/*clientName*/, unsigned int queueSizeLimit ); + ~MidiInWeb( void ); + RtMidi::Api getCurrentApi( void ) { return RtMidi::WEB_MIDI_API; }; + void openPort( unsigned int portNumber, const std::string &portName ); + void openVirtualPort( const std::string &portName ); + void closePort( void ); + void setClientName( const std::string &clientName ); + void setPortName( const std::string &portName ); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + + void onMidiMessage( uint8_t* data, double domHishResTimeStamp ); + + protected: + void initialize( const std::string& clientName ); +}; + +class MidiOutWeb: public MidiOutApi +{ + std::string client_name{}; + std::string web_midi_id{}; + int open_port_number{-1}; + + public: + MidiOutWeb( const std::string &clientName ); + ~MidiOutWeb( void ); + RtMidi::Api getCurrentApi( void ) { return RtMidi::WEB_MIDI_API; }; + void openPort( unsigned int portNumber, const std::string &portName ); + void openVirtualPort( const std::string &portName ); + void closePort( void ); + void setClientName( const std::string &clientName ); + void setPortName( const std::string &portName ); + unsigned int getPortCount( void ); + std::string getPortName( unsigned int portNumber ); + void sendMessage( const unsigned char *message, size_t size ); + + protected: + void initialize( const std::string& clientName ); +}; + +#endif + +#if defined(__RTMIDI_DUMMY__) + +class MidiInDummy: public MidiInApi +{ + public: + MidiInDummy( const std::string &/*clientName*/, unsigned int queueSizeLimit ) : MidiInApi( queueSizeLimit ) { errorString_ = "MidiInDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); } + RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; } + void openPort( unsigned int /*portNumber*/, const std::string &/*portName*/ ) {} + void openVirtualPort( const std::string &/*portName*/ ) {} + void closePort( void ) {} + void setClientName( const std::string &/*clientName*/ ) {}; + void setPortName( const std::string &/*portName*/ ) {}; + unsigned int getPortCount( void ) { return 0; } + std::string getPortName( unsigned int /*portNumber*/ ) { return ""; } + + protected: + void initialize( const std::string& /*clientName*/ ) {} +}; + +class MidiOutDummy: public MidiOutApi +{ + public: + MidiOutDummy( const std::string &/*clientName*/ ) { errorString_ = "MidiOutDummy: This class provides no functionality."; error( RtMidiError::WARNING, errorString_ ); } + RtMidi::Api getCurrentApi( void ) { return RtMidi::RTMIDI_DUMMY; } + void openPort( unsigned int /*portNumber*/, const std::string &/*portName*/ ) {} + void openVirtualPort( const std::string &/*portName*/ ) {} + void closePort( void ) {} + void setClientName( const std::string &/*clientName*/ ) {}; + void setPortName( const std::string &/*portName*/ ) {}; + unsigned int getPortCount( void ) { return 0; } + std::string getPortName( unsigned int /*portNumber*/ ) { return ""; } + void sendMessage( const unsigned char * /*message*/, size_t /*size*/ ) {} + + protected: + void initialize( const std::string& /*clientName*/ ) {} +}; + +#endif + +//*********************************************************************// +// RtMidi Definitions +//*********************************************************************// + +RtMidi :: RtMidi() + : rtapi_(0) +{ +} + +RtMidi :: ~RtMidi() +{ + delete rtapi_; + rtapi_ = 0; +} + +RtMidi::RtMidi(RtMidi&& other) noexcept { + rtapi_ = other.rtapi_; + other.rtapi_ = nullptr; +} + +std::string RtMidi :: getVersion( void ) throw() +{ + return std::string( RTMIDI_VERSION ); +} + +// Define API names and display names. +// Must be in same order as API enum. +extern "C" { +const char* rtmidi_api_names[][2] = { + { "unspecified" , "Unknown" }, + { "core" , "CoreMidi" }, + { "alsa" , "ALSA" }, + { "jack" , "Jack" }, + { "winmm" , "Windows MultiMedia" }, + { "web" , "Web MIDI API" }, + { "dummy" , "Dummy" }, +}; +const unsigned int rtmidi_num_api_names = + sizeof(rtmidi_api_names)/sizeof(rtmidi_api_names[0]); + +// The order here will control the order of RtMidi's API search in +// the constructor. +extern "C" const RtMidi::Api rtmidi_compiled_apis[] = { +#if defined(__MACOSX_CORE__) + RtMidi::MACOSX_CORE, +#endif +#if defined(__LINUX_ALSA__) + RtMidi::LINUX_ALSA, +#endif +#if defined(__UNIX_JACK__) + RtMidi::UNIX_JACK, +#endif +#if defined(__WINDOWS_MM__) + RtMidi::WINDOWS_MM, +#endif +#if defined(__WEB_MIDI_API__) + RtMidi::WEB_MIDI_API, +#endif +#if defined(__RTMIDI_DUMMY__) + RtMidi::RTMIDI_DUMMY, +#endif + RtMidi::UNSPECIFIED, +}; +extern "C" const unsigned int rtmidi_num_compiled_apis = + sizeof(rtmidi_compiled_apis)/sizeof(rtmidi_compiled_apis[0])-1; +} + +void RtMidi :: getCompiledApi( std::vector<RtMidi::Api> &apis ) throw() +{ + apis = std::vector<RtMidi::Api>(rtmidi_compiled_apis, + rtmidi_compiled_apis + rtmidi_num_compiled_apis); +} + +std::string RtMidi :: getApiName( RtMidi::Api api ) +{ + if (api < RtMidi::UNSPECIFIED || api >= RtMidi::NUM_APIS) + return ""; + return rtmidi_api_names[api][0]; +} + +std::string RtMidi :: getApiDisplayName( RtMidi::Api api ) +{ + if (api < RtMidi::UNSPECIFIED || api >= RtMidi::NUM_APIS) + return "Unknown"; + return rtmidi_api_names[api][1]; +} + +RtMidi::Api RtMidi :: getCompiledApiByName( const std::string &name ) +{ + unsigned int i=0; + for (i = 0; i < rtmidi_num_compiled_apis; ++i) + if (name == rtmidi_api_names[rtmidi_compiled_apis[i]][0]) + return rtmidi_compiled_apis[i]; + return RtMidi::UNSPECIFIED; +} + +void RtMidi :: setClientName( const std::string &clientName ) +{ + rtapi_->setClientName( clientName ); +} + +void RtMidi :: setPortName( const std::string &portName ) +{ + rtapi_->setPortName( portName ); +} + + +//*********************************************************************// +// RtMidiIn Definitions +//*********************************************************************// + +void RtMidiIn :: openMidiApi( RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit ) +{ + delete rtapi_; + rtapi_ = 0; + +#if defined(__UNIX_JACK__) + if ( api == UNIX_JACK ) + rtapi_ = new MidiInJack( clientName, queueSizeLimit ); +#endif +#if defined(__LINUX_ALSA__) + if ( api == LINUX_ALSA ) + rtapi_ = new MidiInAlsa( clientName, queueSizeLimit ); +#endif +#if defined(__WINDOWS_MM__) + if ( api == WINDOWS_MM ) + rtapi_ = new MidiInWinMM( clientName, queueSizeLimit ); +#endif +#if defined(__MACOSX_CORE__) + if ( api == MACOSX_CORE ) + rtapi_ = new MidiInCore( clientName, queueSizeLimit ); +#endif +#if defined(__WEB_MIDI_API__) + if ( api == WEB_MIDI_API ) + rtapi_ = new MidiInWeb( clientName, queueSizeLimit ); +#endif +#if defined(__RTMIDI_DUMMY__) + if ( api == RTMIDI_DUMMY ) + rtapi_ = new MidiInDummy( clientName, queueSizeLimit ); +#endif +} + +RTMIDI_DLL_PUBLIC RtMidiIn :: RtMidiIn( RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit ) + : RtMidi() +{ + if ( api != UNSPECIFIED ) { + // Attempt to open the specified API. + openMidiApi( api, clientName, queueSizeLimit ); + if ( rtapi_ ) return; + + // No compiled support for specified API value. Issue a warning + // and continue as if no API was specified. + std::cerr << "\nRtMidiIn: no compiled support for specified API argument!\n\n" << std::endl; + } + + // Iterate through the compiled APIs and return as soon as we find + // one with at least one port or we reach the end of the list. + std::vector< RtMidi::Api > apis; + getCompiledApi( apis ); + for ( unsigned int i=0; i<apis.size(); i++ ) { + openMidiApi( apis[i], clientName, queueSizeLimit ); + if ( rtapi_ && rtapi_->getPortCount() ) break; + } + + if ( rtapi_ ) return; + + // It should not be possible to get here because the preprocessor + // definition __RTMIDI_DUMMY__ is automatically defined if no + // API-specific definitions are passed to the compiler. But just in + // case something weird happens, we'll throw an error. + std::string errorText = "RtMidiIn: no compiled API support found ... critical error!!"; + throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) ); +} + +RtMidiIn :: ~RtMidiIn() throw() +{ +} + + +//*********************************************************************// +// RtMidiOut Definitions +//*********************************************************************// + +void RtMidiOut :: openMidiApi( RtMidi::Api api, const std::string &clientName ) +{ + delete rtapi_; + rtapi_ = 0; + +#if defined(__UNIX_JACK__) + if ( api == UNIX_JACK ) + rtapi_ = new MidiOutJack( clientName ); +#endif +#if defined(__LINUX_ALSA__) + if ( api == LINUX_ALSA ) + rtapi_ = new MidiOutAlsa( clientName ); +#endif +#if defined(__WINDOWS_MM__) + if ( api == WINDOWS_MM ) + rtapi_ = new MidiOutWinMM( clientName ); +#endif +#if defined(__MACOSX_CORE__) + if ( api == MACOSX_CORE ) + rtapi_ = new MidiOutCore( clientName ); +#endif +#if defined(__WEB_MIDI_API__) + if ( api == WEB_MIDI_API ) + rtapi_ = new MidiOutWeb( clientName ); +#endif +#if defined(__RTMIDI_DUMMY__) + if ( api == RTMIDI_DUMMY ) + rtapi_ = new MidiOutDummy( clientName ); +#endif +} + +RTMIDI_DLL_PUBLIC RtMidiOut :: RtMidiOut( RtMidi::Api api, const std::string &clientName) +{ + if ( api != UNSPECIFIED ) { + // Attempt to open the specified API. + openMidiApi( api, clientName ); + if ( rtapi_ ) return; + + // No compiled support for specified API value. Issue a warning + // and continue as if no API was specified. + std::cerr << "\nRtMidiOut: no compiled support for specified API argument!\n\n" << std::endl; + } + + // Iterate through the compiled APIs and return as soon as we find + // one with at least one port or we reach the end of the list. + std::vector< RtMidi::Api > apis; + getCompiledApi( apis ); + for ( unsigned int i=0; i<apis.size(); i++ ) { + openMidiApi( apis[i], clientName ); + if ( rtapi_ && rtapi_->getPortCount() ) break; + } + + if ( rtapi_ ) return; + + // It should not be possible to get here because the preprocessor + // definition __RTMIDI_DUMMY__ is automatically defined if no + // API-specific definitions are passed to the compiler. But just in + // case something weird happens, we'll thrown an error. + std::string errorText = "RtMidiOut: no compiled API support found ... critical error!!"; + throw( RtMidiError( errorText, RtMidiError::UNSPECIFIED ) ); +} + +RtMidiOut :: ~RtMidiOut() throw() +{ +} + +//*********************************************************************// +// Common MidiApi Definitions +//*********************************************************************// + +MidiApi :: MidiApi( void ) + : apiData_( 0 ), connected_( false ), errorCallback_(0), firstErrorOccurred_(false), errorCallbackUserData_(0) +{ +} + +MidiApi :: ~MidiApi( void ) +{ +} + +void MidiApi :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData = 0 ) +{ + errorCallback_ = errorCallback; + errorCallbackUserData_ = userData; +} + +void MidiApi :: error( RtMidiError::Type type, std::string errorString ) +{ + if ( errorCallback_ ) { + + if ( firstErrorOccurred_ ) + return; + + firstErrorOccurred_ = true; + const std::string errorMessage = errorString; + + errorCallback_( type, errorMessage, errorCallbackUserData_ ); + firstErrorOccurred_ = false; + return; + } + + if ( type == RtMidiError::WARNING ) { + std::cerr << '\n' << errorString << "\n\n"; + } + else if ( type == RtMidiError::DEBUG_WARNING ) { +#if defined(__RTMIDI_DEBUG__) + std::cerr << '\n' << errorString << "\n\n"; +#endif + } + else { + std::cerr << '\n' << errorString << "\n\n"; + throw RtMidiError( errorString, type ); + } +} + +//*********************************************************************// +// Common MidiInApi Definitions +//*********************************************************************// + +MidiInApi :: MidiInApi( unsigned int queueSizeLimit ) + : MidiApi() +{ + // Allocate the MIDI queue. + inputData_.queue.ringSize = queueSizeLimit; + if ( inputData_.queue.ringSize > 0 ) + inputData_.queue.ring = new MidiMessage[ inputData_.queue.ringSize ]; +} + +MidiInApi :: ~MidiInApi( void ) +{ + // Delete the MIDI queue. + if ( inputData_.queue.ringSize > 0 ) delete [] inputData_.queue.ring; +} + +void MidiInApi :: setCallback( RtMidiIn::RtMidiCallback callback, void *userData ) +{ + if ( inputData_.usingCallback ) { + errorString_ = "MidiInApi::setCallback: a callback function is already set!"; + error( RtMidiError::WARNING, errorString_ ); + return; + } + + if ( !callback ) { + errorString_ = "RtMidiIn::setCallback: callback function value is invalid!"; + error( RtMidiError::WARNING, errorString_ ); + return; + } + + inputData_.userCallback = callback; + inputData_.userData = userData; + inputData_.usingCallback = true; +} + +void MidiInApi :: cancelCallback() +{ + if ( !inputData_.usingCallback ) { + errorString_ = "RtMidiIn::cancelCallback: no callback function was set!"; + error( RtMidiError::WARNING, errorString_ ); + return; + } + + inputData_.userCallback = 0; + inputData_.userData = 0; + inputData_.usingCallback = false; +} + +void MidiInApi :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) +{ + inputData_.ignoreFlags = 0; + if ( midiSysex ) inputData_.ignoreFlags = 0x01; + if ( midiTime ) inputData_.ignoreFlags |= 0x02; + if ( midiSense ) inputData_.ignoreFlags |= 0x04; +} + +double MidiInApi :: getMessage( std::vector<unsigned char> *message ) +{ + message->clear(); + + if ( inputData_.usingCallback ) { + errorString_ = "RtMidiIn::getNextMessage: a user callback is currently set for this port."; + error( RtMidiError::WARNING, errorString_ ); + return 0.0; + } + + double timeStamp; + if ( !inputData_.queue.pop( message, &timeStamp ) ) + return 0.0; + + return timeStamp; +} + +void MidiInApi :: setBufferSize( unsigned int size, unsigned int count ) +{ + inputData_.bufferSize = size; + inputData_.bufferCount = count; +} + +unsigned int MidiInApi::MidiQueue::size( unsigned int *__back, + unsigned int *__front ) +{ + // Access back/front members exactly once and make stack copies for + // size calculation + unsigned int _back = back, _front = front, _size; + if ( _back >= _front ) + _size = _back - _front; + else + _size = ringSize - _front + _back; + + // Return copies of back/front so no new and unsynchronized accesses + // to member variables are needed. + if ( __back ) *__back = _back; + if ( __front ) *__front = _front; + return _size; +} + +// As long as we haven't reached our queue size limit, push the message. +bool MidiInApi::MidiQueue::push( const MidiInApi::MidiMessage& msg ) +{ + // Local stack copies of front/back + unsigned int _back, _front, _size; + + // Get back/front indexes exactly once and calculate current size + _size = size( &_back, &_front ); + + if ( _size < ringSize-1 ) + { + ring[_back] = msg; + back = (back+1)%ringSize; + return true; + } + + return false; +} + +bool MidiInApi::MidiQueue::pop( std::vector<unsigned char> *msg, double* timeStamp ) +{ + // Local stack copies of front/back + unsigned int _back, _front, _size; + + // Get back/front indexes exactly once and calculate current size + _size = size( &_back, &_front ); + + if ( _size == 0 ) + return false; + + // Copy queued message to the vector pointer argument and then "pop" it. + msg->assign( ring[_front].bytes.begin(), ring[_front].bytes.end() ); + *timeStamp = ring[_front].timeStamp; + + // Update front + front = (front+1)%ringSize; + return true; +} + +//*********************************************************************// +// Common MidiOutApi Definitions +//*********************************************************************// + +MidiOutApi :: MidiOutApi( void ) + : MidiApi() +{ +} + +MidiOutApi :: ~MidiOutApi( void ) +{ +} + +// *************************************************** // +// +// OS/API-specific methods. +// +// *************************************************** // + +#if defined(__MACOSX_CORE__) + +// The CoreMIDI API is based on the use of a callback function for +// MIDI input. We convert the system specific time stamps to delta +// time values. + +// These are not available on iOS. +#if (TARGET_OS_IPHONE == 0) + #include <CoreAudio/HostTime.h> + #include <CoreServices/CoreServices.h> +#endif + +// A structure to hold variables related to the CoreMIDI API +// implementation. +struct CoreMidiData { + MIDIClientRef client; + MIDIPortRef port; + MIDIEndpointRef endpoint; + MIDIEndpointRef destinationId; + unsigned long long lastTime; + MIDISysexSendRequest sysexreq; +}; + +static MIDIClientRef CoreMidiClientSingleton = 0; + +void RtMidi_setCoreMidiClientSingleton(MIDIClientRef client){ + CoreMidiClientSingleton = client; +} + +void RtMidi_disposeCoreMidiClientSingleton(){ + if (CoreMidiClientSingleton == 0){ + return; + } + MIDIClientDispose( CoreMidiClientSingleton ); + CoreMidiClientSingleton = 0; +} + +//*********************************************************************// +// API: OS-X +// Class Definitions: MidiInCore +//*********************************************************************// + +static void midiInputCallback( const MIDIPacketList *list, void *procRef, void */*srcRef*/ ) +{ + MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (procRef); + CoreMidiData *apiData = static_cast<CoreMidiData *> (data->apiData); + + unsigned char status; + unsigned short nBytes, iByte, size; + unsigned long long time; + + bool& continueSysex = data->continueSysex; + MidiInApi::MidiMessage& message = data->message; + + const MIDIPacket *packet = &list->packet[0]; + for ( unsigned int i=0; i<list->numPackets; ++i ) { + + // My interpretation of the CoreMIDI documentation: all message + // types, except sysex, are complete within a packet and there may + // be several of them in a single packet. Sysex messages can be + // broken across multiple packets and PacketLists but are bundled + // alone within each packet (these packets do not contain other + // message types). If sysex messages are split across multiple + // MIDIPacketLists, they must be handled by multiple calls to this + // function. + + nBytes = packet->length; + if ( nBytes == 0 ) { + packet = MIDIPacketNext( packet ); + continue; + } + + // Calculate time stamp. + if ( data->firstMessage ) { + message.timeStamp = 0.0; + data->firstMessage = false; + } + else { + time = packet->timeStamp; + if ( time == 0 ) { // this happens when receiving asynchronous sysex messages + time = AudioGetCurrentHostTime(); + } + time -= apiData->lastTime; + time = AudioConvertHostTimeToNanos( time ); + if ( !continueSysex ) + message.timeStamp = time * 0.000000001; + } + + // Track whether any non-filtered messages were found in this + // packet for timestamp calculation + bool foundNonFiltered = false; + + iByte = 0; + if ( continueSysex ) { + // We have a continuing, segmented sysex message. + if ( !( data->ignoreFlags & 0x01 ) ) { + // If we're not ignoring sysex messages, copy the entire packet. + for ( unsigned int j=0; j<nBytes; ++j ) + message.bytes.push_back( packet->data[j] ); + } + continueSysex = packet->data[nBytes-1] != 0xF7; + + if ( !( data->ignoreFlags & 0x01 ) && !continueSysex ) { + // If not a continuing sysex message, invoke the user callback function or queue the message. + if ( data->usingCallback ) { + RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; + callback( message.timeStamp, &message.bytes, data->userData ); + } + else { + // As long as we haven't reached our queue size limit, push the message. + if ( !data->queue.push( message ) ) + std::cerr << "\nMidiInCore: message queue limit reached!!\n\n"; + } + message.bytes.clear(); + } + } + else { + while ( iByte < nBytes ) { + size = 0; + // We are expecting that the next byte in the packet is a status byte. + status = packet->data[iByte]; + if ( !(status & 0x80) ) break; + // Determine the number of bytes in the MIDI message. + if ( status < 0xC0 ) size = 3; + else if ( status < 0xE0 ) size = 2; + else if ( status < 0xF0 ) size = 3; + else if ( status == 0xF0 ) { + // A MIDI sysex + if ( data->ignoreFlags & 0x01 ) { + size = 0; + iByte = nBytes; + } + else size = nBytes - iByte; + continueSysex = packet->data[nBytes-1] != 0xF7; + } + else if ( status == 0xF1 ) { + // A MIDI time code message + if ( data->ignoreFlags & 0x02 ) { + size = 0; + iByte += 2; + } + else size = 2; + } + else if ( status == 0xF2 ) size = 3; + else if ( status == 0xF3 ) size = 2; + else if ( status == 0xF8 && ( data->ignoreFlags & 0x02 ) ) { + // A MIDI timing tick message and we're ignoring it. + size = 0; + iByte += 1; + } + else if ( status == 0xFE && ( data->ignoreFlags & 0x04 ) ) { + // A MIDI active sensing message and we're ignoring it. + size = 0; + iByte += 1; + } + else size = 1; + + // Copy the MIDI data to our vector. + if ( size ) { + foundNonFiltered = true; + message.bytes.assign( &packet->data[iByte], &packet->data[iByte+size] ); + if ( !continueSysex ) { + // If not a continuing sysex message, invoke the user callback function or queue the message. + if ( data->usingCallback ) { + RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; + callback( message.timeStamp, &message.bytes, data->userData ); + } + else { + // As long as we haven't reached our queue size limit, push the message. + if ( !data->queue.push( message ) ) + std::cerr << "\nMidiInCore: message queue limit reached!!\n\n"; + } + message.bytes.clear(); + } + iByte += size; + } + } + } + + // Save the time of the last non-filtered message + if ( foundNonFiltered ) { + apiData->lastTime = packet->timeStamp; + if ( apiData->lastTime == 0 ) { // this happens when receiving asynchronous sysex messages + apiData->lastTime = AudioGetCurrentHostTime(); + } + } + + packet = MIDIPacketNext(packet); + } +} + +MidiInCore :: MidiInCore( const std::string &clientName, unsigned int queueSizeLimit ) + : MidiInApi( queueSizeLimit ) +{ + MidiInCore::initialize( clientName ); +} + +MidiInCore :: ~MidiInCore( void ) +{ + // Close a connection if it exists. + MidiInCore::closePort(); + + // Cleanup. + CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); + if ( data->endpoint ) MIDIEndpointDispose( data->endpoint ); + delete data; +} + +MIDIClientRef MidiInCore::getCoreMidiClientSingleton(const std::string& clientName) throw() { + + if (CoreMidiClientSingleton == 0){ + // Set up our client. + MIDIClientRef client; + + CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ); + OSStatus result = MIDIClientCreate(name, NULL, NULL, &client ); + if ( result != noErr ) { + std::ostringstream ost; + ost << "MidiInCore::initialize: error creating OS-X MIDI client object (" << result << ")."; + errorString_ = ost.str(); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return 0; + } + CFRelease( name ); + + CoreMidiClientSingleton = client; + } + + return CoreMidiClientSingleton; +} + +void MidiInCore :: initialize( const std::string& clientName ) +{ + // Set up our client. + MIDIClientRef client = getCoreMidiClientSingleton(clientName); + + // Save our api-specific connection information. + CoreMidiData *data = (CoreMidiData *) new CoreMidiData; + data->client = client; + data->endpoint = 0; + apiData_ = (void *) data; + inputData_.apiData = (void *) data; +} + +void MidiInCore :: openPort( unsigned int portNumber, const std::string &portName ) +{ + if ( connected_ ) { + errorString_ = "MidiInCore::openPort: a valid connection already exists!"; + error( RtMidiError::WARNING, errorString_ ); + return; + } + + CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); + unsigned int nSrc = MIDIGetNumberOfSources(); + if ( nSrc < 1 ) { + errorString_ = "MidiInCore::openPort: no MIDI input sources found!"; + error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); + return; + } + + if ( portNumber >= nSrc ) { + std::ostringstream ost; + ost << "MidiInCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( RtMidiError::INVALID_PARAMETER, errorString_ ); + return; + } + + MIDIPortRef port; + CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); + CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ); + OSStatus result = MIDIInputPortCreate( data->client, + portNameRef, + midiInputCallback, (void *)&inputData_, &port ); + CFRelease( portNameRef ); + + if ( result != noErr ) { + errorString_ = "MidiInCore::openPort: error creating OS-X MIDI input port."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + + // Get the desired input source identifier. + MIDIEndpointRef endpoint = MIDIGetSource( portNumber ); + if ( endpoint == 0 ) { + MIDIPortDispose( port ); + errorString_ = "MidiInCore::openPort: error getting MIDI input source reference."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + + // Make the connection. + result = MIDIPortConnectSource( port, endpoint, NULL ); + if ( result != noErr ) { + MIDIPortDispose( port ); + errorString_ = "MidiInCore::openPort: error connecting OS-X MIDI input port."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + + // Save our api-specific port information. + data->port = port; + + connected_ = true; +} + +void MidiInCore :: openVirtualPort( const std::string &portName ) +{ + CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); + + // Create a virtual MIDI input destination. + MIDIEndpointRef endpoint; + CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ); + OSStatus result = MIDIDestinationCreate( data->client, + portNameRef, + midiInputCallback, (void *)&inputData_, &endpoint ); + CFRelease( portNameRef ); + + if ( result != noErr ) { + errorString_ = "MidiInCore::openVirtualPort: error creating virtual OS-X MIDI destination."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + + // Save our api-specific connection information. + data->endpoint = endpoint; +} + +void MidiInCore :: closePort( void ) +{ + CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); + + if ( data->endpoint ) { + MIDIEndpointDispose( data->endpoint ); + data->endpoint = 0; + } + + if ( data->port ) { + MIDIPortDispose( data->port ); + data->port = 0; + } + + connected_ = false; +} + +void MidiInCore :: setClientName ( const std::string& ) +{ + + errorString_ = "MidiInCore::setClientName: this function is not implemented for the MACOSX_CORE API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + +void MidiInCore :: setPortName ( const std::string& ) +{ + + errorString_ = "MidiInCore::setPortName: this function is not implemented for the MACOSX_CORE API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + +unsigned int MidiInCore :: getPortCount() +{ + CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); + return MIDIGetNumberOfSources(); +} + +// This function was submitted by Douglas Casey Tucker and apparently +// derived largely from PortMidi. +CFStringRef EndpointName( MIDIEndpointRef endpoint, bool isExternal ) +{ + CFMutableStringRef result = CFStringCreateMutable( NULL, 0 ); + CFStringRef str; + + // Begin with the endpoint's name. + str = NULL; + MIDIObjectGetStringProperty( endpoint, kMIDIPropertyName, &str ); + if ( str != NULL ) { + CFStringAppend( result, str ); + CFRelease( str ); + } + + // some MIDI devices have a leading space in endpoint name. trim + CFStringRef space = CFStringCreateWithCString(NULL, " ", kCFStringEncodingUTF8); + CFStringTrim(result, space); + CFRelease(space); + + MIDIEntityRef entity = 0; + MIDIEndpointGetEntity( endpoint, &entity ); + if ( entity == 0 ) + // probably virtual + return result; + + if ( CFStringGetLength( result ) == 0 ) { + // endpoint name has zero length -- try the entity + str = NULL; + MIDIObjectGetStringProperty( entity, kMIDIPropertyName, &str ); + if ( str != NULL ) { + CFStringAppend( result, str ); + CFRelease( str ); + } + } + // now consider the device's name + MIDIDeviceRef device = 0; + MIDIEntityGetDevice( entity, &device ); + if ( device == 0 ) + return result; + + str = NULL; + MIDIObjectGetStringProperty( device, kMIDIPropertyName, &str ); + if ( CFStringGetLength( result ) == 0 ) { + CFRelease( result ); + return str; + } + if ( str != NULL ) { + // if an external device has only one entity, throw away + // the endpoint name and just use the device name + if ( isExternal && MIDIDeviceGetNumberOfEntities( device ) < 2 ) { + CFRelease( result ); + return str; + } else { + if ( CFStringGetLength( str ) == 0 ) { + CFRelease( str ); + return result; + } + // does the entity name already start with the device name? + // (some drivers do this though they shouldn't) + // if so, do not prepend + if ( CFStringCompareWithOptions( result, /* endpoint name */ + str /* device name */, + CFRangeMake(0, CFStringGetLength( str ) ), 0 ) != kCFCompareEqualTo ) { + // prepend the device name to the entity name + if ( CFStringGetLength( result ) > 0 ) + CFStringInsert( result, 0, CFSTR(" ") ); + + CFStringInsert( result, 0, str ); + } + CFRelease( str ); + } + } + return result; +} + +// This function was submitted by Douglas Casey Tucker and apparently +// derived largely from PortMidi. +static CFStringRef ConnectedEndpointName( MIDIEndpointRef endpoint ) +{ + CFMutableStringRef result = CFStringCreateMutable( NULL, 0 ); + CFStringRef str; + OSStatus err; + int i; + + // Does the endpoint have connections? + CFDataRef connections = NULL; + int nConnected = 0; + bool anyStrings = false; + err = MIDIObjectGetDataProperty( endpoint, kMIDIPropertyConnectionUniqueID, &connections ); + if ( connections != NULL ) { + // It has connections, follow them + // Concatenate the names of all connected devices + nConnected = CFDataGetLength( connections ) / sizeof(MIDIUniqueID); + if ( nConnected ) { + const SInt32 *pid = (const SInt32 *)(CFDataGetBytePtr(connections)); + for ( i=0; i<nConnected; ++i, ++pid ) { + MIDIUniqueID id = EndianS32_BtoN( *pid ); + MIDIObjectRef connObject; + MIDIObjectType connObjectType; + err = MIDIObjectFindByUniqueID( id, &connObject, &connObjectType ); + if ( err == noErr ) { + if ( connObjectType == kMIDIObjectType_ExternalSource || + connObjectType == kMIDIObjectType_ExternalDestination ) { + // Connected to an external device's endpoint (10.3 and later). + str = EndpointName( (MIDIEndpointRef)(connObject), true ); + } else { + // Connected to an external device (10.2) (or something else, catch- + str = NULL; + MIDIObjectGetStringProperty( connObject, kMIDIPropertyName, &str ); + } + if ( str != NULL ) { + if ( anyStrings ) + CFStringAppend( result, CFSTR(", ") ); + else + anyStrings = true; + CFStringAppend( result, str ); + CFRelease( str ); + } + } + } + } + CFRelease( connections ); + } + if ( anyStrings ) + return result; + + CFRelease( result ); + + // Here, either the endpoint had no connections, or we failed to obtain names + return EndpointName( endpoint, false ); +} + +std::string MidiInCore :: getPortName( unsigned int portNumber ) +{ + CFStringRef nameRef; + MIDIEndpointRef portRef; + char name[128]; + + std::string stringName; + CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); + if ( portNumber >= MIDIGetNumberOfSources() ) { + std::ostringstream ost; + ost << "MidiInCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( RtMidiError::WARNING, errorString_ ); + return stringName; + } + + portRef = MIDIGetSource( portNumber ); + nameRef = ConnectedEndpointName( portRef ); + CFStringGetCString( nameRef, name, sizeof(name), kCFStringEncodingUTF8 ); + CFRelease( nameRef ); + + return stringName = name; +} + +//*********************************************************************// +// API: OS-X +// Class Definitions: MidiOutCore +//*********************************************************************// + +MidiOutCore :: MidiOutCore( const std::string &clientName ) + : MidiOutApi() +{ + MidiOutCore::initialize( clientName ); +} + +MidiOutCore :: ~MidiOutCore( void ) +{ + // Close a connection if it exists. + MidiOutCore::closePort(); + + // Cleanup. + CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); + if ( data->endpoint ) MIDIEndpointDispose( data->endpoint ); + delete data; +} + +MIDIClientRef MidiOutCore::getCoreMidiClientSingleton(const std::string& clientName) throw() { + + if (CoreMidiClientSingleton == 0){ + // Set up our client. + MIDIClientRef client; + + CFStringRef name = CFStringCreateWithCString( NULL, clientName.c_str(), kCFStringEncodingASCII ); + OSStatus result = MIDIClientCreate(name, NULL, NULL, &client ); + if ( result != noErr ) { + std::ostringstream ost; + ost << "MidiInCore::initialize: error creating OS-X MIDI client object (" << result << ")."; + errorString_ = ost.str(); + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return 0; + } + CFRelease( name ); + + CoreMidiClientSingleton = client; + } + + return CoreMidiClientSingleton; +} + +void MidiOutCore :: initialize( const std::string& clientName ) +{ + // Set up our client. + MIDIClientRef client = getCoreMidiClientSingleton(clientName); + + // Save our api-specific connection information. + CoreMidiData *data = (CoreMidiData *) new CoreMidiData; + data->client = client; + data->endpoint = 0; + apiData_ = (void *) data; +} + +unsigned int MidiOutCore :: getPortCount() +{ + CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); + return MIDIGetNumberOfDestinations(); +} + +std::string MidiOutCore :: getPortName( unsigned int portNumber ) +{ + CFStringRef nameRef; + MIDIEndpointRef portRef; + char name[128]; + + std::string stringName; + CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); + if ( portNumber >= MIDIGetNumberOfDestinations() ) { + std::ostringstream ost; + ost << "MidiOutCore::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( RtMidiError::WARNING, errorString_ ); + return stringName; + } + + portRef = MIDIGetDestination( portNumber ); + nameRef = ConnectedEndpointName(portRef); + CFStringGetCString( nameRef, name, sizeof(name), kCFStringEncodingUTF8 ); + CFRelease( nameRef ); + + return stringName = name; +} + +void MidiOutCore :: openPort( unsigned int portNumber, const std::string &portName ) +{ + if ( connected_ ) { + errorString_ = "MidiOutCore::openPort: a valid connection already exists!"; + error( RtMidiError::WARNING, errorString_ ); + return; + } + + CFRunLoopRunInMode( kCFRunLoopDefaultMode, 0, false ); + unsigned int nDest = MIDIGetNumberOfDestinations(); + if (nDest < 1) { + errorString_ = "MidiOutCore::openPort: no MIDI output destinations found!"; + error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); + return; + } + + if ( portNumber >= nDest ) { + std::ostringstream ost; + ost << "MidiOutCore::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( RtMidiError::INVALID_PARAMETER, errorString_ ); + return; + } + + MIDIPortRef port; + CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); + CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ); + OSStatus result = MIDIOutputPortCreate( data->client, portNameRef, &port ); + CFRelease( portNameRef ); + if ( result != noErr ) { + errorString_ = "MidiOutCore::openPort: error creating OS-X MIDI output port."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + + // Get the desired output port identifier. + MIDIEndpointRef destination = MIDIGetDestination( portNumber ); + if ( destination == 0 ) { + MIDIPortDispose( port ); + errorString_ = "MidiOutCore::openPort: error getting MIDI output destination reference."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + + // Save our api-specific connection information. + data->port = port; + data->destinationId = destination; + connected_ = true; +} + +void MidiOutCore :: closePort( void ) +{ + CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); + + if ( data->endpoint ) { + MIDIEndpointDispose( data->endpoint ); + data->endpoint = 0; + } + + if ( data->port ) { + MIDIPortDispose( data->port ); + data->port = 0; + } + + connected_ = false; +} + +void MidiOutCore :: setClientName ( const std::string& ) +{ + + errorString_ = "MidiOutCore::setClientName: this function is not implemented for the MACOSX_CORE API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + +void MidiOutCore :: setPortName ( const std::string& ) +{ + + errorString_ = "MidiOutCore::setPortName: this function is not implemented for the MACOSX_CORE API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + +void MidiOutCore :: openVirtualPort( const std::string &portName ) +{ + CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); + + if ( data->endpoint ) { + errorString_ = "MidiOutCore::openVirtualPort: a virtual output port already exists!"; + error( RtMidiError::WARNING, errorString_ ); + return; + } + + // Create a virtual MIDI output source. + MIDIEndpointRef endpoint; + CFStringRef portNameRef = CFStringCreateWithCString( NULL, portName.c_str(), kCFStringEncodingASCII ); + OSStatus result = MIDISourceCreate( data->client, portNameRef, &endpoint ); + CFRelease( portNameRef ); + + if ( result != noErr ) { + errorString_ = "MidiOutCore::initialize: error creating OS-X virtual MIDI source."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + + // Save our api-specific connection information. + data->endpoint = endpoint; +} + +void MidiOutCore :: sendMessage( const unsigned char *message, size_t size ) +{ + // We use the MIDISendSysex() function to asynchronously send sysex + // messages. Otherwise, we use a single CoreMidi MIDIPacket. + unsigned int nBytes = static_cast<unsigned int> (size); + if ( nBytes == 0 ) { + errorString_ = "MidiOutCore::sendMessage: no data in message argument!"; + error( RtMidiError::WARNING, errorString_ ); + return; + } + + if ( message[0] != 0xF0 && nBytes > 3 ) { + errorString_ = "MidiOutCore::sendMessage: message format problem ... not sysex but > 3 bytes?"; + error( RtMidiError::WARNING, errorString_ ); + return; + } + + MIDITimeStamp timeStamp = AudioGetCurrentHostTime(); + CoreMidiData *data = static_cast<CoreMidiData *> (apiData_); + OSStatus result; + + ByteCount bufsize = nBytes > 65535 ? 65535 : nBytes; + Byte buffer[bufsize+16]; // pad for other struct members + ByteCount listSize = sizeof( buffer ); + MIDIPacketList *packetList = (MIDIPacketList*)buffer; + + ByteCount remainingBytes = nBytes; + while ( remainingBytes ) { + MIDIPacket *packet = MIDIPacketListInit( packetList ); + // A MIDIPacketList can only contain a maximum of 64K of data, so if our message is longer, + // break it up into chunks of 64K or less and send out as a MIDIPacketList with only one + // MIDIPacket. Here, we reuse the memory allocated above on the stack for all. + ByteCount bytesForPacket = remainingBytes > 65535 ? 65535 : remainingBytes; + const Byte* dataStartPtr = (const Byte *) &message[nBytes - remainingBytes]; + packet = MIDIPacketListAdd( packetList, listSize, packet, timeStamp, bytesForPacket, dataStartPtr ); + remainingBytes -= bytesForPacket; + + if ( !packet ) { + errorString_ = "MidiOutCore::sendMessage: could not allocate packet list"; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + + // Send to any destinations that may have connected to us. + if ( data->endpoint ) { + result = MIDIReceived( data->endpoint, packetList ); + if ( result != noErr ) { + errorString_ = "MidiOutCore::sendMessage: error sending MIDI to virtual destinations."; + error( RtMidiError::WARNING, errorString_ ); + } + } + + // And send to an explicit destination port if we're connected. + if ( connected_ ) { + result = MIDISend( data->port, data->destinationId, packetList ); + if ( result != noErr ) { + errorString_ = "MidiOutCore::sendMessage: error sending MIDI message to port."; + error( RtMidiError::WARNING, errorString_ ); + } + } + } +} + +#endif // __MACOSX_CORE__ + + +//*********************************************************************// +// API: LINUX ALSA SEQUENCER +//*********************************************************************// + +// API information found at: +// - http://www.alsa-project.org/documentation.php#Library + +#if defined(__LINUX_ALSA__) + +// The ALSA Sequencer API is based on the use of a callback function for +// MIDI input. +// +// Thanks to Pedro Lopez-Cabanillas for help with the ALSA sequencer +// time stamps and other assorted fixes!!! + +// If you don't need timestamping for incoming MIDI events, define the +// preprocessor definition AVOID_TIMESTAMPING to save resources +// associated with the ALSA sequencer queues. + +#include <pthread.h> +#include <sys/time.h> + +// ALSA header file. +#include <alsa/asoundlib.h> + +// A structure to hold variables related to the ALSA API +// implementation. +struct AlsaMidiData { + snd_seq_t *seq; + unsigned int portNum; + int vport; + snd_seq_port_subscribe_t *subscription; + snd_midi_event_t *coder; + unsigned int bufferSize; + unsigned int requestedBufferSize; + unsigned char *buffer; + pthread_t thread; + pthread_t dummy_thread_id; + snd_seq_real_time_t lastTime; + int queue_id; // an input queue is needed to get timestamped events + int trigger_fds[2]; +}; + +#define PORT_TYPE( pinfo, bits ) ((snd_seq_port_info_get_capability(pinfo) & (bits)) == (bits)) + +//*********************************************************************// +// API: LINUX ALSA +// Class Definitions: MidiInAlsa +//*********************************************************************// + +static void *alsaMidiHandler( void *ptr ) +{ + MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (ptr); + AlsaMidiData *apiData = static_cast<AlsaMidiData *> (data->apiData); + + long nBytes; + double time; + bool continueSysex = false; + bool doDecode = false; + MidiInApi::MidiMessage message; + int poll_fd_count; + struct pollfd *poll_fds; + + snd_seq_event_t *ev; + int result; + result = snd_midi_event_new( 0, &apiData->coder ); + if ( result < 0 ) { + data->doInput = false; + std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing MIDI event parser!\n\n"; + return 0; + } + unsigned char *buffer = (unsigned char *) malloc( apiData->bufferSize ); + if ( buffer == NULL ) { + data->doInput = false; + snd_midi_event_free( apiData->coder ); + apiData->coder = 0; + std::cerr << "\nMidiInAlsa::alsaMidiHandler: error initializing buffer memory!\n\n"; + return 0; + } + snd_midi_event_init( apiData->coder ); + snd_midi_event_no_status( apiData->coder, 1 ); // suppress running status messages + + poll_fd_count = snd_seq_poll_descriptors_count( apiData->seq, POLLIN ) + 1; + poll_fds = (struct pollfd*)alloca( poll_fd_count * sizeof( struct pollfd )); + snd_seq_poll_descriptors( apiData->seq, poll_fds + 1, poll_fd_count - 1, POLLIN ); + poll_fds[0].fd = apiData->trigger_fds[0]; + poll_fds[0].events = POLLIN; + + while ( data->doInput ) { + + if ( snd_seq_event_input_pending( apiData->seq, 1 ) == 0 ) { + // No data pending + if ( poll( poll_fds, poll_fd_count, -1) >= 0 ) { + if ( poll_fds[0].revents & POLLIN ) { + bool dummy; + int res = read( poll_fds[0].fd, &dummy, sizeof(dummy) ); + (void) res; + } + } + continue; + } + + // If here, there should be data. + result = snd_seq_event_input( apiData->seq, &ev ); + if ( result == -ENOSPC ) { + std::cerr << "\nMidiInAlsa::alsaMidiHandler: MIDI input buffer overrun!\n\n"; + continue; + } + else if ( result <= 0 ) { + std::cerr << "\nMidiInAlsa::alsaMidiHandler: unknown MIDI input error!\n"; + perror("System reports"); + continue; + } + + // This is a bit weird, but we now have to decode an ALSA MIDI + // event (back) into MIDI bytes. We'll ignore non-MIDI types. + if ( !continueSysex ) message.bytes.clear(); + + doDecode = false; + switch ( ev->type ) { + + case SND_SEQ_EVENT_PORT_SUBSCRIBED: +#if defined(__RTMIDI_DEBUG__) + std::cout << "MidiInAlsa::alsaMidiHandler: port connection made!\n"; +#endif + break; + + case SND_SEQ_EVENT_PORT_UNSUBSCRIBED: +#if defined(__RTMIDI_DEBUG__) + std::cerr << "MidiInAlsa::alsaMidiHandler: port connection has closed!\n"; + std::cout << "sender = " << (int) ev->data.connect.sender.client << ":" + << (int) ev->data.connect.sender.port + << ", dest = " << (int) ev->data.connect.dest.client << ":" + << (int) ev->data.connect.dest.port + << std::endl; +#endif + break; + + case SND_SEQ_EVENT_QFRAME: // MIDI time code + if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; + break; + + case SND_SEQ_EVENT_TICK: // 0xF9 ... MIDI timing tick + if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; + break; + + case SND_SEQ_EVENT_CLOCK: // 0xF8 ... MIDI timing (clock) tick + if ( !( data->ignoreFlags & 0x02 ) ) doDecode = true; + break; + + case SND_SEQ_EVENT_SENSING: // Active sensing + if ( !( data->ignoreFlags & 0x04 ) ) doDecode = true; + break; + + case SND_SEQ_EVENT_SYSEX: + if ( (data->ignoreFlags & 0x01) ) break; + if ( ev->data.ext.len > apiData->bufferSize ) { + apiData->bufferSize = ev->data.ext.len; + free( buffer ); + buffer = (unsigned char *) malloc( apiData->bufferSize ); + if ( buffer == NULL ) { + data->doInput = false; + std::cerr << "\nMidiInAlsa::alsaMidiHandler: error resizing buffer memory!\n\n"; + break; + } + } + doDecode = true; + break; + + default: + doDecode = true; + } + + if ( doDecode ) { + + nBytes = snd_midi_event_decode( apiData->coder, buffer, apiData->bufferSize, ev ); + if ( nBytes > 0 ) { + // The ALSA sequencer has a maximum buffer size for MIDI sysex + // events of 256 bytes. If a device sends sysex messages larger + // than this, they are segmented into 256 byte chunks. So, + // we'll watch for this and concatenate sysex chunks into a + // single sysex message if necessary. + if ( !continueSysex ) + message.bytes.assign( buffer, &buffer[nBytes] ); + else + message.bytes.insert( message.bytes.end(), buffer, &buffer[nBytes] ); + + continueSysex = ( ( ev->type == SND_SEQ_EVENT_SYSEX ) && ( message.bytes.back() != 0xF7 ) ); + if ( !continueSysex ) { + + // Calculate the time stamp: + message.timeStamp = 0.0; + + // Method 1: Use the system time. + //(void)gettimeofday(&tv, (struct timezone *)NULL); + //time = (tv.tv_sec * 1000000) + tv.tv_usec; + + // Method 2: Use the ALSA sequencer event time data. + // (thanks to Pedro Lopez-Cabanillas!). + + // Using method from: + // https://www.gnu.org/software/libc/manual/html_node/Elapsed-Time.html + + // Perform the carry for the later subtraction by updating y. + // Temp var y is timespec because computation requires signed types, + // while snd_seq_real_time_t has unsigned types. + snd_seq_real_time_t &x( ev->time.time ); + struct timespec y; + y.tv_nsec = apiData->lastTime.tv_nsec; + y.tv_sec = apiData->lastTime.tv_sec; + if ( x.tv_nsec < y.tv_nsec ) { + int nsec = (y.tv_nsec - (int)x.tv_nsec) / 1000000000 + 1; + y.tv_nsec -= 1000000000 * nsec; + y.tv_sec += nsec; + } + if ( x.tv_nsec - y.tv_nsec > 1000000000 ) { + int nsec = ((int)x.tv_nsec - y.tv_nsec) / 1000000000; + y.tv_nsec += 1000000000 * nsec; + y.tv_sec -= nsec; + } + + // Compute the time difference. + time = (int)x.tv_sec - y.tv_sec + ((int)x.tv_nsec - y.tv_nsec)*1e-9; + + apiData->lastTime = ev->time.time; + + if ( data->firstMessage == true ) + data->firstMessage = false; + else + message.timeStamp = time; + } + else { +#if defined(__RTMIDI_DEBUG__) + std::cerr << "\nMidiInAlsa::alsaMidiHandler: event parsing error or not a MIDI event!\n\n"; +#endif + } + } + } + + snd_seq_free_event( ev ); + if ( message.bytes.size() == 0 || continueSysex ) continue; + + if ( data->usingCallback ) { + RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; + callback( message.timeStamp, &message.bytes, data->userData ); + } + else { + // As long as we haven't reached our queue size limit, push the message. + if ( !data->queue.push( message ) ) + std::cerr << "\nMidiInAlsa: message queue limit reached!!\n\n"; + } + } + + if ( buffer ) free( buffer ); + snd_midi_event_free( apiData->coder ); + apiData->coder = 0; + apiData->thread = apiData->dummy_thread_id; + return 0; +} + +MidiInAlsa :: MidiInAlsa( const std::string &clientName, unsigned int queueSizeLimit ) + : MidiInApi( queueSizeLimit ) +{ + MidiInAlsa::initialize( clientName ); +} + +MidiInAlsa :: ~MidiInAlsa() +{ + // Close a connection if it exists. + MidiInAlsa::closePort(); + + // Shutdown the input thread. + AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); + if ( inputData_.doInput ) { + inputData_.doInput = false; + int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof( inputData_.doInput ) ); + (void) res; + if ( !pthread_equal(data->thread, data->dummy_thread_id) ) + pthread_join( data->thread, NULL ); + } + + // Cleanup. + close ( data->trigger_fds[0] ); + close ( data->trigger_fds[1] ); + if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport ); +#ifndef AVOID_TIMESTAMPING + snd_seq_free_queue( data->seq, data->queue_id ); +#endif + snd_seq_close( data->seq ); + delete data; +} + +void MidiInAlsa :: initialize( const std::string& clientName ) +{ + // Set up the ALSA sequencer client. + snd_seq_t *seq; + int result = snd_seq_open( &seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK ); + if ( result < 0 ) { + errorString_ = "MidiInAlsa::initialize: error creating ALSA sequencer client object."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + + // Set client name. + snd_seq_set_client_name( seq, clientName.c_str() ); + + // Save our api-specific connection information. + AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData; + data->seq = seq; + data->portNum = -1; + data->vport = -1; + data->subscription = 0; + data->dummy_thread_id = pthread_self(); + data->thread = data->dummy_thread_id; + data->trigger_fds[0] = -1; + data->trigger_fds[1] = -1; + data->bufferSize = inputData_.bufferSize; + apiData_ = (void *) data; + inputData_.apiData = (void *) data; + + if ( pipe(data->trigger_fds) == -1 ) { + errorString_ = "MidiInAlsa::initialize: error creating pipe objects."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + + // Create the input queue +#ifndef AVOID_TIMESTAMPING + data->queue_id = snd_seq_alloc_named_queue( seq, "RtMidi Queue" ); + // Set arbitrary tempo (mm=100) and resolution (240) + snd_seq_queue_tempo_t *qtempo; + snd_seq_queue_tempo_alloca( &qtempo ); + snd_seq_queue_tempo_set_tempo( qtempo, 600000 ); + snd_seq_queue_tempo_set_ppq( qtempo, 240 ); + snd_seq_set_queue_tempo( data->seq, data->queue_id, qtempo ); + snd_seq_drain_output( data->seq ); +#endif +} + +// This function is used to count or get the pinfo structure for a given port number. +unsigned int portInfo( snd_seq_t *seq, snd_seq_port_info_t *pinfo, unsigned int type, int portNumber ) +{ + snd_seq_client_info_t *cinfo; + int client; + int count = 0; + snd_seq_client_info_alloca( &cinfo ); + + snd_seq_client_info_set_client( cinfo, -1 ); + while ( snd_seq_query_next_client( seq, cinfo ) >= 0 ) { + client = snd_seq_client_info_get_client( cinfo ); + if ( client == 0 ) continue; + // Reset query info + snd_seq_port_info_set_client( pinfo, client ); + snd_seq_port_info_set_port( pinfo, -1 ); + while ( snd_seq_query_next_port( seq, pinfo ) >= 0 ) { + unsigned int atyp = snd_seq_port_info_get_type( pinfo ); + if ( ( ( atyp & SND_SEQ_PORT_TYPE_MIDI_GENERIC ) == 0 ) && + ( ( atyp & SND_SEQ_PORT_TYPE_SYNTH ) == 0 ) && + ( ( atyp & SND_SEQ_PORT_TYPE_APPLICATION ) == 0 ) ) continue; + + unsigned int caps = snd_seq_port_info_get_capability( pinfo ); + if ( ( caps & type ) != type ) continue; + if ( count == portNumber ) return 1; + ++count; + } + } + + // If a negative portNumber was used, return the port count. + if ( portNumber < 0 ) return count; + return 0; +} + +unsigned int MidiInAlsa :: getPortCount() +{ + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca( &pinfo ); + + AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); + return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, -1 ); +} + +std::string MidiInAlsa :: getPortName( unsigned int portNumber ) +{ + snd_seq_client_info_t *cinfo; + snd_seq_port_info_t *pinfo; + snd_seq_client_info_alloca( &cinfo ); + snd_seq_port_info_alloca( &pinfo ); + + std::string stringName; + AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); + if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) ) { + int cnum = snd_seq_port_info_get_client( pinfo ); + snd_seq_get_any_client_info( data->seq, cnum, cinfo ); + std::ostringstream os; + os << snd_seq_client_info_get_name( cinfo ); + os << ":"; + os << snd_seq_port_info_get_name( pinfo ); + os << " "; // These lines added to make sure devices are listed + os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names + os << ":"; + os << snd_seq_port_info_get_port( pinfo ); + stringName = os.str(); + return stringName; + } + + // If we get here, we didn't find a match. + errorString_ = "MidiInAlsa::getPortName: error looking for port name!"; + error( RtMidiError::WARNING, errorString_ ); + return stringName; +} + +void MidiInAlsa :: openPort( unsigned int portNumber, const std::string &portName ) +{ + if ( connected_ ) { + errorString_ = "MidiInAlsa::openPort: a valid connection already exists!"; + error( RtMidiError::WARNING, errorString_ ); + return; + } + + unsigned int nSrc = this->getPortCount(); + if ( nSrc < 1 ) { + errorString_ = "MidiInAlsa::openPort: no MIDI input sources found!"; + error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); + return; + } + + snd_seq_port_info_t *src_pinfo; + snd_seq_port_info_alloca( &src_pinfo ); + AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); + if ( portInfo( data->seq, src_pinfo, SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, (int) portNumber ) == 0 ) { + std::ostringstream ost; + ost << "MidiInAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( RtMidiError::INVALID_PARAMETER, errorString_ ); + return; + } + + snd_seq_addr_t sender, receiver; + sender.client = snd_seq_port_info_get_client( src_pinfo ); + sender.port = snd_seq_port_info_get_port( src_pinfo ); + receiver.client = snd_seq_client_id( data->seq ); + + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca( &pinfo ); + if ( data->vport < 0 ) { + snd_seq_port_info_set_client( pinfo, 0 ); + snd_seq_port_info_set_port( pinfo, 0 ); + snd_seq_port_info_set_capability( pinfo, + SND_SEQ_PORT_CAP_WRITE | + SND_SEQ_PORT_CAP_SUBS_WRITE ); + snd_seq_port_info_set_type( pinfo, + SND_SEQ_PORT_TYPE_MIDI_GENERIC | + SND_SEQ_PORT_TYPE_APPLICATION ); + snd_seq_port_info_set_midi_channels(pinfo, 16); +#ifndef AVOID_TIMESTAMPING + snd_seq_port_info_set_timestamping( pinfo, 1 ); + snd_seq_port_info_set_timestamp_real( pinfo, 1 ); + snd_seq_port_info_set_timestamp_queue( pinfo, data->queue_id ); +#endif + snd_seq_port_info_set_name( pinfo, portName.c_str() ); + data->vport = snd_seq_create_port( data->seq, pinfo ); + + if ( data->vport < 0 ) { + errorString_ = "MidiInAlsa::openPort: ALSA error creating input port."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + data->vport = snd_seq_port_info_get_port( pinfo ); + } + + receiver.port = data->vport; + + if ( !data->subscription ) { + // Make subscription + if ( snd_seq_port_subscribe_malloc( &data->subscription ) < 0 ) { + errorString_ = "MidiInAlsa::openPort: ALSA error allocation port subscription."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + snd_seq_port_subscribe_set_sender( data->subscription, &sender ); + snd_seq_port_subscribe_set_dest( data->subscription, &receiver ); + if ( snd_seq_subscribe_port( data->seq, data->subscription ) ) { + snd_seq_port_subscribe_free( data->subscription ); + data->subscription = 0; + errorString_ = "MidiInAlsa::openPort: ALSA error making port connection."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + } + + if ( inputData_.doInput == false ) { + // Start the input queue +#ifndef AVOID_TIMESTAMPING + snd_seq_start_queue( data->seq, data->queue_id, NULL ); + snd_seq_drain_output( data->seq ); +#endif + // Start our MIDI input thread. + pthread_attr_t attr; + pthread_attr_init( &attr ); + pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); + pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); + + inputData_.doInput = true; + int err = pthread_create( &data->thread, &attr, alsaMidiHandler, &inputData_ ); + pthread_attr_destroy( &attr ); + if ( err ) { + snd_seq_unsubscribe_port( data->seq, data->subscription ); + snd_seq_port_subscribe_free( data->subscription ); + data->subscription = 0; + inputData_.doInput = false; + errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!"; + error( RtMidiError::THREAD_ERROR, errorString_ ); + return; + } + } + + connected_ = true; +} + +void MidiInAlsa :: openVirtualPort( const std::string &portName ) +{ + AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); + if ( data->vport < 0 ) { + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca( &pinfo ); + snd_seq_port_info_set_capability( pinfo, + SND_SEQ_PORT_CAP_WRITE | + SND_SEQ_PORT_CAP_SUBS_WRITE ); + snd_seq_port_info_set_type( pinfo, + SND_SEQ_PORT_TYPE_MIDI_GENERIC | + SND_SEQ_PORT_TYPE_APPLICATION ); + snd_seq_port_info_set_midi_channels( pinfo, 16 ); +#ifndef AVOID_TIMESTAMPING + snd_seq_port_info_set_timestamping( pinfo, 1 ); + snd_seq_port_info_set_timestamp_real( pinfo, 1 ); + snd_seq_port_info_set_timestamp_queue( pinfo, data->queue_id ); +#endif + snd_seq_port_info_set_name( pinfo, portName.c_str() ); + data->vport = snd_seq_create_port( data->seq, pinfo ); + + if ( data->vport < 0 ) { + errorString_ = "MidiInAlsa::openVirtualPort: ALSA error creating virtual port."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + data->vport = snd_seq_port_info_get_port( pinfo ); + } + + if ( inputData_.doInput == false ) { + // Wait for old thread to stop, if still running + if ( !pthread_equal( data->thread, data->dummy_thread_id ) ) + pthread_join( data->thread, NULL ); + + // Start the input queue +#ifndef AVOID_TIMESTAMPING + snd_seq_start_queue( data->seq, data->queue_id, NULL ); + snd_seq_drain_output( data->seq ); +#endif + // Start our MIDI input thread. + pthread_attr_t attr; + pthread_attr_init( &attr ); + pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); + pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); + + inputData_.doInput = true; + int err = pthread_create( &data->thread, &attr, alsaMidiHandler, &inputData_ ); + pthread_attr_destroy( &attr ); + if ( err ) { + if ( data->subscription ) { + snd_seq_unsubscribe_port( data->seq, data->subscription ); + snd_seq_port_subscribe_free( data->subscription ); + data->subscription = 0; + } + inputData_.doInput = false; + errorString_ = "MidiInAlsa::openPort: error starting MIDI input thread!"; + error( RtMidiError::THREAD_ERROR, errorString_ ); + return; + } + } +} + +void MidiInAlsa :: closePort( void ) +{ + AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); + + if ( connected_ ) { + if ( data->subscription ) { + snd_seq_unsubscribe_port( data->seq, data->subscription ); + snd_seq_port_subscribe_free( data->subscription ); + data->subscription = 0; + } + // Stop the input queue +#ifndef AVOID_TIMESTAMPING + snd_seq_stop_queue( data->seq, data->queue_id, NULL ); + snd_seq_drain_output( data->seq ); +#endif + connected_ = false; + } + + // Stop thread to avoid triggering the callback, while the port is intended to be closed + if ( inputData_.doInput ) { + inputData_.doInput = false; + int res = write( data->trigger_fds[1], &inputData_.doInput, sizeof( inputData_.doInput ) ); + (void) res; + if ( !pthread_equal( data->thread, data->dummy_thread_id ) ) + pthread_join( data->thread, NULL ); + } +} + +void MidiInAlsa :: setClientName( const std::string &clientName ) +{ + + AlsaMidiData *data = static_cast<AlsaMidiData *> ( apiData_ ); + snd_seq_set_client_name( data->seq, clientName.c_str() ); + +} + +void MidiInAlsa :: setPortName( const std::string &portName ) +{ + AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca( &pinfo ); + snd_seq_get_port_info( data->seq, data->vport, pinfo ); + snd_seq_port_info_set_name( pinfo, portName.c_str() ); + snd_seq_set_port_info( data->seq, data->vport, pinfo ); +} + +//*********************************************************************// +// API: LINUX ALSA +// Class Definitions: MidiOutAlsa +//*********************************************************************// + +MidiOutAlsa :: MidiOutAlsa( const std::string &clientName ) : MidiOutApi() +{ + MidiOutAlsa::initialize( clientName ); +} + +MidiOutAlsa :: ~MidiOutAlsa() +{ + // Close a connection if it exists. + MidiOutAlsa::closePort(); + + // Cleanup. + AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); + if ( data->vport >= 0 ) snd_seq_delete_port( data->seq, data->vport ); + if ( data->coder ) snd_midi_event_free( data->coder ); + if ( data->buffer ) free( data->buffer ); + snd_seq_close( data->seq ); + delete data; +} + +void MidiOutAlsa :: initialize( const std::string& clientName ) +{ + // Set up the ALSA sequencer client. + snd_seq_t *seq; + int result1 = snd_seq_open( &seq, "default", SND_SEQ_OPEN_OUTPUT, SND_SEQ_NONBLOCK ); + if ( result1 < 0 ) { + errorString_ = "MidiOutAlsa::initialize: error creating ALSA sequencer client object."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + + // Set client name. + snd_seq_set_client_name( seq, clientName.c_str() ); + + // Save our api-specific connection information. + AlsaMidiData *data = (AlsaMidiData *) new AlsaMidiData; + data->seq = seq; + data->portNum = -1; + data->vport = -1; + data->bufferSize = 32; + data->coder = 0; + data->buffer = 0; + int result = snd_midi_event_new( data->bufferSize, &data->coder ); + if ( result < 0 ) { + delete data; + errorString_ = "MidiOutAlsa::initialize: error initializing MIDI event parser!\n\n"; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + data->buffer = (unsigned char *) malloc( data->bufferSize ); + if ( data->buffer == NULL ) { + delete data; + errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n"; + error( RtMidiError::MEMORY_ERROR, errorString_ ); + return; + } + snd_midi_event_init( data->coder ); + apiData_ = (void *) data; +} + +unsigned int MidiOutAlsa :: getPortCount() +{ + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca( &pinfo ); + + AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); + return portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, -1 ); +} + +std::string MidiOutAlsa :: getPortName( unsigned int portNumber ) +{ + snd_seq_client_info_t *cinfo; + snd_seq_port_info_t *pinfo; + snd_seq_client_info_alloca( &cinfo ); + snd_seq_port_info_alloca( &pinfo ); + + std::string stringName; + AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); + if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) ) { + int cnum = snd_seq_port_info_get_client( pinfo ); + snd_seq_get_any_client_info( data->seq, cnum, cinfo ); + std::ostringstream os; + os << snd_seq_client_info_get_name( cinfo ); + os << ":"; + os << snd_seq_port_info_get_name( pinfo ); + os << " "; // These lines added to make sure devices are listed + os << snd_seq_port_info_get_client( pinfo ); // with full portnames added to ensure individual device names + os << ":"; + os << snd_seq_port_info_get_port( pinfo ); + stringName = os.str(); + return stringName; + } + + // If we get here, we didn't find a match. + errorString_ = "MidiOutAlsa::getPortName: error looking for port name!"; + error( RtMidiError::WARNING, errorString_ ); + return stringName; +} + +void MidiOutAlsa :: openPort( unsigned int portNumber, const std::string &portName ) +{ + if ( connected_ ) { + errorString_ = "MidiOutAlsa::openPort: a valid connection already exists!"; + error( RtMidiError::WARNING, errorString_ ); + return; + } + + unsigned int nSrc = this->getPortCount(); + if ( nSrc < 1 ) { + errorString_ = "MidiOutAlsa::openPort: no MIDI output sources found!"; + error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); + return; + } + + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca( &pinfo ); + AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); + if ( portInfo( data->seq, pinfo, SND_SEQ_PORT_CAP_WRITE|SND_SEQ_PORT_CAP_SUBS_WRITE, (int) portNumber ) == 0 ) { + std::ostringstream ost; + ost << "MidiOutAlsa::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( RtMidiError::INVALID_PARAMETER, errorString_ ); + return; + } + + snd_seq_addr_t sender, receiver; + receiver.client = snd_seq_port_info_get_client( pinfo ); + receiver.port = snd_seq_port_info_get_port( pinfo ); + sender.client = snd_seq_client_id( data->seq ); + + if ( data->vport < 0 ) { + data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(), + SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, + SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION ); + if ( data->vport < 0 ) { + errorString_ = "MidiOutAlsa::openPort: ALSA error creating output port."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + } + + sender.port = data->vport; + + // Make subscription + if ( snd_seq_port_subscribe_malloc( &data->subscription ) < 0 ) { + snd_seq_port_subscribe_free( data->subscription ); + errorString_ = "MidiOutAlsa::openPort: error allocating port subscription."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + snd_seq_port_subscribe_set_sender( data->subscription, &sender ); + snd_seq_port_subscribe_set_dest( data->subscription, &receiver ); + snd_seq_port_subscribe_set_time_update( data->subscription, 1 ); + snd_seq_port_subscribe_set_time_real( data->subscription, 1 ); + if ( snd_seq_subscribe_port( data->seq, data->subscription ) ) { + snd_seq_port_subscribe_free( data->subscription ); + errorString_ = "MidiOutAlsa::openPort: ALSA error making port connection."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + + connected_ = true; +} + +void MidiOutAlsa :: closePort( void ) +{ + if ( connected_ ) { + AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); + snd_seq_unsubscribe_port( data->seq, data->subscription ); + snd_seq_port_subscribe_free( data->subscription ); + data->subscription = 0; + connected_ = false; + } +} + +void MidiOutAlsa :: setClientName( const std::string &clientName ) +{ + + AlsaMidiData *data = static_cast<AlsaMidiData *> ( apiData_ ); + snd_seq_set_client_name( data->seq, clientName.c_str() ); + +} + +void MidiOutAlsa :: setPortName( const std::string &portName ) +{ + AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); + snd_seq_port_info_t *pinfo; + snd_seq_port_info_alloca( &pinfo ); + snd_seq_get_port_info( data->seq, data->vport, pinfo ); + snd_seq_port_info_set_name( pinfo, portName.c_str() ); + snd_seq_set_port_info( data->seq, data->vport, pinfo ); +} + +void MidiOutAlsa :: openVirtualPort( const std::string &portName ) +{ + AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); + if ( data->vport < 0 ) { + data->vport = snd_seq_create_simple_port( data->seq, portName.c_str(), + SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ, + SND_SEQ_PORT_TYPE_MIDI_GENERIC|SND_SEQ_PORT_TYPE_APPLICATION ); + + if ( data->vport < 0 ) { + errorString_ = "MidiOutAlsa::openVirtualPort: ALSA error creating virtual port."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + } + } +} + +void MidiOutAlsa :: sendMessage( const unsigned char *message, size_t size ) +{ + long result; + AlsaMidiData *data = static_cast<AlsaMidiData *> (apiData_); + unsigned int nBytes = static_cast<unsigned int> (size); + if ( nBytes > data->bufferSize ) { + data->bufferSize = nBytes; + result = snd_midi_event_resize_buffer( data->coder, nBytes ); + if ( result != 0 ) { + errorString_ = "MidiOutAlsa::sendMessage: ALSA error resizing MIDI event buffer."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + free (data->buffer); + data->buffer = (unsigned char *) malloc( data->bufferSize ); + if ( data->buffer == NULL ) { + errorString_ = "MidiOutAlsa::initialize: error allocating buffer memory!\n\n"; + error( RtMidiError::MEMORY_ERROR, errorString_ ); + return; + } + } + + for ( unsigned int i=0; i<nBytes; ++i ) data->buffer[i] = message[i]; + + unsigned int offset = 0; + while (offset < nBytes) { + snd_seq_event_t ev; + snd_seq_ev_clear( &ev ); + snd_seq_ev_set_source( &ev, data->vport ); + snd_seq_ev_set_subs( &ev ); + snd_seq_ev_set_direct( &ev ); + result = snd_midi_event_encode( data->coder, data->buffer + offset, + (long)(nBytes - offset), &ev ); + if ( result < 0 ) { + errorString_ = "MidiOutAlsa::sendMessage: event parsing error!"; + error( RtMidiError::WARNING, errorString_ ); + return; + } + + if ( ev.type == SND_SEQ_EVENT_NONE ) { + errorString_ = "MidiOutAlsa::sendMessage: incomplete message!"; + error( RtMidiError::WARNING, errorString_ ); + return; + } + + offset += result; + + // Send the event. + result = snd_seq_event_output( data->seq, &ev ); + if ( result < 0 ) { + errorString_ = "MidiOutAlsa::sendMessage: error sending MIDI message to port."; + error( RtMidiError::WARNING, errorString_ ); + return; + } + } + snd_seq_drain_output( data->seq ); +} + +#endif // __LINUX_ALSA__ + + +//*********************************************************************// +// API: Windows Multimedia Library (MM) +//*********************************************************************// + +// API information deciphered from: +// - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midi_reference.asp + +// Thanks to Jean-Baptiste Berruchon for the sysex code. + +#if defined(__WINDOWS_MM__) + +// The Windows MM API is based on the use of a callback function for +// MIDI input. We convert the system specific time stamps to delta +// time values. + +// Windows MM MIDI header files. +#include <windows.h> +#include <mmsystem.h> + +// Convert a null-terminated wide string or ANSI-encoded string to UTF-8. +static std::string ConvertToUTF8(const TCHAR *str) +{ + std::string u8str; + const WCHAR *wstr = L""; +#if defined( UNICODE ) || defined( _UNICODE ) + wstr = str; +#else + // Convert from ANSI encoding to wide string + int wlength = MultiByteToWideChar( CP_ACP, 0, str, -1, NULL, 0 ); + std::wstring wstrtemp; + if ( wlength ) + { + wstrtemp.assign( wlength - 1, 0 ); + MultiByteToWideChar( CP_ACP, 0, str, -1, &wstrtemp[0], wlength ); + wstr = &wstrtemp[0]; + } +#endif + // Convert from wide string to UTF-8 + int length = WideCharToMultiByte( CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL ); + if ( length ) + { + u8str.assign( length - 1, 0 ); + length = WideCharToMultiByte( CP_UTF8, 0, wstr, -1, &u8str[0], length, NULL, NULL ); + } + return u8str; +} + +// A structure to hold variables related to the CoreMIDI API +// implementation. +struct WinMidiData { + HMIDIIN inHandle; // Handle to Midi Input Device + HMIDIOUT outHandle; // Handle to Midi Output Device + DWORD lastTime; + MidiInApi::MidiMessage message; + std::vector<LPMIDIHDR> sysexBuffer; + CRITICAL_SECTION _mutex; // [Patrice] see https://groups.google.com/forum/#!topic/mididev/6OUjHutMpEo +}; + +//*********************************************************************// +// API: Windows MM +// Class Definitions: MidiInWinMM +//*********************************************************************// + +static void CALLBACK midiInputCallback( HMIDIIN /*hmin*/, + UINT inputStatus, + DWORD_PTR instancePtr, + DWORD_PTR midiMessage, + DWORD timestamp ) +{ + if ( inputStatus != MIM_DATA && inputStatus != MIM_LONGDATA && inputStatus != MIM_LONGERROR ) return; + + //MidiInApi::RtMidiInData *data = static_cast<MidiInApi::RtMidiInData *> (instancePtr); + MidiInApi::RtMidiInData *data = (MidiInApi::RtMidiInData *)instancePtr; + WinMidiData *apiData = static_cast<WinMidiData *> (data->apiData); + + // Calculate time stamp. + if ( data->firstMessage == true ) { + apiData->message.timeStamp = 0.0; + data->firstMessage = false; + } + else apiData->message.timeStamp = (double) ( timestamp - apiData->lastTime ) * 0.001; + + if ( inputStatus == MIM_DATA ) { // Channel or system message + + // Make sure the first byte is a status byte. + unsigned char status = (unsigned char) (midiMessage & 0x000000FF); + if ( !(status & 0x80) ) return; + + // Determine the number of bytes in the MIDI message. + unsigned short nBytes = 1; + if ( status < 0xC0 ) nBytes = 3; + else if ( status < 0xE0 ) nBytes = 2; + else if ( status < 0xF0 ) nBytes = 3; + else if ( status == 0xF1 ) { + if ( data->ignoreFlags & 0x02 ) return; + else nBytes = 2; + } + else if ( status == 0xF2 ) nBytes = 3; + else if ( status == 0xF3 ) nBytes = 2; + else if ( status == 0xF8 && ( data->ignoreFlags & 0x02 ) ) { + // A MIDI timing tick message and we're ignoring it. + return; + } + else if ( status == 0xFE && ( data->ignoreFlags & 0x04 ) ) { + // A MIDI active sensing message and we're ignoring it. + return; + } + + // Copy bytes to our MIDI message. + unsigned char *ptr = (unsigned char *) &midiMessage; + for ( int i=0; i<nBytes; ++i ) apiData->message.bytes.push_back( *ptr++ ); + } + else { // Sysex message ( MIM_LONGDATA or MIM_LONGERROR ) + MIDIHDR *sysex = ( MIDIHDR *) midiMessage; + if ( !( data->ignoreFlags & 0x01 ) && inputStatus != MIM_LONGERROR ) { + // Sysex message and we're not ignoring it + for ( int i=0; i<(int)sysex->dwBytesRecorded; ++i ) + apiData->message.bytes.push_back( sysex->lpData[i] ); + } + + // The WinMM API requires that the sysex buffer be requeued after + // input of each sysex message. Even if we are ignoring sysex + // messages, we still need to requeue the buffer in case the user + // decides to not ignore sysex messages in the future. However, + // it seems that WinMM calls this function with an empty sysex + // buffer when an application closes and in this case, we should + // avoid requeueing it, else the computer suddenly reboots after + // one or two minutes. + if ( apiData->sysexBuffer[sysex->dwUser]->dwBytesRecorded > 0 ) { + //if ( sysex->dwBytesRecorded > 0 ) { + EnterCriticalSection( &(apiData->_mutex) ); + MMRESULT result = midiInAddBuffer( apiData->inHandle, apiData->sysexBuffer[sysex->dwUser], sizeof(MIDIHDR) ); + LeaveCriticalSection( &(apiData->_mutex) ); + if ( result != MMSYSERR_NOERROR ) + std::cerr << "\nRtMidiIn::midiInputCallback: error sending sysex to Midi device!!\n\n"; + + if ( data->ignoreFlags & 0x01 ) return; + } + else return; + } + + // Save the time of the last non-filtered message + apiData->lastTime = timestamp; + + if ( data->usingCallback ) { + RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; + callback( apiData->message.timeStamp, &apiData->message.bytes, data->userData ); + } + else { + // As long as we haven't reached our queue size limit, push the message. + if ( !data->queue.push( apiData->message ) ) + std::cerr << "\nMidiInWinMM: message queue limit reached!!\n\n"; + } + + // Clear the vector for the next input message. + apiData->message.bytes.clear(); +} + +MidiInWinMM :: MidiInWinMM( const std::string &clientName, unsigned int queueSizeLimit ) + : MidiInApi( queueSizeLimit ) +{ + MidiInWinMM::initialize( clientName ); +} + +MidiInWinMM :: ~MidiInWinMM() +{ + // Close a connection if it exists. + MidiInWinMM::closePort(); + + WinMidiData *data = static_cast<WinMidiData *> (apiData_); + DeleteCriticalSection( &(data->_mutex) ); + + // Cleanup. + delete data; +} + +void MidiInWinMM :: initialize( const std::string& /*clientName*/ ) +{ + // We'll issue a warning here if no devices are available but not + // throw an error since the user can plugin something later. + unsigned int nDevices = midiInGetNumDevs(); + if ( nDevices == 0 ) { + errorString_ = "MidiInWinMM::initialize: no MIDI input devices currently available."; + error( RtMidiError::WARNING, errorString_ ); + } + + // Save our api-specific connection information. + WinMidiData *data = (WinMidiData *) new WinMidiData; + apiData_ = (void *) data; + inputData_.apiData = (void *) data; + data->message.bytes.clear(); // needs to be empty for first input message + + if ( !InitializeCriticalSectionAndSpinCount( &(data->_mutex), 0x00000400 ) ) { + errorString_ = "MidiInWinMM::initialize: InitializeCriticalSectionAndSpinCount failed."; + error( RtMidiError::WARNING, errorString_ ); + } +} + +void MidiInWinMM :: openPort( unsigned int portNumber, const std::string &/*portName*/ ) +{ + if ( connected_ ) { + errorString_ = "MidiInWinMM::openPort: a valid connection already exists!"; + error( RtMidiError::WARNING, errorString_ ); + return; + } + + unsigned int nDevices = midiInGetNumDevs(); + if (nDevices == 0) { + errorString_ = "MidiInWinMM::openPort: no MIDI input sources found!"; + error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); + return; + } + + if ( portNumber >= nDevices ) { + std::ostringstream ost; + ost << "MidiInWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( RtMidiError::INVALID_PARAMETER, errorString_ ); + return; + } + + WinMidiData *data = static_cast<WinMidiData *> (apiData_); + MMRESULT result = midiInOpen( &data->inHandle, + portNumber, + (DWORD_PTR)&midiInputCallback, + (DWORD_PTR)&inputData_, + CALLBACK_FUNCTION ); + if ( result != MMSYSERR_NOERROR ) { + errorString_ = "MidiInWinMM::openPort: error creating Windows MM MIDI input port."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + + // Allocate and init the sysex buffers. + data->sysexBuffer.resize( inputData_.bufferCount ); + for ( unsigned int i=0; i < inputData_.bufferCount; ++i ) { + data->sysexBuffer[i] = (MIDIHDR*) new char[ sizeof(MIDIHDR) ]; + data->sysexBuffer[i]->lpData = new char[ inputData_.bufferSize ]; + data->sysexBuffer[i]->dwBufferLength = inputData_.bufferSize; + data->sysexBuffer[i]->dwUser = i; // We use the dwUser parameter as buffer indicator + data->sysexBuffer[i]->dwFlags = 0; + + result = midiInPrepareHeader( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) ); + if ( result != MMSYSERR_NOERROR ) { + midiInClose( data->inHandle ); + data->inHandle = 0; + errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (PrepareHeader)."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + + // Register the buffer. + result = midiInAddBuffer( data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR) ); + if ( result != MMSYSERR_NOERROR ) { + midiInClose( data->inHandle ); + data->inHandle = 0; + errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port (AddBuffer)."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + } + + result = midiInStart( data->inHandle ); + if ( result != MMSYSERR_NOERROR ) { + midiInClose( data->inHandle ); + data->inHandle = 0; + errorString_ = "MidiInWinMM::openPort: error starting Windows MM MIDI input port."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + + connected_ = true; +} + +void MidiInWinMM :: openVirtualPort( const std::string &/*portName*/ ) +{ + // This function cannot be implemented for the Windows MM MIDI API. + errorString_ = "MidiInWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!"; + error( RtMidiError::WARNING, errorString_ ); +} + +void MidiInWinMM :: closePort( void ) +{ + if ( connected_ ) { + WinMidiData *data = static_cast<WinMidiData *> (apiData_); + EnterCriticalSection( &(data->_mutex) ); + midiInReset( data->inHandle ); + midiInStop( data->inHandle ); + + for ( size_t i=0; i < data->sysexBuffer.size(); ++i ) { + int result = midiInUnprepareHeader(data->inHandle, data->sysexBuffer[i], sizeof(MIDIHDR)); + delete [] data->sysexBuffer[i]->lpData; + delete [] data->sysexBuffer[i]; + if ( result != MMSYSERR_NOERROR ) { + midiInClose( data->inHandle ); + data->inHandle = 0; + errorString_ = "MidiInWinMM::openPort: error closing Windows MM MIDI input port (midiInUnprepareHeader)."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + } + + midiInClose( data->inHandle ); + data->inHandle = 0; + connected_ = false; + LeaveCriticalSection( &(data->_mutex) ); + } +} + +void MidiInWinMM :: setClientName ( const std::string& ) +{ + + errorString_ = "MidiInWinMM::setClientName: this function is not implemented for the WINDOWS_MM API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + +void MidiInWinMM :: setPortName ( const std::string& ) +{ + + errorString_ = "MidiInWinMM::setPortName: this function is not implemented for the WINDOWS_MM API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + +unsigned int MidiInWinMM :: getPortCount() +{ + return midiInGetNumDevs(); +} + +std::string MidiInWinMM :: getPortName( unsigned int portNumber ) +{ + std::string stringName; + unsigned int nDevices = midiInGetNumDevs(); + if ( portNumber >= nDevices ) { + std::ostringstream ost; + ost << "MidiInWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( RtMidiError::WARNING, errorString_ ); + return stringName; + } + + MIDIINCAPS deviceCaps; + midiInGetDevCaps( portNumber, &deviceCaps, sizeof(MIDIINCAPS)); + stringName = ConvertToUTF8( deviceCaps.szPname ); + + // Next lines added to add the portNumber to the name so that + // the device's names are sure to be listed with individual names + // even when they have the same brand name +#ifndef RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES + std::ostringstream os; + os << " "; + os << portNumber; + stringName += os.str(); +#endif + + return stringName; +} + +//*********************************************************************// +// API: Windows MM +// Class Definitions: MidiOutWinMM +//*********************************************************************// + +MidiOutWinMM :: MidiOutWinMM( const std::string &clientName ) : MidiOutApi() +{ + MidiOutWinMM::initialize( clientName ); +} + +MidiOutWinMM :: ~MidiOutWinMM() +{ + // Close a connection if it exists. + MidiOutWinMM::closePort(); + + // Cleanup. + WinMidiData *data = static_cast<WinMidiData *> (apiData_); + delete data; +} + +void MidiOutWinMM :: initialize( const std::string& /*clientName*/ ) +{ + // We'll issue a warning here if no devices are available but not + // throw an error since the user can plug something in later. + unsigned int nDevices = midiOutGetNumDevs(); + if ( nDevices == 0 ) { + errorString_ = "MidiOutWinMM::initialize: no MIDI output devices currently available."; + error( RtMidiError::WARNING, errorString_ ); + } + + // Save our api-specific connection information. + WinMidiData *data = (WinMidiData *) new WinMidiData; + apiData_ = (void *) data; +} + +unsigned int MidiOutWinMM :: getPortCount() +{ + return midiOutGetNumDevs(); +} + +std::string MidiOutWinMM :: getPortName( unsigned int portNumber ) +{ + std::string stringName; + unsigned int nDevices = midiOutGetNumDevs(); + if ( portNumber >= nDevices ) { + std::ostringstream ost; + ost << "MidiOutWinMM::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( RtMidiError::WARNING, errorString_ ); + return stringName; + } + + MIDIOUTCAPS deviceCaps; + midiOutGetDevCaps( portNumber, &deviceCaps, sizeof( MIDIOUTCAPS ) ); + stringName = ConvertToUTF8( deviceCaps.szPname ); + + // Next lines added to add the portNumber to the name so that + // the device's names are sure to be listed with individual names + // even when they have the same brand name + std::ostringstream os; +#ifndef RTMIDI_DO_NOT_ENSURE_UNIQUE_PORTNAMES + os << " "; + os << portNumber; + stringName += os.str(); +#endif + + return stringName; +} + +void MidiOutWinMM :: openPort( unsigned int portNumber, const std::string &/*portName*/ ) +{ + if ( connected_ ) { + errorString_ = "MidiOutWinMM::openPort: a valid connection already exists!"; + error( RtMidiError::WARNING, errorString_ ); + return; + } + + unsigned int nDevices = midiOutGetNumDevs(); + if ( nDevices < 1 ) { + errorString_ = "MidiOutWinMM::openPort: no MIDI output destinations found!"; + error( RtMidiError::NO_DEVICES_FOUND, errorString_ ); + return; + } + + if ( portNumber >= nDevices ) { + std::ostringstream ost; + ost << "MidiOutWinMM::openPort: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( RtMidiError::INVALID_PARAMETER, errorString_ ); + return; + } + + WinMidiData *data = static_cast<WinMidiData *> (apiData_); + MMRESULT result = midiOutOpen( &data->outHandle, + portNumber, + (DWORD)NULL, + (DWORD)NULL, + CALLBACK_NULL ); + if ( result != MMSYSERR_NOERROR ) { + errorString_ = "MidiOutWinMM::openPort: error creating Windows MM MIDI output port."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + + connected_ = true; +} + +void MidiOutWinMM :: closePort( void ) +{ + if ( connected_ ) { + WinMidiData *data = static_cast<WinMidiData *> (apiData_); + // Disabled because midiOutReset triggers 0x7b (if any note was ON) and 0x79 "Reset All + // Controllers" (to all 16 channels) CC messages which is undesirable (see issue #222) + // midiOutReset( data->outHandle ); + + midiOutClose( data->outHandle ); + data->outHandle = 0; + connected_ = false; + } +} + +void MidiOutWinMM :: setClientName ( const std::string& ) +{ + + errorString_ = "MidiOutWinMM::setClientName: this function is not implemented for the WINDOWS_MM API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + +void MidiOutWinMM :: setPortName ( const std::string& ) +{ + + errorString_ = "MidiOutWinMM::setPortName: this function is not implemented for the WINDOWS_MM API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + +void MidiOutWinMM :: openVirtualPort( const std::string &/*portName*/ ) +{ + // This function cannot be implemented for the Windows MM MIDI API. + errorString_ = "MidiOutWinMM::openVirtualPort: cannot be implemented in Windows MM MIDI API!"; + error( RtMidiError::WARNING, errorString_ ); +} + +void MidiOutWinMM :: sendMessage( const unsigned char *message, size_t size ) +{ + if ( !connected_ ) return; + + unsigned int nBytes = static_cast<unsigned int>(size); + if ( nBytes == 0 ) { + errorString_ = "MidiOutWinMM::sendMessage: message argument is empty!"; + error( RtMidiError::WARNING, errorString_ ); + return; + } + + MMRESULT result; + WinMidiData *data = static_cast<WinMidiData *> (apiData_); + if ( message[0] == 0xF0 ) { // Sysex message + + // Allocate buffer for sysex data. + char *buffer = (char *) malloc( nBytes ); + if ( buffer == NULL ) { + errorString_ = "MidiOutWinMM::sendMessage: error allocating sysex message memory!"; + error( RtMidiError::MEMORY_ERROR, errorString_ ); + return; + } + + // Copy data to buffer. + for ( unsigned int i=0; i<nBytes; ++i ) buffer[i] = message[i]; + + // Create and prepare MIDIHDR structure. + MIDIHDR sysex; + sysex.lpData = (LPSTR) buffer; + sysex.dwBufferLength = nBytes; + sysex.dwFlags = 0; + result = midiOutPrepareHeader( data->outHandle, &sysex, sizeof( MIDIHDR ) ); + if ( result != MMSYSERR_NOERROR ) { + free( buffer ); + errorString_ = "MidiOutWinMM::sendMessage: error preparing sysex header."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + + // Send the message. + result = midiOutLongMsg( data->outHandle, &sysex, sizeof( MIDIHDR ) ); + if ( result != MMSYSERR_NOERROR ) { + free( buffer ); + errorString_ = "MidiOutWinMM::sendMessage: error sending sysex message."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + + // Unprepare the buffer and MIDIHDR. + while ( MIDIERR_STILLPLAYING == midiOutUnprepareHeader( data->outHandle, &sysex, sizeof ( MIDIHDR ) ) ) Sleep( 1 ); + free( buffer ); + } + else { // Channel or system message. + + // Make sure the message size isn't too big. + if ( nBytes > 3 ) { + errorString_ = "MidiOutWinMM::sendMessage: message size is greater than 3 bytes (and not sysex)!"; + error( RtMidiError::WARNING, errorString_ ); + return; + } + + // Pack MIDI bytes into double word. + DWORD packet; + unsigned char *ptr = (unsigned char *) &packet; + for ( unsigned int i=0; i<nBytes; ++i ) { + *ptr = message[i]; + ++ptr; + } + + // Send the message immediately. + result = midiOutShortMsg( data->outHandle, packet ); + if ( result != MMSYSERR_NOERROR ) { + errorString_ = "MidiOutWinMM::sendMessage: error sending MIDI message."; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + } + } +} + +#endif // __WINDOWS_MM__ + + +//*********************************************************************// +// API: UNIX JACK +// +// Written primarily by Alexander Svetalkin, with updates for delta +// time by Gary Scavone, April 2011. +// +// *********************************************************************// + +#if defined(__UNIX_JACK__) + +// JACK header files +#include <jack/jack.h> +#include <jack/midiport.h> +#include <jack/ringbuffer.h> +#include <pthread.h> +#include <sched.h> +#ifdef HAVE_SEMAPHORE + #include <semaphore.h> +#endif + +#define JACK_RINGBUFFER_SIZE 16384 // Default size for ringbuffer + +struct JackMidiData { + jack_client_t *client; + jack_port_t *port; + jack_ringbuffer_t *buff; + int buffMaxWrite; // actual writable size, usually 1 less than ringbuffer + jack_time_t lastTime; +#ifdef HAVE_SEMAPHORE + sem_t sem_cleanup; + sem_t sem_needpost; +#endif + MidiInApi :: RtMidiInData *rtMidiIn; + }; + +//*********************************************************************// +// API: JACK +// Class Definitions: MidiInJack +//*********************************************************************// + +static int jackProcessIn( jack_nframes_t nframes, void *arg ) +{ + JackMidiData *jData = (JackMidiData *) arg; + MidiInApi :: RtMidiInData *rtData = jData->rtMidiIn; + jack_midi_event_t event; + jack_time_t time; + + // Is port created? + if ( jData->port == NULL ) return 0; + + void *buff = jack_port_get_buffer( jData->port, nframes ); + bool& continueSysex = rtData->continueSysex; + unsigned char& ignoreFlags = rtData->ignoreFlags; + + // We have midi events in buffer + int evCount = jack_midi_get_event_count( buff ); + for (int j = 0; j < evCount; j++) { + MidiInApi::MidiMessage& message = rtData->message; + jack_midi_event_get( &event, buff, j ); + + // Compute the delta time. + time = jack_get_time(); + if ( rtData->firstMessage == true ) { + message.timeStamp = 0.0; + rtData->firstMessage = false; + } else + message.timeStamp = ( time - jData->lastTime ) * 0.000001; + + jData->lastTime = time; + + if ( !continueSysex ) + message.bytes.clear(); + + if ( !( ( continueSysex || event.buffer[0] == 0xF0 ) && ( ignoreFlags & 0x01 ) ) ) { + // Unless this is a (possibly continued) SysEx message and we're ignoring SysEx, + // copy the event buffer into the MIDI message struct. + for ( unsigned int i = 0; i < event.size; i++ ) + message.bytes.push_back( event.buffer[i] ); + } + + switch ( event.buffer[0] ) { + case 0xF0: + // Start of a SysEx message + continueSysex = event.buffer[event.size - 1] != 0xF7; + if ( ignoreFlags & 0x01 ) continue; + break; + case 0xF1: + case 0xF8: + // MIDI Time Code or Timing Clock message + if ( ignoreFlags & 0x02 ) continue; + break; + case 0xFE: + // Active Sensing message + if ( ignoreFlags & 0x04 ) continue; + break; + default: + if ( continueSysex ) { + // Continuation of a SysEx message + continueSysex = event.buffer[event.size - 1] != 0xF7; + if ( ignoreFlags & 0x01 ) continue; + } + // All other MIDI messages + } + + if ( !continueSysex ) { + // If not a continuation of a SysEx message, + // invoke the user callback function or queue the message. + if ( rtData->usingCallback ) { + RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) rtData->userCallback; + callback( message.timeStamp, &message.bytes, rtData->userData ); + } + else { + // As long as we haven't reached our queue size limit, push the message. + if ( !rtData->queue.push( message ) ) + std::cerr << "\nMidiInJack: message queue limit reached!!\n\n"; + } + } + } + + return 0; +} + +MidiInJack :: MidiInJack( const std::string &clientName, unsigned int queueSizeLimit ) + : MidiInApi( queueSizeLimit ) +{ + MidiInJack::initialize( clientName ); +} + +void MidiInJack :: initialize( const std::string& clientName ) +{ + JackMidiData *data = new JackMidiData; + apiData_ = (void *) data; + + data->rtMidiIn = &inputData_; + data->port = NULL; + data->client = NULL; + this->clientName = clientName; + + connect(); +} + +void MidiInJack :: connect() +{ + JackMidiData *data = static_cast<JackMidiData *> (apiData_); + if ( data->client ) + return; + + // Initialize JACK client + if (( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL )) == 0) { + errorString_ = "MidiInJack::initialize: JACK server not running?"; + error( RtMidiError::WARNING, errorString_ ); + return; + } + + jack_set_process_callback( data->client, jackProcessIn, data ); + jack_activate( data->client ); +} + +MidiInJack :: ~MidiInJack() +{ + JackMidiData *data = static_cast<JackMidiData *> (apiData_); + MidiInJack::closePort(); + + if ( data->client ) + jack_client_close( data->client ); + delete data; +} + +void MidiInJack :: openPort( unsigned int portNumber, const std::string &portName ) +{ + JackMidiData *data = static_cast<JackMidiData *> (apiData_); + + connect(); + + // Creating new port + if ( data->port == NULL ) + data->port = jack_port_register( data->client, portName.c_str(), + JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 ); + + if ( data->port == NULL ) { + errorString_ = "MidiInJack::openPort: JACK error creating port"; + if (portName.size() >= (size_t)jack_port_name_size()) + errorString_ += " (port name too long?)"; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + + // Connecting to the output + std::string name = getPortName( portNumber ); + jack_connect( data->client, name.c_str(), jack_port_name( data->port ) ); + + connected_ = true; +} + +void MidiInJack :: openVirtualPort( const std::string &portName ) +{ + JackMidiData *data = static_cast<JackMidiData *> (apiData_); + + connect(); + if ( data->port == NULL ) + data->port = jack_port_register( data->client, portName.c_str(), + JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0 ); + + if ( data->port == NULL ) { + errorString_ = "MidiInJack::openVirtualPort: JACK error creating virtual port"; + if (portName.size() >= (size_t)jack_port_name_size()) + errorString_ += " (port name too long?)"; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + } +} + +unsigned int MidiInJack :: getPortCount() +{ + int count = 0; + JackMidiData *data = static_cast<JackMidiData *> (apiData_); + connect(); + if ( !data->client ) + return 0; + + // List of available ports + const char **ports = jack_get_ports( data->client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput ); + + if ( ports == NULL ) return 0; + while ( ports[count] != NULL ) + count++; + + free( ports ); + + return count; +} + +std::string MidiInJack :: getPortName( unsigned int portNumber ) +{ + JackMidiData *data = static_cast<JackMidiData *> (apiData_); + std::string retStr( "" ); + + connect(); + + // List of available ports + const char **ports = jack_get_ports( data->client, NULL, + JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput ); + + // Check port validity + if ( ports == NULL ) { + errorString_ = "MidiInJack::getPortName: no ports available!"; + error( RtMidiError::WARNING, errorString_ ); + return retStr; + } + + unsigned int i; + for ( i=0; i<portNumber && ports[i]; i++ ) {} + if ( i < portNumber || !ports[portNumber] ) { + std::ostringstream ost; + ost << "MidiInJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( RtMidiError::WARNING, errorString_ ); + } + else retStr.assign( ports[portNumber] ); + + jack_free( ports ); + return retStr; +} + +void MidiInJack :: closePort() +{ + JackMidiData *data = static_cast<JackMidiData *> (apiData_); + + if ( data->port == NULL ) return; + jack_port_unregister( data->client, data->port ); + data->port = NULL; + + connected_ = false; +} + +void MidiInJack:: setClientName( const std::string& ) +{ + + errorString_ = "MidiInJack::setClientName: this function is not implemented for the UNIX_JACK API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + +void MidiInJack :: setPortName( const std::string &portName ) +{ + JackMidiData *data = static_cast<JackMidiData *> (apiData_); +#ifdef JACK_HAS_PORT_RENAME + jack_port_rename( data->client, data->port, portName.c_str() ); +#else + jack_port_set_name( data->port, portName.c_str() ); +#endif +} + +//*********************************************************************// +// API: JACK +// Class Definitions: MidiOutJack +//*********************************************************************// + +// Jack process callback +static int jackProcessOut( jack_nframes_t nframes, void *arg ) +{ + JackMidiData *data = (JackMidiData *) arg; + jack_midi_data_t *midiData; + int space; + + // Is port created? + if ( data->port == NULL ) return 0; + + void *buff = jack_port_get_buffer( data->port, nframes ); + jack_midi_clear_buffer( buff ); + + while ( jack_ringbuffer_peek( data->buff, (char *) &space, sizeof( space ) ) == sizeof(space) && + jack_ringbuffer_read_space( data->buff ) >= sizeof(space) + space ) { + jack_ringbuffer_read_advance( data->buff, sizeof(space) ); + + midiData = jack_midi_event_reserve( buff, 0, space ); + if ( midiData ) + jack_ringbuffer_read( data->buff, (char *) midiData, (size_t) space ); + else + jack_ringbuffer_read_advance( data->buff, (size_t) space ); + } + +#ifdef HAVE_SEMAPHORE + if ( !sem_trywait( &data->sem_needpost ) ) + sem_post( &data->sem_cleanup ); +#endif + + return 0; +} + +MidiOutJack :: MidiOutJack( const std::string &clientName ) : MidiOutApi() +{ + MidiOutJack::initialize( clientName ); +} + +void MidiOutJack :: initialize( const std::string& clientName ) +{ + JackMidiData *data = new JackMidiData; + apiData_ = (void *) data; + + data->port = NULL; + data->client = NULL; +#ifdef HAVE_SEMAPHORE + sem_init( &data->sem_cleanup, 0, 0 ); + sem_init( &data->sem_needpost, 0, 0 ); +#endif + this->clientName = clientName; + + connect(); +} + +void MidiOutJack :: connect() +{ + JackMidiData *data = static_cast<JackMidiData *> (apiData_); + if ( data->client ) + return; + + // Initialize output ringbuffers + data->buff = jack_ringbuffer_create( JACK_RINGBUFFER_SIZE ); + data->buffMaxWrite = (int) jack_ringbuffer_write_space( data->buff ); + + // Initialize JACK client + if ( ( data->client = jack_client_open( clientName.c_str(), JackNoStartServer, NULL ) ) == 0 ) { + errorString_ = "MidiOutJack::initialize: JACK server not running?"; + error( RtMidiError::WARNING, errorString_ ); + return; + } + + jack_set_process_callback( data->client, jackProcessOut, data ); + jack_activate( data->client ); +} + +MidiOutJack :: ~MidiOutJack() +{ + JackMidiData *data = static_cast<JackMidiData *> (apiData_); + MidiOutJack::closePort(); + + // Cleanup + jack_ringbuffer_free( data->buff ); + if ( data->client ) { + jack_client_close( data->client ); + } + +#ifdef HAVE_SEMAPHORE + sem_destroy( &data->sem_cleanup ); + sem_destroy( &data->sem_needpost ); +#endif + + delete data; +} + +void MidiOutJack :: openPort( unsigned int portNumber, const std::string &portName ) +{ + JackMidiData *data = static_cast<JackMidiData *> (apiData_); + + connect(); + + // Creating new port + if ( data->port == NULL ) + data->port = jack_port_register( data->client, portName.c_str(), + JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 ); + + if ( data->port == NULL ) { + errorString_ = "MidiOutJack::openPort: JACK error creating port"; + if (portName.size() >= (size_t)jack_port_name_size()) + errorString_ += " (port name too long?)"; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + return; + } + + // Connecting to the output + std::string name = getPortName( portNumber ); + jack_connect( data->client, jack_port_name( data->port ), name.c_str() ); + + connected_ = true; +} + +void MidiOutJack :: openVirtualPort( const std::string &portName ) +{ + JackMidiData *data = static_cast<JackMidiData *> (apiData_); + + connect(); + if ( data->port == NULL ) + data->port = jack_port_register( data->client, portName.c_str(), + JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0 ); + + if ( data->port == NULL ) { + errorString_ = "MidiOutJack::openVirtualPort: JACK error creating virtual port"; + if (portName.size() >= (size_t)jack_port_name_size()) + errorString_ += " (port name too long?)"; + error( RtMidiError::DRIVER_ERROR, errorString_ ); + } +} + +unsigned int MidiOutJack :: getPortCount() +{ + int count = 0; + JackMidiData *data = static_cast<JackMidiData *> (apiData_); + connect(); + if ( !data->client ) + return 0; + + // List of available ports + const char **ports = jack_get_ports( data->client, NULL, + JACK_DEFAULT_MIDI_TYPE, JackPortIsInput ); + + if ( ports == NULL ) return 0; + while ( ports[count] != NULL ) + count++; + + free( ports ); + + return count; +} + +std::string MidiOutJack :: getPortName( unsigned int portNumber ) +{ + JackMidiData *data = static_cast<JackMidiData *> (apiData_); + std::string retStr(""); + + connect(); + + // List of available ports + const char **ports = jack_get_ports( data->client, NULL, + JACK_DEFAULT_MIDI_TYPE, JackPortIsInput ); + + // Check port validity + if ( ports == NULL ) { + errorString_ = "MidiOutJack::getPortName: no ports available!"; + error( RtMidiError::WARNING, errorString_ ); + return retStr; + } + + if ( ports[portNumber] == NULL ) { + std::ostringstream ost; + ost << "MidiOutJack::getPortName: the 'portNumber' argument (" << portNumber << ") is invalid."; + errorString_ = ost.str(); + error( RtMidiError::WARNING, errorString_ ); + } + else retStr.assign( ports[portNumber] ); + + free( ports ); + return retStr; +} + +void MidiOutJack :: closePort() +{ + JackMidiData *data = static_cast<JackMidiData *> (apiData_); + + if ( data->port == NULL ) return; + +#ifdef HAVE_SEMAPHORE + struct timespec ts; + if ( clock_gettime( CLOCK_REALTIME, &ts ) != -1 ) { + ts.tv_sec += 1; // wait max one second + sem_post( &data->sem_needpost ); + sem_timedwait( &data->sem_cleanup, &ts ); + } +#endif + + jack_port_unregister( data->client, data->port ); + data->port = NULL; + + connected_ = false; +} + +void MidiOutJack:: setClientName( const std::string& ) +{ + + errorString_ = "MidiOutJack::setClientName: this function is not implemented for the UNIX_JACK API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + +void MidiOutJack :: setPortName( const std::string &portName ) +{ + JackMidiData *data = static_cast<JackMidiData *> (apiData_); +#ifdef JACK_HAS_PORT_RENAME + jack_port_rename( data->client, data->port, portName.c_str() ); +#else + jack_port_set_name( data->port, portName.c_str() ); +#endif +} + +void MidiOutJack :: sendMessage( const unsigned char *message, size_t size ) +{ + int nBytes = static_cast<int>(size); + JackMidiData *data = static_cast<JackMidiData *> (apiData_); + + if ( size + sizeof(nBytes) > (size_t) data->buffMaxWrite ) + return; + + while ( jack_ringbuffer_write_space(data->buff) < sizeof(nBytes) + size ) + sched_yield(); + + // Write full message to buffer + jack_ringbuffer_write( data->buff, ( char * ) &nBytes, sizeof( nBytes ) ); + jack_ringbuffer_write( data->buff, ( const char * ) message, nBytes ); +} + +#endif // __UNIX_JACK__ + +//*********************************************************************// +// API: Web MIDI +// +// Written primarily by Atsushi Eno, February 2020. +// +// *********************************************************************// + +#if defined(__WEB_MIDI_API__) + +#include <emscripten.h> + +//*********************************************************************// +// API: WEB MIDI +// Class Definitions: WebMidiAccessShim +//*********************************************************************// + +class WebMidiAccessShim +{ +public: + WebMidiAccessShim(); + ~WebMidiAccessShim(); + std::string getPortName( unsigned int portNumber, bool isInput ); +}; + +std::unique_ptr<WebMidiAccessShim> shim{nullptr}; + +void ensureShim() +{ + if ( shim.get() != nullptr ) + return; + shim.reset( new WebMidiAccessShim() ); +} + +bool checkWebMidiAvailability() +{ + ensureShim(); + + return MAIN_THREAD_EM_ASM_INT( { + if ( typeof window._rtmidi_internals_waiting === "undefined" ) { + console.log ( "Attempted to use Web MIDI API without trying to open it." ); + return false; + } + if ( window._rtmidi_internals_waiting ) { + console.log ( "Attempted to use Web MIDI API while it is being queried." ); + return false; + } + if ( _rtmidi_internals_midi_access == null ) { + console.log ( "Attempted to use Web MIDI API while it already turned out to be unavailable." ); + return false; + } + return true; + } ); +} + +WebMidiAccessShim::WebMidiAccessShim() +{ + MAIN_THREAD_ASYNC_EM_ASM( { + if( typeof window._rtmidi_internals_midi_access !== "undefined" ) + return; + if( typeof window._rtmidi_internals_waiting !== "undefined" ) { + console.log( "MIDI Access was requested while another request is in progress." ); + return; + } + + // define functions + window._rtmidi_internals_get_port_by_number = function( portNumber, isInput ) { + var midi = window._rtmidi_internals_midi_access; + var devices = isInput ? midi.inputs : midi.outputs; + var i = 0; + for (var device of devices.values()) { + if ( i == portNumber ) + return device; + i++; + } + console.log( "MIDI " + (isInput ? "input" : "output") + " device of portNumber " + portNumber + " is not found."); + return null; + }; + + window._rtmidi_internals_waiting = true; + window.navigator.requestMIDIAccess( {"sysex": true} ).then( (midiAccess) => { + window._rtmidi_internals_midi_access = midiAccess; + window._rtmidi_internals_latest_message_timestamp = 0.0; + window._rtmidi_internals_waiting = false; + if( midiAccess == null ) { + console.log ( "Could not get access to MIDI API" ); + } + } ); + } ); +} + +WebMidiAccessShim::~WebMidiAccessShim() +{ +} + +std::string WebMidiAccessShim::getPortName( unsigned int portNumber, bool isInput ) +{ + if( !checkWebMidiAvailability() ) + return ""; + char *ret = (char*) MAIN_THREAD_EM_ASM_INT( { + var port = window._rtmidi_internals_get_port_by_number($0, $1); + if( port == null) + return null; + var length = lengthBytesUTF8(port.name) + 1; + var ret = _malloc(length); + stringToUTF8(port.name, ret, length); + return ret; + }, portNumber, isInput); + if (ret == nullptr) + return ""; + std::string s = ret; + free(ret); + return s; +} + +//*********************************************************************// +// API: WEB MIDI +// Class Definitions: MidiInWeb +//*********************************************************************// + +MidiInWeb::MidiInWeb( const std::string &clientName, unsigned int queueSizeLimit ) + : MidiInApi( queueSizeLimit ) +{ + initialize( clientName ); +} + +MidiInWeb::~MidiInWeb( void ) +{ + closePort(); +} + +extern "C" void EMSCRIPTEN_KEEPALIVE rtmidi_onMidiMessageProc( MidiInApi::RtMidiInData* data, uint8_t* inputBytes, int32_t length, double domHighResTimeStamp ) +{ + auto &message = data->message; + message.bytes.resize(message.bytes.size() + length); + memcpy(message.bytes.data(), inputBytes, length); + // FIXME: handle timestamp + if ( data->usingCallback ) { + RtMidiIn::RtMidiCallback callback = (RtMidiIn::RtMidiCallback) data->userCallback; + callback( message.timeStamp, &message.bytes, data->userData ); + } +} + +void MidiInWeb::openPort( unsigned int portNumber, const std::string &portName ) +{ + if( !checkWebMidiAvailability() ) + return; + if (open_port_number >= 0) + return; + + MAIN_THREAD_EM_ASM( { + // In Web MIDI API world, there is no step to open a port, but we have to register the input callback instead. + var input = window._rtmidi_internals_get_port_by_number($0, true); + input.onmidimessage = function(e) { + // In RtMidi world, timestamps are delta time from previous message, while in Web MIDI world + // timestamps are relative to window creation time (i.e. kind of absolute time with window "epoch" time). + var rtmidiTimestamp = window._rtmidi_internals_latest_message_timestamp == 0.0 ? 0.0 : e.timeStamp - window._rtmidi_internals_latest_message_timestamp; + window._rtmidi_internals_latest_message_timestamp = e.timeStamp; + Module.ccall( 'rtmidi_onMidiMessageProc', 'void', ['number', 'array', 'number', 'number'], [$1, e.data, e.data.length, rtmidiTimestamp] ); + }; + }, portNumber, &inputData_ ); + open_port_number = portNumber; +} + +void MidiInWeb::openVirtualPort( const std::string &portName ) +{ + + errorString_ = "MidiInWeb::openVirtualPort: this function is not implemented for the Web MIDI API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + +void MidiInWeb::closePort( void ) +{ + if( open_port_number < 0 ) + return; + + MAIN_THREAD_EM_ASM( { + var input = _rtmidi_internals_get_port_by_number($0, true); + if( input == null ) { + console.log( "Port #" + $0 + " could not be found."); + return; + } + // unregister event handler + input.onmidimessage = null; + }, open_port_number ); + open_port_number = -1; +} + +void MidiInWeb::setClientName( const std::string &clientName ) +{ + client_name = clientName; +} + +void MidiInWeb::setPortName( const std::string &portName ) +{ + + errorString_ = "MidiInWeb::setPortName: this function is not implemented for the Web MIDI API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + +unsigned int MidiInWeb::getPortCount( void ) +{ + if( !checkWebMidiAvailability() ) + return 0; + return MAIN_THREAD_EM_ASM_INT( { return _rtmidi_internals_midi_access.inputs.size; } ); +} + +std::string MidiInWeb::getPortName( unsigned int portNumber ) +{ + if( !checkWebMidiAvailability() ) + return ""; + return shim->getPortName( portNumber, true ); +} + +void MidiInWeb::initialize( const std::string& clientName ) +{ + ensureShim(); + setClientName( clientName ); +} + +//*********************************************************************// +// API: WEB MIDI +// Class Definitions: MidiOutWeb +//*********************************************************************// + +MidiOutWeb::MidiOutWeb( const std::string &clientName ) +{ + initialize( clientName ); +} + +MidiOutWeb::~MidiOutWeb( void ) +{ + closePort(); +} + +void MidiOutWeb::openPort( unsigned int portNumber, const std::string &portName ) +{ + if( !checkWebMidiAvailability() ) + return; + if (open_port_number >= 0) + return; + // In Web MIDI API world, there is no step to open a port. + + open_port_number = portNumber; +} + +void MidiOutWeb::openVirtualPort( const std::string &portName ) +{ + + errorString_ = "MidiOutWeb::openVirtualPort: this function is not implemented for the Web MIDI API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + +void MidiOutWeb::closePort( void ) +{ + // there is really nothing to do for output at JS side. + open_port_number = -1; +} + +void MidiOutWeb::setClientName( const std::string &clientName ) +{ + client_name = clientName; +} + +void MidiOutWeb::setPortName( const std::string &portName ) +{ + + errorString_ = "MidiOutWeb::setPortName: this function is not implemented for the Web MIDI API!"; + error( RtMidiError::WARNING, errorString_ ); + +} + +unsigned int MidiOutWeb::getPortCount( void ) +{ + if( !checkWebMidiAvailability() ) + return 0; + return MAIN_THREAD_EM_ASM_INT( { return _rtmidi_internals_midi_access.outputs.size; } ); +} + +std::string MidiOutWeb::getPortName( unsigned int portNumber ) +{ + if( !checkWebMidiAvailability() ) + return ""; + return shim->getPortName( portNumber, false ); +} + +void MidiOutWeb::sendMessage( const unsigned char *message, size_t size ) +{ + if( open_port_number < 0 ) + return; + + MAIN_THREAD_EM_ASM( { + var output = _rtmidi_internals_get_port_by_number( $0, false ); + if( output == null ) { + console.log( "Port #" + $0 + " could not be found."); + return; + } + var buf = new ArrayBuffer ($2); + var msg = new Uint8Array( buf ); + msg.set( new Uint8Array( Module.HEAPU8.buffer.slice( $1, $1 + $2 ) ) ); + output.send( msg ); + }, open_port_number, message, size ); +} + +void MidiOutWeb::initialize( const std::string& clientName ) +{ + if ( shim.get() != nullptr ) + return; + shim.reset( new WebMidiAccessShim() ); + setClientName( clientName ); +} + +#endif // __WEB_MIDI_API__ diff --git a/distrho/src/jackbridge/rtmidi/RtMidi.h b/distrho/src/jackbridge/rtmidi/RtMidi.h @@ -0,0 +1,658 @@ +/**********************************************************************/ +/*! \class RtMidi + \brief An abstract base class for realtime MIDI input/output. + + This class implements some common functionality for the realtime + MIDI input/output subclasses RtMidiIn and RtMidiOut. + + RtMidi GitHub site: https://github.com/thestk/rtmidi + RtMidi WWW site: http://www.music.mcgill.ca/~gary/rtmidi/ + + RtMidi: realtime MIDI i/o C++ classes + Copyright (c) 2003-2021 Gary P. Scavone + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + asked to send the modifications to the original developer so that + they can be incorporated into the canonical version. This is, + however, not a binding provision of this license. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +/**********************************************************************/ + +/*! + \file RtMidi.h + */ + +#ifndef RTMIDI_H +#define RTMIDI_H + +#if defined _WIN32 || defined __CYGWIN__ + #if defined(RTMIDI_EXPORT) + #define RTMIDI_DLL_PUBLIC __declspec(dllexport) + #else + #define RTMIDI_DLL_PUBLIC + #endif +#else + #if __GNUC__ >= 4 + #define RTMIDI_DLL_PUBLIC __attribute__( (visibility( "default" )) ) + #else + #define RTMIDI_DLL_PUBLIC + #endif +#endif + +#define RTMIDI_VERSION "5.0.0" + +#include <exception> +#include <iostream> +#include <string> +#include <vector> + + +/************************************************************************/ +/*! \class RtMidiError + \brief Exception handling class for RtMidi. + + The RtMidiError class is quite simple but it does allow errors to be + "caught" by RtMidiError::Type. See the RtMidi documentation to know + which methods can throw an RtMidiError. +*/ +/************************************************************************/ + +class RTMIDI_DLL_PUBLIC RtMidiError : public std::exception +{ + public: + //! Defined RtMidiError types. + enum Type { + WARNING, /*!< A non-critical error. */ + DEBUG_WARNING, /*!< A non-critical error which might be useful for debugging. */ + UNSPECIFIED, /*!< The default, unspecified error type. */ + NO_DEVICES_FOUND, /*!< No devices found on system. */ + INVALID_DEVICE, /*!< An invalid device ID was specified. */ + MEMORY_ERROR, /*!< An error occured during memory allocation. */ + INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */ + INVALID_USE, /*!< The function was called incorrectly. */ + DRIVER_ERROR, /*!< A system driver error occured. */ + SYSTEM_ERROR, /*!< A system error occured. */ + THREAD_ERROR /*!< A thread error occured. */ + }; + + //! The constructor. + RtMidiError( const std::string& message, Type type = RtMidiError::UNSPECIFIED ) throw() + : message_(message), type_(type) {} + + //! The destructor. + virtual ~RtMidiError( void ) throw() {} + + //! Prints thrown error message to stderr. + virtual void printMessage( void ) const throw() { std::cerr << '\n' << message_ << "\n\n"; } + + //! Returns the thrown error message type. + virtual const Type& getType( void ) const throw() { return type_; } + + //! Returns the thrown error message string. + virtual const std::string& getMessage( void ) const throw() { return message_; } + + //! Returns the thrown error message as a c-style string. + virtual const char* what( void ) const throw() { return message_.c_str(); } + + protected: + std::string message_; + Type type_; +}; + +//! RtMidi error callback function prototype. +/*! + \param type Type of error. + \param errorText Error description. + + Note that class behaviour is undefined after a critical error (not + a warning) is reported. + */ +typedef void (*RtMidiErrorCallback)( RtMidiError::Type type, const std::string &errorText, void *userData ); + +class MidiApi; + +class RTMIDI_DLL_PUBLIC RtMidi +{ + public: + + RtMidi(RtMidi&& other) noexcept; + //! MIDI API specifier arguments. + enum Api { + UNSPECIFIED, /*!< Search for a working compiled API. */ + MACOSX_CORE, /*!< Macintosh OS-X CoreMIDI API. */ + LINUX_ALSA, /*!< The Advanced Linux Sound Architecture API. */ + UNIX_JACK, /*!< The JACK Low-Latency MIDI Server API. */ + WINDOWS_MM, /*!< The Microsoft Multimedia MIDI API. */ + RTMIDI_DUMMY, /*!< A compilable but non-functional API. */ + WEB_MIDI_API, /*!< W3C Web MIDI API. */ + NUM_APIS /*!< Number of values in this enum. */ + }; + + //! A static function to determine the current RtMidi version. + static std::string getVersion( void ) throw(); + + //! A static function to determine the available compiled MIDI APIs. + /*! + The values returned in the std::vector can be compared against + the enumerated list values. Note that there can be more than one + API compiled for certain operating systems. + */ + static void getCompiledApi( std::vector<RtMidi::Api> &apis ) throw(); + + //! Return the name of a specified compiled MIDI API. + /*! + This obtains a short lower-case name used for identification purposes. + This value is guaranteed to remain identical across library versions. + If the API is unknown, this function will return the empty string. + */ + static std::string getApiName( RtMidi::Api api ); + + //! Return the display name of a specified compiled MIDI API. + /*! + This obtains a long name used for display purposes. + If the API is unknown, this function will return the empty string. + */ + static std::string getApiDisplayName( RtMidi::Api api ); + + //! Return the compiled MIDI API having the given name. + /*! + A case insensitive comparison will check the specified name + against the list of compiled APIs, and return the one which + matches. On failure, the function returns UNSPECIFIED. + */ + static RtMidi::Api getCompiledApiByName( const std::string &name ); + + //! Pure virtual openPort() function. + virtual void openPort( unsigned int portNumber = 0, const std::string &portName = std::string( "RtMidi" ) ) = 0; + + //! Pure virtual openVirtualPort() function. + virtual void openVirtualPort( const std::string &portName = std::string( "RtMidi" ) ) = 0; + + //! Pure virtual getPortCount() function. + virtual unsigned int getPortCount() = 0; + + //! Pure virtual getPortName() function. + virtual std::string getPortName( unsigned int portNumber = 0 ) = 0; + + //! Pure virtual closePort() function. + virtual void closePort( void ) = 0; + + void setClientName( const std::string &clientName ); + void setPortName( const std::string &portName ); + + //! Returns true if a port is open and false if not. + /*! + Note that this only applies to connections made with the openPort() + function, not to virtual ports. + */ + virtual bool isPortOpen( void ) const = 0; + + //! Set an error callback function to be invoked when an error has occured. + /*! + The callback function will be called whenever an error has occured. It is best + to set the error callback function before opening a port. + */ + virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ) = 0; + + protected: + RtMidi(); + virtual ~RtMidi(); + MidiApi *rtapi_; + + /* Make the class non-copyable */ + RtMidi(RtMidi& other) = delete; + RtMidi& operator=(RtMidi& other) = delete; +}; + +/**********************************************************************/ +/*! \class RtMidiIn + \brief A realtime MIDI input class. + + This class provides a common, platform-independent API for + realtime MIDI input. It allows access to a single MIDI input + port. Incoming MIDI messages are either saved to a queue for + retrieval using the getMessage() function or immediately passed to + a user-specified callback function. Create multiple instances of + this class to connect to more than one MIDI device at the same + time. With the OS-X, Linux ALSA, and JACK MIDI APIs, it is also + possible to open a virtual input port to which other MIDI software + clients can connect. +*/ +/**********************************************************************/ + +// **************************************************************** // +// +// RtMidiIn and RtMidiOut class declarations. +// +// RtMidiIn / RtMidiOut are "controllers" used to select an available +// MIDI input or output interface. They present common APIs for the +// user to call but all functionality is implemented by the classes +// MidiInApi, MidiOutApi and their subclasses. RtMidiIn and RtMidiOut +// each create an instance of a MidiInApi or MidiOutApi subclass based +// on the user's API choice. If no choice is made, they attempt to +// make a "logical" API selection. +// +// **************************************************************** // + +class RTMIDI_DLL_PUBLIC RtMidiIn : public RtMidi +{ + public: + //! User callback function type definition. + typedef void (*RtMidiCallback)( double timeStamp, std::vector<unsigned char> *message, void *userData ); + + //! Default constructor that allows an optional api, client name and queue size. + /*! + An exception will be thrown if a MIDI system initialization + error occurs. The queue size defines the maximum number of + messages that can be held in the MIDI queue (when not using a + callback function). If the queue size limit is reached, + incoming messages will be ignored. + + If no API argument is specified and multiple API support has been + compiled, the default order of use is ALSA, JACK (Linux) and CORE, + JACK (OS-X). + + \param api An optional API id can be specified. + \param clientName An optional client name can be specified. This + will be used to group the ports that are created + by the application. + \param queueSizeLimit An optional size of the MIDI input queue can be specified. + */ + RtMidiIn( RtMidi::Api api=UNSPECIFIED, + const std::string& clientName = "RtMidi Input Client", + unsigned int queueSizeLimit = 100 ); + + RtMidiIn(RtMidiIn&& other) noexcept : RtMidi(std::move(other)) { } + + //! If a MIDI connection is still open, it will be closed by the destructor. + ~RtMidiIn ( void ) throw(); + + //! Returns the MIDI API specifier for the current instance of RtMidiIn. + RtMidi::Api getCurrentApi( void ) throw(); + + //! Open a MIDI input connection given by enumeration number. + /*! + \param portNumber An optional port number greater than 0 can be specified. + Otherwise, the default or first port found is opened. + \param portName An optional name for the application port that is used to connect to portId can be specified. + */ + void openPort( unsigned int portNumber = 0, const std::string &portName = std::string( "RtMidi Input" ) ); + + //! Create a virtual input port, with optional name, to allow software connections (OS X, JACK and ALSA only). + /*! + This function creates a virtual MIDI input port to which other + software applications can connect. This type of functionality + is currently only supported by the Macintosh OS-X, any JACK, + and Linux ALSA APIs (the function returns an error for the other APIs). + + \param portName An optional name for the application port that is + used to connect to portId can be specified. + */ + void openVirtualPort( const std::string &portName = std::string( "RtMidi Input" ) ); + + //! Set a callback function to be invoked for incoming MIDI messages. + /*! + The callback function will be called whenever an incoming MIDI + message is received. While not absolutely necessary, it is best + to set the callback function before opening a MIDI port to avoid + leaving some messages in the queue. + + \param callback A callback function must be given. + \param userData Optionally, a pointer to additional data can be + passed to the callback function whenever it is called. + */ + void setCallback( RtMidiCallback callback, void *userData = 0 ); + + //! Cancel use of the current callback function (if one exists). + /*! + Subsequent incoming MIDI messages will be written to the queue + and can be retrieved with the \e getMessage function. + */ + void cancelCallback(); + + //! Close an open MIDI connection (if one exists). + void closePort( void ); + + //! Returns true if a port is open and false if not. + /*! + Note that this only applies to connections made with the openPort() + function, not to virtual ports. + */ + virtual bool isPortOpen() const; + + //! Return the number of available MIDI input ports. + /*! + \return This function returns the number of MIDI ports of the selected API. + */ + unsigned int getPortCount(); + + //! Return a string identifier for the specified MIDI input port number. + /*! + \return The name of the port with the given Id is returned. + \retval An empty string is returned if an invalid port specifier + is provided. User code should assume a UTF-8 encoding. + */ + std::string getPortName( unsigned int portNumber = 0 ); + + //! Specify whether certain MIDI message types should be queued or ignored during input. + /*! + By default, MIDI timing and active sensing messages are ignored + during message input because of their relative high data rates. + MIDI sysex messages are ignored by default as well. Variable + values of "true" imply that the respective message type will be + ignored. + */ + void ignoreTypes( bool midiSysex = true, bool midiTime = true, bool midiSense = true ); + + //! Fill the user-provided vector with the data bytes for the next available MIDI message in the input queue and return the event delta-time in seconds. + /*! + This function returns immediately whether a new message is + available or not. A valid message is indicated by a non-zero + vector size. An exception is thrown if an error occurs during + message retrieval or an input connection was not previously + established. + */ + double getMessage( std::vector<unsigned char> *message ); + + //! Set an error callback function to be invoked when an error has occured. + /*! + The callback function will be called whenever an error has occured. It is best + to set the error callback function before opening a port. + */ + virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ); + + //! Set maximum expected incoming message size. + /*! + For APIs that require manual buffer management, it can be useful to set the buffer + size and buffer count when expecting to receive large SysEx messages. Note that + currently this function has no effect when called after openPort(). The default + buffer size is 1024 with a count of 4 buffers, which should be sufficient for most + cases; as mentioned, this does not affect all API backends, since most either support + dynamically scalable buffers or take care of buffer handling themselves. It is + principally intended for users of the Windows MM backend who must support receiving + especially large messages. + */ + virtual void setBufferSize( unsigned int size, unsigned int count ); + + protected: + void openMidiApi( RtMidi::Api api, const std::string &clientName, unsigned int queueSizeLimit ); +}; + +/**********************************************************************/ +/*! \class RtMidiOut + \brief A realtime MIDI output class. + + This class provides a common, platform-independent API for MIDI + output. It allows one to probe available MIDI output ports, to + connect to one such port, and to send MIDI bytes immediately over + the connection. Create multiple instances of this class to + connect to more than one MIDI device at the same time. With the + OS-X, Linux ALSA and JACK MIDI APIs, it is also possible to open a + virtual port to which other MIDI software clients can connect. +*/ +/**********************************************************************/ + +class RTMIDI_DLL_PUBLIC RtMidiOut : public RtMidi +{ + public: + //! Default constructor that allows an optional client name. + /*! + An exception will be thrown if a MIDI system initialization error occurs. + + If no API argument is specified and multiple API support has been + compiled, the default order of use is ALSA, JACK (Linux) and CORE, + JACK (OS-X). + */ + RtMidiOut( RtMidi::Api api=UNSPECIFIED, + const std::string& clientName = "RtMidi Output Client" ); + + RtMidiOut(RtMidiOut&& other) noexcept : RtMidi(std::move(other)) { } + + //! The destructor closes any open MIDI connections. + ~RtMidiOut( void ) throw(); + + //! Returns the MIDI API specifier for the current instance of RtMidiOut. + RtMidi::Api getCurrentApi( void ) throw(); + + //! Open a MIDI output connection. + /*! + An optional port number greater than 0 can be specified. + Otherwise, the default or first port found is opened. An + exception is thrown if an error occurs while attempting to make + the port connection. + */ + void openPort( unsigned int portNumber = 0, const std::string &portName = std::string( "RtMidi Output" ) ); + + //! Close an open MIDI connection (if one exists). + void closePort( void ); + + //! Returns true if a port is open and false if not. + /*! + Note that this only applies to connections made with the openPort() + function, not to virtual ports. + */ + virtual bool isPortOpen() const; + + //! Create a virtual output port, with optional name, to allow software connections (OS X, JACK and ALSA only). + /*! + This function creates a virtual MIDI output port to which other + software applications can connect. This type of functionality + is currently only supported by the Macintosh OS-X, Linux ALSA + and JACK APIs (the function does nothing with the other APIs). + An exception is thrown if an error occurs while attempting to + create the virtual port. + */ + void openVirtualPort( const std::string &portName = std::string( "RtMidi Output" ) ); + + //! Return the number of available MIDI output ports. + unsigned int getPortCount( void ); + + //! Return a string identifier for the specified MIDI port type and number. + /*! + \return The name of the port with the given Id is returned. + \retval An empty string is returned if an invalid port specifier + is provided. User code should assume a UTF-8 encoding. + */ + std::string getPortName( unsigned int portNumber = 0 ); + + //! Immediately send a single message out an open MIDI output port. + /*! + An exception is thrown if an error occurs during output or an + output connection was not previously established. + */ + void sendMessage( const std::vector<unsigned char> *message ); + + //! Immediately send a single message out an open MIDI output port. + /*! + An exception is thrown if an error occurs during output or an + output connection was not previously established. + + \param message A pointer to the MIDI message as raw bytes + \param size Length of the MIDI message in bytes + */ + void sendMessage( const unsigned char *message, size_t size ); + + //! Set an error callback function to be invoked when an error has occured. + /*! + The callback function will be called whenever an error has occured. It is best + to set the error callback function before opening a port. + */ + virtual void setErrorCallback( RtMidiErrorCallback errorCallback = NULL, void *userData = 0 ); + + protected: + void openMidiApi( RtMidi::Api api, const std::string &clientName ); +}; + + +// **************************************************************** // +// +// MidiInApi / MidiOutApi class declarations. +// +// Subclasses of MidiInApi and MidiOutApi contain all API- and +// OS-specific code necessary to fully implement the RtMidi API. +// +// Note that MidiInApi and MidiOutApi are abstract base classes and +// cannot be explicitly instantiated. RtMidiIn and RtMidiOut will +// create instances of a MidiInApi or MidiOutApi subclass. +// +// **************************************************************** // + +class RTMIDI_DLL_PUBLIC MidiApi +{ + public: + + MidiApi(); + virtual ~MidiApi(); + virtual RtMidi::Api getCurrentApi( void ) = 0; + virtual void openPort( unsigned int portNumber, const std::string &portName ) = 0; + virtual void openVirtualPort( const std::string &portName ) = 0; + virtual void closePort( void ) = 0; + virtual void setClientName( const std::string &clientName ) = 0; + virtual void setPortName( const std::string &portName ) = 0; + + virtual unsigned int getPortCount( void ) = 0; + virtual std::string getPortName( unsigned int portNumber ) = 0; + + inline bool isPortOpen() const { return connected_; } + void setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ); + + //! A basic error reporting function for RtMidi classes. + void error( RtMidiError::Type type, std::string errorString ); + +protected: + virtual void initialize( const std::string& clientName ) = 0; + + void *apiData_; + bool connected_; + std::string errorString_; + RtMidiErrorCallback errorCallback_; + bool firstErrorOccurred_; + void *errorCallbackUserData_; + +}; + +class RTMIDI_DLL_PUBLIC MidiInApi : public MidiApi +{ + public: + + MidiInApi( unsigned int queueSizeLimit ); + virtual ~MidiInApi( void ); + void setCallback( RtMidiIn::RtMidiCallback callback, void *userData ); + void cancelCallback( void ); + virtual void ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ); + double getMessage( std::vector<unsigned char> *message ); + virtual void setBufferSize( unsigned int size, unsigned int count ); + + // A MIDI structure used internally by the class to store incoming + // messages. Each message represents one and only one MIDI message. + struct MidiMessage { + std::vector<unsigned char> bytes; + + //! Time in seconds elapsed since the previous message + double timeStamp; + + // Default constructor. + MidiMessage() + : bytes(0), timeStamp(0.0) {} + }; + + struct MidiQueue { + unsigned int front; + unsigned int back; + unsigned int ringSize; + MidiMessage *ring; + + // Default constructor. + MidiQueue() + : front(0), back(0), ringSize(0), ring(0) {} + bool push( const MidiMessage& ); + bool pop( std::vector<unsigned char>*, double* ); + unsigned int size( unsigned int *back=0, unsigned int *front=0 ); + }; + + // The RtMidiInData structure is used to pass private class data to + // the MIDI input handling function or thread. + struct RtMidiInData { + MidiQueue queue; + MidiMessage message; + unsigned char ignoreFlags; + bool doInput; + bool firstMessage; + void *apiData; + bool usingCallback; + RtMidiIn::RtMidiCallback userCallback; + void *userData; + bool continueSysex; + unsigned int bufferSize; + unsigned int bufferCount; + + // Default constructor. + RtMidiInData() + : ignoreFlags(7), doInput(false), firstMessage(true), apiData(0), usingCallback(false), + userCallback(0), userData(0), continueSysex(false), bufferSize(1024), bufferCount(4) {} + }; + + protected: + RtMidiInData inputData_; +}; + +class RTMIDI_DLL_PUBLIC MidiOutApi : public MidiApi +{ + public: + + MidiOutApi( void ); + virtual ~MidiOutApi( void ); + virtual void sendMessage( const unsigned char *message, size_t size ) = 0; +}; + +// **************************************************************** // +// +// Inline RtMidiIn and RtMidiOut definitions. +// +// **************************************************************** // + +inline RtMidi::Api RtMidiIn :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); } +inline void RtMidiIn :: openPort( unsigned int portNumber, const std::string &portName ) { rtapi_->openPort( portNumber, portName ); } +inline void RtMidiIn :: openVirtualPort( const std::string &portName ) { rtapi_->openVirtualPort( portName ); } +inline void RtMidiIn :: closePort( void ) { rtapi_->closePort(); } +inline bool RtMidiIn :: isPortOpen() const { return rtapi_->isPortOpen(); } +inline void RtMidiIn :: setCallback( RtMidiCallback callback, void *userData ) { static_cast<MidiInApi *>(rtapi_)->setCallback( callback, userData ); } +inline void RtMidiIn :: cancelCallback( void ) { static_cast<MidiInApi *>(rtapi_)->cancelCallback(); } +inline unsigned int RtMidiIn :: getPortCount( void ) { return rtapi_->getPortCount(); } +inline std::string RtMidiIn :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); } +inline void RtMidiIn :: ignoreTypes( bool midiSysex, bool midiTime, bool midiSense ) { static_cast<MidiInApi *>(rtapi_)->ignoreTypes( midiSysex, midiTime, midiSense ); } +inline double RtMidiIn :: getMessage( std::vector<unsigned char> *message ) { return static_cast<MidiInApi *>(rtapi_)->getMessage( message ); } +inline void RtMidiIn :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ) { rtapi_->setErrorCallback(errorCallback, userData); } +inline void RtMidiIn :: setBufferSize( unsigned int size, unsigned int count ) { static_cast<MidiInApi *>(rtapi_)->setBufferSize(size, count); } + +inline RtMidi::Api RtMidiOut :: getCurrentApi( void ) throw() { return rtapi_->getCurrentApi(); } +inline void RtMidiOut :: openPort( unsigned int portNumber, const std::string &portName ) { rtapi_->openPort( portNumber, portName ); } +inline void RtMidiOut :: openVirtualPort( const std::string &portName ) { rtapi_->openVirtualPort( portName ); } +inline void RtMidiOut :: closePort( void ) { rtapi_->closePort(); } +inline bool RtMidiOut :: isPortOpen() const { return rtapi_->isPortOpen(); } +inline unsigned int RtMidiOut :: getPortCount( void ) { return rtapi_->getPortCount(); } +inline std::string RtMidiOut :: getPortName( unsigned int portNumber ) { return rtapi_->getPortName( portNumber ); } +inline void RtMidiOut :: sendMessage( const std::vector<unsigned char> *message ) { static_cast<MidiOutApi *>(rtapi_)->sendMessage( &message->at(0), message->size() ); } +inline void RtMidiOut :: sendMessage( const unsigned char *message, size_t size ) { static_cast<MidiOutApi *>(rtapi_)->sendMessage( message, size ); } +inline void RtMidiOut :: setErrorCallback( RtMidiErrorCallback errorCallback, void *userData ) { rtapi_->setErrorCallback(errorCallback, userData); } + +#endif diff --git a/distrho/src/lv2/atom-forge.h b/distrho/src/lv2/atom-forge.h @@ -1,5 +1,5 @@ /* - Copyright 2008-2013 David Robillard <http://drobilla.net> + Copyright 2008-2016 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -39,6 +39,12 @@ This header is non-normative, it is provided for convenience. */ +/** + @defgroup forge Forge + @ingroup atom + @{ +*/ + #ifndef LV2_ATOM_FORGE_H #define LV2_ATOM_FORGE_H @@ -48,7 +54,7 @@ #include "atom-util.h" #include "urid.h" -#if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1) +#if defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) # define LV2_ATOM_FORGE_DEPRECATED __attribute__((__deprecated__)) #else # define LV2_ATOM_FORGE_DEPRECATED @@ -60,6 +66,15 @@ extern "C" { # include <stdbool.h> #endif +// Disable deprecation warnings for Blank and Resource +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-declarations" +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + /** Handle for LV2_Atom_Forge_Sink. */ typedef void* LV2_Atom_Forge_Sink_Handle; @@ -119,21 +134,14 @@ static inline void lv2_atom_forge_set_buffer(LV2_Atom_Forge* forge, uint8_t* buf, size_t size); /** - Initialise @p forge. + Initialise `forge`. - URIs will be mapped using @p map and stored, a reference to @p map itself is + URIs will be mapped using `map` and stored, a reference to `map` itself is not held. */ static inline void -lv2_atom_forge_init(LV2_Atom_Forge* forge, LV2_URID_Map* map) +lv2_atom_forge_init(LV2_Atom_Forge* forge, const LV2_URID_Map* map) { -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wdeprecated-declarations" -#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif lv2_atom_forge_set_buffer(forge, NULL, 0); forge->Blank = map->map(map->handle, LV2_ATOM__Blank); forge->Bool = map->map(map->handle, LV2_ATOM__Bool); @@ -153,13 +161,9 @@ lv2_atom_forge_init(LV2_Atom_Forge* forge, LV2_URID_Map* map) forge->URI = map->map(map->handle, LV2_ATOM__URI); forge->URID = map->map(map->handle, LV2_ATOM__URID); forge->Vector = map->map(map->handle, LV2_ATOM__Vector); -#if defined(__clang__) -# pragma clang diagnostic pop -#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) -# pragma GCC diagnostic pop -#endif } +/** Access the Atom pointed to by a reference. */ static inline LV2_Atom* lv2_atom_forge_deref(LV2_Atom_Forge* forge, LV2_Atom_Forge_Ref ref) { @@ -208,47 +212,23 @@ lv2_atom_forge_top_is(LV2_Atom_Forge* forge, uint32_t type) (lv2_atom_forge_deref(forge, forge->stack->ref)->type == type); } -/** Return true iff @p type is an atom:Object. */ +/** Return true iff `type` is an atom:Object. */ static inline bool lv2_atom_forge_is_object_type(const LV2_Atom_Forge* forge, uint32_t type) { -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wdeprecated-declarations" -#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif return (type == forge->Object || type == forge->Blank || type == forge->Resource); -#if defined(__clang__) -# pragma clang diagnostic pop -#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) -# pragma GCC diagnostic pop -#endif } -/** Return true iff @p type is an atom:Object with a blank ID. */ +/** Return true iff `type` is an atom:Object with a blank ID. */ static inline bool lv2_atom_forge_is_blank(const LV2_Atom_Forge* forge, uint32_t type, const LV2_Atom_Object_Body* body) { -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wdeprecated-declarations" -#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif return (type == forge->Blank || (type == forge->Object && body->id == 0)); -#if defined(__clang__) -# pragma clang diagnostic pop -#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) -# pragma GCC diagnostic pop -#endif } /** @@ -257,7 +237,7 @@ lv2_atom_forge_is_blank(const LV2_Atom_Forge* forge, @{ */ -/** Set the output buffer where @p forge will write atoms. */ +/** Set the output buffer where `forge` will write atoms. */ static inline void lv2_atom_forge_set_buffer(LV2_Atom_Forge* forge, uint8_t* buf, size_t size) { @@ -271,7 +251,7 @@ lv2_atom_forge_set_buffer(LV2_Atom_Forge* forge, uint8_t* buf, size_t size) } /** - Set the sink function where @p forge will write output. + Set the sink function where `forge` will write output. The return value of forge functions is an LV2_Atom_Forge_Ref which is an integer type safe to use as a pointer but is otherwise opaque. The sink @@ -456,7 +436,7 @@ lv2_atom_forge_typed_string(LV2_Atom_Forge* forge, return out; } -/** Write an atom:String. Note that @p str need not be NULL terminated. */ +/** Write an atom:String. Note that `str` need not be NULL terminated. */ static inline LV2_Atom_Forge_Ref lv2_atom_forge_string(LV2_Atom_Forge* forge, const char* str, uint32_t len) { @@ -464,7 +444,7 @@ lv2_atom_forge_string(LV2_Atom_Forge* forge, const char* str, uint32_t len) } /** - Write an atom:URI. Note that @p uri need not be NULL terminated. + Write an atom:URI. Note that `uri` need not be NULL terminated. This does not map the URI, but writes the complete URI string. To write a mapped URI, use lv2_atom_forge_urid(). */ @@ -474,7 +454,7 @@ lv2_atom_forge_uri(LV2_Atom_Forge* forge, const char* uri, uint32_t len) return lv2_atom_forge_typed_string(forge, forge->URI, uri, len); } -/** Write an atom:Path. Note that @p path need not be NULL terminated. */ +/** Write an atom:Path. Note that `path` need not be NULL terminated. */ static inline LV2_Atom_Forge_Ref lv2_atom_forge_path(LV2_Atom_Forge* forge, const char* path, uint32_t len) { @@ -617,24 +597,12 @@ lv2_atom_forge_resource(LV2_Atom_Forge* forge, LV2_URID id, LV2_URID otype) { -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wdeprecated-declarations" -#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif const LV2_Atom_Object a = { { (uint32_t)sizeof(LV2_Atom_Object_Body), forge->Resource }, { id, otype } }; return lv2_atom_forge_push( forge, frame, lv2_atom_forge_write(forge, &a, sizeof(a))); -#if defined(__clang__) -# pragma clang diagnostic pop -#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) -# pragma GCC diagnostic pop -#endif } /** @@ -650,24 +618,12 @@ lv2_atom_forge_blank(LV2_Atom_Forge* forge, uint32_t id, LV2_URID otype) { -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wdeprecated-declarations" -#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wdeprecated-declarations" -#endif const LV2_Atom_Object a = { { (uint32_t)sizeof(LV2_Atom_Object_Body), forge->Blank }, { id, otype } }; return lv2_atom_forge_push( forge, frame, lv2_atom_forge_write(forge, &a, sizeof(a))); -#if defined(__clang__) -# pragma clang diagnostic pop -#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) -# pragma GCC diagnostic pop -#endif } /** @@ -738,8 +694,15 @@ lv2_atom_forge_beat_time(LV2_Atom_Forge* forge, double beats) /** @} + @} */ +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +# pragma GCC diagnostic pop +#endif + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/distrho/src/lv2/atom-helpers.h b/distrho/src/lv2/atom-helpers.h @@ -1,249 +0,0 @@ -// lv2_atom_helpers.h -// -/**************************************************************************** - Copyright (C) 2005-2013, rncbc aka Rui Nuno Capela. All rights reserved. - - 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 2 - of the License, or (at your option) 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. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -*****************************************************************************/ - -/* Helper functions for LV2 atom:Sequence event buffer. - * - * tentatively adapted from: - * - * - lv2_evbuf.h,c - An abstract/opaque LV2 event buffer implementation. - * - * - event-helpers.h - Helper functions for the LV2 Event extension. - * <http://lv2plug.in/ns/ext/event> - * - * Copyright 2008-2012 David Robillard <http://drobilla.net> - */ - -#ifndef LV2_ATOM_HELPERS_H -#define LV2_ATOM_HELPERS_H - -#include <stdint.h> -#include <stdbool.h> -#include <string.h> -#include <stdlib.h> -#include <assert.h> - -#include "atom-util.h" - -#ifdef __cplusplus -extern "C" { -#endif - -// An abstract/opaque LV2 atom:Sequence buffer. -// -typedef -struct _LV2_Atom_Buffer -{ - uint32_t capacity; - uint32_t chunk_type; - uint32_t sequence_type; - LV2_Atom_Sequence atoms; - -} LV2_Atom_Buffer; - - -// Clear and initialize an existing LV2 atom:Sequenece buffer. -// -static inline -void lv2_atom_buffer_reset ( LV2_Atom_Buffer *buf, bool input ) -{ - if (input) { - buf->atoms.atom.size = sizeof(LV2_Atom_Sequence_Body); - buf->atoms.atom.type = buf->sequence_type; - } else { - buf->atoms.atom.size = buf->capacity; - buf->atoms.atom.type = buf->chunk_type; - } -} - - -// Allocate a new, empty LV2 atom:Sequence buffer. -// -static inline -LV2_Atom_Buffer *lv2_atom_buffer_new ( - uint32_t capacity, uint32_t chunk_type, uint32_t sequence_type, bool input ) -{ - LV2_Atom_Buffer *buf = (LV2_Atom_Buffer *) - malloc(sizeof(LV2_Atom_Buffer) + sizeof(LV2_Atom_Sequence) + capacity); - - buf->capacity = capacity; - buf->chunk_type = chunk_type; - buf->sequence_type = sequence_type; - - lv2_atom_buffer_reset(buf, input); - - return buf; -} - - -// Free an LV2 atom:Sequenece buffer allocated with lv2_atome_buffer_new. -// -static inline -void lv2_atom_buffer_free ( LV2_Atom_Buffer *buf ) -{ - free(buf); -} - - -// Return the total padded size of events stored in a LV2 atom:Sequence buffer. -// -static inline -uint32_t lv2_atom_buffer_get_size ( LV2_Atom_Buffer *buf ) -{ - if (buf->atoms.atom.type == buf->sequence_type) - return buf->atoms.atom.size - uint32_t(sizeof(LV2_Atom_Sequence_Body)); - else - return 0; -} - - -// Return the actual LV2 atom:Sequence implementation. -// -static inline -LV2_Atom_Sequence *lv2_atom_buffer_get_sequence ( LV2_Atom_Buffer *buf ) -{ - return &buf->atoms; -} - - -// An iterator over an atom:Sequence buffer. -// -typedef -struct _LV2_Atom_Buffer_Iterator -{ - LV2_Atom_Buffer *buf; - uint32_t offset; - -} LV2_Atom_Buffer_Iterator; - - -// Reset an iterator to point to the start of an LV2 atom:Sequence buffer. -// -static inline -bool lv2_atom_buffer_begin ( - LV2_Atom_Buffer_Iterator *iter, LV2_Atom_Buffer *buf ) -{ - iter->buf = buf; - iter->offset = 0; - - return (buf->atoms.atom.size > 0); -} - - -// Reset an iterator to point to the end of an LV2 atom:Sequence buffer. -// -static inline -bool lv2_atom_buffer_end ( - LV2_Atom_Buffer_Iterator *iter, LV2_Atom_Buffer *buf ) -{ - iter->buf = buf; - iter->offset = lv2_atom_pad_size(lv2_atom_buffer_get_size(buf)); - - return (iter->offset < buf->capacity - sizeof(LV2_Atom_Event)); -} - - -// Check if a LV2 atom:Sequenece buffer iterator is valid. -// -static inline -bool lv2_atom_buffer_is_valid ( LV2_Atom_Buffer_Iterator *iter ) -{ - return iter->offset < lv2_atom_buffer_get_size(iter->buf); -} - - -// Advance a LV2 atom:Sequenece buffer iterator forward one event. -// -static inline -bool lv2_atom_buffer_increment ( LV2_Atom_Buffer_Iterator *iter ) -{ - if (!lv2_atom_buffer_is_valid(iter)) - return false; - - LV2_Atom_Buffer *buf = iter->buf; - LV2_Atom_Sequence *atoms = &buf->atoms; - uint32_t size = ((LV2_Atom_Event *) ((char *) - LV2_ATOM_CONTENTS(LV2_Atom_Sequence, atoms) + iter->offset))->body.size; - iter->offset += lv2_atom_pad_size(uint32_t(sizeof(LV2_Atom_Event)) + size); - - return true; -} - - -// Get the event currently pointed at a LV2 atom:Sequence buffer iterator. -// -static inline -LV2_Atom_Event *lv2_atom_buffer_get ( - LV2_Atom_Buffer_Iterator *iter, uint8_t **data ) -{ - if (!lv2_atom_buffer_is_valid(iter)) - return NULL; - - LV2_Atom_Buffer *buf = iter->buf; - LV2_Atom_Sequence *atoms = &buf->atoms; - LV2_Atom_Event *ev = (LV2_Atom_Event *) ((char *) - LV2_ATOM_CONTENTS(LV2_Atom_Sequence, atoms) + iter->offset); - - *data = (uint8_t *) LV2_ATOM_BODY(&ev->body); - - return ev; -} - - -// Write an event at a LV2 atom:Sequence buffer iterator. - -static inline -bool lv2_atom_buffer_write ( - LV2_Atom_Buffer_Iterator *iter, - uint32_t frames, - uint32_t /*subframes*/, - uint32_t type, - uint32_t size, - const uint8_t *data ) -{ - LV2_Atom_Buffer *buf = iter->buf; - LV2_Atom_Sequence *atoms = &buf->atoms; - if (buf->capacity - sizeof(LV2_Atom) - atoms->atom.size - < sizeof(LV2_Atom_Event) + size) - return false; - - LV2_Atom_Event *ev = (LV2_Atom_Event*) ((char *) - LV2_ATOM_CONTENTS(LV2_Atom_Sequence, atoms) + iter->offset); - - ev->time.frames = frames; - ev->body.type = type; - ev->body.size = size; - - memcpy(LV2_ATOM_BODY(&ev->body), data, size); - - size = lv2_atom_pad_size(uint32_t(sizeof(LV2_Atom_Event)) + size); - atoms->atom.size += size; - iter->offset += size; - - return true; -} - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif // LV2_ATOM_HELPERS_H - -// end of lv2_atom_helpers.h diff --git a/distrho/src/lv2/atom-util.h b/distrho/src/lv2/atom-util.h @@ -1,5 +1,5 @@ /* - Copyright 2008-2013 David Robillard <http://drobilla.net> + Copyright 2008-2015 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -22,6 +22,12 @@ This header is non-normative, it is provided for convenience. */ +/** + @defgroup util Utilities + @ingroup atom + @{ +*/ + #ifndef LV2_ATOM_UTIL_H #define LV2_ATOM_UTIL_H @@ -44,21 +50,21 @@ lv2_atom_pad_size(uint32_t size) return (size + 7U) & (~7U); } -/** Return the total size of @p atom, including the header. */ +/** Return the total size of `atom`, including the header. */ static inline uint32_t lv2_atom_total_size(const LV2_Atom* atom) { return (uint32_t)sizeof(LV2_Atom) + atom->size; } -/** Return true iff @p atom is null. */ +/** Return true iff `atom` is null. */ static inline bool lv2_atom_is_null(const LV2_Atom* atom) { return !atom || (atom->type == 0 && atom->size == 0); } -/** Return true iff @p a is equal to @p b. */ +/** Return true iff `a` is equal to `b`. */ static inline bool lv2_atom_equals(const LV2_Atom* a, const LV2_Atom* b) { @@ -80,13 +86,20 @@ lv2_atom_sequence_begin(const LV2_Atom_Sequence_Body* body) } /** Get an iterator pointing to the end of a Sequence body. */ +static inline const LV2_Atom_Event* +lv2_atom_sequence_end(const LV2_Atom_Sequence_Body* body, uint32_t size) +{ + return (const LV2_Atom_Event*)((const uint8_t*)body + lv2_atom_pad_size(size)); +} + +/** Get an iterator pointing to the end of a Sequence body. */ static inline LV2_Atom_Event* -lv2_atom_sequence_end(LV2_Atom_Sequence_Body* body, uint32_t size) +lv2_atom_sequence_end2(LV2_Atom_Sequence_Body* body, uint32_t size) { return (LV2_Atom_Event*)((uint8_t*)body + lv2_atom_pad_size(size)); } -/** Return true iff @p i has reached the end of @p body. */ +/** Return true iff `i` has reached the end of `body`. */ static inline bool lv2_atom_sequence_is_end(const LV2_Atom_Sequence_Body* body, uint32_t size, @@ -95,7 +108,7 @@ lv2_atom_sequence_is_end(const LV2_Atom_Sequence_Body* body, return (const uint8_t*)i >= ((const uint8_t*)body + size); } -/** Return an iterator to the element following @p i. */ +/** Return an iterator to the element following `i`. */ static inline const LV2_Atom_Event* lv2_atom_sequence_next(const LV2_Atom_Event* i) { @@ -119,13 +132,13 @@ lv2_atom_sequence_next(const LV2_Atom_Event* i) #define LV2_ATOM_SEQUENCE_FOREACH(seq, iter) \ for (const LV2_Atom_Event* iter = lv2_atom_sequence_begin(&(seq)->body); \ !lv2_atom_sequence_is_end(&(seq)->body, (seq)->atom.size, (iter)); \ - (iter) = lv2_atom_sequence_next(iter)) + iter = lv2_atom_sequence_next(iter)) /** Like LV2_ATOM_SEQUENCE_FOREACH but for a headerless sequence body. */ #define LV2_ATOM_SEQUENCE_BODY_FOREACH(body, size, iter) \ for (const LV2_Atom_Event* iter = lv2_atom_sequence_begin(body); \ !lv2_atom_sequence_is_end(body, size, (iter)); \ - (iter) = lv2_atom_sequence_next(iter)) + iter = lv2_atom_sequence_next(iter)) /** @} @@ -134,7 +147,7 @@ lv2_atom_sequence_next(const LV2_Atom_Event* i) */ /** - Clear all events from @p sequence. + Clear all events from `sequence`. This simply resets the size field, the other fields are left untouched. */ @@ -145,14 +158,14 @@ lv2_atom_sequence_clear(LV2_Atom_Sequence* seq) } /** - Append an event at the end of @p sequence. + Append an event at the end of `sequence`. @param seq Sequence to append to. @param capacity Total capacity of the sequence atom (e.g. as set by the host for sequence output ports). @param event Event to write. - @return A pointer to the newly written event in @p seq, + @return A pointer to the newly written event in `seq`, or NULL on failure (insufficient space). */ static inline LV2_Atom_Event* @@ -165,7 +178,7 @@ lv2_atom_sequence_append_event(LV2_Atom_Sequence* seq, return NULL; } - LV2_Atom_Event* e = lv2_atom_sequence_end(&seq->body, seq->atom.size); + LV2_Atom_Event* e = lv2_atom_sequence_end2(&seq->body, seq->atom.size); memcpy(e, event, total_size); seq->atom.size += lv2_atom_pad_size(total_size); @@ -179,21 +192,21 @@ lv2_atom_sequence_append_event(LV2_Atom_Sequence* seq, @{ */ -/** Get an iterator pointing to the first element in @p tup. */ +/** Get an iterator pointing to the first element in `tup`. */ static inline const LV2_Atom* lv2_atom_tuple_begin(const LV2_Atom_Tuple* tup) { return (const LV2_Atom*)(LV2_ATOM_BODY_CONST(tup)); } -/** Return true iff @p i has reached the end of @p body. */ +/** Return true iff `i` has reached the end of `body`. */ static inline bool lv2_atom_tuple_is_end(const void* body, uint32_t size, const LV2_Atom* i) { return (const uint8_t*)i >= ((const uint8_t*)body + size); } -/** Return an iterator to the element following @p i. */ +/** Return an iterator to the element following `i`. */ static inline const LV2_Atom* lv2_atom_tuple_next(const LV2_Atom* i) { @@ -208,21 +221,21 @@ lv2_atom_tuple_next(const LV2_Atom* i) This macro is used similarly to a for loop (which it expands to), e.g.: @code - LV2_ATOMO_TUPLE_FOREACH(tuple, elem) { + LV2_ATOM_TUPLE_FOREACH(tuple, elem) { // Do something with elem (an LV2_Atom*) here... } @endcode */ #define LV2_ATOM_TUPLE_FOREACH(tuple, iter) \ for (const LV2_Atom* iter = lv2_atom_tuple_begin(tuple); \ - !lv2_atom_tuple_is_end(LV2_ATOM_BODY_CONST(tuple), (tuple)->size, (iter)); \ - (iter) = lv2_atom_tuple_next(iter)) + !lv2_atom_tuple_is_end(LV2_ATOM_BODY_CONST(tuple), (tuple)->atom.size, (iter)); \ + iter = lv2_atom_tuple_next(iter)) /** Like LV2_ATOM_TUPLE_FOREACH but for a headerless tuple body. */ #define LV2_ATOM_TUPLE_BODY_FOREACH(body, size, iter) \ for (const LV2_Atom* iter = (const LV2_Atom*)body; \ !lv2_atom_tuple_is_end(body, size, (iter)); \ - (iter) = lv2_atom_tuple_next(iter)) + iter = lv2_atom_tuple_next(iter)) /** @} @@ -230,14 +243,14 @@ lv2_atom_tuple_next(const LV2_Atom* i) @{ */ -/** Return a pointer to the first property in @p body. */ +/** Return a pointer to the first property in `body`. */ static inline const LV2_Atom_Property_Body* lv2_atom_object_begin(const LV2_Atom_Object_Body* body) { return (const LV2_Atom_Property_Body*)(body + 1); } -/** Return true iff @p i has reached the end of @p obj. */ +/** Return true iff `i` has reached the end of `obj`. */ static inline bool lv2_atom_object_is_end(const LV2_Atom_Object_Body* body, uint32_t size, @@ -246,7 +259,7 @@ lv2_atom_object_is_end(const LV2_Atom_Object_Body* body, return (const uint8_t*)i >= ((const uint8_t*)body + size); } -/** Return an iterator to the property following @p i. */ +/** Return an iterator to the property following `i`. */ static inline const LV2_Atom_Property_Body* lv2_atom_object_next(const LV2_Atom_Property_Body* i) { @@ -272,13 +285,13 @@ lv2_atom_object_next(const LV2_Atom_Property_Body* i) #define LV2_ATOM_OBJECT_FOREACH(obj, iter) \ for (const LV2_Atom_Property_Body* iter = lv2_atom_object_begin(&(obj)->body); \ !lv2_atom_object_is_end(&(obj)->body, (obj)->atom.size, (iter)); \ - (iter) = lv2_atom_object_next(iter)) + iter = lv2_atom_object_next(iter)) /** Like LV2_ATOM_OBJECT_FOREACH but for a headerless object body. */ #define LV2_ATOM_OBJECT_BODY_FOREACH(body, size, iter) \ for (const LV2_Atom_Property_Body* iter = lv2_atom_object_begin(body); \ !lv2_atom_object_is_end(body, size, (iter)); \ - (iter) = lv2_atom_object_next(iter)) + iter = lv2_atom_object_next(iter)) /** @} @@ -297,10 +310,10 @@ static const LV2_Atom_Object_Query LV2_ATOM_OBJECT_QUERY_END = { 0, NULL }; /** Get an object's values for various keys. - The value pointer of each item in @p query will be set to the location of - the corresponding value in @p object. Every value pointer in @p query MUST - be initialised to NULL. This function reads @p object in a single linear - sweep. By allocating @p query on the stack, objects can be "queried" + The value pointer of each item in `query` will be set to the location of + the corresponding value in `object`. Every value pointer in `query` MUST + be initialised to NULL. This function reads `object` in a single linear + sweep. By allocating `query` on the stack, objects can be "queried" quickly without allocating any memory. This function is realtime safe. This function can only do "flat" queries, it is not smart enough to match @@ -359,6 +372,7 @@ lv2_atom_object_body_get(uint32_t size, const LV2_Atom_Object_Body* body, ...) va_start(args, body); for (n_queries = 0; va_arg(args, uint32_t); ++n_queries) { if (!va_arg(args, const LV2_Atom**)) { + va_end(args); return -1; } } @@ -372,6 +386,7 @@ lv2_atom_object_body_get(uint32_t size, const LV2_Atom_Object_Body* body, ...) if (qkey == prop->key && !*qval) { *qval = &prop->value; if (++matches == n_queries) { + va_end(args); return matches; } break; @@ -436,6 +451,63 @@ lv2_atom_object_get(const LV2_Atom_Object* object, ...) } /** + Variable argument version of lv2_atom_object_query() with types. + + This is like lv2_atom_object_get(), but each entry has an additional + parameter to specify the required type. Only atoms with a matching type + will be selected. + + The arguments should be a series of uint32_t key, const LV2_Atom**, uint32_t + type triples, terminated by a zero key. The value pointers MUST be + initialized to NULL. For example: + + @code + const LV2_Atom_String* name = NULL; + const LV2_Atom_Int* age = NULL; + lv2_atom_object_get(obj, + uris.name_key, &name, uris.atom_String, + uris.age_key, &age, uris.atom_Int + 0); + @endcode +*/ +static inline int +lv2_atom_object_get_typed(const LV2_Atom_Object* object, ...) +{ + int matches = 0; + int n_queries = 0; + + /* Count number of keys so we can short-circuit when done */ + va_list args; + va_start(args, object); + for (n_queries = 0; va_arg(args, uint32_t); ++n_queries) { + if (!va_arg(args, const LV2_Atom**) || + !va_arg(args, uint32_t)) { + return -1; + } + } + va_end(args); + + LV2_ATOM_OBJECT_FOREACH(object, prop) { + va_start(args, object); + for (int i = 0; i < n_queries; ++i) { + const uint32_t qkey = va_arg(args, uint32_t); + const LV2_Atom** qval = va_arg(args, const LV2_Atom**); + const uint32_t qtype = va_arg(args, uint32_t); + if (!*qval && qkey == prop->key && qtype == prop->value.type) { + *qval = &prop->value; + if (++matches == n_queries) { + return matches; + } + break; + } + } + va_end(args); + } + return matches; +} + +/** + @} @} */ diff --git a/distrho/src/lv2/atom.h b/distrho/src/lv2/atom.h @@ -1,5 +1,5 @@ /* - Copyright 2008-2012 David Robillard <http://drobilla.net> + Copyright 2008-2016 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -15,8 +15,12 @@ */ /** - @file atom.h C header for the LV2 Atom extension - <http://lv2plug.in/ns/ext/atom>. + @defgroup atom Atom + + A generic value container and several data types, see + <http://lv2plug.in/ns/ext/atom> for details. + + @{ */ #ifndef LV2_ATOM_H @@ -25,50 +29,52 @@ #include <stdint.h> #include <stddef.h> -#define LV2_ATOM_URI "http://lv2plug.in/ns/ext/atom" -#define LV2_ATOM_PREFIX LV2_ATOM_URI "#" - -#define LV2_ATOM__Atom LV2_ATOM_PREFIX "Atom" -#define LV2_ATOM__AtomPort LV2_ATOM_PREFIX "AtomPort" -#define LV2_ATOM__Blank LV2_ATOM_PREFIX "Blank" -#define LV2_ATOM__Bool LV2_ATOM_PREFIX "Bool" -#define LV2_ATOM__Chunk LV2_ATOM_PREFIX "Chunk" -#define LV2_ATOM__Double LV2_ATOM_PREFIX "Double" -#define LV2_ATOM__Event LV2_ATOM_PREFIX "Event" -#define LV2_ATOM__Float LV2_ATOM_PREFIX "Float" -#define LV2_ATOM__Int LV2_ATOM_PREFIX "Int" -#define LV2_ATOM__Literal LV2_ATOM_PREFIX "Literal" -#define LV2_ATOM__Long LV2_ATOM_PREFIX "Long" -#define LV2_ATOM__Number LV2_ATOM_PREFIX "Number" -#define LV2_ATOM__Object LV2_ATOM_PREFIX "Object" -#define LV2_ATOM__Path LV2_ATOM_PREFIX "Path" -#define LV2_ATOM__Property LV2_ATOM_PREFIX "Property" -#define LV2_ATOM__Resource LV2_ATOM_PREFIX "Resource" -#define LV2_ATOM__Sequence LV2_ATOM_PREFIX "Sequence" -#define LV2_ATOM__Sound LV2_ATOM_PREFIX "Sound" -#define LV2_ATOM__String LV2_ATOM_PREFIX "String" -#define LV2_ATOM__Tuple LV2_ATOM_PREFIX "Tuple" -#define LV2_ATOM__URI LV2_ATOM_PREFIX "URI" -#define LV2_ATOM__URID LV2_ATOM_PREFIX "URID" -#define LV2_ATOM__Vector LV2_ATOM_PREFIX "Vector" -#define LV2_ATOM__atomTransfer LV2_ATOM_PREFIX "atomTransfer" -#define LV2_ATOM__beatTime LV2_ATOM_PREFIX "beatTime" -#define LV2_ATOM__bufferType LV2_ATOM_PREFIX "bufferType" -#define LV2_ATOM__childType LV2_ATOM_PREFIX "childType" -#define LV2_ATOM__eventTransfer LV2_ATOM_PREFIX "eventTransfer" -#define LV2_ATOM__frameTime LV2_ATOM_PREFIX "frameTime" -#define LV2_ATOM__supports LV2_ATOM_PREFIX "supports" -#define LV2_ATOM__timeUnit LV2_ATOM_PREFIX "timeUnit" - -#define LV2_ATOM_REFERENCE_TYPE 0 +#define LV2_ATOM_URI "http://lv2plug.in/ns/ext/atom" ///< http://lv2plug.in/ns/ext/atom +#define LV2_ATOM_PREFIX LV2_ATOM_URI "#" ///< http://lv2plug.in/ns/ext/atom# + +#define LV2_ATOM__Atom LV2_ATOM_PREFIX "Atom" ///< http://lv2plug.in/ns/ext/atom#Atom +#define LV2_ATOM__AtomPort LV2_ATOM_PREFIX "AtomPort" ///< http://lv2plug.in/ns/ext/atom#AtomPort +#define LV2_ATOM__Blank LV2_ATOM_PREFIX "Blank" ///< http://lv2plug.in/ns/ext/atom#Blank +#define LV2_ATOM__Bool LV2_ATOM_PREFIX "Bool" ///< http://lv2plug.in/ns/ext/atom#Bool +#define LV2_ATOM__Chunk LV2_ATOM_PREFIX "Chunk" ///< http://lv2plug.in/ns/ext/atom#Chunk +#define LV2_ATOM__Double LV2_ATOM_PREFIX "Double" ///< http://lv2plug.in/ns/ext/atom#Double +#define LV2_ATOM__Event LV2_ATOM_PREFIX "Event" ///< http://lv2plug.in/ns/ext/atom#Event +#define LV2_ATOM__Float LV2_ATOM_PREFIX "Float" ///< http://lv2plug.in/ns/ext/atom#Float +#define LV2_ATOM__Int LV2_ATOM_PREFIX "Int" ///< http://lv2plug.in/ns/ext/atom#Int +#define LV2_ATOM__Literal LV2_ATOM_PREFIX "Literal" ///< http://lv2plug.in/ns/ext/atom#Literal +#define LV2_ATOM__Long LV2_ATOM_PREFIX "Long" ///< http://lv2plug.in/ns/ext/atom#Long +#define LV2_ATOM__Number LV2_ATOM_PREFIX "Number" ///< http://lv2plug.in/ns/ext/atom#Number +#define LV2_ATOM__Object LV2_ATOM_PREFIX "Object" ///< http://lv2plug.in/ns/ext/atom#Object +#define LV2_ATOM__Path LV2_ATOM_PREFIX "Path" ///< http://lv2plug.in/ns/ext/atom#Path +#define LV2_ATOM__Property LV2_ATOM_PREFIX "Property" ///< http://lv2plug.in/ns/ext/atom#Property +#define LV2_ATOM__Resource LV2_ATOM_PREFIX "Resource" ///< http://lv2plug.in/ns/ext/atom#Resource +#define LV2_ATOM__Sequence LV2_ATOM_PREFIX "Sequence" ///< http://lv2plug.in/ns/ext/atom#Sequence +#define LV2_ATOM__Sound LV2_ATOM_PREFIX "Sound" ///< http://lv2plug.in/ns/ext/atom#Sound +#define LV2_ATOM__String LV2_ATOM_PREFIX "String" ///< http://lv2plug.in/ns/ext/atom#String +#define LV2_ATOM__Tuple LV2_ATOM_PREFIX "Tuple" ///< http://lv2plug.in/ns/ext/atom#Tuple +#define LV2_ATOM__URI LV2_ATOM_PREFIX "URI" ///< http://lv2plug.in/ns/ext/atom#URI +#define LV2_ATOM__URID LV2_ATOM_PREFIX "URID" ///< http://lv2plug.in/ns/ext/atom#URID +#define LV2_ATOM__Vector LV2_ATOM_PREFIX "Vector" ///< http://lv2plug.in/ns/ext/atom#Vector +#define LV2_ATOM__atomTransfer LV2_ATOM_PREFIX "atomTransfer" ///< http://lv2plug.in/ns/ext/atom#atomTransfer +#define LV2_ATOM__beatTime LV2_ATOM_PREFIX "beatTime" ///< http://lv2plug.in/ns/ext/atom#beatTime +#define LV2_ATOM__bufferType LV2_ATOM_PREFIX "bufferType" ///< http://lv2plug.in/ns/ext/atom#bufferType +#define LV2_ATOM__childType LV2_ATOM_PREFIX "childType" ///< http://lv2plug.in/ns/ext/atom#childType +#define LV2_ATOM__eventTransfer LV2_ATOM_PREFIX "eventTransfer" ///< http://lv2plug.in/ns/ext/atom#eventTransfer +#define LV2_ATOM__frameTime LV2_ATOM_PREFIX "frameTime" ///< http://lv2plug.in/ns/ext/atom#frameTime +#define LV2_ATOM__supports LV2_ATOM_PREFIX "supports" ///< http://lv2plug.in/ns/ext/atom#supports +#define LV2_ATOM__timeUnit LV2_ATOM_PREFIX "timeUnit" ///< http://lv2plug.in/ns/ext/atom#timeUnit + +#define LV2_ATOM_REFERENCE_TYPE 0 ///< The special type for a reference atom #ifdef __cplusplus extern "C" { #endif +/** @cond */ /** This expression will fail to compile if double does not fit in 64 bits. */ typedef char lv2_atom_assert_double_fits_in_64_bits[ ((sizeof(double) <= sizeof(uint64_t)) * 2) - 1]; +/** @endcond */ /** Return a pointer to the contents of an Atom. The "contents" of an atom @@ -239,6 +245,10 @@ typedef struct { LV2_Atom_Sequence_Body body; /**< Body. */ } LV2_Atom_Sequence; +/** + @} +*/ + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/distrho/src/lv2/buf-size.h b/distrho/src/lv2/buf-size.h @@ -1,5 +1,5 @@ /* - Copyright 2007-2012 David Robillard <http://drobilla.net> + Copyright 2007-2016 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -17,15 +17,28 @@ #ifndef LV2_BUF_SIZE_H #define LV2_BUF_SIZE_H -#define LV2_BUF_SIZE_URI "http://lv2plug.in/ns/ext/buf-size" -#define LV2_BUF_SIZE_PREFIX LV2_BUF_SIZE_URI "#" +/** + @defgroup buf-size Buffer Size -#define LV2_BUF_SIZE__boundedBlockLength LV2_BUF_SIZE_PREFIX "boundedBlockLength" -#define LV2_BUF_SIZE__fixedBlockLength LV2_BUF_SIZE_PREFIX "fixedBlockLength" -#define LV2_BUF_SIZE__maxBlockLength LV2_BUF_SIZE_PREFIX "maxBlockLength" -#define LV2_BUF_SIZE__minBlockLength LV2_BUF_SIZE_PREFIX "minBlockLength" -#define LV2_BUF_SIZE__nominalBlockLength LV2_BUF_SIZE_PREFIX "nominalBlockLength" -#define LV2_BUF_SIZE__powerOf2BlockLength LV2_BUF_SIZE_PREFIX "powerOf2BlockLength" -#define LV2_BUF_SIZE__sequenceSize LV2_BUF_SIZE_PREFIX "sequenceSize" + Access to, and restrictions on, buffer sizes; see + <http://lv2plug.in/ns/ext/buf-size> for details. + + @{ +*/ + +#define LV2_BUF_SIZE_URI "http://lv2plug.in/ns/ext/buf-size" ///< http://lv2plug.in/ns/ext/buf-size +#define LV2_BUF_SIZE_PREFIX LV2_BUF_SIZE_URI "#" ///< http://lv2plug.in/ns/ext/buf-size# + +#define LV2_BUF_SIZE__boundedBlockLength LV2_BUF_SIZE_PREFIX "boundedBlockLength" ///< http://lv2plug.in/ns/ext/buf-size#boundedBlockLength +#define LV2_BUF_SIZE__fixedBlockLength LV2_BUF_SIZE_PREFIX "fixedBlockLength" ///< http://lv2plug.in/ns/ext/buf-size#fixedBlockLength +#define LV2_BUF_SIZE__maxBlockLength LV2_BUF_SIZE_PREFIX "maxBlockLength" ///< http://lv2plug.in/ns/ext/buf-size#maxBlockLength +#define LV2_BUF_SIZE__minBlockLength LV2_BUF_SIZE_PREFIX "minBlockLength" ///< http://lv2plug.in/ns/ext/buf-size#minBlockLength +#define LV2_BUF_SIZE__nominalBlockLength LV2_BUF_SIZE_PREFIX "nominalBlockLength" ///< http://lv2plug.in/ns/ext/buf-size#nominalBlockLength +#define LV2_BUF_SIZE__powerOf2BlockLength LV2_BUF_SIZE_PREFIX "powerOf2BlockLength" ///< http://lv2plug.in/ns/ext/buf-size#powerOf2BlockLength +#define LV2_BUF_SIZE__sequenceSize LV2_BUF_SIZE_PREFIX "sequenceSize" ///< http://lv2plug.in/ns/ext/buf-size#sequenceSize + +/** + @} +*/ #endif /* LV2_BUF_SIZE_H */ diff --git a/distrho/src/lv2/data-access.h b/distrho/src/lv2/data-access.h @@ -1,6 +1,6 @@ /* LV2 Data Access Extension - Copyright 2008-2011 David Robillard <http://drobilla.net> + Copyright 2008-2016 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -16,18 +16,19 @@ */ /** - @file data-access.h - C header for the LV2 Extension Data extension - <http://lv2plug.in/ns/ext/data-access>. + @defgroup data-access Data Access - This extension defines a method for (e.g.) plugin UIs to have (possibly - marshalled) access to the extension_data function on a plugin instance. + Access to plugin extension_data() for UIs, see + <http://lv2plug.in/ns/ext/data-acess> for details. + + @{ */ #ifndef LV2_DATA_ACCESS_H #define LV2_DATA_ACCESS_H -#define LV2_DATA_ACCESS_URI "http://lv2plug.in/ns/ext/data-access" +#define LV2_DATA_ACCESS_URI "http://lv2plug.in/ns/ext/data-access" ///< http://lv2plug.in/ns/ext/data-access +#define LV2_DATA_ACCESS_PREFIX LV2_DATA_ACCESS_URI "#" ///< http://lv2plug.in/ns/ext/data-access# #ifdef __cplusplus extern "C" { @@ -61,3 +62,7 @@ typedef struct { #endif #endif /* LV2_DATA_ACCESS_H */ + +/** + @} +*/ diff --git a/distrho/src/lv2/dynmanifest.h b/distrho/src/lv2/dynmanifest.h @@ -16,10 +16,12 @@ */ /** - @file dynmanifest.h - C header for the LV2 Dynamic Manifest extension - <http://lv2plug.in/ns/ext/dynmanifest>. - Revision: 1.2 + @defgroup dynmanifest Dynamic Manifest + + Support for dynamic data generation, see + <http://lv2plug.in/ns/ext/dynmanifest> for details. + + @{ */ #ifndef LV2_DYN_MANIFEST_H_INCLUDED @@ -29,7 +31,8 @@ #include "lv2.h" -#define LV2_DYN_MANIFEST_URI "http://lv2plug.in/ns/ext/dynmanifest" +#define LV2_DYN_MANIFEST_URI "http://lv2plug.in/ns/ext/dynmanifest" ///< http://lv2plug.in/ns/ext/dynmanifest +#define LV2_DYN_MANIFEST_PREFIX LV2_DYN_MANIFEST_URI "#" ///< http://lv2plug.in/ns/ext/dynmanifest# #ifdef __cplusplus extern "C" { @@ -142,3 +145,7 @@ void lv2_dyn_manifest_close(LV2_Dyn_Manifest_Handle handle); #endif #endif /* LV2_DYN_MANIFEST_H_INCLUDED */ + +/** + @} +*/ diff --git a/distrho/src/lv2/event-helpers.h b/distrho/src/lv2/event-helpers.h @@ -1,5 +1,5 @@ /* - Copyright 2008-2012 David Robillard <http://drobilla.net> + Copyright 2008-2015 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -96,8 +96,8 @@ typedef struct { } LV2_Event_Iterator; -/** Reset an iterator to point to the start of @a buf. - * @return True if @a iter is valid, otherwise false (buffer is empty) */ +/** Reset an iterator to point to the start of `buf`. + * @return True if `iter` is valid, otherwise false (buffer is empty) */ static inline bool lv2_event_begin(LV2_Event_Iterator* iter, LV2_Event_Buffer* buf) @@ -108,8 +108,8 @@ lv2_event_begin(LV2_Event_Iterator* iter, } -/** Check if @a iter is valid. - * @return True if @a iter is valid, otherwise false (past end of buffer) */ +/** Check if `iter` is valid. + * @return True if `iter` is valid, otherwise false (past end of buffer) */ static inline bool lv2_event_is_valid(LV2_Event_Iterator* iter) { @@ -117,9 +117,9 @@ lv2_event_is_valid(LV2_Event_Iterator* iter) } -/** Advance @a iter forward one event. - * @a iter must be valid. - * @return True if @a iter is valid, otherwise false (reached end of buffer) */ +/** Advance `iter` forward one event. + * `iter` must be valid. + * @return True if `iter` is valid, otherwise false (reached end of buffer) */ static inline bool lv2_event_increment(LV2_Event_Iterator* iter) { @@ -138,11 +138,11 @@ lv2_event_increment(LV2_Event_Iterator* iter) /** Dereference an event iterator (get the event currently pointed at). - * @a iter must be valid. - * @a data if non-NULL, will be set to point to the contents of the event + * `iter` must be valid. + * `data` if non-NULL, will be set to point to the contents of the event * returned. - * @return A Pointer to the event @a iter is currently pointing at, or NULL - * if the end of the buffer is reached (in which case @a data is + * @return A Pointer to the event `iter` is currently pointing at, or NULL + * if the end of the buffer is reached (in which case `data` is * also set to NULL). */ static inline LV2_Event* lv2_event_get(LV2_Event_Iterator* iter, @@ -162,8 +162,8 @@ lv2_event_get(LV2_Event_Iterator* iter, } -/** Write an event at @a iter. - * The event (if any) pointed to by @a iter will be overwritten, and @a iter +/** Write an event at `iter`. + * The event (if any) pointed to by `iter` will be overwritten, and `iter` * incremented to point to the following event (i.e. several calls to this * function can be done in sequence without twiddling iter in-between). * @return True if event was written, otherwise false (buffer is full). */ @@ -230,8 +230,8 @@ lv2_event_reserve(LV2_Event_Iterator* iter, } -/** Write an event at @a iter. - * The event (if any) pointed to by @a iter will be overwritten, and @a iter +/** Write an event at `iter`. + * The event (if any) pointed to by `iter` will be overwritten, and `iter` * incremented to point to the following event (i.e. several calls to this * function can be done in sequence without twiddling iter in-between). * @return True if event was written, otherwise false (buffer is full). */ @@ -263,4 +263,3 @@ lv2_event_write_event(LV2_Event_Iterator* iter, #endif #endif /* LV2_EVENT_HELPERS_H */ - diff --git a/distrho/src/lv2/event.h b/distrho/src/lv2/event.h @@ -1,5 +1,5 @@ /* - Copyright 2008-2011 David Robillard <http://drobilla.net> + Copyright 2008-2016 David Robillard <http://drobilla.net> Copyright 2006-2007 Lars Luthman <lars.luthman@gmail.com> Permission to use, copy, modify, and/or distribute this software for any @@ -16,38 +16,32 @@ */ /** - @file event.h - C API for the LV2 Event extension <http://lv2plug.in/ns/ext/event>. - - This extension is a generic transport mechanism for time stamped events - of any type (e.g. MIDI, OSC, ramps, etc). Each port can transport mixed - events of any type; the type of events and timestamps are defined by a URI - which is mapped to an integer by the host for performance reasons. - - This extension requires the host to support the LV2 URI Map extension. - Any host which supports this extension MUST guarantee that any call to - the LV2 URI Map uri_to_id function with the URI of this extension as the - 'map' argument returns a value within the range of uint16_t. + @defgroup event Event + + Generic time-stamped events, see <http://lv2plug.in/ns/ext/event> for + details. + + @{ */ #ifndef LV2_EVENT_H #define LV2_EVENT_H -#define LV2_EVENT_URI "http://lv2plug.in/ns/ext/event" -#define LV2_EVENT_PREFIX LV2_EVENT_URI "#" +#define LV2_EVENT_URI "http://lv2plug.in/ns/ext/event" ///< http://lv2plug.in/ns/ext/event +#define LV2_EVENT_PREFIX LV2_EVENT_URI "#" ///< http://lv2plug.in/ns/ext/event# -#define LV2_EVENT__Event LV2_EVENT_PREFIX "Event" -#define LV2_EVENT__EventPort LV2_EVENT_PREFIX "EventPort" -#define LV2_EVENT__FrameStamp LV2_EVENT_PREFIX "FrameStamp" -#define LV2_EVENT__TimeStamp LV2_EVENT_PREFIX "TimeStamp" -#define LV2_EVENT__generatesTimeStamp LV2_EVENT_PREFIX "generatesTimeStamp" -#define LV2_EVENT__generic LV2_EVENT_PREFIX "generic" -#define LV2_EVENT__inheritsEvent LV2_EVENT_PREFIX "inheritsEvent" -#define LV2_EVENT__inheritsTimeStamp LV2_EVENT_PREFIX "inheritsTimeStamp" -#define LV2_EVENT__supportsEvent LV2_EVENT_PREFIX "supportsEvent" -#define LV2_EVENT__supportsTimeStamp LV2_EVENT_PREFIX "supportsTimeStamp" +#define LV2_EVENT__Event LV2_EVENT_PREFIX "Event" ///< http://lv2plug.in/ns/ext/event#Event +#define LV2_EVENT__EventPort LV2_EVENT_PREFIX "EventPort" ///< http://lv2plug.in/ns/ext/event#EventPort +#define LV2_EVENT__FrameStamp LV2_EVENT_PREFIX "FrameStamp" ///< http://lv2plug.in/ns/ext/event#FrameStamp +#define LV2_EVENT__TimeStamp LV2_EVENT_PREFIX "TimeStamp" ///< http://lv2plug.in/ns/ext/event#TimeStamp +#define LV2_EVENT__generatesTimeStamp LV2_EVENT_PREFIX "generatesTimeStamp" ///< http://lv2plug.in/ns/ext/event#generatesTimeStamp +#define LV2_EVENT__generic LV2_EVENT_PREFIX "generic" ///< http://lv2plug.in/ns/ext/event#generic +#define LV2_EVENT__inheritsEvent LV2_EVENT_PREFIX "inheritsEvent" ///< http://lv2plug.in/ns/ext/event#inheritsEvent +#define LV2_EVENT__inheritsTimeStamp LV2_EVENT_PREFIX "inheritsTimeStamp" ///< http://lv2plug.in/ns/ext/event#inheritsTimeStamp +#define LV2_EVENT__supportsEvent LV2_EVENT_PREFIX "supportsEvent" ///< http://lv2plug.in/ns/ext/event#supportsEvent +#define LV2_EVENT__supportsTimeStamp LV2_EVENT_PREFIX "supportsTimeStamp" ///< http://lv2plug.in/ns/ext/event#supportsTimeStamp -#define LV2_EVENT_AUDIO_STAMP 0 +#define LV2_EVENT_AUDIO_STAMP 0 ///< Special timestamp type for audio frames #include <stdint.h> @@ -255,7 +249,7 @@ typedef struct { @param context The calling context. Like event types, this is a mapped URI, see lv2_context.h. Simple plugin with just a run() method should pass 0 here (the ID of the 'standard' LV2 run context). The host - guarantees that this function is realtime safe iff @a context is + guarantees that this function is realtime safe iff the context is realtime safe. PLUGINS THAT VIOLATE THESE RULES MAY CAUSE CRASHES AND MEMORY LEAKS. @@ -278,7 +272,7 @@ typedef struct { @param context The calling context. Like event types, this is a mapped URI, see lv2_context.h. Simple plugin with just a run() method should pass 0 here (the ID of the 'standard' LV2 run context). The host - guarantees that this function is realtime safe iff @a context is + guarantees that this function is realtime safe iff the context is realtime safe. PLUGINS THAT VIOLATE THESE RULES MAY CAUSE CRASHES AND MEMORY LEAKS. @@ -292,3 +286,7 @@ typedef struct { #endif #endif /* LV2_EVENT_H */ + +/** + @} +*/ diff --git a/distrho/src/lv2/instance-access.h b/distrho/src/lv2/instance-access.h @@ -1,6 +1,6 @@ /* LV2 Instance Access Extension - Copyright 2008-2012 David Robillard <http://drobilla.net> + Copyright 2008-2016 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -15,23 +15,22 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifndef LV2_INSTANCE_ACCESS_H -#define LV2_INSTANCE_ACCESS_H +/** + @defgroup instance-access Instance Access -#define LV2_INSTANCE_ACCESS_URI "http://lv2plug.in/ns/ext/instance-access" + Access to the LV2_Handle of a plugin for UIs; see + <http://lv2plug.in/ns/ext/instance-access> for details. -/** - @file instance-access.h - C header for the LV2 Instance Access extension - <http://lv2plug.in/ns/ext/instance-access>. - - This extension defines a method for (e.g.) plugin UIs to get a direct - handle to an LV2 plugin instance (LV2_Handle), if possible. - - To support this feature the host must pass an LV2_Feature struct to the - UI instantiate method with URI "http://lv2plug.in/ns/ext/instance-access" - and data pointed directly to the LV2_Handle of the plugin instance. + @{ */ +#ifndef LV2_INSTANCE_ACCESS_H +#define LV2_INSTANCE_ACCESS_H + +#define LV2_INSTANCE_ACCESS_URI "http://lv2plug.in/ns/ext/instance-access" ///< http://lv2plug.in/ns/ext/instance-access + #endif /* LV2_INSTANCE_ACCESS_H */ +/** + @} +*/ diff --git a/distrho/src/lv2/log.h b/distrho/src/lv2/log.h @@ -1,5 +1,5 @@ /* - Copyright 2012 David Robillard <http://drobilla.net> + Copyright 2012-2016 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -15,22 +15,26 @@ */ /** - @file log.h C header for the LV2 Log extension - <http://lv2plug.in/ns/ext/log>. + @defgroup log Log + + Interface for plugins to log via the host; see + <http://lv2plug.in/ns/ext/log> for details. + + @{ */ #ifndef LV2_LOG_H #define LV2_LOG_H -#define LV2_LOG_URI "http://lv2plug.in/ns/ext/log" -#define LV2_LOG_PREFIX LV2_LOG_URI "#" +#define LV2_LOG_URI "http://lv2plug.in/ns/ext/log" ///< http://lv2plug.in/ns/ext/log +#define LV2_LOG_PREFIX LV2_LOG_URI "#" ///< http://lv2plug.in/ns/ext/log# -#define LV2_LOG__Entry LV2_LOG_PREFIX "Entry" -#define LV2_LOG__Error LV2_LOG_PREFIX "Error" -#define LV2_LOG__Note LV2_LOG_PREFIX "Note" -#define LV2_LOG__Trace LV2_LOG_PREFIX "Trace" -#define LV2_LOG__Warning LV2_LOG_PREFIX "Warning" -#define LV2_LOG__log LV2_LOG_PREFIX "log" +#define LV2_LOG__Entry LV2_LOG_PREFIX "Entry" ///< http://lv2plug.in/ns/ext/log#Entry +#define LV2_LOG__Error LV2_LOG_PREFIX "Error" ///< http://lv2plug.in/ns/ext/log#Error +#define LV2_LOG__Note LV2_LOG_PREFIX "Note" ///< http://lv2plug.in/ns/ext/log#Note +#define LV2_LOG__Trace LV2_LOG_PREFIX "Trace" ///< http://lv2plug.in/ns/ext/log#Trace +#define LV2_LOG__Warning LV2_LOG_PREFIX "Warning" ///< http://lv2plug.in/ns/ext/log#Warning +#define LV2_LOG__log LV2_LOG_PREFIX "log" ///< http://lv2plug.in/ns/ext/log#log #include <stdarg.h> @@ -40,12 +44,14 @@ extern "C" { #endif +/** @cond */ #ifdef __GNUC__ /** Allow type checking of printf-like functions. */ # define LV2_LOG_FUNC(fmt, arg1) __attribute__((format(printf, fmt, arg1))) #else # define LV2_LOG_FUNC(fmt, arg1) #endif +/** @endcond */ /** Opaque data to host data for LV2_Log_Log. @@ -69,7 +75,7 @@ typedef struct _LV2_Log { The API of this function matches that of the standard C printf function, except for the addition of the first two parameters. This function may - be called from any non-realtime context, or from any context if @p type + be called from any non-realtime context, or from any context if `type` is @ref LV2_LOG__Trace. */ LV2_LOG_FUNC(3, 4) @@ -83,7 +89,7 @@ typedef struct _LV2_Log { The API of this function matches that of the standard C vprintf function, except for the addition of the first two parameters. This function may be called from any non-realtime context, or from any - context if @p type is @ref LV2_LOG__Trace. + context if `type` is @ref LV2_LOG__Trace. */ LV2_LOG_FUNC(3, 0) int (*vprintf)(LV2_Log_Handle handle, @@ -97,3 +103,7 @@ typedef struct _LV2_Log { #endif #endif /* LV2_LOG_H */ + +/** + @} +*/ diff --git a/distrho/src/lv2/logger.h b/distrho/src/lv2/logger.h @@ -1,5 +1,5 @@ /* - Copyright 2012-2013 David Robillard <http://drobilla.net> + Copyright 2012-2016 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -15,13 +15,14 @@ */ /** - @file logger.h Convenience API for easy logging in plugin code. + @defgroup logger Logger + @ingroup log - This file provides simple wrappers for the most common log operations for - use in plugin implementations. If host support for logging is not - available, then these functions will print to stderr instead. + Convenience API for easy logging in plugin code. This API provides simple + wrappers for logging from a plugin, which automatically fall back to + printing to stderr if host support is unavailabe. - This header is non-normative, it is provided for convenience. + @{ */ #ifndef LV2_ATOM_LOGGER_H @@ -49,28 +50,41 @@ typedef struct { } LV2_Log_Logger; /** - Initialise @p logger. + Set `map` as the URI map for `logger`. - URIs will be mapped using @p map and stored, a reference to @p map itself is - not held. Both @p map and @p log may be NULL when unsupported by the host, - in which case the implementation will fall back to printing to stderr. + This affects the message type URIDs (Error, Warning, etc) which are passed + to the log's print functions. */ static inline void -lv2_log_logger_init(LV2_Log_Logger* logger, - LV2_URID_Map* map, - LV2_Log_Log* log) +lv2_log_logger_set_map(LV2_Log_Logger* logger, LV2_URID_Map* map) { - memset(logger, 0, sizeof(LV2_Log_Logger)); - logger->log = log; if (map) { logger->Error = map->map(map->handle, LV2_LOG__Error); logger->Note = map->map(map->handle, LV2_LOG__Note); logger->Trace = map->map(map->handle, LV2_LOG__Trace); logger->Warning = map->map(map->handle, LV2_LOG__Warning); + } else { + logger->Error = logger->Note = logger->Trace = logger->Warning = 0; } } /** + Initialise `logger`. + + URIs will be mapped using `map` and stored, a reference to `map` itself is + not held. Both `map` and `log` may be NULL when unsupported by the host, + in which case the implementation will fall back to printing to stderr. +*/ +static inline void +lv2_log_logger_init(LV2_Log_Logger* logger, + LV2_URID_Map* map, + LV2_Log_Log* log) +{ + logger->log = log; + lv2_log_logger_set_map(logger, map); +} + +/** Log a message to the host, or stderr if support is unavailable. */ LV2_LOG_FUNC(3, 0) @@ -80,7 +94,7 @@ lv2_log_vprintf(LV2_Log_Logger* logger, const char* fmt, va_list args) { - if (logger->log) { + if (logger && logger->log) { return logger->log->vprintf(logger->log->handle, type, fmt, args); } else { return vfprintf(stderr, fmt, args); @@ -135,12 +149,12 @@ lv2_log_warning(LV2_Log_Logger* logger, const char* fmt, ...) return ret; } -/** - @} -*/ - #ifdef __cplusplus } /* extern "C" */ #endif #endif /* LV2_LOG_LOGGER_H */ + +/** + @} +*/ diff --git a/distrho/src/lv2/lv2-midifunctions.h b/distrho/src/lv2/lv2-midifunctions.h @@ -1,98 +0,0 @@ -/**************************************************************************** - - lv2-midifunctions.h - support file for using MIDI in LV2 plugins - - Copyright (C) 2006 Lars Luthman <lars.luthman@gmail.com> - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) 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. - - You should have received a copy of the GNU Lesser General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 01222-1307 USA - -****************************************************************************/ - -#ifndef LV2_MIDIFUNCTIONS -#define LV2_MIDIFUNCTIONS - -#include "lv2-miditype.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct { - LV2_MIDI* midi; - uint32_t frame_count; - uint32_t position; -} LV2_MIDIState; - - -inline double lv2midi_get_event(LV2_MIDIState* state, - double* timestamp, - uint32_t* size, - unsigned char** data) { - - if (state->position >= state->midi->size) { - state->position = state->midi->size; - *timestamp = state->frame_count; - *size = 0; - *data = NULL; - return *timestamp; - } - - *timestamp = *(double*)(state->midi->data + state->position); - *size = (uint32_t)*(size_t*)(state->midi->data + state->position + sizeof(double)); - *data = state->midi->data + state->position + - sizeof(double) + sizeof(size_t); - return *timestamp; -} - - -inline double lv2midi_step(LV2_MIDIState* state) { - - if (state->position >= state->midi->size) { - state->position = state->midi->size; - return state->frame_count; - } - - state->position += (uint32_t)sizeof(double); - size_t size = *(size_t*)(state->midi->data + state->position); - state->position += (uint32_t)sizeof(size_t); - state->position += (uint32_t)size; - return *(double*)(state->midi->data + state->position); -} - - -inline void lv2midi_put_event(LV2_MIDIState* state, - double timestamp, - uint32_t size, - const unsigned char* data) { - - if (state->midi->size + sizeof(double) + sizeof(size_t) + size < state->midi->capacity) - { - *((double*)(state->midi->data + state->midi->size)) = timestamp; - state->midi->size += (uint32_t)sizeof(double); - *((size_t*)(state->midi->data + state->midi->size)) = size; - state->midi->size += (uint32_t)sizeof(size_t); - memcpy(state->midi->data + state->midi->size, data, size); - - state->midi->size += size; - state->midi->event_count++; - } -} - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif - diff --git a/distrho/src/lv2/lv2-miditype.h b/distrho/src/lv2/lv2-miditype.h @@ -1,175 +0,0 @@ -/**************************************************************************** - - lv2-miditype.h - header file for using MIDI in LV2 plugins - - Copyright (C) 2006 Lars Luthman <lars.luthman@gmail.com> - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 01222-1307 USA - -****************************************************************************/ - -#ifndef LV2_MIDITYPE_H -#define LV2_MIDITYPE_H - -#ifdef __cplusplus -extern "C" { -#endif - -/** This data structure is used to contain the MIDI events for one run() - cycle. The port buffer for a LV2 port that has the datatype - <http://ll-plugins.nongnu.org/lv2/ext/miditype> should be a pointer - to an instance of this struct. - - To store two Note On events on MIDI channel 0 in a buffer, with timestamps - 12 and 35.5, you could use something like this code (assuming that - midi_data is a variable of type LV2_MIDI): - @code - - size_t buffer_offset = 0; - *(double*)(midi_data->data + buffer_offset) = 12; - buffer_offset += sizeof(double); - *(size_t*)(midi_data->data + buffer_offset) = 3; - buffer_offset += sizeof(size_t); - midi_data->data[buffer_offset++] = 0x90; - midi_data->data[buffer_offset++] = 0x48; - midi_data->data[buffer_offset++] = 0x64; - ++midi_data->event_count; - - *(double*)(midi_data->data + buffer_offset) = 35.5; - buffer_offset += sizeof(double); - *(size_t*)(midi_data->data + buffer_offset) = 3; - buffer_offset += sizeof(size_t); - midi_data->data[buffer_offset++] = 0x90; - midi_data->data[buffer_offset++] = 0x55; - midi_data->data[buffer_offset++] = 0x64; - ++midi_data->event_count; - - midi_data->size = buffer_offset; - - @endcode - - This would be done by the host in the case of an input port, and by the - plugin in the case of an output port. Whoever is writing events to the - buffer must also take care not to exceed the capacity of the data buffer. - - To read events from a buffer, you could do something like this: - @code - - size_t buffer_offset = 0; - uint32_t i; - for (i = 0; i < midi_data->event_count; ++i) { - double timestamp = *(double*)(midi_data->data + buffer_offset); - buffer_offset += sizeof(double); - size_t size = *(size_t*)(midi_data->data + buffer_offset); - buffer_offset += sizeof(size_t); - do_something_with_event(timestamp, size, - midi_data->data + buffer_offset); - buffer_offset += size; - } - - @endcode -*/ -typedef struct { - - /** The number of MIDI events in the data buffer. - INPUT PORTS: It's the host's responsibility to set this field to the - number of MIDI events contained in the data buffer before calling the - plugin's run() function. The plugin may not change this field. - OUTPUT PORTS: It's the plugin's responsibility to set this field to the - number of MIDI events it has stored in the data buffer before returning - from the run() function. Any initial value should be ignored by the - plugin. - */ - uint32_t event_count; - - /** The size of the data buffer in bytes. It is set by the host and may not - be changed by the plugin. The host is allowed to change this between - run() calls. - */ - uint32_t capacity; - - /** The size of the initial part of the data buffer that actually contains - data. - INPUT PORTS: It's the host's responsibility to set this field to the - number of bytes used by all MIDI events it has written to the buffer - (including timestamps and size fields) before calling the plugin's - run() function. The plugin may not change this field. - OUTPUT PORTS: It's the plugin's responsibility to set this field to - the number of bytes used by all MIDI events it has written to the - buffer (including timestamps and size fields) before returning from - the run() function. Any initial value should be ignored by the plugin. - */ - uint32_t size; - - /** The data buffer that is used to store MIDI events. The events are packed - after each other, and the format of each event is as follows: - - First there is a timestamp, which should have the type "double", - i.e. have the same bit size as a double and the same bit layout as a - double (whatever that is on the current platform). This timestamp gives - the offset from the beginning of the current cycle, in frames, that - the MIDI event occurs on. It must be strictly smaller than the 'nframes' - parameter to the current run() call. The MIDI events in the buffer must - be ordered by their timestamp, e.g. an event with a timestamp of 123.23 - must be stored after an event with a timestamp of 65.0. - - The second part of the event is a size field, which should have the type - "size_t" (as defined in the standard C header stddef.h). It should - contain the size of the MIDI data for this event, i.e. the number of - bytes used to store the actual MIDI event. The bytes used by the - timestamp and the size field should not be counted. - - The third part of the event is the actual MIDI data. There are some - requirements that must be followed: - - * Running status is not allowed. Every event must have its own status - byte. - * Note On events with velocity 0 are not allowed. These events are - equivalent to Note Off in standard MIDI streams, but in order to make - plugins and hosts easier to write, as well as more efficient, only - proper Note Off events are allowed as Note Off. - * "Realtime events" (status bytes 0xF8 to 0xFF) are allowed, but may not - occur inside other events like they are allowed to in hardware MIDI - streams. - * All events must be fully contained in a single data buffer, i.e. events - may not "wrap around" by storing the first few bytes in one buffer and - then wait for the next run() call to store the rest of the event. If - there isn't enough space in the current data buffer to store an event, - the event will either have to wait until next run() call, be ignored, - or compensated for in some more clever way. - * All events must be valid MIDI events. This means for example that - only the first byte in each event (the status byte) may have the eighth - bit set, that Note On and Note Off events are always 3 bytes long etc. - The MIDI writer (host or plugin) is responsible for writing valid MIDI - events to the buffer, and the MIDI reader (plugin or host) can assume - that all events are valid. - - On a platform where double is 8 bytes and size_t is 4 bytes, the data - buffer layout for a 3-byte event followed by a 4-byte event may look - something like this: - _______________________________________________________________ - | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | ... - |TIMESTAMP 1 |SIZE 1 |DATA |TIMESTAMP 2 |SIZE 2 |DATA | ... - - */ - unsigned char* data; - -} LV2_MIDI; - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif diff --git a/distrho/src/lv2/lv2.h b/distrho/src/lv2/lv2.h @@ -19,9 +19,11 @@ */ /** - @file lv2.h - API for the LV2 specification <http://lv2plug.in/ns/lv2core>. - Revision: 12.0 + @defgroup lv2core LV2 Core + + Core LV2 specification, see <http://lv2plug.in/ns/lv2core> for details. + + @{ */ #ifndef LV2_H_INCLUDED @@ -29,93 +31,93 @@ #include <stdint.h> -#define LV2_CORE_URI "http://lv2plug.in/ns/lv2core" -#define LV2_CORE_PREFIX LV2_CORE_URI "#" - -#define LV2_CORE__AllpassPlugin LV2_CORE_PREFIX "AllpassPlugin" -#define LV2_CORE__AmplifierPlugin LV2_CORE_PREFIX "AmplifierPlugin" -#define LV2_CORE__AnalyserPlugin LV2_CORE_PREFIX "AnalyserPlugin" -#define LV2_CORE__AudioPort LV2_CORE_PREFIX "AudioPort" -#define LV2_CORE__BandpassPlugin LV2_CORE_PREFIX "BandpassPlugin" -#define LV2_CORE__CVPort LV2_CORE_PREFIX "CVPort" -#define LV2_CORE__ChorusPlugin LV2_CORE_PREFIX "ChorusPlugin" -#define LV2_CORE__CombPlugin LV2_CORE_PREFIX "CombPlugin" -#define LV2_CORE__CompressorPlugin LV2_CORE_PREFIX "CompressorPlugin" -#define LV2_CORE__ConstantPlugin LV2_CORE_PREFIX "ConstantPlugin" -#define LV2_CORE__ControlPort LV2_CORE_PREFIX "ControlPort" -#define LV2_CORE__ConverterPlugin LV2_CORE_PREFIX "ConverterPlugin" -#define LV2_CORE__DelayPlugin LV2_CORE_PREFIX "DelayPlugin" -#define LV2_CORE__DistortionPlugin LV2_CORE_PREFIX "DistortionPlugin" -#define LV2_CORE__DynamicsPlugin LV2_CORE_PREFIX "DynamicsPlugin" -#define LV2_CORE__EQPlugin LV2_CORE_PREFIX "EQPlugin" -#define LV2_CORE__EnvelopePlugin LV2_CORE_PREFIX "EnvelopePlugin" -#define LV2_CORE__ExpanderPlugin LV2_CORE_PREFIX "ExpanderPlugin" -#define LV2_CORE__ExtensionData LV2_CORE_PREFIX "ExtensionData" -#define LV2_CORE__Feature LV2_CORE_PREFIX "Feature" -#define LV2_CORE__FilterPlugin LV2_CORE_PREFIX "FilterPlugin" -#define LV2_CORE__FlangerPlugin LV2_CORE_PREFIX "FlangerPlugin" -#define LV2_CORE__FunctionPlugin LV2_CORE_PREFIX "FunctionPlugin" -#define LV2_CORE__GatePlugin LV2_CORE_PREFIX "GatePlugin" -#define LV2_CORE__GeneratorPlugin LV2_CORE_PREFIX "GeneratorPlugin" -#define LV2_CORE__HighpassPlugin LV2_CORE_PREFIX "HighpassPlugin" -#define LV2_CORE__InputPort LV2_CORE_PREFIX "InputPort" -#define LV2_CORE__InstrumentPlugin LV2_CORE_PREFIX "InstrumentPlugin" -#define LV2_CORE__LimiterPlugin LV2_CORE_PREFIX "LimiterPlugin" -#define LV2_CORE__LowpassPlugin LV2_CORE_PREFIX "LowpassPlugin" -#define LV2_CORE__MixerPlugin LV2_CORE_PREFIX "MixerPlugin" -#define LV2_CORE__ModulatorPlugin LV2_CORE_PREFIX "ModulatorPlugin" -#define LV2_CORE__MultiEQPlugin LV2_CORE_PREFIX "MultiEQPlugin" -#define LV2_CORE__OscillatorPlugin LV2_CORE_PREFIX "OscillatorPlugin" -#define LV2_CORE__OutputPort LV2_CORE_PREFIX "OutputPort" -#define LV2_CORE__ParaEQPlugin LV2_CORE_PREFIX "ParaEQPlugin" -#define LV2_CORE__PhaserPlugin LV2_CORE_PREFIX "PhaserPlugin" -#define LV2_CORE__PitchPlugin LV2_CORE_PREFIX "PitchPlugin" -#define LV2_CORE__Plugin LV2_CORE_PREFIX "Plugin" -#define LV2_CORE__PluginBase LV2_CORE_PREFIX "PluginBase" -#define LV2_CORE__Point LV2_CORE_PREFIX "Point" -#define LV2_CORE__Port LV2_CORE_PREFIX "Port" -#define LV2_CORE__PortProperty LV2_CORE_PREFIX "PortProperty" -#define LV2_CORE__Resource LV2_CORE_PREFIX "Resource" -#define LV2_CORE__ReverbPlugin LV2_CORE_PREFIX "ReverbPlugin" -#define LV2_CORE__ScalePoint LV2_CORE_PREFIX "ScalePoint" -#define LV2_CORE__SimulatorPlugin LV2_CORE_PREFIX "SimulatorPlugin" -#define LV2_CORE__SpatialPlugin LV2_CORE_PREFIX "SpatialPlugin" -#define LV2_CORE__Specification LV2_CORE_PREFIX "Specification" -#define LV2_CORE__SpectralPlugin LV2_CORE_PREFIX "SpectralPlugin" -#define LV2_CORE__UtilityPlugin LV2_CORE_PREFIX "UtilityPlugin" -#define LV2_CORE__WaveshaperPlugin LV2_CORE_PREFIX "WaveshaperPlugin" -#define LV2_CORE__appliesTo LV2_CORE_PREFIX "appliesTo" -#define LV2_CORE__binary LV2_CORE_PREFIX "binary" -#define LV2_CORE__connectionOptional LV2_CORE_PREFIX "connectionOptional" -#define LV2_CORE__control LV2_CORE_PREFIX "control" -#define LV2_CORE__default LV2_CORE_PREFIX "default" -#define LV2_CORE__designation LV2_CORE_PREFIX "designation" -#define LV2_CORE__documentation LV2_CORE_PREFIX "documentation" -#define LV2_CORE__enumeration LV2_CORE_PREFIX "enumeration" -#define LV2_CORE__extensionData LV2_CORE_PREFIX "extensionData" -#define LV2_CORE__freeWheeling LV2_CORE_PREFIX "freeWheeling" -#define LV2_CORE__hardRTCapable LV2_CORE_PREFIX "hardRTCapable" -#define LV2_CORE__inPlaceBroken LV2_CORE_PREFIX "inPlaceBroken" -#define LV2_CORE__index LV2_CORE_PREFIX "index" -#define LV2_CORE__integer LV2_CORE_PREFIX "integer" -#define LV2_CORE__isLive LV2_CORE_PREFIX "isLive" -#define LV2_CORE__latency LV2_CORE_PREFIX "latency" -#define LV2_CORE__maximum LV2_CORE_PREFIX "maximum" -#define LV2_CORE__microVersion LV2_CORE_PREFIX "microVersion" -#define LV2_CORE__minimum LV2_CORE_PREFIX "minimum" -#define LV2_CORE__minorVersion LV2_CORE_PREFIX "minorVersion" -#define LV2_CORE__name LV2_CORE_PREFIX "name" -#define LV2_CORE__optionalFeature LV2_CORE_PREFIX "optionalFeature" -#define LV2_CORE__port LV2_CORE_PREFIX "port" -#define LV2_CORE__portProperty LV2_CORE_PREFIX "portProperty" -#define LV2_CORE__project LV2_CORE_PREFIX "project" -#define LV2_CORE__prototype LV2_CORE_PREFIX "prototype" -#define LV2_CORE__reportsLatency LV2_CORE_PREFIX "reportsLatency" -#define LV2_CORE__requiredFeature LV2_CORE_PREFIX "requiredFeature" -#define LV2_CORE__sampleRate LV2_CORE_PREFIX "sampleRate" -#define LV2_CORE__scalePoint LV2_CORE_PREFIX "scalePoint" -#define LV2_CORE__symbol LV2_CORE_PREFIX "symbol" -#define LV2_CORE__toggled LV2_CORE_PREFIX "toggled" +#define LV2_CORE_URI "http://lv2plug.in/ns/lv2core" ///< http://lv2plug.in/ns/lv2core +#define LV2_CORE_PREFIX LV2_CORE_URI "#" ///< http://lv2plug.in/ns/lv2core# + +#define LV2_CORE__AllpassPlugin LV2_CORE_PREFIX "AllpassPlugin" ///< http://lv2plug.in/ns/lv2core#AllpassPlugin +#define LV2_CORE__AmplifierPlugin LV2_CORE_PREFIX "AmplifierPlugin" ///< http://lv2plug.in/ns/lv2core#AmplifierPlugin +#define LV2_CORE__AnalyserPlugin LV2_CORE_PREFIX "AnalyserPlugin" ///< http://lv2plug.in/ns/lv2core#AnalyserPlugin +#define LV2_CORE__AudioPort LV2_CORE_PREFIX "AudioPort" ///< http://lv2plug.in/ns/lv2core#AudioPort +#define LV2_CORE__BandpassPlugin LV2_CORE_PREFIX "BandpassPlugin" ///< http://lv2plug.in/ns/lv2core#BandpassPlugin +#define LV2_CORE__CVPort LV2_CORE_PREFIX "CVPort" ///< http://lv2plug.in/ns/lv2core#CVPort +#define LV2_CORE__ChorusPlugin LV2_CORE_PREFIX "ChorusPlugin" ///< http://lv2plug.in/ns/lv2core#ChorusPlugin +#define LV2_CORE__CombPlugin LV2_CORE_PREFIX "CombPlugin" ///< http://lv2plug.in/ns/lv2core#CombPlugin +#define LV2_CORE__CompressorPlugin LV2_CORE_PREFIX "CompressorPlugin" ///< http://lv2plug.in/ns/lv2core#CompressorPlugin +#define LV2_CORE__ConstantPlugin LV2_CORE_PREFIX "ConstantPlugin" ///< http://lv2plug.in/ns/lv2core#ConstantPlugin +#define LV2_CORE__ControlPort LV2_CORE_PREFIX "ControlPort" ///< http://lv2plug.in/ns/lv2core#ControlPort +#define LV2_CORE__ConverterPlugin LV2_CORE_PREFIX "ConverterPlugin" ///< http://lv2plug.in/ns/lv2core#ConverterPlugin +#define LV2_CORE__DelayPlugin LV2_CORE_PREFIX "DelayPlugin" ///< http://lv2plug.in/ns/lv2core#DelayPlugin +#define LV2_CORE__DistortionPlugin LV2_CORE_PREFIX "DistortionPlugin" ///< http://lv2plug.in/ns/lv2core#DistortionPlugin +#define LV2_CORE__DynamicsPlugin LV2_CORE_PREFIX "DynamicsPlugin" ///< http://lv2plug.in/ns/lv2core#DynamicsPlugin +#define LV2_CORE__EQPlugin LV2_CORE_PREFIX "EQPlugin" ///< http://lv2plug.in/ns/lv2core#EQPlugin +#define LV2_CORE__EnvelopePlugin LV2_CORE_PREFIX "EnvelopePlugin" ///< http://lv2plug.in/ns/lv2core#EnvelopePlugin +#define LV2_CORE__ExpanderPlugin LV2_CORE_PREFIX "ExpanderPlugin" ///< http://lv2plug.in/ns/lv2core#ExpanderPlugin +#define LV2_CORE__ExtensionData LV2_CORE_PREFIX "ExtensionData" ///< http://lv2plug.in/ns/lv2core#ExtensionData +#define LV2_CORE__Feature LV2_CORE_PREFIX "Feature" ///< http://lv2plug.in/ns/lv2core#Feature +#define LV2_CORE__FilterPlugin LV2_CORE_PREFIX "FilterPlugin" ///< http://lv2plug.in/ns/lv2core#FilterPlugin +#define LV2_CORE__FlangerPlugin LV2_CORE_PREFIX "FlangerPlugin" ///< http://lv2plug.in/ns/lv2core#FlangerPlugin +#define LV2_CORE__FunctionPlugin LV2_CORE_PREFIX "FunctionPlugin" ///< http://lv2plug.in/ns/lv2core#FunctionPlugin +#define LV2_CORE__GatePlugin LV2_CORE_PREFIX "GatePlugin" ///< http://lv2plug.in/ns/lv2core#GatePlugin +#define LV2_CORE__GeneratorPlugin LV2_CORE_PREFIX "GeneratorPlugin" ///< http://lv2plug.in/ns/lv2core#GeneratorPlugin +#define LV2_CORE__HighpassPlugin LV2_CORE_PREFIX "HighpassPlugin" ///< http://lv2plug.in/ns/lv2core#HighpassPlugin +#define LV2_CORE__InputPort LV2_CORE_PREFIX "InputPort" ///< http://lv2plug.in/ns/lv2core#InputPort +#define LV2_CORE__InstrumentPlugin LV2_CORE_PREFIX "InstrumentPlugin" ///< http://lv2plug.in/ns/lv2core#InstrumentPlugin +#define LV2_CORE__LimiterPlugin LV2_CORE_PREFIX "LimiterPlugin" ///< http://lv2plug.in/ns/lv2core#LimiterPlugin +#define LV2_CORE__LowpassPlugin LV2_CORE_PREFIX "LowpassPlugin" ///< http://lv2plug.in/ns/lv2core#LowpassPlugin +#define LV2_CORE__MixerPlugin LV2_CORE_PREFIX "MixerPlugin" ///< http://lv2plug.in/ns/lv2core#MixerPlugin +#define LV2_CORE__ModulatorPlugin LV2_CORE_PREFIX "ModulatorPlugin" ///< http://lv2plug.in/ns/lv2core#ModulatorPlugin +#define LV2_CORE__MultiEQPlugin LV2_CORE_PREFIX "MultiEQPlugin" ///< http://lv2plug.in/ns/lv2core#MultiEQPlugin +#define LV2_CORE__OscillatorPlugin LV2_CORE_PREFIX "OscillatorPlugin" ///< http://lv2plug.in/ns/lv2core#OscillatorPlugin +#define LV2_CORE__OutputPort LV2_CORE_PREFIX "OutputPort" ///< http://lv2plug.in/ns/lv2core#OutputPort +#define LV2_CORE__ParaEQPlugin LV2_CORE_PREFIX "ParaEQPlugin" ///< http://lv2plug.in/ns/lv2core#ParaEQPlugin +#define LV2_CORE__PhaserPlugin LV2_CORE_PREFIX "PhaserPlugin" ///< http://lv2plug.in/ns/lv2core#PhaserPlugin +#define LV2_CORE__PitchPlugin LV2_CORE_PREFIX "PitchPlugin" ///< http://lv2plug.in/ns/lv2core#PitchPlugin +#define LV2_CORE__Plugin LV2_CORE_PREFIX "Plugin" ///< http://lv2plug.in/ns/lv2core#Plugin +#define LV2_CORE__PluginBase LV2_CORE_PREFIX "PluginBase" ///< http://lv2plug.in/ns/lv2core#PluginBase +#define LV2_CORE__Point LV2_CORE_PREFIX "Point" ///< http://lv2plug.in/ns/lv2core#Point +#define LV2_CORE__Port LV2_CORE_PREFIX "Port" ///< http://lv2plug.in/ns/lv2core#Port +#define LV2_CORE__PortProperty LV2_CORE_PREFIX "PortProperty" ///< http://lv2plug.in/ns/lv2core#PortProperty +#define LV2_CORE__Resource LV2_CORE_PREFIX "Resource" ///< http://lv2plug.in/ns/lv2core#Resource +#define LV2_CORE__ReverbPlugin LV2_CORE_PREFIX "ReverbPlugin" ///< http://lv2plug.in/ns/lv2core#ReverbPlugin +#define LV2_CORE__ScalePoint LV2_CORE_PREFIX "ScalePoint" ///< http://lv2plug.in/ns/lv2core#ScalePoint +#define LV2_CORE__SimulatorPlugin LV2_CORE_PREFIX "SimulatorPlugin" ///< http://lv2plug.in/ns/lv2core#SimulatorPlugin +#define LV2_CORE__SpatialPlugin LV2_CORE_PREFIX "SpatialPlugin" ///< http://lv2plug.in/ns/lv2core#SpatialPlugin +#define LV2_CORE__Specification LV2_CORE_PREFIX "Specification" ///< http://lv2plug.in/ns/lv2core#Specification +#define LV2_CORE__SpectralPlugin LV2_CORE_PREFIX "SpectralPlugin" ///< http://lv2plug.in/ns/lv2core#SpectralPlugin +#define LV2_CORE__UtilityPlugin LV2_CORE_PREFIX "UtilityPlugin" ///< http://lv2plug.in/ns/lv2core#UtilityPlugin +#define LV2_CORE__WaveshaperPlugin LV2_CORE_PREFIX "WaveshaperPlugin" ///< http://lv2plug.in/ns/lv2core#WaveshaperPlugin +#define LV2_CORE__appliesTo LV2_CORE_PREFIX "appliesTo" ///< http://lv2plug.in/ns/lv2core#appliesTo +#define LV2_CORE__binary LV2_CORE_PREFIX "binary" ///< http://lv2plug.in/ns/lv2core#binary +#define LV2_CORE__connectionOptional LV2_CORE_PREFIX "connectionOptional" ///< http://lv2plug.in/ns/lv2core#connectionOptional +#define LV2_CORE__control LV2_CORE_PREFIX "control" ///< http://lv2plug.in/ns/lv2core#control +#define LV2_CORE__default LV2_CORE_PREFIX "default" ///< http://lv2plug.in/ns/lv2core#default +#define LV2_CORE__designation LV2_CORE_PREFIX "designation" ///< http://lv2plug.in/ns/lv2core#designation +#define LV2_CORE__documentation LV2_CORE_PREFIX "documentation" ///< http://lv2plug.in/ns/lv2core#documentation +#define LV2_CORE__enumeration LV2_CORE_PREFIX "enumeration" ///< http://lv2plug.in/ns/lv2core#enumeration +#define LV2_CORE__extensionData LV2_CORE_PREFIX "extensionData" ///< http://lv2plug.in/ns/lv2core#extensionData +#define LV2_CORE__freeWheeling LV2_CORE_PREFIX "freeWheeling" ///< http://lv2plug.in/ns/lv2core#freeWheeling +#define LV2_CORE__hardRTCapable LV2_CORE_PREFIX "hardRTCapable" ///< http://lv2plug.in/ns/lv2core#hardRTCapable +#define LV2_CORE__inPlaceBroken LV2_CORE_PREFIX "inPlaceBroken" ///< http://lv2plug.in/ns/lv2core#inPlaceBroken +#define LV2_CORE__index LV2_CORE_PREFIX "index" ///< http://lv2plug.in/ns/lv2core#index +#define LV2_CORE__integer LV2_CORE_PREFIX "integer" ///< http://lv2plug.in/ns/lv2core#integer +#define LV2_CORE__isLive LV2_CORE_PREFIX "isLive" ///< http://lv2plug.in/ns/lv2core#isLive +#define LV2_CORE__latency LV2_CORE_PREFIX "latency" ///< http://lv2plug.in/ns/lv2core#latency +#define LV2_CORE__maximum LV2_CORE_PREFIX "maximum" ///< http://lv2plug.in/ns/lv2core#maximum +#define LV2_CORE__microVersion LV2_CORE_PREFIX "microVersion" ///< http://lv2plug.in/ns/lv2core#microVersion +#define LV2_CORE__minimum LV2_CORE_PREFIX "minimum" ///< http://lv2plug.in/ns/lv2core#minimum +#define LV2_CORE__minorVersion LV2_CORE_PREFIX "minorVersion" ///< http://lv2plug.in/ns/lv2core#minorVersion +#define LV2_CORE__name LV2_CORE_PREFIX "name" ///< http://lv2plug.in/ns/lv2core#name +#define LV2_CORE__optionalFeature LV2_CORE_PREFIX "optionalFeature" ///< http://lv2plug.in/ns/lv2core#optionalFeature +#define LV2_CORE__port LV2_CORE_PREFIX "port" ///< http://lv2plug.in/ns/lv2core#port +#define LV2_CORE__portProperty LV2_CORE_PREFIX "portProperty" ///< http://lv2plug.in/ns/lv2core#portProperty +#define LV2_CORE__project LV2_CORE_PREFIX "project" ///< http://lv2plug.in/ns/lv2core#project +#define LV2_CORE__prototype LV2_CORE_PREFIX "prototype" ///< http://lv2plug.in/ns/lv2core#prototype +#define LV2_CORE__reportsLatency LV2_CORE_PREFIX "reportsLatency" ///< http://lv2plug.in/ns/lv2core#reportsLatency +#define LV2_CORE__requiredFeature LV2_CORE_PREFIX "requiredFeature" ///< http://lv2plug.in/ns/lv2core#requiredFeature +#define LV2_CORE__sampleRate LV2_CORE_PREFIX "sampleRate" ///< http://lv2plug.in/ns/lv2core#sampleRate +#define LV2_CORE__scalePoint LV2_CORE_PREFIX "scalePoint" ///< http://lv2plug.in/ns/lv2core#scalePoint +#define LV2_CORE__symbol LV2_CORE_PREFIX "symbol" ///< http://lv2plug.in/ns/lv2core#symbol +#define LV2_CORE__toggled LV2_CORE_PREFIX "toggled" ///< http://lv2plug.in/ns/lv2core#toggled #ifdef __cplusplus extern "C" { @@ -135,7 +137,7 @@ typedef void * LV2_Handle; Features allow hosts to make additional functionality available to plugins without requiring modification to the LV2 API. Extensions may define new - features and specify the @ref URI and @ref data to be used if necessary. + features and specify the `URI` and `data` to be used if necessary. Some features, such as lv2:isLive, do not require the host to pass data. */ typedef struct _LV2_Feature { @@ -150,7 +152,7 @@ typedef struct _LV2_Feature { Pointer to arbitrary data. The format of this data is defined by the extension which describes the - feature with the given @ref URI. + feature with the given `URI`. */ void * data; } LV2_Feature; @@ -276,12 +278,12 @@ typedef struct _LV2_Descriptor { things that the plugin MUST NOT do within the run() function (see lv2core.ttl for details). - As a special case, when @p sample_count == 0, the plugin should update + As a special case, when `sample_count` is 0, the plugin should update any output ports that represent a single instant in time (e.g. control ports, but not audio ports). This is particularly useful for latent plugins, which should update their latency output port so hosts can pre-roll plugins to compute latency. Plugins MUST NOT crash when - @p sample_count == 0. + `sample_count` is 0. @param instance Instance to be run. @@ -341,13 +343,24 @@ typedef struct _LV2_Descriptor { } LV2_Descriptor; /** + Helper macro needed for LV2_SYMBOL_EXPORT when using C++. +*/ +#ifdef __cplusplus +# define LV2_SYMBOL_EXTERN extern "C" +#else +# define LV2_SYMBOL_EXTERN +#endif + +/** Put this (LV2_SYMBOL_EXPORT) before any functions that are to be loaded by the host as a symbol from the dynamic library. */ -#ifdef _WIN32 -# define LV2_SYMBOL_EXPORT __declspec(dllexport) +#if defined(__EMSCRIPTEN__) +# define LV2_SYMBOL_EXPORT LV2_SYMBOL_EXTERN __attribute__((used)) +#elif defined(_WIN32) +# define LV2_SYMBOL_EXPORT LV2_SYMBOL_EXTERN __declspec(dllexport) #else -# define LV2_SYMBOL_EXPORT +# define LV2_SYMBOL_EXPORT LV2_SYMBOL_EXTERN __attribute__((visibility("default"))) #endif /** @@ -368,9 +381,9 @@ typedef struct _LV2_Descriptor { function to find the LV2_Descriptor for the desired plugin. Plugins are accessed by index using values from 0 upwards. This function MUST return NULL for out of range indices, so the host can enumerate plugins by - increasing @p index until NULL is returned. + increasing `index` until NULL is returned. - Note that @p index has no meaning, hosts MUST NOT depend on it remaining + Note that `index` has no meaning, hosts MUST NOT depend on it remaining consistent between loads of the plugin library. */ LV2_SYMBOL_EXPORT @@ -418,7 +431,7 @@ typedef struct { Plugins are accessed by index using values from 0 upwards. Out of range indices MUST result in this function returning NULL, so the host can - enumerate plugins by increasing @a index until NULL is returned. + enumerate plugins by increasing `index` until NULL is returned. */ const LV2_Descriptor * (*get_plugin)(LV2_Lib_Handle handle, uint32_t index); @@ -440,6 +453,7 @@ typedef struct { be destroyed (using LV2_Lib_Descriptor::cleanup()) until all plugins loaded from that library have been destroyed. */ +LV2_SYMBOL_EXPORT const LV2_Lib_Descriptor * lv2_lib_descriptor(const char * bundle_path, const LV2_Feature *const * features); @@ -456,3 +470,7 @@ typedef const LV2_Lib_Descriptor * #endif #endif /* LV2_H_INCLUDED */ + +/** + @} +*/ diff --git a/distrho/src/lv2/lv2_kxstudio_properties.h b/distrho/src/lv2/lv2_kxstudio_properties.h @@ -1,6 +1,6 @@ /* LV2 KXStudio Properties Extension - Copyright 2014 Filipe Coelho <falktx@falktx.com> + Copyright 2014-2021 Filipe Coelho <falktx@falktx.com> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -26,7 +26,7 @@ #define LV2_KXSTUDIO_PROPERTIES_URI "http://kxstudio.sf.net/ns/lv2ext/props" #define LV2_KXSTUDIO_PROPERTIES_PREFIX LV2_KXSTUDIO_PROPERTIES_URI "#" -#define LV2_KXSTUDIO_PROPERTIES__NonAutomable LV2_KXSTUDIO_PROPERTIES_PREFIX "NonAutomable" +#define LV2_KXSTUDIO_PROPERTIES__NonAutomatable LV2_KXSTUDIO_PROPERTIES_PREFIX "NonAutomatable" #define LV2_KXSTUDIO_PROPERTIES__TimePositionTicksPerBeat LV2_KXSTUDIO_PROPERTIES_PREFIX "TimePositionTicksPerBeat" #define LV2_KXSTUDIO_PROPERTIES__TransientWindowId LV2_KXSTUDIO_PROPERTIES_PREFIX "TransientWindowId" diff --git a/distrho/src/lv2/midi.h b/distrho/src/lv2/midi.h @@ -1,5 +1,5 @@ /* - Copyright 2012 David Robillard <http://drobilla.net> + Copyright 2012-2016 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -15,8 +15,12 @@ */ /** - @file midi.h - C definitions for the LV2 MIDI extension <http://lv2plug.in/ns/ext/midi>. + @defgroup midi MIDI + + Definitions of standard MIDI messages, see <http://lv2plug.in/ns/ext/midi> + for details. + + @{ */ #ifndef LV2_MIDI_H @@ -30,50 +34,50 @@ extern "C" { # include <stdbool.h> #endif -#define LV2_MIDI_URI "http://lv2plug.in/ns/ext/midi" -#define LV2_MIDI_PREFIX LV2_MIDI_URI "#" - -#define LV2_MIDI__ActiveSense LV2_MIDI_PREFIX "ActiveSense" -#define LV2_MIDI__Aftertouch LV2_MIDI_PREFIX "Aftertouch" -#define LV2_MIDI__Bender LV2_MIDI_PREFIX "Bender" -#define LV2_MIDI__ChannelPressure LV2_MIDI_PREFIX "ChannelPressure" -#define LV2_MIDI__Chunk LV2_MIDI_PREFIX "Chunk" -#define LV2_MIDI__Clock LV2_MIDI_PREFIX "Clock" -#define LV2_MIDI__Continue LV2_MIDI_PREFIX "Continue" -#define LV2_MIDI__Controller LV2_MIDI_PREFIX "Controller" -#define LV2_MIDI__MidiEvent LV2_MIDI_PREFIX "MidiEvent" -#define LV2_MIDI__NoteOff LV2_MIDI_PREFIX "NoteOff" -#define LV2_MIDI__NoteOn LV2_MIDI_PREFIX "NoteOn" -#define LV2_MIDI__ProgramChange LV2_MIDI_PREFIX "ProgramChange" -#define LV2_MIDI__QuarterFrame LV2_MIDI_PREFIX "QuarterFrame" -#define LV2_MIDI__Reset LV2_MIDI_PREFIX "Reset" -#define LV2_MIDI__SongPosition LV2_MIDI_PREFIX "SongPosition" -#define LV2_MIDI__SongSelect LV2_MIDI_PREFIX "SongSelect" -#define LV2_MIDI__Start LV2_MIDI_PREFIX "Start" -#define LV2_MIDI__Stop LV2_MIDI_PREFIX "Stop" -#define LV2_MIDI__SystemCommon LV2_MIDI_PREFIX "SystemCommon" -#define LV2_MIDI__SystemExclusive LV2_MIDI_PREFIX "SystemExclusive" -#define LV2_MIDI__SystemMessage LV2_MIDI_PREFIX "SystemMessage" -#define LV2_MIDI__SystemRealtime LV2_MIDI_PREFIX "SystemRealtime" -#define LV2_MIDI__Tick LV2_MIDI_PREFIX "Tick" -#define LV2_MIDI__TuneRequest LV2_MIDI_PREFIX "TuneRequest" -#define LV2_MIDI__VoiceMessage LV2_MIDI_PREFIX "VoiceMessage" -#define LV2_MIDI__benderValue LV2_MIDI_PREFIX "benderValue" -#define LV2_MIDI__binding LV2_MIDI_PREFIX "binding" -#define LV2_MIDI__byteNumber LV2_MIDI_PREFIX "byteNumber" -#define LV2_MIDI__channel LV2_MIDI_PREFIX "channel" -#define LV2_MIDI__chunk LV2_MIDI_PREFIX "chunk" -#define LV2_MIDI__controllerNumber LV2_MIDI_PREFIX "controllerNumber" -#define LV2_MIDI__controllerValue LV2_MIDI_PREFIX "controllerValue" -#define LV2_MIDI__noteNumber LV2_MIDI_PREFIX "noteNumber" -#define LV2_MIDI__pressure LV2_MIDI_PREFIX "pressure" -#define LV2_MIDI__programNumber LV2_MIDI_PREFIX "programNumber" -#define LV2_MIDI__property LV2_MIDI_PREFIX "property" -#define LV2_MIDI__songNumber LV2_MIDI_PREFIX "songNumber" -#define LV2_MIDI__songPosition LV2_MIDI_PREFIX "songPosition" -#define LV2_MIDI__status LV2_MIDI_PREFIX "status" -#define LV2_MIDI__statusMask LV2_MIDI_PREFIX "statusMask" -#define LV2_MIDI__velocity LV2_MIDI_PREFIX "velocity" +#define LV2_MIDI_URI "http://lv2plug.in/ns/ext/midi" ///< http://lv2plug.in/ns/ext/midi +#define LV2_MIDI_PREFIX LV2_MIDI_URI "#" ///< http://lv2plug.in/ns/ext/midi# + +#define LV2_MIDI__ActiveSense LV2_MIDI_PREFIX "ActiveSense" ///< http://lv2plug.in/ns/ext/midi#ActiveSense +#define LV2_MIDI__Aftertouch LV2_MIDI_PREFIX "Aftertouch" ///< http://lv2plug.in/ns/ext/midi#Aftertouch +#define LV2_MIDI__Bender LV2_MIDI_PREFIX "Bender" ///< http://lv2plug.in/ns/ext/midi#Bender +#define LV2_MIDI__ChannelPressure LV2_MIDI_PREFIX "ChannelPressure" ///< http://lv2plug.in/ns/ext/midi#ChannelPressure +#define LV2_MIDI__Chunk LV2_MIDI_PREFIX "Chunk" ///< http://lv2plug.in/ns/ext/midi#Chunk +#define LV2_MIDI__Clock LV2_MIDI_PREFIX "Clock" ///< http://lv2plug.in/ns/ext/midi#Clock +#define LV2_MIDI__Continue LV2_MIDI_PREFIX "Continue" ///< http://lv2plug.in/ns/ext/midi#Continue +#define LV2_MIDI__Controller LV2_MIDI_PREFIX "Controller" ///< http://lv2plug.in/ns/ext/midi#Controller +#define LV2_MIDI__MidiEvent LV2_MIDI_PREFIX "MidiEvent" ///< http://lv2plug.in/ns/ext/midi#MidiEvent +#define LV2_MIDI__NoteOff LV2_MIDI_PREFIX "NoteOff" ///< http://lv2plug.in/ns/ext/midi#NoteOff +#define LV2_MIDI__NoteOn LV2_MIDI_PREFIX "NoteOn" ///< http://lv2plug.in/ns/ext/midi#NoteOn +#define LV2_MIDI__ProgramChange LV2_MIDI_PREFIX "ProgramChange" ///< http://lv2plug.in/ns/ext/midi#ProgramChange +#define LV2_MIDI__QuarterFrame LV2_MIDI_PREFIX "QuarterFrame" ///< http://lv2plug.in/ns/ext/midi#QuarterFrame +#define LV2_MIDI__Reset LV2_MIDI_PREFIX "Reset" ///< http://lv2plug.in/ns/ext/midi#Reset +#define LV2_MIDI__SongPosition LV2_MIDI_PREFIX "SongPosition" ///< http://lv2plug.in/ns/ext/midi#SongPosition +#define LV2_MIDI__SongSelect LV2_MIDI_PREFIX "SongSelect" ///< http://lv2plug.in/ns/ext/midi#SongSelect +#define LV2_MIDI__Start LV2_MIDI_PREFIX "Start" ///< http://lv2plug.in/ns/ext/midi#Start +#define LV2_MIDI__Stop LV2_MIDI_PREFIX "Stop" ///< http://lv2plug.in/ns/ext/midi#Stop +#define LV2_MIDI__SystemCommon LV2_MIDI_PREFIX "SystemCommon" ///< http://lv2plug.in/ns/ext/midi#SystemCommon +#define LV2_MIDI__SystemExclusive LV2_MIDI_PREFIX "SystemExclusive" ///< http://lv2plug.in/ns/ext/midi#SystemExclusive +#define LV2_MIDI__SystemMessage LV2_MIDI_PREFIX "SystemMessage" ///< http://lv2plug.in/ns/ext/midi#SystemMessage +#define LV2_MIDI__SystemRealtime LV2_MIDI_PREFIX "SystemRealtime" ///< http://lv2plug.in/ns/ext/midi#SystemRealtime +#define LV2_MIDI__Tick LV2_MIDI_PREFIX "Tick" ///< http://lv2plug.in/ns/ext/midi#Tick +#define LV2_MIDI__TuneRequest LV2_MIDI_PREFIX "TuneRequest" ///< http://lv2plug.in/ns/ext/midi#TuneRequest +#define LV2_MIDI__VoiceMessage LV2_MIDI_PREFIX "VoiceMessage" ///< http://lv2plug.in/ns/ext/midi#VoiceMessage +#define LV2_MIDI__benderValue LV2_MIDI_PREFIX "benderValue" ///< http://lv2plug.in/ns/ext/midi#benderValue +#define LV2_MIDI__binding LV2_MIDI_PREFIX "binding" ///< http://lv2plug.in/ns/ext/midi#binding +#define LV2_MIDI__byteNumber LV2_MIDI_PREFIX "byteNumber" ///< http://lv2plug.in/ns/ext/midi#byteNumber +#define LV2_MIDI__channel LV2_MIDI_PREFIX "channel" ///< http://lv2plug.in/ns/ext/midi#channel +#define LV2_MIDI__chunk LV2_MIDI_PREFIX "chunk" ///< http://lv2plug.in/ns/ext/midi#chunk +#define LV2_MIDI__controllerNumber LV2_MIDI_PREFIX "controllerNumber" ///< http://lv2plug.in/ns/ext/midi#controllerNumber +#define LV2_MIDI__controllerValue LV2_MIDI_PREFIX "controllerValue" ///< http://lv2plug.in/ns/ext/midi#controllerValue +#define LV2_MIDI__noteNumber LV2_MIDI_PREFIX "noteNumber" ///< http://lv2plug.in/ns/ext/midi#noteNumber +#define LV2_MIDI__pressure LV2_MIDI_PREFIX "pressure" ///< http://lv2plug.in/ns/ext/midi#pressure +#define LV2_MIDI__programNumber LV2_MIDI_PREFIX "programNumber" ///< http://lv2plug.in/ns/ext/midi#programNumber +#define LV2_MIDI__property LV2_MIDI_PREFIX "property" ///< http://lv2plug.in/ns/ext/midi#property +#define LV2_MIDI__songNumber LV2_MIDI_PREFIX "songNumber" ///< http://lv2plug.in/ns/ext/midi#songNumber +#define LV2_MIDI__songPosition LV2_MIDI_PREFIX "songPosition" ///< http://lv2plug.in/ns/ext/midi#songPosition +#define LV2_MIDI__status LV2_MIDI_PREFIX "status" ///< http://lv2plug.in/ns/ext/midi#status +#define LV2_MIDI__statusMask LV2_MIDI_PREFIX "statusMask" ///< http://lv2plug.in/ns/ext/midi#statusMask +#define LV2_MIDI__velocity LV2_MIDI_PREFIX "velocity" ///< http://lv2plug.in/ns/ext/midi#velocity /** MIDI Message Type. @@ -184,7 +188,7 @@ typedef enum { } LV2_Midi_Controller; /** - Return true iff @p msg is a MIDI voice message (which has a channel). + Return true iff `msg` is a MIDI voice message (which has a channel). */ static inline bool lv2_midi_is_voice_message(const uint8_t* msg) { @@ -192,7 +196,7 @@ lv2_midi_is_voice_message(const uint8_t* msg) { } /** - Return true iff @p msg is a MIDI system message (which has no channel). + Return true iff `msg` is a MIDI system message (which has no channel). */ static inline bool lv2_midi_is_system_message(const uint8_t* msg) { @@ -224,3 +228,7 @@ lv2_midi_message_type(const uint8_t* msg) { #endif #endif /* LV2_MIDI_H */ + +/** + @} +*/ diff --git a/distrho/src/lv2/morph.h b/distrho/src/lv2/morph.h @@ -1,5 +1,5 @@ /* - Copyright 2012 David Robillard <http://drobilla.net> + Copyright 2012-2016 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -14,21 +14,29 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#ifndef LV2_MORPH_H -#define LV2_MORPH_H +/** + @defgroup morph Morph -#include <stdint.h> + Ports that can dynamically change type, see <http://lv2plug.in/ns/ext/morph> + for details. + + @{ +*/ -#include "lv2.h" -#include "urid.h" +#ifndef LV2_MORPH_H +#define LV2_MORPH_H -#define LV2_MORPH_URI "http://lv2plug.in/ns/ext/morph" -#define LV2_MORPH_PREFIX LV2_MORPH_URI "#" +#define LV2_MORPH_URI "http://lv2plug.in/ns/ext/morph" ///< http://lv2plug.in/ns/ext/morph +#define LV2_MORPH_PREFIX LV2_MORPH_URI "#" ///< http://lv2plug.in/ns/ext/morph# -#define LV2_MORPH__AutoMorphPort LV2_MORPH_PREFIX "AutoMorphPort" -#define LV2_MORPH__MorphPort LV2_MORPH_PREFIX "MorphPort" -#define LV2_MORPH__interface LV2_MORPH_PREFIX "interface" -#define LV2_MORPH__supportsType LV2_MORPH_PREFIX "supportsType" -#define LV2_MORPH__currentType LV2_MORPH_PREFIX "currentType" +#define LV2_MORPH__AutoMorphPort LV2_MORPH_PREFIX "AutoMorphPort" ///< http://lv2plug.in/ns/ext/morph#AutoMorphPort +#define LV2_MORPH__MorphPort LV2_MORPH_PREFIX "MorphPort" ///< http://lv2plug.in/ns/ext/morph#MorphPort +#define LV2_MORPH__interface LV2_MORPH_PREFIX "interface" ///< http://lv2plug.in/ns/ext/morph#interface +#define LV2_MORPH__supportsType LV2_MORPH_PREFIX "supportsType" ///< http://lv2plug.in/ns/ext/morph#supportsType +#define LV2_MORPH__currentType LV2_MORPH_PREFIX "currentType" ///< http://lv2plug.in/ns/ext/morph#currentType #endif /* LV2_MORPH_H */ + +/** + @} +*/ diff --git a/distrho/src/lv2/options.h b/distrho/src/lv2/options.h @@ -1,5 +1,5 @@ /* - Copyright 2012 David Robillard <http://drobilla.net> + Copyright 2012-2016 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -14,6 +14,15 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +/** + @defgroup options Options + + Instantiation time options, see <http://lv2plug.in/ns/ext/options> for + details. + + @{ +*/ + #ifndef LV2_OPTIONS_H #define LV2_OPTIONS_H @@ -22,14 +31,14 @@ #include "urid.h" #include "lv2.h" -#define LV2_OPTIONS_URI "http://lv2plug.in/ns/ext/options" -#define LV2_OPTIONS_PREFIX LV2_OPTIONS_URI "#" +#define LV2_OPTIONS_URI "http://lv2plug.in/ns/ext/options" ///< http://lv2plug.in/ns/ext/options +#define LV2_OPTIONS_PREFIX LV2_OPTIONS_URI "#" ///< http://lv2plug.in/ns/ext/options# -#define LV2_OPTIONS__Option LV2_OPTIONS_PREFIX "Option" -#define LV2_OPTIONS__interface LV2_OPTIONS_PREFIX "interface" -#define LV2_OPTIONS__options LV2_OPTIONS_PREFIX "options" -#define LV2_OPTIONS__requiredOption LV2_OPTIONS_PREFIX "requiredOption" -#define LV2_OPTIONS__supportedOption LV2_OPTIONS_PREFIX "supportedOption" +#define LV2_OPTIONS__Option LV2_OPTIONS_PREFIX "Option" ///< http://lv2plug.in/ns/ext/options#Option +#define LV2_OPTIONS__interface LV2_OPTIONS_PREFIX "interface" ///< http://lv2plug.in/ns/ext/options#interface +#define LV2_OPTIONS__options LV2_OPTIONS_PREFIX "options" ///< http://lv2plug.in/ns/ext/options#options +#define LV2_OPTIONS__requiredOption LV2_OPTIONS_PREFIX "requiredOption" ///< http://lv2plug.in/ns/ext/options#requiredOption +#define LV2_OPTIONS__supportedOption LV2_OPTIONS_PREFIX "supportedOption" ///< http://lv2plug.in/ns/ext/options#supportedOption #ifdef __cplusplus extern "C" { @@ -130,3 +139,7 @@ typedef struct _LV2_Options_Interface { #endif #endif /* LV2_OPTIONS_H */ + +/** + @} +*/ diff --git a/distrho/src/lv2/parameters.h b/distrho/src/lv2/parameters.h @@ -1,5 +1,5 @@ /* - Copyright 2012 David Robillard <http://drobilla.net> + Copyright 2012-2016 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -14,36 +14,49 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +/** + @defgroup parameters Parameters + + Common parameters for audio processing, see + <http://lv2plug.in/ns/ext/parameters>. + + @{ +*/ + #ifndef LV2_PARAMETERS_H #define LV2_PARAMETERS_H -#define LV2_PARAMETERS_URI "http://lv2plug.in/ns/ext/parameters" -#define LV2_PARAMETERS_PREFIX LV2_PARAMETERS_URI "#" - -#define LV2_PARAMETERS__CompressorControls LV2_PARAMETERS_PREFIX "CompressorControls" -#define LV2_PARAMETERS__ControlGroup LV2_PARAMETERS_PREFIX "ControlGroup" -#define LV2_PARAMETERS__EnvelopeControls LV2_PARAMETERS_PREFIX "EnvelopeControls" -#define LV2_PARAMETERS__FilterControls LV2_PARAMETERS_PREFIX "FilterControls" -#define LV2_PARAMETERS__OscillatorControls LV2_PARAMETERS_PREFIX "OscillatorControls" -#define LV2_PARAMETERS__amplitude LV2_PARAMETERS_PREFIX "amplitude" -#define LV2_PARAMETERS__attack LV2_PARAMETERS_PREFIX "attack" -#define LV2_PARAMETERS__bypass LV2_PARAMETERS_PREFIX "bypass" -#define LV2_PARAMETERS__cutoffFrequency LV2_PARAMETERS_PREFIX "cutoffFrequency" -#define LV2_PARAMETERS__decay LV2_PARAMETERS_PREFIX "decay" -#define LV2_PARAMETERS__delay LV2_PARAMETERS_PREFIX "delay" -#define LV2_PARAMETERS__dryLevel LV2_PARAMETERS_PREFIX "dryLevel" -#define LV2_PARAMETERS__frequency LV2_PARAMETERS_PREFIX "frequency" -#define LV2_PARAMETERS__gain LV2_PARAMETERS_PREFIX "gain" -#define LV2_PARAMETERS__hold LV2_PARAMETERS_PREFIX "hold" -#define LV2_PARAMETERS__pulseWidth LV2_PARAMETERS_PREFIX "pulseWidth" -#define LV2_PARAMETERS__ratio LV2_PARAMETERS_PREFIX "ratio" -#define LV2_PARAMETERS__release LV2_PARAMETERS_PREFIX "release" -#define LV2_PARAMETERS__resonance LV2_PARAMETERS_PREFIX "resonance" -#define LV2_PARAMETERS__sampleRate LV2_PARAMETERS_PREFIX "sampleRate" -#define LV2_PARAMETERS__sustain LV2_PARAMETERS_PREFIX "sustain" -#define LV2_PARAMETERS__threshold LV2_PARAMETERS_PREFIX "threshold" -#define LV2_PARAMETERS__waveform LV2_PARAMETERS_PREFIX "waveform" -#define LV2_PARAMETERS__wetDryRatio LV2_PARAMETERS_PREFIX "wetDryRatio" -#define LV2_PARAMETERS__wetLevel LV2_PARAMETERS_PREFIX "wetLevel" +#define LV2_PARAMETERS_URI "http://lv2plug.in/ns/ext/parameters" ///< http://lv2plug.in/ns/ext/parameters +#define LV2_PARAMETERS_PREFIX LV2_PARAMETERS_URI "#" ///< http://lv2plug.in/ns/ext/parameters# + +#define LV2_PARAMETERS__CompressorControls LV2_PARAMETERS_PREFIX "CompressorControls" ///< http://lv2plug.in/ns/ext/parameters#CompressorControls +#define LV2_PARAMETERS__ControlGroup LV2_PARAMETERS_PREFIX "ControlGroup" ///< http://lv2plug.in/ns/ext/parameters#ControlGroup +#define LV2_PARAMETERS__EnvelopeControls LV2_PARAMETERS_PREFIX "EnvelopeControls" ///< http://lv2plug.in/ns/ext/parameters#EnvelopeControls +#define LV2_PARAMETERS__FilterControls LV2_PARAMETERS_PREFIX "FilterControls" ///< http://lv2plug.in/ns/ext/parameters#FilterControls +#define LV2_PARAMETERS__OscillatorControls LV2_PARAMETERS_PREFIX "OscillatorControls" ///< http://lv2plug.in/ns/ext/parameters#OscillatorControls +#define LV2_PARAMETERS__amplitude LV2_PARAMETERS_PREFIX "amplitude" ///< http://lv2plug.in/ns/ext/parameters#amplitude +#define LV2_PARAMETERS__attack LV2_PARAMETERS_PREFIX "attack" ///< http://lv2plug.in/ns/ext/parameters#attack +#define LV2_PARAMETERS__bypass LV2_PARAMETERS_PREFIX "bypass" ///< http://lv2plug.in/ns/ext/parameters#bypass +#define LV2_PARAMETERS__cutoffFrequency LV2_PARAMETERS_PREFIX "cutoffFrequency" ///< http://lv2plug.in/ns/ext/parameters#cutoffFrequency +#define LV2_PARAMETERS__decay LV2_PARAMETERS_PREFIX "decay" ///< http://lv2plug.in/ns/ext/parameters#decay +#define LV2_PARAMETERS__delay LV2_PARAMETERS_PREFIX "delay" ///< http://lv2plug.in/ns/ext/parameters#delay +#define LV2_PARAMETERS__dryLevel LV2_PARAMETERS_PREFIX "dryLevel" ///< http://lv2plug.in/ns/ext/parameters#dryLevel +#define LV2_PARAMETERS__frequency LV2_PARAMETERS_PREFIX "frequency" ///< http://lv2plug.in/ns/ext/parameters#frequency +#define LV2_PARAMETERS__gain LV2_PARAMETERS_PREFIX "gain" ///< http://lv2plug.in/ns/ext/parameters#gain +#define LV2_PARAMETERS__hold LV2_PARAMETERS_PREFIX "hold" ///< http://lv2plug.in/ns/ext/parameters#hold +#define LV2_PARAMETERS__pulseWidth LV2_PARAMETERS_PREFIX "pulseWidth" ///< http://lv2plug.in/ns/ext/parameters#pulseWidth +#define LV2_PARAMETERS__ratio LV2_PARAMETERS_PREFIX "ratio" ///< http://lv2plug.in/ns/ext/parameters#ratio +#define LV2_PARAMETERS__release LV2_PARAMETERS_PREFIX "release" ///< http://lv2plug.in/ns/ext/parameters#release +#define LV2_PARAMETERS__resonance LV2_PARAMETERS_PREFIX "resonance" ///< http://lv2plug.in/ns/ext/parameters#resonance +#define LV2_PARAMETERS__sampleRate LV2_PARAMETERS_PREFIX "sampleRate" ///< http://lv2plug.in/ns/ext/parameters#sampleRate +#define LV2_PARAMETERS__sustain LV2_PARAMETERS_PREFIX "sustain" ///< http://lv2plug.in/ns/ext/parameters#sustain +#define LV2_PARAMETERS__threshold LV2_PARAMETERS_PREFIX "threshold" ///< http://lv2plug.in/ns/ext/parameters#threshold +#define LV2_PARAMETERS__waveform LV2_PARAMETERS_PREFIX "waveform" ///< http://lv2plug.in/ns/ext/parameters#waveform +#define LV2_PARAMETERS__wetDryRatio LV2_PARAMETERS_PREFIX "wetDryRatio" ///< http://lv2plug.in/ns/ext/parameters#wetDryRatio +#define LV2_PARAMETERS__wetLevel LV2_PARAMETERS_PREFIX "wetLevel" ///< http://lv2plug.in/ns/ext/parameters#wetLevel #endif /* LV2_PARAMETERS_H */ + +/** + @} +*/ diff --git a/distrho/src/lv2/patch.h b/distrho/src/lv2/patch.h @@ -1,5 +1,5 @@ /* - Copyright 2012 David Robillard <http://drobilla.net> + Copyright 2012-2016 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -15,42 +15,52 @@ */ /** - @file patch.h C header for the LV2 Patch extension - <http://lv2plug.in/ns/ext/patch>. + @defgroup patch Patch - The patch extension is purely data, this header merely defines URIs - for convenience. + Messages for accessing and manipulating properties, see + <http://lv2plug.in/ns/ext/patch> for details. + + Note the patch extension is purely data, this header merely defines URIs for + convenience. + + @{ */ #ifndef LV2_PATCH_H #define LV2_PATCH_H -#define LV2_PATCH_URI "http://lv2plug.in/ns/ext/patch" -#define LV2_PATCH_PREFIX LV2_PATCH_URI "#" - -#define LV2_PATCH__Ack LV2_PATCH_PREFIX "Ack" -#define LV2_PATCH__Delete LV2_PATCH_PREFIX "Delete" -#define LV2_PATCH__Error LV2_PATCH_PREFIX "Error" -#define LV2_PATCH__Get LV2_PATCH_PREFIX "Get" -#define LV2_PATCH__Message LV2_PATCH_PREFIX "Message" -#define LV2_PATCH__Move LV2_PATCH_PREFIX "Move" -#define LV2_PATCH__Patch LV2_PATCH_PREFIX "Patch" -#define LV2_PATCH__Post LV2_PATCH_PREFIX "Post" -#define LV2_PATCH__Put LV2_PATCH_PREFIX "Put" -#define LV2_PATCH__Request LV2_PATCH_PREFIX "Request" -#define LV2_PATCH__Response LV2_PATCH_PREFIX "Response" -#define LV2_PATCH__Set LV2_PATCH_PREFIX "Set" -#define LV2_PATCH__add LV2_PATCH_PREFIX "add" -#define LV2_PATCH__body LV2_PATCH_PREFIX "body" -#define LV2_PATCH__destination LV2_PATCH_PREFIX "destination" -#define LV2_PATCH__property LV2_PATCH_PREFIX "property" -#define LV2_PATCH__readable LV2_PATCH_PREFIX "readable" -#define LV2_PATCH__remove LV2_PATCH_PREFIX "remove" -#define LV2_PATCH__request LV2_PATCH_PREFIX "request" -#define LV2_PATCH__subject LV2_PATCH_PREFIX "subject" -#define LV2_PATCH__sequenceNumber LV2_PATCH_PREFIX "sequenceNumber" -#define LV2_PATCH__value LV2_PATCH_PREFIX "value" -#define LV2_PATCH__wildcard LV2_PATCH_PREFIX "wildcard" -#define LV2_PATCH__writable LV2_PATCH_PREFIX "writable" +#define LV2_PATCH_URI "http://lv2plug.in/ns/ext/patch" ///< http://lv2plug.in/ns/ext/patch +#define LV2_PATCH_PREFIX LV2_PATCH_URI "#" ///< http://lv2plug.in/ns/ext/patch# + +#define LV2_PATCH__Ack LV2_PATCH_PREFIX "Ack" ///< http://lv2plug.in/ns/ext/patch#Ack +#define LV2_PATCH__Delete LV2_PATCH_PREFIX "Delete" ///< http://lv2plug.in/ns/ext/patch#Delete +#define LV2_PATCH__Copy LV2_PATCH_PREFIX "Copy" ///< http://lv2plug.in/ns/ext/patch#Copy +#define LV2_PATCH__Error LV2_PATCH_PREFIX "Error" ///< http://lv2plug.in/ns/ext/patch#Error +#define LV2_PATCH__Get LV2_PATCH_PREFIX "Get" ///< http://lv2plug.in/ns/ext/patch#Get +#define LV2_PATCH__Message LV2_PATCH_PREFIX "Message" ///< http://lv2plug.in/ns/ext/patch#Message +#define LV2_PATCH__Move LV2_PATCH_PREFIX "Move" ///< http://lv2plug.in/ns/ext/patch#Move +#define LV2_PATCH__Patch LV2_PATCH_PREFIX "Patch" ///< http://lv2plug.in/ns/ext/patch#Patch +#define LV2_PATCH__Post LV2_PATCH_PREFIX "Post" ///< http://lv2plug.in/ns/ext/patch#Post +#define LV2_PATCH__Put LV2_PATCH_PREFIX "Put" ///< http://lv2plug.in/ns/ext/patch#Put +#define LV2_PATCH__Request LV2_PATCH_PREFIX "Request" ///< http://lv2plug.in/ns/ext/patch#Request +#define LV2_PATCH__Response LV2_PATCH_PREFIX "Response" ///< http://lv2plug.in/ns/ext/patch#Response +#define LV2_PATCH__Set LV2_PATCH_PREFIX "Set" ///< http://lv2plug.in/ns/ext/patch#Set +#define LV2_PATCH__accept LV2_PATCH_PREFIX "accept" ///< http://lv2plug.in/ns/ext/patch#accept +#define LV2_PATCH__add LV2_PATCH_PREFIX "add" ///< http://lv2plug.in/ns/ext/patch#add +#define LV2_PATCH__body LV2_PATCH_PREFIX "body" ///< http://lv2plug.in/ns/ext/patch#body +#define LV2_PATCH__destination LV2_PATCH_PREFIX "destination" ///< http://lv2plug.in/ns/ext/patch#destination +#define LV2_PATCH__property LV2_PATCH_PREFIX "property" ///< http://lv2plug.in/ns/ext/patch#property +#define LV2_PATCH__readable LV2_PATCH_PREFIX "readable" ///< http://lv2plug.in/ns/ext/patch#readable +#define LV2_PATCH__remove LV2_PATCH_PREFIX "remove" ///< http://lv2plug.in/ns/ext/patch#remove +#define LV2_PATCH__request LV2_PATCH_PREFIX "request" ///< http://lv2plug.in/ns/ext/patch#request +#define LV2_PATCH__subject LV2_PATCH_PREFIX "subject" ///< http://lv2plug.in/ns/ext/patch#subject +#define LV2_PATCH__sequenceNumber LV2_PATCH_PREFIX "sequenceNumber" ///< http://lv2plug.in/ns/ext/patch#sequenceNumber +#define LV2_PATCH__value LV2_PATCH_PREFIX "value" ///< http://lv2plug.in/ns/ext/patch#value +#define LV2_PATCH__wildcard LV2_PATCH_PREFIX "wildcard" ///< http://lv2plug.in/ns/ext/patch#wildcard +#define LV2_PATCH__writable LV2_PATCH_PREFIX "writable" ///< http://lv2plug.in/ns/ext/patch#writable #endif /* LV2_PATCH_H */ + +/** + @} +*/ diff --git a/distrho/src/lv2/port-groups.h b/distrho/src/lv2/port-groups.h @@ -1,5 +1,5 @@ /* - Copyright 2012 David Robillard <http://drobilla.net> + Copyright 2012-2016 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -15,50 +15,57 @@ */ /** - @file port-groups.h - C definitions for the LV2 Port Groups extension - <http://lv2plug.in/ns/ext/port-groups>. + @defgroup port-groups Port Groups + + Multi-channel groups of LV2 ports, see + <http://lv2plug.in/ns/ext/port-groups> for details. + + @{ */ #ifndef LV2_PORT_GROUPS_H #define LV2_PORT_GROUPS_H -#define LV2_PORT_GROUPS_URI "http://lv2plug.in/ns/ext/port-groups" -#define LV2_PORT_GROUPS_PREFIX LV2_PORT_GROUPS_URI "#" +#define LV2_PORT_GROUPS_URI "http://lv2plug.in/ns/ext/port-groups" ///< http://lv2plug.in/ns/ext/port-groups +#define LV2_PORT_GROUPS_PREFIX LV2_PORT_GROUPS_URI "#" ///< http://lv2plug.in/ns/ext/port-groups# -#define LV2_PORT_GROUPS__DiscreteGroup LV2_PORT_GROUPS_PREFIX "DiscreteGroup" -#define LV2_PORT_GROUPS__Element LV2_PORT_GROUPS_PREFIX "Element" -#define LV2_PORT_GROUPS__FivePointOneGroup LV2_PORT_GROUPS_PREFIX "FivePointOneGroup" -#define LV2_PORT_GROUPS__FivePointZeroGroup LV2_PORT_GROUPS_PREFIX "FivePointZeroGroup" -#define LV2_PORT_GROUPS__FourPointZeroGroup LV2_PORT_GROUPS_PREFIX "FourPointZeroGroup" -#define LV2_PORT_GROUPS__Group LV2_PORT_GROUPS_PREFIX "Group" -#define LV2_PORT_GROUPS__InputGroup LV2_PORT_GROUPS_PREFIX "InputGroup" -#define LV2_PORT_GROUPS__MidSideGroup LV2_PORT_GROUPS_PREFIX "MidSideGroup" -#define LV2_PORT_GROUPS__MonoGroup LV2_PORT_GROUPS_PREFIX "MonoGroup" -#define LV2_PORT_GROUPS__OutputGroup LV2_PORT_GROUPS_PREFIX "OutputGroup" -#define LV2_PORT_GROUPS__SevenPointOneGroup LV2_PORT_GROUPS_PREFIX "SevenPointOneGroup" -#define LV2_PORT_GROUPS__SevenPointOneWideGroup LV2_PORT_GROUPS_PREFIX "SevenPointOneWideGroup" -#define LV2_PORT_GROUPS__SixPointOneGroup LV2_PORT_GROUPS_PREFIX "SixPointOneGroup" -#define LV2_PORT_GROUPS__StereoGroup LV2_PORT_GROUPS_PREFIX "StereoGroup" -#define LV2_PORT_GROUPS__ThreePointZeroGroup LV2_PORT_GROUPS_PREFIX "ThreePointZeroGroup" -#define LV2_PORT_GROUPS__center LV2_PORT_GROUPS_PREFIX "center" -#define LV2_PORT_GROUPS__centerLeft LV2_PORT_GROUPS_PREFIX "centerLeft" -#define LV2_PORT_GROUPS__centerRight LV2_PORT_GROUPS_PREFIX "centerRight" -#define LV2_PORT_GROUPS__element LV2_PORT_GROUPS_PREFIX "element" -#define LV2_PORT_GROUPS__group LV2_PORT_GROUPS_PREFIX "group" -#define LV2_PORT_GROUPS__left LV2_PORT_GROUPS_PREFIX "left" -#define LV2_PORT_GROUPS__lowFrequencyEffects LV2_PORT_GROUPS_PREFIX "lowFrequencyEffects" -#define LV2_PORT_GROUPS__mainInput LV2_PORT_GROUPS_PREFIX "mainInput" -#define LV2_PORT_GROUPS__mainOutput LV2_PORT_GROUPS_PREFIX "mainOutput" -#define LV2_PORT_GROUPS__rearCenter LV2_PORT_GROUPS_PREFIX "rearCenter" -#define LV2_PORT_GROUPS__rearLeft LV2_PORT_GROUPS_PREFIX "rearLeft" -#define LV2_PORT_GROUPS__rearRight LV2_PORT_GROUPS_PREFIX "rearRight" -#define LV2_PORT_GROUPS__right LV2_PORT_GROUPS_PREFIX "right" -#define LV2_PORT_GROUPS__side LV2_PORT_GROUPS_PREFIX "side" -#define LV2_PORT_GROUPS__sideChainOf LV2_PORT_GROUPS_PREFIX "sideChainOf" -#define LV2_PORT_GROUPS__sideLeft LV2_PORT_GROUPS_PREFIX "sideLeft" -#define LV2_PORT_GROUPS__sideRight LV2_PORT_GROUPS_PREFIX "sideRight" -#define LV2_PORT_GROUPS__source LV2_PORT_GROUPS_PREFIX "source" -#define LV2_PORT_GROUPS__subGroupOf LV2_PORT_GROUPS_PREFIX "subGroupOf" +#define LV2_PORT_GROUPS__DiscreteGroup LV2_PORT_GROUPS_PREFIX "DiscreteGroup" ///< http://lv2plug.in/ns/ext/port-groups#DiscreteGroup +#define LV2_PORT_GROUPS__Element LV2_PORT_GROUPS_PREFIX "Element" ///< http://lv2plug.in/ns/ext/port-groups#Element +#define LV2_PORT_GROUPS__FivePointOneGroup LV2_PORT_GROUPS_PREFIX "FivePointOneGroup" ///< http://lv2plug.in/ns/ext/port-groups#FivePointOneGroup +#define LV2_PORT_GROUPS__FivePointZeroGroup LV2_PORT_GROUPS_PREFIX "FivePointZeroGroup" ///< http://lv2plug.in/ns/ext/port-groups#FivePointZeroGroup +#define LV2_PORT_GROUPS__FourPointZeroGroup LV2_PORT_GROUPS_PREFIX "FourPointZeroGroup" ///< http://lv2plug.in/ns/ext/port-groups#FourPointZeroGroup +#define LV2_PORT_GROUPS__Group LV2_PORT_GROUPS_PREFIX "Group" ///< http://lv2plug.in/ns/ext/port-groups#Group +#define LV2_PORT_GROUPS__InputGroup LV2_PORT_GROUPS_PREFIX "InputGroup" ///< http://lv2plug.in/ns/ext/port-groups#InputGroup +#define LV2_PORT_GROUPS__MidSideGroup LV2_PORT_GROUPS_PREFIX "MidSideGroup" ///< http://lv2plug.in/ns/ext/port-groups#MidSideGroup +#define LV2_PORT_GROUPS__MonoGroup LV2_PORT_GROUPS_PREFIX "MonoGroup" ///< http://lv2plug.in/ns/ext/port-groups#MonoGroup +#define LV2_PORT_GROUPS__OutputGroup LV2_PORT_GROUPS_PREFIX "OutputGroup" ///< http://lv2plug.in/ns/ext/port-groups#OutputGroup +#define LV2_PORT_GROUPS__SevenPointOneGroup LV2_PORT_GROUPS_PREFIX "SevenPointOneGroup" ///< http://lv2plug.in/ns/ext/port-groups#SevenPointOneGroup +#define LV2_PORT_GROUPS__SevenPointOneWideGroup LV2_PORT_GROUPS_PREFIX "SevenPointOneWideGroup" ///< http://lv2plug.in/ns/ext/port-groups#SevenPointOneWideGroup +#define LV2_PORT_GROUPS__SixPointOneGroup LV2_PORT_GROUPS_PREFIX "SixPointOneGroup" ///< http://lv2plug.in/ns/ext/port-groups#SixPointOneGroup +#define LV2_PORT_GROUPS__StereoGroup LV2_PORT_GROUPS_PREFIX "StereoGroup" ///< http://lv2plug.in/ns/ext/port-groups#StereoGroup +#define LV2_PORT_GROUPS__ThreePointZeroGroup LV2_PORT_GROUPS_PREFIX "ThreePointZeroGroup" ///< http://lv2plug.in/ns/ext/port-groups#ThreePointZeroGroup +#define LV2_PORT_GROUPS__center LV2_PORT_GROUPS_PREFIX "center" ///< http://lv2plug.in/ns/ext/port-groups#center +#define LV2_PORT_GROUPS__centerLeft LV2_PORT_GROUPS_PREFIX "centerLeft" ///< http://lv2plug.in/ns/ext/port-groups#centerLeft +#define LV2_PORT_GROUPS__centerRight LV2_PORT_GROUPS_PREFIX "centerRight" ///< http://lv2plug.in/ns/ext/port-groups#centerRight +#define LV2_PORT_GROUPS__element LV2_PORT_GROUPS_PREFIX "element" ///< http://lv2plug.in/ns/ext/port-groups#element +#define LV2_PORT_GROUPS__group LV2_PORT_GROUPS_PREFIX "group" ///< http://lv2plug.in/ns/ext/port-groups#group +#define LV2_PORT_GROUPS__left LV2_PORT_GROUPS_PREFIX "left" ///< http://lv2plug.in/ns/ext/port-groups#left +#define LV2_PORT_GROUPS__lowFrequencyEffects LV2_PORT_GROUPS_PREFIX "lowFrequencyEffects" ///< http://lv2plug.in/ns/ext/port-groups#lowFrequencyEffects +#define LV2_PORT_GROUPS__mainInput LV2_PORT_GROUPS_PREFIX "mainInput" ///< http://lv2plug.in/ns/ext/port-groups#mainInput +#define LV2_PORT_GROUPS__mainOutput LV2_PORT_GROUPS_PREFIX "mainOutput" ///< http://lv2plug.in/ns/ext/port-groups#mainOutput +#define LV2_PORT_GROUPS__rearCenter LV2_PORT_GROUPS_PREFIX "rearCenter" ///< http://lv2plug.in/ns/ext/port-groups#rearCenter +#define LV2_PORT_GROUPS__rearLeft LV2_PORT_GROUPS_PREFIX "rearLeft" ///< http://lv2plug.in/ns/ext/port-groups#rearLeft +#define LV2_PORT_GROUPS__rearRight LV2_PORT_GROUPS_PREFIX "rearRight" ///< http://lv2plug.in/ns/ext/port-groups#rearRight +#define LV2_PORT_GROUPS__right LV2_PORT_GROUPS_PREFIX "right" ///< http://lv2plug.in/ns/ext/port-groups#right +#define LV2_PORT_GROUPS__side LV2_PORT_GROUPS_PREFIX "side" ///< http://lv2plug.in/ns/ext/port-groups#side +#define LV2_PORT_GROUPS__sideChainOf LV2_PORT_GROUPS_PREFIX "sideChainOf" ///< http://lv2plug.in/ns/ext/port-groups#sideChainOf +#define LV2_PORT_GROUPS__sideLeft LV2_PORT_GROUPS_PREFIX "sideLeft" ///< http://lv2plug.in/ns/ext/port-groups#sideLeft +#define LV2_PORT_GROUPS__sideRight LV2_PORT_GROUPS_PREFIX "sideRight" ///< http://lv2plug.in/ns/ext/port-groups#sideRight +#define LV2_PORT_GROUPS__source LV2_PORT_GROUPS_PREFIX "source" ///< http://lv2plug.in/ns/ext/port-groups#source +#define LV2_PORT_GROUPS__subGroupOf LV2_PORT_GROUPS_PREFIX "subGroupOf" ///< http://lv2plug.in/ns/ext/port-groups#subGroupOf #endif /* LV2_PORT_GROUPS_H */ + +/** + @} +*/ diff --git a/distrho/src/lv2/port-props.h b/distrho/src/lv2/port-props.h @@ -1,5 +1,5 @@ /* - Copyright 2012 David Robillard <http://drobilla.net> + Copyright 2012-2016 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -15,28 +15,34 @@ */ /** - @file port-props.h - C definitions for the LV2 Port Props extension - <http://lv2plug.in/ns/ext/port-props>. + @defgroup port-props Port Properties + + Various port properties. + + @{ */ #ifndef LV2_PORT_PROPS_H #define LV2_PORT_PROPS_H -#define LV2_PORT_PROPS_URI "http://lv2plug.in/ns/ext/port-props" -#define LV2_PORT_PROPS_PREFIX LV2_PORT_PROPS_URI "#" - -#define LV2_PORT_PROPS__causesArtifacts LV2_PORT_PROPS_PREFIX "causesArtifacts" -#define LV2_PORT_PROPS__continuousCV LV2_PORT_PROPS_PREFIX "continuousCV" -#define LV2_PORT_PROPS__discreteCV LV2_PORT_PROPS_PREFIX "discreteCV" -#define LV2_PORT_PROPS__displayPriority LV2_PORT_PROPS_PREFIX "displayPriority" -#define LV2_PORT_PROPS__expensive LV2_PORT_PROPS_PREFIX "expensive" -#define LV2_PORT_PROPS__hasStrictBounds LV2_PORT_PROPS_PREFIX "hasStrictBounds" -#define LV2_PORT_PROPS__logarithmic LV2_PORT_PROPS_PREFIX "logarithmic" -#define LV2_PORT_PROPS__notAutomatic LV2_PORT_PROPS_PREFIX "notAutomatic" -#define LV2_PORT_PROPS__notOnGUI LV2_PORT_PROPS_PREFIX "notOnGUI" -#define LV2_PORT_PROPS__rangeSteps LV2_PORT_PROPS_PREFIX "rangeSteps" -#define LV2_PORT_PROPS__supportsStrictBounds LV2_PORT_PROPS_PREFIX "supportsStrictBounds" -#define LV2_PORT_PROPS__trigger LV2_PORT_PROPS_PREFIX "trigger" +#define LV2_PORT_PROPS_URI "http://lv2plug.in/ns/ext/port-props" ///< http://lv2plug.in/ns/ext/port-props +#define LV2_PORT_PROPS_PREFIX LV2_PORT_PROPS_URI "#" ///< http://lv2plug.in/ns/ext/port-props# + +#define LV2_PORT_PROPS__causesArtifacts LV2_PORT_PROPS_PREFIX "causesArtifacts" ///< http://lv2plug.in/ns/ext/port-props#causesArtifacts +#define LV2_PORT_PROPS__continuousCV LV2_PORT_PROPS_PREFIX "continuousCV" ///< http://lv2plug.in/ns/ext/port-props#continuousCV +#define LV2_PORT_PROPS__discreteCV LV2_PORT_PROPS_PREFIX "discreteCV" ///< http://lv2plug.in/ns/ext/port-props#discreteCV +#define LV2_PORT_PROPS__displayPriority LV2_PORT_PROPS_PREFIX "displayPriority" ///< http://lv2plug.in/ns/ext/port-props#displayPriority +#define LV2_PORT_PROPS__expensive LV2_PORT_PROPS_PREFIX "expensive" ///< http://lv2plug.in/ns/ext/port-props#expensive +#define LV2_PORT_PROPS__hasStrictBounds LV2_PORT_PROPS_PREFIX "hasStrictBounds" ///< http://lv2plug.in/ns/ext/port-props#hasStrictBounds +#define LV2_PORT_PROPS__logarithmic LV2_PORT_PROPS_PREFIX "logarithmic" ///< http://lv2plug.in/ns/ext/port-props#logarithmic +#define LV2_PORT_PROPS__notAutomatic LV2_PORT_PROPS_PREFIX "notAutomatic" ///< http://lv2plug.in/ns/ext/port-props#notAutomatic +#define LV2_PORT_PROPS__notOnGUI LV2_PORT_PROPS_PREFIX "notOnGUI" ///< http://lv2plug.in/ns/ext/port-props#notOnGUI +#define LV2_PORT_PROPS__rangeSteps LV2_PORT_PROPS_PREFIX "rangeSteps" ///< http://lv2plug.in/ns/ext/port-props#rangeSteps +#define LV2_PORT_PROPS__supportsStrictBounds LV2_PORT_PROPS_PREFIX "supportsStrictBounds" ///< http://lv2plug.in/ns/ext/port-props#supportsStrictBounds +#define LV2_PORT_PROPS__trigger LV2_PORT_PROPS_PREFIX "trigger" ///< http://lv2plug.in/ns/ext/port-props#trigger #endif /* LV2_PORT_PROPS_H */ + +/** + @} +*/ diff --git a/distrho/src/lv2/presets.h b/distrho/src/lv2/presets.h @@ -1,5 +1,5 @@ /* - Copyright 2012 David Robillard <http://drobilla.net> + Copyright 2012-2016 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -15,20 +15,27 @@ */ /** - @file presets.h + @defgroup presets Presets - C definitions for the LV2 Presets extension - <http://lv2plug.in/ns/ext/presets>. + Presets for plugins, see <http://lv2plug.in/ns/ext/presets> for details. + + @{ */ #ifndef LV2_PRESETS_H #define LV2_PRESETS_H -#define LV2_PRESETS_URI "http://lv2plug.in/ns/ext/presets" -#define LV2_PRESETS_PREFIX LV2_PRESETS_URI "#" +#define LV2_PRESETS_URI "http://lv2plug.in/ns/ext/presets" ///< http://lv2plug.in/ns/ext/presets +#define LV2_PRESETS_PREFIX LV2_PRESETS_URI "#" ///< http://lv2plug.in/ns/ext/presets# -#define LV2_PRESETS__Preset LV2_PRESETS_PREFIX "Preset" -#define LV2_PRESETS__preset LV2_PRESETS_PREFIX "preset" -#define LV2_PRESETS__value LV2_PRESETS_PREFIX "value" +#define LV2_PRESETS__Bank LV2_PRESETS_PREFIX "Bank" ///< http://lv2plug.in/ns/ext/presets#Bank +#define LV2_PRESETS__Preset LV2_PRESETS_PREFIX "Preset" ///< http://lv2plug.in/ns/ext/presets#Preset +#define LV2_PRESETS__bank LV2_PRESETS_PREFIX "bank" ///< http://lv2plug.in/ns/ext/presets#bank +#define LV2_PRESETS__preset LV2_PRESETS_PREFIX "preset" ///< http://lv2plug.in/ns/ext/presets#preset +#define LV2_PRESETS__value LV2_PRESETS_PREFIX "value" ///< http://lv2plug.in/ns/ext/presets#value #endif /* LV2_PRESETS_H */ + +/** + @} +*/ diff --git a/distrho/src/lv2/resize-port.h b/distrho/src/lv2/resize-port.h @@ -1,5 +1,5 @@ /* - Copyright 2007-2012 David Robillard <http://drobilla.net> + Copyright 2007-2016 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -14,18 +14,26 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +/** + @defgroup resize-port Resize Port + + Dynamically sized LV2 port buffers. + + @{ +*/ + #ifndef LV2_RESIZE_PORT_H #define LV2_RESIZE_PORT_H #include <stddef.h> #include <stdint.h> -#define LV2_RESIZE_PORT_URI "http://lv2plug.in/ns/ext/resize-port" -#define LV2_RESIZE_PORT_PREFIX LV2_RESIZE_PORT_URI "#" +#define LV2_RESIZE_PORT_URI "http://lv2plug.in/ns/ext/resize-port" ///< http://lv2plug.in/ns/ext/resize-port +#define LV2_RESIZE_PORT_PREFIX LV2_RESIZE_PORT_URI "#" ///< http://lv2plug.in/ns/ext/resize-port# -#define LV2_RESIZE_PORT__asLargeAs LV2_RESIZE_PORT_PREFIX "asLargeAs" -#define LV2_RESIZE_PORT__minimumSize LV2_RESIZE_PORT_PREFIX "minimumSize" -#define LV2_RESIZE_PORT__resize LV2_RESIZE_PORT_PREFIX "resize" +#define LV2_RESIZE_PORT__asLargeAs LV2_RESIZE_PORT_PREFIX "asLargeAs" ///< http://lv2plug.in/ns/ext/port#asLargeAs +#define LV2_RESIZE_PORT__minimumSize LV2_RESIZE_PORT_PREFIX "minimumSize" ///< http://lv2plug.in/ns/ext/port#minimumSize +#define LV2_RESIZE_PORT__resize LV2_RESIZE_PORT_PREFIX "resize" ///< http://lv2plug.in/ns/ext/port#resize #ifdef __cplusplus extern "C" { @@ -40,22 +48,25 @@ typedef enum { LV2_RESIZE_PORT_ERR_NO_SPACE = 2 /**< Insufficient space. */ } LV2_Resize_Port_Status; +/** Opaque data for resize method. */ typedef void* LV2_Resize_Port_Feature_Data; +/** Host feature to allow plugins to resize their port buffers. */ typedef struct { + /** Opaque data for resize method. */ LV2_Resize_Port_Feature_Data data; /** - Resize a port buffer to at least @a size bytes. - + Resize a port buffer to at least `size` bytes. + This function MAY return an error, in which case the port buffer was not resized and the port is still connected to the same location. Plugins MUST gracefully handle this situation. - + This function is in the audio threading class. - + The host MUST preserve the contents of the port buffer when resizing. - + Plugins MAY resize a port many times in a single run callback. Hosts SHOULD make this as inexpensive as possible. */ @@ -70,3 +81,6 @@ typedef struct { #endif /* LV2_RESIZE_PORT_H */ +/** + @} +*/ diff --git a/distrho/src/lv2/state.h b/distrho/src/lv2/state.h @@ -1,5 +1,5 @@ /* - Copyright 2010-2012 David Robillard <http://drobilla.net> + Copyright 2010-2016 David Robillard <http://drobilla.net> Copyright 2010 Leonard Ritter <paniq@paniq.org> Permission to use, copy, modify, and/or distribute this software for any @@ -16,37 +16,45 @@ */ /** - @file state.h - C API for the LV2 State extension <http://lv2plug.in/ns/ext/state>. + @defgroup state State + @ingroup lv2 + + An interface for LV2 plugins to save and restore state, see + <http://lv2plug.in/ns/ext/state> for details. + + @{ */ #ifndef LV2_STATE_H #define LV2_STATE_H +#include "lv2.h" + +#include <stdbool.h> #include <stddef.h> #include <stdint.h> -#include "lv2.h" - -#define LV2_STATE_URI "http://lv2plug.in/ns/ext/state" -#define LV2_STATE_PREFIX LV2_STATE_URI "#" +#define LV2_STATE_URI "http://lv2plug.in/ns/ext/state" ///< http://lv2plug.in/ns/ext/state +#define LV2_STATE_PREFIX LV2_STATE_URI "#" ///< http://lv2plug.in/ns/ext/state# -#define LV2_STATE__State LV2_STATE_PREFIX "State" -#define LV2_STATE__interface LV2_STATE_PREFIX "interface" -#define LV2_STATE__loadDefaultState LV2_STATE_PREFIX "loadDefaultState" -#define LV2_STATE__makePath LV2_STATE_PREFIX "makePath" -#define LV2_STATE__mapPath LV2_STATE_PREFIX "mapPath" -#define LV2_STATE__state LV2_STATE_PREFIX "state" +#define LV2_STATE__State LV2_STATE_PREFIX "State" ///< http://lv2plug.in/ns/ext/state#State +#define LV2_STATE__interface LV2_STATE_PREFIX "interface" ///< http://lv2plug.in/ns/ext/state#interface +#define LV2_STATE__loadDefaultState LV2_STATE_PREFIX "loadDefaultState" ///< http://lv2plug.in/ns/ext/state#loadDefaultState +#define LV2_STATE__freePath LV2_STATE_PREFIX "freePath" ///< http://lv2plug.in/ns/ext/state#freePath +#define LV2_STATE__makePath LV2_STATE_PREFIX "makePath" ///< http://lv2plug.in/ns/ext/state#makePath +#define LV2_STATE__mapPath LV2_STATE_PREFIX "mapPath" ///< http://lv2plug.in/ns/ext/state#mapPath +#define LV2_STATE__state LV2_STATE_PREFIX "state" ///< http://lv2plug.in/ns/ext/state#state +#define LV2_STATE__threadSafeRestore LV2_STATE_PREFIX "threadSafeRestore" ///< http://lv2plug.in/ns/ext/state#threadSafeRestore +#define LV2_STATE__StateChanged LV2_STATE_PREFIX "StateChanged" ///< http://lv2plug.in/ns/ext/state#StateChanged #ifdef __cplusplus extern "C" { -#else -# include <stdbool.h> #endif -typedef void* LV2_State_Handle; -typedef void* LV2_State_Map_Path_Handle; -typedef void* LV2_State_Make_Path_Handle; +typedef void* LV2_State_Handle; ///< Opaque handle for state save/restore +typedef void* LV2_State_Free_Path_Handle; ///< Opaque handle for state:freePath feature +typedef void* LV2_State_Map_Path_Handle; ///< Opaque handle for state:mapPath feature +typedef void* LV2_State_Make_Path_Handle; ///< Opaque handle for state:makePath feature /** Flags describing value characteristics. @@ -61,8 +69,8 @@ typedef enum { Values with this flag contain no pointers or references to other areas of memory. It is safe to copy POD values with a simple memcpy and store them for the duration of the process. A POD value is not necessarily - safe to trasmit between processes or machines (e.g. filenames are POD), - see LV2_STATE_IS_PORTABLE for details. + safe to trasmit between processes or machines (for example, filenames + are POD), see LV2_STATE_IS_PORTABLE for details. Implementations MUST NOT attempt to copy or serialise a non-POD value if they do not understand its type (and thus know how to correctly do so). @@ -84,9 +92,9 @@ typedef enum { Native data. This flag is used by the host to indicate that the saved data is only - going to be used locally in the currently running process (e.g. for - instance duplication or snapshots), so the plugin should use the most - efficient representation possible and not worry about serialisation + going to be used locally in the currently running process (for things + like instance duplication or snapshots), so the plugin should use the + most efficient representation possible and not worry about serialisation and portability. */ LV2_STATE_IS_NATIVE = 1 << 2 @@ -99,17 +107,18 @@ typedef enum { LV2_STATE_ERR_BAD_TYPE = 2, /**< Failed due to unsupported type. */ LV2_STATE_ERR_BAD_FLAGS = 3, /**< Failed due to unsupported flags. */ LV2_STATE_ERR_NO_FEATURE = 4, /**< Failed due to missing features. */ - LV2_STATE_ERR_NO_PROPERTY = 5 /**< Failed due to missing property. */ + LV2_STATE_ERR_NO_PROPERTY = 5, /**< Failed due to missing property. */ + LV2_STATE_ERR_NO_SPACE = 6 /**< Failed due to insufficient space. */ } LV2_State_Status; /** A host-provided function to store a property. @param handle Must be the handle passed to LV2_State_Interface.save(). - @param key The key to store @p value under (URID). + @param key The key to store `value` under (URID). @param value Pointer to the value to be stored. - @param size The size of @p value in bytes. - @param type The type of @p value (URID). - @param flags LV2_State_Flags for @p value. + @param size The size of `value` in bytes. + @param type The type of `value` (URID). + @param flags LV2_State_Flags for `value`. @return 0 on success, otherwise a non-zero error code. The host passes a callback of this type to LV2_State_Interface.save(). This @@ -119,7 +128,7 @@ typedef enum { DO NOT INVENT NONSENSE URI SCHEMES FOR THE KEY. Best is to use keys from existing vocabularies. If nothing appropriate is available, use http URIs that point to somewhere you can host documents so documentation can be made - resolvable (e.g. a child of the plugin or project URI). If this is not + resolvable (typically a child of the plugin or project URI). If this is not possible, invent a URN scheme, e.g. urn:myproj:whatever. The plugin MUST NOT pass an invalid URI key. @@ -129,8 +138,8 @@ typedef enum { (http://lv2plug.in/ns/ext/atom) wherever possible. The plugin SHOULD attempt to fall-back and avoid the error if possible. - Note that @p size MUST be > 0, and @p value MUST point to a valid region of - memory @p size bytes long (this is required to make restore unambiguous). + Note that `size` MUST be > 0, and `value` MUST point to a valid region of + memory `size` bytes long (this is required to make restore unambiguous). The plugin MUST NOT attempt to use this function outside of the LV2_State_Interface.restore() context. @@ -151,7 +160,7 @@ typedef LV2_State_Status (*LV2_State_Store_Function)( @param type (Output) If non-NULL, set to the type of the restored value. @param flags (Output) If non-NULL, set to the flags for the restored value. @return A pointer to the restored value (object), or NULL if no value - has been stored under @p key. + has been stored under `key`. A callback of this type is passed by the host to LV2_State_Interface.restore(). This callback is called repeatedly by the @@ -186,14 +195,14 @@ typedef const void* (*LV2_State_Retrieve_Function)( authors should consider this possibility, and always store sensible data with meaningful types to avoid such problems in the future. */ -typedef struct _LV2_State_Interface { +typedef struct { /** - Save plugin state using a host-provided @p store callback. + Save plugin state using a host-provided `store` callback. @param instance The instance handle of the plugin. @param store The host-provided store callback. @param handle An opaque pointer to host data which MUST be passed as the - handle parameter to @p store if it is called. + handle parameter to `store` if it is called. @param flags Flags describing desired properties of this save. These flags may be used to determine the most appropriate values to store. @param features Extensible parameter for passing any additional @@ -204,16 +213,16 @@ typedef struct _LV2_State_Interface { possible, and consider the possibility of state being restored much later on a different machine. - The @p handle pointer and @p store function MUST NOT be used + The `handle` pointer and `store` function MUST NOT be used beyond the scope of save(). This function has its own special threading class: it may not be called concurrently with any "Instantiation" function, but it may be called concurrently with functions in any other class, unless the definition of - that class prohibits it (e.g. it may not be called concurrently with a - "Discovery" function, but it may be called concurrently with an "Audio" - function. The plugin is responsible for any locking or lock-free - techniques necessary to make this possible. + that class prohibits it (for example, it may not be called concurrently + with a "Discovery" function, but it may be called concurrently with an + "Audio" function. The plugin is responsible for any locking or + lock-free techniques necessary to make this possible. Note that in the simple case where state is only modified by restore(), there are no synchronization issues since save() is never called @@ -230,12 +239,12 @@ typedef struct _LV2_State_Interface { const LV2_Feature *const * features); /** - Restore plugin state using a host-provided @p retrieve callback. + Restore plugin state using a host-provided `retrieve` callback. @param instance The instance handle of the plugin. @param retrieve The host-provided retrieve callback. @param handle An opaque pointer to host data which MUST be passed as the - handle parameter to @p retrieve if it is called. + handle parameter to `retrieve` if it is called. @param flags Currently unused. @param features Extensible parameter for passing any additional features to be used for this restore. @@ -247,7 +256,7 @@ typedef struct _LV2_State_Interface { not be retrieved. This allows the host to reset the plugin state with an empty map. - The @p handle pointer and @p store function MUST NOT be used + The `handle` pointer and `store` function MUST NOT be used beyond the scope of restore(). This function is in the "Instantiation" threading class as defined by @@ -262,7 +271,7 @@ typedef struct _LV2_State_Interface { } LV2_State_Interface; /** - Feature data for state:mapPath (LV2_STATE__mapPath). + Feature data for state:mapPath (@ref LV2_STATE__mapPath). */ typedef struct { /** @@ -272,38 +281,38 @@ typedef struct { /** Map an absolute path to an abstract path for use in plugin state. - @param handle MUST be the @p handle member of this struct. + @param handle MUST be the `handle` member of this struct. @param absolute_path The absolute path of a file. @return An abstract path suitable for use in plugin state. The plugin MUST use this function to map any paths that will be stored in plugin state. The returned value is an abstract path which MAY not - be an actual file system path; @ref absolute_path() MUST be used to map + be an actual file system path; absolute_path() MUST be used to map it to an actual path in order to use the file. Plugins MUST NOT make any assumptions about abstract paths except that they can be mapped back to the absolute path of the "same" file (though - not necessarily the same original path) using @ref absolute_path(). + not necessarily the same original path) using absolute_path(). This function may only be called within the context of - LV2_State_Interface methods. The caller is responsible for freeing the - returned value with free(). + LV2_State_Interface methods. The caller must free the returned value + with LV2_State_Free_Path.free_path(). */ char* (*abstract_path)(LV2_State_Map_Path_Handle handle, const char* absolute_path); /** Map an abstract path from plugin state to an absolute path. - @param handle MUST be the @p handle member of this struct. - @param abstract_path An abstract path (e.g. a path from plugin state). + @param handle MUST be the `handle` member of this struct. + @param abstract_path An abstract path (typically from plugin state). @return An absolute file system path. The plugin MUST use this function in order to actually open or otherwise use any paths loaded from plugin state. This function may only be called within the context of - LV2_State_Interface methods. The caller is responsible for freeing the - returned value with free(). + LV2_State_Interface methods. The caller must free the returned value + with LV2_State_Free_Path.free_path(). */ char* (*absolute_path)(LV2_State_Map_Path_Handle handle, const char* abstract_path); @@ -320,7 +329,7 @@ typedef struct { /** Return a path the plugin may use to create a new file. - @param handle MUST be the @p handle member of this struct. + @param handle MUST be the `handle` member of this struct. @param path The path of the new file within a namespace unique to this plugin instance. @return The absolute path to use for the new file. @@ -331,22 +340,50 @@ typedef struct { LV2_Descriptor.instantiate()). The host MUST do whatever is necessary for the plugin to be able to - create a file at the returned path (e.g. using fopen), including - creating any leading directories. + create a file at the returned path (for example, using fopen()), + including creating any leading directories. If this function is passed to LV2_Descriptor.instantiate(), it may be called from any non-realtime context. If it is passed to LV2_State_Interface.save(), it may only be called within the dynamic scope of that function call. - The caller is responsible for freeing the returned value with free(). + The caller must free the returned value with + LV2_State_Free_Path.free_path(). */ char* (*path)(LV2_State_Make_Path_Handle handle, const char* path); } LV2_State_Make_Path; +/** + Feature data for state:freePath (@ref LV2_STATE__freePath). +*/ +typedef struct { + /** + Opaque host data. + */ + LV2_State_Free_Path_Handle handle; + + /** + Free a path returned by a state feature. + + @param handle MUST be the `handle` member of this struct. + @param path The path previously returned by a state feature. + + This function can be used by plugins to free paths allocated by the host + and returned by state features (LV2_State_Map_Path.abstract_path(), + LV2_State_Map_Path.absolute_path(), and LV2_State_Make_Path.path()). + */ + void (*free_path)(LV2_State_Free_Path_Handle handle, + char* path); +} LV2_State_Free_Path; + #ifdef __cplusplus } /* extern "C" */ #endif #endif /* LV2_STATE_H */ + +/** + @} +*/ diff --git a/distrho/src/lv2/time.h b/distrho/src/lv2/time.h @@ -1,5 +1,5 @@ /* - Copyright 2011 David Robillard <http://drobilla.net> + Copyright 2011-2016 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -15,35 +15,39 @@ */ /** - @file time.h C header for the LV2 Time extension - <http://lv2plug.in/ns/ext/time>. + @defgroup time Time + + Properties for describing time, see <http://lv2plug.in/ns/ext/time> for + details. + + Note the time extension is purely data, this header merely defines URIs for + convenience. + + @{ */ #ifndef LV2_TIME_H #define LV2_TIME_H -#ifdef __cplusplus -extern "C" { -#endif - -#define LV2_TIME_URI "http://lv2plug.in/ns/ext/time" - -#define LV2_TIME__Time LV2_TIME_URI "#Time" -#define LV2_TIME__Position LV2_TIME_URI "#Position" -#define LV2_TIME__Rate LV2_TIME_URI "#Rate" -#define LV2_TIME__position LV2_TIME_URI "#position" -#define LV2_TIME__barBeat LV2_TIME_URI "#barBeat" -#define LV2_TIME__bar LV2_TIME_URI "#bar" -#define LV2_TIME__beat LV2_TIME_URI "#beat" -#define LV2_TIME__beatUnit LV2_TIME_URI "#beatUnit" -#define LV2_TIME__beatsPerBar LV2_TIME_URI "#beatsPerBar" -#define LV2_TIME__beatsPerMinute LV2_TIME_URI "#beatsPerMinute" -#define LV2_TIME__frame LV2_TIME_URI "#frame" -#define LV2_TIME__framesPerSecond LV2_TIME_URI "#framesPerSecond" -#define LV2_TIME__speed LV2_TIME_URI "#speed" - -#ifdef __cplusplus -} /* extern "C" */ -#endif +#define LV2_TIME_URI "http://lv2plug.in/ns/ext/time" ///< http://lv2plug.in/ns/ext/time +#define LV2_TIME_PREFIX LV2_TIME_URI "#" ///< http://lv2plug.in/ns/ext/time# + +#define LV2_TIME__Time LV2_TIME_PREFIX "Time" ///< http://lv2plug.in/ns/ext/time#Time +#define LV2_TIME__Position LV2_TIME_PREFIX "Position" ///< http://lv2plug.in/ns/ext/time#Position +#define LV2_TIME__Rate LV2_TIME_PREFIX "Rate" ///< http://lv2plug.in/ns/ext/time#Rate +#define LV2_TIME__position LV2_TIME_PREFIX "position" ///< http://lv2plug.in/ns/ext/time#position +#define LV2_TIME__barBeat LV2_TIME_PREFIX "barBeat" ///< http://lv2plug.in/ns/ext/time#barBeat +#define LV2_TIME__bar LV2_TIME_PREFIX "bar" ///< http://lv2plug.in/ns/ext/time#bar +#define LV2_TIME__beat LV2_TIME_PREFIX "beat" ///< http://lv2plug.in/ns/ext/time#beat +#define LV2_TIME__beatUnit LV2_TIME_PREFIX "beatUnit" ///< http://lv2plug.in/ns/ext/time#beatUnit +#define LV2_TIME__beatsPerBar LV2_TIME_PREFIX "beatsPerBar" ///< http://lv2plug.in/ns/ext/time#beatsPerBar +#define LV2_TIME__beatsPerMinute LV2_TIME_PREFIX "beatsPerMinute" ///< http://lv2plug.in/ns/ext/time#beatsPerMinute +#define LV2_TIME__frame LV2_TIME_PREFIX "frame" ///< http://lv2plug.in/ns/ext/time#frame +#define LV2_TIME__framesPerSecond LV2_TIME_PREFIX "framesPerSecond" ///< http://lv2plug.in/ns/ext/time#framesPerSecond +#define LV2_TIME__speed LV2_TIME_PREFIX "speed" ///< http://lv2plug.in/ns/ext/time#speed + +/** + @} +*/ #endif /* LV2_TIME_H */ diff --git a/distrho/src/lv2/units.h b/distrho/src/lv2/units.h @@ -1,5 +1,5 @@ /* - Copyright 2012 David Robillard <http://drobilla.net> + Copyright 2012-2016 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -15,48 +15,55 @@ */ /** - @file units.h - C definitions for the LV2 Units extension - <http://lv2plug.in/ns/extensions/units>. + @defgroup units Units + + Units for LV2 values, see <http://lv2plug.in/ns/extensions/units> for + details. + + @{ */ #ifndef LV2_UNITS_H #define LV2_UNITS_H -#define LV2_UNITS_URI "http://lv2plug.in/ns/extensions/units" -#define LV2_UNITS_PREFIX LV2_UNITS_URI "#" - -#define LV2_UNITS__Conversion LV2_UNITS_PREFIX "Conversion" -#define LV2_UNITS__Unit LV2_UNITS_PREFIX "Unit" -#define LV2_UNITS__bar LV2_UNITS_PREFIX "bar" -#define LV2_UNITS__beat LV2_UNITS_PREFIX "beat" -#define LV2_UNITS__bpm LV2_UNITS_PREFIX "bpm" -#define LV2_UNITS__cent LV2_UNITS_PREFIX "cent" -#define LV2_UNITS__cm LV2_UNITS_PREFIX "cm" -#define LV2_UNITS__coef LV2_UNITS_PREFIX "coef" -#define LV2_UNITS__conversion LV2_UNITS_PREFIX "conversion" -#define LV2_UNITS__db LV2_UNITS_PREFIX "db" -#define LV2_UNITS__degree LV2_UNITS_PREFIX "degree" -#define LV2_UNITS__frame LV2_UNITS_PREFIX "frame" -#define LV2_UNITS__hz LV2_UNITS_PREFIX "hz" -#define LV2_UNITS__inch LV2_UNITS_PREFIX "inch" -#define LV2_UNITS__khz LV2_UNITS_PREFIX "khz" -#define LV2_UNITS__km LV2_UNITS_PREFIX "km" -#define LV2_UNITS__m LV2_UNITS_PREFIX "m" -#define LV2_UNITS__mhz LV2_UNITS_PREFIX "mhz" -#define LV2_UNITS__midiNote LV2_UNITS_PREFIX "midiNote" -#define LV2_UNITS__mile LV2_UNITS_PREFIX "mile" -#define LV2_UNITS__min LV2_UNITS_PREFIX "min" -#define LV2_UNITS__mm LV2_UNITS_PREFIX "mm" -#define LV2_UNITS__ms LV2_UNITS_PREFIX "ms" -#define LV2_UNITS__name LV2_UNITS_PREFIX "name" -#define LV2_UNITS__oct LV2_UNITS_PREFIX "oct" -#define LV2_UNITS__pc LV2_UNITS_PREFIX "pc" -#define LV2_UNITS__prefixConversion LV2_UNITS_PREFIX "prefixConversion" -#define LV2_UNITS__render LV2_UNITS_PREFIX "render" -#define LV2_UNITS__s LV2_UNITS_PREFIX "s" -#define LV2_UNITS__semitone12TET LV2_UNITS_PREFIX "semitone12TET" -#define LV2_UNITS__symbol LV2_UNITS_PREFIX "symbol" -#define LV2_UNITS__unit LV2_UNITS_PREFIX "unit" +#define LV2_UNITS_URI "http://lv2plug.in/ns/extensions/units" ///< http://lv2plug.in/ns/extensions/units +#define LV2_UNITS_PREFIX LV2_UNITS_URI "#" ///< http://lv2plug.in/ns/extensions/units# + +#define LV2_UNITS__Conversion LV2_UNITS_PREFIX "Conversion" ///< http://lv2plug.in/ns/ext/units#Conversion +#define LV2_UNITS__Unit LV2_UNITS_PREFIX "Unit" ///< http://lv2plug.in/ns/ext/units#Unit +#define LV2_UNITS__bar LV2_UNITS_PREFIX "bar" ///< http://lv2plug.in/ns/ext/units#bar +#define LV2_UNITS__beat LV2_UNITS_PREFIX "beat" ///< http://lv2plug.in/ns/ext/units#beat +#define LV2_UNITS__bpm LV2_UNITS_PREFIX "bpm" ///< http://lv2plug.in/ns/ext/units#bpm +#define LV2_UNITS__cent LV2_UNITS_PREFIX "cent" ///< http://lv2plug.in/ns/ext/units#cent +#define LV2_UNITS__cm LV2_UNITS_PREFIX "cm" ///< http://lv2plug.in/ns/ext/units#cm +#define LV2_UNITS__coef LV2_UNITS_PREFIX "coef" ///< http://lv2plug.in/ns/ext/units#coef +#define LV2_UNITS__conversion LV2_UNITS_PREFIX "conversion" ///< http://lv2plug.in/ns/ext/units#conversion +#define LV2_UNITS__db LV2_UNITS_PREFIX "db" ///< http://lv2plug.in/ns/ext/units#db +#define LV2_UNITS__degree LV2_UNITS_PREFIX "degree" ///< http://lv2plug.in/ns/ext/units#degree +#define LV2_UNITS__frame LV2_UNITS_PREFIX "frame" ///< http://lv2plug.in/ns/ext/units#frame +#define LV2_UNITS__hz LV2_UNITS_PREFIX "hz" ///< http://lv2plug.in/ns/ext/units#hz +#define LV2_UNITS__inch LV2_UNITS_PREFIX "inch" ///< http://lv2plug.in/ns/ext/units#inch +#define LV2_UNITS__khz LV2_UNITS_PREFIX "khz" ///< http://lv2plug.in/ns/ext/units#khz +#define LV2_UNITS__km LV2_UNITS_PREFIX "km" ///< http://lv2plug.in/ns/ext/units#km +#define LV2_UNITS__m LV2_UNITS_PREFIX "m" ///< http://lv2plug.in/ns/ext/units#m +#define LV2_UNITS__mhz LV2_UNITS_PREFIX "mhz" ///< http://lv2plug.in/ns/ext/units#mhz +#define LV2_UNITS__midiNote LV2_UNITS_PREFIX "midiNote" ///< http://lv2plug.in/ns/ext/units#midiNote +#define LV2_UNITS__mile LV2_UNITS_PREFIX "mile" ///< http://lv2plug.in/ns/ext/units#mile +#define LV2_UNITS__min LV2_UNITS_PREFIX "min" ///< http://lv2plug.in/ns/ext/units#min +#define LV2_UNITS__mm LV2_UNITS_PREFIX "mm" ///< http://lv2plug.in/ns/ext/units#mm +#define LV2_UNITS__ms LV2_UNITS_PREFIX "ms" ///< http://lv2plug.in/ns/ext/units#ms +#define LV2_UNITS__name LV2_UNITS_PREFIX "name" ///< http://lv2plug.in/ns/ext/units#name +#define LV2_UNITS__oct LV2_UNITS_PREFIX "oct" ///< http://lv2plug.in/ns/ext/units#oct +#define LV2_UNITS__pc LV2_UNITS_PREFIX "pc" ///< http://lv2plug.in/ns/ext/units#pc +#define LV2_UNITS__prefixConversion LV2_UNITS_PREFIX "prefixConversion" ///< http://lv2plug.in/ns/ext/units#prefixConversion +#define LV2_UNITS__render LV2_UNITS_PREFIX "render" ///< http://lv2plug.in/ns/ext/units#render +#define LV2_UNITS__s LV2_UNITS_PREFIX "s" ///< http://lv2plug.in/ns/ext/units#s +#define LV2_UNITS__semitone12TET LV2_UNITS_PREFIX "semitone12TET" ///< http://lv2plug.in/ns/ext/units#semitone12TET +#define LV2_UNITS__symbol LV2_UNITS_PREFIX "symbol" ///< http://lv2plug.in/ns/ext/units#symbol +#define LV2_UNITS__unit LV2_UNITS_PREFIX "unit" ///< http://lv2plug.in/ns/ext/units#unit #endif /* LV2_UNITS_H */ + +/** + @} +*/ diff --git a/distrho/src/lv2/uri-map.h b/distrho/src/lv2/uri-map.h @@ -1,5 +1,5 @@ /* - Copyright 2008-2011 David Robillard <http://drobilla.net> + Copyright 2008-2016 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -15,8 +15,9 @@ */ /** - @file - C header for the LV2 URI Map extension <http://lv2plug.in/ns/ext/uri-map>. + @defgroup uri-map URI Map + + C API for the LV2 URI Map extension <http://lv2plug.in/ns/ext/uri-map>. This extension defines a simple mechanism for plugins to map URIs to integers, usually for performance reasons (e.g. processing events typed by @@ -25,12 +26,15 @@ values for use in the audio thread without doing any string comparison. This allows the extensibility of RDF with the performance of integers (or centrally defined enumerations). + + @{ */ #ifndef LV2_URI_MAP_H #define LV2_URI_MAP_H -#define LV2_URI_MAP_URI "http://lv2plug.in/ns/ext/uri-map" +#define LV2_URI_MAP_URI "http://lv2plug.in/ns/ext/uri-map" ///< http://lv2plug.in/ns/ext/uri-map +#define LV2_URI_MAP_PREFIX LV2_URI_MAP_URI "#" ///< http://lv2plug.in/ns/ext/uri-map# #include <stdint.h> @@ -96,3 +100,7 @@ typedef struct { #endif #endif /* LV2_URI_MAP_H */ + +/** + @} +*/ diff --git a/distrho/src/lv2/urid.h b/distrho/src/lv2/urid.h @@ -1,5 +1,5 @@ /* - Copyright 2008-2012 David Robillard <http://drobilla.net> + Copyright 2008-2016 David Robillard <http://drobilla.net> Copyright 2011 Gabriel M. Beddingfield <gabrbedd@gmail.com> Permission to use, copy, modify, and/or distribute this software for any @@ -16,22 +16,25 @@ */ /** - @file urid.h - C header for the LV2 URID extension <http://lv2plug.in/ns/ext/urid> + @defgroup urid URID + + Features for mapping URIs to and from integers, see + <http://lv2plug.in/ns/ext/urid> for details. + + @{ */ #ifndef LV2_URID_H #define LV2_URID_H -#define LV2_URID_URI "http://lv2plug.in/ns/ext/urid" -#define LV2_URID_PREFIX LV2_URID_URI "#" +#define LV2_URID_URI "http://lv2plug.in/ns/ext/urid" ///< http://lv2plug.in/ns/ext/urid +#define LV2_URID_PREFIX LV2_URID_URI "#" ///< http://lv2plug.in/ns/ext/urid# -#define LV2_URID__map LV2_URID_PREFIX "map" -#define LV2_URID__unmap LV2_URID_PREFIX "unmap" +#define LV2_URID__map LV2_URID_PREFIX "map" ///< http://lv2plug.in/ns/ext/urid#map +#define LV2_URID__unmap LV2_URID_PREFIX "unmap" ///< http://lv2plug.in/ns/ext/urid#unmap -/* Legacy defines */ -#define LV2_URID_MAP_URI LV2_URID__map -#define LV2_URID_UNMAP_URI LV2_URID__unmap +#define LV2_URID_MAP_URI LV2_URID__map ///< Legacy +#define LV2_URID_UNMAP_URI LV2_URID__unmap ///< Legacy #include <stdint.h> @@ -106,11 +109,11 @@ typedef struct _LV2_URID_Unmap { /** Get the URI for a previously mapped numeric ID. - Returns NULL if @p urid is not yet mapped. Otherwise, the corresponding + Returns NULL if `urid` is not yet mapped. Otherwise, the corresponding URI is returned in a canonical form. This MAY not be the exact same string that was originally passed to LV2_URID_Map::map(), but it MUST be an identical URI according to the URI syntax specification (RFC3986). A - non-NULL return for a given @p urid will always be the same for the life + non-NULL return for a given `urid` will always be the same for the life of the plugin. Plugins that intend to perform string comparison on unmapped URIs SHOULD first canonicalise URI strings with a call to map_uri() followed by a call to unmap_uri(). @@ -127,3 +130,7 @@ typedef struct _LV2_URID_Unmap { #endif #endif /* LV2_URID_H */ + +/** + @} +*/ diff --git a/distrho/src/lv2/worker.h b/distrho/src/lv2/worker.h @@ -1,5 +1,5 @@ /* - Copyright 2012 David Robillard <http://drobilla.net> + Copyright 2012-2016 David Robillard <http://drobilla.net> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -15,8 +15,12 @@ */ /** - @file worker.h C header for the LV2 Worker extension - <http://lv2plug.in/ns/ext/worker>. + @defgroup worker Worker + + Support for non-realtime plugin operations, see + <http://lv2plug.in/ns/ext/worker> for details. + + @{ */ #ifndef LV2_WORKER_H @@ -26,18 +30,18 @@ #include "lv2.h" -#define LV2_WORKER_URI "http://lv2plug.in/ns/ext/worker" -#define LV2_WORKER_PREFIX LV2_WORKER_URI "#" +#define LV2_WORKER_URI "http://lv2plug.in/ns/ext/worker" ///< http://lv2plug.in/ns/ext/worker +#define LV2_WORKER_PREFIX LV2_WORKER_URI "#" ///< http://lv2plug.in/ns/ext/worker# -#define LV2_WORKER__interface LV2_WORKER_PREFIX "interface" -#define LV2_WORKER__schedule LV2_WORKER_PREFIX "schedule" +#define LV2_WORKER__interface LV2_WORKER_PREFIX "interface" ///< http://lv2plug.in/ns/ext/worker#interface +#define LV2_WORKER__schedule LV2_WORKER_PREFIX "schedule" ///< http://lv2plug.in/ns/ext/worker#schedule #ifdef __cplusplus extern "C" { #endif /** - A status code for worker functions. + Status code for worker functions. */ typedef enum { LV2_WORKER_SUCCESS = 0, /**< Completed successfully. */ @@ -45,12 +49,13 @@ typedef enum { LV2_WORKER_ERR_NO_SPACE = 2 /**< Failed due to lack of space. */ } LV2_Worker_Status; +/** Opaque handle for LV2_Worker_Interface::work(). */ typedef void* LV2_Worker_Respond_Handle; /** A function to respond to run() from the worker method. - The @p data MUST be safe for the host to copy and later pass to + The `data` MUST be safe for the host to copy and later pass to work_response(), and the host MUST guarantee that it will be eventually passed to work_response() if this function returns LV2_WORKER_SUCCESS. */ @@ -60,7 +65,7 @@ typedef LV2_Worker_Status (*LV2_Worker_Respond_Function)( const void* data); /** - LV2 Plugin Worker Interface. + Plugin Worker Interface. This is the interface provided by the plugin to implement a worker method. The plugin's extension_data() method should return an LV2_Worker_Interface @@ -71,14 +76,17 @@ typedef struct _LV2_Worker_Interface { The worker method. This is called by the host in a non-realtime context as requested, possibly with an arbitrary message to handle. - A response can be sent to run() using @p respond. The plugin MUST NOT - make any assumptions about which thread calls this method, other than - the fact that there are no real-time requirements. + A response can be sent to run() using `respond`. The plugin MUST NOT + make any assumptions about which thread calls this method, except that + there are no real-time requirements and only one call may be executed at + a time. That is, the host MAY call this method from any non-real-time + thread, but MUST NOT make concurrent calls to this method from several + threads. @param instance The LV2 instance this is a method on. @param respond A function for sending a response to run(). - @param handle Must be passed to @p respond if it is called. - @param size The size of @p data. + @param handle Must be passed to `respond` if it is called. + @param size The size of `data`. @param data Data from run(), or NULL. */ LV2_Worker_Status (*work)(LV2_Handle instance, @@ -92,7 +100,7 @@ typedef struct _LV2_Worker_Interface { run() context when a response from the worker is ready. @param instance The LV2 instance this is a method on. - @param size The size of @p body. + @param size The size of `body`. @param body Message body, or NULL. */ LV2_Worker_Status (*work_response)(LV2_Handle instance, @@ -112,8 +120,15 @@ typedef struct _LV2_Worker_Interface { LV2_Worker_Status (*end_run)(LV2_Handle instance); } LV2_Worker_Interface; +/** Opaque handle for LV2_Worker_Schedule. */ typedef void* LV2_Worker_Schedule_Handle; +/** + Schedule Worker Host Feature. + + The host passes this feature to provide a schedule_work() function, which + the plugin can use to schedule a worker call from run(). +*/ typedef struct _LV2_Worker_Schedule { /** Opaque host data. @@ -138,12 +153,12 @@ typedef struct _LV2_Worker_Schedule { immediately, and responses from the worker are delivered immediately, the effect of the work takes place immediately with sample accuracy. - The @p data MUST be safe for the host to copy and later pass to work(), + The `data` MUST be safe for the host to copy and later pass to work(), and the host MUST guarantee that it will be eventually passed to work() if this function returns LV2_WORKER_SUCCESS. @param handle The handle field of this struct. - @param size The size of @p data. + @param size The size of `data`. @param data Message to pass to work(), or NULL. */ LV2_Worker_Status (*schedule_work)(LV2_Worker_Schedule_Handle handle, @@ -156,3 +171,7 @@ typedef struct _LV2_Worker_Schedule { #endif #endif /* LV2_WORKER_H */ + +/** + @} +*/ diff --git a/distrho/src/travesty/README.txt b/distrho/src/travesty/README.txt @@ -1,9 +1,9 @@ -This folder contains a pure C interface to Steinberg VST3 SDK, codenamed "travesty". +This folder contains a pure C VST3-compatible interface, codenamed "travesty". Name is a play on words from morphing "vestige" (the good old free VST2 reverse-engineered header file) and "three". The main target is to be able to create VST3-compatible plugins without a bloated SDK. -Everything that is required for plugins fits in a few small header files. -Also, being able to build VST3-compatible plugins in pure C code, something not possible with the original SDK. +Everything that is required for plugins fits in a few small header files as presented here. +Also being able to build VST3-compatible plugins in pure C code, something not possible with the original SDK. Please note this project is still a work in progress. Use at your own risk, and please report any issues to https://github.com/DISTRHO/DPF/. diff --git a/distrho/src/travesty/align_pop.h b/distrho/src/travesty/align_pop.h @@ -1,5 +1,5 @@ /* - * travesty, pure C interface to steinberg VST3 SDK + * travesty, pure C VST3-compatible interface * Copyright (C) 2021 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with @@ -14,10 +14,25 @@ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#if defined(__APPLE__) +# if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpragma-pack" +# elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 460 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunknown-warning-option" +# pragma GCC diagnostic ignored "-Wpragma-pack" +# endif +#endif + #if defined(__APPLE__) || defined(_WIN32) # pragma pack(pop) #endif #if defined(__APPLE__) -# pragma GCC diagnostic pop +# if defined(__clang__) +# pragma clang diagnostic pop +# elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 460 +# pragma GCC diagnostic pop +# endif #endif diff --git a/distrho/src/travesty/align_push.h b/distrho/src/travesty/align_push.h @@ -1,5 +1,5 @@ /* - * travesty, pure C interface to steinberg VST3 SDK + * travesty, pure C VST3-compatible interface * Copyright (C) 2021 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with @@ -16,9 +16,16 @@ #if defined(__APPLE__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wunknown-warning-option" -# pragma GCC diagnostic ignored "-Wpragma-pack" +# if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunknown-warning-option" +# pragma clang diagnostic ignored "-Wpragma-pack" +# elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 460 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunknown-warning-option" +# pragma GCC diagnostic ignored "-Wpragma-pack" +# endif + # if defined(__LP64__) || defined(_LP64) # pragma pack(push, 16) # else diff --git a/distrho/src/travesty/audio_processor.h b/distrho/src/travesty/audio_processor.h @@ -1,6 +1,6 @@ /* - * travesty, pure C interface to steinberg VST3 SDK - * Copyright (C) 2021 Filipe Coelho <falktx@falktx.com> + * travesty, pure C VST3-compatible interface + * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -28,8 +28,9 @@ typedef uint64_t v3_speaker_arrangement; enum { - V3_SPEAKER_L = 1, - V3_SPEAKER_R = 1 << 1 + V3_SPEAKER_L = 1 << 0, + V3_SPEAKER_R = 1 << 1, + V3_SPEAKER_M = 1 << 19 }; /** @@ -42,14 +43,19 @@ enum v3_process_mode { V3_OFFLINE }; -inline static const char * -v3_process_mode_str(int32_t d) +static inline +const char* v3_process_mode_str(int32_t d) { - switch (d) { - case V3_REALTIME: return "V3_REALTIME"; - case V3_PREFETCH: return "V3_PREFETCH"; - case V3_OFFLINE: return "V3_OFFLINE"; - default: return "[unknown]"; + switch (d) + { + case V3_REALTIME: + return "V3_REALTIME"; + case V3_PREFETCH: + return "V3_PREFETCH"; + case V3_OFFLINE: + return "V3_OFFLINE"; + default: + return "[unknown]"; } } @@ -58,13 +64,17 @@ enum { V3_SAMPLE_64 }; -inline static const char * -v3_sample_size_str(int32_t d) +static inline +const char* v3_sample_size_str(int32_t d) { - switch (d) { - case V3_SAMPLE_32: return "V3_SAMPLE_32"; - case V3_SAMPLE_64: return "V3_SAMPLE_64"; - default: return "[unknown]"; + switch (d) + { + case V3_SAMPLE_32: + return "V3_SAMPLE_32"; + case V3_SAMPLE_64: + return "V3_SAMPLE_64"; + default: + return "[unknown]"; } } @@ -80,33 +90,28 @@ struct v3_process_setup { */ struct v3_param_value_queue { +#ifndef __cplusplus struct v3_funknown; - - V3_API v3_param_id (*get_param_id)(void *self); - V3_API int32_t (*get_point_count)(void *self); - - V3_API v3_result (*get_point) - (void *self, int32_t idx, int32_t *sample_offset, double *value); - V3_API v3_result (*add_point) - (void *self, int32_t sample_offset, double value, int32_t *idx); +#endif + v3_param_id (V3_API* get_param_id)(void* self); + int32_t (V3_API* get_point_count)(void* self); + v3_result (V3_API* get_point)(void* self, int32_t idx, int32_t* sample_offset, double* value); + v3_result (V3_API* add_point)(void* self, int32_t sample_offset, double value, int32_t* idx); }; -static const v3_tuid v3_param_value_queue_iid = +static constexpr const v3_tuid v3_param_value_queue_iid = V3_ID(0x01263A18, 0xED074F6F, 0x98C9D356, 0x4686F9BA); struct v3_param_changes { +#ifndef __cplusplus struct v3_funknown; - - V3_API int32_t (*get_param_count)(void *self); - - V3_API struct v3_param_value_queue **(*get_param_data) - (void *self, int32_t idx); - - V3_API struct v3_param_value_queue **(*add_param_data) - (void *self, v3_param_id *id, int32_t *index); +#endif + int32_t (V3_API* get_param_count)(void* self); + struct v3_param_value_queue** (V3_API* get_param_data)(void* self, int32_t idx); + struct v3_param_value_queue** (V3_API* add_param_data)(void* self, v3_param_id* id, int32_t* index); }; -static const v3_tuid v3_param_changes_iid = +static constexpr const v3_tuid v3_param_changes_iid = V3_ID(0xA4779663, 0x0BB64A56, 0xB44384A8, 0x466FEB9D); /** @@ -121,55 +126,41 @@ struct v3_frame_rate { struct v3_chord { uint8_t key_note; uint8_t root_note; - int16_t chord_mask; }; enum { - V3_PROCESS_CTX_PLAYING = 1 << 1, - V3_PROCESS_CTX_CYCLE_ACTIVE = 1 << 2, - V3_PROCESS_CTX_RECORDING = 1 << 3, - + V3_PROCESS_CTX_PLAYING = 1 << 1, + V3_PROCESS_CTX_CYCLE_ACTIVE = 1 << 2, + V3_PROCESS_CTX_RECORDING = 1 << 3, V3_PROCESS_CTX_SYSTEM_TIME_VALID = 1 << 8, - V3_PROCESS_CTX_CONT_TIME_VALID = 1 << 17, V3_PROCESS_CTX_PROJECT_TIME_VALID = 1 << 9, - + V3_PROCESS_CTX_TEMPO_VALID = 1 << 10, V3_PROCESS_CTX_BAR_POSITION_VALID = 1 << 11, V3_PROCESS_CTX_CYCLE_VALID = 1 << 12, - - V3_PROCESS_CTX_TEMPO_VALID = 1 << 10, V3_PROCESS_CTX_TIME_SIG_VALID = 1 << 13, - V3_PROCESS_CTX_CHORD_VALID = 1 << 18, - V3_PROCESS_CTX_SMPTE_VALID = 1 << 14, - - V3_PROCESS_CTX_NEXT_CLOCK_VALID = 1 << 15 + V3_PROCESS_CTX_NEXT_CLOCK_VALID = 1 << 15, + V3_PROCESS_CTX_CONT_TIME_VALID = 1 << 17, + V3_PROCESS_CTX_CHORD_VALID = 1 << 18 }; struct v3_process_context { uint32_t state; - double sample_rate; int64_t project_time_in_samples; // with loop - int64_t system_time_ns; - int64_t continuous_time_in_samples; // without loop? unclear - + int64_t continuous_time_in_samples; // without loop double project_time_quarters; double bar_position_quarters; double cycle_start_quarters; double cycle_end_quarters; - double bpm; - int32_t time_sig_numerator; int32_t time_sig_denom; - struct v3_chord chord; - int32_t smpte_offset_subframes; struct v3_frame_rate frame_rate; - int32_t samples_to_next_clock; }; @@ -192,12 +183,13 @@ enum { }; struct v3_process_context_requirements { +#ifndef __cplusplus struct v3_funknown; - - V3_API uint32_t (*get_process_context_requirements)(void *self); +#endif + uint32_t (V3_API* get_process_context_requirements)(void* self); }; -static const v3_tuid v3_process_context_requirements_iid = +static constexpr const v3_tuid v3_process_context_requirements_iid = V3_ID(0x2A654303, 0xEF764E3D, 0x95B5FE83, 0x730EF6D0); /** @@ -207,32 +199,25 @@ static const v3_tuid v3_process_context_requirements_iid = struct v3_audio_bus_buffers { int32_t num_channels; uint64_t channel_silence_bitset; - union { - float **channel_buffers_32; - double **channel_buffers_64; + float** channel_buffers_32; + double** channel_buffers_64; }; }; struct v3_process_data { int32_t process_mode; int32_t symbolic_sample_size; - int32_t nframes; - int32_t num_input_buses; int32_t num_output_buses; - - struct v3_audio_bus_buffers *inputs; - struct v3_audio_bus_buffers *outputs; - - struct v3_param_changes **input_params; - struct v3_param_changes **output_params; - - struct v3_event_list **input_events; - struct v3_event_list **output_events; - - struct v3_process_context *ctx; + struct v3_audio_bus_buffers* inputs; + struct v3_audio_bus_buffers* outputs; + struct v3_param_changes** input_params; + struct v3_param_changes** output_params; + struct v3_event_list** input_events; + struct v3_event_list** output_events; + struct v3_process_context* ctx; }; /** @@ -240,31 +225,45 @@ struct v3_process_data { */ struct v3_audio_processor { +#ifndef __cplusplus struct v3_funknown; +#endif + v3_result (V3_API* set_bus_arrangements)(void* self, v3_speaker_arrangement* inputs, int32_t num_inputs, + v3_speaker_arrangement* outputs, int32_t num_outputs); + v3_result (V3_API* get_bus_arrangement)(void* self, int32_t bus_direction, int32_t idx, v3_speaker_arrangement*); + v3_result (V3_API* can_process_sample_size)(void* self, int32_t symbolic_sample_size); + uint32_t (V3_API* get_latency_samples)(void* self); + v3_result (V3_API* setup_processing)(void* self, struct v3_process_setup* setup); + v3_result (V3_API* set_processing)(void* self, v3_bool state); + v3_result (V3_API* process)(void* self, struct v3_process_data* data); + uint32_t (V3_API* get_tail_samples)(void* self); +}; - V3_API v3_result (*set_bus_arrangements) - (void *self, v3_speaker_arrangement *inputs, int32_t num_inputs, - v3_speaker_arrangement *outputs, int32_t num_outputs); - V3_API v3_result (*get_bus_arrangement) - (void *self, int32_t bus_direction, int32_t idx, v3_speaker_arrangement *); +static constexpr const v3_tuid v3_audio_processor_iid = + V3_ID(0x42043F99, 0xB7DA453C, 0xA569E79D, 0x9AAEC33D); - V3_API v3_result (*can_process_sample_size) - (void *self, int32_t symbolic_sample_size); +#ifdef __cplusplus + +/** + * C++ variants + */ - V3_API uint32_t (*get_latency_samples)(void *self); +struct v3_param_value_queue_cpp : v3_funknown { + v3_param_value_queue queue; +}; - V3_API v3_result (*setup_processing) - (void *self, struct v3_process_setup *); - V3_API v3_result (*set_processing) - (void *self, v3_bool state); +struct v3_param_changes_cpp : v3_funknown { + v3_param_changes changes; +}; - V3_API v3_result (*process) - (void *self, struct v3_process_data *); +struct v3_process_context_requirements_cpp : v3_funknown { + v3_process_context_requirements req; +}; - V3_API uint32_t (*get_tail_samples)(void *self); +struct v3_audio_processor_cpp : v3_funknown { + v3_audio_processor proc; }; -static const v3_tuid v3_audio_processor_iid = - V3_ID(0x42043F99, 0xB7DA453C, 0xA569E79D, 0x9AAEC33D); +#endif #include "align_pop.h" diff --git a/distrho/src/travesty/base.h b/distrho/src/travesty/base.h @@ -1,6 +1,6 @@ /* - * travesty, pure C interface to steinberg VST3 SDK - * Copyright (C) 2021 Filipe Coelho <falktx@falktx.com> + * travesty, pure C VST3-compatible interface + * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -21,6 +21,14 @@ #include <string.h> /** + * deal with C vs C++ differences + */ + +#if !defined(__cplusplus) && !defined(constexpr) +# define constexpr +#endif + +/** * various types */ @@ -31,15 +39,14 @@ typedef uint8_t v3_bool; typedef uint32_t v3_param_id; - /** * low-level ABI nonsense */ typedef uint8_t v3_tuid[16]; -inline static bool -v3_tuid_match(const v3_tuid a, const v3_tuid b) +static inline +bool v3_tuid_match(const v3_tuid a, const v3_tuid b) { return memcmp(a, b, sizeof(v3_tuid)) == 0; } @@ -54,88 +61,90 @@ v3_tuid_match(const v3_tuid a, const v3_tuid b) #if V3_COM_COMPAT enum { - V3_NO_INTERFACE = 0x80004002L, - V3_OK = 0, - V3_TRUE = 0, - V3_FALSE = 1, - V3_INVALID_ARG = 0x80070057L, - V3_NOT_IMPLEMENTED = 0x80004001L, - V3_INTERNAL_ERR = 0x80004005L, - V3_NOT_INITIALISED = 0x8000FFFFL, - V3_NOMEM = 0x8007000EL + V3_NO_INTERFACE = 0x80004002L, + V3_OK = 0, + V3_TRUE = 0, + V3_FALSE = 1, + V3_INVALID_ARG = 0x80070057L, + V3_NOT_IMPLEMENTED = 0x80004001L, + V3_INTERNAL_ERR = 0x80004005L, + V3_NOT_INITIALIZED = 0x8000FFFFL, + V3_NOMEM = 0x8007000EL }; -# define V3_ID(a, b, c, d) { \ - ((a) & 0x000000FF), \ - ((a) & 0x0000FF00) >> 8, \ - ((a) & 0x00FF0000) >> 16, \ - ((a) & 0xFF000000) >> 24, \ - \ - ((b) & 0x00FF0000) >> 16, \ - ((b) & 0xFF000000) >> 24, \ - ((b) & 0x000000FF), \ - ((b) & 0x0000FF00) >> 8, \ - \ - ((c) & 0xFF000000) >> 24, \ - ((c) & 0x00FF0000) >> 16, \ - ((c) & 0x0000FF00) >> 8, \ - ((c) & 0x000000FF), \ - \ - ((d) & 0xFF000000) >> 24, \ - ((d) & 0x00FF0000) >> 16, \ - ((d) & 0x0000FF00) >> 8, \ - ((d) & 0x000000FF), \ +# define V3_ID(a, b, c, d) { \ + ((a) & 0x000000FF), \ + ((a) & 0x0000FF00) >> 8, \ + ((a) & 0x00FF0000) >> 16, \ + ((a) & 0xFF000000) >> 24, \ + \ + ((b) & 0x00FF0000) >> 16, \ + ((b) & 0xFF000000) >> 24, \ + ((b) & 0x000000FF), \ + ((b) & 0x0000FF00) >> 8, \ + \ + ((c) & 0xFF000000) >> 24, \ + ((c) & 0x00FF0000) >> 16, \ + ((c) & 0x0000FF00) >> 8, \ + ((c) & 0x000000FF), \ + \ + ((d) & 0xFF000000) >> 24, \ + ((d) & 0x00FF0000) >> 16, \ + ((d) & 0x0000FF00) >> 8, \ + ((d) & 0x000000FF), \ } #else // V3_COM_COMPAT enum { - V3_NO_INTERFACE = -1, + V3_NO_INTERFACE = -1, V3_OK, V3_TRUE = V3_OK, V3_FALSE, V3_INVALID_ARG, V3_NOT_IMPLEMENTED, V3_INTERNAL_ERR, - V3_NOT_INITIALISED, + V3_NOT_INITIALIZED, V3_NOMEM }; -# define V3_ID(a, b, c, d) { \ - ((a) & 0xFF000000) >> 24, \ - ((a) & 0x00FF0000) >> 16, \ - ((a) & 0x0000FF00) >> 8, \ - ((a) & 0x000000FF), \ - \ - ((b) & 0xFF000000) >> 24, \ - ((b) & 0x00FF0000) >> 16, \ - ((b) & 0x0000FF00) >> 8, \ - ((b) & 0x000000FF), \ - \ - ((c) & 0xFF000000) >> 24, \ - ((c) & 0x00FF0000) >> 16, \ - ((c) & 0x0000FF00) >> 8, \ - ((c) & 0x000000FF), \ - \ - ((d) & 0xFF000000) >> 24, \ - ((d) & 0x00FF0000) >> 16, \ - ((d) & 0x0000FF00) >> 8, \ - ((d) & 0x000000FF), \ +# define V3_ID(a, b, c, d) { \ + ((a) & 0xFF000000) >> 24, \ + ((a) & 0x00FF0000) >> 16, \ + ((a) & 0x0000FF00) >> 8, \ + ((a) & 0x000000FF), \ + \ + ((b) & 0xFF000000) >> 24, \ + ((b) & 0x00FF0000) >> 16, \ + ((b) & 0x0000FF00) >> 8, \ + ((b) & 0x000000FF), \ + \ + ((c) & 0xFF000000) >> 24, \ + ((c) & 0x00FF0000) >> 16, \ + ((c) & 0x0000FF00) >> 8, \ + ((c) & 0x000000FF), \ + \ + ((d) & 0xFF000000) >> 24, \ + ((d) & 0x00FF0000) >> 16, \ + ((d) & 0x0000FF00) >> 8, \ + ((d) & 0x000000FF), \ } #endif // V3_COM_COMPAT +#define V3_ID_COPY(iid) \ + { iid[0], iid[1], iid[ 2], iid[ 3], iid[ 4], iid[ 5], iid[ 6], iid[ 7], \ + iid[8], iid[9], iid[10], iid[11], iid[12], iid[13], iid[14], iid[15] } + /** * funknown */ struct v3_funknown { - V3_API v3_result (*query_interface) - (void *self, const v3_tuid iid, void **obj); - - V3_API uint32_t (*ref)(void *self); - V3_API uint32_t (*unref)(void *self); + v3_result (V3_API* query_interface)(void* self, const v3_tuid iid, void** obj); + uint32_t (V3_API* ref)(void* self); + uint32_t (V3_API* unref)(void* self); }; -static const v3_tuid v3_funknown_iid = +static constexpr const v3_tuid v3_funknown_iid = V3_ID(0x00000000, 0x00000000, 0xC0000000, 0x00000046); /** @@ -143,12 +152,73 @@ static const v3_tuid v3_funknown_iid = */ struct v3_plugin_base { +#ifndef __cplusplus struct v3_funknown; - - V3_API v3_result (*initialise) - (void *self, struct v3_funknown *context); - V3_API v3_result (*terminate)(void *self); +#endif + v3_result (V3_API* initialize)(void* self, struct v3_funknown** context); + v3_result (V3_API* terminate)(void* self); }; -static const v3_tuid v3_plugin_base_iid = +static constexpr const v3_tuid v3_plugin_base_iid = V3_ID(0x22888DDB, 0x156E45AE, 0x8358B348, 0x08190625); + +#ifdef __cplusplus + +/** + * cast object into its proper C++ type. + * this is needed because `struct v3_funknown;` on a C++ class does not inherit `v3_funknown`'s fields. + * + * we can use this as a little helper for keeping both C and C++ compatiblity. + * specialized templated calls are defined where required + * (that is, object inherits from something other than `v3_funknown`) + * + * example usage: `v3_cpp_obj(obj)->method(obj, args...);` + */ + +template<class T> static inline +constexpr T* v3_cpp_obj(T** obj) +{ + /** + * this ugly piece of code is required due to C++ assuming `reinterpret_cast` by default, + * but we need everything to be `static_cast` for it to be `constexpr` compatible. + */ + return static_cast<T*>(static_cast<void*>(static_cast<uint8_t*>(static_cast<void*>(*obj)) + sizeof(void*)*3)); +} + +/** + * helper C++ functions to manually call v3_funknown methods on an object. + */ + +template<class T, class M> static inline +v3_result v3_cpp_obj_query_interface(T** obj, const v3_tuid iid, M*** obj2) +{ + return static_cast<v3_funknown*>(static_cast<void*>(*obj))->query_interface(obj, iid, (void**)obj2); +} + +template<class T> static inline +uint32_t v3_cpp_obj_ref(T** obj) +{ + return static_cast<v3_funknown*>(static_cast<void*>(*obj))->ref(obj); +} + +template<class T> static inline +uint32_t v3_cpp_obj_unref(T** obj) +{ + return static_cast<v3_funknown*>(static_cast<void*>(*obj))->unref(obj); +} + +template<class T> static inline +v3_result v3_cpp_obj_initialize(T** obj, v3_funknown** context) +{ + return static_cast<v3_plugin_base*>( + static_cast<void*>(static_cast<uint8_t*>(static_cast<void*>(*obj)) + sizeof(void*)*3))->initialize(obj, context); +} + +template<class T> static inline +v3_result v3_cpp_obj_terminate(T** obj) +{ + return static_cast<v3_plugin_base*>( + static_cast<void*>(static_cast<uint8_t*>(static_cast<void*>(*obj)) + sizeof(void*)*3))->terminate(obj); +} + +#endif diff --git a/distrho/src/travesty/bstream.h b/distrho/src/travesty/bstream.h @@ -1,6 +1,6 @@ /* - * travesty, pure C interface to steinberg VST3 SDK - * Copyright (C) 2021 Filipe Coelho <falktx@falktx.com> + * travesty, pure C VST3-compatible interface + * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -25,17 +25,14 @@ enum v3_seek_mode { }; struct v3_bstream { +#ifndef __cplusplus struct v3_funknown; - - V3_API v3_result (*read) - (void *self, void *buffer, int32_t num_bytes, int32_t *bytes_read); - V3_API v3_result (*write) - (void *self, void *buffer, int32_t num_bytes, int32_t *bytes_written); - V3_API v3_result (*seek) - (void *self, int64_t pos, int32_t seek_mode, int64_t *result); - V3_API v3_result (*tell) - (void *self, int64_t *pos); +#endif + v3_result (V3_API *read)(void* self, void* buffer, int32_t num_bytes, int32_t* bytes_read); + v3_result (V3_API *write)(void* self, void* buffer, int32_t num_bytes, int32_t* bytes_written); + v3_result (V3_API *seek)(void* self, int64_t pos, int32_t seek_mode, int64_t* result); + v3_result (V3_API *tell)(void* self, int64_t* pos); }; -static const v3_tuid v3_bstream_iid = +static constexpr const v3_tuid v3_bstream_iid = V3_ID(0xC3BF6EA2, 0x30994752, 0x9B6BF990, 0x1EE33E9B); diff --git a/distrho/src/travesty/component.h b/distrho/src/travesty/component.h @@ -1,6 +1,6 @@ /* - * travesty, pure C interface to steinberg VST3 SDK - * Copyright (C) 2021 Filipe Coelho <falktx@falktx.com> + * travesty, pure C VST3-compatible interface + * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -30,13 +30,17 @@ enum v3_media_types { V3_EVENT }; -inline static const char * -v3_media_type_str(int32_t type) +static inline +const char* v3_media_type_str(int32_t type) { - switch (type) { - case V3_AUDIO: return "V3_AUDIO"; - case V3_EVENT: return "V3_EVENT"; - default: return "[unknown]"; + switch (type) + { + case V3_AUDIO: + return "V3_AUDIO"; + case V3_EVENT: + return "V3_EVENT"; + default: + return "[unknown]"; } } @@ -45,13 +49,17 @@ enum v3_bus_direction { V3_OUTPUT }; -inline static const char * -v3_bus_direction_str(int32_t d) +static inline +const char* v3_bus_direction_str(int32_t d) { - switch (d) { - case V3_INPUT: return "V3_INPUT"; - case V3_OUTPUT: return "V3_OUTPUT"; - default: return "[unknown]"; + switch (d) + { + case V3_INPUT: + return "V3_INPUT"; + case V3_OUTPUT: + return "V3_OUTPUT"; + default: + return "[unknown]"; } } @@ -61,57 +69,77 @@ enum v3_bus_types { }; enum v3_bus_flags { - V3_DEFAULT_ACTIVE = 1, + V3_DEFAULT_ACTIVE = 1 << 0, V3_IS_CONTROL_VOLTAGE = 1 << 1 }; +enum v3_io_mode { + V3_SIMPLE = 0, + V3_ADVANCED, + V3_OFFLINE_PROCESSING +}; + struct v3_bus_info { int32_t media_type; int32_t direction; int32_t channel_count; - v3_str_128 bus_name; int32_t bus_type; uint32_t flags; }; +struct v3_routing_info { + int32_t media_type; + int32_t bus_idx; + int32_t channel; +}; + /** * component */ -struct v3_routing_info; - struct v3_component { +#ifndef __cplusplus struct v3_plugin_base; - - V3_API v3_result (*get_controller_class_id) - (void *self, v3_tuid class_id); - - V3_API v3_result (*set_io_mode) - (void *self, int32_t io_mode); - - V3_API int32_t (*get_bus_count) - (void *self, int32_t media_type, int32_t bus_direction); - V3_API v3_result (*get_bus_info) - (void *self, int32_t media_type, int32_t bus_direction, - int32_t bus_idx, struct v3_bus_info *bus_info); - V3_API v3_result (*get_routing_info) - (void *self, struct v3_routing_info *input, - struct v3_routing_info *output); - V3_API v3_result (*activate_bus) - (void *self, int32_t media_type, int32_t bus_direction, - int32_t bus_idx, v3_bool state); - - V3_API v3_result (*set_active) - (void *self, v3_bool state); - - V3_API v3_result (*set_state) - (void *self, struct v3_bstream **); - V3_API v3_result (*get_state) - (void *self, struct v3_bstream **); +#endif + v3_result (V3_API *get_controller_class_id)(void* self, v3_tuid class_id); + v3_result (V3_API *set_io_mode)(void* self, int32_t io_mode); + int32_t (V3_API *get_bus_count)(void* self, int32_t media_type, int32_t bus_direction); + v3_result (V3_API *get_bus_info)(void* self, int32_t media_type, int32_t bus_direction, + int32_t bus_idx, struct v3_bus_info* bus_info); + v3_result (V3_API *get_routing_info)(void* self, struct v3_routing_info* input, struct v3_routing_info* output); + v3_result (V3_API *activate_bus)(void* self, int32_t media_type, int32_t bus_direction, + int32_t bus_idx, v3_bool state); + v3_result (V3_API *set_active)(void* self, v3_bool state); + v3_result (V3_API *set_state)(void* self, struct v3_bstream **); + v3_result (V3_API *get_state)(void* self, struct v3_bstream **); }; -static const v3_tuid v3_component_iid = +static constexpr const v3_tuid v3_component_iid = V3_ID(0xE831FF31, 0xF2D54301, 0x928EBBEE, 0x25697802); +#ifdef __cplusplus + +/** + * C++ variants + */ + +struct v3_component_cpp : v3_funknown { + v3_plugin_base base; + v3_component comp; +}; + +template<> inline +constexpr v3_component* v3_cpp_obj(v3_component** obj) +{ + /** + * this ugly piece of code is required due to C++ assuming `reinterpret_cast` by default, + * but we need everything to be `static_cast` for it to be `constexpr` compatible. + */ + return static_cast<v3_component*>( + static_cast<void*>(static_cast<uint8_t*>(static_cast<void*>(*obj)) + sizeof(void*)*5)); +} + +#endif + #include "align_pop.h" diff --git a/distrho/src/travesty/edit_controller.h b/distrho/src/travesty/edit_controller.h @@ -1,6 +1,6 @@ /* - * travesty, pure C interface to steinberg VST3 SDK - * Copyright (C) 2021 Filipe Coelho <falktx@falktx.com> + * travesty, pure C VST3-compatible interface + * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -26,29 +26,51 @@ * component handler */ +enum { + V3_RESTART_RELOAD_COMPONENT = 1 << 0, + V3_RESTART_IO_CHANGED = 1 << 1, + V3_RESTART_PARAM_VALUES_CHANGED = 1 << 2, + V3_RESTART_LATENCY_CHANGED = 1 << 3, + V3_RESTART_PARAM_TITLES_CHANGED = 1 << 4, + V3_RESTART_MIDI_CC_ASSIGNMENT_CHANGED = 1 << 5, + V3_RESTART_NOTE_EXPRESSION_CHANGED = 1 << 6, + V3_RESTART_IO_TITLES_CHANGED = 1 << 7, + V3_RESTART_PREFETCHABLE_SUPPORT_CHANGED = 1 << 8, + V3_RESTART_ROUTING_INFO_CHANGED = 1 << 9 +}; + struct v3_component_handler { +#ifndef __cplusplus struct v3_funknown; +#endif + v3_result (V3_API* begin_edit)(void* self, v3_param_id); + v3_result (V3_API* perform_edit)(void* self, v3_param_id, double value_normalised); + v3_result (V3_API* end_edit)(void* self, v3_param_id); + v3_result (V3_API* restart_component)(void* self, int32_t flags); +}; - V3_API v3_result (*begin_edit) - (void *self, v3_param_id); - V3_API v3_result (*perform_edit) - (void *self, v3_param_id, double value_normalised); - V3_API v3_result (*end_edit) - (void *self, v3_param_id); +static constexpr const v3_tuid v3_component_handler_iid = + V3_ID(0x93A0BEA3, 0x0BD045DB, 0x8E890B0C, 0xC1E46AC6); - V3_API v3_result (*restart_component) - (void *self, int32_t flags); +struct v3_component_handler2 { +#ifndef __cplusplus + struct v3_funknown; +#endif + v3_result (V3_API* set_dirty)(void* self, v3_bool state); + v3_result (V3_API* request_open_editor)(void* self, const char* name); + v3_result (V3_API* start_group_edit)(void* self); + v3_result (V3_API* finish_group_edit)(void* self); }; -static const v3_tuid v3_component_handler_iid = - V3_ID(0x93A0BEA3, 0x0BD045DB, 0x8E890B0C, 0xC1E46AC6); +static constexpr const v3_tuid v3_component_handler2_iid = + V3_ID(0xF040B4B3, 0xA36045EC, 0xABCDC045, 0xB4D5A2CC); /** * edit controller */ enum { - V3_PARAM_CAN_AUTOMATE = 1, + V3_PARAM_CAN_AUTOMATE = 1 << 0, V3_PARAM_READ_ONLY = 1 << 1, V3_PARAM_WRAP_AROUND = 1 << 2, V3_PARAM_IS_LIST = 1 << 3, @@ -59,55 +81,77 @@ enum { struct v3_param_info { v3_param_id param_id; - v3_str_128 title; v3_str_128 short_title; v3_str_128 units; - int32_t step_count; - double default_normalised_value; - int32_t unit_id; int32_t flags; }; struct v3_edit_controller { +#ifndef __cplusplus struct v3_plugin_base; +#endif + v3_result (V3_API* set_component_state)(void* self, struct v3_bstream**); + v3_result (V3_API* set_state)(void* self, struct v3_bstream**); + v3_result (V3_API* get_state)(void* self, struct v3_bstream**); + int32_t (V3_API* get_parameter_count)(void* self); + v3_result (V3_API* get_parameter_info)(void* self, int32_t param_idx, struct v3_param_info*); + v3_result (V3_API* get_parameter_string_for_value)(void* self, v3_param_id, double normalised, v3_str_128 output); + v3_result (V3_API* get_parameter_value_for_string)(void* self, v3_param_id, int16_t* input, double* output); + double (V3_API* normalised_parameter_to_plain)(void* self, v3_param_id, double normalised); + double (V3_API* plain_parameter_to_normalised)(void* self, v3_param_id, double plain); + double (V3_API* get_parameter_normalised)(void* self, v3_param_id); + v3_result (V3_API* set_parameter_normalised)(void* self, v3_param_id, double normalised); + v3_result (V3_API* set_component_handler)(void* self, struct v3_component_handler**); + struct v3_plugin_view** (V3_API* create_view)(void* self, const char* name); +}; - V3_API v3_result (*set_component_state) - (void *self, struct v3_bstream *); - V3_API v3_result (*set_state) - (void *self, struct v3_bstream *); - V3_API v3_result (*get_state) - (void *self, struct v3_bstream *); +static constexpr const v3_tuid v3_edit_controller_iid = + V3_ID(0xDCD7BBE3, 0x7742448D, 0xA874AACC, 0x979C759E); - V3_API int32_t (*get_parameter_count)(void *self); - V3_API v3_result (*get_param_info) - (void *self, int32_t param_idx, struct v3_param_info *); +/** + * midi mapping + */ - V3_API v3_result (*get_param_string_for_value) - (void *self, v3_param_id, double normalised, v3_str_128 output); - V3_API v3_result (*get_param_value_for_string) - (void *self, v3_param_id, int16_t *input, double *output); +struct v3_midi_mapping { +#ifndef __cplusplus + struct v3_funknown; +#endif + v3_result (V3_API* get_midi_controller_assignment)(void* self, int32_t bus, int16_t channel, int16_t cc, v3_param_id* id); +}; - V3_API double (*normalised_param_to_plain) - (void *self, v3_param_id, double normalised); - V3_API double (*plain_param_to_normalised) - (void *self, v3_param_id, double normalised); +static constexpr const v3_tuid v3_midi_mapping_iid = + V3_ID(0xDF0FF9F7, 0x49B74669, 0xB63AB732, 0x7ADBF5E5); - V3_API double (*get_param_normalised)(void *self, v3_param_id); - V3_API v3_result (*set_param_normalised) - (void *self, v3_param_id, double normalised); +#ifdef __cplusplus - V3_API v3_result (*set_component_handler) - (void *self, struct v3_component_handler **); +/** + * C++ variants + */ - V3_API struct v3_plug_view **(*create_view) - (void *self, const char *name); +struct v3_edit_controller_cpp : v3_funknown { + v3_plugin_base base; + v3_edit_controller ctrl; }; -static const v3_tuid v3_edit_controller_iid = - V3_ID(0xDCD7BBE3, 0x7742448D, 0xA874AACC, 0x979C759E); +struct v3_midi_mapping_cpp : v3_funknown { + v3_midi_mapping map; +}; + +template<> inline +constexpr v3_edit_controller* v3_cpp_obj(v3_edit_controller** obj) +{ + /** + * this ugly piece of code is required due to C++ assuming `reinterpret_cast` by default, + * but we need everything to be `static_cast` for it to be `constexpr` compatible. + */ + return static_cast<v3_edit_controller*>( + static_cast<void*>(static_cast<uint8_t*>(static_cast<void*>(*obj)) + sizeof(void*)*5)); +} + +#endif #include "align_pop.h" diff --git a/distrho/src/travesty/events.h b/distrho/src/travesty/events.h @@ -1,6 +1,6 @@ /* - * travesty, pure C interface to steinberg VST3 SDK - * Copyright (C) 2021 Filipe Coelho <falktx@falktx.com> + * travesty, pure C VST3-compatible interface + * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -22,18 +22,13 @@ /** * note events - * - * i know there's others, but we don't need them right now. whatever. - * handle them later. */ struct v3_event_note_on { int16_t channel; int16_t pitch; // MIDI note number - float tuning; float velocity; - int32_t length; int32_t note_id; }; @@ -41,9 +36,7 @@ struct v3_event_note_on { struct v3_event_note_off { int16_t channel; int16_t pitch; // MIDI note number - float velocity; - int32_t note_id; float tuning; }; @@ -51,14 +44,12 @@ struct v3_event_note_off { struct v3_event_data { uint32_t size; uint32_t type; - - const uint8_t *bytes; + const uint8_t* bytes; }; struct v3_event_poly_pressure { int16_t channel; int16_t pitch; - float pressure; int32_t note_id; }; @@ -67,22 +58,19 @@ struct v3_event_chord { int16_t root; int16_t bass_note; int16_t mask; - uint16_t text_len; - const int16_t *text; + const int16_t* text; }; struct v3_event_scale { int16_t root; int16_t mask; - uint16_t text_len; - const int16_t *text; + const int16_t* text; }; struct v3_event_legacy_midi_cc_out { uint8_t cc_number; - int8_t channel; int8_t value; int8_t value2; @@ -96,9 +84,8 @@ struct v3_event_note_expression_value { struct v3_event_note_expression_text { int32_t note_id; - uint32_t text_len; - const int16_t *text; + const int16_t* text; }; /** @@ -106,7 +93,7 @@ struct v3_event_note_expression_text { */ enum v3_event_flags { - V3_EVENT_IS_LIVE = 1 + V3_EVENT_IS_LIVE = 1 << 0 }; enum v3_event_type { @@ -118,19 +105,15 @@ enum v3_event_type { V3_EVENT_NOTE_EXP_TEXT = 5, V3_EVENT_CHORD = 6, V3_EVENT_SCALE = 7, - V3_EVENT_LEGACY_MIDI_CC_OUT = 65535 }; struct v3_event { int32_t bus_index; int32_t sample_offset; - double ppq_position; uint16_t flags; - uint16_t type; - union { struct v3_event_note_on note_on; struct v3_event_note_off note_off; @@ -149,16 +132,27 @@ struct v3_event { */ struct v3_event_list { +#ifndef __cplusplus struct v3_funknown; - - V3_API uint32_t (*get_event_count)(void *self); - V3_API v3_result (*get_event) - (void *self, int32_t idx, struct v3_event *); - V3_API v3_result (*add_event) - (void *self, struct v3_event *); +#endif + uint32_t (V3_API* get_event_count)(void* self); + v3_result (V3_API* get_event)(void* self, int32_t idx, struct v3_event* event); + v3_result (V3_API* add_event)(void* self, struct v3_event* event); }; -static const v3_tuid v3_event_list_iid = +static constexpr const v3_tuid v3_event_list_iid = V3_ID(0x3A2C4214, 0x346349FE, 0xB2C4F397, 0xB9695A44); +#ifdef __cplusplus + +/** + * C++ variants + */ + +struct v3_event_list_cpp : v3_funknown { + v3_event_list list; +}; + +#endif + #include "align_pop.h" diff --git a/distrho/src/travesty/factory.h b/distrho/src/travesty/factory.h @@ -1,6 +1,6 @@ /* - * travesty, pure C interface to steinberg VST3 SDK - * Copyright (C) 2021 Filipe Coelho <falktx@falktx.com> + * travesty, pure C VST3-compatible interface + * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -26,44 +26,43 @@ struct v3_factory_info { char vendor[64]; char url[256]; char email[128]; - int32_t flags; + int32_t flags; // set to 0x10 (unicode) }; struct v3_class_info { v3_tuid class_id; - int32_t cardinality; // set to 0x7FFFFFFF + int32_t cardinality; // set to 0x7FFFFFFF (many instances) char category[32]; char name[64]; }; struct v3_plugin_factory { +#ifndef __cplusplus struct v3_funknown; - - V3_API v3_result (*get_factory_info) - (void *self, struct v3_factory_info *); - - V3_API int32_t (*num_classes)(void *self); - - V3_API v3_result (*get_class_info) - (void *self, int32_t idx, struct v3_class_info *); - - V3_API v3_result (*create_instance) - (void *self, const v3_tuid class_id, const v3_tuid iid, void **instance); +#endif + v3_result (V3_API *get_factory_info)(void* self, struct v3_factory_info*); + int32_t (V3_API *num_classes)(void* self); + v3_result (V3_API *get_class_info)(void* self, int32_t idx, struct v3_class_info*); + v3_result (V3_API *create_instance)(void* self, const v3_tuid class_id, const v3_tuid iid, void** instance); }; -static const v3_tuid v3_plugin_factory_iid = +static constexpr const v3_tuid v3_plugin_factory_iid = V3_ID(0x7A4D811C, 0x52114A1F, 0xAED9D2EE, 0x0B43BF9F); /** * plugin factory v2 */ +enum { + V3_DISTRIBUTABLE = 1 << 0, + V3_SIMPLE_MODE = 1 << 1 +}; + struct v3_class_info_2 { v3_tuid class_id; int32_t cardinality; // set to 0x7FFFFFFF char category[32]; char name[64]; - uint32_t class_flags; char sub_categories[128]; char vendor[64]; @@ -72,13 +71,13 @@ struct v3_class_info_2 { }; struct v3_plugin_factory_2 { +#ifndef __cplusplus struct v3_plugin_factory; - - V3_API v3_result (*get_class_info_2) - (void *self, int32_t idx, struct v3_class_info_2 *); +#endif + v3_result (V3_API *get_class_info_2)(void* self, int32_t idx, struct v3_class_info_2*); }; -static const v3_tuid v3_plugin_factory_2_iid = +static constexpr const v3_tuid v3_plugin_factory_2_iid = V3_ID(0x0007B650, 0xF24B4C0B, 0xA464EDB9, 0xF00B2ABB); /** @@ -93,7 +92,6 @@ struct v3_class_info_3 { int32_t cardinality; // set to 0x7FFFFFFF char category[32]; int16_t name[64]; - uint32_t class_flags; char sub_categories[128]; int16_t vendor[64]; @@ -102,14 +100,48 @@ struct v3_class_info_3 { }; struct v3_plugin_factory_3 { +#ifndef __cplusplus struct v3_plugin_factory_2; +#endif + v3_result (V3_API *get_class_info_utf16)(void* self, int32_t idx, struct v3_class_info_3*); + v3_result (V3_API *set_host_context)(void* self, struct v3_funknown** host); +}; + +static constexpr const v3_tuid v3_plugin_factory_3_iid = + V3_ID(0x4555A2AB, 0xC1234E57, 0x9B122910, 0x36878931); - V3_API v3_result (*get_class_info_utf16) - (void *self, int32_t idx, struct v3_class_info_3 *); +#ifdef __cplusplus - V3_API v3_result (*set_host_context) - (void *self, struct v3_funknown *host); +/** + * C++ variants + */ + +struct v3_plugin_factory_cpp : v3_funknown { + v3_plugin_factory v1; + v3_plugin_factory_2 v2; + v3_plugin_factory_3 v3; }; -static const v3_tuid v3_plugin_factory_3_iid = - V3_ID(0x4555A2AB, 0xC1234E57, 0x9B122910, 0x36878931); +template<> inline +constexpr v3_plugin_factory_2* v3_cpp_obj(v3_plugin_factory_2** obj) +{ + /** + * this ugly piece of code is required due to C++ assuming `reinterpret_cast` by default, + * but we need everything to be `static_cast` for it to be `constexpr` compatible. + */ + return static_cast<v3_plugin_factory_2*>( + static_cast<void*>(static_cast<uint8_t*>(static_cast<void*>(*obj)) + sizeof(void*)*7)); +} + +template<> inline +constexpr v3_plugin_factory_3* v3_cpp_obj(v3_plugin_factory_3** obj) +{ + /** + * this ugly piece of code is required due to C++ assuming `reinterpret_cast` by default, + * but we need everything to be `static_cast` for it to be `constexpr` compatible. + */ + return static_cast<v3_plugin_factory_3*>( + static_cast<void*>(static_cast<uint8_t*>(static_cast<void*>(*obj)) + sizeof(void*)*8)); +} + +#endif diff --git a/distrho/src/travesty/host.h b/distrho/src/travesty/host.h @@ -0,0 +1,50 @@ +/* + * travesty, pure C VST3-compatible interface + * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +#include "message.h" + +#include "align_push.h" + +/** + * host application + */ + +struct v3_host_application { +#ifndef __cplusplus + struct v3_funknown; +#endif + v3_result (V3_API* get_name)(void* self, v3_str_128 name); // wtf? + v3_result (V3_API* create_instance)(void* self, v3_tuid cid, v3_tuid iid, void** obj); +}; + +static constexpr const v3_tuid v3_host_application_iid = + V3_ID(0x58E595CC, 0xDB2D4969, 0x8B6AAF8C, 0x36A664E5); + +#ifdef __cplusplus + +/** + * C++ variants + */ + +struct v3_host_application_cpp : v3_funknown { + v3_host_application app; +}; + +#endif + +#include "align_pop.h" diff --git a/distrho/src/travesty/message.h b/distrho/src/travesty/message.h @@ -0,0 +1,96 @@ +/* + * travesty, pure C VST3-compatible interface + * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +#include "base.h" + +#include "align_push.h" + +/** + * attribute list + */ + +struct v3_attribute_list { +#ifndef __cplusplus + struct v3_funknown; +#endif + v3_result (V3_API* set_int)(void* self, const char* id, int64_t value); + v3_result (V3_API* get_int)(void* self, const char* id, int64_t* value); + v3_result (V3_API* set_float)(void* self, const char* id, double value); + v3_result (V3_API* get_float)(void* self, const char* id, double* value); + v3_result (V3_API* set_string)(void* self, const char* id, const int16_t* string); + v3_result (V3_API* get_string)(void* self, const char* id, int16_t* string, uint32_t size); + v3_result (V3_API* set_binary)(void* self, const char* id, const void* data, uint32_t size); + v3_result (V3_API* get_binary)(void* self, const char* id, const void** data, uint32_t* size); +}; + +static constexpr const v3_tuid v3_attribute_list_iid = + V3_ID(0x1E5F0AEB, 0xCC7F4533, 0xA2544011, 0x38AD5EE4); + +/** + * message + */ + +struct v3_message { +#ifndef __cplusplus + struct v3_funknown; +#endif + const char* (V3_API* get_message_id)(void* self); + void (V3_API* set_message_id)(void* self, const char* id); + v3_attribute_list** (V3_API* get_attributes)(void* self); +}; + +static constexpr const v3_tuid v3_message_iid = + V3_ID(0x936F033B, 0xC6C047DB, 0xBB0882F8, 0x13C1E613); + +/** + * connection point + */ + +struct v3_connection_point { +#ifndef __cplusplus + struct v3_funknown; +#endif + v3_result (V3_API* connect)(void* self, struct v3_connection_point** other); + v3_result (V3_API* disconnect)(void* self, struct v3_connection_point** other); + v3_result (V3_API* notify)(void* self, struct v3_message** message); +}; + +static constexpr const v3_tuid v3_connection_point_iid = + V3_ID(0x70A4156F, 0x6E6E4026, 0x989148BF, 0xAA60D8D1); + +#ifdef __cplusplus + +/** + * C++ variants + */ + +struct v3_attribute_list_cpp : v3_funknown { + v3_attribute_list attrlist; +}; + +struct v3_message_cpp : v3_funknown { + v3_message msg; +}; + +struct v3_connection_point_cpp : v3_funknown { + v3_connection_point point; +}; + +#endif + +#include "align_pop.h" diff --git a/distrho/src/travesty/unit.h b/distrho/src/travesty/unit.h @@ -0,0 +1,69 @@ +/* + * travesty, pure C VST3-compatible interface + * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +#include "base.h" + +#include "align_push.h" + +struct v3_unit_info { + int32_t id; // 0 for root unit + int32_t parent_unit_id; // -1 for none + v3_str_128 name; + int32_t program_list_id; // -1 for none +}; + +struct v3_program_list_info { + int32_t id; + v3_str_128 name; + int32_t programCount; +}; + +struct v3_unit_information { +#ifndef __cplusplus + struct v3_funknown; +#endif + int32_t (V3_API* get_unit_count)(void* self); + v3_result (V3_API* get_unit_info)(void* self, int32_t unit_idx, v3_unit_info* info); + int32_t (V3_API* get_program_list_count)(void* self); + v3_result (V3_API* get_program_list_info)(void* self, int32_t list_idx, v3_program_list_info* info); + v3_result (V3_API* get_program_name)(void* self, int32_t list_id, int32_t program_idx, v3_str_128 name); + v3_result (V3_API* get_program_info)(void* self, int32_t list_id, int32_t program_idx, const char *attribute_id, v3_str_128 attribute_value); + v3_result (V3_API* has_program_pitch_names)(void* self, int32_t list_id, int32_t program_idx); + v3_result (V3_API* get_program_pitch_name)(void* self, int32_t list_id, int32_t program_idx, int16_t midi_pitch, v3_str_128 name); + int32_t (V3_API* get_selected_unit)(void* self); + v3_result (V3_API* select_unit)(void* self, int32_t unit_id); + v3_result (V3_API* get_unit_by_bus)(void* self, int32_t type, int32_t bus_direction, int32_t bus_idx, int32_t channel, int32_t* unit_id); + v3_result (V3_API* set_unit_program_data)(void* self, int32_t list_or_unit_id, int32_t program_idx, struct v3_bstream** data); +}; + +static constexpr const v3_tuid v3_unit_information_iid = + V3_ID(0x3D4BD6B5, 0x913A4FD2, 0xA886E768, 0xA5EB92C1); + +#ifdef __cplusplus + +/** + * C++ variants + */ + +struct v3_unit_information_cpp : v3_funknown { + v3_unit_information unit; +}; + +#endif + +#include "align_pop.h" diff --git a/distrho/src/travesty/view.h b/distrho/src/travesty/view.h @@ -1,6 +1,6 @@ /* - * travesty, pure C interface to steinberg VST3 SDK - * Copyright (C) 2021 Filipe Coelho <falktx@falktx.com> + * travesty, pure C VST3-compatible interface + * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -19,7 +19,7 @@ #include "base.h" /** - * base IPlugFrame stuff + * base view stuff */ struct v3_view_rect { @@ -41,77 +41,153 @@ struct v3_view_rect { # define V3_VIEW_PLATFORM_TYPE_NATIVE V3_VIEW_PLATFORM_TYPE_X11 #endif -struct v3_plug_frame; +/** + * plugin view + */ -struct v3_plug_view { +struct v3_plugin_frame; + +struct v3_plugin_view { +#ifndef __cplusplus struct v3_funknown; +#endif + v3_result (V3_API* is_platform_type_supported)(void* self, const char* platform_type); + v3_result (V3_API* attached)(void* self, void* parent, const char* platform_type); + v3_result (V3_API* removed)(void* self); + v3_result (V3_API* on_wheel)(void* self, float distance); + v3_result (V3_API* on_key_down)(void* self, int16_t key_char, int16_t key_code, int16_t modifiers); + v3_result (V3_API* on_key_up)(void* self, int16_t key_char, int16_t key_code, int16_t modifiers); + v3_result (V3_API* get_size)(void* self, struct v3_view_rect*); + v3_result (V3_API* on_size)(void* self, struct v3_view_rect*); + v3_result (V3_API* on_focus)(void* self, v3_bool state); + v3_result (V3_API* set_frame)(void* self, struct v3_plugin_frame**); + v3_result (V3_API* can_resize)(void* self); + v3_result (V3_API* check_size_constraint)(void* self, struct v3_view_rect*); +}; - V3_API v3_result (*is_platform_type_supported) - (void *self, const char *platform_type); +static constexpr const v3_tuid v3_plugin_view_iid = + V3_ID(0x5BC32507, 0xD06049EA, 0xA6151B52, 0x2B755B29); - V3_API v3_result (*attached) - (void *self, void *parent, const char *platform_type); - V3_API v3_result (*removed)(void *self); +/** + * plugin frame + */ - V3_API v3_result (*on_wheel)(void *self, float distance); - V3_API v3_result (*on_key_down) - (void *self, int16_t key_char, int16_t key_code, int16_t modifiers); - V3_API v3_result (*on_key_up) - (void *self, int16_t key_char, int16_t key_code, int16_t modifiers); +struct v3_plugin_frame { +#ifndef __cplusplus + struct v3_funknown; +#endif + v3_result (V3_API* resize_view)(void* self, struct v3_plugin_view**, struct v3_view_rect*); +}; - V3_API v3_result (*get_size) - (void *self, struct v3_view_rect *); - V3_API v3_result (*set_size) - (void *self, struct v3_view_rect *); +static constexpr const v3_tuid v3_plugin_frame_iid = + V3_ID(0x367FAF01, 0xAFA94693, 0x8D4DA2A0, 0xED0882A3); - V3_API v3_result (*on_focus) - (void *self, v3_bool state); +/** + * steinberg content scaling support + * (same IID/iface as presonus view scaling) + */ - V3_API v3_result (*set_frame) - (void *self, struct v3_plug_frame *); - V3_API v3_result (*can_resize)(void *self); - V3_API v3_result (*check_size_constraint) - (void *self, struct v3_view_rect *); +struct v3_plugin_view_content_scale { +#ifndef __cplusplus + struct v3_funknown; +#endif + v3_result (V3_API* set_content_scale_factor)(void* self, float factor); }; -static const v3_tuid v3_plug_view_iid = - V3_ID(0x5BC32507, 0xD06049EA, 0xA6151B52, 0x2B755B29); +static constexpr const v3_tuid v3_plugin_view_content_scale_iid = + V3_ID(0x65ED9690, 0x8AC44525, 0x8AADEF7A, 0x72EA703F); -struct v3_plug_frame { - struct v3_funknown; +/** + * support for querying the view to find what control is underneath the mouse + */ - V3_API v3_result (*resize_view) - (void *self, struct v3_plug_view *, struct v3_view_rect *); +struct v3_plugin_view_parameter_finder { +#ifndef __cplusplus + struct v3_funknown; +#endif + v3_result (V3_API* find_parameter)(void* self, int32_t x, int32_t y, v3_param_id *); }; -static const v3_tuid v3_plug_frame_iid = - V3_ID(0x367FAF01, 0xAFA94693, 0x8D4DA2A0, 0xED0882A3); +static constexpr const v3_tuid v3_plugin_view_parameter_finder_iid = + V3_ID(0x0F618302, 0x215D4587, 0xA512073C, 0x77B9D383); /** - * steinberg content scaling support - * (same IID/iface as presonus view scaling) + * linux event handler */ -struct v3_plug_view_content_scale_steinberg { +struct v3_event_handler { +#ifndef __cplusplus struct v3_funknown; +#endif + void (V3_API* on_fd_is_set)(void* self, int fd); +}; + +static constexpr const v3_tuid v3_event_handler_iid = + V3_ID(0x561E65C9, 0x13A0496F, 0x813A2C35, 0x654D7983); + +/** + * linux timer handler + */ - V3_API v3_result (*set_content_scale_factor) - (void *self, float factor); +struct v3_timer_handler { +#ifndef __cplusplus + struct v3_funknown; +#endif + void (V3_API* on_timer)(void* self); }; -static const v3_tuid v3_plug_view_content_scale_steinberg_iid = - V3_ID(0x65ED9690, 0x8AC44525, 0x8AADEF7A, 0x72EA703F); +static constexpr const v3_tuid v3_timer_handler_iid = + V3_ID(0x10BDD94F, 0x41424774, 0x821FAD8F, 0xECA72CA9); /** - * support for querying the view to find what control is underneath the mouse + * linux host run loop */ -struct v3_plug_view_param_finder { +struct v3_run_loop { +#ifndef __cplusplus struct v3_funknown; +#endif + v3_result (V3_API* register_event_handler)(void* self, v3_event_handler** handler, int fd); + v3_result (V3_API* unregister_event_handler)(void* self, v3_event_handler** handler); + v3_result (V3_API* register_timer)(void* self, v3_timer_handler** handler, uint64_t ms); + v3_result (V3_API* unregister_timer)(void* self, v3_timer_handler** handler); +}; + +static constexpr const v3_tuid v3_run_loop_iid = + V3_ID(0x18C35366, 0x97764F1A, 0x9C5B8385, 0x7A871389); + +#ifdef __cplusplus + +/** + * C++ variants + */ - V3_API v3_result (*find_parameter) - (void *self, int32_t x, int32_t y, v3_param_id *); +struct v3_plugin_view_cpp : v3_funknown { + v3_plugin_view view; }; -static const v3_tuid v3_plug_view_param_finder_iid = - V3_ID(0x0F618302, 0x215D4587, 0xA512073C, 0x77B9D383); +struct v3_plugin_frame_cpp : v3_funknown { + v3_plugin_frame frame; +}; + +struct v3_plugin_view_content_scale_cpp : v3_funknown { + v3_plugin_view_content_scale scale; +}; + +struct v3_plugin_view_parameter_finder_cpp : v3_funknown { + v3_plugin_view_parameter_finder finder; +}; + +struct v3_event_handler_cpp : v3_funknown { + v3_event_handler handler; +}; + +struct v3_timer_handler_cpp : v3_funknown { + v3_timer_handler timer; +}; + +struct v3_run_loop_cpp : v3_funknown { + v3_run_loop loop; +}; + +#endif diff --git a/dpf.doxygen b/dpf.doxygen @@ -242,6 +242,7 @@ SEARCH_INCLUDES = YES INCLUDE_PATH = INCLUDE_FILE_PATTERNS = PREDEFINED = DOXYGEN \ + DEBUG \ HAVE_CAIRO=1 \ HAVE_OPENGL=1 \ DISTRHO_PLUGIN_NAME="Plugin Name" \ diff --git a/examples/CVPort/ExamplePluginCVPort.cpp b/examples/CVPort/ExamplePluginCVPort.cpp @@ -21,6 +21,8 @@ START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------------------------------------------- +static constexpr const float kMaxHoldTime = 1.0f; + /** Simple plugin to demonstrate how to modify input/output port type in DPF. The plugin outputs sample & hold (S&H) value of input signal. @@ -28,8 +30,6 @@ START_NAMESPACE_DISTRHO */ class ExamplePluginCVPort : public Plugin { - static constexpr const float kMaxHoldTime = 1.0f; - public: ExamplePluginCVPort() : Plugin(1, 0, 0), // 1 parameters, 0 programs, 0 states @@ -159,7 +159,7 @@ protected: parameter.name = "Hold Time"; parameter.symbol = "hold_time"; - parameter.hints = kParameterIsAutomable|kParameterIsLogarithmic; + parameter.hints = kParameterIsAutomatable|kParameterIsLogarithmic; parameter.ranges.min = 0.0f; parameter.ranges.max = kMaxHoldTime; parameter.ranges.def = 0.1f; diff --git a/examples/CVPort/Makefile b/examples/CVPort/Makefile @@ -23,8 +23,7 @@ include ../../Makefile.plugins.mk # -------------------------------------------------------------- # Enable all possible plugin types -TARGETS += jack -TARGETS += lv2_dsp +TARGETS = jack lv2_dsp all: $(TARGETS) diff --git a/examples/CairoUI/CairoExamplePlugin.cpp b/examples/CairoUI/CairoExamplePlugin.cpp @@ -54,6 +54,19 @@ public: return d_cconst('d', 'C', 'a', 'i'); } + /** + Initialize the audio port @a index.@n + This function will be called once, shortly after the plugin is created. + */ + void initAudioPort(bool input, uint32_t index, AudioPort& port) override + { + // treat meter audio ports as stereo + port.groupId = kPortGroupMono; + + // everything else is as default + Plugin::initAudioPort(input, index, port); + } + void initParameter(uint32_t index, Parameter& parameter) { // unused diff --git a/examples/CairoUI/DistrhoPluginInfo.h b/examples/CairoUI/DistrhoPluginInfo.h @@ -117,4 +117,5 @@ */ #define DISTRHO_UI_URI DISTRHO_PLUGIN_URI "#UI" +#define DISTRHO_UI_FILE_BROWSER 0 #define DISTRHO_UI_USE_CAIRO 1 diff --git a/examples/CairoUI/Makefile b/examples/CairoUI/Makefile @@ -40,7 +40,8 @@ endif # HAVE_LIBLO endif # MACOS_OR_WINDOWS TARGETS += lv2_sep -TARGETS += vst +TARGETS += vst2 +TARGETS += vst3 endif # HAVE_CAIRO diff --git a/examples/EmbedExternalUI/DistrhoPluginInfo.h b/examples/EmbedExternalUI/DistrhoPluginInfo.h @@ -27,6 +27,7 @@ #define DISTRHO_PLUGIN_IS_RT_SAFE 1 #define DISTRHO_PLUGIN_NUM_INPUTS 2 #define DISTRHO_PLUGIN_NUM_OUTPUTS 2 +#define DISTRHO_UI_FILE_BROWSER 0 #define DISTRHO_UI_USER_RESIZABLE 1 enum Parameters { diff --git a/examples/EmbedExternalUI/EmbedExternalExamplePlugin.cpp b/examples/EmbedExternalUI/EmbedExternalExamplePlugin.cpp @@ -100,6 +100,19 @@ protected: * Init */ /** + Initialize the audio port @a index.@n + This function will be called once, shortly after the plugin is created. + */ + void initAudioPort(bool input, uint32_t index, AudioPort& port) override + { + // treat meter audio ports as stereo + port.groupId = kPortGroupStereo; + + // everything else is as default + Plugin::initAudioPort(input, index, port); + } + + /** Initialize the parameter @a index. This function will be called once, shortly after the plugin is created. */ @@ -108,7 +121,7 @@ protected: switch (index) { case kParameterWidth: - parameter.hints = kParameterIsAutomable|kParameterIsInteger; + parameter.hints = kParameterIsAutomatable|kParameterIsInteger; parameter.ranges.def = 512.0f; parameter.ranges.min = 256.0f; parameter.ranges.max = 4096.0f; @@ -117,7 +130,7 @@ protected: parameter.unit = "px"; break; case kParameterHeight: - parameter.hints = kParameterIsAutomable|kParameterIsInteger; + parameter.hints = kParameterIsAutomatable|kParameterIsInteger; parameter.ranges.def = 256.0f; parameter.ranges.min = 256.0f; parameter.ranges.max = 4096.0f; @@ -152,7 +165,7 @@ protected: /** Change a parameter value. The host may call this function from any context, including realtime processing. - When a parameter is marked as automable, you must ensure no non-realtime operations are performed. + When a parameter is marked as automatable, you must ensure no non-realtime operations are performed. @note This function will only be called for parameter inputs. */ void setParameterValue(uint32_t index, float value) override diff --git a/examples/EmbedExternalUI/EmbedExternalExampleUI.cpp b/examples/EmbedExternalUI/EmbedExternalExampleUI.cpp @@ -19,7 +19,8 @@ #include "DistrhoUI.hpp" -#if defined(DISTRHO_OS_MAC) +#if defined(DISTRHO_OS_HAIKU) +#elif defined(DISTRHO_OS_MAC) # import <Cocoa/Cocoa.h> #elif defined(DISTRHO_OS_WINDOWS) # define WIN32_CLASS_NAME "DPF-EmbedExternalExampleUI" @@ -60,7 +61,9 @@ START_NAMESPACE_DISTRHO class EmbedExternalExampleUI : public UI { -#if defined(DISTRHO_OS_MAC) +#if defined(DISTRHO_OS_HAIKU) + char d; +#elif defined(DISTRHO_OS_MAC) NSView* fView; NSExternalWindow* fWindow; #elif defined(DISTRHO_OS_WINDOWS) @@ -73,7 +76,9 @@ class EmbedExternalExampleUI : public UI public: EmbedExternalExampleUI() : UI(512, 256), -#if defined(DISTRHO_OS_MAC) +#if defined(DISTRHO_OS_HAIKU) + d(0) +#elif defined(DISTRHO_OS_MAC) fView(nullptr), fWindow(nullptr) #elif defined(DISTRHO_OS_WINDOWS) @@ -86,7 +91,8 @@ public: const bool standalone = isStandalone(); d_stdout("isStandalone %d", (int)standalone); -#if defined(DISTRHO_OS_MAC) +#if defined(DISTRHO_OS_HAIKU) +#elif defined(DISTRHO_OS_MAC) NSAutoreleasePool* const pool = [[NSAutoreleasePool alloc]init]; [NSApplication sharedApplication]; @@ -218,7 +224,8 @@ public: ~EmbedExternalExampleUI() { -#if defined(DISTRHO_OS_MAC) +#if defined(DISTRHO_OS_HAIKU) +#elif defined(DISTRHO_OS_MAC) if (fView == nullptr) return; @@ -274,7 +281,8 @@ protected: void focus() override { d_stdout("focus"); -#if defined(DISTRHO_OS_MAC) +#if defined(DISTRHO_OS_HAIKU) +#elif defined(DISTRHO_OS_MAC) DISTRHO_SAFE_ASSERT_RETURN(fWindow != nullptr,); [fWindow orderFrontRegardless]; @@ -295,13 +303,14 @@ protected: uintptr_t getNativeWindowHandle() const noexcept override { -#if defined(DISTRHO_OS_MAC) +#if defined(DISTRHO_OS_HAIKU) +#elif defined(DISTRHO_OS_MAC) return (uintptr_t)fView; #elif defined(DISTRHO_OS_WINDOWS) + return (uintptr_t)fWindow; #else return (uintptr_t)fWindow; #endif - return 0; } void sizeChanged(uint width, uint height) override @@ -309,7 +318,8 @@ protected: d_stdout("sizeChanged %u %u", width, height); UI::sizeChanged(width, height); -#if defined(DISTRHO_OS_MAC) +#if defined(DISTRHO_OS_HAIKU) +#elif defined(DISTRHO_OS_MAC) NSRect rect = [fView frame]; rect.size = CGSizeMake((CGFloat)width, (CGFloat)height); [fView setFrame:rect]; @@ -329,7 +339,8 @@ protected: void titleChanged(const char* const title) override { d_stdout("titleChanged %s", title); -#if defined(DISTRHO_OS_MAC) +#if defined(DISTRHO_OS_HAIKU) +#elif defined(DISTRHO_OS_MAC) if (fWindow != nil) { if (NSString* const nsTitle = [[NSString alloc] @@ -351,7 +362,8 @@ protected: void transientParentWindowChanged(const uintptr_t winId) override { d_stdout("transientParentWindowChanged %lu", winId); -#if defined(DISTRHO_OS_MAC) +#if defined(DISTRHO_OS_HAIKU) +#elif defined(DISTRHO_OS_MAC) #elif defined(DISTRHO_OS_WINDOWS) #else DISTRHO_SAFE_ASSERT_RETURN(fWindow != 0,); @@ -362,7 +374,8 @@ protected: void visibilityChanged(const bool visible) override { d_stdout("visibilityChanged %d", visible); -#if defined(DISTRHO_OS_MAC) +#if defined(DISTRHO_OS_HAIKU) +#elif defined(DISTRHO_OS_MAC) DISTRHO_SAFE_ASSERT_RETURN(fView != nullptr,); if (fWindow != nil) { @@ -394,7 +407,8 @@ protected: void uiIdle() override { // d_stdout("uiIdle"); -#if defined(DISTRHO_OS_MAC) +#if defined(DISTRHO_OS_HAIKU) +#elif defined(DISTRHO_OS_MAC) if (isEmbed()) { return; } diff --git a/examples/EmbedExternalUI/Makefile b/examples/EmbedExternalUI/Makefile @@ -35,6 +35,7 @@ TARGETS += jack TARGETS += dssi TARGETS += lv2_sep TARGETS += vst2 +TARGETS += vst3 all: $(TARGETS) diff --git a/examples/ExternalUI/DistrhoPluginInfo.h b/examples/ExternalUI/DistrhoPluginInfo.h @@ -27,6 +27,7 @@ #define DISTRHO_PLUGIN_IS_RT_SAFE 1 #define DISTRHO_PLUGIN_NUM_INPUTS 1 #define DISTRHO_PLUGIN_NUM_OUTPUTS 1 +#define DISTRHO_UI_FILE_BROWSER 0 enum Parameters { kParameterLevel = 0, diff --git a/examples/ExternalUI/ExternalExamplePlugin.cpp b/examples/ExternalUI/ExternalExamplePlugin.cpp @@ -107,7 +107,7 @@ protected: if (index != 0) return; - parameter.hints = kParameterIsAutomable|kParameterIsInteger; + parameter.hints = kParameterIsAutomatable|kParameterIsInteger; parameter.ranges.def = 0.0f; parameter.ranges.min = 0.0f; parameter.ranges.max = 100.0f; @@ -134,7 +134,7 @@ protected: /** Change a parameter value. The host may call this function from any context, including realtime processing. - When a parameter is marked as automable, you must ensure no non-realtime operations are performed. + When a parameter is marked as automatable, you must ensure no non-realtime operations are performed. @note This function will only be called for parameter inputs. */ void setParameterValue(uint32_t index, float value) override diff --git a/examples/FileHandling/DistrhoPluginInfo.h b/examples/FileHandling/DistrhoPluginInfo.h @@ -26,7 +26,7 @@ #define DISTRHO_PLUGIN_NUM_INPUTS 1 #define DISTRHO_PLUGIN_NUM_OUTPUTS 1 #define DISTRHO_PLUGIN_WANT_STATE 1 -#define DISTRHO_PLUGIN_WANT_STATEFILES 1 +#define DISTRHO_UI_FILE_BROWSER 1 #define DISTRHO_UI_USER_RESIZABLE 1 #define DISTRHO_UI_USE_NANOVG 1 diff --git a/examples/FileHandling/FileHandlingPlugin.cpp b/examples/FileHandling/FileHandlingPlugin.cpp @@ -99,6 +99,19 @@ protected: * Init */ /** + Initialize the audio port @a index.@n + This function will be called once, shortly after the plugin is created. + */ + void initAudioPort(bool input, uint32_t index, AudioPort& port) override + { + // treat meter audio ports as stereo + port.groupId = kPortGroupMono; + + // everything else is as default + Plugin::initAudioPort(input, index, port); + } + + /** Initialize the parameter @a index. This function will be called once, shortly after the plugin is created. */ @@ -124,42 +137,28 @@ protected: } /** - Set the state key and default value of @a index.@n - This function will be called once, shortly after the plugin is created.@n - Must be implemented by your plugin class only if DISTRHO_PLUGIN_WANT_STATE is enabled. + Initialize the state @a index.@n + This function will be called once, shortly after the plugin is created. */ - void initState(uint32_t index, String& stateKey, String& defaultStateValue) override + void initState(uint32_t index, State& state) override { switch (index) { case kStateFile1: - stateKey = "file1"; + state.key = "file1"; + state.label = "File 1"; break; case kStateFile2: - stateKey = "file2"; + state.key = "file2"; + state.label = "File 2"; break; case kStateFile3: - stateKey = "file3"; + state.key = "file3"; + state.label = "File 3"; break; } - defaultStateValue = ""; - } - - /** - TODO API under construction - */ - bool isStateFile(uint32_t index) override - { - switch (index) - { - case kStateFile1: - case kStateFile2: - case kStateFile3: - return true; - } - - return false; + state.hints = kStateIsFilenamePath; } /* -------------------------------------------------------------------------------------------------------- diff --git a/examples/FileHandling/Makefile b/examples/FileHandling/Makefile @@ -34,7 +34,8 @@ else TARGETS += lv2_dsp endif -TARGETS += vst +TARGETS += vst2 +TARGETS += vst3 all: $(TARGETS) diff --git a/examples/ImguiSimpleGain/CParamSmooth.hpp b/examples/ImguiSimpleGain/CParamSmooth.hpp @@ -1,41 +0,0 @@ -/** - * One-pole LPF for smooth parameter changes - * - * https://www.musicdsp.org/en/latest/Filters/257-1-pole-lpf-for-smooth-parameter-changes.html - */ - -#ifndef C_PARAM_SMOOTH_H -#define C_PARAM_SMOOTH_H - -#include <math.h> - -#define TWO_PI 6.283185307179586476925286766559f - -class CParamSmooth { -public: - CParamSmooth(float smoothingTimeMs, float samplingRate) - : t(smoothingTimeMs) - { - setSampleRate(samplingRate); - } - - ~CParamSmooth() { } - - void setSampleRate(float samplingRate) { - if (samplingRate != fs) { - fs = samplingRate; - a = exp(-TWO_PI / (t * 0.001f * samplingRate)); - b = 1.0f - a; - z = 0.0f; - } - } - - inline float process(float in) { - return z = (in * b) + (z * a); - } -private: - float a, b, t, z; - double fs = 0.0; -}; - -#endif // #ifndef C_PARAM_SMOOTH_H diff --git a/examples/ImguiSimpleGain/DistrhoPluginInfo.h b/examples/ImguiSimpleGain/DistrhoPluginInfo.h @@ -1,145 +0,0 @@ -/* - * Simple Gain audio effect for DISTRHO Plugin Framework (DPF) - * SPDX-License-Identifier: MIT - * - * Copyright (C) 2021 Jean Pierre Cimalando <jp-dev@inbox.ru> - * Copyright (C) 2021 Filipe Coelho <falktx@falktx.com> - * - * Permission to use, copy, modify, and/or distribute this software for any purpose with - * or without fee is hereby granted, provided that the above copyright notice and this - * permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD - * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN - * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL - * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER - * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -/** - The plugin name.@n - This is used to identify your plugin before a Plugin instance can be created. - @note This macro is required. - */ -#define DISTRHO_PLUGIN_NAME "ImguiSimpleGain" - -/** - Number of audio inputs the plugin has. - @note This macro is required. - */ -#define DISTRHO_PLUGIN_NUM_INPUTS 2 - -/** - Number of audio outputs the plugin has. - @note This macro is required. - */ -#define DISTRHO_PLUGIN_NUM_OUTPUTS 2 - -/** - The plugin URI when exporting in LV2 format. - @note This macro is required. - */ -#define DISTRHO_PLUGIN_URI "http://distrho.sf.net/examples/imguisimplegain" - -/** - Wherever the plugin has a custom %UI. - @see DISTRHO_UI_USE_NANOVG - @see UI - */ -#define DISTRHO_PLUGIN_HAS_UI 1 - -/** - Wherever the plugin processing is realtime-safe.@n - TODO - list rtsafe requirements - */ -#define DISTRHO_PLUGIN_IS_RT_SAFE 1 - -/** - Wherever the plugin is a synth.@n - @ref DISTRHO_PLUGIN_WANT_MIDI_INPUT is automatically enabled when this is too. - @see DISTRHO_PLUGIN_WANT_MIDI_INPUT - */ -#define DISTRHO_PLUGIN_IS_SYNTH 0 - -/** - Enable direct access between the %UI and plugin code. - @see UI::getPluginInstancePointer() - @note DO NOT USE THIS UNLESS STRICTLY NECESSARY!! - Try to avoid it at all costs! - */ -#define DISTRHO_PLUGIN_WANT_DIRECT_ACCESS 0 - -/** - Wherever the plugin introduces latency during audio or midi processing. - @see Plugin::setLatency(uint32_t) - */ -#define DISTRHO_PLUGIN_WANT_LATENCY 0 - -/** - Wherever the plugin wants MIDI input.@n - This is automatically enabled if @ref DISTRHO_PLUGIN_IS_SYNTH is true. - */ -#define DISTRHO_PLUGIN_WANT_MIDI_INPUT 0 - -/** - Wherever the plugin wants MIDI output. - @see Plugin::writeMidiEvent(const MidiEvent&) - */ -#define DISTRHO_PLUGIN_WANT_MIDI_OUTPUT 0 - -/** - Wherever the plugin provides its own internal programs. - @see Plugin::initProgramName(uint32_t, String&) - @see Plugin::loadProgram(uint32_t) - */ -#define DISTRHO_PLUGIN_WANT_PROGRAMS 1 - -/** - Wherever the plugin uses internal non-parameter data. - @see Plugin::initState(uint32_t, String&, String&) - @see Plugin::setState(const char*, const char*) - */ -#define DISTRHO_PLUGIN_WANT_STATE 0 - -/** - Wherever the plugin wants time position information from the host. - @see Plugin::getTimePosition() - */ -#define DISTRHO_PLUGIN_WANT_TIMEPOS 0 - -/** - Wherever the %UI uses NanoVG for drawing instead of the default raw OpenGL calls.@n - When enabled your %UI instance will subclass @ref NanoWidget instead of @ref Widget. - */ -#define DISTRHO_UI_USE_NANOVG 0 - -/** - The %UI URI when exporting in LV2 format.@n - By default this is set to @ref DISTRHO_PLUGIN_URI with "#UI" as suffix. - */ -#define DISTRHO_UI_URI DISTRHO_PLUGIN_URI "#UI" - -/** - Wherever the %UI uses a custom toolkit implementation based on OpenGL.@n - When enabled, the macros @ref DISTRHO_UI_CUSTOM_INCLUDE_PATH and @ref DISTRHO_UI_CUSTOM_WIDGET_TYPE are required. - */ -#define DISTRHO_UI_USE_CUSTOM 1 - -/** - The include path to the header file used by the custom toolkit implementation. - This path must be relative to dpf/distrho/DistrhoUI.hpp - @see DISTRHO_UI_USE_CUSTOM - */ -#define DISTRHO_UI_CUSTOM_INCLUDE_PATH "ImGuiUI.hpp" - -/** - The top-level-widget typedef to use for the custom toolkit. - This widget class MUST be a subclass of DGL TopLevelWindow class. - It is recommended that you keep this widget class inside the DGL namespace, - and define widget type as e.g. DGL_NAMESPACE::MyCustomTopLevelWidget. - @see DISTRHO_UI_USE_CUSTOM - */ -#define DISTRHO_UI_CUSTOM_WIDGET_TYPE DGL_NAMESPACE::ImGuiUI - -#define DISTRHO_UI_USER_RESIZABLE 1 diff --git a/examples/ImguiSimpleGain/ImGuiSrc.cpp b/examples/ImguiSimpleGain/ImGuiSrc.cpp @@ -1,35 +0,0 @@ -/* - * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2021 Jean Pierre Cimalando <jp-dev@inbox.ru> - * - * Permission to use, copy, modify, and/or distribute this software for any purpose with - * or without fee is hereby granted, provided that the above copyright notice and this - * permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD - * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN - * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL - * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER - * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <imgui.h> -#if !defined(IMGUI_GL2) && !defined(IMGUI_GL3) -# define IMGUI_GL2 1 -#endif -#if defined(IMGUI_GL2) -# include <imgui_impl_opengl2.h> -#elif defined(IMGUI_GL3) -# include <imgui_impl_opengl3.h> -#endif - -#include <imgui.cpp> -#include <imgui_draw.cpp> -#include <imgui_tables.cpp> -#include <imgui_widgets.cpp> -#if defined(IMGUI_GL2) -#include <imgui_impl_opengl2.cpp> -#elif defined(IMGUI_GL3) -#include <imgui_impl_opengl3.cpp> -#endif diff --git a/examples/ImguiSimpleGain/ImGuiUI.cpp b/examples/ImguiSimpleGain/ImGuiUI.cpp @@ -1,314 +0,0 @@ -/* - * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2021 Jean Pierre Cimalando <jp-dev@inbox.ru> - * - * Permission to use, copy, modify, and/or distribute this software for any purpose with - * or without fee is hereby granted, provided that the above copyright notice and this - * permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD - * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN - * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL - * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER - * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include "Application.hpp" - -#include <GL/glew.h> - -#include "ImGuiUI.hpp" -#include <imgui.h> -#if !defined(IMGUI_GL2) && !defined(IMGUI_GL3) -# define IMGUI_GL2 1 -#endif -#if defined(IMGUI_GL2) -# include <imgui_impl_opengl2.h> -#elif defined(IMGUI_GL3) -# include <imgui_impl_opengl3.h> -#endif -#include <chrono> -#include <cmath> - -START_NAMESPACE_DGL - -struct ImGuiUI::Impl -{ - explicit Impl(ImGuiUI* self); - ~Impl(); - - void setupGL(); - void cleanupGL(); - - static int mouseButtonToImGui(int button); - - ImGuiUI* fSelf = nullptr; - ImGuiContext* fContext = nullptr; - Color fBackgroundColor{0.25f, 0.25f, 0.25f}; - int fRepaintIntervalMs = 15; - - using Clock = std::chrono::steady_clock; - Clock::time_point fLastRepainted; - bool fWasEverPainted = false; -}; - -ImGuiUI::ImGuiUI(Window& windowToMapTo) - : TopLevelWidget(windowToMapTo), - fImpl(new ImGuiUI::Impl(this)) -{ - getApp().addIdleCallback(this); -} - -ImGuiUI::~ImGuiUI() -{ - delete fImpl; -} - -void ImGuiUI::setBackgroundColor(Color color) -{ - fImpl->fBackgroundColor = color; -} - -void ImGuiUI::setRepaintInterval(int intervalMs) -{ - fImpl->fRepaintIntervalMs = intervalMs; -} - -void ImGuiUI::onDisplay() -{ - ImGui::SetCurrentContext(fImpl->fContext); - -#if defined(IMGUI_GL2) - ImGui_ImplOpenGL2_NewFrame(); -#elif defined(IMGUI_GL3) - ImGui_ImplOpenGL3_NewFrame(); -#endif - - ImGui::NewFrame(); - onImGuiDisplay(); - ImGui::Render(); - - ImGuiIO &io = ImGui::GetIO(); - - glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); - - Color backgroundColor = fImpl->fBackgroundColor; - glClearColor( - backgroundColor.red, backgroundColor.green, - backgroundColor.blue, backgroundColor.alpha); - glClear(GL_COLOR_BUFFER_BIT); - glLoadIdentity(); - -#if defined(IMGUI_GL2) - ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData()); -#elif defined(IMGUI_GL3) - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); -#endif - - fImpl->fLastRepainted = Impl::Clock::now(); - fImpl->fWasEverPainted = true; -} - -bool ImGuiUI::onKeyboard(const KeyboardEvent& event) -{ - ImGui::SetCurrentContext(fImpl->fContext); - ImGuiIO &io = ImGui::GetIO(); - - if (event.press) - io.AddInputCharacter(event.key); - - int imGuiKey = event.key; - if (imGuiKey >= 0 && imGuiKey < 128) - { - if (imGuiKey >= 'a' && imGuiKey <= 'z') - imGuiKey = imGuiKey - 'a' + 'A'; - io.KeysDown[imGuiKey] = event.press; - } - - return io.WantCaptureKeyboard; -} - -bool ImGuiUI::onSpecial(const SpecialEvent& event) -{ - ImGui::SetCurrentContext(fImpl->fContext); - ImGuiIO &io = ImGui::GetIO(); - - int imGuiKey = IM_ARRAYSIZE(io.KeysDown) - event.key; - io.KeysDown[imGuiKey] = event.press; - - switch (event.key) - { - case kKeyShift: - io.KeyShift = event.press; - break; - case kKeyControl: - io.KeyCtrl = event.press; - break; - case kKeyAlt: - io.KeyAlt = event.press; - break; - case kKeySuper: - io.KeySuper = event.press; - break; - default: - break; - } - - return io.WantCaptureKeyboard; -} - -bool ImGuiUI::onMouse(const MouseEvent& event) -{ - ImGui::SetCurrentContext(fImpl->fContext); - ImGuiIO &io = ImGui::GetIO(); - - int imGuiButton = Impl::mouseButtonToImGui(event.button); - if (imGuiButton != -1) - io.MouseDown[imGuiButton] = event.press; - - return io.WantCaptureMouse; -} - -bool ImGuiUI::onMotion(const MotionEvent& event) -{ - ImGui::SetCurrentContext(fImpl->fContext); - ImGuiIO &io = ImGui::GetIO(); - - // FIXME - const double scaleFactor = 1; // getScaleFactor(); - io.MousePos.x = std::round(scaleFactor * event.pos.getX()); - io.MousePos.y = std::round(scaleFactor * event.pos.getY()); - - return false; -} - -bool ImGuiUI::onScroll(const ScrollEvent& event) -{ - ImGui::SetCurrentContext(fImpl->fContext); - ImGuiIO &io = ImGui::GetIO(); - - io.MouseWheel += event.delta.getY(); - io.MouseWheelH += event.delta.getX(); - - return io.WantCaptureMouse; -} - -void ImGuiUI::onResize(const ResizeEvent& event) -{ - TopLevelWidget::onResize(event); - - const uint width = event.size.getWidth(); - const uint height = event.size.getHeight(); - - ImGui::SetCurrentContext(fImpl->fContext); - ImGuiIO &io = ImGui::GetIO(); - - const double scaleFactor = getScaleFactor(); - io.DisplaySize.x = std::round(scaleFactor * width); - io.DisplaySize.y = std::round(scaleFactor * height); -} - -void ImGuiUI::idleCallback() -{ - bool shouldRepaint; - - if (fImpl->fWasEverPainted) - { - Impl::Clock::duration elapsed = - Impl::Clock::now() - fImpl->fLastRepainted; - std::chrono::milliseconds elapsedMs = - std::chrono::duration_cast<std::chrono::milliseconds>(elapsed); - shouldRepaint = elapsedMs.count() > fImpl->fRepaintIntervalMs; - } - else - { - shouldRepaint = true; - } - - if (shouldRepaint) - repaint(); -} - -ImGuiUI::Impl::Impl(ImGuiUI* self) - : fSelf(self) -{ - setupGL(); -} - -ImGuiUI::Impl::~Impl() -{ - cleanupGL(); -} - -void ImGuiUI::Impl::setupGL() -{ - DISTRHO_SAFE_ASSERT_RETURN(glewInit() == 0,); - - IMGUI_CHECKVERSION(); - fContext = ImGui::CreateContext(); - ImGui::SetCurrentContext(fContext); - - ImGuiIO &io = ImGui::GetIO(); - const double scaleFactor = fSelf->getScaleFactor(); - io.DisplaySize.x = std::round(scaleFactor * fSelf->getWidth()); - io.DisplaySize.y = std::round(scaleFactor * fSelf->getHeight()); - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - io.IniFilename = nullptr; - - io.KeyMap[ImGuiKey_Tab] = '\t'; - io.KeyMap[ImGuiKey_LeftArrow] = IM_ARRAYSIZE(io.KeysDown) - kKeyLeft; - io.KeyMap[ImGuiKey_RightArrow] = IM_ARRAYSIZE(io.KeysDown) - kKeyRight; - io.KeyMap[ImGuiKey_UpArrow] = IM_ARRAYSIZE(io.KeysDown) - kKeyUp; - io.KeyMap[ImGuiKey_DownArrow] = IM_ARRAYSIZE(io.KeysDown) - kKeyDown; - io.KeyMap[ImGuiKey_PageUp] = IM_ARRAYSIZE(io.KeysDown) - kKeyPageUp; - io.KeyMap[ImGuiKey_PageDown] = IM_ARRAYSIZE(io.KeysDown) - kKeyPageDown; - io.KeyMap[ImGuiKey_Home] = IM_ARRAYSIZE(io.KeysDown) - kKeyHome; - io.KeyMap[ImGuiKey_End] = IM_ARRAYSIZE(io.KeysDown) - kKeyEnd; - io.KeyMap[ImGuiKey_Insert] = IM_ARRAYSIZE(io.KeysDown) - kKeyInsert; - io.KeyMap[ImGuiKey_Delete] = 127; - io.KeyMap[ImGuiKey_Backspace] = '\b'; - io.KeyMap[ImGuiKey_Space] = ' '; - io.KeyMap[ImGuiKey_Enter] = '\r'; - io.KeyMap[ImGuiKey_Escape] = 27; - io.KeyMap[ImGuiKey_A] = 'A'; - io.KeyMap[ImGuiKey_C] = 'C'; - io.KeyMap[ImGuiKey_V] = 'V'; - io.KeyMap[ImGuiKey_X] = 'X'; - io.KeyMap[ImGuiKey_Y] = 'Y'; - io.KeyMap[ImGuiKey_Z] = 'Z'; - -#if defined(IMGUI_GL2) - ImGui_ImplOpenGL2_Init(); -#elif defined(IMGUI_GL3) - ImGui_ImplOpenGL3_Init(); -#endif -} - -void ImGuiUI::Impl::cleanupGL() -{ - ImGui::SetCurrentContext(fContext); -#if defined(IMGUI_GL2) - ImGui_ImplOpenGL2_Shutdown(); -#elif defined(IMGUI_GL3) - ImGui_ImplOpenGL3_Shutdown(); -#endif - ImGui::DestroyContext(fContext); -} - -int ImGuiUI::Impl::mouseButtonToImGui(int button) -{ - switch (button) - { - default: - return -1; - case 1: - return 0; - case 2: - return 2; - case 3: - return 1; - } -} - -END_NAMESPACE_DGL diff --git a/examples/ImguiSimpleGain/ImGuiUI.hpp b/examples/ImguiSimpleGain/ImGuiUI.hpp @@ -1,56 +0,0 @@ -/* - * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2021 Jean Pierre Cimalando <jp-dev@inbox.ru> - * - * Permission to use, copy, modify, and/or distribute this software for any purpose with - * or without fee is hereby granted, provided that the above copyright notice and this - * permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD - * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN - * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL - * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER - * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#pragma once - -#include "TopLevelWidget.hpp" -#include "Color.hpp" - -#ifndef DGL_OPENGL -# error ImGUI is only available in OpenGL mode -#endif - -START_NAMESPACE_DGL - -/** - ImGui user interface class. -*/ -class ImGuiUI : public TopLevelWidget, - public IdleCallback { -public: - ImGuiUI(Window& windowToMapTo); - ~ImGuiUI() override; - void setBackgroundColor(Color color); - void setRepaintInterval(int intervalMs); - -protected: - virtual void onImGuiDisplay() = 0; - - virtual void onDisplay() override; - virtual bool onKeyboard(const KeyboardEvent& event) override; - virtual bool onSpecial(const SpecialEvent& event) override; - virtual bool onMouse(const MouseEvent& event) override; - virtual bool onMotion(const MotionEvent& event) override; - virtual bool onScroll(const ScrollEvent& event) override; - virtual void onResize(const ResizeEvent& event) override; - virtual void idleCallback() override; - -private: - struct Impl; - Impl* fImpl; -}; - -END_NAMESPACE_DGL diff --git a/examples/ImguiSimpleGain/Makefile b/examples/ImguiSimpleGain/Makefile @@ -1,52 +0,0 @@ -#!/usr/bin/make -f -# Makefile for DISTRHO Plugins # -# ---------------------------- # -# Created by falkTX, Christopher Arndt, and Patrick Desaulniers -# - -# -------------------------------------------------------------- -# Project name, used for binaries - -NAME = d_ImguiSimpleGain - -# -------------------------------------------------------------- -# Files to build - -FILES_DSP = \ - PluginSimpleGain.cpp - -FILES_UI = \ - UISimpleGain.cpp \ - ImGuiUI.cpp \ - ImGuiSrc.cpp - -# -------------------------------------------------------------- -# Do some magic - -include ../../Makefile.plugins.mk - -BUILD_CXX_FLAGS += -I../../../imgui -I../../../imgui/backends -BUILD_CXX_FLAGS += $(shell $(PKG_CONFIG) glew --cflags) -LINK_FLAGS += $(shell $(PKG_CONFIG) glew --libs) - -# -------------------------------------------------------------- -# Enable all selected plugin types - -ifeq ($(HAVE_OPENGL),true) - -TARGETS += jack - -ifneq ($(MACOS_OR_WINDOWS),true) -ifeq ($(HAVE_LIBLO),true) -TARGETS += dssi -endif # HAVE_LIBLO -endif # MACOS_OR_WINDOWS - -TARGETS += lv2_sep -TARGETS += vst - -endif # HAVE_OPENGL - -all: $(TARGETS) - -# -------------------------------------------------------------- diff --git a/examples/ImguiSimpleGain/PluginSimpleGain.cpp b/examples/ImguiSimpleGain/PluginSimpleGain.cpp @@ -1,161 +0,0 @@ -/* - * Simple Gain audio effect based on DISTRHO Plugin Framework (DPF) - * - * SPDX-License-Identifier: MIT - * - * Copyright (C) 2021 Jean Pierre Cimalando <jp-dev@inbox.ru> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#include "PluginSimpleGain.hpp" - -START_NAMESPACE_DISTRHO - -// ----------------------------------------------------------------------- - -PluginSimpleGain::PluginSimpleGain() - : Plugin(paramCount, presetCount, 0) // paramCount param(s), presetCount program(s), 0 states -{ - smooth_gain = new CParamSmooth(20.0f, getSampleRate()); - - for (unsigned p = 0; p < paramCount; ++p) { - Parameter param; - initParameter(p, param); - setParameterValue(p, param.ranges.def); - } -} - -PluginSimpleGain::~PluginSimpleGain() { - delete smooth_gain; -} - -// ----------------------------------------------------------------------- -// Init - -void PluginSimpleGain::initParameter(uint32_t index, Parameter& parameter) { - if (index >= paramCount) - return; - - parameter.ranges.min = -90.0f; - parameter.ranges.max = 30.0f; - parameter.ranges.def = -0.0f; - parameter.unit = "db"; - parameter.hints = kParameterIsAutomable; - - switch (index) { - case paramGain: - parameter.name = "Gain (dB)"; - parameter.shortName = "Gain"; - parameter.symbol = "gain"; - break; - } -} - -/** - Set the name of the program @a index. - This function will be called once, shortly after the plugin is created. -*/ -void PluginSimpleGain::initProgramName(uint32_t index, String& programName) { - if (index < presetCount) { - programName = factoryPresets[index].name; - } -} - -// ----------------------------------------------------------------------- -// Internal data - -/** - Optional callback to inform the plugin about a sample rate change. -*/ -void PluginSimpleGain::sampleRateChanged(double newSampleRate) { - fSampleRate = newSampleRate; - smooth_gain->setSampleRate(newSampleRate); -} - -/** - Get the current value of a parameter. -*/ -float PluginSimpleGain::getParameterValue(uint32_t index) const { - return fParams[index]; -} - -/** - Change a parameter value. -*/ -void PluginSimpleGain::setParameterValue(uint32_t index, float value) { - fParams[index] = value; - - switch (index) { - case paramGain: - gain = DB_CO(CLAMP(fParams[paramGain], -90.0, 30.0)); - break; - } -} - -/** - Load a program. - The host may call this function from any context, - including realtime processing. -*/ -void PluginSimpleGain::loadProgram(uint32_t index) { - if (index < presetCount) { - for (int i=0; i < paramCount; i++) { - setParameterValue(i, factoryPresets[index].params[i]); - } - } -} - -// ----------------------------------------------------------------------- -// Process - -void PluginSimpleGain::activate() { - // plugin is activated -} - - - -void PluginSimpleGain::run(const float** inputs, float** outputs, - uint32_t frames) { - - // get the left and right audio inputs - const float* const inpL = inputs[0]; - const float* const inpR = inputs[1]; - - // get the left and right audio outputs - float* const outL = outputs[0]; - float* const outR = outputs[1]; - - // apply gain against all samples - for (uint32_t i=0; i < frames; ++i) { - float gainval = smooth_gain->process(gain); - outL[i] = inpL[i] * gainval; - outR[i] = inpR[i] * gainval; - } -} - -// ----------------------------------------------------------------------- - -Plugin* createPlugin() { - return new PluginSimpleGain(); -} - -// ----------------------------------------------------------------------- - -END_NAMESPACE_DISTRHO diff --git a/examples/ImguiSimpleGain/PluginSimpleGain.hpp b/examples/ImguiSimpleGain/PluginSimpleGain.hpp @@ -1,161 +0,0 @@ -/* - * Simple Gain audio effect based on DISTRHO Plugin Framework (DPF) - * - * SPDX-License-Identifier: MIT - * - * Copyright (C) 2021 Jean Pierre Cimalando <jp-dev@inbox.ru> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#ifndef PLUGIN_SIMPLEGAIN_H -#define PLUGIN_SIMPLEGAIN_H - -#include "DistrhoPlugin.hpp" -#include "CParamSmooth.hpp" - -START_NAMESPACE_DISTRHO - -#ifndef MIN -#define MIN(a,b) ( (a) < (b) ? (a) : (b) ) -#endif - -#ifndef MAX -#define MAX(a,b) ( (a) > (b) ? (a) : (b) ) -#endif - -#ifndef CLAMP -#define CLAMP(v, min, max) (MIN((max), MAX((min), (v)))) -#endif - -#ifndef DB_CO -#define DB_CO(g) ((g) > -90.0f ? powf(10.0f, (g) * 0.05f) : 0.0f) -#endif - -// ----------------------------------------------------------------------- - -class PluginSimpleGain : public Plugin { -public: - enum Parameters { - paramGain = 0, - paramCount - }; - - PluginSimpleGain(); - - ~PluginSimpleGain(); - -protected: - // ------------------------------------------------------------------- - // Information - - const char* getLabel() const noexcept override { - return "SimpleGain"; - } - - const char* getDescription() const override { - return "A simple audio volume gain plugin"; - } - - const char* getMaker() const noexcept override { - return "example.com"; - } - - const char* getHomePage() const override { - return "https://example.com/plugins/simplegain"; - } - - const char* getLicense() const noexcept override { - return "https://spdx.org/licenses/MIT"; - } - - uint32_t getVersion() const noexcept override { - return d_version(0, 1, 0); - } - - // Go to: - // - // http://service.steinberg.de/databases/plugin.nsf/plugIn - // - // Get a proper plugin UID and fill it in here! - int64_t getUniqueId() const noexcept override { - return d_cconst('a', 'b', 'c', 'd'); - } - - // ------------------------------------------------------------------- - // Init - - void initParameter(uint32_t index, Parameter& parameter) override; - void initProgramName(uint32_t index, String& programName) override; - - // ------------------------------------------------------------------- - // Internal data - - float getParameterValue(uint32_t index) const override; - void setParameterValue(uint32_t index, float value) override; - void loadProgram(uint32_t index) override; - - // ------------------------------------------------------------------- - // Optional - - // Optional callback to inform the plugin about a sample rate change. - void sampleRateChanged(double newSampleRate) override; - - // ------------------------------------------------------------------- - // Process - - void activate() override; - - void run(const float**, float** outputs, uint32_t frames) override; - - - // ------------------------------------------------------------------- - -private: - float fParams[paramCount]; - double fSampleRate; - float gain; - CParamSmooth *smooth_gain; - - DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginSimpleGain) -}; - -struct Preset { - const char* name; - float params[PluginSimpleGain::paramCount]; -}; - -const Preset factoryPresets[] = { - { - "Unity Gain", - {0.0f} - } - //,{ - // "Another preset", // preset name - // {-14.0f, ...} // array of presetCount float param values - //} -}; - -const uint presetCount = sizeof(factoryPresets) / sizeof(Preset); - -// ----------------------------------------------------------------------- - -END_NAMESPACE_DISTRHO - -#endif // #ifndef PLUGIN_SIMPLEGAIN_H diff --git a/examples/ImguiSimpleGain/UISimpleGain.cpp b/examples/ImguiSimpleGain/UISimpleGain.cpp @@ -1,130 +0,0 @@ -/* - * Simple Gain audio effect based on DISTRHO Plugin Framework (DPF) - * - * SPDX-License-Identifier: MIT - * - * Copyright (C) 2021 Jean Pierre Cimalando <jp-dev@inbox.ru> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#include "UISimpleGain.hpp" -#include <imgui.h> -// #include "Window.hpp" - -START_NAMESPACE_DISTRHO - -// ----------------------------------------------------------------------- -// Init / Deinit - -UISimpleGain::UISimpleGain() -: UI(600, 400) -{ - setGeometryConstraints(600, 400, true); -} - -UISimpleGain::~UISimpleGain() { - -} - -// ----------------------------------------------------------------------- -// DSP/Plugin callbacks - -/** - A parameter has changed on the plugin side. - This is called by the host to inform the UI about parameter changes. -*/ -void UISimpleGain::parameterChanged(uint32_t index, float value) { - params[index] = value; - - switch (index) { - case PluginSimpleGain::paramGain: - // do something when Gain param is set, such as update a widget - break; - } - - (void)value; -} - -/** - A program has been loaded on the plugin side. - This is called by the host to inform the UI about program changes. -*/ -void UISimpleGain::programLoaded(uint32_t index) { - if (index < presetCount) { - for (int i=0; i < PluginSimpleGain::paramCount; i++) { - // set values for each parameter and update their widgets - parameterChanged(i, factoryPresets[index].params[i]); - } - } -} - -/** - Optional callback to inform the UI about a sample rate change on the plugin side. -*/ -void UISimpleGain::sampleRateChanged(double newSampleRate) { - (void)newSampleRate; -} - -// ----------------------------------------------------------------------- -// Widget callbacks - - -/** - A function called to draw the view contents. -*/ -void UISimpleGain::onImGuiDisplay() { - float width = getWidth(); - float height = getHeight(); - float margin = 20.0f; - - ImGui::SetNextWindowPos(ImVec2(margin, margin)); - ImGui::SetNextWindowSize(ImVec2(width - 2 * margin, height - 2 * margin)); - - if (ImGui::Begin("Simple gain")) { - static char aboutText[256] = - "This is a demo plugin made with ImGui.\n"; - ImGui::InputTextMultiline("About", aboutText, sizeof(aboutText)); - - float& gain = params[PluginSimpleGain::paramGain]; - if (ImGui::SliderFloat("Gain (dB)", &gain, -90.0f, 30.0f)) - { - if (ImGui::IsItemActivated()) - { - editParameter(PluginSimpleGain::paramGain, true); - } - setParameterValue(PluginSimpleGain::paramGain, gain); - } - if (ImGui::IsItemDeactivated()) - { - editParameter(PluginSimpleGain::paramGain, false); - } - } - ImGui::End(); -} - -// ----------------------------------------------------------------------- - -UI* createUI() { - return new UISimpleGain(); -} - -// ----------------------------------------------------------------------- - -END_NAMESPACE_DISTRHO diff --git a/examples/ImguiSimpleGain/UISimpleGain.hpp b/examples/ImguiSimpleGain/UISimpleGain.hpp @@ -1,55 +0,0 @@ -/* - * Simple Gain audio effect based on DISTRHO Plugin Framework (DPF) - * - * SPDX-License-Identifier: MIT - * - * Copyright (C) 2021 Jean Pierre Cimalando <jp-dev@inbox.ru> - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#ifndef UI_SIMPLEGAIN_H -#define UI_SIMPLEGAIN_H - -#include "DistrhoUI.hpp" -#include "PluginSimpleGain.hpp" - -START_NAMESPACE_DISTRHO - -class UISimpleGain : public UI { -public: - UISimpleGain(); - ~UISimpleGain(); - -protected: - void parameterChanged(uint32_t, float value) override; - void programLoaded(uint32_t index) override; - void sampleRateChanged(double newSampleRate) override; - - void onImGuiDisplay() override; - -private: - float params[PluginSimpleGain::paramCount] {}; - - DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(UISimpleGain) -}; - -END_NAMESPACE_DISTRHO - -#endif diff --git a/examples/Info/CMakeLists.txt b/examples/Info/CMakeLists.txt @@ -2,7 +2,7 @@ # ------------------------------ # dpf_add_plugin(d_info - TARGETS jack lv2 vst2 + TARGETS jack lv2 vst2 vst3 FILES_DSP InfoExamplePlugin.cpp FILES_UI diff --git a/examples/Info/DistrhoPluginInfo.h b/examples/Info/DistrhoPluginInfo.h @@ -26,6 +26,7 @@ #define DISTRHO_PLUGIN_NUM_INPUTS 2 #define DISTRHO_PLUGIN_NUM_OUTPUTS 2 #define DISTRHO_PLUGIN_WANT_TIMEPOS 1 +#define DISTRHO_UI_FILE_BROWSER 0 #define DISTRHO_UI_USER_RESIZABLE 1 #define DISTRHO_UI_USE_NANOVG 1 diff --git a/examples/Info/InfoExamplePlugin.cpp b/examples/Info/InfoExamplePlugin.cpp @@ -104,12 +104,25 @@ protected: * Init */ /** + Initialize the audio port @a index.@n + This function will be called once, shortly after the plugin is created. + */ + void initAudioPort(bool input, uint32_t index, AudioPort& port) override + { + // treat meter audio ports as stereo + port.groupId = kPortGroupStereo; + + // everything else is as default + Plugin::initAudioPort(input, index, port); + } + + /** Initialize the parameter @a index. This function will be called once, shortly after the plugin is created. */ void initParameter(uint32_t index, Parameter& parameter) override { - parameter.hints = kParameterIsAutomable|kParameterIsOutput; + parameter.hints = kParameterIsAutomatable|kParameterIsOutput; parameter.ranges.def = 0.0f; parameter.ranges.min = 0.0f; parameter.ranges.max = 16777216.0f; @@ -193,7 +206,7 @@ protected: /** Change a parameter value. The host may call this function from any context, including realtime processing. - When a parameter is marked as automable, you must ensure no non-realtime operations are performed. + When a parameter is marked as automatable, you must ensure no non-realtime operations are performed. @note This function will only be called for parameter inputs. */ void setParameterValue(uint32_t, float) override diff --git a/examples/Info/Makefile b/examples/Info/Makefile @@ -15,7 +15,7 @@ NAME = d_info FILES_DSP = \ InfoExamplePlugin.cpp -FILES_UI = \ +FILES_UI = \ InfoExampleUI.cpp # -------------------------------------------------------------- @@ -28,12 +28,11 @@ include ../../Makefile.plugins.mk ifeq ($(HAVE_OPENGL),true) TARGETS += jack -TARGETS += lv2_sep -else -TARGETS += lv2_dsp endif # HAVE_OPENGL -TARGETS += vst +TARGETS += lv2_sep +TARGETS += vst2 +TARGETS += vst3 all: $(TARGETS) diff --git a/examples/Info/ResizeHandle.hpp b/examples/Info/ResizeHandle.hpp @@ -1,6 +1,6 @@ /* * Resize handle for DPF - * Copyright (C) 2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -19,6 +19,10 @@ #include "TopLevelWidget.hpp" #include "Color.hpp" +#if defined(DGL_OPENGL) && !defined(DGL_USE_OPENGL3) +#include "OpenGL-include.hpp" +#endif + START_NAMESPACE_DGL /** Resize handle for DPF windows, will sit on bottom-right. */ @@ -29,7 +33,8 @@ public: explicit ResizeHandle(Window& window) : TopLevelWidget(window), handleSize(16), - resizing(false) + hasCursor(false), + isResizing(false) { resetArea(); } @@ -38,12 +43,14 @@ public: explicit ResizeHandle(TopLevelWidget* const tlw) : TopLevelWidget(tlw->getWindow()), handleSize(16), - resizing(false) + hasCursor(false), + isResizing(false) { resetArea(); } - /** Set the handle size, minimum 16. */ + /** Set the handle size, minimum 16. + * Scale factor is automatically applied on top of this size as needed */ void setHandleSize(const uint size) { handleSize = std::max(16u, size); @@ -53,9 +60,15 @@ public: protected: void onDisplay() override { + // TODO implement gl3 stuff in DPF +#ifndef DGL_USE_OPENGL3 const GraphicsContext& context(getGraphicsContext()); const double lineWidth = 1.0 * getScaleFactor(); + #if defined(DGL_OPENGL) && !defined(DGL_USE_OPENGL3) + glMatrixMode(GL_MODELVIEW); + #endif + // draw white lines, 1px wide Color(1.0f, 1.0f, 1.0f).setFor(context); l1.draw(context, lineWidth); @@ -71,6 +84,7 @@ protected: l1b.draw(context, lineWidth); l2b.draw(context, lineWidth); l3b.draw(context, lineWidth); +#endif } bool onMouse(const MouseEvent& ev) override @@ -80,15 +94,16 @@ protected: if (ev.press && area.contains(ev.pos)) { - resizing = true; + isResizing = true; resizingSize = Size<double>(getWidth(), getHeight()); lastResizePoint = ev.pos; return true; } - if (resizing && ! ev.press) + if (isResizing && ! ev.press) { - resizing = false; + isResizing = false; + recheckCursor(ev.pos); return true; } @@ -97,8 +112,11 @@ protected: bool onMotion(const MotionEvent& ev) override { - if (! resizing) + if (! isResizing) + { + recheckCursor(ev.pos); return false; + } const Size<double> offset(ev.pos.getX() - lastResizePoint.getX(), ev.pos.getY() - lastResizePoint.getY()); @@ -106,9 +124,11 @@ protected: resizingSize += offset; lastResizePoint = ev.pos; - // TODO min width, min height - const uint minWidth = 16; - const uint minHeight = 16; + // TODO keepAspectRatio + bool keepAspectRatio; + const Size<uint> minSize(getWindow().getGeometryConstraints(keepAspectRatio)); + const uint minWidth = minSize.getWidth(); + const uint minHeight = minSize.getHeight(); if (resizingSize.getWidth() < minWidth) resizingSize.setWidth(minWidth); @@ -135,10 +155,21 @@ private: uint handleSize; // event handling state - bool resizing; + bool hasCursor, isResizing; Point<double> lastResizePoint; Size<double> resizingSize; + void recheckCursor(const Point<double>& pos) + { + const bool shouldHaveCursor = area.contains(pos); + + if (shouldHaveCursor == hasCursor) + return; + + hasCursor = shouldHaveCursor; + setCursor(shouldHaveCursor ? kMouseCursorDiagonal : kMouseCursorArrow); + } + void resetArea() { const double scaleFactor = getScaleFactor(); diff --git a/examples/Latency/CMakeLists.txt b/examples/Latency/CMakeLists.txt @@ -2,7 +2,7 @@ # ------------------------------ # dpf_add_plugin(d_latency - TARGETS ladspa lv2 vst2 + TARGETS ladspa lv2 vst2 vst3 FILES_DSP LatencyExamplePlugin.cpp) diff --git a/examples/Latency/LatencyExamplePlugin.cpp b/examples/Latency/LatencyExamplePlugin.cpp @@ -109,6 +109,19 @@ protected: * Init */ /** + Initialize the audio port @a index.@n + This function will be called once, shortly after the plugin is created. + */ + void initAudioPort(bool input, uint32_t index, AudioPort& port) override + { + // treat meter audio ports as stereo + port.groupId = kPortGroupMono; + + // everything else is as default + Plugin::initAudioPort(input, index, port); + } + + /** Initialize the parameter @a index. This function will be called once, shortly after the plugin is created. */ @@ -117,7 +130,7 @@ protected: if (index != 0) return; - parameter.hints = kParameterIsAutomable; + parameter.hints = kParameterIsAutomatable; parameter.name = "Latency"; parameter.symbol = "latency"; parameter.unit = "s"; @@ -144,7 +157,7 @@ protected: /** Change a parameter value. The host may call this function from any context, including realtime processing. - When a parameter is marked as automable, you must ensure no non-realtime operations are performed. + When a parameter is marked as automatable, you must ensure no non-realtime operations are performed. @note This function will only be called for parameter inputs. */ void setParameterValue(uint32_t index, float value) override diff --git a/examples/Latency/Makefile b/examples/Latency/Makefile @@ -24,8 +24,9 @@ include ../../Makefile.plugins.mk # Enable all possible plugin types TARGETS += ladspa -TARGETS += lv2_dsp -TARGETS += vst +TARGETS += lv2_sep +TARGETS += vst2 +TARGETS += vst3 all: $(TARGETS) diff --git a/examples/Meters/CMakeLists.txt b/examples/Meters/CMakeLists.txt @@ -2,7 +2,7 @@ # ------------------------------ # dpf_add_plugin(d_meters - TARGETS jack dssi lv2 vst2 + TARGETS jack dssi lv2 vst2 vst3 FILES_DSP ExamplePluginMeters.cpp FILES_UI diff --git a/examples/Meters/DistrhoPluginInfo.h b/examples/Meters/DistrhoPluginInfo.h @@ -26,6 +26,7 @@ #define DISTRHO_PLUGIN_NUM_INPUTS 2 #define DISTRHO_PLUGIN_NUM_OUTPUTS 2 #define DISTRHO_PLUGIN_WANT_STATE 1 +#define DISTRHO_UI_FILE_BROWSER 0 #define DISTRHO_UI_USER_RESIZABLE 1 #define DISTRHO_UI_USE_NANOVG 1 diff --git a/examples/Meters/ExamplePluginMeters.cpp b/examples/Meters/ExamplePluginMeters.cpp @@ -102,6 +102,19 @@ protected: * Init */ /** + Initialize the audio port @a index.@n + This function will be called once, shortly after the plugin is created. + */ + void initAudioPort(bool input, uint32_t index, AudioPort& port) override + { + // treat meter audio ports as stereo + port.groupId = kPortGroupStereo; + + // everything else is as default + Plugin::initAudioPort(input, index, port); + } + + /** Initialize the parameter @a index. This function will be called once, shortly after the plugin is created. */ @@ -120,7 +133,7 @@ protected: switch (index) { case 0: - parameter.hints = kParameterIsAutomable|kParameterIsInteger; + parameter.hints = kParameterIsAutomatable|kParameterIsInteger; parameter.name = "color"; parameter.symbol = "color"; parameter.enumValues.count = 2; @@ -136,12 +149,12 @@ protected: } break; case 1: - parameter.hints = kParameterIsAutomable|kParameterIsOutput; + parameter.hints = kParameterIsAutomatable|kParameterIsOutput; parameter.name = "out-left"; parameter.symbol = "out_left"; break; case 2: - parameter.hints = kParameterIsAutomable|kParameterIsOutput; + parameter.hints = kParameterIsAutomatable|kParameterIsOutput; parameter.name = "out-right"; parameter.symbol = "out_right"; break; diff --git a/examples/Meters/Makefile b/examples/Meters/Makefile @@ -37,7 +37,8 @@ endif # HAVE_LIBLO endif # MACOS_OR_WINDOWS TARGETS += lv2_sep -TARGETS += vst +TARGETS += vst2 +TARGETS += vst3 endif # HAVE_OPENGL diff --git a/examples/Metronome/ExamplePluginMetronome.cpp b/examples/Metronome/ExamplePluginMetronome.cpp @@ -72,7 +72,9 @@ public: : Plugin(4, 0, 0), // 4 parameters, 0 programs, 0 states sampleRate(getSampleRate()), counter(0), + wasPlaying(false), phase(0.0f), + envelope(1.0f), decay(0.0f), gain(0.5f), semitone(72), @@ -149,12 +151,25 @@ protected: * Init */ /** + Initialize the audio port @a index.@n + This function will be called once, shortly after the plugin is created. + */ + void initAudioPort(bool input, uint32_t index, AudioPort& port) override + { + // treat meter audio ports as stereo + port.groupId = kPortGroupMono; + + // everything else is as default + Plugin::initAudioPort(input, index, port); + } + + /** Initialize the parameter @a index. This function will be called once, shortly after the plugin is created. */ void initParameter(uint32_t index, Parameter& parameter) override { - parameter.hints = kParameterIsAutomable; + parameter.hints = kParameterIsAutomatable; switch (index) { diff --git a/examples/Metronome/Makefile b/examples/Metronome/Makefile @@ -25,7 +25,8 @@ include ../../Makefile.plugins.mk TARGETS += jack TARGETS += lv2_dsp -TARGETS += vst +TARGETS += vst2 +TARGETS += vst3 all: $(TARGETS) diff --git a/examples/MidiThrough/CMakeLists.txt b/examples/MidiThrough/CMakeLists.txt @@ -2,7 +2,7 @@ # ------------------------------ # dpf_add_plugin(d_midiThrough - TARGETS jack lv2 vst2 + TARGETS jack lv2 vst2 vst3 FILES_DSP MidiThroughExamplePlugin.cpp) diff --git a/examples/MidiThrough/Makefile b/examples/MidiThrough/Makefile @@ -25,7 +25,8 @@ include ../../Makefile.plugins.mk TARGETS += jack TARGETS += lv2_dsp -TARGETS += vst +TARGETS += vst2 +TARGETS += vst3 all: $(TARGETS) diff --git a/examples/Parameters/CMakeLists.txt b/examples/Parameters/CMakeLists.txt @@ -2,7 +2,7 @@ # ------------------------------ # dpf_add_plugin(d_parameters - TARGETS jack ladspa dssi lv2 vst2 + TARGETS jack ladspa dssi lv2 vst2 vst3 FILES_DSP ExamplePluginParameters.cpp FILES_UI diff --git a/examples/Parameters/DistrhoPluginInfo.h b/examples/Parameters/DistrhoPluginInfo.h @@ -26,6 +26,7 @@ #define DISTRHO_PLUGIN_NUM_INPUTS 2 #define DISTRHO_PLUGIN_NUM_OUTPUTS 2 #define DISTRHO_PLUGIN_WANT_PROGRAMS 1 +#define DISTRHO_UI_FILE_BROWSER 0 #define DISTRHO_UI_USER_RESIZABLE 1 #endif // DISTRHO_PLUGIN_INFO_H_INCLUDED diff --git a/examples/Parameters/ExamplePluginParameters.cpp b/examples/Parameters/ExamplePluginParameters.cpp @@ -111,6 +111,19 @@ The plugin will be treated as an effect, but it will not change the host audio." }; /** + Initialize the audio port @a index.@n + This function will be called once, shortly after the plugin is created. + */ + void initAudioPort(bool input, uint32_t index, AudioPort& port) override + { + // treat meter audio ports as stereo + port.groupId = kPortGroupStereo; + + // everything else is as default + Plugin::initAudioPort(input, index, port); + } + + /** Initialize the parameter @a index. This function will be called once, shortly after the plugin is created. */ @@ -122,10 +135,10 @@ The plugin will be treated as an effect, but it will not change the host audio." */ /** - Changing parameters does not cause any realtime-unsafe operations, so we can mark them as automable. + Changing parameters does not cause any realtime-unsafe operations, so we can mark them as automatable. Also set as boolean because they work as on/off switches. */ - parameter.hints = kParameterIsAutomable|kParameterIsBoolean; + parameter.hints = kParameterIsAutomatable|kParameterIsBoolean; /** Minimum 0 (off), maximum 1 (on). diff --git a/examples/SendNote/DistrhoPluginInfo.h b/examples/SendNote/DistrhoPluginInfo.h @@ -28,5 +28,6 @@ #define DISTRHO_PLUGIN_NUM_OUTPUTS 1 #define DISTRHO_PLUGIN_WANT_MIDI_INPUT 1 #define DISTRHO_PLUGIN_WANT_MIDI_OUTPUT 0 +#define DISTRHO_UI_FILE_BROWSER 0 #endif // DISTRHO_PLUGIN_INFO_H_INCLUDED diff --git a/examples/SendNote/Makefile b/examples/SendNote/Makefile @@ -29,12 +29,11 @@ include ../../Makefile.plugins.mk ifeq ($(HAVE_OPENGL),true) TARGETS += jack -TARGETS += lv2_sep -else -TARGETS += lv2_dsp endif -TARGETS += vst +TARGETS += lv2_sep +TARGETS += vst2 +TARGETS += vst3 all: $(TARGETS) diff --git a/examples/SendNote/SendNoteExamplePlugin.cpp b/examples/SendNote/SendNoteExamplePlugin.cpp @@ -100,11 +100,20 @@ protected: } /* -------------------------------------------------------------------------------------------------------- - * Init and Internal data, unused in this plugin */ + * Init */ - void initParameter(uint32_t, Parameter&) override {} - float getParameterValue(uint32_t) const override { return 0.0f;} - void setParameterValue(uint32_t, float) override {} + /** + Initialize the audio port @a index.@n + This function will be called once, shortly after the plugin is created. + */ + void initAudioPort(bool input, uint32_t index, AudioPort& port) override + { + // treat meter audio ports as stereo + port.groupId = kPortGroupMono; + + // everything else is as default + Plugin::initAudioPort(input, index, port); + } /* -------------------------------------------------------------------------------------------------------- * Audio/MIDI Processing */ diff --git a/examples/States/CMakeLists.txt b/examples/States/CMakeLists.txt @@ -2,7 +2,7 @@ # ------------------------------ # dpf_add_plugin(d_states - TARGETS jack lv2 vst2 + TARGETS jack lv2 vst2 vst3 FILES_DSP ExamplePluginStates.cpp FILES_UI diff --git a/examples/States/DistrhoPluginInfo.h b/examples/States/DistrhoPluginInfo.h @@ -27,6 +27,10 @@ #define DISTRHO_PLUGIN_NUM_OUTPUTS 2 #define DISTRHO_PLUGIN_WANT_PROGRAMS 1 #define DISTRHO_PLUGIN_WANT_STATE 1 +#define DISTRHO_UI_FILE_BROWSER 0 #define DISTRHO_UI_USER_RESIZABLE 1 +// states and presets together require this in order to function +#define DISTRHO_PLUGIN_WANT_FULL_STATE 1 + #endif // DISTRHO_PLUGIN_INFO_H_INCLUDED diff --git a/examples/States/ExamplePluginStates.cpp b/examples/States/ExamplePluginStates.cpp @@ -105,9 +105,17 @@ The plugin will be treated as an effect, but it will not change the host audio." * Init */ /** - This plugin has no parameters.. + Initialize the audio port @a index.@n + This function will be called once, shortly after the plugin is created. */ - void initParameter(uint32_t, Parameter&) override {} + void initAudioPort(bool input, uint32_t index, AudioPort& port) override + { + // treat meter audio ports as stereo + port.groupId = kPortGroupStereo; + + // everything else is as default + Plugin::initAudioPort(input, index, port); + } /** Set the name of the program @a index. @@ -127,55 +135,60 @@ The plugin will be treated as an effect, but it will not change the host audio." } /** - Set the state key and default value of @a index. - This function will be called once, shortly after the plugin is created. + Initialize the state @a index.@n + This function will be called once, shortly after the plugin is created.@n + Must be implemented by your plugin class only if DISTRHO_PLUGIN_WANT_STATE is enabled. */ - void initState(uint32_t index, String& stateKey, String& defaultStateValue) override + void initState(uint32_t index, State& state) override { switch (index) { case 0: - stateKey = "top-left"; + state.key = "top-left"; + state.label = "Top Left"; break; case 1: - stateKey = "top-center"; + state.key = "top-center"; + state.label = "Top Center"; break; case 2: - stateKey = "top-right"; + state.key = "top-right"; + state.label = "Top Right"; break; case 3: - stateKey = "middle-left"; + state.key = "middle-left"; + state.label = "Middle Left"; break; case 4: - stateKey = "middle-center"; + state.key = "middle-center"; + state.label = "Middle Center"; break; case 5: - stateKey = "middle-right"; + state.key = "middle-right"; + state.label = "Middle Right"; break; case 6: - stateKey = "bottom-left"; + state.key = "bottom-left"; + state.label = "Bottom Left"; break; case 7: - stateKey = "bottom-center"; + state.key = "bottom-center"; + state.label = "Bottom Center"; break; case 8: - stateKey = "bottom-right"; + state.key = "bottom-right"; + state.label = "Bottom Right"; break; } - defaultStateValue = "false"; + state.hints = kStateIsHostWritable; + state.defaultValue = "false"; } /* -------------------------------------------------------------------------------------------------------- * Internal data */ /** - This plugin has no parameters.. - */ - void setParameterValue(uint32_t, float) override {} - float getParameterValue(uint32_t) const override { return 0.0f; } - - /** Load a program. The host may call this function from any context, including realtime processing. */ @@ -208,7 +221,6 @@ The plugin will be treated as an effect, but it will not change the host audio." } } -#if DISTRHO_PLUGIN_WANT_FULL_STATE /* FIXME */ /** Get the value of an internal state. The host may call this function from any non-realtime context. @@ -240,7 +252,6 @@ The plugin will be treated as an effect, but it will not change the host audio." return sFalse; } -#endif /** Change an internal state. diff --git a/examples/States/Makefile b/examples/States/Makefile @@ -26,21 +26,10 @@ include ../../Makefile.plugins.mk # -------------------------------------------------------------- # Enable all possible plugin types -ifeq ($(HAVE_OPENGL),true) TARGETS += jack -endif # HAVE_OPENGL - -ifneq ($(MACOS_OR_WINDOWS),true) -TARGETS += dssi -endif # MACOS_OR_WINDOWS - -ifeq ($(HAVE_OPENGL),true) TARGETS += lv2_sep -else -TARGETS += lv2_dsp -endif - -TARGETS += vst +TARGETS += vst2 +TARGETS += vst3 all: $(TARGETS) diff --git a/tests/Demo.cpp b/tests/Demo.cpp @@ -1,6 +1,6 @@ /* * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com> * * Permission to use, copy, modify, and/or distribute this software for any purpose with * or without fee is hereby granted, provided that the above copyright notice and this @@ -23,6 +23,7 @@ #ifdef DGL_OPENGL #include "widgets/ExampleTextWidget.hpp" #endif +#include "widgets/ResizeHandle.hpp" #include "demo_res/DemoArtwork.cpp" #include "images_res/CatPics.cpp" @@ -103,12 +104,13 @@ protected: void onDisplay() override { const GraphicsContext& context(getGraphicsContext()); + const double scaleFactor = getWindow().getScaleFactor(); const int iconSize = bgIcon.getWidth(); Color(0.027f, 0.027f, 0.027f).setFor(context); Rectangle<uint>(0, 0, getSize()).draw(context); - bgIcon.setY(curPage*iconSize + curPage*3); + bgIcon.setY(curPage * iconSize + curPage * 3 * scaleFactor); Color(0.129f, 0.129f, 0.129f).setFor(context); bgIcon.draw(context); @@ -118,7 +120,8 @@ protected: if (curHover != curPage && curHover != -1) { - Rectangle<int> rHover(1, curHover*iconSize + curHover*3, iconSize-2, iconSize-2); + Rectangle<int> rHover(1 * scaleFactor, curHover * iconSize + curHover * 3 * scaleFactor, + iconSize - 2 * scaleFactor, iconSize - 2 * scaleFactor); Color(0.071f, 0.071f, 0.071f).setFor(context); rHover.draw(context); @@ -146,13 +149,13 @@ protected: // draw some text nvg.beginFrame(this); - nvg.fontSize(23.0f); + nvg.fontSize(23.0f * scaleFactor); nvg.textAlign(NanoVG::ALIGN_LEFT|NanoVG::ALIGN_TOP); //nvg.textLineHeight(20.0f); nvg.fillColor(220,220,220,220); - nvg.textBox(10, 420, iconSize, "Haha,", nullptr); - nvg.textBox(15, 440, iconSize, "Look!", nullptr); + nvg.textBox(10 * scaleFactor, 420 * scaleFactor, iconSize, "Haha,", nullptr); + nvg.textBox(15 * scaleFactor, 440 * scaleFactor, iconSize, "Look!", nullptr); nvg.endFrame(); #endif @@ -226,9 +229,10 @@ protected: { const uint width = ev.size.getWidth(); const uint height = ev.size.getHeight(); + const double scaleFactor = getWindow().getScaleFactor(); - bgIcon.setWidth(width-4); - bgIcon.setHeight(width-4); + bgIcon.setWidth(width - 4 * scaleFactor); + bgIcon.setHeight(width - 4 * scaleFactor); lineSep.setStartPos(width, 0); lineSep.setEndPos(width, height); @@ -248,155 +252,6 @@ private: }; // -------------------------------------------------------------------------------------------------------------------- -// Resize handle widget - -class ResizeHandle : public TopLevelWidget -{ - Rectangle<uint> area; - Line<double> l1, l2, l3; - uint baseSize; - - // event handling state - bool resizing; - Point<double> lastResizePoint; - Size<double> resizingSize; - -public: - explicit ResizeHandle(TopLevelWidget* const tlw) - : TopLevelWidget(tlw->getWindow()), - baseSize(16), - resizing(false) - { - resetArea(); - } - - explicit ResizeHandle(Window& window) - : TopLevelWidget(window), - baseSize(16), - resizing(false) - { - resetArea(); - } - - void setBaseSize(const uint size) - { - baseSize = std::max(16u, size); - resetArea(); - } - -protected: - void onDisplay() override - { - const GraphicsContext& context(getGraphicsContext()); - const double offset = getScaleFactor(); - - // draw white lines, 1px wide - Color(1.0f, 1.0f, 1.0f).setFor(context); - l1.draw(context, 1); - l2.draw(context, 1); - l3.draw(context, 1); - - // draw black lines, offset by 1px and 2px wide - Color(0.0f, 0.0f, 0.0f).setFor(context); - Line<double> l1b(l1), l2b(l2), l3b(l3); - l1b.moveBy(offset, offset); - l2b.moveBy(offset, offset); - l3b.moveBy(offset, offset); - l1b.draw(context, 1); - l2b.draw(context, 1); - l3b.draw(context, 1); - } - - bool onMouse(const MouseEvent& ev) override - { - if (ev.button != 1) - return false; - - if (ev.press && area.contains(ev.pos)) - { - resizing = true; - resizingSize = Size<double>(getWidth(), getHeight()); - lastResizePoint = ev.pos; - return true; - } - - if (resizing && ! ev.press) - { - resizing = false; - return true; - } - - return false; - } - - bool onMotion(const MotionEvent& ev) override - { - if (! resizing) - return false; - - const Size<double> offset(ev.pos.getX() - lastResizePoint.getX(), - ev.pos.getY() - lastResizePoint.getY()); - - resizingSize += offset; - lastResizePoint = ev.pos; - - // TODO min width, min height - const uint minWidth = 16; - const uint minHeight = 16; - - if (resizingSize.getWidth() < minWidth) - resizingSize.setWidth(minWidth); - if (resizingSize.getHeight() < minHeight) - resizingSize.setHeight(minHeight); - - setSize(resizingSize.getWidth(), resizingSize.getHeight()); - return true; - } - - void onResize(const ResizeEvent& ev) override - { - TopLevelWidget::onResize(ev); - resetArea(); - } - -private: - void resetArea() - { - const double scaleFactor = getScaleFactor(); - const uint margin = 0.0 * scaleFactor; - const uint size = baseSize * scaleFactor; - - area = Rectangle<uint>(getWidth() - size - margin, - getHeight() - size - margin, - size, size); - - recreateLines(area.getX(), area.getY(), size); - } - - void recreateLines(const uint x, const uint y, const uint size) - { - uint linesize = size; - uint offset = 0; - - // 1st line, full diagonal size - l1.setStartPos(x + size, y); - l1.setEndPos(x, y + size); - - // 2nd line, bit more to the right and down, cropped - offset += size / 3; - linesize -= size / 3; - l2.setStartPos(x + linesize + offset, y + offset); - l2.setEndPos(x + offset, y + linesize + offset); - - // 3rd line, even more right and down - offset += size / 3; - linesize -= size / 3; - l3.setStartPos(x + linesize + offset, y + offset); - l3.setEndPos(x + offset, y + linesize + offset); - } -}; - -// -------------------------------------------------------------------------------------------------------------------- // Main Demo Window, having a left-side tab-like widget and main area for current widget class DemoWindow : public StandaloneWindow, @@ -420,30 +275,31 @@ public: curWidget(nullptr) { const ScopedGraphicsContext sgc(*this); + const double scaleFactor = getScaleFactor(); wColor = new ExampleColorSubWidget(this); wColor->hide(); - wColor->setAbsoluteX(kSidebarWidth); + wColor->setAbsoluteX(kSidebarWidth * scaleFactor); wImages = new ExampleImagesSubWidget(this); wImages->hide(); - wImages->setAbsoluteX(kSidebarWidth); + wImages->setAbsoluteX(kSidebarWidth * scaleFactor); wRects = new ExampleRectanglesSubWidget(this); wRects->hide(); - wRects->setAbsoluteX(kSidebarWidth); + wRects->setAbsoluteX(kSidebarWidth * scaleFactor); wShapes = new ExampleShapesSubWidget(this); wShapes->hide(); - wShapes->setAbsoluteX(kSidebarWidth); + wShapes->setAbsoluteX(kSidebarWidth * scaleFactor); #ifdef DGL_OPENGL wText = new ExampleTextSubWidget(this), wText->hide(); - wText->setAbsoluteX(kSidebarWidth); + wText->setAbsoluteX(kSidebarWidth * scaleFactor); #endif wLeft = new LeftSideWidget(this, this), - wLeft->setAbsolutePos(2, 2); + wLeft->setAbsolutePos(2 * scaleFactor, 2 * scaleFactor); resizer = new ResizeHandle(this); @@ -493,10 +349,12 @@ protected: { StandaloneWindow::onReshape(width, height); - if (width < kSidebarWidth) + const double scaleFactor = getScaleFactor(); + + if (width < kSidebarWidth * scaleFactor) return; - Size<uint> size(width-kSidebarWidth, height); + const Size<uint> size(width - kSidebarWidth * scaleFactor, height); wColor->setSize(size); wImages->setSize(size); wRects->setSize(size); @@ -504,7 +362,7 @@ protected: #ifdef DGL_OPENGL wText->setSize(size); #endif - wLeft->setSize(kSidebarWidth-4, height-4); + wLeft->setSize((kSidebarWidth - 4) * scaleFactor, (height - 4) * scaleFactor); } private: @@ -528,8 +386,10 @@ template <class ExampleWidgetStandaloneWindow> void createAndShowExampleWidgetStandaloneWindow(Application& app) { ExampleWidgetStandaloneWindow swin(app); + const double scaleFactor = swin.getScaleFactor(); + swin.setGeometryConstraints(128 * scaleFactor, 128 * scaleFactor); swin.setResizable(true); - swin.setSize(600, 500); + swin.setSize(600 * scaleFactor, 500 * scaleFactor); swin.setTitle(ExampleWidgetStandaloneWindow::kExampleWidgetName); swin.show(); app.exec(); diff --git a/tests/FileBrowserDialog.cpp b/tests/FileBrowserDialog.cpp @@ -38,21 +38,26 @@ public: loadSharedResources(); #endif setResizable(true); - setSize(500, 200); - setGeometryConstraints(500, 200, true); setTitle("FileBrowserDialog"); + + const double scaleFactor = getScaleFactor(); + setGeometryConstraints(500 * scaleFactor, 200 * scaleFactor, true); + setSize(500 * scaleFactor, 200 * scaleFactor); + done(); } protected: void onNanoDisplay() override { + const double scaleFactor = getScaleFactor(); + // Selected file beginPath(); - fontSize(14); + fontSize(14 * scaleFactor); textAlign(ALIGN_LEFT | ALIGN_MIDDLE); fillColor(255, 255, 255, 255); - text(20, getHeight()/2, selectedFile, NULL); + text(20 * scaleFactor, getHeight()/2, selectedFile, NULL); closePath(); // Button background @@ -66,7 +71,7 @@ protected: // Button label beginPath(); - fontSize(14); + fontSize(14 * scaleFactor); Rectangle<float> buttonTextBounds; textBounds(0, 0, "Press me", NULL, buttonTextBounds); textAlign(ALIGN_CENTER | ALIGN_MIDDLE); @@ -120,6 +125,7 @@ protected: repaint(); FileBrowserOptions opts; + // opts.saving = true; opts.title = "Look at me"; if (! openFileBrowser(opts)) { @@ -138,8 +144,12 @@ protected: { const uint width = ev.size.getWidth(); const uint height = ev.size.getHeight(); + const double scaleFactor = getScaleFactor(); - buttonBounds = Rectangle<uint>(width - 120, height/2 - 20, 100, 40); + buttonBounds = Rectangle<uint>(width - 120 * scaleFactor, + height/2 - 20 * scaleFactor, + 100 * scaleFactor, + 40 * scaleFactor); } void onFocus(const bool focus, CrossingMode) override diff --git a/tests/Makefile b/tests/Makefile @@ -22,16 +22,25 @@ endif # --------------------------------------------------------------------------------------------------------------------- MANUAL_TESTS = -UNIT_TESTS = Application Color Point +UNIT_TESTS = Color Point ifeq ($(HAVE_CAIRO),true) MANUAL_TESTS += Demo.cairo -UNIT_TESTS += Window.cairo endif + ifeq ($(HAVE_OPENGL),true) MANUAL_TESTS += Demo.opengl MANUAL_TESTS += FileBrowserDialog +MANUAL_TESTS += NanoImage MANUAL_TESTS += NanoSubWidgets +endif + +ifneq ($(WASM),true) +UNIT_TESTS += Application +ifeq ($(HAVE_CAIRO),true) +UNIT_TESTS += Window.cairo +endif +ifeq ($(HAVE_OPENGL),true) UNIT_TESTS += Window.opengl endif ifeq ($(HAVE_STUB),true) @@ -40,6 +49,7 @@ endif ifeq ($(HAVE_VULKAN),true) UNIT_TESTS += Window.vulkan endif +endif MANUAL_TARGETS = $(MANUAL_TESTS:%=../build/tests/%$(APP_EXT)) UNIT_TARGET = $(UNIT_TESTS:%=../build/tests/%$(APP_EXT)) @@ -53,6 +63,13 @@ all: $(MANUAL_TARGETS) $(UNIT_TARGET) # --------------------------------------------------------------------------------------------------------------------- +Demo.opengl: ../build/tests/Demo.opengl$(APP_EXT) +FileBrowserDialog: ../build/tests/FileBrowserDialog$(APP_EXT) +NanoImage: ../build/tests/NanoImage$(APP_EXT) +NanoSubWidgets: ../build/tests/NanoSubWidgets$(APP_EXT) + +# --------------------------------------------------------------------------------------------------------------------- + define RUN_TEST ${1} @@ -140,15 +157,21 @@ clean: $(SILENT)$(CXX) $^ $(LINK_FLAGS) $(DGL_SYSTEM_LIBS) $(VULKAN_LIBS) -o $@ ../build/tests/FileBrowserDialog$(APP_EXT): ../build/tests/FileBrowserDialog.cpp.o ../build/libdgl-opengl.a - @echo "Linking Demo (OpenGL)" + @echo "Linking FileBrowserDialog (OpenGL)" + $(SILENT)$(CXX) $^ $(LINK_FLAGS) $(DGL_SYSTEM_LIBS) $(OPENGL_LIBS) -o $@ + +../build/tests/NanoImage$(APP_EXT): ../build/tests/NanoImage.cpp.o ../build/libdgl-opengl.a + @echo "Linking NanoImage (OpenGL)" $(SILENT)$(CXX) $^ $(LINK_FLAGS) $(DGL_SYSTEM_LIBS) $(OPENGL_LIBS) -o $@ ../build/tests/NanoSubWidgets$(APP_EXT): ../build/tests/NanoSubWidgets.cpp.o ../build/libdgl-opengl.a - @echo "Linking Demo (OpenGL)" + @echo "Linking NanoSubWidgets (OpenGL)" $(SILENT)$(CXX) $^ $(LINK_FLAGS) $(DGL_SYSTEM_LIBS) $(OPENGL_LIBS) -o $@ # --------------------------------------------------------------------------------------------------------------------- +.PHONY: Demo.opengl FileBrowserDialog NanoImage NanoSubWidgets + -include $(ALL_OBJS:%.o=%.d) # --------------------------------------------------------------------------------------------------------------------- diff --git a/tests/NanoImage.cpp b/tests/NanoImage.cpp @@ -0,0 +1,227 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com> + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "tests.hpp" + +#include "dgl/NanoVG.hpp" + +START_NAMESPACE_DGL + +// -------------------------------------------------------------------------------------------------------------------- +// Images + +#include "images_res/CatPics.cpp" + +// -------------------------------------------------------------------------------------------------------------------- + +class NanoImageExample : public NanoStandaloneWindow, + public IdleCallback +{ + static const int kImg1y = 0; + static const int kImg2y = 500/2-CatPics::cat2Height/2; + static const int kImg3x = 400/3-CatPics::cat3Width/3; + + static const int kImg1max = 500-CatPics::cat1Width; + static const int kImg2max = 500-CatPics::cat2Width; + static const int kImg3max = 400-CatPics::cat3Height; + + int imgTop1st, imgTop2nd, imgTop3rd; + int img1x, img2x, img3y; + bool img1rev, img2rev, img3rev; + NanoImage img1, img2, img3; + +public: + NanoImageExample(Application& app) + : NanoStandaloneWindow(app), + imgTop1st(1), + imgTop2nd(2), + imgTop3rd(3), + img1x(0), + img2x(kImg2max), + img3y(kImg3max), + img1rev(false), + img2rev(true), + img3rev(true), + img1(createImageFromRawMemory(CatPics::cat1Width, CatPics::cat1Height, (uchar*)CatPics::cat1Data, 0, kImageFormatBGR)), + img2(createImageFromRawMemory(CatPics::cat2Width, CatPics::cat2Height, (uchar*)CatPics::cat2Data, 0, kImageFormatBGR)), + img3(createImageFromRawMemory(CatPics::cat3Width, CatPics::cat3Height, (uchar*)CatPics::cat3Data, 0, kImageFormatBGR)) + { + DISTRHO_SAFE_ASSERT(img1.isValid()); + DISTRHO_SAFE_ASSERT(img2.isValid()); + DISTRHO_SAFE_ASSERT(img3.isValid()); + + DISTRHO_SAFE_ASSERT_UINT2(img1.getSize().getWidth() == CatPics::cat1Width, + img1.getSize().getWidth(), CatPics::cat1Width); + + DISTRHO_SAFE_ASSERT_UINT2(img1.getSize().getHeight() == CatPics::cat1Height, + img1.getSize().getHeight(), CatPics::cat1Height); + + DISTRHO_SAFE_ASSERT_UINT2(img2.getSize().getWidth() == CatPics::cat2Width, + img2.getSize().getWidth(), CatPics::cat2Width); + + DISTRHO_SAFE_ASSERT_UINT2(img2.getSize().getHeight() == CatPics::cat2Height, + img2.getSize().getHeight(), CatPics::cat2Height); + + DISTRHO_SAFE_ASSERT_UINT2(img3.getSize().getWidth() == CatPics::cat3Width, + img3.getSize().getWidth(), CatPics::cat3Width); + + DISTRHO_SAFE_ASSERT_UINT2(img3.getSize().getHeight() == CatPics::cat3Height, + img3.getSize().getHeight(), CatPics::cat3Height); + + setResizable(true); + setSize(500, 500); + setGeometryConstraints(500, 500, false, true); + setTitle("NanoImage"); + done(); + + addIdleCallback(this); + } + +protected: + void onNanoDisplay() override + { + // bottom image + beginPath(); + fillPaint(setupImagePaint(imgTop3rd)); + fill(); + + // middle image + beginPath(); + fillPaint(setupImagePaint(imgTop2nd)); + fill(); + + // top image + beginPath(); + fillPaint(setupImagePaint(imgTop1st)); + fill(); + } + + void idleCallback() noexcept override + { + if (img1rev) + { + img1x -= 2; + if (img1x <= -50) + { + img1rev = false; + setNewTopImg(1); + } + } + else + { + img1x += 2; + if (img1x >= kImg1max+50) + { + img1rev = true; + setNewTopImg(1); + } + } + + if (img2rev) + { + img2x -= 1; + if (img2x <= -50) + { + img2rev = false; + setNewTopImg(2); + } + } + else + { + img2x += 4; + if (img2x >= kImg2max+50) + { + img2rev = true; + setNewTopImg(2); + } + } + + if (img3rev) + { + img3y -= 3; + if (img3y <= -50) + { + img3rev = false; + setNewTopImg(3); + } + } + else + { + img3y += 3; + if (img3y >= kImg3max+50) + { + img3rev = true; + setNewTopImg(3); + } + } + + repaint(); + } + +private: + Paint setupImagePaint(const int imgId) noexcept + { + switch (imgId) + { + case 1: + rect(img1x, kImg1y, CatPics::cat1Width, CatPics::cat1Height); + return imagePattern(img1x, kImg1y, CatPics::cat1Width, CatPics::cat1Height, 0, img1, 1.0f); + case 2: + rect(img2x, kImg2y, CatPics::cat2Width, CatPics::cat2Height); + return imagePattern(img2x, kImg2y, CatPics::cat2Width, CatPics::cat2Height, 0, img2, 1.0f); + case 3: + rect(kImg3x, img3y, CatPics::cat3Width, CatPics::cat3Height); + return imagePattern(kImg3x, img3y, CatPics::cat3Width, CatPics::cat3Height, 0, img3, 1.0f); + }; + + return Paint(); + } + + void setNewTopImg(const int imgId) noexcept + { + if (imgTop1st == imgId) + return; + + if (imgTop2nd == imgId) + { + imgTop2nd = imgTop1st; + imgTop1st = imgId; + return; + } + + imgTop3rd = imgTop2nd; + imgTop2nd = imgTop1st; + imgTop1st = imgId; + } +}; + +// -------------------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DGL + +int main() +{ + USE_NAMESPACE_DGL; + + Application app(true); + NanoImageExample win(app); + win.show(); + app.exec(); + + return 0; +} + +// -------------------------------------------------------------------------------------------------------------------- diff --git a/tests/widgets/ExampleTextWidget.hpp b/tests/widgets/ExampleTextWidget.hpp @@ -42,23 +42,28 @@ public: // StandaloneWindow explicit ExampleTextWidget(Application& app); + // helper + double getScaleFactor(); + protected: void onNanoDisplay() override { const int width = BaseWidget::getWidth(); const int height = BaseWidget::getHeight(); + const double scaleFactor = getScaleFactor(); - NanoVG::fontSize(40.0f); + NanoVG::fontSize(40.0f * scaleFactor); NanoVG::textAlign(NanoVG::Align(NanoVG::ALIGN_CENTER|NanoVG::ALIGN_MIDDLE)); - NanoVG::textLineHeight(20.0f); + NanoVG::textLineHeight(20.0f * scaleFactor); NanoVG::beginPath(); NanoVG::fillColor(220,220,220,255); - NanoVG::roundedRect(10, height/4+10, width-20, height/2-20, 3); + NanoVG::roundedRect(10 * scaleFactor, height/4 + 10 * scaleFactor, + width - 20 * scaleFactor, height/2 - 20 * scaleFactor, 3 * scaleFactor); NanoVG::fill(); NanoVG::fillColor(0,150,0,220); - NanoVG::textBox(10, height/2, width-20, "Hello World!", nullptr); + NanoVG::textBox(10 * scaleFactor, height/2, width - 20 * scaleFactor, "Hello World!", nullptr); } }; @@ -71,6 +76,12 @@ ExampleTextWidget<NanoSubWidget>::ExampleTextWidget(Widget* const parent) setSize(500, 300); } +template<> inline +double ExampleTextWidget<NanoSubWidget>::getScaleFactor() +{ + return getWindow().getScaleFactor(); +} + // TopLevelWidget template<> inline ExampleTextWidget<NanoTopLevelWidget>::ExampleTextWidget(Window& windowToMapTo) @@ -80,6 +91,12 @@ ExampleTextWidget<NanoTopLevelWidget>::ExampleTextWidget(Window& windowToMapTo) setSize(500, 300); } +template<> inline +double ExampleTextWidget<NanoTopLevelWidget>::getScaleFactor() +{ + return NanoTopLevelWidget::getScaleFactor(); +} + // StandaloneWindow template<> inline ExampleTextWidget<NanoStandaloneWindow>::ExampleTextWidget(Application& app) @@ -90,6 +107,12 @@ ExampleTextWidget<NanoStandaloneWindow>::ExampleTextWidget(Application& app) done(); } +template<> inline +double ExampleTextWidget<NanoStandaloneWindow>::getScaleFactor() +{ + return Window::getScaleFactor(); +} + template class ExampleTextWidget<NanoSubWidget>; template class ExampleTextWidget<NanoTopLevelWidget>; template class ExampleTextWidget<NanoStandaloneWindow>; diff --git a/tests/widgets/ResizeHandle.hpp b/tests/widgets/ResizeHandle.hpp @@ -0,0 +1,207 @@ +/* + * Resize handle for DPF + * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> + * + * Permission to use, copy, modify, and/or distribute this software for any purpose with + * or without fee is hereby granted, provided that the above copyright notice and this + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +#include "../../dgl/Color.hpp" +#include "../../dgl/TopLevelWidget.hpp" + +START_NAMESPACE_DGL + +/** Resize handle for DPF windows, will sit on bottom-right. */ +class ResizeHandle : public TopLevelWidget +{ +public: + /** Constructor for placing this handle on top of a window. */ + explicit ResizeHandle(Window& window) + : TopLevelWidget(window), + handleSize(16), + hasCursor(false), + isResizing(false) + { + resetArea(); + } + + /** Overloaded constructor, will fetch the window from an existing top-level widget. */ + explicit ResizeHandle(TopLevelWidget* const tlw) + : TopLevelWidget(tlw->getWindow()), + handleSize(16), + hasCursor(false), + isResizing(false) + { + resetArea(); + } + + /** Set the handle size, minimum 16. + * Scale factor is automatically applied on top of this size as needed */ + void setHandleSize(const uint size) + { + handleSize = std::max(16u, size); + resetArea(); + } + +protected: + void onDisplay() override + { + // TODO implement gl3 stuff in DPF +#ifndef DGL_USE_OPENGL3 + const GraphicsContext& context(getGraphicsContext()); + const double lineWidth = 1.0 * getScaleFactor(); + + #if defined(DGL_OPENGL) && !defined(DGL_USE_OPENGL3) +// glMatrixMode(GL_MODELVIEW); + #endif + + // draw white lines, 1px wide + Color(1.0f, 1.0f, 1.0f).setFor(context); + l1.draw(context, lineWidth); + l2.draw(context, lineWidth); + l3.draw(context, lineWidth); + + // draw black lines, offset by 1px and 1px wide + Color(0.0f, 0.0f, 0.0f).setFor(context); + Line<double> l1b(l1), l2b(l2), l3b(l3); + l1b.moveBy(lineWidth, lineWidth); + l2b.moveBy(lineWidth, lineWidth); + l3b.moveBy(lineWidth, lineWidth); + l1b.draw(context, lineWidth); + l2b.draw(context, lineWidth); + l3b.draw(context, lineWidth); +#endif + } + + bool onMouse(const MouseEvent& ev) override + { + if (ev.button != 1) + return false; + + if (ev.press && area.contains(ev.pos)) + { + isResizing = true; + resizingSize = Size<double>(getWidth(), getHeight()); + lastResizePoint = ev.pos; + return true; + } + + if (isResizing && ! ev.press) + { + isResizing = false; + recheckCursor(ev.pos); + return true; + } + + return false; + } + + bool onMotion(const MotionEvent& ev) override + { + if (! isResizing) + { + recheckCursor(ev.pos); + return false; + } + + const Size<double> offset(ev.pos.getX() - lastResizePoint.getX(), + ev.pos.getY() - lastResizePoint.getY()); + + resizingSize += offset; + lastResizePoint = ev.pos; + + // TODO keepAspectRatio + bool keepAspectRatio; + const Size<uint> minSize(getWindow().getGeometryConstraints(keepAspectRatio)); + const uint minWidth = minSize.getWidth(); + const uint minHeight = minSize.getHeight(); + + if (resizingSize.getWidth() < minWidth) + resizingSize.setWidth(minWidth); + if (resizingSize.getWidth() > 16384) + resizingSize.setWidth(16384); + if (resizingSize.getHeight() < minHeight) + resizingSize.setHeight(minHeight); + if (resizingSize.getHeight() > 16384) + resizingSize.setHeight(16384); + + setSize(resizingSize.getWidth(), resizingSize.getHeight()); + return true; + } + + void onResize(const ResizeEvent& ev) override + { + TopLevelWidget::onResize(ev); + resetArea(); + } + +private: + Rectangle<uint> area; + Line<double> l1, l2, l3; + uint handleSize; + + // event handling state + bool hasCursor, isResizing; + Point<double> lastResizePoint; + Size<double> resizingSize; + + void recheckCursor(const Point<double>& pos) + { + const bool shouldHaveCursor = area.contains(pos); + + if (shouldHaveCursor == hasCursor) + return; + + hasCursor = shouldHaveCursor; + setCursor(shouldHaveCursor ? kMouseCursorDiagonal : kMouseCursorArrow); + } + + void resetArea() + { + const double scaleFactor = getScaleFactor(); + const uint margin = 0.0 * scaleFactor; + const uint size = handleSize * scaleFactor; + + area = Rectangle<uint>(getWidth() - size - margin, + getHeight() - size - margin, + size, size); + + recreateLines(area.getX(), area.getY(), size); + } + + void recreateLines(const uint x, const uint y, const uint size) + { + uint linesize = size; + uint offset = 0; + + // 1st line, full diagonal size + l1.setStartPos(x + size, y); + l1.setEndPos(x, y + size); + + // 2nd line, bit more to the right and down, cropped + offset += size / 3; + linesize -= size / 3; + l2.setStartPos(x + linesize + offset, y + offset); + l2.setEndPos(x + offset, y + linesize + offset); + + // 3rd line, even more right and down + offset += size / 3; + linesize -= size / 3; + l3.setStartPos(x + linesize + offset, y + offset); + l3.setEndPos(x + offset, y + linesize + offset); + } + + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ResizeHandle) +}; + +END_NAMESPACE_DGL diff --git a/utils/generate-vst-bundles.sh b/utils/generate-vst-bundles.sh @@ -1,5 +1,8 @@ #!/bin/bash +echo "WARNING: generate-vst-bundles.sh script is no longer used" +exit 0 + set -e if [ -d bin ]; then diff --git a/utils/lv2-ttl-generator/GNUmakefile b/utils/lv2-ttl-generator/GNUmakefile @@ -9,19 +9,19 @@ ifeq ($(WINDOWS),true) build: ../lv2_ttl_generator.exe ../lv2_ttl_generator.exe: lv2_ttl_generator.c - $(CC) $< $(CFLAGS) -o $@ $(LDFLAGS) -static + $(CC) $< $(BUILD_C_FLAGS) -o $@ $(LINK_FLAGS) -static touch ../lv2_ttl_generator else # WINDOWS -ifneq ($(HAIKU),true) -LDFLAGS += -ldl +ifneq ($(HAIKU_OR_MACOS_OR_WINDOWS),true) +LINK_FLAGS += -ldl endif build: ../lv2_ttl_generator ../lv2_ttl_generator: lv2_ttl_generator.c - $(CC) $< $(CFLAGS) -o $@ $(LDFLAGS) + $(CC) $< $(BUILD_C_FLAGS) -o $@ $(LINK_FLAGS) endif # WINDOWS diff --git a/utils/lv2-ttl-generator/lv2_ttl_generator.c b/utils/lv2-ttl-generator/lv2_ttl_generator.c @@ -63,7 +63,14 @@ int main(int argc, char* argv[]) } #ifdef TTL_GENERATOR_WINDOWS +# if defined(__GNUC__) && (__GNUC__ >= 9) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wcast-function-type" +# endif const TTL_Generator_Function ttlFn = (TTL_Generator_Function)GetProcAddress(handle, "lv2_generate_ttl"); +# if defined(__GNUC__) && (__GNUC__ >= 9) +# pragma GCC diagnostic pop +# endif #else const TTL_Generator_Function ttlFn = (TTL_Generator_Function)dlsym(handle, "lv2_generate_ttl"); #endif diff --git a/utils/package-osx-bundles.sh b/utils/package-osx-bundles.sh @@ -14,10 +14,13 @@ SNAME="$(echo ${NAME} | tr -d ' ' | tr '/' '-')" rm -rf lv2 rm -rf vst2 +rm -rf vst3 -mkdir lv2 vst2 -mv *.lv2 lv2/ -mv *.vst vst2/ +mkdir lv2 vst2 vst3 +cp -RL *.lv2 lv2/ +cp -RL *.vst vst2/ +cp -RL *.vst3 vst3/ +rm -rf *.lv2 *.vst *.vst3 pkgbuild \ --identifier "studio.kx.distrho.plugins.${SNAME}.lv2bundles" \ @@ -31,6 +34,12 @@ pkgbuild \ --root "${PWD}/vst2/" \ ../dpf-${SNAME}-vst2bundles.pkg +pkgbuild \ + --identifier "studio.kx.distrho.plugins.${SNAME}.vst3bundles" \ + --install-location "/Library/Audio/Plug-Ins/VST3/" \ + --root "${PWD}/vst3/" \ + ../dpf-${SNAME}-vst3bundles.pkg + cd .. DPF_UTILS_DIR=$(dirname ${0}) @@ -39,6 +48,7 @@ sed -e "s|@name@|${NAME}|" ${DPF_UTILS_DIR}/plugin.pkg/welcome.txt.in > build/we sed -e "s|@builddir@|${PWD}/build|" \ -e "s|@lv2bundleref@|dpf-${SNAME}-lv2bundles.pkg|" \ -e "s|@vst2bundleref@|dpf-${SNAME}-vst2bundles.pkg|" \ + -e "s|@vst3bundleref@|dpf-${SNAME}-vst3bundles.pkg|" \ -e "s|@name@|${NAME}|g" \ -e "s|@sname@|${SNAME}|g" \ ${DPF_UTILS_DIR}/plugin.pkg/package.xml.in > build/package.xml diff --git a/utils/plugin.app/Contents/Info.plist b/utils/plugin.app/Contents/Info.plist @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleExecutable</key> + <string>@INFO_PLIST_PROJECT_NAME@</string> + <key>CFBundleIconFile</key> + <string></string> + <key>CFBundleIdentifier</key> + <string>studio.kx.distrho.@INFO_PLIST_PROJECT_NAME@</string> + <key>NSHighResolutionCapable</key> + <true/> + <key>NSRequiresAquaSystemAppearance</key> + <false/> + <key>NSMicrophoneUsageDescription</key> + <string>@INFO_PLIST_PROJECT_NAME@ requires microphone permissions for audio input.</string> +</dict> +</plist> diff --git a/utils/plugin.pkg/package.xml.in b/utils/plugin.pkg/package.xml.in @@ -2,7 +2,7 @@ <installer-gui-script minSpecVersion="1"> <title>@name@</title> <domains enable_anywhere="false" enable_currentUserHome="false" enable_localSystem="true" /> - <options customize="always" hostArchitectures="x86_64" require-scripts="false" rootVolumeOnly="true" /> + <options customize="always" hostArchitectures="arm64,x86_64" require-scripts="false" rootVolumeOnly="true" /> <pkg-ref id="studio.kx.distrho.@sname@" /> <welcome file="@builddir@/welcome.txt" mime-type="text/plain" /> <choice id="studio.kx.distrho.@sname@-lv2" title="LV2" description="Install LV2 plugins" visible="true"> @@ -11,8 +11,12 @@ <choice id="studio.kx.distrho.@sname@-vst2" title="VST2" description="Install VST2 plugins" visible="true"> <pkg-ref id="studio.kx.distrho.@sname@-vst2bundles" version="0">@vst2bundleref@</pkg-ref> </choice> + <choice id="studio.kx.distrho.@sname@-vst3" title="VST3" description="Install VST3 plugins" visible="true"> + <pkg-ref id="studio.kx.distrho.@sname@-vst3bundles" version="0">@vst3bundleref@</pkg-ref> + </choice> <choices-outline> <line choice="studio.kx.distrho.@sname@-lv2"/> <line choice="studio.kx.distrho.@sname@-vst2"/> + <line choice="studio.kx.distrho.@sname@-vst3"/> </choices-outline> </installer-gui-script> diff --git a/utils/plugin.vst/Contents/Info.plist b/utils/plugin.vst/Contents/Info.plist @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleDevelopmentRegion</key> diff --git a/utils/plugin.vst/Contents/MacOS/deleteme b/utils/plugin.vst/Contents/MacOS/deleteme @@ -1 +0,0 @@ - diff --git a/utils/symbols/dssi.def b/utils/symbols/dssi.def @@ -0,0 +1,3 @@ +EXPORTS +ladspa_descriptor +dssi_descriptor diff --git a/utils/symbols/dssi.exp b/utils/symbols/dssi.exp @@ -0,0 +1,2 @@ +_ladspa_descriptor +_dssi_descriptor diff --git a/utils/symbols/dssi.version b/utils/symbols/dssi.version @@ -0,0 +1,4 @@ +{ + global: ladspa_descriptor; dssi_descriptor; + local: *; +}; diff --git a/utils/symbols/ladspa.def b/utils/symbols/ladspa.def @@ -0,0 +1,2 @@ +EXPORTS +ladspa_descriptor diff --git a/utils/symbols/ladspa.exp b/utils/symbols/ladspa.exp @@ -0,0 +1 @@ +_ladspa_descriptor diff --git a/utils/symbols/ladspa.version b/utils/symbols/ladspa.version @@ -0,0 +1,4 @@ +{ + global: ladspa_descriptor; + local: *; +}; diff --git a/utils/symbols/lv2-dsp.def b/utils/symbols/lv2-dsp.def @@ -0,0 +1,3 @@ +EXPORTS +lv2_descriptor +lv2_generate_ttl diff --git a/utils/symbols/lv2-dsp.exp b/utils/symbols/lv2-dsp.exp @@ -0,0 +1,2 @@ +_lv2_descriptor +_lv2_generate_ttl diff --git a/utils/symbols/lv2-dsp.version b/utils/symbols/lv2-dsp.version @@ -0,0 +1,4 @@ +{ + global: lv2_descriptor; lv2_generate_ttl; + local: *; +}; diff --git a/utils/symbols/lv2-ui.def b/utils/symbols/lv2-ui.def @@ -0,0 +1,2 @@ +EXPORTS +lv2ui_descriptor diff --git a/utils/symbols/lv2-ui.exp b/utils/symbols/lv2-ui.exp @@ -0,0 +1 @@ +_lv2ui_descriptor diff --git a/utils/symbols/lv2-ui.version b/utils/symbols/lv2-ui.version @@ -0,0 +1,4 @@ +{ + global: lv2ui_descriptor; + local: *; +}; diff --git a/utils/symbols/lv2.def b/utils/symbols/lv2.def @@ -0,0 +1,4 @@ +EXPORTS +lv2_descriptor +lv2ui_descriptor +lv2_generate_ttl diff --git a/utils/symbols/lv2.exp b/utils/symbols/lv2.exp @@ -0,0 +1,3 @@ +_lv2_descriptor +_lv2ui_descriptor +_lv2_generate_ttl diff --git a/utils/symbols/lv2.version b/utils/symbols/lv2.version @@ -0,0 +1,4 @@ +{ + global: lv2_descriptor; lv2ui_descriptor; lv2_generate_ttl; + local: *; +}; diff --git a/utils/symbols/shared.def b/utils/symbols/shared.def @@ -0,0 +1,2 @@ +EXPORTS +createSharedPlugin diff --git a/utils/symbols/shared.exp b/utils/symbols/shared.exp @@ -0,0 +1 @@ +_createSharedPlugin diff --git a/utils/symbols/shared.version b/utils/symbols/shared.version @@ -0,0 +1,4 @@ +{ + global: createSharedPlugin; + local: *; +}; diff --git a/utils/symbols/vst2.def b/utils/symbols/vst2.def @@ -0,0 +1,3 @@ +EXPORTS +VSTPluginMain +main=VSTPluginMain diff --git a/utils/symbols/vst2.exp b/utils/symbols/vst2.exp @@ -0,0 +1 @@ +_VSTPluginMain diff --git a/utils/symbols/vst2.version b/utils/symbols/vst2.version @@ -0,0 +1,4 @@ +{ + global: VSTPluginMain; main; + local: *; +}; diff --git a/utils/symbols/vst3.def b/utils/symbols/vst3.def @@ -0,0 +1,4 @@ +EXPORTS +GetPluginFactory +InitDll +ExitDll diff --git a/utils/symbols/vst3.exp b/utils/symbols/vst3.exp @@ -0,0 +1,3 @@ +_GetPluginFactory +_bundleEntry +_bundleExit diff --git a/utils/symbols/vst3.version b/utils/symbols/vst3.version @@ -0,0 +1,4 @@ +{ + global: GetPluginFactory; ModuleEntry; ModuleExit; + local: *; +}; diff --git a/utils/valgrind-dpf.supp b/utils/valgrind-dpf.supp @@ -0,0 +1,48 @@ +{ + libdl is full of leaks + Memcheck:Leak + ... + fun:_dl_open + ... +} +{ + libdl is full of leaks + Memcheck:Leak + ... + fun:_dl_close + ... +} +{ + libdl is full of leaks + Memcheck:Leak + ... + fun:_dl_init +} +{ + libdl is full of leaks + Memcheck:Leak + fun:calloc + fun:allocate_dtv + fun:_dl_allocate_tls + ... +} +{ + libdl is full of leaks + Memcheck:Leak + ... + fun:call_init.part.0 +} +{ + ignore XInitThreads + Memcheck:Leak + ... + fun:XInitThreads + ... +} +{ + ignore XrmGetStringDatabase + Memcheck:Leak + ... + fun:XrmGetStringDatabase + ... +}