From a029d434b3f67c37bb4663be8e9b4d6623a32955 Mon Sep 17 00:00:00 2001 From: yuzu-eva Date: Wed, 23 Apr 2025 11:21:04 +0200 Subject: restructure project --- plug.c | 390 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 390 insertions(+) create mode 100644 plug.c (limited to 'plug.c') diff --git a/plug.c b/plug.c new file mode 100644 index 0000000..f67382d --- /dev/null +++ b/plug.c @@ -0,0 +1,390 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "plug.h" + +#define SAMPLE_SIZE (1<<13) +#define FONT_SIZE 69 +#define SMOOTHNESS 8 +#define SMEARNESS 6 + +typedef struct { + Music music; + float audio_volume; + bool error; + Font font; + Shader circle; + Shader smear; + char render_option; +} Plug; + +Plug *plug = NULL; + +float in_raw[SAMPLE_SIZE]; +float in_win[SAMPLE_SIZE]; +float complex out_raw[SAMPLE_SIZE]; +float out_log[SAMPLE_SIZE]; +float out_smooth[SAMPLE_SIZE]; +float out_smear[SAMPLE_SIZE]; + +// char render_option = 'b'; + +void fft(float in[], size_t stride, float complex out[], size_t n) +{ + assert(n > 0); + + if (n == 1) { + out[0] = in[0]; + return; + } + + fft(in, stride*2, out, n/2); // even + fft(in + stride, stride*2, out + n/2, n/2); // odd + + // v = o*x + // out = e + o*x e + o*x e e | e - o*x e - o*x o o + for (size_t k = 0; k < n/2; ++k) { + float t = (float)k/n; + float complex v = cexp(-2*I*PI*t)*out[k + n/2]; + float complex e = out[k]; + out[k] = e + v; + out[k + n/2] = e - v; + } +} + +float amp(float complex z) +{ + float a = cabsf(z); + return 2*log10f(a); +} + +void callback(void *bufferData, unsigned int frames) +{ + float (*fs)[2] = bufferData; + + for (size_t i = 0; i < frames; ++i) { + memmove(in_raw, in_raw + 1, (SAMPLE_SIZE - 1)*sizeof(in_raw[0])); + in_raw[SAMPLE_SIZE-1] = fs[i][0]; + } +} + +void PausePlayMusic(void) +{ + if (IsMusicStreamPlaying(plug->music)) { + PauseMusicStream(plug->music); + printf("Music paused\n"); + } else { + ResumeMusicStream(plug->music); + printf("Music resumed\n"); + } +} + +void RaiseVolume(void) +{ + plug->audio_volume += 0.02; + if (plug->audio_volume > 1.0) { + plug->audio_volume = 1.0; + } + printf("Audio Level: %f\n", plug->audio_volume); + SetMusicVolume(plug->music, plug->audio_volume); +} + +void LowerVolume(void) +{ + plug->audio_volume -= 0.02; + if (plug->audio_volume < 0.0) { + plug->audio_volume = 0.0; + } + printf("Audio Level: %f\n", plug->audio_volume); + SetMusicVolume(plug->music, plug->audio_volume); +} + +void PrepareMusicStream(void) +{ + if (IsMusicValid(plug->music)) { + StopMusicStream(plug->music); + UnloadMusicStream(plug->music); + } +} + +void StartMusicStream(void) +{ + if (IsMusicValid(plug->music)) { + plug->error = false; + printf("music.frameCount = %u\n", plug->music.frameCount); + printf("music.stream.sampleRate = %u\n", plug->music.stream.sampleRate); + printf("music.stream.sampleSize = %u\n", plug->music.stream.sampleSize); + printf("music.stream.channels = %u\n", plug->music.stream.channels); + SetMusicVolume(plug->music, plug->audio_volume); + AttachAudioStreamProcessor(plug->music.stream, callback); + PlayMusicStream(plug->music); + } else { + plug->error = true; + } +} + +size_t fft_analyze(float dt) +{ + // Apply Hann Window on input - https://en.wikipedia.org/wiki/Hann_function + for (size_t i = 0; i < SAMPLE_SIZE; ++i) { + float t = (float)i/(SAMPLE_SIZE-1); + float hann = 0.5 - 0.5*cosf(2*PI*t); + in_win[i] = hann * in_raw[i]; + } + + // FFT + fft(in_win, 1, out_raw, SAMPLE_SIZE); + + // Convert into logarithmic scale + float step = 1.06; + float lowf = 1.0f; + size_t m = 0; + float max_amp = 1.0f; + for (float f = lowf; (size_t) f < SAMPLE_SIZE/2; f = ceilf(f*step)) { + float f1 = ceilf(f*step); + float a = 0.0f; + for (size_t q = (size_t) f; q < SAMPLE_SIZE/2 && q < (size_t) f1; ++q) { + float b = amp(out_raw[q]); + if (b > a) a = b; + } + if (max_amp < a) max_amp = a; + out_log[m++] = a; + } + + // Normalize frequencies 0..1 range + for (size_t i = 0; i < m; ++i) { + out_log[i] /= max_amp; + } + + // Interpolate frequencies + for (size_t i = 0; i < m; ++i) { + out_smooth[i] += (out_log[i] - out_smooth[i])*dt*SMOOTHNESS; + out_smear[i] += (out_smooth[i] - out_smear[i])*dt*SMEARNESS; + } + return m; +} + +void fft_render_bars(int w, int h, size_t m) +{ + // Display the frequencies + float cell_width = (float)w/m; + float saturation = 0.75f; + float value = 0.9f; + + // Draw the bars + for (size_t i = 0; i < m; ++i) { + float t = out_smooth[i]; + float hue = (float)i/m; + Color color = ColorFromHSV(hue*360, saturation, value); + float thickness = (cell_width/1.8)*sqrtf(t); + Vector2 start_pos = { + .x = i*cell_width + cell_width/2, + .y = h, + }; + Vector2 end_pos = { + .x = start_pos.x, + .y = start_pos.y - h*2/3*t, + }; + DrawLineEx(start_pos, end_pos, thickness, color); + } + + + // Construct a texture to be used in the shader + // since fragTexCoord doesn't work on shapes + Texture2D texture = { rlGetTextureIdDefault(), 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; + + // Draw smear frames + BeginShaderMode(plug->smear); + for (size_t i = 0; i < m; ++i) { + float start = out_smear[i]; + float end = out_smooth[i]; + float hue = (float)i/m; + Color color = ColorFromHSV(hue*360, saturation, value); + float radius = cell_width*3*sqrt(end); + Vector2 origin = {0}; + Vector2 start_pos = { + .x = i*cell_width + cell_width/2, + .y = h - h*2/3*start, + }; + Vector2 end_pos = { + .x = i*cell_width + cell_width/2, + .y = h - h*2/3*end, + }; + if (end_pos.y >= start_pos.y) { + Rectangle dest = { + .x = start_pos.x - radius/2, + .y = start_pos.y, + .width = radius, + .height = end_pos.y - start_pos.y, + }; + Rectangle source = {0, 0, 1, 0.5}; + DrawTexturePro(texture, source, dest, origin, 0, color); + } else { + Rectangle dest = { + .x = end_pos.x - radius/2, + .y = end_pos.y, + .width = radius, + .height = start_pos.y - end_pos.y, + }; + Rectangle source = {0, 0.5, 1, 0.5}; + DrawTexturePro(texture, source, dest, origin, 0, color); + } + } + EndShaderMode(); + + // Draw the circles + BeginShaderMode(plug->circle); + for (size_t i = 0; i < m; ++i) { + float t = out_smooth[i]; + float hue = (float)i/m; + Color color = ColorFromHSV(hue*360, saturation, value); + float radius = cell_width*4*sqrtf(t); + Vector2 position = { + .x = i*cell_width + cell_width/2 - radius, + .y = h - h*2/3*t - radius, + }; + DrawTextureEx(texture, position, 0, 2*radius, color); + } + EndShaderMode(); +} + +void fft_render_circle(int w, int h, size_t m) +{ + // Display the frequencies + float cell_width = (float)720/m; + float saturation = 0.75f; + float value = 0.9f; + + // Construct a texture to be used in the shader + // since fragTexCoord doesn't work on shapes + Texture2D texture = { rlGetTextureIdDefault(), 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; + + // Draw the circles + BeginShaderMode(plug->circle); + for (size_t i = 0; i < m; ++i) { + float t = out_smooth[i]; + float hue = (float)i/m; + Color color = ColorFromHSV(hue*360, saturation, value); + float radius = cell_width*4*sqrtf(t); + Vector2 position = { + .x = w/2 + (h/2.1 * cos(i * cell_width)) * sqrtf(t) - radius, + .y = h/2 + (h/2.1 * sin(i * cell_width)) * sqrtf(t) - radius, + }; + DrawTextureEx(texture, position, 0, 2*radius, color); + } + EndShaderMode(); +} + +Plug *plug_pre_reload(void) +{ + if (IsMusicValid(plug->music)) { + DetachAudioStreamProcessor(plug->music.stream, callback); + } + return plug; +} + +void plug_post_reload(Plug *prev) +{ + plug = prev; + if (IsMusicValid(plug->music)) { + AttachAudioStreamProcessor(plug->music.stream, callback); + } + UnloadShader(plug->circle); + UnloadShader(plug->smear); + plug->circle = LoadShader(NULL, "./shaders/circles.fs"); + plug->smear = LoadShader(NULL, "./shaders/smear.fs"); +} + +void plug_init(void) +{ + plug = malloc(sizeof(*plug)); + assert(plug != NULL && "Not enough RAM!"); + memset(plug, 0, sizeof(*plug)); + + plug->font = LoadFontEx("./fonts/AlegreyaSans-Regular.ttf", FONT_SIZE, NULL, 0); + plug->circle = LoadShader(NULL, "./shaders/circles.fs"); + plug->smear = LoadShader(NULL, "./shaders/smear.fs"); + plug->audio_volume = 0.5f; + plug->render_option = 'b'; +} + +void plug_update(void) +{ + if (IsMusicValid(plug->music)) { + UpdateMusicStream(plug->music); + } + + if (IsKeyPressed(KEY_SPACE) && IsMusicValid(plug->music)) { + PausePlayMusic(); + } + + if (IsKeyPressed(KEY_UP)) { + RaiseVolume(); + } + + if (IsKeyPressed(KEY_DOWN)) { + LowerVolume(); + } + + if (IsKeyPressed(KEY_N)) { + plug->render_option = 'b'; + } + + if (IsKeyPressed(KEY_I)) { + plug->render_option = 'c'; + } + + if (IsFileDropped()){ + FilePathList droppedFiles = LoadDroppedFiles(); + const char *file_path = droppedFiles.paths[0]; + + PrepareMusicStream(); + plug->music = LoadMusicStream(file_path); + StartMusicStream(); + + UnloadDroppedFiles(droppedFiles); + } + + float w = GetRenderWidth(); + float h = GetRenderHeight(); + float dt = GetFrameTime(); + + BeginDrawing(); + ClearBackground(CLITERAL(Color) { + 0x1, 0x1, 0x1, 0xFF + }); + + if (IsMusicValid(plug->music)) { + size_t m = fft_analyze(dt); + switch(plug->render_option) + { + case 'b': fft_render_bars(w, h, m); break; + case 'c': fft_render_circle(w, h, m); break; + default: break; + } + + } else { + const char *label; + Color color; + if (plug->error) { + label = "Could not load file"; + color = RED; + } else { + label = "Drag & Drop Music Here"; + color = WHITE; + } + Vector2 size = MeasureTextEx(plug->font, label, plug->font.baseSize, 0); + Vector2 position = { + .x = w/2 - size.x/2, + .y = h/2 - size.y/2, + }; + DrawTextEx(plug->font, label, position, plug->font.baseSize, 0, color); + } + EndDrawing(); +} -- cgit v1.2.3