diff --git a/CMakeLists.txt b/CMakeLists.txt index 08d9ace..f3c4578 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,5 +107,14 @@ target_link_libraries(metabolus RtMidi::rtmidi yaml-cpp Qt6::Widgets - atomic ) + +# needed some different calls here +if (WIN32) + +else() + target_link_libraries(metabolus + PRIVATE + atomic + ) +endif() diff --git a/config/audio.yaml b/config/audio.yaml index ed94762..ccd6fea 100644 --- a/config/audio.yaml +++ b/config/audio.yaml @@ -14,3 +14,15 @@ stereoMode: 2 # number of samples per audio buffer 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 diff --git a/config/profiles/default.yaml b/config/profiles/default.yaml index 79e3489..7cef78a 100644 --- a/config/profiles/default.yaml +++ b/config/profiles/default.yaml @@ -4,51 +4,46 @@ # sequences in the form [x, x, x] denote [setValue, sliderMinimum, sliderMaximum] -version: 0x0003 - -# deprecated, useless -Osc1Freq: [100, 20, 600] +version: 0x04 # wavetable selections OscWaveSelector1: 2 OscWaveSelector2: 3 # Frequency parameters -MasterOctaveOffset: [0, -5, 5] -MasterSemitoneOffset: [0, -12, 12] -MasterPitchOffset: [0, -100, 100] -Osc1OctaveOffset: [0, -5, 5] -Osc1SemitoneOffset: [0, -12, 12] -Osc1PitchOffset: [1.34, -100, 100] -Osc2OctaveOffset: [1, -5, 5] -Osc2SemitoneOffset: [0, -12, 12] -Osc2PitchOffset: [12.86, -100, 100] -Osc3OctaveOffset: [1, -5, 5] -Osc3SemitoneOffset: [7, -12, 12] -Osc3PitchOffset: [-8.79, -100, 100] - -# gonna have something like this: -#MasterPitchOffset: -# - [0, -5, 5] # Octave -# - [0, -12, -12] # Semitone -# - [0, -100, 100] # Pitch +MasterPitchOffset: + Octave: [0, -5, 5] + Semitone: [0, -12, 12] + Pitch: [0, -100, 100] +Osc1PitchOffset: + Octave: [0, -5, 5] + Semitone: [0, -12, 12] + Pitch: [1.34, -100, 100] +Osc2PitchOffset: + Octave: [1, -5, 5] + Semitone: [0, -12, 12] + Pitch: [12.86, -100, 100] +Osc3PitchOffset: + Octave: [1, -5, 5] + Semitone: [7, -12, 12] + Pitch: [-8.79, -100, 100] # Envelope generator parameters Osc1Volume: - - [1, 0, 10] # Depth - - [0.05, 0, 2] # Attack - - [0.2, 0, 2] # Decay - - [0.7, 0, 1] # Sustain - - [0.2, 0, 2] # Release + Depth: [1, 0, 10] + Attack: [0.05, 0, 2] + Decay: [0.2, 0, 2] + Sustain: [0.7, 0, 1] + Release: [0.2, 0, 2] 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 + Depth: [4, 0, 8] + Attack: [0.05, 0, 2] + Decay: [0.2, 0, 2] + Sustain: [0.2, 0, 1] + Release: [0.25, 0, 2] 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 + Depth: [3, 0, 8] + Attack: [0.05, 0, 2] + Decay: [0.2, 0, 2] + Sustain: [0.5, 0, 1] + Release: [0.3, 0, 2] diff --git a/config/wavetables/sigmoid.wt b/config/wavetables/sigmoid.wt new file mode 100644 index 0000000..adf473d Binary files /dev/null and b/config/wavetables/sigmoid.wt differ diff --git a/scripts/example_wavetable.py b/scripts/example_wavetable.py index c5bad20..7be1dc3 100644 --- a/scripts/example_wavetable.py +++ b/scripts/example_wavetable.py @@ -3,7 +3,7 @@ import math from generate_wavetable import generateWavetable -WAVETABLE_FILE_NAME = "sharkFin" +WAVETABLE_FILE_NAME = "sigmoid" # process expects a waveform from x=[0, 2pi) centered around f(x)=0 # normalization is handled by the wavetableGenerator @@ -38,12 +38,15 @@ def sharkFin(phase): sample = -2 * (phase/math.pi - 1.0) ** k + 1.0 return sample -def sphere(phase): - return 1 +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, process) + generateWavetable(WAVETABLE_FILE_NAME, sigmoid) if __name__ == "__main__": main() diff --git a/src/ConfigInterface.cpp b/src/ConfigInterface.cpp index 84e39e4..f215851 100644 --- a/src/ConfigInterface.cpp +++ b/src/ConfigInterface.cpp @@ -74,25 +74,29 @@ YAML::Node ConfigInterface::loadProfile(std::string filename) { std::array fCutoffProfile = loadEnvProfile(&config, "FilterCutoff"); std::array fResonanceProfile = loadEnvProfile(&config, "FilterResonance"); + YAML::Node masterNode = config["MasterPitchOffset"]; + YAML::Node osc1Node = config["Osc1PitchOffset"]; + YAML::Node osc2Node = config["Osc2PitchOffset"]; + YAML::Node osc3Node = config["Osc3PitchOffset"]; std::array masterPitchOffsets = {{ - { config["MasterOctaveOffset"][0].as(), config["MasterOctaveOffset"][1].as(), config["MasterOctaveOffset"][2].as() }, - { config["MasterSemitoneOffset"][0].as(), config["MasterSemitoneOffset"][1].as(), config["MasterSemitoneOffset"][2].as() }, - { config["MasterPitchOffset"][0].as(), config["MasterPitchOffset"][1].as(), config["MasterPitchOffset"][2].as() }, + { masterNode["Octave"][0].as(), masterNode["Octave"][1].as(), masterNode["Octave"][2].as() }, + { masterNode["Semitone"][0].as(), masterNode["Semitone"][1].as(), masterNode["Semitone"][2].as() }, + { masterNode["Pitch"][0].as(), masterNode["Pitch"][1].as(), masterNode["Pitch"][2].as() }, }}; std::array osc1PitchOffsets = {{ - { config["Osc1OctaveOffset"][0].as(), config["Osc1OctaveOffset"][1].as(), config["Osc1OctaveOffset"][2].as() }, - { config["Osc1SemitoneOffset"][0].as(), config["Osc1SemitoneOffset"][1].as(), config["Osc1SemitoneOffset"][2].as() }, - { config["Osc1PitchOffset"][0].as(), config["Osc1PitchOffset"][1].as(), config["Osc1PitchOffset"][2].as() }, + { osc1Node["Octave"][0].as(), osc1Node["Octave"][1].as(), osc1Node["Octave"][2].as() }, + { osc1Node["Semitone"][0].as(), osc1Node["Semitone"][1].as(), osc1Node["Semitone"][2].as() }, + { osc1Node["Pitch"][0].as(),osc1Node["Pitch"][1].as(), osc1Node["Pitch"][2].as() }, }}; std::array osc2PitchOffsets = {{ - { config["Osc2OctaveOffset"][0].as(), config["Osc2OctaveOffset"][1].as(), config["Osc2OctaveOffset"][2].as() }, - { config["Osc2SemitoneOffset"][0].as(), config["Osc2SemitoneOffset"][1].as(), config["Osc2SemitoneOffset"][2].as() }, - { config["Osc2PitchOffset"][0].as(), config["Osc2PitchOffset"][1].as(), config["Osc2PitchOffset"][2].as() }, + { osc2Node["Octave"][0].as(), osc2Node["Octave"][1].as(), osc2Node["Octave"][2].as() }, + { osc2Node["Semitone"][0].as(), osc2Node["Semitone"][1].as(), osc2Node["Semitone"][2].as() }, + { osc2Node["Pitch"][0].as(), osc2Node["Pitch"][1].as(), osc2Node["Pitch"][2].as() }, }}; std::array osc3PitchOffsets = {{ - { config["Osc3OctaveOffset"][0].as(), config["Osc3OctaveOffset"][1].as(), config["Osc3OctaveOffset"][2].as() }, - { config["Osc3SemitoneOffset"][0].as(), config["Osc3SemitoneOffset"][1].as(), config["Osc3SemitoneOffset"][2].as() }, - { config["Osc3PitchOffset"][0].as(), config["Osc3PitchOffset"][1].as(), config["Osc3PitchOffset"][2].as() }, + { osc3Node["Octave"][0].as(), osc3Node["Octave"][1].as(), osc3Node["Octave"][2].as() }, + { osc3Node["Semitone"][0].as(), osc3Node["Semitone"][1].as(), osc3Node["Semitone"][2].as() }, + { osc3Node["Pitch"][0].as(), osc3Node["Pitch"][1].as(), osc3Node["Pitch"][2].as() }, }}; // set the values in the paramstore @@ -126,9 +130,11 @@ std::array ConfigInterface::loadEnvProfile(YAML::Node* node, std::stri std::array paramProfile; - for(int i = 0; i < paramProfile.size(); i++) { - paramProfile[i] = { envelopeNode[i][0].as(), envelopeNode[i][1].as(), envelopeNode[i][2].as() }; - } + paramProfile[0] = { envelopeNode["Depth"][0].as(), envelopeNode["Depth"][1].as(), envelopeNode["Depth"][2].as() }; + paramProfile[1] = { envelopeNode["Attack"][0].as(), envelopeNode["Attack"][1].as(), envelopeNode["Attack"][2].as() }; + paramProfile[2] = { envelopeNode["Decay"][0].as(), envelopeNode["Decay"][1].as(), envelopeNode["Decay"][2].as() }; + paramProfile[3] = { envelopeNode["Sustain"][0].as(), envelopeNode["Sustain"][1].as(), envelopeNode["Sustain"][2].as() }; + paramProfile[4] = { envelopeNode["Release"][0].as(), envelopeNode["Release"][1].as(), envelopeNode["Release"][2].as() }; return paramProfile; } diff --git a/src/ConfigInterface.h b/src/ConfigInterface.h index 2a9f91c..4bfa96d 100644 --- a/src/ConfigInterface.h +++ b/src/ConfigInterface.h @@ -8,7 +8,7 @@ #include "ParameterStore.h" -#define CONFIG_VERSION 0x0002 +#define CONFIG_VERSION 0x04 enum class ConfigFile { Audio = 0 diff --git a/src/ui/MainWindow.cpp b/src/ui/MainWindow.cpp index 377fcae..531f4bf 100644 --- a/src/ui/MainWindow.cpp +++ b/src/ui/MainWindow.cpp @@ -157,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) // what I might do is make a variable-length slider-package object - ui_->sliderMasterOctave->setResolution(configRoot["MasterOctaveOffset"][2].as() - configRoot["MasterOctaveOffset"][1].as()); - ui_->sliderMasterOctave->setRange(configRoot["MasterOctaveOffset"][1].as(), configRoot["MasterOctaveOffset"][2].as()); - ui_->sliderMasterOctave->setValue(configRoot["MasterOctaveOffset"][0].as()); + YAML::Node masterNode = configRoot["MasterPitchOffset"]; + YAML::Node osc1Node = configRoot["Osc1PitchOffset"]; + YAML::Node osc2Node = configRoot["Osc2PitchOffset"]; + YAML::Node osc3Node = configRoot["Osc3PitchOffset"]; + ui_->sliderMasterOctave->setResolution(masterNode["Octave"][2].as() - masterNode["Octave"][1].as()); + ui_->sliderMasterOctave->setRange(masterNode["Octave"][1].as(), masterNode["Octave"][2].as()); + ui_->sliderMasterOctave->setValue(masterNode["Octave"][0].as()); - ui_->sliderMasterSemitone->setResolution(configRoot["MasterSemitoneOffset"][2].as() - configRoot["MasterSemitoneOffset"][1].as()); - ui_->sliderMasterSemitone->setRange(configRoot["MasterSemitoneOffset"][1].as(), configRoot["MasterSemitoneOffset"][2].as()); - ui_->sliderMasterSemitone->setValue(configRoot["MasterSemitoneOffset"][0].as()); + ui_->sliderMasterSemitone->setResolution(masterNode["Semitone"][2].as() - masterNode["Semitone"][1].as()); + ui_->sliderMasterSemitone->setRange(masterNode["Semitone"][1].as(), masterNode["Semitone"][2].as()); + ui_->sliderMasterSemitone->setValue(masterNode["Semitone"][0].as()); - ui_->sliderMasterPitch->setRange(configRoot["MasterPitchOffset"][1].as(), configRoot["MasterPitchOffset"][2].as()); - ui_->sliderMasterPitch->setValue(configRoot["MasterPitchOffset"][0].as()); + ui_->sliderMasterPitch->setRange(masterNode["Pitch"][1].as(), masterNode["Pitch"][2].as()); + ui_->sliderMasterPitch->setValue(masterNode["Pitch"][0].as()); - ui_->sliderOsc1Octave->setResolution(configRoot["Osc1OctaveOffset"][2].as() - configRoot["Osc1OctaveOffset"][1].as()); - ui_->sliderOsc1Octave->setRange(configRoot["Osc1OctaveOffset"][1].as(), configRoot["Osc1OctaveOffset"][2].as()); - ui_->sliderOsc1Octave->setValue(configRoot["Osc1OctaveOffset"][0].as()); + ui_->sliderOsc1Octave->setResolution(osc1Node["Octave"][2].as() - osc1Node["Octave"][1].as()); + ui_->sliderOsc1Octave->setRange(osc1Node["Octave"][1].as(), osc1Node["Octave"][2].as()); + ui_->sliderOsc1Octave->setValue(osc1Node["Octave"][0].as()); - ui_->sliderOsc1Semitone->setResolution(configRoot["Osc1SemitoneOffset"][2].as() - configRoot["Osc1SemitoneOffset"][1].as()); - ui_->sliderOsc1Semitone->setRange(configRoot["Osc1SemitoneOffset"][1].as(), configRoot["Osc1SemitoneOffset"][2].as()); - ui_->sliderOsc1Semitone->setValue(configRoot["Osc1SemitoneOffset"][0].as()); + ui_->sliderOsc1Semitone->setResolution(osc1Node["Semitone"][2].as() - osc1Node["Semitone"][1].as()); + ui_->sliderOsc1Semitone->setRange(osc1Node["Semitone"][1].as(), osc1Node["Semitone"][2].as()); + ui_->sliderOsc1Semitone->setValue(osc1Node["Semitone"][0].as()); - ui_->sliderOsc1Pitch->setRange(configRoot["Osc1PitchOffset"][1].as(), configRoot["Osc1PitchOffset"][2].as()); - ui_->sliderOsc1Pitch->setValue(configRoot["Osc1PitchOffset"][0].as()); + ui_->sliderOsc1Pitch->setRange(osc1Node["Pitch"][1].as(), osc1Node["Pitch"][2].as()); + ui_->sliderOsc1Pitch->setValue(osc1Node["Pitch"][0].as()); - ui_->sliderOsc2Octave->setResolution(configRoot["Osc2OctaveOffset"][2].as() - configRoot["Osc2OctaveOffset"][1].as()); - ui_->sliderOsc2Octave->setRange(configRoot["Osc2OctaveOffset"][1].as(), configRoot["Osc2OctaveOffset"][2].as()); - ui_->sliderOsc2Octave->setValue(configRoot["Osc2OctaveOffset"][0].as()); + ui_->sliderOsc2Octave->setResolution(osc2Node["Octave"][2].as() - osc2Node["Octave"][1].as()); + ui_->sliderOsc2Octave->setRange(osc2Node["Octave"][1].as(), osc2Node["Octave"][2].as()); + ui_->sliderOsc2Octave->setValue(osc2Node["Octave"][0].as()); - ui_->sliderOsc2Semitone->setResolution(configRoot["Osc2SemitoneOffset"][2].as() - configRoot["Osc2SemitoneOffset"][1].as()); - ui_->sliderOsc2Semitone->setRange(configRoot["Osc2SemitoneOffset"][1].as(), configRoot["Osc2SemitoneOffset"][2].as()); - ui_->sliderOsc2Semitone->setValue(configRoot["Osc2SemitoneOffset"][0].as()); + ui_->sliderOsc2Semitone->setResolution(osc2Node["Semitone"][2].as() - osc2Node["Semitone"][1].as()); + ui_->sliderOsc2Semitone->setRange(osc2Node["Semitone"][1].as(), osc2Node["Semitone"][2].as()); + ui_->sliderOsc2Semitone->setValue(osc2Node["Semitone"][0].as()); - ui_->sliderOsc2Pitch->setRange(configRoot["Osc2PitchOffset"][1].as(), configRoot["Osc2PitchOffset"][2].as()); - ui_->sliderOsc2Pitch->setValue(configRoot["Osc2PitchOffset"][0].as()); + ui_->sliderOsc2Pitch->setRange(osc2Node["Pitch"][1].as(), osc2Node["Pitch"][2].as()); + ui_->sliderOsc2Pitch->setValue(osc2Node["Pitch"][0].as()); - ui_->sliderOsc3Octave->setResolution(configRoot["Osc3OctaveOffset"][2].as() - configRoot["Osc3OctaveOffset"][1].as()); - ui_->sliderOsc3Octave->setRange(configRoot["Osc3OctaveOffset"][1].as(), configRoot["Osc3OctaveOffset"][2].as()); - ui_->sliderOsc3Octave->setValue(configRoot["Osc3OctaveOffset"][0].as()); + ui_->sliderOsc3Octave->setResolution(osc3Node["Octave"][2].as() - osc3Node["Octave"][1].as()); + ui_->sliderOsc3Octave->setRange(osc3Node["Octave"][1].as(), osc3Node["Octave"][2].as()); + ui_->sliderOsc3Octave->setValue(osc3Node["Octave"][0].as()); - ui_->sliderOsc3Semitone->setResolution(configRoot["Osc3SemitoneOffset"][2].as() - configRoot["Osc3SemitoneOffset"][1].as()); - ui_->sliderOsc3Semitone->setRange(configRoot["Osc3SemitoneOffset"][1].as(), configRoot["Osc3SemitoneOffset"][2].as()); - ui_->sliderOsc3Semitone->setValue(configRoot["Osc3SemitoneOffset"][0].as()); + ui_->sliderOsc3Semitone->setResolution(osc3Node["Semitone"][2].as() - osc3Node["Semitone"][1].as()); + ui_->sliderOsc3Semitone->setRange(osc3Node["Semitone"][1].as(), osc3Node["Semitone"][2].as()); + ui_->sliderOsc3Semitone->setValue(osc3Node["Semitone"][0].as()); + + ui_->sliderOsc3Pitch->setRange(osc3Node["Pitch"][1].as(), osc3Node["Pitch"][2].as()); + ui_->sliderOsc3Pitch->setValue(osc3Node["Pitch"][0].as()); - ui_->sliderOsc3Pitch->setRange(configRoot["Osc3PitchOffset"][1].as(), configRoot["Osc3PitchOffset"][2].as()); - ui_->sliderOsc3Pitch->setValue(configRoot["Osc3PitchOffset"][0].as()); - ui_->comboOsc1WaveSelector1->setCurrentIndex(configRoot["OscWaveSelector1"].as()); ui_->comboOsc1WaveSelector2->setCurrentIndex(configRoot["OscWaveSelector2"].as());