Start rewriting the song (formely fumen) class from scratch for memon 1.0.0

This commit is contained in:
Stepland 2022-03-16 02:10:18 +01:00
parent 204609c4d0
commit b6fff2914d
32 changed files with 2269 additions and 472 deletions

2
.clangd Normal file
View File

@ -0,0 +1,2 @@
CompileFlags:
Add: [-std=c++20]

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.cache
.idea
.vscode
build

View File

@ -9,13 +9,13 @@ In other words, how to create a new F.E.I.S. executable from the source code.
$ cd F.E.I.S./
```
0. Setup the `build` directory
0. Setup a build directory called `build`
```console
$ meson setup build
```
0. Build
0. Compile in that directory
```console
$ meson compile -C build

1259
include/interval_tree.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@ project(
'cpp',
meson_version : '>=0.55.0',
version : '1.1.0',
default_options : ['cpp_std=c++17'],
default_options : ['cpp_std=c++20'],
)
sources = []

107
src/better_note.cpp Normal file
View File

@ -0,0 +1,107 @@
#include "better_note.hpp"
namespace better {
Position::Position(unsigned int index) : x(index % 4), y (index / 4) {
if (index > 15) {
std::stringstream ss;
ss << "Attempted to create Position from invalid index : " << index;
throw std::invalid_argument(ss.str());
}
};
Position::Position(unsigned int x, unsigned int y) : x(x), y(y) {
if (x > 3 or y > 3) {
std::stringstream ss;
ss << "Attempted to create Position from invalid coordinates : ";
ss << this;
throw std::invalid_argument(ss.str());
}
};
unsigned int Position::index() const {
return x + y*4;
};
unsigned int Position::get_x() const {
return x;
};
unsigned int Position::get_y() const {
return y;
};
std::ostream& operator<< (std::ostream& out, const Position& pos) {
out << "(x: " << pos.get_x() << ", y: " << pos.get_y() << ")";
return out;
};
TapNote::TapNote(Fraction time, Position position): time(time), position(position) {};
Fraction TapNote::get_time() const {
return time;
};
Position TapNote::get_position() const {
return position;
};
LongNote::LongNote(Fraction time, Position position, Fraction duration, Position tail_tip)
:
time(time),
position(position),
duration(duration),
tail_tip(tail_tip)
{
if (duration < 0) {
std::stringstream ss;
ss << "Attempted to create a LongNote with negative duration : ";
ss << duration;
throw std::invalid_argument(ss.str());
}
if (tail_tip.get_x() != position.get_x() and tail_tip.get_y() != position.get_y()) {
std::stringstream ss;
ss << "Attempted to create a LongNote with invalid tail tip : ";
ss << "position: " << position << " , tail_tip: " << tail_tip;
throw std::invalid_argument(ss.str());
}
};
Fraction LongNote::get_time() const {
return time;
};
Position LongNote::get_position() const {
return position;
};
Fraction LongNote::get_end() const {
return time + duration;
};
Fraction LongNote::get_duration() const {
return duration;
};
Position LongNote::get_tail_tip() const {
return tail_tip;
};
auto _time_bounds = VariantVisitor {
[](const TapNote& t) -> std::pair<Fraction, Fraction> { return {t.get_time(), t.get_time()}; },
[](const LongNote& l) -> std::pair<Fraction, Fraction> { return {l.get_time(), l.get_end()}; },
};
std::pair<Fraction, Fraction> Note::get_time_bounds() const {
return std::visit(better::_time_bounds, this->note);
};
Position Note::get_position() const {
return std::visit([](const auto& n){return n.get_position();}, this->note);
};
Fraction Note::get_end() const {
return this->get_time_bounds().second;
}
}

78
src/better_note.hpp Normal file
View File

@ -0,0 +1,78 @@
#pragma once
#include <compare>
#include <ostream>
#include <sstream>
#include <utility>
#include <variant>
#include "special_numeric_types.hpp"
#include "variant_visitor.hpp"
namespace better {
/*
A specific square on the controller. (0, 0) is the top-left button, x
goes right, y goes down.
x
0 1 2 3
y 0
1
2
3
*/
class Position {
public:
explicit Position(unsigned int index);
Position(unsigned int x, unsigned int y);
unsigned int index() const;
unsigned int get_x() const;
unsigned int get_y() const;
auto operator<=>(const Position&) const = default;
private:
unsigned int x;
unsigned int y;
};
std::ostream& operator<<(std::ostream& out, const Position& pos);
class TapNote {
public:
TapNote(Fraction time, Position position);
Fraction get_time() const;
Position get_position() const;
private:
Fraction time;
Position position;
};
class LongNote {
public:
LongNote(Fraction time, Position position, Fraction duration, Position tail_tip);
Fraction get_time() const;
Position get_position() const;
Fraction get_end() const;
Fraction get_duration() const;
Position get_tail_tip() const;
private:
Fraction time;
Position position;
Fraction duration;
Position tail_tip;
};
class Note {
public:
template<typename ...Ts>
Note(Ts&&... Args) : note(std::forward<Ts...>(Args...)) {};
std::pair<Fraction, Fraction> get_time_bounds() const;
Position get_position() const;
Fraction get_end() const;
private:
std::variant<TapNote, LongNote> note;
};
}

23
src/better_notes.cpp Normal file
View File

@ -0,0 +1,23 @@
#include "better_notes.hpp"
namespace better {
std::pair<Notes::iterator, bool> Notes::insert(const Note& note) {
std::optional<Notes::iterator> conflicting_note;
in(
note.get_time_bounds(),
[&](iterator it){
if (it->second.get_position() == note.get_position()) {
if (not conflicting_note.has_value()) {
conflicting_note = it;
}
}
}
);
if (conflicting_note.has_value()) {
return {*conflicting_note, false};
} else {
auto it = interval_tree::insert({note.get_time_bounds(), note});
return {it, true};
}
};
}

18
src/better_notes.hpp Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <algorithm>
#include <array>
#include <type_traits>
#include <utility>
#include "interval_tree.hpp"
#include "better_note.hpp"
#include "special_numeric_types.hpp"
namespace better {
class Notes: public interval_tree<Fraction, Note> {
public:
std::pair<iterator, bool> insert(const Note& note);
};
}

30
src/better_song.cpp Normal file
View File

@ -0,0 +1,30 @@
#include "better_song.hpp"
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());
}
};
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());
}
};
}

67
src/better_song.hpp Normal file
View File

@ -0,0 +1,67 @@
#pragma once
#include <filesystem>
#include <map>
#include <optional>
#include <set>
#include <sstream>
#include <string>
#include <tuple>
#include <SFML/System/Time.hpp>
#include "better_notes.hpp"
#include "better_timing.hpp"
#include "special_numeric_types.hpp"
namespace better {
struct Chart {
std::optional<Decimal> level;
Timing timing;
std::optional<std::set<Fraction>> hakus;
Notes notes;
std::optional<sf::Time> time_of_last_event() const;
};
class PreviewLoop {
public:
PreviewLoop(Decimal start, Decimal duration);
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) {
if (s == "BSC") {
return std::make_tuple(1, std::string{});
} else if (s == "ADV") {
return std::make_tuple(2, std::string{});
} else if (s == "EXT") {
return std::make_tuple(3, std::string{});
} else {
return std::make_tuple(4, s);
}
};
const auto order_by_difficulty_name = [](const std::string& a, const std::string& b) {
return difficulty_name_comp_key(a) < difficulty_name_comp_key(b);
};
struct Song {
std::map<
std::string,
better::Chart,
decltype(order_by_difficulty_name)
> charts{order_by_difficulty_name};
Metadata metadata;
};
}

135
src/better_timing.cpp Normal file
View File

@ -0,0 +1,135 @@
#include "better_timing.hpp"
namespace better {
BPMAtBeat::BPMAtBeat(Fraction beats, Fraction bpm) : beats(beats), bpm(bpm) {
if (bpm <= 0) {
std::stringstream ss;
ss << "Attempted to create a BPMAtBeat with negative BPM : ";
ss << bpm;
throw std::invalid_argument(ss.str());
}
};
BPMEvent::BPMEvent(Fraction beats, Fraction seconds, Fraction bpm) :
BPMAtBeat(beats, bpm),
seconds(seconds)
{};
Fraction BPMEvent::get_seconds() const {
return seconds;
};
/*
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 SecondsAtBeat& offset) {
if (events.empty()) {
throw std::invalid_argument(
"Attempted to create a Timing object with no BPM events"
);
}
std::multiset<
BPMAtBeat,
decltype(order_by_beats)
> grouped_by_beats{
events.begin(), events.end(), order_by_beats
};
std::set<BPMAtBeat, decltype(order_by_beats)> sorted_events{
events.begin(), events.end(), order_by_beats
};
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());
}
}
// 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;
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) {
auto beats_since_last_event =
current->get_beats() - previous->get_beats();
auto seconds_since_last_event =
(60 * beats_since_last_event) / 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());
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.
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 {
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 =
(60 * beats_since_previous_event) / bpm_change->get_bpm();
return bpm_change->get_seconds() + seconds_since_previous_event;
};
sf::Time Timing::time_at(Fraction beats) const {
auto microseconds = fractional_seconds_at(beats) * 1000000;
return sf::microseconds(microseconds.convert_to<sf::Int64>());
}
}

57
src/better_timing.hpp Normal file
View File

@ -0,0 +1,57 @@
#pragma once
#include <SFML/Config.hpp>
#include <algorithm>
#include <iterator>
#include <map>
#include <sstream>
#include <vector>
#include <SFML/System/Time.hpp>
#include "special_numeric_types.hpp"
namespace better {
struct SecondsAtBeat {
Fraction seconds;
Fraction beats;
};
class BPMAtBeat {
public:
BPMAtBeat(Fraction beats, Fraction bpm);
Fraction get_beats() const;
Fraction get_bpm() const;
private:
Fraction beats;
Fraction bpm;
};
class BPMEvent : public BPMAtBeat {
public:
BPMEvent(Fraction beats, Fraction seconds, Fraction bpm);
Fraction get_seconds() const;
private:
Fraction seconds;
};
const auto order_by_beats = [](const BPMAtBeat& a, const BPMAtBeat& b) {
return a.get_beats() < b.get_beats();
};
const auto order_by_seconds = [](const BPMEvent& a, const BPMEvent& b) {
return a.get_seconds() < b.get_seconds();
};
class Timing {
public:
Timing(const std::vector<BPMAtBeat>& events, const SecondsAtBeat& offset);
Fraction fractional_seconds_at(Fraction beats) const;
sf::Time time_at(Fraction beats) const;
private:
std::set<BPMEvent, decltype(order_by_beats)> events_by_beats{order_by_beats};
std::set<BPMEvent, decltype(order_by_seconds)> events_by_seconds{order_by_seconds};
};
}

30
src/chart_state.cpp Normal file
View File

@ -0,0 +1,30 @@
#include "chart_state.hpp"
ChartState::ChartState(better::Chart& c, std::filesystem::path assets) :
chart(c),
density_graph(assets)
{
history.push(std::make_shared<OpenChart>(c));
}
std::optional<Note> ChartState::makeLongNoteDummy(int current_tick) const {
if (creating_long_note and long_note_being_created) {
Note long_note = Note(long_note_being_created->first, long_note_being_created->second);
Note dummy_long_note = Note(
long_note.getPos(),
current_tick,
chart.getResolution(),
long_note.getTail_pos());
return dummy_long_note;
} else {
return {};
}
}
std::optional<Note> ChartState::makeCurrentLongNote() const {
if (creating_long_note and long_note_being_created) {
return Note(long_note_being_created->first, long_note_being_created->second);
} else {
return {};
}
}

View File

@ -1,6 +1,6 @@
#pragma once
#include "chart.hpp"
#include "better_song.hpp"
#include "history.hpp"
#include "history_actions.hpp"
#include "notes_clipboard.hpp"
@ -9,16 +9,16 @@
#include <filesystem>
struct Chart_with_History {
explicit Chart_with_History(Chart& c, std::filesystem::path assets);
Chart& ref;
std::set<Note> selectedNotes;
NotesClipboard notesClipboard;
SelectionState timeSelection;
std::optional<std::pair<Note, Note>> longNoteBeingCreated;
bool creatingLongNote;
struct ChartState {
explicit ChartState(better::Chart& c, std::filesystem::path assets);
better::Chart& chart;
std::set<Note> selected_notes;
NotesClipboard notes_clipboard;
SelectionState time_selection;
std::optional<std::pair<Note, Note>> long_note_being_created;
bool creating_long_note;
History<std::shared_ptr<ActionWithMessage>> history;
DensityGraph densityGraph;
DensityGraph density_graph;
std::optional<Note> makeLongNoteDummy(int current_tick) const;
std::optional<Note> makeCurrentLongNote() const;

View File

@ -1,30 +0,0 @@
#include "chart_with_history.hpp"
Chart_with_History::Chart_with_History(Chart& c, std::filesystem::path assets) :
ref(c),
densityGraph(assets)
{
history.push(std::make_shared<OpenChart>(c));
}
std::optional<Note> Chart_with_History::makeLongNoteDummy(int current_tick) const {
if (creatingLongNote and longNoteBeingCreated) {
Note long_note = Note(longNoteBeingCreated->first, longNoteBeingCreated->second);
Note dummy_long_note = Note(
long_note.getPos(),
current_tick,
ref.getResolution(),
long_note.getTail_pos());
return dummy_long_note;
} else {
return {};
}
}
std::optional<Note> Chart_with_History::makeCurrentLongNote() const {
if (creatingLongNote and longNoteBeingCreated) {
return Note(longNoteBeingCreated->first, longNoteBeingCreated->second);
} else {
return {};
}
}

View File

@ -9,102 +9,87 @@
#include <imgui_stdlib.h>
#include <tinyfiledialogs.h>
EditorState::EditorState(Fumen& fumen, std::filesystem::path assets) :
fumen(fumen),
playfield(assets),
linearView(assets)
{
reloadFromFumen(assets);
}
void EditorState::reloadFromFumen(std::filesystem::path assets) {
if (not this->fumen.Charts.empty()) {
this->chart.emplace(this->fumen.Charts.begin()->second, assets);
} else {
this->chart.reset();
}
reloadMusic();
reloadAlbumCover();
}
/*
* Reloads music from what's indicated in the "music path" field of the fumen
* Reloads music from what's indicated in the "music path" field of the song
* Resets the music state in case anything fails
* Updates playbackPosition and previewEnd as well
* Updates playbackPosition and preview_end as well
*/
void EditorState::reloadMusic() {
const auto music_path = std::filesystem::path(fumen.path).parent_path() / fumen.musicPath;
void EditorState::reload_music() {
const auto absolute_music_path = song_path.parent_path() / edited_music_path;
try {
music.emplace(music_path);
music_state.emplace(absolute_music_path);
} catch (const std::exception& e) {
music.reset();
music_state.reset();
}
reloadPreviewEnd();
reload_preview_end();
auto seconds_position = std::clamp(playbackPosition.asSeconds(), -(fumen.offset), previewEnd.asSeconds());
playbackPosition = sf::seconds(seconds_position);
previousPos = playbackPosition;
auto preview_start = sf::Time::Zero;
if (chart_state) {
preview_start = std::min(preview_start, chart_state->chart.timing.time_at(0));
}
playback_position = std::clamp(playback_position, preview_start, preview_end);
previous_pos = playback_position;
}
void EditorState::reloadPreviewEnd() {
auto old_previewEnd = previewEnd;
float music_duration = 0;
if (music) {
music_duration = music->getDuration().asSeconds();
void EditorState::reload_preview_end() {
auto old_preview_end = this->preview_end;
sf::Time music_duration = sf::Time::Zero;
if (music_state) {
music_duration = music_state->getDuration();
}
float chart_runtime = 0;
float chart_end = 0;
if (chart) {
chart_runtime = fumen.getChartRuntime(chart->ref);
chart_end = chart->ref
.time_of_last_event()
.value_or(sf::Time::Zero)
.asSeconds();
}
// Chart end in seconds using the music file "coordinate system"
// (beat 0 is at -offset seconds)
float chart_end = std::max(chart_runtime - fumen.offset, 0.f);
float preview_end_seconds = std::max(music_duration, chart_end) ;
float preview_end_seconds = std::max(music_duration, chart_end);
// Add some extra time at the end to allow for more notes to be placed
// after the end of the chart
// TODO: is this really the way to do it ?
preview_end_seconds += 2.f;
previewEnd = sf::seconds(preview_end_seconds);
if (old_previewEnd != previewEnd and chart) {
chart->densityGraph.should_recompute = true;
this->preview_end = sf::seconds(preview_end_seconds);
if (old_preview_end != this->preview_end and this->chart.has_value()) {
chart->density_graph.should_recompute = true;
}
}
/*
* Reloads the album cover from what's indicated in the "album cover path" field
* of the fumen Resets the album cover state if anything fails
* of the song Resets the album cover state if anything fails
*/
void EditorState::reloadAlbumCover() {
albumCover.emplace();
void EditorState::reload_album_cover() {
album_cover.emplace();
std::filesystem::path album_cover_path =
std::filesystem::path(fumen.path).parent_path() / fumen.albumCoverPath;
std::filesystem::path(song.path).parent_path() / song.albumCoverPath;
if (fumen.albumCoverPath.empty() or not std::filesystem::exists(album_cover_path)
or not albumCover->loadFromFile(album_cover_path.string())) {
albumCover.reset();
if (song.albumCoverPath.empty() or not std::filesystem::exists(album_cover_path)
or not album_cover->loadFromFile(album_cover_path.string())) {
album_cover.reset();
}
}
void EditorState::setPlaybackAndMusicPosition(sf::Time newPosition) {
reloadPreviewEnd();
void EditorState::set_playback_and_music_position(sf::Time newPosition) {
reload_preview_end();
newPosition = sf::seconds(
std::clamp(
newPosition.asSeconds(),
-fumen.offset,
previewEnd.asSeconds()
-song.offset,
this->preview_end.asSeconds()
)
);
previousPos = sf::seconds(newPosition.asSeconds() - 1.f / 60.f);
playbackPosition = newPosition;
previous_pos = sf::seconds(newPosition.asSeconds() - 1.f / 60.f);
playback_position = newPosition;
if (music) {
if (playbackPosition.asSeconds() >= 0 and playbackPosition < music->getDuration()) {
music->setPlayingOffset(playbackPosition);
if (playback_position.asSeconds() >= 0 and playback_position < music->getDuration()) {
music->setPlayingOffset(playback_position);
} else {
music->stop();
}
@ -119,10 +104,10 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
Toolbox::CustomConstraints::ContentSquare);
if (ImGui::Begin("Playfield", &showPlayfield, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
if (not ImGui::IsWindowHovered() and chart and chart->creatingLongNote) {
if (not ImGui::IsWindowHovered() and chart and chart->creating_long_note) {
// cancel long note creation if the mouse is or goes out of the playfield
chart->longNoteBeingCreated.reset();
chart->creatingLongNote = false;
chart->long_note_being_created.reset();
chart->creating_long_note = false;
}
float squareSize = ImGui::GetWindowSize().x / 4.f;
float TitlebarHeight = ImGui::GetWindowSize().y - ImGui::GetWindowSize().x;
@ -136,15 +121,15 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
if (longNoteDummy) {
playfield.drawLongNote(
*longNoteDummy,
playbackPosition,
playback_position,
getCurrentTick(),
fumen.BPM,
song.BPM,
getResolution());
}
for (auto const& note : visibleNotes) {
float note_offset =
(playbackPosition.asSeconds() - getSecondsAt(note.getTiming()));
(playback_position.asSeconds() - getSecondsAt(note.getTiming()));
// auto frame = static_cast<long long
// int>(std::floor(note_offset * 30.f));
int x = note.getPos() % 4;
@ -166,9 +151,9 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
} else {
playfield.drawLongNote(
note,
playbackPosition,
playback_position,
getCurrentTick(),
fumen.BPM,
song.BPM,
getResolution(),
marker,
markerEndingState);
@ -196,15 +181,15 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
if (ImGui::ImageButton(playfield.button, {squareSize, squareSize}, 0)) {
toggleNoteAtCurrentTime(x + 4 * y);
}
if (ImGui::IsItemHovered() and chart and chart->creatingLongNote) {
if (ImGui::IsItemHovered() and chart and chart->creating_long_note) {
// Deal with long note creation stuff
if (not chart->longNoteBeingCreated) {
if (not chart->long_note_being_created) {
Note current_note =
Note(x + 4 * y, static_cast<int>(roundf(getCurrentTick())));
chart->longNoteBeingCreated =
chart->long_note_being_created =
std::make_pair(current_note, current_note);
} else {
chart->longNoteBeingCreated->second =
chart->long_note_being_created->second =
Note(x + 4 * y, static_cast<int>(roundf(getCurrentTick())));
}
}
@ -216,7 +201,7 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
if (chart) {
// Check for collisions then display them
auto ticks_threshold =
static_cast<int>((1.f / 60.f) * fumen.BPM * getResolution());
static_cast<int>((1.f / 60.f) * song.BPM * get_resolution());
std::array<bool, 16> collisions = {};
@ -239,7 +224,7 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
// Display selected notes
for (auto const& note : visibleNotes) {
if (chart->selectedNotes.find(note) != chart->selectedNotes.end()) {
if (chart->selected_notes.find(note) != chart->selected_notes.end()) {
int x = note.getPos() % 4;
int y = note.getPos() / 4;
ImGui::SetCursorPos({x * squareSize, TitlebarHeight + y * squareSize});
@ -263,36 +248,36 @@ void EditorState::displayProperties() {
{
ImGui::Columns(2, nullptr, false);
if (albumCover) {
ImGui::Image(*albumCover, sf::Vector2f(200, 200));
if (album_cover) {
ImGui::Image(*album_cover, sf::Vector2f(200, 200));
} else {
ImGui::BeginChild("Album Cover", ImVec2(200, 200), true);
ImGui::EndChild();
}
ImGui::NextColumn();
ImGui::InputText("Title", &fumen.songTitle);
ImGui::InputText("Artist", &fumen.artist);
ImGui::InputText("Title", &song.songTitle);
ImGui::InputText("Artist", &song.artist);
if (Toolbox::InputTextColored(
music.has_value(),
"Invalid Music Path",
"Music",
&fumen.musicPath)) {
reloadMusic();
&edited_music_path)) {
reload_music();
}
if (Toolbox::InputTextColored(
albumCover.has_value(),
album_cover.has_value(),
"Invalid Album Cover Path",
"Album Cover",
&fumen.albumCoverPath)) {
reloadAlbumCover();
&song.albumCoverPath)) {
reload_album_cover();
}
if (ImGui::InputFloat("BPM", &fumen.BPM, 1.0f, 10.0f)) {
if (fumen.BPM <= 0.0f) {
fumen.BPM = 0.0f;
if (ImGui::InputFloat("BPM", &song.BPM, 1.0f, 10.0f)) {
if (song.BPM <= 0.0f) {
song.BPM = 0.0f;
}
}
ImGui::InputFloat("offset", &fumen.offset, 0.01f, 1.f);
ImGui::InputFloat("offset", &song.offset, 0.01f, 1.f);
}
ImGui::End();
}
@ -305,11 +290,11 @@ void EditorState::displayStatus() {
ImGui::Begin("Status", &showStatus, ImGuiWindowFlags_AlwaysAutoResize);
{
if (not music) {
if (not fumen.musicPath.empty()) {
if (not song.musicPath.empty()) {
ImGui::TextColored(
ImVec4(1, 0.42, 0.41, 1),
"Invalid music path : %s",
fumen.musicPath.c_str());
song.musicPath.c_str());
} else {
ImGui::TextColored(
ImVec4(1, 0.42, 0.41, 1),
@ -317,20 +302,20 @@ void EditorState::displayStatus() {
}
}
if (not albumCover) {
if (not fumen.albumCoverPath.empty()) {
if (not album_cover) {
if (not song.albumCoverPath.empty()) {
ImGui::TextColored(
ImVec4(1, 0.42, 0.41, 1),
"Invalid albumCover path : %s",
fumen.albumCoverPath.c_str());
song.albumCoverPath.c_str());
} else {
ImGui::TextColored(
ImVec4(1, 0.42, 0.41, 1),
"No albumCover loaded");
}
}
if (ImGui::SliderInt("Music Volume", &musicVolume, 0, 10)) {
setMusicVolume(musicVolume);
if (ImGui::SliderInt("Music Volume", &music_volume, 0, 10)) {
set_music_volume(music_volume);
}
}
ImGui::End();
@ -374,7 +359,7 @@ void EditorState::displayPlaybackStatus() {
}
ImGui::TextColored(ImVec4(0.53, 0.53, 0.53, 1), "Timeline Position :");
ImGui::SameLine();
ImGui::TextUnformatted(Toolbox::to_string(playbackPosition).c_str());
ImGui::TextUnformatted(Toolbox::to_string(playback_position).c_str());
}
ImGui::End();
ImGui::PopStyleVar();
@ -383,35 +368,23 @@ void EditorState::displayPlaybackStatus() {
void EditorState::displayTimeline() {
ImGuiIO& io = ImGui::GetIO();
float height = io.DisplaySize.y * 0.9f;
float raw_height = io.DisplaySize.y * 0.9f;
auto height = static_cast<int>(raw_height);
if (chart) {
if (chart->densityGraph.should_recompute) {
chart->densityGraph.should_recompute = false;
chart->densityGraph.update(
static_cast<int>(height),
getChartRuntime(),
if (
chart.has_value()
and (
chart->density_graph.should_recompute
or height != chart->density_graph.last_height.value_or(height)
)
) {
chart->density_graph.should_recompute = false;
chart->density_graph.update(
height,
chart->ref,
fumen.BPM,
getResolution());
} else {
if (chart->densityGraph.last_height) {
if (static_cast<int>(height) != *(chart->densityGraph.last_height)) {
chart->densityGraph.update(
static_cast<int>(height),
getChartRuntime(),
chart->ref,
fumen.BPM,
getResolution());
}
} else {
chart->densityGraph.update(
static_cast<int>(height),
getChartRuntime(),
chart->ref,
fumen.BPM,
getResolution());
}
song.BPM,
get_resolution()
);
}
}
@ -438,7 +411,7 @@ void EditorState::displayTimeline() {
if (chart) {
ImGui::SetCursorPos({0, 0});
ImGui::Image(chart->densityGraph.graph);
AffineTransform<float> scroll(-fumen.offset, previewEnd.asSeconds(), 1.f, 0.f);
AffineTransform<float> scroll(-song.offset, this->preview_end.asSeconds(), 1.f, 0.f);
float slider_pos = scroll.transform(playbackPosition.asSeconds());
ImGui::SetCursorPos({0, 0});
if (ImGui::VSliderFloat("TimelineSlider", ImGui::GetContentRegionMax(), &slider_pos, 0.f, 1.f, "")) {
@ -453,7 +426,7 @@ void EditorState::displayTimeline() {
void EditorState::displayChartList(std::filesystem::path assets) {
if (ImGui::Begin("Chart List", &showChartList, ImGuiWindowFlags_AlwaysAutoResize)) {
if (this->fumen.Charts.empty()) {
if (this->song.Charts.empty()) {
ImGui::Dummy({100, 0});
ImGui::SameLine();
ImGui::Text("- no charts -");
@ -469,7 +442,7 @@ void EditorState::displayChartList(std::filesystem::path assets) {
ImGui::TextDisabled("Note Count");
ImGui::NextColumn();
ImGui::Separator();
for (auto& tuple : fumen.Charts) {
for (auto& tuple : song.Charts) {
if (ImGui::Selectable(
tuple.first.c_str(),
chart ? chart->ref == tuple.second : false,
@ -497,16 +470,16 @@ void EditorState::displayLinearView() {
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(2, 2));
if (ImGui::Begin("Linear View", &showLinearView, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
if (chart) {
linearView.update(
linear_view.update(
chart,
playbackPosition,
getCurrentTick(),
fumen.BPM,
song.BPM,
getResolution(),
ImGui::GetContentRegionMax());
auto cursor_y = ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2.f;
ImGui::SetCursorPos({0, cursor_y});
ImGui::Image(linearView.view);
ImGui::Image(linear_view.view);
} else {
ImGui::TextDisabled("- no chart selected -");
}
@ -566,7 +539,7 @@ void EditorState::updateVisibleNotes() {
visibleNotes.clear();
if (chart) {
float position = playbackPosition.asSeconds();
float position = playback_position.asSeconds();
for (auto const& note : chart->ref.Notes) {
float note_timing_in_seconds = getSecondsAt(note.getTiming());
@ -613,48 +586,18 @@ void EditorState::toggleNoteAtCurrentTime(int pos) {
}
chart->history.push(std::make_shared<ToggledNotes>(toggledNotes, not deleted_something));
chart->densityGraph.should_recompute = true;
chart->density_graph.should_recompute = true;
}
}
void EditorState::setMusicSpeed(int newMusicSpeed) {
musicSpeed = std::clamp(newMusicSpeed, 1, 20);
if (music) {
music->setPitch(musicSpeed / 10.f);
}
}
void EditorState::musicSpeedUp() {
setMusicSpeed(musicSpeed + 1);
}
void EditorState::musicSpeedDown() {
setMusicSpeed(musicSpeed - 1);
}
void EditorState::setMusicVolume(int newMusicVolume) {
musicVolume = std::clamp(newMusicVolume, 0, 10);
if (music) {
music->setVolume(Toolbox::convertVolumeToNormalizedDB(musicVolume)*100.f);
}
}
void EditorState::musicVolumeUp() {
setMusicVolume(musicVolume + 1);
}
void EditorState::musicVolumeDown() {
setMusicVolume(musicVolume -1 );
}
const sf::Time& EditorState::getPreviewEnd() {
reloadPreviewEnd();
return previewEnd;
const sf::Time& EditorState::get_preview_end() {
reload_preview_end();
return preview_end;
}
void ESHelper::save(EditorState& ed) {
try {
ed.fumen.autoSaveAsMemon();
ed.song.autoSaveAsMemon();
} catch (const std::exception& e) {
tinyfd_messageBox("Error", e.what(), "ok", "error", 1);
}
@ -679,7 +622,7 @@ void ESHelper::openFromFile(
Fumen f(file);
f.autoLoadFromMemon();
ed.emplace(f, assets);
Toolbox::pushNewRecentFile(std::filesystem::canonical(ed->fumen.path), settings);
Toolbox::pushNewRecentFile(std::filesystem::canonical(ed->song.path), settings);
} catch (const std::exception& e) {
tinyfd_messageBox("Error", e.what(), "ok", "error", 1);
}
@ -717,8 +660,8 @@ std::optional<Chart> ESHelper::NewChartDialog::display(EditorState& editorState)
}
if (ImGui::BeginCombo("Difficulty", comboPreview.c_str())) {
for (auto dif_name : {"BSC", "ADV", "EXT"}) {
if (editorState.fumen.Charts.find(dif_name)
== editorState.fumen.Charts.end()) {
if (editorState.song.Charts.find(dif_name)
== editorState.song.Charts.end()) {
if (ImGui::Selectable(dif_name, dif_name == difficulty)) {
showCustomDifName = false;
difficulty = dif_name;
@ -735,8 +678,8 @@ std::optional<Chart> ESHelper::NewChartDialog::display(EditorState& editorState)
}
if (showCustomDifName) {
Toolbox::InputTextColored(
editorState.fumen.Charts.find(difficulty)
== editorState.fumen.Charts.end(),
editorState.song.Charts.find(difficulty)
== editorState.song.Charts.end(),
"Chart name has to be unique",
"Difficulty Name",
&difficulty);
@ -765,8 +708,8 @@ std::optional<Chart> ESHelper::NewChartDialog::display(EditorState& editorState)
}
ImGui::Separator();
if (difficulty.empty()
or (editorState.fumen.Charts.find(difficulty)
!= editorState.fumen.Charts.end())) {
or (editorState.song.Charts.find(difficulty)
!= editorState.song.Charts.end())) {
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
ImGui::Button("Create Chart##New Chart");
@ -798,7 +741,7 @@ void ESHelper::ChartPropertiesDialog::display(EditorState& editorState, std::fil
std::set<std::string> difNames {"BSC", "ADV", "EXT"};
showCustomDifName = (difNames.find(difficulty_name) == difNames.end());
for (auto const& tuple : editorState.fumen.Charts) {
for (auto const& tuple : editorState.song.Charts) {
if (tuple.second != editorState.chart->ref) {
difNamesInUse.insert(tuple.first);
}
@ -855,17 +798,17 @@ void ESHelper::ChartPropertiesDialog::display(EditorState& editorState, std::fil
if (ImGui::Button("Apply##New Chart")) {
try {
Chart modified_chart =
editorState.fumen.Charts.at(editorState.chart->ref.dif_name);
editorState.fumen.Charts.erase(editorState.chart->ref.dif_name);
editorState.song.Charts.at(editorState.chart->ref.dif_name);
editorState.song.Charts.erase(editorState.chart->ref.dif_name);
modified_chart.dif_name = this->difficulty_name;
modified_chart.level = this->level;
if (not(editorState.fumen.Charts.emplace(modified_chart.dif_name, modified_chart))
if (not(editorState.song.Charts.emplace(modified_chart.dif_name, modified_chart))
.second) {
throw std::runtime_error(
"Could not insert modified chart in fumen");
"Could not insert modified chart in song");
} else {
editorState.chart.emplace(
editorState.fumen.Charts.at(modified_chart.dif_name),
editorState.song.Charts.at(modified_chart.dif_name),
assets
);
shouldRefreshValues = true;

View File

@ -4,11 +4,12 @@
#include <SFML/Graphics.hpp>
#include <optional>
#include "chart_with_history.hpp"
#include "fumen.hpp"
#include "better_song.hpp"
#include "chart_state.hpp"
#include "history.hpp"
#include "history_actions.hpp"
#include "marker.hpp"
#include "music_state.hpp"
#include "notes_clipboard.hpp"
#include "precise_music.hpp"
#include "time_selection.hpp"
@ -31,73 +32,64 @@ enum saveChangesResponses {
*/
class EditorState {
public:
EditorState(Fumen& fumen, std::filesystem::path assets);
EditorState(
const better::Song& song,
const std::filesystem::path& song_path,
const std::filesystem::path& assets
) :
song(song),
playfield(assets),
linear_view(assets),
edited_music_path(song.metadata.audio.value_or("")),
song_path(song_path)
{
if (not this->song.charts.empty()) {
this->chart_state.emplace(this->song.charts.begin()->second, assets);
}
reload_music();
reload_album_cover();
};
std::optional<Chart_with_History> chart;
better::Song song;
std::optional<ChartState> chart_state;
Fumen fumen;
std::optional<MusicState> music_state;
Playfield playfield;
LinearView linearView;
LinearView linear_view;
// the snap but divided by 4 because you can't set a snap to anything lower
// than 4ths
int snap = 1;
std::optional<PreciseMusic> music;
int musicVolume = 10; // 0 -> 10
void setMusicVolume(int newMusicVolume);
void musicVolumeUp();
void musicVolumeDown();
int musicSpeed = 10; // 1 -> 20
void setMusicSpeed(int newMusicSpeed);
void musicSpeedUp();
void musicSpeedDown();
std::optional<sf::Texture> albumCover;
std::optional<sf::Texture> album_cover;
bool playing;
sf::Time previousPos;
sf::Time playbackPosition;
sf::Time previous_pos;
sf::Time playback_position;
private:
sf::Time previewEnd; // sf::Time (in the audio file "coordinates") at which the chart preview stops, can be
// after the end of the actual audio file
const sf::Time& get_preview_end();
public:
const sf::Time& getPreviewEnd();
void set_playback_and_music_position(sf::Time new_position);
public:
void setPlaybackAndMusicPosition(sf::Time newPosition);
float getBeats() { return getBeatsAt(playbackPosition.asSeconds()); };
float getBeats() { return getBeatsAt(playback_position.asSeconds()); };
float getBeatsAt(float seconds) {
return ((seconds + fumen.offset) / 60.f) * fumen.BPM;
return ((seconds + song.offset) / 60.f) * song.BPM;
};
float getCurrentTick() { return getTicksAt(playbackPosition.asSeconds()); };
float getCurrentTick() { return getTicksAt(playback_position.asSeconds()); };
float getTicksAt(float seconds) {
return getBeatsAt(seconds) * getResolution();
return getBeatsAt(seconds) * get_resolution();
}
float getSecondsAt(int tick) {
return (60.f * tick) / (fumen.BPM * getResolution()) - fumen.offset;
return (60.f * tick) / (song.BPM * get_resolution()) - song.offset;
};
int getResolution() { return chart ? chart->ref.getResolution() : 240; };
int getSnapStep() { return getResolution() / snap; };
int get_resolution() { return chart_state ? chart_state->chart.getResolution() : 240; };
int get_snap_step() { return get_resolution() / snap; };
float ticksToSeconds(int ticks) {
return (60.f * ticks) / (fumen.BPM * getResolution());
};
float getChartRuntime() {
return getPreviewEnd().asSeconds() + fumen.offset;
};
void reloadFromFumen(std::filesystem::path assets);
void reloadMusic();
void reloadAlbumCover();
void reloadPreviewEnd();
void reload_album_cover();
void reload_preview_end();
bool showPlayfield = true;
bool showProperties;
@ -126,6 +118,15 @@ public:
std::set<Note> visibleNotes;
void toggleNoteAtCurrentTime(int pos);
private:
sf::Time preview_end; // sf::Time (in the audio file "coordinates") at which the chart preview stops, can be
// after the end of the actual audio file
std::string edited_music_path;
void reload_music();
std::filesystem::path song_path;
};
namespace ESHelper {

View File

@ -4,7 +4,7 @@ void Move::backwardsInTime(std::optional<EditorState>& ed) {
if (ed and ed->chart) {
float floatTicks = ed->getCurrentTick();
auto prevTick = static_cast<int>(floorf(floatTicks));
int step = ed->getSnapStep();
int step = ed->get_snap_step();
int prevTickInSnap = prevTick;
if (prevTick % step == 0) {
prevTickInSnap -= step;
@ -19,7 +19,7 @@ void Move::forwardsInTime(std::optional<EditorState>& ed) {
if (ed and ed->chart) {
float floatTicks = ed->getCurrentTick();
auto nextTick = static_cast<int>(ceilf(floatTicks));
int step = ed->getSnapStep();
int step = ed->get_snap_step();
int nextTickInSnap = nextTick + (step - nextTick % step);
ed->setPlaybackAndMusicPosition(sf::seconds(ed->getSecondsAt(nextTickInSnap)));
}

View File

@ -139,7 +139,7 @@ void Fumen::saveAsMemon(std::filesystem::path path) {
* Returns how long the chart is in seconds as a float, from beat 0 to the last
* note
*/
float Fumen::getChartRuntime(Chart c) {
float Fumen::get_chart_runtime(Chart c) {
if (!c.Notes.empty()) {
Note last_note = *c.Notes.rbegin();
auto beats = static_cast<float>(last_note.getTiming()) / c.getResolution();

View File

@ -53,5 +53,5 @@ public:
float BPM;
float offset;
float getChartRuntime(Chart c);
float get_chart_runtime(Chart c);
};

View File

@ -76,7 +76,7 @@ int main(int argc, char** argv) {
MarkerEndingState markerEndingState = preferences.markerEndingState;
BlankScreen bg{assets_folder};
std::optional<EditorState> editorState;
std::optional<EditorState> editor_state;
NotificationsQueue notificationsQueue;
ESHelper::NewChartDialog newChartDialog;
ESHelper::ChartPropertiesDialog chartPropertiesDialog;
@ -90,7 +90,7 @@ int main(int argc, char** argv) {
switch (event.type) {
case sf::Event::Closed:
preferences.save();
if (ESHelper::saveOrCancel(editorState)) {
if (ESHelper::saveOrCancel(editor_state)) {
window.close();
}
break;
@ -101,8 +101,8 @@ int main(int argc, char** argv) {
case sf::Event::MouseButtonPressed:
switch (event.mouseButton.button) {
case sf::Mouse::Button::Right:
if (editorState and editorState->chart) {
editorState->chart->creatingLongNote = true;
if (editor_state and editor_state->chart_state) {
editor_state->chart_state->creating_long_note = true;
}
break;
default:
@ -112,15 +112,15 @@ int main(int argc, char** argv) {
case sf::Event::MouseButtonReleased:
switch (event.mouseButton.button) {
case sf::Mouse::Button::Right:
if (editorState and editorState->chart) {
if (editorState->chart->longNoteBeingCreated) {
auto pair = *editorState->chart->longNoteBeingCreated;
if (editor_state and editor_state->chart_state) {
if (editor_state->chart_state->longNoteBeingCreated) {
auto pair = *editor_state->chart->longNoteBeingCreated;
Note new_note = Note(pair.first, pair.second);
std::set<Note> new_note_set = {new_note};
editorState->chart->ref.Notes.insert(new_note);
editorState->chart->longNoteBeingCreated.reset();
editorState->chart->creatingLongNote = false;
editorState->chart->history.push(
editor_state->chart->ref.Notes.insert(new_note);
editor_state->chart->longNoteBeingCreated.reset();
editor_state->chart->creatingLongNote = false;
editor_state->chart->history.push(
std::make_shared<ToggledNotes>(new_note_set, true));
}
}
@ -136,11 +136,11 @@ int main(int argc, char** argv) {
std::floor(event.mouseWheelScroll.delta));
if (delta >= 0) {
for (int i = 0; i < delta; ++i) {
Move::backwardsInTime(editorState);
Move::backwardsInTime(editor_state);
}
} else {
for (int i = 0; i < -delta; ++i) {
Move::forwardsInTime(editorState);
Move::forwardsInTime(editor_state);
}
}
} break;
@ -157,43 +157,43 @@ int main(int argc, char** argv) {
// Discard, in that order : timeSelection,
// selected_notes
case sf::Keyboard::Escape:
if (editorState and editorState->chart) {
if (editor_state and editor_state->chart) {
if (not std::holds_alternative<std::monostate>(
editorState->chart->timeSelection)) {
editorState->chart->timeSelection.emplace<std::monostate>();
} else if (not editorState->chart->selectedNotes.empty()) {
editorState->chart->selectedNotes.clear();
editor_state->chart->timeSelection)) {
editor_state->chart->timeSelection.emplace<std::monostate>();
} else if (not editor_state->chart->selectedNotes.empty()) {
editor_state->chart->selectedNotes.clear();
}
}
break;
// Modify timeSelection
case sf::Keyboard::Tab:
if (editorState and editorState->chart) {
if (editor_state and editor_state->chart) {
// if no timeSelection was previously made
if (std::holds_alternative<std::monostate>(
editorState->chart->timeSelection)) {
editor_state->chart->timeSelection)) {
// set the start of the timeSelection to the
// current time
editorState->chart->timeSelection =
editor_state->chart->timeSelection =
static_cast<unsigned int>(
editorState->getCurrentTick());
editor_state->getCurrentTick());
// if the start of the timeSelection is
// already set
} else if (std::holds_alternative<unsigned int>(
editorState->chart->timeSelection)) {
editor_state->chart->timeSelection)) {
auto current_tick =
static_cast<int>(editorState->getCurrentTick());
static_cast<int>(editor_state->getCurrentTick());
auto selection_start =
static_cast<int>(std::get<unsigned int>(
editorState->chart->timeSelection));
editor_state->chart->timeSelection));
// if we are on the same tick as the
// timeSelection start we discard the
// timeSelection
if (current_tick == selection_start) {
editorState->chart->timeSelection.emplace<std::monostate>();
editor_state->chart->timeSelection.emplace<std::monostate>();
// else we create a full timeSelection
// while paying attention to the order
@ -202,24 +202,24 @@ int main(int argc, char** argv) {
std::min(current_tick, selection_start));
auto duration = static_cast<unsigned int>(
std::abs(current_tick - selection_start));
editorState->chart->timeSelection.emplace<TimeSelection>(
editor_state->chart->timeSelection.emplace<TimeSelection>(
new_selection_start,
duration);
editorState->chart->selectedNotes =
editorState->chart->ref.getNotesBetween(
editor_state->chart->selectedNotes =
editor_state->chart->ref.getNotesBetween(
new_selection_start,
new_selection_start + duration);
}
// if a full timeSelection already exists
} else if (std::holds_alternative<TimeSelection>(
editorState->chart->timeSelection)) {
editor_state->chart->timeSelection)) {
// discard the current timeSelection and set
// the start of the timeSelection to the
// current time
editorState->chart->timeSelection =
editor_state->chart->timeSelection =
static_cast<unsigned int>(
editorState->getCurrentTick());
editor_state->getCurrentTick());
}
}
break;
@ -227,7 +227,7 @@ int main(int argc, char** argv) {
// Delete selected notes from the chart and discard
// timeSelection
case sf::Keyboard::Delete:
Edit::delete_(editorState, notificationsQueue);
Edit::delete_(editor_state, notificationsQueue);
break;
/*
@ -235,49 +235,49 @@ int main(int argc, char** argv) {
*/
case sf::Keyboard::Up:
if (event.key.shift) {
if (editorState) {
editorState->musicVolumeUp();
if (editor_state) {
editor_state->musicVolumeUp();
std::stringstream ss;
ss << "Music Volume : "
<< editorState->musicVolume * 10 << "%";
<< editor_state->musicVolume * 10 << "%";
notificationsQueue.push(
std::make_shared<TextNotification>(ss.str()));
}
} else {
Move::backwardsInTime(editorState);
Move::backwardsInTime(editor_state);
}
break;
case sf::Keyboard::Down:
if (event.key.shift) {
if (editorState) {
editorState->musicVolumeDown();
if (editor_state) {
editor_state->musicVolumeDown();
std::stringstream ss;
ss << "Music Volume : "
<< editorState->musicVolume * 10 << "%";
<< editor_state->musicVolume * 10 << "%";
notificationsQueue.push(
std::make_shared<TextNotification>(ss.str()));
}
} else {
Move::forwardsInTime(editorState);
Move::forwardsInTime(editor_state);
}
break;
case sf::Keyboard::Left:
if (event.key.shift) {
if (editorState) {
editorState->musicSpeedDown();
if (editor_state) {
editor_state->musicSpeedDown();
std::stringstream ss;
ss << "Speed : " << editorState->musicSpeed * 10 << "%";
ss << "Speed : " << editor_state->musicSpeed * 10 << "%";
notificationsQueue.push(
std::make_shared<TextNotification>(ss.str()));
}
} else {
if (editorState and editorState->chart) {
editorState->snap = Toolbox::getPreviousDivisor(
editorState->chart->ref.getResolution(),
editorState->snap);
if (editor_state and editor_state->chart) {
editor_state->snap = Toolbox::getPreviousDivisor(
editor_state->chart->ref.getResolution(),
editor_state->snap);
std::stringstream ss;
ss << "Snap : "
<< Toolbox::toOrdinal(4 * editorState->snap);
<< Toolbox::toOrdinal(4 * editor_state->snap);
notificationsQueue.push(
std::make_shared<TextNotification>(ss.str()));
}
@ -285,19 +285,19 @@ int main(int argc, char** argv) {
break;
case sf::Keyboard::Right:
if (event.key.shift) {
editorState->musicSpeedUp();
editor_state->musicSpeedUp();
std::stringstream ss;
ss << "Speed : " << editorState->musicSpeed * 10 << "%";
ss << "Speed : " << editor_state->musicSpeed * 10 << "%";
notificationsQueue.push(
std::make_shared<TextNotification>(ss.str()));
} else {
if (editorState and editorState->chart) {
editorState->snap = Toolbox::getNextDivisor(
editorState->chart->ref.getResolution(),
editorState->snap);
if (editor_state and editor_state->chart) {
editor_state->snap = Toolbox::getNextDivisor(
editor_state->chart->ref.getResolution(),
editor_state->snap);
std::stringstream ss;
ss << "Snap : "
<< Toolbox::toOrdinal(4 * editorState->snap);
<< Toolbox::toOrdinal(4 * editor_state->snap);
notificationsQueue.push(
std::make_shared<TextNotification>(ss.str()));
}
@ -339,21 +339,21 @@ int main(int argc, char** argv) {
break;
case sf::Keyboard::Space:
if (not ImGui::GetIO().WantTextInput) {
if (editorState) {
editorState->playing = not editorState->playing;
if (editor_state) {
editor_state->playing = not editor_state->playing;
}
}
break;
case sf::Keyboard::Add:
if (editorState) {
editorState->linearView.zoom_in();
if (editor_state) {
editor_state->linearView.zoom_in();
notificationsQueue.push(std::make_shared<TextNotification>(
"Zoom in"));
}
break;
case sf::Keyboard::Subtract:
if (editorState) {
editorState->linearView.zoom_out();
if (editor_state) {
editor_state->linearView.zoom_out();
notificationsQueue.push(std::make_shared<TextNotification>(
"Zoom out"));
}
@ -363,46 +363,46 @@ int main(int argc, char** argv) {
*/
case sf::Keyboard::C:
if (event.key.control) {
Edit::copy(editorState, notificationsQueue);
Edit::copy(editor_state, notificationsQueue);
}
break;
case sf::Keyboard::O:
if (event.key.control) {
if (ESHelper::saveOrCancel(editorState)) {
ESHelper::open(editorState, assets_folder, settings_folder);
if (ESHelper::saveOrCancel(editor_state)) {
ESHelper::open(editor_state, assets_folder, settings_folder);
}
}
break;
case sf::Keyboard::P:
if (event.key.shift) {
editorState->showProperties = true;
editor_state->showProperties = true;
}
break;
case sf::Keyboard::S:
if (event.key.control) {
ESHelper::save(*editorState);
ESHelper::save(*editor_state);
notificationsQueue.push(std::make_shared<TextNotification>(
"Saved file"));
}
break;
case sf::Keyboard::V:
if (event.key.control) {
Edit::paste(editorState, notificationsQueue);
Edit::paste(editor_state, notificationsQueue);
}
break;
case sf::Keyboard::X:
if (event.key.control) {
Edit::cut(editorState, notificationsQueue);
Edit::cut(editor_state, notificationsQueue);
}
break;
case sf::Keyboard::Y:
if (event.key.control) {
Edit::redo(editorState, notificationsQueue);
Edit::redo(editor_state, notificationsQueue);
}
break;
case sf::Keyboard::Z:
if (event.key.control) {
Edit::undo(editorState, notificationsQueue);
Edit::undo(editor_state, notificationsQueue);
}
break;
default:
@ -418,46 +418,46 @@ int main(int argc, char** argv) {
ImGui::SFML::Update(window, delta);
// Audio playback management
if (editorState) {
editorState->updateVisibleNotes();
if (editorState->playing) {
editorState->previousPos = editorState->playbackPosition;
editorState->playbackPosition += delta * (editorState->musicSpeed / 10.f);
if (editorState->music) {
switch (editorState->music->getStatus()) {
if (editor_state) {
editor_state->updateVisibleNotes();
if (editor_state->playing) {
editor_state->previousPos = editor_state->playbackPosition;
editor_state->playbackPosition += delta * (editor_state->musicSpeed / 10.f);
if (editor_state->music) {
switch (editor_state->music->getStatus()) {
case sf::Music::Stopped:
case sf::Music::Paused:
if (editorState->playbackPosition.asSeconds() >= 0
and editorState->playbackPosition
< editorState->music->getDuration()) {
editorState->music->setPlayingOffset(editorState->playbackPosition);
editorState->music->play();
if (editor_state->playbackPosition.asSeconds() >= 0
and editor_state->playbackPosition
< editor_state->music->getDuration()) {
editor_state->music->setPlayingOffset(editor_state->playbackPosition);
editor_state->music->play();
}
break;
case sf::Music::Playing:
editorState->playbackPosition =
editorState->music->getPrecisePlayingOffset();
editor_state->playbackPosition =
editor_state->music->getPrecisePlayingOffset();
break;
default:
break;
}
}
if (beatTick.shouldPlay) {
auto previous_tick = static_cast<int>(editorState->getTicksAt(
editorState->previousPos.asSeconds()));
auto current_tick = static_cast<int>(editorState->getTicksAt(
editorState->playbackPosition.asSeconds()));
if (previous_tick / editorState->getResolution()
!= current_tick / editorState->getResolution()) {
auto previous_tick = static_cast<int>(editor_state->getTicksAt(
editor_state->previousPos.asSeconds()));
auto current_tick = static_cast<int>(editor_state->getTicksAt(
editor_state->playbackPosition.asSeconds()));
if (previous_tick / editor_state->getResolution()
!= current_tick / editor_state->getResolution()) {
beatTick.play();
}
}
if (noteTick.shouldPlay) {
int note_count = 0;
for (auto note : editorState->visibleNotes) {
float noteTiming = editorState->getSecondsAt(note.getTiming());
if (noteTiming >= editorState->previousPos.asSeconds()
and noteTiming <= editorState->playbackPosition.asSeconds()) {
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()) {
note_count++;
}
}
@ -472,69 +472,69 @@ int main(int argc, char** argv) {
}
}
if (editorState->playbackPosition > editorState->getPreviewEnd()) {
editorState->playing = false;
editorState->playbackPosition = editorState->getPreviewEnd();
if (editor_state->playbackPosition > editor_state->getPreviewEnd()) {
editor_state->playing = false;
editor_state->playbackPosition = editor_state->getPreviewEnd();
}
} else {
if (editorState->music) {
if (editorState->music->getStatus() == sf::Music::Playing) {
editorState->music->pause();
if (editor_state->music) {
if (editor_state->music->getStatus() == sf::Music::Playing) {
editor_state->music->pause();
}
}
}
}
// Drawing
if (editorState) {
if (editor_state) {
window.clear(sf::Color(0, 0, 0));
if (editorState->showHistory) {
editorState->chart->history.display(get_message);
if (editor_state->showHistory) {
editor_state->chart->history.display(get_message);
}
if (editorState->showPlayfield) {
editorState->displayPlayfield(marker, markerEndingState);
if (editor_state->showPlayfield) {
editor_state->displayPlayfield(marker, markerEndingState);
}
if (editorState->showLinearView) {
editorState->displayLinearView();
if (editor_state->showLinearView) {
editor_state->displayLinearView();
}
if (editorState->linearView.shouldDisplaySettings) {
editorState->linearView.displaySettings();
if (editor_state->linearView.shouldDisplaySettings) {
editor_state->linearView.displaySettings();
}
if (editorState->showProperties) {
editorState->displayProperties();
if (editor_state->showProperties) {
editor_state->displayProperties();
}
if (editorState->showStatus) {
editorState->displayStatus();
if (editor_state->showStatus) {
editor_state->displayStatus();
}
if (editorState->showPlaybackStatus) {
editorState->displayPlaybackStatus();
if (editor_state->showPlaybackStatus) {
editor_state->displayPlaybackStatus();
}
if (editorState->showTimeline) {
editorState->displayTimeline();
if (editor_state->showTimeline) {
editor_state->displayTimeline();
}
if (editorState->showChartList) {
editorState->displayChartList(assets_folder);
if (editor_state->showChartList) {
editor_state->displayChartList(assets_folder);
}
if (editorState->showNewChartDialog) {
std::optional<Chart> c = newChartDialog.display(*editorState);
if (editor_state->showNewChartDialog) {
std::optional<Chart> c = newChartDialog.display(*editor_state);
if (c) {
editorState->showNewChartDialog = false;
if (editorState->fumen.Charts.try_emplace(c->dif_name, *c).second) {
editorState->chart.emplace(editorState->fumen.Charts.at(c->dif_name), assets_folder);
editor_state->showNewChartDialog = false;
if (editor_state->song.Charts.try_emplace(c->dif_name, *c).second) {
editor_state->chart.emplace(editor_state->song.Charts.at(c->dif_name), assets_folder);
}
}
} else {
newChartDialog.resetValues();
}
if (editorState->showChartProperties) {
chartPropertiesDialog.display(*editorState, assets_folder);
if (editor_state->showChartProperties) {
chartPropertiesDialog.display(*editor_state, assets_folder);
} else {
chartPropertiesDialog.shouldRefreshValues = true;
}
if (editorState->showSoundSettings) {
ImGui::Begin("Sound Settings", &editorState->showSoundSettings, ImGuiWindowFlags_AlwaysAutoResize);
if (editor_state->showSoundSettings) {
ImGui::Begin("Sound Settings", &editor_state->showSoundSettings, ImGuiWindowFlags_AlwaysAutoResize);
{
if (ImGui::TreeNode("Beat Tick")) {
beatTick.displayControls();
@ -561,7 +561,7 @@ int main(int argc, char** argv) {
{
if (ImGui::BeginMenu("File")) {
if (ImGui::MenuItem("New")) {
if (ESHelper::saveOrCancel(editorState)) {
if (ESHelper::saveOrCancel(editor_state)) {
const char* _filepath =
tinyfd_saveFileDialog("New File", nullptr, 0, nullptr, nullptr);
if (_filepath != nullptr) {
@ -569,9 +569,9 @@ int main(int argc, char** argv) {
try {
Fumen f(filepath);
f.autoSaveAsMemon();
editorState.emplace(f, assets_folder);
editor_state.emplace(f, assets_folder);
Toolbox::pushNewRecentFile(std::filesystem::canonical(
editorState->fumen.path), settings_folder);
editor_state->song.path), settings_folder);
} catch (const std::exception& e) {
tinyfd_messageBox(
"Error",
@ -585,8 +585,8 @@ int main(int argc, char** argv) {
}
ImGui::Separator();
if (ImGui::MenuItem("Open", "Ctrl+O")) {
if (ESHelper::saveOrCancel(editorState)) {
ESHelper::open(editorState, assets_folder, settings_folder);
if (ESHelper::saveOrCancel(editor_state)) {
ESHelper::open(editor_state, assets_folder, settings_folder);
}
}
if (ImGui::BeginMenu("Recent Files")) {
@ -594,8 +594,8 @@ int main(int argc, char** argv) {
for (const auto& file : Toolbox::getRecentFiles(settings_folder)) {
ImGui::PushID(i);
if (ImGui::MenuItem(file.c_str())) {
if (ESHelper::saveOrCancel(editorState)) {
ESHelper::openFromFile(editorState, file, assets_folder, settings_folder);
if (ESHelper::saveOrCancel(editor_state)) {
ESHelper::openFromFile(editor_state, file, assets_folder, settings_folder);
}
}
ImGui::PopID();
@ -603,109 +603,109 @@ int main(int argc, char** argv) {
}
ImGui::EndMenu();
}
if (ImGui::MenuItem("Close", "", false, editorState.has_value())) {
if (ESHelper::saveOrCancel(editorState)) {
editorState.reset();
if (ImGui::MenuItem("Close", "", false, editor_state.has_value())) {
if (ESHelper::saveOrCancel(editor_state)) {
editor_state.reset();
}
}
ImGui::Separator();
if (ImGui::MenuItem("Save", "Ctrl+S", false, editorState.has_value())) {
ESHelper::save(*editorState);
if (ImGui::MenuItem("Save", "Ctrl+S", false, editor_state.has_value())) {
ESHelper::save(*editor_state);
}
if (ImGui::MenuItem("Save As", "", false, editorState.has_value())) {
if (ImGui::MenuItem("Save As", "", false, editor_state.has_value())) {
char const* options[1] = {"*.memon"};
const char* _filepath(
tinyfd_saveFileDialog("Save File", nullptr, 1, options, nullptr));
if (_filepath != nullptr) {
std::filesystem::path filepath(_filepath);
try {
editorState->fumen.saveAsMemon(filepath);
editor_state->song.saveAsMemon(filepath);
} catch (const std::exception& e) {
tinyfd_messageBox("Error", e.what(), "ok", "error", 1);
}
}
}
ImGui::Separator();
if (ImGui::MenuItem("Properties", "Shift+P", false, editorState.has_value())) {
editorState->showProperties = true;
if (ImGui::MenuItem("Properties", "Shift+P", false, editor_state.has_value())) {
editor_state->showProperties = true;
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Edit")) {
if (ImGui::MenuItem("Undo", "Ctrl+Z")) {
Edit::undo(editorState, notificationsQueue);
Edit::undo(editor_state, notificationsQueue);
}
if (ImGui::MenuItem("Redo", "Ctrl+Y")) {
Edit::redo(editorState, notificationsQueue);
Edit::redo(editor_state, notificationsQueue);
}
ImGui::Separator();
if (ImGui::MenuItem("Cut", "Ctrl+X")) {
Edit::cut(editorState, notificationsQueue);
Edit::cut(editor_state, notificationsQueue);
}
if (ImGui::MenuItem("Copy", "Ctrl+C")) {
Edit::copy(editorState, notificationsQueue);
Edit::copy(editor_state, notificationsQueue);
}
if (ImGui::MenuItem("Paste", "Ctrl+V")) {
Edit::paste(editorState, notificationsQueue);
Edit::paste(editor_state, notificationsQueue);
}
if (ImGui::MenuItem("Delete", "Delete")) {
Edit::delete_(editorState, notificationsQueue);
Edit::delete_(editor_state, notificationsQueue);
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Chart", editorState.has_value())) {
if (ImGui::BeginMenu("Chart", editor_state.has_value())) {
if (ImGui::MenuItem("Chart List")) {
editorState->showChartList = true;
editor_state->showChartList = true;
}
if (ImGui::MenuItem(
"Properties##Chart",
nullptr,
false,
editorState->chart.has_value())) {
editorState->showChartProperties = true;
editor_state->chart.has_value())) {
editor_state->showChartProperties = true;
}
ImGui::Separator();
if (ImGui::MenuItem("New Chart")) {
editorState->showNewChartDialog = true;
editor_state->showNewChartDialog = true;
}
ImGui::Separator();
if (ImGui::MenuItem(
"Delete Chart",
nullptr,
false,
editorState->chart.has_value())) {
editorState->fumen.Charts.erase(editorState->chart->ref.dif_name);
editorState->chart.reset();
editor_state->chart.has_value())) {
editor_state->song.Charts.erase(editor_state->chart->ref.dif_name);
editor_state->chart.reset();
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("View", editorState.has_value())) {
if (ImGui::MenuItem("Playfield", nullptr, editorState->showPlayfield)) {
editorState->showPlayfield = not editorState->showPlayfield;
if (ImGui::BeginMenu("View", editor_state.has_value())) {
if (ImGui::MenuItem("Playfield", nullptr, editor_state->showPlayfield)) {
editor_state->showPlayfield = not editor_state->showPlayfield;
}
if (ImGui::MenuItem("Linear View", nullptr, editorState->showLinearView)) {
editorState->showLinearView = not editorState->showLinearView;
if (ImGui::MenuItem("Linear View", nullptr, editor_state->showLinearView)) {
editor_state->showLinearView = not editor_state->showLinearView;
}
if (ImGui::MenuItem("Playback Status", nullptr, editorState->showPlaybackStatus)) {
editorState->showPlaybackStatus = not editorState->showPlaybackStatus;
if (ImGui::MenuItem("Playback Status", nullptr, editor_state->showPlaybackStatus)) {
editor_state->showPlaybackStatus = not editor_state->showPlaybackStatus;
}
if (ImGui::MenuItem("Timeline", nullptr, editorState->showTimeline)) {
editorState->showTimeline = not editorState->showTimeline;
if (ImGui::MenuItem("Timeline", nullptr, editor_state->showTimeline)) {
editor_state->showTimeline = not editor_state->showTimeline;
}
if (ImGui::MenuItem("Editor Status", nullptr, editorState->showStatus)) {
editorState->showStatus = not editorState->showStatus;
if (ImGui::MenuItem("Editor Status", nullptr, editor_state->showStatus)) {
editor_state->showStatus = not editor_state->showStatus;
}
if (ImGui::MenuItem("History", nullptr, editorState->showHistory)) {
editorState->showHistory = not editorState->showHistory;
if (ImGui::MenuItem("History", nullptr, editor_state->showHistory)) {
editor_state->showHistory = not editor_state->showHistory;
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Settings", editorState.has_value())) {
if (ImGui::BeginMenu("Settings", editor_state.has_value())) {
if (ImGui::MenuItem("Sound")) {
editorState->showSoundSettings = true;
editor_state->showSoundSettings = true;
}
if (ImGui::MenuItem("Linear View")) {
editorState->linearView.shouldDisplaySettings = true;
editor_state->linearView.shouldDisplaySettings = true;
}
if (ImGui::BeginMenu("Marker")) {
int i = 0;

29
src/music_state.cpp Normal file
View File

@ -0,0 +1,29 @@
#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);
}

19
src/music_state.hpp Normal file
View File

@ -0,0 +1,19 @@
#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();
};

View File

@ -9,7 +9,7 @@
#include "AL/alext.h"
struct PreciseMusic : sf::Music {
PreciseMusic(const std::filesystem::path& path);
explicit PreciseMusic(const std::filesystem::path& path);
std::array<sf::Time, 2> alSecOffsetLatencySoft() const;
sf::Time getPrecisePlayingOffset() const;
sf::Time lag = sf::Time::Zero;

View File

@ -18,5 +18,7 @@ public:
std::string marker;
MarkerEndingState markerEndingState;
const std::filesystem::path file_path;
private:
std::filesystem::path file_path;
};

View File

@ -0,0 +1,7 @@
#pragma once
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <boost/multiprecision/gmp.hpp>
using Fraction = boost::multiprecision::mpq_rational;
using Decimal = boost::multiprecision::cpp_dec_float_50;

24
src/variant_visitor.hpp Normal file
View File

@ -0,0 +1,24 @@
#pragma once
/*
Dark template magic from https://www.modernescpp.com/index.php/visiting-a-std-variant-with-the-overload-pattern
Usage :
std::variant<char, int, float> var = 2017;
auto TypeOfIntegral = VariantVisitor {
[](char) { return "char"; },
[](int) { return "int"; },
[](auto) { return "unknown type"; },
};
std::visit(TypeOfIntegral, var);
*/
template<typename ... Ts>
struct VariantVisitor : Ts ... {
using Ts::operator() ...;
};
template<class... Ts> VariantVisitor(Ts...) -> VariantVisitor<Ts...>;

View File

@ -20,17 +20,12 @@ DensityGraph::DensityGraph(std::filesystem::path assets) :
collision_square.setTextureRect({496, 270, 6, 6});
}
void DensityGraph::update(int height, float chartRuntime, Chart& chart, float BPM, int resolution) {
this->computeDensities(height, chartRuntime, chart, BPM, resolution);
this->updateGraphTexture();
void DensityGraph::update(int height, const better::Chart& chart, const sf::Time& from, const sf::Time& to) {
this->compute_densities(height, chart, from, to);
this->update_graph_texture();
}
void DensityGraph::computeDensities(int height, float chartRuntime, Chart& chart, float BPM, int resolution) {
auto ticksToSeconds = [BPM, resolution](int ticks) -> float {
return (60.f * ticks) / (BPM * resolution);
};
int ticks_threshold = static_cast<int>((1.f / 60.f) * BPM * resolution);
void DensityGraph::compute_densities(int height, const better::Chart& chart, const sf::Time& from, const sf::Time& to) {
last_height = height;
// minus the slider cursor thiccccnesss
@ -41,13 +36,13 @@ void DensityGraph::computeDensities(int height, float chartRuntime, Chart& chart
if (sections >= 1) {
densities.resize(static_cast<unsigned int>(sections), {0, false});
float section_length = chartRuntime / sections;
last_section_length = section_length;
sf::Time section_duration = (to - from) / static_cast<sf::Int64>(sections);
last_section_duration = section_duration;
for (auto const& note : chart.Notes) {
for (auto const& note : chart.notes) {
auto note_time = note.getTiming();
auto note_seconds = ticksToSeconds(note_time);
auto float_section = note_seconds / section_length;
auto note_seconds = chart.timing.time_at(ticksToSeconds(note_time));
auto float_section = note_seconds / section_duration;
auto int_section = static_cast<int>(float_section);
auto section = std::clamp(int_section, 0, sections - 1);
densities.at(section).density += 1;
@ -59,7 +54,7 @@ void DensityGraph::computeDensities(int height, float chartRuntime, Chart& chart
}
}
void DensityGraph::updateGraphTexture() {
void DensityGraph::update_graph_texture() {
if (!graph.create(45, static_cast<unsigned int>(*last_height))) {
std::cerr << "Unable to create DensityGraph's RenderTexture";
throw std::runtime_error(

View File

@ -5,7 +5,7 @@
#include <string>
#include <filesystem>
#include "../chart.hpp"
#include "../better_song.hpp"
class DensityGraph {
public:
@ -26,13 +26,13 @@ public:
std::vector<DensityGraph::density_entry> densities;
std::optional<int> last_height;
std::optional<float> last_section_length;
std::optional<sf::Time> last_section_duration;
void update(int height, float chartRuntime, Chart& chart, float BPM, int resolution);
void update(int height, const better::Chart& chart, const sf::Time& from, const sf::Time& to);
private:
const std::filesystem::path texture_path;
void computeDensities(int height, float chartRuntime, Chart& chart, float BPM, int resolution);
void updateGraphTexture();
void compute_densities(int height, const better::Chart& chart, const sf::Time& from, const sf::Time& to);
void update_graph_texture();
};

View File

@ -51,7 +51,7 @@ void LinearView::resize(unsigned int width, unsigned int height) {
}
void LinearView::update(
const std::optional<Chart_with_History>& chart,
const std::optional<ChartState>& chart,
const sf::Time& playbackPosition,
const float& ticksAtPlaybackPosition,
const float& BPM,
@ -142,7 +142,7 @@ void LinearView::update(
static_cast<int>(SecondsToTicks.transform(
PixelsToSeconds.transform(static_cast<float>(y)) + 0.5f)));
auto notes = chart->ref.getVisibleNotesBetween(lower_bound_ticks, upper_bound_ticks);
auto notes = chart->chart.getVisibleNotesBetween(lower_bound_ticks, upper_bound_ticks);
auto currentLongNote = chart->makeCurrentLongNote();
if (currentLongNote) {
notes.insert(*currentLongNote);
@ -200,16 +200,16 @@ void LinearView::update(
* Draw the timeSelection
*/
selection.setSize({static_cast<float>(x) - 80.f, 0.f});
if (std::holds_alternative<unsigned int>(chart->timeSelection)) {
unsigned int ticks = std::get<unsigned int>(chart->timeSelection);
if (std::holds_alternative<unsigned int>(chart->time_selection)) {
unsigned int ticks = std::get<unsigned int>(chart->time_selection);
float selection_y =
PixelsToTicks.backwards_transform(static_cast<float>(ticks));
if (selection_y > 0.f and selection_y < static_cast<float>(y)) {
selection.setPosition(50.f, selection_y);
view.draw(selection);
}
} else if (std::holds_alternative<TimeSelection>(chart->timeSelection)) {
const auto& ts = std::get<TimeSelection>(chart->timeSelection);
} else if (std::holds_alternative<TimeSelection>(chart->time_selection)) {
const auto& ts = std::get<TimeSelection>(chart->time_selection);
float selection_start_y =
PixelsToTicks.backwards_transform(static_cast<float>(ts.start));
float selection_end_y = PixelsToTicks.backwards_transform(

View File

@ -4,7 +4,7 @@
#include <cmath>
#include <filesystem>
#include "../chart_with_history.hpp"
#include "../chart_state.hpp"
#include "../time_selection.hpp"
#include "../toolbox.hpp"
@ -15,7 +15,7 @@ public:
sf::RenderTexture view;
void update(
const std::optional<Chart_with_History>& chart,
const std::optional<ChartState>& chart,
const sf::Time& playbackPosition,
const float& ticksAtPlaybackPosition,
const float& BPM,