summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-06 11:12:34 +0100
committerskal <pascal.massimino@gmail.com>2026-02-06 11:12:34 +0100
commit5a1adde097e489c259bd052971546e95683c3596 (patch)
treebf03cf8b803604638ad84ddd9cc26de64baea64f /src
parent83f34fb955524c09b7f3e124b97c3d4feef02a0c (diff)
feat(audio): Add Spectral Brush runtime (Phase 1 of Task #5)
Implement C++ runtime foundation for procedural audio tracing tool. Changes: - Created spectral_brush.h/cc with core API - Linear Bezier interpolation - Vertical profile evaluation (Gaussian, Decaying Sinusoid, Noise) - draw_bezier_curve() for spectrogram rendering - Home-brew deterministic RNG for noise profile - Added comprehensive unit tests (test_spectral_brush.cc) - Tests Bezier interpolation, profiles, edge cases - Tests full spectrogram rendering pipeline - All 9 tests pass - Integrated into CMake build system - Fixed test_assets.cc include (asset_manager_utils.h) Design: - Spectral Brush = Central Curve (Bezier) + Vertical Profile - Enables 50-100x compression (5KB .spec to 100 bytes C++ code) - Future: Cubic Bezier, composite profiles, multi-dimensional curves Documentation: - Added doc/SPECTRAL_BRUSH_EDITOR.md (complete architecture) - Updated TODO.md with Phase 1-4 implementation plan - Updated PROJECT_CONTEXT.md to mark Task #5 in progress Test results: 21/21 tests pass (100%) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'src')
-rw-r--r--src/audio/spectral_brush.cc171
-rw-r--r--src/audio/spectral_brush.h80
-rw-r--r--src/generated/assets_data.cc6
-rw-r--r--src/tests/test_assets.cc2
-rw-r--r--src/tests/test_spectral_brush.cc236
5 files changed, 491 insertions, 4 deletions
diff --git a/src/audio/spectral_brush.cc b/src/audio/spectral_brush.cc
new file mode 100644
index 0000000..c6eb64d
--- /dev/null
+++ b/src/audio/spectral_brush.cc
@@ -0,0 +1,171 @@
+// This file is part of the 64k demo project.
+// It implements the "Spectral Brush" primitive for procedural audio generation.
+// Implementation of Bezier curves, vertical profiles, and spectrogram rendering.
+
+#include "spectral_brush.h"
+
+#include <cmath>
+
+// Sample rate constant (matches demo audio configuration)
+static const float SAMPLE_RATE = 32000.0f;
+
+// Evaluate linear Bezier interpolation between control points
+float evaluate_bezier_linear(const float* control_frames,
+ const float* control_values,
+ int n_points,
+ float frame) {
+ if (n_points == 0) {
+ return 0.0f;
+ }
+ if (n_points == 1) {
+ return control_values[0];
+ }
+
+ // Clamp to first/last value if outside range
+ if (frame <= control_frames[0]) {
+ return control_values[0];
+ }
+ if (frame >= control_frames[n_points - 1]) {
+ return control_values[n_points - 1];
+ }
+
+ // Find segment containing frame
+ for (int i = 0; i < n_points - 1; ++i) {
+ if (frame >= control_frames[i] && frame <= control_frames[i + 1]) {
+ // Linear interpolation: value = v0 + (v1 - v0) * t
+ const float t =
+ (frame - control_frames[i]) / (control_frames[i + 1] - control_frames[i]);
+ return control_values[i] * (1.0f - t) + control_values[i + 1] * t;
+ }
+ }
+
+ // Should not reach here (fallback to last value)
+ return control_values[n_points - 1];
+}
+
+// Home-brew deterministic RNG (LCG algorithm)
+uint32_t spectral_brush_rand(uint32_t seed) {
+ // LCG parameters (from Numerical Recipes)
+ // X_{n+1} = (a * X_n + c) mod m
+ const uint32_t a = 1664525;
+ const uint32_t c = 1013904223;
+ return a * seed + c; // Implicit mod 2^32
+}
+
+// Evaluate vertical profile at distance from curve center
+float evaluate_profile(ProfileType type, float distance, float param1, float param2) {
+ switch (type) {
+ case PROFILE_GAUSSIAN: {
+ // Gaussian: exp(-(dist^2 / sigma^2))
+ // param1 = sigma (width in bins)
+ const float sigma = param1;
+ if (sigma <= 0.0f) {
+ return 0.0f;
+ }
+ return expf(-(distance * distance) / (sigma * sigma));
+ }
+
+ case PROFILE_DECAYING_SINUSOID: {
+ // Decaying sinusoid: exp(-decay * dist) * cos(omega * dist)
+ // param1 = decay rate
+ // param2 = oscillation frequency (omega)
+ const float decay = param1;
+ const float omega = param2;
+ const float envelope = expf(-decay * distance);
+ const float oscillation = cosf(omega * distance);
+ return envelope * oscillation;
+ }
+
+ case PROFILE_NOISE: {
+ // Random noise: deterministic RNG based on distance
+ // param1 = amplitude scale
+ // param2 = seed
+ const float amplitude = param1;
+ const uint32_t seed = (uint32_t)(param2) + (uint32_t)(distance * 1000.0f);
+ const uint32_t rand_val = spectral_brush_rand(seed);
+ // Map to [0, 1]
+ const float normalized = (float)(rand_val % 10000) / 10000.0f;
+ return amplitude * normalized;
+ }
+ }
+
+ return 0.0f;
+}
+
+// Internal implementation: Render Bezier curve with profile
+static void draw_bezier_curve_impl(float* spectrogram,
+ int dct_size,
+ int num_frames,
+ const float* control_frames,
+ const float* control_freqs_hz,
+ const float* control_amps,
+ int n_control_points,
+ ProfileType profile_type,
+ float profile_param1,
+ float profile_param2,
+ bool additive) {
+ if (n_control_points < 1) {
+ return; // Nothing to draw
+ }
+
+ // For each frame in the spectrogram
+ for (int f = 0; f < num_frames; ++f) {
+ // 1. Evaluate Bezier curve at this frame
+ const float freq_hz =
+ evaluate_bezier_linear(control_frames, control_freqs_hz, n_control_points, (float)f);
+ const float amplitude =
+ evaluate_bezier_linear(control_frames, control_amps, n_control_points, (float)f);
+
+ // 2. Convert frequency (Hz) to frequency bin index
+ // Nyquist frequency = SAMPLE_RATE / 2
+ // bin = (freq_hz / nyquist) * dct_size
+ const float nyquist = SAMPLE_RATE / 2.0f;
+ const float freq_bin_0 = (freq_hz / nyquist) * dct_size;
+
+ // 3. Apply vertical profile around freq_bin_0
+ for (int b = 0; b < dct_size; ++b) {
+ const float dist = fabsf(b - freq_bin_0);
+ const float profile_val = evaluate_profile(profile_type, dist, profile_param1, profile_param2);
+ const float contribution = amplitude * profile_val;
+
+ const int idx = f * dct_size + b;
+ if (additive) {
+ spectrogram[idx] += contribution;
+ } else {
+ spectrogram[idx] = contribution;
+ }
+ }
+ }
+}
+
+// Draw spectral brush (overwrites spectrogram content)
+void draw_bezier_curve(float* spectrogram,
+ int dct_size,
+ int num_frames,
+ const float* control_frames,
+ const float* control_freqs_hz,
+ const float* control_amps,
+ int n_control_points,
+ ProfileType profile_type,
+ float profile_param1,
+ float profile_param2) {
+ draw_bezier_curve_impl(spectrogram, dct_size, num_frames, control_frames, control_freqs_hz,
+ control_amps, n_control_points, profile_type, profile_param1,
+ profile_param2, false);
+}
+
+// Draw spectral brush (adds to existing spectrogram content)
+void draw_bezier_curve_add(float* spectrogram,
+ int dct_size,
+ int num_frames,
+ const float* control_frames,
+ const float* control_freqs_hz,
+ const float* control_amps,
+ int n_control_points,
+ ProfileType profile_type,
+ float profile_param1,
+ float profile_param2) {
+ draw_bezier_curve_impl(spectrogram, dct_size, num_frames, control_frames, control_freqs_hz,
+ control_amps, n_control_points, profile_type, profile_param1,
+ profile_param2, true);
+}
diff --git a/src/audio/spectral_brush.h b/src/audio/spectral_brush.h
new file mode 100644
index 0000000..3125f35
--- /dev/null
+++ b/src/audio/spectral_brush.h
@@ -0,0 +1,80 @@
+// This file is part of the 64k demo project.
+// It implements the "Spectral Brush" primitive for procedural audio generation.
+// Spectral brushes trace Bezier curves through spectrograms with vertical profiles.
+
+#pragma once
+
+#include <cstdint>
+
+// Profile types for vertical distribution around central Bezier curve
+enum ProfileType {
+ PROFILE_GAUSSIAN = 0, // Smooth harmonic falloff
+ PROFILE_DECAYING_SINUSOID = 1, // Resonant/metallic texture
+ PROFILE_NOISE = 2 // Random texture/grit
+};
+
+// Evaluate linear Bezier interpolation at given frame
+// control_frames: Array of frame positions for control points
+// control_values: Array of values at control points (freq_hz or amplitude)
+// n_points: Number of control points
+// frame: Frame number to evaluate at
+// Returns: Interpolated value at frame (linearly interpolated between control points)
+float evaluate_bezier_linear(const float* control_frames,
+ const float* control_values,
+ int n_points,
+ float frame);
+
+// Draw a spectral brush stroke onto a spectrogram
+// Traces a Bezier curve through time-frequency space with a vertical profile
+// spectrogram: Output buffer (dct_size Ɨ num_frames), modified in-place
+// dct_size: Number of frequency bins (e.g., 512)
+// num_frames: Number of time frames
+// control_frames: Frame positions of Bezier control points
+// control_freqs_hz: Frequency values (Hz) at control points
+// control_amps: Amplitude values at control points (0.0-1.0 typical)
+// n_control_points: Number of control points (minimum 2 for a curve)
+// profile_type: Type of vertical profile to apply
+// profile_param1: First parameter (sigma for Gaussian, decay for sinusoid, amplitude for noise)
+// profile_param2: Second parameter (unused for Gaussian, frequency for sinusoid, seed for noise)
+void draw_bezier_curve(float* spectrogram,
+ int dct_size,
+ int num_frames,
+ const float* control_frames,
+ const float* control_freqs_hz,
+ const float* control_amps,
+ int n_control_points,
+ ProfileType profile_type,
+ float profile_param1,
+ float profile_param2 = 0.0f);
+
+// Additive variant of draw_bezier_curve (adds to existing spectrogram content)
+// Use for compositing multiple profiles (e.g., Gaussian + Noise)
+// Parameters same as draw_bezier_curve()
+void draw_bezier_curve_add(float* spectrogram,
+ int dct_size,
+ int num_frames,
+ const float* control_frames,
+ const float* control_freqs_hz,
+ const float* control_amps,
+ int n_control_points,
+ ProfileType profile_type,
+ float profile_param1,
+ float profile_param2 = 0.0f);
+
+// Evaluate vertical profile at given distance from central curve
+// type: Profile type (Gaussian, Decaying Sinusoid, Noise)
+// distance: Distance in frequency bins from curve center
+// param1: First profile parameter
+// param2: Second profile parameter
+// Returns: Profile amplitude at given distance (0.0-1.0 range typically)
+float evaluate_profile(ProfileType type,
+ float distance,
+ float param1,
+ float param2);
+
+// Home-brew deterministic RNG for noise profile
+// Simple linear congruential generator (LCG) for small code size
+// seed: Input seed value
+// Returns: Pseudo-random uint32_t value
+uint32_t spectral_brush_rand(uint32_t seed);
+
diff --git a/src/generated/assets_data.cc b/src/generated/assets_data.cc
index a29680f..0b2daba 100644
--- a/src/generated/assets_data.cc
+++ b/src/generated/assets_data.cc
@@ -369584,7 +369584,7 @@ static const float ASSET_PROC_PARAMS_NOISE_TEX[] = {1234.000000, 16.000000};
static const char* ASSET_PROC_FUNC_STR_NOISE_TEX = "gen_noise";
-const size_t ASSET_SIZE_SHADER_RENDERER_3D = 9468;
+const size_t ASSET_SIZE_SHADER_RENDERER_3D = 9449;
alignas(16) static const uint8_t ASSET_DATA_SHADER_RENDERER_3D[] = {
0x23, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x20, 0x22, 0x63, 0x6f,
0x6d, 0x6d, 0x6f, 0x6e, 0x5f, 0x75, 0x6e, 0x69, 0x66, 0x6f, 0x72, 0x6d,
@@ -370373,9 +370373,7 @@ alignas(16) static const uint8_t ASSET_DATA_SHADER_RENDERER_3D[] = {
0x6f, 0x73, 0x2e, 0x7a, 0x20, 0x2f, 0x20, 0x63, 0x6c, 0x69, 0x70, 0x5f,
0x70, 0x6f, 0x73, 0x2e, 0x77, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6f,
- 0x75, 0x74, 0x3b, 0x0a, 0x7d, 0x2f, 0x2f, 0x20, 0x64, 0x65, 0x70, 0x65,
- 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x20, 0x74, 0x65, 0x73, 0x74, 0x0a,
- 0x00
+ 0x75, 0x74, 0x3b, 0x0a, 0x7d, 0x00
};
const size_t ASSET_SIZE_SHADER_COMMON_UNIFORMS = 346;
alignas(16) static const uint8_t ASSET_DATA_SHADER_COMMON_UNIFORMS[] = {
diff --git a/src/tests/test_assets.cc b/src/tests/test_assets.cc
index 86b4ba4..2ee18d6 100644
--- a/src/tests/test_assets.cc
+++ b/src/tests/test_assets.cc
@@ -8,6 +8,8 @@
#include "generated/assets.h"
#endif /* defined(USE_TEST_ASSETS) */
+#include "util/asset_manager_utils.h"
+
#include <assert.h>
#include <stdio.h>
#include <string.h>
diff --git a/src/tests/test_spectral_brush.cc b/src/tests/test_spectral_brush.cc
new file mode 100644
index 0000000..1431ba7
--- /dev/null
+++ b/src/tests/test_spectral_brush.cc
@@ -0,0 +1,236 @@
+// This file is part of the 64k demo project.
+// Unit tests for spectral brush primitives.
+// Tests linear Bezier interpolation, profiles, and spectrogram rendering.
+
+#include "audio/spectral_brush.h"
+
+#include <cassert>
+#include <cmath>
+#include <cstdio>
+#include <cstring>
+
+// Test tolerance for floating-point comparisons
+static const float EPSILON = 1e-5f;
+
+// Helper: Compare floats with tolerance
+static bool float_eq(float a, float b) {
+ return fabsf(a - b) < EPSILON;
+}
+
+// Test: Linear Bezier interpolation with 2 control points (simple line)
+void test_bezier_linear_2points() {
+ const float frames[] = {0.0f, 100.0f};
+ const float values[] = {50.0f, 150.0f};
+
+ // At control points, should return exact values
+ assert(float_eq(evaluate_bezier_linear(frames, values, 2, 0.0f), 50.0f));
+ assert(float_eq(evaluate_bezier_linear(frames, values, 2, 100.0f), 150.0f));
+
+ // Midpoint: linear interpolation
+ const float mid = evaluate_bezier_linear(frames, values, 2, 50.0f);
+ assert(float_eq(mid, 100.0f)); // (50 + 150) / 2
+
+ // Quarter point
+ const float quarter = evaluate_bezier_linear(frames, values, 2, 25.0f);
+ assert(float_eq(quarter, 75.0f)); // 50 + (150 - 50) * 0.25
+
+ printf("[PASS] test_bezier_linear_2points\n");
+}
+
+// Test: Linear Bezier interpolation with 4 control points
+void test_bezier_linear_4points() {
+ const float frames[] = {0.0f, 20.0f, 50.0f, 100.0f};
+ const float values[] = {200.0f, 80.0f, 60.0f, 50.0f};
+
+ // At control points
+ assert(float_eq(evaluate_bezier_linear(frames, values, 4, 0.0f), 200.0f));
+ assert(float_eq(evaluate_bezier_linear(frames, values, 4, 20.0f), 80.0f));
+ assert(float_eq(evaluate_bezier_linear(frames, values, 4, 50.0f), 60.0f));
+ assert(float_eq(evaluate_bezier_linear(frames, values, 4, 100.0f), 50.0f));
+
+ // Between first and second point (frame 10)
+ const float interp1 = evaluate_bezier_linear(frames, values, 4, 10.0f);
+ // t = (10 - 0) / (20 - 0) = 0.5
+ // value = 200 * 0.5 + 80 * 0.5 = 140
+ assert(float_eq(interp1, 140.0f));
+
+ // Between third and fourth point (frame 75)
+ const float interp2 = evaluate_bezier_linear(frames, values, 4, 75.0f);
+ // t = (75 - 50) / (100 - 50) = 0.5
+ // value = 60 * 0.5 + 50 * 0.5 = 55
+ assert(float_eq(interp2, 55.0f));
+
+ printf("[PASS] test_bezier_linear_4points\n");
+}
+
+// Test: Edge cases (single point, empty, out of range)
+void test_bezier_edge_cases() {
+ const float frames[] = {50.0f};
+ const float values[] = {123.0f};
+
+ // Single control point: always return that value
+ assert(float_eq(evaluate_bezier_linear(frames, values, 1, 0.0f), 123.0f));
+ assert(float_eq(evaluate_bezier_linear(frames, values, 1, 100.0f), 123.0f));
+
+ // Empty array: return 0
+ assert(float_eq(evaluate_bezier_linear(frames, values, 0, 50.0f), 0.0f));
+
+ // Out of range: clamp to endpoints
+ const float frames2[] = {10.0f, 90.0f};
+ const float values2[] = {100.0f, 200.0f};
+ assert(float_eq(evaluate_bezier_linear(frames2, values2, 2, 0.0f), 100.0f)); // Before start
+ assert(float_eq(evaluate_bezier_linear(frames2, values2, 2, 100.0f), 200.0f)); // After end
+
+ printf("[PASS] test_bezier_edge_cases\n");
+}
+
+// Test: Gaussian profile evaluation
+void test_profile_gaussian() {
+ // At center (distance = 0), should be 1.0
+ assert(float_eq(evaluate_profile(PROFILE_GAUSSIAN, 0.0f, 30.0f, 0.0f), 1.0f));
+
+ // Gaussian falloff: exp(-(dist^2 / sigma^2))
+ const float sigma = 30.0f;
+ const float dist = 15.0f;
+ const float expected = expf(-(dist * dist) / (sigma * sigma));
+ const float actual = evaluate_profile(PROFILE_GAUSSIAN, dist, sigma, 0.0f);
+ assert(float_eq(actual, expected));
+
+ // Far from center: should approach 0
+ const float far = evaluate_profile(PROFILE_GAUSSIAN, 100.0f, 30.0f, 0.0f);
+ assert(far < 0.01f); // Very small
+
+ printf("[PASS] test_profile_gaussian\n");
+}
+
+// Test: Decaying sinusoid profile evaluation
+void test_profile_decaying_sinusoid() {
+ const float decay = 0.15f;
+ const float omega = 0.8f;
+
+ // At center (distance = 0)
+ // exp(-0 * 0.15) * cos(0 * 0.8) = 1.0 * 1.0 = 1.0
+ assert(float_eq(evaluate_profile(PROFILE_DECAYING_SINUSOID, 0.0f, decay, omega), 1.0f));
+
+ // At distance 10
+ const float dist = 10.0f;
+ const float expected = expf(-decay * dist) * cosf(omega * dist);
+ const float actual = evaluate_profile(PROFILE_DECAYING_SINUSOID, dist, decay, omega);
+ assert(float_eq(actual, expected));
+
+ printf("[PASS] test_profile_decaying_sinusoid\n");
+}
+
+// Test: Noise profile evaluation (deterministic)
+void test_profile_noise() {
+ const float amplitude = 0.5f;
+ const uint32_t seed = 42;
+
+ // Same distance + seed should produce same value
+ const float val1 = evaluate_profile(PROFILE_NOISE, 10.0f, amplitude, (float)seed);
+ const float val2 = evaluate_profile(PROFILE_NOISE, 10.0f, amplitude, (float)seed);
+ assert(float_eq(val1, val2));
+
+ // Different distance should produce different value (with high probability)
+ const float val3 = evaluate_profile(PROFILE_NOISE, 20.0f, amplitude, (float)seed);
+ assert(!float_eq(val1, val3));
+
+ // Should be in range [0, amplitude]
+ assert(val1 >= 0.0f && val1 <= amplitude);
+
+ printf("[PASS] test_profile_noise\n");
+}
+
+// Test: draw_bezier_curve full integration
+void test_draw_bezier_curve() {
+ const int dct_size = 512;
+ const int num_frames = 100;
+ float spectrogram[512 * 100];
+ memset(spectrogram, 0, sizeof(spectrogram));
+
+ // Simple curve: constant frequency, linearly decaying amplitude
+ const float frames[] = {0.0f, 100.0f};
+ const float freqs[] = {440.0f, 440.0f}; // A4 note (constant pitch)
+ const float amps[] = {1.0f, 0.0f}; // Fade out
+
+ draw_bezier_curve(spectrogram, dct_size, num_frames, frames, freqs, amps, 2, PROFILE_GAUSSIAN,
+ 30.0f);
+
+ // Verify: At frame 0, should have peak around 440 Hz bin
+ // bin = (440 / 16000) * 512 ā‰ˆ 14.08
+ const int expected_bin = 14;
+ const float val_at_peak = spectrogram[0 * dct_size + expected_bin];
+ assert(val_at_peak > 0.5f); // Should be near 1.0 due to Gaussian
+
+ // Verify: At frame 99 (end), amplitude should be near 0
+ const float val_at_end = spectrogram[99 * dct_size + expected_bin];
+ assert(val_at_end < 0.1f); // Near zero
+
+ // Verify: At frame 50 (midpoint), amplitude should be ~0.5
+ const float val_at_mid = spectrogram[50 * dct_size + expected_bin];
+ assert(val_at_mid > 0.3f && val_at_mid < 0.7f); // Around 0.5
+
+ printf("[PASS] test_draw_bezier_curve\n");
+}
+
+// Test: draw_bezier_curve_add (additive mode)
+void test_draw_bezier_curve_add() {
+ const int dct_size = 512;
+ const int num_frames = 100;
+ float spectrogram[512 * 100];
+ memset(spectrogram, 0, sizeof(spectrogram));
+
+ // Draw first curve
+ const float frames1[] = {0.0f, 100.0f};
+ const float freqs1[] = {440.0f, 440.0f};
+ const float amps1[] = {0.5f, 0.5f};
+ draw_bezier_curve(spectrogram, dct_size, num_frames, frames1, freqs1, amps1, 2, PROFILE_GAUSSIAN,
+ 30.0f);
+
+ const int bin = 14; // ~440 Hz
+ const float val_before_add = spectrogram[0 * dct_size + bin];
+
+ // Add second curve (same frequency, same amplitude)
+ draw_bezier_curve_add(spectrogram, dct_size, num_frames, frames1, freqs1, amps1, 2,
+ PROFILE_GAUSSIAN, 30.0f);
+
+ const float val_after_add = spectrogram[0 * dct_size + bin];
+
+ // Should be approximately doubled
+ assert(val_after_add > val_before_add * 1.8f); // Allow small error
+
+ printf("[PASS] test_draw_bezier_curve_add\n");
+}
+
+// Test: RNG determinism
+void test_rng_determinism() {
+ const uint32_t seed = 12345;
+
+ // Same seed should produce same value
+ const uint32_t val1 = spectral_brush_rand(seed);
+ const uint32_t val2 = spectral_brush_rand(seed);
+ assert(val1 == val2);
+
+ // Different seeds should produce different values
+ const uint32_t val3 = spectral_brush_rand(seed + 1);
+ assert(val1 != val3);
+
+ printf("[PASS] test_rng_determinism\n");
+}
+
+int main() {
+ printf("Running spectral brush tests...\n\n");
+
+ test_bezier_linear_2points();
+ test_bezier_linear_4points();
+ test_bezier_edge_cases();
+ test_profile_gaussian();
+ test_profile_decaying_sinusoid();
+ test_profile_noise();
+ test_draw_bezier_curve();
+ test_draw_bezier_curve_add();
+ test_rng_determinism();
+
+ printf("\nāœ“ All tests passed!\n");
+ return 0;
+}