From 850932428ceea8422c9a0eef10f5e4df3be22c5d Mon Sep 17 00:00:00 2001 From: skal Date: Wed, 4 Feb 2026 23:56:56 +0100 Subject: feat: Add Gantt chart visualization to seq_compiler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements ASCII Gantt chart generation for timeline debugging and visualization. ## New Feature - Added --gantt= flag to seq_compiler - Generates visual timeline showing sequences and effects on time axis - Displays sequence priority, effect priority, and time ranges - Shows explicit sequence end times with [END=...] markers - Detects and warns about invalid time ranges (end < start) ## Usage ```bash ./build/seq_compiler assets/demo.seq src/generated/timeline.cc --gantt=timeline.txt ``` ## Chart Format - Time axis in seconds with 5-second markers - Sequences shown as solid bars (█) - Effects shown as shaded bars (▓) with sequence background (·) - Labels include start/end times and priorities - Legend and documentation at chart end ## Example Output ``` Time (s): 0 5 10 15 20 25 30 |----|----|----|----|----|----|----| SEQ@0s [pri=0] ████████████████████████████████ (0-30s) FlashEffect [pri=4] ▓▓·························· (0-1s) HeptagonEffect [pri=0] ▓▓▓▓▓▓▓▓▓▓▓▓················ (0-10s) ``` ## Benefits - Visualize sequence overlap and layering - Identify timing conflicts and gaps - Verify effect priorities render in correct order - Debug invalid time ranges - Plan demo choreography visually ## Files Changed - tools/seq_compiler.cc: Added generate_gantt_chart() function - assets/demo.seq: Added usage documentation - .gitignore: Exclude generated demo_timeline.txt This debugging tool significantly improves timeline development workflow by providing visual feedback on sequence and effect timing. --- tools/seq_compiler.cc | 141 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 139 insertions(+), 2 deletions(-) (limited to 'tools/seq_compiler.cc') diff --git a/tools/seq_compiler.cc b/tools/seq_compiler.cc index a4fd00c..548f467 100644 --- a/tools/seq_compiler.cc +++ b/tools/seq_compiler.cc @@ -32,6 +32,131 @@ std::string trim(const std::string& str) { return str.substr(first, (last - first + 1)); } +// Generate ASCII Gantt chart for timeline visualization +void generate_gantt_chart(const std::string& output_file, + const std::vector& sequences, + float bpm, const std::string& demo_end_time) { + std::ofstream out(output_file); + if (!out.is_open()) { + std::cerr << "Warning: Could not open Gantt chart output file: " << output_file << "\n"; + return; + } + + // Find max time for the chart + float max_time = demo_end_time.empty() ? 0.0f : std::stof(demo_end_time); + for (const auto& seq : sequences) { + float seq_start = std::stof(seq.start_time); + for (const auto& eff : seq.effects) { + float eff_end = seq_start + std::stof(eff.end); + max_time = std::max(max_time, eff_end); + } + if (seq.end_time != "-1.0") { + max_time = std::max(max_time, seq_start + std::stof(seq.end_time)); + } + } + + // Chart configuration + const int chart_width = 100; + const float time_scale = chart_width / max_time; + + out << "Demo Timeline Gantt Chart\n"; + out << "==============================================================================\n"; + out << "BPM: " << bpm << ", Duration: " << max_time << "s"; + if (!demo_end_time.empty()) { + out << " (explicit end)"; + } + out << "\n\n"; + + // Time axis header + out << "Time (s): "; + for (int i = 0; i <= (int)max_time; i += 5) { + out << i; + int spacing = (i < 10) ? 4 : (i < 100) ? 3 : 2; + if (i + 5 <= max_time) { + for (int j = 0; j < spacing; ++j) out << " "; + } + } + out << "\n"; + out << " "; + for (int i = 0; i < chart_width; ++i) { + if (i % 5 == 0) out << "|"; + else out << "-"; + } + out << "\n\n"; + + // Draw sequences and effects + for (const auto& seq : sequences) { + float seq_start = std::stof(seq.start_time); + float seq_end = max_time; // Default: runs until end + + // Check if sequence has explicit end time + if (seq.end_time != "-1.0") { + seq_end = seq_start + std::stof(seq.end_time); + } else { + // Calculate implicit end from latest effect + for (const auto& eff : seq.effects) { + seq_end = std::max(seq_end, seq_start + std::stof(eff.end)); + } + } + + // Draw sequence bar + out << "SEQ@" << seq_start << "s [pri=" << seq.priority << "]"; + if (seq.end_time != "-1.0") { + out << " [END=" << seq_end << "s]"; + } + out << "\n"; + + int start_col = (int)(seq_start * time_scale); + int end_col = (int)(seq_end * time_scale); + out << " "; + for (int i = 0; i < chart_width; ++i) { + if (i >= start_col && i < end_col) out << "█"; + else out << " "; + } + out << " (" << seq_start << "-" << seq_end << "s)\n"; + + // Draw effects within sequence + for (const auto& eff : seq.effects) { + float eff_start = seq_start + std::stof(eff.start); + float eff_end = seq_start + std::stof(eff.end); + + // Truncate if sequence has explicit end time + if (seq.end_time != "-1.0") { + eff_end = std::min(eff_end, seq_end); + } + + out << " " << eff.class_name << " [pri=" << eff.priority << "]"; + if (eff_end < eff_start) { + out << " *** INVALID TIME RANGE ***"; + } + out << "\n"; + out << " "; + + int eff_start_col = (int)(eff_start * time_scale); + int eff_end_col = (int)(eff_end * time_scale); + + for (int i = 0; i < chart_width; ++i) { + if (i >= eff_start_col && i < eff_end_col) { + out << "▓"; + } else if (i >= start_col && i < end_col) { + out << "·"; // Show sequence background + } else { + out << " "; + } + } + out << " (" << eff_start << "-" << eff_end << "s)\n"; + } + out << "\n"; + } + + out << "==============================================================================\n"; + out << "Legend: █ Sequence ▓ Effect · Sequence background\n"; + out << "Priority: Higher numbers render later (on top)\n"; + + out.close(); + std::cout << "Gantt chart written to: " << output_file << "\n"; +} + // Convert beat notation to time in seconds // Supports: "64b" or "64" (beats), "32.0s" or "32.0" with decimal point (seconds) std::string convert_to_time(const std::string& value, float bpm) { @@ -63,13 +188,20 @@ std::string convert_to_time(const std::string& value, float bpm) { } int main(int argc, char* argv[]) { - if (argc != 3) { - std::cerr << "Usage: " << argv[0] << " \n"; + if (argc < 3 || argc > 4) { + std::cerr << "Usage: " << argv[0] << " [--gantt ]\n"; std::cerr << "Example: " << argv[0] << " assets/demo.seq src/generated/timeline.cc\n"; + std::cerr << " " << argv[0] + << " assets/demo.seq src/generated/timeline.cc --gantt timeline.txt\n"; return 1; } + std::string gantt_output = ""; + if (argc == 4 && std::string(argv[3]).rfind("--gantt=", 0) == 0) { + gantt_output = std::string(argv[3]).substr(8); // Extract filename after --gantt= + } + std::ifstream in_file(argv[1]); if (!in_file.is_open()) { std::cerr << "Error: Could not open input file " << argv[1] << "\n"; @@ -248,5 +380,10 @@ int main(int argc, char* argv[]) { std::cout << "Successfully generated timeline with " << sequences.size() << " sequences.\n"; + // Generate Gantt chart if requested + if (!gantt_output.empty()) { + generate_gantt_chart(gantt_output, sequences, bpm, demo_end_time); + } + return 0; } -- cgit v1.2.3