diff --git a/CMakeLists.txt b/CMakeLists.txt index d35e02a..426608f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,8 +57,11 @@ add_library(sonobulus_core STATIC src/synth/Synth.cpp src/synth/Voice.cpp src/synth/Filter.cpp + src/synth/WavetableLoader.cpp + src/synth/Envelope.cpp src/synth/instruments/Instrument.cpp src/synth/instruments/PianoString.cpp + src/synth/instruments/WavetableOscillator.cpp ) if (WIN32) diff --git a/src/synth/Envelope.cpp b/src/synth/Envelope.cpp new file mode 100644 index 0000000..1202478 --- /dev/null +++ b/src/synth/Envelope.cpp @@ -0,0 +1,24 @@ + +#include "Envelope.hpp" + +Envelope::Envelope() { + +} + +Envelope::Envelope(float sampleRate) { + +} + +void Envelope::noteOn() { + +} + +void Envelope::noteOff() { + +} + +float Envelope::process() { + + return 0.0f; + +} \ No newline at end of file diff --git a/src/synth/Envelope.hpp b/src/synth/Envelope.hpp new file mode 100644 index 0000000..0103b45 --- /dev/null +++ b/src/synth/Envelope.hpp @@ -0,0 +1,55 @@ + +#pragma once + +#define ENVELOPE_MIN_TIME 0.001f // 1 ms + +#include +#include + +class Envelope { + + enum class State { + Idle, + Attack, + Decay, + Sustain, + Release + }; + + public: + + Envelope(); // we shouldn't need services for this + Envelope(float sampleRate); + ~Envelope() = default; + + void setSampleRate(float sampleRate) { sampleRate_ = sampleRate; } + void setAttack(float seconds) { attack_ = std::max(seconds, ENVELOPE_MIN_TIME); } + void setDecay(float seconds) { decay_ = std::max(seconds, ENVELOPE_MIN_TIME); } + void setSustain(float level) { sustain_ = level; } + void setRelease(float seconds) { release_ = std::max(seconds, ENVELOPE_MIN_TIME); } + // values close to zero cause some popping sounds on noteOns/noteOffs + + State state() { return state_; } + + // note events + void noteOn(); // transitions from idle state to attack + void noteOff(); // transitions from sustain to release + + float process(); + + bool isActive() { return state_ != State::Idle; } + + private: + + State state_ = State::Idle; + + float sampleRate_ = 44100.0f; + + float attack_ = 0.0f; + float decay_ = 0.0f; + float sustain_ = 0.0f; + float release_ = 0.0f; + + float value_ = 0.0f; + +}; diff --git a/src/synth/Instruments/WavetableOscillator.cpp b/src/synth/Instruments/WavetableOscillator.cpp index e69de29..32dd39e 100644 --- a/src/synth/Instruments/WavetableOscillator.cpp +++ b/src/synth/Instruments/WavetableOscillator.cpp @@ -0,0 +1,30 @@ + +#include "WavetableOscillator.hpp" + + +WavetableOscillator::WavetableOscillator(ConfigService* config, LoggerService* logger, float sampleRate) { + +} + +void WavetableOscillator::noteOn(float frequency, float velocity) { + envelope_.noteOn(); // hmm we can scale this by velocity +} + +void WavetableOscillator::noteOff() { + envelope_.noteOff(); +} + +bool WavetableOscillator::isActive() { + return envelope_.isActive(); +} + +float WavetableOscillator::process(bool& scopeTrigger) { + // return wavetable_.process() * envelope_.process(); + // more like + return 0.0f; +} + +bool WavetableOscillator::loadWavetable(std::string filename) { + return false; +} + diff --git a/src/synth/Instruments/WavetableOscillator.hpp b/src/synth/Instruments/WavetableOscillator.hpp index 64b6761..19838bc 100644 --- a/src/synth/Instruments/WavetableOscillator.hpp +++ b/src/synth/Instruments/WavetableOscillator.hpp @@ -4,8 +4,9 @@ #include #include "synth/instruments/Instrument.hpp" +#include "synth/Envelope.hpp" -class PianoString : public Instrument { +class WavetableOscillator : public Instrument { public: @@ -24,6 +25,8 @@ private: bool loadWavetable(std::string filename); // TODO: load from conf file instead - std::vector wavetable; + std::vector wavetable_; + + Envelope envelope_; }; diff --git a/src/synth/WavetableLoader.cpp b/src/synth/WavetableLoader.cpp index e69de29..c59104d 100644 --- a/src/synth/WavetableLoader.cpp +++ b/src/synth/WavetableLoader.cpp @@ -0,0 +1,58 @@ + +#include "WavetableLoader.hpp" + +WavetableLoader::WavetableLoader(ConfigService* config, LoggerService* logger) : config_(config), logger_(logger) { + +} + +int32_t WavetableLoader::loadWavetables() { + + // find wavetable files + std::vector wavetableFiles; + for(std::filesystem::directory_entry entry : std::filesystem::directory_iterator(wavetableFilepath_)) { + if(std::filesystem::is_regular_file(entry.status())) { + wavetableFiles.push_back(entry.path()); // TODO: check this file to see if it is valid + } + } + wavetableCount_ = wavetableFiles.size(); + wavetables_.resize(wavetableCount_); + + // load from file to vector + for(size_t i = 0; i < wavetableCount_; i++) { + // logger->log(loading wavetable file...) + std::ifstream inputFile(wavetableFiles[i], std::ios::in | std::ios::binary); + if(!inputFile) // logger->log("error oppening file...") + inputFile.read(reinterpret_cast(wavetables_[i].data()), 2048 * sizeof(float)); + } + + // TODO: wavetable header should contain info like number of samples + + return wavetableCount_; + +} + +float WavetableLoader::sample(size_t wavetableIndex, float phase) { + + float sampleValue = 0.0f; + + if(phase >= 0.0f) { + while(phase >= 2.0f*kPi_) { + phase -= 2.0f*kPi_; + } + } else { + while(phase <= 0.0f) { + phase += 2.0f*kPi_; + } + } + + float scaledPhase = phase * static_cast(2048) / (2.0f*kPi_); + // TODO: I'd like the default domain of wavetables to be [0, 1) but it should also be loaded in from the wavetable file metadata + size_t index = static_cast(round(scaledPhase)); + if(index >= 2048) index = 2048 - 1; + + if(wavetableIndex >= wavetableCount_) { + wavetableIndex = wavetableCount_ - 1; + } + + return wavetables_[wavetableIndex][index]; +} diff --git a/src/synth/WavetableLoader.hpp b/src/synth/WavetableLoader.hpp index cd982a7..9ecf718 100644 --- a/src/synth/WavetableLoader.hpp +++ b/src/synth/WavetableLoader.hpp @@ -1,4 +1,40 @@ +#pragma once + // placeholder to handle loading wavetables from files // so that wavetables can be shared to each voice/instrument // only need to need to load once for x number of voices + +#include +#include +#include + +#include "config/ConfigService.hpp" +#include "LoggerService.hpp" + +class WavetableLoader { + + public: + + WavetableLoader(ConfigService* config, LoggerService* logger); + ~WavetableLoader() = default; + + float sample(size_t wavetableIndex, float phase); + + private: + + int32_t loadWavetables(); + + size_t wavetableCount_ = 0; + std::vector> wavetables_; + + // TODO: this will be configurable + + const std::filesystem::path wavetableFilepath_ = "./config/wavetables"; + + LoggerService* logger_; + ConfigService* config_; + + static constexpr float kPi_ = 3.14159265358979323846; + +}; diff --git a/ui/components/.gitkeep b/ui/components/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/ui/components/SmartSlider.qml b/ui/components/SmartSlider.qml deleted file mode 100644 index 095bdca..0000000 --- a/ui/components/SmartSlider.qml +++ /dev/null @@ -1,2 +0,0 @@ - -haiii :3