F.E.I.S/src/better_timing.cpp

224 lines
7.5 KiB
C++
Raw Normal View History

#include "better_timing.hpp"
#include <fmt/core.h>
#include <json.hpp>
#include "better_beats.hpp"
#include "src/better_beats.hpp"
namespace better {
2022-04-03 15:59:05 +02:00
BPMAtBeat::BPMAtBeat(Decimal bpm, Fraction beats) : bpm(bpm), beats(beats) {
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;
}
Fraction BPMAtBeat::get_beats() const {
return beats;
}
BPMEvent::BPMEvent(Fraction beats, double seconds, Decimal bpm) :
bpm(bpm),
beats(beats),
2022-04-03 15:59:05 +02:00
seconds(seconds)
{};
Decimal BPMEvent::get_bpm() const {
return bpm;
}
Fraction BPMEvent::get_beats() const {
return beats;
}
Fraction BPMEvent::get_seconds() const {
return seconds;
};
// Default constructor, used when creating a new song from scratch
Timing::Timing()
Timing({120, 0}, 0)
2022-04-01 02:30:32 +02: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
*/
Timing::Timing(const std::vector<BPMAtBeat>& events, const Decimal& new_offset) :
offset(new_offset)
{
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()};
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 ";
ss << "BPMs defined at beat " << bpm_at_beat.get_beats();
ss << " :";
std::for_each(begin, end, [&ss](auto b){
ss << " (bpm: " << b.get_bpm() << ", beat: ";
ss << b.get_beats() << "),";
});
throw std::invalid_argument(ss.str());
}
}
auto first_event = sorted_events.begin();
double current_second = 0;
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) {
Fraction beats_since_last_event =
current->get_beats() - previous->get_beats();
auto seconds_since_last_event =
static_cast<double>(60 * beats_since_last_event)
/ static_cast<double>(previous->get_bpm());
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());
}
/*
Return the amount of seconds at the given beat.
Before the first bpm change, compute backwards from the first bpm,
after the first bpm change, compute forwards from the previous bpm
change
*/
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));
if (bpm_change != this->events_by_beats.begin()) {
bpm_change = std::prev(bpm_change);
}
auto beats_since_previous_event = beats - bpm_change->get_beats();
2022-03-17 02:50:30 +01:00
auto seconds_since_previous_event = (
static_cast<double>(60 * beats_since_previous_event)
/ static_cast<double>(bpm_change->get_bpm())
2022-03-17 02:50:30 +01:00
);
return (
static_cast<double>(offset)
+ bpm_change->get_seconds()
+ seconds_since_previous_event
2022-03-17 02:50:30 +01:00
);
};
double Timing::seconds_between(Fraction beat_a, Fraction beat_b) const {
return seconds_at(beat_b) - seconds_at(beat_a);
};
sf::Time Timing::time_at(Fraction beats) const {
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 {
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 {
const auto seconds = static_cast<double>(time.asMicroseconds()) / 1000000.0;
return beats_at(seconds);
};
Fraction Timing::beats_at(double seconds) const {
seconds -= static_cast<double>(offset);
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);
}
auto seconds_since_previous_event = seconds - bpm_change->get_seconds();
2022-03-17 02:50:30 +01:00
auto beats_since_previous_event = (
convert_to_fraction(bpm_change->get_bpm())
* Fraction{seconds_since_previous_event}
/ 60
2022-03-17 02:50:30 +01:00
);
return bpm_change->get_beats() + beats_since_previous_event;
};
2022-04-02 04:10:09 +02:00
nlohmann::ordered_json Timing::dump_to_memon_1_0_0() const {
nlohmann::ordered_json j;
j["offset"] = offset.format("f");
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-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) {
double offset = 0;
2022-04-03 15:59:05 +02:00
if (json.contains("offset")) {
const auto string_offset = json["offset"].get<std::string>();
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,
offset
2022-04-03 15:59:05 +02:00
};
};
2022-04-03 15:59:05 +02:00
2022-04-02 04:10:09 +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>()};
const auto offset = Decimal{metadata["offset"].get<std::string>()};
return Timing{{bpm, 0}, -1 * offset};
2022-04-06 15:47:04 +02:00
};
}