1
0
mirror of synced 2025-02-02 04:17:54 +01:00

WIP memon chart parsing

This commit is contained in:
Stepland 2023-10-14 01:51:52 +02:00
parent f7ef5e9179
commit ec42f68979
8 changed files with 728 additions and 155 deletions

415
src/Data/Note.cpp Normal file
View 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);
}
}

View File

@ -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);
}
};

View File

@ -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;

View File

@ -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 {

View File

@ -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;

View File

@ -1,6 +1,7 @@
sources += files(
'Chart.cpp',
'GradedNote.cpp',
'Note.cpp',
'Preferences.cpp',
'Score.cpp',
'Song.cpp',

View File

@ -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"

View 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...>;