2022-03-16 02:10:18 +01:00
|
|
|
#include "better_timing.hpp"
|
|
|
|
|
2022-04-09 00:54:06 +02:00
|
|
|
#include <string>
|
|
|
|
|
2022-04-07 00:14:01 +02:00
|
|
|
#include <fmt/core.h>
|
2022-03-31 03:50:15 +02:00
|
|
|
#include <json.hpp>
|
|
|
|
|
|
|
|
#include "better_beats.hpp"
|
|
|
|
#include "src/better_beats.hpp"
|
|
|
|
|
2022-03-16 02:10:18 +01:00
|
|
|
namespace better {
|
2022-04-09 00:54:06 +02:00
|
|
|
BPMAtBeat::BPMAtBeat(Decimal bpm_, Fraction beats_) :
|
|
|
|
bpm(bpm_),
|
|
|
|
bpm_as_double(std::stod(bpm_.format("f"))),
|
|
|
|
beats(beats_)
|
|
|
|
{
|
2022-03-16 02:10:18 +01:00
|
|
|
if (bpm <= 0) {
|
|
|
|
std::stringstream ss;
|
|
|
|
ss << "Attempted to create a BPMAtBeat with negative BPM : ";
|
|
|
|
ss << bpm;
|
|
|
|
throw std::invalid_argument(ss.str());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-04-03 15:59:05 +02:00
|
|
|
Decimal BPMAtBeat::get_bpm() const {
|
|
|
|
return bpm;
|
|
|
|
}
|
2022-04-09 00:54:06 +02:00
|
|
|
|
|
|
|
double BPMAtBeat::get_bpm_as_double() const {
|
|
|
|
return bpm_as_double;
|
|
|
|
}
|
2022-04-03 15:59:05 +02:00
|
|
|
|
2022-03-31 03:50:15 +02:00
|
|
|
Fraction BPMAtBeat::get_beats() const {
|
|
|
|
return beats;
|
|
|
|
}
|
|
|
|
|
2022-04-09 00:54:06 +02:00
|
|
|
BPMEvent::BPMEvent(Fraction beats_, double seconds_, Decimal bpm_) :
|
|
|
|
bpm(bpm_),
|
|
|
|
bpm_as_double(std::stod(bpm_.format("f"))),
|
|
|
|
beats(beats_),
|
|
|
|
seconds(seconds_)
|
2022-04-03 15:59:05 +02:00
|
|
|
{};
|
2022-03-31 03:50:15 +02:00
|
|
|
|
2022-04-07 00:14:01 +02:00
|
|
|
Decimal BPMEvent::get_bpm() const {
|
|
|
|
return bpm;
|
|
|
|
}
|
2022-04-09 00:54:06 +02:00
|
|
|
|
|
|
|
double BPMEvent::get_bpm_as_double() const {
|
|
|
|
return bpm_as_double;
|
|
|
|
}
|
2022-04-07 00:14:01 +02:00
|
|
|
|
|
|
|
Fraction BPMEvent::get_beats() const {
|
|
|
|
return beats;
|
|
|
|
}
|
|
|
|
|
2022-04-09 00:54:06 +02:00
|
|
|
double BPMEvent::get_seconds() const {
|
2022-03-16 02:10:18 +01:00
|
|
|
return seconds;
|
|
|
|
};
|
2022-04-07 00:14:01 +02:00
|
|
|
|
2022-03-31 03:50:15 +02:00
|
|
|
|
2022-04-07 00:14:01 +02:00
|
|
|
// Default constructor, used when creating a new song from scratch
|
2022-04-09 00:54:06 +02:00
|
|
|
Timing::Timing() :
|
|
|
|
Timing({{120, 0}}, 0)
|
2022-04-01 02:30:32 +02:00
|
|
|
{};
|
2022-03-16 02:10:18 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
Create a Time Map from a list of BPM changes with times given in
|
|
|
|
beats, the offset parameter is more flexible than a "regular" beat zero
|
|
|
|
offset as it accepts non-zero beats
|
|
|
|
*/
|
2022-04-09 00:54:06 +02:00
|
|
|
Timing::Timing(const std::vector<BPMAtBeat>& events, const Decimal& offset_) :
|
|
|
|
offset(offset_),
|
|
|
|
offset_as_double(std::stod(offset_.format("f")))
|
2022-04-07 00:14:01 +02:00
|
|
|
{
|
2022-03-16 02:10:18 +01:00
|
|
|
if (events.empty()) {
|
|
|
|
throw std::invalid_argument(
|
|
|
|
"Attempted to create a Timing object with no BPM events"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-04-04 22:03:10 +02:00
|
|
|
std::multiset<BPMAtBeat, OrderByBeats> grouped_by_beats{events.begin(), events.end()};
|
|
|
|
std::set<BPMAtBeat, OrderByBeats> sorted_events{events.begin(), events.end()};
|
2022-03-16 02:10:18 +01:00
|
|
|
|
|
|
|
for (const auto& bpm_at_beat : sorted_events) {
|
|
|
|
auto [begin, end] = grouped_by_beats.equal_range(bpm_at_beat);
|
|
|
|
if (std::distance(begin, end) > 1) {
|
|
|
|
std::stringstream ss;
|
|
|
|
ss << "Attempted to create a Timing object with multiple ";
|
2022-04-09 00:54:06 +02:00
|
|
|
ss << "BPMs defined at beat " << bpm_at_beat.get_beats().get_str();
|
2022-03-16 02:10:18 +01:00
|
|
|
ss << " :";
|
|
|
|
std::for_each(begin, end, [&ss](auto b){
|
|
|
|
ss << " (bpm: " << b.get_bpm() << ", beat: ";
|
2022-04-09 00:54:06 +02:00
|
|
|
ss << b.get_beats().get_str() << "),";
|
2022-03-16 02:10:18 +01:00
|
|
|
});
|
|
|
|
throw std::invalid_argument(ss.str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto first_event = sorted_events.begin();
|
2022-04-07 00:14:01 +02:00
|
|
|
double current_second = 0;
|
2022-03-16 02:10:18 +01:00
|
|
|
std::vector<BPMEvent> bpm_changes;
|
|
|
|
bpm_changes.reserve(sorted_events.size());
|
|
|
|
bpm_changes.emplace_back(
|
|
|
|
first_event->get_beats(),
|
|
|
|
current_second,
|
|
|
|
first_event->get_bpm()
|
|
|
|
);
|
|
|
|
|
|
|
|
auto previous = first_event;
|
|
|
|
auto current = std::next(first_event);
|
|
|
|
for (; current != sorted_events.end(); ++previous, ++current) {
|
2022-04-09 00:54:06 +02:00
|
|
|
const Fraction beats_since_last_event = current->get_beats() - previous->get_beats();
|
|
|
|
double seconds_since_last_event = beats_since_last_event.get_d() * 60 / previous->get_bpm_as_double();
|
2022-03-16 02:10:18 +01:00
|
|
|
current_second += seconds_since_last_event;
|
|
|
|
bpm_changes.emplace_back(
|
|
|
|
current->get_beats(),
|
|
|
|
current_second,
|
|
|
|
current->get_bpm()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
this->events_by_beats
|
|
|
|
.insert(bpm_changes.begin(), bpm_changes.end());
|
|
|
|
this->events_by_seconds
|
|
|
|
.insert(bpm_changes.begin(), bpm_changes.end());
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2022-04-07 00:14:01 +02:00
|
|
|
Return the amount of seconds at the given beat.
|
2022-03-16 02:10:18 +01:00
|
|
|
|
|
|
|
Before the first bpm change, compute backwards from the first bpm,
|
|
|
|
after the first bpm change, compute forwards from the previous bpm
|
|
|
|
change
|
|
|
|
*/
|
2022-04-07 00:14:01 +02:00
|
|
|
double Timing::seconds_at(Fraction beats) const {
|
2022-03-17 02:50:30 +01:00
|
|
|
auto bpm_change = this->events_by_beats.upper_bound(BPMEvent(beats, 0, 0));
|
2022-03-16 02:10:18 +01:00
|
|
|
if (bpm_change != this->events_by_beats.begin()) {
|
|
|
|
bpm_change = std::prev(bpm_change);
|
|
|
|
}
|
2022-04-09 00:54:06 +02:00
|
|
|
const Fraction beats_since_previous_event = beats - bpm_change->get_beats();
|
|
|
|
double seconds_since_previous_event = beats_since_previous_event.get_d() * 60 / bpm_change->get_bpm_as_double();
|
2022-03-17 02:50:30 +01:00
|
|
|
return (
|
2022-04-09 00:54:06 +02:00
|
|
|
offset_as_double
|
2022-04-07 00:14:01 +02:00
|
|
|
+ bpm_change->get_seconds()
|
|
|
|
+ seconds_since_previous_event
|
2022-03-17 02:50:30 +01:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2022-04-07 00:14:01 +02:00
|
|
|
double Timing::seconds_between(Fraction beat_a, Fraction beat_b) const {
|
|
|
|
return seconds_at(beat_b) - seconds_at(beat_a);
|
|
|
|
};
|
|
|
|
|
2022-03-16 02:10:18 +01:00
|
|
|
sf::Time Timing::time_at(Fraction beats) const {
|
2022-04-07 00:14:01 +02:00
|
|
|
return sf::seconds(seconds_at(beats));
|
2022-03-17 02:50:30 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
sf::Time Timing::time_between(Fraction beat_a, Fraction beat_b) const {
|
2022-04-07 00:14:01 +02:00
|
|
|
return sf::seconds(seconds_between(beat_a, beat_b));
|
2022-03-17 02:50:30 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
Fraction Timing::beats_at(sf::Time time) const {
|
2022-04-07 00:14:01 +02:00
|
|
|
const auto seconds = static_cast<double>(time.asMicroseconds()) / 1000000.0;
|
|
|
|
return beats_at(seconds);
|
|
|
|
};
|
|
|
|
|
|
|
|
Fraction Timing::beats_at(double seconds) const {
|
2022-04-09 00:54:06 +02:00
|
|
|
seconds -= offset_as_double;
|
2022-04-07 00:14:01 +02:00
|
|
|
auto bpm_change = this->events_by_seconds.upper_bound(BPMEvent(0, seconds, 0));
|
2022-03-17 02:50:30 +01:00
|
|
|
if (bpm_change != this->events_by_seconds.begin()) {
|
|
|
|
bpm_change = std::prev(bpm_change);
|
|
|
|
}
|
2022-04-07 00:14:01 +02:00
|
|
|
auto seconds_since_previous_event = seconds - bpm_change->get_seconds();
|
2022-03-17 02:50:30 +01:00
|
|
|
auto beats_since_previous_event = (
|
2022-03-31 03:50:15 +02:00
|
|
|
convert_to_fraction(bpm_change->get_bpm())
|
2022-04-07 00:14:01 +02:00
|
|
|
* Fraction{seconds_since_previous_event}
|
|
|
|
/ 60
|
2022-03-17 02:50:30 +01:00
|
|
|
);
|
|
|
|
return bpm_change->get_beats() + beats_since_previous_event;
|
|
|
|
};
|
2022-03-31 03:50:15 +02:00
|
|
|
|
2022-04-02 04:10:09 +02:00
|
|
|
nlohmann::ordered_json Timing::dump_to_memon_1_0_0() const {
|
2022-03-31 03:50:15 +02:00
|
|
|
nlohmann::ordered_json j;
|
2022-04-07 00:14:01 +02:00
|
|
|
j["offset"] = offset.format("f");
|
2022-03-31 03:50:15 +02:00
|
|
|
auto bpms = nlohmann::ordered_json::array();
|
|
|
|
for (const auto& bpm_change : events_by_beats) {
|
|
|
|
bpms.push_back({
|
|
|
|
{"beat", beat_to_best_form(bpm_change.get_beats())},
|
|
|
|
{"bpm", bpm_change.get_bpm().format("f")}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
j["bpms"] = bpms;
|
|
|
|
return j;
|
2022-04-07 00:14:01 +02:00
|
|
|
};
|
2022-04-02 04:10:09 +02:00
|
|
|
|
2022-04-03 15:59:05 +02:00
|
|
|
Timing Timing::load_from_memon_1_0_0(const nlohmann::json& json) {
|
2022-04-09 00:54:06 +02:00
|
|
|
Decimal offset = 0;
|
2022-04-03 15:59:05 +02:00
|
|
|
if (json.contains("offset")) {
|
|
|
|
const auto string_offset = json["offset"].get<std::string>();
|
2022-04-07 00:14:01 +02:00
|
|
|
offset = Decimal{string_offset};
|
2022-04-03 15:59:05 +02:00
|
|
|
}
|
|
|
|
std::uint64_t resolution = 240;
|
|
|
|
if (json.contains("resolution")) {
|
|
|
|
resolution = json["resolution"].get<std::uint64_t>();
|
|
|
|
}
|
|
|
|
std::vector<BPMAtBeat> bpms;
|
|
|
|
if (not json.contains("bpms")) {
|
|
|
|
bpms = {{120, 0}};
|
|
|
|
} else {
|
|
|
|
for (const auto& bpm_json : json["bpms"]) {
|
|
|
|
try {
|
|
|
|
bpms.emplace_back(
|
|
|
|
Decimal{bpm_json["bpm"].get<std::string>()},
|
|
|
|
load_memon_1_0_0_beat(bpm_json["beat"], resolution)
|
|
|
|
);
|
|
|
|
} catch (const std::exception&) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
bpms,
|
2022-04-07 00:14:01 +02:00
|
|
|
offset
|
2022-04-03 15:59:05 +02:00
|
|
|
};
|
2022-04-07 00:14:01 +02:00
|
|
|
};
|
2022-04-03 15:59:05 +02:00
|
|
|
|
2022-04-02 04:10:09 +02:00
|
|
|
/*
|
2022-04-07 00:14:01 +02:00
|
|
|
In legacy memon, offset is the OPPOSITE of the time (in seconds) at which
|
2022-04-02 04:10:09 +02:00
|
|
|
the first beat occurs in the music file
|
|
|
|
*/
|
2022-04-03 15:59:05 +02:00
|
|
|
Timing Timing::load_from_memon_legacy(const nlohmann::json& metadata) {
|
|
|
|
const auto bpm = Decimal{metadata["BPM"].get<std::string>()};
|
2022-04-07 00:14:01 +02:00
|
|
|
const auto offset = Decimal{metadata["offset"].get<std::string>()};
|
2022-04-09 00:54:06 +02:00
|
|
|
return Timing{{{bpm, 0}}, -1 * offset};
|
2022-04-06 15:47:04 +02:00
|
|
|
};
|
2022-03-16 02:10:18 +01:00
|
|
|
}
|