Still in the middle of rewriting ...

This commit is contained in:
Stepland 2022-03-17 02:50:30 +01:00
parent b6fff2914d
commit b689c1ede5
19 changed files with 465 additions and 317 deletions

View File

@ -87,6 +87,39 @@ namespace better {
return tail_tip;
};
const auto abs_diff = [](const auto& a, const auto& b){
if (a > b) {
return a - b;
} else {
return b - a;
}
};
unsigned int LongNote::get_tail_length() const {
if (position.get_x() == tail_tip.get_x()) {
return abs_diff(position.get_y(), tail_tip.get_y());
} else {
return abs_diff(position.get_x(), tail_tip.get_x());
}
}
unsigned int LongNote::get_tail_angle() const {
if (position.get_x() == tail_tip.get_x()) {
if (position.get_y() > tail_tip.get_y()) {
return 0;
} else {
return 180;
}
} else {
if (position.get_x() > tail_tip.get_x()) {
return 270;
} else {
return 90;
}
}
}
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()}; },

View File

@ -17,8 +17,8 @@ namespace better {
0 1 2 3
y 0
1
2
3
2
3
*/
class Position {
public:
@ -57,6 +57,8 @@ namespace better {
Fraction get_end() const;
Fraction get_duration() const;
Position get_tail_tip() const;
unsigned int get_tail_length() const;
unsigned int get_tail_angle() const;
private:
Fraction time;
Position position;

View File

@ -63,5 +63,6 @@ namespace better {
decltype(order_by_difficulty_name)
> charts{order_by_difficulty_name};
Metadata metadata;
Timing timing;
};
}

View File

@ -117,19 +117,49 @@ namespace better {
change
*/
Fraction Timing::fractional_seconds_at(Fraction beats) const {
auto bpm_change = this->events_by_beats
.upper_bound(BPMEvent(beats, 0, 0));
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();
auto seconds_since_previous_event = (
Fraction{60}
* beats_since_previous_event
/ bpm_change->get_bpm()
);
return bpm_change->get_seconds() + seconds_since_previous_event;
};
Fraction Timing::fractional_seconds_between(
Fraction beat_a,
Fraction beat_b
) const {
return (
fractional_seconds_at(beat_b)
- fractional_seconds_at(beat_a)
);
};
sf::Time Timing::time_at(Fraction beats) const {
auto microseconds = fractional_seconds_at(beats) * 1000000;
return sf::microseconds(microseconds.convert_to<sf::Int64>());
}
return frac_to_time(fractional_seconds_at(beats));
};
sf::Time Timing::time_between(Fraction beat_a, Fraction beat_b) const {
return frac_to_time(fractional_seconds_between(beat_a, beat_b));
};
Fraction Timing::beats_at(sf::Time time) const {
Fraction fractional_seconds{time.asMicroseconds(), 1000000};
auto bpm_change = this->events_by_seconds.upper_bound(BPMEvent(0, fractional_seconds, 0));
if (bpm_change != this->events_by_seconds.begin()) {
bpm_change = std::prev(bpm_change);
}
auto seconds_since_previous_event = fractional_seconds - bpm_change->get_seconds();
auto beats_since_previous_event = (
bpm_change->get_bpm()
* seconds_since_previous_event
/ Fraction{60}
);
return bpm_change->get_beats() + beats_since_previous_event;
};
}

View File

@ -48,10 +48,19 @@ namespace better {
Timing(const std::vector<BPMAtBeat>& events, const SecondsAtBeat& offset);
Fraction fractional_seconds_at(Fraction beats) const;
Fraction fractional_seconds_between(Fraction beat_a, Fraction beat_b) const;
sf::Time time_at(Fraction beats) const;
sf::Time time_between(Fraction beat_a, Fraction beat_b) const;
Fraction beats_at(sf::Time time) const;
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};
};
const auto frac_to_time = [](const Fraction& f) {
auto microseconds = f * 1000000;
return sf::microseconds(microseconds.convert_to<sf::Int64>());
};
}

View File

@ -1,4 +1,5 @@
#include "chart_state.hpp"
#include "src/better_note.hpp"
ChartState::ChartState(better::Chart& c, std::filesystem::path assets) :
chart(c),
@ -7,9 +8,12 @@ ChartState::ChartState(better::Chart& c, std::filesystem::path assets) :
history.push(std::make_shared<OpenChart>(c));
}
std::optional<Note> ChartState::makeLongNoteDummy(int current_tick) const {
std::optional<better::LongNote> ChartState::make_long_note_dummy(Fraction current_beat) const {
if (creating_long_note and long_note_being_created) {
Note long_note = Note(long_note_being_created->first, long_note_being_created->second);
better::LongNote long_note{
long_note_being_created->first,
long_note_being_created->second
};
Note dummy_long_note = Note(
long_note.getPos(),
current_tick,
@ -21,7 +25,7 @@ std::optional<Note> ChartState::makeLongNoteDummy(int current_tick) const {
}
}
std::optional<Note> ChartState::makeCurrentLongNote() const {
std::optional<better::LongNote> ChartState::make_current_long_note() const {
if (creating_long_note and long_note_being_created) {
return Note(long_note_being_created->first, long_note_being_created->second);
} else {

View File

@ -1,5 +1,6 @@
#pragma once
#include "better_note.hpp"
#include "better_song.hpp"
#include "history.hpp"
#include "history_actions.hpp"
@ -15,11 +16,11 @@ struct ChartState {
std::set<Note> selected_notes;
NotesClipboard notes_clipboard;
SelectionState time_selection;
std::optional<std::pair<Note, Note>> long_note_being_created;
std::optional<better::LongNote> long_note_being_created;
bool creating_long_note;
History<std::shared_ptr<ActionWithMessage>> history;
DensityGraph density_graph;
std::optional<Note> makeLongNoteDummy(int current_tick) const;
std::optional<Note> makeCurrentLongNote() const;
std::optional<better::LongNote> make_long_note_dummy(Fraction current_beat) const;
std::optional<better::LongNote> make_current_long_note() const;
};

View File

@ -1,5 +1,6 @@
#include "editor_state.hpp"
#include <SFML/System/Time.hpp>
#include <algorithm>
#include <cmath>
#include <filesystem>
@ -8,6 +9,27 @@
#include <imgui_internal.h>
#include <imgui_stdlib.h>
#include <tinyfiledialogs.h>
#include "src/time_interval.hpp"
EditorState::EditorState(
const better::Song& song_,
const std::filesystem::path& assets_,
const std::filesystem::path& song_path = {}
) :
song(song_),
song_path(song_path),
playfield(assets_),
linear_view(assets_),
music_path_in_gui(song.metadata.audio.value_or("")),
applicable_timing(song.timing),
assets(assets_)
{
if (not song.charts.empty()) {
open_chart(this->song.charts.begin()->second);
}
reload_music();
reload_album_cover();
};
/*
* Reloads music from what's indicated in the "music path" field of the song
@ -15,47 +37,47 @@
* Updates playbackPosition and preview_end as well
*/
void EditorState::reload_music() {
const auto absolute_music_path = song_path.parent_path() / edited_music_path;
if (not song_path.has_value()) {
music_state.reset();
return;
}
const auto absolute_music_path = song_path->parent_path() / music_path_in_gui;
try {
music_state.emplace(absolute_music_path);
} catch (const std::exception& e) {
music_state.reset();
}
reload_preview_end();
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;
reload_editable_range();
playback_position = std::clamp(
playback_position,
editable_range.start,
editable_range.end
);
previous_playback_position = playback_position;
}
void EditorState::reload_preview_end() {
auto old_preview_end = this->preview_end;
sf::Time music_duration = sf::Time::Zero;
void EditorState::reload_editable_range() {
auto old_range = this->editable_range;
TimeInterval new_range;
if (music_state) {
music_duration = music_state->getDuration();
new_range += music_state->music.getDuration();
}
if (chart_state) {
new_range += chart_state->chart.time_of_last_event().value_or(sf::Time::Zero);
}
float chart_end = 0;
if (chart) {
chart_end = chart->ref
.time_of_last_event()
.value_or(sf::Time::Zero)
.asSeconds();
new_range.end += sf::seconds(10);
// If there is no music, make sure we can edit at least the first whole minute
if (not music_state) {
new_range += sf::seconds(60);
}
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;
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;
this->editable_range = new_range;
if (old_range != new_range and this->chart_state.has_value()) {
chart_state->density_graph.should_recompute = true;
}
}
@ -64,38 +86,56 @@ void EditorState::reload_preview_end() {
* of the song Resets the album cover state if anything fails
*/
void EditorState::reload_album_cover() {
album_cover.emplace();
if (not song_path.has_value() or not song.metadata.jacket.has_value()) {
jacket.reset();
return;
}
std::filesystem::path album_cover_path =
std::filesystem::path(song.path).parent_path() / song.albumCoverPath;
jacket.emplace();
auto jacket_path = song_path->parent_path() / *song.metadata.jacket;
if (song.albumCoverPath.empty() or not std::filesystem::exists(album_cover_path)
or not album_cover->loadFromFile(album_cover_path.string())) {
album_cover.reset();
if (
not std::filesystem::exists(jacket_path)
or not jacket->loadFromFile(jacket_path.string())
) {
jacket.reset();
}
}
void EditorState::set_playback_and_music_position(sf::Time newPosition) {
reload_preview_end();
newPosition = sf::seconds(
std::clamp(
newPosition.asSeconds(),
-song.offset,
this->preview_end.asSeconds()
)
);
previous_pos = sf::seconds(newPosition.asSeconds() - 1.f / 60.f);
void EditorState::set_playback_position(sf::Time newPosition) {
newPosition = std::clamp(newPosition, editable_range.start, editable_range.end);
previous_playback_position = newPosition - (sf::seconds(1) / 60.f);
playback_position = newPosition;
if (music) {
if (playback_position.asSeconds() >= 0 and playback_position < music->getDuration()) {
music->setPlayingOffset(playback_position);
if (music_state) {
if (
playback_position >= sf::Time::Zero
and playback_position < music_state->music.getDuration()
) {
music_state->music.setPlayingOffset(playback_position);
} else {
music->stop();
music_state->music.stop();
}
}
}
float EditorState::current_beats() {
return beats_at(playback_position);
};
float EditorState::beats_at(sf::Time time) {
auto frac_beats = applicable_timing.beats_at(time);
return static_cast<float>(frac_beats);
};
float EditorState::seconds_at(Fraction beat) {
auto frac_seconds = applicable_timing.fractional_seconds_at(beat);
return static_cast<float>(frac_seconds);
};
Fraction EditorState::get_snap_step() {
return Fraction{1, snap};
};
void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndingState) {
ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_Once);
ImGui::SetNextWindowSizeConstraints(
@ -104,32 +144,34 @@ 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->creating_long_note) {
if (
not ImGui::IsWindowHovered()
and chart_state
and chart_state->creating_long_note
) {
// cancel long note creation if the mouse is or goes out of the playfield
chart->long_note_being_created.reset();
chart->creating_long_note = false;
chart_state->long_note_being_created.reset();
chart_state->creating_long_note = false;
}
float squareSize = ImGui::GetWindowSize().x / 4.f;
float TitlebarHeight = ImGui::GetWindowSize().y - ImGui::GetWindowSize().x;
int ImGuiIndex = 0;
if (chart) {
if (chart_state) {
playfield.resize(static_cast<unsigned int>(ImGui::GetWindowSize().x));
auto longNoteDummy =
chart->makeLongNoteDummy(static_cast<int>(roundf(getCurrentTick())));
if (longNoteDummy) {
if (chart_state->long_note_being_created) {
playfield.drawLongNote(
*longNoteDummy,
*chart_state->long_note_being_created,
playback_position,
getCurrentTick(),
song.BPM,
getResolution());
getResolution()
);
}
for (auto const& note : visibleNotes) {
float note_offset =
(playback_position.asSeconds() - getSecondsAt(note.getTiming()));
(playback_position.asSeconds() - seconds_at(note.getTiming()));
// auto frame = static_cast<long long
// int>(std::floor(note_offset * 30.f));
int x = note.getPos() % 4;
@ -152,7 +194,7 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
playfield.drawLongNote(
note,
playback_position,
getCurrentTick(),
current_tick(),
song.BPM,
getResolution(),
marker,
@ -161,9 +203,9 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin
}
ImGui::SetCursorPos({0, TitlebarHeight});
ImGui::Image(playfield.longNoteLayer);
ImGui::Image(playfield.long_note_layer);
ImGui::SetCursorPos({0, TitlebarHeight});
ImGui::Image(playfield.markerLayer);
ImGui::Image(playfield.marker_layer);
}
// Display button grid
@ -181,11 +223,11 @@ 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->creating_long_note) {
if (ImGui::IsItemHovered() and chart_state and chart_state->creating_long_note) {
// Deal with long note creation stuff
if (not chart->long_note_being_created) {
Note current_note =
Note(x + 4 * y, static_cast<int>(roundf(getCurrentTick())));
Note(x + 4 * y, static_cast<int>(roundf(current_tick())));
chart->long_note_being_created =
std::make_pair(current_note, current_note);
} else {
@ -248,8 +290,8 @@ void EditorState::displayProperties() {
{
ImGui::Columns(2, nullptr, false);
if (album_cover) {
ImGui::Image(*album_cover, sf::Vector2f(200, 200));
if (jacket) {
ImGui::Image(*jacket, sf::Vector2f(200, 200));
} else {
ImGui::BeginChild("Album Cover", ImVec2(200, 200), true);
ImGui::EndChild();
@ -266,7 +308,7 @@ void EditorState::displayProperties() {
reload_music();
}
if (Toolbox::InputTextColored(
album_cover.has_value(),
jacket.has_value(),
"Invalid Album Cover Path",
"Album Cover",
&song.albumCoverPath)) {
@ -302,7 +344,7 @@ void EditorState::displayStatus() {
}
}
if (not album_cover) {
if (not jacket) {
if (not song.albumCoverPath.empty()) {
ImGui::TextColored(
ImVec4(1, 0.42, 0.41, 1),
@ -347,7 +389,7 @@ void EditorState::displayPlaybackStatus() {
ImGui::SameLine();
ImGui::TextColored(ImVec4(0.53, 0.53, 0.53, 1), "Beats :");
ImGui::SameLine();
ImGui::Text("%02.2f", this->getBeats());
ImGui::Text("%02.2f", this->get_current_beats());
ImGui::SameLine();
if (music) {
ImGui::TextColored(
@ -590,9 +632,14 @@ void EditorState::toggleNoteAtCurrentTime(int pos) {
}
}
const sf::Time& EditorState::get_preview_end() {
reload_preview_end();
return preview_end;
const TimeInterval& EditorState::get_editable_range() {
reload_editable_range();
return editable_range;
}
void EditorState::open_chart(better::Chart& chart) {
chart_state.emplace(chart, assets);
}
void ESHelper::save(EditorState& ed) {

View File

@ -12,6 +12,7 @@
#include "music_state.hpp"
#include "notes_clipboard.hpp"
#include "precise_music.hpp"
#include "time_interval.hpp"
#include "time_selection.hpp"
#include "widgets/linear_view.hpp"
#include "widgets/playfield.hpp"
@ -28,29 +29,19 @@ enum saveChangesResponses {
/*
* The god class, holds everything there is to know about the currently open
* .memon file
* file
*/
class EditorState {
public:
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();
};
const std::filesystem::path& assets,
const std::filesystem::path& save_path
);
better::Song song;
std::optional<std::filesystem::path> song_path;
std::optional<ChartState> chart_state;
std::optional<MusicState> music_state;
@ -58,38 +49,23 @@ public:
Playfield playfield;
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<sf::Texture> album_cover;
std::optional<sf::Texture> jacket;
bool playing;
sf::Time previous_pos;
sf::Time previous_playback_position;
sf::Time playback_position;
const sf::Time& get_preview_end();
const TimeInterval& get_editable_range();
void set_playback_and_music_position(sf::Time new_position);
void set_playback_position(sf::Time new_position);
float getBeats() { return getBeatsAt(playback_position.asSeconds()); };
float getBeatsAt(float seconds) {
return ((seconds + song.offset) / 60.f) * song.BPM;
};
float getCurrentTick() { return getTicksAt(playback_position.asSeconds()); };
float getTicksAt(float seconds) {
return getBeatsAt(seconds) * get_resolution();
}
float getSecondsAt(int tick) {
return (60.f * tick) / (song.BPM * get_resolution()) - song.offset;
};
int get_resolution() { return chart_state ? chart_state->chart.getResolution() : 240; };
int get_snap_step() { return get_resolution() / snap; };
void reload_album_cover();
void reload_preview_end();
float current_beats();
float beats_at(sf::Time time);
float seconds_at(Fraction beat);
Fraction get_snap_step();
bool showPlayfield = true;
bool showProperties;
@ -120,13 +96,25 @@ public:
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
/*
sf::Time bounds (in the audio file "coordinates") which are accessible
(and maybe editable) from the editor, can extend before and after
the actual audio file
*/
TimeInterval editable_range;
std::string edited_music_path;
void reload_album_cover();
void reload_editable_range();
std::string music_path_in_gui;
void reload_music();
std::filesystem::path song_path;
better::Timing& applicable_timing;
void reload_applicable_timing();
void open_chart(better::Chart& chart);
std::filesystem::path assets;
};
namespace ESHelper {

View File

@ -2,7 +2,7 @@
void Move::backwardsInTime(std::optional<EditorState>& ed) {
if (ed and ed->chart) {
float floatTicks = ed->getCurrentTick();
float floatTicks = ed->current_tick();
auto prevTick = static_cast<int>(floorf(floatTicks));
int step = ed->get_snap_step();
int prevTickInSnap = prevTick;
@ -11,17 +11,17 @@ void Move::backwardsInTime(std::optional<EditorState>& ed) {
} else {
prevTickInSnap -= prevTick % step;
}
ed->setPlaybackAndMusicPosition(sf::seconds(ed->getSecondsAt(prevTickInSnap)));
ed->setPlaybackAndMusicPosition(sf::seconds(ed->seconds_at(prevTickInSnap)));
}
}
void Move::forwardsInTime(std::optional<EditorState>& ed) {
if (ed and ed->chart) {
float floatTicks = ed->getCurrentTick();
float floatTicks = ed->current_tick();
auto nextTick = static_cast<int>(ceilf(floatTicks));
int step = ed->get_snap_step();
int nextTickInSnap = nextTick + (step - nextTick % step);
ed->setPlaybackAndMusicPosition(sf::seconds(ed->getSecondsAt(nextTickInSnap)));
ed->setPlaybackAndMusicPosition(sf::seconds(ed->seconds_at(nextTickInSnap)));
}
}
@ -81,7 +81,7 @@ void Edit::copy(std::optional<EditorState>& ed, NotificationsQueue& nq) {
void Edit::paste(std::optional<EditorState>& ed, NotificationsQueue& nq) {
if (ed and ed->chart and (not ed->chart->notesClipboard.empty())) {
auto tick_offset = static_cast<int>(ed->getCurrentTick());
auto tick_offset = static_cast<int>(ed->current_tick());
std::set<Note> pasted_notes = ed->chart->notesClipboard.paste(tick_offset);
std::stringstream ss;

View File

@ -40,7 +40,7 @@ ToggledNotes::ToggledNotes(std::set<Note> n, bool have_been_added) :
}
void ToggledNotes::doAction(EditorState& ed) const {
ed.setPlaybackAndMusicPosition(sf::seconds(ed.getSecondsAt(notes.begin()->getTiming())));
ed.setPlaybackAndMusicPosition(sf::seconds(ed.seconds_at(notes.begin()->getTiming())));
if (have_been_added) {
for (auto note : notes) {
if (ed.chart->ref.Notes.find(note) == ed.chart->ref.Notes.end()) {
@ -57,7 +57,7 @@ void ToggledNotes::doAction(EditorState& ed) const {
}
void ToggledNotes::undoAction(EditorState& ed) const {
ed.setPlaybackAndMusicPosition(sf::seconds(ed.getSecondsAt(notes.begin()->getTiming())));
ed.setPlaybackAndMusicPosition(sf::seconds(ed.seconds_at(notes.begin()->getTiming())));
if (not have_been_added) {
for (auto note : notes) {
if (ed.chart->ref.Notes.find(note) == ed.chart->ref.Notes.end()) {

View File

@ -2,74 +2,60 @@
#include <cmath>
LNMarker::LNMarker(std::filesystem::path folder) {
triangle_appearance = load_tex_with_prefix<16, 400>(folder, "LN0001_M");
triangle_begin_cycle = load_tex_with_prefix<8, 600>(folder, "LN0001_M");
triangle_cycle = load_tex_with_prefix<16, 500>(folder, "LN0001_M");
square_highlight = load_tex_with_prefix<16, 300>(folder, "LN0001_M");
square_outline = load_tex_with_prefix<16, 100>(folder, "LN0001_M");
square_background = load_tex_with_prefix<16, 200>(folder, "LN0001_M");
tail_cycle = load_tex_with_prefix<16, 0>(folder, "LN0001_M");
LNMarker::LNMarker(std::filesystem::path folder) :
triangle_appearance(load_tex_with_prefix<16, 400>(folder, "LN0001_M")),
triangle_begin_cycle(load_tex_with_prefix<8, 600>(folder, "LN0001_M")),
triangle_cycle(load_tex_with_prefix<16, 500>(folder, "LN0001_M")),
square_highlight(load_tex_with_prefix<16, 300>(folder, "LN0001_M")),
square_outline(load_tex_with_prefix<16, 100>(folder, "LN0001_M")),
square_background(load_tex_with_prefix<16, 200>(folder, "LN0001_M")),
tail_cycle(load_tex_with_prefix<16, 0>(folder, "LN0001_M"))
{
setRepeated<16>(tail_cycle, true);
}
std::optional<std::reference_wrapper<sf::Texture>>
LNMarker::getTriangleTexture(float seconds) {
auto frame = static_cast<long long int>(std::floor(seconds * 30.f));
opt_tex_ref LNMarker::triangle_at(int frame) {
if (frame >= -16 and frame <= -1) {
// approach phase
return triangle_appearance.at(static_cast<unsigned long long int>(16 + frame));
return triangle_appearance.at(16 + frame);
} else if (frame >= 0) {
if (frame <= 7) {
return triangle_begin_cycle.at(static_cast<unsigned long long int>(frame));
return triangle_begin_cycle.at(frame);
} else {
return triangle_cycle.at(static_cast<unsigned long long int>((frame - 8) % 16));
return triangle_cycle.at((frame - 8) % 16);
}
} else {
return {};
}
}
std::optional<std::reference_wrapper<sf::Texture>> LNMarker::getTailTexture(float seconds) {
auto frame = static_cast<long long int>(std::floor(seconds * 30.f));
opt_tex_ref LNMarker::tail_at(int frame) {
if (frame >= -16) {
return tail_cycle.at(static_cast<unsigned long long int>((16 + (frame % 16)) % 16));
return tail_cycle.at((16 + (frame % 16)) % 16);
} else {
return {};
}
}
std::optional<std::reference_wrapper<sf::Texture>>
LNMarker::getSquareHighlightTexture(float seconds) {
auto frame = static_cast<long long int>(std::floor(seconds * 30.f));
opt_tex_ref LNMarker::highlight_at(int frame) {
if (frame >= 0) {
return square_highlight.at(
static_cast<unsigned long long int>((16 + (frame % 16)) % 16));
return square_highlight.at((16 + (frame % 16)) % 16);
} else {
return {};
}
}
std::optional<std::reference_wrapper<sf::Texture>>
LNMarker::getSquareOutlineTexture(float seconds) {
auto frame = static_cast<long long int>(std::floor(seconds * 30.f));
opt_tex_ref LNMarker::outline_at(int frame) {
if (frame >= -16) {
return square_outline.at(
static_cast<unsigned long long int>((16 + (frame % 16)) % 16));
return square_outline.at((16 + (frame % 16)) % 16);
} else {
return {};
}
}
std::optional<std::reference_wrapper<sf::Texture>>
LNMarker::getSquareBackgroundTexture(float seconds) {
auto frame = static_cast<long long int>(std::floor(seconds * 30.f));
opt_tex_ref LNMarker::background_at(int frame) {
if (frame >= -16) {
return square_background.at(
static_cast<unsigned long long int>((16 + (frame % 16)) % 16));
return square_background.at((16 + (frame % 16)) % 16);
} else {
return {};
}

View File

@ -8,6 +8,8 @@
#include <list>
#include <map>
using opt_tex_ref = std::optional<std::reference_wrapper<sf::Texture>>;
/*
* Stores every rotated variant of the long note marker
* This approach is absolutely terrible, I should just dig a little bit into the
@ -18,15 +20,11 @@ class LNMarker {
public:
explicit LNMarker(std::filesystem::path folder);
std::optional<std::reference_wrapper<sf::Texture>> getTriangleTexture(float seconds);
std::optional<std::reference_wrapper<sf::Texture>>
getSquareHighlightTexture(float seconds);
std::optional<std::reference_wrapper<sf::Texture>> getSquareOutlineTexture(float seconds);
std::optional<std::reference_wrapper<sf::Texture>>
getSquareBackgroundTexture(float seconds);
std::optional<std::reference_wrapper<sf::Texture>> getTailTexture(float seconds);
opt_tex_ref triangle_at(int frame);
opt_tex_ref highlight_at(int frame);
opt_tex_ref outline_at(int frame);
opt_tex_ref background_at(int frame);
opt_tex_ref tail_at(int frame);
private:
std::array<sf::Texture, 16> triangle_appearance;
@ -45,7 +43,8 @@ private:
const std::filesystem::path& folder,
const std::string& prefix,
int left_padding = 3,
const std::string& extension = ".png") {
const std::string& extension = ".png"
) {
std::array<sf::Texture, number> res;
for (int frame = first; frame <= first + number - 1; frame++) {
std::stringstream filename;

View File

@ -177,14 +177,14 @@ int main(int argc, char** argv) {
// current time
editor_state->chart->timeSelection =
static_cast<unsigned int>(
editor_state->getCurrentTick());
editor_state->current_tick());
// if the start of the timeSelection is
// already set
} else if (std::holds_alternative<unsigned int>(
editor_state->chart->timeSelection)) {
auto current_tick =
static_cast<int>(editor_state->getCurrentTick());
static_cast<int>(editor_state->current_tick());
auto selection_start =
static_cast<int>(std::get<unsigned int>(
editor_state->chart->timeSelection));
@ -219,7 +219,7 @@ int main(int argc, char** argv) {
// current time
editor_state->chart->timeSelection =
static_cast<unsigned int>(
editor_state->getCurrentTick());
editor_state->current_tick());
}
}
break;
@ -443,9 +443,9 @@ int main(int argc, char** argv) {
}
}
if (beatTick.shouldPlay) {
auto previous_tick = static_cast<int>(editor_state->getTicksAt(
auto previous_tick = static_cast<int>(editor_state->ticks_at(
editor_state->previousPos.asSeconds()));
auto current_tick = static_cast<int>(editor_state->getTicksAt(
auto current_tick = static_cast<int>(editor_state->ticks_at(
editor_state->playbackPosition.asSeconds()));
if (previous_tick / editor_state->getResolution()
!= current_tick / editor_state->getResolution()) {

19
src/time_interval.cpp Normal file
View File

@ -0,0 +1,19 @@
#include "time_interval.hpp"
#include <algorithm>
TimeInterval::TimeInterval(const sf::Time& start, const sf::Time& end) :
start(std::min(start, end)),
end(std::max(start, end))
{};
// interval union
TimeInterval& TimeInterval::operator+=(const TimeInterval& rhs) {
start = std::min(start, rhs.start);
end = std::max(end, rhs.end);
return *this;
};
TimeInterval& TimeInterval::operator+=(const sf::Time& rhs) {
return this->operator+=(TimeInterval{rhs, rhs});
};

26
src/time_interval.hpp Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include <compare>
#include <SFML/System/Time.hpp>
class TimeInterval {
public:
TimeInterval() = default;
TimeInterval(const sf::Time& start, const sf::Time& end);
bool operator==(const TimeInterval&) const = default;
TimeInterval& operator+=(const TimeInterval& rhs);
TimeInterval& operator+=(const sf::Time& rhs);
// passing lhs by value helps optimize chained a+b+c (says cppreference)
template<typename T>
friend TimeInterval operator+(TimeInterval lhs, const T& rhs) {
lhs += rhs;
return lhs;
};
sf::Time start;
sf::Time end;
};

View File

@ -143,7 +143,7 @@ void LinearView::update(
PixelsToSeconds.transform(static_cast<float>(y)) + 0.5f)));
auto notes = chart->chart.getVisibleNotesBetween(lower_bound_ticks, upper_bound_ticks);
auto currentLongNote = chart->makeCurrentLongNote();
auto currentLongNote = chart->make_current_long_note();
if (currentLongNote) {
notes.insert(*currentLongNote);
}

View File

@ -5,7 +5,7 @@
const std::string texture_file = "textures/edit_textures/game_front_edit_tex_1.tex.png";
Playfield::Playfield(std::filesystem::path assets_folder) :
longNoteMarker(assets_folder / "textures" / "long"),
long_note(assets_folder / "textures" / "long"),
texture_path(assets_folder / texture_file)
{
if (!base_texture.loadFromFile(texture_path)) {
@ -26,161 +26,156 @@ Playfield::Playfield(std::filesystem::path assets_folder) :
note_collision.setTexture(base_texture);
note_collision.setTextureRect({576, 0, 192, 192});
if (!markerLayer.create(400, 400)) {
if (!marker_layer.create(400, 400)) {
std::cerr << "Unable to create Playfield's markerLayer";
throw std::runtime_error("Unable to create Playfield's markerLayer");
}
markerLayer.setSmooth(true);
marker_layer.setSmooth(true);
if (!longNoteLayer.create(400, 400)) {
if (!long_note.layer.create(400, 400)) {
std::cerr << "Unable to create Playfield's longNoteLayer";
throw std::runtime_error("Unable to create Playfield's longNoteLayer");
}
longNoteLayer.setSmooth(true);
long_note.layer.setSmooth(true);
LNSquareBackgroud.setTexture(*longNoteMarker.getSquareBackgroundTexture(0));
LNSquareOutline.setTexture(*longNoteMarker.getSquareOutlineTexture(0));
LNSquareHighlight.setTexture(*longNoteMarker.getSquareHighlightTexture(0));
LNTail.setTexture(*longNoteMarker.getTailTexture(0));
LNTriangle.setTexture(*longNoteMarker.getTriangleTexture(0));
long_note.backgroud.setTexture(*long_note.marker.background_at(0));
long_note.outline.setTexture(*long_note.marker.outline_at(0));
long_note.highlight.setTexture(*long_note.marker.highlight_at(0));
long_note.tail.setTexture(*long_note.marker.tail_at(0));
long_note.triangle.setTexture(*long_note.marker.triangle_at(0));
}
void Playfield::resize(unsigned int width) {
if (longNoteLayer.getSize() != sf::Vector2u(width, width)) {
if (!longNoteLayer.create(width, width)) {
if (long_note.layer.getSize() != sf::Vector2u(width, width)) {
if (!long_note.layer.create(width, width)) {
std::cerr << "Unable to resize Playfield's longNoteLayer";
throw std::runtime_error(
"Unable to resize Playfield's longNoteLayer");
}
longNoteLayer.setSmooth(true);
long_note.layer.setSmooth(true);
}
longNoteLayer.clear(sf::Color::Transparent);
long_note.layer.clear(sf::Color::Transparent);
if (markerLayer.getSize() != sf::Vector2u(width, width)) {
if (!markerLayer.create(width, width)) {
if (marker_layer.getSize() != sf::Vector2u(width, width)) {
if (!marker_layer.create(width, width)) {
std::cerr << "Unable to resize Playfield's markerLayer";
throw std::runtime_error(
"Unable to resize Playfield's markerLayer");
}
markerLayer.setSmooth(true);
marker_layer.setSmooth(true);
}
markerLayer.clear(sf::Color::Transparent);
marker_layer.clear(sf::Color::Transparent);
}
void Playfield::drawLongNote(
const Note& note,
void Playfield::draw_long_note(
const better::LongNote& note,
const sf::Time& playbackPosition,
const float& ticksAtPlaybackPosition,
const float& BPM,
const int& resolution) {
float squareSize = static_cast<float>(longNoteLayer.getSize().x) / 4;
const better::Timing& timing
) {
float squareSize = static_cast<float>(long_note.layer.getSize().x) / 4;
auto note_time = timing.time_at(note.get_time());
auto note_offset = playbackPosition - note_time;
auto frame = static_cast<int>(std::floor(note_offset.asSeconds() * 30.f));
const auto x = note.get_position().get_x();
const auto y = note.get_position().get_y();
AffineTransform<float> SecondsToTicksProportional(0.f, (60.f / BPM), 0.f, resolution);
AffineTransform<float> SecondsToTicks(
playbackPosition.asSeconds() - (60.f / BPM),
playbackPosition.asSeconds(),
ticksAtPlaybackPosition - resolution,
ticksAtPlaybackPosition);
auto tail_end = timing.time_at(note.get_end());
float note_offset = SecondsToTicksProportional.backwards_transform(
ticksAtPlaybackPosition - note.getTiming());
auto frame = static_cast<long long int>(std::floor(note_offset * 30.f));
int x = note.getPos() % 4;
int y = note.getPos() / 4;
float tail_end_in_seconds =
SecondsToTicks.backwards_transform(note.getTiming() + note.getLength());
// float tail_end_offset = playbackPosition.asSeconds() -
// tail_end_in_seconds;
if (playbackPosition.asSeconds() < tail_end_in_seconds) {
if (playbackPosition < tail_end) {
// Before or During the long note
auto tail_tex = longNoteMarker.getTailTexture(note_offset);
if (tail_tex) {
auto triangle_distance = static_cast<float>((note.getTail_pos() / 4) + 1);
if (auto tail_tex = long_note.marker.tail_at(frame)) {
AffineTransform<float> OffsetToTriangleDistance(
0.f,
SecondsToTicksProportional.backwards_transform(note.getLength()),
triangle_distance,
0.f);
(tail_end - note_time).asSeconds(),
static_cast<float>(note.get_tail_length()),
0.f
);
LNTail.setTexture(*tail_tex, true);
auto LNTriangle_tex = longNoteMarker.getTriangleTexture(note_offset);
if (LNTriangle_tex) {
LNTriangle.setTexture(*LNTriangle_tex, true);
long_note.tail.setTexture(*tail_tex, true);
if (auto tex = long_note.marker.triangle_at(frame)) {
long_note.triangle.setTexture(*tex, true);
}
auto LNSquareBackgroud_tex =
longNoteMarker.getSquareBackgroundTexture(note_offset);
if (LNSquareBackgroud_tex) {
LNSquareBackgroud.setTexture(*LNSquareBackgroud_tex, true);
if (auto tex = long_note.marker.background_at(frame)) {
long_note.backgroud.setTexture(*tex, true);
}
auto LNSquareOutline_tex = longNoteMarker.getSquareOutlineTexture(note_offset);
if (LNSquareOutline_tex) {
LNSquareOutline.setTexture(*LNSquareOutline_tex, true);
if (auto tex = long_note.marker.outline_at(frame)) {
long_note.outline.setTexture(*tex, true);
}
auto LNSquareHighlight_tex =
longNoteMarker.getSquareHighlightTexture(note_offset);
if (LNSquareHighlight_tex) {
LNSquareHighlight.setTexture(*LNSquareHighlight_tex, true);
if (auto tex = long_note.marker.highlight_at(frame)) {
long_note.highlight.setTexture(*tex, true);
}
auto rect = LNTail.getTextureRect();
auto rect = long_note.tail.getTextureRect();
float tail_length_factor;
if (frame < 8) {
// Before the note : tail goes from triangle tip to note edge
tail_length_factor =
std::max(0.f, OffsetToTriangleDistance.clampedTransform(note_offset) - 1.f);
tail_length_factor = std::max(
0.f,
OffsetToTriangleDistance.clampedTransform(
note_offset.asSeconds()
) - 1.f
);
} else {
// During the note : tail goes from half of the triangle base to
// note edge
tail_length_factor =
std::max(0.f, OffsetToTriangleDistance.clampedTransform(note_offset) - 0.5f);
tail_length_factor = std::max(
0.f,
OffsetToTriangleDistance.clampedTransform(
note_offset.asSeconds()
) - 0.5f
);
}
rect.height = static_cast<int>(rect.height * tail_length_factor);
LNTail.setTextureRect(rect);
LNTail.setOrigin(rect.width / 2.f, -rect.width / 2.f);
LNTail.setRotation(90.f * ((note.getTail_pos() + 2) % 4));
long_note.tail.setTextureRect(rect);
long_note.tail.setOrigin(rect.width / 2.f, -rect.width / 2.f);
long_note.tail.setRotation(note.get_tail_angle() + 180);
rect = LNTriangle.getTextureRect();
LNTriangle.setOrigin(
rect = long_note.triangle.getTextureRect();
long_note.triangle.setOrigin(
rect.width / 2.f,
rect.width * (0.5f + OffsetToTriangleDistance.clampedTransform(note_offset)));
LNTriangle.setRotation(90.f * (note.getTail_pos() % 4));
rect.width * (
0.5f
+ OffsetToTriangleDistance.clampedTransform(
note_offset.asSeconds()
)
)
);
long_note.triangle.setRotation(note.get_tail_angle());
rect = LNSquareBackgroud.getTextureRect();
LNSquareBackgroud.setOrigin(rect.width / 2.f, rect.height / 2.f);
LNSquareBackgroud.setRotation(90.f * (note.getTail_pos() % 4));
rect = long_note.backgroud.getTextureRect();
long_note.backgroud.setOrigin(rect.width / 2.f, rect.height / 2.f);
long_note.backgroud.setRotation(note.get_tail_angle());
rect = LNSquareOutline.getTextureRect();
LNSquareOutline.setOrigin(rect.width / 2.f, rect.height / 2.f);
LNSquareOutline.setRotation(90.f * (note.getTail_pos() % 4));
rect = long_note.outline.getTextureRect();
long_note.outline.setOrigin(rect.width / 2.f, rect.height / 2.f);
long_note.outline.setRotation(note.get_tail_angle());
rect = LNSquareHighlight.getTextureRect();
LNSquareHighlight.setOrigin(rect.width / 2.f, rect.height / 2.f);
rect = long_note.highlight.getTextureRect();
long_note.highlight.setOrigin(rect.width / 2.f, rect.height / 2.f);
float scale = squareSize / rect.width;
LNTail.setScale(scale, scale);
LNTriangle.setScale(scale, scale);
LNSquareBackgroud.setScale(scale, scale);
LNSquareOutline.setScale(scale, scale);
LNSquareHighlight.setScale(scale, scale);
long_note.tail.setScale(scale, scale);
long_note.triangle.setScale(scale, scale);
long_note.backgroud.setScale(scale, scale);
long_note.outline.setScale(scale, scale);
long_note.highlight.setScale(scale, scale);
LNTail.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
LNTriangle.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
LNSquareBackgroud.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
LNSquareOutline.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
LNSquareHighlight.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
long_note.tail.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
long_note.triangle.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
long_note.backgroud.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
long_note.outline.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
long_note.highlight.setPosition((x + 0.5f) * squareSize, (y + 0.5f) * squareSize);
longNoteLayer.draw(LNTail);
longNoteLayer.draw(LNSquareBackgroud);
longNoteLayer.draw(LNSquareOutline);
longNoteLayer.draw(LNTriangle);
longNoteLayer.draw(LNSquareHighlight);
long_note.layer.draw(long_note.tail);
long_note.layer.draw(long_note.backgroud);
long_note.layer.draw(long_note.outline);
long_note.layer.draw(long_note.triangle);
long_note.layer.draw(long_note.highlight);
}
}
}
@ -193,9 +188,9 @@ void Playfield::drawLongNote(
const int& resolution,
Marker& marker,
MarkerEndingState& markerEndingState) {
drawLongNote(note, playbackPosition, ticksAtPlaybackPosition, BPM, resolution);
draw_long_note(note, playbackPosition, ticksAtPlaybackPosition, BPM, resolution);
float squareSize = static_cast<float>(longNoteLayer.getSize().x) / 4;
float squareSize = static_cast<float>(long_note.layer.getSize().x) / 4;
AffineTransform<float> SecondsToTicksProportional(0.f, (60.f / BPM), 0.f, resolution);
AffineTransform<float> SecondsToTicks(
@ -219,10 +214,10 @@ void Playfield::drawLongNote(
auto t = marker.getSprite(markerEndingState, note_offset);
if (t) {
float scale = squareSize / t->get().getSize().x;
markerSprite.setTexture(*t, true);
markerSprite.setScale(scale, scale);
markerSprite.setPosition(x * squareSize, y * squareSize);
markerLayer.draw(markerSprite);
marker_sprite.setTexture(*t, true);
marker_sprite.setScale(scale, scale);
marker_sprite.setPosition(x * squareSize, y * squareSize);
marker_layer.draw(markerSprite);
}
} else {
@ -231,10 +226,10 @@ void Playfield::drawLongNote(
auto t = marker.getSprite(markerEndingState, tail_end_offset);
if (t) {
float scale = squareSize / t->get().getSize().x;
markerSprite.setTexture(*t, true);
markerSprite.setScale(scale, scale);
markerSprite.setPosition(x * squareSize, y * squareSize);
markerLayer.draw(markerSprite);
marker_sprite.setTexture(*t, true);
marker_sprite.setScale(scale, scale);
marker_sprite.setPosition(x * squareSize, y * squareSize);
marker_layer.draw(markerSprite);
}
}
}

View File

@ -1,9 +1,12 @@
#pragma once
#include <SFML/Graphics.hpp>
#include <SFML/Graphics/RenderTexture.hpp>
#include <imgui-SFML.h>
#include <string>
#include "../better_note.hpp"
#include "../better_timing.hpp"
#include "../ln_marker.hpp"
#include "../marker.hpp"
#include "../note.hpp"
@ -17,34 +20,39 @@ public:
sf::Sprite note_selected;
sf::Sprite note_collision;
sf::RenderTexture markerLayer;
sf::Sprite markerSprite;
sf::RenderTexture marker_layer;
sf::Sprite marker_sprite;
LNMarker longNoteMarker;
sf::RenderTexture longNoteLayer;
sf::Sprite LNSquareBackgroud;
sf::Sprite LNSquareOutline;
sf::Sprite LNSquareHighlight;
sf::Sprite LNTail;
sf::Sprite LNTriangle;
struct LongNote {
template<class... Args>
LongNote(Args&& ...args) : marker(std::forward<Args>(args)...) {};
LNMarker marker;
sf::RenderTexture layer;
sf::Sprite backgroud;
sf::Sprite outline;
sf::Sprite highlight;
sf::Sprite tail;
sf::Sprite triangle;
};
LongNote long_note;
void resize(unsigned int width);
void drawLongNote(
const Note& note,
void draw_long_note(
const better::LongNote& note,
const sf::Time& playbackPosition,
const float& ticksAtPlaybackPosition,
const float& BPM,
const int& resolution);
const better::Timing& timing
);
void drawLongNote(
const Note& note,
const sf::Time& playbackPosition,
const float& ticksAtPlaybackPosition,
const float& BPM,
const int& resolution,
const better::Timing& timing,
Marker& marker,
MarkerEndingState& markerEndingState);
MarkerEndingState& markerEndingState
);
private:
const std::filesystem::path texture_path;