WIP memon parsing
This commit is contained in:
parent
c09b5d49c4
commit
8e3a05b22d
22684
include/memon/json.hpp
22684
include/memon/json.hpp
File diff suppressed because it is too large
Load Diff
@ -1,362 +0,0 @@
|
||||
/*
|
||||
memoncpp □□□■——◁
|
||||
v0.2.0
|
||||
https://github.com/Stepland/memoncpp
|
||||
https://github.com/Stepland/memon
|
||||
*/
|
||||
|
||||
#ifndef MEMON_HPP_
|
||||
#define MEMON_HPP_
|
||||
|
||||
#include <cassert>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
#include "json.hpp"
|
||||
|
||||
namespace stepland {
|
||||
|
||||
class note {
|
||||
|
||||
public:
|
||||
|
||||
note(int t_position, int t_timing, int t_length = 0, int t_tail_position = 0) {
|
||||
if (t_timing<0) {
|
||||
throw std::runtime_error(
|
||||
"Tried creating a note with negative timing : "
|
||||
+ std::to_string(t_timing)
|
||||
);
|
||||
}
|
||||
if (!(t_position>=0 and t_position<=15)) {
|
||||
throw std::runtime_error(
|
||||
"Tried creating a note with invalid position : "
|
||||
+ std::to_string(t_position)
|
||||
);
|
||||
}
|
||||
if (t_length<0) {
|
||||
throw std::runtime_error(
|
||||
"Tried creating a note with invalid length : "
|
||||
+ std::to_string(t_length)
|
||||
);
|
||||
}
|
||||
if (t_length > 0) {
|
||||
if (t_tail_position < 0 or t_tail_position > 11 or !tail_pos_correct(t_position, t_tail_position)) {
|
||||
throw std::runtime_error(
|
||||
"Tried creating a long note with invalid tail position : "
|
||||
+ std::to_string(t_tail_position)
|
||||
);
|
||||
}
|
||||
}
|
||||
this->timing = t_timing;
|
||||
this->pos = t_position;
|
||||
this->length = t_length;
|
||||
this->tail_pos = t_tail_position;
|
||||
};
|
||||
|
||||
static bool tail_pos_correct(int note_position, int tail_position) {
|
||||
assert((note_position >= 0 and note_position <= 15));
|
||||
assert((tail_position >= 0 and tail_position <= 11));
|
||||
int x = note_position%4;
|
||||
int y = note_position/4;
|
||||
int dx = 0;
|
||||
int dy = 0;
|
||||
// Vertical
|
||||
if (tail_position%2 == 0) {
|
||||
// Going up
|
||||
if ((tail_position/2)%2 == 0) {
|
||||
dy = -(tail_position/4 + 1);
|
||||
// Going down
|
||||
} else {
|
||||
dy = tail_position/4 +1;
|
||||
}
|
||||
// Horizontal
|
||||
} else {
|
||||
// Going right
|
||||
if ((tail_position/2)%2 == 0) {
|
||||
dx = tail_position/4 + 1;
|
||||
// Going left
|
||||
} else {
|
||||
dx = -(tail_position/4 + 1);
|
||||
}
|
||||
}
|
||||
return ((0 <= x+dx) and (x+dx <= 4)) and ((0 <= y+dy) and (y+dy <= 4));
|
||||
};
|
||||
bool operator==(const note &rhs) const {
|
||||
return timing == rhs.timing && pos == rhs.pos;
|
||||
};
|
||||
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 pos < rhs.pos;
|
||||
};
|
||||
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);
|
||||
};
|
||||
int get_timing() const {return timing;};
|
||||
int get_pos() const {return pos;};
|
||||
int get_length() const {return length;};
|
||||
int get_tail_pos() const {return tail_pos;};
|
||||
|
||||
private:
|
||||
|
||||
int timing;
|
||||
int pos;
|
||||
int length;
|
||||
int tail_pos;
|
||||
|
||||
};
|
||||
|
||||
struct chart {
|
||||
int level;
|
||||
std::set<note> notes;
|
||||
int resolution;
|
||||
};
|
||||
|
||||
struct compare_dif_names {
|
||||
const std::map<std::string,int> names = {{"BSC", 0}, {"ADV", 1}, {"EXT", 2}};
|
||||
bool operator()(const std::string& a, const std::string& b) const {
|
||||
bool a_in_names = names.find(a) != names.end();
|
||||
bool b_in_names = names.find(b) != names.end();
|
||||
if (a_in_names) {
|
||||
if (b_in_names) {
|
||||
return names.at(a) < names.at(b);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (b_in_names) {
|
||||
return false;
|
||||
} else {
|
||||
return a < b;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
* Represents a .memon file : several charts and some metadata
|
||||
*/
|
||||
struct memon {
|
||||
|
||||
struct preview_loop {
|
||||
float position;
|
||||
float duration;
|
||||
};
|
||||
|
||||
std::map<std::string,chart,compare_dif_names> charts;
|
||||
std::string song_title;
|
||||
std::string artist;
|
||||
std::string music_path;
|
||||
std::string album_cover_path;
|
||||
std::optional<preview_loop> preview;
|
||||
float BPM;
|
||||
float offset;
|
||||
|
||||
friend std::istream& operator>>(std::istream& file, memon& m) {
|
||||
nlohmann::json j;
|
||||
file >> j;
|
||||
// Basic checks
|
||||
if (j.find("version") == j.end()) {
|
||||
m.load_from_memon_fallback(j);
|
||||
return file;
|
||||
}
|
||||
if (not j.at("version").is_string()) {
|
||||
throw std::invalid_argument("Unexpected version field : "+j.at("version").dump());
|
||||
}
|
||||
|
||||
auto version = j.at("version").get<std::string>();
|
||||
if (version == "0.1.0") {
|
||||
m.load_from_memon_v0_1_0(j);
|
||||
} else if (version == "0.2.0") {
|
||||
m.load_from_memon_v0_2_0(j);
|
||||
} else {
|
||||
throw std::invalid_argument("Unsupported .memon version : "+version);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
/*
|
||||
* v0.2.0
|
||||
* - "preview" as been added as an optional metadata key holding the song
|
||||
* preview loop info, it's an object with two required fields :
|
||||
* - "position" : time at which loop starts (in floating point seconds)
|
||||
* - "length" : loop length (in floating point seconds)
|
||||
*/
|
||||
void load_from_memon_v0_2_0(nlohmann::json memon_json) {
|
||||
|
||||
auto metadata = memon_json.at("metadata");
|
||||
if (not metadata.is_object()) {
|
||||
throw std::invalid_argument("metadata fields is not an object");
|
||||
}
|
||||
|
||||
this->song_title = metadata.at("song title").get<std::string>();
|
||||
this->artist = metadata.at("artist").get<std::string>();
|
||||
this->music_path = metadata.at("music path").get<std::string>();
|
||||
this->album_cover_path = metadata.at("album cover path").get<std::string>();
|
||||
this->BPM = metadata.at("BPM").get<float>();
|
||||
this->offset = metadata.at("offset").get<float>();
|
||||
|
||||
// "preview" is optional in v0.2.0, it missing is NOT an error
|
||||
if (metadata.find("preview") != metadata.end()) {
|
||||
auto preview_json = metadata.at("preview");
|
||||
float raw_position = preview_json.at("position").get<float>();
|
||||
assert((raw_position >= 0.f));
|
||||
float raw_duration = preview_json.at("duration").get<float>();
|
||||
assert((raw_duration >= 0.f));
|
||||
this->preview.emplace();
|
||||
this->preview->position = raw_position;
|
||||
this->preview->position = raw_duration;
|
||||
}
|
||||
|
||||
if (not memon_json.at("data").is_object()) {
|
||||
throw std::invalid_argument("data field is not an object");
|
||||
}
|
||||
|
||||
for (auto& [dif_name, chart_json] : memon_json.at("data").items()) {
|
||||
|
||||
chart new_chart;
|
||||
new_chart.level = chart_json.at("level").get<int>();
|
||||
new_chart.resolution = chart_json.at("resolution").get<int>();
|
||||
assert((new_chart.resolution > 0));
|
||||
|
||||
if (not chart_json.at("notes").is_array()) {
|
||||
throw std::invalid_argument(dif_name+" chart notes field must be an array");
|
||||
}
|
||||
|
||||
for (auto& note : chart_json.at("notes")) {
|
||||
new_chart.notes.emplace(
|
||||
note.at("n").get<int>(),
|
||||
note.at("t").get<int>(),
|
||||
note.at("l").get<int>(),
|
||||
note.at("p").get<int>()
|
||||
);
|
||||
}
|
||||
this->charts[dif_name] = new_chart;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* v0.1.0
|
||||
* - "data" is now an object mapping a difficulty name to a chart,
|
||||
* this way the difficulty names are guaranteed to be unique
|
||||
* - "jacket path" is now "album cover path" because engrish much ?
|
||||
*/
|
||||
void load_from_memon_v0_1_0(nlohmann::json memon_json) {
|
||||
|
||||
auto metadata = memon_json.at("metadata");
|
||||
if (not metadata.is_object()) {
|
||||
throw std::invalid_argument("metadata fields is not an object");
|
||||
}
|
||||
|
||||
this->song_title = metadata.at("song title").get<std::string>();
|
||||
this->artist = metadata.at("artist").get<std::string>();
|
||||
this->music_path = metadata.at("music path").get<std::string>();
|
||||
this->album_cover_path = metadata.at("album cover path").get<std::string>();
|
||||
this->BPM = metadata.at("BPM").get<float>();
|
||||
this->offset = metadata.at("offset").get<float>();
|
||||
|
||||
if (not memon_json.at("data").is_object()) {
|
||||
throw std::invalid_argument("data field is not an object");
|
||||
}
|
||||
|
||||
for (auto& [dif_name, chart_json] : memon_json.at("data").items()) {
|
||||
|
||||
chart new_chart;
|
||||
new_chart.level = chart_json.at("level").get<int>();
|
||||
new_chart.resolution = chart_json.at("resolution").get<int>();
|
||||
assert((new_chart.resolution > 0));
|
||||
|
||||
if (not chart_json.at("notes").is_array()) {
|
||||
throw std::invalid_argument(dif_name+" chart notes field must be an array");
|
||||
}
|
||||
|
||||
for (auto& note : chart_json.at("notes")) {
|
||||
new_chart.notes.emplace(
|
||||
note.at("n").get<int>(),
|
||||
note.at("t").get<int>(),
|
||||
note.at("l").get<int>(),
|
||||
note.at("p").get<int>()
|
||||
);
|
||||
}
|
||||
this->charts[dif_name] = new_chart;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Fallback parser
|
||||
* Respects the old, unversionned schema, with notable quirks :
|
||||
* - "data" is an array of charts, each with a difficulty name
|
||||
* - the album cover path field is named "jacket path"
|
||||
*/
|
||||
void load_from_memon_fallback(nlohmann::json memon_json) {
|
||||
|
||||
auto metadata = memon_json.at("metadata");
|
||||
if (not metadata.is_object()) {
|
||||
throw std::invalid_argument("metadata fields is not an object");
|
||||
}
|
||||
|
||||
this->song_title = metadata.at("song title").get<std::string>();
|
||||
this->artist = metadata.at("artist").get<std::string>();
|
||||
this->music_path = metadata.at("music path").get<std::string>();
|
||||
this->album_cover_path = metadata.at("jacket path").get<std::string>();
|
||||
this->BPM = metadata.value("BPM",120.f);
|
||||
this->offset = metadata.value("offset",0.f);
|
||||
|
||||
if (not memon_json.at("data").is_array()) {
|
||||
throw std::invalid_argument("data field is not an array");
|
||||
}
|
||||
|
||||
for (auto& chart_json : memon_json.at("data")) {
|
||||
chart new_chart;
|
||||
new_chart.level = chart_json.value("level", 0);
|
||||
new_chart.resolution = chart_json.at("resolution").get<int>();
|
||||
std::string dif_name = chart_json.at("dif_name").get<std::string>();
|
||||
|
||||
if (this->charts.find(dif_name) != this->charts.end()) {
|
||||
throw std::invalid_argument("duplicate chart name in memon : "+dif_name);
|
||||
}
|
||||
if (not chart_json.at("notes").is_array()) {
|
||||
throw std::invalid_argument(dif_name+" chart notes field has bad structure");
|
||||
}
|
||||
|
||||
for (auto& note : chart_json.at("notes")) {
|
||||
if (
|
||||
not (
|
||||
note.is_object()
|
||||
and note.find("n") != note.end()
|
||||
and note.find("t") != note.end()
|
||||
and note.find("l") != note.end()
|
||||
and note.find("p") != note.end()
|
||||
)
|
||||
) {
|
||||
throw std::invalid_argument(dif_name+" chart has notes with bad structure");
|
||||
}
|
||||
|
||||
new_chart.notes.emplace(
|
||||
note.at("n").get<int>(),
|
||||
note.at("t").get<int>(),
|
||||
note.at("l").get<int>(),
|
||||
note.at("p").get<int>()
|
||||
);
|
||||
}
|
||||
this->charts[dif_name] = new_chart;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endif /* MEMON_HPP_ */
|
@ -22,7 +22,8 @@ dependencies = [
|
||||
cc.find_library('atomic'),
|
||||
dependency('nowide'),
|
||||
dependency('nlohmann_json'),
|
||||
dependency('fmt')
|
||||
dependency('fmt'),
|
||||
dependency('mpdec++', fallback: ['mpdecimal', 'mpdecpp_dep'], static: true)
|
||||
]
|
||||
|
||||
if host_machine.system() == 'linux'
|
||||
|
@ -3,7 +3,6 @@
|
||||
#include <cstddef>
|
||||
#include <set>
|
||||
|
||||
#include <memon/memon.hpp>
|
||||
#include <SFML/System/Time.hpp>
|
||||
|
||||
#include "../Input/Buttons.hpp"
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include <cassert>
|
||||
|
||||
#include <SFML/System/Time.hpp>
|
||||
#include <cmath>
|
||||
|
||||
#include "../Input/Buttons.hpp"
|
||||
|
||||
|
@ -10,10 +10,10 @@
|
||||
#include <stdexcept>
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <memon/memon.hpp>
|
||||
|
||||
#include "../Toolkit/UTF8Strings.hpp"
|
||||
#include "../Toolkit/UTF8SFMLRedefinitions.hpp"
|
||||
#include "src/Toolkit/JSONDecimalHandling.hpp"
|
||||
|
||||
namespace Data {
|
||||
|
||||
@ -35,7 +35,7 @@ namespace Data {
|
||||
|
||||
std::optional<std::filesystem::path> Song::full_cover_path() const {
|
||||
if (cover) {
|
||||
return folder/cover.value();
|
||||
return folder / cover.value();
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
@ -43,7 +43,7 @@ namespace Data {
|
||||
|
||||
std::optional<std::filesystem::path> Song::full_audio_path() const {
|
||||
if (audio) {
|
||||
return folder/audio.value();
|
||||
return folder / audio.value();
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
@ -76,7 +76,7 @@ namespace Data {
|
||||
}
|
||||
|
||||
TimeBounds SongDifficulty::get_time_bounds() const {
|
||||
auto chart = song.get_chart(difficulty);
|
||||
auto chart = song.load_chart(difficulty);
|
||||
if (not chart) {
|
||||
throw std::invalid_argument("Song "+song.title+" has no '"+difficulty+"' chart");
|
||||
}
|
||||
@ -90,36 +90,171 @@ namespace Data {
|
||||
return time_bounds;
|
||||
}
|
||||
|
||||
MemonSong::MemonSong(const std::filesystem::path& t_memon_path) :
|
||||
memon_path(t_memon_path)
|
||||
MemonSong::MemonSong(const std::filesystem::path& memon_path_) :
|
||||
memon_path(memon_path_)
|
||||
{
|
||||
auto song_folder = t_memon_path.parent_path();
|
||||
folder = song_folder;
|
||||
stepland::memon m;
|
||||
{
|
||||
std::ifstream file(t_memon_path);
|
||||
file >> m;
|
||||
}
|
||||
this->title = m.song_title;
|
||||
this->artist = m.artist;
|
||||
if (not m.album_cover_path.empty()) {
|
||||
this->cover.emplace(m.album_cover_path);
|
||||
}
|
||||
if (not m.music_path.empty()) {
|
||||
this->audio.emplace(m.music_path);
|
||||
}
|
||||
if (m.preview) {
|
||||
this->preview.emplace(
|
||||
sf::seconds(m.preview->position),
|
||||
sf::seconds(m.preview->duration)
|
||||
folder = memon_path.parent_path();
|
||||
nowide::ifstream f{path_to_utf8_encoded_string(memon_path)};
|
||||
if (not f) {
|
||||
fmt::print(
|
||||
"Could not open file {}\n",
|
||||
path_to_utf8_encoded_string(memon_path)
|
||||
);
|
||||
}
|
||||
for (const auto& [difficulty, chart] : m.charts) {
|
||||
this->chart_levels[difficulty] = chart.level;
|
||||
}
|
||||
return;
|
||||
};
|
||||
const auto json = load_json_preserving_decimals(f);
|
||||
load_from_memon(json);
|
||||
}
|
||||
|
||||
std::optional<Chart> MemonSong::get_chart(const std::string& difficulty) const {
|
||||
|
||||
void MemonSong::load_from_memon(const nlohmann::json& memon) {
|
||||
if (not memon.is_object()) {
|
||||
throw std::invalid_argument(
|
||||
"The json file you tried to load does not contain an object "
|
||||
"at the top level. This is required for a json file to be a "
|
||||
"valid memon file."
|
||||
);
|
||||
}
|
||||
if (not memon.contains("version")) {
|
||||
return load_from_memon_legacy(memon);
|
||||
}
|
||||
if (not memon["version"].is_string()) {
|
||||
throw std::invalid_argument(
|
||||
"The json file you tried to load has a 'version' key at the "
|
||||
"top level but its associated value is not a string. This is "
|
||||
"required for a json file to be a valid memon file."
|
||||
);
|
||||
}
|
||||
const auto version = memon["version"].get<std::string>();
|
||||
if (version == "1.0.0") {
|
||||
return load_from_memon_1_0_0(memon);
|
||||
} else if (version == "0.3.0") {
|
||||
return load_from_memon_0_3_0(memon);
|
||||
} else if (version == "0.2.0") {
|
||||
return load_from_memon_0_2_0(memon);
|
||||
} else if (version == "0.1.0") {
|
||||
return load_from_memon_0_1_0(memon);
|
||||
} else {
|
||||
throw std::invalid_argument(fmt::format(
|
||||
"Unknown memon version : {}",
|
||||
version
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
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>());
|
||||
}
|
||||
if (json.contains("jacket") and json["jacket"].is_string()) {
|
||||
cover = utf8_encoded_string_to_path(json["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"]);
|
||||
}
|
||||
}
|
||||
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"]);
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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 = {}
|
||||
};
|
||||
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;
|
||||
}
|
||||
return song;
|
||||
};
|
||||
|
||||
std::optional<Chart> MemonSong::load_chart(const std::string& difficulty) const {
|
||||
stepland::memon m;
|
||||
{
|
||||
std::ifstream file(memon_path);
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <vector>
|
||||
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <SFML/Audio.hpp>
|
||||
|
||||
#include "Chart.hpp"
|
||||
@ -42,7 +43,7 @@ namespace Data {
|
||||
virtual std::optional<std::filesystem::path> full_cover_path() const;
|
||||
virtual std::optional<std::filesystem::path> full_audio_path() const;
|
||||
|
||||
virtual std::optional<Chart> get_chart(const std::string& difficulty) const = 0;
|
||||
virtual std::optional<Chart> load_chart(const std::string& difficulty) const = 0;
|
||||
|
||||
static bool sort_by_title(const Data::Song& a, const Data::Song& b) {
|
||||
return a.title < b.title;
|
||||
@ -54,7 +55,7 @@ namespace Data {
|
||||
const Data::Song& song;
|
||||
const std::string& difficulty;
|
||||
|
||||
std::optional<Chart> get_chart() const {return song.get_chart(difficulty);};
|
||||
std::optional<Chart> load_chart() const {return song.load_chart(difficulty);};
|
||||
|
||||
// Get the total play interval for this chart :
|
||||
// if there's no audio and no notes, returns a one second inteval starting at sf::Time::Zero
|
||||
@ -72,10 +73,39 @@ namespace Data {
|
||||
};
|
||||
|
||||
struct MemonSong : public Song {
|
||||
explicit MemonSong(const std::filesystem::path& memon_path);
|
||||
std::optional<Chart> get_chart(const std::string& difficulty) const;
|
||||
explicit MemonSong(const std::filesystem::path& memon_path_);
|
||||
std::optional<Chart> load_chart(const std::string& difficulty) const;
|
||||
private:
|
||||
std::filesystem::path memon_path;
|
||||
|
||||
/* Read the json file as memon by trying to guess version.
|
||||
Throws various exceptions on error */
|
||||
void load_from_memon(const nlohmann::json& memon);
|
||||
|
||||
/* Read the json file as memon v1.0.0.
|
||||
https://memon-spec.readthedocs.io/en/latest/changelog.html#v1-0-0 */
|
||||
void load_from_memon_1_0_0(const nlohmann::json& memon);
|
||||
|
||||
/* Read the json file as memon v0.3.0.
|
||||
https://memon-spec.readthedocs.io/en/latest/changelog.html#v0-3-0 */
|
||||
void load_from_memon_0_3_0(const nlohmann::json& memon);
|
||||
|
||||
/* Read the json file as memon v0.2.0.
|
||||
https://memon-spec.readthedocs.io/en/latest/changelog.html#v0-2-0 */
|
||||
void load_from_memon_0_2_0(const nlohmann::json& memon);
|
||||
|
||||
/* Read the json file as memon v0.1.0.
|
||||
https://memon-spec.readthedocs.io/en/latest/changelog.html#v0-1-0 */
|
||||
void load_from_memon_0_1_0(const nlohmann::json& memon);
|
||||
|
||||
/* Read the json file as a "legacy" (pre-versionning) memon file.
|
||||
|
||||
Notable quirks of this archaïc schema :
|
||||
- "data" is an *array* of charts
|
||||
- the difficulty name of a chart is stored as "dif_name" in the chart
|
||||
object
|
||||
- the album cover path field is named "jacket path" */
|
||||
void load_from_memon_legacy(const nlohmann::json& memon);
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -44,7 +44,7 @@ namespace Drawables {
|
||||
}
|
||||
|
||||
DensityGraph DensityGraph::from_song_difficulty(const Data::SongDifficulty& sd) {
|
||||
return DensityGraph::from_time_bounds(*sd.song.get_chart(sd.difficulty), sd.get_time_bounds());
|
||||
return DensityGraph::from_time_bounds(*sd.song.load_chart(sd.difficulty), sd.get_time_bounds());
|
||||
}
|
||||
|
||||
DensityGraph DensityGraph::from_time_bounds(const Data::Chart& chart, const Data::TimeBounds& tb) {
|
||||
|
@ -31,12 +31,12 @@ namespace Gameplay {
|
||||
Screen::Screen(const Data::SongDifficulty& t_song_selection, ScreenResources& t_resources) :
|
||||
HoldsResources(t_resources),
|
||||
song_selection(t_song_selection),
|
||||
chart(*t_song_selection.song.get_chart(t_song_selection.difficulty)),
|
||||
chart(*t_song_selection.song.load_chart(t_song_selection.difficulty)),
|
||||
marker(t_resources.shared.get_selected_marker()),
|
||||
ln_marker(t_resources.shared.get_selected_ln_marker()),
|
||||
graded_density_graph(t_resources.shared.density_graphs.blocking_get(t_song_selection), t_song_selection),
|
||||
music_time_to_progression(get_music_time_to_progression_transform(t_song_selection)),
|
||||
score(t_song_selection.song.get_chart(t_song_selection.difficulty)->notes)
|
||||
score(t_song_selection.song.load_chart(t_song_selection.difficulty)->notes)
|
||||
{
|
||||
for (auto&& note : chart.notes) {
|
||||
notes.emplace_back(Data::GradedNote{note});
|
||||
|
@ -111,7 +111,7 @@ namespace MusicSelect {
|
||||
// We should gray out the panel if the currently selected difficulty doesn't exist for this song
|
||||
bool should_be_grayed_out = m_song->chart_levels.find(last_selected_chart) == m_song->chart_levels.end();
|
||||
if (m_song->cover) {
|
||||
auto loaded_texture = shared.covers.async_get(m_song->folder/m_song->cover.value());
|
||||
auto loaded_texture = shared.covers.async_get(m_song->folder / m_song->cover.value());
|
||||
if (loaded_texture) {
|
||||
sf::Sprite cover{*(loaded_texture->texture)};
|
||||
auto alpha = static_cast<std::uint8_t>(
|
||||
|
@ -14,7 +14,7 @@ namespace Results {
|
||||
HoldsResources(t_resources),
|
||||
graded_density_graph(t_graded_density_graph),
|
||||
song_selection(t_song_selection),
|
||||
chart(*t_song_selection.get_chart()),
|
||||
chart(*t_song_selection.load_chart()),
|
||||
score(t_score)
|
||||
{
|
||||
}
|
||||
|
28
src/Toolkit/JSONDecimalHandling.cpp
Normal file
28
src/Toolkit/JSONDecimalHandling.cpp
Normal file
@ -0,0 +1,28 @@
|
||||
#include "JSONDecimalHandling.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
bool json_decimal_parser::number_float(number_float_t /*unused*/, const string_t& val) {
|
||||
string_t copy = val;
|
||||
string(copy);
|
||||
return true;
|
||||
}
|
||||
|
||||
decimal::Decimal load_as_decimal(const nlohmann::json& j) {
|
||||
if (j.is_string()) {
|
||||
return decimal::Decimal{j.get<std::string>()};
|
||||
} else if (j.is_number_unsigned()) {
|
||||
return decimal::Decimal{j.get<std::uint64_t>()};
|
||||
} else if (j.is_number_integer()) {
|
||||
return decimal::Decimal{j.get<std::int64_t>()};
|
||||
} else {
|
||||
throw std::invalid_argument(fmt::format(
|
||||
"Found a json value of unexpected kind when trying to read"
|
||||
"a decimal number : {} (json type: {})",
|
||||
j.dump(),
|
||||
j.type_name()
|
||||
));
|
||||
}
|
||||
}
|
34
src/Toolkit/JSONDecimalHandling.hpp
Normal file
34
src/Toolkit/JSONDecimalHandling.hpp
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <libmpdec++/decimal.hh>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
/*
|
||||
This class allows parsing json files with nlohmann::json while losslessly
|
||||
recovering decimal number literals by storing their original string
|
||||
representation when parsing intead of their float or double conversion
|
||||
*/
|
||||
using sax_parser = nlohmann::detail::json_sax_dom_parser<nlohmann::json>;
|
||||
|
||||
class json_decimal_parser : public sax_parser {
|
||||
public:
|
||||
/*
|
||||
Inherit the constructor because life is too short to write constructors for
|
||||
derived classes
|
||||
*/
|
||||
using sax_parser::json_sax_dom_parser;
|
||||
|
||||
// override float parsing, divert it to parsing the original string
|
||||
// as a json string literal
|
||||
bool number_float(number_float_t /*unused*/, const string_t& val);
|
||||
};
|
||||
|
||||
template<class InputType>
|
||||
nlohmann::json load_json_preserving_decimals(InputType&& input) {
|
||||
nlohmann::json j;
|
||||
json_decimal_parser sax{j};
|
||||
nlohmann::json::sax_parse(std::forward<InputType>(input), &sax);
|
||||
return j;
|
||||
}
|
||||
|
||||
decimal::Decimal load_as_decimal(const nlohmann::json& j);
|
@ -1,6 +1,7 @@
|
||||
sources += files(
|
||||
'EasingFunctions.cpp',
|
||||
'HSL.cpp',
|
||||
'JSONDecimalHandling.cpp',
|
||||
'SFMLHelpers.cpp',
|
||||
'QuickRNG.cpp',
|
||||
'UTF8FileInputStream.cpp',
|
||||
|
13
subprojects/mpdecimal.wrap
Normal file
13
subprojects/mpdecimal.wrap
Normal file
@ -0,0 +1,13 @@
|
||||
[wrap-file]
|
||||
directory = mpdecimal-2.5.1
|
||||
source_url = http://www.bytereef.org/software/mpdecimal/releases/mpdecimal-2.5.1.tar.gz
|
||||
source_filename = mpdecimal-2.5.1.tar.gz
|
||||
source_hash = 9f9cd4c041f99b5c49ffb7b59d9f12d95b683d88585608aa56a6307667b2b21f
|
||||
patch_filename = mpdecimal_2.5.1-3_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/mpdecimal_2.5.1-3/get_patch
|
||||
patch_hash = d16c61eedab8233d49526b59cce2355def0325687b86c7f7ee3c7c92660dc53a
|
||||
wrapdb_version = 2.5.1-3
|
||||
|
||||
[provide]
|
||||
mpdec = mpdec_dep
|
||||
mpdecpp = mpdecpp_dep
|
Loading…
x
Reference in New Issue
Block a user