// 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 struct EffectEntry { std::string class_name; std::string start; std::string end; std::string priority; std::string extra_args; }; struct SequenceEntry { std::string start_time; std::string priority; std::string end_time; // Optional: -1.0f means "no explicit end" 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)); } // 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 = seq_start; // Start at sequence start // 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"; } // 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; } // 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)); } } 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 out << " \n"; out << " \n"; for (int t = 0; t <= (int)max_time; t += 5) { 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 (const auto& seq : sequences) { float seq_start = std::stof(seq.start_time); float seq_end = seq_start; // Start at sequence start if (seq.end_time != "-1.0") { seq_end = seq_start + std::stof(seq.end_time); } else { for (const auto& eff : seq.effects) { seq_end = std::max(seq_end, seq_start + std::stof(eff.end)); } } 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 [pri=" << seq.priority << "] (" << seq_start << "-" << seq_end << "s)\n"; out << " \n"; // Draw sequence label out << " SEQ@" << seq_start << "s [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; } } // 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; bool is_beat = false; // Check for explicit 'b' suffix (beat) if (!val.empty() && val.back() == 'b') { is_beat = true; val.pop_back(); } // Check for explicit 's' suffix (seconds) else if (!val.empty() && val.back() == 's') { val.pop_back(); return val; // Already in seconds } // If no suffix and no decimal point, assume beats else if (val.find('.') == std::string::npos) { is_beat = true; } if (is_beat) { float beat = std::stof(val); float time = beat * 60.0f / bpm; return std::to_string(time); } return val; // Return as-is (seconds) } int main(int argc, char* argv[]) { if (argc < 2) { std::cerr << "Usage: " << argv[0] << " [output.cc] [--gantt=] [--gantt-html=]\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 timeline.cc --gantt=timeline.txt --gantt-html=timeline.html\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 = ""; // 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 (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 == "END_DEMO") { std::string end_time; if (!(ss >> end_time)) { std::cerr << "Error line " << line_num << ": END_DEMO requires