diff --git a/.gitignore b/.gitignore index a5c2d27..3264729 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /bin .DS_Store /Debug/ -*.wav \ No newline at end of file +*.wav +*.dSYM \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 03743e0..9bbff0a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,6 @@ ], "files.associations": { "algorithm": "c" - } + }, + "FSharp.suggestGitignore": false, } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..1f577ce --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,32 @@ +{ + "tasks": [ + { + "type": "cppbuild", + "label": "C/C++: clang сборка активного файла", + "command": "/usr/bin/clang", + "args": [ + "-fcolor-diagnostics", + "-fansi-escape-codes", + "-g", + "${file}", + "${fileDirname}/parser.c", + "-lm", + "-lraylib", + "-o", + "${fileDirname}/bin/${fileBasenameNoExtension}" + ], + "options": { + "cwd": "${fileDirname}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "Задача создана отладчиком." + } + ], + "version": "2.0.0" +} \ No newline at end of file diff --git a/main.c b/main.c index 23408c0..4ee4c43 100644 --- a/main.c +++ b/main.c @@ -12,8 +12,128 @@ #define PITCH_STANDARD 440.f #define VOLUME 0.5f #define ATTACK_MS 100.f +#define STREAM_BUFFER_SIZE 4096 #define SYNTH_PI 3.1415926535f +#define SYNTH_VOLUME 0.5f + +#define WINDOW_WIDTH 640 +#define WINDOW_HEIGHT 480 + +#define write_log(format,args...) do { \ + printf(format, ## args); \ + } while(0) + +//------------------------------------------------------------------------------------ +// Ring Buffer +//------------------------------------------------------------------------------------ + +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) { + 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); +} + //------------------------------------------------------------------------------------ // General SynthSound @@ -180,6 +300,20 @@ static SynthSound freq(float duration, OscillatorParameterList osc) { } */ + // 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, @@ -216,7 +350,7 @@ static SynthSound get_attack_samples() { // Synth //------------------------------------------------------------------------------------ -static size_t get_semitone_shift_internal(char* root_note, char* target_note) { +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" }; @@ -260,21 +394,21 @@ static size_t get_semitone_shift_internal(char* root_note, char* target_note) { (target_pitch_class - root_pitch_class); } -static float get_hz_by_semitone(size_t semitone) { +static float get_hz_by_semitone(int semitone) { return PITCH_STANDARD * powf(powf(2.f, (1.f / 12.f)), semitone); } -size_t get_semitone_shift(char* target_note) { +int get_semitone_shift(char* target_note) { return get_semitone_shift_internal("A4", target_note); } -SynthSound note(size_t semitone, float beats) { +SynthSound note(int semitone, float beats) { float hz = get_hz_by_semitone(semitone); float duration = beats * BEAT_DURATION; OscillatorParameter first = { - .osc = Saw, - .freq = hz/4.f + .osc = Square, + .freq = hz }; OscillatorParameter second = { @@ -287,10 +421,10 @@ SynthSound note(size_t semitone, float beats) { .freq = hz - 1.f }; - OscillatorParameter oscArray[] = { first, second, third }; + OscillatorParameter oscArray[] = { first/*, second, third */}; OscillatorParameterList parameters = { .array = oscArray, - .count = 3 + .count = 1 }; return freq(duration, parameters); @@ -298,7 +432,7 @@ SynthSound note(size_t semitone, float beats) { SynthSound get_note_sound(Note input) { float length = 1.f / input.length; - size_t semitone_shift = get_semitone_shift(input.name); + int semitone_shift = get_semitone_shift(input.name); return note(semitone_shift, length); } @@ -376,23 +510,159 @@ void pack(uint16_t* d, size_t length) { write_file("output.wav", buffer, fileSize); } +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; +} + +//------------------------------------------------------------------------------------ +// UI +//------------------------------------------------------------------------------------ +/* +int get_zero_crossing(SynthSound* sound) { + int zero_crossing_index = 0; + for (size_t i = 1; i < sound->sample_count; i++) + { + if (sound->samples[i] >= 0.0f && sound->samples[i-1] < 0.0f) // zero-crossing + { + zero_crossing_index = i; + break; + } + } + return zero_crossing_index; +} + +Vector2* GetVisualSignal(SynthSound* sound, int zero_crossing_index) +{ + const int signal_amp = 300; + + Vector2* signal_points = malloc(sizeof(Vector2) * STREAM_BUFFER_SIZE); + + const float screen_vertical_midpoint = (WINDOW_HEIGHT/2); + for (size_t point_idx = 0; point_idx < sound->sample_count; point_idx++) + { + const int signal_idx = (point_idx + zero_crossing_index) % STREAM_BUFFER_SIZE; + signal_points[point_idx].x = (float)point_idx + WINDOW_WIDTH; + signal_points[point_idx].y = screen_vertical_midpoint + (int)(sound->samples[signal_idx] * 300); + } + + return signal_points; +} + +*/ + //------------------------------------------------------------------------------------ // Main //------------------------------------------------------------------------------------ int main(int argc, char **argv) { - const size_t width = 1280; - const size_t height = 720; + InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, "SeeSynth - v0.1"); + SetTargetFPS(60); - InitWindow(width, height, "SeeSynth - v0.1"); - //SetTargetFPS(60); + Note current_note = { + .length = 1, + .name = malloc(sizeof(char) * 3) + }; + SynthSound sound = { + .sample_count = 0 + }; + 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);//float*8 + SetAudioStreamVolume(synth_stream, 0.5f); + + PlayAudioStream(synth_stream); + // Main game loop while (!WindowShouldClose()) // Detect window close button or ESC key { - // Update + // Update Audio states //---------------------------------------------------------------------------------- - // TODO: Update your variables here + // Fill ring buffer from current 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:%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 + //---------------------------------------------------------------------------------- + if (detect_note_pressed(¤t_note)) { + sound = get_note_sound(current_note); + sound_played_count = 0; + write_log("Note played: %s\n", current_note.name); + } //---------------------------------------------------------------------------------- // Draw @@ -400,13 +670,17 @@ int main(int argc, char **argv) { BeginDrawing(); ClearBackground(RAYWHITE); + // int zero_crossing = get_zero_crossing(&sound); + // Vector2* visual_signal = GetVisualSignal(&sound, zero_crossing); + // DrawLineStrip(visual_signal, STREAM_BUFFER_SIZE - zero_crossing, RED); 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); @@ -428,6 +702,9 @@ int main(int argc, char **argv) { // De-Initialization //-------------------------------------------------------------------------------------- + StopAudioStream(synth_stream); + UnloadAudioStream(synth_stream); + CloseAudioDevice(); CloseWindow(); // Close window and OpenGL context //--------------------------------------------------------------------------------------