mirror of
https://gitlab.com/square-game-liberation-front/F.E.I.S.git
synced 2024-11-12 02:00:53 +01:00
Starting to implement memon 1.0.0 saving
This commit is contained in:
parent
b967bc0499
commit
6fe06dfd80
36277
include/json.hpp
36277
include/json.hpp
File diff suppressed because it is too large
Load Diff
33
src/better_beats.cpp
Normal file
33
src/better_beats.cpp
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#include "better_beats.hpp"
|
||||||
|
#include <boost/multiprecision/gmp.hpp>
|
||||||
|
#include "json.hpp"
|
||||||
|
|
||||||
|
bool is_expressible_as_240th(const Fraction& beat) {
|
||||||
|
return (
|
||||||
|
(
|
||||||
|
(240 * boost::multiprecision::numerator(beat))
|
||||||
|
% boost::multiprecision::denominator(beat)
|
||||||
|
) == 0
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
nlohmann::ordered_json beat_to_best_form(const Fraction& beat) {
|
||||||
|
if (is_expressible_as_240th(beat)) {
|
||||||
|
return nlohmann::ordered_json(
|
||||||
|
(240 * boost::multiprecision::numerator(beat))
|
||||||
|
/ boost::multiprecision::denominator(beat)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return beat_to_fraction_tuple(beat);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
nlohmann::ordered_json beat_to_fraction_tuple(const Fraction& beat) {
|
||||||
|
const auto integer_part = static_cast<nlohmann::ordered_json::number_unsigned_t>(beat);
|
||||||
|
const auto remainder = beat % 1;
|
||||||
|
return {
|
||||||
|
integer_part,
|
||||||
|
static_cast<nlohmann::ordered_json::number_unsigned_t>(boost::multiprecision::numerator(remainder)),
|
||||||
|
static_cast<nlohmann::ordered_json::number_unsigned_t>(boost::multiprecision::denominator(remainder)),
|
||||||
|
};
|
||||||
|
};
|
9
src/better_beats.hpp
Normal file
9
src/better_beats.hpp
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <json.hpp>
|
||||||
|
|
||||||
|
#include "special_numeric_types.hpp"
|
||||||
|
|
||||||
|
bool is_expressible_as_240th(const Fraction& beat);
|
||||||
|
nlohmann::ordered_json beat_to_best_form(const Fraction& beat);
|
||||||
|
nlohmann::ordered_json beat_to_fraction_tuple(const Fraction& beat);
|
16
src/better_chart.cpp
Normal file
16
src/better_chart.cpp
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#include "better_chart.hpp"
|
||||||
|
|
||||||
|
#include <json.hpp>
|
||||||
|
|
||||||
|
namespace better {
|
||||||
|
nlohmann::ordered_json Chart::dump_for_memon_1_0_0() const {
|
||||||
|
nlohmann::ordered_json json_chart;
|
||||||
|
if (level) {
|
||||||
|
json_chart["level"] = level->format("f");
|
||||||
|
}
|
||||||
|
if (timing) {
|
||||||
|
json_chart["timing"] = timing->dump_for_memon_1_0_0();
|
||||||
|
}
|
||||||
|
json_chart["notes"] = notes.dump_for_memon_1_0_0();
|
||||||
|
}
|
||||||
|
}
|
23
src/better_chart.hpp
Normal file
23
src/better_chart.hpp
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include <json.hpp>
|
||||||
|
|
||||||
|
#include "better_hakus.hpp"
|
||||||
|
#include "better_notes.hpp"
|
||||||
|
#include "better_timing.hpp"
|
||||||
|
#include "special_numeric_types.hpp"
|
||||||
|
|
||||||
|
namespace better {
|
||||||
|
struct Chart {
|
||||||
|
std::optional<Decimal> level;
|
||||||
|
std::optional<Timing> timing;
|
||||||
|
std::optional<Hakus> hakus;
|
||||||
|
Notes notes;
|
||||||
|
|
||||||
|
nlohmann::ordered_json dump_for_memon_1_0_0() const;
|
||||||
|
};
|
||||||
|
}
|
11
src/better_hakus.cpp
Normal file
11
src/better_hakus.cpp
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#include "better_hakus.hpp"
|
||||||
|
|
||||||
|
#include "better_beats.hpp"
|
||||||
|
|
||||||
|
nlohmann::ordered_json dump_hakus(const Hakus& hakus) {
|
||||||
|
auto j = nlohmann::ordered_json::array();
|
||||||
|
for (const auto& haku : hakus) {
|
||||||
|
j.push_back(beat_to_best_form(haku));
|
||||||
|
}
|
||||||
|
return j;
|
||||||
|
}
|
11
src/better_hakus.hpp
Normal file
11
src/better_hakus.hpp
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include <json.hpp>
|
||||||
|
|
||||||
|
#include "special_numeric_types.hpp"
|
||||||
|
|
||||||
|
using Hakus = std::set<Fraction>;
|
||||||
|
|
||||||
|
nlohmann::ordered_json dump_hakus(const Hakus& hakus);
|
22
src/better_metadata.hpp
Normal file
22
src/better_metadata.hpp
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "special_numeric_types.hpp"
|
||||||
|
|
||||||
|
namespace better {
|
||||||
|
struct PreviewLoop {
|
||||||
|
Decimal start = 0;
|
||||||
|
Decimal duration = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Metadata {
|
||||||
|
std::string title = "";
|
||||||
|
std::string artist = "";
|
||||||
|
std::string audio = "";
|
||||||
|
std::string jacket = "";
|
||||||
|
PreviewLoop preview_loop;
|
||||||
|
std::string preview_file = "";
|
||||||
|
bool use_preview_file = false;
|
||||||
|
};
|
||||||
|
}
|
@ -1,6 +1,9 @@
|
|||||||
#include "better_note.hpp"
|
#include "better_note.hpp"
|
||||||
|
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
|
#include "better_beats.hpp"
|
||||||
|
|
||||||
namespace better {
|
namespace better {
|
||||||
Position::Position(unsigned int index) : x(index % 4), y (index / 4) {
|
Position::Position(unsigned int index) : x(index % 4), y (index / 4) {
|
||||||
if (index > 15) {
|
if (index > 15) {
|
||||||
@ -47,6 +50,13 @@ namespace better {
|
|||||||
return position;
|
return position;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
nlohmann::ordered_json TapNote::dump_for_memon_1_0_0() const {
|
||||||
|
return {
|
||||||
|
{"n", position.index()},
|
||||||
|
{"t", beat_to_best_form(time)}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
LongNote::LongNote(Fraction time, Position position, Fraction duration, Position tail_tip)
|
LongNote::LongNote(Fraction time, Position position, Fraction duration, Position tail_tip)
|
||||||
:
|
:
|
||||||
time(time),
|
time(time),
|
||||||
@ -123,8 +133,24 @@ namespace better {
|
|||||||
return 90;
|
return 90;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
nlohmann::ordered_json LongNote::dump_for_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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto _time_bounds = VariantVisitor {
|
auto _time_bounds = VariantVisitor {
|
||||||
[](const TapNote& t) -> std::pair<Fraction, Fraction> { return {t.get_time(), t.get_time()}; },
|
[](const TapNote& t) -> std::pair<Fraction, Fraction> { return {t.get_time(), t.get_time()}; },
|
||||||
@ -146,5 +172,9 @@ namespace better {
|
|||||||
Fraction Note::get_end() const {
|
Fraction Note::get_end() const {
|
||||||
return this->get_time_bounds().second;
|
return this->get_time_bounds().second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nlohmann::ordered_json Note::dump_for_memon_1_0_0() const {
|
||||||
|
return std::visit([](const auto& n){return n.dump_for_memon_1_0_0();}, this->note);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
|
#include <json.hpp>
|
||||||
|
|
||||||
#include "special_numeric_types.hpp"
|
#include "special_numeric_types.hpp"
|
||||||
#include "variant_visitor.hpp"
|
#include "variant_visitor.hpp"
|
||||||
|
|
||||||
@ -46,6 +48,7 @@ namespace better {
|
|||||||
|
|
||||||
bool operator==(const TapNote&) const = default;
|
bool operator==(const TapNote&) const = default;
|
||||||
|
|
||||||
|
nlohmann::ordered_json dump_for_memon_1_0_0() const;
|
||||||
private:
|
private:
|
||||||
Fraction time;
|
Fraction time;
|
||||||
Position position;
|
Position position;
|
||||||
@ -64,6 +67,9 @@ namespace better {
|
|||||||
unsigned int get_tail_angle() const;
|
unsigned int get_tail_angle() const;
|
||||||
|
|
||||||
bool operator==(const LongNote&) const = default;
|
bool operator==(const LongNote&) const = default;
|
||||||
|
|
||||||
|
nlohmann::ordered_json dump_for_memon_1_0_0() const;
|
||||||
|
int tail_as_6_notation() const;
|
||||||
private:
|
private:
|
||||||
Fraction time;
|
Fraction time;
|
||||||
Position position;
|
Position position;
|
||||||
@ -84,6 +90,8 @@ namespace better {
|
|||||||
auto visit(T& visitor) const {return std::visit(visitor, this->note);};
|
auto visit(T& visitor) const {return std::visit(visitor, this->note);};
|
||||||
|
|
||||||
bool operator==(const Note&) const = default;
|
bool operator==(const Note&) const = default;
|
||||||
|
|
||||||
|
nlohmann::ordered_json dump_for_memon_1_0_0() const;
|
||||||
private:
|
private:
|
||||||
std::variant<TapNote, LongNote> note;
|
std::variant<TapNote, LongNote> note;
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
#include "better_notes.hpp"
|
#include "better_notes.hpp"
|
||||||
|
|
||||||
|
#include <SFML/System/Time.hpp>
|
||||||
|
#include <algorithm>
|
||||||
|
#include "json.hpp"
|
||||||
|
|
||||||
namespace better {
|
namespace better {
|
||||||
std::pair<Notes::iterator, bool> Notes::insert(const Note& note) {
|
std::pair<Notes::iterator, bool> Notes::insert(const Note& note) {
|
||||||
auto conflicting_note = end();
|
auto conflicting_note = end();
|
||||||
@ -56,4 +60,50 @@ namespace better {
|
|||||||
auto it = find(note);
|
auto it = find(note);
|
||||||
interval_tree::erase(it);
|
interval_tree::erase(it);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
bool Notes::is_colliding(const better::Note& note, const better::Timing& timing) {
|
||||||
|
const auto [start_beat, end_beat] = note.get_time_bounds();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Two notes collide if they are within ~one second of each other :
|
||||||
|
Approach and burst animations of original jubeat markers last 16 frames
|
||||||
|
at (supposedly) 30 fps, which means a note needs (a bit more than) half
|
||||||
|
a second of leeway both before *and* after itself to properly display
|
||||||
|
its marker animation, consequently, two consecutive notes on the same
|
||||||
|
button cannot be closer than ~one second from each other.
|
||||||
|
|
||||||
|
I don't really know why I shrink the collision zone down here ?
|
||||||
|
Shouldn't it be 32/30 seconds ? (~1066ms instead of 1000ms ?)
|
||||||
|
|
||||||
|
Reverse-engineering of the jubeat plus iOS app suggest the "official"
|
||||||
|
note collision zone size is 1030 ms, so actually I wasn't that far off
|
||||||
|
with 1000 ms !
|
||||||
|
|
||||||
|
TODO: Make the collision zone customizable
|
||||||
|
*/
|
||||||
|
const auto collision_start = timing.beats_at(timing.time_at(start_beat) - sf::seconds(1));
|
||||||
|
const auto collision_end = timing.beats_at(timing.time_at(end_beat) + sf::seconds(1));
|
||||||
|
|
||||||
|
bool found_collision = false;
|
||||||
|
in(
|
||||||
|
{collision_start, collision_end},
|
||||||
|
[&](Notes::const_iterator it){
|
||||||
|
if (it->second.get_position() == note.get_position()) {
|
||||||
|
if (it->second != note) {
|
||||||
|
found_collision = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return found_collision;
|
||||||
|
};
|
||||||
|
|
||||||
|
nlohmann::ordered_json Notes::dump_for_memon_1_0_0() const {
|
||||||
|
auto json_notes = nlohmann::ordered_json::array();
|
||||||
|
for (const auto& [_, note] : *this) {
|
||||||
|
json_notes.push_back(note.dump_for_memon_1_0_0());
|
||||||
|
}
|
||||||
|
return json_notes;
|
||||||
|
}
|
||||||
}
|
}
|
@ -5,9 +5,12 @@
|
|||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "interval_tree.hpp"
|
#include <interval_tree.hpp>
|
||||||
|
#include <json.hpp>
|
||||||
|
|
||||||
#include "better_note.hpp"
|
#include "better_note.hpp"
|
||||||
|
#include "better_timing.hpp"
|
||||||
|
#include "json.hpp"
|
||||||
#include "special_numeric_types.hpp"
|
#include "special_numeric_types.hpp"
|
||||||
|
|
||||||
namespace better {
|
namespace better {
|
||||||
@ -21,5 +24,14 @@ namespace better {
|
|||||||
const_iterator find(const Note& note) const;
|
const_iterator find(const Note& note) const;
|
||||||
bool contains(const Note& note) const;
|
bool contains(const Note& note) const;
|
||||||
void erase(const Note& note);
|
void erase(const Note& note);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Returns true if the given note (assumed to already be in the container)
|
||||||
|
is colliding with ANOTHER note. This means notes exactly equal to the
|
||||||
|
one passed as an argument are NOT taken into account.
|
||||||
|
*/
|
||||||
|
bool is_colliding(const better::Note& note, const better::Timing& timing);
|
||||||
|
|
||||||
|
nlohmann::ordered_json dump_for_memon_1_0_0() const;
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -2,78 +2,67 @@
|
|||||||
|
|
||||||
#include <SFML/System/Time.hpp>
|
#include <SFML/System/Time.hpp>
|
||||||
|
|
||||||
|
#include "better_hakus.hpp"
|
||||||
|
#include "json.hpp"
|
||||||
|
#include "src/better_hakus.hpp"
|
||||||
#include "std_optional_extras.hpp"
|
#include "std_optional_extras.hpp"
|
||||||
|
#include "variant_visitor.hpp"
|
||||||
|
|
||||||
namespace better {
|
namespace better {
|
||||||
std::optional<sf::Time> Chart::time_of_last_event() const {
|
|
||||||
if (notes.empty()) {
|
|
||||||
return {};
|
|
||||||
} else {
|
|
||||||
return timing.time_at(notes.crbegin()->second.get_end());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
bool Chart::is_colliding(const better::Note& note) {
|
|
||||||
const auto [start_beat, end_beat] = note.get_time_bounds();
|
|
||||||
|
|
||||||
/*
|
|
||||||
Two notes collide if they are within ~one second of each other :
|
|
||||||
Approach and burst animations of original jubeat markers last 16 frames
|
|
||||||
at (supposedly) 30 fps, which means a note needs (a bit more than) half
|
|
||||||
a second of leeway both before *and* after itself, consequently, two
|
|
||||||
consecutive notes on the same button cannot be closer than ~one second
|
|
||||||
from each other.
|
|
||||||
|
|
||||||
I don't really know why I shrink the collision zone down here ?
|
|
||||||
Shouldn't it be 32/30 seconds ? (1.0666... seconds instead of 1 ?)
|
|
||||||
|
|
||||||
TODO: Make the collision zone customizable
|
|
||||||
*/
|
|
||||||
const auto collision_start = timing.beats_at(timing.time_at(start_beat) - sf::seconds(1));
|
|
||||||
const auto collision_end = timing.beats_at(timing.time_at(end_beat) + sf::seconds(1));
|
|
||||||
|
|
||||||
bool found_collision = false;
|
|
||||||
notes.in(
|
|
||||||
{collision_start, collision_end},
|
|
||||||
[&](Notes::const_iterator it){
|
|
||||||
if (it->second.get_position() == note.get_position()) {
|
|
||||||
if (it->second != note) {
|
|
||||||
found_collision = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return found_collision;
|
|
||||||
}
|
|
||||||
|
|
||||||
PreviewLoop::PreviewLoop(Decimal start, Decimal duration) :
|
|
||||||
start(start),
|
|
||||||
duration(duration)
|
|
||||||
{
|
|
||||||
if (start < 0) {
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "Attempted to create a PreviewLoop with negative start ";
|
|
||||||
ss << "position : " << start;
|
|
||||||
throw std::invalid_argument(ss.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (duration < 0) {
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "Attempted to create a PreviewLoop with negative ";
|
|
||||||
ss << "duration : " << duration;
|
|
||||||
throw std::invalid_argument(ss.str());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Decimal PreviewLoop::get_start() const {
|
|
||||||
return start;
|
|
||||||
};
|
|
||||||
|
|
||||||
Decimal PreviewLoop::get_duration() const {
|
|
||||||
return duration;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string stringify_level(std::optional<Decimal> level) {
|
std::string stringify_level(std::optional<Decimal> level) {
|
||||||
return stringify_or(level, "(no level defined)");
|
return stringify_or(level, "(no level defined)");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
nlohmann::ordered_json Song::dump_as_memon_1_0_0() const {
|
||||||
|
nlohmann::ordered_json memon;
|
||||||
|
memon["version"] = "1.0.0";
|
||||||
|
auto json_metadata = dump_metadata_1_0_0();
|
||||||
|
if (not json_metadata.empty()) {
|
||||||
|
memon["metadata"] = json_metadata;
|
||||||
|
}
|
||||||
|
auto json_timing = nlohmann::ordered_json::object();
|
||||||
|
if (timing) {
|
||||||
|
json_timing.update(timing->dump_for_memon_1_0_0());
|
||||||
|
}
|
||||||
|
if (hakus) {
|
||||||
|
json_timing["hakus"] = dump_hakus(*hakus);
|
||||||
|
}
|
||||||
|
if (not json_timing.empty()) {
|
||||||
|
memon["timing"] = json_timing;
|
||||||
|
}
|
||||||
|
auto json_charts = nlohmann::ordered_json::object();
|
||||||
|
for (const auto& [name, chart] : charts) {
|
||||||
|
json_charts[name] = chart.dump_for_memon_1_0_0();
|
||||||
|
}
|
||||||
|
return memon;
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::ordered_json Song::dump_metadata_1_0_0() const {
|
||||||
|
nlohmann::ordered_json json_metadata;
|
||||||
|
if (not metadata.title.empty()) {
|
||||||
|
json_metadata["title"] = metadata.title;
|
||||||
|
}
|
||||||
|
if (not metadata.artist.empty()) {
|
||||||
|
json_metadata["artist"] = metadata.artist;
|
||||||
|
}
|
||||||
|
if (not metadata.audio.empty()) {
|
||||||
|
json_metadata["audio"] = metadata.audio;
|
||||||
|
}
|
||||||
|
if (not metadata.jacket.empty()) {
|
||||||
|
json_metadata["jacket"] = metadata.jacket;
|
||||||
|
}
|
||||||
|
if (metadata.use_preview_file) {
|
||||||
|
if (not metadata.preview_file.empty()) {
|
||||||
|
json_metadata["preview"] = metadata.preview_file;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (metadata.preview_loop.duration != 0) {
|
||||||
|
json_metadata["preview"] = {
|
||||||
|
{"start", metadata.preview_loop.start.format("f")},
|
||||||
|
{"duration", metadata.preview_loop.duration.format("f")}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json_metadata;
|
||||||
|
};
|
||||||
}
|
}
|
@ -8,51 +8,19 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
|
||||||
|
#include <json.hpp>
|
||||||
#include <SFML/System/Time.hpp>
|
#include <SFML/System/Time.hpp>
|
||||||
|
|
||||||
|
#include "better_chart.hpp"
|
||||||
|
#include "better_hakus.hpp"
|
||||||
|
#include "better_metadata.hpp"
|
||||||
#include "better_notes.hpp"
|
#include "better_notes.hpp"
|
||||||
#include "better_timing.hpp"
|
#include "better_timing.hpp"
|
||||||
#include "special_numeric_types.hpp"
|
#include "special_numeric_types.hpp"
|
||||||
|
|
||||||
namespace better {
|
namespace better {
|
||||||
struct Chart {
|
|
||||||
std::optional<Decimal> level;
|
|
||||||
Timing timing;
|
|
||||||
std::optional<std::set<Fraction>> hakus;
|
|
||||||
Notes notes;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Returns true if the given note (assumed to already be part of the
|
|
||||||
chart) is colliding with ANOTHER note in the chart. This means notes
|
|
||||||
exactly equal to the one passed as an argument are NOT taken into
|
|
||||||
account.
|
|
||||||
*/
|
|
||||||
bool is_colliding(const better::Note& note);
|
|
||||||
|
|
||||||
std::optional<sf::Time> time_of_last_event() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string stringify_level(std::optional<Decimal> level);
|
std::string stringify_level(std::optional<Decimal> level);
|
||||||
|
|
||||||
class PreviewLoop {
|
|
||||||
public:
|
|
||||||
PreviewLoop(Decimal start, Decimal duration);
|
|
||||||
|
|
||||||
Decimal get_start() const;
|
|
||||||
Decimal get_duration() const;
|
|
||||||
private:
|
|
||||||
Decimal start;
|
|
||||||
Decimal duration;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Metadata {
|
|
||||||
std::optional<std::string> title;
|
|
||||||
std::optional<std::string> artist;
|
|
||||||
std::optional<std::filesystem::path> audio;
|
|
||||||
std::optional<std::filesystem::path> jacket;
|
|
||||||
std::optional<std::variant<PreviewLoop, std::filesystem::path>> preview;
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto difficulty_name_comp_key = [](const std::string& s) {
|
const auto difficulty_name_comp_key = [](const std::string& s) {
|
||||||
if (s == "BSC") {
|
if (s == "BSC") {
|
||||||
return std::make_tuple(1, std::string{});
|
return std::make_tuple(1, std::string{});
|
||||||
@ -76,6 +44,10 @@ namespace better {
|
|||||||
decltype(order_by_difficulty_name)
|
decltype(order_by_difficulty_name)
|
||||||
> charts{order_by_difficulty_name};
|
> charts{order_by_difficulty_name};
|
||||||
Metadata metadata;
|
Metadata metadata;
|
||||||
Timing timing;
|
std::optional<Timing> timing;
|
||||||
|
std::optional<Hakus> hakus;
|
||||||
|
|
||||||
|
nlohmann::ordered_json dump_as_memon_1_0_0() const;
|
||||||
|
nlohmann::ordered_json dump_metadata_1_0_0() const;
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -1,7 +1,12 @@
|
|||||||
#include "better_timing.hpp"
|
#include "better_timing.hpp"
|
||||||
|
|
||||||
|
#include <json.hpp>
|
||||||
|
|
||||||
|
#include "better_beats.hpp"
|
||||||
|
#include "src/better_beats.hpp"
|
||||||
|
|
||||||
namespace better {
|
namespace better {
|
||||||
BPMAtBeat::BPMAtBeat(Fraction beats, Fraction bpm) : beats(beats), bpm(bpm) {
|
BPMAtBeat::BPMAtBeat(Fraction beats, Decimal bpm) : beats(beats), bpm(bpm) {
|
||||||
if (bpm <= 0) {
|
if (bpm <= 0) {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Attempted to create a BPMAtBeat with negative BPM : ";
|
ss << "Attempted to create a BPMAtBeat with negative BPM : ";
|
||||||
@ -10,14 +15,23 @@ namespace better {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
BPMEvent::BPMEvent(Fraction beats, Fraction seconds, Fraction bpm) :
|
BPMEvent::BPMEvent(Fraction beats, Fraction seconds, Decimal bpm) :
|
||||||
BPMAtBeat(beats, bpm),
|
BPMAtBeat(beats, bpm),
|
||||||
seconds(seconds)
|
seconds(seconds)
|
||||||
{};
|
{};
|
||||||
|
|
||||||
|
Fraction BPMAtBeat::get_beats() const {
|
||||||
|
return beats;
|
||||||
|
}
|
||||||
|
|
||||||
|
Decimal BPMAtBeat::get_bpm() const {
|
||||||
|
return bpm;
|
||||||
|
}
|
||||||
|
|
||||||
Fraction BPMEvent::get_seconds() const {
|
Fraction BPMEvent::get_seconds() const {
|
||||||
return seconds;
|
return seconds;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Create a Time Map from a list of BPM changes with times given in
|
Create a Time Map from a list of BPM changes with times given in
|
||||||
@ -75,7 +89,7 @@ namespace better {
|
|||||||
auto beats_since_last_event =
|
auto beats_since_last_event =
|
||||||
current->get_beats() - previous->get_beats();
|
current->get_beats() - previous->get_beats();
|
||||||
auto seconds_since_last_event =
|
auto seconds_since_last_event =
|
||||||
(60 * beats_since_last_event) / previous->get_bpm();
|
(60 * beats_since_last_event) / convert_to_fraction(previous->get_bpm());
|
||||||
current_second += seconds_since_last_event;
|
current_second += seconds_since_last_event;
|
||||||
bpm_changes.emplace_back(
|
bpm_changes.emplace_back(
|
||||||
current->get_beats(),
|
current->get_beats(),
|
||||||
@ -125,7 +139,7 @@ namespace better {
|
|||||||
auto seconds_since_previous_event = (
|
auto seconds_since_previous_event = (
|
||||||
Fraction{60}
|
Fraction{60}
|
||||||
* beats_since_previous_event
|
* beats_since_previous_event
|
||||||
/ bpm_change->get_bpm()
|
/ convert_to_fraction(bpm_change->get_bpm())
|
||||||
);
|
);
|
||||||
return bpm_change->get_seconds() + seconds_since_previous_event;
|
return bpm_change->get_seconds() + seconds_since_previous_event;
|
||||||
};
|
};
|
||||||
@ -156,10 +170,27 @@ namespace better {
|
|||||||
}
|
}
|
||||||
auto seconds_since_previous_event = fractional_seconds - bpm_change->get_seconds();
|
auto seconds_since_previous_event = fractional_seconds - bpm_change->get_seconds();
|
||||||
auto beats_since_previous_event = (
|
auto beats_since_previous_event = (
|
||||||
bpm_change->get_bpm()
|
convert_to_fraction(bpm_change->get_bpm())
|
||||||
* seconds_since_previous_event
|
* seconds_since_previous_event
|
||||||
/ Fraction{60}
|
/ Fraction{60}
|
||||||
);
|
);
|
||||||
return bpm_change->get_beats() + beats_since_previous_event;
|
return bpm_change->get_beats() + beats_since_previous_event;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
nlohmann::ordered_json Timing::dump_for_memon_1_0_0() const {
|
||||||
|
nlohmann::ordered_json j;
|
||||||
|
j["offset"] = convert_to_decimal(fractional_seconds_at(0), 5).format("f");
|
||||||
|
auto bpms = nlohmann::ordered_json::array();
|
||||||
|
for (const auto& bpm_change : events_by_beats) {
|
||||||
|
nlohmann::ordered_json bpm_event;
|
||||||
|
bpm_event["beat"] = beat_to_best_form(bpm_change.get_beats());
|
||||||
|
bpm_event["bpm"] = bpm_change.get_bpm().format("f");
|
||||||
|
bpms.push_back({
|
||||||
|
{"beat", beat_to_best_form(bpm_change.get_beats())},
|
||||||
|
{"bpm", bpm_change.get_bpm().format("f")}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
j["bpms"] = bpms;
|
||||||
|
return j;
|
||||||
|
}
|
||||||
}
|
}
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include <SFML/System/Time.hpp>
|
#include <SFML/System/Time.hpp>
|
||||||
|
|
||||||
|
#include "json.hpp"
|
||||||
#include "special_numeric_types.hpp"
|
#include "special_numeric_types.hpp"
|
||||||
|
|
||||||
namespace better {
|
namespace better {
|
||||||
@ -19,17 +20,17 @@ namespace better {
|
|||||||
|
|
||||||
class BPMAtBeat {
|
class BPMAtBeat {
|
||||||
public:
|
public:
|
||||||
BPMAtBeat(Fraction beats, Fraction bpm);
|
BPMAtBeat(Fraction beats, Decimal bpm);
|
||||||
Fraction get_beats() const;
|
Fraction get_beats() const;
|
||||||
Fraction get_bpm() const;
|
Decimal get_bpm() const;
|
||||||
private:
|
private:
|
||||||
Fraction beats;
|
Fraction beats;
|
||||||
Fraction bpm;
|
Decimal bpm;
|
||||||
};
|
};
|
||||||
|
|
||||||
class BPMEvent : public BPMAtBeat {
|
class BPMEvent : public BPMAtBeat {
|
||||||
public:
|
public:
|
||||||
BPMEvent(Fraction beats, Fraction seconds, Fraction bpm);
|
BPMEvent(Fraction beats, Fraction seconds, Decimal bpm);
|
||||||
Fraction get_seconds() const;
|
Fraction get_seconds() const;
|
||||||
private:
|
private:
|
||||||
Fraction seconds;
|
Fraction seconds;
|
||||||
@ -53,6 +54,8 @@ namespace better {
|
|||||||
sf::Time time_between(Fraction beat_a, Fraction beat_b) const;
|
sf::Time time_between(Fraction beat_a, Fraction beat_b) const;
|
||||||
|
|
||||||
Fraction beats_at(sf::Time time) const;
|
Fraction beats_at(sf::Time time) const;
|
||||||
|
|
||||||
|
nlohmann::ordered_json dump_for_memon_1_0_0() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::set<BPMEvent, decltype(order_by_beats)> events_by_beats{order_by_beats};
|
std::set<BPMEvent, decltype(order_by_beats)> events_by_beats{order_by_beats};
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "imgui_extras.hpp"
|
#include "imgui_extras.hpp"
|
||||||
#include "metadata_in_gui.hpp"
|
#include "metadata_in_gui.hpp"
|
||||||
#include "special_numeric_types.hpp"
|
#include "special_numeric_types.hpp"
|
||||||
|
#include "src/better_song.hpp"
|
||||||
#include "src/chart.hpp"
|
#include "src/chart.hpp"
|
||||||
#include "std_optional_extras.hpp"
|
#include "std_optional_extras.hpp"
|
||||||
#include "variant_visitor.hpp"
|
#include "variant_visitor.hpp"
|
||||||
@ -32,7 +33,6 @@ EditorState::EditorState(
|
|||||||
playfield(assets_),
|
playfield(assets_),
|
||||||
linear_view(assets_),
|
linear_view(assets_),
|
||||||
song(song_),
|
song(song_),
|
||||||
metadata_in_gui(song_.metadata),
|
|
||||||
applicable_timing(song.timing),
|
applicable_timing(song.timing),
|
||||||
assets(assets_)
|
assets(assets_)
|
||||||
{
|
{
|
||||||
@ -241,12 +241,12 @@ void EditorState::display_properties() {
|
|||||||
}
|
}
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
|
|
||||||
ImGui::InputText("Title", &metadata_in_gui.title);
|
ImGui::InputText("Title", &song.metadata.title);
|
||||||
ImGui::InputText("Artist", &metadata_in_gui.artist);
|
ImGui::InputText("Artist", &song.metadata.artist);
|
||||||
|
|
||||||
if (feis::InputTextColored(
|
if (feis::InputTextColored(
|
||||||
"Audio",
|
"Audio",
|
||||||
&metadata_in_gui.audio,
|
&song.metadata.audio,
|
||||||
music_state.has_value(),
|
music_state.has_value(),
|
||||||
"Invalid Audio Path"
|
"Invalid Audio Path"
|
||||||
)) {
|
)) {
|
||||||
@ -254,7 +254,7 @@ void EditorState::display_properties() {
|
|||||||
}
|
}
|
||||||
if (feis::InputTextColored(
|
if (feis::InputTextColored(
|
||||||
"Jacket",
|
"Jacket",
|
||||||
&metadata_in_gui.jacket,
|
&song.metadata.jacket,
|
||||||
jacket.has_value(),
|
jacket.has_value(),
|
||||||
"Invalid Jacket Path"
|
"Invalid Jacket Path"
|
||||||
)) {
|
)) {
|
||||||
@ -264,45 +264,45 @@ void EditorState::display_properties() {
|
|||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
ImGui::Text("Preview");
|
ImGui::Text("Preview");
|
||||||
ImGui::Checkbox("Use separate preview file", &metadata_in_gui.use_preview_file);
|
ImGui::Checkbox("Use separate preview file", &song.metadata.use_preview_file);
|
||||||
if (metadata_in_gui.use_preview_file) {
|
if (song.metadata.use_preview_file) {
|
||||||
if (feis::InputTextColored(
|
if (feis::InputTextColored(
|
||||||
"File",
|
"File",
|
||||||
&metadata_in_gui.preview_file,
|
&song.metadata.preview_file,
|
||||||
preview_audio.has_value(),
|
preview_audio.has_value(),
|
||||||
"Invalid Path"
|
"Invalid Path"
|
||||||
)) {
|
)) {
|
||||||
reload_preview_audio();
|
reload_preview_audio();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (feis::InputDecimal("Start", &metadata_in_gui.preview_loop.start)) {
|
if (feis::InputDecimal("Start", &song.metadata.preview_loop.start)) {
|
||||||
metadata_in_gui.preview_loop.start = std::max(
|
song.metadata.preview_loop.start = std::max(
|
||||||
Decimal{0},
|
Decimal{0},
|
||||||
metadata_in_gui.preview_loop.start
|
song.metadata.preview_loop.start
|
||||||
);
|
);
|
||||||
if (music_state.has_value()) {
|
if (music_state.has_value()) {
|
||||||
metadata_in_gui.preview_loop.start = std::min(
|
song.metadata.preview_loop.start = std::min(
|
||||||
Decimal{music_state->music.getDuration().asMicroseconds()} / 1000000,
|
Decimal{music_state->music.getDuration().asMicroseconds()} / 1000000,
|
||||||
metadata_in_gui.preview_loop.start
|
song.metadata.preview_loop.start
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (feis::InputDecimal("Duration", &metadata_in_gui.preview_loop.duration)) {
|
if (feis::InputDecimal("Duration", &song.metadata.preview_loop.duration)) {
|
||||||
metadata_in_gui.preview_loop.duration = std::max(
|
song.metadata.preview_loop.duration = std::max(
|
||||||
Decimal{0},
|
Decimal{0},
|
||||||
metadata_in_gui.preview_loop.duration
|
song.metadata.preview_loop.duration
|
||||||
);
|
);
|
||||||
if (music_state.has_value()) {
|
if (music_state.has_value()) {
|
||||||
metadata_in_gui.preview_loop.start = std::min(
|
song.metadata.preview_loop.start = std::min(
|
||||||
(
|
(
|
||||||
Decimal{
|
Decimal{
|
||||||
music_state->music
|
music_state->music
|
||||||
.getDuration()
|
.getDuration()
|
||||||
.asMicroseconds()
|
.asMicroseconds()
|
||||||
} / 1000000
|
} / 1000000
|
||||||
- metadata_in_gui.preview_loop.start
|
- song.metadata.preview_loop.start
|
||||||
),
|
),
|
||||||
metadata_in_gui.preview_loop.start
|
song.metadata.preview_loop.start
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -321,11 +321,11 @@ void EditorState::display_status() {
|
|||||||
ImGui::Begin("Status", &showStatus, ImGuiWindowFlags_AlwaysAutoResize);
|
ImGui::Begin("Status", &showStatus, ImGuiWindowFlags_AlwaysAutoResize);
|
||||||
{
|
{
|
||||||
if (not music_state) {
|
if (not music_state) {
|
||||||
if (not metadata_in_gui.audio.empty()) {
|
if (not song.metadata.audio.empty()) {
|
||||||
ImGui::TextColored(
|
ImGui::TextColored(
|
||||||
ImVec4(1, 0.42, 0.41, 1),
|
ImVec4(1, 0.42, 0.41, 1),
|
||||||
"Invalid music path : %s",
|
"Invalid music path : %s",
|
||||||
metadata_in_gui.audio.c_str());
|
song.metadata.audio.c_str());
|
||||||
} else {
|
} else {
|
||||||
ImGui::TextColored(
|
ImGui::TextColored(
|
||||||
ImVec4(1, 0.42, 0.41, 1),
|
ImVec4(1, 0.42, 0.41, 1),
|
||||||
@ -334,11 +334,11 @@ void EditorState::display_status() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (not jacket) {
|
if (not jacket) {
|
||||||
if (not metadata_in_gui.jacket.empty()) {
|
if (not song.metadata.jacket.empty()) {
|
||||||
ImGui::TextColored(
|
ImGui::TextColored(
|
||||||
ImVec4(1, 0.42, 0.41, 1),
|
ImVec4(1, 0.42, 0.41, 1),
|
||||||
"Invalid jacket path : %s",
|
"Invalid jacket path : %s",
|
||||||
metadata_in_gui.jacket.c_str());
|
song.metadata.jacket.c_str());
|
||||||
} else {
|
} else {
|
||||||
ImGui::TextColored(
|
ImGui::TextColored(
|
||||||
ImVec4(1, 0.42, 0.41, 1),
|
ImVec4(1, 0.42, 0.41, 1),
|
||||||
@ -636,13 +636,13 @@ void EditorState::reload_editable_range() {
|
|||||||
* of the song Resets the album cover state if anything fails
|
* of the song Resets the album cover state if anything fails
|
||||||
*/
|
*/
|
||||||
void EditorState::reload_jacket() {
|
void EditorState::reload_jacket() {
|
||||||
if (not song_path.has_value() or not song.metadata.jacket.has_value()) {
|
if (not song_path.has_value() or song.metadata.jacket.empty()) {
|
||||||
jacket.reset();
|
jacket.reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
jacket.emplace();
|
jacket.emplace();
|
||||||
auto jacket_path = song_path->parent_path() / metadata_in_gui.jacket;
|
auto jacket_path = song_path->parent_path() / song.metadata.jacket;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
not std::filesystem::exists(jacket_path)
|
not std::filesystem::exists(jacket_path)
|
||||||
@ -658,12 +658,12 @@ void EditorState::reload_jacket() {
|
|||||||
* Updates playbackPosition and preview_end as well
|
* Updates playbackPosition and preview_end as well
|
||||||
*/
|
*/
|
||||||
void EditorState::reload_music() {
|
void EditorState::reload_music() {
|
||||||
if (not song_path.has_value()) {
|
if (not song_path.has_value() or song.metadata.audio.empty()) {
|
||||||
music_state.reset();
|
music_state.reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto absolute_music_path = song_path->parent_path() / metadata_in_gui.audio;
|
const auto absolute_music_path = song_path->parent_path() / song.metadata.audio;
|
||||||
try {
|
try {
|
||||||
music_state.emplace(absolute_music_path);
|
music_state.emplace(absolute_music_path);
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
@ -680,35 +680,35 @@ void EditorState::reload_music() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void EditorState::reload_preview_audio() {
|
void EditorState::reload_preview_audio() {
|
||||||
if (not song_path.has_value()) {
|
if (not song_path.has_value() or song.metadata.preview_file.empty()) {
|
||||||
preview_audio.reset();
|
preview_audio.reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto path = song_path->parent_path() / metadata_in_gui.preview_file;
|
const auto path = song_path->parent_path() / song.metadata.preview_file;
|
||||||
try {
|
try {
|
||||||
preview_audio.emplace(path);
|
preview_audio.emplace(path.string());
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
preview_audio.reset();
|
preview_audio.reset();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void reload_applicable_timing() {
|
void EditorState::reload_applicable_timing() {
|
||||||
// TODO: implement
|
if (chart_state) {
|
||||||
|
applicable_timing = chart_state->chart.timing;
|
||||||
|
} else {
|
||||||
|
applicable_timing = song.timing;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditorState::open_chart(better::Chart& chart) {
|
void EditorState::open_chart(better::Chart& chart, const std::string& name) {
|
||||||
chart_state.emplace(chart, assets);
|
chart_state.emplace(chart, name, assets);
|
||||||
reload_applicable_timing();
|
reload_applicable_timing();
|
||||||
reload_editable_range();
|
reload_editable_range();
|
||||||
};
|
};
|
||||||
|
|
||||||
void ESHelper::save(EditorState& ed) {
|
void EditorState::save(const std::filesystem::path& file) {
|
||||||
try {
|
const auto memon = song.dump_as_memon_1_0_0();
|
||||||
ed.song.autoSaveAsMemon();
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
tinyfd_messageBox("Error", e.what(), "ok", "error", 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESHelper::open(std::optional<EditorState>& ed, std::filesystem::path assets, std::filesystem::path settings) {
|
void ESHelper::open(std::optional<EditorState>& ed, std::filesystem::path assets, std::filesystem::path settings) {
|
||||||
|
@ -108,11 +108,12 @@ public:
|
|||||||
void undo(NotificationsQueue& nq);
|
void undo(NotificationsQueue& nq);
|
||||||
void redo(NotificationsQueue& nq);
|
void redo(NotificationsQueue& nq);
|
||||||
|
|
||||||
|
void save(const std::filesystem::path& file);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
better::Song song;
|
better::Song song;
|
||||||
|
|
||||||
MetadataInGui metadata_in_gui;
|
|
||||||
/*
|
/*
|
||||||
sf::Time bounds (in the audio file "coordinates") which are accessible
|
sf::Time bounds (in the audio file "coordinates") which are accessible
|
||||||
(and maybe editable) from the editor, can extend before and after
|
(and maybe editable) from the editor, can extend before and after
|
||||||
@ -127,15 +128,12 @@ private:
|
|||||||
better::Timing& applicable_timing;
|
better::Timing& applicable_timing;
|
||||||
void reload_applicable_timing();
|
void reload_applicable_timing();
|
||||||
|
|
||||||
|
void open_chart(better::Chart& chart, const std::string& name);
|
||||||
|
|
||||||
void open_chart(better::Chart& chart);
|
|
||||||
|
|
||||||
std::filesystem::path assets;
|
std::filesystem::path assets;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace ESHelper {
|
namespace ESHelper {
|
||||||
void save(EditorState& ed);
|
|
||||||
void open(std::optional<EditorState>& ed, std::filesystem::path assets, std::filesystem::path settings);
|
void open(std::optional<EditorState>& ed, std::filesystem::path assets, std::filesystem::path settings);
|
||||||
void openFromFile(
|
void openFromFile(
|
||||||
std::optional<EditorState>& ed,
|
std::optional<EditorState>& ed,
|
||||||
|
7
src/json_decimal_parser.cpp
Normal file
7
src/json_decimal_parser.cpp
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#include "json_decimal_parser.hpp"
|
||||||
|
|
||||||
|
bool json_decimal_parser::number_float(number_float_t /*unused*/, const string_t& val) {
|
||||||
|
string_t copy = val;
|
||||||
|
string(copy);
|
||||||
|
return true;
|
||||||
|
}
|
38
src/json_decimal_parser.hpp
Normal file
38
src/json_decimal_parser.hpp
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <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
|
||||||
|
|
||||||
|
Usage :
|
||||||
|
|
||||||
|
nlohmann::json j;
|
||||||
|
json_decimal_parser sax{j};
|
||||||
|
nlohmann::json::sax_parse(..., &sax);
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 parse_decimal_json(InputType&& input) {
|
||||||
|
nlohmann::json j;
|
||||||
|
json_decimal_parser sax{j};
|
||||||
|
nlohmann::json::sax_parse(std::forward<InputType>(input), &sax);
|
||||||
|
return j;
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
sources += files(
|
sources += files(
|
||||||
|
'better_chart.cpp',
|
||||||
|
'better_metadata.cpp',
|
||||||
'better_note.cpp',
|
'better_note.cpp',
|
||||||
'better_notes.cpp',
|
'better_notes.cpp',
|
||||||
'better_song.cpp',
|
'better_song.cpp',
|
||||||
@ -9,6 +11,7 @@ sources += files(
|
|||||||
'fumen.cpp',
|
'fumen.cpp',
|
||||||
'history_actions.cpp',
|
'history_actions.cpp',
|
||||||
'imgui_extras.cpp',
|
'imgui_extras.cpp',
|
||||||
|
'json_decimal_parser.cpp',
|
||||||
'ln_marker.cpp',
|
'ln_marker.cpp',
|
||||||
'main.cpp',
|
'main.cpp',
|
||||||
'marker.cpp',
|
'marker.cpp',
|
||||||
@ -21,7 +24,6 @@ sources += files(
|
|||||||
'precise_music.cpp',
|
'precise_music.cpp',
|
||||||
'preferences.cpp',
|
'preferences.cpp',
|
||||||
'sound_effect.cpp',
|
'sound_effect.cpp',
|
||||||
'time_interval.cpp',
|
|
||||||
'toolbox.cpp',
|
'toolbox.cpp',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
#include "src/better_song.hpp"
|
||||||
|
|
||||||
MetadataInGui::MetadataInGui(const better::Metadata& metadata) :
|
MetadataInGui::MetadataInGui(const better::Metadata& metadata) :
|
||||||
title(metadata.title.value_or("")),
|
title(metadata.title.value_or("")),
|
||||||
artist(metadata.artist.value_or("")),
|
artist(metadata.artist.value_or("")),
|
||||||
audio(metadata.audio.value_or(std::filesystem::path{}).string()),
|
audio(metadata.audio.value_or(std::filesystem::path{}).string()),
|
||||||
jacket(metadata.jacket.value_or(std::filesystem::path{}).string()),
|
jacket(metadata.jacket.value_or(std::filesystem::path{}).string())
|
||||||
{
|
{
|
||||||
if (metadata.preview.has_value()) {
|
if (metadata.preview.has_value()) {
|
||||||
if (std::holds_alternative<better::PreviewLoop>(*metadata.preview)) {
|
if (std::holds_alternative<better::PreviewLoop>(*metadata.preview)) {
|
||||||
@ -19,4 +20,31 @@ MetadataInGui::MetadataInGui(const better::Metadata& metadata) :
|
|||||||
use_preview_file = true;
|
use_preview_file = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MetadataInGui::operator better::Metadata() const {
|
||||||
|
auto m = better::Metadata{};
|
||||||
|
if (not title.empty()) {
|
||||||
|
m.title = title;
|
||||||
|
}
|
||||||
|
if (not artist.empty()) {
|
||||||
|
m.artist = artist;
|
||||||
|
}
|
||||||
|
if (not audio.empty()) {
|
||||||
|
m.audio = audio;
|
||||||
|
}
|
||||||
|
if (not jacket.empty()) {
|
||||||
|
m.jacket = jacket;
|
||||||
|
}
|
||||||
|
if (use_preview_file) {
|
||||||
|
if (not preview_file.empty()) {
|
||||||
|
m.preview = std::filesystem::path(preview_file);
|
||||||
|
}
|
||||||
|
} else if (preview_loop.start != preview_loop.duration) {
|
||||||
|
m.preview = better::PreviewLoop{
|
||||||
|
preview_loop.start,
|
||||||
|
preview_loop.duration
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return m;
|
||||||
}
|
}
|
@ -1,5 +1,7 @@
|
|||||||
#include "special_numeric_types.hpp"
|
#include "special_numeric_types.hpp"
|
||||||
|
|
||||||
|
#include <boost/math/special_functions/math_fwd.hpp>
|
||||||
|
#include <boost/math/special_functions/pow.hpp>
|
||||||
#include <boost/multiprecision/gmp.hpp>
|
#include <boost/multiprecision/gmp.hpp>
|
||||||
#include <boost/multiprecision/number.hpp>
|
#include <boost/multiprecision/number.hpp>
|
||||||
|
|
||||||
@ -36,4 +38,16 @@ Decimal convert_to_decimal(const Fraction& f, unsigned int precision) {
|
|||||||
Decimal{static_cast<long long>(boost::multiprecision::numerator(floored))}
|
Decimal{static_cast<long long>(boost::multiprecision::numerator(floored))}
|
||||||
/ Decimal{static_cast<long long>(boost::multiprecision::denominator(floored))}
|
/ Decimal{static_cast<long long>(boost::multiprecision::denominator(floored))}
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Fraction convert_to_fraction(const Decimal& d) {
|
||||||
|
const auto reduced = d.reduce();
|
||||||
|
const auto sign = reduced.sign();
|
||||||
|
const auto exponent = reduced.exponent();
|
||||||
|
const auto coefficient = reduced.coeff().u64();
|
||||||
|
if (exponent >= 0) {
|
||||||
|
return Fraction{sign > 0 ? 1 : -1} * Fraction{coefficient} * fast_pow(Fraction{10}, exponent);
|
||||||
|
} else {
|
||||||
|
return Fraction{sign > 0 ? 1 : -1} * Fraction{coefficient} / fast_pow(Fraction{10}, exponent);
|
||||||
|
}
|
||||||
}
|
}
|
@ -22,6 +22,7 @@ Fraction operator%(Fraction a, const Fraction& b);
|
|||||||
Fraction floor_fraction(const Fraction& f);
|
Fraction floor_fraction(const Fraction& f);
|
||||||
Fraction round_fraction(const Fraction& f);
|
Fraction round_fraction(const Fraction& f);
|
||||||
Decimal convert_to_decimal(const Fraction& f, unsigned int precision);
|
Decimal convert_to_decimal(const Fraction& f, unsigned int precision);
|
||||||
|
Fraction convert_to_fraction(const Decimal& d);
|
||||||
|
|
||||||
// Rounds a given beat to the nearest given division (defaults to nearest 1/240th)
|
// Rounds a given beat to the nearest given division (defaults to nearest 1/240th)
|
||||||
const auto round_beats = [](Fraction beats, unsigned int denominator = 240) {
|
const auto round_beats = [](Fraction beats, unsigned int denominator = 240) {
|
||||||
@ -34,4 +35,20 @@ const auto floor_beats = [](Fraction beats, unsigned int denominator = 240) {
|
|||||||
beats *= denominator;
|
beats *= denominator;
|
||||||
const auto nearest = floor_fraction(beats);
|
const auto nearest = floor_fraction(beats);
|
||||||
return nearest / Fraction{denominator};
|
return nearest / Fraction{denominator};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Stolen from : https://github.com/progrock-libraries/kickstart/blob/d62c22efc92006dd76d455cf8f9d4f2a045e9126/source/library/kickstart/main_library/core/ns%E2%96%B8language/operations/intpow.hpp#L36
|
||||||
|
// Essentially this is Horner's rule adapted to calculating a power, so that the
|
||||||
|
// number of floating point multiplications is at worst O(log₂n).
|
||||||
|
template<class Number>
|
||||||
|
Number fast_pow( const Number base, const unsigned int exponent ) {
|
||||||
|
Number result = 1;
|
||||||
|
Number weight = base;
|
||||||
|
for (unsigned int n = exponent; n != 0; weight *= weight) {
|
||||||
|
if(n % 2 != 0) {
|
||||||
|
result *= weight;
|
||||||
|
}
|
||||||
|
n /= 2;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
30
tests/json_decimal.cpp
Normal file
30
tests/json_decimal.cpp
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <map>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <json.hpp>
|
||||||
|
#include <libmpdec++/decimal.hh>
|
||||||
|
|
||||||
|
class json_decimal_parser : public nlohmann::detail::json_sax_dom_parser<nlohmann::json> {
|
||||||
|
public:
|
||||||
|
using nlohmann::detail::json_sax_dom_parser<nlohmann::json>::json_sax_dom_parser;
|
||||||
|
bool number_float(number_float_t /*unused*/, const string_t& val) {
|
||||||
|
string_t copy = val;
|
||||||
|
string(copy);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
nlohmann::json j{"blablabla"};
|
||||||
|
// json_decimal_parser sax{j};
|
||||||
|
// std::string json;
|
||||||
|
// std::getline(std::cin, json);
|
||||||
|
// nlohmann::json::sax_parse(json, &sax);
|
||||||
|
std::cout << "from const char * {} : " << nlohmann::json{"blablabla"} << std::endl;
|
||||||
|
std::cout << "from string {} : " << nlohmann::json{std::string{"blibloblu"}} << std::endl;
|
||||||
|
std::cout << "from const char * () : " << nlohmann::json("blablabla") << std::endl;
|
||||||
|
std::cout << "from string {} : " << nlohmann::json(std::string("blibloblu")) << std::endl;
|
||||||
|
}
|
@ -15,6 +15,13 @@ executable(
|
|||||||
include_directories: inc,
|
include_directories: inc,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
executable(
|
||||||
|
'json_decimal',
|
||||||
|
'json_decimal.cpp',
|
||||||
|
dependencies: deps,
|
||||||
|
include_directories: inc,
|
||||||
|
)
|
||||||
|
|
||||||
executable(
|
executable(
|
||||||
'scrollwheel',
|
'scrollwheel',
|
||||||
'scrollwheel.cpp',
|
'scrollwheel.cpp',
|
||||||
|
Loading…
Reference in New Issue
Block a user