├── .gitmodules ├── lib ├── CMakeLists.txt ├── specialmath │ └── jyzo │ │ ├── test.ps1 │ │ ├── compare.py │ │ ├── test.cpp │ │ ├── README.md │ │ └── jyzo.hpp ├── Uhhyou │ ├── librarylicense.hpp │ ├── cmake │ │ ├── generate_cpp_text.cmake │ │ └── additional_compiler_flag.cmake │ ├── CMakeLists.txt │ ├── dsp │ │ ├── svf.hpp │ │ ├── basiclimiter.hpp │ │ └── multirate.hpp │ ├── gui │ │ ├── numbereditor.hpp │ │ ├── style.hpp │ │ ├── style.cpp │ │ ├── buttonarray.hpp │ │ ├── combobox.hpp │ │ ├── parameterarrayattachment.hpp │ │ ├── widgets.hpp │ │ ├── tabview.hpp │ │ ├── button.hpp │ │ └── popupview.hpp │ └── scaledparameter.hpp └── nlohmann │ └── json_fwd.hpp ├── plugin └── CMakeLists.txt ├── experimental ├── CMakeLists.txt ├── SlopeFilter │ ├── gui │ │ └── popupinformationtext.hpp │ ├── CMakeLists.txt │ ├── dsp │ │ ├── dspcore.hpp │ │ ├── dspcore.cpp │ │ └── filter.hpp │ ├── PluginEditor.h │ ├── PluginProcessor.h │ ├── parameter.hpp │ ├── PluginProcessor.cpp │ └── PluginEditor.cpp ├── TwoBandStereo │ ├── gui │ │ └── popupinformationtext.hpp │ ├── CMakeLists.txt │ ├── dsp │ │ ├── dspcore.hpp │ │ ├── dspcore.cpp │ │ └── crossover.hpp │ ├── PluginEditor.h │ ├── PluginProcessor.h │ ├── parameter.hpp │ ├── PluginProcessor.cpp │ └── PluginEditor.cpp ├── AmplitudeModulator │ ├── gui │ │ └── popupinformationtext.hpp │ ├── CMakeLists.txt │ ├── PluginEditor.h │ ├── dsp │ │ ├── dspcore.hpp │ │ └── dspcore.cpp │ ├── PluginProcessor.h │ ├── parameter.hpp │ ├── PluginProcessor.cpp │ └── PluginEditor.cpp └── EasyOverdrive │ ├── CMakeLists.txt │ ├── gui │ └── popupinformationtext.hpp │ ├── PluginProcessor.h │ ├── dsp │ └── dspcore.hpp │ ├── PluginEditor.h │ ├── PluginProcessor.cpp │ └── parameter.hpp ├── tool ├── README.md └── deploy_windows.py ├── CMakeLists.txt ├── ci ├── ci_macos.sh ├── ci_ubuntu.sh └── ci_windows.ps1 ├── docs └── dev_note.md ├── README.md └── .github └── workflows └── build.yml /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/JUCE"] 2 | path = lib/JUCE 3 | url = https://github.com/juce-framework/JUCE 4 | -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | add_subdirectory(JUCE) 4 | add_subdirectory(Uhhyou) 5 | -------------------------------------------------------------------------------- /plugin/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | # add_subdirectory(MembraneReverb) 4 | # add_subdirectory(MagicFM) 5 | -------------------------------------------------------------------------------- /lib/specialmath/jyzo/test.ps1: -------------------------------------------------------------------------------- 1 | cl.exe /EHsc /std:c++20 /O2 .\test.cpp 2 | test.exe | Out-File -FilePath .\test.json -Encoding 'utf8' 3 | python compare.py 4 | -------------------------------------------------------------------------------- /experimental/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | add_subdirectory(AmplitudeModulator) 4 | add_subdirectory(EasyOverdrive) 5 | add_subdirectory(SlopeFilter) 6 | add_subdirectory(TwoBandStereo) 7 | -------------------------------------------------------------------------------- /tool/README.md: -------------------------------------------------------------------------------- 1 | # Tools 2 | This directory contains tools or helpers for faster development. 3 | 4 | ## `deploy_windows.py` 5 | Copies all VST3 plugins in `build` to a destination path. 6 | 7 | **Notice**: Destination path is fixed to `C:/Program Files/Common Files/VST3`. 8 | -------------------------------------------------------------------------------- /lib/Uhhyou/librarylicense.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Uhhyou { 6 | constexpr const char *libraryLicenseText = R"~^~^~^~^(# License 7 | Copyright 2023 Takamitsu Endo (ryukau@gmail.com). 8 | 9 | SPDX-License-Identifier: AGPL-3.0-only)~^~^~^~^"; 10 | } 11 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | set(CMAKE_CXX_STANDARD 20) 4 | set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64" CACHE STRING "" FORCE) # Build universal binary. 5 | 6 | project(UhhyouPlugins VERSION 0.2.0) 7 | 8 | add_subdirectory(lib) 9 | 10 | # add_subdirectory(plugin) 11 | add_subdirectory(experimental) 12 | -------------------------------------------------------------------------------- /lib/Uhhyou/cmake/generate_cpp_text.cmake: -------------------------------------------------------------------------------- 1 | function(make_includable input_file output_file) 2 | file(READ ${input_file} content) 3 | set(delim "~^~^~^~^") 4 | set(content "namespace Uhhyou {\nconst char* libraryLicenseText = R\"${delim}(${content})${delim}\";\n}") 5 | file(WRITE ${output_file} "${content}") 6 | endfunction(make_includable) 7 | 8 | make_includable(lib/README.md common/librarylicense.hpp) 9 | -------------------------------------------------------------------------------- /ci/ci_macos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Build script for GitHub Actions. 4 | # 5 | 6 | set -e 7 | 8 | cmake --version 9 | cmake -S . -B build -GXcode -DCMAKE_BUILD_TYPE=Release 10 | cmake --build build -j --config Release 11 | 12 | find ./build 13 | 14 | ARTIFACT_DIR="./plugins_macOS" 15 | mkdir -p $ARTIFACT_DIR 16 | 17 | function copyArtifact () { 18 | for artefact in "$1"/**/*_artefacts/ ; do 19 | rm -rf "$artefact"/Release/*.a 20 | cp -r "$artefact"/Release/* $ARTIFACT_DIR 21 | done 22 | } 23 | copyArtifact ./build/experimental 24 | 25 | find $ARTIFACT_DIR # debug 26 | -------------------------------------------------------------------------------- /experimental/SlopeFilter/gui/popupinformationtext.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include 5 | 6 | namespace Uhhyou { 7 | 8 | const char *informationText = JucePlugin_Name " version " JucePlugin_VersionString R"( 9 | 10 | Click outside to close this message. 11 | 12 | ## Controls 13 | ### Knobs & Number Sliders 14 | Right Click: Open host context menu. 15 | Wheel Click: Rotate throught min/default/max value. 16 | Ctrl + Left Click: Reset to default. 17 | Ctrl + Wheel Click: Take floor of current value. 18 | )"; 19 | 20 | } // namespace Uhhyou 21 | -------------------------------------------------------------------------------- /experimental/TwoBandStereo/gui/popupinformationtext.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include 5 | 6 | namespace Uhhyou { 7 | 8 | const char *informationText = JucePlugin_Name " version " JucePlugin_VersionString R"( 9 | 10 | Click outside to close this message. 11 | 12 | ## Controls 13 | ### Knobs & Number Sliders 14 | Right Click: Open host context menu. 15 | Wheel Click: Rotate throught min/default/max value. 16 | Ctrl + Left Click: Reset to default. 17 | Ctrl + Wheel Click: Take floor of current value. 18 | )"; 19 | 20 | } // namespace Uhhyou 21 | -------------------------------------------------------------------------------- /experimental/AmplitudeModulator/gui/popupinformationtext.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include 5 | 6 | namespace Uhhyou { 7 | 8 | const char *informationText = JucePlugin_Name " version " JucePlugin_VersionString R"( 9 | 10 | Click outside to close this message. 11 | 12 | ## Controls 13 | ### Knobs & Number Sliders 14 | Right Click: Open host context menu. 15 | Wheel Click: Rotate throught min/default/max value. 16 | Ctrl + Left Click: Reset to default. 17 | Ctrl + Wheel Click: Take floor of current value. 18 | )"; 19 | 20 | } // namespace Uhhyou 21 | -------------------------------------------------------------------------------- /lib/Uhhyou/cmake/additional_compiler_flag.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | add_library(additional_compiler_flag INTERFACE) 4 | 5 | if((CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 6 | OR(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") 7 | ) 8 | target_compile_options(additional_compiler_flag 9 | INTERFACE 10 | $<$:> 11 | $<$:/fp:fast>) 12 | elseif((CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 13 | OR(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") 14 | OR(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 15 | ) 16 | target_compile_options(additional_compiler_flag 17 | INTERFACE 18 | $<$:> 19 | $<$:-ffast-math>) 20 | endif() 21 | -------------------------------------------------------------------------------- /lib/Uhhyou/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | include("cmake/additional_compiler_flag.cmake") 4 | 5 | # Common components. Separeted to reduce build time. 6 | add_library(UhhyouCommon OBJECT 7 | gui/style.cpp) 8 | 9 | target_link_libraries(UhhyouCommon 10 | PRIVATE 11 | juce::juce_recommended_config_flags 12 | juce::juce_recommended_warning_flags 13 | additional_compiler_flag) 14 | 15 | if(CMAKE_SYSTEM_NAME STREQUAL "Linux") 16 | target_compile_options(UhhyouCommon PRIVATE -fPIC) 17 | endif() 18 | 19 | target_include_directories(UhhyouCommon PUBLIC "..") 20 | target_include_directories(UhhyouCommon PRIVATE 21 | "." 22 | "../JUCE/modules") 23 | target_compile_definitions(UhhyouCommon PRIVATE JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1) 24 | -------------------------------------------------------------------------------- /tool/deploy_windows.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | from pathlib import Path 3 | 4 | 5 | def deployVst3(): 6 | vst3_dir = Path("C:/Program Files/Common Files/VST3") 7 | for category_dir in ["experimental", "plugin"]: 8 | artefact_dir = Path("build") / category_dir 9 | for name in [p.name for p in Path(category_dir).glob("*/")]: 10 | src = artefact_dir / Path( 11 | f"{name}/{name}_artefacts/Release/VST3/{name}.vst3" 12 | ) 13 | dst = vst3_dir / Path(name) 14 | 15 | try: 16 | shutil.copytree(src, dst, dirs_exist_ok=True) 17 | except FileNotFoundError: 18 | print(f"{name} have not been built.") 19 | 20 | 21 | if __name__ == "__main__": 22 | deployVst3() 23 | -------------------------------------------------------------------------------- /lib/specialmath/jyzo/compare.py: -------------------------------------------------------------------------------- 1 | import json 2 | import numpy as np 3 | 4 | with open("test.json", "r", encoding="utf-8") as fp: 5 | test = json.load(fp) 6 | 7 | with open("target.json", "r", encoding="utf-8") as fp: 8 | target = json.load(fp) 9 | 10 | for idx in range(len(test)): 11 | d0 = test[idx] 12 | d1 = target[idx] 13 | 14 | if d0["n"] != d1["n"]: 15 | print(f"Mismatched `n`, {idx}, {d0['n']}, {d1['n']}") 16 | exit() 17 | 18 | nt = min([d0["nt"], d1["nt"]]) 19 | 20 | np.testing.assert_allclose(np.array(d0["rj0"][:nt]), np.array(d1["rj0"][:nt])) 21 | np.testing.assert_allclose(np.array(d0["rj1"][:nt]), np.array(d1["rj1"][:nt])) 22 | np.testing.assert_allclose(np.array(d0["ry0"][:nt]), np.array(d1["ry0"][:nt])) 23 | np.testing.assert_allclose(np.array(d0["ry1"][:nt]), np.array(d1["ry1"][:nt])) 24 | -------------------------------------------------------------------------------- /lib/specialmath/jyzo/test.cpp: -------------------------------------------------------------------------------- 1 | #include "./jyzo.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | template std::string strFromVec(std::vector &vec) 9 | { 10 | std::string text = "["; 11 | for (const auto &x : vec) text += std::format("{},", x); 12 | text.pop_back(); 13 | text += "]"; 14 | return text; 15 | } 16 | 17 | int main() 18 | { 19 | constexpr int nt = 8; 20 | std::string text = "[\n"; 21 | for (int n = 0; n < 16; ++n) { 22 | auto [rj0, rj1, ry0, ry1] = Uhhyou::jyzo(n, nt); 23 | text += std::format( 24 | "{{\"n\": {},\n\"nt\": {},\n\"rj0\": {},\n\"rj1\": {},\n\"ry0\": {},\n\"ry1\": " 25 | "{}\n}},\n", 26 | n, nt, strFromVec(rj0), strFromVec(rj1), strFromVec(ry0), strFromVec(ry1)); 27 | } 28 | text.erase(text.end() - 2); 29 | std::cout << text << "]\n"; 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /experimental/SlopeFilter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | juce_add_plugin(SlopeFilter 4 | PRODUCT_NAME "SlopeFilter" 5 | VERSION "0.0.4" 6 | COMPANY_NAME "UhhyouPlugins" 7 | COMPANY_EMAIL "ryukau@gmail.com" 8 | COMPANY_COPYRIGHT "Copyright 2023 Takamitsu Endo." 9 | PLUGIN_MANUFACTURER_CODE "Uhyo" 10 | PLUGIN_CODE "sx8o" 11 | NEEDS_MIDI_INPUT TRUE 12 | EDITOR_WANTS_KEYBOARD_FOCUS TRUE 13 | FORMATS AU VST3) 14 | 15 | target_sources(SlopeFilter 16 | PRIVATE 17 | dsp/dspcore.cpp 18 | PluginEditor.cpp 19 | PluginProcessor.cpp) 20 | 21 | target_compile_definitions(SlopeFilter 22 | PUBLIC 23 | JUCE_WEB_BROWSER=0 24 | JUCE_USE_CURL=0 25 | JUCE_VST3_CAN_REPLACE_VST2=0) 26 | 27 | target_link_libraries(SlopeFilter 28 | PRIVATE 29 | UhhyouCommon 30 | juce::juce_audio_utils 31 | PUBLIC 32 | juce::juce_recommended_config_flags 33 | juce::juce_recommended_warning_flags 34 | additional_compiler_flag) 35 | -------------------------------------------------------------------------------- /experimental/EasyOverdrive/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | juce_add_plugin(EasyOverdrive 4 | PRODUCT_NAME "EasyOverdrive" 5 | VERSION "0.0.4" 6 | COMPANY_NAME "UhhyouPlugins" 7 | COMPANY_EMAIL "ryukau@gmail.com" 8 | COMPANY_COPYRIGHT "Copyright 2023 Takamitsu Endo." 9 | PLUGIN_MANUFACTURER_CODE "Uhyo" 10 | PLUGIN_CODE "4r89" 11 | NEEDS_MIDI_INPUT TRUE 12 | EDITOR_WANTS_KEYBOARD_FOCUS TRUE 13 | FORMATS AU VST3) 14 | 15 | target_sources(EasyOverdrive 16 | PRIVATE 17 | dsp/dspcore.cpp 18 | PluginEditor.cpp 19 | PluginProcessor.cpp) 20 | 21 | target_compile_definitions(EasyOverdrive 22 | PUBLIC 23 | JUCE_WEB_BROWSER=0 24 | JUCE_USE_CURL=0 25 | JUCE_VST3_CAN_REPLACE_VST2=0) 26 | 27 | target_link_libraries(EasyOverdrive 28 | PRIVATE 29 | UhhyouCommon 30 | juce::juce_audio_utils 31 | PUBLIC 32 | juce::juce_recommended_config_flags 33 | juce::juce_recommended_warning_flags 34 | additional_compiler_flag) 35 | -------------------------------------------------------------------------------- /experimental/TwoBandStereo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | juce_add_plugin(TwoBandStereo 4 | PRODUCT_NAME "TwoBandStereo" 5 | VERSION "0.0.4" 6 | COMPANY_NAME "UhhyouPlugins" 7 | COMPANY_EMAIL "ryukau@gmail.com" 8 | COMPANY_COPYRIGHT "Copyright 2024 Takamitsu Endo." 9 | PLUGIN_MANUFACTURER_CODE "Uhyo" 10 | PLUGIN_CODE "u6um" 11 | NEEDS_MIDI_INPUT TRUE 12 | EDITOR_WANTS_KEYBOARD_FOCUS TRUE 13 | FORMATS AU VST3) 14 | 15 | target_sources(TwoBandStereo 16 | PRIVATE 17 | dsp/dspcore.cpp 18 | PluginEditor.cpp 19 | PluginProcessor.cpp) 20 | 21 | target_compile_definitions(TwoBandStereo 22 | PUBLIC 23 | JUCE_WEB_BROWSER=0 24 | JUCE_USE_CURL=0 25 | JUCE_VST3_CAN_REPLACE_VST2=0) 26 | 27 | target_link_libraries(TwoBandStereo 28 | PRIVATE 29 | UhhyouCommon 30 | juce::juce_audio_utils 31 | PUBLIC 32 | juce::juce_recommended_config_flags 33 | juce::juce_recommended_warning_flags 34 | additional_compiler_flag) 35 | -------------------------------------------------------------------------------- /experimental/AmplitudeModulator/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | juce_add_plugin(AmplitudeModulator 4 | PRODUCT_NAME "AmplitudeModulator" 5 | VERSION "0.0.4" 6 | COMPANY_NAME "UhhyouPlugins" 7 | COMPANY_EMAIL "ryukau@gmail.com" 8 | COMPANY_COPYRIGHT "Copyright 2024 Takamitsu Endo." 9 | PLUGIN_MANUFACTURER_CODE "Uhyo" 10 | PLUGIN_CODE "d5bd" 11 | NEEDS_MIDI_INPUT TRUE 12 | EDITOR_WANTS_KEYBOARD_FOCUS TRUE 13 | FORMATS AU VST3) 14 | 15 | target_sources(AmplitudeModulator 16 | PRIVATE 17 | dsp/dspcore.cpp 18 | PluginEditor.cpp 19 | PluginProcessor.cpp) 20 | 21 | target_compile_definitions(AmplitudeModulator 22 | PUBLIC 23 | JUCE_WEB_BROWSER=0 24 | JUCE_USE_CURL=0 25 | JUCE_VST3_CAN_REPLACE_VST2=0) 26 | 27 | target_link_libraries(AmplitudeModulator 28 | PRIVATE 29 | UhhyouCommon 30 | juce::juce_audio_utils 31 | PUBLIC 32 | juce::juce_recommended_config_flags 33 | juce::juce_recommended_warning_flags 34 | additional_compiler_flag) 35 | -------------------------------------------------------------------------------- /ci/ci_ubuntu.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Build script for GitHub Actions. 4 | # 5 | 6 | set -e 7 | shopt -s globstar 8 | 9 | # Dependencies are based on `JUCE/docs/Linux Dependencies.md`. 10 | sudo apt update 11 | sudo apt install libasound2-dev libjack-jackd2-dev \ 12 | ladspa-sdk \ 13 | libcurl4-openssl-dev \ 14 | libfreetype-dev libfontconfig1-dev \ 15 | libx11-dev libxcomposite-dev libxcursor-dev libxext-dev libxinerama-dev libxrandr-dev libxrender-dev \ 16 | libwebkit2gtk-4.1-dev \ 17 | libglu1-mesa-dev mesa-common-dev 18 | 19 | cmake --version 20 | cmake -S . -B build -DCMAKE_BUILD_TYPE=Release 21 | cmake --build build 22 | 23 | echo "UhhyouDebug: Printing ./build" 24 | tree ./build 25 | 26 | ARTIFACT_DIR="./plugins_ubuntu" 27 | mkdir -p $ARTIFACT_DIR 28 | 29 | function copyArtifact () { 30 | for artefact in "$1"/**/*_artefacts/ ; do 31 | rm -rf "$artefact"/Release/*.a 32 | cp -r "$artefact"/Release/* $ARTIFACT_DIR 33 | done 34 | } 35 | copyArtifact ./build/experimental 36 | 37 | echo "UhhyouDebug: Printing $ARTIFACT_DIR" 38 | tree $ARTIFACT_DIR # debug 39 | -------------------------------------------------------------------------------- /ci/ci_windows.ps1: -------------------------------------------------------------------------------- 1 | # 2 | # Build script for GitHub Actions. 3 | # 4 | 5 | $ErrorActionPreference = "Stop" 6 | 7 | New-Item -Path build -ItemType "directory" -Force 8 | 9 | cmake --version 10 | cmake -S . -B build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=Release 11 | cmake --build build -j --config Release 12 | 13 | # https://gitlab.com/gitlab-org/gitlab-runner/issues/3194#note_196458158 14 | if (!$?) { Exit $LASTEXITCODE } 15 | 16 | Write-Output "UhhyouDebug: Printing ./build" 17 | tree /A /F ./build # debug 18 | 19 | $ARTIFACT_DIR = ".\plugins_windows" 20 | New-Item -Path "$ARTIFACT_DIR" -ItemType "directory" -Force 21 | 22 | function CopyArtifacts { 23 | param ([string]$SearchRootPath) 24 | 25 | Get-ChildItem -Path $SearchRootPath -Recurse -Filter "*_artefacts" | 26 | ForEach-Object { 27 | $PLUGIN_DIR = "$($_.FullName)\Release\VST3" 28 | tree /A /F $PLUGIN_DIR # debug 29 | Copy-Item -Recurse -Path "$PLUGIN_DIR\*.vst3" -Destination $ARTIFACT_DIR -Force 30 | } 31 | } 32 | CopyArtifacts ".\build\experimental" 33 | 34 | Write-Output "UhhyouDebug: Printing $ARTIFACT_DIR" 35 | tree /A /F $ARTIFACT_DIR # debug 36 | -------------------------------------------------------------------------------- /experimental/SlopeFilter/dsp/dspcore.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include "../parameter.hpp" 7 | #include "./filter.hpp" 8 | #include "Uhhyou/dsp/basiclimiter.hpp" 9 | #include "Uhhyou/dsp/multirate.hpp" 10 | #include "Uhhyou/dsp/smoother.hpp" 11 | #include "Uhhyou/dsp/svf.hpp" 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | namespace Uhhyou { 18 | 19 | class DSPCore { 20 | public: 21 | DSPCore(ParameterStore ¶m) : param(param) {} 22 | 23 | ParameterStore ¶m; 24 | bool isPlaying = false; 25 | double tempo = double(120); 26 | double beatsElapsed = double(0); 27 | double timeSigUpper = double(1); 28 | double timeSigLower = double(4); 29 | 30 | void setup(double sampleRate); 31 | void reset(); 32 | void startup(); 33 | size_t getLatency(); 34 | void setParameters(); 35 | void process( 36 | const size_t length, const float *in0, const float *in1, float *out0, float *out1); 37 | 38 | private: 39 | double sampleRate = 44100; 40 | std::array, 2> slopeFilter; 41 | }; 42 | 43 | } // namespace Uhhyou 44 | -------------------------------------------------------------------------------- /docs/dev_note.md: -------------------------------------------------------------------------------- 1 | # Developing on JUCE 2 | This text is based on JUCE 8.0.6. 3 | 4 | ## Build Time 5 | Build time is slow. It seems that following sources are repeatedly built for each plugins. 6 | 7 | ``` 8 | juce_audio_processors.cpp 9 | juce_audio_processors_ara.cpp 10 | juce_audio_processors_lv2_libs.cpp 11 | juce_gui_extra.cpp 12 | juce_gui_basics.cpp 13 | juce_graphics.cpp 14 | juce_graphics_Harfbuzz.cpp 15 | juce_events.cpp 16 | juce_core.cpp 17 | juce_core_CompilationTime.cpp 18 | juce_data_structures.cpp 19 | juce_audio_basics.cpp 20 | juce_graphics_Sheenbidi.c 21 | juce_audio_plugin_client_AAX.cpp 22 | juce_audio_plugin_client_AAX_utils.cpp 23 | juce_audio_plugin_client_ARA.cpp 24 | juce_audio_plugin_client_LV2.cpp 25 | juce_audio_plugin_client_Standalone.cpp 26 | juce_audio_plugin_client_Unity.cpp 27 | juce_audio_plugin_client_VST2.cpp 28 | juce_audio_plugin_client_VST3.cpp 29 | ``` 30 | 31 | `juce_add_plugin()` adds these sources. To speed up build process, they might be separately built as an object library. 32 | 33 | Considerations if trying to bypass `juce_add_plugin()`: 34 | 35 | - `juce_add_plugin()` provides switches to toggle `#ifdef` in these sources. Instead, plugin sources should explicitly `#define` every switches. 36 | - One has to write own cmake to bundle each plugin formats. 37 | -------------------------------------------------------------------------------- /experimental/TwoBandStereo/dsp/dspcore.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include "../parameter.hpp" 7 | #include "./crossover.hpp" 8 | #include "Uhhyou/dsp/basiclimiter.hpp" 9 | #include "Uhhyou/dsp/multirate.hpp" 10 | #include "Uhhyou/dsp/smoother.hpp" 11 | #include "Uhhyou/dsp/svf.hpp" 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | namespace Uhhyou { 18 | 19 | class DSPCore { 20 | public: 21 | DSPCore(ParameterStore ¶m) : param(param) {} 22 | 23 | ParameterStore ¶m; 24 | bool isPlaying = false; 25 | double tempo = double(120); 26 | double beatsElapsed = double(0); 27 | double timeSigUpper = double(1); 28 | double timeSigLower = double(4); 29 | 30 | void setup(double sampleRate); 31 | void reset(); 32 | void startup(); 33 | size_t getLatency(); 34 | void setParameters(); 35 | void process( 36 | const size_t length, const float *in0, const float *in1, float *out0, float *out1); 37 | 38 | private: 39 | double sampleRate = 44100; 40 | 41 | ExpSmoother crossoverFreq; 42 | ExpSmoother lowerStereoSpread; 43 | ExpSmoother upperStereoSpread; 44 | std::array, 2> crossoverFilter; 45 | }; 46 | 47 | } // namespace Uhhyou 48 | -------------------------------------------------------------------------------- /experimental/EasyOverdrive/gui/popupinformationtext.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include 5 | 6 | namespace Uhhyou { 7 | 8 | const char *informationText = JucePlugin_Name " version " JucePlugin_VersionString R"( 9 | 10 | Click outside to close this message. 11 | 12 | ## Controls 13 | ### Knobs & Number Sliders 14 | Right Click: Open host context menu. 15 | Wheel Click: Rotate throught min/default/max value. 16 | Ctrl + Left Click: Reset to default. 17 | Ctrl + Wheel Click: Take floor of current value. 18 | 19 | ### BarBox 20 | Barbox takes keyboard focus when clicked, or mouse wheel is rotated on it. 21 | 22 | List of keyboard shortcuts: 23 | 24 | - A: 'A'lternate between positive and negative sign. 25 | - D: Reset to default. 26 | - Shift + D: Toggle min/default/max. 27 | - E: Emphasize low. 28 | - Shift + E: Emphasize high. 29 | - F: Apply low-pass filter. 30 | - Shift + F: Apply high-pass filter. 31 | - I: Full invert. 32 | - Shift + I: Invert depending on sign. 33 | - L: Lock. 34 | - Shift + L: Lock all. 35 | - N: Normalize depending on sign. 36 | - Shift + N: Full normalize. 37 | - P: Permute. 38 | - R: Randomize. 39 | - Shift + R: Sparse randomize. 40 | - S: Sort descending order. 41 | - Shift + S: Sort ascending order. 42 | - T: Subtle randomize, or random walk. 43 | - Shift + T: Subtle randomize. Moving towards 0. 44 | - Z: Undo. This is only effective for a BarBox. 45 | - Shift + Z: Redo. This is only effective for a BarBox. 46 | - , (Comma): Rotate backward. 47 | - . (Period): Rotate forward. 48 | - 1-4: Decrease 1-4n. 49 | - 5-9: Decimate and hold 2-6 samples. 50 | )"; 51 | 52 | } // namespace Uhhyou 53 | -------------------------------------------------------------------------------- /experimental/TwoBandStereo/PluginEditor.h: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include "Uhhyou/gui/widgets.hpp" 7 | 8 | #include "PluginProcessor.h" 9 | 10 | #include 11 | #include 12 | 13 | namespace Uhhyou { 14 | 15 | class Editor final : public juce::AudioProcessorEditor { 16 | public: 17 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Editor) 18 | 19 | explicit Editor(Processor &); 20 | ~Editor() override; 21 | 22 | void paint(juce::Graphics &) override; 23 | void resized() override; 24 | 25 | virtual void mouseDown(const juce::MouseEvent &) override 26 | { 27 | numberEditor.setVisible(false); 28 | } 29 | 30 | private: // JUCE related internals. 31 | Processor &processor; 32 | Palette palette; 33 | juce::LookAndFeel_V4 lookAndFeel; 34 | 35 | inline juce::ValueTree getStateTree() 36 | { 37 | return processor.param.tree.state.getOrCreateChildWithName("GUI", nullptr); 38 | } 39 | 40 | private: // Action items. 41 | StatusBar statusBar; 42 | NumberEditor numberEditor; 43 | PopUpButton pluginNameButton; 44 | ActionButton<> undoButton; 45 | ActionButton<> redoButton; 46 | ActionButton<> randomizeButton; 47 | std::unique_ptr fileChooser; 48 | PresetManager presetManager; 49 | 50 | private: // Controls tied to parameters. 51 | TextKnob crossoverHz; 52 | TextKnob upperStereoSpread; 53 | TextKnob lowerStereoSpread; 54 | 55 | private: // Drawing items. 56 | std::vector lines; 57 | std::vector labels; 58 | std::vector groupLabels; 59 | }; 60 | 61 | } // namespace Uhhyou 62 | -------------------------------------------------------------------------------- /lib/Uhhyou/dsp/svf.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace Uhhyou { 13 | 14 | namespace SVFTool { 15 | 16 | constexpr double minCutoff = 0.00001; 17 | constexpr double nyquist = 0.49998; 18 | constexpr double maxFlatK 19 | = double(1) / (std::numbers::sqrt2_v - std::numeric_limits::epsilon()); 20 | 21 | template inline T freqToG(T normalizedFreq) 22 | { 23 | return T(std::tan( 24 | std::clamp(double(normalizedFreq), minCutoff, nyquist) * std::numbers::pi_v)); 25 | } 26 | 27 | template inline T qToK(T Q) 28 | { 29 | if (Q < std::numeric_limits::epsilon()) Q = std::numeric_limits::epsilon(); 30 | return T(1) / Q; 31 | } 32 | 33 | } // namespace SVFTool 34 | 35 | template class SVF { 36 | private: 37 | Sample ic1eq = 0; 38 | Sample ic2eq = 0; 39 | 40 | inline std::array processInternal(Sample v0, Sample g, Sample k) 41 | { 42 | Sample v1 = (ic1eq + g * (v0 - ic2eq)) / (Sample(1) + g * (g + k)); 43 | Sample v2 = ic2eq + g * v1; 44 | ic1eq = Sample(2) * v1 - ic1eq; 45 | ic2eq = Sample(2) * v2 - ic2eq; 46 | return {v1, v2}; 47 | } 48 | 49 | public: 50 | void reset() 51 | { 52 | ic1eq = 0; 53 | ic2eq = 0; 54 | } 55 | 56 | Sample lowpass(Sample v0, Sample g, Sample k) 57 | { 58 | auto val = processInternal(v0, g, k); 59 | return val[1]; 60 | } 61 | 62 | Sample highpass(Sample v0, Sample g, Sample k) 63 | { 64 | auto val = processInternal(v0, g, k); 65 | return v0 - k * val[0] - val[1]; 66 | } 67 | }; 68 | 69 | } // namespace Uhhyou 70 | -------------------------------------------------------------------------------- /experimental/SlopeFilter/PluginEditor.h: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include "Uhhyou/gui/widgets.hpp" 7 | 8 | #include "PluginProcessor.h" 9 | 10 | #include 11 | #include 12 | 13 | namespace Uhhyou { 14 | 15 | class Editor final : public juce::AudioProcessorEditor { 16 | public: 17 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Editor) 18 | 19 | explicit Editor(Processor &); 20 | ~Editor() override; 21 | 22 | void paint(juce::Graphics &) override; 23 | void resized() override; 24 | 25 | virtual void mouseDown(const juce::MouseEvent &) override 26 | { 27 | numberEditor.setVisible(false); 28 | } 29 | 30 | private: // JUCE related internals. 31 | Processor &processor; 32 | Palette palette; 33 | juce::LookAndFeel_V4 lookAndFeel; 34 | 35 | inline juce::ValueTree getStateTree() 36 | { 37 | return processor.param.tree.state.getOrCreateChildWithName("GUI", nullptr); 38 | } 39 | 40 | private: // Action items. 41 | StatusBar statusBar; 42 | NumberEditor numberEditor; 43 | PopUpButton pluginNameButton; 44 | ActionButton<> undoButton; 45 | ActionButton<> redoButton; 46 | ActionButton<> randomizeButton; 47 | std::unique_ptr fileChooser; 48 | PresetManager presetManager; 49 | 50 | private: // Controls tied to parameters. 51 | ComboBox shelvingType; 52 | TextKnob startHz; 53 | TextKnob slopeDecibel; 54 | TextKnob outputGain; 55 | 56 | private: // Drawing items. 57 | std::vector lines; 58 | std::vector labels; 59 | std::vector groupLabels; 60 | }; 61 | 62 | } // namespace Uhhyou 63 | -------------------------------------------------------------------------------- /experimental/AmplitudeModulator/PluginEditor.h: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include "Uhhyou/gui/widgets.hpp" 7 | 8 | #include "PluginProcessor.h" 9 | 10 | #include 11 | #include 12 | 13 | namespace Uhhyou { 14 | 15 | class Editor final : public juce::AudioProcessorEditor { 16 | public: 17 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Editor) 18 | 19 | explicit Editor(Processor &); 20 | ~Editor() override; 21 | 22 | void paint(juce::Graphics &) override; 23 | void resized() override; 24 | 25 | virtual void mouseDown(const juce::MouseEvent &) override 26 | { 27 | numberEditor.setVisible(false); 28 | } 29 | 30 | private: // JUCE related internals. 31 | Processor &processor; 32 | Palette palette; 33 | juce::LookAndFeel_V4 lookAndFeel; 34 | 35 | inline juce::ValueTree getStateTree() 36 | { 37 | return processor.param.tree.state.getOrCreateChildWithName("GUI", nullptr); 38 | } 39 | 40 | private: // Action items. 41 | StatusBar statusBar; 42 | NumberEditor numberEditor; 43 | PopUpButton pluginNameButton; 44 | ActionButton<> undoButton; 45 | ActionButton<> redoButton; 46 | ActionButton<> randomizeButton; 47 | std::unique_ptr fileChooser; 48 | PresetManager presetManager; 49 | 50 | private: // Controls tied to parameters. 51 | ComboBox amType; 52 | ToggleButton swapCarriorAndModulator; 53 | TextKnob carriorSideBandMix; 54 | TextKnob outputGain; 55 | 56 | private: // Drawing items. 57 | std::vector lines; 58 | std::vector labels; 59 | std::vector groupLabels; 60 | }; 61 | 62 | } // namespace Uhhyou 63 | -------------------------------------------------------------------------------- /experimental/AmplitudeModulator/dsp/dspcore.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include "../parameter.hpp" 7 | #include "./am.hpp" 8 | #include "Uhhyou/dsp/basiclimiter.hpp" 9 | #include "Uhhyou/dsp/multirate.hpp" 10 | #include "Uhhyou/dsp/smoother.hpp" 11 | #include "Uhhyou/dsp/svf.hpp" 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | namespace Uhhyou { 18 | 19 | class DSPCore { 20 | public: 21 | DSPCore(ParameterStore ¶m) : param(param) {} 22 | 23 | ParameterStore ¶m; 24 | bool isPlaying = false; 25 | double tempo = double(120); 26 | double beatsElapsed = double(0); 27 | double timeSigUpper = double(1); 28 | double timeSigLower = double(4); 29 | 30 | void setup(double sampleRate); 31 | void reset(); 32 | void startup(); 33 | size_t getLatency(); 34 | void setParameters(); 35 | void process( 36 | const size_t length, 37 | const float *inCar0, 38 | const float *inCar1, 39 | const float *inMod0, 40 | const float *inMod1, 41 | float *out0, 42 | float *out1); 43 | 44 | private: 45 | double sampleRate = 44100; 46 | 47 | size_t amType = 0; 48 | bool swapCarriorAndModulator = false; 49 | ExpSmoother carriorSideBandMix; 50 | ExpSmoother outputGain; 51 | 52 | // `AA` is short for anti-aliasing. `Naive` means no anti-alising here. 53 | std::array, 2> amNaive; 54 | std::array, 2> amUsbNaive; 55 | std::array, 2> amLsbNaive; 56 | std::array, 2> amUpperAA; 57 | std::array, 2> amFullAA; 58 | std::array, 2> amUsbAA; 59 | std::array, 2> amLsbAA; 60 | }; 61 | 62 | } // namespace Uhhyou 63 | -------------------------------------------------------------------------------- /experimental/EasyOverdrive/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "dsp/dspcore.hpp" 11 | #include "parameter.hpp" 12 | 13 | #include 14 | 15 | class Processor final : public juce::AudioProcessor { 16 | public: 17 | Processor(); 18 | ~Processor() override; 19 | 20 | void prepareToPlay(double sampleRate, int samplesPerBlock) override; 21 | void releaseResources() override; 22 | 23 | bool isBusesLayoutSupported(const BusesLayout &layouts) const override; 24 | 25 | void processBlock(juce::AudioBuffer &, juce::MidiBuffer &) override; 26 | using AudioProcessor::processBlock; 27 | 28 | juce::AudioProcessorEditor *createEditor() override; 29 | bool hasEditor() const override; 30 | 31 | const juce::String getName() const override; 32 | 33 | bool acceptsMidi() const override; 34 | bool producesMidi() const override; 35 | bool isMidiEffect() const override; 36 | double getTailLengthSeconds() const override; 37 | 38 | int getNumPrograms() override; 39 | int getCurrentProgram() override; 40 | void setCurrentProgram(int index) override; 41 | const juce::String getProgramName(int index) override; 42 | void changeProgramName(int index, const juce::String &newName) override; 43 | 44 | void getStateInformation(juce::MemoryBlock &destData) override; 45 | void setStateInformation(const void *data, int sizeInBytes) override; 46 | 47 | public: 48 | juce::UndoManager undoManager{32768, 512}; 49 | 50 | Uhhyou::ParameterStore param; 51 | Uhhyou::DSPCore dsp; 52 | double previousSampleRate = double(-1); 53 | 54 | private: 55 | std::mutex setupMutex; 56 | 57 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Processor) 58 | }; 59 | -------------------------------------------------------------------------------- /experimental/SlopeFilter/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "dsp/dspcore.hpp" 11 | #include "parameter.hpp" 12 | 13 | #include 14 | 15 | class Processor final : public juce::AudioProcessor { 16 | public: 17 | Processor(); 18 | ~Processor() override; 19 | 20 | void prepareToPlay(double sampleRate, int samplesPerBlock) override; 21 | void releaseResources() override; 22 | 23 | bool isBusesLayoutSupported(const BusesLayout &layouts) const override; 24 | 25 | void processBlock(juce::AudioBuffer &, juce::MidiBuffer &) override; 26 | using AudioProcessor::processBlock; 27 | 28 | juce::AudioProcessorEditor *createEditor() override; 29 | bool hasEditor() const override; 30 | 31 | const juce::String getName() const override; 32 | 33 | bool acceptsMidi() const override; 34 | bool producesMidi() const override; 35 | bool isMidiEffect() const override; 36 | double getTailLengthSeconds() const override; 37 | 38 | int getNumPrograms() override; 39 | int getCurrentProgram() override; 40 | void setCurrentProgram(int index) override; 41 | const juce::String getProgramName(int index) override; 42 | void changeProgramName(int index, const juce::String &newName) override; 43 | 44 | void getStateInformation(juce::MemoryBlock &destData) override; 45 | void setStateInformation(const void *data, int sizeInBytes) override; 46 | 47 | public: 48 | juce::UndoManager undoManager{32768, 512}; 49 | 50 | Uhhyou::ParameterStore param; 51 | Uhhyou::DSPCore dsp; 52 | double previousSampleRate = double(-1); 53 | 54 | private: 55 | std::mutex setupMutex; 56 | 57 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Processor) 58 | }; 59 | -------------------------------------------------------------------------------- /experimental/TwoBandStereo/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "dsp/dspcore.hpp" 11 | #include "parameter.hpp" 12 | 13 | #include 14 | 15 | class Processor final : public juce::AudioProcessor { 16 | public: 17 | Processor(); 18 | ~Processor() override; 19 | 20 | void prepareToPlay(double sampleRate, int samplesPerBlock) override; 21 | void releaseResources() override; 22 | 23 | bool isBusesLayoutSupported(const BusesLayout &layouts) const override; 24 | 25 | void processBlock(juce::AudioBuffer &, juce::MidiBuffer &) override; 26 | using AudioProcessor::processBlock; 27 | 28 | juce::AudioProcessorEditor *createEditor() override; 29 | bool hasEditor() const override; 30 | 31 | const juce::String getName() const override; 32 | 33 | bool acceptsMidi() const override; 34 | bool producesMidi() const override; 35 | bool isMidiEffect() const override; 36 | double getTailLengthSeconds() const override; 37 | 38 | int getNumPrograms() override; 39 | int getCurrentProgram() override; 40 | void setCurrentProgram(int index) override; 41 | const juce::String getProgramName(int index) override; 42 | void changeProgramName(int index, const juce::String &newName) override; 43 | 44 | void getStateInformation(juce::MemoryBlock &destData) override; 45 | void setStateInformation(const void *data, int sizeInBytes) override; 46 | 47 | public: 48 | juce::UndoManager undoManager{32768, 512}; 49 | 50 | Uhhyou::ParameterStore param; 51 | Uhhyou::DSPCore dsp; 52 | double previousSampleRate = double(-1); 53 | 54 | private: 55 | std::mutex setupMutex; 56 | 57 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Processor) 58 | }; 59 | -------------------------------------------------------------------------------- /experimental/AmplitudeModulator/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "dsp/dspcore.hpp" 11 | #include "parameter.hpp" 12 | 13 | #include 14 | 15 | class Processor final : public juce::AudioProcessor { 16 | public: 17 | Processor(); 18 | ~Processor() override; 19 | 20 | void prepareToPlay(double sampleRate, int samplesPerBlock) override; 21 | void releaseResources() override; 22 | 23 | bool isBusesLayoutSupported(const BusesLayout &layouts) const override; 24 | 25 | void processBlock(juce::AudioBuffer &, juce::MidiBuffer &) override; 26 | using AudioProcessor::processBlock; 27 | 28 | juce::AudioProcessorEditor *createEditor() override; 29 | bool hasEditor() const override; 30 | 31 | const juce::String getName() const override; 32 | 33 | bool acceptsMidi() const override; 34 | bool producesMidi() const override; 35 | bool isMidiEffect() const override; 36 | double getTailLengthSeconds() const override; 37 | 38 | int getNumPrograms() override; 39 | int getCurrentProgram() override; 40 | void setCurrentProgram(int index) override; 41 | const juce::String getProgramName(int index) override; 42 | void changeProgramName(int index, const juce::String &newName) override; 43 | 44 | void getStateInformation(juce::MemoryBlock &destData) override; 45 | void setStateInformation(const void *data, int sizeInBytes) override; 46 | 47 | public: 48 | juce::UndoManager undoManager{32768, 512}; 49 | 50 | Uhhyou::ParameterStore param; 51 | Uhhyou::DSPCore dsp; 52 | double previousSampleRate = double(-1); 53 | 54 | private: 55 | std::mutex setupMutex; 56 | 57 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Processor) 58 | }; 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Uhhyou Plugins on JUCE 2 | Audio plugins writtein on [JUCE](https://github.com/juce-framework/JUCE). 3 | 4 | Currently, everything on this repository is experimental. Always render the result to audio file after using the plugins in this repository. Breaking changes might be introduced in future. 5 | 6 | ## Build Instruction 7 | Following softwares are required. 8 | 9 | - C++ compiler 10 | - [Visual Studio Community](https://visualstudio.microsoft.com/vs/community/) on Windows. ["Desktop development with C++"](https://learn.microsoft.com/en-us/cpp/build/vscpp-step-0-installation?view=msvc-170#step-4---choose-workloads) package is required. 11 | - [Xcode](https://developer.apple.com/xcode/) on macOS. 12 | - `g++` on Linux. See [JUCE documentation](https://github.com/juce-framework/JUCE/blob/master/docs/Linux%20Dependencies.md) for other dependencies. 13 | - [CMake](https://cmake.org/) 14 | - [Git](https://git-scm.com/) 15 | 16 | After installation, run following commands on terminal. 17 | 18 | ```bash 19 | git clone --recursive $URL_of_this_repository 20 | cd UhhyouPluginsJuce 21 | 22 | cmake -S . -B build -DCMAKE_BUILD_TYPE=Release 23 | cmake --build build --config Release 24 | ``` 25 | 26 | Plugins will be built into `build/*_artefacts/Release` where `*` is plugin name. 27 | 28 | ## Directory Structure 29 | - `.github/workflows` and `ci`: CI scripts for GitHub Actions. 30 | - `common`: Common components across plugins. 31 | - `lib`: External libraries. 32 | - `tool`: Development tools. 33 | 34 | Other directories are individual plugins. 35 | 36 | ## List of Plugins 37 | - AmplitudeModulator: A showcase of AM anti-aliasing techniques. It has 2 stereo inputs for modulator and carrior, but doesn't have built in oscillator. 38 | - EasyOverdrive: Simple overdrive. 39 | - SlopeFilter: IIR filter to apply arbitrary dB/oct slope. 40 | - TwoBandStereo: 2-band stereo merger. 41 | 42 | ## License 43 | AGPL-3.0-only. See `lib/README.md` for complete licenses which includes the ones from libraries. 44 | -------------------------------------------------------------------------------- /experimental/EasyOverdrive/dsp/dspcore.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include "../parameter.hpp" 7 | #include "./overdrive.hpp" 8 | #include "Uhhyou/dsp/basiclimiter.hpp" 9 | #include "Uhhyou/dsp/multirate.hpp" 10 | #include "Uhhyou/dsp/smoother.hpp" 11 | #include "Uhhyou/dsp/svf.hpp" 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | namespace Uhhyou { 18 | 19 | class DSPCore { 20 | public: 21 | DSPCore(ParameterStore ¶m) : param(param) {} 22 | 23 | ParameterStore ¶m; 24 | bool isPlaying = false; 25 | double tempo = double(120); 26 | double beatsElapsed = double(0); 27 | double timeSigUpper = double(1); 28 | double timeSigLower = double(4); 29 | 30 | void setup(double sampleRate); 31 | void reset(); 32 | void startup(); 33 | size_t getLatency(); 34 | void setParameters(); 35 | void process( 36 | const size_t length, const float *in0, const float *in1, float *out0, float *out1); 37 | 38 | private: 39 | void updateUpRate(); 40 | std::array processFrame(const std::array &frame); 41 | 42 | static constexpr size_t upFold = 16; 43 | static constexpr std::array fold{1, 2, upFold}; 44 | 45 | double sampleRate = 44100; 46 | double upRate = upFold * 44100; 47 | 48 | size_t oversampling = 1; 49 | size_t overDriveType = 0; 50 | bool asymDriveEnabled = true; 51 | bool limiterEnabled = true; 52 | 53 | ExpSmoother preDriveGain; 54 | ExpSmoother limiterInputGain; 55 | ExpSmoother postDriveGain; 56 | 57 | std::array, 2> overDrive; 58 | std::array, 2> asymDrive; 59 | std::array, 2> limiter; 60 | 61 | std::array, 2> upSampler; 62 | std::array>, 2> decimationLowpass; 63 | std::array>, 2> halfbandIir; 64 | }; 65 | 66 | } // namespace Uhhyou 67 | -------------------------------------------------------------------------------- /experimental/SlopeFilter/dsp/dspcore.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include "dspcore.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace Uhhyou { 13 | 14 | constexpr double limiterAttackSecond = 0.001; 15 | 16 | template T decibelToAmp(T dB) { return std::pow(T(10), dB / T(20)); } 17 | 18 | void DSPCore::setup(double sampleRate_) 19 | { 20 | sampleRate = double(sampleRate_); 21 | 22 | SmootherCommon::setSampleRate(sampleRate); 23 | SmootherCommon::setTime(double(0.1)); 24 | 25 | reset(); 26 | startup(); 27 | } 28 | 29 | size_t DSPCore::getLatency() { return 0; } 30 | 31 | #define ASSIGN_PARAMETER(METHOD) \ 32 | auto &pv = param.value; \ 33 | \ 34 | const auto startHz = pv.startHz->load(); \ 35 | const auto slopeDecibel = pv.slopeDecibel->load(); \ 36 | const auto outputGain = pv.outputGain->load(); \ 37 | const bool isHighshelf = bool(pv.shelvingType->load()); \ 38 | for (auto &x : slopeFilter) { \ 39 | x.METHOD(sampleRate, startHz, slopeDecibel, outputGain, isHighshelf); \ 40 | } 41 | 42 | void DSPCore::reset() 43 | { 44 | ASSIGN_PARAMETER(reset); 45 | startup(); 46 | } 47 | 48 | void DSPCore::startup() {} 49 | 50 | void DSPCore::setParameters() { ASSIGN_PARAMETER(push); } 51 | 52 | void DSPCore::process( 53 | const size_t length, const float *in0, const float *in1, float *out0, float *out1) 54 | { 55 | SmootherCommon::setBufferSize(double(length)); 56 | 57 | for (size_t i = 0; i < length; ++i) { 58 | out0[i] = float(slopeFilter[0].process(in0[i])); 59 | out1[i] = float(slopeFilter[1].process(in1[i])); 60 | } 61 | } 62 | 63 | } // namespace Uhhyou 64 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | on: 2 | workflow_dispatch: 3 | push: 4 | branches: 5 | - "*" 6 | tags: 7 | - "*" 8 | 9 | env: 10 | package_path: UhhyouPluginsJuce_plugins 11 | 12 | jobs: 13 | build-ubuntu: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | with: 19 | submodules: recursive 20 | - name: Run script 21 | run: ci/ci_ubuntu.sh 22 | - name: Upload 23 | # if: ${{ startsWith(github.ref, 'refs/tags/') }} 24 | uses: actions/upload-artifact@v4 25 | with: 26 | name: plugins_ubuntu 27 | path: plugins_ubuntu 28 | 29 | build-windows: 30 | runs-on: windows-latest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v4 34 | with: 35 | submodules: recursive 36 | - name: Run script 37 | run: ci/ci_windows.ps1 38 | - name: Upload 39 | # if: ${{ startsWith(github.ref, 'refs/tags/') }} 40 | uses: actions/upload-artifact@v4 41 | with: 42 | name: plugins_windows 43 | path: plugins_windows 44 | 45 | build-macOS: 46 | runs-on: macos-latest 47 | steps: 48 | - name: Checkout 49 | uses: actions/checkout@v4 50 | with: 51 | submodules: recursive 52 | - name: Run script 53 | run: ci/ci_macOS.sh 54 | - name: Upload 55 | # if: ${{ startsWith(github.ref, 'refs/tags/') }} 56 | uses: actions/upload-artifact@v4 57 | with: 58 | name: plugins_macOS 59 | path: plugins_macOS 60 | 61 | release-packaging: 62 | needs: [build-ubuntu, build-windows, build-macOS] 63 | runs-on: ubuntu-latest 64 | if: ${{ startsWith(github.ref, 'refs/tags/UhhyouPluginsJuce') }} 65 | steps: 66 | - name: Checkout 67 | uses: actions/checkout@v4 68 | with: 69 | submodules: recursive 70 | - name: Download Artifacts 71 | uses: actions/download-artifact@v4 72 | with: 73 | path: ${{ env.package_path }} 74 | - name: Run package script 75 | run: | 76 | tree -an 77 | zip -r UhhyouPluginsJuce.zip ${{ env.package_path }} 78 | - name: Get Release Notes 79 | run: 'echo "$(git tag -l --format="%(contents:body)" $GITHUB_REF_NAME)" > RELEASE_NOTES' 80 | - name: Release 81 | uses: softprops/action-gh-release@v1 82 | with: 83 | body_path: RELEASE_NOTES 84 | files: UhhyouPluginsJuce.zip 85 | -------------------------------------------------------------------------------- /experimental/TwoBandStereo/dsp/dspcore.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include "dspcore.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace Uhhyou { 13 | 14 | void DSPCore::setup(double sampleRate_) 15 | { 16 | sampleRate = double(sampleRate_); 17 | 18 | SmootherCommon::setSampleRate(sampleRate); 19 | SmootherCommon::setTime(double(1)); 20 | 21 | reset(); 22 | startup(); 23 | } 24 | 25 | size_t DSPCore::getLatency() { return crossoverFilter[0].getLatency(); } 26 | 27 | #define ASSIGN_PARAMETER(METHOD) \ 28 | auto &pv = param.value; \ 29 | \ 30 | crossoverFreq.METHOD(pv.crossoverHz->load() / sampleRate); \ 31 | lowerStereoSpread.METHOD(pv.lowerStereoSpread->load()); \ 32 | upperStereoSpread.METHOD(pv.upperStereoSpread->load()); 33 | 34 | void DSPCore::reset() 35 | { 36 | ASSIGN_PARAMETER(reset); 37 | 38 | for (auto &x : crossoverFilter) x.reset(); 39 | 40 | startup(); 41 | } 42 | 43 | void DSPCore::startup() {} 44 | 45 | void DSPCore::setParameters() { ASSIGN_PARAMETER(push); } 46 | 47 | template 48 | inline std::array mixStereo(Sample inL, Sample inR, Sample spread) 49 | { 50 | auto mid = (inL + inR) / Sample(2); 51 | 52 | auto outL = std::lerp(mid, inL, spread); 53 | auto outR = std::lerp(mid, inR, spread); 54 | 55 | return {outL, outR}; 56 | } 57 | 58 | void DSPCore::process( 59 | const size_t length, const float *in0, const float *in1, float *out0, float *out1) 60 | { 61 | SmootherCommon::setBufferSize(double(length)); 62 | 63 | for (size_t i = 0; i < length; ++i) { 64 | crossoverFreq.process(); 65 | for (auto &x : crossoverFilter) x.prepare(crossoverFreq.getValue()); 66 | 67 | crossoverFilter[0].process(double(in0[i])); 68 | crossoverFilter[1].process(double(in1[i])); 69 | 70 | auto lower = mixStereo( 71 | crossoverFilter[0].output[0], crossoverFilter[1].output[0], 72 | lowerStereoSpread.process()); 73 | auto upper = mixStereo( 74 | crossoverFilter[0].output[1], crossoverFilter[1].output[1], 75 | upperStereoSpread.process()); 76 | 77 | out0[i] = float(lower[0] + upper[0]); 78 | out1[i] = float(lower[1] + upper[1]); 79 | } 80 | } 81 | 82 | } // namespace Uhhyou 83 | -------------------------------------------------------------------------------- /experimental/EasyOverdrive/PluginEditor.h: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include "Uhhyou/gui/widgets.hpp" 7 | 8 | #include "PluginProcessor.h" 9 | 10 | #include 11 | #include 12 | 13 | namespace Uhhyou { 14 | 15 | class Editor final : public juce::AudioProcessorEditor { 16 | public: 17 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Editor) 18 | 19 | explicit Editor(Processor &); 20 | ~Editor() override; 21 | 22 | void paint(juce::Graphics &) override; 23 | void resized() override; 24 | 25 | virtual void mouseDown(const juce::MouseEvent &) override 26 | { 27 | numberEditor.setVisible(false); 28 | } 29 | 30 | private: // JUCE related internals. 31 | Processor &processor; 32 | Palette palette; 33 | juce::LookAndFeel_V4 lookAndFeel; 34 | 35 | inline juce::ValueTree getStateTree() 36 | { 37 | return processor.param.tree.state.getOrCreateChildWithName("GUI", nullptr); 38 | } 39 | 40 | private: // Action items. 41 | StatusBar statusBar; 42 | NumberEditor numberEditor; 43 | PopUpButton pluginNameButton; 44 | ActionButton<> undoButton; 45 | ActionButton<> redoButton; 46 | ActionButton<> randomizeButton; 47 | std::unique_ptr fileChooser; 48 | PresetManager presetManager; 49 | 50 | private: // Controls tied to parameters. 51 | TextKnob preDriveGain; 52 | TextKnob postDriveGain; 53 | 54 | ComboBox overDriveType; 55 | TextKnob overDriveHoldSecond; 56 | TextKnob overDriveQ; 57 | TextKnob overDriveCharacterAmp; 58 | 59 | ToggleButton asymDriveEnabled; 60 | TextKnob asymDriveDecaySecond; 61 | TextKnob asymDriveDecayBias; 62 | TextKnob asymDriveQ; 63 | TextKnob asymExponentRange; 64 | 65 | ToggleButton limiterEnabled; 66 | TextKnob limiterInputGain; 67 | TextKnob limiterReleaseSecond; 68 | 69 | ComboBox oversampling; 70 | TextKnob parameterSmoothingSecond; 71 | 72 | private: // Drawing and action items. 73 | std::vector lines; 74 | std::vector labels; 75 | std::vector groupLabels; 76 | 77 | juce::ParameterAttachment oversamplingAttachment; 78 | juce::ParameterAttachment limiterEnabledAttachment; 79 | }; 80 | 81 | } // namespace Uhhyou 82 | -------------------------------------------------------------------------------- /lib/specialmath/jyzo/README.md: -------------------------------------------------------------------------------- 1 | # `jyzo` from special_functions by Shanjie Zhang and Jianming Jin 2 | Below is a link to the original Fortran90 code. 3 | 4 | - [special_functions](https://people.sc.fsu.edu/~jburkardt/f_src/special_functions/special_functions.html) 5 | 6 | `jyzo` and its dependency `jyndd` are ported to C++. 7 | 8 | ## Testing 9 | Run `test.ps1` on Windows. 10 | 11 | On the environment using something like `bash`, try following intead. 12 | 13 | ```bash 14 | cc -std=c++20 -O2 test.cpp 15 | ./a.out > test.json 16 | python compare.py 17 | ``` 18 | 19 | - If the first line doesn't work, replace `cc` to `g++` or `clang++`. 20 | - If the third line doesn't work, replace `python` to `python3`. 21 | 22 | ## License 23 | Below is the original `jyzo` license text obtained in 2025-02-27. 24 | 25 | ```fortran 26 | !*****************************************************************************80 27 | ! 28 | !! JYZO computes the zeros of Bessel functions Jn(x), Yn(x) and derivatives. 29 | ! 30 | ! Licensing: 31 | ! 32 | ! This routine is copyrighted by Shanjie Zhang and Jianming Jin. However, 33 | ! they give permission to incorporate this routine into a user program 34 | ! provided that the copyright is acknowledged. 35 | ! 36 | ! Modified: 37 | ! 38 | ! 28 July 2012 39 | ! 40 | ! Author: 41 | ! 42 | ! Shanjie Zhang, Jianming Jin 43 | ! 44 | ! Reference: 45 | ! 46 | ! Shanjie Zhang, Jianming Jin, 47 | ! Computation of Special Functions, 48 | ! Wiley, 1996, 49 | ! ISBN: 0-471-11963-6, 50 | ! LC: QA351.C45. 51 | ! 52 | ! Parameters: 53 | ! 54 | ! Input, integer N, the order of the Bessel functions. 55 | ! 56 | ! Input, integer NT, the number of zeros. 57 | ! 58 | ! Output, real ( kind = rk ) RJ0(NT), RJ1(NT), RY0(NT), RY1(NT), the zeros 59 | ! of Jn(x), Jn'(x), Yn(x), Yn'(x). 60 | ! 61 | ``` 62 | 63 | Below is the original `jyndd` license text obtained in 2025-02-27. 64 | 65 | ```fortran 66 | !*****************************************************************************80 67 | ! 68 | !! JYNDD: Bessel functions Jn(x) and Yn(x), first and second derivatives. 69 | ! 70 | ! Licensing: 71 | ! 72 | ! This routine is copyrighted by Shanjie Zhang and Jianming Jin. However, 73 | ! they give permission to incorporate this routine into a user program 74 | ! provided that the copyright is acknowledged. 75 | ! 76 | ! Modified: 77 | ! 78 | ! 02 August 2012 79 | ! 80 | ! Author: 81 | ! 82 | ! Shanjie Zhang, Jianming Jin 83 | ! 84 | ! Reference: 85 | ! 86 | ! Shanjie Zhang, Jianming Jin, 87 | ! Computation of Special Functions, 88 | ! Wiley, 1996, 89 | ! ISBN: 0-471-11963-6, 90 | ! LC: QA351.C45. 91 | ! 92 | ! Parameters: 93 | ! 94 | ! Input, integer N, the order. 95 | ! 96 | ! Input, real ( kind = rk ) X, the argument. 97 | ! 98 | ! Output, real ( kind = rk ) BJN, DJN, FJN, BYN, DYN, FYN, the values of 99 | ! Jn(x), Jn'(x), Jn"(x), Yn(x), Yn'(x), Yn"(x). 100 | ! 101 | ``` 102 | -------------------------------------------------------------------------------- /lib/Uhhyou/gui/numbereditor.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "style.hpp" 11 | 12 | #include 13 | #include 14 | 15 | namespace Uhhyou { 16 | 17 | class NumberEditor : public juce::TextEditor { 18 | private: 19 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(NumberEditor) 20 | 21 | Uhhyou::Palette &pal; 22 | 23 | std::function updateFn; 24 | 25 | void exitWithUpdate() 26 | { 27 | setVisible(false); 28 | updateFn(getText()); 29 | } 30 | 31 | public: 32 | NumberEditor(Palette &palette) : pal(palette), updateFn([](juce::String) {}) {} 33 | 34 | void invoke( 35 | juce::Component &newParent, 36 | juce::Rectangle bounds, 37 | juce::String numberText, 38 | std::function updateFunction) 39 | { 40 | newParent.addChildComponent(this, -2); 41 | updateFn = updateFunction; 42 | 43 | setBounds(bounds); 44 | setJustification(juce::Justification::centred); 45 | setSelectAllWhenFocused(true); 46 | setText(numberText); 47 | setVisible(true); 48 | grabKeyboardFocus(); 49 | } 50 | 51 | virtual void resized() override 52 | { 53 | applyFontToAllText(pal.getFont(pal.textSizeUi())); 54 | juce::TextEditor::resized(); 55 | } 56 | 57 | virtual void parentSizeChanged() override 58 | { 59 | setBounds({0, 0, getParentWidth(), getParentHeight()}); 60 | juce::TextEditor::parentSizeChanged(); 61 | } 62 | 63 | virtual void focusLost(FocusChangeType) override { exitWithUpdate(); } 64 | virtual void escapePressed() override { setVisible(false); } 65 | virtual void returnPressed() override { exitWithUpdate(); } 66 | }; 67 | 68 | class StatusBar : public juce::TextEditor { 69 | private: 70 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(StatusBar) 71 | 72 | Uhhyou::Palette &pal; 73 | 74 | public: 75 | StatusBar(juce::Component &parent, Palette &palette) : pal(palette) 76 | { 77 | parent.addChildComponent(this); 78 | 79 | setColour(outlineColourId, pal.background()); 80 | 81 | setCaretVisible(false); 82 | setEscapeAndReturnKeysConsumed(false); 83 | setJustification(juce::Justification::centredLeft); 84 | setReadOnly(true); 85 | setSelectAllWhenFocused(true); 86 | setScrollbarsShown(false); 87 | setVisible(true); 88 | } 89 | 90 | void update(const juce::RangedAudioParameter *const parameter) 91 | { 92 | // TODO: Use . 93 | auto text = parameter->getName(256); 94 | text += ": "; 95 | text 96 | += parameter->getText(parameter->getValue(), std::numeric_limits::digits10); 97 | text += " "; 98 | text += parameter->getLabel(); 99 | setText(text); 100 | } 101 | 102 | virtual void resized() override 103 | { 104 | applyFontToAllText(pal.getFont(pal.textSizeUi())); 105 | juce::TextEditor::resized(); 106 | } 107 | }; 108 | 109 | } // namespace Uhhyou 110 | -------------------------------------------------------------------------------- /experimental/TwoBandStereo/parameter.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include "Uhhyou/scale.hpp" 7 | #include "Uhhyou/scaledparameter.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace Uhhyou { 16 | 17 | struct Scales { 18 | using IntScl = IntScale; 19 | using UIntScl = UIntScale; 20 | using LinearScl = LinearScale; 21 | using DecibelScl = DecibelScale; 22 | using NegativeDecibelScl = NegativeDecibelScale; 23 | using BipolarDecibelScl = BipolarDecibelScale; 24 | 25 | UIntScl boolean{1}; 26 | LinearScl unipolar{float(0), float(1)}; 27 | LinearScl bipolar{float(-1), float(1)}; 28 | 29 | DecibelScl gain{float(-60), float(60), true}; 30 | DecibelScl crossoverHz{float(20), float(86.0206), false}; // 10-20000 Hz. 31 | }; 32 | 33 | struct ValueReceivers { 34 | std::atomic *crossoverHz{}; 35 | std::atomic *upperStereoSpread{}; 36 | std::atomic *lowerStereoSpread{}; 37 | 38 | // Internal values used for GUI. 39 | }; 40 | 41 | class ParameterStore { 42 | public: 43 | Scales scale; 44 | ValueReceivers value; 45 | 46 | // `tree` must be defined after `scale` and `value`. Otherwise application will crash 47 | // due to initialization order. `ValueReceivers` might be excessive abstraction, but 48 | // it's there to prevent ordering mistake. 49 | juce::AudioProcessorValueTreeState tree; 50 | 51 | private: 52 | template 53 | inline auto addParameter( 54 | std::unique_ptr &group, ParamType param) 55 | { 56 | auto atomRaw = param->getAtomicRaw(); 57 | group->addChild(std::move(param)); 58 | return atomRaw; 59 | } 60 | 61 | inline auto createParameterGroup(juce::String name) 62 | { 63 | return std::make_unique(name, name, "/"); 64 | } 65 | 66 | auto constructParameter() 67 | { 68 | using Cat = juce::AudioProcessorParameter::Category; 69 | using Rep = ParameterTextRepresentation; 70 | 71 | constexpr int version0 = 0; 72 | 73 | juce::AudioProcessorValueTreeState::ParameterLayout layout; 74 | 75 | auto generalGroup = createParameterGroup("generalGroup"); 76 | 77 | value.crossoverHz = addParameter( 78 | generalGroup, 79 | std::make_unique>( 80 | scale.crossoverHz.invmap(200), scale.crossoverHz, "crossoverHz", 81 | Cat::genericParameter, version0, "", Rep::raw)); 82 | value.upperStereoSpread = addParameter( 83 | generalGroup, 84 | std::make_unique>( 85 | 1.0f, scale.unipolar, "upperStereoSpread", Cat::genericParameter, version0, "", 86 | Rep::raw)); 87 | value.lowerStereoSpread = addParameter( 88 | generalGroup, 89 | std::make_unique>( 90 | 1.0f, scale.unipolar, "lowerStereoSpread", Cat::genericParameter, version0, "", 91 | Rep::raw)); 92 | 93 | layout.add(std::move(generalGroup)); 94 | return layout; 95 | } 96 | 97 | public: 98 | ParameterStore( 99 | juce::AudioProcessor &processor, 100 | juce::UndoManager *undoManager, 101 | const juce::Identifier &id) 102 | : scale(), value(), tree(processor, undoManager, id, constructParameter()) 103 | { 104 | } 105 | }; 106 | 107 | } // namespace Uhhyou 108 | -------------------------------------------------------------------------------- /experimental/AmplitudeModulator/dsp/dspcore.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include "dspcore.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace Uhhyou { 13 | 14 | void DSPCore::setup(double sampleRate_) 15 | { 16 | sampleRate = double(sampleRate_); 17 | 18 | SmootherCommon::setSampleRate(sampleRate); 19 | SmootherCommon::setTime(double(0.1)); 20 | 21 | reset(); 22 | startup(); 23 | } 24 | 25 | size_t DSPCore::getLatency() { return 0; } 26 | 27 | #define ASSIGN_PARAMETER(METHOD) \ 28 | auto &pv = param.value; \ 29 | \ 30 | amType = size_t(pv.amType->load()); \ 31 | swapCarriorAndModulator = bool(pv.swapCarriorAndModulator->load()); \ 32 | carriorSideBandMix.METHOD(pv.carriorSideBandMix->load()); \ 33 | outputGain.METHOD(pv.outputGain->load()); 34 | 35 | void DSPCore::reset() 36 | { 37 | ASSIGN_PARAMETER(reset); 38 | 39 | for (auto &x : amNaive) x.reset(); 40 | for (auto &x : amUpperAA) x.reset(); 41 | for (auto &x : amFullAA) x.reset(); 42 | for (auto &x : amUsbNaive) x.reset(); 43 | for (auto &x : amLsbNaive) x.reset(); 44 | for (auto &x : amUsbAA) x.reset(); 45 | for (auto &x : amLsbAA) x.reset(); 46 | 47 | startup(); 48 | } 49 | 50 | void DSPCore::startup() {} 51 | 52 | void DSPCore::setParameters() { ASSIGN_PARAMETER(push); } 53 | 54 | #define PROCESS_AM(PROCESSOR) \ 55 | for (size_t i = 0; i < length; ++i) { \ 56 | auto sideBand0 = PROCESSOR[0].process(inCar0[i], inMod0[i]); \ 57 | auto sideBand1 = PROCESSOR[1].process(inCar1[i], inMod1[i]); \ 58 | auto mix = carriorSideBandMix.process(); \ 59 | auto gain = outputGain.process(); \ 60 | out0[i] = float(gain * std::lerp(double(inCar0[i]), sideBand0, mix)); \ 61 | out1[i] = float(gain * std::lerp(double(inCar1[i]), sideBand1, mix)); \ 62 | } 63 | 64 | void DSPCore::process( 65 | const size_t length, 66 | const float *inCar0, 67 | const float *inCar1, 68 | const float *inMod0, 69 | const float *inMod1, 70 | float *out0, 71 | float *out1) 72 | { 73 | SmootherCommon::setBufferSize(double(length)); 74 | 75 | if (swapCarriorAndModulator) { 76 | std::swap(inCar0, inCar1); 77 | std::swap(inMod0, inMod1); 78 | } 79 | 80 | if (amType == 0) { // Double Side-band (DSB) 81 | PROCESS_AM(amNaive); 82 | } else if (amType == 1) { // Upper Side-band (USB) 83 | PROCESS_AM(amUsbNaive); 84 | } else if (amType == 2) { // Lower Side-band (LSB) 85 | PROCESS_AM(amLsbNaive); 86 | } else if (amType == 3) { // DSB Upper AA 87 | PROCESS_AM(amUpperAA); 88 | } else if (amType == 4) { // DSB Full AA 89 | PROCESS_AM(amFullAA); 90 | } else if (amType == 5) { // USB AA 91 | PROCESS_AM(amUsbAA); 92 | } else if (amType == 6) { // LSB AA 93 | PROCESS_AM(amLsbAA); 94 | } else { // Default to DSB. 95 | PROCESS_AM(amNaive); 96 | } 97 | } 98 | 99 | } // namespace Uhhyou 100 | -------------------------------------------------------------------------------- /experimental/AmplitudeModulator/parameter.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include "Uhhyou/scale.hpp" 7 | #include "Uhhyou/scaledparameter.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace Uhhyou { 16 | 17 | struct Scales { 18 | using IntScl = IntScale; 19 | using UIntScl = UIntScale; 20 | using LinearScl = LinearScale; 21 | using DecibelScl = DecibelScale; 22 | using NegativeDecibelScl = NegativeDecibelScale; 23 | using BipolarDecibelScl = BipolarDecibelScale; 24 | 25 | UIntScl boolean{1}; 26 | LinearScl unipolar{float(0), float(1)}; 27 | LinearScl bipolar{float(-1), float(1)}; 28 | 29 | UIntScl amType{31}; 30 | DecibelScl gain{float(-60), float(60), true}; 31 | }; 32 | 33 | struct ValueReceivers { 34 | std::atomic *amType{}; 35 | std::atomic *carriorSideBandMix{}; 36 | std::atomic *outputGain{}; 37 | std::atomic *swapCarriorAndModulator{}; 38 | 39 | // Internal values used for GUI. 40 | }; 41 | 42 | class ParameterStore { 43 | public: 44 | Scales scale; 45 | ValueReceivers value; 46 | 47 | // `tree` must be defined after `scale` and `value`. Otherwise application will crash 48 | // due to initialization order. `ValueReceivers` might be excessive abstraction, but 49 | // it's there to prevent ordering mistake. 50 | juce::AudioProcessorValueTreeState tree; 51 | 52 | private: 53 | template 54 | inline auto addParameter( 55 | std::unique_ptr &group, ParamType param) 56 | { 57 | auto atomRaw = param->getAtomicRaw(); 58 | group->addChild(std::move(param)); 59 | return atomRaw; 60 | } 61 | 62 | inline auto createParameterGroup(juce::String name) 63 | { 64 | return std::make_unique(name, name, "/"); 65 | } 66 | 67 | auto constructParameter() 68 | { 69 | using Cat = juce::AudioProcessorParameter::Category; 70 | using Rep = ParameterTextRepresentation; 71 | 72 | constexpr int version0 = 0; 73 | 74 | juce::AudioProcessorValueTreeState::ParameterLayout layout; 75 | 76 | auto generalGroup = createParameterGroup("generalGroup"); 77 | 78 | value.amType = addParameter( 79 | generalGroup, 80 | std::make_unique>( 81 | scale.amType.invmap(0), scale.amType, "amType", Cat::genericParameter, version0)); 82 | 83 | value.carriorSideBandMix = addParameter( 84 | generalGroup, 85 | std::make_unique>( 86 | 0.5f, scale.unipolar, "carriorSideBandMix", Cat::genericParameter, version0, "", 87 | Rep::raw)); 88 | value.outputGain = addParameter( 89 | generalGroup, 90 | std::make_unique>( 91 | scale.gain.invmapDB(0.0f), scale.gain, "outputGain", Cat::genericParameter, 92 | version0, "dB", Rep::display)); 93 | 94 | value.swapCarriorAndModulator = addParameter( 95 | generalGroup, 96 | std::make_unique>( 97 | scale.boolean.invmap(0), scale.boolean, "swapCarriorAndModulator", 98 | Cat::genericParameter, version0)); 99 | 100 | layout.add(std::move(generalGroup)); 101 | return layout; 102 | } 103 | 104 | public: 105 | ParameterStore( 106 | juce::AudioProcessor &processor, 107 | juce::UndoManager *undoManager, 108 | const juce::Identifier &id) 109 | : scale(), value(), tree(processor, undoManager, id, constructParameter()) 110 | { 111 | } 112 | }; 113 | 114 | } // namespace Uhhyou 115 | -------------------------------------------------------------------------------- /experimental/SlopeFilter/parameter.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include "Uhhyou/scale.hpp" 7 | #include "Uhhyou/scaledparameter.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace Uhhyou { 16 | 17 | struct Scales { 18 | using IntScl = IntScale; 19 | using UIntScl = UIntScale; 20 | using LinearScl = LinearScale; 21 | using DecibelScl = DecibelScale; 22 | using NegativeDecibelScl = NegativeDecibelScale; 23 | using BipolarDecibelScl = BipolarDecibelScale; 24 | 25 | UIntScl boolean{1}; 26 | LinearScl unipolar{float(0), float(1)}; 27 | LinearScl bipolar{float(-1), float(1)}; 28 | 29 | UIntScl shelvingType{1}; 30 | DecibelScl startHz{float(20), float(86.84845361644413), false}; 31 | LinearScl slopeDecibel{float(-20), float(20)}; 32 | DecibelScl outputGain{float(-60), float(60), true}; 33 | }; 34 | 35 | struct ValueReceivers { 36 | std::atomic *shelvingType{}; 37 | std::atomic *startHz{}; 38 | std::atomic *slopeDecibel{}; 39 | std::atomic *outputGain{}; 40 | 41 | // Internal values used for GUI. 42 | }; 43 | 44 | class ParameterStore { 45 | public: 46 | Scales scale; 47 | ValueReceivers value; 48 | 49 | // `tree` must be defined after `scale` and `value`. Otherwise application will crash 50 | // due to initialization order. `ValueReceivers` might be excessive abstraction, but 51 | // it's there to prevent ordering mistake. 52 | juce::AudioProcessorValueTreeState tree; 53 | 54 | private: 55 | template 56 | inline auto addParameter( 57 | std::unique_ptr &group, ParamType param) 58 | { 59 | auto atomRaw = param->getAtomicRaw(); 60 | group->addChild(std::move(param)); 61 | return atomRaw; 62 | } 63 | 64 | inline auto createParameterGroup(juce::String name) 65 | { 66 | return std::make_unique(name, name, "/"); 67 | } 68 | 69 | auto constructParameter() 70 | { 71 | using Cat = juce::AudioProcessorParameter::Category; 72 | using Rep = ParameterTextRepresentation; 73 | 74 | constexpr int version0 = 0; 75 | 76 | juce::AudioProcessorValueTreeState::ParameterLayout layout; 77 | 78 | auto generalGroup = createParameterGroup("generalGroup"); 79 | 80 | value.startHz = addParameter( 81 | generalGroup, 82 | std::make_unique>( 83 | 0.0f, scale.startHz, "startHz", Cat::genericParameter, version0, "Hz", Rep::raw)); 84 | value.slopeDecibel = addParameter( 85 | generalGroup, 86 | std::make_unique>( 87 | scale.slopeDecibel.invmap(0.0f), scale.slopeDecibel, "slopeDecibel", 88 | Cat::genericParameter, version0, "dB", Rep::raw)); 89 | value.outputGain = addParameter( 90 | generalGroup, 91 | std::make_unique>( 92 | scale.outputGain.invmapDB(0.0f), scale.outputGain, "outputGain", 93 | Cat::genericParameter, version0, "dB", Rep::display)); 94 | 95 | value.shelvingType = addParameter( 96 | generalGroup, 97 | std::make_unique>( 98 | scale.boolean.invmap(1), scale.boolean, "shelvingType", Cat::genericParameter, 99 | version0)); 100 | layout.add(std::move(generalGroup)); 101 | 102 | return layout; 103 | } 104 | 105 | public: 106 | ParameterStore( 107 | juce::AudioProcessor &processor, 108 | juce::UndoManager *undoManager, 109 | const juce::Identifier &id) 110 | : scale(), value(), tree(processor, undoManager, id, constructParameter()) 111 | { 112 | } 113 | }; 114 | 115 | } // namespace Uhhyou 116 | -------------------------------------------------------------------------------- /lib/Uhhyou/gui/style.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace Uhhyou { 12 | 13 | enum class Style { common, accent, warning }; 14 | 15 | class Palette { 16 | public: 17 | Palette() { load(); } 18 | void load(); 19 | 20 | const juce::Font &getFont(float size) 21 | { 22 | auto key = size_t(fontMapKeyScaling * size); 23 | auto found = fontMap.find(key); 24 | if (found != fontMap.end()) return found->second; 25 | auto inserted = fontMap.emplace( 26 | key, 27 | juce::Font(juce::FontOptions{ 28 | fontName(), fontFace(), key * scalingFactor / fontMapKeyScaling})); 29 | return inserted.first->second; 30 | } 31 | 32 | void resize(float scale) 33 | { 34 | scalingFactor = scale; 35 | 36 | fontMap.clear(); 37 | std::vector sizes{ 38 | size_t(textSizeSmall() * fontMapKeyScaling), 39 | size_t(textSizeUi() * fontMapKeyScaling), 40 | size_t(textSizeBig() * fontMapKeyScaling), 41 | }; 42 | 43 | for (const auto &key : sizes) { 44 | fontMap.emplace( 45 | key, 46 | juce::Font(juce::FontOptions{ 47 | fontName(), fontFace(), key * scalingFactor / fontMapKeyScaling})); 48 | } 49 | 50 | _borderThinScaled = scalingFactor * _borderThin; 51 | _borderThickScaled = scalingFactor * _borderThick; 52 | } 53 | 54 | const juce::String &fontName() { return _fontName; } 55 | const juce::String &fontFace() { return _fontFace; } 56 | const float textSizeSmall() { return 10; } 57 | const float textSizeUi() { return 14; } 58 | const float textSizeBig() { return 20; } 59 | const float borderThin() { return _borderThinScaled; } 60 | const float borderThick() { return _borderThickScaled; } 61 | const juce::Colour &foreground() { return _foreground; } 62 | const juce::Colour &foregroundButtonOn() { return _foregroundButtonOn; } 63 | const juce::Colour &foregroundInactive() { return _foregroundInactive; } 64 | const juce::Colour &background() { return _background; } 65 | const juce::Colour &boxBackground() { return _boxBackground; } 66 | const juce::Colour &border() { return _border; } 67 | const juce::Colour &borderCheckbox() { return _borderCheckbox; } 68 | const juce::Colour &borderLabel() { return _borderLabel; } 69 | const juce::Colour &unfocused() { return _unfocused; } 70 | const juce::Colour &highlightMain() { return _highlightMain; } 71 | const juce::Colour &highlightAccent() { return _highlightAccent; } 72 | const juce::Colour &highlightButton() { return _highlightButton; } 73 | const juce::Colour &highlightWarning() { return _highlightWarning; } 74 | const juce::Colour &overlay() { return _overlay; } 75 | const juce::Colour &overlayHighlight() { return _overlayHighlight; } 76 | const juce::Colour &overlayFaint() { return _overlayFaint; } 77 | 78 | private: 79 | static constexpr unsigned fontMapKeyScaling = 10; 80 | float scalingFactor = float(1); 81 | std::unordered_map fontMap; 82 | 83 | float _borderThin = 1; 84 | float _borderThick = 8; 85 | float _borderThinScaled = _borderThin; 86 | float _borderThickScaled = _borderThick; 87 | 88 | juce::String _fontName{"Tinos"}; 89 | juce::String _fontFace{"Bold Italic"}; 90 | juce::Colour _foreground{juce::uint32{0xff000000}}; 91 | juce::Colour _foregroundButtonOn{juce::uint32{0xff000000}}; 92 | juce::Colour _foregroundInactive{juce::uint32{0xff8a8a8a}}; 93 | juce::Colour _background{juce::uint32{0xffffffff}}; 94 | juce::Colour _boxBackground{juce::uint32{0xffffffff}}; 95 | juce::Colour _border{juce::uint32{0xff000000}}; 96 | juce::Colour _borderCheckbox{juce::uint32{0xff000000}}; 97 | juce::Colour _borderLabel{juce::uint32{0xff000000}}; 98 | juce::Colour _unfocused{juce::uint32{0xffdddddd}}; 99 | juce::Colour _highlightMain{juce::uint32{0xff0ba4f1}}; 100 | juce::Colour _highlightAccent{juce::uint32{0xff13c136}}; 101 | juce::Colour _highlightButton{juce::uint32{0xfffcc04f}}; 102 | juce::Colour _highlightWarning{juce::uint32{0xfffc8080}}; 103 | juce::Colour _overlay{juce::uint32{0x88000000}}; 104 | juce::Colour _overlayHighlight{juce::uint32{0x3300ff00}}; 105 | juce::Colour _overlayFaint{juce::uint32{0x0b000000}}; 106 | }; 107 | 108 | } // namespace Uhhyou 109 | -------------------------------------------------------------------------------- /experimental/SlopeFilter/PluginProcessor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include "PluginProcessor.h" 5 | #include "PluginEditor.h" 6 | 7 | Processor::Processor() 8 | : AudioProcessor( 9 | BusesProperties() 10 | .withInput("Input", juce::AudioChannelSet::stereo(), true) 11 | .withOutput("Output", juce::AudioChannelSet::stereo(), true)) 12 | , param(*this, &undoManager, juce::Identifier("Root")) 13 | , dsp(param) 14 | { 15 | } 16 | 17 | Processor::~Processor() {} 18 | 19 | const juce::String Processor::getName() const { return JucePlugin_Name; } 20 | bool Processor::acceptsMidi() const { return true; } 21 | bool Processor::producesMidi() const { return false; } 22 | bool Processor::isMidiEffect() const { return false; } 23 | double Processor::getTailLengthSeconds() const { return 0.0; } 24 | int Processor::getNumPrograms() { return 1; } 25 | int Processor::getCurrentProgram() { return 0; } 26 | void Processor::setCurrentProgram(int) {} 27 | const juce::String Processor::getProgramName(int) { return {}; } 28 | void Processor::changeProgramName(int, const juce::String &) {} 29 | 30 | void Processor::prepareToPlay(double sampleRate, int) 31 | { 32 | std::lock_guard guard(setupMutex); 33 | 34 | if (previousSampleRate != sampleRate) { 35 | dsp.setup(sampleRate); 36 | } else { 37 | dsp.reset(); 38 | } 39 | setLatencySamples(int(dsp.getLatency())); 40 | previousSampleRate = sampleRate; 41 | } 42 | 43 | void Processor::releaseResources() {} 44 | 45 | bool Processor::isBusesLayoutSupported(const BusesLayout &layouts) const 46 | { 47 | if ( 48 | layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono() 49 | && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) 50 | { 51 | return false; 52 | } 53 | 54 | if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) return false; 55 | 56 | return true; 57 | } 58 | 59 | void Processor::processBlock( 60 | juce::AudioBuffer &buffer, juce::MidiBuffer & /*midi*/) 61 | { 62 | // I guess mutex shouldn't be used here. However, this is a mitigation of a crash on FL 63 | // when refreshing a plugin. It seems like `prepareToPlay` and `processBlock` might be 64 | // called at the same time. 65 | std::lock_guard guard(setupMutex); 66 | 67 | juce::ScopedNoDenormals noDenormals; 68 | 69 | auto audioPlayHead = getPlayHead(); 70 | if (audioPlayHead != nullptr) { 71 | auto positionInfo = audioPlayHead->getPosition(); 72 | if (positionInfo) { 73 | dsp.isPlaying = positionInfo->getIsPlaying(); 74 | 75 | auto bpm = positionInfo->getBpm(); 76 | if (bpm) dsp.tempo = *bpm; 77 | 78 | auto beats = positionInfo->getPpqPosition(); 79 | if (beats) dsp.beatsElapsed = *beats; 80 | 81 | auto timeSignature = positionInfo->getTimeSignature(); 82 | if (timeSignature) { 83 | dsp.timeSigUpper = timeSignature->numerator; 84 | dsp.timeSigLower = timeSignature->denominator; 85 | } 86 | } 87 | } 88 | 89 | for (auto i = getTotalNumInputChannels(); i < getTotalNumOutputChannels(); ++i) { 90 | buffer.clear(i, 0, buffer.getNumSamples()); 91 | } 92 | 93 | dsp.setParameters(); 94 | 95 | auto in0 = buffer.getReadPointer(0); 96 | auto in1 = buffer.getReadPointer(1); 97 | auto out0 = buffer.getWritePointer(0); 98 | auto out1 = buffer.getWritePointer(1); 99 | dsp.process((size_t)buffer.getNumSamples(), in0, in1, out0, out1); 100 | } 101 | 102 | bool Processor::hasEditor() const { return true; } 103 | 104 | juce::AudioProcessorEditor *Processor::createEditor() 105 | { 106 | return new Uhhyou::Editor(*this); 107 | } 108 | 109 | void Processor::getStateInformation(juce::MemoryBlock &destData) 110 | { 111 | auto state = param.tree.copyState(); 112 | std::unique_ptr xml(state.createXml()); 113 | copyXmlToBinary(*xml, destData); 114 | } 115 | 116 | void Processor::setStateInformation(const void *data, int sizeInBytes) 117 | { 118 | std::unique_ptr xmlState(getXmlFromBinary(data, sizeInBytes)); 119 | 120 | if (xmlState.get() != nullptr) { 121 | if (xmlState->hasTagName(param.tree.state.getType())) { 122 | param.tree.replaceState(juce::ValueTree::fromXml(*xmlState)); 123 | } 124 | } 125 | } 126 | 127 | juce::AudioProcessor *JUCE_CALLTYPE createPluginFilter() { return new Processor(); } 128 | -------------------------------------------------------------------------------- /experimental/EasyOverdrive/PluginProcessor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include "PluginProcessor.h" 5 | #include "PluginEditor.h" 6 | 7 | Processor::Processor() 8 | : AudioProcessor( 9 | BusesProperties() 10 | .withInput("Input", juce::AudioChannelSet::stereo(), true) 11 | .withOutput("Output", juce::AudioChannelSet::stereo(), true)) 12 | , param(*this, &undoManager, juce::Identifier("Root")) 13 | , dsp(param) 14 | { 15 | } 16 | 17 | Processor::~Processor() {} 18 | 19 | const juce::String Processor::getName() const { return JucePlugin_Name; } 20 | bool Processor::acceptsMidi() const { return true; } 21 | bool Processor::producesMidi() const { return false; } 22 | bool Processor::isMidiEffect() const { return false; } 23 | double Processor::getTailLengthSeconds() const { return 0.0; } 24 | int Processor::getNumPrograms() { return 1; } 25 | int Processor::getCurrentProgram() { return 0; } 26 | void Processor::setCurrentProgram(int) {} 27 | const juce::String Processor::getProgramName(int) { return {}; } 28 | void Processor::changeProgramName(int, const juce::String &) {} 29 | 30 | void Processor::prepareToPlay(double sampleRate, int) 31 | { 32 | std::lock_guard guard(setupMutex); 33 | 34 | if (previousSampleRate != sampleRate) { 35 | dsp.setup(sampleRate); 36 | } else { 37 | dsp.reset(); 38 | } 39 | setLatencySamples(int(dsp.getLatency())); 40 | previousSampleRate = sampleRate; 41 | } 42 | 43 | void Processor::releaseResources() {} 44 | 45 | bool Processor::isBusesLayoutSupported(const BusesLayout &layouts) const 46 | { 47 | if ( 48 | layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono() 49 | && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) 50 | { 51 | return false; 52 | } 53 | 54 | if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) return false; 55 | 56 | return true; 57 | } 58 | 59 | void Processor::processBlock( 60 | juce::AudioBuffer &buffer, juce::MidiBuffer & /*midi*/) 61 | { 62 | // I guess mutex shouldn't be used here. However, this is a mitigation of a crash on FL 63 | // when refreshing a plugin. It seems like `prepareToPlay` and `processBlock` might be 64 | // called at the same time. 65 | std::lock_guard guard(setupMutex); 66 | 67 | juce::ScopedNoDenormals noDenormals; 68 | 69 | auto audioPlayHead = getPlayHead(); 70 | if (audioPlayHead != nullptr) { 71 | auto positionInfo = audioPlayHead->getPosition(); 72 | if (positionInfo) { 73 | dsp.isPlaying = positionInfo->getIsPlaying(); 74 | 75 | auto bpm = positionInfo->getBpm(); 76 | if (bpm) dsp.tempo = *bpm; 77 | 78 | auto beats = positionInfo->getPpqPosition(); 79 | if (beats) dsp.beatsElapsed = *beats; 80 | 81 | auto timeSignature = positionInfo->getTimeSignature(); 82 | if (timeSignature) { 83 | dsp.timeSigUpper = timeSignature->numerator; 84 | dsp.timeSigLower = timeSignature->denominator; 85 | } 86 | } 87 | } 88 | 89 | for (auto i = getTotalNumInputChannels(); i < getTotalNumOutputChannels(); ++i) { 90 | buffer.clear(i, 0, buffer.getNumSamples()); 91 | } 92 | 93 | dsp.setParameters(); 94 | 95 | auto in0 = buffer.getReadPointer(0); 96 | auto in1 = buffer.getReadPointer(1); 97 | auto out0 = buffer.getWritePointer(0); 98 | auto out1 = buffer.getWritePointer(1); 99 | dsp.process((size_t)buffer.getNumSamples(), in0, in1, out0, out1); 100 | } 101 | 102 | bool Processor::hasEditor() const { return true; } 103 | 104 | juce::AudioProcessorEditor *Processor::createEditor() 105 | { 106 | return new Uhhyou::Editor(*this); 107 | } 108 | 109 | void Processor::getStateInformation(juce::MemoryBlock &destData) 110 | { 111 | auto state = param.tree.copyState(); 112 | std::unique_ptr xml(state.createXml()); 113 | copyXmlToBinary(*xml, destData); 114 | } 115 | 116 | void Processor::setStateInformation(const void *data, int sizeInBytes) 117 | { 118 | std::unique_ptr xmlState(getXmlFromBinary(data, sizeInBytes)); 119 | 120 | if (xmlState.get() != nullptr) { 121 | if (xmlState->hasTagName(param.tree.state.getType())) { 122 | param.tree.replaceState(juce::ValueTree::fromXml(*xmlState)); 123 | } 124 | } 125 | } 126 | 127 | juce::AudioProcessor *JUCE_CALLTYPE createPluginFilter() { return new Processor(); } 128 | -------------------------------------------------------------------------------- /experimental/TwoBandStereo/PluginProcessor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include "PluginProcessor.h" 5 | #include "PluginEditor.h" 6 | 7 | Processor::Processor() 8 | : AudioProcessor( 9 | BusesProperties() 10 | .withInput("Input", juce::AudioChannelSet::stereo(), true) 11 | .withOutput("Output", juce::AudioChannelSet::stereo(), true)) 12 | , param(*this, &undoManager, juce::Identifier("Root")) 13 | , dsp(param) 14 | { 15 | } 16 | 17 | Processor::~Processor() {} 18 | 19 | const juce::String Processor::getName() const { return JucePlugin_Name; } 20 | bool Processor::acceptsMidi() const { return true; } 21 | bool Processor::producesMidi() const { return false; } 22 | bool Processor::isMidiEffect() const { return false; } 23 | double Processor::getTailLengthSeconds() const { return 0.0; } 24 | int Processor::getNumPrograms() { return 1; } 25 | int Processor::getCurrentProgram() { return 0; } 26 | void Processor::setCurrentProgram(int) {} 27 | const juce::String Processor::getProgramName(int) { return {}; } 28 | void Processor::changeProgramName(int, const juce::String &) {} 29 | 30 | void Processor::prepareToPlay(double sampleRate, int) 31 | { 32 | std::lock_guard guard(setupMutex); 33 | 34 | if (previousSampleRate != sampleRate) { 35 | dsp.setup(sampleRate); 36 | } else { 37 | dsp.reset(); 38 | } 39 | setLatencySamples(int(dsp.getLatency())); 40 | previousSampleRate = sampleRate; 41 | } 42 | 43 | void Processor::releaseResources() {} 44 | 45 | bool Processor::isBusesLayoutSupported(const BusesLayout &layouts) const 46 | { 47 | if ( 48 | layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono() 49 | && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) 50 | { 51 | return false; 52 | } 53 | 54 | if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) return false; 55 | 56 | return true; 57 | } 58 | 59 | void Processor::processBlock( 60 | juce::AudioBuffer &buffer, juce::MidiBuffer & /*midi*/) 61 | { 62 | // I guess mutex shouldn't be used here. However, this is a mitigation of a crash on FL 63 | // when refreshing a plugin. It seems like `prepareToPlay` and `processBlock` might be 64 | // called at the same time. 65 | std::lock_guard guard(setupMutex); 66 | 67 | juce::ScopedNoDenormals noDenormals; 68 | 69 | auto audioPlayHead = getPlayHead(); 70 | if (audioPlayHead != nullptr) { 71 | auto positionInfo = audioPlayHead->getPosition(); 72 | if (positionInfo) { 73 | dsp.isPlaying = positionInfo->getIsPlaying(); 74 | 75 | auto bpm = positionInfo->getBpm(); 76 | if (bpm) dsp.tempo = *bpm; 77 | 78 | auto beats = positionInfo->getPpqPosition(); 79 | if (beats) dsp.beatsElapsed = *beats; 80 | 81 | auto timeSignature = positionInfo->getTimeSignature(); 82 | if (timeSignature) { 83 | dsp.timeSigUpper = timeSignature->numerator; 84 | dsp.timeSigLower = timeSignature->denominator; 85 | } 86 | } 87 | } 88 | 89 | for (auto i = getTotalNumInputChannels(); i < getTotalNumOutputChannels(); ++i) { 90 | buffer.clear(i, 0, buffer.getNumSamples()); 91 | } 92 | 93 | dsp.setParameters(); 94 | 95 | auto in0 = buffer.getReadPointer(0); 96 | auto in1 = buffer.getReadPointer(1); 97 | auto out0 = buffer.getWritePointer(0); 98 | auto out1 = buffer.getWritePointer(1); 99 | dsp.process((size_t)buffer.getNumSamples(), in0, in1, out0, out1); 100 | } 101 | 102 | bool Processor::hasEditor() const { return true; } 103 | 104 | juce::AudioProcessorEditor *Processor::createEditor() 105 | { 106 | return new Uhhyou::Editor(*this); 107 | } 108 | 109 | void Processor::getStateInformation(juce::MemoryBlock &destData) 110 | { 111 | auto state = param.tree.copyState(); 112 | std::unique_ptr xml(state.createXml()); 113 | copyXmlToBinary(*xml, destData); 114 | } 115 | 116 | void Processor::setStateInformation(const void *data, int sizeInBytes) 117 | { 118 | std::unique_ptr xmlState(getXmlFromBinary(data, sizeInBytes)); 119 | 120 | if (xmlState.get() != nullptr) { 121 | if (xmlState->hasTagName(param.tree.state.getType())) { 122 | param.tree.replaceState(juce::ValueTree::fromXml(*xmlState)); 123 | } 124 | } 125 | } 126 | 127 | juce::AudioProcessor *JUCE_CALLTYPE createPluginFilter() { return new Processor(); } 128 | -------------------------------------------------------------------------------- /lib/Uhhyou/gui/style.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | // This source is splitted because nlohmann/json.hpp is slow to compile. 5 | 6 | #include "style.hpp" 7 | #include "nlohmann/json.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace Uhhyou { 17 | 18 | namespace fs = std::filesystem; 19 | 20 | /** 21 | Specification of $XDG_CONFIG_HOME: 22 | https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html 23 | */ 24 | inline fs::path getConfigHome() 25 | { 26 | #ifdef _WIN32 27 | char *appdataDir = nullptr; 28 | size_t size = 0; 29 | if (_dupenv_s(&appdataDir, &size, "AppData") == 0 && appdataDir != nullptr) { 30 | auto path = fs::path(appdataDir); 31 | free(appdataDir); 32 | return path; 33 | } 34 | 35 | std::cerr << "%AppData% is empty.\n"; 36 | #elif __APPLE__ 37 | const char *home = std::getenv("HOME"); 38 | if (home != nullptr) return fs::path(home) / "Library/Preferences"; 39 | 40 | std::cerr << "$HOME is empty.\n"; 41 | #else 42 | const char *configDir = std::getenv("XDG_CONFIG_HOME"); 43 | if (configDir != nullptr) return fs::path(configDir); 44 | 45 | const char *home = std::getenv("HOME"); 46 | if (home != nullptr) return fs::path(home) / ".config"; 47 | 48 | std::cerr << "$XDG_CONFIG_HOME and $HOME is empty.\n"; 49 | 50 | #endif 51 | return fs::path(""); 52 | } 53 | 54 | /** 55 | Load style config from `$XDG_CONFIG_HOME/UhhyouPlugins/style/style.json`. 56 | Returns empty json on failure. 57 | */ 58 | inline nlohmann::json loadStyleJson() 59 | { 60 | nlohmann::json data; 61 | 62 | auto styleJsonPath = getConfigHome() / fs::path("UhhyouPlugins/style/style.json"); 63 | 64 | if (!fs::is_regular_file(styleJsonPath)) { 65 | std::cerr << styleJsonPath << " is not regular file or doesn't exist.\n"; 66 | return data; 67 | } 68 | 69 | std::ifstream ifs(styleJsonPath); 70 | if (!ifs.is_open()) { 71 | std::cerr << "Failed to open " << styleJsonPath << "\n"; 72 | return data; 73 | } 74 | 75 | ifs >> data; 76 | return data; 77 | } 78 | 79 | inline juce::uint8 strHexToUInt8(std::string str) 80 | { 81 | return juce::uint8(std::clamp(std::stoi(str, nullptr, 16), 0, 255)); 82 | } 83 | 84 | /** 85 | data[key] must come in string of hex color code. "#123456", "#aabbccdd" etc. 86 | Color will be only loaded if the size of string is either 7 or 9 (RGB or RGBA). 87 | First character is ignored. So "!303030", " 0000ff88" are valid. 88 | */ 89 | inline void loadColor(nlohmann::json &data, std::string key, juce::Colour &color) 90 | { 91 | if (!data.contains(key)) return; 92 | if (!data[key].is_string()) return; 93 | 94 | std::string hex = data[key]; 95 | 96 | if (hex.size() != 7 && hex.size() != 9) return; 97 | 98 | color = juce::Colour( 99 | strHexToUInt8(hex.substr(1, 2)), strHexToUInt8(hex.substr(3, 2)), 100 | strHexToUInt8(hex.substr(5, 2)), 101 | hex.size() != 9 ? juce::uint8(255) : strHexToUInt8(hex.substr(7, 2))); 102 | } 103 | 104 | inline void loadString(nlohmann::json &data, std::string key, juce::String &value) 105 | { 106 | if (!data.contains(key)) return; 107 | if (!data[key].is_string()) return; 108 | 109 | std::string loaded = data[key]; 110 | if (loaded.empty()) return; 111 | 112 | value = loaded; 113 | } 114 | 115 | void Palette::load() 116 | { 117 | auto data = loadStyleJson(); 118 | if (data.is_null()) return; 119 | 120 | loadString(data, "fontFamily", _fontName); 121 | loadString(data, "fontFace", _fontFace); 122 | loadColor(data, "foreground", _foreground); 123 | loadColor(data, "foregroundButtonOn", _foregroundButtonOn); 124 | loadColor(data, "foregroundInactive", _foregroundInactive); 125 | loadColor(data, "background", _background); 126 | loadColor(data, "boxBackground", _boxBackground); 127 | loadColor(data, "border", _border); 128 | loadColor(data, "borderCheckbox", _borderCheckbox); 129 | loadColor(data, "borderLabel", _borderLabel); 130 | loadColor(data, "unfocused", _unfocused); 131 | loadColor(data, "highlightMain", _highlightMain); 132 | loadColor(data, "highlightAccent", _highlightAccent); 133 | loadColor(data, "highlightButton", _highlightButton); 134 | loadColor(data, "highlightWarning", _highlightWarning); 135 | loadColor(data, "overlay", _overlay); 136 | loadColor(data, "overlayHighlight", _overlayHighlight); 137 | loadColor(data, "overlayFaint", _overlayFaint); 138 | } 139 | 140 | } // namespace Uhhyou 141 | -------------------------------------------------------------------------------- /experimental/AmplitudeModulator/PluginProcessor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include "PluginProcessor.h" 5 | #include "PluginEditor.h" 6 | 7 | Processor::Processor() 8 | : AudioProcessor( 9 | BusesProperties() 10 | .withInput("InputCarrior", juce::AudioChannelSet::stereo(), true) 11 | .withInput("InputModulator", juce::AudioChannelSet::stereo(), true) 12 | .withOutput("Output", juce::AudioChannelSet::stereo(), true)) 13 | , param(*this, &undoManager, juce::Identifier("Root")) 14 | , dsp(param) 15 | { 16 | } 17 | 18 | Processor::~Processor() {} 19 | 20 | const juce::String Processor::getName() const { return JucePlugin_Name; } 21 | bool Processor::acceptsMidi() const { return true; } 22 | bool Processor::producesMidi() const { return false; } 23 | bool Processor::isMidiEffect() const { return false; } 24 | double Processor::getTailLengthSeconds() const { return 0.0; } 25 | int Processor::getNumPrograms() { return 1; } 26 | int Processor::getCurrentProgram() { return 0; } 27 | void Processor::setCurrentProgram(int) {} 28 | const juce::String Processor::getProgramName(int) { return {}; } 29 | void Processor::changeProgramName(int, const juce::String &) {} 30 | 31 | void Processor::prepareToPlay(double sampleRate, int) 32 | { 33 | std::lock_guard guard(setupMutex); 34 | 35 | if (previousSampleRate != sampleRate) { 36 | dsp.setup(sampleRate); 37 | } else { 38 | dsp.reset(); 39 | } 40 | setLatencySamples(int(dsp.getLatency())); 41 | previousSampleRate = sampleRate; 42 | } 43 | 44 | void Processor::releaseResources() {} 45 | 46 | bool Processor::isBusesLayoutSupported(const BusesLayout &layouts) const 47 | { 48 | if ( 49 | layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono() 50 | && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) 51 | { 52 | return false; 53 | } 54 | 55 | if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) return false; 56 | 57 | return true; 58 | } 59 | 60 | void Processor::processBlock( 61 | juce::AudioBuffer &buffer, juce::MidiBuffer & /*midi*/) 62 | { 63 | // I guess mutex shouldn't be used here. However, this is a mitigation of a crash on FL 64 | // when refreshing a plugin. It seems like `prepareToPlay` and `processBlock` might be 65 | // called at the same time. 66 | std::lock_guard guard(setupMutex); 67 | 68 | juce::ScopedNoDenormals noDenormals; 69 | 70 | auto audioPlayHead = getPlayHead(); 71 | if (audioPlayHead != nullptr) { 72 | auto positionInfo = audioPlayHead->getPosition(); 73 | if (positionInfo) { 74 | dsp.isPlaying = positionInfo->getIsPlaying(); 75 | 76 | auto bpm = positionInfo->getBpm(); 77 | if (bpm) dsp.tempo = *bpm; 78 | 79 | auto beats = positionInfo->getPpqPosition(); 80 | if (beats) dsp.beatsElapsed = *beats; 81 | 82 | auto timeSignature = positionInfo->getTimeSignature(); 83 | if (timeSignature) { 84 | dsp.timeSigUpper = timeSignature->numerator; 85 | dsp.timeSigLower = timeSignature->denominator; 86 | } 87 | } 88 | } 89 | 90 | for (auto i = getTotalNumInputChannels(); i < getTotalNumOutputChannels(); ++i) { 91 | buffer.clear(i, 0, buffer.getNumSamples()); 92 | } 93 | 94 | dsp.setParameters(); 95 | 96 | auto inCar0 = buffer.getReadPointer(0); 97 | auto inCar1 = buffer.getReadPointer(1); 98 | auto inMod0 = buffer.getReadPointer(2); 99 | auto inMod1 = buffer.getReadPointer(3); 100 | auto out0 = buffer.getWritePointer(0); 101 | auto out1 = buffer.getWritePointer(1); 102 | dsp.process((size_t)buffer.getNumSamples(), inCar0, inCar1, inMod0, inMod1, out0, out1); 103 | } 104 | 105 | bool Processor::hasEditor() const { return true; } 106 | 107 | juce::AudioProcessorEditor *Processor::createEditor() 108 | { 109 | return new Uhhyou::Editor(*this); 110 | } 111 | 112 | void Processor::getStateInformation(juce::MemoryBlock &destData) 113 | { 114 | auto state = param.tree.copyState(); 115 | std::unique_ptr xml(state.createXml()); 116 | copyXmlToBinary(*xml, destData); 117 | } 118 | 119 | void Processor::setStateInformation(const void *data, int sizeInBytes) 120 | { 121 | std::unique_ptr xmlState(getXmlFromBinary(data, sizeInBytes)); 122 | 123 | if (xmlState.get() != nullptr) { 124 | if (xmlState->hasTagName(param.tree.state.getType())) { 125 | param.tree.replaceState(juce::ValueTree::fromXml(*xmlState)); 126 | } 127 | } 128 | } 129 | 130 | juce::AudioProcessor *JUCE_CALLTYPE createPluginFilter() { return new Processor(); } 131 | -------------------------------------------------------------------------------- /experimental/SlopeFilter/dsp/filter.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace Uhhyou { 13 | 14 | /** 15 | 1-pole matched high-shelving filter. 16 | 17 | Reference: 18 | - https://vicanek.de/articles/ShelvingFits.pdf 19 | - Martin Vicanek, "Matched One-Pole Digital Shelving Filters", revised 2019-09-24. 20 | */ 21 | template class MatchedHighShelf1 { 22 | private: 23 | static constexpr Sample kp = Sample(0.0013081403895582485); 24 | std::array coTarget{}; // Target coefficients {b0, b1, -a1}. 25 | std::array coV{}; // Current coefficients. 26 | 27 | Sample x1 = 0; 28 | Sample y1 = 0; 29 | 30 | public: 31 | Sample nyquistGain() const 32 | { 33 | return (coTarget[0] - coTarget[1]) / (Sample(1) + coTarget[2]); 34 | } 35 | 36 | void push(Sample cutoffNormalized, Sample gainAmp) 37 | { 38 | using S = Sample; 39 | 40 | constexpr S minCut = S(10.0 / 48000.0); 41 | constexpr S maxCut = S(20000.0 / 44100.0); 42 | if (cutoffNormalized < minCut) { 43 | cutoffNormalized = minCut; 44 | gainAmp = S(1); 45 | } else if (cutoffNormalized > maxCut) { 46 | cutoffNormalized = maxCut; 47 | gainAmp = S(1); 48 | } 49 | 50 | constexpr S pi = std::numbers::pi_v; 51 | constexpr S phim = S(1.9510565162951536); // 1 - cos(pi * 0.9). 52 | constexpr S pp = S(2) / (pi * pi); 53 | constexpr S xi = pp / (phim * phim) - S(1) / phim; 54 | 55 | const S fc2 = cutoffNormalized * cutoffNormalized / S(4); 56 | S alpha = xi + pp / (gainAmp * fc2); 57 | S beta = xi + pp * gainAmp / fc2; 58 | 59 | S neg_a1 = alpha / (S(1) + alpha + std::sqrt(S(1) + S(2) * alpha)); 60 | S b = -beta / (S(1) + beta + std::sqrt(S(1) + S(2) * beta)); 61 | coTarget[0] = (S(1) - neg_a1) / (S(1) + b); // b0 62 | coTarget[1] = b * coTarget[0]; // b1 63 | coTarget[2] = neg_a1; // -a1 64 | } 65 | 66 | void reset(Sample cutoffNormalized, Sample gainAmp) 67 | { 68 | push(cutoffNormalized, gainAmp); 69 | coV = coTarget; 70 | 71 | x1 = 0; 72 | y1 = 0; 73 | } 74 | 75 | Sample process(Sample x0) 76 | { 77 | for (size_t i = 0; i < coV.size(); ++i) coV[i] += kp * (coTarget[i] - coV[i]); 78 | 79 | auto y0 = coV[0] * x0 + coV[1] * x1 + coV[2] * y1; 80 | x1 = x0; 81 | y1 = y0; 82 | return y0; 83 | } 84 | }; 85 | 86 | template class SlopeFilter { 87 | private: 88 | std::array, nCascade> filters; 89 | 90 | static constexpr Sample kp = Sample(0.0013081403895582485); 91 | Sample gainTarget = Sample(1); 92 | Sample gainV = Sample(1); 93 | 94 | inline Sample lowshelfGain() 95 | { 96 | Sample gain = Sample(1); 97 | for (const auto &flt : filters) gain *= flt.nyquistGain(); 98 | return Sample(1) / std::max(gain, std::numeric_limits::epsilon()); 99 | } 100 | 101 | public: 102 | #define SET_SLOPE_FILTER_PARAMETERS(METHOD) \ 103 | if (isHighshelf) { \ 104 | Sample gainAmp = std::pow(Sample(10), slopeDecibel / Sample(20)); \ 105 | Sample cutoff = startHz / sampleRate; \ 106 | for (auto &flt : filters) { \ 107 | flt.push(cutoff, gainAmp); \ 108 | cutoff *= Sample(2); \ 109 | } \ 110 | gainTarget = outputGain; \ 111 | } else { /* Low shelf */ \ 112 | Sample gainAmp = std::pow(Sample(10), -slopeDecibel / Sample(20)); \ 113 | Sample cutoff = startHz / sampleRate; \ 114 | for (auto &flt : filters) { \ 115 | flt.push(cutoff, gainAmp); \ 116 | cutoff *= Sample(0.5); \ 117 | } \ 118 | gainTarget = outputGain * lowshelfGain(); \ 119 | } 120 | 121 | void push( 122 | Sample sampleRate, 123 | Sample startHz, 124 | Sample slopeDecibel, 125 | Sample outputGain, 126 | bool isHighshelf) 127 | { 128 | SET_SLOPE_FILTER_PARAMETERS(push); 129 | } 130 | 131 | void reset( 132 | Sample sampleRate, 133 | Sample startHz, 134 | Sample slopeDecibel, 135 | Sample outputGain, 136 | bool isHighshelf) 137 | { 138 | SET_SLOPE_FILTER_PARAMETERS(reset); 139 | gainV = gainTarget; 140 | } 141 | 142 | #undef SET_SLOPE_FILTER_PARAMETERS 143 | 144 | Sample process(Sample x0) 145 | { 146 | for (auto &flt : filters) x0 = flt.process(x0); 147 | 148 | gainV += kp * (gainTarget - gainV); 149 | return gainV * x0; 150 | } 151 | }; 152 | 153 | } // namespace Uhhyou 154 | -------------------------------------------------------------------------------- /lib/Uhhyou/scaledparameter.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | #include "scale.hpp" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | // Delete following includes when `std::format` is available on Xcode. 19 | #include 20 | #include 21 | #include 22 | 23 | namespace Uhhyou { 24 | 25 | enum class ParameterTextRepresentation { 26 | normalized, 27 | raw, 28 | display, 29 | }; 30 | 31 | template class ScaledParameter : public juce::RangedAudioParameter { 32 | private: 33 | using textConvFn = std::function; 34 | 35 | float defaultNormalized{}; 36 | std::atomic raw{}; 37 | Scale &scale; // Scale might output int, uint or double. 38 | juce::NormalisableRange range; 39 | 40 | ParameterTextRepresentation textRep = ParameterTextRepresentation::display; 41 | textConvFn toTextFunc; 42 | textConvFn fromTextFunc; 43 | 44 | public: 45 | ScaledParameter( 46 | float defaultNormalized, 47 | Scale &scale, 48 | const juce::String &name, 49 | juce::AudioProcessorParameter::Category category, 50 | int versionHint, 51 | const juce::String &unitLabel = "", 52 | ParameterTextRepresentation textRep = ParameterTextRepresentation::display, 53 | std::pair textConversionFunctions = {nullptr, nullptr}) 54 | : RangedAudioParameter( 55 | juce::ParameterID(name, versionHint), 56 | name, 57 | juce::AudioProcessorParameterWithIDAttributes().withCategory(category).withLabel( 58 | unitLabel)) 59 | , defaultNormalized(defaultNormalized) 60 | , raw(float(scale.map(defaultNormalized))) 61 | , scale(scale) 62 | , range( 63 | float(scale.getMin()), 64 | float(scale.getMax()), 65 | [&](float, float, float normalized) { return normalizedToRaw(normalized); }, 66 | [&](float, float, float rawValue) { return rawToNormalized(rawValue); }, 67 | [&](float, float, float v) { return v; }) 68 | , textRep(textRep) 69 | , toTextFunc(textConversionFunctions.first) 70 | , fromTextFunc(textConversionFunctions.second) 71 | { 72 | assert( 73 | (toTextFunc == nullptr && fromTextFunc == nullptr) 74 | || (toTextFunc != nullptr && fromTextFunc != nullptr)); 75 | } 76 | 77 | virtual ~ScaledParameter() {} 78 | 79 | const juce::NormalisableRange &getNormalisableRange() const override 80 | { 81 | return range; 82 | } 83 | 84 | std::atomic *getAtomicRaw() { return &raw; } 85 | Scale &getScale() { return scale; } 86 | 87 | float rawToNormalized(float rawValue) const 88 | { 89 | return std::clamp(float(scale.invmap(rawValue)), float(0), float(1)); 90 | } 91 | 92 | float normalizedToRaw(float normalized) const 93 | { 94 | return float(scale.map(std::clamp(normalized, float(0), float(1)))); 95 | } 96 | 97 | float getDefaultValue() const override { return defaultNormalized; } 98 | 99 | private: 100 | float getValue() const override { return rawToNormalized(raw); } 101 | 102 | void setValue(float newValue) override 103 | { 104 | raw = float(scale.map(std::clamp(newValue, float(0), float(1)))); 105 | } 106 | 107 | static constexpr int decimalDigitsF32 = std::numeric_limits::digits10 + 1; 108 | 109 | std::string formatNumber(float value, int precision) const 110 | { 111 | // `std::format` was not avaialble on macOS when this was written. 112 | // Use following and delete this method in future. 113 | // 114 | // ``` 115 | // return std::format("{:.{}f}", normalized, precision); 116 | // ``` 117 | 118 | std::ostringstream os; 119 | os.precision(precision); 120 | os << std::fixed << value; 121 | return std::move(os).str(); 122 | } 123 | 124 | juce::String getText(float normalized, int precision = decimalDigitsF32) const override 125 | { 126 | // Argument `precision` is a hack that might break in future. The original name of 127 | // argument is `maximumStringLength`. However, `maximumStringLength` is not documented 128 | // in JUCE 7. 129 | if (precision >= decimalDigitsF32) precision = decimalDigitsF32; 130 | 131 | // `ParameterTextRepresentation::normalized` case doesn't require conversion. 132 | if (toTextFunc != nullptr) { 133 | return juce::String(this->formatNumber(toTextFunc(normalized), precision)); 134 | } else if (textRep == ParameterTextRepresentation::display) { 135 | return juce::String( 136 | this->formatNumber(float(scale.toDisplay(normalized)), precision)); 137 | } else if (textRep == ParameterTextRepresentation::raw) { 138 | return juce::String(this->formatNumber(float(scale.map(normalized)), precision)); 139 | } 140 | return juce::String(this->formatNumber(normalized, precision)); 141 | } 142 | 143 | float getValueForText(const juce::String &text) const override 144 | { 145 | auto value = text.getFloatValue(); 146 | // `ParameterTextRepresentation::normalized` case doesn't require conversion. 147 | if (fromTextFunc != nullptr) { 148 | value = fromTextFunc(value); 149 | } else if (textRep == ParameterTextRepresentation::display) { 150 | value = float(scale.fromDisplay(value)); 151 | } else if (textRep == ParameterTextRepresentation::raw) { 152 | value = rawToNormalized(value); 153 | } 154 | return std::clamp(value, float(0), float(1)); 155 | } 156 | }; 157 | 158 | } // namespace Uhhyou 159 | -------------------------------------------------------------------------------- /lib/Uhhyou/gui/buttonarray.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "parameterarrayattachment.hpp" 11 | #include "style.hpp" 12 | 13 | #include 14 | #include 15 | 16 | namespace Uhhyou { 17 | 18 | template class ButtonArray : public juce::Component { 19 | private: 20 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ButtonArray) 21 | 22 | juce::AudioProcessorEditor &editor; 23 | std::array parameter; 24 | Palette &pal; 25 | Scale &scale; 26 | ParameterArrayAttachment attachment; 27 | 28 | std::array value{}; 29 | 30 | bool isMouseEntered = false; 31 | bool isTogglingOn = false; 32 | juce::Point mousePos; 33 | 34 | inline int getMousePosIndex() { return int((mousePos.x * value.size()) / getWidth()); } 35 | 36 | inline float getRawValue(int index) 37 | { 38 | return value[index] == 0 ? float(scale.getMin()) : float(scale.getMax()); 39 | } 40 | 41 | inline void toggleValueAtIndex(int index) 42 | { 43 | value[index] = isTogglingOn ? 1 : 0; 44 | attachment.beginGesture(index); 45 | attachment.setValueAsPartOfGesture(index, getRawValue(index)); 46 | } 47 | 48 | public: 49 | ButtonArray( 50 | juce::AudioProcessorEditor &editor, 51 | Palette &palette, 52 | juce::UndoManager *undoManager, 53 | std::array parameter, 54 | Scale &scale) 55 | : editor(editor) 56 | , parameter(parameter) 57 | , scale(scale) 58 | , pal(palette) 59 | , attachment( 60 | parameter, 61 | [&](int index, float rawValue) { 62 | if (index < 0 && index >= value.size()) return; 63 | auto newValue = rawValue <= scale.getMin() ? 0 : 1; 64 | if (value[index] != newValue) { 65 | value[index] = newValue; 66 | repaint(); 67 | } 68 | }, 69 | undoManager) 70 | { 71 | attachment.sendInitialUpdate(); 72 | } 73 | 74 | virtual ~ButtonArray() override {} 75 | 76 | virtual void paint(juce::Graphics &ctx) override 77 | { 78 | const float lw1 = pal.borderThin(); // Border width. 79 | const float lw2 = 2 * lw1; 80 | const float lwHalf = lw1 / 2; 81 | const float width = float(getWidth()); 82 | const float height = float(getHeight()); 83 | const float innerWidth = width - lw2; 84 | const float innerHeight = height - lw2; 85 | 86 | // Background and border. 87 | ctx.setColour(pal.boxBackground()); 88 | ctx.fillRoundedRectangle(lwHalf, lwHalf, width - lw1, height - lw1, lw2); 89 | ctx.setColour(pal.foreground()); 90 | ctx.drawRoundedRectangle(lwHalf, lwHalf, width - lw1, height - lw1, lw2, lw1); 91 | 92 | // Buttons. 93 | const float buttonWidth = innerWidth / value.size(); 94 | ctx.setColour(pal.highlightMain()); 95 | for (size_t idx = 0; idx < value.size(); ++idx) { 96 | if (value[idx] != 0) 97 | ctx.fillRoundedRectangle( 98 | idx * buttonWidth + lw2, lw2, buttonWidth - lw2, innerHeight - lw2, lw2); 99 | } 100 | 101 | // Highlight. 102 | const size_t valueIndex = getMousePosIndex(); 103 | if (isMouseEntered && valueIndex < value.size()) { 104 | ctx.setColour(pal.overlayHighlight()); 105 | ctx.fillRoundedRectangle( 106 | valueIndex * buttonWidth + lw1, lw1, buttonWidth, innerHeight, lw2); 107 | } 108 | } 109 | 110 | void mouseMove(const juce::MouseEvent &event) override 111 | { 112 | mousePos = event.position; 113 | repaint(); 114 | } 115 | 116 | void mouseEnter(const juce::MouseEvent &event) override 117 | { 118 | isMouseEntered = true; 119 | mousePos = event.position; 120 | repaint(); 121 | } 122 | 123 | void mouseExit(const juce::MouseEvent &event) override 124 | { 125 | isMouseEntered = false; 126 | mousePos = event.position; 127 | repaint(); 128 | } 129 | 130 | void mouseDown(const juce::MouseEvent &event) override 131 | { 132 | mousePos = event.position; 133 | 134 | if (event.mods.isRightButtonDown()) { 135 | auto hostContext = editor.getHostContext(); 136 | if (hostContext == nullptr) return; 137 | 138 | auto index = getMousePosIndex(); 139 | if (index >= value.size()) return; 140 | 141 | auto hostContextMenu = hostContext->getContextMenuForParameter(parameter[index]); 142 | if (hostContextMenu == nullptr) return; 143 | 144 | hostContextMenu->showNativeMenu(editor.getMouseXYRelative()); 145 | return; 146 | } 147 | 148 | if (!event.mods.isLeftButtonDown()) return; 149 | 150 | const auto index = getMousePosIndex(); 151 | if (index < value.size()) { 152 | isTogglingOn = value[index] == 0; 153 | toggleValueAtIndex(index); 154 | } 155 | repaint(); 156 | } 157 | 158 | void mouseDrag(const juce::MouseEvent &event) override 159 | { 160 | if (!event.mods.isLeftButtonDown()) return; 161 | mousePos = event.position; 162 | const auto index = getMousePosIndex(); 163 | if (index < value.size()) toggleValueAtIndex(index); 164 | repaint(); 165 | } 166 | 167 | void mouseUp(const juce::MouseEvent &) override { attachment.endGesture(); } 168 | 169 | void mouseDoubleClick(const juce::MouseEvent &) override {} 170 | 171 | void 172 | mouseWheelMove(const juce::MouseEvent &, const juce::MouseWheelDetails &wheel) override 173 | { 174 | const auto index = getMousePosIndex(); 175 | if (index < value.size() && wheel.deltaY != 0) { 176 | value[index] = value[index] == 0 ? 1 : 0; 177 | attachment.setValueAsCompleteGesture(index, getRawValue(index)); 178 | } 179 | repaint(); 180 | } 181 | }; 182 | 183 | } // namespace Uhhyou 184 | -------------------------------------------------------------------------------- /experimental/TwoBandStereo/PluginEditor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include "PluginEditor.h" 5 | #include "PluginProcessor.h" 6 | 7 | #include "./gui/popupinformationtext.hpp" 8 | #include "Uhhyou/librarylicense.hpp" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #define HEAD (*this), (palette), &(processor.undoManager) 15 | #define PARAMETER(id) processor.param.tree.getParameter(id) 16 | #define SCALE(name) processor.param.scale.name 17 | #define VALUE(name) processor.param.value.name 18 | #define TREE() processor.param.tree 19 | 20 | // Parameter related arguments. 21 | #define ACTION_BUTTON (*this), palette, statusBar, numberEditor 22 | #define PRM(id, scale) HEAD, PARAMETER(id), SCALE(scale), statusBar, numberEditor 23 | 24 | namespace Uhhyou { 25 | 26 | constexpr int defaultWidth = 2 * 210 + 2 * 20; 27 | constexpr int defaultHeight = 10 * 30; 28 | 29 | inline juce::File getPresetDirectory(const juce::AudioProcessor &processor) 30 | { 31 | auto appDir = juce::File::getSpecialLocation(juce::File::userApplicationDataDirectory) 32 | .getFullPathName(); 33 | auto sep = juce::File::getSeparatorString(); 34 | 35 | juce::File presetDir(appDir + sep + "Uhhyou" + sep + processor.getName()); 36 | if (!(presetDir.exists() && presetDir.isDirectory())) presetDir.createDirectory(); 37 | return presetDir; 38 | } 39 | 40 | template 41 | inline auto constructParamArray( 42 | juce::AudioProcessorValueTreeState &tree, std::string baseName, size_t indexOffset = 0) 43 | { 44 | std::array params; 45 | for (size_t idx = 0; idx < nParameter; ++idx) { 46 | params[idx] = tree.getParameter(baseName + std::to_string(idx + indexOffset)); 47 | } 48 | return params; 49 | } 50 | 51 | Editor::Editor(Processor &processor) 52 | : AudioProcessorEditor(processor) 53 | , processor(processor) 54 | , statusBar(*this, palette) 55 | , numberEditor(palette) 56 | 57 | , pluginNameButton( 58 | *this, palette, processor.getName(), informationText, libraryLicenseText) 59 | , undoButton( 60 | ACTION_BUTTON, 61 | "Undo", 62 | [&]() { 63 | if (processor.undoManager.canUndo()) processor.undoManager.undo(); 64 | }) 65 | , redoButton( 66 | ACTION_BUTTON, 67 | "Redo", 68 | [&]() { 69 | if (processor.undoManager.canRedo()) processor.undoManager.redo(); 70 | }) 71 | , randomizeButton( 72 | ACTION_BUTTON, 73 | "Randomize", 74 | [&]() { 75 | std::uniform_real_distribution dist{0.0f, 1.0f}; 76 | std::random_device dev; 77 | std::mt19937 rng(dev()); 78 | 79 | auto params = processor.getParameters(); 80 | for (auto &prm : params) { 81 | prm->beginChangeGesture(); 82 | prm->setValueNotifyingHost(dist(rng)); 83 | prm->endChangeGesture(); 84 | } 85 | }) 86 | , presetManager((*this), (palette), &(processor.undoManager), processor.param.tree) 87 | 88 | , crossoverHz(PRM("crossoverHz", crossoverHz), 5) 89 | , upperStereoSpread(PRM("upperStereoSpread", unipolar), 5) 90 | , lowerStereoSpread(PRM("lowerStereoSpread", unipolar), 5) 91 | 92 | { 93 | setDefaultColor(lookAndFeel, palette); 94 | 95 | setResizable(true, false); 96 | getConstrainer()->setFixedAspectRatio(double(defaultWidth) / double(defaultHeight)); 97 | const float scale = getStateTree().getProperty("scale", 1.0f); 98 | setSize(int(scale * defaultWidth), int(scale * defaultHeight)); 99 | } 100 | 101 | Editor::~Editor() {} 102 | 103 | void Editor::paint(juce::Graphics &ctx) 104 | { 105 | ctx.setColour(palette.background()); 106 | ctx.fillAll(); 107 | 108 | ctx.setColour(palette.foreground()); 109 | for (const auto &x : lines) x.paint(ctx); 110 | 111 | ctx.setFont(palette.getFont(palette.textSizeUi())); 112 | for (const auto &x : labels) x.paint(ctx); 113 | 114 | auto groupLabelFont = palette.getFont(palette.textSizeUi()); 115 | auto groupLabelMarginWidth 116 | = juce::GlyphArrangement::getStringWidth(groupLabelFont, "W"); 117 | for (const auto &x : groupLabels) { 118 | x.paint(ctx, groupLabelFont, 2 * palette.borderThin(), groupLabelMarginWidth); 119 | } 120 | } 121 | 122 | void Editor::resized() 123 | { 124 | using Rect = juce::Rectangle; 125 | 126 | const float scale = getDesktopScaleFactor() * getHeight() / float(defaultHeight); 127 | getStateTree().setProperty("scale", scale, nullptr); 128 | palette.resize(scale); 129 | 130 | lines.clear(); 131 | labels.clear(); 132 | groupLabels.clear(); 133 | 134 | const int margin = int(5 * scale); 135 | const int labelHeight = int(20 * scale); 136 | const int labelWidth = int(100 * scale); 137 | const int bottom = int(scale * defaultHeight); 138 | 139 | const int uiMargin = 4 * margin; 140 | const int labelX = labelWidth + 2 * margin; 141 | const int labelY = labelHeight + 2 * margin; 142 | const int sectionWidth = 2 * labelWidth + 2 * margin; 143 | 144 | const int top0 = uiMargin; 145 | const int left0 = uiMargin; 146 | const int left1 = left0 + 2 * labelX; 147 | 148 | layoutVerticalSection( 149 | labels, groupLabels, left0, top0, sectionWidth, labelWidth, labelWidth, labelX, 150 | labelHeight, labelY, "Stereo Control", 151 | { 152 | {"Crossover [Hz]", crossoverHz}, 153 | {"Upper Spread", upperStereoSpread}, 154 | {"Lower Spread", lowerStereoSpread}, 155 | }); 156 | 157 | const int nameTop0 = layoutActionSection( 158 | groupLabels, left1, top0, sectionWidth, labelWidth, labelWidth, labelX, labelHeight, 159 | labelY, undoButton, redoButton, randomizeButton, presetManager); 160 | 161 | statusBar.setBounds( 162 | Rect{left0, bottom - labelHeight - uiMargin, 2 * sectionWidth, labelHeight}); 163 | 164 | pluginNameButton.setBounds(Rect{left1, nameTop0, sectionWidth, labelHeight}); 165 | pluginNameButton.scale(scale); 166 | } 167 | 168 | } // namespace Uhhyou 169 | -------------------------------------------------------------------------------- /experimental/SlopeFilter/PluginEditor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include "PluginEditor.h" 5 | #include "PluginProcessor.h" 6 | 7 | #include "./gui/popupinformationtext.hpp" 8 | #include "Uhhyou/librarylicense.hpp" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #define HEAD (*this), (palette), &(processor.undoManager) 15 | #define PARAMETER(id) processor.param.tree.getParameter(id) 16 | #define SCALE(name) processor.param.scale.name 17 | #define VALUE(name) processor.param.value.name 18 | #define TREE() processor.param.tree 19 | 20 | // Parameter related arguments. 21 | #define ACTION_BUTTON (*this), palette, statusBar, numberEditor 22 | #define PRM(id, scale) HEAD, PARAMETER(id), SCALE(scale), statusBar, numberEditor 23 | 24 | namespace Uhhyou { 25 | 26 | constexpr int defaultWidth = 2 * 210 + 2 * 20; 27 | constexpr int defaultHeight = 10 * 30; 28 | 29 | inline juce::File getPresetDirectory(const juce::AudioProcessor &processor) 30 | { 31 | auto appDir = juce::File::getSpecialLocation(juce::File::userApplicationDataDirectory) 32 | .getFullPathName(); 33 | auto sep = juce::File::getSeparatorString(); 34 | 35 | juce::File presetDir(appDir + sep + "Uhhyou" + sep + processor.getName()); 36 | if (!(presetDir.exists() && presetDir.isDirectory())) presetDir.createDirectory(); 37 | return presetDir; 38 | } 39 | 40 | template 41 | inline auto constructParamArray( 42 | juce::AudioProcessorValueTreeState &tree, std::string baseName, size_t indexOffset = 0) 43 | { 44 | std::array params; 45 | for (size_t idx = 0; idx < nParameter; ++idx) { 46 | params[idx] = tree.getParameter(baseName + std::to_string(idx + indexOffset)); 47 | } 48 | return params; 49 | } 50 | 51 | Editor::Editor(Processor &processor) 52 | : AudioProcessorEditor(processor) 53 | , processor(processor) 54 | , statusBar(*this, palette) 55 | , numberEditor(palette) 56 | 57 | , pluginNameButton( 58 | *this, palette, processor.getName(), informationText, libraryLicenseText) 59 | , undoButton( 60 | ACTION_BUTTON, 61 | "Undo", 62 | [&]() { 63 | if (processor.undoManager.canUndo()) processor.undoManager.undo(); 64 | }) 65 | , redoButton( 66 | ACTION_BUTTON, 67 | "Redo", 68 | [&]() { 69 | if (processor.undoManager.canRedo()) processor.undoManager.redo(); 70 | }) 71 | , randomizeButton( 72 | ACTION_BUTTON, 73 | "Randomize", 74 | [&]() { 75 | std::uniform_real_distribution dist{0.0f, 1.0f}; 76 | std::random_device dev; 77 | std::mt19937 rng(dev()); 78 | 79 | auto params = processor.getParameters(); 80 | for (auto &prm : params) { 81 | prm->beginChangeGesture(); 82 | prm->setValueNotifyingHost(dist(rng)); 83 | prm->endChangeGesture(); 84 | } 85 | }) 86 | , presetManager((*this), (palette), &(processor.undoManager), processor.param.tree) 87 | 88 | , shelvingType(PRM("shelvingType", shelvingType), {"Low Shelf", "High Shelf"}) 89 | , startHz(PRM("startHz", startHz), 5) 90 | , slopeDecibel(PRM("slopeDecibel", slopeDecibel), 5) 91 | , outputGain(PRM("outputGain", outputGain), 5) 92 | { 93 | setDefaultColor(lookAndFeel, palette); 94 | 95 | setResizable(true, false); 96 | getConstrainer()->setFixedAspectRatio(double(defaultWidth) / double(defaultHeight)); 97 | const float scale = getStateTree().getProperty("scale", 1.0f); 98 | setSize(int(scale * defaultWidth), int(scale * defaultHeight)); 99 | } 100 | 101 | Editor::~Editor() {} 102 | 103 | void Editor::paint(juce::Graphics &ctx) 104 | { 105 | ctx.setColour(palette.background()); 106 | ctx.fillAll(); 107 | 108 | ctx.setColour(palette.foreground()); 109 | for (const auto &x : lines) x.paint(ctx); 110 | 111 | ctx.setFont(palette.getFont(palette.textSizeUi())); 112 | for (const auto &x : labels) x.paint(ctx); 113 | 114 | auto groupLabelFont = palette.getFont(palette.textSizeUi()); 115 | auto groupLabelMarginWidth 116 | = juce::GlyphArrangement::getStringWidth(groupLabelFont, "W"); 117 | for (const auto &x : groupLabels) { 118 | x.paint(ctx, groupLabelFont, 2 * palette.borderThin(), groupLabelMarginWidth); 119 | } 120 | } 121 | 122 | void Editor::resized() 123 | { 124 | using Rect = juce::Rectangle; 125 | 126 | const float scale = getDesktopScaleFactor() * getHeight() / float(defaultHeight); 127 | getStateTree().setProperty("scale", scale, nullptr); 128 | palette.resize(scale); 129 | 130 | lines.clear(); 131 | labels.clear(); 132 | groupLabels.clear(); 133 | 134 | const int margin = int(5 * scale); 135 | const int labelHeight = int(20 * scale); 136 | const int labelWidth = int(100 * scale); 137 | const int bottom = int(scale * defaultHeight); 138 | 139 | const int uiMargin = 4 * margin; 140 | const int labelX = labelWidth + 2 * margin; 141 | const int labelY = labelHeight + 2 * margin; 142 | const int sectionWidth = 2 * labelWidth + 2 * margin; 143 | 144 | const int top0 = uiMargin; 145 | const int left0 = uiMargin; 146 | const int left1 = left0 + 2 * labelX; 147 | 148 | layoutVerticalSection( 149 | labels, groupLabels, left0, top0, sectionWidth, labelWidth, labelWidth, labelX, 150 | labelHeight, labelY, "Slope Filter", 151 | { 152 | {"Type", shelvingType}, 153 | {"Slope Start [Hz]", startHz}, 154 | {"Slope [dB/oct]", slopeDecibel}, 155 | {"Output [dB]", outputGain}, 156 | }); 157 | 158 | const int nameTop0 = layoutActionSection( 159 | groupLabels, left1, top0, sectionWidth, labelWidth, labelWidth, labelX, labelHeight, 160 | labelY, undoButton, redoButton, randomizeButton, presetManager); 161 | 162 | statusBar.setBounds( 163 | Rect{left0, bottom - labelHeight - uiMargin, 2 * sectionWidth, labelHeight}); 164 | 165 | // Plugin name. 166 | pluginNameButton.setBounds(Rect{left1, nameTop0, sectionWidth, labelHeight}); 167 | pluginNameButton.scale(scale); 168 | } 169 | 170 | } // namespace Uhhyou 171 | -------------------------------------------------------------------------------- /lib/Uhhyou/gui/combobox.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "../scaledparameter.hpp" 11 | #include "./numbereditor.hpp" 12 | #include "style.hpp" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace Uhhyou { 20 | 21 | template 22 | class ComboBox : public juce::Component { 23 | private: 24 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ComboBox) 25 | 26 | protected: 27 | juce::AudioProcessorEditor &editor; 28 | const juce::RangedAudioParameter *const parameter; 29 | 30 | Scale &scale; 31 | Palette &pal; 32 | StatusBar &statusBar; 33 | NumberEditor &numberEditor; 34 | juce::ParameterAttachment attachment; 35 | 36 | juce::PopupMenu menu; 37 | 38 | int itemIndex{}; 39 | int defaultIndex{}; 40 | 41 | bool isMouseEntered = false; 42 | juce::Font font; 43 | std::vector items; 44 | 45 | void updateStatusBar() 46 | { 47 | // TODO: Use . 48 | auto text = parameter->getName(256); 49 | text += ": "; 50 | text += items[itemIndex]; 51 | text += " ("; 52 | text += juce::String(itemIndex + 1); 53 | text += "/"; 54 | text += juce::String(items.size()); 55 | text += ")"; 56 | statusBar.setText(text); 57 | } 58 | 59 | public: 60 | ComboBox( 61 | juce::AudioProcessorEditor &editor, 62 | Palette &palette, 63 | juce::UndoManager *undoManager, 64 | juce::RangedAudioParameter *parameter, 65 | Scale &scale, 66 | StatusBar &statusBar, 67 | NumberEditor &numberEditor, 68 | std::vector menuItems) 69 | : editor(editor) 70 | , parameter(parameter) 71 | , scale(scale) 72 | , pal(palette) 73 | , statusBar(statusBar) 74 | , numberEditor(numberEditor) 75 | , attachment( 76 | *parameter, 77 | [&](float newRaw) { 78 | int idx = int(std::floor(newRaw) + 0.5f); 79 | if (itemIndex == idx || idx < 0 || idx > int(items.size())) return; 80 | itemIndex = idx; 81 | repaint(); 82 | }, 83 | undoManager) 84 | , defaultIndex(int(scale.map(parameter->getDefaultValue()))) 85 | , font(palette.getFont(palette.textSizeUi())) 86 | , items(menuItems) 87 | { 88 | attachment.sendInitialUpdate(); 89 | editor.addAndMakeVisible(*this, 0); 90 | } 91 | 92 | virtual ~ComboBox() override {} 93 | 94 | virtual void resized() override { font = pal.getFont(pal.textSizeUi()); } 95 | 96 | virtual void paint(juce::Graphics &ctx) override 97 | { 98 | const float lw1 = pal.borderThin(); // Border width. 99 | const float lw2 = 2 * lw1; 100 | const float lwHalf = lw1 / 2; 101 | const float width = float(getWidth()); 102 | const float height = float(getHeight()); 103 | 104 | // Background. 105 | ctx.setColour(pal.boxBackground()); 106 | ctx.fillRoundedRectangle(lwHalf, lwHalf, width - lw1, height - lw1, lw2); 107 | 108 | // Border. 109 | if constexpr (style == Uhhyou::Style::accent) { 110 | ctx.setColour(isMouseEntered ? pal.highlightAccent() : pal.border()); 111 | } else if constexpr (style == Uhhyou::Style::warning) { 112 | ctx.setColour(isMouseEntered ? pal.highlightWarning() : pal.border()); 113 | } else { 114 | ctx.setColour(isMouseEntered ? pal.highlightButton() : pal.border()); 115 | } 116 | ctx.drawRoundedRectangle(lwHalf, lwHalf, width - lw1, height - lw1, lw2, lw1); 117 | 118 | // Text. 119 | if (itemIndex >= 0 && itemIndex < int(items.size())) { 120 | ctx.setFont(font); 121 | ctx.setColour(pal.foreground()); 122 | ctx.drawText( 123 | items[size_t(itemIndex)], 124 | juce::Rectangle(float(0), float(0), width, height), 125 | juce::Justification::centred); 126 | } 127 | } 128 | 129 | virtual void mouseMove(const juce::MouseEvent &) override {} 130 | 131 | virtual void mouseEnter(const juce::MouseEvent &) override 132 | { 133 | isMouseEntered = true; 134 | repaint(); 135 | } 136 | 137 | virtual void mouseExit(const juce::MouseEvent &) override 138 | { 139 | isMouseEntered = false; 140 | repaint(); 141 | } 142 | 143 | virtual void mouseDown(const juce::MouseEvent &event) override 144 | { 145 | if (event.mods.isRightButtonDown()) { 146 | auto hostContext = editor.getHostContext(); 147 | if (hostContext == nullptr) return; 148 | 149 | auto hostContextMenu = hostContext->getContextMenuForParameter(parameter); 150 | if (hostContextMenu == nullptr) return; 151 | 152 | hostContextMenu->showNativeMenu(editor.getMouseXYRelative()); 153 | return; 154 | } 155 | 156 | if (event.mods.isLeftButtonDown()) { 157 | menu.clear(); 158 | for (size_t idx = 0; idx < items.size(); ++idx) { 159 | menu.addItem(juce::PopupMenu::Item(items[idx]) 160 | .setID(int(idx) + 1) 161 | .setTicked(int(idx) == itemIndex)); 162 | } 163 | 164 | menu.showMenuAsync( 165 | juce::PopupMenu::Options().withInitiallySelectedItem(itemIndex + 1), [&](int id) { 166 | int idx = id - 1; 167 | if (idx < 0 || idx >= int(items.size())) return; 168 | itemIndex = idx; 169 | attachment.setValueAsCompleteGesture(float(itemIndex)); 170 | updateStatusBar(); 171 | repaint(); 172 | }); 173 | } 174 | } 175 | 176 | virtual void mouseDrag(const juce::MouseEvent &) override {} 177 | virtual void mouseUp(const juce::MouseEvent &) override {} 178 | virtual void mouseDoubleClick(const juce::MouseEvent &) override {} 179 | 180 | virtual void 181 | mouseWheelMove(const juce::MouseEvent &, const juce::MouseWheelDetails &wheel) override 182 | { 183 | if (std::abs(wheel.deltaY) <= std::numeric_limits::epsilon()) return; 184 | if (items.size() <= 0) return; 185 | const int size = int(items.size()); 186 | itemIndex = (itemIndex + (wheel.deltaY < 0 ? -1 : 1) + size) % size; 187 | attachment.setValueAsCompleteGesture(float(itemIndex)); 188 | updateStatusBar(); 189 | repaint(); 190 | } 191 | }; 192 | 193 | } // namespace Uhhyou 194 | -------------------------------------------------------------------------------- /lib/Uhhyou/gui/parameterarrayattachment.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace Uhhyou { 18 | 19 | /** 20 | This is basically `ParameterAttachment`, but can handle an array of parameters instead of 21 | a single parameter. 22 | 23 | I thought taking `AudioProcessorParameterGroup` (APPG) at constructor is better, but APPG 24 | doesn't have method to return `RangedAudioParameter` in JUCE 7. Maybe rewrite this when 25 | something like `RangedAudioParameterGroup` is added to JUCE. 26 | 27 | Advantage of using APPG is easier parameter ID management. Only a group ID is required, 28 | instead of all parameter IDs in a group. Also template can be omitted. Disadvantage is 29 | dynamic allocation. 30 | */ 31 | template 32 | class ParameterArrayAttachment : private juce::AudioProcessorParameter::Listener, 33 | private juce::AsyncUpdater { 34 | public: 35 | ParameterArrayAttachment( 36 | std::array parameter, 37 | std::function parameterChangedCallback, 38 | juce::UndoManager *undoManager = nullptr) 39 | : parameter(parameter) 40 | , undoManager(undoManager) 41 | , parameterChangedCallback(std::move(parameterChangedCallback)) 42 | { 43 | isEditing.fill(false); 44 | 45 | for (auto &x : parameter) x->addListener(this); 46 | 47 | for (int i = 0; i < nParameter; ++i) 48 | indexMap.emplace(std::make_pair(parameter[i]->getParameterIndex(), i)); 49 | } 50 | 51 | virtual ~ParameterArrayAttachment() override 52 | { 53 | for (auto &x : parameter) x->removeListener(this); 54 | cancelPendingUpdate(); 55 | } 56 | 57 | void sendInitialUpdate() 58 | { 59 | for (int i = 0; i < nParameter; ++i) 60 | parameterValueChanged(parameter[i]->getParameterIndex(), parameter[i]->getValue()); 61 | } 62 | 63 | void setValueAsCompleteGesture(int index, float newRawValue) 64 | { 65 | callIfParameterValueChanged(index, newRawValue, [this](int i, float v) { 66 | parameter[i]->beginChangeGesture(); 67 | parameter[i]->setValueNotifyingHost(v); 68 | parameter[i]->endChangeGesture(); 69 | }); 70 | } 71 | 72 | void setValueAsCompleteGesture(const std::array &newRawValue) 73 | { 74 | beginGesture(); 75 | for (int index = 0; index < nParameter; ++index) { 76 | callIfParameterValueChanged(index, newRawValue[index], [this](int i, float v) { 77 | parameter[i]->setValueNotifyingHost(v); 78 | }); 79 | } 80 | endGesture(); 81 | } 82 | 83 | void beginGesture() 84 | { 85 | if (undoManager != nullptr) undoManager->beginNewTransaction(); 86 | for (int index = 0; index < nParameter; ++index) { 87 | if (!isEditing[index]) parameter[index]->beginChangeGesture(); 88 | } 89 | isEditing.fill(true); 90 | } 91 | 92 | void beginGesture(int index) 93 | { 94 | if (isEditing[index]) return; 95 | if (undoManager != nullptr) undoManager->beginNewTransaction(); 96 | parameter[index]->beginChangeGesture(); 97 | isEditing[index] = true; 98 | } 99 | 100 | void setValueAsPartOfGesture(int index, float newRawValue) 101 | { 102 | callIfParameterValueChanged(index, newRawValue, [this](int i, float v) { 103 | parameter[i]->setValueNotifyingHost(v); 104 | }); 105 | } 106 | 107 | void setValueAsPartOfGesture(const std::array &newRawValue) 108 | { 109 | for (int index = 0; index < nParameter; ++index) { 110 | callIfParameterValueChanged(index, newRawValue[index], [this](int i, float v) { 111 | parameter[i]->setValueNotifyingHost(v); 112 | }); 113 | } 114 | } 115 | 116 | void endGesture() 117 | { 118 | for (int index = 0; index < nParameter; ++index) { 119 | if (isEditing[index]) parameter[index]->endChangeGesture(); 120 | } 121 | isEditing.fill(false); 122 | } 123 | 124 | void endGesture(int index) 125 | { 126 | parameter[index]->endChangeGesture(); 127 | isEditing[index] = false; 128 | } 129 | 130 | private: 131 | template 132 | void callIfParameterValueChanged(int index, float newRawValue, Callback &&callback) 133 | { 134 | const auto newValue = parameter[index]->convertTo0to1(newRawValue); 135 | if (parameter[index]->getValue() != newValue) callback(index, newValue); 136 | } 137 | 138 | // `parameterIndex` is a value from `AudioParameter::getParameterIndex()`. 139 | void parameterValueChanged(int parameterIndex, float newValue) override 140 | { 141 | if (!indexMap.contains(parameterIndex)) return; 142 | auto index = indexMap[parameterIndex]; 143 | lastValue[index] = newValue; 144 | 145 | if (juce::MessageManager::getInstance()->isThisTheMessageThread()) { 146 | cancelPendingUpdate(); 147 | handleAsyncUpdate(index); 148 | } else { 149 | triggerAsyncUpdate(); 150 | } 151 | } 152 | 153 | void parameterGestureChanged(int, bool) override {} 154 | 155 | void handleAsyncUpdate() override 156 | { 157 | if (parameterChangedCallback != nullptr) 158 | for (int i = 0; i < nParameter; ++i) 159 | parameterChangedCallback(i, parameter[i]->convertFrom0to1(lastValue[i])); 160 | } 161 | 162 | void handleAsyncUpdate(int index) 163 | { 164 | if (parameterChangedCallback != nullptr) { 165 | parameterChangedCallback( 166 | index, parameter[index]->convertFrom0to1(lastValue[index])); 167 | } 168 | } 169 | 170 | // JUCE 7 holds parameters in a single `juce::Array`. `indexMap` is used to map the 171 | // index of `juce::Array` to the internal index used in this class. 172 | std::unordered_map indexMap; 173 | 174 | std::array parameter; 175 | std::array isEditing{}; 176 | std::array, nParameter> lastValue{}; 177 | juce::UndoManager *undoManager = nullptr; 178 | std::function parameterChangedCallback; 179 | 180 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ParameterArrayAttachment) 181 | }; 182 | 183 | } // namespace Uhhyou 184 | -------------------------------------------------------------------------------- /lib/specialmath/jyzo/jyzo.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | The comments on the funcions are copied from original Fortran90 implementation 3 | (special_functions.f90) found in the link below. 4 | 5 | - https://people.sc.fsu.edu/~jburkardt/f_src/special_functions/special_functions.html 6 | */ 7 | 8 | #pragma once 9 | #include 10 | #include 11 | #include 12 | 13 | namespace Uhhyou { 14 | 15 | //*****************************************************************************80 16 | // 17 | //! JYNDD: Bessel functions Jn(x) and Yn(x), first and second derivatives. 18 | // 19 | // Licensing: 20 | // 21 | // This routine is copyrighted by Shanjie Zhang and Jianming Jin. However, 22 | // they give permission to incorporate this routine into a user program 23 | // provided that the copyright is acknowledged. 24 | // 25 | // Modified: 26 | // 27 | // 02 August 2012 28 | // 29 | // Author: 30 | // 31 | // Shanjie Zhang, Jianming Jin 32 | // 33 | // Reference: 34 | // 35 | // Shanjie Zhang, Jianming Jin, 36 | // Computation of Special Functions, 37 | // Wiley, 1996, 38 | // ISBN: 0-471-11963-6, 39 | // LC: QA351.C45. 40 | // 41 | // Parameters: 42 | // 43 | // Input, integer N, the order. 44 | // 45 | // Input, real ( kind = rk ) X, the argument. 46 | // 47 | // Output, real ( kind = rk ) BJN, DJN, FJN, BYN, DYN, FYN, the values of 48 | // Jn(x), Jn'(x), Jn"(x), Yn(x), Yn'(x), Yn"(x). 49 | // 50 | inline std::array jyndd(int n, double x) 51 | { 52 | double f{}; 53 | 54 | int nt; 55 | for (nt = 1; nt <= 900; ++nt) { 56 | int mt = int(0.5 * std::log10(6.28 * nt) - nt * std::log10(1.36 * std::abs(x) / nt)); 57 | if (20 < mt) break; 58 | } 59 | 60 | std::vector bj(nt, 0.0); 61 | std::vector by(nt, 0.0); 62 | 63 | double bs = 0.0; 64 | double f0 = 0.0; 65 | double f1 = 1.0e-35; 66 | double su = 0.0; 67 | for (int k = nt; k >= 0; --k) { 68 | f = 2.0 * (k + 1.0) * f1 / x - f0; 69 | if (k <= n + 1) bj[k] = f; 70 | if (k == 2 * (k / 2)) { 71 | bs = bs + 2.0 * f; 72 | if (k != 0) su += (k / 2) % 2 == 0 ? f / k : -f / k; 73 | } 74 | f0 = f1; 75 | f1 = f; 76 | } 77 | 78 | for (int k = 0; k <= n + 1; ++k) bj[k] = bj[k] / (bs - f); 79 | 80 | double bjn = bj[n]; 81 | double ec = 0.5772156649015329; 82 | double e0 = 0.3183098861837907; 83 | double s1 = 2.0 * e0 * (std::log(x / 2.0) + ec) * bj[0]; 84 | f0 = s1 - 8.0 * e0 * su / (bs - f); 85 | f1 = (bj[1] * f0 - 2.0 * e0 / x) / bj[0]; 86 | 87 | by[0] = f0; 88 | by[1] = f1; 89 | for (int k = 2; k <= n + 1; ++k) { 90 | f = 2.0 * (k - 1.0) * f1 / x - f0; 91 | by[k] = f; 92 | f0 = f1; 93 | f1 = f; 94 | } 95 | 96 | double byn = by[n]; 97 | double djn = -bj[n + 1] + n * bj[n] / x; 98 | double dyn = -by[n + 1] + n * by[n] / x; 99 | double fjn = (n * n / (x * x) - 1.0) * bjn - djn / x; 100 | double fyn = (n * n / (x * x) - 1.0) * byn - dyn / x; 101 | 102 | return {bjn, djn, fjn, byn, dyn, fyn}; 103 | } 104 | 105 | //*****************************************************************************80 106 | // 107 | //! JYZO computes the zeros of Bessel functions Jn(x), Yn(x) and derivatives. 108 | // 109 | // Licensing: 110 | // 111 | // This routine is copyrighted by Shanjie Zhang and Jianming Jin. However, 112 | // they give permission to incorporate this routine into a user program 113 | // provided that the copyright is acknowledged. 114 | // 115 | // Modified: 116 | // 117 | // 28 July 2012 118 | // 119 | // Author: 120 | // 121 | // Shanjie Zhang, Jianming Jin 122 | // 123 | // Reference: 124 | // 125 | // Shanjie Zhang, Jianming Jin, 126 | // Computation of Special Functions, 127 | // Wiley, 1996, 128 | // ISBN: 0-471-11963-6, 129 | // LC: QA351.C45. 130 | // 131 | // Parameters: 132 | // 133 | // Input, integer N, the order of the Bessel functions. 134 | // 135 | // Input, integer NT, the number of zeros. 136 | // 137 | // Output, real ( kind = rk ) RJ0(NT), RJ1(NT), RY0(NT), RY1(NT), the zeros 138 | // of Jn(x), Jn'(x), Yn(x), Yn'(x). 139 | // 140 | inline std::array, 4> jyzo(int n, int nt) 141 | { 142 | std::vector rj0(nt, 0.0); 143 | std::vector rj1(nt, 0.0); 144 | std::vector ry0(nt, 0.0); 145 | std::vector ry1(nt, 0.0); 146 | 147 | int n_r8 = n; 148 | 149 | double n_r8_pow_1_3 = std::pow(double(n_r8), double(0.33333)); 150 | int n_r8_pow_2 = n_r8 * n_r8; 151 | 152 | double x = n <= 20 ? 2.82141 + 1.15859 * n_r8 153 | : n + 1.85576 * n_r8_pow_1_3 + 1.03315 / n_r8_pow_1_3; 154 | 155 | int l = 0; 156 | while (true) { 157 | double x0 = x; 158 | auto [bjn, djn, fjn, byn, dyn, fyn] = jyndd(n, x); 159 | x = x - bjn / djn; 160 | 161 | if (1.0e-09 < std::abs(x - x0)) continue; 162 | 163 | rj0[l++] = x; 164 | x = x + 3.1416 + (0.0972 + 0.0679 * n_r8 - 0.000354 * n_r8_pow_2) / l; 165 | 166 | if (nt <= l) break; 167 | } 168 | 169 | x = n <= 20 ? 0.961587 + 1.07703 * n_r8 170 | : n_r8 + 0.80861 * n_r8_pow_1_3 + 0.07249 / n_r8_pow_1_3; 171 | 172 | if (n == 0) x = 3.8317; 173 | 174 | l = 0; 175 | while (true) { 176 | double x0 = x; 177 | auto [bjn, djn, fjn, byn, dyn, fyn] = jyndd(n, x); 178 | x = x - djn / fjn; 179 | if (1.0e-09 < std::abs(x - x0)) continue; 180 | rj1[l++] = x; 181 | x = x + 3.1416 + (0.4955 + 0.0915 * n_r8 - 0.000435 * n_r8_pow_2) / l; 182 | 183 | if (nt <= l) break; 184 | } 185 | 186 | x = n <= 20 ? 1.19477 + 1.08933 * n_r8 187 | : n_r8 + 0.93158 * n_r8_pow_1_3 + 0.26035 / n_r8_pow_1_3; 188 | 189 | l = 0; 190 | while (true) { 191 | double x0 = x; 192 | auto [bjn, djn, fjn, byn, dyn, fyn] = jyndd(n, x); 193 | x = x - byn / dyn; 194 | 195 | if (1.0e-09 < std::abs(x - x0)) continue; 196 | 197 | ry0[l++] = x; 198 | x = x + 3.1416 + (0.312 + 0.0852 * n_r8 - 0.000403 * n_r8_pow_2) / l; 199 | 200 | if (nt <= l) break; 201 | } 202 | 203 | x = n <= 20 ? 2.67257 + 1.16099 * n_r8 204 | : n_r8 + 1.8211 * n_r8_pow_1_3 + 0.94001 / n_r8_pow_1_3; 205 | 206 | l = 0; 207 | while (true) { 208 | double x0 = x; 209 | auto [bjn, djn, fjn, byn, dyn, fyn] = jyndd(n, x); 210 | x = x - dyn / fyn; 211 | 212 | if (1.0e-09 < std::abs(x - x0)) continue; 213 | 214 | ry1[l++] = x; 215 | x = x + 3.1416 + (0.197 + 0.0643 * n_r8 - 0.000286 * n_r8_pow_2) / l; 216 | 217 | if (nt <= l) break; 218 | } 219 | 220 | return {rj0, rj1, ry0, ry1}; 221 | } 222 | 223 | } // namespace Uhhyou 224 | -------------------------------------------------------------------------------- /experimental/TwoBandStereo/dsp/crossover.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace Uhhyou { 12 | 13 | template class FixedIntDelay { 14 | private: 15 | int ptr = 0; 16 | std::array buf{}; 17 | 18 | public: 19 | void reset(Sample value = 0) 20 | { 21 | ptr = 0; 22 | buf.fill(value); 23 | } 24 | 25 | Sample process(Sample x0) 26 | { 27 | if (++ptr >= length) ptr = 0; 28 | Sample output = buf[ptr]; 29 | buf[ptr] = x0; 30 | return output; 31 | } 32 | }; 33 | 34 | template struct ComplexIIRDelay; 35 | 36 | template struct ComplexIIRDelay { 37 | void reset() {} 38 | 39 | using C = std::complex; 40 | inline C process1PoleForward(C x0, const std::array &) { return x0; } 41 | inline C process1PoleReversed(C x0, const std::array &) { return x0; } 42 | }; 43 | 44 | template struct ComplexIIRDelay { 45 | static constexpr size_t index = fullStage - recStage; 46 | FixedIntDelay, size_t(1) << index> delay; 47 | ComplexIIRDelay recursion; 48 | 49 | void reset() 50 | { 51 | delay.reset(); 52 | recursion.reset(); 53 | } 54 | 55 | inline std::complex process1PoleForward( 56 | std::complex x0, const std::array, fullStage> &poles) 57 | { 58 | return recursion.process1PoleForward(x0 + poles[index] * delay.process(x0), poles); 59 | } 60 | 61 | inline std::complex process1PoleReversed( 62 | std::complex x0, const std::array, fullStage> &poles) 63 | { 64 | return recursion.process1PoleReversed(poles[index] * x0 + delay.process(x0), poles); 65 | } 66 | }; 67 | 68 | template class ComplexIIR { 69 | private: 70 | static constexpr size_t nPoles = stage - 1; 71 | 72 | Sample a_per_b = 0; 73 | std::array, stage> poles; 74 | 75 | std::complex x1 = 0; 76 | ComplexIIRDelay delay; 77 | 78 | public: 79 | void reset() 80 | { 81 | x1 = 0; 82 | delay.reset(); 83 | } 84 | 85 | void prepare(std::complex pole) 86 | { 87 | a_per_b = pole.real() / pole.imag(); 88 | for (auto &value : poles) { 89 | value = pole; 90 | pole *= pole; 91 | } 92 | } 93 | 94 | std::complex process1PoleForward(Sample x0) 95 | { 96 | std::complex sig = x0 + poles[0] * x1; 97 | x1 = x0; 98 | return delay.process1PoleForward(sig, poles); 99 | } 100 | 101 | std::complex process1PoleReversed(Sample x0) 102 | { 103 | std::complex sig = poles[0] * x0 + x1; 104 | x1 = x0; 105 | return delay.process1PoleReversed(sig, poles); 106 | } 107 | 108 | Sample process2PoleForward(Sample x0) 109 | { 110 | std::complex sig = process1PoleForward(x0); 111 | return sig.real() + a_per_b * sig.imag(); 112 | } 113 | 114 | Sample process2PoleReversed(Sample x0) 115 | { 116 | std::complex sig = process1PoleReversed(x0); 117 | return sig.real() + a_per_b * sig.imag(); 118 | } 119 | }; 120 | 121 | template class LinkwitzRileyFIR { 122 | private: 123 | static constexpr size_t nSection = order / 4; 124 | 125 | std::array, nSection> reverse; 126 | std::array, nSection> forward; 127 | 128 | std::array u1{}; 129 | std::array u2{}; 130 | std::array v1{}; 131 | std::array v2{}; 132 | 133 | Sample gain = Sample(1); 134 | 135 | public: 136 | static constexpr size_t latency = nSection * (size_t(1) << stage) + 1; 137 | 138 | LinkwitzRileyFIR() { static_assert(order % 4 == 0 && order >= 4); } 139 | 140 | void reset() 141 | { 142 | for (auto &x : reverse) x.reset(); 143 | for (auto &x : forward) x.reset(); 144 | 145 | u1.fill({}); 146 | u2.fill({}); 147 | v1.fill({}); 148 | v2.fill({}); 149 | } 150 | 151 | void prepare(Sample normalizedCrossover) 152 | { 153 | constexpr Sample pi = std::numbers::pi_v; 154 | constexpr size_t N = 2 * nSection; // Butterworth order. 155 | 156 | gain = Sample(1); 157 | 158 | auto cutoffRadian = Sample(2) * pi * normalizedCrossover; 159 | for (size_t idx = 0; idx < nSection; ++idx) { 160 | auto m = Sample(2 * idx) - Sample(N) + Sample(1); 161 | auto analogPole = cutoffRadian * std::polar(Sample(-1), pi * m / Sample(2 * N)); 162 | auto pole = (Sample(2) + analogPole) / (Sample(2) - analogPole); 163 | reverse[idx].prepare(pole); 164 | forward[idx].prepare(pole); 165 | gain *= (Sample(1) + Sample(-2) * pole.real() + std::norm(pole)) / Sample(4); 166 | } 167 | 168 | gain = std::pow(gain, Sample(1) / Sample(nSection)); 169 | } 170 | 171 | Sample process(Sample x0) 172 | { 173 | constexpr Sample a1 = Sample(2); // -2 for highpass. 174 | 175 | for (size_t i = 0; i < nSection; ++i) { 176 | Sample u0 = reverse[i].process2PoleReversed(x0 * gain); 177 | x0 = u0 + a1 * u1[i] + u2[i]; 178 | u2[i] = u1[i]; 179 | u1[i] = u0; 180 | 181 | Sample v0 = forward[i].process2PoleForward(x0 * gain); 182 | x0 = v0 + a1 * v1[i] + v2[i]; 183 | v2[i] = v1[i]; 184 | v1[i] = v0; 185 | } 186 | return x0; 187 | } 188 | }; 189 | 190 | template class LinkwitzRileyFIR2Band4n { 191 | public: 192 | static constexpr size_t latency = LinkwitzRileyFIR::latency; 193 | 194 | private: 195 | LinkwitzRileyFIR lowpass; 196 | FixedIntDelay highpassDelay; 197 | 198 | public: 199 | std::array output{}; // 0: low, 1: high. 200 | 201 | LinkwitzRileyFIR2Band4n() { static_assert(order % 4 == 0 && order >= 4); } 202 | 203 | size_t getLatency() { return latency; } 204 | 205 | void reset() 206 | { 207 | lowpass.reset(); 208 | highpassDelay.reset(); 209 | } 210 | 211 | void prepare(Sample normalizedCrossover) { lowpass.prepare(normalizedCrossover); } 212 | 213 | void process(Sample x0) 214 | { 215 | output[0] = lowpass.process(x0); 216 | output[1] = highpassDelay.process(x0) - output[0]; 217 | } 218 | }; 219 | 220 | } // namespace Uhhyou 221 | -------------------------------------------------------------------------------- /lib/Uhhyou/gui/widgets.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include "barbox.hpp" 14 | #include "button.hpp" 15 | #include "buttonarray.hpp" 16 | #include "combobox.hpp" 17 | #include "knob.hpp" 18 | #include "popupview.hpp" 19 | #include "presetmanager.hpp" 20 | #include "style.hpp" 21 | #include "tabview.hpp" 22 | 23 | namespace Uhhyou { 24 | 25 | class TestTile : public juce::Component { 26 | private: 27 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TestTile) 28 | 29 | juce::Colour color; 30 | juce::uint8 alpha = 0xff; 31 | 32 | public: 33 | TestTile(juce::Colour color, juce::uint8 alpha) : color(color), alpha(alpha) {} 34 | 35 | virtual void paint(juce::Graphics &ctx) override 36 | { 37 | ctx.setColour(color); 38 | ctx.fillAll(); 39 | } 40 | }; 41 | 42 | struct Line { 43 | juce::Point start{}; 44 | juce::Point end{}; 45 | float thickness{}; 46 | 47 | Line(juce::Point start, juce::Point end, float thickness) 48 | : start(start), end(end), thickness(thickness) 49 | { 50 | } 51 | 52 | void paint(juce::Graphics &ctx) const 53 | { 54 | ctx.drawLine(start.x, start.y, end.x, end.y, thickness); 55 | } 56 | }; 57 | 58 | struct TextLabel { 59 | juce::String text; 60 | juce::Rectangle rect; 61 | juce::Justification justification; 62 | 63 | TextLabel( 64 | const juce::String &text, 65 | const juce::Rectangle &rect, 66 | juce::Justification justification = juce::Justification::centred) 67 | : text(text), rect(rect), justification(justification) 68 | { 69 | } 70 | 71 | void paint(juce::Graphics &ctx) const { ctx.drawText(text, rect, justification); } 72 | }; 73 | 74 | struct GroupLabel { 75 | juce::String text; 76 | juce::Rectangle rect; 77 | juce::Justification justification = juce::Justification::centred; 78 | 79 | GroupLabel(const juce::String &text) : text(text), rect({0, 0, 0, 0}) {} 80 | 81 | GroupLabel( 82 | const juce::String &text, 83 | const juce::Rectangle &rect, 84 | juce::Justification justification = juce::Justification::centred) 85 | : text(text), rect(rect), justification(justification) 86 | { 87 | } 88 | 89 | void 90 | paint(juce::Graphics &ctx, juce::Font &font, float lineWidth, float marginWidth) const 91 | { 92 | ctx.drawText(text, rect, justification); 93 | 94 | auto textWidth = juce::GlyphArrangement::getStringWidth(font, text); 95 | 96 | auto lineY = rect.getY() + float(0.5) * (rect.getHeight() - lineWidth); 97 | auto centerX = float(0.5) * rect.getWidth(); 98 | auto offsetFronCenter = marginWidth + float(0.5) * textWidth; 99 | auto rectWidth = centerX - offsetFronCenter; 100 | ctx.fillRoundedRectangle( 101 | float(rect.getX()), lineY, rectWidth, lineWidth, lineWidth / 2); 102 | ctx.fillRoundedRectangle( 103 | rect.getX() + centerX + offsetFronCenter, lineY, rectWidth, lineWidth, 104 | lineWidth / 2); 105 | } 106 | }; 107 | 108 | struct LabeledWidget { 109 | uint64_t option; 110 | const juce::String &label; 111 | juce::Component &widget; 112 | 113 | enum Layout : decltype(option) { showLabel = 1, expand = 2 }; 114 | 115 | LabeledWidget( 116 | const juce::String &label, 117 | juce::Component &widget, 118 | uint64_t option = Layout::showLabel) 119 | : label(label), widget(widget), option(option) 120 | { 121 | } 122 | }; 123 | 124 | // Return value is the `top` of next section. 125 | inline int layoutVerticalSection( 126 | std::vector &labels, 127 | std::vector &groupLabels, 128 | int left, 129 | int top, 130 | int sectionWidth, 131 | int labelWidth, 132 | int widgetWidth, 133 | int labelXIncrement, 134 | int labelHeight, 135 | int labelYIncrement, 136 | const juce::String &groupTitle, 137 | std::vector data) 138 | { 139 | using Rect = juce::Rectangle; 140 | using Opt = LabeledWidget::Layout; 141 | 142 | if (groupTitle.isNotEmpty()) { 143 | groupLabels.emplace_back(groupTitle, Rect{left, top, sectionWidth, labelHeight}); 144 | top += labelYIncrement; 145 | } 146 | 147 | const int left1 = left + labelXIncrement; 148 | for (auto &line : data) { 149 | if (line.option & (Opt::showLabel + Opt::expand)) { 150 | labels.emplace_back(line.label, Rect{left, top, labelWidth, labelHeight}); 151 | } 152 | 153 | if (line.option & Opt::expand) { 154 | line.widget.setBounds(Rect{left, top, sectionWidth, labelHeight}); 155 | } else { 156 | line.widget.setBounds(Rect{left1, top, widgetWidth, labelHeight}); 157 | } 158 | 159 | top += labelYIncrement; 160 | } 161 | 162 | return top; 163 | } 164 | 165 | inline int layoutActionSection( 166 | std::vector &groupLabels, 167 | int left, 168 | int top, 169 | int sectionWidth, 170 | int labelWidth, 171 | int widgetWidth, 172 | int labelXIncrement, 173 | int labelHeight, 174 | int labelYIncrement, 175 | juce::Component &undoButton, 176 | juce::Component &redoButton, 177 | juce::Component &randomizeButton, 178 | juce::Component &presetManager) 179 | { 180 | using Rect = juce::Rectangle; 181 | 182 | groupLabels.emplace_back("Action", Rect{left, top, sectionWidth, labelHeight}); 183 | 184 | top += labelYIncrement; 185 | undoButton.setBounds(Rect{left, top, labelWidth, labelHeight}); 186 | redoButton.setBounds(Rect{left + labelXIncrement, top, widgetWidth, labelHeight}); 187 | 188 | top += labelYIncrement; 189 | randomizeButton.setBounds(Rect{left, top, sectionWidth, labelHeight}); 190 | 191 | top += labelYIncrement; 192 | groupLabels.emplace_back("Preset", Rect{left, top, sectionWidth, labelHeight}); 193 | 194 | top += labelYIncrement; 195 | presetManager.setBounds(Rect{left, top, sectionWidth, labelHeight}); 196 | 197 | return top + labelYIncrement; 198 | } 199 | 200 | inline void setDefaultColor(juce::LookAndFeel_V4 &laf, Palette &pal) 201 | { 202 | laf.setColour(juce::CaretComponent::caretColourId, pal.foreground()); 203 | 204 | laf.setColour(juce::TextEditor::backgroundColourId, pal.background()); 205 | laf.setColour(juce::TextEditor::textColourId, pal.foreground()); 206 | laf.setColour(juce::TextEditor::highlightColourId, pal.overlayHighlight()); 207 | laf.setColour(juce::TextEditor::highlightedTextColourId, pal.foreground()); 208 | laf.setColour(juce::TextEditor::outlineColourId, pal.border()); 209 | laf.setColour(juce::TextEditor::focusedOutlineColourId, pal.highlightMain()); 210 | 211 | juce::LookAndFeel::setDefaultLookAndFeel(&laf); 212 | } 213 | 214 | } // namespace Uhhyou 215 | -------------------------------------------------------------------------------- /experimental/AmplitudeModulator/PluginEditor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #include "PluginEditor.h" 5 | #include "PluginProcessor.h" 6 | 7 | #include "./gui/popupinformationtext.hpp" 8 | #include "Uhhyou/librarylicense.hpp" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #define HEAD (*this), (palette), &(processor.undoManager) 15 | #define PARAMETER(id) processor.param.tree.getParameter(id) 16 | #define SCALE(name) processor.param.scale.name 17 | #define VALUE(name) processor.param.value.name 18 | #define TREE() processor.param.tree 19 | 20 | // Parameter related arguments. 21 | #define ACTION_BUTTON (*this), palette, statusBar, numberEditor 22 | #define PRM(id, scale) HEAD, PARAMETER(id), SCALE(scale), statusBar, numberEditor 23 | 24 | namespace Uhhyou { 25 | 26 | constexpr int defaultWidth = 2 * 210 + 2 * 20; 27 | constexpr int defaultHeight = 10 * 30; 28 | 29 | inline juce::File getPresetDirectory(const juce::AudioProcessor &processor) 30 | { 31 | auto appDir = juce::File::getSpecialLocation(juce::File::userApplicationDataDirectory) 32 | .getFullPathName(); 33 | auto sep = juce::File::getSeparatorString(); 34 | 35 | juce::File presetDir(appDir + sep + "Uhhyou" + sep + processor.getName()); 36 | if (!(presetDir.exists() && presetDir.isDirectory())) presetDir.createDirectory(); 37 | return presetDir; 38 | } 39 | 40 | template 41 | inline auto constructParamArray( 42 | juce::AudioProcessorValueTreeState &tree, std::string baseName, size_t indexOffset = 0) 43 | { 44 | std::array params; 45 | for (size_t idx = 0; idx < nParameter; ++idx) { 46 | params[idx] = tree.getParameter(baseName + std::to_string(idx + indexOffset)); 47 | } 48 | return params; 49 | } 50 | 51 | Editor::Editor(Processor &processor) 52 | : AudioProcessorEditor(processor) 53 | , processor(processor) 54 | , statusBar(*this, palette) 55 | , numberEditor(palette) 56 | 57 | , pluginNameButton( 58 | *this, palette, processor.getName(), informationText, libraryLicenseText) 59 | , undoButton( 60 | ACTION_BUTTON, 61 | "Undo", 62 | [&]() { 63 | if (processor.undoManager.canUndo()) processor.undoManager.undo(); 64 | }) 65 | , redoButton( 66 | ACTION_BUTTON, 67 | "Redo", 68 | [&]() { 69 | if (processor.undoManager.canRedo()) processor.undoManager.redo(); 70 | }) 71 | , randomizeButton( 72 | ACTION_BUTTON, 73 | "Randomize", 74 | [&]() { 75 | std::uniform_real_distribution dist{0.0f, 1.0f}; 76 | std::random_device dev; 77 | std::mt19937 rng(dev()); 78 | 79 | auto params = processor.getParameters(); 80 | for (auto &prm : params) { 81 | prm->beginChangeGesture(); 82 | prm->setValueNotifyingHost(dist(rng)); 83 | prm->endChangeGesture(); 84 | } 85 | }) 86 | , presetManager((*this), (palette), &(processor.undoManager), processor.param.tree) 87 | 88 | , amType( 89 | PRM("amType", amType), 90 | { 91 | "Double Side-band (DSB)", 92 | "Upper Side-band (USB)", 93 | "Lower Side-band (LSB)", 94 | "DSB Upper AA", 95 | "DSB Full AA", 96 | "USB AA", 97 | "LSB AA", 98 | "- Reserved -", 99 | "- Reserved -", 100 | "- Reserved -", 101 | "- Reserved -", 102 | "- Reserved -", 103 | "- Reserved -", 104 | "- Reserved -", 105 | "- Reserved -", 106 | "- Reserved -", 107 | "- Reserved -", 108 | "- Reserved -", 109 | "- Reserved -", 110 | "- Reserved -", 111 | "- Reserved -", 112 | "- Reserved -", 113 | "- Reserved -", 114 | "- Reserved -", 115 | "- Reserved -", 116 | "- Reserved -", 117 | "- Reserved -", 118 | "- Reserved -", 119 | "- Reserved -", 120 | "- Reserved -", 121 | "- Reserved -", 122 | "- Reserved -", 123 | }) 124 | , swapCarriorAndModulator(PRM("swapCarriorAndModulator", boolean), "Swap Input") 125 | , carriorSideBandMix(PRM("carriorSideBandMix", unipolar), 5) 126 | , outputGain(PRM("outputGain", gain), 5) 127 | { 128 | setDefaultColor(lookAndFeel, palette); 129 | 130 | setResizable(true, false); 131 | getConstrainer()->setFixedAspectRatio(double(defaultWidth) / double(defaultHeight)); 132 | const float scale = getStateTree().getProperty("scale", 1.0f); 133 | setSize(int(scale * defaultWidth), int(scale * defaultHeight)); 134 | } 135 | 136 | Editor::~Editor() {} 137 | 138 | void Editor::paint(juce::Graphics &ctx) 139 | { 140 | ctx.setColour(palette.background()); 141 | ctx.fillAll(); 142 | 143 | ctx.setColour(palette.foreground()); 144 | for (const auto &x : lines) x.paint(ctx); 145 | 146 | ctx.setFont(palette.getFont(palette.textSizeUi())); 147 | for (const auto &x : labels) x.paint(ctx); 148 | 149 | auto groupLabelFont = palette.getFont(palette.textSizeUi()); 150 | auto groupLabelMarginWidth 151 | = juce::GlyphArrangement::getStringWidth(groupLabelFont, "W"); 152 | for (const auto &x : groupLabels) { 153 | x.paint(ctx, groupLabelFont, 2 * palette.borderThin(), groupLabelMarginWidth); 154 | } 155 | } 156 | 157 | void Editor::resized() 158 | { 159 | using Rect = juce::Rectangle; 160 | 161 | const float scale = getDesktopScaleFactor() * getHeight() / float(defaultHeight); 162 | getStateTree().setProperty("scale", scale, nullptr); 163 | palette.resize(scale); 164 | 165 | lines.clear(); 166 | labels.clear(); 167 | groupLabels.clear(); 168 | 169 | const int margin = int(5 * scale); 170 | const int labelHeight = int(20 * scale); 171 | const int labelWidth = int(100 * scale); 172 | const int bottom = int(scale * defaultHeight); 173 | 174 | const int uiMargin = 4 * margin; 175 | const int labelX = labelWidth + 2 * margin; 176 | const int labelY = labelHeight + 2 * margin; 177 | const int sectionWidth = 2 * labelWidth + 2 * margin; 178 | 179 | const int top0 = uiMargin; 180 | const int left0 = uiMargin; 181 | const int left1 = left0 + 2 * labelX; 182 | 183 | layoutVerticalSection( 184 | labels, groupLabels, left0, top0, sectionWidth, labelWidth, labelWidth, labelX, 185 | labelHeight, labelY, "Amplitude Modulator", 186 | { 187 | {"Type", amType}, 188 | {"Side-band Mix", carriorSideBandMix}, 189 | {"Output [dB]", outputGain}, 190 | {"", swapCarriorAndModulator, LabeledWidget::expand}, 191 | }); 192 | 193 | const int nameTop0 = layoutActionSection( 194 | groupLabels, left1, top0, sectionWidth, labelWidth, labelWidth, labelX, labelHeight, 195 | labelY, undoButton, redoButton, randomizeButton, presetManager); 196 | 197 | statusBar.setBounds( 198 | Rect{left0, bottom - labelHeight - uiMargin, 2 * sectionWidth, labelHeight}); 199 | 200 | pluginNameButton.setBounds(Rect{left1, nameTop0, sectionWidth, labelHeight}); 201 | pluginNameButton.scale(scale); 202 | } 203 | 204 | } // namespace Uhhyou 205 | -------------------------------------------------------------------------------- /lib/nlohmann/json_fwd.hpp: -------------------------------------------------------------------------------- 1 | // __ _____ _____ _____ 2 | // __| | __| | | | JSON for Modern C++ 3 | // | | |__ | | | | | | version 3.11.2 4 | // |_____|_____|_____|_|___| https://github.com/nlohmann/json 5 | // 6 | // SPDX-FileCopyrightText: 2013-2022 Niels Lohmann 7 | // SPDX-License-Identifier: MIT 8 | 9 | #ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ 10 | #define INCLUDE_NLOHMANN_JSON_FWD_HPP_ 11 | 12 | #include // int64_t, uint64_t 13 | #include // map 14 | #include // allocator 15 | #include // string 16 | #include // vector 17 | 18 | // #include 19 | // __ _____ _____ _____ 20 | // __| | __| | | | JSON for Modern C++ 21 | // | | |__ | | | | | | version 3.11.2 22 | // |_____|_____|_____|_|___| https://github.com/nlohmann/json 23 | // 24 | // SPDX-FileCopyrightText: 2013-2022 Niels Lohmann 25 | // SPDX-License-Identifier: MIT 26 | 27 | 28 | 29 | // This file contains all macro definitions affecting or depending on the ABI 30 | 31 | #ifndef JSON_SKIP_LIBRARY_VERSION_CHECK 32 | #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH) 33 | #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 2 34 | #warning "Already included a different version of the library!" 35 | #endif 36 | #endif 37 | #endif 38 | 39 | #define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum) 40 | #define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum) 41 | #define NLOHMANN_JSON_VERSION_PATCH 2 // NOLINT(modernize-macro-to-enum) 42 | 43 | #ifndef JSON_DIAGNOSTICS 44 | #define JSON_DIAGNOSTICS 0 45 | #endif 46 | 47 | #ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 48 | #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 49 | #endif 50 | 51 | #if JSON_DIAGNOSTICS 52 | #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag 53 | #else 54 | #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS 55 | #endif 56 | 57 | #if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 58 | #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp 59 | #else 60 | #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON 61 | #endif 62 | 63 | #ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION 64 | #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 65 | #endif 66 | 67 | // Construct the namespace ABI tags component 68 | #define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b 69 | #define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \ 70 | NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) 71 | 72 | #define NLOHMANN_JSON_ABI_TAGS \ 73 | NLOHMANN_JSON_ABI_TAGS_CONCAT( \ 74 | NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \ 75 | NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON) 76 | 77 | // Construct the namespace version component 78 | #define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ 79 | _v ## major ## _ ## minor ## _ ## patch 80 | #define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \ 81 | NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) 82 | 83 | #if NLOHMANN_JSON_NAMESPACE_NO_VERSION 84 | #define NLOHMANN_JSON_NAMESPACE_VERSION 85 | #else 86 | #define NLOHMANN_JSON_NAMESPACE_VERSION \ 87 | NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \ 88 | NLOHMANN_JSON_VERSION_MINOR, \ 89 | NLOHMANN_JSON_VERSION_PATCH) 90 | #endif 91 | 92 | // Combine namespace components 93 | #define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b 94 | #define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \ 95 | NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) 96 | 97 | #ifndef NLOHMANN_JSON_NAMESPACE 98 | #define NLOHMANN_JSON_NAMESPACE \ 99 | nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \ 100 | NLOHMANN_JSON_ABI_TAGS, \ 101 | NLOHMANN_JSON_NAMESPACE_VERSION) 102 | #endif 103 | 104 | #ifndef NLOHMANN_JSON_NAMESPACE_BEGIN 105 | #define NLOHMANN_JSON_NAMESPACE_BEGIN \ 106 | namespace nlohmann \ 107 | { \ 108 | inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \ 109 | NLOHMANN_JSON_ABI_TAGS, \ 110 | NLOHMANN_JSON_NAMESPACE_VERSION) \ 111 | { 112 | #endif 113 | 114 | #ifndef NLOHMANN_JSON_NAMESPACE_END 115 | #define NLOHMANN_JSON_NAMESPACE_END \ 116 | } /* namespace (inline namespace) NOLINT(readability/namespace) */ \ 117 | } // namespace nlohmann 118 | #endif 119 | 120 | 121 | /*! 122 | @brief namespace for Niels Lohmann 123 | @see https://github.com/nlohmann 124 | @since version 1.0.0 125 | */ 126 | NLOHMANN_JSON_NAMESPACE_BEGIN 127 | 128 | /*! 129 | @brief default JSONSerializer template argument 130 | 131 | This serializer ignores the template arguments and uses ADL 132 | ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) 133 | for serialization. 134 | */ 135 | template 136 | struct adl_serializer; 137 | 138 | /// a class to store JSON values 139 | /// @sa https://json.nlohmann.me/api/basic_json/ 140 | template class ObjectType = 141 | std::map, 142 | template class ArrayType = std::vector, 143 | class StringType = std::string, class BooleanType = bool, 144 | class NumberIntegerType = std::int64_t, 145 | class NumberUnsignedType = std::uint64_t, 146 | class NumberFloatType = double, 147 | template class AllocatorType = std::allocator, 148 | template class JSONSerializer = 149 | adl_serializer, 150 | class BinaryType = std::vector, // cppcheck-suppress syntaxError 151 | class CustomBaseClass = void> 152 | class basic_json; 153 | 154 | /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document 155 | /// @sa https://json.nlohmann.me/api/json_pointer/ 156 | template 157 | class json_pointer; 158 | 159 | /*! 160 | @brief default specialization 161 | @sa https://json.nlohmann.me/api/json/ 162 | */ 163 | using json = basic_json<>; 164 | 165 | /// @brief a minimal map-like container that preserves insertion order 166 | /// @sa https://json.nlohmann.me/api/ordered_map/ 167 | template 168 | struct ordered_map; 169 | 170 | /// @brief specialization that maintains the insertion order of object keys 171 | /// @sa https://json.nlohmann.me/api/ordered_json/ 172 | using ordered_json = basic_json; 173 | 174 | NLOHMANN_JSON_NAMESPACE_END 175 | 176 | #endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ 177 | -------------------------------------------------------------------------------- /lib/Uhhyou/gui/tabview.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "style.hpp" 11 | 12 | #include 13 | #include 14 | 15 | namespace Uhhyou { 16 | 17 | class TabView : public juce::Component { 18 | private: 19 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TabView) 20 | 21 | protected: 22 | struct Tab { 23 | bool isMouseEntered = false; 24 | 25 | juce::Rectangle rect{}; 26 | juce::String label{}; 27 | 28 | std::vector components; 29 | std::function paintCallback = nullptr; 30 | 31 | Tab(const juce::String &label) : label(label) {} 32 | 33 | void paint(juce::Graphics &ctx) 34 | { 35 | if (paintCallback != nullptr) paintCallback(ctx); 36 | } 37 | }; 38 | 39 | Palette &pal; 40 | 41 | size_t activeTabIndex = 0; 42 | std::vector tabs; 43 | float tabHeight = float(20); 44 | 45 | bool isMouseEntered = false; 46 | juce::Font font; 47 | 48 | public: 49 | TabView(Palette &palette, std::vector tabNames) 50 | : pal(palette), font(juce::FontOptions{}) 51 | { 52 | tabs.reserve(tabNames.size()); 53 | for (size_t idx = 0; idx < tabNames.size(); ++idx) { 54 | tabs.push_back(Tab(tabNames[idx])); 55 | } 56 | } 57 | 58 | virtual ~TabView() override {} 59 | 60 | void addWidget(size_t tabIndex, juce::Component *component) 61 | { 62 | if (component == nullptr || tabIndex >= tabs.size()) return; 63 | addChildComponent(component); 64 | tabs[tabIndex].components.push_back(component); 65 | } 66 | 67 | void refreshTab() 68 | { 69 | for (size_t idx = 0; idx < tabs.size(); ++idx) { 70 | bool isVisible = idx == activeTabIndex; 71 | for (auto &cmp : tabs[idx].components) cmp->setVisible(isVisible); 72 | } 73 | } 74 | 75 | virtual void resized() override 76 | { 77 | tabHeight = float(2) * pal.textSizeUi(); 78 | font = pal.getFont(pal.textSizeUi()); 79 | 80 | if (tabs.size() <= 0) return; 81 | const float tabWidth = float(getWidth()) / float(tabs.size()); 82 | for (size_t idx = 0; idx < tabs.size(); ++idx) { 83 | tabs[idx].rect.setBounds(float(idx) * tabWidth, float(0), tabWidth, tabHeight); 84 | } 85 | } 86 | 87 | juce::Rectangle getInnerBounds() 88 | { 89 | int tabHeightI = int(tabHeight); 90 | return juce::Rectangle{ 91 | tabHeightI, 2 * tabHeightI, getWidth() - 2 * tabHeightI, 92 | getHeight() - 3 * tabHeightI}; 93 | } 94 | 95 | virtual void paint(juce::Graphics &ctx) override 96 | { 97 | const float lw1 = pal.borderThin(); // Border width. 98 | // const float lw2 = 2 * lw1; 99 | const float lwHalf = std::ceil(lw1 / 2); 100 | const float width = float(getWidth()); 101 | const float height = float(getHeight()); 102 | 103 | juce::PathStrokeType borderStroke{ 104 | lw1, juce::PathStrokeType::JointStyle::curved, 105 | juce::PathStrokeType::EndCapStyle::rounded}; 106 | 107 | // Inactive tab. 108 | ctx.setFont(font); 109 | for (size_t idx = 0; idx < tabs.size(); ++idx) { 110 | if (idx == activeTabIndex) continue; 111 | const auto &tab = tabs[idx]; 112 | 113 | ctx.setColour(pal.boxBackground()); 114 | ctx.fillRect(tab.rect); 115 | if (tab.isMouseEntered) { 116 | ctx.setColour(pal.overlayHighlight()); 117 | ctx.fillRect(tab.rect); 118 | } 119 | 120 | juce::Path path; 121 | const float tabLeft 122 | = idx == 0 ? tab.rect.getX() + lwHalf : tab.rect.getX() - lwHalf; 123 | const float tabRight = idx >= tabs.size() - 1 ? tab.rect.getRight() - lwHalf 124 | : tab.rect.getRight() + lwHalf; 125 | const float tabTop = tab.rect.getY() + lwHalf; 126 | path.startNewSubPath(tabLeft, tabTop); 127 | path.lineTo(tabRight, tabTop); 128 | path.lineTo(tabRight, tab.rect.getBottom()); 129 | path.lineTo(tabLeft, tab.rect.getBottom()); 130 | path.closeSubPath(); 131 | 132 | ctx.setColour(pal.border()); 133 | ctx.strokePath(path, borderStroke); 134 | 135 | ctx.setColour(pal.foregroundInactive()); 136 | ctx.drawText(tab.label, tab.rect, juce::Justification::centred); 137 | } 138 | 139 | // Active tab. 140 | const auto &activeTab = tabs[activeTabIndex]; 141 | juce::Path path; 142 | const float borderedRight = width - lwHalf; 143 | const float borderedBottom = height - lwHalf; 144 | const float activeTabLeft = activeTab.rect.getX() + lwHalf; 145 | const float activeTabRight = activeTab.rect.getRight() - lwHalf; 146 | path.startNewSubPath(lwHalf, tabHeight); 147 | path.lineTo(activeTabLeft, tabHeight); 148 | path.lineTo(activeTabLeft, lwHalf); 149 | path.lineTo(activeTabRight, lwHalf); 150 | path.lineTo(activeTabRight, tabHeight); 151 | path.lineTo(borderedRight, tabHeight); 152 | path.lineTo(borderedRight, borderedBottom); 153 | path.lineTo(lwHalf, borderedBottom); 154 | path.closeSubPath(); 155 | 156 | ctx.setColour(pal.background()); 157 | ctx.fillPath(path); 158 | 159 | ctx.setColour(pal.foreground()); 160 | ctx.strokePath(path, borderStroke); 161 | ctx.drawText(activeTab.label, activeTab.rect, juce::Justification::centred); 162 | 163 | tabs[activeTabIndex].paint(ctx); 164 | } 165 | 166 | virtual void mouseDrag(const juce::MouseEvent &) override {} 167 | virtual void mouseUp(const juce::MouseEvent &) override {} 168 | virtual void mouseDoubleClick(const juce::MouseEvent &) override {} 169 | 170 | virtual void mouseEnter(const juce::MouseEvent &) override 171 | { 172 | isMouseEntered = true; 173 | repaint(); 174 | } 175 | 176 | virtual void mouseExit(const juce::MouseEvent &) override 177 | { 178 | isMouseEntered = false; 179 | repaint(); 180 | } 181 | 182 | virtual void mouseMove(const juce::MouseEvent &event) override 183 | { 184 | for (auto &tab : tabs) { 185 | tab.isMouseEntered = tab.rect.contains(event.position); 186 | } 187 | repaint(); 188 | } 189 | 190 | virtual void mouseDown(const juce::MouseEvent &event) override 191 | { 192 | for (size_t idx = 0; idx < tabs.size(); ++idx) { 193 | if (tabs[idx].rect.contains(event.position)) { 194 | activeTabIndex = idx; 195 | break; 196 | } 197 | } 198 | refreshTab(); 199 | repaint(); 200 | } 201 | 202 | virtual void mouseWheelMove( 203 | const juce::MouseEvent &event, const juce::MouseWheelDetails &wheel) override 204 | { 205 | if (wheel.deltaY == float(0) || event.position.getY() > float(tabHeight)) return; 206 | if (wheel.deltaY > float(0)) { 207 | activeTabIndex -= 1; 208 | if (activeTabIndex >= tabs.size()) activeTabIndex = tabs.size() - 1; 209 | } else { 210 | activeTabIndex += 1; 211 | if (activeTabIndex >= tabs.size()) activeTabIndex = 0; 212 | } 213 | refreshTab(); 214 | repaint(); 215 | } 216 | }; 217 | 218 | } // namespace Uhhyou 219 | -------------------------------------------------------------------------------- /lib/Uhhyou/gui/button.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Takamitsu Endo (ryukau@gmail.com). 2 | // SPDX-License-Identifier: AGPL-3.0-only 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "../scaledparameter.hpp" 11 | #include "./numbereditor.hpp" 12 | #include "style.hpp" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace Uhhyou { 20 | 21 | template