#pragma once #include #include #include #include #include #include namespace vamp_pico { constexpr auto digit_width = std::size_t{22}; constexpr auto max_pages = std::uint32_t{10000}; inline auto counter_text(std::uint32_t page, std::uint32_t pages) -> std::array { auto text = std::array{}; if (pages == 0U || pages > max_pages || page == 0U || page > pages) { return text; } auto first = std::to_chars(text.data(), text.data() + text.size() - 1U, page); *first.ptr = '/'; std::to_chars(first.ptr + 1U, text.data() + text.size() - 1U, pages); return text; } enum class sprite : std::size_t { VAMP, CONNECTING, OFFLINE, BUSY, READY, LOADING, WIFI, ERROR, PAGE, ACOUSTIC, ELECTRIC, VOCAL, UNAVAILABLE, MAIN_MENU, PERFORMANCE, SETTINGS, BACKING, METRONOME, SOLOS, TEMPO, BACK, ON, OFF, BPM, MANUAL, PREPARING, COUNT_IN, PLAYING, SUSTAIN, HOLD_TO_EXIT, DIGIT_0, DIGIT_1, DIGIT_2, DIGIT_3, DIGIT_4, DIGIT_5, DIGIT_6, DIGIT_7, DIGIT_8, DIGIT_9, SLASH, COUNT, }; struct coverage_image { std::string_view pixels{}; std::size_t width{}; std::size_t height{}; auto at(std::size_t x, std::size_t y) const -> std::uint8_t { if (x >= width || y >= height || (y * width + x) / 2U >= pixels.size()) { return 0U; } auto index = y * width + x; auto packed = static_cast(pixels[index / 2U]); return (index & 1U) == 0U ? packed >> 4U : packed & 15U; } }; inline auto static_sprite(std::string_view atlas, sprite value) -> coverage_image { constexpr auto count = static_cast(sprite::COUNT); auto index = static_cast(value); if (atlas.size() < 8U + count * 8U || atlas.substr(0U, 4U) != "VPSA" || static_cast(atlas[4]) != count || atlas[5] != 0 || atlas[6] != 0 || atlas[7] != 0 || index >= count) { return {}; } auto word = [&](std::size_t offset) -> std::size_t { return static_cast(atlas[offset]) | (static_cast(static_cast(atlas[offset + 1U])) << 8U); }; auto entry = 8U + index * 8U; auto width = word(entry); auto height = word(entry + 2U); auto offset = word(entry + 4U) | (word(entry + 6U) << 16U); auto size = (width * height + 1U) / 2U; if (width == 0U || width > 176U || height != 28U || offset < 8U + count * 8U || offset > atlas.size() || size > atlas.size() - offset) { return {}; } return {atlas.substr(offset, size), width, height}; } class title_cache { public: auto select(std::string_view identifier) -> std::bool_t { if (identifier == identifier_.data()) { return false; } identifier_.fill(0); if (vamp_protocol::valid_title_id(identifier)) { std::copy(identifier.begin(), identifier.end(), identifier_.begin()); } ready_ = false; failed_ = false; return true; } auto accept(std::string_view identifier, std::string_view bytes) -> std::bool_t { if (identifier != identifier_.data() || !vamp_protocol::valid_title_id(identifier) || !vamp_protocol::valid_title_image(bytes)) { return false; } std::copy(bytes.begin() + vamp_protocol::title_header_size, bytes.end(), pixels_.begin()); ready_ = true; failed_ = false; return true; } auto fail(std::string_view identifier) -> std::bool_t { if (identifier != identifier_.data() || ready_) { return false; } failed_ = true; return true; } auto ready() const -> std::bool_t { return ready_; } auto failed() const -> std::bool_t { return failed_; } auto image() const -> coverage_image { return ready_ ? coverage_image{{pixels_.data(), pixels_.size()}, vamp_protocol::title_width, vamp_protocol::title_height} : coverage_image{}; } private: std::array identifier_{}; std::array pixels_{}; std::bool_t ready_{}; std::bool_t failed_{}; }; }