diff --git a/CMakeLists.txt b/CMakeLists.txt index f2478e0..dfbfbba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,7 +77,6 @@ qt_add_executable(metabolus src/synth/Voice.h src/synth/WavetableController.cpp src/synth/WavetableController.h - resources/resources.qrc src/ui/widgets/SmartSlider/SmartSlider.cpp src/ui/widgets/SmartSlider/SmartSlider.h src/ui/widgets/SmartSlider/SmartSlider.ui diff --git a/README.md b/README.md index 2c9e266..c303988 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ This synthesizer isn't very good, but it's neat :3 - [x] Create oscillator class where the actual tone generation occurs. Multiple oscillators increase the sound complexity considerably - [x] Create a UI scope to visualize the synthesized composite waveform -- [ ] 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] Create digital filters, prob biquad. Controllable from ui obv (cutoff + resonance) - [x] Add polyphony somewhere. Probably involves a voice class. If processing power @@ -30,9 +30,9 @@ This synthesizer isn't very good, but it's neat :3 - [ ] Filter upgrades including some more complex solving techniques (State Variable Filter), better key tracking, more natural envelope curves, filter drive, etc. - [ ] Reverb (quite a few ways to do this, needs more research) -- [ ] Pitch tuning with the multiple oscillators (Octave > Semitone > Fine-tune) +- [x] Pitch tuning with the multiple oscillators (Octave > Semitone > Fine-tune) - [ ] Frequency Modulation (huge) -- [ ] Profile saving and loading, also includes loading configurations like keymaps, audio +- [x] Profile saving and loading, also includes loading configurations like keymaps, audio engine config, etc. from a yaml instead of being hardcoded - [ ] Noise - [ ] LFO modulation diff --git a/config/audio.yaml b/config/audio.yaml index 3e356bd..ed94762 100644 --- a/config/audio.yaml +++ b/config/audio.yaml @@ -3,7 +3,7 @@ # Configures properties for the RtAudio engine # Number of samples per second -sampleRate: 44100 +sampleRate: 44100 # unconfigurable: sampleFormat; [-1, 1] float # number of audio channels diff --git a/config/keymap.yaml b/config/keymap.yaml new file mode 100644 index 0000000..2298cd8 --- /dev/null +++ b/config/keymap.yaml @@ -0,0 +1,338 @@ + +# keymap.yaml +# Configures a computer keyboard mapping of keys to midi notes for when you don't have a midi device available +# (there's probably tools available that are able to make your keyboard act as a midi device but this was easier) + +keymap: + Key_Shift: B_2 + Key_Z: C_3 + Key_S: C#3 + Key_X: D_3 + Key_D: D#3 + Key_C: E_3 + Key_V: F_3 + Key_G: F#3 + Key_B: G_3 + Key_H: G#3 + Key_N: A_3 + Key_J: A#3 + Key_M: B_3 + Key_Q: C_4 + Key_2: C#4 + Key_W: D_4 + Key_3: D#4 + Key_E: E_4 + Key_R: F_4 + Key_5: F#4 + Key_T: G_4 + Key_6: G#4 + Key_Y: A_4 + Key_7: A#4 + Key_U: B_4 + Key_I: C_5 + Key_9: C#5 + Key_O: D_5 + +# below are translations from strings to numbers that both Qt and the Oscillators can understand +# do not touch them unless you like the program not running + +# note strings to midi ids, do not touch +# only has sharps for now, you can figure the rest out +notes: + A_0: 21 + A#0: 22 + B_0: 23 + C_1: 24 + C#1: 25 + D_1: 26 + D#1: 27 + E_1: 28 + F_1: 29 + F#1: 30 + G_1: 31 + G#1: 32 + A_1: 33 + A#1: 34 + B_1: 35 + C_2: 36 + C#2: 37 + D_2: 38 + D#2: 39 + E_2: 40 + F_2: 41 + F#2: 42 + G_2: 43 + G#2: 44 + A_2: 45 + A#2: 46 + B_2: 47 + C_3: 48 + C#3: 49 + D_3: 50 + D#3: 51 + E_3: 52 + F_3: 53 + F#3: 54 + G_3: 55 + G#3: 56 + A_3: 57 + A#3: 58 + B_3: 59 + C_4: 60 + C#4: 61 + D_4: 62 + D#4: 63 + E_4: 64 + F_4: 65 + F#4: 66 + G_4: 67 + G#4: 68 + A_4: 69 + A#4: 70 + B_4: 71 + C_5: 72 + C#5: 73 + D_5: 74 + D#5: 75 + E_5: 76 + F_5: 77 + F#5: 78 + G_5: 79 + G#5: 80 + A_5: 81 + A#5: 82 + B_5: 83 + C_6: 84 + C#6: 85 + D_6: 86 + D#6: 87 + E_6: 88 + F_6: 89 + F#6: 90 + G_6: 91 + G#6: 92 + A_6: 93 + A#6: 94 + B_6: 95 + C_7: 96 + C#7: 97 + D_7: 98 + D#7: 99 + E_7: 100 + F_7: 101 + F#7: 102 + G_7: 103 + G#7: 104 + A_7: 105 + A#7: 106 + B_7: 107 + C_8: 108 + +# key strings to qt-key-ids, do not touch +keys: + Key_Space: 0x20 + Key_Any: 0x20 + Key_Exclam: 0x21 + Key_QuoteDbl: 0x22 + Key_NumberSign: 0x23 + Key_Dollar: 0x24 + Key_Percent: 0x25 + Key_Ampersand: 0x26 + Key_Apostrophe: 0x27 + Key_ParenLeft: 0x28 + Key_ParenRight: 0x29 + Key_Asterisk: 0x2a + Key_Plus: 0x2b + Key_Comma: 0x2c + Key_Minus: 0x2d + Key_Period: 0x2e + Key_Slash: 0x2f + Key_0: 0x30 + Key_1: 0x31 + Key_2: 0x32 + Key_3: 0x33 + Key_4: 0x34 + Key_5: 0x35 + Key_6: 0x36 + Key_7: 0x37 + Key_8: 0x38 + Key_9: 0x39 + Key_Colon: 0x3a + Key_Semicolon: 0x3b + Key_Less: 0x3c + Key_Equal: 0x3d + Key_Greater: 0x3e + Key_Question: 0x3f + Key_At: 0x40 + Key_A: 0x41 + Key_B: 0x42 + Key_C: 0x43 + Key_D: 0x44 + Key_E: 0x45 + Key_F: 0x46 + Key_G: 0x47 + Key_H: 0x48 + Key_I: 0x49 + Key_J: 0x4a + Key_K: 0x4b + Key_L: 0x4c + Key_M: 0x4d + Key_N: 0x4e + Key_O: 0x4f + Key_P: 0x50 + Key_Q: 0x51 + Key_R: 0x52 + Key_S: 0x53 + Key_T: 0x54 + Key_U: 0x55 + Key_V: 0x56 + Key_W: 0x57 + Key_X: 0x58 + Key_Y: 0x59 + Key_Z: 0x5a + Key_BracketLeft: 0x5b + Key_Backslash: 0x5c + Key_BracketRight: 0x5d + Key_AsciiCircum: 0x5e + Key_Underscore: 0x5f + Key_QuoteLeft: 0x60 + Key_BraceLeft: 0x7b + Key_Bar: 0x7c + Key_BraceRight: 0x7d + Key_AsciiTilde: 0x7e + Key_nobreakspace: 0x0a0 + Key_exclamdown: 0x0a1 + Key_cent: 0x0a2 + Key_sterling: 0x0a3 + Key_currency: 0x0a4 + Key_yen: 0x0a5 + Key_brokenbar: 0x0a6 + Key_section: 0x0a7 + Key_diaeresis: 0x0a8 + Key_copyright: 0x0a9 + Key_ordfeminine: 0x0aa + Key_guillemotleft: 0x0ab + Key_notsign: 0x0ac + Key_hyphen: 0x0ad + Key_registered: 0x0ae + Key_macron: 0x0af + Key_degree: 0x0b0 + Key_plusminus: 0x0b1 + Key_twosuperior: 0x0b2 + Key_threesuperior: 0x0b3 + Key_acute: 0x0b4 + Key_micro: 0x0b5 + Key_paragraph: 0x0b6 + Key_periodcentered: 0x0b7 + Key_cedilla: 0x0b8 + Key_onesuperior: 0x0b9 + Key_masculine: 0x0ba + Key_guillemotright: 0x0bb + Key_onequarter: 0x0bc + Key_onehalf: 0x0bd + Key_threequarters: 0x0be + Key_questiondown: 0x0bf + Key_Agrave: 0x0c0 + Key_Aacute: 0x0c1 + Key_Acircumflex: 0x0c2 + Key_Atilde: 0x0c3 + Key_Adiaeresis: 0x0c4 + Key_Aring: 0x0c5 + Key_AE: 0x0c6 + Key_Ccedilla: 0x0c7 + Key_Egrave: 0x0c8 + Key_Eacute: 0x0c9 + Key_Ecircumflex: 0x0ca + Key_Ediaeresis: 0x0cb + Key_Igrave: 0x0cc + Key_Iacute: 0x0cd + Key_Icircumflex: 0x0ce + Key_Idiaeresis: 0x0cf + Key_ETH: 0x0d0 + Key_Ntilde: 0x0d1 + Key_Ograve: 0x0d2 + Key_Oacute: 0x0d3 + Key_Ocircumflex: 0x0d4 + Key_Otilde: 0x0d5 + Key_Odiaeresis: 0x0d6 + Key_multiply: 0x0d7 + Key_Ooblique: 0x0d8 + Key_Ugrave: 0x0d9 + Key_Uacute: 0x0da + Key_Ucircumflex: 0x0db + Key_Udiaeresis: 0x0dc + Key_Yacute: 0x0dd + Key_THORN: 0x0de + Key_ssharp: 0x0df + Key_division: 0x0f7 + Key_ydiaeresis: 0x0ff + Key_Escape: 0x01000000 + Key_Tab: 0x01000001 + Key_Backtab: 0x01000002 + Key_Backspace: 0x01000003 + Key_Return: 0x01000004 + Key_Enter: 0x01000005 + Key_Insert: 0x01000006 + Key_Delete: 0x01000007 + Key_Pause: 0x01000008 + Key_Print: 0x01000009 + Key_SysReq: 0x0100000a + Key_Clear: 0x0100000b + Key_Home: 0x01000010 + Key_End: 0x01000011 + Key_Left: 0x01000012 + Key_Up: 0x01000013 + Key_Right: 0x01000014 + Key_Down: 0x01000015 + Key_PageUp: 0x01000016 + Key_PageDown: 0x01000017 + Key_Shift: 0x01000020 + Key_Control: 0x01000021 + Key_Meta: 0x01000022 + Key_Alt: 0x01000023 + Key_CapsLock: 0x01000024 + Key_NumLock: 0x01000025 + Key_ScrollLock: 0x01000026 + Key_F1: 0x01000030 + Key_F2: 0x01000031 + Key_F3: 0x01000032 + Key_F4: 0x01000033 + Key_F5: 0x01000034 + Key_F6: 0x01000035 + Key_F7: 0x01000036 + Key_F8: 0x01000037 + Key_F9: 0x01000038 + Key_F10: 0x01000039 + Key_F11: 0x0100003a + Key_F12: 0x0100003b + Key_F13: 0x0100003c + Key_F14: 0x0100003d + Key_F15: 0x0100003e + Key_F16: 0x0100003f + Key_F17: 0x01000040 + Key_F18: 0x01000041 + Key_F19: 0x01000042 + Key_F20: 0x01000043 + Key_F21: 0x01000044 + Key_F22: 0x01000045 + Key_F23: 0x01000046 + Key_F24: 0x01000047 + Key_F25: 0x01000048 + Key_F26: 0x01000049 + Key_F27: 0x0100004a + Key_F28: 0x0100004b + Key_F29: 0x0100004c + Key_F30: 0x0100004d + Key_F31: 0x0100004e + Key_F32: 0x0100004f + Key_F33: 0x01000050 + Key_F34: 0x01000051 + Key_F35: 0x01000052 + Key_Super_L: 0x01000053 + Key_Super_R: 0x01000054 + Key_Menu: 0x01000055 + Key_Hyper_L: 0x01000056 + Key_Hyper_R: 0x01000057 + Key_Help: 0x01000058 + Key_Direction_L: 0x01000059 + Key_Direction_R: 0x01000060 diff --git a/config/profiles/default.yaml b/config/profiles/default.yaml index 7fa4d50..79e3489 100644 --- a/config/profiles/default.yaml +++ b/config/profiles/default.yaml @@ -4,29 +4,38 @@ # sequences in the form [x, x, x] denote [setValue, sliderMinimum, sliderMaximum] -version: 0x0002 +version: 0x0003 # deprecated, useless Osc1Freq: [100, 20, 600] # wavetable selections OscWaveSelector1: 2 -OscWaveSelector2: 1 +OscWaveSelector2: 3 -# Oscillator frequency parameters +# Frequency parameters +MasterOctaveOffset: [0, -5, 5] +MasterSemitoneOffset: [0, -12, 12] +MasterPitchOffset: [0, -100, 100] Osc1OctaveOffset: [0, -5, 5] Osc1SemitoneOffset: [0, -12, 12] -Osc1PitchOffset: [0, -100, 100] +Osc1PitchOffset: [1.34, -100, 100] Osc2OctaveOffset: [1, -5, 5] Osc2SemitoneOffset: [0, -12, 12] -Osc2PitchOffset: [0, -100, 100] +Osc2PitchOffset: [12.86, -100, 100] Osc3OctaveOffset: [1, -5, 5] Osc3SemitoneOffset: [7, -12, 12] -Osc3PitchOffset: [1.96, -100, 100] +Osc3PitchOffset: [-8.79, -100, 100] + +# gonna have something like this: +#MasterPitchOffset: +# - [0, -5, 5] # Octave +# - [0, -12, -12] # Semitone +# - [0, -100, 100] # Pitch # Envelope generator parameters Osc1Volume: - - [1, 0, 2] # Depth + - [1, 0, 10] # Depth - [0.05, 0, 2] # Attack - [0.2, 0, 2] # Decay - [0.7, 0, 1] # Sustain diff --git a/resources/resources.qrc b/resources/resources.qrc deleted file mode 100644 index e69de29..0000000 diff --git a/src/ConfigInterface.cpp b/src/ConfigInterface.cpp index 058ae0b..dc962b8 100644 --- a/src/ConfigInterface.cpp +++ b/src/ConfigInterface.cpp @@ -47,7 +47,7 @@ int ConfigInterface::getValue(ConfigFile file, std::string key, int defaultVal) } // ugly but if it works it works -void ConfigInterface::loadProfile(std::string filename) { +YAML::Node ConfigInterface::loadProfile(std::string filename) { // load file std::string filepath = "config/profiles/" + filename + ".yaml"; @@ -57,16 +57,16 @@ void ConfigInterface::loadProfile(std::string filename) { config = YAML::LoadFile(filepath); } catch(const std::exception& e) { std::cerr << e.what() << std::endl; - return; + return config; } // check version int version = config["version"].as(); // yaml-cpp parses unquoted hex as integers if(version < CONFIG_VERSION) { std::cout << "Parameter profile version " << version << "is outdated below the compatible version " << CONFIG_VERSION << std::endl; - return; + return config; } else { - std::cout << version << std::endl; + std::cout << "Parameter profile version " << version << std::endl; } // extract values from the config file @@ -74,6 +74,27 @@ void ConfigInterface::loadProfile(std::string filename) { std::array fCutoffProfile = loadEnvProfile(&config, "FilterCutoff"); std::array fResonanceProfile = loadEnvProfile(&config, "FilterResonance"); + 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() }, + }}; + 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() }, + }}; + 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() }, + }}; + 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() }, + }}; + // TODO: remove this once all the parameters are set properly params_->resetToDefaults(); @@ -81,13 +102,26 @@ void ConfigInterface::loadProfile(std::string filename) { params_->set(EnvelopeId::Osc1Volume, osc1VolumeProfile[0].def, osc1VolumeProfile[1].def, osc1VolumeProfile[2].def, osc1VolumeProfile[3].def, osc1VolumeProfile[4].def); params_->set(EnvelopeId::FilterCutoff, fCutoffProfile[0].def, fCutoffProfile[1].def, fCutoffProfile[2].def, fCutoffProfile[3].def, fCutoffProfile[4].def); params_->set(EnvelopeId::FilterResonance, fResonanceProfile[0].def, fResonanceProfile[1].def, fResonanceProfile[2].def, fResonanceProfile[3].def, fResonanceProfile[4].def); - // TODO: why do I bother passing in 5 values independently when I can just do an array ? - // VVV look down there its so easy + // TODO: 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::MasterSemitoneOffset, masterPitchOffsets[1].def); + params_->set(ParamId::MasterPitchOffset, masterPitchOffsets[2].def); + params_->set(ParamId::Osc1OctaveOffset, osc1PitchOffsets[0].def); + params_->set(ParamId::Osc1SemitoneOffset, osc1PitchOffsets[1].def); + params_->set(ParamId::Osc1PitchOffset, osc1PitchOffsets[2].def); + params_->set(ParamId::Osc2OctaveOffset, osc2PitchOffsets[0].def); + params_->set(ParamId::Osc2SemitoneOffset, osc2PitchOffsets[1].def); + params_->set(ParamId::Osc2PitchOffset, osc2PitchOffsets[2].def); + params_->set(ParamId::Osc3OctaveOffset, osc3PitchOffsets[0].def); + params_->set(ParamId::Osc3SemitoneOffset, osc3PitchOffsets[1].def); + params_->set(ParamId::Osc3PitchOffset, osc3PitchOffsets[2].def); // TODO: // load wavetable settings // load oscillator pitch settings + return config; + } std::array ConfigInterface::loadEnvProfile(YAML::Node* node, std::string profile) { diff --git a/src/ConfigInterface.h b/src/ConfigInterface.h index 084c7ad..bbf3280 100644 --- a/src/ConfigInterface.h +++ b/src/ConfigInterface.h @@ -32,7 +32,7 @@ public: int getValue(ConfigFile file, std::string key, int defaultVal); - void loadProfile(std::string filename); + YAML::Node loadProfile(std::string filename); std::array loadEnvProfile(YAML::Node* node, std::string profile); std::array loadEnvProfile(std::string filename, std::string profile); diff --git a/src/KeyboardController.cpp b/src/KeyboardController.cpp index 4b60687..4c9126a 100644 --- a/src/KeyboardController.cpp +++ b/src/KeyboardController.cpp @@ -2,38 +2,40 @@ #include "KeyboardController.h" #include +#include +#include -KeyboardController::KeyboardController(NoteQueue& queue) : queue_(queue) { +KeyboardController::KeyboardController(NoteQueue& queue, ConfigInterface* config) : queue_(queue), config_(config) { + + // load keymap from config file + std::string filepath = "config/keymap.yaml"; + filepath = std::filesystem::absolute(filepath).string(); + YAML::Node file; + try { + file = YAML::LoadFile(filepath); + } catch(const std::exception& e) { + std::cerr << e.what() << std::endl; + return; + } + + YAML::Node keymapNode = file["keymap"]; // node for string to string mappings + YAML::Node notesNode = file["notes"]; // string to midi int mappings + YAML::Node keysNode = file["keys"]; // string to qt key id mappings + + // for each element in the keymap + for (const auto& entry : keymapNode) { + + std::string keyString = entry.first.as(); + std::string noteString = entry.second.as(); + + // match the strings to ints + uint8_t noteValue = notesNode[noteString].as(); + uint32_t keyValue = keysNode[keyString].as(); + + // insert into map + keymap_.emplace(keyValue, noteValue); + } - // TODO: also configurable via a yml - keymap_ = { - { Qt::Key_Shift, 47 }, // B 2 - { Qt::Key_Z, 48 }, // C 3 - { Qt::Key_S, 49 }, // C# - { Qt::Key_X, 50 }, // D - { Qt::Key_D, 51 }, // D# - { Qt::Key_C, 52 }, // E - { Qt::Key_V, 53 }, // F - { Qt::Key_G, 54 }, // F# - { Qt::Key_B, 55 }, // G - { Qt::Key_H, 56 }, // G# - { Qt::Key_N, 57 }, // A - { Qt::Key_J, 58 }, // A# - { Qt::Key_M, 59 }, // B 3 - { Qt::Key_Q, 60 }, // C 4 - { Qt::Key_2, 61 }, // C# - { Qt::Key_W, 62 }, // D - { Qt::Key_3, 63 }, // D# - { Qt::Key_E, 64 }, // E - { Qt::Key_R, 65 }, // F - { Qt::Key_5, 66 }, // F# - { Qt::Key_T, 67 }, // G - { Qt::Key_6, 68 }, // G# - { Qt::Key_Y, 69 }, // A - { Qt::Key_7, 70 }, // A# - { Qt::Key_U, 71 }, // B 4 - { Qt::Key_I, 72 }, // C 5 - }; } void KeyboardController::handleKeyPress(QKeyEvent* e) { diff --git a/src/KeyboardController.h b/src/KeyboardController.h index a793ac9..338014c 100644 --- a/src/KeyboardController.h +++ b/src/KeyboardController.h @@ -5,12 +5,13 @@ #include #include "NoteQueue.h" +#include "ConfigInterface.h" // The keyboardcontroller handles user inputs from a keyboard and maps them to note events class KeyboardController { public: - explicit KeyboardController(NoteQueue& queue); + explicit KeyboardController(NoteQueue& queue, ConfigInterface* config); ~KeyboardController() = default; void handleKeyPress(QKeyEvent* e); @@ -19,6 +20,7 @@ public: private: NoteQueue& queue_; + ConfigInterface* config_; // keymap is key -> midi note id std::unordered_map keymap_; diff --git a/src/ParameterStore.h b/src/ParameterStore.h index 70fdef1..25b203a 100644 --- a/src/ParameterStore.h +++ b/src/ParameterStore.h @@ -9,6 +9,9 @@ enum class ParamId : uint16_t { Osc1Frequency, Osc1WaveSelector1, Osc1WaveSelector2, + MasterOctaveOffset, + MasterSemitoneOffset, + MasterPitchOffset, Osc1OctaveOffset, Osc1SemitoneOffset, Osc1PitchOffset, @@ -69,12 +72,13 @@ struct ParamDefault { float max; }; -// TODO: make these configurable via yml file too -// later TODO: and then when I have full on profile saving there will be a default profile to load from constexpr std::array(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 diff --git a/src/synth/AudioEngine.cpp b/src/synth/AudioEngine.cpp index 6c80fdd..19f030c 100644 --- a/src/synth/AudioEngine.cpp +++ b/src/synth/AudioEngine.cpp @@ -35,9 +35,17 @@ bool AudioEngine::start() { RtAudio::StreamOptions options; options.flags = RTAUDIO_MINIMIZE_LATENCY; - // TODO: error check this please - audio_.openStream(¶ms, nullptr, RTAUDIO_FLOAT32, sampleRate_, &bufferFrames_, &AudioEngine::audioCallback, this, &options); - audio_.startStream(); + RtAudioErrorType status = audio_.openStream(¶ms, nullptr, RTAUDIO_FLOAT32, sampleRate_, &bufferFrames_, &AudioEngine::audioCallback, this, &options); + if(status != RTAUDIO_NO_ERROR) { + std::cout << "Error opening RtAudio stream" << std::endl; + return false; + } + + status = audio_.startStream(); + if(status != RTAUDIO_NO_ERROR) { + std::cout << "Error starting RtAudio stream" << std::endl; + return false; + } // sanity check std::cout << "sample rate: " << sampleRate_ << " buffer frames: " << bufferFrames_ << std::endl; diff --git a/src/synth/AudioEngine.h b/src/synth/AudioEngine.h index 7d00764..152fd40 100644 --- a/src/synth/AudioEngine.h +++ b/src/synth/AudioEngine.h @@ -49,7 +49,8 @@ private: ScopeBuffer scope_ { 1024 }; // stores audio samples for visualization RtAudio audio_{AUDIO_API}; // audio device - // TODO: id like a yml config file or something for these + + // Configurable in the audio.yaml file, assigned defaults if the file is not found uint32_t sampleRate_ = 44100; uint32_t bufferFrames_ = 512; // time per buffer = BF/SR (256/44100 = 5.8ms) uint32_t channels_ = 2; // stereo diff --git a/src/synth/Voice.cpp b/src/synth/Voice.cpp index c7a03a1..d7c391a 100644 --- a/src/synth/Voice.cpp +++ b/src/synth/Voice.cpp @@ -2,6 +2,7 @@ #include "Voice.h" #include #include +#include Voice::Voice(SmoothedParam* params, WavetableController* wavetable) : params_(params), wavetable_(wavetable) { @@ -89,16 +90,22 @@ float Voice::process(float* params, bool& scopeTrigger) { // calculate the note/pitch of the oscillators bool temp = false; - uint8_t osc1NoteOffset = static_cast((SYNTH_NOTES_PER_OCTAVE+1) * getParam(ParamId::Osc1OctaveOffset) + getParam(ParamId::Osc1SemitoneOffset)); - uint8_t osc2NoteOffset = static_cast((SYNTH_NOTES_PER_OCTAVE+1) * getParam(ParamId::Osc2OctaveOffset) + getParam(ParamId::Osc2SemitoneOffset)); - uint8_t osc3NoteOffset = static_cast((SYNTH_NOTES_PER_OCTAVE+1) * getParam(ParamId::Osc3OctaveOffset) + getParam(ParamId::Osc3SemitoneOffset)); + uint8_t osc1NoteOffset = static_cast(std::round((SYNTH_NOTES_PER_OCTAVE) * (getParam(ParamId::Osc1OctaveOffset) + getParam(ParamId::MasterOctaveOffset)) + getParam(ParamId::Osc1SemitoneOffset) + getParam(ParamId::MasterSemitoneOffset))); + uint8_t osc2NoteOffset = static_cast(std::round((SYNTH_NOTES_PER_OCTAVE) * (getParam(ParamId::Osc2OctaveOffset) + getParam(ParamId::MasterOctaveOffset)) + getParam(ParamId::Osc2SemitoneOffset) + getParam(ParamId::MasterSemitoneOffset))); + uint8_t osc3NoteOffset = static_cast(std::round((SYNTH_NOTES_PER_OCTAVE) * (getParam(ParamId::Osc3OctaveOffset) + getParam(ParamId::MasterOctaveOffset)) + getParam(ParamId::Osc3SemitoneOffset) + getParam(ParamId::MasterSemitoneOffset))); // sample oscillators - float osc1 = oscillators_[0].process(osc1NoteOffset + note_, getParam(ParamId::Osc1PitchOffset)/100.0f, scopeTrigger); - float osc2 = oscillators_[1].process(osc2NoteOffset + note_, getParam(ParamId::Osc2PitchOffset)/100.0f, temp); - float osc3 = oscillators_[2].process(osc3NoteOffset + note_, getParam(ParamId::Osc3PitchOffset)/100.0f, temp); - + float osc1 = oscillators_[0].process(osc1NoteOffset + note_, (getParam(ParamId::Osc1PitchOffset) + getParam(ParamId::MasterPitchOffset))/100.0f, scopeTrigger); + float osc2 = oscillators_[1].process(osc2NoteOffset + note_, (getParam(ParamId::Osc2PitchOffset) + getParam(ParamId::MasterPitchOffset))/100.0f, temp); + float osc3 = oscillators_[2].process(osc3NoteOffset + note_, (getParam(ParamId::Osc3PitchOffset) + getParam(ParamId::MasterPitchOffset))/100.0f, temp); + + // TODO: implement controls for noise + //float scale = static_cast(rand()) / static_cast(RAND_MAX); // Range [0.0, 1.0] + //float noise = -1.0f + 2.0f * scale; + // these values didn't sound good so I commented them out before I get controls for them + // mix oscillators - float sampleOut = (osc1 + osc2*0.25f + osc3*0.125f) * gain; + // TODO: implement gain controls for the other oscillators + float sampleOut = (osc1 + osc2*0.4f + osc3*0.15f) * gain; // pre-filtered noise // filter sample float baseFreq = oscillators_[0].frequency(); @@ -109,5 +116,7 @@ float Voice::process(float* params, bool& scopeTrigger) { float filteredSample = filter1_.biquadProcess(sampleOut); // sampleOut = filter2_.biquadProcess(sampleOut); // TODO: for some reason second filter is unstable only on windows 🤷 + //filteredSample += noise*0.125f; // post-filtered noise + return filteredSample; } \ No newline at end of file diff --git a/src/synth/Voice.h b/src/synth/Voice.h index 79f12e5..a440337 100644 --- a/src/synth/Voice.h +++ b/src/synth/Voice.h @@ -12,6 +12,7 @@ // TODO: make configurable #define SYNTH_OSCILLATOR_COUNT 3 +// if there's different oscillator amounts then we need to be able to dynamically create the ui for each of them struct SmoothedParam { float current = 0.0f; @@ -63,7 +64,6 @@ private: // filters Filter filter1_; Filter filter2_; - // TODO: I think the filter's state being uninitialized is what's causing popping when a voice starts for the first time // paramstore pointer SmoothedParam* params_; diff --git a/src/synth/WavetableController.cpp b/src/synth/WavetableController.cpp index 4fa6c7b..0758c21 100644 --- a/src/synth/WavetableController.cpp +++ b/src/synth/WavetableController.cpp @@ -17,6 +17,11 @@ void WavetableController::init() { wavetables_.resize(4); // resize for however many files we find + // don't really know how the files are gonna work + // but I'd like two files- a yaml that contains metadata like name, length, range, datatype, etc. + // and the main data be just a big array of that data type in a binary file + // 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(SYNTH_WAVETABLE_SIZE); diff --git a/src/ui/MainWindow.cpp b/src/ui/MainWindow.cpp index c51bdd9..4867b8e 100644 --- a/src/ui/MainWindow.cpp +++ b/src/ui/MainWindow.cpp @@ -10,7 +10,7 @@ MainWindow::MainWindow(QWidget *parent) : ui_(new Ui::MainWindow), config_(ConfigInterface(¶ms_)), audio_(new AudioEngine(&config_, ¶ms_)), - keyboard_(audio_->noteQueue()), + keyboard_(audio_->noteQueue(), &config_), midi_(audio_->noteQueue()) { // initialize ui @@ -49,6 +49,67 @@ MainWindow::MainWindow(QWidget *parent) : audio_->parameters()->set(ParamId::Osc1WaveSelector2, index); }); + // rogue sliders, TODO: clean these up in a package + connect(ui_->sliderMasterOctave, &SmartSlider::valueChanged, + this, [this](float value) { + audio_->parameters()->set(ParamId::MasterOctaveOffset, value); + ui_->sliderMasterOctave->setResolution(); + }); + connect(ui_->sliderMasterSemitone, &SmartSlider::valueChanged, + this, [this](float value) { + audio_->parameters()->set(ParamId::MasterSemitoneOffset, value); + ui_->sliderMasterSemitone->setResolution(); + }); + connect(ui_->sliderMasterPitch, &SmartSlider::valueChanged, + this, [this](float value) { + audio_->parameters()->set(ParamId::MasterPitchOffset, value); + }); + + connect(ui_->sliderOsc1Octave, &SmartSlider::valueChanged, + this, [this](float value) { + audio_->parameters()->set(ParamId::Osc1OctaveOffset, value); + ui_->sliderOsc1Octave->setResolution(); + }); + connect(ui_->sliderOsc1Semitone, &SmartSlider::valueChanged, + this, [this](float value) { + audio_->parameters()->set(ParamId::Osc1SemitoneOffset, value); + ui_->sliderOsc1Semitone->setResolution(); + }); + connect(ui_->sliderOsc1Pitch, &SmartSlider::valueChanged, + this, [this](float value) { + audio_->parameters()->set(ParamId::Osc1PitchOffset, value); + }); + + connect(ui_->sliderOsc2Octave, &SmartSlider::valueChanged, + this, [this](float value) { + audio_->parameters()->set(ParamId::Osc2OctaveOffset, value); + ui_->sliderOsc2Octave->setResolution(); + }); + connect(ui_->sliderOsc2Semitone, &SmartSlider::valueChanged, + this, [this](float value) { + audio_->parameters()->set(ParamId::Osc2SemitoneOffset, value); + ui_->sliderOsc2Semitone->setResolution(); + }); + connect(ui_->sliderOsc2Pitch, &SmartSlider::valueChanged, + this, [this](float value) { + audio_->parameters()->set(ParamId::Osc2PitchOffset, value); + }); + + connect(ui_->sliderOsc3Octave, &SmartSlider::valueChanged, + this, [this](float value) { + audio_->parameters()->set(ParamId::Osc3OctaveOffset, value); + ui_->sliderOsc3Octave->setResolution(); + }); + connect(ui_->sliderOsc3Semitone, &SmartSlider::valueChanged, + this, [this](float value) { + audio_->parameters()->set(ParamId::Osc3SemitoneOffset, value); + ui_->sliderOsc3Semitone->setResolution(); + }); + connect(ui_->sliderOsc3Pitch, &SmartSlider::valueChanged, + this, [this](float value) { + audio_->parameters()->set(ParamId::Osc3PitchOffset, value); + }); + // synth business audio_->start(); @@ -75,14 +136,60 @@ void MainWindow::onResetClicked() { // initialize to defaults - config_.loadProfile("default"); + YAML::Node configRoot = config_.loadProfile("default"); // update ui from the paramstore ui_->envelopeOsc1Volume->init(EnvelopeId::Osc1Volume, config_.loadEnvProfile("default", "Osc1Volume")); ui_->envelopeFilterCutoff->init(EnvelopeId::FilterCutoff, config_.loadEnvProfile("default", "FilterCutoff")); ui_->envelopeFilterResonance->init(EnvelopeId::FilterResonance, config_.loadEnvProfile("default", "FilterResonance")); - ui_->comboOsc1WaveSelector1->setCurrentIndex(static_cast(PARAM_DEFS[static_cast(ParamId::Osc1WaveSelector1)].def)); - ui_->comboOsc1WaveSelector2->setCurrentIndex(static_cast(PARAM_DEFS[static_cast(ParamId::Osc1WaveSelector2)].def)); + // 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()); + + 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_->sliderMasterPitch->setRange(configRoot["MasterPitchOffset"][1].as(), configRoot["MasterPitchOffset"][2].as()); + ui_->sliderMasterPitch->setValue(configRoot["MasterPitchOffset"][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_->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_->sliderOsc1Pitch->setRange(configRoot["Osc1PitchOffset"][1].as(), configRoot["Osc1PitchOffset"][2].as()); + ui_->sliderOsc1Pitch->setValue(configRoot["Osc1PitchOffset"][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_->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_->sliderOsc2Pitch->setRange(configRoot["Osc2PitchOffset"][1].as(), configRoot["Osc2PitchOffset"][2].as()); + ui_->sliderOsc2Pitch->setValue(configRoot["Osc2PitchOffset"][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_->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_->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()); } diff --git a/src/ui/MainWindow.ui b/src/ui/MainWindow.ui index b6f703b..2e344f4 100644 --- a/src/ui/MainWindow.ui +++ b/src/ui/MainWindow.ui @@ -6,8 +6,8 @@ 0 0 - 1102 - 564 + 1100 + 900 @@ -261,6 +261,602 @@ Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + + + + 340 + 880 + 211 + 20 + + + + Qt::Orientation::Horizontal + + + + + + 410 + 610 + 65 + 280 + + + + true + + + + + + 490 + 580 + 41 + 31 + + + + + 12 + + + + Pitch + + + Qt::AlignmentFlag::AlignCenter + + + + + + 340 + 610 + 65 + 280 + + + + true + + + + + + 410 + 580 + 71 + 31 + + + + + 12 + + + + Semitone + + + Qt::AlignmentFlag::AlignCenter + + + + + + 340 + 600 + 211 + 20 + + + + Qt::Orientation::Horizontal + + + + + + 480 + 610 + 65 + 280 + + + + true + + + + + + 340 + 580 + 71 + 31 + + + + + 12 + + + + Octave + + + Qt::AlignmentFlag::AlignCenter + + + + + + 630 + 580 + 71 + 31 + + + + + 12 + + + + Semitone + + + Qt::AlignmentFlag::AlignCenter + + + + + + 560 + 600 + 211 + 20 + + + + Qt::Orientation::Horizontal + + + + + + 560 + 580 + 71 + 31 + + + + + 12 + + + + Octave + + + Qt::AlignmentFlag::AlignCenter + + + + + + 560 + 880 + 211 + 20 + + + + Qt::Orientation::Horizontal + + + + + + 630 + 610 + 65 + 280 + + + + true + + + + + + 710 + 580 + 41 + 31 + + + + + 12 + + + + Pitch + + + Qt::AlignmentFlag::AlignCenter + + + + + + 560 + 610 + 65 + 280 + + + + true + + + + + + 700 + 610 + 65 + 280 + + + + true + + + + + + 850 + 610 + 65 + 280 + + + + true + + + + + + 920 + 610 + 65 + 280 + + + + true + + + + + + 780 + 580 + 71 + 31 + + + + + 12 + + + + Octave + + + Qt::AlignmentFlag::AlignCenter + + + + + + 780 + 610 + 65 + 280 + + + + true + + + + + + 930 + 580 + 41 + 31 + + + + + 12 + + + + Pitch + + + Qt::AlignmentFlag::AlignCenter + + + + + + 780 + 600 + 211 + 20 + + + + Qt::Orientation::Horizontal + + + + + + 780 + 880 + 211 + 20 + + + + Qt::Orientation::Horizontal + + + + + + 850 + 580 + 71 + 31 + + + + + 12 + + + + Semitone + + + Qt::AlignmentFlag::AlignCenter + + + + + + 390 + 567 + 111 + 20 + + + + + 12 + + + + Oscillator 1 + + + Qt::AlignmentFlag::AlignCenter + + + + + + 611 + 567 + 111 + 20 + + + + + 12 + + + + Oscillator 2 + + + Qt::AlignmentFlag::AlignCenter + + + + + + 830 + 569 + 111 + 20 + + + + + 12 + + + + Oscillator 3 + + + Qt::AlignmentFlag::AlignCenter + + + + + + 120 + 880 + 211 + 20 + + + + Qt::Orientation::Horizontal + + + + + + 170 + 567 + 111 + 20 + + + + + 12 + + + + Master + + + Qt::AlignmentFlag::AlignCenter + + + + + + 270 + 580 + 41 + 31 + + + + + 12 + + + + Pitch + + + Qt::AlignmentFlag::AlignCenter + + + + + + 120 + 610 + 65 + 280 + + + + true + + + + + + 260 + 610 + 65 + 280 + + + + true + + + + + + 120 + 600 + 211 + 20 + + + + Qt::Orientation::Horizontal + + + + + + 190 + 580 + 71 + 31 + + + + + 12 + + + + Semitone + + + Qt::AlignmentFlag::AlignCenter + + + + + + 190 + 610 + 65 + 280 + + + + true + + + + + + 120 + 580 + 71 + 31 + + + + + 12 + + + + Octave + + + Qt::AlignmentFlag::AlignCenter + + @@ -276,6 +872,12 @@
Scope/Scope.h
1 + + SmartSlider + QWidget +
SmartSlider/SmartSlider.h
+ 1 +
diff --git a/src/ui/widgets/EnvelopeGenerator/EnvelopeGenerator.cpp b/src/ui/widgets/EnvelopeGenerator/EnvelopeGenerator.cpp index 09e0f2d..2935cb9 100644 --- a/src/ui/widgets/EnvelopeGenerator/EnvelopeGenerator.cpp +++ b/src/ui/widgets/EnvelopeGenerator/EnvelopeGenerator.cpp @@ -75,24 +75,6 @@ void EnvelopeGenerator::emitEnvelope() { ); } -void EnvelopeGenerator::init(EnvelopeId id) { - - EnvelopeParam params = ENV_PARAMS[static_cast(id)]; - - ui_->sliderDepth->setRange(PARAM_DEFS[static_cast(params.depth)].min, PARAM_DEFS[static_cast(params.depth)].max); - ui_->sliderAttack->setRange(PARAM_DEFS[static_cast(params.a)].min, PARAM_DEFS[static_cast(params.a)].max); - ui_->sliderDecay->setRange(PARAM_DEFS[static_cast(params.d)].min, PARAM_DEFS[static_cast(params.d)].max); - ui_->sliderSustain->setRange(PARAM_DEFS[static_cast(params.s)].min, PARAM_DEFS[static_cast(params.s)].max); - ui_->sliderRelease->setRange(PARAM_DEFS[static_cast(params.r)].min, PARAM_DEFS[static_cast(params.r)].max); - - setDepth(PARAM_DEFS[static_cast(params.depth)].def); - setAttack(PARAM_DEFS[static_cast(params.a)].def); - setDecay(PARAM_DEFS[static_cast(params.d)].def); - setSustain(PARAM_DEFS[static_cast(params.s)].def); - setRelease(PARAM_DEFS[static_cast(params.r)].def); - -} - void EnvelopeGenerator::init(EnvelopeId id, std::array profile) { EnvelopeParam params = ENV_PARAMS[static_cast(id)]; diff --git a/src/ui/widgets/EnvelopeGenerator/EnvelopeGenerator.h b/src/ui/widgets/EnvelopeGenerator/EnvelopeGenerator.h index 935a76c..1f9af26 100644 --- a/src/ui/widgets/EnvelopeGenerator/EnvelopeGenerator.h +++ b/src/ui/widgets/EnvelopeGenerator/EnvelopeGenerator.h @@ -16,8 +16,7 @@ public: explicit EnvelopeGenerator(QWidget* parent = nullptr); ~EnvelopeGenerator(); - // connects signals, sets parameters to the defaults defined in paramStore - void init(EnvelopeId id); + // connects signals, sets parameters to a provided profile void init(EnvelopeId id, std::array profile); // setters diff --git a/src/ui/widgets/SmartSlider/SmartSlider.cpp b/src/ui/widgets/SmartSlider/SmartSlider.cpp index db20c5b..de9d436 100644 --- a/src/ui/widgets/SmartSlider/SmartSlider.cpp +++ b/src/ui/widgets/SmartSlider/SmartSlider.cpp @@ -42,6 +42,7 @@ void SmartSlider::setRange(float min, float max) { ui_->slider->setValue(sliderValue); ui_->spinValue->setValue(value); + } // sets value of the slider and the spinBox, called by other classes @@ -57,6 +58,10 @@ void SmartSlider::setValue(float value) { emit valueChanged(value); } +void SmartSlider::setResolution(int resolution) { + sliderResolution_ = resolution; +} + float SmartSlider::value() { return ui_->spinValue->value(); } diff --git a/src/ui/widgets/SmartSlider/SmartSlider.h b/src/ui/widgets/SmartSlider/SmartSlider.h index 5ef8f38..fe8877b 100644 --- a/src/ui/widgets/SmartSlider/SmartSlider.h +++ b/src/ui/widgets/SmartSlider/SmartSlider.h @@ -9,6 +9,7 @@ QT_END_NAMESPACE // SmartSlider is the widget including a slider, min/max settings, and a value setting parameter class SmartSlider : public QWidget { + Q_OBJECT public: @@ -18,6 +19,8 @@ public: // setters void setRange(float min, float max); void setValue(float value); + void setResolution(int resolution); + void setResolution() { setResolution(static_cast(max_ - min_)); } // getters float value(); diff --git a/src/ui/widgets/SmartSlider/SmartSlider.ui b/src/ui/widgets/SmartSlider/SmartSlider.ui index 1484e8b..ec6ceef 100644 --- a/src/ui/widgets/SmartSlider/SmartSlider.ui +++ b/src/ui/widgets/SmartSlider/SmartSlider.ui @@ -19,6 +19,9 @@ Form + + false + @@ -62,6 +65,12 @@ QAbstractSpinBox::ButtonSymbols::NoButtons + + -100000.000000000000000 + + + 100000.000000000000000 + @@ -84,8 +93,11 @@ QAbstractSpinBox::ButtonSymbols::NoButtons + + -100000.000000000000000 + - 40000.000000000000000 + 100000.000000000000000 @@ -117,8 +129,11 @@ QAbstractSpinBox::ButtonSymbols::NoButtons + + -100000.000000000000000 + - 80000.000000000000000 + 100000.000000000000000