diff options
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/seq_compiler.cc | 132 | ||||
| -rw-r--r-- | tools/spectool.cc | 8 |
2 files changed, 125 insertions, 15 deletions
diff --git a/tools/seq_compiler.cc b/tools/seq_compiler.cc index 8956c32..a4fd00c 100644 --- a/tools/seq_compiler.cc +++ b/tools/seq_compiler.cc @@ -20,17 +20,48 @@ struct EffectEntry { struct SequenceEntry { std::string start_time; std::string priority; + std::string end_time; // Optional: -1.0f means "no explicit end" std::vector<EffectEntry> effects; }; std::string trim(const std::string& str) { - size_t first = str.find_first_not_of(" "); + size_t first = str.find_first_not_of(" \t"); if (std::string::npos == first) - return str; - size_t last = str.find_last_not_of(" "); + return ""; // String is all whitespace, return empty string + size_t last = str.find_last_not_of(" \t"); return str.substr(first, (last - first + 1)); } +// 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 != 3) { std::cerr << "Usage: " << argv[0] << " <input.seq> <output.cc>\n"; @@ -47,27 +78,72 @@ int main(int argc, char* argv[]) { 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() || trimmed[0] == '#') + 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") { + if (command == "END_DEMO") { + std::string end_time; + if (!(ss >> end_time)) { + std::cerr << "Error line " << line_num + << ": END_DEMO requires <time>\n"; + return 1; + } + // Convert beat notation to time + demo_end_time = convert_to_time(end_time, bpm); + std::cout << "Demo end time: " << demo_end_time << "s\n"; + } else if (command == "SEQUENCE") { std::string start, priority; if (!(ss >> start >> priority)) { std::cerr << "Error line " << line_num << ": SEQUENCE requires <start> <priority>\n"; return 1; } - sequences.push_back({start, priority, {}}); + // Convert beat notation to time + std::string start_time = convert_to_time(start, bpm); + + // Check for optional [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); + 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"; + return 1; + } + } + + sequences.push_back({start_time, priority, end_time_str, {}}); current_seq = &sequences.back(); } else if (command == "EFFECT") { if (!current_seq) { @@ -82,18 +158,28 @@ int main(int argc, char* argv[]) { return 1; } - // Capture remaining args - std::string extra_args; - std::getline(ss, extra_args); // Read rest of line - // Remove leading whitespace from getline if any (getline reads the space - // after priority) - extra_args = trim(extra_args); - if (!extra_args.empty()) { - extra_args = ", " + extra_args; + // 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); + + std::string extra_args = ""; + if (!rest_of_line.empty()) { + extra_args = ", " + rest_of_line; } current_seq->effects.push_back( - {class_name, start, end, priority, extra_args}); + {class_name, start_time, end_time, priority, extra_args}); } else { std::cerr << "Error line " << line_num << ": Unknown command '" << command << "'\n"; @@ -124,12 +210,28 @@ int main(int argc, char* argv[]) { 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, WGPUDevice device, " "WGPUQueue queue, WGPUTextureFormat format) {\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) { out_file << " seq->add_effect(std::make_shared<" << eff.class_name << ">(device, queue, format" << eff.extra_args << "), " diff --git a/tools/spectool.cc b/tools/spectool.cc index 1dd4ceb..7349912 100644 --- a/tools/spectool.cc +++ b/tools/spectool.cc @@ -29,7 +29,15 @@ int analyze_audio(const char* in_path, const char* out_path) { printf("Analyzing %s -> %s\n", in_path, out_path); + // Use higher quality resampling for better audio quality + // Source files are typically 44.1kHz or 96kHz, 16/24-bit, mono/stereo ma_decoder_config config = ma_decoder_config_init(ma_format_f32, 1, 32000); + + // CRITICAL: Use highest quality low-pass filter to preserve audio quality + // Default lpfOrder is very low, causing audible aliasing when downsampling + // Maximum lpfOrder is implementation-dependent, but 8 is reasonable for quality + config.resampling.linear.lpfOrder = 8; // Higher = better anti-aliasing (default is likely 1-2) + ma_decoder decoder; if (ma_decoder_init_file(in_path, &config, &decoder) != MA_SUCCESS) { printf("Error: Failed to open or decode audio file: %s\n", in_path); |
