Compare commits
10 Commits
289318140c
...
3a07cb6319
| Author | SHA1 | Date | |
|---|---|---|---|
| 3a07cb6319 | |||
| 0f17ab09c4 | |||
| e2f5cb13e0 | |||
| a0ac5848e3 | |||
| 18ff8dfc9f | |||
| 8b4a09fc39 | |||
| 3f335343e3 | |||
| 537f571f6a | |||
| ee353eadfd | |||
| b6a1fb9b3a |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
build/*
|
build/*
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
scripts/__pycache__/*
|
||||||
@@ -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()
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
BIN
config/wavetables/saw.wt
Normal file
Binary file not shown.
BIN
config/wavetables/sharkFin.wt
Normal file
BIN
config/wavetables/sharkFin.wt
Normal file
Binary file not shown.
BIN
config/wavetables/sigmoid.wt
Normal file
BIN
config/wavetables/sigmoid.wt
Normal file
Binary file not shown.
BIN
config/wavetables/sine.wt
Normal file
BIN
config/wavetables/sine.wt
Normal file
Binary file not shown.
BIN
config/wavetables/square.wt
Normal file
BIN
config/wavetables/square.wt
Normal file
Binary file not shown.
BIN
config/wavetables/triangle.wt
Normal file
BIN
config/wavetables/triangle.wt
Normal file
Binary file not shown.
@@ -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..."
|
||||||
|
|||||||
@@ -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" \
|
||||||
|
|||||||
52
scripts/example_wavetable.py
Normal file
52
scripts/example_wavetable.py
Normal 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()
|
||||||
68
scripts/generate_wavetable.py
Normal file
68
scripts/generate_wavetable.py
Normal 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")
|
||||||
|
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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_);
|
||||||
|
|
||||||
|
|||||||
@@ -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))));
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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,49 +157,53 @@ 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(configRoot["Osc3PitchOffset"][1].as<float>(), configRoot["Osc3PitchOffset"][2].as<float>());
|
ui_->sliderOsc3Pitch->setRange(osc3Node["Pitch"][1].as<float>(), osc3Node["Pitch"][2].as<float>());
|
||||||
ui_->sliderOsc3Pitch->setValue(configRoot["Osc3PitchOffset"][0].as<float>());
|
ui_->sliderOsc3Pitch->setValue(osc3Node["Pitch"][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>());
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
0
tests/.gitkeep
Normal file
Reference in New Issue
Block a user