diff options
Diffstat (limited to 'tools/seq_compiler.cc')
| -rw-r--r-- | tools/seq_compiler.cc | 1146 |
1 files changed, 0 insertions, 1146 deletions
diff --git a/tools/seq_compiler.cc b/tools/seq_compiler.cc deleted file mode 100644 index 462bdba..0000000 --- a/tools/seq_compiler.cc +++ /dev/null @@ -1,1146 +0,0 @@ -// This file is part of the 64k demo project. -// It implements the sequence compiler tool. -// Converts a text-based timeline description into C++ code. - -#include <algorithm> -#include <cmath> -#include <fstream> -#include <iomanip> -#include <iostream> -#include <map> -#include <numeric> -#include <sstream> -#include <string> -#include <vector> - -struct EffectEntry { - std::string class_name; - std::string start; - std::string end; - std::string priority; - std::string extra_args; - std::vector<std::pair<std::string, std::string>> params; // key=value pairs -}; - -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; -}; - -std::string trim(const std::string& str) { - size_t first = str.find_first_not_of(" \t"); - if (std::string::npos == first) - return ""; // String is all whitespace, return empty string - size_t last = str.find_last_not_of(" \t"); - return str.substr(first, (last - first + 1)); -} - -// Parse key=value parameters from extra_args string -// Example: "color=1.0,0.0,0.0 decay=0.95" -> {{"color", "1.0,0.0,0.0"}, -// {"decay", "0.95"}} -std::vector<std::pair<std::string, std::string>> -parse_parameters(const std::string& args) { - std::vector<std::pair<std::string, std::string>> params; - std::istringstream ss(args); - std::string token; - - while (ss >> token) { - size_t eq_pos = token.find('='); - if (eq_pos != std::string::npos) { - std::string key = token.substr(0, eq_pos); - std::string value = token.substr(eq_pos + 1); - params.push_back({key, value}); - } - } - - return params; -} - -// Check if an effect is a post-process effect -bool is_post_process_effect(const std::string& class_name) { - // List of known post-process effects - static const std::vector<std::string> post_process_effects = { - "FadeEffect", "FlashEffect", "GaussianBlurEffect", - "SolarizeEffect", "VignetteEffect", "ChromaAberrationEffect", - "DistortEffect", "ThemeModulationEffect", "CNNEffect", - "PassthroughEffect", "CircleMaskEffect"}; - return std::find(post_process_effects.begin(), post_process_effects.end(), - class_name) != post_process_effects.end(); -} - -// 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; -} - -// Timeline analysis result: max time and sequences sorted by start time -struct TimelineMetrics { - float max_time; - std::vector<SequenceEntry> sorted_sequences; -}; - -// Calculate sequence end time (explicit or derived from latest effect) -float get_sequence_end(const SequenceEntry& seq) { - float seq_start = std::stof(seq.start_time); - if (seq.end_time != "-1.0") { - return seq_start + std::stof(seq.end_time); - } - float seq_end = seq_start; - for (const auto& eff : seq.effects) { - seq_end = std::max(seq_end, seq_start + std::stof(eff.end)); - } - return seq_end; -} - -// Analyze timeline: find max time and sort sequences by start time -TimelineMetrics analyze_timeline(const std::vector<SequenceEntry>& sequences, - const std::string& demo_end_time) { - 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) { - max_time = std::max(max_time, seq_start + std::stof(eff.end)); - } - if (seq.end_time != "-1.0") { - max_time = std::max(max_time, seq_start + std::stof(seq.end_time)); - } - } - - std::vector<SequenceEntry> sorted = sequences; - std::sort(sorted.begin(), sorted.end(), - [](const SequenceEntry& a, const SequenceEntry& b) { - return std::stof(a.start_time) < std::stof(b.start_time); - }); - - return {max_time, sorted}; -} - -// Analyze effect stacking depth across the timeline -void analyze_effect_depth(const std::vector<SequenceEntry>& sequences, - const std::string& demo_end_time, - float sample_rate = 10.0f) { - TimelineMetrics metrics = analyze_timeline(sequences, demo_end_time); - float max_time = metrics.max_time; - - if (max_time <= 0.0f) { - std::cout << "\n=== Effect Depth Analysis ===\n"; - std::cout << "No effects found in timeline.\n"; - return; - } - - // Build list of all effects with absolute times - struct ActiveEffect { - std::string name; - float start_time; - float end_time; - int priority; - }; - std::vector<ActiveEffect> all_effects; - - for (const auto& seq : sequences) { - float seq_start = std::stof(seq.start_time); - float seq_end = get_sequence_end(seq); - - for (const auto& eff : seq.effects) { - float eff_start = seq_start + std::stof(eff.start); - float eff_end = seq_start + std::stof(eff.end); - - // Clamp effect end to sequence end if specified - if (seq.end_time != "-1.0") { - eff_end = std::min(eff_end, seq_end); - } - - all_effects.push_back( - {eff.class_name, eff_start, eff_end, std::stoi(eff.priority)}); - } - } - - // Sample timeline at regular intervals - const float dt = 1.0f / sample_rate; - int max_depth = 0; - float max_depth_time = 0.0f; - std::vector<int> depth_histogram(21, 0); // Track depths 0-20+ - - struct PeakInfo { - float time; - int depth; - std::vector<std::string> effects; - }; - std::vector<PeakInfo> peaks; - - for (float t = 0.0f; t <= max_time; t += dt) { - int depth = 0; - std::vector<std::string> active_effects; - - for (const auto& eff : all_effects) { - if (t >= eff.start_time && t < eff.end_time) { - depth++; - active_effects.push_back(eff.name); - } - } - - // Update max depth - if (depth > max_depth) { - max_depth = depth; - max_depth_time = t; - } - - // Record histogram - int hist_idx = std::min(depth, 20); - depth_histogram[hist_idx]++; - - // Record peaks (>5 effects) - if (depth > 5 && (peaks.empty() || t - peaks.back().time > 0.5f)) { - peaks.push_back({t, depth, active_effects}); - } - } - - // Print analysis report - std::cout << "\n=== Effect Depth Analysis ===\n"; - std::cout << "Timeline duration: " << max_time << "s\n"; - std::cout << "Total effects: " << all_effects.size() << "\n"; - std::cout << "Sample rate: " << sample_rate << " Hz (every " << dt << "s)\n"; - std::cout << "\n"; - - std::cout << "Max concurrent effects: " << max_depth - << " at t=" << max_depth_time << "s\n"; - std::cout << "\n"; - - // Print histogram - std::cout << "Effect Depth Distribution:\n"; - std::cout << "Depth | Count | Percentage | Bar\n"; - std::cout << "------|---------|------------|" - "-------------------------------------\n"; - - int total_samples = - std::accumulate(depth_histogram.begin(), depth_histogram.end(), 0); - - for (size_t i = 0; i < depth_histogram.size(); ++i) { - if (depth_histogram[i] == 0 && i > max_depth) - continue; - - float percentage = 100.0f * depth_histogram[i] / total_samples; - int bar_length = (int)(percentage / 2.0f); // Scale to ~50 chars max - - std::cout << std::setw(5) << (i < 20 ? std::to_string(i) : "20+") << " | " - << std::setw(7) << depth_histogram[i] << " | " << std::setw(9) - << std::fixed << std::setprecision(1) << percentage << "% | "; - - for (int j = 0; j < bar_length; ++j) { - std::cout << "█"; - } - std::cout << "\n"; - } - - // Print bottleneck warnings - if (max_depth > 5) { - std::cout << "\n⚠ WARNING: Performance bottlenecks detected!\n"; - std::cout << "Found " << peaks.size() - << " time periods with >5 effects:\n\n"; - - int peak_count = 0; - for (const auto& peak : peaks) { - if (peak_count >= 10) - break; // Limit output - - std::cout << " t=" << std::fixed << std::setprecision(2) << peak.time - << "s: " << peak.depth << " effects [ "; - - // Show first 5 effects - for (size_t i = 0; i < std::min(size_t(5), peak.effects.size()); ++i) { - std::cout << peak.effects[i]; - if (i < peak.effects.size() - 1) - std::cout << ", "; - } - if (peak.effects.size() > 5) { - std::cout << " +" << (peak.effects.size() - 5) << " more"; - } - std::cout << " ]\n"; - - peak_count++; - } - - if (peaks.size() > 10) { - std::cout << " ... and " << (peaks.size() - 10) << " more peaks\n"; - } - } else { - std::cout << "\n✓ No significant bottlenecks detected (max depth: " - << max_depth << " <= 5)\n"; - } - - std::cout << "\n"; -} - -// Generate ASCII Gantt chart for timeline visualization -void generate_gantt_chart(const std::string& output_file, - const std::vector<SequenceEntry>& 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; - } - - TimelineMetrics metrics = analyze_timeline(sequences, demo_end_time); - float max_time = metrics.max_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 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 += tick_interval) { - out << i; - int spacing = (i < 10) ? 4 : (i < 100) ? 3 : 2; - 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) { - // 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"; - - // Draw sequences and effects - for (size_t seq_idx = 0; seq_idx < metrics.sorted_sequences.size(); - ++seq_idx) { - const auto& seq = metrics.sorted_sequences[seq_idx]; - float seq_start = std::stof(seq.start_time); - float seq_end = get_sequence_end(seq); - - // Draw sequence bar - 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]"; - } - 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"; - } - - // Add separator between sequences - if (seq_idx < metrics.sorted_sequences.size() - 1) { - out << " "; - for (int i = 0; i < chart_width; ++i) { - out << "─"; - } - out << "\n\n"; - } else { - 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"; -} - -// Generate HTML/SVG Gantt chart for timeline visualization -void generate_gantt_html(const std::string& output_file, - const std::vector<SequenceEntry>& sequences, float bpm, - const std::string& demo_end_time) { - std::ofstream out(output_file); - if (!out.is_open()) { - std::cerr << "Warning: Could not open HTML Gantt output file: " - << output_file << "\n"; - return; - } - - TimelineMetrics metrics = analyze_timeline(sequences, demo_end_time); - float max_time = metrics.max_time; - - const int svg_width = 1400; - const int row_height = 30; - const int effect_height = 20; - const int margin_left = 250; - const int margin_top = 60; - const float time_scale = (svg_width - margin_left - 50) / max_time; - - // Count total rows needed - int total_rows = 0; - for (const auto& seq : sequences) { - total_rows += 1 + seq.effects.size(); // 1 for sequence + N for effects - } - - const int svg_height = margin_top + total_rows * row_height + 40; - - out << "<!DOCTYPE html>\n<html>\n<head>\n"; - out << "<meta charset=\"UTF-8\">\n"; - out << "<title>Demo Timeline - BPM " << bpm << "</title>\n"; - out << "<style>\n"; - out << "body { font-family: 'Courier New', monospace; margin: 20px; " - "background: #1e1e1e; color: #d4d4d4; }\n"; - out << "h1 { color: #569cd6; }\n"; - out << ".info { background: #252526; padding: 10px; border-radius: 4px; " - "margin: 10px 0; }\n"; - out << "svg { background: #252526; border-radius: 4px; }\n"; - out << ".sequence-bar { fill: #3a3a3a; stroke: #569cd6; stroke-width: 2; }\n"; - out << ".effect-bar { fill: #4ec9b0; opacity: 0.8; stroke: #2a7a6a; " - "stroke-width: 1; }\n"; - out << ".effect-bar.invalid { fill: #f48771; stroke: #d16969; }\n"; - out << ".label { fill: #d4d4d4; font-size: 12px; }\n"; - out << ".label.effect { fill: #cccccc; font-size: 11px; }\n"; - out << ".axis-line { stroke: #6a6a6a; stroke-width: 1; }\n"; - out << ".axis-label { fill: #858585; font-size: 10px; }\n"; - out << ".time-marker { stroke: #444444; stroke-width: 1; stroke-dasharray: " - "2,2; }\n"; - out << "rect:hover { opacity: 1; }\n"; - out << "title { font-size: 11px; }\n"; - out << "</style>\n</head>\n<body>\n"; - - out << "<h1>Demo Timeline Gantt Chart</h1>\n"; - out << "<div class=\"info\">\n"; - out << "<strong>BPM:</strong> " << bpm << " | "; - out << "<strong>Duration:</strong> " << max_time << "s"; - if (!demo_end_time.empty()) { - out << " (explicit end)"; - } - out << " | <strong>Sequences:</strong> " << sequences.size() << "\n"; - out << "</div>\n\n"; - - out << "<svg width=\"" << svg_width << "\" height=\"" << svg_height - << "\" xmlns=\"http://www.w3.org/2000/svg\">\n"; - - // 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 += tick_interval) { - int x = margin_left + (int)(t * time_scale); - out << " <line x1=\"" << x << "\" y1=\"" << margin_top - 15 << "\" x2=\"" - << x << "\" y2=\"" << margin_top - 5 << "\" class=\"axis-line\"/>\n"; - out << " <text x=\"" << x << "\" y=\"" << margin_top - 20 - << "\" class=\"axis-label\" text-anchor=\"middle\">" << t - << "s</text>\n"; - // Draw vertical time markers - out << " <line x1=\"" << x << "\" y1=\"" << margin_top << "\" x2=\"" << x - << "\" y2=\"" << svg_height - 20 << "\" class=\"time-marker\"/>\n"; - } - - // Draw sequences and effects - int y_offset = margin_top; - for (size_t seq_idx = 0; seq_idx < metrics.sorted_sequences.size(); - ++seq_idx) { - const auto& seq = metrics.sorted_sequences[seq_idx]; - float seq_start = std::stof(seq.start_time); - float seq_end = get_sequence_end(seq); - - int x1 = margin_left + (int)(seq_start * time_scale); - int x2 = margin_left + (int)(seq_end * time_scale); - - // Draw sequence bar - out << " <!-- Sequence -->\n"; - out << " <rect x=\"" << x1 << "\" y=\"" << y_offset << "\" width=\"" - << (x2 - x1) << "\" height=\"" << row_height - << "\" class=\"sequence-bar\">\n"; - 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"; - if (!seq.name.empty()) { - out << " \"" << seq.name << "\""; - } - out << " [pri=" << seq.priority << "]</text>\n"; - - y_offset += row_height; - - // Draw effects - for (const auto& eff : seq.effects) { - float eff_start = seq_start + std::stof(eff.start); - float eff_end = seq_start + std::stof(eff.end); - - if (seq.end_time != "-1.0") { - eff_end = std::min(eff_end, seq_end); - } - - bool invalid = eff_end < eff_start; - int eff_x1 = margin_left + (int)(eff_start * time_scale); - int eff_x2 = margin_left + (int)(eff_end * time_scale); - int eff_width = std::max(2, eff_x2 - eff_x1); - - out << " <rect x=\"" << eff_x1 << "\" y=\"" << (y_offset + 5) - << "\" width=\"" << eff_width << "\" height=\"" << effect_height - << "\" class=\"effect-bar" << (invalid ? " invalid" : "") << "\">\n"; - out << " <title>" << eff.class_name << " [pri=" << eff.priority - << "] (" << eff_start << "-" << eff_end << "s)" - << (invalid ? " *** INVALID TIME RANGE ***" : "") << "</title>\n"; - out << " </rect>\n"; - - out << " <text x=\"20\" y=\"" << (y_offset + effect_height) - << "\" class=\"label effect\">" << eff.class_name - << " [pri=" << eff.priority << "]" << (invalid ? " ⚠" : "") - << "</text>\n"; - - y_offset += row_height; - } - - // Add separator between sequences - if (seq_idx < metrics.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 - out << " <!-- Legend -->\n"; - out << " <rect x=\"10\" y=\"" << (svg_height - 15) - << "\" width=\"20\" height=\"10\" class=\"sequence-bar\"/>\n"; - out << " <text x=\"35\" y=\"" << (svg_height - 7) - << "\" class=\"axis-label\">Sequence</text>\n"; - out << " <rect x=\"120\" y=\"" << (svg_height - 15) - << "\" width=\"20\" height=\"10\" class=\"effect-bar\"/>\n"; - out << " <text x=\"145\" y=\"" << (svg_height - 7) - << "\" class=\"axis-label\">Effect</text>\n"; - out << " <rect x=\"220\" y=\"" << (svg_height - 15) - << "\" width=\"20\" height=\"10\" class=\"effect-bar invalid\"/>\n"; - out << " <text x=\"245\" y=\"" << (svg_height - 7) - << "\" class=\"axis-label\">Invalid Time Range</text>\n"; - - out << "</svg>\n"; - out << "<div class=\"info\">\n"; - out << "<strong>Tip:</strong> Hover over bars to see details. "; - out << "Higher priority numbers render later (on top).\n"; - out << "</div>\n"; - out << "</body>\n</html>\n"; - - out.close(); - std::cout << "HTML 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) { - std::string val = value; - - // Check for explicit 's' suffix (seconds) - return as-is - if (!val.empty() && val.back() == 's') { - val.pop_back(); - return val; - } - - // Check for explicit 'b' suffix (beats) - strip and convert - if (!val.empty() && val.back() == 'b') { - val.pop_back(); - } - - // DEFAULT: All numbers (with or without 'b' suffix) are beats - float beat = std::stof(val); - float time = beat * 60.0f / bpm; - return std::to_string(time); -} - -int main(int argc, char* argv[]) { - if (argc < 2) { - std::cerr << "Usage: " << argv[0] - << " <input.seq> [output.cc] [--gantt=<file.txt>] " - "[--gantt-html=<file.html>] [--analyze]\n"; - std::cerr << "Examples:\n"; - std::cerr << " " << argv[0] - << " assets/demo.seq src/generated/timeline.cc\n"; - std::cerr << " " << argv[0] << " assets/demo.seq --gantt=timeline.txt\n"; - std::cerr << " " << argv[0] - << " assets/demo.seq --gantt-html=timeline.html\n"; - std::cerr << " " << argv[0] << " assets/demo.seq --analyze\n"; - std::cerr << " " << argv[0] - << " assets/demo.seq timeline.cc --gantt=timeline.txt " - "--gantt-html=timeline.html\n"; - std::cerr << "\nOptions:\n"; - std::cerr << " --analyze Analyze effect stacking depth and " - "identify bottlenecks\n"; - std::cerr << "\nIf output.cc is omitted, only validation and Gantt " - "generation are performed.\n"; - return 1; - } - - std::string output_cc = ""; - std::string gantt_output = ""; - std::string gantt_html_output = ""; - bool analyze_depth = false; - - // Parse command line arguments - for (int i = 2; i < argc; ++i) { - std::string arg = argv[i]; - if (arg.rfind("--gantt=", 0) == 0) { - gantt_output = arg.substr(8); - } else if (arg.rfind("--gantt-html=", 0) == 0) { - gantt_html_output = arg.substr(13); - } else if (arg == "--analyze") { - analyze_depth = true; - } else if (output_cc.empty() && arg[0] != '-') { - output_cc = arg; - } - } - - std::ifstream in_file(argv[1]); - if (!in_file.is_open()) { - std::cerr << "Error: Could not open input file " << argv[1] << "\n"; - return 1; - } - - std::vector<SequenceEntry> sequences; - SequenceEntry* current_seq = nullptr; - float bpm = 120.0f; // Default BPM - std::string demo_end_time = ""; // Demo end time (optional) - - std::string line; - int line_num = 0; - while (std::getline(in_file, line)) { - ++line_num; - std::string trimmed = trim(line); - if (trimmed.empty()) - continue; - - // Parse BPM from comment - if (trimmed[0] == '#') { - std::stringstream ss(trimmed); - std::string hash, keyword; - ss >> hash >> keyword; - if (keyword == "BPM") { - ss >> bpm; - std::cout << "Using BPM: " << bpm << "\n"; - } - continue; - } - - std::stringstream ss(trimmed); - std::string command; - ss >> command; - - if (command == "SEQUENCE") { - std::string start, priority; - if (!(ss >> start >> priority)) { - std::cerr << "Error line " << line_num - << ": SEQUENCE requires <start> <priority>\n"; - return 1; - } - // Convert beat notation to time - std::string start_time = convert_to_time(start, bpm); - - // Check for optional "name" and [end_time] - std::string end_time_str = "-1.0"; // Default: no explicit end - 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 << ": Unexpected token '" - << token << "'. Expected \"name\" or [end_time]\n"; - return 1; - } - } - - sequences.push_back({start_time, priority, end_time_str, seq_name, {}}); - current_seq = &sequences.back(); - } else if (command == "EFFECT") { - if (!current_seq) { - std::cerr << "Error line " << line_num - << ": EFFECT found outside of SEQUENCE\n"; - return 1; - } - std::string priority_mod, class_name, start, end; - if (!(ss >> priority_mod >> class_name >> start >> end)) { - std::cerr << "Error line " << line_num - << ": EFFECT requires <+|=|-> <Class> <start> <end>\n"; - return 1; - } - - // Validate priority modifier - if (priority_mod != "+" && priority_mod != "=" && priority_mod != "-") { - std::cerr << "Error line " << line_num - << ": Priority modifier must be '+', '=', or '-', got: " - << priority_mod << "\n"; - return 1; - } - - // Calculate priority based on modifier and sequence state - static int current_priority = 0; - static bool first_in_sequence = true; - static const SequenceEntry* last_seq = nullptr; - - // Reset priority tracking for new sequence - if (current_seq != last_seq) { - current_priority = 0; - first_in_sequence = true; - last_seq = current_seq; - } - - // Handle first effect in sequence - if (first_in_sequence) { - if (priority_mod == "-") { - current_priority = -1; // Background layer - } else { - current_priority = 0; // Default start (+ or =) - } - first_in_sequence = false; - } else { - // Update priority based on modifier for subsequent effects - if (priority_mod == "+") { - current_priority++; - } else if (priority_mod == "-") { - current_priority--; - } - // '=' keeps current_priority unchanged - } - - std::string priority = std::to_string(current_priority); - - // Convert beat notation to time - std::string start_time = convert_to_time(start, bpm); - std::string end_time = convert_to_time(end, bpm); - - // Capture remaining args (but strip inline comments) - std::string rest_of_line; - std::getline(ss, rest_of_line); // Read rest of line - // Strip inline comments (everything from '#' onwards) - size_t comment_pos = rest_of_line.find('#'); - if (comment_pos != std::string::npos) { - rest_of_line = rest_of_line.substr(0, comment_pos); - } - // Remove leading/trailing whitespace - rest_of_line = trim(rest_of_line); - - // Parse parameters from rest of line - std::vector<std::pair<std::string, std::string>> params; - std::string extra_args = ""; - if (!rest_of_line.empty()) { - params = parse_parameters(rest_of_line); - // Keep extra_args for backward compatibility (if no key=value pairs - // found) - if (params.empty()) { - extra_args = ", " + rest_of_line; - } - } - - current_seq->effects.push_back( - {class_name, start_time, end_time, priority, extra_args, params}); - } else { - std::cerr << "Error line " << line_num << ": Unknown command '" << command - << "'\n"; - return 1; - } - } - - // Calculate demo end time from maximum effect end time - float max_end_time = 0.0f; - for (const auto& seq : sequences) { - float seq_start = std::stof(seq.start_time); - for (const auto& eff : seq.effects) { - max_end_time = std::max(max_end_time, seq_start + std::stof(eff.end)); - } - } - demo_end_time = std::to_string(max_end_time); - std::cout << "Demo end time (calculated): " << demo_end_time << "s\n"; - - // Sort sequences by start time (primary) then priority (secondary) - std::sort(sequences.begin(), sequences.end(), - [](const SequenceEntry& a, const SequenceEntry& b) { - float a_start = std::stof(a.start_time); - float b_start = std::stof(b.start_time); - if (a_start != b_start) return a_start < b_start; - return std::stoi(a.priority) < std::stoi(b.priority); - }); - - // Sort effects within each sequence by priority - for (auto& seq : sequences) { - std::sort(seq.effects.begin(), seq.effects.end(), - [](const EffectEntry& a, const EffectEntry& b) { - return std::stoi(a.priority) < std::stoi(b.priority); - }); - } - - // Validate: detect priority collisions among post-process effects - for (size_t seq_idx = 0; seq_idx < sequences.size(); ++seq_idx) { - const auto& seq = sequences[seq_idx]; - std::map<int, std::vector<std::string>> priority_map; - - // Group post-process effects by priority - for (const auto& eff : seq.effects) { - if (is_post_process_effect(eff.class_name)) { - int prio = std::stoi(eff.priority); - priority_map[prio].push_back(eff.class_name); - } - } - - // Check for collisions - for (const auto& [prio, effects] : priority_map) { - if (effects.size() > 1) { - std::cerr << "Warning: Priority collision detected in sequence at time " - << seq.start_time << "s\n"; - std::cerr << " Multiple post-process effects have priority " << prio - << ":\n"; - for (const auto& effect : effects) { - std::cerr << " - " << effect << "\n"; - } - std::cerr << " This may cause unexpected render order. Consider " - "adjusting priorities.\n"; - } - } - } - - // Validate: detect cross-sequence priority collisions for concurrent - // sequences - std::map<std::string, std::vector<size_t>> time_groups; - for (size_t i = 0; i < sequences.size(); ++i) { - time_groups[sequences[i].start_time].push_back(i); - } - - for (const auto& [start_time, seq_indices] : time_groups) { - if (seq_indices.size() > 1) { - // Multiple sequences start at the same time - std::map<int, std::vector<std::pair<std::string, size_t>>> - cross_priority_map; - - for (size_t seq_idx : seq_indices) { - const auto& seq = sequences[seq_idx]; - for (const auto& eff : seq.effects) { - if (is_post_process_effect(eff.class_name)) { - int prio = std::stoi(eff.priority); - cross_priority_map[prio].push_back({eff.class_name, seq_idx}); - } - } - } - - // Check for cross-sequence collisions - for (const auto& [prio, effects] : cross_priority_map) { - if (effects.size() > 1) { - std::cerr << "Warning: Cross-sequence priority collision at time " - << start_time << "s\n"; - std::cerr << " Multiple post-process effects across sequences have " - "priority " - << prio << ":\n"; - for (const auto& [effect, seq_idx] : effects) { - std::cerr << " - " << effect << " (sequence #" << seq_idx - << ")\n"; - } - std::cerr << " Post-process effects from different sequences at the " - "same time will be\n"; - std::cerr - << " merged into a single render chain. Consider adjusting " - "priorities to clarify order.\n"; - } - } - } - } - - // Generate C++ code if output file is specified - if (!output_cc.empty()) { - std::ofstream out_file(output_cc); - if (!out_file.is_open()) { - std::cerr << "Error: Could not open output file " << output_cc << "\n"; - return 1; - } - - out_file << "// Auto-generated by seq_compiler. Do not edit.\n"; - out_file << "#include \"gpu/demo_effects.h\"\n"; - out_file << "#include \"gpu/effect.h\"\n\n"; - - // Generate demo duration function - if (!demo_end_time.empty()) { - out_file << "float GetDemoDuration() {\n"; - out_file << " return " << demo_end_time << "f;\n"; - out_file << "}\n\n"; - } else { - out_file << "float GetDemoDuration() {\n"; - out_file << " return -1.0f; // No end time specified\n"; - out_file << "}\n\n"; - } - - out_file << "void LoadTimeline(MainSequence& main_seq, const GpuContext& " - "ctx) {\n"; - - for (const SequenceEntry& seq : sequences) { - out_file << " {\n"; - out_file << " auto seq = std::make_shared<Sequence>();\n"; - // Set sequence end time if specified - if (seq.end_time != "-1.0") { - out_file << " seq->set_end_time(" << seq.end_time << "f);\n"; - } - for (const EffectEntry& eff : seq.effects) { - // Check if effect has parameters - if (!eff.params.empty() && eff.class_name == "FlashEffect") { - // Generate parameter struct initialization for FlashEffect - out_file << " {\n"; - out_file << " FlashEffectParams p;\n"; - - for (const auto& [key, value] : eff.params) { - if (key == "color") { - // Parse color as r,g,b - std::istringstream color_ss(value); - std::string r, g, b; - std::getline(color_ss, r, ','); - std::getline(color_ss, g, ','); - std::getline(color_ss, b, ','); - out_file << " p.color[0] = " << r << "f;\n"; - out_file << " p.color[1] = " << g << "f;\n"; - out_file << " p.color[2] = " << b << "f;\n"; - } else if (key == "decay") { - out_file << " p.decay_rate = " << value << "f;\n"; - } else if (key == "threshold") { - out_file << " p.trigger_threshold = " << value << "f;\n"; - } - } - - out_file << " seq->add_effect(std::make_shared<" - << eff.class_name << ">(ctx, p), " << eff.start << "f, " - << eff.end << "f, " << eff.priority << ");\n"; - out_file << " }\n"; - } else if (!eff.params.empty() && - eff.class_name == "ChromaAberrationEffect") { - // Generate parameter struct initialization for ChromaAberrationEffect - out_file << " {\n"; - out_file << " ChromaAberrationParams p;\n"; - - for (const auto& [key, value] : eff.params) { - if (key == "offset") { - out_file << " p.offset_scale = " << value << "f;\n"; - } else if (key == "angle") { - out_file << " p.angle = " << value << "f;\n"; - } - } - - out_file << " seq->add_effect(std::make_shared<" - << eff.class_name << ">(ctx, p), " << eff.start << "f, " - << eff.end << "f, " << eff.priority << ");\n"; - out_file << " }\n"; - } else if (!eff.params.empty() && - eff.class_name == "GaussianBlurEffect") { - // Generate parameter struct initialization for GaussianBlurEffect - out_file << " {\n"; - out_file << " GaussianBlurParams p;\n"; - - for (const auto& [key, value] : eff.params) { - if (key == "strength") { - out_file << " p.strength = " << value << "f;\n"; - } - } - - out_file << " seq->add_effect(std::make_shared<" - << eff.class_name << ">(ctx, p), " << eff.start << "f, " - << eff.end << "f, " << eff.priority << ");\n"; - out_file << " }\n"; - } else if (!eff.params.empty() && eff.class_name == "VignetteEffect") { - // Generate parameter struct initialization for VignetteEffect - out_file << " {\n"; - out_file << " VignetteParams p;\n"; - - for (const auto& [key, value] : eff.params) { - if (key == "radius") { - out_file << " p.radius = " << value << "f;\n"; - } else if (key == "softness") { - out_file << " p.softness = " << value << "f;\n"; - } - } - - out_file << " seq->add_effect(std::make_shared<" - << eff.class_name << ">(ctx, p), " << eff.start << "f, " - << eff.end << "f, " << eff.priority << ");\n"; - out_file << " }\n"; - } else if (!eff.params.empty() && eff.class_name == "CNNEffect") { - // Generate parameter struct initialization for CNNEffect - // If layers>1, expand into multiple chained effect instances - int num_layers = 1; - float blend_amount = 1.0f; - - for (const auto& [key, value] : eff.params) { - if (key == "layers") { - num_layers = std::stoi(value); - } else if (key == "blend") { - blend_amount = std::stof(value); - } - } - - // Generate one effect per layer - for (int layer = 0; layer < num_layers; ++layer) { - out_file << " {\n"; - out_file << " CNNEffectParams p;\n"; - out_file << " p.layer_index = " << layer << ";\n"; - out_file << " p.total_layers = " << num_layers << ";\n"; - // Only apply blend_amount on the last layer - if (layer == num_layers - 1) { - out_file << " p.blend_amount = " << blend_amount << "f;\n"; - } else { - out_file << " p.blend_amount = 1.0f;\n"; - } - out_file << " seq->add_effect(std::make_shared<" - << eff.class_name << ">(ctx, p), " << eff.start << "f, " - << eff.end << "f, " << (std::stoi(eff.priority) + layer) - << ");\n"; - out_file << " }\n"; - } - } else if (!eff.params.empty() && eff.class_name == "CNNv2Effect") { - // Generate parameter struct initialization for CNNv2Effect - out_file << " {\n"; - out_file << " CNNv2EffectParams p;\n"; - - for (const auto& [key, value] : eff.params) { - if (key == "blend") { - out_file << " p.blend_amount = " << value << "f;\n"; - } - } - - out_file << " seq->add_effect(std::make_shared<" - << eff.class_name << ">(ctx, p), " << eff.start << "f, " - << eff.end << "f, " << eff.priority << ");\n"; - out_file << " }\n"; - } else { - // No parameters or unsupported effect - use default constructor - out_file << " seq->add_effect(std::make_shared<" << eff.class_name - << ">(ctx" << eff.extra_args << "), " << eff.start << "f, " - << eff.end << "f, " << eff.priority << ");\n"; - } - } - out_file << " main_seq.add_sequence(seq, " << seq.start_time << "f, " - << seq.priority << ");\n"; - out_file << " }\n"; - } - - out_file << "}\n"; - out_file.close(); - - std::cout << "Successfully generated timeline with " << sequences.size() - << " sequences.\n"; - } else { - std::cout << "Validation successful: " << sequences.size() << " sequences, " - << (demo_end_time.empty() ? "no" : "explicit") << " end time.\n"; - } - - // Generate Gantt charts if requested - if (!gantt_output.empty()) { - generate_gantt_chart(gantt_output, sequences, bpm, demo_end_time); - } - if (!gantt_html_output.empty()) { - generate_gantt_html(gantt_html_output, sequences, bpm, demo_end_time); - } - - // Analyze effect stacking depth if requested - if (analyze_depth) { - analyze_effect_depth(sequences, demo_end_time); - } - - return 0; -} |
