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 +++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 src/audio/gen.cc create mode 100644 src/audio/gen.h (limited to 'src/audio') 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); -- cgit v1.2.3