summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/tracker_compiler.cc235
1 files changed, 229 insertions, 6 deletions
diff --git a/tools/tracker_compiler.cc b/tools/tracker_compiler.cc
index 43b4185..d12005d 100644
--- a/tools/tracker_compiler.cc
+++ b/tools/tracker_compiler.cc
@@ -1,5 +1,7 @@
+#include <algorithm>
#include <cmath>
#include <cstdio>
+#include <cstring>
#include <fstream>
#include <iostream>
#include <map>
@@ -175,6 +177,145 @@ ResourceAnalysis analyze_resources(const std::vector<Sample>& samples,
return result;
}
+// Validate and report issues with tracker data
+int validate_tracker_data(const std::vector<Sample>& samples,
+ const std::vector<Pattern>& patterns,
+ const std::vector<Trigger>& score, float bpm) {
+ int warnings = 0;
+ int errors = 0;
+
+ // Validate BPM
+ if (bpm <= 0.0f || bpm > 300.0f) {
+ fprintf(stderr, "WARNING: Unusual BPM value: %.1f\n", bpm);
+ warnings++;
+ }
+
+ // Validate samples
+ for (const auto& s : samples) {
+ if (s.type == GENERATED) {
+ if (s.freq <= 0.0f || s.freq > 20000.0f) {
+ fprintf(stderr, "ERROR: Sample '%s' invalid frequency: %.1f Hz\n",
+ s.name.c_str(), s.freq);
+ errors++;
+ }
+ if (s.dur <= 0.0f || s.dur > 10.0f) {
+ fprintf(stderr, "WARNING: Sample '%s' unusual duration: %.2f s\n",
+ s.name.c_str(), s.dur);
+ warnings++;
+ }
+ }
+ }
+
+ // Validate patterns
+ for (const auto& p : patterns) {
+ if (p.unit_length <= 0.0f) {
+ fprintf(stderr, "ERROR: Pattern '%s' invalid length: %.2f\n",
+ p.name.c_str(), p.unit_length);
+ errors++;
+ }
+
+ // Check event ordering
+ for (size_t i = 1; i < p.events.size(); ++i) {
+ if (p.events[i].unit_time < p.events[i - 1].unit_time) {
+ fprintf(stderr,
+ "WARNING: Pattern '%s' has unsorted events: [%zu]=%.3f < "
+ "[%zu]=%.3f\n",
+ p.name.c_str(), i, p.events[i].unit_time, i - 1,
+ p.events[i - 1].unit_time);
+ warnings++;
+ }
+ }
+
+ // Validate event ranges
+ for (const auto& e : p.events) {
+ if (e.unit_time < 0.0f || e.unit_time > p.unit_length) {
+ fprintf(stderr,
+ "ERROR: Pattern '%s' event time %.3f outside pattern length "
+ "%.2f\n",
+ p.name.c_str(), e.unit_time, p.unit_length);
+ errors++;
+ }
+ if (e.volume < 0.0f || e.volume > 2.0f) {
+ fprintf(stderr,
+ "WARNING: Pattern '%s' unusual volume: %.2f (expected 0.0-2.0)\n",
+ p.name.c_str(), e.volume);
+ warnings++;
+ }
+ if (e.pan < -1.0f || e.pan > 1.0f) {
+ fprintf(stderr,
+ "ERROR: Pattern '%s' invalid pan: %.2f (must be -1.0 to 1.0)\n",
+ p.name.c_str(), e.pan);
+ errors++;
+ }
+ }
+ }
+
+ return errors;
+}
+
+// Write sanitized .track file
+void write_sanitized_track(const char* output_path, float bpm,
+ const std::vector<Sample>& samples,
+ std::vector<Pattern>& patterns,
+ const std::vector<Trigger>& score) {
+ FILE* out = fopen(output_path, "w");
+ if (!out) {
+ fprintf(stderr, "Could not open output file: %s\n", output_path);
+ return;
+ }
+
+ fprintf(out, "# Sanitized tracker file\n");
+ fprintf(out, "# Generated by tracker_compiler --sanitize\n\n");
+
+ fprintf(out, "BPM %.1f\n\n", bpm);
+
+ // Write samples
+ if (!samples.empty()) {
+ fprintf(out, "# Samples (%zu total)\n", samples.size());
+ for (const auto& s : samples) {
+ fprintf(out, "SAMPLE %s", s.name.c_str());
+ if (s.type == GENERATED) {
+ fprintf(out, ", %.1f, %.2f, %.1f, %.2f, %d, %.1f", s.freq, s.dur,
+ s.amp, s.attack, s.harmonics, s.harmonic_decay);
+ }
+ fprintf(out, "\n");
+ }
+ fprintf(out, "\n");
+ }
+
+ // Write patterns (sorted events)
+ for (auto& p : patterns) {
+ // Sort events by time
+ std::sort(p.events.begin(), p.events.end(),
+ [](const Event& a, const Event& b) {
+ return a.unit_time < b.unit_time;
+ });
+
+ fprintf(out, "PATTERN %s", p.name.c_str());
+ if (p.unit_length != 1.0f) {
+ fprintf(out, " LENGTH %.2f", p.unit_length);
+ }
+ fprintf(out, "\n");
+
+ for (const auto& e : p.events) {
+ fprintf(out, " %.4f, %s, %.1f, %.1f\n", e.unit_time,
+ e.sample_name.c_str(), e.volume, e.pan);
+ }
+ fprintf(out, "\n");
+ }
+
+ // Write score
+ if (!score.empty()) {
+ fprintf(out, "SCORE\n");
+ for (const auto& t : score) {
+ fprintf(out, " %.1f, %s\n", t.time, t.pattern_name.c_str());
+ }
+ }
+
+ fclose(out);
+ printf("Sanitized track written to: %s\n", output_path);
+}
+
// Write resource analysis to output file
void write_resource_analysis(FILE* out, const ResourceAnalysis& analysis,
int total_samples) {
@@ -206,14 +347,59 @@ void write_resource_analysis(FILE* out, const ResourceAnalysis& analysis,
}
int main(int argc, char** argv) {
- if (argc < 3) {
- fprintf(stderr, "Usage: %s <input.track> <output.cc>\n", argv[0]);
+ // Parse mode flags
+ bool check_mode = false;
+ bool sanitize_mode = false;
+ const char* input_file = nullptr;
+ const char* output_file = nullptr;
+
+ int arg_idx = 1;
+ while (arg_idx < argc) {
+ if (strcmp(argv[arg_idx], "--check") == 0) {
+ check_mode = true;
+ arg_idx++;
+ } else if (strcmp(argv[arg_idx], "--sanitize") == 0) {
+ sanitize_mode = true;
+ arg_idx++;
+ } else if (!input_file) {
+ input_file = argv[arg_idx];
+ arg_idx++;
+ } else if (!output_file) {
+ output_file = argv[arg_idx];
+ arg_idx++;
+ } else {
+ fprintf(stderr, "Unexpected argument: %s\n", argv[arg_idx]);
+ return 1;
+ }
+ }
+
+ // Validate arguments
+ if (!input_file) {
+ fprintf(stderr, "Usage:\n");
+ fprintf(stderr, " %s <input.track> <output.cc> # Compile\n",
+ argv[0]);
+ fprintf(stderr, " %s --check <input.track> # Validate only\n",
+ argv[0]);
+ fprintf(stderr,
+ " %s --sanitize <input.track> <output.track> # Sanitize\n",
+ argv[0]);
return 1;
}
- std::ifstream in(argv[1]);
+ if (!check_mode && !output_file) {
+ fprintf(stderr, "Error: Output file required for compile/sanitize mode\n");
+ fprintf(stderr, "Use --check for validation only\n");
+ return 1;
+ }
+
+ if (check_mode && sanitize_mode) {
+ fprintf(stderr, "Error: Cannot use --check and --sanitize together\n");
+ return 1;
+ }
+
+ std::ifstream in(input_file);
if (!in.is_open()) {
- fprintf(stderr, "Could not open input file: %s\n", argv[1]);
+ fprintf(stderr, "Could not open input file: %s\n", input_file);
return 1;
}
@@ -235,6 +421,10 @@ int main(int argc, char** argv) {
std::string cmd;
ss >> cmd;
+ // Skip comment lines (including indented comments)
+ if (cmd.empty() || cmd[0] == '#')
+ continue;
+
if (cmd == "BPM") {
ss >> bpm;
} else if (cmd == "SAMPLE") {
@@ -330,9 +520,34 @@ int main(int argc, char** argv) {
}
}
- FILE* out_file = fopen(argv[2], "w");
+ // Validate tracker data
+ int errors = validate_tracker_data(samples, patterns, score, bpm);
+ if (errors > 0) {
+ fprintf(stderr, "\nValidation failed with %d errors\n", errors);
+ if (check_mode) {
+ return 1;
+ }
+ // Continue compilation with warnings
+ }
+
+ // Handle different modes
+ if (check_mode) {
+ printf("Validation passed for: %s\n", input_file);
+ printf(" Patterns: %zu\n", patterns.size());
+ printf(" Score triggers: %zu\n", score.size());
+ printf(" Samples: %zu\n", samples.size());
+ return 0;
+ }
+
+ if (sanitize_mode) {
+ write_sanitized_track(output_file, bpm, samples, patterns, score);
+ return 0;
+ }
+
+ // Normal compilation mode
+ FILE* out_file = fopen(output_file, "w");
if (!out_file) {
- fprintf(stderr, "Could not open output file: %s\n", argv[2]);
+ fprintf(stderr, "Could not open output file: %s\n", output_file);
return 1;
}
fprintf(out_file, "// Generated by tracker_compiler. Do not edit.\n\n");
@@ -366,6 +581,14 @@ int main(int argc, char** argv) {
}
fprintf(out_file, "};\n\n");
+ // Sort pattern events by time (required by runtime early-exit optimization)
+ for (auto& p : patterns) {
+ std::sort(p.events.begin(), p.events.end(),
+ [](const Event& a, const Event& b) {
+ return a.unit_time < b.unit_time;
+ });
+ }
+
for (const auto& p : patterns) {
fprintf(out_file, "static const TrackerEvent PATTERN_EVENTS_%s[] = {\n",
p.name.c_str());