diff --git a/.github/workflows/ci-development.yml b/.github/workflows/ci-development.yml index 0afafbc..030a9ce 100644 --- a/.github/workflows/ci-development.yml +++ b/.github/workflows/ci-development.yml @@ -20,7 +20,7 @@ env: PROJECT_NAME: Disflux BUNDLE_NAME: disflux BUNDLE_ID: com.dimethoxy.disflux - VERSION: 1.1.1 # Disflux-Version + VERSION: 1.1.2 # Disflux-Version BUILD_DIR: build DISPLAY: :0 # Linux pluginval needs this HOMEBREW_NO_INSTALL_CLEANUP: 1 diff --git a/.github/workflows/ci-release.yml b/.github/workflows/ci-release.yml index 5139c07..767aa31 100644 --- a/.github/workflows/ci-release.yml +++ b/.github/workflows/ci-release.yml @@ -20,7 +20,7 @@ env: PROJECT_NAME: Disflux BUNDLE_NAME: disflux BUNDLE_ID: com.dimethoxy.disflux - VERSION: 1.1.1 # Disflux-Version + VERSION: 1.1.2 # Disflux-Version BUILD_DIR: build DISPLAY: :0 # Linux pluginval needs this HOMEBREW_NO_INSTALL_CLEANUP: 1 diff --git a/.github/workflows/ci-snapshot.yml b/.github/workflows/ci-snapshot.yml index 5ae5ec4..e5626a4 100644 --- a/.github/workflows/ci-snapshot.yml +++ b/.github/workflows/ci-snapshot.yml @@ -22,7 +22,7 @@ env: PROJECT_NAME: Disflux BUNDLE_NAME: disflux BUNDLE_ID: com.dimethoxy.disflux - VERSION: 1.1.1 # Disflux-Version + VERSION: 1.1.2 # Disflux-Version BUILD_DIR: build DISPLAY: :0 # Linux pluginval needs this HOMEBREW_NO_INSTALL_CLEANUP: 1 diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..4d49ce6 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,15 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/external/**", + "${workspaceFolder}/src/**" + ], + "defines": [], + "compilerPath": "/usr/bin/clang", + "intelliSenseMode": "linux-gcc-x64" + } + ], + "version": 4 +} diff --git a/.vscode/launch.json b/.vscode/launch.json index 591f534..5d13f74 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -16,7 +16,7 @@ "name": "Launch AudioPluginHost Linux", "type": "cppdbg", "request": "launch", - "program": "/data/development/juce/extras/AudioPluginHost/Builds/LinuxMakefile/build/AudioPluginHost", + "program": "/data/development/dimethoxy/juce/extras/AudioPluginHost/Builds/LinuxMakefile/build/AudioPluginHost", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", diff --git a/.vscode/settings.json b/.vscode/settings.json index c280e05..598b3a2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -89,5 +89,13 @@ "typeinfo": "cpp", "valarray": "cpp", "variant": "cpp" - } + }, + "C_Cpp.default.includePath": [ + "${workspaceFolder}/external/dmt", + "${workspaceFolder}/external/melatonin_perfetto", + "${workspaceFolder}/external/juce/extras/Projucer/JuceLibraryCode" + ], + "git.autoRepositoryDetection": true, + "git.detectSubmodules": true, + "git.openRepositoryInParentFolders": "always" } diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c3bf71..9a81882 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ #============================================================================== cmake_minimum_required(VERSION 3.30) -project(Disflux VERSION 1.1.1) # Disflux-Version +project(Disflux VERSION 1.1.2) # Disflux-Version set(CMAKE_CXX_STANDARD 23) set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS TRUE) set(EXT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/external) @@ -22,12 +22,6 @@ CPMAddPackage( GIT_TAG master SOURCE_DIR ${EXT_DIR}/juce ) -CPMAddPackage( - NAME DMT - GITHUB_REPOSITORY Dimethoxy/DMT - GIT_TAG v25.1.3 - SOURCE_DIR ${EXT_DIR}/dmt -) CPMAddPackage( NAME CLAP_JUCE GITHUB_REPOSITORY free-audio/clap-juce-extensions diff --git a/CMakePresets.json b/CMakePresets.json index cd66104..c73292d 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -122,6 +122,7 @@ "binaryDir": "${sourceDir}/build", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", "CMAKE_C_COMPILER": "/usr/bin/gcc", "CMAKE_CXX_COMPILER": "/usr/bin/g++" }, @@ -143,6 +144,7 @@ "binaryDir": "${sourceDir}/build", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release", + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", "CMAKE_C_COMPILER": "/usr/bin/gcc", "CMAKE_CXX_COMPILER": "/usr/bin/g++" }, @@ -164,6 +166,7 @@ "binaryDir": "${sourceDir}/build", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", "CMAKE_C_COMPILER": "/usr/bin/gcc", "CMAKE_CXX_COMPILER": "/usr/bin/g++", "CMAKE_CXX_FLAGS": "-DPERFETTO=1" @@ -186,6 +189,7 @@ "binaryDir": "${sourceDir}/build", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release", + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", "CMAKE_C_COMPILER": "/usr/bin/gcc", "CMAKE_CXX_COMPILER": "/usr/bin/g++", "CMAKE_CXX_FLAGS": "-DPERFETTO=1" diff --git a/Disflux.filtergraph b/Disflux.filtergraph new file mode 100644 index 0000000..edede74 --- /dev/null +++ b/Disflux.filtergraph @@ -0,0 +1,96 @@ + + + + + + 0. + + + + + + + + + + + + 0. + + + + + + + + + + + + 0. + + + + + + + + + + + + 0. + + + + + + + + + + + + 630.VMjLgzl....O+fWarAhckI2bo8la8HRLt.iHfTlai8FYo41Y8HRUTYTK3HxO9.BOVMEUy.Ea0cVZtMEcgQWY9vSRC8Vav8lak4Fc9LCN33BQqEiXrcmUiMSPt3RPh4BUAkTUP0TPP4hPpYTVtPjcCUDTToUdYYTXvb1UPMGNVMFcQcjKwTjQgASUF4RPp4RPt3hKt3hKt3hcqLjKPUjZTEDLD4RPHAkVpEDTtHEUtD0aM0VVxU0QjYTRWk0cUcUVz0jUj4BVWgkbUcUVtPDTBQjKt3hKt3hKtnFQP4hKUAkTEQ0TtPjYt7VTF4RP1AUPDsVLhw1cVM1LvPkVyDjYic1cVM1ZAAkKIAkKt3hKt3hKt3xMq3hKTETRUAUSAAkKBolQY4BQlMTQPQkV4kkQgAyYGQ0azDCVtEjYic1cVM1ZAAkKIAkKt3hKt3hKt3xMq3hKTETRUAUSAAkKBolQY4BQ1MTQPQkV4kkQgAyYxPkcIcUVmEkQtDSQFEFLUYjKAolKA4hKt3hKt3hKuEDQt.UQpQUPvPjKAgDTZoVPP4hSTYWTxgCaXc1crAEMAcEV40zQtDSQFEFLUYjKAolKA4hKt3hKt3hKt3hKt.UQpQUPvPjKAgDTZoVPP4BSTYWTxgCaXc1cwD0YqwVXtf0UXIWUWkkKDAkPD4hKt3hKt3hKt3hKt3hKt3hKt3hKt3hKJUELPUTPqI1aYcEV5UkQQcVTWgkKDAkKBs1QhcVSxHlKDAkKC4BTG4hKt3hKt3hKt3FUUMTUDQEdqw1XmE0UYQTQFM1YAwyKIMzasA2atUlaz4COuX0TTMCTrU2Yo41TzEFck4C. + + + + + + + + + + + + 0. + + + + + + + + + + diff --git a/external/dmt b/external/dmt deleted file mode 160000 index 523319d..0000000 --- a/external/dmt +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 523319d88f389578172a72a9f1b3752405f1fd1b diff --git a/external/tomlplusplus b/external/tomlplusplus deleted file mode 160000 index f3f6258..0000000 --- a/external/tomlplusplus +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f3f625819a724be76e634efccc9accfca350cc32 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 07266a8..92828bc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,7 +3,7 @@ #============================================================================== cmake_minimum_required(VERSION 3.22) -project(DisfluxPlugin VERSION 1.1.1) # Disflux-Version +project(DisfluxPlugin VERSION 1.1.2) # Disflux-Version # If we are on MacOS, we need to build for arm64 and x86_64 if (APPLE) @@ -41,7 +41,7 @@ juce_add_plugin(${PROJECT_NAME} PLUGIN_CODE DFLX LV2URI "https://dimethoxy.com/plugins/disflux" BUNDLE_ID com.dimethoxy.disflux - ) +) # Add the main source files target_sources(${PROJECT_NAME} @@ -53,7 +53,8 @@ target_sources(${PROJECT_NAME} # Add the external libraries target_include_directories(${PROJECT_NAME} PUBLIC - ${CMAKE_SOURCE_DIR}/external/dmt + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/src/dmt ${CMAKE_SOURCE_DIR}/external/melatonin_perfetto ${CMAKE_SOURCE_DIR}/external/tomlplusplus ) @@ -61,32 +62,32 @@ target_include_directories(${PROJECT_NAME} # Add fonts and icons as binary data so they can be used in the app juce_add_binary_data(FontBinaryData SOURCES - ${CMAKE_SOURCE_DIR}/external/dmt/fonts/poppins/Poppins-Thin.ttf - ${CMAKE_SOURCE_DIR}/external/dmt/fonts/poppins/Poppins-ExtraLight.ttf - ${CMAKE_SOURCE_DIR}/external/dmt/fonts/poppins/Poppins-Light.ttf - ${CMAKE_SOURCE_DIR}/external/dmt/fonts/poppins/Poppins-Regular.ttf - ${CMAKE_SOURCE_DIR}/external/dmt/fonts/poppins/Poppins-Medium.ttf - ${CMAKE_SOURCE_DIR}/external/dmt/fonts/poppins/Poppins-SemiBold.ttf - ${CMAKE_SOURCE_DIR}/external/dmt/fonts/poppins/Poppins-Bold.ttf - ${CMAKE_SOURCE_DIR}/external/dmt/fonts/poppins/Poppins-ExtraBold.ttf - ${CMAKE_SOURCE_DIR}/external/dmt/fonts/poppins/Poppins-Black.ttf - ${CMAKE_SOURCE_DIR}/external/dmt/fonts/sedgwick_ave_display/SedgwickAveDisplay-Regular.ttf - ${CMAKE_SOURCE_DIR}/external/dmt/icons/speed.svg - ${CMAKE_SOURCE_DIR}/external/dmt/icons/back.svg - ${CMAKE_SOURCE_DIR}/external/dmt/icons/height.svg - ${CMAKE_SOURCE_DIR}/external/dmt/icons/thickness.svg - ${CMAKE_SOURCE_DIR}/external/dmt/icons/gear.svg - ${CMAKE_SOURCE_DIR}/external/dmt/icons/angles_up.svg - ${CMAKE_SOURCE_DIR}/external/dmt/icons/bypass.svg - ${CMAKE_SOURCE_DIR}/external/dmt/icons/download.svg - ${CMAKE_SOURCE_DIR}/external/dmt/icons/presets.svg - ${CMAKE_SOURCE_DIR}/external/dmt/icons/close.svg - ${CMAKE_SOURCE_DIR}/external/dmt/icons/reload.svg - ${CMAKE_SOURCE_DIR}/external/dmt/icons/save.svg - ${CMAKE_SOURCE_DIR}/external/dmt/icons/error.svg - ${CMAKE_SOURCE_DIR}/external/dmt/icons/warning.svg - ${CMAKE_SOURCE_DIR}/external/dmt/icons/success.svg - ${CMAKE_SOURCE_DIR}/external/dmt/icons/info.svg + ${CMAKE_SOURCE_DIR}/src/dmt/fonts/poppins/Poppins-Thin.ttf + ${CMAKE_SOURCE_DIR}/src/dmt/fonts/poppins/Poppins-ExtraLight.ttf + ${CMAKE_SOURCE_DIR}/src/dmt/fonts/poppins/Poppins-Light.ttf + ${CMAKE_SOURCE_DIR}/src/dmt/fonts/poppins/Poppins-Regular.ttf + ${CMAKE_SOURCE_DIR}/src/dmt/fonts/poppins/Poppins-Medium.ttf + ${CMAKE_SOURCE_DIR}/src/dmt/fonts/poppins/Poppins-SemiBold.ttf + ${CMAKE_SOURCE_DIR}/src/dmt/fonts/poppins/Poppins-Bold.ttf + ${CMAKE_SOURCE_DIR}/src/dmt/fonts/poppins/Poppins-ExtraBold.ttf + ${CMAKE_SOURCE_DIR}/src/dmt/fonts/poppins/Poppins-Black.ttf + ${CMAKE_SOURCE_DIR}/src/dmt/fonts/sedgwick_ave_display/SedgwickAveDisplay-Regular.ttf + ${CMAKE_SOURCE_DIR}/src/dmt/icons/speed.svg + ${CMAKE_SOURCE_DIR}/src/dmt/icons/back.svg + ${CMAKE_SOURCE_DIR}/src/dmt/icons/height.svg + ${CMAKE_SOURCE_DIR}/src/dmt/icons/thickness.svg + ${CMAKE_SOURCE_DIR}/src/dmt/icons/gear.svg + ${CMAKE_SOURCE_DIR}/src/dmt/icons/angles_up.svg + ${CMAKE_SOURCE_DIR}/src/dmt/icons/bypass.svg + ${CMAKE_SOURCE_DIR}/src/dmt/icons/download.svg + ${CMAKE_SOURCE_DIR}/src/dmt/icons/presets.svg + ${CMAKE_SOURCE_DIR}/src/dmt/icons/close.svg + ${CMAKE_SOURCE_DIR}/src/dmt/icons/reload.svg + ${CMAKE_SOURCE_DIR}/src/dmt/icons/save.svg + ${CMAKE_SOURCE_DIR}/src/dmt/icons/error.svg + ${CMAKE_SOURCE_DIR}/src/dmt/icons/warning.svg + ${CMAKE_SOURCE_DIR}/src/dmt/icons/success.svg + ${CMAKE_SOURCE_DIR}/src/dmt/icons/info.svg ) # Link the libraries @@ -144,7 +145,7 @@ message("JUCE header file generated!") if(MSVC) target_compile_definitions(${PROJECT_NAME} PRIVATE - _SILENCE_CXX23_ALIGEND_STORAGE_DEPRECATION_WARNING + _SILENCE_CXX23_ALIGNED_STORAGE_DEPRECATION_WARNING ) endif() diff --git a/src/app/PluginEditor.cpp b/src/app/PluginEditor.cpp index 0b60151..07b1e36 100644 --- a/src/app/PluginEditor.cpp +++ b/src/app/PluginEditor.cpp @@ -5,11 +5,14 @@ PluginEditor::PluginEditor(PluginProcessor& p) : AudioProcessorEditor(&p) , p(p) - , sizeFactor(p.scaleFactor) // reference to processor's scaleFactor - , disfluxPanel(p.apvts, p.oscilloscopeBuffer) - , compositor("DisFlux", disfluxPanel, p.apvts, p.properties, sizeFactor) + , sizeFactor(p.scaleFactor) + , mainLayout({}, {}) + , compositor("DisFlux", mainLayout, p.apvts, p.properties, sizeFactor) , compositorAttached(true) { + mainLayout.addPanel>( + 0, 0, 1, 1, p.apvts, p.oscilloscopeBuffer); + if (OS_IS_WINDOWS) { setResizable(false, true); } diff --git a/src/app/PluginEditor.h b/src/app/PluginEditor.h index 9b30649..3063ccb 100644 --- a/src/app/PluginEditor.h +++ b/src/app/PluginEditor.h @@ -50,7 +50,7 @@ class PluginEditor Image image; bool isResizing = false; //============================================================================== - dmt::gui::panel::DisfluxPanel disfluxPanel; + dmt::gui::window::Layout mainLayout; dmt::gui::window::Compositor compositor; //============================================================================== juce::Image compositorSnapshot; diff --git a/src/app/PluginProcessor.cpp b/src/app/PluginProcessor.cpp index 6deca57..db64a27 100644 --- a/src/app/PluginProcessor.cpp +++ b/src/app/PluginProcessor.cpp @@ -16,8 +16,8 @@ PluginProcessor::PluginProcessor() , oscilloscopeBuffer(2, 4096) , disfluxProcessor(apvts, dmt::Settings::Audio::frequencySmoothness, - dmt::Settings::Audio::spreadSmoothness, dmt::Settings::Audio::pinchSmoothness, + dmt::Settings::Audio::spreadSmoothness, dmt::Settings::Audio::useOutputHighpass, dmt::Settings::Audio::outputHighpassFrequency, dmt::Settings::Audio::smoothingInterval) diff --git a/src/dmt/.gitignore b/src/dmt/.gitignore new file mode 100644 index 0000000..58bece4 --- /dev/null +++ b/src/dmt/.gitignore @@ -0,0 +1,3 @@ + + + diff --git a/src/dmt/CODE_OF_CONDUCT.md b/src/dmt/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..46dc146 --- /dev/null +++ b/src/dmt/CODE_OF_CONDUCT.md @@ -0,0 +1,133 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +This project is focused on technical collaboration and development. Discussions +or contributions related to politics, religion, or ideology are not appropriate +in this community and should be avoided. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or + advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email + address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting +- Introducing or engaging in discussions about politics, religion, or ideology + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/src/dmt/DmtHeader.h b/src/dmt/DmtHeader.h new file mode 100644 index 0000000..b3390cb --- /dev/null +++ b/src/dmt/DmtHeader.h @@ -0,0 +1,27 @@ +//============================================================================== +/** + * @file DmtHeader.h + * @brief Dimethoxy (DMT) Library - A comprehensive collection of internal + * components and utilities for audio plug-in development. + * + * @details This library is exclusively designed for our plugins and is not + * intended for external use. This file serves as a convenience header, + * including all the headers in the library. + * + * @copyright 2024 Dimethoxy Audio + * + * @author Lunix-420 + */ +//============================================================================== +#pragma once +//============================================================================== +#include "../melatonin_perfetto/melatonin_perfetto/melatonin_perfetto.h" +//============================================================================== +#include "./configuration/Configuration.h" +#include "./dsp/Dsp.h" +#include "./gui/Gui.h" +#include "./model/Model.h" +#include "./theme/Theme.h" +#include "./utility/Utility.h" +#include "./version/Version.h" +//============================================================================== \ No newline at end of file diff --git a/src/dmt/LICENSE.txt b/src/dmt/LICENSE.txt new file mode 100644 index 0000000..be3f7b2 --- /dev/null +++ b/src/dmt/LICENSE.txt @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/src/dmt/README.md b/src/dmt/README.md new file mode 100644 index 0000000..87743ea --- /dev/null +++ b/src/dmt/README.md @@ -0,0 +1,23 @@ +# Dimethoxy Library + +> [!WARNING] +> This library is under active development and primarily intended for use within Dimethoxy projects \ +> External use is unsupported. + +The Dimethoxy Library is the core engine behind all Dimethoxy plugins β€” a modular C++ codebase built for high-performance digital signal processing and plugin development. +From custom DSP algorithms to GUI systems and utility wrappers, this library powers nearly every aspect of our audio tools. + +## Features +- πŸš€ High-performance DSP tailored for aggressive electronic music (Hardstyle, Hardcore, Uptempo, etc.) +- πŸŽ›οΈ Modular components for efficient plugin development +- 🧱 Shared utilities, math, parameter handling, and UI helpers +- πŸ§ͺ Designed for maintainability and extensibility across multiple projects + +## Used In +- [Disflux](https://github.com/Dimethoxy/Disflux) β€” Transient Smearing Audio Plugin for Windows, MacOS and Linux +- [Oscilloscope](https://github.com/Dimethoxy/Oscilloscope) β€” Work in Progress Oscilloscope Audio Plugin for Windows, MacOS and Linux + + +## License +This project is licensed under AGPLv3. \ +Any project using **any** part of this library **must** also be licensed under AGPLv3 or a compatible open-source license. diff --git a/src/dmt/configuration/Configuration.h b/src/dmt/configuration/Configuration.h new file mode 100644 index 0000000..b33efed --- /dev/null +++ b/src/dmt/configuration/Configuration.h @@ -0,0 +1,36 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Configuration header to include all necessary configuration files. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "./Container.h" +#include "./Options.h" +#include "./Properties.h" + +//============================================================================== diff --git a/src/dmt/configuration/Container.h b/src/dmt/configuration/Container.h new file mode 100644 index 0000000..7da22e0 --- /dev/null +++ b/src/dmt/configuration/Container.h @@ -0,0 +1,272 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * This file is part of the Dimethoxy Library, a collection of essential + * classes used across various Dimethoxy projects. + * These files are primarily designed for internal use within our repositories. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Type-safe settings container for Dimethoxy Audio applications. + * Provides runtime-checked, variant-based storage for configuration values. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include +#include + +//============================================================================== + +namespace dmt { +namespace configuration { + +//============================================================================== +/** + * @brief A type-safe container for application settings. + * + * @details + * This class allows adding and retrieving settings with different types. + * It ensures that the types of the settings are consistent, and provides + * type-safe access to the values. All access is checked at runtime. + */ +class Container +{ + //============================================================================ + // Aliases for convenience + using String = juce::String; + using Colour = juce::Colour; + +public: + //============================================================================ + /** + * @brief Pseudo type for storing heterogeneous settings. + * + * @details + * The possible types are: String, Colour, int, float, and bool. + * Used for type-safe, runtime-checked configuration storage. + */ + typedef std::variant SettingValue; + + //============================================================================ + /** + * @brief Type for storing a range limit. + * + * @details + * Used for storing the range limits of settings. + * The range is inclusive. + */ + template + struct Range + { + T min; + T max; + + Range(T _min, T _max) + : min(_min) + , max(_max) + { + } + }; + + //============================================================================ + /** + * @brief Default constructor. + */ + Container() = default; + + //============================================================================ + /** + * @brief Retrieves a setting by its name with type safety. + * + * @tparam T The type of the setting to retrieve. + * @param _name The name of the setting. + * @return A mutable reference to the setting value of type T. + * + * @throws std::runtime_error If the setting is not found or the type does not + * match. + * + * @details + * Throws if the type does not match or the setting does not exist. + * Use for real-time safe, type-checked access to configuration. + */ + template + inline T& get(const String _name) + { + auto it = settings.find(_name); + if (it != settings.end()) { + if (std::holds_alternative(it->second)) { + return std::get(it->second); + } else { + jassertfalse; + throw std::runtime_error("Type mismatch for setting: " + + _name.toStdString()); + } + } else { + jassertfalse; + throw std::runtime_error("Setting not found: " + _name.toStdString()); + } + } + + //============================================================================== + /** + * @brief Adds a new setting or ensures consistency for an existing one. + * + * @tparam T The type of the setting to add. + * @param _name The name of the setting. + * @param _value The value to associate with the setting. + * @return A mutable reference to the setting value of type T. + * + * @throws std::runtime_error If there is a type mismatch when adding an + * existing setting. + * + * @details + * If the setting already exists, the type must match the stored type. + * If it doesn't match, an error is thrown. If the setting doesn't exist, + * it is added to the collection. + */ + template + inline T& add(const String _name, + const T _value, + const T* min = nullptr, + const T* max = nullptr) + { + auto it = settings.find(_name); + if (it != settings.end()) { + if (!std::holds_alternative(it->second)) { + jassertfalse; + throw std::runtime_error("Type mismatch for setting: " + + _name.toStdString()); + } + } else { + settings[_name] = _value; + if (min && max) { + ranges[_name] = Range(*min, *max); + } + } + return std::get(settings[_name]); + } + + //============================================================================== + /** + * @brief Converts all settings to a juce::PropertySet. + * + * @return A juce::PropertySet containing all settings as string values. + * + * @details + * Used for serialization or export of settings to JUCE property sets. + */ + inline juce::PropertySet toPropertySet() const + { + juce::PropertySet propertySet; + for (const auto& [key, value] : settings) { + if (std::holds_alternative(value)) { + propertySet.setValue(key, std::get(value)); + } else if (std::holds_alternative(value)) { + propertySet.setValue(key, std::get(value).toString()); + } else if (std::holds_alternative(value)) { + propertySet.setValue(key, std::get(value)); + } else if (std::holds_alternative(value)) { + propertySet.setValue(key, std::get(value)); + } else if (std::holds_alternative(value)) { + propertySet.setValue(key, std::get(value)); + } + } + return propertySet; + } + + //============================================================================== + /** + * @brief Applies a juce::PropertySet to the settings container. + * + * @param _propertySet Pointer to the property set to apply. + * + * @details + * Updates all settings from the property set, converting types as needed. + * Only updates settings that already exist in the container. + */ + inline void applyPropertySet(juce::PropertySet* _propertySet) + { + for (auto& [key, storedValue] : settings) { + if (_propertySet->containsKey(key)) { + if (std::holds_alternative(storedValue)) { + settings[key] = _propertySet->getValue(key); + } else if (std::holds_alternative(storedValue)) { + settings[key] = Colour::fromString(_propertySet->getValue(key)); + } else if (std::holds_alternative(storedValue)) { + settings[key] = _propertySet->getValue(key).getIntValue(); + } else if (std::holds_alternative(storedValue)) { + settings[key] = _propertySet->getValue(key).getFloatValue(); + } else if (std::holds_alternative(storedValue)) { + settings[key] = _propertySet->getBoolValue(key); + } + } + } + } + + //============================================================================== + /** + * @brief Returns a mutable reference to the internal settings map. + * + * @details + * This is used by adapters that need to provide mutable access to values. + */ + inline std::map& getAllSettingsMutable() + { + return settings; + } + + //============================================================================== + /** + * @brief Tries to retrieve the range of a setting by its name. + * + */ + template + inline std::optional> getRange(const String _name) + { + auto pair = ranges.find(_name); + if (pair != ranges.end()) { + if (auto rangePtr = std::any_cast>(&pair->second)) { + return *rangePtr; + } else { + jassertfalse; + throw std::runtime_error("Type mismatch for range: " + + _name.toStdString()); + } + } + return std::nullopt; // Return empty optional if range is not found + } + +private: + //============================================================================== + // Members initialized in the initializer list + // (none for this class) + + //============================================================================== + // Other members + std::map settings; + std::map ranges; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Container) +}; +//============================================================================== +} // namespace configuration +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/configuration/Options.h b/src/dmt/configuration/Options.h new file mode 100644 index 0000000..909d3c9 --- /dev/null +++ b/src/dmt/configuration/Options.h @@ -0,0 +1,80 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Get the options for the properties file with predefined settings. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "utility/Settings.h" +#include + +//============================================================================== + +namespace dmt { +namespace configuration { + +//============================================================================== +/** + * @brief Get the options for the properties file with predefined settings. + * Set's the application name, folder name, and other options. + * + * @return A juce::PropertiesFile::Options object with default settings. + */ +[[nodiscard]] static inline auto +getOptions() noexcept +{ + juce::PropertiesFile::Options options; + + constexpr auto& name = ProjectInfo::projectName; + options.applicationName = name; + + options.filenameSuffix = ".config"; + options.storageFormat = juce::PropertiesFile::storeAsXML; + + if constexpr (OS_IS_WINDOWS) { + options.folderName = juce::String("Dimethoxy/") + name; + } else if constexpr (OS_IS_DARWIN) { + options.folderName = juce::String("Dimethoxy/") + name; + } else if constexpr (OS_IS_LINUX) { + options.folderName = juce::String(".config/Dimethoxy/") + name; + } + + options.osxLibrarySubFolder = "Application Support"; + options.commonToAllUsers = false; + options.ignoreCaseOfKeyNames = true; + options.doNotSave = false; + options.millisecondsBeforeSaving = -1; + + return options; +} + +//============================================================================== + +} // namespace configuration +} // namespace dmt + +//============================================================================== \ No newline at end of file diff --git a/src/dmt/configuration/Properties.h b/src/dmt/configuration/Properties.h new file mode 100644 index 0000000..45f3755 --- /dev/null +++ b/src/dmt/configuration/Properties.h @@ -0,0 +1,208 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * This file is part of the Dimethoxy Library, a collection of essential + * classes used across various Dimethoxy projects. + * These files are primarily designed for internal use within our repositories. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Singleton class to manage application properties with optimized + * performance. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include +#include +#include + +//============================================================================== + +namespace dmt { +namespace configuration { + +//============================================================================== + +using SettingValue = dmt::configuration::Container::SettingValue; +using SettingsOverwride = std::pair; +using SettingsOverrides = std::vector; +using SettingsReplacement = std::pair; +using SettingsReplacements = std::vector; +//============================================================================== +/** + * @class Properties + * @brief Class to manage application properties + */ +class Properties +{ + using String = juce::String; + +public: + //============================================================================ + /** + * @brief Initialize the properties with options and settings. + */ + void initialize(SettingsOverrides _overwrites = {}, + SettingsReplacements _replacements = {}) noexcept + { + auto options = dmt::configuration::getOptions(); + file.setStorageParameters(options); + auto settings = file.getUserSettings(); + + // Build fallback property set from defaults + fallbackPropertySet = dmt::Settings::container.toPropertySet(); + // Apply overrides and replacements to fallback only + applyOverrides(&fallbackPropertySet, _overwrites); + applyReplacements(&fallbackPropertySet, _replacements); + settings->setFallbackPropertySet(&fallbackPropertySet); + + // Add missing keys from the fallback property set to user settings + bool newKeysAdded = false; + const auto& fallbackKeys = fallbackPropertySet.getAllProperties(); + for (const auto& key : fallbackKeys.getAllKeys()) { + if (!settings->containsKey(key)) { + settings->setValue(key, fallbackKeys[key]); + newKeysAdded = true; // Mark that new keys were added + } + } + + // Remove the "initialized" flag if new keys were added + if (newKeysAdded && settings->containsKey("initialized")) { + settings->removeValue("initialized"); + } + + // Mark as initialized and save the settings + if (!settings->containsKey("initialized")) { + settings->setValue("initialized", true); + } + settings->saveIfNeeded(); + + // Now we apply the settings to the container + dmt::Settings::container.applyPropertySet(settings); + + // Set the app name + auto appName = options.applicationName; + dmt::Settings::appName = appName; + } + + /** + * @brief Save the current container settings to the file system. + */ + void saveCurrentSettings() + { + auto* settings = file.getUserSettings(); + auto currentSet = dmt::Settings::container.toPropertySet(); + for (const auto& key : currentSet.getAllProperties().getAllKeys()) { + settings->setValue(key, currentSet.getValue(key)); + } + settings->saveIfNeeded(); + } + + /** + * @brief Reset the container and file to fallback (default) values. + */ + void resetToFallback() + { + auto* settings = file.getUserSettings(); + // Overwrite container with fallback + dmt::Settings::container.applyPropertySet(&fallbackPropertySet); + // Overwrite file with fallback + for (const auto& key : + fallbackPropertySet.getAllProperties().getAllKeys()) { + settings->setValue(key, fallbackPropertySet.getValue(key)); + } + settings->saveIfNeeded(); + } + +protected: + void applyOverrides(juce::PropertySet* settings, + const SettingsOverrides& _overwrites) + { + for (const auto& [key, value] : _overwrites) { + if (std::holds_alternative(value)) { + settings->setValue(key, std::get(value)); + } else if (std::holds_alternative(value)) { + settings->setValue(key, std::get(value)); + } else if (std::holds_alternative(value)) { + settings->setValue(key, std::get(value)); + } else if (std::holds_alternative(value)) { + settings->setValue(key, std::get(value)); + } else if (std::holds_alternative(value)) { + settings->setValue(key, std::get(value).toString()); + } + } + } + + void applyReplacements(juce::PropertySet* settings, + const SettingsReplacements& _replacements) + { + for (const auto& [fromValue, toValue] : _replacements) { + auto allKeys = settings->getAllProperties().getAllKeys(); + for (const auto& key : allKeys) { + const auto& currentValue = settings->getValue(key); + + // Compare and replace for each supported type + bool match = false; + juce::var newValue; + if (std::holds_alternative(fromValue)) { + if (currentValue == std::get(fromValue)) + match = true; + } else if (std::holds_alternative(fromValue)) { + if (currentValue.getIntValue() == std::get(fromValue)) + match = true; + } else if (std::holds_alternative(fromValue)) { + if (currentValue.getFloatValue() == std::get(fromValue)) + match = true; + } else if (std::holds_alternative(fromValue)) { + bool currentBool = (currentValue == "1" || currentValue == "true"); + if (currentBool == std::get(fromValue)) + match = true; + } else if (std::holds_alternative(fromValue)) { + if (juce::Colour::fromString(currentValue) == + std::get(fromValue)) + match = true; + } + + if (match) { + // Set toValue as the new value + if (std::holds_alternative(toValue)) { + newValue = std::get(toValue); + } else if (std::holds_alternative(toValue)) { + newValue = std::get(toValue); + } else if (std::holds_alternative(toValue)) { + newValue = std::get(toValue); + } else if (std::holds_alternative(toValue)) { + newValue = std::get(toValue); + } else if (std::holds_alternative(toValue)) { + newValue = std::get(toValue).toString(); + } + settings->setValue(key, newValue); + } + } + } + } + +private: + juce::ApplicationProperties file; + juce::PropertySet fallbackPropertySet; +}; +} // namespace configuration +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/configuration/TreeAdapter.h b/src/dmt/configuration/TreeAdapter.h new file mode 100644 index 0000000..0ef3bf3 --- /dev/null +++ b/src/dmt/configuration/TreeAdapter.h @@ -0,0 +1,347 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Adapter for browsing settings as a fixed-depth category/leaf tree. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "configuration/Container.h" +#include + +//============================================================================== + +namespace dmt { +namespace configuration { + +//============================================================================== +/** + * @brief Adapter for browsing settings as a category/leaf tree. + * + * @details + * This adapter provides a fixed-depth (category/leaf) view of a flat + * dmt::configuration::Container. It is intended for use in GUIs or + * other tools that require hierarchical browsing of settings. + * + * The adapter groups settings by splitting their names at the first '.' + * character, treating the prefix as the category and the suffix as the leaf. + * Only settings with at least one '.' are included in the tree. + * + * The adapter holds a reference to the underlying Container, so all + * mutations are reflected in real time. Call rebuild() if settings are + * added or removed after construction. + */ +class TreeAdapter +{ + using Container = dmt::configuration::Container; + +public: + //============================================================================== + /** + * @brief Represents a leaf node in the settings tree. + * + * @details + * Each leaf corresponds to a single setting value within a category. + * The value pointer is always valid as long as the Container exists. + */ + struct Leaf + { + juce::String name; + Container::SettingValue* value; + std::shared_ptr range; // Pointer to range, nullptr if not found + + juce::String toString() const + { + auto toStringImpl = [](const auto& v) -> juce::String { + using T = std::decay_t; + if constexpr (std::is_same_v) + return v; + else if constexpr (std::is_same_v) + return v.toString(); + else if constexpr (std::is_same_v) + return juce::String(v); + else if constexpr (std::is_same_v) + return juce::String(v); + else if constexpr (std::is_same_v) + return v ? "true" : "false"; + else + return {}; + }; + return value ? std::visit(toStringImpl, *value) : juce::String(); + } + + /** + * @brief Returns the std::variant index of the value, or -1 if value is + * null. + */ + int getTypeIndex() const noexcept + { + return value ? static_cast(value->index()) : -1; + } + + /** + * @brief Attempts to parse the given text and set the value. + * + * @param _textToSet The text to parse and set. + * @return true if parsing and setting succeeded, false otherwise. + */ + bool parseAndSet(juce::String _textToSet) + { + if (!value) + return false; + + switch (getTypeIndex()) { + case 0: // juce::String + *value = _textToSet; + return true; + case 1: // juce::Colour + { + juce::Colour c = juce::Colour::fromString(_textToSet); + if (c.isTransparent()) // fromString returns transparent if invalid + return false; + *value = c; + return true; + } + case 2: // int + { + int v = _textToSet.getIntValue(); + // Optionally, check if _textToSet is a valid int string + if (_textToSet.trim().isEmpty() || + !_textToSet.containsOnly("0123456789-+")) + return false; + // Check for range + if (range != nullptr) { + auto* rangePtr = static_cast*>(range.get()); + if (v < rangePtr->min || v > rangePtr->max) + return false; + } + *value = v; + return true; + } + case 3: // float + { + double v = _textToSet.getDoubleValue(); + // Optionally, check if _textToSet is a valid float string + if (_textToSet.trim().isEmpty()) + return false; + // Check for range + if (range != nullptr) { + auto* rangePtr = static_cast*>(range.get()); + if (v < rangePtr->min || v > rangePtr->max) + return false; + } + *value = static_cast(v); + return true; + } + case 4: // bool + { + auto lower = _textToSet.trim().toLowerCase(); + if (lower == "true" || lower == "1") { + *value = true; + return true; + } + if (lower == "false" || lower == "0") { + *value = false; + return true; + } + return false; + } + default: + return false; + } + } + }; + + //============================================================================== + /** + * @brief Represents a category node in the settings tree. + * + * @details + * Each category contains a list of leaves (settings) that share the same + * category prefix. + */ + struct Category + { + juce::String name; + std::vector leaves; + }; + + //============================================================================== + /** + * @brief Constructs the adapter with a reference to a Container. + * + * @param _containerRef Reference to the settings container to adapt. + * + * @details + * The adapter does not take ownership. All mutations to the container + * are reflected in the adapter. Call rebuild() if the set of settings + * changes after construction. + */ + inline explicit TreeAdapter(Container& _containerRef) noexcept + : container(_containerRef) + { + buildTree(); + } + + /** + * @brief Constructs the adapter with a reference to a Container and a block + * list. + * + * @param _containerRef Reference to the settings container to adapt. + * @param _blockedCategories List of category names to block (ignore). + */ + inline TreeAdapter(Container& _containerRef, + std::vector _blockedCategories) noexcept + : container(_containerRef) + , blockedCategories(std::move(_blockedCategories)) + { + buildTree(); + } + + //============================================================================== + /** + * @brief Rebuilds the category/leaf tree from the container. + * + * @details + * Call this after adding or removing settings in the container. + * This operation is not real-time safe and should not be called from + * the audio thread. + */ + inline void rebuild() noexcept { buildTree(); } + + //============================================================================== + /** + * @brief Returns a mutable reference to the vector of categories. + * + * @return Reference to the vector of categories. + * + * @details + * This allows modifications to the categories directly. Use with caution + * as it bypasses the rebuild mechanism. + */ + [[nodiscard]] inline std::vector& getCategories() noexcept + { + return categories; + } + +protected: + //============================================================================== + /** + * @brief Builds the category/leaf tree from the container. + * + * @details + * Groups all settings by their category prefix (before the first '.'). + * Only settings with a '.' in their name are included. + * Categories in the block list are ignored. + */ + inline void buildTree() noexcept + { + categories.clear(); + std::map> categoryMap; + + for (auto& [key, value] : container.getAllSettingsMutable()) { + auto dotIndex = static_cast(key.indexOfChar('.')); + if (dotIndex < 0) + continue; + juce::String category = key.substring(0, dotIndex); + // Blocked category check + if (std::find(blockedCategories.begin(), + blockedCategories.end(), + category) != blockedCategories.end()) + continue; + juce::String leaf = key.substring(dotIndex + 1); + // Hide "General.ThemeVersion" + if (category == "General" && leaf == "ThemeVersion") + continue; + + // Find range pointer (nullptr if not found) + std::shared_ptr rangePtr = nullptr; + int typeIndex = static_cast(value.index()); + try { + switch (typeIndex) { + case 2: { // int + auto opt = container.getRange(key); + if (opt) + rangePtr = std::make_shared>(*opt); + break; + } + case 3: { // float + auto opt = container.getRange(key); + if (opt) + rangePtr = std::make_shared>(*opt); + break; + } + default: + break; + } + } catch (...) { + // ignore errors, leave rangePtr as nullptr + } + + Leaf leafObj{ leaf, &value, rangePtr }; + categoryMap[category].push_back(leafObj); + } + + // Prepare to sort categories: "General" should be first, "Audio" second + std::vector>> sortedCategories; + auto generalIt = categoryMap.find("General"); + if (generalIt != categoryMap.end()) { + // Add "General" first if it exists + sortedCategories.push_back(*generalIt); + categoryMap.erase(generalIt); + } + auto audioIt = categoryMap.find("Audio"); + if (audioIt != categoryMap.end()) { + // Add "Audio" second if it exists + sortedCategories.push_back(*audioIt); + categoryMap.erase(audioIt); + } + // Add the rest + for (auto& entry : categoryMap) { + sortedCategories.push_back(entry); + } + + // Now add to categories vector + for (auto& [category, leaves] : sortedCategories) { + categories.push_back(Category{ category, leaves }); + } + } + +private: + //============================================================================== + // Members initialized in the initializer list + Container& container; + std::vector blockedCategories; // Blocked category names + + //============================================================================== + // Other members + std::vector categories; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TreeAdapter) +}; +} // namespace configuration +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/dsp/Dsp.h b/src/dmt/dsp/Dsp.h new file mode 100644 index 0000000..3e197b5 --- /dev/null +++ b/src/dmt/dsp/Dsp.h @@ -0,0 +1,36 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * DSP header file. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "./data/Data.h" +#include "./effect/Effect.h" +#include "./envelope/Envelope.h" +#include "./filter/Filter.h" +#include "./synth/Synth.h" diff --git a/src/dmt/dsp/data/Data.h b/src/dmt/dsp/data/Data.h new file mode 100644 index 0000000..f24fab8 --- /dev/null +++ b/src/dmt/dsp/data/Data.h @@ -0,0 +1,36 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Data header to include all necessary data files. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "./FifoAudioBuffer.h" +#include "./RingAudioBuffer.h" +#include "./RingBufferInterface.h" + +//============================================================================== \ No newline at end of file diff --git a/src/dmt/dsp/data/FifoAudioBuffer.h b/src/dmt/dsp/data/FifoAudioBuffer.h new file mode 100644 index 0000000..ec5d638 --- /dev/null +++ b/src/dmt/dsp/data/FifoAudioBuffer.h @@ -0,0 +1,200 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * This file is part of the Dimethoxy Library, a collection of essential + * classes used across various Dimethoxy projects. + * These files are primarily designed for internal use within our repositories. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * A lock-free FIFO audio buffer optimized for real-time performance. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once +#include + +//============================================================================== + +namespace dmt { +namespace dsp { +namespace data { + +//============================================================================== +/** + * @brief A lock-free FIFO audio buffer optimized for real-time performance. + * + * This class uses a circular buffer to manage audio data in a lock-free manner. + * + * @tparam SampleType The type of audio sample (e.g., float, double). + */ +template +class alignas(64) FifoAudioBuffer : public juce::AbstractFifo +{ + using BufferData = std::vector>; + using ChannelData = std::vector; + +public: + //============================================================================ + /** + * @brief Constructs a FifoAudioBuffer with the specified number of channels + * and buffer size. + * + * @param _channels The number of audio channels. + * @param _bufferSize The size of the buffer. + */ + constexpr inline FifoAudioBuffer(int _channels, int _bufferSize) noexcept + : AbstractFifo(_bufferSize) + { + buffer.setSize(_channels, _bufferSize); + } + + //============================================================================ + /** + * @brief Adds audio data to the FIFO buffer. + * + * @param _target The audio buffer containing the data to add. + */ + forcedinline void addToFifo( + const juce::AudioBuffer& _target) noexcept + { + const int numSamples = _target.getNumSamples(); + int firstBlockStart, firstBlockSize, secondBlockStart, secondBlockSize; + + prepareToWrite(numSamples, + firstBlockStart, + firstBlockSize, + secondBlockStart, + secondBlockSize); + + for (int channel = 0; channel < buffer.getNumChannels(); ++channel) { + if (firstBlockSize > 0) + buffer.copyFrom( + channel, firstBlockStart, _target, channel, 0, firstBlockSize); + if (secondBlockSize > 0) + buffer.copyFrom(channel, + secondBlockStart, + _target, + channel, + firstBlockSize, + secondBlockSize); + } + + finishedWrite(firstBlockSize + secondBlockSize); + } + + //============================================================================ + /** + * @brief Reads audio data from the FIFO buffer. + * + * @param _target The audio buffer to store the read data. + */ + forcedinline void readFromFifo( + juce::AudioBuffer& _target) noexcept + { + const int numSamples = _target.getNumSamples(); + int firstBlockStart, firstBlockSize, secondBlockStart, secondBlockSize; + + prepareToRead(numSamples, + firstBlockStart, + firstBlockSize, + secondBlockStart, + secondBlockSize); + + for (int channel = 0; channel < buffer.getNumChannels(); ++channel) { + if (firstBlockSize > 0) + _target.copyFrom( + channel, 0, buffer, channel, firstBlockStart, firstBlockSize); + if (secondBlockSize > 0) + _target.copyFrom(channel, + firstBlockSize, + buffer, + channel, + secondBlockStart, + secondBlockSize); + } + + finishedRead(firstBlockSize + secondBlockSize); + } + + //============================================================================ + /** + * @brief Resizes the FIFO buffer. + * + * @param _channels The new number of channels. + * @param _newBufferSize The new buffer size. + */ + forcedinline void setSize(const int _channels, + const int _newBufferSize) noexcept + { + buffer.setSize(_channels, _newBufferSize); + setTotalSize(_newBufferSize); + reset(); + } + + //============================================================================ + /** + * @brief Clears the FIFO buffer. + */ + forcedinline void clear() noexcept + { + buffer.clear(); + reset(); + } + + //============================================================================ + /** + * @brief Gets the number of channels in the FIFO buffer. + * + * @return The number of channels. + */ + forcedinline int getNumChannels() const noexcept + { + return buffer.getNumChannels(); + } + + //============================================================================ + /** + * @brief Gets the number of samples in the FIFO buffer. + * + * @return The number of samples. + */ + forcedinline int getNumSamples() const noexcept + { + return buffer.getNumSamples(); + } + + //============================================================================ + /** + * @brief Gets the underlying audio buffer. + * + * @return A const reference to the audio buffer. + */ + forcedinline const juce::AudioBuffer& getBuffer() const noexcept + { + return buffer; + } + +private: + juce::AudioBuffer buffer; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FifoAudioBuffer) +}; + +} // namespace data +} // namespace dsp +} // namespace dmt diff --git a/src/dmt/dsp/data/RingAudioBuffer.h b/src/dmt/dsp/data/RingAudioBuffer.h new file mode 100644 index 0000000..1b12b02 --- /dev/null +++ b/src/dmt/dsp/data/RingAudioBuffer.h @@ -0,0 +1,309 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * A ring buffer for audio data that supports efficient writing and + * reading. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "dsp/data/FifoAudioBuffer.h" +#include "dsp/data/RingBufferInterface.h" +#include + +//============================================================================== + +namespace dmt { +namespace dsp { +namespace data { + +//============================================================================== +/** + * @brief A ring buffer for audio data that supports efficient writing and + * reading. + * + * @tparam SampleType The type of audio sample (e.g., float, double). + */ +template +class alignas(64) RingAudioBuffer : public RingBufferInterface +{ + using AudioBuffer = juce::AudioBuffer; + using FifoAudioBuffer = dmt::dsp::data::FifoAudioBuffer; + using RingBufferInterface = dmt::dsp::data::RingBufferInterface; + +public: + //============================================================================ + /** + * @brief Constructs a RingAudioBuffer with the specified number of channels + * and samples. + * + * @param _numChannelsToAllocate The number of channels to allocate. + * @param _numSamplesToAllocate The number of samples to allocate. + */ + constexpr RingAudioBuffer(const int _numChannelsToAllocate, + const int _numSamplesToAllocate) noexcept + : RingBufferInterface(ringBuffer, writePosition, readPositions) + , writePosition(0) + , ringBuffer(_numChannelsToAllocate, _numSamplesToAllocate) + , readPositions(static_cast(_numChannelsToAllocate), 0) + { + } + + //============================================================================ + /** + * @brief Writes audio data to the ring buffer. + * + * @param _bufferToWrite The audio buffer containing the data to write. + */ + forcedinline void write(const AudioBuffer& _bufferToWrite) noexcept + { + const int numChannels = getNumChannels(); + const int bufferSize = getNumSamples(); + const int channelsToWrite = _bufferToWrite.getNumChannels(); + const int samplesToWrite = _bufferToWrite.getNumSamples(); + + if (channelsToWrite > numChannels || samplesToWrite > bufferSize) + [[unlikely]] { + jassertfalse; + return; + } + + const int firstBlockSize = getNumSamples() - writePosition; + const int secondBlockSize = samplesToWrite - firstBlockSize; + + for (size_t channel = 0; channel < static_cast(channelsToWrite); + ++channel) { + if (firstBlockSize > 0) + ringBuffer.copyFrom(static_cast(channel), + writePosition, + _bufferToWrite, + static_cast(channel), + 0, + firstBlockSize); + if (secondBlockSize > 0) + ringBuffer.copyFrom(static_cast(channel), + 0, + _bufferToWrite, + static_cast(channel), + firstBlockSize, + secondBlockSize); + } + + updateWritePosition(samplesToWrite); + } + + //============================================================================ + /** + * @brief Writes audio data from a FIFO buffer to the ring buffer. + * + * @param _bufferToWrite The FIFO buffer containing the data to write. + */ + forcedinline void write(FifoAudioBuffer& _bufferToWrite) noexcept + { + const int numChannels = getNumChannels(); + const int bufferSize = getNumSamples(); + const int channelsToWrite = _bufferToWrite.getNumChannels(); + const int samplesToWrite = _bufferToWrite.getNumReady(); + + if (channelsToWrite > numChannels || samplesToWrite > bufferSize) + [[unlikely]] { + jassertfalse; + return; + } + + const AudioBuffer& source = _bufferToWrite.getBuffer(); + int start1, size1, start2, size2; + _bufferToWrite.prepareToRead(samplesToWrite, start1, size1, start2, size2); + + // Block 1 Section 1 + if (size1 > 0) { + int section1size = jmin(size1, bufferSize - writePosition); + for (size_t channel = 0; channel < static_cast(channelsToWrite); + ++channel) { + ringBuffer.copyFrom(static_cast(channel), + writePosition, + source, + static_cast(channel), + start1, + section1size); + } + // Block 1 Section 2 + int section2size = size1 - section1size; + if (section2size > 0) { + for (size_t channel = 0; channel < static_cast(channelsToWrite); + ++channel) { + ringBuffer.copyFrom(static_cast(channel), + 0, + source, + static_cast(channel), + start1 + section1size, + section2size); + } + } + } + // Block 2 Section 3 + if (size2 > 0) { + int block2start = (writePosition + size1) % bufferSize; + int section3size = jmin(size2, bufferSize - block2start); + for (size_t channel = 0; channel < static_cast(channelsToWrite); + ++channel) { + ringBuffer.copyFrom(static_cast(channel), + block2start, + source, + static_cast(channel), + start2, + section3size); + } + // Block 2 Section 4 + int section4size = size2 - section3size; + if (section4size > 0) { + for (size_t channel = 0; channel < static_cast(channelsToWrite); + ++channel) { + ringBuffer.copyFrom(static_cast(channel), + 0, + source, + static_cast(channel), + start2 + section3size, + section4size); + } + } + } + + updateWritePosition(samplesToWrite); + _bufferToWrite.finishedRead(size1 + size2); + } + + //============================================================================ + /** + * @brief Resizes the ring buffer. + * + * @param _numChannelsToAllocate The new number of channels. + * @param _numSamplesToAllocate The new buffer size. + */ + forcedinline void resize(const int _numChannelsToAllocate, + const int _numSamplesToAllocate) noexcept + { + ringBuffer.setSize(_numChannelsToAllocate, _numSamplesToAllocate); + } + + //============================================================================ + /** + * @brief Gets the number of channels in the ring buffer. + * + * @return The number of channels. + */ + forcedinline int getNumChannels() const noexcept + { + return ringBuffer.getNumChannels(); + } + + //============================================================================ + /** + * @brief Gets the number of samples in the ring buffer. + * + * @return The number of samples. + */ + forcedinline int getNumSamples() const noexcept + { + return ringBuffer.getNumSamples(); + } + + //============================================================================ + /** + * @brief Gets the current write position in the ring buffer. + * + * @return The write position. + */ + forcedinline int getWritePosition() const noexcept { return writePosition; } + + //============================================================================ + /** + * @brief Clears the ring buffer. + */ + forcedinline void clear() noexcept + { + ringBuffer.clear(); + writePosition = 0; + } + + //============================================================================ + /** + * @brief Gets the underlying audio buffer. + * + * @return A reference to the audio buffer. + */ + forcedinline AudioBuffer& getBuffer() noexcept { return ringBuffer; } + +protected: + //============================================================================ + /** + * @brief Updates the write position in the ring buffer. + * + * @param _increment The amount to increment the write position. + */ + forcedinline void updateWritePosition(const int _increment) noexcept + { + bool moveWriteOverRead = false; + int newWritePosition = (writePosition + _increment) % getNumSamples(); + if (writePosition == readPositions[0]) [[unlikely]] { + writePosition = newWritePosition; + return; + } + + // Check if the new write position overlaps with any read positions + for (size_t channel = 0; channel < static_cast(getNumChannels()); + ++channel) { + for (size_t i = 0; i < static_cast(_increment); ++i) { + if ((writePosition + static_cast(i)) % getNumSamples() == + readPositions[channel]) [[unlikely]] { + moveWriteOverRead = true; + break; + } + } + } + // Update write position + writePosition = newWritePosition; + + // Update read positions if necessary + if (moveWriteOverRead) { + for (size_t channel = 0; channel < static_cast(getNumChannels()); + ++channel) { + readPositions[channel] = writePosition; + } + } + } + +private: + AudioBuffer ringBuffer; + int writePosition; + std::vector readPositions; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RingAudioBuffer) +}; + +} // namespace data +} // namespace dsp +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/dsp/data/RingBufferInterface.h b/src/dmt/dsp/data/RingBufferInterface.h new file mode 100644 index 0000000..1c0741f --- /dev/null +++ b/src/dmt/dsp/data/RingBufferInterface.h @@ -0,0 +1,188 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * This file is part of the Dimethoxy Library, a collection of essential + * classes used across various Dimethoxy projects. + * These files are primarily designed for internal use within our repositories. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Interface for a ring buffer that provides easy and efficient access to + * audio samples. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "dsp/data/RingAudioBuffer.h" +#include + +//============================================================================== + +namespace dmt { +namespace dsp { +namespace data { + +//============================================================================== +/** + * @brief Interface for a ring buffer that provides easy and efficient access to + * audio samples. + * + * @tparam SampleType The type of audio sample (e.g., float, double). + */ +template +class alignas(64) RingBufferInterface +{ + using AudioBuffer = juce::AudioBuffer; + +public: + //============================================================================ + /** + * @brief Constructs a RingBufferInterface with the given audio buffer and + * positions. + * + * @param _audioBuffer The audio buffer to use. + * @param _writePosition The write position in the buffer. + * @param _readPosition The read positions for each channel. + */ + constexpr RingBufferInterface(AudioBuffer& _audioBuffer, + const int& _writePosition, + std::vector& _readPosition) noexcept + : audioBuffer(_audioBuffer) + , writePosition(_writePosition) + , readPosition(_readPosition) + { + } + + //============================================================================ + /** + * @brief Retrieves a sample from the buffer. + * + * @param _channel The channel to read from. + * @param _sample The sample index to read. + * @return The sample value. + */ + forcedinline SampleType getSample(const int _channel, + const int _sample) const noexcept + { + const int numSamples = audioBuffer.getNumSamples(); + const int block1size = numSamples - writePosition; + if (_sample < block1size) [[likely]] { + return audioBuffer.getSample(_channel, writePosition + _sample); + } + return audioBuffer.getSample(_channel, _sample - block1size); + } + + //============================================================================ + /** + * @brief Gets the read position for a specific channel. + * + * @param _channel The channel index. + * @return The read position. + */ + forcedinline int getReadPosition(int _channel) const noexcept + { + const int numSamples = audioBuffer.getNumSamples(); + const int rawReadPosition = readPosition[_channel]; + if (rawReadPosition > writePosition) [[likely]] { + return rawReadPosition - writePosition; + } + return numSamples - writePosition + rawReadPosition; + } + + //============================================================================ + /** + * @brief Increments the read position for a specific channel. + * + * @param _channel The channel index. + * @param _increment The amount to increment. + */ + forcedinline void incrementReadPosition(int _channel, int _increment) noexcept + { + const int numSamples = audioBuffer.getNumSamples(); + const int oldReadPosition = readPosition[_channel]; + const int newReadPosition = (oldReadPosition + _increment) % numSamples; + readPosition[_channel] = newReadPosition; + } + + //============================================================================ + /** + * @brief Sets the raw read position for a specific channel. + * + * @param _channel The channel index. + * @param _position The new read position. + */ + forcedinline void setRawReadPosition(const int _channel, + const int _position) noexcept + { + readPosition[_channel] = _position; + } + + //============================================================================ + /** + * @brief Gets the raw read position for a specific channel. + * + * @param _channel The channel index. + * @return The raw read position. + */ + forcedinline int getRawReadPosition(const int _channel) const noexcept + { + return readPosition[_channel]; + } + + //============================================================================ + /** + * @brief Equalizes the read positions across all channels. + */ + forcedinline void equalizeReadPositions() noexcept + { + int highestReadPosition = 0; + int highestReadChannel = 0; + + // Find the highest read position among all channels + for (size_t channel = 0; channel < audioBuffer.getNumChannels(); + ++channel) { + const int readPos = getReadPosition(static_cast(channel)); + if (readPos > highestReadPosition) [[likely]] { + highestReadPosition = readPos; + highestReadChannel = static_cast(channel); + } + } + + // Get the raw read position for the channel with the highest read position + const int highestRawReadPosition = getRawReadPosition(highestReadChannel); + + // Set all channels to the highest raw read position found + for (size_t channel = 0; channel < audioBuffer.getNumChannels(); + ++channel) { + setRawReadPosition(static_cast(channel), highestRawReadPosition); + } + } + +private: + AudioBuffer& audioBuffer; + const int& writePosition; + std::vector& readPosition; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RingBufferInterface) +}; + +} // namespace data +} // namespace dsp +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/dsp/effect/DisfluxProcessor.h b/src/dmt/dsp/effect/DisfluxProcessor.h new file mode 100644 index 0000000..33ab188 --- /dev/null +++ b/src/dmt/dsp/effect/DisfluxProcessor.h @@ -0,0 +1,325 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Disflux Processor class for processing audio buffers with a series of + * all-pass filters. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include +#include + +//============================================================================== + +namespace dmt { +namespace dsp { +namespace effect { + +//============================================================================== + +/** + * @brief Disflux Processor + * + * This class processes audio buffers with a series of all-pass filters. + */ +class alignas(64) DisfluxProcessor +{ + constexpr static int FILTER_AMOUNT = 256; + constexpr static float MIN_FREQUENCY = 20.0f; + constexpr static float MAX_FREQUENCY = 20000.0f; + + // Smoothing times (seconds) for each parameter + const float& frequencySmoothTime; + const float& spreadSmoothTime; + const float& pinchSmoothTime; + const bool& useOutputHighpass; + const float& outputHighpassFrequency; + const int& smoothingInterval; + + float lastFrequencySmoothTime = 0.0f; + float lastSpreadSmoothTime = 0.0f; + float lastPinchSmoothTime = 0.0f; + int lastSmoothingInterval = 0; + + using AudioBuffer = juce::AudioBuffer; + using Filter = juce::IIRFilter; + using FilterArray = std::array; + +public: + //============================================================================== + /** + * @brief Constructs a DisfluxProcessor with the given parameters. + * + * @param _apvts The AudioProcessorValueTreeState containing the parameters. + * @param _frequencySmoothTime Smoothing time for frequency parameter. + * @param _spreadSmoothTime Smoothing time for spread parameter. + * @param _pinchSmoothTime Smoothing time for pinch parameter. + * @param _useOutputHighpass Whether to use output highpass filter. + * @param _outputHighpassFrequency Frequency for output highpass filter. + * @param _smoothingInterval Smoothing interval (samples). + */ + DisfluxProcessor(juce::AudioProcessorValueTreeState& _apvts, + const float& _frequencySmoothTime, + const float& _spreadSmoothTime, + const float& _pinchSmoothTime, + const bool& _useOutputHighpass, + const float& _outputHighpassFrequency, + const int& _smoothingInterval) noexcept + : apvts(_apvts) + , frequencySmoothTime(_frequencySmoothTime) + , spreadSmoothTime(_spreadSmoothTime) + , pinchSmoothTime(_pinchSmoothTime) + , useOutputHighpass(_useOutputHighpass) + , outputHighpassFrequency(_outputHighpassFrequency) + , smoothingInterval(_smoothingInterval) + { + cacheLastSmoothingValues(); + } + //============================================================================== + inline void cacheLastSmoothingValues() noexcept + { + lastFrequencySmoothTime = frequencySmoothTime; + lastSpreadSmoothTime = spreadSmoothTime; + lastPinchSmoothTime = pinchSmoothTime; + lastSmoothingInterval = smoothingInterval; + } + + //============================================================================== + /** + * @brief Prepares the processor with the given sample rate. + * + * @param _newSampleRate The sample rate. + */ + inline void prepare(const double _newSampleRate) noexcept + { + sampleRate = static_cast(_newSampleRate); + smoothedFrequency.reset(sampleRate, frequencySmoothTime); + smoothedSpread.reset(sampleRate, spreadSmoothTime); + smoothedPinch.reset(sampleRate, pinchSmoothTime); + + // Set initial values + smoothedFrequency.setCurrentAndTargetValue(frequency); + smoothedSpread.setCurrentAndTargetValue(static_cast(spread)); + smoothedPinch.setCurrentAndTargetValue(pinch); + + setCoefficients(frequency, static_cast(spread), pinch); + + // Prepare output highpass filter (default to 20 Hz) + auto highpassCoeffs = juce::IIRCoefficients::makeHighPass(sampleRate, 20.0); + outputHighpassLeft.setCoefficients(highpassCoeffs); + outputHighpassRight.setCoefficients(highpassCoeffs); + outputHighpassLeft.reset(); + outputHighpassRight.reset(); + + // Track last used frequency for output highpass + lastHighpassFrequency = -1.0f; + } + + //============================================================================== + /** + * @brief Processes an audio buffer. + * + * @param _buffer The audio buffer. + */ + inline void processBlock(AudioBuffer& _buffer) noexcept + { + if (sampleRate <= 0.0f) { + return; + } + + // Load parameters + const int newAmount = apvts.getRawParameterValue("DisfluxAmount")->load(); + const int newSpread = apvts.getRawParameterValue("DisfluxSpread")->load(); + const auto newFrequency = + apvts.getRawParameterValue("DisfluxFrequency")->load(); + const auto newPinch = apvts.getRawParameterValue("DisfluxPinch")->load(); + const auto mix = apvts.getRawParameterValue("DisfluxMix")->load(); + + // Test if smoothing values have changed + if (!juce::approximatelyEqual(lastFrequencySmoothTime, + frequencySmoothTime)) { + smoothedFrequency.reset(sampleRate, frequencySmoothTime); + lastFrequencySmoothTime = frequencySmoothTime; + } + if (!juce::approximatelyEqual(lastSpreadSmoothTime, spreadSmoothTime)) { + smoothedSpread.reset(sampleRate, spreadSmoothTime); + lastSpreadSmoothTime = spreadSmoothTime; + } + if (!juce::approximatelyEqual(lastPinchSmoothTime, pinchSmoothTime)) { + smoothedPinch.reset(sampleRate, pinchSmoothTime); + lastPinchSmoothTime = pinchSmoothTime; + } + // We last highpass values here as it's recalculated on each run anyways + if (lastSmoothingInterval != smoothingInterval) { + smoothingIntervalCountdown = smoothingInterval; + lastSmoothingInterval = smoothingInterval; + } + + // Set smoothing targets + smoothedFrequency.setTargetValue(newFrequency); + smoothedSpread.setTargetValue(static_cast(newSpread)); + smoothedPinch.setTargetValue(newPinch); + + // Only reset filters if amount changes + bool needUpdateCoeffs = false; + + // If the amount of filters has changed, reset the filters + if (amount != newAmount) { + amount = newAmount; + for (size_t filterIndex = 0; filterIndex < amount; ++filterIndex) { + leftFilters[filterIndex].reset(); + rightFilters[filterIndex].reset(); + } + needUpdateCoeffs = false; + smoothedFrequency.skip( + static_cast(sampleRate * frequencySmoothTime)); + smoothedSpread.skip(static_cast(sampleRate * spreadSmoothTime)); + smoothedPinch.skip(static_cast(sampleRate * pinchSmoothTime)); + } + + // Output highpass filter: recalc coeffs only if freq changed and enabled + if (useOutputHighpass && + !juce::approximatelyEqual(lastHighpassFrequency, + outputHighpassFrequency)) { + auto highpassCoeffs = juce::IIRCoefficients::makeHighPass( + sampleRate, outputHighpassFrequency); + outputHighpassLeft.setCoefficients(highpassCoeffs); + outputHighpassRight.setCoefficients(highpassCoeffs); + lastHighpassFrequency = outputHighpassFrequency; + } + + int numSamples = _buffer.getNumSamples(); + int smoothingCountdown = smoothingIntervalCountdown; + float currentFrequency = smoothedFrequency.getCurrentValue(); + float currentSpread = smoothedSpread.getCurrentValue(); + float currentPinch = smoothedPinch.getCurrentValue(); + + for (int sample = 0; sample < numSamples; ++sample) { + // Smoothing interval logic: update filter coefficients every + // smoothingInterval samples + if (smoothingCountdown <= 0) { + currentFrequency = smoothedFrequency.getCurrentValue(); + currentSpread = smoothedSpread.getCurrentValue(); + currentPinch = smoothedPinch.getCurrentValue(); + setCoefficients(currentFrequency, currentSpread, currentPinch); + smoothingCountdown = smoothingInterval; + } + + // Advance smoothing values for each sample + currentFrequency = smoothedFrequency.getNextValue(); + currentSpread = smoothedSpread.getNextValue(); + currentPinch = smoothedPinch.getNextValue(); + + smoothingCountdown--; + + auto const leftDry = _buffer.getSample(0, sample); + auto const rightDry = _buffer.getSample(1, sample); + auto left = leftDry; + auto right = rightDry; + + for (size_t filterIndex = 0; filterIndex < amount; ++filterIndex) { + left = leftFilters[filterIndex].processSingleSampleRaw(left); + right = rightFilters[filterIndex].processSingleSampleRaw(right); + } + + const auto wetGain = mix; + const auto dryGain = 1.0f - wetGain; + left = (left * wetGain) + (leftDry * dryGain); + right = (right * wetGain) + (rightDry * dryGain); + + // Apply output highpass filter if enabled + if (useOutputHighpass) { + left = outputHighpassLeft.processSingleSampleRaw(left); + right = outputHighpassRight.processSingleSampleRaw(right); + } + + _buffer.setSample(0, sample, left); + _buffer.setSample(1, sample, right); + } + smoothingIntervalCountdown = smoothingCountdown; + } + +protected: + //============================================================================== + /** + * @brief Sets the coefficients for the filters. + */ + inline void setCoefficients(float freq, float sprd, float pnch) noexcept + { + const float spreadAmount = sprd; + const float rangeStartFrequency = + juce::jlimit(MIN_FREQUENCY, MAX_FREQUENCY, freq - (spreadAmount / 2.0f)); + const float rangeEndFrequency = + juce::jlimit(MIN_FREQUENCY, MAX_FREQUENCY, freq + (spreadAmount / 2.0f)); + + const float logStartFrequency = std::log(rangeStartFrequency); + const float logEndFrequency = std::log(rangeEndFrequency); + const float logFrequencyDelta = logEndFrequency - logStartFrequency; + + for (size_t filterIndex = 0; filterIndex < amount; ++filterIndex) { + const float logFrequencyOffsetFactor = + (amount == 1) ? 0.5f : static_cast(filterIndex) / (amount - 1); + + const float logFrequency = std::exp( + logStartFrequency + (logFrequencyDelta * logFrequencyOffsetFactor)); + + const auto coefficients = juce::IIRCoefficients::makeAllPass( + static_cast(sampleRate), logFrequency, pnch); + + leftFilters[filterIndex].setCoefficients(coefficients); + rightFilters[filterIndex].setCoefficients(coefficients); + } + } + +private: + //============================================================================== + juce::AudioProcessorValueTreeState& apvts; + float sampleRate = -1.0f; + int amount = 1; + int spread = 0; + float frequency = 800.0f; + float pinch = 1.0f; + FilterArray leftFilters; + FilterArray rightFilters; + + // Smoothing + juce::SmoothedValue + smoothedFrequency; + juce::SmoothedValue smoothedSpread; + juce::SmoothedValue smoothedPinch; + int smoothingIntervalCountdown = 0; + + // Output highpass filter (configurable) + juce::IIRFilter outputHighpassLeft; + juce::IIRFilter outputHighpassRight; + float lastHighpassFrequency = -1.0f; +}; + +//============================================================================== +} // namespace effect +} // namespace dsp +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/dsp/effect/Distortion.h b/src/dmt/dsp/effect/Distortion.h new file mode 100644 index 0000000..93c16c0 --- /dev/null +++ b/src/dmt/dsp/effect/Distortion.h @@ -0,0 +1,362 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Distortion effect processor. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include +#include +#include +#include +#include + +//============================================================================== + +namespace dmt { +namespace dsp { +namespace effect { + +//============================================================================== + +/** + * @brief Distortion effect processor + * + * This class provides various types of distortion effects. + * It is optimized for real-time performance. + */ +struct alignas(64) Distortion +{ + enum class Type + { + Hardclip, + Softclip, + Saturate, + Atan, + Crunch, + Extreme, + Scream, + Sine, + Cosine, + Harmonize, + Weird, + Bitcrush, + }; + + //============================================================================== + /** + * @brief Get the string representation of the distortion type. + * + * @param _type The distortion type. + * @return The string representation of the type. + */ + [[nodiscard]] static inline const juce::String getString( + const Type _type) noexcept + { + switch (_type) { + case Distortion::Type::Hardclip: + return "Hardclip"; + case Distortion::Type::Softclip: + return "Softclip"; + case Distortion::Type::Saturate: + return "Saturate"; + case Distortion::Type::Atan: + return "Atan"; + case Distortion::Type::Crunch: + return "Crunch"; + case Distortion::Type::Extreme: + return "Extreme"; + case Distortion::Type::Scream: + return "Scream"; + case Distortion::Type::Sine: + return "Sine"; + case Distortion::Type::Cosine: + return "Cosine"; + case Distortion::Type::Harmonize: + return "Harmonize"; + case Distortion::Type::Weird: + return "Weird"; + case Distortion::Type::Bitcrush: + return "Bitcrush"; + default: + jassertfalse; + return "Hardclip"; + } + } + + //============================================================================== + /** + * @brief Apply distortion to a sample. + * + * @param _data The sample data. + * @param _type The distortion type. + * @param _drive The drive amount. + */ + static inline void distortSample(float& _data, + const Type _type, + const float _drive) noexcept + { + switch (_type) { + case Type::Hardclip: + _data = std::clamp(_drive * _data, -1.0f, 1.0f); + break; + case Type::Softclip: { + constexpr float threshold1 = 1.0f / 3.0f; + constexpr float threshold2 = 2.0f / 3.0f; + + _data *= _drive; + if (_data > threshold2) + _data = 1.0f; + else if (_data > threshold1) + _data = 1.0f - std::pow(2.0f - 3.0f * _data, 2.0f) / 3.0f; + else if (_data < -threshold2) + _data = -1.0f; + else if (_data < -threshold1) + _data = -1.0f + std::pow(2.0f + 3.0f * _data, 2.0f) / 3.0f; + else + _data = 2.0f * _data; + break; + } + + case Type::Saturate: + if (_data > 0.0f) { + _data = std::clamp( + std::pow(_data, 1.0f / ((_drive / 4.0f) + 0.75f)), -1.0f, 1.0f); + } else { + _data = -std::clamp( + std::pow(-_data, 1.0f / ((_drive / 4.0f) + 0.75f)), -1.0f, 1.0f); + } + break; + case Type::Atan: + if (juce::approximatelyEqual(_data, 0.0f)) { + if (_data > 0.0f) { + _data = std::pow(_data, 1.0f / _drive); + _data = 1.27f * std::atan(_data); + } else { + _data = std::pow(-_data, 1.0f / _drive); + _data = 1.27f * std::atan(_data); + _data = -_data; + } + } + break; + case Type::Crunch: + if (_data > 0.0f) { + _data = std::pow(_data, 1.0f / _drive); + _data = 1.27f * std::atan(_data); + } else { + _data = std::clamp(std::sin(_drive * _data), -1.0f, 1.0f); + _data = std::clamp(_drive * _data, -1.0f, 1.0f); + } + break; + case Type::Extreme: { + const float invertedDrive = 10.0f - (_drive - 1.0f); + if (std::abs(_data) >= ((invertedDrive - 1.0f) / 9.0f)) { + _data = std::signbit(_data) ? -1.0f : 1.0f; + } + break; + } + case Type::Scream: { + auto temp = _data; + const float normalizedDrive = (_drive - 1.0f) / 10.0f; + + if (_data > 0.0f) { + _data = std::clamp( + std::pow(_data, 1.0f / ((_drive / 4.0f) + 0.75f)), -1.0f, 1.0f); + } else { + _data = -std::clamp( + std::pow(-_data, 1.0f / ((_drive / 4.0f) + 0.75f)), -1.0f, 1.0f); + } + + if (_data <= -0.5f) { + constexpr float offset = 3.0f; + temp = 4.0f * _data + offset; + } else if (_data > -0.5f && _data < 0.5f) { + temp = -2.0f * _data; + } else { + constexpr float offset = -3.0f; + temp = 4.0f * _data + offset; + } + + _data = (temp * normalizedDrive) + (_data * (1.0f - normalizedDrive)); + break; + } + case Type::Sine: + _data = std::clamp(std::sin(_drive * _data), -1.0f, 1.0f); + break; + case Type::Cosine: + _data = std::clamp(std::cos(_drive * _data), -1.0f, 1.0f); + break; + case Type::Harmonize: { + _data *= _drive * 5.0f; + const float h1 = std::sin(2.0f * _data); + const float h2 = std::sin(3.0f * _data); + const float h3 = std::sin(4.0f * _data); + _data = (h1 + h2 + h3 + _data) / (_drive * 5.0f); + break; + } + case Type::Weird: { + _data *= _drive * 2.0f; + const float h1 = std::sin(2.0f * _data); + const float h2 = std::sin(3.0f * _data); + const float h3 = std::sin(4.0f * _data); + _data = std::sin(h1 + h2 + h3 + _data); + break; + } + case Type::Bitcrush: { + const float bitDepth = 10.0f - (_drive - 1.0f); + const float exponent = bitDepth - 1.0f; + const float possibleValues = std::pow(2.0f, exponent); + float quantized = (_data + 1.0f) * possibleValues; + quantized = std::round(quantized); + _data = (quantized / possibleValues) - 1.0f; + break; + } + } + } + + //============================================================================== + /** + * @brief Generate a new random seed for girth effect. + * + * @return The new girth seed. + */ + [[nodiscard]] static inline float getNewGirthSeed() noexcept + { + static thread_local std::mt19937 generator{ std::random_device{}() }; + static thread_local std::uniform_real_distribution distribution( + 0.0f, 100.0f); + return distribution(generator); + } + + //============================================================================== + /** + * @brief Generate a vector of girth seeds. + * + * @param _numSamples The number of samples. + * @return A vector of girth seeds. + */ + [[nodiscard]] static inline std::vector getGirthSeeds( + const int _numSamples) noexcept + { + std::vector girthSeeds; + girthSeeds.reserve(static_cast(_numSamples)); + for (int i = 0; i < _numSamples; ++i) { + girthSeeds.push_back(getNewGirthSeed()); + } + return girthSeeds; + } + + //============================================================================== + /** + * @brief Apply girth effect to a sample. + * + * @param _value The sample value. + * @param _girth The girth amount. + */ + static inline void girthSample(float& _value, const float _girth) noexcept + { + _value *= ((getNewGirthSeed() / 100.0f * _girth) + 1.0f); + _value = std::clamp(_value, -1.0f, 1.0f); + } + + //============================================================================== + /** + * @brief Apply girth effect to a sample with a specific seed. + * + * @param _value The sample value. + * @param _girth The girth amount. + * @param _seed The girth seed. + */ + static inline void girthSample(float& _value, + const float _girth, + const float _seed) noexcept + { + _value *= ((_seed / 100.0f * _girth) + 1.0f); + _value = std::clamp(_value, -1.0f, 1.0f); + } + + //============================================================================== + /** + * @brief Apply symmetry effect to a sample. + * + * @param _value The sample value. + * @param _symmetry The symmetry amount. + */ + static inline void symmetrySample(float& _value, + const float _symmetry) noexcept + { + if (_value > 0.0f) { + _value += _value * _symmetry; + } else { + _value -= _value * _symmetry; + } + _value = std::clamp(_value, -1.0f, 1.0f); + } + + //============================================================================== + /** + * @brief Process an audio buffer with distortion, girth, and symmetry + * effects. + * + * @param _buffer The audio buffer. + * @param _type The distortion type. + * @param _symmetry The symmetry amount. + * @param _girth The girth amount. + * @param _drive The drive amount. + */ + static inline void processBuffer(juce::AudioBuffer& _buffer, + const Type _type, + const float _symmetry, + const float _girth, + const float _drive) noexcept + { + std::vector girthSeeds; + if (_girth < 0.0f) { + girthSeeds = getGirthSeeds(_buffer.getNumSamples()); + } + + for (int channel = 0; channel < _buffer.getNumChannels(); ++channel) { + auto* channelData = _buffer.getWritePointer(channel); + for (int sample = 0; sample < _buffer.getNumSamples(); ++sample) { + if (_girth < 0.0f) { + girthSample( + channelData[sample], std::abs(_girth), girthSeeds[sample]); + } else { + girthSample(channelData[sample], _girth); + } + + distortSample(channelData[sample], _type, _drive); + symmetrySample(channelData[sample], _symmetry); + } + } + } +}; + +//============================================================================== +} // namespace effect +} // namespace dsp +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/dsp/effect/Effect.h b/src/dmt/dsp/effect/Effect.h new file mode 100644 index 0000000..5224abb --- /dev/null +++ b/src/dmt/dsp/effect/Effect.h @@ -0,0 +1,37 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Effect header file. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "./DisfluxProcessor.h" +#include "./Distortion.h" +#include "./HeretikProcessor.h" +#include "./LowpassProcessor.h" + +//============================================================================== \ No newline at end of file diff --git a/src/dmt/dsp/effect/HeretikProcessor.h b/src/dmt/dsp/effect/HeretikProcessor.h new file mode 100644 index 0000000..f86e3b7 --- /dev/null +++ b/src/dmt/dsp/effect/HeretikProcessor.h @@ -0,0 +1,168 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * This file defines the HeretikProcessor class, which applies a special kind of + * distortion effect to audio buffers that is based on a delay line that is + * modulated by the input signal. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include +#include + +//============================================================================== + +namespace dmt { +namespace dsp { +namespace effect { + +//============================================================================== + +/** + * @brief Disflux Processor + * + * This class processes audio buffers with a series of all-pass filters. + */ +class alignas(64) HeretikProcessor +{ + using AudioBuffer = juce::AudioBuffer; + using DelayLine = juce::dsp::DelayLine; + using Filter = juce::IIRFilter; + + // Add constexprs for min and max delay times (in ms) + static constexpr float maxDelayMs = 240.0f; + static constexpr float minDelayMs = 1.0f; + +public: + //============================================================================== + /** + * @brief Constructs a DisfluxProcessor with the given parameters. + * + * @param _apvts The AudioProcessorValueTreeState containing the parameters. + */ + HeretikProcessor(juce::AudioProcessorValueTreeState& _apvts) noexcept + : apvts(_apvts) + { + } + + //============================================================================== + /** + * @brief Prepares the processor with the given sample rate. + * + * @param _newSampleRate The sample rate. + */ + inline void prepare(const double _newSampleRate, + const int _samplesPerBlock) noexcept + { + sampleRate = static_cast(_newSampleRate); + juce::dsp::ProcessSpec spec; + spec.sampleRate = sampleRate; + spec.maximumBlockSize = _samplesPerBlock; + spec.numChannels = 2; + delayLine.prepare(spec); + delayLine.setMaximumDelayInSamples((int)sampleRate); + } + + //============================================================================== + /** + * @brief Processes an audio buffer. + * + * @param _buffer The audio buffer. + */ + inline void processBlock(AudioBuffer& _buffer) noexcept + { + if (sampleRate <= 0.0f) { + return; + } + + const float drive = apvts.getRawParameterValue("HeretikDrive")->load(); + const float range = apvts.getRawParameterValue("HeretikRange")->load(); + const float tone = apvts.getRawParameterValue("HeretikTone")->load(); + const float feedback = + apvts.getRawParameterValue("HeretikFeedback")->load(); + const float mix = apvts.getRawParameterValue("HeretikMix")->load(); + + // Applay the delay line to the input buffer + for (int channel = 0; channel < _buffer.getNumChannels(); ++channel) { + Filter& filter = channel == 0 ? leftFilter : rightFilter; + filter.setCoefficients( + juce::IIRCoefficients::makeLowPass(sampleRate, tone, 0.5f)); + auto* channelData = _buffer.getWritePointer(channel); + for (int sample = 0; sample < _buffer.getNumSamples(); ++sample) { + // Dry signal + const float drySample = channelData[sample] + feedbackBuffer[channel]; + delayLine.pushSample(channel, drySample); + + // Set delay time + const int delayInSamples = + getDelayInSamples(drySample, drive, range, filter); + delayLine.setDelay(delayInSamples); + + // Output + const float wetSample = delayLine.popSample(channel); + const float mixSample = (wetSample * mix) + (drySample * (1.0f - mix)); + channelData[sample] = mixSample; + feedbackBuffer[channel] = wetSample * feedback; + } + } + } + +protected: + int getDelayInSamples(const float _drySample, + const float _drive, + const float _range, + Filter& _filter) const noexcept + { + const float filteredSample = _filter.processSingleSampleRaw(_drySample); + const float driveSample = filteredSample * _drive; + const float clampedSample = std::clamp(driveSample, -1.0f, 1.0f); + const float denormalizedSample = (clampedSample + 1.0f) * 0.5f; + const float multipliedSample = denormalizedSample * _range; + const int delayInMs = msInSamples(multipliedSample); + return std::clamp(delayInMs, 0, (int)sampleRate); + } + + int msInSamples(float ms) const noexcept + { + return static_cast(juce::jlimit(minDelayMs, maxDelayMs, ms) * + sampleRate / 1000.0f); + } + +private: + //============================================================================== + juce::AudioProcessorValueTreeState& apvts; + DelayLine delayLine; + float sampleRate = -1.0f; + std::array feedbackBuffer; + Filter leftFilter; + Filter rightFilter; +}; + +//============================================================================== +} // namespace effect +} // namespace dsp +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/dsp/effect/LowpassProcessor.h b/src/dmt/dsp/effect/LowpassProcessor.h new file mode 100644 index 0000000..9e1eccb --- /dev/null +++ b/src/dmt/dsp/effect/LowpassProcessor.h @@ -0,0 +1,271 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Lowpass Processor class for processing audio buffers with a low-pass filter. + * This class has over the top comments because it also serves as a tutorial. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +// We use pragma once to ensure the header is only included once in the build. +#pragma once + +//============================================================================== + +// We include the JUCE header to gain access to the JUCE framework. +#include + +//============================================================================== + +// Some namespace shananigans to avoid conflicts with other libraries. +// Not really necessary but nice to structure the library. +namespace dmt { // Dimethoxy Library Namespace +namespace dsp { // Digital Signal Processing Module +namespace effect { // Effect Module + +//============================================================================== +/** + * @brief LowpassProcessor + * + * This class processes audio buffers with a series of low-pass filters. + * + * We use alignas(64) to align the class to 64 bytes as an attempt to optimize + * the class in a cache-friendly manner. This isn't really necessary but can + * improve cache performance in some cases. + */ +class alignas(64) LowpassProcessor +{ + //============================================================================ + // We define the minimum and maximum frequency values for the filters. + // This uses constexpr to evaluate the values at compile-time. + constexpr static float MIN_FREQUENCY = 20.0f; + constexpr static float MAX_FREQUENCY = 20000.0f; + + //============================================================================ + // We define the maximum amount of filter stages we can have. + // Each stage will add -6 dB/octave to the filter slope. + // With 16 stages we can cover a range of -6 dB/octave to -96 dB/octave. + // This uses constexpr to evaluate the values at compile-time. + constexpr static int MIN_STAGES = 1; + constexpr static int MAX_STAGES = 16; + + //============================================================================ + // We set some type aliases so we don't have to type out the full type names. + // This makes for cleaner and more readable code. + using AudioBuffer = juce::AudioBuffer; + using AudioProcessorValueTreeState = juce::AudioProcessorValueTreeState; + using Filter = juce::IIRFilter; + using FilterArray = std::array; + +public: + //============================================================================ + /** + * @brief Constructs a LowpassProcessor with the given parameters. + * + * This function is called when an object of this class is created. + * We use noexcept to indicate that this function won't throw exceptions. + * + * @param _apvts The AudioProcessorValueTreeState containing the parameters. + * This is a reference so no data is copied. (Indicated by &) + * This means that if the original object is changed, the + * changes will be reflected in this class automatically. + * This is useful for sharing data between classes. Otherwise, + * the data would be copied and changes to the original object + * wouldn't be reflected in this class. In that case we would + * have to pass down each change manually. + */ + LowpassProcessor(AudioProcessorValueTreeState& _apvts) noexcept + : apvts(_apvts) + { + // We don't really need to do anything here. + } + + //============================================================================ + /** + * @brief Prepares the processor with the given sample rate. + * + * This function prepares the processor with the given sample rate. + * We need to know the sample rate to calculate the filter coefficients. + * This function needs to be called before we can process audio. + * Not calling this function first will result in failing to process audio. + * + * We inline this function to optimize performance. + * We also use noexcept to indicate that this function won't throw exceptions. + * + * @param _newSampleRate The sample rate to prepare the processor with. + */ + inline void prepare(const double _newSampleRate) noexcept + { + // We store the sample rate in a class variable so we can use it later. + sampleRate = _newSampleRate; + + // Now that know the sample rate, we can calculate the filter coefficients. + // We need those to start processing audio. + setCoefficients(); + } + + //============================================================================ + /** + * @brief Processes an audio buffer. + * + * This function processes an audio buffer with a low-pass filter. + * Call this function from the AudioProcessor's processBlock() function. + * Make sure to call prepare() before processing audio. + * We inline this function to optimize performance. + * We also use noexcept to indicate that this function won't throw exceptions. + * + * @param _buffer A reference to the audio buffer we want to process. + * This is a reference so no data is copied. (Indicated by &) + * If we didn't use a reference, the buffer would be copied + * when this function is called. + */ + inline void processBlock(AudioBuffer& _buffer) noexcept + { + // We check if the sample rate is less than or equal to zero. + // If it is we didn't call prepare() and we can't process the audio. + if (sampleRate <= 0.0f) [[unlikely]] { + return; // Exit the function early. + } + + // We need to load the parameters from the AudioProcessorValueTreeState + // We load the parameters into local variables so we can compare them + // against the previous values and see if they have changed. + // We also use clamp() to ensure the values stay within the valid range. + const int newStages = + std::clamp((int)apvts.getRawParameterValue("LowpassStages")->load(), + MIN_STAGES, + MAX_STAGES); + const float newFrequency = + std::clamp((float)apvts.getRawParameterValue("LowPassFrequency")->load(), + MIN_FREQUENCY, + MAX_FREQUENCY); + + // Check if the amount of stages has changed. + const bool stagesChanged = stages != newStages; + + // Check if the frequency has changed. + // We can't just compare floats with != because of floating point precision. + // Therefore we use juce::approximatelyEqual() to compare the floats. + const bool frequencyChanged = + !juce::approximatelyEqual(frequency, newFrequency); + + // If either the stages or frequency have changed, we need to recalculate + // the filters coefficients. Recalculating the coefficients is an expensive + // operation so we only do it when the parameters have changed. + if (stagesChanged || frequencyChanged) [[unlikely]] { + stages = newStages; + frequency = newFrequency; + setCoefficients(); + } + + // We load the mix parameter from the AudioProcessorValueTreeState. + // This one is irrelevant for the filter coefficients so we just save it. + mix = apvts.getRawParameterValue("LowpassMix")->load(); + + // Now we process the audio buffer with the low-pass filters. + // We loop over each sample in the buffer and apply the filters. + for (size_t sample = 0; + sample < static_cast(_buffer.getNumSamples()); + ++sample) { + // Let's save the dry signal so we can mix it with the wet signal later. + const float leftDry = _buffer.getSample(0, sample); + const float rightDry = _buffer.getSample(1, sample); + + // For the wet signal we also start with the dry signal. + float left = leftDry; + float right = rightDry; + + // Now we loop over each filter stage and apply them to the audio. + for (size_t filterIndex = 0; filterIndex < stages; ++filterIndex) { + left = leftFilters[filterIndex].processSingleSampleRaw(left); + right = rightFilters[filterIndex].processSingleSampleRaw(right); + } + + // Now calculate the wet and dry gain + const float wetGain = mix; + const float dryGain = 1.0f - mix; + + // Next we mix the wet and dry signal together. + left = (left * wetGain) + (leftDry * dryGain); + right = (right * wetGain) + (rightDry * dryGain); + + // Finally we set the processed samples back into the audio buffer. + _buffer.setSample(0, sample, left); + _buffer.setSample(1, sample, right); + } + } + +protected: + //============================================================================ + /** + * @brief Sets the coefficients for the filters. + * + * This function calculates the coefficients for the filters and sets them. + * We use this function to update the filters when the parameters change. + * This function is defined as protected so it can't be called from outside. + * We inline this function to optimize performance. + * We also use noexcept to indicate that this function won't throw exceptions. + */ + inline void setCoefficients() noexcept + { + const auto coefficients = + juce::IIRCoefficients::makeAllPass(sampleRate, frequency); + + for (size_t filterIndex = 0; filterIndex < MAX_STAGES; ++filterIndex) { + leftFilters[filterIndex].setCoefficients(coefficients); + rightFilters[filterIndex].setCoefficients(coefficients); + } + } + +private: + //============================================================================ + + // A reference (indicated by &) to the APVTS of the plugin. + AudioProcessorValueTreeState& apvts; + + // Tracks the sample rate of the audio. + // We start with -1.0f to indicate that the sample rate is not set yet. + double sampleRate = -1.0f; + + // Tracks the cutoff frequency of the filters. + // We start with 800 Hz as a random default value. + float frequency = 800.0f; + + // Tracks the mix between the dry and wet signal. + // We start with 1.0f to indicate that the wet signal is fully active. + float mix = 1.0f; + + // Tracks how many stages of the filters are active. + // This will determine the slope of the filter. + int stages = 1; + + // Our series of filters for the left and right channels. + // They do the actual filtering of the audio. + FilterArray leftFilters; + FilterArray rightFilters; +}; + +//============================================================================== +// End of the namespaces +} // namespace effect +} // namespace dsp +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/dsp/envelope/AdhEnvelope.h b/src/dmt/dsp/envelope/AdhEnvelope.h new file mode 100644 index 0000000..2e184eb --- /dev/null +++ b/src/dmt/dsp/envelope/AdhEnvelope.h @@ -0,0 +1,210 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Get the options for the properties file with predefined settings. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "utility/Math.h" + +//============================================================================== + +namespace dmt { +namespace dsp { +namespace envelope { + +//============================================================================== + +/** + * @brief AHD Envelope Generator + * + * This class generates an Attack-Hold-Decay (AHD) envelope. + * It is optimized for real-time performance. + */ +class AhdEnvelope +{ +public: + struct Parameters + { + float attack = 0.015f; + float hold = 0.08f; + float decay = 0.5f; + + float attackSkew = 0; + float decaySkew = 10; + }; + + enum class State + { + Attack, + Hold, + Decay, + Idle + }; + + constexpr AhdEnvelope() noexcept = default; + + /** + * @brief Set the envelope parameters. + * @param _newParams The new parameters to set. + */ + inline void setParameters(const Parameters& _newParams) noexcept + { + params = _newParams; + } + + /** + * @brief Set the sample rate. + * @param _newSampleRate The new sample rate to set. + */ + inline void setSampleRate(const float _newSampleRate) noexcept + { + sampleRate = _newSampleRate; + } + + /** + * @brief Trigger the envelope to start. + */ + inline void noteOn() noexcept { sampleIndex = 0; } + + /** + * @brief Get the current state of the envelope. + * @return The current state. + */ + [[nodiscard]] inline State getState() const noexcept + { + if (sampleIndex < getHoldStart()) [[likely]] + return State::Attack; + if (sampleIndex < getDecayStart()) [[likely]] + return State::Hold; + if (sampleIndex < getDecayEnd()) [[likely]] + return State::Decay; + return State::Idle; + } + + /** + * @brief Get the next sample value of the envelope. + * @return The next sample value. + */ + [[nodiscard]] inline float getNextSample() noexcept + { + const auto state = getState(); + const float result = getValue(state); + ++sampleIndex; + return result; + } + +private: + /** + * @brief Get the value of the envelope based on its state. + * @param _state The current state of the envelope. + * @return The value of the envelope. + */ + [[nodiscard]] inline float getValue(const State _state) const noexcept + { + constexpr float one = 1.0f; + constexpr float zero = 0.0f; + + switch (_state) { + case State::Attack: { + const float normalizedPosition = + static_cast(sampleIndex) / sampleRate; + const float skew = getSkew(State::Attack); + return std::pow(normalizedPosition / params.attack, skew); + } + case State::Hold: + return one; + case State::Decay: { + const float decayStart = static_cast(getDecayStart()); + const float normalizedPosition = + (static_cast(sampleIndex) - decayStart) / sampleRate; + const float skew = getSkew(State::Decay); + return one - std::pow(normalizedPosition / params.decay, skew); + } + default: + return zero; + } + } + + /** + * @brief Get the skew value for the given state. + * @param _state The current state of the envelope. + * @return The skew value. + */ + [[nodiscard]] inline float getSkew(const State _state) const noexcept + { + switch (_state) { + case State::Attack: + return dmt::math::linearToExponent(params.attackSkew); + case State::Decay: + return dmt::math::linearToExponent(-params.decaySkew); + default: + return 1.0f; + } + } + + /** + * @brief Get the sample index where the hold phase starts. + * @return The sample index. + */ + [[nodiscard]] inline size_t getHoldStart() const noexcept + { + return static_cast(params.attack * sampleRate); + } + + /** + * @brief Get the sample index where the decay phase starts. + * @return The sample index. + */ + [[nodiscard]] inline size_t getDecayStart() const noexcept + { + const float rawDecayDelay = params.attack + params.hold; + return static_cast(rawDecayDelay * sampleRate) + 1; + } + + /** + * @brief Get the sample index where the decay phase ends. + * @return The sample index. + */ + [[nodiscard]] inline size_t getDecayEnd() const noexcept + { + const float rawDecayEnd = params.attack + params.hold + params.decay; + return static_cast(rawDecayEnd * sampleRate); + } + + float sampleRate = -1.0f; + Parameters params; + size_t sampleIndex = 0; +}; + +//============================================================================== + +} // namespace envelope +} // namespace dsp +} // namespace dmt + +//============================================================================== diff --git a/src/dmt/dsp/envelope/Envelope.h b/src/dmt/dsp/envelope/Envelope.h new file mode 100644 index 0000000..5135454 --- /dev/null +++ b/src/dmt/dsp/envelope/Envelope.h @@ -0,0 +1,34 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Envelope header file. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "./AdhEnvelope.h" + +//============================================================================== \ No newline at end of file diff --git a/src/dmt/dsp/filter/Filter.h b/src/dmt/dsp/filter/Filter.h new file mode 100644 index 0000000..238f438 --- /dev/null +++ b/src/dmt/dsp/filter/Filter.h @@ -0,0 +1,30 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * This file is part of the Dimethoxy Library, a collection of essential + * classes used across various Dimethoxy projects. + * These files are primarily designed for internal use within our repositories. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Filter header file. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== \ No newline at end of file diff --git a/src/dmt/dsp/synth/AnalogOscillator.h b/src/dmt/dsp/synth/AnalogOscillator.h new file mode 100644 index 0000000..6dc04b0 --- /dev/null +++ b/src/dmt/dsp/synth/AnalogOscillator.h @@ -0,0 +1,297 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * High-performance analog oscillator for real-time audio synthesis. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "AnalogWaveform.h" +#include + +//============================================================================== + +namespace dmt { +namespace dsp { +namespace synth { + +//============================================================================== + +/** + * @class AnalogOscillator + * @brief High-performance analog oscillator for real-time audio synthesis. + * + * This class is designed for maximum real-time performance, using aggressive + * optimizations such as constexpr, inline, noexcept, and forceinline. It + * generates analog waveforms with various modulation capabilities. + */ +class alignas(64) AnalogOscillator +{ + using Math = juce::dsp::FastMathApproximations; + static constexpr float twoPi = juce::MathConstants::twoPi; + static constexpr float pi = juce::MathConstants::pi; + +public: + //============================================================================== + /** + * @brief Sets the sample rate for the oscillator. + * @param _newSampleRate The new sample rate in Hz. + */ + inline void setSampleRate(const float _newSampleRate) noexcept + { + TRACER("AnalogOscillator::setSampleRate"); + float rangeEnd = + std::nextafter(392000.0f, std::numeric_limits::max()); + const juce::Range validRange(20.0f, rangeEnd); + jassert(validRange.contains(_newSampleRate)); + sampleRate = _newSampleRate; + } + + //============================================================================== + /** + * @brief Generates the next sample of the waveform. + * @return The next sample value. + */ + [[nodiscard]] forcedinline float getNextSample() noexcept + { + TRACER("AnalogOscillator::getNextSample"); + if (sampleRate <= 0.0f) + return 0.0f; + + advancePhase(); + + auto syncedPhase = getSyncedPhase(phase * pwmModifier); + auto bendedPhase = getBendedPhase(syncedPhase); + + if (phase >= twoPi / pwmModifier) + return 0.0f; + + float sample = waveform.getSample(bendedPhase); + distortSample(sample); + return std::clamp(sample, -1.0f, +1.0f); + } + + //============================================================================== + /** + * @brief Sets the frequency of the oscillator. + * @param _newFrequency The new frequency in Hz. + */ + inline void setFrequency(const float _newFrequency) noexcept + { + TRACER("AnalogOscillator::setFrequency"); + frequency = _newFrequency; + } + + //============================================================================== + /** + * @brief Sets the waveform type of the oscillator. + * @param _type The new waveform type. + */ + inline void setWaveformType( + const dmt::dsp::synth::AnalogWaveform::Type _type) noexcept + { + TRACER("AnalogOscillator::setWaveformType"); + waveform.type = _type; + } + + //============================================================================== + /** + * @brief Sets the drive level for waveform distortion. + * @param _newDrive The new drive level. + */ + inline void setDrive(const float _newDrive) noexcept + { + TRACER("AnalogOscillator::setDrive"); + drive = _newDrive; + } + + //============================================================================== + /** + * @brief Sets the bias level for waveform distortion. + * @param _newBias The new bias level. + */ + inline void setBias(const float _newBias) noexcept + { + TRACER("AnalogOscillator::setBias"); + bias = _newBias; + } + + //============================================================================== + /** + * @brief Sets the initial phase of the oscillator. + * @param _newPhase The new phase value. + */ + inline void setPhase(const float _newPhase) noexcept + { + TRACER("AnalogOscillator::setPhase"); + phase = _newPhase; + } + + //============================================================================== + /** + * @brief Sets the bend modifier for waveform shaping. + * @param _newBendModifier The new bend modifier value. + */ + inline void setBend(const float _newBendModifier) noexcept + { + TRACER("AnalogOscillator::setBend"); + float rangeEnd = std::nextafter(100.0f, std::numeric_limits::max()); + const juce::NormalisableRange sourceRange(-100.0f, rangeEnd); + jassert(sourceRange.getRange().contains(_newBendModifier)); + const auto normalisedValue = sourceRange.convertTo0to1(_newBendModifier); + const juce::NormalisableRange targetRange(0.1f, 0.9f); + posityCycleRatio = targetRange.convertFrom0to1(normalisedValue); + } + + //============================================================================== + /** + * @brief Sets the PWM (Pulse Width Modulation) modifier. + * @param _newPwmModifier The new PWM modifier value. + */ + inline void setPwm(const float _newPwmModifier) noexcept + { + TRACER("AnalogOscillator::setPwm"); + float rangeEnd = std::nextafter(100.0f, std::numeric_limits::max()); + const juce::NormalisableRange sourceRange(0.0f, rangeEnd); + jassert(sourceRange.getRange().contains(_newPwmModifier)); + const auto normalisedValue = sourceRange.convertTo0to1(_newPwmModifier); + const juce::NormalisableRange targetRange(1.0f, 5.0f); + pwmModifier = targetRange.convertFrom0to1(normalisedValue); + } + + //============================================================================== + /** + * @brief Sets the sync modifier for phase synchronization. + * @param _newSyncModifier The new sync modifier value. + */ + inline void setSync(const float _newSyncModifier) noexcept + { + TRACER("AnalogOscillator::setSync"); + float rangeEnd = std::nextafter(100.0f, std::numeric_limits::max()); + const juce::NormalisableRange sourceRange(0.0f, rangeEnd); + jassert(sourceRange.getRange().contains(_newSyncModifier)); + const auto normalisedValue = sourceRange.convertTo0to1(_newSyncModifier); + const juce::NormalisableRange targetRange(1.0f, 5.0f); + syncModifier = targetRange.convertFrom0to1(normalisedValue); + } + +private: + dmt::dsp::synth::AnalogWaveform waveform; + float frequency = 50.0f; + float sampleRate = -1.0f; + float phase = 0.0f; + + float drive = 0.0f; + float bias = 0.0f; + float pwmModifier = 1.0f; + float syncModifier = 1.0f; + float posityCycleRatio = 0.5f; + + //============================================================================== + /** + * @brief Advances the phase of the oscillator. + */ + forcedinline void advancePhase() noexcept + { + TRACER("AnalogOscillator::advancePhase"); + float cycleLength = sampleRate / frequency; + float phaseDelta = twoPi / cycleLength; + phase += phaseDelta; + + if (phase >= twoPi) { + phase -= twoPi; + } + } + + //============================================================================== + /** + * @brief Computes the synced phase based on the raw phase and sync modifier. + * @param _rawPhase The raw phase value. + * @return The synced phase value. + */ + forcedinline float getSyncedPhase(float _rawPhase) const noexcept + { + TRACER("AnalogOscillator::getSyncedPhase"); + float syncedPhase = _rawPhase * syncModifier; + while (syncedPhase >= twoPi) { + syncedPhase -= twoPi; + } + return syncedPhase; + } + + //============================================================================== + /** + * @brief Computes the bended phase based on the raw phase and bend modifier. + * @param _rawPhase The raw phase value. + * @return The bended phase value. + */ + forcedinline float getBendedPhase(float _rawPhase) const noexcept + { + TRACER("AnalogOscillator::getBendedPhase"); + auto bendedPhase = _rawPhase; + + float positiveCycleSize = posityCycleRatio * twoPi; + float negativeCycleRatio = 1.0f - posityCycleRatio; + float negativeCycleSize = negativeCycleRatio * twoPi; + + if (_rawPhase <= positiveCycleSize) { + bendedPhase /= (posityCycleRatio * 2.0f); + } + + if (_rawPhase > positiveCycleSize) { + bendedPhase = (_rawPhase - positiveCycleSize) / negativeCycleSize; + bendedPhase = bendedPhase * pi + pi; + } + return bendedPhase; + } + + //============================================================================== + /** + * @brief Applies distortion to the sample based on the drive and bias + * settings. + * @param _sample The sample value to be distorted. + */ + forcedinline void distortSample(float& _sample) const noexcept + { + TRACER("AnalogOscillator::distortSample"); + constexpr float magicNumber = 0.7615941559558f; + if (drive >= 1.0f) { + _sample = Math::tanh(drive * _sample); + } else { + float invertedDrive = 1.0f - drive; + float wetSample = drive * Math::tanh(_sample); + float drySample = invertedDrive * _sample * magicNumber; + _sample = wetSample + drySample; + } + + _sample = _sample + bias; + } +}; + +//============================================================================== +} // namespace synth +} // namespace dsp +} // namespace dmt diff --git a/src/dmt/dsp/synth/AnalogWaveform.h b/src/dmt/dsp/synth/AnalogWaveform.h new file mode 100644 index 0000000..a590cc4 --- /dev/null +++ b/src/dmt/dsp/synth/AnalogWaveform.h @@ -0,0 +1,153 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Get the options for the properties file with predefined settings. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include + +//============================================================================== + +namespace dmt { +namespace dsp { +namespace synth { + +//============================================================================== +/** + * @brief Represents different types of analog waveforms. + */ +struct AnalogWaveform +{ + static constexpr float twoPi = juce::MathConstants::twoPi; + static constexpr float pi = juce::MathConstants::pi; + + //============================================================================== + /** + * @brief Enumeration of waveform types. + */ + enum class Type + { + Sine, + Saw, + Triangle, + Square + }; + + static const inline juce::StringArray waveformNames = { "Sine", + "Saw", + "Triangle", + "Square" }; + + //============================================================================== + + Type type = Type::Sine; + + //============================================================================== + /** + * @brief Generate a triangle waveform sample. + * @param _x The phase of the waveform. + * @return The waveform sample. + */ + inline float triangle(float _x) const noexcept + { + while (_x > twoPi) + _x -= twoPi; + float result = 2.0f * (_x / twoPi - 0.5f); + if (result > 0.5f) + result = 1.0f - result; + if (result < -0.5f) + result = -1.0f - result; + return 2 * result; + } + + //============================================================================== + /** + * @brief Generate a saw waveform sample. + * @param _x The phase of the waveform. + * @return The waveform sample. + */ + inline float saw(float _x) const noexcept + { + while (_x > twoPi) + _x -= twoPi; + return 2.0f * (_x / twoPi - 0.5f); + } + + //============================================================================== + /** + * @brief Generate a sine waveform sample. + * @param _x The phase of the waveform. + * @return The waveform sample. + */ + inline float sine(float _x) const noexcept { return std::sin(_x); } + + //============================================================================== + /** + * @brief Generate a square waveform sample. + * @param _x The phase of the waveform. + * @return The waveform sample. + */ + inline float square(float _x) const noexcept + { + return (sine(_x) > 0.0f) ? 1.0f : -1.0f; + } + + //============================================================================== + /** + * @brief Get the waveform sample based on the current type. + * @param _x The phase of the waveform. + * @return The waveform sample. + */ + [[nodiscard]] inline float getSample(float _x) const noexcept + { + switch (type) { + case Type::Sine: + return sine(_x); + case Type::Saw: + return saw(_x); + case Type::Triangle: + return triangle(_x); + case Type::Square: + return square(_x); + default: + // impossible to reach this point, exit with assertion + jassert(false); + return 0.0f; + } + } + + //============================================================================== +}; + +//============================================================================== + +} // namespace synth +} // namespace dsp +} // namespace dmt + +//============================================================================== diff --git a/src/dmt/dsp/synth/Synth.h b/src/dmt/dsp/synth/Synth.h new file mode 100644 index 0000000..54b42c8 --- /dev/null +++ b/src/dmt/dsp/synth/Synth.h @@ -0,0 +1,37 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * This file is part of the Dimethoxy Library, a collection of essential + * classes used across various Dimethoxy projects. + * These files are primarily designed for internal use within our repositories. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Get the options for the properties file with predefined settings. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "./AnalogOscillator.h" +#include "./AnalogWaveform.h" +#include "./SynthSound.h" +#include "./SynthVoice.h" + +//============================================================================== \ No newline at end of file diff --git a/src/dmt/dsp/synth/SynthSound.h b/src/dmt/dsp/synth/SynthSound.h new file mode 100644 index 0000000..a9bd8c2 --- /dev/null +++ b/src/dmt/dsp/synth/SynthSound.h @@ -0,0 +1,59 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Get the options for the properties file with predefined settings. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include + +//============================================================================== + +namespace dmt { +namespace dsp { +namespace synth { + +//============================================================================== +/** + * @class SynthSound + * @brief Implementation of the juce::SynthesiserSound class. + */ +class SynthSound : public juce::SynthesiserSound +{ +public: + //============================================================================== + bool appliesToNote [[nodiscard]] (int) noexcept override { return true; } + bool appliesToChannel [[nodiscard]] (int) noexcept override { return true; } +}; + +//============================================================================== + +} // namespace synth +} // namespace dsp +} // namespace dmt + +//============================================================================== \ No newline at end of file diff --git a/src/dmt/dsp/synth/SynthVoice.h b/src/dmt/dsp/synth/SynthVoice.h new file mode 100644 index 0000000..0be0833 --- /dev/null +++ b/src/dmt/dsp/synth/SynthVoice.h @@ -0,0 +1,324 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Get the options for the properties file with predefined settings. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "dsp/envelope/AdhEnvelope.h" +#include "dsp/synth/AnalogOscillator.h" +#include + +//============================================================================== + +namespace dmt { +namespace dsp { +namespace synth { + +//============================================================================== + +/** + * @class SynthVoice + * @brief A class representing a synthesizer voice. + */ +class alignas(64) SynthVoice : public juce::SynthesiserVoice +{ +public: + //============================================================================== + /** + * @brief Constructor for SynthVoice. + * @param _apvts Reference to the AudioProcessorValueTreeState. + */ + SynthVoice(juce::AudioProcessorValueTreeState& _apvts) noexcept + : apvts(_apvts) + { + TRACER("SynthVoice::SynthVoice"); + } + + //============================================================================== + /** + * @brief Determines if the voice can play a given sound. + * @param _sound Pointer to the SynthesiserSound. + * @return True if the sound can be played, false otherwise. + */ + bool canPlaySound(juce::SynthesiserSound* _sound) override + { + TRACER("SynthVoice::canPlaySound"); + return dynamic_cast(_sound) != nullptr; + } + + //============================================================================== + /** + * @brief Handles the event when a controller is moved. + * + * This function is called when a controller is moved, providing the + * controller number and the new value of the controller. It is intended to be + * overridden by derived classes to implement specific behavior for controller + * movements. + * + * @param controllerNumber The number of the controller that was moved. + * @param newControllerValue The new value of the controller. + */ + void controllerMoved(int /*controllerNumber*/, + int /*newControllerValue*/) noexcept override + { + TRACER("SynthVoice::controllerMoved"); + } + + //============================================================================== + /** + * @brief Handles the event when the pitch wheel is moved. + * + * This function is called whenever the pitch wheel is moved. The new pitch + * wheel value is passed as an argument, but it is currently unused. + * + * @param newPitchWheelValue The new value of the pitch wheel. + */ + void pitchWheelMoved(int /*newPitchWheelValue*/) noexcept override + { + TRACER("SynthVoice::pitchWheelMoved"); + } + + //============================================================================== + /** + * @brief Prepares the voice to play. + * @param _sampleRate The sample rate. + * @param _samplesPerBlock Number of samples per block. + * @param _outputChannels Number of output channels. + */ + void prepareToPlay(double _sampleRate, + int /*_samplesPerBlock*/, + int /*_outputChannels*/) noexcept + { + TRACER("SynthVoice::prepareToPlay"); + if (_sampleRate <= 0) + return; + + gainEnvelope.setSampleRate(static_cast(_sampleRate)); + pitchEnvelope.setSampleRate(static_cast(_sampleRate)); + osc.setSampleRate(static_cast(_sampleRate)); + + isPrepared = true; + } + + //============================================================================== + /** + * @brief Starts a note. + * @param _midiNoteNumber The MIDI note number. + * @param _velocity The velocity of the note. + * @param _sound Pointer to the SynthesiserSound. + * @param _currentPitchWheelPosition The current pitch wheel position. + */ + void startNote(int _midiNoteNumber, + float /*_velocity*/, + juce::SynthesiserSound* /*_sound*/, + int /*_currentPitchWheelPosition*/) noexcept override + { + TRACER("SynthVoice::startNote"); + osc.setPhase(0.0f); + note = _midiNoteNumber; + + updateEnvelopeParameters(); + gainEnvelope.noteOn(); + pitchEnvelope.noteOn(); + + callOnNoteReceivers(); + } + + //============================================================================== + void stopNote(float /*_velocity*/, bool /*_allowTailOff*/) noexcept override + { + TRACER("SynthVoice::stopNote"); + } + + /** + * @brief Renders the next block of audio. + * @param _outputBuffer The output buffer. + * @param _startSample The start sample index. + * @param _numSamples The number of samples to render. + */ + void renderNextBlock(juce::AudioBuffer& _outputBuffer, + int _startSample, + int _numSamples) noexcept override + { + TRACER("SynthVoice::renderNextBlock"); + if (!isVoiceActive() || !isPrepared) + return; + + updateEnvelopeParameters(); + updateOscillatorParameters(); + + const float oscGain = + apvts.getRawParameterValue("osc1DistortionPreGain")->load(); + const int oscOctave = apvts.getRawParameterValue("osc1VoiceOctave")->load(); + const int oscSemitone = + apvts.getRawParameterValue("osc1VoiceSemitone")->load(); + const float oscModDepth = + apvts.getRawParameterValue("osc1PitchEnvDepth")->load(); + + const auto endSample = _numSamples + _startSample; + auto* leftChannel = _outputBuffer.getWritePointer(0); + auto* rightChannel = _outputBuffer.getWritePointer(1); + + for (int sample = _startSample; sample < endSample; ++sample) { + osc.setFrequency(getNextFrequency(oscOctave, oscSemitone, oscModDepth)); + const auto rawSample = osc.getNextSample(); + const auto gainedSample = applyGain(rawSample, oscGain); + leftChannel[sample] = gainedSample; + rightChannel[sample] = gainedSample; + } + } + + //============================================================================== + /** + * @brief Adds a callback function to be called when a note is received. + * @param _callbackFunction The callback function. + */ + void addOnNoteReceivers(std::function _callbackFunction) noexcept + { + TRACER("SynthVoice::addOnNoteReceivers"); + onNoteReceivers.push_back(std::move(_callbackFunction)); + } + + //============================================================================== + /** + * @brief Calls all registered note receiver callback functions. + */ + void callOnNoteReceivers() noexcept + { + TRACER("SynthVoice::callOnNoteReceivers"); + for (const auto& func : onNoteReceivers) { + func(); + } + } + +protected: + //============================================================================== + /** + * @brief Updates the envelope parameters from the + * AudioProcessorValueTreeState. + */ + void updateEnvelopeParameters() noexcept + { + TRACER("SynthVoice::updateEnvelopeParameters"); + dmt::dsp::envelope::AhdEnvelope::Parameters gainEnvParameters; + gainEnvParameters.attack = + apvts.getRawParameterValue("osc1GainEnvAttack")->load(); + gainEnvParameters.hold = + apvts.getRawParameterValue("osc1GainEnvHold")->load(); + gainEnvParameters.decay = + apvts.getRawParameterValue("osc1GainEnvDecay")->load(); + gainEnvParameters.decaySkew = + apvts.getRawParameterValue("osc1GainEnvSkew")->load(); + gainEnvParameters.attackSkew = 0; + gainEnvelope.setParameters(gainEnvParameters); + + dmt::dsp::envelope::AhdEnvelope::Parameters pitchEnvParameters; + pitchEnvParameters.attack = 0; + pitchEnvParameters.hold = + apvts.getRawParameterValue("osc1PitchEnvHold")->load(); + pitchEnvParameters.decay = + apvts.getRawParameterValue("osc1PitchEnvDecay")->load(); + pitchEnvParameters.decaySkew = + apvts.getRawParameterValue("osc1PitchEnvSkew")->load(); + pitchEnvParameters.attackSkew = 0; + pitchEnvelope.setParameters(pitchEnvParameters); + } + + //============================================================================== + /** + * @brief Updates the oscillator parameters from the + * AudioProcessorValueTreeState. + */ + void updateOscillatorParameters() noexcept + { + TRACER("SynthVoice::updateOscillatorParameters"); + osc.setWaveformType(static_cast( + apvts.getRawParameterValue("osc1WaveformType")->load())); + osc.setDrive(apvts.getRawParameterValue("osc1DistortionType")->load()); + osc.setBias(apvts.getRawParameterValue("osc1DistortionSymmetry")->load()); + osc.setBend(apvts.getRawParameterValue("osc1WaveformBend")->load()); + osc.setPwm(apvts.getRawParameterValue("osc1WaveformPwm")->load()); + osc.setSync(apvts.getRawParameterValue("osc1WaveformSync")->load()); + } + + //============================================================================== + /** + * @brief Calculates the next frequency for the oscillator. + * @param _rawOctave The raw octave value. + * @param _rawSemitone The raw semitone value. + * @param _rawModDepth The raw modulation depth. + * @return The next frequency. + */ + [[nodiscard]] float getNextFrequency(const int _rawOctave, + const int _rawSemitone, + const float _rawModDepth) noexcept + { + TRACER("SynthVoice::getNextFrequency"); + const int octaves = 12 * _rawOctave; + const int semitone = octaves + _rawSemitone; + const int baseNote = note + semitone; + const float baseFreq = juce::MidiMessage::getMidiNoteInHertz(baseNote); + const float modDepth = _rawModDepth * 2e4f; + const float envelopeSample = pitchEnvelope.getNextSample(); + const float maxFreq = std::clamp(baseFreq + modDepth, baseFreq, 2e4f); + const float newFreq = juce::mapToLog10(envelopeSample, baseFreq, maxFreq); + return std::clamp(newFreq, 20.0f, 2e4f); + } + + //============================================================================== + /** + * @brief Applies gain to a sample. + * @param _sample The input sample. + * @param _oscGain The oscillator gain. + * @return The gained sample. + */ + [[nodiscard]] float applyGain(float _sample, float _oscGain) noexcept + { + TRACER("SynthVoice::applyGain"); + const float envGain = gainEnvelope.getNextSample(); + const float gain = juce::Decibels::decibelsToGain(_oscGain, -96.0f); + return _sample * envGain * gain; + } + +private: + juce::AudioProcessorValueTreeState& apvts; + dmt::dsp::synth::AnalogOscillator osc; + dmt::dsp::envelope::AhdEnvelope gainEnvelope; + dmt::dsp::envelope::AhdEnvelope pitchEnvelope; + int note = 0; + bool isPrepared = false; + std::vector> onNoteReceivers; +}; + +//============================================================================== + +} // namespace synth +} // namespace dsp +} // namespace dmt + +//============================================================================== diff --git a/src/dmt/fonts/poppins/Poppins-Black.ttf b/src/dmt/fonts/poppins/Poppins-Black.ttf new file mode 100644 index 0000000..a9520b7 Binary files /dev/null and b/src/dmt/fonts/poppins/Poppins-Black.ttf differ diff --git a/src/dmt/fonts/poppins/Poppins-BlackItalic.ttf b/src/dmt/fonts/poppins/Poppins-BlackItalic.ttf new file mode 100644 index 0000000..ebfdd70 Binary files /dev/null and b/src/dmt/fonts/poppins/Poppins-BlackItalic.ttf differ diff --git a/src/dmt/fonts/poppins/Poppins-Bold.ttf b/src/dmt/fonts/poppins/Poppins-Bold.ttf new file mode 100644 index 0000000..b94d47f Binary files /dev/null and b/src/dmt/fonts/poppins/Poppins-Bold.ttf differ diff --git a/src/dmt/fonts/poppins/Poppins-BoldItalic.ttf b/src/dmt/fonts/poppins/Poppins-BoldItalic.ttf new file mode 100644 index 0000000..e2e6445 Binary files /dev/null and b/src/dmt/fonts/poppins/Poppins-BoldItalic.ttf differ diff --git a/src/dmt/fonts/poppins/Poppins-ExtraBold.ttf b/src/dmt/fonts/poppins/Poppins-ExtraBold.ttf new file mode 100644 index 0000000..8f008c3 Binary files /dev/null and b/src/dmt/fonts/poppins/Poppins-ExtraBold.ttf differ diff --git a/src/dmt/fonts/poppins/Poppins-ExtraBoldItalic.ttf b/src/dmt/fonts/poppins/Poppins-ExtraBoldItalic.ttf new file mode 100644 index 0000000..b2a9bf5 Binary files /dev/null and b/src/dmt/fonts/poppins/Poppins-ExtraBoldItalic.ttf differ diff --git a/src/dmt/fonts/poppins/Poppins-ExtraLight.ttf b/src/dmt/fonts/poppins/Poppins-ExtraLight.ttf new file mode 100644 index 0000000..ee62382 Binary files /dev/null and b/src/dmt/fonts/poppins/Poppins-ExtraLight.ttf differ diff --git a/src/dmt/fonts/poppins/Poppins-ExtraLightItalic.ttf b/src/dmt/fonts/poppins/Poppins-ExtraLightItalic.ttf new file mode 100644 index 0000000..e392492 Binary files /dev/null and b/src/dmt/fonts/poppins/Poppins-ExtraLightItalic.ttf differ diff --git a/src/dmt/fonts/poppins/Poppins-Italic.ttf b/src/dmt/fonts/poppins/Poppins-Italic.ttf new file mode 100644 index 0000000..4620399 Binary files /dev/null and b/src/dmt/fonts/poppins/Poppins-Italic.ttf differ diff --git a/src/dmt/fonts/poppins/Poppins-Light.ttf b/src/dmt/fonts/poppins/Poppins-Light.ttf new file mode 100644 index 0000000..2ab0221 Binary files /dev/null and b/src/dmt/fonts/poppins/Poppins-Light.ttf differ diff --git a/src/dmt/fonts/poppins/Poppins-LightItalic.ttf b/src/dmt/fonts/poppins/Poppins-LightItalic.ttf new file mode 100644 index 0000000..6f9279d Binary files /dev/null and b/src/dmt/fonts/poppins/Poppins-LightItalic.ttf differ diff --git a/src/dmt/fonts/poppins/Poppins-Medium.ttf b/src/dmt/fonts/poppins/Poppins-Medium.ttf new file mode 100644 index 0000000..e90e87e Binary files /dev/null and b/src/dmt/fonts/poppins/Poppins-Medium.ttf differ diff --git a/src/dmt/fonts/poppins/Poppins-MediumItalic.ttf b/src/dmt/fonts/poppins/Poppins-MediumItalic.ttf new file mode 100644 index 0000000..d8a251c Binary files /dev/null and b/src/dmt/fonts/poppins/Poppins-MediumItalic.ttf differ diff --git a/src/dmt/fonts/poppins/Poppins-Regular.ttf b/src/dmt/fonts/poppins/Poppins-Regular.ttf new file mode 100644 index 0000000..be06e7f Binary files /dev/null and b/src/dmt/fonts/poppins/Poppins-Regular.ttf differ diff --git a/src/dmt/fonts/poppins/Poppins-SemiBold.ttf b/src/dmt/fonts/poppins/Poppins-SemiBold.ttf new file mode 100644 index 0000000..dabf7c2 Binary files /dev/null and b/src/dmt/fonts/poppins/Poppins-SemiBold.ttf differ diff --git a/src/dmt/fonts/poppins/Poppins-SemiBoldItalic.ttf b/src/dmt/fonts/poppins/Poppins-SemiBoldItalic.ttf new file mode 100644 index 0000000..29d5f74 Binary files /dev/null and b/src/dmt/fonts/poppins/Poppins-SemiBoldItalic.ttf differ diff --git a/src/dmt/fonts/poppins/Poppins-Thin.ttf b/src/dmt/fonts/poppins/Poppins-Thin.ttf new file mode 100644 index 0000000..f5c0fdd Binary files /dev/null and b/src/dmt/fonts/poppins/Poppins-Thin.ttf differ diff --git a/src/dmt/fonts/poppins/Poppins-ThinItalic.ttf b/src/dmt/fonts/poppins/Poppins-ThinItalic.ttf new file mode 100644 index 0000000..b910089 Binary files /dev/null and b/src/dmt/fonts/poppins/Poppins-ThinItalic.ttf differ diff --git a/src/dmt/fonts/sedgwick_ave_display/SedgwickAveDisplay-Regular.ttf b/src/dmt/fonts/sedgwick_ave_display/SedgwickAveDisplay-Regular.ttf new file mode 100644 index 0000000..846fdf8 Binary files /dev/null and b/src/dmt/fonts/sedgwick_ave_display/SedgwickAveDisplay-Regular.ttf differ diff --git a/src/dmt/gui/Gui.h b/src/dmt/gui/Gui.h new file mode 100644 index 0000000..3dab4b5 --- /dev/null +++ b/src/dmt/gui/Gui.h @@ -0,0 +1,36 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Gui header file. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "./component/Component.h" +#include "./display/Display.h" +#include "./panel/Panel.h" +#include "./widget/Widget.h" +#include "./window/Window.h" diff --git a/src/dmt/gui/component/AbstractSliderComponent.h b/src/dmt/gui/component/AbstractSliderComponent.h new file mode 100644 index 0000000..d9ee4b1 --- /dev/null +++ b/src/dmt/gui/component/AbstractSliderComponent.h @@ -0,0 +1,145 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * AbstractSliderComponent provides a base class for slider components with + * parameter binding, labels, and context menu support. It encapsulates common + * logic for displaying the parameter value with units and showing the host + * context menu. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "gui/widget/Label.h" +#include "utility/Fonts.h" +#include "utility/HostContextMenu.h" +#include "utility/Settings.h" +#include "utility/Unit.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace component { + +//============================================================================== +/** + * @brief Abstract base class for slider components with parameter binding, + * labels, and context menu. + * + * @details + * Encapsulates common logic for parameter binding, title/info labels, unit + * display, and context menu. Derived classes should implement layout, + * painting, and slider instantiation. + */ +template +class AbstractSliderComponent + : public juce::Component + , public juce::Slider::Listener + , public dmt::Scaleable + , public dmt::HostContextMenu +{ +protected: + using SliderAttachment = juce::AudioProcessorValueTreeState::SliderAttachment; + using Unit = dmt::utility::Unit; + using Label = dmt::gui::widget::Label; + using Fonts = dmt::utility::Fonts; + using Settings = dmt::Settings; + using SliderSettings = Settings::Slider; + using RangedAudioParameter = juce::RangedAudioParameter; + + // Settings references + const float& baseWidth = SliderSettings::baseWidth; + const float& baseHeight = SliderSettings::baseHeight; + const float& rawSliderSize = SliderSettings::sliderSize; + const float& rawLabelsSize = SliderSettings::labelsSize; + const float& rawPadding = SliderSettings::padding; + const juce::Colour& titleFontColour = SliderSettings::titleFontColour; + const juce::Colour& infoFontColour = SliderSettings::infoFontColour; + const float& titleFontSize = SliderSettings::titleFontSize; + const float& infoFontSize = SliderSettings::infoFontSize; + +public: + /** + * @brief Constructs the abstract slider component. + * @param _apvts Reference to the APVTS for parameter binding. + * @param _text Title label text. + * @param _param Parameter ID to bind. + * @param _unitType Unit type for value display. + */ + inline explicit AbstractSliderComponent( + juce::AudioProcessorValueTreeState& _apvts, + const juce::String& _text, + const juce::String& _param, + const Unit::Type _unitType) + : titleLabel(_text, fonts.medium, titleFontSize, titleFontColour) + , infoLabel(juce::String("Info Label"), + fonts.light, + infoFontSize, + infoFontColour, + juce::Justification::centredBottom) + , unitType(_unitType) + , fonts() + { + parameter = _apvts.getParameter(_param); + addAndMakeVisible(titleLabel); + addAndMakeVisible(infoLabel); + } + + /** + * @brief Pointer to the attached parameter. + */ + RangedAudioParameter* parameter = nullptr; + + /** + * @brief Shows the context menu for the attached parameter. + */ + inline void showContextMenuForSlider() noexcept + { + this->showContextMenu(parameter); + } + + /** + * @brief Updates the info label with the current slider value and unit. + * @param value The value to display. + */ + inline void updateLabel(float value) noexcept + { + auto text = Unit::getString(unitType, value); + infoLabel.setText(text); + infoLabel.repaint(); + } + +protected: + Label titleLabel; + Label infoLabel; + Unit::Type unitType; + Fonts fonts; +}; + +} // namespace component +} // namespace gui +} // namespace dmt diff --git a/src/dmt/gui/component/Component.h b/src/dmt/gui/component/Component.h new file mode 100644 index 0000000..5f73c82 --- /dev/null +++ b/src/dmt/gui/component/Component.h @@ -0,0 +1,35 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Component header file. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "./LinearSliderComponent.h" +#include "./OscillatorDisplayComponent.h" +#include "./RotarySliderComponent.h" +#include "./SettingsEditorComponent.h" \ No newline at end of file diff --git a/src/dmt/gui/component/LinearSliderComponent.h b/src/dmt/gui/component/LinearSliderComponent.h new file mode 100644 index 0000000..8b67b65 --- /dev/null +++ b/src/dmt/gui/component/LinearSliderComponent.h @@ -0,0 +1,329 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * LinearSliderComponent provides a composite slider UI element with + * optional SVG title, parameter binding, and context menu support. + * Designed for real-time audio plugin GUIs. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "gui/component/AbstractSliderComponent.h" +#include "gui/widget/Label.h" +#include "gui/widget/LinearSlider.h" +#include "utility/Fonts.h" +#include "utility/HostContextMenu.h" +#include "utility/Icon.h" +#include "utility/Settings.h" +#include "utility/Unit.h" +#include + +//============================================================================== + +// Slider graphics and labels are always included; remove conditional macros + +//============================================================================== + +namespace dmt { +namespace gui { +namespace component { + +//============================================================================== +/** + * @brief Composite slider component with optional SVG title and parameter + * binding. + * + * @details + * This component encapsulates a slider, title label (or SVG icon), and info + * label. It supports parameter attachment, context menu integration, and + * flexible layout. Designed for use in real-time audio plugin GUIs, with a + * focus on performance and type safety. The component is non-copyable and + * leak-detectable. + */ +class LinearSliderComponent + : public AbstractSliderComponent +{ + using LinearSlider = dmt::gui::widget::LinearSlider; + using Type = LinearSlider::Type; + using Orientation = LinearSlider::Orientation; + using SliderAttachment = juce::AudioProcessorValueTreeState::SliderAttachment; + +public: + /** + * @brief Constructs a LinearSliderComponent. + * + * @param _apvts Reference to the AudioProcessorValueTreeState for parameter + * binding. + * @param _text The title text for the slider. + * @param _param The parameter ID to bind. + * @param _unitType The unit type for value display. + * @param _type The slider type (default: Positive). + * @param _orientation The slider orientation (default: Horizontal). + * @param _svgTitle If true, uses an SVG icon as the title. + * + * @details + * The constructor sets up the slider, labels, parameter attachment, and + * context menu. SVG title mode disables the text label and uses an icon + * instead. + */ + inline explicit LinearSliderComponent( + juce::AudioProcessorValueTreeState& _apvts, + const juce::String _text, + const juce::String _param, + const Unit::Type _unitType, + const Type _type = Type::Positive, + const Orientation _orientation = Orientation::Horizontal, + const bool _svgTitle = false) noexcept + : AbstractSliderComponent(_apvts, _text, _param, _unitType) + , orientation(_orientation) + , slider(_type, _orientation) + , sliderAttachment(_apvts, _param, slider) + , svgTitle(_svgTitle) + , svgPadding(dmt::icons::getPadding(_param)) + { + TRACER("LinearSliderComponent::LinearSliderComponent"); + slider.addListener(this); + updateLabel(static_cast(slider.getValue())); + addAndMakeVisible(slider); + addAndMakeVisible(this->infoLabel); + + if (_svgTitle) { + titleIcon = dmt::icons::getIcon(_param); + this->titleLabel.setVisible(false); + } + + this->parameter = _apvts.getParameter(_param); + + slider.onContextMenuRequested = [this]() { + this->showContextMenuForSlider(); + }; + } + + /** + * @brief Lays out the child components. + * + * @details + * Arranges the slider, title label or icon, and info label according to + * orientation. Padding and font sizes are scaled for DPI awareness. + */ + inline void resized() override + { + TRACER("LinearSliderComponent::resized"); + const auto bounds = getLocalBounds(); + const auto padding = rawPadding * this->size; + + slider.setAlwaysOnTop(true); + switch (orientation) { + case Orientation::Horizontal: { + layoutHorizontal(bounds, padding); + break; + } + case Orientation::Vertical: { + layoutVertical(bounds, padding); + break; + } + } + } + + /** + * @brief Lays out the child components for horizontal orientation. + * + * @param bounds The bounds of the component. + * @param padding The padding to apply around the components. + * + * @details + * Arranges the slider in the center with title and info labels above and + * below, respectively. Padding and font sizes are scaled for DPI awareness. + */ + void layoutHorizontal(juce::Rectangle bounds, float padding) noexcept + { + // Calculate the center point and offset for the slider + const int rawHorizontalSliderOffset = static_cast(this->size); + const juce::Point offset(0, rawHorizontalSliderOffset); + const auto centre = bounds.getCentre() + offset; + + // Main slider bounds + auto sliderBounds = + bounds.reduced(static_cast(padding)).withCentre(centre); + slider.setBounds( + bounds.reduced(static_cast(padding)).withCentre(centre)); + + // Title label bounds + auto titleLabelBounds = sliderBounds; + const auto titleLabelHeight = + static_cast(2 * titleFontSize * this->size); + const auto titleLabelOffset = static_cast(4 * this->size); + const auto titleSliderBounds = + titleLabelBounds.removeFromTop(titleLabelHeight) + .reduced(titleLabelOffset); + this->titleLabel.setBounds(titleSliderBounds); + + // Info label bounds + auto infoLabelBounds = sliderBounds; + const auto infoLabelHeight = + static_cast(2 * infoFontSize * this->size); + const auto infoLabelOffset = static_cast(9 * this->size); + const auto infoSliderBounds = + infoLabelBounds.removeFromBottom(infoLabelHeight) + .reduced(infoLabelOffset); + this->infoLabel.setBounds(infoSliderBounds); + } + + /** + * @brief Lays out the child components for vertical orientation. + * + * @param bounds The bounds of the component. + * @param padding The padding to apply around the components. + * + * @details + * Arranges the slider in the center with title and info labels above and + * below, respectively. Padding and font sizes are scaled for DPI awareness. + */ + void layoutVertical(juce::Rectangle bounds, float padding) noexcept + { + this->titleLabel.setBounds( + bounds.withTrimmedTop(static_cast(padding))); + this->infoLabel.setBounds( + bounds.withTrimmedBottom(static_cast(padding))); + + auto sliderBounds = bounds; + sliderBounds.removeFromTop( + static_cast(titleFontSize * this->size + padding)); + sliderBounds.removeFromBottom( + static_cast(infoFontSize * this->size + padding)); + slider.setBounds(sliderBounds); + } + + /** + * @brief Paints the component, including optional SVG icon. + * + * @param _g The graphics context. + * + * @details + * Draws debug bounds if enabled. If SVG title mode is active, draws the icon + * in the title area with appropriate padding. + */ + inline void paint(juce::Graphics& _g) override + { + TRACER("LinearSliderComponent::paint"); + auto bounds = getLocalBounds(); + + // Draw bounds debug + _g.setColour(juce::Colours::green); + if (Settings::debugBounds) + _g.drawRect(bounds, 1); + + constexpr float baseSvgPadding = 2.0f; + if (svgTitle) { + juce::Rectangle iconArea = + bounds.removeFromTop(slider.getY()).toFloat(); + iconArea = iconArea.withY(iconArea.getY() + 6.0f * this->size); + iconArea = iconArea.reduced((svgPadding + baseSvgPadding) * this->size); + titleIcon->drawWithin( + _g, iconArea, juce::RectanglePlacement::centred, 1.0f); + } + } + + /** + * @brief Called when the slider value changes. + * + * @param _slider The slider that changed (unused). + * @details Updates the info label to reflect the new value. + */ + inline void sliderValueChanged(juce::Slider* /*_slider*/) override + { + TRACER("LinearSliderComponent::sliderValueChanged"); + updateLabel(static_cast(slider.getValue())); + } + + /** + * @brief Sets the bounds of the component based on two points. + * + * @param _primaryPoint The first anchor point. + * @param _secondaryPoint The second anchor point. + * + * @details + * Used for interactive placement or resizing. Ensures minimum size and + * orientation-specific layout. + */ + inline void setBoundsByPoints(juce::Point _primaryPoint, + juce::Point _secondaryPoint) noexcept + { + TRACER("LinearSliderComponent::setBoundsByPoints"); + const float padding = 2.0f * rawPadding * this->size; + const float minHeight = 50 * this->size; + const float minWidth = 40 * this->size; + + const auto centre = (_primaryPoint + _secondaryPoint).toFloat() / 2.0f; + const int pointDistance = _primaryPoint.getDistanceFrom(_secondaryPoint); + + switch (orientation) { + case Orientation::Horizontal: { + setBounds(juce::Rectangle() + .withSize(pointDistance, static_cast(minHeight)) + .expanded(static_cast(padding)) + .withCentre(centre.toInt())); + return; + } + case Orientation::Vertical: { + setBounds(juce::Rectangle() + .withSize(static_cast(minWidth), pointDistance) + .expanded(static_cast(padding)) + .withCentre(centre.toInt())); + return; + } + } + } + + /** + * @brief Returns a reference to the internal slider. + * + * @return Reference to the LinearSlider. + * + * @details + * Allows external code to access or customize the slider directly. + */ + [[nodiscard]] inline juce::Slider& getSlider() noexcept + { + TRACER("LinearSliderComponent::getSlider"); + return slider; + } + +private: + LinearSlider slider; + SliderAttachment sliderAttachment; + Orientation orientation; + const bool svgTitle; + const float svgPadding; + std::unique_ptr titleIcon; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(LinearSliderComponent) +}; +//============================================================================== +} // namespace component +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/component/OscillatorDisplayComponent.h b/src/dmt/gui/component/OscillatorDisplayComponent.h new file mode 100644 index 0000000..5bc25d8 --- /dev/null +++ b/src/dmt/gui/component/OscillatorDisplayComponent.h @@ -0,0 +1,159 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * OscillatorDisplayComponent provides a real-time visualization of an + * oscillator's waveform. It uses a lookup table to efficiently render the + * waveform based on the current oscillator parameters. The component supports + * customizable shadows for enhanced visual appeal and is designed to be + * responsive to parameter changes, updating the display accordingly. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "dsp/synth/AnalogOscillator.h" +#include "gui/widget/Shadow.h" +#include "utility/Settings.h" +#include + +//============================================================================== +namespace dmt { +namespace gui { +namespace component { +//============================================================================== +// TODO: Make this use the new display system +class OscillatorDisplayComponent + : public juce::Component + , public juce::Timer + , public dmt::Scaleable +{ + using Shadow = dmt::gui::widget::Shadow; + using AnalogOscillator = dmt::dsp::synth::AnalogOscillator; + + //============================================================================ + // General + using Settings = dmt::Settings::OscillatorDisplay; + const int& fps = dmt::Settings::framerate; + const int& resolution = Settings::resolution; + + // Shadows + const bool& drawOuterShadow = Settings::drawOuterShadow; + const bool& drawInnerShadow = Settings::drawInnerShadow; + const juce::Colour& outerShadowColour = Settings::outerShadowColour; + const juce::Colour& innerShadowColour = Settings::innerShadowColour; + const float& outerShadowRadius = Settings::outerShadowRadius; + const float& innerShadowRadius = Settings::innerShadowRadius; + +public: + //============================================================================ + OscillatorDisplayComponent(juce::AudioProcessorValueTreeState& apvts) + : apvts(apvts) + { + TRACER("OscillatorDisplayComponent::OscillatorDisplayComponent"); + osc.setSampleRate((float)resolution + 1.0f); + osc.setFrequency(1.0f); + startTimerHz(60); + } + //============================================================================ + void paint(juce::Graphics& g) override + { + TRACER("OscillatorDisplayComponent::paint"); + const auto bounds = this->getLocalBounds().toFloat(); + } + +protected: + //============================================================================== + void timerCallback() + { + TRACER("OscillatorDisplayComponent::timerCallback"); + if (isParametersChanged()) { + this->buildTable(); + this->repaint(); + } + } + + void buildTable() + { + TRACER("OscillatorDisplayComponent::buildTable"); + osc.setPhase(0.0f); + table.initialise( + [&](std::size_t index) { return (float)osc.getNextSample(); }, + resolution); + } + + bool isParametersChanged() + { + TRACER("OscillatorDisplayComponent::isParametersChanged"); + } + + //============================================================================== + juce::Path getPath(juce::Rectangle bounds) + { + TRACER("OscillatorDisplayComponent::getPath"); + bounds.setY(bounds.getY() + (bounds.getHeight() / 10.0f)); + bounds.setHeight(bounds.getHeight() - (bounds.getHeight() / 5.0f)); + + auto outerBounds = bounds; + bounds = bounds.reduced(bounds.getWidth() / 6.0f); + + juce::Path path; + + auto startX = bounds.getX(); + auto startY = bounds.getY() + (bounds.getHeight() / 2.0f); + juce::Point start(startX, startY); + path.startNewSubPath(start); + + auto width = bounds.getWidth(); + + for (size_t i = 0; i < width; i++) { + auto x = bounds.getX() + i; + auto y = bounds.getY() + (bounds.getHeight() / 2.0f) - + (table[i / width * resolution] * bounds.getHeight() / 2.0f); + juce::Point p(x, y); + path.lineTo(p); + } + + auto endX = bounds.getX() + bounds.getWidth(); + auto endY = bounds.getY() + (bounds.getHeight() / 2.0f); + juce::Point end(endX, endY); + path.lineTo(end); + + return path; + } + //============================================================================ +private: + Shadow outerShadow = + Shadow(drawOuterShadow, outerShadowColour, outerShadowRadius, false); + Shadow innerShadow = + Shadow(drawInnerShadow, innerShadowColour, innerShadowRadius, true); + + AnalogOscillator osc; + juce::dsp::LookupTable table; + juce::AudioProcessorValueTreeState& apvts; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OscillatorDisplayComponent) +}; +} // namespace components +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/component/RotarySliderComponent.h b/src/dmt/gui/component/RotarySliderComponent.h new file mode 100644 index 0000000..538e490 --- /dev/null +++ b/src/dmt/gui/component/RotarySliderComponent.h @@ -0,0 +1,203 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * RotarySliderComponent provides a composite widget for rotary slider controls, + * including parameter binding, context menu, and value display. Designed for + * real-time audio plugin GUIs with strict performance and type safety. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "gui/component/AbstractSliderComponent.h" +#include "gui/widget/Label.h" +#include "gui/widget/RotarySlider.h" +#include "utility/Fonts.h" +#include "utility/HostContextMenu.h" +#include "utility/Icon.h" +#include "utility/Settings.h" +#include "utility/Unit.h" +#include + +//============================================================================== + +// Slider graphics and labels are always included; remove conditional macros + +//============================================================================== + +namespace dmt { +namespace gui { +namespace component { + +//============================================================================== +/** + * @brief Composite rotary slider component with parameter binding and context + * menu. + * + * @details + * This class encapsulates a rotary slider, a title label, and an info label + * displaying the current value with units. It binds to a parameter in a + * JUCE AudioProcessorValueTreeState for real-time automation and supports + * host context menus for parameter management. + * + * Intended for use in audio plugin GUIs where real-time safety and + * responsiveness are critical. All layout and painting are optimized for + * performance and clarity. + */ +class RotarySliderComponent + : public AbstractSliderComponent +{ + using RotarySlider = dmt::gui::widget::RotarySlider; + using Type = RotarySlider::Type; + using SliderAttachment = juce::AudioProcessorValueTreeState::SliderAttachment; + +public: + /** + * @brief Constructs a RotarySliderComponent with parameter binding. + * + * @param _apvts Reference to the AudioProcessorValueTreeState for parameter + * binding. + * @param _text The label text for the slider. + * @param _param The parameter ID to bind the slider to. + * @param _unitType The unit type for value display. + * @param _type The rotary slider type (default: Positive). + * + * @details + * The constructor sets up the slider, attaches it to the parameter, + * initializes labels, and configures the context menu callback. + * All layout and font settings are derived from the Settings singleton. + */ + inline explicit RotarySliderComponent( + juce::AudioProcessorValueTreeState& _apvts, + const juce::String _text, + const juce::String _param, + const Unit::Type _unitType, + const Type _type = Type::Positive) noexcept + : AbstractSliderComponent(_apvts, _text, _param, _unitType) + , slider(_type) + , sliderAttachment(_apvts, _param, slider) + { + TRACER("RotarySliderComponent::RotarySliderComponent"); + slider.addListener(this); + updateLabel(static_cast(slider.getValue())); + addAndMakeVisible(slider); + // Set up context menu callback for host automation and parameter actions + slider.onContextMenuRequested = [this]() { + this->showContextMenuForSlider(); + }; + } + + /** + * @brief Handles component resizing and lays out child widgets. + * + * @details + * Calculates bounds for the slider and labels based on current scaling and + * padding. Ensures all subcomponents are correctly positioned and sized. + */ + inline void resized() override + { + TRACER("RotarySliderComponent::resized"); + const auto bounds = getLocalBounds(); + const auto padding = rawPadding * this->size; + auto sliderBounds = bounds; + const auto sliderSize = + static_cast(rawSliderSize * sliderBounds.getHeight()); + slider.setBounds(sliderBounds.removeFromTop(sliderSize)); + const int labelPadding = static_cast(padding * 0.5f); + auto labelsBounds = bounds.reduced(labelPadding); + const auto labelsSize = + static_cast(rawLabelsSize * labelsBounds.getHeight()); + labelsBounds = labelsBounds.removeFromBottom(labelsSize); + this->titleLabel.setBounds(labelsBounds); + this->infoLabel.setBounds(labelsBounds); + } + + /** + * @brief Paints debug bounds and center marker if enabled. + * + * @param _g The graphics context. + * + * @details + * Only draws debug outlines and center marker if Settings::debugBounds is + * true. All other painting is handled by subcomponents for maximum + * flexibility. + */ + inline void paint(juce::Graphics& _g) override + { + TRACER("RotarySliderComponent::paint"); + auto bounds = getLocalBounds(); + _g.setColour(juce::Colours::green); + if (Settings::debugBounds) { + _g.drawRect(bounds, 1); + _g.setColour(juce::Colours::green); + _g.drawEllipse(bounds.getCentreX(), bounds.getCentreY(), 5, 5, 1); + } + } + + /** + * @brief Updates the info label when the slider value changes. + * + * @param _slider The slider that changed (unused). + * + * @details + * This method is called by the slider listener. + * It updates the info label to reflect the new value, + * formatted with the appropriate unit. + */ + inline void sliderValueChanged(juce::Slider* /*_slider*/) override + { + TRACER("RotarySliderComponent::sliderValueChanged"); + updateLabel(static_cast(slider.getValue())); + } + + /** + * @brief Sets the size and center position of the component. + * + * @param _centrePoint The point to center the component on. + * + * @details + * Calculates the size based on scaling and centers the component at the + * specified point. Used for dynamic layout in parent containers. + */ + inline void setSizeAndCentre(juce::Point _centrePoint) noexcept + { + TRACER("RotarySliderComponent::setSizeAndCentre"); + const int width = static_cast(baseWidth * this->size); + const int height = static_cast(baseHeight * this->size); + setSize(width, height); + setCentrePosition(_centrePoint.getX(), _centrePoint.getY()); + } + +private: + RotarySlider slider; + SliderAttachment sliderAttachment; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RotarySliderComponent) +}; +//============================================================================== +} // namespace component +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/component/SettingsEditorComponent.h b/src/dmt/gui/component/SettingsEditorComponent.h new file mode 100644 index 0000000..7f4f164 --- /dev/null +++ b/src/dmt/gui/component/SettingsEditorComponent.h @@ -0,0 +1,188 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * SettingsEditorComponent provides a user interface for editing application + * settings in a structured and user-friendly way. It displays settings + * categories in a scrollable list on the left, and when a category is selected, + * it shows the corresponding settings editors on the right. The component + * supports dynamic resizing, customizable scrollbars, and is designed to be + * easily extendable for different types of settings. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "dmt/configuration/TreeAdapter.h" +#include "dmt/gui/widget/ValueCategoryList.h" +#include "dmt/gui/widget/ValueEditorList.h" +#include "dmt/utility/Scaleable.h" +#include "dmt/utility/Settings.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace component { +//============================================================================== +class SettingsEditor + : public juce::Component + , public dmt::Scaleable +{ + using Colour = juce::Colour; + using Settings = dmt::Settings; + using SettingsEditorSettings = dmt::Settings::SettingsEditor; + using String = juce::String; + using Viewport = juce::Viewport; + using TreeAdapter = dmt::configuration::TreeAdapter; + using ValueEditorList = dmt::gui::component::ValueEditorList; + using ValueCategoryList = dmt::gui::component::ValueCategoryList; + using Component = juce::Component; + + //============================================================================== + // SettingsEditor + const Colour& scrollBarColour = SettingsEditorSettings::scrollBarColour; + const float rawScrollBarThickness = + SettingsEditorSettings::scrollBarThickness; + const Colour& scrollBarBackgroundColour = + SettingsEditorSettings::scrollBarBackgroundColour; + const float& rawFontSize = SettingsEditorSettings::fontSize; + + // TODO: Move somewhere else + std::vector blockedCategories = { "TriangleButton", + "OscillatorDisplay", + "Carousel" }; + +public: + SettingsEditor() + : treeAdapter(Settings::container, blockedCategories) + , searchEditor("TestEditor") + , valueCategoryList(treeAdapter.getCategories(), + [this](TreeAdapter::Category& category) { + onCategorySelectedCallback(category); + }) + + { + TRACER("SettingsEditor::SettingsEditor"); + addAndMakeVisible(searchEditor); + addAndMakeVisible(categoryViewport); + addAndMakeVisible(editorViewport); + + categoryViewport.setViewedComponent(&valueCategoryList, false); + categoryViewport.setScrollBarsShown(false, false, false, false); + editorViewport.setViewedComponent(&valueEditorList, false); + editorViewport.setScrollBarsShown(true, false, false, false); + setScrollbarThicknesses(); + setScrollBarColour(); + } + + ~SettingsEditor() override = default; + + void paint(juce::Graphics& /*_g*/) override + { + TRACER("SettingsEditor::paint"); + } + + void resized() override + { + TRACER("SettingsEditor::resized"); + auto bounds = getLocalBounds(); + + // Set bounds for the search editor + // const auto fontSize = static_cast(rawFontSize * size); + // searchEditor.setBounds(bounds.removeFromTop(fontSize)); + + // Calculate and set bounds for the category viewport + const int categoryWidth = static_cast(bounds.getWidth() * 0.35f); + categoryViewport.setBounds(bounds.removeFromLeft(categoryWidth)); + layoutViewport(categoryViewport, valueCategoryList); + + // Set bounds for the editor viewport + editorViewport.setBounds(bounds); + layoutViewport(editorViewport, valueEditorList); + + // Update scrollbar thicknesses + setScrollbarThicknesses(); + } + + void onCategorySelectedCallback(TreeAdapter::Category& category) + { + TRACER("SettingsEditor::onCategorySelectedCallback"); + std::cout << "Selected category: " << category.name << std::endl; + valueEditorList.setCategory(category); + valueEditorList.setOptimalSize(editorViewport.getWidth()); + } + +protected: + template + void layoutViewport(Viewport& viewport, ComponentType& content) + { + TRACER("SettingsEditor::layoutViewport"); + const int optimalWidth = + viewport.isVerticalScrollBarShown() + ? viewport.getWidth() - viewport.getScrollBarThickness() + : viewport.getWidth(); + content.setOptimalSize(optimalWidth); + } + + void setScrollbarThicknesses() + { + TRACER("SettingsEditor::setScrollbarThicknesses"); + const int scrollBarThickness = + static_cast(rawScrollBarThickness * size); + categoryViewport.setScrollBarThickness(scrollBarThickness); + editorViewport.setScrollBarThickness(scrollBarThickness); + } + + void setScrollBarColour() + { + TRACER("SettingsEditor::setScrollBarColour"); + categoryViewport.getVerticalScrollBar().setColour( + juce::ScrollBar::ColourIds::thumbColourId, scrollBarColour); + editorViewport.getVerticalScrollBar().setColour( + juce::ScrollBar::ColourIds::thumbColourId, scrollBarColour); + + categoryViewport.getVerticalScrollBar().setColour( + juce::ScrollBar::ColourIds::backgroundColourId, + scrollBarBackgroundColour); + editorViewport.getVerticalScrollBar().setColour( + juce::ScrollBar::ColourIds::backgroundColourId, + scrollBarBackgroundColour); + } + +private: + TreeAdapter treeAdapter; + TextEditor searchEditor; + Viewport categoryViewport; + Viewport editorViewport; + ValueEditorList valueEditorList; + ValueCategoryList valueCategoryList; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SettingsEditor) +}; +} // namespace component +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/display/AbstractDisplay.h b/src/dmt/gui/display/AbstractDisplay.h new file mode 100644 index 0000000..46f868e --- /dev/null +++ b/src/dmt/gui/display/AbstractDisplay.h @@ -0,0 +1,264 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * AbstractDisplay provides a base class for all display components + * that require custom painting, shadow/border rendering, and repaint timing. + * Designed for extensibility and real-time GUI performance. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "dmt/utility/Scaleable.h" +#include "gui/widget/Shadow.h" +#include "utility/RepaintTimer.h" +#include "utility/Settings.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace display { + +//============================================================================== +/** + * @brief Abstract base class for display components with shadow, border, and + * repaint timer. + * + * @details + * This class provides a foundation for custom display components, handling + * shadow rendering, border drawing, and periodic repainting. Subclasses must + * implement extendResized, paintDisplay, and prepareNextFrame for their own + * drawing and layout logic. + */ +class AbstractDisplay + : public juce::Component + , public dmt::utility::RepaintTimer + , public dmt::Scaleable +{ + //============================================================================ + // Aliases for convenience + using Shadow = dmt::gui::widget::Shadow; + + //============================================================================ + // General + using Display = dmt::Settings::Display; + const juce::Colour& backgroundColour = Display::backgroundColour; + + // Layout + const float& rawCornerSize = Display::cornerSize; + const float& rawPadding = Display::padding; + + // Border + const bool& drawBorder = Display::drawBorder; + const juce::Colour& borderColour = Display::borderColour; + const float& rawBorderStrength = Display::borderStrength; + + // Shadows + const bool& drawOuterShadow = Display::drawOuterShadow; + const bool& drawInnerShadow = Display::drawInnerShadow; + const juce::Colour& outerShadowColour = Display::outerShadowColour; + const juce::Colour& innerShadowColour = Display::innerShadowColour; + const float& outerShadowRadius = Display::outerShadowRadius; + const float& innerShadowRadius = Display::innerShadowRadius; + +public: + //============================================================================== + /** + * @brief Constructs an AbstractDisplay. + * + * @details + * Initializes shadow components and starts the repaint timer. + * Subclasses should implement their own layout and painting logic. + */ + inline explicit AbstractDisplay( + /*juce::AudioProcessorValueTreeState& _apvts*/) noexcept + : outerShadow(drawOuterShadow, outerShadowColour, outerShadowRadius, false) + , innerShadow(drawInnerShadow, innerShadowColour, innerShadowRadius, true) + { + this->startRepaintTimer(); + addAndMakeVisible(outerShadow); + addAndMakeVisible(innerShadow); + } + + //============================================================================== + /** + * @brief Paints the component, including background, border, and display + * content. + * + * @param _g The graphics context. + * + * @details + * Handles all background and border drawing, then delegates to paintDisplay + * for custom content. Calls prepareNextFrame at the end of each paint. + * This method is final and cannot be overridden by subclasses. + */ + inline void paint(juce::Graphics& _g) override final + { + // Precalculation + const auto borderStrength = rawBorderStrength * size; + const auto cornerSize = rawCornerSize * size; + const float outerCornerSize = cornerSize; + const float innerCornerSize = std::clamp( + outerCornerSize - (borderStrength * 0.5f), 0.0f, outerCornerSize); + + // Draw background if border is disabled + if (!drawBorder) { + _g.setColour(backgroundColour); + _g.fillRoundedRectangle(outerBounds.toFloat(), outerCornerSize); + } + + // Draw background and border if border is enabled + if (drawBorder) { + _g.setColour(borderColour); + _g.fillRoundedRectangle(outerBounds.toFloat(), outerCornerSize); + _g.setColour(backgroundColour); + _g.fillRoundedRectangle(innerBounds.toFloat(), innerCornerSize); + } + // Draw display + paintDisplay(_g, innerBounds); + + // We need to draw the border again because drawing it once didn't cut it + if (drawBorder) { + _g.setColour(borderColour); + const auto borderBounds = outerBounds.reduced(borderStrength / 2.0f); + _g.drawRoundedRectangle( + borderBounds.toFloat(), outerCornerSize, borderStrength); + } + + // Prepare next frame + prepareNextFrame(); + } + + //============================================================================== + /** + * @brief Handles resizing and layout of the component and its shadows. + * + * @details + * Updates bounds for outer/inner shadows and calls extendResized for + * subclass-specific layout. This method is final and cannot be overridden. + */ + inline void resized() override final + { + TRACER("DisfluxDisplay::resized"); + + const auto bounds = getLocalBounds(); + const auto borderStrength = rawBorderStrength * size; + const auto cornerSize = rawCornerSize * size; + const auto padding = rawPadding * size; + + outerBounds = bounds.reduced(static_cast(padding)); + innerBounds = outerBounds.reduced(static_cast(borderStrength)); + const float outerCornerSize = cornerSize; + const float innerCornerSize = std::clamp( + outerCornerSize - (borderStrength * 0.5f), 0.0f, outerCornerSize); + + juce::Path outerShadowPath; + outerShadowPath.addRoundedRectangle(outerBounds, outerCornerSize); + outerShadow.setPath(outerShadowPath); + outerShadow.setBoundsRelative(0.0f, 0.0f, 1.0f, 1.0f); + outerShadow.toBack(); + + juce::Path innerShadowPath; + innerShadowPath.addRoundedRectangle(innerBounds, innerCornerSize); + innerShadow.setPath(innerShadowPath); + innerShadow.setBoundsRelative(0.0f, 0.0f, 1.0f, 1.0f); + innerShadow.toBack(); + + // Call the childs extendResized method + extendResized(drawBorder ? innerBounds : outerBounds); + } + +protected: + //============================================================================== + /** + * @brief Extension point for subclasses to handle resizing and layout. + * + * @param _displayBounds The bounds of the display area. + * + * @details + * Subclasses must implement this to layout their own subcomponents. + */ + virtual void extendResized( + const juce::Rectangle& _displayBounds) noexcept = 0; + + //============================================================================== + /** + * @brief Paints the display content. + * + * @param _g The graphics context. + * @param _displayBounds The bounds of the display area. + * + * @details + * Subclasses must implement this to draw their own content. + * Do not call this method directly. + */ + virtual void paintDisplay( + juce::Graphics& _g, + const juce::Rectangle& _displayBounds) noexcept = 0; + + //============================================================================== + /** + * @brief Prepares the next frame for display. + * + * @details + * Subclasses must implement this for any per-frame logic. + * Do not draw or perform heavy calculations here. + */ + virtual void prepareNextFrame() noexcept = 0; + +private: + //============================================================================== + /** + * @brief Called by the repaint timer to trigger a repaint. + * + * @details + * This override is final and cannot be changed by subclasses. + */ + inline void repaintTimerCallback() noexcept override final + { + this->repaint(); + } + + //============================================================================== + // Members initialized in the initializer list + Shadow outerShadow = + Shadow(drawOuterShadow, outerShadowColour, outerShadowRadius, false); + Shadow innerShadow = + Shadow(drawInnerShadow, innerShadowColour, innerShadowRadius, true); + + //============================================================================== + // Other members + juce::Rectangle innerBounds; + juce::Rectangle outerBounds; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AbstractDisplay) +}; + +} // namespace component +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/display/DisfluxDisplay.h b/src/dmt/gui/display/DisfluxDisplay.h new file mode 100644 index 0000000..bd1e41e --- /dev/null +++ b/src/dmt/gui/display/DisfluxDisplay.h @@ -0,0 +1,68 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Specialized display component inheriting from AbstractDisplay. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "dsp/data/FifoAudioBuffer.h" +#include "gui/display/OscilloscopeDisplay.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace display { + +//============================================================================== + +/** + * @class DisfluxDisplay + * @brief Specialized display component inheriting from + * AbstractDisplay. + */ +class alignas(64) DisfluxDisplay + : public dmt::gui::display::OscilloscopeDisplay +{ +public: + using FifoAudioBuffer = dmt::dsp::data::FifoAudioBuffer; + DisfluxDisplay(FifoAudioBuffer& _fifoBuffer, + AudioProcessorValueTreeState& _apvts) + : OscilloscopeDisplay( + _fifoBuffer, // The data buffer + _apvts, // The processers value tree state + true) // This tells the display to not listen to the apvts + { + } + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(DisfluxDisplay) +}; +} // namespace component +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/display/Display.h b/src/dmt/gui/display/Display.h new file mode 100644 index 0000000..35446bf --- /dev/null +++ b/src/dmt/gui/display/Display.h @@ -0,0 +1,35 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided.. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Display header file. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "./AbstractDisplay.h" +#include "./DisfluxDisplay.h" +#include "./OscilloscopeDisplay.h" +#include "./SettingsEditorDisplay.h" \ No newline at end of file diff --git a/src/dmt/gui/display/OscilloscopeDisplay.h b/src/dmt/gui/display/OscilloscopeDisplay.h new file mode 100644 index 0000000..bf6866e --- /dev/null +++ b/src/dmt/gui/display/OscilloscopeDisplay.h @@ -0,0 +1,270 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * OscilloscopeDisplay provides a real-time visualization of the audios + * waveform. It uses a ring buffer to efficiently render the waveform based on + * the current audio data. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "dsp/data/FifoAudioBuffer.h" +#include "dsp/data/RingAudioBuffer.h" +#include "gui/display/AbstractDisplay.h" +#include "gui/widget/Oscilloscope.h" +#include "gui/widget/Shadow.h" +#include "utility/RepaintTimer.h" +#include "utility/Settings.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace display { + +//============================================================================== +template +class OscilloscopeDisplay + : public dmt::gui::display::AbstractDisplay + , public juce::AudioProcessorValueTreeState::Listener +{ + using Oscilloscope = dmt::gui::widget::Oscilloscope; + using RingAudioBuffer = dmt::dsp::data::RingAudioBuffer; + using FifoAudioBuffer = dmt::dsp::data::FifoAudioBuffer; + using Shadow = dmt::gui::widget::Shadow; + using Colour = juce::Colour; + using Settings = dmt::Settings; + using DisplaySettings = dmt::Settings::Display; + + //============================================================================== + // General + const Colour& backgroundColour = DisplaySettings::backgroundColour; + + // Layout + const float& rawCornerSize = DisplaySettings::cornerSize; + const float& rawPadding = DisplaySettings::padding; + // Border + const bool& drawBorder = DisplaySettings::drawBorder; + const Colour& borderColour = DisplaySettings::borderColour; + const float& borderStrength = DisplaySettings::borderStrength; + // Shadows + const bool& drawOuterShadow = DisplaySettings::drawOuterShadow; + const bool& drawInnerShadow = DisplaySettings::drawInnerShadow; + const Colour& outerShadowColour = DisplaySettings::outerShadowColour; + const Colour& innerShadowColour = DisplaySettings::innerShadowColour; + const float& outerShadowRadius = DisplaySettings::outerShadowRadius; + const float& innerShadowRadius = DisplaySettings::innerShadowRadius; + +public: + //============================================================================== + OscilloscopeDisplay(FifoAudioBuffer& _fifoBuffer, + AudioProcessorValueTreeState& _apvts, + bool _useDefaultSettings = false) + : ringBuffer(2, 4096) + , fifoBuffer(_fifoBuffer) + , leftOscilloscope(ringBuffer, 0, size) + , rightOscilloscope(ringBuffer, 1, size) + , useDefaultSettings(_useDefaultSettings) + { + if (!useDefaultSettings) { + _apvts.addParameterListener("OscilloscopeZoom", this); + _apvts.addParameterListener("OscilloscopeThickness", this); + _apvts.addParameterListener("OscilloscopeGain", this); + } else { + // Use default values from dmt::Settings::Oscilloscope + setZoom(dmt::Settings::Oscilloscope::defaultZoom); + setThickness(dmt::Settings::Oscilloscope::defaultThickness); + setHeight(dmt::Settings::Oscilloscope::defaultGain); + } + } + //============================================================================== + void extendResized( + const juce::Rectangle& _displayBounds) noexcept override + { + auto scopeBounds = + _displayBounds.withHeight(_displayBounds.getHeight() * 0.92f) + .withCentre(_displayBounds.getCentre()); + + auto leftScopeBounds = + scopeBounds.removeFromTop(scopeBounds.getHeight() * 0.5f); + auto rightScopeBounds = scopeBounds; + + leftOscilloscope.setBounds(leftScopeBounds); + rightOscilloscope.setBounds(rightScopeBounds); + + // If using default settings, update oscilloscope parameters on resize + if (useDefaultSettings) { + setZoom(dmt::Settings::Oscilloscope::defaultZoom); + setThickness(dmt::Settings::Oscilloscope::defaultThickness); + setHeight(dmt::Settings::Oscilloscope::defaultGain); + } + } + //============================================================================== + void paintDisplay( + juce::Graphics& g, + const juce::Rectangle& /*_displayBounds*/) noexcept override + { + TRACER("OscilloscopeDisplay::paintDisplay"); + + const auto leftScopeBounds = leftOscilloscope.getBounds().toFloat(); + const auto rightScopeBounds = rightOscilloscope.getBounds().toFloat(); + + g.setColour(backgroundColour.brighter(0.05)); + + // Draw vertical lines for both scopes + drawVerticalLines(g, + leftScopeBounds.getX(), + leftScopeBounds.getWidth(), + leftScopeBounds.getY(), + leftScopeBounds.getHeight()); + drawVerticalLines(g, + leftScopeBounds.getX(), + leftScopeBounds.getWidth(), + rightScopeBounds.getY(), + rightScopeBounds.getHeight()); + + // Draw horizontal lines for both scopes + drawHorizontalLines(g, + leftScopeBounds.getX(), + leftScopeBounds.getWidth(), + leftScopeBounds.getY(), + leftScopeBounds.getHeight()); + drawHorizontalLines(g, + leftScopeBounds.getX(), + leftScopeBounds.getWidth(), + rightScopeBounds.getY(), + rightScopeBounds.getHeight()); + + // Draw oscilloscope images + g.drawImageAt(leftOscilloscope.getImage(), + leftOscilloscope.getBounds().getX(), + leftOscilloscope.getBounds().getY()); + g.drawImageAt(rightOscilloscope.getImage(), + rightOscilloscope.getBounds().getX(), + rightOscilloscope.getBounds().getY()); + } + +protected: + //============================================================================== + void drawVerticalLines(juce::Graphics& g, + float scopeX, + float scopeWidth, + float scopeY, + float scopeHeight) const + { + const int numLines = getWidth() / (getHeight() / 4.0f); + const float lineSpacing = scopeWidth / numLines; + + for (size_t i = 1; i < numLines; ++i) { + const float x = scopeX + lineSpacing * i; + g.drawLine(juce::Line(x, scopeY, x, scopeY + scopeHeight), + 2.0f * size); + } + } + //============================================================================== + void drawHorizontalLines(juce::Graphics& g, + float scopeX, + float scopeWidth, + float scopeY, + float scopeHeight) const + { + float lineThicknessModifiers[7] = { + 1.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.5f + }; + float brightnessValues[7] = { 0.15f, 0.05f, 0.05f, 0.05f, + 0.05f, 0.05f, 0.15f }; + + for (size_t i = 0; i < 7; ++i) { + const float y = scopeY + (scopeHeight / 6) * i; + g.setColour(backgroundColour.brighter(brightnessValues[i])); + g.drawLine(juce::Line(scopeX, y, scopeX + scopeWidth, y), + 3.0f * lineThicknessModifiers[i] * size); + } + } + //============================================================================== + void prepareNextFrame() noexcept override + { + TRACER("OscilloscopeDisplay::prepareNextFrame"); + ringBuffer.write(fifoBuffer); + ringBuffer.equalizeReadPositions(); + leftOscilloscope.notify(); + rightOscilloscope.notify(); + } + //============================================================================== + void setZoom(float _zoom) noexcept + { + TRACER("OscilloscopeDisplay::setZoom"); + // Just random math with magic numbers to get a nice feeling zoom + float zoomModifier = (_zoom + 5) / 105.0f; + float maxSamplesPerPixel = 900.0f; + float exponentialModifier = pow(zoomModifier, 4.0f); + float samplesPerPixel = 1.0f + maxSamplesPerPixel * exponentialModifier; + leftOscilloscope.setRawSamplesPerPixel(samplesPerPixel); + rightOscilloscope.setRawSamplesPerPixel(samplesPerPixel); + } + //============================================================================== + void setThickness(float _thickness) noexcept + { + TRACER("OscilloscopeDisplay::setThickness"); + leftOscilloscope.setThickness(_thickness); + rightOscilloscope.setThickness(_thickness); + } + //============================================================================== + void setHeight(float _height) noexcept + { + TRACER("OscilloscopeDisplay::setHeight"); + float amplitude = juce::Decibels::decibelsToGain(_height); + leftOscilloscope.setAmplitude(amplitude); + rightOscilloscope.setAmplitude(amplitude); + } + //============================================================================== + virtual void parameterChanged(const String& _parameterID, float _newValue) + { + if (_parameterID == "OscilloscopeZoom") { + setZoom(_newValue); + } + if (_parameterID == "OscilloscopeThickness") { + setThickness(_newValue); + } + if (_parameterID == "OscilloscopeGain") { + setHeight(_newValue); + } + } + //============================================================================== +private: + RingAudioBuffer ringBuffer; + FifoAudioBuffer& fifoBuffer; + Oscilloscope leftOscilloscope; + Oscilloscope rightOscilloscope; + bool useDefaultSettings; + //============================================================================== + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OscilloscopeDisplay) +}; +} // namespace component +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/display/SettingsEditorDisplay.h b/src/dmt/gui/display/SettingsEditorDisplay.h new file mode 100644 index 0000000..1249db4 --- /dev/null +++ b/src/dmt/gui/display/SettingsEditorDisplay.h @@ -0,0 +1,100 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * AbstractDisplay provides a base class for all display components + * that require custom painting, shadow/border rendering, and repaint timing. + * Designed for extensibility and real-time GUI performance. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "dmt/gui/component/SettingsEditorComponent.h" +#include "dmt/gui/display/AbstractDisplay.h" +#include "dmt/utility/Settings.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace display { +// TODO: Make this use the new display system with multithreading +//============================================================================== +class SettingsEditorDisplay : public dmt::gui::display::AbstractDisplay +{ + using Settings = dmt::Settings; + using SettingsEditorSettings = dmt::Settings::SettingsEditor; + using TextEditor = dmt::gui::widget::TextEditor; + using SettingsEditor = dmt::gui::component::SettingsEditor; + + //============================================================================== + // SettingsEditor + const float& rawPadding = SettingsEditorSettings::padding; + +public: + SettingsEditorDisplay() + { + TRACER("SettingsEditorDisplay::SettingsEditorDisplay"); + addAndMakeVisible(settingsEditor); + } + + ~SettingsEditorDisplay() override = default; + + void extendResized( + const juce::Rectangle& _displayBounds) noexcept override + { + TRACER("SettingsEditorDisplay::extendResized"); + const auto padding = rawPadding * size; + auto settingsBounds = _displayBounds.reduced(padding); + settingsEditor.setBounds(settingsBounds); + } + + void paintDisplay(juce::Graphics& /*_g*/, + const juce::Rectangle& /*_displayBounds*/) noexcept + { + TRACER("SettingsEditorDisplay::paintDisplay"); + if (cachedPadding != rawPadding) { + cachedPadding = rawPadding; + resized(); + } + } + + void prepareNextFrame() noexcept override + { + TRACER("SettingsEditorDisplay::prepareNextFrame"); + // Implement frame preparation logic here + } + +private: + SettingsEditor settingsEditor; + float cachedPadding = 0.0f; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SettingsEditorDisplay) +}; + +} // namespace component +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/panel/AbstractPanel.h b/src/dmt/gui/panel/AbstractPanel.h new file mode 100644 index 0000000..e18aab3 --- /dev/null +++ b/src/dmt/gui/panel/AbstractPanel.h @@ -0,0 +1,484 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * AbstractPanel provides a base class for all panel components in the GUI, + * encapsulating layout, grid, border, shadow, and navigation logic. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "dmt/utility/Scaleable.h" +#include "gui/widget/Label.h" +#include "gui/widget/Shadow.h" +#include "gui/widget/TriangleButton.h" +#include "utility/Fonts.h" +#include "utility/Settings.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace panel { + +//============================================================================== +/** + * @brief Abstract base class for GUI panels with grid layout, navigation, and + * shadow/border rendering. + * + * @details + * This class provides a flexible foundation for all panels in the GUI, + * supporting grid-based layouts, navigation buttons, and customizable + * appearance (shadows, borders, etc). Designed for real-time performance and + * extensibility. Subclasses should override extendResize() and getName() for + * custom behavior. + */ +class AbstractPanel + : public juce::Component + , public juce::Button::Listener + , public dmt::Scaleable +{ +public: + //============================================================================== + using Grid = std::vector>>; + using TriangleButton = dmt::gui::widget::TriangleButton; + using Shadow = dmt::gui::widget::Shadow; + using Label = dmt::gui::widget::Label; + using Fonts = dmt::utility::Fonts; + + // Settings + using LibrarySettings = dmt::Settings; + using Settings = LibrarySettings::Panel; + using Carousel = LibrarySettings::Carousel; + + // Layout + const float& margin = + LibrarySettings::Window::margin; // TODO: Make sure this is used properly + // General + const juce::Colour& backgroundColour = Settings::backgroundColour; + const float& cornerSize = Settings::cornerSize; + const float& rawPadding = Settings::padding; + // Border + const bool& drawBorder = Settings::drawBorder; + const juce::Colour& borderColour = Settings::borderColour; + const float& borderStrength = Settings::borderStrength; + // Shadows + const bool& drawOuterShadow = Settings::drawOuterShadow; + const bool& drawInnerShadow = Settings::drawInnerShadow; + const juce::Colour& outerShadowColour = Settings::outerShadowColour; + const juce::Colour& innerShadowColour = Settings::innerShadowColour; + const float& outerShadowRadius = Settings::outerShadowRadius; + const float& innerShadowRadius = Settings::innerShadowRadius; + // Fonts + const juce::Colour& fontColor = Settings::fontColor; + const float& fontSize = Settings::fontSize; + // Debug + const bool& debugBounds = LibrarySettings::debugBounds; + const bool& debugGrid = LibrarySettings::debugGrid; + + //============================================================================== + /** + * @brief Structure representing the grid layout (columns and rows). + */ + struct Layout + { + int cols; + int rows; + }; + + //============================================================================== + /** + * @brief Constructs an AbstractPanel. + * + * @param _name The panel's name (used for the title label). + * @param _displayName If true, displays the title label. + * + * @details + * Initializes layout, grid, title label, navigation buttons, and shadows. + * The constructor is constexpr for maximum compile-time optimization. + */ + explicit AbstractPanel(const juce::String _name, + const bool _displayName = true) noexcept + : layout({ 1, 1 }) + , rawGridOffsetY(40 * static_cast(_displayName)) + , name(_name) + , titleLabel(_name, fonts.bold, fontSize, juce::Colours::white) + , nextCallback([]() {}) + , prevCallback([]() {}) + , nextButton(dmt::gui::widget::TriangleButton::Right) + , prevButton(dmt::gui::widget::TriangleButton::Left) + , outerShadow(drawOuterShadow, outerShadowColour, outerShadowRadius, false) + , innerShadow(drawInnerShadow, outerShadowColour, outerShadowRadius, true) + { + TRACER("AbstractPanel::AbstractPanel"); + if (_displayName) { + addAndMakeVisible(titleLabel); + } + setLayout(layout); + addAndMakeVisible(outerShadow); + addAndMakeVisible(innerShadow); + } + + //============================================================================== + /** + * @brief Paints the panel, including background, border, and debug overlays. + * + * @param _g The graphics context. + * + * @details + * Draws the panel's background, border, and optional debug grid/bounds. + * Uses settings for appearance. All drawing is performed in local + * coordinates. + */ + inline void paint(juce::Graphics& _g) noexcept override + { + TRACER("AbstractPanel::paint"); + // Precalculation + const auto bounds = this->getLocalBounds().toFloat(); + const auto outerBounds = bounds.reduced(margin * size * 0.5f); + const auto innerBounds = outerBounds.reduced(borderStrength * size); + const float outerCornerSize = cornerSize * size; + const float innerCornerSize = std::clamp( + outerCornerSize - (borderStrength * size * 0.5f), 0.0f, outerCornerSize); + + // draw debug bounds + _g.setColour(juce::Colours::aqua); + if (debugBounds) + _g.drawRect(bounds, 1.0f); + + // Draw background if border is disabled + if (!drawBorder) { + _g.setColour(backgroundColour); + _g.fillRoundedRectangle(outerBounds, outerCornerSize); + } + + // Draw background and border if border is enabled + if (drawBorder) { + _g.setColour(borderColour); + _g.fillRoundedRectangle(outerBounds, outerCornerSize); + _g.setColour(backgroundColour); + _g.fillRoundedRectangle(innerBounds, innerCornerSize); + } + + // draw debug line grid + if (debugGrid) { + _g.setColour(juce::Colours::red); + for (size_t col = 0; col < grid.size(); col++) { + const auto firstPoint = + getGridPoint(bounds.toNearestInt(), static_cast(col), 0); + const auto endPoint = juce::Point( + firstPoint.getX(), static_cast(bounds.getHeight()) - 1); + const auto line = + juce::Line(firstPoint.toFloat(), endPoint.toFloat()); + _g.drawLine(line, 1.0f); + } + for (size_t row = 0; row < grid[0].size(); row++) { + const auto firstPoint = + getGridPoint(bounds.toNearestInt(), 0, static_cast(row)); + const auto endPoint = juce::Point( + static_cast(bounds.getWidth()) - 1, firstPoint.getY()); + const auto line = + juce::Line(firstPoint.toFloat(), endPoint.toFloat()); + _g.drawLine(line, 1.0f); + } + } + } + + //============================================================================== + /** + * @brief Handles resizing of the panel and its subcomponents. + * + * @details + * Updates bounds for shadows, navigation buttons, and the title label. + * Calls extendResize() for subclass-specific resizing. + */ + inline void resized() noexcept override + { + TRACER("AbstractPanel::resized"); + const auto bounds = getLocalBounds(); + const auto outerBounds = bounds.reduced(margin * size * 0.5f); + const auto innerBounds = outerBounds.reduced(borderStrength * size); + const float outerCornerSize = cornerSize * size; + const float innerCornerSize = std::clamp( + outerCornerSize - (borderStrength * size * 0.5f), 0.0f, outerCornerSize); + + juce::Path outerShadowPath; + outerShadowPath.addRoundedRectangle(outerBounds, outerCornerSize); + outerShadow.setPath(outerShadowPath); + outerShadow.setBoundsRelative(0.0f, 0.0f, 1.0f, 1.0f); + outerShadow.toBack(); + + juce::Path innerShadowPath; + innerShadowPath.addRoundedRectangle(innerBounds, innerCornerSize); + innerShadow.setPath(innerShadowPath); + innerShadow.setBoundsRelative(0.0f, 0.0f, 1.0f, 1.0f); + innerShadow.toBack(); + + const int buttonWidth = static_cast(Carousel::buttonWidth * size); + const int buttonHeight = static_cast(Carousel::buttonHeight * size); + const int marginSize = static_cast(size * margin); + const int padding = static_cast(rawPadding * size); + + auto leftBounds = bounds; + auto rightBounds = bounds; + + leftBounds.removeFromRight(bounds.getWidth() - buttonWidth); + leftBounds.setHeight(buttonHeight); + leftBounds.setCentre(leftBounds.getCentreX(), bounds.getCentreY()); + prevButton.setBounds(leftBounds.reduced(marginSize)); + + rightBounds.removeFromLeft(bounds.getWidth() - buttonWidth); + rightBounds.setHeight(buttonHeight); + rightBounds.setCentre(rightBounds.getCentreX(), bounds.getCentreY()); + nextButton.setBounds(rightBounds.reduced(marginSize)); + + titleLabel.setBounds(bounds.reduced(padding + marginSize)); + + extendResize(); + } + + //============================================================================== + /** + * @brief Extension point for subclasses to handle additional resizing logic. + * + * @details + * Override in subclasses to resize custom subcomponents. + */ + virtual inline void extendResize() noexcept {} + + //============================================================================== + /** + * @brief Returns the panel's name. + * + * @return The name of the panel. + * + * @details + * Override in subclasses to provide a custom name. + */ + virtual inline const juce::String getName() noexcept + { + TRACER("AbstractPanel::getName"); + return "Panel"; + } + + //============================================================================== + /** + * @brief Sets the callbacks for navigation buttons. + * + * @param _next Callback for the "next" button. + * @param _prev Callback for the "previous" button. + * + * @details + * Registers the callbacks and makes the navigation buttons visible. + */ + inline void setCallbacks(std::function _next, + std::function _prev) + { + TRACER("AbstractPanel::setCallbacks"); + nextCallback = _next; + prevCallback = _prev; + addAndMakeVisible(nextButton); + addAndMakeVisible(prevButton); + nextButton.addListener(this); + prevButton.addListener(this); + } + + //============================================================================== + /** + * @brief Invokes the "next" callback. + */ + inline void next() + { + TRACER("AbstractPanel::next"); + nextCallback(); + } + + //============================================================================== + /** + * @brief Invokes the "previous" callback. + */ + inline void prev() + { + TRACER("AbstractPanel::prev"); + prevCallback(); + } + + //============================================================================== + /** + * @brief Handles button click events for navigation. + * + * @param _button The button that was clicked. + * + * @details + * Invokes the appropriate callback based on which navigation button was + * pressed. + */ + inline void buttonClicked(juce::Button* _button) override + { + TRACER("AbstractPanel::buttonClicked"); + if (_button == &nextButton) { + nextCallback(); + } else if (_button == &prevButton) { + prevCallback(); + } + } + +protected: + //============================================================================== + /** + * @brief Returns the current grid layout. + * + * @return The current Layout struct. + */ + [[nodiscard]] inline const Layout getLayout() noexcept + { + TRACER("AbstractPanel::getLayout"); + return layout; + } + + //============================================================================== + /** + * @brief Sets the grid layout and recalculates grid points. + * + * @param _layoutToUse The desired layout (columns and rows). + * + * @details + * Recomputes the grid for the new layout, ensuring all points are updated. + */ + inline void setLayout(const Layout _layoutToUse) noexcept + { + TRACER("AbstractPanel::setLayout"); + const int cols = _layoutToUse.cols; + const int rows = _layoutToUse.rows; + const float colSpacing = 1.0f / static_cast(cols + 1); + const float rowSpacing = 1.0f / static_cast(rows + 1); + Grid newGrid( + static_cast(cols + 2), + std::vector>(static_cast(rows + 2))); + + for (size_t col = 0; col <= static_cast(_layoutToUse.cols + 1); + col++) { + for (size_t row = 0; row <= static_cast(_layoutToUse.rows + 1); + row++) { + const float x = static_cast(col) * colSpacing; + const float y = static_cast(row) * rowSpacing; + const auto point = juce::Point(x, y); + newGrid[col][row] = point; + } + } + this->grid = newGrid; + this->layout = _layoutToUse; + } + + //============================================================================== + /** + * @brief Returns the pixel position of a grid point within the given bounds. + * + * @param _bounds The bounding rectangle. + * @param _col The column index. + * @param _row The row index. + * @return The pixel position as a juce::Point. + * + * @details + * Computes the position of a grid point, accounting for grid offset and + * scaling. Asserts if indices are out of bounds. + */ + [[nodiscard]] inline const juce::Point getGridPoint( + const juce::Rectangle _bounds, + const int _col, + const int _row) noexcept + { + TRACER("AbstractPanel::getGridPoint"); + // assert if col and row are out of bounds + jassert(_col >= 0 && static_cast(_col) < grid.size()); + jassert(_row >= 0 && + static_cast(_row) < grid[static_cast(_col)].size()); + + auto rawPoint = grid[static_cast(_col)][static_cast(_row)]; + const auto x = rawPoint.getX() * static_cast(_bounds.getWidth()); + + const float gridOffsetY = static_cast(rawGridOffsetY) * size; + const float offsetBoundsHeight = + static_cast(_bounds.getHeight()) - gridOffsetY; + const auto y = rawPoint.getY() * offsetBoundsHeight + gridOffsetY; + + juce::Point point(x, y); + return point.toInt(); + } + + //============================================================================== + /** + * @brief Sets the raw Y offset for the grid. + * + * @param _offset The offset in pixels. + * + * @details + * Used to vertically shift the grid, e.g., to make space for a title. + */ + inline void setRawGridOffset(const int _offset) noexcept + { + TRACER("AbstractPanel::setRawGridOffset"); + rawGridOffsetY = _offset; + } + + //============================================================================== + /** + * @brief Returns the current raw grid Y offset. + * + * @return The raw grid Y offset in pixels. + */ + [[nodiscard]] inline const int getRawGridOffset() const noexcept + { + TRACER("AbstractPanel::getRawGridOffset"); + return rawGridOffsetY; + } + +private: + //============================================================================== + // Members initialized in the initializer list + Layout layout; + int rawGridOffsetY; + const juce::String name; + Label titleLabel; + std::function nextCallback; + std::function prevCallback; + TriangleButton nextButton; + TriangleButton prevButton; + Shadow outerShadow; + Shadow innerShadow; + + //============================================================================== + // Other members + Grid grid; + Fonts fonts; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AbstractPanel) +}; + +} // namespace panel +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/panel/AnalogGainPanel.h b/src/dmt/gui/panel/AnalogGainPanel.h new file mode 100644 index 0000000..7f0b556 --- /dev/null +++ b/src/dmt/gui/panel/AnalogGainPanel.h @@ -0,0 +1,141 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * AnalogGainPanel is a GUI component that provides controls for an analog gain + * envelope. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "gui/component/LinearSliderComponent.h" +#include "gui/panel/AbstractPanel.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace panel { + +//============================================================================== +class AnalogGainPanel : public dmt::gui::panel::AbstractPanel +{ + using Unit = dmt::utility::Unit; + + using LinearSliderComponent = dmt::gui::component::LinearSliderComponent; + using LinearSliderType = dmt::gui::widget::LinearSlider::Type; + using LinearSliderOrientation = dmt::gui::widget::LinearSlider::Orientation; + +public: + AnalogGainPanel(juce::AudioProcessorValueTreeState& apvts) + : AbstractPanel("Analog Gain Envelope") + , attackSlider(apvts, + juce::String("Attack"), + juce::String("osc1GainEnvAttack"), + Unit::Type::Milliseconds, + LinearSliderType::Positive, + LinearSliderOrientation::Vertical) + , holdSlider(apvts, + juce::String("Hold"), + juce::String("osc1GainEnvHold"), + Unit::Type::Milliseconds, + LinearSliderType::Positive, + LinearSliderOrientation::Vertical) + , decaySlider(apvts, + juce::String("Decay"), + juce::String("osc1GainEnvDecay"), + Unit::Type::Milliseconds, + LinearSliderType::Positive, + LinearSliderOrientation::Vertical) + , skewSlider(apvts, + juce::String("Skew"), + juce::String("osc1GainEnvSkew"), + Unit::Type::EnvelopeSkew, + LinearSliderType::Positive, + LinearSliderOrientation::Vertical) + { + TRACER("AnalogGainPanel::AnalogGainPanel"); + setLayout({ 25, 32 }); + addAndMakeVisible(attackSlider); + addAndMakeVisible(holdSlider); + addAndMakeVisible(decaySlider); + addAndMakeVisible(skewSlider); + } + + void extendResize() noexcept override + { + TRACER("AnalogGainPanel::extendResize"); + const auto bounds = getLocalBounds(); + + const int primaryRow = 3; + const int secundaryRow = 28; + + const int attackCol = 7; + const int holdCol = 11; + const int decayCol = 15; + const int skewCol = 19; + + const auto attackSliderPrimaryPoint = + this->getGridPoint(bounds, attackCol, primaryRow); + const auto attackSliderSecondaryPoint = + this->getGridPoint(bounds, attackCol, secundaryRow); + attackSlider.setBoundsByPoints(attackSliderPrimaryPoint, + attackSliderSecondaryPoint); + + const auto holdSliderPrimaryPoint = + this->getGridPoint(bounds, holdCol, primaryRow); + const auto holdSliderSecondaryPoint = + this->getGridPoint(bounds, holdCol, secundaryRow); + holdSlider.setBoundsByPoints(holdSliderPrimaryPoint, + holdSliderSecondaryPoint); + + const auto decaySliderPrimaryPoint = + this->getGridPoint(bounds, decayCol, primaryRow); + const auto decaySliderSecondaryPoint = + this->getGridPoint(bounds, decayCol, secundaryRow); + decaySlider.setBoundsByPoints(decaySliderPrimaryPoint, + decaySliderSecondaryPoint); + + const auto skewSliderPrimaryPoint = + this->getGridPoint(bounds, skewCol, primaryRow); + const auto skewSliderSecondaryPoint = + this->getGridPoint(bounds, skewCol, secundaryRow); + skewSlider.setBoundsByPoints(skewSliderPrimaryPoint, + skewSliderSecondaryPoint); + } + +private: + LinearSliderComponent attackSlider; + LinearSliderComponent holdSlider; + LinearSliderComponent decaySlider; + LinearSliderComponent skewSlider; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AnalogGainPanel) +}; +//============================================================================== +} // namespace panel +} // namespace gui +} // namespace dmt diff --git a/src/dmt/gui/panel/AnalogOscillatorPanel.h b/src/dmt/gui/panel/AnalogOscillatorPanel.h new file mode 100644 index 0000000..1ad9f7d --- /dev/null +++ b/src/dmt/gui/panel/AnalogOscillatorPanel.h @@ -0,0 +1,56 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * AnalogOscillatorPanel is a GUI component that provides controls for an analog + * oscillator. + * + * Authors: Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "gui/panel/AbstractPanel.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace panel { + +//============================================================================== +class AnalogOscillatorPanel : public dmt::gui::panel::AbstractPanel +{ +public: + AnalogOscillatorPanel(/*juce::AudioProcessorValueTreeState& apvts*/) + : AbstractPanel("Classic Oscillator") + { // + } + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AnalogOscillatorPanel) +}; +//============================================================================== +} // namespace panels +} // namespace gui +} // namespace dmt diff --git a/src/dmt/gui/panel/AnalogPitchPanel.h b/src/dmt/gui/panel/AnalogPitchPanel.h new file mode 100644 index 0000000..2222836 --- /dev/null +++ b/src/dmt/gui/panel/AnalogPitchPanel.h @@ -0,0 +1,136 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * AnalogPitchPanel is a GUI component that provides controls for an analog + * pitch envelope. + * + * Authors: Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "gui/component/LinearSliderComponent.h" +#include "gui/panel/AbstractPanel.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace panel { + +//============================================================================== +class AnalogPitchPanel : public dmt::gui::panel::AbstractPanel +{ + using LinearSliderComponent = dmt::gui::component::LinearSliderComponent; + using LinearSliderType = dmt::gui::widget::LinearSlider::Type; + using LinearSliderOrientation = dmt::gui::widget::LinearSlider::Orientation; + using Unit = dmt::utility::Unit; + +public: + AnalogPitchPanel(juce::AudioProcessorValueTreeState& apvts) + : AbstractPanel("Analog Pitch Envelope") + , attackSlider(apvts, + juce::String("Attack"), + juce::String("osc1PitchEnvAttack"), + Unit::Type::Milliseconds, + LinearSliderType::Positive, + LinearSliderOrientation::Vertical) + , decaySlider(apvts, + juce::String("Decay"), + juce::String("osc1PitchEnvDecay"), + Unit::Type::Milliseconds, + LinearSliderType::Positive, + LinearSliderOrientation::Vertical) + , skewSlider(apvts, + juce::String("Skew"), + juce::String("osc1PitchEnvSkew"), + Unit::Type::EnvelopeSkew, + LinearSliderType::Positive, + LinearSliderOrientation::Vertical) + , depthSlider(apvts, + juce::String("Depth"), + juce::String("osc1PitchEnvDepth"), + Unit::Type::Frequency, + LinearSliderType::Positive, + LinearSliderOrientation::Vertical) + { + setLayout({ 25, 32 }); + addAndMakeVisible(attackSlider); + addAndMakeVisible(depthSlider); + addAndMakeVisible(decaySlider); + addAndMakeVisible(skewSlider); + } + + void extendResize() noexcept override + { + const auto bounds = getLocalBounds(); + + const int primaryRow = 3; + const int secundaryRow = 28; + + const int attackCol = 7; + const int decayCol = 11; + const int skewCol = 15; + const int depthCol = 19; + + const auto attackSliderPrimaryPoint = + this->getGridPoint(bounds, attackCol, primaryRow); + const auto attackSliderSecondaryPoint = + this->getGridPoint(bounds, attackCol, secundaryRow); + attackSlider.setBoundsByPoints(attackSliderPrimaryPoint, + attackSliderSecondaryPoint); + + const auto decaySliderPrimaryPoint = + this->getGridPoint(bounds, decayCol, primaryRow); + const auto decaySliderSecondaryPoint = + this->getGridPoint(bounds, decayCol, secundaryRow); + decaySlider.setBoundsByPoints(decaySliderPrimaryPoint, + decaySliderSecondaryPoint); + + const auto skewSliderPrimaryPoint = + this->getGridPoint(bounds, skewCol, primaryRow); + const auto skewSliderSecondaryPoint = + this->getGridPoint(bounds, skewCol, secundaryRow); + skewSlider.setBoundsByPoints(skewSliderPrimaryPoint, + skewSliderSecondaryPoint); + + const auto depthSliderPrimaryPoint = + this->getGridPoint(bounds, depthCol, primaryRow); + const auto depthSliderSecondaryPoint = + this->getGridPoint(bounds, depthCol, secundaryRow); + depthSlider.setBoundsByPoints(depthSliderPrimaryPoint, + depthSliderSecondaryPoint); + } + +private: + LinearSliderComponent attackSlider; + LinearSliderComponent depthSlider; + LinearSliderComponent decaySlider; + LinearSliderComponent skewSlider; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AnalogPitchPanel) +}; +//============================================================================== +} // namespace panels +} // namespace gui +} // namespace dmt diff --git a/src/dmt/gui/panel/Carousel.h b/src/dmt/gui/panel/Carousel.h new file mode 100644 index 0000000..7abe9da --- /dev/null +++ b/src/dmt/gui/panel/Carousel.h @@ -0,0 +1,94 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Carousel is a GUI component that manages multiple AbstractPanel instances, + * allowing navigation between them using next and previous buttons. + * + * Authors: Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "gui/panel/AbstractPanel.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace panel { + +//============================================================================== +class Carousel : public juce::Component +{ +public: + Carousel() + : index(0) + { + } + void next() + { + panels[index]->setVisible(false); + index++; + if (index >= panels.size()) + index -= (int)panels.size(); + panels[index]->setVisible(true); + repaint(); + } + void previous() + { + panels[index]->setVisible(false); + index--; + if (index < 0) + index += (int)panels.size(); + panels[index]->setVisible(true); + repaint(); + } + void init() + { + for (auto& panel : panels) { + panel->setCallbacks([this]() { next(); }, [this]() { previous(); }); + addChildComponent(*panel); + } + panels[index]->setVisible(true); + } + + void resized() override + { + for (auto& panel : panels) { + panel->setBoundsRelative(0.0f, 0.0f, 1.0f, 1.0f); + } + } + +protected: + std::vector> panels; + +private: + int index; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Carousel) +}; +//============================================================================== +} // namespace panels +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/panel/DisfluxPanel.h b/src/dmt/gui/panel/DisfluxPanel.h new file mode 100644 index 0000000..a915bba --- /dev/null +++ b/src/dmt/gui/panel/DisfluxPanel.h @@ -0,0 +1,220 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * DisfluxPanel is the main panel for the Disflux effect. + * It also comes with an oscilloscope display. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +// Define to 1 to exclude DisfluxDisplay from the build +#define DMT_EXCLUDE_DISFLUX_DISPLAY 0 + +//============================================================================== + +#include "dsp/data/FifoAudioBuffer.h" +#include "gui/component/LinearSliderComponent.h" +#include "gui/component/RotarySliderComponent.h" +#if DMT_EXCLUDE_DISFLUX_DISPLAY == 0 +#include "gui/display/DisfluxDisplay.h" +#endif +#include "gui/panel/AbstractPanel.h" +#include "utility/Settings.h" +#include "utility/Unit.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace panel { + +//============================================================================== +/** + * @brief Panel for Disflux oscilloscope display and parameter controls. + * + * @tparam SampleType The floating-point sample type for audio data. + * + * @details + * This class provides a GUI panel for the Disflux effect, including an + * oscilloscope display and various parameter sliders. It inherits from + * AbstractPanel and uses a grid layout for positioning components. + */ +template +class DisfluxPanel : public dmt::gui::panel::AbstractPanel +{ + using FifoAudioBuffer = dmt::dsp::data::FifoAudioBuffer; +#if DMT_EXCLUDE_DISFLUX_DISPLAY == 0 + using DisfluxDisplay = dmt::gui::display::DisfluxDisplay; +#endif + using LinearSlider = dmt::gui::component::LinearSliderComponent; + using LinearSliderType = dmt::gui::widget::LinearSlider::Type; + using LinearSliderOrientation = dmt::gui::widget::LinearSlider::Orientation; + using RotarySlider = dmt::gui::component::RotarySliderComponent; + using RotarySliderType = dmt::gui::widget::RotarySlider::Type; + using Unit = dmt::utility::Unit; + using Settings = dmt::Settings; + const float& rawPadding = Settings::Panel::padding; + +public: + //============================================================================== + /** + * @brief Constructs a DisfluxPanel. + * + * @param _apvts The AudioProcessorValueTreeState for parameter binding. + * @param _oscilloscopeBuffer The FIFO buffer for oscilloscope display. + * + * @details + * Initializes all display and slider components, sets up the grid layout, + * and adds all subcomponents to the panel. + */ + constexpr inline explicit DisfluxPanel( + juce::AudioProcessorValueTreeState& _apvts, + FifoAudioBuffer& _oscilloscopeBuffer) noexcept + : AbstractPanel("Oscilloscope", false) +#if DMT_EXCLUDE_DISFLUX_DISPLAY == 0 + , display(_oscilloscopeBuffer, _apvts) +#endif + , amountSlider(_apvts, + juce::String("Amount"), + juce::String("DisfluxAmount"), + Unit::Type::DisfluxAmount, + RotarySliderType::Positive) + , spreadSlider(_apvts, + juce::String("Spread"), + juce::String("DisfluxSpread"), + Unit::Type::DisfluxSpread, + RotarySliderType::Positive) + , fequencySlider(_apvts, + juce::String("Frequency"), + juce::String("DisfluxFrequency"), + Unit::Type::DisfluxFrequency, + LinearSliderType::Positive, + LinearSliderOrientation::Horizontal) + , pinchSlider(_apvts, + juce::String("Pinch"), + juce::String("DisfluxPinch"), + Unit::Type::DisfluxPinch, + RotarySliderType::Positive) + , mixSlider(_apvts, + juce::String("Mix"), + juce::String("DisfluxMix"), + Unit::Type::DisfluxMix, + RotarySliderType::Positive) + { + TRACER("DisfluxPanel::DisfluxPanel"); + setLayout({ 22, 60 }); +#if DMT_EXCLUDE_DISFLUX_DISPLAY == 0 + addAndMakeVisible(display); +#endif + addAndMakeVisible(amountSlider); + addAndMakeVisible(spreadSlider); + addAndMakeVisible(fequencySlider); + addAndMakeVisible(pinchSlider); + addAndMakeVisible(mixSlider); + } + + //============================================================================== + /** + * @brief Handles resizing and layout of all subcomponents. + * + * @details + * Positions the oscilloscope display and all sliders using grid points, + * ensuring consistent layout regardless of panel size. + * Override from AbstractPanel. + */ + inline void extendResize() noexcept override + { + TRACER("DisfluxPanel::extendResize"); + auto bounds = getLocalBounds(); + + const float padding = rawPadding * size; + auto displayBounds = bounds.reduced(padding); + const float displayHorizontalPadding = 100.0f; + const float displayVerticalPadding = 57.0f; + displayBounds.removeFromBottom(displayVerticalPadding * size); + displayBounds.removeFromLeft(displayHorizontalPadding * size); + displayBounds.removeFromRight(displayHorizontalPadding * size); +#if DMT_EXCLUDE_DISFLUX_DISPLAY == 0 + display.setBounds(displayBounds); +#endif + + const int upperRotarySliderRow = 17; + const int lowerRotarySliderRow = 43; + const int linearSliderRow = 51; + + const int amountSliderCol = 3; + const int leftFequencySliderCol = 6; + const int rightFequencySliderCol = 17; + const int pinchSliderCol = 20; + + const auto amountSliderPoint = + this->getGridPoint(bounds, amountSliderCol, upperRotarySliderRow); + amountSlider.setSizeAndCentre(amountSliderPoint); + + const auto spreadSliderPoint = + this->getGridPoint(bounds, amountSliderCol, lowerRotarySliderRow); + spreadSlider.setSizeAndCentre(spreadSliderPoint); + + const auto leftFequencySliderPoint = + this->getGridPoint(bounds, leftFequencySliderCol, linearSliderRow); + const auto rightFequencySliderPoint = + this->getGridPoint(bounds, rightFequencySliderCol, linearSliderRow); + fequencySlider.setBoundsByPoints(leftFequencySliderPoint, + rightFequencySliderPoint); + + const auto pinchSliderPoint = + this->getGridPoint(bounds, pinchSliderCol, lowerRotarySliderRow); + pinchSlider.setSizeAndCentre(pinchSliderPoint); + + const auto mixSliderPoint = + this->getGridPoint(bounds, pinchSliderCol, upperRotarySliderRow); + mixSlider.setSizeAndCentre(mixSliderPoint); + } + //============================================================================== + +private: + //============================================================================== + // Members initialized in the initializer list +#if DMT_EXCLUDE_DISFLUX_DISPLAY == 0 + DisfluxDisplay display; +#endif + RotarySlider amountSlider; + RotarySlider spreadSlider; + LinearSlider fequencySlider; + RotarySlider pinchSlider; + RotarySlider mixSlider; + + //============================================================================== + // Other members + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(DisfluxPanel) +}; + +} // namespace panel +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/panel/GainPanel.h b/src/dmt/gui/panel/GainPanel.h new file mode 100644 index 0000000..02e8f4c --- /dev/null +++ b/src/dmt/gui/panel/GainPanel.h @@ -0,0 +1,62 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * GainPanel is a GUI component that provides controls for gain envelopes. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "gui/panel/AbstractPanel.h" +#include "gui/panel/AnalogGainPanel.h" +#include "gui/panel/Carousel.h" +#include "gui/panel/ModernGainPanel.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace panel { + +//============================================================================== +class GainPanel : public dmt::gui::panel::Carousel +{ +public: + GainPanel(juce::AudioProcessorValueTreeState& apvts) + : Carousel() + { + panels.push_back(std::make_unique(apvts)); + panels.push_back(std::make_unique()); + init(); + } + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(GainPanel) +}; +//============================================================================== +} // namespace panels +} // namespace gui +} // namespace dmt diff --git a/src/dmt/gui/panel/HeretikDrivePanel.h b/src/dmt/gui/panel/HeretikDrivePanel.h new file mode 100644 index 0000000..ddd699f --- /dev/null +++ b/src/dmt/gui/panel/HeretikDrivePanel.h @@ -0,0 +1,168 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * HeretikDrivePanel is one of three panels used to control the Heretik effect. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "dsp/data/FifoAudioBuffer.h" +#include "gui/component/LinearSliderComponent.h" +#include "gui/component/RotarySliderComponent.h" +#include "gui/display/DisfluxDisplay.h" +#include "gui/panel/AbstractPanel.h" +#include "utility/Settings.h" +#include "utility/Unit.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace panel { + +//============================================================================== +/** + * @brief Panel for Disflux oscilloscope display and parameter controls. + * + * @tparam SampleType The floating-point sample type for audio data. + * + * @details + * This class provides a GUI panel for the Disflux effect, including an + * oscilloscope display and various parameter sliders. It inherits from + * AbstractPanel and uses a grid layout for positioning components. + */ +template +class HeretikDrivePanel : public dmt::gui::panel::AbstractPanel +{ + using FifoAudioBuffer = dmt::dsp::data::FifoAudioBuffer; + using DisfluxDisplay = dmt::gui::display::DisfluxDisplay; + using LinearSlider = dmt::gui::component::LinearSliderComponent; + using LinearSliderType = dmt::gui::widget::LinearSlider::Type; + using LinearSliderOrientation = dmt::gui::widget::LinearSlider::Orientation; + using RotarySlider = dmt::gui::component::RotarySliderComponent; + using RotarySliderType = dmt::gui::widget::RotarySlider::Type; + using Unit = dmt::utility::Unit; + using Settings = dmt::Settings; + const float& rawPadding = Settings::Panel::padding; + +public: + //============================================================================== + /** + * @brief Constructs a DisfluxPanel. + * + * @param _apvts The AudioProcessorValueTreeState for parameter binding. + * @param _oscilloscopeBuffer The FIFO buffer for oscilloscope display. + * + * @details + * Initializes all display and slider components, sets up the grid layout, + * and adds all subcomponents to the panel. + */ + constexpr inline explicit HeretikDrivePanel( + juce::AudioProcessorValueTreeState& _apvts, + FifoAudioBuffer& _oscilloscopeBuffer) noexcept + : AbstractPanel("Oscilloscope", false) + , driveTypeSlider(_apvts, + juce::String("Type"), + juce::String("HeretikDriveType"), + Unit::Type::HeretikDriveType, + RotarySliderType::Selector) + , driveSlider(_apvts, + juce::String("Amount"), + juce::String("HeretikDrive"), + Unit::Type::HeretikDrive, + LinearSliderType::Positive, + LinearSliderOrientation::Horizontal) + , biasSlider(_apvts, + juce::String("Symmetry"), + juce::String("HeretikDriveBias"), + Unit::Type::HeretikDriveBias, + RotarySliderType::Bipolar) + { + TRACER("DisfluxPanel::DisfluxPanel"); + setLayout({ 20, 60 }); + + addAndMakeVisible(driveTypeSlider); + addAndMakeVisible(driveSlider); + addAndMakeVisible(biasSlider); + } + + //============================================================================== + /** + * @brief Handles resizing and layout of all subcomponents. + * + * @details + * Positions the oscilloscope display and all sliders using grid points, + * ensuring consistent layout regardless of panel size. + * Override from AbstractPanel. + */ + inline void extendResize() noexcept override + { + TRACER("DisfluxPanel::extendResize"); + auto bounds = getLocalBounds(); + + const int upperRotarySliderRow = 21; + const int linearSliderRow = 49; + + const int driveSliderCol = 6; + const int feedbackSliderCol = 15; + + const int leftToneSliderCol = 3; + const int rightToneSliderCol = 18; + + const auto driveSliderPoint = + this->getGridPoint(bounds, driveSliderCol, upperRotarySliderRow); + driveTypeSlider.setSizeAndCentre(driveSliderPoint); + + const auto mixSliderPoint = + this->getGridPoint(bounds, feedbackSliderCol, upperRotarySliderRow); + biasSlider.setSizeAndCentre(mixSliderPoint); + + const auto leftToneSliderPoint = + this->getGridPoint(bounds, leftToneSliderCol, linearSliderRow); + const auto rightToneSliderPoint = + this->getGridPoint(bounds, rightToneSliderCol, linearSliderRow); + driveSlider.setBoundsByPoints(leftToneSliderPoint, rightToneSliderPoint); + } + //============================================================================== + +private: + //============================================================================== + // Members initialized in the initializer list + RotarySlider driveTypeSlider; + LinearSlider driveSlider; + RotarySlider biasSlider; + + //============================================================================== + // Other members + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(HeretikDrivePanel) +}; + +} // namespace panel +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/panel/HeretikFeedbackPanel.h b/src/dmt/gui/panel/HeretikFeedbackPanel.h new file mode 100644 index 0000000..55f17ea --- /dev/null +++ b/src/dmt/gui/panel/HeretikFeedbackPanel.h @@ -0,0 +1,169 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * HeretikFeedbackPanel is one of three panels used to control the Heretik + * effect. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "dsp/data/FifoAudioBuffer.h" +#include "gui/component/LinearSliderComponent.h" +#include "gui/component/RotarySliderComponent.h" +#include "gui/display/DisfluxDisplay.h" +#include "gui/panel/AbstractPanel.h" +#include "utility/Settings.h" +#include "utility/Unit.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace panel { + +//============================================================================== +/** + * @brief Panel for Disflux oscilloscope display and parameter controls. + * + * @tparam SampleType The floating-point sample type for audio data. + * + * @details + * This class provides a GUI panel for the Disflux effect, including an + * oscilloscope display and various parameter sliders. It inherits from + * AbstractPanel and uses a grid layout for positioning components. + */ +template +class HeretikFeedbackPanel : public dmt::gui::panel::AbstractPanel +{ + using FifoAudioBuffer = dmt::dsp::data::FifoAudioBuffer; + using DisfluxDisplay = dmt::gui::display::DisfluxDisplay; + using LinearSlider = dmt::gui::component::LinearSliderComponent; + using LinearSliderType = dmt::gui::widget::LinearSlider::Type; + using LinearSliderOrientation = dmt::gui::widget::LinearSlider::Orientation; + using RotarySlider = dmt::gui::component::RotarySliderComponent; + using RotarySliderType = dmt::gui::widget::RotarySlider::Type; + using Unit = dmt::utility::Unit; + using Settings = dmt::Settings; + const float& rawPadding = Settings::Panel::padding; + +public: + //============================================================================== + /** + * @brief Constructs a DisfluxPanel. + * + * @param _apvts The AudioProcessorValueTreeState for parameter binding. + * @param _oscilloscopeBuffer The FIFO buffer for oscilloscope display. + * + * @details + * Initializes all display and slider components, sets up the grid layout, + * and adds all subcomponents to the panel. + */ + constexpr inline explicit HeretikFeedbackPanel( + juce::AudioProcessorValueTreeState& _apvts, + FifoAudioBuffer& _oscilloscopeBuffer) noexcept + : AbstractPanel("Oscilloscope", false) + , driveTypeSlider(_apvts, + juce::String("Cutoff"), + juce::String("FeedbackFilterCutoff"), + Unit::Type::HeretikFeedbackFilterCutoff, + RotarySliderType::Bipolar) + , driveSlider(_apvts, + juce::String("Feedback"), + juce::String("HeretikFeedback"), + Unit::Type::HeretikFeedback, + LinearSliderType::Positive, + LinearSliderOrientation::Horizontal) + , biasSlider(_apvts, + juce::String("Slope"), + juce::String("HeretikFeedbackFilterSlope"), + Unit::Type::HeretikFeedbackFilterSlope, + RotarySliderType::Selector) + { + TRACER("DisfluxPanel::DisfluxPanel"); + setLayout({ 20, 60 }); + + addAndMakeVisible(driveTypeSlider); + addAndMakeVisible(driveSlider); + addAndMakeVisible(biasSlider); + } + + //============================================================================== + /** + * @brief Handles resizing and layout of all subcomponents. + * + * @details + * Positions the oscilloscope display and all sliders using grid points, + * ensuring consistent layout regardless of panel size. + * Override from AbstractPanel. + */ + inline void extendResize() noexcept override + { + TRACER("DisfluxPanel::extendResize"); + auto bounds = getLocalBounds(); + + const int upperRotarySliderRow = 21; + const int linearSliderRow = 49; + + const int driveSliderCol = 6; + const int feedbackSliderCol = 15; + + const int leftToneSliderCol = 3; + const int rightToneSliderCol = 18; + + const auto driveSliderPoint = + this->getGridPoint(bounds, driveSliderCol, upperRotarySliderRow); + driveTypeSlider.setSizeAndCentre(driveSliderPoint); + + const auto mixSliderPoint = + this->getGridPoint(bounds, feedbackSliderCol, upperRotarySliderRow); + biasSlider.setSizeAndCentre(mixSliderPoint); + + const auto leftToneSliderPoint = + this->getGridPoint(bounds, leftToneSliderCol, linearSliderRow); + const auto rightToneSliderPoint = + this->getGridPoint(bounds, rightToneSliderCol, linearSliderRow); + driveSlider.setBoundsByPoints(leftToneSliderPoint, rightToneSliderPoint); + } + //============================================================================== + +private: + //============================================================================== + // Members initialized in the initializer list + RotarySlider driveTypeSlider; + LinearSlider driveSlider; + RotarySlider biasSlider; + + //============================================================================== + // Other members + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(HeretikFeedbackPanel) +}; + +} // namespace panel +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/panel/HeretikPanel.h b/src/dmt/gui/panel/HeretikPanel.h new file mode 100644 index 0000000..8263c04 --- /dev/null +++ b/src/dmt/gui/panel/HeretikPanel.h @@ -0,0 +1,203 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * DisfluxPanel is the main panel for the Disflux effect. + * It also comes with an oscilloscope display. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "dsp/data/FifoAudioBuffer.h" +#include "gui/component/LinearSliderComponent.h" +#include "gui/component/RotarySliderComponent.h" +#include "gui/display/DisfluxDisplay.h" +#include "gui/panel/AbstractPanel.h" +#include "utility/Settings.h" +#include "utility/Unit.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace panel { + +//============================================================================== +/** + * @brief Panel for Disflux oscilloscope display and parameter controls. + * + * @tparam SampleType The floating-point sample type for audio data. + * + * @details + * This class provides a GUI panel for the Disflux effect, including an + * oscilloscope display and various parameter sliders. It inherits from + * AbstractPanel and uses a grid layout for positioning components. + */ +template +class HeretikPanel : public dmt::gui::panel::AbstractPanel +{ + using FifoAudioBuffer = dmt::dsp::data::FifoAudioBuffer; + using DisfluxDisplay = dmt::gui::display::DisfluxDisplay; + using LinearSlider = dmt::gui::component::LinearSliderComponent; + using LinearSliderType = dmt::gui::widget::LinearSlider::Type; + using LinearSliderOrientation = dmt::gui::widget::LinearSlider::Orientation; + using RotarySlider = dmt::gui::component::RotarySliderComponent; + using RotarySliderType = dmt::gui::widget::RotarySlider::Type; + using Unit = dmt::utility::Unit; + using Settings = dmt::Settings; + const float& rawPadding = Settings::Panel::padding; + +public: + //============================================================================== + /** + * @brief Constructs a DisfluxPanel. + * + * @param _apvts The AudioProcessorValueTreeState for parameter binding. + * @param _oscilloscopeBuffer The FIFO buffer for oscilloscope display. + * + * @details + * Initializes all display and slider components, sets up the grid layout, + * and adds all subcomponents to the panel. + */ + constexpr inline explicit HeretikPanel( + juce::AudioProcessorValueTreeState& _apvts, + FifoAudioBuffer& _oscilloscopeBuffer) noexcept + : AbstractPanel("Oscilloscope", false) + , display(_oscilloscopeBuffer, _apvts) + , driveSlider(_apvts, + juce::String("PreGain"), + juce::String("HeretikPreGain"), + Unit::Type::HeretikPreGain, + RotarySliderType::Bipolar) + , rangeSlider(_apvts, + juce::String("Stereo"), + juce::String("HeretikSpread"), + Unit::Type::HeretikStereo, + RotarySliderType::Positive) + , toneSlider(_apvts, + juce::String("Range"), + juce::String("HeretikRange"), + Unit::Type::HeretikRange, + LinearSliderType::Positive, + LinearSliderOrientation::Horizontal) + , feedbackSlider(_apvts, + juce::String("Saturation"), + juce::String("HeretikDistortion"), + Unit::Type::HeretikDistortion, + RotarySliderType::Positive) + , mixSlider(_apvts, + juce::String("Mix"), + juce::String("HeretikMix"), + Unit::Type::HeretikMix, + RotarySliderType::Positive) + { + TRACER("DisfluxPanel::DisfluxPanel"); + setLayout({ 60, 60 }); + + addAndMakeVisible(display); + addAndMakeVisible(driveSlider); + addAndMakeVisible(rangeSlider); + addAndMakeVisible(toneSlider); + addAndMakeVisible(feedbackSlider); + addAndMakeVisible(mixSlider); + } + + //============================================================================== + /** + * @brief Handles resizing and layout of all subcomponents. + * + * @details + * Positions the oscilloscope display and all sliders using grid points, + * ensuring consistent layout regardless of panel size. + * Override from AbstractPanel. + */ + inline void extendResize() noexcept override + { + TRACER("DisfluxPanel::extendResize"); + auto bounds = getLocalBounds(); + + const float padding = rawPadding * size; + auto displayBounds = bounds.reduced(padding); + const float displayHorizontalPadding = 90.0f; + const float displayVerticalPadding = 53.0f; + displayBounds.removeFromBottom(displayVerticalPadding * size); + displayBounds.removeFromLeft(displayHorizontalPadding * size); + displayBounds.removeFromRight(displayHorizontalPadding * size); + display.setBounds(displayBounds); + + const int upperRotarySliderRow = 16; + const int lowerRotarySliderRow = 43; + const int linearSliderRow = 51; + + const int driveSliderCol = 8; + const int leftToneSliderCol = 16; + const int rightToneSliderCol = 44; + const int feedbackSliderCol = 53; + + const auto driveSliderPoint = + this->getGridPoint(bounds, driveSliderCol, upperRotarySliderRow); + driveSlider.setSizeAndCentre(driveSliderPoint); + + const auto rangeSliderPoint = + this->getGridPoint(bounds, driveSliderCol, lowerRotarySliderRow); + rangeSlider.setSizeAndCentre(rangeSliderPoint); + + const auto leftToneSliderPoint = + this->getGridPoint(bounds, leftToneSliderCol, linearSliderRow); + const auto rightToneSliderPoint = + this->getGridPoint(bounds, rightToneSliderCol, linearSliderRow); + toneSlider.setBoundsByPoints(leftToneSliderPoint, rightToneSliderPoint); + + const auto feedbackSliderPoint = + this->getGridPoint(bounds, feedbackSliderCol, lowerRotarySliderRow); + feedbackSlider.setSizeAndCentre(feedbackSliderPoint); + + const auto mixSliderPoint = + this->getGridPoint(bounds, feedbackSliderCol, upperRotarySliderRow); + mixSlider.setSizeAndCentre(mixSliderPoint); + } + //============================================================================== + +private: + //============================================================================== + // Members initialized in the initializer list + DisfluxDisplay display; + RotarySlider driveSlider; + RotarySlider rangeSlider; + LinearSlider toneSlider; + RotarySlider feedbackSlider; + RotarySlider mixSlider; + + //============================================================================== + // Other members + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(HeretikPanel) +}; + +} // namespace panel +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/panel/ModernGainPanel.h b/src/dmt/gui/panel/ModernGainPanel.h new file mode 100644 index 0000000..71dbf73 --- /dev/null +++ b/src/dmt/gui/panel/ModernGainPanel.h @@ -0,0 +1,56 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * ModernGainPanel is a GUI component that provides controls for modern gain + * envelopes. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "gui/panel/AbstractPanel.h" +#include + +//============================================================================== +namespace dmt { +namespace gui { +namespace panel { +//============================================================================== +class ModernGainPanel : public dmt::gui::panel::AbstractPanel +{ + +public: + ModernGainPanel(/*juce::AudioProcessorValueTreeState& apvts*/) + : AbstractPanel("Modern Gain Envelope") + { + } + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ModernGainPanel) +}; +//============================================================================== +} // namespace panels +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/panel/ModernOscillatorPanel.h b/src/dmt/gui/panel/ModernOscillatorPanel.h new file mode 100644 index 0000000..ebcf1d6 --- /dev/null +++ b/src/dmt/gui/panel/ModernOscillatorPanel.h @@ -0,0 +1,58 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * ModernOscillatorPanel is a GUI component that provides controls for modern + * oscillators. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "gui/panel/AbstractPanel.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace panel { + +//============================================================================== + +class ModernOscillatorPanel : public dmt::gui::panel::AbstractPanel +{ +public: + ModernOscillatorPanel(/*juce::AudioProcessorValueTreeState& apvts*/) + : AbstractPanel("Modern Oscillator") + { // + } + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ModernOscillatorPanel) +}; +//============================================================================== +} // namespace panels +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/panel/ModernPitchPanel.h b/src/dmt/gui/panel/ModernPitchPanel.h new file mode 100644 index 0000000..9f845a9 --- /dev/null +++ b/src/dmt/gui/panel/ModernPitchPanel.h @@ -0,0 +1,59 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * ModernPitchPanel is a GUI component that provides controls for a modern pitch + * envelope. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "gui/panel/AbstractPanel.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace panel { + +//============================================================================== +class ModernPitchPanel : public dmt::gui::panel::AbstractPanel +{ + +public: + ModernPitchPanel(/*juce::AudioProcessorValueTreeState& apvts*/) + : AbstractPanel("Modern Pitch Envelope") + { + // + } + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ModernPitchPanel) +}; +//============================================================================== +} // namespace panels +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/panel/OscSendPanel.h b/src/dmt/gui/panel/OscSendPanel.h new file mode 100644 index 0000000..20b6bb8 --- /dev/null +++ b/src/dmt/gui/panel/OscSendPanel.h @@ -0,0 +1,97 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * OscSendPanel is a GUI component that provides controls for oscillator send + * levels and pan. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "gui/component/LinearSliderComponent.h" +#include "gui/component/RotarySliderComponent.h" +#include "gui/panel/AbstractPanel.h" +#include "utility/Unit.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace panel { + +//============================================================================== +class OscSendPanel : public dmt::gui::panel::AbstractPanel +{ + using RotarySliderComponent = dmt::gui::component::RotarySliderComponent; + using LinearSliderComponent = dmt::gui::component::LinearSliderComponent; + using RotarySliderType = dmt::gui::widget::RotarySlider::Type; + using LinearSliderType = dmt::gui::widget::LinearSlider::Type; + using Unit = dmt::utility::Unit; + +public: + OscSendPanel(juce::AudioProcessorValueTreeState& apvts, + const juce::String channel) + : AbstractPanel(juce::String("Channel " + channel)) + , channel(channel) + , gainSlider(apvts, + juce::String("Gain"), + juce::String("osc1Send" + channel + "Gain"), + Unit::Type::Gain) + , panSlider(apvts, + juce::String("Pan"), + juce::String("osc1Send" + channel + "Pan"), + Unit::Type::Pan, + LinearSliderType::Bipolar) + { + TRACER("OscSendPanel::OscSendPanel"); + setLayout({ 3, 32 }); + addAndMakeVisible(gainSlider); + addAndMakeVisible(panSlider); + } + + void extendResize() noexcept override + { + TRACER("OscSendPanel::extendResize"); + auto bounds = getLocalBounds(); + auto gainSliderPoint = this->getGridPoint(bounds, 2, 10); + gainSlider.setSizeAndCentre(gainSliderPoint); + auto panSliderPrimaryPoint = this->getGridPoint(bounds, 1, 26); + auto panSliderSecundaryPoint = this->getGridPoint(bounds, 3, 26); + panSlider.setBoundsByPoints(panSliderPrimaryPoint, panSliderSecundaryPoint); + } + +private: + const juce::String channel; + RotarySliderComponent gainSlider; + LinearSliderComponent panSlider; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OscSendPanel) +}; +//============================================================================== +} // namespace panel +} // namespace gui +} // namespace dmt +//============================================================================== diff --git a/src/dmt/gui/panel/OscillatorPanel.h b/src/dmt/gui/panel/OscillatorPanel.h new file mode 100644 index 0000000..fd84440 --- /dev/null +++ b/src/dmt/gui/panel/OscillatorPanel.h @@ -0,0 +1,63 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * OscillatorPanel is a GUI component that provides controls for oscillator + * settings. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "gui/panel/AbstractPanel.h" +#include "gui/panel/AnalogOscillatorPanel.h" +#include "gui/panel/Carousel.h" +#include "gui/panel/ModernOscillatorPanel.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace panel { + +//============================================================================== +class OscillatorPanel : public dmt::gui::panel::Carousel +{ +public: + OscillatorPanel() + : Carousel() + { + panels.push_back(std::make_unique()); + panels.push_back(std::make_unique()); + init(); + } + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OscillatorPanel) +}; +//============================================================================== +} // namespace panels +} // namespace gui +} // namespace dmt diff --git a/src/dmt/gui/panel/OscilloscopePanel.h b/src/dmt/gui/panel/OscilloscopePanel.h new file mode 100644 index 0000000..e1eab2a --- /dev/null +++ b/src/dmt/gui/panel/OscilloscopePanel.h @@ -0,0 +1,130 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * OscilloscopePanel is a GUI component that provides controls for the + * oscilloscope display. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "gui/component/LinearSliderComponent.h" +#include "gui/display/OscilloscopeDisplay.h" +#include "gui/panel/AbstractPanel.h" +#include "utility/Unit.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace panel { + +//============================================================================== +template +class OscilloscopePanel : public dmt::gui::panel::AbstractPanel +{ + using FifoAudioBuffer = dmt::dsp::data::FifoAudioBuffer; + using OscilloscopeDisplay = + dmt::gui::display::OscilloscopeDisplay; + + using LinearSlider = dmt::gui::component::LinearSliderComponent; + using LinearSliderType = dmt::gui::widget::LinearSlider::Type; + using LinearSliderOrientation = dmt::gui::widget::LinearSlider::Orientation; + using Unit = dmt::utility::Unit; + + using Settings = dmt::Settings; + + const float& rawPadding = Settings::Panel::padding; + +public: + //============================================================================ + OscilloscopePanel(FifoAudioBuffer& fifoBuffer, + juce::AudioProcessorValueTreeState& apvts) + : AbstractPanel("Oscilloscope", false) + , oscilloscopeComponent(fifoBuffer, apvts) + , zoomSlider(apvts, + juce::String("Zoom"), + juce::String("OscilloscopeZoom"), + Unit::Type::OscilloscopeZoom, + LinearSliderType::Positive, + LinearSliderOrientation::Vertical, + true) + , thicknessSlider(apvts, + juce::String("Thickness"), + juce::String("OscilloscopeThickness"), + Unit::Type::OscilloscopeThickness, + LinearSliderType::Positive, + LinearSliderOrientation::Vertical, + true) + , heightSlider(apvts, + juce::String("Gain"), + juce::String("OscilloscopeGain"), + Unit::Type::OscilloscopeHeight, + LinearSliderType::Bipolar, + LinearSliderOrientation::Vertical, + true) + { + addAndMakeVisible(oscilloscopeComponent); + addAndMakeVisible(zoomSlider); + addAndMakeVisible(thicknessSlider); + addAndMakeVisible(heightSlider); + } + //============================================================================ + void extendResize() noexcept override + { + const auto padding = rawPadding * size; + auto bounds = getLocalBounds().reduced(padding); + + const auto sliderWidth = 38 * size; + + auto leftSliderBounds = bounds.removeFromLeft(sliderWidth); + const auto sliderOffsetX = 4.0f * size; + leftSliderBounds = + leftSliderBounds.withX(leftSliderBounds.getX() + sliderOffsetX); + zoomSlider.setBounds(leftSliderBounds); + + auto rightSliderBounds = bounds.removeFromRight(sliderWidth); + rightSliderBounds = + rightSliderBounds.withX(rightSliderBounds.getX() - sliderOffsetX); + + thicknessSlider.setBounds( + rightSliderBounds.removeFromTop(rightSliderBounds.getHeight() / 2)); + heightSlider.setBounds(rightSliderBounds); + + oscilloscopeComponent.setBounds(bounds); + } + //============================================================================ +private: + OscilloscopeDisplay oscilloscopeComponent; + LinearSlider zoomSlider; + LinearSlider thicknessSlider; + LinearSlider heightSlider; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(OscilloscopePanel) +}; + +} // namespace panel +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/panel/Panel.h b/src/dmt/gui/panel/Panel.h new file mode 100644 index 0000000..894599b --- /dev/null +++ b/src/dmt/gui/panel/Panel.h @@ -0,0 +1,50 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Panel header file. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "./AbstractPanel.h" +#include "./AnalogGainPanel.h" +#include "./AnalogOscillatorPanel.h" +#include "./AnalogPitchPanel.h" +#include "./Carousel.h" +#include "./DisfluxPanel.h" +#include "./GainPanel.h" +#include "./HeretikDrivePanel.h" +#include "./HeretikFeedbackPanel.h" +#include "./HeretikPanel.h" +#include "./ModernGainPanel.h" +#include "./ModernOscillatorPanel.h" +#include "./ModernPitchPanel.h" +#include "./OscSendPanel.h" +#include "./OscillatorPanel.h" +#include "./OscilloscopePanel.h" +#include "./PitchPanel.h" +#include "./VoicingPanel.h" +#include "./WaveformDistortionPanel.h" diff --git a/src/dmt/gui/panel/PitchPanel.h b/src/dmt/gui/panel/PitchPanel.h new file mode 100644 index 0000000..55b5268 --- /dev/null +++ b/src/dmt/gui/panel/PitchPanel.h @@ -0,0 +1,62 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * PitchPanel is a GUI component that provides controls for pitch settings. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "gui/panel/AbstractPanel.h" +#include "gui/panel/AnalogPitchPanel.h" +#include "gui/panel/Carousel.h" +#include "gui/panel/ModernPitchPanel.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace panel { + +//============================================================================== +class PitchPanel : public dmt::gui::panel::Carousel +{ +public: + PitchPanel(juce::AudioProcessorValueTreeState& apvts) + : Carousel() + { + panels.push_back(std::make_unique(apvts)); + panels.push_back(std::make_unique()); + init(); + } + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PitchPanel) +}; +//============================================================================== +} // namespace panels +} // namespace gui +} // namespace dmt diff --git a/src/dmt/gui/panel/SettingsPanel.h b/src/dmt/gui/panel/SettingsPanel.h new file mode 100644 index 0000000..a536d52 --- /dev/null +++ b/src/dmt/gui/panel/SettingsPanel.h @@ -0,0 +1,97 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * SettingsPanel is a GUI component that provides controls for global settings. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "gui/component/LinearSliderComponent.h" +#include "gui/component/RotarySliderComponent.h" +#include "gui/display/SettingsEditorDisplay.h" +#include "gui/panel/AbstractPanel.h" +#include "utility/Settings.h" +#include "utility/Unit.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace panel { + +//============================================================================== + +class SettingsPanel : public dmt::gui::panel::AbstractPanel +{ + using RotarySliderComponent = dmt::gui::component::RotarySliderComponent; + using LinearSliderComponent = dmt::gui::component::LinearSliderComponent; + using RotarySliderType = dmt::gui::widget::RotarySlider::Type; + using LinearSliderType = dmt::gui::widget::LinearSlider::Type; + using Unit = dmt::utility::Unit; + using SettingsEditorDisplay = dmt::gui::display::SettingsEditorDisplay; + using Settings = dmt::Settings; + + //============================================================================== + const float& rawPadding = Settings::Panel::padding; + +public: + SettingsPanel(/*juce::AudioProcessorValueTreeState& apvts*/) + : AbstractPanel("Settings", false) + { + TRACER("SettingsPanel::SettingsPanel"); + setLayout({ 22, 60 }); + addAndMakeVisible(settingsEditor); + } + + ~SettingsPanel() override = default; + + void extendResize() noexcept override + { + TRACER("SettingsPanel::extendResize"); + auto bounds = getLocalBounds(); + + const float padding = rawPadding * size; + auto editorBounds = bounds.reduced(padding); + const float editorHorizontalPadding = 5.0f; + const float editorTopPadding = 5.0f; + const float editorBottomPadding = 5.0f; + editorBounds.removeFromTop(editorTopPadding * size); + editorBounds.removeFromBottom(editorBottomPadding * size); + editorBounds.removeFromLeft(editorHorizontalPadding * size); + editorBounds.removeFromRight(editorHorizontalPadding * size); + settingsEditor.setBounds(editorBounds); + } + +private: + SettingsEditorDisplay settingsEditor; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SettingsPanel) +}; +//============================================================================== +} // namespace panel +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/panel/VoicingPanel.h b/src/dmt/gui/panel/VoicingPanel.h new file mode 100644 index 0000000..6cc6fa6 --- /dev/null +++ b/src/dmt/gui/panel/VoicingPanel.h @@ -0,0 +1,238 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * VoicingPanel is a GUI component that provides controls for oscillator voicing + * settings. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "gui/component/LinearSliderComponent.h" +#include "gui/component/RotarySliderComponent.h" +#include "gui/panel/AbstractPanel.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace panel { + +//============================================================================== +class VoicingPanel : public dmt::gui::panel::AbstractPanel +{ + + using RotarySliderComponent = dmt::gui::component::RotarySliderComponent; + using LinearSliderComponent = dmt::gui::component::LinearSliderComponent; + using RotarySliderType = dmt::gui::widget::RotarySlider::Type; + using LinearSliderType = dmt::gui::widget::LinearSlider::Type; + using Unit = dmt::utility::Unit; + +public: + VoicingPanel(juce::AudioProcessorValueTreeState& apvts) + : AbstractPanel("Voices") + , osctaveSlider(apvts, + juce::String("Octave"), + juce::String("osc1VoiceOctave"), + Unit::Type::Octave, + RotarySliderType::Selector) + , semitonesSlider(apvts, + juce::String("Semitone"), + juce::String("osc1VoiceSemitone"), + Unit::Type::Semitone, + RotarySliderType::Selector) + , fineSlider(apvts, + juce::String("Fine"), + juce::String("osc1VoiceFine"), + Unit::Type::Cents, + LinearSliderType::Bipolar) + , densitySlider(apvts, + juce::String("Density"), + juce::String("osc1VoiceDensity"), + Unit::Type::VoiceDensity, + RotarySliderType::Selector) + , detuneSlider(apvts, + juce::String("Detune"), + juce::String("osc1VoiceDetune"), + Unit::Type::Cents) + , distributionSlider(apvts, + juce::String("Distibution"), + juce::String("osc1VoiceDistribution"), + Unit::Type::VoiceDistribution) + , widthSlider(apvts, + juce::String("Width"), + juce::String("osc1VoiceWidth"), + Unit::Type::Percent, + LinearSliderType::Positive) + , blendSlider(apvts, + juce::String("Bend"), + juce::String("osc1VoiceBlend"), + Unit::Type::Percent, + LinearSliderType::Positive) + , seedSlider(apvts, + juce::String("Seed"), + juce::String("osc1VoiceSeed"), + Unit::Type::Seed, + RotarySliderType::Selector) + , randomlider(apvts, + juce::String("Random"), + juce::String("osc1VoiceRandom"), + Unit::Type::Percent) + + , phaseSlider(apvts, + juce::String("Phase"), + juce::String("osc1VoicePhase"), + Unit::Type::Degree, + LinearSliderType::Positive) + { + TRACER("VoicingPanel::VoicingPanel"); + setLayout({ 31, 32 }); + + addAndMakeVisible(osctaveSlider); + addAndMakeVisible(semitonesSlider); + addAndMakeVisible(fineSlider); + + addAndMakeVisible(densitySlider); + addAndMakeVisible(detuneSlider); + addAndMakeVisible(distributionSlider); + addAndMakeVisible(widthSlider); + addAndMakeVisible(blendSlider); + + addAndMakeVisible(seedSlider); + addAndMakeVisible(randomlider); + addAndMakeVisible(phaseSlider); + } + void extendResize() noexcept override + { + TRACER("VoicingPanel::extendResize"); + const auto bounds = getLocalBounds(); + const int rotarySliderRow = 10; + const int linearSliderRow = 26; + + //============================================================================== + // Left Layout + const int osctaveSliderCol = 3; + const int semitoneSliderCol = 7; + + const auto osctaveSliderPoint = + this->getGridPoint(bounds, osctaveSliderCol, rotarySliderRow); + osctaveSlider.setSizeAndCentre(osctaveSliderPoint); + + const auto semitoneSliderPoint = + this->getGridPoint(bounds, semitoneSliderCol, rotarySliderRow); + semitonesSlider.setSizeAndCentre(semitoneSliderPoint); + + const int fineSliderPrimaryCol = 2; + const int fineSliderSecondaryCol = 8; + + const auto fineSliderPrimaryPoint = + this->getGridPoint(bounds, fineSliderPrimaryCol, linearSliderRow); + const auto fineSliderSecondaryPoint = + this->getGridPoint(bounds, fineSliderSecondaryCol, linearSliderRow); + fineSlider.setBoundsByPoints(fineSliderPrimaryPoint, + fineSliderSecondaryPoint); + //============================================================================== + // Middle Layout + const int densitySliderCol = 12; + const int detuneSliderCol = 16; + const int distributionSliderCol = 20; + + const auto densitySliderPoint = + this->getGridPoint(bounds, densitySliderCol, rotarySliderRow); + densitySlider.setSizeAndCentre(densitySliderPoint); + + const auto detuneSliderPoint = + this->getGridPoint(bounds, detuneSliderCol, rotarySliderRow); + detuneSlider.setSizeAndCentre(detuneSliderPoint); + + const auto distributionSliderPoint = + this->getGridPoint(bounds, distributionSliderCol, rotarySliderRow); + distributionSlider.setSizeAndCentre(distributionSliderPoint); + + const int widthSliderPrimaryCol = 10; + const int widthSliderSecondaryCol = 15; + const int blendSliderPrimaryCol = 17; + const int blendSliderSecondaryCol = 22; + + const auto blendSliderPrimaryPoint = + this->getGridPoint(bounds, widthSliderPrimaryCol, linearSliderRow); + const auto blendSliderSecondaryPoint = + this->getGridPoint(bounds, widthSliderSecondaryCol, linearSliderRow); + blendSlider.setBoundsByPoints(blendSliderPrimaryPoint, + blendSliderSecondaryPoint); + + const auto widthSliderPrimaryPoint = + this->getGridPoint(bounds, blendSliderPrimaryCol, linearSliderRow); + const auto widthSliderSecondaryPoint = + this->getGridPoint(bounds, blendSliderSecondaryCol, linearSliderRow); + widthSlider.setBoundsByPoints(widthSliderPrimaryPoint, + widthSliderSecondaryPoint); + + //============================================================================== + // Right Layout + const int seedSliderCol = 25; + const int randomSliderCol = 29; + + const auto seedSliderPoint = + this->getGridPoint(bounds, seedSliderCol, rotarySliderRow); + seedSlider.setSizeAndCentre(seedSliderPoint); + + const auto randomSliderPoint = + this->getGridPoint(bounds, randomSliderCol, rotarySliderRow); + randomlider.setSizeAndCentre(randomSliderPoint); + + const int phaseSliderPrimaryCol = 24; + const int phaseSliderSecondaryCol = 30; + + const auto phaseSliderPrimaryPoint = + this->getGridPoint(bounds, phaseSliderPrimaryCol, linearSliderRow); + const auto phaseSliderSecondaryPoint = + this->getGridPoint(bounds, phaseSliderSecondaryCol, linearSliderRow); + phaseSlider.setBoundsByPoints(phaseSliderPrimaryPoint, + phaseSliderSecondaryPoint); + } + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(VoicingPanel) + RotarySliderComponent osctaveSlider; + RotarySliderComponent semitonesSlider; + + RotarySliderComponent densitySlider; + RotarySliderComponent detuneSlider; + RotarySliderComponent distributionSlider; + + RotarySliderComponent seedSlider; + RotarySliderComponent randomlider; + + LinearSliderComponent fineSlider; + LinearSliderComponent widthSlider; + LinearSliderComponent blendSlider; + LinearSliderComponent phaseSlider; +}; +//============================================================================== +} // namespace panels +} // namespace gui +} // namespace dmt diff --git a/src/dmt/gui/panel/WaveformDistortionPanel.h b/src/dmt/gui/panel/WaveformDistortionPanel.h new file mode 100644 index 0000000..fbdc75f --- /dev/null +++ b/src/dmt/gui/panel/WaveformDistortionPanel.h @@ -0,0 +1,142 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * WaveformDistortionPanel is a GUI component that provides controls for + * waveform distortion settings. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "gui/component/LinearSliderComponent.h" +#include "gui/component/RotarySliderComponent.h" +#include "gui/panel/AbstractPanel.h" +#include "utility/Unit.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace panel { + +//============================================================================== +class WaveformDistortionPanel : public dmt::gui::panel::AbstractPanel +{ + using RotarySliderComponent = dmt::gui::component::RotarySliderComponent; + using LinearSliderComponent = dmt::gui::component::LinearSliderComponent; + using RotarySliderType = dmt::gui::widget::RotarySlider::Type; + using LinearSliderType = dmt::gui::widget::LinearSlider::Type; + using Unit = dmt::utility::Unit; + +public: + WaveformDistortionPanel(juce::AudioProcessorValueTreeState& apvts) + : AbstractPanel("Waveform Distortion") + , typeSlider(apvts, + juce::String("Type"), + juce::String("osc1DistortionType"), + Unit::Type::DistortionType, + RotarySliderType::Selector) + , gainSlider(apvts, + juce::String("Pregain"), + juce::String("osc1DistortionPreGain"), + Unit::Type::Gain) + , driveSlider(apvts, + juce::String("Drive"), + juce::String("osc1DistortionDrive"), + Unit::Type::Drive) + , biasSlider(apvts, + juce::String("Symmetry"), + juce::String("osc1DistortionSymmetry"), + Unit::Type::Symmetry, + LinearSliderType::Bipolar) + , crushSlider(apvts, + juce::String("Bitcrush"), + juce::String("osc1DistortionCrush"), + Unit::Type::Bitdepth) + { + TRACER("WaveformDistortionPanel::WaveformDistortionPanel"); + setLayout({ 17, 32 }); + addAndMakeVisible(typeSlider); + addAndMakeVisible(driveSlider); + addAndMakeVisible(gainSlider); + addAndMakeVisible(biasSlider); + addAndMakeVisible(crushSlider); + } + void extendResize() noexcept override + { + TRACER("WaveformDistortionPanel::extendResize"); + const auto bounds = getLocalBounds(); + const int rotarySliderRow = 10; + const int linearSliderRow = 26; + + const int typeSliderCol = 4; + const int gainSliderCol = 9; + const int driveSliderCol = 14; + + const int biasPrimaryCol = 2; + const int biasSecondaryCol = 8; + const int crushPrimaryCol = 10; + const int crushSecondaryCol = 16; + + const auto typeSliderPoint = + this->getGridPoint(bounds, typeSliderCol, rotarySliderRow); + typeSlider.setSizeAndCentre(typeSliderPoint); + + const auto gainSliderPoint = + this->getGridPoint(bounds, gainSliderCol, rotarySliderRow); + gainSlider.setSizeAndCentre(gainSliderPoint); + + const auto driveSliderPoint = + this->getGridPoint(bounds, driveSliderCol, rotarySliderRow); + driveSlider.setSizeAndCentre(driveSliderPoint); + + const auto biasSliderPrimaryPoint = + this->getGridPoint(bounds, biasPrimaryCol, linearSliderRow); + const auto biasSliderSecondaryPoint = + this->getGridPoint(bounds, biasSecondaryCol, linearSliderRow); + biasSlider.setBoundsByPoints(biasSliderPrimaryPoint, + biasSliderSecondaryPoint); + + const auto crushSliderPrimaryPoint = + this->getGridPoint(bounds, crushPrimaryCol, linearSliderRow); + const auto crushSliderSecondaryPoint = + this->getGridPoint(bounds, crushSecondaryCol, linearSliderRow); + crushSlider.setBoundsByPoints(crushSliderPrimaryPoint, + crushSliderSecondaryPoint); + } + + RotarySliderComponent typeSlider; + RotarySliderComponent gainSlider; + RotarySliderComponent driveSlider; + LinearSliderComponent biasSlider; + LinearSliderComponent crushSlider; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(WaveformDistortionPanel) +}; +//============================================================================== +} // namespace panel +} // namespace gui +} // namespace dmt +//============================================================================== \ No newline at end of file diff --git a/src/dmt/gui/preset/FolderManager.h b/src/dmt/gui/preset/FolderManager.h new file mode 100644 index 0000000..29076b9 --- /dev/null +++ b/src/dmt/gui/preset/FolderManager.h @@ -0,0 +1,75 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * FolderManager is responsible for managing preset folders within the plugin's + * preset system. It listens to changes in the AudioProcessorValueTreeState to + * update the current folder and maintain a list of available folders. It also + * ensures that a default directory for presets exists and is accessible. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace preset { +class FolderManager : juce::ValueTree::Listener +{ +public: + inline static const juce::File& defaultDirectory = + juce::File::getSpecialLocation( + juce::File::SpecialLocationType::commonDocumentsDirectory) + .getChildFile(ProjectInfo::companyName) + .getChildFile(ProjectInfo::projectName); + + inline static const juce::String& folderNameProperty = "folderName"; + + FolderManager(juce::AudioProcessorValueTreeState& apvts) + : valueTreeState(apvts) + { + // Create default directory + if (!defaultDirectory.exists()) { + const auto result = defaultDirectory.createDirectory(); + if (result.failed()) { + DBG("Could not create preset directory: " + result.getErrorMessage()); + jassertfalse; + } + } + valueTreeState.state.addListener(this); + currentFolder.referTo( + valueTreeState.state.getPropertyAsValue(folderNameProperty, nullptr)); + } + +private: + //============================================================================== + juce::AudioProcessorValueTreeState& valueTreeState; + juce::StringArray folderList; + juce::Value currentFolder; +}; +} // namespace preset +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/preset/FolderPanel.h b/src/dmt/gui/preset/FolderPanel.h new file mode 100644 index 0000000..b7c2ebf --- /dev/null +++ b/src/dmt/gui/preset/FolderPanel.h @@ -0,0 +1,116 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * FolderPanel is a GUI component that provides an interface for managing preset + * folders. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== +*/ + +#pragma once + +#include + + //============================================================================== + + namespace dmt +{ + namespace gui { + namespace preset { + class FolderPanel + : public juce::Component + , juce::Button::Listener + , juce::ComboBox::Listener + { + public: + FolderPanel() + { + configureButton(newButton, "New"); + configureButton(previousFolderButton, "<"); + configureButton(nextFolderButton, ">"); + configureButton(deleteButton, "Delete"); + + folderListBox.setTextWhenNothingSelected("No Folder Selected"); + folderListBox.setTextWhenNoChoicesAvailable("No Folder Available"); + folderListBox.setMouseCursor(juce::MouseCursor::PointingHandCursor); + addAndMakeVisible(folderListBox); + folderListBox.addListener(this); + } + + ~FolderPanel() override + { + newButton.removeListener(this); + previousFolderButton.removeListener(this); + nextFolderButton.removeListener(this); + deleteButton.removeListener(this); + folderListBox.removeListener(this); + } + + void paint(juce::Graphics&) override {} + + void resized() override + { + auto padding = 1; + const auto container = getLocalBounds().reduced(padding); + auto bounds = container; + + newButton.setBounds( + bounds.removeFromLeft(container.proportionOfWidth(0.2)) + .reduced(padding)); + previousFolderButton.setBounds( + bounds.removeFromLeft(container.proportionOfWidth(0.1)) + .reduced(padding)); + folderListBox.setBounds( + bounds.removeFromLeft(container.proportionOfWidth(0.4)) + .reduced(padding)); + nextFolderButton.setBounds( + bounds.removeFromLeft(container.proportionOfWidth(0.1)) + .reduced(padding)); + deleteButton.setBounds(bounds.reduced(padding)); + } + + private: + void buttonClicked(juce::Button* button) override {} + + void comboBoxChanged(juce::ComboBox* comboBoxThatHasChanged) override {} + + void configureButton(juce::Button& button, const juce::String& buttontext) + { + button.setButtonText(buttontext); + button.setMouseCursor(juce::MouseCursor::PointingHandCursor); + addAndMakeVisible(button); + button.addListener(this); + } + + juce::TextButton newButton; + juce::TextButton previousFolderButton; + juce::TextButton nextFolderButton; + juce::TextButton deleteButton; + + juce::ComboBox folderListBox; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FolderPanel) + }; + } // namespace preset + } // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/preset/PresetManager.h b/src/dmt/gui/preset/PresetManager.h new file mode 100644 index 0000000..1548b80 --- /dev/null +++ b/src/dmt/gui/preset/PresetManager.h @@ -0,0 +1,188 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * PresetManager is responsible for managing presets within the plugin's preset + * system. It provides functionality to save, load, and delete presets, as well + * as navigate through available presets. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +#include + +//============================================================================== +namespace dmt { +namespace gui { +namespace preset { +class PresetManager : juce::ValueTree::Listener +{ +public: + inline static const juce::File& defaultDirectory = + juce::File::getSpecialLocation( + juce::File::SpecialLocationType::userDocumentsDirectory) + .getChildFile(ProjectInfo::companyName) + .getChildFile(ProjectInfo::projectName); + + inline static const juce::String& extension = "preset"; + inline static const juce::String& presetNameProperty = "presetName"; + + //============================================================================== + PresetManager(juce::AudioProcessorValueTreeState& apvts) + : valueTreeState(apvts) + { + // Create default directory + if (!defaultDirectory.exists()) { + const auto result = defaultDirectory.createDirectory(); + if (result.failed()) { + DBG("Could not create preset directory: " + result.getErrorMessage()); + jassertfalse; + } + } + valueTreeState.state.addListener(this); + currentPreset.referTo( + valueTreeState.state.getPropertyAsValue(presetNameProperty, nullptr)); + } + + //============================================================================== + void savePreset(const juce::String& presetName) + { + if (presetName.isEmpty()) + return; + currentPreset.setValue(presetName); + const auto xml = valueTreeState.copyState().createXml(); + const auto presetFile = + defaultDirectory.getChildFile(presetName + "." + extension); + if (!xml->writeTo(presetFile)) { + DBG("Could not create preset file: " + presetFile.getFullPathName()); + jassertfalse; + } + } + + void loadPreset(const juce::String& presetName) + { + if (presetName.isEmpty()) + return; + + const auto presetFile = + defaultDirectory.getChildFile(presetName + "." + extension); + if (!presetFile.existsAsFile()) { + DBG("Preset file does not exist: " + presetFile.getFullPathName()); + jassertfalse; + return; + } + + juce::XmlDocument xmlDocument{ presetFile }; + const auto valueTreeToLoad = + juce::ValueTree::fromXml(*xmlDocument.getDocumentElement()); + for (size_t i = 0; i < valueTreeToLoad.getNumChildren(); i++) { + const auto parameterChildToLoad = valueTreeToLoad.getChild(i); + const auto parameterId = parameterChildToLoad.getProperty("id"); + auto parameterTree = + valueTreeState.state.getChildWithProperty("id", parameterId); + if (parameterTree.isValid()) { + parameterTree.copyPropertiesFrom(parameterChildToLoad, nullptr); + } + } + currentPreset.setValue(presetName); + } + + void loadInitPreset() + { + // valueTreeState. + } + + void deletePreset(const juce::String& presetName) + { + if (presetName.isEmpty()) + return; + + const auto presetFile = + defaultDirectory.getChildFile(presetName + "." + extension); + if (!presetFile.existsAsFile()) { + DBG("Preset file does not exist: " + presetFile.getFullPathName()); + jassertfalse; + return; + } + if (!presetFile.deleteFile()) { + DBG("Could not delete preset file: " + presetFile.getFullPathName()); + jassertfalse; + return; + } + currentPreset = ""; + } + + //============================================================================== + int loadNextPreset() + { + const auto presetList = getPresetList(); + if (presetList.isEmpty()) + return -1; + const auto currentIndex = presetList.indexOf(currentPreset.toString()); + const auto nextIndex = + (currentIndex == (presetList.size() - 1)) ? 0 : currentIndex + 1; + loadPreset(presetList[nextIndex]); + return nextIndex; + } + + int loadPreviousPreset() + { + const auto presetList = getPresetList(); + if (presetList.isEmpty()) + return -1; + const auto currentIndex = presetList.indexOf(currentPreset.toString()); + const auto previousIndex = + (currentIndex == 0) ? presetList.size() - 1 : currentIndex - 1; + loadPreset(presetList[previousIndex]); + return previousIndex; + } + + //============================================================================== + juce::StringArray getPresetList() + { + juce::StringArray presetList; + auto fileArray = defaultDirectory.findChildFiles( + juce::File::TypesOfFileToFind::findFiles, false, "*." + extension); + for (const auto& file : fileArray) { + presetList.add(file.getFileNameWithoutExtension()); + } + return presetList; + } + + juce::String getCurrentPreset() const { return currentPreset.toString(); } + + void valueTreeRedirected( + juce::ValueTree& /*treeWhichHasBeenChanged*/) override + { + currentPreset.referTo( + valueTreeState.state.getPropertyAsValue(presetNameProperty, nullptr)); + } + +private: + //============================================================================== + juce::AudioProcessorValueTreeState& valueTreeState; + juce::Value currentPreset; +}; +} // namespace preset +} // namespace gui +} // namespace dmt diff --git a/src/dmt/gui/preset/PresetPanel.h b/src/dmt/gui/preset/PresetPanel.h new file mode 100644 index 0000000..b0af5db --- /dev/null +++ b/src/dmt/gui/preset/PresetPanel.h @@ -0,0 +1,162 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * PresetPanel is a GUI component that provides an interface for managing + * presets in a plugin. It allows users to save, load, and delete presets, as + * well as navigate through them using previous and next buttons. It also + * includes a combo box for selecting presets from a list. + * + * Authors: Lunix-420 + * (Primary Author) + */ +//============================================================================== + +#pragma once + +#include "PresetManager.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace preset { +class PresetPanel + : public juce::Component + , juce::Button::Listener + , juce::ComboBox::Listener +{ +public: + PresetPanel(dmt::gui::preset::PresetManager& pm) + : presetManager(pm) + { + configureButton(saveButton, "Save"); + configureButton(previousPresetButton, "<"); + configureButton(nextPresetButton, ">"); + configureButton(deleteButton, "Delete"); + + presetListBox.setTextWhenNothingSelected("No Preset Selected"); + presetListBox.setTextWhenNoChoicesAvailable("No Presets Available"); + presetListBox.setMouseCursor(juce::MouseCursor::PointingHandCursor); + addAndMakeVisible(presetListBox); + presetListBox.addListener(this); + + loadPresetList(); + } + + ~PresetPanel() override + { + saveButton.removeListener(this); + previousPresetButton.removeListener(this); + nextPresetButton.removeListener(this); + deleteButton.removeListener(this); + presetListBox.removeListener(this); + } + + void paint(juce::Graphics&) override {} + + void resized() override + { + auto padding = 1; + const auto container = getLocalBounds().reduced(padding); + auto bounds = container; + + saveButton.setBounds( + bounds.removeFromLeft(container.proportionOfWidth(0.2)).reduced(padding)); + previousPresetButton.setBounds( + bounds.removeFromLeft(container.proportionOfWidth(0.1)).reduced(padding)); + presetListBox.setBounds( + bounds.removeFromLeft(container.proportionOfWidth(0.4)).reduced(padding)); + nextPresetButton.setBounds( + bounds.removeFromLeft(container.proportionOfWidth(0.1)).reduced(padding)); + deleteButton.setBounds(bounds.reduced(padding)); + } + +private: + void buttonClicked(juce::Button* button) override + { + if (button == &saveButton) { + fileChooser = std::make_unique( + "Save Preset", + dmt::gui::preset::PresetManager::defaultDirectory, + "*." + dmt::gui::preset::PresetManager::extension); + + fileChooser->launchAsync(juce::FileBrowserComponent::saveMode, + [&](const juce::FileChooser& chooser) { + const auto result = chooser.getResult(); + presetManager.savePreset( + result.getFileNameWithoutExtension()); + loadPresetList(); + }); + } + if (button == &previousPresetButton) { + const auto index = presetManager.loadPreviousPreset(); + presetListBox.setSelectedItemIndex(index, juce::dontSendNotification); + } + if (button == &nextPresetButton) { + const auto index = presetManager.loadNextPreset(); + presetListBox.setSelectedItemIndex(index, juce::dontSendNotification); + } + if (button == &deleteButton) { + presetManager.deletePreset(presetManager.getCurrentPreset()); + loadPresetList(); + } + } + + void comboBoxChanged(juce::ComboBox* comboBoxThatHasChanged) override + { + if (comboBoxThatHasChanged == &presetListBox) { + presetManager.loadPreset( + presetListBox.getItemText(presetListBox.getSelectedItemIndex())); + } + } + + void loadPresetList() + { + presetListBox.clear(juce::dontSendNotification); + const auto allPresets = presetManager.getPresetList(); + const auto currentPreset = presetManager.getCurrentPreset(); + presetListBox.addItemList(allPresets, 1); + presetListBox.setSelectedItemIndex(allPresets.indexOf(currentPreset), + juce::dontSendNotification); + } + + void configureButton(juce::Button& button, const juce::String& buttontext) + { + button.setButtonText(buttontext); + button.setMouseCursor(juce::MouseCursor::PointingHandCursor); + addAndMakeVisible(button); + button.addListener(this); + } + + dmt::gui::preset::PresetManager presetManager; + std::unique_ptr fileChooser; + juce::TextButton saveButton; + juce::TextButton previousPresetButton; + juce::TextButton nextPresetButton; + juce::TextButton deleteButton; + + juce::ComboBox presetListBox; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PresetPanel) +}; +} // namespace preset +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/widget/AbstractButton.h b/src/dmt/gui/widget/AbstractButton.h new file mode 100644 index 0000000..39bd5cb --- /dev/null +++ b/src/dmt/gui/widget/AbstractButton.h @@ -0,0 +1,482 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * AbstractButton is a customizable button class that supports shadows, icons, + * and tooltips. It is optimized for real-time performance and designed for + * use in GUI applications. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "gui/widget/Shadow.h" +#include "utility/Icon.h" +#include "utility/Scaleable.h" +#include "utility/Settings.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace widget { + +//============================================================================== +/** + * @class AbstractButton + * @brief A customizable button class with support for shadows, icons, and + * tooltips. + * + * AbstractButton extends the JUCE Button class and provides additional + * functionality for rendering shadows, icons, and background states. + * + * @details This class is designed for GUI applications requiring visually + * appealing and interactive buttons. It supports hover and click states, + * customizable colors, and tooltips. The rationale for this design is to + * centralize button appearance logic and maximize real-time GUI performance. + */ +class AbstractButton + : public juce::Button + , public dmt::Scaleable +{ + using String = juce::String; + using Settings = dmt::Settings; + using Colour = juce::Colour; + using Image = juce::Image; + using ImageComponent = juce::ImageComponent; + using ButtonSettings = dmt::Settings::Button; + + //============================================================================== + // Button + const Colour& backgroundColour = ButtonSettings::backgroundColour; + const Colour& outerShadowColour = ButtonSettings::outerShadowColour; + const Colour& innerShadowColour = ButtonSettings::innerShadowColour; + const Colour& fontColour = ButtonSettings::fontColour; + const Colour& hoverColour = ButtonSettings::hoverColour; + const Colour& clickColour = ButtonSettings::clickColour; + const float& rawCornerRadius = ButtonSettings::cornerRadius; + const float& rawButtonPadding = ButtonSettings::padding; + const float& outerShadowRadius = ButtonSettings::outerShadowRadius; + const float& innerShadowRadius = ButtonSettings::innerShadowRadius; + const bool& drawOuterShadow = ButtonSettings::drawOuterShadow; + const bool& drawInnerShadow = ButtonSettings::drawInnerShadow; + +public: + //============================================================================== + /** + * @brief Constructs an AbstractButton instance. + * + * @param _name The name of the button. + * @param _iconName The name of the icon to display on the button. + * @param _tooltip The tooltip text for the button. + * @param _shouldDrawBorder Whether to draw a border around the button. + * @param _shouldDrawBackground Whether to draw a background for the button. + * @param _shouldDrawShadow Whether to draw shadows for the button. + * @param _alternativeIconHover Whether to use hoverColour or black for the + * icons hover state. + * + * @details The constructor initializes all visual and interactive states, + * including icon, shadow, and background components. All members are + * initialized in the order they appear in the initializer list for + * performance and clarity. + */ + explicit AbstractButton(String _name, + String _iconName, + String _tooltip = "", + bool _shouldDrawBorder = true, + bool _shouldDrawBackground = true, + bool _shouldDrawShadow = true, + bool _alternativeIconHover = false) noexcept + : juce::Button(_name) + , tooltip(_tooltip) + , shouldDrawBorder(_shouldDrawBorder) + , shouldDrawBackground(_shouldDrawBackground) + , shouldDrawShadows(_shouldDrawShadow) + , alternativeIconHover(_alternativeIconHover) + , rawSpecificSvgPadding(dmt::icons::getPadding(_iconName)) + , outerShadow(drawOuterShadow, outerShadowColour, outerShadowRadius, false) + , innerShadow(drawInnerShadow, innerShadowColour, innerShadowRadius, true) + { + TRACER("AbstractButton::AbstractButton"); + icon = dmt::icons::getIcon(_iconName); + + if (shouldDrawShadows) { + addAndMakeVisible(outerShadow); + addAndMakeVisible(innerShadow); + } + + if (shouldDrawBackground) { + addAndMakeVisible(backgroundImageComponent); + addAndMakeVisible(hoverBackgroundImageComponent); + addAndMakeVisible(clickedBackgroundImageComponent); + hoverBackgroundImageComponent.setVisible(false); + clickedBackgroundImageComponent.setVisible(false); + } + + addAndMakeVisible(iconImageComponent); + addAndMakeVisible(hoverIconImageComponent); + hoverIconImageComponent.setVisible(false); + + addMouseListener(this, true); + } + + //============================================================================== + /** + * @brief Destructor for AbstractButton. + * + * @details Virtual and defaulted for safe polymorphic destruction. + */ + inline ~AbstractButton() override = default; + + //============================================================================== + /** + * @brief Resizes the button and its components. + * + * @details This method recalculates the bounds for shadows, background, and + * icons based on the button's size and padding. It is called by the JUCE + * framework when the component is resized, ensuring all visuals remain + * pixel-perfect and performant. + */ + inline void resized() override + { + TRACER("AbstractButton::resized"); + auto bounds = getLocalBounds(); + const auto buttonPadding = rawButtonPadding * size; + auto innerBounds = bounds.reduced(buttonPadding); + const auto cornerRadius = rawCornerRadius * size; + + setShadowBounds(innerBounds, cornerRadius); + setBackgroundBounds(innerBounds); + setIconBounds(innerBounds); + drawBackground(); + drawIcon(); + } + + //============================================================================== + /** + * @brief Retrieves the tooltip text for the button. + * @return The tooltip text as a String. + * + * @details Used by JUCE's tooltip system to display contextual help. + */ + [[nodiscard]] inline String getTooltip() override + { + TRACER("AbstractButton::getTooltip"); + return tooltip; + } + + //============================================================================== + /** + * @brief Sets the button to its passive (default) state. + * + * @details This method is called to visually reset the button to its + * non-hover, non-clicked state. Used for consistent state management. + */ + inline void setPassiveState() + { + TRACER("AbstractButton::setPassiveState"); + if (shouldDrawBackground) { + backgroundImageComponent.setVisible(true); + hoverBackgroundImageComponent.setVisible(false); + clickedBackgroundImageComponent.setVisible(false); + } + iconImageComponent.setVisible(true); + hoverIconImageComponent.setVisible(false); + } + + //============================================================================== + /** + * @brief Sets the button to its hover state. + * + * @details Called when the mouse hovers over the button, updating visuals + * for immediate user feedback. + */ + inline void setHoverState() + { + TRACER("AbstractButton::setHoverState"); + if (shouldDrawBackground) { + backgroundImageComponent.setVisible(false); + hoverBackgroundImageComponent.setVisible(true); + clickedBackgroundImageComponent.setVisible(false); + } + iconImageComponent.setVisible(false); + hoverIconImageComponent.setVisible(true); + } + + //============================================================================== + /** + * @brief Sets the button to its clicked state. + * + * @details Called when the button is pressed, updating visuals for + * immediate user feedback. + */ + inline void setClickedState() + { + TRACER("AbstractButton::setClickedState"); + if (shouldDrawBackground) { + backgroundImageComponent.setVisible(false); + hoverBackgroundImageComponent.setVisible(false); + clickedBackgroundImageComponent.setVisible(true); + } + iconImageComponent.setVisible(false); + hoverIconImageComponent.setVisible(true); + } + + //============================================================================== + /** + * @brief Paints the button. This method is intentionally left empty. + * + * @param g The graphics context. + * @param isMouseOverButton Whether the mouse is over the button. + * @param isButtonDown Whether the button is pressed. + * + * @details All painting is handled by subcomponents for maximum flexibility + * and performance. This override is required by JUCE. + */ + inline void paintButton(juce::Graphics& /*_g*/, + bool /*_isMouseOverButton*/, + bool /*_isButtonDown*/) override + { + TRACER("AbstractButton::paintButton"); + } + +private: + //============================================================================== + /** + * @brief Sets the bounds for the shadows. + * + * @param _innerBounds The inner bounds of the button. + * @param _cornerRadius The corner radius for the shadows. + * + * @details This method ensures that both inner and outer shadows are + * correctly sized and layered for visual consistency. Uses explicit + * casting for type safety. + */ + inline void setShadowBounds(const juce::Rectangle& _innerBounds, + float _cornerRadius) + { + TRACER("AbstractButton::setShadowBounds"); + if (!shouldDrawShadows) + return; + + // Set the bounds for the outer shadow + juce::Path outerShadowPath; + outerShadowPath.addRoundedRectangle(_innerBounds, _cornerRadius); + outerShadow.setPath(outerShadowPath); + outerShadow.setBoundsRelative(0.0f, 0.0f, 1.0f, 1.0f); + + // Set the bounds for the inner shadow + juce::Path innerShadowPath; + innerShadowPath.addRoundedRectangle(_innerBounds, _cornerRadius); + innerShadow.setPath(innerShadowPath); + innerShadow.setBoundsRelative(0.0f, 0.0f, 1.0f, 1.0f); + + // Reorder the components + innerShadow.toBack(); + outerShadow.toBack(); + clickedBackgroundImageComponent.toBack(); + hoverBackgroundImageComponent.toBack(); + backgroundImageComponent.toBack(); + } + + //============================================================================== + /** + * @brief Sets the bounds for the background components. + * + * @param _innerBounds The inner bounds of the button. + * + * @details Ensures that all background images are sized to match the + * button's inner area for pixel-perfect rendering. + */ + inline void setBackgroundBounds(const juce::Rectangle& _innerBounds) + { + TRACER("AbstractButton::setBackgroundBounds"); + if (!shouldDrawBackground) + return; + + if (_innerBounds.getWidth() <= 0 || _innerBounds.getHeight() <= 0) + return; + + // HiDPI support: render background images at higher resolution + const int hiResWidth = static_cast(_innerBounds.getWidth() * scale); + const int hiResHeight = static_cast(_innerBounds.getHeight() * scale); + + backgroundImage = Image(Image::ARGB, hiResWidth, hiResHeight, true); + backgroundImageComponent.setBounds(_innerBounds); + hoverBackgroundImage = Image(Image::ARGB, hiResWidth, hiResHeight, true); + hoverBackgroundImageComponent.setBounds(_innerBounds); + clickedBackgroundImage = Image(Image::ARGB, hiResWidth, hiResHeight, true); + clickedBackgroundImageComponent.setBounds(_innerBounds); + } + + //============================================================================== + /** + * @brief Sets the bounds for the icon components. + * + * @param _innerBounds The inner bounds of the button. + * + * @details Calculates icon area using both global and icon-specific + * padding, ensuring icons are always centered and visually balanced. + */ + inline void setIconBounds(const juce::Rectangle& _innerBounds) + { + TRACER("AbstractButton::setIconBounds"); + const auto specificSvgPadding = rawSpecificSvgPadding * size; + const auto globalSvgPadding = 2.5f * size; + const auto svgPadding = specificSvgPadding + globalSvgPadding; + + const auto iconArea = _innerBounds.reduced(svgPadding); + + if (iconArea.getWidth() <= 0 || iconArea.getHeight() <= 0) + return; + + // Render the icon images at higher resolution + const int hiResWidth = static_cast(iconArea.getWidth() * scale); + const int hiResHeight = static_cast(iconArea.getHeight() * scale); + + iconImage = juce::Image(juce::Image::ARGB, hiResWidth, hiResHeight, true); + iconImageComponent.setBounds(iconArea); + + hoverIconImage = + juce::Image(juce::Image::ARGB, hiResWidth, hiResHeight, true); + hoverIconImageComponent.setBounds(iconArea); + } + + //============================================================================== + /** + * @brief Draws the background components. + * + * @details Fills each background image with the appropriate color and + * rounded rectangle, using explicit casting for type safety. + */ + inline void drawBackground() + { + TRACER("AbstractButton::drawBackground"); + if (!shouldDrawBackground) + return; + + const auto cornerRadius = rawCornerRadius * size; + // Use the hi-res image bounds, but draw at logical size + const auto hiResBackgroundArea = backgroundImage.getBounds().toFloat(); + const auto logicalWidth = hiResBackgroundArea.getWidth() / scale; + const auto logicalHeight = hiResBackgroundArea.getHeight() / scale; + const juce::Rectangle logicalArea(0, 0, logicalWidth, logicalHeight); + + juce::Graphics backgroundGraphics(backgroundImage); + backgroundGraphics.addTransform(juce::AffineTransform::scale(scale, scale)); + backgroundGraphics.fillAll(juce::Colours::transparentBlack); + backgroundGraphics.setColour(backgroundColour); + backgroundGraphics.fillRoundedRectangle(logicalArea, cornerRadius); + backgroundImageComponent.setImage(backgroundImage, + juce::RectanglePlacement::stretchToFit); + + juce::Graphics hoverGraphics(hoverBackgroundImage); + hoverGraphics.addTransform(juce::AffineTransform::scale(scale, scale)); + hoverGraphics.fillAll(juce::Colours::transparentBlack); + hoverGraphics.setColour(hoverColour); + hoverGraphics.fillRoundedRectangle(logicalArea, cornerRadius); + hoverBackgroundImageComponent.setImage( + hoverBackgroundImage, juce::RectanglePlacement::stretchToFit); + + juce::Graphics clickGraphics(clickedBackgroundImage); + clickGraphics.addTransform(juce::AffineTransform::scale(scale, scale)); + clickGraphics.fillAll(juce::Colours::transparentBlack); + clickGraphics.setColour(clickColour); + clickGraphics.fillRoundedRectangle(logicalArea, cornerRadius); + clickedBackgroundImageComponent.setImage( + clickedBackgroundImage, juce::RectanglePlacement::stretchToFit); + } + + //============================================================================== + /** + * @brief Draws the icon components. + * + * @details Handles icon color replacement and placement for both normal + * and hover states, using explicit color replacement for clarity. + */ + inline void drawIcon() + { + TRACER("AbstractButton::drawIcon"); + if (icon != nullptr) { + // Draw normal icon at high res, then scale down for display + juce::Graphics iconGraphics(iconImage); + iconGraphics.addTransform(juce::AffineTransform::scale(scale, scale)); + iconGraphics.fillAll(juce::Colours::transparentBlack); + const auto iconArea = iconImage.getBounds().toFloat() / scale; + const auto clonedIcon = icon->createCopy(); + clonedIcon->replaceColour(juce::Colours::black, juce::Colours::white); + clonedIcon->drawWithin( + iconGraphics, iconArea, juce::RectanglePlacement::centred, 1.0f); + iconImageComponent.setImage(iconImage, + juce::RectanglePlacement::stretchToFit); + + // Draw hover icon at high res, then scale down for display + juce::Graphics hoverIconGraphics(hoverIconImage); + hoverIconGraphics.addTransform( + juce::AffineTransform::scale(scale, scale)); + hoverIconGraphics.fillAll(juce::Colours::transparentBlack); + const auto clonedHoverIcon = icon->createCopy(); + clonedHoverIcon->replaceColour( + juce::Colours::black, + (alternativeIconHover) ? hoverColour : juce::Colours::black); + clonedHoverIcon->drawWithin( + hoverIconGraphics, iconArea, juce::RectanglePlacement::centred, 1.0f); + hoverIconImageComponent.setImage(hoverIconImage, + juce::RectanglePlacement::stretchToFit); + } + } + + //============================================================================== + // Members initialized in the initializer list + String tooltip; + bool shouldDrawBorder; + bool shouldDrawBackground; + bool shouldDrawShadows; + bool alternativeIconHover; + const float rawSpecificSvgPadding; + Shadow outerShadow; + Shadow innerShadow; + + //============================================================================== + // Other members + std::unique_ptr icon; + Image backgroundImage; + ImageComponent backgroundImageComponent; + Image hoverBackgroundImage; + ImageComponent hoverBackgroundImageComponent; + Image clickedBackgroundImage; + ImageComponent clickedBackgroundImageComponent; + Image iconImage; + ImageComponent iconImageComponent; + Image hoverIconImage; + ImageComponent hoverIconImageComponent; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AbstractButton) +}; + +} // namespace widget +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/widget/BorderButton.h b/src/dmt/gui/widget/BorderButton.h new file mode 100644 index 0000000..c6efe6d --- /dev/null +++ b/src/dmt/gui/widget/BorderButton.h @@ -0,0 +1,282 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * This class implements a buttom to bring back the header when it is hidden. + * It uses a fade-in/out effect for the button's opacity. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "dmt/utility/Fonts.h" +#include "utility/RepaintTimer.h" +#include "utility/Scaleable.h" +#include "utility/Settings.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace widget { + +//============================================================================== +/** + * @brief A custom button with fade-in/out effects and border styling. + * + * @details This class implements a buttom to bring back the header when it is + * hidden. + * It uses a fade-in/out effect for the button's opacity. + */ +class BorderButton + : public juce::Button + , public dmt::Scaleable + , private dmt::utility::RepaintTimer +{ + using Settings = dmt::Settings; + using Colour = juce::Colour; + using Graphics = juce::Graphics; + using Image = juce::Image; + using Rectangle = juce::Rectangle; + using Fonts = dmt::utility::Fonts; + + //============================================================================== + // Header + const Colour& backgroundColour = + Settings::Header::borderButtonBackgroundColour; + const Colour& fontColour = Settings::Header::borderButtonFontColour; + const float& rawFontSize = Settings::Header::borderButtonFontSize; + + // Constants for opacity and raw fade speed + static constexpr float MAX_OPACITY = 1.0f; // Fully visible + static constexpr float MIN_OPACITY = 0.0f; // Semi-transparent + static constexpr float RAW_FADE_SPEED = 0.8f; // Base fade speed (per second) + +public: + using ButtonCallback = std::function; + + //============================================================================== + /** + * @brief Constructs a BorderButton instance. + * + * @details Initializes the button with semi-transparency and starts the + * repaint timer for fade-in/out effects. + */ + BorderButton() noexcept + : juce::Button("BorderButton") + , currentOpacity(MIN_OPACITY) // Start with semi-transparency + , isHovered(false) + { + TRACER("BorderButton::BorderButton"); + addMouseListener(this, true); + startRepaintTimer(); // Start the timer in the constructor + } + + //============================================================================== + /** + * @brief Destructor for BorderButton. + */ + ~BorderButton() override = default; + + //============================================================================== + /** + * @brief Handles resizing of the button. + * + * @details Updates the cached image whenever the button is resized to ensure + * proper rendering. + */ + void resized() override + { + TRACER("BorderButton::resized"); + updateCachedImage(); + } + + //============================================================================== + /** + * @brief Paints the button with the current opacity. + * + * @param _g The graphics context. + * @param _isMouseOverButton Unused parameter. + * @param _isButtonDown Unused parameter. + * + * @details Draws the cached image with the current opacity level for + * optimized rendering. + */ + inline void paintButton(Graphics& _g, + bool /*_isMouseOverButton*/, + bool /*_isButtonDown*/) override + { + TRACER("BorderButton::paintButton"); + _g.setOpacity(currentOpacity); + // Draw the cached image scaled to fit the button area + _g.drawImage(cachedImage, getLocalBounds().toFloat()); + } + + //============================================================================== + /** + * @brief Handles mouse enter events. + * + * @param _event The mouse event. + * + * @details Instantly sets the button to maximum opacity when hovered. + */ + inline void mouseEnter(const juce::MouseEvent& /*_event*/) override + { + TRACER("BorderButton::mouseEnter"); + isHovered = true; + currentOpacity = MAX_OPACITY; + repaint(); + } + + //============================================================================== + /** + * @brief Handles mouse exit events. + * + * @param _event The mouse event. + * + * @details Stops the hover effect when the mouse leaves the button. + */ + inline void mouseExit(const juce::MouseEvent& /*_event*/) override + { + TRACER("BorderButton::mouseExit"); + isHovered = false; + } + + //============================================================================== + /** + * @brief Sets the button's opacity to maximum. + * + * @details This method is useful for programmatically forcing the button to + * appear fully visible. + */ + inline void setOpacityToMax() noexcept + { + TRACER("BorderButton::setOpacityToMax"); + currentOpacity = MAX_OPACITY; + repaint(); + } + + //============================================================================== + /** + * @brief Sets the callback to be invoked when the button is clicked. + * + * @param _callback The callback function. + */ + inline void setButtonCallback(ButtonCallback _callback) noexcept + { + TRACER("BorderButton::setButtonCallback"); + buttonCallback = std::move(_callback); + } + + //============================================================================== + /** + * @brief Handles button click events. + * + * @details Invokes the user-defined callback function if it is set. + */ + inline void clicked() override + { + TRACER("BorderButton::clicked"); + if (buttonCallback) { + buttonCallback(); + } + } + +protected: + //============================================================================== + /** + * @brief Updates the cached image for optimized rendering. + * + * @details This method is called whenever the button is resized to ensure + * the cached image matches the new dimensions. + */ + void updateCachedImage() + { + TRACER("BorderButton::updateCachedImage"); + const auto width = getWidth(); + const auto height = getHeight(); + + if (width <= 0 || height <= 0) { + return; // Avoid invalid dimensions + } + + // HiDPI support: render cached image at higher resolution + const int hiResWidth = static_cast(width * scale); + const int hiResHeight = static_cast(height * scale); + + cachedImage = Image(juce::Image::ARGB, hiResWidth, hiResHeight, true); + Graphics g(cachedImage); + + g.addTransform(juce::AffineTransform::scale(scale, scale)); + g.fillAll(juce::Colours::transparentBlack); + g.fillAll(backgroundColour); + + const auto fontSize = static_cast(rawFontSize * size); + const auto font = fonts.medium.withHeight(fontSize); + + g.setFont(font); + g.setColour(fontColour); + g.drawText("Click to Show Header", + juce::Rectangle(0, 0, width, height), + juce::Justification::centred); + } + + //============================================================================== + /** + * @brief Callback for the repaint timer. + * + * @details Adjusts the button's opacity based on hover state and triggers a + * repaint if necessary. + */ + void repaintTimerCallback() noexcept override + { + TRACER("BorderButton::repaintTimerCallback"); + const float fadeSpeed = + RAW_FADE_SPEED / static_cast(Settings::framerate); + + if (!isHovered) { + currentOpacity = std::max(MIN_OPACITY, currentOpacity - fadeSpeed); + repaint(); + } + } + +private: + //============================================================================== + // Members initialized in the initializer list + ButtonCallback buttonCallback; // Callback to be invoked on click + float currentOpacity; // Current transparency level + bool isHovered; // Whether the button is hovered + + //============================================================================== + // Other members + Fonts fonts; + Image cachedImage; // Cached image for optimized rendering + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(BorderButton) +}; +} // namespace widget +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/widget/CallbackButton.h b/src/dmt/gui/widget/CallbackButton.h new file mode 100644 index 0000000..0142894 --- /dev/null +++ b/src/dmt/gui/widget/CallbackButton.h @@ -0,0 +1,205 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Implementation AbstractButton class designed to trigger callbacks on click. + * It supports hover and click states, customizable colors, and tooltips. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "gui/widget/AbstractButton.h" +#include "gui/widget/Shadow.h" +#include "utility/Icon.h" +#include "utility/Settings.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace widget { + +//============================================================================== +/** + * @brief + * A callback-enabled button with customizable icon, tooltip, and visual + * states. + * + * @details + * This class extends AbstractButton to provide a button that triggers a + * callback when clicked. It supports hover and click states, customizable + * colors, and tooltips. The button's appearance is optimized for real-time + * GUI performance. + * + * @note + * The button's visual states are managed internally, and the constructor + * initializes the button with a passive state for consistent appearance. + */ +class CallbackButton final : public dmt::gui::widget::AbstractButton +{ + //============================================================================== + using AbstractButton = dmt::gui::widget::AbstractButton; + +public: + //============================================================================== + /** + * @brief + * Constructs a CallbackButton with the specified parameters. + * + * @param _name The button's name (used for accessibility and identification). + * @param _iconName The icon to display on the button. + * @param _tooltip The tooltip text to show on hover (optional). + * @param _shouldDrawBorder Whether to draw a border around the button. + * @param _shouldDrawBackground Whether to draw a background behind the icon. + * @param _shouldDrawShadow Whether to draw a drop shadow. + * @param _alternativeIconHover Whether to use an alternative icon on hover. + * + * @details + * The constructor initializes the button and sets its initial state to + * passive for visual consistency. All visual options are configurable. + */ + explicit CallbackButton(juce::String _name, + juce::String _iconName, + juce::String _tooltip = "", + bool _shouldDrawBorder = true, + bool _shouldDrawBackground = true, + bool _shouldDrawShadow = true, + bool _alternativeIconHover = false) noexcept + : AbstractButton(_name, + _iconName, + _tooltip, + _shouldDrawBorder, + _shouldDrawBackground, + _shouldDrawShadow, + _alternativeIconHover) + { + TRACER("CallbackButton::CallbackButton"); + setPassiveState(); // Set initial state for consistent appearance + } + + //============================================================================== + /** + * @brief + * Destructor. No special cleanup required. + */ + ~CallbackButton() override = default; + + //============================================================================== + /** + * @brief + * Handles mouse enter events to update the button's visual state. + * + * @param _event The mouse event (unused). + * + * @details + * When the mouse enters the button and it is enabled, the button + * transitions to the hover state for immediate visual feedback. + */ + inline void mouseEnter(const juce::MouseEvent& /*_event*/) override + { + TRACER("CallbackButton::mouseEnter"); + if (isEnabled()) { + setHoverState(); + } + } + + //============================================================================== + /** + * @brief + * Handles mouse exit events to revert the button's visual state. + * + * @param _event The mouse event (unused). + * + * @details + * When the mouse leaves the button and it is enabled, the button returns to + * the passive state to indicate it is no longer hovered. + */ + inline void mouseExit(const juce::MouseEvent& /*_event*/) override + { + TRACER("CallbackButton::mouseExit"); + if (isEnabled()) { + setPassiveState(); + } + } + + //============================================================================== + /** + * @brief + * Handles mouse down events to indicate the button is being pressed. + * + * @param _event The mouse event (unused). + * + * @details + * When the button is pressed and enabled, it transitions to the clicked + * state for immediate visual feedback. + */ + inline void mouseDown(const juce::MouseEvent& /*_event*/) override + { + TRACER("CallbackButton::mouseDown"); + if (isEnabled()) { + setClickedState(); + } + } + + //============================================================================== + /** + * @brief + * Handles mouse up events to trigger the button's callback and update + * state. + * + * @param _event The mouse event (unused). + * + * @details + * On mouse release, if the button is enabled, it transitions to the hover + * state and triggers the onClick callback if set. This ensures the action + * is only performed on mouse up, following standard UI conventions. + */ + inline void mouseUp(const juce::MouseEvent& /*_event*/) override + { + TRACER("CallbackButton::mouseUp"); + if (isEnabled()) { + setHoverState(); + if (onClick) + onClick(); // Trigger the button's click action if set + } + } + +private: + //============================================================================== + // Members initialized in the initializer list + // (none for this class) + + //============================================================================== + // Other members + // (none for this class) + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CallbackButton) +}; + +} // namespace widget +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/widget/Graph.h b/src/dmt/gui/widget/Graph.h new file mode 100644 index 0000000..9c23f34 --- /dev/null +++ b/src/dmt/gui/widget/Graph.h @@ -0,0 +1,135 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Graph widget for visualizing sample data in real-time. Designed for + * performance and type safety, this component renders a waveform-like + * visualization using a user-supplied data source. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once +//============================================================================== +#include +//============================================================================== +namespace dmt { +namespace gui { +namespace widget { +//============================================================================== + +/** + * @brief Graph widget for visualizing sample data in real-time. + * + * @tparam SampleType The numeric type of the samples to be visualized. + * + * @details + * This component draws a waveform-like graph using a user-supplied data source. + * The data source is a callable that returns a sample value for a given index. + * Designed for real-time performance and type safety, this widget is intended + * for use in audio and signal processing applications where efficient rendering + * is critical. The rendering is performed in the paint() method, which is + * optimized for minimal allocations and maximum throughput. + */ +template +class Graph : public juce::Component +{ + //============================================================================== + using DataSource = std::function; + +public: + //============================================================================== + /** + * @brief Constructs a Graph widget with the given data source. + * + * @param _dataSource A callable that returns a sample value for a given + * index. + * + * @details + * The data source should be a fast, thread-safe callable suitable for + * real-time use. The constructor is constexpr for maximum compile-time + * optimization and noexcept for exception safety. + */ + constexpr explicit inline Graph(DataSource _dataSource) noexcept + : dataSource(_dataSource) + { + } + + //============================================================================== + /** + * @brief Handles component resizing. Intentionally left empty. + * + * @details + * This override is required by JUCE. All layout is handled in paint(). + */ + inline void resized() override {} + + //============================================================================== + /** + * @brief Paints the graph visualization. + * + * @param _g The graphics context. + * + * @details + * This method constructs a JUCE Path representing the waveform and renders + * it. The path is preallocated for performance. Explicit casts are used for + * type safety. The method is noexcept to guarantee real-time safety. + */ + inline void paint(juce::Graphics& _g) noexcept override + { + const int32_t width = getWidth(); + const int32_t height = getHeight(); + + juce::Path path; + path.preallocateSpace(static_cast((3 * width) + 6)); + path.startNewSubPath(0.0f, static_cast(height) / 2.0f); + + for (size_t i = 0; i < static_cast(width); ++i) { + const SampleType sample = dataSource(static_cast(i)); + path.lineTo(static_cast(i), + static_cast(height) / 2.0f - + sample * static_cast(height) / 2.0f); + } + + path.lineTo(static_cast(width), static_cast(height) / 2.0f); + path.closeSubPath(); + + _g.setColour(juce::Colours::white); + _g.strokePath(path, juce::PathStrokeType(3.0f)); + } + + //============================================================================== + +private: + //============================================================================== + // Members initialized in the initializer list + DataSource dataSource; + + //============================================================================== + // Other members + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(Graph) +}; + +} // namespace widget +} // namespace gui +} // namespace dmt \ No newline at end of file diff --git a/src/dmt/gui/widget/Label.h b/src/dmt/gui/widget/Label.h new file mode 100644 index 0000000..d5fea56 --- /dev/null +++ b/src/dmt/gui/widget/Label.h @@ -0,0 +1,253 @@ +//============================================================================== +/* β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•— + * β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β•šβ–ˆβ–ˆβ•”β• + * β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ + * β•šβ•β•β•β•β•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β• β•šβ•β• + * Copyright (C) 2024 Dimethoxy Audio (https://dimethoxy.com) + * + * Part of the Dimethoxy Library, primarily intended for Dimethoxy plugins. + * External use is permitted but not recommended. + * No support or compatibility guarantees are provided. + * + * License: + * This code is licensed under the GPLv3 license. You are permitted to use and + * modify this code under the terms of this license. + * You must adhere GPLv3 license for any project using this code or parts of it. + * Your are not allowed to use this code in any closed-source project. + * + * Description: + * Provides a lightweight, high-performance label widget for JUCE-based GUIs. + * Designed for real-time audio applications where rendering efficiency and + * type safety are critical. Supports both single-line and multi-line text, + * with platform-specific font scaling for consistent appearance. + * + * Authors: + * Lunix-420 (Primary Author) + */ +//============================================================================== + +#pragma once + +//============================================================================== + +#include "dmt/utility/Scaleable.h" +#include "utility/Settings.h" +#include + +//============================================================================== + +namespace dmt { +namespace gui { +namespace widget { + +//============================================================================== +/** + * @brief A high-performance, type-safe label widget for JUCE GUIs. + * + * @details + * This class provides a customizable label component optimized for real-time + * audio applications. It supports both single-line and multi-line text + * rendering, with platform-specific font scaling to ensure visual consistency + * across operating systems. The design prioritizes rendering speed and type + * safety, making it suitable for use in performance-critical GUI contexts. + * + * The label can be justified and colored as needed, and is intended to be used + * as a lightweight alternative to more complex text components. + */ +class Label + : public juce::Component + , public dmt::Scaleable