5 Commits

Author SHA1 Message Date
19c90b0434 wip: apply gui states 2024-01-19 01:55:51 +07:00
b996685fe6 feat: parse patch files from json 2024-01-18 01:56:00 +07:00
f0e2d98c12 feat: add stk link to explore in the future 2024-01-17 16:13:44 +07:00
83fe94b34d fix: remove unnecessary stuff 2024-01-17 11:53:19 +07:00
8fc7fbfd30 [feat]: Filter LFO (#23)
Reviewed-on: #23
Co-authored-by: HiveBeats <e1lama@protonmail.com>
Co-committed-by: HiveBeats <e1lama@protonmail.com>
2024-01-17 05:00:26 +03:00
22 changed files with 24971 additions and 62 deletions

15
.vscode/settings.json vendored
View File

@@ -64,7 +64,20 @@
"__string": "cpp",
"compare": "cpp",
"concepts": "cpp",
"numeric": "cpp"
"numeric": "cpp",
"__tree": "cpp",
"any": "cpp",
"deque": "cpp",
"forward_list": "cpp",
"fstream": "cpp",
"iomanip": "cpp",
"map": "cpp",
"shared_mutex": "cpp",
"span": "cpp",
"stack": "cpp",
"valarray": "cpp",
"ranges": "cpp",
"iostream": "cpp"
},
"FSharp.suggestGitignore": false,
}

View File

@@ -17,6 +17,10 @@ FetchContent_Declare(
FetchContent_MakeAvailable(raylib)
# Add nlohmann_json
# FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz)
# FetchContent_MakeAvailable(json)
# Adding our source files
file(GLOB_RECURSE PROJECT_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_LIST_DIR}/src/*.cpp") # Define PROJECT_SOURCES as a list of all source files
set(PROJECT_INCLUDE "${CMAKE_CURRENT_LIST_DIR}/inc/") # Define PROJECT_INCLUDE to be the path to the include directory of the project
@@ -31,6 +35,7 @@ set_target_properties(
target_sources(${PROJECT_NAME} PRIVATE ${PROJECT_SOURCES})
target_include_directories(${PROJECT_NAME} PRIVATE ${PROJECT_INCLUDE})
target_link_libraries(${PROJECT_NAME} PRIVATE raylib)
# target_link_libraries(${PROJECT_NAME} PRIVATE json)
# Setting ASSETS_PATH
target_compile_definitions(${PROJECT_NAME} PUBLIC ASSETS_PATH="${CMAKE_CURRENT_SOURCE_DIR}/assets/") # Set the asset path macro to the absolute path on the dev machine

View File

@@ -35,4 +35,7 @@ for (n = 0; n < totalSamples; n++) {
}
We can replace the sin function with any periodic function that returns an amplitude for a given phase angle. Thus, this small piece of code can be used to produce a very wide range of sounds. Functionally, it is the software equivalent of an oscillator, the basic building block of almost all synthesizers.
We can replace the sin function with any periodic function that returns an amplitude for a given phase angle. Thus, this small piece of code can be used to produce a very wide range of sounds. Functionally, it is the software equivalent of an oscillator, the basic building block of almost all synthesizers.
https://ccrma.stanford.edu/software/stk/index.html

View File

@@ -23,4 +23,5 @@ class Application {
Application(/* args */);
~Application();
void Run();
void ParsePatch(std::string file_path);
};

View File

@@ -1,17 +0,0 @@
#pragma once
#include "Filter.h"
class BandPassFilter : public Filter {
protected:
float GetSampleForFilterType() override;
public:
BandPassFilter();
BandPassFilter(Filter* filter);
BandPassFilter(float freq, float res, float q);
~BandPassFilter();
bool IsSameFilterType(FilterType type) override { return type == BandPass; };
};

View File

@@ -34,3 +34,39 @@ class Filter : public IEffect {
float GetPeakGain() { return m_drive; }
virtual bool IsSameFilterType(FilterType type) { return false; };
};
class BandPassFilter : public Filter {
protected:
float GetSampleForFilterType() override;
public:
BandPassFilter();
BandPassFilter(Filter* filter);
BandPassFilter(float freq, float res, float q);
~BandPassFilter();
bool IsSameFilterType(FilterType type) override { return type == BandPass; };
};
class HighPassFilter : public Filter {
protected:
float GetSampleForFilterType() override;
public:
HighPassFilter();
HighPassFilter(Filter* filter);
HighPassFilter(float freq, float res, float q);
~HighPassFilter();
bool IsSameFilterType(FilterType type) override { return type == HighPass; };
};
class LowPassFilter : public Filter {
protected:
float GetSampleForFilterType() override;
public:
LowPassFilter();
LowPassFilter(Filter* filter);
LowPassFilter(float freq, float res, float q);
~LowPassFilter();
bool IsSameFilterType(FilterType type) override { return type == LowPass; };
};

View File

@@ -1,8 +1,5 @@
#pragma once
#include "Filter.h"
#include "LowPassFilter.h"
#include "BandPassFilter.h"
#include "HighPassFilter.h"
struct FilterFactory {
static Filter* CreateFilter(Filter* old_filter, FilterType new_type) {

View File

@@ -1,14 +0,0 @@
#pragma once
#include "Filter.h"
class HighPassFilter : public Filter {
protected:
float GetSampleForFilterType() override;
public:
HighPassFilter();
HighPassFilter(Filter* filter);
HighPassFilter(float freq, float res, float q);
~HighPassFilter();
bool IsSameFilterType(FilterType type) override { return type == HighPass; };
};

View File

@@ -1,16 +0,0 @@
#pragma once
#include "Filter.h"
class LowPassFilter : public Filter {
protected:
float GetSampleForFilterType() override;
public:
LowPassFilter();
LowPassFilter(Filter* filter);
LowPassFilter(float freq, float res, float q);
~LowPassFilter();
bool IsSameFilterType(FilterType type) override { return type == LowPass; };
};

View File

@@ -29,6 +29,7 @@ class Oscillator {
~Oscillator();
OscillatorType GetType() { return m_osc; }
void SetType(OscillatorType osc);
void SetType(std::string const& osc_name);
float GetVolume() { return m_volume; }
void SetVolume(float volume) { m_volume = volume; }
float GetKey() { return m_key; }

View File

@@ -17,6 +17,7 @@ class Synth {
std::vector<IEffect*> m_effects;
std::vector<float> m_out_signal;
LFO* m_lfo;
float m_lfo_level;
void ZeroSignal();
void GetNote();
void TriggerNoteOnEffects();
@@ -36,5 +37,8 @@ class Synth {
const bool& GetIsNoteTriggered() { return is_note_triggered; }
ADSR* GetADSR() { return (ADSR*)m_effects[0]; }
Filter* GetFilter() { return (Filter*)m_effects[1]; }
LFO* GetLFO() { return m_lfo; }
const float& GetLFOLevel() { return m_lfo_level; }
void SetLFOLevel(float lvl) { assert(0.f >= lvl <= 1.f); m_lfo_level = lvl; }
void SetFilter(FilterType type);
};

View File

@@ -1,6 +1,6 @@
#pragma once
#include "OscillatorType.h"
#include "raygui.h"
#include "raysan/raygui.h"
#include <vector>
#include "Filter.h"

24765
inc/nlohmann/json.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,12 @@
#include "Application.h"
#include "Logger.h"
#include "Settings.h"
#include <cmath> // log10f
#include <fstream> // read file
#include <iostream> // log error
#include <nlohmann/json.hpp>
#include <string>
using json = nlohmann::json;
Application::Application(/* args */) {
InitSynth();
@@ -94,7 +99,7 @@ void Application::UpdateOnNoteInput() {
if (!m_synth.GetIsNoteTriggered()) {
m_synth.Trigger((*m_current_note));
}
//write_log("Note played: %s\n", m_current_note->name.c_str());
// write_log("Note played: %s\n", m_current_note->name.c_str());
} else if (is_note_up() && m_synth.GetIsNoteTriggered()) {
m_synth.Release();
}
@@ -118,3 +123,67 @@ void Application::Run() {
m_renderer.Draw(m_synth, m_synth_gui_state);
}
}
void Application::ParsePatch(std::string file_path) {
std::ifstream f(file_path);
if (!f.is_open()) {
std::cerr << "[ERR] failed to open " << file_path << '\n';
} else {
json data = json::parse(f);
auto oscillators = m_synth.GetOscillators();
for (int i = 0; i < oscillators.size(); i++) {
auto osc = oscillators[i];
auto gui_osc = m_synth_gui_state.oscillators[i];
std::string type =
data["Oscillators"][i]["Wave"].template get<std::string>();
float fine = data["Oscillators"][i]["Tune"].template get<float>();
float volume =
data["Oscillators"][i]["Volume"].template get<float>();
osc->SetType(type);
osc->SetFine(fine);
osc->SetVolume(volume);
gui_osc->waveshape = osc->GetType();
gui_osc->fine = fine;
gui_osc->volume = volume;
}
auto adsr = m_synth.GetADSR();
auto gui_adsr = m_synth_gui_state.adsr;
auto adsr_params = data["ADSR"];
float attack = adsr_params["Attack"].template get<float>();
float decay = adsr_params["Decay"].template get<float>();
float sustain = adsr_params["Sustain"].template get<float>();
float release = adsr_params["Release"].template get<float>();
adsr->SetParameters(attack, decay, sustain, release);
gui_adsr.attack = attack;
gui_adsr.decay = decay;
gui_adsr.sustain = sustain;
gui_adsr.release = release;
auto lfo = m_synth.GetLFO();
lfo->SetFreq(data["LFO"]["Freq"].template get<float>());
m_synth.SetLFOLevel(data["LFO"]["Level"].template get<float>());
auto filter_type =
(FilterType)data["Filter"]["Type"].template get<int>();
m_synth.SetFilter(filter_type);
auto filter = m_synth.GetFilter();
auto gui_filter = m_synth_gui_state.filter;
float filter_freq =
std::log10f(data["Filter"]["Cutoff"].template get<float>());
float filter_res = data["Filter"]["Res"].template get<float>();
float filter_drive = data["Filter"]["Drive"].template get<float>();
filter->SetParameters(filter_freq, filter_res, filter_drive);
gui_filter.freq = filter_freq;
gui_filter.type = filter_type;
}
}

View File

@@ -1,4 +1,4 @@
#include "BandPassFilter.h"
#include "Filter.h"
#include "Settings.h"
BandPassFilter::BandPassFilter() {

View File

@@ -1,4 +1,4 @@
#include "HighPassFilter.h"
#include "Filter.h"
#include "Settings.h"
HighPassFilter::HighPassFilter() {

View File

@@ -1,4 +1,4 @@
#include "LowPassFilter.h"
#include "Filter.h"
#include "Settings.h"
LowPassFilter::LowPassFilter() {

View File

@@ -43,6 +43,19 @@ void Oscillator::SetType(OscillatorType osc) {
}
}
void Oscillator::SetType(std::string const& osc_name) {
if (osc_name == "Sine")
m_osc = Sine;
else if (osc_name == "Triangle")
m_osc = Triangle;
else if (osc_name == "Saw")
m_osc = Saw;
else if (osc_name == "Square")
m_osc = Square;
SetType(m_osc);
}
void Oscillator::SetKey(float key) {
m_key = key;
float freq = KeyBoard::GetHzBySemitone(m_key + m_fine);

View File

@@ -7,7 +7,7 @@
#pragma clang diagnostic ignored "-Wunused-but-set-variable"
#pragma clang diagnostic ignored "-Wunused-variable"
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#include "raygui.h"
#include "raysan/raygui.h"
#pragma clang diagnostic pop
Renderer::Renderer(/* args */) {

View File

@@ -1,7 +1,57 @@
#include "Application.h"
#include <string_view>
#include <vector>
#include <string>
#include <iostream>
inline bool file_exists(const std::string& name) {
if (FILE *file = fopen(name.c_str(), "r")) {
fclose(file);
return true;
} else {
return false;
}
}
[[noreturn]] void print_help(char* const executable) {
std::cout << executable << " [options]\n" <<
"Options:\n" <<
" -h | --help Print this help\n" <<
" -p | --patch Path to a json file with a patch to apply\n" <<
" -v | --version Print a program version" << std::endl;
std::exit(0);
}
int main(int argc, char* argv[]) {
if (argc > 32) {
throw std::runtime_error("Too many input parameters!");
}
std::string patch_file_path;
for (int i = 0; i < argc; i++) {
auto arg = std::string(argv[i]);
if (arg == "-p" || arg == "--patch") {
patch_file_path = std::string(argv[i+1]);
if (patch_file_path.empty()) {
std::cerr << "no file path provided\n";
print_help(argv[0]);
}
else if (!file_exists(patch_file_path)) {
std::cerr << patch_file_path << ": no such file\n";
print_help(argv[0]);
}
}
else if (arg == "-h" || arg == "--help") {
print_help(argv[0]);
}
}
int main() {
Application* app = new Application();
if (!patch_file_path.empty()) {
app->ParsePatch(patch_file_path);
}
app->Run();
delete app;

View File

@@ -4,11 +4,10 @@
#include "Logger.h"
#include "OscillatorType.h"
#include "Settings.h"
#include "LowPassFilter.h"
Synth::Synth(/* args */) {
m_lfo = new LFO();
m_lfo->SetFreq(5.0);
m_lfo->SetFreq(2.0);
AddOscillator();
AddOscillator();
AddEffect(new ADSR());
@@ -80,7 +79,7 @@ void Synth::ApplyFilterLfo() {
float dt = m_lfo->Process();
Filter* filter = (Filter*)m_effects[1];
float freq = filter->GetFreq();
filter->SetParameters(freq + dt, filter->GetRes(), filter->GetPeakGain());
filter->SetParameters(freq + dt*0.5f, filter->GetRes(), filter->GetPeakGain());
}
void Synth::Process() {