diff --git a/.vscode/settings.json b/.vscode/settings.json index 65bd5fd..d080d91 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,7 +7,64 @@ "__bit_reference": "c", "bitset": "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, } \ No newline at end of file diff --git a/build.sh b/build.sh index a9c9dfe..caa2865 100644 --- a/build.sh +++ b/build.sh @@ -1,3 +1,5 @@ #!/bin/bash -CC="${CXX:-cc}" -$CC -Wall -std=c11 ./main.c ./utils.c ./ring_buffer.c ./oscillator.c ./parser.c ./export.c -lm -lraylib -o ./bin/main +CC="${CXX:-c++}" +LL="-lm -lraylib" +FLAGS="-Wall -std=c++17 -I./inc/" +$CC $FLAGS $(find ./src -type f -iregex ".*\.cpp") $LL -o ./bin/main diff --git a/export.c b/export.c deleted file mode 100644 index 8a231de..0000000 --- a/export.c +++ /dev/null @@ -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); -} \ No newline at end of file diff --git a/export.h b/export.h deleted file mode 100644 index 0cb1aaa..0000000 --- a/export.h +++ /dev/null @@ -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 \ No newline at end of file diff --git a/inc/Adder.h b/inc/Adder.h new file mode 100644 index 0000000..545f006 --- /dev/null +++ b/inc/Adder.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include "Oscillator.h" +#include "Settings.h" +#include + +struct Adder +{ + static std::vector & SumOscillators(const std::vector & oscillators, float duration) + { + size_t sample_count = (size_t)(duration * SAMPLE_RATE); + + std::vector* output = new std::vector(); + 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); + } +}; diff --git a/inc/Application.h b/inc/Application.h new file mode 100644 index 0000000..db9b9c9 --- /dev/null +++ b/inc/Application.h @@ -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* 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(); +}; + diff --git a/inc/KeyBoard.h b/inc/KeyBoard.h new file mode 100644 index 0000000..4aa83e6 --- /dev/null +++ b/inc/KeyBoard.h @@ -0,0 +1,80 @@ +#pragma once +#include "Settings.h" +#include +#include +#include +#include + +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() +{ +} diff --git a/inc/Logger.h b/inc/Logger.h new file mode 100644 index 0000000..cdc57d0 --- /dev/null +++ b/inc/Logger.h @@ -0,0 +1,6 @@ +#pragma once +#include "cstdio" + +#define write_log(format,args...) do { \ + printf(format, ## args); \ + } while(0) diff --git a/inc/Note.h b/inc/Note.h new file mode 100644 index 0000000..3f76f7c --- /dev/null +++ b/inc/Note.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +struct Note { + std::string& name; + int length; +}; \ No newline at end of file diff --git a/inc/Oscillator.h b/inc/Oscillator.h new file mode 100644 index 0000000..93111fe --- /dev/null +++ b/inc/Oscillator.h @@ -0,0 +1,40 @@ +#pragma once +#include +#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); +}; diff --git a/inc/OscillatorType.h b/inc/OscillatorType.h new file mode 100644 index 0000000..9ddaaac --- /dev/null +++ b/inc/OscillatorType.h @@ -0,0 +1,7 @@ +#pragma once +typedef enum { + Sine, + Triangle, + Saw, + Square +} OscillatorType; \ No newline at end of file diff --git a/inc/Renderer.h b/inc/Renderer.h new file mode 100644 index 0000000..cbdb1f3 --- /dev/null +++ b/inc/Renderer.h @@ -0,0 +1,27 @@ +#pragma once +#include "Synth.h" +#include "SynthGuiState.h" +#include +#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& oscillators, + const std::vector& guiOscillators, + const Rectangle& panel_bounds); + void DrawOscillatorsShapeInputs(const std::vector& oscillators, + const std::vector& guiOscillators); + void DrawUi(Synth & synth, SynthGuiState & synthGui); + void DrawSignal(Synth & synth, SynthGuiState & synthGui); +public: + Renderer(/* args */); + ~Renderer(); + void Draw(Synth& synth, SynthGuiState & synthGui); +}; + + + + diff --git a/inc/RingBuffer.h b/inc/RingBuffer.h new file mode 100644 index 0000000..b39e579 --- /dev/null +++ b/inc/RingBuffer.h @@ -0,0 +1,131 @@ +#pragma once +#include +#include "Logger.h" +template +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 RingBuffer::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 RingBuffer::~RingBuffer() +{ + delete[] m_items; +} + +template void RingBuffer::Reset() +{ + m_head = 0; + m_tail = 0; + m_is_full = 0; +} + +template void RingBuffer::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 void RingBuffer::retreat_pointer() +{ + m_is_full = 0; + m_tail++; + if (m_tail == m_size) { + m_tail = 0; + } +} + +template void RingBuffer::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 bool RingBuffer::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 std::size_t RingBuffer::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 void RingBuffer::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); +} \ No newline at end of file diff --git a/settings.h b/inc/Settings.h similarity index 78% rename from settings.h rename to inc/Settings.h index 007ee7f..dbf08e5 100644 --- a/settings.h +++ b/inc/Settings.h @@ -1,5 +1,4 @@ -#ifndef SETTINGS_H -#define SETTINGS_H +#pragma once #define SAMPLE_RATE 48000.f #define BPM 120.f @@ -14,6 +13,4 @@ #define WINDOW_WIDTH 640 #define WINDOW_HEIGHT 480 -#define OSCILLATOR_PANEL_WIDTH 200 - -#endif \ No newline at end of file +#define OSCILLATOR_PANEL_WIDTH 200 \ No newline at end of file diff --git a/inc/Synth.h b/inc/Synth.h new file mode 100644 index 0000000..a0612d1 --- /dev/null +++ b/inc/Synth.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include "Oscillator.h" +#include "Note.h" +#include "Adder.h" +#include "Settings.h" + +class Synth +{ +private: + std::vector m_oscillators; + Adder m_adder; + //OscillatorUI* ui_oscillators; + //Note m_current_note; + std::vector m_out_signal; + std::vector & get_note(int semitone, float beats); + +public: + Synth(/* args */); + ~Synth(); + void ProduceNoteSound(Note input); + void AddOscillator(); + const std::vector & GetOutSignal() { return m_out_signal; } + const std::vector& GetOscillators() { return m_oscillators; } +}; \ No newline at end of file diff --git a/inc/SynthGuiState.h b/inc/SynthGuiState.h new file mode 100644 index 0000000..c6630df --- /dev/null +++ b/inc/SynthGuiState.h @@ -0,0 +1,16 @@ +#pragma once +#include "OscillatorType.h" +#include "raygui.h" +#include + +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 oscillators; +}; \ No newline at end of file diff --git a/raygui.h b/inc/raygui.h similarity index 100% rename from raygui.h rename to inc/raygui.h diff --git a/main.c b/main.c deleted file mode 100644 index 1f5e54c..0000000 --- a/main.c +++ /dev/null @@ -1,471 +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); - assert(oscArray); - - 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++) { - osc_set_freq(&synth->oscillators.array[i], 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 note_on(Synth *synth, Note *note) { - -} - -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]; - assert(ui_osc); - - Oscillator* osc = &synth->oscillators.array[ui_osc_i]; - assert(osc); - - 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]; - assert(ui_osc); - - Oscillator* osc = &synth->oscillators.array[ui_osc_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) - { - 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]; - assert(ui); - - 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; - assert(sound); - - 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:%d\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(current_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); - assert(sounds); - - 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); - assert(song_pcm); - - 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; -} diff --git a/oscillator.c b/oscillator.c deleted file mode 100644 index 8cb58e3..0000000 --- a/oscillator.c +++ /dev/null @@ -1,167 +0,0 @@ -#include "oscillator.h" -#include "settings.h" -#include "math.h" -#include "stdlib.h" - -#define TWO_PI 2*SYNTH_PI - -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 void sine_osc_phase_incr(Oscillator* osc) { - osc->phase += osc->phase_dt; - if (osc->phase >= TWO_PI) - osc->phase -= TWO_PI; -} - -static void saw_osc_phase_incr(Oscillator* osc) { - osc->phase += osc->phase_dt; - if (osc->phase >= 1.0f) - osc->phase -= 1.0f; -} - -static float calc_saw_phase_delta(float freq) { - return freq / SAMPLE_RATE; -} - -static float calc_sine_phase_delta(float freq) { - return (TWO_PI * freq) / SAMPLE_RATE; -} - -static float sineosc(Oscillator* osc) { - float result = sinf(osc->phase); - sine_osc_phase_incr(osc); - return result; -} - -static float sign(float v) { - return (v > 0.0) ? 1.f : -1.f; -} - -static float squareosc(Oscillator* osc) { - return sign(sineosc(osc)); -} - -static float triangleosc(Oscillator* osc) { - float result = 1.f - fabsf(osc->phase - 0.5f) * 4.f; - saw_osc_phase_incr(osc); - return result; -} - -static float sawosc(Oscillator* osc) { - float result = osc->phase * 2.f - 1.f; - saw_osc_phase_incr(osc); - return result; -} - -void osc_set_freq(Oscillator* osc, float freq) { - osc->freq = freq; - osc->phase = 0; - switch (osc->osc) - { - case Sine: - osc->phase_dt = calc_sine_phase_delta(freq); - break; - case Square: - osc->phase_dt = calc_sine_phase_delta(freq); - break; - case Triangle: - osc->phase_dt = calc_saw_phase_delta(freq); - break; - case Saw: - osc->phase_dt = calc_saw_phase_delta(freq); - break; - default: - break; - } -} - -void osc_reset(Oscillator* osc) { - osc->volume = 0; - osc->phase = 0; - osc->phase_dt = 0; -} - -float multiosc(OscillatorGenerationParameter param) { - float osc_sample = 0.f; - for (size_t i = 0; i < param.oscillators.count; i++) { - Oscillator* osc = ¶m.oscillators.array[i]; - assert(osc); - - switch (osc->osc) { - case Sine: - osc_sample += sineosc(osc) * osc->volume; - break; - case Triangle: - osc_sample += triangleosc(osc) * osc->volume; - break; - case Square: - osc_sample += squareosc(osc) * osc->volume; - break; - case Saw: - osc_sample += sawosc(osc) * osc->volume; - break; - } - } - - return osc_sample; -} - -SynthSound freq(float duration, OscillatorArray osc) { - size_t sample_count = (size_t)(duration * SAMPLE_RATE); - - float* output = malloc(sizeof(float) * sample_count); - for (size_t i = 0; i < sample_count; i++) { - OscillatorGenerationParameter param = { - .oscillators = osc - }; - output[i] = multiosc(param); - } - - SynthSound res = { - .samples = output, - .sample_count = 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; -} -*/ \ No newline at end of file diff --git a/oscillator.h b/oscillator.h deleted file mode 100644 index 8ac30be..0000000 --- a/oscillator.h +++ /dev/null @@ -1,37 +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; - float phase; - float phase_dt; -} Oscillator; - -typedef struct OscillatorArray { - Oscillator* array; - size_t count; -} OscillatorArray; - -typedef struct OscillatorGenerationParameter { - OscillatorArray oscillators; -} OscillatorGenerationParameter; - -void osc_set_freq(Oscillator* osc, float freq); -void osc_reset(Oscillator* osc); -float multiosc(OscillatorGenerationParameter param); -SynthSound freq(float duration, OscillatorArray osc); - - - -#endif \ No newline at end of file diff --git a/parser.c b/parser.c deleted file mode 100644 index 9a460fa..0000000 --- a/parser.c +++ /dev/null @@ -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; -} -*/ diff --git a/parser.h b/parser.h deleted file mode 100644 index 9a58ea8..0000000 --- a/parser.h +++ /dev/null @@ -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 diff --git a/ring_buffer.c b/ring_buffer.c deleted file mode 100644 index 3d46364..0000000 --- a/ring_buffer.c +++ /dev/null @@ -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); -} diff --git a/ring_buffer.h b/ring_buffer.h deleted file mode 100644 index b840759..0000000 --- a/ring_buffer.h +++ /dev/null @@ -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 diff --git a/src/Application.cpp b/src/Application.cpp new file mode 100644 index 0000000..15f60dd --- /dev/null +++ b/src/Application.cpp @@ -0,0 +1,184 @@ +#include "Application.h" +#include "Settings.h" +#include "Logger.h" +#include + +Application::Application(/* args */) +{ + m_ring_buffer = new RingBuffer((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 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); + } +} diff --git a/src/Oscillator.cpp b/src/Oscillator.cpp new file mode 100644 index 0000000..ebdc0be --- /dev/null +++ b/src/Oscillator.cpp @@ -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; +} \ No newline at end of file diff --git a/src/Renderer.cpp b/src/Renderer.cpp new file mode 100644 index 0000000..86c4e42 --- /dev/null +++ b/src/Renderer.cpp @@ -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& oscillators, const std::vector& 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& oscillators, + const std::vector& 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 oscillators = synth.GetOscillators(); + std::vector guiOscillators = synthGui.oscillators; + + DrawOscillatorsPanels(oscillators, guiOscillators, panel_bounds); + DrawOscillatorsShapeInputs(oscillators, guiOscillators); +} \ No newline at end of file diff --git a/src/SeeSynth.cpp b/src/SeeSynth.cpp new file mode 100644 index 0000000..f99e416 --- /dev/null +++ b/src/SeeSynth.cpp @@ -0,0 +1,8 @@ +#include "Application.h" + +int main() { + Application* app = new Application(); + app->Run(); + + delete app; +} \ No newline at end of file diff --git a/src/Synth.cpp b/src/Synth.cpp new file mode 100644 index 0000000..f388cd9 --- /dev/null +++ b/src/Synth.cpp @@ -0,0 +1,39 @@ +#include "Synth.h" +#include "Settings.h" +#include "KeyBoard.h" +#include "OscillatorType.h" + +Synth::Synth(/* args */) +{ + AddOscillator(); +} + +Synth::~Synth() +{ +} + +std::vector & 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)); +} diff --git a/utils.c b/utils.c deleted file mode 100644 index 694def5..0000000 --- a/utils.c +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/utils.h b/utils.h deleted file mode 100644 index ac30032..0000000 --- a/utils.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef UTILS_H -#define UTILS_H - -#include "stdio.h" -#include "assert.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 \ No newline at end of file