19 Commits

Author SHA1 Message Date
ff238c874b vscode settings 2023-08-08 23:05:23 +04:00
96a7c9a1b8 refactor: remove old c-code 2023-08-08 23:03:37 +04:00
ec01773ab1 feat: add oscillator button 2023-08-08 23:02:25 +04:00
5c485047fb feat: full rendering 2023-08-08 22:07:41 +04:00
a1fef25838 wip: gui 2023-08-08 17:05:08 +04:00
cadeeb323d feat: universal build script 2023-08-08 13:30:01 +04:00
891c747d11 fix: compiler script 2023-08-08 13:10:37 +04:00
7784119f85 fix: compilation warnings 2023-08-08 12:57:37 +04:00
d98e311d16 wip: project compiles 2023-08-08 12:51:37 +04:00
d565817d8f fix: oscillator 2023-08-08 00:06:23 +04:00
56b68bc963 wip: refactor out application methods 2023-08-07 15:01:35 +04:00
e3825b341d wip: application class 2023-08-07 13:27:25 +04:00
78c202a9d6 wip: logging 2023-08-07 12:05:06 +04:00
b02a5d2873 wip: templated ring buffer 2023-08-07 11:58:53 +04:00
64fa5c9271 wip: adder 2023-08-07 10:31:12 +04:00
6561666f7a fix: unitialized note struct 2023-08-07 02:32:34 +04:00
66c839e2ae wip: synth api && adder 2023-08-07 02:28:06 +04:00
d20dbd920f wip: synth class 2023-08-07 00:34:14 +04:00
850dedb319 wip: oscillator class 2023-08-06 23:33:51 +04:00
44 changed files with 535 additions and 1535 deletions

View File

@@ -1,216 +0,0 @@
---
Language: Cpp
# BasedOnStyle: LLVM
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveAssignments:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: true
AlignConsecutiveBitFields:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveDeclarations:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignConsecutiveMacros:
Enabled: false
AcrossEmptyLines: false
AcrossComments: false
AlignCompound: false
PadOperators: false
AlignEscapedNewlines: Right
AlignOperands: Align
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortEnumsOnASingleLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
AttributeMacros:
- __capability
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: Always
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
QualifierAlignment: Leave
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
PackConstructorInitializers: BinPack
BasedOnStyle: ''
ConstructorInitializerAllOnOneLineOrOnePerLine: false
AllowAllConstructorInitializersOnNextLine: true
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
CaseSensitive: false
- Regex: '.*'
Priority: 1
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseLabels: true
IndentCaseBlocks: false
IndentGotoLabels: true
IndentPPDirectives: None
IndentExternBlock: AfterExternBlock
IndentRequiresClause: true
IndentWidth: 4
IndentWrappedFunctionNames: false
InsertBraces: false
InsertTrailingCommas: None
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
LambdaBodyIndentation: Signature
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PenaltyIndentedWhitespace: 0
PointerAlignment: Left
PPIndentWidth: -1
ReferenceAlignment: Pointer
ReflowComments: true
RemoveBracesLLVM: false
RequiresClausePosition: OwnLine
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterForeachMacros: true
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: true
AfterOverloadedOperator: false
AfterRequiresInClause: false
AfterRequiresInExpression: false
BeforeNonEmptyParentheses: false
SpaceAroundPointerQualifiers: Default
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
BitFieldColonSpacing: Both
Standard: Latest
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseCRLF: false
UseTab: Never
WhitespaceSensitiveMacros:
- STRINGIZE
- PP_STRINGIZE
- BOOST_PP_STRINGIZE
- NS_SWIFT_NAME
- CF_SWIFT_NAME
...

1
.gitignore vendored
View File

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

16
.vscode/launch.json vendored
View File

@@ -1,16 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug",
"program": "${workspaceFolder}/bin/main",
"args": [],
"cwd": "${workspaceFolder}"
}
]
}

18
.vscode/tasks.json vendored
View File

@@ -3,12 +3,24 @@
{ {
"type": "cppbuild", "type": "cppbuild",
"label": "C/C++: clang сборка активного файла", "label": "C/C++: clang сборка активного файла",
"command": "sh", "command": "/usr/bin/clang",
"args": [ "args": [
"${workspaceFolder}/build.sh" "-fcolor-diagnostics",
"-fansi-escape-codes",
"-g",
"${file}",
"${fileDirname}/utils.c",
"${fileDirname}/ring_buffer.c",
"${fileDirname}/oscillator.c",
"${fileDirname}/parser.c",
"${fileDirname}/export.c",
"-lm",
"-lraylib",
"-o",
"${fileDirname}/bin/${fileBasenameNoExtension}"
], ],
"options": { "options": {
"cwd": "${workspaceFolder}" "cwd": "${fileDirname}"
}, },
"problemMatcher": [ "problemMatcher": [
"$gcc" "$gcc"

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,5 +1,5 @@
#!/bin/bash #!/bin/bash
CC="${CXX:-c++}" CC="${CXX:-c++}"
LL="-lm -lraylib" LL="-lm -lraylib"
FLAGS="-Wall -std=c++17 -I./inc/ -g" FLAGS="-Wall -std=c++17 -I./inc/"
$CC $FLAGS $(find ./src -type f -iregex ".*\.cpp") $LL -o ./bin/main $CC $FLAGS $(find ./src -type f -iregex ".*\.cpp") $LL -o ./bin/main

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 KiB

View File

@@ -26,7 +26,6 @@ release_samples = int(release_time * sample_rate)
# Attack phase # Attack phase
envelope[:attack_samples] = np.linspace(0, 1, num=attack_samples) envelope[:attack_samples] = np.linspace(0, 1, num=attack_samples)
# 1/n * count;
# Decay phase # Decay phase
decay_slope = (1 - sustain_level) / decay_samples decay_slope = (1 - sustain_level) / decay_samples

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

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,30 +0,0 @@
#pragma once
#include "IEffect.h"
#include "Ramp.h"
#include <cstddef>
class ADSR : public IEffect {
enum ADSRState { sOff, sAttack, sDecay, sSustain, sRelease };
private:
float m_attack_time;
float m_decay_time;
float m_sustain_level;
float m_release_time;
ADSRState m_state;
Ramp* m_ramp;
void ProcessSample(float* sample);
bool IsAttackElapsed();
bool IsDecayElapsed();
bool IsReleaseElapsed();
void RecheckState();
public:
ADSR(/* args */);
~ADSR();
void Trigger() override;
void Release() override;
void Process(std::vector<float>& samples) override;
void SetParameters(float attack, float decay, float sustain, float release);
};

View File

@@ -1,22 +1,30 @@
#pragma once #pragma once
#include <vector>
#include "Oscillator.h" #include "Oscillator.h"
#include "Settings.h" #include "Settings.h"
#include <numeric> #include <numeric>
#include <vector>
struct Adder { struct Adder
static void SumOscillators(const std::vector<Oscillator*>& oscillators, {
std::vector<float>& signal) { static std::vector<float> & SumOscillators(const std::vector<Oscillator*> & oscillators, float duration)
size_t sample_count = STREAM_BUFFER_SIZE; {
size_t sample_count = (size_t)(duration * SAMPLE_RATE);
for (size_t i = 0; i < sample_count; i++) { std::vector<float>* output = new std::vector<float>();
output->reserve(sample_count);
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(duration);
} }
signal[i] = sample; output->push_back(sample);
} }
return (*output);
} }
}; };

View File

@@ -1,26 +1,32 @@
#pragma once #pragma once
#include "Note.h" #include "Note.h"
#include "Renderer.h"
#include "Synth.h" #include "Synth.h"
#include "SynthGuiState.h"
#include "raylib.h" #include "raylib.h"
#include "RingBuffer.h"
#include "Renderer.h"
#include "SynthGuiState.h"
class Application { class Application
private: {
private:
Synth m_synth; Synth m_synth;
SynthGuiState m_synth_gui_state; SynthGuiState m_synth_gui_state;
RingBuffer<float>* m_ring_buffer;
AudioStream m_synth_stream; AudioStream m_synth_stream;
int m_sound_played_count; int m_sound_played_count;
float* m_temp_buffer;
Note* m_current_note; Note* m_current_note;
Renderer m_renderer; Renderer m_renderer;
bool DetectNotePressed(Note* note); std::size_t 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();
void fill_audio_buffer();
public: public:
Application(/* args */); Application(/* args */);
~Application(); ~Application();
void Run(); void Run();
}; };

View File

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

View File

@@ -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; };
};

View File

@@ -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();
}
};

View File

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

View File

@@ -1,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

@@ -1,22 +1,22 @@
#pragma once #pragma once
#include "Settings.h" #include "Settings.h"
#include <cmath> #include <cmath>
#include <cstdlib>
#include <cstring> #include <cstring>
#include <cstdlib>
#include <string> #include <string>
class KeyBoard { class KeyBoard
private: {
static int GetSemitoneShiftInternal(const char* root_note, private:
char* target_note) { /* data */
const char* pitch_classes[12] = {"C", "C#", "D", "D#", "E", "F", static int get_semitone_shift_internal(const char* root_note, char* target_note) {
"F#", "G", "G#", "A", "A#", "B"}; const char* pitch_classes[12] =
{ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" };
// Extract the note number and pitch class for the root note // Extract the note number and pitch class for the root note
int root_note_num = (int)root_note[strlen(root_note) - 1] - '0'; int root_note_num = (int)root_note[strlen(root_note) - 1] - '0';
char* root_pitch_class_str = char* root_pitch_class_str = (char*)malloc((strlen(root_note) - 1) * sizeof(char));
(char*)malloc((strlen(root_note) - 1) * sizeof(char));
strncpy(root_pitch_class_str, root_note, strlen(root_note) - 1); strncpy(root_pitch_class_str, root_note, strlen(root_note) - 1);
int root_pitch_class = -1; int root_pitch_class = -1;
@@ -50,13 +50,13 @@ class KeyBoard {
// Calculate the semitone shift using the formula // Calculate the semitone shift using the formula
return (target_note_num - root_note_num) * 12 + return (target_note_num - root_note_num) * 12 +
(target_pitch_class - root_pitch_class); (target_pitch_class - root_pitch_class);
} }
public:
KeyBoard(/* args */);
~KeyBoard();
public: static float GetHzBySemitone(int semitone) {
static float GetHzBySemitone(float semitone) {
//440 * Math.Pow(2, (note - 69) / 12.0) would it be better?
return PITCH_STANDARD * powf(powf(2.f, (1.f / 12.f)), semitone); return PITCH_STANDARD * powf(powf(2.f, (1.f / 12.f)), semitone);
} }
@@ -64,9 +64,17 @@ 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()
{
}

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

@@ -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)

View File

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

View File

@@ -1,45 +1,40 @@
#pragma once #pragma once
#include<vector>
#include "OscillatorType.h" #include "OscillatorType.h"
#include <vector>
class Oscillator {
private:
OscillatorType m_osc;
float m_fine;
float m_key;
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: class Oscillator
float m_phase_dt; {
float (Oscillator::*m_dt_function)(float freq); //typedef float (Oscillator::*OscFunction)(void);
//typedef float (Oscillator::*DtFunction)(float);
public: private:
Oscillator(OscillatorType osc, float fine, float volume); OscillatorType m_osc;
~Oscillator(); float m_freq;
OscillatorType GetType() { return m_osc; } float m_volume;
void SetType(OscillatorType osc); float m_phase;
float GetVolume() { return m_volume; } float m_phase_dt;
void SetVolume(float volume) { m_volume = volume; } //значение типа "float (Oscillator::*)()" нельзя присвоить сущности типа "float (*)()"
float GetKey() { return m_key; } float (Oscillator::*m_osc_function)(void);
void SetKey(float key); float (Oscillator::*m_dt_function)(float freq);
float GetFine() { return m_fine; } void sine_osc_phase_incr();
void SetFine(float fine) { void saw_osc_phase_incr();
if (fine != m_fine) { float calc_saw_phase_delta(float freq);
assert(fine >= -2.f && fine <= 2.f); float calc_sine_phase_delta(float freq);
m_fine = fine; float sawosc();
} float triangleosc();
} float squareosc();
void Reset(); float sign(float v);
float Process(); float sineosc();
public:
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 GetFreq() { return m_freq; }
void SetFreq(float freq);
void Reset();
float GenerateSample(float duration);
}; };

View File

@@ -1,2 +1,7 @@
#pragma once #pragma once
typedef enum { Sine, Triangle, Saw, Square } OscillatorType; typedef enum {
Sine,
Triangle,
Saw,
Square
} OscillatorType;

View File

@@ -1,16 +0,0 @@
#pragma once
class Ramp {
private:
float m_level;
float m_sample_rate;
float m_increment;
int m_counter;
public:
Ramp(float starting_level, float sample_rate);
~Ramp();
void RampTo(float value, float time);
float Process();
bool IsCompleted();
};

View File

@@ -1,31 +1,27 @@
#pragma once #pragma once
#include "ADSR.h"
#include "Filter.h"
#include "Synth.h" #include "Synth.h"
#include "SynthGuiState.h" #include "SynthGuiState.h"
#include "raylib.h"
#include <vector> #include <vector>
#include "raylib.h"
class Renderer { class Renderer
private: {
void draw_main_panel(const Rectangle& panel_bounds); private:
float draw_oscillators_panels( void DrawMainPanel(const Rectangle& panel_bounds);
const std::vector<Oscillator*>& oscillators, void DrawAddOscillatorButton(Synth & synth, SynthGuiState & synthGui, Rectangle panel_bounds);
const std::vector<OscillatorGuiState*>& gui_oscillators, void DrawOscillatorsPanels(const std::vector<Oscillator*>& oscillators,
const std::vector<OscillatorGuiState*>& guiOscillators,
const Rectangle& panel_bounds); const Rectangle& panel_bounds);
void draw_oscillators_shape_inputs( void DrawOscillatorsShapeInputs(const std::vector<Oscillator*>& oscillators,
const std::vector<Oscillator*>& oscillators,
const std::vector<OscillatorGuiState*>& guiOscillators); const std::vector<OscillatorGuiState*>& guiOscillators);
void draw_ui(Synth& synth, SynthGuiState& synth_gui); void DrawUi(Synth & synth, SynthGuiState & synthGui);
void draw_signal(Synth& synth, SynthGuiState& synth_gui); void DrawSignal(Synth & synth, SynthGuiState & synthGui);
void draw_adsr_panel(ADSR* adsr, ADSRGuiState& gui_adsr, public:
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:
Renderer(/* args */); Renderer(/* args */);
~Renderer(); ~Renderer();
void Draw(Synth& synth, SynthGuiState& synth_gui); void Draw(Synth& synth, SynthGuiState & synthGui);
}; };

View File

@@ -1,8 +1,10 @@
#pragma once #pragma once
#include "Logger.h"
#include <cstddef> #include <cstddef>
template <typename T> class RingBuffer { #include "Logger.h"
private: template <typename T>
class RingBuffer
{
private:
T* m_items; /* data */ T* m_items; /* data */
std::size_t m_head; std::size_t m_head;
std::size_t m_tail; std::size_t m_tail;
@@ -11,8 +13,7 @@ template <typename T> class RingBuffer {
std::size_t m_size; std::size_t m_size;
void advance_pointer(); void advance_pointer();
void retreat_pointer(); void retreat_pointer();
public:
public:
RingBuffer(std::size_t size); RingBuffer(std::size_t size);
~RingBuffer(); ~RingBuffer();
bool IsFull() { return m_is_full; } bool IsFull() { return m_is_full; }
@@ -25,7 +26,8 @@ template <typename T> class RingBuffer {
void Print(); void Print();
}; };
template <typename T> RingBuffer<T>::RingBuffer(std::size_t size) { template <typename T> RingBuffer<T>::RingBuffer(std::size_t size)
{
m_items = new T[size]; m_items = new T[size];
m_head = 0; m_head = 0;
m_tail = 0; m_tail = 0;
@@ -34,42 +36,50 @@ template <typename T> RingBuffer<T>::RingBuffer(std::size_t size) {
m_size = size; m_size = size;
} }
template <typename T> RingBuffer<T>::~RingBuffer() { delete[] m_items; } template <typename T> RingBuffer<T>::~RingBuffer()
{
delete[] m_items;
}
template <typename T> void RingBuffer<T>::Reset() { template <typename T> void RingBuffer<T>::Reset()
{
m_head = 0; m_head = 0;
m_tail = 0; m_tail = 0;
m_is_full = 0; m_is_full = 0;
} }
template <typename T> void RingBuffer<T>::advance_pointer() { template <typename T> void RingBuffer<T>::advance_pointer()
{
if (m_is_full) { if (m_is_full) {
m_tail++; m_tail++;
if (m_tail == m_size) { if (m_tail == m_size) {
m_tail = 0; m_tail = 0;
} }
} }
m_head++; m_head++;
if (m_head == m_size) { if (m_head == m_size) {
m_head = 0; m_head = 0;
} }
std::size_t p_is_full = m_head == m_tail ? 1 : 0; std::size_t p_is_full = m_head == m_tail ? 1 : 0;
m_is_full = p_is_full; m_is_full = p_is_full;
} }
template <typename T> void RingBuffer<T>::retreat_pointer() { template <typename T> void RingBuffer<T>::retreat_pointer()
m_is_full = 0; {
m_is_full = 0;
m_tail++; m_tail++;
if (m_tail == m_size) { if (m_tail == m_size) {
m_tail = 0; m_tail = 0;
} }
} }
template <typename T> void RingBuffer<T>::Write(T* data, std::size_t count) { template <typename T> void RingBuffer<T>::Write(T* data, std::size_t count)
{
if (m_is_full || m_head + count > m_size) { if (m_is_full || m_head + count > m_size) {
write_log("[WARN] Trying to overfill the ring buffer: " write_log("[WARN] Trying to overfill the ring buffer: \n\tIsFull:%d\n\tHead:%zu\n\tCount:%zu\n\t",
"\n\tIsFull:%d\n\tHead:%zu\n\tCount:%zu\n\t", m_is_full,
m_is_full, m_head, count); m_head,
count);
return; return;
} }
m_is_empty = 0; m_is_empty = 0;
@@ -78,10 +88,11 @@ template <typename T> void RingBuffer<T>::Write(T* data, std::size_t count) {
m_items[m_head] = data[i]; m_items[m_head] = data[i];
advance_pointer(); advance_pointer();
} }
// m_is_empty = m_is_full && (m_head == m_tail); //m_is_empty = m_is_full && (m_head == m_tail);
} }
template <typename T> bool RingBuffer<T>::Read(T* output, std::size_t count) { template <typename T> bool RingBuffer<T>::Read(T* output, std::size_t count)
{
if (m_is_empty) { if (m_is_empty) {
write_log("[WARN] Trying to read empty buffer"); write_log("[WARN] Trying to read empty buffer");
return 0; return 0;
@@ -95,21 +106,26 @@ template <typename T> bool RingBuffer<T>::Read(T* output, std::size_t count) {
return 1; return 1;
} }
template <typename T> std::size_t RingBuffer<T>::GetSize() { template <typename T> std::size_t RingBuffer<T>::GetSize()
size_t p_size = m_size; {
if (!m_is_full) { size_t p_size = m_size;
if (m_head >= m_tail) { if(!m_is_full) {
p_size = (m_head - m_tail); if(m_head >= m_tail) {
} else { p_size = (m_head - m_tail);
p_size = (m_size + m_head - m_tail); }
} else {
} p_size = (m_size + m_head - m_tail);
}
}
return p_size; return p_size;
} }
template <typename T> void RingBuffer<T>::Print() { template <typename T> void RingBuffer<T>::Print()
write_log("[INFO] The ring buffer: " {
"\n\tIsFull:%d\n\tIsEmpty:%d\n\tHead:%zu\n\tTail:%zu\n\t", write_log("[INFO] The ring buffer: \n\tIsFull:%d\n\tIsEmpty:%d\n\tHead:%zu\n\tTail:%zu\n\t",
m_is_full, m_is_empty, m_head, m_tail); m_is_full,
m_is_empty,
m_head,
m_tail);
} }

View File

@@ -1,13 +1,12 @@
#pragma once #pragma once
#define SAMPLE_RATE 44100.f #define SAMPLE_RATE 48000.f
#define BPM 120.f #define BPM 120.f
#define BEAT_DURATION 60.f / BPM #define BEAT_DURATION 60.f/BPM
#define PITCH_STANDARD 440.f #define PITCH_STANDARD 440.f
#define VOLUME 0.5f #define VOLUME 0.5f
#define ATTACK_MS 100.f #define ATTACK_MS 100.f
#define STREAM_BUFFER_SIZE 1024 #define STREAM_BUFFER_SIZE 4096
#define FPS 60
#define SYNTH_PI 3.1415926535f #define SYNTH_PI 3.1415926535f
#define SYNTH_VOLUME 0.5f #define SYNTH_VOLUME 0.5f

View File

@@ -1,40 +1,26 @@
#pragma once #pragma once
#include "ADSR.h"
#include "Filter.h"
#include "Adder.h"
#include "IEffect.h"
#include "Note.h"
#include "Oscillator.h"
#include "Settings.h"
#include <vector> #include <vector>
#include "LFO.h" #include "Oscillator.h"
#include "Note.h"
#include "Adder.h"
#include "Settings.h"
class Synth { class Synth
private: {
bool is_note_triggered; private:
std::vector<Oscillator*> m_oscillators; std::vector<Oscillator*> m_oscillators;
std::vector<IEffect*> m_effects; Adder m_adder;
//OscillatorUI* ui_oscillators;
//Note m_current_note;
std::vector<float> m_out_signal; std::vector<float> m_out_signal;
LFO* m_lfo; std::vector<float> & get_note(int semitone, float beats);
void ZeroSignal();
void GetNote(); public:
void TriggerNoteOnEffects();
void UntriggerNoteOnEffects();
void ApplyEffects();
void AddOscillator();
void ApplyFilterLfo();
public:
Synth(/* args */); Synth(/* args */);
~Synth(); ~Synth();
void Trigger(Note input); void ProduceNoteSound(Note input);
void Process(); void AddOscillator();
void Release(); const std::vector<float> & GetOutSignal() { return m_out_signal; }
void AddEffect(IEffect* fx);
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; }
ADSR* GetADSR() { return (ADSR*)m_effects[0]; }
Filter* GetFilter() { return (Filter*)m_effects[1]; }
void SetFilter(FilterType type);
}; };

View File

@@ -2,32 +2,15 @@
#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;
}; };
struct ADSRGuiState {
float attack;
float decay;
float sustain;
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;
FilterGuiState filter;
}; };

View File

@@ -1,94 +0,0 @@
#include "ADSR.h"
#include "Logger.h"
#include "Settings.h"
ADSR::ADSR(/* args */) {
m_attack_time = 1.f;
m_decay_time = 0.4f;
m_sustain_level = 0.6f;
m_release_time = 0.8f;
m_ramp = new Ramp(0, SAMPLE_RATE);
}
ADSR::~ADSR() { delete m_ramp; }
bool ADSR::IsAttackElapsed() {
return m_state == sAttack && m_ramp->IsCompleted();
}
bool ADSR::IsDecayElapsed() {
return m_state == sDecay && m_ramp->IsCompleted();
}
bool ADSR::IsReleaseElapsed() {
return m_state == sRelease && m_ramp->IsCompleted();
}
void ADSR::RecheckState() {
switch (m_state) {
case sAttack:
if (IsAttackElapsed()) {
m_state = sDecay;
m_ramp->RampTo(m_sustain_level, m_decay_time);
}
break;
case sDecay:
if (IsDecayElapsed()) {
m_state = sSustain;
}
break;
case sRelease:
if (IsReleaseElapsed()) {
m_state = sOff;
}
break;
default:
break;
}
}
void ADSR::ProcessSample(float* sample) {
if (m_state == sOff) {
(*sample) = 0;
} else if (m_state == sAttack) {
(*sample) = (*sample) * m_ramp->Process();
} else if (m_state == sDecay) {
(*sample) = (*sample) * m_ramp->Process();
} else if (m_state == sSustain) {
(*sample) = (*sample) * m_sustain_level;
} else if (m_state == sRelease) {
(*sample) = (*sample) * m_ramp->Process();
}
}
void ADSR::Trigger() {
write_log("Set ADSR\n");
if (m_state == sOff) {
m_state = sAttack;
} else if (m_state == sRelease) {
m_state = sAttack;
};
m_ramp->RampTo(1, m_attack_time);
}
void ADSR::Release() {
write_log("Unset ADSR\n");
m_state = sRelease;
m_ramp->RampTo(0, m_release_time);
}
void ADSR::Process(std::vector<float>& samples) {
for (std::size_t i = 0; i < samples.size(); i++) {
RecheckState();
ProcessSample(&samples[i]);
}
}
void ADSR::SetParameters(float attack, float decay, float sustain,
float release) {
m_attack_time = attack;
m_decay_time = decay;
m_sustain_level = sustain;
m_release_time = release;
}

View File

@@ -1,25 +1,32 @@
#include "Application.h" #include "Application.h"
#include "Logger.h"
#include "Settings.h" #include "Settings.h"
#include "Logger.h"
#include <string> #include <string>
Application::Application(/* args */) { Application::Application(/* args */)
InitSynth(); {
InitAudio(); m_ring_buffer = new RingBuffer<float>((std::size_t)STREAM_BUFFER_SIZE);
m_temp_buffer = new float[STREAM_BUFFER_SIZE];
init_synth();
init_audio();
} }
Application::~Application() { Application::~Application()
{
StopAudioStream(m_synth_stream); StopAudioStream(m_synth_stream);
UnloadAudioStream(m_synth_stream); UnloadAudioStream(m_synth_stream);
CloseAudioDevice(); CloseAudioDevice();
CloseWindow(); CloseWindow();
delete m_ring_buffer;
delete[] m_temp_buffer;
// todo: move to gui state class destructor (make it a class) // todo: move to gui state class destructor (make it a class)
for (int i = 0; i < m_synth_gui_state.oscillators.size(); i++) { for(int i = 0; i < m_synth_gui_state.oscillators.size(); i++) {
delete m_synth_gui_state.oscillators[i]; delete m_synth_gui_state.oscillators[i];
} }
} }
void Application::InitAudio() { void Application::init_audio()
{
m_sound_played_count = 0; m_sound_played_count = 0;
InitAudioDevice(); InitAudioDevice();
@@ -31,90 +38,147 @@ 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
m_current_note->name.assign("G4"); {
.length = 1,
.name = (*nameString)
};
//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++)
{
Oscillator* osc = oscillators[i]; Oscillator* osc = oscillators[i];
assert(osc); assert(osc);
OscillatorGuiState* ui = OscillatorGuiState* ui = new OscillatorGuiState {
new OscillatorGuiState{.fine = osc->GetFine(), .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) { std::size_t 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 (IsKeyPressed(KEY_A))
note->name.assign("A2"); {
note->name.assign("A4");
is_pressed = 1; is_pressed = 1;
} }
if (IsKeyDown(KEY_B)) { if (IsKeyPressed(KEY_B))
note->name.assign("B2"); {
note->name.assign("B4");
is_pressed = 1; is_pressed = 1;
} }
if (IsKeyDown(KEY_C)) { if (IsKeyPressed(KEY_C))
note->name.assign("C2"); {
note->name.assign("C4");
is_pressed = 1; is_pressed = 1;
} }
if (IsKeyDown(KEY_D)) { if (IsKeyPressed(KEY_D))
note->name.assign("D2"); {
note->name.assign("D4");
is_pressed = 1; is_pressed = 1;
} }
if (IsKeyDown(KEY_E)) { if (IsKeyPressed(KEY_E))
note->name.assign("E2"); {
note->name.assign("E4");
is_pressed = 1; is_pressed = 1;
} }
if (IsKeyDown(KEY_F)) { if (IsKeyPressed(KEY_F))
note->name.assign("F2"); {
note->name.assign("F4");
is_pressed = 1; is_pressed = 1;
} }
if (IsKeyDown(KEY_G)) { if (IsKeyPressed(KEY_G))
note->name.assign("G2"); {
note->name.assign("G4");
is_pressed = 1; is_pressed = 1;
} }
return is_pressed == 1; return is_pressed;
} }
bool is_note_up() { // Update On Input
return IsKeyUp(KEY_A) || IsKeyUp(KEY_B) || IsKeyUp(KEY_C) || void Application::update_on_note_input()
IsKeyUp(KEY_D) || IsKeyUp(KEY_E) || IsKeyUp(KEY_F) || IsKeyUp(KEY_G); {
} if (detect_note_pressed(m_current_note))
{
void Application::UpdateOnNoteInput() { m_synth.ProduceNoteSound((*m_current_note));
if (DetectNotePressed(m_current_note)) { m_sound_played_count = 0;
if (!m_synth.GetIsNoteTriggered()) { write_log("Note played: %s\n", m_current_note->name.c_str());
m_synth.Trigger((*m_current_note));
}
//write_log("Note played: %s\n", m_current_note->name.c_str());
} else if (is_note_up() && m_synth.GetIsNoteTriggered()) {
m_synth.Release();
} }
// will produce 0 signal if ADSR is in off state
m_synth.Process();
} }
// Play ring-buffered audio // Play ring-buffered audio
void Application::PlayBufferedAudio() { void Application::play_buffered_audio()
if (IsAudioStreamProcessed(m_synth_stream)) { {
UpdateOnNoteInput(); if (IsAudioStreamProcessed(m_synth_stream) && !m_ring_buffer->IsEmpty())
UpdateAudioStream(m_synth_stream, m_synth.GetOutSignal().data(), {
STREAM_BUFFER_SIZE); std::size_t size_to_read = m_ring_buffer->GetSize();
write_log("Samples to play:%zu \n", size_to_read);
//todo: try to start reading directly from ring buffer, avoiding temp_buffer
m_ring_buffer->Read(m_temp_buffer, size_to_read);
// can try the SetAudioStreamCallback
UpdateAudioStream(m_synth_stream, m_temp_buffer, size_to_read);
// can overwrite the ring buffer to avoid that
if (m_synth.GetOutSignal().size() == m_sound_played_count)
{
m_ring_buffer->Reset();
}
} }
} }
void Application::Run() { // Fill ring buffer from current sound
void Application::fill_audio_buffer()
{
if (!m_ring_buffer->IsFull() && m_synth.GetOutSignal().size() != m_sound_played_count)
{
write_log("[INFO] IsFull:%d Samples:%zu Played:%d\n",
m_ring_buffer->IsFull(),
m_synth.GetOutSignal().size(),
m_sound_played_count);
// how many samples need write
std::size_t size_to_fill = 0;
if ((m_synth.GetOutSignal().size() - m_sound_played_count) > m_ring_buffer->GetCapacity())
{
size_to_fill = m_ring_buffer->GetCapacity();
} else
{
size_to_fill = m_synth.GetOutSignal().size() - m_sound_played_count;
}
write_log("[INFO] SizeToFill:%zu\n", size_to_fill);
for (size_t i = 0; i < size_to_fill; i++)
{
m_temp_buffer[i] = m_synth.GetOutSignal()[i];
}
m_ring_buffer->Write(m_temp_buffer, size_to_fill);
m_sound_played_count += size_to_fill;
}
}
void Application::Run()
{
// Main game loop // Main game loop
while (!WindowShouldClose()) { while (!WindowShouldClose()) // Detect window close button or ESC key
PlayBufferedAudio(); {
fill_audio_buffer();
play_buffered_audio();
update_on_note_input();
m_renderer.Draw(m_synth, m_synth_gui_state); m_renderer.Draw(m_synth, m_synth_gui_state);
} }
} }

View File

@@ -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;
}

View File

@@ -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));
}

View File

@@ -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;
}

View File

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

View File

@@ -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;
}

View File

@@ -1,97 +1,112 @@
#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;
} }
Oscillator::~Oscillator() {} Oscillator::~Oscillator()
{
}
void Oscillator::Reset() { void Oscillator::Reset()
{
m_volume = 0; m_volume = 0;
m_phase = 0; m_phase = 0;
m_phase_dt = 0; m_phase_dt = 0;
} }
void Oscillator::SetType(OscillatorType osc) { 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; {
float freq = KeyBoard::GetHzBySemitone(m_key + m_fine); m_freq = freq;
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;
} }

View File

@@ -1,27 +0,0 @@
#include "Ramp.h"
#include "Logger.h"
Ramp::Ramp(float starting_level, float sample_rate) {
m_level = starting_level;
m_sample_rate = sample_rate;
}
Ramp::~Ramp() {}
void Ramp::RampTo(float val, float time) {
m_increment = (val - m_level) / (m_sample_rate * time);
m_counter = (int)(m_sample_rate * time);
write_log("Ramping from: %.1f to: %.1f by: %lf for: %d\n", m_level, val,
m_increment, m_counter);
}
float Ramp::Process() {
if (m_counter > 0) {
m_counter--;
m_level += m_increment;
}
return m_level;
}
bool Ramp::IsCompleted() { return m_counter == 0; }

View File

@@ -1,55 +1,56 @@
#include "Renderer.h" #include "Renderer.h"
#define RAYGUI_IMPLEMENTATION #define RAYGUI_IMPLEMENTATION
#include "Logger.h"
#include "Settings.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-but-set-variable"
#pragma clang diagnostic ignored "-Wunused-variable"
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#include "raygui.h" #include "raygui.h"
#pragma clang diagnostic pop #include "Settings.h"
#include "Logger.h"
Renderer::Renderer(/* args */) { Renderer::Renderer(/* args */)
{
InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "SeeSynth - v0.2"); InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "SeeSynth - v0.2");
SetTargetFPS(FPS); SetTargetFPS(60);
} }
Renderer::~Renderer() {} Renderer::~Renderer()
{
}
void Renderer::Draw(Synth& synth, SynthGuiState& synth_gui) { void Renderer::Draw(Synth& synth, SynthGuiState& synthGui)
{
BeginDrawing(); BeginDrawing();
ClearBackground(RAYWHITE);
draw_ui(synth, synth_gui); ClearBackground(RAYWHITE);
draw_signal(synth, synth_gui); //todo: implement renderer
DrawUi(synth, synthGui);
DrawSignal(synth, synthGui);
//DrawText("Congrats! You created your first window!", 190, 200, 20, LIGHTGRAY);
//DrawFPS(0,0);
EndDrawing(); EndDrawing();
} }
void Renderer::draw_signal(Synth& synth, SynthGuiState& synth_gui) { void Renderer::DrawSignal(Synth & synth, SynthGuiState & synthGui)
GuiGrid((Rectangle){0, 0, WINDOW_WIDTH, WINDOW_HEIGHT}, "", {
WINDOW_HEIGHT / 8, 2); GuiGrid((Rectangle){0, 0, WINDOW_WIDTH, WINDOW_HEIGHT}, "", WINDOW_HEIGHT / 8, 2);
auto signal = synth.GetOutSignal(); auto signal = synth.GetOutSignal();
Vector2* signal_points = new Vector2[signal.size()]; Vector2* signal_points = new Vector2[signal.size()];
const float screen_vertical_midpoint = (WINDOW_HEIGHT / 2); const float screen_vertical_midpoint = (WINDOW_HEIGHT/2);
for (int point_idx = 0; point_idx < signal.size(); point_idx++) { for (int point_idx = 0; point_idx < signal.size(); point_idx++)
{
signal_points[point_idx].x = (float)point_idx + OSCILLATOR_PANEL_WIDTH; signal_points[point_idx].x = (float)point_idx + OSCILLATOR_PANEL_WIDTH;
signal_points[point_idx].y = signal_points[point_idx].y = screen_vertical_midpoint + (int)(signal[point_idx] * 300);
screen_vertical_midpoint + (int)(signal[point_idx] * 300);
} }
DrawLineStrip(signal_points, signal.size(), RED); DrawLineStrip(signal_points, signal.size(), RED);
delete[] signal_points; delete[] signal_points;
} }
void Renderer::draw_oscillators_shape_inputs( void Renderer::DrawOscillatorsShapeInputs(const std::vector<Oscillator*>& oscillators, const std::vector<OscillatorGuiState*>& guiOscillators)
const std::vector<Oscillator*>& oscillators, {
const std::vector<OscillatorGuiState*>& gui_oscillators) { #define WAVE_SHAPE_OPTIONS "Sine;Triangle;Sawtooth;Square"
#define WAVE_SHAPE_OPTIONS "Sine;Triangle;Sawtooth;Square"
// DRAW OSCILLATOR SHAPE INPUTS // DRAW OSCILLATOR SHAPE INPUTS
for (int i = 0; i < oscillators.size(); i += 1) { for (int i = 0; i < oscillators.size(); i += 1)
OscillatorGuiState* ui_osc = gui_oscillators[i]; {
OscillatorGuiState* ui_osc = guiOscillators[i];
assert(ui_osc); assert(ui_osc);
Oscillator* osc = oscillators[i]; Oscillator* osc = oscillators[i];
@@ -57,29 +58,32 @@ void Renderer::draw_oscillators_shape_inputs(
// Shape select // Shape select
int shape_index = (int)(ui_osc->waveshape); int shape_index = (int)(ui_osc->waveshape);
bool is_dropdown_click = bool is_dropdown_click = GuiDropdownBox(ui_osc->shape_dropdown_rect,
GuiDropdownBox(ui_osc->shape_dropdown_rect, WAVE_SHAPE_OPTIONS, WAVE_SHAPE_OPTIONS,
&shape_index, ui_osc->is_dropdown_open); &shape_index,
ui_osc->is_dropdown_open
);
if (is_dropdown_click) { if (is_dropdown_click)
{
write_log("Dropdown clicked!\n"); write_log("Dropdown clicked!\n");
ui_osc->is_dropdown_open = !ui_osc->is_dropdown_open; ui_osc->is_dropdown_open = !ui_osc->is_dropdown_open;
ui_osc->waveshape = (OscillatorType)(shape_index); ui_osc->waveshape = (OscillatorType)(shape_index);
// APPLY STATE TO REAL OSC // APPLY STATE TO REAL OSC
osc->SetType(ui_osc->waveshape); osc->SetType(ui_osc->waveshape);
} }
if (ui_osc->is_dropdown_open) if (ui_osc->is_dropdown_open) break;
break;
} }
} }
float Renderer::draw_oscillators_panels( void Renderer::DrawOscillatorsPanels(const std::vector<Oscillator*>& oscillators,
const std::vector<Oscillator*>& oscillators, const std::vector<OscillatorGuiState*>& guiOscillators,
const std::vector<OscillatorGuiState*>& gui_oscillators, const Rectangle& panel_bounds)
const Rectangle& panel_bounds) { {
float panel_y_offset = 0; float panel_y_offset = 0;
for (int i = 0; i < oscillators.size(); i++) { for (int i = 0; i < oscillators.size(); i++)
OscillatorGuiState* ui_osc = gui_oscillators[i]; {
OscillatorGuiState* ui_osc = guiOscillators[i];
assert(ui_osc); assert(ui_osc);
Oscillator* osc = oscillators[i]; Oscillator* osc = oscillators[i];
@@ -89,200 +93,104 @@ 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;
GuiPanel((Rectangle){(float)osc_panel_x, (float)osc_panel_y, GuiPanel((Rectangle){
(float)osc_panel_width, (float)osc_panel_height}, (float)osc_panel_x,
""); (float)osc_panel_y,
(float)osc_panel_width,
(float)osc_panel_height
},
"");
const float slider_padding = 50.f; const float slider_padding = 50.f;
const float el_spacing = 5.f; const float el_spacing = 5.f;
Rectangle el_rect = {.x = (float)osc_panel_x + slider_padding + 30, Rectangle el_rect = {
.y = (float)osc_panel_y + 10, .x = (float)osc_panel_x + slider_padding + 30,
.width = .y = (float)osc_panel_y + 10,
(float)osc_panel_width - (slider_padding * 2), .width = (float)osc_panel_width - (slider_padding * 2),
.height = 25.f}; .height = 25.f
};
// Volume slider // Volume slider
float decibels = (20.f * log10f(osc->GetVolume())); float decibels = (20.f * log10f(osc->GetVolume()));
char amp_slider_label[32]; char amp_slider_label[32];
snprintf(amp_slider_label, 7, "%.1f dB", decibels); sprintf(amp_slider_label, "%.1f dB", decibels);
decibels = decibels = GuiSlider(el_rect,
GuiSlider(el_rect, amp_slider_label, "", decibels, -60.0f, 0.0f); amp_slider_label,
ui_osc->volume = powf(10.f, decibels * (1.f / 20.f)); "",
el_rect.y += el_rect.height + el_spacing; decibels,
-60.0f,
0.0f
);
ui_osc->volume = powf(10.f, decibels * (1.f/20.f));
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;
} }
void Renderer::draw_main_panel(const Rectangle& panel_bounds) { void Renderer::DrawMainPanel(const Rectangle& panel_bounds)
{
bool is_shape_dropdown_open = false;
int shape_index = 0;
GuiPanel(panel_bounds, ""); GuiPanel(panel_bounds, "");
} }
void Renderer::draw_adsr_panel(ADSR* adsr, ADSRGuiState& gui_adsr, void Renderer::DrawAddOscillatorButton(Synth & synth, SynthGuiState & synthGui, Rectangle panel_bounds)
const Rectangle& panel_bounds, {
float panel_y_offset) { bool click_add_oscillator = GuiButton((Rectangle){
// Draw ADSR Panel panel_bounds.x + 10,
const int osc_panel_width = panel_bounds.width - 20; panel_bounds.y + 10,
const int osc_panel_height = 120; panel_bounds.width - 20,
const int osc_panel_x = panel_bounds.x + 10; 25.f
const int osc_panel_y = panel_bounds.y + 50 + panel_y_offset; }, "Add Oscillator");
panel_y_offset += osc_panel_height + 5; if (click_add_oscillator)
GuiPanel((Rectangle){(float)osc_panel_x, (float)osc_panel_y, {
(float)osc_panel_width, (float)osc_panel_height}, synth.AddOscillator();
"ADSR"); Oscillator* osc = synth.GetOscillators().back();
const float slider_padding = 50.f; OscillatorGuiState* ui = new OscillatorGuiState {
const float el_spacing = 5.f; .freq = osc->GetFreq(),
Rectangle el_rect = {.x = (float)osc_panel_x + slider_padding + 30, .waveshape = osc->GetType(),
.y = (float)osc_panel_y + 10, .volume = osc->GetVolume()
.width = (float)osc_panel_width - (slider_padding * 2), };
.height = 25.f}; synthGui.oscillators.push_back(ui);
// Attack slider
float attack = gui_adsr.attack;
char attack_slider_label[32];
snprintf(attack_slider_label, 7, "%.1f s", attack);
attack = GuiSlider(el_rect, attack_slider_label, "", attack, 0.0f, 2.0f);
gui_adsr.attack = attack;
el_rect.y += el_rect.height + el_spacing;
// Decay slider
float decay = gui_adsr.decay;
char decay_slider_label[32];
snprintf(decay_slider_label, 7, "%.1f s", decay);
decay = GuiSlider(el_rect, decay_slider_label, "", decay, 0.0f, 1.0f);
gui_adsr.decay = decay;
el_rect.y += el_rect.height + el_spacing;
// Sustain slider
float sustain = gui_adsr.sustain;
char sustain_slider_label[32];
snprintf(sustain_slider_label, 7, "%.1f u", sustain);
sustain = GuiSlider(el_rect, sustain_slider_label, "", sustain, 0.0f, 1.0f);
gui_adsr.sustain = sustain;
el_rect.y += el_rect.height + el_spacing;
// Release slider
float release = gui_adsr.release;
char release_slider_label[32];
snprintf(release_slider_label, 7, "%.1f s", release);
release = GuiSlider(el_rect, release_slider_label, "", release, 0.0f, 5.0f);
gui_adsr.release = release;
el_rect.y += el_rect.height + el_spacing;
// apply values to real one
adsr->SetParameters(gui_adsr.attack, gui_adsr.decay, gui_adsr.sustain,
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) { void Renderer::DrawUi(Synth & synth, SynthGuiState & synthGui)
Rectangle panel_bounds = {.x = 0, {
.y = 0, Rectangle panel_bounds = {.x = 0, .y = 0, .width = OSCILLATOR_PANEL_WIDTH, .height = WINDOW_HEIGHT };
.width = OSCILLATOR_PANEL_WIDTH, DrawMainPanel(panel_bounds);
.height = WINDOW_HEIGHT}; DrawAddOscillatorButton(synth, synthGui, panel_bounds);
draw_main_panel(panel_bounds); // Draw Oscillators
std::vector<Oscillator*> oscillators = synth.GetOscillators(); std::vector<Oscillator*> oscillators = synth.GetOscillators();
std::vector<OscillatorGuiState*> gui_oscillators = synth_gui.oscillators; std::vector<OscillatorGuiState*> guiOscillators = synthGui.oscillators;
float panel_y_offset = DrawOscillatorsPanels(oscillators, guiOscillators, panel_bounds);
draw_oscillators_panels(oscillators, gui_oscillators, panel_bounds); DrawOscillatorsShapeInputs(oscillators, guiOscillators);
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);
} }

View File

@@ -1,107 +1,39 @@
#include "Synth.h" #include "Synth.h"
#include "FilterFactory.h"
#include "KeyBoard.h"
#include "Logger.h"
#include "OscillatorType.h"
#include "Settings.h" #include "Settings.h"
#include "LowPassFilter.h" #include "KeyBoard.h"
#include "OscillatorType.h"
Synth::Synth(/* args */) { Synth::Synth(/* args */)
m_lfo = new LFO(); {
m_lfo->SetFreq(5.0);
AddOscillator(); AddOscillator();
AddOscillator(); }
AddEffect(new ADSR());
AddEffect(FilterFactory::GetDefaultFilter()); Synth::~Synth()
for (size_t i = 0; i < STREAM_BUFFER_SIZE; i++) { {
float sample = 0.0f; }
m_out_signal.push_back(sample);
std::vector<float> & Synth::get_note(int semitone, float beats)
{
float hz = KeyBoard::GetHzBySemitone(semitone);
float duration = beats * BEAT_DURATION;
// will change after oscillator starts to be more autonomous
for (Oscillator* osc : m_oscillators)
{
osc->SetFreq(hz);
} }
ZeroSignal();
return m_adder.SumOscillators(m_oscillators, duration); //todo: add other pipeline steps (e.g ADSR, Filters, FX);
} }
Synth::~Synth() { void Synth::ProduceNoteSound(Note input)
m_oscillators.clear(); {
m_effects.clear(); float length = 1.f / input.length;
m_out_signal.clear();
}
void Synth::ZeroSignal() {
float sample = 0.0f;
for (size_t i = 0; i < STREAM_BUFFER_SIZE; i++) {
m_out_signal[i] = sample;
}
}
void Synth::GetNote() {
ZeroSignal();
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::TriggerNoteOnEffects() {
for (IEffect* effect : m_effects) {
effect->Trigger();
}
}
void Synth::UntriggerNoteOnEffects() {
for (IEffect* effect : m_effects) {
effect->Release();
}
}
void Synth::AddOscillator() {
m_oscillators.push_back(new Oscillator(OscillatorType::Sine, 0.0f, VOLUME));
}
void Synth::Trigger(Note input) {
int semitone_shift = KeyBoard::GetSemitoneShift(input.name); int semitone_shift = KeyBoard::GetSemitoneShift(input.name);
m_out_signal = get_note(semitone_shift, length);
for (Oscillator* osc : m_oscillators) {
osc->SetKey(semitone_shift);
}
is_note_triggered = true;
TriggerNoteOnEffects();
} }
void Synth::ApplyFilterLfo() { void Synth::AddOscillator()
float dt = m_lfo->Process(); {
Filter* filter = (Filter*)m_effects[1]; m_oscillators.push_back(new Oscillator(OscillatorType::Sine, 440.f, VOLUME));
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;
}
} }