#include #include #include #include #include #include #include // Enum to differentiate between sample types enum SampleType { GENERATED, ASSET }; struct Sample { std::string name; SampleType type = GENERATED; // Default to GENERATED std::string asset_id_name; // Store AssetId name for asset samples // Parameters for generated samples 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 events; }; struct Trigger { float time; std::string pattern_name; }; int main(int argc, char** argv) { if (argc < 3) { fprintf(stderr, "Usage: %s \n", argv[0]); return 1; } std::ifstream in(argv[1]); if (!in.is_open()) { fprintf(stderr, "Could not open input file: %s\n", argv[1]); return 1; } float bpm = 120.0f; std::vector samples; std::map sample_map; std::vector patterns; std::map pattern_map; std::vector 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; if (name.rfind("ASSET_", 0) == 0) { s.type = ASSET; s.asset_id_name = name; // Parameters for asset samples are ignored, so we don't parse them here. // However, we must consume the rest of the line to avoid issues if a comma is present. std::string dummy; while (ss >> dummy) {} // Consume rest of line } else { s.type = GENERATED; // 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); } } } FILE* out_file = fopen(argv[2], "w"); if (!out_file) { fprintf(stderr, "Could not open output file: %s\n", argv[2]); return 1; } fprintf(out_file, "// Generated by tracker_compiler. Do not edit.\n\n"); fprintf(out_file, "#include \"audio/tracker.h\"\n\n"); // Need to include assets.h for AssetId enum fprintf(out_file, "#include \"generated/assets.h\"\n\n"); fprintf(out_file, "const NoteParams g_tracker_samples[] = {\n"); for (const auto& s : samples) { if (s.type == GENERATED) { fprintf(out_file, " { %.1ff, %.2ff, %.1ff, %.2ff, 0.0f, 0.0f, 0.0f, %d, %.1ff, " "0.0f, 0.0f }, // %s\n", s.freq, s.dur, s.amp, s.attack, s.harmonics, s.harmonic_decay, s.name.c_str()); } else { fprintf(out_file, " { 0 }, // %s (ASSET)\n", s.name.c_str()); } } fprintf(out_file, "};\n"); fprintf(out_file, "const uint32_t g_tracker_samples_count = %zu;\n\n", samples.size()); fprintf(out_file, "const AssetId g_tracker_sample_assets[] = {\n"); for (const auto& s : samples) { if (s.type == ASSET) { fprintf(out_file, " AssetId::%s,\n", s.asset_id_name.c_str()); } else { fprintf(out_file, " AssetId::ASSET_LAST_ID,\n"); } } fprintf(out_file, "};\n\n"); for (const auto& p : patterns) { fprintf(out_file, "static const TrackerEvent PATTERN_EVENTS_%s[] = {\n", p.name.c_str()); for (const auto& e : p.events) { // When referencing a sample, we need to get its index or synth_id. // If it's an asset, the name starts with ASSET_. // For now, assume sample_map is used for both generated and asset samples. // This will need refinement if asset samples are not in sample_map directly. fprintf(out_file, " { %.1ff, %d, %.1ff, %.1ff },\n", e.beat, sample_map[e.sample_name], e.volume, e.pan); } fprintf(out_file, "};\n"); } fprintf(out_file, "\n"); fprintf(out_file, "const TrackerPattern g_tracker_patterns[] = {\n"); for (const auto& p : patterns) { fprintf(out_file, " { PATTERN_EVENTS_%s, %zu, 4.0f }, // %s\n", p.name.c_str(), p.events.size(), p.name.c_str()); } fprintf(out_file, "};\n"); fprintf(out_file, "const uint32_t g_tracker_patterns_count = %zu;\n\n", patterns.size()); fprintf(out_file, "static const TrackerPatternTrigger SCORE_TRIGGERS[] = {\n"); for (const auto& t : score) { fprintf(out_file, " { %.1ff, %d },\n", t.time, pattern_map[t.pattern_name]); } fprintf(out_file, "};\n\n"); fprintf(out_file, "const TrackerScore g_tracker_score = {\n"); fprintf(out_file, " SCORE_TRIGGERS, %zu, %.1ff\n", score.size(), bpm); fprintf(out_file, "};\n"); fclose(out_file); return 0; }