mirror of
https://gitlab.com/square-game-liberation-front/F.E.I.S.git
synced 2024-11-14 19:17:43 +01:00
First working bits of waveform mode for the linear view
This commit is contained in:
parent
bb7c1f8593
commit
d4aa0f3532
@ -101,6 +101,15 @@ namespace better {
|
|||||||
std::for_each(first_element, last_element, [&](const bpm_event_type& ptr){cb(ptr);});
|
std::for_each(first_element, last_element, [&](const bpm_event_type& ptr){cb(ptr);});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename Callback>
|
||||||
|
void for_each_event_between(const sf::Time& start, const sf::Time& end, const Callback& cb) const {
|
||||||
|
for_each_event_between(
|
||||||
|
beats_at(start),
|
||||||
|
beats_at(end),
|
||||||
|
cb
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
bool operator==(const Timing&) const = default;
|
bool operator==(const Timing&) const = default;
|
||||||
|
|
||||||
friend std::ostream& operator<<(std::ostream& out, const Timing& t);
|
friend std::ostream& operator<<(std::ostream& out, const Timing& t);
|
||||||
|
@ -14,20 +14,24 @@ namespace Toolkit {
|
|||||||
template <class Key, class Value>
|
template <class Key, class Value>
|
||||||
class Cache {
|
class Cache {
|
||||||
public:
|
public:
|
||||||
|
using key_type = Key;
|
||||||
|
using value_type = Value;
|
||||||
|
using reference_type = std::reference_wrapper<Value>;
|
||||||
|
using const_reference_type = std::reference_wrapper<const Value>;
|
||||||
Cache(std::function<Value(Key)> _load_resource): load_resource(_load_resource) {};
|
Cache(std::function<Value(Key)> _load_resource): load_resource(_load_resource) {};
|
||||||
|
|
||||||
// Does not trigger loading
|
// Does not trigger loading
|
||||||
std::optional<std::reference_wrapper<Value>> get(const Key& key) const {
|
std::optional<const_reference_type> get(const Key& key) const {
|
||||||
std::shared_lock lock{mapping_mutex};
|
std::shared_lock lock{mapping_mutex};
|
||||||
if (has(key)) {
|
if (has(key)) {
|
||||||
return mapping.at(key);
|
return std::cref(mapping.at(key));
|
||||||
} else {
|
} else {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns empty if not already loaded
|
// Returns empty if not already loaded
|
||||||
std::optional<std::reference_wrapper<Value>> async_load(const Key& key) {
|
std::optional<reference_type> async_load(const Key& key) {
|
||||||
if (not has(key)) {
|
if (not has(key)) {
|
||||||
if (not is_loading(key)) {
|
if (not is_loading(key)) {
|
||||||
async_emplace(key);
|
async_emplace(key);
|
||||||
@ -60,7 +64,7 @@ namespace Toolkit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::reference_wrapper<Value> blocking_load(const Key& key) {
|
reference_type blocking_load(const Key& key) {
|
||||||
std::shared_lock lock{mapping_mutex};
|
std::shared_lock lock{mapping_mutex};
|
||||||
blocking_emplace(key);
|
blocking_emplace(key);
|
||||||
return mapping.at(key);
|
return mapping.at(key);
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
#include "linear_view_colors.hpp"
|
#include "linear_view_colors.hpp"
|
||||||
|
#include "linear_view_mode.hpp"
|
||||||
#include "marker.hpp"
|
#include "marker.hpp"
|
||||||
#include "nowide/fstream.hpp"
|
#include "nowide/fstream.hpp"
|
||||||
#include "variant_visitor.hpp"
|
#include "variant_visitor.hpp"
|
||||||
@ -47,6 +48,7 @@ void config::LinearView::load_from_v1_0_0_table(const toml::table& tbl) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto linear_view_table = tbl["linear_view"].ref<toml::table>();
|
const auto linear_view_table = tbl["linear_view"].ref<toml::table>();
|
||||||
|
mode = linear_view::mode::load_from_v1_0_0_table(linear_view_table);
|
||||||
colors.load_from_v1_0_0_table(linear_view_table);
|
colors.load_from_v1_0_0_table(linear_view_table);
|
||||||
sizes.load_from_v1_0_0_table(linear_view_table);
|
sizes.load_from_v1_0_0_table(linear_view_table);
|
||||||
lane_order = linear_view::lane_order::load_from_v1_0_0_table(linear_view_table);
|
lane_order = linear_view::lane_order::load_from_v1_0_0_table(linear_view_table);
|
||||||
@ -61,6 +63,7 @@ void config::LinearView::load_from_v1_0_0_table(const toml::table& tbl) {
|
|||||||
|
|
||||||
void config::LinearView::dump_as_v1_0_0(toml::table& tbl) {
|
void config::LinearView::dump_as_v1_0_0(toml::table& tbl) {
|
||||||
toml::table linear_view_table;
|
toml::table linear_view_table;
|
||||||
|
linear_view::mode::dump_as_v1_0_0(mode, linear_view_table);
|
||||||
colors.dump_as_v1_0_0(linear_view_table);
|
colors.dump_as_v1_0_0(linear_view_table);
|
||||||
sizes.dump_as_v1_0_0(linear_view_table);
|
sizes.dump_as_v1_0_0(linear_view_table);
|
||||||
linear_view::lane_order::dump_as_v1_0_0(lane_order, linear_view_table);
|
linear_view::lane_order::dump_as_v1_0_0(lane_order, linear_view_table);
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include "quantization_colors.hpp"
|
#include "quantization_colors.hpp"
|
||||||
#include "linear_view_colors.hpp"
|
#include "linear_view_colors.hpp"
|
||||||
|
#include "linear_view_mode.hpp"
|
||||||
#include "linear_view_sizes.hpp"
|
#include "linear_view_sizes.hpp"
|
||||||
#include "marker.hpp"
|
#include "marker.hpp"
|
||||||
#include "widgets/lane_order.hpp"
|
#include "widgets/lane_order.hpp"
|
||||||
@ -22,6 +23,7 @@ namespace config {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct LinearView {
|
struct LinearView {
|
||||||
|
linear_view::Mode mode;
|
||||||
linear_view::Colors colors;
|
linear_view::Colors colors;
|
||||||
linear_view::Sizes sizes;
|
linear_view::Sizes sizes;
|
||||||
linear_view::LaneOrder lane_order;
|
linear_view::LaneOrder lane_order;
|
||||||
|
@ -980,16 +980,18 @@ void EditorState::display_linear_view() {
|
|||||||
if (chart_state) {
|
if (chart_state) {
|
||||||
auto header_height = ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2.f;
|
auto header_height = ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2.f;
|
||||||
ImGui::SetCursorPos({0, header_height});
|
ImGui::SetCursorPos({0, header_height});
|
||||||
linear_view.draw(
|
LinearView::DrawArgs draw_args {
|
||||||
ImGui::GetWindowDrawList(),
|
ImGui::GetWindowDrawList(),
|
||||||
*chart_state,
|
*chart_state,
|
||||||
|
waveform_cache.get(song.metadata.audio),
|
||||||
*applicable_timing,
|
*applicable_timing,
|
||||||
current_exact_beats(),
|
current_exact_beats(),
|
||||||
beats_at(editable_range.end),
|
beats_at(editable_range.end),
|
||||||
get_snap_step(),
|
get_snap_step(),
|
||||||
ImGui::GetContentRegionMax(),
|
ImGui::GetContentRegionMax(),
|
||||||
ImGui::GetCursorScreenPos()
|
ImGui::GetCursorScreenPos()
|
||||||
);
|
};
|
||||||
|
linear_view.draw(draw_args);
|
||||||
} else {
|
} else {
|
||||||
ImGui::TextDisabled("- no chart selected -");
|
ImGui::TextDisabled("- no chart selected -");
|
||||||
}
|
}
|
||||||
@ -1422,7 +1424,7 @@ void EditorState::reload_music() {
|
|||||||
const auto absolute_music_path = song_path->parent_path() / song.metadata.audio;
|
const auto absolute_music_path = song_path->parent_path() / song.metadata.audio;
|
||||||
try {
|
try {
|
||||||
music.emplace(std::make_shared<OpenMusic>(absolute_music_path));
|
music.emplace(std::make_shared<OpenMusic>(absolute_music_path));
|
||||||
waveform_cache.async_emplace(absolute_music_path);
|
waveform_cache.async_emplace(song.metadata.audio);
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
clear_music();
|
clear_music();
|
||||||
}
|
}
|
||||||
|
32
src/linear_view_mode.cpp
Normal file
32
src/linear_view_mode.cpp
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#include "linear_view_mode.hpp"
|
||||||
|
|
||||||
|
#include "variant_visitor.hpp"
|
||||||
|
|
||||||
|
namespace linear_view {
|
||||||
|
Mode load_from_v1_0_0_table(const toml::table& linear_view) {
|
||||||
|
const auto mode_string = linear_view["mode"].value<std::string>();
|
||||||
|
if (not mode_string) {
|
||||||
|
return linear_view::default_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*mode_string == "beats") {
|
||||||
|
return mode::Beats{};
|
||||||
|
} else if (*mode_string == "waveform") {
|
||||||
|
return mode::Waveform{};
|
||||||
|
}
|
||||||
|
|
||||||
|
return linear_view::default_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dump_as_v1_0_0(const Mode& mode, toml::table& linear_view) {
|
||||||
|
const auto _dump = VariantVisitor {
|
||||||
|
[&](const mode::Beats&) {
|
||||||
|
linear_view.insert_or_assign("mode", "beats");
|
||||||
|
},
|
||||||
|
[&](const mode::Waveform&) {
|
||||||
|
linear_view.insert_or_assign("mode", "waveform");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
std::visit(_dump, mode);
|
||||||
|
}
|
||||||
|
}
|
21
src/linear_view_mode.hpp
Normal file
21
src/linear_view_mode.hpp
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
#include <toml++/toml.h>
|
||||||
|
|
||||||
|
namespace linear_view {
|
||||||
|
namespace mode {
|
||||||
|
struct Beats {};
|
||||||
|
struct Waveform {};
|
||||||
|
}
|
||||||
|
|
||||||
|
using Mode = std::variant<mode::Beats, mode::Waveform>;
|
||||||
|
|
||||||
|
namespace mode {
|
||||||
|
linear_view::Mode load_from_v1_0_0_table(const toml::table& linear_view);
|
||||||
|
void dump_as_v1_0_0(const linear_view::Mode& mode, toml::table& linear_view);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Mode default_mode = mode::Beats{};
|
||||||
|
}
|
@ -33,6 +33,7 @@ sources += files(
|
|||||||
'toolbox.cpp',
|
'toolbox.cpp',
|
||||||
'utf8_file_input_stream.cpp',
|
'utf8_file_input_stream.cpp',
|
||||||
'utf8_strings.cpp',
|
'utf8_strings.cpp',
|
||||||
|
'waveform.cpp'
|
||||||
)
|
)
|
||||||
|
|
||||||
conf_data = configuration_data()
|
conf_data = configuration_data()
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
#include "variant_visitor.hpp"
|
#include "variant_visitor.hpp"
|
||||||
|
|
||||||
namespace linear_view {
|
namespace linear_view {
|
||||||
sf::Color QuantizationColors::color_at_beat(const Fraction& time) {
|
sf::Color QuantizationColors::color_at_beat(const Fraction& time) const {
|
||||||
const auto denominator = time.denominator();
|
const auto denominator = time.denominator();
|
||||||
if (denominator > palette.rbegin()->first) {
|
if (denominator > palette.rbegin()->first) {
|
||||||
return default_;
|
return default_;
|
||||||
|
@ -20,7 +20,7 @@ namespace linear_view {
|
|||||||
{16, {68, 254, 0}}
|
{16, {68, 254, 0}}
|
||||||
}};
|
}};
|
||||||
sf::Color default_ = {156, 156, 156};
|
sf::Color default_ = {156, 156, 156};
|
||||||
sf::Color color_at_beat(const Fraction& time);
|
sf::Color color_at_beat(const Fraction& time) const;
|
||||||
|
|
||||||
void load_from_v1_0_0_table(const toml::table& linear_view_table);
|
void load_from_v1_0_0_table(const toml::table& linear_view_table);
|
||||||
void dump_as_v1_0_0(toml::table& linear_view_table);
|
void dump_as_v1_0_0(toml::table& linear_view_table);
|
||||||
|
@ -59,6 +59,10 @@ Fraction::operator double() const {
|
|||||||
return value.get_d();
|
return value.get_d();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Fraction::operator float() const {
|
||||||
|
return static_cast<float>(value.get_d());
|
||||||
|
};
|
||||||
|
|
||||||
const mpz_class& Fraction::numerator() const {
|
const mpz_class& Fraction::numerator() const {
|
||||||
return value.get_num();
|
return value.get_num();
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ public:
|
|||||||
explicit operator std::int64_t() const;
|
explicit operator std::int64_t() const;
|
||||||
explicit operator std::uint64_t() const;
|
explicit operator std::uint64_t() const;
|
||||||
explicit operator double() const;
|
explicit operator double() const;
|
||||||
|
explicit operator float() const;
|
||||||
|
|
||||||
const mpz_class& numerator() const;
|
const mpz_class& numerator() const;
|
||||||
const mpz_class& denominator() const;
|
const mpz_class& denominator() const;
|
||||||
|
@ -111,11 +111,6 @@ public:
|
|||||||
private:
|
private:
|
||||||
T a;
|
T a;
|
||||||
T b;
|
T b;
|
||||||
|
|
||||||
public:
|
|
||||||
void setB(T b) { AffineTransform::b = b; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
T low_input;
|
T low_input;
|
||||||
T high_input;
|
T high_input;
|
||||||
T low_output;
|
T low_output;
|
||||||
|
@ -64,17 +64,22 @@ namespace waveform {
|
|||||||
|
|
||||||
std::optional<Waveform> compute_waveform(const std::filesystem::path& audio) {
|
std::optional<Waveform> compute_waveform(const std::filesystem::path& audio) {
|
||||||
feis::HoldFileStreamMixin<sf::InputSoundFile> sound_file;
|
feis::HoldFileStreamMixin<sf::InputSoundFile> sound_file;
|
||||||
try {
|
if (not sound_file.open_from_path(audio)) {
|
||||||
sound_file.open_from_path(audio);
|
|
||||||
} catch (const std::exception&) {
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
Waveform waveform;
|
Waveform waveform{
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
sound_file.getSampleRate(),
|
||||||
|
sound_file.getChannelCount()
|
||||||
|
};
|
||||||
unsigned int size = 8;
|
unsigned int size = 8;
|
||||||
waveform[size] = load_initial_summary(sound_file, size);
|
waveform.channels_per_chunk_size[size] = load_initial_summary(sound_file, size);
|
||||||
while (waveform.size() < 10) {
|
waveform.chunk_sizes.push_back(size);
|
||||||
waveform[size * 2] = downsample_to_half(waveform.rbegin()->second);
|
while (waveform.channels_per_chunk_size.size() < 10) {
|
||||||
size *= 2;
|
size *= 2;
|
||||||
|
waveform.channels_per_chunk_size[size] = downsample_to_half(waveform.channels_per_chunk_size.rbegin()->second);
|
||||||
|
waveform.chunk_sizes.push_back(size);
|
||||||
}
|
}
|
||||||
return waveform;
|
return waveform;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <map>
|
#include <map>
|
||||||
@ -20,7 +21,12 @@ namespace waveform {
|
|||||||
|
|
||||||
using DataFrame = std::vector<DataPoint>;
|
using DataFrame = std::vector<DataPoint>;
|
||||||
using Channels = std::vector<DataFrame>;
|
using Channels = std::vector<DataFrame>;
|
||||||
using Waveform = std::map<unsigned int, Channels>;
|
struct Waveform {
|
||||||
|
std::map<unsigned int, Channels> channels_per_chunk_size;
|
||||||
|
std::vector<unsigned int> chunk_sizes; // for fast nth chunk size access
|
||||||
|
std::size_t sample_rate;
|
||||||
|
std::size_t channel_count;
|
||||||
|
};
|
||||||
|
|
||||||
Channels load_initial_summary(
|
Channels load_initial_summary(
|
||||||
feis::HoldFileStreamMixin<sf::InputSoundFile>& sound_file,
|
feis::HoldFileStreamMixin<sf::InputSoundFile>& sound_file,
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
#include "../special_numeric_types.hpp"
|
#include "../special_numeric_types.hpp"
|
||||||
#include "../toolbox.hpp"
|
#include "../toolbox.hpp"
|
||||||
#include "../variant_visitor.hpp"
|
#include "../variant_visitor.hpp"
|
||||||
|
#include "linear_view_colors.hpp"
|
||||||
#include "widgets/lane_order.hpp"
|
#include "widgets/lane_order.hpp"
|
||||||
|
|
||||||
|
|
||||||
@ -40,79 +41,6 @@ void SelectionRectangle::reset() {
|
|||||||
end = {-1, -1};
|
end = {-1, -1};
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace linear_view::mode {
|
|
||||||
|
|
||||||
void Waveform::draw_waveform(
|
|
||||||
ImDrawList* draw_list,
|
|
||||||
const sf::Time current_time,
|
|
||||||
int zoom
|
|
||||||
) {
|
|
||||||
if (ImGui::Begin("Waveform view")) {
|
|
||||||
if (not data_is_ready) {
|
|
||||||
feis::CenteredText("Loading ...");
|
|
||||||
return ImGui::End();
|
|
||||||
}
|
|
||||||
if (channels_per_chunk_size.empty()) {
|
|
||||||
feis::CenteredText("No data ???");
|
|
||||||
return ImGui::End();
|
|
||||||
}
|
|
||||||
|
|
||||||
zoom = std::clamp(zoom, 0, static_cast<int>(channels_per_chunk_size.size()) - 1);
|
|
||||||
const auto& channels_it = channels_per_chunk_size.at(channels_per_chunk_size.size() - zoom - 1);
|
|
||||||
const auto& [chunk_size, channels] = channels_it;
|
|
||||||
const auto window = ImGui::GetCurrentWindow();
|
|
||||||
const auto work_rect = window->WorkRect;
|
|
||||||
const float waveform_w_margin = 10.f;
|
|
||||||
const float waveform_bounding_width = work_rect.GetWidth() / channels.size();
|
|
||||||
const float waveform_width = waveform_bounding_width - waveform_w_margin;
|
|
||||||
const AffineTransform<float> value_to_pixel_offset_from_waveform_center{
|
|
||||||
std::numeric_limits<DataPoint::value_type>::min(),
|
|
||||||
std::numeric_limits<DataPoint::value_type>::max(),
|
|
||||||
-waveform_width / 2,
|
|
||||||
waveform_width / 2
|
|
||||||
};
|
|
||||||
const float cursor_y = 50.f;
|
|
||||||
for (std::size_t channel_index = 0; channel_index < channels.size(); channel_index++) {
|
|
||||||
const auto& data_points = channels[channel_index];
|
|
||||||
const std::int64_t sample_at_cursor = time_to_samples(current_time, sound_file.getSampleRate(), sound_file.getChannelCount());
|
|
||||||
const auto chunk_at_cursor = sample_at_cursor / chunk_size / sound_file.getChannelCount();
|
|
||||||
const auto first_chunk = chunk_at_cursor - static_cast<std::int64_t>(cursor_y);
|
|
||||||
const auto end_chunk = first_chunk + static_cast<std::int64_t>(work_rect.GetHeight());
|
|
||||||
const auto waveform_x_center = channel_index * waveform_bounding_width + waveform_bounding_width / 2;
|
|
||||||
for (std::int64_t data_point_index = first_chunk; data_point_index < end_chunk; data_point_index++) {
|
|
||||||
if (data_point_index < 0 or static_cast<std::size_t>(data_point_index) >= data_points.size()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const auto& data_point = data_points[data_point_index];
|
|
||||||
const auto y = work_rect.Min.y + data_point_index - first_chunk;
|
|
||||||
const auto x_offset_min = value_to_pixel_offset_from_waveform_center.transform(data_point.min);
|
|
||||||
const auto x_offset_max = value_to_pixel_offset_from_waveform_center.transform(data_point.max);
|
|
||||||
const auto x_min = work_rect.Min.x + waveform_x_center + x_offset_min;
|
|
||||||
const auto x_max = work_rect.Min.x + waveform_x_center + x_offset_max;
|
|
||||||
draw_list->AddLine({x_min, y}, {x_max, y}, ImColor(sf::Color::White));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::End();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Waveform::prepare_data() {
|
|
||||||
unsigned int size = 8;
|
|
||||||
channels_per_chunk_size.emplace_back(
|
|
||||||
size,
|
|
||||||
waveform::load_initial_summary(sound_file, size)
|
|
||||||
);
|
|
||||||
while (channels_per_chunk_size.size() < 10) {
|
|
||||||
channels_per_chunk_size.emplace_back(
|
|
||||||
size * 2,
|
|
||||||
waveform::downsample_to_half(channels_per_chunk_size.rbegin()->second)
|
|
||||||
);
|
|
||||||
size *= 2;
|
|
||||||
}
|
|
||||||
data_is_ready = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LinearView::LinearView(std::filesystem::path assets, config::Config& config_) :
|
LinearView::LinearView(std::filesystem::path assets, config::Config& config_) :
|
||||||
colors(config_.linear_view.colors),
|
colors(config_.linear_view.colors),
|
||||||
sizes(config_.linear_view.sizes),
|
sizes(config_.linear_view.sizes),
|
||||||
@ -126,7 +54,7 @@ LinearView::LinearView(std::filesystem::path assets, config::Config& config_) :
|
|||||||
set_zoom(config_.linear_view.zoom);
|
set_zoom(config_.linear_view.zoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LinearView::draw(LinearView::draw_args_type& args) {
|
void LinearView::draw(LinearView::DrawArgs& args) {
|
||||||
const auto draw_function = VariantVisitor {
|
const auto draw_function = VariantVisitor {
|
||||||
[&](linear_view::mode::Beats) {this->draw_in_beats_mode(args);},
|
[&](linear_view::mode::Beats) {this->draw_in_beats_mode(args);},
|
||||||
[&](linear_view::mode::Waveform) {this->draw_in_waveform_mode(args);},
|
[&](linear_view::mode::Waveform) {this->draw_in_waveform_mode(args);},
|
||||||
@ -167,7 +95,7 @@ linear_view::ComputedSizes linear_view::compute_sizes(
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LinearView::draw_in_beats_mode(LinearView::draw_args_type& args) {
|
void LinearView::draw_in_beats_mode(LinearView::DrawArgs& args) {
|
||||||
auto [
|
auto [
|
||||||
draw_list,
|
draw_list,
|
||||||
chart_state,
|
chart_state,
|
||||||
@ -260,55 +188,15 @@ void LinearView::draw_in_beats_mode(LinearView::draw_args_type& args) {
|
|||||||
const auto collision_zone_y = beats_to_pixels_absolute.transform(first_colliding_beat);
|
const auto collision_zone_y = beats_to_pixels_absolute.transform(first_colliding_beat);
|
||||||
const auto last_colliding_beat = args.timing.beats_at(note_seconds + collision_zone * 0.5f);
|
const auto last_colliding_beat = args.timing.beats_at(note_seconds + collision_zone * 0.5f);
|
||||||
const auto collision_zone_height = beats_to_pixels_proportional.transform(last_colliding_beat - first_colliding_beat);
|
const auto collision_zone_height = beats_to_pixels_proportional.transform(last_colliding_beat - first_colliding_beat);
|
||||||
const sf::Vector2f collision_zone_pos = {
|
draw_tap_note(
|
||||||
note_x,
|
args,
|
||||||
static_cast<float>(static_cast<double>(collision_zone_y))
|
computed_sizes,
|
||||||
};
|
tap_note,
|
||||||
const sf::Vector2f collizion_zone_size = {
|
{note_x, note_y},
|
||||||
computed_sizes.collizion_zone_width,
|
collision_zone,
|
||||||
static_cast<float>(static_cast<double>(collision_zone_height))
|
static_cast<float>(collision_zone_y),
|
||||||
};
|
static_cast<float>(collision_zone_height)
|
||||||
const auto collision_zone_color = [&](){
|
|
||||||
if (args.chart_state.chart.notes->is_colliding(tap_note, args.timing, collision_zone)) {
|
|
||||||
return colors.conflicting_collision_zone;
|
|
||||||
} else {
|
|
||||||
return colors.normal_collision_zone;
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
const auto tap_note_color = [&](){
|
|
||||||
if (use_quantization_colors) {
|
|
||||||
return quantization_colors.color_at_beat(tap_note.get_time());
|
|
||||||
} else if (args.chart_state.chart.notes->is_colliding(tap_note, args.timing, collision_zone)) {
|
|
||||||
return colors.conflicting_tap_note;
|
|
||||||
} else {
|
|
||||||
return colors.normal_tap_note;
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
draw_rectangle(
|
|
||||||
args.draw_list,
|
|
||||||
args.origin + collision_zone_pos,
|
|
||||||
collizion_zone_size,
|
|
||||||
{0.5f, 0.f},
|
|
||||||
collision_zone_color
|
|
||||||
);
|
);
|
||||||
const sf::Vector2f note_pos = {note_x, note_y};
|
|
||||||
draw_rectangle(
|
|
||||||
args.draw_list,
|
|
||||||
args.origin + note_pos,
|
|
||||||
computed_sizes.note_size,
|
|
||||||
{0.5f, 0.5f},
|
|
||||||
tap_note_color
|
|
||||||
);
|
|
||||||
if (args.chart_state.selected_stuff.notes.contains(tap_note)) {
|
|
||||||
draw_rectangle(
|
|
||||||
args.draw_list,
|
|
||||||
args.origin + note_pos,
|
|
||||||
computed_sizes.selected_note_size,
|
|
||||||
{0.5f, 0.5f},
|
|
||||||
colors.selected_note_fill,
|
|
||||||
colors.selected_note_outline
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[&](const better::LongNote& long_note){
|
[&](const better::LongNote& long_note){
|
||||||
const auto opt_lane = button_to_lane(long_note.get_position());
|
const auto opt_lane = button_to_lane(long_note.get_position());
|
||||||
@ -324,189 +212,58 @@ void LinearView::draw_in_beats_mode(LinearView::draw_args_type& args) {
|
|||||||
const auto note_end_seconds = args.timing.time_at(long_note.get_end());
|
const auto note_end_seconds = args.timing.time_at(long_note.get_end());
|
||||||
const auto last_colliding_beat = args.timing.beats_at(note_end_seconds + collision_zone * 0.5f);
|
const auto last_colliding_beat = args.timing.beats_at(note_end_seconds + collision_zone * 0.5f);
|
||||||
const auto collision_zone_height = beats_to_pixels_proportional.transform(last_colliding_beat - first_colliding_beat);
|
const auto collision_zone_height = beats_to_pixels_proportional.transform(last_colliding_beat - first_colliding_beat);
|
||||||
const sf::Vector2f collision_zone_pos = {
|
|
||||||
note_x,
|
|
||||||
static_cast<float>(static_cast<double>(collision_zone_y))
|
|
||||||
};
|
|
||||||
const sf::Vector2f collision_zone_size = {
|
|
||||||
computed_sizes.collizion_zone_width,
|
|
||||||
static_cast<float>(static_cast<double>(collision_zone_height))
|
|
||||||
};
|
|
||||||
auto collision_zone_color = colors.normal_collision_zone;
|
|
||||||
auto tap_note_color = [&](){
|
|
||||||
if (use_quantization_colors) {
|
|
||||||
return quantization_colors.color_at_beat(long_note.get_time());
|
|
||||||
} else {
|
|
||||||
return colors.normal_tap_note;
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
auto long_note_color = colors.normal_long_note;
|
|
||||||
if (args.chart_state.chart.notes->is_colliding(long_note, args.timing, collision_zone)) {
|
|
||||||
collision_zone_color = colors.conflicting_collision_zone;
|
|
||||||
if (not use_quantization_colors) {
|
|
||||||
tap_note_color = colors.conflicting_tap_note;
|
|
||||||
}
|
|
||||||
long_note_color = colors.conflicting_long_note;
|
|
||||||
}
|
|
||||||
draw_rectangle(
|
|
||||||
args.draw_list,
|
|
||||||
args.origin + collision_zone_pos,
|
|
||||||
collision_zone_size,
|
|
||||||
{0.5f, 0.f},
|
|
||||||
collision_zone_color
|
|
||||||
);
|
|
||||||
const auto long_note_rect_height = beats_to_pixels_proportional.transform(long_note.get_duration());
|
const auto long_note_rect_height = beats_to_pixels_proportional.transform(long_note.get_duration());
|
||||||
const sf::Vector2f long_note_size = {
|
draw_long_note(
|
||||||
computed_sizes.long_note_rect_width,
|
args,
|
||||||
static_cast<float>(static_cast<double>(long_note_rect_height))
|
computed_sizes,
|
||||||
};
|
long_note,
|
||||||
const sf::Vector2f note_pos = {note_x, note_y};
|
{note_x, note_y},
|
||||||
draw_rectangle(
|
static_cast<float>(long_note_rect_height),
|
||||||
args.draw_list,
|
collision_zone,
|
||||||
args.origin + note_pos,
|
static_cast<float>(collision_zone_y),
|
||||||
long_note_size,
|
static_cast<float>(collision_zone_height)
|
||||||
{0.5f, 0.f},
|
|
||||||
long_note_color
|
|
||||||
);
|
);
|
||||||
draw_rectangle(
|
|
||||||
args.draw_list,
|
|
||||||
args.origin + note_pos,
|
|
||||||
computed_sizes.note_size,
|
|
||||||
{0.5f, 0.5f},
|
|
||||||
tap_note_color
|
|
||||||
);
|
|
||||||
if (args.chart_state.selected_stuff.notes.contains(long_note)) {
|
|
||||||
draw_rectangle(
|
|
||||||
args.draw_list,
|
|
||||||
args.origin + note_pos,
|
|
||||||
computed_sizes.selected_note_size,
|
|
||||||
{0.5f, 0.5f},
|
|
||||||
colors.selected_note_fill,
|
|
||||||
colors.selected_note_outline
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const auto first_visible_second = timing.time_at(first_beat_in_frame);
|
const auto first_visible_second = timing.time_at(first_beat_in_frame);
|
||||||
const auto first_visible_collision_zone = timing.beats_at(first_visible_second - sf::milliseconds(500));
|
|
||||||
const auto last_visible_second = timing.time_at(last_beat_in_frame);
|
const auto last_visible_second = timing.time_at(last_beat_in_frame);
|
||||||
const auto last_visible_collision_zone = timing.beats_at(last_visible_second + sf::milliseconds(500));
|
draw_notes(
|
||||||
chart_state.chart.notes->in(
|
first_visible_second,
|
||||||
first_visible_collision_zone,
|
last_visible_second,
|
||||||
last_visible_collision_zone,
|
chart_state,
|
||||||
[&](const better::Notes::iterator& it){
|
timing,
|
||||||
it->second.visit(draw_note);
|
draw_note
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
draw_long_note_dummy(chart_state, snap, draw_note);
|
||||||
if (chart_state.long_note_being_created.has_value()) {
|
draw_cursor(draw_list, origin, computed_sizes);
|
||||||
draw_note(
|
draw_time_selection(
|
||||||
make_long_note_dummy_for_linear_view(
|
|
||||||
*chart_state.long_note_being_created,
|
|
||||||
snap
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the cursor
|
|
||||||
draw_rectangle(
|
|
||||||
draw_list,
|
draw_list,
|
||||||
origin + computed_sizes.cursor_pos,
|
origin,
|
||||||
computed_sizes.cursor_size,
|
chart_state,
|
||||||
{0, 0.5},
|
computed_sizes,
|
||||||
colors.cursor
|
[&](const Fraction& beats) -> float {
|
||||||
|
return static_cast<float>(beats_to_pixels_absolute.transform(beats));
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
handle_mouse_selection(
|
||||||
// Draw the time selection
|
|
||||||
const float selection_width = computed_sizes.timeline_width;
|
|
||||||
if (chart_state.time_selection.has_value()) {
|
|
||||||
const auto pixel_interval = Interval{
|
|
||||||
beats_to_pixels_absolute.transform(chart_state.time_selection->start),
|
|
||||||
beats_to_pixels_absolute.transform(chart_state.time_selection->end)
|
|
||||||
};
|
|
||||||
if (pixel_interval.intersects({0, computed_sizes.y})) {
|
|
||||||
const sf::Vector2f selection_size = {
|
|
||||||
selection_width,
|
|
||||||
static_cast<float>(static_cast<double>(pixel_interval.width()))
|
|
||||||
};
|
|
||||||
const sf::Vector2f selection_pos = {
|
|
||||||
computed_sizes.timeline_left,
|
|
||||||
static_cast<float>(static_cast<double>(pixel_interval.start))
|
|
||||||
};
|
|
||||||
draw_rectangle(
|
|
||||||
draw_list,
|
draw_list,
|
||||||
origin + selection_pos,
|
origin,
|
||||||
selection_size,
|
chart_state,
|
||||||
{0, 0},
|
timing,
|
||||||
colors.tab_selection.fill,
|
computed_sizes,
|
||||||
colors.tab_selection.border
|
[&](const float pixels){
|
||||||
);
|
return beats_to_pixels_absolute.backwards_transform(pixels);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const auto current_window = ImGui::GetCurrentWindow();
|
|
||||||
|
|
||||||
// Don't start the selection rect if we start :
|
|
||||||
// - outside the contents of the window
|
|
||||||
// - over anything
|
|
||||||
if (
|
|
||||||
ImGui::IsMouseClicked(ImGuiMouseButton_Left)
|
|
||||||
and current_window->InnerClipRect.Contains(ImGui::GetMousePos())
|
|
||||||
and not ImGui::IsAnyItemHovered()
|
|
||||||
and ImGui::IsWindowFocused()
|
|
||||||
) {
|
|
||||||
started_selection_inside_window = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (started_selection_inside_window) {
|
|
||||||
if (
|
|
||||||
draw_selection_rect(
|
|
||||||
draw_list,
|
|
||||||
selection_rectangle.start,
|
|
||||||
selection_rectangle.end,
|
|
||||||
colors.selection_rect
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
chart_state.selected_stuff.clear();
|
|
||||||
// Select everything inside the selection rectangle
|
|
||||||
const sf::Vector2f upper_left = {
|
|
||||||
std::min(selection_rectangle.start.x, selection_rectangle.end.x),
|
|
||||||
std::min(selection_rectangle.start.y, selection_rectangle.end.y),
|
|
||||||
};
|
|
||||||
const sf::Vector2f lower_right = {
|
|
||||||
std::max(selection_rectangle.start.x, selection_rectangle.end.x),
|
|
||||||
std::max(selection_rectangle.start.y, selection_rectangle.end.y),
|
|
||||||
};
|
|
||||||
const ImRect full_selection = {upper_left, lower_right};
|
|
||||||
ImRect bpm_zone = {origin.x + computed_sizes.bpm_events_left, -INFINITY, INFINITY, INFINITY};
|
|
||||||
bpm_zone.ClipWith(current_window->InnerRect);
|
|
||||||
if (full_selection.Overlaps(bpm_zone)) {
|
|
||||||
const auto first_selected_beat = beats_to_pixels_absolute.backwards_transform(full_selection.Min.y - origin.y);
|
|
||||||
const auto last_selected_beat = beats_to_pixels_absolute.backwards_transform(full_selection.Max.y - origin.y);
|
|
||||||
timing.for_each_event_between(
|
|
||||||
first_selected_beat,
|
|
||||||
last_selected_beat,
|
|
||||||
[&](const auto& event){
|
|
||||||
args.chart_state.selected_stuff.bpm_events.insert(
|
|
||||||
{event.get_bpm(), event.get_beats()}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
selection_rectangle.reset();
|
|
||||||
started_selection_inside_window = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void LinearView::draw_in_waveform_mode(LinearView::draw_args_type& args) {
|
void LinearView::draw_in_waveform_mode(LinearView::DrawArgs& args) {
|
||||||
auto [
|
auto [
|
||||||
draw_list,
|
draw_list,
|
||||||
chart_state,
|
chart_state,
|
||||||
waveform_cache,
|
opt_ref_to_an_opt_waveform,
|
||||||
timing,
|
timing,
|
||||||
current_beat,
|
current_beat,
|
||||||
last_editable_beat,
|
last_editable_beat,
|
||||||
@ -514,18 +271,78 @@ void LinearView::draw_in_waveform_mode(LinearView::draw_args_type& args) {
|
|||||||
window_size,
|
window_size,
|
||||||
origin
|
origin
|
||||||
] = args;
|
] = args;
|
||||||
|
|
||||||
const auto computed_sizes = linear_view::compute_sizes(window_size, sizes);
|
const auto computed_sizes = linear_view::compute_sizes(window_size, sizes);
|
||||||
|
if (
|
||||||
|
not opt_ref_to_an_opt_waveform
|
||||||
|
or not opt_ref_to_an_opt_waveform.value().get()
|
||||||
|
) {
|
||||||
|
feis::CenteredText("Loading ...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto waveform = opt_ref_to_an_opt_waveform.value().get().value();
|
||||||
|
if (waveform.channels_per_chunk_size.empty()) {
|
||||||
|
feis::CenteredText("No data ???");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Here we compute the range of visible beats from the size of the window
|
zoom = std::clamp(zoom, 0, static_cast<int>(waveform.channels_per_chunk_size.size()) - 1);
|
||||||
// in pixels, we know by definition that the current beat is exactly at
|
const auto chunk_size = waveform.chunk_sizes.at(waveform.chunk_sizes.size() - zoom - 1);
|
||||||
// cursor_y pixels and we use this fact to compute the rest
|
const auto& channels = waveform.channels_per_chunk_size.at(chunk_size);
|
||||||
const auto beats_before_cursor = beats_to_pixels_proportional.backwards_transform(computed_sizes.cursor_y);
|
const auto window = ImGui::GetCurrentWindow();
|
||||||
const auto beats_after_cursor = beats_to_pixels_proportional.backwards_transform(static_cast<float>(computed_sizes.y) - computed_sizes.cursor_y);
|
const auto work_rect = window->WorkRect;
|
||||||
const Fraction first_beat_in_frame = current_beat - beats_before_cursor;
|
const float waveform_w_margin = 10.f;
|
||||||
const Fraction last_beat_in_frame = current_beat + beats_after_cursor;
|
const float waveform_bounding_width = work_rect.GetWidth() / channels.size();
|
||||||
AffineTransform<Fraction> beats_to_pixels_absolute{first_beat_in_frame, last_beat_in_frame, 0, computed_sizes.y};
|
const float waveform_width = waveform_bounding_width - waveform_w_margin;
|
||||||
|
const AffineTransform<float> value_to_pixel_offset_from_waveform_center{
|
||||||
|
std::numeric_limits<waveform::DataPoint::value_type>::min(),
|
||||||
|
std::numeric_limits<waveform::DataPoint::value_type>::max(),
|
||||||
|
-waveform_width / 2,
|
||||||
|
waveform_width / 2
|
||||||
|
};
|
||||||
|
const auto current_time = timing.time_at(current_beat);
|
||||||
|
const std::int64_t sample_at_cursor = time_to_samples(current_time, waveform.sample_rate, waveform.channel_count);
|
||||||
|
const std::int64_t chunk_at_cursor = sample_at_cursor / chunk_size / waveform.channel_count;
|
||||||
|
const auto first_chunk = chunk_at_cursor - static_cast<std::int64_t>(sizes.cursor_height);
|
||||||
|
const auto end_chunk = first_chunk + static_cast<std::int64_t>(work_rect.GetHeight());
|
||||||
|
const AffineTransform<float> seconds_to_pixels_proportional {
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
static_cast<float>(waveform.sample_rate) / chunk_size
|
||||||
|
};
|
||||||
|
for (std::size_t channel_index = 0; channel_index < channels.size(); channel_index++) {
|
||||||
|
const auto& data_points = channels[channel_index];
|
||||||
|
const auto waveform_x_center = channel_index * waveform_bounding_width + waveform_bounding_width / 2;
|
||||||
|
for (std::int64_t data_point_index = first_chunk; data_point_index < end_chunk; data_point_index++) {
|
||||||
|
if (data_point_index < 0 or static_cast<std::size_t>(data_point_index) >= data_points.size()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto& data_point = data_points[data_point_index];
|
||||||
|
const auto y = work_rect.Min.y + data_point_index - first_chunk;
|
||||||
|
const auto x_offset_min = value_to_pixel_offset_from_waveform_center.transform(data_point.min);
|
||||||
|
const auto x_offset_max = value_to_pixel_offset_from_waveform_center.transform(data_point.max);
|
||||||
|
const auto x_min = work_rect.Min.x + waveform_x_center + x_offset_min;
|
||||||
|
const auto x_max = work_rect.Min.x + waveform_x_center + x_offset_max;
|
||||||
|
draw_list->AddLine({x_min, y}, {x_max, y}, ImColor(colors.waveform));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const Fraction first_visible_beat = std::max(Fraction{0}, first_beat_in_frame);
|
const auto seconds_before_cursor = seconds_to_pixels_proportional.backwards_transform(computed_sizes.cursor_y);
|
||||||
|
const auto seconds_after_cursor = seconds_to_pixels_proportional.backwards_transform(static_cast<float>(computed_sizes.y) - computed_sizes.cursor_y);
|
||||||
|
const sf::Time first_visible_second = current_time - sf::seconds(seconds_before_cursor);
|
||||||
|
const sf::Time last_visible_second = current_time + sf::seconds(seconds_after_cursor);
|
||||||
|
const AffineTransform<float> seconds_to_pixels_absolute{
|
||||||
|
first_visible_second.asSeconds(),
|
||||||
|
last_visible_second.asSeconds(),
|
||||||
|
0,
|
||||||
|
static_cast<float>(computed_sizes.y)
|
||||||
|
};
|
||||||
|
const auto beats_to_absolute_pixels = [&](const Fraction& beat){
|
||||||
|
return seconds_to_pixels_absolute.transform(args.timing.seconds_at(beat));
|
||||||
|
};
|
||||||
|
|
||||||
|
const Fraction first_visible_beat = std::max(Fraction{0}, timing.beats_at(first_visible_second));
|
||||||
auto next_beat = [](const auto& first_beat) -> Fraction {
|
auto next_beat = [](const auto& first_beat) -> Fraction {
|
||||||
if (first_beat % 1 == 0) {
|
if (first_beat % 1 == 0) {
|
||||||
return first_beat;
|
return first_beat;
|
||||||
@ -535,11 +352,9 @@ void LinearView::draw_in_waveform_mode(LinearView::draw_args_type& args) {
|
|||||||
}(first_visible_beat);
|
}(first_visible_beat);
|
||||||
|
|
||||||
// Draw the beat lines and numbers
|
// Draw the beat lines and numbers
|
||||||
for (
|
{
|
||||||
Fraction next_beat_line_y = beats_to_pixels_absolute.transform(next_beat);
|
Fraction next_beat_line_y = beats_to_absolute_pixels(next_beat);
|
||||||
next_beat_line_y < computed_sizes.y and next_beat < last_editable_beat;
|
while (next_beat_line_y < computed_sizes.y and next_beat < last_editable_beat) {
|
||||||
next_beat_line_y = beats_to_pixels_absolute.transform(next_beat += 1)
|
|
||||||
) {
|
|
||||||
const sf::Vector2f beat_line_start = {computed_sizes.timeline_left, static_cast<float>(static_cast<double>(next_beat_line_y))};
|
const sf::Vector2f beat_line_start = {computed_sizes.timeline_left, static_cast<float>(static_cast<double>(next_beat_line_y))};
|
||||||
const sf::Vector2f beat_line_end = beat_line_start + sf::Vector2f{computed_sizes.timeline_width, 0};
|
const sf::Vector2f beat_line_end = beat_line_start + sf::Vector2f{computed_sizes.timeline_width, 0};
|
||||||
if (next_beat % 4 == 0) {
|
if (next_beat % 4 == 0) {
|
||||||
@ -557,14 +372,17 @@ void LinearView::draw_in_waveform_mode(LinearView::draw_args_type& args) {
|
|||||||
} else {
|
} else {
|
||||||
draw_list->AddLine(beat_line_start + origin, beat_line_end + origin, ImColor(colors.beat_line));
|
draw_list->AddLine(beat_line_start + origin, beat_line_end + origin, ImColor(colors.beat_line));
|
||||||
}
|
}
|
||||||
|
next_beat += 1;
|
||||||
|
next_beat_line_y = beats_to_absolute_pixels(next_beat);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the bpm changes
|
// Draw the bpm changes
|
||||||
timing.for_each_event_between(
|
timing.for_each_event_between(
|
||||||
first_beat_in_frame,
|
first_visible_second,
|
||||||
last_beat_in_frame,
|
last_visible_second,
|
||||||
[&](const auto& event){
|
[&](const auto& event){
|
||||||
const auto bpm_change_y = beats_to_pixels_absolute.transform(event.get_beats());
|
const auto bpm_change_y = beats_to_absolute_pixels(event.get_beats());
|
||||||
if (bpm_change_y >= 0 and bpm_change_y <= computed_sizes.y) {
|
if (bpm_change_y >= 0 and bpm_change_y <= computed_sizes.y) {
|
||||||
const sf::Vector2f bpm_text_raw_pos = {computed_sizes.bpm_events_left, static_cast<float>(static_cast<double>(bpm_change_y))};
|
const sf::Vector2f bpm_text_raw_pos = {computed_sizes.bpm_events_left, static_cast<float>(static_cast<double>(bpm_change_y))};
|
||||||
const auto bpm_at_beat = better::BPMAtBeat{event.get_bpm(), event.get_beats()};
|
const auto bpm_at_beat = better::BPMAtBeat{event.get_bpm(), event.get_beats()};
|
||||||
@ -589,19 +407,97 @@ void LinearView::draw_in_waveform_mode(LinearView::draw_args_type& args) {
|
|||||||
}
|
}
|
||||||
const auto lane = *opt_lane;
|
const auto lane = *opt_lane;
|
||||||
const float note_x = computed_sizes.timeline_left + computed_sizes.note_width * (lane + 0.5f);
|
const float note_x = computed_sizes.timeline_left + computed_sizes.note_width * (lane + 0.5f);
|
||||||
const float note_y = static_cast<double>(beats_to_pixels_absolute.transform(tap_note.get_time()));
|
const float note_y = beats_to_absolute_pixels(tap_note.get_time());
|
||||||
const auto note_seconds = args.timing.time_at(tap_note.get_time());
|
const auto note_seconds = args.timing.time_at(tap_note.get_time());
|
||||||
const auto first_colliding_beat = args.timing.beats_at(note_seconds - collision_zone * 0.5f);
|
const auto collision_start_time = note_seconds - collision_zone * 0.5f;
|
||||||
const auto collision_zone_y = beats_to_pixels_absolute.transform(first_colliding_beat);
|
const auto collision_zone_y = seconds_to_pixels_absolute.transform(collision_start_time.asSeconds());
|
||||||
const auto last_colliding_beat = args.timing.beats_at(note_seconds + collision_zone * 0.5f);
|
const auto collision_end_time = note_seconds + collision_zone * 0.5f;
|
||||||
const auto collision_zone_height = beats_to_pixels_proportional.transform(last_colliding_beat - first_colliding_beat);
|
const auto collision_zone_height = seconds_to_pixels_proportional.transform(
|
||||||
|
(collision_end_time - collision_start_time).asSeconds()
|
||||||
|
);
|
||||||
|
draw_tap_note(
|
||||||
|
args,
|
||||||
|
computed_sizes,
|
||||||
|
tap_note,
|
||||||
|
{note_x, note_y},
|
||||||
|
collision_zone,
|
||||||
|
collision_zone_y,
|
||||||
|
collision_zone_height
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[&](const better::LongNote& long_note){
|
||||||
|
const auto opt_lane = button_to_lane(long_note.get_position());
|
||||||
|
if (not opt_lane) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto lane = *opt_lane;
|
||||||
|
const float note_x = computed_sizes.timeline_left + computed_sizes.note_width * (lane + 0.5f);
|
||||||
|
const float note_y = beats_to_absolute_pixels(long_note.get_time());
|
||||||
|
const auto note_start_seconds = args.timing.time_at(long_note.get_time());
|
||||||
|
const auto collision_start_time = note_start_seconds - collision_zone * 0.5f;
|
||||||
|
const auto collision_zone_y = seconds_to_pixels_absolute.transform(collision_start_time.asSeconds());
|
||||||
|
const auto note_end_seconds = args.timing.time_at(long_note.get_end());
|
||||||
|
const auto collision_end_time = note_end_seconds + collision_zone * 0.5f;
|
||||||
|
const auto collision_zone_height = seconds_to_pixels_proportional.transform(
|
||||||
|
(collision_end_time - collision_start_time).asSeconds()
|
||||||
|
);
|
||||||
|
const auto long_note_rect_height = seconds_to_pixels_proportional.transform(
|
||||||
|
(note_end_seconds - note_start_seconds).asSeconds()
|
||||||
|
);
|
||||||
|
draw_long_note(
|
||||||
|
args,
|
||||||
|
computed_sizes,
|
||||||
|
long_note,
|
||||||
|
{note_x, note_y},
|
||||||
|
long_note_rect_height,
|
||||||
|
collision_zone,
|
||||||
|
collision_zone_y,
|
||||||
|
collision_zone_height
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
draw_notes(
|
||||||
|
first_visible_second,
|
||||||
|
last_visible_second,
|
||||||
|
chart_state,
|
||||||
|
timing,
|
||||||
|
draw_note
|
||||||
|
);
|
||||||
|
draw_long_note_dummy(chart_state, snap, draw_note);
|
||||||
|
draw_cursor(draw_list, origin, computed_sizes);
|
||||||
|
draw_time_selection(draw_list, origin, chart_state, computed_sizes, beats_to_absolute_pixels);
|
||||||
|
handle_mouse_selection(
|
||||||
|
draw_list,
|
||||||
|
origin,
|
||||||
|
chart_state,
|
||||||
|
timing,
|
||||||
|
computed_sizes,
|
||||||
|
[&](const float pixels){
|
||||||
|
return args.timing.beats_at(
|
||||||
|
seconds_to_pixels_absolute.backwards_transform(pixels)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LinearView::draw_tap_note(
|
||||||
|
LinearView::DrawArgs& args,
|
||||||
|
const linear_view::ComputedSizes& computed_sizes,
|
||||||
|
const better::TapNote& tap_note,
|
||||||
|
const sf::Vector2f note_pos,
|
||||||
|
const sf::Time collision_zone,
|
||||||
|
const float collision_zone_y,
|
||||||
|
const float collision_zone_height
|
||||||
|
) {
|
||||||
const sf::Vector2f collision_zone_pos = {
|
const sf::Vector2f collision_zone_pos = {
|
||||||
note_x,
|
note_pos.x,
|
||||||
static_cast<float>(static_cast<double>(collision_zone_y))
|
collision_zone_y
|
||||||
};
|
};
|
||||||
const sf::Vector2f collizion_zone_size = {
|
const sf::Vector2f collizion_zone_size = {
|
||||||
computed_sizes.collizion_zone_width,
|
computed_sizes.collizion_zone_width,
|
||||||
static_cast<float>(static_cast<double>(collision_zone_height))
|
collision_zone_height
|
||||||
};
|
};
|
||||||
const auto collision_zone_color = [&](){
|
const auto collision_zone_color = [&](){
|
||||||
if (args.chart_state.chart.notes->is_colliding(tap_note, args.timing, collision_zone)) {
|
if (args.chart_state.chart.notes->is_colliding(tap_note, args.timing, collision_zone)) {
|
||||||
@ -626,7 +522,6 @@ void LinearView::draw_in_waveform_mode(LinearView::draw_args_type& args) {
|
|||||||
{0.5f, 0.f},
|
{0.5f, 0.f},
|
||||||
collision_zone_color
|
collision_zone_color
|
||||||
);
|
);
|
||||||
const sf::Vector2f note_pos = {note_x, note_y};
|
|
||||||
draw_rectangle(
|
draw_rectangle(
|
||||||
args.draw_list,
|
args.draw_list,
|
||||||
args.origin + note_pos,
|
args.origin + note_pos,
|
||||||
@ -644,23 +539,20 @@ void LinearView::draw_in_waveform_mode(LinearView::draw_args_type& args) {
|
|||||||
colors.selected_note_outline
|
colors.selected_note_outline
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
[&](const better::LongNote& long_note){
|
|
||||||
const auto opt_lane = button_to_lane(long_note.get_position());
|
|
||||||
if (not opt_lane) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
const auto lane = *opt_lane;
|
|
||||||
float note_x = computed_sizes.timeline_left + computed_sizes.note_width * (lane + 0.5f);
|
void LinearView::draw_long_note(
|
||||||
float note_y = static_cast<double>(beats_to_pixels_absolute.transform(long_note.get_time()));
|
LinearView::DrawArgs& args,
|
||||||
const auto note_start_seconds = args.timing.time_at(long_note.get_time());
|
const linear_view::ComputedSizes& computed_sizes,
|
||||||
const auto first_colliding_beat = args.timing.beats_at(note_start_seconds - collision_zone * 0.5f);
|
const better::LongNote& long_note,
|
||||||
const auto collision_zone_y = beats_to_pixels_absolute.transform(first_colliding_beat);
|
const sf::Vector2f note_pos,
|
||||||
const auto note_end_seconds = args.timing.time_at(long_note.get_end());
|
const float long_note_rect_height,
|
||||||
const auto last_colliding_beat = args.timing.beats_at(note_end_seconds + collision_zone * 0.5f);
|
const sf::Time collision_zone,
|
||||||
const auto collision_zone_height = beats_to_pixels_proportional.transform(last_colliding_beat - first_colliding_beat);
|
const float collision_zone_y,
|
||||||
|
const float collision_zone_height
|
||||||
|
) {
|
||||||
const sf::Vector2f collision_zone_pos = {
|
const sf::Vector2f collision_zone_pos = {
|
||||||
note_x,
|
note_pos.x,
|
||||||
static_cast<float>(static_cast<double>(collision_zone_y))
|
static_cast<float>(static_cast<double>(collision_zone_y))
|
||||||
};
|
};
|
||||||
const sf::Vector2f collision_zone_size = {
|
const sf::Vector2f collision_zone_size = {
|
||||||
@ -690,12 +582,10 @@ void LinearView::draw_in_waveform_mode(LinearView::draw_args_type& args) {
|
|||||||
{0.5f, 0.f},
|
{0.5f, 0.f},
|
||||||
collision_zone_color
|
collision_zone_color
|
||||||
);
|
);
|
||||||
const auto long_note_rect_height = beats_to_pixels_proportional.transform(long_note.get_duration());
|
|
||||||
const sf::Vector2f long_note_size = {
|
const sf::Vector2f long_note_size = {
|
||||||
computed_sizes.long_note_rect_width,
|
computed_sizes.long_note_rect_width,
|
||||||
static_cast<float>(static_cast<double>(long_note_rect_height))
|
long_note_rect_height
|
||||||
};
|
};
|
||||||
const sf::Vector2f note_pos = {note_x, note_y};
|
|
||||||
draw_rectangle(
|
draw_rectangle(
|
||||||
args.draw_list,
|
args.draw_list,
|
||||||
args.origin + note_pos,
|
args.origin + note_pos,
|
||||||
@ -720,31 +610,13 @@ void LinearView::draw_in_waveform_mode(LinearView::draw_args_type& args) {
|
|||||||
colors.selected_note_outline
|
colors.selected_note_outline
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto first_visible_second = timing.time_at(first_beat_in_frame);
|
|
||||||
const auto first_visible_collision_zone = timing.beats_at(first_visible_second - sf::milliseconds(500));
|
|
||||||
const auto last_visible_second = timing.time_at(last_beat_in_frame);
|
|
||||||
const auto last_visible_collision_zone = timing.beats_at(last_visible_second + sf::milliseconds(500));
|
|
||||||
chart_state.chart.notes->in(
|
|
||||||
first_visible_collision_zone,
|
|
||||||
last_visible_collision_zone,
|
|
||||||
[&](const better::Notes::iterator& it){
|
|
||||||
it->second.visit(draw_note);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (chart_state.long_note_being_created.has_value()) {
|
|
||||||
draw_note(
|
|
||||||
make_long_note_dummy_for_linear_view(
|
|
||||||
*chart_state.long_note_being_created,
|
|
||||||
snap
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the cursor
|
void LinearView::draw_cursor(
|
||||||
|
ImDrawList* draw_list,
|
||||||
|
const sf::Vector2f& origin,
|
||||||
|
const linear_view::ComputedSizes& computed_sizes
|
||||||
|
) {
|
||||||
draw_rectangle(
|
draw_rectangle(
|
||||||
draw_list,
|
draw_list,
|
||||||
origin + computed_sizes.cursor_pos,
|
origin + computed_sizes.cursor_pos,
|
||||||
@ -752,22 +624,29 @@ void LinearView::draw_in_waveform_mode(LinearView::draw_args_type& args) {
|
|||||||
{0, 0.5},
|
{0, 0.5},
|
||||||
colors.cursor
|
colors.cursor
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Draw the time selection
|
void LinearView::draw_time_selection(
|
||||||
|
ImDrawList* draw_list,
|
||||||
|
const sf::Vector2f& origin,
|
||||||
|
const ChartState& chart_state,
|
||||||
|
const linear_view::ComputedSizes& computed_sizes,
|
||||||
|
const std::function<float(const Fraction&)> beats_to_absolute_pixels
|
||||||
|
) {
|
||||||
const float selection_width = computed_sizes.timeline_width;
|
const float selection_width = computed_sizes.timeline_width;
|
||||||
if (chart_state.time_selection.has_value()) {
|
if (chart_state.time_selection.has_value()) {
|
||||||
const auto pixel_interval = Interval{
|
const auto pixel_interval = Interval{
|
||||||
beats_to_pixels_absolute.transform(chart_state.time_selection->start),
|
beats_to_absolute_pixels(chart_state.time_selection->start),
|
||||||
beats_to_pixels_absolute.transform(chart_state.time_selection->end)
|
beats_to_absolute_pixels(chart_state.time_selection->end)
|
||||||
};
|
};
|
||||||
if (pixel_interval.intersects({0, computed_sizes.y})) {
|
if (pixel_interval.intersects({0, static_cast<float>(computed_sizes.y)})) {
|
||||||
const sf::Vector2f selection_size = {
|
const sf::Vector2f selection_size = {
|
||||||
selection_width,
|
selection_width,
|
||||||
static_cast<float>(static_cast<double>(pixel_interval.width()))
|
static_cast<float>(pixel_interval.width())
|
||||||
};
|
};
|
||||||
const sf::Vector2f selection_pos = {
|
const sf::Vector2f selection_pos = {
|
||||||
computed_sizes.timeline_left,
|
computed_sizes.timeline_left,
|
||||||
static_cast<float>(static_cast<double>(pixel_interval.start))
|
static_cast<float>(pixel_interval.start)
|
||||||
};
|
};
|
||||||
draw_rectangle(
|
draw_rectangle(
|
||||||
draw_list,
|
draw_list,
|
||||||
@ -779,8 +658,16 @@ void LinearView::draw_in_waveform_mode(LinearView::draw_args_type& args) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LinearView::handle_mouse_selection(
|
||||||
|
ImDrawList* draw_list,
|
||||||
|
const sf::Vector2f& origin,
|
||||||
|
ChartState& chart_state,
|
||||||
|
const better::Timing& timing,
|
||||||
|
const linear_view::ComputedSizes& computed_sizes,
|
||||||
|
const std::function<Fraction(float)> absolute_pixels_to_beats
|
||||||
|
) {
|
||||||
const auto current_window = ImGui::GetCurrentWindow();
|
const auto current_window = ImGui::GetCurrentWindow();
|
||||||
|
|
||||||
// Don't start the selection rect if we start :
|
// Don't start the selection rect if we start :
|
||||||
@ -818,13 +705,13 @@ void LinearView::draw_in_waveform_mode(LinearView::draw_args_type& args) {
|
|||||||
ImRect bpm_zone = {origin.x + computed_sizes.bpm_events_left, -INFINITY, INFINITY, INFINITY};
|
ImRect bpm_zone = {origin.x + computed_sizes.bpm_events_left, -INFINITY, INFINITY, INFINITY};
|
||||||
bpm_zone.ClipWith(current_window->InnerRect);
|
bpm_zone.ClipWith(current_window->InnerRect);
|
||||||
if (full_selection.Overlaps(bpm_zone)) {
|
if (full_selection.Overlaps(bpm_zone)) {
|
||||||
const auto first_selected_beat = beats_to_pixels_absolute.backwards_transform(full_selection.Min.y - origin.y);
|
const auto first_selected_beat = absolute_pixels_to_beats(full_selection.Min.y - origin.y);
|
||||||
const auto last_selected_beat = beats_to_pixels_absolute.backwards_transform(full_selection.Max.y - origin.y);
|
const auto last_selected_beat = absolute_pixels_to_beats(full_selection.Max.y - origin.y);
|
||||||
timing.for_each_event_between(
|
timing.for_each_event_between(
|
||||||
first_selected_beat,
|
first_selected_beat,
|
||||||
last_selected_beat,
|
last_selected_beat,
|
||||||
[&](const auto& event){
|
[&](const auto& event){
|
||||||
args.chart_state.selected_stuff.bpm_events.insert(
|
chart_state.selected_stuff.bpm_events.insert(
|
||||||
{event.get_bpm(), event.get_beats()}
|
{event.get_bpm(), event.get_beats()}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -857,6 +744,7 @@ void LinearView::display_settings() {
|
|||||||
"Waveform",
|
"Waveform",
|
||||||
std::holds_alternative<linear_view::mode::Waveform>(mode)
|
std::holds_alternative<linear_view::mode::Waveform>(mode)
|
||||||
)) {
|
)) {
|
||||||
|
mode = linear_view::mode::Waveform{};
|
||||||
}
|
}
|
||||||
ImGui::EndCombo();
|
ImGui::EndCombo();
|
||||||
}
|
}
|
||||||
@ -920,6 +808,7 @@ void LinearView::display_settings() {
|
|||||||
feis::ColorEdit4("Measure Lines", colors.measure_line);
|
feis::ColorEdit4("Measure Lines", colors.measure_line);
|
||||||
feis::ColorEdit4("Measure Numbers", colors.measure_number);
|
feis::ColorEdit4("Measure Numbers", colors.measure_number);
|
||||||
feis::ColorEdit4("Beat Lines", colors.beat_line);
|
feis::ColorEdit4("Beat Lines", colors.beat_line);
|
||||||
|
feis::ColorEdit4("Waveform", colors.waveform);
|
||||||
if (ImGui::TreeNode("Selection Rectangle")) {
|
if (ImGui::TreeNode("Selection Rectangle")) {
|
||||||
feis::ColorEdit4("Fill##Selection Rectangle Colors", colors.selection_rect.fill);
|
feis::ColorEdit4("Fill##Selection Rectangle Colors", colors.selection_rect.fill);
|
||||||
feis::ColorEdit4("Border##Selection Rectangle Colors", colors.selection_rect.border);
|
feis::ColorEdit4("Border##Selection Rectangle Colors", colors.selection_rect.border);
|
||||||
|
@ -43,69 +43,6 @@ const sf::Color reference_note_grey = {134, 110, 116};
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
namespace linear_view {
|
|
||||||
namespace mode {
|
|
||||||
struct Beats {};
|
|
||||||
struct Waveform {};
|
|
||||||
}
|
|
||||||
using Mode = std::variant<mode::Beats, mode::Waveform>;
|
|
||||||
}
|
|
||||||
|
|
||||||
class LinearView {
|
|
||||||
public:
|
|
||||||
LinearView(std::filesystem::path assets, config::Config& config);
|
|
||||||
|
|
||||||
struct draw_args_type {
|
|
||||||
ImDrawList* draw_list;
|
|
||||||
ChartState& chart_state;
|
|
||||||
const waveform::Cache& waveform_cache;
|
|
||||||
const better::Timing& timing;
|
|
||||||
const Fraction& current_beat;
|
|
||||||
const Fraction& last_editable_beat;
|
|
||||||
const Fraction& snap;
|
|
||||||
const sf::Vector2f& size;
|
|
||||||
const sf::Vector2f& origin;
|
|
||||||
};
|
|
||||||
|
|
||||||
void draw(draw_args_type& args);
|
|
||||||
|
|
||||||
void set_zoom(int zoom);
|
|
||||||
void zoom_in() { set_zoom(zoom + 1); };
|
|
||||||
void zoom_out() { set_zoom(zoom - 1); };
|
|
||||||
float time_factor() { return std::pow(1.25, static_cast<double>(zoom)); };
|
|
||||||
|
|
||||||
bool shouldDisplaySettings;
|
|
||||||
|
|
||||||
void display_settings();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void draw_in_beats_mode(draw_args_type& args);
|
|
||||||
void draw_in_waveform_mode(draw_args_type& args);
|
|
||||||
linear_view::Mode mode;
|
|
||||||
std::string mode_name();
|
|
||||||
linear_view::Colors& colors;
|
|
||||||
linear_view::Sizes& sizes;
|
|
||||||
const sf::Time& collision_zone;
|
|
||||||
|
|
||||||
AffineTransform<Fraction> beats_to_pixels_proportional;
|
|
||||||
AffineTransform<Fraction> seconds_to_pixels_proportional;
|
|
||||||
|
|
||||||
void reload_transforms();
|
|
||||||
|
|
||||||
int& zoom;
|
|
||||||
|
|
||||||
bool& use_quantization_colors;
|
|
||||||
linear_view::QuantizationColors& quantization_colors;
|
|
||||||
|
|
||||||
SelectionRectangle selection_rectangle;
|
|
||||||
bool started_selection_inside_window = false;
|
|
||||||
bool any_bpm_button_hovered = false;
|
|
||||||
|
|
||||||
linear_view::LaneOrder& lane_order;
|
|
||||||
std::string lane_order_name() const;
|
|
||||||
std::optional<unsigned int> button_to_lane(const better::Position& button);
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace linear_view {
|
namespace linear_view {
|
||||||
struct ComputedSizes {
|
struct ComputedSizes {
|
||||||
int x;
|
int x;
|
||||||
@ -132,6 +69,136 @@ namespace linear_view {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class LinearView {
|
||||||
|
public:
|
||||||
|
LinearView(std::filesystem::path assets, config::Config& config);
|
||||||
|
|
||||||
|
struct DrawArgs {
|
||||||
|
ImDrawList* draw_list;
|
||||||
|
ChartState& chart_state;
|
||||||
|
const std::optional<waveform::Cache::const_reference_type> waveform;
|
||||||
|
const better::Timing& timing;
|
||||||
|
const Fraction& current_beat;
|
||||||
|
const Fraction& last_editable_beat;
|
||||||
|
const Fraction& snap;
|
||||||
|
const sf::Vector2f& size;
|
||||||
|
const sf::Vector2f& origin;
|
||||||
|
};
|
||||||
|
|
||||||
|
void draw(DrawArgs& args);
|
||||||
|
|
||||||
|
void set_zoom(int zoom);
|
||||||
|
void zoom_in() { set_zoom(zoom + 1); };
|
||||||
|
void zoom_out() { set_zoom(zoom - 1); };
|
||||||
|
float time_factor() { return std::pow(1.25, static_cast<double>(zoom)); };
|
||||||
|
|
||||||
|
bool shouldDisplaySettings;
|
||||||
|
|
||||||
|
void display_settings();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void draw_in_beats_mode(DrawArgs& args);
|
||||||
|
void draw_in_waveform_mode(DrawArgs& args);
|
||||||
|
void draw_tap_note(
|
||||||
|
LinearView::DrawArgs& args,
|
||||||
|
const linear_view::ComputedSizes& computed_sizes,
|
||||||
|
const better::TapNote& tap_note,
|
||||||
|
const sf::Vector2f note_pos,
|
||||||
|
const sf::Time collision_zone,
|
||||||
|
const float collision_zone_y,
|
||||||
|
const float collision_zone_height
|
||||||
|
);
|
||||||
|
void draw_long_note(
|
||||||
|
LinearView::DrawArgs& args,
|
||||||
|
const linear_view::ComputedSizes& computed_sizes,
|
||||||
|
const better::LongNote& long_note,
|
||||||
|
const sf::Vector2f note_pos,
|
||||||
|
const float long_note_rect_height,
|
||||||
|
const sf::Time collision_zone,
|
||||||
|
const float collision_zone_y,
|
||||||
|
const float collision_zone_height
|
||||||
|
);
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void draw_notes(
|
||||||
|
const sf::Time first_visible_second,
|
||||||
|
const sf::Time last_visible_second,
|
||||||
|
const ChartState& chart_state,
|
||||||
|
const better::Timing& timing,
|
||||||
|
const T& draw_one_note
|
||||||
|
) {
|
||||||
|
const auto first_visible_collision_zone = timing.beats_at(first_visible_second - collision_zone * 0.5f);
|
||||||
|
const auto last_visible_collision_zone = timing.beats_at(last_visible_second + collision_zone * 0.5f);
|
||||||
|
chart_state.chart.notes->in(
|
||||||
|
first_visible_collision_zone,
|
||||||
|
last_visible_collision_zone,
|
||||||
|
[&](const better::Notes::iterator& it){
|
||||||
|
it->second.visit(draw_one_note);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void draw_long_note_dummy(
|
||||||
|
const ChartState& chart_state,
|
||||||
|
const Fraction& snap,
|
||||||
|
const T& draw_one_note
|
||||||
|
) {
|
||||||
|
if (chart_state.long_note_being_created.has_value()) {
|
||||||
|
draw_one_note(
|
||||||
|
make_long_note_dummy_for_linear_view(
|
||||||
|
*chart_state.long_note_being_created,
|
||||||
|
snap
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void draw_cursor(
|
||||||
|
ImDrawList* draw_list,
|
||||||
|
const sf::Vector2f& origin,
|
||||||
|
const linear_view::ComputedSizes& computed_sizes
|
||||||
|
);
|
||||||
|
void draw_time_selection(
|
||||||
|
ImDrawList* draw_list,
|
||||||
|
const sf::Vector2f& origin,
|
||||||
|
const ChartState& chart_state,
|
||||||
|
const linear_view::ComputedSizes& computed_sizes,
|
||||||
|
const std::function<float(const Fraction&)> beats_to_absolute_pixels
|
||||||
|
);
|
||||||
|
void handle_mouse_selection(
|
||||||
|
ImDrawList* draw_list,
|
||||||
|
const sf::Vector2f& origin,
|
||||||
|
ChartState& chart_state,
|
||||||
|
const better::Timing& timing,
|
||||||
|
const linear_view::ComputedSizes& computed_sizes,
|
||||||
|
const std::function<Fraction(float)> absolute_pixels_to_beats
|
||||||
|
);
|
||||||
|
|
||||||
|
linear_view::Mode& mode;
|
||||||
|
std::string mode_name();
|
||||||
|
linear_view::Colors& colors;
|
||||||
|
linear_view::Sizes& sizes;
|
||||||
|
const sf::Time& collision_zone;
|
||||||
|
|
||||||
|
AffineTransform<Fraction> beats_to_pixels_proportional;
|
||||||
|
|
||||||
|
void reload_transforms();
|
||||||
|
|
||||||
|
int& zoom;
|
||||||
|
|
||||||
|
bool& use_quantization_colors;
|
||||||
|
linear_view::QuantizationColors& quantization_colors;
|
||||||
|
|
||||||
|
SelectionRectangle selection_rectangle;
|
||||||
|
bool started_selection_inside_window = false;
|
||||||
|
bool any_bpm_button_hovered = false;
|
||||||
|
|
||||||
|
linear_view::LaneOrder& lane_order;
|
||||||
|
std::string lane_order_name() const;
|
||||||
|
std::optional<unsigned int> button_to_lane(const better::Position& button);
|
||||||
|
};
|
||||||
|
|
||||||
void draw_rectangle(
|
void draw_rectangle(
|
||||||
ImDrawList* draw_list,
|
ImDrawList* draw_list,
|
||||||
const sf::Vector2f& pos,
|
const sf::Vector2f& pos,
|
||||||
|
@ -3,5 +3,4 @@ sources += files([
|
|||||||
'density_graph.cpp',
|
'density_graph.cpp',
|
||||||
'lane_order.cpp',
|
'lane_order.cpp',
|
||||||
'linear_view.cpp',
|
'linear_view.cpp',
|
||||||
'linear_view_waveform_mode.cpp',
|
|
||||||
])
|
])
|
Loading…
Reference in New Issue
Block a user