├── .github └── workflows │ └── build.yml ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── StompTuner.png ├── plugins ├── CairoWidgets │ ├── CairoButton.hpp │ ├── CairoColourTheme.hpp │ ├── CairoKnob.hpp │ ├── CairoLabel.hpp │ ├── CairoLed.hpp │ ├── CairoPeekMeter.hpp │ ├── CairoProgressBar.hpp │ ├── CairoPushButton.hpp │ ├── CairoSwitch.hpp │ ├── CairoToolTip.hpp │ ├── CairoTunerWidget.hpp │ ├── CairoValueDisplay.hpp │ ├── CairoWidgets.hpp │ └── scratch.c ├── StompTuner │ ├── DistrhoPluginInfo.h │ ├── Makefile │ ├── PluginStompTuner.cpp │ ├── PluginStompTuner.hpp │ ├── UIStompTuner.cpp │ ├── UIStompTuner.hpp │ ├── leder.c │ ├── low_high_cut.cc │ ├── low_high_cut.h │ ├── pitch_tracker.cpp │ ├── pitch_tracker.h │ ├── tuner.cc │ └── tuner.hpp ├── Utils │ ├── ResizeHandle.hpp │ └── UiSizeGroup.hpp └── zita-resampler-1.1.0 │ ├── AUTHORS │ ├── README │ ├── gx_resampler.cc │ ├── gx_resampler.h │ ├── resampler-table.cc │ ├── resampler.cc │ └── zita-resampler │ ├── resampler-table.h │ └── resampler.h └── pugl.patch /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | 6 | jobs: 7 | linux: 8 | strategy: 9 | matrix: 10 | target: [linux-arm64, linux-armhf, linux-i686, linux-riscv64, linux-x86_64] 11 | runs-on: ubuntu-20.04 12 | permissions: 13 | contents: write 14 | steps: 15 | - uses: actions/checkout@v3 16 | with: 17 | submodules: recursive 18 | - uses: distrho/dpf-makefile-action@v1 19 | with: 20 | target: ${{ matrix.target }} 21 | pawpaw: true 22 | 23 | macos: 24 | strategy: 25 | matrix: 26 | target: [macos-intel, macos-universal] 27 | runs-on: macos-11 28 | permissions: 29 | contents: write 30 | steps: 31 | - uses: actions/checkout@v3 32 | with: 33 | submodules: recursive 34 | - uses: distrho/dpf-makefile-action@v1 35 | with: 36 | target: ${{ matrix.target }} 37 | pawpaw: true 38 | 39 | windows: 40 | strategy: 41 | matrix: 42 | target: [win32, win64] 43 | runs-on: ubuntu-20.04 44 | permissions: 45 | contents: write 46 | steps: 47 | - uses: actions/checkout@v3 48 | with: 49 | submodules: recursive 50 | - uses: distrho/dpf-makefile-action@v1 51 | with: 52 | target: ${{ matrix.target }} 53 | pawpaw: true 54 | 55 | # pluginval: 56 | # runs-on: ubuntu-20.04 57 | # permissions: 58 | # contents: write 59 | # steps: 60 | # - uses: actions/checkout@v3 61 | # with: 62 | # submodules: recursive 63 | # - uses: distrho/dpf-makefile-action@v1 64 | # with: 65 | # target: pluginval 66 | # pawpaw: true 67 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "DPF"] 2 | path = dpf 3 | url = https://github.com/DISTRHO/DPF.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU General Public License v3.0 or later 2 | 3 | SPDX-License-Identifier: GPL-3.0-or-later 4 | 5 | StompTuner, a Strobe Tuner in Stomp Box Format. 6 | 7 | Copyright (C) 2023 Hermann Meyer. 8 | 9 | This program is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | 19 | You should have received a copy of the GNU General Public License 20 | along with this program. If not, see . 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # Makefile for DISTRHO Plugins # 3 | # ---------------------------- # 4 | # Created by falkTX, Christopher Arndt, and Patrick Desaulniers 5 | # 6 | 7 | # error out if DPF is missing, unless the current rule is 'submodules' 8 | define MISSING_SUBMODULES_ERROR 9 | ============================================================================= 10 | DPF library not found in directory 'dpf'. 11 | Please run "make submodules" to clone the missing Git submodules, then retry. 12 | ============================================================================= 13 | endef 14 | 15 | ifneq ($(MAKECMDGOALS), submodules) 16 | ifeq (,$(wildcard dpf/Makefile.base.mk)) 17 | $(info $(MISSING_SUBMODULES_ERROR)) 18 | $(error Unable to continue) 19 | else 20 | include dpf/Makefile.base.mk 21 | endif 22 | endif 23 | 24 | # -------------------------------------------------------------- 25 | # Installation directories 26 | 27 | PREFIX ?= /usr/local 28 | BINDIR ?= $(PREFIX)/bin 29 | LIBDIR ?= $(PREFIX)/lib 30 | DSSI_DIR ?= $(LIBDIR)/dssi 31 | LADSPA_DIR ?= $(LIBDIR)/ladspa 32 | ifneq ($(MACOS_OR_WINDOWS),true) 33 | LV2_DIR ?= $(LIBDIR)/lv2 34 | VST2_DIR ?= $(LIBDIR)/vst 35 | VST3_DIR ?= $(LIBDIR)/vst3 36 | CLAP_DIR ?= $(LIBDIR)/clap 37 | endif 38 | ifeq ($(MACOS),true) 39 | LV2_DIR ?= /Library/Audio/Plug-Ins/LV2 40 | VST2_DIR ?= /Library/Audio/Plug-Ins/VST 41 | VST3_DIR ?= /Library/Audio/Plug-Ins/VST3 42 | CLAP_DIR ?= /Library/Audio/Plug-Ins/CLAP 43 | AU_DIR ?= /Library/Audio/Plug-Ins/Components 44 | endif 45 | ifeq ($(WINDOWS),true) 46 | LV2_DIR ?= $(COMMONPROGRAMFILES)/LV2 47 | VST2_DIR ?= $(COMMONPROGRAMFILES)/VST2 48 | VST3_DIR ?= $(COMMONPROGRAMFILES)/VST3 49 | CLAP_DIR ?= $(COMMONPROGRAMFILES)/CLAP 50 | endif 51 | 52 | USER_DSSI_DIR ?= $(HOME)/.dssi 53 | USER_LADSPA_DIR ?= $(HOME)/.ladspa 54 | ifneq ($(MACOS_OR_WINDOWS),true) 55 | USER_LV2_DIR ?= $(HOME)/.lv2 56 | USER_VST2_DIR ?= $(HOME)/.vst 57 | USER_VST3_DIR ?= $(HOME)/.vst3 58 | USER_CLAP_DIR ?= $(HOME)/.clap 59 | endif 60 | ifeq ($(MACOS),true) 61 | USER_LV2_DIR ?= $(HOME)/Library/Audio/Plug-Ins/LV2 62 | USER_VST2_DIR ?= $(HOME)/Library/Audio/Plug-Ins/VST 63 | USER_VST3_DIR ?= $(HOME)/Library/Audio/Plug-Ins/VST3 64 | USER_CLAP_DIR ?= $(HOME)/Library/Audio/Plug-Ins/CLAP 65 | USER_AU_DIR ?= $(HOME)/Library/Audio/Plug-Ins/Components 66 | endif 67 | ifeq ($(WINDOWS),true) 68 | USER_LV2_DIR ?= $(APPDATA)/LV2 69 | USER_VST2_DIR ?= $(APPDATA)/VST 70 | USER_VST3_DIR ?= $(APPDATA)/VST3 71 | USER_CLAP_DIR ?= $(APPDATA)/CLAP 72 | endif 73 | 74 | export DESTDIR PREFIX BINDIR LIBDIR 75 | export DSSI_DIR LADSPA_DIR LV2_DIR VST2_DIR VST3_DIR CLAP_DIR 76 | export USER_DSSI_DIR USER_LADSPA_DIR USER_LV2_DIR USER_VST2_DIR USER_VST3_DIR USER_CLAP_DIR 77 | 78 | # -------------------------------------------------------------- 79 | # Targets 80 | 81 | all: libs plugins gen 82 | 83 | # -------------------------------------------------------------- 84 | 85 | submodules: 86 | git submodule update --init --recursive 87 | 88 | pugl_patch.flag: 89 | cd dpf/dgl/src/pugl-upstream/ && git apply ../../../../pugl.patch 90 | touch pugl_patch.flag 91 | 92 | libs: pugl_patch.flag 93 | $(MAKE) -C dpf/dgl ../build/libdgl-cairo.a 94 | 95 | plugins: libs 96 | $(MAKE) all -C plugins/StompTuner 97 | 98 | ifneq ($(CROSS_COMPILING),true) 99 | gen: plugins dpf/utils/lv2_ttl_generator 100 | $(CURDIR)/dpf/utils/generate-ttl.sh 101 | 102 | dpf/utils/lv2_ttl_generator: 103 | $(MAKE) -C dpf/utils/lv2-ttl-generator 104 | else 105 | gen: plugins dpf/utils/lv2_ttl_generator.exe 106 | @dpf/utils/generate-ttl.sh 107 | 108 | dpf/utils/lv2_ttl_generator.exe: 109 | $(MAKE) -C dpf/utils/lv2-ttl-generator WINDOWS=true 110 | endif 111 | 112 | # -------------------------------------------------------------- 113 | 114 | clean: 115 | $(MAKE) clean -C dpf/dgl 116 | $(MAKE) clean -C dpf/utils/lv2-ttl-generator 117 | $(MAKE) clean -C plugins/StompTuner 118 | rm -rf bin build 119 | ifneq (,$(wildcard ./pugl_patch.flag)) 120 | cd dpf/dgl/src/pugl-upstream/ && git apply -R ../../../../pugl.patch 121 | rm -f pugl_patch.flag 122 | endif 123 | install: all 124 | $(MAKE) install -C plugins/StompTuner 125 | 126 | install-user: all 127 | $(MAKE) install-user -C plugins/StompTuner 128 | 129 | # -------------------------------------------------------------- 130 | 131 | .PHONY: all clean install install-user submodules libs plugins gen 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StompTuner 2 | 3 | ![StompTuner](https://github.com/brummer10/StompTuner/blob/main/StompTuner.png?raw=true) 4 | 5 | StompTuner, a Strobe Tuner in Stomp Box Format. The Strobe provide 2 indicators. The outer ring 6 | have a accuracy of 1.0 Cent, the inner ring have a accuracy at 0.1 Cent. 7 | The working frequency range is from 24 - 998 Hz. 8 | The reference Pitch could be selected between 432 - 452 Hz. 9 | 10 | ## Formats 11 | 12 | StompTuner come in the following plug-in formats: 13 | 14 | * [LV2] 15 | * [VST2] 16 | * [VST3] 17 | * [CLAP] 18 | * [AU] 19 | 20 | ## Compiling 21 | 22 | Make sure you have installed the required build tools and libraries (see 23 | section "Prerequisites" below) and then clone this repository (including 24 | sub-modules) and simply run `make` in the project's root directory: 25 | 26 | ```con 27 | git clone --recursive https://github.com:brummer10/StompTuner 28 | cd StompTuner 29 | make 30 | ``` 31 | 32 | ## Installation 33 | 34 | To install all plugin formats to their appropriate system-wide location, run 35 | the following command (root priviledges may be required): 36 | 37 | ```con 38 | make install 39 | ``` 40 | 41 | The makefiles support the usual `PREFIX` and `DESTDIR` variables to change the 42 | installation prefix and set an installation root directory (defaulty: empty). 43 | `PREFIX` defaults to `/usr/local`, but on macOS and Windows it is not used, 44 | since the system-wide installation directories for plugins are fixed. 45 | 46 | Use make's `-n` option to see where the plugins would be installed without 47 | actually installing them. 48 | 49 | You can also set the installation directory for each plugin format with a 50 | dedicated makefile variable. 51 | 52 | * LV2: `LV2_DIR` (`/lib/lv2`) 53 | * VST2: `VST3_DIR` (`/lib/vst2`) 54 | * VST3: `VST3_DIR` (`/lib/vst3`) 55 | * CLAP: `CLAP_DIR` (`/lib/clap`) 56 | * AU: `AU_DIR` (`/Library/Audio/Plug-Ins/Components`) 57 | 58 | Example: 59 | 60 | ```con 61 | make DESTDIR=/tmp/build-root PREFIX=/usr VST2_DIR=/usr/lib/lxvst install 62 | ``` 63 | 64 | To install the plugins only for your current user account, run: 65 | 66 | ```con 67 | make install-user 68 | ``` 69 | 70 | Again, you can also set the installation directory for each plugin format with 71 | a dedicated makefile variable. 72 | 73 | * LV2: `USER_LV2_DIR` (`$HOME/.lv2`) 74 | * VST2: `USER_VST2_DIR` (`$HOME/.vst2`) 75 | * VST3: `USER_VST3_DIR` (`$HOME/.vst3`) 76 | * CLAP: `USER_CLAP_DIR` (`$HOME/.clap`) 77 | * AU: `AU_DIR` (`$HOME/Library/Audio/Plug-Ins/Components`) 78 | 79 | *Note: The given default values for all of the above listed environment 80 | variables differ depending on the target OS.* 81 | 82 | 83 | ## Prerequisites 84 | 85 | * The GCC C++ compiler, library and the usual associated software build tools 86 | (GNU `make`, etc.). 87 | 88 | Debian / Ubuntu users should install the `build-essential` package 89 | to get these, Arch users the `base-devel` package group. 90 | 91 | * [pkgconf] 92 | 93 | The [LV2], [VST2] (Xaymar/vst2sdk) and [VST3] (travesty) headers are included in the 94 | [DPF] framework, which is integrated as a Git sub-module. These need not be 95 | installed separately to build the software in the respective plug-in formats. 96 | 97 | 98 | ## Author 99 | 100 | This software was created by *brummer*. 101 | 102 | 103 | ## Acknowledgements 104 | 105 | This project is built using the DISTRHO Plugin Framework ([DPF]) 106 | 107 | [DPF]: https://github.com/DISTRHO/DPF 108 | [LV2]: http://lv2plug.in/ 109 | [pkgconf]: https://github.com/pkgconf/pkgconf 110 | [VST2]: https://en.wikipedia.org/wiki/Virtual_Studio_Technology 111 | [VST3]: https://en.wikipedia.org/wiki/Virtual_Studio_Technology 112 | [CLAP]:https://en.wikipedia.org/wiki/CLever_Audio_Plug-in 113 | [AU]:https://en.wikipedia.org/wiki/Audio_Unit 114 | -------------------------------------------------------------------------------- /StompTuner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brummer10/StompTuner/4f4df7e6f42501cb42d410b62a99eece8a1a3c1b/StompTuner.png -------------------------------------------------------------------------------- /plugins/CairoWidgets/CairoButton.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CairoButton for the DISTRHO Plugin Framework (DPF) 3 | * 4 | * SPDX-License-Identifier: 0BSD 5 | * 6 | * Copyright (C) 2023 brummer 7 | */ 8 | 9 | #pragma once 10 | 11 | #ifndef CAIROBUTTON_H 12 | #define CAIROBUTTON_H 13 | 14 | #include 15 | 16 | #include "CairoColourTheme.hpp" 17 | 18 | START_NAMESPACE_DGL 19 | 20 | // ----------------------------------------------------------------------- 21 | 22 | class CairoButton : public CairoSubWidget 23 | { 24 | public: 25 | 26 | explicit CairoButton(SubWidget* const parent, CairoColourTheme &theme_, 27 | bool *blocked_, UI *ui, const char* lab, const uint32_t index) 28 | : CairoSubWidget(parent), 29 | theme(theme_), 30 | blocked(blocked_), 31 | setParameterValue([ui] (const uint32_t index, float value) 32 | {ui->setParameterValue(index, value);}), 33 | label(lab), 34 | port(index) 35 | { 36 | init(); 37 | } 38 | 39 | explicit CairoButton(TopLevelWidget* const parent, CairoColourTheme &theme_, 40 | bool *blocked_, UI *ui, const char* lab, const uint32_t index) 41 | : CairoSubWidget(parent), 42 | theme(theme_), 43 | blocked(blocked_), 44 | setParameterValue([ui] (const uint32_t index, float value) 45 | {ui->setParameterValue(index, value);}), 46 | label(lab), 47 | port(index) 48 | { 49 | init(); 50 | } 51 | 52 | void setValue(float v) 53 | { 54 | value = v; 55 | state = (int)value; 56 | repaint(); 57 | } 58 | 59 | protected: 60 | 61 | void init() 62 | { 63 | value = 0.0f; 64 | state = 0; 65 | prelight = false; 66 | } 67 | 68 | void onCairoDisplay(const CairoGraphicsContext& context) override 69 | { 70 | cairo_t* const cr = context.handle; 71 | const Size sz = getSize(); 72 | const int w = sz.getWidth(); 73 | const int h = sz.getHeight(); 74 | 75 | cairo_push_group (cr); 76 | 77 | if (!state) 78 | theme.setCairoColour(cr, theme.idColourBackgroundNormal); 79 | else 80 | theme.setCairoColour(cr, theme.idColourBackgroundActive); 81 | cairo_paint(cr); 82 | 83 | if (prelight) { 84 | theme.setCairoColour(cr, theme.idColourBackgroundPrelight); 85 | cairo_paint(cr); 86 | } 87 | 88 | if (!state) 89 | theme.boxShadowOutset(cr, w, h); 90 | else 91 | theme.boxShadowInset(cr, w, h); 92 | 93 | int offset = 0; 94 | cairo_text_extents_t extents; 95 | if(state==0) { 96 | theme.setCairoColour(cr, theme.idColourForgroundNormal); 97 | } else if(state==1) { 98 | theme.setCairoColour(cr, theme.idColourForgroundActive); 99 | offset = 2; 100 | } 101 | cairo_set_font_size (cr, h/2.2); 102 | cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, 103 | CAIRO_FONT_WEIGHT_BOLD); 104 | cairo_text_extents(cr, label , &extents); 105 | 106 | cairo_move_to (cr, (w-extents.width)*0.5 +offset, (h+extents.height)*0.45 +offset); 107 | cairo_show_text(cr, label); 108 | 109 | cairo_pop_group_to_source (cr); 110 | cairo_paint (cr); 111 | } 112 | 113 | bool onMouse(const MouseEvent& event) override 114 | { 115 | if (!event.press && contains(event.pos)) // mouse button release 116 | { 117 | value = value ? 0.0f : 1.0f; 118 | state = !state; 119 | setParameterValue(port, value); 120 | repaint(); 121 | } 122 | 123 | return CairoSubWidget::onMouse(event); 124 | } 125 | 126 | bool onMotion(const MotionEvent& event) override 127 | { 128 | if (contains(event.pos)) // enter 129 | { 130 | if (!prelight && !(*blocked)) { 131 | prelight = true; 132 | (*blocked) = true; 133 | repaint(); 134 | } 135 | } 136 | else if (prelight) // leave 137 | { 138 | prelight = false; 139 | (*blocked) = false; 140 | repaint(); 141 | } 142 | 143 | return CairoSubWidget::onMotion(event); 144 | } 145 | 146 | private: 147 | CairoColourTheme &theme; 148 | bool *blocked; 149 | std::function setParameterValue; 150 | float value; 151 | uint state; 152 | bool prelight; 153 | const char* label; 154 | const uint32_t port; 155 | DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CairoButton) 156 | }; 157 | 158 | // ----------------------------------------------------------------------- 159 | 160 | END_NAMESPACE_DGL 161 | 162 | #endif 163 | -------------------------------------------------------------------------------- /plugins/CairoWidgets/CairoColourTheme.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CairoColourTheme for the DISTRHO Plugin Framework (DPF) 3 | * 4 | * SPDX-License-Identifier: 0BSD 5 | * 6 | * Copyright (C) 2023 brummer 7 | */ 8 | 9 | #pragma once 10 | 11 | #ifndef CAIROCOLOURTHEME_H 12 | #define CAIROCOLOURTHEME_H 13 | 14 | #include "Cairo.hpp" 15 | 16 | START_NAMESPACE_DGL 17 | 18 | class CairoColourTheme 19 | { 20 | public: 21 | CairoColourTheme() 22 | { 23 | init(); 24 | } 25 | 26 | struct CairoColour{ 27 | double r; 28 | double g; 29 | double b; 30 | double a; 31 | }; 32 | 33 | typedef struct { 34 | const unsigned char * data; 35 | long int position; 36 | } binary_stream; 37 | 38 | cairo_surface_t *cairo_image_surface_create_from_stream ( const unsigned char* name) 39 | { 40 | binary_stream png_stream; 41 | png_stream.data = name; 42 | png_stream.position = 0; 43 | return cairo_image_surface_create_from_png_stream(&png_stream_reader, (void *)&png_stream); 44 | } 45 | 46 | void setCairoColour(cairo_t* const cr, const CairoColour idColour, float darker = 0.8f) 47 | { 48 | cairo_set_source_rgba(cr, idColour.r * darker, idColour.g * darker, 49 | idColour.b * darker, idColour.a); 50 | } 51 | 52 | void setCairoColourWithAlpha(cairo_t* const cr, const CairoColour idColour, float alpha = 1.0f) 53 | { 54 | cairo_set_source_rgba(cr, idColour.r, idColour.g, idColour.b, alpha); 55 | } 56 | 57 | void setIdColour(CairoColour &idColour, double r, double g, double b, double a) { 58 | idColour = CairoColour {r, g, b, a}; 59 | } 60 | 61 | void boxShadowInset(cairo_t* const cr, int width, int height, int x = 0, int y = 0, bool fill = false) 62 | { 63 | cairo_pattern_t *pat = cairo_pattern_create_linear (x, y, x + width, y); 64 | cairo_pattern_add_color_stop_rgba 65 | (pat, 1, idColourBoxLight.r, idColourBoxLight.g, idColourBoxLight.b, idColourBoxLight.a); 66 | cairo_pattern_add_color_stop_rgba 67 | (pat, 0.95, idColourBoxLight.r * 0.6, idColourBoxLight.g * 0.6, idColourBoxLight.b * 0.6, 0.0); 68 | cairo_pattern_add_color_stop_rgba 69 | (pat, 0.1, idColourBoxShadow.r * 2.0, idColourBoxShadow.g * 2.0, idColourBoxShadow.b * 2.0, 0.0); 70 | cairo_pattern_add_color_stop_rgba 71 | (pat, 0, idColourBoxShadow.r, idColourBoxShadow.g, idColourBoxShadow.b, idColourBoxShadow.a); 72 | cairo_set_source(cr, pat); 73 | if (fill) cairo_fill_preserve (cr); 74 | else cairo_paint (cr); 75 | cairo_pattern_destroy (pat); 76 | pat = NULL; 77 | pat = cairo_pattern_create_linear (x, y, x, y + height); 78 | cairo_pattern_add_color_stop_rgba 79 | (pat, 1, idColourBoxLight.r, idColourBoxLight.g, idColourBoxLight.b, idColourBoxLight.a); 80 | cairo_pattern_add_color_stop_rgba 81 | (pat, 0.9, idColourBoxLight.r * 0.6, idColourBoxLight.g * 0.6, idColourBoxLight.b * 0.6, 0.0); 82 | cairo_pattern_add_color_stop_rgba 83 | (pat, 0.1, idColourBoxShadow.r * 2.0, idColourBoxShadow.g * 2.0, idColourBoxShadow.b * 2.0, 0.0); 84 | cairo_pattern_add_color_stop_rgba 85 | (pat, 0, idColourBoxShadow.r, idColourBoxShadow.g, idColourBoxShadow.b, idColourBoxShadow.a); 86 | cairo_set_source(cr, pat); 87 | if (fill) cairo_fill_preserve (cr); 88 | else cairo_paint (cr); 89 | cairo_pattern_destroy (pat); 90 | } 91 | 92 | void boxShadowOutset(cairo_t* const cr, int width, int height, int x = 0, int y = 0, bool fill = false) 93 | { 94 | cairo_pattern_t *pat = cairo_pattern_create_linear (x, y, x + width, y); 95 | cairo_pattern_add_color_stop_rgba 96 | (pat, 0, idColourBoxLight.r, idColourBoxLight.g, idColourBoxLight.b, idColourBoxLight.a); 97 | cairo_pattern_add_color_stop_rgba 98 | (pat, 0.05, idColourBoxLight.r * 0.6, idColourBoxLight.g * 0.6, idColourBoxLight.b * 0.6, 0.0); 99 | cairo_pattern_add_color_stop_rgba 100 | (pat, 0.95, idColourBoxShadow.r * 2.0, idColourBoxShadow.g * 2.0, idColourBoxShadow.b * 2.0, 0.0); 101 | cairo_pattern_add_color_stop_rgba 102 | (pat, 1, idColourBoxShadow.r, idColourBoxShadow.g, idColourBoxShadow.b, idColourBoxShadow.a); 103 | cairo_set_source(cr, pat); 104 | if (fill) cairo_fill_preserve (cr); 105 | else cairo_paint (cr); 106 | cairo_pattern_destroy (pat); 107 | pat = NULL; 108 | pat = cairo_pattern_create_linear (x, y, x, y + height); 109 | cairo_pattern_add_color_stop_rgba 110 | (pat, 0, idColourBoxLight.r, idColourBoxLight.g, idColourBoxLight.b, idColourBoxLight.a); 111 | cairo_pattern_add_color_stop_rgba 112 | (pat, 0.1, idColourBoxLight.r * 0.6, idColourBoxLight.g * 0.6, idColourBoxLight.b * 0.6, 0.0); 113 | cairo_pattern_add_color_stop_rgba 114 | (pat, 0.9, idColourBoxShadow.r * 2.0, idColourBoxShadow.g * 2.0, idColourBoxShadow.b * 2.0, 0.0); 115 | cairo_pattern_add_color_stop_rgba 116 | (pat, 1, idColourBoxShadow.r, idColourBoxShadow.g, idColourBoxShadow.b, idColourBoxShadow.a); 117 | cairo_set_source(cr, pat); 118 | if (fill) cairo_fill_preserve (cr); 119 | else cairo_paint (cr); 120 | cairo_pattern_destroy (pat); 121 | } 122 | 123 | void boxShadow(cairo_t* const cr, int width, int height, int w, int h, int x = 0, int y = 0, bool fill = false) 124 | { 125 | cairo_pattern_t *pat = cairo_pattern_create_linear (x, y, x + w, y); 126 | cairo_pattern_add_color_stop_rgba 127 | (pat, 0, idColourBoxLight.r, idColourBoxLight.g, idColourBoxLight.b, idColourBoxLight.a * 0.8); 128 | cairo_pattern_add_color_stop_rgba 129 | (pat, 0.4, idColourBoxLight.r, idColourBoxLight.g, idColourBoxLight.b, idColourBoxLight.a * 0.3); 130 | cairo_pattern_add_color_stop_rgba 131 | (pat, 1, idColourBoxLight.r, idColourBoxLight.g, idColourBoxLight.b, idColourBoxLight.a * 0.0); 132 | cairo_pattern_set_extend(pat, CAIRO_EXTEND_NONE); 133 | cairo_set_source(cr, pat); 134 | if (fill) cairo_fill_preserve (cr); 135 | else cairo_paint (cr); 136 | cairo_pattern_destroy (pat); 137 | pat = NULL; 138 | 139 | pat = cairo_pattern_create_linear (x, y, x, y + h); 140 | cairo_pattern_add_color_stop_rgba 141 | (pat, 0, idColourBoxLight.r, idColourBoxLight.g, idColourBoxLight.b, idColourBoxLight.a * 0.8); 142 | cairo_pattern_add_color_stop_rgba 143 | (pat, 0.4, idColourBoxLight.r, idColourBoxLight.g, idColourBoxLight.b, idColourBoxLight.a * 0.3); 144 | cairo_pattern_add_color_stop_rgba 145 | (pat, 1, idColourBoxLight.r, idColourBoxLight.g, idColourBoxLight.b, idColourBoxLight.a * 0.0); 146 | cairo_pattern_set_extend(pat, CAIRO_EXTEND_NONE); 147 | cairo_set_source(cr, pat); 148 | if (fill) cairo_fill_preserve (cr); 149 | else cairo_paint (cr); 150 | cairo_pattern_destroy (pat); 151 | pat = NULL; 152 | 153 | pat = cairo_pattern_create_linear (x + width - w, y, x +width, y); 154 | cairo_pattern_add_color_stop_rgba 155 | (pat, 0, idColourBoxShadow.r, idColourBoxShadow.g, idColourBoxShadow.b, idColourBoxShadow.a * 0.0); 156 | cairo_pattern_add_color_stop_rgba 157 | (pat, 0.4, idColourBoxShadow.r, idColourBoxShadow.g, idColourBoxShadow.b, idColourBoxShadow.a * 0.3); 158 | cairo_pattern_add_color_stop_rgba 159 | (pat, 1, idColourBoxShadow.r, idColourBoxShadow.g, idColourBoxShadow.b, idColourBoxShadow.a * 0.8); 160 | cairo_pattern_set_extend(pat, CAIRO_EXTEND_NONE); 161 | cairo_set_source(cr, pat); 162 | if (fill) cairo_fill_preserve (cr); 163 | else cairo_paint (cr); 164 | cairo_pattern_destroy (pat); 165 | pat = NULL; 166 | 167 | pat = cairo_pattern_create_linear (x, y + height - h, x, y +height); 168 | cairo_pattern_add_color_stop_rgba 169 | (pat, 0, idColourBoxShadow.r, idColourBoxShadow.g, idColourBoxShadow.b, idColourBoxShadow.a * 0.0); 170 | cairo_pattern_add_color_stop_rgba 171 | (pat, 0.4, idColourBoxShadow.r, idColourBoxShadow.g, idColourBoxShadow.b, idColourBoxShadow.a * 0.3); 172 | cairo_pattern_add_color_stop_rgba 173 | (pat, 1, idColourBoxShadow.r, idColourBoxShadow.g, idColourBoxShadow.b, idColourBoxShadow.a * 0.8); 174 | cairo_pattern_set_extend(pat, CAIRO_EXTEND_NONE); 175 | cairo_set_source(cr, pat); 176 | if (fill) cairo_fill_preserve (cr); 177 | else cairo_paint (cr); 178 | cairo_pattern_destroy (pat); 179 | pat = NULL; 180 | } 181 | 182 | void knobShadowOutset(cairo_t* const cr, int width, int height, int x = 0, int y = 0) 183 | { 184 | cairo_pattern_t *pat = cairo_pattern_create_linear (x, y, x + width, y + height); 185 | cairo_pattern_add_color_stop_rgba 186 | (pat, 0, idColourBoxLight.r, idColourBoxLight.g, idColourBoxLight.b, idColourBoxLight.a); 187 | cairo_pattern_add_color_stop_rgba 188 | (pat, 0.45, idColourBoxLight.r * 0.6, idColourBoxLight.g * 0.6, idColourBoxLight.b * 0.6, 0.4); 189 | cairo_pattern_add_color_stop_rgba 190 | (pat, 0.65, idColourBoxShadow.r * 2.0, idColourBoxShadow.g * 2.0, idColourBoxShadow.b * 2.0, 0.4); 191 | cairo_pattern_add_color_stop_rgba 192 | (pat, 1, idColourBoxShadow.r, idColourBoxShadow.g, idColourBoxShadow.b, idColourBoxShadow.a); 193 | cairo_pattern_set_extend(pat, CAIRO_EXTEND_NONE); 194 | cairo_set_source(cr, pat); 195 | cairo_fill_preserve (cr); 196 | cairo_pattern_destroy (pat); 197 | } 198 | 199 | void knobShadowInset(cairo_t* const cr, int width, int height, int x = 0, int y = 0) 200 | { 201 | cairo_pattern_t* pat = cairo_pattern_create_linear (x, y, x + width, y + height); 202 | cairo_pattern_add_color_stop_rgba 203 | (pat, 1, idColourBoxLight.r, idColourBoxLight.g, idColourBoxLight.b, idColourBoxLight.a); 204 | cairo_pattern_add_color_stop_rgba 205 | (pat, 0.65, idColourBoxLight.r * 0.6, idColourBoxLight.g * 0.6, idColourBoxLight.b * 0.6, 0.4); 206 | cairo_pattern_add_color_stop_rgba 207 | (pat, 0.55, idColourBoxShadow.r * 2.0, idColourBoxShadow.g * 2.0, idColourBoxShadow.b * 2.0, 0.4); 208 | cairo_pattern_add_color_stop_rgba 209 | (pat, 0, idColourBoxShadow.r, idColourBoxShadow.g, idColourBoxShadow.b, idColourBoxShadow.a); 210 | cairo_pattern_set_extend(pat, CAIRO_EXTEND_NONE); 211 | cairo_set_source(cr, pat); 212 | cairo_fill (cr); 213 | cairo_pattern_destroy (pat); 214 | } 215 | 216 | CairoColour idColourBackground; 217 | CairoColour idColourBackgroundNormal; 218 | CairoColour idColourBackgroundPrelight; 219 | CairoColour idColourBackgroundActive; 220 | CairoColour idColourBackgroundProgress; 221 | 222 | CairoColour idColourForground; 223 | CairoColour idColourForgroundNormal; 224 | CairoColour idColourForgroundPrelight; 225 | CairoColour idColourForgroundActive; 226 | 227 | CairoColour idColourFrame; 228 | 229 | CairoColour idColourBoxShadow; 230 | CairoColour idColourBoxLight; 231 | 232 | protected: 233 | 234 | static cairo_status_t png_stream_reader (void *_stream, unsigned char *data, unsigned int length) 235 | { 236 | binary_stream * stream = (binary_stream *) _stream; 237 | memcpy(data, &stream->data[stream->position],length); 238 | stream->position += length; 239 | return CAIRO_STATUS_SUCCESS; 240 | } 241 | 242 | void init() 243 | { 244 | setIdColour(idColourBackground, 0.13, 0.13, 0.13, 1); 245 | setIdColour(idColourBackgroundNormal, 0.13, 0.13, 0.13, 1.0); 246 | setIdColour(idColourBackgroundPrelight, 0.63, 0.63, 0.63, 0.03); 247 | setIdColour(idColourBackgroundActive, 0.63, 0.13, 0.13, 1.0); 248 | setIdColour(idColourBackgroundProgress, 0.4, 0.4, 0.4, 1.0); 249 | 250 | setIdColour(idColourForground, 0.63, 0.63, 0.63, 1.0); 251 | setIdColour(idColourForgroundNormal, 0.63, 0.63, 0.63, 1.0); 252 | setIdColour(idColourForgroundPrelight, 0.83, 0.83, 0.83, 1.0); 253 | setIdColour(idColourForgroundActive, 0.93, 0.63, 0.63, 1.0); 254 | 255 | setIdColour(idColourFrame, 0.03, 0.03, 0.03, 1.0); 256 | 257 | setIdColour(idColourBoxShadow, 0.05, 0.05, 0.05, 1.0); 258 | setIdColour(idColourBoxLight, 0.33, 0.33, 0.33, 1.0); 259 | } 260 | 261 | private: 262 | DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CairoColourTheme) 263 | }; 264 | 265 | END_NAMESPACE_DGL 266 | 267 | #endif 268 | -------------------------------------------------------------------------------- /plugins/CairoWidgets/CairoKnob.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CairoKnob for the DISTRHO Plugin Framework (DPF) 3 | * 4 | * SPDX-License-Identifier: 0BSD 5 | * 6 | * Copyright (C) 2023 brummer 7 | */ 8 | 9 | #pragma once 10 | 11 | #ifndef CAIROKNOB_H 12 | #define CAIROKNOB_H 13 | 14 | #include 15 | 16 | #ifndef M_PI 17 | #define M_PI 3.14159265358979323846 18 | #endif 19 | 20 | #include "CairoColourTheme.hpp" 21 | 22 | START_NAMESPACE_DGL 23 | 24 | // ----------------------------------------------------------------------- 25 | 26 | class CairoKnob : public CairoSubWidget 27 | { 28 | public: 29 | 30 | explicit CairoKnob(SubWidget* const parent, CairoColourTheme &theme_, bool *blocked_, 31 | UI *ui, const char* lab, const uint32_t index, bool center_ = false, bool indicator = false) 32 | : CairoSubWidget(parent), 33 | theme(theme_), 34 | blocked(blocked_), 35 | setParameterValue([ui] (const uint32_t index, float value) 36 | {ui->setParameterValue(index, value);}), 37 | label(lab), 38 | port(index), 39 | center(center_), 40 | isIndicator(indicator) 41 | { 42 | init(); 43 | } 44 | 45 | explicit CairoKnob(TopLevelWidget* const parent, CairoColourTheme &theme_, bool *blocked_, 46 | UI *ui, const char* lab, const uint32_t index, bool center_ = false, bool indicator = false) 47 | : CairoSubWidget(parent), 48 | theme(theme_), 49 | blocked(blocked_), 50 | setParameterValue([ui] (const uint32_t index, float value) 51 | {ui->setParameterValue(index, value);}), 52 | label(lab), 53 | port(index), 54 | center(center_), 55 | isIndicator(indicator) 56 | { 57 | init(); 58 | } 59 | 60 | void setValue(float v) 61 | { 62 | value = v; 63 | state = getState(); 64 | repaint(); 65 | } 66 | 67 | void setAdjustment(float value_, float min_value_, float max_value_, float value_step_) 68 | { 69 | value = value_; 70 | min_value = min_value_; 71 | max_value = max_value_; 72 | value_step = value_step_; 73 | state = getState(); 74 | stepper = get_stepper(); 75 | repaint(); 76 | } 77 | 78 | void setIndicator(float v) 79 | { 80 | indic = (int)v; 81 | repaint(); 82 | } 83 | 84 | protected: 85 | 86 | void init() 87 | { 88 | value = 0.0f; 89 | min_value = 0.0f; 90 | max_value = 1.0f; 91 | value_step = 0.01f; 92 | posY = 0.0f; 93 | v = 0; 94 | indic = 0.0f; 95 | inDrag = false; 96 | state = getState(); 97 | prelight = false; 98 | stepper = get_stepper(); 99 | } 100 | 101 | float get_stepper() 102 | { 103 | float range = std::fabs(max_value - min_value); 104 | float steps = range / value_step; 105 | return steps * 0.01f; 106 | } 107 | 108 | float getState() 109 | { 110 | return (value - min_value) / (max_value - min_value); 111 | } 112 | 113 | void onCairoDisplay(const CairoGraphicsContext& context) override 114 | { 115 | cairo_t* const cr = context.handle; 116 | 117 | /** get size for the knob **/ 118 | const Size sz = getSize(); 119 | const int width = sz.getWidth(); 120 | const int height = sz.getHeight() - (sz.getHeight() * 0.15); 121 | 122 | const int grow = (width > height) ? height:width; 123 | const int knob_x = grow-1; 124 | const int knob_y = grow-1; 125 | 126 | const int knobx = (width - knob_x) * 0.5; 127 | const int knobx1 = width* 0.5; 128 | 129 | const int knoby = (height - knob_y) * 0.5; 130 | const int knoby1 = height * 0.5; 131 | 132 | /** get geometric values for the knob **/ 133 | const double scale_zero = 20 * (M_PI/180); // defines "dead zone" 134 | const double angle = scale_zero + state * 2 * (M_PI - scale_zero); 135 | 136 | const double pointer_off =knob_x/3.5; 137 | const double radius = MIN(knob_x-pointer_off, knob_y-pointer_off) / 2; 138 | const double lengh_x = (knobx+radius+pointer_off/2) - radius * sin(angle); 139 | const double lengh_y = (knoby+radius+pointer_off/2) + radius * cos(angle); 140 | const double radius_x = (knobx+radius+pointer_off/2) - radius * sin(angle); 141 | const double radius_y = (knoby+radius+pointer_off/2) + radius * cos(angle); 142 | 143 | /** draw the knob **/ 144 | cairo_push_group (cr); 145 | 146 | cairo_arc(cr,knobx1, knoby1, knob_x/2.1, 0, 2 * M_PI ); 147 | theme.knobShadowOutset(cr, width, height); 148 | cairo_stroke_preserve (cr); 149 | cairo_new_path (cr); 150 | 151 | cairo_arc(cr,knobx1, knoby1, knob_x/2.4, 0, 2 * M_PI ); 152 | theme.knobShadowOutset(cr, width, height); 153 | cairo_set_line_width(cr,knobx1/10); 154 | theme.setCairoColour(cr, theme.idColourBoxShadow); 155 | cairo_stroke_preserve (cr); 156 | cairo_new_path (cr); 157 | 158 | cairo_arc(cr,knobx1, knoby1, knob_x/3.1, 0, 2 * M_PI ); 159 | if (isIndicator) 160 | { 161 | if (indic) theme.setCairoColour(cr, theme.idColourBackgroundNormal); 162 | else theme.setCairoColour(cr, theme.idColourBackgroundActive, 0.3); 163 | } 164 | else 165 | { 166 | theme.setCairoColour(cr, theme.idColourBackgroundNormal); 167 | } 168 | cairo_fill_preserve (cr); 169 | theme.knobShadowInset(cr, width, height); 170 | cairo_new_path (cr); 171 | 172 | /** create a rotating pointer on the kob**/ 173 | cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); 174 | cairo_set_line_join(cr, CAIRO_LINE_JOIN_BEVEL); 175 | cairo_move_to(cr, radius_x, radius_y); 176 | cairo_line_to(cr,lengh_x,lengh_y); 177 | cairo_set_line_width(cr,knobx1/10); 178 | theme.setCairoColour(cr, theme.idColourForground); 179 | cairo_stroke_preserve(cr); 180 | cairo_new_path (cr); 181 | 182 | /** create a indicator ring around the knob **/ 183 | const double add_angle = 90 * (M_PI / 180.); 184 | cairo_new_sub_path(cr); 185 | theme.setCairoColour(cr, theme.idColourBackgroundActive); 186 | cairo_set_line_width(cr,knobx1/25); 187 | if (!center) 188 | { 189 | cairo_arc (cr, knobx1, knoby1, knob_x/2.4, 190 | add_angle + scale_zero, add_angle + angle); 191 | } 192 | else 193 | { 194 | const double mid_angle = scale_zero + 0.5 * 2 * (M_PI - scale_zero); 195 | if (state < 0.5f) 196 | cairo_arc_negative (cr, knobx1, knoby1, knob_x/2.4, 197 | add_angle + mid_angle, add_angle + angle); 198 | else 199 | cairo_arc (cr, knobx1, knoby1, knob_x/2.4, 200 | add_angle + mid_angle, add_angle + angle); 201 | } 202 | cairo_stroke(cr); 203 | 204 | /** show value on the kob**/ 205 | theme.setCairoColour(cr, theme.idColourForground); 206 | cairo_text_extents_t extents; 207 | cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, 208 | CAIRO_FONT_WEIGHT_BOLD); 209 | cairo_set_font_size (cr, height * 0.15); 210 | char s[17]; 211 | char sa[17]; 212 | const char* format[] = {"%.1f", "%.2f", "%.3f"}; 213 | if (fabs(value_step)>0.99) { 214 | snprintf(s, 16,"%d", (int) value); 215 | } else if (fabs(value_step)>0.09) { 216 | snprintf(s, 16, format[1-1], value); 217 | } else { 218 | snprintf(s, 16, format[2-1], value); 219 | } 220 | snprintf(sa, strlen(s),"%s", "000000000000000"); 221 | cairo_text_extents(cr, sa, &extents); 222 | int wx = extents.width * 0.5; 223 | cairo_text_extents(cr, s, &extents); 224 | cairo_move_to (cr, knobx1 - wx, knoby1+extents.height/2); 225 | cairo_show_text(cr, s); 226 | cairo_new_path (cr); 227 | 228 | /** show label below the knob**/ 229 | if (prelight) theme.setCairoColour(cr, theme.idColourForgroundPrelight); 230 | else theme.setCairoColour(cr, theme.idColourForgroundNormal); 231 | cairo_set_font_size (cr, height * 0.18); 232 | cairo_text_extents(cr,label , &extents); 233 | cairo_move_to (cr, (width*0.5)-(extents.width/2), height + (height * 0.15)-(extents.height*0.1)); 234 | cairo_show_text(cr, label); 235 | cairo_new_path (cr); 236 | 237 | cairo_pop_group_to_source (cr); 238 | cairo_paint (cr); 239 | } 240 | 241 | bool onKeyboard(const KeyboardEvent& event) override { 242 | if (event.press && prelight) 243 | { 244 | int set_value = 0; 245 | if (event.key == 57357) set_value = 1; // UpArrow 246 | else if (event.key == 57359) set_value = -1; // DownArrow 247 | float v = value + (value_step * set_value); 248 | v = MIN(max_value, MAX(min_value, v)); 249 | setValue(v); 250 | setParameterValue(port, value); 251 | } 252 | return CairoSubWidget::onKeyboard(event); 253 | } 254 | 255 | bool onMouse(const MouseEvent& event) override 256 | { 257 | if (event.press && (event.button == 1) && contains(event.pos)) // mouse button pressed 258 | { 259 | posY = event.pos.getY(); 260 | inDrag = true; 261 | } 262 | else 263 | { 264 | inDrag = false; 265 | } 266 | 267 | return CairoSubWidget::onMouse(event); 268 | } 269 | 270 | bool onScroll(const ScrollEvent& event) override 271 | { 272 | if (!contains(event.pos)) 273 | return CairoSubWidget::onScroll(event); 274 | 275 | const float set_value = (event.delta.getY() > 0.f) ? 1.f : -1.f; 276 | float v1 = value + (value_step * set_value); 277 | v1 = MIN(max_value, MAX(min_value, v1)); 278 | setValue(v1); 279 | setParameterValue(port, value); 280 | 281 | return CairoSubWidget::onScroll(event); 282 | } 283 | 284 | bool onMotion(const MotionEvent& event) override 285 | { 286 | if (inDrag && (std::abs(posY - event.pos.getY()) > 0.f)) 287 | { 288 | const float set_value = (posY - event.pos.getY() > 0.f) ? 1.f : -1.f ; 289 | v += stepper * value_step; 290 | posY = event.pos.getY(); 291 | if (v >= value_step) 292 | { 293 | v = value + (value_step * set_value); 294 | v = MIN(max_value, MAX(min_value, v)); 295 | setValue(v); 296 | setParameterValue(port, value); 297 | v = 0; 298 | } 299 | } 300 | if (contains(event.pos)) // enter 301 | { 302 | if (!prelight && !(*blocked)) { 303 | prelight = true; 304 | (*blocked) = true; 305 | repaint(); 306 | } 307 | } 308 | else if (prelight && !inDrag) // leave 309 | { 310 | prelight = false; 311 | (*blocked) = false; 312 | repaint(); 313 | } 314 | 315 | return CairoSubWidget::onMotion(event); 316 | } 317 | 318 | private: 319 | CairoColourTheme &theme; 320 | bool *blocked; 321 | std::function setParameterValue; 322 | float value; 323 | float min_value; 324 | float max_value; 325 | float value_step; 326 | float posY; 327 | float v; 328 | float stepper; 329 | bool inDrag; 330 | float state; 331 | bool prelight; 332 | const char* label; 333 | const uint32_t port; 334 | bool center; 335 | bool isIndicator; 336 | int indic; 337 | DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CairoKnob) 338 | }; 339 | 340 | // ----------------------------------------------------------------------- 341 | 342 | END_NAMESPACE_DGL 343 | 344 | #endif 345 | -------------------------------------------------------------------------------- /plugins/CairoWidgets/CairoLabel.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CairoLabel for the DISTRHO Plugin Framework (DPF) 3 | * 4 | * SPDX-License-Identifier: 0BSD 5 | * 6 | * Copyright (C) 2023 brummer 7 | */ 8 | 9 | #pragma once 10 | 11 | #ifndef CAIROLABEL_H 12 | #define CAIROLABEL_H 13 | 14 | #include "CairoColourTheme.hpp" 15 | 16 | START_NAMESPACE_DGL 17 | 18 | // ----------------------------------------------------------------------- 19 | 20 | class CairoLabel : public CairoSubWidget 21 | { 22 | public: 23 | 24 | explicit CairoLabel(SubWidget* const parent, CairoColourTheme &theme_, const char* lab) 25 | : CairoSubWidget(parent), 26 | theme(theme_), 27 | label(lab) {} 28 | 29 | explicit CairoLabel(TopLevelWidget* const parent, CairoColourTheme &theme_, const char* lab) 30 | : CairoSubWidget(parent), 31 | theme(theme_), 32 | label(lab) {} 33 | 34 | protected: 35 | 36 | void onCairoDisplay(const CairoGraphicsContext& context) override 37 | { 38 | cairo_t* const cr = context.handle; 39 | 40 | const Size sz = getSize(); 41 | const int width = sz.getWidth(); 42 | const int height = sz.getHeight(); 43 | 44 | cairo_push_group (cr); 45 | 46 | theme.setCairoColour(cr, theme.idColourForgroundNormal); 47 | cairo_text_extents_t extents; 48 | cairo_set_font_size (cr, height * 0.45); 49 | cairo_text_extents(cr,label , &extents); 50 | cairo_move_to (cr, (width * 0.5)-(extents.width * 0.5), height * 0.5 +extents.height * 0.5); 51 | cairo_show_text(cr, label); 52 | cairo_new_path (cr); 53 | 54 | cairo_pop_group_to_source (cr); 55 | cairo_paint (cr); 56 | } 57 | 58 | private: 59 | CairoColourTheme &theme; 60 | const char* label; 61 | DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CairoLabel) 62 | }; 63 | 64 | // ----------------------------------------------------------------------- 65 | 66 | END_NAMESPACE_DGL 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /plugins/CairoWidgets/CairoLed.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CairoLed for the DISTRHO Plugin Framework (DPF) 3 | * 4 | * SPDX-License-Identifier: 0BSD 5 | * 6 | * Copyright (C) 2023 brummer 7 | */ 8 | 9 | #pragma once 10 | 11 | #ifndef CAIROLED_H 12 | #define CAIROLED_H 13 | 14 | #include 15 | 16 | #include "CairoColourTheme.hpp" 17 | 18 | START_NAMESPACE_DGL 19 | 20 | // ----------------------------------------------------------------------- 21 | 22 | class CairoLed : public CairoSubWidget 23 | { 24 | public: 25 | 26 | explicit CairoLed(SubWidget* const parent, CairoColourTheme &theme_) 27 | : CairoSubWidget(parent), 28 | theme(theme_) 29 | { 30 | init(); 31 | } 32 | 33 | explicit CairoLed(TopLevelWidget* const parent, CairoColourTheme &theme_) 34 | : CairoSubWidget(parent), 35 | theme(theme_) 36 | { 37 | init(); 38 | } 39 | 40 | void setValue(float v) 41 | { 42 | state = (int)v; 43 | repaint(); 44 | } 45 | 46 | protected: 47 | 48 | void init() 49 | { 50 | state = 0; 51 | } 52 | 53 | void onCairoDisplay(const CairoGraphicsContext& context) override 54 | { 55 | cairo_t* const cr = context.handle; 56 | const Size sz = getSize(); 57 | const int w = sz.getWidth(); 58 | const int h = sz.getHeight(); 59 | const int r = h < w ? (h - 2) * 0.5 : (w - 2) * 0.5; 60 | 61 | cairo_push_group (cr); 62 | cairo_arc(cr,w * 0.5, h * 0.5, r, 0, 2 * M_PI ); 63 | 64 | if (!state) { 65 | cairo_pattern_t* pat = cairo_pattern_create_radial (w * 0.5, h * 0.5, 66 | 1, w * 0.5, h * 0.5, r); 67 | cairo_pattern_add_color_stop_rgba (pat, 0, 0.83, 0.1, 0.1, 1.0); 68 | cairo_pattern_add_color_stop_rgba (pat, 0.6, 0.33, 0.1, 0.1, 1.0); 69 | cairo_pattern_add_color_stop_rgba (pat, 1, 0.3, 0.3, 0.3, 1.0); 70 | cairo_set_source (cr, pat); 71 | cairo_fill_preserve(cr); 72 | cairo_pattern_destroy (pat); 73 | } 74 | else 75 | { 76 | cairo_pattern_t* pat = cairo_pattern_create_radial (w * 0.5, h * 0.5, 77 | 1, w * 0.5, h * 0.5, r); 78 | cairo_pattern_add_color_stop_rgba (pat, 0, 0.3, 0.1, 0.1, 1.0); 79 | cairo_pattern_add_color_stop_rgba (pat, 0.7, 0.2, 0.1, 0.1, 1.0); 80 | cairo_pattern_add_color_stop_rgba (pat, 1, 0.3, 0.3, 0.3, 1.0); 81 | cairo_set_source (cr, pat); 82 | cairo_fill_preserve(cr); 83 | cairo_pattern_destroy (pat); 84 | } 85 | 86 | cairo_set_line_width(cr,2); 87 | theme.setCairoColour(cr, theme.idColourBoxShadow); 88 | cairo_stroke (cr); 89 | cairo_new_path (cr); 90 | 91 | cairo_pop_group_to_source (cr); 92 | cairo_paint (cr); 93 | } 94 | 95 | private: 96 | CairoColourTheme &theme; 97 | uint state; 98 | DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CairoLed) 99 | }; 100 | 101 | // ----------------------------------------------------------------------- 102 | 103 | END_NAMESPACE_DGL 104 | 105 | #endif 106 | -------------------------------------------------------------------------------- /plugins/CairoWidgets/CairoPeekMeter.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CairoPeekMeter for the DISTRHO Plugin Framework (DPF) 3 | * 4 | * SPDX-License-Identifier: 0BSD 5 | * 6 | * Copyright (C) 2023 brummer 7 | */ 8 | 9 | #pragma once 10 | 11 | #ifndef CAIROPEEKMETER_H 12 | #define CAIROPEEKMETER_H 13 | 14 | #include "CairoColourTheme.hpp" 15 | 16 | START_NAMESPACE_DGL 17 | 18 | // ----------------------------------------------------------------------- 19 | 20 | class CairoPeekMeter : public CairoSubWidget 21 | { 22 | public: 23 | 24 | explicit CairoPeekMeter(SubWidget* const parent, CairoColourTheme &theme_) 25 | : CairoSubWidget(parent), 26 | image(nullptr), 27 | theme(theme_) 28 | { 29 | init(); 30 | } 31 | 32 | explicit CairoPeekMeter(TopLevelWidget* const parent, CairoColourTheme &theme_) 33 | : CairoSubWidget(parent), 34 | image(nullptr), 35 | theme(theme_) 36 | { 37 | init(); 38 | } 39 | 40 | void setValue(float v) 41 | { 42 | value = v; 43 | repaint(); 44 | } 45 | 46 | ~CairoPeekMeter() { 47 | cairo_surface_destroy(image); 48 | } 49 | 50 | protected: 51 | void init() 52 | { 53 | old_value = -70.0f; 54 | std_value = -70.0f; 55 | value = -70.0f; 56 | } 57 | 58 | float power2db(float power) 59 | { 60 | const float falloff = 27 * 60 * 0.0005; 61 | const float fallsoft = 6 * 60 * 0.0005; 62 | //power = 20.*log10(power); 63 | if (power <= 20.*log10(0.00021)) { // -70db 64 | power = 20.*log10(0.00000000001); //-137db 65 | old_value = MIN(0.0,old_value - fallsoft); 66 | } 67 | // retrieve old meter value and consider falloff 68 | if (power < std_value) { 69 | power = MAX(power, std_value - falloff); 70 | old_value = MIN(0.0,old_value - fallsoft); 71 | } 72 | if (power > old_value) { 73 | old_value = power ; 74 | } 75 | 76 | std_value = power; 77 | return power; 78 | } 79 | 80 | float logMeter (float db) 81 | { 82 | float def = 0.0f; /* Meter deflection %age */ 83 | 84 | if (db < -70.0f) { 85 | def = 0.0f; 86 | } else if (db < -60.0f) { 87 | def = (db + 70.0f) * 0.25f; 88 | } else if (db < -50.0f) { 89 | def = (db + 60.0f) * 0.5f + 2.5f; 90 | } else if (db < -40.0f) { 91 | def = (db + 50.0f) * 0.75f + 7.5f; 92 | } else if (db < -30.0f) { 93 | def = (db + 40.0f) * 1.5f + 15.0f; 94 | } else if (db < -20.0f) { 95 | def = (db + 30.0f) * 2.0f + 30.0f; 96 | } else if (db < 6.0f) { 97 | def = (db + 20.0f) * 2.5f + 50.0f; 98 | } else { 99 | def = 115.0f; 100 | } 101 | 102 | /* 115 is the deflection %age that would be 103 | when db=6.0. this is an arbitrary 104 | endpoint for our scaling. 105 | */ 106 | return def/115.0f; 107 | } 108 | 109 | 110 | void drawMeterImage(int width, int height) 111 | { 112 | image = cairo_image_surface_create( 113 | CAIRO_FORMAT_ARGB32, width, height*2); 114 | cairo_t *cri = cairo_create (image); 115 | cairo_push_group (cri); 116 | 117 | theme.setCairoColour(cri, theme.idColourFrame); 118 | cairo_paint(cri); 119 | 120 | cairo_pattern_t *pat = cairo_pattern_create_linear (0, 0, width, 0.0); 121 | cairo_pattern_add_color_stop_rgba(pat, 0.0, 0.1, 0.5, 0.1, 0.4); 122 | cairo_pattern_add_color_stop_rgba(pat, 0.8, 0.4, 0.4, 0.1, 0.4); 123 | cairo_pattern_add_color_stop_rgba(pat, 1.0, 0.5, 0.0, 0.0, 0.4); 124 | cairo_set_source(cri, pat); 125 | 126 | int c = (height) * 0.5 ; 127 | int ci = c-2; 128 | 129 | int i = 1; 130 | int j = 1; 131 | for(;i sz = getSize(); 207 | const int width = sz.getWidth(); 208 | const int height = sz.getHeight() * 0.5; 209 | 210 | double meterstate = logMeter(power2db(value)); 211 | double oldstate = logMeter(old_value); 212 | cairo_set_source_surface (cr, image, 0, height * 0.5); 213 | cairo_rectangle(cr,0, height * 0.5, width, height * 0.5); 214 | cairo_fill(cr); 215 | cairo_set_source_surface (cr, image, 0, -height); 216 | cairo_rectangle(cr, 0,height * 0.5, width*meterstate, height * 0.5); 217 | cairo_fill(cr); 218 | 219 | cairo_rectangle(cr,(width*oldstate)-3, height * 0.5, 3, height * 0.5); 220 | cairo_fill(cr); 221 | drawMeterScale(cr, height, width, height * 0.5); 222 | theme.boxShadowInset(cr, width, height*2); 223 | cairo_pop_group_to_source (cr); 224 | cairo_paint (cr); 225 | } 226 | 227 | void onResize(const ResizeEvent& ev) override 228 | { 229 | cairo_surface_destroy(image); 230 | image = nullptr; 231 | drawMeterImage(ev.size.getWidth(), ev.size.getHeight() * 0.5); 232 | } 233 | 234 | private: 235 | cairo_surface_t* image; 236 | CairoColourTheme &theme; 237 | float value; 238 | float old_value; 239 | float std_value; 240 | DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CairoPeekMeter) 241 | }; 242 | 243 | // ----------------------------------------------------------------------- 244 | 245 | END_NAMESPACE_DGL 246 | 247 | #endif 248 | -------------------------------------------------------------------------------- /plugins/CairoWidgets/CairoProgressBar.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CairoProgressBar for the DISTRHO Plugin Framework (DPF) 3 | * 4 | * SPDX-License-Identifier: 0BSD 5 | * 6 | * Copyright (C) 2023 brummer 7 | */ 8 | 9 | #pragma once 10 | 11 | #ifndef CAIROPROGRESSBAR_H 12 | #define CAIROPROGRESSBAR_H 13 | 14 | #include "CairoColourTheme.hpp" 15 | 16 | START_NAMESPACE_DGL 17 | 18 | // ----------------------------------------------------------------------- 19 | 20 | class CairoProgressBar : public CairoSubWidget 21 | { 22 | public: 23 | 24 | explicit CairoProgressBar(SubWidget* const parent, CairoColourTheme &theme_) 25 | : CairoSubWidget(parent), 26 | theme(theme_) 27 | { 28 | init(); 29 | } 30 | 31 | explicit CairoProgressBar(TopLevelWidget* const parent, CairoColourTheme &theme_) 32 | : CairoSubWidget(parent), 33 | theme(theme_) 34 | { 35 | init(); 36 | } 37 | 38 | void setValue(float v) 39 | { 40 | value = v; 41 | repaint(); 42 | } 43 | 44 | protected: 45 | void init() 46 | { 47 | value = 0.0f; 48 | } 49 | 50 | void onCairoDisplay(const CairoGraphicsContext& context) override 51 | { 52 | cairo_t* const cr = context.handle; 53 | cairo_push_group (cr); 54 | 55 | const Size sz = getSize(); 56 | const int width = sz.getWidth(); 57 | const int height = sz.getHeight(); 58 | 59 | theme.setCairoColour(cr,theme.idColourFrame); 60 | cairo_rectangle(cr,0, 0, width, height); 61 | cairo_set_line_width(cr,2); 62 | cairo_stroke(cr); 63 | 64 | theme.setCairoColour(cr, theme.idColourBackgroundProgress); 65 | cairo_rectangle(cr,0, 0, width * value, height); 66 | cairo_fill(cr); 67 | 68 | cairo_text_extents_t extents; 69 | cairo_set_font_size (cr, height/2.2); 70 | char s[64]; 71 | snprintf(s, 63,"%d%%", (int) (value * 100.0)); 72 | cairo_text_extents(cr,s , &extents); 73 | cairo_move_to (cr, width * 0.5 - extents.width * 0.5, height * 0.5 + extents.height * 0.5 ); 74 | theme.setCairoColour(cr, theme.idColourForground); 75 | cairo_set_operator(cr, CAIRO_OPERATOR_ADD); 76 | cairo_show_text(cr, s); 77 | cairo_set_operator(cr, CAIRO_OPERATOR_OVER); 78 | cairo_new_path (cr); 79 | theme.boxShadowInset(cr, width, height); 80 | cairo_pop_group_to_source (cr); 81 | cairo_paint (cr); 82 | } 83 | 84 | private: 85 | CairoColourTheme &theme; 86 | float value; 87 | DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CairoProgressBar) 88 | }; 89 | 90 | // ----------------------------------------------------------------------- 91 | 92 | END_NAMESPACE_DGL 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /plugins/CairoWidgets/CairoPushButton.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CairoPushButton for the DISTRHO Plugin Framework (DPF) 3 | * 4 | * SPDX-License-Identifier: 0BSD 5 | * 6 | * Copyright (C) 2023 brummer 7 | */ 8 | 9 | #pragma once 10 | 11 | #ifndef CAIROPUSHBUTTON_H 12 | #define CAIROPUSHBUTTON_H 13 | 14 | #include 15 | #include 16 | 17 | #include "extra/Runner.hpp" 18 | 19 | #include "CairoColourTheme.hpp" 20 | #include "CairoLed.hpp" 21 | #include "scratch.c" 22 | 23 | START_NAMESPACE_DGL 24 | 25 | // ----------------------------------------------------------------------- 26 | 27 | class CairoPushButton : public CairoSubWidget, public Runner 28 | { 29 | public: 30 | 31 | explicit CairoPushButton(SubWidget* const parent, CairoColourTheme &theme_, 32 | bool *blocked_, ScopedPointer& led_, UI *ui, const char* lab, const uint32_t index) 33 | : CairoSubWidget(parent), 34 | theme(theme_), 35 | blocked(blocked_), 36 | led(led_), 37 | setParameterValue([ui] (const uint32_t index, float value) 38 | {ui->setParameterValue(index, value);}), 39 | label(lab), 40 | port(index) 41 | { 42 | init(); 43 | } 44 | 45 | explicit CairoPushButton(TopLevelWidget* const parent, CairoColourTheme &theme_, 46 | bool *blocked_, ScopedPointer& led_, UI *ui, const char* lab, const uint32_t index) 47 | : CairoSubWidget(parent), 48 | theme(theme_), 49 | blocked(blocked_), 50 | led(led_), 51 | setParameterValue([ui] (const uint32_t index, float value) 52 | {ui->setParameterValue(index, value);}), 53 | label(lab), 54 | port(index) 55 | { 56 | init(); 57 | } 58 | 59 | ~CairoPushButton() { 60 | cairo_surface_destroy(texture); 61 | } 62 | 63 | void setValue(float v) 64 | { 65 | value = v; 66 | //repaint(); // use uiIdle() to repaint in intervals 67 | } 68 | 69 | protected: 70 | 71 | void init() 72 | { 73 | value = 0.0f; 74 | state = 0; 75 | fontSize = getFontSize(); 76 | texture = theme.cairo_image_surface_create_from_stream (scratch_png); 77 | prelight = false; 78 | setState.store(false, std::memory_order_release); 79 | } 80 | 81 | uint getFontSize() 82 | { 83 | size_t s = strlen(label); 84 | return (s * 0.7); 85 | } 86 | 87 | bool run() override 88 | { 89 | if (setState.load(std::memory_order_acquire)) { 90 | setState.store(false, std::memory_order_release); 91 | state = 0; 92 | //repaint(); // use uiIdle() to repaint in intervals 93 | return false; 94 | } else { 95 | setState.store(true, std::memory_order_release); 96 | return true; 97 | } 98 | } 99 | 100 | void onCairoDisplay(const CairoGraphicsContext& context) override 101 | { 102 | cairo_t* const cr = context.handle; 103 | if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) return; 104 | const Size sz = getSize(); 105 | const int w = sz.getWidth(); 106 | const int h = sz.getHeight(); 107 | 108 | cairo_push_group (cr); 109 | 110 | theme.setCairoColour(cr, theme.idColourBackgroundNormal, 1.0f); 111 | cairo_paint(cr); 112 | 113 | if (prelight) { 114 | theme.setCairoColour(cr, theme.idColourBackgroundPrelight); 115 | cairo_paint(cr); 116 | } 117 | 118 | if (state) { 119 | cairo_rectangle(cr, 1, 1, w -2, h -2); 120 | cairo_set_line_width(cr,2); 121 | theme.setCairoColour(cr, theme.idColourBackgroundNormal); 122 | cairo_stroke(cr); 123 | 124 | cairo_rectangle(cr, 2, 2, w -4, h -4); 125 | cairo_translate (cr, 2,2); 126 | cairo_pattern_t *pat = cairo_pattern_create_for_surface(texture); 127 | cairo_pattern_set_extend (pat, CAIRO_EXTEND_REPEAT); 128 | cairo_set_source(cr, pat); 129 | cairo_fill (cr); 130 | 131 | cairo_set_line_width(cr,1); 132 | cairo_move_to(cr, 1,h); 133 | cairo_line_to(cr, 1, 1); 134 | cairo_line_to(cr, w-2, 1); 135 | theme.setCairoColour(cr, theme.idColourBoxShadow); 136 | cairo_stroke(cr); 137 | cairo_pattern_destroy (pat); 138 | } else { 139 | cairo_pattern_t *pat = cairo_pattern_create_for_surface(texture); 140 | cairo_pattern_set_extend (pat, CAIRO_EXTEND_REPEAT); 141 | cairo_set_source(cr, pat); 142 | cairo_paint (cr); 143 | cairo_pattern_destroy (pat); 144 | theme.boxShadow(cr, w, h, 5, 5); 145 | } 146 | 147 | int offset = 0; 148 | cairo_text_extents_t extents; 149 | if(state==1) { 150 | offset = 2; 151 | } 152 | cairo_set_font_size (cr, w / fontSize); 153 | cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, 154 | CAIRO_FONT_WEIGHT_BOLD); 155 | cairo_text_extents(cr, label , &extents); 156 | 157 | cairo_move_to (cr, (w-extents.width)*0.5 +offset-1, (h+extents.height)*0.72 +offset-1); 158 | cairo_text_path(cr, label); 159 | cairo_set_line_width(cr, 1); 160 | cairo_set_source_rgba(cr, 0.1, 0.1, 0.1, 1); 161 | cairo_stroke (cr); 162 | 163 | cairo_move_to (cr, (w-extents.width)*0.5 +offset+1, (h+extents.height)*0.72 +offset+1); 164 | cairo_text_path(cr, label); 165 | cairo_set_line_width(cr, 1); 166 | cairo_set_source_rgba(cr, 0.33, 0.33, 0.33, 1); 167 | cairo_stroke (cr); 168 | 169 | //theme.setCairoColour(cr, theme.idColourForgroundNormal); 170 | cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1); 171 | cairo_move_to (cr, (w-extents.width)*0.5 +offset, (h+extents.height)*0.72 +offset); 172 | cairo_show_text(cr, label); 173 | 174 | cairo_pop_group_to_source (cr); 175 | cairo_paint (cr); 176 | } 177 | 178 | bool onMouse(const MouseEvent& event) override 179 | { 180 | if (event.press && (event.button == 1) && contains(event.pos)) // mouse button is pressed 181 | { 182 | value = value ? 0.0f : 1.0f; 183 | state = 1; 184 | led->setValue(value); 185 | setParameterValue(port, value); 186 | //repaint(); // use uiIdle() to repaint in intervals 187 | } 188 | else if (state) 189 | { 190 | state = 0; 191 | //repaint(); // use uiIdle() to repaint in intervals 192 | } 193 | 194 | return CairoSubWidget::onMouse(event); 195 | } 196 | 197 | bool onScroll(const ScrollEvent& event) override 198 | { 199 | if (!contains(event.pos)) 200 | return CairoSubWidget::onScroll(event); 201 | 202 | const float set_value = (event.delta.getY() > 0.f) ? 0.f : 1.f; 203 | if (set_value != value) { 204 | state = 1; 205 | setValue(set_value); 206 | led->setValue(value); 207 | setParameterValue(port, value); 208 | if (!isRunnerActive()) startRunner(250); 209 | } 210 | 211 | return CairoSubWidget::onScroll(event); 212 | } 213 | 214 | bool onMotion(const MotionEvent& event) override 215 | { 216 | if (contains(event.pos)) // enter 217 | { 218 | if (!prelight && !(*blocked)) { 219 | prelight = true; 220 | (*blocked) = true; 221 | //repaint(); // use uiIdle() to repaint in intervals 222 | } 223 | } 224 | else if (prelight) // leave 225 | { 226 | prelight = false; 227 | (*blocked) = false; 228 | //repaint(); // use uiIdle() to repaint in intervals 229 | } 230 | 231 | return CairoSubWidget::onMotion(event); 232 | } 233 | 234 | private: 235 | CairoColourTheme &theme; 236 | cairo_surface_t *texture; 237 | bool *blocked; 238 | ScopedPointer& led; 239 | std::function setParameterValue; 240 | float value; 241 | uint state; 242 | bool prelight; 243 | const char* label; 244 | const uint32_t port; 245 | uint fontSize; 246 | std::atomic setState; 247 | DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CairoPushButton) 248 | }; 249 | 250 | // ----------------------------------------------------------------------- 251 | 252 | END_NAMESPACE_DGL 253 | 254 | #endif 255 | -------------------------------------------------------------------------------- /plugins/CairoWidgets/CairoSwitch.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CairoSwitch for the DISTRHO Plugin Framework (DPF) 3 | * 4 | * SPDX-License-Identifier: 0BSD 5 | * 6 | * Copyright (C) 2023 brummer 7 | */ 8 | 9 | #pragma once 10 | 11 | #ifndef CAIROSWITCH_H 12 | #define CAIROSWITCH_H 13 | 14 | #include 15 | 16 | #include "CairoColourTheme.hpp" 17 | 18 | START_NAMESPACE_DGL 19 | 20 | // ----------------------------------------------------------------------- 21 | 22 | class CairoSwitch : public CairoSubWidget 23 | { 24 | public: 25 | 26 | explicit CairoSwitch(SubWidget* const parent, CairoColourTheme &theme_, 27 | bool *blocked_, UI *ui, const char* lab, const uint32_t index) 28 | : CairoSubWidget(parent), 29 | theme(theme_), 30 | blocked(blocked_), 31 | setParameterValue([ui] (const uint32_t index, float value) 32 | {ui->setParameterValue(index, value);}), 33 | label(lab), 34 | port(index) 35 | { 36 | init(); 37 | } 38 | 39 | explicit CairoSwitch(TopLevelWidget* const parent, CairoColourTheme &theme_, 40 | bool *blocked_, UI *ui, const char* lab, const uint32_t index) 41 | : CairoSubWidget(parent), 42 | theme(theme_), 43 | blocked(blocked_), 44 | setParameterValue([ui] (const uint32_t index, float value) 45 | {ui->setParameterValue(index, value);}), 46 | label(lab), 47 | port(index) 48 | { 49 | init(); 50 | } 51 | 52 | void setValue(float v) 53 | { 54 | value = v; 55 | state = (int)value; 56 | repaint(); 57 | } 58 | 59 | protected: 60 | 61 | void init() 62 | { 63 | value = 0.0f; 64 | state = 0; 65 | prelight = false; 66 | } 67 | 68 | 69 | void roundrec(cairo_t *cr, double x, double y, double width, double height, double r) { 70 | cairo_arc(cr, x+r, y+r, r, M_PI, 3*M_PI/2); 71 | cairo_arc(cr, x+width-r, y+r, r, 3*M_PI/2, 0); 72 | cairo_arc(cr, x+width-r, y+height-r, r, 0, M_PI/2); 73 | cairo_arc(cr, x+r, y+height-r, r, M_PI/2, M_PI); 74 | cairo_close_path(cr); 75 | } 76 | 77 | void switchLight(cairo_t *cr, int x, int y, int w) 78 | { 79 | cairo_pattern_t *pat = cairo_pattern_create_linear (x, y, x + w, y); 80 | cairo_pattern_add_color_stop_rgba 81 | (pat, 1, theme.idColourBackgroundActive.r, theme.idColourBackgroundActive.g, 82 | theme.idColourBackgroundActive.b, theme.idColourBackgroundActive.a * 0.8); 83 | cairo_pattern_add_color_stop_rgba 84 | (pat, 0.5, theme.idColourBackgroundActive.r, theme.idColourBackgroundActive.g, 85 | theme.idColourBackgroundActive.b, theme.idColourBackgroundActive.a * 0.4); 86 | cairo_pattern_add_color_stop_rgba 87 | (pat, 0, theme.idColourBackgroundActive.r, theme.idColourBackgroundActive.g, 88 | theme.idColourBackgroundActive.b, theme.idColourBackgroundActive.a * 0.2); 89 | cairo_pattern_set_extend(pat, CAIRO_EXTEND_NONE); 90 | cairo_set_source(cr, pat); 91 | cairo_fill_preserve (cr); 92 | cairo_pattern_destroy (pat); 93 | 94 | } 95 | 96 | void onCairoDisplay(const CairoGraphicsContext& context) override 97 | { 98 | cairo_t* const cr = context.handle; 99 | const Size sz = getSize(); 100 | const int w = sz.getWidth(); 101 | const int h = sz.getHeight() * 0.5; 102 | const int centerH = h * 0.5; 103 | const int centerW = state ? w - centerH : centerH ; 104 | const int offset = h * 0.2; 105 | 106 | cairo_push_group (cr); 107 | 108 | roundrec(cr, 1, 1, w-2, h-2, centerH); 109 | theme.knobShadowOutset(cr, w , h, 0, 0); 110 | cairo_stroke_preserve (cr); 111 | 112 | cairo_new_path(cr); 113 | roundrec(cr, offset, offset, w - (offset * 2), h - (offset * 2), centerH-offset); 114 | theme.setCairoColour(cr, theme.idColourBoxShadow); 115 | cairo_fill_preserve(cr); 116 | if (state) { 117 | //theme.setCairoColourWithAlpha(cr, theme.idColourBackgroundActive, 0.6f); 118 | //cairo_fill_preserve(cr); 119 | switchLight(cr, offset, offset, w - (offset * 2)); 120 | } 121 | theme.setCairoColour(cr, theme.idColourBoxShadow); 122 | cairo_set_line_width(cr,1); 123 | cairo_stroke_preserve (cr); 124 | 125 | cairo_new_path(cr); 126 | cairo_arc(cr,centerW, centerH, h/2.8, 0, 2 * M_PI ); 127 | theme.setCairoColour(cr, theme.idColourBackground); 128 | cairo_fill_preserve(cr); 129 | theme.knobShadowOutset(cr, w * 0.5 , h, centerW - centerH, 0); 130 | theme.setCairoColour(cr, theme.idColourBoxShadow); 131 | cairo_set_line_width(cr,1); 132 | cairo_stroke_preserve (cr); 133 | 134 | cairo_new_path(cr); 135 | cairo_arc(cr,centerW, centerH, h/3.6, 0, 2 * M_PI ); 136 | theme.setCairoColour(cr, theme.idColourBackgroundNormal); 137 | cairo_fill_preserve(cr); 138 | theme.knobShadowInset(cr, w * 0.5 , h, centerW - centerH, 0); 139 | cairo_stroke (cr); 140 | 141 | cairo_text_extents_t extents; 142 | if (prelight) theme.setCairoColour(cr, theme.idColourForgroundPrelight); 143 | else theme.setCairoColour(cr, theme.idColourForgroundNormal); 144 | cairo_set_font_size (cr, h * 0.5); 145 | cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, 146 | CAIRO_FONT_WEIGHT_BOLD); 147 | cairo_text_extents(cr, label , &extents); 148 | 149 | cairo_move_to (cr, (w-extents.width)*0.5, (h+extents.height + offset)); 150 | cairo_show_text(cr, label); 151 | 152 | cairo_pop_group_to_source (cr); 153 | cairo_paint (cr); 154 | } 155 | 156 | bool onMouse(const MouseEvent& event) override 157 | { 158 | if (!event.press && contains(event.pos)) // mouse button release 159 | { 160 | value = value ? 0.0f : 1.0f; 161 | state = !state; 162 | setParameterValue(port, value); 163 | repaint(); 164 | } 165 | 166 | return CairoSubWidget::onMouse(event); 167 | } 168 | 169 | bool onScroll(const ScrollEvent& event) override 170 | { 171 | if (!contains(event.pos)) 172 | return CairoSubWidget::onScroll(event); 173 | 174 | const float set_value = (event.delta.getY() > 0.f) ? 1.f : 0.f; 175 | setValue(set_value); 176 | setParameterValue(port, value); 177 | 178 | return CairoSubWidget::onScroll(event); 179 | } 180 | 181 | bool onMotion(const MotionEvent& event) override 182 | { 183 | if (contains(event.pos)) // enter 184 | { 185 | if (!prelight && !(*blocked)) { 186 | prelight = true; 187 | (*blocked) = true; 188 | repaint(); 189 | } 190 | } 191 | else if (prelight) // leave 192 | { 193 | prelight = false; 194 | (*blocked) = false; 195 | repaint(); 196 | } 197 | 198 | return CairoSubWidget::onMotion(event); 199 | } 200 | 201 | private: 202 | CairoColourTheme &theme; 203 | bool *blocked; 204 | std::function setParameterValue; 205 | float value; 206 | uint state; 207 | bool prelight; 208 | const char* label; 209 | const uint32_t port; 210 | DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CairoSwitch) 211 | }; 212 | 213 | // ----------------------------------------------------------------------- 214 | 215 | END_NAMESPACE_DGL 216 | 217 | #endif 218 | -------------------------------------------------------------------------------- /plugins/CairoWidgets/CairoToolTip.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CairoToolTip for the DISTRHO Plugin Framework (DPF) 3 | * 4 | * SPDX-License-Identifier: 0BSD 5 | * 6 | * Copyright (C) 2023 brummer 7 | */ 8 | 9 | #pragma once 10 | 11 | #ifndef CAIROTOOLTIP_H 12 | #define CAIROTOOLTIP_H 13 | 14 | #include 15 | #include "extra/Runner.hpp" 16 | 17 | #include "CairoColourTheme.hpp" 18 | 19 | START_NAMESPACE_DGL 20 | 21 | // ----------------------------------------------------------------------- 22 | 23 | class CairoToolTip : public CairoSubWidget, public Runner 24 | { 25 | public: 26 | 27 | explicit CairoToolTip(SubWidget* const parent_, CairoColourTheme &theme_, const char* lab) 28 | : CairoSubWidget(parent_), 29 | parent(parent_), 30 | theme(theme_), 31 | label(lab) 32 | { 33 | init(); 34 | } 35 | 36 | explicit CairoToolTip(TopLevelWidget* const parent_, CairoColourTheme &theme_, const char* lab) 37 | : CairoSubWidget(parent_), 38 | parent(parent_), 39 | theme(theme_), 40 | label(lab) 41 | { 42 | init(); 43 | } 44 | 45 | ~CairoToolTip() {if (isRunnerActive()) stopRunner();} 46 | 47 | void setLabel(const char* lab) 48 | { 49 | label = lab; 50 | state.store(false, std::memory_order_release); 51 | if (!isRunnerActive()) { 52 | if (!isVisible()) show(); 53 | startRunner(2500); 54 | } 55 | repaint(); 56 | } 57 | 58 | void unset() 59 | { 60 | if (isVisible()) hide(); 61 | parent->repaint(); 62 | } 63 | 64 | protected: 65 | void init() 66 | { 67 | hide(); 68 | state.store(false, std::memory_order_release); 69 | } 70 | 71 | void onCairoDisplay(const CairoGraphicsContext& context) override 72 | { 73 | cairo_t* const cr = context.handle; 74 | cairo_push_group (cr); 75 | 76 | const Size sz = getSize(); 77 | const int w = sz.getWidth(); 78 | const int h = sz.getHeight(); 79 | theme.setCairoColour(cr, theme.idColourBackground); 80 | 81 | cairo_rectangle(cr, 0, 0, w, h); 82 | cairo_fill_preserve(cr); 83 | theme.setCairoColour(cr, theme.idColourFrame); 84 | cairo_stroke(cr); 85 | cairo_text_extents_t extents; 86 | theme.setCairoColour(cr, theme.idColourForground); 87 | cairo_set_font_size (cr, h * 0.24); 88 | cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, 89 | CAIRO_FONT_WEIGHT_BOLD); 90 | cairo_text_extents(cr, label , &extents); 91 | 92 | cairo_move_to (cr, (w-extents.width)*0.5, (h+extents.height)*0.45); 93 | cairo_show_text(cr, label); 94 | cairo_new_path (cr); 95 | cairo_pop_group_to_source (cr); 96 | cairo_paint (cr); 97 | } 98 | 99 | bool run() override 100 | { 101 | if (state.load(std::memory_order_acquire)) { 102 | state.store(false, std::memory_order_release); 103 | unset(); 104 | return false; 105 | } else { 106 | state.store(true, std::memory_order_release); 107 | return true; 108 | } 109 | } 110 | 111 | private: 112 | Widget * const parent; 113 | CairoColourTheme &theme; 114 | const char* label; 115 | std::atomic state; 116 | DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CairoToolTip) 117 | }; 118 | 119 | // ----------------------------------------------------------------------- 120 | 121 | END_NAMESPACE_DGL 122 | 123 | #endif 124 | -------------------------------------------------------------------------------- /plugins/CairoWidgets/CairoTunerWidget.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CairoTunerWidget for the DISTRHO Plugin Framework (DPF) 3 | * 4 | * SPDX-License-Identifier: 0BSD 5 | * 6 | * Copyright (C) 2023 brummer 7 | */ 8 | 9 | #pragma once 10 | 11 | #ifndef CAIROTUNERWIDGET_H 12 | #define CAIROTUNERWIDGET_H 13 | 14 | #include "CairoColourTheme.hpp" 15 | 16 | START_NAMESPACE_DGL 17 | 18 | // ----------------------------------------------------------------------- 19 | 20 | class CairoTunerWidget : public CairoSubWidget 21 | { 22 | public: 23 | 24 | explicit CairoTunerWidget(SubWidget* const parent, CairoColourTheme &theme_) 25 | : CairoSubWidget(parent), 26 | theme(theme_) 27 | { 28 | init(); 29 | } 30 | 31 | explicit CairoTunerWidget(TopLevelWidget* const parent, CairoColourTheme &theme_) 32 | : CairoSubWidget(parent), 33 | theme(theme_) 34 | { 35 | init(); 36 | } 37 | 38 | void setFrequency(float v) 39 | { 40 | detectedFrequency = v; 41 | //repaint(); // use uiIdle() to repaint in intervals 42 | } 43 | 44 | void setRefFreq(float v) 45 | { 46 | refFreq = v; 47 | //repaint(); // use uiIdle() to repaint in intervals 48 | } 49 | 50 | protected: 51 | void init() 52 | { 53 | detectedFrequency = 0.0f; 54 | detectedNote = 0; 55 | detectedOctave = 0; 56 | cent = 0.0f; 57 | collectCents = 0.0f; 58 | collectMilliCents = 0.0f; 59 | fw = 0; 60 | cw = 0; 61 | refFreq = 440.0; 62 | } 63 | 64 | void getNote() 65 | { 66 | if (detectedFrequency > 23.0f && detectedFrequency < 999.0f) 67 | { 68 | 69 | float fdetectedNote = 12.0 * (log2f(detectedFrequency/refFreq) + 4); 70 | float fdetectedNoter = round(fdetectedNote); 71 | int idetectedNote = fdetectedNoter; 72 | int idetectedOctave = round((fdetectedNoter + 3)/12); 73 | const int octsz = sizeof(octave) / sizeof(octave[0]); 74 | if (idetectedOctave < 0 || idetectedOctave >= octsz) { 75 | // just safety, should not happen with current parameters 76 | // (pitch tracker output 23 .. 999 Hz) 77 | idetectedOctave = octsz - 1; 78 | } 79 | 80 | cent = (fdetectedNote-idetectedNote) * 100.0f; 81 | idetectedNote = idetectedNote % 12; 82 | if (idetectedNote < 0) { 83 | idetectedNote += 12; 84 | } 85 | // avoid octave jumps 86 | if (idetectedNote != detectedNote - 12) { 87 | detectedNote = idetectedNote; 88 | detectedOctave = idetectedOctave; 89 | } 90 | } 91 | else 92 | { 93 | detectedFrequency = 0.0f; 94 | detectedNote = 0; 95 | detectedOctave = 0; 96 | cent = 0.0f; 97 | } 98 | 99 | } 100 | 101 | void drawStrobe(cairo_t* const cr, float di, int x, int y, int radius, float w) { 102 | theme.setCairoColour(cr,theme.idColourBackgroundActive); 103 | cairo_set_line_width(cr,6); 104 | int i; 105 | const int d = 4; 106 | for (i=24; i<55; i++) { 107 | double angle = i * 0.01 * 2 * M_PI; 108 | double rx = radius * sin(angle); 109 | double ry = radius * cos(angle); 110 | double length_x = x - rx; 111 | double length_y = y + ry; 112 | double radius_x = x - rx * w ; 113 | double radius_y = y + ry * w ; 114 | if ((int)di < d) { 115 | cairo_move_to(cr, radius_x, radius_y); 116 | cairo_line_to(cr,length_x,length_y); 117 | } 118 | di++; 119 | if (di>8.0) di = 0.0; 120 | } 121 | cairo_stroke_preserve(cr); 122 | } 123 | 124 | void onCairoDisplay(const CairoGraphicsContext& context) override 125 | { 126 | getNote(); 127 | cairo_t* const cr = context.handle; 128 | if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) return; 129 | 130 | const Size sz = getSize(); 131 | const int width = sz.getWidth(); 132 | const int height = sz.getHeight(); 133 | 134 | cairo_push_group (cr); 135 | 136 | theme.setCairoColour(cr,theme.idColourFrame); 137 | cairo_rectangle(cr,0, 0, width, height); 138 | cairo_set_line_width(cr,2); 139 | cairo_stroke(cr); 140 | 141 | cairo_text_extents_t extents; 142 | cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, 143 | CAIRO_FONT_WEIGHT_BOLD); 144 | cairo_set_font_size (cr, height/8.2); 145 | char s[64]; 146 | snprintf(s, 63, "%.2f Hz", detectedFrequency); 147 | cairo_text_extents(cr,s , &extents); 148 | if (std::abs(fw - extents.width) > 1) fw = extents.width; 149 | cairo_move_to (cr, width * 0.45 - fw , height * 0.15 + extents.height); 150 | theme.setCairoColour(cr, theme.idColourForground); 151 | cairo_show_text(cr, s); 152 | snprintf(s, 63, "%.2f C", cent); 153 | cairo_text_extents(cr,s , &extents); 154 | if (std::abs(cw - extents.width) > 1) cw = extents.width; 155 | cairo_move_to (cr, width * 0.4 - cw , height * 0.35 + extents.height); 156 | cairo_show_text(cr, s); 157 | theme.setCairoColour(cr,theme.idColourBackgroundActive); 158 | cairo_set_font_size (cr, height/3.2); 159 | cairo_text_extents(cr, note_sharp[detectedNote], &extents); 160 | cairo_move_to (cr, width * 0.6 , height * 0.6 + extents.height); 161 | if (detectedFrequency > 23.0f && detectedFrequency < 999.0f) { 162 | cairo_show_text(cr, note_sharp[detectedNote]); 163 | cairo_set_font_size (cr, height/5.3); 164 | cairo_show_text(cr, octave[detectedOctave]); 165 | } else { 166 | cairo_move_to (cr, width * 0.705 , height * 0.6 + extents.height); 167 | cairo_show_text(cr, "#"); 168 | } 169 | cairo_new_path (cr); 170 | float sCent = (cent * 0.016); 171 | if (std::fabs(cent) >= 1.0) { 172 | collectCents += sCent; 173 | if (collectCents>8.0) collectCents = 0.0; 174 | else if (collectCents<0.0) collectCents = 8.0; 175 | } 176 | drawStrobe (cr, collectCents, width*0.9, height, height/1.1, 0.9); 177 | 178 | float sMilliCent = (cent * 0.16); 179 | if (std::fabs(cent) >= 0.1) { 180 | collectMilliCents += sMilliCent; 181 | if (collectMilliCents>8.0) collectMilliCents = 0.0; 182 | else if (collectMilliCents<0.0) collectMilliCents = 8.0; 183 | } 184 | drawStrobe (cr, collectMilliCents, width*0.9, height, height/1.25, 0.95); 185 | 186 | theme.boxShadowInset(cr, width, height); 187 | 188 | cairo_pop_group_to_source (cr); 189 | cairo_paint (cr); 190 | } 191 | 192 | private: 193 | CairoColourTheme &theme; 194 | float detectedFrequency; 195 | int detectedNote; 196 | int detectedOctave; 197 | float cent; 198 | float collectCents; 199 | float collectMilliCents; 200 | float refFreq; 201 | uint fw; 202 | uint cw; 203 | static constexpr const char *note_sharp[] = {"A","A#","B","C","C#","D","D#","E","F","F#","G","G#"}; 204 | static constexpr const char *octave[] = {"0","1","2","3","4","5"," "}; 205 | DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CairoTunerWidget) 206 | }; 207 | 208 | constexpr const char *CairoTunerWidget::note_sharp[]; 209 | constexpr const char *CairoTunerWidget::octave[]; 210 | 211 | // ----------------------------------------------------------------------- 212 | 213 | END_NAMESPACE_DGL 214 | 215 | #endif 216 | -------------------------------------------------------------------------------- /plugins/CairoWidgets/CairoValueDisplay.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CairoValueDisplay for the DISTRHO Plugin Framework (DPF) 3 | * 4 | * SPDX-License-Identifier: 0BSD 5 | * 6 | * Copyright (C) 2023 brummer 7 | */ 8 | 9 | #pragma once 10 | 11 | #ifndef CAIROVALUEDISPLAY_H 12 | #define CAIROVALUEDISPLAY_H 13 | 14 | #include 15 | 16 | #include "CairoColourTheme.hpp" 17 | 18 | START_NAMESPACE_DGL 19 | 20 | // ----------------------------------------------------------------------- 21 | 22 | class CairoValueDisplay : public CairoSubWidget 23 | { 24 | public: 25 | 26 | explicit CairoValueDisplay(SubWidget* const parent, CairoColourTheme &theme_, 27 | const char* label_, bool *blocked_, UI *ui, const uint32_t index) 28 | : CairoSubWidget(parent), 29 | theme(theme_), 30 | label(label_), 31 | blocked(blocked_), 32 | setParameterValue([ui] (const uint32_t index, float value) 33 | {ui->setParameterValue(index, value);}), 34 | port(index) 35 | { 36 | init(); 37 | } 38 | 39 | explicit CairoValueDisplay(TopLevelWidget* const parent, CairoColourTheme &theme_, 40 | const char* label_, bool *blocked_, UI *ui, const uint32_t index) 41 | : CairoSubWidget(parent), 42 | theme(theme_), 43 | label(label_), 44 | blocked(blocked_), 45 | setParameterValue([ui] (const uint32_t index, float value) 46 | {ui->setParameterValue(index, value);}), 47 | port(index) 48 | { 49 | init(); 50 | } 51 | 52 | void setValue(float v) 53 | { 54 | value = v; 55 | //repaint(); // use uiIdle() to repaint in intervals 56 | } 57 | 58 | void setAdjustment(float value_, float min_value_, float max_value_, float value_step_) 59 | { 60 | value = value_; 61 | min_value = min_value_; 62 | max_value = max_value_; 63 | value_step = value_step_; 64 | stepper = getStepper(); 65 | //repaint(); // use uiIdle() to repaint in intervals 66 | } 67 | 68 | std::function setUiValue; 69 | 70 | protected: 71 | 72 | void init() 73 | { 74 | value = 0.0f; 75 | min_value = 0.0f; 76 | max_value = 1.0f; 77 | value_step = 0.01f; 78 | posY = 0.0f; 79 | v = 0; 80 | inDrag = false; 81 | prelight = false; 82 | wx = 0; 83 | stepper = getStepper(); 84 | setUiValue = setDummy; 85 | } 86 | 87 | static void setDummy(const uint32_t index, float value) 88 | { 89 | (void)index; 90 | (void)value; 91 | } 92 | 93 | float getStepper() 94 | { 95 | float range = std::fabs(max_value - min_value); 96 | float steps = range / value_step; 97 | return steps * 0.01f; 98 | } 99 | 100 | void onCairoDisplay(const CairoGraphicsContext& context) override 101 | { 102 | cairo_t* const cr = context.handle; 103 | if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) return; 104 | 105 | /** get size for the value display **/ 106 | const Size sz = getSize(); 107 | const int width = sz.getWidth(); 108 | const int height = sz.getHeight(); 109 | 110 | /** draw the value display **/ 111 | cairo_push_group (cr); 112 | 113 | /** show label on the value display**/ 114 | theme.setCairoColour(cr, theme.idColourForgroundNormal); 115 | cairo_text_extents_t extents; 116 | cairo_set_font_size (cr, height * 0.45); 117 | 118 | cairo_text_extents(cr,label , &extents); 119 | cairo_move_to (cr, (width - extents.width) * 0.15, (height + extents.height) * 0.5); 120 | cairo_show_text(cr, label); 121 | cairo_new_path (cr); 122 | 123 | /** show value on the value display**/ 124 | if (prelight) theme.setCairoColourWithAlpha(cr, theme.idColourForground, 0.81); 125 | else theme.setCairoColour(cr, theme.idColourForground); 126 | cairo_select_font_face (cr, "Sans", CAIRO_FONT_SLANT_NORMAL, 127 | CAIRO_FONT_WEIGHT_BOLD); 128 | cairo_set_font_size (cr, height * 0.55); 129 | char s[17]; 130 | const char* format[] = {"%.1f Hz", "%.2f Hz", "%.3f Hz"}; 131 | if (fabs(value_step)>0.99) { 132 | snprintf(s, 16,"%d", (int) value); 133 | } else if (fabs(value_step)>0.09) { 134 | snprintf(s, 16, format[1-1], value); 135 | } else { 136 | snprintf(s, 16, format[2-1], value); 137 | } 138 | cairo_text_extents(cr, s, &extents); 139 | if (std::abs(wx - extents.width) > 1) wx = extents.width; 140 | cairo_move_to (cr, (width - wx) * 0.95, (height + extents.height) * 0.5); 141 | cairo_show_text(cr, s); 142 | cairo_new_path (cr); 143 | 144 | cairo_rectangle(cr, width * 0.6, 0, width, height); 145 | theme.boxShadowInset(cr, width * 0.4, height, width * 0.6, 0, true); 146 | cairo_pop_group_to_source (cr); 147 | cairo_paint (cr); 148 | } 149 | 150 | bool onKeyboard(const KeyboardEvent& event) override { 151 | if (event.press && prelight) 152 | { 153 | int set_value = 0; 154 | if (event.key == 57357) set_value = 1; // UpArrow 155 | else if (event.key == 57359) set_value = -1; // DownArrow 156 | float v = value + (value_step * set_value); 157 | v = MIN(max_value, MAX(min_value, v)); 158 | setValue(v); 159 | setParameterValue(port, value); 160 | setUiValue(port, value); 161 | } 162 | return CairoSubWidget::onKeyboard(event); 163 | } 164 | 165 | bool onMouse(const MouseEvent& event) override 166 | { 167 | if (event.press && (event.button == 1) && contains(event.pos)) // mouse button pressed 168 | { 169 | posY = event.pos.getY(); 170 | inDrag = true; 171 | } 172 | else 173 | { 174 | inDrag = false; 175 | } 176 | 177 | return CairoSubWidget::onMouse(event); 178 | } 179 | 180 | bool onScroll(const ScrollEvent& event) override 181 | { 182 | if (!contains(event.pos)) 183 | return CairoSubWidget::onScroll(event); 184 | 185 | const float set_value = (event.delta.getY() > 0.f) ? 1.f : -1.f; 186 | float v1 = value + (value_step * set_value); 187 | v1 = MIN(max_value, MAX(min_value, v1)); 188 | setValue(v1); 189 | setParameterValue(port, value); 190 | setUiValue(port, value); 191 | 192 | return CairoSubWidget::onScroll(event); 193 | } 194 | 195 | bool onMotion(const MotionEvent& event) override 196 | { 197 | if (inDrag && (std::abs(posY - event.pos.getY()) > 0.f)) 198 | { 199 | const float set_value = (posY - event.pos.getY() > 0.f) ? 1.f : -1.f ; 200 | v += stepper * value_step; 201 | posY = event.pos.getY(); 202 | if (v >= value_step) 203 | { 204 | v = value + (value_step * set_value); 205 | v = MIN(max_value, MAX(min_value, v)); 206 | setValue(v); 207 | setParameterValue(port, value); 208 | setUiValue(port, value); 209 | v = 0; 210 | } 211 | } 212 | if (contains(event.pos)) // enter 213 | { 214 | if (!prelight && !(*blocked)) { 215 | prelight = true; 216 | (*blocked) = true; 217 | //repaint(); // use uiIdle() to repaint in intervals 218 | } 219 | } 220 | else if (prelight && !inDrag) // leave 221 | { 222 | prelight = false; 223 | (*blocked) = false; 224 | //repaint(); // use uiIdle() to repaint in intervals 225 | } 226 | 227 | return CairoSubWidget::onMotion(event); 228 | } 229 | 230 | private: 231 | CairoColourTheme &theme; 232 | const char* label; 233 | bool *blocked; 234 | std::function setParameterValue; 235 | float value; 236 | float min_value; 237 | float max_value; 238 | float value_step; 239 | float posY; 240 | float v; 241 | float stepper; 242 | bool inDrag; 243 | bool prelight; 244 | uint wx; 245 | const uint32_t port; 246 | DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CairoValueDisplay) 247 | }; 248 | 249 | // ----------------------------------------------------------------------- 250 | 251 | END_NAMESPACE_DGL 252 | 253 | #endif 254 | -------------------------------------------------------------------------------- /plugins/CairoWidgets/CairoWidgets.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * CairoWidgets for the DISTRHO Plugin Framework (DPF) 3 | * 4 | * SPDX-License-Identifier: 0BSD 5 | * 6 | * Copyright (C) 2023 brummer 7 | */ 8 | 9 | #pragma once 10 | 11 | #ifndef CAIROWIDGETS_H 12 | #define CAIROWIDGETS_H 13 | 14 | #include "CairoColourTheme.hpp" 15 | #include "CairoButton.hpp" 16 | #include "CairoLed.hpp" 17 | #include "CairoPushButton.hpp" 18 | #include "CairoKnob.hpp" 19 | #include "CairoProgressBar.hpp" 20 | #include "CairoPeekMeter.hpp" 21 | #include "CairoToolTip.hpp" 22 | #include "CairoSwitch.hpp" 23 | #include "CairoTunerWidget.hpp" 24 | #include "CairoValueDisplay.hpp" 25 | #include "CairoLabel.hpp" 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /plugins/StompTuner/DistrhoPluginInfo.h: -------------------------------------------------------------------------------- 1 | /* 2 | * StompTuner audio effect based on DISTRHO Plugin Framework (DPF) 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 license 5 | * 6 | * Copyright (C) 2023 brummer 7 | */ 8 | 9 | #ifndef DISTRHO_PLUGIN_INFO_H 10 | #define DISTRHO_PLUGIN_INFO_H 11 | 12 | #define DISTRHO_PLUGIN_BRAND "brummer" 13 | #define DISTRHO_PLUGIN_NAME "StompTuner" 14 | #define DISTRHO_PLUGIN_URI "urn:brummer10:stomptuner" 15 | #define DISTRHO_PLUGIN_CLAP_ID "com.github.brummer10.stomptuner" 16 | 17 | #define DISTRHO_PLUGIN_HAS_UI 1 18 | #define DISTRHO_UI_USE_NANOVG 0 19 | #define DISTRHO_UI_USE_CAIRO 1 20 | #define DISTRHO_UI_USER_RESIZABLE 1 21 | 22 | #define DISTRHO_PLUGIN_IS_RT_SAFE 1 23 | #define DISTRHO_PLUGIN_NUM_INPUTS 1 24 | #define DISTRHO_PLUGIN_NUM_OUTPUTS 1 25 | #define DISTRHO_PLUGIN_WANT_TIMEPOS 0 26 | #define DISTRHO_PLUGIN_WANT_PROGRAMS 0 27 | #define DISTRHO_PLUGIN_WANT_MIDI_INPUT 0 28 | #define DISTRHO_PLUGIN_WANT_MIDI_OUTPUT 0 29 | #define DISTRHO_PLUGIN_WANT_PARAMETER_VALUE_CHANGE_REQUEST 0 30 | 31 | #define DISTRHO_PLUGIN_LV2_CATEGORY "lv2:AnalyserPlugin" 32 | #define DISTRHO_PLUGIN_VST3_CATEGORIES "Fx|Analyzer|Mono" 33 | #define DISTRHO_PLUGIN_CLAP_FEATURES "audio-effect", "analyser", "mono" 34 | 35 | #define DPF_VST3_DONT_USE_BRAND_ID 1 36 | #define DISTRHO_PLUGIN_BRAND_ID BrTw 37 | #define DISTRHO_PLUGIN_UNIQUE_ID bSTu 38 | 39 | #endif // DISTRHO_PLUGIN_INFO_H 40 | -------------------------------------------------------------------------------- /plugins/StompTuner/Makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # Makefile for DISTRHO Plugins # 3 | # ---------------------------- # 4 | # Created by falkTX, Christopher Arndt, and Patrick Desaulniers 5 | # 6 | 7 | # -------------------------------------------------------------- 8 | # Project name, used for binaries 9 | 10 | NAME = stomptuner 11 | 12 | # -------------------------------------------------------------- 13 | # Plugin types to build 14 | 15 | BUILD_LV2 ?= true 16 | BUILD_VST2 ?= true 17 | BUILD_VST3 ?= true 18 | BUILD_CLAP ?= true 19 | BUILD_JACK ?= true 20 | BUILD_AU ?= true 21 | BUILD_DSSI ?= false 22 | BUILD_LADSPA ?= false 23 | 24 | # -------------------------------------------------------------- 25 | # Files to build 26 | 27 | FILES_DSP = \ 28 | PluginStompTuner.cpp \ 29 | pitch_tracker.cpp 30 | 31 | FILES_UI = \ 32 | UIStompTuner.cpp 33 | 34 | # -------------------------------------------------------------- 35 | # Do some magic 36 | 37 | UI_TYPE = cairo 38 | include ../../dpf/Makefile.plugins.mk 39 | 40 | BUILD_CXX_FLAGS += -pthread -I../zita-resampler-1.1.0 -I../zita-resampler-1.1.0/zita-resampler \ 41 | -I../CairoWidgets -I../Utils $(shell $(PKG_CONFIG) --cflags fftw3f) 42 | LINK_FLAGS += -pthread $(shell $(PKG_CONFIG) --libs fftw3f) 43 | 44 | # -------------------------------------------------------------- 45 | # Enable all selected plugin types 46 | 47 | ifeq ($(BUILD_LV2),true) 48 | ifeq ($(HAVE_CAIRO),true) 49 | TARGETS += lv2_sep 50 | else 51 | TARGETS += lv2_dsp 52 | endif 53 | endif 54 | 55 | ifeq ($(BUILD_VST2),true) 56 | TARGETS += vst2 57 | endif 58 | 59 | ifeq ($(BUILD_VST3),true) 60 | TARGETS += vst3 61 | endif 62 | 63 | ifeq ($(BUILD_CLAP),true) 64 | TARGETS += clap 65 | endif 66 | 67 | ifeq ($(BUILD_AU),true) 68 | TARGETS += au 69 | endif 70 | 71 | ifeq ($(BUILD_JACK),true) 72 | ifeq ($(HAVE_JACK),true) 73 | TARGETS += jack 74 | endif 75 | endif 76 | 77 | ifeq ($(BUILD_DSSI),true) 78 | ifneq ($(MACOS_OR_WINDOWS),true) 79 | ifeq ($(HAVE_CAIRO),true) 80 | ifeq ($(HAVE_LIBLO),true) 81 | TARGETS += dssi 82 | endif 83 | endif 84 | endif 85 | endif 86 | 87 | ifeq ($(BUILD_LADSPA),true) 88 | TARGETS += ladspa 89 | endif 90 | 91 | all: $(TARGETS) 92 | 93 | install: all 94 | ifeq ($(BUILD_DSSI),true) 95 | ifneq ($(MACOS_OR_WINDOWS),true) 96 | ifeq ($(HAVE_CAIRO),true) 97 | ifeq ($(HAVE_LIBLO),true) 98 | @mkdir -p -m755 $(DESTDIR)$(DSSI_DIR) && \ 99 | install -m755 $(TARGET_DIR)/$(NAME)-dssi$(LIB_EXT) $(DESTDIR)$(DSSI_DIR) 100 | endif 101 | endif 102 | endif 103 | endif 104 | ifeq ($(BUILD_LADSPA),true) 105 | @mkdir -p -m755 $(DESTDIR)$(LADSPA_DIR) && \ 106 | install -m755 $(TARGET_DIR)/$(NAME)-ladspa$(LIB_EXT) $(DESTDIR)$(LADSPA_DIR) 107 | endif 108 | ifeq ($(BUILD_VST2),true) 109 | @mkdir -p -m755 $(DESTDIR)$(VST2_DIR) && \ 110 | install -m755 $(TARGET_DIR)/$(NAME)-vst$(LIB_EXT) $(DESTDIR)$(VST2_DIR) 111 | endif 112 | ifeq ($(BUILD_VST3),true) 113 | @mkdir -p -m755 $(DESTDIR)$(VST3_DIR) && \ 114 | cp -r $(TARGET_DIR)/$(NAME).vst3 $(DESTDIR)$(VST3_DIR) 115 | endif 116 | ifeq ($(BUILD_CLAP),true) 117 | @mkdir -p -m755 $(DESTDIR)$(CLAP_DIR) && \ 118 | install -m755 $(TARGET_DIR)/$(NAME).clap $(DESTDIR)$(CLAP_DIR) 119 | endif 120 | ifeq ($(MACOS),true) 121 | ifeq ($(BUILD_AU),true) 122 | @mkdir -p -m755 $(DESTDIR)$(AU_DIR) && \ 123 | install -m755 $(TARGET_DIR)/$(NAME).component $(DESTDIR)$(AU_DIR) 124 | endif 125 | endif 126 | ifeq ($(BUILD_LV2),true) 127 | @mkdir -p -m755 $(DESTDIR)$(LV2_DIR)/$(NAME).lv2 && \ 128 | install -m755 $(TARGET_DIR)/$(NAME).lv2/*$(LIB_EXT) $(DESTDIR)$(LV2_DIR)/$(NAME).lv2 && \ 129 | install -m644 $(TARGET_DIR)/$(NAME).lv2/*.ttl $(DESTDIR)$(LV2_DIR)/$(NAME).lv2 130 | endif 131 | ifeq ($(BUILD_JACK),true) 132 | ifeq ($(HAVE_JACK),true) 133 | @mkdir -p -m755 $(DESTDIR)$(BINDIR) && \ 134 | install -m755 $(TARGET_DIR)/$(NAME)$(APP_EXT) $(DESTDIR)$(BINDIR) 135 | endif 136 | endif 137 | 138 | install-user: all 139 | ifeq ($(BUILD_DSSI),true) 140 | ifneq ($(MACOS_OR_WINDOWS),true) 141 | ifeq ($(HAVE_CAIRO),true) 142 | ifeq ($(HAVE_LIBLO),true) 143 | @mkdir -p -m755 $(USER_DSSI_DIR) && \ 144 | install -m755 $(TARGET_DIR)/$(NAME)-dssi$(LIB_EXT) $(USER_DSSI_DIR) 145 | endif 146 | endif 147 | endif 148 | endif 149 | ifeq ($(BUILD_LADSPA),true) 150 | @mkdir -p -m755 $(USER_LADSPA_DIR) && \ 151 | install -m755 $(TARGET_DIR)/$(NAME)-ladspa$(LIB_EXT) $(USER_LADSPA_DIR) 152 | endif 153 | ifeq ($(BUILD_VST2),true) 154 | @mkdir -p -m755 $(USER_VST2_DIR) && \ 155 | install -m755 $(TARGET_DIR)/$(NAME)-vst$(LIB_EXT) $(USER_VST2_DIR) 156 | endif 157 | ifeq ($(BUILD_VST3),true) 158 | @mkdir -p -m755 $(USER_VST3_DIR) && \ 159 | cp -r $(TARGET_DIR)/$(NAME).vst3 $(USER_VST3_DIR) 160 | endif 161 | ifeq ($(BUILD_CLAP),true) 162 | @mkdir -p -m755 $(USER_CLAP_DIR) && \ 163 | install -m755 $(TARGET_DIR)/$(NAME).clap $(USER_CLAP_DIR) 164 | endif 165 | ifeq ($(MACOS),true) 166 | ifeq ($(BUILD_AU),true) 167 | @mkdir -p -m755 $(USER_AU_DIR) && \ 168 | install -m755 $(TARGET_DIR)/$(NAME).component $(USER_AU_DIR) 169 | endif 170 | endif 171 | ifeq ($(BUILD_LV2),true) 172 | @mkdir -p -m755 $(USER_LV2_DIR)/$(NAME).lv2 && \ 173 | install -m755 $(TARGET_DIR)/$(NAME).lv2/*$(LIB_EXT) $(USER_LV2_DIR)/$(NAME).lv2 && \ 174 | install -m644 $(TARGET_DIR)/$(NAME).lv2/*.ttl $(USER_LV2_DIR)/$(NAME).lv2 175 | endif 176 | ifeq ($(BUILD_JACK),true) 177 | ifeq ($(HAVE_JACK),true) 178 | @mkdir -p -m755 $(HOME)/bin && \ 179 | install -m755 $(TARGET_DIR)/$(NAME)$(APP_EXT) $(HOME)/bin 180 | endif 181 | endif 182 | 183 | # -------------------------------------------------------------- 184 | 185 | .PHONY: all install install-user 186 | -------------------------------------------------------------------------------- /plugins/StompTuner/PluginStompTuner.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * StompTuner audio effect based on DISTRHO Plugin Framework (DPF) 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 license 5 | * 6 | * Copyright (C) 2023 brummer 7 | */ 8 | 9 | #include "resampler.cc" 10 | #include "resampler-table.cc" 11 | #include "gx_resampler.cc" 12 | 13 | #include "PluginStompTuner.hpp" 14 | #include "tuner.cc" 15 | #include "low_high_cut.cc" 16 | 17 | START_NAMESPACE_DISTRHO 18 | 19 | // ----------------------------------------------------------------------- 20 | 21 | PluginStompTuner::PluginStompTuner() 22 | : Plugin(paramCount, 0, 0), 23 | srChanged(false), 24 | needs_ramp_down(false), 25 | needs_ramp_up(false), 26 | bypassed(false), 27 | bypass_(2) 28 | { 29 | lhcut = new low_high_cut::Dsp(); 30 | dsp = new tuner([this] () {this->setFreq();}); 31 | for (unsigned p = 0; p < paramCount; ++p) { 32 | Parameter param; 33 | initParameter(p, param); 34 | setParameterValue(p, param.ranges.def); 35 | } 36 | lhcut->init_static(getSampleRate(), lhcut); 37 | dsp->init(getSampleRate()); 38 | } 39 | 40 | PluginStompTuner::~PluginStompTuner() { 41 | //dsp->activate(false); 42 | if (dsp) delete dsp; 43 | if (lhcut) delete lhcut; 44 | } 45 | 46 | // ----------------------------------------------------------------------- 47 | // Init 48 | 49 | void PluginStompTuner::initParameter(uint32_t index, Parameter& parameter) { 50 | if (index >= paramCount) 51 | return; 52 | 53 | switch (index) { 54 | case dpf_bypass: 55 | parameter.name = "Bypass"; 56 | parameter.shortName = "Bypass"; 57 | parameter.symbol = "dpf_bypass"; 58 | parameter.ranges.min = 0.0f; 59 | parameter.ranges.max = 1.0f; 60 | parameter.ranges.def = 0.0f; 61 | parameter.designation = kParameterDesignationBypass; 62 | parameter.hints = kParameterIsAutomatable|kParameterIsBoolean|kParameterIsInteger; 63 | break; 64 | case FREQ: 65 | parameter.name = "Frequency"; 66 | parameter.shortName = "Freq"; 67 | parameter.symbol = "FREQ"; 68 | parameter.ranges.min = 0.0f; 69 | parameter.ranges.max = 1000.0f; 70 | parameter.hints = kParameterIsAutomatable|kParameterIsOutput; 71 | break; 72 | case REFFREQ: 73 | parameter.name = "Reference Frequency"; 74 | parameter.shortName = "RefFreq"; 75 | parameter.symbol = "REFFREQ"; 76 | parameter.ranges.min = 432.0f; 77 | parameter.ranges.max = 452.0f; 78 | parameter.ranges.def = 440.0f; 79 | parameter.hints = kParameterIsAutomatable; 80 | break; 81 | } 82 | } 83 | 84 | // ----------------------------------------------------------------------- 85 | // Internal data 86 | 87 | void PluginStompTuner::setFreq() { 88 | setOutputParameterValue(FREQ, dsp->get_freq()); 89 | //fprintf(stderr, "Freq %f\n", fParams[FREQ]); 90 | } 91 | 92 | /** 93 | Optional callback to inform the plugin about a sample rate change. 94 | */ 95 | void PluginStompTuner::sampleRateChanged(double newSampleRate) { 96 | fSampleRate = newSampleRate; 97 | srChanged = true; 98 | lhcut->init_static(fSampleRate, lhcut); 99 | dsp->activate(false); 100 | dsp->init(fSampleRate); 101 | srChanged = false; 102 | } 103 | 104 | /** 105 | Get the current value of a parameter. 106 | */ 107 | float PluginStompTuner::getParameterValue(uint32_t index) const { 108 | //fprintf(stderr, "getParameterValue %i %f\n", index,fParams[index]); 109 | return fParams[index]; 110 | } 111 | 112 | /** 113 | Change a parameter value. 114 | */ 115 | void PluginStompTuner::setParameterValue(uint32_t index, float value) { 116 | fParams[index] = value; 117 | //fprintf(stderr, "setParameterValue %i %f\n", index,value); 118 | //dsp->connect(index, value); 119 | } 120 | 121 | void PluginStompTuner::setOutputParameterValue(uint32_t index, float value) 122 | { 123 | fParams[index] = value; 124 | //fprintf(stderr, "setOutputParameterValue %i %f\n", index,value); 125 | } 126 | 127 | // ----------------------------------------------------------------------- 128 | // Process 129 | 130 | void PluginStompTuner::activate() { 131 | // plugin is activated 132 | fSampleRate = getSampleRate(); 133 | ramp_down_step = 32 * (256 * fSampleRate) / 48000; 134 | ramp_up_step = ramp_down_step; 135 | ramp_down = ramp_down_step; 136 | ramp_up = 0.0; 137 | } 138 | 139 | void PluginStompTuner::run(const float** inputs, float** outputs, 140 | uint32_t frames) { 141 | 142 | if (srChanged) return; 143 | // get the left and right audio inputs 144 | const float* const inpL = inputs[0]; 145 | // const float* const inpR = inputs[1]; 146 | 147 | // get the left and right audio outputs 148 | float* const outL = outputs[0]; 149 | // float* const outR = outputs[1]; 150 | 151 | // do inplace processing on default 152 | if(outL != inpL) 153 | memcpy(outL, inpL, frames*sizeof(float)); 154 | 155 | float buf0[frames]; 156 | float buf[frames]; 157 | memcpy(buf, inpL, frames*sizeof(float)); 158 | // check if bypass is pressed 159 | if (bypass_ != static_cast(fParams[dpf_bypass])) { 160 | bypass_ = static_cast(fParams[dpf_bypass]); 161 | if (bypass_) { 162 | needs_ramp_down = true; 163 | needs_ramp_up = false; 164 | } else { 165 | needs_ramp_down = false; 166 | needs_ramp_up = true; 167 | bypassed = false; 168 | } 169 | } 170 | 171 | if (needs_ramp_down || needs_ramp_up) { 172 | memcpy(buf0, inpL, frames*sizeof(float)); 173 | } 174 | if (!bypassed) { 175 | lhcut->compute_static(frames, buf, buf, lhcut); 176 | dsp->feed_tuner(frames, buf); 177 | } 178 | // check if ramping is needed 179 | if (needs_ramp_down) { 180 | float fade = 0; 181 | for (uint32_t i=0; i= 0.0) { 183 | --ramp_down; 184 | } 185 | fade = MAX(0.0,ramp_down) /ramp_down_step ; 186 | outL[i] = outL[i] * fade + buf0[i] * (1.0 - fade); 187 | } 188 | if (ramp_down <= 0.0) { 189 | // when ramped down, clear buffer from dsp 190 | needs_ramp_down = false; 191 | bypassed = true; 192 | setOutputParameterValue(FREQ, 0.0); 193 | ramp_down = ramp_down_step; 194 | ramp_up = 0.0; 195 | } else { 196 | ramp_up = ramp_down; 197 | } 198 | } else if (needs_ramp_up) { 199 | float fade = 0; 200 | for (uint32_t i=0; i= ramp_up_step) { 208 | needs_ramp_up = false; 209 | ramp_up = 0.0; 210 | ramp_down = ramp_down_step; 211 | } else { 212 | ramp_down = ramp_up; 213 | } 214 | } 215 | } 216 | 217 | // ----------------------------------------------------------------------- 218 | 219 | Plugin* createPlugin() { 220 | return new PluginStompTuner(); 221 | } 222 | 223 | // ----------------------------------------------------------------------- 224 | 225 | END_NAMESPACE_DISTRHO 226 | -------------------------------------------------------------------------------- /plugins/StompTuner/PluginStompTuner.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * StompTuner audio effect based on DISTRHO Plugin Framework (DPF) 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 license 5 | * 6 | * Copyright (C) 2023 brummer 7 | */ 8 | 9 | #ifndef PLUGIN_STOMPTUNER_H 10 | #define PLUGIN_STOMPTUNER_H 11 | 12 | #include "DistrhoPlugin.hpp" 13 | #include 14 | 15 | #include "zita-resampler/resampler.h" 16 | #include "low_high_cut.h" 17 | #include "tuner.hpp" 18 | 19 | START_NAMESPACE_DISTRHO 20 | 21 | #ifndef MIN 22 | #define MIN(a,b) ( (a) < (b) ? (a) : (b) ) 23 | #endif 24 | 25 | #ifndef MAX 26 | #define MAX(a,b) ( (a) > (b) ? (a) : (b) ) 27 | #endif 28 | 29 | #ifndef CLAMP 30 | #define CLAMP(v, min, max) (MIN((max), MAX((min), (v)))) 31 | #endif 32 | 33 | #ifndef DB_CO 34 | #define DB_CO(g) ((g) > -90.0f ? powf(10.0f, (g) * 0.05f) : 0.0f) 35 | #endif 36 | 37 | // ----------------------------------------------------------------------- 38 | 39 | class PluginStompTuner : public Plugin { 40 | public: 41 | enum Parameters { 42 | dpf_bypass = 0, 43 | FREQ, 44 | REFFREQ, 45 | paramCount 46 | }; 47 | 48 | PluginStompTuner(); 49 | 50 | ~PluginStompTuner(); 51 | 52 | protected: 53 | // ------------------------------------------------------------------- 54 | // Information 55 | 56 | const char* getLabel() const noexcept override { 57 | return "StompTuner"; 58 | } 59 | 60 | const char* getDescription() const override { 61 | return R"( . . . . )" ; 62 | } 63 | 64 | const char* getMaker() const noexcept override { 65 | return "brummer"; 66 | } 67 | 68 | const char* getHomePage() const override { 69 | return "https://github.com/brummer10/StompTuner"; 70 | } 71 | 72 | const char* getLicense() const noexcept override { 73 | return "https://spdx.org/licenses/GPL-2.0-or-later"; 74 | } 75 | 76 | uint32_t getVersion() const noexcept override { 77 | return d_version(0, 1, 6); 78 | } 79 | 80 | // Go to: 81 | // 82 | // http://service.steinberg.de/databases/plugin.nsf/plugIn 83 | // 84 | // Get a proper plugin UID and fill it in here! 85 | int64_t getUniqueId() const noexcept override { 86 | return d_cconst('S', 'T', 'u', 'n'); 87 | } 88 | 89 | // ------------------------------------------------------------------- 90 | // Init 91 | 92 | void initParameter(uint32_t index, Parameter& parameter) override; 93 | 94 | // ------------------------------------------------------------------- 95 | // Internal data 96 | 97 | float getParameterValue(uint32_t index) const override; 98 | void setParameterValue(uint32_t index, float value) override; 99 | void setFreq(); 100 | void setOutputParameterValue(uint32_t index, float value); 101 | 102 | // ------------------------------------------------------------------- 103 | // Optional 104 | 105 | // Optional callback to inform the plugin about a sample rate change. 106 | void sampleRateChanged(double newSampleRate) override; 107 | 108 | // ------------------------------------------------------------------- 109 | // Process 110 | 111 | void activate() override; 112 | 113 | void run(const float**, float** outputs, uint32_t frames) override; 114 | 115 | 116 | // ------------------------------------------------------------------- 117 | 118 | private: 119 | float fParams[paramCount]; 120 | double fSampleRate; 121 | bool srChanged; 122 | // bypass ramping 123 | bool needs_ramp_down; 124 | bool needs_ramp_up; 125 | float ramp_down; 126 | float ramp_up; 127 | float ramp_up_step; 128 | float ramp_down_step; 129 | bool bypassed; 130 | uint32_t bypass_; 131 | // pointer to dsp class 132 | low_high_cut::Dsp* lhcut; 133 | tuner* dsp; 134 | 135 | DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginStompTuner) 136 | }; 137 | 138 | // ----------------------------------------------------------------------- 139 | 140 | END_NAMESPACE_DISTRHO 141 | 142 | #endif // #ifndef PLUGIN_STOMPTUNER_H 143 | -------------------------------------------------------------------------------- /plugins/StompTuner/UIStompTuner.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * StompTuner audio effect based on DISTRHO Plugin Framework (DPF) 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 license 5 | * 6 | * Copyright (C) 2023 brummer 7 | */ 8 | 9 | #include "UIStompTuner.hpp" 10 | #include "Window.hpp" 11 | #include "leder.c" 12 | 13 | START_NAMESPACE_DISTRHO 14 | 15 | // ----------------------------------------------------------------------- 16 | // Init / Deinit 17 | 18 | UIStompTuner::UIStompTuner() 19 | : UI(285, 400, true), theme(), fResizeHandle(this) { 20 | kInitialHeight = 400; 21 | kInitialWidth = 285; 22 | blocked = false; 23 | sizeGroup = new UiSizeGroup(kInitialWidth, kInitialHeight); 24 | 25 | theme.setIdColour(theme.idColourBackgroundActive, 0.0, 0.898, 0.647, 1.0); 26 | theme.setIdColour(theme.idColourBackground, 0.17, 0.306, 0.388, 1.0); 27 | theme.setIdColour(theme.idColourForground, 0.0, 0.898, 0.647, 0.6); 28 | theme.setIdColour(theme.idColourForgroundNormal, 0.424, 0.455, 0.494, 1.0); 29 | texture = theme.cairo_image_surface_create_from_stream (leder_png); 30 | 31 | tunerDisplay = new CairoTunerWidget(this, theme); 32 | sizeGroup->addToSizeGroup(tunerDisplay, 32, 50, 220, 140); 33 | 34 | refFrequency = new CairoValueDisplay(this, theme, "Reference Pitch", &blocked, 35 | dynamic_cast(this), PluginStompTuner::REFFREQ); 36 | refFrequency->setAdjustment(440.0, 432.0, 452.0, 0.1); 37 | refFrequency->setUiValue = [this] (const uint32_t index, float value) 38 | {this->setRefFreq(index, value);}; 39 | sizeGroup->addToSizeGroup(refFrequency, 30, 200, 220, 30); 40 | 41 | bypassLed = new CairoLed(this, theme); 42 | sizeGroup->addToSizeGroup(bypassLed, 132, 20, 20, 20); 43 | 44 | bypassSwitch = new CairoPushButton(this, theme, &blocked, bypassLed, 45 | dynamic_cast(this), "StompTuner", PluginStompTuner::dpf_bypass); 46 | sizeGroup->addToSizeGroup(bypassSwitch, 30, 240, 225, 130); 47 | 48 | if (isResizable()) fResizeHandle.hide(); 49 | } 50 | 51 | UIStompTuner::~UIStompTuner() { 52 | cairo_surface_destroy(texture); 53 | } 54 | 55 | // ----------------------------------------------------------------------- 56 | // DSP/Plugin callbacks 57 | 58 | /** 59 | A parameter has changed on the plugin side. 60 | This is called by the host to inform the UI about parameter changes. 61 | */ 62 | void UIStompTuner::parameterChanged(uint32_t index, float value) { 63 | // fprintf(stderr, "index %i, value %f\n", index, value); 64 | switch (index) { 65 | case PluginStompTuner::dpf_bypass: 66 | bypassSwitch->setValue(value); 67 | bypassLed->setValue(value); 68 | break; 69 | case PluginStompTuner::FREQ: 70 | tunerDisplay->setFrequency(value); 71 | break; 72 | case PluginStompTuner::REFFREQ: 73 | tunerDisplay->setRefFreq(value); 74 | break; 75 | } 76 | } 77 | 78 | void UIStompTuner::setRefFreq(uint32_t, float value) { 79 | tunerDisplay->setRefFreq(value); 80 | } 81 | 82 | /** 83 | Optional callback to inform the UI about a sample rate change on the plugin side. 84 | */ 85 | void UIStompTuner::sampleRateChanged(double newSampleRate) { 86 | (void) newSampleRate ; 87 | } 88 | 89 | // ----------------------------------------------------------------------- 90 | // Optional UI callbacks 91 | 92 | /** 93 | Idle callback. 94 | This function is called at regular intervals. 95 | */ 96 | void UIStompTuner::uiIdle() { 97 | repaint(); 98 | } 99 | 100 | /** 101 | Window reshape function, called when the parent window is resized. 102 | */ 103 | void UIStompTuner::uiReshape(uint width, uint height) { 104 | (void)width; 105 | (void)height; 106 | } 107 | 108 | // ----------------------------------------------------------------------- 109 | // Widget callbacks 110 | 111 | /** 112 | A function called to draw the view contents. 113 | */ 114 | void UIStompTuner::onCairoDisplay(const CairoGraphicsContext& context) { 115 | cairo_t* const cr = context.handle; 116 | if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) return; 117 | int width = getWidth(); 118 | int height = getHeight(); 119 | const float scaleH = sizeGroup->getScaleHFactor(); 120 | const float scaleW = sizeGroup->getScaleWFactor(); 121 | 122 | cairo_push_group (cr); 123 | 124 | theme.setCairoColour(cr, theme.idColourBackground); 125 | cairo_paint(cr); 126 | 127 | cairo_pattern_t *pat = cairo_pattern_create_for_surface(texture); 128 | cairo_pattern_set_extend (pat, CAIRO_EXTEND_REPEAT); 129 | cairo_set_source(cr, pat); 130 | cairo_paint (cr); 131 | cairo_pattern_destroy (pat); 132 | 133 | theme.boxShadow(cr, width, height, 25, 25); 134 | 135 | cairo_rectangle(cr, 25 * scaleW, 25 * scaleH, width - (50 * scaleW), height - (50 * scaleH)); 136 | theme.setCairoColour(cr, theme.idColourBackgroundNormal); 137 | cairo_fill_preserve(cr); 138 | theme.setCairoColour(cr, theme.idColourBoxShadow); 139 | cairo_set_line_width(cr,8); 140 | cairo_stroke_preserve(cr); 141 | theme.setCairoColour(cr, theme.idColourBackground, 1.0f); 142 | cairo_set_line_width(cr,1); 143 | cairo_stroke(cr); 144 | 145 | cairo_pop_group_to_source (cr); 146 | cairo_paint (cr); 147 | } 148 | 149 | void UIStompTuner::onResize(const ResizeEvent& ev) 150 | { 151 | UI::onResize(ev); 152 | sizeGroup->resizeAspectSizeGroup(ev.size.getWidth(), ev.size.getHeight()); 153 | } 154 | 155 | UI* createUI() { 156 | return new UIStompTuner; 157 | } 158 | 159 | // ----------------------------------------------------------------------- 160 | 161 | END_NAMESPACE_DISTRHO 162 | -------------------------------------------------------------------------------- /plugins/StompTuner/UIStompTuner.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * StompTuner audio effect based on DISTRHO Plugin Framework (DPF) 3 | * 4 | * SPDX-License-Identifier: GPL-2.0 license 5 | * 6 | * Copyright (C) 2023 brummer 7 | */ 8 | 9 | #ifndef UI_STOMPTUNER_H 10 | #define UI_STOMPTUNER_H 11 | 12 | #include 13 | #include 14 | #include "DistrhoUI.hpp" 15 | #include "PluginStompTuner.hpp" 16 | #include "Cairo.hpp" 17 | #include "CairoWidgets.hpp" 18 | #include "UiSizeGroup.hpp" 19 | #include "ResizeHandle.hpp" 20 | 21 | START_NAMESPACE_DISTRHO 22 | 23 | /** 24 | The main UI class 25 | */ 26 | 27 | class UIStompTuner : public UI 28 | { 29 | public: 30 | UIStompTuner(); 31 | ~UIStompTuner(); 32 | 33 | protected: 34 | void parameterChanged(uint32_t, float value) override; 35 | void setRefFreq(uint32_t, float value); 36 | void sampleRateChanged(double newSampleRate) override; 37 | 38 | void uiIdle() override; 39 | void uiReshape(uint width, uint height) override; 40 | 41 | void onCairoDisplay(const CairoGraphicsContext& context) override; 42 | 43 | void onResize(const ResizeEvent& ev) override; 44 | 45 | private: 46 | CairoColourTheme theme; 47 | cairo_surface_t *texture; 48 | int kInitialHeight; 49 | int kInitialWidth; 50 | bool blocked; 51 | ResizeHandle fResizeHandle; 52 | ScopedPointer sizeGroup; 53 | 54 | ScopedPointer tunerDisplay; 55 | ScopedPointer refFrequency; 56 | ScopedPointer bypassSwitch; 57 | ScopedPointer bypassLed; 58 | DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(UIStompTuner) 59 | }; 60 | 61 | END_NAMESPACE_DISTRHO 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /plugins/StompTuner/low_high_cut.cc: -------------------------------------------------------------------------------- 1 | // generated from file '../src/LV2/faust/low_high_cut.dsp' by dsp2cc: 2 | // Code generated with Faust (https://faust.grame.fr) 3 | 4 | #include 5 | 6 | template inline T mydsp_faustpower2_f(T x) {return (x * x);} 7 | #define always_inline inline __attribute__((always_inline)) 8 | 9 | namespace low_high_cut { 10 | 11 | Dsp::Dsp() { 12 | } 13 | 14 | Dsp::~Dsp() { 15 | } 16 | 17 | inline void Dsp::clear_state_f() 18 | { 19 | for (int l0 = 0; (l0 < 2); l0 = (l0 + 1)) iVec0[l0] = 0; 20 | for (int l1 = 0; (l1 < 2); l1 = (l1 + 1)) fRec4[l1] = 0.0; 21 | for (int l2 = 0; (l2 < 2); l2 = (l2 + 1)) fVec1[l2] = 0.0; 22 | for (int l3 = 0; (l3 < 2); l3 = (l3 + 1)) fRec3[l3] = 0.0; 23 | for (int l4 = 0; (l4 < 2); l4 = (l4 + 1)) fRec2[l4] = 0.0; 24 | for (int l5 = 0; (l5 < 3); l5 = (l5 + 1)) fRec1[l5] = 0.0; 25 | for (int l6 = 0; (l6 < 3); l6 = (l6 + 1)) fRec0[l6] = 0.0; 26 | } 27 | 28 | void Dsp::clear_state_f_static(Dsp *p) 29 | { 30 | p->clear_state_f(); 31 | } 32 | 33 | inline void Dsp::init(uint32_t sample_rate) 34 | { 35 | fSampleRate = sample_rate; 36 | fConst0 = std::min(192000.0, std::max(1.0, double(fSampleRate))); 37 | fConst1 = std::tan((3138.4510609362032 / fConst0)); 38 | fConst2 = (1.0 / fConst1); 39 | fConst3 = (1.0 / (((fConst2 + 0.76536686473017945) / fConst1) + 1.0)); 40 | fConst4 = (1.0 / (((fConst2 + 1.8477590650225735) / fConst1) + 1.0)); 41 | fConst5 = (72.256631032565238 / fConst0); 42 | fConst6 = (1.0 / (fConst5 + 1.0)); 43 | fConst7 = (1.0 - fConst5); 44 | fConst8 = (((fConst2 + -1.8477590650225735) / fConst1) + 1.0); 45 | fConst9 = (2.0 * (1.0 - (1.0 / mydsp_faustpower2_f(fConst1)))); 46 | fConst10 = (((fConst2 + -0.76536686473017945) / fConst1) + 1.0); 47 | clear_state_f(); 48 | } 49 | 50 | void Dsp::init_static(uint32_t sample_rate, Dsp *p) 51 | { 52 | p->init(sample_rate); 53 | } 54 | 55 | void always_inline Dsp::compute(int count, float *input0, float *output0) 56 | { 57 | for (int i = 0; (i < count); i = (i + 1)) { 58 | iVec0[0] = 1; 59 | fRec4[0] = ((9.9999999999999995e-21 * double((1 - iVec0[1]))) - fRec4[1]); 60 | double fTemp0 = (double(input0[i]) + fRec4[0]); 61 | fVec1[0] = fTemp0; 62 | fRec3[0] = (fConst6 * ((fTemp0 - fVec1[1]) + (fConst7 * fRec3[1]))); 63 | fRec2[0] = (fConst6 * ((fRec3[0] - fRec3[1]) + (fConst7 * fRec2[1]))); 64 | fRec1[0] = (fRec2[0] - (fConst4 * ((fConst8 * fRec1[2]) + (fConst9 * fRec1[1])))); 65 | fRec0[0] = ((fConst4 * (fRec1[2] + (fRec1[0] + (2.0 * fRec1[1])))) - (fConst3 * ((fConst10 * fRec0[2]) + (fConst9 * fRec0[1])))); 66 | output0[i] = float((fConst3 * (fRec0[2] + (fRec0[0] + (2.0 * fRec0[1]))))); 67 | iVec0[1] = iVec0[0]; 68 | fRec4[1] = fRec4[0]; 69 | fVec1[1] = fVec1[0]; 70 | fRec3[1] = fRec3[0]; 71 | fRec2[1] = fRec2[0]; 72 | fRec1[2] = fRec1[1]; 73 | fRec1[1] = fRec1[0]; 74 | fRec0[2] = fRec0[1]; 75 | fRec0[1] = fRec0[0]; 76 | } 77 | } 78 | 79 | void Dsp::compute_static(int count, float *input0, float *output0, Dsp *p) 80 | { 81 | p->compute(count, input0, output0); 82 | } 83 | 84 | 85 | void Dsp::del_instance(Dsp *p) 86 | { 87 | delete p; 88 | } 89 | 90 | } 91 | 92 | -------------------------------------------------------------------------------- /plugins/StompTuner/low_high_cut.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #pragma once 4 | 5 | #ifndef LOW_HIGH_CUT_H 6 | #define LOW_HIGH_CUT_H 7 | 8 | namespace low_high_cut { 9 | 10 | class Dsp { 11 | private: 12 | uint32_t fSampleRate; 13 | double fConst0; 14 | double fConst1; 15 | double fConst2; 16 | double fConst3; 17 | double fConst4; 18 | double fConst5; 19 | double fConst6; 20 | int iVec0[2]; 21 | double fRec4[2]; 22 | double fVec1[2]; 23 | double fConst7; 24 | double fRec3[2]; 25 | double fRec2[2]; 26 | double fConst8; 27 | double fConst9; 28 | double fRec1[3]; 29 | double fConst10; 30 | double fRec0[3]; 31 | 32 | void clear_state_f(); 33 | void init(uint32_t sample_rate); 34 | void compute(int count, float *input0, float *output0); 35 | 36 | public: 37 | Dsp(); 38 | ~Dsp(); 39 | static void clear_state_f_static(Dsp*); 40 | static void init_static(uint32_t sample_rate, Dsp*); 41 | static void compute_static(int count, float *input0, float *output0, Dsp*); 42 | static void del_instance(Dsp *p); 43 | }; 44 | 45 | } 46 | 47 | #endif 48 | 49 | -------------------------------------------------------------------------------- /plugins/StompTuner/pitch_tracker.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023, 2010 Hermann Meyer 3 | * Copyright (C) 2009, 2010 Hermann Meyer, James Warden, Andreas Degert 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program; if not, write to the Free Software 17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | * -------------------------------------------------------------------------- 19 | */ 20 | 21 | #include "pitch_tracker.h" 22 | 23 | /**************************************************************** 24 | ** Pitch Tracker 25 | ** 26 | ** some code and ideas taken from K4Guitune (William Spinelli) 27 | ** changed to NSDF method (some code from tartini / Philip McLeod) 28 | ** 29 | */ 30 | 31 | 32 | // downsampling factor 33 | static const int DOWNSAMPLE = 2; 34 | static const float SIGNAL_THRESHOLD_ON = 0.001; 35 | static const float SIGNAL_THRESHOLD_OFF = 0.0009; 36 | static const float TRACKER_PERIOD = 0.1; 37 | // The size of the read buffer 38 | static const int FFT_SIZE = 2048; 39 | 40 | ///////////////////////// INTERNAL WORKER CLASS ////////////////////// 41 | 42 | PitchTrackerWorker::PitchTrackerWorker() 43 | : _execute(false) { 44 | } 45 | 46 | PitchTrackerWorker::~PitchTrackerWorker() { 47 | if( _execute.load(std::memory_order_acquire) ) { 48 | stop(); 49 | }; 50 | } 51 | 52 | void PitchTrackerWorker::stop() { 53 | _execute.store(false, std::memory_order_release); 54 | if (_thd.joinable()) { 55 | cv.notify_one(); 56 | _thd.join(); 57 | } 58 | } 59 | 60 | void PitchTrackerWorker::start(PitchTracker *pt) { 61 | if( _execute.load(std::memory_order_acquire) ) { 62 | stop(); 63 | }; 64 | _execute.store(true, std::memory_order_release); 65 | _thd = std::thread([this, pt]() { 66 | while (_execute.load(std::memory_order_acquire)) { 67 | std::unique_lock lk(m); 68 | pt->busy.store(false, std::memory_order_release); 69 | // wait for signal from dsp that work is to do 70 | cv.wait(lk); 71 | //do work 72 | if (_execute.load(std::memory_order_acquire)) { 73 | pt->static_run(pt); 74 | } 75 | } 76 | // when done 77 | }); 78 | } 79 | 80 | bool PitchTrackerWorker::is_running() const noexcept { 81 | return ( _execute.load(std::memory_order_acquire) && 82 | _thd.joinable() ); 83 | } 84 | 85 | 86 | ///////////////////////// PitchTracker Class //////////////////////// 87 | 88 | 89 | void *PitchTracker::static_run(void *p) { 90 | (reinterpret_cast(p))->run(); 91 | return NULL; 92 | } 93 | 94 | PitchTracker::PitchTracker(std::functionsetFreq_) 95 | : new_freq(setFreq_), 96 | error(false), 97 | tick(0), 98 | resamp(), 99 | m_sampleRate(), 100 | fixed_sampleRate(41000), 101 | m_freq(-1), 102 | signal_threshold_on(SIGNAL_THRESHOLD_ON), 103 | signal_threshold_off(SIGNAL_THRESHOLD_OFF), 104 | tracker_period(TRACKER_PERIOD), 105 | m_buffersize(), 106 | m_fftSize(), 107 | m_buffer(new float[FFT_SIZE]), 108 | m_bufferIndex(0), 109 | m_input(new float[FFT_SIZE]), 110 | m_audioLevel(false), 111 | m_fftwPlanFFT(0), 112 | m_fftwPlanIFFT(0) { 113 | busy.store(false, std::memory_order_release); 114 | const int size = FFT_SIZE + (FFT_SIZE+1) / 2; 115 | m_fftwBufferTime = reinterpret_cast 116 | (fftwf_malloc(size * sizeof(*m_fftwBufferTime))); 117 | m_fftwBufferFreq = reinterpret_cast 118 | (fftwf_malloc(size * sizeof(*m_fftwBufferFreq))); 119 | 120 | memset(m_buffer, 0, FFT_SIZE * sizeof(*m_buffer)); 121 | memset(m_input, 0, FFT_SIZE * sizeof(*m_input)); 122 | memset(m_fftwBufferTime, 0, size * sizeof(*m_fftwBufferTime)); 123 | memset(m_fftwBufferFreq, 0, size * sizeof(*m_fftwBufferFreq)); 124 | 125 | worker.start(this); 126 | if (!m_buffer || !m_input || !m_fftwBufferTime || !m_fftwBufferFreq) { 127 | error = true; 128 | } 129 | } 130 | 131 | 132 | PitchTracker::~PitchTracker() { 133 | worker.stop(); 134 | fftwf_destroy_plan(m_fftwPlanFFT); 135 | fftwf_destroy_plan(m_fftwPlanIFFT); 136 | fftwf_free(m_fftwBufferTime); 137 | fftwf_free(m_fftwBufferFreq); 138 | delete[] m_input; 139 | delete[] m_buffer; 140 | } 141 | 142 | void PitchTracker::set_threshold(float v) { 143 | signal_threshold_on = v; 144 | signal_threshold_off = v*0.9; 145 | } 146 | 147 | void PitchTracker::set_fast_note_detection(bool v) { 148 | if (v) { 149 | signal_threshold_on = SIGNAL_THRESHOLD_ON * 5; 150 | signal_threshold_off = SIGNAL_THRESHOLD_OFF * 5; 151 | tracker_period = TRACKER_PERIOD / 10; 152 | } else { 153 | signal_threshold_on = SIGNAL_THRESHOLD_ON; 154 | signal_threshold_off = SIGNAL_THRESHOLD_OFF; 155 | tracker_period = TRACKER_PERIOD; 156 | } 157 | } 158 | 159 | bool PitchTracker::setParameters(int sampleRate, int buffersize) { 160 | assert(buffersize <= FFT_SIZE); 161 | 162 | if (error) { 163 | return false; 164 | } 165 | m_sampleRate = fixed_sampleRate / DOWNSAMPLE; 166 | resamp.setup(sampleRate, m_sampleRate, 1, 16); // 16 == least quality 167 | 168 | if (m_buffersize != buffersize) { 169 | m_buffersize = buffersize; 170 | m_fftSize = m_buffersize + (m_buffersize+1) / 2; 171 | fftwf_destroy_plan(m_fftwPlanFFT); 172 | fftwf_destroy_plan(m_fftwPlanIFFT); 173 | m_fftwPlanFFT = fftwf_plan_r2r_1d( 174 | m_fftSize, m_fftwBufferTime, m_fftwBufferFreq, 175 | FFTW_R2HC, FFTW_ESTIMATE); 176 | m_fftwPlanIFFT = fftwf_plan_r2r_1d( 177 | m_fftSize, m_fftwBufferFreq, m_fftwBufferTime, 178 | FFTW_HC2R, FFTW_ESTIMATE); 179 | } 180 | 181 | if (!m_fftwPlanFFT || !m_fftwPlanIFFT) { 182 | error = true; 183 | return false; 184 | } 185 | 186 | return !error; 187 | } 188 | 189 | void PitchTracker::init(unsigned int samplerate) { 190 | setParameters(samplerate, FFT_SIZE); 191 | } 192 | 193 | void PitchTracker::reset() { 194 | tick = 0; 195 | m_bufferIndex = 0; 196 | resamp.reset(); 197 | m_freq = -1; 198 | } 199 | 200 | void PitchTracker::add(int count, float* input) { 201 | if (error) { 202 | return; 203 | } 204 | resamp.inp_count = count; 205 | resamp.inp_data = input; 206 | for (;;) { 207 | resamp.out_data = &m_buffer[m_bufferIndex]; 208 | int n = FFT_SIZE - m_bufferIndex; 209 | resamp.out_count = n; 210 | resamp.process(); 211 | n -= resamp.out_count; // n := number of output samples 212 | if (!n) { // all soaked up by filter 213 | return; 214 | } 215 | m_bufferIndex = (m_bufferIndex + n) % FFT_SIZE; 216 | if (resamp.inp_count == 0) { 217 | break; 218 | } 219 | } 220 | if (++tick * count >= m_sampleRate * DOWNSAMPLE * tracker_period) { 221 | if (busy.load(std::memory_order_acquire)) { 222 | return; 223 | } 224 | busy.store(true, std::memory_order_release); 225 | tick = 0; 226 | copy(); 227 | worker.cv.notify_one(); 228 | } 229 | } 230 | 231 | void PitchTracker::copy() { 232 | int start = (FFT_SIZE + m_bufferIndex - m_buffersize) % FFT_SIZE; 233 | int end = (FFT_SIZE + m_bufferIndex) % FFT_SIZE; 234 | int cnt = 0; 235 | if (start >= end) { 236 | cnt = FFT_SIZE - start; 237 | memcpy(m_input, &m_buffer[start], cnt * sizeof(*m_input)); 238 | start = 0; 239 | } 240 | memcpy(&m_input[cnt], &m_buffer[start], (end - start) * sizeof(*m_input)); 241 | } 242 | 243 | inline float sq(float x) { 244 | return x * x; 245 | } 246 | 247 | inline void parabolaTurningPoint(float y_1, float y0, float y1, float xOffset, float *x) { 248 | float yTop = y_1 - y1; 249 | float yBottom = y1 + y_1 - 2 * y0; 250 | if (yBottom != 0.0) { 251 | *x = xOffset + yTop / (2 * yBottom); 252 | } else { 253 | *x = xOffset; 254 | } 255 | } 256 | 257 | static int findMaxima(float *input, int len, int *maxPositions, int *length, int maxLen) { 258 | int pos = 0; 259 | int curMaxPos = 0; 260 | int overallMaxIndex = 0; 261 | 262 | while (pos < (len-1)/3 && input[pos] > 0.0) { 263 | pos += 1; // find the first negitive zero crossing 264 | } 265 | while (pos < len-1 && input[pos] <= 0.0) { 266 | pos += 1; // loop over all the values below zero 267 | } 268 | if (pos == 0) { 269 | pos = 1; // can happen if output[0] is NAN 270 | } 271 | while (pos < len-1) { 272 | if (input[pos] > input[pos-1] && input[pos] >= input[pos+1]) { // a local maxima 273 | if (curMaxPos == 0) { 274 | curMaxPos = pos; // the first maxima (between zero crossings) 275 | } else if (input[pos] > input[curMaxPos]) { 276 | curMaxPos = pos; // a higher maxima (between the zero crossings) 277 | } 278 | } 279 | pos += 1; 280 | if (pos < len-1 && input[pos] <= 0.0) { // a negative zero crossing 281 | if (curMaxPos > 0) { // if there was a maximum 282 | maxPositions[*length] = curMaxPos; // add it to the vector of maxima 283 | *length += 1; 284 | if (overallMaxIndex == 0) { 285 | overallMaxIndex = curMaxPos; 286 | } else if (input[curMaxPos] > input[overallMaxIndex]) { 287 | overallMaxIndex = curMaxPos; 288 | } 289 | if (*length >= maxLen) { 290 | return overallMaxIndex; 291 | } 292 | curMaxPos = 0; // clear the maximum position, so we start looking for a new ones 293 | } 294 | while (pos < len-1 && input[pos] <= 0.0) { 295 | pos += 1; // loop over all the values below zero 296 | } 297 | } 298 | } 299 | if (curMaxPos > 0) { // if there was a maximum in the last part 300 | maxPositions[*length] = curMaxPos; // add it to the vector of maxima 301 | *length += 1; 302 | if (overallMaxIndex == 0) { 303 | overallMaxIndex = curMaxPos; 304 | } else if (input[curMaxPos] > input[overallMaxIndex]) { 305 | overallMaxIndex = curMaxPos; 306 | } 307 | curMaxPos = 0; // clear the maximum position, so we start looking for a new ones 308 | } 309 | return overallMaxIndex; 310 | } 311 | 312 | static int findsubMaximum(float *input, int len, float threshold) { 313 | int indices[10]; 314 | int length = 0; 315 | int overallMaxIndex = findMaxima(input, len, indices, &length, 10); 316 | if (length == 0) { 317 | return -1; 318 | } 319 | threshold += (1.0 - threshold) * (1.0 - input[overallMaxIndex]); 320 | float cutoff = input[overallMaxIndex] * threshold; 321 | for (int j = 0; j < length; j++) { 322 | if (input[indices[j]] >= cutoff) { 323 | return indices[j]; 324 | } 325 | } 326 | // should never get here 327 | return -1; 328 | } 329 | 330 | void PitchTracker::run() { 331 | float sum = 0.0; 332 | for (int k = 0; k < m_buffersize; ++k) { 333 | sum += fabs(m_input[k]); 334 | } 335 | float threshold = (m_audioLevel ? signal_threshold_off : signal_threshold_on); 336 | m_audioLevel = (sum / m_buffersize >= threshold); 337 | if ( m_audioLevel == false ) { 338 | if (m_freq != 0) { 339 | m_freq = 0; 340 | new_freq(); 341 | } 342 | return; 343 | } 344 | 345 | memcpy(m_fftwBufferTime, m_input, m_buffersize * sizeof(*m_fftwBufferTime)); 346 | memset(m_fftwBufferTime+m_buffersize, 0, (m_fftSize - m_buffersize) * sizeof(*m_fftwBufferTime)); 347 | fftwf_execute(m_fftwPlanFFT); 348 | for (int k = 1; k < m_fftSize/2; k++) { 349 | m_fftwBufferFreq[k] = sq(m_fftwBufferFreq[k]) + sq(m_fftwBufferFreq[m_fftSize-k]); 350 | m_fftwBufferFreq[m_fftSize-k] = 0.0; 351 | } 352 | m_fftwBufferFreq[0] = sq(m_fftwBufferFreq[0]); 353 | m_fftwBufferFreq[m_fftSize/2] = sq(m_fftwBufferFreq[m_fftSize/2]); 354 | 355 | fftwf_execute(m_fftwPlanIFFT); 356 | 357 | double sumSq = 2.0 * static_cast(m_fftwBufferTime[0]) / static_cast(m_fftSize); 358 | for (int k = 0; k < m_fftSize - m_buffersize; k++) { 359 | m_fftwBufferTime[k] = m_fftwBufferTime[k+1] / static_cast(m_fftSize); 360 | } 361 | 362 | int count = (m_buffersize + 1) / 2; 363 | for (int k = 0; k < count; k++) { 364 | sumSq -= sq(m_input[m_buffersize-1-k]) + sq(m_input[k]); 365 | // dividing by zero is very slow, so deal with it seperately 366 | if (sumSq > 0.0) { 367 | m_fftwBufferTime[k] *= 2.0 / sumSq; 368 | } else { 369 | m_fftwBufferTime[k] = 0.0; 370 | } 371 | } 372 | const float thres = 0.99; // was 0.6 373 | int maxAutocorrIndex = findsubMaximum(m_fftwBufferTime, count, thres); 374 | 375 | float x = 0.0; 376 | if (maxAutocorrIndex >= 0) { 377 | parabolaTurningPoint(m_fftwBufferTime[maxAutocorrIndex-1], 378 | m_fftwBufferTime[maxAutocorrIndex], 379 | m_fftwBufferTime[maxAutocorrIndex+1], 380 | maxAutocorrIndex+1, &x); 381 | x = m_sampleRate / x; 382 | if (x > 999.0) { // precision drops above 1000 Hz 383 | x = 0.0; 384 | } 385 | } 386 | if (m_freq != x) { 387 | m_freq = x; 388 | new_freq(); 389 | } 390 | } 391 | 392 | float PitchTracker::get_estimated_note() { 393 | return m_freq <= 0.0 ? 1000.0 : 12 * log2f(2.272727e-03f * m_freq); 394 | } 395 | 396 | 397 | -------------------------------------------------------------------------------- /plugins/StompTuner/pitch_tracker.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009, 2010 Hermann Meyer, James Warden, Andreas Degert 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | * -------------------------------------------------------------------------- 18 | */ 19 | 20 | 21 | #pragma once 22 | 23 | #ifndef PITCH_TRACKER_H_ 24 | #define PITCH_TRACKER_H_ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | class PitchTracker; 39 | 40 | ///////////////////////// INTERNAL wORKER CLASS ////////////////////// 41 | 42 | class PitchTrackerWorker { 43 | private: 44 | std::atomic _execute; 45 | std::thread _thd; 46 | std::mutex m; 47 | 48 | public: 49 | PitchTrackerWorker(); 50 | ~PitchTrackerWorker(); 51 | void stop(); 52 | void start(PitchTracker *pt); 53 | bool is_running() const noexcept; 54 | std::condition_variable cv; 55 | }; 56 | 57 | /* ------------- Pitch Tracker ------------- */ 58 | 59 | class PitchTracker { 60 | public: 61 | PitchTracker(std::functionsetFreq_); 62 | ~PitchTracker(); 63 | void init(unsigned int samplerate); 64 | void add(int count, float *input); 65 | float get_estimated_freq() { return m_freq < 0 ? 0 : m_freq; } 66 | float get_estimated_note(); 67 | void reset(); 68 | void set_threshold(float v); 69 | void set_fast_note_detection(bool v); 70 | static void *static_run(void* p); 71 | std::atomic busy; 72 | private: 73 | std::function new_freq; 74 | bool setParameters(int sampleRate, int fftSize ); 75 | void run(); 76 | void copy(); 77 | bool error; 78 | int tick; 79 | PitchTrackerWorker worker; 80 | Resampler resamp; 81 | int m_sampleRate; 82 | int fixed_sampleRate; 83 | float m_freq; 84 | // Value of the threshold above which 85 | // the processing is activated. 86 | float signal_threshold_on; 87 | // Value of the threshold below which 88 | // the input audio signal is deactivated. 89 | float signal_threshold_off; 90 | // Time between frequency estimates (in seconds) 91 | float tracker_period; 92 | // number of samples in input buffer 93 | int m_buffersize; 94 | // Size of the FFT window. 95 | int m_fftSize; 96 | // The audio buffer that stores the input signal. 97 | float *m_buffer; 98 | // Index of the first empty position in the buffer. 99 | int m_bufferIndex; 100 | // buffer for input signal 101 | float *m_input; 102 | // Whether or not the input level is high enough. 103 | bool m_audioLevel; 104 | // Support buffer used to store signals in the time domain. 105 | float *m_fftwBufferTime; 106 | // Support buffer used to store signals in the frequency domain. 107 | float *m_fftwBufferFreq; 108 | // Plan to compute the FFT of a given signal. 109 | fftwf_plan m_fftwPlanFFT; 110 | // Plan to compute the IFFT of a given signal (with additional zero-padding). 111 | fftwf_plan m_fftwPlanIFFT; 112 | }; 113 | 114 | 115 | #endif // PITCH_TRACKER_H_ 116 | -------------------------------------------------------------------------------- /plugins/StompTuner/tuner.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009, 2010 Hermann Meyer, James Warden, Andreas Degert 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | * -------------------------------------------------------------------------- 18 | */ 19 | 20 | #include "pitch_tracker.h" 21 | 22 | /**************************************************************** 23 | ** class tuner 24 | */ 25 | 26 | tuner::tuner(std::functionsetFreq_) 27 | : // trackable(), 28 | pitch_tracker(setFreq_) {} 29 | 30 | void tuner::init(unsigned int samplingFreq) { 31 | pitch_tracker.init(samplingFreq); 32 | } 33 | 34 | int tuner::activate(bool start) { 35 | if (!start) { 36 | pitch_tracker.reset(); 37 | } 38 | return 0; 39 | } 40 | 41 | void tuner::feed_tuner(int count, float* input) { 42 | pitch_tracker.add(count, input); 43 | } 44 | 45 | void tuner::del_instance(tuner *self) 46 | { 47 | delete self; 48 | } 49 | -------------------------------------------------------------------------------- /plugins/StompTuner/tuner.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009, 2010 Hermann Meyer, James Warden, Andreas Degert 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | * -------------------------------------------------------------------------- 18 | */ 19 | 20 | #pragma once 21 | 22 | #ifndef TUNER_H_ 23 | #define TUNER_H_ 24 | 25 | 26 | #include "pitch_tracker.h" 27 | 28 | /**************************************************************** 29 | ** class tuner 30 | */ 31 | 32 | class tuner { 33 | private: 34 | PitchTracker pitch_tracker; 35 | public: 36 | // sigc::signal& signal_freq_changed() { return pitch_tracker.new_freq; } 37 | // Glib::Dispatcher& signal_freq_changed() { return pitch_tracker.new_freq; } 38 | void feed_tuner(int count, float *input); 39 | int activate(bool start); 40 | void init(unsigned int samplingFreq); 41 | static void del_instance(tuner *self); 42 | float get_freq() { return pitch_tracker.get_estimated_freq(); } 43 | float get_note() { return pitch_tracker.get_estimated_note(); } 44 | static inline float db2power(float db) {return pow(10.,db*0.05);} 45 | static void set_threshold_level(tuner& self,float v) {self.pitch_tracker.set_threshold(db2power(v)); } 46 | static void set_fast_note(tuner& self,bool v) {self.pitch_tracker.set_fast_note_detection(v); } 47 | tuner(std::functionsetFreq_); 48 | ~tuner() {}; 49 | }; 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /plugins/Utils/ResizeHandle.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Resize handle for DPF 3 | * Copyright (C) 2021-2022 Filipe Coelho 4 | * 5 | * Permission to use, copy, modify, and/or distribute this software for any purpose with 6 | * or without fee is hereby granted, provided that the above copyright notice and this 7 | * permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD 10 | * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN 11 | * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 12 | * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER 13 | * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 14 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "TopLevelWidget.hpp" 20 | #include "Color.hpp" 21 | 22 | #if defined(DGL_OPENGL) && !defined(DGL_USE_OPENGL3) 23 | #include "OpenGL-include.hpp" 24 | #endif 25 | 26 | START_NAMESPACE_DGL 27 | 28 | /** Resize handle for DPF windows, will sit on bottom-right. */ 29 | class ResizeHandle : public TopLevelWidget 30 | { 31 | public: 32 | /** Constructor for placing this handle on top of a window. */ 33 | explicit ResizeHandle(Window& window) 34 | : TopLevelWidget(window), 35 | handleSize(16), 36 | hasCursor(false), 37 | isResizing(false) 38 | { 39 | resetArea(); 40 | } 41 | 42 | /** Overloaded constructor, will fetch the window from an existing top-level widget. */ 43 | explicit ResizeHandle(TopLevelWidget* const tlw) 44 | : TopLevelWidget(tlw->getWindow()), 45 | handleSize(16), 46 | hasCursor(false), 47 | isResizing(false) 48 | { 49 | resetArea(); 50 | } 51 | 52 | /** Set the handle size, minimum 16. 53 | * Scale factor is automatically applied on top of this size as needed */ 54 | void setHandleSize(const uint size) 55 | { 56 | handleSize = std::max(16u, size); 57 | resetArea(); 58 | } 59 | 60 | protected: 61 | void onDisplay() override 62 | { 63 | // TODO implement gl3 stuff in DPF 64 | #ifndef DGL_USE_OPENGL3 65 | const GraphicsContext& context(getGraphicsContext()); 66 | const double lineWidth = 1.0 * getScaleFactor(); 67 | 68 | #if defined(DGL_OPENGL) && !defined(DGL_USE_OPENGL3) 69 | glMatrixMode(GL_MODELVIEW); 70 | #endif 71 | 72 | // draw white lines, 1px wide 73 | Color(1.0f, 1.0f, 1.0f).setFor(context); 74 | l1.draw(context, lineWidth); 75 | l2.draw(context, lineWidth); 76 | l3.draw(context, lineWidth); 77 | 78 | // draw black lines, offset by 1px and 1px wide 79 | Color(0.0f, 0.0f, 0.0f).setFor(context); 80 | Line l1b(l1), l2b(l2), l3b(l3); 81 | l1b.moveBy(lineWidth, lineWidth); 82 | l2b.moveBy(lineWidth, lineWidth); 83 | l3b.moveBy(lineWidth, lineWidth); 84 | l1b.draw(context, lineWidth); 85 | l2b.draw(context, lineWidth); 86 | l3b.draw(context, lineWidth); 87 | #endif 88 | } 89 | 90 | bool onMouse(const MouseEvent& ev) override 91 | { 92 | if (ev.button != 1) 93 | return false; 94 | 95 | if (ev.press && area.contains(ev.pos)) 96 | { 97 | isResizing = true; 98 | resizingSize = Size(getWidth(), getHeight()); 99 | lastResizePoint = ev.pos; 100 | return true; 101 | } 102 | 103 | if (isResizing && ! ev.press) 104 | { 105 | isResizing = false; 106 | recheckCursor(ev.pos); 107 | return true; 108 | } 109 | 110 | return false; 111 | } 112 | 113 | bool onMotion(const MotionEvent& ev) override 114 | { 115 | if (! isResizing) 116 | { 117 | recheckCursor(ev.pos); 118 | return false; 119 | } 120 | 121 | const Size offset(ev.pos.getX() - lastResizePoint.getX(), 122 | ev.pos.getY() - lastResizePoint.getY()); 123 | 124 | resizingSize += offset; 125 | lastResizePoint = ev.pos; 126 | 127 | // TODO keepAspectRatio 128 | bool keepAspectRatio; 129 | const Size minSize(getWindow().getGeometryConstraints(keepAspectRatio)); 130 | const uint minWidth = minSize.getWidth(); 131 | const uint minHeight = minSize.getHeight(); 132 | 133 | if (resizingSize.getWidth() < minWidth) 134 | resizingSize.setWidth(minWidth); 135 | if (resizingSize.getWidth() > 16384) 136 | resizingSize.setWidth(16384); 137 | if (resizingSize.getHeight() < minHeight) 138 | resizingSize.setHeight(minHeight); 139 | if (resizingSize.getHeight() > 16384) 140 | resizingSize.setHeight(16384); 141 | 142 | if (keepAspectRatio) 143 | { 144 | const uint resize = resizingSize.getWidth() < resizingSize.getHeight() ? 145 | resizingSize.getWidth() : resizingSize.getHeight(); 146 | const uint base = resizingSize.getWidth() < resizingSize.getHeight() ? 147 | getWidth() : getHeight(); 148 | const double scale = (double)resize / (double)base; 149 | setSize(getWidth() * scale, getHeight() * scale); 150 | } 151 | else 152 | { 153 | setSize(resizingSize.getWidth(), resizingSize.getHeight()); 154 | } 155 | return true; 156 | } 157 | 158 | void onResize(const ResizeEvent& ev) override 159 | { 160 | TopLevelWidget::onResize(ev); 161 | resetArea(); 162 | } 163 | 164 | private: 165 | Rectangle area; 166 | Line l1, l2, l3; 167 | uint handleSize; 168 | 169 | // event handling state 170 | bool hasCursor, isResizing; 171 | Point lastResizePoint; 172 | Size resizingSize; 173 | 174 | void recheckCursor(const Point& pos) 175 | { 176 | const bool shouldHaveCursor = area.contains(pos); 177 | 178 | if (shouldHaveCursor == hasCursor) 179 | return; 180 | 181 | hasCursor = shouldHaveCursor; 182 | setCursor(shouldHaveCursor ? kMouseCursorUpLeftDownRight : kMouseCursorArrow); 183 | } 184 | 185 | void resetArea() 186 | { 187 | const double scaleFactor = getScaleFactor(); 188 | const uint margin = 0.0 * scaleFactor; 189 | const uint size = handleSize * scaleFactor; 190 | 191 | area = Rectangle(getWidth() - size - margin, 192 | getHeight() - size - margin, 193 | size, size); 194 | 195 | recreateLines(area.getX(), area.getY(), size); 196 | } 197 | 198 | void recreateLines(const uint x, const uint y, const uint size) 199 | { 200 | uint linesize = size; 201 | uint offset = 0; 202 | 203 | // 1st line, full diagonal size 204 | l1.setStartPos(x + size, y); 205 | l1.setEndPos(x, y + size); 206 | 207 | // 2nd line, bit more to the right and down, cropped 208 | offset += size / 3; 209 | linesize -= size / 3; 210 | l2.setStartPos(x + linesize + offset, y + offset); 211 | l2.setEndPos(x + offset, y + linesize + offset); 212 | 213 | // 3rd line, even more right and down 214 | offset += size / 3; 215 | linesize -= size / 3; 216 | l3.setStartPos(x + linesize + offset, y + offset); 217 | l3.setEndPos(x + offset, y + linesize + offset); 218 | } 219 | 220 | DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ResizeHandle) 221 | }; 222 | 223 | END_NAMESPACE_DGL 224 | -------------------------------------------------------------------------------- /plugins/Utils/UiSizeGroup.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * UiSizeGroup for the DISTRHO Plugin Framework (DPF) 3 | * 4 | * SPDX-License-Identifier: 0BSD 5 | * 6 | * Copyright (C) 2023 brummer 7 | */ 8 | 9 | #ifndef UISIZEGROUP_H 10 | #define UISIZEGROUP_H 11 | 12 | #include 13 | 14 | #include "SubWidget.hpp" 15 | 16 | START_NAMESPACE_DISTRHO 17 | 18 | /** 19 | class to handle resizing for all SubWidgets 20 | */ 21 | 22 | class UiSizeGroup 23 | { 24 | public: 25 | // init the SizeGroup by store the inital size of the parent widget 26 | UiSizeGroup(const uint width, const uint height) 27 | : initW(width), initH(height) 28 | { 29 | scaleHFactor = 1.0f; 30 | scaleWFactor = 1.0f; 31 | scaleFactor = 1.0f; 32 | }; 33 | 34 | ~UiSizeGroup() {}; 35 | 36 | // add a SubWidget to the group and set the initial size/position 37 | void addToSizeGroup(SubWidget * const widget, const uint x, const uint y, const uint w, const uint h) 38 | { 39 | widget->setSize(w, h); 40 | widget->setAbsolutePos(x, y); 41 | 42 | const SizeGroup sg = { widget, x, y, w, h }; 43 | sizeGroup.push_back(sg); 44 | } 45 | 46 | // add a SubWidget to the group and get the size/position 47 | void addToSizeGroup(SubWidget * const widget) 48 | { 49 | const DGL::Rectangle rec = widget->getConstrainedAbsoluteArea(); 50 | 51 | const SizeGroup sg = { widget, rec.getX(), rec.getY(), rec.getWidth(), rec.getHeight() }; 52 | sizeGroup.push_back(sg); 53 | } 54 | 55 | // resize all registered SubWidgets of the group by aspect ratio 56 | void resizeAspectSizeGroup(const uint w, const uint h) 57 | { 58 | scaleHFactor = static_cast(h)/static_cast(initH); 59 | scaleWFactor = static_cast(w)/static_cast(initW); 60 | scaleFactor = scaleHFactor < scaleWFactor ? scaleHFactor : scaleWFactor; 61 | 62 | for (auto const& i : sizeGroup) { 63 | i.widget->setSize(i.w * scaleFactor, i.h * scaleFactor); 64 | i.widget->setAbsolutePos(((i.x + i.w * 0.5) * scaleWFactor) - (i.w * 0.5 * scaleFactor), 65 | ((i.y + i.h * 0.5) * scaleHFactor) - (i.h * 0.5 * scaleFactor)); 66 | } 67 | } 68 | 69 | float getScaleFactor() 70 | { 71 | return scaleFactor; 72 | } 73 | 74 | float getScaleHFactor() 75 | { 76 | return scaleHFactor; 77 | } 78 | 79 | float getScaleWFactor() 80 | { 81 | return scaleWFactor; 82 | } 83 | 84 | protected: 85 | // struct to hold initial size/position for a SubWidget 86 | struct SizeGroup { 87 | SubWidget * const widget; 88 | const uint x; 89 | const uint y; 90 | const uint w; 91 | const uint h; 92 | }; 93 | 94 | private: 95 | std::list sizeGroup; 96 | const uint initW; 97 | const uint initH; 98 | float scaleHFactor; 99 | float scaleWFactor; 100 | float scaleFactor; 101 | DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(UiSizeGroup) 102 | }; 103 | 104 | END_NAMESPACE_DISTRHO 105 | 106 | #endif 107 | -------------------------------------------------------------------------------- /plugins/zita-resampler-1.1.0/AUTHORS: -------------------------------------------------------------------------------- 1 | Fons Adriaensen 2 | -------------------------------------------------------------------------------- /plugins/zita-resampler-1.1.0/README: -------------------------------------------------------------------------------- 1 | Files in this directory are from zita-resampler by 2 | Fons Adriaensen 3 | 4 | Please refer to the version official archive 5 | http://kokkinizita.linuxaudio.org/linuxaudio/ 6 | 7 | These files are only included to ease compilation 8 | and installion of Guitarix. Don't modify. 9 | -------------------------------------------------------------------------------- /plugins/zita-resampler-1.1.0/gx_resampler.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 Hermann Meyer, Andreas Degert, Pete Shorthose 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | * -------------------------------------------------------------------------- 18 | */ 19 | 20 | #include "gx_resampler.h" 21 | #include 22 | 23 | namespace gx_resample 24 | { 25 | 26 | void SimpleResampler::setup(int sampleRate, unsigned int fact) 27 | { 28 | assert(fact <= MAX_UPSAMPLE); 29 | m_fact = fact; 30 | const int qual = 16; // resulting in a total delay of 2*qual (0.7ms @44100) 31 | // upsampler 32 | r_up.clear(); 33 | r_up.setup(sampleRate, sampleRate*fact, 1, qual); 34 | // k == inpsize() == 2 * qual 35 | // pre-fill with k-1 zeros 36 | r_up.inp_count = r_up.inpsize() - 1; 37 | r_up.out_count = 1; 38 | r_up.inp_data = r_up.out_data = 0; 39 | r_up.process(); 40 | // downsampler 41 | r_down.clear(); 42 | r_down.setup(sampleRate*fact, sampleRate, 1, qual); 43 | // k == inpsize() == 2 * qual * fact 44 | // pre-fill with k-1 zeros 45 | r_down.inp_count = r_down.inpsize() - 1; 46 | r_down.out_count = 1; 47 | r_down.inp_data = r_down.out_data = 0; 48 | r_down.process(); 49 | } 50 | 51 | void SimpleResampler::up(int count, float *input, float *output) 52 | { 53 | r_up.inp_count = count; 54 | r_up.inp_data = input; 55 | r_up.out_count = count * m_fact; 56 | r_up.out_data = output; 57 | r_up.process(); 58 | assert(r_up.inp_count == 0); 59 | assert(r_up.out_count == 0); 60 | } 61 | 62 | void SimpleResampler::down(int count, float *input, float *output) 63 | { 64 | r_down.inp_count = count * m_fact; 65 | r_down.inp_data = input; 66 | r_down.out_count = count+1; // +1 == trick to drain input 67 | r_down.out_data = output; 68 | r_down.process(); 69 | assert(r_down.inp_count == 0); 70 | assert(r_down.out_count == 1); 71 | } 72 | 73 | int FixedRateResampler::setup(int _inputRate, int _outputRate) 74 | { 75 | const int qual = 16; // resulting in a total delay of 2*qual (0.7ms @44100) 76 | inputRate = _inputRate; 77 | outputRate = _outputRate; 78 | if (inputRate == outputRate) { 79 | return 0; 80 | } 81 | // upsampler 82 | int ret = r_up.setup(inputRate, outputRate, 1, qual); 83 | if (ret) { 84 | return ret; 85 | } 86 | // k == filtlen() == 2 * qual 87 | // pre-fill with k-1 zeros 88 | r_up.inp_count = r_up.filtlen() - 1; 89 | r_up.out_count = 1; 90 | r_up.inp_data = r_up.out_data = 0; 91 | r_up.process(); 92 | // downsampler 93 | ret = r_down.setup(outputRate, inputRate, 1, qual); 94 | if (ret) { 95 | return ret; 96 | } 97 | // k == filtlen() == 2 * qual * fact 98 | // pre-fill with k-2 zeros 99 | r_down.inp_count = r_down.filtlen() - 2; 100 | r_down.out_count = 1; 101 | r_down.inp_data = r_down.out_data = 0; 102 | r_down.process(); 103 | return 0; 104 | } 105 | 106 | int FixedRateResampler::up(int count, float *input, float *output) 107 | { 108 | if (inputRate == outputRate) { 109 | memcpy(output, input, count*sizeof(float)); 110 | r_down.out_count = count; 111 | return count; 112 | } 113 | r_up.inp_count = count; 114 | r_down.out_count = count+1; // +1 == trick to drain input 115 | r_up.inp_data = input; 116 | int m = max_out_count(count); 117 | r_up.out_count = m; 118 | r_up.out_data = output; 119 | r_up.process(); 120 | assert(r_up.inp_count == 0); 121 | assert(r_up.out_count <= 1); 122 | r_down.inp_count = m - r_up.out_count; 123 | return r_down.inp_count; 124 | } 125 | 126 | void FixedRateResampler::down(float *input, float *output) 127 | { 128 | if (inputRate == outputRate) { 129 | memcpy(output, input, r_down.out_count*sizeof(float)); 130 | return; 131 | } 132 | r_down.inp_data = input; 133 | r_down.out_data = output; 134 | r_down.process(); 135 | assert(r_down.inp_count == 0); 136 | assert(r_down.out_count == 1); 137 | } 138 | 139 | } // namespace gx_resample 140 | -------------------------------------------------------------------------------- /plugins/zita-resampler-1.1.0/gx_resampler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 Hermann Meyer, Andreas Degert, Pete Shorthose 3 | * 4 | * This program is free software; you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 | * -------------------------------------------------------------------------- 18 | */ 19 | 20 | 21 | #pragma once 22 | 23 | #ifndef SRC_HEADERS_GX_RESAMPLER_H_ 24 | #define SRC_HEADERS_GX_RESAMPLER_H_ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | namespace gx_resample 33 | { 34 | 35 | #define MAX_UPSAMPLE 8 36 | 37 | class SimpleResampler { 38 | private: 39 | Resampler r_up, r_down; 40 | int m_fact; 41 | public: 42 | SimpleResampler(): r_up(), r_down(), m_fact() {} 43 | void setup(int sampleRate, unsigned int fact); 44 | void up(int count, float *input, float *output); 45 | void down(int count, float *input, float *output); 46 | }; 47 | 48 | class FixedRateResampler { 49 | private: 50 | Resampler r_up, r_down; 51 | int inputRate, outputRate; 52 | int last_in_count; 53 | public: 54 | int setup(int _inputRate, int _outputRate); 55 | int up(int count, float *input, float *output); 56 | void down(float *input, float *output); 57 | int max_out_count(int in_count) { 58 | return static_cast(ceil((in_count*static_cast(outputRate))/inputRate)); } 59 | int min_out_count(int in_count) { 60 | return static_cast(ceil((in_count*static_cast(inputRate))/outputRate)); } 61 | }; 62 | 63 | } 64 | #endif // SRC_HEADERS_GX_RESAMPLER_H_ 65 | -------------------------------------------------------------------------------- /plugins/zita-resampler-1.1.0/resampler-table.cc: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2006-2012 Fons Adriaensen 4 | // 5 | // This program is free software; you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation; either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | // ---------------------------------------------------------------------------- 19 | 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #ifndef M_PI 28 | #define M_PI 3.14159265358979323846 29 | #endif 30 | 31 | int zita_resampler_major_version (void) 32 | { 33 | return ZITA_RESAMPLER_MAJOR_VERSION; 34 | } 35 | 36 | 37 | int zita_resampler_minor_version (void) 38 | { 39 | return ZITA_RESAMPLER_MINOR_VERSION; 40 | } 41 | 42 | 43 | static double sinc (double x) 44 | { 45 | x = fabs (x); 46 | if (x < 1e-6) return 1.0; 47 | x *= M_PI; 48 | return sin (x) / x; 49 | } 50 | 51 | 52 | static double wind (double x) 53 | { 54 | x = fabs (x); 55 | if (x >= 1.0) return 0.0f; 56 | x *= M_PI; 57 | return 0.384 + 0.500 * cos (x) + 0.116 * cos (2 * x); 58 | } 59 | 60 | 61 | 62 | Resampler_table *Resampler_table::_list = 0; 63 | Resampler_mutex Resampler_table::_mutex; 64 | 65 | 66 | Resampler_table::Resampler_table (double fr, unsigned int hl, unsigned int np) : 67 | _next (0), 68 | _refc (0), 69 | _fr (fr), 70 | _hl (hl), 71 | _np (np) 72 | { 73 | unsigned int i, j; 74 | double t; 75 | float *p; 76 | 77 | _ctab = new float [hl * (np + 1)]; 78 | p = _ctab; 79 | for (j = 0; j <= np; j++) 80 | { 81 | t = (double) j / (double) np; 82 | for (i = 0; i < hl; i++) 83 | { 84 | p [hl - i - 1] = (float)(fr * sinc (t * fr) * wind (t / hl)); 85 | t += 1; 86 | } 87 | p += hl; 88 | } 89 | } 90 | 91 | 92 | Resampler_table::~Resampler_table (void) 93 | { 94 | delete[] _ctab; 95 | } 96 | 97 | 98 | Resampler_table *Resampler_table::create (double fr, unsigned int hl, unsigned int np) 99 | { 100 | Resampler_table *P; 101 | 102 | _mutex.lock (); 103 | P = _list; 104 | while (P) 105 | { 106 | if ((fr >= P->_fr * 0.999) && (fr <= P->_fr * 1.001) && (hl == P->_hl) && (np == P->_np)) 107 | { 108 | P->_refc++; 109 | _mutex.unlock (); 110 | return P; 111 | } 112 | P = P->_next; 113 | } 114 | P = new Resampler_table (fr, hl, np); 115 | P->_refc = 1; 116 | P->_next = _list; 117 | _list = P; 118 | _mutex.unlock (); 119 | return P; 120 | } 121 | 122 | 123 | void Resampler_table::destroy (Resampler_table *T) 124 | { 125 | Resampler_table *P, *Q; 126 | 127 | _mutex.lock (); 128 | if (T) 129 | { 130 | T->_refc--; 131 | if (T->_refc == 0) 132 | { 133 | P = _list; 134 | Q = 0; 135 | while (P) 136 | { 137 | if (P == T) 138 | { 139 | if (Q) Q->_next = T->_next; 140 | else _list = T->_next; 141 | break; 142 | } 143 | Q = P; 144 | P = P->_next; 145 | } 146 | delete T; 147 | } 148 | } 149 | _mutex.unlock (); 150 | } 151 | 152 | 153 | void Resampler_table::print_list (void) 154 | { 155 | Resampler_table *P; 156 | 157 | printf ("Resampler table\n----\n"); 158 | for (P = _list; P; P = P->_next) 159 | { 160 | printf ("refc = %3d fr = %10.6lf hl = %4d np = %4d\n", P->_refc, P->_fr, P->_hl, P->_np); 161 | } 162 | printf ("----\n\n"); 163 | } 164 | 165 | -------------------------------------------------------------------------------- /plugins/zita-resampler-1.1.0/resampler.cc: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2006-2012 Fons Adriaensen 4 | // 5 | // This program is free software; you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation; either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | // ---------------------------------------------------------------------------- 19 | 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #ifndef M_PI 28 | #define M_PI 3.14159265358979323846 29 | #endif 30 | 31 | static unsigned int gcd (unsigned int a, unsigned int b) 32 | { 33 | if (a == 0) return b; 34 | if (b == 0) return a; 35 | while (1) 36 | { 37 | if (a > b) 38 | { 39 | a = a % b; 40 | if (a == 0) return b; 41 | if (a == 1) return 1; 42 | } 43 | else 44 | { 45 | b = b % a; 46 | if (b == 0) return a; 47 | if (b == 1) return 1; 48 | } 49 | } 50 | return 1; 51 | } 52 | 53 | 54 | Resampler::Resampler (void) : 55 | _table (0), 56 | _nchan (0), 57 | _buff (0) 58 | { 59 | reset (); 60 | } 61 | 62 | 63 | Resampler::~Resampler (void) 64 | { 65 | clear (); 66 | } 67 | 68 | 69 | int Resampler::setup (unsigned int fs_inp, 70 | unsigned int fs_out, 71 | unsigned int nchan, 72 | unsigned int hlen) 73 | { 74 | if ((hlen < 8) || (hlen > 96)) return 1; 75 | return setup (fs_inp, fs_out, nchan, hlen, 1.0 - 2.6 / hlen); 76 | } 77 | 78 | 79 | int Resampler::setup (unsigned int fs_inp, 80 | unsigned int fs_out, 81 | unsigned int nchan, 82 | unsigned int hlen, 83 | double frel) 84 | { 85 | unsigned int g, h, k, n, s; 86 | double r; 87 | float *B = 0; 88 | Resampler_table *T = 0; 89 | 90 | k = s = 0; 91 | if (fs_inp && fs_out && nchan) 92 | { 93 | r = (double) fs_out / (double) fs_inp; 94 | g = gcd (fs_out, fs_inp); 95 | n = fs_out / g; 96 | s = fs_inp / g; 97 | if ((16 * r >= 1) && (n <= 1000)) 98 | { 99 | h = hlen; 100 | k = 250; 101 | if (r < 1) 102 | { 103 | frel *= r; 104 | h = (unsigned int)(ceil (h / r)); 105 | k = (unsigned int)(ceil (k / r)); 106 | } 107 | T = Resampler_table::create (frel, h, n); 108 | B = new float [nchan * (2 * h - 1 + k)]; 109 | } 110 | } 111 | clear (); 112 | if (T) 113 | { 114 | _table = T; 115 | _buff = B; 116 | _nchan = nchan; 117 | _inmax = k; 118 | _pstep = s; 119 | return reset (); 120 | } 121 | else return 1; 122 | } 123 | 124 | 125 | void Resampler::clear (void) 126 | { 127 | Resampler_table::destroy (_table); 128 | delete[] _buff; 129 | _buff = 0; 130 | _table = 0; 131 | _nchan = 0; 132 | _inmax = 0; 133 | _pstep = 0; 134 | reset (); 135 | } 136 | 137 | 138 | double Resampler::inpdist (void) const 139 | { 140 | if (!_table) return 0; 141 | return (int)(_table->_hl + 1 - _nread) - (double)_phase / _table->_np; 142 | } 143 | 144 | 145 | int Resampler::inpsize (void) const 146 | { 147 | if (!_table) return 0; 148 | return 2 * _table->_hl; 149 | } 150 | 151 | 152 | int Resampler::reset (void) 153 | { 154 | if (!_table) return 1; 155 | 156 | inp_count = 0; 157 | out_count = 0; 158 | inp_data = 0; 159 | out_data = 0; 160 | _index = 0; 161 | _nread = 0; 162 | _nzero = 0; 163 | _phase = 0; 164 | if (_table) 165 | { 166 | _nread = 2 * _table->_hl; 167 | return 0; 168 | } 169 | return 1; 170 | } 171 | 172 | 173 | int Resampler::process (void) 174 | { 175 | unsigned int hl, ph, np, dp, in, nr, nz, i, n, c; 176 | float *p1, *p2; 177 | 178 | if (!_table) return 1; 179 | 180 | hl = _table->_hl; 181 | np = _table->_np; 182 | dp = _pstep; 183 | in = _index; 184 | nr = _nread; 185 | ph = _phase; 186 | nz = _nzero; 187 | n = (2 * hl - nr) * _nchan; 188 | p1 = _buff + in * _nchan; 189 | p2 = p1 + n; 190 | 191 | while (out_count) 192 | { 193 | if (nr) 194 | { 195 | if (inp_count == 0) break; 196 | if (inp_data) 197 | { 198 | for (c = 0; c < _nchan; c++) p2 [c] = inp_data [c]; 199 | inp_data += _nchan; 200 | nz = 0; 201 | } 202 | else 203 | { 204 | for (c = 0; c < _nchan; c++) p2 [c] = 0; 205 | if (nz < 2 * hl) nz++; 206 | } 207 | nr--; 208 | p2 += _nchan; 209 | inp_count--; 210 | } 211 | else 212 | { 213 | if (out_data) 214 | { 215 | if (nz < 2 * hl) 216 | { 217 | float *c1 = _table->_ctab + hl * ph; 218 | float *c2 = _table->_ctab + hl * (np - ph); 219 | for (c = 0; c < _nchan; c++) 220 | { 221 | float *q1 = p1 + c; 222 | float *q2 = p2 + c; 223 | float s = 1e-20f; 224 | for (i = 0; i < hl; i++) 225 | { 226 | q2 -= _nchan; 227 | s += *q1 * c1 [i] + *q2 * c2 [i]; 228 | q1 += _nchan; 229 | } 230 | *out_data++ = s - 1e-20f; 231 | } 232 | } 233 | else 234 | { 235 | for (c = 0; c < _nchan; c++) *out_data++ = 0; 236 | } 237 | } 238 | out_count--; 239 | 240 | ph += dp; 241 | if (ph >= np) 242 | { 243 | nr = ph / np; 244 | ph -= nr * np; 245 | in += nr; 246 | p1 += nr * _nchan;; 247 | if (in >= _inmax) 248 | { 249 | n = (2 * hl - nr) * _nchan; 250 | memcpy (_buff, p1, n * sizeof (float)); 251 | in = 0; 252 | p1 = _buff; 253 | p2 = p1 + n; 254 | } 255 | } 256 | } 257 | } 258 | _index = in; 259 | _nread = nr; 260 | _phase = ph; 261 | _nzero = nz; 262 | 263 | return 0; 264 | } 265 | 266 | 267 | -------------------------------------------------------------------------------- /plugins/zita-resampler-1.1.0/zita-resampler/resampler-table.h: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2006-2012 Fons Adriaensen 4 | // 5 | // This program is free software; you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation; either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | // ---------------------------------------------------------------------------- 19 | 20 | 21 | #ifndef __RESAMPLER_TABLE_H 22 | #define __RESAMPLER_TABLE_H 23 | 24 | 25 | #include 26 | 27 | 28 | #define ZITA_RESAMPLER_MAJOR_VERSION 1 29 | #define ZITA_RESAMPLER_MINOR_VERSION 1 30 | 31 | 32 | extern int zita_resampler_major_version (void); 33 | extern int zita_resampler_minor_version (void); 34 | 35 | 36 | class Resampler_mutex 37 | { 38 | private: 39 | 40 | friend class Resampler_table; 41 | 42 | Resampler_mutex (void) { pthread_mutex_init (&_mutex, 0); } 43 | ~Resampler_mutex (void) { pthread_mutex_destroy (&_mutex); } 44 | void lock (void) { pthread_mutex_lock (&_mutex); } 45 | void unlock (void) { pthread_mutex_unlock (&_mutex); } 46 | 47 | pthread_mutex_t _mutex; 48 | }; 49 | 50 | 51 | class Resampler_table 52 | { 53 | public: 54 | 55 | static void print_list (void); 56 | 57 | private: 58 | 59 | Resampler_table (double fr, unsigned int hl, unsigned int np); 60 | ~Resampler_table (void); 61 | 62 | friend class Resampler; 63 | friend class VResampler; 64 | 65 | Resampler_table *_next; 66 | unsigned int _refc; 67 | float *_ctab; 68 | double _fr; 69 | unsigned int _hl; 70 | unsigned int _np; 71 | 72 | static Resampler_table *create (double fr, unsigned int hl, unsigned int np); 73 | static void destroy (Resampler_table *T); 74 | 75 | static Resampler_table *_list; 76 | static Resampler_mutex _mutex; 77 | }; 78 | 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /plugins/zita-resampler-1.1.0/zita-resampler/resampler.h: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // 3 | // Copyright (C) 2006-2012 Fons Adriaensen 4 | // 5 | // This program is free software; you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation; either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | // 18 | // ---------------------------------------------------------------------------- 19 | 20 | 21 | #ifndef __RESAMPLER_H 22 | #define __RESAMPLER_H 23 | 24 | 25 | #include 26 | 27 | 28 | class Resampler 29 | { 30 | public: 31 | 32 | Resampler (void); 33 | ~Resampler (void); 34 | 35 | int setup (unsigned int fs_inp, 36 | unsigned int fs_out, 37 | unsigned int nchan, 38 | unsigned int hlen); 39 | 40 | int setup (unsigned int fs_inp, 41 | unsigned int fs_out, 42 | unsigned int nchan, 43 | unsigned int hlen, 44 | double frel); 45 | 46 | void clear (void); 47 | int reset (void); 48 | int nchan (void) const { return _nchan; } 49 | int filtlen (void) const { return inpsize (); } // Deprecated 50 | int inpsize (void) const; 51 | double inpdist (void) const; 52 | int process (void); 53 | 54 | unsigned int inp_count; 55 | unsigned int out_count; 56 | float *inp_data; 57 | float *out_data; 58 | void *inp_list; 59 | void *out_list; 60 | 61 | private: 62 | 63 | Resampler_table *_table; 64 | unsigned int _nchan; 65 | unsigned int _inmax; 66 | unsigned int _index; 67 | unsigned int _nread; 68 | unsigned int _nzero; 69 | unsigned int _phase; 70 | unsigned int _pstep; 71 | float *_buff; 72 | void *_dummy [8]; 73 | }; 74 | 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /pugl.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/win_cairo.c b/src/win_cairo.c 2 | index 0aab254..ee7fd31 100644 3 | --- a/src/win_cairo.c 4 | +++ b/src/win_cairo.c 5 | @@ -13,42 +13,12 @@ 6 | #include 7 | 8 | typedef struct { 9 | - cairo_surface_t* surface; 10 | cairo_t* cr; 11 | - HDC drawDc; 12 | - HBITMAP drawBitmap; 13 | + cairo_surface_t* image_surface; 14 | + int width; 15 | + int height; 16 | } PuglWinCairoSurface; 17 | 18 | -static PuglStatus 19 | -puglWinCairoCreateDrawContext(PuglView* view) 20 | -{ 21 | - PuglInternals* const impl = view->impl; 22 | - PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; 23 | - 24 | - surface->drawDc = CreateCompatibleDC(impl->hdc); 25 | - surface->drawBitmap = CreateCompatibleBitmap( 26 | - impl->hdc, (int)view->lastConfigure.width, (int)view->lastConfigure.height); 27 | - 28 | - DeleteObject(SelectObject(surface->drawDc, surface->drawBitmap)); 29 | - 30 | - return PUGL_SUCCESS; 31 | -} 32 | - 33 | -static PuglStatus 34 | -puglWinCairoDestroyDrawContext(PuglView* view) 35 | -{ 36 | - PuglInternals* const impl = view->impl; 37 | - PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; 38 | - 39 | - DeleteDC(surface->drawDc); 40 | - DeleteObject(surface->drawBitmap); 41 | - 42 | - surface->drawDc = NULL; 43 | - surface->drawBitmap = NULL; 44 | - 45 | - return PUGL_SUCCESS; 46 | -} 47 | - 48 | static PuglStatus 49 | puglWinCairoConfigure(PuglView* view) 50 | { 51 | @@ -67,10 +37,10 @@ puglWinCairoClose(PuglView* view) 52 | { 53 | PuglInternals* const impl = view->impl; 54 | PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; 55 | - 56 | - cairo_surface_destroy(surface->surface); 57 | - 58 | - surface->surface = NULL; 59 | + cairo_destroy (surface->cr); 60 | + cairo_surface_destroy(surface->image_surface); 61 | + surface->cr = NULL; 62 | + surface->image_surface = NULL; 63 | } 64 | 65 | static PuglStatus 66 | @@ -79,9 +49,10 @@ puglWinCairoOpen(PuglView* view) 67 | PuglInternals* const impl = view->impl; 68 | PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; 69 | 70 | - if (!(surface->surface = cairo_win32_surface_create(surface->drawDc)) || 71 | - cairo_surface_status(surface->surface) || 72 | - !(surface->cr = cairo_create(surface->surface)) || 73 | + if (!(surface->image_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 74 | + (int)view->lastConfigure.width, (int)view->lastConfigure.height)) || 75 | + cairo_surface_status(surface->image_surface) || 76 | + !(surface->cr = cairo_create(surface->image_surface)) || 77 | cairo_status(surface->cr)) { 78 | return PUGL_CREATE_CONTEXT_FAILED; 79 | } 80 | @@ -89,6 +60,44 @@ puglWinCairoOpen(PuglView* view) 81 | return PUGL_SUCCESS; 82 | } 83 | 84 | +static PuglStatus 85 | +puglWinCairoCheckResize(PuglView* view) 86 | +{ 87 | + PuglStatus st = PUGL_SUCCESS; 88 | + PuglInternals* const impl = view->impl; 89 | + PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; 90 | + 91 | + // check if the size have changed, if so recreate the image surface 92 | + // to match the new size 93 | + if (surface->width != (int)view->lastConfigure.width || 94 | + surface->height != (int)view->lastConfigure.height) { 95 | + surface->width = (int)view->lastConfigure.width; 96 | + surface->height = (int)view->lastConfigure.height; 97 | + puglWinCairoClose(view); 98 | + st = puglWinCairoOpen(view); 99 | + } else { 100 | + // when reuse the image surface clean up the old content 101 | + cairo_set_operator(surface->cr, CAIRO_OPERATOR_CLEAR); 102 | + cairo_paint(surface->cr); 103 | + cairo_set_operator(surface->cr, CAIRO_OPERATOR_OVER); 104 | + } 105 | + 106 | + return st; 107 | +} 108 | + 109 | +static PuglStatus 110 | +puglWinCairoCreate(PuglView* view) 111 | +{ 112 | + PuglInternals* const impl = view->impl; 113 | + PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; 114 | + 115 | + // get the default size and create the image surface 116 | + surface->width = (int)view->lastConfigure.width; 117 | + surface->height = (int)view->lastConfigure.height; 118 | + 119 | + return puglWinCairoOpen(view); 120 | +} 121 | + 122 | static void 123 | puglWinCairoDestroy(PuglView* view) 124 | { 125 | @@ -96,7 +105,6 @@ puglWinCairoDestroy(PuglView* view) 126 | PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; 127 | 128 | puglWinCairoClose(view); 129 | - puglWinCairoDestroyDrawContext(view); 130 | free(surface); 131 | impl->surface = NULL; 132 | } 133 | @@ -106,9 +114,8 @@ puglWinCairoEnter(PuglView* view, const PuglExposeEvent* expose) 134 | { 135 | PuglStatus st = PUGL_SUCCESS; 136 | 137 | - if (expose && !(st = puglWinCairoCreateDrawContext(view)) && 138 | - !(st = puglWinCairoOpen(view))) { 139 | - st = puglWinEnter(view, expose); 140 | + if (expose) { 141 | + st = puglWinCairoCheckResize(view); 142 | } 143 | 144 | return st; 145 | @@ -121,19 +128,13 @@ puglWinCairoLeave(PuglView* view, const PuglExposeEvent* expose) 146 | PuglWinCairoSurface* const surface = (PuglWinCairoSurface*)impl->surface; 147 | 148 | if (expose) { 149 | - cairo_surface_flush(surface->surface); 150 | - BitBlt(impl->hdc, 151 | - 0, 152 | - 0, 153 | - (int)view->lastConfigure.width, 154 | - (int)view->lastConfigure.height, 155 | - surface->drawDc, 156 | - 0, 157 | - 0, 158 | - SRCCOPY); 159 | - 160 | - puglWinCairoClose(view); 161 | - puglWinCairoDestroyDrawContext(view); 162 | + HDC hdc = BeginPaint(impl->hwnd, &impl->paint); 163 | + cairo_surface_t* win_surface = cairo_win32_surface_create(hdc); 164 | + cairo_t* cri = cairo_create(win_surface); 165 | + cairo_set_source_surface(cri, surface->image_surface, 0.0, 0.0); 166 | + cairo_paint(cri); 167 | + cairo_destroy(cri); 168 | + cairo_surface_destroy(win_surface); 169 | } 170 | 171 | return puglWinLeave(view, expose); 172 | @@ -149,7 +150,7 @@ const PuglBackend* 173 | puglCairoBackend(void) 174 | { 175 | static const PuglBackend backend = {puglWinCairoConfigure, 176 | - puglStubCreate, 177 | + puglWinCairoCreate, 178 | puglWinCairoDestroy, 179 | puglWinCairoEnter, 180 | puglWinCairoLeave, 181 | --------------------------------------------------------------------------------