mirror of
https://gitlab.com/square-game-liberation-front/F.E.I.S.git
synced 2024-11-12 02:00:53 +01:00
Start rewriting the song (formely fumen) class from scratch for memon 1.0.0
This commit is contained in:
parent
204609c4d0
commit
b6fff2914d
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
.cache
|
||||
.idea
|
||||
.vscode
|
||||
build
|
||||
|
@ -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
1259
include/interval_tree.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
107
src/better_note.cpp
Normal 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
78
src/better_note.hpp
Normal 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
23
src/better_notes.cpp
Normal 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
18
src/better_notes.hpp
Normal 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
30
src/better_song.cpp
Normal 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
67
src/better_song.hpp
Normal 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
135
src/better_timing.cpp
Normal 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
57
src/better_timing.hpp
Normal 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
30
src/chart_state.cpp
Normal 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 {};
|
||||
}
|
||||
}
|
@ -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;
|
@ -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 {};
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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)));
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -53,5 +53,5 @@ public:
|
||||
float BPM;
|
||||
float offset;
|
||||
|
||||
float getChartRuntime(Chart c);
|
||||
float get_chart_runtime(Chart c);
|
||||
};
|
||||
|
350
src/main.cpp
350
src/main.cpp
@ -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
29
src/music_state.cpp
Normal 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
19
src/music_state.hpp
Normal 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();
|
||||
};
|
@ -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;
|
||||
|
@ -18,5 +18,7 @@ public:
|
||||
|
||||
std::string marker;
|
||||
MarkerEndingState markerEndingState;
|
||||
const std::filesystem::path file_path;
|
||||
|
||||
private:
|
||||
std::filesystem::path file_path;
|
||||
};
|
||||
|
7
src/special_numeric_types.hpp
Normal file
7
src/special_numeric_types.hpp
Normal 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
24
src/variant_visitor.hpp
Normal 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...>;
|
@ -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(
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user