WIP memon chart parsing
This commit is contained in:
parent
f7ef5e9179
commit
ec42f68979
415
src/Data/Note.cpp
Normal file
415
src/Data/Note.cpp
Normal file
@ -0,0 +1,415 @@
|
||||
#include "Note.hpp"
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include "../Toolkit/VariantVisitor.hpp"
|
||||
|
||||
namespace Data {
|
||||
Position::Position(std::uint64_t x, std::uint64_t y) : x(x), y(y) {
|
||||
if (x > 3 or y > 3) {
|
||||
std::stringstream ss;
|
||||
ss << "Attempted to create Position from invalid coordinates : ";
|
||||
ss << *this;
|
||||
throw std::invalid_argument(ss.str());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Position Position::from_index(std::uint64_t index) {
|
||||
if (index > 15) {
|
||||
std::stringstream ss;
|
||||
ss << "Attempted to create Position from invalid index : " << index;
|
||||
throw std::invalid_argument(ss.str());
|
||||
}
|
||||
return Position{index % 4, index / 4};
|
||||
}
|
||||
|
||||
std::uint64_t Position::index() const {
|
||||
return x + y*4;
|
||||
};
|
||||
|
||||
std::uint64_t Position::get_x() const {
|
||||
return x;
|
||||
};
|
||||
|
||||
std::uint64_t Position::get_y() const {
|
||||
return y;
|
||||
};
|
||||
|
||||
std::ostream& operator<< (std::ostream& out, const Position& pos) {
|
||||
out << fmt::format("{}", pos);
|
||||
return out;
|
||||
};
|
||||
|
||||
Position Position::mirror_horizontally() const {
|
||||
return {3-x, y};
|
||||
}
|
||||
|
||||
Position Position::mirror_vertically() const {
|
||||
return {x, 3-y};
|
||||
}
|
||||
|
||||
Position Position::rotate_90_clockwise() const {
|
||||
return {3-y, x};
|
||||
}
|
||||
|
||||
Position Position::rotate_90_counter_clockwise() const {
|
||||
return {y, 3-x};
|
||||
}
|
||||
|
||||
Position Position::rotate_180() const {
|
||||
return {3-x, 3-y};
|
||||
}
|
||||
|
||||
|
||||
TapNote::TapNote(sf::Time time, Position position): time(time), position(position) {};
|
||||
|
||||
sf::Time TapNote::get_time() const {
|
||||
return time;
|
||||
};
|
||||
|
||||
Position TapNote::get_position() const {
|
||||
return position;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const TapNote& t) {
|
||||
out << fmt::format("{}", t);
|
||||
return out;
|
||||
};
|
||||
|
||||
nlohmann::ordered_json TapNote::dump_to_memon_1_0_0() const {
|
||||
return {
|
||||
{"n", position.index()},
|
||||
{"t", beat_to_best_form(time)}
|
||||
};
|
||||
};
|
||||
|
||||
TapNote TapNote::mirror_horizontally() const {
|
||||
return {time, position.mirror_horizontally()};
|
||||
}
|
||||
|
||||
TapNote TapNote::mirror_vertically() const {
|
||||
return {time, position.mirror_vertically()};
|
||||
}
|
||||
|
||||
TapNote TapNote::rotate_90_clockwise() const {
|
||||
return {time, position.rotate_90_clockwise()};
|
||||
}
|
||||
|
||||
TapNote TapNote::rotate_90_counter_clockwise() const {
|
||||
return {time, position.rotate_90_counter_clockwise()};
|
||||
}
|
||||
|
||||
TapNote TapNote::rotate_180() const {
|
||||
return {time, position.rotate_180()};
|
||||
}
|
||||
|
||||
TapNote TapNote::quantize(unsigned int snap) const {
|
||||
return {round_beats(time, snap), position};
|
||||
}
|
||||
|
||||
|
||||
LongNote::LongNote(sf::Time time, Position position, sf::Time duration, Position tail_tip) :
|
||||
time(time),
|
||||
position(position),
|
||||
duration(duration),
|
||||
tail_tip(tail_tip)
|
||||
{
|
||||
if (duration <= 0) {
|
||||
std::stringstream ss;
|
||||
ss << "Attempted to create a LongNote with zero or negative duration : ";
|
||||
ss << duration;
|
||||
throw std::invalid_argument(ss.str());
|
||||
}
|
||||
if (tail_tip == position) {
|
||||
throw std::invalid_argument(
|
||||
"Attempted to create a LongNote with a zero-length tail"
|
||||
);
|
||||
}
|
||||
if (
|
||||
not (
|
||||
(tail_tip.get_x() == position.get_x())
|
||||
xor (tail_tip.get_y() == position.get_y())
|
||||
)
|
||||
) {
|
||||
std::stringstream ss;
|
||||
ss << "Attempted to create a LongNote with and invalid tail : ";
|
||||
ss << "position: " << position << " , tail_tip: " << tail_tip;
|
||||
throw std::invalid_argument(ss.str());
|
||||
}
|
||||
};
|
||||
|
||||
sf::Time LongNote::get_time() const {
|
||||
return time;
|
||||
};
|
||||
|
||||
Position LongNote::get_position() const {
|
||||
return position;
|
||||
};
|
||||
|
||||
sf::Time LongNote::get_end() const {
|
||||
return time + duration;
|
||||
};
|
||||
|
||||
sf::Time LongNote::get_duration() const {
|
||||
return duration;
|
||||
};
|
||||
|
||||
Position LongNote::get_tail_tip() const {
|
||||
return tail_tip;
|
||||
};
|
||||
|
||||
const auto abs_diff = [](const auto& a, const auto& b){
|
||||
if (a > b) {
|
||||
return a - b;
|
||||
} else {
|
||||
return b - a;
|
||||
}
|
||||
};
|
||||
|
||||
std::uint64_t LongNote::get_tail_length() const {
|
||||
if (position.get_x() == tail_tip.get_x()) {
|
||||
return abs_diff(position.get_y(), tail_tip.get_y());
|
||||
} else {
|
||||
return abs_diff(position.get_x(), tail_tip.get_x());
|
||||
}
|
||||
}
|
||||
|
||||
std::uint64_t LongNote::get_tail_angle() const {
|
||||
if (position.get_x() == tail_tip.get_x()) {
|
||||
if (position.get_y() > tail_tip.get_y()) {
|
||||
return 0;
|
||||
} else {
|
||||
return 180;
|
||||
}
|
||||
} else {
|
||||
if (position.get_x() > tail_tip.get_x()) {
|
||||
return 270;
|
||||
} else {
|
||||
return 90;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const LongNote& l) {
|
||||
out << fmt::format("{}", l);
|
||||
return out;
|
||||
};
|
||||
|
||||
nlohmann::ordered_json LongNote::dump_to_memon_1_0_0() const {
|
||||
return {
|
||||
{"n", position.index()},
|
||||
{"t", beat_to_best_form(time)},
|
||||
{"l", beat_to_best_form(duration)},
|
||||
{"p", tail_as_6_notation()}
|
||||
};
|
||||
};
|
||||
|
||||
int LongNote::tail_as_6_notation() const {
|
||||
if (tail_tip.get_y() == position.get_y()) {
|
||||
return tail_tip.get_x() - static_cast<int>(tail_tip.get_x() > position.get_x());
|
||||
} else {
|
||||
return 3 + tail_tip.get_y() - int(tail_tip.get_y() > position.get_y());
|
||||
}
|
||||
}
|
||||
|
||||
LongNote LongNote::mirror_horizontally() const {
|
||||
return {
|
||||
time,
|
||||
position.mirror_horizontally(),
|
||||
duration,
|
||||
tail_tip.mirror_horizontally()
|
||||
};
|
||||
}
|
||||
|
||||
LongNote LongNote::mirror_vertically() const {
|
||||
return {
|
||||
time,
|
||||
position.mirror_vertically(),
|
||||
duration,
|
||||
tail_tip.mirror_vertically()
|
||||
};
|
||||
}
|
||||
|
||||
LongNote LongNote::rotate_90_clockwise() const {
|
||||
return {
|
||||
time,
|
||||
position.rotate_90_clockwise(),
|
||||
duration,
|
||||
tail_tip.rotate_90_clockwise()
|
||||
};
|
||||
}
|
||||
|
||||
LongNote LongNote::rotate_90_counter_clockwise() const {
|
||||
return {
|
||||
time,
|
||||
position.rotate_90_counter_clockwise(),
|
||||
duration,
|
||||
tail_tip.rotate_90_counter_clockwise()
|
||||
};
|
||||
}
|
||||
|
||||
LongNote LongNote::rotate_180() const {
|
||||
return {
|
||||
time,
|
||||
position.rotate_180(),
|
||||
duration,
|
||||
tail_tip.rotate_180()
|
||||
};
|
||||
}
|
||||
|
||||
LongNote LongNote::quantize(unsigned int snap) const {
|
||||
return {
|
||||
round_beats(time, snap),
|
||||
position,
|
||||
round_beats(duration, snap),
|
||||
tail_tip
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* legacy long note tail index is given relative to the note position :
|
||||
*
|
||||
* 8
|
||||
* 4
|
||||
* 0
|
||||
* 11 7 3 . 1 5 9
|
||||
* 2
|
||||
* 6
|
||||
* 10
|
||||
*/
|
||||
Position convert_legacy_memon_tail_index_to_position(const Position& pos, std::uint64_t tail_index) {
|
||||
auto length = (tail_index / 4) + 1;
|
||||
switch (tail_index % 4) {
|
||||
case 0: // up
|
||||
return {pos.get_x(), pos.get_y() - length};
|
||||
case 1: // right
|
||||
return {pos.get_x() + length, pos.get_y()};
|
||||
case 2: // down
|
||||
return {pos.get_x(), pos.get_y() + length};
|
||||
case 3: // left
|
||||
return {pos.get_x() - length, pos.get_y()};
|
||||
default:
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
memon 1.0.0 stores tail positions as an number between 0 and 5 inclusive.
|
||||
|
||||
Explainations here :
|
||||
https://memon-spec.readthedocs.io/en/latest/schema.html#long-note
|
||||
*/
|
||||
Position convert_6_notation_to_position(const Position& pos, std::uint64_t tail_index) {
|
||||
if (tail_index < 3) { // horizontal
|
||||
if (tail_index < pos.get_x()) {
|
||||
return {tail_index, pos.get_y()};
|
||||
} else {
|
||||
return {tail_index + 1, pos.get_y()};
|
||||
}
|
||||
} else { // vertical
|
||||
tail_index -= 3;
|
||||
if (tail_index < pos.get_y()) {
|
||||
return {pos.get_x(), tail_index};
|
||||
} else {
|
||||
return {pos.get_x(), tail_index + 1};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto _time_bounds = VariantVisitor {
|
||||
[](const TapNote& t) -> std::pair<sf::Time, sf::Time> { return {t.get_time(), t.get_time()}; },
|
||||
[](const LongNote& l) -> std::pair<sf::Time, sf::Time> { return {l.get_time(), l.get_end()}; },
|
||||
};
|
||||
|
||||
sf::Time Note::get_time() const {
|
||||
return std::visit([](const auto& n){return n.get_time();}, this->note);
|
||||
};
|
||||
|
||||
std::pair<sf::Time, sf::Time> Note::get_time_bounds() const {
|
||||
return std::visit(better::_time_bounds, this->note);
|
||||
};
|
||||
|
||||
Position Note::get_position() const {
|
||||
return std::visit([](const auto& n){return n.get_position();}, this->note);
|
||||
};
|
||||
|
||||
sf::Time Note::get_end() const {
|
||||
return this->get_time_bounds().second;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const Note& n) {
|
||||
out << fmt::format("{}", n);
|
||||
return out;
|
||||
};
|
||||
|
||||
nlohmann::ordered_json Note::dump_to_memon_1_0_0() const {
|
||||
return std::visit([](const auto& n){return n.dump_to_memon_1_0_0();}, this->note);
|
||||
}
|
||||
|
||||
Note Note::load_from_memon_1_0_0(const nlohmann::json& json, std::uint64_t resolution) {
|
||||
const auto position = Position::from_index(json["n"].get<std::uint64_t>());
|
||||
const auto time = load_memon_1_0_0_beat(json["t"], resolution);
|
||||
if (not json.contains("l")) {
|
||||
return TapNote{time, position};
|
||||
}
|
||||
|
||||
const auto duration = load_memon_1_0_0_beat(json["l"], resolution);
|
||||
const auto tail_index = json["p"].get<std::uint64_t>();
|
||||
return LongNote{
|
||||
time,
|
||||
position,
|
||||
duration,
|
||||
convert_6_notation_to_position(position, tail_index)
|
||||
};
|
||||
}
|
||||
|
||||
Note Note::load_from_memon_legacy(const nlohmann::json& json, std::uint64_t resolution) {
|
||||
const auto position = Position::from_index(json["n"].get<std::uint64_t>());
|
||||
const auto time = sf::Time{
|
||||
json["t"].get<std::uint64_t>(),
|
||||
resolution,
|
||||
};
|
||||
const auto duration = sf::Time{
|
||||
json["l"].get<std::uint64_t>(),
|
||||
resolution,
|
||||
};
|
||||
const auto tail_index = json["p"].get<std::uint64_t>();
|
||||
if (duration > 0) {
|
||||
return LongNote{
|
||||
time,
|
||||
position,
|
||||
duration,
|
||||
convert_legacy_memon_tail_index_to_position(position, tail_index)
|
||||
};
|
||||
} else {
|
||||
return TapNote{time, position};
|
||||
}
|
||||
}
|
||||
|
||||
Note Note::mirror_horizontally() const {
|
||||
return std::visit([](const auto& n) -> Note {return n.mirror_horizontally();}, this->note);
|
||||
}
|
||||
|
||||
Note Note::mirror_vertically() const {
|
||||
return std::visit([](const auto& n) -> Note {return n.mirror_vertically();}, this->note);
|
||||
}
|
||||
|
||||
Note Note::rotate_90_clockwise() const {
|
||||
return std::visit([](const auto& n) -> Note {return n.rotate_90_clockwise();}, this->note);
|
||||
}
|
||||
|
||||
Note Note::rotate_90_counter_clockwise() const {
|
||||
return std::visit([](const auto& n) -> Note {return n.rotate_90_counter_clockwise();}, this->note);
|
||||
}
|
||||
|
||||
Note Note::rotate_180() const {
|
||||
return std::visit([](const auto& n) -> Note {return n.rotate_180();}, this->note);
|
||||
}
|
||||
|
||||
Note Note::quantize(unsigned int snap) const {
|
||||
return std::visit([=](const auto& n) -> Note {return n.quantize(snap);}, this->note);
|
||||
}
|
||||
}
|
||||
|
@ -1,78 +1,193 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include <SFML/System/Time.hpp>
|
||||
#include <cmath>
|
||||
#include <compare>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#include "../Input/Buttons.hpp"
|
||||
#include <fmt/core.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace Data {
|
||||
struct Note {
|
||||
// offset since the beginning of the audio
|
||||
sf::Time timing;
|
||||
Input::Button position;
|
||||
// zero means it's a normal note
|
||||
sf::Time duration;
|
||||
Input::Button tail;
|
||||
/*
|
||||
A specific square on the controller. (0, 0) is the top-left button, x
|
||||
goes right, y goes down.
|
||||
x →
|
||||
0 1 2 3
|
||||
y 0 □ □ □ □
|
||||
↓ 1 □ □ □ □
|
||||
2 □ □ □ □
|
||||
3 □ □ □ □
|
||||
*/
|
||||
class Position {
|
||||
public:
|
||||
Position(std::uint64_t x, std::uint64_t y);
|
||||
static Position from_index(std::uint64_t index);
|
||||
|
||||
bool operator==(const Note &rhs) const {
|
||||
return timing == rhs.timing && position == rhs.position;
|
||||
};
|
||||
bool operator!=(const Note &rhs) const {
|
||||
return not(rhs == *this);
|
||||
};
|
||||
bool operator<(const Note &rhs) const {
|
||||
if (timing < rhs.timing) {
|
||||
return true;
|
||||
}
|
||||
if (rhs.timing < timing) {
|
||||
return false;
|
||||
}
|
||||
return position < rhs.position;
|
||||
};
|
||||
bool operator>(const Note &rhs) const {
|
||||
return rhs < *this;
|
||||
};
|
||||
bool operator<=(const Note &rhs) const {
|
||||
return !(rhs < *this);
|
||||
};
|
||||
bool operator>=(const Note &rhs) const {
|
||||
return !(*this < rhs);
|
||||
};
|
||||
std::uint64_t index() const;
|
||||
std::uint64_t get_x() const;
|
||||
std::uint64_t get_y() const;
|
||||
|
||||
// get the integer distance between the tail tip starting position
|
||||
// and the target square
|
||||
int get_tail_length() const {
|
||||
auto pos_coords = Input::button_to_coords(position);
|
||||
auto tail_coords = Input::button_to_coords(tail);
|
||||
assert(pos_coords.x == tail_coords.x or pos_coords.y == tail_coords.y);
|
||||
if (pos_coords.x == tail_coords.x) {
|
||||
return std::abs(static_cast<int>(pos_coords.y)-static_cast<int>(tail_coords.y));
|
||||
} else {
|
||||
return std::abs(static_cast<int>(pos_coords.x)-static_cast<int>(tail_coords.x));
|
||||
}
|
||||
}
|
||||
auto operator<=>(const Position&) const = default;
|
||||
friend std::ostream& operator<<(std::ostream& out, const Position& pos);
|
||||
|
||||
// returns the number of clockwise quarter turns needed to rotate
|
||||
// a tail going down to get the tail's actual orienation
|
||||
int get_tail_angle() const {
|
||||
auto pos_coords = Input::button_to_coords(position);
|
||||
auto tail_coords = Input::button_to_coords(tail);
|
||||
assert(pos_coords.x == tail_coords.x or pos_coords.y == tail_coords.y);
|
||||
if (pos_coords.x == tail_coords.x) {
|
||||
if (tail_coords.y > pos_coords.y) {
|
||||
return 2;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
if (tail_coords.x > pos_coords.x) {
|
||||
return 1;
|
||||
} else {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
Position mirror_horizontally() const;
|
||||
Position mirror_vertically() const;
|
||||
Position rotate_90_clockwise() const;
|
||||
Position rotate_90_counter_clockwise() const;
|
||||
Position rotate_180() const;
|
||||
private:
|
||||
std::uint64_t x;
|
||||
std::uint64_t y;
|
||||
};
|
||||
}
|
||||
|
||||
class TapNote {
|
||||
public:
|
||||
TapNote(sf::Time time, Position position);
|
||||
sf::Time get_time() const;
|
||||
Position get_position() const;
|
||||
|
||||
bool operator==(const TapNote&) const = default;
|
||||
friend std::ostream& operator<<(std::ostream& out, const TapNote& t);
|
||||
|
||||
nlohmann::ordered_json dump_to_memon_1_0_0() const;
|
||||
|
||||
TapNote mirror_horizontally() const;
|
||||
TapNote mirror_vertically() const;
|
||||
TapNote rotate_90_clockwise() const;
|
||||
TapNote rotate_90_counter_clockwise() const;
|
||||
TapNote rotate_180() const;
|
||||
TapNote quantize(unsigned int snap) const;
|
||||
private:
|
||||
sf::Time time;
|
||||
Position position;
|
||||
};
|
||||
|
||||
class LongNote {
|
||||
public:
|
||||
LongNote(sf::Time time, Position position, sf::Time duration, Position tail_tip);
|
||||
|
||||
sf::Time get_time() const;
|
||||
Position get_position() const;
|
||||
sf::Time get_end() const;
|
||||
sf::Time get_duration() const;
|
||||
Position get_tail_tip() const;
|
||||
std::uint64_t get_tail_length() const;
|
||||
std::uint64_t get_tail_angle() const;
|
||||
|
||||
bool operator==(const LongNote&) const = default;
|
||||
friend std::ostream& operator<<(std::ostream& out, const LongNote& l);
|
||||
|
||||
nlohmann::ordered_json dump_to_memon_1_0_0() const;
|
||||
int tail_as_6_notation() const;
|
||||
|
||||
LongNote mirror_horizontally() const;
|
||||
LongNote mirror_vertically() const;
|
||||
LongNote rotate_90_clockwise() const;
|
||||
LongNote rotate_90_counter_clockwise() const;
|
||||
LongNote rotate_180() const;
|
||||
LongNote quantize(unsigned int snap) const;
|
||||
private:
|
||||
sf::Time time;
|
||||
Position position;
|
||||
sf::Time duration;
|
||||
Position tail_tip;
|
||||
};
|
||||
|
||||
Position convert_legacy_memon_tail_index_to_position(const Position& pos, std::uint64_t tail_index);
|
||||
Position convert_6_notation_to_position(const Position& pos, std::uint64_t tail_index);
|
||||
|
||||
class Note {
|
||||
public:
|
||||
template<typename ...Ts>
|
||||
Note(Ts&&... Args) requires (std::constructible_from<std::variant<TapNote, LongNote>, Ts...>) : note(std::forward<Ts>(Args)...) {};
|
||||
sf::Time get_time() const;
|
||||
std::pair<sf::Time, sf::Time> get_time_bounds() const;
|
||||
Position get_position() const;
|
||||
sf::Time get_end() const;
|
||||
|
||||
template<typename T>
|
||||
auto visit(T& visitor) const {return std::visit(visitor, this->note);};
|
||||
|
||||
bool operator==(const Note&) const = default;
|
||||
friend std::ostream& operator<<(std::ostream& out, const Note& n);
|
||||
|
||||
nlohmann::ordered_json dump_to_memon_1_0_0() const;
|
||||
|
||||
static Note load_from_memon_1_0_0(
|
||||
const nlohmann::json& json,
|
||||
std::uint64_t resolution = 240
|
||||
);
|
||||
|
||||
static Note load_from_memon_legacy(
|
||||
const nlohmann::json& json,
|
||||
std::uint64_t resolution
|
||||
);
|
||||
|
||||
Note mirror_horizontally() const;
|
||||
Note mirror_vertically() const;
|
||||
Note rotate_90_clockwise() const;
|
||||
Note rotate_90_counter_clockwise() const;
|
||||
Note rotate_180() const;
|
||||
Note quantize(unsigned int snap) const;
|
||||
private:
|
||||
std::variant<TapNote, LongNote> note;
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<Data::Position>: formatter<string_view> {
|
||||
// parse is inherited from formatter<string_view>.
|
||||
template <typename FormatContext>
|
||||
auto format(const Data::Position& pos, FormatContext& ctx) {
|
||||
return format_to(
|
||||
ctx.out(),
|
||||
"(x: {}, y: {})",
|
||||
pos.get_x(),
|
||||
pos.get_y()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<Data::TapNote>: formatter<string_view> {
|
||||
// parse is inherited from formatter<string_view>.
|
||||
template <typename FormatContext>
|
||||
auto format(const Data::TapNote& t, FormatContext& ctx) {
|
||||
return format_to(
|
||||
ctx.out(),
|
||||
"TapNote(time: {}, position: {})",
|
||||
t.get_time(),
|
||||
t.get_position()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<Data::LongNote>: formatter<string_view> {
|
||||
// parse is inherited from formatter<string_view>.
|
||||
template <typename FormatContext>
|
||||
auto format(const Data::LongNote& l, FormatContext& ctx) {
|
||||
return format_to(
|
||||
ctx.out(),
|
||||
"LongNote(time: {}, position: {}, duration: {}, tail tip: {})",
|
||||
l.get_time(),
|
||||
l.get_position(),
|
||||
l.get_duration(),
|
||||
l.get_tail_tip()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<Data::Note>: formatter<string_view> {
|
||||
// parse is inherited from formatter<string_view>.
|
||||
template <typename FormatContext>
|
||||
auto format(const Data::Note& n, FormatContext& ctx) {
|
||||
const auto visitor = [&](const auto& n){return format_to(ctx.out(), "{}", n);};
|
||||
return n.visit(visitor);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
#include <set>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "Note.hpp"
|
||||
@ -66,7 +65,7 @@ namespace Data {
|
||||
int get_score() const override;
|
||||
Rating get_rating() const override;
|
||||
void update(Judgement j) override;
|
||||
int get_judgement_counts(Judgement j) const;
|
||||
int get_judgement_counts(Judgement j) const override;
|
||||
private:
|
||||
int shutter = 0;
|
||||
int shutter_incerment_2x;
|
||||
|
@ -144,114 +144,132 @@ namespace Data {
|
||||
|
||||
void MemonSong::load_from_memon_1_0_0(const nlohmann::json& json) {
|
||||
if (json.contains("metadata")) {
|
||||
title = json.value("title", "");
|
||||
artist = json.value("artist", "");
|
||||
if (json.contains("audio") and json["audio"].is_string()) {
|
||||
audio = utf8_encoded_string_to_path(json["audio"].get<std::string>());
|
||||
auto metadata = json["metadata"];
|
||||
title = metadata.value("title", "");
|
||||
artist = metadata.value("artist", "");
|
||||
if (metadata.contains("audio")) {
|
||||
audio = utf8_encoded_string_to_path(metadata["audio"].get<std::string>());
|
||||
}
|
||||
if (json.contains("jacket") and json["jacket"].is_string()) {
|
||||
cover = utf8_encoded_string_to_path(json["jacket"].get<std::string>());
|
||||
if (metadata.contains("jacket")) {
|
||||
cover = utf8_encoded_string_to_path(metadata["jacket"].get<std::string>());
|
||||
}
|
||||
if (json.contains("preview")) {
|
||||
if (json["preview"].is_string()) {
|
||||
metadata.use_preview_file = true;
|
||||
json["preview"].get_to(metadata.preview_file);
|
||||
} else if (json["preview"].is_object()) {
|
||||
metadata.use_preview_file = false;
|
||||
metadata.preview_loop.start = load_as_decimal(json["preview"]["start"]);
|
||||
metadata.preview_loop.duration = load_as_decimal(json["preview"]["duration"]);
|
||||
if (metadata.contains("preview")) {
|
||||
if (metadata["preview"].is_string()) {
|
||||
preview = utf8_encoded_string_to_path(metadata["preview"].get<std::string>());
|
||||
} else if (metadata["preview"].is_object()) {
|
||||
preview = sf::Music::TimeSpan{
|
||||
sf::milliseconds((load_as_decimal(metadata["preview"]["start"]) * 1000).i32()),
|
||||
sf::milliseconds((load_as_decimal(metadata["preview"]["duration"]) * 1000).i32()),
|
||||
};
|
||||
}
|
||||
}
|
||||
return metadata;
|
||||
}
|
||||
nlohmann::json song_timing_json = R"(
|
||||
{"offset": "0", "resolution": 240, "bpms": [{"beat": 0, "bpm": "120"}]}
|
||||
)"_json;
|
||||
if (memon.contains("timing")) {
|
||||
song_timing_json.update(memon["timing"]);
|
||||
chart_levels.clear();
|
||||
for (const auto& [dif, chart_json] : json["data"].items()) {
|
||||
if (chart_json.contains("level")) {
|
||||
chart_levels[dif] = load_as_decimal(chart_json["level"]).u32();
|
||||
} else {
|
||||
chart_levels[dif] = 0;
|
||||
}
|
||||
}
|
||||
const auto song_timing = std::make_shared<Timing>(Timing::load_from_memon_1_0_0(song_timing_json));
|
||||
const auto hakus = load_hakus(song_timing_json);
|
||||
Song song{
|
||||
.charts = {},
|
||||
.metadata = metadata,
|
||||
.timing = song_timing,
|
||||
.hakus = hakus
|
||||
};
|
||||
for (const auto& [dif, chart_json] : memon["data"].items()) {
|
||||
const auto chart = Chart::load_from_memon_1_0_0(chart_json, song_timing_json);
|
||||
song.charts[dif] = chart;
|
||||
}
|
||||
return song;
|
||||
};
|
||||
|
||||
void MemonSong::load_from_memon_0_3_0(const nlohmann::json& memon) {
|
||||
const auto json_metadata = memon["metadata"];
|
||||
const auto metadata = Metadata::load_from_memon_0_3_0(memon["metadata"]);
|
||||
const auto timing = std::make_shared<Timing>(Timing::load_from_memon_legacy(json_metadata));
|
||||
Song song{
|
||||
.charts = {},
|
||||
.metadata = metadata,
|
||||
.timing = timing,
|
||||
.hakus = {}
|
||||
};
|
||||
for (const auto& [dif, chart_json] : memon["data"].items()) {
|
||||
const auto chart = Chart::load_from_memon_legacy(chart_json);
|
||||
song.charts[dif] = chart;
|
||||
const auto metadata = memon["metadata"];
|
||||
title = metadata["song title"].get<std::string>();
|
||||
artist = metadata["artist"].get<std::string>();
|
||||
if (metadata.contains("music path")) {
|
||||
audio = utf8_encoded_string_to_path(metadata["music path"].get<std::string>());
|
||||
}
|
||||
if (metadata.contains("album cover path")) {
|
||||
cover = utf8_encoded_string_to_path(metadata["album cover path"].get<std::string>());
|
||||
}
|
||||
if (metadata.contains("preview")) {
|
||||
preview = sf::Music::TimeSpan{
|
||||
sf::milliseconds((load_as_decimal(metadata["preview"]["position"]) * 1000).i32()),
|
||||
sf::milliseconds((load_as_decimal(metadata["preview"]["length"]) * 1000).i32()),
|
||||
};
|
||||
}
|
||||
if (metadata.contains("preview path")) {
|
||||
preview = utf8_encoded_string_to_path(metadata["preview path"].get<std::string>());
|
||||
}
|
||||
chart_levels.clear();
|
||||
for (const auto& [dif, chart_json] : memon["data"].items()) {
|
||||
chart_levels[dif] = chart_json["level"].get<unsigned int>();
|
||||
}
|
||||
return song;
|
||||
};
|
||||
|
||||
void MemonSong::load_from_memon_0_2_0(const nlohmann::json& memon) {
|
||||
const auto json_metadata = memon["metadata"];
|
||||
const auto metadata = Metadata::load_from_memon_0_2_0(memon["metadata"]);
|
||||
const auto timing = std::make_shared<Timing>(Timing::load_from_memon_legacy(json_metadata));
|
||||
Song song{
|
||||
.charts = {},
|
||||
.metadata = metadata,
|
||||
.timing = timing,
|
||||
.hakus = {}
|
||||
};
|
||||
for (const auto& [dif, chart_json] : memon["data"].items()) {
|
||||
const auto chart = Chart::load_from_memon_legacy(chart_json);
|
||||
song.charts[dif] = chart;
|
||||
const auto metadata = memon["metadata"];
|
||||
title = metadata["song title"].get<std::string>();
|
||||
artist = metadata["artist"].get<std::string>();
|
||||
{
|
||||
const auto music_path = metadata["music path"].get<std::string>();
|
||||
if (not music_path.empty()) {
|
||||
audio = utf8_encoded_string_to_path(music_path);
|
||||
}
|
||||
}
|
||||
{
|
||||
const auto album_cover_path = metadata["album cover path"].get<std::string>();
|
||||
if (not album_cover_path.empty()) {
|
||||
cover = utf8_encoded_string_to_path(album_cover_path);
|
||||
}
|
||||
}
|
||||
if (metadata.contains("preview")) {
|
||||
preview = sf::Music::TimeSpan{
|
||||
sf::milliseconds((load_as_decimal(metadata["preview"]["position"]) * 1000).i32()),
|
||||
sf::milliseconds((load_as_decimal(metadata["preview"]["length"]) * 1000).i32()),
|
||||
};
|
||||
}
|
||||
chart_levels.clear();
|
||||
for (const auto& [dif, chart_json] : memon["data"].items()) {
|
||||
chart_levels[dif] = chart_json["level"].get<unsigned int>();
|
||||
}
|
||||
return song;
|
||||
};
|
||||
|
||||
void MemonSong::load_from_memon_0_1_0(const nlohmann::json& memon) {
|
||||
const auto json_metadata = memon["metadata"];
|
||||
const auto metadata = Metadata::load_from_memon_0_1_0(memon["metadata"]);
|
||||
const auto timing = std::make_shared<Timing>(Timing::load_from_memon_legacy(json_metadata));
|
||||
Song song{
|
||||
.charts = {},
|
||||
.metadata = metadata,
|
||||
.timing = timing,
|
||||
.hakus = {}
|
||||
};
|
||||
for (const auto& [dif, chart_json] : memon["data"].items()) {
|
||||
const auto chart = Chart::load_from_memon_legacy(chart_json);
|
||||
song.charts[dif] = chart;
|
||||
const auto metadata = memon["metadata"];
|
||||
title = metadata["song title"].get<std::string>();
|
||||
artist = metadata["artist"].get<std::string>();
|
||||
{
|
||||
const auto music_path = metadata["music path"].get<std::string>();
|
||||
if (not music_path.empty()) {
|
||||
audio = utf8_encoded_string_to_path(music_path);
|
||||
}
|
||||
}
|
||||
{
|
||||
const auto album_cover_path = metadata["album cover path"].get<std::string>();
|
||||
if (not album_cover_path.empty()) {
|
||||
cover = utf8_encoded_string_to_path(album_cover_path);
|
||||
}
|
||||
}
|
||||
chart_levels.clear();
|
||||
for (const auto& [dif, chart_json] : memon["data"].items()) {
|
||||
chart_levels[dif] = chart_json["level"].get<unsigned int>();
|
||||
}
|
||||
return song;
|
||||
};
|
||||
|
||||
void MemonSong::load_from_memon_legacy(const nlohmann::json& memon) {
|
||||
const auto json_metadata = memon["metadata"];
|
||||
const auto metadata = Metadata::load_from_memon_legacy(memon["metadata"]);
|
||||
const auto timing = std::make_shared<Timing>(Timing::load_from_memon_legacy(json_metadata));
|
||||
Song song{
|
||||
.charts = {},
|
||||
.metadata = metadata,
|
||||
.timing = timing,
|
||||
.hakus = {}
|
||||
};
|
||||
const auto metadata = memon["metadata"];
|
||||
title = metadata["song title"].get<std::string>();
|
||||
artist = metadata["artist"].get<std::string>();
|
||||
{
|
||||
const auto music_path = metadata["music path"].get<std::string>();
|
||||
if (not music_path.empty()) {
|
||||
audio = utf8_encoded_string_to_path(music_path);
|
||||
}
|
||||
}
|
||||
{
|
||||
const auto jacket_path = metadata["jacket path"].get<std::string>();
|
||||
if (not jacket_path.empty()) {
|
||||
cover = utf8_encoded_string_to_path(jacket_path);
|
||||
}
|
||||
}
|
||||
chart_levels.clear();
|
||||
for (const auto& chart_json : memon["data"]) {
|
||||
const auto dif = chart_json["dif_name"].get<std::string>();
|
||||
const auto chart = Chart::load_from_memon_legacy(chart_json);
|
||||
song.charts[dif] = chart;
|
||||
chart_levels[dif] = chart_json["level"].get<unsigned int>();
|
||||
}
|
||||
return song;
|
||||
};
|
||||
|
||||
std::optional<Chart> MemonSong::load_chart(const std::string& difficulty) const {
|
||||
|
@ -36,7 +36,7 @@ namespace Data {
|
||||
std::optional<std::filesystem::path> cover;
|
||||
// Path the the audio file
|
||||
std::optional<std::filesystem::path> audio;
|
||||
std::optional<sf::Music::TimeSpan> preview;
|
||||
std::optional<std::variant<sf::Music::TimeSpan, std::filesystem::path>> preview;
|
||||
// Mapping from chart difficulty (BSC, ADV, EXT ...) to the numeric level,
|
||||
std::map<std::string, unsigned int, cmp_dif_name> chart_levels;
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
sources += files(
|
||||
'Chart.cpp',
|
||||
'GradedNote.cpp',
|
||||
'Note.cpp',
|
||||
'Preferences.cpp',
|
||||
'Score.cpp',
|
||||
'Song.cpp',
|
||||
|
@ -17,6 +17,8 @@ decimal::Decimal load_as_decimal(const nlohmann::json& j) {
|
||||
return decimal::Decimal{j.get<std::uint64_t>()};
|
||||
} else if (j.is_number_integer()) {
|
||||
return decimal::Decimal{j.get<std::int64_t>()};
|
||||
} else if (j.is_number_float()) {
|
||||
return decimal::Decimal{fmt::format("{}", j.get<double>())};
|
||||
} else {
|
||||
throw std::invalid_argument(fmt::format(
|
||||
"Found a json value of unexpected kind when trying to read"
|
||||
|
23
src/Toolkit/VariantVisitor.hpp
Normal file
23
src/Toolkit/VariantVisitor.hpp
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
Dark template magic from https://www.modernescpp.com/index.php/visiting-a-std-variant-with-the-overload-pattern
|
||||
|
||||
Usage :
|
||||
|
||||
std::variant<char, int, float> var = 2017;
|
||||
|
||||
auto TypeOfIntegral = VariantVisitor {
|
||||
[](char) { return "char"; },
|
||||
[](int) { return "int"; },
|
||||
[](auto) { return "unknown type"; },
|
||||
};
|
||||
|
||||
std::visit(TypeOfIntegral, var);
|
||||
|
||||
*/
|
||||
template<typename ... Ts>
|
||||
struct VariantVisitor : Ts ... {
|
||||
using Ts::operator() ...;
|
||||
};
|
||||
template<class... Ts> VariantVisitor(Ts...) -> VariantVisitor<Ts...>;
|
Loading…
x
Reference in New Issue
Block a user