diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..7058e79 --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + Add: [-std=c++20] \ No newline at end of file diff --git a/.gitignore b/.gitignore index 45d8bea..dc6eb06 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.cache .idea .vscode build diff --git a/docs/Compiling.md b/docs/Compiling.md index 0712d72..b008891 100644 --- a/docs/Compiling.md +++ b/docs/Compiling.md @@ -9,13 +9,13 @@ In other words, how to create a new F.E.I.S. executable from the source code. $ cd F.E.I.S./ ``` -0. Setup the `build` directory +0. Setup a build directory called `build` ```console $ meson setup build ``` -0. Build +0. Compile in that directory ```console $ meson compile -C build diff --git a/include/interval_tree.hpp b/include/interval_tree.hpp new file mode 100644 index 0000000..cf63382 --- /dev/null +++ b/include/interval_tree.hpp @@ -0,0 +1,1259 @@ +/* +From my own for of : https://github.com/NicoG60/interval-tree +(https://github.com/Stepland/interval-tree) +*/ + +#ifndef INTERVAL_TREE_H +#define INTERVAL_TREE_H + +#include +#include +#include +#include +#include + +template< + typename Key, + typename T, + typename Compare = std::less, + typename std::enable_if::value, int>::type = 0 +> +class interval_tree +{ +public: + // ====== TYPEDEFS ========================================================= + typedef Key bound_type; + typedef std::pair key_type; + typedef T mapped_type; + + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + typedef std::pair value_type; + typedef value_type* pointer; + typedef const value_type* const_pointer; + typedef value_type& reference; + typedef const value_type& const_reference; + + + + // ====== NODE ============================================================= + class node + { + friend class interval_tree; + + node() = default; + template + node(Args&& ...args) : data(std::forward(args)...) {} + + inline const key_type& key() { return data.first; } + inline const bound_type& lower() { return key().first; } + inline const bound_type& upper() { return key().second; } + inline mapped_type& value() { return data.second; } + + node* parent = nullptr; + node* left = nullptr; + node* right = nullptr; + + int height = 1; + int bfactor = 0; + bound_type max = bound_type(); + value_type data; + }; + + // ====== KEY COMPARE ====================================================== + class comparator + { + friend class interval_tree; + friend class const_iterator; + + public: + inline bool operator()(const bound_type& lhs, const bound_type& rhs) const { return less(lhs, rhs); } + inline bool operator()(const key_type& lhs, const key_type& rhs) const { return less(lhs, rhs); } + inline bool operator()(const value_type& lhs, const value_type& rhs) const { return less(lhs, rhs); } + + inline bool less(const bound_type& lhs, const bound_type& rhs) const { return comp(lhs, rhs); } + inline bool less(const key_type& lhs, const key_type& rhs) const + { + return less(lhs.first, rhs.first) || (eq(rhs.first, lhs.first) && less(lhs.second, rhs.second)); + } + inline bool less(const value_type& lhs, const value_type& rhs) const { return less(lhs.first, rhs.first); } + + inline bool greater(const bound_type& lhs, const bound_type& rhs) const { return less(rhs, lhs); } + inline bool greater(const key_type& lhs, const key_type& rhs) const { return less(rhs, lhs); } + inline bool greater(const value_type& lhs, const value_type& rhs) const { return less(rhs, lhs); } + + inline bool less_eq(const bound_type& lhs, const bound_type& rhs) const { return !greater(lhs, rhs); } + inline bool less_eq(const key_type& lhs, const key_type& rhs) const { return !greater(lhs, rhs); } + inline bool less_eq(const value_type& lhs, const value_type& rhs) const { return !greater(lhs, rhs); } + + inline bool greater_eq(const bound_type& lhs, const bound_type& rhs) const { return !less(lhs, rhs); } + inline bool greater_eq(const key_type& lhs, const key_type& rhs) const { return !less(lhs, rhs); } + inline bool greater_eq(const value_type& lhs, const value_type& rhs) const { return !less(lhs, rhs); } + + inline bool eq(const bound_type& lhs, const bound_type& rhs) const { return !less(lhs, rhs) && !greater(lhs, rhs); } + inline bool eq(const key_type& lhs, const key_type& rhs) const { return !less(lhs, rhs) && !greater(lhs, rhs); } + inline bool eq(const value_type& lhs, const value_type& rhs) const { return !less(lhs, rhs) && !greater(lhs, rhs); } + + inline bool neq(const bound_type& lhs, const bound_type& rhs) const { return !eq(lhs, rhs); } + inline bool neq(const key_type& lhs, const key_type& rhs) const { return !eq(lhs, rhs); } + inline bool neq(const value_type& lhs, const value_type& rhs) const { return !eq(lhs, rhs); } + + protected: + comparator() = default; + comparator(Compare c) : comp(c) {} + + Compare comp; + }; + + typedef comparator key_compare; + typedef comparator value_compare; + + + + // ====== ITERATOR ========================================================= + class iterator + { + friend class interval_tree; + + public: + typedef interval_tree::difference_type difference_type; + typedef interval_tree::value_type value_type; + typedef interval_tree::pointer pointer; + typedef interval_tree::const_pointer const_pointer; + typedef interval_tree::reference reference; + typedef interval_tree::const_reference const_reference; + typedef std::bidirectional_iterator_tag iterator_category; + + protected: + iterator(const interval_tree* t, node* n = nullptr) : tree(t), n(n) {} + + public: + iterator() = default; + + inline void swap(iterator& other) noexcept + { + std::swap(tree, other.tree); + std::swap(n, other.n); + } + + inline bool operator==(const iterator& other) const { return n == other.n; } + inline bool operator!=(const iterator& other) const { return !(*this == other); } + + inline reference operator*() { return n->data; } + inline pointer operator->() { return &n->data; } + + inline const_reference operator*() const { return n->data; } + inline const_pointer operator->() const { return &n->data; } + + inline iterator& operator++() + { + if(n) + n = tree->next(n); + else + n = tree->leftest(n); + + return *this; + } + + inline iterator operator++(int) + { + iterator it(*this); + ++*this; + return it; + } + + inline iterator& operator--() + { + if(n) + n = tree->prev(n); + else + n = tree->rightest(n); + + return *this; + } + + inline iterator operator--(int) + { + iterator it(*this); + --*this; + return it; + } + + protected: + const interval_tree* tree = nullptr; + node* n = nullptr; + }; + + + + class const_iterator + { + friend class interval_tree; + + public: + typedef interval_tree::difference_type difference_type; + typedef interval_tree::value_type value_type; + typedef interval_tree::const_pointer pointer; + typedef interval_tree::const_pointer const_pointer; + typedef interval_tree::const_reference reference; + typedef interval_tree::const_reference const_reference; + typedef std::bidirectional_iterator_tag iterator_category; + + protected: + const_iterator(const interval_tree* t, node* n = nullptr) : tree(t), n(n) {} + + public: + const_iterator() = default; + const_iterator(const iterator& copy) : tree(copy.tree), n(copy.n) {} + const_iterator(iterator&& move) : tree(move.tree), n(move.n) {} + + inline void swap(const_iterator& other) noexcept + { + std::swap(tree, other.tree); + std::swap(n, other.n); + } + + inline bool operator==(const const_iterator& other) const { return n == other.n; } + inline bool operator!=(const const_iterator& other) const { return !(*this == other); } + + inline reference operator*() const { return n->data; } + inline pointer operator->() const { return &n->data; } + + inline const_iterator& operator++() + { + if(n) + n = tree->next(n); + else + n = tree->leftest(n); + + return *this; + } + + inline const_iterator operator++(int) + { + iterator it(*this); + ++*this; + return it; + } + + inline const_iterator& operator--() + { + if(n) + n = tree->prev(n); + else + n = tree->rightest(n); + + return *this; + } + + inline const_iterator operator--(int) + { + iterator it(*this); + --*this; + return it; + } + + protected: + const interval_tree* tree = nullptr; + node* n = nullptr; + }; + + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator reverse_const_iterator; + +public: + // ====== CONSTRUCTORS ===================================================== + interval_tree() = default; + explicit interval_tree(const Compare& comp) : comp(comp) {} + + template + interval_tree(InputIt first, InputIt last, const Compare& comp = Compare()) : + comp(comp) + { + insert(first, last); + } + + interval_tree(const interval_tree& copy) { *this = copy; } + interval_tree(interval_tree&& move) { *this = move; } + interval_tree(std::initializer_list ilist, const Compare& comp = Compare()) : + comp(comp) + { + insert(ilist); + } + + + + // ====== DESTRUCTOR ======================================================= + ~interval_tree() + { + clear(); + } + + + // ====== ASSIGNMENTS ====================================================== + interval_tree& operator=(const interval_tree& copy) + { + if(root) + delete_node(root); + + root = copy.root ? clone(copy.root) : nullptr; + node_count = copy.node_count; + comp = copy.comp; + + return *this; + } + + interval_tree& operator=(interval_tree&& move) noexcept(std::is_nothrow_move_assignable::value) + { + if(root) + delete_node(root); + + root = std::move(move.root); + node_count = std::move(move.node_count); + comp = std::move(move.comp); + + return *this; + } + + interval_tree& operator=(std::initializer_list ilist) + { + clear(); + insert(ilist); + + return *this; + } + + + + // ====== ITERATORS ======================================================== + inline iterator begin() noexcept + { + return iterator(this, root ? leftest(root) : nullptr); + } + + inline const_iterator begin() const noexcept + { + return const_iterator(this, root ? leftest(root) : nullptr); + } + + inline const_iterator cbegin() const noexcept + { + return const_iterator(this, root ? leftest(root) : nullptr); + } + + inline iterator end() noexcept + { + return iterator(this); + } + + inline const_iterator end() const noexcept + { + return const_iterator(this); + } + + inline const_iterator cend() const noexcept + { + return const_iterator(this); + } + + inline reverse_iterator rbegin() noexcept + { + return reverse_iterator(begin()); + } + + inline reverse_const_iterator rbegin() const noexcept + { + return reverse_const_iterator(begin()); + } + + inline reverse_const_iterator crbegin() const noexcept + { + return reverse_const_iterator(cbegin()); + } + + inline reverse_iterator rend() noexcept + { + return reverse_iterator(end()); + } + + inline reverse_const_iterator rend() const noexcept + { + return reverse_const_iterator(end()); + } + + inline reverse_const_iterator crend() const noexcept + { + return reverse_const_iterator(cend()); + } + + + + // ====== CAPACITY ========================================================= + bool empty() const noexcept + { + return node_count == 0; + } + + size_type size() const noexcept + { + return node_count; + } + + size_type max_size() const noexcept + { + return std::numeric_limits::max(); + } + + + + // ===== MODIFIERS ========================================================= + void clear() noexcept + { + if(root) + { + delete_node(root); + root = nullptr; + node_count = 0; + } + } + + iterator insert(const value_type& value) + { + return emplace(value); + } + + iterator insert(value_type&& value) + { + return emplace(std::forward(value)); + } + + template::value, int>::type = 0> + iterator insert(P&& value) + { + return emplace(std::forward(value)); + } + + iterator insert(const_iterator hint, const value_type& value) + { + return emplace_hint(hint, value); + } + + iterator insert(const_iterator hint, value_type&& value) + { + return emplace_hint(hint, std::forward(value)); + } + + template::value, int>::type = 0> + iterator insert(const_iterator hint, P&& value) + { + return emplace_hint(hint, std::forward

(value)); + } + + template + void insert(InputIt first, InputIt last) + { + while(first != last) + { + emplace(*first); + ++first; + } + } + + void insert(std::initializer_list ilist) + { + insert(ilist.begin(), ilist.end()); + } + + template + iterator emplace(Args&& ...args) + { + node* n = new node(std::forward(args)...); + + if(root) + insert(n); + else + { + node_count ++; + root = n; + } + + return iterator(this, n); + } + + template + iterator emplace_hint(const_iterator hint, Args&& ...args) + { + node* n = new node(std::forward(args)...); + + if(root) + insert(hint, n); + else + { + node_count ++; + root = n; + } + + return iterator(this, n); + } + + iterator erase(const_iterator pos) + { + if(pos.n) + return iterator(this, remove(pos.n)); + else + return iterator(this); + } + + iterator erase(iterator pos) + { + if(pos.n) + return iterator(this, remove(pos.n)); + else + return iterator(this); + } + + iterator erase(const_iterator first, const_iterator last) + { + node* n = first.n; + + while(first != last) + { + n = remove(first.n); + ++first; + } + + return iterator(this, n); + } + + size_type erase(const key_type& key) + { + if(!root) + return 0; + + node* n = find(root, key); + + if(!n) + return 0; + + size_type r = 0; + + do { + n = remove(n); + ++r; + } while(n && comp.eq(n->key(), key)); + + return r; + } + + void swap(interval_tree& other) noexcept(std::is_nothrow_swappable::value) + { + std::swap(root, other.root); + std::swap(node_count, other.node_count); + std::swap(comp, other.comp); + } + + + + // ====== LOOKUP =========================================================== + + size_type count(const key_type& key) const + { + return std::distance(lower_bound(key), upper_bound(key)); + } + + template + void at(const Key& point, CB callback) { in(point, point, callback); } + + template + void at(const Key& point, CB callback) const { in(point, point, callback); } + + std::vector at(const Key& point) + { + std::vector r; + at(point, [&](iterator it){ r.push_back(it); }); + return r; + } + + std::vector at(const Key& point) const + { + std::vector r; + at(point, [&](const_iterator it){ r.push_back(it); }); + return r; + } + + template + void in(const Key& start, const Key& end, CB callback) { in({start, end}, callback); } + + template + void in(const Key& start, const Key& end, CB callback) const { in({start, end}, callback); } + + template + void in(key_type interval, CB callback) + { + if(comp(interval.second, interval.first)) + throw std::range_error("Invalid interval"); + + if(root) + search(root, interval, [&](node* n){ callback(iterator(this, n)); }); + } + + template + void in(key_type interval, CB callback) const + { + if(comp(interval.second, interval.first)) + throw std::range_error("Invalid interval"); + + if(root) + search(root, interval, [&](node* n){ callback(const_iterator(this, n)); }); + } + + std::vector in(const Key& start, const Key& end) + { + std::vector r; + in(start, end, [&](iterator it){ r.push_back(it); }); + return r; + } + + std::vector in(const Key& start, const Key& end) const + { + std::vector r; + in(start, end, [&](const_iterator it){ r.push_back(it); }); + return r; + } + + std::vector in(key_type interval) + { + std::vector r; + in(interval, [&](iterator it){ r.push_back(it); }); + return r; + } + + std::vector in(key_type interval) const + { + std::vector r; + in(interval, [&](const_iterator it){ r.push_back(it); }); + return r; + } + + iterator find(const key_type& k) + { + return _find(k); + } + + const_iterator find(const key_type& k) const + { + return _find(k); + } + + std::pair equal_range(const key_type& key) + { + return _equal_range(key); + } + + std::pair equal_range(const key_type& key) const + { + return _equal_range(key); + } + + iterator lower_bound(const key_type& k) + { + return _lower_bound(k); + } + + const_iterator lower_bound(const key_type& k) const + { + return _lower_bound(k); + } + + iterator upper_bound(const key_type& k) + { + return _upper_bound(k); + } + + const_iterator upper_bound(const key_type& k) const + { + return _upper_bound(k); + } + + + + // ====== OBSERVER ========================================================= + key_compare key_comp() const + { + return comp; + } + + value_compare value_comp() const + { + return comp; + } + + + + // ====== PRIVATE ========================================================== +private: + node* find_root(node* n) const + { + if(!n) + return nullptr; + + while(n->parent) + n = n->parent; + + return n; + } + + node* clone(node* n, node* p = nullptr) const + { + node* nn = new node(n->data); + nn->parent = p; + nn->max = n->max; + nn->height = n->height; + nn->bfactor = n->bfactor; + + if(n->left) + nn->left = clone(n->left, nn); + + if(n->right) + nn->right = clone(n->right, nn); + + return nn; + } + + void update_props(node* n) + { + bound_type m = n->upper(); + int h = 1; + int b = 0; + + if(n->right) + { + m = std::max(m, n->right->max, comp.comp); + h = n->right->height + 1; + b = n->right->height; + } + + if(n->left) + { + m = std::max(m, n->left->max, comp.comp); + h = std::max(h, n->left->height + 1); + b -= n->left->height; + } + + + + if(comp.neq(n->max, m) || + n->height != h || + n->bfactor != b || + (!n->left && !n->right)) + { + n->max = m; + n->height = h; + n->bfactor = b; + + if(n->parent) + update_props(n->parent); + } + } + + void delete_node(node* n) + { + delete_child(n); + delete n; + } + + void delete_child(node* n) + { + if(n->left) + { + delete_node(n->left); + n->left = nullptr; + } + + if(n->right) + { + delete_node(n->right); + n->right = nullptr; + } + } + + inline bool is_left_child(node* n) const + { + return n == n->parent->left; + } + + inline node* leftest(node* n) const + { + while(n->left) + n = n->left; + + return n; + } + + inline node* rightest(node* n) const + { + while(n->right) + n = n->right; + + return n; + } + + inline node* next(node* n) const + { + if(n->right) + return leftest(n->right); + + while(n->parent && !is_left_child(n)) + n = n->parent; + + return n->parent; + } + + inline node* prev(node* n) const + { + if(n->left) + return rightest(n->left); + + while(n->parent && is_left_child(n)) + n = n->parent; + + return n->parent; + } + + node* rotate_right(node* n) + { + node* tmp = n->left; + + n->left = tmp->right; + if(n->left) + n->left->parent = n; + + + if(n->parent) + { + if(is_left_child(n)) + n->parent->left = tmp; + else + n->parent->right = tmp; + } + + tmp->parent = n->parent; + tmp->right = n; + n->parent = tmp; + + update_props(n); + + return tmp; + } + + node* rotate_left(node* n) + { + node* tmp = n->right; + + n->right = tmp->left; + if(n->right) + n->right->parent = n; + + + if(n->parent) + { + if(is_left_child(n)) + n->parent->left = tmp; + else + n->parent->right = tmp; + } + + tmp->parent = n->parent; + tmp->left = n; + n->parent = tmp; + + update_props(n); + + return tmp; + } + + node* lower_bound(node* n, const key_type& k) const + { + node* r = nullptr; + while(n) + { + if(!comp.less(n->key(), k)) + { + r = n; + n = n->left; + } + else + n = n->right; + } + + return r; + } + + node* upper_bound(node* n, const key_type& k) const + { + node* r = nullptr; + while(n) + { + if(comp.greater(n->key(), k)) + { + r = n; + n = n->left; + } + else + n = n->right; + } + + return r; + } + + node* find_leaf_low(const key_type& k) const + { + node* n = root; + node* r = n; + + while(n) + { + r = n; + if(comp(n->key(), k)) + n = n->right; + else + n = n->left; + } + + return r; + } + + node* find_leaf_high(const key_type& k) const + { + node* n = root; + node* r = n; + + while(n) + { + r = n; + if(comp(k, n->key())) + n = n->left; + else + n = n->right; + } + + return r; + } + + node* find_leaf(const_iterator h, const key_type& k) const + { + if(h == end() || !comp(h.n->key(), k)) + { + const_iterator prior = h; + if(prior == begin() || !comp(k, (--prior).n->key())) + { + if(!h.n->left) + return h.n; + else + return prior.n; + } + + return find_leaf_high(k); + } + + return find_leaf_low(k); + } + + template + void search(node* n, const key_type& interval, CB cb) const + { + if(n->left && comp.greater_eq(n->left->max, interval.first)) + search(n->left, interval, cb); + + // if the current node matches + if(interval_overlaps(interval, n->key())) + cb(n); + + if(n->right && comp.greater_eq(interval.second, n->lower())) + search(n->right, interval, cb); + } + + template + void apply(node* n, const CB& cb) + { + if(n->left) + apply(n->left, cb); + + cb(n); + + if(n->right) + apply(n->right, cb); + } + + void insert(node* n) + { + node* p = find_leaf_high(n->key()); + + insert(n, p); + } + + void insert(const_iterator hint, node* n) + { + node* p = find_leaf(hint, n->key()); + + insert(n, p); + } + + void insert(node* n, node* p) + { + if(comp(n->upper(), n->lower())) + throw std::range_error("Invalid interval"); + + n->parent = p; + + if(comp.less(n->key(), p->key())) + p->left = n; + else + p->right = n; + + update_props(n); + + rebalance(p); + + node_count++; + } + + node* remove(node* n) + { + node* r = next(n); + + if(n->left && n->right) + swap_nodes(n, r); + + node* v = n->left ? n->left : n->right; + + replace_in_parent(n, v); + + node_count--; + + v = v ? v : n->parent; + + if(v) + { + update_props(v); + rebalance(v); + } + else if(node_count == 0) + root = nullptr; + + delete n; + + return r; + } + + void replace_in_parent(node* n, node* v) + { + if(v) + v->parent = n->parent; + + if(n->parent) + { + if(is_left_child(n)) + n->parent->left = v; + else + n->parent->right = v; + } + } + + void swap_nodes(node* a, node* b) + { + std::swap(a->height , b->height); + std::swap(a->bfactor, b->bfactor); + + //================================ + + node* pa = a->parent; + node* la = a->left; + node* ra = a->right; + + node* pb = b->parent; + node* lb = b->left; + node* rb = b->right; + + a->parent = pb == a ? b : pb; + a->left = lb == a ? b : lb; + a->right = rb == a ? b : rb; + + b->parent = pa == b ? a : pa; + b->left = la == b ? a : la; + b->right = ra == b ? a : ra; + + //================================ + + if(a->parent) + { + if(a->parent->left == b) + a->parent->left = a; + else + a->parent->right = a; + } + + if(a->left) + a->left->parent = a; + + if(a->right) + a->right->parent = a; + + //================================ + + if(b->parent) + { + if(b->parent->left == a) + b->parent->left = b; + else + b->parent->right = b; + } + + if(b->left) + b->left->parent = b; + + if(b->right) + b->right->parent = b; + } + + void rebalance(node* n) + { + if(n->bfactor < -1) + { + if(n->left && n->left->bfactor > 0) + n->left = rotate_left(n->left); + + n = rotate_right(n); + } + else if(n->bfactor > 1) + { + if(n->right && n->right->bfactor < 0) + n->right = rotate_right(n->right); + + n = rotate_left(n); + } + + if(n->parent) + rebalance(n->parent); + else + root = n; + } + + bool interval_overlaps(const key_type& a, const key_type& b) const + { + return comp.less_eq(a.first, b.second) && comp.greater_eq(a.second, b.first); + } + + bool interval_encloses(const key_type& a, const key_type& b) const + { + return comp.less_eq(a.first, b.first) && comp.greater_eq(a.second, b.second); + } + + template + IT _find(const key_type& k) const + { + node* n = lower_bound(root, k); + + if(n && comp.eq(n->key(), k)) + return n; + + return nullptr; + } + + template + IT _lower_bound(const key_type& k) const + { + return IT(this, lower_bound(root, k)); + } + + template + IT _upper_bound(const key_type& k) const + { + return IT(this, upper_bound(root, k)); + } + + template + std::pair _equal_range(const key_type& k) const + { + return {_lower_bound(k), _upper_bound(k)}; + } + +private: + node* root = nullptr; + size_type node_count = 0; + comparator comp; +}; + +template +void swap(interval_tree& lhs, + interval_tree& rhs) noexcept(noexcept(lhs.swap(rhs))) +{ + lhs.swap(rhs); +} + +template +void swap(typename interval_tree::iterator& lhs, + typename interval_tree::iterator& rhs) noexcept(noexcept(lhs.swap(rhs))) +{ + lhs.swap(rhs); +} + +template +bool operator==(const interval_tree& lhs, + const interval_tree& rhs) +{ + if(lhs.size() != rhs.size()) + return false; + + auto lit = lhs.cbegin(); + auto el = lhs.cend(); + + auto rit = rhs.cbegin(); + auto er = rhs.cend(); + + while(lit != el && rit != er) + { + if(*lit != *rit) + return false; + } + + return true; +} + +template +bool operator!=(const interval_tree& lhs, + const interval_tree& rhs) +{ + return !(lhs == rhs); +} + +template +bool operator <(const interval_tree& lhs, + const interval_tree& rhs) +{ + return std::lexicographical_compare(lhs.cbegin(), lhs.cend(), + rhs.cbegin(), rhs.cend()); +} + +template +bool operator >(const interval_tree& lhs, + const interval_tree& rhs) +{ + return rhs < lhs; +} + +template +bool operator<=(const interval_tree& lhs, + const interval_tree& rhs) +{ + return !(lhs > rhs); +} + +template +bool operator>=(const interval_tree& lhs, + const interval_tree& rhs) +{ + return !(lhs < rhs); +} + +#endif // INTERVAL_TREE_H diff --git a/meson.build b/meson.build index 777cb40..d27d4e0 100644 --- a/meson.build +++ b/meson.build @@ -3,7 +3,7 @@ project( 'cpp', meson_version : '>=0.55.0', version : '1.1.0', - default_options : ['cpp_std=c++17'], + default_options : ['cpp_std=c++20'], ) sources = [] diff --git a/src/better_note.cpp b/src/better_note.cpp new file mode 100644 index 0000000..02fda05 --- /dev/null +++ b/src/better_note.cpp @@ -0,0 +1,107 @@ +#include "better_note.hpp" + +namespace better { + Position::Position(unsigned int index) : x(index % 4), y (index / 4) { + if (index > 15) { + std::stringstream ss; + ss << "Attempted to create Position from invalid index : " << index; + throw std::invalid_argument(ss.str()); + } + }; + + Position::Position(unsigned int x, unsigned int y) : x(x), y(y) { + if (x > 3 or y > 3) { + std::stringstream ss; + ss << "Attempted to create Position from invalid coordinates : "; + ss << this; + throw std::invalid_argument(ss.str()); + } + }; + + unsigned int Position::index() const { + return x + y*4; + }; + + unsigned int Position::get_x() const { + return x; + }; + + unsigned int Position::get_y() const { + return y; + }; + + std::ostream& operator<< (std::ostream& out, const Position& pos) { + out << "(x: " << pos.get_x() << ", y: " << pos.get_y() << ")"; + return out; + }; + + + TapNote::TapNote(Fraction time, Position position): time(time), position(position) {}; + + Fraction TapNote::get_time() const { + return time; + }; + + Position TapNote::get_position() const { + return position; + }; + + LongNote::LongNote(Fraction time, Position position, Fraction duration, Position tail_tip) + : + time(time), + position(position), + duration(duration), + tail_tip(tail_tip) + { + if (duration < 0) { + std::stringstream ss; + ss << "Attempted to create a LongNote with negative duration : "; + ss << duration; + throw std::invalid_argument(ss.str()); + } + if (tail_tip.get_x() != position.get_x() and tail_tip.get_y() != position.get_y()) { + std::stringstream ss; + ss << "Attempted to create a LongNote with invalid tail tip : "; + ss << "position: " << position << " , tail_tip: " << tail_tip; + throw std::invalid_argument(ss.str()); + } + }; + + Fraction LongNote::get_time() const { + return time; + }; + + Position LongNote::get_position() const { + return position; + }; + + Fraction LongNote::get_end() const { + return time + duration; + }; + + Fraction LongNote::get_duration() const { + return duration; + }; + + Position LongNote::get_tail_tip() const { + return tail_tip; + }; + + auto _time_bounds = VariantVisitor { + [](const TapNote& t) -> std::pair { return {t.get_time(), t.get_time()}; }, + [](const LongNote& l) -> std::pair { return {l.get_time(), l.get_end()}; }, + }; + + std::pair Note::get_time_bounds() const { + return std::visit(better::_time_bounds, this->note); + }; + + Position Note::get_position() const { + return std::visit([](const auto& n){return n.get_position();}, this->note); + }; + + Fraction Note::get_end() const { + return this->get_time_bounds().second; + } +} + diff --git a/src/better_note.hpp b/src/better_note.hpp new file mode 100644 index 0000000..776f025 --- /dev/null +++ b/src/better_note.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "special_numeric_types.hpp" +#include "variant_visitor.hpp" + +namespace better { + /* + A specific square on the controller. (0, 0) is the top-left button, x + goes right, y goes down. + x → + 0 1 2 3 + y 0 □ □ □ □ + ↓ 1 □ □ □ □ + 2 □ □ □ □ + 3 □ □ □ □ + */ + class Position { + public: + explicit Position(unsigned int index); + Position(unsigned int x, unsigned int y); + + unsigned int index() const; + unsigned int get_x() const; + unsigned int get_y() const; + + auto operator<=>(const Position&) const = default; + + private: + unsigned int x; + unsigned int y; + }; + + std::ostream& operator<<(std::ostream& out, const Position& pos); + + class TapNote { + public: + TapNote(Fraction time, Position position); + Fraction get_time() const; + Position get_position() const; + private: + Fraction time; + Position position; + }; + + class LongNote { + public: + LongNote(Fraction time, Position position, Fraction duration, Position tail_tip); + + Fraction get_time() const; + Position get_position() const; + Fraction get_end() const; + Fraction get_duration() const; + Position get_tail_tip() const; + private: + Fraction time; + Position position; + Fraction duration; + Position tail_tip; + }; + + class Note { + public: + template + Note(Ts&&... Args) : note(std::forward(Args...)) {}; + std::pair get_time_bounds() const; + Position get_position() const; + Fraction get_end() const; + private: + std::variant note; + }; +} + diff --git a/src/better_notes.cpp b/src/better_notes.cpp new file mode 100644 index 0000000..109616b --- /dev/null +++ b/src/better_notes.cpp @@ -0,0 +1,23 @@ +#include "better_notes.hpp" + +namespace better { + std::pair Notes::insert(const Note& note) { + std::optional conflicting_note; + in( + note.get_time_bounds(), + [&](iterator it){ + if (it->second.get_position() == note.get_position()) { + if (not conflicting_note.has_value()) { + conflicting_note = it; + } + } + } + ); + if (conflicting_note.has_value()) { + return {*conflicting_note, false}; + } else { + auto it = interval_tree::insert({note.get_time_bounds(), note}); + return {it, true}; + } + }; +} \ No newline at end of file diff --git a/src/better_notes.hpp b/src/better_notes.hpp new file mode 100644 index 0000000..8b9c153 --- /dev/null +++ b/src/better_notes.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include +#include + +#include "interval_tree.hpp" + +#include "better_note.hpp" +#include "special_numeric_types.hpp" + +namespace better { + class Notes: public interval_tree { + public: + std::pair insert(const Note& note); + }; +} \ No newline at end of file diff --git a/src/better_song.cpp b/src/better_song.cpp new file mode 100644 index 0000000..10746f5 --- /dev/null +++ b/src/better_song.cpp @@ -0,0 +1,30 @@ +#include "better_song.hpp" + +namespace better { + std::optional Chart::time_of_last_event() const { + if (notes.empty()) { + return {}; + } else { + return timing.time_at(notes.crbegin()->second.get_end()); + } + }; + + PreviewLoop::PreviewLoop(Decimal start, Decimal duration) : + start(start), + duration(duration) + { + if (start < 0) { + std::stringstream ss; + ss << "Attempted to create a PreviewLoop with negative start "; + ss << "position : " << start; + throw std::invalid_argument(ss.str()); + } + + if (duration < 0) { + std::stringstream ss; + ss << "Attempted to create a PreviewLoop with negative "; + ss << "duration : " << duration; + throw std::invalid_argument(ss.str()); + } + }; +} \ No newline at end of file diff --git a/src/better_song.hpp b/src/better_song.hpp new file mode 100644 index 0000000..6b204d5 --- /dev/null +++ b/src/better_song.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "better_notes.hpp" +#include "better_timing.hpp" +#include "special_numeric_types.hpp" + +namespace better { + struct Chart { + std::optional level; + Timing timing; + std::optional> hakus; + Notes notes; + + std::optional time_of_last_event() const; + }; + + class PreviewLoop { + public: + PreviewLoop(Decimal start, Decimal duration); + private: + Decimal start; + Decimal duration; + }; + + struct Metadata { + std::optional title; + std::optional artist; + std::optional audio; + std::optional jacket; + std::optional> preview; + }; + + const auto difficulty_name_comp_key = [](const std::string& s) { + if (s == "BSC") { + return std::make_tuple(1, std::string{}); + } else if (s == "ADV") { + return std::make_tuple(2, std::string{}); + } else if (s == "EXT") { + return std::make_tuple(3, std::string{}); + } else { + return std::make_tuple(4, s); + } + }; + + const auto order_by_difficulty_name = [](const std::string& a, const std::string& b) { + return difficulty_name_comp_key(a) < difficulty_name_comp_key(b); + }; + + struct Song { + std::map< + std::string, + better::Chart, + decltype(order_by_difficulty_name) + > charts{order_by_difficulty_name}; + Metadata metadata; + }; +} \ No newline at end of file diff --git a/src/better_timing.cpp b/src/better_timing.cpp new file mode 100644 index 0000000..9301979 --- /dev/null +++ b/src/better_timing.cpp @@ -0,0 +1,135 @@ +#include "better_timing.hpp" + +namespace better { + BPMAtBeat::BPMAtBeat(Fraction beats, Fraction bpm) : beats(beats), bpm(bpm) { + if (bpm <= 0) { + std::stringstream ss; + ss << "Attempted to create a BPMAtBeat with negative BPM : "; + ss << bpm; + throw std::invalid_argument(ss.str()); + } + }; + + BPMEvent::BPMEvent(Fraction beats, Fraction seconds, Fraction bpm) : + BPMAtBeat(beats, bpm), + seconds(seconds) + {}; + + Fraction BPMEvent::get_seconds() const { + return seconds; + }; + + /* + Create a Time Map from a list of BPM changes with times given in + beats, the offset parameter is more flexible than a "regular" beat zero + offset as it accepts non-zero beats + */ + Timing::Timing(const std::vector& events, const SecondsAtBeat& offset) { + if (events.empty()) { + throw std::invalid_argument( + "Attempted to create a Timing object with no BPM events" + ); + } + + std::multiset< + BPMAtBeat, + decltype(order_by_beats) + > grouped_by_beats{ + events.begin(), events.end(), order_by_beats + }; + + std::set sorted_events{ + events.begin(), events.end(), order_by_beats + }; + + for (const auto& bpm_at_beat : sorted_events) { + auto [begin, end] = grouped_by_beats.equal_range(bpm_at_beat); + if (std::distance(begin, end) > 1) { + std::stringstream ss; + ss << "Attempted to create a Timing object with multiple "; + ss << "BPMs defined at beat " << bpm_at_beat.get_beats(); + ss << " :"; + std::for_each(begin, end, [&ss](auto b){ + ss << " (bpm: " << b.get_bpm() << ", beat: "; + ss << b.get_beats() << "),"; + }); + throw std::invalid_argument(ss.str()); + } + } + + // First compute everything as if the first BPM change happened at + // zero seconds, then shift according to the offset + auto first_event = sorted_events.begin(); + Fraction current_second = 0; + std::vector bpm_changes; + bpm_changes.reserve(sorted_events.size()); + bpm_changes.emplace_back( + first_event->get_beats(), + current_second, + first_event->get_bpm() + ); + + auto previous = first_event; + auto current = std::next(first_event); + for (; current != sorted_events.end(); ++previous, ++current) { + auto beats_since_last_event = + current->get_beats() - previous->get_beats(); + auto seconds_since_last_event = + (60 * beats_since_last_event) / previous->get_bpm(); + current_second += seconds_since_last_event; + bpm_changes.emplace_back( + current->get_beats(), + current_second, + current->get_bpm() + ); + } + this->events_by_beats + .insert(bpm_changes.begin(), bpm_changes.end()); + this->events_by_seconds + .insert(bpm_changes.begin(), bpm_changes.end()); + auto unshifted_seconds_at_offset = + this->fractional_seconds_at(offset.beats); + auto shift = offset.seconds - unshifted_seconds_at_offset; + std::vector shifted_bpm_changes; + std::transform( + bpm_changes.begin(), + bpm_changes.end(), + std::back_inserter(shifted_bpm_changes), + [shift](const BPMEvent& b){ + return BPMEvent( + b.get_beats(), + b.get_seconds() + shift, + b.get_bpm() + ); + } + ); + this->events_by_beats + .insert(shifted_bpm_changes.begin(), shifted_bpm_changes.end()); + this->events_by_seconds + .insert(shifted_bpm_changes.begin(), shifted_bpm_changes.end()); + } + + /* + Return the number of seconds at the given beat. + + Before the first bpm change, compute backwards from the first bpm, + after the first bpm change, compute forwards from the previous bpm + change + */ + Fraction Timing::fractional_seconds_at(Fraction beats) const { + auto bpm_change = this->events_by_beats + .upper_bound(BPMEvent(beats, 0, 0)); + if (bpm_change != this->events_by_beats.begin()) { + bpm_change = std::prev(bpm_change); + } + auto beats_since_previous_event = beats - bpm_change->get_beats(); + auto seconds_since_previous_event = + (60 * beats_since_previous_event) / bpm_change->get_bpm(); + return bpm_change->get_seconds() + seconds_since_previous_event; + }; + + sf::Time Timing::time_at(Fraction beats) const { + auto microseconds = fractional_seconds_at(beats) * 1000000; + return sf::microseconds(microseconds.convert_to()); + } +} \ No newline at end of file diff --git a/src/better_timing.hpp b/src/better_timing.hpp new file mode 100644 index 0000000..1097ff8 --- /dev/null +++ b/src/better_timing.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#include "special_numeric_types.hpp" + +namespace better { + struct SecondsAtBeat { + Fraction seconds; + Fraction beats; + }; + + class BPMAtBeat { + public: + BPMAtBeat(Fraction beats, Fraction bpm); + Fraction get_beats() const; + Fraction get_bpm() const; + private: + Fraction beats; + Fraction bpm; + }; + + class BPMEvent : public BPMAtBeat { + public: + BPMEvent(Fraction beats, Fraction seconds, Fraction bpm); + Fraction get_seconds() const; + private: + Fraction seconds; + }; + + const auto order_by_beats = [](const BPMAtBeat& a, const BPMAtBeat& b) { + return a.get_beats() < b.get_beats(); + }; + + const auto order_by_seconds = [](const BPMEvent& a, const BPMEvent& b) { + return a.get_seconds() < b.get_seconds(); + }; + + class Timing { + public: + Timing(const std::vector& events, const SecondsAtBeat& offset); + + Fraction fractional_seconds_at(Fraction beats) const; + sf::Time time_at(Fraction beats) const; + + private: + std::set events_by_beats{order_by_beats}; + std::set events_by_seconds{order_by_seconds}; + }; +} \ No newline at end of file diff --git a/src/chart_state.cpp b/src/chart_state.cpp new file mode 100644 index 0000000..a8e251a --- /dev/null +++ b/src/chart_state.cpp @@ -0,0 +1,30 @@ +#include "chart_state.hpp" + +ChartState::ChartState(better::Chart& c, std::filesystem::path assets) : + chart(c), + density_graph(assets) +{ + history.push(std::make_shared(c)); +} + +std::optional ChartState::makeLongNoteDummy(int current_tick) const { + if (creating_long_note and long_note_being_created) { + Note long_note = Note(long_note_being_created->first, long_note_being_created->second); + Note dummy_long_note = Note( + long_note.getPos(), + current_tick, + chart.getResolution(), + long_note.getTail_pos()); + return dummy_long_note; + } else { + return {}; + } +} + +std::optional ChartState::makeCurrentLongNote() const { + if (creating_long_note and long_note_being_created) { + return Note(long_note_being_created->first, long_note_being_created->second); + } else { + return {}; + } +} diff --git a/src/chart_with_history.hpp b/src/chart_state.hpp similarity index 50% rename from src/chart_with_history.hpp rename to src/chart_state.hpp index f8c784f..94a1abf 100644 --- a/src/chart_with_history.hpp +++ b/src/chart_state.hpp @@ -1,6 +1,6 @@ #pragma once -#include "chart.hpp" +#include "better_song.hpp" #include "history.hpp" #include "history_actions.hpp" #include "notes_clipboard.hpp" @@ -9,16 +9,16 @@ #include -struct Chart_with_History { - explicit Chart_with_History(Chart& c, std::filesystem::path assets); - Chart& ref; - std::set selectedNotes; - NotesClipboard notesClipboard; - SelectionState timeSelection; - std::optional> longNoteBeingCreated; - bool creatingLongNote; +struct ChartState { + explicit ChartState(better::Chart& c, std::filesystem::path assets); + better::Chart& chart; + std::set selected_notes; + NotesClipboard notes_clipboard; + SelectionState time_selection; + std::optional> long_note_being_created; + bool creating_long_note; History> history; - DensityGraph densityGraph; + DensityGraph density_graph; std::optional makeLongNoteDummy(int current_tick) const; std::optional makeCurrentLongNote() const; diff --git a/src/chart_with_history.cpp b/src/chart_with_history.cpp deleted file mode 100644 index bce4779..0000000 --- a/src/chart_with_history.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "chart_with_history.hpp" - -Chart_with_History::Chart_with_History(Chart& c, std::filesystem::path assets) : - ref(c), - densityGraph(assets) -{ - history.push(std::make_shared(c)); -} - -std::optional Chart_with_History::makeLongNoteDummy(int current_tick) const { - if (creatingLongNote and longNoteBeingCreated) { - Note long_note = Note(longNoteBeingCreated->first, longNoteBeingCreated->second); - Note dummy_long_note = Note( - long_note.getPos(), - current_tick, - ref.getResolution(), - long_note.getTail_pos()); - return dummy_long_note; - } else { - return {}; - } -} - -std::optional Chart_with_History::makeCurrentLongNote() const { - if (creatingLongNote and longNoteBeingCreated) { - return Note(longNoteBeingCreated->first, longNoteBeingCreated->second); - } else { - return {}; - } -} diff --git a/src/editor_state.cpp b/src/editor_state.cpp index 92347c0..3ef5c9e 100644 --- a/src/editor_state.cpp +++ b/src/editor_state.cpp @@ -9,102 +9,87 @@ #include #include -EditorState::EditorState(Fumen& fumen, std::filesystem::path assets) : - fumen(fumen), - playfield(assets), - linearView(assets) -{ - reloadFromFumen(assets); -} - -void EditorState::reloadFromFumen(std::filesystem::path assets) { - if (not this->fumen.Charts.empty()) { - this->chart.emplace(this->fumen.Charts.begin()->second, assets); - } else { - this->chart.reset(); - } - reloadMusic(); - reloadAlbumCover(); -} - /* - * Reloads music from what's indicated in the "music path" field of the fumen + * Reloads music from what's indicated in the "music path" field of the song * Resets the music state in case anything fails - * Updates playbackPosition and previewEnd as well + * Updates playbackPosition and preview_end as well */ -void EditorState::reloadMusic() { - const auto music_path = std::filesystem::path(fumen.path).parent_path() / fumen.musicPath; +void EditorState::reload_music() { + const auto absolute_music_path = song_path.parent_path() / edited_music_path; try { - music.emplace(music_path); + music_state.emplace(absolute_music_path); } catch (const std::exception& e) { - music.reset(); + music_state.reset(); } - reloadPreviewEnd(); + reload_preview_end(); - auto seconds_position = std::clamp(playbackPosition.asSeconds(), -(fumen.offset), previewEnd.asSeconds()); - playbackPosition = sf::seconds(seconds_position); - previousPos = playbackPosition; + auto preview_start = sf::Time::Zero; + if (chart_state) { + preview_start = std::min(preview_start, chart_state->chart.timing.time_at(0)); + } + playback_position = std::clamp(playback_position, preview_start, preview_end); + previous_pos = playback_position; } -void EditorState::reloadPreviewEnd() { - auto old_previewEnd = previewEnd; - float music_duration = 0; - if (music) { - music_duration = music->getDuration().asSeconds(); +void EditorState::reload_preview_end() { + auto old_preview_end = this->preview_end; + sf::Time music_duration = sf::Time::Zero; + if (music_state) { + music_duration = music_state->getDuration(); } - float chart_runtime = 0; + float chart_end = 0; if (chart) { - chart_runtime = fumen.getChartRuntime(chart->ref); + chart_end = chart->ref + .time_of_last_event() + .value_or(sf::Time::Zero) + .asSeconds(); } - // Chart end in seconds using the music file "coordinate system" - // (beat 0 is at -offset seconds) - float chart_end = std::max(chart_runtime - fumen.offset, 0.f); - float preview_end_seconds = std::max(music_duration, chart_end) ; + float preview_end_seconds = std::max(music_duration, chart_end); // Add some extra time at the end to allow for more notes to be placed // after the end of the chart // TODO: is this really the way to do it ? preview_end_seconds += 2.f; - previewEnd = sf::seconds(preview_end_seconds); - if (old_previewEnd != previewEnd and chart) { - chart->densityGraph.should_recompute = true; + this->preview_end = sf::seconds(preview_end_seconds); + if (old_preview_end != this->preview_end and this->chart.has_value()) { + chart->density_graph.should_recompute = true; } } /* * Reloads the album cover from what's indicated in the "album cover path" field - * of the fumen Resets the album cover state if anything fails + * of the song Resets the album cover state if anything fails */ -void EditorState::reloadAlbumCover() { - albumCover.emplace(); +void EditorState::reload_album_cover() { + album_cover.emplace(); std::filesystem::path album_cover_path = - std::filesystem::path(fumen.path).parent_path() / fumen.albumCoverPath; + std::filesystem::path(song.path).parent_path() / song.albumCoverPath; - if (fumen.albumCoverPath.empty() or not std::filesystem::exists(album_cover_path) - or not albumCover->loadFromFile(album_cover_path.string())) { - albumCover.reset(); + if (song.albumCoverPath.empty() or not std::filesystem::exists(album_cover_path) + or not album_cover->loadFromFile(album_cover_path.string())) { + album_cover.reset(); } } -void EditorState::setPlaybackAndMusicPosition(sf::Time newPosition) { - reloadPreviewEnd(); +void EditorState::set_playback_and_music_position(sf::Time newPosition) { + reload_preview_end(); newPosition = sf::seconds( std::clamp( newPosition.asSeconds(), - -fumen.offset, - previewEnd.asSeconds() + -song.offset, + this->preview_end.asSeconds() ) ); - previousPos = sf::seconds(newPosition.asSeconds() - 1.f / 60.f); - playbackPosition = newPosition; + previous_pos = sf::seconds(newPosition.asSeconds() - 1.f / 60.f); + playback_position = newPosition; if (music) { - if (playbackPosition.asSeconds() >= 0 and playbackPosition < music->getDuration()) { - music->setPlayingOffset(playbackPosition); + if (playback_position.asSeconds() >= 0 and playback_position < music->getDuration()) { + music->setPlayingOffset(playback_position); } else { music->stop(); } @@ -119,10 +104,10 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin Toolbox::CustomConstraints::ContentSquare); if (ImGui::Begin("Playfield", &showPlayfield, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) { - if (not ImGui::IsWindowHovered() and chart and chart->creatingLongNote) { + if (not ImGui::IsWindowHovered() and chart and chart->creating_long_note) { // cancel long note creation if the mouse is or goes out of the playfield - chart->longNoteBeingCreated.reset(); - chart->creatingLongNote = false; + chart->long_note_being_created.reset(); + chart->creating_long_note = false; } float squareSize = ImGui::GetWindowSize().x / 4.f; float TitlebarHeight = ImGui::GetWindowSize().y - ImGui::GetWindowSize().x; @@ -136,15 +121,15 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin if (longNoteDummy) { playfield.drawLongNote( *longNoteDummy, - playbackPosition, + playback_position, getCurrentTick(), - fumen.BPM, + song.BPM, getResolution()); } for (auto const& note : visibleNotes) { float note_offset = - (playbackPosition.asSeconds() - getSecondsAt(note.getTiming())); + (playback_position.asSeconds() - getSecondsAt(note.getTiming())); // auto frame = static_cast(std::floor(note_offset * 30.f)); int x = note.getPos() % 4; @@ -166,9 +151,9 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin } else { playfield.drawLongNote( note, - playbackPosition, + playback_position, getCurrentTick(), - fumen.BPM, + song.BPM, getResolution(), marker, markerEndingState); @@ -196,15 +181,15 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin if (ImGui::ImageButton(playfield.button, {squareSize, squareSize}, 0)) { toggleNoteAtCurrentTime(x + 4 * y); } - if (ImGui::IsItemHovered() and chart and chart->creatingLongNote) { + if (ImGui::IsItemHovered() and chart and chart->creating_long_note) { // Deal with long note creation stuff - if (not chart->longNoteBeingCreated) { + if (not chart->long_note_being_created) { Note current_note = Note(x + 4 * y, static_cast(roundf(getCurrentTick()))); - chart->longNoteBeingCreated = + chart->long_note_being_created = std::make_pair(current_note, current_note); } else { - chart->longNoteBeingCreated->second = + chart->long_note_being_created->second = Note(x + 4 * y, static_cast(roundf(getCurrentTick()))); } } @@ -216,7 +201,7 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin if (chart) { // Check for collisions then display them auto ticks_threshold = - static_cast((1.f / 60.f) * fumen.BPM * getResolution()); + static_cast((1.f / 60.f) * song.BPM * get_resolution()); std::array collisions = {}; @@ -239,7 +224,7 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin // Display selected notes for (auto const& note : visibleNotes) { - if (chart->selectedNotes.find(note) != chart->selectedNotes.end()) { + if (chart->selected_notes.find(note) != chart->selected_notes.end()) { int x = note.getPos() % 4; int y = note.getPos() / 4; ImGui::SetCursorPos({x * squareSize, TitlebarHeight + y * squareSize}); @@ -263,36 +248,36 @@ void EditorState::displayProperties() { { ImGui::Columns(2, nullptr, false); - if (albumCover) { - ImGui::Image(*albumCover, sf::Vector2f(200, 200)); + if (album_cover) { + ImGui::Image(*album_cover, sf::Vector2f(200, 200)); } else { ImGui::BeginChild("Album Cover", ImVec2(200, 200), true); ImGui::EndChild(); } ImGui::NextColumn(); - ImGui::InputText("Title", &fumen.songTitle); - ImGui::InputText("Artist", &fumen.artist); + ImGui::InputText("Title", &song.songTitle); + ImGui::InputText("Artist", &song.artist); if (Toolbox::InputTextColored( music.has_value(), "Invalid Music Path", "Music", - &fumen.musicPath)) { - reloadMusic(); + &edited_music_path)) { + reload_music(); } if (Toolbox::InputTextColored( - albumCover.has_value(), + album_cover.has_value(), "Invalid Album Cover Path", "Album Cover", - &fumen.albumCoverPath)) { - reloadAlbumCover(); + &song.albumCoverPath)) { + reload_album_cover(); } - if (ImGui::InputFloat("BPM", &fumen.BPM, 1.0f, 10.0f)) { - if (fumen.BPM <= 0.0f) { - fumen.BPM = 0.0f; + if (ImGui::InputFloat("BPM", &song.BPM, 1.0f, 10.0f)) { + if (song.BPM <= 0.0f) { + song.BPM = 0.0f; } } - ImGui::InputFloat("offset", &fumen.offset, 0.01f, 1.f); + ImGui::InputFloat("offset", &song.offset, 0.01f, 1.f); } ImGui::End(); } @@ -305,11 +290,11 @@ void EditorState::displayStatus() { ImGui::Begin("Status", &showStatus, ImGuiWindowFlags_AlwaysAutoResize); { if (not music) { - if (not fumen.musicPath.empty()) { + if (not song.musicPath.empty()) { ImGui::TextColored( ImVec4(1, 0.42, 0.41, 1), "Invalid music path : %s", - fumen.musicPath.c_str()); + song.musicPath.c_str()); } else { ImGui::TextColored( ImVec4(1, 0.42, 0.41, 1), @@ -317,20 +302,20 @@ void EditorState::displayStatus() { } } - if (not albumCover) { - if (not fumen.albumCoverPath.empty()) { + if (not album_cover) { + if (not song.albumCoverPath.empty()) { ImGui::TextColored( ImVec4(1, 0.42, 0.41, 1), "Invalid albumCover path : %s", - fumen.albumCoverPath.c_str()); + song.albumCoverPath.c_str()); } else { ImGui::TextColored( ImVec4(1, 0.42, 0.41, 1), "No albumCover loaded"); } } - if (ImGui::SliderInt("Music Volume", &musicVolume, 0, 10)) { - setMusicVolume(musicVolume); + if (ImGui::SliderInt("Music Volume", &music_volume, 0, 10)) { + set_music_volume(music_volume); } } ImGui::End(); @@ -374,7 +359,7 @@ void EditorState::displayPlaybackStatus() { } ImGui::TextColored(ImVec4(0.53, 0.53, 0.53, 1), "Timeline Position :"); ImGui::SameLine(); - ImGui::TextUnformatted(Toolbox::to_string(playbackPosition).c_str()); + ImGui::TextUnformatted(Toolbox::to_string(playback_position).c_str()); } ImGui::End(); ImGui::PopStyleVar(); @@ -383,35 +368,23 @@ void EditorState::displayPlaybackStatus() { void EditorState::displayTimeline() { ImGuiIO& io = ImGui::GetIO(); - float height = io.DisplaySize.y * 0.9f; + float raw_height = io.DisplaySize.y * 0.9f; + auto height = static_cast(raw_height); - if (chart) { - if (chart->densityGraph.should_recompute) { - chart->densityGraph.should_recompute = false; - chart->densityGraph.update( - static_cast(height), - getChartRuntime(), + if ( + chart.has_value() + and ( + chart->density_graph.should_recompute + or height != chart->density_graph.last_height.value_or(height) + ) + ) { + chart->density_graph.should_recompute = false; + chart->density_graph.update( + height, chart->ref, - fumen.BPM, - getResolution()); - } else { - if (chart->densityGraph.last_height) { - if (static_cast(height) != *(chart->densityGraph.last_height)) { - chart->densityGraph.update( - static_cast(height), - getChartRuntime(), - chart->ref, - fumen.BPM, - getResolution()); - } - } else { - chart->densityGraph.update( - static_cast(height), - getChartRuntime(), - chart->ref, - fumen.BPM, - getResolution()); - } + song.BPM, + get_resolution() + ); } } @@ -438,7 +411,7 @@ void EditorState::displayTimeline() { if (chart) { ImGui::SetCursorPos({0, 0}); ImGui::Image(chart->densityGraph.graph); - AffineTransform scroll(-fumen.offset, previewEnd.asSeconds(), 1.f, 0.f); + AffineTransform scroll(-song.offset, this->preview_end.asSeconds(), 1.f, 0.f); float slider_pos = scroll.transform(playbackPosition.asSeconds()); ImGui::SetCursorPos({0, 0}); if (ImGui::VSliderFloat("TimelineSlider", ImGui::GetContentRegionMax(), &slider_pos, 0.f, 1.f, "")) { @@ -453,7 +426,7 @@ void EditorState::displayTimeline() { void EditorState::displayChartList(std::filesystem::path assets) { if (ImGui::Begin("Chart List", &showChartList, ImGuiWindowFlags_AlwaysAutoResize)) { - if (this->fumen.Charts.empty()) { + if (this->song.Charts.empty()) { ImGui::Dummy({100, 0}); ImGui::SameLine(); ImGui::Text("- no charts -"); @@ -469,7 +442,7 @@ void EditorState::displayChartList(std::filesystem::path assets) { ImGui::TextDisabled("Note Count"); ImGui::NextColumn(); ImGui::Separator(); - for (auto& tuple : fumen.Charts) { + for (auto& tuple : song.Charts) { if (ImGui::Selectable( tuple.first.c_str(), chart ? chart->ref == tuple.second : false, @@ -497,16 +470,16 @@ void EditorState::displayLinearView() { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(2, 2)); if (ImGui::Begin("Linear View", &showLinearView, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) { if (chart) { - linearView.update( + linear_view.update( chart, playbackPosition, getCurrentTick(), - fumen.BPM, + song.BPM, getResolution(), ImGui::GetContentRegionMax()); auto cursor_y = ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2.f; ImGui::SetCursorPos({0, cursor_y}); - ImGui::Image(linearView.view); + ImGui::Image(linear_view.view); } else { ImGui::TextDisabled("- no chart selected -"); } @@ -566,7 +539,7 @@ void EditorState::updateVisibleNotes() { visibleNotes.clear(); if (chart) { - float position = playbackPosition.asSeconds(); + float position = playback_position.asSeconds(); for (auto const& note : chart->ref.Notes) { float note_timing_in_seconds = getSecondsAt(note.getTiming()); @@ -613,48 +586,18 @@ void EditorState::toggleNoteAtCurrentTime(int pos) { } chart->history.push(std::make_shared(toggledNotes, not deleted_something)); - chart->densityGraph.should_recompute = true; + chart->density_graph.should_recompute = true; } } -void EditorState::setMusicSpeed(int newMusicSpeed) { - musicSpeed = std::clamp(newMusicSpeed, 1, 20); - if (music) { - music->setPitch(musicSpeed / 10.f); - } -} - -void EditorState::musicSpeedUp() { - setMusicSpeed(musicSpeed + 1); -} - -void EditorState::musicSpeedDown() { - setMusicSpeed(musicSpeed - 1); -} - -void EditorState::setMusicVolume(int newMusicVolume) { - musicVolume = std::clamp(newMusicVolume, 0, 10); - if (music) { - music->setVolume(Toolbox::convertVolumeToNormalizedDB(musicVolume)*100.f); - } -} - -void EditorState::musicVolumeUp() { - setMusicVolume(musicVolume + 1); -} - -void EditorState::musicVolumeDown() { - setMusicVolume(musicVolume -1 ); -} - -const sf::Time& EditorState::getPreviewEnd() { - reloadPreviewEnd(); - return previewEnd; +const sf::Time& EditorState::get_preview_end() { + reload_preview_end(); + return preview_end; } void ESHelper::save(EditorState& ed) { try { - ed.fumen.autoSaveAsMemon(); + ed.song.autoSaveAsMemon(); } catch (const std::exception& e) { tinyfd_messageBox("Error", e.what(), "ok", "error", 1); } @@ -679,7 +622,7 @@ void ESHelper::openFromFile( Fumen f(file); f.autoLoadFromMemon(); ed.emplace(f, assets); - Toolbox::pushNewRecentFile(std::filesystem::canonical(ed->fumen.path), settings); + Toolbox::pushNewRecentFile(std::filesystem::canonical(ed->song.path), settings); } catch (const std::exception& e) { tinyfd_messageBox("Error", e.what(), "ok", "error", 1); } @@ -717,8 +660,8 @@ std::optional ESHelper::NewChartDialog::display(EditorState& editorState) } if (ImGui::BeginCombo("Difficulty", comboPreview.c_str())) { for (auto dif_name : {"BSC", "ADV", "EXT"}) { - if (editorState.fumen.Charts.find(dif_name) - == editorState.fumen.Charts.end()) { + if (editorState.song.Charts.find(dif_name) + == editorState.song.Charts.end()) { if (ImGui::Selectable(dif_name, dif_name == difficulty)) { showCustomDifName = false; difficulty = dif_name; @@ -735,8 +678,8 @@ std::optional ESHelper::NewChartDialog::display(EditorState& editorState) } if (showCustomDifName) { Toolbox::InputTextColored( - editorState.fumen.Charts.find(difficulty) - == editorState.fumen.Charts.end(), + editorState.song.Charts.find(difficulty) + == editorState.song.Charts.end(), "Chart name has to be unique", "Difficulty Name", &difficulty); @@ -765,8 +708,8 @@ std::optional ESHelper::NewChartDialog::display(EditorState& editorState) } ImGui::Separator(); if (difficulty.empty() - or (editorState.fumen.Charts.find(difficulty) - != editorState.fumen.Charts.end())) { + or (editorState.song.Charts.find(difficulty) + != editorState.song.Charts.end())) { ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); ImGui::Button("Create Chart##New Chart"); @@ -798,7 +741,7 @@ void ESHelper::ChartPropertiesDialog::display(EditorState& editorState, std::fil std::set difNames {"BSC", "ADV", "EXT"}; showCustomDifName = (difNames.find(difficulty_name) == difNames.end()); - for (auto const& tuple : editorState.fumen.Charts) { + for (auto const& tuple : editorState.song.Charts) { if (tuple.second != editorState.chart->ref) { difNamesInUse.insert(tuple.first); } @@ -855,17 +798,17 @@ void ESHelper::ChartPropertiesDialog::display(EditorState& editorState, std::fil if (ImGui::Button("Apply##New Chart")) { try { Chart modified_chart = - editorState.fumen.Charts.at(editorState.chart->ref.dif_name); - editorState.fumen.Charts.erase(editorState.chart->ref.dif_name); + editorState.song.Charts.at(editorState.chart->ref.dif_name); + editorState.song.Charts.erase(editorState.chart->ref.dif_name); modified_chart.dif_name = this->difficulty_name; modified_chart.level = this->level; - if (not(editorState.fumen.Charts.emplace(modified_chart.dif_name, modified_chart)) + if (not(editorState.song.Charts.emplace(modified_chart.dif_name, modified_chart)) .second) { throw std::runtime_error( - "Could not insert modified chart in fumen"); + "Could not insert modified chart in song"); } else { editorState.chart.emplace( - editorState.fumen.Charts.at(modified_chart.dif_name), + editorState.song.Charts.at(modified_chart.dif_name), assets ); shouldRefreshValues = true; diff --git a/src/editor_state.hpp b/src/editor_state.hpp index 78d91ec..2c878da 100644 --- a/src/editor_state.hpp +++ b/src/editor_state.hpp @@ -4,11 +4,12 @@ #include #include -#include "chart_with_history.hpp" -#include "fumen.hpp" +#include "better_song.hpp" +#include "chart_state.hpp" #include "history.hpp" #include "history_actions.hpp" #include "marker.hpp" +#include "music_state.hpp" #include "notes_clipboard.hpp" #include "precise_music.hpp" #include "time_selection.hpp" @@ -31,73 +32,64 @@ enum saveChangesResponses { */ class EditorState { public: - EditorState(Fumen& fumen, std::filesystem::path assets); + EditorState( + const better::Song& song, + const std::filesystem::path& song_path, + const std::filesystem::path& assets + ) : + song(song), + playfield(assets), + linear_view(assets), + edited_music_path(song.metadata.audio.value_or("")), + song_path(song_path) + { + if (not this->song.charts.empty()) { + this->chart_state.emplace(this->song.charts.begin()->second, assets); + } + reload_music(); + reload_album_cover(); + }; - std::optional chart; + better::Song song; + std::optional chart_state; - Fumen fumen; + std::optional music_state; Playfield playfield; - LinearView linearView; + LinearView linear_view; // the snap but divided by 4 because you can't set a snap to anything lower // than 4ths int snap = 1; - std::optional music; - int musicVolume = 10; // 0 -> 10 - void setMusicVolume(int newMusicVolume); - void musicVolumeUp(); - void musicVolumeDown(); - - int musicSpeed = 10; // 1 -> 20 - void setMusicSpeed(int newMusicSpeed); - void musicSpeedUp(); - void musicSpeedDown(); - - std::optional albumCover; + std::optional album_cover; bool playing; - sf::Time previousPos; - sf::Time playbackPosition; + sf::Time previous_pos; + sf::Time playback_position; -private: - sf::Time previewEnd; // sf::Time (in the audio file "coordinates") at which the chart preview stops, can be - // after the end of the actual audio file + const sf::Time& get_preview_end(); -public: - const sf::Time& getPreviewEnd(); + void set_playback_and_music_position(sf::Time new_position); -public: - void setPlaybackAndMusicPosition(sf::Time newPosition); - - float getBeats() { return getBeatsAt(playbackPosition.asSeconds()); }; + float getBeats() { return getBeatsAt(playback_position.asSeconds()); }; float getBeatsAt(float seconds) { - return ((seconds + fumen.offset) / 60.f) * fumen.BPM; + return ((seconds + song.offset) / 60.f) * song.BPM; }; - float getCurrentTick() { return getTicksAt(playbackPosition.asSeconds()); }; + float getCurrentTick() { return getTicksAt(playback_position.asSeconds()); }; float getTicksAt(float seconds) { - return getBeatsAt(seconds) * getResolution(); + return getBeatsAt(seconds) * get_resolution(); } float getSecondsAt(int tick) { - return (60.f * tick) / (fumen.BPM * getResolution()) - fumen.offset; + return (60.f * tick) / (song.BPM * get_resolution()) - song.offset; }; - int getResolution() { return chart ? chart->ref.getResolution() : 240; }; - int getSnapStep() { return getResolution() / snap; }; + int get_resolution() { return chart_state ? chart_state->chart.getResolution() : 240; }; + int get_snap_step() { return get_resolution() / snap; }; - float ticksToSeconds(int ticks) { - return (60.f * ticks) / (fumen.BPM * getResolution()); - }; - float getChartRuntime() { - return getPreviewEnd().asSeconds() + fumen.offset; - }; - - void reloadFromFumen(std::filesystem::path assets); - void reloadMusic(); - void reloadAlbumCover(); - void reloadPreviewEnd(); + void reload_album_cover(); + void reload_preview_end(); bool showPlayfield = true; bool showProperties; @@ -126,6 +118,15 @@ public: std::set visibleNotes; void toggleNoteAtCurrentTime(int pos); + +private: + sf::Time preview_end; // sf::Time (in the audio file "coordinates") at which the chart preview stops, can be + // after the end of the actual audio file + + std::string edited_music_path; + void reload_music(); + + std::filesystem::path song_path; }; namespace ESHelper { diff --git a/src/editor_state_actions.cpp b/src/editor_state_actions.cpp index 5370a2e..262adae 100644 --- a/src/editor_state_actions.cpp +++ b/src/editor_state_actions.cpp @@ -4,7 +4,7 @@ void Move::backwardsInTime(std::optional& ed) { if (ed and ed->chart) { float floatTicks = ed->getCurrentTick(); auto prevTick = static_cast(floorf(floatTicks)); - int step = ed->getSnapStep(); + int step = ed->get_snap_step(); int prevTickInSnap = prevTick; if (prevTick % step == 0) { prevTickInSnap -= step; @@ -19,7 +19,7 @@ void Move::forwardsInTime(std::optional& ed) { if (ed and ed->chart) { float floatTicks = ed->getCurrentTick(); auto nextTick = static_cast(ceilf(floatTicks)); - int step = ed->getSnapStep(); + int step = ed->get_snap_step(); int nextTickInSnap = nextTick + (step - nextTick % step); ed->setPlaybackAndMusicPosition(sf::seconds(ed->getSecondsAt(nextTickInSnap))); } diff --git a/src/fumen.cpp b/src/fumen.cpp index 1d2edb4..5463c26 100644 --- a/src/fumen.cpp +++ b/src/fumen.cpp @@ -139,7 +139,7 @@ void Fumen::saveAsMemon(std::filesystem::path path) { * Returns how long the chart is in seconds as a float, from beat 0 to the last * note */ -float Fumen::getChartRuntime(Chart c) { +float Fumen::get_chart_runtime(Chart c) { if (!c.Notes.empty()) { Note last_note = *c.Notes.rbegin(); auto beats = static_cast(last_note.getTiming()) / c.getResolution(); diff --git a/src/fumen.hpp b/src/fumen.hpp index 6997ce5..7c6c81c 100644 --- a/src/fumen.hpp +++ b/src/fumen.hpp @@ -53,5 +53,5 @@ public: float BPM; float offset; - float getChartRuntime(Chart c); + float get_chart_runtime(Chart c); }; diff --git a/src/main.cpp b/src/main.cpp index 8452647..0cac490 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -76,7 +76,7 @@ int main(int argc, char** argv) { MarkerEndingState markerEndingState = preferences.markerEndingState; BlankScreen bg{assets_folder}; - std::optional editorState; + std::optional editor_state; NotificationsQueue notificationsQueue; ESHelper::NewChartDialog newChartDialog; ESHelper::ChartPropertiesDialog chartPropertiesDialog; @@ -90,7 +90,7 @@ int main(int argc, char** argv) { switch (event.type) { case sf::Event::Closed: preferences.save(); - if (ESHelper::saveOrCancel(editorState)) { + if (ESHelper::saveOrCancel(editor_state)) { window.close(); } break; @@ -101,8 +101,8 @@ int main(int argc, char** argv) { case sf::Event::MouseButtonPressed: switch (event.mouseButton.button) { case sf::Mouse::Button::Right: - if (editorState and editorState->chart) { - editorState->chart->creatingLongNote = true; + if (editor_state and editor_state->chart_state) { + editor_state->chart_state->creating_long_note = true; } break; default: @@ -112,15 +112,15 @@ int main(int argc, char** argv) { case sf::Event::MouseButtonReleased: switch (event.mouseButton.button) { case sf::Mouse::Button::Right: - if (editorState and editorState->chart) { - if (editorState->chart->longNoteBeingCreated) { - auto pair = *editorState->chart->longNoteBeingCreated; + if (editor_state and editor_state->chart_state) { + if (editor_state->chart_state->longNoteBeingCreated) { + auto pair = *editor_state->chart->longNoteBeingCreated; Note new_note = Note(pair.first, pair.second); std::set new_note_set = {new_note}; - editorState->chart->ref.Notes.insert(new_note); - editorState->chart->longNoteBeingCreated.reset(); - editorState->chart->creatingLongNote = false; - editorState->chart->history.push( + editor_state->chart->ref.Notes.insert(new_note); + editor_state->chart->longNoteBeingCreated.reset(); + editor_state->chart->creatingLongNote = false; + editor_state->chart->history.push( std::make_shared(new_note_set, true)); } } @@ -136,11 +136,11 @@ int main(int argc, char** argv) { std::floor(event.mouseWheelScroll.delta)); if (delta >= 0) { for (int i = 0; i < delta; ++i) { - Move::backwardsInTime(editorState); + Move::backwardsInTime(editor_state); } } else { for (int i = 0; i < -delta; ++i) { - Move::forwardsInTime(editorState); + Move::forwardsInTime(editor_state); } } } break; @@ -157,43 +157,43 @@ int main(int argc, char** argv) { // Discard, in that order : timeSelection, // selected_notes case sf::Keyboard::Escape: - if (editorState and editorState->chart) { + if (editor_state and editor_state->chart) { if (not std::holds_alternative( - editorState->chart->timeSelection)) { - editorState->chart->timeSelection.emplace(); - } else if (not editorState->chart->selectedNotes.empty()) { - editorState->chart->selectedNotes.clear(); + editor_state->chart->timeSelection)) { + editor_state->chart->timeSelection.emplace(); + } else if (not editor_state->chart->selectedNotes.empty()) { + editor_state->chart->selectedNotes.clear(); } } break; // Modify timeSelection case sf::Keyboard::Tab: - if (editorState and editorState->chart) { + if (editor_state and editor_state->chart) { // if no timeSelection was previously made if (std::holds_alternative( - editorState->chart->timeSelection)) { + editor_state->chart->timeSelection)) { // set the start of the timeSelection to the // current time - editorState->chart->timeSelection = + editor_state->chart->timeSelection = static_cast( - editorState->getCurrentTick()); + editor_state->getCurrentTick()); // if the start of the timeSelection is // already set } else if (std::holds_alternative( - editorState->chart->timeSelection)) { + editor_state->chart->timeSelection)) { auto current_tick = - static_cast(editorState->getCurrentTick()); + static_cast(editor_state->getCurrentTick()); auto selection_start = static_cast(std::get( - editorState->chart->timeSelection)); + editor_state->chart->timeSelection)); // if we are on the same tick as the // timeSelection start we discard the // timeSelection if (current_tick == selection_start) { - editorState->chart->timeSelection.emplace(); + editor_state->chart->timeSelection.emplace(); // else we create a full timeSelection // while paying attention to the order @@ -202,24 +202,24 @@ int main(int argc, char** argv) { std::min(current_tick, selection_start)); auto duration = static_cast( std::abs(current_tick - selection_start)); - editorState->chart->timeSelection.emplace( + editor_state->chart->timeSelection.emplace( new_selection_start, duration); - editorState->chart->selectedNotes = - editorState->chart->ref.getNotesBetween( + editor_state->chart->selectedNotes = + editor_state->chart->ref.getNotesBetween( new_selection_start, new_selection_start + duration); } // if a full timeSelection already exists } else if (std::holds_alternative( - editorState->chart->timeSelection)) { + editor_state->chart->timeSelection)) { // discard the current timeSelection and set // the start of the timeSelection to the // current time - editorState->chart->timeSelection = + editor_state->chart->timeSelection = static_cast( - editorState->getCurrentTick()); + editor_state->getCurrentTick()); } } break; @@ -227,7 +227,7 @@ int main(int argc, char** argv) { // Delete selected notes from the chart and discard // timeSelection case sf::Keyboard::Delete: - Edit::delete_(editorState, notificationsQueue); + Edit::delete_(editor_state, notificationsQueue); break; /* @@ -235,49 +235,49 @@ int main(int argc, char** argv) { */ case sf::Keyboard::Up: if (event.key.shift) { - if (editorState) { - editorState->musicVolumeUp(); + if (editor_state) { + editor_state->musicVolumeUp(); std::stringstream ss; ss << "Music Volume : " - << editorState->musicVolume * 10 << "%"; + << editor_state->musicVolume * 10 << "%"; notificationsQueue.push( std::make_shared(ss.str())); } } else { - Move::backwardsInTime(editorState); + Move::backwardsInTime(editor_state); } break; case sf::Keyboard::Down: if (event.key.shift) { - if (editorState) { - editorState->musicVolumeDown(); + if (editor_state) { + editor_state->musicVolumeDown(); std::stringstream ss; ss << "Music Volume : " - << editorState->musicVolume * 10 << "%"; + << editor_state->musicVolume * 10 << "%"; notificationsQueue.push( std::make_shared(ss.str())); } } else { - Move::forwardsInTime(editorState); + Move::forwardsInTime(editor_state); } break; case sf::Keyboard::Left: if (event.key.shift) { - if (editorState) { - editorState->musicSpeedDown(); + if (editor_state) { + editor_state->musicSpeedDown(); std::stringstream ss; - ss << "Speed : " << editorState->musicSpeed * 10 << "%"; + ss << "Speed : " << editor_state->musicSpeed * 10 << "%"; notificationsQueue.push( std::make_shared(ss.str())); } } else { - if (editorState and editorState->chart) { - editorState->snap = Toolbox::getPreviousDivisor( - editorState->chart->ref.getResolution(), - editorState->snap); + if (editor_state and editor_state->chart) { + editor_state->snap = Toolbox::getPreviousDivisor( + editor_state->chart->ref.getResolution(), + editor_state->snap); std::stringstream ss; ss << "Snap : " - << Toolbox::toOrdinal(4 * editorState->snap); + << Toolbox::toOrdinal(4 * editor_state->snap); notificationsQueue.push( std::make_shared(ss.str())); } @@ -285,19 +285,19 @@ int main(int argc, char** argv) { break; case sf::Keyboard::Right: if (event.key.shift) { - editorState->musicSpeedUp(); + editor_state->musicSpeedUp(); std::stringstream ss; - ss << "Speed : " << editorState->musicSpeed * 10 << "%"; + ss << "Speed : " << editor_state->musicSpeed * 10 << "%"; notificationsQueue.push( std::make_shared(ss.str())); } else { - if (editorState and editorState->chart) { - editorState->snap = Toolbox::getNextDivisor( - editorState->chart->ref.getResolution(), - editorState->snap); + if (editor_state and editor_state->chart) { + editor_state->snap = Toolbox::getNextDivisor( + editor_state->chart->ref.getResolution(), + editor_state->snap); std::stringstream ss; ss << "Snap : " - << Toolbox::toOrdinal(4 * editorState->snap); + << Toolbox::toOrdinal(4 * editor_state->snap); notificationsQueue.push( std::make_shared(ss.str())); } @@ -339,21 +339,21 @@ int main(int argc, char** argv) { break; case sf::Keyboard::Space: if (not ImGui::GetIO().WantTextInput) { - if (editorState) { - editorState->playing = not editorState->playing; + if (editor_state) { + editor_state->playing = not editor_state->playing; } } break; case sf::Keyboard::Add: - if (editorState) { - editorState->linearView.zoom_in(); + if (editor_state) { + editor_state->linearView.zoom_in(); notificationsQueue.push(std::make_shared( "Zoom in")); } break; case sf::Keyboard::Subtract: - if (editorState) { - editorState->linearView.zoom_out(); + if (editor_state) { + editor_state->linearView.zoom_out(); notificationsQueue.push(std::make_shared( "Zoom out")); } @@ -363,46 +363,46 @@ int main(int argc, char** argv) { */ case sf::Keyboard::C: if (event.key.control) { - Edit::copy(editorState, notificationsQueue); + Edit::copy(editor_state, notificationsQueue); } break; case sf::Keyboard::O: if (event.key.control) { - if (ESHelper::saveOrCancel(editorState)) { - ESHelper::open(editorState, assets_folder, settings_folder); + if (ESHelper::saveOrCancel(editor_state)) { + ESHelper::open(editor_state, assets_folder, settings_folder); } } break; case sf::Keyboard::P: if (event.key.shift) { - editorState->showProperties = true; + editor_state->showProperties = true; } break; case sf::Keyboard::S: if (event.key.control) { - ESHelper::save(*editorState); + ESHelper::save(*editor_state); notificationsQueue.push(std::make_shared( "Saved file")); } break; case sf::Keyboard::V: if (event.key.control) { - Edit::paste(editorState, notificationsQueue); + Edit::paste(editor_state, notificationsQueue); } break; case sf::Keyboard::X: if (event.key.control) { - Edit::cut(editorState, notificationsQueue); + Edit::cut(editor_state, notificationsQueue); } break; case sf::Keyboard::Y: if (event.key.control) { - Edit::redo(editorState, notificationsQueue); + Edit::redo(editor_state, notificationsQueue); } break; case sf::Keyboard::Z: if (event.key.control) { - Edit::undo(editorState, notificationsQueue); + Edit::undo(editor_state, notificationsQueue); } break; default: @@ -418,46 +418,46 @@ int main(int argc, char** argv) { ImGui::SFML::Update(window, delta); // Audio playback management - if (editorState) { - editorState->updateVisibleNotes(); - if (editorState->playing) { - editorState->previousPos = editorState->playbackPosition; - editorState->playbackPosition += delta * (editorState->musicSpeed / 10.f); - if (editorState->music) { - switch (editorState->music->getStatus()) { + if (editor_state) { + editor_state->updateVisibleNotes(); + if (editor_state->playing) { + editor_state->previousPos = editor_state->playbackPosition; + editor_state->playbackPosition += delta * (editor_state->musicSpeed / 10.f); + if (editor_state->music) { + switch (editor_state->music->getStatus()) { case sf::Music::Stopped: case sf::Music::Paused: - if (editorState->playbackPosition.asSeconds() >= 0 - and editorState->playbackPosition - < editorState->music->getDuration()) { - editorState->music->setPlayingOffset(editorState->playbackPosition); - editorState->music->play(); + if (editor_state->playbackPosition.asSeconds() >= 0 + and editor_state->playbackPosition + < editor_state->music->getDuration()) { + editor_state->music->setPlayingOffset(editor_state->playbackPosition); + editor_state->music->play(); } break; case sf::Music::Playing: - editorState->playbackPosition = - editorState->music->getPrecisePlayingOffset(); + editor_state->playbackPosition = + editor_state->music->getPrecisePlayingOffset(); break; default: break; } } if (beatTick.shouldPlay) { - auto previous_tick = static_cast(editorState->getTicksAt( - editorState->previousPos.asSeconds())); - auto current_tick = static_cast(editorState->getTicksAt( - editorState->playbackPosition.asSeconds())); - if (previous_tick / editorState->getResolution() - != current_tick / editorState->getResolution()) { + auto previous_tick = static_cast(editor_state->getTicksAt( + editor_state->previousPos.asSeconds())); + auto current_tick = static_cast(editor_state->getTicksAt( + editor_state->playbackPosition.asSeconds())); + if (previous_tick / editor_state->getResolution() + != current_tick / editor_state->getResolution()) { beatTick.play(); } } if (noteTick.shouldPlay) { int note_count = 0; - for (auto note : editorState->visibleNotes) { - float noteTiming = editorState->getSecondsAt(note.getTiming()); - if (noteTiming >= editorState->previousPos.asSeconds() - and noteTiming <= editorState->playbackPosition.asSeconds()) { + for (auto note : editor_state->visibleNotes) { + float noteTiming = editor_state->getSecondsAt(note.getTiming()); + if (noteTiming >= editor_state->previousPos.asSeconds() + and noteTiming <= editor_state->playbackPosition.asSeconds()) { note_count++; } } @@ -472,69 +472,69 @@ int main(int argc, char** argv) { } } - if (editorState->playbackPosition > editorState->getPreviewEnd()) { - editorState->playing = false; - editorState->playbackPosition = editorState->getPreviewEnd(); + if (editor_state->playbackPosition > editor_state->getPreviewEnd()) { + editor_state->playing = false; + editor_state->playbackPosition = editor_state->getPreviewEnd(); } } else { - if (editorState->music) { - if (editorState->music->getStatus() == sf::Music::Playing) { - editorState->music->pause(); + if (editor_state->music) { + if (editor_state->music->getStatus() == sf::Music::Playing) { + editor_state->music->pause(); } } } } // Drawing - if (editorState) { + if (editor_state) { window.clear(sf::Color(0, 0, 0)); - if (editorState->showHistory) { - editorState->chart->history.display(get_message); + if (editor_state->showHistory) { + editor_state->chart->history.display(get_message); } - if (editorState->showPlayfield) { - editorState->displayPlayfield(marker, markerEndingState); + if (editor_state->showPlayfield) { + editor_state->displayPlayfield(marker, markerEndingState); } - if (editorState->showLinearView) { - editorState->displayLinearView(); + if (editor_state->showLinearView) { + editor_state->displayLinearView(); } - if (editorState->linearView.shouldDisplaySettings) { - editorState->linearView.displaySettings(); + if (editor_state->linearView.shouldDisplaySettings) { + editor_state->linearView.displaySettings(); } - if (editorState->showProperties) { - editorState->displayProperties(); + if (editor_state->showProperties) { + editor_state->displayProperties(); } - if (editorState->showStatus) { - editorState->displayStatus(); + if (editor_state->showStatus) { + editor_state->displayStatus(); } - if (editorState->showPlaybackStatus) { - editorState->displayPlaybackStatus(); + if (editor_state->showPlaybackStatus) { + editor_state->displayPlaybackStatus(); } - if (editorState->showTimeline) { - editorState->displayTimeline(); + if (editor_state->showTimeline) { + editor_state->displayTimeline(); } - if (editorState->showChartList) { - editorState->displayChartList(assets_folder); + if (editor_state->showChartList) { + editor_state->displayChartList(assets_folder); } - if (editorState->showNewChartDialog) { - std::optional c = newChartDialog.display(*editorState); + if (editor_state->showNewChartDialog) { + std::optional c = newChartDialog.display(*editor_state); if (c) { - editorState->showNewChartDialog = false; - if (editorState->fumen.Charts.try_emplace(c->dif_name, *c).second) { - editorState->chart.emplace(editorState->fumen.Charts.at(c->dif_name), assets_folder); + editor_state->showNewChartDialog = false; + if (editor_state->song.Charts.try_emplace(c->dif_name, *c).second) { + editor_state->chart.emplace(editor_state->song.Charts.at(c->dif_name), assets_folder); } } } else { newChartDialog.resetValues(); } - if (editorState->showChartProperties) { - chartPropertiesDialog.display(*editorState, assets_folder); + if (editor_state->showChartProperties) { + chartPropertiesDialog.display(*editor_state, assets_folder); } else { chartPropertiesDialog.shouldRefreshValues = true; } - if (editorState->showSoundSettings) { - ImGui::Begin("Sound Settings", &editorState->showSoundSettings, ImGuiWindowFlags_AlwaysAutoResize); + if (editor_state->showSoundSettings) { + ImGui::Begin("Sound Settings", &editor_state->showSoundSettings, ImGuiWindowFlags_AlwaysAutoResize); { if (ImGui::TreeNode("Beat Tick")) { beatTick.displayControls(); @@ -561,7 +561,7 @@ int main(int argc, char** argv) { { if (ImGui::BeginMenu("File")) { if (ImGui::MenuItem("New")) { - if (ESHelper::saveOrCancel(editorState)) { + if (ESHelper::saveOrCancel(editor_state)) { const char* _filepath = tinyfd_saveFileDialog("New File", nullptr, 0, nullptr, nullptr); if (_filepath != nullptr) { @@ -569,9 +569,9 @@ int main(int argc, char** argv) { try { Fumen f(filepath); f.autoSaveAsMemon(); - editorState.emplace(f, assets_folder); + editor_state.emplace(f, assets_folder); Toolbox::pushNewRecentFile(std::filesystem::canonical( - editorState->fumen.path), settings_folder); + editor_state->song.path), settings_folder); } catch (const std::exception& e) { tinyfd_messageBox( "Error", @@ -585,8 +585,8 @@ int main(int argc, char** argv) { } ImGui::Separator(); if (ImGui::MenuItem("Open", "Ctrl+O")) { - if (ESHelper::saveOrCancel(editorState)) { - ESHelper::open(editorState, assets_folder, settings_folder); + if (ESHelper::saveOrCancel(editor_state)) { + ESHelper::open(editor_state, assets_folder, settings_folder); } } if (ImGui::BeginMenu("Recent Files")) { @@ -594,8 +594,8 @@ int main(int argc, char** argv) { for (const auto& file : Toolbox::getRecentFiles(settings_folder)) { ImGui::PushID(i); if (ImGui::MenuItem(file.c_str())) { - if (ESHelper::saveOrCancel(editorState)) { - ESHelper::openFromFile(editorState, file, assets_folder, settings_folder); + if (ESHelper::saveOrCancel(editor_state)) { + ESHelper::openFromFile(editor_state, file, assets_folder, settings_folder); } } ImGui::PopID(); @@ -603,109 +603,109 @@ int main(int argc, char** argv) { } ImGui::EndMenu(); } - if (ImGui::MenuItem("Close", "", false, editorState.has_value())) { - if (ESHelper::saveOrCancel(editorState)) { - editorState.reset(); + if (ImGui::MenuItem("Close", "", false, editor_state.has_value())) { + if (ESHelper::saveOrCancel(editor_state)) { + editor_state.reset(); } } ImGui::Separator(); - if (ImGui::MenuItem("Save", "Ctrl+S", false, editorState.has_value())) { - ESHelper::save(*editorState); + if (ImGui::MenuItem("Save", "Ctrl+S", false, editor_state.has_value())) { + ESHelper::save(*editor_state); } - if (ImGui::MenuItem("Save As", "", false, editorState.has_value())) { + if (ImGui::MenuItem("Save As", "", false, editor_state.has_value())) { char const* options[1] = {"*.memon"}; const char* _filepath( tinyfd_saveFileDialog("Save File", nullptr, 1, options, nullptr)); if (_filepath != nullptr) { std::filesystem::path filepath(_filepath); try { - editorState->fumen.saveAsMemon(filepath); + editor_state->song.saveAsMemon(filepath); } catch (const std::exception& e) { tinyfd_messageBox("Error", e.what(), "ok", "error", 1); } } } ImGui::Separator(); - if (ImGui::MenuItem("Properties", "Shift+P", false, editorState.has_value())) { - editorState->showProperties = true; + if (ImGui::MenuItem("Properties", "Shift+P", false, editor_state.has_value())) { + editor_state->showProperties = true; } ImGui::EndMenu(); } if (ImGui::BeginMenu("Edit")) { if (ImGui::MenuItem("Undo", "Ctrl+Z")) { - Edit::undo(editorState, notificationsQueue); + Edit::undo(editor_state, notificationsQueue); } if (ImGui::MenuItem("Redo", "Ctrl+Y")) { - Edit::redo(editorState, notificationsQueue); + Edit::redo(editor_state, notificationsQueue); } ImGui::Separator(); if (ImGui::MenuItem("Cut", "Ctrl+X")) { - Edit::cut(editorState, notificationsQueue); + Edit::cut(editor_state, notificationsQueue); } if (ImGui::MenuItem("Copy", "Ctrl+C")) { - Edit::copy(editorState, notificationsQueue); + Edit::copy(editor_state, notificationsQueue); } if (ImGui::MenuItem("Paste", "Ctrl+V")) { - Edit::paste(editorState, notificationsQueue); + Edit::paste(editor_state, notificationsQueue); } if (ImGui::MenuItem("Delete", "Delete")) { - Edit::delete_(editorState, notificationsQueue); + Edit::delete_(editor_state, notificationsQueue); } ImGui::EndMenu(); } - if (ImGui::BeginMenu("Chart", editorState.has_value())) { + if (ImGui::BeginMenu("Chart", editor_state.has_value())) { if (ImGui::MenuItem("Chart List")) { - editorState->showChartList = true; + editor_state->showChartList = true; } if (ImGui::MenuItem( "Properties##Chart", nullptr, false, - editorState->chart.has_value())) { - editorState->showChartProperties = true; + editor_state->chart.has_value())) { + editor_state->showChartProperties = true; } ImGui::Separator(); if (ImGui::MenuItem("New Chart")) { - editorState->showNewChartDialog = true; + editor_state->showNewChartDialog = true; } ImGui::Separator(); if (ImGui::MenuItem( "Delete Chart", nullptr, false, - editorState->chart.has_value())) { - editorState->fumen.Charts.erase(editorState->chart->ref.dif_name); - editorState->chart.reset(); + editor_state->chart.has_value())) { + editor_state->song.Charts.erase(editor_state->chart->ref.dif_name); + editor_state->chart.reset(); } ImGui::EndMenu(); } - if (ImGui::BeginMenu("View", editorState.has_value())) { - if (ImGui::MenuItem("Playfield", nullptr, editorState->showPlayfield)) { - editorState->showPlayfield = not editorState->showPlayfield; + if (ImGui::BeginMenu("View", editor_state.has_value())) { + if (ImGui::MenuItem("Playfield", nullptr, editor_state->showPlayfield)) { + editor_state->showPlayfield = not editor_state->showPlayfield; } - if (ImGui::MenuItem("Linear View", nullptr, editorState->showLinearView)) { - editorState->showLinearView = not editorState->showLinearView; + if (ImGui::MenuItem("Linear View", nullptr, editor_state->showLinearView)) { + editor_state->showLinearView = not editor_state->showLinearView; } - if (ImGui::MenuItem("Playback Status", nullptr, editorState->showPlaybackStatus)) { - editorState->showPlaybackStatus = not editorState->showPlaybackStatus; + if (ImGui::MenuItem("Playback Status", nullptr, editor_state->showPlaybackStatus)) { + editor_state->showPlaybackStatus = not editor_state->showPlaybackStatus; } - if (ImGui::MenuItem("Timeline", nullptr, editorState->showTimeline)) { - editorState->showTimeline = not editorState->showTimeline; + if (ImGui::MenuItem("Timeline", nullptr, editor_state->showTimeline)) { + editor_state->showTimeline = not editor_state->showTimeline; } - if (ImGui::MenuItem("Editor Status", nullptr, editorState->showStatus)) { - editorState->showStatus = not editorState->showStatus; + if (ImGui::MenuItem("Editor Status", nullptr, editor_state->showStatus)) { + editor_state->showStatus = not editor_state->showStatus; } - if (ImGui::MenuItem("History", nullptr, editorState->showHistory)) { - editorState->showHistory = not editorState->showHistory; + if (ImGui::MenuItem("History", nullptr, editor_state->showHistory)) { + editor_state->showHistory = not editor_state->showHistory; } ImGui::EndMenu(); } - if (ImGui::BeginMenu("Settings", editorState.has_value())) { + if (ImGui::BeginMenu("Settings", editor_state.has_value())) { if (ImGui::MenuItem("Sound")) { - editorState->showSoundSettings = true; + editor_state->showSoundSettings = true; } if (ImGui::MenuItem("Linear View")) { - editorState->linearView.shouldDisplaySettings = true; + editor_state->linearView.shouldDisplaySettings = true; } if (ImGui::BeginMenu("Marker")) { int i = 0; diff --git a/src/music_state.cpp b/src/music_state.cpp new file mode 100644 index 0000000..786af71 --- /dev/null +++ b/src/music_state.cpp @@ -0,0 +1,29 @@ +#include "music_state.hpp" +#include "toolbox.hpp" + + +void MusicState::set_speed(int newMusicSpeed) { + speed = std::clamp(newMusicSpeed, 1, 20); + music.setPitch(speed / 10.f); +} + +void MusicState::speed_up() { + set_speed(speed + 1); +} + +void MusicState::speed_down() { + set_speed(speed - 1); +} + +void MusicState::set_volume(int newMusicVolume) { + volume = std::clamp(newMusicVolume, 0, 10); + music.setVolume(Toolbox::convertVolumeToNormalizedDB(volume)*100.f); +} + +void MusicState::volume_up() { + set_volume(volume + 1); +} + +void MusicState::volume_down() { + set_volume(volume - 1); +} \ No newline at end of file diff --git a/src/music_state.hpp b/src/music_state.hpp new file mode 100644 index 0000000..2804bfd --- /dev/null +++ b/src/music_state.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include "precise_music.hpp" + +struct MusicState { + explicit MusicState(const std::filesystem::path& path) : music(path) {}; + PreciseMusic music; + int volume = 10; // 0 -> 10 + void set_volume(int newMusicVolume); + void volume_up(); + void volume_down(); + + int speed = 10; // 1 -> 20 + void set_speed(int newMusicSpeed); + void speed_up(); + void speed_down(); +}; \ No newline at end of file diff --git a/src/precise_music.hpp b/src/precise_music.hpp index 757c2eb..8e6a837 100644 --- a/src/precise_music.hpp +++ b/src/precise_music.hpp @@ -9,7 +9,7 @@ #include "AL/alext.h" struct PreciseMusic : sf::Music { - PreciseMusic(const std::filesystem::path& path); + explicit PreciseMusic(const std::filesystem::path& path); std::array alSecOffsetLatencySoft() const; sf::Time getPrecisePlayingOffset() const; sf::Time lag = sf::Time::Zero; diff --git a/src/preferences.hpp b/src/preferences.hpp index 307aa55..b39ecdd 100644 --- a/src/preferences.hpp +++ b/src/preferences.hpp @@ -18,5 +18,7 @@ public: std::string marker; MarkerEndingState markerEndingState; - const std::filesystem::path file_path; + +private: + std::filesystem::path file_path; }; diff --git a/src/special_numeric_types.hpp b/src/special_numeric_types.hpp new file mode 100644 index 0000000..4eec5e9 --- /dev/null +++ b/src/special_numeric_types.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include +#include + +using Fraction = boost::multiprecision::mpq_rational; +using Decimal = boost::multiprecision::cpp_dec_float_50; \ No newline at end of file diff --git a/src/variant_visitor.hpp b/src/variant_visitor.hpp new file mode 100644 index 0000000..5e9b537 --- /dev/null +++ b/src/variant_visitor.hpp @@ -0,0 +1,24 @@ + +#pragma once + +/* +Dark template magic from https://www.modernescpp.com/index.php/visiting-a-std-variant-with-the-overload-pattern + +Usage : + + std::variant var = 2017; + + auto TypeOfIntegral = VariantVisitor { + [](char) { return "char"; }, + [](int) { return "int"; }, + [](auto) { return "unknown type"; }, + }; + + std::visit(TypeOfIntegral, var); + +*/ +template +struct VariantVisitor : Ts ... { + using Ts::operator() ...; +}; +template VariantVisitor(Ts...) -> VariantVisitor; \ No newline at end of file diff --git a/src/widgets/density_graph.cpp b/src/widgets/density_graph.cpp index 6596f31..df4b836 100644 --- a/src/widgets/density_graph.cpp +++ b/src/widgets/density_graph.cpp @@ -20,17 +20,12 @@ DensityGraph::DensityGraph(std::filesystem::path assets) : collision_square.setTextureRect({496, 270, 6, 6}); } -void DensityGraph::update(int height, float chartRuntime, Chart& chart, float BPM, int resolution) { - this->computeDensities(height, chartRuntime, chart, BPM, resolution); - this->updateGraphTexture(); +void DensityGraph::update(int height, const better::Chart& chart, const sf::Time& from, const sf::Time& to) { + this->compute_densities(height, chart, from, to); + this->update_graph_texture(); } -void DensityGraph::computeDensities(int height, float chartRuntime, Chart& chart, float BPM, int resolution) { - auto ticksToSeconds = [BPM, resolution](int ticks) -> float { - return (60.f * ticks) / (BPM * resolution); - }; - int ticks_threshold = static_cast((1.f / 60.f) * BPM * resolution); - +void DensityGraph::compute_densities(int height, const better::Chart& chart, const sf::Time& from, const sf::Time& to) { last_height = height; // minus the slider cursor thiccccnesss @@ -41,13 +36,13 @@ void DensityGraph::computeDensities(int height, float chartRuntime, Chart& chart if (sections >= 1) { densities.resize(static_cast(sections), {0, false}); - float section_length = chartRuntime / sections; - last_section_length = section_length; + sf::Time section_duration = (to - from) / static_cast(sections); + last_section_duration = section_duration; - for (auto const& note : chart.Notes) { + for (auto const& note : chart.notes) { auto note_time = note.getTiming(); - auto note_seconds = ticksToSeconds(note_time); - auto float_section = note_seconds / section_length; + auto note_seconds = chart.timing.time_at(ticksToSeconds(note_time)); + auto float_section = note_seconds / section_duration; auto int_section = static_cast(float_section); auto section = std::clamp(int_section, 0, sections - 1); densities.at(section).density += 1; @@ -59,7 +54,7 @@ void DensityGraph::computeDensities(int height, float chartRuntime, Chart& chart } } -void DensityGraph::updateGraphTexture() { +void DensityGraph::update_graph_texture() { if (!graph.create(45, static_cast(*last_height))) { std::cerr << "Unable to create DensityGraph's RenderTexture"; throw std::runtime_error( diff --git a/src/widgets/density_graph.hpp b/src/widgets/density_graph.hpp index fe802ca..53e4ca2 100644 --- a/src/widgets/density_graph.hpp +++ b/src/widgets/density_graph.hpp @@ -5,7 +5,7 @@ #include #include -#include "../chart.hpp" +#include "../better_song.hpp" class DensityGraph { public: @@ -26,13 +26,13 @@ public: std::vector densities; std::optional last_height; - std::optional last_section_length; + std::optional last_section_duration; - void update(int height, float chartRuntime, Chart& chart, float BPM, int resolution); + void update(int height, const better::Chart& chart, const sf::Time& from, const sf::Time& to); private: const std::filesystem::path texture_path; - void computeDensities(int height, float chartRuntime, Chart& chart, float BPM, int resolution); - void updateGraphTexture(); + void compute_densities(int height, const better::Chart& chart, const sf::Time& from, const sf::Time& to); + void update_graph_texture(); }; diff --git a/src/widgets/linear_view.cpp b/src/widgets/linear_view.cpp index 355498c..cd41637 100644 --- a/src/widgets/linear_view.cpp +++ b/src/widgets/linear_view.cpp @@ -51,7 +51,7 @@ void LinearView::resize(unsigned int width, unsigned int height) { } void LinearView::update( - const std::optional& chart, + const std::optional& chart, const sf::Time& playbackPosition, const float& ticksAtPlaybackPosition, const float& BPM, @@ -142,7 +142,7 @@ void LinearView::update( static_cast(SecondsToTicks.transform( PixelsToSeconds.transform(static_cast(y)) + 0.5f))); - auto notes = chart->ref.getVisibleNotesBetween(lower_bound_ticks, upper_bound_ticks); + auto notes = chart->chart.getVisibleNotesBetween(lower_bound_ticks, upper_bound_ticks); auto currentLongNote = chart->makeCurrentLongNote(); if (currentLongNote) { notes.insert(*currentLongNote); @@ -200,16 +200,16 @@ void LinearView::update( * Draw the timeSelection */ selection.setSize({static_cast(x) - 80.f, 0.f}); - if (std::holds_alternative(chart->timeSelection)) { - unsigned int ticks = std::get(chart->timeSelection); + if (std::holds_alternative(chart->time_selection)) { + unsigned int ticks = std::get(chart->time_selection); float selection_y = PixelsToTicks.backwards_transform(static_cast(ticks)); if (selection_y > 0.f and selection_y < static_cast(y)) { selection.setPosition(50.f, selection_y); view.draw(selection); } - } else if (std::holds_alternative(chart->timeSelection)) { - const auto& ts = std::get(chart->timeSelection); + } else if (std::holds_alternative(chart->time_selection)) { + const auto& ts = std::get(chart->time_selection); float selection_start_y = PixelsToTicks.backwards_transform(static_cast(ts.start)); float selection_end_y = PixelsToTicks.backwards_transform( diff --git a/src/widgets/linear_view.hpp b/src/widgets/linear_view.hpp index e887f72..2a87ebd 100644 --- a/src/widgets/linear_view.hpp +++ b/src/widgets/linear_view.hpp @@ -4,7 +4,7 @@ #include #include -#include "../chart_with_history.hpp" +#include "../chart_state.hpp" #include "../time_selection.hpp" #include "../toolbox.hpp" @@ -15,7 +15,7 @@ public: sf::RenderTexture view; void update( - const std::optional& chart, + const std::optional& chart, const sf::Time& playbackPosition, const float& ticksAtPlaybackPosition, const float& BPM,