From 8e199322ea4cb51d81c29d84120e4b142f7241b5 Mon Sep 17 00:00:00 2001 From: skal Date: Sat, 31 Jan 2026 00:43:16 +0100 Subject: add notes --- src/audio/gen.cc | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/audio/gen.h | 38 +++++++++++++++ src/main.cc | 65 ++++++++++++++++++++++++++ 3 files changed, 241 insertions(+) create mode 100644 src/audio/gen.cc create mode 100644 src/audio/gen.h (limited to 'src') diff --git a/src/audio/gen.cc b/src/audio/gen.cc new file mode 100644 index 0000000..3666116 --- /dev/null +++ b/src/audio/gen.cc @@ -0,0 +1,138 @@ +// This file is part of the 64k demo project. +// It implements the procedural audio generation logic. +// Uses IDCT/FDCT to synthesize notes in the frequency domain. + +#include "audio/gen.h" +#include "audio/dct.h" +#include "audio/window.h" +#include +#include +#include + +std::vector generate_note_spectrogram(const NoteParams ¶ms, + int *out_num_frames) { + int num_frames = (int)(params.duration_sec * 32000.0f / DCT_SIZE); + if (num_frames < 1) + num_frames = 1; + *out_num_frames = num_frames; + + std::vector spec_data(num_frames * DCT_SIZE, 0.0f); + float window[WINDOW_SIZE]; + hamming_window_512(window); + + float phase = 0.0f; + float time_step = 1.0f / 32000.0f; + + for (int f = 0; f < num_frames; ++f) { + float pcm_chunk[DCT_SIZE] = {0}; + float frame_time = f * DCT_SIZE * time_step; + + // Generate PCM for this frame + for (int i = 0; i < DCT_SIZE; ++i) { + float t = frame_time + i * time_step; + + // Envelope (Simple Attack) + float env = 1.0f; + if (t < params.attack_sec) { + env = t / params.attack_sec; + } + + // Vibrato + float vib = + sinf(t * params.vibrato_rate * 2.0f * 3.14159f) * params.vibrato_depth; + + // Randomness + float pitch_rnd = + ((float)rand() / RAND_MAX - 0.5f) * params.pitch_randomness; + float amp_rnd = ((float)rand() / RAND_MAX - 0.5f) * params.amp_randomness; + + float sample = 0.0f; + for (int h = 1; h <= params.num_harmonics; ++h) { + float h_amp = powf(params.harmonic_decay, h - 1); + float freq = (params.base_freq + vib + pitch_rnd) * h; + sample += sinf(phase * h) * h_amp; + } + + // Update phase for fundamental (approximate, since freq changes) + phase += + (params.base_freq + vib + pitch_rnd) * 2.0f * 3.14159f * time_step; + + pcm_chunk[i] = sample * params.amplitude * env * (1.0f + amp_rnd); + } + + // Apply window + for (int i = 0; i < DCT_SIZE; ++i) { + pcm_chunk[i] *= window[i]; + } + + // Apply FDCT + float dct_chunk[DCT_SIZE]; + fdct_512(pcm_chunk, dct_chunk); + + // Copy to buffer + for (int i = 0; i < DCT_SIZE; ++i) { + spec_data[f * DCT_SIZE + i] = dct_chunk[i]; + } + } + + return spec_data; +} + +void paste_spectrogram(std::vector &dest_data, int *dest_num_frames, + const std::vector &src_data, int src_num_frames, + int frame_offset) { + if (src_num_frames <= 0) + return; + + int needed_frames = frame_offset + src_num_frames; + if (needed_frames > *dest_num_frames) { + dest_data.resize(needed_frames * DCT_SIZE, 0.0f); + *dest_num_frames = needed_frames; + } + + for (int f = 0; f < src_num_frames; ++f) { + int dst_frame_idx = frame_offset + f; + if (dst_frame_idx < 0) + continue; + + for (int i = 0; i < DCT_SIZE; ++i) { + dest_data[dst_frame_idx * DCT_SIZE + i] += src_data[f * DCT_SIZE + i]; + } + } +} + +void apply_spectral_noise(std::vector &data, int num_frames, + float amount) { + for (int f = 0; f < num_frames; ++f) { + for (int i = 0; i < DCT_SIZE; ++i) { + float rnd = ((float)rand() / RAND_MAX - 0.5f) * 2.0f * amount; + data[f * DCT_SIZE + i] *= (1.0f + rnd); + } + } +} + +void apply_spectral_lowpass(std::vector &data, int num_frames, + float cutoff_ratio) { + int cutoff_bin = (int)(cutoff_ratio * DCT_SIZE); + if (cutoff_bin < 0) + cutoff_bin = 0; + if (cutoff_bin >= DCT_SIZE) + return; + + for (int f = 0; f < num_frames; ++f) { + for (int i = cutoff_bin; i < DCT_SIZE; ++i) { + data[f * DCT_SIZE + i] = 0.0f; + } + } +} + +void apply_spectral_comb(std::vector &data, int num_frames, + float period_bins, float depth) { + for (int i = 0; i < DCT_SIZE; ++i) { + float mod = + 1.0f - depth * (0.5f + 0.5f * cosf((float)i / period_bins * 6.28318f)); + for (int f = 0; f < num_frames; ++f) { + data[f * DCT_SIZE + i] *= mod; + } + } +} \ No newline at end of file diff --git a/src/audio/gen.h b/src/audio/gen.h new file mode 100644 index 0000000..f490115 --- /dev/null +++ b/src/audio/gen.h @@ -0,0 +1,38 @@ +// This file is part of the 64k demo project. +// It defines the procedural audio generation interface. +// Shared between the offline spectool and the runtime demo. + +#pragma once +#include + +struct NoteParams { + float base_freq; + float duration_sec; + float amplitude; + float attack_sec; + float decay_sec; // Unused for now + float vibrato_rate; + float vibrato_depth; + int num_harmonics; + float harmonic_decay; + float pitch_randomness; + float amp_randomness; +}; + +// Generates a single note into a new spectrogram buffer +std::vector generate_note_spectrogram(const NoteParams ¶ms, + int *out_num_frames); + +// Pastes a source spectrogram into a destination spectrogram at a given frame +// offset Expands destination if necessary +void paste_spectrogram(std::vector &dest_data, int *dest_num_frames, + const std::vector &src_data, int src_num_frames, + int frame_offset); + +// Post-processing effects +void apply_spectral_noise(std::vector &data, int num_frames, + float amount); +void apply_spectral_lowpass(std::vector &data, int num_frames, + float cutoff_ratio); +void apply_spectral_comb(std::vector &data, int num_frames, + float period_bins, float depth); diff --git a/src/main.cc b/src/main.cc index 5da76ba..0bc28d6 100644 --- a/src/main.cc +++ b/src/main.cc @@ -4,6 +4,7 @@ #include "assets.h" // Include generated asset header #include "audio/audio.h" +#include "audio/gen.h" #include "audio/synth.h" #include "gpu/gpu.h" #include "platform.h" @@ -12,6 +13,7 @@ #include #include #include +#include #define DEMO_BPM 128.0f #define SECONDS_PER_BEAT (60.0f / DEMO_BPM) @@ -44,6 +46,65 @@ int register_spec_asset(AssetId id) { static float g_spec_buffer_a[SPEC_FRAMES * DCT_SIZE]; static float g_spec_buffer_b[SPEC_FRAMES * DCT_SIZE]; +// Global storage for the melody to ensure it persists +std::vector g_melody_data; + +int generate_melody() { + g_melody_data.clear(); + int melody_frames = 0; + + // Simple C Minor pentatonic-ish sequence + float notes[] = {261.63f, 311.13f, 349.23f, 392.00f, 466.16f, 523.25f}; + int num_notes = 6; + + // 128 beats at 128 BPM = 60 seconds + // Generate a random sequence + srand(12345); // Fixed seed for reproducibility + + for (int i = 0; i < 128; ++i) { + if (i % 4 == 0) continue; // Rest on beat 1 of every bar + + NoteParams params = {}; + params.base_freq = notes[rand() % num_notes]; + if (rand() % 4 == 0) params.base_freq *= 2.0f; // Occasional octave up + + params.duration_sec = (rand() % 2 == 0) ? 0.2f : 0.4f; + params.amplitude = 0.4f; + params.attack_sec = 0.05f; + params.decay_sec = 0.1f; + params.vibrato_rate = 6.0f; + params.vibrato_depth = 1.5f; + params.num_harmonics = 4; + params.harmonic_decay = 0.6f; + params.pitch_randomness = 0.5f; + params.amp_randomness = 0.05f; + + int note_frames = 0; + std::vector note_data = generate_note_spectrogram(params, ¬e_frames); + + // Apply some post-processing for texture + apply_spectral_noise(note_data, note_frames, 0.2f); // Add grit + if (i % 2 == 0) { + apply_spectral_comb(note_data, note_frames, 10.0f, 0.8f); // Phaser-like effect + } + + // Calculate offset in frames + // i is the beat index (quarter notes) + // 1 beat = 60 / 128 seconds = 0.46875 sec + float beat_time = i * SECONDS_PER_BEAT; + int frame_offset = (int)(beat_time * 32000.0f / DCT_SIZE); + + paste_spectrogram(g_melody_data, &melody_frames, note_data, note_frames, frame_offset); + } + + Spectrogram spec; + spec.spectral_data_a = g_melody_data.data(); + spec.spectral_data_b = g_melody_data.data(); + spec.num_frames = melody_frames; + + return synth_register_spectrogram(&spec); +} + void generate_tone(float *buffer, float freq) { memset(buffer, 0, SPEC_FRAMES * DCT_SIZE * sizeof(float)); for (int frame = 0; frame < SPEC_FRAMES; ++frame) { @@ -88,6 +149,10 @@ int main(int argc, char **argv) { const Spectrogram bass_spec = {g_spec_buffer_a, g_spec_buffer_b, SPEC_FRAMES}; int bass_id = synth_register_spectrogram(&bass_spec); + // Generate and play melody + int melody_id = generate_melody(); + synth_trigger_voice(melody_id, 0.6f, 0.0f); + double last_beat_time = 0.0; int beat_count = 0; -- cgit v1.2.3