Density Graph WIP again
This commit is contained in:
parent
8bea6a4226
commit
9bbcff7cf5
41
meson.build
41
meson.build
@ -24,40 +24,40 @@ sources = [
|
||||
'include/imgui/imgui_draw.cpp',
|
||||
'include/imgui/imgui_widgets.cpp',
|
||||
'include/imgui-sfml/imgui-SFML.cpp',
|
||||
'src/Main.cpp',
|
||||
'src/Data/Buttons.hpp',
|
||||
'src/Data/Buttons.cpp',
|
||||
'src/Data/Chart.cpp',
|
||||
'src/Data/Chart.hpp',
|
||||
'src/Data/KeyMapping.hpp',
|
||||
'src/Data/KeyMapping.cpp',
|
||||
'src/Data/Note.hpp',
|
||||
'src/Data/Preferences.hpp',
|
||||
'src/Data/Score.hpp',
|
||||
'src/Data/SongList.hpp',
|
||||
'src/Data/SongList.cpp',
|
||||
# 'src/Screens/Gameplay.hpp',
|
||||
'src/Screens/MusicSelect/ButtonHighlight.hpp',
|
||||
'src/Screens/MusicSelect/ButtonHighlight.cpp',
|
||||
# 'src/Screens/MusicSelect/DensityGraph.hpp',
|
||||
# 'src/Screens/MusicSelect/DensityGraph.cpp',
|
||||
'src/Screens/MusicSelect/MusicSelect.hpp',
|
||||
'src/Screens/MusicSelect/MusicSelect.cpp',
|
||||
'src/Screens/MusicSelect/Panel.hpp',
|
||||
'src/Screens/MusicSelect/Panel.cpp',
|
||||
'src/Screens/MusicSelect/SharedResources.hpp',
|
||||
'src/Screens/MusicSelect/SharedResources.cpp',
|
||||
'src/Screens/MusicSelect/SongInfo.hpp',
|
||||
'src/Screens/MusicSelect/SongInfo.cpp',
|
||||
'src/Screens/MusicSelect/Ribbon.hpp',
|
||||
'src/Screens/MusicSelect/Ribbon.cpp',
|
||||
# 'src/Screens/Result.hpp',
|
||||
'src/Data/Song.hpp',
|
||||
'src/Data/Song.cpp',
|
||||
'src/Resources/TextureCache.cpp',
|
||||
'src/Resources/TextureCache.hpp',
|
||||
# 'src/Resources/CoverAtlas.hpp',
|
||||
# 'src/Resources/CoverAtlas.cpp',
|
||||
# 'src/Screens/Gameplay.hpp',
|
||||
'src/Screens/MusicSelect/ButtonHighlight.hpp',
|
||||
'src/Screens/MusicSelect/ButtonHighlight.cpp',
|
||||
'src/Screens/MusicSelect/DensityGraph.hpp',
|
||||
'src/Screens/MusicSelect/DensityGraph.cpp',
|
||||
'src/Screens/MusicSelect/MusicSelect.hpp',
|
||||
'src/Screens/MusicSelect/MusicSelect.cpp',
|
||||
'src/Screens/MusicSelect/Panel.hpp',
|
||||
'src/Screens/MusicSelect/Panel.cpp',
|
||||
'src/Screens/MusicSelect/Ribbon.hpp',
|
||||
'src/Screens/MusicSelect/Ribbon.cpp',
|
||||
'src/Screens/MusicSelect/SharedResources.hpp',
|
||||
'src/Screens/MusicSelect/SharedResources.cpp',
|
||||
'src/Screens/MusicSelect/SongInfo.hpp',
|
||||
'src/Screens/MusicSelect/SongInfo.cpp',
|
||||
# 'src/Screens/Result.hpp',
|
||||
'src/Toolkit/AffineTransform.hpp',
|
||||
'src/Toolkit/Debuggable.hpp',
|
||||
'src/Toolkit/Cache.hpp',
|
||||
'src/Toolkit/Debuggable.hpp',
|
||||
'src/Toolkit/EasingFunctions.hpp',
|
||||
'src/Toolkit/EasingFunctions.cpp',
|
||||
'src/Toolkit/HSL.hpp',
|
||||
@ -65,6 +65,7 @@ sources = [
|
||||
'src/Toolkit/NormalizedOrigin.hpp',
|
||||
'src/Toolkit/QuickRNG.hpp',
|
||||
'src/Toolkit/QuickRNG.cpp',
|
||||
'src/Main.cpp',
|
||||
]
|
||||
|
||||
executable(
|
||||
|
@ -5,24 +5,28 @@
|
||||
#include "Buttons.hpp"
|
||||
|
||||
namespace Data {
|
||||
Chart::Chart(const stepland::memon& memon, const std::string& chart_name) {
|
||||
auto it = memon.charts.find(chart_name);
|
||||
Chart::Chart(const stepland::memon& memon, const std::string& difficulty) {
|
||||
auto it = memon.charts.find(difficulty);
|
||||
if (it == memon.charts.end()) {
|
||||
throw std::invalid_argument("Memon file has no "+chart_name+" chart");
|
||||
throw std::invalid_argument("Memon file has no "+difficulty+" chart");
|
||||
}
|
||||
auto [_, chart] = *it;
|
||||
level = static_cast<unsigned int>(chart.level);
|
||||
level = chart.level;
|
||||
resolution = static_cast<std::size_t>(chart.resolution);
|
||||
Toolkit::AffineTransform<float> memon_timing_to_300Hz(
|
||||
0.f, static_cast<float>(chart.resolution),
|
||||
-memon.offset*300.f, (-memon.offset+60.f/memon.BPM)*300.f
|
||||
);
|
||||
Toolkit::AffineTransform<float> memon_timing_to_300Hz_proportional(
|
||||
0.f, static_cast<float>(chart.resolution),
|
||||
0.f, (60.f/memon.BPM)*300.f
|
||||
);
|
||||
for (auto &¬e : chart.notes) {
|
||||
auto timing = static_cast<std::size_t>(memon_timing_to_300Hz.transform(note.get_timing()));
|
||||
auto position = static_cast<Button>(note.get_pos());
|
||||
auto length = static_cast<std::size_t>(note.get_length());
|
||||
auto length = static_cast<std::size_t>(memon_timing_to_300Hz_proportional.transform(note.get_length()));
|
||||
auto tail = convert_memon_tail(position, note.get_tail_pos());
|
||||
notes.emplace(timing, position, length, tail);
|
||||
notes.insert({timing, position, length, tail});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,8 +10,8 @@
|
||||
|
||||
namespace Data {
|
||||
struct Chart {
|
||||
explicit Chart(const stepland::memon& memon, const std::string& chart);
|
||||
unsigned int level;
|
||||
Chart(const stepland::memon& memon, const std::string& difficulty);
|
||||
int level;
|
||||
std::set<Note> notes;
|
||||
std::size_t resolution;
|
||||
};
|
||||
|
@ -4,10 +4,10 @@
|
||||
|
||||
namespace Data {
|
||||
struct Note {
|
||||
// Timing is stored as ticks on a 300Hz clock
|
||||
std::size_t timing;
|
||||
// Timing is stored as ticks on a 300Hz clock starting at the begging of the audio
|
||||
long int timing;
|
||||
Button position;
|
||||
// zero is standard note
|
||||
// zero length means it's a standard note
|
||||
std::size_t length;
|
||||
Button tail;
|
||||
|
||||
|
133
src/Data/Song.cpp
Normal file
133
src/Data/Song.cpp
Normal file
@ -0,0 +1,133 @@
|
||||
#include "Song.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
|
||||
#include <memon.hpp>
|
||||
|
||||
namespace fs = ghc::filesystem;
|
||||
|
||||
namespace Data {
|
||||
|
||||
bool cmp_dif_name::operator()(const std::string &a, const std::string &b) const {
|
||||
if (dif_names.find(a) != dif_names.end()) {
|
||||
if (dif_names.find(b) != dif_names.end()) {
|
||||
return dif_names.find(a)->second < dif_names.find(b)->second;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (dif_names.find(b) != dif_names.end()) {
|
||||
return false;
|
||||
} else {
|
||||
return a < b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<fs::path> Song::full_cover_path() const {
|
||||
if (cover) {
|
||||
return folder/cover.value();
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<fs::path> Song::full_audio_path() const {
|
||||
if (audio) {
|
||||
return folder/audio.value();
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
SongList::SongList() :
|
||||
songs()
|
||||
{
|
||||
fs::path song_folder = "songs/";
|
||||
|
||||
if (fs::exists(song_folder) and fs::is_directory(song_folder)) {
|
||||
for (const auto& dir_item : fs::directory_iterator("songs/")) {
|
||||
if (dir_item.is_directory()) {
|
||||
songs.splice(songs.end(), recursiveSongSearch(dir_item.path()));
|
||||
}
|
||||
}
|
||||
}
|
||||
std::cout << "Loaded Data::SongList, found " << songs.size() << " songs" << std::endl;
|
||||
}
|
||||
|
||||
std::list<std::shared_ptr<Song>> recursiveSongSearch(fs::path song_or_pack) {
|
||||
std::list<std::shared_ptr<Song>> res;
|
||||
|
||||
// First try : any .memo file in the folder ?
|
||||
fs::directory_iterator folder_memo{song_or_pack};
|
||||
if (
|
||||
std::any_of(
|
||||
fs::begin(folder_memo),
|
||||
fs::end(folder_memo),
|
||||
[](const fs::directory_entry& de) {return de.path().extension() == ".memo";}
|
||||
)
|
||||
) {
|
||||
throw std::invalid_argument("jujube does not support .memo files for now ...");
|
||||
}
|
||||
|
||||
// Second try : any .memon file in the folder ?
|
||||
// if yes get the first one
|
||||
fs::directory_iterator folder_memon{song_or_pack};
|
||||
auto memon_path = std::find_if(
|
||||
fs::begin(folder_memon),
|
||||
fs::end(folder_memon),
|
||||
[](const fs::directory_entry& de) {return de.path().extension() == ".memon";}
|
||||
);
|
||||
if (memon_path != fs::end(folder_memon)) {
|
||||
res.push_back(std::make_shared<MemonSong>(memon_path->path()));
|
||||
return res;
|
||||
}
|
||||
// Nothing found : recurse in subfolders
|
||||
for (auto& p : fs::directory_iterator(song_or_pack)) {
|
||||
if (p.is_directory()) {
|
||||
res.splice(res.end(), recursiveSongSearch(p));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
MemonSong::MemonSong(const fs::path& t_memon_path) :
|
||||
memon_path(t_memon_path)
|
||||
{
|
||||
auto song_folder = t_memon_path.parent_path();
|
||||
folder = song_folder;
|
||||
stepland::memon m;
|
||||
{
|
||||
std::ifstream file(t_memon_path);
|
||||
file >> m;
|
||||
}
|
||||
this->title = m.song_title;
|
||||
this->artist = m.artist;
|
||||
if (not m.album_cover_path.empty()) {
|
||||
this->cover.emplace(m.album_cover_path);
|
||||
}
|
||||
if (not m.music_path.empty()) {
|
||||
this->audio.emplace(m.music_path);
|
||||
}
|
||||
for (const auto& [difficulty, chart] : m.charts) {
|
||||
this->chart_levels[difficulty] = chart.level;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<Chart> MemonSong::get_chart(const std::string& difficulty) const {
|
||||
stepland::memon m;
|
||||
{
|
||||
std::ifstream file(memon_path);
|
||||
file >> m;
|
||||
}
|
||||
auto chart = m.charts.find(difficulty);
|
||||
if (chart == m.charts.end()) {
|
||||
return {};
|
||||
} else {
|
||||
return Chart(m, difficulty);
|
||||
}
|
||||
}
|
||||
}
|
@ -4,9 +4,13 @@
|
||||
#include <iterator>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <variant>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "Chart.hpp"
|
||||
|
||||
namespace fs = ghc::filesystem;
|
||||
|
||||
@ -22,8 +26,6 @@ namespace Data {
|
||||
|
||||
// Basic metadata about a song
|
||||
struct Song {
|
||||
Song() = default;
|
||||
explicit Song(fs::path song_folder);
|
||||
fs::path folder;
|
||||
std::string title;
|
||||
std::string artist;
|
||||
@ -35,24 +37,39 @@ namespace Data {
|
||||
std::map<std::string, unsigned int, cmp_dif_name> chart_levels;
|
||||
|
||||
std::optional<fs::path> full_cover_path() const;
|
||||
std::optional<fs::path> full_audio_path() const;
|
||||
|
||||
virtual std::optional<Chart> get_chart(const std::string& difficulty) const = 0;
|
||||
|
||||
static bool sort_by_title(const Data::Song& a, const Data::Song& b) {
|
||||
return a.title < b.title;
|
||||
}
|
||||
virtual ~Song() = default;
|
||||
};
|
||||
|
||||
struct MemonSong : public Song {
|
||||
explicit MemonSong(const fs::path& memon_path);
|
||||
std::optional<Chart> get_chart(const std::string& difficulty) const;
|
||||
private:
|
||||
fs::path memon_path;
|
||||
};
|
||||
|
||||
/*
|
||||
struct MemoSong : public Song {
|
||||
explicit MemoSong(const std::unordered_map<std::string, fs::path>& memo_paths);
|
||||
private:
|
||||
std::unordered_map<std::string, fs::path> memo_paths;
|
||||
};
|
||||
*/
|
||||
|
||||
// Class holding all the necessary song data to run the Music Select screen
|
||||
class SongList {
|
||||
public:
|
||||
SongList();
|
||||
std::vector<Song> songs;
|
||||
std::list<std::shared_ptr<Song>> songs;
|
||||
};
|
||||
|
||||
// Returns the folders conscidered to contain a valid song
|
||||
// classic memo files should have the .memo extension
|
||||
// .memon files also accepted
|
||||
const std::vector<fs::path> getSongFolders();
|
||||
std::list<fs::path> recursiveSongSearch(fs::path song_or_pack);
|
||||
const std::vector<fs::path> getMemoFiles(fs::path song_folder);
|
||||
const std::vector<fs::path> getMemonFiles(fs::path song_folder);
|
||||
std::list<std::shared_ptr<Song>> recursiveSongSearch(fs::path song_or_pack);
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
#include "SongList.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
|
||||
#include <memon.hpp>
|
||||
|
||||
namespace fs = ghc::filesystem;
|
||||
|
||||
namespace Data {
|
||||
|
||||
bool cmp_dif_name::operator()(const std::string &a, const std::string &b) const {
|
||||
if (dif_names.find(a) != dif_names.end()) {
|
||||
if (dif_names.find(b) != dif_names.end()) {
|
||||
return dif_names.find(a)->second < dif_names.find(b)->second;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (dif_names.find(b) != dif_names.end()) {
|
||||
return false;
|
||||
} else {
|
||||
return a < b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<fs::path> Song::full_cover_path() const {
|
||||
if (cover) {
|
||||
return folder/cover.value();
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
SongList::SongList::SongList() {
|
||||
|
||||
// Loading all song metadata
|
||||
for (const auto& folder : getSongFolders()) {
|
||||
try {
|
||||
songs.emplace_back(folder);
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Exception while parsing song folder "
|
||||
<< folder.string() << " : " << e.what() << '\n';
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const std::vector<fs::path> getSongFolders() {
|
||||
|
||||
std::vector<fs::path> potential_song_folders;
|
||||
fs::path song_folder = "songs/";
|
||||
|
||||
if (fs::exists(song_folder) and fs::is_directory(song_folder)) {
|
||||
for (const auto& dir_item : fs::directory_iterator("songs/")) {
|
||||
if (dir_item.is_directory()) {
|
||||
for (auto& path : recursiveSongSearch(dir_item.path())) {
|
||||
potential_song_folders.push_back(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return potential_song_folders;
|
||||
}
|
||||
|
||||
std::list<fs::path> recursiveSongSearch(fs::path song_or_pack) {
|
||||
std::list<fs::path> res;
|
||||
fs::directory_iterator folder{song_or_pack};
|
||||
if (
|
||||
std::any_of(
|
||||
fs::begin(folder),
|
||||
fs::end(folder),
|
||||
[](const fs::directory_entry& de) {
|
||||
return de.path().extension() == ".memo" or
|
||||
de.path().extension() == ".memon";
|
||||
}
|
||||
)
|
||||
) {
|
||||
res.push_back(song_or_pack);
|
||||
return res;
|
||||
} else {
|
||||
for (auto& p : fs::directory_iterator(song_or_pack)) {
|
||||
if (p.is_directory()) {
|
||||
res.splice(res.end(), recursiveSongSearch(p));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
Song::Song(fs::path song_folder) :
|
||||
folder(song_folder)
|
||||
{
|
||||
|
||||
// .memon ?
|
||||
auto memon_files = getMemonFiles(song_folder);
|
||||
if (not memon_files.empty()) {
|
||||
if (memon_files.size() > 1) {
|
||||
throw std::invalid_argument("Multiple .memon files");
|
||||
} else {
|
||||
stepland::memon m;
|
||||
std::ifstream file(memon_files.back());
|
||||
file >> m;
|
||||
this->title = m.song_title;
|
||||
this->artist = m.artist;
|
||||
if (not m.album_cover_path.empty()) {
|
||||
this->cover.emplace(m.album_cover_path);
|
||||
}
|
||||
if (not m.music_path.empty()) {
|
||||
this->audio.emplace(m.music_path);
|
||||
}
|
||||
for (const auto& [difficulty, chart] : m.charts) {
|
||||
this->chart_levels[difficulty] = chart.level;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// .memo ?
|
||||
auto memo_files = getMemoFiles(song_folder);
|
||||
if (not memo_files.empty()) {
|
||||
throw std::invalid_argument("jujube does not support .memo files for now ...");
|
||||
} else {
|
||||
throw std::invalid_argument("No valid file found in song folder");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<fs::path> getMemoFiles(fs::path song_folder) {
|
||||
std::vector<fs::path> res;
|
||||
for (const auto& p : fs::directory_iterator(song_folder)) {
|
||||
if (p.path().extension() == ".memo") {
|
||||
res.push_back(p.path());
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
const std::vector<fs::path> getMemonFiles(fs::path song_folder) {
|
||||
std::vector<fs::path> res;
|
||||
for (const auto& p : fs::directory_iterator(song_folder)) {
|
||||
if (p.path().extension() == ".memon") {
|
||||
res.push_back(p.path());
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <cereal/archives/json.hpp>
|
||||
|
||||
#include "Data/SongList.hpp"
|
||||
#include "Data/Song.hpp"
|
||||
#include "Data/Preferences.hpp"
|
||||
// #include "Data/Chart.hpp"
|
||||
// #include "Data/Score.hpp"
|
||||
|
49
src/Screens/MusicSelect/DensityGraph.cpp
Normal file
49
src/Screens/MusicSelect/DensityGraph.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
#include "DensityGraph.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
#include <SFML/Audio.hpp>
|
||||
|
||||
namespace Data {
|
||||
|
||||
DensityGraph compute_density_graph_1(const SongDifficulty& sd) {
|
||||
return compute_density_graph_2(sd.song, sd.difficulty);
|
||||
}
|
||||
|
||||
DensityGraph compute_density_graph_2(const Song& song, const std::string& difficulty) {
|
||||
auto c = song.get_chart(difficulty);
|
||||
if (not c.has_value()) {
|
||||
throw std::invalid_argument("Song "+song.title+" has no "+difficulty+" chart");
|
||||
}
|
||||
if (not song.audio.has_value()) {
|
||||
return compute_density_graph_3(*c, c->notes.begin()->timing, c->notes.rbegin()->timing);
|
||||
}
|
||||
sf::Music m;
|
||||
if (not m.openFromFile(*song.full_audio_path())) {
|
||||
return compute_density_graph_3(*c, c->notes.begin()->timing, c->notes.rbegin()->timing);
|
||||
}
|
||||
return compute_density_graph_3(
|
||||
*c,
|
||||
std::min(0L, c->notes.begin()->timing),
|
||||
std::max(c->notes.rbegin()->timing, static_cast<long>(m.getDuration().asMilliseconds()*3/10))
|
||||
);
|
||||
}
|
||||
|
||||
DensityGraph compute_density_graph_3(const Chart& chart, long start, long end) {
|
||||
DensityGraph d{};
|
||||
if (start != end) {
|
||||
for (auto &¬e : chart.notes) {
|
||||
auto index = (note.timing-start)*115/(end-start);
|
||||
d.at(index) += 1;
|
||||
}
|
||||
std::replace_if(
|
||||
d.begin(),
|
||||
d.end(),
|
||||
std::bind(std::greater<unsigned int>(), std::placeholders::_1, 8),
|
||||
8
|
||||
);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
}
|
41
src/Screens/MusicSelect/DensityGraph.hpp
Normal file
41
src/Screens/MusicSelect/DensityGraph.hpp
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <tuple>
|
||||
|
||||
#include "../../Data/Chart.hpp"
|
||||
#include "../../Data/Song.hpp"
|
||||
#include "../../Toolkit/Cache.hpp"
|
||||
|
||||
namespace Data {
|
||||
using DensityGraph = std::array<unsigned int, 115>;
|
||||
|
||||
struct SongDifficulty {
|
||||
const Song& song;
|
||||
const std::string& difficulty;
|
||||
|
||||
bool operator==(const SongDifficulty &other) const {
|
||||
return (
|
||||
song.folder == other.song.folder and
|
||||
difficulty == other.difficulty
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
DensityGraph compute_density_graph_1(const SongDifficulty& sd);
|
||||
DensityGraph compute_density_graph_2(const Song& song, const std::string& difficulty);
|
||||
DensityGraph compute_density_graph_3(const Chart& chart, long start, long end);
|
||||
|
||||
using DensityGraphCache = Toolkit::Cache<SongDifficulty, DensityGraph, &compute_density_graph_1>;
|
||||
}
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<Data::SongDifficulty> {
|
||||
std::size_t operator()(const Data::SongDifficulty& sd) const {
|
||||
auto song_hash = std::hash<std::string>()(sd.song.folder.string());
|
||||
song_hash ^= std::hash<std::string>()(sd.difficulty) + 0x9e3779b9 + (song_hash<<6) + (song_hash>>2);
|
||||
return song_hash;
|
||||
}
|
||||
};
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
|
||||
#include <SFML/Window.hpp>
|
||||
|
||||
#include "../../Data/SongList.hpp"
|
||||
#include "../../Data/Song.hpp"
|
||||
#include "../../Data/Chart.hpp"
|
||||
#include "../../Data/KeyMapping.hpp"
|
||||
#include "../../Toolkit/AffineTransform.hpp"
|
||||
|
@ -69,22 +69,22 @@ namespace MusicSelect {
|
||||
void SongPanel::click(Ribbon& ribbon, std::size_t from_button_index) {
|
||||
if (selected_chart.has_value()) {
|
||||
// The song was already selected : look for the next chart in order
|
||||
auto it = m_song.chart_levels.upper_bound(*selected_chart);
|
||||
if (it != m_song.chart_levels.cend()) {
|
||||
auto it = m_song->chart_levels.upper_bound(*selected_chart);
|
||||
if (it != m_song->chart_levels.cend()) {
|
||||
selected_chart = it->first;
|
||||
} else {
|
||||
selected_chart = m_song.chart_levels.cbegin()->first;
|
||||
selected_chart = m_song->chart_levels.cbegin()->first;
|
||||
}
|
||||
m_resources.selected_panel->last_click.restart();
|
||||
m_resources.selected_panel->is_first_click = false;
|
||||
} else {
|
||||
// Look for the first chart with dif greater or equal to the last select one
|
||||
// or else select the first chart
|
||||
auto it = m_song.chart_levels.lower_bound(m_resources.get_last_selected_chart());
|
||||
if (it != m_song.chart_levels.cend()) {
|
||||
auto it = m_song->chart_levels.lower_bound(m_resources.get_last_selected_difficulty());
|
||||
if (it != m_song->chart_levels.cend()) {
|
||||
selected_chart = it->first;
|
||||
} else {
|
||||
selected_chart = m_song.chart_levels.cbegin()->first;
|
||||
selected_chart = m_song->chart_levels.cbegin()->first;
|
||||
}
|
||||
// The song was not selected before : first unselect the last one
|
||||
if (m_resources.selected_panel.has_value()) {
|
||||
@ -100,7 +100,7 @@ namespace MusicSelect {
|
||||
|
||||
std::optional<ChartSelection> SongPanel::get_selected_chart() const {
|
||||
if (selected_chart) {
|
||||
return ChartSelection{m_song, *selected_chart};
|
||||
return ChartSelection{*m_song, *selected_chart};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
@ -108,11 +108,11 @@ namespace MusicSelect {
|
||||
|
||||
void SongPanel::draw(sf::RenderTarget& target, sf::RenderStates states) const {
|
||||
states.transform *= getTransform();
|
||||
auto last_selected_chart = m_resources.get_last_selected_chart();
|
||||
auto last_selected_chart = m_resources.get_last_selected_difficulty();
|
||||
// We should gray out the panel if the currently selected difficulty doesn't exist for this song
|
||||
bool should_be_grayed_out = m_song.chart_levels.find(last_selected_chart) == m_song.chart_levels.end();
|
||||
if (m_song.cover) {
|
||||
auto loaded_texture = m_resources.covers.async_get(m_song.folder/m_song.cover.value());
|
||||
bool should_be_grayed_out = m_song->chart_levels.find(last_selected_chart) == m_song->chart_levels.end();
|
||||
if (m_song->cover) {
|
||||
auto loaded_texture = m_resources.covers.async_get(m_song->folder/m_song->cover.value());
|
||||
if (loaded_texture) {
|
||||
sf::Sprite cover{*(loaded_texture->texture)};
|
||||
auto alpha = static_cast<std::uint8_t>(
|
||||
@ -140,7 +140,7 @@ namespace MusicSelect {
|
||||
}
|
||||
target.draw(chart_dif_badge, states);
|
||||
if (not should_be_grayed_out) {
|
||||
auto dif = m_song.chart_levels.at(last_selected_chart);
|
||||
auto dif = m_song->chart_levels.at(last_selected_chart);
|
||||
sf::Text dif_label{
|
||||
std::to_string(dif),
|
||||
m_resources.noto_sans_medium,
|
||||
@ -153,7 +153,7 @@ namespace MusicSelect {
|
||||
}
|
||||
sf::Text song_title;
|
||||
song_title.setFont(m_resources.noto_sans_medium);
|
||||
song_title.setString(m_song.title);
|
||||
song_title.setString(m_song->title);
|
||||
song_title.setCharacterSize(static_cast<unsigned int>(0.06875f*get_size()));
|
||||
song_title.setFillColor(sf::Color::White);
|
||||
auto song_title_bounds = song_title.getLocalBounds();
|
||||
|
@ -6,7 +6,7 @@
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <SFML/Window.hpp>
|
||||
|
||||
#include "../../Data/SongList.hpp"
|
||||
#include "../../Data/Song.hpp"
|
||||
#include "../../Toolkit/AffineTransform.hpp"
|
||||
#include "SharedResources.hpp"
|
||||
|
||||
@ -64,7 +64,7 @@ namespace MusicSelect {
|
||||
|
||||
struct ChartSelection {
|
||||
const Data::Song& song;
|
||||
const std::string& chart;
|
||||
const std::string& difficulty;
|
||||
};
|
||||
|
||||
class SelectablePanel : public Panel {
|
||||
@ -77,13 +77,13 @@ namespace MusicSelect {
|
||||
|
||||
class SongPanel final : public SelectablePanel {
|
||||
public:
|
||||
explicit SongPanel(SharedResources& resources, const Data::Song& t_song) : SelectablePanel(resources), m_song(t_song) {};
|
||||
explicit SongPanel(SharedResources& resources, const std::shared_ptr<const Data::Song>& t_song) : SelectablePanel(resources), m_song(t_song) {};
|
||||
void click(Ribbon& ribbon, std::size_t from_button_index) override;
|
||||
void unselect() override;
|
||||
std::optional<ChartSelection> get_selected_chart() const override;
|
||||
private:
|
||||
void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
|
||||
const Data::Song& m_song;
|
||||
std::shared_ptr<const Data::Song> m_song;
|
||||
const Toolkit::AffineTransform<float> m_seconds_to_alpha{0.0f, 0.15f, 0.f, 255.f};
|
||||
std::optional<std::string> selected_chart;
|
||||
};
|
||||
|
@ -10,7 +10,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "Panel.hpp"
|
||||
#include "../../Data/SongList.hpp"
|
||||
#include "../../Data/Song.hpp"
|
||||
#include "../../Toolkit/QuickRNG.hpp"
|
||||
|
||||
|
||||
@ -69,15 +69,19 @@ namespace MusicSelect {
|
||||
}
|
||||
|
||||
void Ribbon::title_sort(const Data::SongList &song_list) {
|
||||
std::vector<std::reference_wrapper<const Data::Song>> songs;
|
||||
std::vector<std::shared_ptr<const Data::Song>> songs;
|
||||
for (auto &&song : song_list.songs) {
|
||||
songs.push_back(std::cref(song));
|
||||
songs.push_back(song);
|
||||
}
|
||||
std::sort(songs.begin(), songs.end(), Data::Song::sort_by_title);
|
||||
std::sort(
|
||||
songs.begin(),
|
||||
songs.end(),
|
||||
[](std::shared_ptr<const Data::Song> a, std::shared_ptr<const Data::Song> b){return Data::Song::sort_by_title(*a, *b);}
|
||||
);
|
||||
std::map<std::string, std::vector<std::shared_ptr<Panel>>> categories;
|
||||
for (const auto &song : songs) {
|
||||
if (song.get().title.size() > 0) {
|
||||
char letter = song.get().title[0];
|
||||
if (song->title.size() > 0) {
|
||||
char letter = song->title[0];
|
||||
if ('A' <= letter and letter <= 'Z') {
|
||||
categories
|
||||
[std::string(1, letter)]
|
||||
@ -140,21 +144,6 @@ namespace MusicSelect {
|
||||
layout_from_category_map(categories);
|
||||
}
|
||||
|
||||
void Ribbon::test_song_cover_sort() {
|
||||
std::string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
std::map<std::string, std::vector<std::shared_ptr<Panel>>> categories;
|
||||
Toolkit::UniformIntRNG category_size_generator{1, 10};
|
||||
for (auto &&letter : alphabet) {
|
||||
auto category_size = category_size_generator.generate();
|
||||
for (int i = 0; i < category_size; i++) {
|
||||
categories[std::string(1, letter)].push_back(
|
||||
std::make_shared<SongPanel>(m_resources, this->empty_song)
|
||||
);
|
||||
}
|
||||
}
|
||||
layout_from_category_map(categories);
|
||||
}
|
||||
|
||||
|
||||
std::size_t Ribbon::get_layout_column(const std::size_t& button_index) const {
|
||||
return (m_position + (button_index % 4)) % m_layout.size();
|
||||
|
@ -6,7 +6,7 @@
|
||||
#include <SFML/Graphics/Transformable.hpp>
|
||||
|
||||
#include "../../Data/Preferences.hpp"
|
||||
#include "../../Data/SongList.hpp"
|
||||
#include "../../Data/Song.hpp"
|
||||
#include "../../Toolkit/AffineTransform.hpp"
|
||||
#include "../../Toolkit/Debuggable.hpp"
|
||||
#include "../../Toolkit/EasingFunctions.hpp"
|
||||
@ -59,6 +59,5 @@ namespace MusicSelect {
|
||||
std::size_t m_position = 0;
|
||||
mutable std::optional<MoveAnimation> m_move_animation;
|
||||
float m_time_factor = 1.f;
|
||||
Data::Song empty_song;
|
||||
};
|
||||
}
|
@ -22,11 +22,11 @@ namespace MusicSelect {
|
||||
std::cout << "Loaded MusicSelect::SharedResources" << std::endl;
|
||||
}
|
||||
|
||||
std::string SharedResources::get_last_selected_chart() {
|
||||
return get_selected_chart().value_or("BSC");
|
||||
std::string SharedResources::get_last_selected_difficulty() {
|
||||
return get_selected_difficulty().value_or("BSC");
|
||||
}
|
||||
|
||||
std::optional<std::string> SharedResources::get_selected_chart() {
|
||||
std::optional<std::string> SharedResources::get_selected_difficulty() {
|
||||
if (not selected_panel.has_value()) {
|
||||
return {};
|
||||
}
|
||||
@ -34,7 +34,7 @@ namespace MusicSelect {
|
||||
if (not chart_selection.has_value()) {
|
||||
return {};
|
||||
}
|
||||
return chart_selection->chart;
|
||||
return chart_selection->difficulty;
|
||||
}
|
||||
|
||||
std::optional<std::reference_wrapper<const Data::Song>> MusicSelect::SharedResources::get_selected_song() {
|
||||
|
@ -8,7 +8,9 @@
|
||||
|
||||
#include "../../Resources/TextureCache.hpp"
|
||||
#include "../../Data/Preferences.hpp"
|
||||
#include "../../Data/SongList.hpp"
|
||||
#include "../../Data/Song.hpp"
|
||||
|
||||
#include "DensityGraph.hpp"
|
||||
|
||||
namespace MusicSelect {
|
||||
|
||||
@ -32,9 +34,11 @@ namespace MusicSelect {
|
||||
|
||||
sf::Font noto_sans_medium;
|
||||
|
||||
Data::DensityGraphCache density_graphs;
|
||||
|
||||
std::optional<TimedSelectedPanel> selected_panel;
|
||||
std::string get_last_selected_chart();
|
||||
std::optional<std::string> get_selected_chart();
|
||||
std::string get_last_selected_difficulty();
|
||||
std::optional<std::string> get_selected_difficulty();
|
||||
std::optional<std::reference_wrapper<const Data::Song>> get_selected_song();
|
||||
|
||||
sf::Color BSC_color = sf::Color{34,216,92};
|
||||
|
@ -52,9 +52,10 @@ namespace MusicSelect {
|
||||
target.draw(cover, states);
|
||||
}
|
||||
|
||||
SongInfo::SongInfo(SharedResources& resources) : HoldsSharedResources(resources), m_big_cover(resources) {
|
||||
|
||||
}
|
||||
SongInfo::SongInfo(SharedResources& resources) :
|
||||
HoldsSharedResources(resources),
|
||||
m_big_cover(resources)
|
||||
{}
|
||||
|
||||
void SongInfo::draw(sf::RenderTarget& target, sf::RenderStates states) const {
|
||||
states.transform *= getTransform();
|
||||
@ -142,7 +143,7 @@ namespace MusicSelect {
|
||||
target.draw(level_label, states);
|
||||
|
||||
sf::Text level_number_label{
|
||||
std::to_string(selected_chart->song.chart_levels.at(selected_chart->chart)),
|
||||
std::to_string(selected_chart->song.chart_levels.at(selected_chart->difficulty)),
|
||||
m_resources.noto_sans_medium,
|
||||
static_cast<unsigned int>(130.f/768.f*get_screen_width())
|
||||
};
|
||||
@ -151,23 +152,23 @@ namespace MusicSelect {
|
||||
level_number_label.setFillColor(sf::Color::White);
|
||||
target.draw(level_number_label, states);
|
||||
|
||||
std::string full_chart_name = selected_chart->chart;
|
||||
if (selected_chart->chart == "BSC") {
|
||||
full_chart_name = "BASIC";
|
||||
} else if (selected_chart->chart == "ADV") {
|
||||
full_chart_name = "ADVANCED";
|
||||
} else if (selected_chart->chart == "EXT") {
|
||||
full_chart_name = "EXTREME";
|
||||
std::string full_difficulty = selected_chart->difficulty;
|
||||
if (selected_chart->difficulty == "BSC") {
|
||||
full_difficulty = "BASIC";
|
||||
} else if (selected_chart->difficulty == "ADV") {
|
||||
full_difficulty = "ADVANCED";
|
||||
} else if (selected_chart->difficulty == "EXT") {
|
||||
full_difficulty = "EXTREME";
|
||||
}
|
||||
|
||||
sf::Text chart_label{
|
||||
full_chart_name,
|
||||
full_difficulty,
|
||||
m_resources.noto_sans_medium,
|
||||
static_cast<unsigned int>(20.f/768.f*get_screen_width())
|
||||
};
|
||||
Toolkit::set_origin_normalized_no_position(chart_label, 0.5f, 0.f);
|
||||
chart_label.setPosition(get_big_level_x(), get_big_level_y()+(145.f/768.f*get_screen_width()));
|
||||
chart_label.setFillColor(m_resources.get_chart_color(selected_chart->chart));
|
||||
chart_label.setFillColor(m_resources.get_chart_color(selected_chart->difficulty));
|
||||
target.draw(chart_label, states);
|
||||
}
|
||||
|
||||
@ -185,13 +186,13 @@ namespace MusicSelect {
|
||||
auto dif_badge_y = 40.f/768.f*get_screen_width();
|
||||
auto dif_badge_step = 3.f*dif_badge_radius;
|
||||
std::size_t dif_index = 0;
|
||||
for (auto &&[chart_name, level] : selected_chart->song.chart_levels) {
|
||||
for (auto &&[difficulty, level] : selected_chart->song.chart_levels) {
|
||||
sf::CircleShape dif_badge{dif_badge_radius};
|
||||
Toolkit::set_origin_normalized(dif_badge, 0.5f, 0.5f);
|
||||
dif_badge.setFillColor(m_resources.get_chart_color(chart_name));
|
||||
dif_badge.setFillColor(m_resources.get_chart_color(difficulty));
|
||||
dif_badge.setPosition(dif_badge_x+dif_index*dif_badge_step, dif_badge_y);
|
||||
target.draw(dif_badge, states);
|
||||
if (chart_name == selected_chart->chart) {
|
||||
if (difficulty == selected_chart->difficulty) {
|
||||
sf::CircleShape select_triangle(dif_badge_radius, 3);
|
||||
Toolkit::set_origin_normalized(select_triangle, 0.5f, 0.5f);
|
||||
select_triangle.rotate(180.f);
|
||||
|
Loading…
Reference in New Issue
Block a user