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