Merge branch 'main' of https://github.com/Blitblank/metabalus
This commit is contained in:
9
.gitmodules
vendored
Normal file
9
.gitmodules
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
[submodule "lib/rtaudio"]
|
||||
path = lib/rtaudio
|
||||
url = https://github.com/thestk/rtaudio.git
|
||||
[submodule "lib/rtmidi"]
|
||||
path = lib/rtmidi
|
||||
url = https://github.com/thestk/rtmidi.git
|
||||
[submodule "lib/yaml-cpp"]
|
||||
path = lib/yaml-cpp
|
||||
url = https://github.com/jbeder/yaml-cpp.git
|
||||
@@ -9,56 +9,38 @@ find_package(Qt6 REQUIRED COMPONENTS Widgets)
|
||||
|
||||
if (WIN32) # windows 11 x86_64
|
||||
|
||||
# Header-only target (real target, no ::)
|
||||
add_library(rtaudio_headers INTERFACE)
|
||||
target_include_directories(rtaudio_headers INTERFACE
|
||||
"C:/rtaudio/include"
|
||||
"C:/rtaudio/include/rtAudio"
|
||||
add_library(RtAudio::rtaudio SHARED IMPORTED)
|
||||
set_target_properties(RtAudio::rtaudio PROPERTIES
|
||||
IMPORTED_LOCATION "${RtAudio_ROOT}/bin/rtaudio.dll"
|
||||
IMPORTED_IMPLIB "${RtAudio_ROOT}/lib/rtaudio.lib"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${RtAudio_ROOT}/include/rtaudio"
|
||||
)
|
||||
|
||||
add_library(RtMidi::rtmidi SHARED IMPORTED)
|
||||
set_target_properties(RtMidi::rtmidi PROPERTIES
|
||||
IMPORTED_LOCATION "${RtMidi_ROOT}/bin/rtmidi.dll"
|
||||
IMPORTED_IMPLIB "${RtMidi_ROOT}/lib/rtmidi.lib"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${RtMidi_ROOT}/include/rtmidi"
|
||||
)
|
||||
|
||||
# Imported binary (real target, no ::)
|
||||
add_library(rtaudio_binary SHARED IMPORTED)
|
||||
set_target_properties(rtaudio_binary PROPERTIES
|
||||
IMPORTED_LOCATION "C:/rtaudio/bin/rtaudio.dll"
|
||||
IMPORTED_IMPLIB "C:/rtaudio/lib/rtaudio.lib"
|
||||
add_library(yaml-cpp SHARED IMPORTED)
|
||||
set_target_properties(yaml-cpp PROPERTIES
|
||||
IMPORTED_LOCATION "${yaml-cpp_ROOT}/bin/yaml-cpp.dll"
|
||||
IMPORTED_IMPLIB "${yaml-cpp_ROOT}/lib/yaml-cpp.lib"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${yaml-cpp_ROOT}/include"
|
||||
)
|
||||
|
||||
# Unified interface target
|
||||
add_library(rtaudio INTERFACE)
|
||||
target_link_libraries(rtaudio INTERFACE
|
||||
rtaudio_headers
|
||||
rtaudio_binary
|
||||
)
|
||||
|
||||
# Public alias (this is where :: belongs)
|
||||
add_library(RtAudio::RtAudio ALIAS rtaudio)
|
||||
|
||||
add_library(rtmidi_headers INTERFACE)
|
||||
target_include_directories(rtmidi_headers INTERFACE
|
||||
"C:/rtmidi/include"
|
||||
"C:/rtmidi/include/rtMidi"
|
||||
)
|
||||
add_library(rtmidi_binary SHARED IMPORTED)
|
||||
set_target_properties(rtmidi_binary PROPERTIES
|
||||
IMPORTED_LOCATION "C:/rtmidi/bin/rtmidi.dll"
|
||||
IMPORTED_IMPLIB "C:/rtmidi/lib/rtmidi.lib"
|
||||
)
|
||||
add_library(rtmidi INTERFACE)
|
||||
target_link_libraries(rtmidi INTERFACE
|
||||
rtmidi_headers
|
||||
rtmidi_binary
|
||||
)
|
||||
add_library(RtMidi::RtMidi ALIAS rtmidi)
|
||||
|
||||
|
||||
else() # debian 12 x86_64
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(RTAUDIO REQUIRED rtaudio)
|
||||
pkg_check_modules(RTMIDI REQUIRED rtmidi)
|
||||
pkg_check_modules(YAMLCPP REQUIRED yaml-cpp)
|
||||
endif()
|
||||
|
||||
qt_standard_project_setup()
|
||||
|
||||
# TODO: prob fix this to make it less ugly
|
||||
# might nest CMakeList.txt files once I clean up the directory structure
|
||||
qt_add_executable(metabolus
|
||||
src/main.cpp
|
||||
src/ui/MainWindow.cpp
|
||||
@@ -72,6 +54,8 @@ qt_add_executable(metabolus
|
||||
src/MidiController.h
|
||||
src/NoteQueue.cpp
|
||||
src/NoteQueue.h
|
||||
src/ConfigInterface.cpp
|
||||
src/ConfigInterface.h
|
||||
src/synth/AudioEngine.cpp
|
||||
src/synth/AudioEngine.h
|
||||
src/synth/Envelope.cpp
|
||||
@@ -108,27 +92,19 @@ target_include_directories(metabolus PRIVATE
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
|
||||
target_compile_options(metabolus PUBLIC "/Zc:__cplusplus")
|
||||
|
||||
target_link_libraries(metabolus
|
||||
PRIVATE
|
||||
RtAudio::rtaudio
|
||||
RtMidi::rtmidi
|
||||
yaml-cpp
|
||||
Qt6::Widgets
|
||||
RtAudio::RtAudio
|
||||
RtMidi::RtMidi
|
||||
)
|
||||
|
||||
add_custom_command(TARGET metabolus POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"C:/rtaudio/bin/rtaudio.dll"
|
||||
$<TARGET_FILE_DIR:metabolus>
|
||||
)
|
||||
|
||||
add_custom_command(TARGET metabolus POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"C:/rtmidi/bin/rtmidi.dll"
|
||||
$<TARGET_FILE_DIR:metabolus>
|
||||
)
|
||||
|
||||
else()
|
||||
target_include_directories(metabolus PRIVATE ${RTAUDIO_INCLUDE_DIRS} ${RTMIDI_INCLUDE_DIRS})
|
||||
target_link_libraries(metabolus PRIVATE Qt6::Widgets ${RTAUDIO_LIBRARIES} ${RTMIDI_LIBRARIES})
|
||||
target_include_directories(metabolus PRIVATE ${RTAUDIO_INCLUDE_DIRS} ${RTMIDI_INCLUDE_DIRS} ${YAMLCPP_INCLUDE_DIRS})
|
||||
target_link_libraries(metabolus PRIVATE Qt6::Widgets ${RTAUDIO_LIBRARIES} ${RTMIDI_LIBRARIES} ${YAMLCPP_LIBARIES})
|
||||
target_compile_options(metabolus PRIVATE ${RTAUDIO_CFLAGS_OTHER})
|
||||
endif()
|
||||
|
||||
57
README.md
57
README.md
@@ -37,9 +37,56 @@ This synthesizer isn't very good, but it's neat :3
|
||||
- [ ] Noise
|
||||
- [ ] LFO modulation
|
||||
|
||||
## setup
|
||||
TODO: instructions on build setup
|
||||
Package Dependencies: Qt 6, RtAudio, RtMidi
|
||||
## Build Instructions
|
||||
|
||||
Prerequisites:
|
||||
CMake: https://cmake.org/download/ \
|
||||
QtWidgets: https://www.qt.io/development/download-qt-installer-oss
|
||||
|
||||
Windows: MSVC (The build scripts use Visual Studio 17 2022)
|
||||
Linux: GCC
|
||||
|
||||
Clone repository
|
||||
```PowerShell
|
||||
git clone https://github.com/Blitblank/metabalus.git --recursive
|
||||
```
|
||||
or if you forgot to --recursive:
|
||||
```PowerShell
|
||||
git clone https://github.com/Blitblank/metabalus.git
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
\
|
||||
Build. The script will build and install dependencies automatically
|
||||
|
||||
On Windows (MSVC):
|
||||
```PowerShell
|
||||
.\scripts\build.ps1 # builds in build/Debug/
|
||||
```
|
||||
|
||||
On Linux (GCC):
|
||||
```Bash
|
||||
./scripts/build.sh
|
||||
```
|
||||
|
||||
TODO: right now you need to run the executable from the executable's directory because of CWD paths and such. Needs to be fixed because annoying
|
||||
|
||||
Configure the CMake/build script if you have issues
|
||||
|
||||
To clean:
|
||||
```
|
||||
.\scripts\clean.ps1
|
||||
./scripts/clean.sh
|
||||
```
|
||||
Note: dependencies are built into build/lib, so don't delete unless you need to rebuild the libraries
|
||||
Use the install_dependencies script to manually install dependencies.
|
||||
|
||||
Build troubleshooting:
|
||||
On windows, `bcdedit /set IncreaseUserVa 3072` solved cc1plus.exe: out of memory errors while building qt for me
|
||||
|
||||
## Configurations (NOT YET IMPLEMENTED)
|
||||
Default config files are located in the config/ directory, and they are replicated into build/config/ if they dont already exist there. To edit the configurations, edit the config files in the build directory, not the defaults. Most config files are loaded/parsed at startup (TODO: investigate some reloading functions), so the program must be restarted, although not recompiled, for new configs to take effect. \
|
||||
Voice profiles are saved into config files into a human-readable format (YAML) and can be edited manually or by saving within the app. \
|
||||
|
||||
## Wavetables (NOT YET IMPLEMENTED)
|
||||
Wavetables are this synthesizer's starting point for audio synthesis. A wavetable (as defined for this synthesizer, not elsewhere) contains a single period of a particular wave-shape with a discrete number of samples. Wavetables are loaded at runtime and sampled by oscillator objects to define and mix different wave shapes. Further specifications, as well as instructions for generating your own wavetable (including an example python script << TODO), are located within config/wavetables/README.md
|
||||
|
||||
$ ./scripts/build.sh
|
||||
PS ./scripts/build.bat
|
||||
|
||||
16
config/audio.yaml
Normal file
16
config/audio.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
# audio.yaml
|
||||
# Configures properties for the RtAudio engine
|
||||
|
||||
# Number of samples per second
|
||||
sampleRate: 44100
|
||||
# unconfigurable: sampleFormat; [-1, 1] float
|
||||
|
||||
# number of audio channels
|
||||
channels: 2
|
||||
|
||||
# 0 = mono, 1 = stereo, 2 = pseudo-stereo
|
||||
stereoMode: 2
|
||||
|
||||
# number of samples per audio buffer
|
||||
bufferSize: 512
|
||||
45
config/profiles/default.yaml
Normal file
45
config/profiles/default.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
|
||||
# default.yaml
|
||||
# Default voice profile
|
||||
|
||||
# sequences in the form [x, x, x] denote [setValue, sliderMinimum, sliderMaximum]
|
||||
|
||||
version: 0x0002
|
||||
|
||||
# deprecated, useless
|
||||
Osc1Freq: [100, 20, 600]
|
||||
|
||||
# wavetable selections
|
||||
OscWaveSelector1: 2
|
||||
OscWaveSelector2: 1
|
||||
|
||||
# Oscillator frequency parameters
|
||||
Osc1OctaveOffset: [0, -5, 5]
|
||||
Osc1SemitoneOffset: [0, -12, 12]
|
||||
Osc1PitchOffset: [0, -100, 100]
|
||||
Osc2OctaveOffset: [1, -5, 5]
|
||||
Osc2SemitoneOffset: [0, -12, 12]
|
||||
Osc2PitchOffset: [0, -100, 100]
|
||||
Osc3OctaveOffset: [1, -5, 5]
|
||||
Osc3SemitoneOffset: [7, -12, 12]
|
||||
Osc3PitchOffset: [1.96, -100, 100]
|
||||
|
||||
# Envelope generator parameters
|
||||
Osc1Volume:
|
||||
- [1, 0, 2] # Depth
|
||||
- [0.05, 0, 2] # Attack
|
||||
- [0.2, 0, 2] # Decay
|
||||
- [0.7, 0, 1] # Sustain
|
||||
- [0.2, 0, 2] # Release
|
||||
FilterCutoff:
|
||||
- [4, 0, 8] # Depth
|
||||
- [0.05, 0, 2] # Attack
|
||||
- [0.2, 0, 2] # Decay
|
||||
- [0.2, 0, 1] # Sustain
|
||||
- [0.25, 0, 2] # Release
|
||||
FilterResonance:
|
||||
- [3, 0, 8] # Depth
|
||||
- [0.05, 0, 2] # Attack
|
||||
- [0.2, 0, 2] # Decay
|
||||
- [0.5, 0, 1] # Sustain
|
||||
- [0.3, 0, 2] # Release
|
||||
1
lib/rtaudio
Submodule
1
lib/rtaudio
Submodule
Submodule lib/rtaudio added at 409636b5dc
1
lib/rtmidi
Submodule
1
lib/rtmidi
Submodule
Submodule lib/rtmidi added at a3233c2294
1
lib/yaml-cpp
Submodule
1
lib/yaml-cpp
Submodule
Submodule lib/yaml-cpp added at 89ff142b99
@@ -1,69 +0,0 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
REM ================================
|
||||
REM Configuration
|
||||
REM ================================
|
||||
|
||||
set BUILD_DIR=build
|
||||
set CONFIG=Release
|
||||
|
||||
set QT_ROOT=C:\Qt\6.10.1\msvc2022_64
|
||||
set RTAUDIO_ROOT=C:\rtaudio
|
||||
set RTMIDI_ROOT=C:\rtmidi
|
||||
|
||||
REM ================================
|
||||
REM Environment setup
|
||||
REM ================================
|
||||
|
||||
call "%ProgramFiles%\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
|
||||
|
||||
set PATH=%QT_ROOT%\bin;%PATH%
|
||||
|
||||
REM ================================
|
||||
REM Configure
|
||||
REM ================================
|
||||
|
||||
if not exist %BUILD_DIR% (
|
||||
mkdir %BUILD_DIR%
|
||||
)
|
||||
|
||||
cmake -S . -B %BUILD_DIR% ^
|
||||
-G Ninja ^
|
||||
-DCMAKE_BUILD_TYPE=%CONFIG% ^
|
||||
-DRTAUDIO_ROOT=%RTAUDIO_ROOT% ^
|
||||
-DRTMIDI_ROOT=%RTMIDI_ROOT%
|
||||
|
||||
if errorlevel 1 goto error
|
||||
|
||||
REM ================================
|
||||
REM Build
|
||||
REM ================================
|
||||
|
||||
cmake --build %BUILD_DIR%
|
||||
|
||||
if errorlevel 1 goto error
|
||||
|
||||
REM ================================
|
||||
REM Deploy Qt + RtAudio
|
||||
REM ================================
|
||||
|
||||
cd %BUILD_DIR%
|
||||
|
||||
windeployqt metabolus.exe
|
||||
|
||||
copy "%RTAUDIO_ROOT%\bin\rtaudio.dll" .
|
||||
copy "%RTMIDI_ROOT%\bin\rtmidi.dll" .
|
||||
|
||||
echo.
|
||||
echo Build successful.
|
||||
goto end
|
||||
|
||||
:error
|
||||
echo.
|
||||
echo Build FAILED.
|
||||
exit /b 1
|
||||
|
||||
:end
|
||||
endlocal
|
||||
pause
|
||||
76
scripts/build.ps1
Normal file
76
scripts/build.ps1
Normal file
@@ -0,0 +1,76 @@
|
||||
|
||||
$PROJECT_ROOT = $PWD
|
||||
|
||||
# config
|
||||
|
||||
$BUILD_DIR = "$PWD/build"
|
||||
$CONFIG = "Release"
|
||||
|
||||
# change these to your need
|
||||
# or TODO: make qt_root configurable
|
||||
$QT_ROOT = "C:\Qt\6.10.1\msvc2022_64"
|
||||
$RTAUDIO_ROOT = "$BUILD_DIR\lib\rtaudio"
|
||||
$RTMIDI_ROOT = "$BUILD_DIR\lib\rtmidi"
|
||||
$YAMLCPP_ROOT = "$BUILD_DIR\lib\yaml-cpp"
|
||||
|
||||
$CONFIG_ROOT = "$PROJECT_ROOT\config"
|
||||
|
||||
# setup
|
||||
|
||||
& "$Env:Programfiles\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
|
||||
|
||||
$PATH="$QT_ROOT\bin;$PATH"
|
||||
|
||||
if (-not (Test-Path -Path $BUILD_DIR)) {
|
||||
mkdir $BUILD_DIR
|
||||
}
|
||||
|
||||
# detect dependencies
|
||||
|
||||
$libraries = @("rtaudio", "rtmidi", "yaml-cpp")
|
||||
$dependencies_found = 0
|
||||
foreach ($lib in $libraries) {
|
||||
if (Test-Path -Path ".\build\lib\$lib") {
|
||||
Write-Host "found $lib"
|
||||
$dependencies_found++
|
||||
} else {
|
||||
Write-Host "did not find $lib"
|
||||
}
|
||||
}
|
||||
|
||||
if (-not ($dependencies_found -eq $libraries.Count)) {
|
||||
& "scripts\install_dependencies.ps1"
|
||||
} else {
|
||||
Write-Host "All dependencies detected, skipping dependency install step..."
|
||||
}
|
||||
|
||||
# configure
|
||||
Write-Host "Configuring metabolus..."
|
||||
cmake -S . -B $BUILD_DIR -G "Visual Studio 17 2022" `
|
||||
-DQt6_ROOT="$QT_ROOT\lib\cmake\Qt6" `
|
||||
-DRtAudio_ROOT="$RTAUDIO_ROOT" `
|
||||
-DRtMidi_ROOT="$RTMIDI_ROOT" `
|
||||
-Dyaml-cpp_ROOT="$YAMLCPP_ROOT" `
|
||||
|
||||
|
||||
# build
|
||||
Write-Host "Building metabolus..."
|
||||
cmake --build $BUILD_DIR --config $CONFIG
|
||||
|
||||
# TODO: install
|
||||
|
||||
# link dlls
|
||||
Write-Host "Deploying metabolus..."
|
||||
cd $BUILD_DIR
|
||||
|
||||
& "$QT_ROOT\bin\windeployqt6.exe" .\$CONFIG\metabolus.exe
|
||||
|
||||
# copy dlls
|
||||
Copy-Item -Path "$RTAUDIO_ROOT\bin\rtaudio.dll" -Destination .\$CONFIG
|
||||
Copy-Item -Path "$RTMIDI_ROOT\bin\rtmidi.dll" -Destination .\$CONFIG
|
||||
Copy-Item -Path "$YAMLCPP_ROOT\bin\yaml-cpp.dll" -Destination .\$CONFIG
|
||||
|
||||
# copy configs, but don't overwrite
|
||||
Copy-Item -Path "$CONFIG_ROOT" -Destination ".\$CONFIG\" -Recurse -ErrorAction SilentlyContinue
|
||||
|
||||
cd $PROJECT_ROOT
|
||||
42
scripts/install_dependencies.ps1
Normal file
42
scripts/install_dependencies.ps1
Normal file
@@ -0,0 +1,42 @@
|
||||
|
||||
echo "Installing dependencies ... "
|
||||
|
||||
# TODO: add a clean (like delete build dirs) script
|
||||
|
||||
$project_root = $PWD
|
||||
|
||||
if (-not (Test-Path -Path "$PWD\build\lib")) {
|
||||
mkdir "$PWD\build\lib"
|
||||
}
|
||||
|
||||
$build_lib_dir = "$PWD\build\lib"
|
||||
|
||||
# rtaudio
|
||||
mkdir "$build_lib_dir\rtaudio" -Force
|
||||
cd $project_root\lib\rtaudio
|
||||
cmake -S . -B build -G "Visual Studio 17 2022" `
|
||||
-DRTDUIO_API_WASAPI=ON `
|
||||
-DRTAUDIO_API_DS=OFF `
|
||||
-DRT_AUDIO_API_ASIO=OFF `
|
||||
-DRTAUDIO_BUILD_SHARED_LIBS=ON
|
||||
cmake --build build --config Release
|
||||
cmake --install build --prefix "$build_lib_dir\rtaudio"
|
||||
|
||||
# rtmidi
|
||||
mkdir "$build_lib_dir\rtmidi" -Force
|
||||
cd $project_root\lib\rtmidi
|
||||
cmake -S . -B build -G "Visual Studio 17 2022" `
|
||||
-DRT_MIDI_API_WINMM=ON `
|
||||
-DRTMIDI_BUILD_SHARED_LIBS=ON
|
||||
cmake --build build --config Release
|
||||
cmake --install build --prefix "$build_lib_dir\rtmidi"
|
||||
|
||||
# yaml-cpp
|
||||
mkdir "$build_lib_dir\yaml-cpp" -Force
|
||||
cd $project_root\lib\yaml-cpp
|
||||
cmake -S . -B build -G "Visual Studio 17 2022" `
|
||||
-DYAML_BUILD_SHARED_LIBS=ON
|
||||
cmake --build build --config Release
|
||||
cmake --install build --prefix "$build_lib_dir\yaml-cpp"
|
||||
|
||||
cd $project_root
|
||||
0
scripts/install_dependencies.sh
Normal file
0
scripts/install_dependencies.sh
Normal file
113
src/ConfigInterface.cpp
Normal file
113
src/ConfigInterface.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
|
||||
#include "ConfigInterface.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
ConfigInterface::ConfigInterface() {
|
||||
|
||||
//std::cout << "Config constructor" << std::endl;
|
||||
|
||||
}
|
||||
|
||||
ConfigInterface::ConfigInterface(ParameterStore* params): params_(params) {
|
||||
|
||||
}
|
||||
|
||||
// lots of checking in this to make this safe
|
||||
int ConfigInterface::getValue(ConfigFile file, std::string key, int defaultVal) {
|
||||
|
||||
// assemble filepath
|
||||
std::string filepath = configRoot + "/" + filePaths[static_cast<int>(file)];
|
||||
filepath = fs::absolute(filepath).string();
|
||||
|
||||
// attempt to open file
|
||||
YAML::Node config;
|
||||
try {
|
||||
|
||||
YAML::Node config = YAML::LoadFile(filepath);
|
||||
|
||||
// read key if it exists
|
||||
if(config[key]) {
|
||||
return config[key].as<int>(defaultVal);
|
||||
} else {
|
||||
return -1; // key does not exist
|
||||
}
|
||||
|
||||
} catch(const std::exception& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// unreachable
|
||||
return -1;
|
||||
|
||||
}
|
||||
|
||||
// ugly but if it works it works
|
||||
void ConfigInterface::loadProfile(std::string filename) {
|
||||
|
||||
// load file
|
||||
std::string filepath = "config/profiles/" + filename + ".yaml";
|
||||
filepath = std::filesystem::absolute(filepath).string();
|
||||
YAML::Node config;
|
||||
try {
|
||||
config = YAML::LoadFile(filepath);
|
||||
} catch(const std::exception& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// check version
|
||||
int version = config["version"].as<int>(); // yaml-cpp parses unquoted hex as integers
|
||||
if(version < CONFIG_VERSION) {
|
||||
std::cout << "Parameter profile version " << version << "is outdated below the compatible version " << CONFIG_VERSION << std::endl;
|
||||
return;
|
||||
} else {
|
||||
std::cout << version << std::endl;
|
||||
}
|
||||
|
||||
// extract values from the config file
|
||||
std::array<ParamDefault, 5> osc1VolumeProfile = loadEnvProfile(&config, "Osc1Volume");
|
||||
std::array<ParamDefault, 5> fCutoffProfile = loadEnvProfile(&config, "FilterCutoff");
|
||||
std::array<ParamDefault, 5> fResonanceProfile = loadEnvProfile(&config, "FilterResonance");
|
||||
|
||||
// TODO: remove this once all the parameters are set properly
|
||||
params_->resetToDefaults();
|
||||
|
||||
// set the values in the paramstore
|
||||
params_->set(EnvelopeId::Osc1Volume, osc1VolumeProfile[0].def, osc1VolumeProfile[1].def, osc1VolumeProfile[2].def, osc1VolumeProfile[3].def, osc1VolumeProfile[4].def);
|
||||
params_->set(EnvelopeId::FilterCutoff, fCutoffProfile[0].def, fCutoffProfile[1].def, fCutoffProfile[2].def, fCutoffProfile[3].def, fCutoffProfile[4].def);
|
||||
params_->set(EnvelopeId::FilterResonance, fResonanceProfile[0].def, fResonanceProfile[1].def, fResonanceProfile[2].def, fResonanceProfile[3].def, fResonanceProfile[4].def);
|
||||
// TODO: why do I bother passing in 5 values independently when I can just do an array ?
|
||||
// VVV look down there its so easy
|
||||
|
||||
// TODO:
|
||||
// load wavetable settings
|
||||
// load oscillator pitch settings
|
||||
|
||||
}
|
||||
|
||||
std::array<ParamDefault, 5> ConfigInterface::loadEnvProfile(YAML::Node* node, std::string profile) {
|
||||
|
||||
YAML::Node envelopeNode = (*node)[profile];
|
||||
|
||||
std::array<ParamDefault, 5> paramProfile;
|
||||
|
||||
for(int i = 0; i < paramProfile.size(); i++) {
|
||||
paramProfile[i] = { envelopeNode[i][0].as<float>(), envelopeNode[i][1].as<float>(), envelopeNode[i][2].as<float>() };
|
||||
}
|
||||
|
||||
return paramProfile;
|
||||
}
|
||||
|
||||
std::array<ParamDefault, 5> ConfigInterface::loadEnvProfile(std::string filename, std::string profile) {
|
||||
|
||||
std::string filepath = "config/profiles/" + filename + ".yaml";
|
||||
filepath = std::filesystem::absolute(filepath).string();
|
||||
YAML::Node config = YAML::LoadFile(filepath);
|
||||
|
||||
return loadEnvProfile(&config, profile);
|
||||
}
|
||||
46
src/ConfigInterface.h
Normal file
46
src/ConfigInterface.h
Normal file
@@ -0,0 +1,46 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include "yaml-cpp/yaml.h"
|
||||
|
||||
#include "ParameterStore.h"
|
||||
|
||||
#define CONFIG_VERSION 0x0002
|
||||
|
||||
enum class ConfigFile {
|
||||
Audio = 0
|
||||
// other files here
|
||||
};
|
||||
|
||||
// might have a config file for specifying paths to other config files instead of this
|
||||
const std::vector<std::string> filePaths = {
|
||||
"audio.yaml"
|
||||
};
|
||||
|
||||
// Reads from yaml config files
|
||||
// Handles things like profile loading
|
||||
class ConfigInterface {
|
||||
|
||||
public:
|
||||
|
||||
ConfigInterface();
|
||||
ConfigInterface(ParameterStore* params);
|
||||
~ConfigInterface() = default;
|
||||
|
||||
int getValue(ConfigFile file, std::string key, int defaultVal);
|
||||
|
||||
void loadProfile(std::string filename);
|
||||
std::array<ParamDefault, 5> loadEnvProfile(YAML::Node* node, std::string profile);
|
||||
std::array<ParamDefault, 5> loadEnvProfile(std::string filename, std::string profile);
|
||||
|
||||
private:
|
||||
|
||||
const std::string configRoot = "config";
|
||||
|
||||
// loading parameters
|
||||
ParameterStore* params_;
|
||||
|
||||
};
|
||||
@@ -9,7 +9,7 @@ MidiController::MidiController(NoteQueue& queue) : noteQueue_(queue) {
|
||||
midiIn_ = std::make_unique<RtMidiIn>();
|
||||
midiIn_->ignoreTypes(false, false, false);
|
||||
} catch (RtMidiError& e) {
|
||||
std::cerr << "RtMidi init failed: " << e.getMessage() << std::endl;
|
||||
std::cout << "RtMidi init failed: " << e.getMessage() << std::endl;
|
||||
}
|
||||
// TODO: this still doesnt work on windows
|
||||
}
|
||||
@@ -22,7 +22,7 @@ MidiController::~MidiController() {
|
||||
bool MidiController::openDefaultPort() {
|
||||
if (!midiIn_) return false;
|
||||
if (midiIn_->getPortCount() == 0) {
|
||||
std::cerr << "No MIDI input ports available\n";
|
||||
std::cout << "No MIDI input ports available" << std::endl;
|
||||
return false;
|
||||
}
|
||||
return openPort(0);
|
||||
@@ -34,7 +34,7 @@ bool MidiController::openPort(unsigned int index) {
|
||||
try {
|
||||
midiIn_->openPort(index);
|
||||
midiIn_->setCallback(&MidiController::midiCallback, this);
|
||||
std::cout << "Opened MIDI port: " << midiIn_->getPortName(index) << "\n";
|
||||
std::cout << "Opened MIDI port: " << midiIn_->getPortName(index) << std::endl;
|
||||
return true;
|
||||
} catch (RtMidiError& e) {
|
||||
std::cerr << e.getMessage() << std::endl;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// add event to noteQueue, called by MidiController or keyboardController
|
||||
bool NoteQueue::push(const NoteEvent& event) {
|
||||
size_t head = head_.load(std::memory_order_relaxed);
|
||||
size_t next = (head + 1) % SIZE;
|
||||
size_t next = (head + 1) % SYNTH_NOTE_QUEUE_SIZE;
|
||||
|
||||
if(next == tail_.load(std::memory_order_relaxed)) return false; // full
|
||||
|
||||
@@ -22,7 +22,7 @@ bool NoteQueue::pop(NoteEvent& event) {
|
||||
if(tail == head_.load(std::memory_order_acquire)) return false; // empty
|
||||
|
||||
event = buffer_[tail];
|
||||
tail_.store((tail + 1) % SIZE, std::memory_order_release);
|
||||
tail_.store((tail + 1) % SYNTH_NOTE_QUEUE_SIZE, std::memory_order_release);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <cstdint>
|
||||
#include <chrono>
|
||||
|
||||
#define SYNTH_NOTE_QUEUE_SIZE 128
|
||||
|
||||
enum class NoteEventType {
|
||||
NoteOn,
|
||||
NoteOff
|
||||
@@ -29,9 +31,8 @@ public:
|
||||
bool pop(NoteEvent& event);
|
||||
|
||||
private:
|
||||
static constexpr size_t SIZE = 128;
|
||||
|
||||
std::array<NoteEvent, SIZE> buffer_;
|
||||
std::array<NoteEvent, SYNTH_NOTE_QUEUE_SIZE> buffer_;
|
||||
std::atomic<size_t> head_{ 0 };
|
||||
std::atomic<size_t> tail_{ 0 };
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
|
||||
#include "ParameterStore.h"
|
||||
|
||||
#include <iostream>
|
||||
#include "yaml-cpp/yaml.h" // TODO: using yaml.h outside of ConfigInterface feels spaghetti to me
|
||||
#include <filesystem>
|
||||
|
||||
ParameterStore::ParameterStore() {
|
||||
resetToDefaults();
|
||||
//resetToDefaults();
|
||||
}
|
||||
|
||||
// set parameter value
|
||||
@@ -27,9 +31,9 @@ float ParameterStore::get(ParamId id) const {
|
||||
}
|
||||
|
||||
void ParameterStore::resetToDefaults() {
|
||||
|
||||
for(size_t i = 0; i < PARAM_COUNT; i++) {
|
||||
values_[i].store(PARAM_DEFS[i].def, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: applying parameter profiles will work similarly to above function
|
||||
}
|
||||
|
||||
@@ -2,19 +2,20 @@
|
||||
#include <QApplication>
|
||||
|
||||
#include "ui/MainWindow.h"
|
||||
#include "ConfigInterface.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
// std::cout << "Main()" << std::endl;
|
||||
|
||||
|
||||
QApplication app(argc, argv);
|
||||
|
||||
MainWindow window; // entry point goes to MainWindow::MainWindow()
|
||||
window.show();
|
||||
|
||||
int status = app.exec(); // assembles ui
|
||||
int status = app.exec(); // app execution; blocks until window close
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -3,14 +3,12 @@
|
||||
|
||||
#include <iostream>
|
||||
|
||||
AudioEngine::AudioEngine() : synth_(params_) {
|
||||
AudioEngine::AudioEngine(ConfigInterface* config, ParameterStore* params) : params_(params), synth_(params), config_(config) {
|
||||
|
||||
if(audio_.getDeviceCount() < 1) {
|
||||
throw std::runtime_error("No audio devices found");
|
||||
}
|
||||
|
||||
// TODO: get audio configurations
|
||||
synth_.setSampleRate(sampleRate_);
|
||||
|
||||
synth_.setScopeBuffer(&scope_);
|
||||
|
||||
}
|
||||
@@ -21,6 +19,13 @@ AudioEngine::~AudioEngine() {
|
||||
|
||||
bool AudioEngine::start() {
|
||||
|
||||
// get config values
|
||||
sampleRate_ = config_->getValue(ConfigFile::Audio, "sampleRate", sampleRate_);
|
||||
bufferFrames_ = config_->getValue(ConfigFile::Audio, "bufferSize", bufferFrames_);
|
||||
channels_ = config_->getValue(ConfigFile::Audio, "channels", channels_);
|
||||
|
||||
synth_.setSampleRate(sampleRate_);
|
||||
|
||||
// initialize the audio engine
|
||||
RtAudio::StreamParameters params;
|
||||
params.deviceId = audio_.getDefaultOutputDevice();
|
||||
@@ -46,11 +51,13 @@ void AudioEngine::stop() {
|
||||
if(audio_.isStreamOpen()) audio_.closeStream();
|
||||
}
|
||||
|
||||
int32_t AudioEngine::audioCallback( void* outputBuffer, void*, uint32_t nFrames, double, RtAudioStreamStatus status, void* userData) {
|
||||
// called by RtAudio continuously, sends outputBuffer to audio drivers
|
||||
int32_t AudioEngine::audioCallback(void* outputBuffer, void*, uint32_t nFrames, double, RtAudioStreamStatus status, void* userData) {
|
||||
|
||||
// error if process is too slow for the callback. If this is consistent, then need to optimize synth.process() or whatever cascades from it
|
||||
if (status) std::cerr << "Stream underflow" << std::endl;
|
||||
|
||||
// populate audio buffer
|
||||
return static_cast<AudioEngine*>(userData)->process(static_cast<float*>(outputBuffer), nFrames);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <stdint.h>
|
||||
#include <atomic>
|
||||
|
||||
#include "../ConfigInterface.h"
|
||||
#include "Synth.h"
|
||||
#include "../KeyboardController.h"
|
||||
|
||||
@@ -17,7 +18,9 @@
|
||||
class AudioEngine {
|
||||
|
||||
public:
|
||||
AudioEngine();
|
||||
|
||||
AudioEngine() = default;
|
||||
AudioEngine(ConfigInterface* config, ParameterStore* params);
|
||||
~AudioEngine();
|
||||
|
||||
// starts the audio stream. returns true on success and false on failure
|
||||
@@ -27,7 +30,7 @@ public:
|
||||
void stop();
|
||||
|
||||
// getters
|
||||
ParameterStore* parameters() { return ¶ms_; }
|
||||
ParameterStore* parameters() { return params_; }
|
||||
NoteQueue& noteQueue() { return noteQueue_; }
|
||||
ScopeBuffer& scopeBuffer() { return scope_; }
|
||||
|
||||
@@ -39,7 +42,8 @@ private:
|
||||
// calls the synth.process to generate a buffer of audio samples
|
||||
int32_t process(float* out, uint32_t nFrames);
|
||||
|
||||
ParameterStore params_; // stores the control parameters
|
||||
ConfigInterface* config_; // access to config files
|
||||
ParameterStore* params_; // stores the control parameters
|
||||
NoteQueue noteQueue_; // stores note events for passing between threads
|
||||
Synth synth_; // generates audio
|
||||
ScopeBuffer scope_ { 1024 }; // stores audio samples for visualization
|
||||
|
||||
@@ -11,8 +11,13 @@ void ScopeBuffer::push(float sample) {
|
||||
buffer_[w % buffer_.size()] = sample;
|
||||
}
|
||||
|
||||
// TODO: needs a mutex/spinlock to prevent flickering
|
||||
// outputs value from the scope buffer, called by the scope widget
|
||||
void ScopeBuffer::read(std::vector<float>& out) const {
|
||||
|
||||
// yeah this didn't work, maybe it needs to be atomic or something
|
||||
//while(!spinLock_) { int x = 1 + 1; }
|
||||
|
||||
size_t w = writeIndex_.load(std::memory_order_relaxed);
|
||||
for (size_t i = 0; i < out.size(); i++) {
|
||||
size_t idx = (w + trigger_ + i * wavelength_ / out.size()) % buffer_.size();
|
||||
|
||||
@@ -21,6 +21,7 @@ public:
|
||||
void setWavelength(int32_t wavelength) { wavelength_ = wavelength; }
|
||||
int32_t trigger() { return trigger_; }
|
||||
int32_t wavelength() { return wavelength_; }
|
||||
void spinlock(bool lock) { spinLock_ = lock; };
|
||||
|
||||
// NOTE: there are limits to the wavelengths that the scope can show cleanly due to the size of the audio buffer
|
||||
// at a buffer size of 256 at 44100hz the min visible steady frequency is ~172hz
|
||||
@@ -33,4 +34,6 @@ private:
|
||||
int32_t trigger_ = 0; // units in array indices
|
||||
int32_t wavelength_ = 400;
|
||||
|
||||
bool spinLock_ = false;
|
||||
|
||||
};
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
Synth::Synth(const ParameterStore& params) : paramStore_(params) {
|
||||
Synth::Synth(ParameterStore* params) : paramStore_(params) {
|
||||
voices_.fill(Voice(params_.data(), &wavetable_));
|
||||
}
|
||||
|
||||
void Synth::updateParams() {
|
||||
for(size_t i = 0; i < PARAM_COUNT; i++) {
|
||||
params_[i].target = paramStore_.get(static_cast<ParamId>(i));
|
||||
params_[i].target = paramStore_->get(static_cast<ParamId>(i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,9 @@ void Synth::process(float* out, uint32_t nFrames, uint32_t sampleRate) {
|
||||
}
|
||||
}
|
||||
|
||||
// lock the scope from the buffer
|
||||
scope_->spinlock(true);
|
||||
|
||||
for (uint32_t i = 0; i < nFrames; i++) {
|
||||
|
||||
// updates internal buffered parameters for smoothing
|
||||
@@ -133,4 +136,7 @@ void Synth::process(float* out, uint32_t nFrames, uint32_t sampleRate) {
|
||||
}
|
||||
}
|
||||
|
||||
// unlock the scope from the buffer
|
||||
scope_->spinlock(false);
|
||||
|
||||
}
|
||||
@@ -15,7 +15,8 @@
|
||||
class Synth {
|
||||
|
||||
public:
|
||||
Synth(const ParameterStore& params);
|
||||
Synth() = default;
|
||||
Synth(ParameterStore* params);
|
||||
~Synth() = default;
|
||||
|
||||
// generates a buffer of audio samples nFrames long
|
||||
@@ -39,7 +40,7 @@ private:
|
||||
Voice* findFreeVoice();
|
||||
Voice* findVoiceByNote(uint8_t note);
|
||||
|
||||
const ParameterStore& paramStore_;
|
||||
ParameterStore* paramStore_;
|
||||
// smoothed params creates a buffer in case the thread controlling paramStore gets blocked
|
||||
std::array<SmoothedParam, PARAM_COUNT> params_;
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ float Voice::process(float* params, bool& scopeTrigger) {
|
||||
float osc3 = oscillators_[2].process(osc3NoteOffset + note_, getParam(ParamId::Osc3PitchOffset)/100.0f, temp);
|
||||
|
||||
// mix oscillators
|
||||
float sampleOut = (osc1 + osc2*0.5f + osc3*0.25f) * gain;
|
||||
float sampleOut = (osc1 + osc2*0.25f + osc3*0.125f) * gain;
|
||||
|
||||
// filter sample
|
||||
float baseFreq = oscillators_[0].frequency();
|
||||
@@ -106,8 +106,8 @@ float Voice::process(float* params, bool& scopeTrigger) {
|
||||
float resonance = resonanceEnv * getParam(ParamId::FilterResonanceDepth) * velocityGain;
|
||||
filter1_.setParams(Filter::Type::BiquadLowpass, cutoffFreq, resonance);
|
||||
filter2_.setParams(Filter::Type::BiquadLowpass, cutoffFreq, resonance);
|
||||
sampleOut = filter1_.biquadProcess(sampleOut);
|
||||
sampleOut = filter2_.biquadProcess(sampleOut);
|
||||
float filteredSample = filter1_.biquadProcess(sampleOut);
|
||||
// sampleOut = filter2_.biquadProcess(sampleOut); // TODO: for some reason second filter is unstable only on windows 🤷
|
||||
|
||||
return sampleOut;
|
||||
return filteredSample;
|
||||
}
|
||||
@@ -63,6 +63,7 @@ private:
|
||||
// filters
|
||||
Filter filter1_;
|
||||
Filter filter2_;
|
||||
// TODO: I think the filter's state being uninitialized is what's causing popping when a voice starts for the first time
|
||||
|
||||
// paramstore pointer
|
||||
SmoothedParam* params_;
|
||||
|
||||
@@ -9,7 +9,7 @@ WavetableController::WavetableController() {
|
||||
|
||||
init();
|
||||
|
||||
std::cout << "wavetable init" << std::endl;
|
||||
//std::cout << "wavetable init" << std::endl;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
#define SYNTH_WAVETABLE_SIZE 2048
|
||||
#ifndef M_PI // I hate my stupid chungus life
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
MainWindow::MainWindow(QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
ui_(new Ui::MainWindow),
|
||||
audio_(new AudioEngine()),
|
||||
config_(ConfigInterface(¶ms_)),
|
||||
audio_(new AudioEngine(&config_, ¶ms_)),
|
||||
keyboard_(audio_->noteQueue()),
|
||||
midi_(audio_->noteQueue()) {
|
||||
|
||||
@@ -52,7 +53,10 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
audio_->start();
|
||||
|
||||
// midi
|
||||
#ifndef _WIN32
|
||||
midi_.openPort(1); // TODO: error check
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow() {
|
||||
@@ -71,12 +75,13 @@ void MainWindow::onResetClicked() {
|
||||
|
||||
// initialize to defaults
|
||||
|
||||
// envelopeGenerators
|
||||
ui_->envelopeOsc1Volume->init(EnvelopeId::Osc1Volume);
|
||||
ui_->envelopeFilterCutoff->init(EnvelopeId::FilterCutoff);
|
||||
ui_->envelopeFilterResonance->init(EnvelopeId::FilterResonance);
|
||||
config_.loadProfile("default");
|
||||
|
||||
// update ui from the paramstore
|
||||
ui_->envelopeOsc1Volume->init(EnvelopeId::Osc1Volume, config_.loadEnvProfile("default", "Osc1Volume"));
|
||||
ui_->envelopeFilterCutoff->init(EnvelopeId::FilterCutoff, config_.loadEnvProfile("default", "FilterCutoff"));
|
||||
ui_->envelopeFilterResonance->init(EnvelopeId::FilterResonance, config_.loadEnvProfile("default", "FilterResonance"));
|
||||
|
||||
// comboBoxes
|
||||
ui_->comboOsc1WaveSelector1->setCurrentIndex(static_cast<int>(PARAM_DEFS[static_cast<size_t>(ParamId::Osc1WaveSelector1)].def));
|
||||
ui_->comboOsc1WaveSelector2->setCurrentIndex(static_cast<int>(PARAM_DEFS[static_cast<size_t>(ParamId::Osc1WaveSelector2)].def));
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <QMainWindow>
|
||||
#include <QKeyEvent>
|
||||
|
||||
#include "../ConfigInterface.h"
|
||||
#include "../synth/AudioEngine.h"
|
||||
#include "../MidiController.h"
|
||||
|
||||
@@ -27,8 +28,11 @@ private slots:
|
||||
void onResetClicked();
|
||||
|
||||
private:
|
||||
|
||||
Ui::MainWindow *ui_;
|
||||
|
||||
ParameterStore params_;
|
||||
ConfigInterface config_;
|
||||
AudioEngine* audio_ = nullptr;
|
||||
KeyboardController keyboard_;
|
||||
MidiController midi_;
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
#include <iostream>
|
||||
|
||||
// TODO: package the rogue sliders into the envelopeGenerators with a "base" column (its what the "peak" slider in the esp synth was supposed to be)
|
||||
|
||||
EnvelopeGenerator::EnvelopeGenerator(QWidget* parent) : QWidget(parent), ui_(new Ui::EnvelopeGenerator) {
|
||||
|
||||
ui_->setupUi(this);
|
||||
@@ -94,3 +92,21 @@ void EnvelopeGenerator::init(EnvelopeId id) {
|
||||
setRelease(PARAM_DEFS[static_cast<size_t>(params.r)].def);
|
||||
|
||||
}
|
||||
|
||||
void EnvelopeGenerator::init(EnvelopeId id, std::array<ParamDefault, 5> profile) {
|
||||
|
||||
EnvelopeParam params = ENV_PARAMS[static_cast<size_t>(id)];
|
||||
|
||||
ui_->sliderDepth->setRange(profile[0].min, profile[0].max);
|
||||
ui_->sliderAttack->setRange(profile[1].min, profile[1].max);
|
||||
ui_->sliderDecay->setRange(profile[2].min, profile[2].max);
|
||||
ui_->sliderSustain->setRange(profile[3].min, profile[3].max);
|
||||
ui_->sliderRelease->setRange(profile[4].min, profile[4].max);
|
||||
|
||||
setDepth(profile[0].def);
|
||||
setAttack(profile[1].def);
|
||||
setDecay(profile[2].def);
|
||||
setSustain(profile[3].def);
|
||||
setRelease(profile[4].def);
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ public:
|
||||
|
||||
// connects signals, sets parameters to the defaults defined in paramStore
|
||||
void init(EnvelopeId id);
|
||||
void init(EnvelopeId id, std::array<ParamDefault, 5> profile);
|
||||
|
||||
// setters
|
||||
void setDepth(float v);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "Scope.h"
|
||||
#include "ui_Scope.h"
|
||||
|
||||
// TODO: fix include directories because what is this
|
||||
#include "../../../synth/ScopeBuffer.h"
|
||||
|
||||
#include <QPainter>
|
||||
@@ -23,7 +24,7 @@ void Scope::setScopeBuffer(ScopeBuffer* buffer) {
|
||||
}
|
||||
|
||||
void Scope::paintEvent(QPaintEvent*) {
|
||||
if (!buffer_) return;
|
||||
if(!buffer_) return;
|
||||
|
||||
int32_t wavelength = buffer_->wavelength();
|
||||
int32_t trigger = buffer_->trigger();
|
||||
|
||||
@@ -32,4 +32,5 @@ private:
|
||||
ScopeBuffer* buffer_ = nullptr;
|
||||
std::vector<float> samples_;
|
||||
QTimer timer_;
|
||||
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user