// 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 #include #include #include #include #include #include #include #include #include struct EffectEntry { std::string class_name; std::string start; std::string end; std::string priority; std::string extra_args; std::vector> 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 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> parse_parameters(const std::string& args) { std::vector> 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 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 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& 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 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& 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 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 depth_histogram(21, 0); // Track depths 0-20+ struct PeakInfo { float time; int depth; std::vector effects; }; std::vector peaks; for (float t = 0.0f; t <= max_time; t += dt) { int depth = 0; std::vector 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& 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& 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 << "\n\n\n"; out << "\n"; out << "Demo Timeline - BPM " << bpm << "\n"; out << "\n\n\n"; out << "

Demo Timeline Gantt Chart

\n"; out << "
\n"; out << "BPM: " << bpm << " | "; out << "Duration: " << max_time << "s"; if (!demo_end_time.empty()) { out << " (explicit end)"; } out << " | Sequences: " << sequences.size() << "\n"; out << "
\n\n"; out << "\n"; // Draw time axis with adaptive tick interval const int tick_interval = calculate_tick_interval(max_time); out << " \n"; out << " \n"; for (int t = 0; t <= (int)max_time; t += tick_interval) { int x = margin_left + (int)(t * time_scale); out << " \n"; out << " " << t << "s\n"; // Draw vertical time markers out << " \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 << " \n"; out << " \n"; out << " SEQ@" << seq_start << "s"; if (!seq.name.empty()) { out << " \"" << seq.name << "\""; } out << " [pri=" << seq.priority << "] (" << seq_start << "-" << seq_end << "s)\n"; out << " \n"; // Draw sequence label out << " SEQ@" << seq_start << "s"; if (!seq.name.empty()) { out << " \"" << seq.name << "\""; } out << " [pri=" << seq.priority << "]\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 << " \n"; out << " " << eff.class_name << " [pri=" << eff.priority << "] (" << eff_start << "-" << eff_end << "s)" << (invalid ? " *** INVALID TIME RANGE ***" : "") << "\n"; out << " \n"; out << " " << eff.class_name << " [pri=" << eff.priority << "]" << (invalid ? " ⚠" : "") << "\n"; y_offset += row_height; } // Add separator between sequences if (seq_idx < metrics.sorted_sequences.size() - 1) { out << " \n"; out << " \n"; y_offset += 10; // Extra spacing after separator } } // Legend out << " \n"; out << " \n"; out << " Sequence\n"; out << " \n"; out << " Effect\n"; out << " \n"; out << " Invalid Time Range\n"; out << "\n"; out << "
\n"; out << "Tip: Hover over bars to see details. "; out << "Higher priority numbers render later (on top).\n"; out << "
\n"; out << "\n\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] << " [output.cc] [--gantt=] " "[--gantt-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 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 \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 <+|=|-> \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> 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> 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> 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>> 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();\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; }