├── .clang-format ├── .github └── workflows │ └── build_and_test.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── CPM.cmake └── spectrum.plist.in ├── fonts └── DroidSansMono.ttf ├── icons └── settings.svg ├── src ├── analyzer │ ├── AnalyzerProcessor.cpp │ └── AnalyzerProcessor.h └── plugin │ ├── SpectrumPlugin.cpp │ ├── SpectrumPlugin.h │ ├── SpectrumPluginEntry.cpp │ ├── SpectrumPluginEntryImpl.cpp │ ├── SpectrumPluginEntryImpl.h │ └── ui │ ├── AnalyzerFrame.h │ ├── Common.h │ ├── DbGridLabelsFrame.h │ ├── FrequencyGridLabelsFrame.h │ ├── GridFrame.h │ ├── MainFrame.h │ └── SettingsFrame.h └── tests └── AnalyzerProcessorTests.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Chromium 3 | AccessModifierOffset: -2 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveAssignments: false 6 | AlignConsecutiveDeclarations: false 7 | AlignEscapedNewlines: Left 8 | AlignOperands: true 9 | AlignTrailingComments: false 10 | AllowAllArgumentsOnNextLine: false 11 | AllowAllConstructorInitializersOnNextLine: false 12 | AllowAllParametersOfDeclarationOnNextLine: false 13 | AllowShortBlocksOnASingleLine: Never 14 | AllowShortCaseLabelsOnASingleLine: true 15 | AllowShortEnumsOnASingleLine: false 16 | AllowShortFunctionsOnASingleLine: Inline 17 | AllowShortIfStatementsOnASingleLine: Never 18 | AllowShortLambdasOnASingleLine: All 19 | AllowShortLoopsOnASingleLine: false 20 | AlwaysBreakAfterReturnType: None 21 | AlwaysBreakBeforeMultilineStrings: false 22 | AlwaysBreakTemplateDeclarations: Yes 23 | BinPackArguments: true 24 | BinPackParameters: true 25 | BraceWrapping: 26 | BeforeCatch: true 27 | BeforeElse: false 28 | BreakBeforeBinaryOperators: None 29 | BreakBeforeBraces: Custom 30 | BreakBeforeTernaryOperators: false 31 | BreakConstructorInitializers: AfterColon 32 | BreakInheritanceList: AfterComma 33 | BreakStringLiterals: true 34 | ColumnLimit: 100 35 | CompactNamespaces: false 36 | ContinuationIndentWidth: 4 37 | Cpp11BracedListStyle: false 38 | DerivePointerAlignment: false 39 | DisableFormat: false 40 | ExperimentalAutoDetectBinPacking: false 41 | FixNamespaceComments: false 42 | IncludeBlocks: Regroup 43 | IncludeCategories: 44 | - Regex: '^[.]' 45 | Priority: 0 46 | - Regex: '^"(.*)"' 47 | Priority: 1 48 | - Regex: '^<.*>' 49 | Priority: 2 50 | IndentCaseLabels: false 51 | IndentPPDirectives: None 52 | IndentWidth: 4 53 | IndentWrappedFunctionNames: false 54 | KeepEmptyLinesAtTheStartOfBlocks: false 55 | Language: Cpp 56 | MaxEmptyLinesToKeep: 1 57 | NamespaceIndentation: None 58 | PackConstructorInitializers: BinPack 59 | PenaltyBreakAssignment: 200 60 | PenaltyBreakBeforeFirstCallParameter: 1000 61 | PenaltyBreakOpenParenthesis: 1000 62 | PenaltyReturnTypeOnItsOwnLine: 1000 63 | PenaltyExcessCharacter: 2 64 | PenaltyIndentedWhitespace: 0 65 | PointerAlignment: Left 66 | ReflowComments: false 67 | SeparateDefinitionBlocks: Always 68 | SortIncludes: CaseInsensitive 69 | SortUsingDeclarations: false 70 | SpaceAfterCStyleCast: false 71 | SpaceAfterLogicalNot: true 72 | SpaceAfterTemplateKeyword: false 73 | SpaceBeforeAssignmentOperators: true 74 | SpaceBeforeCaseColon: false 75 | SpaceBeforeCpp11BracedList: true 76 | SpaceBeforeCtorInitializerColon: true 77 | SpaceBeforeInheritanceColon: true 78 | SpaceBeforeParens: ControlStatements 79 | SpaceBeforeRangeBasedForLoopColon: true 80 | SpaceBeforeSquareBrackets: false 81 | SpaceInEmptyBlock: true 82 | SpaceInEmptyParentheses: false 83 | SpacesInAngles: false 84 | SpacesInCStyleCastParentheses: false 85 | SpacesInContainerLiterals: false 86 | SpacesInParentheses: false 87 | SpacesInSquareBrackets: false 88 | Standard: c++17 89 | UseTab: Never 90 | 91 | ... 92 | -------------------------------------------------------------------------------- /.github/workflows/build_and_test.yml: -------------------------------------------------------------------------------- 1 | name: Spectrum 2 | 3 | on: 4 | workflow_dispatch: # lets you run a build from the UI 5 | push: 6 | pull_request: 7 | 8 | # When pushing new commits, cancel any running builds on that branch 9 | concurrency: 10 | group: ${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | env: 14 | BUILD_TYPE: Release 15 | BUILD_DIR: Builds 16 | HOMEBREW_NO_INSTALL_CLEANUP: 1 17 | SCCACHE_GHA_ENABLED: true 18 | SCCACHE_CACHE_MULTIARCH: 1 19 | IPP_DIR: C:\Program Files (x86)\Intel\oneAPI\ipp\latest\lib\cmake\ipp # This is needed by the IPP cmake config 20 | 21 | defaults: 22 | run: 23 | shell: bash 24 | 25 | jobs: 26 | build_and_test: 27 | # don't double run on PRs 28 | if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name 29 | name: ${{ matrix.name }} 30 | runs-on: ${{ matrix.os }} 31 | strategy: 32 | fail-fast: false # show all errors for each platform (vs. cancel jobs on error) 33 | matrix: 34 | include: 35 | - name: macOS 36 | os: macos-latest 37 | # extra-flags: -G Ninja 38 | - name: Windows 39 | os: windows-latest 40 | 41 | steps: 42 | # Setup MSVC toolchain and developer command prompt (Windows) 43 | - uses: ilammy/msvc-dev-cmd@v1 44 | 45 | - name: Cache IPP (Windows) 46 | if: runner.os == 'Windows' 47 | id: cache-ipp 48 | uses: actions/cache@v4 49 | with: 50 | key: ipp-v6 51 | path: C:\Program Files (x86)\Intel 52 | 53 | - name: Install IPP (Windows) 54 | if: (runner.os == 'Windows') && (steps.cache-ipp.outputs.cache-hit != 'true') 55 | run: | 56 | curl --output oneapi.exe https://registrationcenter-download.intel.com/akdlm/IRC_NAS/2e89fab4-e1c7-4f14-a1ef-6cddba8c5fa7/intel-ipp-2022.0.0.796_offline.exe 57 | ./oneapi.exe -s -x -f oneapi 58 | ./oneapi/bootstrapper.exe -s -c --action install --components=intel.oneapi.win.ipp.devel --eula=accept -p=NEED_VS2022_INTEGRATION=1 --log-dir=. 59 | 60 | - name: Save IPP cache (even on CI fail) 61 | if: runner.os == 'Windows' && (steps.cache-ipp.outputs.cache-hit != 'true') 62 | uses: actions/cache/save@v4 63 | with: 64 | path: C:\Program Files (x86)\Intel 65 | key: ipp-v6 66 | 67 | - name: Install Ninja (Windows) 68 | if: runner.os == 'Windows' 69 | run: choco install ninja 70 | 71 | - name: Install macOS Deps 72 | if: ${{ matrix.name == 'macOS' }} 73 | run: brew install ninja 74 | 75 | - name: Checkout code 76 | uses: actions/checkout@v4 77 | with: 78 | submodules: recursive 79 | fetch-depth: 0 80 | 81 | - name: Cache the build 82 | uses: mozilla-actions/sccache-action@v0.0.9 83 | 84 | - name: Configure 85 | run: >- 86 | cmake -B ${{ env.BUILD_DIR }} -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE}} 87 | -G Ninja -DUSE_SANITIZER=OFF -DBUILD_PLUGIN=ON -DBUILD_TESTS=ON 88 | -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache ${{ matrix.extra-flags }} . 89 | 90 | - name: Build 91 | run: cmake --build ${{ env.BUILD_DIR }} --config ${{ env.BUILD_TYPE }} 92 | 93 | - name: Test 94 | working-directory: ${{ env.BUILD_DIR }} 95 | run: | 96 | for i in {1..5}; do 97 | if [ "$RUNNER_OS" == "Windows" ]; then 98 | ./testrunner.exe 99 | else 100 | ./testrunner 101 | fi 102 | done 103 | 104 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | cmake-build-* 4 | build/ 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.28) 2 | 3 | include(cmake/CPM.cmake) 4 | 5 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") # Use static runtimes on Windows 6 | set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15 CACHE STRING "Build for 10.15") 7 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 8 | 9 | project(spectrum VERSION 1.0.0 LANGUAGES C CXX) 10 | 11 | option(USE_SANITIZER "Build and link with the address sanitizer" OFF) 12 | option(BUILD_TESTS "Build unit test executable" OFF) 13 | option(BUILD_PLUGIN "Build audio plugin" OFF) 14 | 15 | if(NOT CMAKE_BUILD_TYPE) 16 | message(STATUS "CMAKE_BUILD_TYPE unspecified; picking Release") 17 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING "" FORCE) 18 | endif() 19 | 20 | CPMAddPackage("gh:tadmn/tad-bits#500124abce002a69664b3324fba8bff24556f1c4") 21 | include(${tad-bits_SOURCE_DIR}/cmake/compile-options.cmake) 22 | 23 | CPMAddPackage("gh:tadmn/choc#020ebce3e20628d4978e04ab23cea5fc70dea2aa") 24 | CPMAddPackage("gh:tadmn/dsp#7378801601d9d1aae07923f7049d30f858cd447b") 25 | CPMAddPackage("gh:tadmn/farbot#3cf08080cef9e7c0347fdc144e81b02f5e27a8ca") 26 | CPMAddPackage("gh:tadmn/FastFourier#5604ebd66f0e5a8a94780b1f3e3b03f2731cce85") 27 | 28 | add_library(spectrum-analyzer-processor STATIC src/analyzer/AnalyzerProcessor.cpp src/analyzer/AnalyzerProcessor.h) 29 | target_link_libraries(spectrum-analyzer-processor PUBLIC tad-bits choc dsp farbot FastFourier) 30 | target_include_directories(spectrum-analyzer-processor PUBLIC src/Analyzer) 31 | add_compiler_warnings(spectrum-analyzer-processor) 32 | 33 | if (BUILD_TESTS) 34 | CPMAddPackage("gh:catchorg/Catch2@3.8.1") 35 | set(TEST_RUNNER testrunner) 36 | add_executable(${TEST_RUNNER} tests/AnalyzerProcessorTests.cpp) 37 | target_link_libraries(${TEST_RUNNER} PRIVATE spectrum-analyzer-processor Catch2::Catch2WithMain) 38 | target_include_directories(${TEST_RUNNER} PRIVATE src) 39 | add_compiler_warnings(${TEST_RUNNER}) 40 | endif() 41 | 42 | if (BUILD_PLUGIN) 43 | set(PLUGIN_NAME "Spectrum") 44 | 45 | CPMAddPackage("gh:free-audio/clap#1.2.6") 46 | CPMAddPackage("gh:free-audio/clap-helpers#58ab81b1dc8219e859529c1306f364bb3aedf7d5") 47 | 48 | set(CLAP_WRAPPER_DOWNLOAD_DEPENDENCIES TRUE CACHE BOOL "Download plugin dependencies") 49 | set(CLAP_WRAPPER_OUTPUT_NAME "${PLUGIN_NAME}" CACHE BOOL "ClapAsVST3 Wrapper name") 50 | CPMAddPackage("gh:free-audio/clap-wrapper#5ba58f16a7816085d46cb8b7bb40b1eb1d9561d2") 51 | 52 | CPMAddPackage("gh:tadmn/visage#53cf815dbd7b81942401e60cd82fcd31c32ffd7a") 53 | CPMAddPackage("gh:Neargye/magic_enum@0.9.7") 54 | CPMAddPackage("gh:nlohmann/json@3.11.3") 55 | 56 | # Visage embedded file resources 57 | file(GLOB_RECURSE FONT_FILES fonts/*.ttf) 58 | add_embedded_resources(EmbeddedFontResources "Fonts.h" "resources::fonts" "${FONT_FILES}") 59 | 60 | file(GLOB_RECURSE ICON_FILES icons/*.svg) 61 | add_embedded_resources(EmbeddedIconResources "Icons.h" "resources::icons" "${ICON_FILES}") 62 | 63 | file(GLOB_RECURSE SOURCE_FILES src/plugin/*.cpp src/plugin/*.h) 64 | add_library(${PROJECT_NAME}-impl STATIC ${SOURCE_FILES}) 65 | 66 | target_link_libraries(${PROJECT_NAME}-impl PUBLIC clap) 67 | target_link_libraries(${PROJECT_NAME}-impl PRIVATE 68 | spectrum-analyzer-processor 69 | clap-helpers clap-wrapper-extensions 70 | visage EmbeddedFontResources EmbeddedIconResources 71 | magic_enum nlohmann_json 72 | ) 73 | 74 | target_compile_definitions(${PROJECT_NAME}-impl PRIVATE 75 | PLUGIN_NAME="${PLUGIN_NAME}" 76 | PRODUCT_VERSION="${PROJECT_VERSION}" 77 | ) 78 | 79 | add_compiler_warnings(${PROJECT_NAME}-impl) 80 | 81 | make_clapfirst_plugins( 82 | TARGET_NAME ${PROJECT_NAME} 83 | IMPL_TARGET ${PROJECT_NAME}-impl 84 | 85 | OUTPUT_NAME "${PLUGIN_NAME}" 86 | 87 | ENTRY_SOURCE src/plugin/SpectrumPluginEntry.cpp 88 | 89 | BUNDLE_IDENTIFIER "com.tadmn.spectrum" 90 | BUNDLE_VERSION ${PROJECT_VERSION} 91 | 92 | COPY_AFTER_BUILD ON 93 | 94 | PLUGIN_FORMATS CLAP 95 | 96 | ASSET_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${PROJECT_NAME}_artifacts 97 | ) 98 | endif() -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Tad Nicol 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spectrum 2 | Free, liberally licensed, 2-dimensional audio spectrum analyzer. 3 | 4 |