summaryrefslogtreecommitdiff
path: root/tools/tracker_compiler.cc
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-02 22:05:50 +0100
committerskal <pascal.massimino@gmail.com>2026-02-02 22:05:50 +0100
commit5d2ec00c0179d1ee1b07883511d34dac272c680f (patch)
treebdb50394baa8c5df1ac64e39a9560b0d5d8e0fcc /tools/tracker_compiler.cc
parent4fc02a8d2acf1eafce36c1348261890d54b8b5b5 (diff)
feat: Integrate tracker system and update project context documentation
- Implemented the basic tracker system with runtime support (tracker.h, tracker.cc). - Added a sample music track file (assets/music.track). - Created a tracker compiler tool (tools/tracker_compiler.cc) to generate music data. - Updated CMakeLists.txt to build the tracker compiler and integrate generated data. - Updated GEMINI.md to reflect new file locations and project context.
Diffstat (limited to 'tools/tracker_compiler.cc')
-rw-r--r--tools/tracker_compiler.cc159
1 files changed, 159 insertions, 0 deletions
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;
+}