├── .clang-format
├── .gitignore
├── .gitmodules
├── CMakeLists.txt
├── LICENSE
├── README.md
├── modules
├── CMakeLists.txt
└── cmake
│ ├── SubprojectVersion.cmake
│ ├── WarningFlags.cmake
│ └── pluginval.cmake
├── preview.png
├── res
├── CMakeLists.txt
├── RobotoCondensed-Bold.ttf
├── RobotoCondensed-Regular.ttf
├── gui.xml
├── knob.svg
├── pointer.svg
└── presets
│ └── Default.mypreset
└── src
├── CMakeLists.txt
├── PluginProcessor.cpp
├── PluginProcessor.hpp
├── dsp
├── BiquadFilter.cpp
├── BiquadFilter.hpp
├── BitReduction.cpp
├── BitReduction.hpp
├── CMakeLists.txt
└── WaveWarp.hpp
├── gui
├── CMakeLists.txt
├── item
│ └── DynText.hpp
└── lnf
│ ├── MainLNF.cpp
│ └── MainLNF.hpp
└── pch.h
/.clang-format:
--------------------------------------------------------------------------------
1 | ---
2 | AccessModifierOffset: -4
3 | AlignAfterOpenBracket: Align
4 | AlignConsecutiveAssignments: false
5 | AlignConsecutiveDeclarations: false
6 | AlignEscapedNewlines: Left
7 | AlignOperands: Align
8 | AlignTrailingComments: false
9 | AllowAllParametersOfDeclarationOnNextLine: false
10 | AllowShortBlocksOnASingleLine: Never
11 | AllowShortCaseLabelsOnASingleLine: false
12 | AllowShortFunctionsOnASingleLine: All
13 | AllowShortIfStatementsOnASingleLine: Never
14 | AllowShortLoopsOnASingleLine: false
15 | AlwaysBreakAfterDefinitionReturnType: None
16 | AlwaysBreakAfterReturnType: None
17 | AlwaysBreakBeforeMultilineStrings: false
18 | AlwaysBreakTemplateDeclarations: Yes
19 | BinPackArguments: false
20 | BinPackParameters: false
21 | BreakAfterJavaFieldAnnotations: false
22 | BreakBeforeBinaryOperators: NonAssignment
23 | BreakBeforeBraces: Allman
24 | BreakBeforeTernaryOperators: true
25 | BreakConstructorInitializersBeforeComma: false
26 | BreakStringLiterals: false
27 | ColumnLimit: 0
28 | ConstructorInitializerAllOnOneLineOrOnePerLine: true
29 | ConstructorInitializerIndentWidth: 4
30 | ContinuationIndentWidth: 4
31 | Cpp11BracedListStyle: false
32 | DerivePointerAlignment: false
33 | DisableFormat: false
34 | ExperimentalAutoDetectBinPacking: false
35 | ForEachMacros: ['forEachXmlChildElement']
36 | IndentCaseLabels: true
37 | IndentWidth: 4
38 | IndentWrappedFunctionNames: true
39 | KeepEmptyLinesAtTheStartOfBlocks: false
40 | Language: Cpp
41 | MaxEmptyLinesToKeep: 1
42 | NamespaceIndentation: Inner
43 | PointerAlignment: Left
44 | ReflowComments: false
45 | SortIncludes: true
46 | SpaceAfterCStyleCast: true
47 | SpaceAfterLogicalNot: true
48 | SpaceBeforeAssignmentOperators: true
49 | SpaceBeforeCpp11BracedList: true
50 | SpaceBeforeParens: NonEmptyParentheses
51 | SpaceInEmptyParentheses: false
52 | SpaceBeforeInheritanceColon: true
53 | SpacesInAngles: false
54 | SpacesInCStyleCastParentheses: false
55 | SpacesInContainerLiterals: true
56 | SpacesInParentheses: false
57 | SpacesInSquareBrackets: false
58 | Standard: "c++17"
59 | TabWidth: 4
60 | UseTab: Never
61 | ---
62 | Language: ObjC
63 | BasedOnStyle: Chromium
64 | AlignTrailingComments: true
65 | BreakBeforeBraces: Allman
66 | ColumnLimit: 0
67 | IndentWidth: 4
68 | KeepEmptyLinesAtTheStartOfBlocks: false
69 | ObjCSpaceAfterProperty: true
70 | ObjCSpaceBeforeProtocolList: true
71 | PointerAlignment: Left
72 | SpacesBeforeTrailingComments: 1
73 | TabWidth: 4
74 | UseTab: Never
75 | ...
76 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # build artifacts
2 | build*/
3 | bin/
4 | *_Packaged
5 | *.exe
6 | *.dmg
7 |
8 | # IDE settings
9 | .vscode/
10 | .idea/
11 |
12 | # Mac extras
13 | .DS_Store
14 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "modules/JUCE"]
2 | path = modules/JUCE
3 | url = git@github.com:juce-framework/JUCE.git
4 | ignore = dirty
5 | [submodule "modules/foleys_gui_magic"]
6 | path = modules/foleys_gui_magic
7 | url = git@github.com:ffAudio/foleys_gui_magic.git
8 | ignore = dirty
9 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.15)
2 | set(CMAKE_OSX_DEPLOYMENT_TARGET "10.12" CACHE STRING "Minimum OS X deployment target")
3 | project(HandyOsc VERSION 1.0.0)
4 |
5 | set(CMAKE_CXX_STANDARD 17)
6 | add_subdirectory(modules)
7 | include_directories(modules)
8 |
9 | # juce_set_vst2_sdk_path(C:/SDKs/VST_SDK/VST2_SDK/)
10 | # juce_set_aax_sdk_path(NONE)
11 |
12 | # set default plugin formats to build
13 | if(IOS)
14 | set(JUCE_FORMATS Standalone AUv3)
15 | else()
16 | set(JUCE_FORMATS AU VST3 Standalone)
17 | endif()
18 |
19 | # Build LV2 only on Linux
20 | if(UNIX AND NOT APPLE)
21 | message(STATUS "Building LV2 plugin format")
22 | list(APPEND JUCE_FORMATS LV2)
23 | endif()
24 |
25 | # Build VST2 if SDK target exists
26 | if(TARGET juce_vst2_sdk)
27 | message(STATUS "Building VST2 plugin format")
28 | list(APPEND JUCE_FORMATS VST)
29 | endif()
30 |
31 | # Build AAX if SDK target exists
32 | if(TARGET juce_aax_sdk)
33 | message(STATUS "Building AAX plugin format")
34 | list(APPEND JUCE_FORMATS AAX)
35 | endif()
36 |
37 | juce_add_plugin(HandyOsc
38 | COMPANY_NAME Pachuch
39 | PLUGIN_MANUFACTURER_CODE PHCH
40 | PLUGIN_CODE spg3
41 | FORMATS ${JUCE_FORMATS}
42 | ProductName "HandyOsc"
43 |
44 | VST2_CATEGORY kPlugCategEffect
45 | VST3_CATEGORIES Fx Delay
46 | AU_MAIN_TYPE kAudioUnitType_Effect
47 | AAX_CATEGORY AAX_ePlugInCategory_Delay
48 |
49 | LV2URI https://github.com/Pachuch/HandyOsc
50 |
51 | # ICON_BIG res/logo.png
52 | MICROPHONE_PERMISSION_ENABLED TRUE
53 | NEEDS_STORE_KIT TRUE
54 | REQUIRES_FULL_SCREEN TRUE
55 | IPHONE_SCREEN_ORIENTATIONS UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight
56 | IPAD_SCREEN_ORIENTATIONS UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight
57 | )
58 |
59 | # create JUCE header
60 | juce_generate_juce_header(HandyOsc)
61 |
62 | # add sources
63 | add_subdirectory(src)
64 | include_directories(src)
65 | add_subdirectory(res)
66 |
67 | target_compile_definitions(HandyOsc
68 | PUBLIC
69 | JUCE_DISPLAY_SPLASH_SCREEN=0
70 | JUCE_REPORT_APP_USAGE=0
71 | JUCE_WEB_BROWSER=0
72 | JUCE_USE_CURL=0
73 | JUCE_VST3_CAN_REPLACE_VST2=1
74 | EXAMPLE_AUTO_UPDATE=1
75 | )
76 |
77 | if(ASIOSDK_DIR)
78 | message(STATUS "Using ASIO SDK from ${ASIOSDK_DIR}")
79 | target_include_directories(juce_plugin_modules PUBLIC ${ASIOSDK_DIR}/common)
80 | target_compile_definitions(juce_plugin_modules PUBLIC JUCE_ASIO=1)
81 | endif()
82 |
83 | target_link_libraries(HandyOsc PUBLIC
84 | juce_plugin_modules
85 | )
86 |
87 | option(RUN_PLUGINVAL "Run pluginval on HandyOsc plugins" OFF)
88 | if(RUN_PLUGINVAL)
89 | create_pluginval_target(HandyOsc_VST3 "HandyOsc.vst3")
90 | endif()
91 |
92 | # we need these flags for notarization on MacOS
93 | option(MACOS_RELEASE "Set build flags for MacOS Release" OFF)
94 | if(MACOS_RELEASE)
95 | message(STATUS "Setting MacOS release flags...")
96 | set_target_properties(HandyOsc_Standalone PROPERTIES
97 | XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME YES)
98 | endif()
99 |
100 | if(IOS)
101 | message(STATUS "Setting iOS-specific properties...")
102 |
103 | foreach(target IN ITEMS BinaryData polylogarithm juce_plugin_modules HandyOsc HandyOsc_Standalone HandyOsc_AUv3)
104 | set_target_properties(${target}
105 | PROPERTIES
106 | RUNTIME_OUTPUT_DIRECTORY "./"
107 | ARCHIVE_OUTPUT_DIRECTORY "./"
108 | LIBRARY_OUTPUT_DIRECTORY "./")
109 | endforeach()
110 |
111 | set_target_properties(HandyOsc_Standalone PROPERTIES
112 | XCODE_ATTRIBUTE_INSTALL_PATH "$(LOCAL_APPS_DIR)"
113 | XCODE_ATTRIBUTE_SKIP_INSTALL "NO"
114 | XCODE_ATTRIBUTE_ENABLE_IN_APP_PURCHASE "YES")
115 |
116 | set_target_properties(HandyOsc_AUv3 PROPERTIES
117 | XCODE_ATTRIBUTE_INSTALL_PATH "$(LOCAL_APPS_DIR)/HandyOsc.app/PlugIns"
118 | XCODE_ATTRIBUTE_SKIP_INSTALL "NO"
119 | XCODE_ATTRIBUTE_ENABLE_IN_APP_PURCHASE "YES")
120 | endif()
121 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2022, Pachuch
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | 3. Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HandyOsc Audio Plugin - Turn any audio into a digital synth
2 |
3 |
4 |
5 |
6 |
7 | This is a effect plugin that uses wave warping logic to create sawtooth-like waves from input audio. Combine warp and bit reduction to get new sounds!
8 | * Supported hosts: VST3 only.
9 |
10 | I haven't tested other plugin formats other than VST3. But JUCE supports VST, VST3, AU, AUv3, AAX and LV2 audio plug-ins, I might get lucky.
11 |
12 | # Features:
13 | 1. 3 waveform warping algorithms
14 | 2. Bitcrush
15 | 3. Pre/Post filter
16 | 4. Oscilloscope
17 |
18 | # Dependencies
19 |
20 | Build on top of the JUCE framework and foleys_gui_magic GUI builder.
21 |
22 | # Build
23 |
24 | Examples:
25 | * Windows + MSVC:
26 | ```console
27 | mkdir build
28 | cd build
29 | cmake .. -G "Visual Studio 17 2022"
30 | cmake --build . --target HandyOsc_VST3 --config Release
31 | ```
32 |
33 | * Linux + GCC:
34 | ```console
35 | mkdir build
36 | cd build
37 | cmake .. -G "Unix Makefiles"
38 | cmake --build . --target HandyOsc_VST3 --config Release
39 | ```
--------------------------------------------------------------------------------
/modules/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | add_subdirectory(JUCE)
2 | include(cmake/SubprojectVersion.cmake)
3 | subproject_version(JUCE juce_version)
4 | message(STATUS "VERSION for JUCE: ${juce_version}")
5 |
6 | juce_add_modules(foleys_gui_magic/modules/foleys_gui_magic)
7 |
8 | include(cmake/WarningFlags.cmake)
9 | add_library(juce_plugin_modules STATIC)
10 | target_link_libraries(juce_plugin_modules
11 | PRIVATE
12 | BinaryData
13 | juce::juce_audio_utils
14 | juce::juce_audio_plugin_client
15 | juce::juce_dsp
16 | foleys_gui_magic
17 | PUBLIC
18 | juce::juce_recommended_config_flags
19 | juce::juce_recommended_lto_flags
20 | warning_flags
21 | )
22 |
23 | target_compile_definitions(juce_plugin_modules
24 | PUBLIC
25 | JUCE_DISPLAY_SPLASH_SCREEN=0
26 | JUCE_REPORT_APP_USAGE=0
27 | JUCE_WEB_BROWSER=0
28 | JUCE_USE_CURL=0
29 | JUCE_VST3_CAN_REPLACE_VST2=0
30 | JUCE_JACK=1
31 | JUCE_ALSA=1
32 | JUCE_MODAL_LOOPS_PERMITTED=1
33 | FOLEYS_SHOW_GUI_EDITOR_PALLETTE=0
34 | FOLEYS_ENABLE_BINARY_DATA=1
35 | JucePlugin_Manufacturer="Pachuch"
36 | JucePlugin_VersionString="${CMAKE_PROJECT_VERSION}"
37 | JucePlugin_Name="${CMAKE_PROJECT_NAME}"
38 | INTERFACE
39 | $
40 | )
41 |
42 | target_include_directories(juce_plugin_modules
43 | INTERFACE
44 | $
45 | )
46 |
47 | set_target_properties(juce_plugin_modules PROPERTIES
48 | POSITION_INDEPENDENT_CODE TRUE
49 | VISIBILITY_INLINES_HIDDEN TRUE
50 | C_VISBILITY_PRESET hidden
51 | CXX_VISIBILITY_PRESET hidden
52 | )
53 |
54 | if(IOS)
55 | target_link_libraries(juce_plugin_modules PRIVATE juce::juce_product_unlocking)
56 | target_compile_definitions(juce_plugin_modules PUBLIC JUCE_IN_APP_PURCHASES=1)
57 | endif()
58 |
59 | if(NOT APPLE)
60 | message(STATUS "Linking with OpenGL")
61 | target_link_libraries(juce_plugin_modules PRIVATE juce::juce_opengl)
62 | target_compile_definitions(juce_plugin_modules PUBLIC FOLEYS_ENABLE_OPEN_GL_CONTEXT=1)
63 | endif()
64 |
65 | include(cmake/pluginval.cmake)
66 |
--------------------------------------------------------------------------------
/modules/cmake/SubprojectVersion.cmake:
--------------------------------------------------------------------------------
1 | # subproject_version( )
2 | #
3 | # Extract version of a sub-project, which was previously included with add_subdirectory().
4 | function(subproject_version subproject_name VERSION_VAR)
5 | # Read CMakeLists.txt for subproject and extract project() call(s) from it.
6 | file(STRINGS "${${subproject_name}_SOURCE_DIR}/CMakeLists.txt" project_calls REGEX "[ \t]*project\\(")
7 | # For every project() call try to extract its VERSION option
8 | foreach(project_call ${project_calls})
9 | string(REGEX MATCH "VERSION[ ]+([^ )]+)" version_param "${project_call}")
10 | if(version_param)
11 | set(version_value "${CMAKE_MATCH_1}")
12 | endif()
13 | endforeach()
14 | if(version_value)
15 | set(${VERSION_VAR} "${version_value}" PARENT_SCOPE)
16 | else()
17 | message("WARNING: Cannot extract version for subproject '${subproject_name}'")
18 | endif()
19 |
20 | endfunction(subproject_version)
21 |
--------------------------------------------------------------------------------
/modules/cmake/WarningFlags.cmake:
--------------------------------------------------------------------------------
1 | add_library(warning_flags INTERFACE)
2 |
3 | if((CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") OR (CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC"))
4 | target_compile_options(warning_flags INTERFACE
5 | /W4 # base warning level
6 | /wd4458 # declaration hides class member (from Foley's GUI Magic)
7 | /wd4505 # since VS2019 doesn't handle [[ maybe_unused ]] for static functions (RTNeural::debug_print)
8 | )
9 | elseif((CMAKE_CXX_COMPILER_ID STREQUAL "Clang") OR (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang"))
10 | target_compile_options(warning_flags INTERFACE
11 | -Wall -Wshadow-all -Wshorten-64-to-32 -Wstrict-aliasing -Wuninitialized
12 | -Wunused-parameter -Wconversion -Wsign-compare -Wint-conversion
13 | -Wconditional-uninitialized -Woverloaded-virtual -Wreorder
14 | -Wconstant-conversion -Wsign-conversion -Wunused-private-field
15 | -Wbool-conversion -Wno-extra-semi -Wunreachable-code
16 | -Wzero-as-null-pointer-constant -Wcast-align
17 | -Wno-inconsistent-missing-destructor-override -Wshift-sign-overflow
18 | -Wnullable-to-nonnull-conversion -Wno-missing-field-initializers
19 | -Wno-ignored-qualifiers -Wpedantic -Wno-pessimizing-move
20 | # These lines suppress some custom warnings.
21 | # Comment them out to be more strict.
22 | -Wno-shadow-field-in-constructor
23 | # Needed for ARM processor, OSX versions below 10.14
24 | -fno-aligned-allocation
25 | )
26 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
27 | target_compile_options(warning_flags INTERFACE
28 | -Wall -Wextra -Wstrict-aliasing -Wuninitialized -Wunused-parameter
29 | -Wsign-compare -Woverloaded-virtual -Wreorder -Wunreachable-code
30 | -Wzero-as-null-pointer-constant -Wcast-align -Wno-implicit-fallthrough
31 | -Wno-maybe-uninitialized -Wno-missing-field-initializers -Wno-pedantic
32 | -Wno-ignored-qualifiers -Wno-unused-function -Wno-pessimizing-move
33 | # From LV2 Wrapper
34 | -Wno-parentheses -Wno-deprecated-declarations -Wno-redundant-decls
35 | # These lines suppress some custom warnings.
36 | # Comment them out to be more strict.
37 | -Wno-redundant-move
38 | )
39 |
40 | if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "7.0.0")
41 | target_compile_options(warning_flags INTERFACE "-Wno-strict-overflow")
42 | endif()
43 | endif()
44 |
--------------------------------------------------------------------------------
/modules/cmake/pluginval.cmake:
--------------------------------------------------------------------------------
1 | set_property(GLOBAL PROPERTY pluginval_setup)
2 | function(create_pluginval_target target plugin)
3 | message(STATUS "Creating pluginval target for ${target}")
4 |
5 | if (APPLE)
6 | set(pluginval_url "https://github.com/Tracktion/pluginval/releases/latest/download/pluginval_macOS.zip")
7 | set(pluginval_exe pluginval/pluginval.app/Contents/MacOS/pluginval)
8 | elseif (WIN32)
9 | set(pluginval_url "https://github.com/Tracktion/pluginval/releases/latest/download/pluginval_Windows.zip")
10 | set(pluginval_exe pluginval/pluginval.exe)
11 | else ()
12 | set(pluginval_url "https://github.com/Tracktion/pluginval/releases/latest/download/pluginval_Linux.zip")
13 | set(pluginval_exe pluginval/pluginval)
14 | endif ()
15 |
16 | get_property(is_pluginval_setup GLOBAL PROPERTY pluginval_setup)
17 | if ("${is_pluginval_setup}" STREQUAL "yes")
18 | message(STATUS "Skipping pluginval setup...")
19 | else ()
20 | add_custom_target(download-pluginval)
21 | add_custom_command(TARGET download-pluginval
22 | POST_BUILD
23 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
24 | COMMAND cmake -E make_directory pluginval
25 | COMMAND curl -L ${pluginval_url} -o pluginval/pluginval.zip
26 | COMMAND cd pluginval && unzip -o pluginval.zip
27 | )
28 | add_custom_target(pluginval-all)
29 | set_property(GLOBAL PROPERTY pluginval_setup "yes")
30 | endif()
31 |
32 | set(name "${target}-pluginval")
33 | add_custom_target(${name})
34 | add_dependencies(${name} ${target})
35 | add_dependencies(${name} download-pluginval)
36 | get_target_property(plugin_location ${target} LIBRARY_OUTPUT_DIRECTORY)
37 | add_custom_command(TARGET ${name}
38 | POST_BUILD
39 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
40 | COMMAND ${pluginval_exe} --validate-in-process --strictness-level 5 --timeout-ms 600000 --output-dir "." --validate "${plugin_location}/${plugin}" || exit 1
41 | )
42 | add_dependencies(pluginval-all ${name})
43 | endfunction()
44 |
--------------------------------------------------------------------------------
/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulmour/HandyOsc/0b00d59d2a83eefe13d7d1c39a40ed4a74b6e3b2/preview.png
--------------------------------------------------------------------------------
/res/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | juce_add_binary_data(BinaryData SOURCES
2 | gui.xml
3 |
4 | knob.svg
5 | pointer.svg
6 | RobotoCondensed-Bold.ttf
7 | RobotoCondensed-Regular.ttf
8 |
9 | presets/Default.mypreset
10 | )
11 |
12 | # Need to build BinaryData with -fPIC flag on Linux
13 | set_target_properties(BinaryData PROPERTIES
14 | POSITION_INDEPENDENT_CODE TRUE)
15 |
--------------------------------------------------------------------------------
/res/RobotoCondensed-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulmour/HandyOsc/0b00d59d2a83eefe13d7d1c39a40ed4a74b6e3b2/res/RobotoCondensed-Bold.ttf
--------------------------------------------------------------------------------
/res/RobotoCondensed-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eulmour/HandyOsc/0b00d59d2a83eefe13d7d1c39a40ed4a74b6e3b2/res/RobotoCondensed-Regular.ttf
--------------------------------------------------------------------------------
/res/gui.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
21 |
22 |
24 |
25 |
27 |
28 |
30 |
32 |
34 |
36 |
38 |
39 |
40 |
42 |
44 |
45 |
46 |
47 |
49 |
52 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
64 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/res/knob.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/res/pointer.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/res/presets/Default.mypreset:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | option(BUILD_HEADLESS "Build HandyOsc headless utilities" OFF)
2 | if(NOT (IOS OR MACOS_RELEASE) AND BUILD_HEADLESS)
3 | message(STATUS "Building HandyOsc Headless!")
4 | add_subdirectory(headless)
5 | endif()
6 |
7 | file (GLOB FILES_SRC CONFIGURE_DEPENDS "*.hpp" "*.cpp")
8 |
9 | # target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE "gui")
10 |
11 | target_sources(HandyOsc PRIVATE ${FILES_SRC})
12 |
13 | target_precompile_headers(HandyOsc PRIVATE pch.h)
14 |
15 | add_subdirectory("dsp")
16 | add_subdirectory("gui")
17 |
--------------------------------------------------------------------------------
/src/PluginProcessor.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | ==============================================================================
3 |
4 | This file contains the basic framework code for a JUCE plugin processor.
5 |
6 | ==============================================================================
7 | */
8 |
9 | #include "PluginProcessor.hpp"
10 |
11 | namespace IDs {
12 |
13 | static juce::String ioGain { "ioGain" };
14 | static juce::String ioPeak { "ioPeak" };
15 | static juce::String ioReductionMix { "ioReductionMix" };
16 | static juce::String ioWarpMix { "ioWarpMix" };
17 |
18 | static juce::String controlReduction { "controlReduction" };
19 | static juce::String controlWarp { "controlWarp" };
20 | static juce::String controlLogicType { "controlLogicType" };
21 | static juce::String filterCutoff{ "filterCutoff" };
22 | static juce::String filterResonance{ "filterResonance" };
23 | static juce::String filterPlace{ "filterPlace" };
24 | static juce::Identifier oscilloscope { "oscilloscope" };
25 |
26 | } // namespace IDs
27 |
28 | juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout()
29 | {
30 | juce::AudioProcessorValueTreeState::ParameterLayout layout;
31 |
32 | auto controlGroup = std::make_unique ("controlGroup", TRANS ("Control"), "|");
33 | controlGroup->addChild (
34 | std::make_unique (IDs::controlLogicType, "Warp Type", juce::StringArray("A", "B", "C"), 0),
35 | std::make_unique (IDs::controlReduction, "Reduction", 1, 16, 16),
36 | std::make_unique (IDs::controlWarp, "Warp", juce::NormalisableRange (0.f, 1.f, 0.f, 0.25f), 0.005f)
37 | );
38 |
39 | auto filterGroup = std::make_unique ("filterGroup", TRANS ("Filter"), "|");
40 | filterGroup->addChild (
41 | std::make_unique (IDs::filterPlace, "Placement", juce::StringArray("Off", "Pre", "Post"), 1),
42 | std::make_unique (IDs::filterCutoff, "Cutoff", juce::NormalisableRange (20.f, 20000.f, 0.f, .25f), 20000.f),
43 | std::make_unique (IDs::filterResonance, "Resonance", juce::NormalisableRange (0.1f, 96.f, 0.f, .25f), .1f)
44 | );
45 |
46 | auto ioGroup = std::make_unique ("ioGroup", TRANS ("I/O"), "|");
47 | ioGroup->addChild (
48 | std::make_unique (IDs::ioGain, "Gain", juce::NormalisableRange (0.f, 2.f), 1.0f),
49 | std::make_unique (IDs::ioReductionMix, "Reduction Mix", juce::NormalisableRange (0.f, 1.f), 1.0f),
50 | std::make_unique (IDs::ioWarpMix, "Warp Mix", juce::NormalisableRange (0.f, 1.f), 1.0f)
51 | );
52 |
53 | layout.add (std::move (controlGroup), std::move(filterGroup), std::move (ioGroup));
54 |
55 | return layout;
56 | }
57 |
58 | //==============================================================================
59 | ExampleAudioProcessor::ExampleAudioProcessor()
60 | #ifndef JucePlugin_PreferredChannelConfigurations
61 | : AudioProcessor (BusesProperties()
62 | #if ! JucePlugin_IsMidiEffect
63 | #if ! JucePlugin_IsSynth
64 | .withInput ("Input", juce::AudioChannelSet::stereo(), true)
65 | #endif
66 | .withOutput ("Output", juce::AudioChannelSet::stereo(), true)
67 | #endif
68 | )
69 | , vts (*this, nullptr, juce::Identifier ("Parameters"), createParameterLayout()),
70 | biquadFilter(3000.f, 1.f)
71 | #endif
72 | {
73 | this->controlReduction = vts.getRawParameterValue (IDs::controlReduction);
74 | jassert (this->controlReduction != nullptr);
75 |
76 | this->controlWarp = vts.getRawParameterValue (IDs::controlWarp);
77 | jassert (this->controlWarp != nullptr);
78 |
79 | this->filterCutoff = vts.getRawParameterValue (IDs::filterCutoff);
80 | jassert (this->filterCutoff != nullptr);
81 |
82 | this->filterResonance = vts.getRawParameterValue (IDs::filterResonance);
83 | jassert (this->filterResonance != nullptr);
84 |
85 | this->ioGain = vts.getRawParameterValue (IDs::ioGain);
86 | jassert (this->ioGain != nullptr);
87 |
88 | this->ioReductionMix = vts.getRawParameterValue (IDs::ioReductionMix);
89 | jassert (this->ioReductionMix != nullptr);
90 |
91 | this->ioWarpMix = vts.getRawParameterValue (IDs::ioWarpMix);
92 | jassert (this->ioWarpMix != nullptr);
93 |
94 | this->waveWarp.type = vts.getRawParameterValue (IDs::controlLogicType);
95 | jassert (this->waveWarp.type != nullptr);
96 |
97 | vts.addParameterListener (IDs::controlReduction, this);
98 | vts.addParameterListener (IDs::controlWarp, this);
99 | vts.addParameterListener (IDs::filterCutoff, this);
100 | vts.addParameterListener (IDs::filterResonance, this);
101 | vts.addParameterListener (IDs::filterPlace, this);
102 | vts.addParameterListener (IDs::ioGain, this);
103 | vts.addParameterListener (IDs::ioReductionMix, this);
104 | vts.addParameterListener (IDs::ioWarpMix, this);
105 |
106 | oscilloscope = magicState.createAndAddObject (IDs::oscilloscope, 0);
107 | // peakInfo = magicState.createAndAddObject (IDs::oscilloscope, 0);
108 | magicState.setGuiValueTree (BinaryData::gui_xml, BinaryData::gui_xmlSize);
109 | }
110 |
111 | ExampleAudioProcessor::~ExampleAudioProcessor() = default;
112 |
113 | //==============================================================================
114 | const juce::String ExampleAudioProcessor::getName() const
115 | {
116 | return JucePlugin_Name;
117 | }
118 |
119 | bool ExampleAudioProcessor::acceptsMidi() const
120 | {
121 | #if JucePlugin_WantsMidiInput
122 | return true;
123 | #else
124 | return false;
125 | #endif
126 | }
127 |
128 | bool ExampleAudioProcessor::producesMidi() const
129 | {
130 | #if JucePlugin_ProducesMidiOutput
131 | return true;
132 | #else
133 | return false;
134 | #endif
135 | }
136 |
137 | bool ExampleAudioProcessor::isMidiEffect() const
138 | {
139 | #if JucePlugin_IsMidiEffect
140 | return true;
141 | #else
142 | return false;
143 | #endif
144 | }
145 |
146 | double ExampleAudioProcessor::getTailLengthSeconds() const
147 | {
148 | return 0.0;
149 | }
150 |
151 | int ExampleAudioProcessor::getNumPrograms()
152 | {
153 | return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs,
154 | // so this should be at least 1, even if you're not really implementing programs.
155 | }
156 |
157 | int ExampleAudioProcessor::getCurrentProgram()
158 | {
159 | return 0;
160 | }
161 |
162 | void ExampleAudioProcessor::setCurrentProgram (int index)
163 | {
164 | (void)index;
165 | }
166 |
167 | const juce::String ExampleAudioProcessor::getProgramName (int index)
168 | {
169 | (void)index;
170 | return {};
171 | }
172 |
173 | void ExampleAudioProcessor::changeProgramName (int index, const juce::String& newName)
174 | {
175 | (void)index;
176 | (void)newName;
177 | }
178 |
179 | //==============================================================================
180 | void ExampleAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
181 | {
182 | // Use this method as the place to do any pre-playback
183 | // initialisation that you need..
184 |
185 | this->biquadFilter.setSampleRate(sampleRate);
186 |
187 | // MAGIC GUI: this will setup all internals like MagicPlotSources etc.
188 | magicState.prepareToPlay (sampleRate, samplesPerBlock);
189 | }
190 |
191 | void ExampleAudioProcessor::releaseResources()
192 | {
193 | // When playback stops, you can use this as an opportunity to free up any
194 | // spare memory, etc.
195 | }
196 |
197 | #ifndef JucePlugin_PreferredChannelConfigurations
198 | bool ExampleAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
199 | {
200 | #if JucePlugin_IsMidiEffect
201 | juce::ignoreUnused (layouts);
202 | return true;
203 | #else
204 | // This is the place where you check if the layout is supported.
205 | // In this template code we only support mono or stereo.
206 | // Some plugin hosts, such as certain GarageBand versions, will only
207 | // load plugins that support stereo bus layouts.
208 | if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono()
209 | && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo())
210 | return false;
211 |
212 | // This checks if the input layout matches the output layout
213 | #if ! JucePlugin_IsSynth
214 | if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet())
215 | return false;
216 | #endif
217 |
218 | return true;
219 | #endif
220 | }
221 | #endif
222 |
223 | void ExampleAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages)
224 | {
225 | juce::ScopedNoDenormals noDenormals;
226 | auto totalNumInputChannels = getTotalNumInputChannels();
227 | auto totalNumOutputChannels = getTotalNumOutputChannels();
228 |
229 | // In case we have more outputs than inputs, this code clears any output
230 | // channels that didn't contain input data, (because these aren't
231 | // guaranteed to be empty - they may contain garbage).
232 | // This is here to avoid people getting screaming feedback
233 | // when they first compile a plugin, but obviously you don't need to keep
234 | // this code if your algorithm always overwrites all the output channels.
235 | for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
236 | buffer.clear (i, 0, buffer.getNumSamples());
237 |
238 | // juce::dsp::AudioBlock inBuffer(buffer);
239 | juce::dsp::AudioBlock outBuffer(buffer);
240 |
241 | // This is the place where you'd normally do the guts of your plugin's
242 | // audio processing...
243 | // Make sure to reset the state if your inner loop is processing
244 | // the samples and the outer loop is handling the channels.
245 | // Alternatively, you can process the samples with the channels
246 | // interleaved by keeping the same state.
247 | switch (this->filterPlacement) {
248 | case FilterPlacement::Pre:
249 | this->biquadFilter.process>(outBuffer);
250 |
251 | this->bitReduction.process>(
252 | outBuffer, this->controlReduction->load());
253 |
254 | this->waveWarp.process>(
255 | outBuffer, this->controlWarp->load());
256 | break;
257 | case FilterPlacement::Post:
258 | this->bitReduction.process>(
259 | outBuffer, this->controlReduction->load());
260 |
261 | this->waveWarp.process>(
262 | outBuffer, this->controlWarp->load());
263 |
264 | this->biquadFilter.process>(outBuffer);
265 | break;
266 | default:
267 | this->bitReduction.process>(
268 | outBuffer, this->controlReduction->load());
269 |
270 | this->waveWarp.process>(
271 | outBuffer, this->controlWarp->load());
272 | }
273 |
274 | for (int channel = 0; channel < totalNumInputChannels; ++channel)
275 | {
276 | auto sample = buffer.getWritePointer (channel);
277 |
278 | for (int i = 0; i < buffer.getNumSamples(); ++i)
279 | {
280 | sample[i] *= this->ioGain->load();
281 | }
282 | }
283 |
284 | // magicState.getGuiTree().
285 | oscilloscope->pushSamples(buffer);
286 | }
287 |
288 | //==============================================================================
289 | bool ExampleAudioProcessor::hasEditor() const
290 | {
291 | return true; // (change this to false if you choose to not supply an editor)
292 | }
293 |
294 | // juce::SharedResourcePointer;
295 | juce::AudioProcessorEditor* ExampleAudioProcessor::createEditor()
296 | {
297 | this->mainLNF = std::make_shared();
298 |
299 | // Register GUI items for Foleys GUI Magic
300 | auto builder = std::make_unique (magicState);
301 |
302 | builder->registerJUCEFactories();
303 | builder->registerJUCELookAndFeels();
304 |
305 | builder->registerLookAndFeel("MainLNF", std::make_unique());
306 | LookAndFeel::setDefaultLookAndFeel(this->mainLNF.get());
307 |
308 | builder->registerFactory("DynText", &DynTextItem::factory);
309 |
310 | auto editor = new foleys::MagicPluginEditor (magicState, std::move (builder));
311 |
312 | editor->setResizeLimits (260, 500, 520, 800);
313 | editor->setSize (260, 500);
314 | return editor;
315 | }
316 |
317 | //==============================================================================
318 | void ExampleAudioProcessor::getStateInformation (juce::MemoryBlock& destData)
319 | {
320 | (void)destData;
321 | // You should use this method to store your parameters in the memory block.
322 | // You could do that either as raw data, or use the XML or ValueTree classes
323 | // as intermediaries to make it easy to save and load complex data.
324 | }
325 |
326 | void ExampleAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
327 | {
328 | (void)data;
329 | (void)sizeInBytes;
330 | // You should use this method to restore your parameters from this memory block,
331 | // whose contents will have been created by the getStateInformation() call.
332 | }
333 |
334 | void ExampleAudioProcessor::parameterChanged (const juce::String& param, float value)
335 | {
336 | if (param == IDs::filterCutoff) {
337 | biquadFilter.setFrequency(value);
338 | } else if (param == IDs::filterResonance) {
339 | biquadFilter.setResonance (value);
340 | } else if (param == IDs::filterPlace) {
341 | switch (juce::roundToInt(value)) {
342 | case 1: this->filterPlacement = FilterPlacement::Pre; break;
343 | case 2: this->filterPlacement = FilterPlacement::Post; break;
344 | default: this->filterPlacement = FilterPlacement::Off; break;
345 | }
346 | } else if (param == IDs::ioReductionMix) {
347 | this->bitReduction.setMixAmount(value);
348 | } else if (param == IDs::ioWarpMix) {
349 | this->waveWarp.setMixAmount(value);
350 | }
351 | }
352 |
353 | //==============================================================================
354 | // This creates new instances of the plugin..
355 | juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
356 | {
357 | return new ExampleAudioProcessor();
358 | }
359 |
--------------------------------------------------------------------------------
/src/PluginProcessor.hpp:
--------------------------------------------------------------------------------
1 | /*
2 | ==============================================================================
3 |
4 | This file contains the basic framework code for a JUCE plugin processor.
5 |
6 | ==============================================================================
7 | */
8 |
9 | #pragma once
10 |
11 | #include "JuceHeader.h"
12 | #include "foleys_gui_magic/foleys_gui_magic.h"
13 |
14 | #include "gui/lnf/MainLNF.hpp"
15 | #include "gui/item/DynText.hpp"
16 |
17 | #include "dsp/BitReduction.hpp"
18 | #include "dsp/WaveWarp.hpp"
19 | #include "dsp/BiquadFilter.hpp"
20 |
21 | //==============================================================================
22 | /**
23 | */
24 | class ExampleAudioProcessor : public juce::AudioProcessor,
25 | private juce::AudioProcessorValueTreeState::Listener
26 | #if JucePlugin_Enable_ARA
27 | , public juce::AudioProcessorARAExtension
28 | #endif
29 | {
30 | public:
31 | //==============================================================================
32 | ExampleAudioProcessor();
33 | ~ExampleAudioProcessor() override;
34 |
35 | //==============================================================================
36 | void prepareToPlay (double sampleRate, int samplesPerBlock) override;
37 | void releaseResources() override;
38 |
39 | #ifndef JucePlugin_PreferredChannelConfigurations
40 | bool isBusesLayoutSupported (const BusesLayout& layouts) const override;
41 | #endif
42 |
43 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override;
44 |
45 | //==============================================================================
46 | juce::AudioProcessorEditor* createEditor() override;
47 | bool hasEditor() const override;
48 |
49 | //==============================================================================
50 | const juce::String getName() const override;
51 |
52 | bool acceptsMidi() const override;
53 | bool producesMidi() const override;
54 | bool isMidiEffect() const override;
55 | double getTailLengthSeconds() const override;
56 |
57 | //==============================================================================
58 | int getNumPrograms() override;
59 | int getCurrentProgram() override;
60 | void setCurrentProgram (int index) override;
61 | const juce::String getProgramName (int index) override;
62 | void changeProgramName (int index, const juce::String& newName) override;
63 |
64 | //==============================================================================
65 | void getStateInformation (juce::MemoryBlock& destData) override;
66 | void setStateInformation (const void* data, int sizeInBytes) override;
67 |
68 | private:
69 | //==============================================================================
70 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ExampleAudioProcessor)
71 | void parameterChanged (const juce::String& param, float value) override;
72 |
73 | protected:
74 | juce::AudioProcessorValueTreeState vts;
75 | foleys::MagicProcessorState magicState { *this };
76 |
77 | foleys::MagicPlotSource* oscilloscope = nullptr;
78 |
79 | std::atomic* ioGain;
80 | std::atomic* ioReductionMix;
81 | std::atomic* ioWarpMix;
82 |
83 | std::atomic* controlReduction;
84 | std::atomic* controlWarp;
85 |
86 | std::atomic* filterCutoff;
87 | std::atomic* filterResonance;
88 |
89 | // toggles
90 | enum class FilterPlacement {
91 | Off, Pre, Post
92 | } filterPlacement = FilterPlacement::Pre;
93 |
94 | float outPeak = 0.f;
95 |
96 | // juce::Label outPeakLabel;
97 |
98 | private:
99 | BitReduction bitReduction;
100 | WaveWarp waveWarp;
101 | BiquadFilter biquadFilter;
102 |
103 | std::shared_ptr mainLNF;
104 | };
105 |
--------------------------------------------------------------------------------
/src/dsp/BiquadFilter.cpp:
--------------------------------------------------------------------------------
1 | #include "BiquadFilter.hpp"
2 |
3 | #define _USE_MATH_DEFINES
4 | #include
5 |
6 | BiquadFilter::BiquadFilter(float freq, float res)
7 | : mF(freq),
8 | mQ(res),
9 | mDbGain(0),
10 | mType(BiquadFilter::FilterType::LowPass),
11 | mSampleRate(48000) {
12 |
13 | clear();
14 | this->coeff[0].updateFilterCoeff(this->mType, mF, mQ, mDbGain, this->mSampleRate);
15 | this->coeff[1].updateFilterCoeff(this->mType, mF, mQ, mDbGain, this->mSampleRate);
16 | }
17 |
18 | void BiquadFilter::clear()
19 | {
20 | this->coeff[0].mZ1 = 0;
21 | this->coeff[0].mZ2 = 0;
22 | this->coeff[1].mZ1 = 0;
23 | this->coeff[1].mZ2 = 0;
24 | }
25 |
26 | void BiquadFilter::setFilterType (BiquadFilter::FilterType type)
27 | {
28 | if (type != mType)
29 | {
30 | mType = type;
31 | clear();
32 | }
33 | }
34 |
35 | void BiquadFilter::setFrequency(double f) {
36 |
37 | if (mF != f) {
38 | mF = static_cast(f);
39 | this->updateCoefficients();
40 | }
41 | }
42 |
43 | void BiquadFilter::setResonance(double q) {
44 |
45 | if (mQ != q) {
46 | mQ = static_cast(q);
47 | this->updateCoefficients();
48 | }
49 | }
50 |
51 | void BiquadFilter::Coefficients::updateFilterCoeff(FilterType type, float freq, float res, float dbGain, double sampleRate)
52 | {
53 | if (freq <= 0 || freq != freq)
54 | {
55 | mA0 = 1;
56 | mA1 = 0;
57 | mA2 = 0;
58 | mB1 = 0;
59 | mB2 = 0;
60 | return;
61 | }
62 |
63 | double norm;
64 | double V = pow (10, fabs (dbGain) / 20.0);
65 | double K = tan (M_PI * (freq / sampleRate));
66 |
67 | switch (type)
68 | {
69 | case FilterType::LowPass:
70 | norm = 1 / (1 + K / res + K * K);
71 | mA0 = K * K * norm;
72 | mA1 = 2 * mA0;
73 | mA2 = mA0;
74 | mB1 = 2 * (K * K - 1) * norm;
75 | mB2 = (1 - K / res + K * K) * norm;
76 | break;
77 |
78 | case FilterType::HighPass:
79 | norm = 1 / (1 + K / res + K * K);
80 | mA0 = 1 * norm;
81 | mA1 = -2 * mA0;
82 | mA2 = mA0;
83 | mB1 = 2 * (K * K - 1) * norm;
84 | mB2 = (1 - K / res + K * K) * norm;
85 | break;
86 |
87 | case FilterType::BandPass:
88 | norm = 1 / (1 + K / res + K * K);
89 | mA0 = K / res * norm;
90 | mA1 = 0;
91 | mA2 = -mA0;
92 | mB1 = 2 * (K * K - 1) * norm;
93 | mB2 = (1 - K / res + K * K) * norm;
94 | break;
95 |
96 | case FilterType::Notch:
97 | norm = 1 / (1 + K / res + K * K);
98 | mA0 = (1 + K * K) * norm;
99 | mA1 = 2 * (K * K - 1) * norm;
100 | mA2 = mA0;
101 | mB1 = mA1;
102 | mB2 = (1 - K / res + K * K) * norm;
103 | break;
104 |
105 | case FilterType::Peak:
106 |
107 | if (dbGain >= 0)
108 | { // boost
109 | norm = 1 / (1 + K / res + K * K);
110 | mA0 = (1 + K / res * V + K * K) * norm;
111 | mA1 = 2 * (K * K - 1) * norm;
112 | mA2 = (1 - K / res * V + K * K) * norm;
113 | mB1 = mA1;
114 | mB2 = (1 - K / res + K * K) * norm;
115 | }
116 | else
117 | { // cut
118 | norm = 1 / (1 + K / res * V + K * K);
119 | mA0 = (1 + K / res + K * K) * norm;
120 | mA1 = 2 * (K * K - 1) * norm;
121 | mA2 = (1 - K / res + K * K) * norm;
122 | mB1 = mA1;
123 | mB2 = (1 - K / res * V + K * K) * norm;
124 | }
125 | break;
126 |
127 | case FilterType::LowShelf:
128 | if (dbGain >= 0)
129 | { // boost
130 | norm = 1 / (1 + K / res + K * K);
131 | mA0 = (1 + sqrt (V) * K / res + V * K * K) * norm;
132 | mA1 = 2 * (V * K * K - 1) * norm;
133 | mA2 = (1 - sqrt (V) * K / res + V * K * K) * norm;
134 | mB1 = 2 * (K * K - 1) * norm;
135 | mB2 = (1 - K / res + K * K) * norm;
136 | }
137 | else
138 | { // cut
139 | norm = 1 / (1 + sqrt (V) * K / res + V * K * K);
140 | mA0 = (1 + K / res + K * K) * norm;
141 | mA1 = 2 * (K * K - 1) * norm;
142 | mA2 = (1 - K / res + K * K) * norm;
143 | mB1 = 2 * (V * K * K - 1) * norm;
144 | mB2 = (1 - sqrt (V) * K / res + V * K * K) * norm;
145 | }
146 | break;
147 |
148 | case FilterType::HighShelf:
149 | if (dbGain >= 0)
150 | { // boost
151 | norm = 1 / (1 + K / res + K * K);
152 | mA0 = (V + sqrt (V) * K / res + K * K) * norm;
153 | mA1 = 2 * (K * K - V) * norm;
154 | mA2 = (V - sqrt (V) * K / res + K * K) * norm;
155 | mB1 = 2 * (K * K - 1) * norm;
156 | mB2 = (1 - K / res + K * K) * norm;
157 | }
158 | else
159 | { // cut
160 | norm = 1 / (V + sqrt (V) * K / res + K * K);
161 | mA0 = (1 + K / res + K * K) * norm;
162 | mA1 = 2 * (K * K - 1) * norm;
163 | mA2 = (1 - K / res + K * K) * norm;
164 | mB1 = 2 * (K * K - V) * norm;
165 | mB2 = (V - sqrt (V) * K / res + K * K) * norm;
166 | }
167 | break;
168 |
169 | case FilterType::LowShelfNoQ:
170 | if (dbGain >= 0)
171 | { // boost
172 | norm = 1 / (1 + sqrt (2) * K + K * K);
173 | mA0 = (1 + sqrt (2 * V) * K + V * K * K) * norm;
174 | mA1 = 2 * (V * K * K - 1) * norm;
175 | mA2 = (1 - sqrt (2 * V) * K + V * K * K) * norm;
176 | mB1 = 2 * (K * K - 1) * norm;
177 | mB2 = (1 - sqrt (2) * K + K * K) * norm;
178 | }
179 | else
180 | { // cut
181 | norm = 1 / (1 + sqrt (2 * V) * K + V * K * K);
182 | mA0 = (1 + sqrt (2) * K + K * K) * norm;
183 | mA1 = 2 * (K * K - 1) * norm;
184 | mA2 = (1 - sqrt (2) * K + K * K) * norm;
185 | mB1 = 2 * (V * K * K - 1) * norm;
186 | mB2 = (1 - sqrt (2 * V) * K + V * K * K) * norm;
187 | }
188 | break;
189 |
190 | case FilterType::HighShelfNoQ:
191 | if (dbGain >= 0)
192 | { // boost
193 | norm = 1 / (1 + sqrt (2) * K + K * K);
194 | mA0 = (V + sqrt (2 * V) * K + K * K) * norm;
195 | mA1 = 2 * (K * K - V) * norm;
196 | mA2 = (V - sqrt (2 * V) * K + K * K) * norm;
197 | mB1 = 2 * (K * K - 1) * norm;
198 | mB2 = (1 - sqrt (2) * K + K * K) * norm;
199 | }
200 | else
201 | { // cut
202 | norm = 1 / (V + sqrt (2 * V) * K + K * K);
203 | mA0 = (1 + sqrt (2) * K + K * K) * norm;
204 | mA1 = 2 * (K * K - 1) * norm;
205 | mA2 = (1 - sqrt (2) * K + K * K) * norm;
206 | mB1 = 2 * (K * K - V) * norm;
207 | mB2 = (V - sqrt (2 * V) * K + K * K) * norm;
208 | }
209 | break;
210 |
211 | case FilterType::AllPass:
212 | norm = 1 / (1 + K / res + K * K);
213 | mA0 = (1 - K / res + K * K) * norm;
214 | mA1 = 2 * (K * K - 1) * norm;
215 | mA2 = 1;
216 | mB1 = mA1;
217 | mB2 = mA0;
218 | break;
219 |
220 | case FilterType::Off:
221 | mA0 = 1;
222 | mA1 = 0;
223 | mA2 = 0;
224 | mB1 = 0;
225 | mB2 = 0;
226 | break;
227 | }
228 | }
229 |
230 | //void BiquadFilter::copyCoeffFrom (BiquadFilter& other)
231 | //{
232 | // mA0 = other.mA0;
233 | // mA1 = other.mA1;
234 | // mA2 = other.mA2;
235 | // mB1 = other.mB1;
236 | // mB2 = other.mB2;
237 | //}
238 |
239 | float BiquadFilter::Coefficients::getMagnitudeResponseAt (float f, double sampleRate) const
240 | {
241 | auto const piw0 = (f / sampleRate) * M_PI * 2;
242 | auto const cosw = std::cos (piw0);
243 | auto const sinw = std::sin (piw0);
244 |
245 | auto square = [] (auto z) {
246 | return z * z;
247 | };
248 |
249 | auto const numerator = sqrt (square (mA0 * square (cosw) - mA0 * square (sinw) + mA1 * cosw + mA2) + square (2 * mA0 * cosw * sinw + mA1 * (sinw)));
250 | auto const denominator = sqrt (square (square (cosw) - square (sinw) + mB1 * cosw + mB2) + square (2 * cosw * sinw + mB1 * (sinw)));
251 |
252 | return static_cast (numerator / denominator);
253 | }
254 |
255 | void BiquadFilter::updateCoefficients() {
256 | this->coeff[0].updateFilterCoeff(this->mType, mF, mQ, mDbGain, this->mSampleRate);
257 | this->coeff[1].updateFilterCoeff(this->mType, mF, mQ, mDbGain, this->mSampleRate);
258 | }
259 |
--------------------------------------------------------------------------------
/src/dsp/BiquadFilter.hpp:
--------------------------------------------------------------------------------
1 | #ifndef HANDYOSC_DSP_BIQUAD_FILTER_HPP
2 | #define HANDYOSC_DSP_BIQUAD_FILTER_HPP
3 |
4 | #include "JuceHeader.h"
5 |
6 | class BiquadFilter
7 | {
8 | public:
9 | BiquadFilter() = default;
10 | BiquadFilter(float freq, float res);
11 |
12 | enum class FilterType
13 | {
14 | Off,
15 | LowPass,
16 | HighPass,
17 | BandPass,
18 | Notch,
19 | Peak,
20 | LowShelf,
21 | HighShelf,
22 | LowShelfNoQ,
23 | HighShelfNoQ,
24 | AllPass
25 | };
26 |
27 | template
28 | void process (const ProcessContext& context) noexcept
29 | {
30 | auto& outputBlock = context.getOutputBlock();
31 |
32 | const auto numOutputChannels = outputBlock.getNumChannels();
33 | const auto numSamples = outputBlock.getNumSamples();
34 |
35 | jassert (outputBlock.getNumSamples() == numSamples);
36 | juce::ignoreUnused (numSamples);
37 |
38 | if (numOutputChannels != 2 || numOutputChannels == 0 || numOutputChannels > 2)
39 | return;
40 |
41 | if (context.isBypassed)
42 | return;
43 |
44 | for (size_t channel = 0; channel < numOutputChannels; ++channel)
45 | {
46 | auto* outputSamples = outputBlock.getChannelPointer (channel);
47 |
48 | for (size_t i = 0; i < numSamples; ++i)
49 | {
50 | outputSamples[i] = this->filter(outputSamples[i], channel);
51 | }
52 | }
53 | }
54 |
55 | void setSampleRate (double sampleRate) { mSampleRate = sampleRate; }
56 | void clear();
57 |
58 | void setFilterType (FilterType type);
59 |
60 | void setFrequency(double f);
61 | void setResonance(double q);
62 |
63 | // void copyCoeffFrom (BiquadFilter& other);
64 | bool usesGain() const { return mType == FilterType::Peak || mType == FilterType::HighShelf || mType == FilterType::LowShelf; }
65 | bool usesQ() { return true; }
66 |
67 | inline float filter (float in, int channel) {
68 | double out = in * this->coeff[channel].mA0 + this->coeff[channel].mZ1;
69 | this->coeff[channel].mZ1 = in * this->coeff[channel].mA1 + this->coeff[channel].mZ2 - this->coeff[channel].mB1 * out;
70 | this->coeff[channel].mZ2 = in * this->coeff[channel].mA2 - this->coeff[channel].mB2 * out;
71 | return static_cast(out);
72 | }
73 |
74 | float mF;
75 | float mQ;
76 | float mDbGain;
77 | FilterType mType;
78 |
79 | protected:
80 |
81 | double mSampleRate;
82 |
83 | struct Coefficients {
84 | double mA0;
85 | double mA1;
86 | double mA2;
87 | double mB1;
88 | double mB2;
89 | double mZ1;
90 | double mZ2;
91 |
92 | void updateFilterCoeff(FilterType type, float freq, float res, float dbGain, double sampleRate);
93 | float getMagnitudeResponseAt (float f, double sampleRate) const;
94 |
95 | } coeff[2] = {};
96 |
97 | void updateCoefficients();
98 | };
99 |
100 | #endif // HANDYOSC_DSP_BIQUAD_FILTER_HPP
--------------------------------------------------------------------------------
/src/dsp/BitReduction.cpp:
--------------------------------------------------------------------------------
1 | #include "BitReduction.hpp"
2 |
--------------------------------------------------------------------------------
/src/dsp/BitReduction.hpp:
--------------------------------------------------------------------------------
1 |
2 | #ifndef HANDYOSC_BIT_REDUCTION_HPP
3 | #define HANDYOSC_BIT_REDUCTION_HPP
4 |
5 | #include "JuceHeader.h"
6 |
7 | inline float drywet(float dry, float wet, float mix) {
8 | return (wet * mix) + (dry * (1 - mix));
9 | }
10 |
11 | class BitReduction {
12 | public:
13 | BitReduction() = default;
14 |
15 | template
16 | void process(const ProcessContext& context, float amount) noexcept {
17 | const auto& inputBlock = context.getInputBlock();
18 | auto& outputBlock = context.getOutputBlock();
19 |
20 | const auto numInputChannels = inputBlock.getNumChannels();
21 | const auto numOutputChannels = outputBlock.getNumChannels();
22 | const auto numSamples = outputBlock.getNumSamples();
23 |
24 | jassert (inputBlock.getNumSamples() == numSamples);
25 | juce::ignoreUnused (numSamples);
26 |
27 | if (numOutputChannels != 2 || numInputChannels == 0 || numInputChannels > 2)
28 | return;
29 |
30 | if (numInputChannels == 2)
31 | {
32 | outputBlock.copyFrom (inputBlock);
33 | }
34 | else
35 | {
36 | outputBlock.getSingleChannelBlock (0).copyFrom (inputBlock);
37 | outputBlock.getSingleChannelBlock (1).copyFrom (inputBlock);
38 | }
39 |
40 | if (context.isBypassed)
41 | return;
42 |
43 | for (size_t channel = 0; channel < numInputChannels; ++channel)
44 | {
45 | auto* inputSamples = inputBlock.getChannelPointer (channel);
46 | auto* outputSamples = outputBlock.getChannelPointer (channel);
47 |
48 | for (size_t i = 0; i < numSamples; ++i) {
49 | outputSamples[i] = this->mix(inputSamples[i],
50 | processSample (inputSamples[i], amount)
51 | );
52 | }
53 | }
54 | }
55 |
56 | void setMixAmount(float amount) { this->mixAmount = amount; }
57 |
58 | protected:
59 | inline float processSample(float x, float amount) noexcept {
60 | return static_cast(static_cast(x * amount) / amount);
61 | }
62 |
63 | inline float mix(float dry, float wet) const noexcept {
64 | return (wet * this->mixAmount) + (dry * (1 - this->mixAmount));
65 | }
66 |
67 | float mixAmount = 1.f;
68 | };
69 |
70 | #endif // HANDYOSC_BIT_REDUCTION_HPP
--------------------------------------------------------------------------------
/src/dsp/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | file (GLOB FILES_DSP CONFIGURE_DEPENDS
2 | "*.hpp" "*.cpp"
3 | )
4 |
5 | target_sources(${CMAKE_PROJECT_NAME}
6 | PRIVATE
7 | ${FILES_DSP}
8 | )
--------------------------------------------------------------------------------
/src/dsp/WaveWarp.hpp:
--------------------------------------------------------------------------------
1 | #ifndef HANDYOSC_WAVE_WARP_HPP
2 | #define HANDYOSC_WAVE_WARP_HPP
3 |
4 | #include "JuceHeader.h"
5 |
6 | class WaveWarp {
7 | public:
8 | WaveWarp() = default;
9 |
10 | template
11 | void process(const ProcessContext& context, float amount) noexcept {
12 |
13 | auto& outputBlock = context.getOutputBlock();
14 |
15 | const auto numOutputChannels = outputBlock.getNumChannels();
16 | const auto numSamples = outputBlock.getNumSamples();
17 |
18 | jassert (outputBlock.getNumSamples() == numSamples);
19 | juce::ignoreUnused (numSamples);
20 |
21 | if (numOutputChannels != 2 || numOutputChannels == 0 || numOutputChannels > 2)
22 | return;
23 |
24 | if (context.isBypassed)
25 | return;
26 |
27 | switch (juce::roundToInt(this->type->load())) {
28 |
29 | case 0:
30 |
31 | for (size_t channel = 0; channel < numOutputChannels; ++channel) {
32 | auto* outputSamples = outputBlock.getChannelPointer (channel);
33 | for (size_t i = 0; i < numSamples; ++i) {
34 |
35 | if (std::abs(outputSamples[i]) > std::abs(this->floatSample[channel])) {
36 | this->floatSample[channel] = outputSamples[i];
37 | }
38 | else {
39 | if (this->floatSample[channel] > 0)
40 | this->floatSample[channel] -= amount;
41 | else if (this->floatSample[channel] < 0)
42 | this->floatSample[channel] += amount;
43 | }
44 |
45 | outputSamples[i] = this->mix(outputSamples[i], this->floatSample[channel]);
46 | }
47 | }
48 |
49 | break;
50 | case 1:
51 |
52 | for (size_t channel = 0; channel < numOutputChannels; ++channel) {
53 | auto* outputSamples = outputBlock.getChannelPointer (channel);
54 | for (size_t i = 0; i < numSamples; ++i) {
55 |
56 | if (outputSamples[i] > std::abs(this->floatSample[channel])) {
57 | this->floatSample[channel] = outputSamples[i];
58 | }
59 | else {
60 | if (this->floatSample[channel] > 0)
61 | this->floatSample[channel] -= amount;
62 | else if (this->floatSample[channel] < 0)
63 | this->floatSample[channel] += amount;
64 | }
65 |
66 | outputSamples[i] = this->mix(outputSamples[i], this->floatSample[channel]);
67 | }
68 | }
69 |
70 | break;
71 | case 2:
72 |
73 | for (size_t channel = 0; channel < numOutputChannels; ++channel) {
74 | auto* outputSamples = outputBlock.getChannelPointer (channel);
75 | for (size_t i = 0; i < numSamples; ++i) {
76 |
77 | if (outputSamples[i] > this->floatSample[channel]) {
78 | this->floatSample[channel] = outputSamples[i];
79 | }
80 | else {
81 | if (this->floatSample[channel] > 0)
82 | this->floatSample[channel] -= amount;
83 | else if (this->floatSample[channel] < 0)
84 | this->floatSample[channel] += amount;
85 | }
86 |
87 | outputSamples[i] = this->mix(outputSamples[i], this->floatSample[channel]);
88 | }
89 | }
90 |
91 | break;
92 |
93 | default:
94 | break;
95 | }
96 | }
97 |
98 | void setMixAmount(float amount) { this->mixAmount = amount; }
99 |
100 | std::atomic* type;
101 |
102 | private:
103 |
104 | [[nodiscard]] inline float mix(float dry, float wet) const noexcept {
105 | return (wet * this->mixAmount) + (dry * (1 - this->mixAmount));
106 | }
107 |
108 | float floatSample[2] = { 0.f };
109 | float mixAmount = 1.f;
110 | };
111 | #endif // HANDYOSC_WAVE_WARP_HPP
--------------------------------------------------------------------------------
/src/gui/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | file (GLOB FILES_GUI CONFIGURE_DEPENDS
2 | "*.hpp" "*.cpp"
3 | "item/*.hpp" "item/*.cpp"
4 | "lnf/*.hpp" "lnf/*.cpp"
5 | )
6 |
7 | target_sources(${CMAKE_PROJECT_NAME}
8 | PRIVATE
9 | ${FILES_GUI}
10 | )
--------------------------------------------------------------------------------
/src/gui/item/DynText.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "JuceHeader.h"
3 | #include "foleys_gui_magic/foleys_gui_magic.h"
4 |
5 | class DynTextItem : public foleys::GuiItem
6 | {
7 | public:
8 | FOLEYS_DECLARE_GUI_FACTORY (DynTextItem)
9 |
10 | DynTextItem(foleys::MagicGUIBuilder& builder, const juce::ValueTree& node)
11 | : foleys::GuiItem (builder, node) {
12 |
13 | const auto textColor = node.getProperty("color");
14 | const auto fontSize = node.getProperty("font-size");
15 | const auto initText = node.getProperty("text");
16 |
17 | component = std::make_unique();
18 | addAndMakeVisible(component.get());
19 | }
20 |
21 | ~DynTextItem() {}
22 |
23 | Component* getWrappedComponent() override {
24 | return this->component.get();
25 | }
26 |
27 | void update() override {}
28 |
29 | protected:
30 |
31 | class DynText : public juce::Component {
32 | public:
33 | DynText() {}
34 | ~DynText() {}
35 |
36 | void paint(Graphics& g) override {
37 | auto bounds = Rectangle (0, 0, getWidth(), getHeight()).toFloat();
38 | g.setColour (Colours::white);
39 | g.drawFittedText (text, bounds.toNearestInt(), Justification::centred, 1);
40 | }
41 |
42 | void resized() override {}
43 | void refreshStates() {}
44 | void setText(juce::String text) { this->text.swapWith(text); }
45 |
46 | protected:
47 | juce::String text{"Peak: -inf Db"};
48 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DynText)
49 | };
50 |
51 | std::unique_ptr component;
52 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DynTextItem)
53 | };
54 |
55 |
--------------------------------------------------------------------------------
/src/gui/lnf/MainLNF.cpp:
--------------------------------------------------------------------------------
1 | #include "MainLNF.hpp"
2 |
3 | MainLNF::MainLNF() {
4 | roboto = juce::Typeface::createSystemTypefaceFor (BinaryData::RobotoCondensedRegular_ttf,
5 | BinaryData::RobotoCondensedRegular_ttfSize);
6 |
7 | robotoBold = juce::Typeface::createSystemTypefaceFor (BinaryData::RobotoCondensedBold_ttf,
8 | BinaryData::RobotoCondensedBold_ttfSize);
9 | }
10 |
11 | juce::Typeface::Ptr MainLNF::getTypefaceForFont (const juce::Font& font) {
12 | return font.isBold() ? robotoBold : roboto;
13 | }
14 |
15 | /** Positions ComboBox text to leave room for Label */
16 | void MainLNF::positionComboBoxText (juce::ComboBox& box, juce::Label& label) {
17 | juce::LookAndFeel_V4::positionComboBoxText (box, label);
18 | label.setFont (label.getFont().boldened());
19 | label.setJustificationType (juce::Justification::centred);
20 | }
21 |
22 | /** Creates tabbed layout for tabbed components */
23 | void MainLNF::createTabTextLayout (
24 | const juce::TabBarButton& button,
25 | float length,
26 | float depth,
27 | juce::Colour colour,
28 | juce::TextLayout& textLayout
29 | ) {
30 | juce::Font font (depth * 0.45f, juce::Font::bold);
31 | font.setUnderline (button.hasKeyboardFocus (false));
32 |
33 | juce::AttributedString s;
34 | s.setJustification (juce::Justification::centred);
35 | s.append (button.getButtonText().trim(), font, colour);
36 |
37 | textLayout.createLayout (s, length);
38 | }
39 |
40 | /** Finds the proper parent component for a popup menu with these options */
41 | juce::Component* MainLNF::getParentComponentForMenuOptions (const juce::PopupMenu::Options& options) {
42 | #if JUCE_IOS
43 | if (juce::PluginHostType::getPluginLoadedAs() == juce::AudioProcessor::wrapperType_AudioUnitv3)
44 | {
45 | if (options.getParentComponent() == nullptr && options.getTargetComponent() != nullptr)
46 | return options.getTargetComponent()->getTopLevelComponent();
47 | }
48 | #endif
49 | return juce::LookAndFeel_V2::getParentComponentForMenuOptions (options);
50 | }
--------------------------------------------------------------------------------
/src/gui/lnf/MainLNF.hpp:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "JuceHeader.h"
3 | #include "BinaryData.h"
4 |
5 | class MainLNF : public juce::LookAndFeel_V4 {
6 | public:
7 | MainLNF();
8 |
9 | juce::Typeface::Ptr getTypefaceForFont (const juce::Font&) override;
10 |
11 | /** Positions ComboBox text to leave room for Label */
12 | void positionComboBoxText (juce::ComboBox& box, juce::Label& label) override;
13 |
14 | /** Creates tabbed layout for tabbed components */
15 | void createTabTextLayout (
16 | const juce::TabBarButton& button,
17 | float length,
18 | float depth,
19 | juce::Colour colour,
20 | juce::TextLayout& textLayout);
21 |
22 | /** Finds the proper parent component for a popup menu with these options */
23 | juce::Component* getParentComponentForMenuOptions (const juce::PopupMenu::Options& options) override;
24 |
25 | protected:
26 | std::unique_ptr knob = juce::Drawable::createFromImageData (
27 | BinaryData::knob_svg, BinaryData::knob_svgSize);
28 |
29 | std::unique_ptr pointer = juce::Drawable::createFromImageData (
30 | BinaryData::pointer_svg, BinaryData::pointer_svgSize);
31 |
32 | juce::Typeface::Ptr roboto;
33 | juce::Typeface::Ptr robotoBold;
34 |
35 | private:
36 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainLNF)
37 | };
--------------------------------------------------------------------------------
/src/pch.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
--------------------------------------------------------------------------------