From 5d5ab5380122f0a35286dea1164325cca570fe53 Mon Sep 17 00:00:00 2001 From: Cristian Gora Date: Fri, 13 Feb 2026 17:51:10 +0100 Subject: [PATCH] Initial commit --- Makefile | 15 ++++ gen.c | 25 ++++++ gen.sh | 5 ++ main.c | 266 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 311 insertions(+) create mode 100644 Makefile create mode 100644 gen.c create mode 100755 gen.sh create mode 100644 main.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..63c509c --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +CC=cc +CFLAGS=-Wall -Wextra -O3 # -ggdb +LIBS=-lraylib -lm + +all: fft gen + +fft: main.c + $(CC) $(CFLAGS) -o fft main.c $(LIBS) + +gen: gen.c + $(CC) $(CFLAGS) -o gen gen.c $(LIBS) + +clean: + rm -rf fft + rm -rf gen diff --git a/gen.c b/gen.c new file mode 100644 index 0000000..863596e --- /dev/null +++ b/gen.c @@ -0,0 +1,25 @@ +#include +#include +#include + +#define SAMPLE_RATE 44100 +#define DURATION 5 +#define FREQUENCY 440 +#define VOLUME 0.3f + +int main() { + for (size_t i = 0; i < DURATION * SAMPLE_RATE; ++i) { + float t = (float)i / SAMPLE_RATE; + float volume = (float)INT16_MAX * VOLUME; + int16_t sample = 0; + + sample = (int16_t) + ( + (sinf(2*M_PI * FREQUENCY * t) * volume) + + (sinf(2*M_PI * 2*FREQUENCY * t) * volume) + ); + fwrite(&sample, sizeof(sample), 1, stdout); + } + + return 0; +} diff --git a/gen.sh b/gen.sh new file mode 100755 index 0000000..0ea5b7d --- /dev/null +++ b/gen.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -xe + +make gen && ./gen | ffmpeg -f s16le -i pipe: -y output.mp3 diff --git a/main.c b/main.c new file mode 100644 index 0000000..db1911e --- /dev/null +++ b/main.c @@ -0,0 +1,266 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STEP 1.06f +#define LOWF 1.0f +#define LERP 0.4f + +typedef struct { + float left; + float right; +} Frame; + +#define N (1<<13) +Frame g_frames[N]; +size_t g_frames_count = 0; + +float g_in[N]; +float complex g_out1[N]; +float complex g_out2[N]; +float complex *g_out_front = g_out1; +float complex *g_out_back = g_out2; + +void dft(float in[], float complex out[], size_t n) +{ + for (size_t f = 0; f < n; ++f) { + out[f] = 0.0f; + for (size_t i = 0; i < n; ++i) { + float t = (float)i / n; + out[f] += in[i] * cexpf(2*I*M_PI*f*t); + } + } +} + +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); + fft(in + stride, stride*2, out + n/2, n/2); + + for (size_t k = 0; k < n/2; ++k) { + float t = (float)k/n; + float complex v = cexpf(-2*I*M_PI*t) * out[k + n/2]; + float complex e = out[k]; + out[k] = e + v; + out[k + n/2] = e - v; + } +} + +void callback(void *buffer_data, unsigned int frames) +{ + if (frames <= N - g_frames_count) { + memcpy(g_frames + g_frames_count, buffer_data, sizeof(Frame) * frames); + g_frames_count += frames; + } else if (frames <= N) { + memmove(g_frames, g_frames + frames, sizeof(Frame) * (N - frames)); + memcpy(g_frames + (N - frames), buffer_data, sizeof(Frame) * frames); + } else { + memcpy(g_frames, buffer_data, sizeof(Frame) * N); + g_frames_count = N; + } +} + +void render_wave(void) +{ + float w = GetScreenWidth(); + float h = GetScreenHeight(); + float cell_width = (float)w/g_frames_count; + Color color = {50, 50, 50, 20}; +#if 1 + for (size_t i = 0; i < N; ++i) { + float sample = fabsf(g_frames[i].left); + // if (sample > 0.0f) { + // DrawRectangle(i*cell_width, h/2 - h/2*sample, 1, h/2*sample, color); + // } else { + // DrawRectangle(i*cell_width, h/2, 1, h/2*-sample, color); + // } + float height = h/2*sample/2; + // color.a = 40 + 128*sample; + color.r = 50*sample; + color.g = 64 - color.r; + color.b = 64 - color.r; + DrawRectangle(i*cell_width, h/2 - height, 1, 2*height, color); + } +#else + size_t to = g_frames_count == 0 ? 1 : g_frames_count; + for (size_t i = 0; i < to - 1; ++i) { + float t1 = (float)i/g_frames_count; + float t2 = (float)(i+1)/g_frames_count; + // float hann1 = 0.5f - 0.5f * cosf(2*M_PI*t1); + // float hann2 = 0.5f - 0.5f * cosf(2*M_PI*t2); + float sample1 = g_frames[i].left/3/* * hann1*/; + float sample2 = g_frames[i+1].left/3/* * hann2*/; + DrawLine(i*cell_width, h/2 + h/2*sample1, (i+1)*cell_width, h/2 + h/2*sample2, + color); + } +#endif +} + +float amp(float complex z) +{ + float a = crealf(z); + float b = cimagf(z); + return logf(a*a + b*b); +} + +float complex clerp(float complex start, float complex end, float amount) +{ + float complex result = start + amount*(end - start); + return result; +} + +float g_max_amp = 0.0f; +void render_fft(void) +{ + float w = GetScreenWidth(); + float h = GetScreenHeight(); + + for (size_t i = 0; i < g_frames_count; ++i) { + float t = (float)i/g_frames_count; + float hann = 0.5f - 0.5f * cosf(2*M_PI*t); + g_in[i] = g_frames[i].left * hann; + } + + float complex *tmp = g_out_back; + g_out_back = g_out_front; + g_out_front = tmp; + + fft(g_in, 1, g_out_back, N); + + for (size_t i = 0; i < N; ++i) { + float a = amp(g_out_back[i]); + if (a > g_max_amp) { + g_max_amp = a; + } + + g_out_back[i] = clerp(g_out_front[i], g_out_back[i], LERP); + } + + float step = STEP; + float lowf = LOWF; + size_t m = 0; + for (float f = lowf; (size_t)f < N/2; f = ceilf(f*step)) { + m += 1; + } + + float cell_width = (float)w/m; + float prev_t = 0.0f; + m = 0; + for (float f = lowf; (size_t)f < N/2; f = ceilf(f*step)) { + float f1 = ceilf(f*step); + float a = 0.0f; + for (size_t q = (size_t)f; q < N/2 && q < (size_t)f1; ++q) { + float b = amp(g_out_back[q]); + if (b > a) { + a = b; + } + } + float t = a/g_max_amp; + float tf = f / N/2; + Color c = {255*t, 255*tf, 255*m, 255*t}; + // DrawRectangle(m*cell_width, h - h/2*t, cell_width, h/2*t, c); + // RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color); + // RLAPI void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Color color); + float x1 = m*cell_width; + float y1 = h - h/2*prev_t; + float x2 = x1 + cell_width; + float y2 = h - h/2*t; + // DrawLine(x1, y1, x2, y2, c); + Vector2 v1 = {x1, h}; + Vector2 v2 = {x2, h}; + Vector2 v3 = {x1, y1}; + Vector2 v4 = {x2, y2}; + DrawTriangle(v1, v2, v4, c); + DrawTriangle(v4, v3, v1, c); + prev_t = t; + m += 1; + } +} + +int main(int argc, char **argv) +{ + if (argc <= 1) { + fprintf(stderr, "ERROR: Incorrect usage\n"); + return EXIT_FAILURE; + } + + SetConfigFlags(FLAG_WINDOW_RESIZABLE); + InitWindow(800, 600, "Fast Fourier Transform"); + SetTargetFPS(60); + InitAudioDevice(); + + Music music = LoadMusicStream(argv[1]); + printf("music.frameCount = %u\n", music.frameCount); + printf("music.stream.sampleRate = %u\n", music.stream.sampleRate); + printf("music.stream.sampleSize = %u\n", music.stream.sampleSize); + printf("music.stream.channels = %u\n", music.stream.channels); + + AttachAudioStreamProcessor(music.stream, callback); + SetMusicVolume(music, 0.5); + PlayMusicStream(music); + + bool toggle_wave = true; + bool toggle_fft = true; + float pitch = 1.0f; + + while (!WindowShouldClose()) { + UpdateMusicStream(music); + + if (IsKeyPressed(KEY_SPACE)) { + if (IsMusicStreamPlaying(music)) { + PauseMusicStream(music); + } else { + ResumeMusicStream(music); + } + } + if (IsKeyPressed(KEY_UP)) { + pitch += 0.05f; + SetMusicPitch(music, pitch); + } + if (IsKeyPressed(KEY_DOWN)) { + pitch -= 0.05f; + SetMusicPitch(music, pitch); + } + if (IsKeyPressed(KEY_LEFT)) { + SeekMusicStream(music, GetMusicTimePlayed(music) - 10.0f); + } + if (IsKeyPressed(KEY_RIGHT)) { + SeekMusicStream(music, GetMusicTimePlayed(music) + 10.0f); + } + if (IsKeyPressed(KEY_W)) { + toggle_wave = !toggle_wave; + } + if (IsKeyPressed(KEY_F)) { + toggle_fft = !toggle_fft; + } + + BeginDrawing(); + ClearBackground(BLACK); + if (toggle_wave) { + render_wave(); + } + if (toggle_fft) { + render_fft(); + } + EndDrawing(); + } + + CloseAudioDevice(); + CloseWindow(); + + return EXIT_SUCCESS; +}