// 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 // 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); }