6 Commits

Author SHA1 Message Date
2cfc49bac3 feat: all filter types 2024-01-17 08:56:59 +07:00
29ceabca74 wip: State Variable Filter (FILTER LFO WORKS NOW!!!) 2024-01-17 08:56:58 +07:00
4fd0cb279d wip: moog filter mentioned 2024-01-17 08:56:58 +07:00
61c0d3b787 wip: state-variable filter doc 2024-01-17 08:56:58 +07:00
2b4e3cb573 [feat]: Oscillator fine-tune (#22)
closes #19

Reviewed-on: #22
2023-09-17 02:26:44 +03:00
bb3ccc296a [feat]: Filter (#18)
closes #16

Reviewed-on: #18
2023-09-10 00:56:47 +03:00
28 changed files with 561 additions and 235 deletions

3
.gitignore vendored
View File

@@ -3,4 +3,5 @@
/Debug/
*.wav
*.dSYM
/lib
/lib
/build

37
CMakeLists.txt Normal file
View File

@@ -0,0 +1,37 @@
cmake_minimum_required(VERSION 3.5)
project(SeeSynth)
#set(CMAKE_C_STANDARD 99)
# Adding Raylib
include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build the supplied examples
set(BUILD_GAMES OFF CACHE BOOL "" FORCE) # don't build the supplied example games
FetchContent_Declare(
raylib
GIT_REPOSITORY "https://github.com/raysan5/raylib.git"
GIT_TAG "4.5.0"
GIT_PROGRESS TRUE
)
FetchContent_MakeAvailable(raylib)
# 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
# Declaring our executable
add_executable(${PROJECT_NAME})
set_target_properties(
${PROJECT_NAME} PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON)
target_sources(${PROJECT_NAME} PRIVATE ${PROJECT_SOURCES})
target_include_directories(${PROJECT_NAME} PRIVATE ${PROJECT_INCLUDE})
target_link_libraries(${PROJECT_NAME} PRIVATE raylib)
# 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
#target_compile_definitions(${PROJECT_NAME} PUBLIC ASSETS_PATH="./assets") # Set the asset path macro in release mode to a relative path that assumes the assets folder is in the same directory as the game executable

214
docs/StateVariableFilter.md Normal file
View File

@@ -0,0 +1,214 @@
https://www.earlevel.com/main/2003/03/02/the-digital-state-variable-filter/
The frequency control coefficient, f, is defined as
![Alt text](image.png)
where Fs is the sample rate and Fc is the filters corner frequency you want to set. The q coefficient is defined as
![Alt text](image-1.png)
where Q normally ranges from 0.5 to inifinity (where the filter oscillates).
The main drawback of the digital state variable is that it becomes unstable at higher frequencies. It depends on the Q setting, but basically the upper bound of stability is about where f reaches 1, which is at one-sixth of the sample rate (8 kHz at 48 kHz). The only way around this is to oversample. A simple way to double the filters sample rate (and thereby double the filters frequency range) is to run the filter twice with the same input sample, and discard one output sample.
example with double-sampling
```
input = input buffer;
output = output buffer;
fs = sampling frequency;
fc = cutoff frequency normally something like:
440.0*pow(2.0, (midi_note - 69.0)/12.0);
res = resonance 0 to 1;
drive = internal distortion 0 to 0.1
freq = 2.0*sin(PI*MIN(0.25, fc/(fs*2))); // the fs*2 is because it's double sampled
damp = MIN(2.0*(1.0 - pow(res, 0.25)), MIN(2.0, 2.0/freq - freq*0.5));
notch = notch output
low = low pass output
high = high pass output
band = band pass output
peak = peaking output = low - high
--
double sampled svf loop:
for (i=0; i<numSamples; i++)
{
in = input[i];
notch = in - damp*band;
low = low + freq*band;
high = notch - low;
band = freq*high + band - drive*band*band*band;
out = 0.5*(notch or low or high or band or peak);
notch = in - damp*band;
low = low + freq*band;
high = notch - low;
band = freq*high + band - drive*band*band*band;
out += 0.5*(same out as above);
output[i] = out;
}
```
Also that could work as it's a voltage-controlled filter
https://www.musicdsp.org/en/latest/Filters/24-moog-vcf.html
```
//Init
cutoff = cutoff freq in Hz
fs = sampling frequency //(e.g. 44100Hz)
res = resonance [0 - 1] //(minimum - maximum)
f = 2 * cutoff / fs; //[0 - 1]
k = 3.6*f - 1.6*f*f -1; //(Empirical tunning)
p = (k+1)*0.5;
scale = e^((1-p)*1.386249;
r = res*scale;
y4 = output;
y1=y2=y3=y4=oldx=oldy1=oldy2=oldy3=0;
//Loop
//--Inverted feed back for corner peaking
x = input - r*y4;
//Four cascaded onepole filters (bilinear transform)
y1=x*p + oldx*p - k*y1;
y2=y1*p+oldy1*p - k*y2;
y3=y2*p+oldy2*p - k*y3;
y4=y3*p+oldy3*p - k*y4;
//Clipper band limited sigmoid
y4 = y4 - (y4^3)/6;
oldx = x;
oldy1 = y1;
oldy2 = y2;
oldy3 = y3;
```
```
#pragma once
namespace DistoCore
{
template<class T>
class MoogFilter
{
public:
MoogFilter();
~MoogFilter() {};
T getSampleRate() const { return sampleRate; }
void setSampleRate(T fs) { sampleRate = fs; calc(); }
T getResonance() const { return resonance; }
void setResonance(T filterRezo) { resonance = filterRezo; calc(); }
T getCutoff() const { return cutoff; }
T getCutoffHz() const { return cutoff * sampleRate * 0.5; }
void setCutoff(T filterCutoff) { cutoff = filterCutoff; calc(); }
void init();
void calc();
T process(T input);
// filter an input sample using normalized params
T filter(T input, T cutoff, T resonance);
protected:
// cutoff and resonance [0 - 1]
T cutoff;
T resonance;
T sampleRate;
T fs;
T y1,y2,y3,y4;
T oldx;
T oldy1,oldy2,oldy3;
T x;
T r;
T p;
T k;
};
/**
* Construct Moog-filter.
*/
template<class T>
MoogFilter<T>::MoogFilter()
: sampleRate(T(44100.0))
, cutoff(T(1.0))
, resonance(T(0.0))
{
init();
}
/**
* Initialize filter buffers.
*/
template<class T>
void MoogFilter<T>::init()
{
// initialize values
y1=y2=y3=y4=oldx=oldy1=oldy2=oldy3=T(0.0);
calc();
}
/**
* Calculate coefficients.
*/
template<class T>
void MoogFilter<T>::calc()
{
// TODO: replace with your constant
const double kPi = 3.1415926535897931;
// empirical tuning
p = cutoff * (T(1.8) - T(0.8) * cutoff);
// k = p + p - T(1.0);
// A much better tuning seems to be:
k = T(2.0) * sin(cutoff * kPi * T(0.5)) - T(1.0);
T t1 = (T(1.0) - p) * T(1.386249);
T t2 = T(12.0) + t1 * t1;
r = resonance * (t2 + T(6.0) * t1) / (t2 - T(6.0) * t1);
};
/**
* Process single sample.
*/
template<class T>
T MoogFilter<T>::process(T input)
{
// process input
x = input - r * y4;
// four cascaded one-pole filters (bilinear transform)
y1 = x * p + oldx * p - k * y1;
y2 = y1 * p + oldy1 * p - k * y2;
y3 = y2 * p + oldy2 * p - k * y3;
y4 = y3 * p + oldy3 * p - k * y4;
// clipper band limited sigmoid
y4 -= (y4 * y4 * y4) / T(6.0);
oldx = x; oldy1 = y1; oldy2 = y2; oldy3 = y3;
return y4;
}
/**
* Filter single sample using specified params.
*/
template<class T>
T MoogFilter<T>::filter(T input, T filterCutoff, T filterRezo)
{
// set params first
cutoff = filterCutoff;
resonance = filterRezo;
calc();
return process(input);
}
}
```

BIN
docs/image-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
docs/image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

19
docs/matrix.md Normal file
View File

@@ -0,0 +1,19 @@
// signal -> adsr -> filter
// ^ ^ ^
// | | |
// can be modulated
// by lfo, or adsr
у каждого из них должна быть ручка для изменения состояния
на каждом семпле?????
багует сам алгоритм изменения частоты, либо алгоритм пересчета коэфициентов фильтра

View File

@@ -1,9 +1,9 @@
#pragma once
#include "Effect.h"
#include "IEffect.h"
#include "Ramp.h"
#include <cstddef>
class ADSR : public Effect {
class ADSR : public IEffect {
enum ADSRState { sOff, sAttack, sDecay, sSustain, sRelease };
private:
@@ -14,11 +14,11 @@ class ADSR : public Effect {
ADSRState m_state;
Ramp* m_ramp;
void process_sample(float* sample);
bool is_attack_elapsed();
bool is_decay_elapsed();
bool is_release_elapsed();
void recheck_state();
void ProcessSample(float* sample);
bool IsAttackElapsed();
bool IsDecayElapsed();
bool IsReleaseElapsed();
void RecheckState();
public:
ADSR(/* args */);

View File

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

View File

@@ -1,12 +0,0 @@
#pragma once
#include <vector>
class Effect {
private:
/* data */
public:
Effect(/* args */){};
~Effect(){};
virtual void Trigger(){};
virtual void Release(){};
virtual void Process(std::vector<float>& samples){};
};

View File

@@ -1,35 +1,36 @@
#pragma once
#include "Effect.h"
#include "IEffect.h"
#include "Settings.h"
enum FilterType {
LowPass,
BandPass,
HighPass
};
enum FilterType { LowPass, BandPass, HighPass };
class Filter : public Effect {
class Filter : public IEffect {
protected:
float m_freq; // cutoff frequency
float m_q; // filter quantity (resonance)
float m_order; // filter order (peakGain)
/* todo: filter adsr */
float m_norm, m_v, m_k;
float m_a0, m_a1, m_a2, m_b1, m_b2;
float m_z1, m_z2;
void CalculateNormals();
virtual void CalculateCoefficients(){};
// float* m_output; // output buffer
float m_fs = SAMPLE_RATE; // sampling frequency;
float m_fc; // cutoff frequency normally something like: 440.0*pow(2.0,
// (midi_note - 69.0)/12.0);
float m_res; // resonance 0 to 1;
float m_drive; // internal distortion 0 to 0.1
float m_freq;
float m_damp;
float m_notcho; // notch output
float m_lowo; // low pass output
float m_higho; // high pass output
float m_bando; // band pass output
float m_peako; // peaking output = low - high
virtual float GetSampleForFilterType(){ return 0.0; };
public:
Filter(/* args */);
virtual ~Filter();
void Trigger() override;
void Release() override;
void Trigger() override final;
void Release() override final;
float Process(float in);
void Process(std::vector<float>& samples) override;
void SetParameters(float freq, float res, float q);
float GetFreq() { return m_freq; }
float GetRes() { return m_q; }
float GetPeakGain() { return m_norm; }
virtual bool IsSameFilterType(FilterType type){ return false; };
void Process(std::vector<float>& samples) override final;
void SetParameters(float freq, float res, float drive);
float GetFreq() { return m_fc; }
float GetRes() { return m_res; }
float GetPeakGain() { return m_drive; }
virtual bool IsSameFilterType(FilterType type) { return false; };
};

View File

@@ -2,8 +2,8 @@
#include "Filter.h"
class HighPassFilter : public Filter {
private:
void CalculateCoefficients() override;
protected:
float GetSampleForFilterType() override;
public:
HighPassFilter();

10
inc/IEffect.h Normal file
View File

@@ -0,0 +1,10 @@
#pragma once
#include <vector>
class IEffect {
private:
/* data */
public:
virtual void Trigger() = 0;
virtual void Release() = 0;
virtual void Process(std::vector<float>& samples) = 0;
};

View File

@@ -54,10 +54,9 @@ class KeyBoard {
}
public:
KeyBoard(/* args */);
~KeyBoard();
static float GetHzBySemitone(int semitone) {
static float GetHzBySemitone(float semitone) {
//440 * Math.Pow(2, (note - 69) / 12.0) would it be better?
return PITCH_STANDARD * powf(powf(2.f, (1.f / 12.f)), semitone);
}
@@ -71,7 +70,3 @@ class KeyBoard {
return result;
}
};
KeyBoard::KeyBoard(/* args */) {}
KeyBoard::~KeyBoard() {}

13
inc/LFO.h Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
#include "Oscillator.h"
class LFO: public Oscillator
{
private:
/* data */
public:
LFO(/* args */);
~LFO();
void SetFreq(float freq) { m_phase_dt = (this->*m_dt_function)(freq); }
};

View File

@@ -4,7 +4,7 @@
class LowPassFilter : public Filter {
protected:
void CalculateCoefficients() override;
float GetSampleForFilterType() override;
public:
LowPassFilter();
@@ -13,3 +13,4 @@ class LowPassFilter : public Filter {
~LowPassFilter();
bool IsSameFilterType(FilterType type) override { return type == LowPass; };
};

View File

@@ -5,31 +5,41 @@
class Oscillator {
private:
OscillatorType m_osc;
float m_freq;
float m_fine;
float m_key;
float m_volume;
float m_phase;
float m_phase_dt;
float (Oscillator::*m_osc_function)(void);
void SineOscPhaseIncr();
void SawOscPhaseIncr();
float CalcSawPhaseDelta(float freq);
float CalcSinePhaseDelta(float freq);
float SawOsc();
float TriangleOsc();
float SquareOsc();
float Sign(float v);
float SineOsc();
protected:
float m_phase_dt;
float (Oscillator::*m_dt_function)(float freq);
void sine_osc_phase_incr();
void saw_osc_phase_incr();
float calc_saw_phase_delta(float freq);
float calc_sine_phase_delta(float freq);
float sawosc();
float triangleosc();
float squareosc();
float sign(float v);
float sineosc();
public:
Oscillator(OscillatorType osc, float freq, float volume);
Oscillator(OscillatorType osc, float fine, float volume);
~Oscillator();
OscillatorType GetType() { return m_osc; }
void SetType(OscillatorType osc);
float GetVolume() { return m_volume; }
void SetVolume(float volume) { m_volume = volume; }
float GetFreq() { return m_freq; }
void SetFreq(float freq);
float GetKey() { return m_key; }
void SetKey(float key);
float GetFine() { return m_fine; }
void SetFine(float fine) {
if (fine != m_fine) {
assert(fine >= -2.f && fine <= 2.f);
m_fine = fine;
}
}
void Reset();
float Process();
};

View File

@@ -3,33 +3,34 @@
#include "ADSR.h"
#include "Filter.h"
#include "Adder.h"
#include "Effect.h"
#include "IEffect.h"
#include "Note.h"
#include "Oscillator.h"
#include "Settings.h"
#include <vector>
#include "LFO.h"
class Synth {
private:
bool is_note_triggered;
std::vector<Oscillator*> m_oscillators;
std::vector<Effect*> m_effects;
std::vector<IEffect*> m_effects;
std::vector<float> m_out_signal;
Oscillator* m_lfo;
void zero_signal();
void get_note();
void trigger_note_on_effects();
void untrigger_note_on_effects();
void apply_effects();
void add_oscillator();
void apply_filter_lfo();
LFO* m_lfo;
void ZeroSignal();
void GetNote();
void TriggerNoteOnEffects();
void UntriggerNoteOnEffects();
void ApplyEffects();
void AddOscillator();
void ApplyFilterLfo();
public:
Synth(/* args */);
~Synth();
void Trigger(Note input);
void Process();
void Release();
void AddEffect(Effect* fx);
void AddEffect(IEffect* fx);
const std::vector<float>& GetOutSignal() { return m_out_signal; }
const std::vector<Oscillator*>& GetOscillators() { return m_oscillators; }
const bool& GetIsNoteTriggered() { return is_note_triggered; }

View File

@@ -6,7 +6,7 @@
struct OscillatorGuiState {
float volume;
float freq; // todo: remove or change to pitch shift
float fine;
OscillatorType waveshape;
bool is_dropdown_open;
Rectangle shape_dropdown_rect;

View File

@@ -12,33 +12,33 @@ ADSR::ADSR(/* args */) {
ADSR::~ADSR() { delete m_ramp; }
bool ADSR::is_attack_elapsed() {
bool ADSR::IsAttackElapsed() {
return m_state == sAttack && m_ramp->IsCompleted();
}
bool ADSR::is_decay_elapsed() {
bool ADSR::IsDecayElapsed() {
return m_state == sDecay && m_ramp->IsCompleted();
}
bool ADSR::is_release_elapsed() {
bool ADSR::IsReleaseElapsed() {
return m_state == sRelease && m_ramp->IsCompleted();
}
void ADSR::recheck_state() {
void ADSR::RecheckState() {
switch (m_state) {
case sAttack:
if (is_attack_elapsed()) {
if (IsAttackElapsed()) {
m_state = sDecay;
m_ramp->RampTo(m_sustain_level, m_decay_time);
}
break;
case sDecay:
if (is_decay_elapsed()) {
if (IsDecayElapsed()) {
m_state = sSustain;
}
break;
case sRelease:
if (is_release_elapsed()) {
if (IsReleaseElapsed()) {
m_state = sOff;
}
break;
@@ -47,7 +47,7 @@ void ADSR::recheck_state() {
}
}
void ADSR::process_sample(float* sample) {
void ADSR::ProcessSample(float* sample) {
if (m_state == sOff) {
(*sample) = 0;
} else if (m_state == sAttack) {
@@ -80,8 +80,8 @@ void ADSR::Release() {
void ADSR::Process(std::vector<float>& samples) {
for (std::size_t i = 0; i < samples.size(); i++) {
recheck_state();
process_sample(&samples[i]);
RecheckState();
ProcessSample(&samples[i]);
}
}

View File

@@ -43,7 +43,7 @@ void Application::InitSynth() {
assert(osc);
OscillatorGuiState* ui =
new OscillatorGuiState{.freq = osc->GetFreq(),
new OscillatorGuiState{.fine = osc->GetFine(),
.waveshape = osc->GetType(),
.volume = osc->GetVolume()};
m_synth_gui_state.oscillators.push_back(ui);
@@ -94,7 +94,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();
}

View File

@@ -1,23 +1,22 @@
#include "BandPassFilter.h"
#include "Settings.h"
BandPassFilter::BandPassFilter(/* args */) {}
BandPassFilter::BandPassFilter(Filter* filter) {
m_freq = filter->GetFreq();
m_q = filter->GetRes();
m_order = filter->GetPeakGain();
BandPassFilter::BandPassFilter() {
SetParameters(200, 0.1, 0.001);
}
BandPassFilter::BandPassFilter(float freq, float res, float q) {}
BandPassFilter::BandPassFilter(
Filter* filter) {
SetParameters(filter->GetFreq(), filter->GetRes(), filter->GetPeakGain());
}
BandPassFilter::BandPassFilter(float freq, float res,
float q) {
SetParameters(freq, res, q);
}
BandPassFilter::~BandPassFilter() {}
void BandPassFilter::CalculateCoefficients() {
CalculateNormals();
m_norm = 1 / (1 + m_k / m_q + m_k * m_k);
m_a0 = m_k / m_q * m_norm;
m_a1 = 0;
m_a2 = -m_a0;
m_b1 = 2 * (m_k * m_k - 1) * m_norm;
m_b2 = (1 - m_k / m_q + m_k * m_k) * m_norm;
float BandPassFilter::GetSampleForFilterType() {
return m_lowo;
}

View File

@@ -1,36 +1,50 @@
#include "Filter.h"
#include "Settings.h"
#include <algorithm>
#include <cmath>
Filter::Filter(/* args */) {}
Filter::~Filter() {}
void Filter::CalculateNormals() {
m_v = powf(10, fabs(m_order) / 20.0);
m_k = tanf(M_PI * m_freq);
}
void Filter::Trigger() {}
void Filter::Release() {}
float Filter::Process(float in) {
// may move to a compile-time dictionary calculation, if needed
CalculateCoefficients();
float out = in * m_a0 + m_z1;
m_z1 = in * m_a1 + m_z2 - m_b1 * out;
m_z2 = in * m_a2 - m_b2 * out;
return out;
}
void Filter::Process(std::vector<float>& samples) {
for (std::size_t i = 0; i < samples.size(); i++) {
samples[i] = Process(samples[i]);
}
}
void Filter::SetParameters(float freq, float res, float q) {
m_freq = freq / SAMPLE_RATE;
m_q = res;
m_order = q;
float Filter::Process(float in) {
m_notcho = in - m_damp * m_bando;
m_lowo = m_lowo + m_freq * m_bando;
m_higho = m_notcho - m_lowo;
m_bando =
m_freq * m_higho + m_bando - m_drive * m_bando * m_bando * m_bando;
// (m_notcho or m_lowo or m_higho or m_bando or m_peako)
float out = 0.5 * GetSampleForFilterType();
m_notcho = in - m_damp * m_bando;
m_lowo = m_lowo + m_freq * m_bando;
m_higho = m_notcho - m_lowo;
m_bando =
m_freq * m_higho + m_bando - m_drive * m_bando * m_bando * m_bando;
out += 0.5 * GetSampleForFilterType();
return out;
}
void Filter::SetParameters(float freq, float res, float drive) {
m_fc = freq;
m_res = res;
m_drive = drive;
// the fs*2 is because it's double sampled
m_freq =
2.0 * std::sinf(SYNTH_PI * std::min(0.25f, m_fc / (m_fs * 2)));
m_damp = std::min(2.0f * (1.0f - std::powf(m_res, 0.25f)),
std::min(2.0f, 2.0f / m_freq - m_freq * 0.5f));
}

View File

@@ -1,23 +1,22 @@
#include "HighPassFilter.h"
#include "Settings.h"
HighPassFilter::HighPassFilter(/* args */) {}
HighPassFilter::HighPassFilter(Filter* filter) {
m_freq = filter->GetFreq();
m_q = filter->GetRes();
m_order = filter->GetPeakGain();
HighPassFilter::HighPassFilter() {
SetParameters(200, 0.1, 0.001);
}
HighPassFilter::HighPassFilter(float freq, float res, float q) {}
HighPassFilter::HighPassFilter(
Filter* filter) {
SetParameters(filter->GetFreq(), filter->GetRes(), filter->GetPeakGain());
}
HighPassFilter::HighPassFilter(float freq, float res,
float q) {
SetParameters(freq, res, q);
}
HighPassFilter::~HighPassFilter() {}
void HighPassFilter::CalculateCoefficients() {
CalculateNormals();
m_norm = 1 / (1 + m_k / m_q + m_k * m_k);
m_a0 = 1 * m_norm;
m_a1 = -2 * m_a0;
m_a2 = m_a0;
m_b1 = 2 * (m_k * m_k - 1) * m_norm;
m_b2 = (1 - m_k / m_q + m_k * m_k) * m_norm;
float HighPassFilter::GetSampleForFilterType() {
return m_higho;
}

10
src/LFO.cpp Normal file
View File

@@ -0,0 +1,10 @@
#include "LFO.h"
LFO::LFO(/* args */): Oscillator(Sine, 0.f, 0.5f)
{
}
LFO::~LFO()
{
}

View File

@@ -2,32 +2,21 @@
#include "Settings.h"
LowPassFilter::LowPassFilter() {
// todo: defaults
m_freq = 200.f / SAMPLE_RATE;
m_q = 1.0f;//0.707f;
m_order = 0;
SetParameters(200, 0.1, 0.001);
}
LowPassFilter::LowPassFilter(float freq, float res, float q) {
m_freq = freq / SAMPLE_RATE;
m_q = res;
m_order = q;
LowPassFilter::LowPassFilter(
Filter* filter) {
SetParameters(filter->GetFreq(), filter->GetRes(), filter->GetPeakGain());
}
LowPassFilter::LowPassFilter(Filter* filter) {
m_freq = filter->GetFreq();
m_q = filter->GetRes();
m_order = filter->GetPeakGain();
LowPassFilter::LowPassFilter(float freq, float res,
float q) {
SetParameters(freq, res, q);
}
LowPassFilter::~LowPassFilter() {}
void LowPassFilter::CalculateCoefficients() {
CalculateNormals();
m_norm = 1 / (1 + m_k / m_q + m_k * m_k);
m_a0 = m_k * m_k * m_norm;
m_a1 = 2 * m_a0;
m_a2 = m_a0;
m_b1 = 2 * (m_k * m_k - 1) * m_norm;
m_b2 = (1 - m_k / m_q + m_k * m_k) * m_norm;
float LowPassFilter::GetSampleForFilterType() {
return m_lowo;
}

View File

@@ -1,11 +1,15 @@
#include "Oscillator.h"
#include "Settings.h"
#include "KeyBoard.h"
#include "Logger.h"
#define TWO_PI 2 * SYNTH_PI
Oscillator::Oscillator(OscillatorType osc, float freq, float volume) {
Oscillator::Oscillator(OscillatorType osc, float fine, float volume) {
assert(fine >= -2.f && fine <= 2.f);
assert(volume >= 0.f && volume <= 1.f);
SetType(osc);
m_freq = freq;
m_fine = fine;
m_volume = volume;
}
@@ -21,26 +25,27 @@ void Oscillator::SetType(OscillatorType osc) {
m_osc = osc;
switch (m_osc) {
case Sine:
m_osc_function = &Oscillator::sineosc;
m_dt_function = &Oscillator::calc_sine_phase_delta;
m_osc_function = &Oscillator::SineOsc;
m_dt_function = &Oscillator::CalcSinePhaseDelta;
break;
case Triangle:
m_osc_function = &Oscillator::triangleosc;
m_dt_function = &Oscillator::calc_saw_phase_delta;
m_osc_function = &Oscillator::TriangleOsc;
m_dt_function = &Oscillator::CalcSawPhaseDelta;
break;
case Square:
m_osc_function = &Oscillator::squareosc;
m_dt_function = &Oscillator::calc_sine_phase_delta;
m_osc_function = &Oscillator::SquareOsc;
m_dt_function = &Oscillator::CalcSinePhaseDelta;
break;
case Saw:
m_osc_function = &Oscillator::sawosc;
m_dt_function = &Oscillator::calc_saw_phase_delta;
m_osc_function = &Oscillator::SawOsc;
m_dt_function = &Oscillator::CalcSawPhaseDelta;
break;
}
}
void Oscillator::SetFreq(float freq) {
m_freq = freq;
void Oscillator::SetKey(float key) {
m_key = key;
float freq = KeyBoard::GetHzBySemitone(m_key + m_fine);
m_phase = 0;
m_phase_dt = (this->*m_dt_function)(freq);
}
@@ -49,44 +54,44 @@ float Oscillator::Process() {
return (this->*m_osc_function)() * m_volume;
}
void Oscillator::sine_osc_phase_incr() {
void Oscillator::SineOscPhaseIncr() {
m_phase += m_phase_dt;
if (m_phase >= TWO_PI)
m_phase -= TWO_PI;
}
void Oscillator::saw_osc_phase_incr() {
void Oscillator::SawOscPhaseIncr() {
m_phase += m_phase_dt;
if (m_phase >= 1.0f)
m_phase -= 1.0f;
}
float Oscillator::calc_saw_phase_delta(float freq) {
float Oscillator::CalcSawPhaseDelta(float freq) {
return freq / SAMPLE_RATE;
}
float Oscillator::calc_sine_phase_delta(float freq) {
float Oscillator::CalcSinePhaseDelta(float freq) {
return (TWO_PI * freq) / SAMPLE_RATE;
}
float Oscillator::sineosc() {
float Oscillator::SineOsc() {
float result = sinf(m_phase);
sine_osc_phase_incr();
SineOscPhaseIncr();
return result;
}
float Oscillator::sign(float v) { return (v > 0.0) ? 1.f : -1.f; }
float Oscillator::Sign(float v) { return (v > 0.0) ? 1.f : -1.f; }
float Oscillator::squareosc() { return sign(sineosc()); }
float Oscillator::SquareOsc() { return Sign(SineOsc()); }
float Oscillator::triangleosc() {
float Oscillator::TriangleOsc() {
float result = 1.f - fabsf(m_phase - 0.5f) * 4.f;
saw_osc_phase_incr();
SawOscPhaseIncr();
return result;
}
float Oscillator::sawosc() {
float Oscillator::SawOsc() {
float result = m_phase * 2.f - 1.f;
saw_osc_phase_incr();
SawOscPhaseIncr();
return result;
}

View File

@@ -89,7 +89,7 @@ float Renderer::draw_oscillators_panels(
// Draw Oscillator Panel
const int osc_panel_width = panel_bounds.width - 20;
const int osc_panel_height = has_shape_param ? 130 : 100;
const int osc_panel_height = has_shape_param ? 150 : 120;
const int osc_panel_x = panel_bounds.x + 10;
const int osc_panel_y = panel_bounds.y + 50 + panel_y_offset;
panel_y_offset += osc_panel_height + 5;
@@ -112,13 +112,23 @@ float Renderer::draw_oscillators_panels(
decibels =
GuiSlider(el_rect, amp_slider_label, "", decibels, -60.0f, 0.0f);
ui_osc->volume = powf(10.f, decibels * (1.f / 20.f));
osc->SetVolume(ui_osc->volume);
el_rect.y += el_rect.height + el_spacing;
// Fine slider
float fine = osc->GetFine();
char fine_slider_label[10];
snprintf(fine_slider_label, 9, "%.3f u", fine);
fine = GuiSlider(el_rect, fine_slider_label, "", fine, -2.f, 2.f);
ui_osc->fine = fine;
el_rect.y += el_rect.height + el_spacing;
// Defer shape drop-down box.
ui_osc->shape_dropdown_rect = el_rect;
el_rect.y += el_rect.height + el_spacing;
// Apply values to real
osc->SetVolume(ui_osc->volume);
osc->SetFine(ui_osc->fine);
}
return panel_y_offset;
@@ -191,7 +201,7 @@ void Renderer::draw_second_panel(Rectangle& bounds) {
}
float Renderer::DrawFilterPanel(Synth& synth, FilterGuiState& gui_filter,
const Rectangle& panel_bounds) {
const Rectangle& panel_bounds) {
#define FILTER_TYPE_OPTIONS "LP;BP;HP"
Filter* filter = synth.GetFilter();
float panel_y_offset = 0;
@@ -221,20 +231,20 @@ float Renderer::DrawFilterPanel(Synth& synth, FilterGuiState& gui_filter,
gui_filter.freq = powf(10.f, freq);
el_rect.y += el_rect.height + el_spacing;
//todo: implement that when Res will be fixed
// Resonance slider
// float res = gui_filter.res;
// char res_slider_label[32];
// snprintf(res_slider_label, 7, "%.1f u", res);
// res = GuiSlider(el_rect, res_slider_label, "", res, 0.0f, 1.0f);
// gui_filter.res = res;
// el_rect.y += el_rect.height + el_spacing;
// todo: implement that when Res will be fixed
// Resonance slider
// float res = gui_filter.res;
// char res_slider_label[32];
// snprintf(res_slider_label, 7, "%.1f u", res);
// res = GuiSlider(el_rect, res_slider_label, "", res, 0.0f, 1.0f);
// gui_filter.res = res;
// el_rect.y += el_rect.height + el_spacing;
// Shape select
int shape_index = (int)(gui_filter.type);
bool is_dropdown_click =
GuiDropdownBox(el_rect, FILTER_TYPE_OPTIONS,
&shape_index, gui_filter.is_dropdown_open);
GuiDropdownBox(el_rect, FILTER_TYPE_OPTIONS, &shape_index,
gui_filter.is_dropdown_open);
if (is_dropdown_click) {
write_log("Dropdown clicked!\n");
@@ -247,7 +257,12 @@ float Renderer::DrawFilterPanel(Synth& synth, FilterGuiState& gui_filter,
// apply values to real one
// todo: thrid (order) parameter
// todo: why resonance changing does not work?
filter->SetParameters(gui_filter.freq, filter->GetRes(), filter->GetPeakGain());
// todo: limit filter lowest frequency to ~40 hz
if (gui_filter.freq < 40.0) {
gui_filter.freq = 50.0;
}
filter->SetParameters(gui_filter.freq, filter->GetRes(),
filter->GetPeakGain());
return panel_y_offset;
}

View File

@@ -1,21 +1,23 @@
#include "Synth.h"
#include "FilterFactory.h"
#include "KeyBoard.h"
#include "Logger.h"
#include "OscillatorType.h"
#include "Settings.h"
#include "FilterFactory.h"
#include "LowPassFilter.h"
Synth::Synth(/* args */) {
m_lfo = new Oscillator(OscillatorType::Sine, 5.f, VOLUME);
add_oscillator();
add_oscillator();
m_lfo = new LFO();
m_lfo->SetFreq(5.0);
AddOscillator();
AddOscillator();
AddEffect(new ADSR());
AddEffect(FilterFactory::GetDefaultFilter());
for (size_t i = 0; i < STREAM_BUFFER_SIZE; i++) {
float sample = 0.0f;
m_out_signal.push_back(sample);
}
zero_signal();
ZeroSignal();
}
Synth::~Synth() {
@@ -24,80 +26,80 @@ Synth::~Synth() {
m_out_signal.clear();
}
void Synth::zero_signal() {
void Synth::ZeroSignal() {
float sample = 0.0f;
for (size_t i = 0; i < STREAM_BUFFER_SIZE; i++) {
m_out_signal[i] = sample;
}
}
void Synth::get_note() {
zero_signal();
void Synth::GetNote() {
ZeroSignal();
Adder::SumOscillators(m_oscillators, m_out_signal);
}
void Synth::apply_effects() {
for (Effect* effect : m_effects) {
effect->Process(m_out_signal);
void Synth::ApplyEffects() {
auto* adsr = m_effects[0];
adsr->Process(m_out_signal);
Filter* filter = (Filter*)m_effects[1];
for (std::size_t i = 0; i < m_out_signal.size(); i++) {
ApplyFilterLfo();
m_out_signal[i] = filter->Process(m_out_signal[i]);
}
}
void Synth::trigger_note_on_effects() {
for (Effect* effect : m_effects) {
void Synth::TriggerNoteOnEffects() {
for (IEffect* effect : m_effects) {
effect->Trigger();
}
}
void Synth::untrigger_note_on_effects() {
for (Effect* effect : m_effects) {
void Synth::UntriggerNoteOnEffects() {
for (IEffect* effect : m_effects) {
effect->Release();
}
}
void Synth::add_oscillator() {
m_oscillators.push_back(
new Oscillator(OscillatorType::Sine, 440.f, VOLUME));
void Synth::AddOscillator() {
m_oscillators.push_back(new Oscillator(OscillatorType::Sine, 0.0f, VOLUME));
}
void Synth::Trigger(Note input) {
int semitone_shift = KeyBoard::GetSemitoneShift(input.name);
float hz = KeyBoard::GetHzBySemitone(semitone_shift);
for (Oscillator* osc : m_oscillators) {
osc->SetFreq(hz);
osc->SetKey(semitone_shift);
}
is_note_triggered = true;
trigger_note_on_effects();
TriggerNoteOnEffects();
}
// todo: fix this
void Synth::apply_filter_lfo() {
void Synth::ApplyFilterLfo() {
float dt = m_lfo->Process();
Filter* filter = (Filter*)m_effects[1];
float freq = filter->GetFreq();
//todo: check formula
//filter->SetParameters(freq + dt * 0.2f, filter->GetRes(), filter->GetPeakGain());
filter->SetParameters(freq + dt, filter->GetRes(), filter->GetPeakGain());
}
void Synth::Process() {
//todo: on each sample.
//in order to do that, we need to move to per-sample processing
apply_filter_lfo();
get_note();
apply_effects();
GetNote();
ApplyEffects();
}
void Synth::Release() {
zero_signal();
ZeroSignal();
is_note_triggered = false;
untrigger_note_on_effects();
UntriggerNoteOnEffects();
}
void Synth::AddEffect(Effect* fx) { m_effects.push_back(fx); }
void Synth::AddEffect(IEffect* fx) { m_effects.push_back(fx); }
void Synth::SetFilter(FilterType type) {
Filter* old_filter = this->GetFilter();
if (!old_filter->IsSameFilterType(type)) {
// todo: implement other types of state variable filters;
Filter* new_filter = FilterFactory::CreateFilter(old_filter, type);
delete old_filter;
m_effects[1] = new_filter;