summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt5
-rw-r--r--src/audio/gen.cc138
-rw-r--r--src/audio/gen.h38
-rw-r--r--src/main.cc65
-rw-r--r--tools/spectool.cc80
5 files changed, 320 insertions, 6 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 349c9e6..7f42c47 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -102,6 +102,7 @@ add_executable(demo64k
src/platform.cc
src/gpu/gpu.cc
src/audio/audio.cc
+ src/audio/gen.cc
src/audio/fdct.cc
src/audio/idct.cc
src/audio/window.cc
@@ -161,6 +162,7 @@ if(DEMO_BUILD_TESTS)
src/audio/audio.cc
src/audio/window.cc
src/audio/fdct.cc
+ src/audio/gen.cc
src/audio/synth.cc
src/audio/idct.cc
third_party/glfw3webgpu/glfw3webgpu.c
@@ -188,11 +190,12 @@ endif()
# Tools
option(DEMO_BUILD_TOOLS "Build tools" OFF)
-if(DEMO_BUILD_TOOLS)
+if(DEMO_BUILD_TOOLS OR DEMO_BUILD_TESTS)
add_executable(spectool
tools/spectool.cc
src/platform.cc
src/audio/audio.cc
+ src/audio/gen.cc
src/audio/fdct.cc
src/audio/idct.cc
src/audio/window.cc
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 <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+std::vector<float> generate_note_spectrogram(const NoteParams &params,
+ 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<float> 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<float> &dest_data, int *dest_num_frames,
+ const std::vector<float> &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<float> &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<float> &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<float> &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 <vector>
+
+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<float> generate_note_spectrogram(const NoteParams &params,
+ 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<float> &dest_data, int *dest_num_frames,
+ const std::vector<float> &src_data, int src_num_frames,
+ int frame_offset);
+
+// Post-processing effects
+void apply_spectral_noise(std::vector<float> &data, int num_frames,
+ float amount);
+void apply_spectral_lowpass(std::vector<float> &data, int num_frames,
+ float cutoff_ratio);
+void apply_spectral_comb(std::vector<float> &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 <math.h>
#include <stdio.h>
#include <string.h>
+#include <vector>
#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<float> 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<float> note_data = generate_note_spectrogram(params, &note_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;
diff --git a/tools/spectool.cc b/tools/spectool.cc
index 29a2952..7733b98 100644
--- a/tools/spectool.cc
+++ b/tools/spectool.cc
@@ -4,6 +4,7 @@
#include "audio/audio.h"
#include "audio/dct.h"
+#include "audio/gen.h"
#include "audio/synth.h"
#include "audio/window.h"
#include "platform.h"
@@ -12,6 +13,9 @@
#include "miniaudio.h"
+#include <math.h>
+#include <stdlib.h>
+#include <time.h>
#include <vector>
// Simple .spec file format:
@@ -149,6 +153,60 @@ int play_spec(const char *in_path) {
return 0;
}
+int test_gen(const char *out_path) {
+ printf("Generating test spectrogram -> %s\n", out_path);
+
+ std::vector<float> track_data;
+ int track_frames = 0;
+
+ // Generate a simple C Major scale
+ float freqs[] = {261.63f, 293.66f, 329.63f, 349.23f, 392.00f, 440.00f, 493.88f, 523.25f};
+
+ srand(time(NULL));
+
+ for (int i = 0; i < 8; ++i) {
+ NoteParams params;
+ params.base_freq = freqs[i];
+ params.duration_sec = 0.5f;
+ params.amplitude = 0.5f;
+ params.attack_sec = 0.05f;
+ params.decay_sec = 0.1f;
+ params.vibrato_rate = 5.0f;
+ params.vibrato_depth = 2.0f;
+ params.num_harmonics = 5;
+ params.harmonic_decay = 0.5f;
+ params.pitch_randomness = 1.0f;
+ params.amp_randomness = 0.05f;
+
+ int note_frames = 0;
+ std::vector<float> note_data = generate_note_spectrogram(params, &note_frames);
+
+ // Paste at 0.4s intervals (overlap)
+ int offset = (int)(i * 0.4f * 32000.0f / DCT_SIZE);
+ paste_spectrogram(track_data, &track_frames, note_data, note_frames, offset);
+ }
+
+ // Write to file (Duplicate logic, but fine for now)
+ FILE *f_out = fopen(out_path, "wb");
+ if (!f_out) {
+ printf("Error: Failed to open output file: %s\n", out_path);
+ return 1;
+ }
+
+ SpecHeader header;
+ memcpy(header.magic, "SPEC", 4);
+ header.version = 1;
+ header.dct_size = DCT_SIZE;
+ header.num_frames = track_frames;
+
+ fwrite(&header, sizeof(SpecHeader), 1, f_out);
+ fwrite(track_data.data(), sizeof(float), track_data.size(), f_out);
+ fclose(f_out);
+
+ printf("Generated %d frames.\n", track_frames);
+ return 0;
+}
+
void print_usage() {
printf("Usage: spectool <command> <input> [output]\n");
printf("Commands:\n");
@@ -156,26 +214,38 @@ void print_usage() {
"save as a spectrogram.\n");
printf(
" play <input.spec> Play a spectrogram file.\n");
+ printf(" test_gen <output.spec> Generate a test spectrogram.\n");
}
int main(int argc, char **argv) {
- if (argc < 3) {
+ if (argc < 2) {
print_usage();
return 1;
}
const char *command = argv[1];
- const char *input_path = argv[2];
if (strcmp(command, "analyze") == 0) {
if (argc < 4) {
- printf("Error: 'analyze' command requires an output file.\n");
+ printf("Error: 'analyze' command requires input and output files.\n");
print_usage();
return 1;
}
- return analyze_audio(input_path, argv[3]);
+ return analyze_audio(argv[2], argv[3]);
} else if (strcmp(command, "play") == 0) {
- return play_spec(input_path);
+ if (argc < 3) {
+ printf("Error: 'play' command requires an input file.\n");
+ print_usage();
+ return 1;
+ }
+ return play_spec(argv[2]);
+ } else if (strcmp(command, "test_gen") == 0) {
+ if (argc < 3) {
+ printf("Error: 'test_gen' command requires an output file.\n");
+ print_usage();
+ return 1;
+ }
+ return test_gen(argv[2]);
} else {
printf("Error: Unknown command '%s'\n", command);
print_usage();