mirror of
https://gitlab.com/square-game-liberation-front/F.E.I.S.git
synced 2025-02-23 05:30:00 +01:00
clangd is still absolutely sharting itself, I have to go the old-fashioned way and fix compiler errors one at a time ...
This commit is contained in:
parent
3ab51d5bb1
commit
32ea4b309b
@ -75,7 +75,12 @@ namespace better {
|
||||
"Attempted to create a LongNote with a zero-length tail"
|
||||
);
|
||||
}
|
||||
if (tail_tip.get_x() != position.get_x() and tail_tip.get_y() != position.get_y()) {
|
||||
if (
|
||||
not (
|
||||
(tail_tip.get_x() == position.get_x())
|
||||
xor (tail_tip.get_y() == position.get_y())
|
||||
)
|
||||
) {
|
||||
std::stringstream ss;
|
||||
ss << "Attempted to create a LongNote with and invalid tail : ";
|
||||
ss << "position: " << position << " , tail_tip: " << tail_tip;
|
||||
@ -174,6 +179,8 @@ namespace better {
|
||||
return {pos.get_x(), pos.get_y() + length};
|
||||
case 3: // left
|
||||
return {pos.get_x() - length, pos.get_y()};
|
||||
default:
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "better_timing.hpp"
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <json.hpp>
|
||||
|
||||
#include "better_beats.hpp"
|
||||
@ -23,21 +24,28 @@ namespace better {
|
||||
return beats;
|
||||
}
|
||||
|
||||
BPMEvent::BPMEvent(Fraction beats, Fraction seconds, Decimal bpm) :
|
||||
BPMAtBeat(bpm, beats),
|
||||
BPMEvent::BPMEvent(Fraction beats, double seconds, Decimal bpm) :
|
||||
bpm(bpm),
|
||||
beats(beats),
|
||||
seconds(seconds)
|
||||
{};
|
||||
|
||||
Decimal BPMEvent::get_bpm() const {
|
||||
return bpm;
|
||||
}
|
||||
|
||||
Fraction BPMEvent::get_beats() const {
|
||||
return beats;
|
||||
}
|
||||
|
||||
Fraction BPMEvent::get_seconds() const {
|
||||
return seconds;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Create a default-constructed Timing, which corresponds to the fallback
|
||||
timing object from the memon spec : 120 BPM, offset 0
|
||||
*/
|
||||
Timing::Timing():
|
||||
Timing({{120, 0}}, {0,0})
|
||||
// Default constructor, used when creating a new song from scratch
|
||||
Timing::Timing()
|
||||
Timing({120, 0}, 0)
|
||||
{};
|
||||
|
||||
/*
|
||||
@ -45,7 +53,9 @@ namespace better {
|
||||
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 SecondsAtBeat& offset) {
|
||||
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"
|
||||
@ -70,10 +80,8 @@ namespace better {
|
||||
}
|
||||
}
|
||||
|
||||
// First compute everything as if the first BPM change happened at
|
||||
// zero seconds, then shift according to the offset
|
||||
auto first_event = sorted_events.begin();
|
||||
Fraction current_second = 0;
|
||||
double current_second = 0;
|
||||
std::vector<BPMEvent> bpm_changes;
|
||||
bpm_changes.reserve(sorted_events.size());
|
||||
bpm_changes.emplace_back(
|
||||
@ -85,10 +93,11 @@ namespace better {
|
||||
auto previous = first_event;
|
||||
auto current = std::next(first_event);
|
||||
for (; current != sorted_events.end(); ++previous, ++current) {
|
||||
auto beats_since_last_event =
|
||||
Fraction beats_since_last_event =
|
||||
current->get_beats() - previous->get_beats();
|
||||
auto seconds_since_last_event =
|
||||
(60 * beats_since_last_event) / convert_to_fraction(previous->get_bpm());
|
||||
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(),
|
||||
@ -100,86 +109,67 @@ namespace better {
|
||||
.insert(bpm_changes.begin(), bpm_changes.end());
|
||||
this->events_by_seconds
|
||||
.insert(bpm_changes.begin(), bpm_changes.end());
|
||||
auto unshifted_seconds_at_offset =
|
||||
this->fractional_seconds_at(offset.beats);
|
||||
auto shift = offset.seconds - unshifted_seconds_at_offset;
|
||||
std::vector<BPMEvent> shifted_bpm_changes;
|
||||
std::transform(
|
||||
bpm_changes.begin(),
|
||||
bpm_changes.end(),
|
||||
std::back_inserter(shifted_bpm_changes),
|
||||
[shift](const BPMEvent& b){
|
||||
return BPMEvent(
|
||||
b.get_beats(),
|
||||
b.get_seconds() + shift,
|
||||
b.get_bpm()
|
||||
);
|
||||
}
|
||||
);
|
||||
this->events_by_beats
|
||||
.insert(shifted_bpm_changes.begin(), shifted_bpm_changes.end());
|
||||
this->events_by_seconds
|
||||
.insert(shifted_bpm_changes.begin(), shifted_bpm_changes.end());
|
||||
}
|
||||
|
||||
/*
|
||||
Return the number of seconds at the given beat.
|
||||
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
|
||||
*/
|
||||
Fraction Timing::fractional_seconds_at(Fraction beats) const {
|
||||
double Timing::seconds_at(Fraction beats) const {
|
||||
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();
|
||||
auto seconds_since_previous_event = (
|
||||
Fraction{60}
|
||||
* beats_since_previous_event
|
||||
/ convert_to_fraction(bpm_change->get_bpm())
|
||||
static_cast<double>(60 * beats_since_previous_event)
|
||||
/ static_cast<double>(bpm_change->get_bpm())
|
||||
);
|
||||
return (
|
||||
static_cast<double>(offset)
|
||||
+ bpm_change->get_seconds()
|
||||
+ seconds_since_previous_event
|
||||
);
|
||||
return bpm_change->get_seconds() + seconds_since_previous_event;
|
||||
};
|
||||
|
||||
Fraction Timing::fractional_seconds_between(
|
||||
Fraction beat_a,
|
||||
Fraction beat_b
|
||||
) const {
|
||||
return (
|
||||
fractional_seconds_at(beat_b)
|
||||
- fractional_seconds_at(beat_a)
|
||||
);
|
||||
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 frac_to_time(fractional_seconds_at(beats));
|
||||
return sf::seconds(seconds_at(beats));
|
||||
};
|
||||
|
||||
sf::Time Timing::time_between(Fraction beat_a, Fraction beat_b) const {
|
||||
return frac_to_time(fractional_seconds_between(beat_a, beat_b));
|
||||
return sf::seconds(seconds_between(beat_a, beat_b));
|
||||
};
|
||||
|
||||
Fraction Timing::beats_at(sf::Time time) const {
|
||||
Fraction fractional_seconds{convert_to_mpz(static_cast<std::uint64_t>(time.asMicroseconds())), 1000000};
|
||||
auto bpm_change = this->events_by_seconds.upper_bound(BPMEvent(0, fractional_seconds, 0));
|
||||
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));
|
||||
if (bpm_change != this->events_by_seconds.begin()) {
|
||||
bpm_change = std::prev(bpm_change);
|
||||
}
|
||||
auto seconds_since_previous_event = fractional_seconds - bpm_change->get_seconds();
|
||||
auto seconds_since_previous_event = seconds - bpm_change->get_seconds();
|
||||
auto beats_since_previous_event = (
|
||||
convert_to_fraction(bpm_change->get_bpm())
|
||||
* seconds_since_previous_event
|
||||
/ Fraction{60}
|
||||
* Fraction{seconds_since_previous_event}
|
||||
/ 60
|
||||
);
|
||||
return bpm_change->get_beats() + beats_since_previous_event;
|
||||
};
|
||||
|
||||
nlohmann::ordered_json Timing::dump_to_memon_1_0_0() const {
|
||||
nlohmann::ordered_json j;
|
||||
const auto offset = fractional_seconds_at(0);
|
||||
j["offset"] = convert_to_decimal(offset, 5).format("f");
|
||||
j["offset"] = offset.format("f");
|
||||
auto bpms = nlohmann::ordered_json::array();
|
||||
for (const auto& bpm_change : events_by_beats) {
|
||||
bpms.push_back({
|
||||
@ -189,14 +179,13 @@ namespace better {
|
||||
}
|
||||
j["bpms"] = bpms;
|
||||
return j;
|
||||
}
|
||||
};
|
||||
|
||||
Timing Timing::load_from_memon_1_0_0(const nlohmann::json& json) {
|
||||
Fraction offset = 0;
|
||||
double offset = 0;
|
||||
if (json.contains("offset")) {
|
||||
const auto string_offset = json["offset"].get<std::string>();
|
||||
const auto decimal_offset = Decimal{string_offset};
|
||||
offset = convert_to_fraction(decimal_offset);
|
||||
offset = Decimal{string_offset};
|
||||
}
|
||||
std::uint64_t resolution = 240;
|
||||
if (json.contains("resolution")) {
|
||||
@ -219,25 +208,17 @@ namespace better {
|
||||
}
|
||||
return {
|
||||
bpms,
|
||||
{offset, 0}
|
||||
offset
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
For this function, offset is the OPPOSITE of the time (in seconds) at which
|
||||
In legacy memon, offset is the OPPOSITE of the time (in seconds) at which
|
||||
the first beat occurs in the music file
|
||||
*/
|
||||
Timing Timing::load_from_memon_legacy(const nlohmann::json& metadata) {
|
||||
const auto bpm = Decimal{metadata["BPM"].get<std::string>()};
|
||||
const auto offset = convert_to_fraction(Decimal{metadata["offset"].get<std::string>()});
|
||||
const BPMAtBeat bpm_at_beat{bpm, 0};
|
||||
const SecondsAtBeat seconds_at_beat{-1 * offset, 0};
|
||||
return Timing{{bpm_at_beat}, seconds_at_beat};
|
||||
}
|
||||
|
||||
sf::Time frac_to_time(const Fraction& f) {
|
||||
const Fraction microseconds = f * 1000000;
|
||||
const mpz_class approximated = microseconds.get_num() / microseconds.get_den();
|
||||
return sf::microseconds(convert_to_int64(approximated));
|
||||
const auto offset = Decimal{metadata["offset"].get<std::string>()};
|
||||
return Timing{{bpm, 0}, -1 * offset};
|
||||
};
|
||||
}
|
@ -15,7 +15,7 @@
|
||||
|
||||
namespace better {
|
||||
struct SecondsAtBeat {
|
||||
Fraction seconds;
|
||||
Decimal seconds;
|
||||
Fraction beats;
|
||||
};
|
||||
|
||||
@ -29,12 +29,16 @@ namespace better {
|
||||
Fraction beats;
|
||||
};
|
||||
|
||||
class BPMEvent : public BPMAtBeat {
|
||||
class BPMEvent {
|
||||
public:
|
||||
BPMEvent(Fraction beats, Fraction seconds, Decimal bpm);
|
||||
BPMEvent(Fraction beats, double seconds, Decimal bpm);
|
||||
Decimal get_bpm() const;
|
||||
Fraction get_beats() const;
|
||||
Fraction get_seconds() const;
|
||||
private:
|
||||
Fraction seconds;
|
||||
Decimal bpm;
|
||||
Fraction beats;
|
||||
double seconds;
|
||||
};
|
||||
|
||||
struct OrderByBeats {
|
||||
@ -54,24 +58,25 @@ namespace better {
|
||||
class Timing {
|
||||
public:
|
||||
Timing();
|
||||
Timing(const std::vector<BPMAtBeat>& events, const SecondsAtBeat& offset);
|
||||
Timing(const std::vector<BPMAtBeat>& events, const Decimal& offset);
|
||||
|
||||
Fraction fractional_seconds_at(Fraction beats) const;
|
||||
Fraction fractional_seconds_between(Fraction beat_a, Fraction beat_b) const;
|
||||
double seconds_at(Fraction beats) const;
|
||||
double seconds_between(Fraction beat_a, Fraction beat_b) const;
|
||||
sf::Time time_at(Fraction beats) const;
|
||||
sf::Time time_between(Fraction beat_a, Fraction beat_b) const;
|
||||
|
||||
Fraction beats_at(sf::Time time) const;
|
||||
Fraction beats_at(double seconds) const;
|
||||
|
||||
nlohmann::ordered_json dump_to_memon_1_0_0() const;
|
||||
|
||||
static Timing load_from_memon_1_0_0(const nlohmann::json& json);
|
||||
static Timing load_from_memon_legacy(const nlohmann::json& metadata);
|
||||
|
||||
Decimal offset;
|
||||
|
||||
private:
|
||||
std::set<BPMEvent, OrderByBeats> events_by_beats;
|
||||
std::set<BPMEvent, OrderBySeconds> events_by_seconds;
|
||||
};
|
||||
|
||||
sf::Time frac_to_time(const Fraction& f);
|
||||
}
|
@ -58,6 +58,44 @@ EditorState::EditorState(
|
||||
reload_jacket();
|
||||
};
|
||||
|
||||
int EditorState::get_volume() const {
|
||||
return volume;
|
||||
}
|
||||
|
||||
void EditorState::set_volume(int newMusicVolume) {
|
||||
volume = std::clamp(newMusicVolume, 0, 10);
|
||||
if (music) {
|
||||
music->setVolume(Toolbox::convertVolumeToNormalizedDB(volume)*100.f);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorState::volume_up() {
|
||||
set_volume(volume + 1);
|
||||
}
|
||||
|
||||
void EditorState::volume_down() {
|
||||
set_volume(volume - 1);
|
||||
}
|
||||
|
||||
int EditorState::get_speed() const {
|
||||
return speed;
|
||||
}
|
||||
|
||||
void EditorState::set_speed(int newMusicSpeed) {
|
||||
speed = std::clamp(newMusicSpeed, 1, 20);
|
||||
if (music) {
|
||||
music->setPitch(speed / 10.f);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorState::speed_up() {
|
||||
set_speed(speed + 1);
|
||||
}
|
||||
|
||||
void EditorState::speed_down() {
|
||||
set_speed(speed - 1);
|
||||
}
|
||||
|
||||
|
||||
const Interval<sf::Time>& EditorState::get_editable_range() {
|
||||
reload_editable_range();
|
||||
@ -68,14 +106,14 @@ void EditorState::set_playback_position(sf::Time newPosition) {
|
||||
newPosition = std::clamp(newPosition, editable_range.start, editable_range.end);
|
||||
previous_playback_position = newPosition - (sf::seconds(1) / 60.f);
|
||||
playback_position = newPosition;
|
||||
if (music_state) {
|
||||
if (music) {
|
||||
if (
|
||||
playback_position >= sf::Time::Zero
|
||||
and playback_position < music_state->music.getDuration()
|
||||
and playback_position < music->getDuration()
|
||||
) {
|
||||
music_state->music.setPlayingOffset(playback_position);
|
||||
music->setPlayingOffset(playback_position);
|
||||
} else {
|
||||
music_state->music.stop();
|
||||
music->stop();
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -187,7 +225,12 @@ void EditorState::display_playfield(Marker& marker, MarkerEndingState markerEndi
|
||||
(ImVec4) ImColor::HSV(0, 0, 1.f, 0.5f));
|
||||
if (ImGui::ImageButton(playfield.button, {squareSize, squareSize}, 0)) {
|
||||
if (chart_state) {
|
||||
chart_state->toggle_note(playback_position, snap, {x, y});
|
||||
chart_state->toggle_note(
|
||||
playback_position,
|
||||
snap,
|
||||
{x, y},
|
||||
applicable_timing
|
||||
);
|
||||
}
|
||||
}
|
||||
if (ImGui::IsItemHovered() and chart_state and chart_state->creating_long_note) {
|
||||
@ -262,7 +305,7 @@ void EditorState::display_properties() {
|
||||
if (feis::InputTextColored(
|
||||
"Audio",
|
||||
&song.metadata.audio,
|
||||
music_state.has_value(),
|
||||
music.has_value(),
|
||||
"Invalid Audio Path"
|
||||
)) {
|
||||
reload_music();
|
||||
@ -295,9 +338,9 @@ void EditorState::display_properties() {
|
||||
Decimal{0},
|
||||
song.metadata.preview_loop.start
|
||||
);
|
||||
if (music_state.has_value()) {
|
||||
if (music.has_value()) {
|
||||
song.metadata.preview_loop.start = std::min(
|
||||
Decimal{music_state->music.getDuration().asMicroseconds()} / 1000000,
|
||||
Decimal{music->getDuration().asMicroseconds()} / 1000000,
|
||||
song.metadata.preview_loop.start
|
||||
);
|
||||
}
|
||||
@ -307,12 +350,12 @@ void EditorState::display_properties() {
|
||||
Decimal{0},
|
||||
song.metadata.preview_loop.duration
|
||||
);
|
||||
if (music_state.has_value()) {
|
||||
if (music.has_value()) {
|
||||
song.metadata.preview_loop.start = std::min(
|
||||
(
|
||||
Decimal{
|
||||
music_state->music
|
||||
.getDuration()
|
||||
music
|
||||
->getDuration()
|
||||
.asMicroseconds()
|
||||
} / 1000000
|
||||
- song.metadata.preview_loop.start
|
||||
@ -335,7 +378,7 @@ status of the editor. Will appear in the "Editor Status" window
|
||||
void EditorState::display_status() {
|
||||
ImGui::Begin("Status", &showStatus, ImGuiWindowFlags_AlwaysAutoResize);
|
||||
{
|
||||
if (not music_state) {
|
||||
if (not music) {
|
||||
if (not song.metadata.audio.empty()) {
|
||||
ImGui::TextColored(
|
||||
ImVec4(1, 0.42, 0.41, 1),
|
||||
@ -396,12 +439,12 @@ void EditorState::display_playback_status() {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("Beats :");
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted(convert_to_decimal(this->current_exact_beats(), 3).format(".3f").c_str());
|
||||
ImGui::TextUnformatted(fmt::format("{:.3f}", current_exact_beats().get_d()).c_str());
|
||||
ImGui::SameLine();
|
||||
if (music_state) {
|
||||
if (music) {
|
||||
ImGui::TextDisabled("Music File Offset :");
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted(Toolbox::to_string(music_state->music.getPrecisePlayingOffset()).c_str());
|
||||
ImGui::TextUnformatted(Toolbox::to_string(music->getPrecisePlayingOffset()).c_str());
|
||||
ImGui::SameLine();
|
||||
}
|
||||
ImGui::TextDisabled("Timeline Position :");
|
||||
@ -524,6 +567,7 @@ void EditorState::display_linear_view() {
|
||||
if (chart_state) {
|
||||
linear_view.update(
|
||||
*chart_state,
|
||||
applicable_timing,
|
||||
playback_position,
|
||||
ImGui::GetContentRegionMax()
|
||||
);
|
||||
@ -589,7 +633,7 @@ EditorState::SaveOutcome EditorState::ask_to_save_if_needed() {
|
||||
}
|
||||
case EditorState::UserWantsToSave::No:
|
||||
return EditorState::SaveOutcome::UserDeclindedSaving;
|
||||
case EditorState::UserWantsToSave::Cancel:
|
||||
default:
|
||||
return EditorState::SaveOutcome::UserCanceled;
|
||||
}
|
||||
};
|
||||
@ -662,12 +706,21 @@ void EditorState::open_chart(const std::string& name) {
|
||||
reload_applicable_timing();
|
||||
};
|
||||
|
||||
void EditorState::update_visible_notes() {
|
||||
if (chart_state) {
|
||||
chart_state->update_visible_notes(
|
||||
playback_position,
|
||||
applicable_timing
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void EditorState::reload_editable_range() {
|
||||
auto old_range = this->editable_range;
|
||||
Interval<sf::Time> new_range;
|
||||
if (music_state) {
|
||||
new_range += music_state->music.getDuration();
|
||||
if (music) {
|
||||
new_range += music->getDuration();
|
||||
}
|
||||
if (chart_state and not chart_state->chart.notes.empty()) {
|
||||
const auto beat_of_last_event = chart_state->chart.notes.crbegin()->second.get_end();
|
||||
@ -677,7 +730,7 @@ void EditorState::reload_editable_range() {
|
||||
new_range.end += sf::seconds(10);
|
||||
|
||||
// If there is no music, make sure we can edit at least the first whole minute
|
||||
if (not music_state) {
|
||||
if (not music) {
|
||||
new_range += sf::seconds(60);
|
||||
}
|
||||
|
||||
@ -715,15 +768,15 @@ void EditorState::reload_jacket() {
|
||||
*/
|
||||
void EditorState::reload_music() {
|
||||
if (not song_path.has_value() or song.metadata.audio.empty()) {
|
||||
music_state.reset();
|
||||
music.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto absolute_music_path = song_path->parent_path() / song.metadata.audio;
|
||||
try {
|
||||
music_state.emplace(absolute_music_path);
|
||||
music.emplace(absolute_music_path);
|
||||
} catch (const std::exception& e) {
|
||||
music_state.reset();
|
||||
music.reset();
|
||||
}
|
||||
|
||||
reload_editable_range();
|
||||
@ -778,7 +831,7 @@ void EditorState::save(const std::filesystem::path& path) {
|
||||
|
||||
void feis::open(std::optional<EditorState>& ed, std::filesystem::path assets, std::filesystem::path settings) {
|
||||
if (ed and ed->ask_to_save_if_needed() == EditorState::SaveOutcome::UserCanceled) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const char* _filepath = tinyfd_openFileDialog(
|
||||
|
@ -12,7 +12,6 @@
|
||||
#include "history.hpp"
|
||||
#include "history_actions.hpp"
|
||||
#include "marker.hpp"
|
||||
#include "music_state.hpp"
|
||||
#include "notes_clipboard.hpp"
|
||||
#include "notifications_queue.hpp"
|
||||
#include "playfield.hpp"
|
||||
@ -39,7 +38,18 @@ public:
|
||||
|
||||
std::optional<ChartState> chart_state;
|
||||
|
||||
std::optional<MusicState> music_state;
|
||||
std::optional<PreciseMusic> music;
|
||||
|
||||
int get_volume() const;
|
||||
void set_volume(int newMusicVolume);
|
||||
void volume_up();
|
||||
void volume_down();
|
||||
|
||||
/* These speed dials also work when no music is loaded */
|
||||
int get_speed() const;
|
||||
void set_speed(int newMusicSpeed);
|
||||
void speed_up();
|
||||
void speed_down();
|
||||
|
||||
std::optional<sf::Music> preview_audio;
|
||||
|
||||
@ -100,7 +110,7 @@ public:
|
||||
|
||||
SaveOutcome ask_to_save_if_needed();
|
||||
|
||||
void save_if_needed();
|
||||
SaveOutcome save_if_needed();
|
||||
|
||||
bool needs_to_save() const;
|
||||
|
||||
@ -131,8 +141,13 @@ public:
|
||||
|
||||
void open_chart(const std::string& name);
|
||||
|
||||
void update_visible_notes();
|
||||
|
||||
private:
|
||||
|
||||
int volume = 10; // 0 -> 10
|
||||
int speed = 10; // 1 -> 20
|
||||
|
||||
/*
|
||||
sf::Time bounds (in the audio file "coordinates") which are accessible
|
||||
(and maybe editable) from the editor, can extend before and after
|
||||
|
@ -91,12 +91,12 @@ public:
|
||||
if (next_actions.empty()) {
|
||||
last_saved_action = nullptr;
|
||||
} else {
|
||||
last_saved_action = previous_actions.front();
|
||||
last_saved_action = &previous_actions.front();
|
||||
}
|
||||
}
|
||||
|
||||
bool current_state_is_saved() const {
|
||||
return last_saved_action == previous_actions.front();
|
||||
return last_saved_action == &previous_actions.front();
|
||||
};
|
||||
|
||||
private:
|
||||
|
@ -11,7 +11,7 @@ LNMarker::LNMarker(std::filesystem::path folder) :
|
||||
square_background(load_tex_with_prefix<16, 200>(folder, "LN0001_M")),
|
||||
tail_cycle(load_tex_with_prefix<16, 0>(folder, "LN0001_M"))
|
||||
{
|
||||
for (tex& : tail_cycle) {
|
||||
for (auto& tex : tail_cycle) {
|
||||
tex.setRepeated(true);
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ private:
|
||||
template<std::size_t number, unsigned int first = 0>
|
||||
std::array<sf::Texture, number> load_tex_with_prefix(
|
||||
const std::filesystem::path& folder,
|
||||
const std::string& prefix,
|
||||
const std::string& prefix
|
||||
) {
|
||||
std::array<sf::Texture, number> res;
|
||||
for (unsigned int frame = first; frame <= first + number - 1; frame++) {
|
||||
@ -52,17 +52,15 @@ std::array<sf::Texture, number> load_tex_with_prefix(
|
||||
"{prefix}{frame:03}.png",
|
||||
fmt::arg("prefix", prefix),
|
||||
fmt::arg("frame", frame)
|
||||
)
|
||||
std::stringstream filename;
|
||||
filename << prefix << std::setfill('0') << std::setw(3)
|
||||
<< frame << ".png";
|
||||
std::filesystem::path texFile = folder / filename.str();
|
||||
);
|
||||
std::filesystem::path texFile = folder / filename;
|
||||
sf::Texture tex;
|
||||
if (!tex.loadFromFile(texFile.string())) {
|
||||
std::stringstream err;
|
||||
err << "Unable to load texture folder " << folder
|
||||
<< "\nfailed on texture " << filename.str();
|
||||
throw std::runtime_error(err.str());
|
||||
throw std::runtime_error(fmt::format(
|
||||
"Unable to load texture folder {}, failed on texture {}",
|
||||
folder.string(),
|
||||
filename
|
||||
));
|
||||
}
|
||||
tex.setSmooth(true);
|
||||
res.at(frame - first) = tex;
|
||||
|
53
src/main.cpp
53
src/main.cpp
@ -196,12 +196,12 @@ int main() {
|
||||
// Arrow keys
|
||||
case sf::Keyboard::Up:
|
||||
if (event.key.shift) {
|
||||
if (editor_state and editor_state->music_state) {
|
||||
editor_state->music_state->volume_up();
|
||||
if (editor_state) {
|
||||
editor_state->volume_up();
|
||||
notificationsQueue.push(
|
||||
std::make_shared<TextNotification>(fmt::format(
|
||||
"Music Volume : {}%",
|
||||
editor_state->music_state->volume * 10
|
||||
editor_state->get_volume() * 10
|
||||
))
|
||||
);
|
||||
}
|
||||
@ -213,12 +213,12 @@ int main() {
|
||||
break;
|
||||
case sf::Keyboard::Down:
|
||||
if (event.key.shift) {
|
||||
if (editor_state and editor_state->music_state) {
|
||||
editor_state->music_state->volume_down();
|
||||
if (editor_state) {
|
||||
editor_state->volume_down();
|
||||
notificationsQueue.push(
|
||||
std::make_shared<TextNotification>(fmt::format(
|
||||
"Music Volume : {}%",
|
||||
editor_state->music_state->volume * 10
|
||||
editor_state->get_volume() * 10
|
||||
))
|
||||
);
|
||||
}
|
||||
@ -230,11 +230,11 @@ int main() {
|
||||
break;
|
||||
case sf::Keyboard::Left:
|
||||
if (event.key.shift) {
|
||||
if (editor_state and editor_state->music_state) {
|
||||
editor_state->music_state->speed_down();
|
||||
if (editor_state) {
|
||||
editor_state->speed_down();
|
||||
notificationsQueue.push(std::make_shared<TextNotification>(fmt::format(
|
||||
"Speed : {}%",
|
||||
editor_state->music_state->speed * 10
|
||||
editor_state->get_speed() * 10
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
@ -251,11 +251,11 @@ int main() {
|
||||
break;
|
||||
case sf::Keyboard::Right:
|
||||
if (event.key.shift) {
|
||||
if (editor_state and editor_state->music_state) {
|
||||
editor_state->music_state->speed_up();
|
||||
if (editor_state) {
|
||||
editor_state->speed_up();
|
||||
notificationsQueue.push(std::make_shared<TextNotification>(fmt::format(
|
||||
"Speed : {}%",
|
||||
editor_state->music_state->speed * 10
|
||||
editor_state->get_speed() * 10
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
@ -397,28 +397,23 @@ int main() {
|
||||
|
||||
// Audio playback management
|
||||
if (editor_state) {
|
||||
if (editor_state->chart_state) {
|
||||
editor_state->chart_state->update_visible_notes(
|
||||
editor_state->playback_position,
|
||||
editor_state->applicable_timing
|
||||
);
|
||||
}
|
||||
editor_state->update_visible_notes();
|
||||
if (editor_state->playing) {
|
||||
editor_state->previous_playback_position = editor_state->playback_position;
|
||||
editor_state->playback_position += delta * (editor_state->musicSpeed / 10.f);
|
||||
editor_state->playback_position += delta * (editor_state->get_speed() / 10.f);
|
||||
if (editor_state->music) {
|
||||
switch (editor_state->music->getStatus()) {
|
||||
case sf::Music::Stopped:
|
||||
case sf::Music::Paused:
|
||||
if (editor_state->playbackPosition.asSeconds() >= 0
|
||||
and editor_state->playbackPosition
|
||||
if (editor_state->playback_position.asSeconds() >= 0
|
||||
and editor_state->playback_position
|
||||
< editor_state->music->getDuration()) {
|
||||
editor_state->music->setPlayingOffset(editor_state->playbackPosition);
|
||||
editor_state->music->setPlayingOffset(editor_state->playback_position);
|
||||
editor_state->music->play();
|
||||
}
|
||||
break;
|
||||
case sf::Music::Playing:
|
||||
editor_state->playbackPosition =
|
||||
editor_state->playback_position =
|
||||
editor_state->music->getPrecisePlayingOffset();
|
||||
break;
|
||||
default:
|
||||
@ -427,9 +422,9 @@ int main() {
|
||||
}
|
||||
if (beatTick.shouldPlay) {
|
||||
auto previous_tick = static_cast<int>(editor_state->ticks_at(
|
||||
editor_state->previousPos.asSeconds()));
|
||||
editor_state->previous_playback_position.asSeconds()));
|
||||
auto current_tick = static_cast<int>(editor_state->ticks_at(
|
||||
editor_state->playbackPosition.asSeconds()));
|
||||
editor_state->playback_position.asSeconds()));
|
||||
if (previous_tick / editor_state->getResolution()
|
||||
!= current_tick / editor_state->getResolution()) {
|
||||
beatTick.play();
|
||||
@ -439,8 +434,8 @@ int main() {
|
||||
int note_count = 0;
|
||||
for (auto note : editor_state->visibleNotes) {
|
||||
float noteTiming = editor_state->getSecondsAt(note.getTiming());
|
||||
if (noteTiming >= editor_state->previousPos.asSeconds()
|
||||
and noteTiming <= editor_state->playbackPosition.asSeconds()) {
|
||||
if (noteTiming >= editor_state->previous_playback_position.asSeconds()
|
||||
and noteTiming <= editor_state->playback_position.asSeconds()) {
|
||||
note_count++;
|
||||
}
|
||||
}
|
||||
@ -455,9 +450,9 @@ int main() {
|
||||
}
|
||||
}
|
||||
|
||||
if (editor_state->playbackPosition > editor_state->getPreviewEnd()) {
|
||||
if (editor_state->playback_position > editor_state->getPreviewEnd()) {
|
||||
editor_state->playing = false;
|
||||
editor_state->playbackPosition = editor_state->getPreviewEnd();
|
||||
editor_state->playback_position = editor_state->getPreviewEnd();
|
||||
}
|
||||
} else {
|
||||
if (editor_state->music) {
|
||||
|
@ -16,7 +16,6 @@ sources += files(
|
||||
'ln_marker.cpp',
|
||||
'main.cpp',
|
||||
'marker.cpp',
|
||||
'music_state.cpp',
|
||||
'note.cpp',
|
||||
'notes_clipboard.cpp',
|
||||
'notification.cpp',
|
||||
|
@ -1,29 +0,0 @@
|
||||
#include "music_state.hpp"
|
||||
#include "toolbox.hpp"
|
||||
|
||||
|
||||
void MusicState::set_speed(int newMusicSpeed) {
|
||||
speed = std::clamp(newMusicSpeed, 1, 20);
|
||||
music.setPitch(speed / 10.f);
|
||||
}
|
||||
|
||||
void MusicState::speed_up() {
|
||||
set_speed(speed + 1);
|
||||
}
|
||||
|
||||
void MusicState::speed_down() {
|
||||
set_speed(speed - 1);
|
||||
}
|
||||
|
||||
void MusicState::set_volume(int newMusicVolume) {
|
||||
volume = std::clamp(newMusicVolume, 0, 10);
|
||||
music.setVolume(Toolbox::convertVolumeToNormalizedDB(volume)*100.f);
|
||||
}
|
||||
|
||||
void MusicState::volume_up() {
|
||||
set_volume(volume + 1);
|
||||
}
|
||||
|
||||
void MusicState::volume_down() {
|
||||
set_volume(volume - 1);
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include "precise_music.hpp"
|
||||
|
||||
struct MusicState {
|
||||
explicit MusicState(const std::filesystem::path& path) : music(path) {};
|
||||
PreciseMusic music;
|
||||
int volume = 10; // 0 -> 10
|
||||
void set_volume(int newMusicVolume);
|
||||
void volume_up();
|
||||
void volume_down();
|
||||
|
||||
int speed = 10; // 1 -> 20
|
||||
void set_speed(int newMusicSpeed);
|
||||
void speed_up();
|
||||
void speed_down();
|
||||
};
|
@ -7,54 +7,54 @@
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <SFML/Graphics/RenderTexture.hpp>
|
||||
|
||||
// #include "better_note.hpp"
|
||||
// #include "better_timing.hpp"
|
||||
// #include "ln_marker.hpp"
|
||||
// #include "marker.hpp"
|
||||
|
||||
struct LongNote {
|
||||
// template<typename ...Ts>
|
||||
// LongNote(Ts&&... Args) : marker(std::forward<Ts>(Args)...) {};
|
||||
|
||||
LNMarker marker;
|
||||
sf::RenderTexture layer;
|
||||
sf::Sprite backgroud;
|
||||
sf::Sprite outline;
|
||||
sf::Sprite highlight;
|
||||
sf::Sprite tail;
|
||||
sf::Sprite triangle;
|
||||
};
|
||||
#include "better_note.hpp"
|
||||
#include "better_timing.hpp"
|
||||
#include "ln_marker.hpp"
|
||||
#include "marker.hpp"
|
||||
|
||||
class Playfield {
|
||||
// public:
|
||||
// Playfield(std::filesystem::path assets_folder);
|
||||
// sf::Texture base_texture;
|
||||
// sf::Sprite button;
|
||||
// sf::Sprite button_pressed;
|
||||
// sf::Sprite note_selected;
|
||||
// sf::Sprite note_collision;
|
||||
public:
|
||||
Playfield(std::filesystem::path assets_folder);
|
||||
sf::Texture base_texture;
|
||||
sf::Sprite button;
|
||||
sf::Sprite button_pressed;
|
||||
sf::Sprite note_selected;
|
||||
sf::Sprite note_collision;
|
||||
|
||||
// sf::RenderTexture marker_layer;
|
||||
// sf::Sprite marker_sprite;
|
||||
sf::RenderTexture marker_layer;
|
||||
sf::Sprite marker_sprite;
|
||||
|
||||
// LongNote long_note;
|
||||
struct LongNote {
|
||||
template<typename ...Ts>
|
||||
LongNote(Ts&&... Args) : marker(std::forward<Ts>(Args)...) {};
|
||||
|
||||
// void resize(unsigned int width);
|
||||
LNMarker marker;
|
||||
sf::RenderTexture layer;
|
||||
sf::Sprite backgroud;
|
||||
sf::Sprite outline;
|
||||
sf::Sprite highlight;
|
||||
sf::Sprite tail;
|
||||
sf::Sprite triangle;
|
||||
};
|
||||
|
||||
LongNote long_note;
|
||||
|
||||
// void draw_tail_and_receptor(
|
||||
// const better::LongNote& note,
|
||||
// const sf::Time& playbackPosition,
|
||||
// const better::Timing& timing
|
||||
// );
|
||||
void resize(unsigned int width);
|
||||
|
||||
// void draw_long_note(
|
||||
// const better::LongNote& note,
|
||||
// const sf::Time& playbackPosition,
|
||||
// const better::Timing& timing,
|
||||
// Marker& marker,
|
||||
// MarkerEndingState& markerEndingState
|
||||
// );
|
||||
void draw_tail_and_receptor(
|
||||
const better::LongNote& note,
|
||||
const sf::Time& playbackPosition,
|
||||
const better::Timing& timing
|
||||
);
|
||||
|
||||
// private:
|
||||
// const std::filesystem::path texture_path;
|
||||
void draw_long_note(
|
||||
const better::LongNote& note,
|
||||
const sf::Time& playbackPosition,
|
||||
const better::Timing& timing,
|
||||
Marker& marker,
|
||||
MarkerEndingState& markerEndingState
|
||||
);
|
||||
|
||||
private:
|
||||
const std::filesystem::path texture_path;
|
||||
};
|
||||
|
@ -30,15 +30,6 @@ Fraction round_fraction(const Fraction& f) {
|
||||
return floor_fraction(f + Fraction{1, 2});
|
||||
};
|
||||
|
||||
Decimal convert_to_decimal(const Fraction& f, std::uint64_t precision) {
|
||||
const Fraction precision_mod = fast_pow(Fraction{1, 10}, precision);
|
||||
const Fraction floored = f - (f % precision_mod);
|
||||
return (
|
||||
Decimal{convert_to_int64(floored.get_num())}
|
||||
/ Decimal{convert_to_int64(floored.get_den())}
|
||||
);
|
||||
};
|
||||
|
||||
Fraction convert_to_fraction(const Decimal& d) {
|
||||
const auto reduced = d.reduce();
|
||||
const auto sign = reduced.sign();
|
||||
@ -47,7 +38,7 @@ Fraction convert_to_fraction(const Decimal& d) {
|
||||
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);
|
||||
return Fraction{sign > 0 ? 1 : -1} * Fraction{coefficient} / fast_pow(Fraction{10}, -exponent);
|
||||
}
|
||||
};
|
||||
|
||||
@ -61,37 +52,4 @@ Fraction floor_beats(Fraction beats, std::uint64_t denominator) {
|
||||
beats *= denominator;
|
||||
const auto nearest = floor_fraction(beats);
|
||||
return nearest / Fraction{denominator};
|
||||
};
|
||||
|
||||
mpz_class convert_to_mpz(std::uint64_t u) {
|
||||
auto low = static_cast<std::uint32_t>(u);
|
||||
auto high = static_cast<std::uint32_t>(u >> 32);
|
||||
return mpz_class{low} + (mpz_class{high} << 32);
|
||||
};
|
||||
|
||||
mpz_class convert_to_mpz(std::int64_t i) {
|
||||
if (i < 0) {
|
||||
return -1 * convert_to_mpz(static_cast<std::uint64_t>(-1 * i));
|
||||
} else {
|
||||
return convert_to_mpz(static_cast<std::uint64_t>(i));
|
||||
}
|
||||
};
|
||||
|
||||
std::uint64_t convert_to_uint64(const mpz_class& m) {
|
||||
if (m < 0) {
|
||||
throw std::invalid_argument("Cannot convert negative mpz to std::uint64_t");
|
||||
}
|
||||
|
||||
const auto low = static_cast<std::uint64_t>(m.get_ui());
|
||||
const mpz_class high_m = m >> 32;
|
||||
const auto high = static_cast<std::uint64_t>(high_m.get_ui());
|
||||
return low + (high << 32);
|
||||
};
|
||||
|
||||
std::int64_t convert_to_int64(const mpz_class& m) {
|
||||
if (m < 0) {
|
||||
return -1 * static_cast<std::int64_t>(convert_to_uint64(-1 * m));
|
||||
} else {
|
||||
return static_cast<std::int64_t>(convert_to_uint64(m));
|
||||
}
|
||||
};
|
@ -13,7 +13,6 @@ std::strong_ordering operator<=>(const Fraction& lhs, const Fraction& rhs);
|
||||
Fraction operator%(Fraction a, const Fraction& b);
|
||||
Fraction floor_fraction(const Fraction& f);
|
||||
Fraction round_fraction(const Fraction& f);
|
||||
Decimal convert_to_decimal(const Fraction& f, std::uint64_t precision);
|
||||
Fraction convert_to_fraction(const Decimal& d);
|
||||
|
||||
// Rounds a given beat to the nearest given division (defaults to nearest 1/240th)
|
||||
@ -35,9 +34,4 @@ Number fast_pow(const Number base, const std::uint64_t exponent) {
|
||||
n /= 2;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
mpz_class convert_to_mpz(std::uint64_t u);
|
||||
mpz_class convert_to_mpz(std::int64_t i);
|
||||
std::uint64_t convert_to_uint64(const mpz_class& m);
|
||||
std::int64_t convert_to_int64(const mpz_class& m);
|
||||
};
|
@ -1,11 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
template<class A, class B>
|
||||
B apply_or(std::optional<A> opt, B(*func)(A), B b) {
|
||||
B apply_or(std::optional<A> opt, std::function<B(A)> func, B b) {
|
||||
if (opt.has_value()) {
|
||||
return func(*opt);
|
||||
} else {
|
||||
@ -15,14 +16,13 @@ B apply_or(std::optional<A> opt, B(*func)(A), B b) {
|
||||
|
||||
template<class A>
|
||||
std::string stringify_or(std::optional<A> opt, std::string fallback) {
|
||||
auto cb = [](const A& a){
|
||||
std::stringstream ss;
|
||||
ss << a;
|
||||
return ss.str();
|
||||
};
|
||||
return apply_or(
|
||||
opt,
|
||||
&cb,
|
||||
[](const A& a) -> std::string {
|
||||
std::stringstream ss;
|
||||
ss << a;
|
||||
return ss.str();
|
||||
},
|
||||
fallback
|
||||
);
|
||||
}
|
@ -54,7 +54,7 @@ void LinearView::resize(unsigned int width, unsigned int height) {
|
||||
|
||||
void LinearView::update(
|
||||
const ChartState& chart_state,
|
||||
const Timing& timing,
|
||||
const better::Timing& timing,
|
||||
const sf::Time& playback_position,
|
||||
const ImVec2& size
|
||||
) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user