Compare commits
20 Commits
feature/os
...
refactor/c
| Author | SHA1 | Date | |
|---|---|---|---|
|
ff238c874b
|
|||
|
96a7c9a1b8
|
|||
|
ec01773ab1
|
|||
|
5c485047fb
|
|||
|
a1fef25838
|
|||
|
cadeeb323d
|
|||
|
891c747d11
|
|||
|
7784119f85
|
|||
|
d98e311d16
|
|||
|
d565817d8f
|
|||
|
56b68bc963
|
|||
|
e3825b341d
|
|||
|
78c202a9d6
|
|||
|
b02a5d2873
|
|||
|
64fa5c9271
|
|||
|
6561666f7a
|
|||
|
66c839e2ae
|
|||
|
d20dbd920f
|
|||
|
850dedb319
|
|||
| bcb75a65f9 |
59
.vscode/settings.json
vendored
59
.vscode/settings.json
vendored
@@ -7,7 +7,64 @@
|
|||||||
"__bit_reference": "c",
|
"__bit_reference": "c",
|
||||||
"bitset": "c",
|
"bitset": "c",
|
||||||
"chrono": "c",
|
"chrono": "c",
|
||||||
"unordered_map": "c"
|
"unordered_map": "c",
|
||||||
|
"__bits": "cpp",
|
||||||
|
"__config": "cpp",
|
||||||
|
"__debug": "cpp",
|
||||||
|
"__errc": "cpp",
|
||||||
|
"__hash_table": "cpp",
|
||||||
|
"__locale": "cpp",
|
||||||
|
"__mutex_base": "cpp",
|
||||||
|
"__node_handle": "cpp",
|
||||||
|
"__split_buffer": "cpp",
|
||||||
|
"__threading_support": "cpp",
|
||||||
|
"__tuple": "cpp",
|
||||||
|
"__verbose_abort": "cpp",
|
||||||
|
"array": "cpp",
|
||||||
|
"atomic": "cpp",
|
||||||
|
"bit": "cpp",
|
||||||
|
"cctype": "cpp",
|
||||||
|
"clocale": "cpp",
|
||||||
|
"cmath": "cpp",
|
||||||
|
"complex": "cpp",
|
||||||
|
"cstdarg": "cpp",
|
||||||
|
"cstddef": "cpp",
|
||||||
|
"cstdint": "cpp",
|
||||||
|
"cstdio": "cpp",
|
||||||
|
"cstdlib": "cpp",
|
||||||
|
"cstring": "cpp",
|
||||||
|
"ctime": "cpp",
|
||||||
|
"cwchar": "cpp",
|
||||||
|
"cwctype": "cpp",
|
||||||
|
"exception": "cpp",
|
||||||
|
"initializer_list": "cpp",
|
||||||
|
"ios": "cpp",
|
||||||
|
"iosfwd": "cpp",
|
||||||
|
"istream": "cpp",
|
||||||
|
"limits": "cpp",
|
||||||
|
"locale": "cpp",
|
||||||
|
"memory": "cpp",
|
||||||
|
"mutex": "cpp",
|
||||||
|
"new": "cpp",
|
||||||
|
"optional": "cpp",
|
||||||
|
"ostream": "cpp",
|
||||||
|
"ratio": "cpp",
|
||||||
|
"sstream": "cpp",
|
||||||
|
"stdexcept": "cpp",
|
||||||
|
"streambuf": "cpp",
|
||||||
|
"string": "cpp",
|
||||||
|
"string_view": "cpp",
|
||||||
|
"system_error": "cpp",
|
||||||
|
"tuple": "cpp",
|
||||||
|
"type_traits": "cpp",
|
||||||
|
"typeinfo": "cpp",
|
||||||
|
"variant": "cpp",
|
||||||
|
"vector": "cpp",
|
||||||
|
"__nullptr": "cpp",
|
||||||
|
"__string": "cpp",
|
||||||
|
"compare": "cpp",
|
||||||
|
"concepts": "cpp",
|
||||||
|
"numeric": "cpp"
|
||||||
},
|
},
|
||||||
"FSharp.suggestGitignore": false,
|
"FSharp.suggestGitignore": false,
|
||||||
}
|
}
|
||||||
6
build.sh
6
build.sh
@@ -1,3 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
CC="${CXX:-cc}"
|
CC="${CXX:-c++}"
|
||||||
$CC -Wall -std=c11 ./main.c ./utils.c ./ring_buffer.c ./oscillator.c ./parser.c ./export.c -lm -lraylib -o ./bin/main
|
LL="-lm -lraylib"
|
||||||
|
FLAGS="-Wall -std=c++17 -I./inc/"
|
||||||
|
$CC $FLAGS $(find ./src -type f -iregex ".*\.cpp") $LL -o ./bin/main
|
||||||
|
|||||||
38
docs/Resources.md
Normal file
38
docs/Resources.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
http://basicsynth.com/index.php?page=basic
|
||||||
|
|
||||||
|
Signal Generator
|
||||||
|
The most straightforward method of sound generation in software is to evaluate a periodic function for each sample time. A periodic function is any function that repeats at a constant interval, called the period. Consider the circle in the figure below. Starting at the 3:00 position and then sweeping around the circle counter-clockwise, we make a complete cycle in 2π radians and then the movement repeats. Thus the period is 2π radians. If we plot the points on the circumference over time we produce the waveform as shown below.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
For audio signals, the period is the time it takes for the waveform to repeat and is thus the inverse of the frequency. In other words, a frequency of 100Hz repeats every 1/100 second. We need to generate an amplitude value for every sample time, thus the number of samples in one period is equal to the time of the period divided by the time of one sample. Since the time of one sample is the inverse of the sample rate, and the period is the inverse of the frequency, the number of samples is also the sample rate divided by the frequency: ((1/f) / (1/fs)) = (fs/f). Since our period is also equal to 2π radians, the phase increment for one sample time (φ) is 2π divided by the number of samples in one period:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
where the frequency of the signal is f and the sample rate fs. The amplitude for any given sample is the y value of the phase at that point in time multiplied by the radius of the circle. In other words, the amplitude is the sine of the phase angle and we can also derive the phase increment from the sine function.
|
||||||
|
|
||||||
|
Signal Generation Equation
|
||||||
|
|
||||||
|
The value sn is the nth sample, An the peak amplitude (volume) at sample n, and θn the phase at sample n. To calculate θn for any sample n, we can multiply the phase increment for one sample time (φ) by the sample number. To calculate φ we need to determine the radians for one sample time at a given frequency. As there are 2π radians per cycle, we multiply the frequency by 2π to get the radians per second. The phase increment for one sample time is then the radians per second multiplied by the time for one sample. Substiting for θn in the original equation yields:
|
||||||
|
|
||||||
|
Signal Generation Equation
|
||||||
|
|
||||||
|
We can implement this as a program loop.
|
||||||
|
|
||||||
|
totalSamples = duration * sampleRate;
|
||||||
|
for (n = 0; n < totalSamples; n++)
|
||||||
|
sample[n] = sin((twoPI/sampleRate) * frequency * n);
|
||||||
|
Since 2π/fs is constant through the loop, we can calculate it once. We can also replace the multiplication of the phase with a repeated addition.
|
||||||
|
|
||||||
|
phaseIncr = (twoPI/sampleRate) * frequency;
|
||||||
|
phase = 0;
|
||||||
|
totalSamples = duration * sampleRate;
|
||||||
|
for (n = 0; n < totalSamples; n++) {
|
||||||
|
sample[n] = sin(phase);
|
||||||
|
phase += phaseIncr;
|
||||||
|
if (phase >= twoPI)
|
||||||
|
phase -= twoPI;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
We can replace the sin function with any periodic function that returns an amplitude for a given phase angle. Thus, this small piece of code can be used to produce a very wide range of sounds. Functionally, it is the software equivalent of an oscillator, the basic building block of almost all synthesizers.
|
||||||
74
export.c
74
export.c
@@ -1,74 +0,0 @@
|
|||||||
#include "export.h"
|
|
||||||
|
|
||||||
#include "stdio.h"
|
|
||||||
#include "string.h"
|
|
||||||
#include "settings.h"
|
|
||||||
|
|
||||||
uint16_t toInt16Sample(float sample) {
|
|
||||||
return (uint16_t)(sample * 32767.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void write_file(char* filename, void* data, int size) {
|
|
||||||
FILE* fp = fopen(filename, "wb"); // open file for writing in binary mode
|
|
||||||
if (fp == NULL) {
|
|
||||||
fprintf(stderr, "Cannot open file: %s\n", filename);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
fwrite(data, size, 1, fp); // write data to file
|
|
||||||
|
|
||||||
fclose(fp); // close file
|
|
||||||
}
|
|
||||||
|
|
||||||
void pack(uint16_t* d, size_t length) {
|
|
||||||
size_t dataLength = length * 2;
|
|
||||||
|
|
||||||
int bytesPerSample = 2;
|
|
||||||
int byteRate = SAMPLE_RATE * bytesPerSample;
|
|
||||||
|
|
||||||
size_t fileSize = 36 + dataLength;
|
|
||||||
|
|
||||||
uint8_t* buffer = (uint8_t*)malloc(fileSize);
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
// RIFF header
|
|
||||||
memcpy(buffer + i, "RIFF", 4);
|
|
||||||
i += 4;
|
|
||||||
memcpy(buffer + i, &fileSize, 4);
|
|
||||||
i += 4;
|
|
||||||
memcpy(buffer + i, "WAVE", 4);
|
|
||||||
i += 4;
|
|
||||||
|
|
||||||
// fmt subchunk
|
|
||||||
memcpy(buffer + i, "fmt ", 4);
|
|
||||||
i += 4;
|
|
||||||
int fmtSize = 16;
|
|
||||||
memcpy(buffer + i, &fmtSize, 4);
|
|
||||||
i += 4;
|
|
||||||
uint16_t audioFormat = 1;
|
|
||||||
memcpy(buffer + i, &audioFormat, 2);
|
|
||||||
i += 2;
|
|
||||||
uint16_t numChannels = 1;
|
|
||||||
memcpy(buffer + i, &numChannels, 2);
|
|
||||||
i += 2;
|
|
||||||
int sampleRate = (int)SAMPLE_RATE;
|
|
||||||
memcpy(buffer + i, &sampleRate, 4);
|
|
||||||
i += 4;
|
|
||||||
memcpy(buffer + i, &byteRate, 4);
|
|
||||||
i += 4;
|
|
||||||
memcpy(buffer + i, &bytesPerSample, 2);
|
|
||||||
i += 2;
|
|
||||||
int bitsPerSample = bytesPerSample * 8;
|
|
||||||
memcpy(buffer + i, &bitsPerSample, 2);
|
|
||||||
i += 2;
|
|
||||||
|
|
||||||
// data subchunk
|
|
||||||
memcpy(buffer + i, "data", 4);
|
|
||||||
i += 4;
|
|
||||||
memcpy(buffer + i, &dataLength, 4);
|
|
||||||
i += 4;
|
|
||||||
memcpy(buffer + i, d, dataLength);
|
|
||||||
|
|
||||||
write_file("output.wav", buffer, fileSize);
|
|
||||||
}
|
|
||||||
9
export.h
9
export.h
@@ -1,9 +0,0 @@
|
|||||||
#ifndef EXPORT_H
|
|
||||||
#define EXPORT_H
|
|
||||||
|
|
||||||
#include "stdlib.h"
|
|
||||||
|
|
||||||
uint16_t toInt16Sample(float sample);
|
|
||||||
void pack(uint16_t* d, size_t length);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
30
inc/Adder.h
Normal file
30
inc/Adder.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "Oscillator.h"
|
||||||
|
#include "Settings.h"
|
||||||
|
#include <numeric>
|
||||||
|
|
||||||
|
struct Adder
|
||||||
|
{
|
||||||
|
static std::vector<float> & SumOscillators(const std::vector<Oscillator*> & oscillators, float duration)
|
||||||
|
{
|
||||||
|
size_t sample_count = (size_t)(duration * SAMPLE_RATE);
|
||||||
|
|
||||||
|
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;
|
||||||
|
for (Oscillator* osc : oscillators)
|
||||||
|
{
|
||||||
|
sample += osc->GenerateSample(duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
output->push_back(sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (*output);
|
||||||
|
}
|
||||||
|
};
|
||||||
32
inc/Application.h
Normal file
32
inc/Application.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Note.h"
|
||||||
|
#include "Synth.h"
|
||||||
|
#include "raylib.h"
|
||||||
|
#include "RingBuffer.h"
|
||||||
|
#include "Renderer.h"
|
||||||
|
#include "SynthGuiState.h"
|
||||||
|
|
||||||
|
class Application
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
Synth m_synth;
|
||||||
|
SynthGuiState m_synth_gui_state;
|
||||||
|
RingBuffer<float>* m_ring_buffer;
|
||||||
|
AudioStream m_synth_stream;
|
||||||
|
int m_sound_played_count;
|
||||||
|
float* m_temp_buffer;
|
||||||
|
Note* m_current_note;
|
||||||
|
Renderer m_renderer;
|
||||||
|
std::size_t detect_note_pressed(Note* note);
|
||||||
|
void init_synth();
|
||||||
|
void init_audio();
|
||||||
|
void update_on_note_input();
|
||||||
|
void play_buffered_audio();
|
||||||
|
void fill_audio_buffer();
|
||||||
|
|
||||||
|
public:
|
||||||
|
Application(/* args */);
|
||||||
|
~Application();
|
||||||
|
void Run();
|
||||||
|
};
|
||||||
|
|
||||||
80
inc/KeyBoard.h
Normal file
80
inc/KeyBoard.h
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Settings.h"
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class KeyBoard
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
/* data */
|
||||||
|
static int get_semitone_shift_internal(const char* root_note, char* target_note) {
|
||||||
|
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
|
||||||
|
int root_note_num = (int)root_note[strlen(root_note) - 1] - '0';
|
||||||
|
|
||||||
|
char* root_pitch_class_str = (char*)malloc((strlen(root_note) - 1) * sizeof(char));
|
||||||
|
strncpy(root_pitch_class_str, root_note, strlen(root_note) - 1);
|
||||||
|
|
||||||
|
int root_pitch_class = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
if (strcmp(pitch_classes[i], root_pitch_class_str) == 0) {
|
||||||
|
root_pitch_class = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(root_pitch_class_str);
|
||||||
|
|
||||||
|
// Extract the note number and pitch class for the target note
|
||||||
|
int target_note_num = (int)target_note[strlen(target_note) - 1] - '0';
|
||||||
|
|
||||||
|
char* target_pitch_class_str =
|
||||||
|
(char*)malloc((strlen(target_note) - 1) * sizeof(char));
|
||||||
|
strncpy(target_pitch_class_str, target_note, strlen(target_note) - 1);
|
||||||
|
|
||||||
|
int target_pitch_class = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
if (strcmp(pitch_classes[i], target_pitch_class_str) == 0) {
|
||||||
|
target_pitch_class = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(target_pitch_class_str);
|
||||||
|
|
||||||
|
// Calculate the semitone shift using the formula
|
||||||
|
return (target_note_num - root_note_num) * 12 +
|
||||||
|
(target_pitch_class - root_pitch_class);
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
KeyBoard(/* args */);
|
||||||
|
~KeyBoard();
|
||||||
|
|
||||||
|
static float GetHzBySemitone(int semitone) {
|
||||||
|
return PITCH_STANDARD * powf(powf(2.f, (1.f / 12.f)), semitone);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int GetSemitoneShift(const std::string& target_note) {
|
||||||
|
char* target_note_cstr = new char[target_note.length() + 1];
|
||||||
|
strcpy(target_note_cstr, target_note.c_str());
|
||||||
|
|
||||||
|
int result = get_semitone_shift_internal("A4", target_note_cstr);
|
||||||
|
|
||||||
|
delete[] target_note_cstr;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
KeyBoard::KeyBoard(/* args */)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyBoard::~KeyBoard()
|
||||||
|
{
|
||||||
|
}
|
||||||
6
inc/Logger.h
Normal file
6
inc/Logger.h
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "cstdio"
|
||||||
|
|
||||||
|
#define write_log(format,args...) do { \
|
||||||
|
printf(format, ## args); \
|
||||||
|
} while(0)
|
||||||
8
inc/Note.h
Normal file
8
inc/Note.h
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
struct Note {
|
||||||
|
std::string& name;
|
||||||
|
int length;
|
||||||
|
};
|
||||||
40
inc/Oscillator.h
Normal file
40
inc/Oscillator.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
#include<vector>
|
||||||
|
#include "OscillatorType.h"
|
||||||
|
|
||||||
|
|
||||||
|
class Oscillator
|
||||||
|
{
|
||||||
|
//typedef float (Oscillator::*OscFunction)(void);
|
||||||
|
//typedef float (Oscillator::*DtFunction)(float);
|
||||||
|
|
||||||
|
private:
|
||||||
|
OscillatorType m_osc;
|
||||||
|
float m_freq;
|
||||||
|
float m_volume;
|
||||||
|
float m_phase;
|
||||||
|
float m_phase_dt;
|
||||||
|
//значение типа "float (Oscillator::*)()" нельзя присвоить сущности типа "float (*)()"
|
||||||
|
float (Oscillator::*m_osc_function)(void);
|
||||||
|
float (Oscillator::*m_dt_function)(float freq);
|
||||||
|
void sine_osc_phase_incr();
|
||||||
|
void saw_osc_phase_incr();
|
||||||
|
float calc_saw_phase_delta(float freq);
|
||||||
|
float calc_sine_phase_delta(float freq);
|
||||||
|
float sawosc();
|
||||||
|
float triangleosc();
|
||||||
|
float squareosc();
|
||||||
|
float sign(float v);
|
||||||
|
float sineosc();
|
||||||
|
public:
|
||||||
|
Oscillator(OscillatorType osc, float 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);
|
||||||
|
};
|
||||||
7
inc/OscillatorType.h
Normal file
7
inc/OscillatorType.h
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
typedef enum {
|
||||||
|
Sine,
|
||||||
|
Triangle,
|
||||||
|
Saw,
|
||||||
|
Square
|
||||||
|
} OscillatorType;
|
||||||
27
inc/Renderer.h
Normal file
27
inc/Renderer.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Synth.h"
|
||||||
|
#include "SynthGuiState.h"
|
||||||
|
#include <vector>
|
||||||
|
#include "raylib.h"
|
||||||
|
|
||||||
|
class Renderer
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
void DrawMainPanel(const Rectangle& panel_bounds);
|
||||||
|
void DrawAddOscillatorButton(Synth & synth, SynthGuiState & synthGui, Rectangle panel_bounds);
|
||||||
|
void DrawOscillatorsPanels(const std::vector<Oscillator*>& oscillators,
|
||||||
|
const std::vector<OscillatorGuiState*>& guiOscillators,
|
||||||
|
const Rectangle& panel_bounds);
|
||||||
|
void DrawOscillatorsShapeInputs(const std::vector<Oscillator*>& oscillators,
|
||||||
|
const std::vector<OscillatorGuiState*>& guiOscillators);
|
||||||
|
void DrawUi(Synth & synth, SynthGuiState & synthGui);
|
||||||
|
void DrawSignal(Synth & synth, SynthGuiState & synthGui);
|
||||||
|
public:
|
||||||
|
Renderer(/* args */);
|
||||||
|
~Renderer();
|
||||||
|
void Draw(Synth& synth, SynthGuiState & synthGui);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
131
inc/RingBuffer.h
Normal file
131
inc/RingBuffer.h
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cstddef>
|
||||||
|
#include "Logger.h"
|
||||||
|
template <typename T>
|
||||||
|
class RingBuffer
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
T* m_items; /* data */
|
||||||
|
std::size_t m_head;
|
||||||
|
std::size_t m_tail;
|
||||||
|
bool m_is_full;
|
||||||
|
bool m_is_empty;
|
||||||
|
std::size_t m_size;
|
||||||
|
void advance_pointer();
|
||||||
|
void retreat_pointer();
|
||||||
|
public:
|
||||||
|
RingBuffer(std::size_t size);
|
||||||
|
~RingBuffer();
|
||||||
|
bool IsFull() { return m_is_full; }
|
||||||
|
bool IsEmpty() { return m_is_empty; }
|
||||||
|
std::size_t GetSize();
|
||||||
|
std::size_t GetCapacity() { return m_size; }
|
||||||
|
void Reset();
|
||||||
|
void Write(T* data, size_t count);
|
||||||
|
bool Read(T* output, size_t count);
|
||||||
|
void Print();
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T> RingBuffer<T>::RingBuffer(std::size_t size)
|
||||||
|
{
|
||||||
|
m_items = new T[size];
|
||||||
|
m_head = 0;
|
||||||
|
m_tail = 0;
|
||||||
|
m_is_full = 0;
|
||||||
|
m_is_empty = 1;
|
||||||
|
m_size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> RingBuffer<T>::~RingBuffer()
|
||||||
|
{
|
||||||
|
delete[] m_items;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> void RingBuffer<T>::Reset()
|
||||||
|
{
|
||||||
|
m_head = 0;
|
||||||
|
m_tail = 0;
|
||||||
|
m_is_full = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> void RingBuffer<T>::advance_pointer()
|
||||||
|
{
|
||||||
|
if (m_is_full) {
|
||||||
|
m_tail++;
|
||||||
|
if (m_tail == m_size) {
|
||||||
|
m_tail = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_head++;
|
||||||
|
if (m_head == m_size) {
|
||||||
|
m_head = 0;
|
||||||
|
}
|
||||||
|
std::size_t p_is_full = m_head == m_tail ? 1 : 0;
|
||||||
|
m_is_full = p_is_full;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> void RingBuffer<T>::retreat_pointer()
|
||||||
|
{
|
||||||
|
m_is_full = 0;
|
||||||
|
m_tail++;
|
||||||
|
if (m_tail == m_size) {
|
||||||
|
m_tail = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> void RingBuffer<T>::Write(T* data, std::size_t count)
|
||||||
|
{
|
||||||
|
if (m_is_full || m_head + count > m_size) {
|
||||||
|
write_log("[WARN] Trying to overfill the ring buffer: \n\tIsFull:%d\n\tHead:%zu\n\tCount:%zu\n\t",
|
||||||
|
m_is_full,
|
||||||
|
m_head,
|
||||||
|
count);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_is_empty = 0;
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < count; i++) {
|
||||||
|
m_items[m_head] = data[i];
|
||||||
|
advance_pointer();
|
||||||
|
}
|
||||||
|
//m_is_empty = m_is_full && (m_head == m_tail);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> bool RingBuffer<T>::Read(T* output, std::size_t count)
|
||||||
|
{
|
||||||
|
if (m_is_empty) {
|
||||||
|
write_log("[WARN] Trying to read empty buffer");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < count; i++) {
|
||||||
|
output[i] = m_items[m_tail];
|
||||||
|
retreat_pointer();
|
||||||
|
}
|
||||||
|
m_is_empty = !m_is_full && (m_head == m_tail);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> std::size_t RingBuffer<T>::GetSize()
|
||||||
|
{
|
||||||
|
size_t p_size = m_size;
|
||||||
|
if(!m_is_full) {
|
||||||
|
if(m_head >= m_tail) {
|
||||||
|
p_size = (m_head - m_tail);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
p_size = (m_size + m_head - m_tail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
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",
|
||||||
|
m_is_full,
|
||||||
|
m_is_empty,
|
||||||
|
m_head,
|
||||||
|
m_tail);
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
#ifndef SETTINGS_H
|
#pragma once
|
||||||
#define SETTINGS_H
|
|
||||||
|
|
||||||
#define SAMPLE_RATE 48000.f
|
#define SAMPLE_RATE 48000.f
|
||||||
#define BPM 120.f
|
#define BPM 120.f
|
||||||
@@ -15,5 +14,3 @@
|
|||||||
#define WINDOW_WIDTH 640
|
#define WINDOW_WIDTH 640
|
||||||
#define WINDOW_HEIGHT 480
|
#define WINDOW_HEIGHT 480
|
||||||
#define OSCILLATOR_PANEL_WIDTH 200
|
#define OSCILLATOR_PANEL_WIDTH 200
|
||||||
|
|
||||||
#endif
|
|
||||||
26
inc/Synth.h
Normal file
26
inc/Synth.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "Oscillator.h"
|
||||||
|
#include "Note.h"
|
||||||
|
#include "Adder.h"
|
||||||
|
#include "Settings.h"
|
||||||
|
|
||||||
|
class Synth
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
std::vector<Oscillator*> m_oscillators;
|
||||||
|
Adder m_adder;
|
||||||
|
//OscillatorUI* ui_oscillators;
|
||||||
|
//Note m_current_note;
|
||||||
|
std::vector<float> m_out_signal;
|
||||||
|
std::vector<float> & get_note(int semitone, float beats);
|
||||||
|
|
||||||
|
public:
|
||||||
|
Synth(/* args */);
|
||||||
|
~Synth();
|
||||||
|
void ProduceNoteSound(Note input);
|
||||||
|
void AddOscillator();
|
||||||
|
const std::vector<float> & GetOutSignal() { return m_out_signal; }
|
||||||
|
const std::vector<Oscillator*>& GetOscillators() { return m_oscillators; }
|
||||||
|
};
|
||||||
16
inc/SynthGuiState.h
Normal file
16
inc/SynthGuiState.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "OscillatorType.h"
|
||||||
|
#include "raygui.h"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct OscillatorGuiState {
|
||||||
|
float volume;
|
||||||
|
float freq;//todo: remove or change to pitch shift
|
||||||
|
OscillatorType waveshape;
|
||||||
|
bool is_dropdown_open;
|
||||||
|
Rectangle shape_dropdown_rect;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SynthGuiState {
|
||||||
|
std::vector<OscillatorGuiState*> oscillators;
|
||||||
|
};
|
||||||
449
main.c
449
main.c
@@ -1,449 +0,0 @@
|
|||||||
#include "stdlib.h"
|
|
||||||
#include "stdio.h"
|
|
||||||
#include "string.h"
|
|
||||||
#include "math.h"
|
|
||||||
|
|
||||||
#include "parser.h"
|
|
||||||
#include "utils.h"
|
|
||||||
#include "ring_buffer.h"
|
|
||||||
#include "settings.h"
|
|
||||||
#include "oscillator.h"
|
|
||||||
#include "export.h"
|
|
||||||
|
|
||||||
#include "raylib.h"
|
|
||||||
#define RAYGUI_IMPLEMENTATION
|
|
||||||
#include "raygui.h"
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------------
|
|
||||||
// Synth
|
|
||||||
//------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
typedef struct OscillatorUI {
|
|
||||||
float volume;
|
|
||||||
float freq;//todo: remove or change to pitch shift
|
|
||||||
OscillatorType waveshape;
|
|
||||||
bool is_dropdown_open;
|
|
||||||
Rectangle shape_dropdown_rect;
|
|
||||||
} OscillatorUI;
|
|
||||||
|
|
||||||
typedef struct Synth {
|
|
||||||
OscillatorArray oscillators;
|
|
||||||
OscillatorUI* ui_oscillators;
|
|
||||||
Note current_note;
|
|
||||||
SynthSound* out_signal;
|
|
||||||
} Synth;
|
|
||||||
|
|
||||||
static int get_semitone_shift_internal(char* root_note, char* target_note) {
|
|
||||||
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
|
|
||||||
int root_note_num = (int)root_note[strlen(root_note) - 1] - '0';
|
|
||||||
|
|
||||||
char* root_pitch_class_str = malloc((strlen(root_note) - 1) * sizeof(char));
|
|
||||||
strncpy(root_pitch_class_str, root_note, strlen(root_note) - 1);
|
|
||||||
|
|
||||||
int root_pitch_class = -1;
|
|
||||||
|
|
||||||
for (int i = 0; i < 12; i++) {
|
|
||||||
if (strcmp(pitch_classes[i], root_pitch_class_str) == 0) {
|
|
||||||
root_pitch_class = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
free(root_pitch_class_str);
|
|
||||||
|
|
||||||
// Extract the note number and pitch class for the target note
|
|
||||||
int target_note_num = (int)target_note[strlen(target_note) - 1] - '0';
|
|
||||||
|
|
||||||
char* target_pitch_class_str =
|
|
||||||
malloc((strlen(target_note) - 1) * sizeof(char));
|
|
||||||
strncpy(target_pitch_class_str, target_note, strlen(target_note) - 1);
|
|
||||||
|
|
||||||
int target_pitch_class = -1;
|
|
||||||
|
|
||||||
for (int i = 0; i < 12; i++) {
|
|
||||||
if (strcmp(pitch_classes[i], target_pitch_class_str) == 0) {
|
|
||||||
target_pitch_class = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
free(target_pitch_class_str);
|
|
||||||
|
|
||||||
// Calculate the semitone shift using the formula
|
|
||||||
return (target_note_num - root_note_num) * 12 +
|
|
||||||
(target_pitch_class - root_pitch_class);
|
|
||||||
}
|
|
||||||
|
|
||||||
static float get_hz_by_semitone(int semitone) {
|
|
||||||
return PITCH_STANDARD * powf(powf(2.f, (1.f / 12.f)), semitone);
|
|
||||||
}
|
|
||||||
|
|
||||||
int get_semitone_shift(char* target_note) {
|
|
||||||
return get_semitone_shift_internal("A4", target_note);
|
|
||||||
}
|
|
||||||
|
|
||||||
static OscillatorArray init_osc_array() {
|
|
||||||
Oscillator first = {
|
|
||||||
.osc = Square,
|
|
||||||
.freq = 440.f,
|
|
||||||
.volume = VOLUME
|
|
||||||
};
|
|
||||||
|
|
||||||
Oscillator* oscArray = malloc(sizeof(Oscillator*) * 1);
|
|
||||||
oscArray[0] = first;
|
|
||||||
|
|
||||||
OscillatorArray oscillators = {
|
|
||||||
.array = oscArray,
|
|
||||||
.count = 1
|
|
||||||
};
|
|
||||||
|
|
||||||
return oscillators;
|
|
||||||
}
|
|
||||||
|
|
||||||
SynthSound note(Synth* synth, int semitone, float beats) {
|
|
||||||
float hz = get_hz_by_semitone(semitone);
|
|
||||||
float duration = beats * BEAT_DURATION;
|
|
||||||
|
|
||||||
// will change after oscillator starts to be more autonomous
|
|
||||||
for (size_t i = 0; i < synth->oscillators.count; i++) {
|
|
||||||
synth->oscillators.array[i].freq = hz;
|
|
||||||
}
|
|
||||||
|
|
||||||
return freq(duration, synth->oscillators);
|
|
||||||
}
|
|
||||||
|
|
||||||
SynthSound get_note_sound(Synth* synth, Note input) {
|
|
||||||
float length = 1.f / input.length;
|
|
||||||
int semitone_shift = get_semitone_shift(input.name);
|
|
||||||
return note(synth, semitone_shift, length);
|
|
||||||
}
|
|
||||||
//-------------------------------------------------------
|
|
||||||
|
|
||||||
size_t detect_note_pressed(Note* note) {
|
|
||||||
size_t is_pressed = 0;
|
|
||||||
note->length = 8;
|
|
||||||
if (IsKeyPressed(KEY_A)) {
|
|
||||||
strcpy(note->name, "A4");
|
|
||||||
is_pressed = 1;
|
|
||||||
}
|
|
||||||
if (IsKeyPressed(KEY_B)) {
|
|
||||||
strcpy(note->name, "B4");
|
|
||||||
is_pressed = 1;
|
|
||||||
}
|
|
||||||
if (IsKeyPressed(KEY_C)) {
|
|
||||||
strcpy(note->name, "C4");
|
|
||||||
is_pressed = 1;
|
|
||||||
}
|
|
||||||
if (IsKeyPressed(KEY_D)) {
|
|
||||||
strcpy(note->name, "D4");
|
|
||||||
is_pressed = 1;
|
|
||||||
}
|
|
||||||
if (IsKeyPressed(KEY_E)) {
|
|
||||||
strcpy(note->name, "E4");
|
|
||||||
is_pressed = 1;
|
|
||||||
}
|
|
||||||
if (IsKeyPressed(KEY_F)) {
|
|
||||||
strcpy(note->name, "F4");
|
|
||||||
is_pressed = 1;
|
|
||||||
}
|
|
||||||
if (IsKeyPressed(KEY_G)) {
|
|
||||||
strcpy(note->name, "G4");
|
|
||||||
is_pressed = 1;
|
|
||||||
}
|
|
||||||
return is_pressed;
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------------
|
|
||||||
// GUI
|
|
||||||
//------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
void DrawUi(Synth *synth) {
|
|
||||||
const int panel_x_start = 0;
|
|
||||||
const int panel_y_start = 0;
|
|
||||||
const int panel_width = OSCILLATOR_PANEL_WIDTH;
|
|
||||||
const int panel_height = WINDOW_HEIGHT;
|
|
||||||
|
|
||||||
bool is_shape_dropdown_open = false;
|
|
||||||
int shape_index = 0;
|
|
||||||
|
|
||||||
|
|
||||||
GuiPanel((Rectangle){
|
|
||||||
panel_x_start,
|
|
||||||
panel_y_start,
|
|
||||||
panel_width,
|
|
||||||
panel_height
|
|
||||||
},
|
|
||||||
"");
|
|
||||||
|
|
||||||
bool click_add_oscillator = GuiButton((Rectangle){
|
|
||||||
panel_x_start + 10,
|
|
||||||
panel_y_start + 10,
|
|
||||||
panel_width - 20,
|
|
||||||
25
|
|
||||||
}, "Add Oscillator");
|
|
||||||
if (click_add_oscillator)
|
|
||||||
{
|
|
||||||
// synth->ui_oscillator_count += 1;
|
|
||||||
// // Set defaults:
|
|
||||||
// UiOscillator *ui_osc = synth->ui_oscillator + (synth->ui_oscillator_count - 1);
|
|
||||||
// ui_osc->shape = WaveShape_SINE;
|
|
||||||
// ui_osc->freq = BASE_NOTE_FREQ;
|
|
||||||
// ui_osc->amplitude_ratio = 0.1f;
|
|
||||||
// ui_osc->shape_parameter_0 = 0.5f;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw Oscillators
|
|
||||||
float panel_y_offset = 0;
|
|
||||||
//synth->ui_oscillator_count = 1
|
|
||||||
for (int ui_osc_i = 0; ui_osc_i < synth->oscillators.count; ui_osc_i++)
|
|
||||||
{
|
|
||||||
OscillatorUI* ui_osc = &synth->ui_oscillators[ui_osc_i];
|
|
||||||
Oscillator* osc = &synth->oscillators.array[ui_osc_i];
|
|
||||||
const bool has_shape_param = (ui_osc->waveshape == Square);
|
|
||||||
|
|
||||||
// Draw Oscillator Panel
|
|
||||||
const int osc_panel_width = panel_width - 20;
|
|
||||||
const int osc_panel_height = has_shape_param ? 130 : 100;
|
|
||||||
const int osc_panel_x = panel_x_start + 10;
|
|
||||||
const int osc_panel_y = panel_y_start + 50 + panel_y_offset;
|
|
||||||
panel_y_offset += osc_panel_height + 5;
|
|
||||||
GuiPanel((Rectangle){
|
|
||||||
osc_panel_x,
|
|
||||||
osc_panel_y,
|
|
||||||
osc_panel_width,
|
|
||||||
osc_panel_height
|
|
||||||
},
|
|
||||||
"");
|
|
||||||
|
|
||||||
const float slider_padding = 50.f;
|
|
||||||
const float el_spacing = 5.f;
|
|
||||||
Rectangle el_rect = {
|
|
||||||
.x = osc_panel_x + slider_padding + 30,
|
|
||||||
.y = osc_panel_y + 10,
|
|
||||||
.width = osc_panel_width - (slider_padding * 2),
|
|
||||||
.height = 25
|
|
||||||
};
|
|
||||||
|
|
||||||
// Volume slider
|
|
||||||
float decibels = (20.f * log10f(osc->volume));
|
|
||||||
char amp_slider_label[32];
|
|
||||||
sprintf(amp_slider_label, "%.1f dB", decibels);
|
|
||||||
decibels = GuiSlider(el_rect,
|
|
||||||
amp_slider_label,
|
|
||||||
"",
|
|
||||||
decibels,
|
|
||||||
-60.0f,
|
|
||||||
0.0f
|
|
||||||
);
|
|
||||||
ui_osc->volume = powf(10.f, decibels * (1.f/20.f));
|
|
||||||
osc->volume = ui_osc->volume;
|
|
||||||
|
|
||||||
el_rect.y += el_rect.height + el_spacing;
|
|
||||||
|
|
||||||
// Defer shape drop-down box.
|
|
||||||
ui_osc->shape_dropdown_rect = el_rect;
|
|
||||||
el_rect.y += el_rect.height + el_spacing;
|
|
||||||
/*
|
|
||||||
Rectangle delete_button_rect = el_rect;
|
|
||||||
delete_button_rect.x = osc_panel_x + 5;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
// DRAW OSCILLATOR SHAPE INPUTS
|
|
||||||
for (int ui_osc_i = 0; ui_osc_i < synth->oscillators.count; ui_osc_i += 1)
|
|
||||||
{
|
|
||||||
OscillatorUI* ui_osc = &synth->ui_oscillators[ui_osc_i];
|
|
||||||
Oscillator* osc = &synth->oscillators.array[ui_osc_i];
|
|
||||||
// Shape select
|
|
||||||
int shape_index = (int)(ui_osc->waveshape);
|
|
||||||
bool is_dropdown_click = GuiDropdownBox(ui_osc->shape_dropdown_rect,
|
|
||||||
WAVE_SHAPE_OPTIONS,
|
|
||||||
&shape_index,
|
|
||||||
ui_osc->is_dropdown_open
|
|
||||||
);
|
|
||||||
|
|
||||||
if (is_dropdown_click)
|
|
||||||
{
|
|
||||||
ui_osc->is_dropdown_open = !ui_osc->is_dropdown_open;
|
|
||||||
ui_osc->waveshape = (OscillatorType)(shape_index);
|
|
||||||
// APPLY STATE TO REAL OSC
|
|
||||||
osc->osc = (OscillatorType)(shape_index);
|
|
||||||
}
|
|
||||||
if (ui_osc->is_dropdown_open) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawSignal(Synth* synth) {
|
|
||||||
GuiGrid((Rectangle){0, 0, WINDOW_WIDTH, WINDOW_HEIGHT}, "", WINDOW_HEIGHT / 8, 2);
|
|
||||||
Vector2* signal_points = malloc(sizeof(Vector2) * synth->out_signal->sample_count);
|
|
||||||
const float screen_vertical_midpoint = (WINDOW_HEIGHT/2);
|
|
||||||
for (int point_idx = 0; point_idx < synth->out_signal->sample_count; point_idx++)
|
|
||||||
{
|
|
||||||
signal_points[point_idx].x = (float)point_idx + OSCILLATOR_PANEL_WIDTH;
|
|
||||||
signal_points[point_idx].y = screen_vertical_midpoint + (int)(synth->out_signal->samples[point_idx] * 300);
|
|
||||||
}
|
|
||||||
DrawLineStrip(signal_points, synth->out_signal->sample_count, RED);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------------
|
|
||||||
// Main
|
|
||||||
//------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
|
||||||
InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "SeeSynth - v0.2");
|
|
||||||
SetTargetFPS(60);
|
|
||||||
|
|
||||||
//todo: move that variables to Synth declaration
|
|
||||||
Note g_current_note = {
|
|
||||||
.length = 1,
|
|
||||||
.name = malloc(sizeof(char) * 3)
|
|
||||||
};
|
|
||||||
|
|
||||||
SynthSound g_sound = {
|
|
||||||
.sample_count = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
Synth synth = {
|
|
||||||
.current_note = g_current_note,
|
|
||||||
.out_signal = &g_sound,
|
|
||||||
.oscillators = init_osc_array()
|
|
||||||
};
|
|
||||||
//todo: move somewhere in initialization
|
|
||||||
synth.ui_oscillators = malloc(sizeof(OscillatorUI) * synth.oscillators.count);
|
|
||||||
for (size_t i = 0; i < synth.oscillators.count; i++)
|
|
||||||
{
|
|
||||||
OscillatorUI* ui = &synth.ui_oscillators[i];
|
|
||||||
ui->freq = synth.oscillators.array[i].freq;
|
|
||||||
ui->waveshape = synth.oscillators.array[i].osc;
|
|
||||||
ui->volume = synth.oscillators.array[i].volume;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sound_played_count = 0;
|
|
||||||
float temp_buffer[STREAM_BUFFER_SIZE];
|
|
||||||
RingBuffer ring_buffer = ring_buffer_init(STREAM_BUFFER_SIZE);
|
|
||||||
|
|
||||||
InitAudioDevice();
|
|
||||||
SetMasterVolume(SYNTH_VOLUME);
|
|
||||||
SetAudioStreamBufferSizeDefault(STREAM_BUFFER_SIZE);
|
|
||||||
AudioStream synth_stream = LoadAudioStream(SAMPLE_RATE, sizeof(float) * 8, 1);
|
|
||||||
SetAudioStreamVolume(synth_stream, 0.5f);
|
|
||||||
|
|
||||||
PlayAudioStream(synth_stream);
|
|
||||||
|
|
||||||
// Main game loop
|
|
||||||
while (!WindowShouldClose()) // Detect window close button or ESC key
|
|
||||||
{
|
|
||||||
// Update Audio states
|
|
||||||
//----------------------------------------------------------------------------------
|
|
||||||
// Fill ring buffer from current sound
|
|
||||||
SynthSound* sound = synth.out_signal;
|
|
||||||
size_t size_for_buffer = 0;
|
|
||||||
if (!ring_buffer.is_full && sound->sample_count != sound_played_count) {
|
|
||||||
write_log("[INFO] IsFull:%d Samples:%zu Played:%zu\n",
|
|
||||||
ring_buffer.is_full,
|
|
||||||
sound->sample_count,
|
|
||||||
sound_played_count);
|
|
||||||
|
|
||||||
// how many samples need write
|
|
||||||
size_t size_to_fill = 0;
|
|
||||||
|
|
||||||
if ((sound->sample_count - sound_played_count) > ring_buffer.size) {
|
|
||||||
size_to_fill = ring_buffer.size;
|
|
||||||
} else {
|
|
||||||
size_to_fill = sound->sample_count - sound_played_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
write_log("[INFO] SizeToFill:%zu\n", size_to_fill);
|
|
||||||
for (size_t i = 0; i < size_to_fill; i++) {
|
|
||||||
temp_buffer[i] = sound->samples[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
ring_buffer_write(&ring_buffer, temp_buffer, size_to_fill);
|
|
||||||
sound_played_count += size_to_fill;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Play ring-buffered audio
|
|
||||||
if (IsAudioStreamProcessed(synth_stream) && !ring_buffer.is_empty) {
|
|
||||||
size_t size_to_read = ring_buffer_size(&ring_buffer);
|
|
||||||
|
|
||||||
write_log("Samples to play:%zu \n", size_to_read);
|
|
||||||
//todo: try to start reading directly from ring buffer, avoiding temp_buffer
|
|
||||||
ring_buffer_read(&ring_buffer, temp_buffer, size_to_read);
|
|
||||||
// can try the SetAudioStreamCallback
|
|
||||||
UpdateAudioStream(synth_stream, temp_buffer, size_to_read);
|
|
||||||
// can overwrite the ring buffer to avoid that
|
|
||||||
if (sound->sample_count == sound_played_count) {
|
|
||||||
ring_buffer_reset(&ring_buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//----------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Update On Input
|
|
||||||
//----------------------------------------------------------------------------------
|
|
||||||
Note current_note = synth.current_note;
|
|
||||||
if (detect_note_pressed(¤t_note)) {
|
|
||||||
*sound = get_note_sound(&synth, current_note);
|
|
||||||
sound_played_count = 0;
|
|
||||||
write_log("Note played: %s\n", current_note.name);
|
|
||||||
}
|
|
||||||
//----------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Draw
|
|
||||||
//----------------------------------------------------------------------------------
|
|
||||||
BeginDrawing();
|
|
||||||
|
|
||||||
ClearBackground(RAYWHITE);
|
|
||||||
DrawUi(&synth);
|
|
||||||
DrawSignal(&synth);
|
|
||||||
//DrawText("Congrats! You created your first window!", 190, 200, 20, LIGHTGRAY);
|
|
||||||
//DrawFPS(0,0);
|
|
||||||
|
|
||||||
EndDrawing();
|
|
||||||
//----------------------------------------------------------------------------------
|
|
||||||
}
|
|
||||||
|
|
||||||
char* input = "A4-4 A4-4 A4-4 A4-4 A4-2 A4-4 A4-4 A4-4 A4-4 A4-4 A4-2 D5-4 D5-4 D5-4 D5-4 D5-4 D5-4 D5-2 C5-4 C5-4 C5-4 C5-4 C5-4 C5-4 C5-2 G4-2 ";
|
|
||||||
char* buf = malloc(strlen(input) + 1);
|
|
||||||
strcpy(buf, input);
|
|
||||||
|
|
||||||
NoteArray note_array = parse_notes(buf, strlen(buf));
|
|
||||||
SynthSound* sounds = malloc(sizeof(SynthSound) * note_array.count);
|
|
||||||
for (size_t i = 0; i < note_array.count; i++) {
|
|
||||||
Note note = note_array.notes[i];
|
|
||||||
sounds[i] = get_note_sound(&synth, note);
|
|
||||||
}
|
|
||||||
|
|
||||||
SynthSound song = concat_sounds(sounds, note_array.count);
|
|
||||||
uint16_t* song_pcm = malloc(sizeof(uint16_t) * song.sample_count);
|
|
||||||
for (size_t i = 0; i < song.sample_count; i++) {
|
|
||||||
song_pcm[i] = toInt16Sample(song.samples[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pack(song_pcm, song.sample_count);
|
|
||||||
|
|
||||||
// De-Initialization
|
|
||||||
//--------------------------------------------------------------------------------------
|
|
||||||
StopAudioStream(synth_stream);
|
|
||||||
UnloadAudioStream(synth_stream);
|
|
||||||
CloseAudioDevice();
|
|
||||||
CloseWindow(); // Close window and OpenGL context
|
|
||||||
//--------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
153
oscillator.c
153
oscillator.c
@@ -1,153 +0,0 @@
|
|||||||
#include "oscillator.h"
|
|
||||||
#include "settings.h"
|
|
||||||
#include "math.h"
|
|
||||||
#include "stdlib.h"
|
|
||||||
|
|
||||||
static SynthSound get_init_samples(float duration) {
|
|
||||||
size_t sample_count = (size_t)(duration * SAMPLE_RATE);
|
|
||||||
float* samples = malloc(sizeof(float) * sample_count);
|
|
||||||
|
|
||||||
for (double i = 0.0; i < duration * SAMPLE_RATE; i++) {
|
|
||||||
samples[(int)i] = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
SynthSound res = {
|
|
||||||
.samples = samples,
|
|
||||||
.sample_count = sample_count
|
|
||||||
};
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
static float pos(float hz, float x) {
|
|
||||||
return fmodf(hz * x / SAMPLE_RATE, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static float sineosc(float hz, float x) {
|
|
||||||
return sinf(x * (2.f * SYNTH_PI * hz / SAMPLE_RATE));
|
|
||||||
}
|
|
||||||
|
|
||||||
static float sign(float v) {
|
|
||||||
return (v > 0.0) ? 1.f : -1.f;
|
|
||||||
}
|
|
||||||
|
|
||||||
static float squareosc(float hz, float x) {
|
|
||||||
return sign(sineosc(hz, x));
|
|
||||||
}
|
|
||||||
|
|
||||||
static float triangleosc(float hz, float x) {
|
|
||||||
return 1.f - fabsf(pos(hz, x) - 0.5f) * 4.f;
|
|
||||||
}
|
|
||||||
|
|
||||||
static float sawosc(float hz, float x) {
|
|
||||||
return pos(hz, x) * 2.f - 1.f;
|
|
||||||
}
|
|
||||||
|
|
||||||
float multiosc(OscillatorGenerationParameter param) {
|
|
||||||
float osc_sample = 0.f;
|
|
||||||
for (size_t i = 0; i < param.oscillators.count; i++) {
|
|
||||||
Oscillator osc = param.oscillators.array[i];
|
|
||||||
switch (osc.osc) {
|
|
||||||
case Sine:
|
|
||||||
osc_sample += sineosc(osc.freq, param.sample) * osc.volume;
|
|
||||||
break;
|
|
||||||
case Triangle:
|
|
||||||
osc_sample += triangleosc(osc.freq, param.sample) * osc.volume;
|
|
||||||
break;
|
|
||||||
case Square:
|
|
||||||
osc_sample += squareosc(osc.freq, param.sample) * osc.volume;
|
|
||||||
break;
|
|
||||||
case Saw:
|
|
||||||
osc_sample += sawosc(osc.freq, param.sample) * osc.volume;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return osc_sample;
|
|
||||||
}
|
|
||||||
|
|
||||||
SynthSound freq(float duration, OscillatorArray osc) {
|
|
||||||
SynthSound samples = get_init_samples(duration);
|
|
||||||
// SynthSound attack = get_attack_samples();
|
|
||||||
|
|
||||||
float* output = malloc(sizeof(float) * samples.sample_count);
|
|
||||||
for (int i = 0; i < samples.sample_count; i++) {
|
|
||||||
float sample = samples.samples[i];
|
|
||||||
OscillatorGenerationParameter param = {
|
|
||||||
.oscillators = osc,
|
|
||||||
.sample = sample
|
|
||||||
};
|
|
||||||
output[i] = multiosc(param);
|
|
||||||
}
|
|
||||||
|
|
||||||
// create attack and release
|
|
||||||
/*
|
|
||||||
let adsrLength = Seq.length output
|
|
||||||
let attackArray = attack |> Seq.take adsrLength
|
|
||||||
let release = Seq.rev attackArray
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
todo: I will change the ADSR approach to an explicit ADSR module(with it's own state)
|
|
||||||
size_t adsr_length = samples.sample_count;
|
|
||||||
float *attackArray = NULL, *releaseArray = NULL;
|
|
||||||
|
|
||||||
if (adsr_length > 0) {
|
|
||||||
//todo: calloc
|
|
||||||
attackArray = malloc(sizeof(float) * adsr_length);
|
|
||||||
size_t attack_length = attack.sample_count < adsr_length
|
|
||||||
? attack.sample_count
|
|
||||||
: adsr_length;
|
|
||||||
|
|
||||||
memcpy(attackArray, attack.samples, attack_length);
|
|
||||||
//todo: calloc
|
|
||||||
releaseArray = malloc(sizeof(float) * adsr_length);
|
|
||||||
memcpy(releaseArray, attackArray, attack_length);
|
|
||||||
reverse_array(releaseArray, 0, adsr_length);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// if (samples.sample_count > 1024) {
|
|
||||||
// samples.sample_count = 1024;
|
|
||||||
// }
|
|
||||||
// //todo: move to somewhere
|
|
||||||
// for (size_t i = 0; i < 1024; i++) {
|
|
||||||
// synth_sound.samples[i] = 0.0f;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// for (size_t i = 0; i < samples.sample_count; i++) {
|
|
||||||
// synth_sound.samples[i] = output[i];
|
|
||||||
// }
|
|
||||||
// synth_sound.sample_count = samples.sample_count;
|
|
||||||
|
|
||||||
|
|
||||||
// return zipped array
|
|
||||||
SynthSound res = {
|
|
||||||
.samples = output,
|
|
||||||
.sample_count = samples.sample_count
|
|
||||||
};
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
static SynthSound get_attack_samples() {
|
|
||||||
float attack_time = 0.001 * ATTACK_MS;
|
|
||||||
size_t sample_count = (size_t)(attack_time * SAMPLE_RATE);
|
|
||||||
float* attack = malloc(sizeof(float) * sample_count);
|
|
||||||
float samples_to_rise = SAMPLE_RATE * attack_time;
|
|
||||||
float rising_delta = 1.0 / samples_to_rise;
|
|
||||||
float i = 0.0;
|
|
||||||
|
|
||||||
for (int j = 0; j < sample_count; j++) {
|
|
||||||
i += rising_delta;
|
|
||||||
attack[j] = fmin(i, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
SynthSound res = {
|
|
||||||
.samples = attack,
|
|
||||||
.sample_count = sample_count
|
|
||||||
};
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
34
oscillator.h
34
oscillator.h
@@ -1,34 +0,0 @@
|
|||||||
#ifndef OSCILLATOR_H
|
|
||||||
#define OSCILLATOR_H
|
|
||||||
|
|
||||||
#include "utils.h"
|
|
||||||
#define WAVE_SHAPE_OPTIONS "Sine;Triangle;Sawtooth;Square"
|
|
||||||
typedef enum {
|
|
||||||
Sine,
|
|
||||||
Triangle,
|
|
||||||
Saw,
|
|
||||||
Square
|
|
||||||
} OscillatorType;
|
|
||||||
|
|
||||||
typedef struct Oscillator {
|
|
||||||
OscillatorType osc;
|
|
||||||
float freq;
|
|
||||||
float volume;
|
|
||||||
} Oscillator;
|
|
||||||
|
|
||||||
typedef struct OscillatorArray {
|
|
||||||
Oscillator* array;
|
|
||||||
size_t count;
|
|
||||||
} OscillatorArray;
|
|
||||||
|
|
||||||
typedef struct OscillatorGenerationParameter {
|
|
||||||
OscillatorArray oscillators;
|
|
||||||
float sample;
|
|
||||||
} OscillatorGenerationParameter;
|
|
||||||
|
|
||||||
float multiosc(OscillatorGenerationParameter param);
|
|
||||||
SynthSound freq(float duration, OscillatorArray osc);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
94
parser.c
94
parser.c
@@ -1,94 +0,0 @@
|
|||||||
#include "parser.h"
|
|
||||||
#include "string.h"
|
|
||||||
#include "stdio.h"
|
|
||||||
|
|
||||||
struct StringArray {
|
|
||||||
char** array;
|
|
||||||
size_t count;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void trim(char* str) {
|
|
||||||
size_t len = strlen(str);
|
|
||||||
while (len > 0 && (str[len - 1] == '\n' || str[len - 1] == ' ')) {
|
|
||||||
str[--len] = '\0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct StringArray parse_note_parts(char* input) {
|
|
||||||
size_t count = 0;
|
|
||||||
size_t i = 0;
|
|
||||||
while (input[i] != '\0') {
|
|
||||||
if (input[i] == ' ')
|
|
||||||
count++;
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
char** array = malloc(sizeof(char*) * count);
|
|
||||||
|
|
||||||
char* sep = " ";
|
|
||||||
char* line = strtok(input, sep);
|
|
||||||
i = 0;
|
|
||||||
while (line != NULL) {
|
|
||||||
array[i] = strdup(line);
|
|
||||||
line = strtok(NULL, sep);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct StringArray result = {
|
|
||||||
.array = array,
|
|
||||||
.count = count
|
|
||||||
};
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
NoteArray parse_notes(char* input, size_t len) {
|
|
||||||
struct StringArray note_strings = parse_note_parts(input);
|
|
||||||
|
|
||||||
NoteArray notes;
|
|
||||||
notes.count = note_strings.count;
|
|
||||||
|
|
||||||
char* end;
|
|
||||||
for (size_t i = 0; i < note_strings.count; i++) {
|
|
||||||
char* line = note_strings.array[i];
|
|
||||||
trim(line);
|
|
||||||
|
|
||||||
char* note_name = strtok(line, "-");
|
|
||||||
char* note_length_str = strtok(NULL, "-");
|
|
||||||
|
|
||||||
int note_length = strtol(note_length_str, &end, 10);
|
|
||||||
if (*end != '\0') {
|
|
||||||
fprintf(stderr,
|
|
||||||
"Failed to parse note length: %s\n", note_length_str);
|
|
||||||
return notes;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* buf = malloc(strlen(note_name) + 1);
|
|
||||||
strcpy(buf, note_name);
|
|
||||||
|
|
||||||
Note note = {
|
|
||||||
.length = note_length,
|
|
||||||
.name = buf
|
|
||||||
};
|
|
||||||
|
|
||||||
notes.notes[i] = note;
|
|
||||||
}
|
|
||||||
|
|
||||||
return notes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
static int test(int argc, char **argv) {
|
|
||||||
char* input = "A4-4 A4-2 C5-8 C5-4 ";
|
|
||||||
char* buf = malloc(strlen(input) + 1);
|
|
||||||
strcpy(buf, input);
|
|
||||||
|
|
||||||
NoteArray note_array = parse_notes(buf, strlen(buf));
|
|
||||||
for (size_t i = 0; i < note_array.count; i++) {
|
|
||||||
Note note = note_array.notes[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
20
parser.h
20
parser.h
@@ -1,20 +0,0 @@
|
|||||||
#ifndef PARSER_H
|
|
||||||
#define PARSER_H
|
|
||||||
|
|
||||||
#include "stdlib.h"
|
|
||||||
|
|
||||||
#define MAX_NOTES 1024
|
|
||||||
|
|
||||||
typedef struct Note {
|
|
||||||
char* name;
|
|
||||||
int length;
|
|
||||||
} Note;
|
|
||||||
|
|
||||||
typedef struct NoteArray {
|
|
||||||
Note notes[MAX_NOTES];
|
|
||||||
size_t count;
|
|
||||||
} NoteArray;
|
|
||||||
|
|
||||||
NoteArray parse_notes(char* input, size_t len);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
#include "ring_buffer.h"
|
|
||||||
#include "utils.h"
|
|
||||||
|
|
||||||
RingBuffer ring_buffer_init(size_t buffer_size) {
|
|
||||||
RingBuffer buffer = {
|
|
||||||
.items = calloc(buffer_size, sizeof(float)),
|
|
||||||
.head = 0,
|
|
||||||
.tail = 0,
|
|
||||||
.is_full = 0,
|
|
||||||
.is_empty = 1,
|
|
||||||
.size = buffer_size
|
|
||||||
};
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ring_buffer_reset(RingBuffer* me) {
|
|
||||||
me->head = 0;
|
|
||||||
me->tail = 0;
|
|
||||||
me->is_full = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// +
|
|
||||||
static void advance_pointer(RingBuffer* me) {
|
|
||||||
if(me->is_full) {
|
|
||||||
me->tail++;
|
|
||||||
if (me->tail == me->size) {
|
|
||||||
me->tail = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
me->head++;
|
|
||||||
if (me->head == me->size) {
|
|
||||||
me->head = 0;
|
|
||||||
}
|
|
||||||
size_t is_full = me->head == me->tail ? 1 : 0;
|
|
||||||
me->is_full = is_full;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -
|
|
||||||
static void retreat_pointer(RingBuffer* me) {
|
|
||||||
me->is_full = 0;
|
|
||||||
me->tail++;
|
|
||||||
if (me->tail == me->size) {
|
|
||||||
me->tail = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ring_buffer_write(RingBuffer* buffer, float* data, size_t count) {
|
|
||||||
if (buffer->is_full || buffer->head + count > buffer->size) {
|
|
||||||
write_log("[WARN] Trying to overfill the ring buffer: \n\tIsFull:%d\n\tHead:%zu\n\tCount:%zu\n\t",
|
|
||||||
buffer->is_full,
|
|
||||||
buffer->head,
|
|
||||||
count);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
buffer->is_empty = 0;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < count; i++) {
|
|
||||||
buffer->items[buffer->head] = data[i];
|
|
||||||
advance_pointer(buffer);
|
|
||||||
}
|
|
||||||
//me->is_empty = is_full && (me->head == me->tail);
|
|
||||||
}
|
|
||||||
|
|
||||||
int ring_buffer_read(RingBuffer* buffer, float* output, size_t count) {
|
|
||||||
if (buffer->is_empty) {
|
|
||||||
write_log("[WARN] Trying to read empty buffer");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < count; i++) {
|
|
||||||
output[i] = buffer->items[buffer->tail];
|
|
||||||
retreat_pointer(buffer);
|
|
||||||
}
|
|
||||||
buffer->is_empty = !buffer->is_full && (buffer->head == buffer->tail);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t ring_buffer_size(RingBuffer* buffer) {
|
|
||||||
size_t size = buffer->size;
|
|
||||||
if(!buffer->is_full) {
|
|
||||||
if(buffer->head >= buffer->tail) {
|
|
||||||
size = (buffer->head - buffer->tail);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
size = (buffer->size + buffer->head - buffer->tail);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ring_buffer_print(RingBuffer* me) {
|
|
||||||
write_log("[INFO] The ring buffer: \n\tIsFull:%d\n\tIsEmpty:%d\n\tHead:%zu\n\tTail:%zu\n\t",
|
|
||||||
me->is_full,
|
|
||||||
me->is_empty,
|
|
||||||
me->head,
|
|
||||||
me->tail);
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#ifndef RING_BUFFER_H
|
|
||||||
#define RING_BUFFER_H
|
|
||||||
|
|
||||||
#include "stdlib.h"
|
|
||||||
|
|
||||||
typedef struct RingBuffer {
|
|
||||||
float* items;
|
|
||||||
size_t head;
|
|
||||||
size_t tail;
|
|
||||||
int is_full;
|
|
||||||
int is_empty;
|
|
||||||
size_t size;
|
|
||||||
} RingBuffer;
|
|
||||||
|
|
||||||
RingBuffer ring_buffer_init(size_t buffer_size);
|
|
||||||
void ring_buffer_reset(RingBuffer* me);
|
|
||||||
void ring_buffer_write(RingBuffer* buffer, float* data, size_t count);
|
|
||||||
int ring_buffer_read(RingBuffer* buffer, float* output, size_t count);
|
|
||||||
size_t ring_buffer_size(RingBuffer* buffer);
|
|
||||||
void ring_buffer_print(RingBuffer* me);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
184
src/Application.cpp
Normal file
184
src/Application.cpp
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
#include "Application.h"
|
||||||
|
#include "Settings.h"
|
||||||
|
#include "Logger.h"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
Application::Application(/* args */)
|
||||||
|
{
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
StopAudioStream(m_synth_stream);
|
||||||
|
UnloadAudioStream(m_synth_stream);
|
||||||
|
CloseAudioDevice();
|
||||||
|
CloseWindow();
|
||||||
|
delete m_ring_buffer;
|
||||||
|
delete[] m_temp_buffer;
|
||||||
|
// todo: move to gui state class destructor (make it a class)
|
||||||
|
for(int i = 0; i < m_synth_gui_state.oscillators.size(); i++) {
|
||||||
|
delete m_synth_gui_state.oscillators[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::init_audio()
|
||||||
|
{
|
||||||
|
m_sound_played_count = 0;
|
||||||
|
|
||||||
|
InitAudioDevice();
|
||||||
|
SetMasterVolume(SYNTH_VOLUME);
|
||||||
|
SetAudioStreamBufferSizeDefault(STREAM_BUFFER_SIZE);
|
||||||
|
m_synth_stream = LoadAudioStream(SAMPLE_RATE, sizeof(float) * 8, 1);
|
||||||
|
SetAudioStreamVolume(m_synth_stream, 0.5f);
|
||||||
|
|
||||||
|
PlayAudioStream(m_synth_stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::init_synth()
|
||||||
|
{
|
||||||
|
//todo: move that variables to Synth declaration
|
||||||
|
std::string* nameString = new std::string(std::string(new char[3]));
|
||||||
|
m_current_note = new Note
|
||||||
|
{
|
||||||
|
.length = 1,
|
||||||
|
.name = (*nameString)
|
||||||
|
};
|
||||||
|
|
||||||
|
//todo: move somewhere in initialization
|
||||||
|
std::vector<Oscillator*> oscillators = m_synth.GetOscillators();
|
||||||
|
m_synth_gui_state.oscillators.reserve(oscillators.size());
|
||||||
|
for (size_t i = 0; i < oscillators.size(); i++)
|
||||||
|
{
|
||||||
|
Oscillator* osc = oscillators[i];
|
||||||
|
assert(osc);
|
||||||
|
|
||||||
|
OscillatorGuiState* ui = new OscillatorGuiState {
|
||||||
|
.freq = osc->GetFreq(),
|
||||||
|
.waveshape = osc->GetType(),
|
||||||
|
.volume = osc->GetVolume()
|
||||||
|
};
|
||||||
|
m_synth_gui_state.oscillators.push_back(ui);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t Application::detect_note_pressed(Note* note)
|
||||||
|
{
|
||||||
|
std::size_t is_pressed = 0;
|
||||||
|
note->length = 8;
|
||||||
|
if (IsKeyPressed(KEY_A))
|
||||||
|
{
|
||||||
|
note->name.assign("A4");
|
||||||
|
is_pressed = 1;
|
||||||
|
}
|
||||||
|
if (IsKeyPressed(KEY_B))
|
||||||
|
{
|
||||||
|
note->name.assign("B4");
|
||||||
|
is_pressed = 1;
|
||||||
|
}
|
||||||
|
if (IsKeyPressed(KEY_C))
|
||||||
|
{
|
||||||
|
note->name.assign("C4");
|
||||||
|
is_pressed = 1;
|
||||||
|
}
|
||||||
|
if (IsKeyPressed(KEY_D))
|
||||||
|
{
|
||||||
|
note->name.assign("D4");
|
||||||
|
is_pressed = 1;
|
||||||
|
}
|
||||||
|
if (IsKeyPressed(KEY_E))
|
||||||
|
{
|
||||||
|
note->name.assign("E4");
|
||||||
|
is_pressed = 1;
|
||||||
|
}
|
||||||
|
if (IsKeyPressed(KEY_F))
|
||||||
|
{
|
||||||
|
note->name.assign("F4");
|
||||||
|
is_pressed = 1;
|
||||||
|
}
|
||||||
|
if (IsKeyPressed(KEY_G))
|
||||||
|
{
|
||||||
|
note->name.assign("G4");
|
||||||
|
is_pressed = 1;
|
||||||
|
}
|
||||||
|
return is_pressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update On Input
|
||||||
|
void Application::update_on_note_input()
|
||||||
|
{
|
||||||
|
if (detect_note_pressed(m_current_note))
|
||||||
|
{
|
||||||
|
m_synth.ProduceNoteSound((*m_current_note));
|
||||||
|
m_sound_played_count = 0;
|
||||||
|
write_log("Note played: %s\n", m_current_note->name.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Play ring-buffered audio
|
||||||
|
void Application::play_buffered_audio()
|
||||||
|
{
|
||||||
|
if (IsAudioStreamProcessed(m_synth_stream) && !m_ring_buffer->IsEmpty())
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
while (!WindowShouldClose()) // Detect window close button or ESC key
|
||||||
|
{
|
||||||
|
fill_audio_buffer();
|
||||||
|
play_buffered_audio();
|
||||||
|
update_on_note_input();
|
||||||
|
|
||||||
|
m_renderer.Draw(m_synth, m_synth_gui_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
112
src/Oscillator.cpp
Normal file
112
src/Oscillator.cpp
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
#include "Oscillator.h"
|
||||||
|
#include "Settings.h"
|
||||||
|
|
||||||
|
#define TWO_PI 2*SYNTH_PI
|
||||||
|
|
||||||
|
Oscillator::Oscillator(OscillatorType osc, float freq, float volume)
|
||||||
|
{
|
||||||
|
SetType(osc);
|
||||||
|
m_freq = freq;
|
||||||
|
m_volume = volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
Oscillator::~Oscillator()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Oscillator::Reset()
|
||||||
|
{
|
||||||
|
m_volume = 0;
|
||||||
|
m_phase = 0;
|
||||||
|
m_phase_dt = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Oscillator::SetType(OscillatorType osc)
|
||||||
|
{
|
||||||
|
m_osc = osc;
|
||||||
|
switch (m_osc) {
|
||||||
|
case Sine:
|
||||||
|
m_osc_function = &Oscillator::sineosc;
|
||||||
|
m_dt_function = &Oscillator::calc_sine_phase_delta;
|
||||||
|
break;
|
||||||
|
case Triangle:
|
||||||
|
m_osc_function = &Oscillator::triangleosc;
|
||||||
|
m_dt_function = &Oscillator::calc_saw_phase_delta;
|
||||||
|
break;
|
||||||
|
case Square:
|
||||||
|
m_osc_function = &Oscillator::squareosc;
|
||||||
|
m_dt_function = &Oscillator::calc_sine_phase_delta;
|
||||||
|
break;
|
||||||
|
case Saw:
|
||||||
|
m_osc_function = &Oscillator::sawosc;
|
||||||
|
m_dt_function = &Oscillator::calc_saw_phase_delta;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Oscillator::SetFreq(float freq)
|
||||||
|
{
|
||||||
|
m_freq = freq;
|
||||||
|
m_phase = 0;
|
||||||
|
m_phase_dt = (this->*m_dt_function)(freq);
|
||||||
|
}
|
||||||
|
|
||||||
|
float Oscillator::GenerateSample(float duration)
|
||||||
|
{
|
||||||
|
return (this->*m_osc_function)() * m_volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Oscillator::sine_osc_phase_incr()
|
||||||
|
{
|
||||||
|
m_phase += m_phase_dt;
|
||||||
|
if (m_phase >= TWO_PI)
|
||||||
|
m_phase -= TWO_PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Oscillator::saw_osc_phase_incr()
|
||||||
|
{
|
||||||
|
m_phase += m_phase_dt;
|
||||||
|
if (m_phase >= 1.0f)
|
||||||
|
m_phase -= 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Oscillator::calc_saw_phase_delta(float freq)
|
||||||
|
{
|
||||||
|
return freq / SAMPLE_RATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Oscillator::calc_sine_phase_delta(float freq)
|
||||||
|
{
|
||||||
|
return (TWO_PI * freq) / SAMPLE_RATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Oscillator::sineosc()
|
||||||
|
{
|
||||||
|
float result = sinf(m_phase);
|
||||||
|
sine_osc_phase_incr();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Oscillator::sign(float v)
|
||||||
|
{
|
||||||
|
return (v > 0.0) ? 1.f : -1.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Oscillator::squareosc()
|
||||||
|
{
|
||||||
|
return sign(sineosc());
|
||||||
|
}
|
||||||
|
|
||||||
|
float Oscillator::triangleosc()
|
||||||
|
{
|
||||||
|
float result = 1.f - fabsf(m_phase - 0.5f) * 4.f;
|
||||||
|
saw_osc_phase_incr();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
float Oscillator::sawosc()
|
||||||
|
{
|
||||||
|
float result = m_phase * 2.f - 1.f;
|
||||||
|
saw_osc_phase_incr();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
196
src/Renderer.cpp
Normal file
196
src/Renderer.cpp
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
#include "Renderer.h"
|
||||||
|
#define RAYGUI_IMPLEMENTATION
|
||||||
|
#include "raygui.h"
|
||||||
|
#include "Settings.h"
|
||||||
|
#include "Logger.h"
|
||||||
|
|
||||||
|
Renderer::Renderer(/* args */)
|
||||||
|
{
|
||||||
|
InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "SeeSynth - v0.2");
|
||||||
|
SetTargetFPS(60);
|
||||||
|
}
|
||||||
|
|
||||||
|
Renderer::~Renderer()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::Draw(Synth& synth, SynthGuiState& synthGui)
|
||||||
|
{
|
||||||
|
BeginDrawing();
|
||||||
|
|
||||||
|
ClearBackground(RAYWHITE);
|
||||||
|
//todo: implement renderer
|
||||||
|
DrawUi(synth, synthGui);
|
||||||
|
DrawSignal(synth, synthGui);
|
||||||
|
//DrawText("Congrats! You created your first window!", 190, 200, 20, LIGHTGRAY);
|
||||||
|
//DrawFPS(0,0);
|
||||||
|
|
||||||
|
EndDrawing();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::DrawSignal(Synth & synth, SynthGuiState & synthGui)
|
||||||
|
{
|
||||||
|
GuiGrid((Rectangle){0, 0, WINDOW_WIDTH, WINDOW_HEIGHT}, "", WINDOW_HEIGHT / 8, 2);
|
||||||
|
auto signal = synth.GetOutSignal();
|
||||||
|
Vector2* signal_points = new Vector2[signal.size()];
|
||||||
|
const float screen_vertical_midpoint = (WINDOW_HEIGHT/2);
|
||||||
|
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].y = screen_vertical_midpoint + (int)(signal[point_idx] * 300);
|
||||||
|
}
|
||||||
|
DrawLineStrip(signal_points, signal.size(), RED);
|
||||||
|
delete[] signal_points;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::DrawOscillatorsShapeInputs(const std::vector<Oscillator*>& oscillators, const std::vector<OscillatorGuiState*>& guiOscillators)
|
||||||
|
{
|
||||||
|
#define WAVE_SHAPE_OPTIONS "Sine;Triangle;Sawtooth;Square"
|
||||||
|
|
||||||
|
// DRAW OSCILLATOR SHAPE INPUTS
|
||||||
|
for (int i = 0; i < oscillators.size(); i += 1)
|
||||||
|
{
|
||||||
|
OscillatorGuiState* ui_osc = guiOscillators[i];
|
||||||
|
assert(ui_osc);
|
||||||
|
|
||||||
|
Oscillator* osc = oscillators[i];
|
||||||
|
assert(osc);
|
||||||
|
|
||||||
|
// Shape select
|
||||||
|
int shape_index = (int)(ui_osc->waveshape);
|
||||||
|
bool is_dropdown_click = GuiDropdownBox(ui_osc->shape_dropdown_rect,
|
||||||
|
WAVE_SHAPE_OPTIONS,
|
||||||
|
&shape_index,
|
||||||
|
ui_osc->is_dropdown_open
|
||||||
|
);
|
||||||
|
|
||||||
|
if (is_dropdown_click)
|
||||||
|
{
|
||||||
|
write_log("Dropdown clicked!\n");
|
||||||
|
ui_osc->is_dropdown_open = !ui_osc->is_dropdown_open;
|
||||||
|
ui_osc->waveshape = (OscillatorType)(shape_index);
|
||||||
|
// APPLY STATE TO REAL OSC
|
||||||
|
osc->SetType(ui_osc->waveshape);
|
||||||
|
}
|
||||||
|
if (ui_osc->is_dropdown_open) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::DrawOscillatorsPanels(const std::vector<Oscillator*>& oscillators,
|
||||||
|
const std::vector<OscillatorGuiState*>& guiOscillators,
|
||||||
|
const Rectangle& panel_bounds)
|
||||||
|
{
|
||||||
|
float panel_y_offset = 0;
|
||||||
|
for (int i = 0; i < oscillators.size(); i++)
|
||||||
|
{
|
||||||
|
OscillatorGuiState* ui_osc = guiOscillators[i];
|
||||||
|
assert(ui_osc);
|
||||||
|
|
||||||
|
Oscillator* osc = oscillators[i];
|
||||||
|
assert(osc);
|
||||||
|
|
||||||
|
const bool has_shape_param = (ui_osc->waveshape == Square);
|
||||||
|
|
||||||
|
// Draw Oscillator Panel
|
||||||
|
const int osc_panel_width = panel_bounds.width - 20;
|
||||||
|
const int osc_panel_height = has_shape_param ? 130 : 100;
|
||||||
|
const int osc_panel_x = panel_bounds.x + 10;
|
||||||
|
const int osc_panel_y = panel_bounds.y + 50 + panel_y_offset;
|
||||||
|
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
|
||||||
|
},
|
||||||
|
"");
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
// Volume slider
|
||||||
|
float decibels = (20.f * log10f(osc->GetVolume()));
|
||||||
|
char amp_slider_label[32];
|
||||||
|
sprintf(amp_slider_label, "%.1f dB", decibels);
|
||||||
|
decibels = GuiSlider(el_rect,
|
||||||
|
amp_slider_label,
|
||||||
|
"",
|
||||||
|
decibels,
|
||||||
|
-60.0f,
|
||||||
|
0.0f
|
||||||
|
);
|
||||||
|
ui_osc->volume = powf(10.f, decibels * (1.f/20.f));
|
||||||
|
osc->SetVolume(ui_osc->volume);
|
||||||
|
|
||||||
|
el_rect.y += el_rect.height + el_spacing;
|
||||||
|
|
||||||
|
// Defer shape drop-down box.
|
||||||
|
ui_osc->shape_dropdown_rect = el_rect;
|
||||||
|
el_rect.y += el_rect.height + el_spacing;
|
||||||
|
/*
|
||||||
|
Rectangle delete_button_rect = el_rect;
|
||||||
|
delete_button_rect.x = osc_panel_x + 5;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::DrawMainPanel(const Rectangle& panel_bounds)
|
||||||
|
{
|
||||||
|
bool is_shape_dropdown_open = false;
|
||||||
|
int shape_index = 0;
|
||||||
|
GuiPanel(panel_bounds, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::DrawAddOscillatorButton(Synth & synth, SynthGuiState & synthGui, Rectangle panel_bounds)
|
||||||
|
{
|
||||||
|
bool click_add_oscillator = GuiButton((Rectangle){
|
||||||
|
panel_bounds.x + 10,
|
||||||
|
panel_bounds.y + 10,
|
||||||
|
panel_bounds.width - 20,
|
||||||
|
25.f
|
||||||
|
}, "Add Oscillator");
|
||||||
|
if (click_add_oscillator)
|
||||||
|
{
|
||||||
|
synth.AddOscillator();
|
||||||
|
Oscillator* osc = synth.GetOscillators().back();
|
||||||
|
|
||||||
|
OscillatorGuiState* ui = new OscillatorGuiState {
|
||||||
|
.freq = osc->GetFreq(),
|
||||||
|
.waveshape = osc->GetType(),
|
||||||
|
.volume = osc->GetVolume()
|
||||||
|
};
|
||||||
|
synthGui.oscillators.push_back(ui);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::DrawUi(Synth & synth, SynthGuiState & synthGui)
|
||||||
|
{
|
||||||
|
Rectangle panel_bounds = {.x = 0, .y = 0, .width = OSCILLATOR_PANEL_WIDTH, .height = WINDOW_HEIGHT };
|
||||||
|
DrawMainPanel(panel_bounds);
|
||||||
|
DrawAddOscillatorButton(synth, synthGui, panel_bounds);
|
||||||
|
// Draw Oscillators
|
||||||
|
std::vector<Oscillator*> oscillators = synth.GetOscillators();
|
||||||
|
std::vector<OscillatorGuiState*> guiOscillators = synthGui.oscillators;
|
||||||
|
|
||||||
|
DrawOscillatorsPanels(oscillators, guiOscillators, panel_bounds);
|
||||||
|
DrawOscillatorsShapeInputs(oscillators, guiOscillators);
|
||||||
|
}
|
||||||
8
src/SeeSynth.cpp
Normal file
8
src/SeeSynth.cpp
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#include "Application.h"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
Application* app = new Application();
|
||||||
|
app->Run();
|
||||||
|
|
||||||
|
delete app;
|
||||||
|
}
|
||||||
39
src/Synth.cpp
Normal file
39
src/Synth.cpp
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#include "Synth.h"
|
||||||
|
#include "Settings.h"
|
||||||
|
#include "KeyBoard.h"
|
||||||
|
#include "OscillatorType.h"
|
||||||
|
|
||||||
|
Synth::Synth(/* args */)
|
||||||
|
{
|
||||||
|
AddOscillator();
|
||||||
|
}
|
||||||
|
|
||||||
|
Synth::~Synth()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_adder.SumOscillators(m_oscillators, duration); //todo: add other pipeline steps (e.g ADSR, Filters, FX);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synth::ProduceNoteSound(Note input)
|
||||||
|
{
|
||||||
|
float length = 1.f / input.length;
|
||||||
|
int semitone_shift = KeyBoard::GetSemitoneShift(input.name);
|
||||||
|
m_out_signal = get_note(semitone_shift, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Synth::AddOscillator()
|
||||||
|
{
|
||||||
|
m_oscillators.push_back(new Oscillator(OscillatorType::Sine, 440.f, VOLUME));
|
||||||
|
}
|
||||||
30
utils.c
30
utils.c
@@ -1,30 +0,0 @@
|
|||||||
#include "utils.h"
|
|
||||||
#include "stdlib.h"
|
|
||||||
#include "string.h"
|
|
||||||
|
|
||||||
// frees the original sounds
|
|
||||||
SynthSound concat_sounds(SynthSound* sounds, size_t count) {
|
|
||||||
size_t total_count = 0;
|
|
||||||
for (size_t i = 0; i < count; i++) {
|
|
||||||
total_count += sounds[i].sample_count;
|
|
||||||
}
|
|
||||||
// array to hold the result
|
|
||||||
float* total = malloc(total_count * sizeof(float));
|
|
||||||
|
|
||||||
size_t current_count = 0;
|
|
||||||
for (size_t i = 0; i < count; i++) {
|
|
||||||
memcpy(total + current_count,
|
|
||||||
sounds[i].samples,
|
|
||||||
sounds[i].sample_count * sizeof(float));
|
|
||||||
current_count += sounds[i].sample_count;
|
|
||||||
|
|
||||||
free(sounds[i].samples);
|
|
||||||
}
|
|
||||||
|
|
||||||
SynthSound result = {
|
|
||||||
.samples = total,
|
|
||||||
.sample_count = total_count
|
|
||||||
};
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
22
utils.h
22
utils.h
@@ -1,22 +0,0 @@
|
|||||||
#ifndef UTILS_H
|
|
||||||
#define UTILS_H
|
|
||||||
|
|
||||||
#include "stdio.h"
|
|
||||||
|
|
||||||
#define write_log(format,args...) do { \
|
|
||||||
printf(format, ## args); \
|
|
||||||
} while(0)
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------------
|
|
||||||
// General SynthSound
|
|
||||||
//------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
typedef struct SynthSound {
|
|
||||||
float* samples;
|
|
||||||
size_t sample_count;
|
|
||||||
} SynthSound;
|
|
||||||
|
|
||||||
// frees the original sounds
|
|
||||||
SynthSound concat_sounds(SynthSound* sounds, size_t count);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
Reference in New Issue
Block a user