11 Commits

Author SHA1 Message Date
a6e15c4853 init 2026-04-18 17:05:49 -05:00
3a07cb6319 organize voice profile + tweaks 2026-02-08 16:08:29 -06:00
0f17ab09c4 consolidate linux install script 2026-02-08 14:30:03 -06:00
e2f5cb13e0 refactor parameterstore 2026-02-08 14:11:16 -06:00
a0ac5848e3 add some default wavetables 2026-02-07 19:52:59 -06:00
18ff8dfc9f configurable wavetable files 2026-02-07 18:08:38 -06:00
8b4a09fc39 checkpoint 2026-02-07 16:54:18 -06:00
3f335343e3 wavetable loading checkpoint 2026-02-07 15:13:47 -06:00
537f571f6a preliminary wavetable generation 2026-02-07 12:56:21 -06:00
ee353eadfd remove param_defaults 2026-02-03 19:30:33 -06:00
b6a1fb9b3a fix windows build 2026-02-01 20:18:57 -06:00
27 changed files with 343 additions and 215 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,3 @@
build/* build/*
.vscode/* .vscode/*
scripts/__pycache__/*

View File

@@ -13,7 +13,7 @@ if (WIN32) # windows 11 x86_64
set_target_properties(RtAudio::rtaudio PROPERTIES set_target_properties(RtAudio::rtaudio PROPERTIES
IMPORTED_LOCATION "${RtAudio_ROOT}/bin/rtaudio.dll" IMPORTED_LOCATION "${RtAudio_ROOT}/bin/rtaudio.dll"
IMPORTED_IMPLIB "${RtAudio_ROOT}/lib/rtaudio.lib" IMPORTED_IMPLIB "${RtAudio_ROOT}/lib/rtaudio.lib"
INTERFACE_INCLUDE_DIRECTORIES "${RtAudio_ROOT}/include" INTERFACE_INCLUDE_DIRECTORIES "${RtAudio_ROOT}/include/rtaudio"
) )
add_library(RtMidi::rtmidi SHARED IMPORTED) add_library(RtMidi::rtmidi SHARED IMPORTED)
@@ -108,3 +108,13 @@ target_link_libraries(metabolus
yaml-cpp yaml-cpp
Qt6::Widgets Qt6::Widgets
) )
# needed some different calls here
if (WIN32)
else()
target_link_libraries(metabolus
PRIVATE
atomic
)
endif()

View File

@@ -21,7 +21,7 @@ This synthesizer isn't very good, but it's neat :3
oscillators increase the sound complexity considerably oscillators increase the sound complexity considerably
- [x] Create a UI scope to visualize the synthesized composite waveform - [x] Create a UI scope to visualize the synthesized composite waveform
- [x] Create wavetables for more complex tone generation. Needs to be selectable from ui - [x] Create wavetables for more complex tone generation. Needs to be selectable from ui
- [ ] Wavetable file loading - [x] Wavetable file loading
- [x] Create digital filters, prob biquad. Controllable from ui obv (cutoff + resonance) - [x] Create digital filters, prob biquad. Controllable from ui obv (cutoff + resonance)
- [x] Add polyphony somewhere. Probably involves a voice class. If processing power - [x] Add polyphony somewhere. Probably involves a voice class. If processing power
allows it, tie a voice to each midi note allows it, tie a voice to each midi note
@@ -60,7 +60,7 @@ Build. The script will build and install dependencies automatically
On Windows (MSVC): On Windows (MSVC):
```PowerShell ```PowerShell
.\scripts\build.ps1 # builds in build/Debug/ .\scripts\build.ps1
``` ```
On Linux (GCC): On Linux (GCC):
@@ -68,7 +68,7 @@ On Linux (GCC):
./scripts/build.sh ./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 Note: executing the app from the root directory will make the app use the default root level configs. Run the app from the build directory to customize your configurations with the build/config configurations.
Configure the CMake/build script if you have issues Configure the CMake/build script if you have issues
@@ -80,13 +80,10 @@ To clean:
Note: dependencies are built into build/lib, so don't delete unless you need to rebuild the libraries 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. Use the install_dependencies script to manually install dependencies.
Build troubleshooting: ## Configurations
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. \ 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. \ 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
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 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), are located within the scripts directory.

View File

@@ -14,3 +14,15 @@ stereoMode: 2
# number of samples per audio buffer # number of samples per audio buffer
bufferSize: 512 bufferSize: 512
# synth settings
#define SYNTH_PITCH_STANDARD 440.0f // frequency of home pitch
#define SYNTH_MIDI_HOME 69 // midi note index of home pitch
#define SYNTH_NOTES_PER_OCTAVE 12
# midi home note (usually A4=69) = pitchStandard hz (usually 440 or 432 hz)
pitchStandard: 440.0
midi_home: 69
notesPerOctave: 12 # changes to this give some interesting non-western harmonics

View File

@@ -4,51 +4,46 @@
# sequences in the form [x, x, x] denote [setValue, sliderMinimum, sliderMaximum] # sequences in the form [x, x, x] denote [setValue, sliderMinimum, sliderMaximum]
version: 0x0003 version: 0x04
# deprecated, useless
Osc1Freq: [100, 20, 600]
# wavetable selections # wavetable selections
OscWaveSelector1: 2 OscWaveSelector1: 2
OscWaveSelector2: 3 OscWaveSelector2: 3
# Frequency parameters # Frequency parameters
MasterOctaveOffset: [0, -5, 5] MasterPitchOffset:
MasterSemitoneOffset: [0, -12, 12] Octave: [0, -5, 5]
MasterPitchOffset: [0, -100, 100] Semitone: [0, -12, 12]
Osc1OctaveOffset: [0, -5, 5] Pitch: [0, -100, 100]
Osc1SemitoneOffset: [0, -12, 12] Osc1PitchOffset:
Osc1PitchOffset: [1.34, -100, 100] Octave: [0, -5, 5]
Osc2OctaveOffset: [1, -5, 5] Semitone: [0, -12, 12]
Osc2SemitoneOffset: [0, -12, 12] Pitch: [1.34, -100, 100]
Osc2PitchOffset: [12.86, -100, 100] Osc2PitchOffset:
Osc3OctaveOffset: [1, -5, 5] Octave: [1, -5, 5]
Osc3SemitoneOffset: [7, -12, 12] Semitone: [0, -12, 12]
Osc3PitchOffset: [-8.79, -100, 100] Pitch: [12.86, -100, 100]
Osc3PitchOffset:
# gonna have something like this: Octave: [1, -5, 5]
#MasterPitchOffset: Semitone: [7, -12, 12]
# - [0, -5, 5] # Octave Pitch: [-8.79, -100, 100]
# - [0, -12, -12] # Semitone
# - [0, -100, 100] # Pitch
# Envelope generator parameters # Envelope generator parameters
Osc1Volume: Osc1Volume:
- [1, 0, 10] # Depth Depth: [1, 0, 10]
- [0.05, 0, 2] # Attack Attack: [0.05, 0, 2]
- [0.2, 0, 2] # Decay Decay: [0.2, 0, 2]
- [0.7, 0, 1] # Sustain Sustain: [0.7, 0, 1]
- [0.2, 0, 2] # Release Release: [0.2, 0, 2]
FilterCutoff: FilterCutoff:
- [4, 0, 8] # Depth Depth: [4, 0, 8]
- [0.05, 0, 2] # Attack Attack: [0.05, 0, 2]
- [0.2, 0, 2] # Decay Decay: [0.2, 0, 2]
- [0.2, 0, 1] # Sustain Sustain: [0.2, 0, 1]
- [0.25, 0, 2] # Release Release: [0.25, 0, 2]
FilterResonance: FilterResonance:
- [3, 0, 8] # Depth Depth: [3, 0, 8]
- [0.05, 0, 2] # Attack Attack: [0.05, 0, 2]
- [0.2, 0, 2] # Decay Decay: [0.2, 0, 2]
- [0.5, 0, 1] # Sustain Sustain: [0.5, 0, 1]
- [0.3, 0, 2] # Release Release: [0.3, 0, 2]

BIN
config/wavetables/saw.wt Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
config/wavetables/sine.wt Normal file

Binary file not shown.

BIN
config/wavetables/square.wt Normal file

Binary file not shown.

Binary file not shown.

View File

@@ -26,7 +26,6 @@ if (-not (Test-Path -Path $BUILD_DIR)) {
} }
# detect dependencies # detect dependencies
$libraries = @("rtaudio", "rtmidi", "yaml-cpp") $libraries = @("rtaudio", "rtmidi", "yaml-cpp")
$dependencies_found = 0 $dependencies_found = 0
foreach ($lib in $libraries) { foreach ($lib in $libraries) {
@@ -38,6 +37,7 @@ foreach ($lib in $libraries) {
} }
} }
# run the install script if dependencies not found
if (-not ($dependencies_found -eq $libraries.Count)) { if (-not ($dependencies_found -eq $libraries.Count)) {
& "scripts\install_dependencies.ps1" & "scripts\install_dependencies.ps1"
} else { } else {
@@ -58,6 +58,8 @@ Write-Host "Building metabolus..."
cmake --build $BUILD_DIR --config $CONFIG cmake --build $BUILD_DIR --config $CONFIG
# TODO: install # TODO: install
cd $BUILD_DIR
cmake --install . --prefix ".\"
# link dlls # link dlls
Write-Host "Deploying metabolus..." Write-Host "Deploying metabolus..."

View File

@@ -8,6 +8,26 @@ RTAUDIO_ROOT=${LIB_ROOT}/rtaudio
RTMIDI_ROOT=${LIB_ROOT}/rtmidi RTMIDI_ROOT=${LIB_ROOT}/rtmidi
YAMLCPP_ROOT=${LIB_ROOT}/yaml-cpp YAMLCPP_ROOT=${LIB_ROOT}/yaml-cpp
# detect dependencies
libraries=("rtaudio" "rtmidi" "yaml-cpp")
dependencies_found=0
for lib in "${libraries[@]}"; do
if [[ -e "./build/lib/$lib" ]]; then
echo "found $lib"
((dependencies_found++))
else
echo "did not find $lib"
fi
done
# run the install script if dependencies not found
if [[ "$dependencies_found" -ne "${#libraries[@]}" ]]; then
./scripts/install_dependencies.sh
else
echo "All dependencies detected, skipping dependency install step..."
fi
cmake -S . -B ${BUILD_DIR} -G Ninja \ cmake -S . -B ${BUILD_DIR} -G Ninja \
-DRtAudio_DIR="${RTAUDIO_ROOT}/share/rtaudio" \ -DRtAudio_DIR="${RTAUDIO_ROOT}/share/rtaudio" \
-DRtMidi_DIR="${RTMIDI_ROOT}/share/rtmidi" \ -DRtMidi_DIR="${RTMIDI_ROOT}/share/rtmidi" \

View File

@@ -0,0 +1,52 @@
import math
from generate_wavetable import generateWavetable
WAVETABLE_FILE_NAME = "sigmoid"
# process expects a waveform from x=[0, 2pi) centered around f(x)=0
# normalization is handled by the wavetableGenerator
def sine(phase):
return math.sin(phase)
def square(phase):
sample = 1.0
if(phase <= math.pi):
sample = -1
return sample
def saw(phase):
return (phase / math.pi) - 1.0
def triangle(phase):
sample = 0.0
if(phase <= math.pi/2.0):
sample = phase * 2.0/math.pi
elif(phase <= 3.0*math.pi/2.0):
sample = phase * (-2.0/math.pi) + 2.0
else:
sample = phase * 2.0/math.pi - 4.0
return sample
def sharkFin(phase):
k = 10.0
if(phase <= math.pi):
sample = 2 * (phase/math.pi) ** k - 1.0
else:
sample = -2 * (phase/math.pi - 1.0) ** k + 1.0
return sample
def sigmoid(phase):
k = 4
a = -k * (phase - math.pi)
sample = 2 / (1 + math.exp(a)) - 1
return sample
# pass in the name of your wavtable file and the process function
def main():
generateWavetable(WAVETABLE_FILE_NAME, sigmoid)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,68 @@
# this is a script for generating a wavetable file
# a wavetable file consists of a one-dimensional array of samples representing one period of a waveform
# metadata includes:
# - file version (for program compatibility)
# - binary format (float, double, int32, etc.) (RIGHT NOW I ONLY USE FLOAT)
# - domain (normal is a phase from x=0 to x=2pi)
# - range (depending on datatypes, e.g. float=[-1,1], int32=[-2^15, 2^15-1])
# - waveform RMS (for loudness normalization)
# - sample count
# the synth program uses the filename, not any metadata
# this script uses the function defined in example_wavetable.py to calculate samples
# if you want a custom wavetable, copy/edit/modify the example function (desmos is great for brainstorming)
# import this script and call generateWavetable(processFunction) to generate a custom wavetable
from array import array
import math
wavetableLength = 2048
def createFile(name):
filename = name + ".wt"
print("creating file " + filename)
file = open(filename, "wb")
return file
def writeMetadata(file):
print(">> im writing metadata")
def writeData(file, processFunction):
print(">> im generating the wavetable")
# init variables
data_list = [None] * wavetableLength
phaseInc = 2*math.pi / wavetableLength
x = 0
accumulator = 0
# generate each discrete sample
for i in range(wavetableLength):
sample = processFunction(x)
accumulator += sample * sample
x += phaseInc
data_list[i] = sample
# normalize by rms
rms = math.sqrt(accumulator/wavetableLength)
print(">> wavetable RMS: ", rms)
for i in range(wavetableLength):
data_list[i] /= rms
# write to file
binary_data = array("f", data_list)
file.write(binary_data)
def closeFile(file):
print(">> finishing up")
file.close()
def generateWavetable(name, processFunction):
print("Hello main")
file = createFile(name)
writeMetadata(file)
writeData(file, processFunction)
closeFile(file)
print("done")

View File

@@ -70,74 +70,76 @@ YAML::Node ConfigInterface::loadProfile(std::string filename) {
} }
// extract values from the config file // extract values from the config file
std::array<ParamDefault, 5> osc1VolumeProfile = loadEnvProfile(&config, "Osc1Volume"); std::array<Param, 5> osc1VolumeProfile = loadEnvProfile(&config, "Osc1Volume");
std::array<ParamDefault, 5> fCutoffProfile = loadEnvProfile(&config, "FilterCutoff"); std::array<Param, 5> fCutoffProfile = loadEnvProfile(&config, "FilterCutoff");
std::array<ParamDefault, 5> fResonanceProfile = loadEnvProfile(&config, "FilterResonance"); std::array<Param, 5> fResonanceProfile = loadEnvProfile(&config, "FilterResonance");
std::array<ParamDefault, 3> masterPitchOffsets = {{ YAML::Node masterNode = config["MasterPitchOffset"];
{ config["MasterOctaveOffset"][0].as<float>(), config["MasterOctaveOffset"][1].as<float>(), config["MasterOctaveOffset"][2].as<float>() }, YAML::Node osc1Node = config["Osc1PitchOffset"];
{ config["MasterSemitoneOffset"][0].as<float>(), config["MasterSemitoneOffset"][1].as<float>(), config["MasterSemitoneOffset"][2].as<float>() }, YAML::Node osc2Node = config["Osc2PitchOffset"];
{ config["MasterPitchOffset"][0].as<float>(), config["MasterPitchOffset"][1].as<float>(), config["MasterPitchOffset"][2].as<float>() }, YAML::Node osc3Node = config["Osc3PitchOffset"];
std::array<Param, 3> masterPitchOffsets = {{
{ masterNode["Octave"][0].as<float>(), masterNode["Octave"][1].as<float>(), masterNode["Octave"][2].as<float>() },
{ masterNode["Semitone"][0].as<float>(), masterNode["Semitone"][1].as<float>(), masterNode["Semitone"][2].as<float>() },
{ masterNode["Pitch"][0].as<float>(), masterNode["Pitch"][1].as<float>(), masterNode["Pitch"][2].as<float>() },
}}; }};
std::array<ParamDefault, 3> osc1PitchOffsets = {{ std::array<Param, 3> osc1PitchOffsets = {{
{ config["Osc1OctaveOffset"][0].as<float>(), config["Osc1OctaveOffset"][1].as<float>(), config["Osc1OctaveOffset"][2].as<float>() }, { osc1Node["Octave"][0].as<float>(), osc1Node["Octave"][1].as<float>(), osc1Node["Octave"][2].as<float>() },
{ config["Osc1SemitoneOffset"][0].as<float>(), config["Osc1SemitoneOffset"][1].as<float>(), config["Osc1SemitoneOffset"][2].as<float>() }, { osc1Node["Semitone"][0].as<float>(), osc1Node["Semitone"][1].as<float>(), osc1Node["Semitone"][2].as<float>() },
{ config["Osc1PitchOffset"][0].as<float>(), config["Osc1PitchOffset"][1].as<float>(), config["Osc1PitchOffset"][2].as<float>() }, { osc1Node["Pitch"][0].as<float>(),osc1Node["Pitch"][1].as<float>(), osc1Node["Pitch"][2].as<float>() },
}}; }};
std::array<ParamDefault, 3> osc2PitchOffsets = {{ std::array<Param, 3> osc2PitchOffsets = {{
{ config["Osc2OctaveOffset"][0].as<float>(), config["Osc2OctaveOffset"][1].as<float>(), config["Osc2OctaveOffset"][2].as<float>() }, { osc2Node["Octave"][0].as<float>(), osc2Node["Octave"][1].as<float>(), osc2Node["Octave"][2].as<float>() },
{ config["Osc2SemitoneOffset"][0].as<float>(), config["Osc2SemitoneOffset"][1].as<float>(), config["Osc2SemitoneOffset"][2].as<float>() }, { osc2Node["Semitone"][0].as<float>(), osc2Node["Semitone"][1].as<float>(), osc2Node["Semitone"][2].as<float>() },
{ config["Osc2PitchOffset"][0].as<float>(), config["Osc2PitchOffset"][1].as<float>(), config["Osc2PitchOffset"][2].as<float>() }, { osc2Node["Pitch"][0].as<float>(), osc2Node["Pitch"][1].as<float>(), osc2Node["Pitch"][2].as<float>() },
}}; }};
std::array<ParamDefault, 3> osc3PitchOffsets = {{ std::array<Param, 3> osc3PitchOffsets = {{
{ config["Osc3OctaveOffset"][0].as<float>(), config["Osc3OctaveOffset"][1].as<float>(), config["Osc3OctaveOffset"][2].as<float>() }, { osc3Node["Octave"][0].as<float>(), osc3Node["Octave"][1].as<float>(), osc3Node["Octave"][2].as<float>() },
{ config["Osc3SemitoneOffset"][0].as<float>(), config["Osc3SemitoneOffset"][1].as<float>(), config["Osc3SemitoneOffset"][2].as<float>() }, { osc3Node["Semitone"][0].as<float>(), osc3Node["Semitone"][1].as<float>(), osc3Node["Semitone"][2].as<float>() },
{ config["Osc3PitchOffset"][0].as<float>(), config["Osc3PitchOffset"][1].as<float>(), config["Osc3PitchOffset"][2].as<float>() }, { osc3Node["Pitch"][0].as<float>(), osc3Node["Pitch"][1].as<float>(), osc3Node["Pitch"][2].as<float>() },
}}; }};
// TODO: remove this once all the parameters are set properly
params_->resetToDefaults();
// set the values in the paramstore // 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::Osc1Volume, osc1VolumeProfile[0].val, osc1VolumeProfile[1].val, osc1VolumeProfile[2].val, osc1VolumeProfile[3].val, osc1VolumeProfile[4].val);
params_->set(EnvelopeId::FilterCutoff, fCutoffProfile[0].def, fCutoffProfile[1].def, fCutoffProfile[2].def, fCutoffProfile[3].def, fCutoffProfile[4].def); params_->set(EnvelopeId::FilterCutoff, fCutoffProfile[0].val, fCutoffProfile[1].val, fCutoffProfile[2].val, fCutoffProfile[3].val, fCutoffProfile[4].val);
params_->set(EnvelopeId::FilterResonance, fResonanceProfile[0].def, fResonanceProfile[1].def, fResonanceProfile[2].def, fResonanceProfile[3].def, fResonanceProfile[4].def); params_->set(EnvelopeId::FilterResonance, fResonanceProfile[0].val, fResonanceProfile[1].val, fResonanceProfile[2].val, fResonanceProfile[3].val, fResonanceProfile[4].val);
// TODO: why do I bother passing in 5 values independently when I can just do an array like in loadEnvProfile ? // TODO: why do I bother passing in 5 values independently when I can just do an array like in loadEnvProfile ?
params_->set(ParamId::MasterOctaveOffset, masterPitchOffsets[0].def); params_->set(ParamId::MasterOctaveOffset, masterPitchOffsets[0].val);
params_->set(ParamId::MasterSemitoneOffset, masterPitchOffsets[1].def); params_->set(ParamId::MasterSemitoneOffset, masterPitchOffsets[1].val);
params_->set(ParamId::MasterPitchOffset, masterPitchOffsets[2].def); params_->set(ParamId::MasterPitchOffset, masterPitchOffsets[2].val);
params_->set(ParamId::Osc1OctaveOffset, osc1PitchOffsets[0].def); params_->set(ParamId::Osc1OctaveOffset, osc1PitchOffsets[0].val);
params_->set(ParamId::Osc1SemitoneOffset, osc1PitchOffsets[1].def); params_->set(ParamId::Osc1SemitoneOffset, osc1PitchOffsets[1].val);
params_->set(ParamId::Osc1PitchOffset, osc1PitchOffsets[2].def); params_->set(ParamId::Osc1PitchOffset, osc1PitchOffsets[2].val);
params_->set(ParamId::Osc2OctaveOffset, osc2PitchOffsets[0].def); params_->set(ParamId::Osc2OctaveOffset, osc2PitchOffsets[0].val);
params_->set(ParamId::Osc2SemitoneOffset, osc2PitchOffsets[1].def); params_->set(ParamId::Osc2SemitoneOffset, osc2PitchOffsets[1].val);
params_->set(ParamId::Osc2PitchOffset, osc2PitchOffsets[2].def); params_->set(ParamId::Osc2PitchOffset, osc2PitchOffsets[2].val);
params_->set(ParamId::Osc3OctaveOffset, osc3PitchOffsets[0].def); params_->set(ParamId::Osc3OctaveOffset, osc3PitchOffsets[0].val);
params_->set(ParamId::Osc3SemitoneOffset, osc3PitchOffsets[1].def); params_->set(ParamId::Osc3SemitoneOffset, osc3PitchOffsets[1].val);
params_->set(ParamId::Osc3PitchOffset, osc3PitchOffsets[2].def); params_->set(ParamId::Osc3PitchOffset, osc3PitchOffsets[2].val);
// TODO: params_->set(ParamId::Osc1WaveSelector1, static_cast<float>(config["OscWaveSelector1"].as<int>()));
// load wavetable settings params_->set(ParamId::Osc1WaveSelector2, static_cast<float>(config["OscWaveSelector2"].as<int>()));
// load oscillator pitch settings
return config; return config;
} }
std::array<ParamDefault, 5> ConfigInterface::loadEnvProfile(YAML::Node* node, std::string profile) { std::array<Param, 5> ConfigInterface::loadEnvProfile(YAML::Node* node, std::string profile) {
YAML::Node envelopeNode = (*node)[profile]; YAML::Node envelopeNode = (*node)[profile];
std::array<ParamDefault, 5> paramProfile; std::array<Param, 5> paramProfile;
for(int i = 0; i < paramProfile.size(); i++) { paramProfile[0] = { envelopeNode["Depth"][0].as<float>(), envelopeNode["Depth"][1].as<float>(), envelopeNode["Depth"][2].as<float>() };
paramProfile[i] = { envelopeNode[i][0].as<float>(), envelopeNode[i][1].as<float>(), envelopeNode[i][2].as<float>() }; paramProfile[1] = { envelopeNode["Attack"][0].as<float>(), envelopeNode["Attack"][1].as<float>(), envelopeNode["Attack"][2].as<float>() };
} paramProfile[2] = { envelopeNode["Decay"][0].as<float>(), envelopeNode["Decay"][1].as<float>(), envelopeNode["Decay"][2].as<float>() };
paramProfile[3] = { envelopeNode["Sustain"][0].as<float>(), envelopeNode["Sustain"][1].as<float>(), envelopeNode["Sustain"][2].as<float>() };
paramProfile[4] = { envelopeNode["Release"][0].as<float>(), envelopeNode["Release"][1].as<float>(), envelopeNode["Release"][2].as<float>() };
return paramProfile; return paramProfile;
} }
std::array<ParamDefault, 5> ConfigInterface::loadEnvProfile(std::string filename, std::string profile) { std::array<Param, 5> ConfigInterface::loadEnvProfile(std::string filename, std::string profile) {
std::string filepath = "config/profiles/" + filename + ".yaml"; std::string filepath = "config/profiles/" + filename + ".yaml";
filepath = std::filesystem::absolute(filepath).string(); filepath = std::filesystem::absolute(filepath).string();

View File

@@ -8,7 +8,7 @@
#include "ParameterStore.h" #include "ParameterStore.h"
#define CONFIG_VERSION 0x0002 #define CONFIG_VERSION 0x04
enum class ConfigFile { enum class ConfigFile {
Audio = 0 Audio = 0
@@ -33,8 +33,8 @@ public:
int getValue(ConfigFile file, std::string key, int defaultVal); int getValue(ConfigFile file, std::string key, int defaultVal);
YAML::Node loadProfile(std::string filename); YAML::Node loadProfile(std::string filename);
std::array<ParamDefault, 5> loadEnvProfile(YAML::Node* node, std::string profile); std::array<Param, 5> loadEnvProfile(YAML::Node* node, std::string profile);
std::array<ParamDefault, 5> loadEnvProfile(std::string filename, std::string profile); std::array<Param, 5> loadEnvProfile(std::string filename, std::string profile);
private: private:

View File

@@ -6,12 +6,14 @@
#include <filesystem> #include <filesystem>
ParameterStore::ParameterStore() { ParameterStore::ParameterStore() {
//resetToDefaults();
} }
// set parameter value // set parameter value
void ParameterStore::set(ParamId id, float value) { void ParameterStore::set(ParamId id, float value) {
values_[static_cast<size_t>(id)].store(value, std::memory_order_relaxed); Param oldParam = values_[static_cast<size_t>(id)].load(std::memory_order_relaxed);
Param updatedParam = { value, oldParam.min, oldParam.max };
values_[static_cast<size_t>(id)].store(updatedParam, std::memory_order_relaxed);
} }
// set a whole envelope of parameters // set a whole envelope of parameters
@@ -27,13 +29,5 @@ void ParameterStore::set(EnvelopeId id, float depth, float a, float d, float s,
// get a single parameter // get a single parameter
float ParameterStore::get(ParamId id) const { float ParameterStore::get(ParamId id) const {
return values_[static_cast<size_t>(id)].load(std::memory_order_relaxed); return values_[static_cast<size_t>(id)].load(std::memory_order_relaxed).val;
}
void ParameterStore::resetToDefaults() {
for(size_t i = 0; i < PARAM_COUNT; i++) {
values_[i].store(PARAM_DEFS[i].def, std::memory_order_relaxed);
}
} }

View File

@@ -66,45 +66,12 @@ constexpr std::array<EnvelopeParam, static_cast<size_t>(EnvelopeId::Count)> ENV_
{ ParamId::FilterResonanceDepth, ParamId::FilterResonanceEnvA, ParamId::FilterResonanceEnvD, ParamId::FilterResonanceEnvS, ParamId::FilterResonanceEnvR }, // FilterResonance { ParamId::FilterResonanceDepth, ParamId::FilterResonanceEnvA, ParamId::FilterResonanceEnvD, ParamId::FilterResonanceEnvS, ParamId::FilterResonanceEnvR }, // FilterResonance
}}; }};
struct ParamDefault { struct Param {
float def; float val;
float min; float min;
float max; float max;
}; };
constexpr std::array<ParamDefault, static_cast<size_t>(ParamId::Count)> PARAM_DEFS {{
{ 100.0f, 20.0f, 600.0f}, // Osc1Freq
{ 2.0f, 0.0f, 0.0f}, // OscWaveSelector1
{ 1.0f, 0.0f, 0.0f}, // OscWaveSelector2
{ 0.0f, -5.0f, 5.0f}, // MasterOctaveOffset
{ 0.0f, -12.0f, 12.0f}, // MasterSemitoneOffset
{ 0.0f, -100.0f, 100.0f}, // MasterPitchOffset
{ 0.0f, -5.0f, 5.0f}, // Osc1OctaveOffset
{ 0.0f, -12.0f, 12.0f}, // Osc1SemitoneOffset
{ 0.0f, -100.0f, 100.0f}, // Osc1PitchOffset
{ 1.0f, -5.0f, 5.0f}, // Osc2OctaveOffset
{ 0.0f, -12.0f, 12.0f}, // Osc2SemitoneOffset
{ 0.0f, -100.0f, 100.0f}, // Osc2PitchOffset
{ 1.0f, -5.0f, 5.0f}, // Osc3OctaveOffset
{ 7.0f, -12.0f, 12.0f}, // Osc3SemitoneOffset
{ 1.96f, -100.0f, 100.0f}, // Osc3PitchOffset
{ 1.0f, 0.0f, 2.0f}, // Osc1VolumeDepth
{ 0.05f, 0.0f, 2.0f}, // Osc1VolumeEnvA
{ 0.2f, 0.0f, 2.0f}, // Osc1VolumeEnvD
{ 0.7f, 0.0f, 1.0f}, // Osc1VolumeEnvS
{ 0.2f, 0.0f, 2.0f}, // Osc1VolumeEnvR
{ 4.0f, 0.0f, 8.0f}, // FilterCutoffDepth
{ 0.05f, 0.0f, 2.0f}, // FilterCutoffEnvA
{ 0.20f, 0.0f, 2.0f}, // FilterCutoffEnvD
{ 0.2f, 0.0f, 1.0f}, // FilterCutoffEnvS
{ 0.25f, 0.0f, 2.0f}, // FilterCutoffEnvR
{ 3.0f, 0.0f, 8.0f}, // FilterResonanceDepth
{ 0.05f, 0.0f, 2.0f}, // FilterResonanceEnvA
{ 0.20f, 0.0f, 2.0f}, // FilterResonanceEnvD
{ 0.5f, 0.0f, 1.0f}, // FilterResonanceEnvS
{ 0.30f, 0.0f, 2.0f}, // FilterResonanceEnvR
}};
constexpr size_t PARAM_COUNT = static_cast<size_t>(ParamId::Count); constexpr size_t PARAM_COUNT = static_cast<size_t>(ParamId::Count);
class ParameterStore { class ParameterStore {
@@ -119,10 +86,9 @@ public:
void set(EnvelopeId id, float depth, float a, float d, float s, float r); void set(EnvelopeId id, float depth, float a, float d, float s, float r);
float get(ParamId id) const; float get(ParamId id) const;
int32_t getInt(ParamId id) const { return static_cast<int32_t>(get(id)); } int32_t getInt(ParamId id) const { return static_cast<int32_t>(get(id)); }
void resetToDefaults();
private: private:
std::array<std::atomic<float>, PARAM_COUNT> values_; std::array<std::atomic<Param>, PARAM_COUNT> values_;
}; };

View File

@@ -24,8 +24,7 @@ float Oscillator::process(uint8_t note, float detune, bool& scopeTrigger) {
float Oscillator::process(float frequency, bool& scopeTrigger) { float Oscillator::process(float frequency, bool& scopeTrigger) {
float pitchOffset = 0.5f; float phaseInc = 2.0f * M_PI * frequency / sampleRate_;
float phaseInc = pitchOffset * 2.0f * M_PI * frequency / sampleRate_;
float sampleOut = wavetable_->sample(activeWavetable_, phase_); float sampleOut = wavetable_->sample(activeWavetable_, phase_);

View File

@@ -81,6 +81,7 @@ float Voice::process(float* params, bool& scopeTrigger) {
float velocityGain = std::lerp(velocityCenter, velocity_, velocitySensitivity); float velocityGain = std::lerp(velocityCenter, velocity_, velocitySensitivity);
float gain = gainEnv * getParam(ParamId::Osc1VolumeDepth) * velocityGain; float gain = gainEnv * getParam(ParamId::Osc1VolumeDepth) * velocityGain;
gain *= (100.0f - static_cast<float>(note_)) * 0.005f + 0.75;
// sample generation // sample generation
uint8_t osc1Wave = (static_cast<uint8_t>(std::round(getParam(ParamId::Osc1WaveSelector1)))); uint8_t osc1Wave = (static_cast<uint8_t>(std::round(getParam(ParamId::Osc1WaveSelector1))));

View File

@@ -3,6 +3,7 @@
#include <cmath> #include <cmath>
#include <iostream> #include <iostream>
#include <fstream>
WavetableController::WavetableController() { WavetableController::WavetableController() {
// load from files // load from files
@@ -15,35 +16,25 @@ WavetableController::WavetableController() {
void WavetableController::init() { void WavetableController::init() {
wavetables_.resize(4); // resize for however many files we find // find wavetable files
std::vector<std::filesystem::path> wavetableFiles;
// don't really know how the files are gonna work for(std::filesystem::directory_entry entry : std::filesystem::directory_iterator(wavetablesRoot_)) {
// but I'd like two files- a yaml that contains metadata like name, length, range, datatype, etc. if(std::filesystem::is_regular_file(entry.status())) {
// and the main data be just a big array of that data type in a binary file wavetableFiles.push_back(entry.path());
// although having it all in a single bin makes the most sense with the metadata being in the header
float phase = 0.0f;
float phaseInc = 2.0f * M_PI / static_cast<float>(SYNTH_WAVETABLE_SIZE);
for(int i = 0; i < SYNTH_WAVETABLE_SIZE; i++) {
wavetables_[0][i] = std::sin(phase) / 0.707f; // sine
wavetables_[1][i] = (phase >= M_PI) ? 1.0f : -1.0f; // square
wavetables_[2][i] = ((phase / M_PI) - 1.0f) / 0.577f; // saw
// triangle
float tri = 0.0f;
if(phase <= M_PI/2.0f) {
tri = phase * 2.0f/M_PI;
} else if(phase <= 3.0f*M_PI/2.0f) {
tri = phase * -2.0f/M_PI + 2.0f;
} else {
tri = phase * 2.0f/M_PI - 4.0f;
} }
wavetables_[3][i] = tri / 0.577f;
phase += phaseInc;
} }
uint32_t wavetableCount = wavetableFiles.size();
wavetables_.resize(wavetableCount);
// load the wavetable files
for(int i = 0; i < wavetableCount; i++) {
std::cout << "loading wavetable file [" << i << "]: " << wavetableFiles[i] << std::endl;
std::ifstream inputFile(wavetableFiles[i], std::ios::in | std::ios::binary);
if(!inputFile) std::cout << "error opening file" << std::endl;
inputFile.read(reinterpret_cast<char*>(wavetables_[i].data()), SYNTH_WAVETABLE_SIZE * sizeof(float));
}
// wavetable data structure is best explained in scripts/generate_wavetable.py
} }

View File

@@ -4,6 +4,7 @@
#include <array> #include <array>
#include <vector> #include <vector>
#include <cstdint> #include <cstdint>
#include <filesystem>
#define SYNTH_WAVETABLE_SIZE 2048 #define SYNTH_WAVETABLE_SIZE 2048
#ifndef M_PI // I hate my stupid chungus life #ifndef M_PI // I hate my stupid chungus life
@@ -30,6 +31,8 @@ private:
std::vector<Wavetable> wavetables_; std::vector<Wavetable> wavetables_;
const std::filesystem::path wavetablesRoot_ = "./config/wavetables";
}; };

View File

@@ -48,6 +48,17 @@ MainWindow::MainWindow(QWidget *parent) :
this, [this](int index) { this, [this](int index) {
audio_->parameters()->set(ParamId::Osc1WaveSelector2, index); audio_->parameters()->set(ParamId::Osc1WaveSelector2, index);
}); });
ui_->comboOsc1WaveSelector1->clear();
ui_->comboOsc1WaveSelector2->clear();
for(std::filesystem::directory_entry entry : std::filesystem::directory_iterator("config/wavetables")) {
if(std::filesystem::is_regular_file(entry.status())) {
std::string fileName = entry.path().string().substr(18);
fileName.erase(fileName.length() - 3);
ui_->comboOsc1WaveSelector1->addItem(QString::fromStdString(fileName));
ui_->comboOsc1WaveSelector2->addItem(QString::fromStdString(fileName));
}
}
// rogue sliders, TODO: clean these up in a package // rogue sliders, TODO: clean these up in a package
connect(ui_->sliderMasterOctave, &SmartSlider::valueChanged, connect(ui_->sliderMasterOctave, &SmartSlider::valueChanged,
@@ -146,50 +157,54 @@ void MainWindow::onResetClicked() {
// TODO: clean these up, maybe put them in a package like the envelope generators (it'll help encapsulate the int-snapping business) // TODO: clean these up, maybe put them in a package like the envelope generators (it'll help encapsulate the int-snapping business)
// what I might do is make a variable-length slider-package object // what I might do is make a variable-length slider-package object
ui_->sliderMasterOctave->setResolution(configRoot["MasterOctaveOffset"][2].as<int>() - configRoot["MasterOctaveOffset"][1].as<int>()); YAML::Node masterNode = configRoot["MasterPitchOffset"];
ui_->sliderMasterOctave->setRange(configRoot["MasterOctaveOffset"][1].as<int>(), configRoot["MasterOctaveOffset"][2].as<int>()); YAML::Node osc1Node = configRoot["Osc1PitchOffset"];
ui_->sliderMasterOctave->setValue(configRoot["MasterOctaveOffset"][0].as<int>()); YAML::Node osc2Node = configRoot["Osc2PitchOffset"];
YAML::Node osc3Node = configRoot["Osc3PitchOffset"];
ui_->sliderMasterOctave->setResolution(masterNode["Octave"][2].as<int>() - masterNode["Octave"][1].as<int>());
ui_->sliderMasterOctave->setRange(masterNode["Octave"][1].as<int>(), masterNode["Octave"][2].as<int>());
ui_->sliderMasterOctave->setValue(masterNode["Octave"][0].as<int>());
ui_->sliderMasterSemitone->setResolution(configRoot["MasterSemitoneOffset"][2].as<int>() - configRoot["MasterSemitoneOffset"][1].as<int>()); ui_->sliderMasterSemitone->setResolution(masterNode["Semitone"][2].as<int>() - masterNode["Semitone"][1].as<int>());
ui_->sliderMasterSemitone->setRange(configRoot["MasterSemitoneOffset"][1].as<int>(), configRoot["MasterSemitoneOffset"][2].as<int>()); ui_->sliderMasterSemitone->setRange(masterNode["Semitone"][1].as<int>(), masterNode["Semitone"][2].as<int>());
ui_->sliderMasterSemitone->setValue(configRoot["MasterSemitoneOffset"][0].as<int>()); ui_->sliderMasterSemitone->setValue(masterNode["Semitone"][0].as<int>());
ui_->sliderMasterPitch->setRange(configRoot["MasterPitchOffset"][1].as<float>(), configRoot["MasterPitchOffset"][2].as<float>()); ui_->sliderMasterPitch->setRange(masterNode["Pitch"][1].as<float>(), masterNode["Pitch"][2].as<float>());
ui_->sliderMasterPitch->setValue(configRoot["MasterPitchOffset"][0].as<float>()); ui_->sliderMasterPitch->setValue(masterNode["Pitch"][0].as<float>());
ui_->sliderOsc1Octave->setResolution(configRoot["Osc1OctaveOffset"][2].as<int>() - configRoot["Osc1OctaveOffset"][1].as<int>()); ui_->sliderOsc1Octave->setResolution(osc1Node["Octave"][2].as<int>() - osc1Node["Octave"][1].as<int>());
ui_->sliderOsc1Octave->setRange(configRoot["Osc1OctaveOffset"][1].as<int>(), configRoot["Osc1OctaveOffset"][2].as<int>()); ui_->sliderOsc1Octave->setRange(osc1Node["Octave"][1].as<int>(), osc1Node["Octave"][2].as<int>());
ui_->sliderOsc1Octave->setValue(configRoot["Osc1OctaveOffset"][0].as<int>()); ui_->sliderOsc1Octave->setValue(osc1Node["Octave"][0].as<int>());
ui_->sliderOsc1Semitone->setResolution(configRoot["Osc1SemitoneOffset"][2].as<int>() - configRoot["Osc1SemitoneOffset"][1].as<int>()); ui_->sliderOsc1Semitone->setResolution(osc1Node["Semitone"][2].as<int>() - osc1Node["Semitone"][1].as<int>());
ui_->sliderOsc1Semitone->setRange(configRoot["Osc1SemitoneOffset"][1].as<int>(), configRoot["Osc1SemitoneOffset"][2].as<int>()); ui_->sliderOsc1Semitone->setRange(osc1Node["Semitone"][1].as<int>(), osc1Node["Semitone"][2].as<int>());
ui_->sliderOsc1Semitone->setValue(configRoot["Osc1SemitoneOffset"][0].as<int>()); ui_->sliderOsc1Semitone->setValue(osc1Node["Semitone"][0].as<int>());
ui_->sliderOsc1Pitch->setRange(configRoot["Osc1PitchOffset"][1].as<float>(), configRoot["Osc1PitchOffset"][2].as<float>()); ui_->sliderOsc1Pitch->setRange(osc1Node["Pitch"][1].as<float>(), osc1Node["Pitch"][2].as<float>());
ui_->sliderOsc1Pitch->setValue(configRoot["Osc1PitchOffset"][0].as<float>()); ui_->sliderOsc1Pitch->setValue(osc1Node["Pitch"][0].as<float>());
ui_->sliderOsc2Octave->setResolution(configRoot["Osc2OctaveOffset"][2].as<int>() - configRoot["Osc2OctaveOffset"][1].as<int>()); ui_->sliderOsc2Octave->setResolution(osc2Node["Octave"][2].as<int>() - osc2Node["Octave"][1].as<int>());
ui_->sliderOsc2Octave->setRange(configRoot["Osc2OctaveOffset"][1].as<int>(), configRoot["Osc2OctaveOffset"][2].as<int>()); ui_->sliderOsc2Octave->setRange(osc2Node["Octave"][1].as<int>(), osc2Node["Octave"][2].as<int>());
ui_->sliderOsc2Octave->setValue(configRoot["Osc2OctaveOffset"][0].as<int>()); ui_->sliderOsc2Octave->setValue(osc2Node["Octave"][0].as<int>());
ui_->sliderOsc2Semitone->setResolution(configRoot["Osc2SemitoneOffset"][2].as<int>() - configRoot["Osc2SemitoneOffset"][1].as<int>()); ui_->sliderOsc2Semitone->setResolution(osc2Node["Semitone"][2].as<int>() - osc2Node["Semitone"][1].as<int>());
ui_->sliderOsc2Semitone->setRange(configRoot["Osc2SemitoneOffset"][1].as<int>(), configRoot["Osc2SemitoneOffset"][2].as<int>()); ui_->sliderOsc2Semitone->setRange(osc2Node["Semitone"][1].as<int>(), osc2Node["Semitone"][2].as<int>());
ui_->sliderOsc2Semitone->setValue(configRoot["Osc2SemitoneOffset"][0].as<int>()); ui_->sliderOsc2Semitone->setValue(osc2Node["Semitone"][0].as<int>());
ui_->sliderOsc2Pitch->setRange(configRoot["Osc2PitchOffset"][1].as<float>(), configRoot["Osc2PitchOffset"][2].as<float>()); ui_->sliderOsc2Pitch->setRange(osc2Node["Pitch"][1].as<float>(), osc2Node["Pitch"][2].as<float>());
ui_->sliderOsc2Pitch->setValue(configRoot["Osc2PitchOffset"][0].as<float>()); ui_->sliderOsc2Pitch->setValue(osc2Node["Pitch"][0].as<float>());
ui_->sliderOsc3Octave->setResolution(configRoot["Osc3OctaveOffset"][2].as<int>() - configRoot["Osc3OctaveOffset"][1].as<int>()); ui_->sliderOsc3Octave->setResolution(osc3Node["Octave"][2].as<int>() - osc3Node["Octave"][1].as<int>());
ui_->sliderOsc3Octave->setRange(configRoot["Osc3OctaveOffset"][1].as<int>(), configRoot["Osc3OctaveOffset"][2].as<int>()); ui_->sliderOsc3Octave->setRange(osc3Node["Octave"][1].as<int>(), osc3Node["Octave"][2].as<int>());
ui_->sliderOsc3Octave->setValue(configRoot["Osc3OctaveOffset"][0].as<int>()); ui_->sliderOsc3Octave->setValue(osc3Node["Octave"][0].as<int>());
ui_->sliderOsc3Semitone->setResolution(configRoot["Osc3SemitoneOffset"][2].as<int>() - configRoot["Osc3SemitoneOffset"][1].as<int>()); ui_->sliderOsc3Semitone->setResolution(osc3Node["Semitone"][2].as<int>() - osc3Node["Semitone"][1].as<int>());
ui_->sliderOsc3Semitone->setRange(configRoot["Osc3SemitoneOffset"][1].as<int>(), configRoot["Osc3SemitoneOffset"][2].as<int>()); ui_->sliderOsc3Semitone->setRange(osc3Node["Semitone"][1].as<int>(), osc3Node["Semitone"][2].as<int>());
ui_->sliderOsc3Semitone->setValue(configRoot["Osc3SemitoneOffset"][0].as<int>()); ui_->sliderOsc3Semitone->setValue(osc3Node["Semitone"][0].as<int>());
ui_->sliderOsc3Pitch->setRange(osc3Node["Pitch"][1].as<float>(), osc3Node["Pitch"][2].as<float>());
ui_->sliderOsc3Pitch->setValue(osc3Node["Pitch"][0].as<float>());
ui_->sliderOsc3Pitch->setRange(configRoot["Osc3PitchOffset"][1].as<float>(), configRoot["Osc3PitchOffset"][2].as<float>());
ui_->sliderOsc3Pitch->setValue(configRoot["Osc3PitchOffset"][0].as<float>());
ui_->comboOsc1WaveSelector1->setCurrentIndex(configRoot["OscWaveSelector1"].as<int>()); ui_->comboOsc1WaveSelector1->setCurrentIndex(configRoot["OscWaveSelector1"].as<int>());
ui_->comboOsc1WaveSelector2->setCurrentIndex(configRoot["OscWaveSelector2"].as<int>()); ui_->comboOsc1WaveSelector2->setCurrentIndex(configRoot["OscWaveSelector2"].as<int>());

View File

@@ -75,7 +75,7 @@ void EnvelopeGenerator::emitEnvelope() {
); );
} }
void EnvelopeGenerator::init(EnvelopeId id, std::array<ParamDefault, 5> profile) { void EnvelopeGenerator::init(EnvelopeId id, std::array<Param, 5> profile) {
EnvelopeParam params = ENV_PARAMS[static_cast<size_t>(id)]; EnvelopeParam params = ENV_PARAMS[static_cast<size_t>(id)];
@@ -85,10 +85,10 @@ void EnvelopeGenerator::init(EnvelopeId id, std::array<ParamDefault, 5> profile)
ui_->sliderSustain->setRange(profile[3].min, profile[3].max); ui_->sliderSustain->setRange(profile[3].min, profile[3].max);
ui_->sliderRelease->setRange(profile[4].min, profile[4].max); ui_->sliderRelease->setRange(profile[4].min, profile[4].max);
setDepth(profile[0].def); setDepth(profile[0].val);
setAttack(profile[1].def); setAttack(profile[1].val);
setDecay(profile[2].def); setDecay(profile[2].val);
setSustain(profile[3].def); setSustain(profile[3].val);
setRelease(profile[4].def); setRelease(profile[4].val);
} }

View File

@@ -17,7 +17,7 @@ public:
~EnvelopeGenerator(); ~EnvelopeGenerator();
// connects signals, sets parameters to a provided profile // connects signals, sets parameters to a provided profile
void init(EnvelopeId id, std::array<ParamDefault, 5> profile); void init(EnvelopeId id, std::array<Param, 5> profile);
// setters // setters
void setDepth(float v); void setDepth(float v);

0
tests/.gitkeep Normal file
View File