15 Commits

28 changed files with 235 additions and 561 deletions

3
.gitignore vendored
View File

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

View File

@@ -1,37 +0,0 @@
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

View File

@@ -1,214 +0,0 @@
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);
}
}
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

View File

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

View File

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

View File

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

12
inc/Effect.h Normal file
View File

@@ -0,0 +1,12 @@
#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,36 +1,35 @@
#pragma once
#include "IEffect.h"
#include "Settings.h"
#include "Effect.h"
enum FilterType { LowPass, BandPass, HighPass };
enum FilterType {
LowPass,
BandPass,
HighPass
};
class Filter : public IEffect {
class Filter : public Effect {
protected:
// 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; };
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(){};
public:
Filter(/* args */);
virtual ~Filter();
void Trigger() override final;
void Release() override final;
void Trigger() override;
void Release() override;
float Process(float in);
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; };
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; };
};

View File

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

View File

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

View File

@@ -1,13 +0,0 @@
#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:
float GetSampleForFilterType() override;
void CalculateCoefficients() override;
public:
LowPassFilter();
@@ -13,4 +13,3 @@ class LowPassFilter : public Filter {
~LowPassFilter();
bool IsSameFilterType(FilterType type) override { return type == LowPass; };
};

View File

@@ -5,41 +5,31 @@
class Oscillator {
private:
OscillatorType m_osc;
float m_fine;
float m_key;
float m_freq;
float m_volume;
float m_phase;
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_osc_function)(void);
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 fine, float volume);
Oscillator(OscillatorType osc, float freq, 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 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;
}
}
float GetFreq() { return m_freq; }
void SetFreq(float freq);
void Reset();
float Process();
};

View File

@@ -3,34 +3,33 @@
#include "ADSR.h"
#include "Filter.h"
#include "Adder.h"
#include "IEffect.h"
#include "Effect.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<IEffect*> m_effects;
std::vector<Effect*> m_effects;
std::vector<float> m_out_signal;
LFO* m_lfo;
void ZeroSignal();
void GetNote();
void TriggerNoteOnEffects();
void UntriggerNoteOnEffects();
void ApplyEffects();
void AddOscillator();
void ApplyFilterLfo();
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();
public:
Synth(/* args */);
~Synth();
void Trigger(Note input);
void Process();
void Release();
void AddEffect(IEffect* fx);
void AddEffect(Effect* 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 fine;
float freq; // todo: remove or change to pitch shift
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::IsAttackElapsed() {
bool ADSR::is_attack_elapsed() {
return m_state == sAttack && m_ramp->IsCompleted();
}
bool ADSR::IsDecayElapsed() {
bool ADSR::is_decay_elapsed() {
return m_state == sDecay && m_ramp->IsCompleted();
}
bool ADSR::IsReleaseElapsed() {
bool ADSR::is_release_elapsed() {
return m_state == sRelease && m_ramp->IsCompleted();
}
void ADSR::RecheckState() {
void ADSR::recheck_state() {
switch (m_state) {
case sAttack:
if (IsAttackElapsed()) {
if (is_attack_elapsed()) {
m_state = sDecay;
m_ramp->RampTo(m_sustain_level, m_decay_time);
}
break;
case sDecay:
if (IsDecayElapsed()) {
if (is_decay_elapsed()) {
m_state = sSustain;
}
break;
case sRelease:
if (IsReleaseElapsed()) {
if (is_release_elapsed()) {
m_state = sOff;
}
break;
@@ -47,7 +47,7 @@ void ADSR::RecheckState() {
}
}
void ADSR::ProcessSample(float* sample) {
void ADSR::process_sample(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++) {
RecheckState();
ProcessSample(&samples[i]);
recheck_state();
process_sample(&samples[i]);
}
}

View File

@@ -43,7 +43,7 @@ void Application::InitSynth() {
assert(osc);
OscillatorGuiState* ui =
new OscillatorGuiState{.fine = osc->GetFine(),
new OscillatorGuiState{.freq = osc->GetFreq(),
.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,22 +1,23 @@
#include "BandPassFilter.h"
#include "Settings.h"
BandPassFilter::BandPassFilter() {
SetParameters(200, 0.1, 0.001);
BandPassFilter::BandPassFilter(/* args */) {}
BandPassFilter::BandPassFilter(Filter* filter) {
m_freq = filter->GetFreq();
m_q = filter->GetRes();
m_order = filter->GetPeakGain();
}
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(float freq, float res, float q) {}
BandPassFilter::~BandPassFilter() {}
float BandPassFilter::GetSampleForFilterType() {
return m_lowo;
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;
}

View File

@@ -1,50 +1,36 @@
#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]);
}
}
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));
void Filter::SetParameters(float freq, float res, float q) {
m_freq = freq / SAMPLE_RATE;
m_q = res;
m_order = q;
}

View File

@@ -1,22 +1,23 @@
#include "HighPassFilter.h"
#include "Settings.h"
HighPassFilter::HighPassFilter() {
SetParameters(200, 0.1, 0.001);
HighPassFilter::HighPassFilter(/* args */) {}
HighPassFilter::HighPassFilter(Filter* filter) {
m_freq = filter->GetFreq();
m_q = filter->GetRes();
m_order = filter->GetPeakGain();
}
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(float freq, float res, float q) {}
HighPassFilter::~HighPassFilter() {}
float HighPassFilter::GetSampleForFilterType() {
return m_higho;
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;
}

View File

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

View File

@@ -2,21 +2,32 @@
#include "Settings.h"
LowPassFilter::LowPassFilter() {
SetParameters(200, 0.1, 0.001);
// todo: defaults
m_freq = 200.f / SAMPLE_RATE;
m_q = 1.0f;//0.707f;
m_order = 0;
}
LowPassFilter::LowPassFilter(
Filter* filter) {
SetParameters(filter->GetFreq(), filter->GetRes(), filter->GetPeakGain());
LowPassFilter::LowPassFilter(float freq, float res, float q) {
m_freq = freq / SAMPLE_RATE;
m_q = res;
m_order = q;
}
LowPassFilter::LowPassFilter(float freq, float res,
float q) {
SetParameters(freq, res, q);
LowPassFilter::LowPassFilter(Filter* filter) {
m_freq = filter->GetFreq();
m_q = filter->GetRes();
m_order = filter->GetPeakGain();
}
LowPassFilter::~LowPassFilter() {}
float LowPassFilter::GetSampleForFilterType() {
return m_lowo;
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;
}

View File

@@ -1,15 +1,11 @@
#include "Oscillator.h"
#include "Settings.h"
#include "KeyBoard.h"
#include "Logger.h"
#define TWO_PI 2 * SYNTH_PI
Oscillator::Oscillator(OscillatorType osc, float fine, float volume) {
assert(fine >= -2.f && fine <= 2.f);
assert(volume >= 0.f && volume <= 1.f);
Oscillator::Oscillator(OscillatorType osc, float freq, float volume) {
SetType(osc);
m_fine = fine;
m_freq = freq;
m_volume = volume;
}
@@ -25,27 +21,26 @@ void Oscillator::SetType(OscillatorType osc) {
m_osc = osc;
switch (m_osc) {
case Sine:
m_osc_function = &Oscillator::SineOsc;
m_dt_function = &Oscillator::CalcSinePhaseDelta;
m_osc_function = &Oscillator::sineosc;
m_dt_function = &Oscillator::calc_sine_phase_delta;
break;
case Triangle:
m_osc_function = &Oscillator::TriangleOsc;
m_dt_function = &Oscillator::CalcSawPhaseDelta;
m_osc_function = &Oscillator::triangleosc;
m_dt_function = &Oscillator::calc_saw_phase_delta;
break;
case Square:
m_osc_function = &Oscillator::SquareOsc;
m_dt_function = &Oscillator::CalcSinePhaseDelta;
m_osc_function = &Oscillator::squareosc;
m_dt_function = &Oscillator::calc_sine_phase_delta;
break;
case Saw:
m_osc_function = &Oscillator::SawOsc;
m_dt_function = &Oscillator::CalcSawPhaseDelta;
m_osc_function = &Oscillator::sawosc;
m_dt_function = &Oscillator::calc_saw_phase_delta;
break;
}
}
void Oscillator::SetKey(float key) {
m_key = key;
float freq = KeyBoard::GetHzBySemitone(m_key + m_fine);
void Oscillator::SetFreq(float freq) {
m_freq = freq;
m_phase = 0;
m_phase_dt = (this->*m_dt_function)(freq);
}
@@ -54,44 +49,44 @@ float Oscillator::Process() {
return (this->*m_osc_function)() * m_volume;
}
void Oscillator::SineOscPhaseIncr() {
void Oscillator::sine_osc_phase_incr() {
m_phase += m_phase_dt;
if (m_phase >= TWO_PI)
m_phase -= TWO_PI;
}
void Oscillator::SawOscPhaseIncr() {
void Oscillator::saw_osc_phase_incr() {
m_phase += m_phase_dt;
if (m_phase >= 1.0f)
m_phase -= 1.0f;
}
float Oscillator::CalcSawPhaseDelta(float freq) {
float Oscillator::calc_saw_phase_delta(float freq) {
return freq / SAMPLE_RATE;
}
float Oscillator::CalcSinePhaseDelta(float freq) {
float Oscillator::calc_sine_phase_delta(float freq) {
return (TWO_PI * freq) / SAMPLE_RATE;
}
float Oscillator::SineOsc() {
float Oscillator::sineosc() {
float result = sinf(m_phase);
SineOscPhaseIncr();
sine_osc_phase_incr();
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;
SawOscPhaseIncr();
saw_osc_phase_incr();
return result;
}
float Oscillator::SawOsc() {
float Oscillator::sawosc() {
float result = m_phase * 2.f - 1.f;
SawOscPhaseIncr();
saw_osc_phase_incr();
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 ? 150 : 120;
const int osc_panel_height = has_shape_param ? 130 : 100;
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,23 +112,13 @@ 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));
el_rect.y += el_rect.height + el_spacing;
osc->SetVolume(ui_osc->volume);
// 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;
@@ -201,7 +191,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;
@@ -231,20 +221,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");
@@ -257,12 +247,7 @@ float Renderer::DrawFilterPanel(Synth& synth, FilterGuiState& gui_filter,
// apply values to real one
// todo: thrid (order) parameter
// todo: why resonance changing does not work?
// 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());
filter->SetParameters(gui_filter.freq, filter->GetRes(), filter->GetPeakGain());
return panel_y_offset;
}

View File

@@ -1,23 +1,21 @@
#include "Synth.h"
#include "FilterFactory.h"
#include "KeyBoard.h"
#include "Logger.h"
#include "OscillatorType.h"
#include "Settings.h"
#include "LowPassFilter.h"
#include "FilterFactory.h"
Synth::Synth(/* args */) {
m_lfo = new LFO();
m_lfo->SetFreq(5.0);
AddOscillator();
AddOscillator();
m_lfo = new Oscillator(OscillatorType::Sine, 5.f, VOLUME);
add_oscillator();
add_oscillator();
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);
}
ZeroSignal();
zero_signal();
}
Synth::~Synth() {
@@ -26,80 +24,80 @@ Synth::~Synth() {
m_out_signal.clear();
}
void Synth::ZeroSignal() {
void Synth::zero_signal() {
float sample = 0.0f;
for (size_t i = 0; i < STREAM_BUFFER_SIZE; i++) {
m_out_signal[i] = sample;
}
}
void Synth::GetNote() {
ZeroSignal();
void Synth::get_note() {
zero_signal();
Adder::SumOscillators(m_oscillators, 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::apply_effects() {
for (Effect* effect : m_effects) {
effect->Process(m_out_signal);
}
}
void Synth::TriggerNoteOnEffects() {
for (IEffect* effect : m_effects) {
void Synth::trigger_note_on_effects() {
for (Effect* effect : m_effects) {
effect->Trigger();
}
}
void Synth::UntriggerNoteOnEffects() {
for (IEffect* effect : m_effects) {
void Synth::untrigger_note_on_effects() {
for (Effect* effect : m_effects) {
effect->Release();
}
}
void Synth::AddOscillator() {
m_oscillators.push_back(new Oscillator(OscillatorType::Sine, 0.0f, VOLUME));
void Synth::add_oscillator() {
m_oscillators.push_back(
new Oscillator(OscillatorType::Sine, 440.f, 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->SetKey(semitone_shift);
osc->SetFreq(hz);
}
is_note_triggered = true;
TriggerNoteOnEffects();
trigger_note_on_effects();
}
void Synth::ApplyFilterLfo() {
// todo: fix this
void Synth::apply_filter_lfo() {
float dt = m_lfo->Process();
Filter* filter = (Filter*)m_effects[1];
float freq = filter->GetFreq();
filter->SetParameters(freq + dt, filter->GetRes(), filter->GetPeakGain());
//todo: check formula
//filter->SetParameters(freq + dt * 0.2f, filter->GetRes(), filter->GetPeakGain());
}
void Synth::Process() {
GetNote();
ApplyEffects();
//todo: on each sample.
//in order to do that, we need to move to per-sample processing
apply_filter_lfo();
get_note();
apply_effects();
}
void Synth::Release() {
ZeroSignal();
zero_signal();
is_note_triggered = false;
UntriggerNoteOnEffects();
untrigger_note_on_effects();
}
void Synth::AddEffect(IEffect* fx) { m_effects.push_back(fx); }
void Synth::AddEffect(Effect* 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;