Compare commits
13 Commits
feature/os
...
feature/ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
df7b886526
|
|||
|
54c4e540ac
|
|||
|
a0514bad98
|
|||
|
fd67e7b843
|
|||
|
de31b73673
|
|||
|
564955c911
|
|||
|
ef40eaf7ef
|
|||
|
d883bbbf12
|
|||
|
73aae9a490
|
|||
|
c6c2956ac0
|
|||
|
635de894ad
|
|||
|
c16447f30e
|
|||
|
c63db4fa07
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,5 +3,4 @@
|
|||||||
/Debug/
|
/Debug/
|
||||||
*.wav
|
*.wav
|
||||||
*.dSYM
|
*.dSYM
|
||||||
/lib
|
/lib
|
||||||
/build
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
where Fs is the sample rate and Fc is the filter’s corner frequency you want to set. The q coefficient is defined as
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
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 filter’s sample rate (and thereby double the filter’s 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
BIN
docs/image-1.png
Binary file not shown.
|
Before Width: | Height: | Size: 3.0 KiB |
BIN
docs/image.png
BIN
docs/image.png
Binary file not shown.
|
Before Width: | Height: | Size: 3.3 KiB |
@@ -1,19 +0,0 @@
|
|||||||
|
|
||||||
// signal -> adsr -> filter
|
|
||||||
// ^ ^ ^
|
|
||||||
// | | |
|
|
||||||
// can be modulated
|
|
||||||
// by lfo, or adsr
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
у каждого из них должна быть ручка для изменения состояния
|
|
||||||
на каждом семпле?????
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
багует сам алгоритм изменения частоты, либо алгоритм пересчета коэфициентов фильтра
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
35
inc/ADSR.h
35
inc/ADSR.h
@@ -1,30 +1,37 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "IEffect.h"
|
#include "Effect.h"
|
||||||
#include "Ramp.h"
|
#include "Ramp.h"
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
||||||
class ADSR : public IEffect {
|
struct ADSRParameters {
|
||||||
enum ADSRState { sOff, sAttack, sDecay, sSustain, sRelease };
|
float attack_time; // Attack time in seconds
|
||||||
|
float decay_time; // Decay time in seconds
|
||||||
|
float sustain_level; // Sustain level (0 to 1)
|
||||||
|
float release_time;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum ADSRState { Off, Attack, Decay, Sustain, Release };
|
||||||
|
|
||||||
|
class ADSR : public Effect {
|
||||||
private:
|
private:
|
||||||
float m_attack_time;
|
ADSRParameters m_parameters;
|
||||||
float m_decay_time;
|
|
||||||
float m_sustain_level;
|
|
||||||
float m_release_time;
|
|
||||||
ADSRState m_state;
|
ADSRState m_state;
|
||||||
Ramp* m_ramp;
|
Ramp* m_ramp;
|
||||||
|
|
||||||
void ProcessSample(float* sample);
|
void process_sample(float* sample);
|
||||||
bool IsAttackElapsed();
|
bool is_attack_elapsed();
|
||||||
bool IsDecayElapsed();
|
bool is_decay_elapsed();
|
||||||
bool IsReleaseElapsed();
|
bool is_release_elapsed();
|
||||||
void RecheckState();
|
void recheck_state();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ADSR(/* args */);
|
ADSR(/* args */);
|
||||||
|
ADSR(ADSRParameters param);
|
||||||
~ADSR();
|
~ADSR();
|
||||||
void Trigger() override;
|
void OnSetNote() override;
|
||||||
void Release() override;
|
void OnUnsetNote() override;
|
||||||
|
// void RetriggerState() override;
|
||||||
void Process(std::vector<float>& samples) override;
|
void Process(std::vector<float>& samples) override;
|
||||||
|
void Reset();
|
||||||
void SetParameters(float attack, float decay, float sustain, float release);
|
void SetParameters(float attack, float decay, float sustain, float release);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,12 +8,16 @@
|
|||||||
struct Adder {
|
struct Adder {
|
||||||
static void SumOscillators(const std::vector<Oscillator*>& oscillators,
|
static void SumOscillators(const std::vector<Oscillator*>& oscillators,
|
||||||
std::vector<float>& signal) {
|
std::vector<float>& signal) {
|
||||||
size_t sample_count = STREAM_BUFFER_SIZE;
|
size_t sample_count =
|
||||||
|
STREAM_BUFFER_SIZE; //(size_t)(1.f/FPS * SAMPLE_RATE);
|
||||||
|
|
||||||
|
// std::vector<float>* output = new std::vector<float>();
|
||||||
|
// output->reserve(sample_count);
|
||||||
|
|
||||||
for (size_t i = 0; i < sample_count; i++) {
|
for (size_t i = 0; i < sample_count; i++) {
|
||||||
float sample = 0.0f;
|
float sample = 0.0f;
|
||||||
for (Oscillator* osc : oscillators) {
|
for (Oscillator* osc : oscillators) {
|
||||||
sample += osc->Process();
|
sample += osc->GenerateSample(1.f);
|
||||||
}
|
}
|
||||||
|
|
||||||
signal[i] = sample;
|
signal[i] = sample;
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ class Application {
|
|||||||
int m_sound_played_count;
|
int m_sound_played_count;
|
||||||
Note* m_current_note;
|
Note* m_current_note;
|
||||||
Renderer m_renderer;
|
Renderer m_renderer;
|
||||||
bool DetectNotePressed(Note* note);
|
bool detect_note_pressed(Note* note);
|
||||||
void InitSynth();
|
void init_synth();
|
||||||
void InitAudio();
|
void init_audio();
|
||||||
void UpdateOnNoteInput();
|
void update_on_note_input();
|
||||||
void PlayBufferedAudio();
|
void play_buffered_audio();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Application(/* args */);
|
Application(/* args */);
|
||||||
|
|||||||
@@ -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; };
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
13
inc/Effect.h
Normal file
13
inc/Effect.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <vector>
|
||||||
|
class Effect {
|
||||||
|
private:
|
||||||
|
/* data */
|
||||||
|
public:
|
||||||
|
Effect(/* args */){};
|
||||||
|
~Effect(){};
|
||||||
|
virtual void OnSetNote(){};
|
||||||
|
virtual void OnUnsetNote(){};
|
||||||
|
// virtual void RetriggerState(){};
|
||||||
|
virtual void Process(std::vector<float>& samples){};
|
||||||
|
};
|
||||||
36
inc/Filter.h
36
inc/Filter.h
@@ -1,36 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "IEffect.h"
|
|
||||||
#include "Settings.h"
|
|
||||||
|
|
||||||
enum FilterType { LowPass, BandPass, HighPass };
|
|
||||||
|
|
||||||
class Filter : public IEffect {
|
|
||||||
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; };
|
|
||||||
|
|
||||||
public:
|
|
||||||
Filter(/* args */);
|
|
||||||
virtual ~Filter();
|
|
||||||
void Trigger() override final;
|
|
||||||
void Release() override final;
|
|
||||||
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; };
|
|
||||||
};
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
#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) {
|
|
||||||
Filter* new_filter;
|
|
||||||
switch (new_type) {
|
|
||||||
case LowPass:
|
|
||||||
new_filter = new LowPassFilter(old_filter);
|
|
||||||
break;
|
|
||||||
case BandPass:
|
|
||||||
new_filter = new BandPassFilter(old_filter);
|
|
||||||
break;
|
|
||||||
case HighPass:
|
|
||||||
new_filter = new HighPassFilter(old_filter);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return new_filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Filter* GetDefaultFilter() {
|
|
||||||
return new LowPassFilter();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -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; };
|
|
||||||
};
|
|
||||||
@@ -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;
|
|
||||||
};
|
|
||||||
@@ -7,7 +7,8 @@
|
|||||||
|
|
||||||
class KeyBoard {
|
class KeyBoard {
|
||||||
private:
|
private:
|
||||||
static int GetSemitoneShiftInternal(const char* root_note,
|
/* data */
|
||||||
|
static int get_semitone_shift_internal(const char* root_note,
|
||||||
char* target_note) {
|
char* target_note) {
|
||||||
const char* pitch_classes[12] = {"C", "C#", "D", "D#", "E", "F",
|
const char* pitch_classes[12] = {"C", "C#", "D", "D#", "E", "F",
|
||||||
"F#", "G", "G#", "A", "A#", "B"};
|
"F#", "G", "G#", "A", "A#", "B"};
|
||||||
@@ -54,9 +55,10 @@ class KeyBoard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
KeyBoard(/* args */);
|
||||||
|
~KeyBoard();
|
||||||
|
|
||||||
static float GetHzBySemitone(float semitone) {
|
static float GetHzBySemitone(int 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);
|
return PITCH_STANDARD * powf(powf(2.f, (1.f / 12.f)), semitone);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,9 +66,13 @@ class KeyBoard {
|
|||||||
char* target_note_cstr = new char[target_note.length() + 1];
|
char* target_note_cstr = new char[target_note.length() + 1];
|
||||||
strcpy(target_note_cstr, target_note.c_str());
|
strcpy(target_note_cstr, target_note.c_str());
|
||||||
|
|
||||||
int result = GetSemitoneShiftInternal("A4", target_note_cstr);
|
int result = get_semitone_shift_internal("A4", target_note_cstr);
|
||||||
|
|
||||||
delete[] target_note_cstr;
|
delete[] target_note_cstr;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
KeyBoard::KeyBoard(/* args */) {}
|
||||||
|
|
||||||
|
KeyBoard::~KeyBoard() {}
|
||||||
|
|||||||
13
inc/LFO.h
13
inc/LFO.h
@@ -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); }
|
|
||||||
};
|
|
||||||
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "cstdio"
|
#include "cstdio"
|
||||||
|
|
||||||
#define write_log(format, args...) \
|
#define write_log(format,args...) do { \
|
||||||
do { \
|
printf(format, ## args); \
|
||||||
printf(format, ##args); \
|
} while(0)
|
||||||
} while (0)
|
|
||||||
|
|||||||
@@ -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; };
|
|
||||||
};
|
|
||||||
|
|
||||||
@@ -5,41 +5,31 @@
|
|||||||
class Oscillator {
|
class Oscillator {
|
||||||
private:
|
private:
|
||||||
OscillatorType m_osc;
|
OscillatorType m_osc;
|
||||||
float m_fine;
|
float m_freq;
|
||||||
float m_key;
|
|
||||||
float m_volume;
|
float m_volume;
|
||||||
float m_phase;
|
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 m_phase_dt;
|
||||||
|
float (Oscillator::*m_osc_function)(void);
|
||||||
float (Oscillator::*m_dt_function)(float freq);
|
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:
|
public:
|
||||||
Oscillator(OscillatorType osc, float fine, float volume);
|
Oscillator(OscillatorType osc, float freq, float volume);
|
||||||
~Oscillator();
|
~Oscillator();
|
||||||
OscillatorType GetType() { return m_osc; }
|
OscillatorType GetType() { return m_osc; }
|
||||||
void SetType(OscillatorType osc);
|
void SetType(OscillatorType osc);
|
||||||
float GetVolume() { return m_volume; }
|
float GetVolume() { return m_volume; }
|
||||||
void SetVolume(float volume) { m_volume = volume; }
|
void SetVolume(float volume) { m_volume = volume; }
|
||||||
float GetKey() { return m_key; }
|
float GetFreq() { return m_freq; }
|
||||||
void SetKey(float key);
|
void SetFreq(float freq);
|
||||||
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();
|
void Reset();
|
||||||
float Process();
|
float GenerateSample(float duration);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "ADSR.h"
|
#include "ADSR.h"
|
||||||
#include "Filter.h"
|
|
||||||
#include "Synth.h"
|
#include "Synth.h"
|
||||||
#include "SynthGuiState.h"
|
#include "SynthGuiState.h"
|
||||||
#include "raylib.h"
|
#include "raylib.h"
|
||||||
@@ -9,6 +8,8 @@
|
|||||||
class Renderer {
|
class Renderer {
|
||||||
private:
|
private:
|
||||||
void draw_main_panel(const Rectangle& panel_bounds);
|
void draw_main_panel(const Rectangle& panel_bounds);
|
||||||
|
void draw_add_oscillator_button(Synth& synth, SynthGuiState& synth_gui,
|
||||||
|
Rectangle panel_bounds);
|
||||||
float draw_oscillators_panels(
|
float draw_oscillators_panels(
|
||||||
const std::vector<Oscillator*>& oscillators,
|
const std::vector<Oscillator*>& oscillators,
|
||||||
const std::vector<OscillatorGuiState*>& gui_oscillators,
|
const std::vector<OscillatorGuiState*>& gui_oscillators,
|
||||||
@@ -20,9 +21,6 @@ class Renderer {
|
|||||||
void draw_signal(Synth& synth, SynthGuiState& synth_gui);
|
void draw_signal(Synth& synth, SynthGuiState& synth_gui);
|
||||||
void draw_adsr_panel(ADSR* adsr, ADSRGuiState& gui_adsr,
|
void draw_adsr_panel(ADSR* adsr, ADSRGuiState& gui_adsr,
|
||||||
const Rectangle& panel_bounds, float panel_y_offset);
|
const Rectangle& panel_bounds, float panel_y_offset);
|
||||||
void draw_second_panel(Rectangle& bounds);
|
|
||||||
float DrawFilterPanel(Synth& synth, FilterGuiState& gui_filter,
|
|
||||||
const Rectangle& panel_bounds);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Renderer(/* args */);
|
Renderer(/* args */);
|
||||||
|
|||||||
33
inc/Synth.h
33
inc/Synth.h
@@ -1,40 +1,37 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ADSR.h"
|
#include "ADSR.h"
|
||||||
#include "Filter.h"
|
|
||||||
#include "Adder.h"
|
#include "Adder.h"
|
||||||
#include "IEffect.h"
|
#include "Effect.h"
|
||||||
#include "Note.h"
|
#include "Note.h"
|
||||||
#include "Oscillator.h"
|
#include "Oscillator.h"
|
||||||
#include "Settings.h"
|
#include "Settings.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "LFO.h"
|
|
||||||
|
|
||||||
class Synth {
|
class Synth {
|
||||||
private:
|
private:
|
||||||
bool is_note_triggered;
|
bool is_note_triggered;
|
||||||
std::vector<Oscillator*> m_oscillators;
|
std::vector<Oscillator*> m_oscillators;
|
||||||
std::vector<IEffect*> m_effects;
|
std::vector<Effect*> m_effects;
|
||||||
|
// OscillatorUI* ui_oscillators;
|
||||||
|
// Note m_current_note;
|
||||||
std::vector<float> m_out_signal;
|
std::vector<float> m_out_signal;
|
||||||
LFO* m_lfo;
|
void zero_signal();
|
||||||
void ZeroSignal();
|
void get_note();
|
||||||
void GetNote();
|
void trigger_note_on_effects();
|
||||||
void TriggerNoteOnEffects();
|
void untrigger_note_on_effects();
|
||||||
void UntriggerNoteOnEffects();
|
void apply_effects();
|
||||||
void ApplyEffects();
|
|
||||||
void AddOscillator();
|
|
||||||
void ApplyFilterLfo();
|
|
||||||
public:
|
public:
|
||||||
Synth(/* args */);
|
Synth(/* args */);
|
||||||
~Synth();
|
~Synth();
|
||||||
void Trigger(Note input);
|
void TriggerNote(Note input);
|
||||||
void Process();
|
void ProduceSound();
|
||||||
void Release();
|
void StopSound();
|
||||||
void AddEffect(IEffect* fx);
|
void AddOscillator();
|
||||||
|
void AddEffect(Effect* fx);
|
||||||
const std::vector<float>& GetOutSignal() { return m_out_signal; }
|
const std::vector<float>& GetOutSignal() { return m_out_signal; }
|
||||||
const std::vector<Oscillator*>& GetOscillators() { return m_oscillators; }
|
const std::vector<Oscillator*>& GetOscillators() { return m_oscillators; }
|
||||||
const bool& GetIsNoteTriggered() { return is_note_triggered; }
|
const bool& GetIsNoteTriggered() { return is_note_triggered; }
|
||||||
ADSR* GetADSR() { return (ADSR*)m_effects[0]; }
|
ADSR* GetADSR() { return (ADSR*)m_effects[0]; }
|
||||||
Filter* GetFilter() { return (Filter*)m_effects[1]; }
|
|
||||||
void SetFilter(FilterType type);
|
|
||||||
};
|
};
|
||||||
@@ -2,11 +2,10 @@
|
|||||||
#include "OscillatorType.h"
|
#include "OscillatorType.h"
|
||||||
#include "raygui.h"
|
#include "raygui.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "Filter.h"
|
|
||||||
|
|
||||||
struct OscillatorGuiState {
|
struct OscillatorGuiState {
|
||||||
float volume;
|
float volume;
|
||||||
float fine;
|
float freq; // todo: remove or change to pitch shift
|
||||||
OscillatorType waveshape;
|
OscillatorType waveshape;
|
||||||
bool is_dropdown_open;
|
bool is_dropdown_open;
|
||||||
Rectangle shape_dropdown_rect;
|
Rectangle shape_dropdown_rect;
|
||||||
@@ -19,15 +18,7 @@ struct ADSRGuiState {
|
|||||||
float release;
|
float release;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FilterGuiState {
|
|
||||||
float freq;
|
|
||||||
float res; //todo: res
|
|
||||||
FilterType type;
|
|
||||||
bool is_dropdown_open;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SynthGuiState {
|
struct SynthGuiState {
|
||||||
std::vector<OscillatorGuiState*> oscillators;
|
std::vector<OscillatorGuiState*> oscillators;
|
||||||
ADSRGuiState adsr;
|
ADSRGuiState adsr;
|
||||||
FilterGuiState filter;
|
|
||||||
};
|
};
|
||||||
89
src/ADSR.cpp
89
src/ADSR.cpp
@@ -3,43 +3,46 @@
|
|||||||
#include "Settings.h"
|
#include "Settings.h"
|
||||||
|
|
||||||
ADSR::ADSR(/* args */) {
|
ADSR::ADSR(/* args */) {
|
||||||
m_attack_time = 1.f;
|
m_parameters.attack_time = 1.f;
|
||||||
m_decay_time = 0.4f;
|
m_parameters.decay_time = 0.4f;
|
||||||
m_sustain_level = 0.6f;
|
m_parameters.sustain_level = 0.6f;
|
||||||
m_release_time = 0.8f;
|
m_parameters.release_time = 0.8f;
|
||||||
m_ramp = new Ramp(0, SAMPLE_RATE);
|
m_ramp = new Ramp(0, SAMPLE_RATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ADSR::ADSR(ADSRParameters param) { m_parameters = param; }
|
||||||
|
|
||||||
ADSR::~ADSR() { delete m_ramp; }
|
ADSR::~ADSR() { delete m_ramp; }
|
||||||
|
|
||||||
bool ADSR::IsAttackElapsed() {
|
bool ADSR::is_attack_elapsed() {
|
||||||
return m_state == sAttack && m_ramp->IsCompleted();
|
return m_state == Attack && m_ramp->IsCompleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ADSR::IsDecayElapsed() {
|
bool ADSR::is_decay_elapsed() {
|
||||||
return m_state == sDecay && m_ramp->IsCompleted();
|
return m_state == Decay && m_ramp->IsCompleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ADSR::IsReleaseElapsed() {
|
bool ADSR::is_release_elapsed() {
|
||||||
return m_state == sRelease && m_ramp->IsCompleted();
|
return m_state == Release && m_ramp->IsCompleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADSR::RecheckState() {
|
void ADSR::recheck_state() {
|
||||||
switch (m_state) {
|
switch (m_state) {
|
||||||
case sAttack:
|
case Attack:
|
||||||
if (IsAttackElapsed()) {
|
if (is_attack_elapsed()) {
|
||||||
m_state = sDecay;
|
m_state = Decay;
|
||||||
m_ramp->RampTo(m_sustain_level, m_decay_time);
|
m_ramp->RampTo(m_parameters.sustain_level,
|
||||||
|
m_parameters.decay_time);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case sDecay:
|
case Decay:
|
||||||
if (IsDecayElapsed()) {
|
if (is_decay_elapsed()) {
|
||||||
m_state = sSustain;
|
m_state = Sustain;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case sRelease:
|
case Release:
|
||||||
if (IsReleaseElapsed()) {
|
if (is_release_elapsed()) {
|
||||||
m_state = sOff;
|
m_state = Off;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -47,48 +50,48 @@ void ADSR::RecheckState() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADSR::ProcessSample(float* sample) {
|
void ADSR::process_sample(float* sample) {
|
||||||
if (m_state == sOff) {
|
if (m_state == Off) {
|
||||||
(*sample) = 0;
|
(*sample) = 0;
|
||||||
} else if (m_state == sAttack) {
|
} else if (m_state == Attack) {
|
||||||
(*sample) = (*sample) * m_ramp->Process();
|
(*sample) = (*sample) * m_ramp->Process();
|
||||||
} else if (m_state == sDecay) {
|
} else if (m_state == Decay) {
|
||||||
(*sample) = (*sample) * m_ramp->Process();
|
(*sample) = (*sample) * m_ramp->Process();
|
||||||
} else if (m_state == sSustain) {
|
} else if (m_state == Sustain) {
|
||||||
(*sample) = (*sample) * m_sustain_level;
|
(*sample) = (*sample) * m_parameters.sustain_level;
|
||||||
} else if (m_state == sRelease) {
|
} else if (m_state == Release) {
|
||||||
(*sample) = (*sample) * m_ramp->Process();
|
(*sample) = (*sample) * m_ramp->Process();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADSR::Trigger() {
|
void ADSR::OnSetNote() {
|
||||||
write_log("Set ADSR\n");
|
write_log("Set ADSR\n");
|
||||||
if (m_state == sOff) {
|
if (m_state == Off) {
|
||||||
m_state = sAttack;
|
m_state = Attack;
|
||||||
} else if (m_state == sRelease) {
|
} else if (m_state == Release) {
|
||||||
m_state = sAttack;
|
m_state = Attack;
|
||||||
};
|
};
|
||||||
|
|
||||||
m_ramp->RampTo(1, m_attack_time);
|
m_ramp->RampTo(1, m_parameters.attack_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADSR::Release() {
|
void ADSR::OnUnsetNote() {
|
||||||
write_log("Unset ADSR\n");
|
write_log("Unset ADSR\n");
|
||||||
m_state = sRelease;
|
m_state = Release;
|
||||||
m_ramp->RampTo(0, m_release_time);
|
m_ramp->RampTo(0, m_parameters.release_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADSR::Process(std::vector<float>& samples) {
|
void ADSR::Process(std::vector<float>& samples) {
|
||||||
for (std::size_t i = 0; i < samples.size(); i++) {
|
for (std::size_t i = 0; i < samples.size(); i++) {
|
||||||
RecheckState();
|
recheck_state();
|
||||||
ProcessSample(&samples[i]);
|
process_sample(&samples[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADSR::SetParameters(float attack, float decay, float sustain,
|
void ADSR::SetParameters(float attack, float decay, float sustain,
|
||||||
float release) {
|
float release) {
|
||||||
m_attack_time = attack;
|
m_parameters.attack_time = attack;
|
||||||
m_decay_time = decay;
|
m_parameters.decay_time = decay;
|
||||||
m_sustain_level = sustain;
|
m_parameters.sustain_level = sustain;
|
||||||
m_release_time = release;
|
m_parameters.release_time = release;
|
||||||
}
|
}
|
||||||
@@ -4,8 +4,8 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
Application::Application(/* args */) {
|
Application::Application(/* args */) {
|
||||||
InitSynth();
|
init_synth();
|
||||||
InitAudio();
|
init_audio();
|
||||||
}
|
}
|
||||||
|
|
||||||
Application::~Application() {
|
Application::~Application() {
|
||||||
@@ -19,7 +19,7 @@ Application::~Application() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::InitAudio() {
|
void Application::init_audio() {
|
||||||
m_sound_played_count = 0;
|
m_sound_played_count = 0;
|
||||||
|
|
||||||
InitAudioDevice();
|
InitAudioDevice();
|
||||||
@@ -31,11 +31,12 @@ void Application::InitAudio() {
|
|||||||
PlayAudioStream(m_synth_stream);
|
PlayAudioStream(m_synth_stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::InitSynth() {
|
void Application::init_synth() {
|
||||||
|
// todo: move that variables to Synth declaration
|
||||||
std::string* nameString = new std::string(std::string(new char[3]));
|
std::string* nameString = new std::string(std::string(new char[3]));
|
||||||
m_current_note = new Note{.length = 1, .name = (*nameString)};
|
m_current_note = new Note{.length = 1, .name = (*nameString)};
|
||||||
m_current_note->name.assign("G4");
|
m_current_note->name.assign("G4");
|
||||||
|
// todo: move somewhere in initialization
|
||||||
std::vector<Oscillator*> oscillators = m_synth.GetOscillators();
|
std::vector<Oscillator*> oscillators = m_synth.GetOscillators();
|
||||||
m_synth_gui_state.oscillators.reserve(oscillators.size());
|
m_synth_gui_state.oscillators.reserve(oscillators.size());
|
||||||
for (size_t i = 0; i < oscillators.size(); i++) {
|
for (size_t i = 0; i < oscillators.size(); i++) {
|
||||||
@@ -43,14 +44,14 @@ void Application::InitSynth() {
|
|||||||
assert(osc);
|
assert(osc);
|
||||||
|
|
||||||
OscillatorGuiState* ui =
|
OscillatorGuiState* ui =
|
||||||
new OscillatorGuiState{.fine = osc->GetFine(),
|
new OscillatorGuiState{.freq = osc->GetFreq(),
|
||||||
.waveshape = osc->GetType(),
|
.waveshape = osc->GetType(),
|
||||||
.volume = osc->GetVolume()};
|
.volume = osc->GetVolume()};
|
||||||
m_synth_gui_state.oscillators.push_back(ui);
|
m_synth_gui_state.oscillators.push_back(ui);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Application::DetectNotePressed(Note* note) {
|
bool Application::detect_note_pressed(Note* note) {
|
||||||
std::size_t is_pressed = 0;
|
std::size_t is_pressed = 0;
|
||||||
note->length = 8;
|
note->length = 8;
|
||||||
if (IsKeyDown(KEY_A)) {
|
if (IsKeyDown(KEY_A)) {
|
||||||
@@ -85,36 +86,46 @@ bool Application::DetectNotePressed(Note* note) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool is_note_up() {
|
bool is_note_up() {
|
||||||
return IsKeyUp(KEY_A) || IsKeyUp(KEY_B) || IsKeyUp(KEY_C) ||
|
return IsKeyUp(KEY_A) || IsKeyUp(KEY_B) ||
|
||||||
IsKeyUp(KEY_D) || IsKeyUp(KEY_E) || IsKeyUp(KEY_F) || IsKeyUp(KEY_G);
|
IsKeyUp(KEY_C) || IsKeyUp(KEY_D) ||
|
||||||
|
IsKeyUp(KEY_E) || IsKeyUp(KEY_F) || IsKeyUp(KEY_G);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::UpdateOnNoteInput() {
|
// Update On Input
|
||||||
if (DetectNotePressed(m_current_note)) {
|
void Application::update_on_note_input() {
|
||||||
|
if (detect_note_pressed(m_current_note)) {
|
||||||
|
|
||||||
if (!m_synth.GetIsNoteTriggered()) {
|
if (!m_synth.GetIsNoteTriggered()) {
|
||||||
m_synth.Trigger((*m_current_note));
|
m_synth.TriggerNote((*m_current_note));
|
||||||
}
|
}
|
||||||
//write_log("Note played: %s\n", m_current_note->name.c_str());
|
|
||||||
|
// m_sound_played_count = 0;
|
||||||
|
write_log("Note played: %s\n", m_current_note->name.c_str());
|
||||||
} else if (is_note_up() && m_synth.GetIsNoteTriggered()) {
|
} else if (is_note_up() && m_synth.GetIsNoteTriggered()) {
|
||||||
m_synth.Release();
|
m_synth.StopSound();
|
||||||
}
|
}
|
||||||
// will produce 0 signal if ADSR is in off state
|
// will produce 0 signal if ADSR is in off state
|
||||||
m_synth.Process();
|
m_synth.ProduceSound();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Play ring-buffered audio
|
// Play ring-buffered audio
|
||||||
void Application::PlayBufferedAudio() {
|
void Application::play_buffered_audio() {
|
||||||
if (IsAudioStreamProcessed(m_synth_stream)) {
|
if (IsAudioStreamProcessed(m_synth_stream)) {
|
||||||
UpdateOnNoteInput();
|
// const float audio_frame_start_time = GetTime();
|
||||||
|
update_on_note_input();
|
||||||
UpdateAudioStream(m_synth_stream, m_synth.GetOutSignal().data(),
|
UpdateAudioStream(m_synth_stream, m_synth.GetOutSignal().data(),
|
||||||
STREAM_BUFFER_SIZE);
|
STREAM_BUFFER_SIZE);
|
||||||
|
// const float audio_freme_duration = GetTime() -
|
||||||
|
// audio_frame_start_time; write_log("Frame time: %.3f%% \n", 100.0f /
|
||||||
|
// ((1.0f / audio_freme_duration) /
|
||||||
|
// ((float)SAMPLE_RATE/STREAM_BUFFER_SIZE)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::Run() {
|
void Application::Run() {
|
||||||
// Main game loop
|
// Main game loop
|
||||||
while (!WindowShouldClose()) {
|
while (!WindowShouldClose()) {
|
||||||
PlayBufferedAudio();
|
play_buffered_audio();
|
||||||
m_renderer.Draw(m_synth, m_synth_gui_state);
|
m_renderer.Draw(m_synth, m_synth_gui_state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
#include "BandPassFilter.h"
|
|
||||||
#include "Settings.h"
|
|
||||||
|
|
||||||
BandPassFilter::BandPassFilter() {
|
|
||||||
SetParameters(200, 0.1, 0.001);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 BandPassFilter::GetSampleForFilterType() {
|
|
||||||
return m_lowo;
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
#include "Filter.h"
|
|
||||||
#include "Settings.h"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
Filter::Filter(/* args */) {}
|
|
||||||
|
|
||||||
Filter::~Filter() {}
|
|
||||||
|
|
||||||
void Filter::Trigger() {}
|
|
||||||
|
|
||||||
void Filter::Release() {}
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#include "HighPassFilter.h"
|
|
||||||
#include "Settings.h"
|
|
||||||
|
|
||||||
HighPassFilter::HighPassFilter() {
|
|
||||||
SetParameters(200, 0.1, 0.001);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 HighPassFilter::GetSampleForFilterType() {
|
|
||||||
return m_higho;
|
|
||||||
}
|
|
||||||
10
src/LFO.cpp
10
src/LFO.cpp
@@ -1,10 +0,0 @@
|
|||||||
#include "LFO.h"
|
|
||||||
|
|
||||||
|
|
||||||
LFO::LFO(/* args */): Oscillator(Sine, 0.f, 0.5f)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
LFO::~LFO()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#include "LowPassFilter.h"
|
|
||||||
#include "Settings.h"
|
|
||||||
|
|
||||||
LowPassFilter::LowPassFilter() {
|
|
||||||
SetParameters(200, 0.1, 0.001);
|
|
||||||
}
|
|
||||||
|
|
||||||
LowPassFilter::LowPassFilter(
|
|
||||||
Filter* filter) {
|
|
||||||
SetParameters(filter->GetFreq(), filter->GetRes(), filter->GetPeakGain());
|
|
||||||
}
|
|
||||||
|
|
||||||
LowPassFilter::LowPassFilter(float freq, float res,
|
|
||||||
float q) {
|
|
||||||
SetParameters(freq, res, q);
|
|
||||||
}
|
|
||||||
|
|
||||||
LowPassFilter::~LowPassFilter() {}
|
|
||||||
|
|
||||||
float LowPassFilter::GetSampleForFilterType() {
|
|
||||||
return m_lowo;
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,11 @@
|
|||||||
#include "Oscillator.h"
|
#include "Oscillator.h"
|
||||||
#include "Settings.h"
|
#include "Settings.h"
|
||||||
#include "KeyBoard.h"
|
|
||||||
#include "Logger.h"
|
|
||||||
|
|
||||||
#define TWO_PI 2 * SYNTH_PI
|
#define TWO_PI 2 * SYNTH_PI
|
||||||
|
|
||||||
Oscillator::Oscillator(OscillatorType osc, float fine, float volume) {
|
Oscillator::Oscillator(OscillatorType osc, float freq, float volume) {
|
||||||
assert(fine >= -2.f && fine <= 2.f);
|
|
||||||
assert(volume >= 0.f && volume <= 1.f);
|
|
||||||
SetType(osc);
|
SetType(osc);
|
||||||
m_fine = fine;
|
m_freq = freq;
|
||||||
m_volume = volume;
|
m_volume = volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,73 +21,72 @@ void Oscillator::SetType(OscillatorType osc) {
|
|||||||
m_osc = osc;
|
m_osc = osc;
|
||||||
switch (m_osc) {
|
switch (m_osc) {
|
||||||
case Sine:
|
case Sine:
|
||||||
m_osc_function = &Oscillator::SineOsc;
|
m_osc_function = &Oscillator::sineosc;
|
||||||
m_dt_function = &Oscillator::CalcSinePhaseDelta;
|
m_dt_function = &Oscillator::calc_sine_phase_delta;
|
||||||
break;
|
break;
|
||||||
case Triangle:
|
case Triangle:
|
||||||
m_osc_function = &Oscillator::TriangleOsc;
|
m_osc_function = &Oscillator::triangleosc;
|
||||||
m_dt_function = &Oscillator::CalcSawPhaseDelta;
|
m_dt_function = &Oscillator::calc_saw_phase_delta;
|
||||||
break;
|
break;
|
||||||
case Square:
|
case Square:
|
||||||
m_osc_function = &Oscillator::SquareOsc;
|
m_osc_function = &Oscillator::squareosc;
|
||||||
m_dt_function = &Oscillator::CalcSinePhaseDelta;
|
m_dt_function = &Oscillator::calc_sine_phase_delta;
|
||||||
break;
|
break;
|
||||||
case Saw:
|
case Saw:
|
||||||
m_osc_function = &Oscillator::SawOsc;
|
m_osc_function = &Oscillator::sawosc;
|
||||||
m_dt_function = &Oscillator::CalcSawPhaseDelta;
|
m_dt_function = &Oscillator::calc_saw_phase_delta;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Oscillator::SetKey(float key) {
|
void Oscillator::SetFreq(float freq) {
|
||||||
m_key = key;
|
m_freq = freq;
|
||||||
float freq = KeyBoard::GetHzBySemitone(m_key + m_fine);
|
|
||||||
m_phase = 0;
|
m_phase = 0;
|
||||||
m_phase_dt = (this->*m_dt_function)(freq);
|
m_phase_dt = (this->*m_dt_function)(freq);
|
||||||
}
|
}
|
||||||
|
|
||||||
float Oscillator::Process() {
|
float Oscillator::GenerateSample(float duration) {
|
||||||
return (this->*m_osc_function)() * m_volume;
|
return (this->*m_osc_function)() * m_volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Oscillator::SineOscPhaseIncr() {
|
void Oscillator::sine_osc_phase_incr() {
|
||||||
m_phase += m_phase_dt;
|
m_phase += m_phase_dt;
|
||||||
if (m_phase >= TWO_PI)
|
if (m_phase >= TWO_PI)
|
||||||
m_phase -= TWO_PI;
|
m_phase -= TWO_PI;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Oscillator::SawOscPhaseIncr() {
|
void Oscillator::saw_osc_phase_incr() {
|
||||||
m_phase += m_phase_dt;
|
m_phase += m_phase_dt;
|
||||||
if (m_phase >= 1.0f)
|
if (m_phase >= 1.0f)
|
||||||
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;
|
return freq / SAMPLE_RATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
float Oscillator::CalcSinePhaseDelta(float freq) {
|
float Oscillator::calc_sine_phase_delta(float freq) {
|
||||||
return (TWO_PI * freq) / SAMPLE_RATE;
|
return (TWO_PI * freq) / SAMPLE_RATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
float Oscillator::SineOsc() {
|
float Oscillator::sineosc() {
|
||||||
float result = sinf(m_phase);
|
float result = sinf(m_phase);
|
||||||
SineOscPhaseIncr();
|
sine_osc_phase_incr();
|
||||||
return result;
|
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;
|
float result = 1.f - fabsf(m_phase - 0.5f) * 4.f;
|
||||||
SawOscPhaseIncr();
|
saw_osc_phase_incr();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
float Oscillator::SawOsc() {
|
float Oscillator::sawosc() {
|
||||||
float result = m_phase * 2.f - 1.f;
|
float result = m_phase * 2.f - 1.f;
|
||||||
SawOscPhaseIncr();
|
saw_osc_phase_incr();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
165
src/Renderer.cpp
165
src/Renderer.cpp
@@ -19,10 +19,13 @@ Renderer::~Renderer() {}
|
|||||||
|
|
||||||
void Renderer::Draw(Synth& synth, SynthGuiState& synth_gui) {
|
void Renderer::Draw(Synth& synth, SynthGuiState& synth_gui) {
|
||||||
BeginDrawing();
|
BeginDrawing();
|
||||||
ClearBackground(RAYWHITE);
|
|
||||||
|
|
||||||
|
ClearBackground(RAYWHITE);
|
||||||
|
// todo: implement renderer
|
||||||
draw_ui(synth, synth_gui);
|
draw_ui(synth, synth_gui);
|
||||||
draw_signal(synth, synth_gui);
|
draw_signal(synth, synth_gui);
|
||||||
|
// DrawText("Congrats! You created your first window!", 190, 200, 20,
|
||||||
|
// LIGHTGRAY); DrawFPS(0,0);
|
||||||
|
|
||||||
EndDrawing();
|
EndDrawing();
|
||||||
}
|
}
|
||||||
@@ -89,7 +92,7 @@ float Renderer::draw_oscillators_panels(
|
|||||||
|
|
||||||
// Draw Oscillator Panel
|
// Draw Oscillator Panel
|
||||||
const int osc_panel_width = panel_bounds.width - 20;
|
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_x = panel_bounds.x + 10;
|
||||||
const int osc_panel_y = panel_bounds.y + 50 + panel_y_offset;
|
const int osc_panel_y = panel_bounds.y + 50 + panel_y_offset;
|
||||||
panel_y_offset += osc_panel_height + 5;
|
panel_y_offset += osc_panel_height + 5;
|
||||||
@@ -112,23 +115,28 @@ float Renderer::draw_oscillators_panels(
|
|||||||
decibels =
|
decibels =
|
||||||
GuiSlider(el_rect, amp_slider_label, "", decibels, -60.0f, 0.0f);
|
GuiSlider(el_rect, amp_slider_label, "", decibels, -60.0f, 0.0f);
|
||||||
ui_osc->volume = powf(10.f, decibels * (1.f / 20.f));
|
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;
|
el_rect.y += el_rect.height + el_spacing;
|
||||||
|
|
||||||
// Defer shape drop-down box.
|
// Defer shape drop-down box.
|
||||||
ui_osc->shape_dropdown_rect = el_rect;
|
ui_osc->shape_dropdown_rect = el_rect;
|
||||||
el_rect.y += el_rect.height + el_spacing;
|
el_rect.y += el_rect.height + el_spacing;
|
||||||
|
|
||||||
// Apply values to real
|
Rectangle delete_button_rect = el_rect;
|
||||||
osc->SetVolume(ui_osc->volume);
|
delete_button_rect.x = osc_panel_x + 5;
|
||||||
osc->SetFine(ui_osc->fine);
|
delete_button_rect.y -= el_rect.height + el_spacing;
|
||||||
|
delete_button_rect.width = 30;
|
||||||
|
bool is_delete_button_pressed = GuiButton(delete_button_rect, "X");
|
||||||
|
if (is_delete_button_pressed) {
|
||||||
|
// memmove(
|
||||||
|
// synth->ui_oscillator + ui_osc_i,
|
||||||
|
// synth->ui_oscillator + ui_osc_i + 1,
|
||||||
|
// (synth->ui_oscillator_count - ui_osc_i) *
|
||||||
|
// sizeof(UiOscillator)
|
||||||
|
// );
|
||||||
|
// synth->ui_oscillator_count -= 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return panel_y_offset;
|
return panel_y_offset;
|
||||||
@@ -138,6 +146,47 @@ void Renderer::draw_main_panel(const Rectangle& panel_bounds) {
|
|||||||
GuiPanel(panel_bounds, "");
|
GuiPanel(panel_bounds, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Renderer::draw_add_oscillator_button(Synth& synth,
|
||||||
|
SynthGuiState& synth_gui,
|
||||||
|
Rectangle panel_bounds) {
|
||||||
|
//clang-format off
|
||||||
|
bool click_add_oscillator =
|
||||||
|
GuiButton((Rectangle){panel_bounds.x + 10, panel_bounds.y + 10,
|
||||||
|
panel_bounds.width - 20, 25.f},
|
||||||
|
"Add Oscillator");
|
||||||
|
//clang-format on
|
||||||
|
|
||||||
|
if (click_add_oscillator) {
|
||||||
|
synth.AddOscillator();
|
||||||
|
Oscillator* osc = synth.GetOscillators().back();
|
||||||
|
|
||||||
|
OscillatorGuiState* ui =
|
||||||
|
new OscillatorGuiState{.freq = osc->GetFreq(),
|
||||||
|
.waveshape = osc->GetType(),
|
||||||
|
.volume = osc->GetVolume()};
|
||||||
|
synth_gui.oscillators.push_back(ui);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::draw_ui(Synth& synth, SynthGuiState& synth_gui) {
|
||||||
|
Rectangle panel_bounds = {.x = 0,
|
||||||
|
.y = 0,
|
||||||
|
.width = OSCILLATOR_PANEL_WIDTH,
|
||||||
|
.height = WINDOW_HEIGHT};
|
||||||
|
draw_main_panel(panel_bounds);
|
||||||
|
draw_add_oscillator_button(synth, synth_gui, panel_bounds);
|
||||||
|
// Draw Oscillators
|
||||||
|
std::vector<Oscillator*> oscillators = synth.GetOscillators();
|
||||||
|
std::vector<OscillatorGuiState*> gui_oscillators = synth_gui.oscillators;
|
||||||
|
|
||||||
|
float panel_y_offset =
|
||||||
|
draw_oscillators_panels(oscillators, gui_oscillators, panel_bounds);
|
||||||
|
draw_oscillators_shape_inputs(oscillators, gui_oscillators);
|
||||||
|
|
||||||
|
draw_adsr_panel(synth.GetADSR(), synth_gui.adsr, panel_bounds,
|
||||||
|
panel_y_offset);
|
||||||
|
}
|
||||||
|
|
||||||
void Renderer::draw_adsr_panel(ADSR* adsr, ADSRGuiState& gui_adsr,
|
void Renderer::draw_adsr_panel(ADSR* adsr, ADSRGuiState& gui_adsr,
|
||||||
const Rectangle& panel_bounds,
|
const Rectangle& panel_bounds,
|
||||||
float panel_y_offset) {
|
float panel_y_offset) {
|
||||||
@@ -194,95 +243,3 @@ void Renderer::draw_adsr_panel(ADSR* adsr, ADSRGuiState& gui_adsr,
|
|||||||
adsr->SetParameters(gui_adsr.attack, gui_adsr.decay, gui_adsr.sustain,
|
adsr->SetParameters(gui_adsr.attack, gui_adsr.decay, gui_adsr.sustain,
|
||||||
gui_adsr.release);
|
gui_adsr.release);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::draw_second_panel(Rectangle& bounds) {
|
|
||||||
bounds.x += bounds.width;
|
|
||||||
GuiPanel(bounds, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
float Renderer::DrawFilterPanel(Synth& synth, FilterGuiState& gui_filter,
|
|
||||||
const Rectangle& panel_bounds) {
|
|
||||||
#define FILTER_TYPE_OPTIONS "LP;BP;HP"
|
|
||||||
Filter* filter = synth.GetFilter();
|
|
||||||
float panel_y_offset = 0;
|
|
||||||
|
|
||||||
// Draw Filter Panel
|
|
||||||
const int osc_panel_width = panel_bounds.width - 20;
|
|
||||||
const int osc_panel_height = 100;
|
|
||||||
const int osc_panel_x = panel_bounds.x + 10;
|
|
||||||
const int osc_panel_y = panel_bounds.y + 50;
|
|
||||||
panel_y_offset += osc_panel_height + 5;
|
|
||||||
GuiPanel((Rectangle){(float)osc_panel_x, (float)osc_panel_y,
|
|
||||||
(float)osc_panel_width, (float)osc_panel_height},
|
|
||||||
"Filter");
|
|
||||||
|
|
||||||
const float slider_padding = 50.f;
|
|
||||||
const float el_spacing = 5.f;
|
|
||||||
Rectangle el_rect = {.x = (float)osc_panel_x + slider_padding + 30,
|
|
||||||
.y = (float)osc_panel_y + 10,
|
|
||||||
.width = (float)osc_panel_width - (slider_padding * 2),
|
|
||||||
.height = 25.f};
|
|
||||||
|
|
||||||
// Frequency slider
|
|
||||||
float freq = log10f(gui_filter.freq);
|
|
||||||
char freq_slider_label[32];
|
|
||||||
snprintf(freq_slider_label, 10, "%.1f hz", powf(10.f, freq));
|
|
||||||
freq = GuiSlider(el_rect, freq_slider_label, "", freq, 0.f, 4.f);
|
|
||||||
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;
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
if (is_dropdown_click) {
|
|
||||||
write_log("Dropdown clicked!\n");
|
|
||||||
gui_filter.is_dropdown_open = !gui_filter.is_dropdown_open;
|
|
||||||
gui_filter.type = (FilterType)(shape_index);
|
|
||||||
// APPLY STATE TO REAL SYNTH
|
|
||||||
synth.SetFilter(gui_filter.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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());
|
|
||||||
|
|
||||||
return panel_y_offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Renderer::draw_ui(Synth& synth, SynthGuiState& synth_gui) {
|
|
||||||
Rectangle panel_bounds = {.x = 0,
|
|
||||||
.y = 0,
|
|
||||||
.width = OSCILLATOR_PANEL_WIDTH,
|
|
||||||
.height = WINDOW_HEIGHT};
|
|
||||||
draw_main_panel(panel_bounds);
|
|
||||||
|
|
||||||
std::vector<Oscillator*> oscillators = synth.GetOscillators();
|
|
||||||
std::vector<OscillatorGuiState*> gui_oscillators = synth_gui.oscillators;
|
|
||||||
|
|
||||||
float panel_y_offset =
|
|
||||||
draw_oscillators_panels(oscillators, gui_oscillators, panel_bounds);
|
|
||||||
draw_adsr_panel(synth.GetADSR(), synth_gui.adsr, panel_bounds,
|
|
||||||
panel_y_offset);
|
|
||||||
draw_oscillators_shape_inputs(oscillators, gui_oscillators);
|
|
||||||
|
|
||||||
draw_second_panel(panel_bounds);
|
|
||||||
DrawFilterPanel(synth, synth_gui.filter, panel_bounds);
|
|
||||||
}
|
|
||||||
|
|||||||
106
src/Synth.cpp
106
src/Synth.cpp
@@ -1,23 +1,18 @@
|
|||||||
#include "Synth.h"
|
#include "Synth.h"
|
||||||
#include "FilterFactory.h"
|
#include "ADSR.h"
|
||||||
#include "KeyBoard.h"
|
#include "KeyBoard.h"
|
||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
#include "OscillatorType.h"
|
#include "OscillatorType.h"
|
||||||
#include "Settings.h"
|
#include "Settings.h"
|
||||||
#include "LowPassFilter.h"
|
|
||||||
|
|
||||||
Synth::Synth(/* args */) {
|
Synth::Synth(/* args */) {
|
||||||
m_lfo = new LFO();
|
|
||||||
m_lfo->SetFreq(5.0);
|
|
||||||
AddOscillator();
|
|
||||||
AddOscillator();
|
AddOscillator();
|
||||||
AddEffect(new ADSR());
|
AddEffect(new ADSR());
|
||||||
AddEffect(FilterFactory::GetDefaultFilter());
|
|
||||||
for (size_t i = 0; i < STREAM_BUFFER_SIZE; i++) {
|
for (size_t i = 0; i < STREAM_BUFFER_SIZE; i++) {
|
||||||
float sample = 0.0f;
|
float sample = 0.0f;
|
||||||
m_out_signal.push_back(sample);
|
m_out_signal.push_back(sample);
|
||||||
}
|
}
|
||||||
ZeroSignal();
|
zero_signal();
|
||||||
}
|
}
|
||||||
|
|
||||||
Synth::~Synth() {
|
Synth::~Synth() {
|
||||||
@@ -26,82 +21,63 @@ Synth::~Synth() {
|
|||||||
m_out_signal.clear();
|
m_out_signal.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Synth::ZeroSignal() {
|
void Synth::zero_signal() {
|
||||||
float sample = 0.0f;
|
float sample = 0.0f;
|
||||||
for (size_t i = 0; i < STREAM_BUFFER_SIZE; i++) {
|
for (size_t i = 0; i < STREAM_BUFFER_SIZE; i++) {
|
||||||
m_out_signal[i] = sample;
|
m_out_signal[i] = sample;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Synth::GetNote() {
|
void Synth::get_note() {
|
||||||
ZeroSignal();
|
zero_signal();
|
||||||
Adder::SumOscillators(m_oscillators, m_out_signal);
|
Adder::SumOscillators(m_oscillators, m_out_signal);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Synth::ApplyEffects() {
|
void Synth::apply_effects() {
|
||||||
auto* adsr = m_effects[0];
|
for (Effect* effect : m_effects) {
|
||||||
adsr->Process(m_out_signal);
|
effect->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::TriggerNoteOnEffects() {
|
void Synth::trigger_note_on_effects() {
|
||||||
for (IEffect* effect : m_effects) {
|
for (Effect* effect : m_effects) {
|
||||||
effect->Trigger();
|
effect->OnSetNote();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Synth::UntriggerNoteOnEffects() {
|
void Synth::untrigger_note_on_effects() {
|
||||||
for (IEffect* effect : m_effects) {
|
for (Effect* effect : m_effects) {
|
||||||
effect->Release();
|
effect->OnUnsetNote();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Synth::TriggerNote(Note input) {
|
||||||
|
int semitone_shift = KeyBoard::GetSemitoneShift(input.name);
|
||||||
|
float hz = KeyBoard::GetHzBySemitone(semitone_shift);
|
||||||
|
|
||||||
|
// will change after oscillator starts to be more autonomous
|
||||||
|
for (Oscillator* osc : m_oscillators) {
|
||||||
|
osc->SetFreq(hz);
|
||||||
|
}
|
||||||
|
is_note_triggered = true;
|
||||||
|
trigger_note_on_effects();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synth::ProduceSound() {
|
||||||
|
get_note();
|
||||||
|
apply_effects();
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: rename to something like untrigger note
|
||||||
|
void Synth::StopSound() {
|
||||||
|
zero_signal();
|
||||||
|
is_note_triggered = false;
|
||||||
|
untrigger_note_on_effects();
|
||||||
|
}
|
||||||
|
|
||||||
void Synth::AddOscillator() {
|
void Synth::AddOscillator() {
|
||||||
m_oscillators.push_back(new Oscillator(OscillatorType::Sine, 0.0f, VOLUME));
|
m_oscillators.push_back(
|
||||||
|
new Oscillator(OscillatorType::Sine, 440.f, VOLUME));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Synth::Trigger(Note input) {
|
void Synth::AddEffect(Effect* fx) { m_effects.push_back(fx); }
|
||||||
int semitone_shift = KeyBoard::GetSemitoneShift(input.name);
|
|
||||||
|
|
||||||
for (Oscillator* osc : m_oscillators) {
|
|
||||||
osc->SetKey(semitone_shift);
|
|
||||||
}
|
|
||||||
is_note_triggered = true;
|
|
||||||
TriggerNoteOnEffects();
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
void Synth::Process() {
|
|
||||||
GetNote();
|
|
||||||
ApplyEffects();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Synth::Release() {
|
|
||||||
ZeroSignal();
|
|
||||||
is_note_triggered = false;
|
|
||||||
UntriggerNoteOnEffects();
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user