diff options
| -rw-r--r-- | CMakeLists.txt | 9 | ||||
| -rw-r--r-- | GEMINI.md | 30 | ||||
| -rw-r--r-- | assets/music.track | 35 | ||||
| -rw-r--r-- | src/audio/tracker.cc | 95 | ||||
| -rw-r--r-- | src/audio/tracker.h | 42 | ||||
| -rw-r--r-- | tools/tracker_compiler.cc | 159 |
6 files changed, 357 insertions, 13 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a4bcab..1ecf8ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,6 +112,15 @@ else() set(SEQ_COMPILER_DEPENDS seq_compiler) endif() +if (DEFINED TRACKER_COMPILER_PATH) + set(TRACKER_COMPILER_CMD ${TRACKER_COMPILER_PATH}) + set(TRACKER_COMPILER_DEPENDS ${TRACKER_COMPILER_PATH}) +else() + add_executable(tracker_compiler tools/tracker_compiler.cc) + set(TRACKER_COMPILER_CMD $<TARGET_FILE:tracker_compiler>) + set(TRACKER_COMPILER_DEPENDS tracker_compiler) +endif() + #-- - Code Generation Helpers -- - function(pack_assets NAME INPUT_TXT HEADER_VAR DATA_CC_VAR TARGET_NAME) set(OUT_H ${CMAKE_CURRENT_SOURCE_DIR}/src/generated/${NAME}.h) @@ -1,16 +1,20 @@ # Project Context @PROJECT_CONTEXT.md -@ASSET_SYSTEM.md -@BUILD.md -@FETCH_DEPS.md -@CONTRIBUTING.md -@HOWTO.md -@3D.md @TODO.md -@SPEC_EDITOR.md -@TRACKER.md -@PROCEDURAL.md -@src/util/asset_manager.h -@tools/asset_packer.cc -@tools/spectool.cc -@tools/specview.cc + +# helping and quick-start tutorials + +@README.md +@doc/HOWTO.md + +# design docs +@doc/ASSET_SYSTEM.md +@doc/BUILD.md +@doc/FETCH_DEPS.md +@doc/3D.md +@doc/SPEC_EDITOR.md +@doc/TRACKER.md +@doc/PROCEDURAL.md + +# coding guidelines +@doc/CONTRIBUTING.md diff --git a/assets/music.track b/assets/music.track new file mode 100644 index 0000000..f847fff --- /dev/null +++ b/assets/music.track @@ -0,0 +1,35 @@ +# Music Score for 64k Demo + +BPM 120 + +# Samples: name, freq, duration, amp, attack, harmonics, harmonic_decay +SAMPLE kick, 50, 0.2, 1.0, 0.01, 1, 0.5 +SAMPLE snare, 200, 0.2, 0.8, 0.01, 5, 0.7 +SAMPLE hihat, 1000, 0.05, 0.3, 0.005, 10, 0.4 + +# Patterns: beat, sample, volume, pan +PATTERN drum_loop + 0.0, kick, 1.0, 0.0 + 1.0, snare, 0.7, 0.0 + 2.0, kick, 1.0, 0.0 + 2.5, kick, 0.6, 0.2 + 3.0, snare, 0.7, 0.0 + +PATTERN hihat_roll + 0.0, hihat, 0.5, -0.5 + 0.5, hihat, 0.4, 0.5 + 1.0, hihat, 0.5, -0.5 + 1.5, hihat, 0.4, 0.5 + 2.0, hihat, 0.5, -0.5 + 2.5, hihat, 0.4, 0.5 + 3.0, hihat, 0.5, -0.5 + 3.5, hihat, 0.4, 0.5 + +# Score: time_sec, pattern_name +SCORE + 0.0, drum_loop + 0.0, hihat_roll + 4.0, drum_loop + 4.0, hihat_roll + 8.0, drum_loop + 12.0, drum_loop diff --git a/src/audio/tracker.cc b/src/audio/tracker.cc new file mode 100644 index 0000000..0b57ce3 --- /dev/null +++ b/src/audio/tracker.cc @@ -0,0 +1,95 @@ +// This file is part of the 64k demo project. +// It implements the tracker runtime logic. + +#include "tracker.h" +#include "audio/synth.h" +#include <vector> + +static uint32_t g_last_trigger_idx = 0; + +struct ManagedSpectrogram { + int synth_id; + float* data; + bool active; +}; + +// Simple pool for dynamic spectrograms +static ManagedSpectrogram g_spec_pool[MAX_SPECTROGRAMS]; + +void tracker_init() { + g_last_trigger_idx = 0; + for (int i = 0; i < MAX_SPECTROGRAMS; ++i) { + g_spec_pool[i].synth_id = -1; + g_spec_pool[i].data = nullptr; + g_spec_pool[i].active = false; + } +} + +static int get_free_pool_slot() { + for (int i = 0; i < MAX_SPECTROGRAMS; ++i) { + if (!g_spec_pool[i].active) + return i; + } + // If no free slot, find one where the synth voice is inactive + // (In a real implementation, we'd check if any voice is still using this) + // For now, just wrap around or return -1 + return -1; +} + +void tracker_update(float time_sec) { + while (g_last_trigger_idx < g_tracker_score.num_triggers) { + const TrackerPatternTrigger& trigger = + g_tracker_score.triggers[g_last_trigger_idx]; + + if (trigger.time_sec > time_sec) + break; + + // Trigger pattern! + const TrackerPattern& pattern = g_tracker_patterns[trigger.pattern_id]; + + // Generate spectrogram for the pattern + int dest_num_frames = 0; + std::vector<float> pattern_data; + + float beat_to_sec = 60.0f / g_tracker_score.bpm; + + for (uint32_t i = 0; i < pattern.num_events; ++i) { + const TrackerEvent& event = pattern.events[i]; + const NoteParams& params = g_tracker_samples[event.sample_id]; + + int note_frames = 0; + std::vector<float> note_data = + generate_note_spectrogram(params, ¬e_frames); + + int frame_offset = (int)(event.beat * beat_to_sec * 32000.0f / DCT_SIZE); + paste_spectrogram(pattern_data, &dest_num_frames, note_data, note_frames, + frame_offset); + } + + // Register with synth + int slot = get_free_pool_slot(); + if (slot != -1) { + // Clean up old if needed + if (g_spec_pool[slot].synth_id != -1) { + synth_unregister_spectrogram(g_spec_pool[slot].synth_id); + delete[] g_spec_pool[slot].data; + } + + g_spec_pool[slot].data = new float[pattern_data.size()]; + memcpy(g_spec_pool[slot].data, pattern_data.data(), + pattern_data.size() * sizeof(float)); + + Spectrogram spec; + spec.spectral_data_a = g_spec_pool[slot].data; + spec.spectral_data_b = g_spec_pool[slot].data; + spec.num_frames = dest_num_frames; + + g_spec_pool[slot].synth_id = synth_register_spectrogram(&spec); + g_spec_pool[slot].active = true; + + synth_trigger_voice(g_spec_pool[slot].synth_id, 1.0f, 0.0f); + } + + g_last_trigger_idx++; + } +} diff --git a/src/audio/tracker.h b/src/audio/tracker.h new file mode 100644 index 0000000..d97b483 --- /dev/null +++ b/src/audio/tracker.h @@ -0,0 +1,42 @@ +// This file is part of the 64k demo project. +// It defines the tracker system for music composition. + +#pragma once + +#include "audio/gen.h" +#include <cstdint> + +struct TrackerEvent { + float beat; + uint16_t sample_id; + float volume; + float pan; +}; + +struct TrackerPattern { + const TrackerEvent* events; + uint32_t num_events; + float num_beats; +}; + +struct TrackerPatternTrigger { + float time_sec; + uint16_t pattern_id; + // Modifiers could be added here +}; + +struct TrackerScore { + const TrackerPatternTrigger* triggers; + uint32_t num_triggers; + float bpm; +}; + +// Global music data generated by tracker_compiler +extern const NoteParams g_tracker_samples[]; +extern const uint32_t g_tracker_samples_count; +extern const TrackerPattern g_tracker_patterns[]; +extern const uint32_t g_tracker_patterns_count; +extern const TrackerScore g_tracker_score; + +void tracker_init(); +void tracker_update(float time_sec); diff --git a/tools/tracker_compiler.cc b/tools/tracker_compiler.cc new file mode 100644 index 0000000..491aebb --- /dev/null +++ b/tools/tracker_compiler.cc @@ -0,0 +1,159 @@ +#include <cstdio> +#include <fstream> +#include <iostream> +#include <string> +#include <vector> +#include <sstream> +#include <map> + +struct Sample { + std::string name; + float freq, dur, amp, attack, harmonic_decay; + int harmonics; +}; + +struct Event { + float beat; + std::string sample_name; + float volume, pan; +}; + +struct Pattern { + std::string name; + std::vector<Event> events; +}; + +struct Trigger { + float time; + std::string pattern_name; +}; + +int main(int argc, char** argv) { + if (argc < 3) { + std::cerr << "Usage: " << argv[0] << " <input.track> <output.cc>" << std::endl; + return 1; + } + + std::ifstream in(argv[1]); + if (!in.is_open()) { + std::cerr << "Could not open input file: " << argv[1] << std::endl; + return 1; + } + + float bpm = 120.0f; + std::vector<Sample> samples; + std::map<std::string, int> sample_map; + std::vector<Pattern> patterns; + std::map<std::string, int> pattern_map; + std::vector<Trigger> score; + + std::string line; + std::string current_section = ""; + + while (std::getline(in, line)) { + if (line.empty() || line[0] == '#') continue; + + std::stringstream ss(line); + std::string cmd; + ss >> cmd; + + if (cmd == "BPM") { + ss >> bpm; + } else if (cmd == "SAMPLE") { + Sample s; + std::string name; + ss >> name; + if (name.back() == ',') name.pop_back(); + s.name = name; + + // Very simple parsing: freq, dur, amp, attack, harmonics, harmonic_decay + char comma; + ss >> s.freq >> comma >> s.dur >> comma >> s.amp >> comma >> s.attack >> comma >> s.harmonics >> comma >> s.harmonic_decay; + + sample_map[s.name] = samples.size(); + samples.push_back(s); + } else if (cmd == "PATTERN") { + Pattern p; + ss >> p.name; + current_section = "PATTERN:" + p.name; + patterns.push_back(p); + pattern_map[p.name] = patterns.size() - 1; + } else if (cmd == "SCORE") { + current_section = "SCORE"; + } else { + if (current_section.rfind("PATTERN:", 0) == 0) { + // Parse event: beat, sample, vol, pan + Event e; + float beat; + std::stringstream ss2(line); + ss2 >> beat; + char comma; + ss2 >> comma; + std::string sname; + ss2 >> sname; + if (sname.back() == ',') sname.pop_back(); + e.beat = beat; + e.sample_name = sname; + ss2 >> e.volume >> comma >> e.pan; + patterns.back().events.push_back(e); + } else if (current_section == "SCORE") { + Trigger t; + float time; + std::stringstream ss2(line); + ss2 >> time; + char comma; + ss2 >> comma; + std::string pname; + ss2 >> pname; + t.time = time; + t.pattern_name = pname; + score.push_back(t); + } + } + } + + std::ofstream out(argv[2]); + out << "// Generated by tracker_compiler. Do not edit.\n\n"; + out << "#include \"audio/tracker.h\"\n\n"; + + out << "const NoteParams g_tracker_samples[] = {\n"; + for (const auto& s : samples) { + out << " { " << s.freq << "f, " << s.dur << "f, " << s.amp << "f, " << s.attack << "f, 0.0f, 0.0f, 0.0f, " + << s.harmonics << ", " << s.harmonic_decay << "f, 0.0f, 0.0f }, // " << s.name << "\n"; + } + out << "}; +"; + out << "const uint32_t g_tracker_samples_count = " << samples.size() << ";\n\n"; + + for (const auto& p : patterns) { + out << "static const TrackerEvent PATTERN_EVENTS_" << p.name << "[] = {\n"; + for (const auto& e : p.events) { + out << " { " << e.beat << "f, " << sample_map[e.sample_name] << ", " << e.volume << "f, " << e.pan << "f },\n"; + } + out << "}; +"; + } + out << "\n"; + + out << "const TrackerPattern g_tracker_patterns[] = {\n"; + for (const auto& p : patterns) { + out << " { PATTERN_EVENTS_" << p.name << ", " << p.events.size() << ", 4.0f }, // " << p.name << "\n"; + } + out << "}; +"; + out << "const uint32_t g_tracker_patterns_count = " << patterns.size() << ";\n\n"; + + out << "static const TrackerPatternTrigger SCORE_TRIGGERS[] = {\n"; + for (const auto& t : score) { + out << " { " << t.time << "f, " << pattern_map[t.pattern_name] << " },\n"; + } + out << "}; +\n"; + + out << "const TrackerScore g_tracker_score = {\n"; + out << " SCORE_TRIGGERS, " << score.size() << ", " << bpm << "f\n"; + out << "}; +"; + + return 0; +} |
