#include "synth_gui.h" #define RAYGUI_IMPLEMENTATION #include "raygui.h" //#include "raylib.h" void draw_text(synth_t * synth, Font f, int x, int y) { char buf[64]; int count = 0; float text_size = 10; int offset = 12; snprintf(buf, sizeof buf, "lfo freq %.4f", synth->cc_lfo_freq.value); DrawText(buf, x, y + offset * count++, text_size, GRAY); snprintf(buf, sizeof buf, "lfo amp %.4f", synth->cc_lfo_amp.value); DrawText(buf, x, y + offset * count++, text_size, GRAY); snprintf(buf, sizeof buf, "filter reso %.4f", synth->cc_resonance.value); DrawText(buf, x, y + offset * count++, text_size, GRAY); snprintf(buf, sizeof buf, "filter cutoff %.1f", synth->cc_cutoff.value); DrawText(buf, x, y + offset * count++, text_size, GRAY); snprintf(buf, sizeof buf, "extra pitch %.4f", synth->cc_pitch.value); DrawText(buf, x, y + offset * count++, text_size, GRAY); DrawRectangleLines(x - 6, y - 3, 120, 3 + (count * offset) + 3, GRAY); } int flag =0; void mouse(synth_t *synth, PaStream *stream) { float m = GetMouseWheelMove(); int x = 0; if (m < 0) x = -1; else if (m > 0) x = 1; int y = GetMouseY(); if (x) { if (y > 20 && y <= 32) { cc_step(&synth->cc_lfo_freq, x); } else if (y > 32 && y <= 44) { cc_step(&synth->cc_lfo_amp, x); } else if (y > 44 && y <= 56) { cc_step(&synth->cc_resonance, x); } else if (y > 56 && y <= 68) { cc_step(&synth->cc_cutoff, x); } else if (y > 68 && y <= 80) { cc_step(&synth->cc_pitch, x); } } if (CheckCollisionPointRec(GetMousePosition(), (Rectangle){30, 250, 30, 30})) { SetMouseCursor(6); if (IsMouseButtonPressed(0)) { flag = 1; } } else { SetMouseCursor(0); } if (IsMouseButtonDown(0) && flag) { SetMouseCursor(6); Vector2 dx = GetMouseDelta(); int x = 1; if (IsKeyDown(KEY_LEFT_SHIFT)) { x = 3; } if (dx.y < 0) cc_step(&synth->cc_adsr_s, 1*x); if (dx.y > 0) cc_step(&synth->cc_adsr_s, -1*x); } if (IsMouseButtonReleased(0)) { flag = 0; } } void keyboard(synth_t * synth, PaStream *stream) { int keys[] = {KEY_Q, KEY_TWO, KEY_W, KEY_THREE, KEY_E, KEY_R, KEY_FIVE, KEY_T, KEY_SIX, KEY_Y, KEY_SEVEN, KEY_U, KEY_I, KEY_NINE, KEY_O, KEY_ZERO, KEY_P, KEY_LEFT_BRACKET, KEY_EQUAL, KEY_RIGHT_BRACKET, 0}; float note; for (int i = 0; keys[i]; i++) { if (IsKeyPressed(keys[i])) { //printf("Note On : %s[%d] %fHz\n", int_to_note(i % 12), (synth->octave + (i / 12)) % 8, note); synth->midi_note[i].freq = 16.35160 * pow(2, (synth->octave + i / 12.0)); //synth->midi_note[i].freq = notes[i % 12][(synth->octave + (i / 12)) % 8]; synth->midi_note[i].channel = -1; synth->midi_note[i].noteOn = Pa_GetStreamTime(synth->stream); synth->midi_note[i].noteOff = 0; synth->midi_note[i].velocity = 1.0; synth->midi_note[i].elapsed = 0; synth->midi_note[i].active = 1; int flag = 1; for (int j = 0; j < synth->midi_active_n; j++) { if (synth->midi_active[j] == &synth->midi_note[i]) { flag = 0; } } if (flag) { synth->midi_active[synth->midi_active_n++] = &synth->midi_note[i]; } //synth->adsr.elapsed = 0; synth->active = 1; } } for (int i = 0; keys[i]; i++) { if (IsKeyReleased(keys[i])) { synth->midi_note[i].noteOff = Pa_GetStreamTime(synth->stream); synth->midi_note[i].noteOffSample = synth->midi_note[i].elapsed; note = notes[i % 12][(synth->octave + (i / 12)) % 8]; } } int mods[] = {KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, KEY_N, KEY_M, 0}; synth->modi = 0; for (int i = 0; mods[i]; i++) { if (IsKeyDown(mods[i])) { synth->modifiers[synth->modi++] = mods[i]; } } if (IsKeyDown(265)) { // up for (int i = 0; i < synth->modi; i++) { if (synth->modifiers[i] == KEY_Z) { cc_step(&synth->cc_cutoff, 1); } if (synth->modifiers[i] == KEY_X) { cc_step(&synth->cc_resonance, 1); } if (synth->modifiers[i] == KEY_C) { cc_step(&synth->cc_pitch, 1); } if (synth->modifiers[i] == KEY_V) { cc_step(&synth->cc_lfo_freq, 1); } if (synth->modifiers[i] == KEY_B) { cc_step(&synth->cc_lfo_amp, 1); } } } if (IsKeyDown(264)) { // down for (int i = 0; i < synth->modi; i++) { if (synth->modifiers[i] == KEY_Z) { cc_step(&synth->cc_cutoff, -1); } if (synth->modifiers[i] == KEY_X) { cc_step(&synth->cc_resonance, -1); } if (synth->modifiers[i] == KEY_C) { cc_step(&synth->cc_pitch, -1); } if (synth->modifiers[i] == KEY_V) { cc_step(&synth->cc_lfo_freq, -1); } if (synth->modifiers[i] == KEY_B) { cc_step(&synth->cc_lfo_amp, -1); } } } if (IsKeyDown(KEY_ENTER)) { // down for (int i = 0; i < synth->modi; i++) { if (synth->modifiers[i] == KEY_Z) { cc_reset(&synth->cc_cutoff); } if (synth->modifiers[i] == KEY_X) { cc_reset(&synth->cc_resonance); } if (synth->modifiers[i] == KEY_C) { cc_reset(&synth->cc_pitch); } if (synth->modifiers[i] == KEY_V) { cc_reset(&synth->cc_lfo_freq); } if (synth->modifiers[i] == KEY_B) { cc_reset(&synth->cc_lfo_amp); } } } } void draw_bars(synth_t * synth, int x, int y, int width, int height, int offset) { char buf[64]; int count = 0; snprintf(buf, sizeof buf, "%.3f", synth->adsr.a); synth->adsr.a = GuiSlider((Rectangle){ x, y + count++ * (height + offset), width, height }, "A: ", buf, synth->adsr.a , 0.00001f, 2.0f); snprintf(buf, sizeof buf, "%.3f", synth->adsr.peak); synth->adsr.peak = GuiSlider((Rectangle){ x, y + count++ * (height + offset), width, height }, "P: ", buf, synth->adsr.peak , 0.0f, 1.0f); snprintf(buf, sizeof buf, "%.3f", synth->adsr.d); synth->adsr.d = GuiSlider((Rectangle){ x, y + count++ * (height + offset), width, height }, "D: ", buf, synth->adsr.d , 0.001f, 2.0f); snprintf(buf, sizeof buf, "%.3f", synth->adsr.s); synth->adsr.s = GuiSlider((Rectangle){ x, y + count++ * (height + offset), width, height }, "S: ", buf, synth->adsr.s , 0.0f, 1.0f); snprintf(buf, sizeof buf, "%.3f", synth->adsr.r); synth->adsr.r = GuiSlider((Rectangle){ x, y + count++ * (height + offset), width, height }, "R: ", buf, synth->adsr.r , 0.001f, 3.0f); snprintf(buf, sizeof buf, "%.3f", synth->gain); synth->gain = GuiSlider((Rectangle){ x, y + count++ * (height + offset), width, height }, "gain: ", buf, synth->gain , 0.0f, 2.0f); snprintf(buf, sizeof buf, "%.3f", synth->del_feedback); synth->del_feedback = GuiSlider((Rectangle){ x, y + count++ * (height + offset), width, height }, "fb: ", buf, synth->del_feedback , 0.0f, 1.0f); snprintf(buf, sizeof buf, "%.3f", synth->del_time); synth->del_time = GuiSlider((Rectangle){ x, y + count++ * (height + offset), width, height }, "del_time: ", buf, synth->del_time , 0.0f, 0.3f); } void frequencyToColor(float frequency, int *red, int *green, int *blue) { // Assuming frequency ranges from 20 to 20000 Hz float minFrequency = 20.0; float maxFrequency = 20000.0; // Map the frequency to a value between 0 and 1 float normalizedFrequency = (frequency - minFrequency) / (maxFrequency - minFrequency); // Define colors for the rainbow spectrum float hue = normalizedFrequency * 360.0; // Convert HSV to RGB float c = 1.0; float x = c * (1.0 - fabs(fmod(hue / 60.0, 2.0) - 1.0)); float m = 0.0; float r, g, b; if (hue >= 0 && hue < 60) { r = c; g = x; b = 0; } else if (hue >= 60 && hue < 120) { r = x; g = c; b = 0; } else if (hue >= 120 && hue < 180) { r = 0; g = c; b = x; } else if (hue >= 180 && hue < 240) { r = 0; g = x; b = c; } else if (hue >= 240 && hue < 300) { r = x; g = 0; b = c; } else { r = c; g = 0; b = x; } *red = (int)((r + m) * 255); *green = (int)((g + m) * 255); *blue = (int)((b + m) * 255); } #include "fftw3.h" float amp(float re, float im) { float a = fabsf(re); float b = fabsf(im); if (a < b) return b; return a; } void draw_adsr(synth_t *synth, int x, int y, int width, int height) { int x_prev = x; for (int i = 0; i < synth->midi_active_n; i++) { int rec_y = y + height - 1 + 4.5 * floor( (WIDTH / 24) * - fix_adsr(&synth->adsr, synth->midi_active[i]->noteOn, synth->midi_active[i]->noteOff, synth->midi_active[i]->elapsed, synth->midi_active[i]->noteOffSample)); int rec_width = (WIDTH - x - x) / synth->midi_active_n; int rec_height = HEIGHT - rec_y; int red, green, blue; frequencyToColor(synth->midi_active[i]->freq * 25, &red, &green, &blue); DrawRectangleGradientV(x_prev, rec_y - 10, rec_width, rec_height, ColorAlpha(BLUE, .2) ,ColorAlpha((Color) {red, green, blue}, .2)); //DrawRectangleGradientV(x_prev, rec_y - 10, rec_width, rec_height, ColorAlpha(GREEN, .2) ,ColorAlpha(RED, .2)); //DrawRectangleLines(x_prev, rec_y - 10, rec_width, rec_height, GRAY); x_prev += rec_width; } } int freeze; void draw_wave(synth_t *synth, int x, int y, int width, int height) { Color col; for (int i = 0; i < width; i++) { for (int j = 0; j < synth->viz.rate_divider; j++) { int ii = synth->viz.rate_divider * (i) + j; if (synth->viz.wave_viz_buffer[ii] > 1 || synth->viz.wave_viz_buffer[ii] < -1) col = RED; else if (synth->viz.wave_viz_buffer[ii] >= 0.99 || synth->viz.wave_viz_buffer[ii] <= -0.99) col = MAGENTA; else col = WHITE; DrawPixel(i + x , y + height / 2 + floor(50 * synth->viz.wave_viz_buffer[ii + j]), col); } } /* for (int j = 0; j < 100; j++) { */ /* DrawPixel(synth->viz.tmp_index / synth->viz.rate_divider + 20, j + y + height / 2, BLUE); */ /* DrawPixel(synth->viz.tmp_index / synth->viz.rate_divider + 20, -j + y + height / 2, BLUE); */ /* } */ //EndScissorMode(); } void draw_fft(synth_t *synth, int x, int y, int width, int height) { int viz_size = width * synth->viz.rate_divider; int fft_output_len = viz_size / 2 + 1; fftwf_complex* output = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * fft_output_len); fftwf_plan forward_plan = fftwf_plan_dft_r2c_1d(fft_output_len, synth->viz.fft_input_buffer, output, FFTW_ESTIMATE); fftwf_execute(forward_plan); fftwf_destroy_plan(forward_plan); // "Squash" into the Logarithmic Scale float step = 1.06; float lowf = 1.0f; size_t m = 0; float max_amp_tso = 1.0f; for (float f = lowf; (size_t) f < fft_output_len/2; f = ceilf(f*step)) { float f1 = ceilf(f*step); float a = 0.0f; for (size_t q = (size_t) f; q < fft_output_len/2 && q < (size_t) f1; ++q) { float b = log(sqrt(output[q][0]*output[q][0] + output[q][1]*output[q][1])); if (b > a) a = b; } if (max_amp_tso < a) max_amp_tso = a; synth->viz.fft_output_buffer[m++] = a; } // Normalize Frequencies to 0..1 range for (size_t i = 0; i < m; ++i) { synth->viz.fft_output_buffer[i] /= max_amp_tso; } // Smooth out and smear the values for (size_t i = 0; i < m; ++i) { float smoothness = 4; synth->viz.fft_smooth_buffer[i] += (synth->viz.fft_output_buffer[i] - synth->viz.fft_smooth_buffer[i])*smoothness*((float)1/30); /* float smearness = 3; */ /* ss_smear[i] += (synth->viz.fft_smooth_buffer[i] - ss_smear[i])*smearness*((float)1/30); */ } // Display the Bars float cell_width = (float)width/m; for (size_t i = 0; i < m; ++i) { float t = synth->viz.fft_smooth_buffer[i]; float saturation = 0.75f; float value = 1.0f; float hue = (float)i/m; Color color = ColorFromHSV(hue*360, saturation, value); Vector2 startPos = { x + i*cell_width + cell_width/2, y + height - height*2/3*t, }; Vector2 endPos = { x + i*cell_width + cell_width/2, y + height, }; float thick = cell_width/3*sqrtf(t); DrawLineEx(startPos, endPos, thick, BLUE); } fftwf_free(output); } void draw_osc(synth_t * synth, int x, int y, int width, int height) { DrawRectangle(x, y, width, height, BLACK); DrawRectangleLines(x, y, width, height, WHITE); float osc_wave[width]; for (int i = 0; i < width; i++) { osc_wave[i] = 0; } for (int i = 0; i < synth->midi_active_n; i++) { midi_note_t * note = synth->midi_active[i]; midi_note_t note_dup; note_dup.freq = note->freq; note_dup.channel = note->channel; note_dup.noteOn = note->noteOn; note_dup.noteOff = note->noteOff; note_dup.velocity = note->velocity; note_dup.wvt_index = 0; note_dup.lfo_index = note->lfo_index; note_dup.elapsed = note->elapsed; note_dup.noteOffSample = note->noteOffSample; note_dup.adsr = note->adsr; note_dup.active = note->active; for (int i = 0; i < width; i++) { float freq = SAMPLE_RATE/(float)width; osc_wave[i] += synth->gen[synth->geni](freq, ¬e_dup, synth->x, SAMPLE_RATE); } break; } float max = 0; for (int i = 0; i < width; i++) { if (fabs(osc_wave[i]) > max) max = fabs(osc_wave[i]); } if (synth->midi_active_n) { for (int i = 0; i < width; i++) { DrawPixel(i + x , y + height / 2 + floor(height/2 * osc_wave[i] / max), RED); } } } void draw_adsr_graph(synth_t * synth, int x, int y, int width, int height) { float total = synth->adsr.a + synth->adsr.d + synth->adsr.r + 0.3; /*sustain*/ int total_samples = total * SAMPLE_RATE; int a = synth->adsr.a/total * width; int d = synth->adsr.d/total * width; int s = 0.3/total * width; int r = synth->adsr.r/total * width; float adsr_graph[width]; for (int i = 0; i < width; i++) { float point = 0; if (i < a) { //draw atack point = fix_adsr(&synth->adsr, 1, // note On 0, // note Off (float)i/width * total_samples, 0); } else if (i < a + d) { // draw decay point = fix_adsr(&synth->adsr, 1, // note On 0, // note Off (float)i/width * total_samples, 0); } else if (i < a + d + s) { // draw sustain point = fix_adsr(&synth->adsr, 1, // note On 0, // note Off (float)i/width * total_samples, 0); } else if (i < a + d + s + r) { // draw release point = fix_adsr(&synth->adsr, 0, // note On 1, // note Off (float)i/width * total_samples, (float)(a + d + s)/width * total_samples); } adsr_graph[i] = point * height; } adsr_graph[0] = adsr_graph[1]; // remove 1st 0 for (int i = 0; i < width; i++) { DrawPixel(i + x , y + height - adsr_graph[i], RED); } int off_idx = a + d + s; for (int i = 0; i < synth->midi_active_n; i++) { midi_note_t * note = synth->midi_active[i]; int elapsed_samples = note->elapsed; float elapsed_secs = (float)elapsed_samples / SAMPLE_RATE; int j = elapsed_secs / total * width; if (note->noteOff == 0) { if (j < a + d) { DrawCircle(j + x , y + height - adsr_graph[j], 5, RED); } else if (j < a + d + s - 1) { DrawCircle(j + x , y + height - adsr_graph[j], 5, RED); } else if (j > a + d + s - 1) { DrawCircle(a + d + s + x , y + height - adsr_graph[a + d + s], 5, RED); } } else { // note off int id = a + d + s + (float)(note->elapsed - note->noteOffSample)/SAMPLE_RATE / total * width; if (id > 0 && id < width) DrawCircle(id + x , y + height - adsr_graph[id], 5, RED); } } } void draw_signals(synth_t * synth, int x, int y, int width, int height) { DrawRectangleLines(x, y, width, height, WHITE); if (synth->viz.wave_enabled || synth->viz.spectrum_enabled) synth->viz.rate_divider = GuiSlider((Rectangle){ x + (width / 2) / 2, y - 12, width / 2, 12 }, "", NULL, synth->viz.rate_divider , 1, 150); int viz_size = width * synth->viz.rate_divider; float samples[RING_SIZE]; int rc = PaUtil_ReadRingBuffer( &synth->viz.wave_buffer, &samples, RING_SIZE); synth->viz.freeze = GuiCheckBox((Rectangle){ x + width - 16, y, 16, 16 }, "", synth->viz.freeze); if (!synth->viz.freeze) { for (int i = 0; i < rc; i++) { synth->viz.fft_input_buffer[synth->viz.tmp_index] = samples[i]; synth->viz.tmp_buffer[synth->viz.tmp_index++] = samples[i]; if (synth->viz.tmp_index >= viz_size) { synth->viz.tmp_index = 0; } } for (int i = synth->viz.tmp_index; i < viz_size + synth->viz.tmp_index; i++) { synth->viz.wave_viz_buffer[i - synth->viz.tmp_index] = synth->viz.tmp_buffer[i % (viz_size)]; } } else { } synth->viz.spectrum_enabled = GuiCheckBox((Rectangle){ x, y, 16, 16 }, "spectrum", synth->viz.spectrum_enabled); if (synth->viz.spectrum_enabled) { draw_fft(synth, x, y, width, height); } synth->viz.wave_enabled = GuiCheckBox((Rectangle){ x + 100, y, 16, 16 }, "wave", synth->viz.wave_enabled); if (synth->viz.wave_enabled) { draw_wave(synth, x, y, width, height); } synth->viz.adsr_enabled = GuiCheckBox((Rectangle){ x + 170, y, 16, 16 }, "adsr", synth->viz.adsr_enabled); if (synth->viz.adsr_enabled) { draw_adsr(synth, x, y, width, height); } synth->viz.osc_enabled = GuiCheckBox((Rectangle){ x + width - 170, y, 16, 16 }, "osc", synth->viz.osc_enabled); if (synth->viz.osc_enabled) { draw_osc(synth, x + width - 80, y + height - 80, 80, 80); } synth->viz.adsr_graph_enabled = GuiCheckBox((Rectangle){ x + width - 270, y, 16, 16 }, "adsr graph", synth->viz.adsr_graph_enabled); if (synth->viz.adsr_graph_enabled) { draw_adsr_graph(synth, x + 20, y + 20, width - 40, height - 40); } } void draw_cc(cc_t * cc, int x, int y, int width, int height) { //DrawRectangleLines(x, y, width, height, WHITE); int min = 110; int max = 110 + (360+70 - 110) * (cc->target) / (cc->max - cc->min); DrawRing((Vector2){x + width/2, y + height/2}, width / 2 - 6, width / 2, min, max, 0, Fade(MAROON, 0.7f)); DrawCircle(x + width/2, y + height/2, width / 2 - 5, BLACK); // Draw circle sector outline char buf[32]; snprintf(buf, sizeof buf, "%0.2f", cc->target); DrawText(buf, x + width/2 - MeasureText(buf, 10) / 2, y + height/2 - 10 / 2, 10, GRAY); snprintf(buf, sizeof buf, "%s", cc->name); DrawText(buf, x + width/2 - MeasureText(buf, 10) / 2, y + height - 10 / 2, 10, GRAY); } void rayrun(synth_t *synth){ PaTime current_time = 0; PaTime prev_time = 0; InitWindow(WIDTH, HEIGHT, "Raylib synth"); Font f = LoadFont("/usr/share/fonts/TTF/DejaVuSans.ttf"); SetTargetFPS(60); // Set our game to run at 60 frames-per-second while (!WindowShouldClose()) { keyboard(synth, synth->stream); mouse(synth, synth->stream); int prec = 100000; // Draw //---------------------------------------------------------------------------------- BeginDrawing(); ClearBackground(BLACK); draw_cc(&synth->cc_adsr_s, 30, 250, 30, 30); DrawLineBezier((Vector2){30, 250}, (Vector2){30, 300}, .3, BLUE); // GUI char buf[64]; osc_sound(0); snprintf(buf, sizeof buf, "%d", synth->wvt_pos); synth->wvt_pos = GuiSlider((Rectangle){WIDTH / 2 - 108, 150 + 42 + 42, 216, 24 }, "", buf, synth->wvt_pos , 0, 127); set_sound_start(synth->wvt_pos*2048); set_sound_len(synth->wvt_pos*2048 + 2048); draw_bars(synth, 33, 20, 256, 20, 4); draw_text(synth, f, WIDTH / 2 - 108, 20); if ( GuiButton((Rectangle){ WIDTH / 2 - 108, 150 - 6 - 6 + 42, 216, 6 }, "")) { synth->x = 1; } snprintf(buf, sizeof buf, "%.1f", synth->x); synth->x = GuiSlider((Rectangle){ WIDTH / 2 - 108, 150 + 42, 216, 24 }, "x", buf, synth->x , 0.0f, 2.0f); synth->filter = GuiToggle((Rectangle){ WIDTH - 100 - 50 - 100 - 50 , 50 , 100, 24 }, "FILTER", synth->filter); GuiSpinner((Rectangle){ WIDTH - 100 - 50 , 50, 100, 24 }, "oct: ", &(synth->octave), 0, 7, 0); snprintf(buf, sizeof buf, "generator %d --> ", synth->geni); GuiSpinner((Rectangle){ WIDTH - 100 - 50 , 50 + 24 + 6, 100, 24 }, buf, &(synth->geni), 0, 6, 0); synth->clamp = GuiToggle((Rectangle){ WIDTH - 100 - 50, 50 + 24 + 6 + 24 + 6, 100, 24 }, "clamp", synth->clamp); synth->delay = GuiToggle((Rectangle){ WIDTH - 100 - 50, 50 + 24 + 6 + 24 + 6 + 24 + 6, 100, 24 }, "delay", synth->delay); // signals draw_signals(synth, 20, 390, WIDTH - 2*20, 200); //draw_signals(synth, 300, 390, WIDTH - 2*300, 200); //DrawText("THE SYNTH!!!!!!!!!!!!!!!!!!1", WIDTH / 2 - 100, 50, 20, LIGHTGRAY); /* DrawText("KEYBOARD: Q .. ]", WIDTH / 2 -300, HEIGHT - 300 - 50, 20, LIGHTGRAY); */ /* snprintf(buf, sizeof buf, "stream time: %f", Pa_GetStreamTime(synth->stream)); */ /* DrawText(buf, WIDTH / 2 -300, HEIGHT - 300, 11, LIGHTGRAY); */ EndDrawing(); //---------------------------------------------------------------------------------- current_time = Pa_GetStreamTime(synth->stream); //printf("%f :: %ld\n", current_time - prev_time, phase); prev_time = current_time; } CloseWindow(); }