diff --git a/CMakeLists.txt b/CMakeLists.txt index e5066d1..0ce08f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,10 @@ add_library(sonobulus_core STATIC src/synth/KeyboardController.cpp src/synth/MidiController.cpp src/synth/NoteQueue.cpp + src/synth/Scope.cpp + src/synth/Synth.cpp + src/synth/Voice.cpp + src/synth/Instrument.cpp ) target_link_libraries(sonobulus_core PRIVATE Qt6::Core diff --git a/src/synth/Instrument.cpp b/src/synth/Instrument.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/synth/Instrument.hpp b/src/synth/Instrument.hpp new file mode 100644 index 0000000..ed86b53 --- /dev/null +++ b/src/synth/Instrument.hpp @@ -0,0 +1,15 @@ + +#pragma once + +// an instrument is our state space model. it calculates discrete samples as a function of time, the current states, and the inputs +// an instrumeent is owned by a voice +class Instrument { + +public: + + Instrument() = default; + ~Instrument() = default; + +private: + +}; \ No newline at end of file diff --git a/src/synth/Scope.cpp b/src/synth/Scope.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/synth/Scope.hpp b/src/synth/Scope.hpp new file mode 100644 index 0000000..904fb74 --- /dev/null +++ b/src/synth/Scope.hpp @@ -0,0 +1,27 @@ + +#pragma once + +#include +#include + +class ScopeBuffer { + +public: + + ScopeBuffer(size_t size); + ~ScopeBuffer() = default; + + void push(float sample); + void read(std::vector& out); + +private: + + std::vector buffer_; + std::atomic writeIndex_{0}; + + size_t trigger_ = 0; + uint32_t wavelgnth = 400; + + bool spinLock_ = false; + +}; diff --git a/src/synth/Synth.cpp b/src/synth/Synth.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/synth/Synth.hpp b/src/synth/Synth.hpp new file mode 100644 index 0000000..686be07 --- /dev/null +++ b/src/synth/Synth.hpp @@ -0,0 +1,35 @@ + +#pragma once + +#include "ConfigService.hpp" +#include "LoggerService.hpp" +#include "NoteQueue.hpp" +#include "Voice.hpp" +#include "Scope.hpp" + +#include + +class Synth { + +public: + + Synth(ConfigService* config, LoggerService* logger, ScopeBuffer* scope); + ~Synth(); + + void process(float* out, size_t nFrames, uint32_t sampleRate); + void handleNoteEvent(const NoteEvent& event); + +private: + + Voice* findFreeVoice(); + Voice* findVoiceByNote(uint8_t note); + + std::vector sustainedNotes_; + + // voices + static constexpr size_t MAX_VOICES = 32; + std::array voices_; + + ScopeBuffer* scope_ = nullptr; + +} \ No newline at end of file diff --git a/src/synth/Voice.cpp b/src/synth/Voice.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/synth/Voice.hpp b/src/synth/Voice.hpp new file mode 100644 index 0000000..994e814 --- /dev/null +++ b/src/synth/Voice.hpp @@ -0,0 +1,36 @@ + +#pragma once + +#include + +#include "Instrument.hpp" + +// a voice is a tone generator that the synth uses for polyphony +// the synth mixes multiple voices together into a polyphonic audio. calculations for samples are handled in the instrument +class Voice { + +public: + + Voice() = default; + ~Voice() = default; + + void noteOn(int8_t midiNote, float velocity); + void noteOff(); + bool isActive(); + + float process(bool& scopeTrigger); + uint8_t note() { return note_; } + +private: + + float sampleRate_ = 44100.0f; + + inline float noteToFrequency(uint8_t note); + + uint8_t note_ = 0; + float velocity_ = 1.0f; + bool active_ = false; + + Instrument* instrument; + +};