summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-05 00:44:11 +0100
committerskal <pascal.massimino@gmail.com>2026-02-05 00:44:11 +0100
commitabd1d77d4718a680f17d8ca25d31294d72068acb (patch)
treed52f7a29a731dc85ec713a21ccfea2290388190b /tools
parente2543192e90a43a26444a27ccc3b49276a944b2c (diff)
feat: Enhance Gantt charts with sequence names, adaptive ticks, and sorting
Improvements to seq_compiler Gantt chart visualization: - Add optional sequence name support: SEQUENCE <start> <pri> ["name"] [end] Names displayed in both ASCII and HTML Gantt charts for better readability - Implement adaptive tick intervals based on demo duration: * ≤5s: 1s intervals * ≤40s: 2s intervals (fixes 32.5s demo from 5s to 2s) * ≤100s: 5s intervals * >100s: 10s+ intervals - Sort sequences by start time in Gantt output for chronological visualization - Add horizontal visual separators between sequences in both ASCII and HTML - Update documentation (SEQUENCE.md) and quick reference (demo.seq) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'tools')
-rw-r--r--tools/seq_compiler.cc137
1 files changed, 113 insertions, 24 deletions
diff --git a/tools/seq_compiler.cc b/tools/seq_compiler.cc
index b61e895..3931e32 100644
--- a/tools/seq_compiler.cc
+++ b/tools/seq_compiler.cc
@@ -21,6 +21,7 @@ struct SequenceEntry {
std::string start_time;
std::string priority;
std::string end_time; // Optional: -1.0f means "no explicit end"
+ std::string name; // Optional: human-readable name for Gantt charts
std::vector<EffectEntry> effects;
};
@@ -32,6 +33,15 @@ std::string trim(const std::string& str) {
return str.substr(first, (last - first + 1));
}
+// Calculate adaptive tick interval based on timeline duration
+int calculate_tick_interval(float max_time) {
+ if (max_time <= 5) return 1;
+ if (max_time <= 40) return 2;
+ if (max_time <= 100) return 5;
+ if (max_time <= 200) return 10;
+ return 20;
+}
+
// Generate ASCII Gantt chart for timeline visualization
void generate_gantt_chart(const std::string& output_file,
const std::vector<SequenceEntry>& sequences,
@@ -67,25 +77,41 @@ void generate_gantt_chart(const std::string& output_file,
}
out << "\n\n";
- // Time axis header
+ // Time axis header with adaptive tick interval
+ const int tick_interval = calculate_tick_interval(max_time);
out << "Time (s): ";
- for (int i = 0; i <= (int)max_time; i += 5) {
+ for (int i = 0; i <= (int)max_time; i += tick_interval) {
out << i;
int spacing = (i < 10) ? 4 : (i < 100) ? 3 : 2;
- if (i + 5 <= max_time) {
+ if (i + tick_interval <= 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 << "-";
+ // Check if this column aligns with any tick mark
+ bool is_tick = false;
+ for (int t = 0; t <= (int)max_time; t += tick_interval) {
+ if (std::abs(i - (int)(t * time_scale)) < 1) {
+ is_tick = true;
+ break;
+ }
+ }
+ out << (is_tick ? "|" : "-");
}
out << "\n\n";
+ // Sort sequences by start time for better readability
+ std::vector<SequenceEntry> sorted_sequences = sequences;
+ std::sort(sorted_sequences.begin(), sorted_sequences.end(),
+ [](const SequenceEntry& a, const SequenceEntry& b) {
+ return std::stof(a.start_time) < std::stof(b.start_time);
+ });
+
// Draw sequences and effects
- for (const auto& seq : sequences) {
+ for (size_t seq_idx = 0; seq_idx < sorted_sequences.size(); ++seq_idx) {
+ const auto& seq = sorted_sequences[seq_idx];
float seq_start = std::stof(seq.start_time);
float seq_end = seq_start; // Start at sequence start
@@ -100,7 +126,11 @@ void generate_gantt_chart(const std::string& output_file,
}
// Draw sequence bar
- out << "SEQ@" << seq_start << "s [pri=" << seq.priority << "]";
+ out << "SEQ@" << seq_start << "s";
+ if (!seq.name.empty()) {
+ out << " \"" << seq.name << "\"";
+ }
+ out << " [pri=" << seq.priority << "]";
if (seq.end_time != "-1.0") {
out << " [END=" << seq_end << "s]";
}
@@ -146,7 +176,17 @@ void generate_gantt_chart(const std::string& output_file,
}
out << " (" << eff_start << "-" << eff_end << "s)\n";
}
- out << "\n";
+
+ // Add separator between sequences
+ if (seq_idx < sorted_sequences.size() - 1) {
+ out << " ";
+ for (int i = 0; i < chart_width; ++i) {
+ out << "─";
+ }
+ out << "\n\n";
+ } else {
+ out << "\n";
+ }
}
out << "==============================================================================\n";
@@ -227,13 +267,14 @@ void generate_gantt_html(const std::string& output_file,
out << "<svg width=\"" << svg_width << "\" height=\"" << svg_height << "\" xmlns=\"http://www.w3.org/2000/svg\">\n";
- // Draw time axis
+ // Draw time axis with adaptive tick interval
+ const int tick_interval = calculate_tick_interval(max_time);
out << " <!-- Time axis -->\n";
out << " <line x1=\"" << margin_left << "\" y1=\"" << margin_top - 10
<< "\" x2=\"" << (svg_width - 50) << "\" y2=\"" << margin_top - 10
<< "\" class=\"axis-line\"/>\n";
- for (int t = 0; t <= (int)max_time; t += 5) {
+ for (int t = 0; t <= (int)max_time; t += tick_interval) {
int x = margin_left + (int)(t * time_scale);
out << " <line x1=\"" << x << "\" y1=\"" << margin_top - 15
<< "\" x2=\"" << x << "\" y2=\"" << margin_top - 5
@@ -246,9 +287,17 @@ void generate_gantt_html(const std::string& output_file,
<< "\" class=\"time-marker\"/>\n";
}
+ // Sort sequences by start time for better readability
+ std::vector<SequenceEntry> sorted_sequences = sequences;
+ std::sort(sorted_sequences.begin(), sorted_sequences.end(),
+ [](const SequenceEntry& a, const SequenceEntry& b) {
+ return std::stof(a.start_time) < std::stof(b.start_time);
+ });
+
// Draw sequences and effects
int y_offset = margin_top;
- for (const auto& seq : sequences) {
+ for (size_t seq_idx = 0; seq_idx < sorted_sequences.size(); ++seq_idx) {
+ const auto& seq = sorted_sequences[seq_idx];
float seq_start = std::stof(seq.start_time);
float seq_end = seq_start; // Start at sequence start
@@ -268,13 +317,21 @@ void generate_gantt_html(const std::string& output_file,
out << " <rect x=\"" << x1 << "\" y=\"" << y_offset
<< "\" width=\"" << (x2 - x1) << "\" height=\"" << row_height
<< "\" class=\"sequence-bar\">\n";
- out << " <title>SEQ@" << seq_start << "s [pri=" << seq.priority << "] ("
+ out << " <title>SEQ@" << seq_start << "s";
+ if (!seq.name.empty()) {
+ out << " \"" << seq.name << "\"";
+ }
+ out << " [pri=" << seq.priority << "] ("
<< seq_start << "-" << seq_end << "s)</title>\n";
out << " </rect>\n";
// Draw sequence label
out << " <text x=\"10\" y=\"" << (y_offset + row_height / 2 + 4)
- << "\" class=\"label\">SEQ@" << seq_start << "s [pri=" << seq.priority << "]</text>\n";
+ << "\" class=\"label\">SEQ@" << seq_start << "s";
+ if (!seq.name.empty()) {
+ out << " \"" << seq.name << "\"";
+ }
+ out << " [pri=" << seq.priority << "]</text>\n";
y_offset += row_height;
@@ -306,6 +363,15 @@ void generate_gantt_html(const std::string& output_file,
y_offset += row_height;
}
+
+ // Add separator between sequences
+ if (seq_idx < sorted_sequences.size() - 1) {
+ out << " <!-- Separator -->\n";
+ out << " <line x1=\"" << margin_left << "\" y1=\"" << (y_offset + 5)
+ << "\" x2=\"" << (svg_width - 50) << "\" y2=\"" << (y_offset + 5)
+ << "\" style=\"stroke:#444444; stroke-width:1; stroke-dasharray:4,2;\"/>\n";
+ y_offset += 10; // Extra spacing after separator
+ }
}
// Legend
@@ -441,25 +507,48 @@ int main(int argc, char* argv[]) {
// Convert beat notation to time
std::string start_time = convert_to_time(start, bpm);
- // Check for optional [end_time]
+ // Check for optional "name" and [end_time]
std::string end_time_str = "-1.0"; // Default: no explicit end
- std::string optional_param;
- if (ss >> optional_param) {
- // Check if it's wrapped in brackets [time]
- if (optional_param.size() >= 3 &&
- optional_param.front() == '[' &&
- optional_param.back() == ']') {
- // Extract time from [time]
- std::string time_value = optional_param.substr(1, optional_param.size() - 2);
+ std::string seq_name = ""; // Default: no name
+
+ // Read remaining tokens
+ std::string rest_of_line;
+ std::getline(ss, rest_of_line);
+ std::stringstream rest_ss(rest_of_line);
+ std::string token;
+
+ while (rest_ss >> token) {
+ if (token.front() == '"') {
+ // Name in quotes: read until closing quote
+ std::string name_part = token.substr(1); // Remove opening quote
+ if (name_part.back() == '"') {
+ // Complete name in single token
+ name_part.pop_back(); // Remove closing quote
+ seq_name = name_part;
+ } else {
+ // Multi-word name: read until closing quote
+ seq_name = name_part;
+ while (rest_ss >> token) {
+ if (token.back() == '"') {
+ token.pop_back(); // Remove closing quote
+ seq_name += " " + token;
+ break;
+ }
+ seq_name += " " + token;
+ }
+ }
+ } else if (token.front() == '[' && token.back() == ']') {
+ // End time in brackets [time]
+ std::string time_value = token.substr(1, token.size() - 2);
end_time_str = convert_to_time(time_value, bpm);
} else {
std::cerr << "Error line " << line_num
- << ": Optional sequence end time must be in brackets [time]\n";
+ << ": Unexpected token '" << token << "'. Expected \"name\" or [end_time]\n";
return 1;
}
}
- sequences.push_back({start_time, priority, end_time_str, {}});
+ sequences.push_back({start_time, priority, end_time_str, seq_name, {}});
current_seq = &sequences.back();
} else if (command == "EFFECT") {
if (!current_seq) {